11<template >
22 <div class =" filled-textfield" :class =" { focus: isFocus }" >
3- <label :class =" { lifted: !isEmpty }" >{{ props.label }}</label >
3+ <label :class =" { lifted: !isEmpty }" class =" function-label" >
4+ <component v-for =" item in labelContent" :is =" item" ></component >
5+ </label >
46 <input
57 @focus =" isFocus = true"
68 @blur =" isFocus = false"
1113 </div >
1214</template >
1315
14- <script setup lang="ts">
16+ <script setup lang="tsx">
17+ /* @jsxImportSource vue */
1518import { computed , ref , watch } from " vue" ;
1619
1720const value = defineModel <string >({ required: true });
1821const props = defineProps <{
1922 label: string ;
23+ identifiers? : string [];
2024}>();
2125const isFocus = ref (false );
2226const isEmpty = computed (() => value .value === " " );
@@ -32,6 +36,26 @@ function refreshInput() {
3236 inputRef .value .setSelectionRange (selectionStart , selectionEnd );
3337}
3438watch (value , refreshInput );
39+
40+ import { tokenize } from " ./functionTokenize" ;
41+ const labelContent = computed (() =>
42+ isEmpty .value
43+ ? [<span >{ props .label } </span >]
44+ : tokenize (value .value , props .identifiers || [" x" ]).map ((token ) => (
45+ <span
46+ class = { [
47+ token .type ,
48+ token .raw ,
49+ " function-label-item" ,
50+ token .level ? " level-" + Math .min (token .level , 5 ) : undefined ,
51+ token .err ? " error" : undefined ,
52+ token .sqrtLevel ? " under-sqrt" : undefined ,
53+ ]}
54+ >
55+ <span class = " inner" >{ token .raw } </span >
56+ </span >
57+ ))
58+ );
3559 </script >
3660
3761<style lang="scss">
@@ -46,6 +70,7 @@ watch(value, refreshInput);
4670 padding : 0 ;
4771 display : flex ;
4872 font-family : var (--font-math );
73+ position : relative ;
4974 & .focus {
5075 background-color : var (--s-color-surface-container-highest );
5176 border-bottom-color : var (--s-color-primary );
@@ -60,24 +85,76 @@ watch(value, refreshInput);
6085 width : 0 ;
6186 flex-grow : 1 ;
6287 caret-color : var (--s-color-primary );
63- line-height : 1.2 ;
6488 z-index : 1 ;
65- color : var ( --s-color ) ;
89+ color : transparent ;
6690 }
6791 label {
92+ display : block ;
6893 color : var (--s-color-outline );
6994 position : absolute ;
70- line-height : 1.2 ;
95+ white-space : pre ;
96+ max-width : 100% ;
97+ box-sizing : border-box ;
7198 }
7299 input ,
73100 label {
74- padding : 0.4em 0.45em 0.3em 0.45em ;
101+ padding : 0.2em 0.45em 0.1em 0.45em ;
102+ line-height : 1.6 ;
103+ overflow : hidden ;
75104 }
76105 & .styled label {
77106 transform : translateY (-0.05em );
78107 }
79108 label .lifted {
80- opacity : 0 ;
109+ color : var (--s-color );
110+ }
111+ }
112+
113+ .function-label {
114+ .function-label-item ,
115+ .function-label-item .inner {
116+ display : inline-block ;
117+ }
118+ .identifier {
119+ color : var (--s-color-primary );
120+ }
121+ .operator {
122+ color : var (--s-color-secondary );
123+ }
124+ .bracket {
125+ color : var (--s-color-secondary );
126+ }
127+ @for $i from 1 through 5 {
128+ .bracket.level-#{$i } .inner {
129+ transform : scaleY (#{0.8 + $i * 0.2 } );
130+ }
131+ }
132+ .unknown {
133+ color : var (--s-color-error );
134+ }
135+ .function {
136+ color : var (--s-color-tertiary );
137+ }
138+ .error .inner {
139+ text-decoration : underline var (--s-color-error );
140+ text-decoration-thickness : 0.05em ;
141+ text-underline-offset : 0.1em ;
142+ }
143+ .sqrt {
144+ transform : scaleY (1.3 ) translateY (-1px );
145+ }
146+ .under-sqrt {
147+ background-image : linear-gradient (
148+ to bottom ,
149+ transparent 0.1em ,
150+ var (--s-color-tertiary ) 0.1em ,
151+ var (--s-color-tertiary ) 0.14em ,
152+ transparent 0.14em ,
153+ transparent 100%
154+ );
155+ & .sqrt {
156+ transform : none !important ;
157+ }
81158 }
82159}
83160 </style >
0 commit comments