@ng-forge/dynamic-forms 0.1.1 → 0.1.3

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.
Files changed (20) hide show
  1. package/README.md +33 -0
  2. package/fesm2022/ng-forge-dynamic-forms-integration.mjs +269 -0
  3. package/fesm2022/ng-forge-dynamic-forms-integration.mjs.map +1 -0
  4. package/fesm2022/{ng-forge-dynamic-forms-ng-forge-dynamic-forms-Dcd04rfS.mjs → ng-forge-dynamic-forms-ng-forge-dynamic-forms-B_676NHl.mjs} +989 -1211
  5. package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-B_676NHl.mjs.map +1 -0
  6. package/fesm2022/{ng-forge-dynamic-forms-page-field.component-DjrahUgb.mjs → ng-forge-dynamic-forms-page-field.component-YHFQcpCS.mjs} +31 -29
  7. package/fesm2022/ng-forge-dynamic-forms-page-field.component-YHFQcpCS.mjs.map +1 -0
  8. package/fesm2022/ng-forge-dynamic-forms-testing-test-button.harness-BenmCFcM.mjs +31 -0
  9. package/fesm2022/ng-forge-dynamic-forms-testing-test-button.harness-BenmCFcM.mjs.map +1 -0
  10. package/fesm2022/ng-forge-dynamic-forms-testing.mjs +968 -0
  11. package/fesm2022/ng-forge-dynamic-forms-testing.mjs.map +1 -0
  12. package/fesm2022/{ng-forge-dynamic-forms-text-field.component-7QVNm95W.mjs → ng-forge-dynamic-forms-text-field.component-CiYq73aF.mjs} +2 -2
  13. package/fesm2022/{ng-forge-dynamic-forms-text-field.component-7QVNm95W.mjs.map → ng-forge-dynamic-forms-text-field.component-CiYq73aF.mjs.map} +1 -1
  14. package/fesm2022/ng-forge-dynamic-forms.mjs +1 -1
  15. package/package.json +17 -3
  16. package/types/ng-forge-dynamic-forms-integration.d.ts +315 -0
  17. package/types/ng-forge-dynamic-forms-testing.d.ts +565 -0
  18. package/types/ng-forge-dynamic-forms.d.ts +353 -549
  19. package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-Dcd04rfS.mjs.map +0 -1
  20. package/fesm2022/ng-forge-dynamic-forms-page-field.component-DjrahUgb.mjs.map +0 -1
@@ -2,10 +2,10 @@ import * as rxjs from 'rxjs';
2
2
  import { Observable } from 'rxjs';
3
3
  import * as _angular_forms from '@angular/forms';
4
4
  import * as _angular_forms_signals from '@angular/forms/signals';
5
- import { ValidationError as ValidationError$1, FieldContext, TreeValidationResult, FieldTree, Schema, form, SchemaPath, SchemaPathTree } from '@angular/forms/signals';
5
+ import { ValidationError as ValidationError$1, FieldContext, TreeValidationResult, FieldTree, Schema, SchemaPath, SchemaPathTree } from '@angular/forms/signals';
6
6
  import * as _ng_forge_dynamic_forms from '@ng-forge/dynamic-forms';
7
7
  import * as _angular_core from '@angular/core';
8
- import { Signal, ResourceRef, Injector, WritableSignal, InjectionToken, Type, OnDestroy, InputSignal, EnvironmentProviders, PipeTransform } from '@angular/core';
8
+ import { Signal, ResourceRef, Injector, WritableSignal, InjectionToken, Type, OnDestroy, InputSignal, Provider, EnvironmentProviders, PipeTransform } from '@angular/core';
9
9
 
