@ng-forge/dynamic-forms 0.5.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,11 +1,11 @@
1
1
  import * as rxjs from 'rxjs';
2
2
  import { Observable } from 'rxjs';
3
+ import * as _angular_core from '@angular/core';
4
+ import { Signal, Injector, WritableSignal, InjectionToken, Type, ResourceRef, Provider, EnvironmentProviders, PipeTransform } from '@angular/core';
3
5
  import * as _angular_forms_signals from '@angular/forms/signals';
4
6
  import { FieldTree, FieldContext, ValidationError as ValidationError$1, TreeValidationResult, SchemaPath, SchemaPathTree } from '@angular/forms/signals';
5
7
  import * as _angular_forms from '@angular/forms';
6
8
  import * as _ng_forge_dynamic_forms from '@ng-forge/dynamic-forms';
7
- import * as _angular_core from '@angular/core';
8
- import { Signal, Injector, WritableSignal, InjectionToken, Type, ResourceRef, OnDestroy, InputSignal, Provider, EnvironmentProviders, PipeTransform } from '@angular/core';
9
9
  import { FormSchema } from '@ng-forge/dynamic-forms/schema';
10
10
 
11
11
  type WithInputSignals<T> = {
@@ -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)
@@ -578,92 +855,216 @@ type GroupAllowedChildren = LeafFieldTypes | RowField;
578
855
  */
579
856
  type ArrayAllowedChildren = LeafFieldTypes | RowField | GroupField;
580
857
 
858
+ /**
859
+ * An array item template is an array of allowed children that defines one OBJECT array item's fields.
860
+ * Used for object arrays where each item is `{ key1: value1, key2: value2, ... }`.
861
+ */
862
+ type ArrayItemTemplate = readonly ArrayAllowedChildren[];
863
+ /**
864
+ * An array item definition can be either:
865
+ * - A single field (ArrayAllowedChildren) for PRIMITIVE array items - extracts field value directly
866
+ * - An array of fields (ArrayItemTemplate) for OBJECT array items - merges fields into object
867
+ *
868
+ * This allows support for:
869
+ * - Primitive arrays: `['tag1', 'tag2']`
870
+ * - Object arrays: `[{ name: 'Alice', email: '...' }]`
871
+ * - Heterogeneous arrays: `[{ value: 'x' }, 'y']` (mixed primitives and objects)
872
+ */
873
+ type ArrayItemDefinition = ArrayAllowedChildren | ArrayItemTemplate;
581
874
  /**
582
875
  * Array field interface for creating dynamic field collections that map to array values.
583
876
  *
584
877
  * Key concepts:
585
- * - The `fields` array defines the TEMPLATE (single field definition), not instances
586
- * - Array items are created/removed dynamically at runtime via event bus
587
- * - For primitive arrays: Use a leaf field as template → `['value1', 'value2']`
588
- * - For object arrays: Use a group field as template → `[{key1: val1}, {key2: val2}]`
878
+ * - The outer `fields` array defines INITIAL ITEMS (each element is one array item to render)
879
+ * - Each element can be either:
880
+ * - A single FieldDef (primitive item) - the field's value is extracted directly
881
+ * - An array of FieldDefs (object item) - fields are merged into an object
882
+ * - Items can have different field configurations (heterogeneous arrays supported)
883
+ * - Empty `fields: []` means no initial items - user adds via buttons with explicit templates
884
+ * - Initial values are declared via `value` property on each field definition
589
885
  *
590
886
  * @example
591
- * // Primitive array (flat values)
887
+ * // Primitive array: ['angular', 'typescript']
592
888
  * {
593
889
  * key: 'tags',
594
890
  * type: 'array',
595
- * fields: [{ key: 'tag', type: 'input' }]
891
+ * fields: [
892
+ * { key: 'tag', type: 'input', value: 'angular' }, // Single field = primitive
893
+ * { key: 'tag', type: 'input', value: 'typescript' },
894
+ * ]
596
895
  * }
597
- * // Creates: { tags: ['tag1', 'tag2', 'tag3'] }
598
896
  *
599
897
  * @example
600
- * // Object array (nested groups)
898
+ * // Object array: [{ name: 'Alice', email: '...' }]
601
899
  * {
602
900
  * key: 'contacts',
603
901
  * type: 'array',
604
- * fields: [{
605
- * type: 'group',
606
- * fields: [
607
- * { key: 'name', type: 'input' },
608
- * { key: 'email', type: 'input' }
609
- * ]
610
- * }]
902
+ * fields: [
903
+ * [ // Array = object
904
+ * { key: 'name', type: 'input', value: 'Alice' },
905
+ * { key: 'email', type: 'input', value: 'alice@example.com' }
906
+ * ],
907
+ * ]
908
+ * }
909
+ *
910
+ * @example
911
+ * // Heterogeneous array: [{ label: 'Structured' }, 'Simple']
912
+ * {
913
+ * key: 'items',
914
+ * type: 'array',
915
+ * fields: [
916
+ * [{ key: 'label', type: 'input', value: 'Structured' }], // Object item
917
+ * { key: 'value', type: 'input', value: 'Simple' }, // Primitive item
918
+ * ]
919
+ * }
920
+ *
921
+ * @example
922
+ * // Empty array (add items via buttons)
923
+ * {
924
+ * key: 'tags',
925
+ * type: 'array',
926
+ * fields: [] // No initial items - user adds via button with template
611
927
  * }
612
- * // Creates: { contacts: [{name: '', email: ''}, {name: '', email: ''}] }
613
928
  *
614
929
  * Nesting constraints: Arrays can contain rows, leaf fields, or groups (for object arrays),
615
930
  * but NOT pages or other arrays. Runtime validation enforces these rules.
616
931
  *
617
932
  * Note: Arrays are container fields and do not support `meta` since they have no native form element.
618
933
  */
