@ng-forge/dynamic-forms 0.9.0-next.11 → 0.9.0-next.12

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 (26) hide show
  1. package/fesm2022/{ng-forge-dynamic-forms-array-field.component-DJ-LiLm_.mjs → ng-forge-dynamic-forms-array-field.component-BgCrTME1.mjs} +154 -69
  2. package/fesm2022/ng-forge-dynamic-forms-array-field.component-BgCrTME1.mjs.map +1 -0
  3. package/fesm2022/{ng-forge-dynamic-forms-container-field.component-Pcxg5PQu.mjs → ng-forge-dynamic-forms-container-field.component-VHaBk_1l.mjs} +9 -5
  4. package/fesm2022/ng-forge-dynamic-forms-container-field.component-VHaBk_1l.mjs.map +1 -0
  5. package/fesm2022/{ng-forge-dynamic-forms-container-utils-a4RstR2Z.mjs → ng-forge-dynamic-forms-container-utils-CGJiiKBY.mjs} +2 -2
  6. package/fesm2022/{ng-forge-dynamic-forms-container-utils-a4RstR2Z.mjs.map → ng-forge-dynamic-forms-container-utils-CGJiiKBY.mjs.map} +1 -1
  7. package/fesm2022/{ng-forge-dynamic-forms-css-wrapper.component-CJACCIEK.mjs → ng-forge-dynamic-forms-css-wrapper.component-BOdw40ZF.mjs} +2 -2
  8. package/fesm2022/{ng-forge-dynamic-forms-css-wrapper.component-CJACCIEK.mjs.map → ng-forge-dynamic-forms-css-wrapper.component-BOdw40ZF.mjs.map} +1 -1
  9. package/fesm2022/{ng-forge-dynamic-forms-emit-initialization-Bjf_I0D_.mjs → ng-forge-dynamic-forms-emit-initialization-D72mQxlX.mjs} +2 -2
  10. package/fesm2022/{ng-forge-dynamic-forms-emit-initialization-Bjf_I0D_.mjs.map → ng-forge-dynamic-forms-emit-initialization-D72mQxlX.mjs.map} +1 -1
  11. package/fesm2022/{ng-forge-dynamic-forms-group-field.component-fzKwPrQr.mjs → ng-forge-dynamic-forms-group-field.component-BvTQwoLg.mjs} +9 -5
  12. package/fesm2022/ng-forge-dynamic-forms-group-field.component-BvTQwoLg.mjs.map +1 -0
  13. package/fesm2022/{ng-forge-dynamic-forms-ng-forge-dynamic-forms-o3rJX4vG.mjs → ng-forge-dynamic-forms-ng-forge-dynamic-forms-BAPGCjUs.mjs} +396 -248
  14. package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-BAPGCjUs.mjs.map +1 -0
  15. package/fesm2022/{ng-forge-dynamic-forms-page-field.component-BX4R9GGa.mjs → ng-forge-dynamic-forms-page-field.component-DDzmhJNn.mjs} +9 -5
  16. package/fesm2022/ng-forge-dynamic-forms-page-field.component-DDzmhJNn.mjs.map +1 -0
  17. package/fesm2022/{ng-forge-dynamic-forms-text-field.component-B9SK8Vun.mjs → ng-forge-dynamic-forms-text-field.component-Bo4zNJXY.mjs} +2 -2
  18. package/fesm2022/{ng-forge-dynamic-forms-text-field.component-B9SK8Vun.mjs.map → ng-forge-dynamic-forms-text-field.component-Bo4zNJXY.mjs.map} +1 -1
  19. package/fesm2022/ng-forge-dynamic-forms.mjs +1 -1
  20. package/package.json +1 -1
  21. package/types/ng-forge-dynamic-forms.d.ts +488 -468
  22. package/fesm2022/ng-forge-dynamic-forms-array-field.component-DJ-LiLm_.mjs.map +0 -1
  23. package/fesm2022/ng-forge-dynamic-forms-container-field.component-Pcxg5PQu.mjs.map +0 -1
  24. package/fesm2022/ng-forge-dynamic-forms-group-field.component-fzKwPrQr.mjs.map +0 -1
  25. package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-o3rJX4vG.mjs.map +0 -1
  26. package/fesm2022/ng-forge-dynamic-forms-page-field.component-BX4R9GGa.mjs.map +0 -1
@@ -1,8 +1,8 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, reflectComponentType, Injector, inject, EnvironmentInjector, DestroyRef, computed, input, ViewContainerRef, signal, Directive, Injectable, isSignal, untracked, linkedSignal, ChangeDetectionStrategy, Component, resourceFromSnapshots, runInInjectionContext, afterNextRender, isWritableSignal, model, makeEnvironmentProviders, Pipe } from '@angular/core';
2
+ import { InjectionToken, reflectComponentType, Injector, inject, EnvironmentInjector, DestroyRef, computed, signal, input, ViewContainerRef, Directive, Injectable, isSignal, untracked, linkedSignal, ChangeDetectionStrategy, Component, resourceFromSnapshots, runInInjectionContext, afterNextRender, isWritableSignal, model, makeEnvironmentProviders, Pipe } from '@angular/core';
3
3
  import { explicitEffect } from 'ngxtension/explicit-effect';
4
- import { of, forkJoin, from, catchError, map, switchMap, isObservable, firstValueFrom, exhaustMap, EMPTY, Subject, filter, TimeoutError, throwError, defer, combineLatest, pipe, debounceTime, distinctUntilChanged, tap, startWith, scan as scan$1, Observable, concatMap, take as take$1, pairwise, takeUntil, combineLatestWith, auditTime, scheduled, queueScheduler, merge, timer } from 'rxjs';
5
4
  import { takeUntilDestroyed, toObservable, toSignal, rxResource, outputFromObservable } from '@angular/core/rxjs-interop';
5
+ import { of, forkJoin, from, catchError, map, switchMap, isObservable, firstValueFrom, exhaustMap, EMPTY, Subject, filter, TimeoutError, throwError, defer, combineLatest, pipe, debounceTime, distinctUntilChanged, tap, startWith, scan as scan$1, Observable, concatMap, take as take$1, pairwise, takeUntil, combineLatestWith, auditTime, scheduled, queueScheduler, merge, timer } from 'rxjs';
6
6
  import { submit, email, min, max, minLength, maxLength, pattern, required, validate, validateAsync, validateHttp, disabled, readonly, hidden, applyEach, applyWhenValue, applyWhen, apply, schema, validateStandardSchema, validateTree, form, provideSignalFormsConfig } from '@angular/forms/signals';
7
7
  import { scan, map as map$1, filter as filter$1, take, switchMap as switchMap$1, timeout, catchError as catchError$1, shareReplay } from 'rxjs/operators';
8
8
  import { HttpClient } from '@angular/common/http';
@@ -252,65 +252,57 @@ const FORM_OPTIONS = new InjectionToken('FORM_OPTIONS');
252
252
  * and inherited by all children via Angular's hierarchical injector.
253
253
  */
254
254
  const EXTERNAL_DATA = new InjectionToken('EXTERNAL_DATA');
