@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/README.md CHANGED
@@ -34,23 +34,23 @@ This package has no React, Vue, or Svelte dependencies. It exports pure function
34
34
  Evaluate dynamic expressions against form state:
35
35
 
36
36
  ```typescript
37
- import { evaluate, buildEvaluationContext } from '@formality-ui/core';
37
+ import { evaluate, buildEvaluationContext } from "@formality-ui/core";
38
38
 
39
39
  const context = buildEvaluationContext({
40
40
  fields: {
41
- client: { value: { id: 5, name: 'Acme' } },
41
+ client: { value: { id: 5, name: "Acme" } },
42
42
  signed: { value: true },
43
43
  },
44
- record: { originalName: 'Old Name' },
44
+ record: { originalName: "Old Name" },
45
45
  });
46
46
 
47
47
  // Simple field access
48
- evaluate('client', context); // { id: 5, name: 'Acme' }
49
- evaluate('client.id', context); // 5
48
+ evaluate("client", context); // { id: 5, name: 'Acme' }
49
+ evaluate("client.id", context); // 5
50
50
 
51
51
  // Expressions
52
- evaluate('client && signed', context); // true
53
- evaluate('client.id > 3', context); // true
52
+ evaluate("client && signed", context); // true
53
+ evaluate("client.id > 3", context); // true
54
54
  ```
55
55
 
56
56
  ### Condition Evaluation
@@ -58,18 +58,18 @@ evaluate('client.id > 3', context); // true
58
58
  Evaluate conditions for field visibility and disabled state:
59
59
 
60
60
  ```typescript
61
- import { evaluateConditions } from '@formality-ui/core';
61
+ import { evaluateConditions } from "@formality-ui/core";
62
62
 
63
63
  const result = evaluateConditions({
64
64
  conditions: [
65
- { when: 'signed', is: false, disabled: true },
66
- { when: 'archived', truthy: true, visible: false },
65
+ { when: "signed", is: false, disabled: true },
66
+ { when: "archived", truthy: true, visible: false },
67
67
  ],
68
68
  fieldValues: { signed: false, archived: false },
69
69
  });
70
70
 
71
- result.disabled; // true
72
- result.visible; // true
71
+ result.disabled; // true
72
+ result.visible; // true
73
73
  ```
74
74
 
75
75
  ### Validation
@@ -77,10 +77,15 @@ result.visible; // true
77
77
  Compose and run validators:
78
78
 
79
79
  ```typescript
80
- import { runValidator, composeValidators, required, minLength } from '@formality-ui/core';
80
+ import {
81
+ runValidator,
82
+ composeValidators,
83
+ required,
84
+ minLength,
85
+ } from "@formality-ui/core";
81
86
 
82
87
  const validator = composeValidators([required(), minLength(3)]);
83
- const result = await runValidator(validator, 'ab', {});
88
+ const result = await runValidator(validator, "ab", {});
84
89
  // { type: 'minLength', message: 'Minimum 3 characters required' }
85
90
  ```
86
91
 
@@ -89,13 +94,18 @@ const result = await runValidator(validator, 'ab', {});
89
94
  Parse and format values:
90
95
 
91
96
  ```typescript
92
- import { parse, format, createFloatParser, createFloatFormatter } from '@formality-ui/core';
97
+ import {
98
+ parse,
99
+ format,
100
+ createFloatParser,
101
+ createFloatFormatter,
102
+ } from "@formality-ui/core";
93
103
 
94
104
  const parser = createFloatParser();
95
105
  const formatter = createFloatFormatter(2);
96
106
 
97
- parse('42.567', parser); // 42.567 (number)
98
- format(42.567, formatter); // '42.57' (string, 2 decimals)
107
+ parse("42.567", parser); // 42.567 (number)
108
+ format(42.567, formatter); // '42.57' (string, 2 decimals)
99
109
  ```
100
110
 
101
111
  ### Label Resolution
@@ -103,100 +113,100 @@ format(42.567, formatter); // '42.57' (string, 2 decimals)
103
113
  Auto-generate human-readable labels from field names:
