@formality-ui/core 0.0.0 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -31,7 +31,8 @@ var FIELD_STATE_PROPERTIES = /* @__PURE__ */ new Set([
31
31
  "isDirty",
32
32
  "isValidating",
33
33
  "error",
34
- "invalid"
34
+ "invalid",
35
+ "disabled"
35
36
  ]);
36
37
  var FIELD_PROXY_MARKER = /* @__PURE__ */ Symbol.for("formality.fieldProxy");
37
38
  var FIELD_PROXY_VALUE = /* @__PURE__ */ Symbol.for("formality.fieldProxyValue");
@@ -120,6 +121,13 @@ function buildEvaluationContext(fieldValues, record, props, fieldStates) {
120
121
  }
121
122
 
122
123
  // src/expression/evaluate.ts
124
+ jsep.addUnaryOp("typeof");
125
+ function isSafeNumber(value) {
126
+ if (value === null || value === void 0) {
127
+ return false;
128
+ }
129
+ return typeof value === "number" && !Number.isNaN(value) && Number.isFinite(value);
130
+ }
123
131
  function getProperty(obj, key) {
124
132
  if (obj === null || obj === void 0) {
125
133
  return void 0;
@@ -168,17 +176,84 @@ function evaluateNode(node, context) {
168
176
  const right = evaluateNode(binaryNode.right, context);
169
177
  const leftValue = unwrapFieldProxy(left);
170
178
  const rightValue = unwrapFieldProxy(right);
179
+ if (binaryNode.operator === "+") {
180
+ const leftIsString = typeof leftValue === "string";
181
+ const rightIsString = typeof rightValue === "string";
182
+ const leftIsArray = Array.isArray(leftValue);
183
+ const rightIsArray = Array.isArray(rightValue);
184
+ if (leftIsString || rightIsString || leftIsArray || rightIsArray) {
185
+ const leftStr = leftIsArray ? leftValue.join(",") : String(leftValue ?? "");
186
+ const rightStr = rightIsArray ? rightValue.join(",") : String(rightValue ?? "");
187
+ return leftStr + rightStr;
188
+ }
189
+ if (!isSafeNumber(leftValue) || !isSafeNumber(rightValue)) {
190
+ if (process.env.NODE_ENV !== "production") {
191
+ console.warn(
192
+ `[Formality Expression] Type error: Invalid operands for + (null/undefined not allowed): left=${typeof leftValue}, right=${typeof rightValue}`
193
+ );
194
+ }
195
+ return void 0;
196
+ }
197
+ const result = leftValue + rightValue;
198
+ if (!Number.isFinite(result)) {
199
+ if (process.env.NODE_ENV !== "production") {
200
+ console.warn(
201
+ `[Formality Expression] Arithmetic overflow: ${leftValue} + ${rightValue} produced ${result}`
202
+ );
203
+ }
204
+ return void 0;
205
+ }
206
+ return result;
207
+ }
208
+ const arithmeticOps = ["-", "*", "/", "%"];
209
+ if (arithmeticOps.includes(binaryNode.operator)) {
210
+ if (!isSafeNumber(leftValue) || !isSafeNumber(rightValue)) {
211
+ if (process.env.NODE_ENV !== "production") {
212
+ console.warn(
213
+ `[Formality Expression] Type error: Invalid operands for ${binaryNode.operator} (null/undefined not allowed): left=${typeof leftValue}, right=${typeof rightValue}`
214
+ );
215
+ }
216
+ return void 0;
217
+ }
218
+ const l = leftValue;
219
+ const r = rightValue;
220
+ if (binaryNode.operator === "/" || binaryNode.operator === "%") {
221
+ if (r === 0) {
222
+ if (process.env.NODE_ENV !== "production") {
223
+ const opName = binaryNode.operator === "/" ? "Division" : "Modulo";
224
+ console.warn(`[Formality Expression] ${opName} by zero`);
225
+ }
226
+ return void 0;
227
+ }
228
+ }
229
+ let result;
230
+ switch (binaryNode.operator) {
231
+ case "-":
232
+ result = l - r;
233
+ break;
234
+ case "*":
235
+ result = l * r;
236
+ break;
237
+ case "/":
238
+ result = l / r;
239
+ break;
240
+ case "%":
241
+ result = l % r;
242
+ break;
243
+ default:
244
+ throw new Error(`Unknown operator: ${binaryNode.operator}`);
245
+ }
246
+ if (!Number.isFinite(result)) {
247
+ if (process.env.NODE_ENV !== "production") {
248
+ console.warn(
249
+ `[Formality Expression] Arithmetic overflow: ${l} ${binaryNode.operator} ${r} produced ${result}`
250
+ );
251
+ }
252
+ return void 0;
253
+ }
254
+ return result;
255
+ }
171
256
  switch (binaryNode.operator) {
172
- case "+":
173
- return leftValue + rightValue;
174
- case "-":
175
- return leftValue - rightValue;
176
- case "*":
177
- return leftValue * rightValue;
178
- case "/":
179
- return leftValue / rightValue;
180
- case "%":
181
- return leftValue % rightValue;
182
257
  case "===":
183
258
  return leftValue === rightValue;
184
259
  case "!==":
@@ -314,15 +389,43 @@ function inferFieldsFromExpression(expr) {
314
389
  IDENTIFIER_REGEX.lastIndex = 0;
315
390
  while ((match = IDENTIFIER_REGEX.exec(expr)) !== null) {
316
391
  const identifier = match[1];
392
+ const matchIndex = match.index;
393
+ let inString = false;
394
+ let stringChar = "";
395
+ let escapeNext = false;
396
+ for (let i = 0; i < matchIndex; i++) {
397
+ const char = expr[i];
398
+ if (escapeNext) {
399
+ escapeNext = false;
400
+ continue;
401
+ }
402
+ if (char === "\\") {
403
+ escapeNext = true;
404
+ continue;
405
+ }
406
+ if (!inString && (char === '"' || char === "'" || char === "`")) {
407
+ inString = true;
408
+ stringChar = char;
409
+ continue;
410
+ }
411
+ if (inString && char === stringChar) {
412
+ inString = false;
413
+ stringChar = "";
414
+ continue;
415
+ }
416
+ }
417
+ if (inString) {
418
+ continue;
419
+ }
317
420
  if (KEYWORD_SET.has(identifier)) {
318
421
  continue;
319
422
  }
320
- const beforeMatch = expr.slice(0, match.index);
423
+ const beforeMatch = expr.slice(0, matchIndex);
321
424
  const lastNonWhitespace = beforeMatch.trimEnd().slice(-1);
322
425
  if (lastNonWhitespace === ".") {
323
426
  continue;
324
427
  }
325
- const afterMatch = expr.slice(match.index + identifier.length);
428
+ const afterMatch = expr.slice(matchIndex + identifier.length);
326
429
  if (afterMatch.startsWith(".") && QUALIFIED_PREFIX_SET.has(identifier)) {
327
430
  continue;
328
431
  }
@@ -355,9 +458,72 @@ function inferFieldsFromDescriptor(descriptor) {
355
458
  }
356
459
 
357
460
  // src/conditions/evaluate.ts
358
- function evaluateConditionMatch(condition, context, fieldValues) {
461
+ function evaluateFieldMatcher(fieldName, matcher, fieldValues, fieldStates) {
462
+ const fieldValue = fieldValues[fieldName];
463
+ const fieldState = fieldStates?.[fieldName];
464
+ if (typeof matcher !== "object" || matcher === null) {
465
+ return fieldValue === matcher;
466
+ }
467
+ if (matcher.isValid !== void 0) {
468
+ const isFieldValid = fieldState ? !fieldState.invalid && !fieldState.error : true;
469
+ if (matcher.isValid !== isFieldValid) {
470
+ return false;
471
+ }
472
+ }
473
+ if (matcher.isDisabled !== void 0) {
474
+ const isFieldDisabled = fieldState?.disabled ?? false;
475
+ if (matcher.isDisabled !== isFieldDisabled) {
476
+ return false;
477
+ }
478
+ }
479
+ if (matcher.is !== void 0) {
480
+ if (fieldValue !== matcher.is) {
481
+ return false;
482
+ }
483
+ }
484
+ const truthyCheck = matcher.truthy ?? matcher.isTruthy;
485
+ if (truthyCheck !== void 0) {
486
+ const isTruthy = Boolean(fieldValue);
487
+ if (truthyCheck !== isTruthy) {
488
+ return false;
489
+ }
490
+ }
491
+ const hasAnyMatcher = matcher.is !== void 0 || matcher.truthy !== void 0 || matcher.isTruthy !== void 0 || matcher.isValid !== void 0 || matcher.isDisabled !== void 0;
492
+ if (!hasAnyMatcher) {
493
+ return Boolean(fieldValue);
494
+ }
495
+ return true;
496
+ }
497
+ function isStateFieldMatcher(value) {
498
+ if (typeof value !== "object" || value === null) {
499
+ return false;
500
+ }
501
+ return "isValid" in value || "isDisabled" in value;
502
+ }
503
+ function evaluateConditionMatch(condition, context, fieldValues, fieldStates) {
504
+ if (condition.when !== void 0 && typeof condition.when === "object") {
505
+ for (const [fieldName, matcher] of Object.entries(condition.when)) {
506
+ if (!evaluateFieldMatcher(fieldName, matcher, fieldValues, fieldStates)) {
507
+ return false;
508
+ }
509
+ }
510
+ if (condition.isDisabled !== void 0) {
511
+ if (!fieldStates) {
512
+ return false;
513
+ }
514
+ const fieldsWithStateMatchers = Object.entries(condition.when).filter(([, matcher]) => isStateFieldMatcher(matcher)).map(([fieldName]) => fieldName);
515
+ const fieldsToCheck = fieldsWithStateMatchers.length > 0 ? fieldsWithStateMatchers : Object.keys(condition.when);
516
+ const allFieldsMatchDisabled = fieldsToCheck.every(
517
+ (fieldName) => fieldStates[fieldName]?.disabled === condition.isDisabled
518
+ );
519
+ if (!allFieldsMatchDisabled) {
520
+ return false;
521
+ }
522
+ }
523
+ return true;
524
+ }
359
525
  let triggerValue;
360
- if (condition.when !== void 0) {
526
+ if (typeof condition.when === "string") {
361
527
  triggerValue = fieldValues[condition.when];
362
528
  } else if (condition.selectWhen !== void 0) {
363
529
  if (typeof condition.selectWhen === "string") {
@@ -370,6 +536,21 @@ function evaluateConditionMatch(condition, context, fieldValues) {
370
536
  } else {
371
537
  return false;
372
538
  }
539
+ if (typeof condition.when === "string" && fieldStates) {
540
+ const fieldState = fieldStates[condition.when];
541
+ if (condition.isValid !== void 0) {
542
+ const isFieldValid = fieldState ? !fieldState.invalid && !fieldState.error : true;
543
+ if (condition.isValid !== isFieldValid) {
544
+ return false;
545
+ }
546
+ }
547
+ if (condition.isDisabled !== void 0) {
548
+ const isFieldDisabled = fieldState?.disabled ?? false;
549
+ if (condition.isDisabled !== isFieldDisabled) {
550
+ return false;
551
+ }
552
+ }
553
+ }
373
554
  if (condition.is !== void 0) {
374
555
  return triggerValue === condition.is;
375
556
  }
@@ -377,11 +558,19 @@ function evaluateConditionMatch(condition, context, fieldValues) {
377
558
  const isTruthy = Boolean(triggerValue);
378
559
  return condition.truthy ? isTruthy : !isTruthy;
379
560
  }
561
+ if (condition.isValid !== void 0 || condition.isDisabled !== void 0) {
562
+ return true;
563
+ }
380
564
  return Boolean(triggerValue);
381
565
  }
382
566
  function evaluateConditions(input) {
383
567
  const { conditions, fieldValues, fieldStates, record, props } = input;
384
- const context = buildEvaluationContext(fieldValues, record, props, fieldStates);
568
+ const context = buildEvaluationContext(
569
+ fieldValues,
570
+ record,
571
+ props,
572
+ fieldStates
573
+ );
385
574
  let disabled;
386
575
  let visible;
387
576
  let setValue;
@@ -389,7 +578,12 @@ function evaluateConditions(input) {
389
578
  let hasVisibleCondition = false;
390
579
  let hasSetCondition = false;
391
580
  for (const condition of conditions) {
392
- const isMatched = evaluateConditionMatch(condition, context, fieldValues);
581
+ const isMatched = evaluateConditionMatch(
582
+ condition,
583
+ context,
584
+ fieldValues,
585
+ fieldStates
586
+ );
393
587
  if (!isMatched) {
394
588
  continue;
395
589
  }
@@ -411,7 +605,9 @@ function evaluateConditions(input) {
411
605
  } else if (typeof condition.selectSet === "function") {
412
606
  setValue = condition.selectSet;
413
607
  } else {
414
- setValue = unwrapFieldProxy(evaluateDescriptor(condition.selectSet, context));
608
+ setValue = unwrapFieldProxy(
609
+ evaluateDescriptor(condition.selectSet, context)
610
+ );
415
611
  }
416
612
  }
417
613
  }
@@ -424,9 +620,14 @@ function evaluateConditions(input) {
424
620
  hasSetCondition
425
621
  };
426
622
  }
427
- function conditionMatches(condition, fieldValues, record, props) {
428
- const context = buildEvaluationContext(fieldValues, record, props);
429
- return evaluateConditionMatch(condition, context, fieldValues);
623
+ function conditionMatches(condition, fieldValues, record, props, fieldStates) {
624
+ const context = buildEvaluationContext(
625
+ fieldValues,
626
+ record,
627
+ props,
628
+ fieldStates
629
+ );
630
+ return evaluateConditionMatch(condition, context, fieldValues, fieldStates);
430
631
  }
431
632
  function mergeConditionResults(results) {
432
633
  let disabled;
@@ -462,7 +663,11 @@ function inferFieldsFromConditions(conditions) {
462
663
  const fields = [];
463
664
  for (const condition of conditions) {
464
665
  if (condition.when !== void 0) {
465
- fields.push(condition.when);
666
+ if (typeof condition.when === "string") {
667
+ fields.push(condition.when);
668
+ } else {
669
+ fields.push(...Object.keys(condition.when));
670
+ }
466
671
  }
467
672
  if (condition.selectWhen !== void 0) {
468
673
  fields.push(...inferFieldsFromDescriptor(condition.selectWhen));
@@ -498,7 +703,12 @@ function resolveNamedValidator(name, validators) {
498
703
  async function runValidator(spec, value, formValues, namedValidators) {
499
704
  if (Array.isArray(spec)) {
500
705
  for (const item of spec) {
501
- const result = await runValidator(item, value, formValues, namedValidators);
706
+ const result = await runValidator(
707
+ item,
708
+ value,
709
+ formValues,
710
+ namedValidators
711
+ );
502
712
  if (!isValid(result)) {
503
713
  return result;
504
714
  }
@@ -507,7 +717,9 @@ async function runValidator(spec, value, formValues, namedValidators) {
507
717
  }
508
718
  if (typeof spec === "string") {
509
719
  if (!namedValidators) {
510
- console.warn(`Named validator "${spec}" requested but no validators provided`);
720
+ console.warn(
721
+ `Named validator "${spec}" requested but no validators provided`
722
+ );
511
723
  return true;
512
724
  }
513
725
  const validator = resolveNamedValidator(spec, namedValidators);
@@ -560,7 +772,12 @@ function isValid(result) {
560
772
  function composeValidators(validators, namedValidators) {
561
773
  return async (value, formValues) => {
562
774
  for (const spec of validators) {
563
- const result = await runValidator(spec, value, formValues, namedValidators);
775
+ const result = await runValidator(
776
+ spec,
777
+ value,
778
+ formValues,
779
+ namedValidators
780
+ );
564
781
  if (!isValid(result)) {
565
782
  return result;
566
783
  }
@@ -742,7 +959,9 @@ function format(value, formatterSpec, namedFormatters) {
742
959
  const formatter = namedFormatters?.[formatterSpec];
743
960
  if (!formatter) {
744
961
  if (process.env.NODE_ENV !== "production") {
745
- console.warn(`Formatter "${formatterSpec}" not found in formatters config`);
962
+ console.warn(
963
+ `Formatter "${formatterSpec}" not found in formatters config`
964
+ );
746
965
  }
747
966
  return value;
748
967
  }