@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.
Files changed (24) hide show
  1. package/fesm2022/{ng-forge-dynamic-forms-array-field.component-DbWes42V.mjs → ng-forge-dynamic-forms-array-field.component-DJ-LiLm_.mjs} +17 -4
  2. package/fesm2022/ng-forge-dynamic-forms-array-field.component-DJ-LiLm_.mjs.map +1 -0
  3. package/fesm2022/{ng-forge-dynamic-forms-container-field.component-CMhqFGJZ.mjs → ng-forge-dynamic-forms-container-field.component-Pcxg5PQu.mjs} +3 -3
  4. package/fesm2022/{ng-forge-dynamic-forms-container-field.component-CMhqFGJZ.mjs.map → ng-forge-dynamic-forms-container-field.component-Pcxg5PQu.mjs.map} +1 -1
  5. package/fesm2022/{ng-forge-dynamic-forms-container-utils-D_nNzKo1.mjs → ng-forge-dynamic-forms-container-utils-a4RstR2Z.mjs} +2 -2
  6. package/fesm2022/{ng-forge-dynamic-forms-container-utils-D_nNzKo1.mjs.map → ng-forge-dynamic-forms-container-utils-a4RstR2Z.mjs.map} +1 -1
  7. package/fesm2022/{ng-forge-dynamic-forms-css-wrapper.component-htvP_o95.mjs → ng-forge-dynamic-forms-css-wrapper.component-CJACCIEK.mjs} +2 -2
  8. 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
  9. package/fesm2022/{ng-forge-dynamic-forms-emit-initialization-DtkxP7pE.mjs → ng-forge-dynamic-forms-emit-initialization-Bjf_I0D_.mjs} +2 -2
  10. package/fesm2022/{ng-forge-dynamic-forms-emit-initialization-DtkxP7pE.mjs.map → ng-forge-dynamic-forms-emit-initialization-Bjf_I0D_.mjs.map} +1 -1
  11. package/fesm2022/{ng-forge-dynamic-forms-group-field.component-BhYFIDA1.mjs → ng-forge-dynamic-forms-group-field.component-fzKwPrQr.mjs} +24 -4
  12. package/fesm2022/ng-forge-dynamic-forms-group-field.component-fzKwPrQr.mjs.map +1 -0
  13. package/fesm2022/{ng-forge-dynamic-forms-ng-forge-dynamic-forms-DWPPLOrV.mjs → ng-forge-dynamic-forms-ng-forge-dynamic-forms-o3rJX4vG.mjs} +328 -81
  14. package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-o3rJX4vG.mjs.map +1 -0
  15. package/fesm2022/{ng-forge-dynamic-forms-page-field.component-B9v_1shD.mjs → ng-forge-dynamic-forms-page-field.component-BX4R9GGa.mjs} +3 -3
  16. 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
  17. package/fesm2022/{ng-forge-dynamic-forms-text-field.component-B4LnTnIA.mjs → ng-forge-dynamic-forms-text-field.component-B9SK8Vun.mjs} +2 -2
  18. package/fesm2022/{ng-forge-dynamic-forms-text-field.component-B4LnTnIA.mjs.map → ng-forge-dynamic-forms-text-field.component-B9SK8Vun.mjs.map} +1 -1
  19. package/fesm2022/ng-forge-dynamic-forms.mjs +1 -1
  20. package/package.json +1 -1
  21. package/types/ng-forge-dynamic-forms.d.ts +1827 -1608
  22. package/fesm2022/ng-forge-dynamic-forms-array-field.component-DbWes42V.mjs.map +0 -1
  23. package/fesm2022/ng-forge-dynamic-forms-group-field.component-BhYFIDA1.mjs.map +0 -1
  24. 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