104
114
 
105
115
  ```typescript
106
- import { humanizeLabel, resolveLabel } from '@formality-ui/core';
116
+ import { humanizeLabel, resolveLabel } from "@formality-ui/core";
107
117
 
108
- humanizeLabel('clientContact'); // 'Client Contact'
109
- humanizeLabel('minGrossMargin'); // 'Min Gross Margin'
110
- humanizeLabel('userId'); // 'User Id'
118
+ humanizeLabel("clientContact"); // 'Client Contact'
119
+ humanizeLabel("minGrossMargin"); // 'Min Gross Margin'
120
+ humanizeLabel("userId"); // 'User Id'
111
121
  ```
112
122
 
113
123
  ## API Reference
114
124
 
115
125
  ### Expression Engine
116
126
 
117
- | Function | Description |
118
- |----------|-------------|
119
- | `evaluate(expr, context)` | Evaluate an expression string against context |
120
- | `evaluateDescriptor(descriptor, context)` | Evaluate a SelectValue descriptor |
121
- | `buildEvaluationContext(fields, record, props)` | Build evaluation context |
122
- | `buildFormContext(fields, record)` | Build form-level context |
123
- | `buildFieldContext(name, fields, record, props)` | Build field-level context |
124
- | `inferFieldsFromExpression(expr)` | Extract field dependencies from expression |
125
- | `inferFieldsFromDescriptor(descriptor)` | Extract field dependencies from descriptor |
126
- | `clearExpressionCache()` | Clear the expression parser cache |
127
+ | Function | Description |
128
+ | ------------------------------------------------ | --------------------------------------------- |
129
+ | `evaluate(expr, context)` | Evaluate an expression string against context |
130
+ | `evaluateDescriptor(descriptor, context)` | Evaluate a SelectValue descriptor |
131
+ | `buildEvaluationContext(fields, record, props)` | Build evaluation context |
132
+ | `buildFormContext(fields, record)` | Build form-level context |
133
+ | `buildFieldContext(name, fields, record, props)` | Build field-level context |
134
+ | `inferFieldsFromExpression(expr)` | Extract field dependencies from expression |
135
+ | `inferFieldsFromDescriptor(descriptor)` | Extract field dependencies from descriptor |
136
+ | `clearExpressionCache()` | Clear the expression parser cache |
127
137
 
128
138
  ### Condition Evaluation
129
139
 
130
- | Function | Description |
131
- |----------|-------------|
132
- | `evaluateConditions(input)` | Evaluate conditions and return disabled/visible/setValue |
133
- | `conditionMatches(condition, context)` | Check if a single condition matches |
134
- | `mergeConditionResults(results)` | Merge multiple condition results |
135
- | `inferFieldsFromConditions(conditions)` | Extract field dependencies from conditions |
140
+ | Function | Description |
141
+ | --------------------------------------- | -------------------------------------------------------- |
142
+ | `evaluateConditions(input)` | Evaluate conditions and return disabled/visible/setValue |
143
+ | `conditionMatches(condition, context)` | Check if a single condition matches |
144
+ | `mergeConditionResults(results)` | Merge multiple condition results |
145
+ | `inferFieldsFromConditions(conditions)` | Extract field dependencies from conditions |
136
146
 
137
147
  ### Validation
138
148
 
