@ng-forge/dynamic-forms 0.5.2 → 0.6.1

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.
@@ -3,7 +3,7 @@ import { Observable } from 'rxjs';
3
3
  import * as _angular_core from '@angular/core';
4
4
  import { Signal, Injector, WritableSignal, InjectionToken, Type, ResourceRef, Provider, EnvironmentProviders, PipeTransform } from '@angular/core';
5
5
  import * as _angular_forms_signals from '@angular/forms/signals';
6
- import { FieldTree, FieldContext, ValidationError as ValidationError$1, TreeValidationResult, SchemaPath, SchemaPathTree } from '@angular/forms/signals';
6
+ import { FieldTree, FieldContext, TreeValidationResult, ValidationError as ValidationError$1, SchemaPath, SchemaPathTree } from '@angular/forms/signals';
7
7
  import * as _angular_forms from '@angular/forms';
8
8
  import * as _ng_forge_dynamic_forms from '@ng-forge/dynamic-forms';
9
9
  import { FormSchema } from '@ng-forge/dynamic-forms/schema';
@@ -251,6 +251,30 @@ interface FieldDef<TProps, TMeta extends FieldMeta = FieldMeta> {
251
251
  * ```
252
252
  */
253
253
  tabIndex?: number | undefined;
254
+ /**
255
+ * Whether to exclude this field's value from submission output when hidden.
256
+ *
257
+ * Overrides both the global `withValueExclusionDefaults()` and form-level `FormOptions` settings.
258
+ *
259
+ * @default undefined (uses form-level or global setting)
260
+ */
261
+ excludeValueIfHidden?: boolean;
262
+ /**
263
+ * Whether to exclude this field's value from submission output when disabled.
264
+ *
265
+ * Overrides both the global `withValueExclusionDefaults()` and form-level `FormOptions` settings.
266
+ *
267
+ * @default undefined (uses form-level or global setting)
268
+ */
269
+ excludeValueIfDisabled?: boolean;
270
+ /**
271
+ * Whether to exclude this field's value from submission output when readonly.
272
+ *
273
+ * Overrides both the global `withValueExclusionDefaults()` and form-level `FormOptions` settings.
274
+ *
275
+ * @default undefined (uses form-level or global setting)
276
+ */
277
+ excludeValueIfReadonly?: boolean;
254
278
  /**
255
279
  * Column sizing configuration for responsive grid layout.
256
280
  *
@@ -447,6 +471,249 @@ interface ResolvedField {
447
471
  inputs: Signal<Record<string, unknown>>;
448
472
  }
449
473
 
474
+ /**
475
+ * Configuration for an HTTP request used by declarative HTTP validators, derivations, and conditions.
476
+ *
477
+ * All string values in `queryParams` are treated as expressions evaluated by ExpressionParser.
478
+ * When `evaluateBodyExpressions` is true, top-level string values in `body` are also evaluated as expressions (shallow only).
479
+ */
480
+ interface HttpRequestConfig {
481
+ /** URL to send the request to */
482
+ url: string;
483
+ /** HTTP method. Defaults to 'GET' */
484
+ method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
485
+ /**
486
+ * URL path parameters. Keys correspond to `:key` placeholders in the URL.
487
+ * Values are expressions evaluated by ExpressionParser against the current EvaluationContext.
488
+ *
489
+ * @example
490
+ * ```typescript
491
+ * {
492
+ * url: '/api/users/:userId/orders/:orderId',
493
+ * params: { userId: 'formValue.userId', orderId: 'fieldValue' }
494
+ * }
495
+ * // With userId=42 and fieldValue='abc' → '/api/users/42/orders/abc'
496
+ * ```
497
+ */
498
+ params?: Record<string, string>;
499
+ /**
500
+ * Query parameters appended to the URL.
501
+ * Values are expressions evaluated by ExpressionParser against the current EvaluationContext.
502
+ */
503
+ queryParams?: Record<string, string>;
504
+ /** Request body (for POST/PUT/PATCH) */
505
+ body?: Record<string, unknown>;
506
+ /**
507
+ * When true, top-level string values in `body` are treated as expressions
508
+ * and evaluated by ExpressionParser (shallow only — nested objects are passed through as-is).
509
+ */
510
+ evaluateBodyExpressions?: boolean;
511
+ /** HTTP headers to include in the request */
512
+ headers?: Record<string, string>;
513
+ }
514
+
515
+ /**
516
+ * Comparison operators for field value and form value conditions.
517
+ *
518
+ * @public
519
+ */
520
+ type ComparisonOperator = 'equals' | 'notEquals' | 'greater' | 'less' | 'greaterOrEqual' | 'lessOrEqual' | 'contains' | 'startsWith' | 'endsWith' | 'matches';
521
+ /**
522
+ * Condition that compares a specific field's value against an expected value.
523
+ *
524
+ * @public
525
+ */
526
+ interface FieldValueCondition {
527
+ type: 'fieldValue';
528
+ /** Path to the field whose value to compare */
529
+ fieldPath: string;
530
+ /** Comparison operator */
531
+ operator: ComparisonOperator;
532
+ /** Value to compare against */
533
+ value?: unknown;
534
+ }
535
+ /**
536
+ * Condition that compares the entire form value against an expected value.
537
+ *
538
+ * @deprecated Use `FieldValueCondition` for specific field checks, or
539
+ * `JavascriptCondition` with an expression for complex form-level comparisons.
540
+ * Will be removed in a future minor version.
541
+ *
542
+ * @public
543
+ */
544
+ interface FormValueCondition {
545
+ type: 'formValue';
546
+ /** Comparison operator */
547
+ operator: ComparisonOperator;
548
+ /** Value to compare against */
549
+ value?: unknown;
550
+ }
551
+ /**
552
+ * Condition that invokes a registered custom function by name (new API).
553
+ *
554
+ * Register functions via `customFnConfig.customFunctions`.
555
+ *
556
+ * @internal
557
+ */
558
+ interface CustomConditionNew {
559
+ type: 'custom';
560
+ /** Name of the registered custom function to invoke */
561
+ functionName: string;
562
+ }
563
+ /**
564
+ * @deprecated Use `CustomCondition` with `functionName` instead.
565
+ * The `expression` field was misnamed — it holds a function registry key, not a JS expression.
566
+ * Will be removed in a future minor version.
567
+ *
568
+ * @internal
569
+ */
570
+ interface CustomConditionDeprecated {
571
+ type: 'custom';
572
+ /**
573
+ * @deprecated Use `functionName` instead. Will be removed in a future minor version.
574
+ */
575
+ expression: string;
576
+ }
577
+ /**
578
+ * Condition that invokes a registered custom function by name.
579
+ *
580
+ * Use `functionName` (new API). The `expression` field is deprecated.
581
+ * Register functions via `customFnConfig.customFunctions`.
582
+ *
583
+ * @public
584
+ */
585
+ type CustomCondition = CustomConditionNew | CustomConditionDeprecated;
586
+ /**
587
+ * Condition that evaluates a JavaScript expression using the secure AST-based parser.
588
+ *
589
+ * The expression has access to `formValue`, `fieldValue`, `externalData`, etc.
590
+ *
591
+ * @public
592
+ */
593
+ interface JavascriptCondition {
594
+ type: 'javascript';
595
+ /** JavaScript expression string to evaluate */
596
+ expression: string;
597
+ }
598
+ /**
599
+ * Condition that evaluates based on an HTTP response from a remote server.
600
+ *
601
+ * The HTTP request is resolved reactively — when dependent form values change,
602
+ * the request is re-evaluated (with debouncing). The result is cached per
603
+ * resolved request to avoid redundant network calls.
604
+ *
605
+ * Since `LogicFn` must return `boolean` synchronously, this condition uses
606
+ * a signal-based async resolution pattern internally.
607
+ *
608
+ * @public
609
+ */
610
+ interface HttpCondition {
611
+ type: 'http';
612
+ /** HTTP request configuration */
613
+ http: HttpRequestConfig;
614
+ /**
615
+ * Expression to extract boolean from response (scope: `{ response }`).
616
+ * e.g. `'response.allowed'`, `'response.permissions.canEdit'`
617
+ * When omitted, the entire response is coerced to boolean: `!!response`
618
+ */
619
+ responseExpression?: string;
620
+ /**
621
+ * Value to return while the HTTP request is in-flight.
622
+ *
623
+ * Choose based on the logic type and desired UX:
624
+ * - For `hidden`: `false` = visible while loading, `true` = hidden while loading
625
+ * - For `disabled`: `false` = enabled while loading, `true` = disabled while loading
626
+ * - For `required`: `false` = optional while loading, `true` = required while loading
627
+ *
628
+ * @default false
629
+ */
630
+ pendingValue?: boolean;
631
+ /** Cache duration in ms for HTTP responses. @default 30000 */
632
+ cacheDurationMs?: number;
633
+ /**
634
+ * Debounce time in milliseconds for re-evaluation when dependent form values change.
635
+ * @default 300
636
+ */
637
+ debounceMs?: number;
638
+ }
639
+ /**
640
+ * Condition that evaluates based on a registered async function.
641
+ *
642
+ * The function is resolved reactively — when dependent form values change,
643
+ * the function is re-evaluated (with debouncing). The result is cached per
644
+ * evaluation to avoid redundant calls.
645
+ *
646
+ * Since `LogicFn` must return `boolean` synchronously, this condition uses
647
+ * a signal-based async resolution pattern internally.
648
+ *
649
+ * @public
650
+ */
651
+ interface AsyncCondition {
652
+ type: 'async';
653
+ /** Name of the registered async condition function */
654
+ asyncFunctionName: string;
655
+ /**
656
+ * Value to return while async resolution is pending.
657
+ *
658
+ * Choose based on the logic type and desired UX:
659
+ * - For `hidden`: `false` = visible while loading, `true` = hidden while loading
660
+ * - For `disabled`: `false` = enabled while loading, `true` = disabled while loading
661
+ * - For `required`: `false` = optional while loading, `true` = required while loading
662
+ *
663
+ * @default false
664
+ */
665
+ pendingValue?: boolean;
666
+ /**
667
+ * Debounce ms for re-evaluation.
668
+ * @default 300
669
+ */
670
+ debounceMs?: number;
671
+ }
672
+ /**
673
+ * Logical AND — all sub-conditions must be true.
674
+ *
675
+ * @public
676
+ */
677
+ interface AndCondition {
678
+ type: 'and';
679
+ /** Sub-conditions that must all evaluate to true */
680
+ conditions: ConditionalExpression[];
681
+ }
682
+ /**
683
+ * Logical OR — at least one sub-condition must be true.
684
+ *
685
+ * @public
686
+ */
687
+ interface OrCondition {
688
+ type: 'or';
689
+ /** Sub-conditions where at least one must evaluate to true */
690
+ conditions: ConditionalExpression[];
691
+ }
692
+ /**
693
+ * Discriminated union of all conditional expression types.
694
+ *
695
+ * Each variant only allows the properties relevant to its type,
696
+ * providing compile-time safety against invalid property combinations.
697
+ *
698
+ * @public
699
+ */
700
+ type ConditionalExpression = FieldValueCondition | FormValueCondition | CustomCondition | JavascriptCondition | HttpCondition | AsyncCondition | AndCondition | OrCondition;
701
+
702
+ /**
703
+ * Logic configuration for container fields (page, group, row, array).
704
+ * Containers only support the 'hidden' logic type since they are layout containers,
705
+ * not form controls (they can't be readonly, disabled, or required).
706
+ */
707
+ interface ContainerLogicConfig {
708
+ /** Only 'hidden' is supported for container fields */
709
+ type: 'hidden';
710
+ /**
711
+ * Condition that determines when this container is hidden.
712
+ * Can be a boolean or a conditional expression evaluated against form values.
713
+ */
714
+ condition: ConditionalExpression | boolean;
715
+ }
716
+
450
717
  /**
451
718
  * Row field interface for creating horizontal layouts
452
719
  * This is a special field type that contains other definitions arranged horizontally
@@ -467,6 +734,11 @@ interface RowField<TFields extends readonly RowAllowedChildren[] = readonly RowA
467
734
  readonly label?: never;
468
735
  /** Rows do not support meta - they have no native form element **/
469
736
  readonly meta?: never;
737
+ /**
738
+ * Logic configurations for conditional row visibility.
739
+ * Only 'hidden' type logic is supported for rows.
740
+ */
741
+ readonly logic?: ContainerLogicConfig[];
470
742
  }
471
743
  /**
472
744
  * Type guard for RowField with proper type narrowing
@@ -491,6 +763,11 @@ interface GroupField<TFields extends readonly GroupAllowedChildren[] = readonly
491
763
  readonly label?: never;
492
764
  /** Groups do not support meta - they have no native form element **/
493
765
  readonly meta?: never;
766
+ /**
767
+ * Logic configurations for conditional group visibility.
768
+ * Only 'hidden' type logic is supported for groups.
769
+ */
770
+ readonly logic?: ContainerLogicConfig[];
494
771
  }
495
772
  /**
496
773
  * Type guard for GroupField with proper type narrowing
@@ -559,13 +836,13 @@ interface HiddenField<TValue extends HiddenValue = HiddenValue> extends FieldDef
559
836
  * Fields that are allowed as children of Page fields
560
837
  * Pages can contain: rows, groups, arrays, and leaf fields (but NOT other pages)
561
838
  */
562
- type PageAllowedChildren = LeafFieldTypes | RowField | GroupField | ArrayField;
839
+ type PageAllowedChildren = LeafFieldTypes | RowField | GroupField | ArrayField | SimplifiedArrayField;
563
840
  /**
564
841
  * Fields that are allowed as children of Row fields
565
842
  * Rows can contain: groups, arrays, and leaf fields (but NOT pages, other rows, or hidden fields)
566
843
  * Hidden fields are excluded because rows are for horizontal layouts and hidden fields don't render
567
844
  */
568
- type RowAllowedChildren = Exclude<LeafFieldTypes, HiddenField> | GroupField | ArrayField;
845
+ type RowAllowedChildren = Exclude<LeafFieldTypes, HiddenField> | GroupField | ArrayField | SimplifiedArrayField;
569
846
  /**
570
847
  * Fields that are allowed as children of Group fields
571
848
  * Groups can contain: rows and leaf fields (but NOT pages or other groups)
@@ -582,7 +859,7 @@ type ArrayAllowedChildren = LeafFieldTypes | RowField | GroupField;
582
859
  * An array item template is an array of allowed children that defines one OBJECT array item's fields.
583
860
  * Used for object arrays where each item is `{ key1: value1, key2: value2, ... }`.
584
861
  */
585
- type ArrayItemTemplate$1 = readonly ArrayAllowedChildren[];
862
+ type ArrayItemTemplate = readonly ArrayAllowedChildren[];
586
863
  /**
587
864
  * An array item definition can be either:
588
865
  * - A single field (ArrayAllowedChildren) for PRIMITIVE array items - extracts field value directly
@@ -593,7 +870,7 @@ type ArrayItemTemplate$1 = readonly ArrayAllowedChildren[];
593
870
  * - Object arrays: `[{ name: 'Alice', email: '...' }]`
594
871
  * - Heterogeneous arrays: `[{ value: 'x' }, 'y']` (mixed primitives and objects)
595
872
  */
596
- type ArrayItemDefinition = ArrayAllowedChildren | ArrayItemTemplate$1;
873
+ type ArrayItemDefinition = ArrayAllowedChildren | ArrayItemTemplate;
597
874
  /**
598
875
  * Array field interface for creating dynamic field collections that map to array values.
599
876
  *
@@ -669,6 +946,21 @@ interface ArrayField<TFields extends readonly ArrayItemDefinition[] = readonly A
669
946
  readonly label?: never;
670
947
  /** Arrays do not support meta - they have no native form element **/
671
948
  readonly meta?: never;
949
+ /**
950
+ * Logic configurations for conditional array visibility.
951
+ * Only 'hidden' type logic is supported for arrays.
952
+ */
953
+ readonly logic?: ContainerLogicConfig[];
954
+ /**
955
+ * Minimum number of items required in the array.
956
+ * Validation fails if the array has fewer items than this value.
957
+ */
958
+ readonly minLength?: number;
959
+ /**
960
+ * Maximum number of items allowed in the array.
961
+ * Validation fails if the array has more items than this value.
962
+ */
963
+ readonly maxLength?: number;
672
964
  }
673
965
  /**
674
966
  * Type guard for ArrayField with proper type narrowing.
@@ -676,36 +968,103 @@ interface ArrayField<TFields extends readonly ArrayItemDefinition[] = readonly A
676
968
  * Fields can contain either single FieldDefs (primitive items) or arrays of FieldDefs (object items).
677
969
  */
678
970
  declare function isArrayField(field: FieldDef<any>): field is ArrayField;
679
-
680
- interface ConditionalExpression {
681
- /** Expression type */
682
- type: 'fieldValue' | 'formValue' | 'custom' | 'javascript' | 'and' | 'or';
683
- /** Field path for fieldValue type */
684
- fieldPath?: string;
685
- /** Comparison operator */
686
- operator?: 'equals' | 'notEquals' | 'greater' | 'less' | 'greaterOrEqual' | 'lessOrEqual' | 'contains' | 'startsWith' | 'endsWith' | 'matches';
687
- /** Value to compare against */
688
- value?: unknown;
689
- /** JavaScript expression for custom logic */
690
- expression?: string;
691
- /** Array of sub-conditions for 'and' and 'or' types */
692
- conditions?: ConditionalExpression[];
971
+ /**
972
+ * Configuration for auto-generated add/remove buttons in simplified array fields.
973
+ */
974
+ interface ArrayButtonConfig {
975
+ /** Custom label for the button */
976
+ readonly label?: string;
977
+ /** Additional properties passed to the button component */
978
+ readonly props?: Record<string, unknown>;
693
979
  }
694
-
695
980
  /**
696
- * Logic configuration for page fields.
697
- * Pages only support the 'hidden' logic type since they are layout containers,
698
- * not form controls (they can't be readonly, disabled, or required).
981
+ * Simplified array field interface for common use cases.
982
+ *
983
+ * Instead of manually specifying each item in `fields`, provide a `template` that defines
984
+ * the structure of a single item, and a `value` array with initial data.
985
+ * The library auto-generates add/remove buttons.
986
+ *
987
+ * Discriminant: `template` presence → simplified API; `Array.isArray(template)` → object vs primitive.
988
+ *
989
+ * @example
990
+ * // Primitive array: ['angular', 'typescript']
991
+ * {
992
+ * key: 'tags',
993
+ * type: 'array',
994
+ * template: { key: 'value', type: 'input', label: 'Tag', required: true },
995
+ * value: ['angular', 'typescript']
996
+ * }
997
+ *
998
+ * @example
999
+ * // Object array: [{ name: 'Jane', phone: '555' }]
1000
+ * {
1001
+ * key: 'contacts',
1002
+ * type: 'array',
1003
+ * template: [
1004
+ * { key: 'name', type: 'input', label: 'Contact Name', required: true },
1005
+ * { key: 'phone', type: 'input', label: 'Phone Number' },
1006
+ * ],
1007
+ * value: [{ name: 'Jane', phone: '555' }]
1008
+ * }
1009
+ *
1010
+ * @example
1011
+ * // Button customization / opt-out
1012
+ * {
1013
+ * key: 'tags',
1014
+ * type: 'array',
1015
+ * template: { key: 'value', type: 'input', label: 'Tag' },
1016
+ * addButton: { label: 'Add Tag', props: { color: 'primary' } },
1017
+ * removeButton: false
1018
+ * }
699
1019
  */
700
- interface PageLogicConfig {
701
- /** Only 'hidden' is supported for pages */
702
- type: 'hidden';
1020
+ interface SimplifiedArrayField extends FieldDef<never> {
1021
+ type: 'array';
703
1022
  /**
704
- * Condition that determines when this page is hidden.
705
- * Can be a boolean or a conditional expression evaluated against form values.
1023
+ * Template defining the structure of a single array item.
1024
+ * - Single field (ArrayAllowedChildren) → primitive array (each item is a single value)
1025
+ * - Array of fields (readonly ArrayAllowedChildren[]) → object array (each item is an object)
706
1026
  */
707
- condition: ConditionalExpression | boolean;
1027
+ readonly template: ArrayAllowedChildren | readonly ArrayAllowedChildren[];
1028
+ /** Initial values for the array. Each element creates one array item. */
1029
+ readonly value?: readonly unknown[];
1030
+ /**
1031
+ * Configuration for the auto-generated "Add" button, or `false` to disable it.
1032
+ * Defaults to a button with label "Add".
1033
+ */
1034
+ readonly addButton?: ArrayButtonConfig | false;
1035
+ /**
1036
+ * Configuration for the auto-generated "Remove" button on each item, or `false` to disable it.
1037
+ * Defaults to a button with label "Remove".
1038
+ */
1039
+ readonly removeButton?: ArrayButtonConfig | false;
1040
+ /** Simplified arrays do not support the label property */
1041
+ readonly label?: never;
1042
+ /** Simplified arrays do not support meta */
1043
+ readonly meta?: never;
1044
+ /**
1045
+ * Logic configurations for conditional array visibility.
1046
+ * Only 'hidden' type logic is supported for arrays.
1047
+ */
1048
+ readonly logic?: ContainerLogicConfig[];
1049
+ /**
1050
+ * Minimum number of items required in the array.
1051
+ * Validation fails if the array has fewer items than this value.
1052
+ */
1053
+ readonly minLength?: number;
1054
+ /**
1055
+ * Maximum number of items allowed in the array.
1056
+ * Validation fails if the array has more items than this value.
1057
+ */
1058
+ readonly maxLength?: number;
1059
+ /** Mutually exclusive with `template` — use `fields` for the full API instead */
1060
+ readonly fields?: never;
708
1061
  }
1062
+ /**
1063
+ * Type guard for SimplifiedArrayField.
1064
+ * Checks for `type: 'array'` with a `template` property (discriminant from full ArrayField).
1065
+ */
1066
+ declare function isSimplifiedArrayField(field: FieldDef<any>): field is SimplifiedArrayField;
1067
+
709
1068
  /**
710
1069
  * Page field interface for creating top-level page layouts
711
1070
  * This is a special field type that contains other field definitions
@@ -749,7 +1108,7 @@ interface PageField<TFields extends readonly PageAllowedChildren[] = PageAllowed
749
1108
  * }
750
1109
  * ```
751
1110
  */
752
- readonly logic?: PageLogicConfig[];
1111
+ readonly logic?: ContainerLogicConfig[];
753
1112
  }
