@ng-forge/dynamic-forms 0.5.1 → 0.5.2

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.
@@ -1,11 +1,11 @@
1
1
  import * as rxjs from 'rxjs';
2
2
  import { Observable } from 'rxjs';
3
+ import * as _angular_core from '@angular/core';
4
+ import { Signal, Injector, WritableSignal, InjectionToken, Type, ResourceRef, Provider, EnvironmentProviders, PipeTransform } from '@angular/core';
3
5
  import * as _angular_forms_signals from '@angular/forms/signals';
4
6
  import { FieldTree, FieldContext, ValidationError as ValidationError$1, TreeValidationResult, SchemaPath, SchemaPathTree } from '@angular/forms/signals';
5
7
  import * as _angular_forms from '@angular/forms';
6
8
  import * as _ng_forge_dynamic_forms from '@ng-forge/dynamic-forms';
7
- import * as _angular_core from '@angular/core';
8
- import { Signal, Injector, WritableSignal, InjectionToken, Type, ResourceRef, OnDestroy, InputSignal, Provider, EnvironmentProviders, PipeTransform } from '@angular/core';
9
9
  import { FormSchema } from '@ng-forge/dynamic-forms/schema';
10
10
 
11
11
  type WithInputSignals<T> = {
@@ -578,51 +578,91 @@ type GroupAllowedChildren = LeafFieldTypes | RowField;
578
578
  */
579
579
  type ArrayAllowedChildren = LeafFieldTypes | RowField | GroupField;
580
580
 
581
+ /**
582
+ * An array item template is an array of allowed children that defines one OBJECT array item's fields.
583
+ * Used for object arrays where each item is `{ key1: value1, key2: value2, ... }`.
584
+ */
585
+ type ArrayItemTemplate$1 = readonly ArrayAllowedChildren[];
586
+ /**
587
+ * An array item definition can be either:
588
+ * - A single field (ArrayAllowedChildren) for PRIMITIVE array items - extracts field value directly
589
+ * - An array of fields (ArrayItemTemplate) for OBJECT array items - merges fields into object
590
+ *
591
+ * This allows support for:
592
+ * - Primitive arrays: `['tag1', 'tag2']`
593
+ * - Object arrays: `[{ name: 'Alice', email: '...' }]`
594
+ * - Heterogeneous arrays: `[{ value: 'x' }, 'y']` (mixed primitives and objects)
595
+ */
596
+ type ArrayItemDefinition = ArrayAllowedChildren | ArrayItemTemplate$1;
581
597
  /**
582
598
  * Array field interface for creating dynamic field collections that map to array values.
583
599
  *
584
600
  * Key concepts:
585
- * - The `fields` array defines the TEMPLATE (single field definition), not instances
586
- * - Array items are created/removed dynamically at runtime via event bus
587
- * - For primitive arrays: Use a leaf field as template → `['value1', 'value2']`
588
- * - For object arrays: Use a group field as template → `[{key1: val1}, {key2: val2}]`
601
+ * - The outer `fields` array defines INITIAL ITEMS (each element is one array item to render)
602
+ * - Each element can be either:
603
+ * - A single FieldDef (primitive item) - the field's value is extracted directly
604
+ * - An array of FieldDefs (object item) - fields are merged into an object
605
+ * - Items can have different field configurations (heterogeneous arrays supported)
606
+ * - Empty `fields: []` means no initial items - user adds via buttons with explicit templates
607
+ * - Initial values are declared via `value` property on each field definition
589
608
  *
590
609
  * @example
591
- * // Primitive array (flat values)
610
+ * // Primitive array: ['angular', 'typescript']
592
611
  * {
593
612
  * key: 'tags',
594
613
  * type: 'array',
595
- * fields: [{ key: 'tag', type: 'input' }]
614
+ * fields: [
615
+ * { key: 'tag', type: 'input', value: 'angular' }, // Single field = primitive
616
+ * { key: 'tag', type: 'input', value: 'typescript' },
617
+ * ]
596
618
  * }
597
- * // Creates: { tags: ['tag1', 'tag2', 'tag3'] }
598
619
  *
599
620
  * @example
600
- * // Object array (nested groups)
621
+ * // Object array: [{ name: 'Alice', email: '...' }]
601
622
  * {
602
623
  * key: 'contacts',
603
624
  * type: 'array',
604
- * fields: [{
605
- * type: 'group',
606
- * fields: [
607
- * { key: 'name', type: 'input' },
608
- * { key: 'email', type: 'input' }
609
- * ]
610
- * }]
625
+ * fields: [
626
+ * [ // Array = object
627
+ * { key: 'name', type: 'input', value: 'Alice' },
628
+ * { key: 'email', type: 'input', value: 'alice@example.com' }
629
+ * ],
630
+ * ]
631
+ * }
632
+ *
633
+ * @example
634
+ * // Heterogeneous array: [{ label: 'Structured' }, 'Simple']
635
+ * {
636
+ * key: 'items',
637
+ * type: 'array',
638
+ * fields: [
639
+ * [{ key: 'label', type: 'input', value: 'Structured' }], // Object item
640
+ * { key: 'value', type: 'input', value: 'Simple' }, // Primitive item
641
+ * ]
642
+ * }
643
+ *
644
+ * @example
645
+ * // Empty array (add items via buttons)
646
+ * {
647
+ * key: 'tags',
648
+ * type: 'array',
649
+ * fields: [] // No initial items - user adds via button with template
611
650
  * }
612
- * // Creates: { contacts: [{name: '', email: ''}, {name: '', email: ''}] }
613
651
  *
614
652
  * Nesting constraints: Arrays can contain rows, leaf fields, or groups (for object arrays),
615
653
  * but NOT pages or other arrays. Runtime validation enforces these rules.
616
654
  *
617
655
  * Note: Arrays are container fields and do not support `meta` since they have no native form element.
618
656
  */
619
- interface ArrayField<TFields extends readonly ArrayAllowedChildren[] = readonly ArrayAllowedChildren[]> extends FieldDef<never> {
657
+ interface ArrayField<TFields extends readonly ArrayItemDefinition[] = readonly ArrayItemDefinition[]> extends FieldDef<never> {
620
658
  type: 'array';
621
659
  /**
622
- * Template field definition(s) that will be cloned for each array item.
623
- * Typically contains a single field definition:
624
- * - For flat arrays: A leaf field (input, select, checkbox, etc.)
625
- * - For object arrays: A group field containing multiple child fields
660
+ * Array of item definitions. Each element defines one array item:
661
+ * - Single FieldDef = primitive item (field's value is extracted directly)
662
+ * - Array of FieldDefs = object item (fields are merged into an object)
663
+ * - Empty array `[]` = no initial items (add via buttons with explicit templates)
664
+ * - Items can have different configurations (heterogeneous arrays supported)
665
+ * - Initial values are set via `value` property on each field definition
626
666
  */
627
667
  readonly fields: TFields;
628
668
  /** Array fields do not have a label property **/
@@ -631,7 +671,9 @@ interface ArrayField<TFields extends readonly ArrayAllowedChildren[] = readonly
631
671
  readonly meta?: never;
632
672
  }
633
673
  /**
634
- * Type guard for ArrayField with proper type narrowing
674
+ * Type guard for ArrayField with proper type narrowing.
675
+ * Validates that the field is an array type with a fields property that is an array.
676
+ * Fields can contain either single FieldDefs (primitive items) or arrays of FieldDefs (object items).
635
677
  */
636
678
  declare function isArrayField(field: FieldDef<any>): field is ArrayField;
637
679
 
@@ -1110,15 +1152,196 @@ interface DebouncedDerivationLogicConfig extends BaseDerivationLogicConfig {
1110
1152
  * @public
1111
1153
  */
1112
1154
  type DerivationLogicConfig = OnChangeDerivationLogicConfig | DebouncedDerivationLogicConfig;
1155
+ /**
1156
+ * Base configuration for property derivation logic.
1157
+ *
1158
+ * Property derivations compute values for field properties (like `minDate`, `options`,
1159
+ * `label`, `placeholder`) based on form values and external data. Unlike value derivations
1160
+ * which set the field's value, property derivations update component input properties.
1161
+ *
1162
+ * All property derivations are self-targeting: the logic is placed on the field whose
1163
+ * property should be derived.
1164
+ *
1165
+ * @internal
1166
+ */
1167
+ interface BasePropertyDerivationLogicConfig {
1168
+ /**
1169
+ * Logic type identifier for property derivation.
1170
+ */
1171
+ type: 'propertyDerivation';
1172
+ /**
1173
+ * The target property to set on the field component.
1174
+ *
1175
+ * **Depth limit (max 2 levels):** Only simple and single-dot-nested paths are
1176
+ * supported. Paths with 2+ dots (e.g., `'a.b.c'`) will throw a `DynamicFormError`
1177
+ * at runtime. This is an architectural constraint of the override merging strategy.
1178
+ *
1179
+ * Supported formats:
1180
+ * - Simple: `'minDate'`, `'options'`, `'label'`, `'placeholder'`
1181
+ * - Nested (1 dot): `'props.appearance'`, `'meta.autocomplete'`
1182
+ *
1183
+ * **Note:** There is no compile-time validation that the property name matches
1184
+ * an actual input on the field component. Typos will silently write to the
1185
+ * override store with no visible effect. A dev-mode warning is emitted when
1186
+ * the override key doesn't match any existing input property.
1187
+ *
1188
+ * @example
1189
+ * ```typescript
1190
+ * targetProperty: 'minDate' // ✅ Simple property
1191
+ * targetProperty: 'options' // ✅ Simple property
1192
+ * targetProperty: 'props.appearance' // ✅ Single-nested property
1193
+ * targetProperty: 'a.b.c' // ❌ Throws DynamicFormError (too deep)
1194
+ * ```
1195
+ */
1196
+ targetProperty: string;
1197
+ /**
1198
+ * Optional name for this derivation for debugging purposes.
1199
+ *
1200
+ * When provided, this name appears in property derivation debug logs.
1201
+ */
1202
+ debugName?: string;
1203
+ /**
1204
+ * Condition that determines when this property derivation applies.
1205
+ *
1206
+ * Defaults to `true` (always apply).
1207
+ *
1208
+ * Note: FormStateCondition is not supported for property derivations.
1209
+ */
1210
+ condition?: ConditionalExpression | boolean;
1211
+ /**
1212
+ * Static value to set on the target property.
1213
+ *
1214
+ * Mutually exclusive with `expression` and `functionName`.
1215
+ */
1216
+ value?: unknown;
1217
+ /**
1218
+ * JavaScript expression to evaluate for the derived property value.
1219
+ *
1220
+ * Has access to `formValue` object containing all form values.
1221
+ * For array fields, `formValue` is scoped to the current array item.
1222
+ *
1223
+ * Mutually exclusive with `value` and `functionName`.
1224
+ *
1225
+ * @example
1226
+ * ```typescript
1227
+ * expression: 'formValue.startDate'
1228
+ * expression: 'formValue.quantity > 10 ? "bulk" : "standard"'
1229
+ * ```
1230
+ */
1231
+ expression?: string;
1232
+ /**
1233
+ * Name of a registered custom property derivation function.
1234
+ *
1235
+ * Register functions in `customFnConfig.propertyDerivations`.
1236
+ * Mutually exclusive with `value` and `expression`.
1237
+ */
1238
+ functionName?: string;
1239
+ /**
1240
+ * Explicit field dependencies for property derivations.
1241
+ *
1242
+ * When using `functionName`, you can optionally specify which fields
1243
+ * the function depends on. If not provided with `functionName`, defaults
1244
+ * to all fields ('*').
1245
+ * For `expression`, dependencies are automatically extracted.
1246
+ * For `value`, no dependencies are needed (static value).
1247
+ */
1248
+ dependsOn?: string[];
1249
+ }
1250
+ /**
1251
+ * Property derivation that evaluates immediately on change (default).
1252
+ *
1253
+ * @internal
1254
+ */
1255
+ interface OnChangePropertyDerivationLogicConfig extends BasePropertyDerivationLogicConfig {
1256
+ /**
1257
+ * Trigger for immediate evaluation.
1258
+ * @default 'onChange'
1259
+ */
1260
+ trigger?: 'onChange';
1261
+ /** Not allowed for onChange trigger */
1262
+ debounceMs?: never;
1263
+ }
1264
+ /**
1265
+ * Property derivation that evaluates after a debounce period.
1266
+ *
1267
+ * @internal
1268
+ */
1269
+ interface DebouncedPropertyDerivationLogicConfig extends BasePropertyDerivationLogicConfig {
1270
+ /**
1271
+ * Trigger for debounced evaluation.
1272
+ */
1273
+ trigger: 'debounced';
1274
+ /**
1275
+ * Debounce duration in milliseconds.
1276
+ * @default 500
1277
+ */
1278
+ debounceMs?: number;
1279
+ }
1280
+ /**
1281
+ * Configuration for property derivation logic.
1282
+ *
1283
+ * Enables reactive derivation of field properties (like `minDate`, `options`,
1284
+ * `label`, `placeholder`) based on form values and external data.
1285
+ *
1286
+ * Property derivations are self-targeting: the logic is placed on the field
1287
+ * whose property should be derived.
1288
+ *
1289
+ * @example
1290
+ * ```typescript
1291
+ * // Derive endDate's minDate from startDate value
1292
+ * {
1293
+ * key: 'endDate',
1294
+ * type: 'datepicker',
1295
+ * logic: [{
1296
+ * type: 'propertyDerivation',
1297
+ * targetProperty: 'minDate',
1298
+ * expression: 'formValue.startDate'
1299
+ * }]
1300
+ * }
1301
+ *
1302
+ * // Derive options from another field's value
1303
+ * {
1304
+ * key: 'city',
1305
+ * type: 'select',
1306
+ * logic: [{
1307
+ * type: 'propertyDerivation',
1308
+ * targetProperty: 'options',
1309
+ * functionName: 'getCitiesForCountry',
1310
+ * dependsOn: ['country']
1311
+ * }]
1312
+ * }
1313
+ *
1314
+ * // Derive label conditionally
1315
+ * {
1316
+ * key: 'phone',
1317
+ * type: 'input',
1318
+ * logic: [{
1319
+ * type: 'propertyDerivation',
1320
+ * targetProperty: 'label',
1321
+ * value: 'Mobile Phone',
1322
+ * condition: {
1323
+ * type: 'fieldValue',
1324
+ * fieldPath: 'contactType',
1325
+ * operator: 'equals',
1326
+ * value: 'mobile'
1327
+ * }
1328
+ * }]
1329
+ * }
1330
+ * ```
1331
+ *
1332
+ * @public
1333
+ */
1334
+ type PropertyDerivationLogicConfig = OnChangePropertyDerivationLogicConfig | DebouncedPropertyDerivationLogicConfig;
1113
1335
  /**
1114
1336
  * Union type for all logic configurations.
1115
1337
  *
1116
1338
  * - `StateLogicConfig`: For field state changes (hidden, readonly, disabled, required)
1117
1339
  * - `DerivationLogicConfig`: For value derivation
1340
+ * - `PropertyDerivationLogicConfig`: For property derivation
1118
1341
  *
1119
1342
  * @public
1120
1343
  */
1121
- type LogicConfig = StateLogicConfig | DerivationLogicConfig;
1344
+ type LogicConfig = StateLogicConfig | DerivationLogicConfig | PropertyDerivationLogicConfig;
1122
1345
  /**
1123
1346
  * Log level for derivation debug output.
1124
1347
  *
@@ -1138,6 +1361,237 @@ type DerivationLogLevel = 'none' | 'summary' | 'verbose';
1138
1361
  * @public
1139
1362
  */
1140
1363
  declare function isFormStateCondition(condition: StateLogicConfig['condition'] | DerivationLogicConfig['condition']): condition is FormStateCondition;
1364
+ /**
1365
+ * Type guard to check if a logic config is a PropertyDerivationLogicConfig.
1366
+ *
1367
+ * @param config - The logic config to check
1368
+ * @returns true if the config is for property derivation
1369
+ *
1370
+ * @public
1371
+ */
1372
+ declare function isPropertyDerivationLogicConfig(config: LogicConfig): config is PropertyDerivationLogicConfig;
1373
+
1374
+ /**
1375
+ * Interface for dynamic forms logger implementations.
1376
+ * Allows custom logging integrations (Sentry, DataDog, etc.)
1377
+ */
1378
+ interface Logger {
1379
+ /**
1380
+ * Log a debug message.
1381
+ * Use for detailed diagnostic information during development.
1382
+ */
1383
+ debug(message: string, ...args: unknown[]): void;
1384
+ /**
1385
+ * Log an info message.
1386
+ * Use for general operational information.
1387
+ */
1388
+ info(message: string, ...args: unknown[]): void;
1389
+ /**
1390
+ * Log a warning message.
1391
+ * Use for potentially problematic situations that don't prevent operation.
1392
+ */
1393
+ warn(message: string, ...args: unknown[]): void;
1394
+ /**
1395
+ * Log an error message.
1396
+ * Use for error conditions that may affect functionality.
1397
+ */
1398
+ error(message: string, ...args: unknown[]): void;
1399
+ }
1400
+
1401
+ /**
1402
+ * Context for resolving button disabled state.
1403
+ *
1404
+ * @public
1405
+ */
1406
+ interface ButtonLogicContext {
1407
+ /** The form's FieldTree instance (supports both string and number keys for array indices) */
1408
+ form: FieldTree<unknown, string | number>;
1409
+ /** Form-level options */
1410
+ formOptions?: FormOptions;
1411
+ /** Field-level logic array (if provided) */
1412
+ fieldLogic?: LogicConfig[];
1413
+ /** Explicit disabled state from field definition */
1414
+ explicitlyDisabled?: boolean;
1415
+ /** Current page validity signal (for paged forms) */
1416
+ currentPageValid?: Signal<boolean>;
1417
+ /** Current form value for evaluating conditional expressions */
1418
+ formValue?: unknown;
1419
+ /** Optional logger for diagnostic output. Falls back to no-op logger if not provided. */
1420
+ logger?: Logger;
1421
+ }
1422
+ /**
1423
+ * Allowed logic types for non-form-bound elements (buttons, text fields, etc.).
1424
+ * These elements only support hidden and disabled states - not readonly or required
1425
+ * since they don't participate in form validation.
1426
+ *
1427
+ * @public
1428
+ */
1429
+ type NonFieldLogicType = 'hidden' | 'disabled';
1430
+ /**
1431
+ * Logic config restricted to types valid for non-form-bound elements.
1432
+ * This is a subset of LogicConfig that only includes hidden and disabled types.
1433
+ *
1434
+ * @public
1435
+ */
1436
+ type NonFieldLogicConfig = LogicConfig & {
1437
+ type: NonFieldLogicType;
1438
+ };
1439
+ /**
1440
+ * Context for resolving state (hidden/disabled) for non-form-bound elements.
1441
+ *
1442
+ * This is a generalized context that can be used by any field mapper (buttons, text fields,
1443
+ * or any non-form-bound elements) to evaluate logic from a `logic` array.
1444
+ *
1445
+ * Note: While this context accepts the full `LogicConfig[]` for flexibility, the resolver
1446
+ * functions only process hidden and disabled types. Other logic types (readonly, required,
1447
+ * derivation) are ignored for non-form-bound elements.
1448
+ *
1449
+ * @public
1450
+ */
1451
+ interface NonFieldLogicContext {
1452
+ /** The form's FieldTree instance (supports both string and number keys for array indices) */
1453
+ form: FieldTree<unknown, string | number>;
1454
+ /**
1455
+ * Field-level logic array containing conditions.
1456
+ * Accepts full LogicConfig[] for compatibility, but only hidden and disabled types are processed.
1457
+ */
1458
+ fieldLogic?: LogicConfig[];
1459
+ /** Explicit state from field definition (e.g., `hidden: true` or `disabled: true`) */
1460
+ explicitValue?: boolean;
1461
+ /** Current form value for evaluating conditional expressions */
1462
+ formValue?: unknown;
1463
+ /** Optional logger for diagnostic output. Falls back to no-op logger if not provided. */
1464
+ logger?: Logger;
1465
+ }
1466
+ /**
1467
+ * Resolves the disabled state for a submit button.
1468
+ *
1469
+ * The disabled state is determined by (in order of precedence):
1470
+ * 1. Explicit `disabled: true` on the field definition
1471
+ * 2. Field-level `logic` array (if present, overrides form-level defaults)
1472
+ * 3. Form-level `options.submitButton` defaults
1473
+ *
1474
+ * @param ctx - The button logic context
1475
+ * @returns A computed signal that returns true when the button should be disabled
1476
+ *
1477
+ * @example
1478
+ * ```typescript
1479
+ * const disabled = resolveSubmitButtonDisabled({
1480
+ * form: formInstance,
1481
+ * formOptions: config.options,
1482
+ * fieldLogic: buttonField.logic,
1483
+ * explicitlyDisabled: buttonField.disabled,
1484
+ * });
1485
+ *
1486
+ * // Use in template
1487
+ * <button [disabled]="disabled()">Submit</button>
1488
+ * ```
1489
+ *
1490
+ * @public
1491
+ */
1492
+ declare function resolveSubmitButtonDisabled(ctx: ButtonLogicContext): Signal<boolean>;
1493
+ /**
1494
+ * Resolves the disabled state for a next page button.
1495
+ *
1496
+ * The disabled state is determined by (in order of precedence):
1497
+ * 1. Explicit `disabled: true` on the field definition
1498
+ * 2. Field-level `logic` array (if present, overrides form-level defaults)
1499
+ * 3. Form-level `options.nextButton` defaults
1500
+ *
1501
+ * @param ctx - The button logic context
1502
+ * @returns A computed signal that returns true when the button should be disabled
1503
+ *
1504
+ * @example
1505
+ * ```typescript
1506
+ * const disabled = resolveNextButtonDisabled({
1507
+ * form: formInstance,
1508
+ * formOptions: config.options,
1509
+ * fieldLogic: buttonField.logic,
1510
+ * explicitlyDisabled: buttonField.disabled,
1511
+ * currentPageValid: pageOrchestrator.currentPageValid,
1512
+ * });
1513
+ * ```
1514
+ *
1515
+ * @public
1516
+ */
1517
+ declare function resolveNextButtonDisabled(ctx: ButtonLogicContext): Signal<boolean>;
1518
+ /**
1519
+ * Resolves the hidden state for non-form-bound elements (buttons, text fields, etc.).
1520
+ *
1521
+ * This is a generalized function that can be used by any field mapper (buttons, text fields,
1522
+ * or any non-form-bound elements) to evaluate hidden logic. It's particularly useful for
1523
+ * elements that don't have a FieldTree but still need conditional visibility.
1524
+ *
1525
+ * The hidden state is determined by (in order of precedence):
1526
+ * 1. Explicit `hidden: true` on the field definition
1527
+ * 2. Field-level `logic` array with `type: 'hidden'` conditions
1528
+ *
1529
+ * @param ctx - The context containing form, logic array, and explicit hidden state
1530
+ * @returns A computed signal that returns true when the element should be hidden
1531
+ *
1532
+ * @example
1533
+ * ```typescript
1534
+ * // In an array button mapper
1535
+ * const hiddenSignal = resolveNonFieldHidden({
1536
+ * form: rootFormRegistry.getRootForm()!,
1537
+ * fieldLogic: buttonField.logic,
1538
+ * explicitValue: buttonField.hidden,
1539
+ * formValue: rootFormRegistry.getFormValue(),
1540
+ * });
1541
+ *
1542
+ * return computed(() => ({
1543
+ * ...baseInputs,
1544
+ * hidden: hiddenSignal(),
1545
+ * }));
1546
+ * ```
1547
+ *
1548
+ * @example
1549
+ * ```typescript
1550
+ * // In a text field mapper
1551
+ * const hiddenSignal = resolveNonFieldHidden({
1552
+ * form: rootFormRegistry.getRootForm()!,
1553
+ * fieldLogic: textField.logic,
1554
+ * explicitValue: textField.hidden,
1555
+ * formValue: rootFormRegistry.getFormValue(),
1556
+ * });
1557
+ * ```
1558
+ *
1559
+ * @public
1560
+ */
1561
+ declare function resolveNonFieldHidden(ctx: NonFieldLogicContext): Signal<boolean>;
1562
+ /**
1563
+ * Resolves the disabled state for non-form-bound elements (buttons, text fields, etc.).
1564
+ *
1565
+ * This is a generalized function that can be used by any field mapper to evaluate
1566
+ * disabled logic. It handles elements that don't have form-level disabled defaults
1567
+ * (unlike submit/next buttons which have form-level config).
1568
+ *
1569
+ * The disabled state is determined by (in order of precedence):
1570
+ * 1. Explicit `disabled: true` on the field definition
1571
+ * 2. Field-level `logic` array with `type: 'disabled'` conditions
1572
+ *
1573
+ * @param ctx - The context containing form, logic array, and explicit disabled state
1574
+ * @returns A computed signal that returns true when the element should be disabled
1575
+ *
1576
+ * @example
1577
+ * ```typescript
1578
+ * // In an array button mapper
1579
+ * const disabledSignal = resolveNonFieldDisabled({
1580
+ * form: rootFormRegistry.getRootForm()!,
1581
+ * fieldLogic: buttonField.logic,
1582
+ * explicitValue: buttonField.disabled,
1583
+ * formValue: rootFormRegistry.getFormValue(),
1584
+ * });
1585
+ *
1586
+ * return computed(() => ({
1587
+ * ...baseInputs,
1588
+ * disabled: disabledSignal(),
1589
+ * }));
1590
+ * ```
1591
+ *
1592
+ * @public
1593
+ */
1594
+ declare function resolveNonFieldDisabled(ctx: NonFieldLogicContext): Signal<boolean>;
1141
1595
 
1142
1596
  /**
1143
1597
  * Text element type for rendering different HTML text elements
@@ -1155,8 +1609,12 @@ type TextProps = {
1155
1609
  */
1156
1610
  interface TextField extends FieldDef<TextProps> {
1157
1611
  type: 'text';
1158
- /** Logic rules for conditional visibility (hidden, readonly, disabled) */
1159
- readonly logic?: LogicConfig[];
1612
+ /**
1613
+ * Logic rules for conditional visibility.
1614
+ * Text fields only support 'hidden' logic type since they are display-only
1615
+ * and don't participate in form validation (no disabled/readonly/required).
1616
+ */
1617
+ readonly logic?: NonFieldLogicConfig[];
1160
1618
  }
1161
1619
 
1162
1620
  /**
@@ -1773,33 +2231,6 @@ interface HttpCustomValidator<TValue = unknown, TResult = unknown> {
1773
2231
  readonly onError?: (error: unknown, ctx: FieldContext<TValue>) => ValidationError$1 | ValidationError$1[] | null;
1774
2232
  }
1775
2233
 
1776
- /**
1777
- * Interface for dynamic forms logger implementations.
1778
- * Allows custom logging integrations (Sentry, DataDog, etc.)
1779
- */
1780
- interface Logger {
1781
- /**
1782
- * Log a debug message.
1783
- * Use for detailed diagnostic information during development.
1784
- */
1785
- debug(message: string, ...args: unknown[]): void;
1786
- /**
1787
- * Log an info message.
1788
- * Use for general operational information.
1789
- */
1790
- info(message: string, ...args: unknown[]): void;
1791
- /**
1792
- * Log a warning message.
1793
- * Use for potentially problematic situations that don't prevent operation.
1794
- */
1795
- warn(message: string, ...args: unknown[]): void;
1796
- /**
1797
- * Log an error message.
1798
- * Use for error conditions that may affect functionality.
1799
- */
1800
- error(message: string, ...args: unknown[]): void;
1801
- }
1802
-
1803
2234
  interface EvaluationContext<TValue = unknown> {
1804
2235
  /** Current field value */
1805
2236
  fieldValue: TValue;
@@ -2421,6 +2852,46 @@ interface CustomFnConfig {
2421
2852
  * ```
2422
2853
  */
2423
2854
  derivations?: Record<string, CustomFunction>;
2855
+ /**
2856
+ * Custom property derivation functions for reactive property updates.
2857
+ *
2858
+ * These functions compute derived values for field properties (like `minDate`,
2859
+ * `options`, `label`, `placeholder`) and are called when a
2860
+ * `PropertyDerivationLogicConfig` references them by `functionName`.
2861
+ *
2862
+ * Property derivation functions:
2863
+ * - Receive an `EvaluationContext` with access to `formValue`
2864
+ * - Return the value to set on the target property
2865
+ * - Are called reactively when dependencies change
2866
+ *
2867
+ * @example
2868
+ * ```typescript
2869
+ * propertyDerivations: {
2870
+ * getCitiesForCountry: (context) => {
2871
+ * const countryCities: Record<string, Array<{ label: string; value: string }>> = {
2872
+ * 'USA': [{ label: 'New York', value: 'ny' }, { label: 'LA', value: 'la' }],
2873
+ * 'Germany': [{ label: 'Berlin', value: 'berlin' }, { label: 'Munich', value: 'munich' }],
2874
+ * };
2875
+ * return countryCities[context.formValue.country as string] ?? [];
2876
+ * },
2877
+ * }
2878
+ * ```
2879
+ *
2880
+ * Field configuration using a property derivation function:
2881
+ * ```typescript
2882
+ * {
2883
+ * key: 'city',
2884
+ * type: 'select',
2885
+ * logic: [{
2886
+ * type: 'propertyDerivation',
2887
+ * targetProperty: 'options',
2888
+ * functionName: 'getCitiesForCountry',
2889
+ * dependsOn: ['country']
2890
+ * }]
2891
+ * }
2892
+ * ```
2893
+ */
2894
+ propertyDerivations?: Record<string, CustomFunction>;
2424
2895
  /**
2425
2896
  * Custom validators using Angular's public FieldContext API
2426
2897
  *
@@ -2767,146 +3238,14 @@ interface FormModeDetectionResult {
2767
3238
  }
2768
3239
 
2769
3240
  /**
2770
- * Type guard to check if a field is a container field (page, row, group, or array)
2771
- * Container fields have a 'fields' property and don't contribute values directly.
2772
- * This overload works with RegisteredFieldTypes for full type narrowing.
2773
- */
2774
- declare function isContainerField(field: RegisteredFieldTypes): field is ContainerFieldTypes;
2775
- /**
2776
- * Type guard to check if a field is a container field (page, row, group, or array).
2777
- * This overload works with any FieldDef for looser type checking.
2778
- */
2779
- declare function isContainerField(field: FieldDef<unknown>): boolean;
2780
- /**
2781
- * Type guard to check if a field is a leaf field (value or display field)
2782
- * Leaf fields don't have children and either contribute values or display content
2783
- */
2784
- declare function isLeafField(field: RegisteredFieldTypes): field is LeafFieldTypes;
2785
- /**
2786
- * Type guard to check if a field has a value property (value-bearing field)
2787
- * These fields contribute to the form value output
2788
- * Note: Using `unknown` in the Extract condition to match any value type
2789
- */
2790
- declare function isValueBearingField(field: RegisteredFieldTypes): field is Extract<RegisteredFieldTypes, {
2791
- value: unknown;
2792
- }>;
2793
- /**
2794
- * Type guard to check if a field is excluded from form values (display-only field)
2795
- * Currently this includes text fields and any other fields without a value property
2796
- */
2797
- declare function isDisplayOnlyField(field: RegisteredFieldTypes): boolean;
2798
-
2799
- /**
2800
- * Helper types for creating type-safe field configurations with proper nesting constraints
2801
- */
2802
-
2803
- /**
2804
- * Type helper for accessing nested field paths safely
2805
- * This allows accessing child paths while maintaining some type safety
2806
- */
2807
- type FieldPathAccess<TValue> = {
2808
- [K in keyof TValue]: SchemaPath<TValue[K]> | SchemaPathTree<TValue[K]>;
2809
- };
2810
-
2811
- /**
2812
- * Result of processing all pending derivations.
2813
- *
2814
- * @public
2815
- */
2816
- interface DerivationProcessingResult {
2817
- /** Number of derivations successfully applied */
2818
- appliedCount: number;
2819
- /** Number of derivations skipped (condition not met or value unchanged) */
2820
- skippedCount: number;
2821
- /** Number of derivations that failed with errors */
2822
- errorCount: number;
2823
- /**
2824
- * Number of derivations that encountered warnings (e.g., missing target field).
2825
- *
2826
- * These are not counted as errors because they may be intentional (conditional fields)
2827
- * but indicate potential configuration issues if unexpected.
2828
- */
2829
- warnCount: number;
2830
- /** Total iterations performed */
2831
- iterations: number;
2832
- /** Whether max iterations was reached (potential loop) */
2833
- maxIterationsReached: boolean;
2834
- }
2835
-
2836
- /**
2837
- * Information about a single derivation evaluation for logging.
3241
+ * Error from async field component loading.
2838
3242
  *
2839
3243
  * @public
2840
3244
  */
2841
- interface DerivationLogEntry {
2842
- /** Debug name if provided in the derivation config */
2843
- debugName?: string;
2844
- /** Field key where the derivation is defined and targets */
2845
- fieldKey: string;
2846
- /** Result of the derivation evaluation */
2847
- result: 'applied' | 'skipped' | 'error';
2848
- /** Reason for skipping (if result is 'skipped') */
2849
- skipReason?: 'condition-false' | 'value-unchanged' | 'already-applied' | 'target-not-found';
2850
- /** The new value that was set (if result is 'applied') */
2851
- newValue?: unknown;
2852
- /** The previous value before the derivation (if result is 'applied') */
2853
- previousValue?: unknown;
2854
- /** Error message (if result is 'error') */
2855
- error?: string;
2856
- /** Duration in milliseconds (optional, for performance tracking) */
2857
- durationMs?: number;
2858
- }
2859
-
2860
- /**
2861
- * Service interface for derivation logging.
2862
- *
2863
- * Provides methods to log derivation processing events at different verbosity levels.
2864
- * Use `DERIVATION_LOGGER` injection token to get an instance.
2865
- *
2866
- * @public
2867
- */
2868
- interface DerivationLogger {
2869
- /**
2870
- * Logs the start of a derivation processing cycle.
2871
- * Only logs when config level is 'verbose'.
2872
- */
2873
- cycleStart(trigger: 'onChange' | 'debounced', entryCount: number): void;
2874
- /**
2875
- * Logs the start of a derivation iteration within a cycle.
2876
- * Only logs when config level is 'verbose'.
2877
- */
2878
- iteration(iterationNumber: number): void;
2879
- /**
2880
- * Logs a single derivation evaluation result.
2881
- * Only logs when config level is 'verbose'.
2882
- */
2883
- evaluation(entry: DerivationLogEntry): void;
2884
- /**
2885
- * Logs a summary of derivation processing results.
2886
- * Logs when config level is 'summary' or 'verbose' and something interesting happened.
2887
- */
2888
- summary(result: DerivationProcessingResult, trigger: 'onChange' | 'debounced'): void;
2889
- /**
2890
- * Logs when max iterations are reached (always logged as warning).
2891
- */
2892
- maxIterationsReached(result: DerivationProcessingResult, trigger: 'onChange' | 'debounced'): void;
2893
- }
2894
-
2895
- /**
2896
- * Minimal configuration for creating a DerivationOrchestrator.
2897
- * Only includes form-specific signals that cannot be injected.
2898
- *
2899
- * @internal
2900
- */
2901
- interface DerivationOrchestratorConfig {
2902
- /** Signal containing the flattened schema fields */
2903
- schemaFields: Signal<FieldDef<unknown>[] | undefined>;
2904
- /** Signal containing the current form value */
2905
- formValue: Signal<Record<string, unknown>>;
2906
- /** Signal containing the form accessor */
2907
- form: Signal<FieldTree<unknown>>;
2908
- /** Signal containing the derivation logger */
2909
- derivationLogger: Signal<DerivationLogger>;
3245
+ interface FieldLoadingError {
3246
+ readonly fieldType: string;
3247
+ readonly fieldKey: string;
3248
+ readonly error: Error;
2910
3249
  }
2911
3250
 
2912
3251
  /**
@@ -3072,108 +3411,85 @@ declare class PageNavigationStateChangeEvent implements FormEvent {
3072
3411
  }
3073
3412
 
3074
3413
  /**
3075
- * Dynamic form component that renders a complete form based on configuration.
3076
- *
3077
- * This is the main entry point for the dynamic form system. It handles form state management,
3078
- * validation, field rendering, and event coordination using Angular's signal-based reactive forms.
3079
- *
3080
- * @typeParam TFields - Array of registered field types available for this form
3081
- * @typeParam TModel - The strongly-typed interface for form values
3414
+ * Dynamic form component — renders a form based on configuration.
3415
+ * Delegates state management to `FormStateManager`.
3082
3416
  *
3083
3417
  * @example
3084
3418
  *```html
3085
- * <form
3086
- * [dynamic-form]="formConfig"
3087
- * [(value)]="formData"
3088
- * (submitted)="handleSubmit($event)"
3089
- * (validityChange)="handleValidityChange($event)">
3090
- * </form>
3419
+ * <form [dynamic-form]="formConfig" [(value)]="formData" (submitted)="handleSubmit($event)"></form>
3091
3420
  * ```
3092
3421
  */
3093
- declare class DynamicForm<TFields extends RegisteredFieldTypes[] = RegisteredFieldTypes[], TModel extends Record<string, unknown> = InferFormValue$1<TFields> & Record<string, unknown>> implements OnDestroy {
3094
- private readonly destroyRef;
3095
- private readonly fieldRegistry;
3096
- private readonly injector;
3097
- private readonly eventBus;
3098
- private readonly rootFormRegistry;
3099
- private readonly functionRegistry;
3100
- private readonly schemaRegistry;
3101
- private readonly logger;
3102
- private readonly derivationLogConfig;
3422
+ declare class DynamicForm<TFields extends RegisteredFieldTypes[] = RegisteredFieldTypes[], TModel extends Record<string, unknown> = InferFormValue$1<TFields> & Record<string, unknown>> {
3103
3423
  /** Form configuration defining the structure, validation, and behavior. */
3104
- config: InputSignal<FormConfig<TFields>>;
3424
+ config: _angular_core.InputSignal<FormConfig<TFields, InferFormValue$1<TFields extends readonly RegisteredFieldTypes[] ? TFields : RegisteredFieldTypes[]>, Record<string, unknown>, unknown>>;
3105
3425
  /** Runtime form options that override config options when provided. */
3106
- formOptions: InputSignal<FormOptions | undefined>;
3426
+ formOptions: _angular_core.InputSignal<FormOptions | undefined>;
3107
3427
  /** Form values for two-way data binding. */
3108
3428
  value: _angular_core.ModelSignal<Partial<TModel> | undefined>;
3109
- private readonly memoizedFlattenFields;
3110
- private readonly memoizedFlattenFieldsForRendering;
3111
- private readonly memoizedKeyBy;
3112
- private readonly memoizedDefaultValues;
3113
- private readonly rawFieldRegistry;
3114
- readonly formModeDetection: Signal<FormModeDetectionResult>;
3115
- private readonly formSetup;
3116
- readonly pageFieldDefinitions: Signal<PageField<_ng_forge_dynamic_forms.PageAllowedChildren[]>[]>;
3117
- readonly effectiveFormOptions: Signal<{
3429
+ private destroyRef;
3430
+ private injector;
3431
+ private eventBus;
3432
+ private logger;
3433
+ /**
3434
+ * Connect input signals to the shared deps holder BEFORE injecting FormStateManager.
3435
+ * Field initializers run in declaration order, so this runs after inputs are created
3436
+ * but before stateManager is injected. FormStateManager reads from these signals
3437
+ * in computeds/effects, which are all lazy and evaluated after construction.
3438
+ */
3439
+ private readonly _connectStateDeps;
3440
+ /** State manager that owns all form state. */
3441
+ private stateManager;
3442
+ private componentId;
3443
+ /** The currently active config used for form rendering */
3444
+ activeConfig: Signal<FormConfig<TFields, InferFormValue$1<TFields extends readonly RegisteredFieldTypes[] ? TFields : RegisteredFieldTypes[]>, Record<string, unknown>, unknown> | undefined>;
3445
+ /** Current render phase: 'render' = showing form, 'teardown' = hiding old components */
3446
+ renderPhase: Signal<"teardown" | "render">;
3447
+ /** Computed form mode detection with validation */
3448
+ formModeDetection: Signal<_ng_forge_dynamic_forms.FormModeDetectionResult>;
3449
+ /** Page field definitions for paged forms */
3450
+ pageFieldDefinitions: Signal<_ng_forge_dynamic_forms.PageField<_ng_forge_dynamic_forms.PageAllowedChildren[]>[]>;
3451
+ /** Effective form options (merged from config and input) */
3452
+ effectiveFormOptions: Signal<{
3118
3453
  disabled?: boolean;
3119
3454
  maxDerivationIterations?: number;
3120
3455
  submitButton?: _ng_forge_dynamic_forms.SubmitButtonOptions;
3121
3456
  nextButton?: _ng_forge_dynamic_forms.NextButtonOptions;
3122
3457
  emitFormValueOnEvents?: boolean;
3123
3458
  }>;
3124
- /**
3125
- * Computed derivation logger based on injected config.
3126
- *
3127
- * Uses factory pattern to return no-op logger when logging is disabled,
3128
- * avoiding any logging overhead in production.
3129
- */
3130
- private readonly derivationLogger;
3131
- readonly fieldSignalContext: Signal<FieldSignalContext<TModel>>;
3132
- /**
3133
- * Injector for field components with FIELD_SIGNAL_CONTEXT.
3134
- * Other form-level tokens (DEFAULT_PROPS, etc.) are provided at component level.
3135
- */
3136
- private readonly fieldInjector;
3137
- readonly defaultValues: _angular_core.WritableSignal<TModel>;
3138
- private readonly entity;
3139
- readonly form: Signal<FieldTree<TModel, string | number>>;
3140
- private readonly componentId;
3141
- private readonly totalComponentsCount;
3142
- private readonly fieldsSource;
3143
- /** Collects errors from async field component loading for error boundary patterns. */
3144
- readonly fieldLoadingErrors: _angular_core.WritableSignal<{
3145
- fieldType: string;
3146
- fieldKey: string;
3147
- error: Error;
3148
- }[]>;
3149
- /** Current form values (reactive). */
3150
- 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)>;
3151
- /** Whether the form is currently valid. */
3152
- readonly valid: Signal<boolean>;
3153
- /** Whether the form is currently invalid. */
3154
- readonly invalid: Signal<boolean>;
3155
- /** Whether any form field has been modified. */
3156
- readonly dirty: Signal<boolean>;
3157
- /** Whether any form field has been touched (blurred). */
3158
- readonly touched: Signal<boolean>;
3159
- /** Current validation errors from all fields. */
3160
- readonly errors: Signal<_angular_forms_signals.ValidationError.WithField[]>;
3161
- /** Whether the form is disabled (from options or form state). */
3162
- readonly disabled: Signal<boolean>;
3163
- /** Whether the form is currently submitting. */
3164
- readonly submitting: Signal<boolean>;
3165
- /**
3166
- * Configuration for the derivation orchestrator.
3167
- * Bundles the form-specific signals needed by the orchestrator.
3168
- *
3169
- * @internal Used by DERIVATION_ORCHESTRATOR provider.
3170
- */
3171
- readonly derivationOrchestratorConfig: DerivationOrchestratorConfig;
3172
- readonly initialized$: rxjs.Observable<boolean>;
3459
+ /** Field signal context for injection into child components */
3460
+ fieldSignalContext: Signal<_ng_forge_dynamic_forms.FieldSignalContext<TModel>>;
3461
+ /** Default values computed from field definitions */
3462
+ defaultValues: WritableSignal<TModel>;
3463
+ /** The Angular Signal Form instance */
3464
+ form: Signal<FieldTree<TModel, string | number>>;
3465
+ /** Current form values (reactive) */
3466
+ 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)>;
3467
+ /** Whether the form is currently valid */
3468
+ valid: Signal<boolean>;
3469
+ /** Whether the form is currently invalid */
3470
+ invalid: Signal<boolean>;
3471
+ /** Whether any form field has been modified */
3472
+ dirty: Signal<boolean>;
3473
+ /** Whether any form field has been touched (blurred) */
3474
+ touched: Signal<boolean>;
3475
+ /** Current validation errors from all fields */
3476
+ errors: Signal<_angular_forms_signals.ValidationError.WithField[]>;
3477
+ /** Whether the form is disabled (from options or form state) */
3478
+ disabled: Signal<boolean>;
3479
+ /** Whether the form is currently submitting */
3480
+ submitting: Signal<boolean>;
3481
+ /** Collects errors from async field component loading for error boundary patterns */
3482
+ fieldLoadingErrors: WritableSignal<FieldLoadingError[]>;
3483
+ /** Whether to render the form template */
3484
+ shouldRender: Signal<boolean>;
3485
+ /** Resolved fields ready for rendering */
3486
+ protected resolvedFields: Signal<ResolvedField[]>;
3487
+ private totalComponentsCount;
3488
+ initialized$: rxjs.Observable<boolean>;
3173
3489
  /** Emits when form validity changes. */
3174
- readonly validityChange: _angular_core.OutputRef<boolean>;
3490
+ validityChange: _angular_core.OutputRef<boolean>;
3175
3491
  /** Emits when form dirty state changes. */
3176
- readonly dirtyChange: _angular_core.OutputRef<boolean>;
3492
+ dirtyChange: _angular_core.OutputRef<boolean>;
3177
3493
  /**
3178
3494
  * Emits form values when submitted (via SubmitEvent) and form is valid.
3179
3495
  *
@@ -3183,23 +3499,22 @@ declare class DynamicForm<TFields extends RegisteredFieldTypes[] = RegisteredFie
3183
3499
  *
3184
3500
  * Note: Does not emit when `submission.action` is configured - use one or the other.
3185
3501
  */
3186
- readonly submitted: _angular_core.OutputRef<TModel>;
3502
+ submitted: _angular_core.OutputRef<Partial<TModel>>;
3187
3503
  /** Emits when form is reset to default values. */
3188
- readonly reset: _angular_core.OutputRef<FormResetEvent>;
3504
+ reset: _angular_core.OutputRef<FormResetEvent>;
3189
3505
  /** Emits when form is cleared to empty state. */
3190
- readonly cleared: _angular_core.OutputRef<FormClearEvent>;
3506
+ cleared: _angular_core.OutputRef<FormClearEvent>;
3191
3507
  /** Emits all form events for custom event handling. */
3192
- readonly events: _angular_core.OutputRef<_ng_forge_dynamic_forms.FormEvent>;
3508
+ events: _angular_core.OutputRef<_ng_forge_dynamic_forms.FormEvent>;
3193
3509
  /**
3194
3510
  * Emits when all form components are initialized and ready for interaction.
3195
3511
  * Useful for E2E testing to ensure the form is fully rendered before interaction.
3196
3512
  */
3197
- readonly initialized: _angular_core.OutputRef<boolean>;
3513
+ initialized: _angular_core.OutputRef<boolean>;
3198
3514
  /** Emits when the current page changes in paged forms. */
3199
- readonly onPageChange: _angular_core.OutputRef<PageChangeEvent>;
3515
+ onPageChange: _angular_core.OutputRef<PageChangeEvent>;
3200
3516
  /** Emits when page navigation state changes (canGoNext, canGoPrevious, etc.). */
3201
- readonly onPageNavigationStateChange: _angular_core.OutputRef<PageNavigationStateChangeEvent>;
3202
- protected readonly resolvedFields: Signal<ResolvedField[]>;
3517
+ onPageNavigationStateChange: _angular_core.OutputRef<PageNavigationStateChangeEvent>;
3203
3518
  constructor();
3204
3519
  /**
3205
3520
  * Handles native form submission triggered by:
@@ -3215,12 +3530,6 @@ declare class DynamicForm<TFields extends RegisteredFieldTypes[] = RegisteredFie
3215
3530
  protected onNativeSubmit(event: Event): void;
3216
3531
  private setupEffects;
3217
3532
  private setupEventHandlers;
3218
- private registerValidatorsFromConfig;
3219
- private createFormSetupFromConfig;
3220
- private createEmptyFormSetup;
3221
- private onFormReset;
3222
- private onFormClear;
3223
- ngOnDestroy(): void;
3224
3533
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<DynamicForm<any, any>, never>;
3225
3534
  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>;
3226
3535
  }
@@ -3239,6 +3548,48 @@ interface DynamicFormFeature<TKind extends string = string> {
3239
3548
  ɵproviders: Provider[];
3240
3549
  }
3241
3550
 
3551
+ /**
3552
+ * Type guard to check if a field is a container field (page, row, group, or array)
3553
+ * Container fields have a 'fields' property and don't contribute values directly.
3554
+ * This overload works with RegisteredFieldTypes for full type narrowing.
3555
+ */
3556
+ declare function isContainerField(field: RegisteredFieldTypes): field is ContainerFieldTypes;
3557
+ /**
3558
+ * Type guard to check if a field is a container field (page, row, group, or array).
3559
+ * This overload works with any FieldDef for looser type checking.
3560
+ */
3561
+ declare function isContainerField(field: FieldDef<unknown>): boolean;
3562
+ /**
3563
+ * Type guard to check if a field is a leaf field (value or display field)
3564
+ * Leaf fields don't have children and either contribute values or display content
3565
+ */
3566
+ declare function isLeafField(field: RegisteredFieldTypes): field is LeafFieldTypes;
3567
+ /**
3568
+ * Type guard to check if a field has a value property (value-bearing field)
3569
+ * These fields contribute to the form value output
3570
+ * Note: Using `unknown` in the Extract condition to match any value type
3571
+ */
3572
+ declare function isValueBearingField(field: RegisteredFieldTypes): field is Extract<RegisteredFieldTypes, {
3573
+ value: unknown;
3574
+ }>;
3575
+ /**
3576
+ * Type guard to check if a field is excluded from form values (display-only field)
3577
+ * Currently this includes text fields and any other fields without a value property
3578
+ */
3579
+ declare function isDisplayOnlyField(field: RegisteredFieldTypes): boolean;
3580
+
3581
+ /**
3582
+ * Helper types for creating type-safe field configurations with proper nesting constraints
3583
+ */
3584
+
3585
+ /**
3586
+ * Type helper for accessing nested field paths safely
3587
+ * This allows accessing child paths while maintaining some type safety
3588
+ */
3589
+ type FieldPathAccess<TValue> = {
3590
+ [K in keyof TValue]: SchemaPath<TValue[K]> | SchemaPathTree<TValue[K]>;
3591
+ };
3592
+
3242
3593
  /**
3243
3594
  * Extract FieldDef type from FieldTypeDefinition
3244
3595
  */
@@ -3643,80 +3994,6 @@ declare const DEFAULT_VALIDATION_MESSAGES: InjectionToken<Signal<ValidationMessa
3643
3994
  */
3644
3995
  declare const FORM_OPTIONS: InjectionToken<Signal<FormOptions | undefined>>;
3645
3996
 
3646
- /**
3647
- * Context for resolving button disabled state.
3648
- *
3649
- * @public
3650
- */
3651
- interface ButtonLogicContext {
3652
- /** The form's FieldTree instance (supports both string and number keys for array indices) */
3653
- form: FieldTree<unknown, string | number>;
3654
- /** Form-level options */
3655
- formOptions?: FormOptions;
3656
- /** Field-level logic array (if provided) */
3657
- fieldLogic?: LogicConfig[];
3658
- /** Explicit disabled state from field definition */
3659
- explicitlyDisabled?: boolean;
3660
- /** Current page validity signal (for paged forms) */
3661
- currentPageValid?: Signal<boolean>;
3662
- /** Current form value for evaluating conditional expressions */
3663
- formValue?: unknown;
3664
- /** Optional logger for diagnostic output. Falls back to no-op logger if not provided. */
3665
- logger?: Logger;
3666
- }
3667
- /**
3668
- * Resolves the disabled state for a submit button.
3669
- *
3670
- * The disabled state is determined by (in order of precedence):
3671
- * 1. Explicit `disabled: true` on the field definition
3672
- * 2. Field-level `logic` array (if present, overrides form-level defaults)
3673
- * 3. Form-level `options.submitButton` defaults
3674
- *
3675
- * @param ctx - The button logic context
3676
- * @returns A computed signal that returns true when the button should be disabled
3677
- *
3678
- * @example
3679
- * ```typescript
3680
- * const disabled = resolveSubmitButtonDisabled({
3681
- * form: formInstance,
3682
- * formOptions: config.options,
3683
- * fieldLogic: buttonField.logic,
3684
- * explicitlyDisabled: buttonField.disabled,
3685
- * });
3686
- *
3687
- * // Use in template
3688
- * <button [disabled]="disabled()">Submit</button>
3689
- * ```
3690
- *
3691
- * @public
3692
- */
3693
- declare function resolveSubmitButtonDisabled(ctx: ButtonLogicContext): Signal<boolean>;
3694
- /**
3695
- * Resolves the disabled state for a next page button.
3696
- *
3697
- * The disabled state is determined by (in order of precedence):
3698
- * 1. Explicit `disabled: true` on the field definition
3699
- * 2. Field-level `logic` array (if present, overrides form-level defaults)
3700
- * 3. Form-level `options.nextButton` defaults
3701
- *
3702
- * @param ctx - The button logic context
3703
- * @returns A computed signal that returns true when the button should be disabled
3704
- *
3705
- * @example
3706
- * ```typescript
3707
- * const disabled = resolveNextButtonDisabled({
3708
- * form: formInstance,
3709
- * formOptions: config.options,
3710
- * fieldLogic: buttonField.logic,
3711
- * explicitlyDisabled: buttonField.disabled,
3712
- * currentPageValid: pageOrchestrator.currentPageValid,
3713
- * });
3714
- * ```
3715
- *
3716
- * @public
3717
- */
3718
- declare function resolveNextButtonDisabled(ctx: ButtonLogicContext): Signal<boolean>;
3719
-
3720
3997
  interface FieldWithValidation {
3721
3998
  readonly required?: boolean;
3722
3999
  readonly email?: boolean;
@@ -3815,77 +4092,375 @@ declare class PreviousPageEvent implements FormEvent {
3815
4092
  }
3816
4093
 
3817
4094
  /**
3818
- * Event dispatched to add a new item to an array field.
4095
+ * Template type for OBJECT array items: array of field configurations.
4096
+ * Each field's value is merged into the item object.
4097
+ */
4098
+ type ArrayItemTemplate = ArrayAllowedChildren[];
4099
+ /**
4100
+ * Template type for array items that can be either:
4101
+ * - A single field (ArrayAllowedChildren) for PRIMITIVE items - extracts field value directly
4102
+ * - An array of fields (ArrayItemTemplate) for OBJECT items - merges fields into object
4103
+ *
4104
+ * Examples:
4105
+ * - Primitive: `{ key: 'tag', type: 'input', value: 'angular' }` → 'angular'
4106
+ * - Object: `[{ key: 'name', type: 'input' }, { key: 'email', type: 'input' }]` → { name: '', email: '' }
4107
+ */
4108
+ type ArrayItemDefinitionTemplate = ArrayAllowedChildren | ArrayItemTemplate;
4109
+ /**
4110
+ * Event dispatched to append a new item at the END of an array field.
4111
+ *
4112
+ * This is the most common array operation - adding items to the end.
4113
+ * For other positions, use {@link PrependArrayItemEvent} or {@link InsertArrayItemEvent}.
4114
+ *
4115
+ * @example
4116
+ * ```typescript
4117
+ * // Object item: append { name, email } object
4118
+ * eventBus.dispatch(arrayEvent('contacts').append([
4119
+ * { key: 'name', type: 'input', label: 'Name' },
4120
+ * { key: 'email', type: 'input', label: 'Email' }
4121
+ * ]));
4122
+ *
4123
+ * // Primitive item: append single value
4124
+ * eventBus.dispatch(arrayEvent('tags').append(
4125
+ * { key: 'tag', type: 'input', label: 'Tag' }
4126
+ * ));
4127
+ * ```
4128
+ *
4129
+ * @typeParam TTemplate - The type of the template (single field or array of fields)
4130
+ */
4131
+ declare class AppendArrayItemEvent<TTemplate extends ArrayItemDefinitionTemplate = ArrayItemDefinitionTemplate> implements FormEvent {
4132
+ /** The key of the array field to append an item to */
4133
+ readonly arrayKey: string;
4134
+ /**
4135
+ * Template for the new array item. REQUIRED.
4136
+ * - Single field (FieldDef): Creates a primitive item (field's value is extracted directly)
4137
+ * - Array of fields (FieldDef[]): Creates an object item (fields merged into object)
4138
+ */
4139
+ readonly template: TTemplate;
4140
+ readonly type: "append-array-item";
4141
+ constructor(
4142
+ /** The key of the array field to append an item to */
4143
+ arrayKey: string,
4144
+ /**
4145
+ * Template for the new array item. REQUIRED.
4146
+ * - Single field (FieldDef): Creates a primitive item (field's value is extracted directly)
4147
+ * - Array of fields (FieldDef[]): Creates an object item (fields merged into object)
4148
+ */
4149
+ template: TTemplate);
4150
+ }
4151
+
4152
+ /**
4153
+ * Event dispatched to prepend a new item at the BEGINNING of an array field.
3819
4154
  *
3820
- * This event is used to dynamically add items to array fields at runtime.
3821
- * The array field component listens for this event and adds a new item
3822
- * based on the provided field template, or uses the array's own template if not provided.
4155
+ * Use this when new items should appear at the start of the list.
4156
+ * For appending to the end, use {@link AppendArrayItemEvent}.
3823
4157
  *
3824
4158
  * @example
3825
4159
  * ```typescript
3826
- * // Add item using array's own template
3827
- * eventBus.dispatch(AddArrayItemEvent, 'contacts');
4160
+ * // Object item: prepend { name } object
4161
+ * eventBus.dispatch(arrayEvent('contacts').prepend([
4162
+ * { key: 'name', type: 'input', label: 'Name' }
4163
+ * ]));
4164
+ *
4165
+ * // Primitive item: prepend single value
4166
+ * eventBus.dispatch(arrayEvent('tags').prepend(
4167
+ * { key: 'tag', type: 'input', label: 'Tag' }
4168
+ * ));
4169
+ * ```
4170
+ *
4171
+ * @typeParam TTemplate - The type of the template (single field or array of fields)
4172
+ */
4173
+ declare class PrependArrayItemEvent<TTemplate extends ArrayItemDefinitionTemplate = ArrayItemDefinitionTemplate> implements FormEvent {
4174
+ /** The key of the array field to prepend an item to */
4175
+ readonly arrayKey: string;
4176
+ /**
4177
+ * Template for the new array item. REQUIRED.
4178
+ * - Single field (FieldDef): Creates a primitive item (field's value is extracted directly)
4179
+ * - Array of fields (FieldDef[]): Creates an object item (fields merged into object)
4180
+ */
4181
+ readonly template: TTemplate;
4182
+ readonly type: "prepend-array-item";
4183
+ constructor(
4184
+ /** The key of the array field to prepend an item to */
4185
+ arrayKey: string,
4186
+ /**
4187
+ * Template for the new array item. REQUIRED.
4188
+ * - Single field (FieldDef): Creates a primitive item (field's value is extracted directly)
4189
+ * - Array of fields (FieldDef[]): Creates an object item (fields merged into object)
4190
+ */
4191
+ template: TTemplate);
4192
+ }
4193
+
4194
+ /**
4195
+ * Event dispatched to insert a new item at a SPECIFIC INDEX in an array field.
3828
4196
  *
3829
- * // Add item with explicit field template
3830
- * const fieldTemplate = { key: 'email', type: 'input' };
3831
- * eventBus.dispatch(AddArrayItemEvent, 'contacts', fieldTemplate);
4197
+ * Use this when you need precise control over where the new item appears.
4198
+ * For simpler operations, use {@link AppendArrayItemEvent} or {@link PrependArrayItemEvent}.
3832
4199
  *
3833
- * // Add item at specific index
3834
- * eventBus.dispatch(AddArrayItemEvent, 'contacts', fieldTemplate, 2);
4200
+ * @example
4201
+ * ```typescript
4202
+ * // Object item: insert { name } object at index 2
4203
+ * eventBus.dispatch(arrayEvent('contacts').insertAt(2, [
4204
+ * { key: 'name', type: 'input', label: 'Name' }
4205
+ * ]));
4206
+ *
4207
+ * // Primitive item: insert single value at index 2
4208
+ * eventBus.dispatch(arrayEvent('tags').insertAt(2,
4209
+ * { key: 'tag', type: 'input', label: 'Tag' }
4210
+ * ));
3835
4211
  * ```
4212
+ *
4213
+ * @typeParam TTemplate - The type of the template (single field or array of fields)
3836
4214
  */
3837
- declare class AddArrayItemEvent implements FormEvent {
3838
- /** The key of the array field to add an item to */
4215
+ declare class InsertArrayItemEvent<TTemplate extends ArrayItemDefinitionTemplate = ArrayItemDefinitionTemplate> implements FormEvent {
4216
+ /** The key of the array field to insert an item into */
3839
4217
  readonly arrayKey: string;
4218
+ /** The index at which to insert the new item */
4219
+ readonly index: number;
3840
4220
  /**
3841
- * Optional field template definition to render for the new array item.
3842
- * If not provided, the array field component will use its own template.
4221
+ * Template for the new array item. REQUIRED.
4222
+ * - Single field (FieldDef): Creates a primitive item (field's value is extracted directly)
4223
+ * - Array of fields (FieldDef[]): Creates an object item (fields merged into object)
3843
4224
  */
3844
- readonly field?: ArrayAllowedChildren | undefined;
3845
- /** Optional index where to insert the item (defaults to end of array) */
3846
- readonly index?: number | undefined;
3847
- readonly type: "add-array-item";
4225
+ readonly template: TTemplate;
4226
+ readonly type: "insert-array-item";
3848
4227
  constructor(
3849
- /** The key of the array field to add an item to */
4228
+ /** The key of the array field to insert an item into */
3850
4229
  arrayKey: string,
4230
+ /** The index at which to insert the new item */
4231
+ index: number,
3851
4232
  /**
3852
- * Optional field template definition to render for the new array item.
3853
- * If not provided, the array field component will use its own template.
4233
+ * Template for the new array item. REQUIRED.
4234
+ * - Single field (FieldDef): Creates a primitive item (field's value is extracted directly)
4235
+ * - Array of fields (FieldDef[]): Creates an object item (fields merged into object)
3854
4236
  */
3855
- field?: ArrayAllowedChildren | undefined,
3856
- /** Optional index where to insert the item (defaults to end of array) */
3857
- index?: number | undefined);
4237
+ template: TTemplate);
4238
+ }
4239
+
4240
+ /**
4241
+ * Event dispatched to remove the LAST item from an array field.
4242
+ *
4243
+ * Equivalent to JavaScript's `Array.pop()` - removes from the end.
4244
+ * For removing from the beginning, use {@link ShiftArrayItemEvent}.
4245
+ * For removing at a specific index, use {@link RemoveAtIndexEvent}.
4246
+ *
4247
+ * @example
4248
+ * ```typescript
4249
+ * // Use the builder API (recommended)
4250
+ * eventBus.dispatch(arrayEvent('contacts').pop());
4251
+ *
4252
+ * // Or instantiate directly
4253
+ * eventBus.dispatch(new PopArrayItemEvent('contacts'));
4254
+ * ```
4255
+ */
4256
+ declare class PopArrayItemEvent implements FormEvent {
4257
+ /** The key of the array field to remove the last item from */
4258
+ readonly arrayKey: string;
4259
+ readonly type: "pop-array-item";
4260
+ constructor(
4261
+ /** The key of the array field to remove the last item from */
4262
+ arrayKey: string);
4263
+ }
4264
+
4265
+ /**
4266
+ * Event dispatched to remove the FIRST item from an array field.
4267
+ *
4268
+ * Equivalent to JavaScript's `Array.shift()` - removes from the beginning.
4269
+ * For removing from the end, use {@link PopArrayItemEvent}.
4270
+ * For removing at a specific index, use {@link RemoveAtIndexEvent}.
4271
+ *
4272
+ * @example
4273
+ * ```typescript
4274
+ * // Use the builder API (recommended)
4275
+ * eventBus.dispatch(arrayEvent('contacts').shift());
4276
+ *
4277
+ * // Or instantiate directly
4278
+ * eventBus.dispatch(new ShiftArrayItemEvent('contacts'));
4279
+ * ```
4280
+ */
4281
+ declare class ShiftArrayItemEvent implements FormEvent {
4282
+ /** The key of the array field to remove the first item from */
4283
+ readonly arrayKey: string;
4284
+ readonly type: "shift-array-item";
4285
+ constructor(
4286
+ /** The key of the array field to remove the first item from */
4287
+ arrayKey: string);
3858
4288
  }
3859
4289
 
3860
4290
  /**
3861
- * Event dispatched to remove an item from an array field.
4291
+ * Event dispatched to remove an item at a SPECIFIC INDEX from an array field.
3862
4292
  *
3863
- * This event is used to dynamically remove items from array fields at runtime.
3864
- * The array field component listens for this event and removes the item
3865
- * at the specified index.
4293
+ * Use this when you need precise control over which item to remove.
4294
+ * For simpler operations, use {@link PopArrayItemEvent} or {@link ShiftArrayItemEvent}.
3866
4295
  *
3867
4296
  * @example
3868
4297
  * ```typescript
3869
- * // Remove item at index 1 from 'contacts' array
3870
- * eventBus.dispatch(RemoveArrayItemEvent, 'contacts', 1);
4298
+ * // Use the builder API (recommended)
4299
+ * eventBus.dispatch(arrayEvent('contacts').removeAt(2));
3871
4300
  *
3872
- * // Remove last item
3873
- * eventBus.dispatch(RemoveArrayItemEvent, 'contacts');
4301
+ * // Or instantiate directly
4302
+ * eventBus.dispatch(new RemoveAtIndexEvent('contacts', 2));
3874
4303
  * ```
3875
4304
  */
3876
- declare class RemoveArrayItemEvent implements FormEvent {
4305
+ declare class RemoveAtIndexEvent implements FormEvent {
3877
4306
  /** The key of the array field to remove an item from */
3878
4307
  readonly arrayKey: string;
3879
- /** Index of the item to remove (defaults to last item) */
3880
- readonly index?: number | undefined;
3881
- readonly type: "remove-array-item";
4308
+ /** The index of the item to remove */
4309
+ readonly index: number;
4310
+ readonly type: "remove-at-index";
3882
4311
  constructor(
3883
4312
  /** The key of the array field to remove an item from */
3884
4313
  arrayKey: string,
3885
- /** Index of the item to remove (defaults to last item) */
3886
- index?: number | undefined);
4314
+ /** The index of the item to remove */
4315
+ index: number);
3887
4316
  }
3888
4317
 
4318
+ /**
4319
+ * Builder for array manipulation events.
4320
+ *
4321
+ * Provides a fluent, discoverable API for array operations.
4322
+ * Type `arrayEvent('key').` in your IDE to see all available operations.
4323
+ *
4324
+ * **BREAKING CHANGE**: Template is now required for add operations.
4325
+ *
4326
+ * Supports both primitive and object array items:
4327
+ * - Primitive: Pass a single field definition → extracts field value directly
4328
+ * - Object: Pass an array of field definitions → merges fields into object
4329
+ *
4330
+ * @example
4331
+ * ```typescript
4332
+ * import { arrayEvent } from '@ng-forge/dynamic-forms';
4333
+ *
4334
+ * // Object item: append { name, email } object
4335
+ * eventBus.dispatch(arrayEvent('contacts').append([
4336
+ * { key: 'name', type: 'input', label: 'Name' },
4337
+ * { key: 'email', type: 'input', label: 'Email' }
4338
+ * ]));
4339
+ *
4340
+ * // Primitive item: append single value
4341
+ * eventBus.dispatch(arrayEvent('tags').append(
4342
+ * { key: 'tag', type: 'input', label: 'Tag' }
4343
+ * ));
4344
+ *
4345
+ * // Removing items (no template needed)
4346
+ * eventBus.dispatch(arrayEvent('contacts').pop()); // Remove last
4347
+ * eventBus.dispatch(arrayEvent('contacts').shift()); // Remove first
4348
+ * eventBus.dispatch(arrayEvent('contacts').removeAt(2)); // Remove at index
4349
+ * ```
4350
+ *
4351
+ * @param arrayKey - The key of the array field to operate on
4352
+ * @returns An object with methods for all 6 array operations
4353
+ */
4354
+ declare function arrayEvent(arrayKey: string): {
4355
+ /**
4356
+ * Append a new item at the END of the array.
4357
+ * This is the most common operation for adding items.
4358
+ *
4359
+ * @param template - Template for the new item (REQUIRED)
4360
+ * - Single field: Creates a primitive item (field's value is extracted directly)
4361
+ * - Array of fields: Creates an object item (fields merged into object)
4362
+ * @returns An AppendArrayItemEvent to dispatch
4363
+ *
4364
+ * @example
4365
+ * ```typescript
4366
+ * // Object item
4367
+ * eventBus.dispatch(arrayEvent('contacts').append([
4368
+ * { key: 'name', type: 'input', label: 'Name' }
4369
+ * ]));
4370
+ *
4371
+ * // Primitive item
4372
+ * eventBus.dispatch(arrayEvent('tags').append(
4373
+ * { key: 'tag', type: 'input', label: 'Tag' }
4374
+ * ));
4375
+ * ```
4376
+ */
4377
+ append: <T extends ArrayItemDefinitionTemplate>(template: T) => AppendArrayItemEvent<T>;
4378
+ /**
4379
+ * Prepend a new item at the BEGINNING of the array.
4380
+ * Use when new items should appear at the start.
4381
+ *
4382
+ * @param template - Template for the new item (REQUIRED)
4383
+ * - Single field: Creates a primitive item (field's value is extracted directly)
4384
+ * - Array of fields: Creates an object item (fields merged into object)
4385
+ * @returns A PrependArrayItemEvent to dispatch
4386
+ *
4387
+ * @example
4388
+ * ```typescript
4389
+ * // Object item
4390
+ * eventBus.dispatch(arrayEvent('contacts').prepend([
4391
+ * { key: 'name', type: 'input', label: 'Name' }
4392
+ * ]));
4393
+ *
4394
+ * // Primitive item
4395
+ * eventBus.dispatch(arrayEvent('tags').prepend(
4396
+ * { key: 'tag', type: 'input', label: 'Tag' }
4397
+ * ));
4398
+ * ```
4399
+ */
4400
+ prepend: <T extends ArrayItemDefinitionTemplate>(template: T) => PrependArrayItemEvent<T>;
4401
+ /**
4402
+ * Insert a new item at a SPECIFIC INDEX in the array.
4403
+ * Use when you need precise control over item placement.
4404
+ *
4405
+ * @param index - The position at which to insert the new item
4406
+ * @param template - Template for the new item (REQUIRED)
4407
+ * - Single field: Creates a primitive item (field's value is extracted directly)
4408
+ * - Array of fields: Creates an object item (fields merged into object)
4409
+ * @returns An InsertArrayItemEvent to dispatch
4410
+ *
4411
+ * @example
4412
+ * ```typescript
4413
+ * // Object item at index 2
4414
+ * eventBus.dispatch(arrayEvent('contacts').insertAt(2, [
4415
+ * { key: 'name', type: 'input', label: 'Name' }
4416
+ * ]));
4417
+ *
4418
+ * // Primitive item at index 2
4419
+ * eventBus.dispatch(arrayEvent('tags').insertAt(2,
4420
+ * { key: 'tag', type: 'input', label: 'Tag' }
4421
+ * ));
4422
+ * ```
4423
+ */
4424
+ insertAt: <T extends ArrayItemDefinitionTemplate>(index: number, template: T) => InsertArrayItemEvent<T>;
4425
+ /**
4426
+ * Remove the LAST item from the array.
4427
+ * Equivalent to JavaScript's `Array.pop()`.
4428
+ *
4429
+ * @returns A PopArrayItemEvent to dispatch
4430
+ *
4431
+ * @example
4432
+ * ```typescript
4433
+ * eventBus.dispatch(arrayEvent('contacts').pop());
4434
+ * ```
4435
+ */
4436
+ pop: () => PopArrayItemEvent;
4437
+ /**
4438
+ * Remove the FIRST item from the array.
4439
+ * Equivalent to JavaScript's `Array.shift()`.
4440
+ *
4441
+ * @returns A ShiftArrayItemEvent to dispatch
4442
+ *
4443
+ * @example
4444
+ * ```typescript
4445
+ * eventBus.dispatch(arrayEvent('contacts').shift());
4446
+ * ```
4447
+ */
4448
+ shift: () => ShiftArrayItemEvent;
4449
+ /**
4450
+ * Remove an item at a SPECIFIC INDEX from the array.
4451
+ * Use when you need to remove a specific item by position.
4452
+ *
4453
+ * @param index - The position of the item to remove
4454
+ * @returns A RemoveAtIndexEvent to dispatch
4455
+ *
4456
+ * @example
4457
+ * ```typescript
4458
+ * eventBus.dispatch(arrayEvent('contacts').removeAt(2));
4459
+ * ```
4460
+ */
4461
+ removeAt: (index: number) => RemoveAtIndexEvent;
4462
+ };
4463
+
3889
4464
  /**
3890
4465
  * Event bus service for form-wide event communication.
3891
4466
  *
@@ -3984,6 +4559,8 @@ interface TokenContext {
3984
4559
  arrayKey?: string;
3985
4560
  /** Current form value for complex indexing */
3986
4561
  formValue?: unknown;
4562
+ /** Template for array item creation */
4563
+ template?: unknown;
3987
4564
  }
3988
4565
  /**
3989
4566
  * Resolves special tokens in event arguments to their actual values
@@ -3992,6 +4569,7 @@ interface TokenContext {
3992
4569
  * - $key: The current field key
3993
4570
  * - $index: The array index (if inside an array field)
3994
4571
  * - $arrayKey: The parent array field key (if inside an array field)
4572
+ * - $template: The template for array item creation
3995
4573
  * - formValue: Reference to the current form value for indexing
3996
4574
  *
3997
4575
  * @param args - Array of arguments that may contain tokens
@@ -4005,6 +4583,10 @@ interface TokenContext {
4005
4583
  * @example
4006
4584
  * resolveTokens(['$key', 'static'], { key: 'myField' })
4007
4585
  * // Returns: ['myField', 'static']
4586
+ *
4587
+ * @example
4588
+ * resolveTokens(['$arrayKey', '$template'], { arrayKey: 'contacts', template: [{ key: 'name', type: 'input' }] })
4589
+ * // Returns: ['contacts', [{ key: 'name', type: 'input' }]]
4008
4590
  */
4009
4591
  declare function resolveTokens(args: readonly (string | number | boolean | null | undefined)[], context: TokenContext): (string | number | boolean | null | undefined | unknown)[];
4010
4592
 
@@ -4221,8 +4803,14 @@ declare function pageFieldMapper(fieldDef: PageField): Signal<Record<string, unk
4221
4803
  * Maps a text field definition to component inputs.
4222
4804
  *
4223
4805
  * Text fields are display-only fields that don't participate in the form schema.
4224
- * This mapper handles the `logic` configuration by creating reactive signals
4225
- * that evaluate conditions using the form value from RootFormRegistryService.
4806
+ * This mapper handles the `logic` configuration by using the non-field-hidden resolver
4807
+ * to evaluate conditions against the form value from RootFormRegistryService.
4808
+ *
4809
+ * Hidden state is resolved using the non-field-hidden resolver which considers:
4810
+ * 1. Explicit `hidden: true` on the field definition
4811
+ * 2. Field-level `logic` array with `type: 'hidden'` conditions
4812
+ *
4813
+ * Note: Text fields don't support disabled logic since they are display-only.
4226
4814
  *
4227
4815
  * @param fieldDef The text field definition
4228
4816
  * @returns Signal containing Record of input names to values for ngComponentOutlet
@@ -4343,9 +4931,7 @@ declare class GroupFieldComponent<TModel extends Record<string, unknown> = Recor
4343
4931
  private readonly injector;
4344
4932
  private readonly eventBus;
4345
4933
  private readonly logger;
4346
- private readonly memoizedFlattenFields;
4347
- private readonly memoizedKeyBy;
4348
- private readonly memoizedDefaultValues;
4934
+ private readonly fieldProcessors;
4349
4935
  field: _angular_core.InputSignal<GroupField<readonly _ng_forge_dynamic_forms.GroupAllowedChildren[]>>;
4350
4936
  key: _angular_core.InputSignal<string>;
4351
4937
  className: _angular_core.InputSignal<string | undefined>;
@@ -4353,6 +4939,11 @@ declare class GroupFieldComponent<TModel extends Record<string, unknown> = Recor
4353
4939
  private readonly rawFieldRegistry;
4354
4940
  private readonly formSetup;
4355
4941
  readonly defaultValues: _angular_core.WritableSignal<Record<string, unknown>>;
4942
+ /**
4943
+ * Entity computed from parent value, group key, and defaults.
4944
+ * Uses deep equality check to prevent unnecessary updates when
4945
+ * object spread creates new references with identical values.
4946
+ */
4356
4947
  private readonly entity;
4357
4948
  private readonly form;
4358
4949
  readonly formValue: _angular_core.Signal<{}>;
@@ -4363,6 +4954,7 @@ declare class GroupFieldComponent<TModel extends Record<string, unknown> = Recor
4363
4954
  readonly errors: _angular_core.Signal<_angular_forms_signals.ValidationError.WithField[]>;
4364
4955
  readonly disabled: _angular_core.Signal<boolean>;
4365
4956
  private readonly nestedFieldTree;
4957
+ private readonly groupFieldSignalContext;
4366
4958
  private readonly groupInjector;
4367
4959
  readonly validityChange: _angular_core.OutputRef<boolean>;
4368
4960
  readonly dirtyChange: _angular_core.OutputRef<boolean>;
@@ -4376,13 +4968,9 @@ declare class GroupFieldComponent<TModel extends Record<string, unknown> = Recor
4376
4968
  }
4377
4969
 
4378
4970
  /**
4379
- * Resolved array item ready for declarative rendering with ngComponentOutlet.
4380
- * Each item has a stable unique ID and a linkedSignal-based index that
4381
- * automatically updates when items are added/removed.
4971
+ * A single field within a resolved array item.
4382
4972
  */
4383
- interface ResolvedArrayItem {
4384
- /** Unique identifier for this item (stable across position changes). */
4385
- id: string;
4973
+ interface ResolvedArrayItemField {
4386
4974
  /** The loaded component type. */
4387
4975
  component: Type<unknown>;
4388
4976
  /** Injector providing ARRAY_CONTEXT and FIELD_SIGNAL_CONTEXT. */
@@ -4390,13 +4978,26 @@ interface ResolvedArrayItem {
4390
4978
  /** Inputs signal for ngComponentOutlet - evaluated in template for reactivity. */
4391
4979
  inputs: Signal<Record<string, unknown>>;
4392
4980
  }
4981
+ /**
4982
+ * Resolved array item ready for declarative rendering with ngComponentOutlet.
4983
+ * Each item has a stable unique ID and a linkedSignal-based index that
4984
+ * automatically updates when items are added/removed.
4985
+ * Supports multiple fields per array item (e.g., name + email without a wrapper).
4986
+ */
4987
+ interface ResolvedArrayItem {
4988
+ /** Unique identifier for this item (stable across position changes). */
4989
+ id: string;
4990
+ /** All fields to render for this array item. */
4991
+ fields: ResolvedArrayItemField[];
4992
+ }
4393
4993
 
4394
4994
  /**
4395
4995
  * Container component for rendering dynamic arrays of fields.
4396
4996
  *
4397
- * Supports add/remove operations via AddArrayItemEvent and RemoveArrayItemEvent.
4997
+ * Supports add/remove operations via the arrayEvent() builder API.
4398
4998
  * Uses differential updates to optimize rendering - only recreates items when necessary.
4399
4999
  * Each item gets a scoped injector with ARRAY_CONTEXT for position-aware operations.
5000
+ * Supports multiple sibling fields per array item (e.g., name + email without a wrapper).
4400
5001
  */
4401
5002
  declare class ArrayFieldComponent<TModel extends Record<string, unknown> = Record<string, unknown>> {
4402
5003
  private readonly destroyRef;
@@ -4405,25 +5006,62 @@ declare class ArrayFieldComponent<TModel extends Record<string, unknown> = Recor
4405
5006
  private readonly parentInjector;
4406
5007
  private readonly eventBus;
4407
5008
  private readonly logger;
4408
- field: _angular_core.InputSignal<ArrayField<readonly _ng_forge_dynamic_forms.ArrayAllowedChildren[]>>;
5009
+ private readonly templateRegistry;
5010
+ private readonly generateItemId;
5011
+ field: _angular_core.InputSignal<ArrayField<readonly ArrayItemDefinition[]>>;
4409
5012
  key: _angular_core.InputSignal<string>;
4410
5013
  className: _angular_core.InputSignal<string | undefined>;
4411
5014
  readonly hostClasses: _angular_core.Signal<string>;
4412
5015
  private readonly rawFieldRegistry;
4413
- private readonly fieldTemplate;
5016
+ /**
5017
+ * Gets the item templates (field definitions) for the array.
5018
+ * Each element can be either:
5019
+ * - A single FieldDef (primitive item) - normalized to [FieldDef]
5020
+ * - An array of FieldDefs (object item) - used as-is
5021
+ *
5022
+ * Returns normalized templates where all items are arrays for consistent handling.
5023
+ */
5024
+ private readonly itemTemplates;
4414
5025
  private readonly arrayFieldTrees;
4415
5026
  private readonly resolvedItemsSignal;
4416
5027
  private readonly updateVersion;
4417
- private readonly itemOrderSignal;
4418
- readonly resolvedItems: _angular_core.WritableSignal<ResolvedArrayItem[]>;
5028
+ /**
5029
+ * Map of item IDs to their current positions. O(1) lookup vs O(n) indexOf().
5030
+ * Used by child linkedSignals to reactively track their position in the array.
5031
+ */
5032
+ private readonly itemPositionMap;
5033
+ /** Read-only view of resolved items for template consumption. */
5034
+ readonly resolvedItems: _angular_core.Signal<ResolvedArrayItem[]>;
4419
5035
  constructor();
4420
5036
  private setupEffects;
4421
5037
  private setupEventHandlers;
5038
+ /**
5039
+ * Handles add operations from events (append, prepend, insert).
5040
+ * Creates resolved items FIRST, then updates form value.
5041
+ * This ensures prepend/insert work correctly - differential update sees "none"
5042
+ * because resolved items count already matches the new array length.
5043
+ *
5044
+ * Supports both primitive (single FieldDef) and object (FieldDef[]) templates.
5045
+ */
5046
+ private handleAddFromEvent;
4422
5047
  private performDifferentialUpdate;
4423
5048
  private resolveAllItems;
4424
5049
  private appendItems;
5050
+ /**
5051
+ * Creates an observable that resolves a single array item.
5052
+ *
5053
+ * Template resolution order:
5054
+ * 1. Use overrideTemplate if provided (from recreate with stored templates)
5055
+ * 2. Use itemTemplates[index] if within defined templates range
5056
+ * 3. Return undefined (item cannot be resolved without a template)
5057
+ */
4425
5058
  private createResolveItemObservable;
4426
- private addItem;
5059
+ /**
5060
+ * Handles remove operations from events (pop, shift, removeAt).
5061
+ * Updates resolvedItems FIRST, then form value - this ensures differential
5062
+ * update sees "none" (lengths match) and avoids unnecessary recreates.
5063
+ * Remaining items' linkedSignal indices auto-update via itemPositionMap.
5064
+ */
4427
5065
  private removeItem;
4428
5066
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<ArrayFieldComponent<any>, never>;
4429
5067
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<ArrayFieldComponent<any>, "array-field", never, { "field": { "alias": "field"; "required": true; "isSignal": true; }; "key": { "alias": "key"; "required": true; "isSignal": true; }; "className": { "alias": "className"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
@@ -4501,97 +5139,16 @@ declare function omit<T extends object, K extends keyof T>(obj: T, keys: K[]): O
4501
5139
  declare function interpolateParams(message: string, error: ValidationError): string;
4502
5140
 
4503
5141
  /**
4504
- * Registry service that maintains references to root form fields.
4505
- * This provides a type-safe way to access root form values from child fields
4506
- * without relying on internal Angular Forms API navigation.
4507
- *
4508
- * This service should be provided at the component level to ensure proper
4509
- * isolation between different form instances.
5142
+ * Registry service that provides access to the root form and its values.
4510
5143
  *
4511
- * IMPORTANT: This registry supports two registration patterns:
4512
- * 1. registerFormValueSignal() - Register the SOURCE signal BEFORE form creation
4513
- * This allows logic functions to read form values during schema evaluation.
4514
- * 2. registerRootForm() - Register the FieldTree AFTER form creation
4515
- * This provides access to the full form structure.
4516
- *
4517
- * For cross-field logic to work correctly, registerFormValueSignal() should be
4518
- * called BEFORE the form() call.
5144
+ * Constructed via factory in `provideDynamicFormDI` with signals from
5145
+ * `FormStateManager` — no DI injection, no Maps, no lifecycle.
4519
5146
  */
4520
5147
  declare class RootFormRegistryService {
4521
- private readonly _rootForms;
4522
- private readonly _formValueSignals;
4523
- private readonly formContexts;
4524
- /**
4525
- * Registers the form value signal BEFORE form creation.
4526
- *
4527
- * This is the recommended pattern for cross-field logic:
4528
- * 1. Create the formValue signal
4529
- * 2. Register it with registerFormValueSignal()
4530
- * 3. Create the form with form(formValue, schema(...))
4531
- *
4532
- * This allows logic functions to access form values during schema evaluation.
4533
- */
4534
- registerFormValueSignal(valueSignal: Signal<Record<string, unknown>>, formId?: string): void;
4535
- /**
4536
- * Gets the registered form value signal by id.
4537
- *
4538
- * This allows direct access to the source signal, which can be read
4539
- * reactively to get form values.
4540
- */
4541
- getFormValueSignal(formId?: string): Signal<Record<string, unknown>> | undefined;
4542
- /**
4543
- * Registers a root form field with an optional identifier.
4544
- * If no id is provided, uses 'default' as the key.
4545
- *
4546
- * This triggers reactive updates for any computed signals that
4547
- * depend on getRootForm().
4548
- */
4549
- registerRootForm(rootField: FieldTree<unknown>, formId?: string): void;
4550
- /**
4551
- * Gets a registered root form field by id.
4552
- *
4553
- * IMPORTANT: This method reads from a signal, which means any computed
4554
- * signal that calls this method will automatically re-evaluate when
4555
- * the form is registered or updated.
4556
- */
4557
- getRootForm(formId?: string): FieldTree<unknown> | undefined;
4558
- /**
4559
- * Gets the form value, preferring the direct signal if available.
4560
- *
4561
- * This method tries in order:
4562
- * 1. Form value signal (if registered) - reads directly from source signal
4563
- * 2. Root form's .value() (if registered) - reads from FieldTree
4564
- * 3. Returns empty object if nothing is registered
4565
- *
4566
- * Reading from signals creates reactive dependencies, so computeds will
4567
- * re-evaluate when form values change.
4568
- */
4569
- getFormValue(formId?: string): Record<string, unknown>;
4570
- /**
4571
- * Registers form context (field paths and metadata) for a specific form.
4572
- * This can be used to store additional context information.
4573
- */
4574
- registerFormContext(context: Record<string, unknown>, formId?: string): void;
4575
- /**
4576
- * Gets form context by id.
4577
- */
4578
- getFormContext(formId?: string): Record<string, unknown>;
4579
- /**
4580
- * Unregisters a form and its context.
4581
- * This should be called when a form component is destroyed to prevent memory leaks.
4582
- */
4583
- unregisterForm(formId?: string): void;
4584
- /**
4585
- * Gets all registered form IDs.
4586
- */
4587
- getRegisteredFormIds(): string[];
4588
- /**
4589
- * Clears all registered forms. Useful for testing.
4590
- */
4591
- clear(): void;
4592
- static ɵfac: _angular_core.ɵɵFactoryDeclaration<RootFormRegistryService, never>;
4593
- static ɵprov: _angular_core.ɵɵInjectableDeclaration<RootFormRegistryService>;
5148
+ readonly formValue: Signal<Record<string, unknown>>;
5149
+ readonly rootForm: Signal<FieldTree<Record<string, unknown>> | undefined>;
5150
+ constructor(formValue: Signal<Record<string, unknown>>, rootForm: Signal<FieldTree<Record<string, unknown>> | undefined>);
4594
5151
  }
4595
5152
 
4596
- export { ARRAY_CONTEXT, AddArrayItemEvent, ArrayFieldComponent, BUILT_IN_FIELDS, ConsoleLogger, DEFAULT_PROPS, DEFAULT_VALIDATION_MESSAGES, DynamicForm, DynamicFormError, DynamicFormLogger, DynamicTextPipe, EventBus, FIELD_REGISTRY, FIELD_SIGNAL_CONTEXT, FORM_OPTIONS, FormClearEvent, FormResetEvent, GroupFieldComponent, NextPageEvent, NoopLogger, PageChangeEvent, PreviousPageEvent, RemoveArrayItemEvent, RootFormRegistryService, RowFieldComponent, SubmitEvent, applyMetaToElement, applyValidator, applyValidators, arrayFieldMapper, baseFieldMapper, buildBaseInputs, createField, dynamicTextToObservable, field, formConfig, getArrayLength, groupFieldMapper, hasFormValue, interpolateParams, isArrayField, isCheckedField, isContainerField, isDisplayOnlyField, isEqual, isFormStateCondition, isGroupField, isLeafField, isPageField, isRowField, isValueBearingField, isValueField, omit, pageFieldMapper, provideDynamicForm, resolveNextButtonDisabled, resolveSubmitButtonDisabled, resolveTokens, rowFieldMapper, textFieldMapper, withEventFormValue, withLoggerConfig };
4597
- export type { ArrayAllowedChildren, ArrayContext, ArrayField, ArrayFieldTree, ArrayItemContext, AsyncCustomValidator, AsyncValidatorConfig, AvailableFieldTypes, BaseCheckedField, BaseValidatorConfig, BaseValueField, BuiltInValidatorConfig, ButtonLogicContext, CheckedFieldComponent, ConditionalExpression, ContainerFieldTypes, CustomFnConfig, CustomValidator, CustomValidatorConfig, DynamicFormFieldRegistry, DynamicText, EvaluationContext, ExtractField, ExtractFieldDefs, ExtractFormValue, FieldComponent, FieldDef, FieldMeta, FieldOption, FieldPathAccess, FieldRegistryContainers, FieldRegistryLeaves, FieldSignalContext, FieldTypeDefinition, FieldWithValidation, FormConfig, FormEvent, FormEventConstructor, FormMode, FormModeDetectionResult, FormOptions, FormStateCondition, GroupAllowedChildren, GroupField, HttpCustomValidator, HttpResourceRequest, HttpValidatorConfig, InferFormValue$1 as InferFormValue, LeafFieldTypes, Logger, LogicConfig, MapperFn, NarrowField, NarrowFields, NextButtonOptions, PageAllowedChildren, PageField, Prettify, RegisteredFieldTypes, RowAllowedChildren, RowField, SchemaApplicationConfig, SchemaDefinition, SubmissionActionResult, SubmissionConfig, SubmitButtonOptions, TextElementType, TextField, TextProps, TokenContext, ValidationError, ValidationMessages, ValidatorConfig, ValueFieldComponent, ValueHandlingMode, ValueType, WithInputSignals };
5153
+ export { ARRAY_CONTEXT, AppendArrayItemEvent, ArrayFieldComponent, BUILT_IN_FIELDS, ConsoleLogger, DEFAULT_PROPS, DEFAULT_VALIDATION_MESSAGES, DynamicForm, DynamicFormError, DynamicFormLogger, DynamicTextPipe, EventBus, FIELD_REGISTRY, FIELD_SIGNAL_CONTEXT, FORM_OPTIONS, FormClearEvent, FormResetEvent, GroupFieldComponent, InsertArrayItemEvent, NextPageEvent, NoopLogger, PageChangeEvent, PopArrayItemEvent, PrependArrayItemEvent, PreviousPageEvent, RemoveAtIndexEvent, RootFormRegistryService, RowFieldComponent, ShiftArrayItemEvent, SubmitEvent, applyMetaToElement, applyValidator, applyValidators, arrayEvent, arrayFieldMapper, baseFieldMapper, buildBaseInputs, createField, dynamicTextToObservable, field, formConfig, getArrayLength, groupFieldMapper, hasFormValue, interpolateParams, isArrayField, isCheckedField, isContainerField, isDisplayOnlyField, isEqual, isFormStateCondition, isGroupField, isLeafField, isPageField, isPropertyDerivationLogicConfig, isRowField, isValueBearingField, isValueField, omit, pageFieldMapper, provideDynamicForm, resolveNextButtonDisabled, resolveNonFieldDisabled, resolveNonFieldHidden, resolveSubmitButtonDisabled, resolveTokens, rowFieldMapper, textFieldMapper, withEventFormValue, withLoggerConfig };
5154
+ export type { ArrayAllowedChildren, ArrayContext, ArrayField, ArrayFieldTree, ArrayItemContext, ArrayItemDefinitionTemplate, ArrayItemTemplate, AsyncCustomValidator, AsyncValidatorConfig, AvailableFieldTypes, BaseCheckedField, BaseValidatorConfig, BaseValueField, BuiltInValidatorConfig, ButtonLogicContext, CheckedFieldComponent, ConditionalExpression, ContainerFieldTypes, CustomFnConfig, CustomValidator, CustomValidatorConfig, DynamicFormFieldRegistry, DynamicText, EvaluationContext, ExtractField, ExtractFieldDefs, ExtractFormValue, FieldComponent, FieldDef, FieldMeta, FieldOption, FieldPathAccess, FieldRegistryContainers, FieldRegistryLeaves, FieldSignalContext, FieldTypeDefinition, FieldWithValidation, FormConfig, FormEvent, FormEventConstructor, FormMode, FormModeDetectionResult, FormOptions, FormStateCondition, GroupAllowedChildren, GroupField, HttpCustomValidator, HttpResourceRequest, HttpValidatorConfig, InferFormValue$1 as InferFormValue, LeafFieldTypes, Logger, LogicConfig, MapperFn, NarrowField, NarrowFields, NextButtonOptions, NonFieldLogicConfig, NonFieldLogicContext, NonFieldLogicType, PageAllowedChildren, PageField, Prettify, PropertyDerivationLogicConfig, RegisteredFieldTypes, RowAllowedChildren, RowField, SchemaApplicationConfig, SchemaDefinition, StateLogicConfig, SubmissionActionResult, SubmissionConfig, SubmitButtonOptions, TextElementType, TextField, TextProps, TokenContext, ValidationError, ValidationMessages, ValidatorConfig, ValueFieldComponent, ValueHandlingMode, ValueType, WithInputSignals };