139
- | Function | Description |
140
- |----------|-------------|
141
- | `runValidator(spec, value, formValues, validators)` | Run validator(s) asynchronously |
142
- | `runValidatorSync(spec, value, formValues, validators)` | Run validator(s) synchronously |
143
- | `isValid(result)` | Check if validation result is valid |
144
- | `composeValidators(validators)` | Compose multiple validators |
145
- | `required()` | Built-in required validator |
146
- | `minLength(min)` | Built-in minimum length validator |
147
- | `maxLength(max)` | Built-in maximum length validator |
148
- | `pattern(regex)` | Built-in pattern validator |
149
- | `resolveErrorMessage(result, messages)` | Resolve error message from result |
150
- | `formatTypeAsMessage(type)` | Format validation type as message |
151
- | `createErrorMessages(config)` | Create error messages configuration |
152
- | `getErrorType(result)` | Get the error type from result |
153
- | `createValidationError(type, message)` | Create a validation error object |
149
+ | Function | Description |
150
+ | ------------------------------------------------------- | ----------------------------------- |
151
+ | `runValidator(spec, value, formValues, validators)` | Run validator(s) asynchronously |
152
+ | `runValidatorSync(spec, value, formValues, validators)` | Run validator(s) synchronously |
153
+ | `isValid(result)` | Check if validation result is valid |
154
+ | `composeValidators(validators)` | Compose multiple validators |
155
+ | `required()` | Built-in required validator |
156
+ | `minLength(min)` | Built-in minimum length validator |
157
+ | `maxLength(max)` | Built-in maximum length validator |
158
+ | `pattern(regex)` | Built-in pattern validator |
159
+ | `resolveErrorMessage(result, messages)` | Resolve error message from result |
160
+ | `formatTypeAsMessage(type)` | Format validation type as message |
161
+ | `createErrorMessages(config)` | Create error messages configuration |
162
+ | `getErrorType(result)` | Get the error type from result |
163
+ | `createValidationError(type, message)` | Create a validation error object |
154
164
 
155
165
  ### Value Transformation
156
166
 
157
- | Function | Description |
158
- |----------|-------------|
159
- | `parse(value, parser, parsers)` | Parse input value |
160
- | `format(value, formatter, formatters)` | Format value for display |
161
- | `extractValueField(value, field)` | Extract a field from a value object |
162
- | `transformFieldName(name, config)` | Transform field name based on config |
163
- | `createFloatParser()` | Create float parser |
164
- | `createFloatFormatter(decimals)` | Create float formatter with precision |
165
- | `createIntParser()` | Create integer parser |
166
- | `createTrimParser()` | Create string trimming parser |
167
- | `createDefaultParsers()` | Create default parsers configuration |
168
- | `createDefaultFormatters()` | Create default formatters configuration |
167
+ | Function | Description |
168
+ | -------------------------------------- | --------------------------------------- |
169
+ | `parse(value, parser, parsers)` | Parse input value |
170
+ | `format(value, formatter, formatters)` | Format value for display |
171
+ | `extractValueField(value, field)` | Extract a field from a value object |
172
+ | `transformFieldName(name, config)` | Transform field name based on config |
173
+ | `createFloatParser()` | Create float parser |
174
+ | `createFloatFormatter(decimals)` | Create float formatter with precision |
175
+ | `createIntParser()` | Create integer parser |
176
+ | `createTrimParser()` | Create string trimming parser |
177
+ | `createDefaultParsers()` | Create default parsers configuration |
178
+ | `createDefaultFormatters()` | Create default formatters configuration |
169
179
 
170
180
  ### Configuration
171
181
 
172
- | Function | Description |
173
- |----------|-------------|
174
- | `deepMerge(target, source)` | Deep merge objects |
175
- | `mergeInputConfigs(base, override)` | Merge input configurations |
176
- | `resolveInputConfig(type, inputs, formInputs)` | Resolve input configuration |
177
- | `resolveFieldType(name, config, inputs)` | Resolve field type from config |
178
- | `mergeStaticProps(layers)` | Merge static props from multiple layers |
179
- | `mergeFieldProps(layers)` | Merge props from multiple layers |
180
- | `createConfigContext(config)` | Create configuration context |
181
- | `resolveInitialValue(name, config, inputConfig, record)` | Resolve initial field value |
182
- | `resolveAllInitialValues(config, inputs, record)` | Resolve all initial values |
183
- | `isEmptyValue(value)` | Check if a value is empty |
184
- | `getInputDefaultValue(inputConfig)` | Get default value from input config |
185
- | `mergeRecordWithDefaults(config, inputs, record)` | Merge record with default values |
182
+ | Function | Description |
183
+ | -------------------------------------------------------- | --------------------------------------- |
184
+ | `deepMerge(target, source)` | Deep merge objects |
185
+ | `mergeInputConfigs(base, override)` | Merge input configurations |
186
+ | `resolveInputConfig(type, inputs, formInputs)` | Resolve input configuration |
187
+ | `resolveFieldType(name, config, inputs)` | Resolve field type from config |
188
+ | `mergeStaticProps(layers)` | Merge static props from multiple layers |
189
+ | `mergeFieldProps(layers)` | Merge props from multiple layers |
190
+ | `createConfigContext(config)` | Create configuration context |
191
+ | `resolveInitialValue(name, config, inputConfig, record)` | Resolve initial field value |
192
+ | `resolveAllInitialValues(config, inputs, record)` | Resolve all initial values |
193
+ | `isEmptyValue(value)` | Check if a value is empty |
194
+ | `getInputDefaultValue(inputConfig)` | Get default value from input config |
195
+ | `mergeRecordWithDefaults(config, inputs, record)` | Merge record with default values |
186
196
 
