@ng-forge/dynamic-forms 1.0.0-rc.1 → 1.0.0-rc.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 (24) hide show
  1. package/README.md +16 -0
  2. package/fesm2022/{ng-forge-dynamic-forms-array-field.component-By12r5Cj.mjs → ng-forge-dynamic-forms-array-field.component-D5qCzJsL.mjs} +2 -2
  3. package/fesm2022/{ng-forge-dynamic-forms-array-field.component-By12r5Cj.mjs.map → ng-forge-dynamic-forms-array-field.component-D5qCzJsL.mjs.map} +1 -1
  4. package/fesm2022/{ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-QJzwEYSj.mjs → ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-D6ntLtW9.mjs} +2 -2
  5. package/fesm2022/{ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-QJzwEYSj.mjs.map → ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-D6ntLtW9.mjs.map} +1 -1
  6. package/fesm2022/{ng-forge-dynamic-forms-container-field.component-BGFguOa9.mjs → ng-forge-dynamic-forms-container-field.component-nB6KFqVs.mjs} +2 -2
  7. package/fesm2022/{ng-forge-dynamic-forms-container-field.component-BGFguOa9.mjs.map → ng-forge-dynamic-forms-container-field.component-nB6KFqVs.mjs.map} +1 -1
  8. package/fesm2022/{ng-forge-dynamic-forms-group-field.component-Bz4GD8Hy.mjs → ng-forge-dynamic-forms-group-field.component-Bq6Sgd6Z.mjs} +2 -2
  9. package/fesm2022/ng-forge-dynamic-forms-group-field.component-Bq6Sgd6Z.mjs.map +1 -0
  10. package/fesm2022/ng-forge-dynamic-forms-integration.mjs +6 -6
  11. package/fesm2022/ng-forge-dynamic-forms-integration.mjs.map +1 -1
  12. package/fesm2022/ng-forge-dynamic-forms-internal.mjs +281 -42
  13. package/fesm2022/ng-forge-dynamic-forms-internal.mjs.map +1 -1
  14. package/fesm2022/{ng-forge-dynamic-forms-ng-forge-dynamic-forms-DTkMZcnb.mjs → ng-forge-dynamic-forms-ng-forge-dynamic-forms-C59pvc26.mjs} +86 -407
  15. package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-C59pvc26.mjs.map +1 -0
  16. package/fesm2022/{ng-forge-dynamic-forms-page-field.component-CjCd5Byo.mjs → ng-forge-dynamic-forms-page-field.component-DRaxbiqA.mjs} +2 -2
  17. package/fesm2022/{ng-forge-dynamic-forms-page-field.component-CjCd5Byo.mjs.map → ng-forge-dynamic-forms-page-field.component-DRaxbiqA.mjs.map} +1 -1
  18. package/fesm2022/ng-forge-dynamic-forms.mjs +2 -2
  19. package/package.json +3 -3
  20. package/types/ng-forge-dynamic-forms-integration.d.ts +6 -10
  21. package/types/ng-forge-dynamic-forms-internal.d.ts +1951 -1750
  22. package/types/ng-forge-dynamic-forms.d.ts +61 -270
  23. package/fesm2022/ng-forge-dynamic-forms-group-field.component-Bz4GD8Hy.mjs.map +0 -1
  24. package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-DTkMZcnb.mjs.map +0 -1
@@ -1,14 +1,13 @@
1
1
  import * as rxjs from 'rxjs';
2
2
  import * as _angular_core from '@angular/core';
3
- import { Type, Injector, Signal, EnvironmentInjector, WritableSignal, Provider, EnvironmentProviders, TemplateRef, InjectionToken } from '@angular/core';
3
+ import { Type, Injector, Signal, EnvironmentInjector, WritableSignal, Provider, EnvironmentProviders, TemplateRef } from '@angular/core';
4
4
  import * as _ng_forge_dynamic_forms_internal from '@ng-forge/dynamic-forms/internal';