255
+
255
256
  /**
256
- * Injection token for array-level template registry.
257
- *
258
- * This registry tracks which template was used to create each array item,
259
- * keyed by the item's unique ID. This is essential for "recreate" operations
260
- * (e.g., after removing items from the middle) where we need to re-resolve
261
- * items using their original templates, not a fallback.
262
- *
263
- * The token is provided by ArrayFieldComponent at its level and shared
264
- * across all items in that array. Each nested array gets its own registry.
265
- *
266
- * @example
267
- * ```typescript
268
- * // In ArrayFieldComponent
269
- * providers: [
270
- * { provide: ARRAY_TEMPLATE_REGISTRY, useValue: new Map() }
271
- * ]
272
- *
273
- * // When adding items
274
- * const registry = inject(ARRAY_TEMPLATE_REGISTRY);
275
- * registry.set(itemId, templates);
257
+ * Injection token for the dynamic forms logger.
276
258
  *
277
- * // During recreate, look up original template
278
- * const originalTemplate = registry.get(existingItemId);
279
- * ```
259
+ * Provided by provideDynamicForm() with ConsoleLogger as default.
260
+ * Override with withLoggerConfig(false) to disable logging.
280
261
  */
281
- const ARRAY_TEMPLATE_REGISTRY = new InjectionToken('ARRAY_TEMPLATE_REGISTRY');
262
+ const DynamicFormLogger = new InjectionToken('DynamicFormLogger');
263
+
282
264
  /**
283
- * Injection token for array-level item ID generator.
284
- *
285
- * Provides a function that generates unique IDs for array items. Each array
286
- * component instance gets its own generator (via useFactory), ensuring:
287
- * - SSR hydration compatibility (server and client generate same IDs for same array)
288
- * - No global state pollution between form instances
289
- * - Deterministic IDs within each array's lifecycle
290
- *
291
- * The token is provided by ArrayFieldComponent at its level with a factory
292
- * that creates a fresh counter for each array instance.
293
- *
294
- * @example
295
- * ```typescript
296
- * // In ArrayFieldComponent
297
- * providers: [
298
- * { provide: ARRAY_ITEM_ID_GENERATOR, useFactory: createArrayItemIdGenerator }
299
- * ]
300
- *
301
- * // Usage
302
- * const generateId = inject(ARRAY_ITEM_ID_GENERATOR);
303
- * const itemId = generateId(); // 'item-0', 'item-1', etc.
304
- * ```
265
+ * Shared empty chain — returned when no wrappers apply so callers get a
266
+ * stable reference. Freezing makes accidental mutation observable.
305
267
  */
306
- const ARRAY_ITEM_ID_GENERATOR = new InjectionToken('ARRAY_ITEM_ID_GENERATOR');
268
+ const EMPTY_WRAPPERS = Object.freeze([]);
307
269
  /**
308
- * Factory function that creates a new array item ID generator.
309
- * Each invocation creates an independent counter starting at 0.
270
+ * Element-wise identity (`===`) comparator for wrapper chains. Used as the
271
+ * `equal` option on signal memoisation so reconciled `FieldDef`s with the
272
+ * same chain don't churn downstream — relies on `WrapperConfig` objects
273
+ * being stable across ticks, which holds for configs declared in
274
+ * `FormConfig` / `createWrappers(...)`.
310
275
  */
