@ng-forge/dynamic-forms 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +77 -0
- package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms--Qgd-rJE.mjs +7359 -0
- package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms--Qgd-rJE.mjs.map +1 -0
- package/fesm2022/ng-forge-dynamic-forms-page-field.component-DcZh9mv6.mjs +112 -0
- package/fesm2022/ng-forge-dynamic-forms-page-field.component-DcZh9mv6.mjs.map +1 -0
- package/fesm2022/ng-forge-dynamic-forms-text-field.component-CnLc464X.mjs +113 -0
- package/fesm2022/ng-forge-dynamic-forms-text-field.component-CnLc464X.mjs.map +1 -0
- package/fesm2022/ng-forge-dynamic-forms.mjs +2 -0
- package/fesm2022/ng-forge-dynamic-forms.mjs.map +1 -0
- package/package.json +51 -0
- package/types/ng-forge-dynamic-forms.d.ts +3462 -0
|
@@ -0,0 +1,3462 @@
|
|
|
1
|
+
import * as rxjs from 'rxjs';
|
|
2
|
+
import { Observable } from 'rxjs';
|
|
3
|
+
import * as _angular_forms from '@angular/forms';
|
|
4
|
+
import * as _angular_forms_signals from '@angular/forms/signals';
|
|
5
|
+
import { ValidationError as ValidationError$1, FieldContext, TreeValidationResult, FieldTree, Schema, form, SchemaPath, SchemaPathTree } from '@angular/forms/signals';
|
|
6
|
+
import * as _ng_forge_dynamic_forms from '@ng-forge/dynamic-forms';
|
|
7
|
+
import * as _angular_core from '@angular/core';
|
|
8
|
+
import { Signal, ResourceRef, Injector, WritableSignal, InjectionToken, Type, OnDestroy, InputSignal, EnvironmentProviders, PipeTransform } from '@angular/core';
|
|
9
|
+
|
|
10
|
+
type WithInputSignals<T> = {
|
|
11
|
+
readonly [K in keyof T]-?: Signal<T[K]>;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
type Prettify<T> = {
|
|
15
|
+
[K in keyof T]: T[K];
|
|
16
|
+
} & {};
|
|
17
|
+
|
|
18
|
+
type DynamicText = string | Observable<string> | Signal<string>;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Base interface for all dynamic form field definitions.
|
|
22
|
+
*
|
|
23
|
+
* This interface defines the common properties that all field types must implement.
|
|
24
|
+
* Field-specific properties are defined through the generic TProps parameter,
|
|
25
|
+
* providing type safety for field-specific configuration.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```typescript
|
|
29
|
+
* // Basic text input field
|
|
30
|
+
* const textField: FieldDef<{ placeholder: string }> = {
|
|
31
|
+
* key: 'email',
|
|
32
|
+
* type: 'input',
|
|
33
|
+
* label: 'Email Address',
|
|
34
|
+
* props: { placeholder: 'Enter your email' },
|
|
35
|
+
* col: 12
|
|
36
|
+
* };
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```typescript
|
|
41
|
+
* // Complex field with conditional logic
|
|
42
|
+
* const conditionalField: FieldDef<SelectProps> = {
|
|
43
|
+
* key: 'country',
|
|
44
|
+
* type: 'select',
|
|
45
|
+
* label: 'Country',
|
|
46
|
+
* props: {
|
|
47
|
+
* options: [
|
|
48
|
+
* { label: 'United States', value: 'us' },
|
|
49
|
+
* { label: 'Canada', value: 'ca' }
|
|
50
|
+
* ]
|
|
51
|
+
* },
|
|
52
|
+
* hidden: false,
|
|
53
|
+
* disabled: false,
|
|
54
|
+
* col: 6
|
|
55
|
+
* };
|
|
56
|
+
* ```
|
|
57
|
+
*
|
|
58
|
+
* @typeParam TProps - Field-specific properties interface
|
|
59
|
+
*
|
|
60
|
+
* @public
|
|
61
|
+
*/
|
|
62
|
+
interface FieldDef<TProps> {
|
|
63
|
+
/**
|
|
64
|
+
* Unique field identifier used for form binding and value tracking.
|
|
65
|
+
*
|
|
66
|
+
* This key is used to associate the field with form values and must be
|
|
67
|
+
* unique within the form. It follows object property naming conventions.
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```typescript
|
|
71
|
+
* // Simple field key
|
|
72
|
+
* key: 'email'
|
|
73
|
+
*
|
|
74
|
+
* // Nested object notation
|
|
75
|
+
* key: 'address.street'
|
|
76
|
+
*
|
|
77
|
+
* // Array notation
|
|
78
|
+
* key: 'hobbies[0]'
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
key: string;
|
|
82
|
+
/**
|
|
83
|
+
* Field type identifier for component selection.
|
|
84
|
+
*
|
|
85
|
+
* Determines which component will be rendered for this field.
|
|
86
|
+
* Must match a registered field type name in the field registry.
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```typescript
|
|
90
|
+
* type: 'input' // Text input field
|
|
91
|
+
* type: 'select' // Dropdown selection
|
|
92
|
+
* type: 'checkbox' // Boolean checkbox
|
|
93
|
+
* type: 'group' // Field group container
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
type: string;
|
|
97
|
+
/**
|
|
98
|
+
* Human-readable field label displayed to users.
|
|
99
|
+
*
|
|
100
|
+
* Provides accessible labeling for form fields and is typically
|
|
101
|
+
* displayed above or beside the field input. Supports static strings,
|
|
102
|
+
* translation keys, Observables, and Signals for dynamic content.
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* ```typescript
|
|
106
|
+
* // Static string
|
|
107
|
+
* label: 'Email Address'
|
|
108
|
+
*
|
|
109
|
+
* // Translation key (auto-detected)
|
|
110
|
+
* label: 'form.email.label'
|
|
111
|
+
*
|
|
112
|
+
* // Observable from translation service
|
|
113
|
+
* label: this.transloco.selectTranslate('form.email.label')
|
|
114
|
+
*
|
|
115
|
+
* // Signal-based
|
|
116
|
+
* label: computed(() => this.translations().email.label)
|
|
117
|
+
* ```
|
|
118
|
+
*/
|
|
119
|
+
label?: DynamicText;
|
|
120
|
+
/**
|
|
121
|
+
* Field-specific properties and configuration options.
|
|
122
|
+
*
|
|
123
|
+
* Contains type-specific configuration that varies based on the field type.
|
|
124
|
+
* The shape is defined by the TProps generic parameter.
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* ```typescript
|
|
128
|
+
* // Input field props
|
|
129
|
+
* props: { placeholder: 'Enter email', type: 'email' }
|
|
130
|
+
*
|
|
131
|
+
* // Select field props
|
|
132
|
+
* props: { options: [{ label: 'Yes', value: true }], multiple: false }
|
|
133
|
+
*
|
|
134
|
+
* // Button field props
|
|
135
|
+
* props: { buttonType: 'submit', variant: 'primary' }
|
|
136
|
+
* ```
|
|
137
|
+
*/
|
|
138
|
+
props?: TProps;
|
|
139
|
+
/**
|
|
140
|
+
* Additional CSS classes for custom styling.
|
|
141
|
+
*
|
|
142
|
+
* Space-separated string of CSS class names that will be applied
|
|
143
|
+
* to the field container for custom styling.
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* ```typescript
|
|
147
|
+
* className: 'highlight required-field'
|
|
148
|
+
* className: 'mt-4 text-center'
|
|
149
|
+
* ```
|
|
150
|
+
*/
|
|
151
|
+
className?: string;
|
|
152
|
+
/**
|
|
153
|
+
* Whether the field is disabled and cannot be interacted with.
|
|
154
|
+
*
|
|
155
|
+
* When true, the field is rendered in a disabled state and cannot
|
|
156
|
+
* receive user input. The value can still be modified programmatically.
|
|
157
|
+
*
|
|
158
|
+
* @value false
|
|
159
|
+
*/
|
|
160
|
+
disabled?: boolean;
|
|
161
|
+
/**
|
|
162
|
+
* Whether the field is read-only.
|
|
163
|
+
*
|
|
164
|
+
* When true, the field displays its value but cannot be modified
|
|
165
|
+
* by user interaction. Differs from disabled in styling and accessibility.
|
|
166
|
+
*
|
|
167
|
+
* @value false
|
|
168
|
+
*/
|
|
169
|
+
readonly?: boolean;
|
|
170
|
+
/**
|
|
171
|
+
* Whether the field is hidden from view.
|
|
172
|
+
*
|
|
173
|
+
* When true, the field is not rendered in the UI but still participates
|
|
174
|
+
* in form state and validation. Useful for conditional field display.
|
|
175
|
+
*
|
|
176
|
+
* @value false
|
|
177
|
+
*/
|
|
178
|
+
hidden?: boolean;
|
|
179
|
+
/**
|
|
180
|
+
* Tab index for keyboard navigation.
|
|
181
|
+
*
|
|
182
|
+
* Controls the order in which fields receive focus when navigating
|
|
183
|
+
* with the Tab key. Follows standard HTML tabindex behavior.
|
|
184
|
+
*
|
|
185
|
+
* @example
|
|
186
|
+
* ```typescript
|
|
187
|
+
* tabIndex: 1 // First field in tab order
|
|
188
|
+
* tabIndex: -1 // Excluded from tab navigation
|
|
189
|
+
* tabIndex: 0 // Natural tab order
|
|
190
|
+
* ```
|
|
191
|
+
*/
|
|
192
|
+
tabIndex?: number | undefined;
|
|
193
|
+
/**
|
|
194
|
+
* Column sizing configuration for responsive grid layout.
|
|
195
|
+
*
|
|
196
|
+
* Specifies how many columns this field should span in a 12-column
|
|
197
|
+
* grid system. Supports responsive behavior and field arrangement.
|
|
198
|
+
*
|
|
199
|
+
* @example
|
|
200
|
+
* ```typescript
|
|
201
|
+
* col: 12 // Full width
|
|
202
|
+
* col: 6 // Half width
|
|
203
|
+
* col: 4 // One third width
|
|
204
|
+
* col: 3 // Quarter width
|
|
205
|
+
* ```
|
|
206
|
+
*
|
|
207
|
+
* @value 12
|
|
208
|
+
*/
|
|
209
|
+
col?: number;
|
|
210
|
+
}
|
|
211
|
+
type IncludedKeys = 'label' | 'className' | 'hidden' | 'tabIndex';
|
|
212
|
+
/**
|
|
213
|
+
* Type utility for extracting component input properties from field definitions.
|
|
214
|
+
*
|
|
215
|
+
* Transforms field definition properties into Angular component input signals,
|
|
216
|
+
* enabling type-safe component binding with automatic signal creation.
|
|
217
|
+
*
|
|
218
|
+
* @example
|
|
219
|
+
* ```typescript
|
|
220
|
+
* // Field definition
|
|
221
|
+
* interface MyFieldDef extends FieldDef<MyProps> {
|
|
222
|
+
* customProp: string;
|
|
223
|
+
* }
|
|
224
|
+
*
|
|
225
|
+
* // Component using FieldComponent type
|
|
226
|
+
* @Component({...})
|
|
227
|
+
* export class MyFieldComponent implements FieldComponent<MyFieldDef> {
|
|
228
|
+
* label = input<string>();
|
|
229
|
+
* className = input<string>();
|
|
230
|
+
* hidden = input<boolean>();
|
|
231
|
+
* tabIndex = input<number>();
|
|
232
|
+
* }
|
|
233
|
+
* ```
|
|
234
|
+
*
|
|
235
|
+
* @typeParam T - Field definition type to extract properties from
|
|
236
|
+
*
|
|
237
|
+
* @public
|
|
238
|
+
*/
|
|
239
|
+
type FieldComponent<T extends FieldDef<any>> = Prettify<WithInputSignals<Pick<T, IncludedKeys>>>;
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Re-export Angular's ValidationError for consistency
|
|
243
|
+
* This replaces our custom ValidationError interface
|
|
244
|
+
*/
|
|
245
|
+
type ValidationError = ValidationError$1;
|
|
246
|
+
interface ValidationMessages {
|
|
247
|
+
required?: DynamicText;
|
|
248
|
+
email?: DynamicText;
|
|
249
|
+
min?: DynamicText;
|
|
250
|
+
max?: DynamicText;
|
|
251
|
+
minLength?: DynamicText;
|
|
252
|
+
maxLength?: DynamicText;
|
|
253
|
+
pattern?: DynamicText;
|
|
254
|
+
[key: string]: DynamicText | undefined;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Row field interface for creating horizontal layouts
|
|
259
|
+
* This is a special field type that contains other definitions arranged horizontally
|
|
260
|
+
* The row itself doesn't have a value - it's a layout container
|
|
261
|
+
* This is a programmatic field type only - users cannot customize this field type
|
|
262
|
+
*
|
|
263
|
+
* TypeScript cannot enforce field nesting rules due to circular dependency limitations.
|
|
264
|
+
* For documentation: Rows should contain groups and leaf fields, but NOT pages or other rows.
|
|
265
|
+
* Runtime validation enforces these rules.
|
|
266
|
+
*/
|
|
267
|
+
interface RowField<TFields extends readonly RowAllowedChildren[] = readonly RowAllowedChildren[]> extends FieldDef<never> {
|
|
268
|
+
type: 'row';
|
|
269
|
+
/** Child definitions to render within this row */
|
|
270
|
+
readonly fields: TFields;
|
|
271
|
+
/** Row fields do not have a label property **/
|
|
272
|
+
readonly label?: never;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Type guard for RowField with proper type narrowing
|
|
276
|
+
*/
|
|
277
|
+
declare function isRowField(field: FieldDef<any>): field is RowField;
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Group field interface for creating logical field groupings that map to object values
|
|
281
|
+
* Groups create nested form structures where child field values are collected into an object
|
|
282
|
+
* This is a programmatic grouping only - users cannot customize this field type
|
|
283
|
+
*
|
|
284
|
+
* TypeScript cannot enforce field nesting rules due to circular dependency limitations.
|
|
285
|
+
* For documentation: Groups should contain rows and leaf fields, but NOT pages or other groups.
|
|
286
|
+
* Runtime validation enforces these rules.
|
|
287
|
+
*/
|
|
288
|
+
interface GroupField<TFields extends readonly GroupAllowedChildren[] = readonly GroupAllowedChildren[]> extends FieldDef<never> {
|
|
289
|
+
type: 'group';
|
|
290
|
+
readonly fields: TFields;
|
|
291
|
+
/** Groups do not have a label property - they are logical containers only **/
|
|
292
|
+
readonly label?: never;
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Type guard for GroupField with proper type narrowing
|
|
296
|
+
*/
|
|
297
|
+
declare function isGroupField(field: FieldDef<any>): field is GroupField;
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Type constraints for field nesting rules
|
|
301
|
+
* These ensure that container fields can only contain valid child field types
|
|
302
|
+
*
|
|
303
|
+
* Note: We explicitly list types instead of using Exclude to avoid circular dependencies
|
|
304
|
+
*/
|
|
305
|
+
/**
|
|
306
|
+
* Fields that are allowed as children of Page fields
|
|
307
|
+
* Pages can contain: rows, groups, arrays, and leaf fields (but NOT other pages)
|
|
308
|
+
*/
|
|
309
|
+
type PageAllowedChildren = LeafFieldTypes | RowField | GroupField | ArrayField;
|
|
310
|
+
/**
|
|
311
|
+
* Fields that are allowed as children of Row fields
|
|
312
|
+
* Rows can contain: groups, arrays, and leaf fields (but NOT pages or other rows)
|
|
313
|
+
*/
|
|
314
|
+
type RowAllowedChildren = LeafFieldTypes | GroupField | ArrayField;
|
|
315
|
+
/**
|
|
316
|
+
* Fields that are allowed as children of Group fields
|
|
317
|
+
* Groups can contain: rows and leaf fields (but NOT pages or other groups)
|
|
318
|
+
*/
|
|
319
|
+
type GroupAllowedChildren = LeafFieldTypes | RowField;
|
|
320
|
+
/**
|
|
321
|
+
* Fields that are allowed as children of Array fields
|
|
322
|
+
* Arrays can contain: rows, groups, and leaf fields (but NOT pages or other arrays)
|
|
323
|
+
* Groups are used for creating object arrays where each array item is an object
|
|
324
|
+
*/
|
|
325
|
+
type ArrayAllowedChildren = LeafFieldTypes | RowField | GroupField;
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Array field interface for creating dynamic field collections that map to array values.
|
|
329
|
+
*
|
|
330
|
+
* Key concepts:
|
|
331
|
+
* - The `fields` array defines the TEMPLATE (single field definition), not instances
|
|
332
|
+
* - Array items are created/removed dynamically at runtime via event bus
|
|
333
|
+
* - For primitive arrays: Use a leaf field as template → `['value1', 'value2']`
|
|
334
|
+
* - For object arrays: Use a group field as template → `[{key1: val1}, {key2: val2}]`
|
|
335
|
+
*
|
|
336
|
+
* @example
|
|
337
|
+
* // Primitive array (flat values)
|
|
338
|
+
* {
|
|
339
|
+
* key: 'tags',
|
|
340
|
+
* type: 'array',
|
|
341
|
+
* fields: [{ key: 'tag', type: 'input' }]
|
|
342
|
+
* }
|
|
343
|
+
* // Creates: { tags: ['tag1', 'tag2', 'tag3'] }
|
|
344
|
+
*
|
|
345
|
+
* @example
|
|
346
|
+
* // Object array (nested groups)
|
|
347
|
+
* {
|
|
348
|
+
* key: 'contacts',
|
|
349
|
+
* type: 'array',
|
|
350
|
+
* fields: [{
|
|
351
|
+
* type: 'group',
|
|
352
|
+
* fields: [
|
|
353
|
+
* { key: 'name', type: 'input' },
|
|
354
|
+
* { key: 'email', type: 'input' }
|
|
355
|
+
* ]
|
|
356
|
+
* }]
|
|
357
|
+
* }
|
|
358
|
+
* // Creates: { contacts: [{name: '', email: ''}, {name: '', email: ''}] }
|
|
359
|
+
*
|
|
360
|
+
* Nesting constraints: Arrays can contain rows, leaf fields, or groups (for object arrays),
|
|
361
|
+
* but NOT pages or other arrays. Runtime validation enforces these rules.
|
|
362
|
+
*/
|
|
363
|
+
interface ArrayField<TFields extends readonly ArrayAllowedChildren[] = readonly ArrayAllowedChildren[]> extends FieldDef<never> {
|
|
364
|
+
type: 'array';
|
|
365
|
+
/**
|
|
366
|
+
* Template field definition(s) that will be cloned for each array item.
|
|
367
|
+
* Typically contains a single field definition:
|
|
368
|
+
* - For flat arrays: A leaf field (input, select, checkbox, etc.)
|
|
369
|
+
* - For object arrays: A group field containing multiple child fields
|
|
370
|
+
*/
|
|
371
|
+
readonly fields: TFields;
|
|
372
|
+
/** Array fields do not have a label property **/
|
|
373
|
+
readonly label?: never;
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Type guard for ArrayField with proper type narrowing
|
|
377
|
+
*/
|
|
378
|
+
declare function isArrayField(field: FieldDef<any>): field is ArrayField;
|
|
379
|
+
|
|
380
|
+
interface ConditionalExpression {
|
|
381
|
+
/** Expression type */
|
|
382
|
+
type: 'fieldValue' | 'formValue' | 'custom' | 'javascript' | 'and' | 'or';
|
|
383
|
+
/** Field path for fieldValue type */
|
|
384
|
+
fieldPath?: string;
|
|
385
|
+
/** Comparison operator */
|
|
386
|
+
operator?: 'equals' | 'notEquals' | 'greater' | 'less' | 'greaterOrEqual' | 'lessOrEqual' | 'contains' | 'startsWith' | 'endsWith' | 'matches';
|
|
387
|
+
/** Value to compare against */
|
|
388
|
+
value?: unknown;
|
|
389
|
+
/** JavaScript expression for custom logic */
|
|
390
|
+
expression?: string;
|
|
391
|
+
/** Array of sub-conditions for 'and' and 'or' types */
|
|
392
|
+
conditions?: ConditionalExpression[];
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Logic configuration for page fields.
|
|
397
|
+
* Pages only support the 'hidden' logic type since they are layout containers,
|
|
398
|
+
* not form controls (they can't be readonly, disabled, or required).
|
|
399
|
+
*/
|
|
400
|
+
interface PageLogicConfig {
|
|
401
|
+
/** Only 'hidden' is supported for pages */
|
|
402
|
+
type: 'hidden';
|
|
403
|
+
/**
|
|
404
|
+
* Condition that determines when this page is hidden.
|
|
405
|
+
* Can be a boolean or a conditional expression evaluated against form values.
|
|
406
|
+
*/
|
|
407
|
+
condition: ConditionalExpression | boolean;
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Page field interface for creating top-level page layouts
|
|
411
|
+
* This is a special field type that contains other field definitions
|
|
412
|
+
* The page itself doesn't have a value - it's a layout container like row
|
|
413
|
+
* Pages can only be used at the top level and cannot be nested
|
|
414
|
+
* This is a programmatic field type only - users cannot customize this field type
|
|
415
|
+
*
|
|
416
|
+
* TypeScript cannot enforce field nesting rules due to circular dependency limitations.
|
|
417
|
+
* For documentation: Pages should contain rows, groups, and leaf fields, but NOT other pages.
|
|
418
|
+
* Runtime validation enforces these rules.
|
|
419
|
+
*/
|
|
420
|
+
interface PageField<TFields extends readonly PageAllowedChildren[] = PageAllowedChildren[]> extends FieldDef<never> {
|
|
421
|
+
type: 'page';
|
|
422
|
+
/** Child field definitions to render within this page */
|
|
423
|
+
readonly fields: TFields;
|
|
424
|
+
/** Page fields do not have a label property **/
|
|
425
|
+
readonly label?: never;
|
|
426
|
+
/**
|
|
427
|
+
* Logic configurations for conditional page visibility.
|
|
428
|
+
* Only 'hidden' type logic is supported for pages.
|
|
429
|
+
*
|
|
430
|
+
* @example
|
|
431
|
+
* ```typescript
|
|
432
|
+
* {
|
|
433
|
+
* key: 'businessPage',
|
|
434
|
+
* type: 'page',
|
|
435
|
+
* logic: [{
|
|
436
|
+
* type: 'hidden',
|
|
437
|
+
* condition: {
|
|
438
|
+
* type: 'fieldValue',
|
|
439
|
+
* fieldPath: 'accountType',
|
|
440
|
+
* operator: 'notEquals',
|
|
441
|
+
* value: 'business',
|
|
442
|
+
* },
|
|
443
|
+
* }],
|
|
444
|
+
* fields: [...]
|
|
445
|
+
* }
|
|
446
|
+
* ```
|
|
447
|
+
*/
|
|
448
|
+
readonly logic?: PageLogicConfig[];
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Type guard for PageField with proper type narrowing
|
|
452
|
+
*/
|
|
453
|
+
declare function isPageField(field: FieldDef<unknown>): field is PageField;
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Special form-state conditions for button disabled logic.
|
|
457
|
+
*
|
|
458
|
+
* These conditions evaluate form or page-level state rather than field values,
|
|
459
|
+
* and are primarily used for controlling button disabled states.
|
|
460
|
+
*
|
|
461
|
+
* @example
|
|
462
|
+
* ```typescript
|
|
463
|
+
* // Disable submit button when form is invalid or submitting
|
|
464
|
+
* {
|
|
465
|
+
* key: 'submit',
|
|
466
|
+
* type: 'submit',
|
|
467
|
+
* label: 'Submit',
|
|
468
|
+
* logic: [
|
|
469
|
+
* { type: 'disabled', condition: 'formInvalid' },
|
|
470
|
+
* { type: 'disabled', condition: 'formSubmitting' },
|
|
471
|
+
* ]
|
|
472
|
+
* }
|
|
473
|
+
*
|
|
474
|
+
* // Disable next button when current page is invalid
|
|
475
|
+
* nextButton({
|
|
476
|
+
* key: 'next',
|
|
477
|
+
* label: 'Next',
|
|
478
|
+
* logic: [
|
|
479
|
+
* { type: 'disabled', condition: 'pageInvalid' },
|
|
480
|
+
* ]
|
|
481
|
+
* })
|
|
482
|
+
* ```
|
|
483
|
+
*
|
|
484
|
+
* @public
|
|
485
|
+
*/
|
|
486
|
+
type FormStateCondition =
|
|
487
|
+
/** True when form.valid() === false */
|
|
488
|
+
'formInvalid'
|
|
489
|
+
/** True when form.submitting() === true */
|
|
490
|
+
| 'formSubmitting'
|
|
491
|
+
/** True when fields on the current page are invalid (for paged forms) */
|
|
492
|
+
| 'pageInvalid';
|
|
493
|
+
/**
|
|
494
|
+
* Configuration for conditional field logic.
|
|
495
|
+
*
|
|
496
|
+
* Defines how field behavior changes based on conditions.
|
|
497
|
+
* Supports hiding, disabling, making readonly, or requiring fields
|
|
498
|
+
* based on form state or field values.
|
|
499
|
+
*
|
|
500
|
+
* @example
|
|
501
|
+
* ```typescript
|
|
502
|
+
* // Hide email field when contact method is not email
|
|
503
|
+
* {
|
|
504
|
+
* type: 'hidden',
|
|
505
|
+
* condition: {
|
|
506
|
+
* type: 'fieldValue',
|
|
507
|
+
* fieldPath: 'contactMethod',
|
|
508
|
+
* operator: 'notEquals',
|
|
509
|
+
* value: 'email'
|
|
510
|
+
* }
|
|
511
|
+
* }
|
|
512
|
+
*
|
|
513
|
+
* // Disable button when form is submitting
|
|
514
|
+
* {
|
|
515
|
+
* type: 'disabled',
|
|
516
|
+
* condition: 'formSubmitting'
|
|
517
|
+
* }
|
|
518
|
+
* ```
|
|
519
|
+
*
|
|
520
|
+
* @public
|
|
521
|
+
*/
|
|
522
|
+
interface LogicConfig {
|
|
523
|
+
/**
|
|
524
|
+
* Logic type identifier.
|
|
525
|
+
*
|
|
526
|
+
* - `hidden`: Hide the field from view (still participates in form state)
|
|
527
|
+
* - `readonly`: Make the field read-only
|
|
528
|
+
* - `disabled`: Disable user interaction
|
|
529
|
+
* - `required`: Make the field required
|
|
530
|
+
*/
|
|
531
|
+
type: 'hidden' | 'readonly' | 'disabled' | 'required';
|
|
532
|
+
/**
|
|
533
|
+
* Condition that determines when this logic applies.
|
|
534
|
+
*
|
|
535
|
+
* Can be:
|
|
536
|
+
* - `boolean`: Static value (always applies or never applies)
|
|
537
|
+
* - `ConditionalExpression`: Expression evaluated against field/form values
|
|
538
|
+
* - `FormStateCondition`: Special form/page state check (for buttons)
|
|
539
|
+
*
|
|
540
|
+
* @example
|
|
541
|
+
* ```typescript
|
|
542
|
+
* // Static condition
|
|
543
|
+
* condition: true
|
|
544
|
+
*
|
|
545
|
+
* // Field value condition
|
|
546
|
+
* condition: {
|
|
547
|
+
* type: 'fieldValue',
|
|
548
|
+
* fieldPath: 'status',
|
|
549
|
+
* operator: 'equals',
|
|
550
|
+
* value: 'locked'
|
|
551
|
+
* }
|
|
552
|
+
*
|
|
553
|
+
* // Form state condition (for buttons)
|
|
554
|
+
* condition: 'formSubmitting'
|
|
555
|
+
* ```
|
|
556
|
+
*/
|
|
557
|
+
condition: ConditionalExpression | boolean | FormStateCondition;
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Type guard to check if a condition is a FormStateCondition.
|
|
561
|
+
*
|
|
562
|
+
* @param condition - The condition to check
|
|
563
|
+
* @returns true if the condition is a FormStateCondition
|
|
564
|
+
*
|
|
565
|
+
* @public
|
|
566
|
+
*/
|
|
567
|
+
declare function isFormStateCondition(condition: LogicConfig['condition']): condition is FormStateCondition;
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* Text element type for rendering different HTML text elements
|
|
571
|
+
*/
|
|
572
|
+
type TextElementType = 'p' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'span';
|
|
573
|
+
/**
|
|
574
|
+
* Properties for text field configuration
|
|
575
|
+
*/
|
|
576
|
+
type TextProps = {
|
|
577
|
+
/** The HTML element type to render */
|
|
578
|
+
elementType: TextElementType;
|
|
579
|
+
};
|
|
580
|
+
/**
|
|
581
|
+
* Text field definition for displaying translatable text content
|
|
582
|
+
*/
|
|
583
|
+
interface TextField extends FieldDef<TextProps> {
|
|
584
|
+
type: 'text';
|
|
585
|
+
/** Logic rules for conditional visibility (hidden, readonly, disabled) */
|
|
586
|
+
readonly logic?: LogicConfig[];
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/**
|
|
590
|
+
* Container fields registry - augment this interface to add custom container fields
|
|
591
|
+
*
|
|
592
|
+
* @example
|
|
593
|
+
* ```typescript
|
|
594
|
+
* declare module '@ng-forge/dynamic-forms' {
|
|
595
|
+
* interface FieldRegistryContainers {
|
|
596
|
+
* 'my-container': MyContainerFieldDef;
|
|
597
|
+
* }
|
|
598
|
+
* }
|
|
599
|
+
* ```
|
|
600
|
+
*/
|
|
601
|
+
interface FieldRegistryContainers {
|
|
602
|
+
page: PageField;
|
|
603
|
+
row: RowField;
|
|
604
|
+
group: GroupField;
|
|
605
|
+
array: ArrayField;
|
|
606
|
+
}
|
|
607
|
+
/**
|
|
608
|
+
* Leaf fields registry - augment this interface to add custom leaf fields
|
|
609
|
+
*
|
|
610
|
+
* @example
|
|
611
|
+
* ```typescript
|
|
612
|
+
* declare module '@ng-forge/dynamic-forms' {
|
|
613
|
+
* interface FieldRegistryLeaves {
|
|
614
|
+
* 'my-input': MyInputFieldDef;
|
|
615
|
+
* }
|
|
616
|
+
* }
|
|
617
|
+
* ```
|
|
618
|
+
*/
|
|
619
|
+
interface FieldRegistryLeaves {
|
|
620
|
+
text: TextField;
|
|
621
|
+
}
|
|
622
|
+
/**
|
|
623
|
+
* Global interface for dynamic form field definitions with categorization
|
|
624
|
+
* This interface combines containers and leaves from their respective registries
|
|
625
|
+
*
|
|
626
|
+
* Container fields: Layout fields that contain other fields (page, row, group, array)
|
|
627
|
+
* Leaf fields: Fields that can hold values or display content (input, text, etc.)
|
|
628
|
+
*
|
|
629
|
+
* To add custom fields, augment FieldRegistryContainers or FieldRegistryLeaves
|
|
630
|
+
*/
|
|
631
|
+
interface DynamicFormFieldRegistry {
|
|
632
|
+
/**
|
|
633
|
+
* Container fields that hold other fields (no value, have children)
|
|
634
|
+
*/
|
|
635
|
+
containers: FieldRegistryContainers;
|
|
636
|
+
/**
|
|
637
|
+
* Leaf fields that have values or display content
|
|
638
|
+
*/
|
|
639
|
+
leaves: FieldRegistryLeaves;
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* Union type of all registered container field definitions
|
|
643
|
+
*/
|
|
644
|
+
type ContainerFieldTypes = DynamicFormFieldRegistry['containers'][keyof DynamicFormFieldRegistry['containers']];
|
|
645
|
+
/**
|
|
646
|
+
* Union type of all registered leaf field definitions
|
|
647
|
+
*/
|
|
648
|
+
type LeafFieldTypes = DynamicFormFieldRegistry['leaves'][keyof DynamicFormFieldRegistry['leaves']];
|
|
649
|
+
/**
|
|
650
|
+
* Union type of all registered field definitions
|
|
651
|
+
*/
|
|
652
|
+
type RegisteredFieldTypes = ContainerFieldTypes | LeafFieldTypes;
|
|
653
|
+
/**
|
|
654
|
+
* Extract field types that are available in the registry
|
|
655
|
+
*/
|
|
656
|
+
type AvailableFieldTypes = keyof DynamicFormFieldRegistry['containers'] | keyof DynamicFormFieldRegistry['leaves'];
|
|
657
|
+
|
|
658
|
+
/**
|
|
659
|
+
* Helper type to convert union to intersection
|
|
660
|
+
* Note: `any` is necessary for distributive conditional types
|
|
661
|
+
*/
|
|
662
|
+
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
|
|
663
|
+
/**
|
|
664
|
+
* Depth counter for recursion limiting (prevents infinite type instantiation)
|
|
665
|
+
*/
|
|
666
|
+
type Depth = [never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
|
667
|
+
/**
|
|
668
|
+
* Process a single field and determine its contribution to the form value type
|
|
669
|
+
* Handles:
|
|
670
|
+
* - Page fields: flatten children (valueHandling: 'flatten')
|
|
671
|
+
* - Row fields: flatten children (valueHandling: 'flatten')
|
|
672
|
+
* - Group fields: nest children under group key (valueHandling: 'include')
|
|
673
|
+
* - Text fields: exclude from values (valueHandling: 'exclude')
|
|
674
|
+
* - Value fields: include with their key and value type
|
|
675
|
+
*/
|
|
676
|
+
type ProcessField<T, D extends number = 5> = [D] extends [never] ? Record<string, unknown> : T extends {
|
|
677
|
+
type: 'page';
|
|
678
|
+
fields: infer TFields;
|
|
679
|
+
} ? TFields extends RegisteredFieldTypes[] ? InferFormValueWithDepth<TFields, Depth[D]> : never : T extends {
|
|
680
|
+
type: 'row';
|
|
681
|
+
fields: infer TFields;
|
|
682
|
+
} ? TFields extends RegisteredFieldTypes[] ? InferFormValueWithDepth<TFields, Depth[D]> : never : T extends {
|
|
683
|
+
type: 'group';
|
|
684
|
+
key: infer K;
|
|
685
|
+
fields: infer TFields;
|
|
686
|
+
} ? K extends string ? TFields extends RegisteredFieldTypes[] ? {
|
|
687
|
+
[P in K]: InferFormValueWithDepth<TFields, Depth[D]>;
|
|
688
|
+
} : {
|
|
689
|
+
[P in K]: Record<string, unknown>;
|
|
690
|
+
} : never : T extends {
|
|
691
|
+
type: 'text';
|
|
692
|
+
} ? never : T extends {
|
|
693
|
+
key: infer K;
|
|
694
|
+
value: infer V;
|
|
695
|
+
required: true;
|
|
696
|
+
} ? K extends string ? {
|
|
697
|
+
[P in K]: V;
|
|
698
|
+
} : never : T extends {
|
|
699
|
+
key: infer K;
|
|
700
|
+
value: infer V;
|
|
701
|
+
} ? K extends string ? {
|
|
702
|
+
[P in K]: V | undefined;
|
|
703
|
+
} : never : never;
|
|
704
|
+
/**
|
|
705
|
+
* Internal helper with depth tracking
|
|
706
|
+
*/
|
|
707
|
+
type InferFormValueWithDepth<T extends RegisteredFieldTypes[], D extends number = 5> = UnionToIntersection<ProcessField<T[number], D>>;
|
|
708
|
+
/**
|
|
709
|
+
* Infer form value type from an array of field definitions
|
|
710
|
+
* Recursively processes nested structures and merges the results
|
|
711
|
+
* Limited to 5 levels of nesting to prevent infinite type instantiation
|
|
712
|
+
*
|
|
713
|
+
* @example
|
|
714
|
+
* ```typescript
|
|
715
|
+
* const fields = [
|
|
716
|
+
* { type: 'input', key: 'email', value: '', required: true },
|
|
717
|
+
* { type: 'group', key: 'address', fields: [
|
|
718
|
+
* { type: 'input', key: 'street', value: '' },
|
|
719
|
+
* { type: 'input', key: 'city', value: '' }
|
|
720
|
+
* ]}
|
|
721
|
+
* ] as const;
|
|
722
|
+
*
|
|
723
|
+
* type FormValue = InferFormValue<typeof fields>;
|
|
724
|
+
* // Result: { email: string; address: { street?: string; city?: string } }
|
|
725
|
+
* ```
|
|
726
|
+
*/
|
|
727
|
+
type InferFormValue$1<T extends RegisteredFieldTypes[]> = InferFormValueWithDepth<T, 5>;
|
|
728
|
+
|
|
729
|
+
/**
|
|
730
|
+
* Base configuration shared by all validators
|
|
731
|
+
*/
|
|
732
|
+
interface BaseValidatorConfig {
|
|
733
|
+
/** Conditional logic for when validator applies */
|
|
734
|
+
when?: ConditionalExpression;
|
|
735
|
+
}
|
|
736
|
+
/**
|
|
737
|
+
* Built-in validator configuration (required, email, min, max, etc.)
|
|
738
|
+
*/
|
|
739
|
+
interface BuiltInValidatorConfig extends BaseValidatorConfig {
|
|
740
|
+
/** Validator type identifier */
|
|
741
|
+
type: 'required' | 'email' | 'min' | 'max' | 'minLength' | 'maxLength' | 'pattern';
|
|
742
|
+
/** Static value for the validator (e.g., min value, pattern) */
|
|
743
|
+
value?: number | string | RegExp;
|
|
744
|
+
/** Dynamic value expression that evaluates to validator parameter */
|
|
745
|
+
expression?: string;
|
|
746
|
+
}
|
|
747
|
+
/**
|
|
748
|
+
* Custom validator configuration using Angular's public FieldContext API
|
|
749
|
+
* Returns ValidationError | ValidationError[] | null synchronously
|
|
750
|
+
*
|
|
751
|
+
* Supports two patterns:
|
|
752
|
+
* 1. Function-based: { type: 'custom', functionName: 'myValidator' }
|
|
753
|
+
* 2. Expression-based: { type: 'custom', expression: 'fieldValue === formValue.password', kind: 'passwordMismatch' }
|
|
754
|
+
*/
|
|
755
|
+
interface CustomValidatorConfig extends BaseValidatorConfig {
|
|
756
|
+
/** Validator type identifier */
|
|
757
|
+
type: 'custom';
|
|
758
|
+
/** Name of registered validator function (function-based pattern) */
|
|
759
|
+
functionName?: string;
|
|
760
|
+
/** Optional parameters to pass to validator function */
|
|
761
|
+
params?: Record<string, unknown>;
|
|
762
|
+
/** JavaScript expression to evaluate (expression-based pattern) */
|
|
763
|
+
expression?: string;
|
|
764
|
+
/** Error kind for expression-based validators - links to validationMessages */
|
|
765
|
+
kind?: string;
|
|
766
|
+
/**
|
|
767
|
+
* Parameters to include in error object for message interpolation (expression-based pattern)
|
|
768
|
+
* Map of parameter names to expressions that will be evaluated in the same context as the validation expression
|
|
769
|
+
*
|
|
770
|
+
* @example
|
|
771
|
+
* errorParams: {
|
|
772
|
+
* minValue: 'formValue.minValue',
|
|
773
|
+
* maxValue: 'formValue.maxValue'
|
|
774
|
+
* }
|
|
775
|
+
* // Allows message template: "Value must be between {{minValue}} and {{maxValue}}"
|
|
776
|
+
*/
|
|
777
|
+
errorParams?: Record<string, string>;
|
|
778
|
+
}
|
|
779
|
+
/**
|
|
780
|
+
* Async custom validator configuration using Angular's validateAsync API
|
|
781
|
+
* Returns Observable<ValidationError | ValidationError[] | null>
|
|
782
|
+
*/
|
|
783
|
+
interface AsyncValidatorConfig extends BaseValidatorConfig {
|
|
784
|
+
/** Validator type identifier */
|
|
785
|
+
type: 'customAsync';
|
|
786
|
+
/** Name of registered async validator function */
|
|
787
|
+
functionName: string;
|
|
788
|
+
/** Optional parameters to pass to validator function */
|
|
789
|
+
params?: Record<string, unknown>;
|
|
790
|
+
}
|
|
791
|
+
/**
|
|
792
|
+
* HTTP validator configuration using Angular's validateHttp API
|
|
793
|
+
* Provides optimized HTTP validation with automatic request cancellation
|
|
794
|
+
*/
|
|
795
|
+
interface HttpValidatorConfig extends BaseValidatorConfig {
|
|
796
|
+
/** Validator type identifier */
|
|
797
|
+
type: 'customHttp';
|
|
798
|
+
/** Name of registered HTTP validator configuration */
|
|
799
|
+
functionName: string;
|
|
800
|
+
/** Optional parameters to pass to HTTP validator */
|
|
801
|
+
params?: Record<string, unknown>;
|
|
802
|
+
}
|
|
803
|
+
/**
|
|
804
|
+
* Configuration for signal forms validator functions that can be serialized from API
|
|
805
|
+
* Discriminated union type for type-safe validator configuration
|
|
806
|
+
*/
|
|
807
|
+
type ValidatorConfig = BuiltInValidatorConfig | CustomValidatorConfig | AsyncValidatorConfig | HttpValidatorConfig;
|
|
808
|
+
|
|
809
|
+
/**
|
|
810
|
+
* Configuration for applying predefined schemas
|
|
811
|
+
*/
|
|
812
|
+
interface SchemaApplicationConfig {
|
|
813
|
+
/** Schema application type */
|
|
814
|
+
type: 'apply' | 'applyWhen' | 'applyWhenValue' | 'applyEach';
|
|
815
|
+
/** Schema identifier or inline schema definition */
|
|
816
|
+
schema: string | SchemaDefinition;
|
|
817
|
+
/** Condition for conditional application */
|
|
818
|
+
condition?: ConditionalExpression;
|
|
819
|
+
/** Type predicate for applyWhenValue */
|
|
820
|
+
typePredicate?: string;
|
|
821
|
+
}
|
|
822
|
+
/**
|
|
823
|
+
* Reusable schema definition that can be referenced by name
|
|
824
|
+
*/
|
|
825
|
+
interface SchemaDefinition {
|
|
826
|
+
/** Unique schema identifier */
|
|
827
|
+
name: string;
|
|
828
|
+
/** Schema description */
|
|
829
|
+
description?: string;
|
|
830
|
+
/** Field path pattern this schema applies to */
|
|
831
|
+
pathPattern?: string;
|
|
832
|
+
/** Validators to apply */
|
|
833
|
+
validators?: ValidatorConfig[];
|
|
834
|
+
/** Logic rules to apply */
|
|
835
|
+
logic?: LogicConfig[];
|
|
836
|
+
/** Nested schema applications */
|
|
837
|
+
subSchemas?: SchemaApplicationConfig[];
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
/**
|
|
841
|
+
* Custom validator function signature using Angular's public FieldContext API
|
|
842
|
+
*
|
|
843
|
+
* Takes FieldContext (full Angular context) and optional params, returns validation error(s) or null.
|
|
844
|
+
* Provides access to field state, form hierarchy, and other fields for validation logic.
|
|
845
|
+
*
|
|
846
|
+
* **Use FieldContext public APIs to access:**
|
|
847
|
+
* - Current field value: `ctx.value()`
|
|
848
|
+
* - Field state: `ctx.state` (errors, touched, dirty, etc.)
|
|
849
|
+
* - Other field values: `ctx.valueOf(path)` where path is a FieldPath
|
|
850
|
+
* - Other field states: `ctx.stateOf(path)`
|
|
851
|
+
* - Other fields: `ctx.fieldOf(path)`
|
|
852
|
+
* - Current field tree: `ctx.field`
|
|
853
|
+
*
|
|
854
|
+
* **Return Types:**
|
|
855
|
+
* - Single error: `{ kind: 'errorKind' }` for field-level validation
|
|
856
|
+
* - Multiple errors: `[{ kind: 'error1' }, { kind: 'error2' }]` for cross-field validation
|
|
857
|
+
* - No error: `null` when validation passes
|
|
858
|
+
*
|
|
859
|
+
* **Best Practice - Return Only Error Kind:**
|
|
860
|
+
* Validators should focus on validation logic, not presentation.
|
|
861
|
+
* Return just the error `kind` and configure messages at field level for i18n support.
|
|
862
|
+
*
|
|
863
|
+
* @example Single Field Validation
|
|
864
|
+
* ```typescript
|
|
865
|
+
* const noSpaces: CustomValidator<string> = (ctx) => {
|
|
866
|
+
* const value = ctx.value();
|
|
867
|
+
* if (typeof value === 'string' && value.includes(' ')) {
|
|
868
|
+
* return { kind: 'noSpaces' };
|
|
869
|
+
* }
|
|
870
|
+
* return null;
|
|
871
|
+
* };
|
|
872
|
+
*
|
|
873
|
+
* // Field configuration
|
|
874
|
+
* {
|
|
875
|
+
* key: 'username',
|
|
876
|
+
* validators: [{ type: 'custom', functionName: 'noSpaces' }],
|
|
877
|
+
* validationMessages: {
|
|
878
|
+
* noSpaces: 'Spaces are not allowed'
|
|
879
|
+
* }
|
|
880
|
+
* }
|
|
881
|
+
* ```
|
|
882
|
+
*
|
|
883
|
+
* @example Cross-Field Validation with valueOf()
|
|
884
|
+
* ```typescript
|
|
885
|
+
* // Compare two fields using public API
|
|
886
|
+
* const lessThan: CustomValidator<number> = (ctx, params) => {
|
|
887
|
+
* const value = ctx.value();
|
|
888
|
+
* const compareToPath = params?.field as string;
|
|
889
|
+
*
|
|
890
|
+
* // Use valueOf() to access other field - public API!
|
|
891
|
+
* const otherValue = ctx.valueOf(compareToPath as any);
|
|
892
|
+
*
|
|
893
|
+
* if (otherValue !== undefined && value >= otherValue) {
|
|
894
|
+
* return { kind: 'notLessThan' };
|
|
895
|
+
* }
|
|
896
|
+
* return null;
|
|
897
|
+
* };
|
|
898
|
+
* ```
|
|
899
|
+
*
|
|
900
|
+
* @example Multiple Errors (Cross-Field Validation)
|
|
901
|
+
* ```typescript
|
|
902
|
+
* const validateDateRange: CustomValidator = (ctx) => {
|
|
903
|
+
* const errors: ValidationError[] = [];
|
|
904
|
+
*
|
|
905
|
+
* const startDate = ctx.valueOf('startDate' as any);
|
|
906
|
+
* const endDate = ctx.valueOf('endDate' as any);
|
|
907
|
+
*
|
|
908
|
+
* if (!startDate) errors.push({ kind: 'startDateRequired' });
|
|
909
|
+
* if (!endDate) errors.push({ kind: 'endDateRequired' });
|
|
910
|
+
* if (startDate && endDate && startDate > endDate) {
|
|
911
|
+
* errors.push({ kind: 'invalidDateRange' });
|
|
912
|
+
* }
|
|
913
|
+
*
|
|
914
|
+
* return errors.length > 0 ? errors : null;
|
|
915
|
+
* };
|
|
916
|
+
* ```
|
|
917
|
+
*
|
|
918
|
+
* **Message Resolution Priority:**
|
|
919
|
+
* 1. Field-level `validationMessages[kind]` (highest priority - per-field customization)
|
|
920
|
+
* 2. Form-level `defaultValidationMessages[kind]` (fallback for common messages)
|
|
921
|
+
* 3. **No message configured = Warning logged + error NOT displayed**
|
|
922
|
+
*
|
|
923
|
+
* **Important:** Validator-returned messages are NOT used. All messages MUST be explicitly configured.
|
|
924
|
+
*
|
|
925
|
+
* @template TValue The type of value stored in the field being validated
|
|
926
|
+
*/
|
|
927
|
+
type CustomValidator<TValue = unknown> = (ctx: FieldContext<TValue>, params?: Record<string, unknown>) => ValidationError$1 | ValidationError$1[] | null;
|
|
928
|
+
/**
|
|
929
|
+
* Async custom validator configuration using Angular's resource-based API
|
|
930
|
+
*
|
|
931
|
+
* Angular's validateAsync uses the resource API for async validation,
|
|
932
|
+
* which provides better integration with Signal Forms lifecycle management.
|
|
933
|
+
*
|
|
934
|
+
* **Structure:**
|
|
935
|
+
* - `params`: Function that computes params from field context
|
|
936
|
+
* - `factory`: Function that creates a ResourceRef from params signal
|
|
937
|
+
* - `onSuccess`: Maps successful resource result to validation errors
|
|
938
|
+
* - `onError`: Optional handler for resource errors (network failures, etc.)
|
|
939
|
+
*
|
|
940
|
+
* **Use Cases:**
|
|
941
|
+
* - Database lookups via services with resource API
|
|
942
|
+
* - Complex async business logic with Angular resources
|
|
943
|
+
* - Any async operation using Angular's resource pattern
|
|
944
|
+
*
|
|
945
|
+
* **Note:** For HTTP validation, prefer `HttpCustomValidator` which provides
|
|
946
|
+
* a simpler API specifically designed for HTTP requests.
|
|
947
|
+
*
|
|
948
|
+
* @example Database Lookup with Resource
|
|
949
|
+
* ```typescript
|
|
950
|
+
* const checkUsernameAvailable: AsyncCustomValidator<string> = {
|
|
951
|
+
* params: (ctx) => ({ username: ctx.value() }),
|
|
952
|
+
* factory: (params) => {
|
|
953
|
+
* const injector = inject(Injector);
|
|
954
|
+
* return resource({
|
|
955
|
+
* request: () => params(),
|
|
956
|
+
* loader: ({ request }) => {
|
|
957
|
+
* if (!request?.username) return null;
|
|
958
|
+
* const service = injector.get(UserService);
|
|
959
|
+
* return firstValueFrom(service.checkAvailability(request.username));
|
|
960
|
+
* }
|
|
961
|
+
* });
|
|
962
|
+
* },
|
|
963
|
+
* onSuccess: (result, ctx) => {
|
|
964
|
+
* if (!result) return null;
|
|
965
|
+
* return result.available ? null : { kind: 'usernameTaken' };
|
|
966
|
+
* },
|
|
967
|
+
* onError: (error, ctx) => {
|
|
968
|
+
* console.error('Availability check failed:', error);
|
|
969
|
+
* return null; // Don't block form on network errors
|
|
970
|
+
* }
|
|
971
|
+
* };
|
|
972
|
+
* ```
|
|
973
|
+
*
|
|
974
|
+
* @template TValue The type of value stored in the field being validated
|
|
975
|
+
* @template TParams The type of params passed to the resource
|
|
976
|
+
* @template TResult The type of result returned by the resource
|
|
977
|
+
*/
|
|
978
|
+
interface AsyncCustomValidator<TValue = unknown, TParams = unknown, TResult = unknown> {
|
|
979
|
+
/**
|
|
980
|
+
* Function that receives field context and returns resource params.
|
|
981
|
+
* Params will be tracked as a signal and trigger resource reload when changed.
|
|
982
|
+
*/
|
|
983
|
+
readonly params: (ctx: FieldContext<TValue>, config?: Record<string, unknown>) => TParams;
|
|
984
|
+
/**
|
|
985
|
+
* Function that creates a ResourceRef from the params signal.
|
|
986
|
+
* The resource will be automatically managed by Angular's lifecycle.
|
|
987
|
+
*/
|
|
988
|
+
readonly factory: (params: Signal<TParams | undefined>) => ResourceRef<TResult | undefined>;
|
|
989
|
+
/**
|
|
990
|
+
* Optional function to map successful resource result to validation errors.
|
|
991
|
+
* Return null if validation passes, or ValidationError/ValidationError[] if validation fails.
|
|
992
|
+
*/
|
|
993
|
+
readonly onSuccess?: (result: TResult, ctx: FieldContext<TValue>) => TreeValidationResult;
|
|
994
|
+
/**
|
|
995
|
+
* Optional error handler for resource errors (network failures, etc.).
|
|
996
|
+
* Return null to ignore the error, or ValidationError to display it to the user.
|
|
997
|
+
*/
|
|
998
|
+
readonly onError?: (error: unknown, ctx: FieldContext<TValue>) => ValidationError$1 | ValidationError$1[] | null;
|
|
999
|
+
}
|
|
1000
|
+
/**
|
|
1001
|
+
* HTTP request configuration for validateHttp API
|
|
1002
|
+
*/
|
|
1003
|
+
interface HttpResourceRequest {
|
|
1004
|
+
url: string;
|
|
1005
|
+
method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
1006
|
+
body?: any;
|
|
1007
|
+
headers?: Record<string, string | string[]>;
|
|
1008
|
+
}
|
|
1009
|
+
/**
|
|
1010
|
+
* HTTP-based validator configuration for Angular's validateHttp API
|
|
1011
|
+
*
|
|
1012
|
+
* Angular's validateHttp provides optimized HTTP validation with automatic
|
|
1013
|
+
* request cancellation and integration with the resource API.
|
|
1014
|
+
*
|
|
1015
|
+
* **Benefits:**
|
|
1016
|
+
* - Automatic request cancellation when field value changes
|
|
1017
|
+
* - Built-in integration with Angular's resource management
|
|
1018
|
+
* - Simpler than AsyncCustomValidator for HTTP use cases
|
|
1019
|
+
*
|
|
1020
|
+
* **Structure:**
|
|
1021
|
+
* - `request`: Function that returns URL string or HttpResourceRequest
|
|
1022
|
+
* - `onSuccess`: REQUIRED - Maps HTTP response to validation errors (inverted logic!)
|
|
1023
|
+
* - `onError`: Optional handler for HTTP errors (network failures, 4xx/5xx)
|
|
1024
|
+
*
|
|
1025
|
+
* **Important:** `onSuccess` uses inverted logic - it maps SUCCESSFUL HTTP responses
|
|
1026
|
+
* to validation errors. For example, if the API returns `{ available: false }`,
|
|
1027
|
+
* your `onSuccess` should return `{ kind: 'usernameTaken' }`.
|
|
1028
|
+
*
|
|
1029
|
+
* @example Username Availability Check (GET)
|
|
1030
|
+
* ```typescript
|
|
1031
|
+
* const checkUsername: HttpCustomValidator<string> = {
|
|
1032
|
+
* request: (ctx) => {
|
|
1033
|
+
* const username = ctx.value();
|
|
1034
|
+
* if (!username) return undefined; // Skip validation if empty
|
|
1035
|
+
* return `/api/users/check-username?username=${encodeURIComponent(username)}`;
|
|
1036
|
+
* },
|
|
1037
|
+
* onSuccess: (response, ctx) => {
|
|
1038
|
+
* // Inverted logic: successful response may indicate validation failure
|
|
1039
|
+
* return response.available ? null : { kind: 'usernameTaken' };
|
|
1040
|
+
* },
|
|
1041
|
+
* onError: (error, ctx) => {
|
|
1042
|
+
* console.error('Availability check failed:', error);
|
|
1043
|
+
* return null; // Don't block form on network errors
|
|
1044
|
+
* }
|
|
1045
|
+
* };
|
|
1046
|
+
* ```
|
|
1047
|
+
*
|
|
1048
|
+
* @example Address Validation (POST with Body)
|
|
1049
|
+
* ```typescript
|
|
1050
|
+
* const validateAddress: HttpCustomValidator = {
|
|
1051
|
+
* request: (ctx) => {
|
|
1052
|
+
* const zipCode = ctx.value();
|
|
1053
|
+
* if (!zipCode) return undefined;
|
|
1054
|
+
*
|
|
1055
|
+
* return {
|
|
1056
|
+
* url: '/api/validate-address',
|
|
1057
|
+
* method: 'POST',
|
|
1058
|
+
* body: {
|
|
1059
|
+
* street: ctx.valueOf('street' as any),
|
|
1060
|
+
* city: ctx.valueOf('city' as any),
|
|
1061
|
+
* zipCode: zipCode
|
|
1062
|
+
* },
|
|
1063
|
+
* headers: { 'Content-Type': 'application/json' }
|
|
1064
|
+
* };
|
|
1065
|
+
* },
|
|
1066
|
+
* onSuccess: (response) => {
|
|
1067
|
+
* return response.valid ? null : { kind: 'invalidAddress' };
|
|
1068
|
+
* }
|
|
1069
|
+
* };
|
|
1070
|
+
* ```
|
|
1071
|
+
*
|
|
1072
|
+
* @template TValue The type of value stored in the field being validated
|
|
1073
|
+
* @template TResult The type of HTTP response
|
|
1074
|
+
*/
|
|
1075
|
+
interface HttpCustomValidator<TValue = unknown, TResult = unknown> {
|
|
1076
|
+
/**
|
|
1077
|
+
* Function that returns the HTTP request configuration.
|
|
1078
|
+
* Can return:
|
|
1079
|
+
* - `undefined` to skip validation (e.g., if field is empty)
|
|
1080
|
+
* - `string` for simple GET requests (just the URL)
|
|
1081
|
+
* - `HttpResourceRequest` for full control (method, body, headers)
|
|
1082
|
+
*/
|
|
1083
|
+
readonly request: (ctx: FieldContext<TValue>, config?: Record<string, unknown>) => string | HttpResourceRequest | undefined;
|
|
1084
|
+
/**
|
|
1085
|
+
* REQUIRED function to map successful HTTP response to validation errors.
|
|
1086
|
+
*
|
|
1087
|
+
* **Inverted Logic:** This is called on successful HTTP responses.
|
|
1088
|
+
* Return null if validation passes, or ValidationError/ValidationError[] if validation fails.
|
|
1089
|
+
*
|
|
1090
|
+
* Example: API returns `{ available: false }` → return `{ kind: 'usernameTaken' }`
|
|
1091
|
+
*/
|
|
1092
|
+
readonly onSuccess: (result: TResult, ctx: FieldContext<TValue>) => TreeValidationResult;
|
|
1093
|
+
/**
|
|
1094
|
+
* Optional error handler for HTTP errors (network failures, 4xx/5xx status codes).
|
|
1095
|
+
* Return null to ignore the error, or ValidationError to display it to the user.
|
|
1096
|
+
*
|
|
1097
|
+
* Common pattern: Log the error and return null to avoid blocking form submission
|
|
1098
|
+
* on network issues.
|
|
1099
|
+
*/
|
|
1100
|
+
readonly onError?: (error: unknown, ctx: FieldContext<TValue>) => ValidationError$1 | ValidationError$1[] | null;
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
interface EvaluationContext<TValue = unknown> {
|
|
1104
|
+
/** Current field value */
|
|
1105
|
+
fieldValue: TValue;
|
|
1106
|
+
/** Complete form value */
|
|
1107
|
+
formValue: Record<string, unknown>;
|
|
1108
|
+
/** Field path for relative references */
|
|
1109
|
+
fieldPath: string;
|
|
1110
|
+
/** Custom evaluation functions */
|
|
1111
|
+
customFunctions?: Record<string, (context: EvaluationContext) => unknown>;
|
|
1112
|
+
/** Allow additional properties for flexible expression evaluation */
|
|
1113
|
+
[key: string]: unknown;
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
/**
|
|
1117
|
+
* Custom function signature for conditional expressions
|
|
1118
|
+
*
|
|
1119
|
+
* Custom functions are used for conditional logic in:
|
|
1120
|
+
* - `when` conditions (field visibility, conditional validation)
|
|
1121
|
+
* - `readonly` logic (dynamic readonly state)
|
|
1122
|
+
* - `disabled` logic (dynamic disabled state)
|
|
1123
|
+
* - Dynamic value calculations
|
|
1124
|
+
*
|
|
1125
|
+
* Unlike validators, custom functions:
|
|
1126
|
+
* - Return any value (typically boolean for conditions, but could be strings, numbers, etc.)
|
|
1127
|
+
* - Do NOT return ValidationError objects
|
|
1128
|
+
* - Receive EvaluationContext (field values) not FieldContext (field state)
|
|
1129
|
+
*
|
|
1130
|
+
* @example
|
|
1131
|
+
* ```typescript
|
|
1132
|
+
* // Boolean condition function
|
|
1133
|
+
* const isAdult: CustomFunction = (ctx) => {
|
|
1134
|
+
* return ctx.age >= 18;
|
|
1135
|
+
* };
|
|
1136
|
+
*
|
|
1137
|
+
* // Used in field configuration:
|
|
1138
|
+
* {
|
|
1139
|
+
* key: 'alcoholPreference',
|
|
1140
|
+
* type: 'select',
|
|
1141
|
+
* when: { function: 'isAdult' } // Only show if isAdult returns true
|
|
1142
|
+
* }
|
|
1143
|
+
* ```
|
|
1144
|
+
*
|
|
1145
|
+
* @example
|
|
1146
|
+
* ```typescript
|
|
1147
|
+
* // Calculation function
|
|
1148
|
+
* const calculateDiscount: CustomFunction = (ctx) => {
|
|
1149
|
+
* const price = ctx.price || 0;
|
|
1150
|
+
* const isVip = ctx.isVip || false;
|
|
1151
|
+
* return isVip ? price * 0.8 : price * 0.9;
|
|
1152
|
+
* };
|
|
1153
|
+
* ```
|
|
1154
|
+
*/
|
|
1155
|
+
type CustomFunction = (context: EvaluationContext) => unknown;
|
|
1156
|
+
|
|
1157
|
+
/**
|
|
1158
|
+
* The result type for submission actions.
|
|
1159
|
+
* Can be either a Promise or an Observable.
|
|
1160
|
+
*
|
|
1161
|
+
* For success: return `undefined`, `null`, `void`, or any non-TreeValidationResult value.
|
|
1162
|
+
* For server errors: return `TreeValidationResult` (array of field errors).
|
|
1163
|
+
*/
|
|
1164
|
+
type SubmissionActionResult = Promise<TreeValidationResult> | Observable<TreeValidationResult | unknown>;
|
|
1165
|
+
/**
|
|
1166
|
+
* Configuration for form submission handling.
|
|
1167
|
+
*
|
|
1168
|
+
* When provided, enables integration with Angular Signal Forms' native `submit()` function.
|
|
1169
|
+
* The submission mechanism is **optional** - you can still handle submission manually
|
|
1170
|
+
* via the `(submitted)` output if you prefer.
|
|
1171
|
+
*
|
|
1172
|
+
* Supports both Promise-based and Observable-based submission actions. When an Observable
|
|
1173
|
+
* is returned, it will be automatically converted using `firstValueFrom`.
|
|
1174
|
+
*
|
|
1175
|
+
* @example
|
|
1176
|
+
* ```typescript
|
|
1177
|
+
* // Using Observable (recommended for HTTP calls)
|
|
1178
|
+
* // Simply return the HTTP observable - no need to map the result
|
|
1179
|
+
* const config: FormConfig = {
|
|
1180
|
+
* fields: [...],
|
|
1181
|
+
* submission: {
|
|
1182
|
+
* action: (form) => this.http.post('/api/submit', form().value())
|
|
1183
|
+
* }
|
|
1184
|
+
* };
|
|
1185
|
+
*
|
|
1186
|
+
* // With error handling for server validation errors
|
|
1187
|
+
* const config: FormConfig = {
|
|
1188
|
+
* fields: [...],
|
|
1189
|
+
* submission: {
|
|
1190
|
+
* action: (form) => {
|
|
1191
|
+
* return this.http.post('/api/submit', form().value()).pipe(
|
|
1192
|
+
* catchError((error) => {
|
|
1193
|
+
* if (error.error?.code === 'EMAIL_EXISTS') {
|
|
1194
|
+
* return of([{ field: form.email, error: { kind: 'server', message: 'Email already exists' }}]);
|
|
1195
|
+
* }
|
|
1196
|
+
* throw error;
|
|
1197
|
+
* })
|
|
1198
|
+
* );
|
|
1199
|
+
* }
|
|
1200
|
+
* }
|
|
1201
|
+
* };
|
|
1202
|
+
*
|
|
1203
|
+
* // Using Promise (also supported)
|
|
1204
|
+
* const config: FormConfig = {
|
|
1205
|
+
* fields: [...],
|
|
1206
|
+
* submission: {
|
|
1207
|
+
* action: async (form) => {
|
|
1208
|
+
* const value = form().value();
|
|
1209
|
+
* try {
|
|
1210
|
+
* await this.api.submit(value);
|
|
1211
|
+
* return undefined;
|
|
1212
|
+
* } catch (error) {
|
|
1213
|
+
* if (error.code === 'EMAIL_EXISTS') {
|
|
1214
|
+
* return [{ field: form.email, error: { kind: 'server', message: 'Email already exists' }}];
|
|
1215
|
+
* }
|
|
1216
|
+
* throw error;
|
|
1217
|
+
* }
|
|
1218
|
+
* }
|
|
1219
|
+
* }
|
|
1220
|
+
* };
|
|
1221
|
+
* ```
|
|
1222
|
+
*
|
|
1223
|
+
* @typeParam TValue - The form value type
|
|
1224
|
+
*
|
|
1225
|
+
* @public
|
|
1226
|
+
* @experimental
|
|
1227
|
+
*/
|
|
1228
|
+
interface SubmissionConfig<TValue = unknown> {
|
|
1229
|
+
/**
|
|
1230
|
+
* Action called when the form is submitted.
|
|
1231
|
+
*
|
|
1232
|
+
* This function receives the form's `FieldTree` and should return either a Promise or
|
|
1233
|
+
* an Observable. For Observable returns, it will be automatically converted using `firstValueFrom`.
|
|
1234
|
+
*
|
|
1235
|
+
* **Return values:**
|
|
1236
|
+
* - For success: return `undefined`, `null`, or simply let the Observable complete (the emitted value is ignored unless it's a TreeValidationResult)
|
|
1237
|
+
* - For server errors: return `TreeValidationResult` (array of field/error pairs)
|
|
1238
|
+
*
|
|
1239
|
+
* **Observable support:** You can return an HttpClient observable directly without mapping the result.
|
|
1240
|
+
* The form will treat Observable completion as success. Only return `TreeValidationResult` if you need
|
|
1241
|
+
* to report server-side validation errors.
|
|
1242
|
+
*
|
|
1243
|
+
* Server errors returned will be automatically applied to the corresponding form fields.
|
|
1244
|
+
*
|
|
1245
|
+
* While this action is executing, `form().submitting()` will be `true`, enabling
|
|
1246
|
+
* automatic button disabling and loading states.
|
|
1247
|
+
*
|
|
1248
|
+
* @param form - The form's FieldTree instance
|
|
1249
|
+
* @returns Promise or Observable - for errors, return TreeValidationResult
|
|
1250
|
+
*
|
|
1251
|
+
* @example
|
|
1252
|
+
* ```typescript
|
|
1253
|
+
* // Simple - just return the HTTP observable
|
|
1254
|
+
* action: (form) => this.http.post('/api/register', form().value())
|
|
1255
|
+
*
|
|
1256
|
+
* // With server error handling
|
|
1257
|
+
* action: (form) => {
|
|
1258
|
+
* return this.http.post('/api/register', form().value()).pipe(
|
|
1259
|
+
* catchError((error) => {
|
|
1260
|
+
* if (error.status === 409) {
|
|
1261
|
+
* return of([{
|
|
1262
|
+
* field: form.email,
|
|
1263
|
+
* error: { kind: 'server', message: 'Email already exists' }
|
|
1264
|
+
* }]);
|
|
1265
|
+
* }
|
|
1266
|
+
* throw error;
|
|
1267
|
+
* })
|
|
1268
|
+
* );
|
|
1269
|
+
* }
|
|
1270
|
+
*
|
|
1271
|
+
* // Promise-based alternative
|
|
1272
|
+
* action: async (form) => {
|
|
1273
|
+
* await fetch('/api/register', {
|
|
1274
|
+
* method: 'POST',
|
|
1275
|
+
* body: JSON.stringify(form().value())
|
|
1276
|
+
* });
|
|
1277
|
+
* // Return undefined for success, or TreeValidationResult for errors
|
|
1278
|
+
* }
|
|
1279
|
+
* ```
|
|
1280
|
+
*/
|
|
1281
|
+
action: (form: FieldTree<TValue>) => SubmissionActionResult;
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
/**
|
|
1285
|
+
* Configuration interface for defining dynamic form structure and behavior.
|
|
1286
|
+
*
|
|
1287
|
+
* This interface defines the complete form schema including field definitions,
|
|
1288
|
+
* validation rules, conditional logic, and submission handling using Angular's
|
|
1289
|
+
* signal-based reactive forms.
|
|
1290
|
+
*
|
|
1291
|
+
* @example
|
|
1292
|
+
* ```typescript
|
|
1293
|
+
* const formConfig: FormConfig = {
|
|
1294
|
+
* fields: [
|
|
1295
|
+
* { type: 'input', key: 'email', value: '', label: 'Email', required: true, email: true },
|
|
1296
|
+
* { type: 'group', key: 'address', label: 'Address', fields: [
|
|
1297
|
+
* { type: 'input', key: 'street', value: '', label: 'Street' },
|
|
1298
|
+
* { type: 'input', key: 'city', value: '', label: 'City' }
|
|
1299
|
+
* ]},
|
|
1300
|
+
* { type: 'button', key: 'submit', label: 'Submit', buttonType: 'submit' }
|
|
1301
|
+
* ],
|
|
1302
|
+
* schemas: [
|
|
1303
|
+
* { name: 'emailSchema', schema: { email: validators.email } }
|
|
1304
|
+
* ]
|
|
1305
|
+
* };
|
|
1306
|
+
* ```
|
|
1307
|
+
*
|
|
1308
|
+
* @typeParam TFields - Array of registered field types available for this form
|
|
1309
|
+
* @typeParam TValue - The strongly-typed interface for form values
|
|
1310
|
+
*
|
|
1311
|
+
* @public
|
|
1312
|
+
*/
|
|
1313
|
+
interface FormConfig<TFields extends RegisteredFieldTypes[] = RegisteredFieldTypes[], TValue = InferFormValue$1<TFields>> {
|
|
1314
|
+
/**
|
|
1315
|
+
* Array of field definitions that define the form structure.
|
|
1316
|
+
*
|
|
1317
|
+
* Fields are rendered in the order they appear in this array.
|
|
1318
|
+
* Supports nested groups and conditional field visibility.
|
|
1319
|
+
*
|
|
1320
|
+
* @example
|
|
1321
|
+
* ```typescript
|
|
1322
|
+
* fields: [
|
|
1323
|
+
* { type: 'input', key: 'firstName', label: 'First Name' },
|
|
1324
|
+
* { type: 'group', key: 'address', label: 'Address', fields: [
|
|
1325
|
+
* { type: 'input', key: 'street', label: 'Street' }
|
|
1326
|
+
* ]}
|
|
1327
|
+
* ]
|
|
1328
|
+
* ```
|
|
1329
|
+
*/
|
|
1330
|
+
fields: TFields;
|
|
1331
|
+
/**
|
|
1332
|
+
* Optional form-level validation schema.
|
|
1333
|
+
*
|
|
1334
|
+
* Provides additional validation beyond field-level validation.
|
|
1335
|
+
* Useful for cross-field validation rules.
|
|
1336
|
+
*
|
|
1337
|
+
* @example
|
|
1338
|
+
* ```typescript
|
|
1339
|
+
* schema: {
|
|
1340
|
+
* passwordConfirm: validators.equals('password')
|
|
1341
|
+
* }
|
|
1342
|
+
* ```
|
|
1343
|
+
*/
|
|
1344
|
+
schema?: Schema<TValue>;
|
|
1345
|
+
/**
|
|
1346
|
+
* Global form configuration options.
|
|
1347
|
+
*
|
|
1348
|
+
* Controls form-wide behavior like validation timing and disabled state.
|
|
1349
|
+
*
|
|
1350
|
+
* @value {}
|
|
1351
|
+
*/
|
|
1352
|
+
options?: FormOptions;
|
|
1353
|
+
/**
|
|
1354
|
+
* Global schemas available to all fields.
|
|
1355
|
+
*
|
|
1356
|
+
* Reusable validation schemas that can be referenced by field definitions.
|
|
1357
|
+
* Promotes consistency and reduces duplication.
|
|
1358
|
+
*
|
|
1359
|
+
* @example
|
|
1360
|
+
* ```typescript
|
|
1361
|
+
* schemas: [
|
|
1362
|
+
* { name: 'addressSchema', schema: {
|
|
1363
|
+
* street: validators.required,
|
|
1364
|
+
* zipCode: validators.pattern(/^\d{5}$/)
|
|
1365
|
+
* }}
|
|
1366
|
+
* ]
|
|
1367
|
+
* ```
|
|
1368
|
+
*/
|
|
1369
|
+
schemas?: SchemaDefinition[];
|
|
1370
|
+
/**
|
|
1371
|
+
* Form-level validation messages that act as fallback for field-level messages.
|
|
1372
|
+
*
|
|
1373
|
+
* These messages are used when a field has validation errors but no
|
|
1374
|
+
* field-level `validationMessages` are defined for that specific error.
|
|
1375
|
+
* This allows you to define common validation messages once at the form level
|
|
1376
|
+
* instead of repeating them for each field.
|
|
1377
|
+
*
|
|
1378
|
+
* @example
|
|
1379
|
+
* ```typescript
|
|
1380
|
+
* defaultValidationMessages: {
|
|
1381
|
+
* required: 'This field is required',
|
|
1382
|
+
* email: 'Please enter a valid email address',
|
|
1383
|
+
* minLength: 'Must be at least {{requiredLength}} characters'
|
|
1384
|
+
* }
|
|
1385
|
+
* ```
|
|
1386
|
+
*/
|
|
1387
|
+
defaultValidationMessages?: ValidationMessages;
|
|
1388
|
+
/**
|
|
1389
|
+
* Signal forms adapter configuration.
|
|
1390
|
+
*/
|
|
1391
|
+
customFnConfig?: CustomFnConfig;
|
|
1392
|
+
/**
|
|
1393
|
+
* Form submission configuration.
|
|
1394
|
+
*
|
|
1395
|
+
* When provided, enables integration with Angular Signal Forms' native `submit()` function.
|
|
1396
|
+
* The submission mechanism is **optional** - you can still handle submission manually
|
|
1397
|
+
* via the `(submitted)` output if you prefer.
|
|
1398
|
+
*
|
|
1399
|
+
* While the submission action is executing, `form().submitting()` will be `true`,
|
|
1400
|
+
* which automatically disables submit buttons (unless configured otherwise).
|
|
1401
|
+
*
|
|
1402
|
+
* Server errors returned from the action will be automatically applied to the
|
|
1403
|
+
* corresponding form fields.
|
|
1404
|
+
*
|
|
1405
|
+
* @example
|
|
1406
|
+
* ```typescript
|
|
1407
|
+
* const config: FormConfig = {
|
|
1408
|
+
* fields: [...],
|
|
1409
|
+
* submission: {
|
|
1410
|
+
* action: async (form) => {
|
|
1411
|
+
* const value = form().value();
|
|
1412
|
+
* try {
|
|
1413
|
+
* await this.api.submit(value);
|
|
1414
|
+
* return undefined;
|
|
1415
|
+
* } catch (error) {
|
|
1416
|
+
* return [{ field: form.email, error: { kind: 'server', message: 'Email exists' }}];
|
|
1417
|
+
* }
|
|
1418
|
+
* }
|
|
1419
|
+
* }
|
|
1420
|
+
* };
|
|
1421
|
+
* ```
|
|
1422
|
+
*/
|
|
1423
|
+
submission?: SubmissionConfig<TValue>;
|
|
1424
|
+
}
|
|
1425
|
+
/**
|
|
1426
|
+
* Signal forms adapter configuration for advanced form behavior.
|
|
1427
|
+
*
|
|
1428
|
+
* Provides configuration options for signal forms integration including
|
|
1429
|
+
* legacy migration, custom functions, and custom validators.
|
|
1430
|
+
*
|
|
1431
|
+
* @example
|
|
1432
|
+
* ```typescript
|
|
1433
|
+
* customFnConfig: {
|
|
1434
|
+
* customFunctions: {
|
|
1435
|
+
* isAdult: (context) => context.age >= 18,
|
|
1436
|
+
* formatCurrency: (context) => new Intl.NumberFormat('en-US', {
|
|
1437
|
+
* style: 'currency',
|
|
1438
|
+
* currency: 'USD'
|
|
1439
|
+
* }).format(context.value)
|
|
1440
|
+
* },
|
|
1441
|
+
* simpleValidators: {
|
|
1442
|
+
* noSpaces: (value) => {
|
|
1443
|
+
* return typeof value === 'string' && value.includes(' ')
|
|
1444
|
+
* ? { kind: 'noSpaces', message: 'Spaces not allowed' }
|
|
1445
|
+
* : null;
|
|
1446
|
+
* }
|
|
1447
|
+
* },
|
|
1448
|
+
* contextValidators: {
|
|
1449
|
+
* lessThanField: (ctx, params) => {
|
|
1450
|
+
* const value = ctx.value();
|
|
1451
|
+
* const otherField = params?.field as string;
|
|
1452
|
+
* const otherValue = ctx.root()[otherField]?.value();
|
|
1453
|
+
* if (otherValue !== undefined && value >= otherValue) {
|
|
1454
|
+
* return { kind: 'notLessThan', message: `Must be less than ${otherField}` };
|
|
1455
|
+
* }
|
|
1456
|
+
* return null;
|
|
1457
|
+
* }
|
|
1458
|
+
* }
|
|
1459
|
+
* }
|
|
1460
|
+
* ```
|
|
1461
|
+
*
|
|
1462
|
+
* @public
|
|
1463
|
+
*/
|
|
1464
|
+
interface CustomFnConfig {
|
|
1465
|
+
/**
|
|
1466
|
+
* Custom evaluation functions for conditional expressions.
|
|
1467
|
+
*
|
|
1468
|
+
* Used for: when/readonly/disabled logic
|
|
1469
|
+
* Return type: any value (typically boolean)
|
|
1470
|
+
*
|
|
1471
|
+
* @example
|
|
1472
|
+
* ```typescript
|
|
1473
|
+
* customFunctions: {
|
|
1474
|
+
* isAdult: (context) => context.age >= 18,
|
|
1475
|
+
* calculateAge: (context) => {
|
|
1476
|
+
* const birthDate = new Date(context.birthDate);
|
|
1477
|
+
* return new Date().getFullYear() - birthDate.getFullYear();
|
|
1478
|
+
* }
|
|
1479
|
+
* }
|
|
1480
|
+
* ```
|
|
1481
|
+
*/
|
|
1482
|
+
customFunctions?: Record<string, CustomFunction>;
|
|
1483
|
+
/**
|
|
1484
|
+
* Custom validators using Angular's public FieldContext API
|
|
1485
|
+
*
|
|
1486
|
+
* (ctx, params?) => ValidationError | ValidationError[] | null
|
|
1487
|
+
*
|
|
1488
|
+
* Validators receive FieldContext which provides access to:
|
|
1489
|
+
* - Current field value: `ctx.value()`
|
|
1490
|
+
* - Field state: `ctx.state` (errors, touched, dirty, etc.)
|
|
1491
|
+
* - Other field values: `ctx.valueOf(path)` - public API!
|
|
1492
|
+
* - Other field states: `ctx.stateOf(path)`
|
|
1493
|
+
* - Parameters from JSON configuration
|
|
1494
|
+
*
|
|
1495
|
+
* **Return Types:**
|
|
1496
|
+
* - Single error: `{ kind: 'errorKind' }` for field-level validation
|
|
1497
|
+
* - Multiple errors: `[{ kind: 'error1' }, { kind: 'error2' }]` for cross-field validation
|
|
1498
|
+
* - No error: `null` when validation passes
|
|
1499
|
+
*
|
|
1500
|
+
* @example Single Field Validation
|
|
1501
|
+
* ```typescript
|
|
1502
|
+
* validators: {
|
|
1503
|
+
* noSpaces: (ctx) => {
|
|
1504
|
+
* const value = ctx.value();
|
|
1505
|
+
* if (typeof value === 'string' && value.includes(' ')) {
|
|
1506
|
+
* return { kind: 'noSpaces' };
|
|
1507
|
+
* }
|
|
1508
|
+
* return null;
|
|
1509
|
+
* }
|
|
1510
|
+
* }
|
|
1511
|
+
* ```
|
|
1512
|
+
*
|
|
1513
|
+
* @example Cross-Field Validation (Public API)
|
|
1514
|
+
* ```typescript
|
|
1515
|
+
* validators: {
|
|
1516
|
+
* lessThan: (ctx, params) => {
|
|
1517
|
+
* const value = ctx.value();
|
|
1518
|
+
* const compareToPath = params?.field as string;
|
|
1519
|
+
*
|
|
1520
|
+
* // Use valueOf() to access other field - public API!
|
|
1521
|
+
* const otherValue = ctx.valueOf(compareToPath as any);
|
|
1522
|
+
*
|
|
1523
|
+
* if (otherValue !== undefined && value >= otherValue) {
|
|
1524
|
+
* return { kind: 'notLessThan' };
|
|
1525
|
+
* }
|
|
1526
|
+
* return null;
|
|
1527
|
+
* }
|
|
1528
|
+
* }
|
|
1529
|
+
* ```
|
|
1530
|
+
*
|
|
1531
|
+
* @example Multiple Errors
|
|
1532
|
+
* ```typescript
|
|
1533
|
+
* validators: {
|
|
1534
|
+
* validateDateRange: (ctx) => {
|
|
1535
|
+
* const errors: ValidationError[] = [];
|
|
1536
|
+
* const startDate = ctx.valueOf('startDate' as any);
|
|
1537
|
+
* const endDate = ctx.valueOf('endDate' as any);
|
|
1538
|
+
*
|
|
1539
|
+
* if (!startDate) errors.push({ kind: 'startDateRequired' });
|
|
1540
|
+
* if (!endDate) errors.push({ kind: 'endDateRequired' });
|
|
1541
|
+
* if (startDate && endDate && startDate > endDate) {
|
|
1542
|
+
* errors.push({ kind: 'invalidDateRange' });
|
|
1543
|
+
* }
|
|
1544
|
+
*
|
|
1545
|
+
* return errors.length > 0 ? errors : null;
|
|
1546
|
+
* }
|
|
1547
|
+
* }
|
|
1548
|
+
* ```
|
|
1549
|
+
*/
|
|
1550
|
+
validators?: Record<string, CustomValidator>;
|
|
1551
|
+
/**
|
|
1552
|
+
* Async custom validators using Angular's resource-based validateAsync() API
|
|
1553
|
+
*
|
|
1554
|
+
* Angular's validateAsync uses the resource API for async validation.
|
|
1555
|
+
* Validators must provide params, factory, onSuccess, and optional onError callbacks.
|
|
1556
|
+
*
|
|
1557
|
+
* **Structure:**
|
|
1558
|
+
* - `params`: Function that computes params from field context
|
|
1559
|
+
* - `factory`: Function that creates ResourceRef from params signal
|
|
1560
|
+
* - `onSuccess`: Maps resource result to validation errors
|
|
1561
|
+
* - `onError`: Optional handler for resource errors
|
|
1562
|
+
*
|
|
1563
|
+
* **Use Cases:**
|
|
1564
|
+
* - Database lookups via services with resource API
|
|
1565
|
+
* - Complex async business logic with Angular resources
|
|
1566
|
+
*
|
|
1567
|
+
* **Note:** For HTTP validation, prefer `httpValidators` which provides
|
|
1568
|
+
* a simpler API specifically designed for HTTP requests.
|
|
1569
|
+
*
|
|
1570
|
+
* @example Database Lookup with Resource
|
|
1571
|
+
* ```typescript
|
|
1572
|
+
* asyncValidators: {
|
|
1573
|
+
* checkUsernameAvailable: {
|
|
1574
|
+
* params: (ctx) => ({ username: ctx.value() }),
|
|
1575
|
+
* factory: (params) => {
|
|
1576
|
+
* const injector = inject(Injector);
|
|
1577
|
+
* return resource({
|
|
1578
|
+
* request: () => params(),
|
|
1579
|
+
* loader: ({ request }) => {
|
|
1580
|
+
* if (!request?.username) return null;
|
|
1581
|
+
* const service = injector.get(UserService);
|
|
1582
|
+
* return firstValueFrom(service.checkAvailability(request.username));
|
|
1583
|
+
* }
|
|
1584
|
+
* });
|
|
1585
|
+
* },
|
|
1586
|
+
* onSuccess: (result, ctx) => {
|
|
1587
|
+
* if (!result) return null;
|
|
1588
|
+
* return result.available ? null : { kind: 'usernameTaken' };
|
|
1589
|
+
* },
|
|
1590
|
+
* onError: (error, ctx) => {
|
|
1591
|
+
* console.error('Availability check failed:', error);
|
|
1592
|
+
* return null; // Don't block form on network errors
|
|
1593
|
+
* }
|
|
1594
|
+
* }
|
|
1595
|
+
* }
|
|
1596
|
+
* ```
|
|
1597
|
+
*/
|
|
1598
|
+
asyncValidators?: Record<string, AsyncCustomValidator>;
|
|
1599
|
+
/**
|
|
1600
|
+
* HTTP validators using Angular's validateHttp() API
|
|
1601
|
+
*
|
|
1602
|
+
* Angular's validateHttp provides HTTP validation with automatic request
|
|
1603
|
+
* cancellation and integration with the resource API.
|
|
1604
|
+
*
|
|
1605
|
+
* **Structure:**
|
|
1606
|
+
* - `request`: Function that returns URL string or HttpResourceRequest
|
|
1607
|
+
* - `onSuccess`: REQUIRED - Maps HTTP response to validation errors (inverted logic!)
|
|
1608
|
+
* - `onError`: Optional handler for HTTP errors
|
|
1609
|
+
*
|
|
1610
|
+
* **Benefits:**
|
|
1611
|
+
* - Automatic request cancellation when field value changes
|
|
1612
|
+
* - Built-in integration with Angular's resource management
|
|
1613
|
+
* - Simpler than asyncValidators for HTTP use cases
|
|
1614
|
+
*
|
|
1615
|
+
* **Important:** `onSuccess` uses inverted logic - it maps SUCCESSFUL HTTP responses
|
|
1616
|
+
* to validation errors. For example, if the API returns `{ available: false }`,
|
|
1617
|
+
* your `onSuccess` should return `{ kind: 'usernameTaken' }`.
|
|
1618
|
+
*
|
|
1619
|
+
* @example Username Availability Check (GET)
|
|
1620
|
+
* ```typescript
|
|
1621
|
+
* httpValidators: {
|
|
1622
|
+
* checkUsername: {
|
|
1623
|
+
* request: (ctx) => {
|
|
1624
|
+
* const username = ctx.value();
|
|
1625
|
+
* if (!username) return undefined; // Skip validation if empty
|
|
1626
|
+
* return `/api/users/check-username?username=${encodeURIComponent(username)}`;
|
|
1627
|
+
* },
|
|
1628
|
+
* onSuccess: (response, ctx) => {
|
|
1629
|
+
* // Inverted logic: successful response may indicate validation failure
|
|
1630
|
+
* return response.available ? null : { kind: 'usernameTaken' };
|
|
1631
|
+
* },
|
|
1632
|
+
* onError: (error, ctx) => {
|
|
1633
|
+
* console.error('Availability check failed:', error);
|
|
1634
|
+
* return null; // Don't block form on network errors
|
|
1635
|
+
* }
|
|
1636
|
+
* }
|
|
1637
|
+
* }
|
|
1638
|
+
* ```
|
|
1639
|
+
*
|
|
1640
|
+
* @example Address Validation (POST with Body)
|
|
1641
|
+
* ```typescript
|
|
1642
|
+
* httpValidators: {
|
|
1643
|
+
* validateAddress: {
|
|
1644
|
+
* request: (ctx) => {
|
|
1645
|
+
* const zipCode = ctx.value();
|
|
1646
|
+
* if (!zipCode) return undefined;
|
|
1647
|
+
*
|
|
1648
|
+
* return {
|
|
1649
|
+
* url: '/api/validate-address',
|
|
1650
|
+
* method: 'POST',
|
|
1651
|
+
* body: {
|
|
1652
|
+
* street: ctx.valueOf('street' as any),
|
|
1653
|
+
* city: ctx.valueOf('city' as any),
|
|
1654
|
+
* zipCode: zipCode
|
|
1655
|
+
* },
|
|
1656
|
+
* headers: { 'Content-Type': 'application/json' }
|
|
1657
|
+
* };
|
|
1658
|
+
* },
|
|
1659
|
+
* onSuccess: (response) => {
|
|
1660
|
+
* return response.valid ? null : { kind: 'invalidAddress' };
|
|
1661
|
+
* }
|
|
1662
|
+
* }
|
|
1663
|
+
* }
|
|
1664
|
+
* ```
|
|
1665
|
+
*/
|
|
1666
|
+
httpValidators?: Record<string, HttpCustomValidator>;
|
|
1667
|
+
}
|
|
1668
|
+
/**
|
|
1669
|
+
* Global form configuration options.
|
|
1670
|
+
*
|
|
1671
|
+
* Controls form-wide behavior including disabled state
|
|
1672
|
+
* and button behavior configuration.
|
|
1673
|
+
*
|
|
1674
|
+
* @example
|
|
1675
|
+
* ```typescript
|
|
1676
|
+
* options: {
|
|
1677
|
+
* disabled: false,
|
|
1678
|
+
* submitButton: { disableWhenInvalid: true }
|
|
1679
|
+
* }
|
|
1680
|
+
* ```
|
|
1681
|
+
*
|
|
1682
|
+
* @public
|
|
1683
|
+
*/
|
|
1684
|
+
interface FormOptions {
|
|
1685
|
+
/**
|
|
1686
|
+
* Disable the entire form.
|
|
1687
|
+
*
|
|
1688
|
+
* When enabled, all form fields become read-only and cannot
|
|
1689
|
+
* be modified by user interaction.
|
|
1690
|
+
*
|
|
1691
|
+
* @value false
|
|
1692
|
+
*/
|
|
1693
|
+
disabled?: boolean;
|
|
1694
|
+
/**
|
|
1695
|
+
* Default disabled behavior for submit buttons.
|
|
1696
|
+
*
|
|
1697
|
+
* Controls when submit buttons are automatically disabled.
|
|
1698
|
+
* Can be overridden per-button via the `logic` array on individual button fields.
|
|
1699
|
+
*
|
|
1700
|
+
* @example
|
|
1701
|
+
* ```typescript
|
|
1702
|
+
* options: {
|
|
1703
|
+
* submitButton: {
|
|
1704
|
+
* disableWhenInvalid: true, // Disable when form is invalid
|
|
1705
|
+
* disableWhileSubmitting: true, // Disable during submission
|
|
1706
|
+
* }
|
|
1707
|
+
* }
|
|
1708
|
+
* ```
|
|
1709
|
+
*/
|
|
1710
|
+
submitButton?: SubmitButtonOptions;
|
|
1711
|
+
/**
|
|
1712
|
+
* Default disabled behavior for next page buttons.
|
|
1713
|
+
*
|
|
1714
|
+
* Controls when next page buttons are automatically disabled in paged forms.
|
|
1715
|
+
* Can be overridden per-button via the `logic` array on individual button fields.
|
|
1716
|
+
*
|
|
1717
|
+
* @example
|
|
1718
|
+
* ```typescript
|
|
1719
|
+
* options: {
|
|
1720
|
+
* nextButton: {
|
|
1721
|
+
* disableWhenPageInvalid: true, // Disable when current page is invalid
|
|
1722
|
+
* disableWhileSubmitting: true, // Disable during submission
|
|
1723
|
+
* }
|
|
1724
|
+
* }
|
|
1725
|
+
* ```
|
|
1726
|
+
*/
|
|
1727
|
+
nextButton?: NextButtonOptions;
|
|
1728
|
+
}
|
|
1729
|
+
/**
|
|
1730
|
+
* Options for controlling submit button disabled behavior.
|
|
1731
|
+
*
|
|
1732
|
+
* @public
|
|
1733
|
+
*/
|
|
1734
|
+
interface SubmitButtonOptions {
|
|
1735
|
+
/**
|
|
1736
|
+
* Disable submit button when the form is invalid.
|
|
1737
|
+
*
|
|
1738
|
+
* @default true
|
|
1739
|
+
*/
|
|
1740
|
+
disableWhenInvalid?: boolean;
|
|
1741
|
+
/**
|
|
1742
|
+
* Disable submit button while the form is submitting.
|
|
1743
|
+
*
|
|
1744
|
+
* Requires `submission.action` to be configured for automatic detection.
|
|
1745
|
+
*
|
|
1746
|
+
* @default true
|
|
1747
|
+
*/
|
|
1748
|
+
disableWhileSubmitting?: boolean;
|
|
1749
|
+
}
|
|
1750
|
+
/**
|
|
1751
|
+
* Options for controlling next page button disabled behavior.
|
|
1752
|
+
*
|
|
1753
|
+
* @public
|
|
1754
|
+
*/
|
|
1755
|
+
interface NextButtonOptions {
|
|
1756
|
+
/**
|
|
1757
|
+
* Disable next button when the current page has invalid fields.
|
|
1758
|
+
*
|
|
1759
|
+
* @default true
|
|
1760
|
+
*/
|
|
1761
|
+
disableWhenPageInvalid?: boolean;
|
|
1762
|
+
/**
|
|
1763
|
+
* Disable next button while the form is submitting.
|
|
1764
|
+
*
|
|
1765
|
+
* @default true
|
|
1766
|
+
*/
|
|
1767
|
+
disableWhileSubmitting?: boolean;
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1770
|
+
/**
|
|
1771
|
+
* Field signal context - the "nervous system" of the dynamic form.
|
|
1772
|
+
* Provided via FIELD_SIGNAL_CONTEXT injection token.
|
|
1773
|
+
*
|
|
1774
|
+
* Gives mappers and components access to form state, values, and configuration.
|
|
1775
|
+
* Container fields (Group, Array) create scoped contexts with nested forms.
|
|
1776
|
+
*/
|
|
1777
|
+
interface FieldSignalContext<TModel = unknown> {
|
|
1778
|
+
injector: Injector;
|
|
1779
|
+
value: WritableSignal<Partial<TModel> | undefined>;
|
|
1780
|
+
defaultValues: () => TModel;
|
|
1781
|
+
form: ReturnType<typeof form<TModel>>;
|
|
1782
|
+
defaultValidationMessages?: ValidationMessages;
|
|
1783
|
+
/** Form-level options used by button mappers to determine disabled state defaults. */
|
|
1784
|
+
formOptions?: FormOptions;
|
|
1785
|
+
/** Current page validity signal for paged forms. Used by next button to determine disabled state. */
|
|
1786
|
+
currentPageValid?: Signal<boolean>;
|
|
1787
|
+
}
|
|
1788
|
+
/**
|
|
1789
|
+
* Array context for fields rendered within arrays.
|
|
1790
|
+
* Provides position and parent array information for components inside array fields.
|
|
1791
|
+
*/
|
|
1792
|
+
interface ArrayContext {
|
|
1793
|
+
/** The key of the parent array field. */
|
|
1794
|
+
arrayKey: string;
|
|
1795
|
+
/**
|
|
1796
|
+
* The index of this item within the array.
|
|
1797
|
+
* Uses linkedSignal to automatically update when items are added/removed,
|
|
1798
|
+
* allowing index-dependent logic to react without component recreation.
|
|
1799
|
+
*/
|
|
1800
|
+
index: Signal<number>;
|
|
1801
|
+
/** The current form value for token resolution. */
|
|
1802
|
+
formValue: unknown;
|
|
1803
|
+
/** The array field definition. */
|
|
1804
|
+
field: FieldDef<unknown>;
|
|
1805
|
+
}
|
|
1806
|
+
/**
|
|
1807
|
+
* Mapper function that converts a field definition to component inputs.
|
|
1808
|
+
*
|
|
1809
|
+
* Mappers run within an injection context and can inject FIELD_SIGNAL_CONTEXT.
|
|
1810
|
+
* Returns a Signal to enable reactive updates when dependencies change.
|
|
1811
|
+
*/
|
|
1812
|
+
type MapperFn<T extends FieldDef<unknown>> = (input: T) => Signal<Record<string, unknown>>;
|
|
1813
|
+
|
|
1814
|
+
/**
|
|
1815
|
+
* Defines how a field type handles form values and data collection.
|
|
1816
|
+
*
|
|
1817
|
+
* - 'include': Field contributes to form values (default for input fields)
|
|
1818
|
+
* - 'exclude': Field is excluded from form values (for display/layout fields)
|
|
1819
|
+
* - 'flatten': Field's children are flattened to parent level (for container fields)
|
|
1820
|
+
*/
|
|
1821
|
+
type ValueHandlingMode = 'include' | 'exclude' | 'flatten';
|
|
1822
|
+
/**
|
|
1823
|
+
* Configuration interface for registering custom field types.
|
|
1824
|
+
*
|
|
1825
|
+
* Defines how a field type should be handled by the dynamic form system,
|
|
1826
|
+
* including its component loading strategy and field-to-component mapping logic.
|
|
1827
|
+
* Supports both eager-loaded and lazy-loaded components.
|
|
1828
|
+
*
|
|
1829
|
+
* @typeParam T - The field definition type this field type handles
|
|
1830
|
+
*
|
|
1831
|
+
* @example
|
|
1832
|
+
* ```typescript
|
|
1833
|
+
* const CustomInputType: FieldTypeDefinition<InputFieldDef> = {
|
|
1834
|
+
* name: 'custom-input',
|
|
1835
|
+
* loadComponent: () => import('./custom-input.component').then(m => m.CustomInputComponent),
|
|
1836
|
+
* mapper: customInputMapper
|
|
1837
|
+
* };
|
|
1838
|
+
*
|
|
1839
|
+
* // Register with providers
|
|
1840
|
+
* provideDynamicForm(CustomInputType)
|
|
1841
|
+
* ```
|
|
1842
|
+
*/
|
|
1843
|
+
interface FieldTypeDefinition<T extends FieldDef<any> = any> {
|
|
1844
|
+
/** Unique identifier for the field type */
|
|
1845
|
+
name: string;
|
|
1846
|
+
/** Field definition type marker (internal use) */
|
|
1847
|
+
_fieldDef?: T;
|
|
1848
|
+
/** Function to load the component (supports lazy loading) */
|
|
1849
|
+
loadComponent?: () => Promise<any>;
|
|
1850
|
+
/** Mapper function that converts field definition to component bindings */
|
|
1851
|
+
mapper: MapperFn<T>;
|
|
1852
|
+
/** How this field type handles form values and data collection (defaults to 'include') */
|
|
1853
|
+
valueHandling?: ValueHandlingMode;
|
|
1854
|
+
}
|
|
1855
|
+
/**
|
|
1856
|
+
* Injection token for the global field type registry.
|
|
1857
|
+
*
|
|
1858
|
+
* Provides access to the map of registered field types throughout the application.
|
|
1859
|
+
* The registry is populated by the provideDynamicForm function and used by
|
|
1860
|
+
* field rendering components to resolve field types to their implementations.
|
|
1861
|
+
*
|
|
1862
|
+
* @example
|
|
1863
|
+
* ```typescript
|
|
1864
|
+
* constructor(@Inject(FIELD_REGISTRY) private registry: Map<string, FieldTypeDefinition>) {
|
|
1865
|
+
* const inputType = registry.get('input');
|
|
1866
|
+
* }
|
|
1867
|
+
* ```
|
|
1868
|
+
*/
|
|
1869
|
+
declare const FIELD_REGISTRY: InjectionToken<Map<string, FieldTypeDefinition<any>>>;
|
|
1870
|
+
/**
|
|
1871
|
+
* Gets the value handling mode for a specific field type from the registry.
|
|
1872
|
+
*
|
|
1873
|
+
* @param fieldType - The field type identifier
|
|
1874
|
+
* @param registry - The field type registry
|
|
1875
|
+
* @returns The value handling mode ('include' is the default if not specified)
|
|
1876
|
+
*/
|
|
1877
|
+
declare function getFieldValueHandling(fieldType: string, registry: Map<string, FieldTypeDefinition>): ValueHandlingMode;
|
|
1878
|
+
|
|
1879
|
+
/**
|
|
1880
|
+
* Resolved field ready for rendering with ngComponentOutlet.
|
|
1881
|
+
*/
|
|
1882
|
+
interface ResolvedField {
|
|
1883
|
+
key: string;
|
|
1884
|
+
component: Type<unknown>;
|
|
1885
|
+
injector: Injector;
|
|
1886
|
+
inputs: Signal<Record<string, unknown>>;
|
|
1887
|
+
}
|
|
1888
|
+
|
|
1889
|
+
/**
|
|
1890
|
+
* Form mode enumeration distinguishing between paged and non-paged forms
|
|
1891
|
+
*/
|
|
1892
|
+
type FormMode = 'paged' | 'non-paged';
|
|
1893
|
+
/**
|
|
1894
|
+
* Result of form mode detection with validation details
|
|
1895
|
+
*/
|
|
1896
|
+
interface FormModeDetectionResult {
|
|
1897
|
+
/** The detected form mode */
|
|
1898
|
+
mode: FormMode;
|
|
1899
|
+
/** Whether the form configuration is valid for the detected mode */
|
|
1900
|
+
isValid: boolean;
|
|
1901
|
+
/** Array of validation errors if form is invalid */
|
|
1902
|
+
errors: string[];
|
|
1903
|
+
}
|
|
1904
|
+
|
|
1905
|
+
/**
|
|
1906
|
+
* Type guard to check if a field is a container field (page, row, group, or array)
|
|
1907
|
+
* Container fields have a 'fields' property and don't contribute values directly.
|
|
1908
|
+
* This overload works with RegisteredFieldTypes for full type narrowing.
|
|
1909
|
+
*/
|
|
1910
|
+
declare function isContainerField(field: RegisteredFieldTypes): field is ContainerFieldTypes;
|
|
1911
|
+
/**
|
|
1912
|
+
* Type guard to check if a field is a container field (page, row, group, or array).
|
|
1913
|
+
* This overload works with any FieldDef for looser type checking.
|
|
1914
|
+
*/
|
|
1915
|
+
declare function isContainerField(field: FieldDef<unknown>): boolean;
|
|
1916
|
+
/**
|
|
1917
|
+
* Type guard to check if a field is a leaf field (value or display field)
|
|
1918
|
+
* Leaf fields don't have children and either contribute values or display content
|
|
1919
|
+
*/
|
|
1920
|
+
declare function isLeafField(field: RegisteredFieldTypes): field is LeafFieldTypes;
|
|
1921
|
+
/**
|
|
1922
|
+
* Type guard to check if a field has a value property (value-bearing field)
|
|
1923
|
+
* These fields contribute to the form value output
|
|
1924
|
+
* Note: Using `unknown` in the Extract condition to match any value type
|
|
1925
|
+
*/
|
|
1926
|
+
declare function isValueBearingField(field: RegisteredFieldTypes): field is Extract<RegisteredFieldTypes, {
|
|
1927
|
+
value: unknown;
|
|
1928
|
+
}>;
|
|
1929
|
+
/**
|
|
1930
|
+
* Type guard to check if a field is excluded from form values (display-only field)
|
|
1931
|
+
* Currently this includes text fields and any other fields without a value property
|
|
1932
|
+
*/
|
|
1933
|
+
declare function isDisplayOnlyField(field: RegisteredFieldTypes): boolean;
|
|
1934
|
+
|
|
1935
|
+
/**
|
|
1936
|
+
* Helper types for creating type-safe field configurations with proper nesting constraints
|
|
1937
|
+
*/
|
|
1938
|
+
|
|
1939
|
+
/**
|
|
1940
|
+
* Type helper for accessing nested field paths safely
|
|
1941
|
+
* This allows accessing child paths while maintaining some type safety
|
|
1942
|
+
*/
|
|
1943
|
+
type FieldPathAccess<TValue> = {
|
|
1944
|
+
[K in keyof TValue]: SchemaPath<TValue[K]> | SchemaPathTree<TValue[K]>;
|
|
1945
|
+
};
|
|
1946
|
+
|
|
1947
|
+
/**
|
|
1948
|
+
* Base interface for all form events in the dynamic form system.
|
|
1949
|
+
*
|
|
1950
|
+
* All form events must implement this interface to be compatible with
|
|
1951
|
+
* the event bus system. The type property is used for event filtering
|
|
1952
|
+
* and type-safe subscriptions.
|
|
1953
|
+
*
|
|
1954
|
+
* @example
|
|
1955
|
+
* ```typescript
|
|
1956
|
+
* export class SubmitEvent implements FormEvent {
|
|
1957
|
+
* readonly type = 'submit';
|
|
1958
|
+
* }
|
|
1959
|
+
*
|
|
1960
|
+
* export class ValidationErrorEvent implements FormEvent {
|
|
1961
|
+
* readonly type = 'validation-error';
|
|
1962
|
+
* constructor(public errors: ValidationErrors) {}
|
|
1963
|
+
* }
|
|
1964
|
+
* ```
|
|
1965
|
+
*/
|
|
1966
|
+
interface FormEvent {
|
|
1967
|
+
/** Unique identifier for the event type used for filtering and subscriptions */
|
|
1968
|
+
readonly type: string;
|
|
1969
|
+
}
|
|
1970
|
+
/**
|
|
1971
|
+
* Constructor type for form event classes.
|
|
1972
|
+
*
|
|
1973
|
+
* Defines the shape of event class constructors that can be used with
|
|
1974
|
+
* the event bus dispatch system. Supports both parameterless and
|
|
1975
|
+
* parameterized event constructors.
|
|
1976
|
+
*
|
|
1977
|
+
* @typeParam T - The form event type being constructed
|
|
1978
|
+
*
|
|
1979
|
+
* @example
|
|
1980
|
+
* ```typescript
|
|
1981
|
+
* // Parameterless event constructor
|
|
1982
|
+
* const SubmitEventCtor: FormEventConstructor<SubmitEvent> = SubmitEvent;
|
|
1983
|
+
*
|
|
1984
|
+
* // Parameterized event constructor
|
|
1985
|
+
* const ErrorEventCtor: FormEventConstructor<ErrorEvent> = ErrorEvent;
|
|
1986
|
+
* eventBus.dispatch(ErrorEventCtor);
|
|
1987
|
+
* ```
|
|
1988
|
+
*/
|
|
1989
|
+
type FormEventConstructor<T extends FormEvent = FormEvent> = new (...args: any[]) => T;
|
|
1990
|
+
|
|
1991
|
+
/**
|
|
1992
|
+
* Event dispatched when the form should be cleared.
|
|
1993
|
+
*
|
|
1994
|
+
* This event instructs the dynamic form component to clear all field values,
|
|
1995
|
+
* resetting them to empty/undefined state regardless of their default values.
|
|
1996
|
+
*
|
|
1997
|
+
* @example
|
|
1998
|
+
* ```typescript
|
|
1999
|
+
* // Dispatch from a button component
|
|
2000
|
+
* eventBus.dispatch(FormClearEvent);
|
|
2001
|
+
* ```
|
|
2002
|
+
*
|
|
2003
|
+
* @example
|
|
2004
|
+
* ```typescript
|
|
2005
|
+
* // Listen for clear events
|
|
2006
|
+
* eventBus.on<FormClearEvent>('form-clear').subscribe(() => {
|
|
2007
|
+
* console.log('Form was cleared');
|
|
2008
|
+
* });
|
|
2009
|
+
* ```
|
|
2010
|
+
*/
|
|
2011
|
+
declare class FormClearEvent implements FormEvent {
|
|
2012
|
+
readonly type: "form-clear";
|
|
2013
|
+
}
|
|
2014
|
+
|
|
2015
|
+
/**
|
|
2016
|
+
* Event dispatched when the form should be reset to its default values.
|
|
2017
|
+
*
|
|
2018
|
+
* This event instructs the dynamic form component to restore all field values
|
|
2019
|
+
* to their initial default values as defined in the form configuration.
|
|
2020
|
+
*
|
|
2021
|
+
* @example
|
|
2022
|
+
* ```typescript
|
|
2023
|
+
* // Dispatch from a button component
|
|
2024
|
+
* eventBus.dispatch(FormResetEvent);
|
|
2025
|
+
* ```
|
|
2026
|
+
*
|
|
2027
|
+
* @example
|
|
2028
|
+
* ```typescript
|
|
2029
|
+
* // Listen for reset events
|
|
2030
|
+
* eventBus.on<FormResetEvent>('form-reset').subscribe(() => {
|
|
2031
|
+
* console.log('Form was reset to defaults');
|
|
2032
|
+
* });
|
|
2033
|
+
* ```
|
|
2034
|
+
*/
|
|
2035
|
+
declare class FormResetEvent implements FormEvent {
|
|
2036
|
+
readonly type: "form-reset";
|
|
2037
|
+
}
|
|
2038
|
+
|
|
2039
|
+
declare class PageChangeEvent implements FormEvent {
|
|
2040
|
+
/** The current page index (0-based) */
|
|
2041
|
+
readonly currentPageIndex: number;
|
|
2042
|
+
/** Total number of pages */
|
|
2043
|
+
readonly totalPages: number;
|
|
2044
|
+
/** Previous page index (0-based), undefined if first navigation */
|
|
2045
|
+
readonly previousPageIndex?: number | undefined;
|
|
2046
|
+
readonly type: "page-change";
|
|
2047
|
+
constructor(
|
|
2048
|
+
/** The current page index (0-based) */
|
|
2049
|
+
currentPageIndex: number,
|
|
2050
|
+
/** Total number of pages */
|
|
2051
|
+
totalPages: number,
|
|
2052
|
+
/** Previous page index (0-based), undefined if first navigation */
|
|
2053
|
+
previousPageIndex?: number | undefined);
|
|
2054
|
+
}
|
|
2055
|
+
|
|
2056
|
+
/**
|
|
2057
|
+
* State interface for the page orchestrator
|
|
2058
|
+
*/
|
|
2059
|
+
interface PageOrchestratorState {
|
|
2060
|
+
/** Current page index (0-based) */
|
|
2061
|
+
currentPageIndex: number;
|
|
2062
|
+
/** Total number of pages */
|
|
2063
|
+
totalPages: number;
|
|
2064
|
+
/** Whether the current page is the first page */
|
|
2065
|
+
isFirstPage: boolean;
|
|
2066
|
+
/** Whether the current page is the last page */
|
|
2067
|
+
isLastPage: boolean;
|
|
2068
|
+
/** Whether navigation is currently disabled */
|
|
2069
|
+
navigationDisabled: boolean;
|
|
2070
|
+
}
|
|
2071
|
+
|
|
2072
|
+
declare class PageNavigationStateChangeEvent implements FormEvent {
|
|
2073
|
+
state: PageOrchestratorState;
|
|
2074
|
+
type: "page-navigation-state-change";
|
|
2075
|
+
constructor(state: PageOrchestratorState);
|
|
2076
|
+
}
|
|
2077
|
+
|
|
2078
|
+
/**
|
|
2079
|
+
* Dynamic form component that renders a complete form based on configuration.
|
|
2080
|
+
*
|
|
2081
|
+
* This is the main entry point for the dynamic form system. It handles form state management,
|
|
2082
|
+
* validation, field rendering, and event coordination using Angular's signal-based reactive forms.
|
|
2083
|
+
*
|
|
2084
|
+
* @typeParam TFields - Array of registered field types available for this form
|
|
2085
|
+
* @typeParam TModel - The strongly-typed interface for form values
|
|
2086
|
+
*
|
|
2087
|
+
* @example
|
|
2088
|
+
*```html
|
|
2089
|
+
* <form
|
|
2090
|
+
* [dynamic-form]="formConfig"
|
|
2091
|
+
* [(value)]="formData"
|
|
2092
|
+
* (submitted)="handleSubmit($event)"
|
|
2093
|
+
* (validityChange)="handleValidityChange($event)">
|
|
2094
|
+
* </form>
|
|
2095
|
+
* ```
|
|
2096
|
+
*/
|
|
2097
|
+
declare class DynamicForm<TFields extends RegisteredFieldTypes[] = RegisteredFieldTypes[], TModel = InferFormValue$1<TFields>> implements OnDestroy {
|
|
2098
|
+
private readonly destroyRef;
|
|
2099
|
+
private readonly fieldRegistry;
|
|
2100
|
+
private readonly injector;
|
|
2101
|
+
private readonly eventBus;
|
|
2102
|
+
private readonly rootFormRegistry;
|
|
2103
|
+
private readonly functionRegistry;
|
|
2104
|
+
private readonly schemaRegistry;
|
|
2105
|
+
/** Form configuration defining the structure, validation, and behavior. */
|
|
2106
|
+
config: InputSignal<FormConfig<TFields>>;
|
|
2107
|
+
/** Runtime form options that override config options when provided. */
|
|
2108
|
+
formOptions: InputSignal<FormOptions | undefined>;
|
|
2109
|
+
/** Form values for two-way data binding. */
|
|
2110
|
+
value: _angular_core.ModelSignal<Partial<TModel> | undefined>;
|
|
2111
|
+
private readonly memoizedFlattenFields;
|
|
2112
|
+
private readonly memoizedFlattenFieldsForRendering;
|
|
2113
|
+
private readonly memoizedKeyBy;
|
|
2114
|
+
private readonly memoizedDefaultValues;
|
|
2115
|
+
private readonly rawFieldRegistry;
|
|
2116
|
+
readonly formModeDetection: Signal<FormModeDetectionResult>;
|
|
2117
|
+
private readonly formSetup;
|
|
2118
|
+
readonly pageFieldDefinitions: Signal<PageField<_ng_forge_dynamic_forms.PageAllowedChildren[]>[]>;
|
|
2119
|
+
readonly effectiveFormOptions: Signal<{
|
|
2120
|
+
disabled?: boolean;
|
|
2121
|
+
submitButton?: _ng_forge_dynamic_forms.SubmitButtonOptions;
|
|
2122
|
+
nextButton?: _ng_forge_dynamic_forms.NextButtonOptions;
|
|
2123
|
+
}>;
|
|
2124
|
+
readonly fieldSignalContext: Signal<FieldSignalContext<TModel>>;
|
|
2125
|
+
private readonly fieldInjector;
|
|
2126
|
+
readonly defaultValues: _angular_core.WritableSignal<TModel>;
|
|
2127
|
+
private readonly entity;
|
|
2128
|
+
readonly form: Signal<_angular_forms_signals.FieldTree<TModel, string | number>>;
|
|
2129
|
+
private readonly componentId;
|
|
2130
|
+
private readonly totalComponentsCount;
|
|
2131
|
+
private readonly fieldsSource;
|
|
2132
|
+
/** Collects errors from async field component loading for error boundary patterns. */
|
|
2133
|
+
readonly fieldLoadingErrors: _angular_core.WritableSignal<{
|
|
2134
|
+
fieldType: string;
|
|
2135
|
+
fieldKey: string;
|
|
2136
|
+
error: Error;
|
|
2137
|
+
}[]>;
|
|
2138
|
+
/** Current form values (reactive). */
|
|
2139
|
+
readonly formValue: Signal<TModel | (TModel extends infer T ? T extends TModel ? T extends _angular_forms.AbstractControl<unknown, infer TValue extends unknown, any> ? TValue : never : never : never)>;
|
|
2140
|
+
/** Whether the form is currently valid. */
|
|
2141
|
+
readonly valid: Signal<boolean>;
|
|
2142
|
+
/** Whether the form is currently invalid. */
|
|
2143
|
+
readonly invalid: Signal<boolean>;
|
|
2144
|
+
/** Whether any form field has been modified. */
|
|
2145
|
+
readonly dirty: Signal<boolean>;
|
|
2146
|
+
/** Whether any form field has been touched (blurred). */
|
|
2147
|
+
readonly touched: Signal<boolean>;
|
|
2148
|
+
/** Current validation errors from all fields. */
|
|
2149
|
+
readonly errors: Signal<_angular_forms_signals.ValidationError.WithField[]>;
|
|
2150
|
+
/** Whether the form is disabled (from options or form state). */
|
|
2151
|
+
readonly disabled: Signal<boolean>;
|
|
2152
|
+
/** Whether the form is currently submitting. */
|
|
2153
|
+
readonly submitting: Signal<boolean>;
|
|
2154
|
+
/**
|
|
2155
|
+
* Announcement message for screen readers.
|
|
2156
|
+
* Updates when form submission fails due to validation errors.
|
|
2157
|
+
*/
|
|
2158
|
+
readonly errorAnnouncement: _angular_core.WritableSignal<string>;
|
|
2159
|
+
readonly initialized$: rxjs.Observable<boolean>;
|
|
2160
|
+
/** Emits when form validity changes. */
|
|
2161
|
+
readonly validityChange: _angular_core.OutputRef<boolean>;
|
|
2162
|
+
/** Emits when form dirty state changes. */
|
|
2163
|
+
readonly dirtyChange: _angular_core.OutputRef<boolean>;
|
|
2164
|
+
/**
|
|
2165
|
+
* Emits form values when submitted (via SubmitEvent).
|
|
2166
|
+
* Note: Does not emit when `submission.action` is configured - use one or the other.
|
|
2167
|
+
*/
|
|
2168
|
+
readonly submitted: _angular_core.OutputRef<Partial<TModel> | undefined>;
|
|
2169
|
+
/** Emits when form is reset to default values. */
|
|
2170
|
+
readonly reset: _angular_core.OutputRef<FormResetEvent>;
|
|
2171
|
+
/** Emits when form is cleared to empty state. */
|
|
2172
|
+
readonly cleared: _angular_core.OutputRef<FormClearEvent>;
|
|
2173
|
+
/** Emits all form events for custom event handling. */
|
|
2174
|
+
readonly events: _angular_core.OutputRef<_ng_forge_dynamic_forms.FormEvent>;
|
|
2175
|
+
/**
|
|
2176
|
+
* Emits when all form components are initialized and ready for interaction.
|
|
2177
|
+
* Useful for E2E testing to ensure the form is fully rendered before interaction.
|
|
2178
|
+
*/
|
|
2179
|
+
readonly initialized: _angular_core.OutputRef<boolean>;
|
|
2180
|
+
/** Emits when the current page changes in paged forms. */
|
|
2181
|
+
readonly onPageChange: _angular_core.OutputRef<PageChangeEvent>;
|
|
2182
|
+
/** Emits when page navigation state changes (canGoNext, canGoPrevious, etc.). */
|
|
2183
|
+
readonly onPageNavigationStateChange: _angular_core.OutputRef<PageNavigationStateChangeEvent>;
|
|
2184
|
+
protected readonly resolvedFields: Signal<ResolvedField[]>;
|
|
2185
|
+
private readonly syncEntityToValue;
|
|
2186
|
+
private readonly clearErrorsOnConfigChange;
|
|
2187
|
+
private readonly emitPagedFormInitialized;
|
|
2188
|
+
private readonly emitNonPagedFormInitialized;
|
|
2189
|
+
private readonly handleFormReset;
|
|
2190
|
+
private readonly handleFormClear;
|
|
2191
|
+
/**
|
|
2192
|
+
* Announces form validation errors to screen readers when submission is attempted
|
|
2193
|
+
* on an invalid form.
|
|
2194
|
+
*/
|
|
2195
|
+
private readonly handleErrorAnnouncement;
|
|
2196
|
+
private readonly handleSubmission;
|
|
2197
|
+
/**
|
|
2198
|
+
* Handles native form submission triggered by:
|
|
2199
|
+
* - Pressing Enter in an input field
|
|
2200
|
+
* - Clicking a button with type="submit"
|
|
2201
|
+
* - Programmatic form.submit() calls
|
|
2202
|
+
*
|
|
2203
|
+
* This method prevents the default form submission behavior (page reload)
|
|
2204
|
+
* and dispatches a SubmitEvent through the EventBus for processing.
|
|
2205
|
+
*
|
|
2206
|
+
* @param event - The native submit event from the form element
|
|
2207
|
+
*/
|
|
2208
|
+
protected onNativeSubmit(event: Event): void;
|
|
2209
|
+
private registerValidatorsFromConfig;
|
|
2210
|
+
private createFormSetupFromConfig;
|
|
2211
|
+
private createEmptyFormSetup;
|
|
2212
|
+
private onFormReset;
|
|
2213
|
+
private onFormClear;
|
|
2214
|
+
/**
|
|
2215
|
+
* Counts the total number of validation errors across all fields.
|
|
2216
|
+
* Used for screen reader announcements.
|
|
2217
|
+
*/
|
|
2218
|
+
private countErrors;
|
|
2219
|
+
ngOnDestroy(): void;
|
|
2220
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<DynamicForm<any, any>, never>;
|
|
2221
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<DynamicForm<any, any>, "form[dynamic-form]", never, { "config": { "alias": "dynamic-form"; "required": true; "isSignal": true; }; "formOptions": { "alias": "formOptions"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; "validityChange": "validityChange"; "dirtyChange": "dirtyChange"; "submitted": "submitted"; "reset": "reset"; "cleared": "cleared"; "events": "events"; "initialized": "initialized"; "onPageChange": "onPageChange"; "onPageNavigationStateChange": "onPageNavigationStateChange"; }, never, never, true, never>;
|
|
2222
|
+
}
|
|
2223
|
+
|
|
2224
|
+
/**
|
|
2225
|
+
* Extract FieldDef type from FieldTypeDefinition
|
|
2226
|
+
*/
|
|
2227
|
+
type ExtractFieldDef<T> = T extends FieldTypeDefinition<infer F> ? F : never;
|
|
2228
|
+
/**
|
|
2229
|
+
* Union of all FieldDef types from provided field types
|
|
2230
|
+
*/
|
|
2231
|
+
type FieldDefUnion<T extends FieldTypeDefinition[]> = ExtractFieldDef<T[number]>;
|
|
2232
|
+
/**
|
|
2233
|
+
* Infer form value type from field definitions
|
|
2234
|
+
*/
|
|
2235
|
+
type InferFormValue<TFieldDefs extends FieldDef<any>[]> = {
|
|
2236
|
+
[K in TFieldDefs[number]['key']]: any;
|
|
2237
|
+
};
|
|
2238
|
+
/**
|
|
2239
|
+
* Provider result with type inference
|
|
2240
|
+
*/
|
|
2241
|
+
type ProvideDynamicFormResult<T extends FieldTypeDefinition[]> = EnvironmentProviders & {
|
|
2242
|
+
__fieldDefs?: FieldDefUnion<T>;
|
|
2243
|
+
__formValue?: InferFormValue<FieldDefUnion<T>[]>;
|
|
2244
|
+
};
|
|
2245
|
+
/**
|
|
2246
|
+
* Provider function to configure the dynamic form system with field types and options.
|
|
2247
|
+
*
|
|
2248
|
+
* This function creates environment providers that can be used at application or route level
|
|
2249
|
+
* to register field types. It provides type-safe field registration with automatic type inference.
|
|
2250
|
+
*
|
|
2251
|
+
* @param fieldTypes - Custom field type definitions to register alongside built-in types
|
|
2252
|
+
* @returns Environment providers for dependency injection with type inference
|
|
2253
|
+
*
|
|
2254
|
+
* @example
|
|
2255
|
+
* ```typescript
|
|
2256
|
+
* // Application-level setup
|
|
2257
|
+
* bootstrapApplication(AppComponent, {
|
|
2258
|
+
* providers: [
|
|
2259
|
+
* provideDynamicForm(...withMaterialFields())
|
|
2260
|
+
* ]
|
|
2261
|
+
* });
|
|
2262
|
+
* ```
|
|
2263
|
+
*
|
|
2264
|
+
* @example
|
|
2265
|
+
* ```typescript
|
|
2266
|
+
* // Route-level setup
|
|
2267
|
+
* const routes: Routes = [
|
|
2268
|
+
* {
|
|
2269
|
+
* path: 'form',
|
|
2270
|
+
* component: FormComponent,
|
|
2271
|
+
* providers: [provideDynamicForm(...withMaterialFields())]
|
|
2272
|
+
* }
|
|
2273
|
+
* ];
|
|
2274
|
+
* ```
|
|
2275
|
+
*
|
|
2276
|
+
* @example
|
|
2277
|
+
* ```typescript
|
|
2278
|
+
* // Custom field types with type inference
|
|
2279
|
+
* import { CustomFieldType, AnotherFieldType } from './custom-fields';
|
|
2280
|
+
*
|
|
2281
|
+
* export const appConfig: ApplicationConfig = {
|
|
2282
|
+
* providers: [
|
|
2283
|
+
* provideDynamicForm(CustomFieldType, AnotherFieldType)
|
|
2284
|
+
* ]
|
|
2285
|
+
* };
|
|
2286
|
+
* ```
|
|
2287
|
+
*
|
|
2288
|
+
* @typeParam T - Array of field type definitions for type inference
|
|
2289
|
+
*
|
|
2290
|
+
* @public
|
|
2291
|
+
*/
|
|
2292
|
+
declare function provideDynamicForm<const T extends FieldTypeDefinition[]>(...fieldTypes: T): ProvideDynamicFormResult<T>;
|
|
2293
|
+
/**
|
|
2294
|
+
* Extract FieldDef types from provider result
|
|
2295
|
+
*/
|
|
2296
|
+
type ExtractFieldDefs<T> = T extends {
|
|
2297
|
+
__fieldDefs?: infer F;
|
|
2298
|
+
} ? F : never;
|
|
2299
|
+
/**
|
|
2300
|
+
* Extract form value type from provider result
|
|
2301
|
+
*/
|
|
2302
|
+
type ExtractFormValue<T> = T extends {
|
|
2303
|
+
__formValue?: infer V;
|
|
2304
|
+
} ? V : never;
|
|
2305
|
+
|
|
2306
|
+
/**
|
|
2307
|
+
* Built-in field types provided by the dynamic form library.
|
|
2308
|
+
*
|
|
2309
|
+
* These core field types handle form structure and layout. They are automatically
|
|
2310
|
+
* registered when using provideDynamicForm() and form the foundation for building
|
|
2311
|
+
* complex form layouts with nested fields and multi-step flows.
|
|
2312
|
+
*
|
|
2313
|
+
* @example
|
|
2314
|
+
* ```typescript
|
|
2315
|
+
* // Row field for horizontal layout
|
|
2316
|
+
* { type: 'row', fields: [
|
|
2317
|
+
* { type: 'input', key: 'firstName' },
|
|
2318
|
+
* { type: 'input', key: 'lastName' }
|
|
2319
|
+
* ]}
|
|
2320
|
+
*
|
|
2321
|
+
* // Group field for nested form sections
|
|
2322
|
+
* { type: 'group', key: 'address', fields: [
|
|
2323
|
+
* { type: 'input', key: 'street' },
|
|
2324
|
+
* { type: 'input', key: 'city' }
|
|
2325
|
+
* ]}
|
|
2326
|
+
*
|
|
2327
|
+
* // Array field for array-based form sections
|
|
2328
|
+
* { type: 'array', key: 'items', fields: [
|
|
2329
|
+
* { type: 'input', key: 'name' },
|
|
2330
|
+
* { type: 'input', key: 'quantity' }
|
|
2331
|
+
* ]}
|
|
2332
|
+
*
|
|
2333
|
+
* // Page field for multi-step forms
|
|
2334
|
+
* { type: 'page', key: 'step1', fields: [...] }
|
|
2335
|
+
* ```
|
|
2336
|
+
*/
|
|
2337
|
+
declare const BUILT_IN_FIELDS: FieldTypeDefinition[];
|
|
2338
|
+
|
|
2339
|
+
interface FieldOption<T = unknown> {
|
|
2340
|
+
label: DynamicText;
|
|
2341
|
+
value: T;
|
|
2342
|
+
disabled?: boolean;
|
|
2343
|
+
[key: string]: unknown;
|
|
2344
|
+
}
|
|
2345
|
+
|
|
2346
|
+
/**
|
|
2347
|
+
* Injection token for providing field signal context to mappers and components.
|
|
2348
|
+
*
|
|
2349
|
+
* The field signal context is the "nervous system" of the dynamic form library,
|
|
2350
|
+
* providing access to:
|
|
2351
|
+
* - The form instance and structure
|
|
2352
|
+
* - Current form values (as signals)
|
|
2353
|
+
* - Default values
|
|
2354
|
+
* - Validation messages
|
|
2355
|
+
* - The injector for creating components
|
|
2356
|
+
*
|
|
2357
|
+
* This token is provided by the DynamicForm component and can be scoped
|
|
2358
|
+
* for nested forms (Groups, Arrays) via child injectors.
|
|
2359
|
+
*
|
|
2360
|
+
* @example
|
|
2361
|
+
* ```typescript
|
|
2362
|
+
* // In a mapper function
|
|
2363
|
+
* export function mapValueField(fieldDef: BaseValueField<any, any>): Binding[] {
|
|
2364
|
+
* const context = inject(FIELD_SIGNAL_CONTEXT);
|
|
2365
|
+
* const form = context.form();
|
|
2366
|
+
* // ... use context
|
|
2367
|
+
* }
|
|
2368
|
+
*
|
|
2369
|
+
* // In a component
|
|
2370
|
+
* export class MyFieldComponent {
|
|
2371
|
+
* private context = inject(FIELD_SIGNAL_CONTEXT);
|
|
2372
|
+
* }
|
|
2373
|
+
* ```
|
|
2374
|
+
*/
|
|
2375
|
+
declare const FIELD_SIGNAL_CONTEXT: InjectionToken<FieldSignalContext<unknown>>;
|
|
2376
|
+
/**
|
|
2377
|
+
* Injection token for providing array context metadata to mappers and components.
|
|
2378
|
+
*
|
|
2379
|
+
* This token is optionally provided by ArrayFieldComponent when creating injectors
|
|
2380
|
+
* for array items. It contains metadata about the array item's position and parent.
|
|
2381
|
+
*
|
|
2382
|
+
* Mappers can inject this token with {optional: true} to access array context:
|
|
2383
|
+
*
|
|
2384
|
+
* @example
|
|
2385
|
+
* ```typescript
|
|
2386
|
+
* // In a mapper function
|
|
2387
|
+
* export function buttonMapper(fieldDef: FieldDef<any>): Binding[] {
|
|
2388
|
+
* const arrayContext = inject(ARRAY_CONTEXT, { optional: true });
|
|
2389
|
+
* if (arrayContext) {
|
|
2390
|
+
* // Use arrayContext.index, arrayContext.arrayKey, etc.
|
|
2391
|
+
* }
|
|
2392
|
+
* }
|
|
2393
|
+
* ```
|
|
2394
|
+
*/
|
|
2395
|
+
declare const ARRAY_CONTEXT: InjectionToken<ArrayContext>;
|
|
2396
|
+
|
|
2397
|
+
/**
|
|
2398
|
+
* Context for resolving button disabled state.
|
|
2399
|
+
*
|
|
2400
|
+
* @public
|
|
2401
|
+
*/
|
|
2402
|
+
interface ButtonLogicContext {
|
|
2403
|
+
/** The form's FieldTree instance */
|
|
2404
|
+
form: FieldTree<unknown>;
|
|
2405
|
+
/** Form-level options */
|
|
2406
|
+
formOptions?: FormOptions;
|
|
2407
|
+
/** Field-level logic array (if provided) */
|
|
2408
|
+
fieldLogic?: LogicConfig[];
|
|
2409
|
+
/** Explicit disabled state from field definition */
|
|
2410
|
+
explicitlyDisabled?: boolean;
|
|
2411
|
+
/** Current page validity signal (for paged forms) */
|
|
2412
|
+
currentPageValid?: Signal<boolean>;
|
|
2413
|
+
/** Current form value for evaluating conditional expressions */
|
|
2414
|
+
formValue?: unknown;
|
|
2415
|
+
}
|
|
2416
|
+
/**
|
|
2417
|
+
* Resolves the disabled state for a submit button.
|
|
2418
|
+
*
|
|
2419
|
+
* The disabled state is determined by (in order of precedence):
|
|
2420
|
+
* 1. Explicit `disabled: true` on the field definition
|
|
2421
|
+
* 2. Field-level `logic` array (if present, overrides form-level defaults)
|
|
2422
|
+
* 3. Form-level `options.submitButton` defaults
|
|
2423
|
+
*
|
|
2424
|
+
* @param ctx - The button logic context
|
|
2425
|
+
* @returns A computed signal that returns true when the button should be disabled
|
|
2426
|
+
*
|
|
2427
|
+
* @example
|
|
2428
|
+
* ```typescript
|
|
2429
|
+
* const disabled = resolveSubmitButtonDisabled({
|
|
2430
|
+
* form: formInstance,
|
|
2431
|
+
* formOptions: config.options,
|
|
2432
|
+
* fieldLogic: buttonField.logic,
|
|
2433
|
+
* explicitlyDisabled: buttonField.disabled,
|
|
2434
|
+
* });
|
|
2435
|
+
*
|
|
2436
|
+
* // Use in template
|
|
2437
|
+
* <button [disabled]="disabled()">Submit</button>
|
|
2438
|
+
* ```
|
|
2439
|
+
*
|
|
2440
|
+
* @public
|
|
2441
|
+
*/
|
|
2442
|
+
declare function resolveSubmitButtonDisabled(ctx: ButtonLogicContext): Signal<boolean>;
|
|
2443
|
+
/**
|
|
2444
|
+
* Resolves the disabled state for a next page button.
|
|
2445
|
+
*
|
|
2446
|
+
* The disabled state is determined by (in order of precedence):
|
|
2447
|
+
* 1. Explicit `disabled: true` on the field definition
|
|
2448
|
+
* 2. Field-level `logic` array (if present, overrides form-level defaults)
|
|
2449
|
+
* 3. Form-level `options.nextButton` defaults
|
|
2450
|
+
*
|
|
2451
|
+
* @param ctx - The button logic context
|
|
2452
|
+
* @returns A computed signal that returns true when the button should be disabled
|
|
2453
|
+
*
|
|
2454
|
+
* @example
|
|
2455
|
+
* ```typescript
|
|
2456
|
+
* const disabled = resolveNextButtonDisabled({
|
|
2457
|
+
* form: formInstance,
|
|
2458
|
+
* formOptions: config.options,
|
|
2459
|
+
* fieldLogic: buttonField.logic,
|
|
2460
|
+
* explicitlyDisabled: buttonField.disabled,
|
|
2461
|
+
* currentPageValid: pageOrchestrator.currentPageValid,
|
|
2462
|
+
* });
|
|
2463
|
+
* ```
|
|
2464
|
+
*
|
|
2465
|
+
* @public
|
|
2466
|
+
*/
|
|
2467
|
+
declare function resolveNextButtonDisabled(ctx: ButtonLogicContext): Signal<boolean>;
|
|
2468
|
+
|
|
2469
|
+
interface FieldWithValidation {
|
|
2470
|
+
readonly required?: boolean;
|
|
2471
|
+
readonly email?: boolean;
|
|
2472
|
+
readonly min?: number;
|
|
2473
|
+
readonly max?: number;
|
|
2474
|
+
readonly minLength?: number;
|
|
2475
|
+
readonly maxLength?: number;
|
|
2476
|
+
readonly pattern?: string | RegExp;
|
|
2477
|
+
readonly validators?: ValidatorConfig[];
|
|
2478
|
+
readonly validationMessages?: ValidationMessages;
|
|
2479
|
+
readonly logic?: LogicConfig[];
|
|
2480
|
+
readonly schemas?: SchemaApplicationConfig[];
|
|
2481
|
+
}
|
|
2482
|
+
|
|
2483
|
+
/**
|
|
2484
|
+
* Supported primitive value types for form fields.
|
|
2485
|
+
* This type represents all possible value types that can be used in form fields.
|
|
2486
|
+
*/
|
|
2487
|
+
type ValueType = string | number | boolean | Date | object | unknown[];
|
|
2488
|
+
interface BaseValueField<TProps, TValue> extends FieldDef<TProps>, FieldWithValidation {
|
|
2489
|
+
value?: TValue;
|
|
2490
|
+
/**
|
|
2491
|
+
* Placeholder text displayed when the field is empty.
|
|
2492
|
+
* Supports static strings, Observables, and Signals for dynamic content.
|
|
2493
|
+
*/
|
|
2494
|
+
placeholder?: DynamicText;
|
|
2495
|
+
required?: boolean;
|
|
2496
|
+
}
|
|
2497
|
+
declare function isValueField<TProps>(field: FieldDef<TProps>): field is BaseValueField<TProps, ValueType>;
|
|
2498
|
+
type ExcludedKeys$1 = 'type' | 'conditionals' | 'value' | 'valueType' | 'disabled' | 'readonly' | 'hidden' | 'col' | 'minValue' | 'maxValue' | 'step' | 'required' | 'email' | 'min' | 'max' | 'minLength' | 'maxLength' | 'pattern' | 'validators' | 'logic' | 'schemas';
|
|
2499
|
+
type ValueFieldComponent<T extends BaseValueField<any, unknown>> = Prettify<WithInputSignals<Omit<T, ExcludedKeys$1>>>;
|
|
2500
|
+
|
|
2501
|
+
interface BaseCheckedField<TProps> extends FieldDef<TProps>, FieldWithValidation {
|
|
2502
|
+
value?: boolean;
|
|
2503
|
+
/**
|
|
2504
|
+
* Placeholder text displayed when the field is empty.
|
|
2505
|
+
* Supports static strings, Observables, and Signals for dynamic content.
|
|
2506
|
+
*/
|
|
2507
|
+
placeholder?: DynamicText;
|
|
2508
|
+
required?: boolean;
|
|
2509
|
+
}
|
|
2510
|
+
declare function isCheckedField<TProps>(field: FieldDef<TProps>): field is BaseCheckedField<TProps>;
|
|
2511
|
+
type ExcludedKeys = 'type' | 'conditionals' | 'value' | 'disabled' | 'readonly' | 'hidden' | 'col' | keyof FieldWithValidation;
|
|
2512
|
+
type CheckedFieldComponent<T extends BaseCheckedField<any>> = Prettify<WithInputSignals<Omit<T, ExcludedKeys>>>;
|
|
2513
|
+
|
|
2514
|
+
/**
|
|
2515
|
+
* HTML input types supported by input fields
|
|
2516
|
+
*/
|
|
2517
|
+
type InputType = 'text' | 'email' | 'password' | 'number' | 'tel' | 'url';
|
|
2518
|
+
/**
|
|
2519
|
+
* Props for input fields with optional inputType specification.
|
|
2520
|
+
* This is a generic interface that can be extended by framework-specific implementations.
|
|
2521
|
+
*/
|
|
2522
|
+
interface InputProps<T extends InputType = InputType> {
|
|
2523
|
+
/**
|
|
2524
|
+
* The HTML input type. Determines the value type:
|
|
2525
|
+
* - 'number': value will be number
|
|
2526
|
+
* - 'text', 'email', 'password', 'tel', 'url': value will be string
|
|
2527
|
+
*/
|
|
2528
|
+
type?: T;
|
|
2529
|
+
placeholder?: DynamicText;
|
|
2530
|
+
}
|
|
2531
|
+
/**
|
|
2532
|
+
* Number input field with strict number value type.
|
|
2533
|
+
* When props.type is 'number', value must be number.
|
|
2534
|
+
*/
|
|
2535
|
+
interface NumberInputField<TProps extends {
|
|
2536
|
+
type?: string;
|
|
2537
|
+
} = {
|
|
2538
|
+
type: 'number';
|
|
2539
|
+
placeholder?: DynamicText;
|
|
2540
|
+
}> extends BaseValueField<TProps, number> {
|
|
2541
|
+
type: 'input';
|
|
2542
|
+
props?: TProps;
|
|
2543
|
+
}
|
|
2544
|
+
/**
|
|
2545
|
+
* String input field with strict string value type.
|
|
2546
|
+
* When props.type is text/email/etc, value must be string.
|
|
2547
|
+
*/
|
|
2548
|
+
interface StringInputField<TProps extends {
|
|
2549
|
+
type?: string;
|
|
2550
|
+
} = {
|
|
2551
|
+
type: 'text' | 'email' | 'password' | 'tel' | 'url';
|
|
2552
|
+
placeholder?: DynamicText;
|
|
2553
|
+
}> extends BaseValueField<TProps, string> {
|
|
2554
|
+
type: 'input';
|
|
2555
|
+
props?: TProps;
|
|
2556
|
+
}
|
|
2557
|
+
/**
|
|
2558
|
+
* Helper type to extract the input type from props
|
|
2559
|
+
*/
|
|
2560
|
+
type ExtractInputType<TProps> = TProps extends {
|
|
2561
|
+
type?: infer T;
|
|
2562
|
+
} ? T : InputType;
|
|
2563
|
+
/**
|
|
2564
|
+
* Fallback variant when props is undefined - allows both string and number values
|
|
2565
|
+
*/
|
|
2566
|
+
interface UndefinedPropsInputField extends BaseValueField<undefined, string | number> {
|
|
2567
|
+
type: 'input';
|
|
2568
|
+
props?: never;
|
|
2569
|
+
}
|
|
2570
|
+
/**
|
|
2571
|
+
* Builds discriminated union based on what input types are possible in TProps
|
|
2572
|
+
*/
|
|
2573
|
+
type BuildInputFieldUnion<TProps extends {
|
|
2574
|
+
type?: string;
|
|
2575
|
+
}> = (ExtractInputType<TProps> extends infer T ? T extends string ? ('number' extends T ? NumberInputField<TProps> : never) | (Exclude<T, 'number'> extends never ? never : StringInputField<TProps>) : StringInputField<TProps> : never) | UndefinedPropsInputField;
|
|
2576
|
+
/**
|
|
2577
|
+
* Input field with automatic type-safe value inference.
|
|
2578
|
+
* TypeScript automatically infers the correct value type based on props.type.
|
|
2579
|
+
*
|
|
2580
|
+
* @example
|
|
2581
|
+
* // Direct usage - automatic strict type safety
|
|
2582
|
+
* const numberField: InputField = {
|
|
2583
|
+
* type: 'input',
|
|
2584
|
+
* key: 'age',
|
|
2585
|
+
* props: { type: 'number' },
|
|
2586
|
+
* value: 25 // ✓ number only (string is type error!)
|
|
2587
|
+
* };
|
|
2588
|
+
*
|
|
2589
|
+
* const textField: InputField = {
|
|
2590
|
+
* type: 'input',
|
|
2591
|
+
* key: 'name',
|
|
2592
|
+
* props: { type: 'text' },
|
|
2593
|
+
* value: 'hello' // ✓ string only (number is type error!)
|
|
2594
|
+
* };
|
|
2595
|
+
*
|
|
2596
|
+
* @example
|
|
2597
|
+
* // Framework usage - extend with custom props
|
|
2598
|
+
* interface MatInputProps extends InputProps {
|
|
2599
|
+
* appearance?: 'fill' | 'outline';
|
|
2600
|
+
* hint?: string;
|
|
2601
|
+
* }
|
|
2602
|
+
* type MatInputField = InputField<MatInputProps>; // Automatically gets discriminated union
|
|
2603
|
+
*/
|
|
2604
|
+
type InputField<TProps extends {
|
|
2605
|
+
type?: string;
|
|
2606
|
+
} = InputProps> = BuildInputFieldUnion<TProps>;
|
|
2607
|
+
|
|
2608
|
+
interface CheckboxField<TProps> extends BaseCheckedField<TProps> {
|
|
2609
|
+
type: 'checkbox';
|
|
2610
|
+
}
|
|
2611
|
+
|
|
2612
|
+
interface SelectProps {
|
|
2613
|
+
placeholder?: DynamicText;
|
|
2614
|
+
}
|
|
2615
|
+
interface SelectField<T, TProps = SelectProps> extends BaseValueField<TProps, T> {
|
|
2616
|
+
type: 'select';
|
|
2617
|
+
readonly options: readonly FieldOption<T>[];
|
|
2618
|
+
}
|
|
2619
|
+
|
|
2620
|
+
/**
|
|
2621
|
+
* Event arguments that can contain static values or tokens to be resolved at runtime
|
|
2622
|
+
* Tokens supported: $key, $index, $arrayKey, formValue
|
|
2623
|
+
*/
|
|
2624
|
+
type EventArgs = readonly (string | number | boolean | null | undefined)[];
|
|
2625
|
+
/**
|
|
2626
|
+
* Interface for button fields
|
|
2627
|
+
*/
|
|
2628
|
+
interface ButtonField<TProps, TEvent extends FormEvent> extends FieldDef<TProps> {
|
|
2629
|
+
type: 'button';
|
|
2630
|
+
event: FormEventConstructor<TEvent>;
|
|
2631
|
+
/**
|
|
2632
|
+
* Optional arguments to pass to the event constructor.
|
|
2633
|
+
* Can contain special tokens that will be resolved at runtime:
|
|
2634
|
+
* - $key: The current field key
|
|
2635
|
+
* - $index: The array index (if inside an array field)
|
|
2636
|
+
* - $arrayKey: The parent array field key (if inside an array field)
|
|
2637
|
+
* - formValue: Access to the current form value for indexing
|
|
2638
|
+
*
|
|
2639
|
+
* @example
|
|
2640
|
+
* eventArgs: ['$arrayKey', '$index']
|
|
2641
|
+
* eventArgs: ['contacts', 0]
|
|
2642
|
+
*/
|
|
2643
|
+
eventArgs?: EventArgs;
|
|
2644
|
+
}
|
|
2645
|
+
|
|
2646
|
+
interface TextareaProps {
|
|
2647
|
+
placeholder?: DynamicText;
|
|
2648
|
+
rows?: number;
|
|
2649
|
+
cols?: number | undefined;
|
|
2650
|
+
}
|
|
2651
|
+
interface TextareaField<TProps = TextareaProps> extends BaseValueField<TProps, string> {
|
|
2652
|
+
type: 'textarea';
|
|
2653
|
+
maxLength?: number | undefined;
|
|
2654
|
+
}
|
|
2655
|
+
|
|
2656
|
+
interface RadioField<T, TProps> extends BaseValueField<TProps, T> {
|
|
2657
|
+
type: 'radio';
|
|
2658
|
+
options: FieldOption<T>[];
|
|
2659
|
+
}
|
|
2660
|
+
|
|
2661
|
+
/**
|
|
2662
|
+
* Multi-checkbox field for selecting multiple values from a list of options.
|
|
2663
|
+
* The value is an array of selected option values.
|
|
2664
|
+
*
|
|
2665
|
+
* @example
|
|
2666
|
+
* const tagsField: MultiCheckboxField<string> = {
|
|
2667
|
+
* type: 'multi-checkbox',
|
|
2668
|
+
* key: 'tags',
|
|
2669
|
+
* value: ['tag1', 'tag2'], // Array of selected values
|
|
2670
|
+
* options: [
|
|
2671
|
+
* { label: 'Tag 1', value: 'tag1' },
|
|
2672
|
+
* { label: 'Tag 2', value: 'tag2' },
|
|
2673
|
+
* { label: 'Tag 3', value: 'tag3' }
|
|
2674
|
+
* ]
|
|
2675
|
+
* };
|
|
2676
|
+
*/
|
|
2677
|
+
interface MultiCheckboxField<TValue, TProps = object> extends BaseValueField<TProps, TValue[]> {
|
|
2678
|
+
type: 'multi-checkbox';
|
|
2679
|
+
options: FieldOption<TValue>[];
|
|
2680
|
+
}
|
|
2681
|
+
|
|
2682
|
+
interface DatepickerProps {
|
|
2683
|
+
placeholder?: DynamicText;
|
|
2684
|
+
}
|
|
2685
|
+
interface DatepickerField<TProps> extends BaseValueField<TProps, Date | string> {
|
|
2686
|
+
type: 'datepicker';
|
|
2687
|
+
minDate?: Date | string | null;
|
|
2688
|
+
maxDate?: Date | string | null;
|
|
2689
|
+
startAt?: Date | null;
|
|
2690
|
+
}
|
|
2691
|
+
|
|
2692
|
+
interface SliderField<TProps> extends BaseValueField<TProps, number> {
|
|
2693
|
+
type: 'slider';
|
|
2694
|
+
minValue?: number;
|
|
2695
|
+
maxValue?: number;
|
|
2696
|
+
step?: number;
|
|
2697
|
+
}
|
|
2698
|
+
|
|
2699
|
+
interface ToggleField<TProps> extends BaseCheckedField<TProps> {
|
|
2700
|
+
type: 'toggle';
|
|
2701
|
+
}
|
|
2702
|
+
|
|
2703
|
+
/**
|
|
2704
|
+
* Apply validator configuration to field path, following the logic pattern.
|
|
2705
|
+
*
|
|
2706
|
+
* Accepts both SchemaPath and SchemaPathTree to support being called from both
|
|
2707
|
+
* individual field mappings and schema functions.
|
|
2708
|
+
*
|
|
2709
|
+
* Note: ValidatorConfig is runtime configuration that doesn't encode the field's
|
|
2710
|
+
* TypeScript type. The type assertions to specific types (string, number) are safe
|
|
2711
|
+
* because Angular's validators perform runtime value checks regardless of the static
|
|
2712
|
+
* type parameter.
|
|
2713
|
+
*
|
|
2714
|
+
* Cross-field validators (those referencing formValue.*) are skipped at field level.
|
|
2715
|
+
* They are collected by the parent form component using collectCrossFieldEntries
|
|
2716
|
+
* and executed at form level using validateTree.
|
|
2717
|
+
*
|
|
2718
|
+
* @param config The validator configuration
|
|
2719
|
+
* @param fieldPath The field path to apply the validator to
|
|
2720
|
+
* @param fieldKey Optional field key (used for logging)
|
|
2721
|
+
*/
|
|
2722
|
+
declare function applyValidator(config: ValidatorConfig, fieldPath: SchemaPath<any> | SchemaPathTree<any>, fieldKey?: string): void;
|
|
2723
|
+
/**
|
|
2724
|
+
* Apply multiple validators to a field path
|
|
2725
|
+
*
|
|
2726
|
+
* @param configs Array of validator configurations
|
|
2727
|
+
* @param fieldPath The field path to apply validators to
|
|
2728
|
+
* @param fieldKey Optional field key (used for logging)
|
|
2729
|
+
*/
|
|
2730
|
+
declare function applyValidators(configs: ValidatorConfig[], fieldPath: SchemaPath<any> | SchemaPathTree<any>, fieldKey?: string): void;
|
|
2731
|
+
|
|
2732
|
+
declare class SubmitEvent implements FormEvent {
|
|
2733
|
+
readonly type: "submit";
|
|
2734
|
+
}
|
|
2735
|
+
|
|
2736
|
+
declare class NextPageEvent implements FormEvent {
|
|
2737
|
+
readonly type: "next-page";
|
|
2738
|
+
}
|
|
2739
|
+
|
|
2740
|
+
declare class PreviousPageEvent implements FormEvent {
|
|
2741
|
+
readonly type: "previous-page";
|
|
2742
|
+
}
|
|
2743
|
+
|
|
2744
|
+
/**
|
|
2745
|
+
* Event dispatched to add a new item to an array field.
|
|
2746
|
+
*
|
|
2747
|
+
* This event is used to dynamically add items to array fields at runtime.
|
|
2748
|
+
* The array field component listens for this event and adds a new item
|
|
2749
|
+
* based on the provided field template, or uses the array's own template if not provided.
|
|
2750
|
+
*
|
|
2751
|
+
* @example
|
|
2752
|
+
* ```typescript
|
|
2753
|
+
* // Add item using array's own template
|
|
2754
|
+
* eventBus.dispatch(AddArrayItemEvent, 'contacts');
|
|
2755
|
+
*
|
|
2756
|
+
* // Add item with explicit field template
|
|
2757
|
+
* const fieldTemplate = { key: 'email', type: 'input' };
|
|
2758
|
+
* eventBus.dispatch(AddArrayItemEvent, 'contacts', fieldTemplate);
|
|
2759
|
+
*
|
|
2760
|
+
* // Add item at specific index
|
|
2761
|
+
* eventBus.dispatch(AddArrayItemEvent, 'contacts', fieldTemplate, 2);
|
|
2762
|
+
* ```
|
|
2763
|
+
*/
|
|
2764
|
+
declare class AddArrayItemEvent implements FormEvent {
|
|
2765
|
+
/** The key of the array field to add an item to */
|
|
2766
|
+
readonly arrayKey: string;
|
|
2767
|
+
/**
|
|
2768
|
+
* Optional field template definition to render for the new array item.
|
|
2769
|
+
* If not provided, the array field component will use its own template.
|
|
2770
|
+
*/
|
|
2771
|
+
readonly field?: ArrayAllowedChildren | undefined;
|
|
2772
|
+
/** Optional index where to insert the item (defaults to end of array) */
|
|
2773
|
+
readonly index?: number | undefined;
|
|
2774
|
+
readonly type: "add-array-item";
|
|
2775
|
+
constructor(
|
|
2776
|
+
/** The key of the array field to add an item to */
|
|
2777
|
+
arrayKey: string,
|
|
2778
|
+
/**
|
|
2779
|
+
* Optional field template definition to render for the new array item.
|
|
2780
|
+
* If not provided, the array field component will use its own template.
|
|
2781
|
+
*/
|
|
2782
|
+
field?: ArrayAllowedChildren | undefined,
|
|
2783
|
+
/** Optional index where to insert the item (defaults to end of array) */
|
|
2784
|
+
index?: number | undefined);
|
|
2785
|
+
}
|
|
2786
|
+
|
|
2787
|
+
/**
|
|
2788
|
+
* Event dispatched to remove an item from an array field.
|
|
2789
|
+
*
|
|
2790
|
+
* This event is used to dynamically remove items from array fields at runtime.
|
|
2791
|
+
* The array field component listens for this event and removes the item
|
|
2792
|
+
* at the specified index.
|
|
2793
|
+
*
|
|
2794
|
+
* @example
|
|
2795
|
+
* ```typescript
|
|
2796
|
+
* // Remove item at index 1 from 'contacts' array
|
|
2797
|
+
* eventBus.dispatch(RemoveArrayItemEvent, 'contacts', 1);
|
|
2798
|
+
*
|
|
2799
|
+
* // Remove last item
|
|
2800
|
+
* eventBus.dispatch(RemoveArrayItemEvent, 'contacts');
|
|
2801
|
+
* ```
|
|
2802
|
+
*/
|
|
2803
|
+
declare class RemoveArrayItemEvent implements FormEvent {
|
|
2804
|
+
/** The key of the array field to remove an item from */
|
|
2805
|
+
readonly arrayKey: string;
|
|
2806
|
+
/** Index of the item to remove (defaults to last item) */
|
|
2807
|
+
readonly index?: number | undefined;
|
|
2808
|
+
readonly type: "remove-array-item";
|
|
2809
|
+
constructor(
|
|
2810
|
+
/** The key of the array field to remove an item from */
|
|
2811
|
+
arrayKey: string,
|
|
2812
|
+
/** Index of the item to remove (defaults to last item) */
|
|
2813
|
+
index?: number | undefined);
|
|
2814
|
+
}
|
|
2815
|
+
|
|
2816
|
+
/**
|
|
2817
|
+
* Event bus service for form-wide event communication.
|
|
2818
|
+
*
|
|
2819
|
+
* Provides type-safe event dispatching and subscription for form components
|
|
2820
|
+
* to communicate without direct coupling. Uses RxJS observables for reactive
|
|
2821
|
+
* event handling and supports both single event type and multiple event type subscriptions.
|
|
2822
|
+
*
|
|
2823
|
+
* @example
|
|
2824
|
+
* ```typescript
|
|
2825
|
+
* @Component({
|
|
2826
|
+
* providers: [EventBus]
|
|
2827
|
+
* })
|
|
2828
|
+
* export class MyFormComponent {
|
|
2829
|
+
* constructor(private eventBus: EventBus) {
|
|
2830
|
+
* // Subscribe to form submission events
|
|
2831
|
+
* this.eventBus.on<SubmitEvent>('submit').subscribe(() => {
|
|
2832
|
+
* console.log('Form was submitted');
|
|
2833
|
+
* });
|
|
2834
|
+
*
|
|
2835
|
+
* // Subscribe to multiple event types
|
|
2836
|
+
* this.eventBus.on<SubmitEvent | FormResetEvent>(['submit', 'form-reset']).subscribe(event => {
|
|
2837
|
+
* console.log('Form event:', event.type);
|
|
2838
|
+
* });
|
|
2839
|
+
* }
|
|
2840
|
+
*
|
|
2841
|
+
* onSubmit() {
|
|
2842
|
+
* // Dispatch a submit event
|
|
2843
|
+
* this.eventBus.dispatch(SubmitEvent);
|
|
2844
|
+
* }
|
|
2845
|
+
* }
|
|
2846
|
+
* ```
|
|
2847
|
+
*/
|
|
2848
|
+
declare class EventBus {
|
|
2849
|
+
private pipeline$;
|
|
2850
|
+
events$: Observable<FormEvent>;
|
|
2851
|
+
/**
|
|
2852
|
+
* Dispatches an event to all subscribers.
|
|
2853
|
+
*
|
|
2854
|
+
* Creates an instance of the provided event constructor and broadcasts it
|
|
2855
|
+
* through the event pipeline to all active subscribers.
|
|
2856
|
+
*
|
|
2857
|
+
* @param eventConstructor - Constructor function for the event to dispatch
|
|
2858
|
+
*
|
|
2859
|
+
* @example
|
|
2860
|
+
* ```typescript
|
|
2861
|
+
* // Dispatch a submit event
|
|
2862
|
+
* eventBus.dispatch(SubmitEvent);
|
|
2863
|
+
*
|
|
2864
|
+
* // Dispatch a custom event
|
|
2865
|
+
* eventBus.dispatch(CustomFormEvent);
|
|
2866
|
+
* ```
|
|
2867
|
+
*/
|
|
2868
|
+
dispatch<T extends FormEventConstructor>(eventConstructor: T, ...args: any[]): void;
|
|
2869
|
+
/**
|
|
2870
|
+
* Subscribes to events of a specific type.
|
|
2871
|
+
*
|
|
2872
|
+
* @param eventType - The type of event to subscribe to
|
|
2873
|
+
* @returns Observable that emits events of the specified type
|
|
2874
|
+
*/
|
|
2875
|
+
on<T extends FormEvent>(eventType: T['type']): Observable<T>;
|
|
2876
|
+
/**
|
|
2877
|
+
* Subscribes to events of multiple types.
|
|
2878
|
+
*
|
|
2879
|
+
* @param eventType - Array of event types to subscribe to
|
|
2880
|
+
* @returns Observable that emits events matching any of the specified types
|
|
2881
|
+
*/
|
|
2882
|
+
on<T extends FormEvent>(eventType: Array<T['type']>): Observable<T>;
|
|
2883
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<EventBus, never>;
|
|
2884
|
+
static ɵprov: _angular_core.ɵɵInjectableDeclaration<EventBus>;
|
|
2885
|
+
}
|
|
2886
|
+
|
|
2887
|
+
/**
|
|
2888
|
+
* Context for token resolution in event args
|
|
2889
|
+
*/
|
|
2890
|
+
interface TokenContext {
|
|
2891
|
+
/** Current field key */
|
|
2892
|
+
key?: string;
|
|
2893
|
+
/** Array index if field is inside an array */
|
|
2894
|
+
index?: number;
|
|
2895
|
+
/** Parent array field key if field is inside an array */
|
|
2896
|
+
arrayKey?: string;
|
|
2897
|
+
/** Current form value for complex indexing */
|
|
2898
|
+
formValue?: unknown;
|
|
2899
|
+
}
|
|
2900
|
+
/**
|
|
2901
|
+
* Resolves special tokens in event arguments to their actual values
|
|
2902
|
+
*
|
|
2903
|
+
* Supported tokens:
|
|
2904
|
+
* - $key: The current field key
|
|
2905
|
+
* - $index: The array index (if inside an array field)
|
|
2906
|
+
* - $arrayKey: The parent array field key (if inside an array field)
|
|
2907
|
+
* - formValue: Reference to the current form value for indexing
|
|
2908
|
+
*
|
|
2909
|
+
* @param args - Array of arguments that may contain tokens
|
|
2910
|
+
* @param context - Context object containing token values
|
|
2911
|
+
* @returns Array with resolved values
|
|
2912
|
+
*
|
|
2913
|
+
* @example
|
|
2914
|
+
* resolveTokens(['$arrayKey', '$index'], { arrayKey: 'contacts', index: 2 })
|
|
2915
|
+
* // Returns: ['contacts', 2]
|
|
2916
|
+
*
|
|
2917
|
+
* @example
|
|
2918
|
+
* resolveTokens(['$key', 'static'], { key: 'myField' })
|
|
2919
|
+
* // Returns: ['myField', 'static']
|
|
2920
|
+
*/
|
|
2921
|
+
declare function resolveTokens(args: readonly (string | number | boolean | null | undefined)[], context: TokenContext): (string | number | boolean | null | undefined | unknown)[];
|
|
2922
|
+
|
|
2923
|
+
/**
|
|
2924
|
+
* Context object for array item operations (add/remove)
|
|
2925
|
+
* Provides the necessary information to resolve tokens in array item event arguments
|
|
2926
|
+
*/
|
|
2927
|
+
interface ArrayItemContext {
|
|
2928
|
+
/** The key of the field */
|
|
2929
|
+
key: string;
|
|
2930
|
+
/** Array index for the item */
|
|
2931
|
+
index?: number;
|
|
2932
|
+
/** Parent array field key */
|
|
2933
|
+
arrayKey?: string;
|
|
2934
|
+
/** Form value for token resolution */
|
|
2935
|
+
formValue?: unknown;
|
|
2936
|
+
}
|
|
2937
|
+
|
|
2938
|
+
/**
|
|
2939
|
+
* Maps a value field definition to component inputs.
|
|
2940
|
+
*
|
|
2941
|
+
* Value fields are input fields that contribute to the form's value (text, number, etc.).
|
|
2942
|
+
* This mapper injects FIELD_SIGNAL_CONTEXT to access the form structure and retrieve the field proxy.
|
|
2943
|
+
*
|
|
2944
|
+
* For fields with specific properties (select, datepicker, textarea, slider), use the specialized mappers:
|
|
2945
|
+
* - selectFieldMapper: for fields with options (select, radio, multi-checkbox)
|
|
2946
|
+
* - datepickerFieldMapper: for fields with minDate, maxDate, startAt
|
|
2947
|
+
* - textareaFieldMapper: for fields with rows, cols
|
|
2948
|
+
* - sliderFieldMapper: for fields with minValue, maxValue, step
|
|
2949
|
+
*
|
|
2950
|
+
* @param fieldDef The value field definition
|
|
2951
|
+
* @returns Signal containing Record of input names to values for ngComponentOutlet
|
|
2952
|
+
*/
|
|
2953
|
+
declare function valueFieldMapper<TProps = unknown, TValue = unknown>(fieldDef: BaseValueField<TProps, TValue>): Signal<Record<string, unknown>>;
|
|
2954
|
+
|
|
2955
|
+
/**
|
|
2956
|
+
* Builds base input properties from a field definition.
|
|
2957
|
+
*
|
|
2958
|
+
* This is a helper function that extracts common field properties.
|
|
2959
|
+
* Used by mappers to build the inputs record.
|
|
2960
|
+
*
|
|
2961
|
+
* @param fieldDef The field definition to extract properties from
|
|
2962
|
+
* @returns Record of input names to values
|
|
2963
|
+
*/
|
|
2964
|
+
declare function buildBaseInputs(fieldDef: FieldDef<unknown>): Record<string, unknown>;
|
|
2965
|
+
/**
|
|
2966
|
+
* Base field mapper that extracts common field properties into component inputs.
|
|
2967
|
+
*
|
|
2968
|
+
* Returns a Signal containing the Record of input names to values that will be
|
|
2969
|
+
* passed to ngComponentOutlet. The signal enables reactive updates.
|
|
2970
|
+
*
|
|
2971
|
+
* @param fieldDef The field definition to map
|
|
2972
|
+
* @returns Signal containing Record of input names to values
|
|
2973
|
+
*/
|
|
2974
|
+
declare function baseFieldMapper(fieldDef: FieldDef<unknown>): Signal<Record<string, unknown>>;
|
|
2975
|
+
|
|
2976
|
+
/**
|
|
2977
|
+
* Maps a checkbox/toggle field definition to component inputs.
|
|
2978
|
+
*
|
|
2979
|
+
* Checkbox fields are checked fields that contribute to the form's value as boolean.
|
|
2980
|
+
* This mapper injects FIELD_SIGNAL_CONTEXT to access the form structure and retrieve the field proxy.
|
|
2981
|
+
*
|
|
2982
|
+
* @param fieldDef The checkbox field definition
|
|
2983
|
+
* @returns Signal containing Record of input names to values for ngComponentOutlet
|
|
2984
|
+
*/
|
|
2985
|
+
declare function checkboxFieldMapper(fieldDef: BaseCheckedField<unknown>): Signal<Record<string, unknown>>;
|
|
2986
|
+
|
|
2987
|
+
/**
|
|
2988
|
+
* Maps a row field definition to component inputs.
|
|
2989
|
+
*
|
|
2990
|
+
* Row components are layout containers that don't change the form shape.
|
|
2991
|
+
* The row component will inject FIELD_SIGNAL_CONTEXT directly.
|
|
2992
|
+
*
|
|
2993
|
+
* @param fieldDef The row field definition
|
|
2994
|
+
* @returns Signal containing Record of input names to values for ngComponentOutlet
|
|
2995
|
+
*/
|
|
2996
|
+
declare function rowFieldMapper(fieldDef: RowField): Signal<Record<string, unknown>>;
|
|
2997
|
+
|
|
2998
|
+
/**
|
|
2999
|
+
* Maps a group field definition to component inputs.
|
|
3000
|
+
*
|
|
3001
|
+
* Group components create nested form structures under the group's key.
|
|
3002
|
+
* The group component will inject the parent FIELD_SIGNAL_CONTEXT and create
|
|
3003
|
+
* a scoped child injector for its nested fields.
|
|
3004
|
+
*
|
|
3005
|
+
* @param fieldDef The group field definition
|
|
3006
|
+
* @returns Signal containing Record of input names to values for ngComponentOutlet
|
|
3007
|
+
*/
|
|
3008
|
+
declare function groupFieldMapper(fieldDef: GroupField): Signal<Record<string, unknown>>;
|
|
3009
|
+
|
|
3010
|
+
/**
|
|
3011
|
+
* Maps an array field definition to component inputs.
|
|
3012
|
+
*
|
|
3013
|
+
* Array components create nested form structures under the array's key.
|
|
3014
|
+
* The array component will inject the parent FIELD_SIGNAL_CONTEXT and create
|
|
3015
|
+
* a scoped child injector for its array item fields.
|
|
3016
|
+
*
|
|
3017
|
+
* @param fieldDef The array field definition
|
|
3018
|
+
* @returns Signal containing Record of input names to values for ngComponentOutlet
|
|
3019
|
+
*/
|
|
3020
|
+
declare function arrayFieldMapper(fieldDef: ArrayField): Signal<Record<string, unknown>>;
|
|
3021
|
+
|
|
3022
|
+
/**
|
|
3023
|
+
* Maps a page field definition to component inputs.
|
|
3024
|
+
*
|
|
3025
|
+
* Page fields are layout containers that don't modify the form context.
|
|
3026
|
+
* The page component will inject FIELD_SIGNAL_CONTEXT directly.
|
|
3027
|
+
*
|
|
3028
|
+
* @param fieldDef The page field definition
|
|
3029
|
+
* @returns Signal containing Record of input names to values for ngComponentOutlet
|
|
3030
|
+
*/
|
|
3031
|
+
declare function pageFieldMapper(fieldDef: PageField): Signal<Record<string, unknown>>;
|
|
3032
|
+
|
|
3033
|
+
/**
|
|
3034
|
+
* Field types that have an options property.
|
|
3035
|
+
*/
|
|
3036
|
+
type FieldWithOptions<T = unknown, TProps = unknown> = SelectField<T, TProps> | RadioField<T, TProps> | MultiCheckboxField<T, TProps>;
|
|
3037
|
+
/**
|
|
3038
|
+
* Maps a field with options (select, radio, multi-checkbox) to component inputs.
|
|
3039
|
+
*
|
|
3040
|
+
* Extends the base value field mapper by adding the options property.
|
|
3041
|
+
*
|
|
3042
|
+
* @param fieldDef The field definition with options
|
|
3043
|
+
* @returns Signal containing Record of input names to values for ngComponentOutlet
|
|
3044
|
+
*/
|
|
3045
|
+
declare function optionsFieldMapper<T, TProps>(fieldDef: FieldWithOptions<T, TProps>): Signal<Record<string, unknown>>;
|
|
3046
|
+
|
|
3047
|
+
/**
|
|
3048
|
+
* Maps a datepicker field to component inputs.
|
|
3049
|
+
*
|
|
3050
|
+
* Extends the base value field mapper by adding datepicker-specific properties:
|
|
3051
|
+
* - minDate: Minimum selectable date
|
|
3052
|
+
* - maxDate: Maximum selectable date
|
|
3053
|
+
* - startAt: Initial date to display when opening the picker
|
|
3054
|
+
*
|
|
3055
|
+
* @param fieldDef The datepicker field definition
|
|
3056
|
+
* @returns Signal containing Record of input names to values for ngComponentOutlet
|
|
3057
|
+
*/
|
|
3058
|
+
declare function datepickerFieldMapper<TProps>(fieldDef: DatepickerField<TProps>): Signal<Record<string, unknown>>;
|
|
3059
|
+
|
|
3060
|
+
/**
|
|
3061
|
+
* Converts DynamicText (string | Observable | Signal) to Observable<string>
|
|
3062
|
+
* Unifies all three types into a consistent Observable stream
|
|
3063
|
+
*
|
|
3064
|
+
* @param value - The dynamic text value to convert
|
|
3065
|
+
* @param injector - Optional injector for signal conversion
|
|
3066
|
+
* @returns Observable<string> - The value as an observable stream
|
|
3067
|
+
*/
|
|
3068
|
+
declare function dynamicTextToObservable(value: DynamicText | undefined, injector?: Injector): Observable<string>;
|
|
3069
|
+
|
|
3070
|
+
/**
|
|
3071
|
+
* Computed signal that determines if errors should be displayed
|
|
3072
|
+
* Based on field's invalid, touched, and error count
|
|
3073
|
+
*
|
|
3074
|
+
* @param field - Signal containing FieldTree
|
|
3075
|
+
* @returns Signal<boolean> - True if errors should be displayed
|
|
3076
|
+
*/
|
|
3077
|
+
declare function shouldShowErrors<T>(field: Signal<FieldTree<T>>): Signal<boolean>;
|
|
3078
|
+
|
|
3079
|
+
/**
|
|
3080
|
+
* Resolved validation error with interpolated message
|
|
3081
|
+
*/
|
|
3082
|
+
interface ResolvedError {
|
|
3083
|
+
kind: string;
|
|
3084
|
+
message: string;
|
|
3085
|
+
}
|
|
3086
|
+
/**
|
|
3087
|
+
* Factory function that creates a signal of resolved validation errors
|
|
3088
|
+
* Handles async resolution of DynamicText validation messages
|
|
3089
|
+
*
|
|
3090
|
+
* @param field - Signal containing FieldTree
|
|
3091
|
+
* @param validationMessages - Signal containing custom field-level validation messages
|
|
3092
|
+
* @param defaultValidationMessages - Signal containing default validation messages (fallback)
|
|
3093
|
+
* @param injector - Optional injector for DynamicText resolution
|
|
3094
|
+
* @returns Signal<ResolvedError[]> - Reactively resolved error messages
|
|
3095
|
+
*
|
|
3096
|
+
* @example
|
|
3097
|
+
* ```typescript
|
|
3098
|
+
* readonly resolvedErrors = createResolvedErrorsSignal(
|
|
3099
|
+
* this.field,
|
|
3100
|
+
* this.validationMessages,
|
|
3101
|
+
* this.defaultValidationMessages
|
|
3102
|
+
* );
|
|
3103
|
+
* ```
|
|
3104
|
+
*/
|
|
3105
|
+
declare function createResolvedErrorsSignal<T>(field: Signal<FieldTree<T>>, validationMessages: Signal<ValidationMessages | undefined>, defaultValidationMessages?: Signal<ValidationMessages | undefined>, injector?: Injector): Signal<ResolvedError[]>;
|
|
3106
|
+
|
|
3107
|
+
/**
|
|
3108
|
+
* Internal Angular Signal Forms structure interface.
|
|
3109
|
+
*
|
|
3110
|
+
* WARNING: These properties are internal Angular APIs and may change between versions.
|
|
3111
|
+
* This interface documents our dependency on Angular internals for form structure access.
|
|
3112
|
+
*
|
|
3113
|
+
* Used to access:
|
|
3114
|
+
* - `structure.childrenMap()` - Map of child field keys to their FieldTree instances
|
|
3115
|
+
* - `fieldProxy` - The FieldTree proxy for a FieldNode
|
|
3116
|
+
*/
|
|
3117
|
+
interface FormInternals<T = unknown> {
|
|
3118
|
+
/**
|
|
3119
|
+
* Internal structure containing form graph information.
|
|
3120
|
+
* Present on forms created via `form()` function.
|
|
3121
|
+
*/
|
|
3122
|
+
structure?: {
|
|
3123
|
+
/**
|
|
3124
|
+
* Returns a Map of child field keys to their FieldTree instances.
|
|
3125
|
+
* Keys are strings for object properties or stringified indices for arrays.
|
|
3126
|
+
*/
|
|
3127
|
+
childrenMap?: () => Map<string, FieldTree<unknown>>;
|
|
3128
|
+
};
|
|
3129
|
+
/**
|
|
3130
|
+
* The FieldTree proxy for navigating the form graph.
|
|
3131
|
+
* Present on FieldNode instances.
|
|
3132
|
+
*/
|
|
3133
|
+
fieldProxy?: FieldTree<T>;
|
|
3134
|
+
}
|
|
3135
|
+
/**
|
|
3136
|
+
* Type guard for checking if a form-like object has the internal structure property.
|
|
3137
|
+
*
|
|
3138
|
+
* @param form - The form object to check
|
|
3139
|
+
* @returns True if the form has a structure property with childrenMap
|
|
3140
|
+
*/
|
|
3141
|
+
declare function hasFormStructure(form: unknown): form is FormInternals;
|
|
3142
|
+
/**
|
|
3143
|
+
* Safely retrieves the childrenMap from a form's internal structure.
|
|
3144
|
+
*
|
|
3145
|
+
* @param form - The form object (from `form()` function or FieldTree)
|
|
3146
|
+
* @returns The childrenMap or null if not available
|
|
3147
|
+
*/
|
|
3148
|
+
declare function getChildrenMap(form: unknown): Map<string, FieldTree<unknown>> | null;
|
|
3149
|
+
/**
|
|
3150
|
+
* Safely retrieves the fieldProxy from a form node.
|
|
3151
|
+
*
|
|
3152
|
+
* @param node - The form node (FieldNode or similar)
|
|
3153
|
+
* @returns The fieldProxy FieldTree or null if not available
|
|
3154
|
+
*/
|
|
3155
|
+
declare function getFieldProxy<T = unknown>(node: unknown): FieldTree<T> | null;
|
|
3156
|
+
/**
|
|
3157
|
+
* Retrieves a child FieldTree from a form by key.
|
|
3158
|
+
*
|
|
3159
|
+
* @param form - The parent form
|
|
3160
|
+
* @param key - The child field key
|
|
3161
|
+
* @returns The child FieldTree or null if not found
|
|
3162
|
+
*/
|
|
3163
|
+
declare function getChildFieldTree(form: unknown, key: string): FieldTree<unknown> | null;
|
|
3164
|
+
|
|
3165
|
+
/**
|
|
3166
|
+
* Pipe that handles dynamic text resolution with support for static strings,
|
|
3167
|
+
* Observables, and Signals.
|
|
3168
|
+
*
|
|
3169
|
+
* Supports:
|
|
3170
|
+
* - Static strings (pass-through)
|
|
3171
|
+
* - Observables (subscribed internally)
|
|
3172
|
+
* - Signals (converted to Observable using toObservable for reactivity)
|
|
3173
|
+
*
|
|
3174
|
+
* @example
|
|
3175
|
+
* ```html
|
|
3176
|
+
* <!-- Static string -->
|
|
3177
|
+
* {{ 'Hello World' | dynamicText | async }}
|
|
3178
|
+
*
|
|
3179
|
+
* <!-- Observable -->
|
|
3180
|
+
* {{ transloco.selectTranslate('key') | dynamicText | async }}
|
|
3181
|
+
*
|
|
3182
|
+
* <!-- Signal -->
|
|
3183
|
+
* {{ myTextSignal | dynamicText | async }}
|
|
3184
|
+
* ```
|
|
3185
|
+
*
|
|
3186
|
+
* @public
|
|
3187
|
+
*/
|
|
3188
|
+
declare class DynamicTextPipe implements PipeTransform {
|
|
3189
|
+
private readonly injector;
|
|
3190
|
+
/**
|
|
3191
|
+
* Transforms dynamic text input into a resolved string value
|
|
3192
|
+
*
|
|
3193
|
+
* @param value - The dynamic text value to resolve
|
|
3194
|
+
* @returns The resolved string value as an Observable
|
|
3195
|
+
*/
|
|
3196
|
+
transform(value: DynamicText | undefined): Observable<string>;
|
|
3197
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<DynamicTextPipe, never>;
|
|
3198
|
+
static ɵpipe: _angular_core.ɵɵPipeDeclaration<DynamicTextPipe, "dynamicText", true>;
|
|
3199
|
+
}
|
|
3200
|
+
|
|
3201
|
+
/**
|
|
3202
|
+
* Layout container for horizontal field arrangement.
|
|
3203
|
+
*
|
|
3204
|
+
* Does not create a new form context - fields share the parent's context.
|
|
3205
|
+
* Field values are flattened into the parent form (no nesting under row key).
|
|
3206
|
+
* Purely a visual/layout container with no impact on form structure.
|
|
3207
|
+
*/
|
|
3208
|
+
declare class RowFieldComponent {
|
|
3209
|
+
private readonly destroyRef;
|
|
3210
|
+
private readonly fieldRegistry;
|
|
3211
|
+
private readonly injector;
|
|
3212
|
+
private readonly eventBus;
|
|
3213
|
+
field: _angular_core.InputSignal<RowField<readonly _ng_forge_dynamic_forms.RowAllowedChildren[]>>;
|
|
3214
|
+
key: _angular_core.InputSignal<string>;
|
|
3215
|
+
readonly disabled: _angular_core.Signal<boolean>;
|
|
3216
|
+
private readonly rawFieldRegistry;
|
|
3217
|
+
private readonly fieldsSource;
|
|
3218
|
+
protected readonly resolvedFields: _angular_core.Signal<ResolvedField[]>;
|
|
3219
|
+
private readonly emitInitializedOnFieldsResolved;
|
|
3220
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<RowFieldComponent, never>;
|
|
3221
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<RowFieldComponent, "div[row-field]", never, { "field": { "alias": "field"; "required": true; "isSignal": true; }; "key": { "alias": "key"; "required": true; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
3222
|
+
}
|
|
3223
|
+
|
|
3224
|
+
/**
|
|
3225
|
+
* Container component for rendering nested form groups.
|
|
3226
|
+
*
|
|
3227
|
+
* Creates a scoped form context with its own validation state.
|
|
3228
|
+
* Child fields receive a FIELD_SIGNAL_CONTEXT scoped to this group's form instance.
|
|
3229
|
+
* Group values are nested under the group's key in the parent form.
|
|
3230
|
+
*/
|
|
3231
|
+
declare class GroupFieldComponent<TModel = Record<string, unknown>> {
|
|
3232
|
+
private readonly destroyRef;
|
|
3233
|
+
private readonly fieldRegistry;
|
|
3234
|
+
private readonly parentFieldSignalContext;
|
|
3235
|
+
private readonly injector;
|
|
3236
|
+
private readonly eventBus;
|
|
3237
|
+
private readonly memoizedFlattenFields;
|
|
3238
|
+
private readonly memoizedKeyBy;
|
|
3239
|
+
private readonly memoizedDefaultValues;
|
|
3240
|
+
field: _angular_core.InputSignal<GroupField<readonly _ng_forge_dynamic_forms.GroupAllowedChildren[]>>;
|
|
3241
|
+
key: _angular_core.InputSignal<string>;
|
|
3242
|
+
private readonly rawFieldRegistry;
|
|
3243
|
+
private readonly formSetup;
|
|
3244
|
+
readonly defaultValues: _angular_core.WritableSignal<Record<string, unknown>>;
|
|
3245
|
+
private readonly entity;
|
|
3246
|
+
private readonly form;
|
|
3247
|
+
readonly formValue: _angular_core.Signal<{}>;
|
|
3248
|
+
readonly valid: _angular_core.Signal<boolean>;
|
|
3249
|
+
readonly invalid: _angular_core.Signal<boolean>;
|
|
3250
|
+
readonly dirty: _angular_core.Signal<boolean>;
|
|
3251
|
+
readonly touched: _angular_core.Signal<boolean>;
|
|
3252
|
+
readonly errors: _angular_core.Signal<_angular_forms_signals.ValidationError.WithField[]>;
|
|
3253
|
+
readonly disabled: _angular_core.Signal<boolean>;
|
|
3254
|
+
/**
|
|
3255
|
+
* Get the nested FieldTree from the parent form for this group.
|
|
3256
|
+
* This allows child fields to update the parent form directly,
|
|
3257
|
+
* avoiding the need for separate form synchronization.
|
|
3258
|
+
*/
|
|
3259
|
+
private readonly nestedFieldTree;
|
|
3260
|
+
private readonly groupInjector;
|
|
3261
|
+
readonly validityChange: _angular_core.OutputRef<boolean>;
|
|
3262
|
+
readonly dirtyChange: _angular_core.OutputRef<boolean>;
|
|
3263
|
+
readonly submitted: _angular_core.OutputRef<SubmitEvent>;
|
|
3264
|
+
private readonly fieldsSource;
|
|
3265
|
+
protected readonly resolvedFields: _angular_core.Signal<ResolvedField[]>;
|
|
3266
|
+
private readonly emitInitializedOnFieldsResolved;
|
|
3267
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<GroupFieldComponent<any>, never>;
|
|
3268
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<GroupFieldComponent<any>, "fieldset[group-field]", never, { "field": { "alias": "field"; "required": true; "isSignal": true; }; "key": { "alias": "key"; "required": true; "isSignal": true; }; }, { "validityChange": "validityChange"; "dirtyChange": "dirtyChange"; "submitted": "submitted"; }, never, never, true, never>;
|
|
3269
|
+
}
|
|
3270
|
+
|
|
3271
|
+
/**
|
|
3272
|
+
* Resolved array item ready for declarative rendering with ngComponentOutlet.
|
|
3273
|
+
* Each item has a stable unique ID and a linkedSignal-based index that
|
|
3274
|
+
* automatically updates when items are added/removed.
|
|
3275
|
+
*/
|
|
3276
|
+
interface ResolvedArrayItem {
|
|
3277
|
+
/** Unique identifier for this item (stable across position changes). */
|
|
3278
|
+
id: string;
|
|
3279
|
+
/** The loaded component type. */
|
|
3280
|
+
component: Type<unknown>;
|
|
3281
|
+
/** Injector providing ARRAY_CONTEXT and FIELD_SIGNAL_CONTEXT. */
|
|
3282
|
+
injector: Injector;
|
|
3283
|
+
/** Inputs signal for ngComponentOutlet - evaluated in template for reactivity. */
|
|
3284
|
+
inputs: Signal<Record<string, unknown>>;
|
|
3285
|
+
}
|
|
3286
|
+
|
|
3287
|
+
/**
|
|
3288
|
+
* Container component for rendering dynamic arrays of fields.
|
|
3289
|
+
*
|
|
3290
|
+
* Supports add/remove operations via AddArrayItemEvent and RemoveArrayItemEvent.
|
|
3291
|
+
* Uses differential updates to optimize rendering - only recreates items when necessary.
|
|
3292
|
+
* Each item gets a scoped injector with ARRAY_CONTEXT for position-aware operations.
|
|
3293
|
+
*/
|
|
3294
|
+
declare class ArrayFieldComponent<TModel = Record<string, unknown>> {
|
|
3295
|
+
private readonly destroyRef;
|
|
3296
|
+
private readonly fieldRegistry;
|
|
3297
|
+
private readonly parentFieldSignalContext;
|
|
3298
|
+
private readonly parentInjector;
|
|
3299
|
+
private readonly eventBus;
|
|
3300
|
+
field: _angular_core.InputSignal<ArrayField<readonly _ng_forge_dynamic_forms.ArrayAllowedChildren[]>>;
|
|
3301
|
+
key: _angular_core.InputSignal<string>;
|
|
3302
|
+
private readonly rawFieldRegistry;
|
|
3303
|
+
private readonly fieldTemplate;
|
|
3304
|
+
private readonly arrayFieldTrees;
|
|
3305
|
+
private readonly resolvedItemsSignal;
|
|
3306
|
+
private readonly updateVersion;
|
|
3307
|
+
private readonly itemOrderSignal;
|
|
3308
|
+
readonly resolvedItems: _angular_core.WritableSignal<ResolvedArrayItem[]>;
|
|
3309
|
+
private readonly syncFieldsOnArrayChange;
|
|
3310
|
+
private readonly handleAddItem;
|
|
3311
|
+
private readonly handleRemoveItem;
|
|
3312
|
+
private performDifferentialUpdate;
|
|
3313
|
+
private resolveAllItems;
|
|
3314
|
+
private appendItems;
|
|
3315
|
+
private createResolveItemObservable;
|
|
3316
|
+
private addItem;
|
|
3317
|
+
private removeItem;
|
|
3318
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<ArrayFieldComponent<any>, never>;
|
|
3319
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<ArrayFieldComponent<any>, "array-field", never, { "field": { "alias": "field"; "required": true; "isSignal": true; }; "key": { "alias": "key"; "required": true; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
3320
|
+
}
|
|
3321
|
+
|
|
3322
|
+
/**
|
|
3323
|
+
* Injection function for accessing the dynamic form field registry.
|
|
3324
|
+
*
|
|
3325
|
+
* Provides a convenient API for interacting with registered field types,
|
|
3326
|
+
* including type checking, component loading, and registration management.
|
|
3327
|
+
* Must be called within an injection context.
|
|
3328
|
+
*
|
|
3329
|
+
* @returns Object with methods for field registry interaction
|
|
3330
|
+
*
|
|
3331
|
+
* @example
|
|
3332
|
+
* ```typescript
|
|
3333
|
+
* @Component({...})
|
|
3334
|
+
* export class MyComponent {
|
|
3335
|
+
* private fieldRegistry = injectFieldRegistry();
|
|
3336
|
+
*
|
|
3337
|
+
* async loadCustomField() {
|
|
3338
|
+
* if (this.fieldRegistry.hasType('custom-input')) {
|
|
3339
|
+
* const component = await this.fieldRegistry.loadTypeComponent('custom-input');
|
|
3340
|
+
* // Use component...
|
|
3341
|
+
* }
|
|
3342
|
+
* }
|
|
3343
|
+
* }
|
|
3344
|
+
* ```
|
|
3345
|
+
*
|
|
3346
|
+
* @example
|
|
3347
|
+
* ```typescript
|
|
3348
|
+
* // In a service
|
|
3349
|
+
* @Injectable()
|
|
3350
|
+
* export class FormBuilderService {
|
|
3351
|
+
* private fieldRegistry = injectFieldRegistry();
|
|
3352
|
+
*
|
|
3353
|
+
* getAvailableFieldTypes(): string[] {
|
|
3354
|
+
* return this.fieldRegistry.getTypes().map(type => type.name);
|
|
3355
|
+
* }
|
|
3356
|
+
* }
|
|
3357
|
+
* ```
|
|
3358
|
+
*
|
|
3359
|
+
* @public
|
|
3360
|
+
*/
|
|
3361
|
+
declare function injectFieldRegistry(): {
|
|
3362
|
+
/**
|
|
3363
|
+
* Retrieves a field type definition by its registered name.
|
|
3364
|
+
*
|
|
3365
|
+
* @param name - The registered name of the field type
|
|
3366
|
+
* @returns The field type definition if found, undefined otherwise
|
|
3367
|
+
*
|
|
3368
|
+
* @example
|
|
3369
|
+
* ```typescript
|
|
3370
|
+
* const inputType = fieldRegistry.getType('input');
|
|
3371
|
+
* if (inputType) {
|
|
3372
|
+
* console.log('Input field type found:', inputType.name);
|
|
3373
|
+
* }
|
|
3374
|
+
* ```
|
|
3375
|
+
*/
|
|
3376
|
+
getType(name: string): FieldTypeDefinition | undefined;
|
|
3377
|
+
/**
|
|
3378
|
+
* Checks if a field type is registered in the registry.
|
|
3379
|
+
*
|
|
3380
|
+
* @param name - The name of the field type to check
|
|
3381
|
+
* @returns True if the field type exists, false otherwise
|
|
3382
|
+
*
|
|
3383
|
+
* @example
|
|
3384
|
+
* ```typescript
|
|
3385
|
+
* if (fieldRegistry.hasType('custom-input')) {
|
|
3386
|
+
* // Safe to use custom-input field type
|
|
3387
|
+
* const component = await fieldRegistry.loadTypeComponent('custom-input');
|
|
3388
|
+
* }
|
|
3389
|
+
* ```
|
|
3390
|
+
*/
|
|
3391
|
+
hasType(name: string): boolean;
|
|
3392
|
+
/**
|
|
3393
|
+
* Loads a field type component with support for lazy loading.
|
|
3394
|
+
*
|
|
3395
|
+
* Handles both synchronous component references and asynchronous
|
|
3396
|
+
* dynamic imports. Automatically extracts default exports from ES modules.
|
|
3397
|
+
*
|
|
3398
|
+
* @param name - The name of the field type to load
|
|
3399
|
+
* @returns Promise resolving to the component constructor
|
|
3400
|
+
* @throws {Error} When field type is not registered or loading fails
|
|
3401
|
+
*
|
|
3402
|
+
* @example
|
|
3403
|
+
* ```typescript
|
|
3404
|
+
* try {
|
|
3405
|
+
* const InputComponent = await fieldRegistry.loadTypeComponent('input');
|
|
3406
|
+
* const componentRef = vcr.createComponent(InputComponent);
|
|
3407
|
+
* } catch (error) {
|
|
3408
|
+
* console.error('Failed to load component:', error);
|
|
3409
|
+
* }
|
|
3410
|
+
* ```
|
|
3411
|
+
*/
|
|
3412
|
+
loadTypeComponent(name: string): Promise<Type<unknown>>;
|
|
3413
|
+
/**
|
|
3414
|
+
* Gets all registered field type definitions.
|
|
3415
|
+
*
|
|
3416
|
+
* @returns Array of all field type definitions in the registry
|
|
3417
|
+
*
|
|
3418
|
+
* @example
|
|
3419
|
+
* ```typescript
|
|
3420
|
+
* const allTypes = fieldRegistry.getTypes();
|
|
3421
|
+
* const typeNames = allTypes.map(type => type.name);
|
|
3422
|
+
* console.log('Available field types:', typeNames);
|
|
3423
|
+
* ```
|
|
3424
|
+
*/
|
|
3425
|
+
getTypes(): FieldTypeDefinition[];
|
|
3426
|
+
/**
|
|
3427
|
+
* Registers multiple field types at once.
|
|
3428
|
+
*
|
|
3429
|
+
* Useful for bulk registration of custom field types. Overwrites
|
|
3430
|
+
* existing registrations with the same name.
|
|
3431
|
+
*
|
|
3432
|
+
* @param types - Array of field type definitions to register
|
|
3433
|
+
*
|
|
3434
|
+
* @example
|
|
3435
|
+
* ```typescript
|
|
3436
|
+
* fieldRegistry.registerTypes([
|
|
3437
|
+
* CustomInputType,
|
|
3438
|
+
* CustomSelectType,
|
|
3439
|
+
* CustomDatePickerType
|
|
3440
|
+
* ]);
|
|
3441
|
+
* ```
|
|
3442
|
+
*/
|
|
3443
|
+
registerTypes(types: FieldTypeDefinition[]): void;
|
|
3444
|
+
/**
|
|
3445
|
+
* Provides direct access to the underlying registry Map.
|
|
3446
|
+
*
|
|
3447
|
+
* Use with caution - direct modification can affect form behavior.
|
|
3448
|
+
* Primarily intended for advanced use cases and debugging.
|
|
3449
|
+
*
|
|
3450
|
+
* @returns The raw Map containing field type registrations
|
|
3451
|
+
*
|
|
3452
|
+
* @example
|
|
3453
|
+
* ```typescript
|
|
3454
|
+
* const rawRegistry = fieldRegistry.raw;
|
|
3455
|
+
* console.log('Registry size:', rawRegistry.size);
|
|
3456
|
+
* ```
|
|
3457
|
+
*/
|
|
3458
|
+
readonly raw: Map<string, FieldTypeDefinition<any>>;
|
|
3459
|
+
};
|
|
3460
|
+
|
|
3461
|
+
export { ARRAY_CONTEXT, AddArrayItemEvent, ArrayFieldComponent, BUILT_IN_FIELDS, DynamicForm, DynamicTextPipe, EventBus, FIELD_REGISTRY, FIELD_SIGNAL_CONTEXT, FormClearEvent, FormResetEvent, GroupFieldComponent, NextPageEvent, PageChangeEvent, PreviousPageEvent, RemoveArrayItemEvent, RowFieldComponent, SubmitEvent, applyValidator, applyValidators, arrayFieldMapper, baseFieldMapper, buildBaseInputs, checkboxFieldMapper, createResolvedErrorsSignal, datepickerFieldMapper, dynamicTextToObservable, getChildFieldTree, getChildrenMap, getFieldProxy, getFieldValueHandling, groupFieldMapper, hasFormStructure, injectFieldRegistry, isArrayField, isCheckedField, isContainerField, isDisplayOnlyField, isFormStateCondition, isGroupField, isLeafField, isPageField, isRowField, isValueBearingField, isValueField, optionsFieldMapper, pageFieldMapper, provideDynamicForm, resolveNextButtonDisabled, resolveSubmitButtonDisabled, resolveTokens, rowFieldMapper, shouldShowErrors, valueFieldMapper };
|
|
3462
|
+
export type { ArrayAllowedChildren, ArrayContext, ArrayField, ArrayItemContext, AsyncCustomValidator, AsyncValidatorConfig, AvailableFieldTypes, BaseCheckedField, BaseValidatorConfig, BaseValueField, BuiltInValidatorConfig, ButtonField, ButtonLogicContext, CheckboxField, CheckedFieldComponent, ConditionalExpression, ContainerFieldTypes, CustomFnConfig, CustomValidator, CustomValidatorConfig, DatepickerField, DatepickerProps, DynamicFormFieldRegistry, DynamicText, EvaluationContext, EventArgs, ExtractFieldDefs, ExtractFormValue, FieldComponent, FieldDef, FieldOption, FieldPathAccess, FieldRegistryContainers, FieldRegistryLeaves, FieldSignalContext, FieldTypeDefinition, FieldWithValidation, FormConfig, FormEvent, FormEventConstructor, FormInternals, FormMode, FormModeDetectionResult, FormOptions, FormStateCondition, GroupAllowedChildren, GroupField, HttpCustomValidator, HttpResourceRequest, HttpValidatorConfig, InferFormValue$1 as InferFormValue, InputField, InputProps, LeafFieldTypes, LogicConfig, MapperFn, MultiCheckboxField, NextButtonOptions, PageAllowedChildren, PageField, Prettify, RadioField, RegisteredFieldTypes, ResolvedError, RowAllowedChildren, RowField, SchemaApplicationConfig, SchemaDefinition, SelectField, SelectProps, SliderField, SubmissionActionResult, SubmissionConfig, SubmitButtonOptions, TextElementType, TextField, TextProps, TextareaField, TextareaProps, ToggleField, TokenContext, ValidationError, ValidationMessages, ValidatorConfig, ValueFieldComponent, ValueHandlingMode, ValueType, WithInputSignals };
|