187
197
  ### Labels & Ordering
188
198
 
189
- | Function | Description |
190
- |----------|-------------|
191
- | `humanizeLabel(fieldName)` | Convert camelCase to human-readable |
192
- | `resolveLabel(name, config, evaluated, props)` | Resolve field label |
193
- | `resolveFormTitle(formConfig, record)` | Resolve form title |
194
- | `isAutoGeneratedLabel(label, fieldName)` | Check if label was auto-generated |
195
- | `createLabelWithUnit(label, unit)` | Create label with unit suffix |
196
- | `parseLabelWithUnit(labelWithUnit)` | Parse label with unit |
197
- | `sortFieldsByOrder(fields, config)` | Sort fields by order property |
198
- | `getUnusedFields(allFields, usedFields)` | Get unused field names |
199
- | `getOrderedUnusedFields(allFields, usedFields, config)` | Get ordered unused fields |
199
+ | Function | Description |
200
+ | ------------------------------------------------------- | ----------------------------------- |
201
+ | `humanizeLabel(fieldName)` | Convert camelCase to human-readable |
202
+ | `resolveLabel(name, config, evaluated, props)` | Resolve field label |
203
+ | `resolveFormTitle(formConfig, record)` | Resolve form title |
204
+ | `isAutoGeneratedLabel(label, fieldName)` | Check if label was auto-generated |
205
+ | `createLabelWithUnit(label, unit)` | Create label with unit suffix |
206
+ | `parseLabelWithUnit(labelWithUnit)` | Parse label with unit |
207
+ | `sortFieldsByOrder(fields, config)` | Sort fields by order property |
208
+ | `getUnusedFields(allFields, usedFields)` | Get unused field names |
209
+ | `getOrderedUnusedFields(allFields, usedFields, config)` | Get ordered unused fields |
200
210
 
201
211
  ## TypeScript Types
202
212
 
