@ng-forge/dynamic-forms 0.9.0-next.10 → 0.9.0-next.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/{ng-forge-dynamic-forms-array-field.component-DbWes42V.mjs → ng-forge-dynamic-forms-array-field.component-BgCrTME1.mjs} +167 -69
- package/fesm2022/ng-forge-dynamic-forms-array-field.component-BgCrTME1.mjs.map +1 -0
- package/fesm2022/{ng-forge-dynamic-forms-container-field.component-CMhqFGJZ.mjs → ng-forge-dynamic-forms-container-field.component-VHaBk_1l.mjs} +9 -5
- package/fesm2022/ng-forge-dynamic-forms-container-field.component-VHaBk_1l.mjs.map +1 -0
- package/fesm2022/{ng-forge-dynamic-forms-container-utils-D_nNzKo1.mjs → ng-forge-dynamic-forms-container-utils-CGJiiKBY.mjs} +2 -2
- package/fesm2022/{ng-forge-dynamic-forms-container-utils-D_nNzKo1.mjs.map → ng-forge-dynamic-forms-container-utils-CGJiiKBY.mjs.map} +1 -1
- package/fesm2022/{ng-forge-dynamic-forms-css-wrapper.component-htvP_o95.mjs → ng-forge-dynamic-forms-css-wrapper.component-BOdw40ZF.mjs} +2 -2
- package/fesm2022/{ng-forge-dynamic-forms-css-wrapper.component-htvP_o95.mjs.map → ng-forge-dynamic-forms-css-wrapper.component-BOdw40ZF.mjs.map} +1 -1
- package/fesm2022/{ng-forge-dynamic-forms-emit-initialization-DtkxP7pE.mjs → ng-forge-dynamic-forms-emit-initialization-D72mQxlX.mjs} +2 -2
- package/fesm2022/{ng-forge-dynamic-forms-emit-initialization-DtkxP7pE.mjs.map → ng-forge-dynamic-forms-emit-initialization-D72mQxlX.mjs.map} +1 -1
- package/fesm2022/{ng-forge-dynamic-forms-group-field.component-BhYFIDA1.mjs → ng-forge-dynamic-forms-group-field.component-BvTQwoLg.mjs} +30 -6
- package/fesm2022/ng-forge-dynamic-forms-group-field.component-BvTQwoLg.mjs.map +1 -0
- package/fesm2022/{ng-forge-dynamic-forms-ng-forge-dynamic-forms-DWPPLOrV.mjs → ng-forge-dynamic-forms-ng-forge-dynamic-forms-BAPGCjUs.mjs} +713 -318
- package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-BAPGCjUs.mjs.map +1 -0
- package/fesm2022/{ng-forge-dynamic-forms-page-field.component-B9v_1shD.mjs → ng-forge-dynamic-forms-page-field.component-DDzmhJNn.mjs} +9 -5
- package/fesm2022/ng-forge-dynamic-forms-page-field.component-DDzmhJNn.mjs.map +1 -0
- package/fesm2022/{ng-forge-dynamic-forms-text-field.component-B4LnTnIA.mjs → ng-forge-dynamic-forms-text-field.component-Bo4zNJXY.mjs} +2 -2
- package/fesm2022/{ng-forge-dynamic-forms-text-field.component-B4LnTnIA.mjs.map → ng-forge-dynamic-forms-text-field.component-Bo4zNJXY.mjs.map} +1 -1
- package/fesm2022/ng-forge-dynamic-forms.mjs +1 -1
- package/package.json +1 -1
- package/types/ng-forge-dynamic-forms.d.ts +2042 -1803
- package/fesm2022/ng-forge-dynamic-forms-array-field.component-DbWes42V.mjs.map +0 -1
- package/fesm2022/ng-forge-dynamic-forms-container-field.component-CMhqFGJZ.mjs.map +0 -1
- package/fesm2022/ng-forge-dynamic-forms-group-field.component-BhYFIDA1.mjs.map +0 -1
- package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-DWPPLOrV.mjs.map +0 -1
- package/fesm2022/ng-forge-dynamic-forms-page-field.component-B9v_1shD.mjs.map +0 -1
|
@@ -16,502 +16,631 @@ type Prettify<T> = {
|
|
|
16
16
|
[K in keyof T]: T[K];
|
|
17
17
|
} & {};
|
|
18
18
|
|
|
19
|
-
type DynamicText = string | Observable<string> | Signal<string>;
|
|
20
|
-
|
|
21
19
|
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* All string values in `queryParams` are treated as expressions evaluated by ExpressionParser.
|
|
25
|
-
* When `evaluateBodyExpressions` is true, top-level string values in `body` are also evaluated as expressions (shallow only).
|
|
20
|
+
* Interface for dynamic forms logger implementations.
|
|
21
|
+
* Allows custom logging integrations (Sentry, DataDog, etc.)
|
|
26
22
|
*/
|
|
27
|
-
interface
|
|
28
|
-
/** URL to send the request to */
|
|
29
|
-
url: string;
|
|
30
|
-
/** HTTP method. Defaults to 'GET' */
|
|
31
|
-
method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
23
|
+
interface Logger {
|
|
32
24
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* @example
|
|
37
|
-
* ```typescript
|
|
38
|
-
* {
|
|
39
|
-
* url: '/api/users/:userId/orders/:orderId',
|
|
40
|
-
* params: { userId: 'formValue.userId', orderId: 'fieldValue' }
|
|
41
|
-
* }
|
|
42
|
-
* // With userId=42 and fieldValue='abc' → '/api/users/42/orders/abc'
|
|
43
|
-
* ```
|
|
25
|
+
* Log a debug message.
|
|
26
|
+
* Use for detailed diagnostic information during development.
|
|
44
27
|
*/
|
|
45
|
-
|
|
28
|
+
debug(message: string, ...args: unknown[]): void;
|
|
46
29
|
/**
|
|
47
|
-
*
|
|
48
|
-
*
|
|
30
|
+
* Log an info message.
|
|
31
|
+
* Use for general operational information.
|
|
49
32
|
*/
|
|
50
|
-
|
|
51
|
-
/** Request body (for POST/PUT/PATCH) */
|
|
52
|
-
body?: Record<string, unknown>;
|
|
33
|
+
info(message: string, ...args: unknown[]): void;
|
|
53
34
|
/**
|
|
54
|
-
*
|
|
55
|
-
*
|
|
35
|
+
* Log a warning message.
|
|
36
|
+
* Use for potentially problematic situations that don't prevent operation.
|
|
56
37
|
*/
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
|
|
38
|
+
warn(message: string, ...args: unknown[]): void;
|
|
39
|
+
/**
|
|
40
|
+
* Log an error message.
|
|
41
|
+
* Use for error conditions that may affect functionality.
|
|
42
|
+
*/
|
|
43
|
+
error(message: string, ...args: unknown[]): void;
|
|
60
44
|
}
|
|
61
45
|
|
|
62
46
|
/**
|
|
63
|
-
*
|
|
47
|
+
* Shared shape for DI-scoped warning trackers that dedupe log output.
|
|
64
48
|
*
|
|
65
|
-
*
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Condition that compares a specific field's value against an expected value.
|
|
49
|
+
* Used by both the derivation-warning tracker (keys are field paths) and the
|
|
50
|
+
* deprecation-warning tracker (keys are deprecation IDs). The property is
|
|
51
|
+
* generic so the same shape works for any string-keyed warning domain.
|
|
70
52
|
*
|
|
71
|
-
* @
|
|
53
|
+
* @internal
|
|
72
54
|
*/
|
|
73
|
-
interface
|
|
74
|
-
|
|
75
|
-
/** Path to the field whose value to compare */
|
|
76
|
-
fieldPath: string;
|
|
77
|
-
/** Comparison operator */
|
|
78
|
-
operator: ComparisonOperator;
|
|
79
|
-
/** Value to compare against */
|
|
80
|
-
value?: unknown;
|
|
55
|
+
interface WarningTracker {
|
|
56
|
+
warnedKeys: Set<string>;
|
|
81
57
|
}
|
|
58
|
+
|
|
82
59
|
/**
|
|
83
|
-
*
|
|
60
|
+
* Represents the state information of a single form field.
|
|
84
61
|
*
|
|
85
|
-
*
|
|
62
|
+
* Used in evaluation contexts to allow expressions and conditions
|
|
63
|
+
* to reference field state (e.g., `fieldState.touched`, `formFieldState.email.dirty`).
|
|
86
64
|
*
|
|
87
65
|
* @public
|
|
88
66
|
*/
|
|
89
|
-
interface
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
67
|
+
interface FieldStateInfo {
|
|
68
|
+
readonly touched: boolean;
|
|
69
|
+
readonly dirty: boolean;
|
|
70
|
+
/** Convenience property: equivalent to `!dirty` */
|
|
71
|
+
readonly pristine: boolean;
|
|
72
|
+
readonly valid: boolean;
|
|
73
|
+
readonly invalid: boolean;
|
|
74
|
+
readonly pending: boolean;
|
|
75
|
+
/**
|
|
76
|
+
* Whether the field is currently hidden.
|
|
77
|
+
*
|
|
78
|
+
* Note: This is a library-managed property, not a native Angular Signal Forms
|
|
79
|
+
* property. Returns `false` if the field instance does not expose a `hidden` signal.
|
|
80
|
+
*/
|
|
81
|
+
readonly hidden: boolean;
|
|
82
|
+
/**
|
|
83
|
+
* Whether the field is currently readonly.
|
|
84
|
+
*
|
|
85
|
+
* Note: This is a library-managed property, not a native Angular Signal Forms
|
|
86
|
+
* property. Returns `false` if the field instance does not expose a `readonly` signal.
|
|
87
|
+
*/
|
|
88
|
+
readonly readonly: boolean;
|
|
89
|
+
/**
|
|
90
|
+
* Whether the field is currently disabled.
|
|
91
|
+
*
|
|
92
|
+
* Note: This is a library-managed property, not a native Angular Signal Forms
|
|
93
|
+
* property. Returns `false` if the field instance does not expose a `disabled` signal.
|
|
94
|
+
*/
|
|
95
|
+
readonly disabled: boolean;
|
|
93
96
|
}
|
|
94
97
|
/**
|
|
95
|
-
*
|
|
98
|
+
* Map of field keys to their state information.
|
|
96
99
|
*
|
|
97
|
-
*
|
|
100
|
+
* Used as `formFieldState` in evaluation contexts to access
|
|
101
|
+
* state of any field in the form by key.
|
|
98
102
|
*
|
|
99
103
|
* @public
|
|
100
104
|
*/
|
|
101
|
-
|
|
102
|
-
type: 'javascript';
|
|
103
|
-
/** JavaScript expression string to evaluate */
|
|
104
|
-
expression: string;
|
|
105
|
-
}
|
|
105
|
+
type FormFieldStateMap = Record<string, FieldStateInfo | undefined>;
|
|
106
106
|
/**
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
* The HTTP request is resolved reactively — when dependent form values change,
|
|
110
|
-
* the request is re-evaluated (with debouncing). The result is cached per
|
|
111
|
-
* resolved request to avoid redundant network calls.
|
|
107
|
+
* Field state context for the current field being evaluated.
|
|
112
108
|
*
|
|
113
|
-
*
|
|
114
|
-
* a signal-based async resolution pattern internally.
|
|
109
|
+
* Used as `fieldState` in evaluation contexts.
|
|
115
110
|
*
|
|
116
111
|
* @public
|
|
117
112
|
*/
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
113
|
+
type FieldStateContext = FieldStateInfo;
|
|
114
|
+
|
|
115
|
+
interface EvaluationContext<TValue = unknown, TFormValue extends Record<string, unknown> = any> {
|
|
116
|
+
/** Current field value */
|
|
117
|
+
fieldValue: TValue;
|
|
122
118
|
/**
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
119
|
+
* Form value for the current evaluation scope.
|
|
120
|
+
*
|
|
121
|
+
* For regular (non-array) derivations, this contains the complete form value.
|
|
122
|
+
* For array item derivations, this is scoped to the current array item.
|
|
123
|
+
* Use `rootFormValue` to access the complete form when inside an array context.
|
|
126
124
|
*/
|
|
127
|
-
|
|
125
|
+
formValue: TFormValue;
|
|
126
|
+
/** Field path for relative references */
|
|
127
|
+
fieldPath: string;
|
|
128
|
+
/** Custom evaluation functions */
|
|
129
|
+
customFunctions?: Record<string, (context: EvaluationContext) => unknown>;
|
|
130
|
+
/** Logger for diagnostic output */
|
|
131
|
+
logger: Logger;
|
|
128
132
|
/**
|
|
129
|
-
* Value
|
|
133
|
+
* Value of the field's nearest parent **group** (or array item, when the
|
|
134
|
+
* field has no inner group above the array boundary).
|
|
130
135
|
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
136
|
+
* Complements `formValue` and `fieldValue` for derivations on fields nested
|
|
137
|
+
* inside groups built by factory helpers, where the parent group's key
|
|
138
|
+
* isn't known at config-authoring time. Mirrors the runtime semantics of
|
|
139
|
+
* the `'$group'` token used in `dependsOn`.
|
|
135
140
|
*
|
|
136
|
-
*
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
*
|
|
143
|
-
*
|
|
141
|
+
* Resolution at evaluation time:
|
|
142
|
+
* - Inside a group at form root: the parent group's value object (e.g.,
|
|
143
|
+
* for `address.state`, `groupValue === formValue.address`).
|
|
144
|
+
* - Inside nested groups: the innermost parent group's value object.
|
|
145
|
+
* - Directly inside an array item (no inner group): the array item itself
|
|
146
|
+
* (same shape as `formValue` in array context).
|
|
147
|
+
* - Inside a group inside an array item: the inner group's value object
|
|
148
|
+
* within the current item.
|
|
149
|
+
* - Field at form root with no parent container: `undefined`.
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* ```typescript
|
|
153
|
+
* // Field 'state' inside group 'address':
|
|
154
|
+
* deriveStateFromCountry: (ctx) => {
|
|
155
|
+
* const country = ctx.groupValue?.country; // no need to know parent key
|
|
156
|
+
* return country === 'usa' ? 'NY' : '';
|
|
157
|
+
* }
|
|
158
|
+
* ```
|
|
144
159
|
*/
|
|
145
|
-
|
|
146
|
-
}
|
|
147
|
-
/**
|
|
148
|
-
* Condition that evaluates based on a registered async function.
|
|
149
|
-
*
|
|
150
|
-
* The function is resolved reactively — when dependent form values change,
|
|
151
|
-
* the function is re-evaluated (with debouncing). The result is cached per
|
|
152
|
-
* evaluation to avoid redundant calls.
|
|
153
|
-
*
|
|
154
|
-
* Since `LogicFn` must return `boolean` synchronously, this condition uses
|
|
155
|
-
* a signal-based async resolution pattern internally.
|
|
156
|
-
*
|
|
157
|
-
* @public
|
|
158
|
-
*/
|
|
159
|
-
interface AsyncCondition {
|
|
160
|
-
type: 'async';
|
|
161
|
-
/** Name of the registered async condition function */
|
|
162
|
-
asyncFunctionName: string;
|
|
160
|
+
groupValue?: unknown;
|
|
163
161
|
/**
|
|
164
|
-
*
|
|
162
|
+
* Root form value when inside an array context.
|
|
165
163
|
*
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
164
|
+
* This provides access to values outside the current array item.
|
|
165
|
+
* When a derivation targets an array item field (e.g., `items.$.lineTotal`),
|
|
166
|
+
* `formValue` is scoped to the current array item, while `rootFormValue`
|
|
167
|
+
* provides access to the entire form value including fields outside the array.
|
|
170
168
|
*
|
|
171
|
-
* @
|
|
169
|
+
* @example
|
|
170
|
+
* ```typescript
|
|
171
|
+
* // In an array item derivation (on the lineTotal field inside lineItems array):
|
|
172
|
+
* {
|
|
173
|
+
* key: 'lineTotal',
|
|
174
|
+
* type: 'input',
|
|
175
|
+
* // formValue = current array item { quantity: 2, unitPrice: 50 }
|
|
176
|
+
* // rootFormValue = entire form { globalDiscount: 0.1, lineItems: [...] }
|
|
177
|
+
* derivation: 'formValue.quantity * formValue.unitPrice * (1 - rootFormValue.globalDiscount)'
|
|
178
|
+
* }
|
|
179
|
+
* ```
|
|
180
|
+
*
|
|
181
|
+
* For non-array derivations, `rootFormValue` is not set and `formValue`
|
|
182
|
+
* contains the entire form value.
|
|
172
183
|
*/
|
|
173
|
-
|
|
184
|
+
rootFormValue?: Record<string, unknown>;
|
|
174
185
|
/**
|
|
175
|
-
*
|
|
176
|
-
* @default 300
|
|
186
|
+
* Current array index when inside an array derivation.
|
|
177
187
|
*/
|
|
178
|
-
|
|
179
|
-
}
|
|
180
|
-
/**
|
|
181
|
-
* Logical AND — all sub-conditions must be true.
|
|
182
|
-
*
|
|
183
|
-
* @public
|
|
184
|
-
*/
|
|
185
|
-
interface AndCondition {
|
|
186
|
-
type: 'and';
|
|
187
|
-
/** Sub-conditions that must all evaluate to true */
|
|
188
|
-
conditions: ConditionalExpression[];
|
|
189
|
-
}
|
|
190
|
-
/**
|
|
191
|
-
* Logical OR — at least one sub-condition must be true.
|
|
192
|
-
*
|
|
193
|
-
* @public
|
|
194
|
-
*/
|
|
195
|
-
interface OrCondition {
|
|
196
|
-
type: 'or';
|
|
197
|
-
/** Sub-conditions where at least one must evaluate to true */
|
|
198
|
-
conditions: ConditionalExpression[];
|
|
199
|
-
}
|
|
200
|
-
/**
|
|
201
|
-
* Discriminated union of all conditional expression types.
|
|
202
|
-
*
|
|
203
|
-
* Each variant only allows the properties relevant to its type,
|
|
204
|
-
* providing compile-time safety against invalid property combinations.
|
|
205
|
-
*
|
|
206
|
-
* @public
|
|
207
|
-
*/
|
|
208
|
-
type ConditionalExpression = FieldValueCondition | CustomCondition | JavascriptCondition | HttpCondition | AsyncCondition | AndCondition | OrCondition;
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* Logic configuration for container fields (page, group, row, array).
|
|
212
|
-
* Containers only support the 'hidden' logic type since they are layout containers,
|
|
213
|
-
* not form controls (they can't be readonly, disabled, or required).
|
|
214
|
-
*/
|
|
215
|
-
interface ContainerLogicConfig {
|
|
216
|
-
/** Only 'hidden' is supported for container fields */
|
|
217
|
-
type: 'hidden';
|
|
188
|
+
arrayIndex?: number;
|
|
218
189
|
/**
|
|
219
|
-
*
|
|
220
|
-
* Can be a boolean or a conditional expression evaluated against form values.
|
|
190
|
+
* Path to the array field when inside an array derivation.
|
|
221
191
|
*/
|
|
222
|
-
|
|
192
|
+
arrayPath?: string;
|
|
193
|
+
/**
|
|
194
|
+
* External data signals resolved to their current values.
|
|
195
|
+
*
|
|
196
|
+
* This allows forms to reference data from outside the form context
|
|
197
|
+
* in conditional logic, derivations, and other expressions.
|
|
198
|
+
*
|
|
199
|
+
* @example
|
|
200
|
+
* ```typescript
|
|
201
|
+
* // In form config:
|
|
202
|
+
* externalData: {
|
|
203
|
+
* userRole: computed(() => this.userService.currentRole()),
|
|
204
|
+
* permissions: computed(() => this.authService.permissions()),
|
|
205
|
+
* }
|
|
206
|
+
*
|
|
207
|
+
* // In JavaScript expression:
|
|
208
|
+
* condition: {
|
|
209
|
+
* type: 'javascript',
|
|
210
|
+
* expression: "externalData.userRole === 'admin'"
|
|
211
|
+
* }
|
|
212
|
+
* ```
|
|
213
|
+
*/
|
|
214
|
+
externalData?: Record<string, unknown>;
|
|
215
|
+
/**
|
|
216
|
+
* DI-scoped tracker for deprecation warnings.
|
|
217
|
+
* Used by the condition evaluator to deduplicate deprecation warnings.
|
|
218
|
+
*/
|
|
219
|
+
deprecationTracker?: WarningTracker;
|
|
220
|
+
/**
|
|
221
|
+
* State of the current field being evaluated.
|
|
222
|
+
*
|
|
223
|
+
* Provides access to field state properties like `touched`, `dirty`, `valid`, etc.
|
|
224
|
+
* Uses a Proxy for lazy evaluation — properties are only read when accessed.
|
|
225
|
+
*
|
|
226
|
+
* @example
|
|
227
|
+
* ```typescript
|
|
228
|
+
* // In a JavaScript expression:
|
|
229
|
+
* condition: {
|
|
230
|
+
* type: 'javascript',
|
|
231
|
+
* expression: "fieldState.touched && fieldState.dirty"
|
|
232
|
+
* }
|
|
233
|
+
* ```
|
|
234
|
+
*/
|
|
235
|
+
fieldState?: FieldStateContext;
|
|
236
|
+
/**
|
|
237
|
+
* State of all fields in the form, keyed by field key.
|
|
238
|
+
*
|
|
239
|
+
* Provides access to any field's state properties by key.
|
|
240
|
+
* Uses a Proxy for lazy evaluation — field state is only read when accessed.
|
|
241
|
+
*
|
|
242
|
+
* @example
|
|
243
|
+
* ```typescript
|
|
244
|
+
* // In a JavaScript expression:
|
|
245
|
+
* condition: {
|
|
246
|
+
* type: 'javascript',
|
|
247
|
+
* expression: "formFieldState.email.dirty && formFieldState.email.valid"
|
|
248
|
+
* }
|
|
249
|
+
* ```
|
|
250
|
+
*/
|
|
251
|
+
formFieldState?: FormFieldStateMap;
|
|
252
|
+
/** Allow additional properties for flexible expression evaluation */
|
|
253
|
+
[key: string]: unknown;
|
|
223
254
|
}
|
|
224
255
|
|
|
225
256
|
/**
|
|
226
|
-
*
|
|
227
|
-
* Used for object arrays where each item is `{ key1: value1, key2: value2, ... }`.
|
|
228
|
-
*/
|
|
229
|
-
type ArrayItemTemplate = readonly ArrayAllowedChildren[];
|
|
230
|
-
/**
|
|
231
|
-
* An array item definition can be either:
|
|
232
|
-
* - A single field (ArrayAllowedChildren) for PRIMITIVE array items - extracts field value directly
|
|
233
|
-
* - An array of fields (ArrayItemTemplate) for OBJECT array items - merges fields into object
|
|
257
|
+
* Custom function signature for conditional expressions
|
|
234
258
|
*
|
|
235
|
-
*
|
|
236
|
-
* -
|
|
237
|
-
* -
|
|
238
|
-
* -
|
|
239
|
-
|
|
240
|
-
type ArrayItemDefinition = ArrayAllowedChildren | ArrayItemTemplate;
|
|
241
|
-
/**
|
|
242
|
-
* Array field interface for creating dynamic field collections that map to array values.
|
|
259
|
+
* Custom functions are used for conditional logic in:
|
|
260
|
+
* - `when` conditions (field visibility, conditional validation)
|
|
261
|
+
* - `readonly` logic (dynamic readonly state)
|
|
262
|
+
* - `disabled` logic (dynamic disabled state)
|
|
263
|
+
* - Dynamic value calculations
|
|
243
264
|
*
|
|
244
|
-
*
|
|
245
|
-
* -
|
|
246
|
-
* -
|
|
247
|
-
*
|
|
248
|
-
* - An array of FieldDefs (object item) - fields are merged into an object
|
|
249
|
-
* - Items can have different field configurations (heterogeneous arrays supported)
|
|
250
|
-
* - Empty `fields: []` means no initial items - user adds via buttons with explicit templates
|
|
251
|
-
* - Initial values are declared via `value` property on each field definition
|
|
265
|
+
* Unlike validators, custom functions:
|
|
266
|
+
* - Return any value (typically boolean for conditions, but could be strings, numbers, etc.)
|
|
267
|
+
* - Do NOT return ValidationError objects
|
|
268
|
+
* - Receive EvaluationContext (field values) not FieldContext (field state)
|
|
252
269
|
*
|
|
253
270
|
* @example
|
|
254
|
-
*
|
|
271
|
+
* ```typescript
|
|
272
|
+
* // Boolean condition function
|
|
273
|
+
* const isAdult: CustomFunction = (ctx) => {
|
|
274
|
+
* return ctx.age >= 18;
|
|
275
|
+
* };
|
|
276
|
+
*
|
|
277
|
+
* // Used in field configuration:
|
|
255
278
|
* {
|
|
256
|
-
* key: '
|
|
257
|
-
* type: '
|
|
258
|
-
*
|
|
259
|
-
* { key: 'tag', type: 'input', value: 'angular' }, // Single field = primitive
|
|
260
|
-
* { key: 'tag', type: 'input', value: 'typescript' },
|
|
261
|
-
* ]
|
|
279
|
+
* key: 'alcoholPreference',
|
|
280
|
+
* type: 'select',
|
|
281
|
+
* when: { function: 'isAdult' } // Only show if isAdult returns true
|
|
262
282
|
* }
|
|
283
|
+
* ```
|
|
263
284
|
*
|
|
264
285
|
* @example
|
|
265
|
-
*
|
|
266
|
-
*
|
|
267
|
-
*
|
|
268
|
-
*
|
|
269
|
-
*
|
|
270
|
-
*
|
|
271
|
-
*
|
|
272
|
-
*
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
286
|
+
* ```typescript
|
|
287
|
+
* // Calculation function
|
|
288
|
+
* const calculateDiscount: CustomFunction = (ctx) => {
|
|
289
|
+
* const price = ctx.price || 0;
|
|
290
|
+
* const isVip = ctx.isVip || false;
|
|
291
|
+
* return isVip ? price * 0.8 : price * 0.9;
|
|
292
|
+
* };
|
|
293
|
+
* ```
|
|
294
|
+
*/
|
|
295
|
+
type CustomFunction<TFormValue extends Record<string, unknown> = any> = (context: EvaluationContext<unknown, TFormValue>) => unknown;
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Async function for derivations — returns any value.
|
|
299
|
+
*
|
|
300
|
+
* The function receives the evaluation context and may return a Promise or Observable.
|
|
301
|
+
* Register functions in `customFnConfig.asyncDerivations`.
|
|
276
302
|
*
|
|
277
303
|
* @example
|
|
278
|
-
*
|
|
279
|
-
* {
|
|
280
|
-
*
|
|
281
|
-
*
|
|
282
|
-
*
|
|
283
|
-
*
|
|
284
|
-
*
|
|
285
|
-
*
|
|
286
|
-
*
|
|
304
|
+
* ```typescript
|
|
305
|
+
* const fetchPrice: AsyncDerivationFunction = async (context) => {
|
|
306
|
+
* const response = await fetch(`/api/price?product=${context.formValue.productId}`);
|
|
307
|
+
* const data = await response.json();
|
|
308
|
+
* return data.price;
|
|
309
|
+
* };
|
|
310
|
+
* ```
|
|
311
|
+
*
|
|
312
|
+
* @public
|
|
313
|
+
*/
|
|
314
|
+
type AsyncDerivationFunction<TFormValue extends Record<string, unknown> = any> = (context: EvaluationContext<unknown, TFormValue>) => Promise<unknown> | Observable<unknown>;
|
|
315
|
+
/**
|
|
316
|
+
* Async function for conditions — returns boolean.
|
|
317
|
+
*
|
|
318
|
+
* The function receives the evaluation context and may return a Promise or Observable.
|
|
319
|
+
* Register functions in `customFnConfig.asyncConditions`.
|
|
287
320
|
*
|
|
288
321
|
* @example
|
|
289
|
-
*
|
|
290
|
-
* {
|
|
291
|
-
*
|
|
292
|
-
*
|
|
293
|
-
*
|
|
294
|
-
* }
|
|
322
|
+
* ```typescript
|
|
323
|
+
* const checkPermission: AsyncConditionFunction = async (context) => {
|
|
324
|
+
* const response = await fetch(`/api/permissions?user=${context.formValue.userId}`);
|
|
325
|
+
* const data = await response.json();
|
|
326
|
+
* return data.canEdit;
|
|
327
|
+
* };
|
|
328
|
+
* ```
|
|
295
329
|
*
|
|
296
|
-
*
|
|
297
|
-
|
|
330
|
+
* @public
|
|
331
|
+
*/
|
|
332
|
+
type AsyncConditionFunction<TFormValue extends Record<string, unknown> = any> = (context: EvaluationContext<unknown, TFormValue>) => Promise<boolean> | Observable<boolean>;
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Configuration for an HTTP request used by declarative HTTP validators, derivations, and conditions.
|
|
298
336
|
*
|
|
299
|
-
*
|
|
337
|
+
* All string values in `queryParams` are treated as expressions evaluated by ExpressionParser.
|
|
338
|
+
* When `evaluateBodyExpressions` is true, top-level string values in `body` are also evaluated as expressions (shallow only).
|
|
300
339
|
*/
|
|
301
|
-
interface
|
|
302
|
-
|
|
340
|
+
interface HttpRequestConfig {
|
|
341
|
+
/** URL to send the request to */
|
|
342
|
+
url: string;
|
|
343
|
+
/** HTTP method. Defaults to 'GET' */
|
|
344
|
+
method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
303
345
|
/**
|
|
304
|
-
*
|
|
305
|
-
*
|
|
306
|
-
*
|
|
307
|
-
*
|
|
308
|
-
*
|
|
309
|
-
*
|
|
346
|
+
* URL path parameters. Keys correspond to `:key` placeholders in the URL.
|
|
347
|
+
* Values are expressions evaluated by ExpressionParser against the current EvaluationContext.
|
|
348
|
+
*
|
|
349
|
+
* @example
|
|
350
|
+
* ```typescript
|
|
351
|
+
* {
|
|
352
|
+
* url: '/api/users/:userId/orders/:orderId',
|
|
353
|
+
* params: { userId: 'formValue.userId', orderId: 'fieldValue' }
|
|
354
|
+
* }
|
|
355
|
+
* // With userId=42 and fieldValue='abc' → '/api/users/42/orders/abc'
|
|
356
|
+
* ```
|
|
310
357
|
*/
|
|
311
|
-
|
|
312
|
-
/** Array fields do not have a label property **/
|
|
313
|
-
readonly label?: never;
|
|
314
|
-
/** Arrays do not support meta - they have no native form element **/
|
|
315
|
-
readonly meta?: never;
|
|
358
|
+
params?: Record<string, string>;
|
|
316
359
|
/**
|
|
317
|
-
*
|
|
318
|
-
*
|
|
360
|
+
* Query parameters appended to the URL.
|
|
361
|
+
* Values are expressions evaluated by ExpressionParser against the current EvaluationContext.
|
|
319
362
|
*/
|
|
320
|
-
|
|
363
|
+
queryParams?: Record<string, string>;
|
|
364
|
+
/** Request body (for POST/PUT/PATCH) */
|
|
365
|
+
body?: Record<string, unknown>;
|
|
321
366
|
/**
|
|
322
|
-
*
|
|
323
|
-
*
|
|
324
|
-
*/
|
|
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.
|
|
367
|
+
* When true, top-level string values in `body` are treated as expressions
|
|
368
|
+
* and evaluated by ExpressionParser (shallow only — nested objects are passed through as-is).
|
|
329
369
|
*/
|
|
330
|
-
|
|
370
|
+
evaluateBodyExpressions?: boolean;
|
|
371
|
+
/** HTTP headers to include in the request */
|
|
372
|
+
headers?: Record<string, string>;
|
|
331
373
|
}
|
|
374
|
+
|
|
332
375
|
/**
|
|
333
|
-
*
|
|
334
|
-
*
|
|
335
|
-
*
|
|
376
|
+
* Comparison operators for field value and form value conditions.
|
|
377
|
+
*
|
|
378
|
+
* @public
|
|
336
379
|
*/
|
|
337
|
-
|
|
380
|
+
type ComparisonOperator = 'equals' | 'notEquals' | 'greater' | 'less' | 'greaterOrEqual' | 'lessOrEqual' | 'contains' | 'startsWith' | 'endsWith' | 'matches';
|
|
338
381
|
/**
|
|
339
|
-
*
|
|
382
|
+
* Condition that compares a specific field's value against an expected value.
|
|
383
|
+
*
|
|
384
|
+
* @public
|
|
340
385
|
*/
|
|
341
|
-
interface
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
386
|
+
interface FieldValueCondition {
|
|
387
|
+
type: 'fieldValue';
|
|
388
|
+
/** Path to the field whose value to compare */
|
|
389
|
+
fieldPath: string;
|
|
390
|
+
/** Comparison operator */
|
|
391
|
+
operator: ComparisonOperator;
|
|
392
|
+
/** Value to compare against */
|
|
393
|
+
value?: unknown;
|
|
346
394
|
}
|
|
347
395
|
/**
|
|
348
|
-
*
|
|
349
|
-
*
|
|
350
|
-
* Instead of manually specifying each item in `fields`, provide a `template` that defines
|
|
351
|
-
* the structure of a single item, and a `value` array with initial data.
|
|
352
|
-
* The library auto-generates add/remove buttons.
|
|
396
|
+
* Condition that invokes a custom function.
|
|
353
397
|
*
|
|
354
|
-
*
|
|
398
|
+
* Two mutually exclusive forms:
|
|
399
|
+
* - `functionName`: name of a function registered in `customFnConfig.customFunctions`.
|
|
400
|
+
* JSON-serializable; suitable for configs loaded from APIs, databases, or OpenAPI.
|
|
401
|
+
* - `fn`: inline function. NOT JSON-serializable; for code-only configs.
|
|
355
402
|
*
|
|
356
|
-
*
|
|
357
|
-
* // Primitive array: ['angular', 'typescript']
|
|
358
|
-
* {
|
|
359
|
-
* key: 'tags',
|
|
360
|
-
* type: 'array',
|
|
361
|
-
* template: { key: 'value', type: 'input', label: 'Tag', required: true },
|
|
362
|
-
* value: ['angular', 'typescript']
|
|
363
|
-
* }
|
|
403
|
+
* Exactly one of `functionName` or `fn` must be set.
|
|
364
404
|
*
|
|
365
|
-
*
|
|
366
|
-
*
|
|
367
|
-
*
|
|
368
|
-
*
|
|
369
|
-
* type: 'array',
|
|
370
|
-
* template: [
|
|
371
|
-
* { key: 'name', type: 'input', label: 'Contact Name', required: true },
|
|
372
|
-
* { key: 'phone', type: 'input', label: 'Phone Number' },
|
|
373
|
-
* ],
|
|
374
|
-
* value: [{ name: 'Jane', phone: '555' }]
|
|
375
|
-
* }
|
|
405
|
+
* Encoded as a strict discriminated union (XOR via `?: never`). `CustomValidatorConfig`
|
|
406
|
+
* intentionally uses a permissive interface for the same `fn`/`functionName` split
|
|
407
|
+
* because validators have a third source (`expression`) and the historical interface
|
|
408
|
+
* was already runtime-checked — the asymmetry is by design, not an oversight.
|
|
376
409
|
*
|
|
377
|
-
* @
|
|
378
|
-
* // Button customization / opt-out
|
|
379
|
-
* {
|
|
380
|
-
* key: 'tags',
|
|
381
|
-
* type: 'array',
|
|
382
|
-
* template: { key: 'value', type: 'input', label: 'Tag' },
|
|
383
|
-
* addButton: { label: 'Add Tag', props: { color: 'primary' } },
|
|
384
|
-
* removeButton: false
|
|
385
|
-
* }
|
|
410
|
+
* @public
|
|
386
411
|
*/
|
|
387
|
-
|
|
388
|
-
type: '
|
|
412
|
+
type CustomCondition = {
|
|
413
|
+
type: 'custom';
|
|
414
|
+
/** Name of the registered custom function to invoke */
|
|
415
|
+
functionName: string;
|
|
416
|
+
/** Inline form is forbidden when `functionName` is set */
|
|
417
|
+
fn?: never;
|
|
418
|
+
} | {
|
|
419
|
+
type: 'custom';
|
|
389
420
|
/**
|
|
390
|
-
*
|
|
391
|
-
*
|
|
392
|
-
* -
|
|
421
|
+
* Inline custom function. Mutually exclusive with `functionName`.
|
|
422
|
+
*
|
|
423
|
+
* NOT JSON-serializable — for code-only configs. For configs loaded
|
|
424
|
+
* from JSON / OpenAPI / databases, use `functionName` to reference
|
|
425
|
+
* a function registered in `customFnConfig.customFunctions`.
|
|
393
426
|
*/
|
|
394
|
-
|
|
395
|
-
/**
|
|
396
|
-
|
|
427
|
+
fn: CustomFunction;
|
|
428
|
+
/** Registered form is forbidden when `fn` is set */
|
|
429
|
+
functionName?: never;
|
|
430
|
+
};
|
|
431
|
+
/**
|
|
432
|
+
* Condition that evaluates a JavaScript expression using the secure AST-based parser.
|
|
433
|
+
*
|
|
434
|
+
* The expression has access to `formValue`, `fieldValue`, `externalData`, etc.
|
|
435
|
+
*
|
|
436
|
+
* @public
|
|
437
|
+
*/
|
|
438
|
+
interface JavascriptCondition {
|
|
439
|
+
type: 'javascript';
|
|
440
|
+
/** JavaScript expression string to evaluate */
|
|
441
|
+
expression: string;
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Condition that evaluates based on an HTTP response from a remote server.
|
|
445
|
+
*
|
|
446
|
+
* The HTTP request is resolved reactively — when dependent form values change,
|
|
447
|
+
* the request is re-evaluated (with debouncing). The result is cached per
|
|
448
|
+
* resolved request to avoid redundant network calls.
|
|
449
|
+
*
|
|
450
|
+
* Since `LogicFn` must return `boolean` synchronously, this condition uses
|
|
451
|
+
* a signal-based async resolution pattern internally.
|
|
452
|
+
*
|
|
453
|
+
* @public
|
|
454
|
+
*/
|
|
455
|
+
interface HttpCondition {
|
|
456
|
+
type: 'http';
|
|
457
|
+
/** HTTP request configuration */
|
|
458
|
+
http: HttpRequestConfig;
|
|
397
459
|
/**
|
|
398
|
-
*
|
|
399
|
-
*
|
|
460
|
+
* Expression to extract boolean from response (scope: `{ response }`).
|
|
461
|
+
* e.g. `'response.allowed'`, `'response.permissions.canEdit'`
|
|
462
|
+
* When omitted, the entire response is coerced to boolean: `!!response`
|
|
400
463
|
*/
|
|
401
|
-
|
|
464
|
+
responseExpression?: string;
|
|
402
465
|
/**
|
|
403
|
-
*
|
|
404
|
-
*
|
|
466
|
+
* Value to return while the HTTP request is in-flight.
|
|
467
|
+
*
|
|
468
|
+
* Choose based on the logic type and desired UX:
|
|
469
|
+
* - For `hidden`: `false` = visible while loading, `true` = hidden while loading
|
|
470
|
+
* - For `disabled`: `false` = enabled while loading, `true` = disabled while loading
|
|
471
|
+
* - For `required`: `false` = optional while loading, `true` = required while loading
|
|
472
|
+
*
|
|
473
|
+
* @default false
|
|
405
474
|
*/
|
|
406
|
-
|
|
407
|
-
/**
|
|
408
|
-
|
|
409
|
-
/** Simplified arrays do not support meta */
|
|
410
|
-
readonly meta?: never;
|
|
475
|
+
pendingValue?: boolean;
|
|
476
|
+
/** Cache duration in ms for HTTP responses. @default 30000 */
|
|
477
|
+
cacheDurationMs?: number;
|
|
411
478
|
/**
|
|
412
|
-
*
|
|
413
|
-
*
|
|
479
|
+
* Debounce time in milliseconds for re-evaluation when dependent form values change.
|
|
480
|
+
* @default 300
|
|
414
481
|
*/
|
|
415
|
-
|
|
482
|
+
debounceMs?: number;
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Shared fields for both branches of {@link AsyncCondition}.
|
|
486
|
+
*
|
|
487
|
+
* @internal
|
|
488
|
+
*/
|
|
489
|
+
interface AsyncConditionBase {
|
|
490
|
+
type: 'async';
|
|
416
491
|
/**
|
|
417
|
-
*
|
|
418
|
-
*
|
|
492
|
+
* Value to return while async resolution is pending.
|
|
493
|
+
*
|
|
494
|
+
* Choose based on the logic type and desired UX:
|
|
495
|
+
* - For `hidden`: `false` = visible while loading, `true` = hidden while loading
|
|
496
|
+
* - For `disabled`: `false` = enabled while loading, `true` = disabled while loading
|
|
497
|
+
* - For `required`: `false` = optional while loading, `true` = required while loading
|
|
498
|
+
*
|
|
499
|
+
* @default false
|
|
419
500
|
*/
|
|
420
|
-
|
|
501
|
+
pendingValue?: boolean;
|
|
421
502
|
/**
|
|
422
|
-
*
|
|
423
|
-
*
|
|
503
|
+
* Debounce ms for re-evaluation.
|
|
504
|
+
* @default 300
|
|
424
505
|
*/
|
|
425
|
-
|
|
426
|
-
/** Mutually exclusive with `template` — use `fields` for the full API instead */
|
|
427
|
-
readonly fields?: never;
|
|
506
|
+
debounceMs?: number;
|
|
428
507
|
}
|
|
429
508
|
/**
|
|
430
|
-
*
|
|
431
|
-
* Checks for `type: 'array'` with a `template` property (discriminant from full ArrayField).
|
|
432
|
-
*/
|
|
433
|
-
declare function isSimplifiedArrayField(field: FieldDef<any>): field is SimplifiedArrayField;
|
|
434
|
-
|
|
435
|
-
/**
|
|
436
|
-
* Group field interface for creating logical field groupings that map to object values
|
|
437
|
-
* Groups create nested form structures where child field values are collected into an object
|
|
438
|
-
* This is a programmatic grouping only - users cannot customize this field type
|
|
509
|
+
* Condition that resolves asynchronously via a custom function.
|
|
439
510
|
*
|
|
440
|
-
*
|
|
441
|
-
*
|
|
442
|
-
*
|
|
511
|
+
* Two mutually exclusive forms:
|
|
512
|
+
* - `asyncFunctionName`: name of a function registered in `customFnConfig.asyncConditions`.
|
|
513
|
+
* JSON-serializable; suitable for configs loaded from APIs, databases, or OpenAPI.
|
|
514
|
+
* - `asyncFn`: inline async function. NOT JSON-serializable; for code-only configs.
|
|
443
515
|
*
|
|
444
|
-
*
|
|
516
|
+
* Exactly one of `asyncFunctionName` or `asyncFn` must be set.
|
|
517
|
+
*
|
|
518
|
+
* The function is resolved reactively — when dependent form values change,
|
|
519
|
+
* the function is re-evaluated (with debouncing). The result is cached per
|
|
520
|
+
* evaluation to avoid redundant calls.
|
|
521
|
+
*
|
|
522
|
+
* Since `LogicFn` must return `boolean` synchronously, this condition uses
|
|
523
|
+
* a signal-based async resolution pattern internally.
|
|
524
|
+
*
|
|
525
|
+
* @public
|
|
445
526
|
*/
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
/**
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
readonly meta?: never;
|
|
527
|
+
type AsyncCondition = (AsyncConditionBase & {
|
|
528
|
+
/** Name of the registered async condition function */
|
|
529
|
+
asyncFunctionName: string;
|
|
530
|
+
/** Inline form is forbidden when `asyncFunctionName` is set */
|
|
531
|
+
asyncFn?: never;
|
|
532
|
+
}) | (AsyncConditionBase & {
|
|
453
533
|
/**
|
|
454
|
-
*
|
|
455
|
-
*
|
|
534
|
+
* Inline async condition function. Mutually exclusive with `asyncFunctionName`.
|
|
535
|
+
*
|
|
536
|
+
* NOT JSON-serializable — for code-only configs. For configs loaded
|
|
537
|
+
* from JSON / OpenAPI / databases, use `asyncFunctionName` to reference
|
|
538
|
+
* a function registered in `customFnConfig.asyncConditions`.
|
|
456
539
|
*/
|
|
457
|
-
|
|
540
|
+
asyncFn: AsyncConditionFunction;
|
|
541
|
+
/** Registered form is forbidden when `asyncFn` is set */
|
|
542
|
+
asyncFunctionName?: never;
|
|
543
|
+
});
|
|
544
|
+
/**
|
|
545
|
+
* Logical AND — all sub-conditions must be true.
|
|
546
|
+
*
|
|
547
|
+
* @public
|
|
548
|
+
*/
|
|
549
|
+
interface AndCondition {
|
|
550
|
+
type: 'and';
|
|
551
|
+
/** Sub-conditions that must all evaluate to true */
|
|
552
|
+
conditions: ConditionalExpression[];
|
|
458
553
|
}
|
|
459
554
|
/**
|
|
460
|
-
*
|
|
555
|
+
* Logical OR — at least one sub-condition must be true.
|
|
556
|
+
*
|
|
557
|
+
* @public
|
|
461
558
|
*/
|
|
462
|
-
|
|
463
|
-
|
|
559
|
+
interface OrCondition {
|
|
560
|
+
type: 'or';
|
|
561
|
+
/** Sub-conditions where at least one must evaluate to true */
|
|
562
|
+
conditions: ConditionalExpression[];
|
|
563
|
+
}
|
|
464
564
|
/**
|
|
465
|
-
*
|
|
466
|
-
*
|
|
565
|
+
* Discriminated union of all conditional expression types.
|
|
566
|
+
*
|
|
567
|
+
* Each variant only allows the properties relevant to its type,
|
|
568
|
+
* providing compile-time safety against invalid property combinations.
|
|
569
|
+
*
|
|
570
|
+
* @public
|
|
467
571
|
*/
|
|
468
|
-
type
|
|
572
|
+
type ConditionalExpression = FieldValueCondition | CustomCondition | JavascriptCondition | HttpCondition | AsyncCondition | AndCondition | OrCondition;
|
|
573
|
+
|
|
469
574
|
/**
|
|
470
|
-
*
|
|
471
|
-
*
|
|
472
|
-
*
|
|
575
|
+
* Logic configuration for container fields (page, group, row, array).
|
|
576
|
+
* Containers only support the 'hidden' logic type since they are layout containers,
|
|
577
|
+
* not form controls (they can't be readonly, disabled, or required).
|
|
473
578
|
*/
|
|
474
|
-
|
|
579
|
+
interface ContainerLogicConfig {
|
|
580
|
+
/** Only 'hidden' is supported for container fields */
|
|
581
|
+
type: 'hidden';
|
|
582
|
+
/**
|
|
583
|
+
* Condition that determines when this container is hidden.
|
|
584
|
+
* Can be a boolean or a conditional expression evaluated against form values.
|
|
585
|
+
*/
|
|
586
|
+
condition: ConditionalExpression | boolean;
|
|
587
|
+
}
|
|
588
|
+
|
|
475
589
|
/**
|
|
476
|
-
*
|
|
477
|
-
*
|
|
478
|
-
*
|
|
479
|
-
*
|
|
480
|
-
* - Storing IDs when updating existing records
|
|
481
|
-
* - Persisting metadata that shouldn't be user-editable
|
|
482
|
-
* - Tracking computed values that need to be submitted
|
|
483
|
-
*
|
|
484
|
-
* Unlike the `hidden` property on other fields (which hides a rendered field),
|
|
485
|
-
* this field type renders nothing at all - it's a componentless field.
|
|
590
|
+
* Row field interface for creating horizontal layouts.
|
|
591
|
+
* A row is a synthetic field type that resolves to a Container at runtime,
|
|
592
|
+
* with a synthesized `{ type: 'row' }` wrapper applied for layout. The row
|
|
593
|
+
* itself does not hold a value — its children flatten into the parent form.
|
|
486
594
|
*
|
|
487
|
-
*
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
595
|
+
* Note: Rows do not support `meta` since they have no native form element.
|
|
596
|
+
*/
|
|
597
|
+
interface RowField<TFields extends readonly RowAllowedChildren[] = readonly RowAllowedChildren[]> extends FieldDef<never> {
|
|
598
|
+
type: 'row';
|
|
599
|
+
/** Child definitions to render within this row */
|
|
600
|
+
readonly fields: TFields;
|
|
601
|
+
/** Row fields do not have a label property **/
|
|
602
|
+
readonly label?: never;
|
|
603
|
+
/** Rows do not support meta - they have no native form element **/
|
|
604
|
+
readonly meta?: never;
|
|
605
|
+
/**
|
|
606
|
+
* Logic configurations for conditional row visibility.
|
|
607
|
+
* Only 'hidden' type logic is supported for rows.
|
|
608
|
+
*/
|
|
609
|
+
readonly logic?: ContainerLogicConfig[];
|
|
610
|
+
}
|
|
611
|
+
/**
|
|
612
|
+
* Type guard for RowField with proper type narrowing
|
|
613
|
+
*/
|
|
614
|
+
declare function isRowField(field: FieldDef<any>): field is RowField;
|
|
615
|
+
|
|
616
|
+
/**
|
|
617
|
+
* Group field interface for creating logical field groupings that map to object values
|
|
618
|
+
* Groups create nested form structures where child field values are collected into an object
|
|
619
|
+
* This is a programmatic grouping only - users cannot customize this field type
|
|
495
620
|
*
|
|
496
|
-
*
|
|
497
|
-
*
|
|
498
|
-
*
|
|
499
|
-
* key: 'tagIds',
|
|
500
|
-
* value: [1, 2, 3],
|
|
501
|
-
* };
|
|
502
|
-
* ```
|
|
621
|
+
* TypeScript cannot enforce field nesting rules due to circular dependency limitations.
|
|
622
|
+
* For documentation: Groups should contain rows and leaf fields, but NOT pages or other groups.
|
|
623
|
+
* Runtime validation enforces these rules.
|
|
503
624
|
*
|
|
504
|
-
*
|
|
625
|
+
* Note: Groups are container fields and do not support `meta` since they have no native form element.
|
|
505
626
|
*/
|
|
506
|
-
interface
|
|
507
|
-
|
|
508
|
-
|
|
627
|
+
interface GroupField<TFields extends readonly GroupAllowedChildren[] = readonly GroupAllowedChildren[]> extends FieldDef<never> {
|
|
628
|
+
type: 'group';
|
|
629
|
+
readonly fields: TFields;
|
|
630
|
+
/** Groups do not have a label property - they are logical containers only **/
|
|
631
|
+
readonly label?: never;
|
|
632
|
+
/** Groups do not support meta - they have no native form element **/
|
|
633
|
+
readonly meta?: never;
|
|
509
634
|
/**
|
|
510
|
-
*
|
|
511
|
-
*
|
|
635
|
+
* Logic configurations for conditional group visibility.
|
|
636
|
+
* Only 'hidden' type logic is supported for groups.
|
|
512
637
|
*/
|
|
513
|
-
|
|
638
|
+
readonly logic?: ContainerLogicConfig[];
|
|
514
639
|
}
|
|
640
|
+
/**
|
|
641
|
+
* Type guard for GroupField with proper type narrowing
|
|
642
|
+
*/
|
|
643
|
+
declare function isGroupField(field: FieldDef<any>): field is GroupField;
|
|
515
644
|
|
|
516
645
|
/**
|
|
517
646
|
* Page field interface for creating top-level page layouts
|
|
@@ -722,1442 +851,1254 @@ type ExtractFields<T> = T extends {
|
|
|
722
851
|
type InferFormValue$1<T> = ExtractFields<T> extends readonly RegisteredFieldTypes[] ? InferFormValueWithDepth<ExtractFields<T> extends RegisteredFieldTypes[] ? ExtractFields<T> : [...ExtractFields<T>], 5> : never;
|
|
723
852
|
|
|
724
853
|
/**
|
|
725
|
-
*
|
|
726
|
-
*
|
|
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
|
|
854
|
+
* Custom validator function signature using Angular's public FieldContext API
|
|
770
855
|
*
|
|
771
|
-
*
|
|
772
|
-
*
|
|
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>.
|
|
856
|
+
* Takes FieldContext (full Angular context) and optional params, returns validation error(s) or null.
|
|
857
|
+
* Provides access to field state, form hierarchy, and other fields for validation logic.
|
|
802
858
|
*
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
params?: Record<string, unknown>;
|
|
811
|
-
}
|
|
812
|
-
/**
|
|
813
|
-
* Function-based HTTP validator configuration — requires a registered function.
|
|
859
|
+
* **Use FieldContext public APIs to access:**
|
|
860
|
+
* - Current field value: `ctx.value()`
|
|
861
|
+
* - Field state: `ctx.state` (errors, touched, dirty, etc.)
|
|
862
|
+
* - Other field values: `ctx.valueOf(path)` where path is a FieldPath
|
|
863
|
+
* - Other field states: `ctx.stateOf(path)`
|
|
864
|
+
* - Other fields: `ctx.fieldTreeOf(path)`
|
|
865
|
+
* - Current field tree: `ctx.fieldTree`
|
|
814
866
|
*
|
|
815
|
-
*
|
|
816
|
-
* `
|
|
867
|
+
* **Return Types:**
|
|
868
|
+
* - Single error: `{ kind: 'errorKind' }` for field-level validation
|
|
869
|
+
* - Multiple errors: `[{ kind: 'error1' }, { kind: 'error2' }]` for cross-field validation
|
|
870
|
+
* - No error: `null` when validation passes
|
|
817
871
|
*
|
|
818
|
-
*
|
|
872
|
+
* **Best Practice - Return Only Error Kind:**
|
|
873
|
+
* Validators should focus on validation logic, not presentation.
|
|
874
|
+
* Return just the error `kind` and configure messages at field level for i18n support.
|
|
819
875
|
*
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
}
|
|
829
|
-
/**
|
|
830
|
-
* Declarative HTTP validator configuration — fully JSON-serializable, no function registration required.
|
|
876
|
+
* @example Single Field Validation
|
|
877
|
+
* ```typescript
|
|
878
|
+
* const noSpaces: CustomValidator<string> = (ctx) => {
|
|
879
|
+
* const value = ctx.value();
|
|
880
|
+
* if (typeof value === 'string' && value.includes(' ')) {
|
|
881
|
+
* return { kind: 'noSpaces' };
|
|
882
|
+
* }
|
|
883
|
+
* return null;
|
|
884
|
+
* };
|
|
831
885
|
*
|
|
832
|
-
*
|
|
833
|
-
*
|
|
886
|
+
* // Field configuration
|
|
887
|
+
* {
|
|
888
|
+
* key: 'username',
|
|
889
|
+
* validators: [{ type: 'custom', functionName: 'noSpaces' }],
|
|
890
|
+
* validationMessages: {
|
|
891
|
+
* noSpaces: 'Spaces are not allowed'
|
|
892
|
+
* }
|
|
893
|
+
* }
|
|
894
|
+
* ```
|
|
834
895
|
*
|
|
835
|
-
*
|
|
836
|
-
*
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
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.
|
|
896
|
+
* @example Cross-Field Validation with valueOf()
|
|
897
|
+
* ```typescript
|
|
898
|
+
* // Compare two fields using public API
|
|
899
|
+
* const lessThan: CustomValidator<number> = (ctx, params) => {
|
|
900
|
+
* const value = ctx.value();
|
|
901
|
+
* const compareToPath = params?.field as string;
|
|
849
902
|
*
|
|
850
|
-
*
|
|
851
|
-
*
|
|
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.
|
|
903
|
+
* // Use valueOf() to access other field - public API!
|
|
904
|
+
* const otherValue = ctx.valueOf(compareToPath as any);
|
|
858
905
|
*
|
|
859
|
-
*
|
|
860
|
-
*
|
|
906
|
+
* if (otherValue !== undefined && value >= otherValue) {
|
|
907
|
+
* return { kind: 'notLessThan' };
|
|
908
|
+
* }
|
|
909
|
+
* return null;
|
|
910
|
+
* };
|
|
911
|
+
* ```
|
|
861
912
|
*
|
|
862
|
-
* @example
|
|
913
|
+
* @example Multiple Errors (Cross-Field Validation)
|
|
863
914
|
* ```typescript
|
|
864
|
-
*
|
|
865
|
-
*
|
|
866
|
-
*
|
|
867
|
-
*
|
|
868
|
-
*
|
|
869
|
-
*
|
|
870
|
-
*
|
|
871
|
-
*
|
|
872
|
-
*
|
|
873
|
-
* }
|
|
874
|
-
*
|
|
875
|
-
*
|
|
876
|
-
*
|
|
877
|
-
*
|
|
878
|
-
* label: 'Next',
|
|
879
|
-
* logic: [
|
|
880
|
-
* { type: 'disabled', condition: 'pageInvalid' },
|
|
881
|
-
* ]
|
|
882
|
-
* })
|
|
915
|
+
* const validateDateRange: CustomValidator = (ctx) => {
|
|
916
|
+
* const errors: ValidationError[] = [];
|
|
917
|
+
*
|
|
918
|
+
* const startDate = ctx.valueOf('startDate' as any);
|
|
919
|
+
* const endDate = ctx.valueOf('endDate' as any);
|
|
920
|
+
*
|
|
921
|
+
* if (!startDate) errors.push({ kind: 'startDateRequired' });
|
|
922
|
+
* if (!endDate) errors.push({ kind: 'endDateRequired' });
|
|
923
|
+
* if (startDate && endDate && startDate > endDate) {
|
|
924
|
+
* errors.push({ kind: 'invalidDateRange' });
|
|
925
|
+
* }
|
|
926
|
+
*
|
|
927
|
+
* return errors.length > 0 ? errors : null;
|
|
928
|
+
* };
|
|
883
929
|
* ```
|
|
884
930
|
*
|
|
885
|
-
*
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
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).
|
|
931
|
+
* **Message Resolution Priority:**
|
|
932
|
+
* 1. Field-level `validationMessages[kind]` (highest priority - per-field customization)
|
|
933
|
+
* 2. Form-level `defaultValidationMessages[kind]` (fallback for common messages)
|
|
934
|
+
* 3. **No message configured = Warning logged + error NOT displayed**
|
|
896
935
|
*
|
|
897
|
-
*
|
|
936
|
+
* **Important:** Validator-returned messages are NOT used. All messages MUST be explicitly configured.
|
|
937
|
+
*
|
|
938
|
+
* @template TValue The type of value stored in the field being validated
|
|
898
939
|
*/
|
|
899
|
-
type
|
|
940
|
+
type CustomValidator<TValue = unknown> = (ctx: FieldContext<TValue>, params?: Record<string, unknown>) => ValidationError$1 | ValidationError$1[] | null;
|
|
900
941
|
/**
|
|
901
|
-
*
|
|
942
|
+
* Async custom validator configuration using Angular's resource-based API
|
|
902
943
|
*
|
|
903
|
-
*
|
|
944
|
+
* Angular's validateAsync uses the resource API for async validation,
|
|
945
|
+
* which provides better integration with Signal Forms lifecycle management.
|
|
946
|
+
*
|
|
947
|
+
* **Structure:**
|
|
948
|
+
* - `params`: Function that computes params from field context
|
|
949
|
+
* - `factory`: Function that creates a ResourceRef from params signal
|
|
950
|
+
* - `onSuccess`: Maps successful resource result to validation errors
|
|
951
|
+
* - `onError`: Optional handler for resource errors (network failures, etc.)
|
|
952
|
+
*
|
|
953
|
+
* **Use Cases:**
|
|
954
|
+
* - Database lookups via services with resource API
|
|
955
|
+
* - Complex async business logic with Angular resources
|
|
956
|
+
* - Any async operation using Angular's resource pattern
|
|
957
|
+
*
|
|
958
|
+
* **Note:** For HTTP validation, prefer `HttpCustomValidator` which provides
|
|
959
|
+
* a simpler API specifically designed for HTTP requests.
|
|
960
|
+
*
|
|
961
|
+
* @example Database Lookup with Resource
|
|
962
|
+
* ```typescript
|
|
963
|
+
* const checkUsernameAvailable: AsyncCustomValidator<string> = {
|
|
964
|
+
* params: (ctx) => ({ username: ctx.value() }),
|
|
965
|
+
* factory: (params) => {
|
|
966
|
+
* const injector = inject(Injector);
|
|
967
|
+
* return resource({
|
|
968
|
+
* request: () => params(),
|
|
969
|
+
* loader: ({ request }) => {
|
|
970
|
+
* if (!request?.username) return null;
|
|
971
|
+
* const service = injector.get(UserService);
|
|
972
|
+
* return firstValueFrom(service.checkAvailability(request.username));
|
|
973
|
+
* }
|
|
974
|
+
* });
|
|
975
|
+
* },
|
|
976
|
+
* onSuccess: (result, ctx) => {
|
|
977
|
+
* if (!result) return null;
|
|
978
|
+
* return result.available ? null : { kind: 'usernameTaken' };
|
|
979
|
+
* },
|
|
980
|
+
* onError: (error, ctx) => {
|
|
981
|
+
* console.error('Availability check failed:', error);
|
|
982
|
+
* return null; // Don't block form on network errors
|
|
983
|
+
* }
|
|
984
|
+
* };
|
|
985
|
+
* ```
|
|
986
|
+
*
|
|
987
|
+
* @template TValue The type of value stored in the field being validated
|
|
988
|
+
* @template TParams The type of params passed to the resource
|
|
989
|
+
* @template TResult The type of result returned by the resource
|
|
904
990
|
*/
|
|
905
|
-
interface
|
|
991
|
+
interface AsyncCustomValidator<TValue = unknown, TParams = unknown, TResult = unknown> {
|
|
906
992
|
/**
|
|
907
|
-
*
|
|
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
|
|
993
|
+
* Function that receives field context and returns resource params.
|
|
994
|
+
* Params will be tracked as a signal and trigger resource reload when changed.
|
|
913
995
|
*/
|
|
914
|
-
|
|
996
|
+
readonly params: (ctx: FieldContext<TValue>, config?: Record<string, unknown>) => TParams;
|
|
915
997
|
/**
|
|
916
|
-
*
|
|
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
|
-
* ```
|
|
998
|
+
* Function that creates a ResourceRef from the params signal.
|
|
999
|
+
* The resource will be automatically managed by Angular's lifecycle.
|
|
939
1000
|
*/
|
|
940
|
-
|
|
941
|
-
}
|
|
942
|
-
/**
|
|
943
|
-
* State logic that evaluates immediately on change (default).
|
|
944
|
-
*
|
|
945
|
-
* @internal
|
|
946
|
-
*/
|
|
947
|
-
interface ImmediateStateLogicConfig extends BaseStateLogicConfig {
|
|
1001
|
+
readonly factory: (params: Signal<TParams | undefined>) => ResourceRef<TResult | undefined>;
|
|
948
1002
|
/**
|
|
949
|
-
*
|
|
950
|
-
*
|
|
1003
|
+
* Optional function to map successful resource result to validation errors.
|
|
1004
|
+
* Return null if validation passes, or ValidationError/ValidationError[] if validation fails.
|
|
951
1005
|
*/
|
|
952
|
-
|
|
953
|
-
/**
|
|
954
|
-
|
|
1006
|
+
readonly onSuccess?: (result: TResult, ctx: FieldContext<TValue>) => TreeValidationResult;
|
|
1007
|
+
/**
|
|
1008
|
+
* Optional error handler for resource errors (network failures, etc.).
|
|
1009
|
+
* Return null to ignore the error, or ValidationError to display it to the user.
|
|
1010
|
+
*/
|
|
1011
|
+
readonly onError?: (error: unknown, ctx: FieldContext<TValue>) => ValidationError$1 | ValidationError$1[] | null;
|
|
955
1012
|
}
|
|
956
1013
|
/**
|
|
957
|
-
*
|
|
958
|
-
*
|
|
959
|
-
* @internal
|
|
1014
|
+
* HTTP request configuration for validateHttp API
|
|
960
1015
|
*/
|
|
961
|
-
interface
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
trigger: 'debounced';
|
|
967
|
-
/**
|
|
968
|
-
* Debounce duration in milliseconds.
|
|
969
|
-
* @default 500
|
|
970
|
-
*/
|
|
971
|
-
debounceMs?: number;
|
|
1016
|
+
interface HttpResourceRequest {
|
|
1017
|
+
url: string;
|
|
1018
|
+
method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
1019
|
+
body?: unknown;
|
|
1020
|
+
headers?: Record<string, string | string[]>;
|
|
972
1021
|
}
|
|
973
1022
|
/**
|
|
974
|
-
*
|
|
1023
|
+
* HTTP-based validator configuration for Angular's validateHttp API
|
|
975
1024
|
*
|
|
976
|
-
*
|
|
977
|
-
*
|
|
978
|
-
* based on form state or field values.
|
|
1025
|
+
* Angular's validateHttp provides optimized HTTP validation with automatic
|
|
1026
|
+
* request cancellation and integration with the resource API.
|
|
979
1027
|
*
|
|
980
|
-
*
|
|
1028
|
+
* **Benefits:**
|
|
1029
|
+
* - Automatic request cancellation when field value changes
|
|
1030
|
+
* - Built-in integration with Angular's resource management
|
|
1031
|
+
* - Simpler than AsyncCustomValidator for HTTP use cases
|
|
1032
|
+
*
|
|
1033
|
+
* **Structure:**
|
|
1034
|
+
* - `request`: Function that returns URL string or HttpResourceRequest
|
|
1035
|
+
* - `onSuccess`: REQUIRED - Maps HTTP response to validation errors (inverted logic!)
|
|
1036
|
+
* - `onError`: Optional handler for HTTP errors (network failures, 4xx/5xx)
|
|
1037
|
+
*
|
|
1038
|
+
* **Important:** `onSuccess` uses inverted logic - it maps SUCCESSFUL HTTP responses
|
|
1039
|
+
* to validation errors. For example, if the API returns `{ available: false }`,
|
|
1040
|
+
* your `onSuccess` should return `{ kind: 'usernameTaken' }`.
|
|
1041
|
+
*
|
|
1042
|
+
* @example Username Availability Check (GET)
|
|
981
1043
|
* ```typescript
|
|
982
|
-
*
|
|
983
|
-
* {
|
|
984
|
-
*
|
|
985
|
-
*
|
|
986
|
-
*
|
|
987
|
-
*
|
|
988
|
-
*
|
|
989
|
-
*
|
|
1044
|
+
* const checkUsername: HttpCustomValidator<string> = {
|
|
1045
|
+
* request: (ctx) => {
|
|
1046
|
+
* const username = ctx.value();
|
|
1047
|
+
* if (!username) return undefined; // Skip validation if empty
|
|
1048
|
+
* return `/api/users/check-username?username=${encodeURIComponent(username)}`;
|
|
1049
|
+
* },
|
|
1050
|
+
* onSuccess: (response, ctx) => {
|
|
1051
|
+
* // Inverted logic: successful response may indicate validation failure
|
|
1052
|
+
* return response.available ? null : { kind: 'usernameTaken' };
|
|
1053
|
+
* },
|
|
1054
|
+
* onError: (error, ctx) => {
|
|
1055
|
+
* console.error('Availability check failed:', error);
|
|
1056
|
+
* return null; // Don't block form on network errors
|
|
990
1057
|
* }
|
|
991
|
-
* }
|
|
1058
|
+
* };
|
|
1059
|
+
* ```
|
|
992
1060
|
*
|
|
993
|
-
*
|
|
994
|
-
*
|
|
995
|
-
*
|
|
996
|
-
*
|
|
997
|
-
*
|
|
1061
|
+
* @example Address Validation (POST with Body)
|
|
1062
|
+
* ```typescript
|
|
1063
|
+
* const validateAddress: HttpCustomValidator = {
|
|
1064
|
+
* request: (ctx) => {
|
|
1065
|
+
* const zipCode = ctx.value();
|
|
1066
|
+
* if (!zipCode) return undefined;
|
|
998
1067
|
*
|
|
999
|
-
*
|
|
1000
|
-
*
|
|
1001
|
-
*
|
|
1002
|
-
*
|
|
1003
|
-
*
|
|
1004
|
-
*
|
|
1005
|
-
*
|
|
1006
|
-
*
|
|
1007
|
-
*
|
|
1068
|
+
* return {
|
|
1069
|
+
* url: '/api/validate-address',
|
|
1070
|
+
* method: 'POST',
|
|
1071
|
+
* body: {
|
|
1072
|
+
* street: ctx.valueOf('street' as any),
|
|
1073
|
+
* city: ctx.valueOf('city' as any),
|
|
1074
|
+
* zipCode: zipCode
|
|
1075
|
+
* },
|
|
1076
|
+
* headers: { 'Content-Type': 'application/json' }
|
|
1077
|
+
* };
|
|
1078
|
+
* },
|
|
1079
|
+
* onSuccess: (response) => {
|
|
1080
|
+
* return response.valid ? null : { kind: 'invalidAddress' };
|
|
1008
1081
|
* }
|
|
1009
|
-
* }
|
|
1082
|
+
* };
|
|
1010
1083
|
* ```
|
|
1011
1084
|
*
|
|
1012
|
-
* @
|
|
1085
|
+
* @template TValue The type of value stored in the field being validated
|
|
1086
|
+
* @template TResult The type of HTTP response
|
|
1013
1087
|
*/
|
|
1014
|
-
|
|
1015
|
-
/**
|
|
1016
|
-
* Shared fields that appear on all derivation logic config variants.
|
|
1017
|
-
*
|
|
1018
|
-
* @internal
|
|
1019
|
-
*/
|
|
1020
|
-
interface SharedDerivationFields {
|
|
1088
|
+
interface HttpCustomValidator<TValue = unknown, TResult = unknown> {
|
|
1021
1089
|
/**
|
|
1022
|
-
*
|
|
1090
|
+
* Function that returns the HTTP request configuration.
|
|
1091
|
+
* Can return:
|
|
1092
|
+
* - `undefined` to skip validation (e.g., if field is empty)
|
|
1093
|
+
* - `string` for simple GET requests (just the URL)
|
|
1094
|
+
* - `HttpResourceRequest` for full control (method, body, headers)
|
|
1023
1095
|
*/
|
|
1024
|
-
|
|
1096
|
+
readonly request: (ctx: FieldContext<TValue>, config?: Record<string, unknown>) => string | HttpResourceRequest | undefined;
|
|
1025
1097
|
/**
|
|
1026
|
-
*
|
|
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.
|
|
1098
|
+
* REQUIRED function to map successful HTTP response to validation errors.
|
|
1036
1099
|
*
|
|
1037
|
-
*
|
|
1038
|
-
*
|
|
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
|
-
* }
|
|
1100
|
+
* **Inverted Logic:** This is called on successful HTTP responses.
|
|
1101
|
+
* Return null if validation passes, or ValidationError/ValidationError[] if validation fails.
|
|
1049
1102
|
*
|
|
1050
|
-
*
|
|
1051
|
-
* {
|
|
1052
|
-
* key: 'total',
|
|
1053
|
-
* type: 'input',
|
|
1054
|
-
* logic: [{
|
|
1055
|
-
* type: 'derivation',
|
|
1056
|
-
* expression: 'formValue.quantity * formValue.unitPrice'
|
|
1057
|
-
* }]
|
|
1058
|
-
* }
|
|
1059
|
-
* ```
|
|
1103
|
+
* Example: API returns `{ available: false }` → return `{ kind: 'usernameTaken' }`
|
|
1060
1104
|
*/
|
|
1061
|
-
|
|
1105
|
+
readonly onSuccess: (result: TResult, ctx: FieldContext<TValue>) => TreeValidationResult;
|
|
1062
1106
|
/**
|
|
1063
|
-
* Optional
|
|
1064
|
-
*
|
|
1065
|
-
* When provided, this name appears in derivation debug logs,
|
|
1066
|
-
* making it easier to identify specific derivations in complex forms.
|
|
1107
|
+
* Optional error handler for HTTP errors (network failures, 4xx/5xx status codes).
|
|
1108
|
+
* Return null to ignore the error, or ValidationError to display it to the user.
|
|
1067
1109
|
*
|
|
1068
|
-
*
|
|
1069
|
-
*
|
|
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
|
-
* ```
|
|
1110
|
+
* Common pattern: Log the error and return null to avoid blocking form submission
|
|
1111
|
+
* on network issues.
|
|
1080
1112
|
*/
|
|
1081
|
-
|
|
1113
|
+
readonly onError?: (error: unknown, ctx: FieldContext<TValue>) => ValidationError$1 | ValidationError$1[] | null;
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
/**
|
|
1117
|
+
* Mapping configuration for interpreting HTTP responses as validation results.
|
|
1118
|
+
*
|
|
1119
|
+
* Expressions in `validWhen` and `errorParams` are evaluated with scope `{ response }` only —
|
|
1120
|
+
* they do NOT have access to `formValue`, `fieldValue`, or the full `EvaluationContext`.
|
|
1121
|
+
* This is intentional for v1: response mapping is purely about interpreting the HTTP response.
|
|
1122
|
+
*/
|
|
1123
|
+
interface HttpValidationResponseMapping {
|
|
1082
1124
|
/**
|
|
1083
|
-
*
|
|
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
|
|
1125
|
+
* Expression evaluated with scope `{ response }`. Must evaluate to `true` (strict boolean) to be valid.
|
|
1097
1126
|
*
|
|
1098
|
-
*
|
|
1099
|
-
*
|
|
1100
|
-
*
|
|
1101
|
-
* fieldPath: 'country',
|
|
1102
|
-
* operator: 'equals',
|
|
1103
|
-
* value: 'USA'
|
|
1104
|
-
* }
|
|
1105
|
-
* ```
|
|
1127
|
+
* NOTE: This is the OPPOSITE of the function-based HttpCustomValidator.onSuccess
|
|
1128
|
+
* convention, which maps successful HTTP responses to validation errors.
|
|
1129
|
+
* Here, `=== true` means "validation passed". Non-boolean results are treated as invalid and trigger a warning.
|
|
1106
1130
|
*/
|
|
1107
|
-
|
|
1131
|
+
validWhen: string;
|
|
1132
|
+
/** Error kind returned when validation fails — maps to field-level `validationMessages` */
|
|
1133
|
+
errorKind: string;
|
|
1108
1134
|
/**
|
|
1109
|
-
*
|
|
1110
|
-
*
|
|
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
|
-
* ```
|
|
1135
|
+
* Parameters to include in the validation error for message interpolation.
|
|
1136
|
+
* Map of parameter names to expressions evaluated against `{ response }`.
|
|
1131
1137
|
*/
|
|
1132
|
-
|
|
1138
|
+
errorParams?: Record<string, string>;
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
/**
|
|
1142
|
+
* Base configuration shared by all validators
|
|
1143
|
+
*/
|
|
1144
|
+
interface BaseValidatorConfig {
|
|
1145
|
+
/** Conditional logic for when validator applies */
|
|
1146
|
+
when?: ConditionalExpression;
|
|
1147
|
+
}
|
|
1148
|
+
/**
|
|
1149
|
+
* Built-in validator configuration (required, email, min, max, etc.)
|
|
1150
|
+
*/
|
|
1151
|
+
interface BuiltInValidatorConfig extends BaseValidatorConfig {
|
|
1152
|
+
/** Validator type identifier */
|
|
1153
|
+
type: 'required' | 'email' | 'min' | 'max' | 'minLength' | 'maxLength' | 'pattern';
|
|
1154
|
+
/** Static value for the validator (e.g., min value, pattern) */
|
|
1155
|
+
value?: number | string | RegExp;
|
|
1156
|
+
/** Dynamic value expression that evaluates to validator parameter */
|
|
1157
|
+
expression?: string;
|
|
1158
|
+
}
|
|
1159
|
+
/**
|
|
1160
|
+
* Custom validator configuration using Angular's public FieldContext API.
|
|
1161
|
+
* Returns ValidationError | ValidationError[] | null synchronously.
|
|
1162
|
+
*
|
|
1163
|
+
* Three authoring forms (mutually exclusive at runtime, not enforced by TypeScript):
|
|
1164
|
+
* 1. Registered function: `{ type: 'custom', functionName: 'myValidator' }`
|
|
1165
|
+
* 2. Inline function (code-only): `{ type: 'custom', fn: (ctx) => ... }`
|
|
1166
|
+
* 3. Expression-based: `{ type: 'custom', expression: 'fieldValue === formValue.password', kind: 'passwordMismatch' }`
|
|
1167
|
+
*
|
|
1168
|
+
* Unlike `AsyncValidatorConfig` and `FunctionHttpValidatorConfig` (strict `fn` ↔
|
|
1169
|
+
* `functionName` XOR via discriminated unions), this surface keeps a permissive
|
|
1170
|
+
* interface — the historical expression-vs-functionName split was already
|
|
1171
|
+
* runtime-checked, so adding `fn` follows the same precedent. The runtime
|
|
1172
|
+
* resolver picks one source and warns if `fn` and `functionName` are both set;
|
|
1173
|
+
* inline `fn` wins.
|
|
1174
|
+
*
|
|
1175
|
+
* `fn` is NOT JSON-serializable — for code-only configs. For configs loaded from
|
|
1176
|
+
* JSON / OpenAPI / databases, prefer `functionName` to reference a function
|
|
1177
|
+
* registered in `customFnConfig.validators`.
|
|
1178
|
+
*/
|
|
1179
|
+
interface CustomValidatorConfig extends BaseValidatorConfig {
|
|
1180
|
+
/** Validator type identifier */
|
|
1181
|
+
type: 'custom';
|
|
1182
|
+
/** Name of registered validator function (function-based pattern) */
|
|
1183
|
+
functionName?: string;
|
|
1133
1184
|
/**
|
|
1134
|
-
*
|
|
1135
|
-
*
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1185
|
+
* Inline custom validator (code-only authoring).
|
|
1186
|
+
* Mutually exclusive with `functionName`. NOT JSON-serializable.
|
|
1187
|
+
*/
|
|
1188
|
+
fn?: CustomValidator;
|
|
1189
|
+
/** Optional parameters to pass to validator function */
|
|
1190
|
+
params?: Record<string, unknown>;
|
|
1191
|
+
/** JavaScript expression to evaluate (expression-based pattern) */
|
|
1192
|
+
expression?: string;
|
|
1193
|
+
/** Error kind for expression-based validators - links to validationMessages */
|
|
1194
|
+
kind?: string;
|
|
1195
|
+
/**
|
|
1196
|
+
* Parameters to include in error object for message interpolation (expression-based pattern)
|
|
1197
|
+
* Map of parameter names to expressions that will be evaluated in the same context as the validation expression
|
|
1141
1198
|
*
|
|
1142
1199
|
* @example
|
|
1143
|
-
*
|
|
1144
|
-
*
|
|
1145
|
-
*
|
|
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
|
-
* }]
|
|
1200
|
+
* errorParams: {
|
|
1201
|
+
* minValue: 'formValue.minValue',
|
|
1202
|
+
* maxValue: 'formValue.maxValue'
|
|
1154
1203
|
* }
|
|
1155
|
-
*
|
|
1204
|
+
* // Allows message template: "Value must be between {{minValue}} and {{maxValue}}"
|
|
1156
1205
|
*/
|
|
1157
|
-
|
|
1206
|
+
errorParams?: Record<string, string>;
|
|
1158
1207
|
}
|
|
1159
1208
|
/**
|
|
1160
|
-
*
|
|
1209
|
+
* Shared fields for both branches of {@link AsyncValidatorConfig}.
|
|
1210
|
+
*
|
|
1161
1211
|
* @internal
|
|
1162
1212
|
*/
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
debounceMs?: number;
|
|
1170
|
-
};
|
|
1213
|
+
interface AsyncValidatorConfigShared extends BaseValidatorConfig {
|
|
1214
|
+
/** Validator type identifier. */
|
|
1215
|
+
type: 'async';
|
|
1216
|
+
/** Optional parameters to pass to validator function */
|
|
1217
|
+
params?: Record<string, unknown>;
|
|
1218
|
+
}
|
|
1171
1219
|
/**
|
|
1172
|
-
*
|
|
1173
|
-
*
|
|
1220
|
+
* Async custom validator configuration using Angular's `validateAsync` API.
|
|
1221
|
+
* Returns Observable<ValidationError | ValidationError[] | null>.
|
|
1174
1222
|
*
|
|
1175
|
-
*
|
|
1223
|
+
* Two mutually exclusive authoring forms:
|
|
1224
|
+
* - `functionName`: name of a function registered in `customFnConfig.asyncValidators`.
|
|
1225
|
+
* JSON-serializable; suitable for configs loaded from APIs, databases, or OpenAPI.
|
|
1226
|
+
* - `fn`: inline async validator. NOT JSON-serializable; for code-only configs.
|
|
1227
|
+
*
|
|
1228
|
+
* Exactly one of `functionName` or `fn` must be set.
|
|
1176
1229
|
*/
|
|
1177
|
-
|
|
1178
|
-
/**
|
|
1179
|
-
|
|
1230
|
+
type AsyncValidatorConfig = (AsyncValidatorConfigShared & {
|
|
1231
|
+
/** Name of registered async validator function */
|
|
1232
|
+
functionName: string;
|
|
1233
|
+
/** Inline form is forbidden when `functionName` is set */
|
|
1234
|
+
fn?: never;
|
|
1235
|
+
}) | (AsyncValidatorConfigShared & {
|
|
1180
1236
|
/**
|
|
1181
|
-
*
|
|
1182
|
-
*
|
|
1183
|
-
* The request is sent when dependencies change, with automatic
|
|
1184
|
-
* debouncing and cancellation of in-flight requests.
|
|
1237
|
+
* Inline async validator. Mutually exclusive with `functionName`.
|
|
1185
1238
|
*
|
|
1186
|
-
*
|
|
1239
|
+
* NOT JSON-serializable — for code-only configs. For configs loaded
|
|
1240
|
+
* from JSON / OpenAPI / databases, use `functionName` to reference
|
|
1241
|
+
* a validator registered in `customFnConfig.asyncValidators`.
|
|
1242
|
+
*/
|
|
1243
|
+
fn: AsyncCustomValidator;
|
|
1244
|
+
/** Registered form is forbidden when `fn` is set */
|
|
1245
|
+
functionName?: never;
|
|
1246
|
+
});
|
|
1247
|
+
/**
|
|
1248
|
+
* Shared fields for both branches of {@link FunctionHttpValidatorConfig}.
|
|
1249
|
+
*
|
|
1250
|
+
* @internal
|
|
1251
|
+
*/
|
|
1252
|
+
interface FunctionHttpValidatorConfigShared extends BaseValidatorConfig {
|
|
1253
|
+
/** Validator type identifier. */
|
|
1254
|
+
type: 'http';
|
|
1255
|
+
/** Optional parameters to pass to HTTP validator */
|
|
1256
|
+
params?: Record<string, unknown>;
|
|
1257
|
+
}
|
|
1258
|
+
/**
|
|
1259
|
+
* Function-based HTTP validator configuration. Uses Angular's `validateHttp` API.
|
|
1260
|
+
*
|
|
1261
|
+
* Two mutually exclusive authoring forms:
|
|
1262
|
+
* - `functionName`: name of an HTTP validator registered in `customFnConfig.httpValidators`.
|
|
1263
|
+
* JSON-serializable.
|
|
1264
|
+
* - `fn`: inline HTTP validator. NOT JSON-serializable; for code-only configs.
|
|
1265
|
+
*
|
|
1266
|
+
* Exactly one of `functionName` or `fn` must be set.
|
|
1267
|
+
*
|
|
1268
|
+
* Discriminated from `DeclarativeHttpValidatorConfig` by the presence of `functionName`
|
|
1269
|
+
* or `fn` (and absence of `http` + `responseMapping`).
|
|
1270
|
+
*/
|
|
1271
|
+
type FunctionHttpValidatorConfig = (FunctionHttpValidatorConfigShared & {
|
|
1272
|
+
/** Name of registered HTTP validator configuration */
|
|
1273
|
+
functionName: string;
|
|
1274
|
+
/** Inline form is forbidden when `functionName` is set */
|
|
1275
|
+
fn?: never;
|
|
1276
|
+
}) | (FunctionHttpValidatorConfigShared & {
|
|
1277
|
+
/**
|
|
1278
|
+
* Inline HTTP validator. Mutually exclusive with `functionName`.
|
|
1187
1279
|
*
|
|
1188
|
-
*
|
|
1189
|
-
*
|
|
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
|
-
* ```
|
|
1280
|
+
* NOT JSON-serializable — for code-only configs. For configs loaded
|
|
1281
|
+
* from JSON / OpenAPI / databases, use `functionName` to reference
|
|
1282
|
+
* an HTTP validator registered in `customFnConfig.httpValidators`.
|
|
1230
1283
|
*/
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
expression?: never;
|
|
1284
|
+
fn: HttpCustomValidator;
|
|
1285
|
+
/** Registered form is forbidden when `fn` is set */
|
|
1234
1286
|
functionName?: never;
|
|
1235
|
-
|
|
1236
|
-
}
|
|
1287
|
+
});
|
|
1237
1288
|
/**
|
|
1238
|
-
*
|
|
1239
|
-
* TypeScript enforces `dependsOn` at the type level.
|
|
1289
|
+
* Declarative HTTP validator configuration — fully JSON-serializable, no function registration required.
|
|
1240
1290
|
*
|
|
1241
|
-
*
|
|
1291
|
+
* Uses `HttpRequestConfig` to define the HTTP request and `HttpValidationResponseMapping`
|
|
1292
|
+
* to interpret the response as a validation result. Powered by Angular's `validateHttp` API.
|
|
1293
|
+
*
|
|
1294
|
+
* Discriminated from `FunctionHttpValidatorConfig` by the presence of `http` + `responseMapping`
|
|
1295
|
+
* (and absence of `functionName`).
|
|
1242
1296
|
*/
|
|
1243
|
-
interface
|
|
1244
|
-
/**
|
|
1245
|
-
|
|
1246
|
-
/**
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
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;
|
|
1297
|
+
interface DeclarativeHttpValidatorConfig extends BaseValidatorConfig {
|
|
1298
|
+
/** Validator type identifier */
|
|
1299
|
+
type: 'http';
|
|
1300
|
+
/** HTTP request configuration with expression-based query params and body */
|
|
1301
|
+
http: HttpRequestConfig;
|
|
1302
|
+
/** Mapping that interprets the HTTP response as a validation result */
|
|
1303
|
+
responseMapping: HttpValidationResponseMapping;
|
|
1282
1304
|
}
|
|
1283
1305
|
/**
|
|
1284
|
-
*
|
|
1306
|
+
* Configuration for signal forms validator functions that can be serialized from API.
|
|
1307
|
+
* Discriminated union type for type-safe validator configuration.
|
|
1308
|
+
*
|
|
1309
|
+
* Note: `FunctionHttpValidatorConfig` and `DeclarativeHttpValidatorConfig` both use `type: 'http'`.
|
|
1310
|
+
* They are discriminated by property presence: `functionName` → function-based, `http` → declarative.
|
|
1311
|
+
* Use `isFunctionHttpValidator()` for type-safe narrowing when `type` alone is insufficient.
|
|
1312
|
+
*/
|
|
1313
|
+
type ValidatorConfig = BuiltInValidatorConfig | CustomValidatorConfig | AsyncValidatorConfig | FunctionHttpValidatorConfig | DeclarativeHttpValidatorConfig;
|
|
1314
|
+
|
|
1315
|
+
/**
|
|
1316
|
+
* Special form-state conditions for button disabled logic.
|
|
1317
|
+
*
|
|
1318
|
+
* These conditions evaluate form or page-level state rather than field values,
|
|
1319
|
+
* and are primarily used for controlling button disabled states.
|
|
1320
|
+
*
|
|
1321
|
+
* @example
|
|
1322
|
+
* ```typescript
|
|
1323
|
+
* // Disable submit button when form is invalid or submitting
|
|
1324
|
+
* {
|
|
1325
|
+
* key: 'submit',
|
|
1326
|
+
* type: 'submit',
|
|
1327
|
+
* label: 'Submit',
|
|
1328
|
+
* logic: [
|
|
1329
|
+
* { type: 'disabled', condition: 'formInvalid' },
|
|
1330
|
+
* { type: 'disabled', condition: 'formSubmitting' },
|
|
1331
|
+
* ]
|
|
1332
|
+
* }
|
|
1333
|
+
*
|
|
1334
|
+
* // Disable next button when current page is invalid
|
|
1335
|
+
* nextButton({
|
|
1336
|
+
* key: 'next',
|
|
1337
|
+
* label: 'Next',
|
|
1338
|
+
* logic: [
|
|
1339
|
+
* { type: 'disabled', condition: 'pageInvalid' },
|
|
1340
|
+
* ]
|
|
1341
|
+
* })
|
|
1342
|
+
* ```
|
|
1343
|
+
*
|
|
1344
|
+
* @public
|
|
1345
|
+
*/
|
|
1346
|
+
type FormStateCondition =
|
|
1347
|
+
/** True when form.valid() === false */
|
|
1348
|
+
'formInvalid'
|
|
1349
|
+
/** True when form.submitting() === true */
|
|
1350
|
+
| 'formSubmitting'
|
|
1351
|
+
/** True when fields on the current page are invalid (for paged forms) */
|
|
1352
|
+
| 'pageInvalid';
|
|
1353
|
+
/**
|
|
1354
|
+
* Logic type for controlling field state (hidden, readonly, disabled, required).
|
|
1355
|
+
*
|
|
1356
|
+
* @public
|
|
1357
|
+
*/
|
|
1358
|
+
type StateLogicType = 'hidden' | 'readonly' | 'disabled' | 'required';
|
|
1359
|
+
/**
|
|
1360
|
+
* Base configuration for conditional field state logic.
|
|
1285
1361
|
*
|
|
1286
1362
|
* @internal
|
|
1287
1363
|
*/
|
|
1288
|
-
interface
|
|
1289
|
-
source?: never;
|
|
1364
|
+
interface BaseStateLogicConfig {
|
|
1290
1365
|
/**
|
|
1291
|
-
*
|
|
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.
|
|
1366
|
+
* Logic type identifier for field state.
|
|
1296
1367
|
*
|
|
1297
|
-
*
|
|
1298
|
-
*
|
|
1299
|
-
*
|
|
1300
|
-
*
|
|
1301
|
-
* expression: 'formValue.price * (1 - formValue.discount / 100)'
|
|
1302
|
-
* ```
|
|
1368
|
+
* - `hidden`: Hide the field from view (still participates in form state)
|
|
1369
|
+
* - `readonly`: Make the field read-only
|
|
1370
|
+
* - `disabled`: Disable user interaction
|
|
1371
|
+
* - `required`: Make the field required
|
|
1303
1372
|
*/
|
|
1304
|
-
|
|
1373
|
+
type: StateLogicType;
|
|
1305
1374
|
/**
|
|
1306
|
-
*
|
|
1375
|
+
* Condition that determines when this logic applies.
|
|
1307
1376
|
*
|
|
1308
|
-
*
|
|
1309
|
-
*
|
|
1377
|
+
* Can be:
|
|
1378
|
+
* - `boolean`: Static value (always applies or never applies)
|
|
1379
|
+
* - `ConditionalExpression`: Expression evaluated against field/form values
|
|
1380
|
+
* - `FormStateCondition`: Special form/page state check (for buttons)
|
|
1310
1381
|
*
|
|
1311
1382
|
* @example
|
|
1312
1383
|
* ```typescript
|
|
1313
|
-
* //
|
|
1314
|
-
*
|
|
1315
|
-
*
|
|
1316
|
-
*
|
|
1317
|
-
*
|
|
1318
|
-
*
|
|
1319
|
-
*
|
|
1320
|
-
*
|
|
1384
|
+
* // Static condition
|
|
1385
|
+
* condition: true
|
|
1386
|
+
*
|
|
1387
|
+
* // Field value condition
|
|
1388
|
+
* condition: {
|
|
1389
|
+
* type: 'fieldValue',
|
|
1390
|
+
* fieldPath: 'status',
|
|
1391
|
+
* operator: 'equals',
|
|
1392
|
+
* value: 'locked'
|
|
1321
1393
|
* }
|
|
1394
|
+
*
|
|
1395
|
+
* // Form state condition (for buttons)
|
|
1396
|
+
* condition: 'formSubmitting'
|
|
1322
1397
|
* ```
|
|
1323
1398
|
*/
|
|
1324
|
-
|
|
1325
|
-
value?: never;
|
|
1326
|
-
functionName?: never;
|
|
1327
|
-
http?: never;
|
|
1328
|
-
asyncFunctionName?: never;
|
|
1329
|
-
responseExpression?: never;
|
|
1399
|
+
condition: ConditionalExpression | boolean | FormStateCondition;
|
|
1330
1400
|
}
|
|
1331
1401
|
/**
|
|
1332
|
-
*
|
|
1402
|
+
* State logic that evaluates immediately on change (default).
|
|
1333
1403
|
*
|
|
1334
1404
|
* @internal
|
|
1335
1405
|
*/
|
|
1336
|
-
interface
|
|
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;
|
|
1406
|
+
interface ImmediateStateLogicConfig extends BaseStateLogicConfig {
|
|
1352
1407
|
/**
|
|
1353
|
-
*
|
|
1354
|
-
*
|
|
1355
|
-
* For `value` (static), no dependencies are needed.
|
|
1356
|
-
* Provide `dependsOn` to conditionally re-evaluate when specific fields change.
|
|
1408
|
+
* Trigger for immediate evaluation.
|
|
1409
|
+
* @default 'onChange'
|
|
1357
1410
|
*/
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
http?: never;
|
|
1362
|
-
asyncFunctionName?: never;
|
|
1363
|
-
responseExpression?: never;
|
|
1411
|
+
trigger?: 'onChange';
|
|
1412
|
+
/** Not allowed for onChange trigger */
|
|
1413
|
+
debounceMs?: never;
|
|
1364
1414
|
}
|
|
1365
1415
|
/**
|
|
1366
|
-
*
|
|
1416
|
+
* State logic that evaluates after a debounce period.
|
|
1367
1417
|
*
|
|
1368
1418
|
* @internal
|
|
1369
1419
|
*/
|
|
1370
|
-
interface
|
|
1371
|
-
source?: never;
|
|
1420
|
+
interface DebouncedStateLogicConfig extends BaseStateLogicConfig {
|
|
1372
1421
|
/**
|
|
1373
|
-
*
|
|
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
|
-
* ```
|
|
1422
|
+
* Trigger for debounced evaluation.
|
|
1423
|
+
* Evaluates after the value has stabilized for the debounce duration.
|
|
1384
1424
|
*/
|
|
1385
|
-
|
|
1425
|
+
trigger: 'debounced';
|
|
1386
1426
|
/**
|
|
1387
|
-
*
|
|
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
|
-
* ```
|
|
1427
|
+
* Debounce duration in milliseconds.
|
|
1428
|
+
* @default 500
|
|
1405
1429
|
*/
|
|
1406
|
-
|
|
1407
|
-
value?: never;
|
|
1408
|
-
expression?: never;
|
|
1409
|
-
http?: never;
|
|
1410
|
-
asyncFunctionName?: never;
|
|
1411
|
-
responseExpression?: never;
|
|
1430
|
+
debounceMs?: number;
|
|
1412
1431
|
}
|
|
1413
1432
|
/**
|
|
1414
|
-
*
|
|
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.
|
|
1433
|
+
* Configuration for conditional field state logic.
|
|
1465
1434
|
*
|
|
1466
|
-
*
|
|
1467
|
-
*
|
|
1468
|
-
*
|
|
1435
|
+
* Defines how field behavior changes based on conditions.
|
|
1436
|
+
* Supports hiding, disabling, making readonly, or requiring fields
|
|
1437
|
+
* based on form state or field values.
|
|
1469
1438
|
*
|
|
1470
1439
|
* @example
|
|
1471
1440
|
* ```typescript
|
|
1472
|
-
* //
|
|
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
|
-
* }
|
|
1486
|
-
*
|
|
1487
|
-
* // Compute total from quantity and price
|
|
1488
|
-
* {
|
|
1489
|
-
* key: 'total',
|
|
1490
|
-
* derivation: 'formValue.quantity * formValue.unitPrice'
|
|
1491
|
-
* }
|
|
1492
|
-
*
|
|
1493
|
-
* // Use custom function for complex logic
|
|
1441
|
+
* // Hide email field when contact method is not email
|
|
1494
1442
|
* {
|
|
1495
|
-
*
|
|
1496
|
-
*
|
|
1497
|
-
* type: '
|
|
1498
|
-
*
|
|
1499
|
-
*
|
|
1443
|
+
* type: 'hidden',
|
|
1444
|
+
* condition: {
|
|
1445
|
+
* type: 'fieldValue',
|
|
1446
|
+
* fieldPath: 'contactMethod',
|
|
1447
|
+
* operator: 'notEquals',
|
|
1448
|
+
* value: 'email'
|
|
1449
|
+
* }
|
|
1500
1450
|
* }
|
|
1501
1451
|
*
|
|
1502
|
-
* //
|
|
1503
|
-
* // (applies after user stops typing)
|
|
1452
|
+
* // Disable button when form is submitting
|
|
1504
1453
|
* {
|
|
1505
|
-
*
|
|
1506
|
-
*
|
|
1507
|
-
* type: 'derivation',
|
|
1508
|
-
* expression: 'formValue.email.toLowerCase()',
|
|
1509
|
-
* trigger: 'debounced',
|
|
1510
|
-
* debounceMs: 500
|
|
1511
|
-
* }]
|
|
1454
|
+
* type: 'disabled',
|
|
1455
|
+
* condition: 'formSubmitting'
|
|
1512
1456
|
* }
|
|
1513
1457
|
*
|
|
1514
|
-
* //
|
|
1458
|
+
* // Debounced visibility (avoids flicker during rapid typing)
|
|
1515
1459
|
* {
|
|
1516
|
-
*
|
|
1517
|
-
*
|
|
1460
|
+
* type: 'hidden',
|
|
1461
|
+
* trigger: 'debounced',
|
|
1462
|
+
* debounceMs: 300,
|
|
1463
|
+
* condition: {
|
|
1464
|
+
* type: 'fieldValue',
|
|
1465
|
+
* fieldPath: 'search',
|
|
1466
|
+
* operator: 'isEmpty'
|
|
1467
|
+
* }
|
|
1518
1468
|
* }
|
|
1519
1469
|
* ```
|
|
1520
1470
|
*
|
|
1521
1471
|
* @public
|
|
1522
1472
|
*/
|
|
1523
|
-
type
|
|
1524
|
-
/**
|
|
1525
|
-
* Union type for all logic configurations.
|
|
1526
|
-
*
|
|
1527
|
-
* - `StateLogicConfig`: For field state changes (hidden, readonly, disabled, required)
|
|
1528
|
-
* - `DerivationLogicConfig`: For value derivation (including property derivation via `targetProperty`)
|
|
1529
|
-
*
|
|
1530
|
-
* @public
|
|
1531
|
-
*/
|
|
1532
|
-
type LogicConfig = StateLogicConfig | DerivationLogicConfig;
|
|
1533
|
-
/**
|
|
1534
|
-
* Log level for derivation debug output.
|
|
1535
|
-
*
|
|
1536
|
-
* - 'none': No debug logging
|
|
1537
|
-
* - 'summary': Log cycle completion with counts (default in dev mode)
|
|
1538
|
-
* - 'verbose': Log individual derivation evaluations with details
|
|
1539
|
-
*
|
|
1540
|
-
* @public
|
|
1541
|
-
*/
|
|
1542
|
-
type DerivationLogLevel = 'none' | 'summary' | 'verbose';
|
|
1473
|
+
type StateLogicConfig = ImmediateStateLogicConfig | DebouncedStateLogicConfig;
|
|
1543
1474
|
/**
|
|
1544
|
-
*
|
|
1545
|
-
*
|
|
1546
|
-
* @param condition - The condition to check
|
|
1547
|
-
* @returns true if the condition is a FormStateCondition
|
|
1475
|
+
* Shared fields that appear on all derivation logic config variants.
|
|
1548
1476
|
*
|
|
1549
|
-
* @
|
|
1550
|
-
*/
|
|
1551
|
-
declare function isFormStateCondition(condition: StateLogicConfig['condition'] | DerivationLogicConfig['condition']): condition is FormStateCondition;
|
|
1552
|
-
|
|
1553
|
-
/**
|
|
1554
|
-
* Configuration for applying predefined schemas
|
|
1555
|
-
*/
|
|
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;
|
|
1565
|
-
}
|
|
1566
|
-
/**
|
|
1567
|
-
* Reusable schema definition that can be referenced by name
|
|
1477
|
+
* @internal
|
|
1568
1478
|
*/
|
|
1569
|
-
interface
|
|
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[];
|
|
1582
|
-
}
|
|
1583
|
-
|
|
1584
|
-
/**
|
|
1585
|
-
* Custom validator function signature using Angular's public FieldContext API
|
|
1586
|
-
*
|
|
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.
|
|
1589
|
-
*
|
|
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`
|
|
1597
|
-
*
|
|
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
|
|
1602
|
-
*
|
|
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.
|
|
1606
|
-
*
|
|
1607
|
-
* @example Single Field Validation
|
|
1608
|
-
* ```typescript
|
|
1609
|
-
* const noSpaces: CustomValidator<string> = (ctx) => {
|
|
1610
|
-
* const value = ctx.value();
|
|
1611
|
-
* if (typeof value === 'string' && value.includes(' ')) {
|
|
1612
|
-
* return { kind: 'noSpaces' };
|
|
1613
|
-
* }
|
|
1614
|
-
* return null;
|
|
1615
|
-
* };
|
|
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
|
-
* ```
|
|
1626
|
-
*
|
|
1627
|
-
* @example Cross-Field Validation with valueOf()
|
|
1628
|
-
* ```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);
|
|
1636
|
-
*
|
|
1637
|
-
* if (otherValue !== undefined && value >= otherValue) {
|
|
1638
|
-
* return { kind: 'notLessThan' };
|
|
1639
|
-
* }
|
|
1640
|
-
* return null;
|
|
1641
|
-
* };
|
|
1642
|
-
* ```
|
|
1643
|
-
*
|
|
1644
|
-
* @example Multiple Errors (Cross-Field Validation)
|
|
1645
|
-
* ```typescript
|
|
1646
|
-
* const validateDateRange: CustomValidator = (ctx) => {
|
|
1647
|
-
* const errors: ValidationError[] = [];
|
|
1648
|
-
*
|
|
1649
|
-
* const startDate = ctx.valueOf('startDate' as any);
|
|
1650
|
-
* const endDate = ctx.valueOf('endDate' as any);
|
|
1651
|
-
*
|
|
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
|
-
* }
|
|
1657
|
-
*
|
|
1658
|
-
* return errors.length > 0 ? errors : null;
|
|
1659
|
-
* };
|
|
1660
|
-
* ```
|
|
1661
|
-
*
|
|
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**
|
|
1666
|
-
*
|
|
1667
|
-
* **Important:** Validator-returned messages are NOT used. All messages MUST be explicitly configured.
|
|
1668
|
-
*
|
|
1669
|
-
* @template TValue The type of value stored in the field being validated
|
|
1670
|
-
*/
|
|
1671
|
-
type CustomValidator<TValue = unknown> = (ctx: FieldContext<TValue>, params?: Record<string, unknown>) => ValidationError$1 | ValidationError$1[] | null;
|
|
1672
|
-
/**
|
|
1673
|
-
* Async custom validator configuration using Angular's resource-based API
|
|
1674
|
-
*
|
|
1675
|
-
* Angular's validateAsync uses the resource API for async validation,
|
|
1676
|
-
* which provides better integration with Signal Forms lifecycle management.
|
|
1677
|
-
*
|
|
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.)
|
|
1683
|
-
*
|
|
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
|
|
1688
|
-
*
|
|
1689
|
-
* **Note:** For HTTP validation, prefer `HttpCustomValidator` which provides
|
|
1690
|
-
* a simpler API specifically designed for HTTP requests.
|
|
1691
|
-
*
|
|
1692
|
-
* @example Database Lookup with Resource
|
|
1693
|
-
* ```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
|
-
* };
|
|
1716
|
-
* ```
|
|
1717
|
-
*
|
|
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
|
|
1721
|
-
*/
|
|
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;
|
|
1479
|
+
interface SharedDerivationFields {
|
|
1728
1480
|
/**
|
|
1729
|
-
*
|
|
1730
|
-
* The resource will be automatically managed by Angular's lifecycle.
|
|
1481
|
+
* Logic type identifier for value derivation.
|
|
1731
1482
|
*/
|
|
1732
|
-
|
|
1483
|
+
type: 'derivation';
|
|
1733
1484
|
/**
|
|
1734
|
-
*
|
|
1735
|
-
*
|
|
1485
|
+
* Target property name for property derivation.
|
|
1486
|
+
*
|
|
1487
|
+
* When set, this derivation targets a field property (like `minDate`, `options`,
|
|
1488
|
+
* `label`, `placeholder`) instead of the field's value. The derivation is routed
|
|
1489
|
+
* to the property derivation pipeline.
|
|
1490
|
+
*
|
|
1491
|
+
* When absent, the derivation targets the field's value (default behavior).
|
|
1492
|
+
*
|
|
1493
|
+
* **Depth limit (max 2 levels):** Only simple and single-dot-nested paths are
|
|
1494
|
+
* supported. Paths with 2+ dots (e.g., `'a.b.c'`) will throw at runtime.
|
|
1495
|
+
*
|
|
1496
|
+
* @example
|
|
1497
|
+
* ```typescript
|
|
1498
|
+
* // Property derivation (new unified API)
|
|
1499
|
+
* {
|
|
1500
|
+
* key: 'endDate',
|
|
1501
|
+
* type: 'datepicker',
|
|
1502
|
+
* logic: [{
|
|
1503
|
+
* type: 'derivation',
|
|
1504
|
+
* targetProperty: 'minDate',
|
|
1505
|
+
* expression: 'formValue.startDate'
|
|
1506
|
+
* }]
|
|
1507
|
+
* }
|
|
1508
|
+
*
|
|
1509
|
+
* // Value derivation (no targetProperty — existing behavior)
|
|
1510
|
+
* {
|
|
1511
|
+
* key: 'total',
|
|
1512
|
+
* type: 'input',
|
|
1513
|
+
* logic: [{
|
|
1514
|
+
* type: 'derivation',
|
|
1515
|
+
* expression: 'formValue.quantity * formValue.unitPrice'
|
|
1516
|
+
* }]
|
|
1517
|
+
* }
|
|
1518
|
+
* ```
|
|
1736
1519
|
*/
|
|
1737
|
-
|
|
1520
|
+
targetProperty?: string;
|
|
1738
1521
|
/**
|
|
1739
|
-
* Optional
|
|
1740
|
-
*
|
|
1522
|
+
* Optional name for this derivation for debugging purposes.
|
|
1523
|
+
*
|
|
1524
|
+
* When provided, this name appears in derivation debug logs,
|
|
1525
|
+
* making it easier to identify specific derivations in complex forms.
|
|
1526
|
+
*
|
|
1527
|
+
* @example
|
|
1528
|
+
* ```typescript
|
|
1529
|
+
* {
|
|
1530
|
+
* key: 'lineTotal',
|
|
1531
|
+
* type: 'input',
|
|
1532
|
+
* logic: [{
|
|
1533
|
+
* type: 'derivation',
|
|
1534
|
+
* debugName: 'Calculate line total',
|
|
1535
|
+
* expression: 'formValue.quantity * formValue.unitPrice'
|
|
1536
|
+
* }]
|
|
1537
|
+
* }
|
|
1538
|
+
* ```
|
|
1741
1539
|
*/
|
|
1742
|
-
|
|
1743
|
-
}
|
|
1744
|
-
/**
|
|
1745
|
-
* HTTP request configuration for validateHttp API
|
|
1746
|
-
*/
|
|
1747
|
-
interface HttpResourceRequest {
|
|
1748
|
-
url: string;
|
|
1749
|
-
method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
1750
|
-
body?: unknown;
|
|
1751
|
-
headers?: Record<string, string | string[]>;
|
|
1752
|
-
}
|
|
1753
|
-
/**
|
|
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)
|
|
1768
|
-
*
|
|
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' }`.
|
|
1772
|
-
*
|
|
1773
|
-
* @example Username Availability Check (GET)
|
|
1774
|
-
* ```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
|
|
1788
|
-
* }
|
|
1789
|
-
* };
|
|
1790
|
-
* ```
|
|
1791
|
-
*
|
|
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;
|
|
1798
|
-
*
|
|
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' };
|
|
1812
|
-
* }
|
|
1813
|
-
* };
|
|
1814
|
-
* ```
|
|
1815
|
-
*
|
|
1816
|
-
* @template TValue The type of value stored in the field being validated
|
|
1817
|
-
* @template TResult The type of HTTP response
|
|
1818
|
-
*/
|
|
1819
|
-
interface HttpCustomValidator<TValue = unknown, TResult = unknown> {
|
|
1540
|
+
debugName?: string;
|
|
1820
1541
|
/**
|
|
1821
|
-
*
|
|
1822
|
-
*
|
|
1823
|
-
*
|
|
1824
|
-
* - `
|
|
1825
|
-
* - `
|
|
1542
|
+
* Condition that determines when this derivation applies.
|
|
1543
|
+
*
|
|
1544
|
+
* Can be:
|
|
1545
|
+
* - `boolean`: Static value (always applies or never applies)
|
|
1546
|
+
* - `ConditionalExpression`: Expression evaluated against field/form values
|
|
1547
|
+
*
|
|
1548
|
+
* Defaults to `true` (always apply).
|
|
1549
|
+
*
|
|
1550
|
+
* Note: FormStateCondition is not supported for derivations.
|
|
1551
|
+
*
|
|
1552
|
+
* @example
|
|
1553
|
+
* ```typescript
|
|
1554
|
+
* // Always compute (default)
|
|
1555
|
+
* condition: true
|
|
1556
|
+
*
|
|
1557
|
+
* // Conditional derivation
|
|
1558
|
+
* condition: {
|
|
1559
|
+
* type: 'fieldValue',
|
|
1560
|
+
* fieldPath: 'country',
|
|
1561
|
+
* operator: 'equals',
|
|
1562
|
+
* value: 'USA'
|
|
1563
|
+
* }
|
|
1564
|
+
* ```
|
|
1826
1565
|
*/
|
|
1827
|
-
|
|
1566
|
+
condition?: ConditionalExpression | boolean;
|
|
1828
1567
|
/**
|
|
1829
|
-
*
|
|
1568
|
+
* When true, the derivation stops running after the user manually
|
|
1569
|
+
* edits the target field.
|
|
1830
1570
|
*
|
|
1831
|
-
*
|
|
1832
|
-
*
|
|
1571
|
+
* This is useful for "smart defaults" — values that should be
|
|
1572
|
+
* auto-filled initially but respected once the user explicitly changes them.
|
|
1833
1573
|
*
|
|
1834
|
-
*
|
|
1574
|
+
* Uses the field's `dirty()` signal to detect user modification.
|
|
1575
|
+
* Derivations write directly to `value.set()` which does not trigger
|
|
1576
|
+
* `markAsDirty()`, so `dirty === true` reliably indicates a user edit.
|
|
1577
|
+
*
|
|
1578
|
+
* @example
|
|
1579
|
+
* ```typescript
|
|
1580
|
+
* // Auto-fill display name from first + last name, but stop if user edits it
|
|
1581
|
+
* {
|
|
1582
|
+
* key: 'displayName',
|
|
1583
|
+
* logic: [{
|
|
1584
|
+
* type: 'derivation',
|
|
1585
|
+
* expression: 'formValue.firstName + " " + formValue.lastName',
|
|
1586
|
+
* stopOnUserOverride: true
|
|
1587
|
+
* }]
|
|
1588
|
+
* }
|
|
1589
|
+
* ```
|
|
1835
1590
|
*/
|
|
1836
|
-
|
|
1591
|
+
stopOnUserOverride?: boolean;
|
|
1837
1592
|
/**
|
|
1838
|
-
*
|
|
1839
|
-
*
|
|
1593
|
+
* When true (and `stopOnUserOverride` is also true), clears the
|
|
1594
|
+
* user-override flag when any dependency of this derivation changes,
|
|
1595
|
+
* allowing the derivation to run again.
|
|
1840
1596
|
*
|
|
1841
|
-
*
|
|
1842
|
-
*
|
|
1597
|
+
* This is useful when a user override should only persist until the
|
|
1598
|
+
* underlying data changes — e.g., when switching countries, the
|
|
1599
|
+
* phone prefix should re-derive even if the user previously edited it.
|
|
1600
|
+
*
|
|
1601
|
+
* @example
|
|
1602
|
+
* ```typescript
|
|
1603
|
+
* {
|
|
1604
|
+
* key: 'phonePrefix',
|
|
1605
|
+
* logic: [{
|
|
1606
|
+
* type: 'derivation',
|
|
1607
|
+
* value: '+1',
|
|
1608
|
+
* condition: { type: 'fieldValue', fieldPath: 'country', operator: 'equals', value: 'USA' },
|
|
1609
|
+
* stopOnUserOverride: true,
|
|
1610
|
+
* reEngageOnDependencyChange: true,
|
|
1611
|
+
* dependsOn: ['country']
|
|
1612
|
+
* }]
|
|
1613
|
+
* }
|
|
1614
|
+
* ```
|
|
1843
1615
|
*/
|
|
1844
|
-
|
|
1616
|
+
reEngageOnDependencyChange?: boolean;
|
|
1845
1617
|
}
|
|
1846
|
-
|
|
1847
1618
|
/**
|
|
1848
|
-
*
|
|
1849
|
-
*
|
|
1619
|
+
* Trigger variants for derivation timing.
|
|
1620
|
+
* @internal
|
|
1850
1621
|
*/
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1622
|
+
type ImmediateDerivationTrigger = {
|
|
1623
|
+
trigger?: 'onChange';
|
|
1624
|
+
debounceMs?: never;
|
|
1625
|
+
};
|
|
1626
|
+
type DebouncedDerivationTrigger = {
|
|
1627
|
+
trigger: 'debounced';
|
|
1628
|
+
debounceMs?: number;
|
|
1629
|
+
};
|
|
1630
|
+
/**
|
|
1631
|
+
* Base for HTTP derivations. `source: 'http'` is required.
|
|
1632
|
+
* TypeScript enforces `dependsOn` and `responseExpression` at the type level.
|
|
1633
|
+
*
|
|
1634
|
+
* @internal
|
|
1635
|
+
*/
|
|
1636
|
+
interface HttpDerivationBase extends SharedDerivationFields {
|
|
1637
|
+
/** Identifies this derivation as HTTP-driven. */
|
|
1638
|
+
source: 'http';
|
|
1639
|
+
/** Forbidden on HTTP source */
|
|
1640
|
+
fn?: never;
|
|
1641
|
+
/** Forbidden on HTTP source */
|
|
1642
|
+
asyncFn?: never;
|
|
1857
1643
|
/**
|
|
1858
|
-
*
|
|
1859
|
-
*
|
|
1644
|
+
* HTTP request configuration for server-driven derivations.
|
|
1645
|
+
*
|
|
1646
|
+
* The request is sent when dependencies change, with automatic
|
|
1647
|
+
* debouncing and cancellation of in-flight requests.
|
|
1648
|
+
*
|
|
1649
|
+
* Configure debounce via the `trigger: 'debounced'` + `debounceMs` mechanism.
|
|
1650
|
+
*
|
|
1651
|
+
* @example
|
|
1652
|
+
* ```typescript
|
|
1653
|
+
* {
|
|
1654
|
+
* key: 'exchangeRate',
|
|
1655
|
+
* logic: [{
|
|
1656
|
+
* type: 'derivation',
|
|
1657
|
+
* source: 'http',
|
|
1658
|
+
* http: {
|
|
1659
|
+
* url: '/api/exchange-rate',
|
|
1660
|
+
* method: 'GET',
|
|
1661
|
+
* queryParams: {
|
|
1662
|
+
* from: 'formValue.sourceCurrency',
|
|
1663
|
+
* to: 'formValue.targetCurrency',
|
|
1664
|
+
* },
|
|
1665
|
+
* },
|
|
1666
|
+
* responseExpression: 'response.rate',
|
|
1667
|
+
* dependsOn: ['sourceCurrency', 'targetCurrency'],
|
|
1668
|
+
* }]
|
|
1669
|
+
* }
|
|
1670
|
+
* ```
|
|
1860
1671
|
*/
|
|
1861
|
-
|
|
1672
|
+
http: HttpRequestConfig;
|
|
1862
1673
|
/**
|
|
1863
|
-
*
|
|
1864
|
-
*
|
|
1674
|
+
* Explicit field dependencies. Required for HTTP derivations to prevent
|
|
1675
|
+
* wildcard triggering on every keystroke.
|
|
1676
|
+
*
|
|
1677
|
+
* The wildcard token `'*'` is rejected. The structural token `'$group'`
|
|
1678
|
+
* (and `'$group.X'`) is allowed and resolves to the field's parent container
|
|
1679
|
+
* path — be aware this fires whenever any sibling under that group changes,
|
|
1680
|
+
* so the caller owns the request frequency (consider `trigger: 'debounced'`).
|
|
1865
1681
|
*/
|
|
1866
|
-
|
|
1682
|
+
dependsOn: string[];
|
|
1867
1683
|
/**
|
|
1868
|
-
*
|
|
1869
|
-
*
|
|
1684
|
+
* Expression to extract the derived value from the HTTP response.
|
|
1685
|
+
*
|
|
1686
|
+
* Evaluated via `ExpressionParser` with `{ response }` as the evaluation scope.
|
|
1687
|
+
*
|
|
1688
|
+
* @example
|
|
1689
|
+
* ```typescript
|
|
1690
|
+
* responseExpression: 'response.rate'
|
|
1691
|
+
* responseExpression: 'response.data.suggestedPrice'
|
|
1692
|
+
* ```
|
|
1870
1693
|
*/
|
|
1871
|
-
|
|
1694
|
+
responseExpression: string;
|
|
1695
|
+
value?: never;
|
|
1696
|
+
expression?: never;
|
|
1697
|
+
functionName?: never;
|
|
1698
|
+
asyncFunctionName?: never;
|
|
1872
1699
|
}
|
|
1873
|
-
|
|
1874
1700
|
/**
|
|
1875
|
-
* Shared
|
|
1876
|
-
*
|
|
1877
|
-
* Used by both the derivation-warning tracker (keys are field paths) and the
|
|
1878
|
-
* deprecation-warning tracker (keys are deprecation IDs). The property is
|
|
1879
|
-
* generic so the same shape works for any string-keyed warning domain.
|
|
1701
|
+
* Shared fields for both branches of {@link AsyncFunctionDerivationBase}.
|
|
1880
1702
|
*
|
|
1881
1703
|
* @internal
|
|
1882
1704
|
*/
|
|
1883
|
-
interface
|
|
1884
|
-
|
|
1705
|
+
interface AsyncFunctionDerivationShared extends SharedDerivationFields {
|
|
1706
|
+
/** Identifies this derivation as async-function-driven. */
|
|
1707
|
+
source: 'asyncFunction';
|
|
1708
|
+
/** Forbidden on asyncFunction source */
|
|
1709
|
+
fn?: never;
|
|
1710
|
+
/**
|
|
1711
|
+
* Explicit field dependencies. Required for async derivations to prevent
|
|
1712
|
+
* wildcard triggering on every form change.
|
|
1713
|
+
*
|
|
1714
|
+
* The wildcard token `'*'` is rejected. The structural token `'$group'`
|
|
1715
|
+
* (and `'$group.X'`) is allowed and resolves to the field's parent container
|
|
1716
|
+
* path — be aware this fires whenever any sibling under that group changes,
|
|
1717
|
+
* so the caller owns the invocation frequency (handle this via
|
|
1718
|
+
* `trigger: 'debounced'` or via debouncing inside the async function).
|
|
1719
|
+
*/
|
|
1720
|
+
dependsOn: string[];
|
|
1721
|
+
value?: never;
|
|
1722
|
+
expression?: never;
|
|
1723
|
+
functionName?: never;
|
|
1724
|
+
http?: never;
|
|
1725
|
+
responseExpression?: never;
|
|
1885
1726
|
}
|
|
1886
|
-
|
|
1887
1727
|
/**
|
|
1888
|
-
*
|
|
1889
|
-
*
|
|
1890
|
-
*
|
|
1891
|
-
* to reference field state (e.g., `fieldState.touched`, `formFieldState.email.dirty`).
|
|
1728
|
+
* Base for async function derivations. `source: 'asyncFunction'` is required.
|
|
1729
|
+
* TypeScript enforces `dependsOn` at the type level, and `asyncFunctionName` /
|
|
1730
|
+
* `asyncFn` are mutually exclusive (XOR).
|
|
1892
1731
|
*
|
|
1893
|
-
* @
|
|
1732
|
+
* @internal
|
|
1894
1733
|
*/
|
|
1895
|
-
|
|
1896
|
-
readonly touched: boolean;
|
|
1897
|
-
readonly dirty: boolean;
|
|
1898
|
-
/** Convenience property: equivalent to `!dirty` */
|
|
1899
|
-
readonly pristine: boolean;
|
|
1900
|
-
readonly valid: boolean;
|
|
1901
|
-
readonly invalid: boolean;
|
|
1902
|
-
readonly pending: boolean;
|
|
1734
|
+
type AsyncFunctionDerivationBase = (AsyncFunctionDerivationShared & {
|
|
1903
1735
|
/**
|
|
1904
|
-
*
|
|
1736
|
+
* Name of a registered async derivation function.
|
|
1905
1737
|
*
|
|
1906
|
-
*
|
|
1907
|
-
*
|
|
1908
|
-
*/
|
|
1909
|
-
readonly hidden: boolean;
|
|
1910
|
-
/**
|
|
1911
|
-
* Whether the field is currently readonly.
|
|
1738
|
+
* The function receives the evaluation context and returns a Promise or Observable
|
|
1739
|
+
* of the derived value. Register functions in `customFnConfig.asyncDerivations`.
|
|
1912
1740
|
*
|
|
1913
|
-
*
|
|
1914
|
-
*
|
|
1741
|
+
* @example
|
|
1742
|
+
* ```typescript
|
|
1743
|
+
* {
|
|
1744
|
+
* key: 'suggestedPrice',
|
|
1745
|
+
* logic: [{
|
|
1746
|
+
* type: 'derivation',
|
|
1747
|
+
* source: 'asyncFunction',
|
|
1748
|
+
* asyncFunctionName: 'fetchSuggestedPrice',
|
|
1749
|
+
* dependsOn: ['productId', 'quantity'],
|
|
1750
|
+
* }]
|
|
1751
|
+
* }
|
|
1752
|
+
* ```
|
|
1915
1753
|
*/
|
|
1916
|
-
|
|
1754
|
+
asyncFunctionName: string;
|
|
1755
|
+
/** Inline form is forbidden when `asyncFunctionName` is set */
|
|
1756
|
+
asyncFn?: never;
|
|
1757
|
+
}) | (AsyncFunctionDerivationShared & {
|
|
1917
1758
|
/**
|
|
1918
|
-
*
|
|
1759
|
+
* Inline async derivation function. Mutually exclusive with `asyncFunctionName`.
|
|
1919
1760
|
*
|
|
1920
|
-
*
|
|
1921
|
-
*
|
|
1761
|
+
* NOT JSON-serializable — for code-only configs. For configs loaded
|
|
1762
|
+
* from JSON / OpenAPI / databases, use `asyncFunctionName` to reference
|
|
1763
|
+
* a function registered in `customFnConfig.asyncDerivations`.
|
|
1922
1764
|
*/
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
*
|
|
1928
|
-
* Used as `formFieldState` in evaluation contexts to access
|
|
1929
|
-
* state of any field in the form by key.
|
|
1930
|
-
*
|
|
1931
|
-
* @public
|
|
1932
|
-
*/
|
|
1933
|
-
type FormFieldStateMap = Record<string, FieldStateInfo | undefined>;
|
|
1765
|
+
asyncFn: AsyncDerivationFunction;
|
|
1766
|
+
/** Registered form is forbidden when `asyncFn` is set */
|
|
1767
|
+
asyncFunctionName?: never;
|
|
1768
|
+
});
|
|
1934
1769
|
/**
|
|
1935
|
-
*
|
|
1936
|
-
*
|
|
1937
|
-
* Used as `fieldState` in evaluation contexts.
|
|
1770
|
+
* Base for expression derivations.
|
|
1938
1771
|
*
|
|
1939
|
-
* @
|
|
1772
|
+
* @internal
|
|
1940
1773
|
*/
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
* Form value for the current evaluation scope.
|
|
1948
|
-
*
|
|
1949
|
-
* For regular (non-array) derivations, this contains the complete form value.
|
|
1950
|
-
* For array item derivations, this is scoped to the current array item.
|
|
1951
|
-
* Use `rootFormValue` to access the complete form when inside an array context.
|
|
1952
|
-
*/
|
|
1953
|
-
formValue: TFormValue;
|
|
1954
|
-
/** Field path for relative references */
|
|
1955
|
-
fieldPath: string;
|
|
1956
|
-
/** Custom evaluation functions */
|
|
1957
|
-
customFunctions?: Record<string, (context: EvaluationContext) => unknown>;
|
|
1958
|
-
/** Logger for diagnostic output */
|
|
1959
|
-
logger: Logger;
|
|
1774
|
+
interface ExpressionDerivationBase extends SharedDerivationFields {
|
|
1775
|
+
source?: never;
|
|
1776
|
+
/** Forbidden on expression mode */
|
|
1777
|
+
fn?: never;
|
|
1778
|
+
/** Forbidden on expression mode */
|
|
1779
|
+
asyncFn?: never;
|
|
1960
1780
|
/**
|
|
1961
|
-
*
|
|
1962
|
-
* field has no inner group above the array boundary).
|
|
1963
|
-
*
|
|
1964
|
-
* Complements `formValue` and `fieldValue` for derivations on fields nested
|
|
1965
|
-
* inside groups built by factory helpers, where the parent group's key
|
|
1966
|
-
* isn't known at config-authoring time. Mirrors the runtime semantics of
|
|
1967
|
-
* the `'$group'` token used in `dependsOn`.
|
|
1781
|
+
* JavaScript expression to evaluate for the derived value.
|
|
1968
1782
|
*
|
|
1969
|
-
*
|
|
1970
|
-
*
|
|
1971
|
-
*
|
|
1972
|
-
* - Inside nested groups: the innermost parent group's value object.
|
|
1973
|
-
* - Directly inside an array item (no inner group): the array item itself
|
|
1974
|
-
* (same shape as `formValue` in array context).
|
|
1975
|
-
* - Inside a group inside an array item: the inner group's value object
|
|
1976
|
-
* within the current item.
|
|
1977
|
-
* - Field at form root with no parent container: `undefined`.
|
|
1783
|
+
* Has access to `formValue` object containing all form values.
|
|
1784
|
+
* For array fields, `formValue` is scoped to the current array item.
|
|
1785
|
+
* Uses the same secure AST-based parser as other expressions.
|
|
1978
1786
|
*
|
|
1979
1787
|
* @example
|
|
1980
1788
|
* ```typescript
|
|
1981
|
-
*
|
|
1982
|
-
*
|
|
1983
|
-
*
|
|
1984
|
-
* return country === 'usa' ? 'NY' : '';
|
|
1985
|
-
* }
|
|
1789
|
+
* expression: 'formValue.quantity * formValue.unitPrice'
|
|
1790
|
+
* expression: 'formValue.firstName + " " + formValue.lastName'
|
|
1791
|
+
* expression: 'formValue.price * (1 - formValue.discount / 100)'
|
|
1986
1792
|
* ```
|
|
1987
1793
|
*/
|
|
1988
|
-
|
|
1794
|
+
expression: string;
|
|
1989
1795
|
/**
|
|
1990
|
-
*
|
|
1796
|
+
* Explicit field dependencies for expressions.
|
|
1991
1797
|
*
|
|
1992
|
-
*
|
|
1993
|
-
*
|
|
1994
|
-
* `formValue` is scoped to the current array item, while `rootFormValue`
|
|
1995
|
-
* provides access to the entire form value including fields outside the array.
|
|
1798
|
+
* For `expression`, dependencies are automatically extracted from the expression.
|
|
1799
|
+
* Provide `dependsOn` to override automatic detection for complex expressions.
|
|
1996
1800
|
*
|
|
1997
1801
|
* @example
|
|
1998
1802
|
* ```typescript
|
|
1999
|
-
* //
|
|
1803
|
+
* // Override automatic detection for complex expressions
|
|
2000
1804
|
* {
|
|
2001
|
-
* key: '
|
|
2002
|
-
*
|
|
2003
|
-
*
|
|
2004
|
-
*
|
|
2005
|
-
*
|
|
1805
|
+
* key: 'total',
|
|
1806
|
+
* logic: [{
|
|
1807
|
+
* type: 'derivation',
|
|
1808
|
+
* expression: 'calculateTotal(formValue)',
|
|
1809
|
+
* dependsOn: ['quantity', 'unitPrice', 'discount']
|
|
1810
|
+
* }]
|
|
2006
1811
|
* }
|
|
2007
1812
|
* ```
|
|
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
1813
|
*/
|
|
2020
|
-
|
|
1814
|
+
dependsOn?: string[];
|
|
1815
|
+
value?: never;
|
|
1816
|
+
functionName?: never;
|
|
1817
|
+
http?: never;
|
|
1818
|
+
asyncFunctionName?: never;
|
|
1819
|
+
responseExpression?: never;
|
|
1820
|
+
}
|
|
1821
|
+
/**
|
|
1822
|
+
* Base for value derivations.
|
|
1823
|
+
*
|
|
1824
|
+
* @internal
|
|
1825
|
+
*/
|
|
1826
|
+
interface ValueDerivationBase extends SharedDerivationFields {
|
|
1827
|
+
source?: never;
|
|
1828
|
+
/** Forbidden on value mode */
|
|
1829
|
+
fn?: never;
|
|
1830
|
+
/** Forbidden on value mode */
|
|
1831
|
+
asyncFn?: never;
|
|
2021
1832
|
/**
|
|
2022
|
-
*
|
|
1833
|
+
* Static value to set on this field.
|
|
2023
1834
|
*
|
|
2024
|
-
*
|
|
2025
|
-
* in conditional logic, derivations, and other expressions.
|
|
1835
|
+
* Use when the derived value is a constant.
|
|
2026
1836
|
*
|
|
2027
1837
|
* @example
|
|
2028
1838
|
* ```typescript
|
|
2029
|
-
* //
|
|
2030
|
-
*
|
|
2031
|
-
*
|
|
2032
|
-
*
|
|
2033
|
-
* }
|
|
2034
|
-
*
|
|
2035
|
-
* // In JavaScript expression:
|
|
2036
|
-
* condition: {
|
|
2037
|
-
* type: 'javascript',
|
|
2038
|
-
* expression: "externalData.userRole === 'admin'"
|
|
2039
|
-
* }
|
|
1839
|
+
* value: '+1' // String
|
|
1840
|
+
* value: 100 // Number
|
|
1841
|
+
* value: true // Boolean
|
|
1842
|
+
* value: { code: 'US' } // Object
|
|
2040
1843
|
* ```
|
|
2041
1844
|
*/
|
|
2042
|
-
|
|
1845
|
+
value: unknown;
|
|
2043
1846
|
/**
|
|
2044
|
-
*
|
|
2045
|
-
*
|
|
1847
|
+
* Explicit field dependencies for value derivations.
|
|
1848
|
+
*
|
|
1849
|
+
* For `value` (static), no dependencies are needed.
|
|
1850
|
+
* Provide `dependsOn` to conditionally re-evaluate when specific fields change.
|
|
2046
1851
|
*/
|
|
2047
|
-
|
|
1852
|
+
dependsOn?: string[];
|
|
1853
|
+
expression?: never;
|
|
1854
|
+
functionName?: never;
|
|
1855
|
+
http?: never;
|
|
1856
|
+
asyncFunctionName?: never;
|
|
1857
|
+
responseExpression?: never;
|
|
1858
|
+
}
|
|
1859
|
+
/**
|
|
1860
|
+
* Shared fields for both branches of {@link FunctionDerivationBase}.
|
|
1861
|
+
*
|
|
1862
|
+
* @internal
|
|
1863
|
+
*/
|
|
1864
|
+
interface FunctionDerivationShared extends SharedDerivationFields {
|
|
1865
|
+
source?: never;
|
|
1866
|
+
/** Forbidden on function-derivation mode */
|
|
1867
|
+
asyncFn?: never;
|
|
2048
1868
|
/**
|
|
2049
|
-
*
|
|
1869
|
+
* Explicit field dependencies for function derivations.
|
|
2050
1870
|
*
|
|
2051
|
-
*
|
|
2052
|
-
*
|
|
1871
|
+
* If not provided, defaults to all fields ('*').
|
|
1872
|
+
* Specify `dependsOn` for better performance when the function only
|
|
1873
|
+
* depends on a subset of fields.
|
|
2053
1874
|
*
|
|
2054
1875
|
* @example
|
|
2055
1876
|
* ```typescript
|
|
2056
|
-
* //
|
|
2057
|
-
*
|
|
2058
|
-
*
|
|
2059
|
-
*
|
|
1877
|
+
* // Only re-evaluate when country changes
|
|
1878
|
+
* {
|
|
1879
|
+
* key: 'currency',
|
|
1880
|
+
* logic: [{
|
|
1881
|
+
* type: 'derivation',
|
|
1882
|
+
* functionName: 'getCurrencyForCountry',
|
|
1883
|
+
* dependsOn: ['country']
|
|
1884
|
+
* }]
|
|
2060
1885
|
* }
|
|
2061
1886
|
* ```
|
|
2062
1887
|
*/
|
|
2063
|
-
|
|
1888
|
+
dependsOn?: string[];
|
|
1889
|
+
value?: never;
|
|
1890
|
+
expression?: never;
|
|
1891
|
+
http?: never;
|
|
1892
|
+
asyncFunctionName?: never;
|
|
1893
|
+
responseExpression?: never;
|
|
1894
|
+
}
|
|
1895
|
+
/**
|
|
1896
|
+
* Base for custom sync function derivations. `functionName` and `fn` are
|
|
1897
|
+
* mutually exclusive (XOR).
|
|
1898
|
+
*
|
|
1899
|
+
* @internal
|
|
1900
|
+
*/
|
|
1901
|
+
type FunctionDerivationBase = (FunctionDerivationShared & {
|
|
2064
1902
|
/**
|
|
2065
|
-
*
|
|
1903
|
+
* Name of a registered custom derivation function.
|
|
2066
1904
|
*
|
|
2067
|
-
*
|
|
2068
|
-
*
|
|
1905
|
+
* The function receives the evaluation context and returns the derived value.
|
|
1906
|
+
* Register functions in `customFnConfig.derivations`.
|
|
2069
1907
|
*
|
|
2070
1908
|
* @example
|
|
2071
1909
|
* ```typescript
|
|
2072
|
-
*
|
|
2073
|
-
*
|
|
2074
|
-
*
|
|
2075
|
-
* expression: "formFieldState.email.dirty && formFieldState.email.valid"
|
|
2076
|
-
* }
|
|
1910
|
+
* functionName: 'getCurrencyForCountry'
|
|
1911
|
+
* functionName: 'calculateTax'
|
|
1912
|
+
* functionName: 'formatPhoneNumber'
|
|
2077
1913
|
* ```
|
|
2078
1914
|
*/
|
|
2079
|
-
|
|
2080
|
-
/**
|
|
2081
|
-
|
|
2082
|
-
}
|
|
2083
|
-
|
|
1915
|
+
functionName: string;
|
|
1916
|
+
/** Inline form is forbidden when `functionName` is set */
|
|
1917
|
+
fn?: never;
|
|
1918
|
+
}) | (FunctionDerivationShared & {
|
|
1919
|
+
/**
|
|
1920
|
+
* Inline custom derivation function. Mutually exclusive with `functionName`.
|
|
1921
|
+
*
|
|
1922
|
+
* NOT JSON-serializable — for code-only configs. For configs loaded
|
|
1923
|
+
* from JSON / OpenAPI / databases, use `functionName` to reference
|
|
1924
|
+
* a function registered in `customFnConfig.derivations`.
|
|
1925
|
+
*/
|
|
1926
|
+
fn: CustomFunction;
|
|
1927
|
+
/** Registered form is forbidden when `fn` is set */
|
|
1928
|
+
functionName?: never;
|
|
1929
|
+
});
|
|
2084
1930
|
/**
|
|
2085
|
-
*
|
|
1931
|
+
* HTTP derivation that evaluates immediately on change (default).
|
|
1932
|
+
* @internal
|
|
1933
|
+
*/
|
|
1934
|
+
type OnChangeHttpDerivationLogicConfig = HttpDerivationBase & ImmediateDerivationTrigger;
|
|
1935
|
+
/**
|
|
1936
|
+
* HTTP derivation that evaluates after a debounce period.
|
|
1937
|
+
* @internal
|
|
1938
|
+
*/
|
|
1939
|
+
type DebouncedHttpDerivationLogicConfig = HttpDerivationBase & DebouncedDerivationTrigger;
|
|
1940
|
+
/**
|
|
1941
|
+
* Async function derivation that evaluates immediately on change (default).
|
|
1942
|
+
* @internal
|
|
1943
|
+
*/
|
|
1944
|
+
type OnChangeAsyncFunctionDerivationLogicConfig = AsyncFunctionDerivationBase & ImmediateDerivationTrigger;
|
|
1945
|
+
/**
|
|
1946
|
+
* Async function derivation that evaluates after a debounce period.
|
|
1947
|
+
* @internal
|
|
1948
|
+
*/
|
|
1949
|
+
type DebouncedAsyncFunctionDerivationLogicConfig = AsyncFunctionDerivationBase & DebouncedDerivationTrigger;
|
|
1950
|
+
/**
|
|
1951
|
+
* Expression derivation that evaluates immediately on change (default).
|
|
1952
|
+
* @internal
|
|
1953
|
+
*/
|
|
1954
|
+
type OnChangeExpressionDerivationLogicConfig = ExpressionDerivationBase & ImmediateDerivationTrigger;
|
|
1955
|
+
/**
|
|
1956
|
+
* Expression derivation that evaluates after a debounce period.
|
|
1957
|
+
* @internal
|
|
1958
|
+
*/
|
|
1959
|
+
type DebouncedExpressionDerivationLogicConfig = ExpressionDerivationBase & DebouncedDerivationTrigger;
|
|
1960
|
+
/**
|
|
1961
|
+
* Value derivation that evaluates immediately on change (default).
|
|
1962
|
+
* @internal
|
|
1963
|
+
*/
|
|
1964
|
+
type OnChangeValueDerivationLogicConfig = ValueDerivationBase & ImmediateDerivationTrigger;
|
|
1965
|
+
/**
|
|
1966
|
+
* Value derivation that evaluates after a debounce period.
|
|
1967
|
+
* @internal
|
|
1968
|
+
*/
|
|
1969
|
+
type DebouncedValueDerivationLogicConfig = ValueDerivationBase & DebouncedDerivationTrigger;
|
|
1970
|
+
/**
|
|
1971
|
+
* Function derivation that evaluates immediately on change (default).
|
|
1972
|
+
* @internal
|
|
1973
|
+
*/
|
|
1974
|
+
type OnChangeFunctionDerivationLogicConfig = FunctionDerivationBase & ImmediateDerivationTrigger;
|
|
1975
|
+
/**
|
|
1976
|
+
* Function derivation that evaluates after a debounce period.
|
|
1977
|
+
* @internal
|
|
1978
|
+
*/
|
|
1979
|
+
type DebouncedFunctionDerivationLogicConfig = FunctionDerivationBase & DebouncedDerivationTrigger;
|
|
1980
|
+
/**
|
|
1981
|
+
* Configuration for value derivation logic.
|
|
2086
1982
|
*
|
|
2087
|
-
*
|
|
2088
|
-
* -
|
|
2089
|
-
*
|
|
2090
|
-
* - `disabled` logic (dynamic disabled state)
|
|
2091
|
-
* - Dynamic value calculations
|
|
1983
|
+
* Enables programmatic value derivation based on conditions.
|
|
1984
|
+
* Derivations are self-targeting: the logic is placed on the field
|
|
1985
|
+
* that should receive the computed value.
|
|
2092
1986
|
*
|
|
2093
|
-
*
|
|
2094
|
-
*
|
|
2095
|
-
*
|
|
2096
|
-
*
|
|
1987
|
+
* @example
|
|
1988
|
+
* ```typescript
|
|
1989
|
+
* // Set phone prefix based on country selection
|
|
1990
|
+
* {
|
|
1991
|
+
* key: 'phonePrefix',
|
|
1992
|
+
* logic: [{
|
|
1993
|
+
* type: 'derivation',
|
|
1994
|
+
* value: '+1',
|
|
1995
|
+
* condition: {
|
|
1996
|
+
* type: 'fieldValue',
|
|
1997
|
+
* fieldPath: 'country',
|
|
1998
|
+
* operator: 'equals',
|
|
1999
|
+
* value: 'USA'
|
|
2000
|
+
* }
|
|
2001
|
+
* }]
|
|
2002
|
+
* }
|
|
2003
|
+
*
|
|
2004
|
+
* // Compute total from quantity and price
|
|
2005
|
+
* {
|
|
2006
|
+
* key: 'total',
|
|
2007
|
+
* derivation: 'formValue.quantity * formValue.unitPrice'
|
|
2008
|
+
* }
|
|
2009
|
+
*
|
|
2010
|
+
* // Use custom function for complex logic
|
|
2011
|
+
* {
|
|
2012
|
+
* key: 'currency',
|
|
2013
|
+
* logic: [{
|
|
2014
|
+
* type: 'derivation',
|
|
2015
|
+
* functionName: 'getCurrencyForCountry'
|
|
2016
|
+
* }]
|
|
2017
|
+
* }
|
|
2097
2018
|
*
|
|
2098
|
-
*
|
|
2099
|
-
*
|
|
2100
|
-
*
|
|
2101
|
-
*
|
|
2102
|
-
*
|
|
2103
|
-
*
|
|
2019
|
+
* // Self-transform with debounced trigger
|
|
2020
|
+
* // (applies after user stops typing)
|
|
2021
|
+
* {
|
|
2022
|
+
* key: 'email',
|
|
2023
|
+
* logic: [{
|
|
2024
|
+
* type: 'derivation',
|
|
2025
|
+
* expression: 'formValue.email.toLowerCase()',
|
|
2026
|
+
* trigger: 'debounced',
|
|
2027
|
+
* debounceMs: 500
|
|
2028
|
+
* }]
|
|
2029
|
+
* }
|
|
2104
2030
|
*
|
|
2105
|
-
* //
|
|
2031
|
+
* // Array item derivation (formValue is scoped to current item)
|
|
2106
2032
|
* {
|
|
2107
|
-
* key: '
|
|
2108
|
-
*
|
|
2109
|
-
* when: { function: 'isAdult' } // Only show if isAdult returns true
|
|
2033
|
+
* key: 'lineTotal', // Inside array field
|
|
2034
|
+
* derivation: 'formValue.quantity * formValue.unitPrice'
|
|
2110
2035
|
* }
|
|
2111
2036
|
* ```
|
|
2112
2037
|
*
|
|
2113
|
-
* @
|
|
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
|
-
* };
|
|
2121
|
-
* ```
|
|
2038
|
+
* @public
|
|
2122
2039
|
*/
|
|
2123
|
-
type
|
|
2124
|
-
|
|
2040
|
+
type DerivationLogicConfig = OnChangeHttpDerivationLogicConfig | DebouncedHttpDerivationLogicConfig | OnChangeAsyncFunctionDerivationLogicConfig | DebouncedAsyncFunctionDerivationLogicConfig | OnChangeExpressionDerivationLogicConfig | DebouncedExpressionDerivationLogicConfig | OnChangeValueDerivationLogicConfig | DebouncedValueDerivationLogicConfig | OnChangeFunctionDerivationLogicConfig | DebouncedFunctionDerivationLogicConfig;
|
|
2125
2041
|
/**
|
|
2126
|
-
*
|
|
2127
|
-
*
|
|
2128
|
-
* The function receives the evaluation context and may return a Promise or Observable.
|
|
2129
|
-
* Register functions in `customFnConfig.asyncDerivations`.
|
|
2042
|
+
* Union type for all logic configurations.
|
|
2130
2043
|
*
|
|
2131
|
-
*
|
|
2132
|
-
*
|
|
2133
|
-
* const fetchPrice: AsyncDerivationFunction = async (context) => {
|
|
2134
|
-
* const response = await fetch(`/api/price?product=${context.formValue.productId}`);
|
|
2135
|
-
* const data = await response.json();
|
|
2136
|
-
* return data.price;
|
|
2137
|
-
* };
|
|
2138
|
-
* ```
|
|
2044
|
+
* - `StateLogicConfig`: For field state changes (hidden, readonly, disabled, required)
|
|
2045
|
+
* - `DerivationLogicConfig`: For value derivation (including property derivation via `targetProperty`)
|
|
2139
2046
|
*
|
|
2140
2047
|
* @public
|
|
2141
2048
|
*/
|
|
2142
|
-
type
|
|
2049
|
+
type LogicConfig = StateLogicConfig | DerivationLogicConfig;
|
|
2143
2050
|
/**
|
|
2144
|
-
*
|
|
2051
|
+
* Log level for derivation debug output.
|
|
2145
2052
|
*
|
|
2146
|
-
*
|
|
2147
|
-
*
|
|
2053
|
+
* - 'none': No debug logging
|
|
2054
|
+
* - 'summary': Log cycle completion with counts (default in dev mode)
|
|
2055
|
+
* - 'verbose': Log individual derivation evaluations with details
|
|
2148
2056
|
*
|
|
2149
|
-
* @
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
*
|
|
2154
|
-
*
|
|
2155
|
-
*
|
|
2156
|
-
*
|
|
2057
|
+
* @public
|
|
2058
|
+
*/
|
|
2059
|
+
type DerivationLogLevel = 'none' | 'summary' | 'verbose';
|
|
2060
|
+
/**
|
|
2061
|
+
* Type guard to check if a condition is a FormStateCondition.
|
|
2062
|
+
*
|
|
2063
|
+
* @param condition - The condition to check
|
|
2064
|
+
* @returns true if the condition is a FormStateCondition
|
|
2157
2065
|
*
|
|
2158
2066
|
* @public
|
|
2159
2067
|
*/
|
|
2160
|
-
|
|
2068
|
+
declare function isFormStateCondition(condition: StateLogicConfig['condition'] | DerivationLogicConfig['condition']): condition is FormStateCondition;
|
|
2069
|
+
|
|
2070
|
+
/**
|
|
2071
|
+
* Configuration for applying predefined schemas
|
|
2072
|
+
*/
|
|
2073
|
+
interface SchemaApplicationConfig {
|
|
2074
|
+
/** Schema application type */
|
|
2075
|
+
type: 'apply' | 'applyWhen' | 'applyWhenValue' | 'applyEach';
|
|
2076
|
+
/** Schema identifier or inline schema definition */
|
|
2077
|
+
schema: string | SchemaDefinition;
|
|
2078
|
+
/** Condition for conditional application */
|
|
2079
|
+
condition?: ConditionalExpression;
|
|
2080
|
+
/** Type predicate for applyWhenValue */
|
|
2081
|
+
typePredicate?: string;
|
|
2082
|
+
}
|
|
2083
|
+
/**
|
|
2084
|
+
* Reusable schema definition that can be referenced by name
|
|
2085
|
+
*/
|
|
2086
|
+
interface SchemaDefinition {
|
|
2087
|
+
/** Unique schema identifier */
|
|
2088
|
+
name: string;
|
|
2089
|
+
/** Schema description */
|
|
2090
|
+
description?: string;
|
|
2091
|
+
/** Field path pattern this schema applies to */
|
|
2092
|
+
pathPattern?: string;
|
|
2093
|
+
/** Validators to apply */
|
|
2094
|
+
validators?: ValidatorConfig[];
|
|
2095
|
+
/** Logic rules to apply */
|
|
2096
|
+
logic?: LogicConfig[];
|
|
2097
|
+
/** Nested schema applications */
|
|
2098
|
+
subSchemas?: SchemaApplicationConfig[];
|
|
2099
|
+
}
|
|
2100
|
+
|
|
2101
|
+
type DynamicText = string | Observable<string> | Signal<string>;
|
|
2161
2102
|
|
|
2162
2103
|
/**
|
|
2163
2104
|
* Re-export Angular's ValidationError for consistency
|
|
@@ -2244,7 +2185,6 @@ type SubmissionActionResult = Promise<TreeValidationResult> | Observable<TreeVal
|
|
|
2244
2185
|
* @typeParam TValue - The form value type
|
|
2245
2186
|
*
|
|
2246
2187
|
* @public
|
|
2247
|
-
* @experimental
|
|
2248
2188
|
*/
|
|
2249
2189
|
interface SubmissionConfig<TValue = unknown> {
|
|
2250
2190
|
/**
|
|
@@ -3257,6 +3197,58 @@ interface TextField extends FieldDef<TextProps> {
|
|
|
3257
3197
|
readonly logic?: NonFieldLogicConfig[];
|
|
3258
3198
|
}
|
|
3259
3199
|
|
|
3200
|
+
/**
|
|
3201
|
+
* Scalar types supported by hidden fields.
|
|
3202
|
+
* These are the primitive value types that can be stored.
|
|
3203
|
+
*/
|
|
3204
|
+
type HiddenScalar = string | number | boolean;
|
|
3205
|
+
/**
|
|
3206
|
+
* All value types supported by hidden fields.
|
|
3207
|
+
* Supports both scalar values and arrays of scalars for storing
|
|
3208
|
+
* multiple IDs or similar data.
|
|
3209
|
+
*/
|
|
3210
|
+
type HiddenValue = HiddenScalar | HiddenScalar[];
|
|
3211
|
+
/**
|
|
3212
|
+
* Hidden field definition for storing values without rendering UI.
|
|
3213
|
+
*
|
|
3214
|
+
* Hidden fields participate in the form schema and contribute to form values,
|
|
3215
|
+
* but do not render any visible component. They are useful for:
|
|
3216
|
+
* - Storing IDs when updating existing records
|
|
3217
|
+
* - Persisting metadata that shouldn't be user-editable
|
|
3218
|
+
* - Tracking computed values that need to be submitted
|
|
3219
|
+
*
|
|
3220
|
+
* Unlike the `hidden` property on other fields (which hides a rendered field),
|
|
3221
|
+
* this field type renders nothing at all - it's a componentless field.
|
|
3222
|
+
*
|
|
3223
|
+
* @example
|
|
3224
|
+
* ```typescript
|
|
3225
|
+
* // Store a record ID for updates
|
|
3226
|
+
* const idField: HiddenField<string> = {
|
|
3227
|
+
* type: 'hidden',
|
|
3228
|
+
* key: 'id',
|
|
3229
|
+
* value: '550e8400-e29b-41d4-a716-446655440000',
|
|
3230
|
+
* };
|
|
3231
|
+
*
|
|
3232
|
+
* // Store multiple tag IDs
|
|
3233
|
+
* const tagIdsField: HiddenField<number[]> = {
|
|
3234
|
+
* type: 'hidden',
|
|
3235
|
+
* key: 'tagIds',
|
|
3236
|
+
* value: [1, 2, 3],
|
|
3237
|
+
* };
|
|
3238
|
+
* ```
|
|
3239
|
+
*
|
|
3240
|
+
* @typeParam TValue - The type of value stored (must be HiddenValue compatible)
|
|
3241
|
+
*/
|
|
3242
|
+
interface HiddenField<TValue extends HiddenValue = HiddenValue> extends FieldDef<never> {
|
|
3243
|
+
/** Discriminant for hidden field type */
|
|
3244
|
+
type: 'hidden';
|
|
3245
|
+
/**
|
|
3246
|
+
* The value to store in the form.
|
|
3247
|
+
* This is required - a hidden field without a value serves no purpose.
|
|
3248
|
+
*/
|
|
3249
|
+
value: TValue;
|
|
3250
|
+
}
|
|
3251
|
+
|
|
3260
3252
|
/**
|
|
3261
3253
|
* Configuration for the built-in 'css' wrapper type.
|
|
3262
3254
|
*
|
|
@@ -3284,17 +3276,129 @@ interface CssWrapper {
|
|
|
3284
3276
|
* horizontal field arrangement with 12-column sizing utilities (`df-col-1`
|
|
3285
3277
|
* through `df-col-12`) available to child fields via their own `col` property.
|
|
3286
3278
|
*
|
|
3287
|
-
* Applied automatically by `rowFieldMapper` when a user writes `{ type: 'row' }` —
|
|
3288
|
-
* users do not construct this config directly.
|
|
3279
|
+
* Applied automatically by `rowFieldMapper` when a user writes `{ type: 'row' }` —
|
|
3280
|
+
* users do not construct this config directly.
|
|
3281
|
+
*
|
|
3282
|
+
* @example
|
|
3283
|
+
* ```typescript
|
|
3284
|
+
* const wrapper: RowWrapper = { type: 'row' };
|
|
3285
|
+
* ```
|
|
3286
|
+
*/
|
|
3287
|
+
interface RowWrapper {
|
|
3288
|
+
readonly type: 'row';
|
|
3289
|
+
}
|
|
3290
|
+
|
|
3291
|
+
/**
|
|
3292
|
+
* Resolves a wrapper type name to its registered config interface.
|
|
3293
|
+
*
|
|
3294
|
+
* When `TWrappers` is a specific registered key (e.g., `'css'`), resolves to
|
|
3295
|
+
* the full config type from `FieldRegistryWrappers` (e.g., `CssWrapper`),
|
|
3296
|
+
* providing type-safe access to wrapper-specific properties like `cssClasses`.
|
|
3297
|
+
*
|
|
3298
|
+
* When `TWrappers` is the full `RegisteredWrapperTypes` union, distributes
|
|
3299
|
+
* to produce a discriminated union of all registered wrapper configs.
|
|
3300
|
+
*
|
|
3301
|
+
* @example
|
|
3302
|
+
* ```typescript
|
|
3303
|
+
* // Resolves to CssWrapper — cssClasses is typed
|
|
3304
|
+
* type CssConfig = WrapperConfig<'css'>;
|
|
3305
|
+
*
|
|
3306
|
+
* // Union of all registered wrapper configs
|
|
3307
|
+
* type AnyConfig = WrapperConfig;
|
|
3308
|
+
* ```
|
|
3309
|
+
*/
|
|
3310
|
+
type WrapperConfig<TWrappers extends RegisteredWrapperTypes = RegisteredWrapperTypes> = TWrappers extends keyof FieldRegistryWrappers ? FieldRegistryWrappers[TWrappers] : {
|
|
3311
|
+
readonly type: TWrappers;
|
|
3312
|
+
};
|
|
3313
|
+
/**
|
|
3314
|
+
* Signature of a lazy component loader — either a direct component class or
|
|
3315
|
+
* an ES module whose `default` export is the component. Shared by field and
|
|
3316
|
+
* wrapper registrations so the `loadComponent: () => import('./x.component')`
|
|
3317
|
+
* idiom types cleanly in both places.
|
|
3318
|
+
*/
|
|
3319
|
+
type LazyComponentLoader<T = unknown> = () => Promise<Type<T> | {
|
|
3320
|
+
default: Type<T>;
|
|
3321
|
+
}>;
|
|
3322
|
+
/**
|
|
3323
|
+
* Configuration interface for registering wrapper types.
|
|
3324
|
+
*
|
|
3325
|
+
* Defines how a wrapper component is loaded and identified. Wrapper components
|
|
3326
|
+
* provide visual decoration around field content (sections, headers, styling)
|
|
3327
|
+
* without affecting the form data structure.
|
|
3328
|
+
*
|
|
3329
|
+
* @example
|
|
3330
|
+
* ```typescript
|
|
3331
|
+
* const SectionWrapper: WrapperTypeDefinition = {
|
|
3332
|
+
* name: 'section',
|
|
3333
|
+
* loadComponent: () => import('./section-wrapper.component').then(m => m.SectionWrapperComponent),
|
|
3334
|
+
* };
|
|
3335
|
+
* ```
|
|
3336
|
+
*/
|
|
3337
|
+
interface WrapperTypeDefinition<T extends WrapperConfig = WrapperConfig> {
|
|
3338
|
+
/** Unique identifier for the wrapper type (also serves as discriminant from FieldTypeDefinition) */
|
|
3339
|
+
wrapperName: string;
|
|
3340
|
+
/** Wrapper definition type marker (internal use) */
|
|
3341
|
+
_wrapper?: T;
|
|
3342
|
+
/**
|
|
3343
|
+
* Function to load the wrapper component (supports lazy loading).
|
|
3344
|
+
* Returns a Promise that resolves to the component class or module with default export.
|
|
3345
|
+
*/
|
|
3346
|
+
loadComponent: LazyComponentLoader;
|
|
3347
|
+
/**
|
|
3348
|
+
* Field types this wrapper should auto-apply to.
|
|
3349
|
+
*
|
|
3350
|
+
* When a field's `type` matches any entry, the wrapper is injected into that
|
|
3351
|
+
* field's effective wrapper chain at the lowest priority (can be overridden
|
|
3352
|
+
* by `FormConfig.defaultWrappers` or the field-level `wrappers` array, and
|
|
3353
|
+
* fully cleared with `wrappers: null`).
|
|
3354
|
+
*/
|
|
3355
|
+
types?: readonly string[];
|
|
3356
|
+
}
|
|
3357
|
+
/**
|
|
3358
|
+
* Type guard for WrapperTypeDefinition.
|
|
3359
|
+
*
|
|
3360
|
+
* Discriminates via `wrapperName` — field types use `name`, wrapper types use `wrapperName`.
|
|
3361
|
+
*/
|
|
3362
|
+
declare function isWrapperTypeDefinition(value: unknown): value is WrapperTypeDefinition;
|
|
3363
|
+
/**
|
|
3364
|
+
* Contract that wrapper components must satisfy.
|
|
3365
|
+
*
|
|
3366
|
+
* Each wrapper exposes a `#fieldComponent` ViewContainerRef where the inner
|
|
3367
|
+
* content (next wrapper, or the field) is rendered imperatively. Config
|
|
3368
|
+
* properties (minus `type`) are set on the component via `setInput`; a
|
|
3369
|
+
* wrapper opts into a key by declaring a matching `input()`, and unknown
|
|
3370
|
+
* keys are silently dropped. Wrappers may additionally declare a
|
|
3371
|
+
* `fieldInputs` input for the wrapped field's mapper outputs (see
|
|
3372
|
+
* `WrapperFieldInputs` for when that's undefined).
|
|
3289
3373
|
*
|
|
3290
3374
|
* @example
|
|
3291
3375
|
* ```typescript
|
|
3292
|
-
*
|
|
3376
|
+
* @Component({
|
|
3377
|
+
* template: `
|
|
3378
|
+
* <dbx-section [header]="header() ?? ''">
|
|
3379
|
+
* <ng-container #fieldComponent></ng-container>
|
|
3380
|
+
* </dbx-section>
|
|
3381
|
+
* `,
|
|
3382
|
+
* })
|
|
3383
|
+
* export class SectionWrapperComponent implements FieldWrapper {
|
|
3384
|
+
* readonly fieldComponent = viewChild.required('fieldComponent', { read: ViewContainerRef });
|
|
3385
|
+
* readonly header = input<string>();
|
|
3386
|
+
* readonly fieldInputs = input<WrapperFieldInputs>();
|
|
3387
|
+
* }
|
|
3293
3388
|
* ```
|
|
3294
3389
|
*/
|
|
3295
|
-
interface
|
|
3296
|
-
|
|
3390
|
+
interface FieldWrapper {
|
|
3391
|
+
/** ViewContainerRef slot where inner content is rendered */
|
|
3392
|
+
readonly fieldComponent: Signal<ViewContainerRef>;
|
|
3297
3393
|
}
|
|
3394
|
+
/**
|
|
3395
|
+
* Injection token for the wrapper type registry.
|
|
3396
|
+
*
|
|
3397
|
+
* Provides access to the map of registered wrapper types. The registry is
|
|
3398
|
+
* populated via `provideDynamicForm()` and used by `ContainerFieldComponent` to
|
|
3399
|
+
* resolve wrapper types to their component implementations.
|
|
3400
|
+
*/
|
|
3401
|
+
declare const WRAPPER_REGISTRY: InjectionToken<Map<string, WrapperTypeDefinition<RowWrapper | _ng_forge_dynamic_forms.CssWrapper>>>;
|
|
3298
3402
|
|
|
3299
3403
|
/**
|
|
3300
3404
|
* Container field interface for wrapping child fields with UI chrome.
|
|
@@ -3346,36 +3450,281 @@ interface RowWrapper {
|
|
|
3346
3450
|
* };
|
|
3347
3451
|
* ```
|
|
3348
3452
|
*/
|
|
3349
|
-
interface ContainerField<TFields extends readonly ContainerAllowedChildren[] = readonly ContainerAllowedChildren[], TWrapperConfigs extends readonly WrapperConfig[] = readonly WrapperConfig[]> extends FieldDef<never> {
|
|
3350
|
-
type: 'container';
|
|
3351
|
-
/** Child definitions to render within this container */
|
|
3352
|
-
readonly fields: TFields;
|
|
3453
|
+
interface ContainerField<TFields extends readonly ContainerAllowedChildren[] = readonly ContainerAllowedChildren[], TWrapperConfigs extends readonly WrapperConfig[] = readonly WrapperConfig[]> extends FieldDef<never> {
|
|
3454
|
+
type: 'container';
|
|
3455
|
+
/** Child definitions to render within this container */
|
|
3456
|
+
readonly fields: TFields;
|
|
3457
|
+
/**
|
|
3458
|
+
* Wrapper components to chain around the children.
|
|
3459
|
+
* Applied outermost-first: the first wrapper in the array is the outermost.
|
|
3460
|
+
* Each wrapper component receives the subsequent wrapper (or children) inside
|
|
3461
|
+
* its `#fieldComponent` ViewContainerRef slot.
|
|
3462
|
+
*/
|
|
3463
|
+
readonly wrappers: TWrapperConfigs;
|
|
3464
|
+
/** Container fields do not have a label property */
|
|
3465
|
+
readonly label?: never;
|
|
3466
|
+
/** Containers do not support meta — they have no native form element */
|
|
3467
|
+
readonly meta?: never;
|
|
3468
|
+
/**
|
|
3469
|
+
* Logic configurations for conditional container visibility.
|
|
3470
|
+
* Only 'hidden' type logic is supported for containers.
|
|
3471
|
+
*/
|
|
3472
|
+
readonly logic?: ContainerLogicConfig[];
|
|
3473
|
+
}
|
|
3474
|
+
/**
|
|
3475
|
+
* Type guard for ContainerField with proper type narrowing.
|
|
3476
|
+
*
|
|
3477
|
+
* `wrappers` is part of the type but optional in practice — many configs use
|
|
3478
|
+
* containers purely as flex/layout wrappers without any wrapper components.
|
|
3479
|
+
* Match on `type === 'container'` and the `fields` shape only, mirroring
|
|
3480
|
+
* `isPageField` / `isRowField`.
|
|
3481
|
+
*/
|
|
3482
|
+
declare function isContainerTypedField(field: FieldDef<any>): field is ContainerField;
|
|
3483
|
+
|
|
3484
|
+
/**
|
|
3485
|
+
* Type constraints for field nesting rules
|
|
3486
|
+
* These ensure that container fields can only contain valid child field types
|
|
3487
|
+
*
|
|
3488
|
+
* Note: We explicitly list types instead of using Exclude to avoid circular dependencies
|
|
3489
|
+
*/
|
|
3490
|
+
/**
|
|
3491
|
+
* Fields that are allowed as children of Page fields
|
|
3492
|
+
* Pages can contain: rows, groups, arrays, and leaf fields (but NOT other pages)
|
|
3493
|
+
*/
|
|
3494
|
+
type PageAllowedChildren = LeafFieldTypes | RowField | GroupField | ArrayField | SimplifiedArrayField | ContainerField;
|
|
3495
|
+
/**
|
|
3496
|
+
* Fields that are allowed as children of Group fields
|
|
3497
|
+
* Groups can contain: rows and leaf fields (but NOT pages or other groups)
|
|
3498
|
+
*/
|
|
3499
|
+
type GroupAllowedChildren = LeafFieldTypes | RowField | ContainerField;
|
|
3500
|
+
/**
|
|
3501
|
+
* Fields that are allowed as children of Array fields
|
|
3502
|
+
* Arrays can contain: rows, groups, and leaf fields (but NOT pages or other arrays)
|
|
3503
|
+
* Groups are used for creating object arrays where each array item is an object
|
|
3504
|
+
*/
|
|
3505
|
+
type ArrayAllowedChildren = LeafFieldTypes | RowField | GroupField | ContainerField;
|
|
3506
|
+
/**
|
|
3507
|
+
* Fields that are allowed as children of Container fields.
|
|
3508
|
+
* Containers are pure layout primitives that flatten their children into the
|
|
3509
|
+
* parent form, so most registered field types may appear inside — including
|
|
3510
|
+
* hidden fields and other containers — but NOT pages.
|
|
3511
|
+
*/
|
|
3512
|
+
type ContainerAllowedChildren = LeafFieldTypes | RowField | GroupField | ArrayField | SimplifiedArrayField | ContainerField;
|
|
3513
|
+
/**
|
|
3514
|
+
* Row is a synthetic field type that resolves to a Container at runtime, so
|
|
3515
|
+
* it accepts the same children as a Container.
|
|
3516
|
+
*/
|
|
3517
|
+
type RowAllowedChildren = ContainerAllowedChildren;
|
|
3518
|
+
|
|
3519
|
+
/**
|
|
3520
|
+
* An array item template is an array of allowed children that defines one OBJECT array item's fields.
|
|
3521
|
+
* Used for object arrays where each item is `{ key1: value1, key2: value2, ... }`.
|
|
3522
|
+
*/
|
|
3523
|
+
type ArrayItemTemplate = readonly ArrayAllowedChildren[];
|
|
3524
|
+
/**
|
|
3525
|
+
* An array item definition can be either:
|
|
3526
|
+
* - A single field (ArrayAllowedChildren) for PRIMITIVE array items - extracts field value directly
|
|
3527
|
+
* - An array of fields (ArrayItemTemplate) for OBJECT array items - merges fields into object
|
|
3528
|
+
*
|
|
3529
|
+
* This allows support for:
|
|
3530
|
+
* - Primitive arrays: `['tag1', 'tag2']`
|
|
3531
|
+
* - Object arrays: `[{ name: 'Alice', email: '...' }]`
|
|
3532
|
+
* - Heterogeneous arrays: `[{ value: 'x' }, 'y']` (mixed primitives and objects)
|
|
3533
|
+
*/
|
|
3534
|
+
type ArrayItemDefinition = ArrayAllowedChildren | ArrayItemTemplate;
|
|
3535
|
+
/**
|
|
3536
|
+
* Array field interface for creating dynamic field collections that map to array values.
|
|
3537
|
+
*
|
|
3538
|
+
* Key concepts:
|
|
3539
|
+
* - The outer `fields` array defines INITIAL ITEMS (each element is one array item to render)
|
|
3540
|
+
* - Each element can be either:
|
|
3541
|
+
* - A single FieldDef (primitive item) - the field's value is extracted directly
|
|
3542
|
+
* - An array of FieldDefs (object item) - fields are merged into an object
|
|
3543
|
+
* - Items can have different field configurations (heterogeneous arrays supported)
|
|
3544
|
+
* - Empty `fields: []` means no initial items - user adds via buttons with explicit templates
|
|
3545
|
+
* - Initial values are declared via `value` property on each field definition
|
|
3546
|
+
*
|
|
3547
|
+
* @example
|
|
3548
|
+
* // Primitive array: ['angular', 'typescript']
|
|
3549
|
+
* {
|
|
3550
|
+
* key: 'tags',
|
|
3551
|
+
* type: 'array',
|
|
3552
|
+
* fields: [
|
|
3553
|
+
* { key: 'tag', type: 'input', value: 'angular' }, // Single field = primitive
|
|
3554
|
+
* { key: 'tag', type: 'input', value: 'typescript' },
|
|
3555
|
+
* ]
|
|
3556
|
+
* }
|
|
3557
|
+
*
|
|
3558
|
+
* @example
|
|
3559
|
+
* // Object array: [{ name: 'Alice', email: '...' }]
|
|
3560
|
+
* {
|
|
3561
|
+
* key: 'contacts',
|
|
3562
|
+
* type: 'array',
|
|
3563
|
+
* fields: [
|
|
3564
|
+
* [ // Array = object
|
|
3565
|
+
* { key: 'name', type: 'input', value: 'Alice' },
|
|
3566
|
+
* { key: 'email', type: 'input', value: 'alice@example.com' }
|
|
3567
|
+
* ],
|
|
3568
|
+
* ]
|
|
3569
|
+
* }
|
|
3570
|
+
*
|
|
3571
|
+
* @example
|
|
3572
|
+
* // Heterogeneous array: [{ label: 'Structured' }, 'Simple']
|
|
3573
|
+
* {
|
|
3574
|
+
* key: 'items',
|
|
3575
|
+
* type: 'array',
|
|
3576
|
+
* fields: [
|
|
3577
|
+
* [{ key: 'label', type: 'input', value: 'Structured' }], // Object item
|
|
3578
|
+
* { key: 'value', type: 'input', value: 'Simple' }, // Primitive item
|
|
3579
|
+
* ]
|
|
3580
|
+
* }
|
|
3581
|
+
*
|
|
3582
|
+
* @example
|
|
3583
|
+
* // Empty array (add items via buttons)
|
|
3584
|
+
* {
|
|
3585
|
+
* key: 'tags',
|
|
3586
|
+
* type: 'array',
|
|
3587
|
+
* fields: [] // No initial items - user adds via button with template
|
|
3588
|
+
* }
|
|
3589
|
+
*
|
|
3590
|
+
* Nesting constraints: Arrays can contain rows, leaf fields, or groups (for object arrays),
|
|
3591
|
+
* but NOT pages or other arrays. Runtime validation enforces these rules.
|
|
3592
|
+
*
|
|
3593
|
+
* Note: Arrays are container fields and do not support `meta` since they have no native form element.
|
|
3594
|
+
*/
|
|
3595
|
+
interface ArrayField<TFields extends readonly ArrayItemDefinition[] = readonly ArrayItemDefinition[]> extends FieldDef<never> {
|
|
3596
|
+
type: 'array';
|
|
3597
|
+
/**
|
|
3598
|
+
* Array of item definitions. Each element defines one array item:
|
|
3599
|
+
* - Single FieldDef = primitive item (field's value is extracted directly)
|
|
3600
|
+
* - Array of FieldDefs = object item (fields are merged into an object)
|
|
3601
|
+
* - Empty array `[]` = no initial items (add via buttons with explicit templates)
|
|
3602
|
+
* - Items can have different configurations (heterogeneous arrays supported)
|
|
3603
|
+
* - Initial values are set via `value` property on each field definition
|
|
3604
|
+
*/
|
|
3605
|
+
readonly fields: TFields;
|
|
3606
|
+
/** Array fields do not have a label property **/
|
|
3607
|
+
readonly label?: never;
|
|
3608
|
+
/** Arrays do not support meta - they have no native form element **/
|
|
3609
|
+
readonly meta?: never;
|
|
3610
|
+
/**
|
|
3611
|
+
* Logic configurations for conditional array visibility.
|
|
3612
|
+
* Only 'hidden' type logic is supported for arrays.
|
|
3613
|
+
*/
|
|
3614
|
+
readonly logic?: ContainerLogicConfig[];
|
|
3615
|
+
/**
|
|
3616
|
+
* Minimum number of items required in the array.
|
|
3617
|
+
* Validation fails if the array has fewer items than this value.
|
|
3618
|
+
*/
|
|
3619
|
+
readonly minLength?: number;
|
|
3620
|
+
/**
|
|
3621
|
+
* Maximum number of items allowed in the array.
|
|
3622
|
+
* Validation fails if the array has more items than this value.
|
|
3623
|
+
*/
|
|
3624
|
+
readonly maxLength?: number;
|
|
3625
|
+
}
|
|
3626
|
+
/**
|
|
3627
|
+
* Type guard for ArrayField with proper type narrowing.
|
|
3628
|
+
* Validates that the field is an array type with a fields property that is an array.
|
|
3629
|
+
* Fields can contain either single FieldDefs (primitive items) or arrays of FieldDefs (object items).
|
|
3630
|
+
*/
|
|
3631
|
+
declare function isArrayField(field: FieldDef<any>): field is ArrayField;
|
|
3632
|
+
/**
|
|
3633
|
+
* Configuration for auto-generated add/remove buttons in simplified array fields.
|
|
3634
|
+
*/
|
|
3635
|
+
interface ArrayButtonConfig {
|
|
3636
|
+
/** Custom label for the button */
|
|
3637
|
+
readonly label?: string;
|
|
3638
|
+
/** Additional properties passed to the button component */
|
|
3639
|
+
readonly props?: Record<string, unknown>;
|
|
3640
|
+
}
|
|
3641
|
+
/**
|
|
3642
|
+
* Simplified array field interface for common use cases.
|
|
3643
|
+
*
|
|
3644
|
+
* Instead of manually specifying each item in `fields`, provide a `template` that defines
|
|
3645
|
+
* the structure of a single item, and a `value` array with initial data.
|
|
3646
|
+
* The library auto-generates add/remove buttons.
|
|
3647
|
+
*
|
|
3648
|
+
* Discriminant: `template` presence → simplified API; `Array.isArray(template)` → object vs primitive.
|
|
3649
|
+
*
|
|
3650
|
+
* @example
|
|
3651
|
+
* // Primitive array: ['angular', 'typescript']
|
|
3652
|
+
* {
|
|
3653
|
+
* key: 'tags',
|
|
3654
|
+
* type: 'array',
|
|
3655
|
+
* template: { key: 'value', type: 'input', label: 'Tag', required: true },
|
|
3656
|
+
* value: ['angular', 'typescript']
|
|
3657
|
+
* }
|
|
3658
|
+
*
|
|
3659
|
+
* @example
|
|
3660
|
+
* // Object array: [{ name: 'Jane', phone: '555' }]
|
|
3661
|
+
* {
|
|
3662
|
+
* key: 'contacts',
|
|
3663
|
+
* type: 'array',
|
|
3664
|
+
* template: [
|
|
3665
|
+
* { key: 'name', type: 'input', label: 'Contact Name', required: true },
|
|
3666
|
+
* { key: 'phone', type: 'input', label: 'Phone Number' },
|
|
3667
|
+
* ],
|
|
3668
|
+
* value: [{ name: 'Jane', phone: '555' }]
|
|
3669
|
+
* }
|
|
3670
|
+
*
|
|
3671
|
+
* @example
|
|
3672
|
+
* // Button customization / opt-out
|
|
3673
|
+
* {
|
|
3674
|
+
* key: 'tags',
|
|
3675
|
+
* type: 'array',
|
|
3676
|
+
* template: { key: 'value', type: 'input', label: 'Tag' },
|
|
3677
|
+
* addButton: { label: 'Add Tag', props: { color: 'primary' } },
|
|
3678
|
+
* removeButton: false
|
|
3679
|
+
* }
|
|
3680
|
+
*/
|
|
3681
|
+
interface SimplifiedArrayField extends FieldDef<never> {
|
|
3682
|
+
type: 'array';
|
|
3353
3683
|
/**
|
|
3354
|
-
*
|
|
3355
|
-
*
|
|
3356
|
-
*
|
|
3357
|
-
* its `#fieldComponent` ViewContainerRef slot.
|
|
3684
|
+
* Template defining the structure of a single array item.
|
|
3685
|
+
* - Single field (ArrayAllowedChildren) → primitive array (each item is a single value)
|
|
3686
|
+
* - Array of fields (readonly ArrayAllowedChildren[]) → object array (each item is an object)
|
|
3358
3687
|
*/
|
|
3359
|
-
readonly
|
|
3360
|
-
/**
|
|
3688
|
+
readonly template: ArrayAllowedChildren | readonly ArrayAllowedChildren[];
|
|
3689
|
+
/** Initial values for the array. Each element creates one array item. */
|
|
3690
|
+
readonly value?: readonly unknown[];
|
|
3691
|
+
/**
|
|
3692
|
+
* Configuration for the auto-generated "Add" button, or `false` to disable it.
|
|
3693
|
+
* Defaults to a button with label "Add".
|
|
3694
|
+
*/
|
|
3695
|
+
readonly addButton?: ArrayButtonConfig | false;
|
|
3696
|
+
/**
|
|
3697
|
+
* Configuration for the auto-generated "Remove" button on each item, or `false` to disable it.
|
|
3698
|
+
* Defaults to a button with label "Remove".
|
|
3699
|
+
*/
|
|
3700
|
+
readonly removeButton?: ArrayButtonConfig | false;
|
|
3701
|
+
/** Simplified arrays do not support the label property */
|
|
3361
3702
|
readonly label?: never;
|
|
3362
|
-
/**
|
|
3703
|
+
/** Simplified arrays do not support meta */
|
|
3363
3704
|
readonly meta?: never;
|
|
3364
3705
|
/**
|
|
3365
|
-
* Logic configurations for conditional
|
|
3366
|
-
* Only 'hidden' type logic is supported for
|
|
3706
|
+
* Logic configurations for conditional array visibility.
|
|
3707
|
+
* Only 'hidden' type logic is supported for arrays.
|
|
3367
3708
|
*/
|
|
3368
3709
|
readonly logic?: ContainerLogicConfig[];
|
|
3710
|
+
/**
|
|
3711
|
+
* Minimum number of items required in the array.
|
|
3712
|
+
* Validation fails if the array has fewer items than this value.
|
|
3713
|
+
*/
|
|
3714
|
+
readonly minLength?: number;
|
|
3715
|
+
/**
|
|
3716
|
+
* Maximum number of items allowed in the array.
|
|
3717
|
+
* Validation fails if the array has more items than this value.
|
|
3718
|
+
*/
|
|
3719
|
+
readonly maxLength?: number;
|
|
3720
|
+
/** Mutually exclusive with `template` — use `fields` for the full API instead */
|
|
3721
|
+
readonly fields?: never;
|
|
3369
3722
|
}
|
|
3370
3723
|
/**
|
|
3371
|
-
* Type guard for
|
|
3372
|
-
*
|
|
3373
|
-
* `wrappers` is part of the type but optional in practice — many configs use
|
|
3374
|
-
* containers purely as flex/layout wrappers without any wrapper components.
|
|
3375
|
-
* Match on `type === 'container'` and the `fields` shape only, mirroring
|
|
3376
|
-
* `isPageField` / `isRowField`.
|
|
3724
|
+
* Type guard for SimplifiedArrayField.
|
|
3725
|
+
* Checks for `type: 'array'` with a `template` property (discriminant from full ArrayField).
|
|
3377
3726
|
*/
|
|
3378
|
-
declare function
|
|
3727
|
+
declare function isSimplifiedArrayField(field: FieldDef<any>): field is SimplifiedArrayField;
|
|
3379
3728
|
|
|
3380
3729
|
/**
|
|
3381
3730
|
* Container fields registry - augment this interface to add custom container fields
|
|
@@ -3525,180 +3874,6 @@ type NarrowField<T> = T extends {
|
|
|
3525
3874
|
*/
|
|
3526
3875
|
type NarrowFields = readonly NarrowField<RegisteredFieldTypes>[];
|
|
3527
3876
|
|
|
3528
|
-
/**
|
|
3529
|
-
* Type constraints for field nesting rules
|
|
3530
|
-
* These ensure that container fields can only contain valid child field types
|
|
3531
|
-
*
|
|
3532
|
-
* Note: We explicitly list types instead of using Exclude to avoid circular dependencies
|
|
3533
|
-
*/
|
|
3534
|
-
/**
|
|
3535
|
-
* Fields that are allowed as children of Page fields
|
|
3536
|
-
* Pages can contain: rows, groups, arrays, and leaf fields (but NOT other pages)
|
|
3537
|
-
*/
|
|
3538
|
-
type PageAllowedChildren = LeafFieldTypes | RowField | GroupField | ArrayField | SimplifiedArrayField | ContainerField;
|
|
3539
|
-
/**
|
|
3540
|
-
* Fields that are allowed as children of Group fields
|
|
3541
|
-
* Groups can contain: rows and leaf fields (but NOT pages or other groups)
|
|
3542
|
-
*/
|
|
3543
|
-
type GroupAllowedChildren = LeafFieldTypes | RowField | ContainerField;
|
|
3544
|
-
/**
|
|
3545
|
-
* Fields that are allowed as children of Array fields
|
|
3546
|
-
* Arrays can contain: rows, groups, and leaf fields (but NOT pages or other arrays)
|
|
3547
|
-
* Groups are used for creating object arrays where each array item is an object
|
|
3548
|
-
*/
|
|
3549
|
-
type ArrayAllowedChildren = LeafFieldTypes | RowField | GroupField | ContainerField;
|
|
3550
|
-
/**
|
|
3551
|
-
* Fields that are allowed as children of Container fields.
|
|
3552
|
-
* Containers are pure layout primitives that flatten their children into the
|
|
3553
|
-
* parent form, so most registered field types may appear inside — including
|
|
3554
|
-
* hidden fields and other containers — but NOT pages.
|
|
3555
|
-
*/
|
|
3556
|
-
type ContainerAllowedChildren = LeafFieldTypes | RowField | GroupField | ArrayField | SimplifiedArrayField | ContainerField;
|
|
3557
|
-
/**
|
|
3558
|
-
* Row is a synthetic field type that resolves to a Container at runtime, so
|
|
3559
|
-
* it accepts the same children as a Container.
|
|
3560
|
-
*/
|
|
3561
|
-
type RowAllowedChildren = ContainerAllowedChildren;
|
|
3562
|
-
|
|
3563
|
-
/**
|
|
3564
|
-
* Row field interface for creating horizontal layouts.
|
|
3565
|
-
* A row is a synthetic field type that resolves to a Container at runtime,
|
|
3566
|
-
* with a synthesized `{ type: 'row' }` wrapper applied for layout. The row
|
|
3567
|
-
* itself does not hold a value — its children flatten into the parent form.
|
|
3568
|
-
*
|
|
3569
|
-
* Note: Rows do not support `meta` since they have no native form element.
|
|
3570
|
-
*/
|
|
3571
|
-
interface RowField<TFields extends readonly RowAllowedChildren[] = readonly RowAllowedChildren[]> extends FieldDef<never> {
|
|
3572
|
-
type: 'row';
|
|
3573
|
-
/** Child definitions to render within this row */
|
|
3574
|
-
readonly fields: TFields;
|
|
3575
|
-
/** Row fields do not have a label property **/
|
|
3576
|
-
readonly label?: never;
|
|
3577
|
-
/** Rows do not support meta - they have no native form element **/
|
|
3578
|
-
readonly meta?: never;
|
|
3579
|
-
/**
|
|
3580
|
-
* Logic configurations for conditional row visibility.
|
|
3581
|
-
* Only 'hidden' type logic is supported for rows.
|
|
3582
|
-
*/
|
|
3583
|
-
readonly logic?: ContainerLogicConfig[];
|
|
3584
|
-
}
|
|
3585
|
-
/**
|
|
3586
|
-
* Type guard for RowField with proper type narrowing
|
|
3587
|
-
*/
|
|
3588
|
-
declare function isRowField(field: FieldDef<any>): field is RowField;
|
|
3589
|
-
|
|
3590
|
-
/**
|
|
3591
|
-
* Resolves a wrapper type name to its registered config interface.
|
|
3592
|
-
*
|
|
3593
|
-
* When `TWrappers` is a specific registered key (e.g., `'css'`), resolves to
|
|
3594
|
-
* the full config type from `FieldRegistryWrappers` (e.g., `CssWrapper`),
|
|
3595
|
-
* providing type-safe access to wrapper-specific properties like `cssClasses`.
|
|
3596
|
-
*
|
|
3597
|
-
* When `TWrappers` is the full `RegisteredWrapperTypes` union, distributes
|
|
3598
|
-
* to produce a discriminated union of all registered wrapper configs.
|
|
3599
|
-
*
|
|
3600
|
-
* @example
|
|
3601
|
-
* ```typescript
|
|
3602
|
-
* // Resolves to CssWrapper — cssClasses is typed
|
|
3603
|
-
* type CssConfig = WrapperConfig<'css'>;
|
|
3604
|
-
*
|
|
3605
|
-
* // Union of all registered wrapper configs
|
|
3606
|
-
* type AnyConfig = WrapperConfig;
|
|
3607
|
-
* ```
|
|
3608
|
-
*/
|
|
3609
|
-
type WrapperConfig<TWrappers extends RegisteredWrapperTypes = RegisteredWrapperTypes> = TWrappers extends keyof FieldRegistryWrappers ? FieldRegistryWrappers[TWrappers] : {
|
|
3610
|
-
readonly type: TWrappers;
|
|
3611
|
-
};
|
|
3612
|
-
/**
|
|
3613
|
-
* Signature of a lazy component loader — either a direct component class or
|
|
3614
|
-
* an ES module whose `default` export is the component. Shared by field and
|
|
3615
|
-
* wrapper registrations so the `loadComponent: () => import('./x.component')`
|
|
3616
|
-
* idiom types cleanly in both places.
|
|
3617
|
-
*/
|
|
3618
|
-
type LazyComponentLoader<T = unknown> = () => Promise<Type<T> | {
|
|
3619
|
-
default: Type<T>;
|
|
3620
|
-
}>;
|
|
3621
|
-
/**
|
|
3622
|
-
* Configuration interface for registering wrapper types.
|
|
3623
|
-
*
|
|
3624
|
-
* Defines how a wrapper component is loaded and identified. Wrapper components
|
|
3625
|
-
* provide visual decoration around field content (sections, headers, styling)
|
|
3626
|
-
* without affecting the form data structure.
|
|
3627
|
-
*
|
|
3628
|
-
* @example
|
|
3629
|
-
* ```typescript
|
|
3630
|
-
* const SectionWrapper: WrapperTypeDefinition = {
|
|
3631
|
-
* name: 'section',
|
|
3632
|
-
* loadComponent: () => import('./section-wrapper.component').then(m => m.SectionWrapperComponent),
|
|
3633
|
-
* };
|
|
3634
|
-
* ```
|
|
3635
|
-
*/
|
|
3636
|
-
interface WrapperTypeDefinition<T extends WrapperConfig = WrapperConfig> {
|
|
3637
|
-
/** Unique identifier for the wrapper type (also serves as discriminant from FieldTypeDefinition) */
|
|
3638
|
-
wrapperName: string;
|
|
3639
|
-
/** Wrapper definition type marker (internal use) */
|
|
3640
|
-
_wrapper?: T;
|
|
3641
|
-
/**
|
|
3642
|
-
* Function to load the wrapper component (supports lazy loading).
|
|
3643
|
-
* Returns a Promise that resolves to the component class or module with default export.
|
|
3644
|
-
*/
|
|
3645
|
-
loadComponent: LazyComponentLoader;
|
|
3646
|
-
/**
|
|
3647
|
-
* Field types this wrapper should auto-apply to.
|
|
3648
|
-
*
|
|
3649
|
-
* When a field's `type` matches any entry, the wrapper is injected into that
|
|
3650
|
-
* field's effective wrapper chain at the lowest priority (can be overridden
|
|
3651
|
-
* by `FormConfig.defaultWrappers` or the field-level `wrappers` array, and
|
|
3652
|
-
* fully cleared with `wrappers: null`).
|
|
3653
|
-
*/
|
|
3654
|
-
types?: readonly string[];
|
|
3655
|
-
}
|
|
3656
|
-
/**
|
|
3657
|
-
* Type guard for WrapperTypeDefinition.
|
|
3658
|
-
*
|
|
3659
|
-
* Discriminates via `wrapperName` — field types use `name`, wrapper types use `wrapperName`.
|
|
3660
|
-
*/
|
|
3661
|
-
declare function isWrapperTypeDefinition(value: unknown): value is WrapperTypeDefinition;
|
|
3662
|
-
/**
|
|
3663
|
-
* Contract that wrapper components must satisfy.
|
|
3664
|
-
*
|
|
3665
|
-
* Each wrapper exposes a `#fieldComponent` ViewContainerRef where the inner
|
|
3666
|
-
* content (next wrapper, or the field) is rendered imperatively. Config
|
|
3667
|
-
* properties (minus `type`) are set on the component via `setInput`; a
|
|
3668
|
-
* wrapper opts into a key by declaring a matching `input()`, and unknown
|
|
3669
|
-
* keys are silently dropped. Wrappers may additionally declare a
|
|
3670
|
-
* `fieldInputs` input for the wrapped field's mapper outputs (see
|
|
3671
|
-
* `WrapperFieldInputs` for when that's undefined).
|
|
3672
|
-
*
|
|
3673
|
-
* @example
|
|
3674
|
-
* ```typescript
|
|
3675
|
-
* @Component({
|
|
3676
|
-
* template: `
|
|
3677
|
-
* <dbx-section [header]="header() ?? ''">
|
|
3678
|
-
* <ng-container #fieldComponent></ng-container>
|
|
3679
|
-
* </dbx-section>
|
|
3680
|
-
* `,
|
|
3681
|
-
* })
|
|
3682
|
-
* export class SectionWrapperComponent implements FieldWrapper {
|
|
3683
|
-
* readonly fieldComponent = viewChild.required('fieldComponent', { read: ViewContainerRef });
|
|
3684
|
-
* readonly header = input<string>();
|
|
3685
|
-
* readonly fieldInputs = input<WrapperFieldInputs>();
|
|
3686
|
-
* }
|
|
3687
|
-
* ```
|
|
3688
|
-
*/
|
|
3689
|
-
interface FieldWrapper {
|
|
3690
|
-
/** ViewContainerRef slot where inner content is rendered */
|
|
3691
|
-
readonly fieldComponent: Signal<ViewContainerRef>;
|
|
3692
|
-
}
|
|
3693
|
-
/**
|
|
3694
|
-
* Injection token for the wrapper type registry.
|
|
3695
|
-
*
|
|
3696
|
-
* Provides access to the map of registered wrapper types. The registry is
|
|
3697
|
-
* populated via `provideDynamicForm()` and used by `ContainerFieldComponent` to
|
|
3698
|
-
* resolve wrapper types to their component implementations.
|
|
3699
|
-
*/
|
|
3700
|
-
declare const WRAPPER_REGISTRY: InjectionToken<Map<string, WrapperTypeDefinition<RowWrapper | _ng_forge_dynamic_forms.CssWrapper>>>;
|
|
3701
|
-
|
|
3702
3877
|
/**
|
|
3703
3878
|
* Base interface for native HTML attributes that can be passed to form field elements.
|
|
3704
3879
|
*
|
|
@@ -3798,18 +3973,26 @@ interface FieldDef<TProps, TMeta extends FieldMeta = FieldMeta> {
|
|
|
3798
3973
|
/**
|
|
3799
3974
|
* Field type identifier for component selection.
|
|
3800
3975
|
*
|
|
3801
|
-
* Determines which component will be rendered for this field.
|
|
3802
|
-
*
|
|
3976
|
+
* Determines which component will be rendered for this field. Names
|
|
3977
|
+
* registered through module augmentation of `DynamicFormFieldRegistry`
|
|
3978
|
+
* are surfaced as IDE autocomplete suggestions via `RegisteredFieldTypes`;
|
|
3979
|
+
* the `(string & {})` branch keeps arbitrary custom identifiers accepted
|
|
3980
|
+
* as an escape hatch for fields that haven't been augmented into the
|
|
3981
|
+
* registry.
|
|
3803
3982
|
*
|
|
3804
3983
|
* @example
|
|
3805
3984
|
* ```typescript
|
|
3806
|
-
* type: 'input' // Text input field
|
|
3807
|
-
* type: 'select' // Dropdown selection
|
|
3808
|
-
* type: 'checkbox' // Boolean checkbox
|
|
3809
|
-
* type: 'group' // Field group container
|
|
3985
|
+
* type: 'input' // Text input field (registered)
|
|
3986
|
+
* type: 'select' // Dropdown selection (registered)
|
|
3987
|
+
* type: 'checkbox' // Boolean checkbox (registered)
|
|
3988
|
+
* type: 'group' // Field group container (registered)
|
|
3989
|
+
* type: 'my-custom' // Unregistered — still accepted, no autocomplete
|
|
3810
3990
|
* ```
|
|
3991
|
+
*
|
|
3992
|
+
* Wrapper types are intentionally excluded — wrappers are configured through
|
|
3993
|
+
* `wrappers` / `withFormWrappers()` rather than via the field-type discriminant.
|
|
3811
3994
|
*/
|
|
3812
|
-
type: string;
|
|
3995
|
+
type: RegisteredFieldTypes['type'] | (string & {});
|
|
3813
3996
|
/**
|
|
3814
3997
|
* Human-readable field label displayed to users.
|
|
3815
3998
|
*
|
|
@@ -4083,6 +4266,21 @@ interface ArrayContext {
|
|
|
4083
4266
|
/** The array field definition. */
|
|
4084
4267
|
field: FieldDef<unknown>;
|
|
4085
4268
|
}
|
|
4269
|
+
/**
|
|
4270
|
+
* Group context for fields rendered inside `group` containers.
|
|
4271
|
+
*
|
|
4272
|
+
* Carries a reactive dot-separated chain of group ancestors (including the
|
|
4273
|
+
* current group). Used by `mapFieldToInputs` to scope DOM IDs of group
|
|
4274
|
+
* children, so the same leaf key can appear inside different groups without
|
|
4275
|
+
* producing duplicate `id` attributes (issue #401).
|
|
4276
|
+
*
|
|
4277
|
+
* Re-provided by every group's child injector — a nested group reads its
|
|
4278
|
+
* parent's context signal and composes its own path through `computed()`.
|
|
4279
|
+
*/
|
|
4280
|
+
interface GroupContext {
|
|
4281
|
+
/** Dot-separated path of group ancestors, including the current group's key (e.g. `'address'` or `'user.address'`). */
|
|
4282
|
+
groupPath: Signal<string>;
|
|
4283
|
+
}
|
|
4086
4284
|
/**
|
|
4087
4285
|
* Mapper function that converts a field definition to component inputs.
|
|
4088
4286
|
*
|
|
@@ -4183,7 +4381,7 @@ interface FieldTypeDefinition<T extends FieldDef<any> = any> {
|
|
|
4183
4381
|
/** Semantic scope for tooling to discover interchangeable field alternatives */
|
|
4184
4382
|
scope?: FieldScope | FieldScope[];
|
|
4185
4383
|
/** Mapped component inputs that must exist before the renderer instantiates the component */
|
|
4186
|
-
renderReadyWhen?: RenderReadyInput[];
|
|
4384
|
+
renderReadyWhen?: readonly RenderReadyInput[];
|
|
4187
4385
|
}
|
|
4188
4386
|
/**
|
|
4189
4387
|
* Injection token for the global field type registry.
|
|
@@ -4212,6 +4410,15 @@ interface ResolvedField {
|
|
|
4212
4410
|
injector: Injector;
|
|
4213
4411
|
inputs: Signal<Record<string, unknown>>;
|
|
4214
4412
|
renderReady: Signal<boolean>;
|
|
4413
|
+
/**
|
|
4414
|
+
* Whether the field is currently hidden. Sourced from the FieldTree's
|
|
4415
|
+
* `state.hidden()` for value-bearing fields (set by Angular Signal Forms in
|
|
4416
|
+
* response to `hidden(path, …)` calls), or from the mapper's explicit
|
|
4417
|
+
* `hidden` input for non-value fields (button, text, container, …). Consumers
|
|
4418
|
+
* must `@if (!field.hidden())` around `*dfFieldOutlet` so hidden fields are
|
|
4419
|
+
* removed from the DOM — Angular Signal Forms warns (NG01916) otherwise.
|
|
4420
|
+
*/
|
|
4421
|
+
hidden: Signal<boolean>;
|
|
4215
4422
|
}
|
|
4216
4423
|
|
|
4217
4424
|
/**
|
|
@@ -4782,6 +4989,26 @@ declare const FIELD_SIGNAL_CONTEXT: InjectionToken<FieldSignalContext<Record<str
|
|
|
4782
4989
|
* ```
|
|
4783
4990
|
*/
|
|
4784
4991
|
declare const ARRAY_CONTEXT: InjectionToken<ArrayContext>;
|
|
4992
|
+
/**
|
|
4993
|
+
* Injection token for providing group ancestry to mappers and components.
|
|
4994
|
+
*
|
|
4995
|
+
* Re-provided in two places:
|
|
4996
|
+
* 1. `GroupFieldComponent` provides a context whose `groupPath()` is the
|
|
4997
|
+
* parent path extended by its own key (e.g. `'address'` or `'user.address'`).
|
|
4998
|
+
* 2. `createArrayItemInjector` provides a sentinel with an empty `groupPath`,
|
|
4999
|
+
* resetting the chain at array boundaries — descendants of an array item
|
|
5000
|
+
* that re-enter a group compose paths INSIDE the array item, matching the
|
|
5001
|
+
* property-derivation collector's keying.
|
|
5002
|
+
*
|
|
5003
|
+
* `mapFieldToInputs` reads this to scope DOM IDs and override-store keys so
|
|
5004
|
+
* the same leaf key can appear in different groups without colliding (#401).
|
|
5005
|
+
*
|
|
5006
|
+
* Inject with `{ optional: true }` because top-level fields with no group
|
|
5007
|
+
* ancestor (and no enclosing array) won't have the token provided. The token
|
|
5008
|
+
* IS present for fields inside an array even when no inner group exists — the
|
|
5009
|
+
* array-boundary sentinel makes injection succeed with an empty `groupPath`.
|
|
5010
|
+
*/
|
|
5011
|
+
declare const GROUP_CONTEXT: InjectionToken<GroupContext>;
|
|
4785
5012
|
/**
|
|
4786
5013
|
* Injection token for form-level default props.
|
|
4787
5014
|
*
|
|
@@ -6284,8 +6511,6 @@ declare function applyMetaToElement(element: Element, meta: FieldMeta | undefine
|
|
|
6284
6511
|
* );
|
|
6285
6512
|
* // user.value() keeps the old user data during loading transitions
|
|
6286
6513
|
* ```
|
|
6287
|
-
*
|
|
6288
|
-
* @experimental Uses Angular's experimental resource composition APIs.
|
|
6289
6514
|
*/
|
|
6290
6515
|
declare function withPreviousValue<T>(input: Resource<T>): Resource<T>;
|
|
6291
6516
|
|
|
@@ -6336,6 +6561,7 @@ declare class GroupFieldComponent<TModel extends Record<string, unknown> = Recor
|
|
|
6336
6561
|
private readonly destroyRef;
|
|
6337
6562
|
private readonly fieldRegistry;
|
|
6338
6563
|
private readonly parentFieldSignalContext;
|
|
6564
|
+
private readonly parentGroupContext;
|
|
6339
6565
|
private readonly injector;
|
|
6340
6566
|
protected readonly environmentInjector: EnvironmentInjector;
|
|
6341
6567
|
private readonly eventBus;
|
|
@@ -6365,6 +6591,13 @@ declare class GroupFieldComponent<TModel extends Record<string, unknown> = Recor
|
|
|
6365
6591
|
readonly disabled: _angular_core.Signal<boolean>;
|
|
6366
6592
|
private readonly nestedFieldTree;
|
|
6367
6593
|
private readonly groupFieldSignalContext;
|
|
6594
|
+
/**
|
|
6595
|
+
* Group context propagated to descendants — carries the dot-separated path of
|
|
6596
|
+
* group ancestors so child fields can scope their DOM IDs (issue #401).
|
|
6597
|
+
* Reactive via `computed()`: a nested group composes its path from the parent
|
|
6598
|
+
* context signal + its own key, so the chain stays consistent if either changes.
|
|
6599
|
+
*/
|
|
6600
|
+
private readonly groupContext;
|
|
6368
6601
|
private readonly groupInjector;
|
|
6369
6602
|
readonly validityChange: _angular_core.OutputRef<boolean>;
|
|
6370
6603
|
readonly dirtyChange: _angular_core.OutputRef<boolean>;
|
|
@@ -6396,6 +6629,12 @@ interface ResolvedArrayItemField {
|
|
|
6396
6629
|
inputs: Signal<Record<string, unknown>>;
|
|
6397
6630
|
/** Whether required mapped inputs are available for safe component instantiation. */
|
|
6398
6631
|
renderReady: Signal<boolean>;
|
|
6632
|
+
/**
|
|
6633
|
+
* Whether the array-item field is currently hidden — mirrors `ResolvedField.hidden`
|
|
6634
|
+
* so the same `@if (!field.hidden())` template gate works for top-level and
|
|
6635
|
+
* array-item fields.
|
|
6636
|
+
*/
|
|
6637
|
+
hidden: Signal<boolean>;
|
|
6399
6638
|
}
|
|
6400
6639
|
/**
|
|
6401
6640
|
* Resolved array item ready for declarative rendering with ngComponentOutlet.
|
|
@@ -6426,7 +6665,9 @@ declare class ArrayFieldComponent<TModel extends Record<string, unknown> = Recor
|
|
|
6426
6665
|
protected readonly environmentInjector: EnvironmentInjector;
|
|
6427
6666
|
private readonly eventBus;
|
|
6428
6667
|
private readonly logger;
|
|
6429
|
-
private readonly
|
|
6668
|
+
private readonly arrayItemRegistry;
|
|
6669
|
+
private readonly slot;
|
|
6670
|
+
/** Bound id-generator reference threaded to `resolveArrayItem` so callsites stay terse. */
|
|
6430
6671
|
private readonly generateItemId;
|
|
6431
6672
|
field: _angular_core.InputSignal<ArrayField<readonly ArrayItemDefinition[]>>;
|
|
6432
6673
|
key: _angular_core.InputSignal<string>;
|
|
@@ -6491,9 +6732,7 @@ declare class ArrayFieldComponent<TModel extends Record<string, unknown> = Recor
|
|
|
6491
6732
|
private readonly itemTemplates;
|
|
6492
6733
|
private readonly arrayFieldTrees;
|
|
6493
6734
|
private readonly resolvedItemsSignal;
|
|
6494
|
-
private readonly
|
|
6495
|
-
private readonly pendingInitializationCycle;
|
|
6496
|
-
private readonly settledInitializationCycle;
|
|
6735
|
+
private readonly fsm;
|
|
6497
6736
|
/**
|
|
6498
6737
|
* Map of item IDs to their current positions. O(1) lookup vs O(n) indexOf().
|
|
6499
6738
|
* Used by child linkedSignals to reactively track their position in the array.
|
|
@@ -6756,5 +6995,5 @@ declare class RootFormRegistryService {
|
|
|
6756
6995
|
*/
|
|
6757
6996
|
declare const INITIALIZATION_TIMEOUT_MS: InjectionToken<number>;
|
|
6758
6997
|
|
|
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 };
|
|
6998
|
+
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 };
|
|
6999
|
+
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 };
|