10
10
  type WithInputSignals<T> = {
11
11
  readonly [K in keyof T]-?: Signal<T[K]>;
@@ -654,6 +654,57 @@ type RegisteredFieldTypes = ContainerFieldTypes | LeafFieldTypes;
654
654
  * Extract field types that are available in the registry
655
655
  */
656
656
  type AvailableFieldTypes = keyof DynamicFormFieldRegistry['containers'] | keyof DynamicFormFieldRegistry['leaves'];
657
+ /**
658
+ * Combined registry mapping type names to field definitions.
659
+ * This flattens containers and leaves into a single mapping.
660
+ */
661
+ type FieldTypeMap = DynamicFormFieldRegistry['containers'] & DynamicFormFieldRegistry['leaves'];
662
+ /**
663
+ * Extract a specific field type from RegisteredFieldTypes based on the `type` discriminant.
664
+ * This enables proper type narrowing when defining fields.
665
+ *
666
+ * @example
667
+ * ```typescript
668
+ * // Extract a specific field type
669
+ * type MyInputField = ExtractField<'input'>;
670
+ *
671
+ * // Use in field definitions for proper props inference
672
+ * const field: ExtractField<'input'> = {
673
+ * type: 'input',
674
+ * key: 'email',
675
+ * value: '',
676
+ * props: { type: 'email' } // Only input props allowed here
677
+ * };
678
+ * ```
679
+ */
680
+ type ExtractField<T extends AvailableFieldTypes> = T extends keyof FieldTypeMap ? FieldTypeMap[T] : never;
681
+ /**
682
+ * Narrow a field definition based on its `type` property.
683
+ * Use this to get proper type inference when working with field unions.
684
+ *
685
+ * @example
686
+ * ```typescript
687
+ * function processField<T extends RegisteredFieldTypes>(field: T): NarrowField<T> {
688
+ * return field as NarrowField<T>;
689
+ * }
690
+ * ```
691
+ */
692
+ type NarrowField<T> = T extends {
693
+ type: infer TType;
694
+ } ? (TType extends AvailableFieldTypes ? ExtractField<TType> : T) : T;
695
+ /**
696
+ * Narrow each field in an array based on its `type` property.
697
+ * Use with `satisfies` to get proper type inference for field arrays.
698
+ *
699
+ * @example
700
+ * ```typescript
701
+ * const fields = [
702
+ * { type: 'input', key: 'name', value: '', props: { type: 'text' } },
703
+ * { type: 'select', key: 'country', value: 'us', options: [...] },
704
+ * ] as const satisfies NarrowFields;
705
+ * ```
706
+ */
707
+ type NarrowFields = readonly NarrowField<RegisteredFieldTypes>[];
657
708
 
658
709
  /**
659
710
  * Helper type to convert union to intersection
@@ -664,27 +715,49 @@ type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
664
715
  * Depth counter for recursion limiting (prevents infinite type instantiation)
665
716
  */
666
717
  type Depth = [never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
718
+ /**
719
+ * Widens literal types to their primitive equivalents.
720
+ * This prevents `as const` from over-narrowing types like `''` to literal `''` instead of `string`.
721
+ *
722
+ * @example
723
+ * ```typescript
724
+ * type A = Widen<''>; // string
725
+ * type B = Widen<false>; // boolean
726
+ * type C = Widen<42>; // number
727
+ * type D = Widen<string[]>; // string[]
728
+ * ```
729
+ */
730
+ type Widen<T> = T extends string ? string : T extends number ? number : T extends boolean ? boolean : T;
731
+ /**
732
+ * Infer value type based on field type and props.
733
+ * - Slider fields: always number
734
+ * - Input fields with props.type: 'number': number
735
+ * - Other fields: widen the literal value type
736
+ */
737
+ type InferValueType<T, V> = T extends {
738
+ type: 'slider';
739
+ } ? number : T extends {
740
+ type: 'input';
741
+ props: {
742
+ type: 'number';
743
+ };
744
+ } ? number : Widen<V>;
745
+ /** Make type optional if field is not required */
746
+ type MaybeOptional<T, V> = T extends {
747
+ required: true;
748
+ } ? V : V | undefined;
667
749
  /**
668
750
  * Process a single field and determine its contribution to the form value type
669
- * Handles:
670
- * - Page fields: flatten children (valueHandling: 'flatten')
671
- * - Row fields: flatten children (valueHandling: 'flatten')
672
- * - Group fields: nest children under group key (valueHandling: 'include')
673
- * - Text fields: exclude from values (valueHandling: 'exclude')
674
- * - Value fields: include with their key and value type
675
751
  */
676
752
  type ProcessField<T, D extends number = 5> = [D] extends [never] ? Record<string, unknown> : T extends {
677
- type: 'page';
678
- fields: infer TFields;
679
- } ? TFields extends RegisteredFieldTypes[] ? InferFormValueWithDepth<TFields, Depth[D]> : never : T extends {
680
- type: 'row';
681
- fields: infer TFields;
682
- } ? TFields extends RegisteredFieldTypes[] ? InferFormValueWithDepth<TFields, Depth[D]> : never : T extends {
753
+ type: 'page' | 'row';
754
+ fields: infer F;
755
+ } ? F extends RegisteredFieldTypes[] ? InferFormValueWithDepth<F, Depth[D]> : never : T extends {
683
756
  type: 'group';
684
757
  key: infer K;
685
- fields: infer TFields;
686
- } ? K extends string ? TFields extends RegisteredFieldTypes[] ? {
687
- [P in K]: InferFormValueWithDepth<TFields, Depth[D]>;
758
+ fields: infer F;
759
+ } ? K extends string ? F extends RegisteredFieldTypes[] ? {
760
+ [P in K]: InferFormValueWithDepth<F, Depth[D]>;
688
761
  } : {
689
762
  [P in K]: Record<string, unknown>;
690
763
  } : never : T extends {
@@ -692,39 +765,46 @@ type ProcessField<T, D extends number = 5> = [D] extends [never] ? Record<string
692
765
  } ? never : T extends {
693
766
  key: infer K;
694
767
  value: infer V;
695
- required: true;
696
- } ? K extends string ? {
697
- [P in K]: V;
698
- } : never : T extends {
699
- key: infer K;
700
- value: infer V;
701
768
  } ? K extends string ? {
702
- [P in K]: V | undefined;
769
+ [P in K]: MaybeOptional<T, InferValueType<T, V>>;
703
770
  } : never : never;
704
771
  /**
705
772
  * Internal helper with depth tracking
706
773
  */
707
774
  type InferFormValueWithDepth<T extends RegisteredFieldTypes[], D extends number = 5> = UnionToIntersection<ProcessField<T[number], D>>;
708
775
  /**
709
- * Infer form value type from an array of field definitions
710
- * Recursively processes nested structures and merges the results
711
- * Limited to 5 levels of nesting to prevent infinite type instantiation
776
+ * Helper to extract fields from either fields array or FormConfig
777
+ */
778
+ type ExtractFields<T> = T extends {
779
+ fields: infer TFields;
780
+ } ? TFields extends readonly RegisteredFieldTypes[] ? TFields : never : T extends readonly RegisteredFieldTypes[] ? T : never;
781
+ /**
782
+ * Infer form value type from fields array or FormConfig.
783
+ * Recursively processes nested structures and merges the results.
784
+ * Limited to 5 levels of nesting to prevent infinite type instantiation.
712
785
  *
713
786
  * @example
714
787
  * ```typescript
788
+ * // From fields array
715
789
  * const fields = [
716
790
  * { type: 'input', key: 'email', value: '', required: true },
717
- * { type: 'group', key: 'address', fields: [
718
- * { type: 'input', key: 'street', value: '' },
719
- * { type: 'input', key: 'city', value: '' }
720
- * ]}
791
+ * { type: 'input', key: 'name', value: '' }
721
792
  * ] as const;
793
+ * type FieldsValue = InferFormValue<typeof fields>;
722
794
  *
723
- * type FormValue = InferFormValue<typeof fields>;
724
- * // Result: { email: string; address: { street?: string; city?: string } }
795
+ * // From FormConfig
796
+ * const config = {
797
+ * fields: [
798
+ * { type: 'input', key: 'email', value: '', required: true },
799
+ * { type: 'input', key: 'name', value: '' }
800
+ * ]
801
+ * } as const satisfies FormConfig;
802
+ * type ConfigValue = InferFormValue<typeof config>;
803
+ *
804
+ * // Result: { email: string; name?: string }
725
805
  * ```
726
806
  */
727
- type InferFormValue$1<T extends RegisteredFieldTypes[]> = InferFormValueWithDepth<T, 5>;
807
+ type InferFormValue$1<T> = ExtractFields<T> extends readonly RegisteredFieldTypes[] ? InferFormValueWithDepth<ExtractFields<T> extends RegisteredFieldTypes[] ? ExtractFields<T> : [...ExtractFields<T>], 5> : never;
728
808
 
729
809
  /**
730
810
  * Base configuration shared by all validators
@@ -1003,7 +1083,7 @@ interface AsyncCustomValidator<TValue = unknown, TParams = unknown, TResult = un
1003
1083
  interface HttpResourceRequest {
1004
1084
  url: string;
1005
1085
  method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
1006
- body?: any;
1086
+ body?: unknown;
1007
1087
  headers?: Record<string, string | string[]>;
1008
1088
  }
1009
1089
  /**
@@ -1100,6 +1180,33 @@ interface HttpCustomValidator<TValue = unknown, TResult = unknown> {
1100
1180
  readonly onError?: (error: unknown, ctx: FieldContext<TValue>) => ValidationError$1 | ValidationError$1[] | null;
1101
1181
  }
1102
1182
 
1183
+ /**
1184
+ * Interface for dynamic forms logger implementations.
1185
+ * Allows custom logging integrations (Sentry, DataDog, etc.)
1186
+ */
1187
+ interface DynamicFormLogger {
1188
+ /**
1189
+ * Log a debug message.
1190
+ * Use for detailed diagnostic information during development.
1191
+ */
1192
+ debug(message: string, ...args: unknown[]): void;
1193
+ /**
1194
+ * Log an info message.
1195
+ * Use for general operational information.
1196
+ */
1197
+ info(message: string, ...args: unknown[]): void;
1198
+ /**
1199
+ * Log a warning message.
1200
+ * Use for potentially problematic situations that don't prevent operation.
1201
+ */
1202
+ warn(message: string, ...args: unknown[]): void;
1203
+ /**
1204
+ * Log an error message.
1205
+ * Use for error conditions that may affect functionality.
1206
+ */
1207
+ error(message: string, ...args: unknown[]): void;
1208
+ }
1209
+
1103
1210
  interface EvaluationContext<TValue = unknown> {
1104
1211
  /** Current field value */
1105
1212
  fieldValue: TValue;
@@ -1109,6 +1216,8 @@ interface EvaluationContext<TValue = unknown> {
1109
1216
  fieldPath: string;
1110
1217
  /** Custom evaluation functions */
1111
1218
  customFunctions?: Record<string, (context: EvaluationContext) => unknown>;
1219
+ /** Logger for diagnostic output */
1220
+ logger: DynamicFormLogger;
1112
1221
  /** Allow additional properties for flexible expression evaluation */
1113
1222
  [key: string]: unknown;
1114
1223
  }
@@ -1290,19 +1399,18 @@ interface SubmissionConfig<TValue = unknown> {
1290
1399
  *
1291
1400
  * @example
1292
1401
  * ```typescript
1293
- * const formConfig: FormConfig = {
1402
+ * const formConfig = {
1294
1403
  * fields: [
1295
- * { type: 'input', key: 'email', value: '', label: 'Email', required: true, email: true },
1404
+ * { type: 'input', key: 'email', value: '', label: 'Email', required: true },
1296
1405
  * { type: 'group', key: 'address', label: 'Address', fields: [
1297
1406
  * { type: 'input', key: 'street', value: '', label: 'Street' },
1298
1407
  * { type: 'input', key: 'city', value: '', label: 'City' }
1299
1408
  * ]},
1300
- * { type: 'button', key: 'submit', label: 'Submit', buttonType: 'submit' }
1301
1409
  * ],
1302
- * schemas: [
1303
- * { name: 'emailSchema', schema: { email: validators.email } }
1304
- * ]
1305
- * };
1410
+ * } as const satisfies FormConfig;
1411
+ *
1412
+ * // Infer form value type from config
1413
+ * type FormValue = InferFormValue<typeof formConfig>;
1306
1414
  * ```
1307
1415
  *
1308
1416
  * @typeParam TFields - Array of registered field types available for this form
@@ -1310,7 +1418,7 @@ interface SubmissionConfig<TValue = unknown> {
1310
1418
  *
1311
1419
  * @public
1312
1420
  */
1313
- interface FormConfig<TFields extends RegisteredFieldTypes[] = RegisteredFieldTypes[], TValue = InferFormValue$1<TFields>> {
1421
+ interface FormConfig<TFields extends NarrowFields | RegisteredFieldTypes[] = RegisteredFieldTypes[], TValue = InferFormValue$1<TFields extends readonly RegisteredFieldTypes[] ? TFields : RegisteredFieldTypes[]>> {
1314
1422
  /**
1315
1423
  * Array of field definitions that define the form structure.
1316
1424
  *
@@ -1773,12 +1881,15 @@ interface NextButtonOptions {
1773
1881
  *
1774
1882
  * Gives mappers and components access to form state, values, and configuration.
1775
1883
  * Container fields (Group, Array) create scoped contexts with nested forms.
1884
+ *
1885
+ * The `form` property uses Angular's FieldTree which includes Subfields<TModel>
1886
+ * for type-safe child field access via bracket notation when TModel is a Record.
1776
1887
  */
1777
- interface FieldSignalContext<TModel = unknown> {
1888
+ interface FieldSignalContext<TModel extends Record<string, unknown> = Record<string, unknown>> {
1778
1889
  injector: Injector;
1779
1890
  value: WritableSignal<Partial<TModel> | undefined>;
1780
1891
  defaultValues: () => TModel;
1781
- form: ReturnType<typeof form<TModel>>;
1892
+ form: FieldTree<TModel>;
1782
1893
  defaultValidationMessages?: ValidationMessages;
1783
1894
  /** Form-level options used by button mappers to determine disabled state defaults. */
1784
1895
  formOptions?: FormOptions;
@@ -1867,14 +1978,6 @@ interface FieldTypeDefinition<T extends FieldDef<any> = any> {
1867
1978
  * ```
1868
1979
  */
1869
1980
  declare const FIELD_REGISTRY: InjectionToken<Map<string, FieldTypeDefinition<any>>>;
1870
- /**
1871
- * Gets the value handling mode for a specific field type from the registry.
1872
- *
1873
- * @param fieldType - The field type identifier
1874
- * @param registry - The field type registry
1875
- * @returns The value handling mode ('include' is the default if not specified)
1876
- */
1877
- declare function getFieldValueHandling(fieldType: string, registry: Map<string, FieldTypeDefinition>): ValueHandlingMode;
1878
1981
 
1879
1982
  /**
1880
1983
  * Resolved field ready for rendering with ngComponentOutlet.
@@ -2094,7 +2197,7 @@ declare class PageNavigationStateChangeEvent implements FormEvent {
2094
2197
  * </form>
2095
2198
  * ```
2096
2199
  */
2097
- declare class DynamicForm<TFields extends RegisteredFieldTypes[] = RegisteredFieldTypes[], TModel = InferFormValue$1<TFields>> implements OnDestroy {
2200
+ declare class DynamicForm<TFields extends RegisteredFieldTypes[] = RegisteredFieldTypes[], TModel extends Record<string, unknown> = InferFormValue$1<TFields> & Record<string, unknown>> implements OnDestroy {
2098
2201
  private readonly destroyRef;
2099
2202
  private readonly fieldRegistry;
2100
2203
  private readonly injector;
@@ -2102,6 +2205,7 @@ declare class DynamicForm<TFields extends RegisteredFieldTypes[] = RegisteredFie
2102
2205
  private readonly rootFormRegistry;
2103
2206
  private readonly functionRegistry;
2104
2207
  private readonly schemaRegistry;
2208
+ private readonly logger;
2105
2209
  /** Form configuration defining the structure, validation, and behavior. */
2106
2210
  config: InputSignal<FormConfig<TFields>>;
2107
2211
  /** Runtime form options that override config options when provided. */
@@ -2151,11 +2255,6 @@ declare class DynamicForm<TFields extends RegisteredFieldTypes[] = RegisteredFie
2151
2255
  readonly disabled: Signal<boolean>;
2152
2256
  /** Whether the form is currently submitting. */
2153
2257
  readonly submitting: Signal<boolean>;
2154
- /**
2155
- * Announcement message for screen readers.
2156
- * Updates when form submission fails due to validation errors.
2157
- */
2158
- readonly errorAnnouncement: _angular_core.WritableSignal<string>;
2159
2258
  readonly initialized$: rxjs.Observable<boolean>;
2160
2259
  /** Emits when form validity changes. */
2161
2260
  readonly validityChange: _angular_core.OutputRef<boolean>;
@@ -2182,18 +2281,7 @@ declare class DynamicForm<TFields extends RegisteredFieldTypes[] = RegisteredFie
2182
2281
  /** Emits when page navigation state changes (canGoNext, canGoPrevious, etc.). */
2183
2282
  readonly onPageNavigationStateChange: _angular_core.OutputRef<PageNavigationStateChangeEvent>;
2184
2283
  protected readonly resolvedFields: Signal<ResolvedField[]>;
2185
- private readonly syncEntityToValue;
2186
- private readonly clearErrorsOnConfigChange;
2187
- private readonly emitPagedFormInitialized;
2188
- private readonly emitNonPagedFormInitialized;
2189
- private readonly handleFormReset;
2190
- private readonly handleFormClear;
2191
- /**
2192
- * Announces form validation errors to screen readers when submission is attempted
2193
- * on an invalid form.
2194
- */
2195
- private readonly handleErrorAnnouncement;
2196
- private readonly handleSubmission;
2284
+ constructor();
2197
2285
  /**
2198
2286
  * Handles native form submission triggered by:
2199
2287
  * - Pressing Enter in an input field
@@ -2206,21 +2294,32 @@ declare class DynamicForm<TFields extends RegisteredFieldTypes[] = RegisteredFie
2206
2294
  * @param event - The native submit event from the form element
2207
2295
  */
2208
2296
  protected onNativeSubmit(event: Event): void;
2297
+ private setupEffects;
2298
+ private setupEventHandlers;
2209
2299
  private registerValidatorsFromConfig;
2210
2300
  private createFormSetupFromConfig;
2211
2301
  private createEmptyFormSetup;
2212
2302
  private onFormReset;
2213
2303
  private onFormClear;
2214
- /**
2215
- * Counts the total number of validation errors across all fields.
2216
- * Used for screen reader announcements.
2217
- */
2218
- private countErrors;
2219
2304
  ngOnDestroy(): void;
2220
2305
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<DynamicForm<any, any>, never>;
2221
2306
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<DynamicForm<any, any>, "form[dynamic-form]", never, { "config": { "alias": "dynamic-form"; "required": true; "isSignal": true; }; "formOptions": { "alias": "formOptions"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; "validityChange": "validityChange"; "dirtyChange": "dirtyChange"; "submitted": "submitted"; "reset": "reset"; "cleared": "cleared"; "events": "events"; "initialized": "initialized"; "onPageChange": "onPageChange"; "onPageNavigationStateChange": "onPageNavigationStateChange"; }, never, never, true, never>;
2222
2307
  }
2223
2308
 
2309
+ /**
2310
+ * Base interface for dynamic form features.
2311
+ * Features are configuration options that can be passed to provideDynamicForm
2312
+ * alongside field type definitions.
2313
+ *
2314
+ * Uses Angular-style internal marker (ɵkind) to distinguish from field types.
2315
+ */
2316
+ interface DynamicFormFeature<TKind extends string = string> {
2317
+ /** Internal marker to identify this as a feature, not a field type */
2318
+ ɵkind: TKind;
2319
+ /** Providers to register for this feature */
2320
+ ɵproviders: Provider[];
2321
+ }
2322
+
2224
2323
  /**
2225
2324
  * Extract FieldDef type from FieldTypeDefinition
2226
2325
  */
@@ -2242,13 +2341,23 @@ type ProvideDynamicFormResult<T extends FieldTypeDefinition[]> = EnvironmentProv
2242
2341
  __fieldDefs?: FieldDefUnion<T>;
2243
2342
  __formValue?: InferFormValue<FieldDefUnion<T>[]>;
2244
2343
  };
2344
+ /**
2345
+ * Union type for items that can be passed to provideDynamicForm
2346
+ */
2347
+ type FieldTypeOrFeature = FieldTypeDefinition | DynamicFormFeature;
2348
+ /**
2349
+ * Extract only FieldTypeDefinition items from a tuple type
2350
+ */
2351
+ type ExtractFieldTypes<T extends FieldTypeOrFeature[]> = {
2352
+ [K in keyof T]: T[K] extends FieldTypeDefinition ? T[K] : never;
2353
+ }[number] extends infer U ? U extends FieldTypeDefinition ? U[] : never : never;
2245
2354
  /**
2246
2355
  * Provider function to configure the dynamic form system with field types and options.
2247
2356
  *
2248
2357
  * This function creates environment providers that can be used at application or route level
2249
2358
  * to register field types. It provides type-safe field registration with automatic type inference.
2250
2359
  *
2251
- * @param fieldTypes - Custom field type definitions to register alongside built-in types
2360
+ * @param items - Field type definitions and/or features (like withLogger)
2252
2361
  * @returns Environment providers for dependency injection with type inference
2253
2362
  *
2254
2363
  * @example
@@ -2263,6 +2372,19 @@ type ProvideDynamicFormResult<T extends FieldTypeDefinition[]> = EnvironmentProv
2263
2372
  *
2264
2373
  * @example
2265
2374
  * ```typescript
2375
+ * // With logging configuration
2376
+ * bootstrapApplication(AppComponent, {
2377
+ * providers: [
2378
+ * provideDynamicForm(
2379
+ * ...withMaterialFields(),
2380
+ * withLogger({ level: LogLevel.Debug })
2381
+ * )
2382
+ * ]
2383
+ * });
2384
+ * ```
2385
+ *
2386
+ * @example
2387
+ * ```typescript
2266
2388
  * // Route-level setup
2267
2389
  * const routes: Routes = [
2268
2390
  * {
@@ -2289,7 +2411,7 @@ type ProvideDynamicFormResult<T extends FieldTypeDefinition[]> = EnvironmentProv
2289
2411
  *
2290
2412
  * @public
2291
2413
  */
2292
- declare function provideDynamicForm<const T extends FieldTypeDefinition[]>(...fieldTypes: T): ProvideDynamicFormResult<T>;
2414
+ declare function provideDynamicForm<const T extends FieldTypeOrFeature[]>(...items: T): ProvideDynamicFormResult<ExtractFieldTypes<T> extends FieldTypeDefinition[] ? ExtractFieldTypes<T> : FieldTypeDefinition[]>;
2293
2415
  /**
2294
2416
  * Extract FieldDef types from provider result
2295
2417
  */
@@ -2336,6 +2458,97 @@ type ExtractFormValue<T> = T extends {
2336
2458
  */
2337
2459
  declare const BUILT_IN_FIELDS: FieldTypeDefinition[];
2338
2460
 
2461
+ /**
2462
+ * Log levels for the dynamic forms logger.
2463
+ * Each level includes all levels above it (more severe).
2464
+ *
2465
+ * - Off: No logging
2466
+ * - Error: Only errors
2467
+ * - Warn: Errors + warnings
2468
+ * - Info: Errors + warnings + info
2469
+ * - Debug: All messages including debug
2470
+ */
2471
+ declare enum LogLevel {
2472
+ /** Disable all logging */
2473
+ Off = 0,
2474
+ /** Only log errors */
2475
+ Error = 1,
2476
+ /** Log errors and warnings */
2477
+ Warn = 2,
2478
+ /** Log errors, warnings, and info messages */
2479
+ Info = 3,
2480
+ /** Log all messages including debug */
2481
+ Debug = 4
2482
+ }
2483
+
2484
+ /**
2485
+ * Configuration options for the logger feature
2486
+ */
2487
+ interface LoggerFeatureOptions {
2488
+ /**
2489
+ * Log level to use. Messages below this level will be filtered.
2490
+ * @default LogLevel.Warn in dev, LogLevel.Off in prod
2491
+ */
2492
+ level?: LogLevel;
2493
+ /**
2494
+ * Custom logger implementation.
2495
+ * Use this to integrate with external logging services (Sentry, DataDog, etc.)
2496
+ * If provided, `level` is ignored - your implementation controls filtering.
2497
+ */
2498
+ logger?: DynamicFormLogger;
2499
+ }
2500
+ /**
2501
+ * Configure logging for dynamic forms.
2502
+ *
2503
+ * @example
2504
+ * ```typescript
2505
+ * // Set log level
2506
+ * provideDynamicForm(
2507
+ * ...withMaterialFields(),
2508
+ * withLogger({ level: LogLevel.Debug })
2509
+ * )
2510
+ *
2511
+ * // Disable logging entirely
2512
+ * provideDynamicForm(
2513
+ * ...withMaterialFields(),
2514
+ * withLogger({ level: LogLevel.Off })
2515
+ * )
2516
+ *
2517
+ * // Custom logger implementation
2518
+ * provideDynamicForm(
2519
+ * ...withMaterialFields(),
2520
+ * withLogger({ logger: mySentryLogger })
2521
+ * )
2522
+ * ```
2523
+ *
2524
+ * @param options - Logger configuration options
2525
+ * @returns A DynamicFormFeature that configures logging
2526
+ */
2527
+ declare function withLogger(options?: LoggerFeatureOptions): DynamicFormFeature<'logger'>;
2528
+
2529
+ /**
2530
+ * Injection token for the dynamic forms logger.
2531
+ *
2532
+ * Default behavior:
2533
+ * - Development mode: ConsoleLogger with LogLevel.Warn
2534
+ * - Production mode: NoopLogger (silent)
2535
+ *
2536
+ * Override by providing a custom logger via withLogger() feature
2537
+ * or by providing directly to this token.
2538
+ */
2539
+ declare const DYNAMIC_FORM_LOGGER: InjectionToken<DynamicFormLogger>;
2540
+
2541
+ /**
2542
+ * No-operation logger implementation.
2543
+ * All methods are no-ops - used in production by default.
2544
+ */
2545
+ declare class NoopLogger implements DynamicFormLogger {
2546
+ debug(): void;
2547
+ info(): void;
2548
+ warn(): void;
2549
+ error(): void;
2550
+ }
2551
+
2339
2552
  interface FieldOption<T = unknown> {
2340
2553
  label: DynamicText;
2341
2554
  value: T;
@@ -2372,7 +2585,7 @@ interface FieldOption<T = unknown> {
2372
2585
  * }
2373
2586
  * ```
2374
2587
  */
2375
- declare const FIELD_SIGNAL_CONTEXT: InjectionToken<FieldSignalContext<unknown>>;
2588
+ declare const FIELD_SIGNAL_CONTEXT: InjectionToken<FieldSignalContext<Record<string, unknown>>>;
2376
2589
  /**
2377
2590
  * Injection token for providing array context metadata to mappers and components.
2378
2591
  *
@@ -2400,8 +2613,8 @@ declare const ARRAY_CONTEXT: InjectionToken<ArrayContext>;
2400
2613
  * @public
2401
2614
  */
2402
2615
  interface ButtonLogicContext {
2403
- /** The form's FieldTree instance */
2404
- form: FieldTree<unknown>;
2616
+ /** The form's FieldTree instance (supports both string and number keys for array indices) */
2617
+ form: FieldTree<unknown, string | number>;
2405
2618
  /** Form-level options */
2406
2619
  formOptions?: FormOptions;
2407
2620
  /** Field-level logic array (if provided) */
@@ -2412,6 +2625,8 @@ interface ButtonLogicContext {
2412
2625
  currentPageValid?: Signal<boolean>;
2413
2626
  /** Current form value for evaluating conditional expressions */
2414
2627
  formValue?: unknown;
2628
+ /** Optional logger for diagnostic output. Falls back to no-op logger if not provided. */
2629
+ logger?: DynamicFormLogger;
2415
2630
  }
2416
2631
  /**
2417
2632
  * Resolves the disabled state for a submit button.
@@ -2511,223 +2726,8 @@ declare function isCheckedField<TProps>(field: FieldDef<TProps>): field is BaseC
2511
2726
  type ExcludedKeys = 'type' | 'conditionals' | 'value' | 'disabled' | 'readonly' | 'hidden' | 'col' | keyof FieldWithValidation;
2512
2727
  type CheckedFieldComponent<T extends BaseCheckedField<any>> = Prettify<WithInputSignals<Omit<T, ExcludedKeys>>>;
2513
2728
 
2514
- /**
2515
- * HTML input types supported by input fields
2516
- */
2517
- type InputType = 'text' | 'email' | 'password' | 'number' | 'tel' | 'url';
2518
- /**
2519
- * Props for input fields with optional inputType specification.
2520
- * This is a generic interface that can be extended by framework-specific implementations.
2521
- */
2522
- interface InputProps<T extends InputType = InputType> {
2523
- /**
2524
- * The HTML input type. Determines the value type:
2525
- * - 'number': value will be number
2526
- * - 'text', 'email', 'password', 'tel', 'url': value will be string
2527
- */
2528
- type?: T;
2529
- placeholder?: DynamicText;
2530
- }
2531
- /**
2532
- * Number input field with strict number value type.
2533
- * When props.type is 'number', value must be number.
2534
- */
2535
- interface NumberInputField<TProps extends {
2536
- type?: string;
2537
- } = {
2538
- type: 'number';
2539
- placeholder?: DynamicText;
2540
- }> extends BaseValueField<TProps, number> {
2541
- type: 'input';
2542
- props?: TProps;
2543
- }
2544
- /**
2545
- * String input field with strict string value type.
2546
- * When props.type is text/email/etc, value must be string.
2547
- */
2548
- interface StringInputField<TProps extends {
2549
- type?: string;
2550
- } = {
2551
- type: 'text' | 'email' | 'password' | 'tel' | 'url';
2552
- placeholder?: DynamicText;
2553
- }> extends BaseValueField<TProps, string> {
2554
- type: 'input';
2555
- props?: TProps;
2556
- }
2557
- /**
2558
- * Helper type to extract the input type from props
2559
- */
2560
- type ExtractInputType<TProps> = TProps extends {
2561
- type?: infer T;
2562
- } ? T : InputType;
2563
- /**
2564
- * Fallback variant when props is undefined - allows both string and number values
2565
- */
2566
- interface UndefinedPropsInputField extends BaseValueField<undefined, string | number> {
2567
- type: 'input';
2568
- props?: never;
2569
- }
2570
- /**
2571
- * Builds discriminated union based on what input types are possible in TProps
2572
- */
2573
- type BuildInputFieldUnion<TProps extends {
2574
- type?: string;
2575
- }> = (ExtractInputType<TProps> extends infer T ? T extends string ? ('number' extends T ? NumberInputField<TProps> : never) | (Exclude<T, 'number'> extends never ? never : StringInputField<TProps>) : StringInputField<TProps> : never) | UndefinedPropsInputField;
2576
- /**
2577
- * Input field with automatic type-safe value inference.
2578
- * TypeScript automatically infers the correct value type based on props.type.
2579
- *
2580
- * @example
2581
- * // Direct usage - automatic strict type safety
2582
- * const numberField: InputField = {
2583
- * type: 'input',
2584
- * key: 'age',
2585
- * props: { type: 'number' },
2586
- * value: 25 // ✓ number only (string is type error!)
2587
- * };
2588
- *
2589
- * const textField: InputField = {
2590
- * type: 'input',
2591
- * key: 'name',
2592
- * props: { type: 'text' },
2593
- * value: 'hello' // ✓ string only (number is type error!)
2594
- * };
2595
- *
2596
- * @example
2597
- * // Framework usage - extend with custom props
2598
- * interface MatInputProps extends InputProps {
2599
- * appearance?: 'fill' | 'outline';
2600
- * hint?: string;
2601
- * }
2602
- * type MatInputField = InputField<MatInputProps>; // Automatically gets discriminated union
2603
- */
2604
- type InputField<TProps extends {
2605
- type?: string;
2606
- } = InputProps> = BuildInputFieldUnion<TProps>;
2607
-
2608
- interface CheckboxField<TProps> extends BaseCheckedField<TProps> {
2609
- type: 'checkbox';
2610
- }
2611
-
2612
- interface SelectProps {
2613
- placeholder?: DynamicText;
2614
- }
2615
- interface SelectField<T, TProps = SelectProps> extends BaseValueField<TProps, T> {
2616
- type: 'select';
2617
- readonly options: readonly FieldOption<T>[];
2618
- }
2619
-
2620
- /**
2621
- * Event arguments that can contain static values or tokens to be resolved at runtime
2622
- * Tokens supported: $key, $index, $arrayKey, formValue
2623
- */
2624
- type EventArgs = readonly (string | number | boolean | null | undefined)[];
2625
- /**
2626
- * Interface for button fields
2627
- */
2628
- interface ButtonField<TProps, TEvent extends FormEvent> extends FieldDef<TProps> {
2629
- type: 'button';
2630
- event: FormEventConstructor<TEvent>;
2631
- /**
2632
- * Optional arguments to pass to the event constructor.
2633
- * Can contain special tokens that will be resolved at runtime:
2634
- * - $key: The current field key
2635
- * - $index: The array index (if inside an array field)
2636
- * - $arrayKey: The parent array field key (if inside an array field)
2637
- * - formValue: Access to the current form value for indexing
2638
- *
2639
- * @example
2640
- * eventArgs: ['$arrayKey', '$index']
2641
- * eventArgs: ['contacts', 0]
2642
- */
2643
- eventArgs?: EventArgs;
2644
- }
2645
-
2646
- interface TextareaProps {
2647
- placeholder?: DynamicText;
2648
- rows?: number;
2649
- cols?: number | undefined;
2650
- }
2651
- interface TextareaField<TProps = TextareaProps> extends BaseValueField<TProps, string> {
2652
- type: 'textarea';
2653
- maxLength?: number | undefined;
2654
- }
2655
-
2656
- interface RadioField<T, TProps> extends BaseValueField<TProps, T> {
2657
- type: 'radio';
2658
- options: FieldOption<T>[];
2659
- }
2660
-
2661
- /**
2662
- * Multi-checkbox field for selecting multiple values from a list of options.
2663
- * The value is an array of selected option values.
2664
- *
2665
- * @example
2666
- * const tagsField: MultiCheckboxField<string> = {
2667
- * type: 'multi-checkbox',
2668
- * key: 'tags',
2669
- * value: ['tag1', 'tag2'], // Array of selected values
2670
- * options: [
2671
- * { label: 'Tag 1', value: 'tag1' },
2672
- * { label: 'Tag 2', value: 'tag2' },
2673
- * { label: 'Tag 3', value: 'tag3' }
2674
- * ]
2675
- * };
2676
- */
2677
- interface MultiCheckboxField<TValue, TProps = object> extends BaseValueField<TProps, TValue[]> {
2678
- type: 'multi-checkbox';
2679
- options: FieldOption<TValue>[];
2680
- }
2681
-
2682
- interface DatepickerProps {
2683
- placeholder?: DynamicText;
2684
- }
2685
- interface DatepickerField<TProps> extends BaseValueField<TProps, Date | string> {
2686
- type: 'datepicker';
2687
- minDate?: Date | string | null;
2688
- maxDate?: Date | string | null;
2689
- startAt?: Date | null;
2690
- }
2691
-
2692
- interface SliderField<TProps> extends BaseValueField<TProps, number> {
2693
- type: 'slider';
2694
- minValue?: number;
2695
- maxValue?: number;
2696
- step?: number;
2697
- }
2698
-
2699
- interface ToggleField<TProps> extends BaseCheckedField<TProps> {
2700
- type: 'toggle';
2701
- }
2702
-
2703
- /**
2704
- * Apply validator configuration to field path, following the logic pattern.
2705
- *
2706
- * Accepts both SchemaPath and SchemaPathTree to support being called from both
2707
- * individual field mappings and schema functions.
2708
- *
2709
- * Note: ValidatorConfig is runtime configuration that doesn't encode the field's
2710
- * TypeScript type. The type assertions to specific types (string, number) are safe
2711
- * because Angular's validators perform runtime value checks regardless of the static
2712
- * type parameter.
2713
- *
2714
- * Cross-field validators (those referencing formValue.*) are skipped at field level.
2715
- * They are collected by the parent form component using collectCrossFieldEntries
2716
- * and executed at form level using validateTree.
2717
- *
2718
- * @param config The validator configuration
2719
- * @param fieldPath The field path to apply the validator to
2720
- * @param fieldKey Optional field key (used for logging)
2721
- */
2722
- declare function applyValidator(config: ValidatorConfig, fieldPath: SchemaPath<any> | SchemaPathTree<any>, fieldKey?: string): void;
2723
- /**
2724
- * Apply multiple validators to a field path
2725
- *
2726
- * @param configs Array of validator configurations
2727
- * @param fieldPath The field path to apply validators to
2728
- * @param fieldKey Optional field key (used for logging)
2729
- */
2730
- declare function applyValidators(configs: ValidatorConfig[], fieldPath: SchemaPath<any> | SchemaPathTree<any>, fieldKey?: string): void;
2729
+ declare function applyValidator(config: ValidatorConfig, fieldPath: SchemaPath<any> | SchemaPathTree<any>): void;
2730
+ declare function applyValidators(configs: ValidatorConfig[], fieldPath: SchemaPath<any> | SchemaPathTree<any>): void;
2731
2731
 
2732
2732
  declare class SubmitEvent implements FormEvent {
2733
2733
  readonly type: "submit";
@@ -2935,23 +2935,6 @@ interface ArrayItemContext {
2935
2935
  formValue?: unknown;
2936
2936
  }
2937
2937
 
2938
- /**
2939
- * Maps a value field definition to component inputs.
2940
- *
2941
- * Value fields are input fields that contribute to the form's value (text, number, etc.).
2942
- * This mapper injects FIELD_SIGNAL_CONTEXT to access the form structure and retrieve the field proxy.
2943
- *
2944
- * For fields with specific properties (select, datepicker, textarea, slider), use the specialized mappers:
2945
- * - selectFieldMapper: for fields with options (select, radio, multi-checkbox)
2946
- * - datepickerFieldMapper: for fields with minDate, maxDate, startAt
2947
- * - textareaFieldMapper: for fields with rows, cols
2948
- * - sliderFieldMapper: for fields with minValue, maxValue, step
2949
- *
2950
- * @param fieldDef The value field definition
2951
- * @returns Signal containing Record of input names to values for ngComponentOutlet
2952
- */
2953
- declare function valueFieldMapper<TProps = unknown, TValue = unknown>(fieldDef: BaseValueField<TProps, TValue>): Signal<Record<string, unknown>>;
2954
-
2955
2938
  /**
2956
2939
  * Builds base input properties from a field definition.
2957
2940
  *
@@ -2973,17 +2956,6 @@ declare function buildBaseInputs(fieldDef: FieldDef<unknown>): Record<string, un
2973
2956
  */
2974
2957
  declare function baseFieldMapper(fieldDef: FieldDef<unknown>): Signal<Record<string, unknown>>;
2975
2958
 
2976
- /**
2977
- * Maps a checkbox/toggle field definition to component inputs.
2978
- *
2979
- * Checkbox fields are checked fields that contribute to the form's value as boolean.
2980
- * This mapper injects FIELD_SIGNAL_CONTEXT to access the form structure and retrieve the field proxy.
2981
- *
2982
- * @param fieldDef The checkbox field definition
2983
- * @returns Signal containing Record of input names to values for ngComponentOutlet
2984
- */
2985
- declare function checkboxFieldMapper(fieldDef: BaseCheckedField<unknown>): Signal<Record<string, unknown>>;
2986
-
2987
2959
  /**
2988
2960
  * Maps a row field definition to component inputs.
2989
2961
  *
@@ -3031,31 +3003,16 @@ declare function arrayFieldMapper(fieldDef: ArrayField): Signal<Record<string, u
3031
3003
  declare function pageFieldMapper(fieldDef: PageField): Signal<Record<string, unknown>>;
3032
3004
 
3033
3005
  /**
3034
- * Field types that have an options property.
3035
- */
3036
- type FieldWithOptions<T = unknown, TProps = unknown> = SelectField<T, TProps> | RadioField<T, TProps> | MultiCheckboxField<T, TProps>;
3037
- /**
3038
- * Maps a field with options (select, radio, multi-checkbox) to component inputs.
3039
- *
3040
- * Extends the base value field mapper by adding the options property.
3041
- *
3042
- * @param fieldDef The field definition with options
3043
- * @returns Signal containing Record of input names to values for ngComponentOutlet
3044
- */
3045
- declare function optionsFieldMapper<T, TProps>(fieldDef: FieldWithOptions<T, TProps>): Signal<Record<string, unknown>>;
3046
-
3047
- /**
3048
- * Maps a datepicker field to component inputs.
3006
+ * Maps a text field definition to component inputs.
3049
3007
  *
3050
- * Extends the base value field mapper by adding datepicker-specific properties:
3051
- * - minDate: Minimum selectable date
3052
- * - maxDate: Maximum selectable date
3053
- * - startAt: Initial date to display when opening the picker
3008
+ * Text fields are display-only fields that don't participate in the form schema.
3009
+ * This mapper handles the `logic` configuration by creating reactive signals
3010
+ * that evaluate conditions using the form value from RootFormRegistryService.
3054
3011
  *
3055
- * @param fieldDef The datepicker field definition
3012
+ * @param fieldDef The text field definition
3056
3013
  * @returns Signal containing Record of input names to values for ngComponentOutlet
3057
3014
  */
3058
- declare function datepickerFieldMapper<TProps>(fieldDef: DatepickerField<TProps>): Signal<Record<string, unknown>>;
3015
+ declare function textFieldMapper(fieldDef: TextField): Signal<Record<string, unknown>>;
3059
3016
 
3060
3017
  /**
3061
3018
  * Converts DynamicText (string | Observable | Signal) to Observable<string>
@@ -3067,65 +3024,6 @@ declare function datepickerFieldMapper<TProps>(fieldDef: DatepickerField<TProps>
3067
3024
  */
3068
3025
  declare function dynamicTextToObservable(value: DynamicText | undefined, injector?: Injector): Observable<string>;
3069
3026
 
3070
- /**
3071
- * Computed signal that determines if errors should be displayed
3072
- * Based on field's invalid, touched, and error count
3073
- *
3074
- * @param field - Signal containing FieldTree
3075
- * @returns Signal<boolean> - True if errors should be displayed
3076
- */
3077
- declare function shouldShowErrors<T>(field: Signal<FieldTree<T>>): Signal<boolean>;
3078
-
3079
- /**
3080
- * Resolved validation error with interpolated message
3081
- */
3082
- interface ResolvedError {
3083
- kind: string;
3084
- message: string;
3085
- }
3086
- /**
3087
- * Factory function that creates a signal of resolved validation errors
3088
- * Handles async resolution of DynamicText validation messages
3089
- *
3090
- * @param field - Signal containing FieldTree
3091
- * @param validationMessages - Signal containing custom field-level validation messages
3092
- * @param defaultValidationMessages - Signal containing default validation messages (fallback)
3093
- * @param injector - Optional injector for DynamicText resolution
3094
- * @returns Signal<ResolvedError[]> - Reactively resolved error messages
3095
- *
3096
- * @example
3097
- * ```typescript
3098
- * readonly resolvedErrors = createResolvedErrorsSignal(
3099
- * this.field,
3100
- * this.validationMessages,
3101
- * this.defaultValidationMessages
3102
- * );
3103
- * ```
3104
- */
3105
- declare function createResolvedErrorsSignal<T>(field: Signal<FieldTree<T>>, validationMessages: Signal<ValidationMessages | undefined>, defaultValidationMessages?: Signal<ValidationMessages | undefined>, injector?: Injector): Signal<ResolvedError[]>;
3106
-
3107
- interface ChildData {
3108
- reader: FieldTree<unknown>;
3109
- }
3110
- interface ChildrenData {
3111
- byPropertyKey: Map<string, ChildData>;
3112
- }
3113
- interface FormInternals<T = unknown> {
3114
- structure: {
3115
- childrenMap: () => ChildrenData;
3116
- };
3117
- fieldProxy?: FieldTree<T>;
3118
- }
3119
- type FormInput = FormInternals | Signal<FormInternals> | unknown;
3120
- /**
3121
- * Type guard that unwraps signals and checks for FormInternals structure.
3122
- * Use this when the input might be a signal wrapping FormInternals.
3123
- */
3124
- declare function hasFormStructure(form: FormInput): form is FormInternals | Signal<FormInternals>;
3125
- declare function getChildrenMap(form: FormInput): Map<string, FieldTree<unknown>> | null;
3126
- declare function getFieldProxy<T = unknown>(node: unknown): FieldTree<T> | null;
3127
- declare function getChildFieldTree(form: unknown, key: string): FieldTree<unknown> | null;
3128
-
3129
3027
  /**
3130
3028
  * Pipe that handles dynamic text resolution with support for static strings,
3131
3029
  * Observables, and Signals.
@@ -3174,13 +3072,15 @@ declare class RowFieldComponent {
3174
3072
  private readonly fieldRegistry;
3175
3073
  private readonly injector;
3176
3074
  private readonly eventBus;
3075
+ private readonly logger;
3177
3076
  field: _angular_core.InputSignal<RowField<readonly _ng_forge_dynamic_forms.RowAllowedChildren[]>>;
3178
3077
  key: _angular_core.InputSignal<string>;
3179
3078
  readonly disabled: _angular_core.Signal<boolean>;
3180
3079
  private readonly rawFieldRegistry;
3181
3080
  private readonly fieldsSource;
3182
3081
  protected readonly resolvedFields: _angular_core.Signal<ResolvedField[]>;
3183
- private readonly emitInitializedOnFieldsResolved;
3082
+ constructor();
3083
+ private setupEffects;
3184
3084
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<RowFieldComponent, never>;
3185
3085
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<RowFieldComponent, "div[row-field]", never, { "field": { "alias": "field"; "required": true; "isSignal": true; }; "key": { "alias": "key"; "required": true; "isSignal": true; }; }, {}, never, never, true, never>;
3186
3086
  }
@@ -3192,12 +3092,13 @@ declare class RowFieldComponent {
3192
3092
  * Child fields receive a FIELD_SIGNAL_CONTEXT scoped to this group's form instance.
3193
3093
  * Group values are nested under the group's key in the parent form.
3194
3094
  */
3195
- declare class GroupFieldComponent<TModel = Record<string, unknown>> {
3095
+ declare class GroupFieldComponent<TModel extends Record<string, unknown> = Record<string, unknown>> {
3196
3096
  private readonly destroyRef;
3197
3097
  private readonly fieldRegistry;
3198
3098
  private readonly parentFieldSignalContext;
3199
3099
  private readonly injector;
3200
3100
  private readonly eventBus;
3101
+ private readonly logger;
3201
3102
  private readonly memoizedFlattenFields;
3202
3103
  private readonly memoizedKeyBy;
3203
3104
  private readonly memoizedDefaultValues;
@@ -3215,11 +3116,6 @@ declare class GroupFieldComponent<TModel = Record<string, unknown>> {
3215
3116
  readonly touched: _angular_core.Signal<boolean>;
3216
3117
  readonly errors: _angular_core.Signal<_angular_forms_signals.ValidationError.WithField[]>;
3217
3118
  readonly disabled: _angular_core.Signal<boolean>;
3218
- /**
3219
- * Get the nested FieldTree from the parent form for this group.
3220
- * This allows child fields to update the parent form directly,
3221
- * avoiding the need for separate form synchronization.
3222
- */
3223
3119
  private readonly nestedFieldTree;
3224
3120
  private readonly groupInjector;
3225
3121
  readonly validityChange: _angular_core.OutputRef<boolean>;
@@ -3227,7 +3123,8 @@ declare class GroupFieldComponent<TModel = Record<string, unknown>> {
3227
3123
  readonly submitted: _angular_core.OutputRef<SubmitEvent>;
3228
3124
  private readonly fieldsSource;
3229
3125
  protected readonly resolvedFields: _angular_core.Signal<ResolvedField[]>;
3230
- private readonly emitInitializedOnFieldsResolved;
3126
+ constructor();
3127
+ private setupEffects;
3231
3128
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<GroupFieldComponent<any>, never>;
3232
3129
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<GroupFieldComponent<any>, "fieldset[group-field]", never, { "field": { "alias": "field"; "required": true; "isSignal": true; }; "key": { "alias": "key"; "required": true; "isSignal": true; }; }, { "validityChange": "validityChange"; "dirtyChange": "dirtyChange"; "submitted": "submitted"; }, never, never, true, never>;
3233
3130
  }
@@ -3255,12 +3152,13 @@ interface ResolvedArrayItem {
3255
3152
  * Uses differential updates to optimize rendering - only recreates items when necessary.
3256
3153
  * Each item gets a scoped injector with ARRAY_CONTEXT for position-aware operations.
3257
3154
  */
3258
- declare class ArrayFieldComponent<TModel = Record<string, unknown>> {
3155
+ declare class ArrayFieldComponent<TModel extends Record<string, unknown> = Record<string, unknown>> {
3259
3156
  private readonly destroyRef;
3260
3157
  private readonly fieldRegistry;
3261
3158
  private readonly parentFieldSignalContext;
3262
3159
  private readonly parentInjector;
3263
3160
  private readonly eventBus;
3161
+ private readonly logger;
3264
3162
  field: _angular_core.InputSignal<ArrayField<readonly _ng_forge_dynamic_forms.ArrayAllowedChildren[]>>;
3265
3163
  key: _angular_core.InputSignal<string>;
3266
3164
  private readonly rawFieldRegistry;
@@ -3270,9 +3168,9 @@ declare class ArrayFieldComponent<TModel = Record<string, unknown>> {
3270
3168
  private readonly updateVersion;
3271
3169
  private readonly itemOrderSignal;
3272
3170
  readonly resolvedItems: _angular_core.WritableSignal<ResolvedArrayItem[]>;
3273
- private readonly syncFieldsOnArrayChange;
3274
- private readonly handleAddItem;
3275
- private readonly handleRemoveItem;
3171
+ constructor();
3172
+ private setupEffects;
3173
+ private setupEventHandlers;
3276
3174
  private performDifferentialUpdate;
3277
3175
  private resolveAllItems;
3278
3176
  private appendItems;
@@ -3284,143 +3182,49 @@ declare class ArrayFieldComponent<TModel = Record<string, unknown>> {
3284
3182
  }
3285
3183
 
3286
3184
  /**
3287
- * Injection function for accessing the dynamic form field registry.
3288
- *
3289
- * Provides a convenient API for interacting with registered field types,
3290
- * including type checking, component loading, and registration management.
3291
- * Must be called within an injection context.
3185
+ * Type representing a FieldTree for an array.
3186
+ * Extends FieldTree to add numeric indexing for accessing item FieldTrees.
3187
+ */
3188
+ type ArrayFieldTree<T> = FieldTree<T[]> & {
3189
+ readonly [index: number]: FieldTree<T> | undefined;
3190
+ };
3191
+ /**
3192
+ * Get the length of an array FieldTree.
3193
+ */
3194
+ declare function getArrayLength<T>(arrayFieldTree: ArrayFieldTree<T>): number;
3195
+
3196
+ /**
3197
+ * Native JavaScript utility functions to replace lodash-es
3198
+ * These are simple implementations for common object manipulation patterns
3199
+ */
3200
+ /**
3201
+ * Creates a new object without the specified keys
3202
+ * Native replacement for lodash omit()
3292
3203
  *
3293
- * @returns Object with methods for field registry interaction
3204
+ * @param obj - Source object
3205
+ * @param keys - Keys to omit
3206
+ * @returns New object without specified keys
3294
3207
  *
3295
3208
  * @example
3296
3209
  * ```typescript
3297
- * @Component({...})
3298
- * export class MyComponent {
3299
- * private fieldRegistry = injectFieldRegistry();
3300
- *
3301
- * async loadCustomField() {
3302
- * if (this.fieldRegistry.hasType('custom-input')) {
3303
- * const component = await this.fieldRegistry.loadTypeComponent('custom-input');
3304
- * // Use component...
3305
- * }
3306
- * }
3307
- * }
3210
+ * const obj = { a: 1, b: 2, c: 3 };
3211
+ * omit(obj, ['b']); // { a: 1, c: 3 }
3308
3212
  * ```
3213
+ */
3214
+ declare function omit<T extends object, K extends keyof T>(obj: T, keys: K[]): Omit<T, K>;
3215
+
3216
+ /**
3217
+ * Interpolates {{param}} placeholders in message with error values
3309
3218
  *
3310
- * @example
3311
- * ```typescript
3312
- * // In a service
3313
- * @Injectable()
3314
- * export class FormBuilderService {
3315
- * private fieldRegistry = injectFieldRegistry();
3316
- *
3317
- * getAvailableFieldTypes(): string[] {
3318
- * return this.fieldRegistry.getTypes().map(type => type.name);
3319
- * }
3320
- * }
3321
- * ```
3219
+ * @param message - Message template with {{param}} placeholders
3220
+ * @param error - Validation error containing parameter values
3221
+ * @returns Message with interpolated parameters
3322
3222
  *
3323
- * @public
3223
+ * @example
3224
+ * interpolateParams("Min value is {{min}}", { kind: 'min', min: 5 })
3225
+ * // Returns: "Min value is 5"
3324
3226
  */
3325
- declare function injectFieldRegistry(): {
3326
- /**
3327
- * Retrieves a field type definition by its registered name.
3328
- *
3329
- * @param name - The registered name of the field type
3330
- * @returns The field type definition if found, undefined otherwise
3331
- *
3332
- * @example
3333
- * ```typescript
3334
- * const inputType = fieldRegistry.getType('input');
3335
- * if (inputType) {
3336
- * console.log('Input field type found:', inputType.name);
3337
- * }
3338
- * ```
3339
- */
3340
- getType(name: string): FieldTypeDefinition | undefined;
3341
- /**
3342
- * Checks if a field type is registered in the registry.
3343
- *
3344
- * @param name - The name of the field type to check
3345
- * @returns True if the field type exists, false otherwise
3346
- *
3347
- * @example
3348
- * ```typescript
3349
- * if (fieldRegistry.hasType('custom-input')) {
3350
- * // Safe to use custom-input field type
3351
- * const component = await fieldRegistry.loadTypeComponent('custom-input');
3352
- * }
3353
- * ```
3354
- */
3355
- hasType(name: string): boolean;
3356
- /**
3357
- * Loads a field type component with support for lazy loading.
3358
- *
3359
- * Handles both synchronous component references and asynchronous
3360
- * dynamic imports. Automatically extracts default exports from ES modules.
3361
- *
3362
- * @param name - The name of the field type to load
3363
- * @returns Promise resolving to the component constructor
3364
- * @throws {Error} When field type is not registered or loading fails
3365
- *
3366
- * @example
3367
- * ```typescript
3368
- * try {
3369
- * const InputComponent = await fieldRegistry.loadTypeComponent('input');
3370
- * const componentRef = vcr.createComponent(InputComponent);
3371
- * } catch (error) {
3372
- * console.error('Failed to load component:', error);
3373
- * }
3374
- * ```
3375
- */
3376
- loadTypeComponent(name: string): Promise<Type<unknown>>;
3377
- /**
3378
- * Gets all registered field type definitions.
3379
- *
3380
- * @returns Array of all field type definitions in the registry
3381
- *
3382
- * @example
3383
- * ```typescript
3384
- * const allTypes = fieldRegistry.getTypes();
3385
- * const typeNames = allTypes.map(type => type.name);
3386
- * console.log('Available field types:', typeNames);
3387
- * ```
3388
- */
3389
- getTypes(): FieldTypeDefinition[];
3390
- /**
3391
- * Registers multiple field types at once.
3392
- *
3393
- * Useful for bulk registration of custom field types. Overwrites
3394
- * existing registrations with the same name.
3395
- *
3396
- * @param types - Array of field type definitions to register
3397
- *
3398
- * @example
3399
- * ```typescript
3400
- * fieldRegistry.registerTypes([
3401
- * CustomInputType,
3402
- * CustomSelectType,
3403
- * CustomDatePickerType
3404
- * ]);
3405
- * ```
3406
- */
3407
- registerTypes(types: FieldTypeDefinition[]): void;
3408
- /**
3409
- * Provides direct access to the underlying registry Map.
3410
- *
3411
- * Use with caution - direct modification can affect form behavior.
3412
- * Primarily intended for advanced use cases and debugging.
3413
- *
3414
- * @returns The raw Map containing field type registrations
3415
- *
3416
- * @example
3417
- * ```typescript
3418
- * const rawRegistry = fieldRegistry.raw;
3419
- * console.log('Registry size:', rawRegistry.size);
3420
- * ```
3421
- */
3422
- readonly raw: Map<string, FieldTypeDefinition<any>>;
3423
- };
3227
+ declare function interpolateParams(message: string, error: ValidationError): string;
3424
3228
 
3425
- export { ARRAY_CONTEXT, AddArrayItemEvent, ArrayFieldComponent, BUILT_IN_FIELDS, DynamicForm, DynamicTextPipe, EventBus, FIELD_REGISTRY, FIELD_SIGNAL_CONTEXT, FormClearEvent, FormResetEvent, GroupFieldComponent, NextPageEvent, PageChangeEvent, PreviousPageEvent, RemoveArrayItemEvent, RowFieldComponent, SubmitEvent, applyValidator, applyValidators, arrayFieldMapper, baseFieldMapper, buildBaseInputs, checkboxFieldMapper, createResolvedErrorsSignal, datepickerFieldMapper, dynamicTextToObservable, getChildFieldTree, getChildrenMap, getFieldProxy, getFieldValueHandling, groupFieldMapper, hasFormStructure, injectFieldRegistry, isArrayField, isCheckedField, isContainerField, isDisplayOnlyField, isFormStateCondition, isGroupField, isLeafField, isPageField, isRowField, isValueBearingField, isValueField, optionsFieldMapper, pageFieldMapper, provideDynamicForm, resolveNextButtonDisabled, resolveSubmitButtonDisabled, resolveTokens, rowFieldMapper, shouldShowErrors, valueFieldMapper };
3426
- export type { ArrayAllowedChildren, ArrayContext, ArrayField, ArrayItemContext, AsyncCustomValidator, AsyncValidatorConfig, AvailableFieldTypes, BaseCheckedField, BaseValidatorConfig, BaseValueField, BuiltInValidatorConfig, ButtonField, ButtonLogicContext, CheckboxField, CheckedFieldComponent, ConditionalExpression, ContainerFieldTypes, CustomFnConfig, CustomValidator, CustomValidatorConfig, DatepickerField, DatepickerProps, DynamicFormFieldRegistry, DynamicText, EvaluationContext, EventArgs, ExtractFieldDefs, ExtractFormValue, FieldComponent, FieldDef, FieldOption, FieldPathAccess, FieldRegistryContainers, FieldRegistryLeaves, FieldSignalContext, FieldTypeDefinition, FieldWithValidation, FormConfig, FormEvent, FormEventConstructor, FormInternals, FormMode, FormModeDetectionResult, FormOptions, FormStateCondition, GroupAllowedChildren, GroupField, HttpCustomValidator, HttpResourceRequest, HttpValidatorConfig, InferFormValue$1 as InferFormValue, InputField, InputProps, LeafFieldTypes, LogicConfig, MapperFn, MultiCheckboxField, NextButtonOptions, PageAllowedChildren, PageField, Prettify, RadioField, RegisteredFieldTypes, ResolvedError, RowAllowedChildren, RowField, SchemaApplicationConfig, SchemaDefinition, SelectField, SelectProps, SliderField, SubmissionActionResult, SubmissionConfig, SubmitButtonOptions, TextElementType, TextField, TextProps, TextareaField, TextareaProps, ToggleField, TokenContext, ValidationError, ValidationMessages, ValidatorConfig, ValueFieldComponent, ValueHandlingMode, ValueType, WithInputSignals };
3229
+ export { ARRAY_CONTEXT, AddArrayItemEvent, ArrayFieldComponent, BUILT_IN_FIELDS, DYNAMIC_FORM_LOGGER, DynamicForm, DynamicTextPipe, EventBus, FIELD_REGISTRY, FIELD_SIGNAL_CONTEXT, FormClearEvent, FormResetEvent, GroupFieldComponent, LogLevel, NextPageEvent, NoopLogger, PageChangeEvent, PreviousPageEvent, RemoveArrayItemEvent, RowFieldComponent, SubmitEvent, applyValidator, applyValidators, arrayFieldMapper, baseFieldMapper, buildBaseInputs, dynamicTextToObservable, getArrayLength, groupFieldMapper, interpolateParams, isArrayField, isCheckedField, isContainerField, isDisplayOnlyField, isFormStateCondition, isGroupField, isLeafField, isPageField, isRowField, isValueBearingField, isValueField, omit, pageFieldMapper, provideDynamicForm, resolveNextButtonDisabled, resolveSubmitButtonDisabled, resolveTokens, rowFieldMapper, textFieldMapper, withLogger };
3230
+ export type { ArrayAllowedChildren, ArrayContext, ArrayField, ArrayFieldTree, ArrayItemContext, AsyncCustomValidator, AsyncValidatorConfig, AvailableFieldTypes, BaseCheckedField, BaseValidatorConfig, BaseValueField, BuiltInValidatorConfig, ButtonLogicContext, CheckedFieldComponent, ConditionalExpression, ContainerFieldTypes, CustomFnConfig, CustomValidator, CustomValidatorConfig, DynamicFormFieldRegistry, DynamicFormLogger, DynamicText, EvaluationContext, ExtractField, ExtractFieldDefs, ExtractFormValue, FieldComponent, FieldDef, FieldOption, FieldPathAccess, FieldRegistryContainers, FieldRegistryLeaves, FieldSignalContext, FieldTypeDefinition, FieldWithValidation, FormConfig, FormEvent, FormEventConstructor, FormMode, FormModeDetectionResult, FormOptions, FormStateCondition, GroupAllowedChildren, GroupField, HttpCustomValidator, HttpResourceRequest, HttpValidatorConfig, InferFormValue$1 as InferFormValue, LeafFieldTypes, LoggerFeatureOptions, 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 };