5
- import { FieldDef, FormEvent, RegisteredFieldTypes, InferFormValue as InferFormValue$1, FormConfig, FormOptions, EventBus, LazyComponentLoader, WrapperTypeDefinition, FieldTypeDefinition, DerivationLogLevel, Logger, ValueExclusionConfig, ValidationExecutionConfig, AnyAddon, WrapperFieldInputs, TextAddon, TemplateAddon, ComponentAddon, AddonActionPreset, AddonActionContext, AddonKindDefinition, FieldAddonSupport, BaseAddon, ArrayItemDefinition, NarrowFields, AvailableFieldTypes, ExtractField } from '@ng-forge/dynamic-forms/internal';
6
- export { ADDON_KIND_REGISTRY, AddonActionContext, AddonActionPreset, AddonKindDefinition, AddonKindSchema, AddonShapeValidator, AddonSlot, AnyAddon, ArrayAllowedChildren, ArrayButtonConfig, ArrayField, ArrayItemTemplate, AsyncCondition, AsyncConditionFunction, AsyncCustomValidator, AsyncDerivationFunction, AsyncValidatorConfig, AvailableFieldTypes, BaseAddon, BaseCheckedField, BaseValidatorConfig, BaseValueField, BuiltInValidatorConfig, ButtonLogicContext, CheckedFieldComponent, CommonAddonActionPreset, CommonAddonSlot, ComponentAddon, ConditionalExpression, ContainerAllowedChildren, ContainerField, ContainerFieldTypes, CssWrapper, CustomFnConfig, CustomFunction, CustomValidator, CustomValidatorConfig, DF_FIELD_TEMPLATES, DeclarativeHttpValidatorConfig, DynamicFormActionRegistry, DynamicFormAddonActionPresetRegistry, DynamicFormAddonRegistry, DynamicFormAddonSlotRegistry, DynamicFormError, DynamicFormFieldRegistry, DynamicFormLogger, DynamicText, DynamicValue, EvaluationContext, ExtractField, FieldAddonSupport, FieldBoundAddonActionContext, FieldComponent, FieldDef, FieldMeta, FieldOption, FieldPathAccess, FieldRegistryContainers, FieldRegistryLeaves, FieldRegistryWrappers, FieldWithValidation, FieldWrapper, FormConfig, FormEvent, FormEventConstructor, FormMode, FormModeDetectionResult, FormOptions, FormStateCondition, GroupAllowedChildren, GroupField, HttpCondition, HttpCustomValidator, HttpRequestConfig, HttpValidationResponseMapping, InferFormValue, LeafFieldTypes, Logger, LogicConfig, NarrowField, NarrowFields, NextButtonOptions, NonFieldLogicConfig, NonFieldLogicContext, NonFieldLogicType, NoopLogger, OrphanAddonActionContext, PageAllowedChildren, PageField, Prettify, RegisteredActionRef, RegisteredFieldTypes, ResolvedValidationExecutionConfig, ResolvedValueExclusionConfig, RowAllowedChildren, RowField, SchemaApplicationConfig, SchemaDefinition, SimplifiedArrayField, StateLogicConfig, SubmissionActionResult, SubmissionConfig, SubmitButtonOptions, TemplateAddon, TextAddon, TextElementType, TextField, TextProps, TokenContext, ValidationError, ValidationExecutionConfig, ValidationMessages, ValidatorConfig, ValueExclusionConfig, ValueFieldComponent, ValueType, WRAPPER_REGISTRY, WithInputSignals, WrapperConfig, WrapperTypeDefinition, evaluateNonFieldDisabled, evaluateNonFieldHidden, hasFormValue, isArrayField, isCheckedField, isContainerField, isContainerTypedField, isDisplayOnlyField, isFieldBoundContext, isFormStateCondition, isGroupField, isLeafField, isPageField, isRowField, isSimplifiedArrayField, isValueBearingField, isValueField, isWrapperTypeDefinition, resolveDynamicValue, resolveNextButtonDisabled, resolveNonFieldDisabled, resolveNonFieldHidden, resolveSubmitButtonDisabled } from '@ng-forge/dynamic-forms/internal';
5
+ import { FieldDef, FormEvent, RegisteredFieldTypes, InferFormModel, FormConfig, FormOptions, EventBus, LazyComponentLoader, WrapperTypeDefinition, FieldTypeDefinition, InferFormValue as InferFormValue$1, DerivationLogLevel, Logger, ValueExclusionConfig, ValidationExecutionConfig, TextAddon, WrapperFieldInputs, TemplateAddon, ComponentAddon, BaseAddon, AddonTypeDefinition, AddonActionHandler, ArrayItemDefinition } from '@ng-forge/dynamic-forms/internal';
6
+ export { AddonActionContext, AddonActionPreset, AddonSlot, AddonTypeDefinition, AnyAddon, ArrayAllowedChildren, ArrayButtonConfig, ArrayField, ArrayItemTemplate, AsyncCondition, AsyncConditionFunction, AsyncCustomValidator, AsyncDerivationFunction, AsyncValidatorConfig, AvailableFieldTypes, BaseAddon, BaseCheckedField, BaseValidatorConfig, BaseValueField, BuiltInValidatorConfig, CheckedFieldComponent, CommonAddonActionPreset, CommonAddonSlot, ComponentAddon, ConditionalExpression, ContainerAllowedChildren, ContainerField, ContainerFieldTypes, CssWrapper, CustomFnConfig, CustomFunction, CustomValidator, CustomValidatorConfig, DF_FIELD_TEMPLATES, DeclarativeHttpValidatorConfig, DynamicFormActionRegistry, DynamicFormAddonActionPresetRegistry, DynamicFormAddonRegistry, DynamicFormAddonSlotRegistry, DynamicFormError, DynamicFormFieldRegistry, DynamicFormLogger, DynamicText, DynamicValue, EvaluationContext, ExtractField, FieldBoundAddonActionContext, FieldComponent, FieldDef, FieldMeta, FieldOption, FieldPathAccess, FieldRegistryContainers, FieldRegistryLeaves, FieldRegistryWrappers, FieldWithValidation, FieldWrapper, FormConfig, FormEvent, FormEventConstructor, FormMode, FormModeDetectionResult, FormOptions, FormStateCondition, GroupAllowedChildren, GroupField, HiddenField, HiddenScalar, HiddenValue, HttpCondition, HttpCustomValidator, HttpRequestConfig, HttpValidationResponseMapping, InferFormValue, LeafFieldTypes, Logger, LogicConfig, NarrowField, NarrowFields, NextButtonOptions, NoopLogger, OrphanAddonActionContext, PageAllowedChildren, PageField, RegisteredActionRef, RegisteredFieldTypes, ResolvedValidationExecutionConfig, ResolvedValueExclusionConfig, RowAllowedChildren, RowField, SchemaApplicationConfig, SchemaDefinition, SimplifiedArrayField, StateLogicConfig, SubmissionActionResult, SubmissionConfig, SubmitButtonOptions, TemplateAddon, TextAddon, TextElementType, TextField, TextProps, TokenContext, ValidationError, ValidationExecutionConfig, ValidationMessages, ValidatorConfig, ValueExclusionConfig, ValueFieldComponent, ValueType, WithInputSignals, WrapperConfig, WrapperTypeDefinition, hasFormValue, isArrayField, isContainerField, isDisplayOnlyField, isFieldBoundContext, isFormStateCondition, isGroupField, isHiddenField, isLeafField, isPageField, isRowField, isSimplifiedArrayField, isValueBearingField, isWrapperTypeDefinition, resolveDynamicValue } from '@ng-forge/dynamic-forms/internal';
7
7
  import * as _angular_forms_signals from '@angular/forms/signals';
8
8
  import { FieldTree } from '@angular/forms/signals';
9
9
  import * as _angular_forms from '@angular/forms';
10
10
  import * as _ng_forge_dynamic_forms from '@ng-forge/dynamic-forms';
11
- import { FormSchema } from '@ng-forge/dynamic-forms/schema';
12
11
 
13
12
  /** Resolved field ready for rendering with ngComponentOutlet / DfFieldOutlet. */