619
- interface ArrayField<TFields extends readonly ArrayAllowedChildren[] = readonly ArrayAllowedChildren[]> extends FieldDef<never> {
934
+ interface ArrayField<TFields extends readonly ArrayItemDefinition[] = readonly ArrayItemDefinition[]> extends FieldDef<never> {
620
935
  type: 'array';
621
936
  /**
622
- * Template field definition(s) that will be cloned for each array item.
623
- * Typically contains a single field definition:
624
- * - For flat arrays: A leaf field (input, select, checkbox, etc.)
625
- * - For object arrays: A group field containing multiple child fields
937
+ * Array of item definitions. Each element defines one array item:
938
+ * - Single FieldDef = primitive item (field's value is extracted directly)
939
+ * - Array of FieldDefs = object item (fields are merged into an object)
940
+ * - Empty array `[]` = no initial items (add via buttons with explicit templates)
941
+ * - Items can have different configurations (heterogeneous arrays supported)
942
+ * - Initial values are set via `value` property on each field definition
626
943
  */
627
944
  readonly fields: TFields;
628
945
  /** Array fields do not have a label property **/
629
946
  readonly label?: never;
630
947
  /** Arrays do not support meta - they have no native form element **/
631
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;
632
964
  }
633
965
  /**
634
- * Type guard for ArrayField with proper type narrowing
966
+ * Type guard for ArrayField with proper type narrowing.
967
+ * Validates that the field is an array type with a fields property that is an array.
968
+ * Fields can contain either single FieldDefs (primitive items) or arrays of FieldDefs (object items).
635
969
  */
636
970
  declare function isArrayField(field: FieldDef<any>): field is ArrayField;
637
-
638
- interface ConditionalExpression {
639
- /** Expression type */
640
- type: 'fieldValue' | 'formValue' | 'custom' | 'javascript' | 'and' | 'or';
641
- /** Field path for fieldValue type */
642
- fieldPath?: string;
643
- /** Comparison operator */
644
- operator?: 'equals' | 'notEquals' | 'greater' | 'less' | 'greaterOrEqual' | 'lessOrEqual' | 'contains' | 'startsWith' | 'endsWith' | 'matches';
645
- /** Value to compare against */
646
- value?: unknown;
647
- /** JavaScript expression for custom logic */
648
- expression?: string;
649
- /** Array of sub-conditions for 'and' and 'or' types */
650
- 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>;
651
979
  }
652
-
653
980
  /**
654
- * Logic configuration for page fields.
655
- * Pages only support the 'hidden' logic type since they are layout containers,
656
- * 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
+ * }
657
1019
  */
658
- interface PageLogicConfig {
659
- /** Only 'hidden' is supported for pages */
660
- type: 'hidden';
1020
+ interface SimplifiedArrayField extends FieldDef<never> {
1021
+ type: 'array';
661
1022
  /**
662
- * Condition that determines when this page is hidden.
663
- * 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)
664
1026
  */
665
- 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;
666
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
+
667
1068
  /**
668
1069
  * Page field interface for creating top-level page layouts
669
1070
  * This is a special field type that contains other field definitions
@@ -707,7 +1108,7 @@ interface PageField<TFields extends readonly PageAllowedChildren[] = PageAllowed
707
1108
  * }
708
1109
  * ```
709
1110
  */
710
- readonly logic?: PageLogicConfig[];
1111
+ readonly logic?: ContainerLogicConfig[];
711
1112
  }
712
1113
  /**
713
1114
  * Type guard for PageField with proper type narrowing
@@ -874,19 +1275,52 @@ interface DebouncedStateLogicConfig extends BaseStateLogicConfig {
874
1275
  */
875
1276
  type StateLogicConfig = ImmediateStateLogicConfig | DebouncedStateLogicConfig;
876
1277
  /**
877
- * Base configuration for value derivation logic.
878
- *
879
- * All derivations target the field they are defined on (self-targeting).
880
- * This simplifies configuration by removing the need for `targetField` -
881
- * derivation logic is placed on the field that should receive the computed value.
1278
+ * Shared fields that appear on all derivation logic config variants.
882
1279
  *
883
1280
  * @internal
884
1281
  */
885
- interface BaseDerivationLogicConfig {
1282
+ interface SharedDerivationFields {
886
1283
  /**
887
1284
  * Logic type identifier for value derivation.
888
1285
  */
889
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;
890
1324
  /**
891
1325
  * Optional name for this derivation for debugging purposes.
892
1326
  *
@@ -934,74 +1368,199 @@ interface BaseDerivationLogicConfig {
934
1368
  */
935
1369
  condition?: ConditionalExpression | boolean;
936
1370
  /**
937
- * Static value to set on this field.
1371
+ * When true, the derivation stops running after the user manually
1372
+ * edits the target field.
938
1373
  *
939
- * Use when the derived value is a constant.
940
- * 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.
941
1380
  *
942
1381
  * @example
943
1382
  * ```typescript
944
- * value: '+1' // String
945
- * value: 100 // Number
946
- * value: true // Boolean
947
- * 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
+ * }
948
1392
  * ```
949
1393
  */
950
- value?: unknown;
1394
+ stopOnUserOverride?: boolean;
951
1395
  /**
952
- * JavaScript expression to evaluate for the derived value.
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.
953
1399
  *
954
- * Has access to `formValue` object containing all form values.
955
- * For array fields, `formValue` is scoped to the current array item.
956
- * Uses the same secure AST-based parser as other expressions.
957
- * Mutually exclusive with `value` and `functionName`.
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.
958
1403
  *
959
1404
  * @example
960
1405
  * ```typescript
961
- * expression: 'formValue.quantity * formValue.unitPrice'
962
- * expression: 'formValue.firstName + " " + formValue.lastName'
963
- * expression: 'formValue.price * (1 - formValue.discount / 100)'
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
+ * }
964
1417
  * ```
965
1418
  */
966
- expression?: string;
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';
967
1442
  /**
968
- * Name of a registered custom derivation function.
1443
+ * HTTP request configuration for server-driven derivations.
969
1444
  *
970
- * The function receives the evaluation context and returns the derived value.
971
- * Register functions in `customFnConfig.derivations`.
972
- * Mutually exclusive with `value` and `expression`.
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.
973
1449
  *
974
1450
  * @example
975
1451
  * ```typescript
976
- * functionName: 'getCurrencyForCountry'
977
- * functionName: 'calculateTax'
978
- * functionName: 'formatPhoneNumber'
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
+ * }
979
1469
  * ```
980
1470
  */
981
- functionName?: string;
1471
+ http: HttpRequestConfig;
1472
+ /**
1473
+ * Explicit field dependencies. Required for HTTP derivations to prevent
1474
+ * wildcard triggering on every keystroke.
1475
+ */
1476
+ dependsOn: string[];
982
1477
  /**
983
- * Explicit field dependencies for derivations.
1478
+ * Expression to extract the derived value from the HTTP response.
984
1479
  *
985
- * When using `functionName`, you can optionally specify which fields
986
- * the function depends on. This enables more efficient re-evaluation
987
- * by only triggering when specific dependencies change.
1480
+ * Evaluated via `ExpressionParser` with `{ response }` as the evaluation scope.
988
1481
  *
989
- * If not provided with `functionName`, defaults to all fields ('*').
990
- * For `expression`, dependencies are automatically extracted from the expression.
991
- * For `value`, no dependencies are needed (static value).
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`.
992
1508
  *
993
1509
  * @example
994
1510
  * ```typescript
995
- * // Only re-evaluate when country changes
996
1511
  * {
997
- * key: 'currency',
1512
+ * key: 'suggestedPrice',
998
1513
  * logic: [{
999
1514
  * type: 'derivation',
1000
- * functionName: 'getCurrencyForCountry',
1001
- * dependsOn: ['country']
1515
+ * source: 'asyncFunction',
1516
+ * asyncFunctionName: 'fetchSuggestedPrice',
1517
+ * dependsOn: ['productId', 'quantity'],
1002
1518
  * }]
1003
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;
1541
+ /**
1542
+ * JavaScript expression to evaluate for the derived value.
1543
+ *
1544
+ * Has access to `formValue` object containing all form values.
1545
+ * For array fields, `formValue` is scoped to the current array item.
1546
+ * Uses the same secure AST-based parser as other expressions.
1547
+ *
1548
+ * @example
1549
+ * ```typescript
1550
+ * expression: 'formValue.quantity * formValue.unitPrice'
1551
+ * expression: 'formValue.firstName + " " + formValue.lastName'
1552
+ * expression: 'formValue.price * (1 - formValue.discount / 100)'
1553
+ * ```
1554
+ */
1555
+ expression: string;
1556
+ /**
1557
+ * Explicit field dependencies for expressions.
1004
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
1005
1564
  * // Override automatic detection for complex expressions
1006
1565
  * {
1007
1566
  * key: 'total',
@@ -1014,41 +1573,144 @@ interface BaseDerivationLogicConfig {
1014
1573
  * ```
1015
1574
  */
1016
1575
  dependsOn?: string[];
1576
+ value?: never;
1577
+ functionName?: never;
1578
+ http?: never;
1579
+ asyncFunctionName?: never;
1580
+ responseExpression?: never;
1017
1581
  }
1018
1582
  /**
1019
- * Derivation logic that evaluates immediately on change (default).
1583
+ * Base for value derivations.
1020
1584
  *
1021
1585
  * @internal
1022
1586
  */
1023
- interface OnChangeDerivationLogicConfig extends BaseDerivationLogicConfig {
1587
+ interface ValueDerivationBase extends SharedDerivationFields {
1588
+ source?: never;
1024
1589
  /**
1025
- * Trigger for immediate evaluation.
1026
- * @default 'onChange'
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
+ * ```
1027
1601
  */
1028
- trigger?: 'onChange';
1029
- /** Not allowed for onChange trigger */
1030
- debounceMs?: never;
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;
1031
1615
  }
1032
1616
  /**
1033
- * Derivation logic that evaluates after a debounce period.
1034
- *
1035
- * Use this for self-transforms (lowercase, trim, format) to avoid
1036
- * interrupting the user while they're actively typing.
1617
+ * Base for custom sync function derivations.
1037
1618
  *
1038
1619
  * @internal
1039
1620
  */
1040
- interface DebouncedDerivationLogicConfig extends BaseDerivationLogicConfig {
1621
+ interface FunctionDerivationBase extends SharedDerivationFields {
1622
+ source?: never;
1041
1623
  /**
1042
- * Trigger for debounced evaluation.
1043
- * Evaluates after the value has stabilized for the debounce duration.
1624
+ * Name of a registered custom derivation function.
1625
+ *
1626
+ * The function receives the evaluation context and returns the derived value.
1627
+ * Register functions in `customFnConfig.derivations`.
1628
+ *
1629
+ * @example
1630
+ * ```typescript
1631
+ * functionName: 'getCurrencyForCountry'
1632
+ * functionName: 'calculateTax'
1633
+ * functionName: 'formatPhoneNumber'
1634
+ * ```
1044
1635
  */
1045
- trigger: 'debounced';
1636
+ functionName: string;
1046
1637
  /**
1047
- * Debounce duration in milliseconds.
1048
- * @default 500
1638
+ * Explicit field dependencies for function derivations.
1639
+ *
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.
1643
+ *
1644
+ * @example
1645
+ * ```typescript
1646
+ * // Only re-evaluate when country changes
1647
+ * {
1648
+ * key: 'currency',
1649
+ * logic: [{
1650
+ * type: 'derivation',
1651
+ * functionName: 'getCurrencyForCountry',
1652
+ * dependsOn: ['country']
1653
+ * }]
1654
+ * }
1655
+ * ```
1049
1656
  */
1050
- debounceMs?: number;
1657
+ dependsOn?: string[];
1658
+ value?: never;
1659
+ expression?: never;
1660
+ http?: never;
1661
+ asyncFunctionName?: never;
1662
+ responseExpression?: never;
1051
1663
  }
1664
+ /**
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).
1696
+ * @internal
1697
+ */
1698
+ type OnChangeValueDerivationLogicConfig = ValueDerivationBase & ImmediateDerivationTrigger;
1699
+ /**
1700
+ * Value derivation that evaluates after a debounce period.
1701
+ * @internal
1702
+ */
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;
1052
1714
  /**
1053
1715
  * Configuration for value derivation logic.
1054
1716
  *
@@ -1109,16 +1771,162 @@ interface DebouncedDerivationLogicConfig extends BaseDerivationLogicConfig {
1109
1771
  *
1110
1772
  * @public
1111
1773
  */
1112
- type DerivationLogicConfig = OnChangeDerivationLogicConfig | DebouncedDerivationLogicConfig;
1774
+ type DerivationLogicConfig = OnChangeHttpDerivationLogicConfig | DebouncedHttpDerivationLogicConfig | OnChangeAsyncFunctionDerivationLogicConfig | DebouncedAsyncFunctionDerivationLogicConfig | OnChangeExpressionDerivationLogicConfig | DebouncedExpressionDerivationLogicConfig | OnChangeValueDerivationLogicConfig | DebouncedValueDerivationLogicConfig | OnChangeFunctionDerivationLogicConfig | DebouncedFunctionDerivationLogicConfig;
1775
+ /**
1776
+ * Base configuration for property derivation logic.
1777
+ *
1778
+ * Property derivations compute values for field properties (like `minDate`, `options`,
1779
+ * `label`, `placeholder`) based on form values and external data. Unlike value derivations
1780
+ * which set the field's value, property derivations update component input properties.
1781
+ *
1782
+ * All property derivations are self-targeting: the logic is placed on the field whose
1783
+ * property should be derived.
1784
+ *
1785
+ * @internal
1786
+ */
1787
+ interface BasePropertyDerivationLogicConfig {
1788
+ /**
1789
+ * Logic type identifier for property derivation.
1790
+ */
1791
+ type: 'propertyDerivation';
1792
+ /**
1793
+ * The target property to set on the field component.
1794
+ *
1795
+ * **Depth limit (max 2 levels):** Only simple and single-dot-nested paths are
1796
+ * supported. Paths with 2+ dots (e.g., `'a.b.c'`) will throw a `DynamicFormError`
1797
+ * at runtime. This is an architectural constraint of the override merging strategy.
1798
+ *
1799
+ * Supported formats:
1800
+ * - Simple: `'minDate'`, `'options'`, `'label'`, `'placeholder'`
1801
+ * - Nested (1 dot): `'props.appearance'`, `'meta.autocomplete'`
1802
+ *
1803
+ * **Note:** There is no compile-time validation that the property name matches
1804
+ * an actual input on the field component. Typos will silently write to the
1805
+ * override store with no visible effect. A dev-mode warning is emitted when
1806
+ * the override key doesn't match any existing input property.
1807
+ *
1808
+ * @example
1809
+ * ```typescript
1810
+ * targetProperty: 'minDate' // ✅ Simple property
1811
+ * targetProperty: 'options' // ✅ Simple property
1812
+ * targetProperty: 'props.appearance' // ✅ Single-nested property
1813
+ * targetProperty: 'a.b.c' // ❌ Throws DynamicFormError (too deep)
1814
+ * ```
1815
+ */
1816
+ targetProperty: string;
1817
+ /**
1818
+ * Optional name for this derivation for debugging purposes.
1819
+ *
1820
+ * When provided, this name appears in property derivation debug logs.
1821
+ */
1822
+ debugName?: string;
1823
+ /**
1824
+ * Condition that determines when this property derivation applies.
1825
+ *
1826
+ * Defaults to `true` (always apply).
1827
+ *
1828
+ * Note: FormStateCondition is not supported for property derivations.
1829
+ */
1830
+ condition?: ConditionalExpression | boolean;
1831
+ /**
1832
+ * Static value to set on the target property.
1833
+ *
1834
+ * Mutually exclusive with `expression` and `functionName`.
1835
+ */
1836
+ value?: unknown;
1837
+ /**
1838
+ * JavaScript expression to evaluate for the derived property value.
1839
+ *
1840
+ * Has access to `formValue` object containing all form values.
1841
+ * For array fields, `formValue` is scoped to the current array item.
1842
+ *
1843
+ * Mutually exclusive with `value` and `functionName`.
1844
+ *
1845
+ * @example
1846
+ * ```typescript
1847
+ * expression: 'formValue.startDate'
1848
+ * expression: 'formValue.quantity > 10 ? "bulk" : "standard"'
1849
+ * ```
1850
+ */
1851
+ expression?: string;
1852
+ /**
1853
+ * Name of a registered custom property derivation function.
1854
+ *
1855
+ * Register functions in `customFnConfig.propertyDerivations`.
1856
+ * Mutually exclusive with `value` and `expression`.
1857
+ */
1858
+ functionName?: string;
1859
+ /**
1860
+ * Explicit field dependencies for property derivations.
1861
+ *
1862
+ * When using `functionName`, you can optionally specify which fields
1863
+ * the function depends on. If not provided with `functionName`, defaults
1864
+ * to all fields ('*').
1865
+ * For `expression`, dependencies are automatically extracted.
1866
+ * For `value`, no dependencies are needed (static value).
1867
+ */
1868
+ dependsOn?: string[];
1869
+ }
1870
+ /**
1871
+ * Property derivation that evaluates immediately on change (default).
1872
+ *
1873
+ * @internal
1874
+ */
1875
+ interface OnChangePropertyDerivationLogicConfig extends BasePropertyDerivationLogicConfig {
1876
+ /**
1877
+ * Trigger for immediate evaluation.
1878
+ * @default 'onChange'
1879
+ */
1880
+ trigger?: 'onChange';
1881
+ /** Not allowed for onChange trigger */
1882
+ debounceMs?: never;
1883
+ }
1884
+ /**
1885
+ * Property derivation that evaluates after a debounce period.
1886
+ *
1887
+ * @internal
1888
+ */
1889
+ interface DebouncedPropertyDerivationLogicConfig extends BasePropertyDerivationLogicConfig {
1890
+ /**
1891
+ * Trigger for debounced evaluation.
1892
+ */
1893
+ trigger: 'debounced';
1894
+ /**
1895
+ * Debounce duration in milliseconds.
1896
+ * @default 500
1897
+ */
1898
+ debounceMs?: number;
1899
+ }
1900
+ /**
1901
+ * Configuration for property derivation logic.
1902
+ *
1903
+ * @deprecated Use `DerivationLogicConfig` with `targetProperty` instead.
1904
+ * The unified `type: 'derivation'` with `targetProperty` provides the same
1905
+ * functionality with a simpler API surface.
1906
+ *
1907
+ * ```typescript
1908
+ * // Before (deprecated)
1909
+ * { type: 'propertyDerivation', targetProperty: 'minDate', expression: '...' }
1910
+ *
1911
+ * // After (preferred)
1912
+ * { type: 'derivation', targetProperty: 'minDate', expression: '...' }
1913
+ * ```
1914
+ *
1915
+ * Will be removed in a future minor version.
1916
+ *
1917
+ * @public
1918
+ */
1919
+ type PropertyDerivationLogicConfig = OnChangePropertyDerivationLogicConfig | DebouncedPropertyDerivationLogicConfig;
1113
1920
  /**
1114
1921
  * Union type for all logic configurations.
1115
1922
  *
1116
1923
  * - `StateLogicConfig`: For field state changes (hidden, readonly, disabled, required)
1117
1924
  * - `DerivationLogicConfig`: For value derivation
1925
+ * - `PropertyDerivationLogicConfig`: For property derivation
1118
1926
  *
1119
1927
  * @public
1120
1928
  */
1121
- type LogicConfig = StateLogicConfig | DerivationLogicConfig;
1929
+ type LogicConfig = StateLogicConfig | DerivationLogicConfig | PropertyDerivationLogicConfig;
1122
1930
  /**
1123
1931
  * Log level for derivation debug output.
1124
1932
  *
@@ -1138,6 +1946,221 @@ type DerivationLogLevel = 'none' | 'summary' | 'verbose';
1138
1946
  * @public
1139
1947
  */
1140
1948
  declare function isFormStateCondition(condition: StateLogicConfig['condition'] | DerivationLogicConfig['condition']): condition is FormStateCondition;
1949
+ /**
1950
+ * Type guard to check if a logic config is a PropertyDerivationLogicConfig.
1951
+ *
1952
+ * @deprecated Use `isDerivationLogicConfig(config) && hasTargetProperty(config)` instead.
1953
+ * Will be removed in a future minor version.
1954
+ *
1955
+ * @param config - The logic config to check
1956
+ * @returns true if the config is for property derivation
1957
+ *
1958
+ * @public
1959
+ */
1960
+ declare function isPropertyDerivationLogicConfig(config: LogicConfig): config is PropertyDerivationLogicConfig;
1961
+
1962
+ /**
1963
+ * Interface for dynamic forms logger implementations.
1964
+ * Allows custom logging integrations (Sentry, DataDog, etc.)
1965
+ */
1966
+ interface Logger {
1967
+ /**
1968
+ * Log a debug message.
1969
+ * Use for detailed diagnostic information during development.
1970
+ */
1971
+ debug(message: string, ...args: unknown[]): void;
1972
+ /**
1973
+ * Log an info message.
1974
+ * Use for general operational information.
1975
+ */
1976
+ info(message: string, ...args: unknown[]): void;
1977
+ /**
1978
+ * Log a warning message.
1979
+ * Use for potentially problematic situations that don't prevent operation.
1980
+ */
1981
+ warn(message: string, ...args: unknown[]): void;
1982
+ /**
1983
+ * Log an error message.
1984
+ * Use for error conditions that may affect functionality.
1985
+ */
1986
+ error(message: string, ...args: unknown[]): void;
1987
+ }
1988
+
1989
+ /**
1990
+ * Context for resolving button disabled state.
1991
+ *
1992
+ * @public
1993
+ */
1994
+ interface ButtonLogicContext {
1995
+ /** The form's FieldTree instance (supports both string and number keys for array indices) */
1996
+ form: FieldTree<unknown, string | number>;
1997
+ /** Form-level options */
1998
+ formOptions?: FormOptions;
1999
+ /** Field-level logic array (if provided) */
2000
+ fieldLogic?: LogicConfig[];
2001
+ /** Explicit disabled state from field definition */
2002
+ explicitlyDisabled?: boolean;
2003
+ /** Current page validity signal (for paged forms) */
2004
+ currentPageValid?: Signal<boolean>;
2005
+ /** Current form value for evaluating conditional expressions */
2006
+ formValue?: unknown;
2007
+ /** Optional logger for diagnostic output. Falls back to no-op logger if not provided. */
2008
+ logger?: Logger;
2009
+ }
2010
+ /**
2011
+ * Allowed logic types for non-form-bound elements (buttons, text fields, etc.).
2012
+ * These elements only support hidden and disabled states - not readonly or required
2013
+ * since they don't participate in form validation.
2014
+ *
2015
+ * @public
2016
+ */
2017
+ type NonFieldLogicType = 'hidden' | 'disabled';
2018
+ /**
2019
+ * Logic config restricted to types valid for non-form-bound elements.
2020
+ * This is a subset of LogicConfig that only includes hidden and disabled types.
2021
+ *
2022
+ * @public
2023
+ */
2024
+ type NonFieldLogicConfig = LogicConfig & {
2025
+ type: NonFieldLogicType;
2026
+ };
2027
+ /**
2028
+ * Context for resolving state (hidden/disabled) for non-form-bound elements.
2029
+ *
2030
+ * This is a generalized context that can be used by any field mapper (buttons, text fields,
2031
+ * or any non-form-bound elements) to evaluate logic from a `logic` array.
2032
+ *
2033
+ * Note: While this context accepts the full `LogicConfig[]` for flexibility, the resolver
2034
+ * functions only process hidden and disabled types. Other logic types (readonly, required,
2035
+ * derivation) are ignored for non-form-bound elements.
2036
+ *
2037
+ * @public
2038
+ */
2039
+ interface NonFieldLogicContext {
2040
+ /** The form's FieldTree instance (supports both string and number keys for array indices) */
2041
+ form: FieldTree<unknown, string | number>;
2042
+ /**
2043
+ * Field-level logic array containing conditions.
2044
+ * Accepts full LogicConfig[] for compatibility, but only hidden and disabled types are processed.
2045
+ */
2046
+ fieldLogic?: LogicConfig[];
2047
+ /** Explicit state from field definition (e.g., `hidden: true` or `disabled: true`) */
2048
+ explicitValue?: boolean;
2049
+ /** Current form value for evaluating conditional expressions */
2050
+ formValue?: unknown;
2051
+ /** Optional logger for diagnostic output. Falls back to no-op logger if not provided. */
2052
+ logger?: Logger;
2053
+ }
2054
+ /**
2055
+ * Resolves the disabled state for a submit button.
2056
+ *
2057
+ * The disabled state is determined by (in order of precedence):
2058
+ * 1. Explicit `disabled: true` on the field definition
2059
+ * 2. Field-level `logic` array (if present, overrides form-level defaults)
2060
+ * 3. Form-level `options.submitButton` defaults
2061
+ *
2062
+ * @param ctx - The button logic context
2063
+ * @returns A computed signal that returns true when the button should be disabled
2064
+ *
2065
+ * @example
2066
+ * ```typescript
2067
+ * const disabled = resolveSubmitButtonDisabled({
2068
+ * form: formInstance,
2069
+ * formOptions: config.options,
2070
+ * fieldLogic: buttonField.logic,
2071
+ * explicitlyDisabled: buttonField.disabled,
2072
+ * });
2073
+ *
2074
+ * // Use in template
2075
+ * <button [disabled]="disabled()">Submit</button>
2076
+ * ```
2077
+ *
2078
+ * @public
2079
+ */
2080
+ declare function resolveSubmitButtonDisabled(ctx: ButtonLogicContext): Signal<boolean>;
2081
+ /**
2082
+ * Resolves the disabled state for a next page button.
2083
+ *
2084
+ * The disabled state is determined by (in order of precedence):
2085
+ * 1. Explicit `disabled: true` on the field definition
2086
+ * 2. Field-level `logic` array (if present, overrides form-level defaults)
2087
+ * 3. Form-level `options.nextButton` defaults
2088
+ *
2089
+ * @param ctx - The button logic context
2090
+ * @returns A computed signal that returns true when the button should be disabled
2091
+ *
2092
+ * @example
2093
+ * ```typescript
2094
+ * const disabled = resolveNextButtonDisabled({
2095
+ * form: formInstance,
2096
+ * formOptions: config.options,
2097
+ * fieldLogic: buttonField.logic,
2098
+ * explicitlyDisabled: buttonField.disabled,
2099
+ * currentPageValid: pageOrchestrator.currentPageValid,
2100
+ * });
2101
+ * ```
2102
+ *
2103
+ * @public
2104
+ */
2105
+ declare function resolveNextButtonDisabled(ctx: ButtonLogicContext): Signal<boolean>;
2106
+ /**
2107
+ * Evaluates the hidden state for non-form-bound elements synchronously.
2108
+ *
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.
2111
+ *
2112
+ * The hidden state is determined by (in order of precedence):
2113
+ * 1. Explicit `hidden: true` on the field definition
2114
+ * 2. Field-level `logic` array with `type: 'hidden'` conditions
2115
+ *
2116
+ * @param ctx - The context containing form, logic array, and explicit hidden state
2117
+ * @returns true when the element should be hidden
2118
+ *
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.
2124
+ *
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.
2128
+ *
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
2131
+ *
2132
+ * @public
2133
+ */
2134
+ declare function resolveNonFieldHidden(ctx: NonFieldLogicContext): Signal<boolean>;
2135
+ /**
2136
+ * Evaluates the disabled state for non-form-bound elements synchronously.
2137
+ *
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.
2140
+ *
2141
+ * The disabled state is determined by (in order of precedence):
2142
+ * 1. Explicit `disabled: true` on the field definition
2143
+ * 2. Field-level `logic` array with `type: 'disabled'` conditions
2144
+ *
2145
+ * @param ctx - The context containing form, logic array, and explicit disabled state
2146
+ * @returns true when the element should be disabled
2147
+ *
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.
2153
+ *
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
2160
+ *
2161
+ * @public
2162
+ */
2163
+ declare function resolveNonFieldDisabled(ctx: NonFieldLogicContext): Signal<boolean>;
1141
2164
 
1142
2165
  /**
1143
2166
  * Text element type for rendering different HTML text elements
@@ -1155,8 +2178,12 @@ type TextProps = {
1155
2178
  */
1156
2179
  interface TextField extends FieldDef<TextProps> {
1157
2180
  type: 'text';
1158
- /** Logic rules for conditional visibility (hidden, readonly, disabled) */
1159
- readonly logic?: LogicConfig[];
2181
+ /**
2182
+ * Logic rules for conditional visibility.
2183
+ * Text fields only support 'hidden' logic type since they are display-only
2184
+ * and don't participate in form validation (no disabled/readonly/required).
2185
+ */
2186
+ readonly logic?: NonFieldLogicConfig[];
1160
2187
  }
1161
2188
 
1162
2189
  /**
@@ -1175,7 +2202,7 @@ interface FieldRegistryContainers {
1175
2202
  page: PageField;
1176
2203
  row: RowField;
1177
2204
  group: GroupField;
1178
- array: ArrayField;
2205
+ array: ArrayField | SimplifiedArrayField;
1179
2206
  }
1180
2207
  /**
1181
2208
  * Leaf fields registry - augment this interface to add custom leaf fields
@@ -1290,18 +2317,28 @@ type UnionToIntersection<U> = (U extends U ? (k: U) => void : never) extends (k:
1290
2317
  */
1291
2318
  type Depth = [never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
1292
2319
  /**
1293
- * Widens literal types to their primitive equivalents.
2320
+ * Widens literal types to their primitive equivalents, recursively for objects and arrays.
1294
2321
  * This prevents `as const` from over-narrowing types like `''` to literal `''` instead of `string`.
1295
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
+ *
1296
2330
  * @example
1297
2331
  * ```typescript
1298
2332
  * type A = Widen<''>; // string
1299
2333
  * type B = Widen<false>; // boolean
1300
2334
  * type C = Widen<42>; // number
1301
2335
  * type D = Widen<string[]>; // string[]
2336
+ * type E = Widen<{ name: 'Jane' }>; // { name: string }
1302
2337
  * ```
1303
2338
  */
1304
- 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;
1305
2342
  /**
1306
2343
  * Infer value type based on field type and props.
1307
2344
  * - Slider fields: always number
@@ -1325,6 +2362,21 @@ type MaybeOptional<T, V> = T extends {
1325
2362
  } ? V : T extends {
1326
2363
  required: true;
1327
2364
  } ? V : V | undefined;
2365
+ /**
2366
+ * Infers the value type for an array field.
2367
+ * Handles three cases:
2368
+ * 1. Simplified array with template + value → infer item type from value
2369
+ * 2. Simplified array with template only → unknown[]
2370
+ * 3. Full-API array with fields → infer from children
2371
+ */
2372
+ type InferArrayValue<T, D extends number> = T extends {
2373
+ template: unknown;
2374
+ value: readonly (infer Item)[];
2375
+ } ? Widen<Item>[] : T extends {
2376
+ template: unknown;
2377
+ } ? unknown[] : T extends {
2378
+ fields: infer F;
2379
+ } ? F extends RegisteredFieldTypes[] ? InferFormValueWithDepth<F, Depth[D]>[] : unknown[] : unknown[];
1328
2380
  /**
1329
2381
  * Process a single field and determine its contribution to the form value type
1330
2382
  */
@@ -1342,11 +2394,8 @@ type ProcessField<T, D extends number = 5> = [D] extends [never] ? Record<string
1342
2394
  } : never : T extends {
1343
2395
  type: 'array';
1344
2396
  key: infer K;
1345
- fields: infer F;
1346
- } ? K extends string ? F extends RegisteredFieldTypes[] ? {
1347
- [P in K]: InferFormValueWithDepth<F, Depth[D]>[];
1348
- } : {
1349
- [P in K]: unknown[];
2397
+ } ? K extends string ? {
2398
+ [P in K]: InferArrayValue<T, D>;
1350
2399
  } : never : T extends {
1351
2400
  type: 'text';
1352
2401
  } ? never : T extends {
@@ -1399,6 +2448,31 @@ type ExtractFields<T> = T extends {
1399
2448
  */
1400
2449
  type InferFormValue$1<T> = ExtractFields<T> extends readonly RegisteredFieldTypes[] ? InferFormValueWithDepth<ExtractFields<T> extends RegisteredFieldTypes[] ? ExtractFields<T> : [...ExtractFields<T>], 5> : never;
1401
2450
 
2451
+ /**
2452
+ * Mapping configuration for interpreting HTTP responses as validation results.
2453
+ *
2454
+ * Expressions in `validWhen` and `errorParams` are evaluated with scope `{ response }` only —
2455
+ * they do NOT have access to `formValue`, `fieldValue`, or the full `EvaluationContext`.
2456
+ * This is intentional for v1: response mapping is purely about interpreting the HTTP response.
2457
+ */
2458
+ interface HttpValidationResponseMapping {
2459
+ /**
2460
+ * Expression evaluated with scope `{ response }`. Truthy = valid (no error).
2461
+ *
2462
+ * NOTE: This is the OPPOSITE of the function-based HttpCustomValidator.onSuccess
2463
+ * convention, which maps successful HTTP responses to validation errors.
2464
+ * Here, truthy means "validation passed".
2465
+ */
2466
+ validWhen: string;
2467
+ /** Error kind returned when validation fails — maps to field-level `validationMessages` */
2468
+ errorKind: string;
2469
+ /**
2470
+ * Parameters to include in the validation error for message interpolation.
2471
+ * Map of parameter names to expressions evaluated against `{ response }`.
2472
+ */
2473
+ errorParams?: Record<string, string>;
2474
+ }
2475
+
1402
2476
  /**
1403
2477
  * Base configuration shared by all validators
1404
2478
  */
@@ -1450,34 +2524,80 @@ interface CustomValidatorConfig extends BaseValidatorConfig {
1450
2524
  errorParams?: Record<string, string>;
1451
2525
  }
1452
2526
  /**
1453
- * Async custom validator configuration using Angular's validateAsync API
1454
- * Returns Observable<ValidationError | ValidationError[] | null>
2527
+ * Async custom validator configuration using Angular's validateAsync API.
2528
+ * Returns Observable<ValidationError | ValidationError[] | null>.
2529
+ *
2530
+ * Accepts both `'async'` (preferred) and `'customAsync'` (deprecated) type literals.
1455
2531
  */
1456
2532
  interface AsyncValidatorConfig extends BaseValidatorConfig {
1457
- /** Validator type identifier */
1458
- type: 'customAsync';
2533
+ /**
2534
+ * Validator type identifier.
2535
+ *
2536
+ * Use `'async'` for new code. `'customAsync'` is accepted for backward compatibility
2537
+ * but emits a deprecation warning in dev mode.
2538
+ */
2539
+ type: 'async' | 'customAsync';
1459
2540
  /** Name of registered async validator function */
1460
2541
  functionName: string;
1461
2542
  /** Optional parameters to pass to validator function */
1462
2543
  params?: Record<string, unknown>;
1463
2544
  }
1464
2545
  /**
1465
- * HTTP validator configuration using Angular's validateHttp API
1466
- * Provides optimized HTTP validation with automatic request cancellation
2546
+ * Function-based HTTP validator configuration — requires a registered function.
2547
+ *
2548
+ * Uses Angular's `validateHttp` API. The function is registered via
2549
+ * `customFnConfig.httpValidators`.
2550
+ *
2551
+ * Discriminated from `DeclarativeHttpValidatorConfig` by the presence of `functionName`.
2552
+ *
2553
+ * Accepts both `'http'` (preferred) and `'customHttp'` (deprecated) type literals.
2554
+ *
2555
+ * @deprecated Prefer `DeclarativeHttpValidatorConfig` (`type: 'http'` with `http` + `responseMapping`)
2556
+ * for fully JSON-serializable validation. Use this form only when function registration is required.
1467
2557
  */
1468
- interface HttpValidatorConfig extends BaseValidatorConfig {
1469
- /** Validator type identifier */
1470
- type: 'customHttp';
2558
+ interface FunctionHttpValidatorConfig extends BaseValidatorConfig {
2559
+ /**
2560
+ * Validator type identifier.
2561
+ *
2562
+ * Use `'http'` for new code. `'customHttp'` is accepted for backward compatibility
2563
+ * but emits a deprecation warning in dev mode.
2564
+ */
2565
+ type: 'http' | 'customHttp';
1471
2566
  /** Name of registered HTTP validator configuration */
1472
2567
  functionName: string;
1473
2568
  /** Optional parameters to pass to HTTP validator */
1474
2569
  params?: Record<string, unknown>;
1475
2570
  }
1476
2571
  /**
1477
- * Configuration for signal forms validator functions that can be serialized from API
1478
- * Discriminated union type for type-safe validator configuration
2572
+ * @deprecated Use `FunctionHttpValidatorConfig` instead. Will be removed in a future minor version.
2573
+ */
2574
+ type HttpValidatorConfig = FunctionHttpValidatorConfig;
2575
+ /**
2576
+ * Declarative HTTP validator configuration — fully JSON-serializable, no function registration required.
2577
+ *
2578
+ * Uses `HttpRequestConfig` to define the HTTP request and `HttpValidationResponseMapping`
2579
+ * to interpret the response as a validation result. Powered by Angular's `validateHttp` API.
2580
+ *
2581
+ * Discriminated from `FunctionHttpValidatorConfig` by the presence of `http` + `responseMapping`
2582
+ * (and absence of `functionName`).
1479
2583
  */
1480
- type ValidatorConfig = BuiltInValidatorConfig | CustomValidatorConfig | AsyncValidatorConfig | HttpValidatorConfig;
2584
+ interface DeclarativeHttpValidatorConfig extends BaseValidatorConfig {
2585
+ /** Validator type identifier */
2586
+ type: 'http';
2587
+ /** HTTP request configuration with expression-based query params and body */
2588
+ http: HttpRequestConfig;
2589
+ /** Mapping that interprets the HTTP response as a validation result */
2590
+ responseMapping: HttpValidationResponseMapping;
2591
+ }
2592
+ /**
2593
+ * Configuration for signal forms validator functions that can be serialized from API.
2594
+ * Discriminated union type for type-safe validator configuration.
2595
+ *
2596
+ * Note: `FunctionHttpValidatorConfig` and `DeclarativeHttpValidatorConfig` both use `type: 'http'`.
2597
+ * They are discriminated by property presence: `functionName` → function-based, `http` → declarative.
2598
+ * Use `isFunctionHttpValidator()` for type-safe narrowing when `type` alone is insufficient.
2599
+ */
2600
+ type ValidatorConfig = BuiltInValidatorConfig | CustomValidatorConfig | AsyncValidatorConfig | FunctionHttpValidatorConfig | DeclarativeHttpValidatorConfig;
1481
2601
 
1482
2602
  /**
1483
2603
  * Configuration for applying predefined schemas
@@ -1774,31 +2894,74 @@ interface HttpCustomValidator<TValue = unknown, TResult = unknown> {
1774
2894
  }
1775
2895
 
1776
2896
  /**
1777
- * Interface for dynamic forms logger implementations.
1778
- * Allows custom logging integrations (Sentry, DataDog, etc.)
2897
+ * Tracks deprecated API usages that have already been warned about to prevent log spam.
2898
+ * Scoped to a single form instance via DI.
2899
+ *
2900
+ * This follows the same pattern as DerivationWarningTracker — DI-scoped rather than
2901
+ * module-scoped to avoid SSR issues where state would be shared across requests.
2902
+ *
2903
+ * @public
2904
+ */
2905
+ interface DeprecationWarningTracker {
2906
+ /** Set of deprecation keys we've already warned about */
2907
+ warnedKeys: Set<string>;
2908
+ }
2909
+
2910
+ /**
2911
+ * Represents the state information of a single form field.
2912
+ *
2913
+ * Used in evaluation contexts to allow expressions and conditions
2914
+ * to reference field state (e.g., `fieldState.touched`, `formFieldState.email.dirty`).
2915
+ *
2916
+ * @public
1779
2917
  */
1780
- interface Logger {
1781
- /**
1782
- * Log a debug message.
1783
- * Use for detailed diagnostic information during development.
1784
- */
1785
- debug(message: string, ...args: unknown[]): void;
2918
+ interface FieldStateInfo {
2919
+ readonly touched: boolean;
2920
+ readonly dirty: boolean;
2921
+ /** Convenience property: equivalent to `!dirty` */
2922
+ readonly pristine: boolean;
2923
+ readonly valid: boolean;
2924
+ readonly invalid: boolean;
2925
+ readonly pending: boolean;
1786
2926
  /**
1787
- * Log an info message.
1788
- * Use for general operational information.
2927
+ * Whether the field is currently hidden.
2928
+ *
2929
+ * Note: This is a library-managed property, not a native Angular Signal Forms
2930
+ * property. Returns `false` if the field instance does not expose a `hidden` signal.
1789
2931
  */
1790
- info(message: string, ...args: unknown[]): void;
2932
+ readonly hidden: boolean;
1791
2933
  /**
1792
- * Log a warning message.
1793
- * Use for potentially problematic situations that don't prevent operation.
2934
+ * Whether the field is currently readonly.
2935
+ *
2936
+ * Note: This is a library-managed property, not a native Angular Signal Forms
2937
+ * property. Returns `false` if the field instance does not expose a `readonly` signal.
1794
2938
  */
1795
- warn(message: string, ...args: unknown[]): void;
2939
+ readonly readonly: boolean;
1796
2940
  /**
1797
- * Log an error message.
1798
- * Use for error conditions that may affect functionality.
2941
+ * Whether the field is currently disabled.
2942
+ *
2943
+ * Note: This is a library-managed property, not a native Angular Signal Forms
2944
+ * property. Returns `false` if the field instance does not expose a `disabled` signal.
1799
2945
  */
1800
- error(message: string, ...args: unknown[]): void;
2946
+ readonly disabled: boolean;
1801
2947
  }
2948
+ /**
2949
+ * Map of field keys to their state information.
2950
+ *
2951
+ * Used as `formFieldState` in evaluation contexts to access
2952
+ * state of any field in the form by key.
2953
+ *
2954
+ * @public
2955
+ */
2956
+ type FormFieldStateMap = Record<string, FieldStateInfo | undefined>;
2957
+ /**
2958
+ * Field state context for the current field being evaluated.
2959
+ *
2960
+ * Used as `fieldState` in evaluation contexts.
2961
+ *
2962
+ * @public
2963
+ */
2964
+ type FieldStateContext = FieldStateInfo;
1802
2965
 
1803
2966
  interface EvaluationContext<TValue = unknown> {
1804
2967
  /** Current field value */
@@ -1871,6 +3034,43 @@ interface EvaluationContext<TValue = unknown> {
1871
3034
  * ```
1872
3035
  */
1873
3036
  externalData?: Record<string, unknown>;
3037
+ /**
3038
+ * DI-scoped tracker for deprecation warnings.
3039
+ * Used by the condition evaluator to deduplicate deprecation warnings.
3040
+ */
3041
+ deprecationTracker?: DeprecationWarningTracker;
3042
+ /**
3043
+ * State of the current field being evaluated.
3044
+ *
3045
+ * Provides access to field state properties like `touched`, `dirty`, `valid`, etc.
3046
+ * Uses a Proxy for lazy evaluation — properties are only read when accessed.
3047
+ *
3048
+ * @example
3049
+ * ```typescript
3050
+ * // In a JavaScript expression:
3051
+ * condition: {
3052
+ * type: 'javascript',
3053
+ * expression: "fieldState.touched && fieldState.dirty"
3054
+ * }
3055
+ * ```
3056
+ */
3057
+ fieldState?: FieldStateContext;
3058
+ /**
3059
+ * State of all fields in the form, keyed by field key.
3060
+ *
3061
+ * Provides access to any field's state properties by key.
3062
+ * Uses a Proxy for lazy evaluation — field state is only read when accessed.
3063
+ *
3064
+ * @example
3065
+ * ```typescript
3066
+ * // In a JavaScript expression:
3067
+ * condition: {
3068
+ * type: 'javascript',
3069
+ * expression: "formFieldState.email.dirty && formFieldState.email.valid"
3070
+ * }
3071
+ * ```
3072
+ */
3073
+ formFieldState?: FormFieldStateMap;
1874
3074
  /** Allow additional properties for flexible expression evaluation */
1875
3075
  [key: string]: unknown;
1876
3076
  }
@@ -1916,6 +3116,43 @@ interface EvaluationContext<TValue = unknown> {
1916
3116
  */
1917
3117
  type CustomFunction = (context: EvaluationContext) => unknown;
1918
3118
 
3119
+ /**
3120
+ * Async function for derivations — returns any value.
3121
+ *
3122
+ * The function receives the evaluation context and may return a Promise or Observable.
3123
+ * Register functions in `customFnConfig.asyncDerivations`.
3124
+ *
3125
+ * @example
3126
+ * ```typescript
3127
+ * const fetchPrice: AsyncDerivationFunction = async (context) => {
3128
+ * const response = await fetch(`/api/price?product=${context.formValue.productId}`);
3129
+ * const data = await response.json();
3130
+ * return data.price;
3131
+ * };
3132
+ * ```
3133
+ *
3134
+ * @public
3135
+ */
3136
+ type AsyncDerivationFunction = (context: EvaluationContext) => Promise<unknown> | Observable<unknown>;
3137
+ /**
3138
+ * Async function for conditions — returns boolean.
3139
+ *
3140
+ * The function receives the evaluation context and may return a Promise or Observable.
3141
+ * Register functions in `customFnConfig.asyncConditions`.
3142
+ *
3143
+ * @example
3144
+ * ```typescript
3145
+ * const checkPermission: AsyncConditionFunction = async (context) => {
3146
+ * const response = await fetch(`/api/permissions?user=${context.formValue.userId}`);
3147
+ * const data = await response.json();
3148
+ * return data.canEdit;
3149
+ * };
3150
+ * ```
3151
+ *
3152
+ * @public
3153
+ */
3154
+ type AsyncConditionFunction = (context: EvaluationContext) => Promise<boolean> | Observable<boolean>;
3155
+
1919
3156
  /**
1920
3157
  * Re-export Angular's ValidationError for consistency
1921
3158
  * This replaces our custom ValidationError interface
@@ -2421,6 +3658,103 @@ interface CustomFnConfig {
2421
3658
  * ```
2422
3659
  */
2423
3660
  derivations?: Record<string, CustomFunction>;
3661
+ /**
3662
+ * @deprecated Register functions under `derivations` instead. Use `type: 'derivation'`
3663
+ * with `targetProperty` in field logic configs. Will be removed in a future minor version.
3664
+ *
3665
+ * Custom property derivation functions for reactive property updates.
3666
+ *
3667
+ * These functions compute derived values for field properties (like `minDate`,
3668
+ * `options`, `label`, `placeholder`) and are called when a
3669
+ * `PropertyDerivationLogicConfig` references them by `functionName`.
3670
+ *
3671
+ * Property derivation functions:
3672
+ * - Receive an `EvaluationContext` with access to `formValue`
3673
+ * - Return the value to set on the target property
3674
+ * - Are called reactively when dependencies change
3675
+ *
3676
+ * @example
3677
+ * ```typescript
3678
+ * propertyDerivations: {
3679
+ * getCitiesForCountry: (context) => {
3680
+ * const countryCities: Record<string, Array<{ label: string; value: string }>> = {
3681
+ * 'USA': [{ label: 'New York', value: 'ny' }, { label: 'LA', value: 'la' }],
3682
+ * 'Germany': [{ label: 'Berlin', value: 'berlin' }, { label: 'Munich', value: 'munich' }],
3683
+ * };
3684
+ * return countryCities[context.formValue.country as string] ?? [];
3685
+ * },
3686
+ * }
3687
+ * ```
3688
+ *
3689
+ * Field configuration using a property derivation function:
3690
+ * ```typescript
3691
+ * {
3692
+ * key: 'city',
3693
+ * type: 'select',
3694
+ * logic: [{
3695
+ * type: 'propertyDerivation',
3696
+ * targetProperty: 'options',
3697
+ * functionName: 'getCitiesForCountry',
3698
+ * dependsOn: ['country']
3699
+ * }]
3700
+ * }
3701
+ * ```
3702
+ */
3703
+ propertyDerivations?: Record<string, CustomFunction>;
3704
+ /**
3705
+ * Async derivation functions for asynchronous value derivation logic.
3706
+ *
3707
+ * These functions perform asynchronous operations (service calls, complex pipelines)
3708
+ * and return the derived value via a Promise or Observable. They are called when a
3709
+ * `DerivationLogicConfig` references them by `asyncFunctionName`.
3710
+ *
3711
+ * Async derivation functions:
3712
+ * - Receive an `EvaluationContext` with access to `formValue`
3713
+ * - Return a Promise or Observable of the value to set on the target field
3714
+ * - Handle their own I/O (no HttpClient provided — use injected services)
3715
+ * - Require explicit `dependsOn` to avoid triggering on every form change
3716
+ *
3717
+ * @example
3718
+ * ```typescript
3719
+ * asyncDerivations: {
3720
+ * fetchSuggestedPrice: async (context) => {
3721
+ * const response = await fetch(`/api/price?product=${context.formValue.productId}`);
3722
+ * const data = await response.json();
3723
+ * return data.suggestedPrice;
3724
+ * },
3725
+ * lookupAddress: (context) => {
3726
+ * return addressService.lookup(context.formValue.zipCode).pipe(
3727
+ * map(result => result.formattedAddress)
3728
+ * );
3729
+ * },
3730
+ * }
3731
+ * ```
3732
+ */
3733
+ asyncDerivations?: Record<string, AsyncDerivationFunction>;
3734
+ /**
3735
+ * Async condition functions for asynchronous field state logic.
3736
+ *
3737
+ * These functions perform asynchronous operations and return a boolean
3738
+ * indicating whether the condition is met. They are referenced by
3739
+ * `asyncFunctionName` in `AsyncCondition` expressions.
3740
+ *
3741
+ * Async condition functions:
3742
+ * - Receive an `EvaluationContext` with access to `formValue`
3743
+ * - Return a Promise or Observable of boolean
3744
+ * - Handle their own I/O (use injected services)
3745
+ *
3746
+ * @example
3747
+ * ```typescript
3748
+ * asyncConditions: {
3749
+ * checkPermission: async (context) => {
3750
+ * const response = await fetch(`/api/permissions?role=${context.formValue.role}`);
3751
+ * const data = await response.json();
3752
+ * return data.canEdit;
3753
+ * },
3754
+ * }
3755
+ * ```
3756
+ */
3757
+ asyncConditions?: Record<string, AsyncConditionFunction>;
2424
3758
  /**
2425
3759
  * Custom validators using Angular's public FieldContext API
2426
3760
  *
@@ -2538,6 +3872,10 @@ interface CustomFnConfig {
2538
3872
  */
2539
3873
  asyncValidators?: Record<string, AsyncCustomValidator>;
2540
3874
  /**
3875
+ * @deprecated Prefer declarative HTTP validators using `type: 'http'` with `http` + `responseMapping`
3876
+ * in the validators array. Function-based HTTP validators now also use `type: 'http'` with `functionName`.
3877
+ * Will be removed in a future minor version.
3878
+ *
2541
3879
  * HTTP validators using Angular's validateHttp() API
2542
3880
  *
2543
3881
  * Angular's validateHttp provides HTTP validation with automatic request
@@ -2680,233 +4018,128 @@ interface FormOptions {
2680
4018
  */
2681
4019
  nextButton?: NextButtonOptions;
2682
4020
  /**
2683
- * Whether to attach the current form value to all events dispatched through the EventBus.
4021
+ * Whether to exclude values of hidden fields from submission output.
2684
4022
  *
2685
- * This per-form setting overrides the global `withEventFormValue()` feature:
2686
- * - `true` - Enable form value emission for this form (even if globally disabled)
2687
- * - `false` - Disable form value emission for this form (even if globally enabled)
2688
- * - `undefined` - Use global setting (default)
2689
- *
2690
- * When enabled, events will include a `formValue` property with the current form state.
2691
- * Use the `hasFormValue()` type guard to safely access this property.
2692
- *
2693
- * @example
2694
- * ```typescript
2695
- * // Enable for this form only (no global withEventFormValue() needed)
2696
- * const config: FormConfig = {
2697
- * fields: [...],
2698
- * options: { emitFormValueOnEvents: true }
2699
- * };
2700
- *
2701
- * // Disable for this form (when globally enabled)
2702
- * const config: FormConfig = {
2703
- * fields: [...],
2704
- * options: { emitFormValueOnEvents: false }
2705
- * };
2706
- * ```
4023
+ * Overrides the global `withValueExclusionDefaults()` setting for this form.
4024
+ * Can be further overridden per-field on individual `FieldDef` entries.
2707
4025
  *
2708
4026
  * @default undefined (uses global setting)
2709
4027
  */
2710
- emitFormValueOnEvents?: boolean;
2711
- }
2712
- /**
2713
- * Options for controlling submit button disabled behavior.
2714
- *
2715
- * @public
2716
- */
2717
- interface SubmitButtonOptions {
4028
+ excludeValueIfHidden?: boolean;
2718
4029
  /**
2719
- * Disable submit button when the form is invalid.
4030
+ * Whether to exclude values of disabled fields from submission output.
2720
4031
  *
2721
- * @default true
4032
+ * Overrides the global `withValueExclusionDefaults()` setting for this form.
4033
+ * Can be further overridden per-field on individual `FieldDef` entries.
4034
+ *
4035
+ * @default undefined (uses global setting)
2722
4036
  */
2723
- disableWhenInvalid?: boolean;
4037
+ excludeValueIfDisabled?: boolean;
2724
4038
  /**
2725
- * Disable submit button while the form is submitting.
4039
+ * Whether to exclude values of readonly fields from submission output.
2726
4040
  *
2727
- * Requires `submission.action` to be configured for automatic detection.
4041
+ * Overrides the global `withValueExclusionDefaults()` setting for this form.
4042
+ * Can be further overridden per-field on individual `FieldDef` entries.
2728
4043
  *
2729
- * @default true
4044
+ * @default undefined (uses global setting)
2730
4045
  */
2731
- disableWhileSubmitting?: boolean;
2732
- }
2733
- /**
2734
- * Options for controlling next page button disabled behavior.
2735
- *
2736
- * @public
2737
- */
2738
- interface NextButtonOptions {
4046
+ excludeValueIfReadonly?: boolean;
2739
4047
  /**
2740
- * Disable next button when the current page has invalid fields.
4048
+ * Whether to attach the current form value to all events dispatched through the EventBus.
2741
4049
  *
2742
- * @default true
2743
- */
2744
- disableWhenPageInvalid?: boolean;
2745
- /**
2746
- * Disable next button while the form is submitting.
4050
+ * This per-form setting overrides the global `withEventFormValue()` feature:
4051
+ * - `true` - Enable form value emission for this form (even if globally disabled)
4052
+ * - `false` - Disable form value emission for this form (even if globally enabled)
4053
+ * - `undefined` - Use global setting (default)
2747
4054
  *
2748
- * @default true
2749
- */
2750
- disableWhileSubmitting?: boolean;
2751
- }
2752
-
2753
- /**
2754
- * Form mode enumeration distinguishing between paged and non-paged forms
2755
- */
2756
- type FormMode = 'paged' | 'non-paged';
2757
- /**
2758
- * Result of form mode detection with validation details
2759
- */
2760
- interface FormModeDetectionResult {
2761
- /** The detected form mode */
2762
- mode: FormMode;
2763
- /** Whether the form configuration is valid for the detected mode */
2764
- isValid: boolean;
2765
- /** Array of validation errors if form is invalid */
2766
- errors: string[];
2767
- }
2768
-
2769
- /**
2770
- * Type guard to check if a field is a container field (page, row, group, or array)
2771
- * Container fields have a 'fields' property and don't contribute values directly.
2772
- * This overload works with RegisteredFieldTypes for full type narrowing.
2773
- */
2774
- declare function isContainerField(field: RegisteredFieldTypes): field is ContainerFieldTypes;
2775
- /**
2776
- * Type guard to check if a field is a container field (page, row, group, or array).
2777
- * This overload works with any FieldDef for looser type checking.
2778
- */
2779
- declare function isContainerField(field: FieldDef<unknown>): boolean;
2780
- /**
2781
- * Type guard to check if a field is a leaf field (value or display field)
2782
- * Leaf fields don't have children and either contribute values or display content
2783
- */
2784
- declare function isLeafField(field: RegisteredFieldTypes): field is LeafFieldTypes;
2785
- /**
2786
- * Type guard to check if a field has a value property (value-bearing field)
2787
- * These fields contribute to the form value output
2788
- * Note: Using `unknown` in the Extract condition to match any value type
2789
- */
2790
- declare function isValueBearingField(field: RegisteredFieldTypes): field is Extract<RegisteredFieldTypes, {
2791
- value: unknown;
2792
- }>;
2793
- /**
2794
- * Type guard to check if a field is excluded from form values (display-only field)
2795
- * Currently this includes text fields and any other fields without a value property
2796
- */
2797
- declare function isDisplayOnlyField(field: RegisteredFieldTypes): boolean;
2798
-
2799
- /**
2800
- * Helper types for creating type-safe field configurations with proper nesting constraints
2801
- */
2802
-
2803
- /**
2804
- * Type helper for accessing nested field paths safely
2805
- * This allows accessing child paths while maintaining some type safety
2806
- */
2807
- type FieldPathAccess<TValue> = {
2808
- [K in keyof TValue]: SchemaPath<TValue[K]> | SchemaPathTree<TValue[K]>;
2809
- };
2810
-
2811
- /**
2812
- * Result of processing all pending derivations.
2813
- *
2814
- * @public
2815
- */
2816
- interface DerivationProcessingResult {
2817
- /** Number of derivations successfully applied */
2818
- appliedCount: number;
2819
- /** Number of derivations skipped (condition not met or value unchanged) */
2820
- skippedCount: number;
2821
- /** Number of derivations that failed with errors */
2822
- errorCount: number;
2823
- /**
2824
- * Number of derivations that encountered warnings (e.g., missing target field).
4055
+ * When enabled, events will include a `formValue` property with the current form state.
4056
+ * Use the `hasFormValue()` type guard to safely access this property.
2825
4057
  *
2826
- * These are not counted as errors because they may be intentional (conditional fields)
2827
- * but indicate potential configuration issues if unexpected.
4058
+ * @example
4059
+ * ```typescript
4060
+ * // Enable for this form only (no global withEventFormValue() needed)
4061
+ * const config: FormConfig = {
4062
+ * fields: [...],
4063
+ * options: { emitFormValueOnEvents: true }
4064
+ * };
4065
+ *
4066
+ * // Disable for this form (when globally enabled)
4067
+ * const config: FormConfig = {
4068
+ * fields: [...],
4069
+ * options: { emitFormValueOnEvents: false }
4070
+ * };
4071
+ * ```
4072
+ *
4073
+ * @default undefined (uses global setting)
2828
4074
  */
2829
- warnCount: number;
2830
- /** Total iterations performed */
2831
- iterations: number;
2832
- /** Whether max iterations was reached (potential loop) */
2833
- maxIterationsReached: boolean;
4075
+ emitFormValueOnEvents?: boolean;
2834
4076
  }
2835
-
2836
4077
  /**
2837
- * Information about a single derivation evaluation for logging.
4078
+ * Options for controlling submit button disabled behavior.
2838
4079
  *
2839
4080
  * @public
2840
4081
  */
2841
- interface DerivationLogEntry {
2842
- /** Debug name if provided in the derivation config */
2843
- debugName?: string;
2844
- /** Field key where the derivation is defined and targets */
2845
- fieldKey: string;
2846
- /** Result of the derivation evaluation */
2847
- result: 'applied' | 'skipped' | 'error';
2848
- /** Reason for skipping (if result is 'skipped') */
2849
- skipReason?: 'condition-false' | 'value-unchanged' | 'already-applied' | 'target-not-found';
2850
- /** The new value that was set (if result is 'applied') */
2851
- newValue?: unknown;
2852
- /** The previous value before the derivation (if result is 'applied') */
2853
- previousValue?: unknown;
2854
- /** Error message (if result is 'error') */
2855
- error?: string;
2856
- /** Duration in milliseconds (optional, for performance tracking) */
2857
- durationMs?: number;
4082
+ interface SubmitButtonOptions {
4083
+ /**
4084
+ * Disable submit button when the form is invalid.
4085
+ *
4086
+ * @default true
4087
+ */
4088
+ disableWhenInvalid?: boolean;
4089
+ /**
4090
+ * Disable submit button while the form is submitting.
4091
+ *
4092
+ * Requires `submission.action` to be configured for automatic detection.
4093
+ *
4094
+ * @default true
4095
+ */
4096
+ disableWhileSubmitting?: boolean;
2858
4097
  }
2859
-
2860
4098
  /**
2861
- * Service interface for derivation logging.
2862
- *
2863
- * Provides methods to log derivation processing events at different verbosity levels.
2864
- * Use `DERIVATION_LOGGER` injection token to get an instance.
4099
+ * Options for controlling next page button disabled behavior.
2865
4100
  *
2866
4101
  * @public
2867
4102
  */
2868
- interface DerivationLogger {
2869
- /**
2870
- * Logs the start of a derivation processing cycle.
2871
- * Only logs when config level is 'verbose'.
2872
- */
2873
- cycleStart(trigger: 'onChange' | 'debounced', entryCount: number): void;
2874
- /**
2875
- * Logs the start of a derivation iteration within a cycle.
2876
- * Only logs when config level is 'verbose'.
2877
- */
2878
- iteration(iterationNumber: number): void;
2879
- /**
2880
- * Logs a single derivation evaluation result.
2881
- * Only logs when config level is 'verbose'.
2882
- */
2883
- evaluation(entry: DerivationLogEntry): void;
4103
+ interface NextButtonOptions {
2884
4104
  /**
2885
- * Logs a summary of derivation processing results.
2886
- * Logs when config level is 'summary' or 'verbose' and something interesting happened.
4105
+ * Disable next button when the current page has invalid fields.
4106
+ *
4107
+ * @default true
2887
4108
  */
2888
- summary(result: DerivationProcessingResult, trigger: 'onChange' | 'debounced'): void;
4109
+ disableWhenPageInvalid?: boolean;
2889
4110
  /**
2890
- * Logs when max iterations are reached (always logged as warning).
4111
+ * Disable next button while the form is submitting.
4112
+ *
4113
+ * @default true
2891
4114
  */
2892
- maxIterationsReached(result: DerivationProcessingResult, trigger: 'onChange' | 'debounced'): void;
4115
+ disableWhileSubmitting?: boolean;
4116
+ }
4117
+
4118
+ /**
4119
+ * Form mode enumeration distinguishing between paged and non-paged forms
4120
+ */
4121
+ type FormMode = 'paged' | 'non-paged';
4122
+ /**
4123
+ * Result of form mode detection with validation details
4124
+ */
4125
+ interface FormModeDetectionResult {
4126
+ /** The detected form mode */
4127
+ mode: FormMode;
4128
+ /** Whether the form configuration is valid for the detected mode */
4129
+ isValid: boolean;
4130
+ /** Array of validation errors if form is invalid */
4131
+ errors: string[];
2893
4132
  }
2894
4133
 
2895
4134
  /**
2896
- * Minimal configuration for creating a DerivationOrchestrator.
2897
- * Only includes form-specific signals that cannot be injected.
4135
+ * Error from async field component loading.
2898
4136
  *
2899
- * @internal
4137
+ * @public
2900
4138
  */
2901
- interface DerivationOrchestratorConfig {
2902
- /** Signal containing the flattened schema fields */
2903
- schemaFields: Signal<FieldDef<unknown>[] | undefined>;
2904
- /** Signal containing the current form value */
2905
- formValue: Signal<Record<string, unknown>>;
2906
- /** Signal containing the form accessor */
2907
- form: Signal<FieldTree<unknown>>;
2908
- /** Signal containing the derivation logger */
2909
- derivationLogger: Signal<DerivationLogger>;
4139
+ interface FieldLoadingError {
4140
+ readonly fieldType: string;
4141
+ readonly fieldKey: string;
4142
+ readonly error: Error;
2910
4143
  }
2911
4144
 
2912
4145
  /**
@@ -3072,108 +4305,88 @@ declare class PageNavigationStateChangeEvent implements FormEvent {
3072
4305
  }
3073
4306
 
3074
4307
  /**
3075
- * Dynamic form component that renders a complete form based on configuration.
3076
- *
3077
- * This is the main entry point for the dynamic form system. It handles form state management,
3078
- * validation, field rendering, and event coordination using Angular's signal-based reactive forms.
3079
- *
3080
- * @typeParam TFields - Array of registered field types available for this form
3081
- * @typeParam TModel - The strongly-typed interface for form values
4308
+ * Dynamic form component — renders a form based on configuration.
4309
+ * Delegates state management to `FormStateManager`.
3082
4310
  *
3083
4311
  * @example
3084
4312
  *```html
3085
- * <form
3086
- * [dynamic-form]="formConfig"
3087
- * [(value)]="formData"
3088
- * (submitted)="handleSubmit($event)"
3089
- * (validityChange)="handleValidityChange($event)">
3090
- * </form>
4313
+ * <form [dynamic-form]="formConfig" [(value)]="formData" (submitted)="handleSubmit($event)"></form>
3091
4314
  * ```
3092
4315
  */
3093
- declare class DynamicForm<TFields extends RegisteredFieldTypes[] = RegisteredFieldTypes[], TModel extends Record<string, unknown> = InferFormValue$1<TFields> & Record<string, unknown>> implements OnDestroy {
3094
- private readonly destroyRef;
3095
- private readonly fieldRegistry;
3096
- private readonly injector;
3097
- private readonly eventBus;
3098
- private readonly rootFormRegistry;
3099
- private readonly functionRegistry;
3100
- private readonly schemaRegistry;
3101
- private readonly logger;
3102
- private readonly derivationLogConfig;
4316
+ declare class DynamicForm<TFields extends RegisteredFieldTypes[] = RegisteredFieldTypes[], TModel extends Record<string, unknown> = InferFormValue$1<TFields> & Record<string, unknown>> {
3103
4317
  /** Form configuration defining the structure, validation, and behavior. */
3104
- config: InputSignal<FormConfig<TFields>>;
4318
+ config: _angular_core.InputSignal<FormConfig<TFields, InferFormValue$1<TFields extends readonly RegisteredFieldTypes[] ? TFields : RegisteredFieldTypes[]>, Record<string, unknown>, unknown>>;
3105
4319
  /** Runtime form options that override config options when provided. */
3106
- formOptions: InputSignal<FormOptions | undefined>;
4320
+ formOptions: _angular_core.InputSignal<FormOptions | undefined>;
3107
4321
  /** Form values for two-way data binding. */
3108
4322
  value: _angular_core.ModelSignal<Partial<TModel> | undefined>;
3109
- private readonly memoizedFlattenFields;
3110
- private readonly memoizedFlattenFieldsForRendering;
3111
- private readonly memoizedKeyBy;
3112
- private readonly memoizedDefaultValues;
3113
- private readonly rawFieldRegistry;
3114
- readonly formModeDetection: Signal<FormModeDetectionResult>;
3115
- private readonly formSetup;
3116
- readonly pageFieldDefinitions: Signal<PageField<_ng_forge_dynamic_forms.PageAllowedChildren[]>[]>;
3117
- readonly effectiveFormOptions: Signal<{
4323
+ private destroyRef;
4324
+ private injector;
4325
+ private eventBus;
4326
+ private logger;
4327
+ /**
4328
+ * Connect input signals to the shared deps holder BEFORE injecting FormStateManager.
4329
+ * Field initializers run in declaration order, so this runs after inputs are created
4330
+ * but before stateManager is injected. FormStateManager reads from these signals
4331
+ * in computeds/effects, which are all lazy and evaluated after construction.
4332
+ */
4333
+ private readonly _connectStateDeps;
4334
+ /** State manager that owns all form state. */
4335
+ private stateManager;
4336
+ private componentId;
4337
+ /** The currently active config used for form rendering */
4338
+ activeConfig: Signal<FormConfig<TFields, InferFormValue$1<TFields extends readonly RegisteredFieldTypes[] ? TFields : RegisteredFieldTypes[]>, Record<string, unknown>, unknown> | undefined>;
4339
+ /** Current render phase: 'render' = showing form, 'teardown' = hiding old components */
4340
+ renderPhase: Signal<"teardown" | "render">;
4341
+ /** Computed form mode detection with validation */
4342
+ formModeDetection: Signal<_ng_forge_dynamic_forms.FormModeDetectionResult>;
4343
+ /** Page field definitions for paged forms */
4344
+ pageFieldDefinitions: Signal<_ng_forge_dynamic_forms.PageField<_ng_forge_dynamic_forms.PageAllowedChildren[]>[]>;
4345
+ /** Effective form options (merged from config and input) */
4346
+ effectiveFormOptions: Signal<{
3118
4347
  disabled?: boolean;
3119
4348
  maxDerivationIterations?: number;
3120
4349
  submitButton?: _ng_forge_dynamic_forms.SubmitButtonOptions;
3121
4350
  nextButton?: _ng_forge_dynamic_forms.NextButtonOptions;
4351
+ excludeValueIfHidden?: boolean;
4352
+ excludeValueIfDisabled?: boolean;
4353
+ excludeValueIfReadonly?: boolean;
3122
4354
  emitFormValueOnEvents?: boolean;
3123
4355
  }>;
3124
- /**
3125
- * Computed derivation logger based on injected config.
3126
- *
3127
- * Uses factory pattern to return no-op logger when logging is disabled,
3128
- * avoiding any logging overhead in production.
3129
- */
3130
- private readonly derivationLogger;
3131
- readonly fieldSignalContext: Signal<FieldSignalContext<TModel>>;
3132
- /**
3133
- * Injector for field components with FIELD_SIGNAL_CONTEXT.
3134
- * Other form-level tokens (DEFAULT_PROPS, etc.) are provided at component level.
3135
- */
3136
- private readonly fieldInjector;
3137
- readonly defaultValues: _angular_core.WritableSignal<TModel>;
3138
- private readonly entity;
3139
- readonly form: Signal<FieldTree<TModel, string | number>>;
3140
- private readonly componentId;
3141
- private readonly totalComponentsCount;
3142
- private readonly fieldsSource;
3143
- /** Collects errors from async field component loading for error boundary patterns. */
3144
- readonly fieldLoadingErrors: _angular_core.WritableSignal<{
3145
- fieldType: string;
3146
- fieldKey: string;
3147
- error: Error;
3148
- }[]>;
3149
- /** Current form values (reactive). */
3150
- readonly formValue: Signal<TModel | (TModel extends infer T ? T extends TModel ? T extends _angular_forms.AbstractControl<unknown, infer TValue extends unknown, any> ? TValue : never : never : never)>;
3151
- /** Whether the form is currently valid. */
3152
- readonly valid: Signal<boolean>;
3153
- /** Whether the form is currently invalid. */
3154
- readonly invalid: Signal<boolean>;
3155
- /** Whether any form field has been modified. */
3156
- readonly dirty: Signal<boolean>;
3157
- /** Whether any form field has been touched (blurred). */
3158
- readonly touched: Signal<boolean>;
3159
- /** Current validation errors from all fields. */
3160
- readonly errors: Signal<_angular_forms_signals.ValidationError.WithField[]>;
3161
- /** Whether the form is disabled (from options or form state). */
3162
- readonly disabled: Signal<boolean>;
3163
- /** Whether the form is currently submitting. */
3164
- readonly submitting: Signal<boolean>;
3165
- /**
3166
- * Configuration for the derivation orchestrator.
3167
- * Bundles the form-specific signals needed by the orchestrator.
3168
- *
3169
- * @internal Used by DERIVATION_ORCHESTRATOR provider.
3170
- */
3171
- readonly derivationOrchestratorConfig: DerivationOrchestratorConfig;
3172
- readonly initialized$: rxjs.Observable<boolean>;
4356
+ /** Field signal context for injection into child components */
4357
+ fieldSignalContext: Signal<_ng_forge_dynamic_forms.FieldSignalContext<TModel>>;
4358
+ /** Default values computed from field definitions */
4359
+ defaultValues: WritableSignal<TModel>;
4360
+ /** The Angular Signal Form instance */
4361
+ form: Signal<FieldTree<TModel, string | number>>;
4362
+ /** Current form values (reactive) */
4363
+ formValue: Signal<TModel | (TModel extends infer T ? T extends TModel ? T extends _angular_forms.AbstractControl<unknown, infer TValue extends unknown, any> ? TValue : never : never : never)>;
4364
+ /** Whether the form is currently valid */
4365
+ valid: Signal<boolean>;
4366
+ /** Whether the form is currently invalid */
4367
+ invalid: Signal<boolean>;
4368
+ /** Whether any form field has been modified */
4369
+ dirty: Signal<boolean>;
4370
+ /** Whether any form field has been touched (blurred) */
4371
+ touched: Signal<boolean>;
4372
+ /** Current validation errors from all fields */
4373
+ errors: Signal<_angular_forms_signals.ValidationError.WithField[]>;
4374
+ /** Whether the form is disabled (from options or form state) */
4375
+ disabled: Signal<boolean>;
4376
+ /** Whether the form is currently submitting */
4377
+ submitting: Signal<boolean>;
4378
+ /** Collects errors from async field component loading for error boundary patterns */
4379
+ fieldLoadingErrors: WritableSignal<FieldLoadingError[]>;
4380
+ /** Whether to render the form template */
4381
+ shouldRender: Signal<boolean>;
4382
+ /** Resolved fields ready for rendering */
4383
+ protected resolvedFields: Signal<ResolvedField[]>;
4384
+ private totalComponentsCount;
4385
+ initialized$: rxjs.Observable<boolean>;
3173
4386
  /** Emits when form validity changes. */
3174
- readonly validityChange: _angular_core.OutputRef<boolean>;
4387
+ validityChange: _angular_core.OutputRef<boolean>;
3175
4388
  /** Emits when form dirty state changes. */
3176
- readonly dirtyChange: _angular_core.OutputRef<boolean>;
4389
+ dirtyChange: _angular_core.OutputRef<boolean>;
3177
4390
  /**
3178
4391
  * Emits form values when submitted (via SubmitEvent) and form is valid.
3179
4392
  *
@@ -3183,23 +4396,22 @@ declare class DynamicForm<TFields extends RegisteredFieldTypes[] = RegisteredFie
3183
4396
  *
3184
4397
  * Note: Does not emit when `submission.action` is configured - use one or the other.
3185
4398
  */
3186
- readonly submitted: _angular_core.OutputRef<TModel>;
4399
+ submitted: _angular_core.OutputRef<Partial<TModel>>;
3187
4400
  /** Emits when form is reset to default values. */
3188
- readonly reset: _angular_core.OutputRef<FormResetEvent>;
4401
+ reset: _angular_core.OutputRef<FormResetEvent>;
3189
4402
  /** Emits when form is cleared to empty state. */
3190
- readonly cleared: _angular_core.OutputRef<FormClearEvent>;
4403
+ cleared: _angular_core.OutputRef<FormClearEvent>;
3191
4404
  /** Emits all form events for custom event handling. */
3192
- readonly events: _angular_core.OutputRef<_ng_forge_dynamic_forms.FormEvent>;
4405
+ events: _angular_core.OutputRef<_ng_forge_dynamic_forms.FormEvent>;
3193
4406
  /**
3194
4407
  * Emits when all form components are initialized and ready for interaction.
3195
4408
  * Useful for E2E testing to ensure the form is fully rendered before interaction.
3196
4409
  */
3197
- readonly initialized: _angular_core.OutputRef<boolean>;
4410
+ initialized: _angular_core.OutputRef<boolean>;
3198
4411
  /** Emits when the current page changes in paged forms. */
3199
- readonly onPageChange: _angular_core.OutputRef<PageChangeEvent>;
4412
+ onPageChange: _angular_core.OutputRef<PageChangeEvent>;
3200
4413
  /** Emits when page navigation state changes (canGoNext, canGoPrevious, etc.). */
3201
- readonly onPageNavigationStateChange: _angular_core.OutputRef<PageNavigationStateChangeEvent>;
3202
- protected readonly resolvedFields: Signal<ResolvedField[]>;
4414
+ onPageNavigationStateChange: _angular_core.OutputRef<PageNavigationStateChangeEvent>;
3203
4415
  constructor();
3204
4416
  /**
3205
4417
  * Handles native form submission triggered by:
@@ -3215,12 +4427,6 @@ declare class DynamicForm<TFields extends RegisteredFieldTypes[] = RegisteredFie
3215
4427
  protected onNativeSubmit(event: Event): void;
3216
4428
  private setupEffects;
3217
4429
  private setupEventHandlers;
3218
- private registerValidatorsFromConfig;
3219
- private createFormSetupFromConfig;
3220
- private createEmptyFormSetup;
3221
- private onFormReset;
3222
- private onFormClear;
3223
- ngOnDestroy(): void;
3224
4430
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<DynamicForm<any, any>, never>;
3225
4431
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<DynamicForm<any, any>, "form[dynamic-form]", never, { "config": { "alias": "dynamic-form"; "required": true; "isSignal": true; }; "formOptions": { "alias": "formOptions"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; "validityChange": "validityChange"; "dirtyChange": "dirtyChange"; "submitted": "submitted"; "reset": "reset"; "cleared": "cleared"; "events": "events"; "initialized": "initialized"; "onPageChange": "onPageChange"; "onPageNavigationStateChange": "onPageNavigationStateChange"; }, never, never, true, never>;
3226
4432
  }
@@ -3239,6 +4445,48 @@ interface DynamicFormFeature<TKind extends string = string> {
3239
4445
  ɵproviders: Provider[];
3240
4446
  }
3241
4447
 
4448
+ /**
4449
+ * Type guard to check if a field is a container field (page, row, group, or array)
4450
+ * Container fields have a 'fields' property and don't contribute values directly.
4451
+ * This overload works with RegisteredFieldTypes for full type narrowing.
4452
+ */
4453
+ declare function isContainerField(field: RegisteredFieldTypes): field is ContainerFieldTypes;
4454
+ /**
4455
+ * Type guard to check if a field is a container field (page, row, group, or array).
4456
+ * This overload works with any FieldDef for looser type checking.
4457
+ */
4458
+ declare function isContainerField(field: FieldDef<unknown>): boolean;
4459
+ /**
4460
+ * Type guard to check if a field is a leaf field (value or display field)
4461
+ * Leaf fields don't have children and either contribute values or display content
4462
+ */
4463
+ declare function isLeafField(field: RegisteredFieldTypes): field is LeafFieldTypes;
4464
+ /**
4465
+ * Type guard to check if a field has a value property (value-bearing field)
4466
+ * These fields contribute to the form value output
4467
+ * Note: Using `unknown` in the Extract condition to match any value type
4468
+ */
4469
+ declare function isValueBearingField(field: RegisteredFieldTypes): field is Extract<RegisteredFieldTypes, {
4470
+ value: unknown;
4471
+ }>;
4472
+ /**
4473
+ * Type guard to check if a field is excluded from form values (display-only field)
4474
+ * Currently this includes text fields and any other fields without a value property
4475
+ */
4476
+ declare function isDisplayOnlyField(field: RegisteredFieldTypes): boolean;
4477
+
4478
+ /**
4479
+ * Helper types for creating type-safe field configurations with proper nesting constraints
4480
+ */
4481
+
4482
+ /**
4483
+ * Type helper for accessing nested field paths safely
4484
+ * This allows accessing child paths while maintaining some type safety
4485
+ */
4486
+ type FieldPathAccess<TValue> = {
4487
+ [K in keyof TValue]: SchemaPath<TValue[K]> | SchemaPathTree<TValue[K]>;
4488
+ };
4489
+
3242
4490
  /**
3243
4491
  * Extract FieldDef type from FieldTypeDefinition
3244
4492
  */
@@ -3509,6 +4757,100 @@ declare class NoopLogger implements Logger {
3509
4757
  */
3510
4758
  declare function withEventFormValue(): DynamicFormFeature<'event-form-value'>;
3511
4759
 
4760
+ /**
4761
+ * Configuration for excluding field values from form submission output
4762
+ * based on their reactive state (hidden, disabled, readonly).
4763
+ *
4764
+ * Supports a 3-tier configuration hierarchy: Field > Form > Global.
4765
+ * The most specific level wins for each property.
4766
+ *
4767
+ * @public
4768
+ */
4769
+ interface ValueExclusionConfig {
4770
+ /**
4771
+ * Whether to exclude the value of hidden fields from submission output.
4772
+ *
4773
+ * When `true`, fields whose `hidden()` state is `true` will have their
4774
+ * values omitted from the submitted form value.
4775
+ *
4776
+ * @remarks
4777
+ * This does NOT affect HiddenField (`type: 'hidden'`) — those fields
4778
+ * store values without UI and their `hidden()` state is always `false`.
4779
+ */
4780
+ readonly excludeValueIfHidden?: boolean;
4781
+ /**
4782
+ * Whether to exclude the value of disabled fields from submission output.
4783
+ *
4784
+ * When `true`, fields whose `disabled()` state is `true` will have their
4785
+ * values omitted from the submitted form value.
4786
+ */
4787
+ readonly excludeValueIfDisabled?: boolean;
4788
+ /**
4789
+ * Whether to exclude the value of readonly fields from submission output.
4790
+ *
4791
+ * When `true`, fields whose `readonly()` state is `true` will have their
4792
+ * values omitted from the submitted form value.
4793
+ */
4794
+ readonly excludeValueIfReadonly?: boolean;
4795
+ }
4796
+ /**
4797
+ * Fully resolved exclusion config with all properties required.
4798
+ * Produced by the 3-tier resolution: `field ?? form ?? global`.
4799
+ *
4800
+ * @public
4801
+ */
4802
+ type ResolvedValueExclusionConfig = Required<ValueExclusionConfig>;
4803
+
4804
+ /**
4805
+ * Configures global value exclusion defaults for form submission output.
4806
+ *
4807
+ * Value exclusion is **enabled by default** — field values are excluded from the
4808
+ * `(submitted)` output based on their reactive state. Use this feature to
4809
+ * override those defaults. This does NOT affect two-way binding (`value` model /
4810
+ * `entity`) — fields retain their values internally.
4811
+ *
4812
+ * @example Default behavior (all exclusions enabled)
4813
+ * ```typescript
4814
+ * provideDynamicForm(
4815
+ * ...withMaterialFields(),
4816
+ * withValueExclusionDefaults()
4817
+ * )
4818
+ * ```
4819
+ *
4820
+ * @example Disable specific exclusions
4821
+ * ```typescript
4822
+ * provideDynamicForm(
4823
+ * ...withMaterialFields(),
4824
+ * withValueExclusionDefaults({ excludeValueIfReadonly: false })
4825
+ * )
4826
+ * ```
4827
+ *
4828
+ * @example Disable all exclusions (restore pre-v1 behavior)
4829
+ * ```typescript
4830
+ * provideDynamicForm(
4831
+ * ...withMaterialFields(),
4832
+ * withValueExclusionDefaults({
4833
+ * excludeValueIfHidden: false,
4834
+ * excludeValueIfDisabled: false,
4835
+ * excludeValueIfReadonly: false,
4836
+ * })
4837
+ * )
4838
+ * ```
4839
+ *
4840
+ * @remarks
4841
+ * **Precedence rules:**
4842
+ * 1. Per-field `excludeValueIf*` on `FieldDef` — wins for that field
4843
+ * 2. Per-form `excludeValueIf*` on `FormOptions` — wins for all fields in that form
4844
+ * 3. Global `withValueExclusionDefaults()` — baseline default
4845
+ * 4. No global feature — uses token default (all enabled)
4846
+ *
4847
+ * @param config - Partial override of exclusion rules. Unspecified properties default to `true`.
4848
+ * @returns A DynamicFormFeature that configures value exclusion defaults
4849
+ *
4850
+ * @public
4851
+ */
4852
+ declare function withValueExclusionDefaults(config?: Partial<ValueExclusionConfig>): DynamicFormFeature<'value-exclusion'>;
4853
+
3512
4854
  interface FieldOption<T = unknown> {
3513
4855
  label: DynamicText;
3514
4856
  value: T;
@@ -3595,127 +4937,53 @@ declare const DEFAULT_PROPS: InjectionToken<Signal<Record<string, unknown> | und
3595
4937
  /**
3596
4938
  * Injection token for form-level default validation messages.
3597
4939
  *
3598
- * Default validation messages act as fallbacks when fields have validation
3599
- * errors but no field-level `validationMessages` defined.
3600
- *
3601
- * Like DEFAULT_PROPS, this token is provided ONCE at the DynamicForm level
3602
- * and inherited by all children via Angular's hierarchical injector.
3603
- *
3604
- * The token provides a Signal to enable reactivity - when config changes, mappers
3605
- * that read the signal inside their computed() will automatically update.
3606
- *
3607
- * @example
3608
- * ```typescript
3609
- * // In a mapper or component
3610
- * const defaultMessagesSignal = inject(DEFAULT_VALIDATION_MESSAGES);
3611
- *
3612
- * return computed(() => {
3613
- * const defaultMessages = defaultMessagesSignal?.(); // Read inside computed for reactivity
3614
- * const message = defaultMessages?.required ?? 'Field is required';
3615
- * // ...
3616
- * });
3617
- * ```
3618
- */
3619
- declare const DEFAULT_VALIDATION_MESSAGES: InjectionToken<Signal<ValidationMessages | undefined>>;
3620
- /**
3621
- * Injection token for form-level options.
3622
- *
3623
- * Form options control form-wide behavior including button disabled states.
3624
- * Used by button mappers to determine default disabled behavior.
3625
- *
3626
- * Like DEFAULT_PROPS, this token is provided ONCE at the DynamicForm level
3627
- * and inherited by all children via Angular's hierarchical injector.
3628
- *
3629
- * The token provides a Signal to enable reactivity - when config changes, mappers
3630
- * that read the signal inside their computed() will automatically update.
3631
- *
3632
- * @example
3633
- * ```typescript
3634
- * // In a button mapper
3635
- * const formOptionsSignal = inject(FORM_OPTIONS);
3636
- *
3637
- * return computed(() => {
3638
- * const formOptions = formOptionsSignal?.(); // Read inside computed for reactivity
3639
- * const disableWhenInvalid = formOptions?.submitButton?.disableWhenInvalid ?? true;
3640
- * // ...
3641
- * });
3642
- * ```
3643
- */
3644
- declare const FORM_OPTIONS: InjectionToken<Signal<FormOptions | undefined>>;
3645
-
3646
- /**
3647
- * Context for resolving button disabled state.
3648
- *
3649
- * @public
3650
- */
3651
- interface ButtonLogicContext {
3652
- /** The form's FieldTree instance (supports both string and number keys for array indices) */
3653
- form: FieldTree<unknown, string | number>;
3654
- /** Form-level options */
3655
- formOptions?: FormOptions;
3656
- /** Field-level logic array (if provided) */
3657
- fieldLogic?: LogicConfig[];
3658
- /** Explicit disabled state from field definition */
3659
- explicitlyDisabled?: boolean;
3660
- /** Current page validity signal (for paged forms) */
3661
- currentPageValid?: Signal<boolean>;
3662
- /** Current form value for evaluating conditional expressions */
3663
- formValue?: unknown;
3664
- /** Optional logger for diagnostic output. Falls back to no-op logger if not provided. */
3665
- logger?: Logger;
3666
- }
3667
- /**
3668
- * Resolves the disabled state for a submit button.
3669
- *
3670
- * The disabled state is determined by (in order of precedence):
3671
- * 1. Explicit `disabled: true` on the field definition
3672
- * 2. Field-level `logic` array (if present, overrides form-level defaults)
3673
- * 3. Form-level `options.submitButton` defaults
4940
+ * Default validation messages act as fallbacks when fields have validation
4941
+ * errors but no field-level `validationMessages` defined.
3674
4942
  *
3675
- * @param ctx - The button logic context
3676
- * @returns A computed signal that returns true when the button should be disabled
4943
+ * Like DEFAULT_PROPS, this token is provided ONCE at the DynamicForm level
4944
+ * and inherited by all children via Angular's hierarchical injector.
4945
+ *
4946
+ * The token provides a Signal to enable reactivity - when config changes, mappers
4947
+ * that read the signal inside their computed() will automatically update.
3677
4948
  *
3678
4949
  * @example
3679
4950
  * ```typescript
3680
- * const disabled = resolveSubmitButtonDisabled({
3681
- * form: formInstance,
3682
- * formOptions: config.options,
3683
- * fieldLogic: buttonField.logic,
3684
- * explicitlyDisabled: buttonField.disabled,
3685
- * });
4951
+ * // In a mapper or component
4952
+ * const defaultMessagesSignal = inject(DEFAULT_VALIDATION_MESSAGES);
3686
4953
  *
3687
- * // Use in template
3688
- * <button [disabled]="disabled()">Submit</button>
4954
+ * return computed(() => {
4955
+ * const defaultMessages = defaultMessagesSignal?.(); // Read inside computed for reactivity
4956
+ * const message = defaultMessages?.required ?? 'Field is required';
4957
+ * // ...
4958
+ * });
3689
4959
  * ```
3690
- *
3691
- * @public
3692
4960
  */
3693
- declare function resolveSubmitButtonDisabled(ctx: ButtonLogicContext): Signal<boolean>;
4961
+ declare const DEFAULT_VALIDATION_MESSAGES: InjectionToken<Signal<ValidationMessages | undefined>>;
3694
4962
  /**
3695
- * Resolves the disabled state for a next page button.
4963
+ * Injection token for form-level options.
3696
4964
  *
3697
- * The disabled state is determined by (in order of precedence):
3698
- * 1. Explicit `disabled: true` on the field definition
3699
- * 2. Field-level `logic` array (if present, overrides form-level defaults)
3700
- * 3. Form-level `options.nextButton` defaults
4965
+ * Form options control form-wide behavior including button disabled states.
4966
+ * Used by button mappers to determine default disabled behavior.
3701
4967
  *
3702
- * @param ctx - The button logic context
3703
- * @returns A computed signal that returns true when the button should be disabled
4968
+ * Like DEFAULT_PROPS, this token is provided ONCE at the DynamicForm level
4969
+ * and inherited by all children via Angular's hierarchical injector.
4970
+ *
4971
+ * The token provides a Signal to enable reactivity - when config changes, mappers
4972
+ * that read the signal inside their computed() will automatically update.
3704
4973
  *
3705
4974
  * @example
3706
4975
  * ```typescript
3707
- * const disabled = resolveNextButtonDisabled({
3708
- * form: formInstance,
3709
- * formOptions: config.options,
3710
- * fieldLogic: buttonField.logic,
3711
- * explicitlyDisabled: buttonField.disabled,
3712
- * currentPageValid: pageOrchestrator.currentPageValid,
4976
+ * // In a button mapper
4977
+ * const formOptionsSignal = inject(FORM_OPTIONS);
4978
+ *
4979
+ * return computed(() => {
4980
+ * const formOptions = formOptionsSignal?.(); // Read inside computed for reactivity
4981
+ * const disableWhenInvalid = formOptions?.submitButton?.disableWhenInvalid ?? true;
4982
+ * // ...
3713
4983
  * });
3714
4984
  * ```
3715
- *
3716
- * @public
3717
4985
  */
3718
- declare function resolveNextButtonDisabled(ctx: ButtonLogicContext): Signal<boolean>;
4986
+ declare const FORM_OPTIONS: InjectionToken<Signal<FormOptions | undefined>>;
3719
4987
 
3720
4988
  interface FieldWithValidation {
3721
4989
  readonly required?: boolean;
@@ -3783,7 +5051,7 @@ interface BaseValueField<TProps, TValue, TMeta extends FieldMeta = FieldMeta> ex
3783
5051
  required?: boolean;
3784
5052
  }
3785
5053
  declare function isValueField<TProps, TMeta extends FieldMeta = FieldMeta>(field: FieldDef<TProps, TMeta>): field is BaseValueField<TProps, ValueType, TMeta>;
3786
- 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';
5054
+ 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';
3787
5055
  type ValueFieldComponent<T extends BaseValueField<Record<string, unknown> | unknown, unknown>> = Prettify<WithInputSignals<Omit<T, ExcludedKeys$1>>>;
3788
5056
 
3789
5057
  interface BaseCheckedField<TProps, TMeta extends FieldMeta = FieldMeta> extends FieldDef<TProps, TMeta>, FieldWithValidation {
@@ -3796,7 +5064,7 @@ interface BaseCheckedField<TProps, TMeta extends FieldMeta = FieldMeta> extends
3796
5064
  required?: boolean;
3797
5065
  }
3798
5066
  declare function isCheckedField<TProps, TMeta extends FieldMeta = FieldMeta>(field: FieldDef<TProps, TMeta>): field is BaseCheckedField<TProps, TMeta>;
3799
- type ExcludedKeys = 'type' | 'conditionals' | 'value' | 'disabled' | 'readonly' | 'hidden' | 'col' | keyof FieldWithValidation;
5067
+ type ExcludedKeys = 'type' | 'conditionals' | 'value' | 'disabled' | 'readonly' | 'hidden' | 'col' | keyof FieldWithValidation | 'excludeValueIfHidden' | 'excludeValueIfDisabled' | 'excludeValueIfReadonly';
3800
5068
  type CheckedFieldComponent<T extends BaseCheckedField<Record<string, unknown> | unknown>> = Prettify<WithInputSignals<Omit<T, ExcludedKeys>>>;
3801
5069
 
3802
5070
  declare function applyValidator(config: ValidatorConfig, fieldPath: SchemaPath<any> | SchemaPathTree<any>): void;
@@ -3815,77 +5083,367 @@ declare class PreviousPageEvent implements FormEvent {
3815
5083
  }
3816
5084
 
3817
5085
  /**
3818
- * Event dispatched to add a new item to an array field.
5086
+ * Template type for array items used in events.
5087
+ * Canonical definition lives in {@link ArrayItemDefinition} — this alias
5088
+ * preserves the events-specific naming convention.
5089
+ */
5090
+ type ArrayItemDefinitionTemplate = ArrayItemDefinition;
5091
+
5092
+ /**
5093
+ * Event dispatched to append a new item at the END of an array field.
5094
+ *
5095
+ * This is the most common array operation - adding items to the end.
5096
+ * For other positions, use {@link PrependArrayItemEvent} or {@link InsertArrayItemEvent}.
5097
+ *
5098
+ * @example
5099
+ * ```typescript
5100
+ * // Object item: append { name, email } object
5101
+ * eventBus.dispatch(arrayEvent('contacts').append([
5102
+ * { key: 'name', type: 'input', label: 'Name' },
5103
+ * { key: 'email', type: 'input', label: 'Email' }
5104
+ * ]));
5105
+ *
5106
+ * // Primitive item: append single value
5107
+ * eventBus.dispatch(arrayEvent('tags').append(
5108
+ * { key: 'tag', type: 'input', label: 'Tag' }
5109
+ * ));
5110
+ * ```
5111
+ *
5112
+ * @typeParam TTemplate - The type of the template (single field or array of fields)
5113
+ */
5114
+ declare class AppendArrayItemEvent<TTemplate extends ArrayItemDefinitionTemplate = ArrayItemDefinitionTemplate> implements FormEvent {
5115
+ /** The key of the array field to append an item to */
5116
+ readonly arrayKey: string;
5117
+ /**
5118
+ * Template for the new array item. REQUIRED.
5119
+ * - Single field (FieldDef): Creates a primitive item (field's value is extracted directly)
5120
+ * - Array of fields (FieldDef[]): Creates an object item (fields merged into object)
5121
+ */
5122
+ readonly template: TTemplate;
5123
+ readonly type: "append-array-item";
5124
+ constructor(
5125
+ /** The key of the array field to append an item to */
5126
+ arrayKey: string,
5127
+ /**
5128
+ * Template for the new array item. REQUIRED.
5129
+ * - Single field (FieldDef): Creates a primitive item (field's value is extracted directly)
5130
+ * - Array of fields (FieldDef[]): Creates an object item (fields merged into object)
5131
+ */
5132
+ template: TTemplate);
5133
+ }
5134
+
5135
+ /**
5136
+ * Event dispatched to prepend a new item at the BEGINNING of an array field.
3819
5137
  *
3820
- * This event is used to dynamically add items to array fields at runtime.
3821
- * The array field component listens for this event and adds a new item
3822
- * based on the provided field template, or uses the array's own template if not provided.
5138
+ * Use this when new items should appear at the start of the list.
5139
+ * For appending to the end, use {@link AppendArrayItemEvent}.
3823
5140
  *
3824
5141
  * @example
3825
5142
  * ```typescript
3826
- * // Add item using array's own template
3827
- * eventBus.dispatch(AddArrayItemEvent, 'contacts');
5143
+ * // Object item: prepend { name } object
5144
+ * eventBus.dispatch(arrayEvent('contacts').prepend([
5145
+ * { key: 'name', type: 'input', label: 'Name' }
5146
+ * ]));
5147
+ *
5148
+ * // Primitive item: prepend single value
5149
+ * eventBus.dispatch(arrayEvent('tags').prepend(
5150
+ * { key: 'tag', type: 'input', label: 'Tag' }
5151
+ * ));
5152
+ * ```
5153
+ *
5154
+ * @typeParam TTemplate - The type of the template (single field or array of fields)
5155
+ */
5156
+ declare class PrependArrayItemEvent<TTemplate extends ArrayItemDefinitionTemplate = ArrayItemDefinitionTemplate> implements FormEvent {
5157
+ /** The key of the array field to prepend an item to */
5158
+ readonly arrayKey: string;
5159
+ /**
5160
+ * Template for the new array item. REQUIRED.
5161
+ * - Single field (FieldDef): Creates a primitive item (field's value is extracted directly)
5162
+ * - Array of fields (FieldDef[]): Creates an object item (fields merged into object)
5163
+ */
5164
+ readonly template: TTemplate;
5165
+ readonly type: "prepend-array-item";
5166
+ constructor(
5167
+ /** The key of the array field to prepend an item to */
5168
+ arrayKey: string,
5169
+ /**
5170
+ * Template for the new array item. REQUIRED.
5171
+ * - Single field (FieldDef): Creates a primitive item (field's value is extracted directly)
5172
+ * - Array of fields (FieldDef[]): Creates an object item (fields merged into object)
5173
+ */
5174
+ template: TTemplate);
5175
+ }
5176
+
5177
+ /**
5178
+ * Event dispatched to insert a new item at a SPECIFIC INDEX in an array field.
3828
5179
  *
3829
- * // Add item with explicit field template
3830
- * const fieldTemplate = { key: 'email', type: 'input' };
3831
- * eventBus.dispatch(AddArrayItemEvent, 'contacts', fieldTemplate);
5180
+ * Use this when you need precise control over where the new item appears.
5181
+ * For simpler operations, use {@link AppendArrayItemEvent} or {@link PrependArrayItemEvent}.
3832
5182
  *
3833
- * // Add item at specific index
3834
- * eventBus.dispatch(AddArrayItemEvent, 'contacts', fieldTemplate, 2);
5183
+ * @example
5184
+ * ```typescript
5185
+ * // Object item: insert { name } object at index 2
5186
+ * eventBus.dispatch(arrayEvent('contacts').insertAt(2, [
5187
+ * { key: 'name', type: 'input', label: 'Name' }
5188
+ * ]));
5189
+ *
5190
+ * // Primitive item: insert single value at index 2
5191
+ * eventBus.dispatch(arrayEvent('tags').insertAt(2,
5192
+ * { key: 'tag', type: 'input', label: 'Tag' }
5193
+ * ));
3835
5194
  * ```
5195
+ *
5196
+ * @typeParam TTemplate - The type of the template (single field or array of fields)
3836
5197
  */
3837
- declare class AddArrayItemEvent implements FormEvent {
3838
- /** The key of the array field to add an item to */
5198
+ declare class InsertArrayItemEvent<TTemplate extends ArrayItemDefinitionTemplate = ArrayItemDefinitionTemplate> implements FormEvent {
5199
+ /** The key of the array field to insert an item into */
3839
5200
  readonly arrayKey: string;
5201
+ /** The index at which to insert the new item */
5202
+ readonly index: number;
3840
5203
  /**
3841
- * Optional field template definition to render for the new array item.
3842
- * If not provided, the array field component will use its own template.
5204
+ * Template for the new array item. REQUIRED.
5205
+ * - Single field (FieldDef): Creates a primitive item (field's value is extracted directly)
5206
+ * - Array of fields (FieldDef[]): Creates an object item (fields merged into object)
3843
5207
  */
3844
- readonly field?: ArrayAllowedChildren | undefined;
3845
- /** Optional index where to insert the item (defaults to end of array) */
3846
- readonly index?: number | undefined;
3847
- readonly type: "add-array-item";
5208
+ readonly template: TTemplate;
5209
+ readonly type: "insert-array-item";
3848
5210
  constructor(
3849
- /** The key of the array field to add an item to */
5211
+ /** The key of the array field to insert an item into */
3850
5212
  arrayKey: string,
5213
+ /** The index at which to insert the new item */
5214
+ index: number,
3851
5215
  /**
3852
- * Optional field template definition to render for the new array item.
3853
- * If not provided, the array field component will use its own template.
5216
+ * Template for the new array item. REQUIRED.
5217
+ * - Single field (FieldDef): Creates a primitive item (field's value is extracted directly)
5218
+ * - Array of fields (FieldDef[]): Creates an object item (fields merged into object)
3854
5219
  */
3855
- field?: ArrayAllowedChildren | undefined,
3856
- /** Optional index where to insert the item (defaults to end of array) */
3857
- index?: number | undefined);
5220
+ template: TTemplate);
5221
+ }
5222
+
5223
+ /**
5224
+ * Event dispatched to remove the LAST item from an array field.
5225
+ *
5226
+ * Equivalent to JavaScript's `Array.pop()` - removes from the end.
5227
+ * For removing from the beginning, use {@link ShiftArrayItemEvent}.
5228
+ * For removing at a specific index, use {@link RemoveAtIndexEvent}.
5229
+ *
5230
+ * @example
5231
+ * ```typescript
5232
+ * // Use the builder API (recommended)
5233
+ * eventBus.dispatch(arrayEvent('contacts').pop());
5234
+ *
5235
+ * // Or instantiate directly
5236
+ * eventBus.dispatch(new PopArrayItemEvent('contacts'));
5237
+ * ```
5238
+ */
5239
+ declare class PopArrayItemEvent implements FormEvent {
5240
+ /** The key of the array field to remove the last item from */
5241
+ readonly arrayKey: string;
5242
+ readonly type: "pop-array-item";
5243
+ constructor(
5244
+ /** The key of the array field to remove the last item from */
5245
+ arrayKey: string);
3858
5246
  }
3859
5247
 
3860
5248
  /**
3861
- * Event dispatched to remove an item from an array field.
5249
+ * Event dispatched to remove the FIRST item from an array field.
3862
5250
  *
3863
- * This event is used to dynamically remove items from array fields at runtime.
3864
- * The array field component listens for this event and removes the item
3865
- * at the specified index.
5251
+ * Equivalent to JavaScript's `Array.shift()` - removes from the beginning.
5252
+ * For removing from the end, use {@link PopArrayItemEvent}.
5253
+ * For removing at a specific index, use {@link RemoveAtIndexEvent}.
3866
5254
  *
3867
5255
  * @example
3868
5256
  * ```typescript
3869
- * // Remove item at index 1 from 'contacts' array
3870
- * eventBus.dispatch(RemoveArrayItemEvent, 'contacts', 1);
5257
+ * // Use the builder API (recommended)
5258
+ * eventBus.dispatch(arrayEvent('contacts').shift());
3871
5259
  *
3872
- * // Remove last item
3873
- * eventBus.dispatch(RemoveArrayItemEvent, 'contacts');
5260
+ * // Or instantiate directly
5261
+ * eventBus.dispatch(new ShiftArrayItemEvent('contacts'));
3874
5262
  * ```
3875
5263
  */
3876
- declare class RemoveArrayItemEvent implements FormEvent {
5264
+ declare class ShiftArrayItemEvent implements FormEvent {
5265
+ /** The key of the array field to remove the first item from */
5266
+ readonly arrayKey: string;
5267
+ readonly type: "shift-array-item";
5268
+ constructor(
5269
+ /** The key of the array field to remove the first item from */
5270
+ arrayKey: string);
5271
+ }
5272
+
5273
+ /**
5274
+ * Event dispatched to remove an item at a SPECIFIC INDEX from an array field.
5275
+ *
5276
+ * Use this when you need precise control over which item to remove.
5277
+ * For simpler operations, use {@link PopArrayItemEvent} or {@link ShiftArrayItemEvent}.
5278
+ *
5279
+ * @example
5280
+ * ```typescript
5281
+ * // Use the builder API (recommended)
5282
+ * eventBus.dispatch(arrayEvent('contacts').removeAt(2));
5283
+ *
5284
+ * // Or instantiate directly
5285
+ * eventBus.dispatch(new RemoveAtIndexEvent('contacts', 2));
5286
+ * ```
5287
+ */
5288
+ declare class RemoveAtIndexEvent implements FormEvent {
3877
5289
  /** The key of the array field to remove an item from */
3878
5290
  readonly arrayKey: string;
3879
- /** Index of the item to remove (defaults to last item) */
3880
- readonly index?: number | undefined;
3881
- readonly type: "remove-array-item";
5291
+ /** The index of the item to remove */
5292
+ readonly index: number;
5293
+ readonly type: "remove-at-index";
3882
5294
  constructor(
3883
5295
  /** The key of the array field to remove an item from */
3884
5296
  arrayKey: string,
3885
- /** Index of the item to remove (defaults to last item) */
3886
- index?: number | undefined);
5297
+ /** The index of the item to remove */
5298
+ index: number);
3887
5299
  }
3888
5300
 
5301
+ /**
5302
+ * Builder for array manipulation events.
5303
+ *
5304
+ * Provides a fluent, discoverable API for array operations.
5305
+ * Type `arrayEvent('key').` in your IDE to see all available operations.
5306
+ *
5307
+ * **BREAKING CHANGE**: Template is now required for add operations.
5308
+ *
5309
+ * Supports both primitive and object array items:
5310
+ * - Primitive: Pass a single field definition → extracts field value directly
5311
+ * - Object: Pass an array of field definitions → merges fields into object
5312
+ *
5313
+ * @example
5314
+ * ```typescript
5315
+ * import { arrayEvent } from '@ng-forge/dynamic-forms';
5316
+ *
5317
+ * // Object item: append { name, email } object
5318
+ * eventBus.dispatch(arrayEvent('contacts').append([
5319
+ * { key: 'name', type: 'input', label: 'Name' },
5320
+ * { key: 'email', type: 'input', label: 'Email' }
5321
+ * ]));
5322
+ *
5323
+ * // Primitive item: append single value
5324
+ * eventBus.dispatch(arrayEvent('tags').append(
5325
+ * { key: 'tag', type: 'input', label: 'Tag' }
5326
+ * ));
5327
+ *
5328
+ * // Removing items (no template needed)
5329
+ * eventBus.dispatch(arrayEvent('contacts').pop()); // Remove last
5330
+ * eventBus.dispatch(arrayEvent('contacts').shift()); // Remove first
5331
+ * eventBus.dispatch(arrayEvent('contacts').removeAt(2)); // Remove at index
5332
+ * ```
5333
+ *
5334
+ * @param arrayKey - The key of the array field to operate on
5335
+ * @returns An object with methods for all 6 array operations
5336
+ */
5337
+ declare function arrayEvent(arrayKey: string): {
5338
+ /**
5339
+ * Append a new item at the END of the array.
5340
+ * This is the most common operation for adding items.
5341
+ *
5342
+ * @param template - Template for the new item (REQUIRED)
5343
+ * - Single field: Creates a primitive item (field's value is extracted directly)
5344
+ * - Array of fields: Creates an object item (fields merged into object)
5345
+ * @returns An AppendArrayItemEvent to dispatch
5346
+ *
5347
+ * @example
5348
+ * ```typescript
5349
+ * // Object item
5350
+ * eventBus.dispatch(arrayEvent('contacts').append([
5351
+ * { key: 'name', type: 'input', label: 'Name' }
5352
+ * ]));
5353
+ *
5354
+ * // Primitive item
5355
+ * eventBus.dispatch(arrayEvent('tags').append(
5356
+ * { key: 'tag', type: 'input', label: 'Tag' }
5357
+ * ));
5358
+ * ```
5359
+ */
5360
+ append: <T extends ArrayItemDefinitionTemplate>(template: T) => AppendArrayItemEvent<T>;
5361
+ /**
5362
+ * Prepend a new item at the BEGINNING of the array.
5363
+ * Use when new items should appear at the start.
5364
+ *
5365
+ * @param template - Template for the new item (REQUIRED)
5366
+ * - Single field: Creates a primitive item (field's value is extracted directly)
5367
+ * - Array of fields: Creates an object item (fields merged into object)
5368
+ * @returns A PrependArrayItemEvent to dispatch
5369
+ *
5370
+ * @example
5371
+ * ```typescript
5372
+ * // Object item
5373
+ * eventBus.dispatch(arrayEvent('contacts').prepend([
5374
+ * { key: 'name', type: 'input', label: 'Name' }
5375
+ * ]));
5376
+ *
5377
+ * // Primitive item
5378
+ * eventBus.dispatch(arrayEvent('tags').prepend(
5379
+ * { key: 'tag', type: 'input', label: 'Tag' }
5380
+ * ));
5381
+ * ```
5382
+ */
5383
+ prepend: <T extends ArrayItemDefinitionTemplate>(template: T) => PrependArrayItemEvent<T>;
5384
+ /**
5385
+ * Insert a new item at a SPECIFIC INDEX in the array.
5386
+ * Use when you need precise control over item placement.
5387
+ *
5388
+ * @param index - The position at which to insert the new item
5389
+ * @param template - Template for the new item (REQUIRED)
5390
+ * - Single field: Creates a primitive item (field's value is extracted directly)
5391
+ * - Array of fields: Creates an object item (fields merged into object)
5392
+ * @returns An InsertArrayItemEvent to dispatch
5393
+ *
5394
+ * @example
5395
+ * ```typescript
5396
+ * // Object item at index 2
5397
+ * eventBus.dispatch(arrayEvent('contacts').insertAt(2, [
5398
+ * { key: 'name', type: 'input', label: 'Name' }
5399
+ * ]));
5400
+ *
5401
+ * // Primitive item at index 2
5402
+ * eventBus.dispatch(arrayEvent('tags').insertAt(2,
5403
+ * { key: 'tag', type: 'input', label: 'Tag' }
5404
+ * ));
5405
+ * ```
5406
+ */
5407
+ insertAt: <T extends ArrayItemDefinitionTemplate>(index: number, template: T) => InsertArrayItemEvent<T>;
5408
+ /**
5409
+ * Remove the LAST item from the array.
5410
+ * Equivalent to JavaScript's `Array.pop()`.
5411
+ *
5412
+ * @returns A PopArrayItemEvent to dispatch
5413
+ *
5414
+ * @example
5415
+ * ```typescript
5416
+ * eventBus.dispatch(arrayEvent('contacts').pop());
5417
+ * ```
5418
+ */
5419
+ pop: () => PopArrayItemEvent;
5420
+ /**
5421
+ * Remove the FIRST item from the array.
5422
+ * Equivalent to JavaScript's `Array.shift()`.
5423
+ *
5424
+ * @returns A ShiftArrayItemEvent to dispatch
5425
+ *
5426
+ * @example
5427
+ * ```typescript
5428
+ * eventBus.dispatch(arrayEvent('contacts').shift());
5429
+ * ```
5430
+ */
5431
+ shift: () => ShiftArrayItemEvent;
5432
+ /**
5433
+ * Remove an item at a SPECIFIC INDEX from the array.
5434
+ * Use when you need to remove a specific item by position.
5435
+ *
5436
+ * @param index - The position of the item to remove
5437
+ * @returns A RemoveAtIndexEvent to dispatch
5438
+ *
5439
+ * @example
5440
+ * ```typescript
5441
+ * eventBus.dispatch(arrayEvent('contacts').removeAt(2));
5442
+ * ```
5443
+ */
5444
+ removeAt: (index: number) => RemoveAtIndexEvent;
5445
+ };
5446
+
3889
5447
  /**
3890
5448
  * Event bus service for form-wide event communication.
3891
5449
  *
@@ -3984,6 +5542,8 @@ interface TokenContext {
3984
5542
  arrayKey?: string;
3985
5543
  /** Current form value for complex indexing */
3986
5544
  formValue?: unknown;
5545
+ /** Template for array item creation */
5546
+ template?: unknown;
3987
5547
  }
3988
5548
  /**
3989
5549
  * Resolves special tokens in event arguments to their actual values
@@ -3992,6 +5552,7 @@ interface TokenContext {
3992
5552
  * - $key: The current field key
3993
5553
  * - $index: The array index (if inside an array field)
3994
5554
  * - $arrayKey: The parent array field key (if inside an array field)
5555
+ * - $template: The template for array item creation
3995
5556
  * - formValue: Reference to the current form value for indexing
3996
5557
  *
3997
5558
  * @param args - Array of arguments that may contain tokens
@@ -4005,6 +5566,10 @@ interface TokenContext {
4005
5566
  * @example
4006
5567
  * resolveTokens(['$key', 'static'], { key: 'myField' })
4007
5568
  * // Returns: ['myField', 'static']
5569
+ *
5570
+ * @example
5571
+ * resolveTokens(['$arrayKey', '$template'], { arrayKey: 'contacts', template: [{ key: 'name', type: 'input' }] })
5572
+ * // Returns: ['contacts', [{ key: 'name', type: 'input' }]]
4008
5573
  */
4009
5574
  declare function resolveTokens(args: readonly (string | number | boolean | null | undefined)[], context: TokenContext): (string | number | boolean | null | undefined | unknown)[];
4010
5575
 
@@ -4177,6 +5742,8 @@ declare function baseFieldMapper(fieldDef: FieldDef<unknown>): Signal<Record<str
4177
5742
  * Row components are layout containers that don't change the form shape.
4178
5743
  * The row component will inject FIELD_SIGNAL_CONTEXT directly.
4179
5744
  *
5745
+ * Supports hidden state resolution via `logic` array or static `hidden` property.
5746
+ *
4180
5747
  * @param fieldDef The row field definition
4181
5748
  * @returns Signal containing Record of input names to values for ngComponentOutlet
4182
5749
  */
@@ -4189,6 +5756,8 @@ declare function rowFieldMapper(fieldDef: RowField): Signal<Record<string, unkno
4189
5756
  * The group component will inject the parent FIELD_SIGNAL_CONTEXT and create
4190
5757
  * a scoped child injector for its nested fields.
4191
5758
  *
5759
+ * Supports hidden state resolution via `logic` array or static `hidden` property.
5760
+ *
4192
5761
  * @param fieldDef The group field definition
4193
5762
  * @returns Signal containing Record of input names to values for ngComponentOutlet
4194
5763
  */
@@ -4201,6 +5770,8 @@ declare function groupFieldMapper(fieldDef: GroupField): Signal<Record<string, u
4201
5770
  * The array component will inject the parent FIELD_SIGNAL_CONTEXT and create
4202
5771
  * a scoped child injector for its array item fields.
4203
5772
  *
5773
+ * Supports hidden state resolution via `logic` array or static `hidden` property.
5774
+ *
4204
5775
  * @param fieldDef The array field definition
4205
5776
  * @returns Signal containing Record of input names to values for ngComponentOutlet
4206
5777
  */
@@ -4212,6 +5783,10 @@ declare function arrayFieldMapper(fieldDef: ArrayField): Signal<Record<string, u
4212
5783
  * Page fields are layout containers that don't modify the form context.
4213
5784
  * The page component will inject FIELD_SIGNAL_CONTEXT directly.
4214
5785
  *
5786
+ * Note: Unlike other container mappers (group, row, array), the page mapper does NOT
5787
+ * resolve hidden logic here. Page visibility is managed by the {@link PageOrchestratorComponent}
5788
+ * which evaluates `ContainerLogicConfig` conditions and controls the `isVisible` input.
5789
+ *
4215
5790
  * @param fieldDef The page field definition
4216
5791
  * @returns Signal containing Record of input names to values for ngComponentOutlet
4217
5792
  */
@@ -4221,8 +5796,14 @@ declare function pageFieldMapper(fieldDef: PageField): Signal<Record<string, unk
4221
5796
  * Maps a text field definition to component inputs.
4222
5797
  *
4223
5798
  * Text fields are display-only fields that don't participate in the form schema.
4224
- * This mapper handles the `logic` configuration by creating reactive signals
4225
- * that evaluate conditions using the form value from RootFormRegistryService.
5799
+ * This mapper handles the `logic` configuration by using the non-field-hidden resolver
5800
+ * to evaluate conditions against the form value from RootFormRegistryService.
5801
+ *
5802
+ * Hidden state is resolved using the non-field-hidden resolver which considers:
5803
+ * 1. Explicit `hidden: true` on the field definition
5804
+ * 2. Field-level `logic` array with `type: 'hidden'` conditions
5805
+ *
5806
+ * Note: Text fields don't support disabled logic since they are display-only.
4226
5807
  *
4227
5808
  * @param fieldDef The text field definition
4228
5809
  * @returns Signal containing Record of input names to values for ngComponentOutlet
@@ -4318,6 +5899,7 @@ declare class RowFieldComponent {
4318
5899
  field: _angular_core.InputSignal<RowField<readonly _ng_forge_dynamic_forms.RowAllowedChildren[]>>;
4319
5900
  key: _angular_core.InputSignal<string>;
4320
5901
  className: _angular_core.InputSignal<string | undefined>;
5902
+ hidden: _angular_core.InputSignal<boolean>;
4321
5903
  readonly hostClasses: _angular_core.Signal<string>;
4322
5904
  readonly disabled: _angular_core.Signal<boolean>;
4323
5905
  private readonly rawFieldRegistry;
@@ -4326,7 +5908,7 @@ declare class RowFieldComponent {
4326
5908
  constructor();
4327
5909
  private setupEffects;
4328
5910
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<RowFieldComponent, never>;
4329
- 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>;
5911
+ 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>;
4330
5912
  }
4331
5913
 
4332
5914
  /**
@@ -4343,16 +5925,20 @@ declare class GroupFieldComponent<TModel extends Record<string, unknown> = Recor
4343
5925
  private readonly injector;
4344
5926
  private readonly eventBus;
4345
5927
  private readonly logger;
4346
- private readonly memoizedFlattenFields;
4347
- private readonly memoizedKeyBy;
4348
- private readonly memoizedDefaultValues;
5928
+ private readonly fieldProcessors;
4349
5929
  field: _angular_core.InputSignal<GroupField<readonly _ng_forge_dynamic_forms.GroupAllowedChildren[]>>;
4350
5930
  key: _angular_core.InputSignal<string>;
4351
5931
  className: _angular_core.InputSignal<string | undefined>;
5932
+ hidden: _angular_core.InputSignal<boolean>;
4352
5933
  readonly hostClasses: _angular_core.Signal<string>;
4353
5934
  private readonly rawFieldRegistry;
4354
5935
  private readonly formSetup;
4355
5936
  readonly defaultValues: _angular_core.WritableSignal<Record<string, unknown>>;
5937
+ /**
5938
+ * Entity computed from parent value, group key, and defaults.
5939
+ * Uses deep equality check to prevent unnecessary updates when
5940
+ * object spread creates new references with identical values.
5941
+ */
4356
5942
  private readonly entity;
4357
5943
  private readonly form;
4358
5944
  readonly formValue: _angular_core.Signal<{}>;
@@ -4363,6 +5949,7 @@ declare class GroupFieldComponent<TModel extends Record<string, unknown> = Recor
4363
5949
  readonly errors: _angular_core.Signal<_angular_forms_signals.ValidationError.WithField[]>;
4364
5950
  readonly disabled: _angular_core.Signal<boolean>;
4365
5951
  private readonly nestedFieldTree;
5952
+ private readonly groupFieldSignalContext;
4366
5953
  private readonly groupInjector;
4367
5954
  readonly validityChange: _angular_core.OutputRef<boolean>;
4368
5955
  readonly dirtyChange: _angular_core.OutputRef<boolean>;
@@ -4372,17 +5959,13 @@ declare class GroupFieldComponent<TModel extends Record<string, unknown> = Recor
4372
5959
  constructor();
4373
5960
  private setupEffects;
4374
5961
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<GroupFieldComponent<any>, never>;
4375
- 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>;
5962
+ 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>;
4376
5963
  }
4377
5964
 
4378
5965
  /**
4379
- * Resolved array item ready for declarative rendering with ngComponentOutlet.
4380
- * Each item has a stable unique ID and a linkedSignal-based index that
4381
- * automatically updates when items are added/removed.
5966
+ * A single field within a resolved array item.
4382
5967
  */
4383
- interface ResolvedArrayItem {
4384
- /** Unique identifier for this item (stable across position changes). */
4385
- id: string;
5968
+ interface ResolvedArrayItemField {
4386
5969
  /** The loaded component type. */
4387
5970
  component: Type<unknown>;
4388
5971
  /** Injector providing ARRAY_CONTEXT and FIELD_SIGNAL_CONTEXT. */
@@ -4390,13 +5973,26 @@ interface ResolvedArrayItem {
4390
5973
  /** Inputs signal for ngComponentOutlet - evaluated in template for reactivity. */
4391
5974
  inputs: Signal<Record<string, unknown>>;
4392
5975
  }
5976
+ /**
5977
+ * Resolved array item ready for declarative rendering with ngComponentOutlet.
5978
+ * Each item has a stable unique ID and a linkedSignal-based index that
5979
+ * automatically updates when items are added/removed.
5980
+ * Supports multiple fields per array item (e.g., name + email without a wrapper).
5981
+ */
5982
+ interface ResolvedArrayItem {
5983
+ /** Unique identifier for this item (stable across position changes). */
5984
+ id: string;
5985
+ /** All fields to render for this array item. */
5986
+ fields: ResolvedArrayItemField[];
5987
+ }
4393
5988
 
4394
5989
  /**
4395
5990
  * Container component for rendering dynamic arrays of fields.
4396
5991
  *
4397
- * Supports add/remove operations via AddArrayItemEvent and RemoveArrayItemEvent.
5992
+ * Supports add/remove operations via the arrayEvent() builder API.
4398
5993
  * Uses differential updates to optimize rendering - only recreates items when necessary.
4399
5994
  * Each item gets a scoped injector with ARRAY_CONTEXT for position-aware operations.
5995
+ * Supports multiple sibling fields per array item (e.g., name + email without a wrapper).
4400
5996
  */
4401
5997
  declare class ArrayFieldComponent<TModel extends Record<string, unknown> = Record<string, unknown>> {
4402
5998
  private readonly destroyRef;
@@ -4405,28 +6001,111 @@ declare class ArrayFieldComponent<TModel extends Record<string, unknown> = Recor
4405
6001
  private readonly parentInjector;
4406
6002
  private readonly eventBus;
4407
6003
  private readonly logger;
4408
- field: _angular_core.InputSignal<ArrayField<readonly _ng_forge_dynamic_forms.ArrayAllowedChildren[]>>;
6004
+ private readonly templateRegistry;
6005
+ private readonly generateItemId;
6006
+ field: _angular_core.InputSignal<ArrayField<readonly ArrayItemDefinition[]>>;
4409
6007
  key: _angular_core.InputSignal<string>;
4410
6008
  className: _angular_core.InputSignal<string | undefined>;
6009
+ hidden: _angular_core.InputSignal<boolean>;
4411
6010
  readonly hostClasses: _angular_core.Signal<string>;
4412
6011
  private readonly rawFieldRegistry;
4413
- private readonly fieldTemplate;
6012
+ /**
6013
+ * Gets the auto-remove button FieldDef from normalization metadata.
6014
+ * Set by simplified array normalization for primitive arrays with remove buttons.
6015
+ * The button is rendered alongside each item without wrapping in a row,
6016
+ * preserving flat primitive form values.
6017
+ */
6018
+ private readonly autoRemoveButton;
6019
+ /**
6020
+ * For primitive array items, the key of the value field template (e.g., 'value').
6021
+ * Used to wrap the FormControl in the item context so getFieldTree(key) works.
6022
+ * Returns undefined for object arrays (FormGroup items have natural child navigation).
6023
+ *
6024
+ * Detection order:
6025
+ * 1. Normalization metadata (for simplified arrays — always available at normalization time)
6026
+ * 2. Existing item definitions (non-empty full-API arrays)
6027
+ * 3. Dynamically discovered key from handleAddFromEvent (empty full-API arrays)
6028
+ *
6029
+ * Uses linkedSignal: recomputes when field() changes, supports manual set()
6030
+ * for the dynamic discovery case.
6031
+ */
6032
+ private readonly primitiveFieldKey;
6033
+ /**
6034
+ * Gets the item templates (field definitions) for the array.
6035
+ * Each element can be either:
6036
+ * - A single FieldDef (primitive item) - normalized to [FieldDef]
6037
+ * - An array of FieldDefs (object item) - used as-is
6038
+ *
6039
+ * When auto-remove is configured, the remove button is appended to each item's
6040
+ * template list for rendering. This is purely visual — the form schema uses the
6041
+ * original primitive item definition (single FieldDef → FormControl → flat value).
6042
+ *
6043
+ * Returns normalized templates where all items are arrays for consistent handling.
6044
+ */
6045
+ private readonly itemTemplates;
4414
6046
  private readonly arrayFieldTrees;
4415
6047
  private readonly resolvedItemsSignal;
4416
6048
  private readonly updateVersion;
4417
- private readonly itemOrderSignal;
4418
- readonly resolvedItems: _angular_core.WritableSignal<ResolvedArrayItem[]>;
6049
+ /**
6050
+ * Map of item IDs to their current positions. O(1) lookup vs O(n) indexOf().
6051
+ * Used by child linkedSignals to reactively track their position in the array.
6052
+ */
6053
+ private readonly itemPositionMap;
6054
+ /** Read-only view of resolved items for template consumption. */
6055
+ readonly resolvedItems: _angular_core.Signal<ResolvedArrayItem[]>;
6056
+ /**
6057
+ * Caches the result of appending the auto-remove button to template arrays.
6058
+ * Keyed by template array reference — cache hits occur during recreate/resolution
6059
+ * operations where stored template references are reused. Add operations via
6060
+ * handleAddFromEvent always create a fresh `[...template]` copy (line ~277),
6061
+ * so each add is a cache miss by design (the spread is needed for mutability).
6062
+ */
6063
+ private readonly autoRemoveCache;
4419
6064
  constructor();
4420
6065
  private setupEffects;
4421
6066
  private setupEventHandlers;
6067
+ /**
6068
+ * Handles add operations from events (append, prepend, insert).
6069
+ * Creates resolved items FIRST, then updates form value.
6070
+ * This ensures prepend/insert work correctly - differential update sees "none"
6071
+ * because resolved items count already matches the new array length.
6072
+ *
6073
+ * Supports both primitive (single FieldDef) and object (FieldDef[]) templates.
6074
+ */
6075
+ private handleAddFromEvent;
4422
6076
  private performDifferentialUpdate;
4423
6077
  private resolveAllItems;
4424
6078
  private appendItems;
6079
+ /**
6080
+ * Creates an observable that resolves a single array item.
6081
+ *
6082
+ * Template resolution order:
6083
+ * 1. Use overrideTemplate if provided (from recreate with stored templates)
6084
+ * 2. Use itemTemplates[index] if within defined templates range
6085
+ * 3. Return undefined (item cannot be resolved without a template)
6086
+ */
4425
6087
  private createResolveItemObservable;
4426
- private addItem;
6088
+ /**
6089
+ * Appends the auto-remove button to a templates array for rendering.
6090
+ * The button is added for visual rendering only — it doesn't affect the form schema.
6091
+ * Original templates are stored in templateRegistry WITHOUT the remove button,
6092
+ * so this method is called during resolution to add it dynamically.
6093
+ *
6094
+ * Uses a WeakMap cache keyed by template array reference. Cache hits occur
6095
+ * during recreate/resolution paths where stored templates are reused.
6096
+ * Add operations always pass a fresh `[...template]` copy, so they miss
6097
+ * the cache intentionally (the copy is needed for mutable item construction).
6098
+ */
6099
+ private withAutoRemove;
6100
+ /**
6101
+ * Handles remove operations from events (pop, shift, removeAt).
6102
+ * Updates resolvedItems FIRST, then form value - this ensures differential
6103
+ * update sees "none" (lengths match) and avoids unnecessary recreates.
6104
+ * Remaining items' linkedSignal indices auto-update via itemPositionMap.
6105
+ */
4427
6106
  private removeItem;
4428
6107
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<ArrayFieldComponent<any>, never>;
4429
- 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>;
6108
+ 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>;
4430
6109
  }
4431
6110
 
4432
6111
  /**
@@ -4501,97 +6180,16 @@ declare function omit<T extends object, K extends keyof T>(obj: T, keys: K[]): O
4501
6180
  declare function interpolateParams(message: string, error: ValidationError): string;
4502
6181
 
4503
6182
  /**
4504
- * Registry service that maintains references to root form fields.
4505
- * This provides a type-safe way to access root form values from child fields
4506
- * without relying on internal Angular Forms API navigation.
4507
- *
4508
- * This service should be provided at the component level to ensure proper
4509
- * isolation between different form instances.
6183
+ * Registry service that provides access to the root form and its values.
4510
6184
  *
4511
- * IMPORTANT: This registry supports two registration patterns:
4512
- * 1. registerFormValueSignal() - Register the SOURCE signal BEFORE form creation
4513
- * This allows logic functions to read form values during schema evaluation.
4514
- * 2. registerRootForm() - Register the FieldTree AFTER form creation
4515
- * This provides access to the full form structure.
4516
- *
4517
- * For cross-field logic to work correctly, registerFormValueSignal() should be
4518
- * called BEFORE the form() call.
6185
+ * Constructed via factory in `provideDynamicFormDI` with signals from
6186
+ * `FormStateManager` — no DI injection, no Maps, no lifecycle.
4519
6187
  */
4520
6188
  declare class RootFormRegistryService {
4521
- private readonly _rootForms;
4522
- private readonly _formValueSignals;
4523
- private readonly formContexts;
4524
- /**
4525
- * Registers the form value signal BEFORE form creation.
4526
- *
4527
- * This is the recommended pattern for cross-field logic:
4528
- * 1. Create the formValue signal
4529
- * 2. Register it with registerFormValueSignal()
4530
- * 3. Create the form with form(formValue, schema(...))
4531
- *
4532
- * This allows logic functions to access form values during schema evaluation.
4533
- */
4534
- registerFormValueSignal(valueSignal: Signal<Record<string, unknown>>, formId?: string): void;
4535
- /**
4536
- * Gets the registered form value signal by id.
4537
- *
4538
- * This allows direct access to the source signal, which can be read
4539
- * reactively to get form values.
4540
- */
4541
- getFormValueSignal(formId?: string): Signal<Record<string, unknown>> | undefined;
4542
- /**
4543
- * Registers a root form field with an optional identifier.
4544
- * If no id is provided, uses 'default' as the key.
4545
- *
4546
- * This triggers reactive updates for any computed signals that
4547
- * depend on getRootForm().
4548
- */
4549
- registerRootForm(rootField: FieldTree<unknown>, formId?: string): void;
4550
- /**
4551
- * Gets a registered root form field by id.
4552
- *
4553
- * IMPORTANT: This method reads from a signal, which means any computed
4554
- * signal that calls this method will automatically re-evaluate when
4555
- * the form is registered or updated.
4556
- */
4557
- getRootForm(formId?: string): FieldTree<unknown> | undefined;
4558
- /**
4559
- * Gets the form value, preferring the direct signal if available.
4560
- *
4561
- * This method tries in order:
4562
- * 1. Form value signal (if registered) - reads directly from source signal
4563
- * 2. Root form's .value() (if registered) - reads from FieldTree
4564
- * 3. Returns empty object if nothing is registered
4565
- *
4566
- * Reading from signals creates reactive dependencies, so computeds will
4567
- * re-evaluate when form values change.
4568
- */
4569
- getFormValue(formId?: string): Record<string, unknown>;
4570
- /**
4571
- * Registers form context (field paths and metadata) for a specific form.
4572
- * This can be used to store additional context information.
4573
- */
4574
- registerFormContext(context: Record<string, unknown>, formId?: string): void;
4575
- /**
4576
- * Gets form context by id.
4577
- */
4578
- getFormContext(formId?: string): Record<string, unknown>;
4579
- /**
4580
- * Unregisters a form and its context.
4581
- * This should be called when a form component is destroyed to prevent memory leaks.
4582
- */
4583
- unregisterForm(formId?: string): void;
4584
- /**
4585
- * Gets all registered form IDs.
4586
- */
4587
- getRegisteredFormIds(): string[];
4588
- /**
4589
- * Clears all registered forms. Useful for testing.
4590
- */
4591
- clear(): void;
4592
- static ɵfac: _angular_core.ɵɵFactoryDeclaration<RootFormRegistryService, never>;
4593
- static ɵprov: _angular_core.ɵɵInjectableDeclaration<RootFormRegistryService>;
6189
+ readonly formValue: Signal<Record<string, unknown>>;
6190
+ readonly rootForm: Signal<FieldTree<Record<string, unknown>> | undefined>;
6191
+ constructor(formValue: Signal<Record<string, unknown>>, rootForm: Signal<FieldTree<Record<string, unknown>> | undefined>);
4594
6192
  }
4595
6193
 
4596
- export { ARRAY_CONTEXT, AddArrayItemEvent, ArrayFieldComponent, BUILT_IN_FIELDS, ConsoleLogger, DEFAULT_PROPS, DEFAULT_VALIDATION_MESSAGES, DynamicForm, DynamicFormError, DynamicFormLogger, DynamicTextPipe, EventBus, FIELD_REGISTRY, FIELD_SIGNAL_CONTEXT, FORM_OPTIONS, FormClearEvent, FormResetEvent, GroupFieldComponent, NextPageEvent, NoopLogger, PageChangeEvent, PreviousPageEvent, RemoveArrayItemEvent, RootFormRegistryService, RowFieldComponent, SubmitEvent, applyMetaToElement, applyValidator, applyValidators, arrayFieldMapper, baseFieldMapper, buildBaseInputs, createField, dynamicTextToObservable, field, formConfig, getArrayLength, groupFieldMapper, hasFormValue, interpolateParams, isArrayField, isCheckedField, isContainerField, isDisplayOnlyField, isEqual, isFormStateCondition, isGroupField, isLeafField, isPageField, isRowField, isValueBearingField, isValueField, omit, pageFieldMapper, provideDynamicForm, resolveNextButtonDisabled, resolveSubmitButtonDisabled, resolveTokens, rowFieldMapper, textFieldMapper, withEventFormValue, withLoggerConfig };
4597
- export type { ArrayAllowedChildren, ArrayContext, ArrayField, ArrayFieldTree, ArrayItemContext, AsyncCustomValidator, AsyncValidatorConfig, AvailableFieldTypes, BaseCheckedField, BaseValidatorConfig, BaseValueField, BuiltInValidatorConfig, ButtonLogicContext, CheckedFieldComponent, ConditionalExpression, ContainerFieldTypes, CustomFnConfig, CustomValidator, CustomValidatorConfig, DynamicFormFieldRegistry, DynamicText, EvaluationContext, ExtractField, ExtractFieldDefs, ExtractFormValue, FieldComponent, FieldDef, FieldMeta, FieldOption, FieldPathAccess, FieldRegistryContainers, FieldRegistryLeaves, FieldSignalContext, FieldTypeDefinition, FieldWithValidation, FormConfig, FormEvent, FormEventConstructor, FormMode, FormModeDetectionResult, FormOptions, FormStateCondition, GroupAllowedChildren, GroupField, HttpCustomValidator, HttpResourceRequest, HttpValidatorConfig, InferFormValue$1 as InferFormValue, LeafFieldTypes, Logger, LogicConfig, MapperFn, NarrowField, NarrowFields, NextButtonOptions, PageAllowedChildren, PageField, Prettify, RegisteredFieldTypes, RowAllowedChildren, RowField, SchemaApplicationConfig, SchemaDefinition, SubmissionActionResult, SubmissionConfig, SubmitButtonOptions, TextElementType, TextField, TextProps, TokenContext, ValidationError, ValidationMessages, ValidatorConfig, ValueFieldComponent, ValueHandlingMode, ValueType, WithInputSignals };
6194
+ 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, 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 };
6195
+ 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 };