@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 +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/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
|
|
37
|
+
import { evaluate, buildEvaluationContext } from "@formality-ui/core";
|
|
38
38
|
|
|
39
39
|
const context = buildEvaluationContext({
|
|
40
40
|
fields: {
|
|
41
|
-
client: { value: { id: 5, name:
|
|
41
|
+
client: { value: { id: 5, name: "Acme" } },
|
|
42
42
|
signed: { value: true },
|
|
43
43
|
},
|
|
44
|
-
record: { originalName:
|
|
44
|
+
record: { originalName: "Old Name" },
|
|
45
45
|
});
|
|
46
46
|
|
|
47
47
|
// Simple field access
|
|
48
|
-
evaluate(
|
|
49
|
-
evaluate(
|
|
48
|
+
evaluate("client", context); // { id: 5, name: 'Acme' }
|
|
49
|
+
evaluate("client.id", context); // 5
|
|
50
50
|
|
|
51
51
|
// Expressions
|
|
52
|
-
evaluate(
|
|
53
|
-
evaluate(
|
|
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
|
|
61
|
+
import { evaluateConditions } from "@formality-ui/core";
|
|
62
62
|
|
|
63
63
|
const result = evaluateConditions({
|
|
64
64
|
conditions: [
|
|
65
|
-
{ when:
|
|
66
|
-
{ when:
|
|
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;
|
|
72
|
-
result.visible;
|
|
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 {
|
|
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,
|
|
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 {
|
|
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(
|
|
98
|
-
format(42.567, formatter);
|
|
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
|
|
116
|
+
import { humanizeLabel, resolveLabel } from "@formality-ui/core";
|
|
107
117
|
|
|
108
|
-
humanizeLabel(
|
|
109
|
-
humanizeLabel(
|
|
110
|
-
humanizeLabel(
|
|
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
|
|
118
|
-
|
|
119
|
-
| `evaluate(expr, context)`
|
|
120
|
-
| `evaluateDescriptor(descriptor, context)`
|
|
121
|
-
| `buildEvaluationContext(fields, record, props)`
|
|
122
|
-
| `buildFormContext(fields, record)`
|
|
123
|
-
| `buildFieldContext(name, fields, record, props)` | Build field-level context
|
|
124
|
-
| `inferFieldsFromExpression(expr)`
|
|
125
|
-
| `inferFieldsFromDescriptor(descriptor)`
|
|
126
|
-
| `clearExpressionCache()`
|
|
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
|
|
131
|
-
|
|
132
|
-
| `evaluateConditions(input)`
|
|
133
|
-
| `conditionMatches(condition, context)`
|
|
134
|
-
| `mergeConditionResults(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
|
|
140
|
-
|
|
141
|
-
| `runValidator(spec, value, formValues, validators)`
|
|
142
|
-
| `runValidatorSync(spec, value, formValues, validators)` | Run validator(s) synchronously
|
|
143
|
-
| `isValid(result)`
|
|
144
|
-
| `composeValidators(validators)`
|
|
145
|
-
| `required()`
|
|
146
|
-
| `minLength(min)`
|
|
147
|
-
| `maxLength(max)`
|
|
148
|
-
| `pattern(regex)`
|
|
149
|
-
| `resolveErrorMessage(result, messages)`
|
|
150
|
-
| `formatTypeAsMessage(type)`
|
|
151
|
-
| `createErrorMessages(config)`
|
|
152
|
-
| `getErrorType(result)`
|
|
153
|
-
| `createValidationError(type, message)`
|
|
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
|
|
158
|
-
|
|
159
|
-
| `parse(value, parser, parsers)`
|
|
160
|
-
| `format(value, formatter, formatters)` | Format value for display
|
|
161
|
-
| `extractValueField(value, field)`
|
|
162
|
-
| `transformFieldName(name, config)`
|
|
163
|
-
| `createFloatParser()`
|
|
164
|
-
| `createFloatFormatter(decimals)`
|
|
165
|
-
| `createIntParser()`
|
|
166
|
-
| `createTrimParser()`
|
|
167
|
-
| `createDefaultParsers()`
|
|
168
|
-
| `createDefaultFormatters()`
|
|
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
|
|
173
|
-
|
|
174
|
-
| `deepMerge(target, source)`
|
|
175
|
-
| `mergeInputConfigs(base, override)`
|
|
176
|
-
| `resolveInputConfig(type, inputs, formInputs)`
|
|
177
|
-
| `resolveFieldType(name, config, inputs)`
|
|
178
|
-
| `mergeStaticProps(layers)`
|
|
179
|
-
| `mergeFieldProps(layers)`
|
|
180
|
-
| `createConfigContext(config)`
|
|
181
|
-
| `resolveInitialValue(name, config, inputConfig, record)` | Resolve initial field value
|
|
182
|
-
| `resolveAllInitialValues(config, inputs, record)`
|
|
183
|
-
| `isEmptyValue(value)`
|
|
184
|
-
| `getInputDefaultValue(inputConfig)`
|
|
185
|
-
| `mergeRecordWithDefaults(config, inputs, record)`
|
|
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
|
|
190
|
-
|
|
191
|
-
| `humanizeLabel(fieldName)`
|
|
192
|
-
| `resolveLabel(name, config, evaluated, props)`
|
|
193
|
-
| `resolveFormTitle(formConfig, record)`
|
|
194
|
-
| `isAutoGeneratedLabel(label, fieldName)`
|
|
195
|
-
| `createLabelWithUnit(label, unit)`
|
|
196
|
-
| `parseLabelWithUnit(labelWithUnit)`
|
|
197
|
-
| `sortFieldsByOrder(fields, config)`
|
|
198
|
-
| `getUnusedFields(allFields, usedFields)`
|
|
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
|
|
252
|
+
} from "@formality-ui/core";
|
|
243
253
|
```
|
|
244
254
|
|
|
245
255
|
## Constants
|
|
246
256
|
|
|
247
|
-
| Constant
|
|
248
|
-
|
|
257
|
+
| Constant | Description |
|
|
258
|
+
| -------------------- | --------------------------------------- |
|
|
249
259
|
| `QUALIFIED_PREFIXES` | Prefixes for qualified field references |
|
|
250
|
-
| `KEYWORDS`
|
|
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,
|
|
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(
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
435
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
968
|
+
console.warn(
|
|
969
|
+
`Formatter "${formatterSpec}" not found in formatters config`
|
|
970
|
+
);
|
|
752
971
|
}
|
|
753
972
|
return value;
|
|
754
973
|
}
|