@@ -239,15 +249,15 @@ import type {
239
249
 
240
250
  // Expression
241
251
  EvaluationContext,
242
- } from '@formality-ui/core';
252
+ } from "@formality-ui/core";
243
253
  ```
244
254
 
245
255
  ## Constants
246
256
 
247
- | Constant | Description |
248
- |----------|-------------|
257
+ | Constant | Description |
258
+ | -------------------- | --------------------------------------- |
249
259
  | `QUALIFIED_PREFIXES` | Prefixes for qualified field references |
250
- | `KEYWORDS` | Reserved keywords in expressions |
260
+ | `KEYWORDS` | Reserved keywords in expressions |
251
261
 
252
262
  ## License
253
263
 
package/dist/index.cjs CHANGED
@@ -37,7 +37,8 @@ var FIELD_STATE_PROPERTIES = /* @__PURE__ */ new Set([
37
37
  "isDirty",
38
38
  "isValidating",
39
39
  "error",
40
- "invalid"
40
+ "invalid",
41
+ "disabled"
41
42
  ]);
42
43
  var FIELD_PROXY_MARKER = /* @__PURE__ */ Symbol.for("formality.fieldProxy");
43
44
  var FIELD_PROXY_VALUE = /* @__PURE__ */ Symbol.for("formality.fieldProxyValue");
@@ -126,6 +127,13 @@ function buildEvaluationContext(fieldValues, record, props, fieldStates) {
126
127
  }
127
128
 
128
129
  // src/expression/evaluate.ts
130
+ jsep__default.default.addUnaryOp("typeof");
131
+ function isSafeNumber(value) {
132
+ if (value === null || value === void 0) {
133
+ return false;
134
+ }
135
+ return typeof value === "number" && !Number.isNaN(value) && Number.isFinite(value);
136
+ }
129
137
  function getProperty(obj, key) {
130
138
  if (obj === null || obj === void 0) {
131
139
  return void 0;
@@ -174,17 +182,84 @@ function evaluateNode(node, context) {
174
182
  const right = evaluateNode(binaryNode.right, context);
175
183
  const leftValue = unwrapFieldProxy(left);
176
184
  const rightValue = unwrapFieldProxy(right);
185
+ if (binaryNode.operator === "+") {
186
+ const leftIsString = typeof leftValue === "string";
187
+ const rightIsString = typeof rightValue === "string";
188
+ const leftIsArray = Array.isArray(leftValue);
189
+ const rightIsArray = Array.isArray(rightValue);
190
+ if (leftIsString || rightIsString || leftIsArray || rightIsArray) {
191
+ const leftStr = leftIsArray ? leftValue.join(",") : String(leftValue ?? "");
192
+ const rightStr = rightIsArray ? rightValue.join(",") : String(rightValue ?? "");
193
+ return leftStr + rightStr;
194
+ }
195
+ if (!isSafeNumber(leftValue) || !isSafeNumber(rightValue)) {
196
+ if (process.env.NODE_ENV !== "production") {
197
+ console.warn(
198
+ `[Formality Expression] Type error: Invalid operands for + (null/undefined not allowed): left=${typeof leftValue}, right=${typeof rightValue}`
199
+ );
200
+ }
201
+ return void 0;
202
+ }
203
+ const result = leftValue + rightValue;
204
+ if (!Number.isFinite(result)) {
205
+ if (process.env.NODE_ENV !== "production") {
206
+ console.warn(
207
+ `[Formality Expression] Arithmetic overflow: ${leftValue} + ${rightValue} produced ${result}`
208
+ );
209
+ }
210
+ return void 0;
211
+ }
212
+ return result;
213
+ }
214
+ const arithmeticOps = ["-", "*", "/", "%"];
215
+ if (arithmeticOps.includes(binaryNode.operator)) {
216
+ if (!isSafeNumber(leftValue) || !isSafeNumber(rightValue)) {
217
+ if (process.env.NODE_ENV !== "production") {
218
+ console.warn(
219
+ `[Formality Expression] Type error: Invalid operands for ${binaryNode.operator} (null/undefined not allowed): left=${typeof leftValue}, right=${typeof rightValue}`
220
+ );
221
+ }
222
+ return void 0;
223
+ }
224
+ const l = leftValue;
225
+ const r = rightValue;
226
+ if (binaryNode.operator === "/" || binaryNode.operator === "%") {
227
+ if (r === 0) {
228
+ if (process.env.NODE_ENV !== "production") {
229
+ const opName = binaryNode.operator === "/" ? "Division" : "Modulo";
230
+ console.warn(`[Formality Expression] ${opName} by zero`);
231
+ }
232
+ return void 0;
233
+ }
234
+ }
235
+ let result;
236
+ switch (binaryNode.operator) {
237
+ case "-":
238
+ result = l - r;
239
+ break;
240
+ case "*":
241
+ result = l * r;
242
+ break;
243
+ case "/":
244
+ result = l / r;
245
+ break;
246
+ case "%":
247
+ result = l % r;
248
+ break;
249
+ default:
250
+ throw new Error(`Unknown operator: ${binaryNode.operator}`);
251
+ }
252
+ if (!Number.isFinite(result)) {
253
+ if (process.env.NODE_ENV !== "production") {
254
+ console.warn(
255
+ `[Formality Expression] Arithmetic overflow: ${l} ${binaryNode.operator} ${r} produced ${result}`
256
+ );
257
+ }
258
+ return void 0;
259
+ }
260
+ return result;
261
+ }
177
262
  switch (binaryNode.operator) {
178
- case "+":
179
- return leftValue + rightValue;
180
- case "-":
181
- return leftValue - rightValue;
182
- case "*":
183
- return leftValue * rightValue;
184
- case "/":
185
- return leftValue / rightValue;
186
- case "%":
187
- return leftValue % rightValue;
188
263
  case "===":
189
264
  return leftValue === rightValue;
190
265
  case "!==":
@@ -320,15 +395,43 @@ function inferFieldsFromExpression(expr) {
320
395
  IDENTIFIER_REGEX.lastIndex = 0;
321
396
  while ((match = IDENTIFIER_REGEX.exec(expr)) !== null) {
322
397
  const identifier = match[1];
398
+ const matchIndex = match.index;
399
+ let inString = false;
400
+ let stringChar = "";
401
+ let escapeNext = false;
402
+ for (let i = 0; i < matchIndex; i++) {
403
+ const char = expr[i];
404
+ if (escapeNext) {
405
+ escapeNext = false;
406
+ continue;
407
+ }
408
+ if (char === "\\") {
409
+ escapeNext = true;
410
+ continue;
411
+ }
412
+ if (!inString && (char === '"' || char === "'" || char === "`")) {
413
+ inString = true;
414
+ stringChar = char;
415
+ continue;
416
+ }
417
+ if (inString && char === stringChar) {
418
+ inString = false;
419
+ stringChar = "";
420
+ continue;
421
+ }
422
+ }
423
+ if (inString) {
424
+ continue;
425
+ }
323
426
  if (KEYWORD_SET.has(identifier)) {
324
427
  continue;
325
428
  }