- * Configuration for an HTTP request used by declarative HTTP validators, derivations, and conditions.
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 HttpRequestConfig {
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
- * URL path parameters. Keys correspond to `:key` placeholders in the URL.
34
- * Values are expressions evaluated by ExpressionParser against the current EvaluationContext.
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
- params?: Record<string, string>;
30
+ debug(message: string, ...args: unknown[]): void;
46
31
  /**
47
- * Query parameters appended to the URL.
48
- * Values are expressions evaluated by ExpressionParser against the current EvaluationContext.
32
+ * Log an info message.
33
+ * Use for general operational information.
49
34
  */
50
- queryParams?: Record<string, string>;
51
- /** Request body (for POST/PUT/PATCH) */
52
- body?: Record<string, unknown>;
35
+ info(message: string, ...args: unknown[]): void;
53
36
  /**
54
- * When true, top-level string values in `body` are treated as expressions
55
- * and evaluated by ExpressionParser (shallow only — nested objects are passed through as-is).
37
+ * Log a warning message.
38
+ * Use for potentially problematic situations that don't prevent operation.
56
39
  */
57
- evaluateBodyExpressions?: boolean;
58
- /** HTTP headers to include in the request */
59
- headers?: Record<string, string>;
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
- * Comparison operators for field value and form value conditions.
49
+ * Shared shape for DI-scoped warning trackers that dedupe log output.
64
50
  *
65
- * @public
66
- */
67
- type ComparisonOperator = 'equals' | 'notEquals' | 'greater' | 'less' | 'greaterOrEqual' | 'lessOrEqual' | 'contains' | 'startsWith' | 'endsWith' | 'matches';
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
- * @public
55
+ * @internal
72
56
  */
73
- interface FieldValueCondition {
74
- type: 'fieldValue';
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
- * Condition that invokes a registered custom function by name.
62
+ * Represents the state information of a single form field.
84
63
  *
85
- * Register functions via `customFnConfig.customFunctions`.
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 CustomCondition {
90
- type: 'custom';
91
- /** Name of the registered custom function to invoke */
92
- functionName: string;
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
- * Condition that evaluates a JavaScript expression using the secure AST-based parser.
100
+ * Map of field keys to their state information.
96
101
  *
97
- * The expression has access to `formValue`, `fieldValue`, `externalData`, etc.
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
- interface JavascriptCondition {
102
- type: 'javascript';
103
- /** JavaScript expression string to evaluate */
104
- expression: string;
105
- }
107
+ type FormFieldStateMap = Record<string, FieldStateInfo | undefined>;
106
108
  /**
107
- * Condition that evaluates based on an HTTP response from a remote server.
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
- * Since `LogicFn` must return `boolean` synchronously, this condition uses
114
- * a signal-based async resolution pattern internally.
111
+ * Used as `fieldState` in evaluation contexts.
115
112
  *
116
113
  * @public
117
114
  */
118
- interface HttpCondition {
119
- type: 'http';
120
- /** HTTP request configuration */
121
- http: HttpRequestConfig;
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
- * Expression to extract boolean from response (scope: `{ response }`).
124
- * e.g. `'response.allowed'`, `'response.permissions.canEdit'`
125
- * When omitted, the entire response is coerced to boolean: `!!response`
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
- responseExpression?: string;
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 to return while the HTTP request is in-flight.
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
- * Choose based on the logic type and desired UX:
132
- * - For `hidden`: `false` = visible while loading, `true` = hidden while loading
133
- * - For `disabled`: `false` = enabled while loading, `true` = disabled while loading
134
- * - For `required`: `false` = optional while loading, `true` = required while loading
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
- * @default false
137
- */
138
- pendingValue?: boolean;
139
- /** Cache duration in ms for HTTP responses. @default 30000 */
140
- cacheDurationMs?: number;
141
- /**
142
- * Debounce time in milliseconds for re-evaluation when dependent form values change.
143
- * @default 300
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
- debounceMs?: number;
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
- * Value to return while async resolution is pending.
164
+ * Root form value when inside an array context.
165
165
  *
166
- * Choose based on the logic type and desired UX:
167
- * - For `hidden`: `false` = visible while loading, `true` = hidden while loading
168
- * - For `disabled`: `false` = enabled while loading, `true` = disabled while loading
169
- * - For `required`: `false` = optional while loading, `true` = required while loading
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
- * @default false
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
- pendingValue?: boolean;
186
+ rootFormValue?: Record<string, unknown>;
174
187
  /**
175
- * Debounce ms for re-evaluation.
176
- * @default 300
188
+ * Current array index when inside an array derivation.
177
189
  */
178
- debounceMs?: number;
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
- * Condition that determines when this container is hidden.
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
- condition: ConditionalExpression | boolean;
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
- * An array item template is an array of allowed children that defines one OBJECT array item's fields.
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
- * This allows support for:
236
- * - Primitive arrays: `['tag1', 'tag2']`
237
- * - Object arrays: `[{ name: 'Alice', email: '...' }]`
238
- * - Heterogeneous arrays: `[{ value: 'x' }, 'y']` (mixed primitives and objects)
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
- * Key concepts:
245
- * - The outer `fields` array defines INITIAL ITEMS (each element is one array item to render)
246
- * - Each element can be either:
247
- * - A single FieldDef (primitive item) - the field's value is extracted directly
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
- * // Primitive array: ['angular', 'typescript']
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: 'tags',
257
- * type: 'array',
258
- * fields: [
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
- * // Object array: [{ name: 'Alice', email: '...' }]
266
- * {
267
- * key: 'contacts',
268
- * type: 'array',
269
- * fields: [
270
- * [ // Array = object
271
- * { key: 'name', type: 'input', value: 'Alice' },
272
- * { key: 'email', type: 'input', value: 'alice@example.com' }
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
- * // Heterogeneous array: [{ label: 'Structured' }, 'Simple']
279
- * {
280
- * key: 'items',
281
- * type: 'array',
282
- * fields: [
283
- * [{ key: 'label', type: 'input', value: 'Structured' }], // Object item
284
- * { key: 'value', type: 'input', value: 'Simple' }, // Primitive item
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
- * // Empty array (add items via buttons)
290
- * {
291
- * key: 'tags',
292
- * type: 'array',
293
- * fields: [] // No initial items - user adds via button with template
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
- * Nesting constraints: Arrays can contain rows, leaf fields, or groups (for object arrays),
297
- * but NOT pages or other arrays. Runtime validation enforces these rules.
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
- * Note: Arrays are container fields and do not support `meta` since they have no native form element.
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 ArrayField<TFields extends readonly ArrayItemDefinition[] = readonly ArrayItemDefinition[]> extends FieldDef<never> {
302
- type: 'array';
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
- * Array of item definitions. Each element defines one array item:
305
- * - Single FieldDef = primitive item (field's value is extracted directly)
306
- * - Array of FieldDefs = object item (fields are merged into an object)
307
- * - Empty array `[]` = no initial items (add via buttons with explicit templates)
308
- * - Items can have different configurations (heterogeneous arrays supported)
309
- * - Initial values are set via `value` property on each field definition
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
- readonly fields: TFields;
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
- * Logic configurations for conditional array visibility.
318
- * Only 'hidden' type logic is supported for arrays.
362
+ * Query parameters appended to the URL.
363
+ * Values are expressions evaluated by ExpressionParser against the current EvaluationContext.
319
364
  */
320
- readonly logic?: ContainerLogicConfig[];
365
+ queryParams?: Record<string, string>;
366
+ /** Request body (for POST/PUT/PATCH) */
367
+ body?: Record<string, unknown>;
321
368
  /**
322
- * Minimum number of items required in the array.
323
- * Validation fails if the array has fewer items than this value.
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
- readonly minLength?: number;
326
- /**
327
- * Maximum number of items allowed in the array.
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
- * Type guard for ArrayField with proper type narrowing.
334
- * Validates that the field is an array type with a fields property that is an array.
335
- * Fields can contain either single FieldDefs (primitive items) or arrays of FieldDefs (object items).
378
+ * Comparison operators for field value and form value conditions.
379
+ *
380
+ * @public
336
381
  */
337
- declare function isArrayField(field: FieldDef<any>): field is ArrayField;
382
+ type ComparisonOperator = 'equals' | 'notEquals' | 'greater' | 'less' | 'greaterOrEqual' | 'lessOrEqual' | 'contains' | 'startsWith' | 'endsWith' | 'matches';
338
383
  /**
339
- * Configuration for auto-generated add/remove buttons in simplified array fields.
384
+ * Condition that compares a specific field's value against an expected value.
385
+ *
386
+ * @public
340
387
  */
341
- interface ArrayButtonConfig {
342
- /** Custom label for the button */
343
- readonly label?: string;
344
- /** Additional properties passed to the button component */
345
- readonly props?: Record<string, unknown>;
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
- * Simplified array field interface for common use cases.
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
- * Discriminant: `template` presence → simplified API; `Array.isArray(template)` → object vs primitive.
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
- * @example
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
- * @example
366
- * // Object array: [{ name: 'Jane', phone: '555' }]
367
- * {
368
- * key: 'contacts',
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
- * @example
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
- interface SimplifiedArrayField extends FieldDef<never> {
388
- type: 'array';
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
- * Template defining the structure of a single array item.
391
- * - Single field (ArrayAllowedChildren) → primitive array (each item is a single value)
392
- * - Array of fields (readonly ArrayAllowedChildren[]) → object array (each item is an object)
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
- readonly template: ArrayAllowedChildren | readonly ArrayAllowedChildren[];
395
- /** Initial values for the array. Each element creates one array item. */
396
- readonly value?: readonly unknown[];
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
- * Configuration for the auto-generated "Add" button, or `false` to disable it.
399
- * Defaults to a button with label "Add".
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
- readonly addButton?: ArrayButtonConfig | false;
466
+ responseExpression?: string;
402
467
  /**
403
- * Configuration for the auto-generated "Remove" button on each item, or `false` to disable it.
404
- * Defaults to a button with label "Remove".
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
- readonly removeButton?: ArrayButtonConfig | false;
407
- /** Simplified arrays do not support the label property */
408
- readonly label?: never;
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
- * Logic configurations for conditional array visibility.
413
- * Only 'hidden' type logic is supported for arrays.
481
+ * Debounce time in milliseconds for re-evaluation when dependent form values change.
482
+ * @default 300
414
483
  */
415
- readonly logic?: ContainerLogicConfig[];
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
- * Minimum number of items required in the array.
418
- * Validation fails if the array has fewer items than this value.
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
- readonly minLength?: number;
503
+ pendingValue?: boolean;
421
504
  /**
422
- * Maximum number of items allowed in the array.
423
- * Validation fails if the array has more items than this value.
505
+ * Debounce ms for re-evaluation.
506
+ * @default 300
424
507
  */
425
- readonly maxLength?: number;
426
- /** Mutually exclusive with `template` — use `fields` for the full API instead */
427
- readonly fields?: never;
508
+ debounceMs?: number;
428
509
  }
429
510
  /**
430
- * Type guard for SimplifiedArrayField.
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
- * TypeScript cannot enforce field nesting rules due to circular dependency limitations.
441
- * For documentation: Groups should contain rows and leaf fields, but NOT pages or other groups.
442
- * Runtime validation enforces these rules.
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
- * Note: Groups are container fields and do not support `meta` since they have no native form element.
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
- interface GroupField<TFields extends readonly GroupAllowedChildren[] = readonly GroupAllowedChildren[]> extends FieldDef<never> {
447
- type: 'group';
448
- readonly fields: TFields;
449
- /** Groups do not have a label property - they are logical containers only **/
450
- readonly label?: never;
451
- /** Groups do not support meta - they have no native form element **/
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
- * Logic configurations for conditional group visibility.
455
- * Only 'hidden' type logic is supported for groups.
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
- readonly logic?: ContainerLogicConfig[];
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
- * Type guard for GroupField with proper type narrowing
557
+ * Logical OR — at least one sub-condition must be true.
558
+ *
559
+ * @public
461
560
  */
462
- declare function isGroupField(field: FieldDef<any>): field is GroupField;
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
- * Scalar types supported by hidden fields.
466
- * These are the primitive value types that can be stored.
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 HiddenScalar = string | number | boolean;
574
+ type ConditionalExpression = FieldValueCondition | CustomCondition | JavascriptCondition | HttpCondition | AsyncCondition | AndCondition | OrCondition;
575
+
469
576
  /**
470
- * All value types supported by hidden fields.
471
- * Supports both scalar values and arrays of scalars for storing
472
- * multiple IDs or similar data.
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
- type HiddenValue = HiddenScalar | HiddenScalar[];
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
- * Hidden field definition for storing values without rendering UI.
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
- * Mapping configuration for interpreting HTTP responses as validation results.
1091
+ * Custom validator function signature using Angular's public FieldContext API
726
1092
  *
727
- * Expressions in `validWhen` and `errorParams` are evaluated with scope `{ response }` only —
728
- * they do NOT have access to `formValue`, `fieldValue`, or the full `EvaluationContext`.
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
- * Enables programmatic value derivation based on conditions.
1467
- * Derivations are self-targeting: the logic is placed on the field
1468
- * that should receive the computed value.
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
- * @example
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
- * // Set phone prefix based on country selection
1473
- * {
1474
- * key: 'phonePrefix',
1475
- * logic: [{
1476
- * type: 'derivation',
1477
- * value: '+1',
1478
- * condition: {
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
- * // Compute total from quantity and price
1123
+ * // Field configuration
1488
1124
  * {
1489
- * key: 'total',
1490
- * derivation: 'formValue.quantity * formValue.unitPrice'
1125
+ * key: 'username',
1126
+ * validators: [{ type: 'custom', functionName: 'noSpaces' }],
1127
+ * validationMessages: {
1128
+ * noSpaces: 'Spaces are not allowed'
1129
+ * }
1491
1130
  * }
1131
+ * ```
1492
1132
  *
1493
- * // Use custom function for complex logic
1494
- * {
1495
- * key: 'currency',
1496
- * logic: [{
1497
- * type: 'derivation',
1498
- * functionName: 'getCurrencyForCountry'
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
- * // Self-transform with debounced trigger
1503
- * // (applies after user stops typing)
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
- * // Array item derivation (formValue is scoped to current item)
1515
- * {
1516
- * key: 'lineTotal', // Inside array field
1517
- * derivation: 'formValue.quantity * formValue.unitPrice'
1518
- * }
1143
+ * if (otherValue !== undefined && value >= otherValue) {
1144
+ * return { kind: 'notLessThan' };
1145
+ * }
1146
+ * return null;
1147
+ * };
1519
1148
  * ```
1520
1149
  *
1521
- * @public
1522
- */
1523
- type DerivationLogicConfig = OnChangeHttpDerivationLogicConfig | DebouncedHttpDerivationLogicConfig | OnChangeAsyncFunctionDerivationLogicConfig | DebouncedAsyncFunctionDerivationLogicConfig | OnChangeExpressionDerivationLogicConfig | DebouncedExpressionDerivationLogicConfig | OnChangeValueDerivationLogicConfig | DebouncedValueDerivationLogicConfig | OnChangeFunctionDerivationLogicConfig | DebouncedFunctionDerivationLogicConfig;
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
- * - `StateLogicConfig`: For field state changes (hidden, readonly, disabled, required)
1528
- * - `DerivationLogicConfig`: For value derivation (including property derivation via `targetProperty`)
1155
+ * const startDate = ctx.valueOf('startDate' as any);
1156
+ * const endDate = ctx.valueOf('endDate' as any);
1529
1157
  *
1530
- * @public
1531
- */
1532
- type LogicConfig = StateLogicConfig | DerivationLogicConfig;
1533
- /**
1534
- * Log level for derivation debug output.
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
- * - 'none': No debug logging
1537
- * - 'summary': Log cycle completion with counts (default in dev mode)
1538
- * - 'verbose': Log individual derivation evaluations with details
1164
+ * return errors.length > 0 ? errors : null;
1165
+ * };
1166
+ * ```
1539
1167
  *
1540
- * @public
1541
- */
1542
- type DerivationLogLevel = 'none' | 'summary' | 'verbose';
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
- * @param condition - The condition to check
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
- * @public
1175
+ * @template TValue The type of value stored in the field being validated
1550
1176
  */
1551
- declare function isFormStateCondition(condition: StateLogicConfig['condition'] | DerivationLogicConfig['condition']): condition is FormStateCondition;
1552
-
1177
+ type CustomValidator<TValue = unknown> = (ctx: FieldContext<TValue>, params?: Record<string, unknown>) => ValidationError$1 | ValidationError$1[] | null;
1553
1178
  /**
1554
- * Configuration for applying predefined schemas
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 SchemaApplicationConfig {
1557
- /** Schema application type */
1558
- type: 'apply' | 'applyWhen' | 'applyWhenValue' | 'applyEach';
1559
- /** Schema identifier or inline schema definition */
1560
- schema: string | SchemaDefinition;
1561
- /** Condition for conditional application */
1562
- condition?: ConditionalExpression;
1563
- /** Type predicate for applyWhenValue */
1564
- typePredicate?: string;
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
- * Reusable schema definition that can be referenced by name
1251
+ * HTTP request configuration for validateHttp API
1568
1252
  */
1569
- interface SchemaDefinition {
1570
- /** Unique schema identifier */
1571
- name: string;
1572
- /** Schema description */
1573
- description?: string;
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
- * Custom validator function signature using Angular's public FieldContext API
1260
+ * HTTP-based validator configuration for Angular's validateHttp API
1586
1261
  *
1587
- * Takes FieldContext (full Angular context) and optional params, returns validation error(s) or null.
1588
- * Provides access to field state, form hierarchy, and other fields for validation logic.
1262
+ * Angular's validateHttp provides optimized HTTP validation with automatic
1263
+ * request cancellation and integration with the resource API.
1589
1264
  *
1590
- * **Use FieldContext public APIs to access:**
1591
- * - Current field value: `ctx.value()`
1592
- * - Field state: `ctx.state` (errors, touched, dirty, etc.)
1593
- * - Other field values: `ctx.valueOf(path)` where path is a FieldPath
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
- * **Return Types:**
1599
- * - Single error: `{ kind: 'errorKind' }` for field-level validation
1600
- * - Multiple errors: `[{ kind: 'error1' }, { kind: 'error2' }]` for cross-field validation
1601
- * - No error: `null` when validation passes
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
- * **Best Practice - Return Only Error Kind:**
1604
- * Validators should focus on validation logic, not presentation.
1605
- * Return just the error `kind` and configure messages at field level for i18n support.
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 Single Field Validation
1279
+ * @example Username Availability Check (GET)
1608
1280
  * ```typescript
1609
- * const noSpaces: CustomValidator<string> = (ctx) => {
1610
- * const value = ctx.value();
1611
- * if (typeof value === 'string' && value.includes(' ')) {
1612
- * return { kind: 'noSpaces' };
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 Cross-Field Validation with valueOf()
1298
+ * @example Address Validation (POST with Body)
1628
1299
  * ```typescript
1629
- * // Compare two fields using public API
1630
- * const lessThan: CustomValidator<number> = (ctx, params) => {
1631
- * const value = ctx.value();
1632
- * const compareToPath = params?.field as string;
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
- * if (otherValue !== undefined && value >= otherValue) {
1638
- * return { kind: 'notLessThan' };
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
- * @example Multiple Errors (Cross-Field Validation)
1645
- * ```typescript
1646
- * const validateDateRange: CustomValidator = (ctx) => {
1647
- * const errors: ValidationError[] = [];
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
- * const startDate = ctx.valueOf('startDate' as any);
1650
- * const endDate = ctx.valueOf('endDate' as any);
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
- * if (!startDate) errors.push({ kind: 'startDateRequired' });
1653
- * if (!endDate) errors.push({ kind: 'endDateRequired' });
1654
- * if (startDate && endDate && startDate > endDate) {
1655
- * errors.push({ kind: 'invalidDateRange' });
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
- * return errors.length > 0 ? errors : null;
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
- * **Message Resolution Priority:**
1663
- * 1. Field-level `validationMessages[kind]` (highest priority - per-field customization)
1664
- * 2. Form-level `defaultValidationMessages[kind]` (fallback for common messages)
1665
- * 3. **No message configured = Warning logged + error NOT displayed**
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
- * **Important:** Validator-returned messages are NOT used. All messages MUST be explicitly configured.
1503
+ * Exactly one of `functionName` or `fn` must be set.
1668
1504
  *
1669
- * @template TValue The type of value stored in the field being validated
1505
+ * Discriminated from `DeclarativeHttpValidatorConfig` by the presence of `functionName`
1506
+ * or `fn` (and absence of `http` + `responseMapping`).
1670
1507
  */
1671
- type CustomValidator<TValue = unknown> = (ctx: FieldContext<TValue>, params?: Record<string, unknown>) => ValidationError$1 | ValidationError$1[] | null;
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
- * Async custom validator configuration using Angular's resource-based API
1526
+ * Declarative HTTP validator configuration — fully JSON-serializable, no function registration required.
1674
1527
  *
1675
- * Angular's validateAsync uses the resource API for async validation,
1676
- * which provides better integration with Signal Forms lifecycle management.
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
- * **Structure:**
1679
- * - `params`: Function that computes params from field context
1680
- * - `factory`: Function that creates a ResourceRef from params signal
1681
- * - `onSuccess`: Maps successful resource result to validation errors
1682
- * - `onError`: Optional handler for resource errors (network failures, etc.)
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
- * **Use Cases:**
1685
- * - Database lookups via services with resource API
1686
- * - Complex async business logic with Angular resources
1687
- * - Any async operation using Angular's resource pattern
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
- * **Note:** For HTTP validation, prefer `HttpCustomValidator` which provides
1690
- * a simpler API specifically designed for HTTP requests.
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 Database Lookup with Resource
1558
+ * @example
1693
1559
  * ```typescript
1694
- * const checkUsernameAvailable: AsyncCustomValidator<string> = {
1695
- * params: (ctx) => ({ username: ctx.value() }),
1696
- * factory: (params) => {
1697
- * const injector = inject(Injector);
1698
- * return resource({
1699
- * request: () => params(),
1700
- * loader: ({ request }) => {
1701
- * if (!request?.username) return null;
1702
- * const service = injector.get(UserService);
1703
- * return firstValueFrom(service.checkAvailability(request.username));
1704
- * }
1705
- * });
1706
- * },
1707
- * onSuccess: (result, ctx) => {
1708
- * if (!result) return null;
1709
- * return result.available ? null : { kind: 'usernameTaken' };
1710
- * },
1711
- * onError: (error, ctx) => {
1712
- * console.error('Availability check failed:', error);
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
- * @template TValue The type of value stored in the field being validated
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
- interface AsyncCustomValidator<TValue = unknown, TParams = unknown, TResult = unknown> {
1723
- /**
1724
- * Function that receives field context and returns resource params.
1725
- * Params will be tracked as a signal and trigger resource reload when changed.
1726
- */
1727
- readonly params: (ctx: FieldContext<TValue>, config?: Record<string, unknown>) => TParams;
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
- * Function that creates a ResourceRef from the params signal.
1730
- * The resource will be automatically managed by Angular's lifecycle.
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
- readonly factory: (params: Signal<TParams | undefined>) => ResourceRef<TResult | undefined>;
1610
+ type: StateLogicType;
1733
1611
  /**
1734
- * Optional function to map successful resource result to validation errors.
1735
- * Return null if validation passes, or ValidationError/ValidationError[] if validation fails.
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
- readonly onSuccess?: (result: TResult, ctx: FieldContext<TValue>) => TreeValidationResult;
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
- * Optional error handler for resource errors (network failures, etc.).
1740
- * Return null to ignore the error, or ValidationError to display it to the user.
1645
+ * Trigger for immediate evaluation.
1646
+ * @default 'onChange'
1741
1647
  */
1742
- readonly onError?: (error: unknown, ctx: FieldContext<TValue>) => ValidationError$1 | ValidationError$1[] | null;
1648
+ trigger?: 'onChange';
1649
+ /** Not allowed for onChange trigger */
1650
+ debounceMs?: never;
1743
1651
  }
1744
1652
  /**
1745
- * HTTP request configuration for validateHttp API
1653
+ * State logic that evaluates after a debounce period.
1654
+ *
1655
+ * @internal
1746
1656
  */
1747
- interface HttpResourceRequest {
1748
- url: string;
1749
- method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
1750
- body?: unknown;
1751
- headers?: Record<string, string | string[]>;
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
- * HTTP-based validator configuration for Angular's validateHttp API
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
- * **Important:** `onSuccess` uses inverted logic - it maps SUCCESSFUL HTTP responses
1770
- * to validation errors. For example, if the API returns `{ available: false }`,
1771
- * your `onSuccess` should return `{ kind: 'usernameTaken' }`.
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 Username Availability Check (GET)
1676
+ * @example
1774
1677
  * ```typescript
1775
- * const checkUsername: HttpCustomValidator<string> = {
1776
- * request: (ctx) => {
1777
- * const username = ctx.value();
1778
- * if (!username) return undefined; // Skip validation if empty
1779
- * return `/api/users/check-username?username=${encodeURIComponent(username)}`;
1780
- * },
1781
- * onSuccess: (response, ctx) => {
1782
- * // Inverted logic: successful response may indicate validation failure
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
- * @example Address Validation (POST with Body)
1793
- * ```typescript
1794
- * const validateAddress: HttpCustomValidator = {
1795
- * request: (ctx) => {
1796
- * const zipCode = ctx.value();
1797
- * if (!zipCode) return undefined;
1689
+ * // Disable button when form is submitting
1690
+ * {
1691
+ * type: 'disabled',
1692
+ * condition: 'formSubmitting'
1693
+ * }
1798
1694
  *
1799
- * return {
1800
- * url: '/api/validate-address',
1801
- * method: 'POST',
1802
- * body: {
1803
- * street: ctx.valueOf('street' as any),
1804
- * city: ctx.valueOf('city' as any),
1805
- * zipCode: zipCode
1806
- * },
1807
- * headers: { 'Content-Type': 'application/json' }
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
- * @template TValue The type of value stored in the field being validated
1817
- * @template TResult The type of HTTP response
1708
+ * @public
1818
1709
  */
1819
- interface HttpCustomValidator<TValue = unknown, TResult = unknown> {
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
- * Function that returns the HTTP request configuration.
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
- readonly request: (ctx: FieldContext<TValue>, config?: Record<string, unknown>) => string | HttpResourceRequest | undefined;
1720
+ type: 'derivation';
1828
1721
  /**
1829
- * REQUIRED function to map successful HTTP response to validation errors.
1722
+ * Target property name for property derivation.
1830
1723
  *
1831
- * **Inverted Logic:** This is called on successful HTTP responses.
1832
- * Return null if validation passes, or ValidationError/ValidationError[] if validation fails.
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
- * Example: API returns `{ available: false }` → return `{ kind: 'usernameTaken' }`
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
- readonly onSuccess: (result: TResult, ctx: FieldContext<TValue>) => TreeValidationResult;
1757
+ targetProperty?: string;
1837
1758
  /**
1838
- * Optional error handler for HTTP errors (network failures, 4xx/5xx status codes).
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
- * Common pattern: Log the error and return null to avoid blocking form submission
1842
- * on network issues.
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
- readonly onError?: (error: unknown, ctx: FieldContext<TValue>) => ValidationError$1 | ValidationError$1[] | null;
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
- * Interface for dynamic forms logger implementations.
1849
- * Allows custom logging integrations (Sentry, DataDog, etc.)
1856
+ * Trigger variants for derivation timing.
1857
+ * @internal
1850
1858
  */
1851
- interface Logger {
1852
- /**
1853
- * Log a debug message.
1854
- * Use for detailed diagnostic information during development.
1855
- */
1856
- debug(message: string, ...args: unknown[]): void;
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
- * Log an info message.
1859
- * Use for general operational information.
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
- info(message: string, ...args: unknown[]): void;
1909
+ http: HttpRequestConfig;
1862
1910
  /**
1863
- * Log a warning message.
1864
- * Use for potentially problematic situations that don't prevent operation.
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
- warn(message: string, ...args: unknown[]): void;
1919
+ dependsOn: string[];
1867
1920
  /**
1868
- * Log an error message.
1869
- * Use for error conditions that may affect functionality.
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
- error(message: string, ...args: unknown[]): void;
1931
+ responseExpression: string;
1932
+ value?: never;
1933
+ expression?: never;
1934
+ functionName?: never;
1935
+ asyncFunctionName?: never;
1872
1936
  }
1873
-
1874
1937
  /**
1875
- * Shared shape for DI-scoped warning trackers that dedupe log output.
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 WarningTracker {
1884
- warnedKeys: Set<string>;
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
- * Represents the state information of a single form field.
1889
- *
1890
- * Used in evaluation contexts to allow expressions and conditions
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
- * @public
1969
+ * @internal
1894
1970
  */
1895
- interface FieldStateInfo {
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
- * Whether the field is currently hidden.
1973
+ * Name of a registered async derivation function.
1905
1974
  *
1906
- * Note: This is a library-managed property, not a native Angular Signal Forms
1907
- * property. Returns `false` if the field instance does not expose a `hidden` signal.
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
- * Note: This is a library-managed property, not a native Angular Signal Forms
1914
- * property. Returns `false` if the field instance does not expose a `readonly` signal.
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
- readonly readonly: boolean;
1991
+ asyncFunctionName: string;
1992
+ /** Inline form is forbidden when `asyncFunctionName` is set */
1993
+ asyncFn?: never;
1994
+ }) | (AsyncFunctionDerivationShared & {
1917
1995
  /**
1918
- * Whether the field is currently disabled.
1996
+ * Inline async derivation function. Mutually exclusive with `asyncFunctionName`.
1919
1997
  *
1920
- * Note: This is a library-managed property, not a native Angular Signal Forms
1921
- * property. Returns `false` if the field instance does not expose a `disabled` signal.
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
- readonly disabled: boolean;
1924
- }
1925
- /**
1926
- * Map of field keys to their state information.
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
- * Field state context for the current field being evaluated.
1936
- *
1937
- * Used as `fieldState` in evaluation contexts.
2007
+ * Base for expression derivations.
1938
2008
  *
1939
- * @public
2009
+ * @internal
1940
2010
  */
1941
- type FieldStateContext = FieldStateInfo;
1942
-
1943
- interface EvaluationContext<TValue = unknown, TFormValue extends Record<string, unknown> = any> {
1944
- /** Current field value */
1945
- fieldValue: TValue;
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
- * Value of the field's nearest parent **group** (or array item, when the
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
- * Resolution at evaluation time:
1970
- * - Inside a group at form root: the parent group's value object (e.g.,
1971
- * for `address.state`, `groupValue === formValue.address`).
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
- * // Field 'state' inside group 'address':
1982
- * deriveStateFromCountry: (ctx) => {
1983
- * const country = ctx.groupValue?.country; // no need to know parent key
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
- groupValue?: unknown;
2031
+ expression: string;
1989
2032
  /**
1990
- * Root form value when inside an array context.
2033
+ * Explicit field dependencies for expressions.
1991
2034
  *
1992
- * This provides access to values outside the current array item.
1993
- * When a derivation targets an array item field (e.g., `items.$.lineTotal`),
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
- * // In an array item derivation (on the lineTotal field inside lineItems array):
2040
+ * // Override automatic detection for complex expressions
2000
2041
  * {
2001
- * key: 'lineTotal',
2002
- * type: 'input',
2003
- * // formValue = current array item { quantity: 2, unitPrice: 50 }
2004
- * // rootFormValue = entire form { globalDiscount: 0.1, lineItems: [...] }
2005
- * derivation: 'formValue.quantity * formValue.unitPrice * (1 - rootFormValue.globalDiscount)'
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
- arrayPath?: string;
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
- * External data signals resolved to their current values.
2070
+ * Static value to set on this field.
2023
2071
  *
2024
- * This allows forms to reference data from outside the form context
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
- * // In form config:
2030
- * externalData: {
2031
- * userRole: computed(() => this.userService.currentRole()),
2032
- * permissions: computed(() => this.authService.permissions()),
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
- externalData?: Record<string, unknown>;
2082
+ value: unknown;
2043
2083
  /**
2044
- * DI-scoped tracker for deprecation warnings.
2045
- * Used by the condition evaluator to deduplicate deprecation warnings.
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
- deprecationTracker?: WarningTracker;
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
- * State of the current field being evaluated.
2106
+ * Explicit field dependencies for function derivations.
2050
2107
  *
2051
- * Provides access to field state properties like `touched`, `dirty`, `valid`, etc.
2052
- * Uses a Proxy for lazy evaluation — properties are only read when accessed.
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
- * // In a JavaScript expression:
2057
- * condition: {
2058
- * type: 'javascript',
2059
- * expression: "fieldState.touched && fieldState.dirty"
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
- fieldState?: FieldStateContext;
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
- * State of all fields in the form, keyed by field key.
2140
+ * Name of a registered custom derivation function.
2066
2141
  *
2067
- * Provides access to any field's state properties by key.
2068
- * Uses a Proxy for lazy evaluation — field state is only read when accessed.
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
- * // In a JavaScript expression:
2073
- * condition: {
2074
- * type: 'javascript',
2075
- * expression: "formFieldState.email.dirty && formFieldState.email.valid"
2076
- * }
2147
+ * functionName: 'getCurrencyForCountry'
2148
+ * functionName: 'calculateTax'
2149
+ * functionName: 'formatPhoneNumber'
2077
2150
  * ```
2078
2151
  */
2079
- formFieldState?: FormFieldStateMap;
2080
- /** Allow additional properties for flexible expression evaluation */
2081
- [key: string]: unknown;
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
- * Custom function signature for conditional expressions
2086
- *
2087
- * Custom functions are used for conditional logic in:
2088
- * - `when` conditions (field visibility, conditional validation)
2089
- * - `readonly` logic (dynamic readonly state)
2090
- * - `disabled` logic (dynamic disabled state)
2091
- * - Dynamic value calculations
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
- * Unlike validators, custom functions:
2094
- * - Return any value (typically boolean for conditions, but could be strings, numbers, etc.)
2095
- * - Do NOT return ValidationError objects
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
- * // Boolean condition function
2101
- * const isAdult: CustomFunction = (ctx) => {
2102
- * return ctx.age >= 18;
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
- * // Used in field configuration:
2241
+ * // Compute total from quantity and price
2106
2242
  * {
2107
- * key: 'alcoholPreference',
2108
- * type: 'select',
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
- * @example
2114
- * ```typescript
2115
- * // Calculation function
2116
- * const calculateDiscount: CustomFunction = (ctx) => {
2117
- * const price = ctx.price || 0;
2118
- * const isVip = ctx.isVip || false;
2119
- * return isVip ? price * 0.8 : price * 0.9;
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 CustomFunction<TFormValue extends Record<string, unknown> = any> = (context: EvaluationContext<unknown, TFormValue>) => unknown;
2124
-
2277
+ type DerivationLogicConfig = OnChangeHttpDerivationLogicConfig | DebouncedHttpDerivationLogicConfig | OnChangeAsyncFunctionDerivationLogicConfig | DebouncedAsyncFunctionDerivationLogicConfig | OnChangeExpressionDerivationLogicConfig | DebouncedExpressionDerivationLogicConfig | OnChangeValueDerivationLogicConfig | DebouncedValueDerivationLogicConfig | OnChangeFunctionDerivationLogicConfig | DebouncedFunctionDerivationLogicConfig;
2125
2278
  /**
2126
- * Async function for derivations — returns any value.
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
- * @example
2132
- * ```typescript
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 AsyncDerivationFunction<TFormValue extends Record<string, unknown> = any> = (context: EvaluationContext<unknown, TFormValue>) => Promise<unknown> | Observable<unknown>;
2286
+ type LogicConfig = StateLogicConfig | DerivationLogicConfig;
2143
2287
  /**
2144
- * Async function for conditions — returns boolean.
2288
+ * Log level for derivation debug output.
2145
2289
  *
2146
- * The function receives the evaluation context and may return a Promise or Observable.
2147
- * Register functions in `customFnConfig.asyncConditions`.
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
- * @example
2150
- * ```typescript
2151
- * const checkPermission: AsyncConditionFunction = async (context) => {
2152
- * const response = await fetch(`/api/permissions?user=${context.formValue.userId}`);
2153
- * const data = await response.json();
2154
- * return data.canEdit;
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
- type AsyncConditionFunction<TFormValue extends Record<string, unknown> = any> = (context: EvaluationContext<unknown, TFormValue>) => Promise<boolean> | Observable<boolean>;
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 };