14
13
  interface ResolvedField {
@@ -47,6 +46,7 @@ declare class FormResetEvent implements FormEvent {
47
46
  readonly type: "form-reset";
48
47
  }
49
48
 
49
+ /** Event emitted when the active page of a paged form changes. */
50
50
  declare class PageChangeEvent implements FormEvent {
51
51
  /** The current page index (0-based) */
52
52
  readonly currentPageIndex: number;
@@ -85,19 +85,45 @@ declare class PageNavigationStateChangeEvent implements FormEvent {
85
85
  }
86
86
 
87
87
  /**
88
- * Dynamic form component — renders a form based on configuration.
89
- * Delegates state management to `FormStateManager`.
88
+ * Renders a form from a {@link FormConfig}. Attribute component on a native
89
+ * `<form>` element (selector `form[dynamic-form]`); state management is
90
+ * delegated to `FormStateManager`.
91
+ *
92
+ * Provide a UI adapter once via {@link provideDynamicForm} (for example
93
+ * `provideDynamicForm(...withMaterialFields())`), then bind a config and
94
+ * listen for `submitted`. Field-value types are inferred from the config when
95
+ * it is authored with `as const satisfies FormConfig`.
96
+ *
97
+ * @example
98
+ * ```ts
99
+ * @Component({
100
+ * imports: [DynamicForm],
101
+ * template: `<form [dynamic-form]="config" (submitted)="onSubmit($event)"></form>`,
102
+ * })
103
+ * export class SignInComponent {
104
+ * config = {
105
+ * fields: [
106
+ * { key: 'email', type: 'input', value: '', label: 'Email', required: true, email: true },
107
+ * { type: 'submit', key: 'submit', label: 'Sign in' },
108
+ * ],
109
+ * } as const satisfies FormConfig;
110
+ *
111
+ * onSubmit(value: InferFormValue<typeof this.config.fields>) {
112
+ * console.log(value); // inferred: { email: string }
113
+ * }
114
+ * }
115
+ * ```
90
116
  */
91
- declare class DynamicForm<TFields extends RegisteredFieldTypes[] = RegisteredFieldTypes[], TModel extends Record<string, unknown> = InferFormValue$1<TFields> & Record<string, unknown>> {
117
+ declare class DynamicForm<TFields extends RegisteredFieldTypes[] = RegisteredFieldTypes[], TModel extends Record<string, unknown> = InferFormModel<TFields>> {
92
118
  /** Form configuration defining the structure, validation, and behavior. */
93
- config: _angular_core.InputSignal<FormConfig<TFields, InferFormValue$1<TFields extends readonly RegisteredFieldTypes[] ? TFields : RegisteredFieldTypes[]>, Record<string, unknown>, unknown>>;
119
+ config: _angular_core.InputSignal<FormConfig<TFields, _ng_forge_dynamic_forms.InferFormValue<TFields extends readonly RegisteredFieldTypes[] ? TFields : RegisteredFieldTypes[]>, Record<string, unknown>, unknown>>;
94
120
  /** Runtime form options that override config options when provided. */
95
121
  formOptions: _angular_core.InputSignal<FormOptions | undefined>;
96
122
  /** Form values for two-way data binding. */
97
123
  value: _angular_core.ModelSignal<Partial<TModel> | undefined>;
98
124
  /**
99
125
  * How lenient the addon validator is on `config.fields[...].addons`.
100
- * `'inline'` (default) keeps everything; `'json'` drops code-only kinds
126
+ * `'inline'` (default) keeps everything; `'json'` drops code-only types
101
127
  * with a warning. Read at form init and on every config swap — change
102
128
  * `source` AND the config to force re-validation mid-flight.
103
129
  */
@@ -122,7 +148,7 @@ declare class DynamicForm<TFields extends RegisteredFieldTypes[] = RegisteredFie
122
148
  protected readonly idPrefix: Signal<string>;
123
149
  private componentId;
124
150
  /** The currently active config used for form rendering */
125
- activeConfig: Signal<FormConfig<TFields, InferFormValue$1<TFields extends readonly RegisteredFieldTypes[] ? TFields : RegisteredFieldTypes[]>, Record<string, unknown>, unknown> | undefined>;
151
+ activeConfig: Signal<FormConfig<TFields, _ng_forge_dynamic_forms.InferFormValue<TFields extends readonly RegisteredFieldTypes[] ? TFields : RegisteredFieldTypes[]>, Record<string, unknown>, unknown> | undefined>;
126
152
  /** Current render phase: 'render' = showing form, 'teardown' = hiding old components */
127
153
  renderPhase: Signal<"teardown" | "render">;
128
154
  /** Computed form mode detection with validation */
@@ -181,8 +207,8 @@ declare class DynamicForm<TFields extends RegisteredFieldTypes[] = RegisteredFie
181
207
  validityChange: _angular_core.OutputRef<boolean>;
182
208
  /** Emits when form dirty state changes. */
183
209
  dirtyChange: _angular_core.OutputRef<boolean>;
184
- /** Emits form values when submitted (via SubmitEvent) and form is valid. */
185
- submitted: _angular_core.OutputRef<Partial<TModel>>;
210
+ /** Emits form values when submitted (via FormSubmitEvent) and form is valid. */
211
+ submitted: _angular_core.OutputRef<TModel>;
186
212
  /** Emits when form is reset to default values. */
187
213
  reset: _angular_core.OutputRef<FormResetEvent>;
188
214
  /** Emits when form is cleared to empty state. */
@@ -452,77 +478,6 @@ declare function withLegacyStatusClasses(): DynamicFormFeature<'legacy-status-cl
452
478
  /** Zero-cost type carrier used in wrapper registrations. */
453
479
  declare function wrapperProps<T>(): T;
454
480
 
455
- /**
456
- * Dispatcher component that renders an addon by looking up its `kind` in
457
- * `ADDON_KIND_REGISTRY` and instantiating the registered component.
458
- */
459
- declare class DfAddonSlot {
460
- private readonly registry;
461
- private readonly logger;
462
- private readonly hostInjector;
463
- readonly addon: _angular_core.InputSignal<AnyAddon>;
464
- /**
465
- * Optional read-only view of the host field's mapped inputs — same shape
466
- * wrappers receive (`field?: ReadonlyFieldTree`, `key`, `props`, etc.).
467
- * Forwarded to kind components so they can read field state without
468
- * re-injecting `FIELD_SIGNAL_CONTEXT` and rebuilding context themselves.
469
- */
470
- readonly fieldInputs: _angular_core.InputSignal<WrapperFieldInputs | undefined>;
471
- /**
472
- * Pre-resolved `hidden` signal supplied by the host field. When provided,
473
- * the dispatcher reads this instead of re-resolving `addon.hidden` —
474
- * lets the host (e.g., `prime-input`) share a single subscription per
475
- * Observable-typed `hidden` between its own visibility filter and the
476
- * slot's `[style.display]` binding.
477
- */
478
- readonly hidden: _angular_core.InputSignal<Signal<boolean> | undefined>;
479
- /** `slot` HTML attribute forwarded to host — needed for Ionic shadow projection. */
480
- protected readonly slotAttr: Signal<_ng_forge_dynamic_forms.AddonSlot>;
481
- protected readonly className: Signal<string | null>;
482
- /**
483
- * Locally-resolved `hidden` signal — always reflects `addon.hidden`
484
- * regardless of whether the host supplied a pre-resolved signal.
485
- * Populated via `explicitEffect` so `resolveDynamicValue` (which may call
486
- * `toSignal` for Observable-typed values) does NOT run inside a reactive
487
- * context (NG0602). The WeakMap caches per `hidden`-value identity, so
488
- * swapping `addon` inputs that share the same Observable reuses the
489
- * subscription instead of leaking a fresh one per swap.
490
- */
491
- private readonly _hiddenByValue;
492
- private readonly _resolvedHidden;
493
- /**
494
- * `hidden` resolved from `DynamicValue<boolean>` to a flat `Signal<boolean>`.
495
- * Prefers the host-supplied signal when present (avoids duplicate `toSignal`
496
- * subscriptions for Observable-typed `hidden`); falls back to the locally-
497
- * resolved signal, which is always populated by the addon-tracking effect.
498
- */
499
- protected readonly isHidden: Signal<boolean>;
500
- /**
501
- * Latest async-loaded `{ kind, component }` pair. Tagging the result with
502
- * its kind lets `resolvedComponent` filter stale loads structurally — no
503
- * monotonic token needed. When `addon.kind` changes mid-load, the prior
504
- * load still completes and writes here, but the computed ignores it
505
- * because the recorded kind no longer matches the current one.
506
- */
507
- private readonly asyncLoaded;
508
- /**
509
- * Resolved kind component — composed declaratively from two sources of
510
- * truth: the registry's synchronous cache (warm hits, e.g., a second
511
- * `<df-addon-slot>` for an already-loaded kind) and the kind-tagged async
512
- * loader result. Reading `addon().kind` first makes the computed react to
513
- * addon swaps and naturally invalidate stale async-load results.
514
- */
515
- protected readonly resolvedComponent: Signal<Type<unknown> | undefined>;
516
- /** Inputs passed to the kind component — addon plus the wrapper-style host bag. */
517
- protected readonly kindInputs: Signal<{
518
- addon: AnyAddon;
519
- fieldInputs: WrapperFieldInputs | undefined;
520
- }>;
521
- constructor();
522
- static ɵfac: _angular_core.ɵɵFactoryDeclaration<DfAddonSlot, never>;
523
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<DfAddonSlot, "df-addon-slot", never, { "addon": { "alias": "addon"; "required": true; "isSignal": true; }; "fieldInputs": { "alias": "fieldInputs"; "required": false; "isSignal": true; }; "hidden": { "alias": "hidden"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
524
- }
525
-
526
481
  /**
527
482
  * Marks an `<ng-template>` projected into `<df-dynamic-form>` content as
528
483
  * available to template addons by name.
@@ -536,7 +491,7 @@ declare class DfTemplate {
536
491
  static ɵdir: _angular_core.ɵɵDirectiveDeclaration<DfTemplate, "ng-template[dfTemplate]", ["dfTemplate"], { "dfTemplate": { "alias": "dfTemplate"; "required": true; "isSignal": true; }; }, {}, never, never, true, never>;
537
492
  }
538
493
 
539
- /** Renderer for the universal `text` addon kind. */
494
+ /** Renderer for the universal `text` addon type. */
540
495
  declare class TextAddonComponent {
541
496
  readonly addon: _angular_core.InputSignal<TextAddon>;
542
497
  /** Accepted for contract uniformity — `NgComponentOutlet` setInput is strict; every kind must declare it. */
@@ -545,7 +500,7 @@ declare class TextAddonComponent {
545
500
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<TextAddonComponent, "df-text-addon", never, { "addon": { "alias": "addon"; "required": true; "isSignal": true; }; "fieldInputs": { "alias": "fieldInputs"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
546
501
  }
547
502
 
548
- /** Renderer for the universal `template` addon kind. */
503
+ /** Renderer for the universal `template` addon type. */
549
504
  declare class TemplateAddonComponent {
550
505
  private readonly templates;
551
506
  private readonly logger;
@@ -563,7 +518,7 @@ declare class TemplateAddonComponent {
563
518
  }
564
519
 
565
520
  /**
566
- * Renderer for the universal `component` addon kind.
521
+ * Renderer for the universal `component` addon type.
567
522
  *
568
523
  * @codeOnly The `component` loader is a function; this kind is dropped in
569
524
  * lenient validation when the config originated from JSON.
@@ -589,107 +544,11 @@ declare class ComponentAddonComponent {
589
544
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<ComponentAddonComponent, "df-component-addon", never, { "addon": { "alias": "addon"; "required": true; "isSignal": true; }; "fieldInputs": { "alias": "fieldInputs"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
590
545
  }
591
546
 
592
- /**
593
- * Optional collaborators a preset may need beyond the base context. Passed
594
- * by the button kind component which has access to its own DI scope.
595
- */
596
- interface PresetCollaborators {
597
- /** Per-field input-type override signal — populated only inside an input field. */
598
- readonly typeOverride?: WritableSignal<string | undefined>;
599
- /** Writer that pushes a value into the host field's tree. */
600
- readonly fieldValueSetter?: (next: unknown) => void;
601
- /**
602
- * Read the host field's configured default value (from the form's
603
- * `defaultValues` map keyed by `field.key`). Used by the `'reset'`
604
- * preset; absent when the addon is rendered outside an active field
605
- * context, in which case `'reset'` falls back to clearing.
606
- */
607
- readonly fieldDefaultValueGetter?: () => unknown;
608
- /**
609
- * Baseline configured input type (from `props().type`), read at preset-run
610
- * time. Used by `toggle-password-visibility` to refuse to corrupt
611
- * `'email'`/`'number'`/etc. inputs into `'password'` when no override is
612
- * yet set.
613
- */
614
- readonly baselineType?: () => string | undefined;
615
- /** Logger for warnings about presets that can't be fulfilled in this context. */
616
- readonly logger: Logger;
617
- }
618
- /**
619
- * Apply a built-in preset action against the addon's host field. Shared
620
- * across all adapters — each adapter's `ADDON_PRESET_HANDLER` provider
621
- * delegates here.
622
- *
623
- * @param adapterLabel Used in warnings — `'Material'` / `'Bootstrap'` / etc.
624
- * @param fieldLabel Used in warnings — `'mat-input'` / `'bs-input'` / etc.
625
- */
626
- declare function runPresetAction(preset: AddonActionPreset, ctx: AddonActionContext, collaborators: PresetCollaborators, adapterLabel: string, fieldLabel: string): Promise<void>;
627
-
628
- /**
629
- * Cache for resolved addon-kind components.
630
- *
631
- * @internal
632
- */
633
- declare const ADDON_KIND_COMPONENT_CACHE: InjectionToken<Map<string, Type<unknown>>>;
634
- /**
635
- * Public shape returned by {@link injectAddonKindRegistry}. Pinned explicitly
636
- * so the API extractor doesn't churn between versions when the inner factory
637
- * gets refactored.
638
- */
639
- interface AddonKindRegistryRef {
640
- getKind(kind: string): AddonKindDefinition | undefined;
641
- hasKind(kind: string): boolean;
642
- getKindNames(): string[];
643
- loadKindComponent(kind: string): Promise<Type<unknown>>;
644
- getLoadedKindComponent(kind: string): Type<unknown> | undefined;
645
- readonly raw: ReadonlyMap<string, AddonKindDefinition>;
646
- }
647
- /** Injection function for accessing the addon kind registry. */
648
- declare function injectAddonKindRegistry(): AddonKindRegistryRef;
649
-
650
- /** One entry per field type that declares addon support. */
651
- interface FieldAddonSupportEntry {
652
- readonly name: string;
653
- readonly slots: FieldAddonSupport['slots'];
654
- readonly allowedKinds: FieldAddonSupport['allowedKinds'];
655
- }
656
- /**
657
- * Walk `FIELD_REGISTRY` and return every field type that opted into addons
658
- * (i.e., declared `addons` on its `FieldTypeDefinition`).
659
- *
660
- * @Component({ ... })
661
- * class FormBuilder {
662
- * protected readonly addonable = injectFieldsSupportingAddons();
663
- *
664
- * constructor() {
665
- * // -> [{ name: 'prime-input', slots: ['prefix', 'suffix'], allowedKinds: [...] }, ...]
666
- * }
667
- * }
668
- * ```
669
- */
670
- declare function injectFieldsSupportingAddons(): FieldAddonSupportEntry[];
671
-
672
- /** Register a custom addon kind with the form. */
673
- declare function withCustomAddon<T extends BaseAddon>(definition: AddonKindDefinition<T>): DynamicFormFeature<'addons'>;
547
+ /** Register a custom addon type with the form. */
548
+ declare function withCustomAddon<T extends BaseAddon>(definition: AddonTypeDefinition<T>): DynamicFormFeature<'addons'>;
674
549
 
675
- /** Handler signature for user-registered addon actions. */
676
- type AddonActionHandler<TValue = unknown> = (ctx: AddonActionContext<TValue>) => void;
677
- /**
678
- * Multi-provider token populated by `provideAddonActions({...})` calls.
679
- * `ADDON_ACTION_REGISTRY` aggregates these into a single name → handler map.
680
- *
681
- * @internal
682
- */
683
- declare const ADDON_ACTION_HANDLERS: InjectionToken<readonly Record<string, AddonActionHandler<unknown>>[]>;
684
550
  /**
685
- * Form-scoped name → handler map. Read by adapter button kinds at click
686
- * time for `actionRef: 'name'` lookups. Provided at form scope (not root)
687
- * because `ADDON_ACTION_HANDLERS` is form-scoped via `provideAddonActions`.
688
- */
689
- declare const ADDON_ACTION_REGISTRY: InjectionToken<ReadonlyMap<string, AddonActionHandler<unknown>>>;
690
-
691
- /**
692
- * Typed feature returned by {@link provideAddonActions}. Carries the
551
+ * Typed feature returned by {@link withAddonActions}. Carries the
693
552
  * registered handler keys as a phantom type so consumers can derive the
694
553
  * `DynamicFormActionRegistry` augmentation in one line instead of hand-typing
695
554
  * every key.
@@ -699,16 +558,7 @@ type AddonActionsFeature<K extends string = string> = DynamicFormFeature<'addon-
699
558
  readonly __handlerKeys?: K;
700
559
  };
701
560
  /** Register named action handlers reachable via JSON-driven addon configs. */
702
- declare function provideAddonActions<const H extends Record<string, AddonActionHandler>>(handlers: H): AddonActionsFeature<keyof H & string>;
703
-
704
- /**
705
- * Multi-provider token used by `withCustomAddon(...)` features to contribute
706
- * addon kind definitions. `provideDynamicForm` aggregates all values into
707
- * the form-scoped `ADDON_KIND_REGISTRY` map.
708
- *
709
- * @internal
710
- */
711
- declare const ADDON_KIND_DEFINITIONS: InjectionToken<readonly AddonKindDefinition<_ng_forge_dynamic_forms.BaseAddon<_ng_forge_dynamic_forms.AddonSlot>>[]>;
561
+ declare function withAddonActions<const H extends Record<string, AddonActionHandler>>(handlers: H): AddonActionsFeature<keyof H & string>;
712
562
 
713
563
  /** Reason an addon was dropped during validation. */
714
564
  type AddonWarning = {
@@ -726,55 +576,30 @@ type AddonWarning = {
726
576
  slot: string;
727
577
  allowedSlots: readonly string[];
728
578
  } | {
729
- type: 'unknown-kind';
579
+ type: 'unknown-type';
730
580
  fieldKey: string;
731
- kind: string;
732
- registeredKinds: readonly string[];
581
+ addonType: string;
582
+ registeredTypes: readonly string[];
733
583
  } | {
734
- type: 'kind-not-allowed';
584
+ type: 'type-not-allowed';
735
585
  fieldKey: string;
736
586
  fieldType: string;
737
- kind: string;
738
- allowedKinds: readonly string[];
587
+ addonType: string;
588
+ allowedTypes: readonly string[];
739
589
  } | {
740
590
  type: 'shape-violation';
741
591
  fieldKey: string;
742
- kind: string;
592
+ addonType: string;
743
593
  reason: string;
744
594
  } | {
745
- type: 'code-only-kind-in-json';
595
+ type: 'code-only-type-in-json';
746
596
  fieldKey: string;
747
- kind: string;
597
+ addonType: string;
748
598
  } | {
749
599
  type: 'code-only-action-in-json';
750
600
  fieldKey: string;
751
601
  reason: string;
752
602
  };
753
- /** Render an `AddonWarning` to a developer-friendly message. */
754
- declare function formatAddonWarning(w: AddonWarning): string;
755
-
756
- /**
757
- * Walk a field's addons; return the survivors plus a list of warnings for
758
- * anything dropped.
759
- *
760
- * @param field The field to validate.
761
- * @param fieldRegistry Snapshot of `FIELD_REGISTRY` map.
762
- * @param kindRegistry Snapshot of `ADDON_KIND_REGISTRY` map.
763
- * @param source Whether the config originated from JSON (lenient drop
764
- * of code-only kinds) or inline TypeScript.
765
- */
766
- declare function validateFieldAddons(field: FieldDef<unknown>, fieldRegistry: ReadonlyMap<string, FieldTypeDefinition>, kindRegistry: ReadonlyMap<string, AddonKindDefinition>, source: 'json' | 'inline'): {
767
- addons: AnyAddon[];
768
- warnings: AddonWarning[];
769
- };
770
- /**
771
- * Recursively walk a field tree, validating each field's addons. Returns a
772
- * sanitized copy of the input field array plus accumulated warnings.
773
- */
774
- declare function walkAndValidateAddons(fields: readonly FieldDef<unknown>[], fieldRegistry: ReadonlyMap<string, FieldTypeDefinition>, kindRegistry: ReadonlyMap<string, AddonKindDefinition>, source: 'json' | 'inline'): {
775
- fields: FieldDef<unknown>[];
776
- warnings: AddonWarning[];
777
- };
778
603
 
779
604
  /** Options accepted by {@link sanitizeFormConfig}. */
780
605
  interface SanitizeFormConfigOptions {
@@ -793,7 +618,7 @@ interface SanitizedFormConfig {
793
618
  }
794
619
  /**
795
620
  * Walk a `FormConfig`, validate every field's addons against the active
796
- * `FIELD_REGISTRY` and `ADDON_KIND_REGISTRY`, and return a sanitized copy
621
+ * `FIELD_REGISTRY` and `ADDON_TYPE_REGISTRY`, and return a sanitized copy
797
622
  * plus a list of dropped-addon warnings.
798
623
  *
799
624
  * @Component({ ... })
@@ -809,28 +634,18 @@ interface SanitizedFormConfig {
809
634
  * ```
810
635
  */
811
636
  declare function sanitizeFormConfig(config: FormConfig, options?: SanitizeFormConfigOptions): SanitizedFormConfig;
812
- /**
813
- * Default warning sink. Pass a `DynamicFormLogger` to route through the
814
- * form's configured logger (respects `NoopLogger` opt-out); omit it to
815
- * fall back to `console.warn` for build-time / admin-UI use cases that
816
- * run outside an Angular injection context.
817
- */
818
- declare function logAddonWarnings(warnings: readonly AddonWarning[], logger?: Logger): void;
819
- /**
820
- * DI-free variant of {@link sanitizeFormConfig} for tooling that lives
821
- * outside Angular's injector — build-time linters, MCP-side checks, CLI
822
- * scripts.
823
- */
824
- declare function sanitizeFormConfigPure(config: FormConfig, fieldRegistry: ReadonlyMap<string, _ng_forge_dynamic_forms_internal.FieldTypeDefinition>, kindRegistry: ReadonlyMap<string, _ng_forge_dynamic_forms_internal.AddonKindDefinition>, options?: SanitizeFormConfigOptions): SanitizedFormConfig;
825
637
 
826
- declare class SubmitEvent implements FormEvent {
638
+ /** Event dispatched to submit the form (the action behind a `submit` button). */
639
+ declare class FormSubmitEvent implements FormEvent {
827
640
  readonly type: "submit";
828
641
  }
829
642
 
643
+ /** Event dispatched to advance a paged form to the next page. */
830
644
  declare class NextPageEvent implements FormEvent {
831
645
  readonly type: "next-page";
832
646
  }
833
647
 
648
+ /** Event dispatched to return a paged form to the previous page. */
834
649
  declare class PreviousPageEvent implements FormEvent {
835
650
  readonly type: "previous-page";
836
651
  }
@@ -1063,29 +878,5 @@ interface ArrayItemContext {
1063
878
  formValue?: unknown;
1064
879
  }
1065
880
 
1066
- /**
1067
- * Type-safe form config builder that ensures schema matches field structure.
1068
- *
1069
- * @typeParam TFields - The field definitions array (narrowed via `as const`)
1070
- * @typeParam TProps - Optional form-level default props type
1071
- * @param config - The form configuration object
1072
- * @returns The same configuration with proper type inference
1073
- */
1074
- declare function formConfig<const TFields extends NarrowFields, TProps extends object = Record<string, unknown>>(config: Omit<FormConfig<TFields, InferFormValue$1<TFields>, TProps>, 'schema'> & {
1075
- schema?: FormSchema<InferFormValue$1<TFields>>;
1076
- }): FormConfig<TFields, InferFormValue$1<TFields>, TProps>;
1077
-
1078
- /**
1079
- * Creates a typed field configuration with helpful error messages for common mistakes.
1080
- *
1081
- * @param type - The field type (e.g., 'input', 'select', 'group')
1082
- * @param config - The field configuration (excluding the 'type' property)
1083
- * @returns A properly typed field configuration
1084
- * @throws DynamicFormError if the configuration contains common mistakes
1085
- */
1086
- declare function createField<T extends AvailableFieldTypes>(type: T, config: Omit<ExtractField<T>, 'type'>): ExtractField<T>;
1087
- /** Shorthand alias for createField */
1088
- declare const field: typeof createField;
1089
-
1090
- export { ADDON_ACTION_HANDLERS, ADDON_ACTION_REGISTRY, ADDON_KIND_COMPONENT_CACHE, ADDON_KIND_DEFINITIONS, AppendArrayItemEvent, BUILT_IN_FIELDS, ComponentAddonComponent, ConsoleLogger, DfAddonSlot, DfTemplate, DynamicForm, EventDispatcher, FormClearEvent, FormResetEvent, InsertArrayItemEvent, MoveArrayItemEvent, NextPageEvent, PageChangeEvent, PopArrayItemEvent, PrependArrayItemEvent, PreviousPageEvent, RemoveAtIndexEvent, ShiftArrayItemEvent, SubmitEvent, TemplateAddonComponent, TextAddonComponent, arrayEvent, createField, createWrappers, field, formConfig, formatAddonWarning, injectAddonKindRegistry, injectFieldsSupportingAddons, isWrappersBundle, logAddonWarnings, provideAddonActions, provideDynamicForm, runPresetAction, sanitizeFormConfig, sanitizeFormConfigPure, validateFieldAddons, walkAndValidateAddons, withCustomAddon, withEventFormValue, withLegacyStatusClasses, withLoggerConfig, withValidationExecutionDefaults, withValueExclusionDefaults, wrapperProps };
1091
- export type { AddonActionHandler, AddonActionsFeature, AddonWarning, ArrayItemContext, ArrayItemDefinitionTemplate, ExtractFieldDefs, ExtractFormValue, FieldAddonSupportEntry, InferWrapperRegistry, PresetCollaborators, SanitizeFormConfigOptions, SanitizedFormConfig, WrapperRegistration, WrappersBundle };
881
+ export { AppendArrayItemEvent, BUILT_IN_FIELDS, ComponentAddonComponent, ConsoleLogger, DfTemplate, DynamicForm, EventDispatcher, FormClearEvent, FormResetEvent, FormSubmitEvent, InsertArrayItemEvent, MoveArrayItemEvent, NextPageEvent, PageChangeEvent, PopArrayItemEvent, PrependArrayItemEvent, PreviousPageEvent, RemoveAtIndexEvent, ShiftArrayItemEvent, TemplateAddonComponent, TextAddonComponent, arrayEvent, createWrappers, isWrappersBundle, provideDynamicForm, sanitizeFormConfig, withAddonActions, withCustomAddon, withEventFormValue, withLegacyStatusClasses, withLoggerConfig, withValidationExecutionDefaults, withValueExclusionDefaults, wrapperProps };
882
+ export type { AddonActionsFeature, AddonWarning, ArrayItemContext, ArrayItemDefinitionTemplate, ExtractFieldDefs, ExtractFormValue, InferWrapperRegistry, SanitizeFormConfigOptions, SanitizedFormConfig, WrapperRegistration, WrappersBundle };
@@ -1 +0,0 @@
1
- {"version":3,"file":"ng-forge-dynamic-forms-group-field.component-Bz4GD8Hy.mjs","mappings":";;;;;;;;;AA0Dc;YAKK;;;;AAIA;AACE;AACF;AACA;YAMA;;;;;AAQjB;;;;YAOS;;;;AAIQ;AACf;AACA;AAEA;AACE;AACA;;AAEA;;AAGE;AACA;;;;;;;AAQF;AACA;AACA;AACA;;;;;;;AAOJ,IAKiB;;;AAIb;;AAEA;AACF;AAEE;AACA;AACD;AAGc;AACf;AACE;;AAGE;AACA;;AAGF;AACF;;;;;AAQO;;AACA;;AACA;;AACA;;AACA;;AACA;;AAEQ;AACf;;AAEA;;;;AAIA;;;AAGE;;AAEA;AACE;;;;AAKN,IAMiB;AACf;;;AAGE;AACF;;AAGe;;AAEf;;;AAGC;AACF;;;AAQQ;;;AAQU;AAGf;AACA;;;AAGA;AACE;;;;AAOH;YAQH;;;;;AAWE;;AAIM;;AAIJ;;;AA/Me;;;;;;AAhBlB;;;;AARS;;;;;;;AAWR;AACA;AACA;AACA;AACA;wEAIA;AACA","names":[],"ignoreList":[],"sources":["../../../../packages/dynamic-forms/src/lib/fields/group/group-field.component.ts"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n computed,\n DestroyRef,\n EnvironmentInjector,\n inject,\n Injector,\n input,\n linkedSignal,\n runInInjectionContext,\n untracked,\n} from '@angular/core';\nimport { DfFieldOutlet } from '../../directives/df-field-outlet/df-field-outlet.directive';\nimport { outputFromObservable, toObservable } from '@angular/core/rxjs-interop';\nimport { explicitEffect } from 'ngxtension/explicit-effect';\nimport { derivedFromDeferred } from '@ng-forge/dynamic-forms/internal';\nimport { createFieldResolutionPipe, ResolvedField } from '../../utils/resolve-field/resolve-field';\nimport { computeContainerHostClasses, setupContainerInitEffect } from '../../utils/container-utils/container-utils';\nimport { CONTAINER_FIELD_PROCESSORS } from '../../utils/container-utils/container-field-processors';\nimport { isEqual } from '@ng-forge/dynamic-forms/internal';\nimport { DynamicFormLogger } from '@ng-forge/dynamic-forms/internal';\nimport { GroupField } from '@ng-forge/dynamic-forms/internal';\nimport { injectFieldRegistry } from '../../utils/inject-field-registry/inject-field-registry';\nimport { FieldTree, form } from '@angular/forms/signals';\nimport { FieldDef } from '@ng-forge/dynamic-forms/internal';\nimport { FieldSignalContext, GroupContext } from '@ng-forge/dynamic-forms/internal';\nimport { FIELD_SIGNAL_CONTEXT, GROUP_CONTEXT, injectFieldSignalContext } from '@ng-forge/dynamic-forms/internal';\nimport { createSchemaFromFields } from '../../core/schema-builder';\nimport { EventBus } from '@ng-forge/dynamic-forms/internal';\nimport { SubmitEvent } from '../../events/constants/submit.event';\n\n/** Container component for rendering nested form groups. */\n@Component({\n selector: 'fieldset[group-field]',\n imports: [DfFieldOutlet],\n template: `\n @for (field of resolvedFields(); track field.key) {\n @if (!field.hidden()) {\n <ng-container *dfFieldOutlet=\"field; environmentInjector: environmentInjector\" />\n }\n }\n `,\n styleUrl: './group-field.component.scss',\n host: {\n '[class]': 'hostClasses()',\n role: 'group',\n '[class.disabled]': 'disabled()',\n '[class.df-container-hidden]': 'hidden()',\n '[attr.aria-hidden]': 'hidden() || null',\n // The group element's own id is the plain `key()` — only its DESCENDANTS\n // compose scoped ids via GROUP_CONTEXT (e.g. children of an `address`\n // group render as `address_street`). The group selector is `#address`.\n '[id]': '`${key()}`',\n '[attr.data-testid]': 'key()',\n },\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport default class GroupFieldComponent<TModel extends Record<string, unknown> = Record<string, unknown>> {\n // ─────────────────────────────────────────────────────────────────────────────\n // Dependencies\n // ─────────────────────────────────────────────────────────────────────────────\n\n private readonly destroyRef = inject(DestroyRef);\n private readonly fieldRegistry = injectFieldRegistry();\n private readonly parentFieldSignalContext = injectFieldSignalContext<TModel>();\n private readonly parentGroupContext = inject(GROUP_CONTEXT, { optional: true });\n private readonly injector = inject(Injector);\n protected readonly environmentInjector = inject(EnvironmentInjector);\n private readonly eventBus = inject(EventBus);\n private readonly logger = inject(DynamicFormLogger);\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Memoized Functions\n // ─────────────────────────────────────────────────────────────────────────────\n\n private readonly fieldProcessors = inject(CONTAINER_FIELD_PROCESSORS);\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Inputs\n // ─────────────────────────────────────────────────────────────────────────────\n\n field = input.required<GroupField>();\n key = input.required<string>();\n className = input<string>();\n hidden = input(false);\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Computed Signals\n // ─────────────────────────────────────────────────────────────────────────────\n\n readonly hostClasses = computed(() => computeContainerHostClasses('group', this.className()));\n\n private readonly rawFieldRegistry = computed(() => this.fieldRegistry.raw);\n\n private readonly formSetup = computed(() => {\n const groupField = this.field();\n const registry = this.rawFieldRegistry();\n\n if (groupField.fields && groupField.fields.length > 0) {\n const flattenedFields = this.fieldProcessors.memoizedFlattenFields(groupField.fields, registry);\n const flattenedFieldsForRendering = this.fieldProcessors.memoizedFlattenFieldsForRendering(groupField.fields, registry);\n const fieldsById = this.fieldProcessors.memoizedKeyBy(flattenedFields);\n const defaultValues = this.fieldProcessors.memoizedDefaultValues(fieldsById, registry);\n\n return {\n fields: flattenedFieldsForRendering,\n schemaFields: flattenedFields,\n originalFields: groupField.fields,\n defaultValues,\n registry,\n };\n }\n\n return {\n fields: [],\n schemaFields: [],\n originalFields: [],\n defaultValues: {},\n registry,\n };\n });\n\n readonly defaultValues = linkedSignal(() => this.formSetup().defaultValues);\n\n /**\n * Entity computed from parent value, group key, and defaults.\n * Uses deep equality check to prevent unnecessary updates when\n * object spread creates new references with identical values.\n */\n private readonly entity = linkedSignal(\n () => {\n const parentValue = this.parentFieldSignalContext.value();\n const groupKey = this.field().key;\n const defaults = this.defaultValues();\n const groupValue = (parentValue as Record<string, unknown>)?.[groupKey] || {};\n return { ...defaults, ...groupValue };\n },\n {\n debugName: 'GroupFieldComponent.entity',\n equal: isEqual,\n },\n );\n\n private readonly form = computed(() => {\n return runInInjectionContext(this.injector, () => {\n const setup = this.formSetup();\n\n if (setup.schemaFields.length > 0) {\n const schema = createSchemaFromFields(setup.schemaFields, setup.registry);\n return untracked(() => form(this.entity, schema));\n }\n\n return untracked(() => form(this.entity));\n });\n });\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Public State Signals\n // ─────────────────────────────────────────────────────────────────────────────\n\n readonly formValue = computed(() => this.entity());\n readonly valid = computed(() => this.form()().valid());\n readonly invalid = computed(() => this.form()().invalid());\n readonly dirty = computed(() => this.form()().dirty());\n readonly touched = computed(() => this.form()().touched());\n readonly errors = computed(() => this.form()().errors());\n readonly disabled = computed(() => this.form()().disabled());\n\n private readonly nestedFieldTree = computed((): FieldTree<Record<string, unknown>> | undefined => {\n const parentForm = this.parentFieldSignalContext.form as Record<string, FieldTree<Record<string, unknown>>>;\n const groupKey = this.field().key;\n return parentForm[groupKey];\n });\n\n private readonly groupFieldSignalContext: FieldSignalContext<Record<string, unknown>> = (() => {\n const nestedFieldTree = this.nestedFieldTree;\n return {\n injector: this.injector,\n value: this.parentFieldSignalContext.value,\n defaultValues: this.defaultValues,\n get form() {\n return nestedFieldTree() ?? ({} as FieldTree<Record<string, unknown>>);\n },\n };\n })();\n\n /**\n * Group context propagated to descendants — carries the dot-separated path of\n * group ancestors so child fields can scope their DOM IDs (issue #401).\n * Reactive via `computed()`: a nested group composes its path from the parent\n * context signal + its own key, so the chain stays consistent if either changes.\n */\n private readonly groupContext: GroupContext = {\n groupPath: computed(() => {\n const ownKey = this.field().key;\n const parentPath = this.parentGroupContext?.groupPath();\n return parentPath ? `${parentPath}.${ownKey}` : ownKey;\n }),\n };\n\n private readonly groupInjector = Injector.create({\n parent: this.injector,\n providers: [\n { provide: FIELD_SIGNAL_CONTEXT, useValue: this.groupFieldSignalContext },\n { provide: GROUP_CONTEXT, useValue: this.groupContext },\n ],\n });\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Outputs\n // ─────────────────────────────────────────────────────────────────────────────\n\n readonly validityChange = outputFromObservable(toObservable(this.valid));\n readonly dirtyChange = outputFromObservable(toObservable(this.dirty));\n readonly submitted = outputFromObservable(this.eventBus.on<SubmitEvent>('submit'));\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Field Resolution\n // ─────────────────────────────────────────────────────────────────────────────\n\n private readonly fieldsSource = computed(() => this.formSetup().fields);\n\n protected readonly resolvedFields = derivedFromDeferred(\n this.fieldsSource,\n createFieldResolutionPipe(() => ({\n loadTypeComponent: (type: string) => this.fieldRegistry.loadTypeComponent(type),\n registry: this.rawFieldRegistry(),\n injector: this.groupInjector,\n destroyRef: this.destroyRef,\n onError: (fieldDef: FieldDef<unknown>, error: unknown) => {\n const fieldKey = fieldDef.key || '<no key>';\n this.logger.error(\n `Failed to load component for field type '${fieldDef.type}' (key: ${fieldKey}) ` +\n `within group '${this.field().key}'. Ensure the field type is registered in your field registry.`,\n error,\n );\n },\n })),\n { initialValue: [] as ResolvedField[], injector: this.injector },\n );\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Constructor\n // ─────────────────────────────────────────────────────────────────────────────\n\n constructor() {\n this.setupEffects();\n }\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Private Methods\n // ─────────────────────────────────────────────────────────────────────────────\n\n private setupEffects(): void {\n setupContainerInitEffect(this.resolvedFields, this.eventBus, 'group', () => this.field().key, this.injector);\n\n explicitEffect(\n [this.nestedFieldTree, this.field],\n ([tree, field]) => {\n if (!tree) {\n this.logger.warn(\n `Group field \"${field.key}\" not found in parent form. ` + `Ensure the parent form schema includes this group field.`,\n );\n }\n },\n { defer: true },\n );\n }\n}\n\nexport { GroupFieldComponent };\n"]}