326
- const beforeMatch = expr.slice(0, match.index);
429
+ const beforeMatch = expr.slice(0, matchIndex);
327
430
  const lastNonWhitespace = beforeMatch.trimEnd().slice(-1);
328
431
  if (lastNonWhitespace === ".") {
329
432
  continue;
330
433
  }
331
- const afterMatch = expr.slice(match.index + identifier.length);
434
+ const afterMatch = expr.slice(matchIndex + identifier.length);
332
435
  if (afterMatch.startsWith(".") && QUALIFIED_PREFIX_SET.has(identifier)) {
333
436
  continue;
334
437
  }
@@ -361,9 +464,72 @@ function inferFieldsFromDescriptor(descriptor) {
361
464
  }
362
465
 
363
466
  // src/conditions/evaluate.ts
364
- function evaluateConditionMatch(condition, context, fieldValues) {
467
+ function evaluateFieldMatcher(fieldName, matcher, fieldValues, fieldStates) {
468
+ const fieldValue = fieldValues[fieldName];
469
+ const fieldState = fieldStates?.[fieldName];
470
+ if (typeof matcher !== "object" || matcher === null) {
471
+ return fieldValue === matcher;
472
+ }
473
+ if (matcher.isValid !== void 0) {
474
+ const isFieldValid = fieldState ? !fieldState.invalid && !fieldState.error : true;
475
+ if (matcher.isValid !== isFieldValid) {
476
+ return false;
477
+ }
478
+ }
479
+ if (matcher.isDisabled !== void 0) {
480
+ const isFieldDisabled = fieldState?.disabled ?? false;
481
+ if (matcher.isDisabled !== isFieldDisabled) {
482
+ return false;
483
+ }
484
+ }
485
+ if (matcher.is !== void 0) {
486
+ if (fieldValue !== matcher.is) {
487
+ return false;
488
+ }
489
+ }
490
+ const truthyCheck = matcher.truthy ?? matcher.isTruthy;
491
+ if (truthyCheck !== void 0) {
492
+ const isTruthy = Boolean(fieldValue);
493
+ if (truthyCheck !== isTruthy) {
494
+ return false;
495
+ }
496
+ }
497
+ const hasAnyMatcher = matcher.is !== void 0 || matcher.truthy !== void 0 || matcher.isTruthy !== void 0 || matcher.isValid !== void 0 || matcher.isDisabled !== void 0;
498
+ if (!hasAnyMatcher) {
499
+ return Boolean(fieldValue);
500
+ }
501
+ return true;
502
+ }
503
+ function isStateFieldMatcher(value) {
504
+ if (typeof value !== "object" || value === null) {
505
+ return false;
506
+ }
507
+ return "isValid" in value || "isDisabled" in value;
508
+ }
509
+ function evaluateConditionMatch(condition, context, fieldValues, fieldStates) {
510
+ if (condition.when !== void 0 && typeof condition.when === "object") {
511
+ for (const [fieldName, matcher] of Object.entries(condition.when)) {
512
+ if (!evaluateFieldMatcher(fieldName, matcher, fieldValues, fieldStates)) {
513
+ return false;
514
+ }
515
+ }
516
+ if (condition.isDisabled !== void 0) {
517
+ if (!fieldStates) {
518
+ return false;
519
+ }
520
+ const fieldsWithStateMatchers = Object.entries(condition.when).filter(([, matcher]) => isStateFieldMatcher(matcher)).map(([fieldName]) => fieldName);
521
+ const fieldsToCheck = fieldsWithStateMatchers.length > 0 ? fieldsWithStateMatchers : Object.keys(condition.when);
522
+ const allFieldsMatchDisabled = fieldsToCheck.every(
523
+ (fieldName) => fieldStates[fieldName]?.disabled === condition.isDisabled
524
+ );
525
+ if (!allFieldsMatchDisabled) {
526
+ return false;
527
+ }
528
+ }
529
+ return true;
530
+ }
365
531
  let triggerValue;
366
- if (condition.when !== void 0) {
532
+ if (typeof condition.when === "string") {
367
533
  triggerValue = fieldValues[condition.when];
368
534
  } else if (condition.selectWhen !== void 0) {
369
535
  if (typeof condition.selectWhen === "string") {
@@ -376,6 +542,21 @@ function evaluateConditionMatch(condition, context, fieldValues) {
376
542
  } else {
377
543
  return false;
378
544
  }
545
+ if (typeof condition.when === "string" && fieldStates) {
546
+ const fieldState = fieldStates[condition.when];
547
+ if (condition.isValid !== void 0) {
548
+ const isFieldValid = fieldState ? !fieldState.invalid && !fieldState.error : true;
549
+ if (condition.isValid !== isFieldValid) {
550
+ return false;
551
+ }
552
+ }
553
+ if (condition.isDisabled !== void 0) {
554
+ const isFieldDisabled = fieldState?.disabled ?? false;
555
+ if (condition.isDisabled !== isFieldDisabled) {
556
+ return false;
557
+ }
558
+ }
559
+ }
379
560
  if (condition.is !== void 0) {
380
561
  return triggerValue === condition.is;
381
562
  }
@@ -383,11 +564,19 @@ function evaluateConditionMatch(condition, context, fieldValues) {
383
564
  const isTruthy = Boolean(triggerValue);
384
565
  return condition.truthy ? isTruthy : !isTruthy;
385
566
  }
567
+ if (condition.isValid !== void 0 || condition.isDisabled !== void 0) {
568
+ return true;
569
+ }
386
570
  return Boolean(triggerValue);
387
571
  }
388
572
  function evaluateConditions(input) {
389
573
  const { conditions, fieldValues, fieldStates, record, props } = input;
390
- const context = buildEvaluationContext(fieldValues, record, props, fieldStates);
574
+ const context = buildEvaluationContext(
575
+ fieldValues,
576
+ record,
577
+ props,
578
+ fieldStates
579
+ );
391
580
  let disabled;
392
581
  let visible;
393
582
  let setValue;
@@ -395,7 +584,12 @@ function evaluateConditions(input) {
395
584
  let hasVisibleCondition = false;
396
585
  let hasSetCondition = false;
397
586
  for (const condition of conditions) {
398
- const isMatched = evaluateConditionMatch(condition, context, fieldValues);
587
+ const isMatched = evaluateConditionMatch(
588
+ condition,
589
+ context,
590
+ fieldValues,
591
+ fieldStates
592
+ );
399
593
  if (!isMatched) {
400
594
  continue;
401
595
  }
@@ -417,7 +611,9 @@ function evaluateConditions(input) {
417
611
  } else if (typeof condition.selectSet === "function") {
418
612
  setValue = condition.selectSet;
419
613
  } else {
420
- setValue = unwrapFieldProxy(evaluateDescriptor(condition.selectSet, context));
614
+ setValue = unwrapFieldProxy(
615
+ evaluateDescriptor(condition.selectSet, context)
616
+ );
421
617
  }
422
618
  }
423
619
  }
@@ -430,9 +626,14 @@ function evaluateConditions(input) {
430
626
  hasSetCondition
431
627
  };
432
628
  }
433
- function conditionMatches(condition, fieldValues, record, props) {
434
- const context = buildEvaluationContext(fieldValues, record, props);
435
- return evaluateConditionMatch(condition, context, fieldValues);
629
+ function conditionMatches(condition, fieldValues, record, props, fieldStates) {
630
+ const context = buildEvaluationContext(
631
+ fieldValues,
632
+ record,
633
+ props,
634
+ fieldStates
635
+ );
636
+ return evaluateConditionMatch(condition, context, fieldValues, fieldStates);
436
637
  }
437
638
  function mergeConditionResults(results) {
438
639
  let disabled;
@@ -468,7 +669,11 @@ function inferFieldsFromConditions(conditions) {
468
669
  const fields = [];
469
670
  for (const condition of conditions) {
470
671
  if (condition.when !== void 0) {
471
- fields.push(condition.when);
672
+ if (typeof condition.when === "string") {
673
+ fields.push(condition.when);
674
+ } else {
675
+ fields.push(...Object.keys(condition.when));
676
+ }
472
677
  }
473
678
  if (condition.selectWhen !== void 0) {
474
679
  fields.push(...inferFieldsFromDescriptor(condition.selectWhen));
@@ -504,7 +709,12 @@ function resolveNamedValidator(name, validators) {
504
709
  async function runValidator(spec, value, formValues, namedValidators) {
505
710
  if (Array.isArray(spec)) {
506
711
  for (const item of spec) {
507
- const result = await runValidator(item, value, formValues, namedValidators);
712
+ const result = await runValidator(
713
+ item,
714
+ value,
715
+ formValues,
716
+ namedValidators
717
+ );
508
718
  if (!isValid(result)) {
509
719
  return result;
510
720
  }
@@ -513,7 +723,9 @@ async function runValidator(spec, value, formValues, namedValidators) {
513
723
  }
514
724
  if (typeof spec === "string") {
515
725
  if (!namedValidators) {
516
- console.warn(`Named validator "${spec}" requested but no validators provided`);
726
+ console.warn(
727
+ `Named validator "${spec}" requested but no validators provided`
728
+ );
517
729
  return true;
518
730
  }
519
731
  const validator = resolveNamedValidator(spec, namedValidators);
@@ -566,7 +778,12 @@ function isValid(result) {
566
778
  function composeValidators(validators, namedValidators) {
567
779
  return async (value, formValues) => {
568
780
  for (const spec of validators) {
569
- const result = await runValidator(spec, value, formValues, namedValidators);
781
+ const result = await runValidator(
782
+ spec,
783
+ value,
784
+ formValues,
785
+ namedValidators
786
+ );
570
787
  if (!isValid(result)) {
571
788
  return result;
572
789
  }
@@ -748,7 +965,9 @@ function format(value, formatterSpec, namedFormatters) {
748
965
  const formatter = namedFormatters?.[formatterSpec];
749
966
  if (!formatter) {
750
967
  if (process.env.NODE_ENV !== "production") {
751
- console.warn(`Formatter "${formatterSpec}" not found in formatters config`);
968
+ console.warn(
969
+ `Formatter "${formatterSpec}" not found in formatters config`
970
+ );
752
971
  }
753
972
  return value;
754
973
  }