@formality-ui/core 0.0.0 → 0.2.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/README.md +103 -93
- package/dist/index.cjs +245 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +111 -16
- package/dist/index.d.ts +111 -16
- package/dist/index.js +245 -26
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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,
|
|
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(
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
429
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
962
|
+
console.warn(
|
|
963
|
+
`Formatter "${formatterSpec}" not found in formatters config`
|
|
964
|
+
);
|
|
746
965
|
}
|
|
747
966
|
return value;
|
|
748
967
|
}
|