7575 :aria-label =" `Reference Name Input for ${parameter.name}`"
7676 type =" text"
7777 class =" c-input--md"
78- @change =" updateParameters"
78+ @input =" updateParameters"
7979 />
8080 <div v-else class =" --em" >{{ parameter.name }}</div >
8181 <span class =" c-test-datum__string" >=</span >
135135 :aria-label =" `Sample Size for ${parameter.name}`"
136136 type =" number"
137137 class =" c-input--sm c-comps__value"
138- @change =" updateParameters"
138+ @input =" updateParameters"
139139 />
140140 </div >
141141
155155 :aria-label =" `Reference Test Value for ${parameter.name}`"
156156 type =" text"
157157 class =" c-input--md c-comps__value"
158- @change =" updateTestValue(parameter)"
158+ @input =" updateTestValue(parameter)"
159159 />
160160 </div >
161161 </div >
175175 v-model =" expression"
176176 class =" c-comps__expression-value"
177177 placeholder =" Enter an expression"
178- @change =" updateExpression"
178+ @input =" updateExpression"
179+ @blur =" handleExpressionBlur"
179180 ></textarea >
180181 <div v-else >
181182 <div class =" c-comps__expression-value" aria-label =" Expression" >
184185 </div >
185186 <span
186187 v-if =" expression && expressionOutput"
187- class =" icon-alert-triangle c-comps__expression-msg --bad"
188+ :class =" [
189+ 'c-comps__expression-msg',
190+ expressionBlurResult === 'invalid' ? '--bad-strong' : '--bad'
191+ ]"
188192 >
189- Invalid: {{ expressionOutput }}
193+ {{ expressionOutput }}
190194 </span >
191195 <span
192196 v-else-if =" expression && !expressionOutput && isEditing"
193- class =" c-comps__expression-msg --good"
197+ :class =" [
198+ 'c-comps__expression-msg',
199+ expressionBlurResult === 'valid' ? '--good-strong' : '--good'
200+ ]"
194201 >
195202 Expression valid
196203 </span >
199206</template >
200207
201208<script setup>
202- import { evaluate } from ' mathjs' ;
209+ import { all , create } from ' mathjs' ;
203210import { inject , onBeforeMount , onBeforeUnmount , ref , watch } from ' vue' ;
204211
205212import ObjectPathString from ' ../../../ui/components/ObjectPathString.vue' ;
206213import CompsManager from ' ../CompsManager' ;
207214
215+ const config = {};
216+ const math = create (all, config);
217+
208218const openmct = inject (' openmct' );
209219const domainObject = inject (' domainObject' );
210220const compsManagerPool = inject (' compsManagerPool' );
@@ -215,6 +225,7 @@ const testDataApplied = ref(false);
215225const parameters = ref (null );
216226const expression = ref (null );
217227const expressionOutput = ref (null );
228+ const expressionBlurResult = ref (null ); // 'valid' | 'invalid' | null
218229const outputFormat = ref (null );
219230const parameterValueOptionsMap = ref ({});
220231const telemetryObjectsMap = ref ({});
@@ -295,6 +306,11 @@ onBeforeMount(async () => {
295306 expression .value = compsManager .getExpression ();
296307 outputFormat .value = compsManager .getOutputFormat ();
297308 applyTestData ();
309+
310+ // Set initial blur state if expression is invalid on load
311+ if (expressionOutput .value ) {
312+ expressionBlurResult .value = ' invalid' ;
313+ }
298314});
299315
300316onBeforeUnmount (() => {
@@ -387,6 +403,7 @@ function toggleTestData() {
387403}
388404
389405function updateExpression () {
406+ expressionBlurResult .value = null ; // Reset blur evaluation when typing
390407 openmct .objects .mutate (domainObject, ` configuration.comps.expression` , expression .value );
391408 compsManager .setDomainObject (domainObject);
392409 applyTestData ();
@@ -428,7 +445,7 @@ function applyTestData() {
428445 }
429446
430447 try {
431- const testOutput = evaluate (expression .value , scope);
448+ const testOutput = validateAndEvaluateExpression (expression .value , scope);
432449 const formattedData = getValueFormatter ().format (testOutput);
433450 currentTestOutput .value = formattedData;
434451 expressionOutput .value = null ;
@@ -438,6 +455,33 @@ function applyTestData() {
438455 }
439456}
440457
458+ function validateAndEvaluateExpression (evalExpression , scope ) {
459+ const parsed = math .parse (evalExpression);
460+ const dependencies = parsed
461+ .filter ((node ) => node .isSymbolNode || node .isFunctionNode )
462+ .map ((node ) => node .name );
463+
464+ const uniqueDeps = Array .from (new Set (dependencies));
465+
466+ // check if any variables are predefined mathjs functions or constants
467+ // then check if any variables are defined in the scope
468+ // anything else is considered undefined and will throw an error
469+ const undefinedVars = uniqueDeps .filter ((dep ) => {
470+ if (math[dep] !== undefined ) {
471+ return false ;
472+ }
473+ if (Object .prototype .hasOwnProperty .call (scope, dep)) {
474+ return false ;
475+ }
476+ return true ;
477+ });
478+
479+ if (undefinedVars .length > 0 ) {
480+ throw new Error (` Undefined variables: ${ undefinedVars .join (' , ' )} ` );
481+ }
482+ return math .evaluate (evalExpression, scope);
483+ }
484+
441485function telemetryProcessor (data ) {
442486 if (testDataApplied .value ) {
443487 return ;
@@ -463,4 +507,17 @@ function reload() {
463507function clearData () {
464508 currentCompOutput .value = null ;
465509}
510+
511+ function handleExpressionBlur () {
512+ applyTestData ();
513+
514+ // Set blur result based on expressionOutput after evaluation
515+ if (expressionOutput .value ) {
516+ expressionBlurResult .value = ' invalid' ;
517+ } else if (expression .value && ! expressionOutput .value ) {
518+ expressionBlurResult .value = ' valid' ;
519+ } else {
520+ expressionBlurResult .value = null ;
521+ }
522+ }
466523< / script>
0 commit comments