754
1113
  /**
755
1114
  * Type guard for PageField with proper type narrowing
@@ -916,19 +1275,52 @@ interface DebouncedStateLogicConfig extends BaseStateLogicConfig {
916
1275
  */
917
1276
  type StateLogicConfig = ImmediateStateLogicConfig | DebouncedStateLogicConfig;
918
1277
  /**
919
- * Base configuration for value derivation logic.
920
- *
921
- * All derivations target the field they are defined on (self-targeting).
922
- * This simplifies configuration by removing the need for `targetField` -
923
- * derivation logic is placed on the field that should receive the computed value.
1278
+ * Shared fields that appear on all derivation logic config variants.
924
1279
  *
925
1280
  * @internal
926
1281
  */
927
- interface BaseDerivationLogicConfig {
1282
+ interface SharedDerivationFields {
928
1283
  /**
929
1284
  * Logic type identifier for value derivation.
930
1285
  */
931
1286
  type: 'derivation';
1287
+ /**
1288
+ * Target property name for property derivation.
1289
+ *
1290
+ * When set, this derivation targets a field property (like `minDate`, `options`,
1291
+ * `label`, `placeholder`) instead of the field's value. The derivation is routed
1292
+ * to the property derivation pipeline.
1293
+ *
1294
+ * When absent, the derivation targets the field's value (default behavior).
1295
+ *
1296
+ * **Depth limit (max 2 levels):** Only simple and single-dot-nested paths are
1297
+ * supported. Paths with 2+ dots (e.g., `'a.b.c'`) will throw at runtime.
1298
+ *
1299
+ * @example
1300
+ * ```typescript
1301
+ * // Property derivation (new unified API)
1302
+ * {
1303
+ * key: 'endDate',
1304
+ * type: 'datepicker',
1305
+ * logic: [{
1306
+ * type: 'derivation',
1307
+ * targetProperty: 'minDate',
1308
+ * expression: 'formValue.startDate'
1309
+ * }]
1310
+ * }
1311
+ *
1312
+ * // Value derivation (no targetProperty — existing behavior)
1313
+ * {
1314
+ * key: 'total',
1315
+ * type: 'input',
1316
+ * logic: [{
1317
+ * type: 'derivation',
1318
+ * expression: 'formValue.quantity * formValue.unitPrice'
1319
+ * }]
1320
+ * }
1321
+ * ```
1322
+ */
1323
+ targetProperty?: string;
932
1324
  /**
933
1325
  * Optional name for this derivation for debugging purposes.
934
1326
  *
@@ -976,27 +1368,182 @@ interface BaseDerivationLogicConfig {
976
1368
  */
977
1369
  condition?: ConditionalExpression | boolean;
978
1370
  /**
979
- * Static value to set on this field.
1371
+ * When true, the derivation stops running after the user manually
1372
+ * edits the target field.
980
1373
  *
981
- * Use when the derived value is a constant.
982
- * Mutually exclusive with `expression` and `functionName`.
1374
+ * This is useful for "smart defaults" — values that should be
1375
+ * auto-filled initially but respected once the user explicitly changes them.
1376
+ *
1377
+ * Uses the field's `dirty()` signal to detect user modification.
1378
+ * Derivations write directly to `value.set()` which does not trigger
1379
+ * `markAsDirty()`, so `dirty === true` reliably indicates a user edit.
983
1380
  *
984
1381
  * @example
985
1382
  * ```typescript
986
- * value: '+1' // String
987
- * value: 100 // Number
988
- * value: true // Boolean
989
- * value: { code: 'US' } // Object
1383
+ * // Auto-fill display name from first + last name, but stop if user edits it
1384
+ * {
1385
+ * key: 'displayName',
1386
+ * logic: [{
1387
+ * type: 'derivation',
1388
+ * expression: 'formValue.firstName + " " + formValue.lastName',
1389
+ * stopOnUserOverride: true
1390
+ * }]
1391
+ * }
990
1392
  * ```
991
1393
  */
992
- value?: unknown;
1394
+ stopOnUserOverride?: boolean;
1395
+ /**
1396
+ * When true (and `stopOnUserOverride` is also true), clears the
1397
+ * user-override flag when any dependency of this derivation changes,
1398
+ * allowing the derivation to run again.
1399
+ *
1400
+ * This is useful when a user override should only persist until the
1401
+ * underlying data changes — e.g., when switching countries, the
1402
+ * phone prefix should re-derive even if the user previously edited it.
1403
+ *
1404
+ * @example
1405
+ * ```typescript
1406
+ * {
1407
+ * key: 'phonePrefix',
1408
+ * logic: [{
1409
+ * type: 'derivation',
1410
+ * value: '+1',
1411
+ * condition: { type: 'fieldValue', fieldPath: 'country', operator: 'equals', value: 'USA' },
1412
+ * stopOnUserOverride: true,
1413
+ * reEngageOnDependencyChange: true,
1414
+ * dependsOn: ['country']
1415
+ * }]
1416
+ * }
1417
+ * ```
1418
+ */
1419
+ reEngageOnDependencyChange?: boolean;
1420
+ }
1421
+ /**
1422
+ * Trigger variants for derivation timing.
1423
+ * @internal
1424
+ */
1425
+ type ImmediateDerivationTrigger = {
1426
+ trigger?: 'onChange';
1427
+ debounceMs?: never;
1428
+ };
1429
+ type DebouncedDerivationTrigger = {
1430
+ trigger: 'debounced';
1431
+ debounceMs?: number;
1432
+ };
1433
+ /**
1434
+ * Base for HTTP derivations. `source: 'http'` is required.
1435
+ * TypeScript enforces `dependsOn` and `responseExpression` at the type level.
1436
+ *
1437
+ * @internal
1438
+ */
1439
+ interface HttpDerivationBase extends SharedDerivationFields {
1440
+ /** Identifies this derivation as HTTP-driven. */
1441
+ source: 'http';
1442
+ /**
1443
+ * HTTP request configuration for server-driven derivations.
1444
+ *
1445
+ * The request is sent when dependencies change, with automatic
1446
+ * debouncing and cancellation of in-flight requests.
1447
+ *
1448
+ * Configure debounce via the `trigger: 'debounced'` + `debounceMs` mechanism.
1449
+ *
1450
+ * @example
1451
+ * ```typescript
1452
+ * {
1453
+ * key: 'exchangeRate',
1454
+ * logic: [{
1455
+ * type: 'derivation',
1456
+ * source: 'http',
1457
+ * http: {
1458
+ * url: '/api/exchange-rate',
1459
+ * method: 'GET',
1460
+ * queryParams: {
1461
+ * from: 'formValue.sourceCurrency',
1462
+ * to: 'formValue.targetCurrency',
1463
+ * },
1464
+ * },
1465
+ * responseExpression: 'response.rate',
1466
+ * dependsOn: ['sourceCurrency', 'targetCurrency'],
1467
+ * }]
1468
+ * }
1469
+ * ```
1470
+ */
1471
+ http: HttpRequestConfig;
1472
+ /**
1473
+ * Explicit field dependencies. Required for HTTP derivations to prevent
1474
+ * wildcard triggering on every keystroke.
1475
+ */
1476
+ dependsOn: string[];
1477
+ /**
1478
+ * Expression to extract the derived value from the HTTP response.
1479
+ *
1480
+ * Evaluated via `ExpressionParser` with `{ response }` as the evaluation scope.
1481
+ *
1482
+ * @example
1483
+ * ```typescript
1484
+ * responseExpression: 'response.rate'
1485
+ * responseExpression: 'response.data.suggestedPrice'
1486
+ * ```
1487
+ */
1488
+ responseExpression: string;
1489
+ value?: never;
1490
+ expression?: never;
1491
+ functionName?: never;
1492
+ asyncFunctionName?: never;
1493
+ }
1494
+ /**
1495
+ * Base for async function derivations. `source: 'asyncFunction'` is required.
1496
+ * TypeScript enforces `dependsOn` at the type level.
1497
+ *
1498
+ * @internal
1499
+ */
1500
+ interface AsyncFunctionDerivationBase extends SharedDerivationFields {
1501
+ /** Identifies this derivation as async-function-driven. */
1502
+ source: 'asyncFunction';
1503
+ /**
1504
+ * Name of a registered async derivation function.
1505
+ *
1506
+ * The function receives the evaluation context and returns a Promise or Observable
1507
+ * of the derived value. Register functions in `customFnConfig.asyncDerivations`.
1508
+ *
1509
+ * @example
1510
+ * ```typescript
1511
+ * {
1512
+ * key: 'suggestedPrice',
1513
+ * logic: [{
1514
+ * type: 'derivation',
1515
+ * source: 'asyncFunction',
1516
+ * asyncFunctionName: 'fetchSuggestedPrice',
1517
+ * dependsOn: ['productId', 'quantity'],
1518
+ * }]
1519
+ * }
1520
+ * ```
1521
+ */
1522
+ asyncFunctionName: string;
1523
+ /**
1524
+ * Explicit field dependencies. Required for async derivations to prevent
1525
+ * wildcard triggering on every form change.
1526
+ */
1527
+ dependsOn: string[];
1528
+ value?: never;
1529
+ expression?: never;
1530
+ functionName?: never;
1531
+ http?: never;
1532
+ responseExpression?: never;
1533
+ }
1534
+ /**
1535
+ * Base for expression derivations.
1536
+ *
1537
+ * @internal
1538
+ */
1539
+ interface ExpressionDerivationBase extends SharedDerivationFields {
1540
+ source?: never;
993
1541
  /**
994
1542
  * JavaScript expression to evaluate for the derived value.
995
1543
  *
996
1544
  * Has access to `formValue` object containing all form values.
997
1545
  * For array fields, `formValue` is scoped to the current array item.
998
1546
  * Uses the same secure AST-based parser as other expressions.
999
- * Mutually exclusive with `value` and `functionName`.
1000
1547
  *
1001
1548
  * @example
1002
1549
  * ```typescript
@@ -1005,13 +1552,79 @@ interface BaseDerivationLogicConfig {
1005
1552
  * expression: 'formValue.price * (1 - formValue.discount / 100)'
1006
1553
  * ```
1007
1554
  */
1008
- expression?: string;
1555
+ expression: string;
1556
+ /**
1557
+ * Explicit field dependencies for expressions.
1558
+ *
1559
+ * For `expression`, dependencies are automatically extracted from the expression.
1560
+ * Provide `dependsOn` to override automatic detection for complex expressions.
1561
+ *
1562
+ * @example
1563
+ * ```typescript
1564
+ * // Override automatic detection for complex expressions
1565
+ * {
1566
+ * key: 'total',
1567
+ * logic: [{
1568
+ * type: 'derivation',
1569
+ * expression: 'calculateTotal(formValue)',
1570
+ * dependsOn: ['quantity', 'unitPrice', 'discount']
1571
+ * }]
1572
+ * }
1573
+ * ```
1574
+ */
1575
+ dependsOn?: string[];
1576
+ value?: never;
1577
+ functionName?: never;
1578
+ http?: never;
1579
+ asyncFunctionName?: never;
1580
+ responseExpression?: never;
1581
+ }
1582
+ /**
1583
+ * Base for value derivations.
1584
+ *
1585
+ * @internal
1586
+ */
1587
+ interface ValueDerivationBase extends SharedDerivationFields {
1588
+ source?: never;
1589
+ /**
1590
+ * Static value to set on this field.
1591
+ *
1592
+ * Use when the derived value is a constant.
1593
+ *
1594
+ * @example
1595
+ * ```typescript
1596
+ * value: '+1' // String
1597
+ * value: 100 // Number
1598
+ * value: true // Boolean
1599
+ * value: { code: 'US' } // Object
1600
+ * ```
1601
+ */
1602
+ value: unknown;
1603
+ /**
1604
+ * Explicit field dependencies for value derivations.
1605
+ *
1606
+ * For `value` (static), no dependencies are needed.
1607
+ * Provide `dependsOn` to conditionally re-evaluate when specific fields change.
1608
+ */
1609
+ dependsOn?: string[];
1610
+ expression?: never;
1611
+ functionName?: never;
1612
+ http?: never;
1613
+ asyncFunctionName?: never;
1614
+ responseExpression?: never;
1615
+ }
1616
+ /**
1617
+ * Base for custom sync function derivations.
1618
+ *
1619
+ * @internal
1620
+ */
1621
+ interface FunctionDerivationBase extends SharedDerivationFields {
1622
+ source?: never;
1009
1623
  /**
1010
1624
  * Name of a registered custom derivation function.
1011
1625
  *
1012
1626
  * The function receives the evaluation context and returns the derived value.
1013
1627
  * Register functions in `customFnConfig.derivations`.
1014
- * Mutually exclusive with `value` and `expression`.
1015
1628
  *
1016
1629
  * @example
1017
1630
  * ```typescript
@@ -1020,17 +1633,13 @@ interface BaseDerivationLogicConfig {
1020
1633
  * functionName: 'formatPhoneNumber'
1021
1634
  * ```
1022
1635
  */
1023
- functionName?: string;
1636
+ functionName: string;
1024
1637
  /**
1025
- * Explicit field dependencies for derivations.
1026
- *
1027
- * When using `functionName`, you can optionally specify which fields
1028
- * the function depends on. This enables more efficient re-evaluation
1029
- * by only triggering when specific dependencies change.
1638
+ * Explicit field dependencies for function derivations.
1030
1639
  *
1031
- * If not provided with `functionName`, defaults to all fields ('*').
1032
- * For `expression`, dependencies are automatically extracted from the expression.
1033
- * For `value`, no dependencies are needed (static value).
1640
+ * If not provided, defaults to all fields ('*').
1641
+ * Specify `dependsOn` for better performance when the function only
1642
+ * depends on a subset of fields.
1034
1643
  *
1035
1644
  * @example
1036
1645
  * ```typescript
@@ -1043,54 +1652,65 @@ interface BaseDerivationLogicConfig {
1043
1652
  * dependsOn: ['country']
1044
1653
  * }]
1045
1654
  * }
1046
- *
1047
- * // Override automatic detection for complex expressions
1048
- * {
1049
- * key: 'total',
1050
- * logic: [{
1051
- * type: 'derivation',
1052
- * expression: 'calculateTotal(formValue)',
1053
- * dependsOn: ['quantity', 'unitPrice', 'discount']
1054
- * }]
1055
- * }
1056
1655
  * ```
1057
1656
  */
1058
1657
  dependsOn?: string[];
1658
+ value?: never;
1659
+ expression?: never;
1660
+ http?: never;
1661
+ asyncFunctionName?: never;
1662
+ responseExpression?: never;
1059
1663
  }
1060
1664
  /**
1061
- * Derivation logic that evaluates immediately on change (default).
1062
- *
1665
+ * HTTP derivation that evaluates immediately on change (default).
1666
+ * @internal
1667
+ */
1668
+ type OnChangeHttpDerivationLogicConfig = HttpDerivationBase & ImmediateDerivationTrigger;
1669
+ /**
1670
+ * HTTP derivation that evaluates after a debounce period.
1671
+ * @internal
1672
+ */
1673
+ type DebouncedHttpDerivationLogicConfig = HttpDerivationBase & DebouncedDerivationTrigger;
1674
+ /**
1675
+ * Async function derivation that evaluates immediately on change (default).
1676
+ * @internal
1677
+ */
1678
+ type OnChangeAsyncFunctionDerivationLogicConfig = AsyncFunctionDerivationBase & ImmediateDerivationTrigger;
1679
+ /**
1680
+ * Async function derivation that evaluates after a debounce period.
1681
+ * @internal
1682
+ */
1683
+ type DebouncedAsyncFunctionDerivationLogicConfig = AsyncFunctionDerivationBase & DebouncedDerivationTrigger;
1684
+ /**
1685
+ * Expression derivation that evaluates immediately on change (default).
1686
+ * @internal
1687
+ */
1688
+ type OnChangeExpressionDerivationLogicConfig = ExpressionDerivationBase & ImmediateDerivationTrigger;
1689
+ /**
1690
+ * Expression derivation that evaluates after a debounce period.
1691
+ * @internal
1692
+ */
1693
+ type DebouncedExpressionDerivationLogicConfig = ExpressionDerivationBase & DebouncedDerivationTrigger;
1694
+ /**
1695
+ * Value derivation that evaluates immediately on change (default).
1063
1696
  * @internal
1064
1697
  */
1065
- interface OnChangeDerivationLogicConfig extends BaseDerivationLogicConfig {
1066
- /**
1067
- * Trigger for immediate evaluation.
1068
- * @default 'onChange'
1069
- */
1070
- trigger?: 'onChange';
1071
- /** Not allowed for onChange trigger */
1072
- debounceMs?: never;
1073
- }
1698
+ type OnChangeValueDerivationLogicConfig = ValueDerivationBase & ImmediateDerivationTrigger;
1074
1699
  /**
1075
- * Derivation logic that evaluates after a debounce period.
1076
- *
1077
- * Use this for self-transforms (lowercase, trim, format) to avoid
1078
- * interrupting the user while they're actively typing.
1079
- *
1700
+ * Value derivation that evaluates after a debounce period.
1080
1701
  * @internal
1081
1702
  */
1082
- interface DebouncedDerivationLogicConfig extends BaseDerivationLogicConfig {
1083
- /**
1084
- * Trigger for debounced evaluation.
1085
- * Evaluates after the value has stabilized for the debounce duration.
1086
- */
1087
- trigger: 'debounced';
1088
- /**
1089
- * Debounce duration in milliseconds.
1090
- * @default 500
1091
- */
1092
- debounceMs?: number;
1093
- }
1703
+ type DebouncedValueDerivationLogicConfig = ValueDerivationBase & DebouncedDerivationTrigger;
1704
+ /**
1705
+ * Function derivation that evaluates immediately on change (default).
1706
+ * @internal
1707
+ */
1708
+ type OnChangeFunctionDerivationLogicConfig = FunctionDerivationBase & ImmediateDerivationTrigger;
1709
+ /**
1710
+ * Function derivation that evaluates after a debounce period.
1711
+ * @internal
1712
+ */
1713
+ type DebouncedFunctionDerivationLogicConfig = FunctionDerivationBase & DebouncedDerivationTrigger;
1094
1714
  /**
1095
1715
  * Configuration for value derivation logic.
1096
1716
  *
@@ -1151,7 +1771,7 @@ interface DebouncedDerivationLogicConfig extends BaseDerivationLogicConfig {
1151
1771
  *
1152
1772
  * @public
1153
1773
  */
1154
- type DerivationLogicConfig = OnChangeDerivationLogicConfig | DebouncedDerivationLogicConfig;
1774
+ type DerivationLogicConfig = OnChangeHttpDerivationLogicConfig | DebouncedHttpDerivationLogicConfig | OnChangeAsyncFunctionDerivationLogicConfig | DebouncedAsyncFunctionDerivationLogicConfig | OnChangeExpressionDerivationLogicConfig | DebouncedExpressionDerivationLogicConfig | OnChangeValueDerivationLogicConfig | DebouncedValueDerivationLogicConfig | OnChangeFunctionDerivationLogicConfig | DebouncedFunctionDerivationLogicConfig;
1155
1775
  /**
1156
1776
  * Base configuration for property derivation logic.
1157
1777
  *
@@ -1280,55 +1900,20 @@ interface DebouncedPropertyDerivationLogicConfig extends BasePropertyDerivationL
1280
1900
  /**
1281
1901
  * Configuration for property derivation logic.
1282
1902
  *
1283
- * Enables reactive derivation of field properties (like `minDate`, `options`,
1284
- * `label`, `placeholder`) based on form values and external data.
1903
+ * @deprecated Use `DerivationLogicConfig` with `targetProperty` instead.
1904
+ * The unified `type: 'derivation'` with `targetProperty` provides the same
1905
+ * functionality with a simpler API surface.
1285
1906
  *
1286
- * Property derivations are self-targeting: the logic is placed on the field
1287
- * whose property should be derived.
1288
- *
1289
- * @example
1290
1907
  * ```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
- * }
1908
+ * // Before (deprecated)
1909
+ * { type: 'propertyDerivation', targetProperty: 'minDate', expression: '...' }
1313
1910
  *
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
- * }
1911
+ * // After (preferred)
1912
+ * { type: 'derivation', targetProperty: 'minDate', expression: '...' }
1330
1913
  * ```
1331
1914
  *
1915
+ * Will be removed in a future minor version.
1916
+ *
1332
1917
  * @public
1333
1918
  */
1334
1919
  type PropertyDerivationLogicConfig = OnChangePropertyDerivationLogicConfig | DebouncedPropertyDerivationLogicConfig;
@@ -1364,6 +1949,9 @@ declare function isFormStateCondition(condition: StateLogicConfig['condition'] |
1364
1949
  /**
1365
1950
  * Type guard to check if a logic config is a PropertyDerivationLogicConfig.
1366
1951
  *
1952
+ * @deprecated Use `isDerivationLogicConfig(config) && hasTargetProperty(config)` instead.
1953
+ * Will be removed in a future minor version.
1954
+ *
1367
1955
  * @param config - The logic config to check
1368
1956
  * @returns true if the config is for property derivation
1369
1957
  *
@@ -1516,78 +2104,59 @@ declare function resolveSubmitButtonDisabled(ctx: ButtonLogicContext): Signal<bo
1516
2104
  */
1517
2105
  declare function resolveNextButtonDisabled(ctx: ButtonLogicContext): Signal<boolean>;
1518
2106
  /**
1519
- * Resolves the hidden state for non-form-bound elements (buttons, text fields, etc.).
2107
+ * Evaluates the hidden state for non-form-bound elements synchronously.
1520
2108
  *
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.
2109
+ * This is a pure function (no signal allocation) that can be called directly inside
2110
+ * an existing `computed()` block without creating ephemeral signal allocations.
1524
2111
  *
1525
2112
  * The hidden state is determined by (in order of precedence):
1526
2113
  * 1. Explicit `hidden: true` on the field definition
1527
2114
  * 2. Field-level `logic` array with `type: 'hidden'` conditions
1528
2115
  *
1529
2116
  * @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
2117
+ * @returns true when the element should be hidden
1531
2118
  *
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
- * });
2119
+ * @public
2120
+ */
2121
+ declare function evaluateNonFieldHidden(ctx: NonFieldLogicContext): boolean;
2122
+ /**
2123
+ * Resolves the hidden state for non-form-bound elements as a reactive signal.
1541
2124
  *
1542
- * return computed(() => ({
1543
- * ...baseInputs,
1544
- * hidden: hiddenSignal(),
1545
- * }));
1546
- * ```
2125
+ * Wraps {@link evaluateNonFieldHidden} in a `computed()` for use cases where a standalone
2126
+ * signal is needed. When calling from inside an existing `computed()`, prefer
2127
+ * {@link evaluateNonFieldHidden} directly to avoid ephemeral signal allocation.
1547
2128
  *
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
- * ```
2129
+ * @param ctx - The context containing form, logic array, and explicit hidden state
2130
+ * @returns A computed signal that returns true when the element should be hidden
1558
2131
  *
1559
2132
  * @public
1560
2133
  */
1561
2134
  declare function resolveNonFieldHidden(ctx: NonFieldLogicContext): Signal<boolean>;
1562
2135
  /**
1563
- * Resolves the disabled state for non-form-bound elements (buttons, text fields, etc.).
2136
+ * Evaluates the disabled state for non-form-bound elements synchronously.
1564
2137
  *
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).
2138
+ * This is a pure function (no signal allocation) that can be called directly inside
2139
+ * an existing `computed()` block without creating ephemeral signal allocations.
1568
2140
  *
1569
2141
  * The disabled state is determined by (in order of precedence):
1570
2142
  * 1. Explicit `disabled: true` on the field definition
1571
2143
  * 2. Field-level `logic` array with `type: 'disabled'` conditions
1572
2144
  *
1573
2145
  * @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
2146
+ * @returns true when the element should be disabled
1575
2147
  *
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
- * });
2148
+ * @public
2149
+ */
2150
+ declare function evaluateNonFieldDisabled(ctx: NonFieldLogicContext): boolean;
2151
+ /**
2152
+ * Resolves the disabled state for non-form-bound elements as a reactive signal.
1585
2153
  *
1586
- * return computed(() => ({
1587
- * ...baseInputs,
1588
- * disabled: disabledSignal(),
1589
- * }));
1590
- * ```
2154
+ * Wraps {@link evaluateNonFieldDisabled} in a `computed()` for use cases where a standalone
2155
+ * signal is needed. When calling from inside an existing `computed()`, prefer
2156
+ * {@link evaluateNonFieldDisabled} directly to avoid ephemeral signal allocation.
2157
+ *
2158
+ * @param ctx - The context containing form, logic array, and explicit disabled state
2159
+ * @returns A computed signal that returns true when the element should be disabled
1591
2160
  *
1592
2161
  * @public
1593
2162
  */
@@ -1633,7 +2202,7 @@ interface FieldRegistryContainers {
1633
2202
  page: PageField;
1634
2203
  row: RowField;
1635
2204
  group: GroupField;
1636
- array: ArrayField;
2205
+ array: ArrayField | SimplifiedArrayField;
1637
2206
  }
1638
2207
  /**
1639
2208
  * Leaf fields registry - augment this interface to add custom leaf fields
@@ -1748,18 +2317,28 @@ type UnionToIntersection<U> = (U extends U ? (k: U) => void : never) extends (k:
1748
2317
  */
1749
2318
  type Depth = [never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
1750
2319
  /**
1751
- * Widens literal types to their primitive equivalents.
2320
+ * Widens literal types to their primitive equivalents, recursively for objects and arrays.
1752
2321
  * This prevents `as const` from over-narrowing types like `''` to literal `''` instead of `string`.
1753
2322
  *
2323
+ * Depth-limited to prevent slow type checking on deeply nested `as const` objects.
2324
+ * Falls back to `T` (unwidened) when depth is exhausted.
2325
+ *
2326
+ * Note: For object types, `readonly` modifiers are intentionally stripped (`-readonly`)
2327
+ * so that inferred form value types have mutable properties. This is by design since
2328
+ * `as const` adds `readonly` to all properties, but form values should be mutable.
2329
+ *
1754
2330
  * @example
1755
2331
  * ```typescript
1756
2332
  * type A = Widen<''>; // string
1757
2333
  * type B = Widen<false>; // boolean
1758
2334
  * type C = Widen<42>; // number
1759
2335
  * type D = Widen<string[]>; // string[]
2336
+ * type E = Widen<{ name: 'Jane' }>; // { name: string }
1760
2337
  * ```
1761
2338
  */
1762
- type Widen<T> = T extends string ? string : T extends number ? number : T extends boolean ? boolean : T;
2339
+ type Widen<T, D extends number = 5> = [D] extends [never] ? T : T extends string ? string : T extends number ? number : T extends boolean ? boolean : T extends readonly (infer U)[] ? Widen<U, Depth[D]>[] : T extends Record<string, unknown> ? {
2340
+ -readonly [K in keyof T]: Widen<T[K], Depth[D]>;
2341
+ } : T;
1763
2342
  /**
1764
2343
  * Infer value type based on field type and props.
1765
2344
  * - Slider fields: always number
@@ -1783,6 +2362,38 @@ type MaybeOptional<T, V> = T extends {
1783
2362
  } ? V : T extends {
1784
2363
  required: true;
1785
2364
  } ? V : V | undefined;
2365
+ /**
2366
+ * Infers the value type from a single template field (for primitive arrays).
2367
+ * Mirrors InferValueType logic: slider → number, input[number] → number, else string.
2368
+ */
2369
+ type InferSingleTemplateValue<T, D extends number> = T extends {
2370
+ type: 'slider';
2371
+ } ? number : T extends {
2372
+ type: 'input';
2373
+ props: {
2374
+ type: 'number';
2375
+ };
2376
+ } ? number : T extends {
2377
+ type: 'checkbox' | 'toggle';
2378
+ } ? boolean : T extends {
2379
+ value: infer V;
2380
+ } ? Widen<V, Depth[D]> : string;
2381
+ /**
2382
+ * Infers the value type for an array field.
2383
+ * Handles four cases:
2384
+ * 1. Simplified array with template + value → infer item type from value
2385
+ * 2. Simplified array with array template (no value) → infer object type from template fields
2386
+ * 3. Simplified array with single-field template (no value) → infer primitive type from template field
2387
+ * 4. Full-API array with fields → infer from children
2388
+ */
2389
+ type InferArrayValue<T, D extends number> = T extends {
2390
+ template: unknown;
2391
+ value: readonly (infer Item)[];
2392
+ } ? Widen<Item>[] : T extends {
2393
+ template: infer TArr;
2394
+ } ? TArr extends readonly RegisteredFieldTypes[] ? InferFormValueWithDepth<[...TArr], Depth[D]>[] : InferSingleTemplateValue<TArr, D>[] : T extends {
2395
+ fields: infer F;
2396
+ } ? F extends RegisteredFieldTypes[] ? InferFormValueWithDepth<F, Depth[D]>[] : unknown[] : unknown[];
1786
2397
  /**
1787
2398
  * Process a single field and determine its contribution to the form value type
1788
2399
  */
@@ -1800,11 +2411,8 @@ type ProcessField<T, D extends number = 5> = [D] extends [never] ? Record<string
1800
2411
  } : never : T extends {
1801
2412
  type: 'array';
1802
2413
  key: infer K;
1803
- fields: infer F;
1804
- } ? K extends string ? F extends RegisteredFieldTypes[] ? {
1805
- [P in K]: InferFormValueWithDepth<F, Depth[D]>[];
1806
- } : {
1807
- [P in K]: unknown[];
2414
+ } ? K extends string ? {
2415
+ [P in K]: InferArrayValue<T, D>;
1808
2416
  } : never : T extends {
1809
2417
  type: 'text';
1810
2418
  } ? never : T extends {
@@ -1857,6 +2465,31 @@ type ExtractFields<T> = T extends {
1857
2465
  */
1858
2466
  type InferFormValue$1<T> = ExtractFields<T> extends readonly RegisteredFieldTypes[] ? InferFormValueWithDepth<ExtractFields<T> extends RegisteredFieldTypes[] ? ExtractFields<T> : [...ExtractFields<T>], 5> : never;
1859
2467
 
2468
+ /**
2469
+ * Mapping configuration for interpreting HTTP responses as validation results.
2470
+ *
2471
+ * Expressions in `validWhen` and `errorParams` are evaluated with scope `{ response }` only —
2472
+ * they do NOT have access to `formValue`, `fieldValue`, or the full `EvaluationContext`.
2473
+ * This is intentional for v1: response mapping is purely about interpreting the HTTP response.
2474
+ */
2475
+ interface HttpValidationResponseMapping {
2476
+ /**
2477
+ * Expression evaluated with scope `{ response }`. Must evaluate to `true` (strict boolean) to be valid.
2478
+ *
2479
+ * NOTE: This is the OPPOSITE of the function-based HttpCustomValidator.onSuccess
2480
+ * convention, which maps successful HTTP responses to validation errors.
2481
+ * Here, `=== true` means "validation passed". Non-boolean results are treated as invalid and trigger a warning.
2482
+ */
2483
+ validWhen: string;
2484
+ /** Error kind returned when validation fails — maps to field-level `validationMessages` */
2485
+ errorKind: string;
2486
+ /**
2487
+ * Parameters to include in the validation error for message interpolation.
2488
+ * Map of parameter names to expressions evaluated against `{ response }`.
2489
+ */
2490
+ errorParams?: Record<string, string>;
2491
+ }
2492
+
1860
2493
  /**
1861
2494
  * Base configuration shared by all validators
1862
2495
  */
@@ -1908,34 +2541,80 @@ interface CustomValidatorConfig extends BaseValidatorConfig {
1908
2541
  errorParams?: Record<string, string>;
1909
2542
  }
1910
2543
  /**
1911
- * Async custom validator configuration using Angular's validateAsync API
1912
- * Returns Observable<ValidationError | ValidationError[] | null>
2544
+ * Async custom validator configuration using Angular's validateAsync API.
2545
+ * Returns Observable<ValidationError | ValidationError[] | null>.
2546
+ *
2547
+ * Accepts both `'async'` (preferred) and `'customAsync'` (deprecated) type literals.
1913
2548
  */
1914
2549
  interface AsyncValidatorConfig extends BaseValidatorConfig {
1915
- /** Validator type identifier */
1916
- type: 'customAsync';
2550
+ /**
2551
+ * Validator type identifier.
2552
+ *
2553
+ * Use `'async'` for new code. `'customAsync'` is accepted for backward compatibility
2554
+ * but emits a deprecation warning in dev mode.
2555
+ */
2556
+ type: 'async' | 'customAsync';
1917
2557
  /** Name of registered async validator function */
1918
2558
  functionName: string;
1919
2559
  /** Optional parameters to pass to validator function */
1920
2560
  params?: Record<string, unknown>;
1921
2561
  }
1922
2562
  /**
1923
- * HTTP validator configuration using Angular's validateHttp API
1924
- * Provides optimized HTTP validation with automatic request cancellation
2563
+ * Function-based HTTP validator configuration — requires a registered function.
2564
+ *
2565
+ * Uses Angular's `validateHttp` API. The function is registered via
2566
+ * `customFnConfig.httpValidators`.
2567
+ *
2568
+ * Discriminated from `DeclarativeHttpValidatorConfig` by the presence of `functionName`.
2569
+ *
2570
+ * Accepts both `'http'` (preferred) and `'customHttp'` (deprecated) type literals.
2571
+ *
2572
+ * @deprecated Prefer `DeclarativeHttpValidatorConfig` (`type: 'http'` with `http` + `responseMapping`)
2573
+ * for fully JSON-serializable validation. Use this form only when function registration is required.
1925
2574
  */
1926
- interface HttpValidatorConfig extends BaseValidatorConfig {
1927
- /** Validator type identifier */
1928
- type: 'customHttp';
2575
+ interface FunctionHttpValidatorConfig extends BaseValidatorConfig {
2576
+ /**
2577
+ * Validator type identifier.
2578
+ *
2579
+ * Use `'http'` for new code. `'customHttp'` is accepted for backward compatibility
2580
+ * but emits a deprecation warning in dev mode.
2581
+ */
2582
+ type: 'http' | 'customHttp';
1929
2583
  /** Name of registered HTTP validator configuration */
1930
2584
  functionName: string;
1931
2585
  /** Optional parameters to pass to HTTP validator */
1932
2586
  params?: Record<string, unknown>;
1933
2587
  }
1934
2588
  /**
1935
- * Configuration for signal forms validator functions that can be serialized from API
1936
- * Discriminated union type for type-safe validator configuration
2589
+ * @deprecated Use `FunctionHttpValidatorConfig` instead. Will be removed in a future minor version.
2590
+ */
2591
+ type HttpValidatorConfig = FunctionHttpValidatorConfig;
2592
+ /**
2593
+ * Declarative HTTP validator configuration — fully JSON-serializable, no function registration required.
2594
+ *
2595
+ * Uses `HttpRequestConfig` to define the HTTP request and `HttpValidationResponseMapping`
2596
+ * to interpret the response as a validation result. Powered by Angular's `validateHttp` API.
2597
+ *
2598
+ * Discriminated from `FunctionHttpValidatorConfig` by the presence of `http` + `responseMapping`
2599
+ * (and absence of `functionName`).
2600
+ */
2601
+ interface DeclarativeHttpValidatorConfig extends BaseValidatorConfig {
2602
+ /** Validator type identifier */
2603
+ type: 'http';
2604
+ /** HTTP request configuration with expression-based query params and body */
2605
+ http: HttpRequestConfig;
2606
+ /** Mapping that interprets the HTTP response as a validation result */
2607
+ responseMapping: HttpValidationResponseMapping;
2608
+ }
2609
+ /**
2610
+ * Configuration for signal forms validator functions that can be serialized from API.
2611
+ * Discriminated union type for type-safe validator configuration.
2612
+ *
2613
+ * Note: `FunctionHttpValidatorConfig` and `DeclarativeHttpValidatorConfig` both use `type: 'http'`.
2614
+ * They are discriminated by property presence: `functionName` → function-based, `http` → declarative.
2615
+ * Use `isFunctionHttpValidator()` for type-safe narrowing when `type` alone is insufficient.
1937
2616
  */
1938
- type ValidatorConfig = BuiltInValidatorConfig | CustomValidatorConfig | AsyncValidatorConfig | HttpValidatorConfig;
2617
+ type ValidatorConfig = BuiltInValidatorConfig | CustomValidatorConfig | AsyncValidatorConfig | FunctionHttpValidatorConfig | DeclarativeHttpValidatorConfig;
1939
2618
 
1940
2619
  /**
1941
2620
  * Configuration for applying predefined schemas
@@ -2231,7 +2910,77 @@ interface HttpCustomValidator<TValue = unknown, TResult = unknown> {
2231
2910
  readonly onError?: (error: unknown, ctx: FieldContext<TValue>) => ValidationError$1 | ValidationError$1[] | null;
2232
2911
  }
2233
2912
 
2234
- interface EvaluationContext<TValue = unknown> {
2913
+ /**
2914
+ * Tracks deprecated API usages that have already been warned about to prevent log spam.
2915
+ * Scoped to a single form instance via DI.
2916
+ *
2917
+ * This follows the same pattern as DerivationWarningTracker — DI-scoped rather than
2918
+ * module-scoped to avoid SSR issues where state would be shared across requests.
2919
+ *
2920
+ * @public
2921
+ */
2922
+ interface DeprecationWarningTracker {
2923
+ /** Set of deprecation keys we've already warned about */
2924
+ warnedKeys: Set<string>;
2925
+ }
2926
+
2927
+ /**
2928
+ * Represents the state information of a single form field.
2929
+ *
2930
+ * Used in evaluation contexts to allow expressions and conditions
2931
+ * to reference field state (e.g., `fieldState.touched`, `formFieldState.email.dirty`).
2932
+ *
2933
+ * @public
2934
+ */
2935
+ interface FieldStateInfo {
2936
+ readonly touched: boolean;
2937
+ readonly dirty: boolean;
2938
+ /** Convenience property: equivalent to `!dirty` */
2939
+ readonly pristine: boolean;
2940
+ readonly valid: boolean;
2941
+ readonly invalid: boolean;
2942
+ readonly pending: boolean;
2943
+ /**
2944
+ * Whether the field is currently hidden.
2945
+ *
2946
+ * Note: This is a library-managed property, not a native Angular Signal Forms
2947
+ * property. Returns `false` if the field instance does not expose a `hidden` signal.
2948
+ */
2949
+ readonly hidden: boolean;
2950
+ /**
2951
+ * Whether the field is currently readonly.
2952
+ *
2953
+ * Note: This is a library-managed property, not a native Angular Signal Forms
2954
+ * property. Returns `false` if the field instance does not expose a `readonly` signal.
2955
+ */
2956
+ readonly readonly: boolean;
2957
+ /**
2958
+ * Whether the field is currently disabled.
2959
+ *
2960
+ * Note: This is a library-managed property, not a native Angular Signal Forms
2961
+ * property. Returns `false` if the field instance does not expose a `disabled` signal.
2962
+ */
2963
+ readonly disabled: boolean;
2964
+ }
2965
+ /**
2966
+ * Map of field keys to their state information.
2967
+ *
2968
+ * Used as `formFieldState` in evaluation contexts to access
2969
+ * state of any field in the form by key.
2970
+ *
2971
+ * @public
2972
+ */
2973
+ type FormFieldStateMap = Record<string, FieldStateInfo | undefined>;
2974
+ /**
2975
+ * Field state context for the current field being evaluated.
2976
+ *
2977
+ * Used as `fieldState` in evaluation contexts.
2978
+ *
2979
+ * @public
2980
+ */
2981
+ type FieldStateContext = FieldStateInfo;
2982
+
2983
+ interface EvaluationContext<TValue = unknown, TFormValue extends Record<string, unknown> = any> {
2235
2984
  /** Current field value */
2236
2985
  fieldValue: TValue;
2237
2986
  /**
@@ -2241,7 +2990,7 @@ interface EvaluationContext<TValue = unknown> {
2241
2990
  * For array item derivations, this is scoped to the current array item.
2242
2991
  * Use `rootFormValue` to access the complete form when inside an array context.
2243
2992
  */
2244
- formValue: Record<string, unknown>;
2993
+ formValue: TFormValue;
2245
2994
  /** Field path for relative references */
2246
2995
  fieldPath: string;
2247
2996
  /** Custom evaluation functions */
@@ -2302,6 +3051,43 @@ interface EvaluationContext<TValue = unknown> {
2302
3051
  * ```
2303
3052
  */
2304
3053
  externalData?: Record<string, unknown>;
3054
+ /**
3055
+ * DI-scoped tracker for deprecation warnings.
3056
+ * Used by the condition evaluator to deduplicate deprecation warnings.
3057
+ */
3058
+ deprecationTracker?: DeprecationWarningTracker;
3059
+ /**
3060
+ * State of the current field being evaluated.
3061
+ *
3062
+ * Provides access to field state properties like `touched`, `dirty`, `valid`, etc.
3063
+ * Uses a Proxy for lazy evaluation — properties are only read when accessed.
3064
+ *
3065
+ * @example
3066
+ * ```typescript
3067
+ * // In a JavaScript expression:
3068
+ * condition: {
3069
+ * type: 'javascript',
3070
+ * expression: "fieldState.touched && fieldState.dirty"
3071
+ * }
3072
+ * ```
3073
+ */
3074
+ fieldState?: FieldStateContext;
3075
+ /**
3076
+ * State of all fields in the form, keyed by field key.
3077
+ *
3078
+ * Provides access to any field's state properties by key.
3079
+ * Uses a Proxy for lazy evaluation — field state is only read when accessed.
3080
+ *
3081
+ * @example
3082
+ * ```typescript
3083
+ * // In a JavaScript expression:
3084
+ * condition: {
3085
+ * type: 'javascript',
3086
+ * expression: "formFieldState.email.dirty && formFieldState.email.valid"
3087
+ * }
3088
+ * ```
3089
+ */
3090
+ formFieldState?: FormFieldStateMap;
2305
3091
  /** Allow additional properties for flexible expression evaluation */
2306
3092
  [key: string]: unknown;
2307
3093
  }
@@ -2345,7 +3131,44 @@ interface EvaluationContext<TValue = unknown> {
2345
3131
  * };
2346
3132
  * ```
2347
3133
  */
2348
- type CustomFunction = (context: EvaluationContext) => unknown;
3134
+ type CustomFunction<TFormValue extends Record<string, unknown> = any> = (context: EvaluationContext<unknown, TFormValue>) => unknown;
3135
+
3136
+ /**
3137
+ * Async function for derivations — returns any value.
3138
+ *
3139
+ * The function receives the evaluation context and may return a Promise or Observable.
3140
+ * Register functions in `customFnConfig.asyncDerivations`.
3141
+ *
3142
+ * @example
3143
+ * ```typescript
3144
+ * const fetchPrice: AsyncDerivationFunction = async (context) => {
3145
+ * const response = await fetch(`/api/price?product=${context.formValue.productId}`);
3146
+ * const data = await response.json();
3147
+ * return data.price;
3148
+ * };
3149
+ * ```
3150
+ *
3151
+ * @public
3152
+ */
3153
+ type AsyncDerivationFunction<TFormValue extends Record<string, unknown> = any> = (context: EvaluationContext<unknown, TFormValue>) => Promise<unknown> | Observable<unknown>;
3154
+ /**
3155
+ * Async function for conditions — returns boolean.
3156
+ *
3157
+ * The function receives the evaluation context and may return a Promise or Observable.
3158
+ * Register functions in `customFnConfig.asyncConditions`.
3159
+ *
3160
+ * @example
3161
+ * ```typescript
3162
+ * const checkPermission: AsyncConditionFunction = async (context) => {
3163
+ * const response = await fetch(`/api/permissions?user=${context.formValue.userId}`);
3164
+ * const data = await response.json();
3165
+ * return data.canEdit;
3166
+ * };
3167
+ * ```
3168
+ *
3169
+ * @public
3170
+ */
3171
+ type AsyncConditionFunction<TFormValue extends Record<string, unknown> = any> = (context: EvaluationContext<unknown, TFormValue>) => Promise<boolean> | Observable<boolean>;
2349
3172
 
2350
3173
  /**
2351
3174
  * Re-export Angular's ValidationError for consistency
@@ -2610,7 +3433,7 @@ interface FormConfig<TFields extends NarrowFields | RegisteredFieldTypes[] = Reg
2610
3433
  /**
2611
3434
  * Signal forms adapter configuration.
2612
3435
  */
2613
- customFnConfig?: CustomFnConfig;
3436
+ customFnConfig?: CustomFnConfig<TValue extends Record<string, unknown> ? TValue : Record<string, unknown>>;
2614
3437
  /**
2615
3438
  * Form submission configuration.
2616
3439
  *
@@ -2765,7 +3588,7 @@ interface FormConfig<TFields extends NarrowFields | RegisteredFieldTypes[] = Reg
2765
3588
  *
2766
3589
  * @public
2767
3590
  */
2768
- interface CustomFnConfig {
3591
+ interface CustomFnConfig<TFormValue extends Record<string, unknown> = Record<string, unknown>> {
2769
3592
  /**
2770
3593
  * Custom evaluation functions for conditional expressions.
2771
3594
  *
@@ -2783,7 +3606,7 @@ interface CustomFnConfig {
2783
3606
  * }
2784
3607
  * ```
2785
3608
  */
2786
- customFunctions?: Record<string, CustomFunction>;
3609
+ customFunctions?: Record<string, CustomFunction<TFormValue>>;
2787
3610
  /**
2788
3611
  * Custom derivation functions for value derivation logic.
2789
3612
  *
@@ -2851,8 +3674,11 @@ interface CustomFnConfig {
2851
3674
  * }
2852
3675
  * ```
2853
3676
  */
2854
- derivations?: Record<string, CustomFunction>;
3677
+ derivations?: Record<string, CustomFunction<TFormValue>>;
2855
3678
  /**
3679
+ * @deprecated Register functions under `derivations` instead. Use `type: 'derivation'`
3680
+ * with `targetProperty` in field logic configs. Will be removed in a future minor version.
3681
+ *
2856
3682
  * Custom property derivation functions for reactive property updates.
2857
3683
  *
2858
3684
  * These functions compute derived values for field properties (like `minDate`,
@@ -2891,7 +3717,61 @@ interface CustomFnConfig {
2891
3717
  * }
2892
3718
  * ```
2893
3719
  */
2894
- propertyDerivations?: Record<string, CustomFunction>;
3720
+ propertyDerivations?: Record<string, CustomFunction<TFormValue>>;
3721
+ /**
3722
+ * Async derivation functions for asynchronous value derivation logic.
3723
+ *
3724
+ * These functions perform asynchronous operations (service calls, complex pipelines)
3725
+ * and return the derived value via a Promise or Observable. They are called when a
3726
+ * `DerivationLogicConfig` references them by `asyncFunctionName`.
3727
+ *
3728
+ * Async derivation functions:
3729
+ * - Receive an `EvaluationContext` with access to `formValue`
3730
+ * - Return a Promise or Observable of the value to set on the target field
3731
+ * - Handle their own I/O (no HttpClient provided — use injected services)
3732
+ * - Require explicit `dependsOn` to avoid triggering on every form change
3733
+ *
3734
+ * @example
3735
+ * ```typescript
3736
+ * asyncDerivations: {
3737
+ * fetchSuggestedPrice: async (context) => {
3738
+ * const response = await fetch(`/api/price?product=${context.formValue.productId}`);
3739
+ * const data = await response.json();
3740
+ * return data.suggestedPrice;
3741
+ * },
3742
+ * lookupAddress: (context) => {
3743
+ * return addressService.lookup(context.formValue.zipCode).pipe(
3744
+ * map(result => result.formattedAddress)
3745
+ * );
3746
+ * },
3747
+ * }
3748
+ * ```
3749
+ */
3750
+ asyncDerivations?: Record<string, AsyncDerivationFunction<TFormValue>>;
3751
+ /**
3752
+ * Async condition functions for asynchronous field state logic.
3753
+ *
3754
+ * These functions perform asynchronous operations and return a boolean
3755
+ * indicating whether the condition is met. They are referenced by
3756
+ * `asyncFunctionName` in `AsyncCondition` expressions.
3757
+ *
3758
+ * Async condition functions:
3759
+ * - Receive an `EvaluationContext` with access to `formValue`
3760
+ * - Return a Promise or Observable of boolean
3761
+ * - Handle their own I/O (use injected services)
3762
+ *
3763
+ * @example
3764
+ * ```typescript
3765
+ * asyncConditions: {
3766
+ * checkPermission: async (context) => {
3767
+ * const response = await fetch(`/api/permissions?role=${context.formValue.role}`);
3768
+ * const data = await response.json();
3769
+ * return data.canEdit;
3770
+ * },
3771
+ * }
3772
+ * ```
3773
+ */
3774
+ asyncConditions?: Record<string, AsyncConditionFunction<TFormValue>>;
2895
3775
  /**
2896
3776
  * Custom validators using Angular's public FieldContext API
2897
3777
  *
@@ -3009,6 +3889,10 @@ interface CustomFnConfig {
3009
3889
  */
3010
3890
  asyncValidators?: Record<string, AsyncCustomValidator>;
3011
3891
  /**
3892
+ * @deprecated Prefer declarative HTTP validators using `type: 'http'` with `http` + `responseMapping`
3893
+ * in the validators array. Function-based HTTP validators now also use `type: 'http'` with `functionName`.
3894
+ * Will be removed in a future minor version.
3895
+ *
3012
3896
  * HTTP validators using Angular's validateHttp() API
3013
3897
  *
3014
3898
  * Angular's validateHttp provides HTTP validation with automatic request
@@ -3134,22 +4018,49 @@ interface FormOptions {
3134
4018
  */
3135
4019
  submitButton?: SubmitButtonOptions;
3136
4020
  /**
3137
- * Default disabled behavior for next page buttons.
4021
+ * Default disabled behavior for next page buttons.
4022
+ *
4023
+ * Controls when next page buttons are automatically disabled in paged forms.
4024
+ * Can be overridden per-button via the `logic` array on individual button fields.
4025
+ *
4026
+ * @example
4027
+ * ```typescript
4028
+ * options: {
4029
+ * nextButton: {
4030
+ * disableWhenPageInvalid: true, // Disable when current page is invalid
4031
+ * disableWhileSubmitting: true, // Disable during submission
4032
+ * }
4033
+ * }
4034
+ * ```
4035
+ */
4036
+ nextButton?: NextButtonOptions;
4037
+ /**
4038
+ * Whether to exclude values of hidden fields from submission output.
4039
+ *
4040
+ * Overrides the global `withValueExclusionDefaults()` setting for this form.
4041
+ * Can be further overridden per-field on individual `FieldDef` entries.
4042
+ *
4043
+ * @default undefined (uses global setting)
4044
+ */
4045
+ excludeValueIfHidden?: boolean;
4046
+ /**
4047
+ * Whether to exclude values of disabled fields from submission output.
4048
+ *
4049
+ * Overrides the global `withValueExclusionDefaults()` setting for this form.
4050
+ * Can be further overridden per-field on individual `FieldDef` entries.
4051
+ *
4052
+ * @default undefined (uses global setting)
4053
+ */
4054
+ excludeValueIfDisabled?: boolean;
4055
+ /**
4056
+ * Whether to exclude values of readonly fields from submission output.
3138
4057
  *
3139
- * Controls when next page buttons are automatically disabled in paged forms.
3140
- * Can be overridden per-button via the `logic` array on individual button fields.
4058
+ * Overrides the global `withValueExclusionDefaults()` setting for this form.
4059
+ * Can be further overridden per-field on individual `FieldDef` entries.
3141
4060
  *
3142
- * @example
3143
- * ```typescript
3144
- * options: {
3145
- * nextButton: {
3146
- * disableWhenPageInvalid: true, // Disable when current page is invalid
3147
- * disableWhileSubmitting: true, // Disable during submission
3148
- * }
3149
- * }
3150
- * ```
4061
+ * @default undefined (uses global setting)
3151
4062
  */
3152
- nextButton?: NextButtonOptions;
4063
+ excludeValueIfReadonly?: boolean;
3153
4064
  /**
3154
4065
  * Whether to attach the current form value to all events dispatched through the EventBus.
3155
4066
  *
@@ -3430,15 +4341,10 @@ declare class DynamicForm<TFields extends RegisteredFieldTypes[] = RegisteredFie
3430
4341
  private injector;
3431
4342
  private eventBus;
3432
4343
  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;
4344
+ private dispatcher;
4345
+ /** State manager that owns all form state. Initialized via connectDeps() to guarantee
4346
+ * FORM_STATE_DEPS is populated before FormStateManager is injected. */
4347
+ private readonly stateManager;
3442
4348
  private componentId;
3443
4349
  /** The currently active config used for form rendering */
3444
4350
  activeConfig: Signal<FormConfig<TFields, InferFormValue$1<TFields extends readonly RegisteredFieldTypes[] ? TFields : RegisteredFieldTypes[]>, Record<string, unknown>, unknown> | undefined>;
@@ -3454,6 +4360,9 @@ declare class DynamicForm<TFields extends RegisteredFieldTypes[] = RegisteredFie
3454
4360
  maxDerivationIterations?: number;
3455
4361
  submitButton?: _ng_forge_dynamic_forms.SubmitButtonOptions;
3456
4362
  nextButton?: _ng_forge_dynamic_forms.NextButtonOptions;
4363
+ excludeValueIfHidden?: boolean;
4364
+ excludeValueIfDisabled?: boolean;
4365
+ excludeValueIfReadonly?: boolean;
3457
4366
  emitFormValueOnEvents?: boolean;
3458
4367
  }>;
3459
4368
  /** Field signal context for injection into child components */
@@ -3473,7 +4382,7 @@ declare class DynamicForm<TFields extends RegisteredFieldTypes[] = RegisteredFie
3473
4382
  /** Whether any form field has been touched (blurred) */
3474
4383
  touched: Signal<boolean>;
3475
4384
  /** Current validation errors from all fields */
3476
- errors: Signal<_angular_forms_signals.ValidationError.WithField[]>;
4385
+ errors: Signal<_angular_forms_signals.ValidationError.WithFieldTree[]>;
3477
4386
  /** Whether the form is disabled (from options or form state) */
3478
4387
  disabled: Signal<boolean>;
3479
4388
  /** Whether the form is currently submitting */
@@ -3484,6 +4393,13 @@ declare class DynamicForm<TFields extends RegisteredFieldTypes[] = RegisteredFie
3484
4393
  shouldRender: Signal<boolean>;
3485
4394
  /** Resolved fields ready for rendering */
3486
4395
  protected resolvedFields: Signal<ResolvedField[]>;
4396
+ /**
4397
+ * Recursively counts container components that will emit ComponentInitializedEvent.
4398
+ * Includes the dynamic-form component itself (+1).
4399
+ *
4400
+ * Recurses into all container children, including those nested inside array
4401
+ * item templates, to avoid a premature (initialized) emission.
4402
+ */
3487
4403
  private totalComponentsCount;
3488
4404
  initialized$: rxjs.Observable<boolean>;
3489
4405
  /** Emits when form validity changes. */
@@ -3529,11 +4445,176 @@ declare class DynamicForm<TFields extends RegisteredFieldTypes[] = RegisteredFie
3529
4445
  */
3530
4446
  protected onNativeSubmit(event: Event): void;
3531
4447
  private setupEffects;
4448
+ /**
4449
+ * Populates FORM_STATE_DEPS with this component's input signals, then injects
4450
+ * FormStateManager. Must be called as a field initializer (after the input signals
4451
+ * are declared) so that FormStateManager reads populated deps when it is constructed.
4452
+ * inject() is valid here because field initializers run inside the injection context.
4453
+ */
4454
+ private connectDeps;
3532
4455
  private setupEventHandlers;
3533
4456
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<DynamicForm<any, any>, never>;
3534
4457
  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>;
3535
4458
  }
3536
4459
 
4460
+ declare class EventBus {
4461
+ private readonly pipeline$;
4462
+ private readonly globalEmitFormValue;
4463
+ private readonly rootFormRegistry;
4464
+ private readonly formOptions;
4465
+ private readonly logger;
4466
+ events$: Observable<FormEvent>;
4467
+ /**
4468
+ * Dispatches a pre-created event instance directly.
4469
+ *
4470
+ * Use this overload with the `arrayEvent()` factory or any other code that
4471
+ * produces a `FormEvent` instance rather than a constructor:
4472
+ *
4473
+ * ```typescript
4474
+ * eventBus.dispatch(arrayEvent('contacts').append(template));
4475
+ * eventBus.dispatch(arrayEvent('contacts').pop());
4476
+ * ```
4477
+ *
4478
+ * @param event - A FormEvent instance to dispatch
4479
+ */
4480
+ dispatch(event: FormEvent): void;
4481
+ /**
4482
+ * Dispatches an event to all subscribers by instantiating the provided constructor.
4483
+ *
4484
+ * Creates an instance of the provided event constructor and broadcasts it
4485
+ * through the event pipeline to all active subscribers.
4486
+ *
4487
+ * If `withEventFormValue()` is enabled globally or `options.emitFormValueOnEvents`
4488
+ * is set to `true` in the form config, the current form value will be attached
4489
+ * to the event's `formValue` property.
4490
+ *
4491
+ * @param eventConstructor - Constructor function for the event to dispatch
4492
+ * @param args - Arguments to pass to the event constructor
4493
+ *
4494
+ * @example
4495
+ * ```typescript
4496
+ * // Dispatch a submit event
4497
+ * eventBus.dispatch(SubmitEvent);
4498
+ *
4499
+ * // Dispatch a custom event with args
4500
+ * eventBus.dispatch(CustomFormEvent, 'arg1', 42);
4501
+ * ```
4502
+ */
4503
+ dispatch<T extends FormEventConstructor>(eventConstructor: T, ...args: ConstructorParameters<T>): void;
4504
+ /**
4505
+ * Dispatches a pre-created event instance directly.
4506
+ * Used internally by EventDispatcher to forward events into the bus.
4507
+ * @internal
4508
+ */
4509
+ emitInstance(event: FormEvent): void;
4510
+ /**
4511
+ * Shared emit path for both dispatch() and emitInstance().
4512
+ * Attaches form value if configured, then pushes to the pipeline.
4513
+ */
4514
+ private emit;
4515
+ /**
4516
+ * Emits an event through the pipeline. Catches any synchronous exception that escapes
4517
+ * RxJS's own error handling so dispatch() callers are never disrupted by a failing subscriber.
4518
+ */
4519
+ private safeEmit;
4520
+ /**
4521
+ * Determines whether form value should be attached to events.
4522
+ *
4523
+ * Precedence rules:
4524
+ * 1. Per-form setting (if defined) takes precedence
4525
+ * 2. Falls back to global setting
4526
+ */
4527
+ private shouldEmitFormValue;
4528
+ /**
4529
+ * Subscribes to events of a specific type.
4530
+ *
4531
+ * @param eventType - The type of event to subscribe to
4532
+ * @returns Observable that emits events of the specified type
4533
+ */
4534
+ on<T extends FormEvent>(eventType: T['type']): Observable<T>;
4535
+ /**
4536
+ * Subscribes to events of multiple types.
4537
+ *
4538
+ * @param eventType - Array of event types to subscribe to
4539
+ * @returns Observable that emits events matching any of the specified types
4540
+ */
4541
+ on<T extends FormEvent>(eventType: Array<T['type']>): Observable<T>;
4542
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<EventBus, never>;
4543
+ static ɵprov: _angular_core.ɵɵInjectableDeclaration<EventBus>;
4544
+ }
4545
+
4546
+ /**
4547
+ * Injectable service for dispatching events into a DynamicForm from outside the form.
4548
+ *
4549
+ * ## When to use
4550
+ *
4551
+ * Use `EventDispatcher` when you need to drive form behaviour **from the host component** —
4552
+ * for example, appending array items in response to a field value change, triggering a form
4553
+ * reset from a toolbar button, or reacting to external application state.
4554
+ *
4555
+ * This is the recommended alternative to accessing the form's internals via `viewChild`.
4556
+ *
4557
+ * ## Setup
4558
+ *
4559
+ * Provide `EventDispatcher` at the **host component** level (not root). DynamicForm
4560
+ * automatically detects it and connects its internal event bus to the dispatcher.
4561
+ *
4562
+ * ```typescript
4563
+ * @Component({
4564
+ * providers: [EventDispatcher],
4565
+ * template: `<form [dynamic-form]="config" [(value)]="formValue"></form>`
4566
+ * })
4567
+ * export class MyComponent {
4568
+ * readonly dispatcher = inject(EventDispatcher);
4569
+ * readonly formValue = signal<Record<string, unknown>>({});
4570
+ *
4571
+ * constructor() {
4572
+ * effect(() => {
4573
+ * const category = this.formValue()?.['category'] as string | undefined;
4574
+ * if (category) {
4575
+ * this.dispatcher.dispatch(
4576
+ * arrayEvent('tasks').append([{ key: 'name', type: 'input', label: 'Task', value: category }])
4577
+ * );
4578
+ * }
4579
+ * });
4580
+ * }
4581
+ * }
4582
+ * ```
4583
+ *
4584
+ * ## Multi-form note
4585
+ *
4586
+ * If multiple `DynamicForm` instances exist under the same provider scope, **all forms**
4587
+ * will receive dispatched events. To target a specific form, scope the provider to a
4588
+ * wrapper component that contains only that form.
4589
+ *
4590
+ * ## What events can be dispatched
4591
+ *
4592
+ * Any `FormEvent` instance is accepted — array events, reset/clear events, custom events, etc.
4593
+ * The `arrayEvent()` builder is the recommended way to construct array manipulation events:
4594
+ *
4595
+ * ```typescript
4596
+ * dispatcher.dispatch(arrayEvent('tasks').append(template));
4597
+ * dispatcher.dispatch(arrayEvent('tasks').removeAt(0));
4598
+ * dispatcher.dispatch(new FormResetEvent());
4599
+ * ```
4600
+ */
4601
+ declare class EventDispatcher {
4602
+ private bus;
4603
+ /**
4604
+ * Dispatches a form event into the connected DynamicForm's event bus.
4605
+ * No-op if no form is currently connected.
4606
+ *
4607
+ * @param event - A FormEvent instance. Use the `arrayEvent()` builder for array operations.
4608
+ */
4609
+ dispatch(event: FormEvent): void;
4610
+ /** @internal - Called by DynamicForm on init */
4611
+ connect(bus: EventBus): void;
4612
+ /** @internal - Called by DynamicForm on destroy */
4613
+ disconnect(): void;
4614
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<EventDispatcher, never>;
4615
+ static ɵprov: _angular_core.ɵɵInjectableDeclaration<EventDispatcher>;
4616
+ }
4617
+
3537
4618
  /**
3538
4619
  * Base interface for dynamic form features.
3539
4620
  * Features are configuration options that can be passed to provideDynamicForm
@@ -3860,6 +4941,100 @@ declare class NoopLogger implements Logger {
3860
4941
  */
3861
4942
  declare function withEventFormValue(): DynamicFormFeature<'event-form-value'>;
3862
4943
 
4944
+ /**
4945
+ * Configuration for excluding field values from form submission output
4946
+ * based on their reactive state (hidden, disabled, readonly).
4947
+ *
4948
+ * Supports a 3-tier configuration hierarchy: Field > Form > Global.
4949
+ * The most specific level wins for each property.
4950
+ *
4951
+ * @public
4952
+ */
4953
+ interface ValueExclusionConfig {
4954
+ /**
4955
+ * Whether to exclude the value of hidden fields from submission output.
4956
+ *
4957
+ * When `true`, fields whose `hidden()` state is `true` will have their
4958
+ * values omitted from the submitted form value.
4959
+ *
4960
+ * @remarks
4961
+ * This does NOT affect HiddenField (`type: 'hidden'`) — those fields
4962
+ * store values without UI and their `hidden()` state is always `false`.
4963
+ */
4964
+ readonly excludeValueIfHidden?: boolean;
4965
+ /**
4966
+ * Whether to exclude the value of disabled fields from submission output.
4967
+ *
4968
+ * When `true`, fields whose `disabled()` state is `true` will have their
4969
+ * values omitted from the submitted form value.
4970
+ */
4971
+ readonly excludeValueIfDisabled?: boolean;
4972
+ /**
4973
+ * Whether to exclude the value of readonly fields from submission output.
4974
+ *
4975
+ * When `true`, fields whose `readonly()` state is `true` will have their
4976
+ * values omitted from the submitted form value.
4977
+ */
4978
+ readonly excludeValueIfReadonly?: boolean;
4979
+ }
4980
+ /**
4981
+ * Fully resolved exclusion config with all properties required.
4982
+ * Produced by the 3-tier resolution: `field ?? form ?? global`.
4983
+ *
4984
+ * @public
4985
+ */
4986
+ type ResolvedValueExclusionConfig = Required<ValueExclusionConfig>;
4987
+
4988
+ /**
4989
+ * Configures global value exclusion defaults for form submission output.
4990
+ *
4991
+ * Value exclusion is **enabled by default** — field values are excluded from the
4992
+ * `(submitted)` output based on their reactive state. Use this feature to
4993
+ * override those defaults. This does NOT affect two-way binding (`value` model /
4994
+ * `entity`) — fields retain their values internally.
4995
+ *
4996
+ * @example Default behavior (all exclusions enabled)
4997
+ * ```typescript
4998
+ * provideDynamicForm(
4999
+ * ...withMaterialFields(),
5000
+ * withValueExclusionDefaults()
5001
+ * )
5002
+ * ```
5003
+ *
5004
+ * @example Disable specific exclusions
5005
+ * ```typescript
5006
+ * provideDynamicForm(
5007
+ * ...withMaterialFields(),
5008
+ * withValueExclusionDefaults({ excludeValueIfReadonly: false })
5009
+ * )
5010
+ * ```
5011
+ *
5012
+ * @example Disable all exclusions (restore pre-v1 behavior)
5013
+ * ```typescript
5014
+ * provideDynamicForm(
5015
+ * ...withMaterialFields(),
5016
+ * withValueExclusionDefaults({
5017
+ * excludeValueIfHidden: false,
5018
+ * excludeValueIfDisabled: false,
5019
+ * excludeValueIfReadonly: false,
5020
+ * })
5021
+ * )
5022
+ * ```
5023
+ *
5024
+ * @remarks
5025
+ * **Precedence rules:**
5026
+ * 1. Per-field `excludeValueIf*` on `FieldDef` — wins for that field
5027
+ * 2. Per-form `excludeValueIf*` on `FormOptions` — wins for all fields in that form
5028
+ * 3. Global `withValueExclusionDefaults()` — baseline default
5029
+ * 4. No global feature — uses token default (all enabled)
5030
+ *
5031
+ * @param config - Partial override of exclusion rules. Unspecified properties default to `true`.
5032
+ * @returns A DynamicFormFeature that configures value exclusion defaults
5033
+ *
5034
+ * @public
5035
+ */
5036
+ declare function withValueExclusionDefaults(config?: Partial<ValueExclusionConfig>): DynamicFormFeature<'value-exclusion'>;
5037
+
3863
5038
  interface FieldOption<T = unknown> {
3864
5039
  label: DynamicText;
3865
5040
  value: T;
@@ -4060,7 +5235,7 @@ interface BaseValueField<TProps, TValue, TMeta extends FieldMeta = FieldMeta> ex
4060
5235
  required?: boolean;
4061
5236
  }
4062
5237
  declare function isValueField<TProps, TMeta extends FieldMeta = FieldMeta>(field: FieldDef<TProps, TMeta>): field is BaseValueField<TProps, ValueType, TMeta>;
4063
- type ExcludedKeys$1 = 'type' | 'conditionals' | 'value' | 'valueType' | 'disabled' | 'readonly' | 'hidden' | 'col' | 'minValue' | 'maxValue' | 'step' | 'required' | 'email' | 'min' | 'max' | 'minLength' | 'maxLength' | 'pattern' | 'validators' | 'logic' | 'derivation' | 'schemas';
5238
+ type ExcludedKeys$1 = 'type' | 'conditionals' | 'value' | 'valueType' | 'disabled' | 'readonly' | 'hidden' | 'col' | 'minValue' | 'maxValue' | 'step' | 'required' | 'email' | 'min' | 'max' | 'minLength' | 'maxLength' | 'pattern' | 'validators' | 'logic' | 'derivation' | 'schemas' | 'excludeValueIfHidden' | 'excludeValueIfDisabled' | 'excludeValueIfReadonly';
4064
5239
  type ValueFieldComponent<T extends BaseValueField<Record<string, unknown> | unknown, unknown>> = Prettify<WithInputSignals<Omit<T, ExcludedKeys$1>>>;
4065
5240
 
4066
5241
  interface BaseCheckedField<TProps, TMeta extends FieldMeta = FieldMeta> extends FieldDef<TProps, TMeta>, FieldWithValidation {
@@ -4073,7 +5248,7 @@ interface BaseCheckedField<TProps, TMeta extends FieldMeta = FieldMeta> extends
4073
5248
  required?: boolean;
4074
5249
  }
4075
5250
  declare function isCheckedField<TProps, TMeta extends FieldMeta = FieldMeta>(field: FieldDef<TProps, TMeta>): field is BaseCheckedField<TProps, TMeta>;
4076
- type ExcludedKeys = 'type' | 'conditionals' | 'value' | 'disabled' | 'readonly' | 'hidden' | 'col' | keyof FieldWithValidation;
5251
+ type ExcludedKeys = 'type' | 'conditionals' | 'value' | 'disabled' | 'readonly' | 'hidden' | 'col' | keyof FieldWithValidation | 'excludeValueIfHidden' | 'excludeValueIfDisabled' | 'excludeValueIfReadonly';
4077
5252
  type CheckedFieldComponent<T extends BaseCheckedField<Record<string, unknown> | unknown>> = Prettify<WithInputSignals<Omit<T, ExcludedKeys>>>;
4078
5253
 
4079
5254
  declare function applyValidator(config: ValidatorConfig, fieldPath: SchemaPath<any> | SchemaPathTree<any>): void;
@@ -4092,20 +5267,12 @@ declare class PreviousPageEvent implements FormEvent {
4092
5267
  }
4093
5268
 
4094
5269
  /**
4095
- * Template type for OBJECT array items: array of field configurations.
4096
- * Each field's value is merged into the item object.
5270
+ * Template type for array items used in events.
5271
+ * Canonical definition lives in {@link ArrayItemDefinition} — this alias
5272
+ * preserves the events-specific naming convention.
4097
5273
  */
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;
5274
+ type ArrayItemDefinitionTemplate = ArrayItemDefinition;
5275
+
4109
5276
  /**
4110
5277
  * Event dispatched to append a new item at the END of an array field.
4111
5278
  *
@@ -4461,92 +5628,6 @@ declare function arrayEvent(arrayKey: string): {
4461
5628
  removeAt: (index: number) => RemoveAtIndexEvent;
4462
5629
  };
4463
5630
 
4464
- /**
4465
- * Event bus service for form-wide event communication.
4466
- *
4467
- * Provides type-safe event dispatching and subscription for form components
4468
- * to communicate without direct coupling. Uses RxJS observables for reactive
4469
- * event handling and supports both single event type and multiple event type subscriptions.
4470
- *
4471
- * @example
4472
- * ```typescript
4473
- * @Component({
4474
- * providers: [EventBus]
4475
- * })
4476
- * export class MyFormComponent {
4477
- * constructor(private eventBus: EventBus) {
4478
- * // Subscribe to form submission events
4479
- * this.eventBus.on<SubmitEvent>('submit').subscribe(() => {
4480
- * console.log('Form was submitted');
4481
- * });
4482
- *
4483
- * // Subscribe to multiple event types
4484
- * this.eventBus.on<SubmitEvent | FormResetEvent>(['submit', 'form-reset']).subscribe(event => {
4485
- * console.log('Form event:', event.type);
4486
- * });
4487
- * }
4488
- *
4489
- * onSubmit() {
4490
- * // Dispatch a submit event
4491
- * this.eventBus.dispatch(SubmitEvent);
4492
- * }
4493
- * }
4494
- * ```
4495
- */
4496
- declare class EventBus {
4497
- private readonly pipeline$;
4498
- private readonly globalEmitFormValue;
4499
- private readonly rootFormRegistry;
4500
- private readonly formOptions;
4501
- events$: Observable<FormEvent>;
4502
- /**
4503
- * Dispatches an event to all subscribers.
4504
- *
4505
- * Creates an instance of the provided event constructor and broadcasts it
4506
- * through the event pipeline to all active subscribers.
4507
- *
4508
- * If `withEventFormValue()` is enabled globally or `options.emitFormValueOnEvents`
4509
- * is set to `true` in the form config, the current form value will be attached
4510
- * to the event's `formValue` property.
4511
- *
4512
- * @param eventConstructor - Constructor function for the event to dispatch
4513
- *
4514
- * @example
4515
- * ```typescript
4516
- * // Dispatch a submit event
4517
- * eventBus.dispatch(SubmitEvent);
4518
- *
4519
- * // Dispatch a custom event
4520
- * eventBus.dispatch(CustomFormEvent);
4521
- * ```
4522
- */
4523
- dispatch<T extends FormEventConstructor>(eventConstructor: T, ...args: ConstructorParameters<T>): void;
4524
- /**
4525
- * Determines whether form value should be attached to events.
4526
- *
4527
- * Precedence rules:
4528
- * 1. Per-form setting (if defined) takes precedence
4529
- * 2. Falls back to global setting
4530
- */
4531
- private shouldEmitFormValue;
4532
- /**
4533
- * Subscribes to events of a specific type.
4534
- *
4535
- * @param eventType - The type of event to subscribe to
4536
- * @returns Observable that emits events of the specified type
4537
- */
4538
- on<T extends FormEvent>(eventType: T['type']): Observable<T>;
4539
- /**
4540
- * Subscribes to events of multiple types.
4541
- *
4542
- * @param eventType - Array of event types to subscribe to
4543
- * @returns Observable that emits events matching any of the specified types
4544
- */
4545
- on<T extends FormEvent>(eventType: Array<T['type']>): Observable<T>;
4546
- static ɵfac: _angular_core.ɵɵFactoryDeclaration<EventBus, never>;
4547
- static ɵprov: _angular_core.ɵɵInjectableDeclaration<EventBus>;
4548
- }
4549
-
4550
5631
  /**
4551
5632
  * Context for token resolution in event args
4552
5633
  */
@@ -4759,6 +5840,8 @@ declare function baseFieldMapper(fieldDef: FieldDef<unknown>): Signal<Record<str
4759
5840
  * Row components are layout containers that don't change the form shape.
4760
5841
  * The row component will inject FIELD_SIGNAL_CONTEXT directly.
4761
5842
  *
5843
+ * Supports hidden state resolution via `logic` array or static `hidden` property.
5844
+ *
4762
5845
  * @param fieldDef The row field definition
4763
5846
  * @returns Signal containing Record of input names to values for ngComponentOutlet
4764
5847
  */
@@ -4771,6 +5854,8 @@ declare function rowFieldMapper(fieldDef: RowField): Signal<Record<string, unkno
4771
5854
  * The group component will inject the parent FIELD_SIGNAL_CONTEXT and create
4772
5855
  * a scoped child injector for its nested fields.
4773
5856
  *
5857
+ * Supports hidden state resolution via `logic` array or static `hidden` property.
5858
+ *
4774
5859
  * @param fieldDef The group field definition
4775
5860
  * @returns Signal containing Record of input names to values for ngComponentOutlet
4776
5861
  */
@@ -4783,6 +5868,8 @@ declare function groupFieldMapper(fieldDef: GroupField): Signal<Record<string, u
4783
5868
  * The array component will inject the parent FIELD_SIGNAL_CONTEXT and create
4784
5869
  * a scoped child injector for its array item fields.
4785
5870
  *
5871
+ * Supports hidden state resolution via `logic` array or static `hidden` property.
5872
+ *
4786
5873
  * @param fieldDef The array field definition
4787
5874
  * @returns Signal containing Record of input names to values for ngComponentOutlet
4788
5875
  */
@@ -4794,6 +5881,10 @@ declare function arrayFieldMapper(fieldDef: ArrayField): Signal<Record<string, u
4794
5881
  * Page fields are layout containers that don't modify the form context.
4795
5882
  * The page component will inject FIELD_SIGNAL_CONTEXT directly.
4796
5883
  *
5884
+ * Note: Unlike other container mappers (group, row, array), the page mapper does NOT
5885
+ * resolve hidden logic here. Page visibility is managed by the {@link PageOrchestratorComponent}
5886
+ * which evaluates `ContainerLogicConfig` conditions and controls the `isVisible` input.
5887
+ *
4797
5888
  * @param fieldDef The page field definition
4798
5889
  * @returns Signal containing Record of input names to values for ngComponentOutlet
4799
5890
  */
@@ -4906,6 +5997,7 @@ declare class RowFieldComponent {
4906
5997
  field: _angular_core.InputSignal<RowField<readonly _ng_forge_dynamic_forms.RowAllowedChildren[]>>;
4907
5998
  key: _angular_core.InputSignal<string>;
4908
5999
  className: _angular_core.InputSignal<string | undefined>;
6000
+ hidden: _angular_core.InputSignal<boolean>;
4909
6001
  readonly hostClasses: _angular_core.Signal<string>;
4910
6002
  readonly disabled: _angular_core.Signal<boolean>;
4911
6003
  private readonly rawFieldRegistry;
@@ -4914,7 +6006,7 @@ declare class RowFieldComponent {
4914
6006
  constructor();
4915
6007
  private setupEffects;
4916
6008
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<RowFieldComponent, never>;
4917
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<RowFieldComponent, "div[row-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>;
6009
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<RowFieldComponent, "div[row-field]", never, { "field": { "alias": "field"; "required": true; "isSignal": true; }; "key": { "alias": "key"; "required": true; "isSignal": true; }; "className": { "alias": "className"; "required": false; "isSignal": true; }; "hidden": { "alias": "hidden"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
4918
6010
  }
4919
6011
 
4920
6012
  /**
@@ -4935,6 +6027,7 @@ declare class GroupFieldComponent<TModel extends Record<string, unknown> = Recor
4935
6027
  field: _angular_core.InputSignal<GroupField<readonly _ng_forge_dynamic_forms.GroupAllowedChildren[]>>;
4936
6028
  key: _angular_core.InputSignal<string>;
4937
6029
  className: _angular_core.InputSignal<string | undefined>;
6030
+ hidden: _angular_core.InputSignal<boolean>;
4938
6031
  readonly hostClasses: _angular_core.Signal<string>;
4939
6032
  private readonly rawFieldRegistry;
4940
6033
  private readonly formSetup;
@@ -4951,7 +6044,7 @@ declare class GroupFieldComponent<TModel extends Record<string, unknown> = Recor
4951
6044
  readonly invalid: _angular_core.Signal<boolean>;
4952
6045
  readonly dirty: _angular_core.Signal<boolean>;
4953
6046
  readonly touched: _angular_core.Signal<boolean>;
4954
- readonly errors: _angular_core.Signal<_angular_forms_signals.ValidationError.WithField[]>;
6047
+ readonly errors: _angular_core.Signal<_angular_forms_signals.ValidationError.WithFieldTree[]>;
4955
6048
  readonly disabled: _angular_core.Signal<boolean>;
4956
6049
  private readonly nestedFieldTree;
4957
6050
  private readonly groupFieldSignalContext;
@@ -4964,7 +6057,7 @@ declare class GroupFieldComponent<TModel extends Record<string, unknown> = Recor
4964
6057
  constructor();
4965
6058
  private setupEffects;
4966
6059
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<GroupFieldComponent<any>, never>;
4967
- 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; }; "className": { "alias": "className"; "required": false; "isSignal": true; }; }, { "validityChange": "validityChange"; "dirtyChange": "dirtyChange"; "submitted": "submitted"; }, never, never, true, never>;
6060
+ 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; }; "className": { "alias": "className"; "required": false; "isSignal": true; }; "hidden": { "alias": "hidden"; "required": false; "isSignal": true; }; }, { "validityChange": "validityChange"; "dirtyChange": "dirtyChange"; "submitted": "submitted"; }, never, never, true, never>;
4968
6061
  }
4969
6062
 
4970
6063
  /**
@@ -5011,14 +6104,40 @@ declare class ArrayFieldComponent<TModel extends Record<string, unknown> = Recor
5011
6104
  field: _angular_core.InputSignal<ArrayField<readonly ArrayItemDefinition[]>>;
5012
6105
  key: _angular_core.InputSignal<string>;
5013
6106
  className: _angular_core.InputSignal<string | undefined>;
5014
- readonly hostClasses: _angular_core.Signal<string>;
6107
+ hidden: _angular_core.InputSignal<boolean>;
6108
+ readonly hostClasses: Signal<string>;
5015
6109
  private readonly rawFieldRegistry;
6110
+ /**
6111
+ * Gets the auto-remove button FieldDef from normalization metadata.
6112
+ * Set by simplified array normalization for primitive arrays with remove buttons.
6113
+ * The button is rendered alongside each item without wrapping in a row,
6114
+ * preserving flat primitive form values.
6115
+ */
6116
+ private readonly autoRemoveButton;
6117
+ /**
6118
+ * For primitive array items, the key of the value field template (e.g., 'value').
6119
+ * Used to wrap the FormControl in the item context so getFieldTree(key) works.
6120
+ * Returns undefined for object arrays (FormGroup items have natural child navigation).
6121
+ *
6122
+ * Detection order:
6123
+ * 1. Normalization metadata (for simplified arrays — always available at normalization time)
6124
+ * 2. Existing item definitions (non-empty full-API arrays)
6125
+ * 3. Dynamically discovered key from handleAddFromEvent (empty full-API arrays)
6126
+ *
6127
+ * Uses linkedSignal: recomputes when field() changes, supports manual set()
6128
+ * for the dynamic discovery case.
6129
+ */
6130
+ private readonly primitiveFieldKey;
5016
6131
  /**
5017
6132
  * Gets the item templates (field definitions) for the array.
5018
6133
  * Each element can be either:
5019
6134
  * - A single FieldDef (primitive item) - normalized to [FieldDef]
5020
6135
  * - An array of FieldDefs (object item) - used as-is
5021
6136
  *
6137
+ * When auto-remove is configured, the remove button is appended to each item's
6138
+ * template list for rendering. This is purely visual — the form schema uses the
6139
+ * original primitive item definition (single FieldDef → FormControl → flat value).
6140
+ *
5022
6141
  * Returns normalized templates where all items are arrays for consistent handling.
5023
6142
  */
5024
6143
  private readonly itemTemplates;
@@ -5031,7 +6150,20 @@ declare class ArrayFieldComponent<TModel extends Record<string, unknown> = Recor
5031
6150
  */
5032
6151
  private readonly itemPositionMap;
5033
6152
  /** Read-only view of resolved items for template consumption. */
5034
- readonly resolvedItems: _angular_core.Signal<ResolvedArrayItem[]>;
6153
+ readonly resolvedItems: Signal<ResolvedArrayItem[]>;
6154
+ /**
6155
+ * Whether the array has reached its configured maxLength.
6156
+ * Exposed as a public signal so add-button components can bind [disabled]="atMaxLength()".
6157
+ */
6158
+ readonly atMaxLength: Signal<boolean>;
6159
+ /**
6160
+ * Caches the result of appending the auto-remove button to template arrays.
6161
+ * Keyed by template array reference — cache hits occur during recreate/resolution
6162
+ * operations where stored template references are reused. Add operations via
6163
+ * handleAddFromEvent always create a fresh `[...template]` copy (line ~277),
6164
+ * so each add is a cache miss by design (the spread is needed for mutability).
6165
+ */
6166
+ private readonly autoRemoveCache;
5035
6167
  constructor();
5036
6168
  private setupEffects;
5037
6169
  private setupEventHandlers;
@@ -5056,6 +6188,18 @@ declare class ArrayFieldComponent<TModel extends Record<string, unknown> = Recor
5056
6188
  * 3. Return undefined (item cannot be resolved without a template)
5057
6189
  */
5058
6190
  private createResolveItemObservable;
6191
+ /**
6192
+ * Appends the auto-remove button to a templates array for rendering.
6193
+ * The button is added for visual rendering only — it doesn't affect the form schema.
6194
+ * Original templates are stored in templateRegistry WITHOUT the remove button,
6195
+ * so this method is called during resolution to add it dynamically.
6196
+ *
6197
+ * Uses a WeakMap cache keyed by template array reference. Cache hits occur
6198
+ * during recreate/resolution paths where stored templates are reused.
6199
+ * Add operations always pass a fresh `[...template]` copy, so they miss
6200
+ * the cache intentionally (the copy is needed for mutable item construction).
6201
+ */
6202
+ private withAutoRemove;
5059
6203
  /**
5060
6204
  * Handles remove operations from events (pop, shift, removeAt).
5061
6205
  * Updates resolvedItems FIRST, then form value - this ensures differential
@@ -5064,7 +6208,7 @@ declare class ArrayFieldComponent<TModel extends Record<string, unknown> = Recor
5064
6208
  */
5065
6209
  private removeItem;
5066
6210
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<ArrayFieldComponent<any>, never>;
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>;
6211
+ 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; }; "hidden": { "alias": "hidden"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
5068
6212
  }
5069
6213
 
5070
6214
  /**
@@ -5150,5 +6294,12 @@ declare class RootFormRegistryService {
5150
6294
  constructor(formValue: Signal<Record<string, unknown>>, rootForm: Signal<FieldTree<Record<string, unknown>> | undefined>);
5151
6295
  }
5152
6296
 
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 };
6297
+ /**
6298
+ * Injection token for configuring the initialization timeout in milliseconds.
6299
+ * Defaults to 10 seconds. When the timeout is reached, a warning is logged
6300
+ * and (initialized) emits true as a best-effort fallback.
6301
+ */
6302
+ declare const INITIALIZATION_TIMEOUT_MS: InjectionToken<number>;
6303
+
6304
+ export { ARRAY_CONTEXT, AppendArrayItemEvent, ArrayFieldComponent, BUILT_IN_FIELDS, ConsoleLogger, DEFAULT_PROPS, DEFAULT_VALIDATION_MESSAGES, DynamicForm, DynamicFormError, DynamicFormLogger, DynamicTextPipe, EventBus, EventDispatcher, FIELD_REGISTRY, FIELD_SIGNAL_CONTEXT, FORM_OPTIONS, FormClearEvent, FormResetEvent, GroupFieldComponent, INITIALIZATION_TIMEOUT_MS, InsertArrayItemEvent, NextPageEvent, NoopLogger, PageChangeEvent, PopArrayItemEvent, PrependArrayItemEvent, PreviousPageEvent, RemoveAtIndexEvent, RootFormRegistryService, RowFieldComponent, ShiftArrayItemEvent, SubmitEvent, applyMetaToElement, applyValidator, applyValidators, arrayEvent, arrayFieldMapper, baseFieldMapper, buildBaseInputs, createField, dynamicTextToObservable, evaluateNonFieldDisabled, evaluateNonFieldHidden, field, formConfig, getArrayLength, groupFieldMapper, hasFormValue, interpolateParams, isArrayField, isCheckedField, isContainerField, isDisplayOnlyField, isEqual, isFormStateCondition, isGroupField, isLeafField, isPageField, isPropertyDerivationLogicConfig, isRowField, isSimplifiedArrayField, isValueBearingField, isValueField, omit, pageFieldMapper, provideDynamicForm, resolveNextButtonDisabled, resolveNonFieldDisabled, resolveNonFieldHidden, resolveSubmitButtonDisabled, resolveTokens, rowFieldMapper, textFieldMapper, withEventFormValue, withLoggerConfig, withValueExclusionDefaults };
6305
+ export type { ArrayAllowedChildren, ArrayButtonConfig, ArrayContext, ArrayField, ArrayFieldTree, ArrayItemContext, ArrayItemDefinitionTemplate, ArrayItemTemplate, AsyncCondition, AsyncConditionFunction, AsyncCustomValidator, AsyncDerivationFunction, AsyncValidatorConfig, AvailableFieldTypes, BaseCheckedField, BaseValidatorConfig, BaseValueField, BuiltInValidatorConfig, ButtonLogicContext, CheckedFieldComponent, ConditionalExpression, ContainerFieldTypes, CustomFnConfig, CustomValidator, CustomValidatorConfig, DeclarativeHttpValidatorConfig, DynamicFormFieldRegistry, DynamicText, EvaluationContext, ExtractField, ExtractFieldDefs, ExtractFormValue, FieldComponent, FieldDef, FieldMeta, FieldOption, FieldPathAccess, FieldRegistryContainers, FieldRegistryLeaves, FieldSignalContext, FieldStateContext, FieldStateInfo, FieldTypeDefinition, FieldWithValidation, FormConfig, FormEvent, FormEventConstructor, FormFieldStateMap, FormMode, FormModeDetectionResult, FormOptions, FormStateCondition, GroupAllowedChildren, GroupField, HttpCondition, HttpCustomValidator, HttpRequestConfig, HttpResourceRequest, HttpValidationResponseMapping, HttpValidatorConfig, InferFormValue$1 as InferFormValue, LeafFieldTypes, Logger, LogicConfig, MapperFn, NarrowField, NarrowFields, NextButtonOptions, NonFieldLogicConfig, NonFieldLogicContext, NonFieldLogicType, PageAllowedChildren, PageField, Prettify, PropertyDerivationLogicConfig, RegisteredFieldTypes, ResolvedValueExclusionConfig, RowAllowedChildren, RowField, SchemaApplicationConfig, SchemaDefinition, SimplifiedArrayField, StateLogicConfig, SubmissionActionResult, SubmissionConfig, SubmitButtonOptions, TextElementType, TextField, TextProps, TokenContext, ValidationError, ValidationMessages, ValidatorConfig, ValueExclusionConfig, ValueFieldComponent, ValueHandlingMode, ValueType, WithInputSignals };