@ng-forge/dynamic-forms 0.9.0-next.10 → 0.9.0-next.11
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/fesm2022/{ng-forge-dynamic-forms-array-field.component-DbWes42V.mjs → ng-forge-dynamic-forms-array-field.component-DJ-LiLm_.mjs} +17 -4
- package/fesm2022/ng-forge-dynamic-forms-array-field.component-DJ-LiLm_.mjs.map +1 -0
- package/fesm2022/{ng-forge-dynamic-forms-container-field.component-CMhqFGJZ.mjs → ng-forge-dynamic-forms-container-field.component-Pcxg5PQu.mjs} +3 -3
- package/fesm2022/{ng-forge-dynamic-forms-container-field.component-CMhqFGJZ.mjs.map → ng-forge-dynamic-forms-container-field.component-Pcxg5PQu.mjs.map} +1 -1
- package/fesm2022/{ng-forge-dynamic-forms-container-utils-D_nNzKo1.mjs → ng-forge-dynamic-forms-container-utils-a4RstR2Z.mjs} +2 -2
- package/fesm2022/{ng-forge-dynamic-forms-container-utils-D_nNzKo1.mjs.map → ng-forge-dynamic-forms-container-utils-a4RstR2Z.mjs.map} +1 -1
- package/fesm2022/{ng-forge-dynamic-forms-css-wrapper.component-htvP_o95.mjs → ng-forge-dynamic-forms-css-wrapper.component-CJACCIEK.mjs} +2 -2
- package/fesm2022/{ng-forge-dynamic-forms-css-wrapper.component-htvP_o95.mjs.map → ng-forge-dynamic-forms-css-wrapper.component-CJACCIEK.mjs.map} +1 -1
- package/fesm2022/{ng-forge-dynamic-forms-emit-initialization-DtkxP7pE.mjs → ng-forge-dynamic-forms-emit-initialization-Bjf_I0D_.mjs} +2 -2
- package/fesm2022/{ng-forge-dynamic-forms-emit-initialization-DtkxP7pE.mjs.map → ng-forge-dynamic-forms-emit-initialization-Bjf_I0D_.mjs.map} +1 -1
- package/fesm2022/{ng-forge-dynamic-forms-group-field.component-BhYFIDA1.mjs → ng-forge-dynamic-forms-group-field.component-fzKwPrQr.mjs} +24 -4
- package/fesm2022/ng-forge-dynamic-forms-group-field.component-fzKwPrQr.mjs.map +1 -0
- package/fesm2022/{ng-forge-dynamic-forms-ng-forge-dynamic-forms-DWPPLOrV.mjs → ng-forge-dynamic-forms-ng-forge-dynamic-forms-o3rJX4vG.mjs} +328 -81
- package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-o3rJX4vG.mjs.map +1 -0
- package/fesm2022/{ng-forge-dynamic-forms-page-field.component-B9v_1shD.mjs → ng-forge-dynamic-forms-page-field.component-BX4R9GGa.mjs} +3 -3
- package/fesm2022/{ng-forge-dynamic-forms-page-field.component-B9v_1shD.mjs.map → ng-forge-dynamic-forms-page-field.component-BX4R9GGa.mjs.map} +1 -1
- package/fesm2022/{ng-forge-dynamic-forms-text-field.component-B4LnTnIA.mjs → ng-forge-dynamic-forms-text-field.component-B9SK8Vun.mjs} +2 -2
- package/fesm2022/{ng-forge-dynamic-forms-text-field.component-B4LnTnIA.mjs.map → ng-forge-dynamic-forms-text-field.component-B9SK8Vun.mjs.map} +1 -1
- package/fesm2022/ng-forge-dynamic-forms.mjs +1 -1
- package/package.json +1 -1
- package/types/ng-forge-dynamic-forms.d.ts +1827 -1608
- package/fesm2022/ng-forge-dynamic-forms-array-field.component-DbWes42V.mjs.map +0 -1
- package/fesm2022/ng-forge-dynamic-forms-group-field.component-BhYFIDA1.mjs.map +0 -1
- package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-DWPPLOrV.mjs.map +0 -1
|
@@ -19,462 +19,828 @@ type Prettify<T> = {
|
|
|
19
19
|
type DynamicText = string | Observable<string> | Signal<string>;
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* All string values in `queryParams` are treated as expressions evaluated by ExpressionParser.
|
|
25
|
-
* When `evaluateBodyExpressions` is true, top-level string values in `body` are also evaluated as expressions (shallow only).
|
|
22
|
+
* Interface for dynamic forms logger implementations.
|
|
23
|
+
* Allows custom logging integrations (Sentry, DataDog, etc.)
|
|
26
24
|
*/
|
|
27
|
-
interface
|
|
28
|
-
/** URL to send the request to */
|
|
29
|
-
url: string;
|
|
30
|
-
/** HTTP method. Defaults to 'GET' */
|
|
31
|
-
method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
25
|
+
interface Logger {
|
|
32
26
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* @example
|
|
37
|
-
* ```typescript
|
|
38
|
-
* {
|
|
39
|
-
* url: '/api/users/:userId/orders/:orderId',
|
|
40
|
-
* params: { userId: 'formValue.userId', orderId: 'fieldValue' }
|
|
41
|
-
* }
|
|
42
|
-
* // With userId=42 and fieldValue='abc' → '/api/users/42/orders/abc'
|
|
43
|
-
* ```
|
|
27
|
+
* Log a debug message.
|
|
28
|
+
* Use for detailed diagnostic information during development.
|
|
44
29
|
*/
|
|
45
|
-
|
|
30
|
+
debug(message: string, ...args: unknown[]): void;
|
|
46
31
|
/**
|
|
47
|
-
*
|
|
48
|
-
*
|
|
32
|
+
* Log an info message.
|
|
33
|
+
* Use for general operational information.
|
|
49
34
|
*/
|
|
50
|
-
|
|
51
|
-
/** Request body (for POST/PUT/PATCH) */
|
|
52
|
-
body?: Record<string, unknown>;
|
|
35
|
+
info(message: string, ...args: unknown[]): void;
|
|
53
36
|
/**
|
|
54
|
-
*
|
|
55
|
-
*
|
|
37
|
+
* Log a warning message.
|
|
38
|
+
* Use for potentially problematic situations that don't prevent operation.
|
|
56
39
|
*/
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
|
|
40
|
+
warn(message: string, ...args: unknown[]): void;
|
|
41
|
+
/**
|
|
42
|
+
* Log an error message.
|
|
43
|
+
* Use for error conditions that may affect functionality.
|
|
44
|
+
*/
|
|
45
|
+
error(message: string, ...args: unknown[]): void;
|
|
60
46
|
}
|
|
61
47
|
|
|
62
48
|
/**
|
|
63
|
-
*
|
|
49
|
+
* Shared shape for DI-scoped warning trackers that dedupe log output.
|
|
64
50
|
*
|
|
65
|
-
*
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Condition that compares a specific field's value against an expected value.
|
|
51
|
+
* Used by both the derivation-warning tracker (keys are field paths) and the
|
|
52
|
+
* deprecation-warning tracker (keys are deprecation IDs). The property is
|
|
53
|
+
* generic so the same shape works for any string-keyed warning domain.
|
|
70
54
|
*
|
|
71
|
-
* @
|
|
55
|
+
* @internal
|
|
72
56
|
*/
|
|
73
|
-
interface
|
|
74
|
-
|
|
75
|
-
/** Path to the field whose value to compare */
|
|
76
|
-
fieldPath: string;
|
|
77
|
-
/** Comparison operator */
|
|
78
|
-
operator: ComparisonOperator;
|
|
79
|
-
/** Value to compare against */
|
|
80
|
-
value?: unknown;
|
|
57
|
+
interface WarningTracker {
|
|
58
|
+
warnedKeys: Set<string>;
|
|
81
59
|
}
|
|
60
|
+
|
|
82
61
|
/**
|
|
83
|
-
*
|
|
62
|
+
* Represents the state information of a single form field.
|
|
84
63
|
*
|
|
85
|
-
*
|
|
64
|
+
* Used in evaluation contexts to allow expressions and conditions
|
|
65
|
+
* to reference field state (e.g., `fieldState.touched`, `formFieldState.email.dirty`).
|
|
86
66
|
*
|
|
87
67
|
* @public
|
|
88
68
|
*/
|
|
89
|
-
interface
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
69
|
+
interface FieldStateInfo {
|
|
70
|
+
readonly touched: boolean;
|
|
71
|
+
readonly dirty: boolean;
|
|
72
|
+
/** Convenience property: equivalent to `!dirty` */
|
|
73
|
+
readonly pristine: boolean;
|
|
74
|
+
readonly valid: boolean;
|
|
75
|
+
readonly invalid: boolean;
|
|
76
|
+
readonly pending: boolean;
|
|
77
|
+
/**
|
|
78
|
+
* Whether the field is currently hidden.
|
|
79
|
+
*
|
|
80
|
+
* Note: This is a library-managed property, not a native Angular Signal Forms
|
|
81
|
+
* property. Returns `false` if the field instance does not expose a `hidden` signal.
|
|
82
|
+
*/
|
|
83
|
+
readonly hidden: boolean;
|
|
84
|
+
/**
|
|
85
|
+
* Whether the field is currently readonly.
|
|
86
|
+
*
|
|
87
|
+
* Note: This is a library-managed property, not a native Angular Signal Forms
|
|
88
|
+
* property. Returns `false` if the field instance does not expose a `readonly` signal.
|
|
89
|
+
*/
|
|
90
|
+
readonly readonly: boolean;
|
|
91
|
+
/**
|
|
92
|
+
* Whether the field is currently disabled.
|
|
93
|
+
*
|
|
94
|
+
* Note: This is a library-managed property, not a native Angular Signal Forms
|
|
95
|
+
* property. Returns `false` if the field instance does not expose a `disabled` signal.
|
|
96
|
+
*/
|
|
97
|
+
readonly disabled: boolean;
|
|
93
98
|
}
|
|
94
99
|
/**
|
|
95
|
-
*
|
|
100
|
+
* Map of field keys to their state information.
|
|
96
101
|
*
|
|
97
|
-
*
|
|
102
|
+
* Used as `formFieldState` in evaluation contexts to access
|
|
103
|
+
* state of any field in the form by key.
|
|
98
104
|
*
|
|
99
105
|
* @public
|
|
100
106
|
*/
|
|
101
|
-
|
|
102
|
-
type: 'javascript';
|
|
103
|
-
/** JavaScript expression string to evaluate */
|
|
104
|
-
expression: string;
|
|
105
|
-
}
|
|
107
|
+
type FormFieldStateMap = Record<string, FieldStateInfo | undefined>;
|
|
106
108
|
/**
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
* The HTTP request is resolved reactively — when dependent form values change,
|
|
110
|
-
* the request is re-evaluated (with debouncing). The result is cached per
|
|
111
|
-
* resolved request to avoid redundant network calls.
|
|
109
|
+
* Field state context for the current field being evaluated.
|
|
112
110
|
*
|
|
113
|
-
*
|
|
114
|
-
* a signal-based async resolution pattern internally.
|
|
111
|
+
* Used as `fieldState` in evaluation contexts.
|
|
115
112
|
*
|
|
116
113
|
* @public
|
|
117
114
|
*/
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
115
|
+
type FieldStateContext = FieldStateInfo;
|
|
116
|
+
|
|
117
|
+
interface EvaluationContext<TValue = unknown, TFormValue extends Record<string, unknown> = any> {
|
|
118
|
+
/** Current field value */
|
|
119
|
+
fieldValue: TValue;
|
|
122
120
|
/**
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
121
|
+
* Form value for the current evaluation scope.
|
|
122
|
+
*
|
|
123
|
+
* For regular (non-array) derivations, this contains the complete form value.
|
|
124
|
+
* For array item derivations, this is scoped to the current array item.
|
|
125
|
+
* Use `rootFormValue` to access the complete form when inside an array context.
|
|
126
126
|
*/
|
|
127
|
-
|
|
127
|
+
formValue: TFormValue;
|
|
128
|
+
/** Field path for relative references */
|
|
129
|
+
fieldPath: string;
|
|
130
|
+
/** Custom evaluation functions */
|
|
131
|
+
customFunctions?: Record<string, (context: EvaluationContext) => unknown>;
|
|
132
|
+
/** Logger for diagnostic output */
|
|
133
|
+
logger: Logger;
|
|
128
134
|
/**
|
|
129
|
-
* Value
|
|
135
|
+
* Value of the field's nearest parent **group** (or array item, when the
|
|
136
|
+
* field has no inner group above the array boundary).
|
|
130
137
|
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
138
|
+
* Complements `formValue` and `fieldValue` for derivations on fields nested
|
|
139
|
+
* inside groups built by factory helpers, where the parent group's key
|
|
140
|
+
* isn't known at config-authoring time. Mirrors the runtime semantics of
|
|
141
|
+
* the `'$group'` token used in `dependsOn`.
|
|
135
142
|
*
|
|
136
|
-
*
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
*
|
|
143
|
-
*
|
|
143
|
+
* Resolution at evaluation time:
|
|
144
|
+
* - Inside a group at form root: the parent group's value object (e.g.,
|
|
145
|
+
* for `address.state`, `groupValue === formValue.address`).
|
|
146
|
+
* - Inside nested groups: the innermost parent group's value object.
|
|
147
|
+
* - Directly inside an array item (no inner group): the array item itself
|
|
148
|
+
* (same shape as `formValue` in array context).
|
|
149
|
+
* - Inside a group inside an array item: the inner group's value object
|
|
150
|
+
* within the current item.
|
|
151
|
+
* - Field at form root with no parent container: `undefined`.
|
|
152
|
+
*
|
|
153
|
+
* @example
|
|
154
|
+
* ```typescript
|
|
155
|
+
* // Field 'state' inside group 'address':
|
|
156
|
+
* deriveStateFromCountry: (ctx) => {
|
|
157
|
+
* const country = ctx.groupValue?.country; // no need to know parent key
|
|
158
|
+
* return country === 'usa' ? 'NY' : '';
|
|
159
|
+
* }
|
|
160
|
+
* ```
|
|
144
161
|
*/
|
|
145
|
-
|
|
146
|
-
}
|
|
147
|
-
/**
|
|
148
|
-
* Condition that evaluates based on a registered async function.
|
|
149
|
-
*
|
|
150
|
-
* The function is resolved reactively — when dependent form values change,
|
|
151
|
-
* the function is re-evaluated (with debouncing). The result is cached per
|
|
152
|
-
* evaluation to avoid redundant calls.
|
|
153
|
-
*
|
|
154
|
-
* Since `LogicFn` must return `boolean` synchronously, this condition uses
|
|
155
|
-
* a signal-based async resolution pattern internally.
|
|
156
|
-
*
|
|
157
|
-
* @public
|
|
158
|
-
*/
|
|
159
|
-
interface AsyncCondition {
|
|
160
|
-
type: 'async';
|
|
161
|
-
/** Name of the registered async condition function */
|
|
162
|
-
asyncFunctionName: string;
|
|
162
|
+
groupValue?: unknown;
|
|
163
163
|
/**
|
|
164
|
-
*
|
|
164
|
+
* Root form value when inside an array context.
|
|
165
165
|
*
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
166
|
+
* This provides access to values outside the current array item.
|
|
167
|
+
* When a derivation targets an array item field (e.g., `items.$.lineTotal`),
|
|
168
|
+
* `formValue` is scoped to the current array item, while `rootFormValue`
|
|
169
|
+
* provides access to the entire form value including fields outside the array.
|
|
170
170
|
*
|
|
171
|
-
* @
|
|
171
|
+
* @example
|
|
172
|
+
* ```typescript
|
|
173
|
+
* // In an array item derivation (on the lineTotal field inside lineItems array):
|
|
174
|
+
* {
|
|
175
|
+
* key: 'lineTotal',
|
|
176
|
+
* type: 'input',
|
|
177
|
+
* // formValue = current array item { quantity: 2, unitPrice: 50 }
|
|
178
|
+
* // rootFormValue = entire form { globalDiscount: 0.1, lineItems: [...] }
|
|
179
|
+
* derivation: 'formValue.quantity * formValue.unitPrice * (1 - rootFormValue.globalDiscount)'
|
|
180
|
+
* }
|
|
181
|
+
* ```
|
|
182
|
+
*
|
|
183
|
+
* For non-array derivations, `rootFormValue` is not set and `formValue`
|
|
184
|
+
* contains the entire form value.
|
|
172
185
|
*/
|
|
173
|
-
|
|
186
|
+
rootFormValue?: Record<string, unknown>;
|
|
174
187
|
/**
|
|
175
|
-
*
|
|
176
|
-
* @default 300
|
|
188
|
+
* Current array index when inside an array derivation.
|
|
177
189
|
*/
|
|
178
|
-
|
|
179
|
-
}
|
|
180
|
-
/**
|
|
181
|
-
* Logical AND — all sub-conditions must be true.
|
|
182
|
-
*
|
|
183
|
-
* @public
|
|
184
|
-
*/
|
|
185
|
-
interface AndCondition {
|
|
186
|
-
type: 'and';
|
|
187
|
-
/** Sub-conditions that must all evaluate to true */
|
|
188
|
-
conditions: ConditionalExpression[];
|
|
189
|
-
}
|
|
190
|
-
/**
|
|
191
|
-
* Logical OR — at least one sub-condition must be true.
|
|
192
|
-
*
|
|
193
|
-
* @public
|
|
194
|
-
*/
|
|
195
|
-
interface OrCondition {
|
|
196
|
-
type: 'or';
|
|
197
|
-
/** Sub-conditions where at least one must evaluate to true */
|
|
198
|
-
conditions: ConditionalExpression[];
|
|
199
|
-
}
|
|
200
|
-
/**
|
|
201
|
-
* Discriminated union of all conditional expression types.
|
|
202
|
-
*
|
|
203
|
-
* Each variant only allows the properties relevant to its type,
|
|
204
|
-
* providing compile-time safety against invalid property combinations.
|
|
205
|
-
*
|
|
206
|
-
* @public
|
|
207
|
-
*/
|
|
208
|
-
type ConditionalExpression = FieldValueCondition | CustomCondition | JavascriptCondition | HttpCondition | AsyncCondition | AndCondition | OrCondition;
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* Logic configuration for container fields (page, group, row, array).
|
|
212
|
-
* Containers only support the 'hidden' logic type since they are layout containers,
|
|
213
|
-
* not form controls (they can't be readonly, disabled, or required).
|
|
214
|
-
*/
|
|
215
|
-
interface ContainerLogicConfig {
|
|
216
|
-
/** Only 'hidden' is supported for container fields */
|
|
217
|
-
type: 'hidden';
|
|
190
|
+
arrayIndex?: number;
|
|
218
191
|
/**
|
|
219
|
-
*
|
|
220
|
-
* Can be a boolean or a conditional expression evaluated against form values.
|
|
192
|
+
* Path to the array field when inside an array derivation.
|
|
221
193
|
*/
|
|
222
|
-
|
|
194
|
+
arrayPath?: string;
|
|
195
|
+
/**
|
|
196
|
+
* External data signals resolved to their current values.
|
|
197
|
+
*
|
|
198
|
+
* This allows forms to reference data from outside the form context
|
|
199
|
+
* in conditional logic, derivations, and other expressions.
|
|
200
|
+
*
|
|
201
|
+
* @example
|
|
202
|
+
* ```typescript
|
|
203
|
+
* // In form config:
|
|
204
|
+
* externalData: {
|
|
205
|
+
* userRole: computed(() => this.userService.currentRole()),
|
|
206
|
+
* permissions: computed(() => this.authService.permissions()),
|
|
207
|
+
* }
|
|
208
|
+
*
|
|
209
|
+
* // In JavaScript expression:
|
|
210
|
+
* condition: {
|
|
211
|
+
* type: 'javascript',
|
|
212
|
+
* expression: "externalData.userRole === 'admin'"
|
|
213
|
+
* }
|
|
214
|
+
* ```
|
|
215
|
+
*/
|
|
216
|
+
externalData?: Record<string, unknown>;
|
|
217
|
+
/**
|
|
218
|
+
* DI-scoped tracker for deprecation warnings.
|
|
219
|
+
* Used by the condition evaluator to deduplicate deprecation warnings.
|
|
220
|
+
*/
|
|
221
|
+
deprecationTracker?: WarningTracker;
|
|
222
|
+
/**
|
|
223
|
+
* State of the current field being evaluated.
|
|
224
|
+
*
|
|
225
|
+
* Provides access to field state properties like `touched`, `dirty`, `valid`, etc.
|
|
226
|
+
* Uses a Proxy for lazy evaluation — properties are only read when accessed.
|
|
227
|
+
*
|
|
228
|
+
* @example
|
|
229
|
+
* ```typescript
|
|
230
|
+
* // In a JavaScript expression:
|
|
231
|
+
* condition: {
|
|
232
|
+
* type: 'javascript',
|
|
233
|
+
* expression: "fieldState.touched && fieldState.dirty"
|
|
234
|
+
* }
|
|
235
|
+
* ```
|
|
236
|
+
*/
|
|
237
|
+
fieldState?: FieldStateContext;
|
|
238
|
+
/**
|
|
239
|
+
* State of all fields in the form, keyed by field key.
|
|
240
|
+
*
|
|
241
|
+
* Provides access to any field's state properties by key.
|
|
242
|
+
* Uses a Proxy for lazy evaluation — field state is only read when accessed.
|
|
243
|
+
*
|
|
244
|
+
* @example
|
|
245
|
+
* ```typescript
|
|
246
|
+
* // In a JavaScript expression:
|
|
247
|
+
* condition: {
|
|
248
|
+
* type: 'javascript',
|
|
249
|
+
* expression: "formFieldState.email.dirty && formFieldState.email.valid"
|
|
250
|
+
* }
|
|
251
|
+
* ```
|
|
252
|
+
*/
|
|
253
|
+
formFieldState?: FormFieldStateMap;
|
|
254
|
+
/** Allow additional properties for flexible expression evaluation */
|
|
255
|
+
[key: string]: unknown;
|
|
223
256
|
}
|
|
224
257
|
|
|
225
258
|
/**
|
|
226
|
-
*
|
|
227
|
-
* Used for object arrays where each item is `{ key1: value1, key2: value2, ... }`.
|
|
228
|
-
*/
|
|
229
|
-
type ArrayItemTemplate = readonly ArrayAllowedChildren[];
|
|
230
|
-
/**
|
|
231
|
-
* An array item definition can be either:
|
|
232
|
-
* - A single field (ArrayAllowedChildren) for PRIMITIVE array items - extracts field value directly
|
|
233
|
-
* - An array of fields (ArrayItemTemplate) for OBJECT array items - merges fields into object
|
|
259
|
+
* Custom function signature for conditional expressions
|
|
234
260
|
*
|
|
235
|
-
*
|
|
236
|
-
* -
|
|
237
|
-
* -
|
|
238
|
-
* -
|
|
239
|
-
|
|
240
|
-
type ArrayItemDefinition = ArrayAllowedChildren | ArrayItemTemplate;
|
|
241
|
-
/**
|
|
242
|
-
* Array field interface for creating dynamic field collections that map to array values.
|
|
261
|
+
* Custom functions are used for conditional logic in:
|
|
262
|
+
* - `when` conditions (field visibility, conditional validation)
|
|
263
|
+
* - `readonly` logic (dynamic readonly state)
|
|
264
|
+
* - `disabled` logic (dynamic disabled state)
|
|
265
|
+
* - Dynamic value calculations
|
|
243
266
|
*
|
|
244
|
-
*
|
|
245
|
-
* -
|
|
246
|
-
* -
|
|
247
|
-
*
|
|
248
|
-
* - An array of FieldDefs (object item) - fields are merged into an object
|
|
249
|
-
* - Items can have different field configurations (heterogeneous arrays supported)
|
|
250
|
-
* - Empty `fields: []` means no initial items - user adds via buttons with explicit templates
|
|
251
|
-
* - Initial values are declared via `value` property on each field definition
|
|
267
|
+
* Unlike validators, custom functions:
|
|
268
|
+
* - Return any value (typically boolean for conditions, but could be strings, numbers, etc.)
|
|
269
|
+
* - Do NOT return ValidationError objects
|
|
270
|
+
* - Receive EvaluationContext (field values) not FieldContext (field state)
|
|
252
271
|
*
|
|
253
272
|
* @example
|
|
254
|
-
*
|
|
273
|
+
* ```typescript
|
|
274
|
+
* // Boolean condition function
|
|
275
|
+
* const isAdult: CustomFunction = (ctx) => {
|
|
276
|
+
* return ctx.age >= 18;
|
|
277
|
+
* };
|
|
278
|
+
*
|
|
279
|
+
* // Used in field configuration:
|
|
255
280
|
* {
|
|
256
|
-
* key: '
|
|
257
|
-
* type: '
|
|
258
|
-
*
|
|
259
|
-
* { key: 'tag', type: 'input', value: 'angular' }, // Single field = primitive
|
|
260
|
-
* { key: 'tag', type: 'input', value: 'typescript' },
|
|
261
|
-
* ]
|
|
281
|
+
* key: 'alcoholPreference',
|
|
282
|
+
* type: 'select',
|
|
283
|
+
* when: { function: 'isAdult' } // Only show if isAdult returns true
|
|
262
284
|
* }
|
|
285
|
+
* ```
|
|
263
286
|
*
|
|
264
287
|
* @example
|
|
265
|
-
*
|
|
266
|
-
*
|
|
267
|
-
*
|
|
268
|
-
*
|
|
269
|
-
*
|
|
270
|
-
*
|
|
271
|
-
*
|
|
272
|
-
*
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
288
|
+
* ```typescript
|
|
289
|
+
* // Calculation function
|
|
290
|
+
* const calculateDiscount: CustomFunction = (ctx) => {
|
|
291
|
+
* const price = ctx.price || 0;
|
|
292
|
+
* const isVip = ctx.isVip || false;
|
|
293
|
+
* return isVip ? price * 0.8 : price * 0.9;
|
|
294
|
+
* };
|
|
295
|
+
* ```
|
|
296
|
+
*/
|
|
297
|
+
type CustomFunction<TFormValue extends Record<string, unknown> = any> = (context: EvaluationContext<unknown, TFormValue>) => unknown;
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Async function for derivations — returns any value.
|
|
301
|
+
*
|
|
302
|
+
* The function receives the evaluation context and may return a Promise or Observable.
|
|
303
|
+
* Register functions in `customFnConfig.asyncDerivations`.
|
|
276
304
|
*
|
|
277
305
|
* @example
|
|
278
|
-
*
|
|
279
|
-
* {
|
|
280
|
-
*
|
|
281
|
-
*
|
|
282
|
-
*
|
|
283
|
-
*
|
|
284
|
-
*
|
|
285
|
-
*
|
|
286
|
-
*
|
|
306
|
+
* ```typescript
|
|
307
|
+
* const fetchPrice: AsyncDerivationFunction = async (context) => {
|
|
308
|
+
* const response = await fetch(`/api/price?product=${context.formValue.productId}`);
|
|
309
|
+
* const data = await response.json();
|
|
310
|
+
* return data.price;
|
|
311
|
+
* };
|
|
312
|
+
* ```
|
|
313
|
+
*
|
|
314
|
+
* @public
|
|
315
|
+
*/
|
|
316
|
+
type AsyncDerivationFunction<TFormValue extends Record<string, unknown> = any> = (context: EvaluationContext<unknown, TFormValue>) => Promise<unknown> | Observable<unknown>;
|
|
317
|
+
/**
|
|
318
|
+
* Async function for conditions — returns boolean.
|
|
319
|
+
*
|
|
320
|
+
* The function receives the evaluation context and may return a Promise or Observable.
|
|
321
|
+
* Register functions in `customFnConfig.asyncConditions`.
|
|
287
322
|
*
|
|
288
323
|
* @example
|
|
289
|
-
*
|
|
290
|
-
* {
|
|
291
|
-
*
|
|
292
|
-
*
|
|
293
|
-
*
|
|
294
|
-
* }
|
|
324
|
+
* ```typescript
|
|
325
|
+
* const checkPermission: AsyncConditionFunction = async (context) => {
|
|
326
|
+
* const response = await fetch(`/api/permissions?user=${context.formValue.userId}`);
|
|
327
|
+
* const data = await response.json();
|
|
328
|
+
* return data.canEdit;
|
|
329
|
+
* };
|
|
330
|
+
* ```
|
|
295
331
|
*
|
|
296
|
-
*
|
|
297
|
-
|
|
332
|
+
* @public
|
|
333
|
+
*/
|
|
334
|
+
type AsyncConditionFunction<TFormValue extends Record<string, unknown> = any> = (context: EvaluationContext<unknown, TFormValue>) => Promise<boolean> | Observable<boolean>;
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Configuration for an HTTP request used by declarative HTTP validators, derivations, and conditions.
|
|
298
338
|
*
|
|
299
|
-
*
|
|
339
|
+
* All string values in `queryParams` are treated as expressions evaluated by ExpressionParser.
|
|
340
|
+
* When `evaluateBodyExpressions` is true, top-level string values in `body` are also evaluated as expressions (shallow only).
|
|
300
341
|
*/
|
|
301
|
-
interface
|
|
302
|
-
|
|
342
|
+
interface HttpRequestConfig {
|
|
343
|
+
/** URL to send the request to */
|
|
344
|
+
url: string;
|
|
345
|
+
/** HTTP method. Defaults to 'GET' */
|
|
346
|
+
method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
303
347
|
/**
|
|
304
|
-
*
|
|
305
|
-
*
|
|
306
|
-
*
|
|
307
|
-
*
|
|
308
|
-
*
|
|
309
|
-
*
|
|
348
|
+
* URL path parameters. Keys correspond to `:key` placeholders in the URL.
|
|
349
|
+
* Values are expressions evaluated by ExpressionParser against the current EvaluationContext.
|
|
350
|
+
*
|
|
351
|
+
* @example
|
|
352
|
+
* ```typescript
|
|
353
|
+
* {
|
|
354
|
+
* url: '/api/users/:userId/orders/:orderId',
|
|
355
|
+
* params: { userId: 'formValue.userId', orderId: 'fieldValue' }
|
|
356
|
+
* }
|
|
357
|
+
* // With userId=42 and fieldValue='abc' → '/api/users/42/orders/abc'
|
|
358
|
+
* ```
|
|
310
359
|
*/
|
|
311
|
-
|
|
312
|
-
/** Array fields do not have a label property **/
|
|
313
|
-
readonly label?: never;
|
|
314
|
-
/** Arrays do not support meta - they have no native form element **/
|
|
315
|
-
readonly meta?: never;
|
|
360
|
+
params?: Record<string, string>;
|
|
316
361
|
/**
|
|
317
|
-
*
|
|
318
|
-
*
|
|
362
|
+
* Query parameters appended to the URL.
|
|
363
|
+
* Values are expressions evaluated by ExpressionParser against the current EvaluationContext.
|
|
319
364
|
*/
|
|
320
|
-
|
|
365
|
+
queryParams?: Record<string, string>;
|
|
366
|
+
/** Request body (for POST/PUT/PATCH) */
|
|
367
|
+
body?: Record<string, unknown>;
|
|
321
368
|
/**
|
|
322
|
-
*
|
|
323
|
-
*
|
|
369
|
+
* When true, top-level string values in `body` are treated as expressions
|
|
370
|
+
* and evaluated by ExpressionParser (shallow only — nested objects are passed through as-is).
|
|
324
371
|
*/
|
|
325
|
-
|
|
326
|
-
/**
|
|
327
|
-
|
|
328
|
-
* Validation fails if the array has more items than this value.
|
|
329
|
-
*/
|
|
330
|
-
readonly maxLength?: number;
|
|
372
|
+
evaluateBodyExpressions?: boolean;
|
|
373
|
+
/** HTTP headers to include in the request */
|
|
374
|
+
headers?: Record<string, string>;
|
|
331
375
|
}
|
|
376
|
+
|
|
332
377
|
/**
|
|
333
|
-
*
|
|
334
|
-
*
|
|
335
|
-
*
|
|
378
|
+
* Comparison operators for field value and form value conditions.
|
|
379
|
+
*
|
|
380
|
+
* @public
|
|
336
381
|
*/
|
|
337
|
-
|
|
382
|
+
type ComparisonOperator = 'equals' | 'notEquals' | 'greater' | 'less' | 'greaterOrEqual' | 'lessOrEqual' | 'contains' | 'startsWith' | 'endsWith' | 'matches';
|
|
338
383
|
/**
|
|
339
|
-
*
|
|
384
|
+
* Condition that compares a specific field's value against an expected value.
|
|
385
|
+
*
|
|
386
|
+
* @public
|
|
340
387
|
*/
|
|
341
|
-
interface
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
388
|
+
interface FieldValueCondition {
|
|
389
|
+
type: 'fieldValue';
|
|
390
|
+
/** Path to the field whose value to compare */
|
|
391
|
+
fieldPath: string;
|
|
392
|
+
/** Comparison operator */
|
|
393
|
+
operator: ComparisonOperator;
|
|
394
|
+
/** Value to compare against */
|
|
395
|
+
value?: unknown;
|
|
346
396
|
}
|
|
347
397
|
/**
|
|
348
|
-
*
|
|
349
|
-
*
|
|
350
|
-
* Instead of manually specifying each item in `fields`, provide a `template` that defines
|
|
351
|
-
* the structure of a single item, and a `value` array with initial data.
|
|
352
|
-
* The library auto-generates add/remove buttons.
|
|
398
|
+
* Condition that invokes a custom function.
|
|
353
399
|
*
|
|
354
|
-
*
|
|
400
|
+
* Two mutually exclusive forms:
|
|
401
|
+
* - `functionName`: name of a function registered in `customFnConfig.customFunctions`.
|
|
402
|
+
* JSON-serializable; suitable for configs loaded from APIs, databases, or OpenAPI.
|
|
403
|
+
* - `fn`: inline function. NOT JSON-serializable; for code-only configs.
|
|
355
404
|
*
|
|
356
|
-
*
|
|
357
|
-
* // Primitive array: ['angular', 'typescript']
|
|
358
|
-
* {
|
|
359
|
-
* key: 'tags',
|
|
360
|
-
* type: 'array',
|
|
361
|
-
* template: { key: 'value', type: 'input', label: 'Tag', required: true },
|
|
362
|
-
* value: ['angular', 'typescript']
|
|
363
|
-
* }
|
|
405
|
+
* Exactly one of `functionName` or `fn` must be set.
|
|
364
406
|
*
|
|
365
|
-
*
|
|
366
|
-
*
|
|
367
|
-
*
|
|
368
|
-
*
|
|
369
|
-
* type: 'array',
|
|
370
|
-
* template: [
|
|
371
|
-
* { key: 'name', type: 'input', label: 'Contact Name', required: true },
|
|
372
|
-
* { key: 'phone', type: 'input', label: 'Phone Number' },
|
|
373
|
-
* ],
|
|
374
|
-
* value: [{ name: 'Jane', phone: '555' }]
|
|
375
|
-
* }
|
|
407
|
+
* Encoded as a strict discriminated union (XOR via `?: never`). `CustomValidatorConfig`
|
|
408
|
+
* intentionally uses a permissive interface for the same `fn`/`functionName` split
|
|
409
|
+
* because validators have a third source (`expression`) and the historical interface
|
|
410
|
+
* was already runtime-checked — the asymmetry is by design, not an oversight.
|
|
376
411
|
*
|
|
377
|
-
* @
|
|
378
|
-
* // Button customization / opt-out
|
|
379
|
-
* {
|
|
380
|
-
* key: 'tags',
|
|
381
|
-
* type: 'array',
|
|
382
|
-
* template: { key: 'value', type: 'input', label: 'Tag' },
|
|
383
|
-
* addButton: { label: 'Add Tag', props: { color: 'primary' } },
|
|
384
|
-
* removeButton: false
|
|
385
|
-
* }
|
|
412
|
+
* @public
|
|
386
413
|
*/
|
|
387
|
-
|
|
388
|
-
type: '
|
|
414
|
+
type CustomCondition = {
|
|
415
|
+
type: 'custom';
|
|
416
|
+
/** Name of the registered custom function to invoke */
|
|
417
|
+
functionName: string;
|
|
418
|
+
/** Inline form is forbidden when `functionName` is set */
|
|
419
|
+
fn?: never;
|
|
420
|
+
} | {
|
|
421
|
+
type: 'custom';
|
|
389
422
|
/**
|
|
390
|
-
*
|
|
391
|
-
*
|
|
392
|
-
* -
|
|
423
|
+
* Inline custom function. Mutually exclusive with `functionName`.
|
|
424
|
+
*
|
|
425
|
+
* NOT JSON-serializable — for code-only configs. For configs loaded
|
|
426
|
+
* from JSON / OpenAPI / databases, use `functionName` to reference
|
|
427
|
+
* a function registered in `customFnConfig.customFunctions`.
|
|
393
428
|
*/
|
|
394
|
-
|
|
395
|
-
/**
|
|
396
|
-
|
|
429
|
+
fn: CustomFunction;
|
|
430
|
+
/** Registered form is forbidden when `fn` is set */
|
|
431
|
+
functionName?: never;
|
|
432
|
+
};
|
|
433
|
+
/**
|
|
434
|
+
* Condition that evaluates a JavaScript expression using the secure AST-based parser.
|
|
435
|
+
*
|
|
436
|
+
* The expression has access to `formValue`, `fieldValue`, `externalData`, etc.
|
|
437
|
+
*
|
|
438
|
+
* @public
|
|
439
|
+
*/
|
|
440
|
+
interface JavascriptCondition {
|
|
441
|
+
type: 'javascript';
|
|
442
|
+
/** JavaScript expression string to evaluate */
|
|
443
|
+
expression: string;
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Condition that evaluates based on an HTTP response from a remote server.
|
|
447
|
+
*
|
|
448
|
+
* The HTTP request is resolved reactively — when dependent form values change,
|
|
449
|
+
* the request is re-evaluated (with debouncing). The result is cached per
|
|
450
|
+
* resolved request to avoid redundant network calls.
|
|
451
|
+
*
|
|
452
|
+
* Since `LogicFn` must return `boolean` synchronously, this condition uses
|
|
453
|
+
* a signal-based async resolution pattern internally.
|
|
454
|
+
*
|
|
455
|
+
* @public
|
|
456
|
+
*/
|
|
457
|
+
interface HttpCondition {
|
|
458
|
+
type: 'http';
|
|
459
|
+
/** HTTP request configuration */
|
|
460
|
+
http: HttpRequestConfig;
|
|
397
461
|
/**
|
|
398
|
-
*
|
|
399
|
-
*
|
|
462
|
+
* Expression to extract boolean from response (scope: `{ response }`).
|
|
463
|
+
* e.g. `'response.allowed'`, `'response.permissions.canEdit'`
|
|
464
|
+
* When omitted, the entire response is coerced to boolean: `!!response`
|
|
400
465
|
*/
|
|
401
|
-
|
|
466
|
+
responseExpression?: string;
|
|
402
467
|
/**
|
|
403
|
-
*
|
|
404
|
-
*
|
|
468
|
+
* Value to return while the HTTP request is in-flight.
|
|
469
|
+
*
|
|
470
|
+
* Choose based on the logic type and desired UX:
|
|
471
|
+
* - For `hidden`: `false` = visible while loading, `true` = hidden while loading
|
|
472
|
+
* - For `disabled`: `false` = enabled while loading, `true` = disabled while loading
|
|
473
|
+
* - For `required`: `false` = optional while loading, `true` = required while loading
|
|
474
|
+
*
|
|
475
|
+
* @default false
|
|
405
476
|
*/
|
|
406
|
-
|
|
407
|
-
/**
|
|
408
|
-
|
|
409
|
-
/** Simplified arrays do not support meta */
|
|
410
|
-
readonly meta?: never;
|
|
477
|
+
pendingValue?: boolean;
|
|
478
|
+
/** Cache duration in ms for HTTP responses. @default 30000 */
|
|
479
|
+
cacheDurationMs?: number;
|
|
411
480
|
/**
|
|
412
|
-
*
|
|
413
|
-
*
|
|
481
|
+
* Debounce time in milliseconds for re-evaluation when dependent form values change.
|
|
482
|
+
* @default 300
|
|
414
483
|
*/
|
|
415
|
-
|
|
484
|
+
debounceMs?: number;
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Shared fields for both branches of {@link AsyncCondition}.
|
|
488
|
+
*
|
|
489
|
+
* @internal
|
|
490
|
+
*/
|
|
491
|
+
interface AsyncConditionBase {
|
|
492
|
+
type: 'async';
|
|
416
493
|
/**
|
|
417
|
-
*
|
|
418
|
-
*
|
|
494
|
+
* Value to return while async resolution is pending.
|
|
495
|
+
*
|
|
496
|
+
* Choose based on the logic type and desired UX:
|
|
497
|
+
* - For `hidden`: `false` = visible while loading, `true` = hidden while loading
|
|
498
|
+
* - For `disabled`: `false` = enabled while loading, `true` = disabled while loading
|
|
499
|
+
* - For `required`: `false` = optional while loading, `true` = required while loading
|
|
500
|
+
*
|
|
501
|
+
* @default false
|
|
419
502
|
*/
|
|
420
|
-
|
|
503
|
+
pendingValue?: boolean;
|
|
421
504
|
/**
|
|
422
|
-
*
|
|
423
|
-
*
|
|
505
|
+
* Debounce ms for re-evaluation.
|
|
506
|
+
* @default 300
|
|
424
507
|
*/
|
|
425
|
-
|
|
426
|
-
/** Mutually exclusive with `template` — use `fields` for the full API instead */
|
|
427
|
-
readonly fields?: never;
|
|
508
|
+
debounceMs?: number;
|
|
428
509
|
}
|
|
429
510
|
/**
|
|
430
|
-
*
|
|
431
|
-
* Checks for `type: 'array'` with a `template` property (discriminant from full ArrayField).
|
|
432
|
-
*/
|
|
433
|
-
declare function isSimplifiedArrayField(field: FieldDef<any>): field is SimplifiedArrayField;
|
|
434
|
-
|
|
435
|
-
/**
|
|
436
|
-
* Group field interface for creating logical field groupings that map to object values
|
|
437
|
-
* Groups create nested form structures where child field values are collected into an object
|
|
438
|
-
* This is a programmatic grouping only - users cannot customize this field type
|
|
511
|
+
* Condition that resolves asynchronously via a custom function.
|
|
439
512
|
*
|
|
440
|
-
*
|
|
441
|
-
*
|
|
442
|
-
*
|
|
513
|
+
* Two mutually exclusive forms:
|
|
514
|
+
* - `asyncFunctionName`: name of a function registered in `customFnConfig.asyncConditions`.
|
|
515
|
+
* JSON-serializable; suitable for configs loaded from APIs, databases, or OpenAPI.
|
|
516
|
+
* - `asyncFn`: inline async function. NOT JSON-serializable; for code-only configs.
|
|
443
517
|
*
|
|
444
|
-
*
|
|
518
|
+
* Exactly one of `asyncFunctionName` or `asyncFn` must be set.
|
|
519
|
+
*
|
|
520
|
+
* The function is resolved reactively — when dependent form values change,
|
|
521
|
+
* the function is re-evaluated (with debouncing). The result is cached per
|
|
522
|
+
* evaluation to avoid redundant calls.
|
|
523
|
+
*
|
|
524
|
+
* Since `LogicFn` must return `boolean` synchronously, this condition uses
|
|
525
|
+
* a signal-based async resolution pattern internally.
|
|
526
|
+
*
|
|
527
|
+
* @public
|
|
445
528
|
*/
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
/**
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
readonly meta?: never;
|
|
529
|
+
type AsyncCondition = (AsyncConditionBase & {
|
|
530
|
+
/** Name of the registered async condition function */
|
|
531
|
+
asyncFunctionName: string;
|
|
532
|
+
/** Inline form is forbidden when `asyncFunctionName` is set */
|
|
533
|
+
asyncFn?: never;
|
|
534
|
+
}) | (AsyncConditionBase & {
|
|
453
535
|
/**
|
|
454
|
-
*
|
|
455
|
-
*
|
|
536
|
+
* Inline async condition function. Mutually exclusive with `asyncFunctionName`.
|
|
537
|
+
*
|
|
538
|
+
* NOT JSON-serializable — for code-only configs. For configs loaded
|
|
539
|
+
* from JSON / OpenAPI / databases, use `asyncFunctionName` to reference
|
|
540
|
+
* a function registered in `customFnConfig.asyncConditions`.
|
|
456
541
|
*/
|
|
457
|
-
|
|
542
|
+
asyncFn: AsyncConditionFunction;
|
|
543
|
+
/** Registered form is forbidden when `asyncFn` is set */
|
|
544
|
+
asyncFunctionName?: never;
|
|
545
|
+
});
|
|
546
|
+
/**
|
|
547
|
+
* Logical AND — all sub-conditions must be true.
|
|
548
|
+
*
|
|
549
|
+
* @public
|
|
550
|
+
*/
|
|
551
|
+
interface AndCondition {
|
|
552
|
+
type: 'and';
|
|
553
|
+
/** Sub-conditions that must all evaluate to true */
|
|
554
|
+
conditions: ConditionalExpression[];
|
|
458
555
|
}
|
|
459
556
|
/**
|
|
460
|
-
*
|
|
557
|
+
* Logical OR — at least one sub-condition must be true.
|
|
558
|
+
*
|
|
559
|
+
* @public
|
|
461
560
|
*/
|
|
462
|
-
|
|
463
|
-
|
|
561
|
+
interface OrCondition {
|
|
562
|
+
type: 'or';
|
|
563
|
+
/** Sub-conditions where at least one must evaluate to true */
|
|
564
|
+
conditions: ConditionalExpression[];
|
|
565
|
+
}
|
|
464
566
|
/**
|
|
465
|
-
*
|
|
466
|
-
*
|
|
567
|
+
* Discriminated union of all conditional expression types.
|
|
568
|
+
*
|
|
569
|
+
* Each variant only allows the properties relevant to its type,
|
|
570
|
+
* providing compile-time safety against invalid property combinations.
|
|
571
|
+
*
|
|
572
|
+
* @public
|
|
467
573
|
*/
|
|
468
|
-
type
|
|
574
|
+
type ConditionalExpression = FieldValueCondition | CustomCondition | JavascriptCondition | HttpCondition | AsyncCondition | AndCondition | OrCondition;
|
|
575
|
+
|
|
469
576
|
/**
|
|
470
|
-
*
|
|
471
|
-
*
|
|
472
|
-
*
|
|
577
|
+
* Logic configuration for container fields (page, group, row, array).
|
|
578
|
+
* Containers only support the 'hidden' logic type since they are layout containers,
|
|
579
|
+
* not form controls (they can't be readonly, disabled, or required).
|
|
473
580
|
*/
|
|
474
|
-
|
|
581
|
+
interface ContainerLogicConfig {
|
|
582
|
+
/** Only 'hidden' is supported for container fields */
|
|
583
|
+
type: 'hidden';
|
|
584
|
+
/**
|
|
585
|
+
* Condition that determines when this container is hidden.
|
|
586
|
+
* Can be a boolean or a conditional expression evaluated against form values.
|
|
587
|
+
*/
|
|
588
|
+
condition: ConditionalExpression | boolean;
|
|
589
|
+
}
|
|
590
|
+
|
|
475
591
|
/**
|
|
476
|
-
*
|
|
477
|
-
*
|
|
592
|
+
* An array item template is an array of allowed children that defines one OBJECT array item's fields.
|
|
593
|
+
* Used for object arrays where each item is `{ key1: value1, key2: value2, ... }`.
|
|
594
|
+
*/
|
|
595
|
+
type ArrayItemTemplate = readonly ArrayAllowedChildren[];
|
|
596
|
+
/**
|
|
597
|
+
* An array item definition can be either:
|
|
598
|
+
* - A single field (ArrayAllowedChildren) for PRIMITIVE array items - extracts field value directly
|
|
599
|
+
* - An array of fields (ArrayItemTemplate) for OBJECT array items - merges fields into object
|
|
600
|
+
*
|
|
601
|
+
* This allows support for:
|
|
602
|
+
* - Primitive arrays: `['tag1', 'tag2']`
|
|
603
|
+
* - Object arrays: `[{ name: 'Alice', email: '...' }]`
|
|
604
|
+
* - Heterogeneous arrays: `[{ value: 'x' }, 'y']` (mixed primitives and objects)
|
|
605
|
+
*/
|
|
606
|
+
type ArrayItemDefinition = ArrayAllowedChildren | ArrayItemTemplate;
|
|
607
|
+
/**
|
|
608
|
+
* Array field interface for creating dynamic field collections that map to array values.
|
|
609
|
+
*
|
|
610
|
+
* Key concepts:
|
|
611
|
+
* - The outer `fields` array defines INITIAL ITEMS (each element is one array item to render)
|
|
612
|
+
* - Each element can be either:
|
|
613
|
+
* - A single FieldDef (primitive item) - the field's value is extracted directly
|
|
614
|
+
* - An array of FieldDefs (object item) - fields are merged into an object
|
|
615
|
+
* - Items can have different field configurations (heterogeneous arrays supported)
|
|
616
|
+
* - Empty `fields: []` means no initial items - user adds via buttons with explicit templates
|
|
617
|
+
* - Initial values are declared via `value` property on each field definition
|
|
618
|
+
*
|
|
619
|
+
* @example
|
|
620
|
+
* // Primitive array: ['angular', 'typescript']
|
|
621
|
+
* {
|
|
622
|
+
* key: 'tags',
|
|
623
|
+
* type: 'array',
|
|
624
|
+
* fields: [
|
|
625
|
+
* { key: 'tag', type: 'input', value: 'angular' }, // Single field = primitive
|
|
626
|
+
* { key: 'tag', type: 'input', value: 'typescript' },
|
|
627
|
+
* ]
|
|
628
|
+
* }
|
|
629
|
+
*
|
|
630
|
+
* @example
|
|
631
|
+
* // Object array: [{ name: 'Alice', email: '...' }]
|
|
632
|
+
* {
|
|
633
|
+
* key: 'contacts',
|
|
634
|
+
* type: 'array',
|
|
635
|
+
* fields: [
|
|
636
|
+
* [ // Array = object
|
|
637
|
+
* { key: 'name', type: 'input', value: 'Alice' },
|
|
638
|
+
* { key: 'email', type: 'input', value: 'alice@example.com' }
|
|
639
|
+
* ],
|
|
640
|
+
* ]
|
|
641
|
+
* }
|
|
642
|
+
*
|
|
643
|
+
* @example
|
|
644
|
+
* // Heterogeneous array: [{ label: 'Structured' }, 'Simple']
|
|
645
|
+
* {
|
|
646
|
+
* key: 'items',
|
|
647
|
+
* type: 'array',
|
|
648
|
+
* fields: [
|
|
649
|
+
* [{ key: 'label', type: 'input', value: 'Structured' }], // Object item
|
|
650
|
+
* { key: 'value', type: 'input', value: 'Simple' }, // Primitive item
|
|
651
|
+
* ]
|
|
652
|
+
* }
|
|
653
|
+
*
|
|
654
|
+
* @example
|
|
655
|
+
* // Empty array (add items via buttons)
|
|
656
|
+
* {
|
|
657
|
+
* key: 'tags',
|
|
658
|
+
* type: 'array',
|
|
659
|
+
* fields: [] // No initial items - user adds via button with template
|
|
660
|
+
* }
|
|
661
|
+
*
|
|
662
|
+
* Nesting constraints: Arrays can contain rows, leaf fields, or groups (for object arrays),
|
|
663
|
+
* but NOT pages or other arrays. Runtime validation enforces these rules.
|
|
664
|
+
*
|
|
665
|
+
* Note: Arrays are container fields and do not support `meta` since they have no native form element.
|
|
666
|
+
*/
|
|
667
|
+
interface ArrayField<TFields extends readonly ArrayItemDefinition[] = readonly ArrayItemDefinition[]> extends FieldDef<never> {
|
|
668
|
+
type: 'array';
|
|
669
|
+
/**
|
|
670
|
+
* Array of item definitions. Each element defines one array item:
|
|
671
|
+
* - Single FieldDef = primitive item (field's value is extracted directly)
|
|
672
|
+
* - Array of FieldDefs = object item (fields are merged into an object)
|
|
673
|
+
* - Empty array `[]` = no initial items (add via buttons with explicit templates)
|
|
674
|
+
* - Items can have different configurations (heterogeneous arrays supported)
|
|
675
|
+
* - Initial values are set via `value` property on each field definition
|
|
676
|
+
*/
|
|
677
|
+
readonly fields: TFields;
|
|
678
|
+
/** Array fields do not have a label property **/
|
|
679
|
+
readonly label?: never;
|
|
680
|
+
/** Arrays do not support meta - they have no native form element **/
|
|
681
|
+
readonly meta?: never;
|
|
682
|
+
/**
|
|
683
|
+
* Logic configurations for conditional array visibility.
|
|
684
|
+
* Only 'hidden' type logic is supported for arrays.
|
|
685
|
+
*/
|
|
686
|
+
readonly logic?: ContainerLogicConfig[];
|
|
687
|
+
/**
|
|
688
|
+
* Minimum number of items required in the array.
|
|
689
|
+
* Validation fails if the array has fewer items than this value.
|
|
690
|
+
*/
|
|
691
|
+
readonly minLength?: number;
|
|
692
|
+
/**
|
|
693
|
+
* Maximum number of items allowed in the array.
|
|
694
|
+
* Validation fails if the array has more items than this value.
|
|
695
|
+
*/
|
|
696
|
+
readonly maxLength?: number;
|
|
697
|
+
}
|
|
698
|
+
/**
|
|
699
|
+
* Type guard for ArrayField with proper type narrowing.
|
|
700
|
+
* Validates that the field is an array type with a fields property that is an array.
|
|
701
|
+
* Fields can contain either single FieldDefs (primitive items) or arrays of FieldDefs (object items).
|
|
702
|
+
*/
|
|
703
|
+
declare function isArrayField(field: FieldDef<any>): field is ArrayField;
|
|
704
|
+
/**
|
|
705
|
+
* Configuration for auto-generated add/remove buttons in simplified array fields.
|
|
706
|
+
*/
|
|
707
|
+
interface ArrayButtonConfig {
|
|
708
|
+
/** Custom label for the button */
|
|
709
|
+
readonly label?: string;
|
|
710
|
+
/** Additional properties passed to the button component */
|
|
711
|
+
readonly props?: Record<string, unknown>;
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* Simplified array field interface for common use cases.
|
|
715
|
+
*
|
|
716
|
+
* Instead of manually specifying each item in `fields`, provide a `template` that defines
|
|
717
|
+
* the structure of a single item, and a `value` array with initial data.
|
|
718
|
+
* The library auto-generates add/remove buttons.
|
|
719
|
+
*
|
|
720
|
+
* Discriminant: `template` presence → simplified API; `Array.isArray(template)` → object vs primitive.
|
|
721
|
+
*
|
|
722
|
+
* @example
|
|
723
|
+
* // Primitive array: ['angular', 'typescript']
|
|
724
|
+
* {
|
|
725
|
+
* key: 'tags',
|
|
726
|
+
* type: 'array',
|
|
727
|
+
* template: { key: 'value', type: 'input', label: 'Tag', required: true },
|
|
728
|
+
* value: ['angular', 'typescript']
|
|
729
|
+
* }
|
|
730
|
+
*
|
|
731
|
+
* @example
|
|
732
|
+
* // Object array: [{ name: 'Jane', phone: '555' }]
|
|
733
|
+
* {
|
|
734
|
+
* key: 'contacts',
|
|
735
|
+
* type: 'array',
|
|
736
|
+
* template: [
|
|
737
|
+
* { key: 'name', type: 'input', label: 'Contact Name', required: true },
|
|
738
|
+
* { key: 'phone', type: 'input', label: 'Phone Number' },
|
|
739
|
+
* ],
|
|
740
|
+
* value: [{ name: 'Jane', phone: '555' }]
|
|
741
|
+
* }
|
|
742
|
+
*
|
|
743
|
+
* @example
|
|
744
|
+
* // Button customization / opt-out
|
|
745
|
+
* {
|
|
746
|
+
* key: 'tags',
|
|
747
|
+
* type: 'array',
|
|
748
|
+
* template: { key: 'value', type: 'input', label: 'Tag' },
|
|
749
|
+
* addButton: { label: 'Add Tag', props: { color: 'primary' } },
|
|
750
|
+
* removeButton: false
|
|
751
|
+
* }
|
|
752
|
+
*/
|
|
753
|
+
interface SimplifiedArrayField extends FieldDef<never> {
|
|
754
|
+
type: 'array';
|
|
755
|
+
/**
|
|
756
|
+
* Template defining the structure of a single array item.
|
|
757
|
+
* - Single field (ArrayAllowedChildren) → primitive array (each item is a single value)
|
|
758
|
+
* - Array of fields (readonly ArrayAllowedChildren[]) → object array (each item is an object)
|
|
759
|
+
*/
|
|
760
|
+
readonly template: ArrayAllowedChildren | readonly ArrayAllowedChildren[];
|
|
761
|
+
/** Initial values for the array. Each element creates one array item. */
|
|
762
|
+
readonly value?: readonly unknown[];
|
|
763
|
+
/**
|
|
764
|
+
* Configuration for the auto-generated "Add" button, or `false` to disable it.
|
|
765
|
+
* Defaults to a button with label "Add".
|
|
766
|
+
*/
|
|
767
|
+
readonly addButton?: ArrayButtonConfig | false;
|
|
768
|
+
/**
|
|
769
|
+
* Configuration for the auto-generated "Remove" button on each item, or `false` to disable it.
|
|
770
|
+
* Defaults to a button with label "Remove".
|
|
771
|
+
*/
|
|
772
|
+
readonly removeButton?: ArrayButtonConfig | false;
|
|
773
|
+
/** Simplified arrays do not support the label property */
|
|
774
|
+
readonly label?: never;
|
|
775
|
+
/** Simplified arrays do not support meta */
|
|
776
|
+
readonly meta?: never;
|
|
777
|
+
/**
|
|
778
|
+
* Logic configurations for conditional array visibility.
|
|
779
|
+
* Only 'hidden' type logic is supported for arrays.
|
|
780
|
+
*/
|
|
781
|
+
readonly logic?: ContainerLogicConfig[];
|
|
782
|
+
/**
|
|
783
|
+
* Minimum number of items required in the array.
|
|
784
|
+
* Validation fails if the array has fewer items than this value.
|
|
785
|
+
*/
|
|
786
|
+
readonly minLength?: number;
|
|
787
|
+
/**
|
|
788
|
+
* Maximum number of items allowed in the array.
|
|
789
|
+
* Validation fails if the array has more items than this value.
|
|
790
|
+
*/
|
|
791
|
+
readonly maxLength?: number;
|
|
792
|
+
/** Mutually exclusive with `template` — use `fields` for the full API instead */
|
|
793
|
+
readonly fields?: never;
|
|
794
|
+
}
|
|
795
|
+
/**
|
|
796
|
+
* Type guard for SimplifiedArrayField.
|
|
797
|
+
* Checks for `type: 'array'` with a `template` property (discriminant from full ArrayField).
|
|
798
|
+
*/
|
|
799
|
+
declare function isSimplifiedArrayField(field: FieldDef<any>): field is SimplifiedArrayField;
|
|
800
|
+
|
|
801
|
+
/**
|
|
802
|
+
* Group field interface for creating logical field groupings that map to object values
|
|
803
|
+
* Groups create nested form structures where child field values are collected into an object
|
|
804
|
+
* This is a programmatic grouping only - users cannot customize this field type
|
|
805
|
+
*
|
|
806
|
+
* TypeScript cannot enforce field nesting rules due to circular dependency limitations.
|
|
807
|
+
* For documentation: Groups should contain rows and leaf fields, but NOT pages or other groups.
|
|
808
|
+
* Runtime validation enforces these rules.
|
|
809
|
+
*
|
|
810
|
+
* Note: Groups are container fields and do not support `meta` since they have no native form element.
|
|
811
|
+
*/
|
|
812
|
+
interface GroupField<TFields extends readonly GroupAllowedChildren[] = readonly GroupAllowedChildren[]> extends FieldDef<never> {
|
|
813
|
+
type: 'group';
|
|
814
|
+
readonly fields: TFields;
|
|
815
|
+
/** Groups do not have a label property - they are logical containers only **/
|
|
816
|
+
readonly label?: never;
|
|
817
|
+
/** Groups do not support meta - they have no native form element **/
|
|
818
|
+
readonly meta?: never;
|
|
819
|
+
/**
|
|
820
|
+
* Logic configurations for conditional group visibility.
|
|
821
|
+
* Only 'hidden' type logic is supported for groups.
|
|
822
|
+
*/
|
|
823
|
+
readonly logic?: ContainerLogicConfig[];
|
|
824
|
+
}
|
|
825
|
+
/**
|
|
826
|
+
* Type guard for GroupField with proper type narrowing
|
|
827
|
+
*/
|
|
828
|
+
declare function isGroupField(field: FieldDef<any>): field is GroupField;
|
|
829
|
+
|
|
830
|
+
/**
|
|
831
|
+
* Scalar types supported by hidden fields.
|
|
832
|
+
* These are the primitive value types that can be stored.
|
|
833
|
+
*/
|
|
834
|
+
type HiddenScalar = string | number | boolean;
|
|
835
|
+
/**
|
|
836
|
+
* All value types supported by hidden fields.
|
|
837
|
+
* Supports both scalar values and arrays of scalars for storing
|
|
838
|
+
* multiple IDs or similar data.
|
|
839
|
+
*/
|
|
840
|
+
type HiddenValue = HiddenScalar | HiddenScalar[];
|
|
841
|
+
/**
|
|
842
|
+
* Hidden field definition for storing values without rendering UI.
|
|
843
|
+
*
|
|
478
844
|
* Hidden fields participate in the form schema and contribute to form values,
|
|
479
845
|
* but do not render any visible component. They are useful for:
|
|
480
846
|
* - Storing IDs when updating existing records
|
|
@@ -722,1442 +1088,1252 @@ type ExtractFields<T> = T extends {
|
|
|
722
1088
|
type InferFormValue$1<T> = ExtractFields<T> extends readonly RegisteredFieldTypes[] ? InferFormValueWithDepth<ExtractFields<T> extends RegisteredFieldTypes[] ? ExtractFields<T> : [...ExtractFields<T>], 5> : never;
|
|
723
1089
|
|
|
724
1090
|
/**
|
|
725
|
-
*
|
|
1091
|
+
* Custom validator function signature using Angular's public FieldContext API
|
|
726
1092
|
*
|
|
727
|
-
*
|
|
728
|
-
*
|
|
729
|
-
* This is intentional for v1: response mapping is purely about interpreting the HTTP response.
|
|
730
|
-
*/
|
|
731
|
-
interface HttpValidationResponseMapping {
|
|
732
|
-
/**
|
|
733
|
-
* Expression evaluated with scope `{ response }`. Must evaluate to `true` (strict boolean) to be valid.
|
|
734
|
-
*
|
|
735
|
-
* NOTE: This is the OPPOSITE of the function-based HttpCustomValidator.onSuccess
|
|
736
|
-
* convention, which maps successful HTTP responses to validation errors.
|
|
737
|
-
* Here, `=== true` means "validation passed". Non-boolean results are treated as invalid and trigger a warning.
|
|
738
|
-
*/
|
|
739
|
-
validWhen: string;
|
|
740
|
-
/** Error kind returned when validation fails — maps to field-level `validationMessages` */
|
|
741
|
-
errorKind: string;
|
|
742
|
-
/**
|
|
743
|
-
* Parameters to include in the validation error for message interpolation.
|
|
744
|
-
* Map of parameter names to expressions evaluated against `{ response }`.
|
|
745
|
-
*/
|
|
746
|
-
errorParams?: Record<string, string>;
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
/**
|
|
750
|
-
* Base configuration shared by all validators
|
|
751
|
-
*/
|
|
752
|
-
interface BaseValidatorConfig {
|
|
753
|
-
/** Conditional logic for when validator applies */
|
|
754
|
-
when?: ConditionalExpression;
|
|
755
|
-
}
|
|
756
|
-
/**
|
|
757
|
-
* Built-in validator configuration (required, email, min, max, etc.)
|
|
758
|
-
*/
|
|
759
|
-
interface BuiltInValidatorConfig extends BaseValidatorConfig {
|
|
760
|
-
/** Validator type identifier */
|
|
761
|
-
type: 'required' | 'email' | 'min' | 'max' | 'minLength' | 'maxLength' | 'pattern';
|
|
762
|
-
/** Static value for the validator (e.g., min value, pattern) */
|
|
763
|
-
value?: number | string | RegExp;
|
|
764
|
-
/** Dynamic value expression that evaluates to validator parameter */
|
|
765
|
-
expression?: string;
|
|
766
|
-
}
|
|
767
|
-
/**
|
|
768
|
-
* Custom validator configuration using Angular's public FieldContext API
|
|
769
|
-
* Returns ValidationError | ValidationError[] | null synchronously
|
|
770
|
-
*
|
|
771
|
-
* Supports two patterns:
|
|
772
|
-
* 1. Function-based: { type: 'custom', functionName: 'myValidator' }
|
|
773
|
-
* 2. Expression-based: { type: 'custom', expression: 'fieldValue === formValue.password', kind: 'passwordMismatch' }
|
|
774
|
-
*/
|
|
775
|
-
interface CustomValidatorConfig extends BaseValidatorConfig {
|
|
776
|
-
/** Validator type identifier */
|
|
777
|
-
type: 'custom';
|
|
778
|
-
/** Name of registered validator function (function-based pattern) */
|
|
779
|
-
functionName?: string;
|
|
780
|
-
/** Optional parameters to pass to validator function */
|
|
781
|
-
params?: Record<string, unknown>;
|
|
782
|
-
/** JavaScript expression to evaluate (expression-based pattern) */
|
|
783
|
-
expression?: string;
|
|
784
|
-
/** Error kind for expression-based validators - links to validationMessages */
|
|
785
|
-
kind?: string;
|
|
786
|
-
/**
|
|
787
|
-
* Parameters to include in error object for message interpolation (expression-based pattern)
|
|
788
|
-
* Map of parameter names to expressions that will be evaluated in the same context as the validation expression
|
|
789
|
-
*
|
|
790
|
-
* @example
|
|
791
|
-
* errorParams: {
|
|
792
|
-
* minValue: 'formValue.minValue',
|
|
793
|
-
* maxValue: 'formValue.maxValue'
|
|
794
|
-
* }
|
|
795
|
-
* // Allows message template: "Value must be between {{minValue}} and {{maxValue}}"
|
|
796
|
-
*/
|
|
797
|
-
errorParams?: Record<string, string>;
|
|
798
|
-
}
|
|
799
|
-
/**
|
|
800
|
-
* Async custom validator configuration using Angular's validateAsync API.
|
|
801
|
-
* Returns Observable<ValidationError | ValidationError[] | null>.
|
|
802
|
-
*
|
|
803
|
-
*/
|
|
804
|
-
interface AsyncValidatorConfig extends BaseValidatorConfig {
|
|
805
|
-
/** Validator type identifier. */
|
|
806
|
-
type: 'async';
|
|
807
|
-
/** Name of registered async validator function */
|
|
808
|
-
functionName: string;
|
|
809
|
-
/** Optional parameters to pass to validator function */
|
|
810
|
-
params?: Record<string, unknown>;
|
|
811
|
-
}
|
|
812
|
-
/**
|
|
813
|
-
* Function-based HTTP validator configuration — requires a registered function.
|
|
814
|
-
*
|
|
815
|
-
* Uses Angular's `validateHttp` API. The function is registered via
|
|
816
|
-
* `customFnConfig.httpValidators`.
|
|
817
|
-
*
|
|
818
|
-
* Discriminated from `DeclarativeHttpValidatorConfig` by the presence of `functionName`.
|
|
819
|
-
*
|
|
820
|
-
*/
|
|
821
|
-
interface FunctionHttpValidatorConfig extends BaseValidatorConfig {
|
|
822
|
-
/** Validator type identifier. */
|
|
823
|
-
type: 'http';
|
|
824
|
-
/** Name of registered HTTP validator configuration */
|
|
825
|
-
functionName: string;
|
|
826
|
-
/** Optional parameters to pass to HTTP validator */
|
|
827
|
-
params?: Record<string, unknown>;
|
|
828
|
-
}
|
|
829
|
-
/**
|
|
830
|
-
* Declarative HTTP validator configuration — fully JSON-serializable, no function registration required.
|
|
831
|
-
*
|
|
832
|
-
* Uses `HttpRequestConfig` to define the HTTP request and `HttpValidationResponseMapping`
|
|
833
|
-
* to interpret the response as a validation result. Powered by Angular's `validateHttp` API.
|
|
834
|
-
*
|
|
835
|
-
* Discriminated from `FunctionHttpValidatorConfig` by the presence of `http` + `responseMapping`
|
|
836
|
-
* (and absence of `functionName`).
|
|
837
|
-
*/
|
|
838
|
-
interface DeclarativeHttpValidatorConfig extends BaseValidatorConfig {
|
|
839
|
-
/** Validator type identifier */
|
|
840
|
-
type: 'http';
|
|
841
|
-
/** HTTP request configuration with expression-based query params and body */
|
|
842
|
-
http: HttpRequestConfig;
|
|
843
|
-
/** Mapping that interprets the HTTP response as a validation result */
|
|
844
|
-
responseMapping: HttpValidationResponseMapping;
|
|
845
|
-
}
|
|
846
|
-
/**
|
|
847
|
-
* Configuration for signal forms validator functions that can be serialized from API.
|
|
848
|
-
* Discriminated union type for type-safe validator configuration.
|
|
849
|
-
*
|
|
850
|
-
* Note: `FunctionHttpValidatorConfig` and `DeclarativeHttpValidatorConfig` both use `type: 'http'`.
|
|
851
|
-
* They are discriminated by property presence: `functionName` → function-based, `http` → declarative.
|
|
852
|
-
* Use `isFunctionHttpValidator()` for type-safe narrowing when `type` alone is insufficient.
|
|
853
|
-
*/
|
|
854
|
-
type ValidatorConfig = BuiltInValidatorConfig | CustomValidatorConfig | AsyncValidatorConfig | FunctionHttpValidatorConfig | DeclarativeHttpValidatorConfig;
|
|
855
|
-
|
|
856
|
-
/**
|
|
857
|
-
* Special form-state conditions for button disabled logic.
|
|
858
|
-
*
|
|
859
|
-
* These conditions evaluate form or page-level state rather than field values,
|
|
860
|
-
* and are primarily used for controlling button disabled states.
|
|
861
|
-
*
|
|
862
|
-
* @example
|
|
863
|
-
* ```typescript
|
|
864
|
-
* // Disable submit button when form is invalid or submitting
|
|
865
|
-
* {
|
|
866
|
-
* key: 'submit',
|
|
867
|
-
* type: 'submit',
|
|
868
|
-
* label: 'Submit',
|
|
869
|
-
* logic: [
|
|
870
|
-
* { type: 'disabled', condition: 'formInvalid' },
|
|
871
|
-
* { type: 'disabled', condition: 'formSubmitting' },
|
|
872
|
-
* ]
|
|
873
|
-
* }
|
|
874
|
-
*
|
|
875
|
-
* // Disable next button when current page is invalid
|
|
876
|
-
* nextButton({
|
|
877
|
-
* key: 'next',
|
|
878
|
-
* label: 'Next',
|
|
879
|
-
* logic: [
|
|
880
|
-
* { type: 'disabled', condition: 'pageInvalid' },
|
|
881
|
-
* ]
|
|
882
|
-
* })
|
|
883
|
-
* ```
|
|
884
|
-
*
|
|
885
|
-
* @public
|
|
886
|
-
*/
|
|
887
|
-
type FormStateCondition =
|
|
888
|
-
/** True when form.valid() === false */
|
|
889
|
-
'formInvalid'
|
|
890
|
-
/** True when form.submitting() === true */
|
|
891
|
-
| 'formSubmitting'
|
|
892
|
-
/** True when fields on the current page are invalid (for paged forms) */
|
|
893
|
-
| 'pageInvalid';
|
|
894
|
-
/**
|
|
895
|
-
* Logic type for controlling field state (hidden, readonly, disabled, required).
|
|
896
|
-
*
|
|
897
|
-
* @public
|
|
898
|
-
*/
|
|
899
|
-
type StateLogicType = 'hidden' | 'readonly' | 'disabled' | 'required';
|
|
900
|
-
/**
|
|
901
|
-
* Base configuration for conditional field state logic.
|
|
902
|
-
*
|
|
903
|
-
* @internal
|
|
904
|
-
*/
|
|
905
|
-
interface BaseStateLogicConfig {
|
|
906
|
-
/**
|
|
907
|
-
* Logic type identifier for field state.
|
|
908
|
-
*
|
|
909
|
-
* - `hidden`: Hide the field from view (still participates in form state)
|
|
910
|
-
* - `readonly`: Make the field read-only
|
|
911
|
-
* - `disabled`: Disable user interaction
|
|
912
|
-
* - `required`: Make the field required
|
|
913
|
-
*/
|
|
914
|
-
type: StateLogicType;
|
|
915
|
-
/**
|
|
916
|
-
* Condition that determines when this logic applies.
|
|
917
|
-
*
|
|
918
|
-
* Can be:
|
|
919
|
-
* - `boolean`: Static value (always applies or never applies)
|
|
920
|
-
* - `ConditionalExpression`: Expression evaluated against field/form values
|
|
921
|
-
* - `FormStateCondition`: Special form/page state check (for buttons)
|
|
922
|
-
*
|
|
923
|
-
* @example
|
|
924
|
-
* ```typescript
|
|
925
|
-
* // Static condition
|
|
926
|
-
* condition: true
|
|
927
|
-
*
|
|
928
|
-
* // Field value condition
|
|
929
|
-
* condition: {
|
|
930
|
-
* type: 'fieldValue',
|
|
931
|
-
* fieldPath: 'status',
|
|
932
|
-
* operator: 'equals',
|
|
933
|
-
* value: 'locked'
|
|
934
|
-
* }
|
|
935
|
-
*
|
|
936
|
-
* // Form state condition (for buttons)
|
|
937
|
-
* condition: 'formSubmitting'
|
|
938
|
-
* ```
|
|
939
|
-
*/
|
|
940
|
-
condition: ConditionalExpression | boolean | FormStateCondition;
|
|
941
|
-
}
|
|
942
|
-
/**
|
|
943
|
-
* State logic that evaluates immediately on change (default).
|
|
944
|
-
*
|
|
945
|
-
* @internal
|
|
946
|
-
*/
|
|
947
|
-
interface ImmediateStateLogicConfig extends BaseStateLogicConfig {
|
|
948
|
-
/**
|
|
949
|
-
* Trigger for immediate evaluation.
|
|
950
|
-
* @default 'onChange'
|
|
951
|
-
*/
|
|
952
|
-
trigger?: 'onChange';
|
|
953
|
-
/** Not allowed for onChange trigger */
|
|
954
|
-
debounceMs?: never;
|
|
955
|
-
}
|
|
956
|
-
/**
|
|
957
|
-
* State logic that evaluates after a debounce period.
|
|
958
|
-
*
|
|
959
|
-
* @internal
|
|
960
|
-
*/
|
|
961
|
-
interface DebouncedStateLogicConfig extends BaseStateLogicConfig {
|
|
962
|
-
/**
|
|
963
|
-
* Trigger for debounced evaluation.
|
|
964
|
-
* Evaluates after the value has stabilized for the debounce duration.
|
|
965
|
-
*/
|
|
966
|
-
trigger: 'debounced';
|
|
967
|
-
/**
|
|
968
|
-
* Debounce duration in milliseconds.
|
|
969
|
-
* @default 500
|
|
970
|
-
*/
|
|
971
|
-
debounceMs?: number;
|
|
972
|
-
}
|
|
973
|
-
/**
|
|
974
|
-
* Configuration for conditional field state logic.
|
|
975
|
-
*
|
|
976
|
-
* Defines how field behavior changes based on conditions.
|
|
977
|
-
* Supports hiding, disabling, making readonly, or requiring fields
|
|
978
|
-
* based on form state or field values.
|
|
979
|
-
*
|
|
980
|
-
* @example
|
|
981
|
-
* ```typescript
|
|
982
|
-
* // Hide email field when contact method is not email
|
|
983
|
-
* {
|
|
984
|
-
* type: 'hidden',
|
|
985
|
-
* condition: {
|
|
986
|
-
* type: 'fieldValue',
|
|
987
|
-
* fieldPath: 'contactMethod',
|
|
988
|
-
* operator: 'notEquals',
|
|
989
|
-
* value: 'email'
|
|
990
|
-
* }
|
|
991
|
-
* }
|
|
992
|
-
*
|
|
993
|
-
* // Disable button when form is submitting
|
|
994
|
-
* {
|
|
995
|
-
* type: 'disabled',
|
|
996
|
-
* condition: 'formSubmitting'
|
|
997
|
-
* }
|
|
998
|
-
*
|
|
999
|
-
* // Debounced visibility (avoids flicker during rapid typing)
|
|
1000
|
-
* {
|
|
1001
|
-
* type: 'hidden',
|
|
1002
|
-
* trigger: 'debounced',
|
|
1003
|
-
* debounceMs: 300,
|
|
1004
|
-
* condition: {
|
|
1005
|
-
* type: 'fieldValue',
|
|
1006
|
-
* fieldPath: 'search',
|
|
1007
|
-
* operator: 'isEmpty'
|
|
1008
|
-
* }
|
|
1009
|
-
* }
|
|
1010
|
-
* ```
|
|
1011
|
-
*
|
|
1012
|
-
* @public
|
|
1013
|
-
*/
|
|
1014
|
-
type StateLogicConfig = ImmediateStateLogicConfig | DebouncedStateLogicConfig;
|
|
1015
|
-
/**
|
|
1016
|
-
* Shared fields that appear on all derivation logic config variants.
|
|
1017
|
-
*
|
|
1018
|
-
* @internal
|
|
1019
|
-
*/
|
|
1020
|
-
interface SharedDerivationFields {
|
|
1021
|
-
/**
|
|
1022
|
-
* Logic type identifier for value derivation.
|
|
1023
|
-
*/
|
|
1024
|
-
type: 'derivation';
|
|
1025
|
-
/**
|
|
1026
|
-
* Target property name for property derivation.
|
|
1027
|
-
*
|
|
1028
|
-
* When set, this derivation targets a field property (like `minDate`, `options`,
|
|
1029
|
-
* `label`, `placeholder`) instead of the field's value. The derivation is routed
|
|
1030
|
-
* to the property derivation pipeline.
|
|
1031
|
-
*
|
|
1032
|
-
* When absent, the derivation targets the field's value (default behavior).
|
|
1033
|
-
*
|
|
1034
|
-
* **Depth limit (max 2 levels):** Only simple and single-dot-nested paths are
|
|
1035
|
-
* supported. Paths with 2+ dots (e.g., `'a.b.c'`) will throw at runtime.
|
|
1036
|
-
*
|
|
1037
|
-
* @example
|
|
1038
|
-
* ```typescript
|
|
1039
|
-
* // Property derivation (new unified API)
|
|
1040
|
-
* {
|
|
1041
|
-
* key: 'endDate',
|
|
1042
|
-
* type: 'datepicker',
|
|
1043
|
-
* logic: [{
|
|
1044
|
-
* type: 'derivation',
|
|
1045
|
-
* targetProperty: 'minDate',
|
|
1046
|
-
* expression: 'formValue.startDate'
|
|
1047
|
-
* }]
|
|
1048
|
-
* }
|
|
1049
|
-
*
|
|
1050
|
-
* // Value derivation (no targetProperty — existing behavior)
|
|
1051
|
-
* {
|
|
1052
|
-
* key: 'total',
|
|
1053
|
-
* type: 'input',
|
|
1054
|
-
* logic: [{
|
|
1055
|
-
* type: 'derivation',
|
|
1056
|
-
* expression: 'formValue.quantity * formValue.unitPrice'
|
|
1057
|
-
* }]
|
|
1058
|
-
* }
|
|
1059
|
-
* ```
|
|
1060
|
-
*/
|
|
1061
|
-
targetProperty?: string;
|
|
1062
|
-
/**
|
|
1063
|
-
* Optional name for this derivation for debugging purposes.
|
|
1064
|
-
*
|
|
1065
|
-
* When provided, this name appears in derivation debug logs,
|
|
1066
|
-
* making it easier to identify specific derivations in complex forms.
|
|
1067
|
-
*
|
|
1068
|
-
* @example
|
|
1069
|
-
* ```typescript
|
|
1070
|
-
* {
|
|
1071
|
-
* key: 'lineTotal',
|
|
1072
|
-
* type: 'input',
|
|
1073
|
-
* logic: [{
|
|
1074
|
-
* type: 'derivation',
|
|
1075
|
-
* debugName: 'Calculate line total',
|
|
1076
|
-
* expression: 'formValue.quantity * formValue.unitPrice'
|
|
1077
|
-
* }]
|
|
1078
|
-
* }
|
|
1079
|
-
* ```
|
|
1080
|
-
*/
|
|
1081
|
-
debugName?: string;
|
|
1082
|
-
/**
|
|
1083
|
-
* Condition that determines when this derivation applies.
|
|
1084
|
-
*
|
|
1085
|
-
* Can be:
|
|
1086
|
-
* - `boolean`: Static value (always applies or never applies)
|
|
1087
|
-
* - `ConditionalExpression`: Expression evaluated against field/form values
|
|
1088
|
-
*
|
|
1089
|
-
* Defaults to `true` (always apply).
|
|
1090
|
-
*
|
|
1091
|
-
* Note: FormStateCondition is not supported for derivations.
|
|
1092
|
-
*
|
|
1093
|
-
* @example
|
|
1094
|
-
* ```typescript
|
|
1095
|
-
* // Always compute (default)
|
|
1096
|
-
* condition: true
|
|
1097
|
-
*
|
|
1098
|
-
* // Conditional derivation
|
|
1099
|
-
* condition: {
|
|
1100
|
-
* type: 'fieldValue',
|
|
1101
|
-
* fieldPath: 'country',
|
|
1102
|
-
* operator: 'equals',
|
|
1103
|
-
* value: 'USA'
|
|
1104
|
-
* }
|
|
1105
|
-
* ```
|
|
1106
|
-
*/
|
|
1107
|
-
condition?: ConditionalExpression | boolean;
|
|
1108
|
-
/**
|
|
1109
|
-
* When true, the derivation stops running after the user manually
|
|
1110
|
-
* edits the target field.
|
|
1111
|
-
*
|
|
1112
|
-
* This is useful for "smart defaults" — values that should be
|
|
1113
|
-
* auto-filled initially but respected once the user explicitly changes them.
|
|
1114
|
-
*
|
|
1115
|
-
* Uses the field's `dirty()` signal to detect user modification.
|
|
1116
|
-
* Derivations write directly to `value.set()` which does not trigger
|
|
1117
|
-
* `markAsDirty()`, so `dirty === true` reliably indicates a user edit.
|
|
1118
|
-
*
|
|
1119
|
-
* @example
|
|
1120
|
-
* ```typescript
|
|
1121
|
-
* // Auto-fill display name from first + last name, but stop if user edits it
|
|
1122
|
-
* {
|
|
1123
|
-
* key: 'displayName',
|
|
1124
|
-
* logic: [{
|
|
1125
|
-
* type: 'derivation',
|
|
1126
|
-
* expression: 'formValue.firstName + " " + formValue.lastName',
|
|
1127
|
-
* stopOnUserOverride: true
|
|
1128
|
-
* }]
|
|
1129
|
-
* }
|
|
1130
|
-
* ```
|
|
1131
|
-
*/
|
|
1132
|
-
stopOnUserOverride?: boolean;
|
|
1133
|
-
/**
|
|
1134
|
-
* When true (and `stopOnUserOverride` is also true), clears the
|
|
1135
|
-
* user-override flag when any dependency of this derivation changes,
|
|
1136
|
-
* allowing the derivation to run again.
|
|
1137
|
-
*
|
|
1138
|
-
* This is useful when a user override should only persist until the
|
|
1139
|
-
* underlying data changes — e.g., when switching countries, the
|
|
1140
|
-
* phone prefix should re-derive even if the user previously edited it.
|
|
1141
|
-
*
|
|
1142
|
-
* @example
|
|
1143
|
-
* ```typescript
|
|
1144
|
-
* {
|
|
1145
|
-
* key: 'phonePrefix',
|
|
1146
|
-
* logic: [{
|
|
1147
|
-
* type: 'derivation',
|
|
1148
|
-
* value: '+1',
|
|
1149
|
-
* condition: { type: 'fieldValue', fieldPath: 'country', operator: 'equals', value: 'USA' },
|
|
1150
|
-
* stopOnUserOverride: true,
|
|
1151
|
-
* reEngageOnDependencyChange: true,
|
|
1152
|
-
* dependsOn: ['country']
|
|
1153
|
-
* }]
|
|
1154
|
-
* }
|
|
1155
|
-
* ```
|
|
1156
|
-
*/
|
|
1157
|
-
reEngageOnDependencyChange?: boolean;
|
|
1158
|
-
}
|
|
1159
|
-
/**
|
|
1160
|
-
* Trigger variants for derivation timing.
|
|
1161
|
-
* @internal
|
|
1162
|
-
*/
|
|
1163
|
-
type ImmediateDerivationTrigger = {
|
|
1164
|
-
trigger?: 'onChange';
|
|
1165
|
-
debounceMs?: never;
|
|
1166
|
-
};
|
|
1167
|
-
type DebouncedDerivationTrigger = {
|
|
1168
|
-
trigger: 'debounced';
|
|
1169
|
-
debounceMs?: number;
|
|
1170
|
-
};
|
|
1171
|
-
/**
|
|
1172
|
-
* Base for HTTP derivations. `source: 'http'` is required.
|
|
1173
|
-
* TypeScript enforces `dependsOn` and `responseExpression` at the type level.
|
|
1174
|
-
*
|
|
1175
|
-
* @internal
|
|
1176
|
-
*/
|
|
1177
|
-
interface HttpDerivationBase extends SharedDerivationFields {
|
|
1178
|
-
/** Identifies this derivation as HTTP-driven. */
|
|
1179
|
-
source: 'http';
|
|
1180
|
-
/**
|
|
1181
|
-
* HTTP request configuration for server-driven derivations.
|
|
1182
|
-
*
|
|
1183
|
-
* The request is sent when dependencies change, with automatic
|
|
1184
|
-
* debouncing and cancellation of in-flight requests.
|
|
1185
|
-
*
|
|
1186
|
-
* Configure debounce via the `trigger: 'debounced'` + `debounceMs` mechanism.
|
|
1187
|
-
*
|
|
1188
|
-
* @example
|
|
1189
|
-
* ```typescript
|
|
1190
|
-
* {
|
|
1191
|
-
* key: 'exchangeRate',
|
|
1192
|
-
* logic: [{
|
|
1193
|
-
* type: 'derivation',
|
|
1194
|
-
* source: 'http',
|
|
1195
|
-
* http: {
|
|
1196
|
-
* url: '/api/exchange-rate',
|
|
1197
|
-
* method: 'GET',
|
|
1198
|
-
* queryParams: {
|
|
1199
|
-
* from: 'formValue.sourceCurrency',
|
|
1200
|
-
* to: 'formValue.targetCurrency',
|
|
1201
|
-
* },
|
|
1202
|
-
* },
|
|
1203
|
-
* responseExpression: 'response.rate',
|
|
1204
|
-
* dependsOn: ['sourceCurrency', 'targetCurrency'],
|
|
1205
|
-
* }]
|
|
1206
|
-
* }
|
|
1207
|
-
* ```
|
|
1208
|
-
*/
|
|
1209
|
-
http: HttpRequestConfig;
|
|
1210
|
-
/**
|
|
1211
|
-
* Explicit field dependencies. Required for HTTP derivations to prevent
|
|
1212
|
-
* wildcard triggering on every keystroke.
|
|
1213
|
-
*
|
|
1214
|
-
* The wildcard token `'*'` is rejected. The structural token `'$group'`
|
|
1215
|
-
* (and `'$group.X'`) is allowed and resolves to the field's parent container
|
|
1216
|
-
* path — be aware this fires whenever any sibling under that group changes,
|
|
1217
|
-
* so the caller owns the request frequency (consider `trigger: 'debounced'`).
|
|
1218
|
-
*/
|
|
1219
|
-
dependsOn: string[];
|
|
1220
|
-
/**
|
|
1221
|
-
* Expression to extract the derived value from the HTTP response.
|
|
1222
|
-
*
|
|
1223
|
-
* Evaluated via `ExpressionParser` with `{ response }` as the evaluation scope.
|
|
1224
|
-
*
|
|
1225
|
-
* @example
|
|
1226
|
-
* ```typescript
|
|
1227
|
-
* responseExpression: 'response.rate'
|
|
1228
|
-
* responseExpression: 'response.data.suggestedPrice'
|
|
1229
|
-
* ```
|
|
1230
|
-
*/
|
|
1231
|
-
responseExpression: string;
|
|
1232
|
-
value?: never;
|
|
1233
|
-
expression?: never;
|
|
1234
|
-
functionName?: never;
|
|
1235
|
-
asyncFunctionName?: never;
|
|
1236
|
-
}
|
|
1237
|
-
/**
|
|
1238
|
-
* Base for async function derivations. `source: 'asyncFunction'` is required.
|
|
1239
|
-
* TypeScript enforces `dependsOn` at the type level.
|
|
1240
|
-
*
|
|
1241
|
-
* @internal
|
|
1242
|
-
*/
|
|
1243
|
-
interface AsyncFunctionDerivationBase extends SharedDerivationFields {
|
|
1244
|
-
/** Identifies this derivation as async-function-driven. */
|
|
1245
|
-
source: 'asyncFunction';
|
|
1246
|
-
/**
|
|
1247
|
-
* Name of a registered async derivation function.
|
|
1248
|
-
*
|
|
1249
|
-
* The function receives the evaluation context and returns a Promise or Observable
|
|
1250
|
-
* of the derived value. Register functions in `customFnConfig.asyncDerivations`.
|
|
1251
|
-
*
|
|
1252
|
-
* @example
|
|
1253
|
-
* ```typescript
|
|
1254
|
-
* {
|
|
1255
|
-
* key: 'suggestedPrice',
|
|
1256
|
-
* logic: [{
|
|
1257
|
-
* type: 'derivation',
|
|
1258
|
-
* source: 'asyncFunction',
|
|
1259
|
-
* asyncFunctionName: 'fetchSuggestedPrice',
|
|
1260
|
-
* dependsOn: ['productId', 'quantity'],
|
|
1261
|
-
* }]
|
|
1262
|
-
* }
|
|
1263
|
-
* ```
|
|
1264
|
-
*/
|
|
1265
|
-
asyncFunctionName: string;
|
|
1266
|
-
/**
|
|
1267
|
-
* Explicit field dependencies. Required for async derivations to prevent
|
|
1268
|
-
* wildcard triggering on every form change.
|
|
1269
|
-
*
|
|
1270
|
-
* The wildcard token `'*'` is rejected. The structural token `'$group'`
|
|
1271
|
-
* (and `'$group.X'`) is allowed and resolves to the field's parent container
|
|
1272
|
-
* path — be aware this fires whenever any sibling under that group changes,
|
|
1273
|
-
* so the caller owns the invocation frequency (handle this via
|
|
1274
|
-
* `trigger: 'debounced'` or via debouncing inside the async function).
|
|
1275
|
-
*/
|
|
1276
|
-
dependsOn: string[];
|
|
1277
|
-
value?: never;
|
|
1278
|
-
expression?: never;
|
|
1279
|
-
functionName?: never;
|
|
1280
|
-
http?: never;
|
|
1281
|
-
responseExpression?: never;
|
|
1282
|
-
}
|
|
1283
|
-
/**
|
|
1284
|
-
* Base for expression derivations.
|
|
1285
|
-
*
|
|
1286
|
-
* @internal
|
|
1287
|
-
*/
|
|
1288
|
-
interface ExpressionDerivationBase extends SharedDerivationFields {
|
|
1289
|
-
source?: never;
|
|
1290
|
-
/**
|
|
1291
|
-
* JavaScript expression to evaluate for the derived value.
|
|
1292
|
-
*
|
|
1293
|
-
* Has access to `formValue` object containing all form values.
|
|
1294
|
-
* For array fields, `formValue` is scoped to the current array item.
|
|
1295
|
-
* Uses the same secure AST-based parser as other expressions.
|
|
1296
|
-
*
|
|
1297
|
-
* @example
|
|
1298
|
-
* ```typescript
|
|
1299
|
-
* expression: 'formValue.quantity * formValue.unitPrice'
|
|
1300
|
-
* expression: 'formValue.firstName + " " + formValue.lastName'
|
|
1301
|
-
* expression: 'formValue.price * (1 - formValue.discount / 100)'
|
|
1302
|
-
* ```
|
|
1303
|
-
*/
|
|
1304
|
-
expression: string;
|
|
1305
|
-
/**
|
|
1306
|
-
* Explicit field dependencies for expressions.
|
|
1307
|
-
*
|
|
1308
|
-
* For `expression`, dependencies are automatically extracted from the expression.
|
|
1309
|
-
* Provide `dependsOn` to override automatic detection for complex expressions.
|
|
1310
|
-
*
|
|
1311
|
-
* @example
|
|
1312
|
-
* ```typescript
|
|
1313
|
-
* // Override automatic detection for complex expressions
|
|
1314
|
-
* {
|
|
1315
|
-
* key: 'total',
|
|
1316
|
-
* logic: [{
|
|
1317
|
-
* type: 'derivation',
|
|
1318
|
-
* expression: 'calculateTotal(formValue)',
|
|
1319
|
-
* dependsOn: ['quantity', 'unitPrice', 'discount']
|
|
1320
|
-
* }]
|
|
1321
|
-
* }
|
|
1322
|
-
* ```
|
|
1323
|
-
*/
|
|
1324
|
-
dependsOn?: string[];
|
|
1325
|
-
value?: never;
|
|
1326
|
-
functionName?: never;
|
|
1327
|
-
http?: never;
|
|
1328
|
-
asyncFunctionName?: never;
|
|
1329
|
-
responseExpression?: never;
|
|
1330
|
-
}
|
|
1331
|
-
/**
|
|
1332
|
-
* Base for value derivations.
|
|
1333
|
-
*
|
|
1334
|
-
* @internal
|
|
1335
|
-
*/
|
|
1336
|
-
interface ValueDerivationBase extends SharedDerivationFields {
|
|
1337
|
-
source?: never;
|
|
1338
|
-
/**
|
|
1339
|
-
* Static value to set on this field.
|
|
1340
|
-
*
|
|
1341
|
-
* Use when the derived value is a constant.
|
|
1342
|
-
*
|
|
1343
|
-
* @example
|
|
1344
|
-
* ```typescript
|
|
1345
|
-
* value: '+1' // String
|
|
1346
|
-
* value: 100 // Number
|
|
1347
|
-
* value: true // Boolean
|
|
1348
|
-
* value: { code: 'US' } // Object
|
|
1349
|
-
* ```
|
|
1350
|
-
*/
|
|
1351
|
-
value: unknown;
|
|
1352
|
-
/**
|
|
1353
|
-
* Explicit field dependencies for value derivations.
|
|
1354
|
-
*
|
|
1355
|
-
* For `value` (static), no dependencies are needed.
|
|
1356
|
-
* Provide `dependsOn` to conditionally re-evaluate when specific fields change.
|
|
1357
|
-
*/
|
|
1358
|
-
dependsOn?: string[];
|
|
1359
|
-
expression?: never;
|
|
1360
|
-
functionName?: never;
|
|
1361
|
-
http?: never;
|
|
1362
|
-
asyncFunctionName?: never;
|
|
1363
|
-
responseExpression?: never;
|
|
1364
|
-
}
|
|
1365
|
-
/**
|
|
1366
|
-
* Base for custom sync function derivations.
|
|
1367
|
-
*
|
|
1368
|
-
* @internal
|
|
1369
|
-
*/
|
|
1370
|
-
interface FunctionDerivationBase extends SharedDerivationFields {
|
|
1371
|
-
source?: never;
|
|
1372
|
-
/**
|
|
1373
|
-
* Name of a registered custom derivation function.
|
|
1374
|
-
*
|
|
1375
|
-
* The function receives the evaluation context and returns the derived value.
|
|
1376
|
-
* Register functions in `customFnConfig.derivations`.
|
|
1377
|
-
*
|
|
1378
|
-
* @example
|
|
1379
|
-
* ```typescript
|
|
1380
|
-
* functionName: 'getCurrencyForCountry'
|
|
1381
|
-
* functionName: 'calculateTax'
|
|
1382
|
-
* functionName: 'formatPhoneNumber'
|
|
1383
|
-
* ```
|
|
1384
|
-
*/
|
|
1385
|
-
functionName: string;
|
|
1386
|
-
/**
|
|
1387
|
-
* Explicit field dependencies for function derivations.
|
|
1388
|
-
*
|
|
1389
|
-
* If not provided, defaults to all fields ('*').
|
|
1390
|
-
* Specify `dependsOn` for better performance when the function only
|
|
1391
|
-
* depends on a subset of fields.
|
|
1392
|
-
*
|
|
1393
|
-
* @example
|
|
1394
|
-
* ```typescript
|
|
1395
|
-
* // Only re-evaluate when country changes
|
|
1396
|
-
* {
|
|
1397
|
-
* key: 'currency',
|
|
1398
|
-
* logic: [{
|
|
1399
|
-
* type: 'derivation',
|
|
1400
|
-
* functionName: 'getCurrencyForCountry',
|
|
1401
|
-
* dependsOn: ['country']
|
|
1402
|
-
* }]
|
|
1403
|
-
* }
|
|
1404
|
-
* ```
|
|
1405
|
-
*/
|
|
1406
|
-
dependsOn?: string[];
|
|
1407
|
-
value?: never;
|
|
1408
|
-
expression?: never;
|
|
1409
|
-
http?: never;
|
|
1410
|
-
asyncFunctionName?: never;
|
|
1411
|
-
responseExpression?: never;
|
|
1412
|
-
}
|
|
1413
|
-
/**
|
|
1414
|
-
* HTTP derivation that evaluates immediately on change (default).
|
|
1415
|
-
* @internal
|
|
1416
|
-
*/
|
|
1417
|
-
type OnChangeHttpDerivationLogicConfig = HttpDerivationBase & ImmediateDerivationTrigger;
|
|
1418
|
-
/**
|
|
1419
|
-
* HTTP derivation that evaluates after a debounce period.
|
|
1420
|
-
* @internal
|
|
1421
|
-
*/
|
|
1422
|
-
type DebouncedHttpDerivationLogicConfig = HttpDerivationBase & DebouncedDerivationTrigger;
|
|
1423
|
-
/**
|
|
1424
|
-
* Async function derivation that evaluates immediately on change (default).
|
|
1425
|
-
* @internal
|
|
1426
|
-
*/
|
|
1427
|
-
type OnChangeAsyncFunctionDerivationLogicConfig = AsyncFunctionDerivationBase & ImmediateDerivationTrigger;
|
|
1428
|
-
/**
|
|
1429
|
-
* Async function derivation that evaluates after a debounce period.
|
|
1430
|
-
* @internal
|
|
1431
|
-
*/
|
|
1432
|
-
type DebouncedAsyncFunctionDerivationLogicConfig = AsyncFunctionDerivationBase & DebouncedDerivationTrigger;
|
|
1433
|
-
/**
|
|
1434
|
-
* Expression derivation that evaluates immediately on change (default).
|
|
1435
|
-
* @internal
|
|
1436
|
-
*/
|
|
1437
|
-
type OnChangeExpressionDerivationLogicConfig = ExpressionDerivationBase & ImmediateDerivationTrigger;
|
|
1438
|
-
/**
|
|
1439
|
-
* Expression derivation that evaluates after a debounce period.
|
|
1440
|
-
* @internal
|
|
1441
|
-
*/
|
|
1442
|
-
type DebouncedExpressionDerivationLogicConfig = ExpressionDerivationBase & DebouncedDerivationTrigger;
|
|
1443
|
-
/**
|
|
1444
|
-
* Value derivation that evaluates immediately on change (default).
|
|
1445
|
-
* @internal
|
|
1446
|
-
*/
|
|
1447
|
-
type OnChangeValueDerivationLogicConfig = ValueDerivationBase & ImmediateDerivationTrigger;
|
|
1448
|
-
/**
|
|
1449
|
-
* Value derivation that evaluates after a debounce period.
|
|
1450
|
-
* @internal
|
|
1451
|
-
*/
|
|
1452
|
-
type DebouncedValueDerivationLogicConfig = ValueDerivationBase & DebouncedDerivationTrigger;
|
|
1453
|
-
/**
|
|
1454
|
-
* Function derivation that evaluates immediately on change (default).
|
|
1455
|
-
* @internal
|
|
1456
|
-
*/
|
|
1457
|
-
type OnChangeFunctionDerivationLogicConfig = FunctionDerivationBase & ImmediateDerivationTrigger;
|
|
1458
|
-
/**
|
|
1459
|
-
* Function derivation that evaluates after a debounce period.
|
|
1460
|
-
* @internal
|
|
1461
|
-
*/
|
|
1462
|
-
type DebouncedFunctionDerivationLogicConfig = FunctionDerivationBase & DebouncedDerivationTrigger;
|
|
1463
|
-
/**
|
|
1464
|
-
* Configuration for value derivation logic.
|
|
1093
|
+
* Takes FieldContext (full Angular context) and optional params, returns validation error(s) or null.
|
|
1094
|
+
* Provides access to field state, form hierarchy, and other fields for validation logic.
|
|
1465
1095
|
*
|
|
1466
|
-
*
|
|
1467
|
-
*
|
|
1468
|
-
*
|
|
1096
|
+
* **Use FieldContext public APIs to access:**
|
|
1097
|
+
* - Current field value: `ctx.value()`
|
|
1098
|
+
* - Field state: `ctx.state` (errors, touched, dirty, etc.)
|
|
1099
|
+
* - Other field values: `ctx.valueOf(path)` where path is a FieldPath
|
|
1100
|
+
* - Other field states: `ctx.stateOf(path)`
|
|
1101
|
+
* - Other fields: `ctx.fieldTreeOf(path)`
|
|
1102
|
+
* - Current field tree: `ctx.fieldTree`
|
|
1469
1103
|
*
|
|
1470
|
-
*
|
|
1104
|
+
* **Return Types:**
|
|
1105
|
+
* - Single error: `{ kind: 'errorKind' }` for field-level validation
|
|
1106
|
+
* - Multiple errors: `[{ kind: 'error1' }, { kind: 'error2' }]` for cross-field validation
|
|
1107
|
+
* - No error: `null` when validation passes
|
|
1108
|
+
*
|
|
1109
|
+
* **Best Practice - Return Only Error Kind:**
|
|
1110
|
+
* Validators should focus on validation logic, not presentation.
|
|
1111
|
+
* Return just the error `kind` and configure messages at field level for i18n support.
|
|
1112
|
+
*
|
|
1113
|
+
* @example Single Field Validation
|
|
1471
1114
|
* ```typescript
|
|
1472
|
-
*
|
|
1473
|
-
*
|
|
1474
|
-
*
|
|
1475
|
-
*
|
|
1476
|
-
*
|
|
1477
|
-
*
|
|
1478
|
-
*
|
|
1479
|
-
* type: 'fieldValue',
|
|
1480
|
-
* fieldPath: 'country',
|
|
1481
|
-
* operator: 'equals',
|
|
1482
|
-
* value: 'USA'
|
|
1483
|
-
* }
|
|
1484
|
-
* }]
|
|
1485
|
-
* }
|
|
1115
|
+
* const noSpaces: CustomValidator<string> = (ctx) => {
|
|
1116
|
+
* const value = ctx.value();
|
|
1117
|
+
* if (typeof value === 'string' && value.includes(' ')) {
|
|
1118
|
+
* return { kind: 'noSpaces' };
|
|
1119
|
+
* }
|
|
1120
|
+
* return null;
|
|
1121
|
+
* };
|
|
1486
1122
|
*
|
|
1487
|
-
* //
|
|
1123
|
+
* // Field configuration
|
|
1488
1124
|
* {
|
|
1489
|
-
* key: '
|
|
1490
|
-
*
|
|
1125
|
+
* key: 'username',
|
|
1126
|
+
* validators: [{ type: 'custom', functionName: 'noSpaces' }],
|
|
1127
|
+
* validationMessages: {
|
|
1128
|
+
* noSpaces: 'Spaces are not allowed'
|
|
1129
|
+
* }
|
|
1491
1130
|
* }
|
|
1131
|
+
* ```
|
|
1492
1132
|
*
|
|
1493
|
-
*
|
|
1494
|
-
*
|
|
1495
|
-
*
|
|
1496
|
-
*
|
|
1497
|
-
*
|
|
1498
|
-
*
|
|
1499
|
-
* }]
|
|
1500
|
-
* }
|
|
1133
|
+
* @example Cross-Field Validation with valueOf()
|
|
1134
|
+
* ```typescript
|
|
1135
|
+
* // Compare two fields using public API
|
|
1136
|
+
* const lessThan: CustomValidator<number> = (ctx, params) => {
|
|
1137
|
+
* const value = ctx.value();
|
|
1138
|
+
* const compareToPath = params?.field as string;
|
|
1501
1139
|
*
|
|
1502
|
-
*
|
|
1503
|
-
*
|
|
1504
|
-
* {
|
|
1505
|
-
* key: 'email',
|
|
1506
|
-
* logic: [{
|
|
1507
|
-
* type: 'derivation',
|
|
1508
|
-
* expression: 'formValue.email.toLowerCase()',
|
|
1509
|
-
* trigger: 'debounced',
|
|
1510
|
-
* debounceMs: 500
|
|
1511
|
-
* }]
|
|
1512
|
-
* }
|
|
1140
|
+
* // Use valueOf() to access other field - public API!
|
|
1141
|
+
* const otherValue = ctx.valueOf(compareToPath as any);
|
|
1513
1142
|
*
|
|
1514
|
-
*
|
|
1515
|
-
* {
|
|
1516
|
-
*
|
|
1517
|
-
*
|
|
1518
|
-
* }
|
|
1143
|
+
* if (otherValue !== undefined && value >= otherValue) {
|
|
1144
|
+
* return { kind: 'notLessThan' };
|
|
1145
|
+
* }
|
|
1146
|
+
* return null;
|
|
1147
|
+
* };
|
|
1519
1148
|
* ```
|
|
1520
1149
|
*
|
|
1521
|
-
* @
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
* Union type for all logic configurations.
|
|
1150
|
+
* @example Multiple Errors (Cross-Field Validation)
|
|
1151
|
+
* ```typescript
|
|
1152
|
+
* const validateDateRange: CustomValidator = (ctx) => {
|
|
1153
|
+
* const errors: ValidationError[] = [];
|
|
1526
1154
|
*
|
|
1527
|
-
*
|
|
1528
|
-
*
|
|
1155
|
+
* const startDate = ctx.valueOf('startDate' as any);
|
|
1156
|
+
* const endDate = ctx.valueOf('endDate' as any);
|
|
1529
1157
|
*
|
|
1530
|
-
*
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
*
|
|
1158
|
+
* if (!startDate) errors.push({ kind: 'startDateRequired' });
|
|
1159
|
+
* if (!endDate) errors.push({ kind: 'endDateRequired' });
|
|
1160
|
+
* if (startDate && endDate && startDate > endDate) {
|
|
1161
|
+
* errors.push({ kind: 'invalidDateRange' });
|
|
1162
|
+
* }
|
|
1535
1163
|
*
|
|
1536
|
-
*
|
|
1537
|
-
*
|
|
1538
|
-
*
|
|
1164
|
+
* return errors.length > 0 ? errors : null;
|
|
1165
|
+
* };
|
|
1166
|
+
* ```
|
|
1539
1167
|
*
|
|
1540
|
-
*
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
* Type guard to check if a condition is a FormStateCondition.
|
|
1168
|
+
* **Message Resolution Priority:**
|
|
1169
|
+
* 1. Field-level `validationMessages[kind]` (highest priority - per-field customization)
|
|
1170
|
+
* 2. Form-level `defaultValidationMessages[kind]` (fallback for common messages)
|
|
1171
|
+
* 3. **No message configured = Warning logged + error NOT displayed**
|
|
1545
1172
|
*
|
|
1546
|
-
*
|
|
1547
|
-
* @returns true if the condition is a FormStateCondition
|
|
1173
|
+
* **Important:** Validator-returned messages are NOT used. All messages MUST be explicitly configured.
|
|
1548
1174
|
*
|
|
1549
|
-
* @
|
|
1175
|
+
* @template TValue The type of value stored in the field being validated
|
|
1550
1176
|
*/
|
|
1551
|
-
|
|
1552
|
-
|
|
1177
|
+
type CustomValidator<TValue = unknown> = (ctx: FieldContext<TValue>, params?: Record<string, unknown>) => ValidationError$1 | ValidationError$1[] | null;
|
|
1553
1178
|
/**
|
|
1554
|
-
*
|
|
1179
|
+
* Async custom validator configuration using Angular's resource-based API
|
|
1180
|
+
*
|
|
1181
|
+
* Angular's validateAsync uses the resource API for async validation,
|
|
1182
|
+
* which provides better integration with Signal Forms lifecycle management.
|
|
1183
|
+
*
|
|
1184
|
+
* **Structure:**
|
|
1185
|
+
* - `params`: Function that computes params from field context
|
|
1186
|
+
* - `factory`: Function that creates a ResourceRef from params signal
|
|
1187
|
+
* - `onSuccess`: Maps successful resource result to validation errors
|
|
1188
|
+
* - `onError`: Optional handler for resource errors (network failures, etc.)
|
|
1189
|
+
*
|
|
1190
|
+
* **Use Cases:**
|
|
1191
|
+
* - Database lookups via services with resource API
|
|
1192
|
+
* - Complex async business logic with Angular resources
|
|
1193
|
+
* - Any async operation using Angular's resource pattern
|
|
1194
|
+
*
|
|
1195
|
+
* **Note:** For HTTP validation, prefer `HttpCustomValidator` which provides
|
|
1196
|
+
* a simpler API specifically designed for HTTP requests.
|
|
1197
|
+
*
|
|
1198
|
+
* @example Database Lookup with Resource
|
|
1199
|
+
* ```typescript
|
|
1200
|
+
* const checkUsernameAvailable: AsyncCustomValidator<string> = {
|
|
1201
|
+
* params: (ctx) => ({ username: ctx.value() }),
|
|
1202
|
+
* factory: (params) => {
|
|
1203
|
+
* const injector = inject(Injector);
|
|
1204
|
+
* return resource({
|
|
1205
|
+
* request: () => params(),
|
|
1206
|
+
* loader: ({ request }) => {
|
|
1207
|
+
* if (!request?.username) return null;
|
|
1208
|
+
* const service = injector.get(UserService);
|
|
1209
|
+
* return firstValueFrom(service.checkAvailability(request.username));
|
|
1210
|
+
* }
|
|
1211
|
+
* });
|
|
1212
|
+
* },
|
|
1213
|
+
* onSuccess: (result, ctx) => {
|
|
1214
|
+
* if (!result) return null;
|
|
1215
|
+
* return result.available ? null : { kind: 'usernameTaken' };
|
|
1216
|
+
* },
|
|
1217
|
+
* onError: (error, ctx) => {
|
|
1218
|
+
* console.error('Availability check failed:', error);
|
|
1219
|
+
* return null; // Don't block form on network errors
|
|
1220
|
+
* }
|
|
1221
|
+
* };
|
|
1222
|
+
* ```
|
|
1223
|
+
*
|
|
1224
|
+
* @template TValue The type of value stored in the field being validated
|
|
1225
|
+
* @template TParams The type of params passed to the resource
|
|
1226
|
+
* @template TResult The type of result returned by the resource
|
|
1555
1227
|
*/
|
|
1556
|
-
interface
|
|
1557
|
-
/**
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1228
|
+
interface AsyncCustomValidator<TValue = unknown, TParams = unknown, TResult = unknown> {
|
|
1229
|
+
/**
|
|
1230
|
+
* Function that receives field context and returns resource params.
|
|
1231
|
+
* Params will be tracked as a signal and trigger resource reload when changed.
|
|
1232
|
+
*/
|
|
1233
|
+
readonly params: (ctx: FieldContext<TValue>, config?: Record<string, unknown>) => TParams;
|
|
1234
|
+
/**
|
|
1235
|
+
* Function that creates a ResourceRef from the params signal.
|
|
1236
|
+
* The resource will be automatically managed by Angular's lifecycle.
|
|
1237
|
+
*/
|
|
1238
|
+
readonly factory: (params: Signal<TParams | undefined>) => ResourceRef<TResult | undefined>;
|
|
1239
|
+
/**
|
|
1240
|
+
* Optional function to map successful resource result to validation errors.
|
|
1241
|
+
* Return null if validation passes, or ValidationError/ValidationError[] if validation fails.
|
|
1242
|
+
*/
|
|
1243
|
+
readonly onSuccess?: (result: TResult, ctx: FieldContext<TValue>) => TreeValidationResult;
|
|
1244
|
+
/**
|
|
1245
|
+
* Optional error handler for resource errors (network failures, etc.).
|
|
1246
|
+
* Return null to ignore the error, or ValidationError to display it to the user.
|
|
1247
|
+
*/
|
|
1248
|
+
readonly onError?: (error: unknown, ctx: FieldContext<TValue>) => ValidationError$1 | ValidationError$1[] | null;
|
|
1565
1249
|
}
|
|
1566
1250
|
/**
|
|
1567
|
-
*
|
|
1251
|
+
* HTTP request configuration for validateHttp API
|
|
1568
1252
|
*/
|
|
1569
|
-
interface
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
/** Field path pattern this schema applies to */
|
|
1575
|
-
pathPattern?: string;
|
|
1576
|
-
/** Validators to apply */
|
|
1577
|
-
validators?: ValidatorConfig[];
|
|
1578
|
-
/** Logic rules to apply */
|
|
1579
|
-
logic?: LogicConfig[];
|
|
1580
|
-
/** Nested schema applications */
|
|
1581
|
-
subSchemas?: SchemaApplicationConfig[];
|
|
1253
|
+
interface HttpResourceRequest {
|
|
1254
|
+
url: string;
|
|
1255
|
+
method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
1256
|
+
body?: unknown;
|
|
1257
|
+
headers?: Record<string, string | string[]>;
|
|
1582
1258
|
}
|
|
1583
|
-
|
|
1584
1259
|
/**
|
|
1585
|
-
*
|
|
1260
|
+
* HTTP-based validator configuration for Angular's validateHttp API
|
|
1586
1261
|
*
|
|
1587
|
-
*
|
|
1588
|
-
*
|
|
1262
|
+
* Angular's validateHttp provides optimized HTTP validation with automatic
|
|
1263
|
+
* request cancellation and integration with the resource API.
|
|
1589
1264
|
*
|
|
1590
|
-
* **
|
|
1591
|
-
* -
|
|
1592
|
-
* -
|
|
1593
|
-
* -
|
|
1594
|
-
* - Other field states: `ctx.stateOf(path)`
|
|
1595
|
-
* - Other fields: `ctx.fieldTreeOf(path)`
|
|
1596
|
-
* - Current field tree: `ctx.fieldTree`
|
|
1265
|
+
* **Benefits:**
|
|
1266
|
+
* - Automatic request cancellation when field value changes
|
|
1267
|
+
* - Built-in integration with Angular's resource management
|
|
1268
|
+
* - Simpler than AsyncCustomValidator for HTTP use cases
|
|
1597
1269
|
*
|
|
1598
|
-
* **
|
|
1599
|
-
* -
|
|
1600
|
-
* -
|
|
1601
|
-
* -
|
|
1270
|
+
* **Structure:**
|
|
1271
|
+
* - `request`: Function that returns URL string or HttpResourceRequest
|
|
1272
|
+
* - `onSuccess`: REQUIRED - Maps HTTP response to validation errors (inverted logic!)
|
|
1273
|
+
* - `onError`: Optional handler for HTTP errors (network failures, 4xx/5xx)
|
|
1602
1274
|
*
|
|
1603
|
-
* **
|
|
1604
|
-
*
|
|
1605
|
-
*
|
|
1275
|
+
* **Important:** `onSuccess` uses inverted logic - it maps SUCCESSFUL HTTP responses
|
|
1276
|
+
* to validation errors. For example, if the API returns `{ available: false }`,
|
|
1277
|
+
* your `onSuccess` should return `{ kind: 'usernameTaken' }`.
|
|
1606
1278
|
*
|
|
1607
|
-
* @example
|
|
1279
|
+
* @example Username Availability Check (GET)
|
|
1608
1280
|
* ```typescript
|
|
1609
|
-
* const
|
|
1610
|
-
*
|
|
1611
|
-
*
|
|
1612
|
-
* return
|
|
1281
|
+
* const checkUsername: HttpCustomValidator<string> = {
|
|
1282
|
+
* request: (ctx) => {
|
|
1283
|
+
* const username = ctx.value();
|
|
1284
|
+
* if (!username) return undefined; // Skip validation if empty
|
|
1285
|
+
* return `/api/users/check-username?username=${encodeURIComponent(username)}`;
|
|
1286
|
+
* },
|
|
1287
|
+
* onSuccess: (response, ctx) => {
|
|
1288
|
+
* // Inverted logic: successful response may indicate validation failure
|
|
1289
|
+
* return response.available ? null : { kind: 'usernameTaken' };
|
|
1290
|
+
* },
|
|
1291
|
+
* onError: (error, ctx) => {
|
|
1292
|
+
* console.error('Availability check failed:', error);
|
|
1293
|
+
* return null; // Don't block form on network errors
|
|
1613
1294
|
* }
|
|
1614
|
-
* return null;
|
|
1615
1295
|
* };
|
|
1616
|
-
*
|
|
1617
|
-
* // Field configuration
|
|
1618
|
-
* {
|
|
1619
|
-
* key: 'username',
|
|
1620
|
-
* validators: [{ type: 'custom', functionName: 'noSpaces' }],
|
|
1621
|
-
* validationMessages: {
|
|
1622
|
-
* noSpaces: 'Spaces are not allowed'
|
|
1623
|
-
* }
|
|
1624
|
-
* }
|
|
1625
1296
|
* ```
|
|
1626
1297
|
*
|
|
1627
|
-
* @example
|
|
1298
|
+
* @example Address Validation (POST with Body)
|
|
1628
1299
|
* ```typescript
|
|
1629
|
-
*
|
|
1630
|
-
*
|
|
1631
|
-
*
|
|
1632
|
-
*
|
|
1633
|
-
*
|
|
1634
|
-
* // Use valueOf() to access other field - public API!
|
|
1635
|
-
* const otherValue = ctx.valueOf(compareToPath as any);
|
|
1300
|
+
* const validateAddress: HttpCustomValidator = {
|
|
1301
|
+
* request: (ctx) => {
|
|
1302
|
+
* const zipCode = ctx.value();
|
|
1303
|
+
* if (!zipCode) return undefined;
|
|
1636
1304
|
*
|
|
1637
|
-
*
|
|
1638
|
-
*
|
|
1305
|
+
* return {
|
|
1306
|
+
* url: '/api/validate-address',
|
|
1307
|
+
* method: 'POST',
|
|
1308
|
+
* body: {
|
|
1309
|
+
* street: ctx.valueOf('street' as any),
|
|
1310
|
+
* city: ctx.valueOf('city' as any),
|
|
1311
|
+
* zipCode: zipCode
|
|
1312
|
+
* },
|
|
1313
|
+
* headers: { 'Content-Type': 'application/json' }
|
|
1314
|
+
* };
|
|
1315
|
+
* },
|
|
1316
|
+
* onSuccess: (response) => {
|
|
1317
|
+
* return response.valid ? null : { kind: 'invalidAddress' };
|
|
1639
1318
|
* }
|
|
1640
|
-
* return null;
|
|
1641
1319
|
* };
|
|
1642
1320
|
* ```
|
|
1643
1321
|
*
|
|
1644
|
-
* @
|
|
1645
|
-
*
|
|
1646
|
-
|
|
1647
|
-
|
|
1322
|
+
* @template TValue The type of value stored in the field being validated
|
|
1323
|
+
* @template TResult The type of HTTP response
|
|
1324
|
+
*/
|
|
1325
|
+
interface HttpCustomValidator<TValue = unknown, TResult = unknown> {
|
|
1326
|
+
/**
|
|
1327
|
+
* Function that returns the HTTP request configuration.
|
|
1328
|
+
* Can return:
|
|
1329
|
+
* - `undefined` to skip validation (e.g., if field is empty)
|
|
1330
|
+
* - `string` for simple GET requests (just the URL)
|
|
1331
|
+
* - `HttpResourceRequest` for full control (method, body, headers)
|
|
1332
|
+
*/
|
|
1333
|
+
readonly request: (ctx: FieldContext<TValue>, config?: Record<string, unknown>) => string | HttpResourceRequest | undefined;
|
|
1334
|
+
/**
|
|
1335
|
+
* REQUIRED function to map successful HTTP response to validation errors.
|
|
1336
|
+
*
|
|
1337
|
+
* **Inverted Logic:** This is called on successful HTTP responses.
|
|
1338
|
+
* Return null if validation passes, or ValidationError/ValidationError[] if validation fails.
|
|
1339
|
+
*
|
|
1340
|
+
* Example: API returns `{ available: false }` → return `{ kind: 'usernameTaken' }`
|
|
1341
|
+
*/
|
|
1342
|
+
readonly onSuccess: (result: TResult, ctx: FieldContext<TValue>) => TreeValidationResult;
|
|
1343
|
+
/**
|
|
1344
|
+
* Optional error handler for HTTP errors (network failures, 4xx/5xx status codes).
|
|
1345
|
+
* Return null to ignore the error, or ValidationError to display it to the user.
|
|
1346
|
+
*
|
|
1347
|
+
* Common pattern: Log the error and return null to avoid blocking form submission
|
|
1348
|
+
* on network issues.
|
|
1349
|
+
*/
|
|
1350
|
+
readonly onError?: (error: unknown, ctx: FieldContext<TValue>) => ValidationError$1 | ValidationError$1[] | null;
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
/**
|
|
1354
|
+
* Mapping configuration for interpreting HTTP responses as validation results.
|
|
1648
1355
|
*
|
|
1649
|
-
*
|
|
1650
|
-
*
|
|
1356
|
+
* Expressions in `validWhen` and `errorParams` are evaluated with scope `{ response }` only —
|
|
1357
|
+
* they do NOT have access to `formValue`, `fieldValue`, or the full `EvaluationContext`.
|
|
1358
|
+
* This is intentional for v1: response mapping is purely about interpreting the HTTP response.
|
|
1359
|
+
*/
|
|
1360
|
+
interface HttpValidationResponseMapping {
|
|
1361
|
+
/**
|
|
1362
|
+
* Expression evaluated with scope `{ response }`. Must evaluate to `true` (strict boolean) to be valid.
|
|
1363
|
+
*
|
|
1364
|
+
* NOTE: This is the OPPOSITE of the function-based HttpCustomValidator.onSuccess
|
|
1365
|
+
* convention, which maps successful HTTP responses to validation errors.
|
|
1366
|
+
* Here, `=== true` means "validation passed". Non-boolean results are treated as invalid and trigger a warning.
|
|
1367
|
+
*/
|
|
1368
|
+
validWhen: string;
|
|
1369
|
+
/** Error kind returned when validation fails — maps to field-level `validationMessages` */
|
|
1370
|
+
errorKind: string;
|
|
1371
|
+
/**
|
|
1372
|
+
* Parameters to include in the validation error for message interpolation.
|
|
1373
|
+
* Map of parameter names to expressions evaluated against `{ response }`.
|
|
1374
|
+
*/
|
|
1375
|
+
errorParams?: Record<string, string>;
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
/**
|
|
1379
|
+
* Base configuration shared by all validators
|
|
1380
|
+
*/
|
|
1381
|
+
interface BaseValidatorConfig {
|
|
1382
|
+
/** Conditional logic for when validator applies */
|
|
1383
|
+
when?: ConditionalExpression;
|
|
1384
|
+
}
|
|
1385
|
+
/**
|
|
1386
|
+
* Built-in validator configuration (required, email, min, max, etc.)
|
|
1387
|
+
*/
|
|
1388
|
+
interface BuiltInValidatorConfig extends BaseValidatorConfig {
|
|
1389
|
+
/** Validator type identifier */
|
|
1390
|
+
type: 'required' | 'email' | 'min' | 'max' | 'minLength' | 'maxLength' | 'pattern';
|
|
1391
|
+
/** Static value for the validator (e.g., min value, pattern) */
|
|
1392
|
+
value?: number | string | RegExp;
|
|
1393
|
+
/** Dynamic value expression that evaluates to validator parameter */
|
|
1394
|
+
expression?: string;
|
|
1395
|
+
}
|
|
1396
|
+
/**
|
|
1397
|
+
* Custom validator configuration using Angular's public FieldContext API.
|
|
1398
|
+
* Returns ValidationError | ValidationError[] | null synchronously.
|
|
1651
1399
|
*
|
|
1652
|
-
*
|
|
1653
|
-
*
|
|
1654
|
-
*
|
|
1655
|
-
*
|
|
1656
|
-
* }
|
|
1400
|
+
* Three authoring forms (mutually exclusive at runtime, not enforced by TypeScript):
|
|
1401
|
+
* 1. Registered function: `{ type: 'custom', functionName: 'myValidator' }`
|
|
1402
|
+
* 2. Inline function (code-only): `{ type: 'custom', fn: (ctx) => ... }`
|
|
1403
|
+
* 3. Expression-based: `{ type: 'custom', expression: 'fieldValue === formValue.password', kind: 'passwordMismatch' }`
|
|
1657
1404
|
*
|
|
1658
|
-
*
|
|
1659
|
-
*
|
|
1660
|
-
*
|
|
1405
|
+
* Unlike `AsyncValidatorConfig` and `FunctionHttpValidatorConfig` (strict `fn` ↔
|
|
1406
|
+
* `functionName` XOR via discriminated unions), this surface keeps a permissive
|
|
1407
|
+
* interface — the historical expression-vs-functionName split was already
|
|
1408
|
+
* runtime-checked, so adding `fn` follows the same precedent. The runtime
|
|
1409
|
+
* resolver picks one source and warns if `fn` and `functionName` are both set;
|
|
1410
|
+
* inline `fn` wins.
|
|
1411
|
+
*
|
|
1412
|
+
* `fn` is NOT JSON-serializable — for code-only configs. For configs loaded from
|
|
1413
|
+
* JSON / OpenAPI / databases, prefer `functionName` to reference a function
|
|
1414
|
+
* registered in `customFnConfig.validators`.
|
|
1415
|
+
*/
|
|
1416
|
+
interface CustomValidatorConfig extends BaseValidatorConfig {
|
|
1417
|
+
/** Validator type identifier */
|
|
1418
|
+
type: 'custom';
|
|
1419
|
+
/** Name of registered validator function (function-based pattern) */
|
|
1420
|
+
functionName?: string;
|
|
1421
|
+
/**
|
|
1422
|
+
* Inline custom validator (code-only authoring).
|
|
1423
|
+
* Mutually exclusive with `functionName`. NOT JSON-serializable.
|
|
1424
|
+
*/
|
|
1425
|
+
fn?: CustomValidator;
|
|
1426
|
+
/** Optional parameters to pass to validator function */
|
|
1427
|
+
params?: Record<string, unknown>;
|
|
1428
|
+
/** JavaScript expression to evaluate (expression-based pattern) */
|
|
1429
|
+
expression?: string;
|
|
1430
|
+
/** Error kind for expression-based validators - links to validationMessages */
|
|
1431
|
+
kind?: string;
|
|
1432
|
+
/**
|
|
1433
|
+
* Parameters to include in error object for message interpolation (expression-based pattern)
|
|
1434
|
+
* Map of parameter names to expressions that will be evaluated in the same context as the validation expression
|
|
1435
|
+
*
|
|
1436
|
+
* @example
|
|
1437
|
+
* errorParams: {
|
|
1438
|
+
* minValue: 'formValue.minValue',
|
|
1439
|
+
* maxValue: 'formValue.maxValue'
|
|
1440
|
+
* }
|
|
1441
|
+
* // Allows message template: "Value must be between {{minValue}} and {{maxValue}}"
|
|
1442
|
+
*/
|
|
1443
|
+
errorParams?: Record<string, string>;
|
|
1444
|
+
}
|
|
1445
|
+
/**
|
|
1446
|
+
* Shared fields for both branches of {@link AsyncValidatorConfig}.
|
|
1447
|
+
*
|
|
1448
|
+
* @internal
|
|
1449
|
+
*/
|
|
1450
|
+
interface AsyncValidatorConfigShared extends BaseValidatorConfig {
|
|
1451
|
+
/** Validator type identifier. */
|
|
1452
|
+
type: 'async';
|
|
1453
|
+
/** Optional parameters to pass to validator function */
|
|
1454
|
+
params?: Record<string, unknown>;
|
|
1455
|
+
}
|
|
1456
|
+
/**
|
|
1457
|
+
* Async custom validator configuration using Angular's `validateAsync` API.
|
|
1458
|
+
* Returns Observable<ValidationError | ValidationError[] | null>.
|
|
1459
|
+
*
|
|
1460
|
+
* Two mutually exclusive authoring forms:
|
|
1461
|
+
* - `functionName`: name of a function registered in `customFnConfig.asyncValidators`.
|
|
1462
|
+
* JSON-serializable; suitable for configs loaded from APIs, databases, or OpenAPI.
|
|
1463
|
+
* - `fn`: inline async validator. NOT JSON-serializable; for code-only configs.
|
|
1464
|
+
*
|
|
1465
|
+
* Exactly one of `functionName` or `fn` must be set.
|
|
1466
|
+
*/
|
|
1467
|
+
type AsyncValidatorConfig = (AsyncValidatorConfigShared & {
|
|
1468
|
+
/** Name of registered async validator function */
|
|
1469
|
+
functionName: string;
|
|
1470
|
+
/** Inline form is forbidden when `functionName` is set */
|
|
1471
|
+
fn?: never;
|
|
1472
|
+
}) | (AsyncValidatorConfigShared & {
|
|
1473
|
+
/**
|
|
1474
|
+
* Inline async validator. Mutually exclusive with `functionName`.
|
|
1475
|
+
*
|
|
1476
|
+
* NOT JSON-serializable — for code-only configs. For configs loaded
|
|
1477
|
+
* from JSON / OpenAPI / databases, use `functionName` to reference
|
|
1478
|
+
* a validator registered in `customFnConfig.asyncValidators`.
|
|
1479
|
+
*/
|
|
1480
|
+
fn: AsyncCustomValidator;
|
|
1481
|
+
/** Registered form is forbidden when `fn` is set */
|
|
1482
|
+
functionName?: never;
|
|
1483
|
+
});
|
|
1484
|
+
/**
|
|
1485
|
+
* Shared fields for both branches of {@link FunctionHttpValidatorConfig}.
|
|
1486
|
+
*
|
|
1487
|
+
* @internal
|
|
1488
|
+
*/
|
|
1489
|
+
interface FunctionHttpValidatorConfigShared extends BaseValidatorConfig {
|
|
1490
|
+
/** Validator type identifier. */
|
|
1491
|
+
type: 'http';
|
|
1492
|
+
/** Optional parameters to pass to HTTP validator */
|
|
1493
|
+
params?: Record<string, unknown>;
|
|
1494
|
+
}
|
|
1495
|
+
/**
|
|
1496
|
+
* Function-based HTTP validator configuration. Uses Angular's `validateHttp` API.
|
|
1661
1497
|
*
|
|
1662
|
-
*
|
|
1663
|
-
*
|
|
1664
|
-
*
|
|
1665
|
-
*
|
|
1498
|
+
* Two mutually exclusive authoring forms:
|
|
1499
|
+
* - `functionName`: name of an HTTP validator registered in `customFnConfig.httpValidators`.
|
|
1500
|
+
* JSON-serializable.
|
|
1501
|
+
* - `fn`: inline HTTP validator. NOT JSON-serializable; for code-only configs.
|
|
1666
1502
|
*
|
|
1667
|
-
*
|
|
1503
|
+
* Exactly one of `functionName` or `fn` must be set.
|
|
1668
1504
|
*
|
|
1669
|
-
*
|
|
1505
|
+
* Discriminated from `DeclarativeHttpValidatorConfig` by the presence of `functionName`
|
|
1506
|
+
* or `fn` (and absence of `http` + `responseMapping`).
|
|
1670
1507
|
*/
|
|
1671
|
-
type
|
|
1508
|
+
type FunctionHttpValidatorConfig = (FunctionHttpValidatorConfigShared & {
|
|
1509
|
+
/** Name of registered HTTP validator configuration */
|
|
1510
|
+
functionName: string;
|
|
1511
|
+
/** Inline form is forbidden when `functionName` is set */
|
|
1512
|
+
fn?: never;
|
|
1513
|
+
}) | (FunctionHttpValidatorConfigShared & {
|
|
1514
|
+
/**
|
|
1515
|
+
* Inline HTTP validator. Mutually exclusive with `functionName`.
|
|
1516
|
+
*
|
|
1517
|
+
* NOT JSON-serializable — for code-only configs. For configs loaded
|
|
1518
|
+
* from JSON / OpenAPI / databases, use `functionName` to reference
|
|
1519
|
+
* an HTTP validator registered in `customFnConfig.httpValidators`.
|
|
1520
|
+
*/
|
|
1521
|
+
fn: HttpCustomValidator;
|
|
1522
|
+
/** Registered form is forbidden when `fn` is set */
|
|
1523
|
+
functionName?: never;
|
|
1524
|
+
});
|
|
1672
1525
|
/**
|
|
1673
|
-
*
|
|
1526
|
+
* Declarative HTTP validator configuration — fully JSON-serializable, no function registration required.
|
|
1674
1527
|
*
|
|
1675
|
-
*
|
|
1676
|
-
*
|
|
1528
|
+
* Uses `HttpRequestConfig` to define the HTTP request and `HttpValidationResponseMapping`
|
|
1529
|
+
* to interpret the response as a validation result. Powered by Angular's `validateHttp` API.
|
|
1677
1530
|
*
|
|
1678
|
-
*
|
|
1679
|
-
*
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1531
|
+
* Discriminated from `FunctionHttpValidatorConfig` by the presence of `http` + `responseMapping`
|
|
1532
|
+
* (and absence of `functionName`).
|
|
1533
|
+
*/
|
|
1534
|
+
interface DeclarativeHttpValidatorConfig extends BaseValidatorConfig {
|
|
1535
|
+
/** Validator type identifier */
|
|
1536
|
+
type: 'http';
|
|
1537
|
+
/** HTTP request configuration with expression-based query params and body */
|
|
1538
|
+
http: HttpRequestConfig;
|
|
1539
|
+
/** Mapping that interprets the HTTP response as a validation result */
|
|
1540
|
+
responseMapping: HttpValidationResponseMapping;
|
|
1541
|
+
}
|
|
1542
|
+
/**
|
|
1543
|
+
* Configuration for signal forms validator functions that can be serialized from API.
|
|
1544
|
+
* Discriminated union type for type-safe validator configuration.
|
|
1683
1545
|
*
|
|
1684
|
-
*
|
|
1685
|
-
*
|
|
1686
|
-
* -
|
|
1687
|
-
|
|
1546
|
+
* Note: `FunctionHttpValidatorConfig` and `DeclarativeHttpValidatorConfig` both use `type: 'http'`.
|
|
1547
|
+
* They are discriminated by property presence: `functionName` → function-based, `http` → declarative.
|
|
1548
|
+
* Use `isFunctionHttpValidator()` for type-safe narrowing when `type` alone is insufficient.
|
|
1549
|
+
*/
|
|
1550
|
+
type ValidatorConfig = BuiltInValidatorConfig | CustomValidatorConfig | AsyncValidatorConfig | FunctionHttpValidatorConfig | DeclarativeHttpValidatorConfig;
|
|
1551
|
+
|
|
1552
|
+
/**
|
|
1553
|
+
* Special form-state conditions for button disabled logic.
|
|
1688
1554
|
*
|
|
1689
|
-
*
|
|
1690
|
-
*
|
|
1555
|
+
* These conditions evaluate form or page-level state rather than field values,
|
|
1556
|
+
* and are primarily used for controlling button disabled states.
|
|
1691
1557
|
*
|
|
1692
|
-
* @example
|
|
1558
|
+
* @example
|
|
1693
1559
|
* ```typescript
|
|
1694
|
-
*
|
|
1695
|
-
*
|
|
1696
|
-
*
|
|
1697
|
-
*
|
|
1698
|
-
*
|
|
1699
|
-
*
|
|
1700
|
-
*
|
|
1701
|
-
*
|
|
1702
|
-
*
|
|
1703
|
-
*
|
|
1704
|
-
*
|
|
1705
|
-
*
|
|
1706
|
-
*
|
|
1707
|
-
*
|
|
1708
|
-
*
|
|
1709
|
-
*
|
|
1710
|
-
*
|
|
1711
|
-
*
|
|
1712
|
-
*
|
|
1713
|
-
* return null; // Don't block form on network errors
|
|
1714
|
-
* }
|
|
1715
|
-
* };
|
|
1560
|
+
* // Disable submit button when form is invalid or submitting
|
|
1561
|
+
* {
|
|
1562
|
+
* key: 'submit',
|
|
1563
|
+
* type: 'submit',
|
|
1564
|
+
* label: 'Submit',
|
|
1565
|
+
* logic: [
|
|
1566
|
+
* { type: 'disabled', condition: 'formInvalid' },
|
|
1567
|
+
* { type: 'disabled', condition: 'formSubmitting' },
|
|
1568
|
+
* ]
|
|
1569
|
+
* }
|
|
1570
|
+
*
|
|
1571
|
+
* // Disable next button when current page is invalid
|
|
1572
|
+
* nextButton({
|
|
1573
|
+
* key: 'next',
|
|
1574
|
+
* label: 'Next',
|
|
1575
|
+
* logic: [
|
|
1576
|
+
* { type: 'disabled', condition: 'pageInvalid' },
|
|
1577
|
+
* ]
|
|
1578
|
+
* })
|
|
1716
1579
|
* ```
|
|
1717
1580
|
*
|
|
1718
|
-
* @
|
|
1719
|
-
* @template TParams The type of params passed to the resource
|
|
1720
|
-
* @template TResult The type of result returned by the resource
|
|
1581
|
+
* @public
|
|
1721
1582
|
*/
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1583
|
+
type FormStateCondition =
|
|
1584
|
+
/** True when form.valid() === false */
|
|
1585
|
+
'formInvalid'
|
|
1586
|
+
/** True when form.submitting() === true */
|
|
1587
|
+
| 'formSubmitting'
|
|
1588
|
+
/** True when fields on the current page are invalid (for paged forms) */
|
|
1589
|
+
| 'pageInvalid';
|
|
1590
|
+
/**
|
|
1591
|
+
* Logic type for controlling field state (hidden, readonly, disabled, required).
|
|
1592
|
+
*
|
|
1593
|
+
* @public
|
|
1594
|
+
*/
|
|
1595
|
+
type StateLogicType = 'hidden' | 'readonly' | 'disabled' | 'required';
|
|
1596
|
+
/**
|
|
1597
|
+
* Base configuration for conditional field state logic.
|
|
1598
|
+
*
|
|
1599
|
+
* @internal
|
|
1600
|
+
*/
|
|
1601
|
+
interface BaseStateLogicConfig {
|
|
1728
1602
|
/**
|
|
1729
|
-
*
|
|
1730
|
-
*
|
|
1603
|
+
* Logic type identifier for field state.
|
|
1604
|
+
*
|
|
1605
|
+
* - `hidden`: Hide the field from view (still participates in form state)
|
|
1606
|
+
* - `readonly`: Make the field read-only
|
|
1607
|
+
* - `disabled`: Disable user interaction
|
|
1608
|
+
* - `required`: Make the field required
|
|
1731
1609
|
*/
|
|
1732
|
-
|
|
1610
|
+
type: StateLogicType;
|
|
1733
1611
|
/**
|
|
1734
|
-
*
|
|
1735
|
-
*
|
|
1612
|
+
* Condition that determines when this logic applies.
|
|
1613
|
+
*
|
|
1614
|
+
* Can be:
|
|
1615
|
+
* - `boolean`: Static value (always applies or never applies)
|
|
1616
|
+
* - `ConditionalExpression`: Expression evaluated against field/form values
|
|
1617
|
+
* - `FormStateCondition`: Special form/page state check (for buttons)
|
|
1618
|
+
*
|
|
1619
|
+
* @example
|
|
1620
|
+
* ```typescript
|
|
1621
|
+
* // Static condition
|
|
1622
|
+
* condition: true
|
|
1623
|
+
*
|
|
1624
|
+
* // Field value condition
|
|
1625
|
+
* condition: {
|
|
1626
|
+
* type: 'fieldValue',
|
|
1627
|
+
* fieldPath: 'status',
|
|
1628
|
+
* operator: 'equals',
|
|
1629
|
+
* value: 'locked'
|
|
1630
|
+
* }
|
|
1631
|
+
*
|
|
1632
|
+
* // Form state condition (for buttons)
|
|
1633
|
+
* condition: 'formSubmitting'
|
|
1634
|
+
* ```
|
|
1736
1635
|
*/
|
|
1737
|
-
|
|
1636
|
+
condition: ConditionalExpression | boolean | FormStateCondition;
|
|
1637
|
+
}
|
|
1638
|
+
/**
|
|
1639
|
+
* State logic that evaluates immediately on change (default).
|
|
1640
|
+
*
|
|
1641
|
+
* @internal
|
|
1642
|
+
*/
|
|
1643
|
+
interface ImmediateStateLogicConfig extends BaseStateLogicConfig {
|
|
1738
1644
|
/**
|
|
1739
|
-
*
|
|
1740
|
-
*
|
|
1645
|
+
* Trigger for immediate evaluation.
|
|
1646
|
+
* @default 'onChange'
|
|
1741
1647
|
*/
|
|
1742
|
-
|
|
1648
|
+
trigger?: 'onChange';
|
|
1649
|
+
/** Not allowed for onChange trigger */
|
|
1650
|
+
debounceMs?: never;
|
|
1743
1651
|
}
|
|
1744
1652
|
/**
|
|
1745
|
-
*
|
|
1653
|
+
* State logic that evaluates after a debounce period.
|
|
1654
|
+
*
|
|
1655
|
+
* @internal
|
|
1746
1656
|
*/
|
|
1747
|
-
interface
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1657
|
+
interface DebouncedStateLogicConfig extends BaseStateLogicConfig {
|
|
1658
|
+
/**
|
|
1659
|
+
* Trigger for debounced evaluation.
|
|
1660
|
+
* Evaluates after the value has stabilized for the debounce duration.
|
|
1661
|
+
*/
|
|
1662
|
+
trigger: 'debounced';
|
|
1663
|
+
/**
|
|
1664
|
+
* Debounce duration in milliseconds.
|
|
1665
|
+
* @default 500
|
|
1666
|
+
*/
|
|
1667
|
+
debounceMs?: number;
|
|
1752
1668
|
}
|
|
1753
1669
|
/**
|
|
1754
|
-
*
|
|
1755
|
-
*
|
|
1756
|
-
* Angular's validateHttp provides optimized HTTP validation with automatic
|
|
1757
|
-
* request cancellation and integration with the resource API.
|
|
1758
|
-
*
|
|
1759
|
-
* **Benefits:**
|
|
1760
|
-
* - Automatic request cancellation when field value changes
|
|
1761
|
-
* - Built-in integration with Angular's resource management
|
|
1762
|
-
* - Simpler than AsyncCustomValidator for HTTP use cases
|
|
1763
|
-
*
|
|
1764
|
-
* **Structure:**
|
|
1765
|
-
* - `request`: Function that returns URL string or HttpResourceRequest
|
|
1766
|
-
* - `onSuccess`: REQUIRED - Maps HTTP response to validation errors (inverted logic!)
|
|
1767
|
-
* - `onError`: Optional handler for HTTP errors (network failures, 4xx/5xx)
|
|
1670
|
+
* Configuration for conditional field state logic.
|
|
1768
1671
|
*
|
|
1769
|
-
*
|
|
1770
|
-
*
|
|
1771
|
-
*
|
|
1672
|
+
* Defines how field behavior changes based on conditions.
|
|
1673
|
+
* Supports hiding, disabling, making readonly, or requiring fields
|
|
1674
|
+
* based on form state or field values.
|
|
1772
1675
|
*
|
|
1773
|
-
* @example
|
|
1676
|
+
* @example
|
|
1774
1677
|
* ```typescript
|
|
1775
|
-
*
|
|
1776
|
-
*
|
|
1777
|
-
*
|
|
1778
|
-
*
|
|
1779
|
-
*
|
|
1780
|
-
*
|
|
1781
|
-
*
|
|
1782
|
-
*
|
|
1783
|
-
* return response.available ? null : { kind: 'usernameTaken' };
|
|
1784
|
-
* },
|
|
1785
|
-
* onError: (error, ctx) => {
|
|
1786
|
-
* console.error('Availability check failed:', error);
|
|
1787
|
-
* return null; // Don't block form on network errors
|
|
1678
|
+
* // Hide email field when contact method is not email
|
|
1679
|
+
* {
|
|
1680
|
+
* type: 'hidden',
|
|
1681
|
+
* condition: {
|
|
1682
|
+
* type: 'fieldValue',
|
|
1683
|
+
* fieldPath: 'contactMethod',
|
|
1684
|
+
* operator: 'notEquals',
|
|
1685
|
+
* value: 'email'
|
|
1788
1686
|
* }
|
|
1789
|
-
* }
|
|
1790
|
-
* ```
|
|
1687
|
+
* }
|
|
1791
1688
|
*
|
|
1792
|
-
*
|
|
1793
|
-
*
|
|
1794
|
-
*
|
|
1795
|
-
*
|
|
1796
|
-
*
|
|
1797
|
-
* if (!zipCode) return undefined;
|
|
1689
|
+
* // Disable button when form is submitting
|
|
1690
|
+
* {
|
|
1691
|
+
* type: 'disabled',
|
|
1692
|
+
* condition: 'formSubmitting'
|
|
1693
|
+
* }
|
|
1798
1694
|
*
|
|
1799
|
-
*
|
|
1800
|
-
*
|
|
1801
|
-
*
|
|
1802
|
-
*
|
|
1803
|
-
*
|
|
1804
|
-
*
|
|
1805
|
-
*
|
|
1806
|
-
*
|
|
1807
|
-
*
|
|
1808
|
-
* };
|
|
1809
|
-
* },
|
|
1810
|
-
* onSuccess: (response) => {
|
|
1811
|
-
* return response.valid ? null : { kind: 'invalidAddress' };
|
|
1695
|
+
* // Debounced visibility (avoids flicker during rapid typing)
|
|
1696
|
+
* {
|
|
1697
|
+
* type: 'hidden',
|
|
1698
|
+
* trigger: 'debounced',
|
|
1699
|
+
* debounceMs: 300,
|
|
1700
|
+
* condition: {
|
|
1701
|
+
* type: 'fieldValue',
|
|
1702
|
+
* fieldPath: 'search',
|
|
1703
|
+
* operator: 'isEmpty'
|
|
1812
1704
|
* }
|
|
1813
|
-
* }
|
|
1705
|
+
* }
|
|
1814
1706
|
* ```
|
|
1815
1707
|
*
|
|
1816
|
-
* @
|
|
1817
|
-
* @template TResult The type of HTTP response
|
|
1708
|
+
* @public
|
|
1818
1709
|
*/
|
|
1819
|
-
|
|
1710
|
+
type StateLogicConfig = ImmediateStateLogicConfig | DebouncedStateLogicConfig;
|
|
1711
|
+
/**
|
|
1712
|
+
* Shared fields that appear on all derivation logic config variants.
|
|
1713
|
+
*
|
|
1714
|
+
* @internal
|
|
1715
|
+
*/
|
|
1716
|
+
interface SharedDerivationFields {
|
|
1820
1717
|
/**
|
|
1821
|
-
*
|
|
1822
|
-
* Can return:
|
|
1823
|
-
* - `undefined` to skip validation (e.g., if field is empty)
|
|
1824
|
-
* - `string` for simple GET requests (just the URL)
|
|
1825
|
-
* - `HttpResourceRequest` for full control (method, body, headers)
|
|
1718
|
+
* Logic type identifier for value derivation.
|
|
1826
1719
|
*/
|
|
1827
|
-
|
|
1720
|
+
type: 'derivation';
|
|
1828
1721
|
/**
|
|
1829
|
-
*
|
|
1722
|
+
* Target property name for property derivation.
|
|
1830
1723
|
*
|
|
1831
|
-
*
|
|
1832
|
-
*
|
|
1724
|
+
* When set, this derivation targets a field property (like `minDate`, `options`,
|
|
1725
|
+
* `label`, `placeholder`) instead of the field's value. The derivation is routed
|
|
1726
|
+
* to the property derivation pipeline.
|
|
1833
1727
|
*
|
|
1834
|
-
*
|
|
1728
|
+
* When absent, the derivation targets the field's value (default behavior).
|
|
1729
|
+
*
|
|
1730
|
+
* **Depth limit (max 2 levels):** Only simple and single-dot-nested paths are
|
|
1731
|
+
* supported. Paths with 2+ dots (e.g., `'a.b.c'`) will throw at runtime.
|
|
1732
|
+
*
|
|
1733
|
+
* @example
|
|
1734
|
+
* ```typescript
|
|
1735
|
+
* // Property derivation (new unified API)
|
|
1736
|
+
* {
|
|
1737
|
+
* key: 'endDate',
|
|
1738
|
+
* type: 'datepicker',
|
|
1739
|
+
* logic: [{
|
|
1740
|
+
* type: 'derivation',
|
|
1741
|
+
* targetProperty: 'minDate',
|
|
1742
|
+
* expression: 'formValue.startDate'
|
|
1743
|
+
* }]
|
|
1744
|
+
* }
|
|
1745
|
+
*
|
|
1746
|
+
* // Value derivation (no targetProperty — existing behavior)
|
|
1747
|
+
* {
|
|
1748
|
+
* key: 'total',
|
|
1749
|
+
* type: 'input',
|
|
1750
|
+
* logic: [{
|
|
1751
|
+
* type: 'derivation',
|
|
1752
|
+
* expression: 'formValue.quantity * formValue.unitPrice'
|
|
1753
|
+
* }]
|
|
1754
|
+
* }
|
|
1755
|
+
* ```
|
|
1835
1756
|
*/
|
|
1836
|
-
|
|
1757
|
+
targetProperty?: string;
|
|
1837
1758
|
/**
|
|
1838
|
-
* Optional
|
|
1839
|
-
* Return null to ignore the error, or ValidationError to display it to the user.
|
|
1759
|
+
* Optional name for this derivation for debugging purposes.
|
|
1840
1760
|
*
|
|
1841
|
-
*
|
|
1842
|
-
*
|
|
1761
|
+
* When provided, this name appears in derivation debug logs,
|
|
1762
|
+
* making it easier to identify specific derivations in complex forms.
|
|
1763
|
+
*
|
|
1764
|
+
* @example
|
|
1765
|
+
* ```typescript
|
|
1766
|
+
* {
|
|
1767
|
+
* key: 'lineTotal',
|
|
1768
|
+
* type: 'input',
|
|
1769
|
+
* logic: [{
|
|
1770
|
+
* type: 'derivation',
|
|
1771
|
+
* debugName: 'Calculate line total',
|
|
1772
|
+
* expression: 'formValue.quantity * formValue.unitPrice'
|
|
1773
|
+
* }]
|
|
1774
|
+
* }
|
|
1775
|
+
* ```
|
|
1843
1776
|
*/
|
|
1844
|
-
|
|
1777
|
+
debugName?: string;
|
|
1778
|
+
/**
|
|
1779
|
+
* Condition that determines when this derivation applies.
|
|
1780
|
+
*
|
|
1781
|
+
* Can be:
|
|
1782
|
+
* - `boolean`: Static value (always applies or never applies)
|
|
1783
|
+
* - `ConditionalExpression`: Expression evaluated against field/form values
|
|
1784
|
+
*
|
|
1785
|
+
* Defaults to `true` (always apply).
|
|
1786
|
+
*
|
|
1787
|
+
* Note: FormStateCondition is not supported for derivations.
|
|
1788
|
+
*
|
|
1789
|
+
* @example
|
|
1790
|
+
* ```typescript
|
|
1791
|
+
* // Always compute (default)
|
|
1792
|
+
* condition: true
|
|
1793
|
+
*
|
|
1794
|
+
* // Conditional derivation
|
|
1795
|
+
* condition: {
|
|
1796
|
+
* type: 'fieldValue',
|
|
1797
|
+
* fieldPath: 'country',
|
|
1798
|
+
* operator: 'equals',
|
|
1799
|
+
* value: 'USA'
|
|
1800
|
+
* }
|
|
1801
|
+
* ```
|
|
1802
|
+
*/
|
|
1803
|
+
condition?: ConditionalExpression | boolean;
|
|
1804
|
+
/**
|
|
1805
|
+
* When true, the derivation stops running after the user manually
|
|
1806
|
+
* edits the target field.
|
|
1807
|
+
*
|
|
1808
|
+
* This is useful for "smart defaults" — values that should be
|
|
1809
|
+
* auto-filled initially but respected once the user explicitly changes them.
|
|
1810
|
+
*
|
|
1811
|
+
* Uses the field's `dirty()` signal to detect user modification.
|
|
1812
|
+
* Derivations write directly to `value.set()` which does not trigger
|
|
1813
|
+
* `markAsDirty()`, so `dirty === true` reliably indicates a user edit.
|
|
1814
|
+
*
|
|
1815
|
+
* @example
|
|
1816
|
+
* ```typescript
|
|
1817
|
+
* // Auto-fill display name from first + last name, but stop if user edits it
|
|
1818
|
+
* {
|
|
1819
|
+
* key: 'displayName',
|
|
1820
|
+
* logic: [{
|
|
1821
|
+
* type: 'derivation',
|
|
1822
|
+
* expression: 'formValue.firstName + " " + formValue.lastName',
|
|
1823
|
+
* stopOnUserOverride: true
|
|
1824
|
+
* }]
|
|
1825
|
+
* }
|
|
1826
|
+
* ```
|
|
1827
|
+
*/
|
|
1828
|
+
stopOnUserOverride?: boolean;
|
|
1829
|
+
/**
|
|
1830
|
+
* When true (and `stopOnUserOverride` is also true), clears the
|
|
1831
|
+
* user-override flag when any dependency of this derivation changes,
|
|
1832
|
+
* allowing the derivation to run again.
|
|
1833
|
+
*
|
|
1834
|
+
* This is useful when a user override should only persist until the
|
|
1835
|
+
* underlying data changes — e.g., when switching countries, the
|
|
1836
|
+
* phone prefix should re-derive even if the user previously edited it.
|
|
1837
|
+
*
|
|
1838
|
+
* @example
|
|
1839
|
+
* ```typescript
|
|
1840
|
+
* {
|
|
1841
|
+
* key: 'phonePrefix',
|
|
1842
|
+
* logic: [{
|
|
1843
|
+
* type: 'derivation',
|
|
1844
|
+
* value: '+1',
|
|
1845
|
+
* condition: { type: 'fieldValue', fieldPath: 'country', operator: 'equals', value: 'USA' },
|
|
1846
|
+
* stopOnUserOverride: true,
|
|
1847
|
+
* reEngageOnDependencyChange: true,
|
|
1848
|
+
* dependsOn: ['country']
|
|
1849
|
+
* }]
|
|
1850
|
+
* }
|
|
1851
|
+
* ```
|
|
1852
|
+
*/
|
|
1853
|
+
reEngageOnDependencyChange?: boolean;
|
|
1845
1854
|
}
|
|
1846
|
-
|
|
1847
1855
|
/**
|
|
1848
|
-
*
|
|
1849
|
-
*
|
|
1856
|
+
* Trigger variants for derivation timing.
|
|
1857
|
+
* @internal
|
|
1850
1858
|
*/
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1859
|
+
type ImmediateDerivationTrigger = {
|
|
1860
|
+
trigger?: 'onChange';
|
|
1861
|
+
debounceMs?: never;
|
|
1862
|
+
};
|
|
1863
|
+
type DebouncedDerivationTrigger = {
|
|
1864
|
+
trigger: 'debounced';
|
|
1865
|
+
debounceMs?: number;
|
|
1866
|
+
};
|
|
1867
|
+
/**
|
|
1868
|
+
* Base for HTTP derivations. `source: 'http'` is required.
|
|
1869
|
+
* TypeScript enforces `dependsOn` and `responseExpression` at the type level.
|
|
1870
|
+
*
|
|
1871
|
+
* @internal
|
|
1872
|
+
*/
|
|
1873
|
+
interface HttpDerivationBase extends SharedDerivationFields {
|
|
1874
|
+
/** Identifies this derivation as HTTP-driven. */
|
|
1875
|
+
source: 'http';
|
|
1876
|
+
/** Forbidden on HTTP source */
|
|
1877
|
+
fn?: never;
|
|
1878
|
+
/** Forbidden on HTTP source */
|
|
1879
|
+
asyncFn?: never;
|
|
1857
1880
|
/**
|
|
1858
|
-
*
|
|
1859
|
-
*
|
|
1881
|
+
* HTTP request configuration for server-driven derivations.
|
|
1882
|
+
*
|
|
1883
|
+
* The request is sent when dependencies change, with automatic
|
|
1884
|
+
* debouncing and cancellation of in-flight requests.
|
|
1885
|
+
*
|
|
1886
|
+
* Configure debounce via the `trigger: 'debounced'` + `debounceMs` mechanism.
|
|
1887
|
+
*
|
|
1888
|
+
* @example
|
|
1889
|
+
* ```typescript
|
|
1890
|
+
* {
|
|
1891
|
+
* key: 'exchangeRate',
|
|
1892
|
+
* logic: [{
|
|
1893
|
+
* type: 'derivation',
|
|
1894
|
+
* source: 'http',
|
|
1895
|
+
* http: {
|
|
1896
|
+
* url: '/api/exchange-rate',
|
|
1897
|
+
* method: 'GET',
|
|
1898
|
+
* queryParams: {
|
|
1899
|
+
* from: 'formValue.sourceCurrency',
|
|
1900
|
+
* to: 'formValue.targetCurrency',
|
|
1901
|
+
* },
|
|
1902
|
+
* },
|
|
1903
|
+
* responseExpression: 'response.rate',
|
|
1904
|
+
* dependsOn: ['sourceCurrency', 'targetCurrency'],
|
|
1905
|
+
* }]
|
|
1906
|
+
* }
|
|
1907
|
+
* ```
|
|
1860
1908
|
*/
|
|
1861
|
-
|
|
1909
|
+
http: HttpRequestConfig;
|
|
1862
1910
|
/**
|
|
1863
|
-
*
|
|
1864
|
-
*
|
|
1911
|
+
* Explicit field dependencies. Required for HTTP derivations to prevent
|
|
1912
|
+
* wildcard triggering on every keystroke.
|
|
1913
|
+
*
|
|
1914
|
+
* The wildcard token `'*'` is rejected. The structural token `'$group'`
|
|
1915
|
+
* (and `'$group.X'`) is allowed and resolves to the field's parent container
|
|
1916
|
+
* path — be aware this fires whenever any sibling under that group changes,
|
|
1917
|
+
* so the caller owns the request frequency (consider `trigger: 'debounced'`).
|
|
1865
1918
|
*/
|
|
1866
|
-
|
|
1919
|
+
dependsOn: string[];
|
|
1867
1920
|
/**
|
|
1868
|
-
*
|
|
1869
|
-
*
|
|
1921
|
+
* Expression to extract the derived value from the HTTP response.
|
|
1922
|
+
*
|
|
1923
|
+
* Evaluated via `ExpressionParser` with `{ response }` as the evaluation scope.
|
|
1924
|
+
*
|
|
1925
|
+
* @example
|
|
1926
|
+
* ```typescript
|
|
1927
|
+
* responseExpression: 'response.rate'
|
|
1928
|
+
* responseExpression: 'response.data.suggestedPrice'
|
|
1929
|
+
* ```
|
|
1870
1930
|
*/
|
|
1871
|
-
|
|
1931
|
+
responseExpression: string;
|
|
1932
|
+
value?: never;
|
|
1933
|
+
expression?: never;
|
|
1934
|
+
functionName?: never;
|
|
1935
|
+
asyncFunctionName?: never;
|
|
1872
1936
|
}
|
|
1873
|
-
|
|
1874
1937
|
/**
|
|
1875
|
-
* Shared
|
|
1876
|
-
*
|
|
1877
|
-
* Used by both the derivation-warning tracker (keys are field paths) and the
|
|
1878
|
-
* deprecation-warning tracker (keys are deprecation IDs). The property is
|
|
1879
|
-
* generic so the same shape works for any string-keyed warning domain.
|
|
1938
|
+
* Shared fields for both branches of {@link AsyncFunctionDerivationBase}.
|
|
1880
1939
|
*
|
|
1881
1940
|
* @internal
|
|
1882
1941
|
*/
|
|
1883
|
-
interface
|
|
1884
|
-
|
|
1942
|
+
interface AsyncFunctionDerivationShared extends SharedDerivationFields {
|
|
1943
|
+
/** Identifies this derivation as async-function-driven. */
|
|
1944
|
+
source: 'asyncFunction';
|
|
1945
|
+
/** Forbidden on asyncFunction source */
|
|
1946
|
+
fn?: never;
|
|
1947
|
+
/**
|
|
1948
|
+
* Explicit field dependencies. Required for async derivations to prevent
|
|
1949
|
+
* wildcard triggering on every form change.
|
|
1950
|
+
*
|
|
1951
|
+
* The wildcard token `'*'` is rejected. The structural token `'$group'`
|
|
1952
|
+
* (and `'$group.X'`) is allowed and resolves to the field's parent container
|
|
1953
|
+
* path — be aware this fires whenever any sibling under that group changes,
|
|
1954
|
+
* so the caller owns the invocation frequency (handle this via
|
|
1955
|
+
* `trigger: 'debounced'` or via debouncing inside the async function).
|
|
1956
|
+
*/
|
|
1957
|
+
dependsOn: string[];
|
|
1958
|
+
value?: never;
|
|
1959
|
+
expression?: never;
|
|
1960
|
+
functionName?: never;
|
|
1961
|
+
http?: never;
|
|
1962
|
+
responseExpression?: never;
|
|
1885
1963
|
}
|
|
1886
|
-
|
|
1887
1964
|
/**
|
|
1888
|
-
*
|
|
1889
|
-
*
|
|
1890
|
-
*
|
|
1891
|
-
* to reference field state (e.g., `fieldState.touched`, `formFieldState.email.dirty`).
|
|
1965
|
+
* Base for async function derivations. `source: 'asyncFunction'` is required.
|
|
1966
|
+
* TypeScript enforces `dependsOn` at the type level, and `asyncFunctionName` /
|
|
1967
|
+
* `asyncFn` are mutually exclusive (XOR).
|
|
1892
1968
|
*
|
|
1893
|
-
* @
|
|
1969
|
+
* @internal
|
|
1894
1970
|
*/
|
|
1895
|
-
|
|
1896
|
-
readonly touched: boolean;
|
|
1897
|
-
readonly dirty: boolean;
|
|
1898
|
-
/** Convenience property: equivalent to `!dirty` */
|
|
1899
|
-
readonly pristine: boolean;
|
|
1900
|
-
readonly valid: boolean;
|
|
1901
|
-
readonly invalid: boolean;
|
|
1902
|
-
readonly pending: boolean;
|
|
1971
|
+
type AsyncFunctionDerivationBase = (AsyncFunctionDerivationShared & {
|
|
1903
1972
|
/**
|
|
1904
|
-
*
|
|
1973
|
+
* Name of a registered async derivation function.
|
|
1905
1974
|
*
|
|
1906
|
-
*
|
|
1907
|
-
*
|
|
1908
|
-
*/
|
|
1909
|
-
readonly hidden: boolean;
|
|
1910
|
-
/**
|
|
1911
|
-
* Whether the field is currently readonly.
|
|
1975
|
+
* The function receives the evaluation context and returns a Promise or Observable
|
|
1976
|
+
* of the derived value. Register functions in `customFnConfig.asyncDerivations`.
|
|
1912
1977
|
*
|
|
1913
|
-
*
|
|
1914
|
-
*
|
|
1978
|
+
* @example
|
|
1979
|
+
* ```typescript
|
|
1980
|
+
* {
|
|
1981
|
+
* key: 'suggestedPrice',
|
|
1982
|
+
* logic: [{
|
|
1983
|
+
* type: 'derivation',
|
|
1984
|
+
* source: 'asyncFunction',
|
|
1985
|
+
* asyncFunctionName: 'fetchSuggestedPrice',
|
|
1986
|
+
* dependsOn: ['productId', 'quantity'],
|
|
1987
|
+
* }]
|
|
1988
|
+
* }
|
|
1989
|
+
* ```
|
|
1915
1990
|
*/
|
|
1916
|
-
|
|
1991
|
+
asyncFunctionName: string;
|
|
1992
|
+
/** Inline form is forbidden when `asyncFunctionName` is set */
|
|
1993
|
+
asyncFn?: never;
|
|
1994
|
+
}) | (AsyncFunctionDerivationShared & {
|
|
1917
1995
|
/**
|
|
1918
|
-
*
|
|
1996
|
+
* Inline async derivation function. Mutually exclusive with `asyncFunctionName`.
|
|
1919
1997
|
*
|
|
1920
|
-
*
|
|
1921
|
-
*
|
|
1998
|
+
* NOT JSON-serializable — for code-only configs. For configs loaded
|
|
1999
|
+
* from JSON / OpenAPI / databases, use `asyncFunctionName` to reference
|
|
2000
|
+
* a function registered in `customFnConfig.asyncDerivations`.
|
|
1922
2001
|
*/
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
*
|
|
1928
|
-
* Used as `formFieldState` in evaluation contexts to access
|
|
1929
|
-
* state of any field in the form by key.
|
|
1930
|
-
*
|
|
1931
|
-
* @public
|
|
1932
|
-
*/
|
|
1933
|
-
type FormFieldStateMap = Record<string, FieldStateInfo | undefined>;
|
|
2002
|
+
asyncFn: AsyncDerivationFunction;
|
|
2003
|
+
/** Registered form is forbidden when `asyncFn` is set */
|
|
2004
|
+
asyncFunctionName?: never;
|
|
2005
|
+
});
|
|
1934
2006
|
/**
|
|
1935
|
-
*
|
|
1936
|
-
*
|
|
1937
|
-
* Used as `fieldState` in evaluation contexts.
|
|
2007
|
+
* Base for expression derivations.
|
|
1938
2008
|
*
|
|
1939
|
-
* @
|
|
2009
|
+
* @internal
|
|
1940
2010
|
*/
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
* Form value for the current evaluation scope.
|
|
1948
|
-
*
|
|
1949
|
-
* For regular (non-array) derivations, this contains the complete form value.
|
|
1950
|
-
* For array item derivations, this is scoped to the current array item.
|
|
1951
|
-
* Use `rootFormValue` to access the complete form when inside an array context.
|
|
1952
|
-
*/
|
|
1953
|
-
formValue: TFormValue;
|
|
1954
|
-
/** Field path for relative references */
|
|
1955
|
-
fieldPath: string;
|
|
1956
|
-
/** Custom evaluation functions */
|
|
1957
|
-
customFunctions?: Record<string, (context: EvaluationContext) => unknown>;
|
|
1958
|
-
/** Logger for diagnostic output */
|
|
1959
|
-
logger: Logger;
|
|
2011
|
+
interface ExpressionDerivationBase extends SharedDerivationFields {
|
|
2012
|
+
source?: never;
|
|
2013
|
+
/** Forbidden on expression mode */
|
|
2014
|
+
fn?: never;
|
|
2015
|
+
/** Forbidden on expression mode */
|
|
2016
|
+
asyncFn?: never;
|
|
1960
2017
|
/**
|
|
1961
|
-
*
|
|
1962
|
-
* field has no inner group above the array boundary).
|
|
1963
|
-
*
|
|
1964
|
-
* Complements `formValue` and `fieldValue` for derivations on fields nested
|
|
1965
|
-
* inside groups built by factory helpers, where the parent group's key
|
|
1966
|
-
* isn't known at config-authoring time. Mirrors the runtime semantics of
|
|
1967
|
-
* the `'$group'` token used in `dependsOn`.
|
|
2018
|
+
* JavaScript expression to evaluate for the derived value.
|
|
1968
2019
|
*
|
|
1969
|
-
*
|
|
1970
|
-
*
|
|
1971
|
-
*
|
|
1972
|
-
* - Inside nested groups: the innermost parent group's value object.
|
|
1973
|
-
* - Directly inside an array item (no inner group): the array item itself
|
|
1974
|
-
* (same shape as `formValue` in array context).
|
|
1975
|
-
* - Inside a group inside an array item: the inner group's value object
|
|
1976
|
-
* within the current item.
|
|
1977
|
-
* - Field at form root with no parent container: `undefined`.
|
|
2020
|
+
* Has access to `formValue` object containing all form values.
|
|
2021
|
+
* For array fields, `formValue` is scoped to the current array item.
|
|
2022
|
+
* Uses the same secure AST-based parser as other expressions.
|
|
1978
2023
|
*
|
|
1979
2024
|
* @example
|
|
1980
2025
|
* ```typescript
|
|
1981
|
-
*
|
|
1982
|
-
*
|
|
1983
|
-
*
|
|
1984
|
-
* return country === 'usa' ? 'NY' : '';
|
|
1985
|
-
* }
|
|
2026
|
+
* expression: 'formValue.quantity * formValue.unitPrice'
|
|
2027
|
+
* expression: 'formValue.firstName + " " + formValue.lastName'
|
|
2028
|
+
* expression: 'formValue.price * (1 - formValue.discount / 100)'
|
|
1986
2029
|
* ```
|
|
1987
2030
|
*/
|
|
1988
|
-
|
|
2031
|
+
expression: string;
|
|
1989
2032
|
/**
|
|
1990
|
-
*
|
|
2033
|
+
* Explicit field dependencies for expressions.
|
|
1991
2034
|
*
|
|
1992
|
-
*
|
|
1993
|
-
*
|
|
1994
|
-
* `formValue` is scoped to the current array item, while `rootFormValue`
|
|
1995
|
-
* provides access to the entire form value including fields outside the array.
|
|
2035
|
+
* For `expression`, dependencies are automatically extracted from the expression.
|
|
2036
|
+
* Provide `dependsOn` to override automatic detection for complex expressions.
|
|
1996
2037
|
*
|
|
1997
2038
|
* @example
|
|
1998
2039
|
* ```typescript
|
|
1999
|
-
* //
|
|
2040
|
+
* // Override automatic detection for complex expressions
|
|
2000
2041
|
* {
|
|
2001
|
-
* key: '
|
|
2002
|
-
*
|
|
2003
|
-
*
|
|
2004
|
-
*
|
|
2005
|
-
*
|
|
2042
|
+
* key: 'total',
|
|
2043
|
+
* logic: [{
|
|
2044
|
+
* type: 'derivation',
|
|
2045
|
+
* expression: 'calculateTotal(formValue)',
|
|
2046
|
+
* dependsOn: ['quantity', 'unitPrice', 'discount']
|
|
2047
|
+
* }]
|
|
2006
2048
|
* }
|
|
2007
2049
|
* ```
|
|
2008
|
-
*
|
|
2009
|
-
* For non-array derivations, `rootFormValue` is not set and `formValue`
|
|
2010
|
-
* contains the entire form value.
|
|
2011
|
-
*/
|
|
2012
|
-
rootFormValue?: Record<string, unknown>;
|
|
2013
|
-
/**
|
|
2014
|
-
* Current array index when inside an array derivation.
|
|
2015
|
-
*/
|
|
2016
|
-
arrayIndex?: number;
|
|
2017
|
-
/**
|
|
2018
|
-
* Path to the array field when inside an array derivation.
|
|
2019
2050
|
*/
|
|
2020
|
-
|
|
2051
|
+
dependsOn?: string[];
|
|
2052
|
+
value?: never;
|
|
2053
|
+
functionName?: never;
|
|
2054
|
+
http?: never;
|
|
2055
|
+
asyncFunctionName?: never;
|
|
2056
|
+
responseExpression?: never;
|
|
2057
|
+
}
|
|
2058
|
+
/**
|
|
2059
|
+
* Base for value derivations.
|
|
2060
|
+
*
|
|
2061
|
+
* @internal
|
|
2062
|
+
*/
|
|
2063
|
+
interface ValueDerivationBase extends SharedDerivationFields {
|
|
2064
|
+
source?: never;
|
|
2065
|
+
/** Forbidden on value mode */
|
|
2066
|
+
fn?: never;
|
|
2067
|
+
/** Forbidden on value mode */
|
|
2068
|
+
asyncFn?: never;
|
|
2021
2069
|
/**
|
|
2022
|
-
*
|
|
2070
|
+
* Static value to set on this field.
|
|
2023
2071
|
*
|
|
2024
|
-
*
|
|
2025
|
-
* in conditional logic, derivations, and other expressions.
|
|
2072
|
+
* Use when the derived value is a constant.
|
|
2026
2073
|
*
|
|
2027
2074
|
* @example
|
|
2028
2075
|
* ```typescript
|
|
2029
|
-
* //
|
|
2030
|
-
*
|
|
2031
|
-
*
|
|
2032
|
-
*
|
|
2033
|
-
* }
|
|
2034
|
-
*
|
|
2035
|
-
* // In JavaScript expression:
|
|
2036
|
-
* condition: {
|
|
2037
|
-
* type: 'javascript',
|
|
2038
|
-
* expression: "externalData.userRole === 'admin'"
|
|
2039
|
-
* }
|
|
2076
|
+
* value: '+1' // String
|
|
2077
|
+
* value: 100 // Number
|
|
2078
|
+
* value: true // Boolean
|
|
2079
|
+
* value: { code: 'US' } // Object
|
|
2040
2080
|
* ```
|
|
2041
2081
|
*/
|
|
2042
|
-
|
|
2082
|
+
value: unknown;
|
|
2043
2083
|
/**
|
|
2044
|
-
*
|
|
2045
|
-
*
|
|
2084
|
+
* Explicit field dependencies for value derivations.
|
|
2085
|
+
*
|
|
2086
|
+
* For `value` (static), no dependencies are needed.
|
|
2087
|
+
* Provide `dependsOn` to conditionally re-evaluate when specific fields change.
|
|
2046
2088
|
*/
|
|
2047
|
-
|
|
2089
|
+
dependsOn?: string[];
|
|
2090
|
+
expression?: never;
|
|
2091
|
+
functionName?: never;
|
|
2092
|
+
http?: never;
|
|
2093
|
+
asyncFunctionName?: never;
|
|
2094
|
+
responseExpression?: never;
|
|
2095
|
+
}
|
|
2096
|
+
/**
|
|
2097
|
+
* Shared fields for both branches of {@link FunctionDerivationBase}.
|
|
2098
|
+
*
|
|
2099
|
+
* @internal
|
|
2100
|
+
*/
|
|
2101
|
+
interface FunctionDerivationShared extends SharedDerivationFields {
|
|
2102
|
+
source?: never;
|
|
2103
|
+
/** Forbidden on function-derivation mode */
|
|
2104
|
+
asyncFn?: never;
|
|
2048
2105
|
/**
|
|
2049
|
-
*
|
|
2106
|
+
* Explicit field dependencies for function derivations.
|
|
2050
2107
|
*
|
|
2051
|
-
*
|
|
2052
|
-
*
|
|
2108
|
+
* If not provided, defaults to all fields ('*').
|
|
2109
|
+
* Specify `dependsOn` for better performance when the function only
|
|
2110
|
+
* depends on a subset of fields.
|
|
2053
2111
|
*
|
|
2054
2112
|
* @example
|
|
2055
2113
|
* ```typescript
|
|
2056
|
-
* //
|
|
2057
|
-
*
|
|
2058
|
-
*
|
|
2059
|
-
*
|
|
2114
|
+
* // Only re-evaluate when country changes
|
|
2115
|
+
* {
|
|
2116
|
+
* key: 'currency',
|
|
2117
|
+
* logic: [{
|
|
2118
|
+
* type: 'derivation',
|
|
2119
|
+
* functionName: 'getCurrencyForCountry',
|
|
2120
|
+
* dependsOn: ['country']
|
|
2121
|
+
* }]
|
|
2060
2122
|
* }
|
|
2061
2123
|
* ```
|
|
2062
2124
|
*/
|
|
2063
|
-
|
|
2125
|
+
dependsOn?: string[];
|
|
2126
|
+
value?: never;
|
|
2127
|
+
expression?: never;
|
|
2128
|
+
http?: never;
|
|
2129
|
+
asyncFunctionName?: never;
|
|
2130
|
+
responseExpression?: never;
|
|
2131
|
+
}
|
|
2132
|
+
/**
|
|
2133
|
+
* Base for custom sync function derivations. `functionName` and `fn` are
|
|
2134
|
+
* mutually exclusive (XOR).
|
|
2135
|
+
*
|
|
2136
|
+
* @internal
|
|
2137
|
+
*/
|
|
2138
|
+
type FunctionDerivationBase = (FunctionDerivationShared & {
|
|
2064
2139
|
/**
|
|
2065
|
-
*
|
|
2140
|
+
* Name of a registered custom derivation function.
|
|
2066
2141
|
*
|
|
2067
|
-
*
|
|
2068
|
-
*
|
|
2142
|
+
* The function receives the evaluation context and returns the derived value.
|
|
2143
|
+
* Register functions in `customFnConfig.derivations`.
|
|
2069
2144
|
*
|
|
2070
2145
|
* @example
|
|
2071
2146
|
* ```typescript
|
|
2072
|
-
*
|
|
2073
|
-
*
|
|
2074
|
-
*
|
|
2075
|
-
* expression: "formFieldState.email.dirty && formFieldState.email.valid"
|
|
2076
|
-
* }
|
|
2147
|
+
* functionName: 'getCurrencyForCountry'
|
|
2148
|
+
* functionName: 'calculateTax'
|
|
2149
|
+
* functionName: 'formatPhoneNumber'
|
|
2077
2150
|
* ```
|
|
2078
2151
|
*/
|
|
2079
|
-
|
|
2080
|
-
/**
|
|
2081
|
-
|
|
2082
|
-
}
|
|
2083
|
-
|
|
2152
|
+
functionName: string;
|
|
2153
|
+
/** Inline form is forbidden when `functionName` is set */
|
|
2154
|
+
fn?: never;
|
|
2155
|
+
}) | (FunctionDerivationShared & {
|
|
2156
|
+
/**
|
|
2157
|
+
* Inline custom derivation function. Mutually exclusive with `functionName`.
|
|
2158
|
+
*
|
|
2159
|
+
* NOT JSON-serializable — for code-only configs. For configs loaded
|
|
2160
|
+
* from JSON / OpenAPI / databases, use `functionName` to reference
|
|
2161
|
+
* a function registered in `customFnConfig.derivations`.
|
|
2162
|
+
*/
|
|
2163
|
+
fn: CustomFunction;
|
|
2164
|
+
/** Registered form is forbidden when `fn` is set */
|
|
2165
|
+
functionName?: never;
|
|
2166
|
+
});
|
|
2084
2167
|
/**
|
|
2085
|
-
*
|
|
2086
|
-
*
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
*
|
|
2091
|
-
*
|
|
2168
|
+
* HTTP derivation that evaluates immediately on change (default).
|
|
2169
|
+
* @internal
|
|
2170
|
+
*/
|
|
2171
|
+
type OnChangeHttpDerivationLogicConfig = HttpDerivationBase & ImmediateDerivationTrigger;
|
|
2172
|
+
/**
|
|
2173
|
+
* HTTP derivation that evaluates after a debounce period.
|
|
2174
|
+
* @internal
|
|
2175
|
+
*/
|
|
2176
|
+
type DebouncedHttpDerivationLogicConfig = HttpDerivationBase & DebouncedDerivationTrigger;
|
|
2177
|
+
/**
|
|
2178
|
+
* Async function derivation that evaluates immediately on change (default).
|
|
2179
|
+
* @internal
|
|
2180
|
+
*/
|
|
2181
|
+
type OnChangeAsyncFunctionDerivationLogicConfig = AsyncFunctionDerivationBase & ImmediateDerivationTrigger;
|
|
2182
|
+
/**
|
|
2183
|
+
* Async function derivation that evaluates after a debounce period.
|
|
2184
|
+
* @internal
|
|
2185
|
+
*/
|
|
2186
|
+
type DebouncedAsyncFunctionDerivationLogicConfig = AsyncFunctionDerivationBase & DebouncedDerivationTrigger;
|
|
2187
|
+
/**
|
|
2188
|
+
* Expression derivation that evaluates immediately on change (default).
|
|
2189
|
+
* @internal
|
|
2190
|
+
*/
|
|
2191
|
+
type OnChangeExpressionDerivationLogicConfig = ExpressionDerivationBase & ImmediateDerivationTrigger;
|
|
2192
|
+
/**
|
|
2193
|
+
* Expression derivation that evaluates after a debounce period.
|
|
2194
|
+
* @internal
|
|
2195
|
+
*/
|
|
2196
|
+
type DebouncedExpressionDerivationLogicConfig = ExpressionDerivationBase & DebouncedDerivationTrigger;
|
|
2197
|
+
/**
|
|
2198
|
+
* Value derivation that evaluates immediately on change (default).
|
|
2199
|
+
* @internal
|
|
2200
|
+
*/
|
|
2201
|
+
type OnChangeValueDerivationLogicConfig = ValueDerivationBase & ImmediateDerivationTrigger;
|
|
2202
|
+
/**
|
|
2203
|
+
* Value derivation that evaluates after a debounce period.
|
|
2204
|
+
* @internal
|
|
2205
|
+
*/
|
|
2206
|
+
type DebouncedValueDerivationLogicConfig = ValueDerivationBase & DebouncedDerivationTrigger;
|
|
2207
|
+
/**
|
|
2208
|
+
* Function derivation that evaluates immediately on change (default).
|
|
2209
|
+
* @internal
|
|
2210
|
+
*/
|
|
2211
|
+
type OnChangeFunctionDerivationLogicConfig = FunctionDerivationBase & ImmediateDerivationTrigger;
|
|
2212
|
+
/**
|
|
2213
|
+
* Function derivation that evaluates after a debounce period.
|
|
2214
|
+
* @internal
|
|
2215
|
+
*/
|
|
2216
|
+
type DebouncedFunctionDerivationLogicConfig = FunctionDerivationBase & DebouncedDerivationTrigger;
|
|
2217
|
+
/**
|
|
2218
|
+
* Configuration for value derivation logic.
|
|
2092
2219
|
*
|
|
2093
|
-
*
|
|
2094
|
-
*
|
|
2095
|
-
*
|
|
2096
|
-
* - Receive EvaluationContext (field values) not FieldContext (field state)
|
|
2220
|
+
* Enables programmatic value derivation based on conditions.
|
|
2221
|
+
* Derivations are self-targeting: the logic is placed on the field
|
|
2222
|
+
* that should receive the computed value.
|
|
2097
2223
|
*
|
|
2098
2224
|
* @example
|
|
2099
2225
|
* ```typescript
|
|
2100
|
-
* //
|
|
2101
|
-
*
|
|
2102
|
-
*
|
|
2103
|
-
*
|
|
2226
|
+
* // Set phone prefix based on country selection
|
|
2227
|
+
* {
|
|
2228
|
+
* key: 'phonePrefix',
|
|
2229
|
+
* logic: [{
|
|
2230
|
+
* type: 'derivation',
|
|
2231
|
+
* value: '+1',
|
|
2232
|
+
* condition: {
|
|
2233
|
+
* type: 'fieldValue',
|
|
2234
|
+
* fieldPath: 'country',
|
|
2235
|
+
* operator: 'equals',
|
|
2236
|
+
* value: 'USA'
|
|
2237
|
+
* }
|
|
2238
|
+
* }]
|
|
2239
|
+
* }
|
|
2104
2240
|
*
|
|
2105
|
-
* //
|
|
2241
|
+
* // Compute total from quantity and price
|
|
2106
2242
|
* {
|
|
2107
|
-
* key: '
|
|
2108
|
-
*
|
|
2109
|
-
* when: { function: 'isAdult' } // Only show if isAdult returns true
|
|
2243
|
+
* key: 'total',
|
|
2244
|
+
* derivation: 'formValue.quantity * formValue.unitPrice'
|
|
2110
2245
|
* }
|
|
2111
|
-
* ```
|
|
2112
2246
|
*
|
|
2113
|
-
*
|
|
2114
|
-
*
|
|
2115
|
-
*
|
|
2116
|
-
*
|
|
2117
|
-
*
|
|
2118
|
-
*
|
|
2119
|
-
*
|
|
2120
|
-
* }
|
|
2247
|
+
* // Use custom function for complex logic
|
|
2248
|
+
* {
|
|
2249
|
+
* key: 'currency',
|
|
2250
|
+
* logic: [{
|
|
2251
|
+
* type: 'derivation',
|
|
2252
|
+
* functionName: 'getCurrencyForCountry'
|
|
2253
|
+
* }]
|
|
2254
|
+
* }
|
|
2255
|
+
*
|
|
2256
|
+
* // Self-transform with debounced trigger
|
|
2257
|
+
* // (applies after user stops typing)
|
|
2258
|
+
* {
|
|
2259
|
+
* key: 'email',
|
|
2260
|
+
* logic: [{
|
|
2261
|
+
* type: 'derivation',
|
|
2262
|
+
* expression: 'formValue.email.toLowerCase()',
|
|
2263
|
+
* trigger: 'debounced',
|
|
2264
|
+
* debounceMs: 500
|
|
2265
|
+
* }]
|
|
2266
|
+
* }
|
|
2267
|
+
*
|
|
2268
|
+
* // Array item derivation (formValue is scoped to current item)
|
|
2269
|
+
* {
|
|
2270
|
+
* key: 'lineTotal', // Inside array field
|
|
2271
|
+
* derivation: 'formValue.quantity * formValue.unitPrice'
|
|
2272
|
+
* }
|
|
2121
2273
|
* ```
|
|
2274
|
+
*
|
|
2275
|
+
* @public
|
|
2122
2276
|
*/
|
|
2123
|
-
type
|
|
2124
|
-
|
|
2277
|
+
type DerivationLogicConfig = OnChangeHttpDerivationLogicConfig | DebouncedHttpDerivationLogicConfig | OnChangeAsyncFunctionDerivationLogicConfig | DebouncedAsyncFunctionDerivationLogicConfig | OnChangeExpressionDerivationLogicConfig | DebouncedExpressionDerivationLogicConfig | OnChangeValueDerivationLogicConfig | DebouncedValueDerivationLogicConfig | OnChangeFunctionDerivationLogicConfig | DebouncedFunctionDerivationLogicConfig;
|
|
2125
2278
|
/**
|
|
2126
|
-
*
|
|
2127
|
-
*
|
|
2128
|
-
* The function receives the evaluation context and may return a Promise or Observable.
|
|
2129
|
-
* Register functions in `customFnConfig.asyncDerivations`.
|
|
2279
|
+
* Union type for all logic configurations.
|
|
2130
2280
|
*
|
|
2131
|
-
*
|
|
2132
|
-
*
|
|
2133
|
-
* const fetchPrice: AsyncDerivationFunction = async (context) => {
|
|
2134
|
-
* const response = await fetch(`/api/price?product=${context.formValue.productId}`);
|
|
2135
|
-
* const data = await response.json();
|
|
2136
|
-
* return data.price;
|
|
2137
|
-
* };
|
|
2138
|
-
* ```
|
|
2281
|
+
* - `StateLogicConfig`: For field state changes (hidden, readonly, disabled, required)
|
|
2282
|
+
* - `DerivationLogicConfig`: For value derivation (including property derivation via `targetProperty`)
|
|
2139
2283
|
*
|
|
2140
2284
|
* @public
|
|
2141
2285
|
*/
|
|
2142
|
-
type
|
|
2286
|
+
type LogicConfig = StateLogicConfig | DerivationLogicConfig;
|
|
2143
2287
|
/**
|
|
2144
|
-
*
|
|
2288
|
+
* Log level for derivation debug output.
|
|
2145
2289
|
*
|
|
2146
|
-
*
|
|
2147
|
-
*
|
|
2290
|
+
* - 'none': No debug logging
|
|
2291
|
+
* - 'summary': Log cycle completion with counts (default in dev mode)
|
|
2292
|
+
* - 'verbose': Log individual derivation evaluations with details
|
|
2148
2293
|
*
|
|
2149
|
-
* @
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
*
|
|
2154
|
-
*
|
|
2155
|
-
*
|
|
2156
|
-
*
|
|
2294
|
+
* @public
|
|
2295
|
+
*/
|
|
2296
|
+
type DerivationLogLevel = 'none' | 'summary' | 'verbose';
|
|
2297
|
+
/**
|
|
2298
|
+
* Type guard to check if a condition is a FormStateCondition.
|
|
2299
|
+
*
|
|
2300
|
+
* @param condition - The condition to check
|
|
2301
|
+
* @returns true if the condition is a FormStateCondition
|
|
2157
2302
|
*
|
|
2158
2303
|
* @public
|
|
2159
2304
|
*/
|
|
2160
|
-
|
|
2305
|
+
declare function isFormStateCondition(condition: StateLogicConfig['condition'] | DerivationLogicConfig['condition']): condition is FormStateCondition;
|
|
2306
|
+
|
|
2307
|
+
/**
|
|
2308
|
+
* Configuration for applying predefined schemas
|
|
2309
|
+
*/
|
|
2310
|
+
interface SchemaApplicationConfig {
|
|
2311
|
+
/** Schema application type */
|
|
2312
|
+
type: 'apply' | 'applyWhen' | 'applyWhenValue' | 'applyEach';
|
|
2313
|
+
/** Schema identifier or inline schema definition */
|
|
2314
|
+
schema: string | SchemaDefinition;
|
|
2315
|
+
/** Condition for conditional application */
|
|
2316
|
+
condition?: ConditionalExpression;
|
|
2317
|
+
/** Type predicate for applyWhenValue */
|
|
2318
|
+
typePredicate?: string;
|
|
2319
|
+
}
|
|
2320
|
+
/**
|
|
2321
|
+
* Reusable schema definition that can be referenced by name
|
|
2322
|
+
*/
|
|
2323
|
+
interface SchemaDefinition {
|
|
2324
|
+
/** Unique schema identifier */
|
|
2325
|
+
name: string;
|
|
2326
|
+
/** Schema description */
|
|
2327
|
+
description?: string;
|
|
2328
|
+
/** Field path pattern this schema applies to */
|
|
2329
|
+
pathPattern?: string;
|
|
2330
|
+
/** Validators to apply */
|
|
2331
|
+
validators?: ValidatorConfig[];
|
|
2332
|
+
/** Logic rules to apply */
|
|
2333
|
+
logic?: LogicConfig[];
|
|
2334
|
+
/** Nested schema applications */
|
|
2335
|
+
subSchemas?: SchemaApplicationConfig[];
|
|
2336
|
+
}
|
|
2161
2337
|
|
|
2162
2338
|
/**
|
|
2163
2339
|
* Re-export Angular's ValidationError for consistency
|
|
@@ -4083,6 +4259,21 @@ interface ArrayContext {
|
|
|
4083
4259
|
/** The array field definition. */
|
|
4084
4260
|
field: FieldDef<unknown>;
|
|
4085
4261
|
}
|
|
4262
|
+
/**
|
|
4263
|
+
* Group context for fields rendered inside `group` containers.
|
|
4264
|
+
*
|
|
4265
|
+
* Carries a reactive dot-separated chain of group ancestors (including the
|
|
4266
|
+
* current group). Used by `mapFieldToInputs` to scope DOM IDs of group
|
|
4267
|
+
* children, so the same leaf key can appear inside different groups without
|
|
4268
|
+
* producing duplicate `id` attributes (issue #401).
|
|
4269
|
+
*
|
|
4270
|
+
* Re-provided by every group's child injector — a nested group reads its
|
|
4271
|
+
* parent's context signal and composes its own path through `computed()`.
|
|
4272
|
+
*/
|
|
4273
|
+
interface GroupContext {
|
|
4274
|
+
/** Dot-separated path of group ancestors, including the current group's key (e.g. `'address'` or `'user.address'`). */
|
|
4275
|
+
groupPath: Signal<string>;
|
|
4276
|
+
}
|
|
4086
4277
|
/**
|
|
4087
4278
|
* Mapper function that converts a field definition to component inputs.
|
|
4088
4279
|
*
|
|
@@ -4782,6 +4973,26 @@ declare const FIELD_SIGNAL_CONTEXT: InjectionToken<FieldSignalContext<Record<str
|
|
|
4782
4973
|
* ```
|
|
4783
4974
|
*/
|
|
4784
4975
|
declare const ARRAY_CONTEXT: InjectionToken<ArrayContext>;
|
|
4976
|
+
/**
|
|
4977
|
+
* Injection token for providing group ancestry to mappers and components.
|
|
4978
|
+
*
|
|
4979
|
+
* Re-provided in two places:
|
|
4980
|
+
* 1. `GroupFieldComponent` provides a context whose `groupPath()` is the
|
|
4981
|
+
* parent path extended by its own key (e.g. `'address'` or `'user.address'`).
|
|
4982
|
+
* 2. `createArrayItemInjector` provides a sentinel with an empty `groupPath`,
|
|
4983
|
+
* resetting the chain at array boundaries — descendants of an array item
|
|
4984
|
+
* that re-enter a group compose paths INSIDE the array item, matching the
|
|
4985
|
+
* property-derivation collector's keying.
|
|
4986
|
+
*
|
|
4987
|
+
* `mapFieldToInputs` reads this to scope DOM IDs and override-store keys so
|
|
4988
|
+
* the same leaf key can appear in different groups without colliding (#401).
|
|
4989
|
+
*
|
|
4990
|
+
* Inject with `{ optional: true }` because top-level fields with no group
|
|
4991
|
+
* ancestor (and no enclosing array) won't have the token provided. The token
|
|
4992
|
+
* IS present for fields inside an array even when no inner group exists — the
|
|
4993
|
+
* array-boundary sentinel makes injection succeed with an empty `groupPath`.
|
|
4994
|
+
*/
|
|
4995
|
+
declare const GROUP_CONTEXT: InjectionToken<GroupContext>;
|
|
4785
4996
|
/**
|
|
4786
4997
|
* Injection token for form-level default props.
|
|
4787
4998
|
*
|
|
@@ -6336,6 +6547,7 @@ declare class GroupFieldComponent<TModel extends Record<string, unknown> = Recor
|
|
|
6336
6547
|
private readonly destroyRef;
|
|
6337
6548
|
private readonly fieldRegistry;
|
|
6338
6549
|
private readonly parentFieldSignalContext;
|
|
6550
|
+
private readonly parentGroupContext;
|
|
6339
6551
|
private readonly injector;
|
|
6340
6552
|
protected readonly environmentInjector: EnvironmentInjector;
|
|
6341
6553
|
private readonly eventBus;
|
|
@@ -6365,6 +6577,13 @@ declare class GroupFieldComponent<TModel extends Record<string, unknown> = Recor
|
|
|
6365
6577
|
readonly disabled: _angular_core.Signal<boolean>;
|
|
6366
6578
|
private readonly nestedFieldTree;
|
|
6367
6579
|
private readonly groupFieldSignalContext;
|
|
6580
|
+
/**
|
|
6581
|
+
* Group context propagated to descendants — carries the dot-separated path of
|
|
6582
|
+
* group ancestors so child fields can scope their DOM IDs (issue #401).
|
|
6583
|
+
* Reactive via `computed()`: a nested group composes its path from the parent
|
|
6584
|
+
* context signal + its own key, so the chain stays consistent if either changes.
|
|
6585
|
+
*/
|
|
6586
|
+
private readonly groupContext;
|
|
6368
6587
|
private readonly groupInjector;
|
|
6369
6588
|
readonly validityChange: _angular_core.OutputRef<boolean>;
|
|
6370
6589
|
readonly dirtyChange: _angular_core.OutputRef<boolean>;
|
|
@@ -6756,5 +6975,5 @@ declare class RootFormRegistryService {
|
|
|
6756
6975
|
*/
|
|
6757
6976
|
declare const INITIALIZATION_TIMEOUT_MS: InjectionToken<number>;
|
|
6758
6977
|
|
|
6759
|
-
export { ARRAY_CONTEXT, AppendArrayItemEvent, ArrayFieldComponent, BUILT_IN_FIELDS, ConsoleLogger, ContainerFieldComponent, DEFAULT_PROPS, DEFAULT_VALIDATION_MESSAGES, DEFAULT_WRAPPERS, DynamicForm, DynamicFormError, DynamicFormLogger, DynamicTextPipe, EventBus, EventDispatcher, FIELD_REGISTRY, FIELD_SIGNAL_CONTEXT, FORM_OPTIONS, FormClearEvent, FormResetEvent, GroupFieldComponent, INITIALIZATION_TIMEOUT_MS, InsertArrayItemEvent, MoveArrayItemEvent, NextPageEvent, NoopLogger, PageChangeEvent, PopArrayItemEvent, PrependArrayItemEvent, PreviousPageEvent, RemoveAtIndexEvent, RootFormRegistryService, ShiftArrayItemEvent, SubmitEvent, WRAPPER_REGISTRY, applyMetaToElement, applyValidator, applyValidators, arrayEvent, arrayFieldMapper, baseFieldMapper, buildBaseInputs, containerFieldMapper, createField, createWrappers, dynamicTextToObservable, evaluateNonFieldDisabled, evaluateNonFieldHidden, field, formConfig, getArrayLength, groupFieldMapper, hasFormValue, interpolateParams, isArrayField, isCheckedField, isContainerField, isContainerTypedField, isDisplayOnlyField, isEqual, isFormStateCondition, isGroupField, isLeafField, isPageField, isRowField, isSimplifiedArrayField, isValueBearingField, isValueField, isWrapperTypeDefinition, isWrappersBundle, omit, pageFieldMapper, provideDynamicForm, resolveNextButtonDisabled, resolveNonFieldDisabled, resolveNonFieldHidden, resolveSubmitButtonDisabled, resolveTokens, rowFieldMapper, textFieldMapper, toReadonlyFieldTree, withEventFormValue, withLoggerConfig, withPreviousValue, withValidationExecutionDefaults, withValueExclusionDefaults, wrapperProps };
|
|
6760
|
-
export type { ArrayAllowedChildren, ArrayButtonConfig, ArrayContext, ArrayField, ArrayFieldTree, ArrayItemContext, ArrayItemDefinitionTemplate, ArrayItemTemplate, AsyncCondition, AsyncConditionFunction, AsyncCustomValidator, AsyncDerivationFunction, AsyncValidatorConfig, AvailableFieldTypes, BaseCheckedField, BaseValidatorConfig, BaseValueField, BuiltInValidatorConfig, ButtonLogicContext, CheckedFieldComponent, ConditionalExpression, ContainerAllowedChildren, ContainerField, ContainerFieldTypes, CssWrapper, CustomFnConfig, CustomValidator, CustomValidatorConfig, DeclarativeHttpValidatorConfig, DynamicFormFieldRegistry, DynamicText, EvaluationContext, ExtractField, ExtractFieldDefs, ExtractFormValue, FieldComponent, FieldDef, FieldMeta, FieldOption, FieldPathAccess, FieldRegistryContainers, FieldRegistryLeaves, FieldRegistryWrappers, FieldScope, FieldSignalContext, FieldStateContext, FieldStateInfo, FieldTypeDefinition, FieldWithValidation, FieldWrapper, FormConfig, FormEvent, FormEventConstructor, FormFieldStateMap, FormMode, FormModeDetectionResult, FormOptions, FormStateCondition, GroupAllowedChildren, GroupField, HttpCondition, HttpCustomValidator, HttpRequestConfig, HttpResourceRequest, HttpValidationResponseMapping, InferFormValue$1 as InferFormValue, InferWrapperRegistry, LeafFieldTypes, Logger, LogicConfig, MapperFn, NarrowField, NarrowFields, NextButtonOptions, NonFieldLogicConfig, NonFieldLogicContext, NonFieldLogicType, PageAllowedChildren, PageField, Prettify, ReadonlyFieldTree, RegisteredFieldTypes, ResolvedValidationExecutionConfig, ResolvedValueExclusionConfig, RowAllowedChildren, RowField, SchemaApplicationConfig, SchemaDefinition, SimplifiedArrayField, StateLogicConfig, SubmissionActionResult, SubmissionConfig, SubmitButtonOptions, TextElementType, TextField, TextProps, TokenContext, ValidationError, ValidationExecutionConfig, ValidationMessages, ValidatorConfig, ValueExclusionConfig, ValueFieldComponent, ValueHandlingMode, ValueType, WithInputSignals, WrapperConfig, WrapperFieldInputs, WrapperRegistration, WrapperTypeDefinition, WrappersBundle };
|
|
6978
|
+
export { ARRAY_CONTEXT, AppendArrayItemEvent, ArrayFieldComponent, BUILT_IN_FIELDS, ConsoleLogger, ContainerFieldComponent, DEFAULT_PROPS, DEFAULT_VALIDATION_MESSAGES, DEFAULT_WRAPPERS, DynamicForm, DynamicFormError, DynamicFormLogger, DynamicTextPipe, EventBus, EventDispatcher, FIELD_REGISTRY, FIELD_SIGNAL_CONTEXT, FORM_OPTIONS, FormClearEvent, FormResetEvent, GROUP_CONTEXT, GroupFieldComponent, INITIALIZATION_TIMEOUT_MS, InsertArrayItemEvent, MoveArrayItemEvent, NextPageEvent, NoopLogger, PageChangeEvent, PopArrayItemEvent, PrependArrayItemEvent, PreviousPageEvent, RemoveAtIndexEvent, RootFormRegistryService, ShiftArrayItemEvent, SubmitEvent, WRAPPER_REGISTRY, applyMetaToElement, applyValidator, applyValidators, arrayEvent, arrayFieldMapper, baseFieldMapper, buildBaseInputs, containerFieldMapper, createField, createWrappers, dynamicTextToObservable, evaluateNonFieldDisabled, evaluateNonFieldHidden, field, formConfig, getArrayLength, groupFieldMapper, hasFormValue, interpolateParams, isArrayField, isCheckedField, isContainerField, isContainerTypedField, isDisplayOnlyField, isEqual, isFormStateCondition, isGroupField, isLeafField, isPageField, isRowField, isSimplifiedArrayField, isValueBearingField, isValueField, isWrapperTypeDefinition, isWrappersBundle, omit, pageFieldMapper, provideDynamicForm, resolveNextButtonDisabled, resolveNonFieldDisabled, resolveNonFieldHidden, resolveSubmitButtonDisabled, resolveTokens, rowFieldMapper, textFieldMapper, toReadonlyFieldTree, withEventFormValue, withLoggerConfig, withPreviousValue, withValidationExecutionDefaults, withValueExclusionDefaults, wrapperProps };
|
|
6979
|
+
export type { ArrayAllowedChildren, ArrayButtonConfig, ArrayContext, ArrayField, ArrayFieldTree, ArrayItemContext, ArrayItemDefinitionTemplate, ArrayItemTemplate, AsyncCondition, AsyncConditionFunction, AsyncCustomValidator, AsyncDerivationFunction, AsyncValidatorConfig, AvailableFieldTypes, BaseCheckedField, BaseValidatorConfig, BaseValueField, BuiltInValidatorConfig, ButtonLogicContext, CheckedFieldComponent, ConditionalExpression, ContainerAllowedChildren, ContainerField, ContainerFieldTypes, CssWrapper, CustomFnConfig, CustomFunction, CustomValidator, CustomValidatorConfig, DeclarativeHttpValidatorConfig, DynamicFormFieldRegistry, DynamicText, EvaluationContext, ExtractField, ExtractFieldDefs, ExtractFormValue, FieldComponent, FieldDef, FieldMeta, FieldOption, FieldPathAccess, FieldRegistryContainers, FieldRegistryLeaves, FieldRegistryWrappers, FieldScope, FieldSignalContext, FieldStateContext, FieldStateInfo, FieldTypeDefinition, FieldWithValidation, FieldWrapper, FormConfig, FormEvent, FormEventConstructor, FormFieldStateMap, FormMode, FormModeDetectionResult, FormOptions, FormStateCondition, GroupAllowedChildren, GroupField, HttpCondition, HttpCustomValidator, HttpRequestConfig, HttpResourceRequest, HttpValidationResponseMapping, InferFormValue$1 as InferFormValue, InferWrapperRegistry, LeafFieldTypes, Logger, LogicConfig, MapperFn, NarrowField, NarrowFields, NextButtonOptions, NonFieldLogicConfig, NonFieldLogicContext, NonFieldLogicType, PageAllowedChildren, PageField, Prettify, ReadonlyFieldTree, RegisteredFieldTypes, ResolvedValidationExecutionConfig, ResolvedValueExclusionConfig, RowAllowedChildren, RowField, SchemaApplicationConfig, SchemaDefinition, SimplifiedArrayField, StateLogicConfig, SubmissionActionResult, SubmissionConfig, SubmitButtonOptions, TextElementType, TextField, TextProps, TokenContext, ValidationError, ValidationExecutionConfig, ValidationMessages, ValidatorConfig, ValueExclusionConfig, ValueFieldComponent, ValueHandlingMode, ValueType, WithInputSignals, WrapperConfig, WrapperFieldInputs, WrapperRegistration, WrapperTypeDefinition, WrappersBundle };
|