311
- function createArrayItemIdGenerator() {
312
- let counter = 0;
313
- return () => `item-${counter++}`;
276
+ function isSameWrapperChain(a, b) {
277
+ if (a === b)
278
+ return true;
279
+ if (a.length !== b.length)
280
+ return false;
281
+ for (let i = 0; i < a.length; i++) {
282
+ if (a[i] !== b[i])
283
+ return false;
284
+ }
285
+ return true;
286
+ }
287
+ /**
288
+ * Resolves the wrapper chain for a field.
289
+ *
290
+ * Merge order (outermost → innermost): auto-associations, form defaults,
291
+ * field-level wrappers. `wrappers: null` skips all three; `skipAutoWrappers` /
292
+ * `skipDefaultWrappers` on the field opt out of individual layers. Fresh
293
+ * array per call — downstream memoisation is on the consuming computed.
294
+ */
295
+ function resolveWrappers(field, defaultWrappers, autoAssociations) {
296
+ if (field.wrappers === null) {
297
+ return EMPTY_WRAPPERS;
298
+ }
299
+ const autoWrappers = field.skipAutoWrappers ? EMPTY_WRAPPERS : (autoAssociations.get(field.type) ?? EMPTY_WRAPPERS);
300
+ const defaults = field.skipDefaultWrappers ? EMPTY_WRAPPERS : (defaultWrappers ?? EMPTY_WRAPPERS);
301
+ const fieldLevel = field.wrappers ?? EMPTY_WRAPPERS;
302
+ if (autoWrappers.length === 0 && defaults.length === 0 && fieldLevel.length === 0) {
303
+ return EMPTY_WRAPPERS;
304
+ }
305
+ return [...autoWrappers, ...defaults, ...fieldLevel];
314
306
  }
315
307
 
316
308
  /**
@@ -507,58 +499,6 @@ class WrapperAwareInjector extends Injector {
507
499
  }
508
500
  }
509
501
 
510
- /**
511
- * Injection token for the dynamic forms logger.
512
- *
513
- * Provided by provideDynamicForm() with ConsoleLogger as default.
514
- * Override with withLoggerConfig(false) to disable logging.
515
- */
516
- const DynamicFormLogger = new InjectionToken('DynamicFormLogger');
517
-
518
- /**
519
- * Shared empty chain — returned when no wrappers apply so callers get a
520
- * stable reference. Freezing makes accidental mutation observable.
521
- */
522
- const EMPTY_WRAPPERS = Object.freeze([]);
523
- /**
524
- * Element-wise identity (`===`) comparator for wrapper chains. Used as the
525
- * `equal` option on signal memoisation so reconciled `FieldDef`s with the
526
- * same chain don't churn downstream — relies on `WrapperConfig` objects
527
- * being stable across ticks, which holds for configs declared in
528
- * `FormConfig` / `createWrappers(...)`.
529
- */
530
- function isSameWrapperChain(a, b) {
531
- if (a === b)
532
- return true;
533
- if (a.length !== b.length)
534
- return false;
535
- for (let i = 0; i < a.length; i++) {
536
- if (a[i] !== b[i])
537
- return false;
538
- }
539
- return true;
540
- }
541
- /**
542
- * Resolves the wrapper chain for a field.
543
- *
544
- * Merge order (outermost → innermost): auto-associations, form defaults,
545
- * field-level wrappers. `wrappers: null` skips all three; `skipAutoWrappers` /
546
- * `skipDefaultWrappers` on the field opt out of individual layers. Fresh
547
- * array per call — downstream memoisation is on the consuming computed.
548
- */
549
- function resolveWrappers(field, defaultWrappers, autoAssociations) {
550
- if (field.wrappers === null) {
551
- return EMPTY_WRAPPERS;
552
- }
553
- const autoWrappers = field.skipAutoWrappers ? EMPTY_WRAPPERS : (autoAssociations.get(field.type) ?? EMPTY_WRAPPERS);
554
- const defaults = field.skipDefaultWrappers ? EMPTY_WRAPPERS : (defaultWrappers ?? EMPTY_WRAPPERS);
555
- const fieldLevel = field.wrappers ?? EMPTY_WRAPPERS;
556
- if (autoWrappers.length === 0 && defaults.length === 0 && fieldLevel.length === 0) {
557
- return EMPTY_WRAPPERS;
558
- }
559
- return [...autoWrappers, ...defaults, ...fieldLevel];
560
- }
561
-
562
502
  /**
563
503
  * Owns the wrapper-chain lifecycle for a field or container: async component
564
504
  * loading, switchMap-based cancellation of stale loads, flicker-tolerant
@@ -749,6 +689,155 @@ function toReadonlyFieldTreeCached(cache, field) {
749
689
  return view;
750
690
  }
751
691
 
692
+ const EMPTY_STATE = Object.freeze({ phase: 'empty' });
693
+ /**
694
+ * Encapsulates the imperative `ViewContainerRef` / `ComponentRef` lifecycle
695
+ * for the innermost field component. Pulled out of `DfFieldOutlet` so the
696
+ * directive itself stays declarative — signals + constructor wiring — while
697
+ * the unavoidable mutable bookkeeping (Angular's view container API is
698
+ * imperative) lives in one place behind a small, focused surface.
699
+ *
700
+ * State is held in a single `signal<FieldSlotState>` discriminated union;
701
+ * every mutation is an explicit `state.set(...)` transition. No nullable
702
+ * instance fields, no `undefined`-initialized refs — each phase carries
703
+ * exactly the data it owns.
704
+ *
705
+ * Responsibilities:
706
+ * - Create the component (or reuse the same-class instance across a rebuild,
707
+ * preserving its DOM state, focus, and caret).
708
+ * - Push mapper inputs via `setInput`, dedup'd per-key so dev-mode NG0303
709
+ * warnings for undeclared mapper keys don't replay on every emission.
710
+ * - Detach cleanly before the outer VCR clears, so same-class reuse can
711
+ * re-insert the preserved `hostView`.
712
+ * - Destroy on directive teardown when the host view is still detached.
713
+ */
714
+ class FieldComponentSlot {
715
+ state = signal(EMPTY_STATE, ...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
716
+ /**
717
+ * Per-key cache for `pushInputs`. Lives outside the lifecycle state machine on
718
+ * purpose — it's a `setInput`-call optimization that changes on every keystroke,
719
+ * not a phase transition. Keeping it here means the `state` signal only emits
720
+ * on real `empty ⇄ mounted ⇄ detached` transitions (≈ once per
721
+ * mount/destroy cycle), not once per input emission.
722
+ */
723
+ lastInputs = null;
724
+ /** Read-only view of the lifecycle phase. Useful for tests and assertions. */
725
+ phase = () => this.state().phase;
726
+ /** Read-only view of the full state. Exposed so tests can assert ref stability. */
727
+ snapshot = () => this.state();
728
+ /**
729
+ * Mount the field component into `slot`. Reuses the existing `ComponentRef`
730
+ * when its component type matches (preserving DOM/focus/caret across a
731
+ * wrapper-chain rebuild); otherwise discards the old ref and creates a fresh
732
+ * one.
733
+ */
734
+ mountOrReuse(slot, component, fieldInjector, environmentInjector, rawInputs) {
735
+ const current = this.state();
736
+ if (current.phase !== 'empty' && current.ref.componentType === component) {
737
+ slot.insert(current.ref.hostView);
738
+ this.lastInputs = null;
739
+ this.state.set(Object.freeze({ phase: 'mounted', ref: current.ref, slot }));
740
+ this.pushInputs(rawInputs);
741
+ if (current.phase === 'detached')
742
+ this.replayFocus(current.focusSnapshot);
743
+ return;
744
+ }
745
+ // Different component class — destroy the prior ref (if any) and create
746
+ // a fresh one. The `empty` transition before `createComponent` ensures a
747
+ // throw inside the factory doesn't leave a dangling ref the next mount
748
+ // would try to re-insert.
749
+ if (current.phase !== 'empty')
750
+ current.ref.destroy();
751
+ this.lastInputs = null;
752
+ this.state.set(EMPTY_STATE);
753
+ const ref = slot.createComponent(component, {
754
+ environmentInjector,
755
+ injector: createWrapperAwareInjector(fieldInjector, slot.injector),
756
+ });
757
+ this.state.set(Object.freeze({ phase: 'mounted', ref, slot }));
758
+ this.pushInputs(rawInputs);
759
+ }
760
+ /**
761
+ * Push mapper inputs onto the mounted component. Per-key reference compare
762
+ * skips `setInput` for unchanged values — necessary in dev mode where
763
+ * `setInput` on unknown keys logs NG0303 per call (Angular 21 is lenient
764
+ * at runtime but warns); without the cache, every emission would re-warn
765
+ * for every undeclared mapper key.
766
+ *
767
+ * Updates the `lastInputs` cache (a plain field, not part of `state`) so the
768
+ * `state` signal doesn't churn its reference on every keystroke.
769
+ *
770
+ * `reflectComponentType` does NOT include hostDirective-forwarded inputs
771
+ * (angular/angular#49734), so a declared-input filter here would skip every
772
+ * value flowing through `NgForgeFieldShell` / `NgForgeField` /
773
+ * `NgForgeAction`. Mapper-as-contract is enforced by the `*_INPUTS`
774
+ * lockstep type assertions, not at runtime here.
775
+ */
776
+ pushInputs(rawInputs) {
777
+ const current = this.state();
778
+ if (current.phase !== 'mounted')
779
+ return;
780
+ const prev = this.lastInputs;
781
+ if (prev === rawInputs)
782
+ return;
783
+ for (const [key, value] of Object.entries(rawInputs)) {
784
+ if (prev && prev[key] === value)
785
+ continue;
786
+ current.ref.setInput(key, value);
787
+ }
788
+ this.lastInputs = rawInputs;
789
+ }
790
+ /**
791
+ * Detach the host view from its current slot, capturing focus + caret so the
792
+ * next `mountOrReuse()` can replay them after re-inserting into the new
793
+ * slot. A stranded `mounted` state with a bad slot index (re-entrancy during
794
+ * a partial rebuild) is treated like `empty` to avoid leaks.
795
+ */
796
+ detach() {
797
+ const current = this.state();
798
+ if (current.phase !== 'mounted')
799
+ return;
800
+ const focusSnapshot = this.captureFocus(current.ref);
801
+ const idx = current.slot.indexOf(current.ref.hostView);
802
+ if (idx >= 0)
803
+ current.slot.detach(idx);
804
+ this.lastInputs = null;
805
+ this.state.set(Object.freeze({ phase: 'detached', ref: current.ref, focusSnapshot }));
806
+ }
807
+ /**
808
+ * Called on directive teardown. Only destroys when the host view is still
809
+ * detached — otherwise the outer `vcr.clear()` cascade has already destroyed
810
+ * it (calling `.destroy()` again would throw).
811
+ */
812
+ destroyOnTeardown() {
813
+ const current = this.state();
814
+ if (current.phase === 'detached')
815
+ current.ref.destroy();
816
+ this.lastInputs = null;
817
+ this.state.set(EMPTY_STATE);
818
+ }
819
+ captureFocus(ref) {
820
+ const hostEl = ref.location.nativeElement;
821
+ const active = document.activeElement;
822
+ if (!(active instanceof HTMLElement) || !hostEl.contains(active))
823
+ return null;
824
+ const sel = active;
825
+ return {
826
+ element: active,
827
+ selectionStart: typeof sel.selectionStart === 'number' ? sel.selectionStart : null,
828
+ selectionEnd: typeof sel.selectionEnd === 'number' ? sel.selectionEnd : null,
829
+ };
830
+ }
831
+ replayFocus(snap) {
832
+ if (!snap || !snap.element.isConnected)
833
+ return;
834
+ snap.element.focus();
835
+ if (snap.selectionStart !== null && snap.selectionEnd !== null && 'setSelectionRange' in snap.element) {
836
+ snap.element.setSelectionRange(snap.selectionStart, snap.selectionEnd);
837
+ }
838
+ }
839
+ }
840
+
752
841
  /**
753
842
  * Structural directive that renders a `ResolvedField` with its effective
754
843
  * wrapper chain.
@@ -769,12 +858,16 @@ function toReadonlyFieldTreeCached(cache, field) {
769
858
  * includes the field's mapper outputs and a `ReadonlyFieldTree` view of its
770
859
  * form state.
771
860
  *
772
- * Rendering is gated by `field.renderReady()` — the directive waits until
773
- * the mapper produces the required inputs before instantiating the component.
774
- * Once rendered, a transient `renderReady → false` does *not* tear the chain
775
- * down; the controller keeps the mounted components alive and ignores the
776
- * flicker. Only a structural change (wrappers or component class) triggers
777
- * a rebuild.
861
+ * Rendering is gated by `field.renderReady() && !field.hidden()` — the
862
+ * directive waits until the mapper produces the required inputs AND the
863
+ * field isn't hidden before instantiating the component. Once rendered, a
864
+ * transient `renderReady → false` does *not* tear the chain down; the
865
+ * controller keeps the mounted components alive and ignores the flicker.
866
+ * Only a structural change (wrappers or component class) triggers a rebuild.
867
+ *
868
+ * Imperative `ComponentRef` / `ViewContainerRef` lifecycle is encapsulated
869
+ * in `FieldComponentSlot` — the directive itself is signal definitions
870
+ * plus three method calls (`mountOrReuse`, `detach`, `destroyOnTeardown`).
778
871
  *
779
872
  * @example
780
873
  * ```html
@@ -794,25 +887,16 @@ class DfFieldOutlet {
794
887
  wrapperAutoAssociations = inject(WRAPPER_AUTO_ASSOCIATIONS);
795
888
  defaultWrappersSignal = inject(DEFAULT_WRAPPERS, { optional: true });
796
889
  readonlyFieldCache = inject(READONLY_FIELD_TREE_CACHE);
797
- fieldRef;
798
- /**
799
- * VCR the `fieldRef`'s host view is currently inserted into (either the outer
800
- * VCR when there are no wrappers, or the innermost wrapper's `#fieldComponent`
801
- * slot). Tracked so `beforeRebuild` can detach the view before the outer VCR
802
- * cascade-destroys it.
803
- */
804
- fieldSlot;
805
- /** Focus + caret captured during detach, replayed after re-insert. */
806
- focusSnapshot;
890
+ /** Encapsulates Angular's imperative ComponentRef / ViewContainerRef lifecycle. */
891
+ fieldComponent = new FieldComponentSlot();
892
+ componentIdentity = computed(() => this.dfFieldOutlet().component, ...(ngDevMode ? [{ debugName: "componentIdentity" }] : /* istanbul ignore next */ []));
807
893
  /**
808
- * Last rawInputs reference pushed to the innermost field. Used to dedupe the
809
- * rawInputs effect when the same snapshot was already applied as part of the
810
- * initial render — keeps per-keystroke input updates O(changed-keys) instead
811
- * of re-walking the whole input bag.
894
+ * Gate for the wrapper chain controller: true only when required inputs are populated AND the
895
+ * field isn't hidden. Combining the two prevents an initial-mount race where the chain mounts
896
+ * during the brief window between `renderReady` flipping true and `hidden()` settling — that
897
+ * window is exactly where Angular Signal Forms' `[formField]` directive emits NG01916.
812
898
  */
813
- lastPushedInputs;
814
- componentIdentity = computed(() => this.dfFieldOutlet().component, ...(ngDevMode ? [{ debugName: "componentIdentity" }] : /* istanbul ignore next */ []));
815
- renderReady = computed(() => this.dfFieldOutlet().renderReady(), ...(ngDevMode ? [{ debugName: "renderReady" }] : /* istanbul ignore next */ []));
899
+ renderReady = computed(() => this.dfFieldOutlet().renderReady() && !this.dfFieldOutlet().hidden(), ...(ngDevMode ? [{ debugName: "renderReady" }] : /* istanbul ignore next */ []));
816
900
  rawInputs = computed(() => this.dfFieldOutlet().inputs(), ...(ngDevMode ? [{ debugName: "rawInputs" }] : /* istanbul ignore next */ []));
817
901
  /**
818
902
  * Effective wrapper chain. Element-wise identity comparison keeps the signal
@@ -839,109 +923,17 @@ class DfFieldOutlet {
839
923
  rebuildKey: this.componentIdentity,
840
924
  fieldInputs: this.fieldInputs,
841
925
  fieldInjector: this.fieldInjector,
842
- beforeRebuild: () => this.detachFieldRef(),
926
+ beforeRebuild: () => this.fieldComponent.detach(),
843
927
  renderInnermost: (slot) => {
844
928
  const resolved = this.dfFieldOutlet();
845
- if (this.fieldRef && this.fieldRef.componentType === resolved.component) {
846
- // Same component class — re-insert the preserved hostView. Browser
847
- // loses focus on detach, so we replay the snapshot captured in
848
- // detachFieldRef. Input value persists on the DOM node itself.
849
- slot.insert(this.fieldRef.hostView);
850
- this.fieldSlot = slot;
851
- this.pushRawInputs(this.fieldRef, this.rawInputs());
852
- this.restoreFocusSnapshot();
853
- return;
854
- }
855
- // Different component class — discard the old ref and create fresh.
856
- // Merge field + element injectors so the field can inject ancestor
857
- // wrappers AND form-context tokens (ARRAY_CONTEXT etc.).
858
- // Clear state before createComponent — if it throws (bad provider,
859
- // invalid class) we don't want to leak a reference to a destroyed ref
860
- // that the same-component branch would later try to re-insert.
861
- this.focusSnapshot = undefined;
862
- this.lastPushedInputs = undefined;
863
- const previousRef = this.fieldRef;
864
- this.fieldRef = undefined;
865
- this.fieldSlot = undefined;
866
- previousRef?.destroy();
867
- this.fieldRef = slot.createComponent(resolved.component, {
868
- environmentInjector: this.fieldEnvInjector(),
869
- injector: createWrapperAwareInjector(resolved.injector, slot.injector),
870
- });
871
- this.fieldSlot = slot;
872
- // setInput dirties the signal synchronously; the next CD pass reads
873
- // it. Safe outside CD — see wrapper-chain-controller scheduling.
874
- this.pushRawInputs(this.fieldRef, this.rawInputs());
929
+ this.fieldComponent.mountOrReuse(slot, resolved.component, resolved.injector, this.fieldEnvInjector(), this.rawInputs());
875
930
  },
876
931
  });
877
- // Push rawInputs to the innermost field. Safe mid-rebuild — renderInnermost
878
- // re-reads rawInputs synchronously on mount, so skipping here loses nothing.
879
- explicitEffect([this.rawInputs], ([rawInputs]) => {
880
- if (!this.fieldRef)
881
- return;
882
- if (this.lastPushedInputs === rawInputs)
883
- return;
884
- this.pushRawInputs(this.fieldRef, rawInputs);
885
- });
886
- this.destroyRef.onDestroy(() => {
887
- // Only destroy when detached — vcr.clear() cascades otherwise.
888
- if (this.fieldRef && !this.fieldSlot)
889
- this.fieldRef.destroy();
890
- this.fieldRef = undefined;
891
- this.fieldSlot = undefined;
892
- this.lastPushedInputs = undefined;
893
- });
894
- }
895
- detachFieldRef() {
896
- if (!this.fieldRef)
897
- return;
898
- if (!this.fieldSlot) {
899
- // Stranded fieldRef — destroy to prevent a leak.
900
- this.fieldRef.destroy();
901
- this.fieldRef = undefined;
902
- return;
903
- }
904
- this.captureFocusSnapshot();
905
- const idx = this.fieldSlot.indexOf(this.fieldRef.hostView);
906
- if (idx >= 0)
907
- this.fieldSlot.detach(idx);
908
- this.fieldSlot = undefined;
909
- }
910
- /** Capture active element + selection before the hostView is detached from DOM. */
911
- captureFocusSnapshot() {
912
- if (!this.fieldRef)
913
- return;
914
- const hostEl = this.fieldRef.location.nativeElement;
915
- const active = document.activeElement;
916
- if (!(active instanceof HTMLElement) || !hostEl.contains(active))
917
- return;
918
- const sel = active;
919
- this.focusSnapshot = {
920
- element: active,
921
- selectionStart: typeof sel.selectionStart === 'number' ? sel.selectionStart : null,
922
- selectionEnd: typeof sel.selectionEnd === 'number' ? sel.selectionEnd : null,
923
- };
924
- }
925
- /** Re-apply the pre-detach focus + caret after the hostView is back in the DOM. */
926
- restoreFocusSnapshot() {
927
- const snap = this.focusSnapshot;
928
- this.focusSnapshot = undefined;
929
- if (!snap || !snap.element.isConnected)
930
- return;
931
- snap.element.focus();
932
- if (snap.selectionStart !== null && snap.selectionEnd !== null && 'setSelectionRange' in snap.element) {
933
- snap.element.setSelectionRange(snap.selectionStart, snap.selectionEnd);
934
- }
935
- }
936
- pushRawInputs(ref, rawInputs) {
937
- // setInput is lenient in Angular 21 — unknown keys silently drop (not NG0303). reflectComponentType does NOT include hostDirective forwarded inputs (angular/angular#49734), so a declared-input filter here would skip every value flowing through NgForgeFieldShell / NgForgeField / NgForgeAction. Mapper-as-contract is enforced by the *_INPUTS lockstep type assertions, not at runtime here.
938
- const last = this.lastPushedInputs;
939
- for (const [key, value] of Object.entries(rawInputs)) {
940
- if (last && last[key] === value)
941
- continue;
942
- ref.setInput(key, value);
943
- }
944
- this.lastPushedInputs = rawInputs;
932
+ // Push rawInputs to the innermost field on every change. The slot dedupes per-key, so the
933
+ // initial render's synchronous push from `renderInnermost` and the effect's first fire
934
+ // converge cleanly with no double-pushing.
935
+ explicitEffect([this.rawInputs], ([rawInputs]) => this.fieldComponent.pushInputs(rawInputs));
936
+ this.destroyRef.onDestroy(() => this.fieldComponent.destroyOnTeardown());
945
937
  }
946
938
  buildFieldInputs(rawInputs) {
947
939
  const fieldTreeCandidate = rawInputs['field'];
@@ -4180,9 +4172,9 @@ class PageOrchestratorComponent {
4180
4172
  }
4181
4173
  }
4182
4174
  }
4183
- `, isInline: true, styles: [":host{display:block;position:relative;width:100%}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, deferBlockDependencies: [() => [import('./ng-forge-dynamic-forms-page-field.component-BX4R9GGa.mjs').then(m => m.default)], () => [import('./ng-forge-dynamic-forms-page-field.component-BX4R9GGa.mjs').then(m => m.default)]] });
4175
+ `, isInline: true, styles: [":host{display:block;position:relative;width:100%}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, deferBlockDependencies: [() => [import('./ng-forge-dynamic-forms-page-field.component-DDzmhJNn.mjs').then(m => m.default)], () => [import('./ng-forge-dynamic-forms-page-field.component-DDzmhJNn.mjs').then(m => m.default)]] });
4184
4176
  }
4185
- i0.ɵɵngDeclareClassMetadataAsync({ minVersion: "18.0.0", version: "21.2.8", ngImport: i0, type: PageOrchestratorComponent, resolveDeferredDeps: () => [import('./ng-forge-dynamic-forms-page-field.component-BX4R9GGa.mjs').then(m => m.default)], resolveMetadata: PageFieldComponent => ({ decorators: [{
4177
+ i0.ɵɵngDeclareClassMetadataAsync({ minVersion: "18.0.0", version: "21.2.8", ngImport: i0, type: PageOrchestratorComponent, resolveDeferredDeps: () => [import('./ng-forge-dynamic-forms-page-field.component-DDzmhJNn.mjs').then(m => m.default)], resolveMetadata: PageFieldComponent => ({ decorators: [{
4186
4178
  type: Component,
4187
4179
  args: [{ selector: 'div[page-orchestrator]', imports: [PageFieldComponent], template: `
4188
4180
  @for (pageField of pageFields(); track pageField.key; let i = $index) {
@@ -4614,6 +4606,85 @@ function isValidNonPagedForm(fields) {
4614
4606
  return result.mode === 'non-paged' && result.isValid;
4615
4607
  }
4616
4608
 
4609
+ function createSlot() {
4610
+ let counter = 0;
4611
+ const templates = new Map();
4612
+ const itemOrder = [];
4613
+ const primitiveFieldKey = signal(undefined, ...(ngDevMode ? [{ debugName: "primitiveFieldKey" }] : /* istanbul ignore next */ []));
4614
+ return {
4615
+ templates,
4616
+ itemOrder,
4617
+ primitiveFieldKey,
4618
+ nextId: () => `item-${counter++}`,
4619
+ setTemplate(itemId, t) {
4620
+ templates.set(itemId, t);
4621
+ },
4622
+ getTemplate(itemId) {
4623
+ return templates.get(itemId);
4624
+ },
4625
+ hasTemplate(itemId) {
4626
+ return templates.has(itemId);
4627
+ },
4628
+ setIdAt(index, itemId) {
4629
+ itemOrder[index] = itemId;
4630
+ },
4631
+ removeAt(index) {
4632
+ const [removed] = itemOrder.splice(index, 1);
4633
+ if (removed !== undefined)
4634
+ templates.delete(removed);
4635
+ },
4636
+ moveItem(fromIndex, toIndex) {
4637
+ const [id] = itemOrder.splice(fromIndex, 1);
4638
+ if (id !== undefined)
4639
+ itemOrder.splice(toIndex, 0, id);
4640
+ },
4641
+ truncate(length) {
4642
+ const removed = itemOrder.splice(length);
4643
+ for (const id of removed)
4644
+ templates.delete(id);
4645
+ },
4646
+ clear() {
4647
+ templates.clear();
4648
+ itemOrder.length = 0;
4649
+ counter = 0;
4650
+ primitiveFieldKey.set(undefined);
4651
+ },
4652
+ };
4653
+ }
4654
+ /**
4655
+ * Form-scoped registry of per-array durable state. One slot per array path
4656
+ * (e.g. 'members', 'team.members', 'orders.lineItems'). Survives
4657
+ * `ArrayFieldComponent` lifecycle, dies with the `DynamicForm`. Cleared on
4658
+ * schema swap by the `formSetup` effect in `FormStateManager`.
4659
+ *
4660
+ * `ArrayFieldComponent` injects this service and obtains its slot via
4661
+ * `slotFor(arrayPath)` on mount. Replaces the prior per-component
4662
+ * `ARRAY_TEMPLATE_REGISTRY` + `ARRAY_ITEM_ID_GENERATOR` providers.
4663
+ */
4664
+ class ArrayItemRegistryService {
4665
+ slots = new Map();
4666
+ /** Get-or-create a slot for the array at the given path. */
4667
+ slotFor(arrayPath) {
4668
+ let slot = this.slots.get(arrayPath);
4669
+ if (!slot) {
4670
+ slot = createSlot();
4671
+ this.slots.set(arrayPath, slot);
4672
+ }
4673
+ return slot;
4674
+ }
4675
+ /** Drop all slots — called from the `formSetup` effect on schema change. */
4676
+ clearAll() {
4677
+ for (const slot of this.slots.values())
4678
+ slot.clear();
4679
+ this.slots.clear();
4680
+ }
4681
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ArrayItemRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
4682
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ArrayItemRegistryService });
4683
+ }
4684
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ArrayItemRegistryService, decorators: [{
4685
+ type: Injectable
4686
+ }] });
4687
+
4617
4688
  class SchemaRegistryService {
4618
4689
  registeredSchemas = new Map();
4619
4690
  /**
@@ -4860,8 +4931,6 @@ class HttpConditionFunctionCacheService {
4860
4931
  * );
4861
4932
  * // user.value() keeps the old user data during loading transitions
4862
4933
  * ```
4863
- *
4864
- * @experimental Uses Angular's experimental resource composition APIs.
4865
4934
  */
4866
4935
  function withPreviousValue(input) {
4867
4936
  const derived = linkedSignal({ ...(ngDevMode ? { debugName: "derived" } : /* istanbul ignore next */ {}), source: input.snapshot,
@@ -8794,6 +8863,40 @@ function createRenderReadySignal(inputs, definition) {
8794
8863
  return requiredInputs.every((inputName) => currentInputs[inputName] !== undefined);
8795
8864
  });
8796
8865
  }
8866
+ /**
8867
+ * Reads the current hidden state from mapper inputs. Uniform across all field
8868
+ * types — value-bearing leaves source from the `FieldTree`'s `state.hidden()`
8869
+ * (where Angular Signal Forms cascades `hidden(path, …)`), and non-field outputs
8870
+ * (button/text/containers) source from the mapper's explicit `inputs.hidden`.
8871
+ *
8872
+ * The `@if (!field.hidden())` gate at every `*dfFieldOutlet` consumption site uses
8873
+ * this signal, removing hidden fields from the DOM. That's required to silence
8874
+ * Angular's `NG01916: Field 'X' is hidden but is being rendered` warning — and
8875
+ * is the prescribed pattern from the
8876
+ * [`hidden` API docs](https://angular.dev/api/forms/signals/hidden).
8877
+ *
8878
+ * Container internal state (e.g. an `ArrayFieldComponent`'s items + dynamic
8879
+ * templates + id counter) survives this destroy/recreate via the form-scoped
8880
+ * `ArrayItemRegistryService` keyed by array path.
8881
+ */
8882
+ function createHiddenSignal(inputs) {
8883
+ return computed(() => {
8884
+ const currentInputs = inputs();
8885
+ const fieldCandidate = currentInputs['field'];
8886
+ // Value-bearing leaves expose `field` as a callable `FieldTree` whose invocation returns a
8887
+ // `FieldState` with a `hidden` accessor. `isSignal()` doesn't recognise the Proxy-wrapped
8888
+ // FieldTree (no SIGNAL symbol), so we duck-type the shape and call `state.hidden()` directly.
8889
+ if (typeof fieldCandidate === 'function') {
8890
+ const state = fieldCandidate();
8891
+ if (state && typeof state.hidden === 'function') {
8892
+ return state.hidden() === true;
8893
+ }
8894
+ }
8895
+ // Non-value fields (button, text, containers) carry an explicit `hidden` input set by
8896
+ // `applyHiddenLogic` based on the mapper-side evaluation.
8897
+ return currentInputs['hidden'] === true;
8898
+ });
8899
+ }
8797
8900
  /**
8798
8901
  * Resolves a single field definition to a ResolvedField using RxJS.
8799
8902
  * Loads the component asynchronously and maps inputs in the injection context.
@@ -8830,6 +8933,7 @@ function resolveField(fieldDef, context) {
8830
8933
  injector: context.injector,
8831
8934
  inputs,
8832
8935
  renderReady: createRenderReadySignal(inputs, definition),
8936
+ hidden: createHiddenSignal(inputs),
8833
8937
  };
8834
8938
  }), catchError((error) => {
8835
8939
  // Only call onError if component is not destroyed to avoid accessing cleaned-up state
@@ -8866,6 +8970,7 @@ function resolveFieldSync(fieldDef, context) {
8866
8970
  injector: context.injector,
8867
8971
  inputs,
8868
8972
  renderReady: createRenderReadySignal(inputs, definition),
8973
+ hidden: createHiddenSignal(inputs),
8869
8974
  };
8870
8975
  }
8871
8976
  /**
@@ -8892,13 +8997,14 @@ function reconcileFields(prev, curr) {
8892
8997
  }
8893
8998
  // Same component instance + injector, but the field definition changed.
8894
8999
  // Preserve component identity (so ngComponentOutlet keeps the same instance)
8895
- // while swapping in the freshly-mapped inputs/renderReady signals so prop
8896
- // updates (e.g. select options) reach the component.
9000
+ // while swapping in the freshly-mapped inputs/renderReady/hidden signals
9001
+ // so prop updates (e.g. select options) reach the component.
8897
9002
  return {
8898
9003
  ...existing,
8899
9004
  fieldDef: field.fieldDef,
8900
9005
  inputs: field.inputs,
8901
9006
  renderReady: field.renderReady,
9007
+ hidden: field.hidden,
8902
9008
  };
8903
9009
  }
8904
9010
  // New field, type changed, or context changed (new injector) - use new field
@@ -10083,6 +10189,12 @@ class FormStateManager {
10083
10189
  eventBus = inject(EventBus);
10084
10190
  functionRegistry = inject(FunctionRegistryService);
10085
10191
  schemaRegistry = inject(SchemaRegistryService);
10192
+ // Optional so unit-test setups that drive FormStateManager directly without provideDynamicFormDI()
10193
+ // (e.g. mocked RootFormRegistryService specs) don't have to manually wire the registry. Real
10194
+ // DynamicForm usage always provides it via coreProviders; if you hit the dev warning below in
10195
+ // your own setup, you're using DynamicForm without provideDynamicFormDI() — fix the providers
10196
+ // chain rather than ignoring it.
10197
+ arrayItemRegistry = inject(ArrayItemRegistryService, { optional: true });
10086
10198
  /** Host component dependencies (config, formOptions, value). */
10087
10199
  deps = (() => {
10088
10200
  const raw = inject(FORM_STATE_DEPS);
@@ -10709,12 +10821,24 @@ class FormStateManager {
10709
10821
  }
10710
10822
  });
10711
10823
  // Schema change resets the saved store + transition tracker so values from a stale schema
10712
- // can't leak into the new one.
10824
+ // can't leak into the new one. Also drops every per-array slot in the registry so a fresh
10825
+ // schema starts with an empty template/itemOrder/id-counter state.
10826
+ let warnedAboutMissingArrayRegistry = false;
10713
10827
  explicitEffect([this.formSetup], () => {
10714
10828
  this.prevFieldStateSnapshot.set({});
10715
10829
  if (Object.keys(this.excludedValueStore()).length > 0) {
10716
10830
  this.excludedValueStore.set({});
10717
10831
  }
10832
+ if (this.arrayItemRegistry) {
10833
+ this.arrayItemRegistry.clearAll();
10834
+ }
10835
+ else if (DEV_MODE && !warnedAboutMissingArrayRegistry) {
10836
+ warnedAboutMissingArrayRegistry = true;
10837
+ this.logger.warn('ArrayItemRegistryService was not provided to FormStateManager. ' +
10838
+ 'Array fields will lose dynamically-added item templates across hide/show cycles. ' +
10839
+ 'This usually means DynamicForm was bootstrapped without provideDynamicFormDI() — ' +
10840
+ 'check your providers chain.');
10841
+ }
10718
10842
  });
10719
10843
  // Outward sync uses boundFormValue (honors only explicit excludeValueIf* opt-ins) so
10720
10844
  // global defaults that target submission output don't reshape the host's bound model.
@@ -14452,6 +14576,7 @@ function coreProviders() {
14452
14576
  deps: [FormStateManager],
14453
14577
  },
14454
14578
  FieldContextRegistryService,
14579
+ ArrayItemRegistryService,
14455
14580
  {
14456
14581
  provide: DEFAULT_PROPS,
14457
14582
  useFactory: (stateManager) => computed(() => stateManager.activeConfig()?.defaultProps),
@@ -14892,7 +15017,13 @@ class DynamicForm {
14892
15017
  }
14893
15018
  @case ('non-paged') {
14894
15019
  @for (field of resolvedFields(); track field.key) {
14895
- <ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
15020
+ <!-- @if + DfFieldOutlet's own renderReady-&-!hidden gate together silence NG01916:
15021
+ the template @if removes the host from the DOM (Angular's prescribed pattern), and
15022
+ the directive's gate stops a same-CD-pass mount before [formField] can warn.
15023
+ Don't dedupe one without the other — see DfFieldOutlet.renderReady. -->
15024
+ @if (!field.hidden()) {
15025
+ <ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
15026
+ }
14896
15027
  }
14897
15028
  }
14898
15029
  @default {
@@ -14912,7 +15043,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
14912
15043
  }
14913
15044
  @case ('non-paged') {
14914
15045
  @for (field of resolvedFields(); track field.key) {
14915
- <ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
15046
+ <!-- @if + DfFieldOutlet's own renderReady-&-!hidden gate together silence NG01916:
15047
+ the template @if removes the host from the DOM (Angular's prescribed pattern), and
15048
+ the directive's gate stops a same-CD-pass mount before [formField] can warn.
15049
+ Don't dedupe one without the other — see DfFieldOutlet.renderReady. -->
15050
+ @if (!field.hidden()) {
15051
+ <ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
15052
+ }
14916
15053
  }
14917
15054
  }
14918
15055
  @default {
@@ -15495,8 +15632,14 @@ function containerFieldMapper(fieldDef) {
15495
15632
  * ```
15496
15633
  */
15497
15634
  /**
15498
- * Base field type definitions for fields that don't use the `field` input.
15499
- * Used by display-only fields like `text` that render immediately without
15635
+ * Base for container field types that consume the mapper-emitted `field` input
15636
+ * (row, group, array, page, container).
15637
+ */
15638
+ const CONTAINER_FIELD_TYPES_BASE = {
15639
+ renderReadyWhen: ['field'],
15640
+ };
15641
+ /**
15642
+ * Base for display-only fields (`text`) that render immediately without
15500
15643
  * waiting for form value integration.
15501
15644
  */
15502
15645
  const DISPLAY_FIELD_TYPES_BASE = {
@@ -15515,31 +15658,35 @@ const BUILT_IN_FIELDS = [
15515
15658
  // `row` is a virtual field type: it maps to ContainerFieldComponent, and
15516
15659
  // the rowFieldMapper injects a `{ type: 'row' }` wrapper so the container
15517
15660
  // renders the flex/grid layout via RowWrapperComponent.
15518
- loadComponent: () => import('./ng-forge-dynamic-forms-container-field.component-Pcxg5PQu.mjs'),
15661
+ loadComponent: () => import('./ng-forge-dynamic-forms-container-field.component-VHaBk_1l.mjs'),
15519
15662
  mapper: rowFieldMapper,
15520
15663
  valueHandling: 'flatten',
15664
+ ...CONTAINER_FIELD_TYPES_BASE,
15521
15665
  },
15522
15666
  {
15523
15667
  name: 'group',
15524
- loadComponent: () => import('./ng-forge-dynamic-forms-group-field.component-fzKwPrQr.mjs'),
15668
+ loadComponent: () => import('./ng-forge-dynamic-forms-group-field.component-BvTQwoLg.mjs'),
15525
15669
  mapper: groupFieldMapper,
15526
15670
  valueHandling: 'include',
15671
+ ...CONTAINER_FIELD_TYPES_BASE,
15527
15672
  },
15528
15673
  {
15529
15674
  name: 'array',
15530
- loadComponent: () => import('./ng-forge-dynamic-forms-array-field.component-DJ-LiLm_.mjs'),
15675
+ loadComponent: () => import('./ng-forge-dynamic-forms-array-field.component-BgCrTME1.mjs'),
15531
15676
  mapper: arrayFieldMapper,
15532
15677
  valueHandling: 'include',
15678
+ ...CONTAINER_FIELD_TYPES_BASE,
15533
15679
  },
15534
15680
  {
15535
15681
  name: 'page',
15536
- loadComponent: () => import('./ng-forge-dynamic-forms-page-field.component-BX4R9GGa.mjs'),
15682
+ loadComponent: () => import('./ng-forge-dynamic-forms-page-field.component-DDzmhJNn.mjs'),
15537
15683
  mapper: pageFieldMapper,
15538
15684
  valueHandling: 'flatten',
15685
+ ...CONTAINER_FIELD_TYPES_BASE,
15539
15686
  },
15540
15687
  {
15541
15688
  name: 'text',
15542
- loadComponent: () => import('./ng-forge-dynamic-forms-text-field.component-B9SK8Vun.mjs'),
15689
+ loadComponent: () => import('./ng-forge-dynamic-forms-text-field.component-Bo4zNJXY.mjs'),
15543
15690
  mapper: textFieldMapper,
15544
15691
  valueHandling: 'exclude',
15545
15692
  ...DISPLAY_FIELD_TYPES_BASE,
@@ -15551,15 +15698,16 @@ const BUILT_IN_FIELDS = [
15551
15698
  },
15552
15699
  {
15553
15700
  name: 'container',
15554
- loadComponent: () => import('./ng-forge-dynamic-forms-container-field.component-Pcxg5PQu.mjs'),
15701
+ loadComponent: () => import('./ng-forge-dynamic-forms-container-field.component-VHaBk_1l.mjs'),
15555
15702
  mapper: containerFieldMapper,
15556
15703
  valueHandling: 'flatten',
15704
+ ...CONTAINER_FIELD_TYPES_BASE,
15557
15705
  },
15558
15706
  ];
15559
15707
  const BUILT_IN_WRAPPERS = [
15560
15708
  {
15561
15709
  wrapperName: 'css',
15562
- loadComponent: () => import('./ng-forge-dynamic-forms-css-wrapper.component-CJACCIEK.mjs'),
15710
+ loadComponent: () => import('./ng-forge-dynamic-forms-css-wrapper.component-BOdw40ZF.mjs'),
15563
15711
  },
15564
15712
  {
15565
15713
  wrapperName: 'row',
@@ -16633,5 +16781,5 @@ function extractErrorParams(error) {
16633
16781
  * Generated bundle index. Do not edit.
16634
16782
  */
16635
16783
 
16636
- export { ShiftArrayItemEvent as $, ARRAY_CONTEXT as A, BUILT_IN_FIELDS as B, CONTAINER_FIELD_PROCESSORS as C, DynamicFormLogger as D, EventBus as E, FIELD_SIGNAL_CONTEXT as F, GROUP_CONTEXT as G, DEFAULT_VALIDATION_MESSAGES as H, DynamicForm as I, DynamicFormError as J, EventDispatcher as K, FIELD_REGISTRY as L, FORM_OPTIONS as M, FormClearEvent as N, FormResetEvent as O, INITIALIZATION_TIMEOUT_MS as P, InsertArrayItemEvent as Q, RootFormRegistryService as R, MoveArrayItemEvent as S, NextPageEvent as T, NoopLogger as U, PageChangeEvent as V, WRAPPER_AUTO_ASSOCIATIONS as W, PopArrayItemEvent as X, PrependArrayItemEvent as Y, PreviousPageEvent as Z, RemoveAtIndexEvent as _, DEFAULT_WRAPPERS as a, SubmitEvent as a0, WRAPPER_REGISTRY as a1, applyMetaToElement as a2, applyValidator as a3, applyValidators as a4, arrayEvent as a5, arrayFieldMapper as a6, baseFieldMapper as a7, buildBaseInputs as a8, containerFieldMapper as a9, provideDynamicForm as aA, resolveNextButtonDisabled as aB, resolveNonFieldDisabled as aC, resolveNonFieldHidden as aD, resolveSubmitButtonDisabled as aE, resolveTokens as aF, rowFieldMapper as aG, textFieldMapper as aH, toReadonlyFieldTree as aI, withEventFormValue as aJ, withLoggerConfig as aK, withPreviousValue as aL, withValidationExecutionDefaults as aM, withValueExclusionDefaults as aN, wrapperProps as aO, createField as aa, createWrappers as ab, evaluateNonFieldDisabled as ac, evaluateNonFieldHidden as ad, field as ae, formConfig as af, getArrayLength as ag, groupFieldMapper as ah, hasFormValue as ai, interpolateParams as aj, isArrayField as ak, isCheckedField as al, isContainerField as am, isContainerTypedField as an, isDisplayOnlyField as ao, isFormStateCondition as ap, isLeafField as aq, isPageField as ar, isRowField as as, isSimplifiedArrayField as at, isValueBearingField as au, isValueField as av, isWrapperTypeDefinition as aw, isWrappersBundle as ax, omit as ay, pageFieldMapper as az, isSameWrapperChain as b, createFieldResolutionPipe as c, derivedFromDeferred as d, createWrapperChainController as e, DfFieldOutlet as f, isEqual as g, createSchemaFromFields as h, injectFieldRegistry as i, createRenderReadySignal as j, ARRAY_TEMPLATE_REGISTRY as k, ARRAY_ITEM_ID_GENERATOR as l, mapFieldToInputs as m, getNormalizedArrayMetadata as n, getFieldValueHandling as o, getFieldDefaultValue as p, isGroupField as q, resolveWrappers as r, createArrayItemIdGenerator as s, ComponentInitializedEvent as t, DynamicTextPipe as u, validatePageNesting as v, dynamicTextToObservable as w, AppendArrayItemEvent as x, ConsoleLogger as y, DEFAULT_PROPS as z };
16637
- //# sourceMappingURL=ng-forge-dynamic-forms-ng-forge-dynamic-forms-o3rJX4vG.mjs.map
16784
+ export { SubmitEvent as $, ARRAY_CONTEXT as A, BUILT_IN_FIELDS as B, CONTAINER_FIELD_PROCESSORS as C, DynamicFormLogger as D, EventBus as E, FIELD_SIGNAL_CONTEXT as F, GROUP_CONTEXT as G, DynamicForm as H, DynamicFormError as I, EventDispatcher as J, FIELD_REGISTRY as K, FORM_OPTIONS as L, FormClearEvent as M, FormResetEvent as N, INITIALIZATION_TIMEOUT_MS as O, InsertArrayItemEvent as P, MoveArrayItemEvent as Q, RootFormRegistryService as R, NextPageEvent as S, NoopLogger as T, PageChangeEvent as U, PopArrayItemEvent as V, WRAPPER_AUTO_ASSOCIATIONS as W, PrependArrayItemEvent as X, PreviousPageEvent as Y, RemoveAtIndexEvent as Z, ShiftArrayItemEvent as _, DEFAULT_WRAPPERS as a, WRAPPER_REGISTRY as a0, applyMetaToElement as a1, applyValidator as a2, applyValidators as a3, arrayEvent as a4, arrayFieldMapper as a5, baseFieldMapper as a6, buildBaseInputs as a7, containerFieldMapper as a8, createField as a9, resolveNextButtonDisabled as aA, resolveNonFieldDisabled as aB, resolveNonFieldHidden as aC, resolveSubmitButtonDisabled as aD, resolveTokens as aE, rowFieldMapper as aF, textFieldMapper as aG, toReadonlyFieldTree as aH, withEventFormValue as aI, withLoggerConfig as aJ, withPreviousValue as aK, withValidationExecutionDefaults as aL, withValueExclusionDefaults as aM, wrapperProps as aN, createWrappers as aa, evaluateNonFieldDisabled as ab, evaluateNonFieldHidden as ac, field as ad, formConfig as ae, getArrayLength as af, groupFieldMapper as ag, hasFormValue as ah, interpolateParams as ai, isArrayField as aj, isCheckedField as ak, isContainerField as al, isContainerTypedField as am, isDisplayOnlyField as an, isFormStateCondition as ao, isLeafField as ap, isPageField as aq, isRowField as ar, isSimplifiedArrayField as as, isValueBearingField as at, isValueField as au, isWrapperTypeDefinition as av, isWrappersBundle as aw, omit as ax, pageFieldMapper as ay, provideDynamicForm as az, isSameWrapperChain as b, createFieldResolutionPipe as c, derivedFromDeferred as d, createWrapperChainController as e, DfFieldOutlet as f, isEqual as g, createSchemaFromFields as h, injectFieldRegistry as i, createHiddenSignal as j, createRenderReadySignal as k, ArrayItemRegistryService as l, mapFieldToInputs as m, getNormalizedArrayMetadata as n, getFieldValueHandling as o, getFieldDefaultValue as p, isGroupField as q, resolveWrappers as r, ComponentInitializedEvent as s, DynamicTextPipe as t, dynamicTextToObservable as u, validatePageNesting as v, AppendArrayItemEvent as w, ConsoleLogger as x, DEFAULT_PROPS as y, DEFAULT_VALIDATION_MESSAGES as z };
16785
+ //# sourceMappingURL=ng-forge-dynamic-forms-ng-forge-dynamic-forms-BAPGCjUs.mjs.map