@ng-forge/dynamic-forms 0.7.0 → 0.8.0-next.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (18) hide show
  1. package/README.md +2 -0
  2. package/fesm2022/ng-forge-dynamic-forms-css-wrapper.component-Civn5kLJ.mjs +50 -0
  3. package/fesm2022/ng-forge-dynamic-forms-css-wrapper.component-Civn5kLJ.mjs.map +1 -0
  4. package/fesm2022/ng-forge-dynamic-forms-integration.mjs.map +1 -1
  5. package/fesm2022/{ng-forge-dynamic-forms-ng-forge-dynamic-forms-Sad9wa1d.mjs → ng-forge-dynamic-forms-ng-forge-dynamic-forms-D276M2vh.mjs} +1430 -389
  6. package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-D276M2vh.mjs.map +1 -0
  7. package/fesm2022/{ng-forge-dynamic-forms-page-field.component-Cen4ArED.mjs → ng-forge-dynamic-forms-page-field.component-CEN5u1dd.mjs} +9 -14
  8. package/fesm2022/ng-forge-dynamic-forms-page-field.component-CEN5u1dd.mjs.map +1 -0
  9. package/fesm2022/ng-forge-dynamic-forms-row-wrapper.component-BIvEUoia.mjs +34 -0
  10. package/fesm2022/ng-forge-dynamic-forms-row-wrapper.component-BIvEUoia.mjs.map +1 -0
  11. package/fesm2022/{ng-forge-dynamic-forms-text-field.component-cE1EUmYR.mjs → ng-forge-dynamic-forms-text-field.component-9OIByjZZ.mjs} +5 -5
  12. package/fesm2022/{ng-forge-dynamic-forms-text-field.component-cE1EUmYR.mjs.map → ng-forge-dynamic-forms-text-field.component-9OIByjZZ.mjs.map} +1 -1
  13. package/fesm2022/ng-forge-dynamic-forms.mjs +1 -1
  14. package/package.json +2 -2
  15. package/types/ng-forge-dynamic-forms-integration.d.ts +25 -23
  16. package/types/ng-forge-dynamic-forms.d.ts +3360 -2781
  17. package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-Sad9wa1d.mjs.map +0 -1
  18. package/fesm2022/ng-forge-dynamic-forms-page-field.component-Cen4ArED.mjs.map +0 -1
@@ -1,128 +1,58 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, inject, Injectable, isSignal, untracked, isWritableSignal, isDevMode, Injector, DestroyRef, computed, input, linkedSignal, ChangeDetectionStrategy, Component, resourceFromSnapshots, signal, runInInjectionContext, afterNextRender, model, EnvironmentInjector, makeEnvironmentProviders, Pipe } from '@angular/core';
3
- import { NgComponentOutlet } from '@angular/common';
4
- import { toObservable, toSignal, takeUntilDestroyed, rxResource, outputFromObservable } from '@angular/core/rxjs-interop';
5
- 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';
6
- import { isObservable, firstValueFrom, exhaustMap, EMPTY, from, catchError, Subject, filter, TimeoutError, of, throwError, debounceTime, distinctUntilChanged, tap, take as take$1, startWith, pairwise, map as map$1, switchMap as switchMap$1, Observable, takeUntil, combineLatestWith, auditTime, scheduled, queueScheduler, merge, timer, defer, combineLatest, pipe, forkJoin, scan as scan$1, concatMap } from 'rxjs';
7
- import { scan, map, filter as filter$1, take, switchMap, timeout, catchError as catchError$1, shareReplay } from 'rxjs/operators';
2
+ import { InjectionToken, reflectComponentType, Injector, inject, EnvironmentInjector, DestroyRef, computed, input, ViewContainerRef, signal, Directive, Injectable, isSignal, untracked, isWritableSignal, isDevMode, linkedSignal, ChangeDetectionStrategy, Component, resourceFromSnapshots, runInInjectionContext, afterNextRender, model, makeEnvironmentProviders, Pipe, viewChild, TemplateRef } from '@angular/core';
8
3
  import { explicitEffect } from 'ngxtension/explicit-effect';
4
+ import { of, forkJoin, from, catchError, map, switchMap, isObservable, firstValueFrom, exhaustMap, EMPTY, Subject, filter, TimeoutError, throwError, debounceTime, distinctUntilChanged, tap, take as take$1, startWith, pairwise, Observable, takeUntil, combineLatestWith, auditTime, scheduled, queueScheduler, merge, timer, defer, combineLatest, pipe, scan as scan$1, concatMap } from 'rxjs';
5
+ import { takeUntilDestroyed, toObservable, toSignal, rxResource, outputFromObservable } from '@angular/core/rxjs-interop';
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
+ import { scan, map as map$1, filter as filter$1, take, switchMap as switchMap$1, timeout, catchError as catchError$1, shareReplay } from 'rxjs/operators';
9
8
  import { HttpClient } from '@angular/common/http';
10
9
  import { isStandardSchemaMarker } from '@ng-forge/dynamic-forms/schema';
11
10
  import { NG_STATUS_CLASSES } from '@angular/forms/signals/compat';
12
11
 
13
12
  /**
14
- * Wraps a submission action to handle both Promise and Observable returns.
15
- * Converts Observables to Promises for compatibility with Angular Signal Forms' submit().
16
- *
17
- * Errors are NOT caught here — they propagate so that submit() can reject its Promise,
18
- * allowing the caller's catchError to log and keep the submission stream alive.
13
+ * Type guard for WrapperTypeDefinition.
19
14
  *
20
- * @param action - The submission action function
21
- * @returns A wrapped function that returns a Promise
15
+ * Discriminates via `wrapperName` — field types use `name`, wrapper types use `wrapperName`.
22
16
  */
23
- function wrapSubmissionAction(action) {
24
- return async (formTree) => {
25
- const result = action(formTree);
26
- // If the action returns an Observable, convert it to a Promise
27
- if (isObservable(result)) {
28
- await firstValueFrom(result);
29
- return;
30
- }
31
- await Promise.resolve(result);
32
- };
17
+ function isWrapperTypeDefinition(value) {
18
+ return typeof value === 'object' && value !== null && 'wrapperName' in value;
33
19
  }
34
20
  /**
35
- * Creates an Observable that handles form submission with optional submission action.
36
- *
37
- * This utility encapsulates the submission handling logic:
38
- * - Listens for submit events from the event bus
39
- * - If a submission.action is configured, wraps it and uses Angular Signal Forms' submit()
40
- * - Handles both Promise and Observable returns from the action
41
- * - Uses exhaustMap to ignore new submissions while one is in-flight (first-submit-wins)
42
- *
43
- * The returned Observable should be subscribed to with takeUntilDestroyed() in the component.
21
+ * Injection token for the wrapper type registry.
44
22
  *
45
- * @param options - Configuration options for the submission handler
46
- * @returns Observable that processes submissions (emits when submission completes)
47
- *
48
- * @example
49
- * ```typescript
50
- * // In component constructor
51
- * createSubmissionHandler({
52
- * eventBus: this.eventBus,
53
- * configSignal: this.config,
54
- * formSignal: this.form,
55
- * })
56
- * .pipe(takeUntilDestroyed())
57
- * .subscribe();
58
- * ```
23
+ * Provides access to the map of registered wrapper types. The registry is
24
+ * populated via `provideDynamicForm()` and used by `ContainerFieldComponent` to
25
+ * resolve wrapper types to their component implementations.
59
26
  */
60
- function createSubmissionHandler(options) {
61
- const { eventBus, configSignal, formSignal, validSignal, logger } = options;
62
- // exhaustMap ensures first-submit-wins: a second submit event while the first
63
- // is in-flight is silently dropped rather than cancelling the running Promise.
64
- // switchMap would unsubscribe the Observable wrapper but cannot cancel the
65
- // underlying Promise, causing both side effects to execute.
66
- return eventBus.on('submit').pipe(exhaustMap(() => {
67
- const submissionConfig = configSignal().submission;
68
- // If no submission action is configured, let the submitted output handle it
69
- // This maintains backward compatibility for users handling submission manually
70
- if (!submissionConfig?.action) {
71
- return EMPTY;
72
- }
73
- // Guard: match the (submitted) output's safety contract — reject submission
74
- // when the form is invalid or has pending async validators.
75
- if (!validSignal()) {
76
- logger.debug('Submission action skipped: form is not valid (invalid or pending async validators)');
77
- return EMPTY;
78
- }
79
- // Type assertion needed: submission.action accepts the form tree but its signature
80
- // is defined broadly in FormConfig. The actual runtime type is FieldTree<TModel>.
81
- const wrappedAction = wrapSubmissionAction(submissionConfig.action);
82
- // Use Angular Signal Forms' native submit() function
83
- // This automatically:
84
- // - Sets form.submitting() to true during execution
85
- // - Applies server errors to form fields on completion
86
- // - Sets form.submitting() to false when done
87
- // catchError keeps the exhaustMap stream alive after action failure —
88
- // without it, an unhandled error would terminate all future submissions.
89
- return from(submit(formSignal(), wrappedAction)).pipe(catchError((error) => {
90
- logger.error('Submission action failed:', error);
91
- return EMPTY;
92
- }));
93
- }));
94
- }
95
-
27
+ const WRAPPER_REGISTRY = new InjectionToken('WRAPPER_REGISTRY', {
28
+ providedIn: 'root',
29
+ factory: () => new Map(),
30
+ });
96
31
  /**
97
- * Injection token for globally enabling form value emission on events.
32
+ * Component cache for loaded wrapper components.
98
33
  *
99
- * When set to `true`, all events dispatched through the EventBus will include
100
- * the current form value in the `formValue` property.
101
- *
102
- * This token is configured via `withEventFormValue()` feature function.
103
- * Per-form overrides can be set via `options.emitFormValueOnEvents` in the form config.
34
+ * Caches resolved wrapper component classes for instant re-resolution.
35
+ * SSR-safe because it's DI-scoped, not module-scoped.
104
36
  *
105
- * @internal
37
+ * NOTE: The cache grows across the application lifetime. It's bounded by the
38
+ * number of wrapper types registered (typically small), so this is not a leak;
39
+ * the COMPONENT_CACHE for field types has the same shape.
106
40
  */
107
- const EMIT_FORM_VALUE_ON_EVENTS = new InjectionToken('EMIT_FORM_VALUE_ON_EVENTS', {
41
+ const WRAPPER_COMPONENT_CACHE = new InjectionToken('WRAPPER_COMPONENT_CACHE', {
108
42
  providedIn: 'root',
109
- factory: () => false,
43
+ factory: () => new Map(),
110
44
  });
111
-
112
45
  /**
113
- * Registry service that provides access to the root form and its values.
114
- *
115
- * Constructed via factory in `provideDynamicFormDI` with signals from
116
- * `FormStateManager` — no DI injection, no Maps, no lifecycle.
46
+ * Pre-computed reverse index: `fieldType → WrapperConfig[]` for every
47
+ * registered `WrapperTypeDefinition.types` entry. Built once in the
48
+ * `provideDynamicForm(...)` factory so `resolveWrappers` can look up
49
+ * auto-associations in O(1) per field instead of scanning the full
50
+ * wrapper registry on every render.
117
51
  */
118
- class RootFormRegistryService {
119
- formValue;
120
- rootForm;
121
- constructor(formValue, rootForm) {
122
- this.formValue = formValue;
123
- this.rootForm = rootForm;
124
- }
125
- }
52
+ const WRAPPER_AUTO_ASSOCIATIONS = new InjectionToken('WRAPPER_AUTO_ASSOCIATIONS', {
53
+ providedIn: 'root',
54
+ factory: () => new Map(),
55
+ });
126
56
 
127
57
  /**
128
58
  * Base error class for all Dynamic Forms errors.
@@ -139,6 +69,18 @@ class DynamicFormError extends Error {
139
69
  }
140
70
  }
141
71
 
72
+ /**
73
+ * Injection token for form-level default wrappers.
74
+ *
75
+ * Provides a Signal of the `defaultWrappers` array from FormConfig. Consumed by
76
+ * `DfFieldOutlet` / `ContainerFieldComponent` to merge into each field's
77
+ * effective wrapper chain (lowest priority after auto-associations, higher
78
+ * than field-level `wrappers`).
79
+ *
80
+ * Provided once at the DynamicForm level and inherited via Angular's
81
+ * hierarchical injector.
82
+ */
83
+ const DEFAULT_WRAPPERS = new InjectionToken('DEFAULT_WRAPPERS');
142
84
  /**
143
85
  * Injection token for providing field signal context to mappers and components.
144
86
  *
@@ -345,6 +287,201 @@ function createArrayItemIdGenerator() {
345
287
  return () => `item-${counter++}`;
346
288
  }
347
289
 
290
+ /**
291
+ * Module-level cache keyed by component class. `reflectComponentType` returns
292
+ * immutable metadata per class, so we probe it once and reuse the resulting
293
+ * `Set<templateName>` for every `setInputIfDeclared` call afterwards.
294
+ *
295
+ * We cache `templateName` (the public input name, aka alias) rather than
296
+ * `propName` (the class field name) because `ComponentRef.setInput()` keys by
297
+ * the public name — that's also the name wrapper configs use. An aliased
298
+ * input declared as `input(..., { alias: 'header' })` with class field
299
+ * `headerText` must be addressed as `'header'`; caching `propName` would
300
+ * leave the alias out of the declared set and silently drop config values.
301
+ *
302
+ * SSR-safe: the WeakMap is keyed by the component class object — classes are
303
+ * created per Angular application bootstrap and GC'd with it, so this does not
304
+ * leak state between server renders.
305
+ */
306
+ const inputNamesCache = new WeakMap();
307
+ function getDeclaredInputs(componentType) {
308
+ let inputs = inputNamesCache.get(componentType);
309
+ if (!inputs) {
310
+ const meta = reflectComponentType(componentType);
311
+ inputs = new Set(meta?.inputs.map((i) => i.templateName) ?? []);
312
+ inputNamesCache.set(componentType, inputs);
313
+ }
314
+ return inputs;
315
+ }
316
+ /**
317
+ * Set an input on a ComponentRef only when the target component actually declares it.
318
+ *
319
+ * Angular's `ComponentRef.setInput()` throws NG0303 when the input is missing.
320
+ * For config keys driven by user data (e.g. a wrapper config containing a prop
321
+ * the wrapper doesn't care about) that would surface as a noisy runtime error.
322
+ * We probe the component's metadata via `reflectComponentType` (public API),
323
+ * cached per component class — called once per input key per emission, so
324
+ * avoiding the reflection scan on each call is meaningful under heavy typing.
325
+ */
326
+ function setInputIfDeclared(ref, inputName, value) {
327
+ const declared = getDeclaredInputs(ref.componentType);
328
+ if (declared.has(inputName)) {
329
+ ref.setInput(inputName, value);
330
+ }
331
+ }
332
+ /**
333
+ * Narrows an ES-module-with-default-export from a bare component class.
334
+ * Lazy component loaders may return either shape depending on how the user
335
+ * wrote the import expression (`import('./x')` vs `import('./x').then(m => m.Foo)`).
336
+ */
337
+ function hasDefaultExport(value) {
338
+ return typeof value === 'object' && value !== null && 'default' in value && !!value.default;
339
+ }
340
+ /**
341
+ * Pick the component class out of whatever a lazy loader returned.
342
+ */
343
+ function resolveDefaultExport(result) {
344
+ return hasDefaultExport(result) ? result.default : result;
345
+ }
346
+ /**
347
+ * Resolve a wrapper type name to its component class, with DI-scoped caching.
348
+ */
349
+ async function loadWrapperComponent(type, registry, cache) {
350
+ const cached = cache.get(type);
351
+ if (cached)
352
+ return cached;
353
+ const definition = registry.get(type);
354
+ if (!definition)
355
+ return undefined;
356
+ const component = resolveDefaultExport(await definition.loadComponent());
357
+ if (component)
358
+ cache.set(type, component);
359
+ return component;
360
+ }
361
+ /**
362
+ * Load every wrapper for a chain, all-or-nothing. A single failed load
363
+ * aborts the whole chain (emits `[]`) so the field renders bare rather
364
+ * than in a partially-wrapped, visually-misleading state. Each failure
365
+ * is logged; field-type resolution still throws — this isn't silent.
366
+ */
367
+ function loadWrapperComponents(configs, registry, cache, logger) {
368
+ if (configs.length === 0)
369
+ return of([]);
370
+ return forkJoin(configs.map((config) => from(loadWrapperComponent(config.type, registry, cache)).pipe(catchError(() => of(undefined)), map((component) => {
371
+ if (!component) {
372
+ logger.error(`Wrapper type '${config.type}' could not be loaded. Ensure it is registered via provideDynamicForm().`);
373
+ return null;
374
+ }
375
+ return { config, component };
376
+ })))).pipe(map((results) => (results.some((r) => r === null) ? [] : results)));
377
+ }
378
+ /**
379
+ * Recursively create each wrapper component, threading the next one (or the
380
+ * innermost content) into its `#fieldComponent` slot.
381
+ *
382
+ * Each wrapper receives:
383
+ * - Each of its config properties (minus `type`) as an individual Angular input
384
+ * - `fieldInputs` as a single input, if provided
385
+ *
386
+ * Returns the list of wrapper `ComponentRef`s — callers retain them for later
387
+ * cleanup and for re-setting `fieldInputs` when the mapper signal emits.
388
+ */
389
+ function renderWrapperChain(options) {
390
+ const refs = [];
391
+ renderStep(options.outerContainer, options.loadedWrappers, options, refs);
392
+ return refs;
393
+ }
394
+ function renderStep(slot, remaining, options, refs) {
395
+ if (remaining.length === 0) {
396
+ options.renderInnermost(slot);
397
+ return;
398
+ }
399
+ const [wrapper, ...rest] = remaining;
400
+ // `slot.injector` is the element injector seen from inside the current slot —
401
+ // for nested slots that includes the outer wrapper's component, enabling
402
+ // `inject(OuterWrapper)`. When a field injector is provided we merge it in
403
+ // front so tokens like `ARRAY_CONTEXT` also resolve.
404
+ const wrapperInjector = options.fieldInjector ? createWrapperAwareInjector(options.fieldInjector, slot.injector) : slot.injector;
405
+ const ref = slot.createComponent(wrapper.component, {
406
+ environmentInjector: options.environmentInjector,
407
+ injector: wrapperInjector,
408
+ });
409
+ refs.push(ref);
410
+ for (const [key, value] of Object.entries(wrapper.config)) {
411
+ // `type` is the registry discriminant; `fieldInputs` is set explicitly
412
+ // below. Don't let a stray config key override either.
413
+ if (key === 'type' || key === 'fieldInputs')
414
+ continue;
415
+ setInputIfDeclared(ref, key, value);
416
+ }
417
+ if (options.fieldInputs !== undefined) {
418
+ setInputIfDeclared(ref, 'fieldInputs', options.fieldInputs);
419
+ }
420
+ // Required to resolve `viewChild.required('fieldComponent')` before we
421
+ // recurse into the slot. O(chain length) CD passes per rebuild — acceptable
422
+ // for typical chains (≤3); refactor to a template-based render if it bites.
423
+ ref.changeDetectorRef.detectChanges();
424
+ const inner = resolveInnerSlot(ref);
425
+ if (!inner) {
426
+ options.logger.error(`Wrapper component for type '${wrapper.config.type}' does not provide a 'fieldComponent' ViewContainerRef. ` +
427
+ `Ensure the wrapper component has a viewChild('fieldComponent', { read: ViewContainerRef }) query ` +
428
+ `and that #fieldComponent is not inside a conditional (@if, @defer).`);
429
+ // Unwind: destroy every wrapper built so far, including this one, so the
430
+ // caller doesn't end up with a partial chain left in the DOM.
431
+ while (refs.length)
432
+ refs.pop().destroy();
433
+ return;
434
+ }
435
+ renderStep(inner, rest, options, refs);
436
+ }
437
+ /**
438
+ * Read a wrapper's `#fieldComponent` ViewContainerRef. `viewChild.required`
439
+ * throws when the query can't resolve (missing template ref, or ref sitting
440
+ * inside `@if` / `@defer` / `@for`); any throw from this pure signal read
441
+ * means the slot is unusable, so we return undefined and let the caller log
442
+ * the actionable "wrapper missing fieldComponent" message.
443
+ */
444
+ function resolveInnerSlot(ref) {
445
+ const contract = ref.instance;
446
+ try {
447
+ return contract.fieldComponent?.();
448
+ }
449
+ catch {
450
+ return undefined;
451
+ }
452
+ }
453
+ /**
454
+ * Merged injector: check the field-level injector first (for tokens like
455
+ * `ARRAY_CONTEXT` / `FIELD_SIGNAL_CONTEXT`), fall back to the element-chain
456
+ * injector (so nested wrappers can still `inject(OuterWrapper)`).
457
+ *
458
+ * Precedence: a field-level token shadows a same-named token provided by an
459
+ * outer wrapper. That matches "more specific context wins" but is worth
460
+ * keeping in mind if a wrapper exports service tokens.
461
+ */
462
+ function createWrapperAwareInjector(fieldInjector, elementInjector) {
463
+ if (fieldInjector === elementInjector)
464
+ return fieldInjector;
465
+ return new WrapperAwareInjector(fieldInjector, elementInjector);
466
+ }
467
+ /** Sentinel — distinguishes "token not found" from a legitimate `undefined` provider value. */
468
+ const WRAPPER_NOT_FOUND = {};
469
+ class WrapperAwareInjector extends Injector {
470
+ fieldInjector;
471
+ elementInjector;
472
+ constructor(fieldInjector, elementInjector) {
473
+ super();
474
+ this.fieldInjector = fieldInjector;
475
+ this.elementInjector = elementInjector;
476
+ }
477
+ get(token, notFoundValue, options) {
478
+ const field = this.fieldInjector.get(token, WRAPPER_NOT_FOUND, options);
479
+ if (field !== WRAPPER_NOT_FOUND)
480
+ return field;
481
+ return this.elementInjector.get(token, notFoundValue, options);
482
+ }
483
+ }
484
+
348
485
  /**
349
486
  * Injection token for the dynamic forms logger.
350
487
  *
@@ -353,6 +490,567 @@ function createArrayItemIdGenerator() {
353
490
  */
354
491
  const DynamicFormLogger = new InjectionToken('DynamicFormLogger');
355
492
 
493
+ /**
494
+ * Shared empty chain — returned when no wrappers apply so callers get a
495
+ * stable reference. Freezing makes accidental mutation observable.
496
+ */
497
+ const EMPTY_WRAPPERS = Object.freeze([]);
498
+ /**
499
+ * Element-wise identity (`===`) comparator for wrapper chains. Used as the
500
+ * `equal` option on signal memoisation so reconciled `FieldDef`s with the
501
+ * same chain don't churn downstream — relies on `WrapperConfig` objects
502
+ * being stable across ticks, which holds for configs declared in
503
+ * `FormConfig` / `createWrappers(...)`.
504
+ */
505
+ function isSameWrapperChain(a, b) {
506
+ if (a === b)
507
+ return true;
508
+ if (a.length !== b.length)
509
+ return false;
510
+ for (let i = 0; i < a.length; i++) {
511
+ if (a[i] !== b[i])
512
+ return false;
513
+ }
514
+ return true;
515
+ }
516
+ /**
517
+ * Resolves the wrapper chain for a field.
518
+ *
519
+ * Merge order (outermost → innermost): auto-associations, form defaults,
520
+ * field-level wrappers. `wrappers: null` skips all three; `skipAutoWrappers` /
521
+ * `skipDefaultWrappers` on the field opt out of individual layers. Fresh
522
+ * array per call — downstream memoisation is on the consuming computed.
523
+ */
524
+ function resolveWrappers(field, defaultWrappers, autoAssociations) {
525
+ if (field.wrappers === null) {
526
+ return EMPTY_WRAPPERS;
527
+ }
528
+ const autoWrappers = field.skipAutoWrappers ? EMPTY_WRAPPERS : (autoAssociations.get(field.type) ?? EMPTY_WRAPPERS);
529
+ const defaults = field.skipDefaultWrappers ? EMPTY_WRAPPERS : (defaultWrappers ?? EMPTY_WRAPPERS);
530
+ const fieldLevel = field.wrappers ?? EMPTY_WRAPPERS;
531
+ if (autoWrappers.length === 0 && defaults.length === 0 && fieldLevel.length === 0) {
532
+ return EMPTY_WRAPPERS;
533
+ }
534
+ return [...autoWrappers, ...defaults, ...fieldLevel];
535
+ }
536
+
537
+ /**
538
+ * Owns the wrapper-chain lifecycle for a field or container: async component
539
+ * loading, switchMap-based cancellation of stale loads, flicker-tolerant
540
+ * rebuilds (only structural changes tear down), and `fieldInputs` push-through.
541
+ * Must be called from a DI injection context.
542
+ */
543
+ function createWrapperChainController(opts) {
544
+ const deps = injectChainDeps();
545
+ const state = createStateSignal(opts);
546
+ const mounted = { value: null };
547
+ let refs = [];
548
+ buildEmissionStream(state, deps)
549
+ .pipe(takeUntilDestroyed(deps.destroyRef))
550
+ .subscribe((emission) => {
551
+ // Wrap the whole render path — a throw here (bad wrapper template,
552
+ // caller's renderInnermost blew up, etc.) would otherwise terminate
553
+ // the subscription and silently freeze subsequent chain updates.
554
+ try {
555
+ refs = applyEmission(emission, { opts, deps, mounted, refs });
556
+ }
557
+ catch (err) {
558
+ deps.logger.error('Wrapper chain render failed; tearing down partial state.', err);
559
+ opts.vcr().clear();
560
+ refs = [];
561
+ mounted.value = null;
562
+ }
563
+ });
564
+ pushFieldInputsOnChange(opts, () => refs);
565
+ deps.destroyRef.onDestroy(() => opts.vcr().clear());
566
+ }
567
+ function injectChainDeps() {
568
+ return {
569
+ registry: inject(WRAPPER_REGISTRY),
570
+ cache: inject(WRAPPER_COMPONENT_CACHE),
571
+ logger: inject(DynamicFormLogger),
572
+ destroyRef: inject(DestroyRef),
573
+ environmentInjector: inject(EnvironmentInjector),
574
+ };
575
+ }
576
+ function createStateSignal(opts) {
577
+ return computed(() => ({
578
+ open: opts.gate?.() ?? true,
579
+ wrappers: opts.wrappers(),
580
+ rebuildKey: opts.rebuildKey?.(),
581
+ }), { equal: (a, b) => a.open === b.open && a.rebuildKey === b.rebuildKey && isSameWrapperChain(a.wrappers, b.wrappers) });
582
+ }
583
+ /**
584
+ * Resolve a `ChainState` into a `ChainEmission`. `switchMap` cancellation
585
+ * guarantees in-flight async loads are discarded when a newer state arrives.
586
+ */
587
+ function buildEmissionStream(state, deps) {
588
+ return toObservable(state).pipe(switchMap((s) => resolveLoadedWrappers(s, deps)));
589
+ }
590
+ function resolveLoadedWrappers(state, deps) {
591
+ if (!state.open) {
592
+ return of({ state, loaded: null });
593
+ }
594
+ if (state.wrappers.every((w) => deps.cache.has(w.type))) {
595
+ // Sync fast-path — the cache only holds SUCCESSFUL loads, so "every cached"
596
+ // means every wrapper was previously resolved cleanly. No need to re-log.
597
+ const loaded = state.wrappers.map((config) => ({ config, component: deps.cache.get(config.type) }));
598
+ return of({ state, loaded });
599
+ }
600
+ return loadWrapperComponents(state.wrappers, deps.registry, deps.cache, deps.logger).pipe(map((loaded) => ({ state, loaded })));
601
+ }
602
+ /**
603
+ * Diff what's mounted against the next state and act:
604
+ * - Gate-only flicker → no-op (preserve the mounted chain)
605
+ * - Structural change → beforeRebuild → vcr.clear → render fresh
606
+ * - Idempotent re-emission → no-op
607
+ *
608
+ * Returns the (possibly new) wrapper refs so the caller can track them for
609
+ * `fieldInputs` push-through.
610
+ */
611
+ function applyEmission({ state, loaded }, ctx) {
612
+ const { opts, deps, mounted, refs } = ctx;
613
+ const vcr = opts.vcr();
614
+ const structurallyChanged = isStructurallyDifferent(mounted.value, state);
615
+ // Gate-only flicker after first mount — keep the chain alive so the user's
616
+ // focus / caret / scroll survive. The caller's rawInputs effect continues
617
+ // to push live mapper outputs through the still-mounted innermost.
618
+ if (!structurallyChanged && !state.open)
619
+ return refs;
620
+ // Real structural change — tear down. Angular cascades destroy through
621
+ // every nested ComponentRef, so walking `refs` manually is redundant.
622
+ // `beforeRebuild` gives the caller a chance to detach views it wants to
623
+ // preserve (e.g. the innermost field when only wrappers changed).
624
+ if (structurallyChanged && mounted.value !== null) {
625
+ opts.beforeRebuild?.();
626
+ vcr.clear();
627
+ mounted.value = null;
628
+ }
629
+ if (!state.open || loaded === null)
630
+ return [];
631
+ // Same structure + already mounted — idempotent re-emission, nothing to do.
632
+ if (mounted.value !== null)
633
+ return refs;
634
+ const newRefs = renderWrapperChain({
635
+ outerContainer: vcr,
636
+ loadedWrappers: loaded,
637
+ environmentInjector: deps.environmentInjector,
638
+ logger: deps.logger,
639
+ fieldInputs: opts.fieldInputs?.(),
640
+ fieldInjector: opts.fieldInjector?.(),
641
+ renderInnermost: opts.renderInnermost,
642
+ });
643
+ mounted.value = { wrappers: state.wrappers, rebuildKey: state.rebuildKey };
644
+ return newRefs;
645
+ }
646
+ function isStructurallyDifferent(mounted, next) {
647
+ if (mounted === null)
648
+ return true;
649
+ if (mounted.rebuildKey !== next.rebuildKey)
650
+ return true;
651
+ return !isSameWrapperChain(mounted.wrappers, next.wrappers);
652
+ }
653
+ /**
654
+ * Push the latest `fieldInputs` bag to every mounted wrapper whenever it
655
+ * changes — without rebuilding the chain. No-op when the chain is empty
656
+ * (gated off, or nothing rendered yet).
657
+ */
658
+ function pushFieldInputsOnChange(opts, getRefs) {
659
+ if (!opts.fieldInputs)
660
+ return;
661
+ explicitEffect([opts.fieldInputs], ([fi]) => {
662
+ for (const ref of getRefs())
663
+ setInputIfDeclared(ref, 'fieldInputs', fi);
664
+ });
665
+ }
666
+
667
+ /**
668
+ * Get the length of an array FieldTree.
669
+ */
670
+ function getArrayLength(arrayFieldTree) {
671
+ const value = arrayFieldTree().value();
672
+ return Array.isArray(value) ? value.length : 0;
673
+ }
674
+ /**
675
+ * Build a `ReadonlyFieldTree` by extracting the whitelisted read signals from a
676
+ * Signal Forms `FieldTree`. Returns a fresh plain object — no casting, no proxying,
677
+ * so consumers only see the narrow surface and Angular's `WritableSignal` capability
678
+ * on `value` is hidden.
679
+ */
680
+ function toReadonlyFieldTree(field) {
681
+ const state = field();
682
+ return {
683
+ value: state.value,
684
+ valid: state.valid,
685
+ invalid: state.invalid,
686
+ touched: state.touched,
687
+ dirty: state.dirty,
688
+ required: state.required,
689
+ disabled: state.disabled,
690
+ hidden: state.hidden,
691
+ errors: state.errors,
692
+ };
693
+ }
694
+ /**
695
+ * DI-scoped cache for `ReadonlyFieldTree` views, keyed on the source FieldTree
696
+ * identity. A fresh WeakMap per root injector keeps renders isolated under SSR —
697
+ * Angular creates a new root injector per request, so there's no shared state
698
+ * between server renders (same pattern as `COMPONENT_CACHE`).
699
+ *
700
+ * @internal
701
+ */
702
+ const READONLY_FIELD_TREE_CACHE = new InjectionToken('READONLY_FIELD_TREE_CACHE', {
703
+ providedIn: 'root',
704
+ factory: () => new WeakMap(),
705
+ });
706
+ /**
707
+ * Cached variant of {@link toReadonlyFieldTree}. Each FieldTree instance is a
708
+ * stable reference for the lifetime of its form, so one `ReadonlyFieldTree`
709
+ * view per tree is enough. Callers on hot paths (`pushInputs` on every mapper
710
+ * emission) avoid re-allocating the nine-property view object.
711
+ *
712
+ * @internal
713
+ */
714
+ function toReadonlyFieldTreeCached(cache, field) {
715
+ const key = field;
716
+ const cached = cache.get(key);
717
+ if (cached)
718
+ return cached;
719
+ const view = toReadonlyFieldTree(field);
720
+ cache.set(key, view);
721
+ return view;
722
+ }
723
+
724
+ /**
725
+ * Structural directive that renders a `ResolvedField` with its effective
726
+ * wrapper chain.
727
+ *
728
+ * Replaces `*ngComponentOutlet` in field-rendering templates. When the field
729
+ * has no wrappers, the component is created directly at the outlet position
730
+ * (no extra DOM nesting). When wrappers apply, they chain outermost-first and
731
+ * the field renders in the innermost slot.
732
+ *
733
+ * Effective wrappers are merged from (outermost → innermost):
734
+ * 1. `WrapperTypeDefinition.types` auto-associations for the field's `type`
735
+ * 2. `FormConfig.defaultWrappers`
736
+ * 3. Field-level `FieldDef.wrappers` (`null` = explicit opt-out; `[]` does not
737
+ * opt out — auto + defaults still apply)
738
+ *
739
+ * Wrapper config keys (minus `type`) are pushed as individual Angular inputs;
740
+ * every wrapper also receives `fieldInputs` — a `WrapperFieldInputs` bag that
741
+ * includes the field's mapper outputs and a `ReadonlyFieldTree` view of its
742
+ * form state.
743
+ *
744
+ * Rendering is gated by `field.renderReady()` — the directive waits until
745
+ * the mapper produces the required inputs before instantiating the component.
746
+ * Once rendered, a transient `renderReady → false` does *not* tear the chain
747
+ * down; the controller keeps the mounted components alive and ignores the
748
+ * flicker. Only a structural change (wrappers or component class) triggers
749
+ * a rebuild.
750
+ *
751
+ * @example
752
+ * ```html
753
+ * \@for (field of resolvedFields(); track field.key) {
754
+ * <ng-container *dfFieldOutlet="field; environmentInjector: envInjector" />
755
+ * }
756
+ * ```
757
+ */
758
+ class DfFieldOutlet {
759
+ // Named to match the structural directive microsyntax directly
760
+ // (`*dfFieldOutlet="field; environmentInjector: env"`) so no aliasing is needed.
761
+ dfFieldOutlet = input.required(...(ngDevMode ? [{ debugName: "dfFieldOutlet" }] : /* istanbul ignore next */ []));
762
+ dfFieldOutletEnvironmentInjector = input(undefined, ...(ngDevMode ? [{ debugName: "dfFieldOutletEnvironmentInjector" }] : /* istanbul ignore next */ []));
763
+ vcrRef = inject(ViewContainerRef);
764
+ vcr = signal(this.vcrRef).asReadonly();
765
+ destroyRef = inject(DestroyRef);
766
+ wrapperAutoAssociations = inject(WRAPPER_AUTO_ASSOCIATIONS);
767
+ defaultWrappersSignal = inject(DEFAULT_WRAPPERS, { optional: true });
768
+ readonlyFieldCache = inject(READONLY_FIELD_TREE_CACHE);
769
+ fieldRef;
770
+ /**
771
+ * VCR the `fieldRef`'s host view is currently inserted into (either the outer
772
+ * VCR when there are no wrappers, or the innermost wrapper's `#fieldComponent`
773
+ * slot). Tracked so `beforeRebuild` can detach the view before the outer VCR
774
+ * cascade-destroys it.
775
+ */
776
+ fieldSlot;
777
+ /** Focus + caret captured during detach, replayed after re-insert. */
778
+ focusSnapshot;
779
+ /**
780
+ * Last rawInputs reference pushed to the innermost field. Used to dedupe the
781
+ * rawInputs effect when the same snapshot was already applied as part of the
782
+ * initial render — keeps per-keystroke input updates O(changed-keys) instead
783
+ * of re-walking the whole input bag.
784
+ */
785
+ lastPushedInputs;
786
+ componentIdentity = computed(() => this.dfFieldOutlet().component, ...(ngDevMode ? [{ debugName: "componentIdentity" }] : /* istanbul ignore next */ []));
787
+ renderReady = computed(() => this.dfFieldOutlet().renderReady(), ...(ngDevMode ? [{ debugName: "renderReady" }] : /* istanbul ignore next */ []));
788
+ rawInputs = computed(() => this.dfFieldOutlet().inputs(), ...(ngDevMode ? [{ debugName: "rawInputs" }] : /* istanbul ignore next */ []));
789
+ /**
790
+ * Effective wrapper chain. Element-wise identity comparison keeps the signal
791
+ * stable across `ResolvedField` reference changes that don't actually change
792
+ * the chain — avoids rebuilds on reconciled fields.
793
+ */
794
+ wrappers = computed(() => resolveWrappers(this.dfFieldOutlet().fieldDef, this.defaultWrappersSignal?.(), this.wrapperAutoAssociations), { ...(ngDevMode ? { debugName: "wrappers" } : /* istanbul ignore next */ {}), equal: isSameWrapperChain });
795
+ /**
796
+ * `fieldInputs` bag handed to every wrapper in the chain. Memoised on
797
+ * `rawInputs` identity so repeated emissions with the same underlying object
798
+ * return the same view and don't cascade OnPush re-evaluations.
799
+ */
800
+ fieldInputs = computed(() => this.buildFieldInputs(this.rawInputs()), ...(ngDevMode ? [{ debugName: "fieldInputs" }] : /* istanbul ignore next */ []));
801
+ defaultEnvInjector = inject(EnvironmentInjector);
802
+ /** Environment injector for the innermost field component — `[environmentInjector]` input takes precedence over the directive's own DI. */
803
+ fieldEnvInjector = computed(() => this.dfFieldOutletEnvironmentInjector() ?? this.defaultEnvInjector, ...(ngDevMode ? [{ debugName: "fieldEnvInjector" }] : /* istanbul ignore next */ []));
804
+ /** Field-level injector (FIELD_SIGNAL_CONTEXT, ARRAY_CONTEXT, …). Threaded to the controller so wrappers can inject it too. */
805
+ fieldInjector = computed(() => this.dfFieldOutlet().injector, ...(ngDevMode ? [{ debugName: "fieldInjector" }] : /* istanbul ignore next */ []));
806
+ constructor() {
807
+ createWrapperChainController({
808
+ vcr: this.vcr,
809
+ wrappers: this.wrappers,
810
+ gate: this.renderReady,
811
+ rebuildKey: this.componentIdentity,
812
+ fieldInputs: this.fieldInputs,
813
+ fieldInjector: this.fieldInjector,
814
+ beforeRebuild: () => this.detachFieldRef(),
815
+ renderInnermost: (slot) => {
816
+ const resolved = this.dfFieldOutlet();
817
+ if (this.fieldRef && this.fieldRef.componentType === resolved.component) {
818
+ // Same component class — re-insert the preserved hostView. Browser
819
+ // loses focus on detach, so we replay the snapshot captured in
820
+ // detachFieldRef. Input value persists on the DOM node itself.
821
+ slot.insert(this.fieldRef.hostView);
822
+ this.fieldSlot = slot;
823
+ this.pushRawInputs(this.fieldRef, this.rawInputs());
824
+ this.restoreFocusSnapshot();
825
+ return;
826
+ }
827
+ // Different component class — discard the old ref and create fresh.
828
+ // Merge field + element injectors so the field can inject ancestor
829
+ // wrappers AND form-context tokens (ARRAY_CONTEXT etc.).
830
+ // Clear state before createComponent — if it throws (bad provider,
831
+ // invalid class) we don't want to leak a reference to a destroyed ref
832
+ // that the same-component branch would later try to re-insert.
833
+ this.focusSnapshot = undefined;
834
+ this.lastPushedInputs = undefined;
835
+ const previousRef = this.fieldRef;
836
+ this.fieldRef = undefined;
837
+ this.fieldSlot = undefined;
838
+ previousRef?.destroy();
839
+ this.fieldRef = slot.createComponent(resolved.component, {
840
+ environmentInjector: this.fieldEnvInjector(),
841
+ injector: createWrapperAwareInjector(resolved.injector, slot.injector),
842
+ });
843
+ this.fieldSlot = slot;
844
+ this.pushRawInputs(this.fieldRef, this.rawInputs());
845
+ },
846
+ });
847
+ // Push rawInputs to the innermost field. Safe mid-rebuild — renderInnermost
848
+ // re-reads rawInputs synchronously on mount, so skipping here loses nothing.
849
+ explicitEffect([this.rawInputs], ([rawInputs]) => {
850
+ if (!this.fieldRef)
851
+ return;
852
+ if (this.lastPushedInputs === rawInputs)
853
+ return;
854
+ this.pushRawInputs(this.fieldRef, rawInputs);
855
+ });
856
+ this.destroyRef.onDestroy(() => {
857
+ // Only destroy when detached — vcr.clear() cascades otherwise.
858
+ if (this.fieldRef && !this.fieldSlot)
859
+ this.fieldRef.destroy();
860
+ this.fieldRef = undefined;
861
+ this.fieldSlot = undefined;
862
+ this.lastPushedInputs = undefined;
863
+ });
864
+ }
865
+ detachFieldRef() {
866
+ if (!this.fieldRef)
867
+ return;
868
+ if (!this.fieldSlot) {
869
+ // Stranded fieldRef — destroy to prevent a leak.
870
+ this.fieldRef.destroy();
871
+ this.fieldRef = undefined;
872
+ return;
873
+ }
874
+ this.captureFocusSnapshot();
875
+ const idx = this.fieldSlot.indexOf(this.fieldRef.hostView);
876
+ if (idx >= 0)
877
+ this.fieldSlot.detach(idx);
878
+ this.fieldSlot = undefined;
879
+ }
880
+ /** Capture active element + selection before the hostView is detached from DOM. */
881
+ captureFocusSnapshot() {
882
+ if (!this.fieldRef)
883
+ return;
884
+ const hostEl = this.fieldRef.location.nativeElement;
885
+ const active = document.activeElement;
886
+ if (!(active instanceof HTMLElement) || !hostEl.contains(active))
887
+ return;
888
+ const sel = active;
889
+ this.focusSnapshot = {
890
+ element: active,
891
+ selectionStart: typeof sel.selectionStart === 'number' ? sel.selectionStart : null,
892
+ selectionEnd: typeof sel.selectionEnd === 'number' ? sel.selectionEnd : null,
893
+ };
894
+ }
895
+ /** Re-apply the pre-detach focus + caret after the hostView is back in the DOM. */
896
+ restoreFocusSnapshot() {
897
+ const snap = this.focusSnapshot;
898
+ this.focusSnapshot = undefined;
899
+ if (!snap || !snap.element.isConnected)
900
+ return;
901
+ snap.element.focus();
902
+ if (snap.selectionStart !== null && snap.selectionEnd !== null && 'setSelectionRange' in snap.element) {
903
+ snap.element.setSelectionRange(snap.selectionStart, snap.selectionEnd);
904
+ }
905
+ }
906
+ pushRawInputs(ref, rawInputs) {
907
+ // Ref-identity dedupe per key — mappers must emit immutable snapshots.
908
+ const last = this.lastPushedInputs;
909
+ for (const [key, value] of Object.entries(rawInputs)) {
910
+ if (last && last[key] === value)
911
+ continue;
912
+ setInputIfDeclared(ref, key, value);
913
+ }
914
+ this.lastPushedInputs = rawInputs;
915
+ }
916
+ buildFieldInputs(rawInputs) {
917
+ const fieldTreeCandidate = rawInputs['field'];
918
+ // A FieldTree is a callable (() => FieldState). We expose it to wrappers via a narrow
919
+ // read-only view; raw FieldTree is still pushed to the innermost component for writes.
920
+ const readonlyField = fieldTreeCandidate && typeof fieldTreeCandidate === 'function'
921
+ ? toReadonlyFieldTreeCached(this.readonlyFieldCache, fieldTreeCandidate)
922
+ : undefined;
923
+ // Shallow spread — relies on the mapper contract (see WrapperFieldInputs)
924
+ // that rawInputs are emitted as fresh snapshots, not mutated in place.
925
+ return {
926
+ ...rawInputs,
927
+ field: readonlyField,
928
+ };
929
+ }
930
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DfFieldOutlet, deps: [], target: i0.ɵɵFactoryTarget.Directive });
931
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.8", type: DfFieldOutlet, isStandalone: true, selector: "[dfFieldOutlet]", inputs: { dfFieldOutlet: { classPropertyName: "dfFieldOutlet", publicName: "dfFieldOutlet", isSignal: true, isRequired: true, transformFunction: null }, dfFieldOutletEnvironmentInjector: { classPropertyName: "dfFieldOutletEnvironmentInjector", publicName: "dfFieldOutletEnvironmentInjector", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
932
+ }
933
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DfFieldOutlet, decorators: [{
934
+ type: Directive,
935
+ args: [{
936
+ selector: '[dfFieldOutlet]',
937
+ }]
938
+ }], ctorParameters: () => [], propDecorators: { dfFieldOutlet: [{ type: i0.Input, args: [{ isSignal: true, alias: "dfFieldOutlet", required: true }] }], dfFieldOutletEnvironmentInjector: [{ type: i0.Input, args: [{ isSignal: true, alias: "dfFieldOutletEnvironmentInjector", required: false }] }] } });
939
+
940
+ /**
941
+ * Wraps a submission action to handle both Promise and Observable returns.
942
+ * Converts Observables to Promises for compatibility with Angular Signal Forms' submit().
943
+ *
944
+ * Errors are NOT caught here — they propagate so that submit() can reject its Promise,
945
+ * allowing the caller's catchError to log and keep the submission stream alive.
946
+ *
947
+ * @param action - The submission action function
948
+ * @returns A wrapped function that returns a Promise
949
+ */
950
+ function wrapSubmissionAction(action) {
951
+ return async (formTree) => {
952
+ const result = action(formTree);
953
+ // If the action returns an Observable, convert it to a Promise
954
+ if (isObservable(result)) {
955
+ await firstValueFrom(result);
956
+ return;
957
+ }
958
+ await Promise.resolve(result);
959
+ };
960
+ }
961
+ /**
962
+ * Creates an Observable that handles form submission with optional submission action.
963
+ *
964
+ * This utility encapsulates the submission handling logic:
965
+ * - Listens for submit events from the event bus
966
+ * - If a submission.action is configured, wraps it and uses Angular Signal Forms' submit()
967
+ * - Handles both Promise and Observable returns from the action
968
+ * - Uses exhaustMap to ignore new submissions while one is in-flight (first-submit-wins)
969
+ *
970
+ * The returned Observable should be subscribed to with takeUntilDestroyed() in the component.
971
+ *
972
+ * @param options - Configuration options for the submission handler
973
+ * @returns Observable that processes submissions (emits when submission completes)
974
+ *
975
+ * @example
976
+ * ```typescript
977
+ * // In component constructor
978
+ * createSubmissionHandler({
979
+ * eventBus: this.eventBus,
980
+ * configSignal: this.config,
981
+ * formSignal: this.form,
982
+ * })
983
+ * .pipe(takeUntilDestroyed())
984
+ * .subscribe();
985
+ * ```
986
+ */
987
+ function createSubmissionHandler(options) {
988
+ const { eventBus, configSignal, formSignal, validSignal, logger } = options;
989
+ // exhaustMap ensures first-submit-wins: a second submit event while the first
990
+ // is in-flight is silently dropped rather than cancelling the running Promise.
991
+ // switchMap would unsubscribe the Observable wrapper but cannot cancel the
992
+ // underlying Promise, causing both side effects to execute.
993
+ return eventBus.on('submit').pipe(exhaustMap(() => {
994
+ const submissionConfig = configSignal().submission;
995
+ // If no submission action is configured, let the submitted output handle it
996
+ // This maintains backward compatibility for users handling submission manually
997
+ if (!submissionConfig?.action) {
998
+ return EMPTY;
999
+ }
1000
+ // Guard: match the (submitted) output's safety contract — reject submission
1001
+ // when the form is invalid or has pending async validators.
1002
+ if (!validSignal()) {
1003
+ logger.debug('Submission action skipped: form is not valid (invalid or pending async validators)');
1004
+ return EMPTY;
1005
+ }
1006
+ // Type assertion needed: submission.action accepts the form tree but its signature
1007
+ // is defined broadly in FormConfig. The actual runtime type is FieldTree<TModel>.
1008
+ const wrappedAction = wrapSubmissionAction(submissionConfig.action);
1009
+ // Use Angular Signal Forms' native submit() function
1010
+ // This automatically:
1011
+ // - Sets form.submitting() to true during execution
1012
+ // - Applies server errors to form fields on completion
1013
+ // - Sets form.submitting() to false when done
1014
+ // catchError keeps the exhaustMap stream alive after action failure —
1015
+ // without it, an unhandled error would terminate all future submissions.
1016
+ return from(submit(formSignal(), wrappedAction)).pipe(catchError((error) => {
1017
+ logger.error('Submission action failed:', error);
1018
+ return EMPTY;
1019
+ }));
1020
+ }));
1021
+ }
1022
+
1023
+ /**
1024
+ * Injection token for globally enabling form value emission on events.
1025
+ *
1026
+ * When set to `true`, all events dispatched through the EventBus will include
1027
+ * the current form value in the `formValue` property.
1028
+ *
1029
+ * This token is configured via `withEventFormValue()` feature function.
1030
+ * Per-form overrides can be set via `options.emitFormValueOnEvents` in the form config.
1031
+ *
1032
+ * @internal
1033
+ */
1034
+ const EMIT_FORM_VALUE_ON_EVENTS = new InjectionToken('EMIT_FORM_VALUE_ON_EVENTS', {
1035
+ providedIn: 'root',
1036
+ factory: () => false,
1037
+ });
1038
+
1039
+ /**
1040
+ * Registry service that provides access to the root form and its values.
1041
+ *
1042
+ * Constructed via factory in `provideDynamicFormDI` with signals from
1043
+ * `FormStateManager` — no DI injection, no Maps, no lifecycle.
1044
+ */
1045
+ class RootFormRegistryService {
1046
+ formValue;
1047
+ rootForm;
1048
+ constructor(formValue, rootForm) {
1049
+ this.formValue = formValue;
1050
+ this.rootForm = rootForm;
1051
+ }
1052
+ }
1053
+
356
1054
  /**
357
1055
  * No-operation logger implementation.
358
1056
  * All methods are no-ops - used in production by default.
@@ -532,10 +1230,10 @@ class EventBus {
532
1230
  }
533
1231
  return this.pipeline$.pipe(filter((event) => event.type === eventType));
534
1232
  }
535
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: EventBus, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
536
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: EventBus });
1233
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: EventBus, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1234
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: EventBus });
537
1235
  }
538
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: EventBus, decorators: [{
1236
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: EventBus, decorators: [{
539
1237
  type: Injectable
540
1238
  }] });
541
1239
 
@@ -588,7 +1286,7 @@ const INITIALIZATION_TIMEOUT_MS = new InjectionToken('INITIALIZATION_TIMEOUT_MS'
588
1286
  * ```
589
1287
  */
590
1288
  function createInitializationTracker(eventBus, expectedCount) {
591
- return eventBus.on('component-initialized').pipe(scan((count) => count + 1, 0), map((currentCount) => currentCount >= expectedCount), filter$1((isComplete) => isComplete));
1289
+ return eventBus.on('component-initialized').pipe(scan((count) => count + 1, 0), map$1((currentCount) => currentCount >= expectedCount), filter$1((isComplete) => isComplete));
592
1290
  }
593
1291
  /**
594
1292
  * Creates an observable that tracks component initialization progress with detailed status.
@@ -613,7 +1311,7 @@ function createInitializationTracker(eventBus, expectedCount) {
613
1311
  * ```
614
1312
  */
615
1313
  function createDetailedInitializationTracker(eventBus, expectedCount) {
616
- return eventBus.on('component-initialized').pipe(scan((count) => count + 1, 0), map((currentCount) => ({
1314
+ return eventBus.on('component-initialized').pipe(scan((count) => count + 1, 0), map$1((currentCount) => ({
617
1315
  currentCount,
618
1316
  expectedCount,
619
1317
  isComplete: currentCount >= expectedCount,
@@ -649,11 +1347,11 @@ function setupInitializationTracking(options) {
649
1347
  const { eventBus, totalComponentsCount, injector, componentId } = options;
650
1348
  const timeoutMs = injector.get(INITIALIZATION_TIMEOUT_MS);
651
1349
  const logger = injector.get(DynamicFormLogger);
652
- return toObservable(totalComponentsCount, { injector }).pipe(take(1), switchMap((count) => {
1350
+ return toObservable(totalComponentsCount, { injector }).pipe(take(1), switchMap$1((count) => {
653
1351
  let tracking$;
654
1352
  if (count === 1) {
655
1353
  // Only dynamic-form component, emit immediately when it initializes
656
- tracking$ = eventBus.on('component-initialized').pipe(filter$1((event) => event.componentType === 'dynamic-form' && event.componentId === componentId), map(() => true), take(1));
1354
+ tracking$ = eventBus.on('component-initialized').pipe(filter$1((event) => event.componentType === 'dynamic-form' && event.componentId === componentId), map$1(() => true), take(1));
657
1355
  }
658
1356
  else {
659
1357
  tracking$ = createInitializationTracker(eventBus, count);
@@ -787,6 +1485,14 @@ function isSimplifiedArrayField(field) {
787
1485
  return field.type === 'array' && 'template' in field;
788
1486
  }
789
1487
 
1488
+ /**
1489
+ * Type guard for ContainerField with proper type narrowing
1490
+ */
1491
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Type guard must accept any field type
1492
+ function isContainerTypedField(field) {
1493
+ return field.type === 'container' && 'fields' in field && 'wrappers' in field;
1494
+ }
1495
+
790
1496
  /**
791
1497
  * Type guard to check if a field definition has child fields.
792
1498
  * This is a looser check that works with FieldDef<unknown> without requiring RegisteredFieldTypes.
@@ -795,7 +1501,7 @@ function hasChildFields(field) {
795
1501
  return 'fields' in field && field.fields != null;
796
1502
  }
797
1503
  /** Container field type names */
798
- const CONTAINER_TYPES$1 = new Set(['page', 'row', 'group', 'array']);
1504
+ const CONTAINER_TYPES$1 = new Set(['page', 'row', 'group', 'array', 'container']);
799
1505
  function isContainerField(field) {
800
1506
  return CONTAINER_TYPES$1.has(field.type);
801
1507
  }
@@ -1709,10 +2415,10 @@ class FunctionRegistryService {
1709
2415
  this.clearAsyncValidators();
1710
2416
  this.clearHttpValidators();
1711
2417
  }
1712
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FunctionRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1713
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FunctionRegistryService });
2418
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: FunctionRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2419
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: FunctionRegistryService });
1714
2420
  }
1715
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FunctionRegistryService, decorators: [{
2421
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: FunctionRegistryService, decorators: [{
1716
2422
  type: Injectable
1717
2423
  }] });
1718
2424
 
@@ -1751,10 +2457,10 @@ class SchemaRegistryService {
1751
2457
  }
1752
2458
  return schema;
1753
2459
  }
1754
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: SchemaRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1755
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: SchemaRegistryService });
2460
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: SchemaRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2461
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: SchemaRegistryService });
1756
2462
  }
1757
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: SchemaRegistryService, decorators: [{
2463
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: SchemaRegistryService, decorators: [{
1758
2464
  type: Injectable
1759
2465
  }] });
1760
2466
 
@@ -2335,10 +3041,10 @@ class FieldContextRegistryService {
2335
3041
  },
2336
3042
  };
2337
3043
  }
2338
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FieldContextRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2339
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FieldContextRegistryService });
3044
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: FieldContextRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3045
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: FieldContextRegistryService });
2340
3046
  }
2341
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FieldContextRegistryService, decorators: [{
3047
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: FieldContextRegistryService, decorators: [{
2342
3048
  type: Injectable
2343
3049
  }] });
2344
3050
 
@@ -5566,7 +6272,7 @@ function createHttpDerivationStream(entry, formValue$, context) {
5566
6272
  // Capture after the guard — TS narrows this to `string` here
5567
6273
  const responseExpression = entry.responseExpression;
5568
6274
  const debounceMs = entry.debounceMs ?? DEFAULT_HTTP_DEBOUNCE_MS;
5569
- return formValue$.pipe(startWith(null), pairwise(), map$1(([previous, current]) => ({
6275
+ return formValue$.pipe(startWith(null), pairwise(), map(([previous, current]) => ({
5570
6276
  current: current,
5571
6277
  changedFields: getChangedKeys(previous, current),
5572
6278
  })),
@@ -5575,7 +6281,7 @@ function createHttpDerivationStream(entry, formValue$, context) {
5575
6281
  if (changedFields.size === 0)
5576
6282
  return false;
5577
6283
  return entry.dependsOn.some((dep) => changedFields.has(dep));
5578
- }), debounceTime(debounceMs), switchMap$1(({ current, changedFields }) => {
6284
+ }), debounceTime(debounceMs), switchMap(({ current, changedFields }) => {
5579
6285
  return new Observable((subscriber) => {
5580
6286
  const formAccessor = untracked(() => context.form());
5581
6287
  // Check stopOnUserOverride — skip if the user has manually edited the target field
@@ -5760,7 +6466,7 @@ function createAsyncDerivationStream(entry, formValue$, context) {
5760
6466
  // first emission (null vs actual form value), so every dependsOn field matches.
5761
6467
  // This intentionally fires the async derivation on initial form load — same
5762
6468
  // pattern as http-derivation-stream.ts.
5763
- return formValue$.pipe(startWith(null), pairwise(), map$1(([previous, current]) => ({
6469
+ return formValue$.pipe(startWith(null), pairwise(), map(([previous, current]) => ({
5764
6470
  current: current,
5765
6471
  changedFields: getChangedKeys(previous, current),
5766
6472
  })),
@@ -5769,7 +6475,7 @@ function createAsyncDerivationStream(entry, formValue$, context) {
5769
6475
  if (changedFields.size === 0)
5770
6476
  return false;
5771
6477
  return entry.dependsOn.some((dep) => changedFields.has(dep));
5772
- }), debounceTime(debounceMs), switchMap$1(({ current, changedFields }) => {
6478
+ }), debounceTime(debounceMs), switchMap(({ current, changedFields }) => {
5773
6479
  return new Observable((subscriber) => {
5774
6480
  const formAccessor = untracked(() => context.form());
5775
6481
  // Check stopOnUserOverride — skip if the user has manually edited the target field
@@ -5971,7 +6677,7 @@ class DerivationOrchestrator {
5971
6677
  auditTime(0),
5972
6678
  // startWith + pairwise: Track previous and current values to detect changed fields.
5973
6679
  // This enables reEngageOnDependencyChange for onChange derivations.
5974
- startWith(null), pairwise(), filter((pair) => pair[1] !== null), map$1(([previous, current]) => ({
6680
+ startWith(null), pairwise(), filter((pair) => pair[1] !== null), map(([previous, current]) => ({
5975
6681
  collection: current[0],
5976
6682
  formAccessor: current[2],
5977
6683
  changedFields: getChangedKeys(previous?.[1] ?? null, current[1]),
@@ -5999,14 +6705,14 @@ class DerivationOrchestrator {
5999
6705
  debounceTime(DEFAULT_DEBOUNCE_MS),
6000
6706
  // startWith + pairwise: Track previous and current values to detect changes.
6001
6707
  // startWith(null) ensures pairwise has an initial value to pair with.
6002
- startWith(null), pairwise(), filter((pair) => pair[1] !== null), map$1(([previous, current]) => ({
6708
+ startWith(null), pairwise(), filter((pair) => pair[1] !== null), map(([previous, current]) => ({
6003
6709
  current,
6004
6710
  changedFields: getChangedKeys(previous, current),
6005
6711
  })), filter(({ changedFields }) => changedFields.size > 0),
6006
6712
  // switchMap: For debounced derivations, it's OK to cancel pending work
6007
6713
  // if new changes come in - we want the latest debounced values.
6008
6714
  // (Unlike onChange which uses exhaustMap to prevent cancellation)
6009
- switchMap$1(({ changedFields }) => {
6715
+ switchMap(({ changedFields }) => {
6010
6716
  const collection = untracked(() => this.derivationCollection());
6011
6717
  const formAccessor = untracked(() => this.config.form());
6012
6718
  if (!collection || !formAccessor)
@@ -6027,11 +6733,11 @@ class DerivationOrchestrator {
6027
6733
  createPeriodStream(debounceMs, collection, formAccessor, changedFields) {
6028
6734
  const additionalWait = Math.max(0, debounceMs - DEFAULT_DEBOUNCE_MS);
6029
6735
  if (additionalWait === 0) {
6030
- return of(null).pipe(map$1(() => {
6736
+ return of(null).pipe(map(() => {
6031
6737
  this.applyDebouncedEntriesForPeriod(debounceMs, collection, formAccessor, changedFields);
6032
6738
  }));
6033
6739
  }
6034
- return timer(additionalWait).pipe(map$1(() => {
6740
+ return timer(additionalWait).pipe(map(() => {
6035
6741
  const currentCollection = untracked(() => this.derivationCollection());
6036
6742
  const currentFormAccessor = untracked(() => this.config.form());
6037
6743
  if (currentCollection && currentFormAccessor) {
@@ -6766,10 +7472,10 @@ class PropertyDerivationOrchestrator {
6766
7472
  }
6767
7473
  setupDebouncedStream() {
6768
7474
  toObservable(this.config.formValue, { injector: this.injector })
6769
- .pipe(debounceTime(DEFAULT_DEBOUNCE_MS), startWith(null), pairwise(), filter((pair) => pair[1] !== null), map$1(([previous, current]) => ({
7475
+ .pipe(debounceTime(DEFAULT_DEBOUNCE_MS), startWith(null), pairwise(), filter((pair) => pair[1] !== null), map(([previous, current]) => ({
6770
7476
  current,
6771
7477
  changedFields: getChangedKeys(previous, current),
6772
- })), filter(({ changedFields }) => changedFields.size > 0), switchMap$1(({ changedFields }) => {
7478
+ })), filter(({ changedFields }) => changedFields.size > 0), switchMap(({ changedFields }) => {
6773
7479
  const collection = untracked(() => this.propertyDerivationCollection());
6774
7480
  if (!collection)
6775
7481
  return of(null);
@@ -6785,7 +7491,7 @@ class PropertyDerivationOrchestrator {
6785
7491
  }
6786
7492
  createPeriodStream(debounceMs, collection, changedFields) {
6787
7493
  const additionalWait = Math.max(0, debounceMs - DEFAULT_DEBOUNCE_MS);
6788
- return timer(additionalWait).pipe(map$1(() => {
7494
+ return timer(additionalWait).pipe(map(() => {
6789
7495
  // For periods beyond DEFAULT_DEBOUNCE_MS, re-read collection as it may have changed during the wait.
6790
7496
  // For zero-wait periods, use the original collection directly since no time has elapsed.
6791
7497
  const currentCollection = additionalWait > 0 ? (untracked(() => this.propertyDerivationCollection()) ?? collection) : collection;
@@ -7134,6 +7840,40 @@ class ShiftArrayItemEvent {
7134
7840
  }
7135
7841
  }
7136
7842
 
7843
+ /**
7844
+ * Event dispatched to move an existing item from one index to another within an array field.
7845
+ *
7846
+ * This is an atomic reorder operation — the item at `fromIndex` is removed and
7847
+ * reinserted at `toIndex`. No template is required because the existing item
7848
+ * (resolved component, form value, and stored template) is preserved.
7849
+ *
7850
+ * @example
7851
+ * ```typescript
7852
+ * // Use the builder API (recommended)
7853
+ * eventBus.dispatch(arrayEvent('contacts').move(0, 2));
7854
+ *
7855
+ * // Or instantiate directly
7856
+ * eventBus.dispatch(new MoveArrayItemEvent('contacts', 0, 2));
7857
+ * ```
7858
+ */
7859
+ class MoveArrayItemEvent {
7860
+ arrayKey;
7861
+ fromIndex;
7862
+ toIndex;
7863
+ type = 'move-array-item';
7864
+ constructor(
7865
+ /** The key of the array field containing the item to move */
7866
+ arrayKey,
7867
+ /** The current index of the item to move */
7868
+ fromIndex,
7869
+ /** The target index to move the item to */
7870
+ toIndex) {
7871
+ this.arrayKey = arrayKey;
7872
+ this.fromIndex = fromIndex;
7873
+ this.toIndex = toIndex;
7874
+ }
7875
+ }
7876
+
7137
7877
  /**
7138
7878
  * Event dispatched to remove an item at a SPECIFIC INDEX from an array field.
7139
7879
  *
@@ -7522,8 +8262,8 @@ class PageOrchestratorComponent {
7522
8262
  }
7523
8263
  return false;
7524
8264
  }
7525
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: PageOrchestratorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
7526
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.6", type: PageOrchestratorComponent, isStandalone: true, selector: "div[page-orchestrator]", inputs: { pageFields: { classPropertyName: "pageFields", publicName: "pageFields", isSignal: true, isRequired: true, transformFunction: null }, form: { classPropertyName: "form", publicName: "form", isSignal: true, isRequired: true, transformFunction: null }, fieldSignalContext: { classPropertyName: "fieldSignalContext", publicName: "fieldSignalContext", isSignal: true, isRequired: true, transformFunction: null } }, host: { properties: { "attr.data-current-page": "state().currentPageIndex", "attr.data-total-pages": "state().totalPages" }, classAttribute: "df-page-orchestrator" }, providers: [
8265
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: PageOrchestratorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8266
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: PageOrchestratorComponent, isStandalone: true, selector: "div[page-orchestrator]", inputs: { pageFields: { classPropertyName: "pageFields", publicName: "pageFields", isSignal: true, isRequired: true, transformFunction: null }, form: { classPropertyName: "form", publicName: "form", isSignal: true, isRequired: true, transformFunction: null }, fieldSignalContext: { classPropertyName: "fieldSignalContext", publicName: "fieldSignalContext", isSignal: true, isRequired: true, transformFunction: null } }, host: { properties: { "attr.data-current-page": "state().currentPageIndex", "attr.data-total-pages": "state().totalPages" }, classAttribute: "df-page-orchestrator" }, providers: [
7527
8267
  {
7528
8268
  provide: FIELD_SIGNAL_CONTEXT,
7529
8269
  useFactory: (orchestrator) => orchestrator.extendedFieldSignalContext(),
@@ -7553,9 +8293,9 @@ class PageOrchestratorComponent {
7553
8293
  }
7554
8294
  }
7555
8295
  }
7556
- `, isInline: true, styles: [":host{display:block;position:relative;width:100%}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, deferBlockDependencies: [() => [import('./ng-forge-dynamic-forms-page-field.component-Cen4ArED.mjs').then(m => m.default)], () => [import('./ng-forge-dynamic-forms-page-field.component-Cen4ArED.mjs').then(m => m.default)]] });
8296
+ `, isInline: true, styles: [":host{display:block;position:relative;width:100%}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, deferBlockDependencies: [() => [import('./ng-forge-dynamic-forms-page-field.component-CEN5u1dd.mjs').then(m => m.default)], () => [import('./ng-forge-dynamic-forms-page-field.component-CEN5u1dd.mjs').then(m => m.default)]] });
7557
8297
  }
7558
- i0.ɵɵngDeclareClassMetadataAsync({ minVersion: "18.0.0", version: "21.2.6", ngImport: i0, type: PageOrchestratorComponent, resolveDeferredDeps: () => [import('./ng-forge-dynamic-forms-page-field.component-Cen4ArED.mjs').then(m => m.default)], resolveMetadata: PageFieldComponent => ({ decorators: [{
8298
+ i0.ɵɵngDeclareClassMetadataAsync({ minVersion: "18.0.0", version: "21.2.8", ngImport: i0, type: PageOrchestratorComponent, resolveDeferredDeps: () => [import('./ng-forge-dynamic-forms-page-field.component-CEN5u1dd.mjs').then(m => m.default)], resolveMetadata: PageFieldComponent => ({ decorators: [{
7559
8299
  type: Component,
7560
8300
  args: [{ selector: 'div[page-orchestrator]', imports: [PageFieldComponent], template: `
7561
8301
  @for (pageField of pageFields(); track pageField.key; let i = $index) {
@@ -7707,7 +8447,7 @@ function validateSimplifiedTemplate(field) {
7707
8447
  * Expands a simplified array field into a full ArrayField + optional add button.
7708
8448
  */
7709
8449
  function expandSimplifiedArray(field) {
7710
- const { template, value = [], addButton, removeButton, key, logic, minLength, maxLength } = field;
8450
+ const { template, value = [], addButton, removeButton, key, logic, minLength, maxLength, wrappers, skipAutoWrappers, skipDefaultWrappers, } = field;
7711
8451
  const isObjectTemplate = Array.isArray(template);
7712
8452
  const values = value;
7713
8453
  // Validate template before expansion
@@ -7744,6 +8484,15 @@ function expandSimplifiedArray(field) {
7744
8484
  if (maxLength !== undefined) {
7745
8485
  arrayFieldObj['maxLength'] = maxLength;
7746
8486
  }
8487
+ if (wrappers !== undefined) {
8488
+ arrayFieldObj['wrappers'] = wrappers;
8489
+ }
8490
+ if (skipAutoWrappers !== undefined) {
8491
+ arrayFieldObj['skipAutoWrappers'] = skipAutoWrappers;
8492
+ }
8493
+ if (skipDefaultWrappers !== undefined) {
8494
+ arrayFieldObj['skipDefaultWrappers'] = skipDefaultWrappers;
8495
+ }
7747
8496
  // Safe cast: we're constructing a valid ArrayField shape with key, type, and fields
7748
8497
  const arrayField = arrayFieldObj;
7749
8498
  // Store normalization metadata via Symbol property instead of a runtime property
@@ -7826,7 +8575,7 @@ function buildRemoveButton(config) {
7826
8575
  * Type guard to check if a field is a container with fields
7827
8576
  */
7828
8577
  function isContainerWithFields(field) {
7829
- return ((field.type === 'row' || field.type === 'group' || field.type === 'page') &&
8578
+ return ((field.type === 'row' || field.type === 'group' || field.type === 'page' || field.type === 'container') &&
7830
8579
  'fields' in field &&
7831
8580
  Array.isArray(field.fields));
7832
8581
  }
@@ -8239,7 +8988,7 @@ function createHttpConditionLogicFunction(condition) {
8239
8988
  options['body'] = request.body;
8240
8989
  if (request.headers)
8241
8990
  options['headers'] = request.headers;
8242
- return httpClient.request(method, request.url, options).pipe(map$1((response) => extractBoolean(response, condition.responseExpression, pendingValue, logger)), tap((value) => {
8991
+ return httpClient.request(method, request.url, options).pipe(map((response) => extractBoolean(response, condition.responseExpression, pendingValue, logger)), tap((value) => {
8243
8992
  const requestKey = stableStringify(request);
8244
8993
  cache.set(requestKey, value, cacheDurationMs);
8245
8994
  }), catchError((error) => {
@@ -8364,7 +9113,7 @@ function createAsyncConditionLogicFunction(condition) {
8364
9113
  const evaluationContext = untracked(() => fieldContextRegistry.createReactiveEvaluationContext(capturedCtx, functionRegistry.getCustomFunctions()));
8365
9114
  // asyncFn may return Promise<boolean> or Observable<boolean>.
8366
9115
  // from() normalizes both to Observable.
8367
- return from(asyncFn(evaluationContext)).pipe(map$1((result) => !!result), catchError((error) => {
9116
+ return from(asyncFn(evaluationContext)).pipe(map((result) => !!result), catchError((error) => {
8368
9117
  logger.warn('Async Condition - function failed:', error);
8369
9118
  return of(pendingValue);
8370
9119
  }));
@@ -9147,8 +9896,8 @@ function getNumberValidationConfig(fieldDef) {
9147
9896
  * Cross-field validators are skipped at field level and applied at form level via validateTree.
9148
9897
  */
9149
9898
  function mapFieldToForm(fieldDef, fieldPath) {
9150
- // Layout containers (page, row) - flatten children to current level
9151
- if (isPageField(fieldDef) || isRowField(fieldDef)) {
9899
+ // Layout containers (page, row, container) - flatten children to current level
9900
+ if (isPageField(fieldDef) || isRowField(fieldDef) || isContainerTypedField(fieldDef)) {
9152
9901
  mapContainerChildren(fieldDef.fields, fieldPath);
9153
9902
  return;
9154
9903
  }
@@ -9305,8 +10054,8 @@ function mapArrayFieldToForm(arrayField, fieldPath) {
9305
10054
  const pathRecord = itemPath;
9306
10055
  // Map ALL unique template fields from all object items
9307
10056
  for (const templateField of allObjectFields) {
9308
- if (isRowField(templateField) || isPageField(templateField)) {
9309
- // Row/page templates flatten their children
10057
+ if (isRowField(templateField) || isPageField(templateField) || isContainerTypedField(templateField)) {
10058
+ // Row/page/container templates flatten their children
9310
10059
  mapContainerChildren(templateField.fields, itemPath);
9311
10060
  }
9312
10061
  else if (isGroupField(templateField)) {
@@ -9345,8 +10094,8 @@ function mapArrayFieldToForm(arrayField, fieldPath) {
9345
10094
  function collectFieldsFromObjectItem(itemFields, allFields) {
9346
10095
  const seenKeys = new Set(allFields.map((f) => f.key).filter(Boolean));
9347
10096
  for (const field of itemFields) {
9348
- // For row/page fields, we need to collect their children's keys
9349
- if (isRowField(field) || isPageField(field)) {
10097
+ // For row/page/container fields, we need to collect their children's keys
10098
+ if (isRowField(field) || isPageField(field) || isContainerTypedField(field)) {
9350
10099
  // Use a synthetic key for container fields to dedupe them
9351
10100
  const containerKey = `__container_${field.type}_${JSON.stringify(field.fields?.map((f) => f.key))}`;
9352
10101
  if (!seenKeys.has(containerKey)) {
@@ -9477,17 +10226,24 @@ function getFieldDefaultValue(field, registry) {
9477
10226
  }
9478
10227
  return groupDefaults;
9479
10228
  }
9480
- // Use explicit value if provided, with type-specific handling for null
10229
+ // Narrow once: `field` is FieldDef<unknown>, but value/nullable are defined on BaseValueField.
10230
+ // This cast avoids per-access `as` and documents what we're reading.
10231
+ const valueField = field;
10232
+ const isNullable = valueField.nullable === true;
10233
+ // Use explicit value if provided (including null when nullable)
9481
10234
  if ('value' in field) {
9482
- // If value is explicitly set (even to null/undefined), respect it
9483
- if (field.value !== null && field.value !== undefined) {
9484
- return field.value;
10235
+ if (valueField.value !== null && valueField.value !== undefined) {
10236
+ return valueField.value;
9485
10237
  }
9486
- // Handle explicit null: use type-specific default
9487
- if (field.value === null) {
9488
- return field.type === 'checkbox' ? false : '';
10238
+ // Preserve explicit null for nullable fields
10239
+ if (valueField.value === null && isNullable) {
10240
+ return null;
9489
10241
  }
9490
- // Handle explicit undefined: fall through to type-specific defaults
10242
+ // Non-nullable null and undefined both fall through to type-specific defaults below
10243
+ }
10244
+ // Nullable fields with no explicit value default to null
10245
+ if (isNullable) {
10246
+ return null;
9491
10247
  }
9492
10248
  // Type-specific defaults when no value is specified
9493
10249
  if (field.type === 'checkbox') {
@@ -9519,8 +10275,8 @@ function getFieldDefaultValue(field, registry) {
9519
10275
  // Groups wrap their value under the group key
9520
10276
  itemValue[templateField.key] = fieldValue;
9521
10277
  }
9522
- else if (templateField.type === 'row') {
9523
- // Rows flatten their fields directly
10278
+ else if (templateField.type === 'row' || templateField.type === 'container') {
10279
+ // Rows and containers flatten their fields directly
9524
10280
  if (fieldValue && typeof fieldValue === 'object') {
9525
10281
  itemValue = { ...itemValue, ...fieldValue };
9526
10282
  }
@@ -10209,18 +10965,20 @@ function flattenFields(fields, registry, options = {}) {
10209
10965
  // Step 1: Determine how this field type should handle its value in the form
10210
10966
  // valueHandling can be: 'include', 'exclude', or 'flatten'
10211
10967
  const valueHandling = getFieldValueHandling(field.type, registry);
10212
- // Step 2: Check if this is a row field that should be preserved for DOM rendering
10968
+ // Step 2: Check if this is a row or container field that should be preserved for DOM rendering
10213
10969
  // Row fields need to render their container element for grid layouts to work
10214
- if (options.preserveRows && isRowField(field)) {
10970
+ // Container fields need to render their container for the wrapper chain
10971
+ if (options.preserveRows && (isRowField(field) || isContainerTypedField(field))) {
10215
10972
  if (field.fields) {
10216
10973
  // Recursively flatten children while preserving row structure
10217
10974
  const flattenedChildren = flattenFields(normalizeFieldsArray(field.fields), registry, options);
10218
- // Keep the row field in the result with its flattened children
10219
- // This allows the row component to render its container while children are flattened
10975
+ // Keep the row/container field in the result with its flattened children
10976
+ // This allows the container component to render while children are flattened
10977
+ const autoPrefix = isRowField(field) ? 'auto_row' : 'auto_container';
10220
10978
  result.push({
10221
10979
  ...field,
10222
10980
  fields: flattenedChildren,
10223
- key: field.key || `auto_row_${autoKeyCounter++}`,
10981
+ key: field.key || `${autoPrefix}_${autoKeyCounter++}`,
10224
10982
  });
10225
10983
  }
10226
10984
  }
@@ -10792,6 +11550,17 @@ function mapFieldToInputs(fieldDef, fieldRegistry) {
10792
11550
  });
10793
11551
  }
10794
11552
 
11553
+ function createRenderReadySignal(inputs, definition) {
11554
+ const explicitRenderReadyWhen = definition?.renderReadyWhen;
11555
+ const requiredInputs = definition?.mapper && explicitRenderReadyWhen === undefined ? ['field'] : (explicitRenderReadyWhen ?? []);
11556
+ if (requiredInputs.length === 0) {
11557
+ return computed(() => true);
11558
+ }
11559
+ return computed(() => {
11560
+ const currentInputs = inputs();
11561
+ return requiredInputs.every((inputName) => currentInputs[inputName] !== undefined);
11562
+ });
11563
+ }
10795
11564
  /**
10796
11565
  * Resolves a single field definition to a ResolvedField using RxJS.
10797
11566
  * Loads the component asynchronously and maps inputs in the injection context.
@@ -10805,7 +11574,7 @@ function mapFieldToInputs(fieldDef, fieldRegistry) {
10805
11574
  * @returns Observable that emits ResolvedField or undefined (for componentless fields or on error)
10806
11575
  */
10807
11576
  function resolveField(fieldDef, context) {
10808
- return from(context.loadTypeComponent(fieldDef.type)).pipe(map$1((component) => {
11577
+ return from(context.loadTypeComponent(fieldDef.type)).pipe(map((component) => {
10809
11578
  // Check if component is destroyed before proceeding
10810
11579
  if (context.destroyRef.destroyed) {
10811
11580
  return undefined;
@@ -10816,15 +11585,18 @@ function resolveField(fieldDef, context) {
10816
11585
  }
10817
11586
  // Run mapper in injection context
10818
11587
  const inputs = runInInjectionContext(context.injector, () => mapFieldToInputs(fieldDef, context.registry));
11588
+ const definition = context.registry.get(fieldDef.type);
10819
11589
  // Fields with components should always have inputs (componentless fields are handled above)
10820
11590
  if (!inputs) {
10821
11591
  return undefined;
10822
11592
  }
10823
11593
  return {
10824
11594
  key: fieldDef.key,
11595
+ fieldDef,
10825
11596
  component,
10826
11597
  injector: context.injector,
10827
11598
  inputs,
11599
+ renderReady: createRenderReadySignal(inputs, definition),
10828
11600
  };
10829
11601
  }), catchError((error) => {
10830
11602
  // Only call onError if component is not destroyed to avoid accessing cleaned-up state
@@ -10850,14 +11622,17 @@ function resolveFieldSync(fieldDef, context) {
10850
11622
  return undefined;
10851
11623
  }
10852
11624
  const inputs = runInInjectionContext(context.injector, () => mapFieldToInputs(fieldDef, context.registry));
11625
+ const definition = context.registry.get(fieldDef.type);
10853
11626
  if (!inputs) {
10854
11627
  return undefined;
10855
11628
  }
10856
11629
  return {
10857
11630
  key: fieldDef.key,
11631
+ fieldDef,
10858
11632
  component,
10859
11633
  injector: context.injector,
10860
11634
  inputs,
11635
+ renderReady: createRenderReadySignal(inputs, definition),
10861
11636
  };
10862
11637
  }
10863
11638
  /**
@@ -10885,13 +11660,13 @@ function reconcileFields(prev, curr) {
10885
11660
  * Used by container components (page, group) to resolve their child fields.
10886
11661
  */
10887
11662
  function createFieldResolutionPipe(getContext) {
10888
- return pipe(switchMap$1((fields) => {
11663
+ return pipe(switchMap((fields) => {
10889
11664
  if (!fields || fields.length === 0) {
10890
11665
  return of([]);
10891
11666
  }
10892
11667
  const context = getContext();
10893
11668
  return forkJoin(fields.map((f) => resolveField(f, context)));
10894
- }), map$1((fields) => fields.filter((f) => f !== undefined)), scan$1(reconcileFields, []));
11669
+ }), map((fields) => fields.filter((f) => f !== undefined)), scan$1(reconcileFields, []));
10895
11670
  }
10896
11671
 
10897
11672
  /**
@@ -11156,13 +11931,8 @@ function injectFieldRegistry() {
11156
11931
  if (cached) {
11157
11932
  return cached;
11158
11933
  }
11159
- // Handle async loading
11160
11934
  try {
11161
- const result = await fieldType.loadComponent();
11162
- // Handle ES module imports - extract default export if needed
11163
- const moduleResult = result;
11164
- const component = (typeof moduleResult === 'object' && 'default' in moduleResult && moduleResult.default) || result;
11165
- // Populate cache for future sync lookups
11935
+ const component = resolveDefaultExport(await fieldType.loadComponent());
11166
11936
  if (component) {
11167
11937
  componentCache.set(name, component);
11168
11938
  }
@@ -12344,13 +13114,13 @@ class FormStateManager {
12344
13114
  cleared$;
12345
13115
  /** Emits once when the state machine reaches 'ready'. Useful for tests. */
12346
13116
  get ready$() {
12347
- return this.machine.state$.pipe(filter(isReadyState), take$1(1), map$1(() => undefined));
13117
+ return this.machine.state$.pipe(filter(isReadyState), take$1(1), map(() => undefined));
12348
13118
  }
12349
13119
  // ─────────────────────────────────────────────────────────────────────────────
12350
13120
  // Constructor
12351
13121
  // ─────────────────────────────────────────────────────────────────────────────
12352
13122
  constructor() {
12353
- this.resolvedFields = derivedFromDeferred(this.fieldsSource, pipe(switchMap$1((fields) => {
13123
+ this.resolvedFields = derivedFromDeferred(this.fieldsSource, pipe(switchMap((fields) => {
12354
13124
  if (!fields || fields.length === 0) {
12355
13125
  return of([]);
12356
13126
  }
@@ -12399,13 +13169,13 @@ class FormStateManager {
12399
13169
  if (asyncObs.length === 0) {
12400
13170
  return of(results);
12401
13171
  }
12402
- return forkJoin(asyncObs).pipe(map$1((asyncResults) => {
13172
+ return forkJoin(asyncObs).pipe(map((asyncResults) => {
12403
13173
  for (let j = 0; j < asyncResults.length; j++) {
12404
13174
  results[asyncIndexes[j]] = asyncResults[j];
12405
13175
  }
12406
13176
  return results;
12407
13177
  }));
12408
- }), map$1((fields) => fields.filter((f) => f !== undefined)), scan$1(reconcileFields, [])), { initialValue: [], injector: this.injector });
13178
+ }), map((fields) => fields.filter((f) => f !== undefined)), scan$1(reconcileFields, [])), { initialValue: [], injector: this.injector });
12409
13179
  // Length comparison works because reconcileFields preserves ordering and
12410
13180
  // fieldsSource changes atomically during transitions (lengths won't match until settled).
12411
13181
  this.isFieldPipelineSettled = computed(() => {
@@ -12420,7 +13190,7 @@ class FormStateManager {
12420
13190
  }),
12421
13191
  // switchMap is intentional here: mapping is synchronous (reads signals, no async Promise).
12422
13192
  // The exhaustMap concern in createSubmissionHandler does not apply to this path.
12423
- switchMap$1(() => {
13193
+ switchMap(() => {
12424
13194
  const config = this.activeConfig();
12425
13195
  const submissionConfig = config?.submission;
12426
13196
  if (submissionConfig?.action) {
@@ -12431,8 +13201,8 @@ class FormStateManager {
12431
13201
  }
12432
13202
  return of(this.filteredFormValue());
12433
13203
  }), takeUntilDestroyed(this.destroyRef));
12434
- this.reset$ = this.eventBus.on('form-reset').pipe(map$1(() => undefined), takeUntilDestroyed(this.destroyRef));
12435
- this.cleared$ = this.eventBus.on('form-clear').pipe(map$1(() => undefined), takeUntilDestroyed(this.destroyRef));
13204
+ this.reset$ = this.eventBus.on('form-reset').pipe(map(() => undefined), takeUntilDestroyed(this.destroyRef));
13205
+ this.cleared$ = this.eventBus.on('form-clear').pipe(map(() => undefined), takeUntilDestroyed(this.destroyRef));
12436
13206
  // Create the state machine eagerly
12437
13207
  this.machine = createFormStateMachine({
12438
13208
  injector: this.injector,
@@ -12602,10 +13372,10 @@ class FormStateManager {
12602
13372
  ngOnDestroy() {
12603
13373
  this.machine.dispatch({ type: Action.Destroy });
12604
13374
  }
12605
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FormStateManager, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
12606
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FormStateManager });
13375
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: FormStateManager, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
13376
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: FormStateManager });
12607
13377
  }
12608
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FormStateManager, decorators: [{
13378
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: FormStateManager, decorators: [{
12609
13379
  type: Injectable
12610
13380
  }], ctorParameters: () => [] });
12611
13381
 
@@ -12876,6 +13646,11 @@ function provideDynamicFormDI() {
12876
13646
  useFactory: (stateManager) => computed(() => stateManager.activeConfig()?.defaultProps),
12877
13647
  deps: [FormStateManager],
12878
13648
  },
13649
+ {
13650
+ provide: DEFAULT_WRAPPERS,
13651
+ useFactory: (stateManager) => computed(() => stateManager.activeConfig()?.defaultWrappers),
13652
+ deps: [FormStateManager],
13653
+ },
12879
13654
  {
12880
13655
  provide: DEFAULT_VALIDATION_MESSAGES,
12881
13656
  useFactory: (stateManager) => computed(() => stateManager.activeConfig()?.defaultValidationMessages),
@@ -13006,10 +13781,10 @@ class EventDispatcher {
13006
13781
  disconnect() {
13007
13782
  this.bus = undefined;
13008
13783
  }
13009
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: EventDispatcher, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
13010
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: EventDispatcher });
13784
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: EventDispatcher, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
13785
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: EventDispatcher });
13011
13786
  }
13012
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: EventDispatcher, decorators: [{
13787
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: EventDispatcher, decorators: [{
13013
13788
  type: Injectable
13014
13789
  }] });
13015
13790
 
@@ -13219,8 +13994,8 @@ class DynamicForm {
13219
13994
  error: (err) => this.logger.error('Submission handler error', err),
13220
13995
  });
13221
13996
  }
13222
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: DynamicForm, deps: [], target: i0.ɵɵFactoryTarget.Component });
13223
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.6", type: DynamicForm, isStandalone: true, selector: "form[dynamic-form]", inputs: { config: { classPropertyName: "config", publicName: "dynamic-form", isSignal: true, isRequired: true, transformFunction: null }, formOptions: { classPropertyName: "formOptions", publicName: "formOptions", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", validityChange: "validityChange", dirtyChange: "dirtyChange", submitted: "submitted", reset: "reset", cleared: "cleared", events: "events", initialized: "initialized", onPageChange: "onPageChange", onPageNavigationStateChange: "onPageNavigationStateChange" }, host: { attributes: { "novalidate": "" }, listeners: { "submit": "onNativeSubmit($event)" }, properties: { "class.disabled": "disabled()", "class.df-form-paged": "formModeDetection().mode === \"paged\"", "class.df-form-non-paged": "formModeDetection().mode === \"non-paged\"", "attr.data-form-mode": "formModeDetection().mode" }, classAttribute: "df-dynamic-form df-form" }, providers: [provideDynamicFormDI()], ngImport: i0, template: `
13997
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DynamicForm, deps: [], target: i0.ɵɵFactoryTarget.Component });
13998
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: DynamicForm, isStandalone: true, selector: "form[dynamic-form]", inputs: { config: { classPropertyName: "config", publicName: "dynamic-form", isSignal: true, isRequired: true, transformFunction: null }, formOptions: { classPropertyName: "formOptions", publicName: "formOptions", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", validityChange: "validityChange", dirtyChange: "dirtyChange", submitted: "submitted", reset: "reset", cleared: "cleared", events: "events", initialized: "initialized", onPageChange: "onPageChange", onPageNavigationStateChange: "onPageNavigationStateChange" }, host: { attributes: { "novalidate": "" }, listeners: { "submit": "onNativeSubmit($event)" }, properties: { "class.disabled": "disabled()", "class.df-form-paged": "formModeDetection().mode === \"paged\"", "class.df-form-non-paged": "formModeDetection().mode === \"non-paged\"", "attr.data-form-mode": "formModeDetection().mode" }, classAttribute: "df-dynamic-form df-form" }, providers: [provideDynamicFormDI()], ngImport: i0, template: `
13224
13999
  @if (shouldRender()) {
13225
14000
  @switch (formModeDetection().mode) {
13226
14001
  @case ('paged') {
@@ -13228,14 +14003,7 @@ class DynamicForm {
13228
14003
  }
13229
14004
  @case ('non-paged') {
13230
14005
  @for (field of resolvedFields(); track field.key) {
13231
- <ng-container
13232
- *ngComponentOutlet="
13233
- field.component;
13234
- injector: field.injector;
13235
- environmentInjector: environmentInjector;
13236
- inputs: field.inputs()
13237
- "
13238
- />
14006
+ <ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
13239
14007
  }
13240
14008
  }
13241
14009
  @default {
@@ -13243,11 +14011,11 @@ class DynamicForm {
13243
14011
  }
13244
14012
  }
13245
14013
  }
13246
- `, isInline: true, styles: [":host,.df-form{--df-grid-columns: 12;--df-grid-gap: .5rem;--df-grid-row-gap: .5rem;--df-breakpoint-sm: 576px;--df-breakpoint-md: 768px;--df-breakpoint-lg: 992px;--df-breakpoint-xl: 1200px;--df-grid-gap-sm: .5rem;--df-grid-gap-md: .5rem;--df-grid-gap-lg: .5rem;--df-grid-gap-xl: .5rem;--df-grid-row-gap-sm: .5rem;--df-grid-row-gap-md: .5rem;--df-grid-row-gap-lg: .5rem;--df-grid-row-gap-xl: .5rem;--df-array-item-gap: var(--df-grid-row-gap);--df-group-gap: var(--df-grid-gap);--df-group-padding: var(--df-grid-gap)}.df-form{display:grid;grid-template-columns:1fr;gap:var(--df-grid-row-gap);width:100%}.df-form>*{grid-column:1/-1}.df-row{display:grid;grid-template-columns:repeat(var(--df-grid-columns, 12),1fr);gap:var(--df-grid-gap);align-items:start;width:100%}.df-row>*:not([class*=df-col-]){grid-column:1/-1}.df-col-1{grid-column:span 1}.df-col-2{grid-column:span 2}.df-col-3{grid-column:span 3}.df-col-4{grid-column:span 4}.df-col-5{grid-column:span 5}.df-col-6{grid-column:span 6}.df-col-7{grid-column:span 7}.df-col-8{grid-column:span 8}.df-col-9{grid-column:span 9}.df-col-10{grid-column:span 10}.df-col-11{grid-column:span 11}.df-col-12{grid-column:span 12}.df-col-auto{grid-column:span auto;width:auto}.df-col-full{grid-column:1/-1}.df-col-start-1{grid-column-start:1}.df-col-start-2{grid-column-start:2}.df-col-start-3{grid-column-start:3}.df-col-start-4{grid-column-start:4}.df-col-start-5{grid-column-start:5}.df-col-start-6{grid-column-start:6}.df-col-start-7{grid-column-start:7}.df-col-start-8{grid-column-start:8}.df-col-start-9{grid-column-start:9}.df-col-start-10{grid-column-start:10}.df-col-start-11{grid-column-start:11}.df-col-start-12{grid-column-start:12}.df-col-end-1{grid-column-end:1}.df-col-end-2{grid-column-end:2}.df-col-end-3{grid-column-end:3}.df-col-end-4{grid-column-end:4}.df-col-end-5{grid-column-end:5}.df-col-end-6{grid-column-end:6}.df-col-end-7{grid-column-end:7}.df-col-end-8{grid-column-end:8}.df-col-end-9{grid-column-end:9}.df-col-end-10{grid-column-end:10}.df-col-end-11{grid-column-end:11}.df-col-end-12{grid-column-end:12}.df-col-end-13{grid-column-end:13}@media(max-width:576px){.df-form{--df-grid-gap: var(--df-grid-gap-sm);--df-grid-row-gap: var(--df-grid-row-gap-sm)}.df-row{grid-template-columns:1fr}.df-row>*{grid-column:1/-1!important}.df-row.df-row-mobile-keep-cols{grid-template-columns:repeat(var(--df-grid-columns),1fr)}.df-row.df-row-mobile-keep-cols>*{grid-column:revert!important}}@media(min-width:577px)and (max-width:768px){.df-form{--df-grid-gap: var(--df-grid-gap-md);--df-grid-row-gap: var(--df-grid-row-gap-md)}.df-row{--df-grid-columns: 6}.df-col-sm-1{grid-column:span 1}.df-col-sm-2{grid-column:span 2}.df-col-sm-3{grid-column:span 3}.df-col-sm-4{grid-column:span 4}.df-col-sm-5{grid-column:span 5}.df-col-sm-6{grid-column:span 6}.df-col-sm-full{grid-column:1/-1}}@media(min-width:769px)and (max-width:992px){.df-form{--df-grid-gap: var(--df-grid-gap-lg);--df-grid-row-gap: var(--df-grid-row-gap-lg)}.df-col-md-1{grid-column:span 1}.df-col-md-2{grid-column:span 2}.df-col-md-3{grid-column:span 3}.df-col-md-4{grid-column:span 4}.df-col-md-5{grid-column:span 5}.df-col-md-6{grid-column:span 6}.df-col-md-7{grid-column:span 7}.df-col-md-8{grid-column:span 8}.df-col-md-9{grid-column:span 9}.df-col-md-10{grid-column:span 10}.df-col-md-11{grid-column:span 11}.df-col-md-12{grid-column:span 12}.df-col-md-full{grid-column:1/-1}}@media(min-width:993px){.df-form{--df-grid-gap: var(--df-grid-gap-xl);--df-grid-row-gap: var(--df-grid-row-gap-xl)}.df-col-lg-1{grid-column:span 1}.df-col-lg-2{grid-column:span 2}.df-col-lg-3{grid-column:span 3}.df-col-lg-4{grid-column:span 4}.df-col-lg-5{grid-column:span 5}.df-col-lg-6{grid-column:span 6}.df-col-lg-7{grid-column:span 7}.df-col-lg-8{grid-column:span 8}.df-col-lg-9{grid-column:span 9}.df-col-lg-10{grid-column:span 10}.df-col-lg-11{grid-column:span 11}.df-col-lg-12{grid-column:span 12}.df-col-lg-full{grid-column:1/-1}}.df-gap-none{--df-grid-gap: 0}.df-gap-xs{--df-grid-gap: .25rem}.df-gap-sm{--df-grid-gap: .5rem}.df-gap-md{--df-grid-gap: 1rem}.df-gap-lg{--df-grid-gap: 1.5rem}.df-gap-xl{--df-grid-gap: 2rem}.df-row-gap-none{--df-grid-row-gap: 0}.df-row-gap-xs{--df-grid-row-gap: .25rem}.df-row-gap-sm{--df-grid-row-gap: .5rem}.df-row-gap-md{--df-grid-row-gap: 1rem}.df-row-gap-lg{--df-grid-row-gap: 1.5rem}.df-row-gap-xl{--df-grid-row-gap: 2rem}.df-field{display:block;width:100%;min-width:0;overflow:hidden;margin:0}.df-group,.df-page{display:block;width:100%}.df-form.disabled,.df-row.disabled,.df-field.disabled{opacity:.6;pointer-events:none}.df-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.df-sr-only-focusable:focus,.df-sr-only-focusable:active{position:static;width:auto;height:auto;padding:inherit;margin:inherit;overflow:visible;clip:auto;white-space:normal}.df-live-region{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.df-form :focus-visible{outline:2px solid var(--df-focus-color, #005fcc);outline-offset:2px}.df-group:focus-within{outline:1px dashed var(--df-focus-color, #005fcc);outline-offset:4px}:host{display:grid;grid-template-columns:1fr;gap:var(--df-grid-row-gap);width:100%}:host>*{grid-column:1/-1}:host.df-form-spacing-custom{--df-grid-row-gap: var(--form-custom-spacing, 1.5rem)}:host.df-form-gap-none{--df-grid-row-gap: 0}:host.df-form-gap-xs{--df-grid-row-gap: .25rem}:host.df-form-gap-sm{--df-grid-row-gap: .5rem}:host.df-form-gap-md{--df-grid-row-gap: 1rem}:host.df-form-gap-lg{--df-grid-row-gap: 1.5rem}:host.df-form-gap-xl{--df-grid-row-gap: 2rem}:host.df-form-paged{--df-grid-row-gap: 0}\n"], dependencies: [{ kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }, { kind: "component", type: PageOrchestratorComponent, selector: "div[page-orchestrator]", inputs: ["pageFields", "form", "fieldSignalContext"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
14014
+ `, isInline: true, styles: [":host,.df-form{--df-grid-columns: 12;--df-grid-gap: .5rem;--df-grid-row-gap: .5rem;--df-breakpoint-sm: 576px;--df-breakpoint-md: 768px;--df-breakpoint-lg: 992px;--df-breakpoint-xl: 1200px;--df-grid-gap-sm: .5rem;--df-grid-gap-md: .5rem;--df-grid-gap-lg: .5rem;--df-grid-gap-xl: .5rem;--df-grid-row-gap-sm: .5rem;--df-grid-row-gap-md: .5rem;--df-grid-row-gap-lg: .5rem;--df-grid-row-gap-xl: .5rem;--df-array-item-gap: var(--df-grid-row-gap);--df-group-gap: var(--df-grid-gap);--df-group-padding: var(--df-grid-gap)}.df-form{display:grid;grid-template-columns:1fr;gap:var(--df-grid-row-gap);width:100%}.df-form>*{grid-column:1/-1}.df-row{display:grid;grid-template-columns:repeat(var(--df-grid-columns, 12),1fr);gap:var(--df-grid-gap);align-items:start;width:100%}.df-row>*:not([class*=df-col-]){grid-column:1/-1}.df-col-1{grid-column:span 1}.df-col-2{grid-column:span 2}.df-col-3{grid-column:span 3}.df-col-4{grid-column:span 4}.df-col-5{grid-column:span 5}.df-col-6{grid-column:span 6}.df-col-7{grid-column:span 7}.df-col-8{grid-column:span 8}.df-col-9{grid-column:span 9}.df-col-10{grid-column:span 10}.df-col-11{grid-column:span 11}.df-col-12{grid-column:span 12}.df-col-auto{grid-column:span auto;width:auto}.df-col-full{grid-column:1/-1}.df-col-start-1{grid-column-start:1}.df-col-start-2{grid-column-start:2}.df-col-start-3{grid-column-start:3}.df-col-start-4{grid-column-start:4}.df-col-start-5{grid-column-start:5}.df-col-start-6{grid-column-start:6}.df-col-start-7{grid-column-start:7}.df-col-start-8{grid-column-start:8}.df-col-start-9{grid-column-start:9}.df-col-start-10{grid-column-start:10}.df-col-start-11{grid-column-start:11}.df-col-start-12{grid-column-start:12}.df-col-end-1{grid-column-end:1}.df-col-end-2{grid-column-end:2}.df-col-end-3{grid-column-end:3}.df-col-end-4{grid-column-end:4}.df-col-end-5{grid-column-end:5}.df-col-end-6{grid-column-end:6}.df-col-end-7{grid-column-end:7}.df-col-end-8{grid-column-end:8}.df-col-end-9{grid-column-end:9}.df-col-end-10{grid-column-end:10}.df-col-end-11{grid-column-end:11}.df-col-end-12{grid-column-end:12}.df-col-end-13{grid-column-end:13}@media(max-width:576px){.df-form{--df-grid-gap: var(--df-grid-gap-sm);--df-grid-row-gap: var(--df-grid-row-gap-sm)}.df-row{grid-template-columns:1fr}.df-row>*{grid-column:1/-1!important}.df-row.df-row-mobile-keep-cols{grid-template-columns:repeat(var(--df-grid-columns),1fr)}.df-row.df-row-mobile-keep-cols>*{grid-column:revert!important}}@media(min-width:577px)and (max-width:768px){.df-form{--df-grid-gap: var(--df-grid-gap-md);--df-grid-row-gap: var(--df-grid-row-gap-md)}.df-row{--df-grid-columns: 6}.df-col-sm-1{grid-column:span 1}.df-col-sm-2{grid-column:span 2}.df-col-sm-3{grid-column:span 3}.df-col-sm-4{grid-column:span 4}.df-col-sm-5{grid-column:span 5}.df-col-sm-6{grid-column:span 6}.df-col-sm-full{grid-column:1/-1}}@media(min-width:769px)and (max-width:992px){.df-form{--df-grid-gap: var(--df-grid-gap-lg);--df-grid-row-gap: var(--df-grid-row-gap-lg)}.df-col-md-1{grid-column:span 1}.df-col-md-2{grid-column:span 2}.df-col-md-3{grid-column:span 3}.df-col-md-4{grid-column:span 4}.df-col-md-5{grid-column:span 5}.df-col-md-6{grid-column:span 6}.df-col-md-7{grid-column:span 7}.df-col-md-8{grid-column:span 8}.df-col-md-9{grid-column:span 9}.df-col-md-10{grid-column:span 10}.df-col-md-11{grid-column:span 11}.df-col-md-12{grid-column:span 12}.df-col-md-full{grid-column:1/-1}}@media(min-width:993px){.df-form{--df-grid-gap: var(--df-grid-gap-xl);--df-grid-row-gap: var(--df-grid-row-gap-xl)}.df-col-lg-1{grid-column:span 1}.df-col-lg-2{grid-column:span 2}.df-col-lg-3{grid-column:span 3}.df-col-lg-4{grid-column:span 4}.df-col-lg-5{grid-column:span 5}.df-col-lg-6{grid-column:span 6}.df-col-lg-7{grid-column:span 7}.df-col-lg-8{grid-column:span 8}.df-col-lg-9{grid-column:span 9}.df-col-lg-10{grid-column:span 10}.df-col-lg-11{grid-column:span 11}.df-col-lg-12{grid-column:span 12}.df-col-lg-full{grid-column:1/-1}}.df-gap-none{--df-grid-gap: 0}.df-gap-xs{--df-grid-gap: .25rem}.df-gap-sm{--df-grid-gap: .5rem}.df-gap-md{--df-grid-gap: 1rem}.df-gap-lg{--df-grid-gap: 1.5rem}.df-gap-xl{--df-grid-gap: 2rem}.df-row-gap-none{--df-grid-row-gap: 0}.df-row-gap-xs{--df-grid-row-gap: .25rem}.df-row-gap-sm{--df-grid-row-gap: .5rem}.df-row-gap-md{--df-grid-row-gap: 1rem}.df-row-gap-lg{--df-grid-row-gap: 1.5rem}.df-row-gap-xl{--df-grid-row-gap: 2rem}.df-field{display:block;width:100%;min-width:0;overflow:hidden;margin:0}.df-group,.df-page{display:block;width:100%}.df-form.disabled,.df-row.disabled,.df-field.disabled{opacity:.6;pointer-events:none}.df-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.df-sr-only-focusable:focus,.df-sr-only-focusable:active{position:static;width:auto;height:auto;padding:inherit;margin:inherit;overflow:visible;clip:auto;white-space:normal}.df-live-region{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.df-form :focus-visible{outline:2px solid var(--df-focus-color, #005fcc);outline-offset:2px}.df-group:focus-within{outline:1px dashed var(--df-focus-color, #005fcc);outline-offset:4px}:host{display:grid;grid-template-columns:1fr;gap:var(--df-grid-row-gap);width:100%}:host>*{grid-column:1/-1}:host.df-form-spacing-custom{--df-grid-row-gap: var(--form-custom-spacing, 1.5rem)}:host.df-form-gap-none{--df-grid-row-gap: 0}:host.df-form-gap-xs{--df-grid-row-gap: .25rem}:host.df-form-gap-sm{--df-grid-row-gap: .5rem}:host.df-form-gap-md{--df-grid-row-gap: 1rem}:host.df-form-gap-lg{--df-grid-row-gap: 1.5rem}:host.df-form-gap-xl{--df-grid-row-gap: 2rem}:host.df-form-paged{--df-grid-row-gap: 0}\n"], dependencies: [{ kind: "directive", type: DfFieldOutlet, selector: "[dfFieldOutlet]", inputs: ["dfFieldOutlet", "dfFieldOutletEnvironmentInjector"] }, { kind: "component", type: PageOrchestratorComponent, selector: "div[page-orchestrator]", inputs: ["pageFields", "form", "fieldSignalContext"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
13247
14015
  }
13248
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: DynamicForm, decorators: [{
14016
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DynamicForm, decorators: [{
13249
14017
  type: Component,
13250
- args: [{ selector: 'form[dynamic-form]', imports: [NgComponentOutlet, PageOrchestratorComponent], template: `
14018
+ args: [{ selector: 'form[dynamic-form]', imports: [DfFieldOutlet, PageOrchestratorComponent], template: `
13251
14019
  @if (shouldRender()) {
13252
14020
  @switch (formModeDetection().mode) {
13253
14021
  @case ('paged') {
@@ -13255,14 +14023,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImpor
13255
14023
  }
13256
14024
  @case ('non-paged') {
13257
14025
  @for (field of resolvedFields(); track field.key) {
13258
- <ng-container
13259
- *ngComponentOutlet="
13260
- field.component;
13261
- injector: field.injector;
13262
- environmentInjector: environmentInjector;
13263
- inputs: field.inputs()
13264
- "
13265
- />
14026
+ <ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
13266
14027
  }
13267
14028
  }
13268
14029
  @default {
@@ -13281,7 +14042,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImpor
13281
14042
  }, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host,.df-form{--df-grid-columns: 12;--df-grid-gap: .5rem;--df-grid-row-gap: .5rem;--df-breakpoint-sm: 576px;--df-breakpoint-md: 768px;--df-breakpoint-lg: 992px;--df-breakpoint-xl: 1200px;--df-grid-gap-sm: .5rem;--df-grid-gap-md: .5rem;--df-grid-gap-lg: .5rem;--df-grid-gap-xl: .5rem;--df-grid-row-gap-sm: .5rem;--df-grid-row-gap-md: .5rem;--df-grid-row-gap-lg: .5rem;--df-grid-row-gap-xl: .5rem;--df-array-item-gap: var(--df-grid-row-gap);--df-group-gap: var(--df-grid-gap);--df-group-padding: var(--df-grid-gap)}.df-form{display:grid;grid-template-columns:1fr;gap:var(--df-grid-row-gap);width:100%}.df-form>*{grid-column:1/-1}.df-row{display:grid;grid-template-columns:repeat(var(--df-grid-columns, 12),1fr);gap:var(--df-grid-gap);align-items:start;width:100%}.df-row>*:not([class*=df-col-]){grid-column:1/-1}.df-col-1{grid-column:span 1}.df-col-2{grid-column:span 2}.df-col-3{grid-column:span 3}.df-col-4{grid-column:span 4}.df-col-5{grid-column:span 5}.df-col-6{grid-column:span 6}.df-col-7{grid-column:span 7}.df-col-8{grid-column:span 8}.df-col-9{grid-column:span 9}.df-col-10{grid-column:span 10}.df-col-11{grid-column:span 11}.df-col-12{grid-column:span 12}.df-col-auto{grid-column:span auto;width:auto}.df-col-full{grid-column:1/-1}.df-col-start-1{grid-column-start:1}.df-col-start-2{grid-column-start:2}.df-col-start-3{grid-column-start:3}.df-col-start-4{grid-column-start:4}.df-col-start-5{grid-column-start:5}.df-col-start-6{grid-column-start:6}.df-col-start-7{grid-column-start:7}.df-col-start-8{grid-column-start:8}.df-col-start-9{grid-column-start:9}.df-col-start-10{grid-column-start:10}.df-col-start-11{grid-column-start:11}.df-col-start-12{grid-column-start:12}.df-col-end-1{grid-column-end:1}.df-col-end-2{grid-column-end:2}.df-col-end-3{grid-column-end:3}.df-col-end-4{grid-column-end:4}.df-col-end-5{grid-column-end:5}.df-col-end-6{grid-column-end:6}.df-col-end-7{grid-column-end:7}.df-col-end-8{grid-column-end:8}.df-col-end-9{grid-column-end:9}.df-col-end-10{grid-column-end:10}.df-col-end-11{grid-column-end:11}.df-col-end-12{grid-column-end:12}.df-col-end-13{grid-column-end:13}@media(max-width:576px){.df-form{--df-grid-gap: var(--df-grid-gap-sm);--df-grid-row-gap: var(--df-grid-row-gap-sm)}.df-row{grid-template-columns:1fr}.df-row>*{grid-column:1/-1!important}.df-row.df-row-mobile-keep-cols{grid-template-columns:repeat(var(--df-grid-columns),1fr)}.df-row.df-row-mobile-keep-cols>*{grid-column:revert!important}}@media(min-width:577px)and (max-width:768px){.df-form{--df-grid-gap: var(--df-grid-gap-md);--df-grid-row-gap: var(--df-grid-row-gap-md)}.df-row{--df-grid-columns: 6}.df-col-sm-1{grid-column:span 1}.df-col-sm-2{grid-column:span 2}.df-col-sm-3{grid-column:span 3}.df-col-sm-4{grid-column:span 4}.df-col-sm-5{grid-column:span 5}.df-col-sm-6{grid-column:span 6}.df-col-sm-full{grid-column:1/-1}}@media(min-width:769px)and (max-width:992px){.df-form{--df-grid-gap: var(--df-grid-gap-lg);--df-grid-row-gap: var(--df-grid-row-gap-lg)}.df-col-md-1{grid-column:span 1}.df-col-md-2{grid-column:span 2}.df-col-md-3{grid-column:span 3}.df-col-md-4{grid-column:span 4}.df-col-md-5{grid-column:span 5}.df-col-md-6{grid-column:span 6}.df-col-md-7{grid-column:span 7}.df-col-md-8{grid-column:span 8}.df-col-md-9{grid-column:span 9}.df-col-md-10{grid-column:span 10}.df-col-md-11{grid-column:span 11}.df-col-md-12{grid-column:span 12}.df-col-md-full{grid-column:1/-1}}@media(min-width:993px){.df-form{--df-grid-gap: var(--df-grid-gap-xl);--df-grid-row-gap: var(--df-grid-row-gap-xl)}.df-col-lg-1{grid-column:span 1}.df-col-lg-2{grid-column:span 2}.df-col-lg-3{grid-column:span 3}.df-col-lg-4{grid-column:span 4}.df-col-lg-5{grid-column:span 5}.df-col-lg-6{grid-column:span 6}.df-col-lg-7{grid-column:span 7}.df-col-lg-8{grid-column:span 8}.df-col-lg-9{grid-column:span 9}.df-col-lg-10{grid-column:span 10}.df-col-lg-11{grid-column:span 11}.df-col-lg-12{grid-column:span 12}.df-col-lg-full{grid-column:1/-1}}.df-gap-none{--df-grid-gap: 0}.df-gap-xs{--df-grid-gap: .25rem}.df-gap-sm{--df-grid-gap: .5rem}.df-gap-md{--df-grid-gap: 1rem}.df-gap-lg{--df-grid-gap: 1.5rem}.df-gap-xl{--df-grid-gap: 2rem}.df-row-gap-none{--df-grid-row-gap: 0}.df-row-gap-xs{--df-grid-row-gap: .25rem}.df-row-gap-sm{--df-grid-row-gap: .5rem}.df-row-gap-md{--df-grid-row-gap: 1rem}.df-row-gap-lg{--df-grid-row-gap: 1.5rem}.df-row-gap-xl{--df-grid-row-gap: 2rem}.df-field{display:block;width:100%;min-width:0;overflow:hidden;margin:0}.df-group,.df-page{display:block;width:100%}.df-form.disabled,.df-row.disabled,.df-field.disabled{opacity:.6;pointer-events:none}.df-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.df-sr-only-focusable:focus,.df-sr-only-focusable:active{position:static;width:auto;height:auto;padding:inherit;margin:inherit;overflow:visible;clip:auto;white-space:normal}.df-live-region{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.df-form :focus-visible{outline:2px solid var(--df-focus-color, #005fcc);outline-offset:2px}.df-group:focus-within{outline:1px dashed var(--df-focus-color, #005fcc);outline-offset:4px}:host{display:grid;grid-template-columns:1fr;gap:var(--df-grid-row-gap);width:100%}:host>*{grid-column:1/-1}:host.df-form-spacing-custom{--df-grid-row-gap: var(--form-custom-spacing, 1.5rem)}:host.df-form-gap-none{--df-grid-row-gap: 0}:host.df-form-gap-xs{--df-grid-row-gap: .25rem}:host.df-form-gap-sm{--df-grid-row-gap: .5rem}:host.df-form-gap-md{--df-grid-row-gap: 1rem}:host.df-form-gap-lg{--df-grid-row-gap: 1.5rem}:host.df-form-gap-xl{--df-grid-row-gap: 2rem}:host.df-form-paged{--df-grid-row-gap: 0}\n"] }]
13282
14043
  }], ctorParameters: () => [], propDecorators: { config: [{ type: i0.Input, args: [{ isSignal: true, alias: "dynamic-form", required: true }] }], formOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "formOptions", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], validityChange: [{ type: i0.Output, args: ["validityChange"] }], dirtyChange: [{ type: i0.Output, args: ["dirtyChange"] }], submitted: [{ type: i0.Output, args: ["submitted"] }], reset: [{ type: i0.Output, args: ["reset"] }], cleared: [{ type: i0.Output, args: ["cleared"] }], events: [{ type: i0.Output, args: ["events"] }], initialized: [{ type: i0.Output, args: ["initialized"] }], onPageChange: [{ type: i0.Output, args: ["onPageChange"] }], onPageNavigationStateChange: [{ type: i0.Output, args: ["onPageNavigationStateChange"] }] } });
13283
14044
  /**
13284
- * Recursively counts container fields (page, row, group, array) in a field tree.
14045
+ * Recursively counts container fields (page, row, group, array, wrapper) in a field tree.
13285
14046
  * Descends into container children including array item templates to ensure
13286
14047
  * nested containers are counted for accurate initialization tracking.
13287
14048
  */
@@ -13698,24 +14459,28 @@ function groupFieldMapper(fieldDef) {
13698
14459
  });
13699
14460
  }
13700
14461
 
14462
+ const ROW_WRAPPERS = [{ type: 'row' }];
13701
14463
  /**
13702
- * Maps a row field definition to component inputs.
14464
+ * Maps a row field definition to container component inputs.
13703
14465
  *
13704
- * Row components are layout containers that don't change the form shape.
13705
- * The row component will inject FIELD_SIGNAL_CONTEXT directly.
14466
+ * `row` is a virtual field type: it resolves to `ContainerFieldComponent` with a
14467
+ * synthesized `{ type: 'row' }` wrapper, so the user-facing config stays
14468
+ * `{ type: 'row', fields: [...] }` while the runtime uses the container +
14469
+ * wrapper pipeline.
13706
14470
  *
13707
- * Supports hidden state resolution via `logic` array or static `hidden` property.
13708
- *
13709
- * @param fieldDef The row field definition
13710
- * @returns Signal containing Record of input names to values for ngComponentOutlet
14471
+ * Supports hidden state resolution via `logic` array or static `hidden` property.
13711
14472
  */
13712
14473
  function rowFieldMapper(fieldDef) {
13713
14474
  const rootFormRegistry = inject(RootFormRegistryService);
13714
14475
  const className = buildClassName(fieldDef);
13715
14476
  return computed(() => {
14477
+ // Rebuilt each emission so logic-driven updates to fieldDef (e.g. `disabled`)
14478
+ // flow through. Shallow spread is safe while RowField has no nested
14479
+ // mutable props; add a deep-clone if that stops being true.
14480
+ const containerField = { ...fieldDef, wrappers: ROW_WRAPPERS };
13716
14481
  const inputs = {
13717
14482
  key: fieldDef.key,
13718
- field: fieldDef,
14483
+ field: containerField,
13719
14484
  ...(className !== undefined && { className }),
13720
14485
  };
13721
14486
  applyHiddenLogic(inputs, fieldDef, rootFormRegistry);
@@ -13774,6 +14539,38 @@ function textFieldMapper(fieldDef) {
13774
14539
  });
13775
14540
  }
13776
14541
 
14542
+ /**
14543
+ * Maps a container field definition to component inputs.
14544
+ *
14545
+ * Container components are layout containers that don't change the form shape.
14546
+ * The container component will inject FIELD_SIGNAL_CONTEXT directly.
14547
+ *
14548
+ * Supports hidden state resolution via `logic` array or static `hidden` property.
14549
+ *
14550
+ * @param fieldDef The container field definition
14551
+ * @returns Signal containing Record of input names to values for ngComponentOutlet
14552
+ */
14553
+ function containerFieldMapper(fieldDef) {
14554
+ const rootFormRegistry = inject(RootFormRegistryService);
14555
+ const className = buildClassName(fieldDef);
14556
+ return computed(() => {
14557
+ // Nullify wrapper-related props on the field passed to the container
14558
+ // component — DfFieldOutlet handles field-level wrappers at the outlet
14559
+ // level. Passing them here would cause double-wrapping since the
14560
+ // container's internal wrapper chain also resolves from `this.field()`.
14561
+ // We set wrappers to undefined (not stripped) so the object identity
14562
+ // stays close to the original fieldDef shape.
14563
+ const containerField = { ...fieldDef, wrappers: undefined, skipAutoWrappers: undefined, skipDefaultWrappers: undefined };
14564
+ const inputs = {
14565
+ key: fieldDef.key,
14566
+ field: containerField,
14567
+ ...(className !== undefined && { className }),
14568
+ };
14569
+ applyHiddenLogic(inputs, fieldDef, rootFormRegistry);
14570
+ return inputs;
14571
+ });
14572
+ }
14573
+
13777
14574
  /**
13778
14575
  * Built-in field types provided by the dynamic form library.
13779
14576
  *
@@ -13805,6 +14602,14 @@ function textFieldMapper(fieldDef) {
13805
14602
  * { type: 'page', key: 'step1', fields: [...] }
13806
14603
  * ```
13807
14604
  */
14605
+ /**
14606
+ * Base field type definitions for fields that don't use the `field` input.
14607
+ * Used by display-only fields like `text` that render immediately without
14608
+ * waiting for form value integration.
14609
+ */
14610
+ const DISPLAY_FIELD_TYPES_BASE = {
14611
+ renderReadyWhen: [],
14612
+ };
13808
14613
  /**
13809
14614
  * Built-in field types provided by the dynamic form library.
13810
14615
  *
@@ -13815,7 +14620,10 @@ function textFieldMapper(fieldDef) {
13815
14620
  const BUILT_IN_FIELDS = [
13816
14621
  {
13817
14622
  name: 'row',
13818
- loadComponent: () => Promise.resolve().then(function () { return rowField_component; }),
14623
+ // `row` is a virtual field type: it maps to ContainerFieldComponent, and
14624
+ // the rowFieldMapper injects a `{ type: 'row' }` wrapper so the container
14625
+ // renders the flex/grid layout via RowWrapperComponent.
14626
+ loadComponent: () => Promise.resolve().then(function () { return containerField_component; }),
13819
14627
  mapper: rowFieldMapper,
13820
14628
  valueHandling: 'flatten',
13821
14629
  },
@@ -13833,23 +14641,90 @@ const BUILT_IN_FIELDS = [
13833
14641
  },
13834
14642
  {
13835
14643
  name: 'page',
13836
- loadComponent: () => import('./ng-forge-dynamic-forms-page-field.component-Cen4ArED.mjs'),
14644
+ loadComponent: () => import('./ng-forge-dynamic-forms-page-field.component-CEN5u1dd.mjs'),
13837
14645
  mapper: pageFieldMapper,
13838
14646
  valueHandling: 'flatten',
13839
14647
  },
13840
14648
  {
13841
14649
  name: 'text',
13842
- loadComponent: () => import('./ng-forge-dynamic-forms-text-field.component-cE1EUmYR.mjs'),
14650
+ loadComponent: () => import('./ng-forge-dynamic-forms-text-field.component-9OIByjZZ.mjs'),
13843
14651
  mapper: textFieldMapper,
13844
14652
  valueHandling: 'exclude',
14653
+ ...DISPLAY_FIELD_TYPES_BASE,
13845
14654
  },
13846
14655
  {
13847
14656
  name: 'hidden',
13848
14657
  // Componentless field - no loadComponent or mapper needed
13849
14658
  valueHandling: 'include',
13850
14659
  },
14660
+ {
14661
+ name: 'container',
14662
+ loadComponent: () => Promise.resolve().then(function () { return containerField_component; }),
14663
+ mapper: containerFieldMapper,
14664
+ valueHandling: 'flatten',
14665
+ },
14666
+ ];
14667
+ const BUILT_IN_WRAPPERS = [
14668
+ {
14669
+ wrapperName: 'css',
14670
+ loadComponent: () => import('./ng-forge-dynamic-forms-css-wrapper.component-Civn5kLJ.mjs'),
14671
+ },
14672
+ {
14673
+ wrapperName: 'row',
14674
+ loadComponent: () => import('./ng-forge-dynamic-forms-row-wrapper.component-BIvEUoia.mjs'),
14675
+ },
13851
14676
  ];
13852
14677
 
14678
+ /**
14679
+ * Bundle wrapper registrations into a single object that can be passed to
14680
+ * `provideDynamicForm(...)`.
14681
+ *
14682
+ * @example
14683
+ * ```typescript
14684
+ * const appWrappers = createWrappers(
14685
+ * {
14686
+ * wrapperName: 'section',
14687
+ * loadComponent: () => import('./section-wrapper'),
14688
+ * props: wrapperProps<SectionWrapper>(),
14689
+ * },
14690
+ * {
14691
+ * wrapperName: 'highlight',
14692
+ * loadComponent: () => import('./highlight-wrapper'),
14693
+ * types: ['input', 'select'],
14694
+ * props: wrapperProps<HighlightWrapper>(),
14695
+ * },
14696
+ * );
14697
+ *
14698
+ * declare module '@ng-forge/dynamic-forms' {
14699
+ * interface FieldRegistryWrappers extends InferWrapperRegistry<typeof appWrappers> {}
14700
+ * }
14701
+ *
14702
+ * bootstrapApplication(AppComponent, {
14703
+ * providers: [provideDynamicForm(appWrappers)],
14704
+ * });
14705
+ * ```
14706
+ */
14707
+ function createWrappers(...registrations) {
14708
+ return {
14709
+ ɵkind: 'wrappers',
14710
+ ɵregistrations: registrations,
14711
+ ɵdefinitions: registrations.map(({ wrapperName, loadComponent, types }) => ({
14712
+ wrapperName,
14713
+ loadComponent,
14714
+ types,
14715
+ })),
14716
+ };
14717
+ }
14718
+ /** Type guard for a `WrappersBundle`. */
14719
+ function isWrappersBundle(value) {
14720
+ return (typeof value === 'object' &&
14721
+ value !== null &&
14722
+ 'ɵkind' in value &&
14723
+ value.ɵkind === 'wrappers' &&
14724
+ 'ɵdefinitions' in value &&
14725
+ Array.isArray(value.ɵdefinitions));
14726
+ }
14727
+
13853
14728
  /**
13854
14729
  * Provider function to configure the dynamic form system with field types and options.
13855
14730
  *
@@ -13899,19 +14774,14 @@ const BUILT_IN_FIELDS = [
13899
14774
  * @public
13900
14775
  */
13901
14776
  function provideDynamicForm(...items) {
13902
- // Separate field types from features
13903
- const fieldTypes = items.filter((item) => !isDynamicFormFeature(item));
14777
+ // Separate field types, wrapper types, wrapper bundles, and features
14778
+ const fieldTypes = items.filter((item) => !isDynamicFormFeature(item) && !isWrapperTypeDefinition(item) && !isWrappersBundle(item));
14779
+ const wrapperTypes = items.filter(isWrapperTypeDefinition);
14780
+ const wrapperBundles = items.filter(isWrappersBundle);
13904
14781
  const features = items.filter(isDynamicFormFeature);
13905
14782
  const fields = [...BUILT_IN_FIELDS, ...fieldTypes];
13906
- // Extract config providers from field type arrays
13907
- const configProviders = [];
13908
- fieldTypes.forEach((fieldTypeArray) => {
13909
- const fieldTypeWithConfig = fieldTypeArray;
13910
- if (fieldTypeWithConfig.__configProviders) {
13911
- configProviders.push(...fieldTypeWithConfig.__configProviders);
13912
- }
13913
- });
13914
- // Extract providers from features
14783
+ const wrappers = [...BUILT_IN_WRAPPERS, ...wrapperTypes, ...wrapperBundles.flatMap((bundle) => bundle.ɵdefinitions)];
14784
+ // Extract providers from features (includes config features like material-config, bootstrap-config, etc.)
13915
14785
  const featureProviders = [];
13916
14786
  const hasLoggerFeature = features.some((feature) => feature.ɵkind === 'logger');
13917
14787
  features.forEach((feature) => {
@@ -13938,7 +14808,41 @@ function provideDynamicForm(...items) {
13938
14808
  return registry;
13939
14809
  },
13940
14810
  },
13941
- ...configProviders,
14811
+ // Always provide default Wrapper classes
14812
+ {
14813
+ provide: WRAPPER_REGISTRY,
14814
+ useFactory: () => {
14815
+ const logger = inject(DynamicFormLogger);
14816
+ const registry = new Map();
14817
+ // Add custom wrapper types
14818
+ wrappers.forEach((wrapperType) => {
14819
+ if (registry.has(wrapperType.wrapperName)) {
14820
+ logger.warn(`Wrapper type "${wrapperType.wrapperName}" is already registered. Overwriting.`);
14821
+ }
14822
+ registry.set(wrapperType.wrapperName, wrapperType);
14823
+ });
14824
+ return registry;
14825
+ },
14826
+ },
14827
+ // Pre-computed reverse index for auto-association lookup — built once so
14828
+ // resolveWrappers is O(1) per field render instead of scanning every
14829
+ // registered wrapper.
14830
+ {
14831
+ provide: WRAPPER_AUTO_ASSOCIATIONS,
14832
+ useFactory: () => {
14833
+ const autoMap = new Map();
14834
+ for (const wrapperType of wrappers) {
14835
+ if (!wrapperType.types)
14836
+ continue;
14837
+ for (const fieldType of wrapperType.types) {
14838
+ const existing = autoMap.get(fieldType) ?? [];
14839
+ existing.push({ type: wrapperType.wrapperName });
14840
+ autoMap.set(fieldType, existing);
14841
+ }
14842
+ }
14843
+ return autoMap;
14844
+ },
14845
+ },
13942
14846
  ...featureProviders,
13943
14847
  ]);
13944
14848
  }
@@ -14073,8 +14977,38 @@ function withValueExclusionDefaults(config) {
14073
14977
  return createFeature('value-exclusion', [{ provide: VALUE_EXCLUSION_DEFAULTS, useValue: resolved }]);
14074
14978
  }
14075
14979
 
14980
+ /**
14981
+ * Zero-cost type carrier used in wrapper registrations.
14982
+ *
14983
+ * Returns `undefined` at runtime but is typed as `T`, so TypeScript can
14984
+ * thread a wrapper's config type through `createWrappers(...)` into the
14985
+ * `InferWrapperRegistry<typeof ...>` utility without requiring users to
14986
+ * hand-write the augmentation shape.
14987
+ *
14988
+ * @example
14989
+ * ```typescript
14990
+ * const wrappers = createWrappers(
14991
+ * {
14992
+ * wrapperName: 'section',
14993
+ * loadComponent: () => import('./section-wrapper'),
14994
+ * props: wrapperProps<SectionWrapper>(),
14995
+ * },
14996
+ * );
14997
+ *
14998
+ * declare module '@ng-forge/dynamic-forms' {
14999
+ * interface FieldRegistryWrappers extends InferWrapperRegistry<typeof wrappers> {}
15000
+ * }
15001
+ * ```
15002
+ */
15003
+ function wrapperProps() {
15004
+ return undefined;
15005
+ }
15006
+
14076
15007
  function isValueField(field) {
14077
- return 'value' in field;
15008
+ // `nullable: true` without an explicit `value` is still a value-bearing declaration:
15009
+ // the field resolves to `null` at runtime. Treating it as a value field keeps
15010
+ // downstream narrowing consistent with getFieldDefaultValue's behavior.
15011
+ return 'value' in field || 'nullable' in field;
14078
15012
  }
14079
15013
 
14080
15014
  function isCheckedField(field) {
@@ -14112,10 +15046,13 @@ function isCheckedField(field) {
14112
15046
  * eventBus.dispatch(arrayEvent('contacts').pop()); // Remove last
14113
15047
  * eventBus.dispatch(arrayEvent('contacts').shift()); // Remove first
14114
15048
  * eventBus.dispatch(arrayEvent('contacts').removeAt(2)); // Remove at index
15049
+ *
15050
+ * // Reordering items (no template needed, preserves item identity)
15051
+ * eventBus.dispatch(arrayEvent('contacts').move(0, 2)); // Move first to third
14115
15052
  * ```
14116
15053
  *
14117
15054
  * @param arrayKey - The key of the array field to operate on
14118
- * @returns An object with methods for all 6 array operations
15055
+ * @returns An object with methods for all 7 array operations
14119
15056
  */
14120
15057
  function arrayEvent(arrayKey) {
14121
15058
  return {
@@ -14226,6 +15163,22 @@ function arrayEvent(arrayKey) {
14226
15163
  * ```
14227
15164
  */
14228
15165
  removeAt: (index) => new RemoveAtIndexEvent(arrayKey, index),
15166
+ /**
15167
+ * Move an existing item from one position to another within the array.
15168
+ * This is an atomic reorder — the item is NOT destroyed and recreated.
15169
+ * The resolved component, form value, and stored template are preserved.
15170
+ *
15171
+ * @param from - The current index of the item to move
15172
+ * @param to - The target index to move the item to
15173
+ * @returns A MoveArrayItemEvent to dispatch
15174
+ *
15175
+ * @example
15176
+ * ```typescript
15177
+ * // Move item from index 0 to index 2
15178
+ * eventBus.dispatch(arrayEvent('contacts').move(0, 2));
15179
+ * ```
15180
+ */
15181
+ move: (from, to) => new MoveArrayItemEvent(arrayKey, from, to),
14229
15182
  };
14230
15183
  }
14231
15184
 
@@ -14341,11 +15294,11 @@ function formConfig(config) {
14341
15294
  /**
14342
15295
  * Container field types that do NOT support labels
14343
15296
  */
14344
- const CONTAINER_TYPES = ['group', 'row', 'array'];
15297
+ const CONTAINER_TYPES = ['group', 'row', 'array', 'container'];
14345
15298
  /**
14346
15299
  * Container and page field types that only support 'hidden' logic
14347
15300
  */
14348
- const HIDDEN_ONLY_LOGIC_TYPES = ['group', 'row', 'array', 'page'];
15301
+ const HIDDEN_ONLY_LOGIC_TYPES = ['group', 'row', 'array', 'container', 'page'];
14349
15302
  /**
14350
15303
  * Field types that support options at the field level
14351
15304
  */
@@ -14605,10 +15558,10 @@ class DynamicTextPipe {
14605
15558
  }
14606
15559
  return of(value || '');
14607
15560
  }
14608
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: DynamicTextPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
14609
- static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.2.6", ngImport: i0, type: DynamicTextPipe, isStandalone: true, name: "dynamicText" });
15561
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DynamicTextPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
15562
+ static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.2.8", ngImport: i0, type: DynamicTextPipe, isStandalone: true, name: "dynamicText" });
14610
15563
  }
14611
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: DynamicTextPipe, decorators: [{
15564
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DynamicTextPipe, decorators: [{
14612
15565
  type: Pipe,
14613
15566
  args: [{
14614
15567
  name: 'dynamicText',
@@ -14658,111 +15611,30 @@ function computeContainerHostClasses(containerType, className) {
14658
15611
  * When resolved fields become non-empty, emits a ComponentInitializedEvent
14659
15612
  * via afterNextRender to signal the container is ready.
14660
15613
  *
15614
+ * `componentType` may be a static value or a lazy getter — the latter lets
15615
+ * virtual field types (e.g. `row`, which renders via ContainerFieldComponent)
15616
+ * preserve their original type on the emitted event instead of collapsing to
15617
+ * the host component's type.
15618
+ *
14661
15619
  * @param resolvedFields - Signal of resolved fields
14662
15620
  * @param eventBus - EventBus for dispatching initialization events
14663
- * @param componentType - The type of container component
15621
+ * @param componentType - Static type, or a getter evaluated at emission time
14664
15622
  * @param fieldKeyFn - Function returning the field's key
14665
15623
  * @param injector - Injector for afterNextRender scheduling
14666
15624
  */
14667
15625
  function setupContainerInitEffect(resolvedFields, eventBus, componentType, fieldKeyFn, injector) {
14668
- explicitEffect([resolvedFields], ([fields]) => {
14669
- if (fields.length > 0) {
14670
- emitComponentInitialized(eventBus, componentType, fieldKeyFn(), injector);
15626
+ const allReady = computed(() => {
15627
+ const fields = resolvedFields();
15628
+ return fields.length > 0 && fields.every((field) => field.renderReady());
15629
+ }, ...(ngDevMode ? [{ debugName: "allReady" }] : /* istanbul ignore next */ []));
15630
+ explicitEffect([allReady], ([ready]) => {
15631
+ if (ready) {
15632
+ const type = typeof componentType === 'function' ? componentType() : componentType;
15633
+ emitComponentInitialized(eventBus, type, fieldKeyFn(), injector);
14671
15634
  }
14672
15635
  });
14673
15636
  }
14674
15637
 
14675
- /**
14676
- * Layout container for horizontal field arrangement.
14677
- *
14678
- * Does not create a new form context - fields share the parent's context.
14679
- * Field values are flattened into the parent form (no nesting under row key).
14680
- * Purely a visual/layout container with no impact on form structure.
14681
- */
14682
- class RowFieldComponent {
14683
- // ─────────────────────────────────────────────────────────────────────────────
14684
- // Dependencies
14685
- // ─────────────────────────────────────────────────────────────────────────────
14686
- destroyRef = inject(DestroyRef);
14687
- fieldRegistry = injectFieldRegistry();
14688
- injector = inject(Injector);
14689
- environmentInjector = inject(EnvironmentInjector);
14690
- eventBus = inject(EventBus);
14691
- logger = inject(DynamicFormLogger);
14692
- // ─────────────────────────────────────────────────────────────────────────────
14693
- // Inputs
14694
- // ─────────────────────────────────────────────────────────────────────────────
14695
- field = input.required(...(ngDevMode ? [{ debugName: "field" }] : /* istanbul ignore next */ []));
14696
- key = input.required(...(ngDevMode ? [{ debugName: "key" }] : /* istanbul ignore next */ []));
14697
- className = input(...(ngDevMode ? [undefined, { debugName: "className" }] : /* istanbul ignore next */ []));
14698
- hidden = input(false, ...(ngDevMode ? [{ debugName: "hidden" }] : /* istanbul ignore next */ []));
14699
- // ─────────────────────────────────────────────────────────────────────────────
14700
- // Computed Signals
14701
- // ─────────────────────────────────────────────────────────────────────────────
14702
- hostClasses = computed(() => computeContainerHostClasses('row', this.className()), ...(ngDevMode ? [{ debugName: "hostClasses" }] : /* istanbul ignore next */ []));
14703
- disabled = computed(() => this.field().disabled || false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
14704
- rawFieldRegistry = computed(() => this.fieldRegistry.raw, ...(ngDevMode ? [{ debugName: "rawFieldRegistry" }] : /* istanbul ignore next */ []));
14705
- // ─────────────────────────────────────────────────────────────────────────────
14706
- // Field Resolution
14707
- // ─────────────────────────────────────────────────────────────────────────────
14708
- fieldsSource = computed(() => (this.field().fields || []), ...(ngDevMode ? [{ debugName: "fieldsSource" }] : /* istanbul ignore next */ []));
14709
- resolvedFields = derivedFromDeferred(this.fieldsSource, createFieldResolutionPipe(() => ({
14710
- loadTypeComponent: (type) => this.fieldRegistry.loadTypeComponent(type),
14711
- registry: this.rawFieldRegistry(),
14712
- injector: this.injector,
14713
- destroyRef: this.destroyRef,
14714
- onError: (fieldDef, error) => {
14715
- const fieldKey = fieldDef.key || '<no key>';
14716
- const rowKey = this.field().key || '<no key>';
14717
- this.logger.error(`Failed to load component for field type '${fieldDef.type}' (key: ${fieldKey}) ` +
14718
- `within row '${rowKey}'. Ensure the field type is registered in your field registry.`, error);
14719
- },
14720
- })), { initialValue: [], injector: this.injector });
14721
- // ─────────────────────────────────────────────────────────────────────────────
14722
- // Constructor
14723
- // ─────────────────────────────────────────────────────────────────────────────
14724
- constructor() {
14725
- this.setupEffects();
14726
- }
14727
- // ─────────────────────────────────────────────────────────────────────────────
14728
- // Private Methods
14729
- // ─────────────────────────────────────────────────────────────────────────────
14730
- setupEffects() {
14731
- setupContainerInitEffect(this.resolvedFields, this.eventBus, 'row', () => this.field().key, this.injector);
14732
- }
14733
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: RowFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
14734
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.6", type: RowFieldComponent, isStandalone: true, selector: "div[row-field]", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: true, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, hidden: { classPropertyName: "hidden", publicName: "hidden", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "hostClasses()", "class.disabled": "disabled()", "class.df-container-hidden": "hidden()", "attr.aria-hidden": "hidden() || null", "id": "`${key()}`", "attr.data-testid": "key()" } }, ngImport: i0, template: `
14735
- @for (field of resolvedFields(); track field.key) {
14736
- <ng-container
14737
- *ngComponentOutlet="field.component; injector: field.injector; environmentInjector: environmentInjector; inputs: field.inputs()"
14738
- />
14739
- }
14740
- `, isInline: true, styles: [":host,.df-form{--df-grid-columns: 12;--df-grid-gap: .5rem;--df-grid-row-gap: .5rem;--df-breakpoint-sm: 576px;--df-breakpoint-md: 768px;--df-breakpoint-lg: 992px;--df-breakpoint-xl: 1200px;--df-grid-gap-sm: .5rem;--df-grid-gap-md: .5rem;--df-grid-gap-lg: .5rem;--df-grid-gap-xl: .5rem;--df-grid-row-gap-sm: .5rem;--df-grid-row-gap-md: .5rem;--df-grid-row-gap-lg: .5rem;--df-grid-row-gap-xl: .5rem;--df-array-item-gap: var(--df-grid-row-gap);--df-group-gap: var(--df-grid-gap);--df-group-padding: var(--df-grid-gap)}.df-form{display:grid;grid-template-columns:1fr;gap:var(--df-grid-row-gap);width:100%}.df-form>*{grid-column:1/-1}.df-row{display:grid;grid-template-columns:repeat(var(--df-grid-columns, 12),1fr);gap:var(--df-grid-gap);align-items:start;width:100%}.df-row>*:not([class*=df-col-]){grid-column:1/-1}.df-col-1{grid-column:span 1}.df-col-2{grid-column:span 2}.df-col-3{grid-column:span 3}.df-col-4{grid-column:span 4}.df-col-5{grid-column:span 5}.df-col-6{grid-column:span 6}.df-col-7{grid-column:span 7}.df-col-8{grid-column:span 8}.df-col-9{grid-column:span 9}.df-col-10{grid-column:span 10}.df-col-11{grid-column:span 11}.df-col-12{grid-column:span 12}.df-col-auto{grid-column:span auto;width:auto}.df-col-full{grid-column:1/-1}.df-col-start-1{grid-column-start:1}.df-col-start-2{grid-column-start:2}.df-col-start-3{grid-column-start:3}.df-col-start-4{grid-column-start:4}.df-col-start-5{grid-column-start:5}.df-col-start-6{grid-column-start:6}.df-col-start-7{grid-column-start:7}.df-col-start-8{grid-column-start:8}.df-col-start-9{grid-column-start:9}.df-col-start-10{grid-column-start:10}.df-col-start-11{grid-column-start:11}.df-col-start-12{grid-column-start:12}.df-col-end-1{grid-column-end:1}.df-col-end-2{grid-column-end:2}.df-col-end-3{grid-column-end:3}.df-col-end-4{grid-column-end:4}.df-col-end-5{grid-column-end:5}.df-col-end-6{grid-column-end:6}.df-col-end-7{grid-column-end:7}.df-col-end-8{grid-column-end:8}.df-col-end-9{grid-column-end:9}.df-col-end-10{grid-column-end:10}.df-col-end-11{grid-column-end:11}.df-col-end-12{grid-column-end:12}.df-col-end-13{grid-column-end:13}@media(max-width:576px){.df-form{--df-grid-gap: var(--df-grid-gap-sm);--df-grid-row-gap: var(--df-grid-row-gap-sm)}.df-row{grid-template-columns:1fr}.df-row>*{grid-column:1/-1!important}.df-row.df-row-mobile-keep-cols{grid-template-columns:repeat(var(--df-grid-columns),1fr)}.df-row.df-row-mobile-keep-cols>*{grid-column:revert!important}}@media(min-width:577px)and (max-width:768px){.df-form{--df-grid-gap: var(--df-grid-gap-md);--df-grid-row-gap: var(--df-grid-row-gap-md)}.df-row{--df-grid-columns: 6}.df-col-sm-1{grid-column:span 1}.df-col-sm-2{grid-column:span 2}.df-col-sm-3{grid-column:span 3}.df-col-sm-4{grid-column:span 4}.df-col-sm-5{grid-column:span 5}.df-col-sm-6{grid-column:span 6}.df-col-sm-full{grid-column:1/-1}}@media(min-width:769px)and (max-width:992px){.df-form{--df-grid-gap: var(--df-grid-gap-lg);--df-grid-row-gap: var(--df-grid-row-gap-lg)}.df-col-md-1{grid-column:span 1}.df-col-md-2{grid-column:span 2}.df-col-md-3{grid-column:span 3}.df-col-md-4{grid-column:span 4}.df-col-md-5{grid-column:span 5}.df-col-md-6{grid-column:span 6}.df-col-md-7{grid-column:span 7}.df-col-md-8{grid-column:span 8}.df-col-md-9{grid-column:span 9}.df-col-md-10{grid-column:span 10}.df-col-md-11{grid-column:span 11}.df-col-md-12{grid-column:span 12}.df-col-md-full{grid-column:1/-1}}@media(min-width:993px){.df-form{--df-grid-gap: var(--df-grid-gap-xl);--df-grid-row-gap: var(--df-grid-row-gap-xl)}.df-col-lg-1{grid-column:span 1}.df-col-lg-2{grid-column:span 2}.df-col-lg-3{grid-column:span 3}.df-col-lg-4{grid-column:span 4}.df-col-lg-5{grid-column:span 5}.df-col-lg-6{grid-column:span 6}.df-col-lg-7{grid-column:span 7}.df-col-lg-8{grid-column:span 8}.df-col-lg-9{grid-column:span 9}.df-col-lg-10{grid-column:span 10}.df-col-lg-11{grid-column:span 11}.df-col-lg-12{grid-column:span 12}.df-col-lg-full{grid-column:1/-1}}.df-gap-none{--df-grid-gap: 0}.df-gap-xs{--df-grid-gap: .25rem}.df-gap-sm{--df-grid-gap: .5rem}.df-gap-md{--df-grid-gap: 1rem}.df-gap-lg{--df-grid-gap: 1.5rem}.df-gap-xl{--df-grid-gap: 2rem}.df-row-gap-none{--df-grid-row-gap: 0}.df-row-gap-xs{--df-grid-row-gap: .25rem}.df-row-gap-sm{--df-grid-row-gap: .5rem}.df-row-gap-md{--df-grid-row-gap: 1rem}.df-row-gap-lg{--df-grid-row-gap: 1.5rem}.df-row-gap-xl{--df-grid-row-gap: 2rem}.df-field{display:block;width:100%;min-width:0;overflow:hidden;margin:0}.df-group,.df-page{display:block;width:100%}.df-form.disabled,.df-row.disabled,.df-field.disabled{opacity:.6;pointer-events:none}.df-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.df-sr-only-focusable:focus,.df-sr-only-focusable:active{position:static;width:auto;height:auto;padding:inherit;margin:inherit;overflow:visible;clip:auto;white-space:normal}.df-live-region{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.df-form :focus-visible{outline:2px solid var(--df-focus-color, #005fcc);outline-offset:2px}.df-group:focus-within{outline:1px dashed var(--df-focus-color, #005fcc);outline-offset:4px}:host{display:flex;flex-wrap:wrap;gap:var(--df-grid-gap);align-items:flex-start;width:100%}:host ::ng-deep>*:not([class*=df-col-]){flex:0 0 auto}:host ::ng-deep>.df-col-1{flex:0 0 calc(8.3333333333% - var(--df-grid-gap) * .9166666667);max-width:calc(8.3333333333% - var(--df-grid-gap) * .9166666667)}:host ::ng-deep>.df-col-2{flex:0 0 calc(16.6666666667% - var(--df-grid-gap) * .8333333333);max-width:calc(16.6666666667% - var(--df-grid-gap) * .8333333333)}:host ::ng-deep>.df-col-3{flex:0 0 calc(25% - var(--df-grid-gap) * .75);max-width:calc(25% - var(--df-grid-gap) * .75)}:host ::ng-deep>.df-col-4{flex:0 0 calc(33.3333333333% - var(--df-grid-gap) * .6666666667);max-width:calc(33.3333333333% - var(--df-grid-gap) * .6666666667)}:host ::ng-deep>.df-col-5{flex:0 0 calc(41.6666666667% - var(--df-grid-gap) * .5833333333);max-width:calc(41.6666666667% - var(--df-grid-gap) * .5833333333)}:host ::ng-deep>.df-col-6{flex:0 0 calc(50% - var(--df-grid-gap) * .5);max-width:calc(50% - var(--df-grid-gap) * .5)}:host ::ng-deep>.df-col-7{flex:0 0 calc(58.3333333333% - var(--df-grid-gap) * .4166666667);max-width:calc(58.3333333333% - var(--df-grid-gap) * .4166666667)}:host ::ng-deep>.df-col-8{flex:0 0 calc(66.6666666667% - var(--df-grid-gap) * .3333333333);max-width:calc(66.6666666667% - var(--df-grid-gap) * .3333333333)}:host ::ng-deep>.df-col-9{flex:0 0 calc(75% - var(--df-grid-gap) * .25);max-width:calc(75% - var(--df-grid-gap) * .25)}:host ::ng-deep>.df-col-10{flex:0 0 calc(83.3333333333% - var(--df-grid-gap) * .1666666667);max-width:calc(83.3333333333% - var(--df-grid-gap) * .1666666667)}:host ::ng-deep>.df-col-11{flex:0 0 calc(91.6666666667% - var(--df-grid-gap) * .0833333333);max-width:calc(91.6666666667% - var(--df-grid-gap) * .0833333333)}:host ::ng-deep>.df-col-12{flex:0 0 calc(100% - var(--df-grid-gap) * 0);max-width:calc(100% - var(--df-grid-gap) * 0)}:host.df-gap-custom{--df-grid-gap: var(--row-custom-gap, 1rem)}@media(max-width:576px){:host.df-row-mobile-stack ::ng-deep>*{flex:0 0 100%!important;max-width:100%!important}}@media(min-width:577px)and (max-width:768px){:host.df-row-tablet-2col ::ng-deep>*{flex:0 0 calc(50% - var(--df-grid-gap) / 2);max-width:calc(50% - var(--df-grid-gap) / 2)}}:host.df-container-hidden{display:none}@media(max-width:576px){:host ::ng-deep>*{flex:0 0 100%!important;max-width:100%!important}:host.df-row-mobile-keep-cols{flex-direction:row}:host.df-row-mobile-keep-cols ::ng-deep>*:not([class*=df-col-]){flex:0 0 auto!important;max-width:none!important}}\n"], dependencies: [{ kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
14741
- }
14742
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: RowFieldComponent, decorators: [{
14743
- type: Component,
14744
- args: [{ selector: 'div[row-field]', imports: [NgComponentOutlet], template: `
14745
- @for (field of resolvedFields(); track field.key) {
14746
- <ng-container
14747
- *ngComponentOutlet="field.component; injector: field.injector; environmentInjector: environmentInjector; inputs: field.inputs()"
14748
- />
14749
- }
14750
- `, host: {
14751
- '[class]': 'hostClasses()',
14752
- '[class.disabled]': 'disabled()',
14753
- '[class.df-container-hidden]': 'hidden()',
14754
- '[attr.aria-hidden]': 'hidden() || null',
14755
- '[id]': '`${key()}`',
14756
- '[attr.data-testid]': 'key()',
14757
- }, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host,.df-form{--df-grid-columns: 12;--df-grid-gap: .5rem;--df-grid-row-gap: .5rem;--df-breakpoint-sm: 576px;--df-breakpoint-md: 768px;--df-breakpoint-lg: 992px;--df-breakpoint-xl: 1200px;--df-grid-gap-sm: .5rem;--df-grid-gap-md: .5rem;--df-grid-gap-lg: .5rem;--df-grid-gap-xl: .5rem;--df-grid-row-gap-sm: .5rem;--df-grid-row-gap-md: .5rem;--df-grid-row-gap-lg: .5rem;--df-grid-row-gap-xl: .5rem;--df-array-item-gap: var(--df-grid-row-gap);--df-group-gap: var(--df-grid-gap);--df-group-padding: var(--df-grid-gap)}.df-form{display:grid;grid-template-columns:1fr;gap:var(--df-grid-row-gap);width:100%}.df-form>*{grid-column:1/-1}.df-row{display:grid;grid-template-columns:repeat(var(--df-grid-columns, 12),1fr);gap:var(--df-grid-gap);align-items:start;width:100%}.df-row>*:not([class*=df-col-]){grid-column:1/-1}.df-col-1{grid-column:span 1}.df-col-2{grid-column:span 2}.df-col-3{grid-column:span 3}.df-col-4{grid-column:span 4}.df-col-5{grid-column:span 5}.df-col-6{grid-column:span 6}.df-col-7{grid-column:span 7}.df-col-8{grid-column:span 8}.df-col-9{grid-column:span 9}.df-col-10{grid-column:span 10}.df-col-11{grid-column:span 11}.df-col-12{grid-column:span 12}.df-col-auto{grid-column:span auto;width:auto}.df-col-full{grid-column:1/-1}.df-col-start-1{grid-column-start:1}.df-col-start-2{grid-column-start:2}.df-col-start-3{grid-column-start:3}.df-col-start-4{grid-column-start:4}.df-col-start-5{grid-column-start:5}.df-col-start-6{grid-column-start:6}.df-col-start-7{grid-column-start:7}.df-col-start-8{grid-column-start:8}.df-col-start-9{grid-column-start:9}.df-col-start-10{grid-column-start:10}.df-col-start-11{grid-column-start:11}.df-col-start-12{grid-column-start:12}.df-col-end-1{grid-column-end:1}.df-col-end-2{grid-column-end:2}.df-col-end-3{grid-column-end:3}.df-col-end-4{grid-column-end:4}.df-col-end-5{grid-column-end:5}.df-col-end-6{grid-column-end:6}.df-col-end-7{grid-column-end:7}.df-col-end-8{grid-column-end:8}.df-col-end-9{grid-column-end:9}.df-col-end-10{grid-column-end:10}.df-col-end-11{grid-column-end:11}.df-col-end-12{grid-column-end:12}.df-col-end-13{grid-column-end:13}@media(max-width:576px){.df-form{--df-grid-gap: var(--df-grid-gap-sm);--df-grid-row-gap: var(--df-grid-row-gap-sm)}.df-row{grid-template-columns:1fr}.df-row>*{grid-column:1/-1!important}.df-row.df-row-mobile-keep-cols{grid-template-columns:repeat(var(--df-grid-columns),1fr)}.df-row.df-row-mobile-keep-cols>*{grid-column:revert!important}}@media(min-width:577px)and (max-width:768px){.df-form{--df-grid-gap: var(--df-grid-gap-md);--df-grid-row-gap: var(--df-grid-row-gap-md)}.df-row{--df-grid-columns: 6}.df-col-sm-1{grid-column:span 1}.df-col-sm-2{grid-column:span 2}.df-col-sm-3{grid-column:span 3}.df-col-sm-4{grid-column:span 4}.df-col-sm-5{grid-column:span 5}.df-col-sm-6{grid-column:span 6}.df-col-sm-full{grid-column:1/-1}}@media(min-width:769px)and (max-width:992px){.df-form{--df-grid-gap: var(--df-grid-gap-lg);--df-grid-row-gap: var(--df-grid-row-gap-lg)}.df-col-md-1{grid-column:span 1}.df-col-md-2{grid-column:span 2}.df-col-md-3{grid-column:span 3}.df-col-md-4{grid-column:span 4}.df-col-md-5{grid-column:span 5}.df-col-md-6{grid-column:span 6}.df-col-md-7{grid-column:span 7}.df-col-md-8{grid-column:span 8}.df-col-md-9{grid-column:span 9}.df-col-md-10{grid-column:span 10}.df-col-md-11{grid-column:span 11}.df-col-md-12{grid-column:span 12}.df-col-md-full{grid-column:1/-1}}@media(min-width:993px){.df-form{--df-grid-gap: var(--df-grid-gap-xl);--df-grid-row-gap: var(--df-grid-row-gap-xl)}.df-col-lg-1{grid-column:span 1}.df-col-lg-2{grid-column:span 2}.df-col-lg-3{grid-column:span 3}.df-col-lg-4{grid-column:span 4}.df-col-lg-5{grid-column:span 5}.df-col-lg-6{grid-column:span 6}.df-col-lg-7{grid-column:span 7}.df-col-lg-8{grid-column:span 8}.df-col-lg-9{grid-column:span 9}.df-col-lg-10{grid-column:span 10}.df-col-lg-11{grid-column:span 11}.df-col-lg-12{grid-column:span 12}.df-col-lg-full{grid-column:1/-1}}.df-gap-none{--df-grid-gap: 0}.df-gap-xs{--df-grid-gap: .25rem}.df-gap-sm{--df-grid-gap: .5rem}.df-gap-md{--df-grid-gap: 1rem}.df-gap-lg{--df-grid-gap: 1.5rem}.df-gap-xl{--df-grid-gap: 2rem}.df-row-gap-none{--df-grid-row-gap: 0}.df-row-gap-xs{--df-grid-row-gap: .25rem}.df-row-gap-sm{--df-grid-row-gap: .5rem}.df-row-gap-md{--df-grid-row-gap: 1rem}.df-row-gap-lg{--df-grid-row-gap: 1.5rem}.df-row-gap-xl{--df-grid-row-gap: 2rem}.df-field{display:block;width:100%;min-width:0;overflow:hidden;margin:0}.df-group,.df-page{display:block;width:100%}.df-form.disabled,.df-row.disabled,.df-field.disabled{opacity:.6;pointer-events:none}.df-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.df-sr-only-focusable:focus,.df-sr-only-focusable:active{position:static;width:auto;height:auto;padding:inherit;margin:inherit;overflow:visible;clip:auto;white-space:normal}.df-live-region{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.df-form :focus-visible{outline:2px solid var(--df-focus-color, #005fcc);outline-offset:2px}.df-group:focus-within{outline:1px dashed var(--df-focus-color, #005fcc);outline-offset:4px}:host{display:flex;flex-wrap:wrap;gap:var(--df-grid-gap);align-items:flex-start;width:100%}:host ::ng-deep>*:not([class*=df-col-]){flex:0 0 auto}:host ::ng-deep>.df-col-1{flex:0 0 calc(8.3333333333% - var(--df-grid-gap) * .9166666667);max-width:calc(8.3333333333% - var(--df-grid-gap) * .9166666667)}:host ::ng-deep>.df-col-2{flex:0 0 calc(16.6666666667% - var(--df-grid-gap) * .8333333333);max-width:calc(16.6666666667% - var(--df-grid-gap) * .8333333333)}:host ::ng-deep>.df-col-3{flex:0 0 calc(25% - var(--df-grid-gap) * .75);max-width:calc(25% - var(--df-grid-gap) * .75)}:host ::ng-deep>.df-col-4{flex:0 0 calc(33.3333333333% - var(--df-grid-gap) * .6666666667);max-width:calc(33.3333333333% - var(--df-grid-gap) * .6666666667)}:host ::ng-deep>.df-col-5{flex:0 0 calc(41.6666666667% - var(--df-grid-gap) * .5833333333);max-width:calc(41.6666666667% - var(--df-grid-gap) * .5833333333)}:host ::ng-deep>.df-col-6{flex:0 0 calc(50% - var(--df-grid-gap) * .5);max-width:calc(50% - var(--df-grid-gap) * .5)}:host ::ng-deep>.df-col-7{flex:0 0 calc(58.3333333333% - var(--df-grid-gap) * .4166666667);max-width:calc(58.3333333333% - var(--df-grid-gap) * .4166666667)}:host ::ng-deep>.df-col-8{flex:0 0 calc(66.6666666667% - var(--df-grid-gap) * .3333333333);max-width:calc(66.6666666667% - var(--df-grid-gap) * .3333333333)}:host ::ng-deep>.df-col-9{flex:0 0 calc(75% - var(--df-grid-gap) * .25);max-width:calc(75% - var(--df-grid-gap) * .25)}:host ::ng-deep>.df-col-10{flex:0 0 calc(83.3333333333% - var(--df-grid-gap) * .1666666667);max-width:calc(83.3333333333% - var(--df-grid-gap) * .1666666667)}:host ::ng-deep>.df-col-11{flex:0 0 calc(91.6666666667% - var(--df-grid-gap) * .0833333333);max-width:calc(91.6666666667% - var(--df-grid-gap) * .0833333333)}:host ::ng-deep>.df-col-12{flex:0 0 calc(100% - var(--df-grid-gap) * 0);max-width:calc(100% - var(--df-grid-gap) * 0)}:host.df-gap-custom{--df-grid-gap: var(--row-custom-gap, 1rem)}@media(max-width:576px){:host.df-row-mobile-stack ::ng-deep>*{flex:0 0 100%!important;max-width:100%!important}}@media(min-width:577px)and (max-width:768px){:host.df-row-tablet-2col ::ng-deep>*{flex:0 0 calc(50% - var(--df-grid-gap) / 2);max-width:calc(50% - var(--df-grid-gap) / 2)}}:host.df-container-hidden{display:none}@media(max-width:576px){:host ::ng-deep>*{flex:0 0 100%!important;max-width:100%!important}:host.df-row-mobile-keep-cols{flex-direction:row}:host.df-row-mobile-keep-cols ::ng-deep>*:not([class*=df-col-]){flex:0 0 auto!important;max-width:none!important}}\n"] }]
14758
- }], ctorParameters: () => [], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: true }] }], key: [{ type: i0.Input, args: [{ isSignal: true, alias: "key", required: true }] }], className: [{ type: i0.Input, args: [{ isSignal: true, alias: "className", required: false }] }], hidden: [{ type: i0.Input, args: [{ isSignal: true, alias: "hidden", required: false }] }] } });
14759
-
14760
- var rowField_component = /*#__PURE__*/Object.freeze({
14761
- __proto__: null,
14762
- RowFieldComponent: RowFieldComponent,
14763
- default: RowFieldComponent
14764
- });
14765
-
14766
15638
  /**
14767
15639
  * Container component for rendering nested form groups.
14768
15640
  *
@@ -14915,22 +15787,18 @@ class GroupFieldComponent {
14915
15787
  }
14916
15788
  }, { defer: true });
14917
15789
  }
14918
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: GroupFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
14919
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.6", type: GroupFieldComponent, isStandalone: true, selector: "fieldset[group-field]", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: true, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, hidden: { classPropertyName: "hidden", publicName: "hidden", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { validityChange: "validityChange", dirtyChange: "dirtyChange", submitted: "submitted" }, host: { attributes: { "role": "group" }, properties: { "class": "hostClasses()", "class.disabled": "disabled()", "class.df-container-hidden": "hidden()", "attr.aria-hidden": "hidden() || null", "id": "`${key()}`", "attr.data-testid": "key()" } }, ngImport: i0, template: `
15790
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: GroupFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
15791
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: GroupFieldComponent, isStandalone: true, selector: "fieldset[group-field]", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: true, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, hidden: { classPropertyName: "hidden", publicName: "hidden", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { validityChange: "validityChange", dirtyChange: "dirtyChange", submitted: "submitted" }, host: { attributes: { "role": "group" }, properties: { "class": "hostClasses()", "class.disabled": "disabled()", "class.df-container-hidden": "hidden()", "attr.aria-hidden": "hidden() || null", "id": "`${key()}`", "attr.data-testid": "key()" } }, ngImport: i0, template: `
14920
15792
  @for (field of resolvedFields(); track field.key) {
14921
- <ng-container
14922
- *ngComponentOutlet="field.component; injector: field.injector; environmentInjector: environmentInjector; inputs: field.inputs()"
14923
- />
15793
+ <ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
14924
15794
  }
14925
- `, isInline: true, styles: [":host,.df-form{--df-grid-columns: 12;--df-grid-gap: .5rem;--df-grid-row-gap: .5rem;--df-breakpoint-sm: 576px;--df-breakpoint-md: 768px;--df-breakpoint-lg: 992px;--df-breakpoint-xl: 1200px;--df-grid-gap-sm: .5rem;--df-grid-gap-md: .5rem;--df-grid-gap-lg: .5rem;--df-grid-gap-xl: .5rem;--df-grid-row-gap-sm: .5rem;--df-grid-row-gap-md: .5rem;--df-grid-row-gap-lg: .5rem;--df-grid-row-gap-xl: .5rem;--df-array-item-gap: var(--df-grid-row-gap);--df-group-gap: var(--df-grid-gap);--df-group-padding: var(--df-grid-gap)}.df-form{display:grid;grid-template-columns:1fr;gap:var(--df-grid-row-gap);width:100%}.df-form>*{grid-column:1/-1}.df-row{display:grid;grid-template-columns:repeat(var(--df-grid-columns, 12),1fr);gap:var(--df-grid-gap);align-items:start;width:100%}.df-row>*:not([class*=df-col-]){grid-column:1/-1}.df-col-1{grid-column:span 1}.df-col-2{grid-column:span 2}.df-col-3{grid-column:span 3}.df-col-4{grid-column:span 4}.df-col-5{grid-column:span 5}.df-col-6{grid-column:span 6}.df-col-7{grid-column:span 7}.df-col-8{grid-column:span 8}.df-col-9{grid-column:span 9}.df-col-10{grid-column:span 10}.df-col-11{grid-column:span 11}.df-col-12{grid-column:span 12}.df-col-auto{grid-column:span auto;width:auto}.df-col-full{grid-column:1/-1}.df-col-start-1{grid-column-start:1}.df-col-start-2{grid-column-start:2}.df-col-start-3{grid-column-start:3}.df-col-start-4{grid-column-start:4}.df-col-start-5{grid-column-start:5}.df-col-start-6{grid-column-start:6}.df-col-start-7{grid-column-start:7}.df-col-start-8{grid-column-start:8}.df-col-start-9{grid-column-start:9}.df-col-start-10{grid-column-start:10}.df-col-start-11{grid-column-start:11}.df-col-start-12{grid-column-start:12}.df-col-end-1{grid-column-end:1}.df-col-end-2{grid-column-end:2}.df-col-end-3{grid-column-end:3}.df-col-end-4{grid-column-end:4}.df-col-end-5{grid-column-end:5}.df-col-end-6{grid-column-end:6}.df-col-end-7{grid-column-end:7}.df-col-end-8{grid-column-end:8}.df-col-end-9{grid-column-end:9}.df-col-end-10{grid-column-end:10}.df-col-end-11{grid-column-end:11}.df-col-end-12{grid-column-end:12}.df-col-end-13{grid-column-end:13}@media(max-width:576px){.df-form{--df-grid-gap: var(--df-grid-gap-sm);--df-grid-row-gap: var(--df-grid-row-gap-sm)}.df-row{grid-template-columns:1fr}.df-row>*{grid-column:1/-1!important}.df-row.df-row-mobile-keep-cols{grid-template-columns:repeat(var(--df-grid-columns),1fr)}.df-row.df-row-mobile-keep-cols>*{grid-column:revert!important}}@media(min-width:577px)and (max-width:768px){.df-form{--df-grid-gap: var(--df-grid-gap-md);--df-grid-row-gap: var(--df-grid-row-gap-md)}.df-row{--df-grid-columns: 6}.df-col-sm-1{grid-column:span 1}.df-col-sm-2{grid-column:span 2}.df-col-sm-3{grid-column:span 3}.df-col-sm-4{grid-column:span 4}.df-col-sm-5{grid-column:span 5}.df-col-sm-6{grid-column:span 6}.df-col-sm-full{grid-column:1/-1}}@media(min-width:769px)and (max-width:992px){.df-form{--df-grid-gap: var(--df-grid-gap-lg);--df-grid-row-gap: var(--df-grid-row-gap-lg)}.df-col-md-1{grid-column:span 1}.df-col-md-2{grid-column:span 2}.df-col-md-3{grid-column:span 3}.df-col-md-4{grid-column:span 4}.df-col-md-5{grid-column:span 5}.df-col-md-6{grid-column:span 6}.df-col-md-7{grid-column:span 7}.df-col-md-8{grid-column:span 8}.df-col-md-9{grid-column:span 9}.df-col-md-10{grid-column:span 10}.df-col-md-11{grid-column:span 11}.df-col-md-12{grid-column:span 12}.df-col-md-full{grid-column:1/-1}}@media(min-width:993px){.df-form{--df-grid-gap: var(--df-grid-gap-xl);--df-grid-row-gap: var(--df-grid-row-gap-xl)}.df-col-lg-1{grid-column:span 1}.df-col-lg-2{grid-column:span 2}.df-col-lg-3{grid-column:span 3}.df-col-lg-4{grid-column:span 4}.df-col-lg-5{grid-column:span 5}.df-col-lg-6{grid-column:span 6}.df-col-lg-7{grid-column:span 7}.df-col-lg-8{grid-column:span 8}.df-col-lg-9{grid-column:span 9}.df-col-lg-10{grid-column:span 10}.df-col-lg-11{grid-column:span 11}.df-col-lg-12{grid-column:span 12}.df-col-lg-full{grid-column:1/-1}}.df-gap-none{--df-grid-gap: 0}.df-gap-xs{--df-grid-gap: .25rem}.df-gap-sm{--df-grid-gap: .5rem}.df-gap-md{--df-grid-gap: 1rem}.df-gap-lg{--df-grid-gap: 1.5rem}.df-gap-xl{--df-grid-gap: 2rem}.df-row-gap-none{--df-grid-row-gap: 0}.df-row-gap-xs{--df-grid-row-gap: .25rem}.df-row-gap-sm{--df-grid-row-gap: .5rem}.df-row-gap-md{--df-grid-row-gap: 1rem}.df-row-gap-lg{--df-grid-row-gap: 1.5rem}.df-row-gap-xl{--df-grid-row-gap: 2rem}.df-field{display:block;width:100%;min-width:0;overflow:hidden;margin:0}.df-group,.df-page{display:block;width:100%}.df-form.disabled,.df-row.disabled,.df-field.disabled{opacity:.6;pointer-events:none}.df-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.df-sr-only-focusable:focus,.df-sr-only-focusable:active{position:static;width:auto;height:auto;padding:inherit;margin:inherit;overflow:visible;clip:auto;white-space:normal}.df-live-region{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.df-form :focus-visible{outline:2px solid var(--df-focus-color, #005fcc);outline-offset:2px}.df-group:focus-within{outline:1px dashed var(--df-focus-color, #005fcc);outline-offset:4px}:host{border:none;margin:0;padding:0;min-width:0;display:grid;grid-template-columns:1fr;gap:var(--df-grid-row-gap, .5rem);width:100%}:host>*{grid-column:1/-1}:host.df-container-hidden{display:none}\n"], dependencies: [{ kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
15795
+ `, isInline: true, styles: [":host,.df-form{--df-grid-columns: 12;--df-grid-gap: .5rem;--df-grid-row-gap: .5rem;--df-breakpoint-sm: 576px;--df-breakpoint-md: 768px;--df-breakpoint-lg: 992px;--df-breakpoint-xl: 1200px;--df-grid-gap-sm: .5rem;--df-grid-gap-md: .5rem;--df-grid-gap-lg: .5rem;--df-grid-gap-xl: .5rem;--df-grid-row-gap-sm: .5rem;--df-grid-row-gap-md: .5rem;--df-grid-row-gap-lg: .5rem;--df-grid-row-gap-xl: .5rem;--df-array-item-gap: var(--df-grid-row-gap);--df-group-gap: var(--df-grid-gap);--df-group-padding: var(--df-grid-gap)}.df-form{display:grid;grid-template-columns:1fr;gap:var(--df-grid-row-gap);width:100%}.df-form>*{grid-column:1/-1}.df-row{display:grid;grid-template-columns:repeat(var(--df-grid-columns, 12),1fr);gap:var(--df-grid-gap);align-items:start;width:100%}.df-row>*:not([class*=df-col-]){grid-column:1/-1}.df-col-1{grid-column:span 1}.df-col-2{grid-column:span 2}.df-col-3{grid-column:span 3}.df-col-4{grid-column:span 4}.df-col-5{grid-column:span 5}.df-col-6{grid-column:span 6}.df-col-7{grid-column:span 7}.df-col-8{grid-column:span 8}.df-col-9{grid-column:span 9}.df-col-10{grid-column:span 10}.df-col-11{grid-column:span 11}.df-col-12{grid-column:span 12}.df-col-auto{grid-column:span auto;width:auto}.df-col-full{grid-column:1/-1}.df-col-start-1{grid-column-start:1}.df-col-start-2{grid-column-start:2}.df-col-start-3{grid-column-start:3}.df-col-start-4{grid-column-start:4}.df-col-start-5{grid-column-start:5}.df-col-start-6{grid-column-start:6}.df-col-start-7{grid-column-start:7}.df-col-start-8{grid-column-start:8}.df-col-start-9{grid-column-start:9}.df-col-start-10{grid-column-start:10}.df-col-start-11{grid-column-start:11}.df-col-start-12{grid-column-start:12}.df-col-end-1{grid-column-end:1}.df-col-end-2{grid-column-end:2}.df-col-end-3{grid-column-end:3}.df-col-end-4{grid-column-end:4}.df-col-end-5{grid-column-end:5}.df-col-end-6{grid-column-end:6}.df-col-end-7{grid-column-end:7}.df-col-end-8{grid-column-end:8}.df-col-end-9{grid-column-end:9}.df-col-end-10{grid-column-end:10}.df-col-end-11{grid-column-end:11}.df-col-end-12{grid-column-end:12}.df-col-end-13{grid-column-end:13}@media(max-width:576px){.df-form{--df-grid-gap: var(--df-grid-gap-sm);--df-grid-row-gap: var(--df-grid-row-gap-sm)}.df-row{grid-template-columns:1fr}.df-row>*{grid-column:1/-1!important}.df-row.df-row-mobile-keep-cols{grid-template-columns:repeat(var(--df-grid-columns),1fr)}.df-row.df-row-mobile-keep-cols>*{grid-column:revert!important}}@media(min-width:577px)and (max-width:768px){.df-form{--df-grid-gap: var(--df-grid-gap-md);--df-grid-row-gap: var(--df-grid-row-gap-md)}.df-row{--df-grid-columns: 6}.df-col-sm-1{grid-column:span 1}.df-col-sm-2{grid-column:span 2}.df-col-sm-3{grid-column:span 3}.df-col-sm-4{grid-column:span 4}.df-col-sm-5{grid-column:span 5}.df-col-sm-6{grid-column:span 6}.df-col-sm-full{grid-column:1/-1}}@media(min-width:769px)and (max-width:992px){.df-form{--df-grid-gap: var(--df-grid-gap-lg);--df-grid-row-gap: var(--df-grid-row-gap-lg)}.df-col-md-1{grid-column:span 1}.df-col-md-2{grid-column:span 2}.df-col-md-3{grid-column:span 3}.df-col-md-4{grid-column:span 4}.df-col-md-5{grid-column:span 5}.df-col-md-6{grid-column:span 6}.df-col-md-7{grid-column:span 7}.df-col-md-8{grid-column:span 8}.df-col-md-9{grid-column:span 9}.df-col-md-10{grid-column:span 10}.df-col-md-11{grid-column:span 11}.df-col-md-12{grid-column:span 12}.df-col-md-full{grid-column:1/-1}}@media(min-width:993px){.df-form{--df-grid-gap: var(--df-grid-gap-xl);--df-grid-row-gap: var(--df-grid-row-gap-xl)}.df-col-lg-1{grid-column:span 1}.df-col-lg-2{grid-column:span 2}.df-col-lg-3{grid-column:span 3}.df-col-lg-4{grid-column:span 4}.df-col-lg-5{grid-column:span 5}.df-col-lg-6{grid-column:span 6}.df-col-lg-7{grid-column:span 7}.df-col-lg-8{grid-column:span 8}.df-col-lg-9{grid-column:span 9}.df-col-lg-10{grid-column:span 10}.df-col-lg-11{grid-column:span 11}.df-col-lg-12{grid-column:span 12}.df-col-lg-full{grid-column:1/-1}}.df-gap-none{--df-grid-gap: 0}.df-gap-xs{--df-grid-gap: .25rem}.df-gap-sm{--df-grid-gap: .5rem}.df-gap-md{--df-grid-gap: 1rem}.df-gap-lg{--df-grid-gap: 1.5rem}.df-gap-xl{--df-grid-gap: 2rem}.df-row-gap-none{--df-grid-row-gap: 0}.df-row-gap-xs{--df-grid-row-gap: .25rem}.df-row-gap-sm{--df-grid-row-gap: .5rem}.df-row-gap-md{--df-grid-row-gap: 1rem}.df-row-gap-lg{--df-grid-row-gap: 1.5rem}.df-row-gap-xl{--df-grid-row-gap: 2rem}.df-field{display:block;width:100%;min-width:0;overflow:hidden;margin:0}.df-group,.df-page{display:block;width:100%}.df-form.disabled,.df-row.disabled,.df-field.disabled{opacity:.6;pointer-events:none}.df-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.df-sr-only-focusable:focus,.df-sr-only-focusable:active{position:static;width:auto;height:auto;padding:inherit;margin:inherit;overflow:visible;clip:auto;white-space:normal}.df-live-region{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.df-form :focus-visible{outline:2px solid var(--df-focus-color, #005fcc);outline-offset:2px}.df-group:focus-within{outline:1px dashed var(--df-focus-color, #005fcc);outline-offset:4px}:host{border:none;margin:0;padding:0;min-width:0;display:grid;grid-template-columns:1fr;gap:var(--df-grid-row-gap, .5rem);width:100%}:host>*{grid-column:1/-1}:host.df-container-hidden{display:none}\n"], dependencies: [{ kind: "directive", type: DfFieldOutlet, selector: "[dfFieldOutlet]", inputs: ["dfFieldOutlet", "dfFieldOutletEnvironmentInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
14926
15796
  }
14927
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: GroupFieldComponent, decorators: [{
15797
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: GroupFieldComponent, decorators: [{
14928
15798
  type: Component,
14929
- args: [{ selector: 'fieldset[group-field]', imports: [NgComponentOutlet], template: `
15799
+ args: [{ selector: 'fieldset[group-field]', imports: [DfFieldOutlet], template: `
14930
15800
  @for (field of resolvedFields(); track field.key) {
14931
- <ng-container
14932
- *ngComponentOutlet="field.component; injector: field.injector; environmentInjector: environmentInjector; inputs: field.inputs()"
14933
- />
15801
+ <ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
14934
15802
  }
14935
15803
  `, host: {
14936
15804
  '[class]': 'hostClasses()',
@@ -15140,7 +16008,7 @@ function resolveArrayItem(options) {
15140
16008
  return positionMap.get(itemId) ?? index;
15141
16009
  }, ...(ngDevMode ? [{ debugName: "indexSignal" }] : /* istanbul ignore next */ []));
15142
16010
  // Resolve all templates in parallel
15143
- const fieldObservables = templates.map((template) => from(loadTypeComponent(template.type)).pipe(map$1((component) => {
16011
+ const fieldObservables = templates.map((template) => from(loadTypeComponent(template.type)).pipe(map((component) => {
15144
16012
  if (destroyRef.destroyed) {
15145
16013
  return undefined;
15146
16014
  }
@@ -15162,9 +16030,12 @@ function resolveArrayItem(options) {
15162
16030
  return undefined;
15163
16031
  }
15164
16032
  return {
16033
+ key: template.key,
16034
+ fieldDef: template,
15165
16035
  component,
15166
16036
  injector,
15167
16037
  inputs,
16038
+ renderReady: createRenderReadySignal(inputs, registry.get(template.type)),
15168
16039
  };
15169
16040
  }), catchError((error) => {
15170
16041
  if (!destroyRef.destroyed) {
@@ -15174,7 +16045,7 @@ function resolveArrayItem(options) {
15174
16045
  }
15175
16046
  return of(undefined);
15176
16047
  })));
15177
- return forkJoin(fieldObservables).pipe(map$1((fields) => {
16048
+ return forkJoin(fieldObservables).pipe(map((fields) => {
15178
16049
  const validFields = fields.filter((f) => f !== undefined);
15179
16050
  // If no fields resolved, return undefined
15180
16051
  if (validFields.length === 0) {
@@ -15194,6 +16065,7 @@ const ARRAY_EVENT_TYPES = [
15194
16065
  'append-array-item',
15195
16066
  'prepend-array-item',
15196
16067
  'insert-array-item',
16068
+ 'move-array-item',
15197
16069
  'pop-array-item',
15198
16070
  'shift-array-item',
15199
16071
  'remove-at-index',
@@ -15210,6 +16082,8 @@ function toArrayAction(event) {
15210
16082
  return { action: 'add', template: event.template, index: 0 };
15211
16083
  case 'insert-array-item':
15212
16084
  return { action: 'add', template: event.template, index: event.index };
16085
+ case 'move-array-item':
16086
+ return { action: 'move', fromIndex: event.fromIndex, toIndex: event.toIndex };
15213
16087
  case 'pop-array-item':
15214
16088
  return { action: 'remove' };
15215
16089
  case 'shift-array-item':
@@ -15239,13 +16113,13 @@ function toArrayAction(event) {
15239
16113
  * ```
15240
16114
  */
15241
16115
  function observeArrayActions(eventBus, arrayKey) {
15242
- return eventBus.on(ARRAY_EVENT_TYPES).pipe(filter((event) => event.arrayKey === arrayKey()), map$1(toArrayAction));
16116
+ return eventBus.on(ARRAY_EVENT_TYPES).pipe(filter((event) => event.arrayKey === arrayKey()), map(toArrayAction));
15243
16117
  }
15244
16118
 
15245
16119
  /**
15246
16120
  * Container component for rendering dynamic arrays of fields.
15247
16121
  *
15248
- * Supports add/remove operations via the arrayEvent() builder API.
16122
+ * Supports add/remove/move operations via the arrayEvent() builder API.
15249
16123
  * Uses differential updates to optimize rendering - only recreates items when necessary.
15250
16124
  * Each item gets a scoped injector with ARRAY_CONTEXT for position-aware operations.
15251
16125
  * Supports multiple sibling fields per array item (e.g., name + email without a wrapper).
@@ -15315,6 +16189,21 @@ class ArrayFieldComponent {
15315
16189
  }
15316
16190
  return undefined;
15317
16191
  }, ...(ngDevMode ? [{ debugName: "primitiveFieldKey" }] : /* istanbul ignore next */ []));
16192
+ /**
16193
+ * Normalized item templates WITHOUT auto-remove button appended.
16194
+ * Each element is normalized to an array: single FieldDef → [FieldDef], array stays as-is.
16195
+ *
16196
+ * Used by moveItem() to stash raw templates into the templateRegistry, preserving
16197
+ * the invariant that registry entries are pre-synthesis (withAutoRemove() adds the
16198
+ * button during resolution).
16199
+ */
16200
+ rawItemTemplates = computed(() => {
16201
+ const arrayField = this.field();
16202
+ const definitions = arrayField.fields || [];
16203
+ return definitions.map((def) => {
16204
+ return (Array.isArray(def) ? def : [def]);
16205
+ });
16206
+ }, ...(ngDevMode ? [{ debugName: "rawItemTemplates" }] : /* istanbul ignore next */ []));
15318
16207
  /**
15319
16208
  * Gets the item templates (field definitions) for the array.
15320
16209
  * Each element can be either:
@@ -15328,17 +16217,12 @@ class ArrayFieldComponent {
15328
16217
  * Returns normalized templates where all items are arrays for consistent handling.
15329
16218
  */
15330
16219
  itemTemplates = computed(() => {
15331
- const arrayField = this.field();
15332
- const definitions = arrayField.fields || [];
16220
+ const raw = this.rawItemTemplates();
15333
16221
  const removeButton = this.autoRemoveButton();
15334
- // Normalize: single FieldDef → [FieldDef], array stays as-is
15335
- // Append auto-remove button if configured (for primitive simplified arrays)
15336
- return definitions.map((def) => {
15337
- const normalized = (Array.isArray(def) ? def : [def]);
15338
- if (removeButton) {
15339
- return [...normalized, removeButton];
15340
- }
15341
- return normalized;
16222
+ if (!removeButton)
16223
+ return raw;
16224
+ return raw.map((normalized) => {
16225
+ return [...normalized, removeButton];
15342
16226
  });
15343
16227
  }, ...(ngDevMode ? [{ debugName: "itemTemplates" }] : /* istanbul ignore next */ []));
15344
16228
  arrayFieldTrees = computed(() => {
@@ -15364,6 +16248,8 @@ class ArrayFieldComponent {
15364
16248
  // ─────────────────────────────────────────────────────────────────────────────
15365
16249
  resolvedItemsSignal = signal([], ...(ngDevMode ? [{ debugName: "resolvedItemsSignal" }] : /* istanbul ignore next */ []));
15366
16250
  updateVersion = signal(0, ...(ngDevMode ? [{ debugName: "updateVersion" }] : /* istanbul ignore next */ []));
16251
+ pendingInitializationCycle = signal(null, ...(ngDevMode ? [{ debugName: "pendingInitializationCycle" }] : /* istanbul ignore next */ []));
16252
+ settledInitializationCycle = signal(null, ...(ngDevMode ? [{ debugName: "settledInitializationCycle" }] : /* istanbul ignore next */ []));
15367
16253
  /**
15368
16254
  * Map of item IDs to their current positions. O(1) lookup vs O(n) indexOf().
15369
16255
  * Used by child linkedSignals to reactively track their position in the array.
@@ -15374,6 +16260,7 @@ class ArrayFieldComponent {
15374
16260
  }, ...(ngDevMode ? [{ debugName: "itemPositionMap" }] : /* istanbul ignore next */ []));
15375
16261
  /** Read-only view of resolved items for template consumption. */
15376
16262
  resolvedItems = computed(() => this.resolvedItemsSignal(), ...(ngDevMode ? [{ debugName: "resolvedItems" }] : /* istanbul ignore next */ []));
16263
+ allResolvedFieldsRenderReady = computed(() => this.resolvedItems().every((item) => item.fields.every((field) => field.renderReady())), ...(ngDevMode ? [{ debugName: "allResolvedFieldsRenderReady" }] : /* istanbul ignore next */ []));
15377
16264
  /**
15378
16265
  * Whether the array has reached its configured maxLength.
15379
16266
  * Exposed as a public signal so add-button components can bind [disabled]="atMaxLength()".
@@ -15412,6 +16299,12 @@ class ArrayFieldComponent {
15412
16299
  explicitEffect([this.arrayFieldTrees], ([fieldTrees]) => {
15413
16300
  this.performDifferentialUpdate(fieldTrees);
15414
16301
  });
16302
+ explicitEffect([this.pendingInitializationCycle, this.settledInitializationCycle, this.allResolvedFieldsRenderReady], ([pending, settled, allReady]) => {
16303
+ if (pending !== null && settled === pending && allReady) {
16304
+ emitComponentInitialized(this.eventBus, 'array', this.field().key, this.parentInjector);
16305
+ this.pendingInitializationCycle.set(null);
16306
+ }
16307
+ });
15415
16308
  }
15416
16309
  setupEventHandlers() {
15417
16310
  observeArrayActions(this.eventBus, () => this.key())
@@ -15428,6 +16321,9 @@ class ArrayFieldComponent {
15428
16321
  }
15429
16322
  void this.handleAddFromEvent(action.template, action.index);
15430
16323
  }
16324
+ else if (action.action === 'move') {
16325
+ this.moveItem(action.fromIndex, action.toIndex);
16326
+ }
15431
16327
  else {
15432
16328
  this.removeItem(action.index);
15433
16329
  }
@@ -15442,9 +16338,13 @@ class ArrayFieldComponent {
15442
16338
  * Supports both primitive (single FieldDef) and object (FieldDef[]) templates.
15443
16339
  */
15444
16340
  async handleAddFromEvent(template, index) {
15445
- // Normalize template to mutable array for consistent handling
16341
+ // Normalize template to mutable array for consistent handling.
16342
+ // A single FieldDef with valueHandling: 'flatten' (container, row) is an object item
16343
+ // whose children should be flattened — NOT a primitive item. Only true leaf fields
16344
+ // (input, checkbox, etc.) are primitive when passed as a single FieldDef.
15446
16345
  const templates = Array.isArray(template) ? [...template] : [template];
15447
- const isPrimitiveItem = !Array.isArray(template);
16346
+ const isSingleField = !Array.isArray(template);
16347
+ const isPrimitiveItem = isSingleField && getFieldValueHandling(templates[0].type, this.rawFieldRegistry()) !== 'flatten';
15448
16348
  // Track primitive field key for full-API arrays that start empty.
15449
16349
  // Simplified arrays already have this info from normalization metadata.
15450
16350
  if (isPrimitiveItem && templates[0].key && !this.primitiveFieldKey()) {
@@ -15472,14 +16372,14 @@ class ArrayFieldComponent {
15472
16372
  for (const templateField of templates) {
15473
16373
  const rawValue = getFieldDefaultValue(templateField, this.rawFieldRegistry());
15474
16374
  const valueHandling = getFieldValueHandling(templateField.type, this.rawFieldRegistry());
15475
- const isContainer = templateField.type === 'group' || templateField.type === 'row';
16375
+ const isContainer = templateField.type === 'group' || templateField.type === 'row' || templateField.type === 'container';
15476
16376
  if (isContainer) {
15477
16377
  if (isGroupField(templateField)) {
15478
16378
  // Groups wrap their fields under the group key
15479
16379
  value = { ...value, [templateField.key]: rawValue };
15480
16380
  }
15481
16381
  else {
15482
- // Rows flatten their fields directly
16382
+ // Rows and containers flatten their fields directly
15483
16383
  value = { ...value, ...rawValue };
15484
16384
  }
15485
16385
  }
@@ -15544,26 +16444,31 @@ class ArrayFieldComponent {
15544
16444
  // Clean up template registry when all items are removed
15545
16445
  this.templateRegistry.clear();
15546
16446
  this.resolvedItemsSignal.set([]);
16447
+ if (resolvedItems.length === 0 && this.pendingInitializationCycle() === null && this.settledInitializationCycle() === null) {
16448
+ this.pendingInitializationCycle.set(currentVersion);
16449
+ this.settledInitializationCycle.set(currentVersion);
16450
+ }
15547
16451
  break;
15548
16452
  case 'initial':
16453
+ this.pendingInitializationCycle.set(currentVersion);
16454
+ this.settledInitializationCycle.set(null);
15549
16455
  void this.resolveAllItems(fieldTrees, currentVersion);
15550
16456
  break;
15551
16457
  case 'append':
15552
16458
  void this.appendItems(fieldTrees, operation.startIndex, operation.endIndex, currentVersion);
15553
16459
  break;
15554
16460
  case 'recreate': {
15555
- // Before clearing, capture templates by position for items beyond defined templates.
15556
- // This allows dynamically added items to be recreated with their original templates.
15557
- const itemTemplates = this.itemTemplates();
15558
- const positionalTemplates = resolvedItems.map((item, idx) => {
15559
- // For items within the defined templates range, use the config directly
15560
- if (idx < itemTemplates.length) {
15561
- return undefined; // Will use itemTemplates[idx] during resolve
15562
- }
15563
- // For dynamically added items, look up their stored template
16461
+ // Capture templates by item ID so each item is recreated with its original template,
16462
+ // even after move operations that change positions. Registry entries (from dynamic adds
16463
+ // or moves) take priority over positional itemTemplates lookup.
16464
+ const positionalTemplates = resolvedItems.map((item) => {
16465
+ // Registry entries (from dynamic adds or moves) take priority;
16466
+ // unmoved initial items return undefined → falls through to itemTemplates[idx] during resolve.
15564
16467
  return this.templateRegistry.get(item.id);
15565
16468
  });
15566
16469
  this.resolvedItemsSignal.set([]);
16470
+ this.pendingInitializationCycle.set(currentVersion);
16471
+ this.settledInitializationCycle.set(null);
15567
16472
  void this.resolveAllItems(fieldTrees, currentVersion, positionalTemplates);
15568
16473
  break;
15569
16474
  }
@@ -15580,7 +16485,7 @@ class ArrayFieldComponent {
15580
16485
  return of(undefined);
15581
16486
  })));
15582
16487
  try {
15583
- const items = await firstValueFrom(forkJoin(safeItemObservables).pipe(map$1((items) => items.filter((item) => item !== undefined))));
16488
+ const items = await firstValueFrom(forkJoin(safeItemObservables).pipe(map((items) => items.filter((item) => item !== undefined))));
15584
16489
  if (updateId === this.updateVersion()) {
15585
16490
  // Update template registry with new item IDs for dynamically added items
15586
16491
  if (positionalTemplates) {
@@ -15599,7 +16504,9 @@ class ArrayFieldComponent {
15599
16504
  });
15600
16505
  }
15601
16506
  this.resolvedItemsSignal.set(items);
15602
- emitComponentInitialized(this.eventBus, 'array', this.field().key, this.parentInjector);
16507
+ if (this.pendingInitializationCycle() === updateId) {
16508
+ this.settledInitializationCycle.set(updateId);
16509
+ }
15603
16510
  }
15604
16511
  }
15605
16512
  catch (err) {
@@ -15620,7 +16527,7 @@ class ArrayFieldComponent {
15620
16527
  }));
15621
16528
  });
15622
16529
  try {
15623
- const newItems = await firstValueFrom(forkJoin(safeItemObservables).pipe(map$1((items) => items.filter((item) => item !== undefined))));
16530
+ const newItems = await firstValueFrom(forkJoin(safeItemObservables).pipe(map((items) => items.filter((item) => item !== undefined))));
15624
16531
  if (updateId === this.updateVersion()) {
15625
16532
  this.resolvedItemsSignal.update((current) => [...current, ...newItems]);
15626
16533
  }
@@ -15701,6 +16608,61 @@ class ArrayFieldComponent {
15701
16608
  this.autoRemoveCache.set(templates, cached);
15702
16609
  return cached;
15703
16610
  }
16611
+ /**
16612
+ * Handles move operations — reorders an existing item without destroying it.
16613
+ * Updates resolvedItems and form value atomically. Since the array length
16614
+ * doesn't change, `determineDifferentialOperation` returns 'none' and no
16615
+ * recreate is triggered. The `@for` loop tracks by `item.id`, so Angular
16616
+ * moves the DOM node instead of destroying/recreating. The `itemPositionMap`
16617
+ * computed auto-recomputes, propagating new indices to child linkedSignals.
16618
+ */
16619
+ moveItem(fromIndex, toIndex) {
16620
+ const arrayKey = this.field().key;
16621
+ const parentForm = this.parentFieldSignalContext.form;
16622
+ const currentValue = parentForm().value();
16623
+ const currentArray = getArrayValue(currentValue, arrayKey);
16624
+ const length = currentArray.length;
16625
+ if (fromIndex < 0 || fromIndex >= length) {
16626
+ this.logger.warn(`moveArrayItem fromIndex ${fromIndex} is out of bounds for array '${arrayKey}' with length ${length}. Operation skipped.`);
16627
+ return;
16628
+ }
16629
+ if (toIndex < 0 || toIndex >= length) {
16630
+ this.logger.warn(`moveArrayItem toIndex ${toIndex} is out of bounds for array '${arrayKey}' with length ${length}. Operation skipped.`);
16631
+ return;
16632
+ }
16633
+ if (fromIndex === toIndex)
16634
+ return;
16635
+ // Register raw (pre-auto-remove) templates for all items whose position will change.
16636
+ // Initial items use itemTemplates[currentIndex] during recreate, but after
16637
+ // a move their position no longer matches their original template. Stashing
16638
+ // the template by item ID lets the recreate path resolve the correct one.
16639
+ // We use rawItemTemplates (without auto-remove button) because the recreate path
16640
+ // calls withAutoRemove() during resolution — storing the synthesized version would
16641
+ // cause duplicate remove buttons.
16642
+ const rawTemplates = this.rawItemTemplates();
16643
+ const currentItems = this.resolvedItemsSignal();
16644
+ const lo = Math.min(fromIndex, toIndex);
16645
+ const hi = Math.max(fromIndex, toIndex);
16646
+ for (let i = lo; i <= hi; i++) {
16647
+ const item = currentItems[i];
16648
+ if (item && !this.templateRegistry.has(item.id) && i < rawTemplates.length) {
16649
+ this.templateRegistry.set(item.id, rawTemplates[i]);
16650
+ }
16651
+ }
16652
+ // Reorder resolvedItems — splice preserves object identity (no destroy/recreate)
16653
+ this.resolvedItemsSignal.update((current) => {
16654
+ const newItems = [...current];
16655
+ const [moved] = newItems.splice(fromIndex, 1);
16656
+ newItems.splice(toIndex, 0, moved);
16657
+ return newItems;
16658
+ });
16659
+ // Reorder form value array the same way
16660
+ const newArray = [...currentArray];
16661
+ const [movedValue] = newArray.splice(fromIndex, 1);
16662
+ newArray.splice(toIndex, 0, movedValue);
16663
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
16664
+ parentForm().value.set({ ...currentValue, [arrayKey]: newArray });
16665
+ }
15704
16666
  /**
15705
16667
  * Handles remove operations from events (pop, shift, removeAt).
15706
16668
  * Updates resolvedItems FIRST, then form value - this ensures differential
@@ -15751,8 +16713,8 @@ class ArrayFieldComponent {
15751
16713
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
15752
16714
  parentForm().value.set({ ...currentValue, [arrayKey]: newArray });
15753
16715
  }
15754
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: ArrayFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
15755
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.6", type: ArrayFieldComponent, isStandalone: true, selector: "array-field", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: true, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, hidden: { classPropertyName: "hidden", publicName: "hidden", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "hostClasses()", "class.df-container-hidden": "hidden()", "attr.aria-hidden": "hidden() || null", "id": "`${key()}`", "attr.data-testid": "key()" } }, providers: [
16716
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ArrayFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
16717
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: ArrayFieldComponent, isStandalone: true, selector: "array-field", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: true, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, hidden: { classPropertyName: "hidden", publicName: "hidden", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "hostClasses()", "class.df-container-hidden": "hidden()", "attr.aria-hidden": "hidden() || null", "id": "`${key()}`", "attr.data-testid": "key()" } }, providers: [
15756
16718
  // Each array gets its own template registry to track templates used for dynamically added items
15757
16719
  { provide: ARRAY_TEMPLATE_REGISTRY, useFactory: () => new Map() },
15758
16720
  // Each array gets its own ID generator for SSR hydration compatibility
@@ -15767,17 +16729,15 @@ class ArrayFieldComponent {
15767
16729
  [attr.data-array-item-index]="i"
15768
16730
  >
15769
16731
  @for (field of item.fields; track $index) {
15770
- <ng-container
15771
- *ngComponentOutlet="field.component; injector: field.injector; environmentInjector: environmentInjector; inputs: field.inputs()"
15772
- />
16732
+ <ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
15773
16733
  }
15774
16734
  </div>
15775
16735
  }
15776
- `, isInline: true, styles: [":host,.df-form{--df-grid-columns: 12;--df-grid-gap: .5rem;--df-grid-row-gap: .5rem;--df-breakpoint-sm: 576px;--df-breakpoint-md: 768px;--df-breakpoint-lg: 992px;--df-breakpoint-xl: 1200px;--df-grid-gap-sm: .5rem;--df-grid-gap-md: .5rem;--df-grid-gap-lg: .5rem;--df-grid-gap-xl: .5rem;--df-grid-row-gap-sm: .5rem;--df-grid-row-gap-md: .5rem;--df-grid-row-gap-lg: .5rem;--df-grid-row-gap-xl: .5rem;--df-array-item-gap: var(--df-grid-row-gap);--df-group-gap: var(--df-grid-gap);--df-group-padding: var(--df-grid-gap)}.df-form{display:grid;grid-template-columns:1fr;gap:var(--df-grid-row-gap);width:100%}.df-form>*{grid-column:1/-1}.df-row{display:grid;grid-template-columns:repeat(var(--df-grid-columns, 12),1fr);gap:var(--df-grid-gap);align-items:start;width:100%}.df-row>*:not([class*=df-col-]){grid-column:1/-1}.df-col-1{grid-column:span 1}.df-col-2{grid-column:span 2}.df-col-3{grid-column:span 3}.df-col-4{grid-column:span 4}.df-col-5{grid-column:span 5}.df-col-6{grid-column:span 6}.df-col-7{grid-column:span 7}.df-col-8{grid-column:span 8}.df-col-9{grid-column:span 9}.df-col-10{grid-column:span 10}.df-col-11{grid-column:span 11}.df-col-12{grid-column:span 12}.df-col-auto{grid-column:span auto;width:auto}.df-col-full{grid-column:1/-1}.df-col-start-1{grid-column-start:1}.df-col-start-2{grid-column-start:2}.df-col-start-3{grid-column-start:3}.df-col-start-4{grid-column-start:4}.df-col-start-5{grid-column-start:5}.df-col-start-6{grid-column-start:6}.df-col-start-7{grid-column-start:7}.df-col-start-8{grid-column-start:8}.df-col-start-9{grid-column-start:9}.df-col-start-10{grid-column-start:10}.df-col-start-11{grid-column-start:11}.df-col-start-12{grid-column-start:12}.df-col-end-1{grid-column-end:1}.df-col-end-2{grid-column-end:2}.df-col-end-3{grid-column-end:3}.df-col-end-4{grid-column-end:4}.df-col-end-5{grid-column-end:5}.df-col-end-6{grid-column-end:6}.df-col-end-7{grid-column-end:7}.df-col-end-8{grid-column-end:8}.df-col-end-9{grid-column-end:9}.df-col-end-10{grid-column-end:10}.df-col-end-11{grid-column-end:11}.df-col-end-12{grid-column-end:12}.df-col-end-13{grid-column-end:13}@media(max-width:576px){.df-form{--df-grid-gap: var(--df-grid-gap-sm);--df-grid-row-gap: var(--df-grid-row-gap-sm)}.df-row{grid-template-columns:1fr}.df-row>*{grid-column:1/-1!important}.df-row.df-row-mobile-keep-cols{grid-template-columns:repeat(var(--df-grid-columns),1fr)}.df-row.df-row-mobile-keep-cols>*{grid-column:revert!important}}@media(min-width:577px)and (max-width:768px){.df-form{--df-grid-gap: var(--df-grid-gap-md);--df-grid-row-gap: var(--df-grid-row-gap-md)}.df-row{--df-grid-columns: 6}.df-col-sm-1{grid-column:span 1}.df-col-sm-2{grid-column:span 2}.df-col-sm-3{grid-column:span 3}.df-col-sm-4{grid-column:span 4}.df-col-sm-5{grid-column:span 5}.df-col-sm-6{grid-column:span 6}.df-col-sm-full{grid-column:1/-1}}@media(min-width:769px)and (max-width:992px){.df-form{--df-grid-gap: var(--df-grid-gap-lg);--df-grid-row-gap: var(--df-grid-row-gap-lg)}.df-col-md-1{grid-column:span 1}.df-col-md-2{grid-column:span 2}.df-col-md-3{grid-column:span 3}.df-col-md-4{grid-column:span 4}.df-col-md-5{grid-column:span 5}.df-col-md-6{grid-column:span 6}.df-col-md-7{grid-column:span 7}.df-col-md-8{grid-column:span 8}.df-col-md-9{grid-column:span 9}.df-col-md-10{grid-column:span 10}.df-col-md-11{grid-column:span 11}.df-col-md-12{grid-column:span 12}.df-col-md-full{grid-column:1/-1}}@media(min-width:993px){.df-form{--df-grid-gap: var(--df-grid-gap-xl);--df-grid-row-gap: var(--df-grid-row-gap-xl)}.df-col-lg-1{grid-column:span 1}.df-col-lg-2{grid-column:span 2}.df-col-lg-3{grid-column:span 3}.df-col-lg-4{grid-column:span 4}.df-col-lg-5{grid-column:span 5}.df-col-lg-6{grid-column:span 6}.df-col-lg-7{grid-column:span 7}.df-col-lg-8{grid-column:span 8}.df-col-lg-9{grid-column:span 9}.df-col-lg-10{grid-column:span 10}.df-col-lg-11{grid-column:span 11}.df-col-lg-12{grid-column:span 12}.df-col-lg-full{grid-column:1/-1}}.df-gap-none{--df-grid-gap: 0}.df-gap-xs{--df-grid-gap: .25rem}.df-gap-sm{--df-grid-gap: .5rem}.df-gap-md{--df-grid-gap: 1rem}.df-gap-lg{--df-grid-gap: 1.5rem}.df-gap-xl{--df-grid-gap: 2rem}.df-row-gap-none{--df-grid-row-gap: 0}.df-row-gap-xs{--df-grid-row-gap: .25rem}.df-row-gap-sm{--df-grid-row-gap: .5rem}.df-row-gap-md{--df-grid-row-gap: 1rem}.df-row-gap-lg{--df-grid-row-gap: 1.5rem}.df-row-gap-xl{--df-grid-row-gap: 2rem}.df-field{display:block;width:100%;min-width:0;overflow:hidden;margin:0}.df-group,.df-page{display:block;width:100%}.df-form.disabled,.df-row.disabled,.df-field.disabled{opacity:.6;pointer-events:none}.df-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.df-sr-only-focusable:focus,.df-sr-only-focusable:active{position:static;width:auto;height:auto;padding:inherit;margin:inherit;overflow:visible;clip:auto;white-space:normal}.df-live-region{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.df-form :focus-visible{outline:2px solid var(--df-focus-color, #005fcc);outline-offset:2px}.df-group:focus-within{outline:1px dashed var(--df-focus-color, #005fcc);outline-offset:4px}:host{display:grid;grid-template-columns:1fr;gap:var(--df-array-item-gap, var(--df-grid-row-gap, .5rem));width:100%}:host>*{grid-column:1/-1}:host.df-container-hidden{display:none}.df-array-item{display:grid;grid-template-columns:1fr;gap:var(--df-grid-row-gap, .5rem);width:100%}.df-array-item>*{grid-column:1/-1}\n"], dependencies: [{ kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
16736
+ `, isInline: true, styles: [":host,.df-form{--df-grid-columns: 12;--df-grid-gap: .5rem;--df-grid-row-gap: .5rem;--df-breakpoint-sm: 576px;--df-breakpoint-md: 768px;--df-breakpoint-lg: 992px;--df-breakpoint-xl: 1200px;--df-grid-gap-sm: .5rem;--df-grid-gap-md: .5rem;--df-grid-gap-lg: .5rem;--df-grid-gap-xl: .5rem;--df-grid-row-gap-sm: .5rem;--df-grid-row-gap-md: .5rem;--df-grid-row-gap-lg: .5rem;--df-grid-row-gap-xl: .5rem;--df-array-item-gap: var(--df-grid-row-gap);--df-group-gap: var(--df-grid-gap);--df-group-padding: var(--df-grid-gap)}.df-form{display:grid;grid-template-columns:1fr;gap:var(--df-grid-row-gap);width:100%}.df-form>*{grid-column:1/-1}.df-row{display:grid;grid-template-columns:repeat(var(--df-grid-columns, 12),1fr);gap:var(--df-grid-gap);align-items:start;width:100%}.df-row>*:not([class*=df-col-]){grid-column:1/-1}.df-col-1{grid-column:span 1}.df-col-2{grid-column:span 2}.df-col-3{grid-column:span 3}.df-col-4{grid-column:span 4}.df-col-5{grid-column:span 5}.df-col-6{grid-column:span 6}.df-col-7{grid-column:span 7}.df-col-8{grid-column:span 8}.df-col-9{grid-column:span 9}.df-col-10{grid-column:span 10}.df-col-11{grid-column:span 11}.df-col-12{grid-column:span 12}.df-col-auto{grid-column:span auto;width:auto}.df-col-full{grid-column:1/-1}.df-col-start-1{grid-column-start:1}.df-col-start-2{grid-column-start:2}.df-col-start-3{grid-column-start:3}.df-col-start-4{grid-column-start:4}.df-col-start-5{grid-column-start:5}.df-col-start-6{grid-column-start:6}.df-col-start-7{grid-column-start:7}.df-col-start-8{grid-column-start:8}.df-col-start-9{grid-column-start:9}.df-col-start-10{grid-column-start:10}.df-col-start-11{grid-column-start:11}.df-col-start-12{grid-column-start:12}.df-col-end-1{grid-column-end:1}.df-col-end-2{grid-column-end:2}.df-col-end-3{grid-column-end:3}.df-col-end-4{grid-column-end:4}.df-col-end-5{grid-column-end:5}.df-col-end-6{grid-column-end:6}.df-col-end-7{grid-column-end:7}.df-col-end-8{grid-column-end:8}.df-col-end-9{grid-column-end:9}.df-col-end-10{grid-column-end:10}.df-col-end-11{grid-column-end:11}.df-col-end-12{grid-column-end:12}.df-col-end-13{grid-column-end:13}@media(max-width:576px){.df-form{--df-grid-gap: var(--df-grid-gap-sm);--df-grid-row-gap: var(--df-grid-row-gap-sm)}.df-row{grid-template-columns:1fr}.df-row>*{grid-column:1/-1!important}.df-row.df-row-mobile-keep-cols{grid-template-columns:repeat(var(--df-grid-columns),1fr)}.df-row.df-row-mobile-keep-cols>*{grid-column:revert!important}}@media(min-width:577px)and (max-width:768px){.df-form{--df-grid-gap: var(--df-grid-gap-md);--df-grid-row-gap: var(--df-grid-row-gap-md)}.df-row{--df-grid-columns: 6}.df-col-sm-1{grid-column:span 1}.df-col-sm-2{grid-column:span 2}.df-col-sm-3{grid-column:span 3}.df-col-sm-4{grid-column:span 4}.df-col-sm-5{grid-column:span 5}.df-col-sm-6{grid-column:span 6}.df-col-sm-full{grid-column:1/-1}}@media(min-width:769px)and (max-width:992px){.df-form{--df-grid-gap: var(--df-grid-gap-lg);--df-grid-row-gap: var(--df-grid-row-gap-lg)}.df-col-md-1{grid-column:span 1}.df-col-md-2{grid-column:span 2}.df-col-md-3{grid-column:span 3}.df-col-md-4{grid-column:span 4}.df-col-md-5{grid-column:span 5}.df-col-md-6{grid-column:span 6}.df-col-md-7{grid-column:span 7}.df-col-md-8{grid-column:span 8}.df-col-md-9{grid-column:span 9}.df-col-md-10{grid-column:span 10}.df-col-md-11{grid-column:span 11}.df-col-md-12{grid-column:span 12}.df-col-md-full{grid-column:1/-1}}@media(min-width:993px){.df-form{--df-grid-gap: var(--df-grid-gap-xl);--df-grid-row-gap: var(--df-grid-row-gap-xl)}.df-col-lg-1{grid-column:span 1}.df-col-lg-2{grid-column:span 2}.df-col-lg-3{grid-column:span 3}.df-col-lg-4{grid-column:span 4}.df-col-lg-5{grid-column:span 5}.df-col-lg-6{grid-column:span 6}.df-col-lg-7{grid-column:span 7}.df-col-lg-8{grid-column:span 8}.df-col-lg-9{grid-column:span 9}.df-col-lg-10{grid-column:span 10}.df-col-lg-11{grid-column:span 11}.df-col-lg-12{grid-column:span 12}.df-col-lg-full{grid-column:1/-1}}.df-gap-none{--df-grid-gap: 0}.df-gap-xs{--df-grid-gap: .25rem}.df-gap-sm{--df-grid-gap: .5rem}.df-gap-md{--df-grid-gap: 1rem}.df-gap-lg{--df-grid-gap: 1.5rem}.df-gap-xl{--df-grid-gap: 2rem}.df-row-gap-none{--df-grid-row-gap: 0}.df-row-gap-xs{--df-grid-row-gap: .25rem}.df-row-gap-sm{--df-grid-row-gap: .5rem}.df-row-gap-md{--df-grid-row-gap: 1rem}.df-row-gap-lg{--df-grid-row-gap: 1.5rem}.df-row-gap-xl{--df-grid-row-gap: 2rem}.df-field{display:block;width:100%;min-width:0;overflow:hidden;margin:0}.df-group,.df-page{display:block;width:100%}.df-form.disabled,.df-row.disabled,.df-field.disabled{opacity:.6;pointer-events:none}.df-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.df-sr-only-focusable:focus,.df-sr-only-focusable:active{position:static;width:auto;height:auto;padding:inherit;margin:inherit;overflow:visible;clip:auto;white-space:normal}.df-live-region{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.df-form :focus-visible{outline:2px solid var(--df-focus-color, #005fcc);outline-offset:2px}.df-group:focus-within{outline:1px dashed var(--df-focus-color, #005fcc);outline-offset:4px}:host{display:grid;grid-template-columns:1fr;gap:var(--df-array-item-gap, var(--df-grid-row-gap, .5rem));width:100%}:host>*{grid-column:1/-1}:host.df-container-hidden{display:none}.df-array-item{display:grid;grid-template-columns:1fr;gap:var(--df-grid-row-gap, .5rem);width:100%}.df-array-item>*{grid-column:1/-1}\n"], dependencies: [{ kind: "directive", type: DfFieldOutlet, selector: "[dfFieldOutlet]", inputs: ["dfFieldOutlet", "dfFieldOutletEnvironmentInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
15777
16737
  }
15778
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: ArrayFieldComponent, decorators: [{
16738
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ArrayFieldComponent, decorators: [{
15779
16739
  type: Component,
15780
- args: [{ selector: 'array-field', imports: [NgComponentOutlet], template: `
16740
+ args: [{ selector: 'array-field', imports: [DfFieldOutlet], template: `
15781
16741
  @for (item of resolvedItems(); track item.id; let i = $index) {
15782
16742
  <div
15783
16743
  class="df-array-item"
@@ -15787,9 +16747,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImpor
15787
16747
  [attr.data-array-item-index]="i"
15788
16748
  >
15789
16749
  @for (field of item.fields; track $index) {
15790
- <ng-container
15791
- *ngComponentOutlet="field.component; injector: field.injector; environmentInjector: environmentInjector; inputs: field.inputs()"
15792
- />
16750
+ <ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
15793
16751
  }
15794
16752
  </div>
15795
16753
  }
@@ -15814,12 +16772,95 @@ var arrayField_component = /*#__PURE__*/Object.freeze({
15814
16772
  });
15815
16773
 
15816
16774
  /**
15817
- * Get the length of an array FieldTree.
16775
+ * Layout container that wraps child fields with UI chrome.
16776
+ *
16777
+ * Resolves children like a row, then chains wrapper components around them
16778
+ * using imperative `ViewContainerRef.createComponent()` — each wrapper's
16779
+ * `#fieldComponent` slot hosts the next wrapper or the children template.
16780
+ *
16781
+ * Does not create a new form context - fields share the parent's context.
16782
+ * Field values are flattened into the parent form (no nesting under container key).
16783
+ * Purely a visual/layout container with no impact on form structure.
15818
16784
  */
15819
- function getArrayLength(arrayFieldTree) {
15820
- const value = arrayFieldTree().value();
15821
- return Array.isArray(value) ? value.length : 0;
16785
+ class ContainerFieldComponent {
16786
+ destroyRef = inject(DestroyRef);
16787
+ fieldRegistry = injectFieldRegistry();
16788
+ injector = inject(Injector);
16789
+ environmentInjector = inject(EnvironmentInjector);
16790
+ eventBus = inject(EventBus);
16791
+ logger = inject(DynamicFormLogger);
16792
+ wrapperAutoAssociations = inject(WRAPPER_AUTO_ASSOCIATIONS);
16793
+ defaultWrappersSignal = inject(DEFAULT_WRAPPERS, { optional: true });
16794
+ childrenTpl = viewChild.required('childrenTpl', { read: TemplateRef });
16795
+ wrapperContainer = viewChild.required('wrapperContainer', { read: ViewContainerRef });
16796
+ field = input.required(...(ngDevMode ? [{ debugName: "field" }] : /* istanbul ignore next */ []));
16797
+ key = input.required(...(ngDevMode ? [{ debugName: "key" }] : /* istanbul ignore next */ []));
16798
+ className = input(...(ngDevMode ? [undefined, { debugName: "className" }] : /* istanbul ignore next */ []));
16799
+ hidden = input(false, ...(ngDevMode ? [{ debugName: "hidden" }] : /* istanbul ignore next */ []));
16800
+ hostClasses = computed(() => computeContainerHostClasses('container', this.className()), ...(ngDevMode ? [{ debugName: "hostClasses" }] : /* istanbul ignore next */ []));
16801
+ disabled = computed(() => this.field().disabled || false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
16802
+ rawFieldRegistry = computed(() => this.fieldRegistry.raw, ...(ngDevMode ? [{ debugName: "rawFieldRegistry" }] : /* istanbul ignore next */ []));
16803
+ fieldsSource = computed(() => (this.field().fields || []), ...(ngDevMode ? [{ debugName: "fieldsSource" }] : /* istanbul ignore next */ []));
16804
+ resolvedFields = derivedFromDeferred(this.fieldsSource, createFieldResolutionPipe(() => ({
16805
+ loadTypeComponent: (type) => this.fieldRegistry.loadTypeComponent(type),
16806
+ registry: this.rawFieldRegistry(),
16807
+ injector: this.injector,
16808
+ destroyRef: this.destroyRef,
16809
+ onError: (fieldDef, error) => {
16810
+ const fieldKey = fieldDef.key || '<no key>';
16811
+ const containerKey = this.field().key || '<no key>';
16812
+ this.logger.error(`Failed to load component for field type '${fieldDef.type}' (key: ${fieldKey}) ` +
16813
+ `within container '${containerKey}'. Ensure the field type is registered in your field registry.`, error);
16814
+ },
16815
+ })), { initialValue: [], injector: this.injector });
16816
+ wrappers = computed(() => resolveWrappers(this.field(), this.defaultWrappersSignal?.(), this.wrapperAutoAssociations), { ...(ngDevMode ? { debugName: "wrappers" } : /* istanbul ignore next */ {}), equal: isSameWrapperChain });
16817
+ constructor() {
16818
+ // `row` is a virtual field type backed by ContainerFieldComponent — at
16819
+ // runtime the spread in rowFieldMapper preserves `type: 'row'` on the
16820
+ // field input, even though the declared type is `ContainerField`. Widen
16821
+ // here so `(events)` consumers filtering by `componentType === 'row'`
16822
+ // keep receiving emissions.
16823
+ setupContainerInitEffect(this.resolvedFields, this.eventBus, () => (this.field().type === 'row' ? 'row' : 'container'), () => this.field().key, this.injector);
16824
+ createWrapperChainController({
16825
+ vcr: this.wrapperContainer,
16826
+ wrappers: this.wrappers,
16827
+ renderInnermost: (slot) => slot.createEmbeddedView(this.childrenTpl()),
16828
+ });
16829
+ }
16830
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ContainerFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
16831
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: ContainerFieldComponent, isStandalone: true, selector: "div[container-field]", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: true, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, hidden: { classPropertyName: "hidden", publicName: "hidden", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "hostClasses()", "class.disabled": "disabled()", "class.df-container-hidden": "hidden()", "attr.aria-hidden": "hidden() || null", "id": "`${key()}`", "attr.data-testid": "key()" } }, viewQueries: [{ propertyName: "childrenTpl", first: true, predicate: ["childrenTpl"], descendants: true, read: TemplateRef, isSignal: true }, { propertyName: "wrapperContainer", first: true, predicate: ["wrapperContainer"], descendants: true, read: ViewContainerRef, isSignal: true }], ngImport: i0, template: `
16832
+ <ng-template #childrenTpl>
16833
+ @for (field of resolvedFields(); track field.key) {
16834
+ <ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
16835
+ }
16836
+ </ng-template>
16837
+ <ng-container #wrapperContainer></ng-container>
16838
+ `, isInline: true, styles: [":host{display:block;width:100%}:host.df-container-hidden{display:none}\n"], dependencies: [{ kind: "directive", type: DfFieldOutlet, selector: "[dfFieldOutlet]", inputs: ["dfFieldOutlet", "dfFieldOutletEnvironmentInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
15822
16839
  }
16840
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ContainerFieldComponent, decorators: [{
16841
+ type: Component,
16842
+ args: [{ selector: 'div[container-field]', imports: [DfFieldOutlet], template: `
16843
+ <ng-template #childrenTpl>
16844
+ @for (field of resolvedFields(); track field.key) {
16845
+ <ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
16846
+ }
16847
+ </ng-template>
16848
+ <ng-container #wrapperContainer></ng-container>
16849
+ `, host: {
16850
+ '[class]': 'hostClasses()',
16851
+ '[class.disabled]': 'disabled()',
16852
+ '[class.df-container-hidden]': 'hidden()',
16853
+ '[attr.aria-hidden]': 'hidden() || null',
16854
+ '[id]': '`${key()}`',
16855
+ '[attr.data-testid]': 'key()',
16856
+ }, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;width:100%}:host.df-container-hidden{display:none}\n"] }]
16857
+ }], ctorParameters: () => [], propDecorators: { childrenTpl: [{ type: i0.ViewChild, args: ['childrenTpl', { ...{ read: TemplateRef }, isSignal: true }] }], wrapperContainer: [{ type: i0.ViewChild, args: ['wrapperContainer', { ...{ read: ViewContainerRef }, isSignal: true }] }], field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: true }] }], key: [{ type: i0.Input, args: [{ isSignal: true, alias: "key", required: true }] }], className: [{ type: i0.Input, args: [{ isSignal: true, alias: "className", required: false }] }], hidden: [{ type: i0.Input, args: [{ isSignal: true, alias: "hidden", required: false }] }] } });
16858
+
16859
+ var containerField_component = /*#__PURE__*/Object.freeze({
16860
+ __proto__: null,
16861
+ ContainerFieldComponent: ContainerFieldComponent,
16862
+ default: ContainerFieldComponent
16863
+ });
15823
16864
 
15824
16865
  /**
15825
16866
  * Interpolates {{param}} placeholders in message with error values
@@ -15952,5 +16993,5 @@ function extractErrorParams(error) {
15952
16993
  * Generated bundle index. Do not edit.
15953
16994
  */
15954
16995
 
15955
- export { hasFormValue as $, ARRAY_CONTEXT as A, BUILT_IN_FIELDS as B, ConsoleLogger as C, DynamicFormLogger as D, EventBus as E, FIELD_REGISTRY as F, GroupFieldComponent as G, applyMetaToElement as H, INITIALIZATION_TIMEOUT_MS as I, applyValidator as J, applyValidators as K, arrayEvent as L, arrayFieldMapper as M, NextPageEvent as N, baseFieldMapper as O, PageChangeEvent as P, buildBaseInputs as Q, RemoveAtIndexEvent as R, ShiftArrayItemEvent as S, createField as T, dynamicTextToObservable as U, evaluateNonFieldDisabled as V, evaluateNonFieldHidden as W, field as X, formConfig as Y, getArrayLength as Z, groupFieldMapper as _, createFieldResolutionPipe as a, interpolateParams as a0, isArrayField as a1, isCheckedField as a2, isContainerField as a3, isDisplayOnlyField as a4, isEqual as a5, isFormStateCondition as a6, isGroupField as a7, isLeafField as a8, isPageField as a9, isRowField as aa, isSimplifiedArrayField as ab, isValueBearingField as ac, isValueField as ad, omit as ae, pageFieldMapper as af, provideDynamicForm as ag, resolveNextButtonDisabled as ah, resolveNonFieldDisabled as ai, resolveNonFieldHidden as aj, resolveSubmitButtonDisabled as ak, resolveTokens as al, rowFieldMapper as am, textFieldMapper as an, withEventFormValue as ao, withLoggerConfig as ap, withPreviousValue as aq, withValueExclusionDefaults as ar, DynamicTextPipe as b, computeContainerHostClasses as c, derivedFromDeferred as d, AppendArrayItemEvent as e, ArrayFieldComponent as f, DEFAULT_PROPS as g, DEFAULT_VALIDATION_MESSAGES as h, injectFieldRegistry as i, DynamicForm as j, DynamicFormError as k, EventDispatcher as l, FIELD_SIGNAL_CONTEXT as m, FORM_OPTIONS as n, FormClearEvent as o, FormResetEvent as p, InsertArrayItemEvent as q, NoopLogger as r, setupContainerInitEffect as s, PopArrayItemEvent as t, PrependArrayItemEvent as u, validatePageNesting as v, PreviousPageEvent as w, RootFormRegistryService as x, RowFieldComponent as y, SubmitEvent as z };
15956
- //# sourceMappingURL=ng-forge-dynamic-forms-ng-forge-dynamic-forms-Sad9wa1d.mjs.map
16996
+ export { evaluateNonFieldDisabled as $, ARRAY_CONTEXT as A, BUILT_IN_FIELDS as B, ConsoleLogger as C, DynamicFormLogger as D, EventBus as E, FIELD_REGISTRY as F, GroupFieldComponent as G, PreviousPageEvent as H, INITIALIZATION_TIMEOUT_MS as I, RootFormRegistryService as J, SubmitEvent as K, applyMetaToElement as L, MoveArrayItemEvent as M, NextPageEvent as N, applyValidator as O, PageChangeEvent as P, applyValidators as Q, RemoveAtIndexEvent as R, ShiftArrayItemEvent as S, arrayEvent as T, arrayFieldMapper as U, baseFieldMapper as V, WRAPPER_REGISTRY as W, buildBaseInputs as X, containerFieldMapper as Y, createField as Z, createWrappers as _, createFieldResolutionPipe as a, evaluateNonFieldHidden as a0, field as a1, formConfig as a2, getArrayLength as a3, groupFieldMapper as a4, hasFormValue as a5, interpolateParams as a6, isArrayField as a7, isCheckedField as a8, isContainerField as a9, withPreviousValue as aA, withValueExclusionDefaults as aB, wrapperProps as aC, isContainerTypedField as aa, isDisplayOnlyField as ab, isEqual as ac, isFormStateCondition as ad, isGroupField as ae, isLeafField as af, isPageField as ag, isRowField as ah, isSimplifiedArrayField as ai, isValueBearingField as aj, isValueField as ak, isWrapperTypeDefinition as al, isWrappersBundle as am, omit as an, pageFieldMapper as ao, provideDynamicForm as ap, resolveNextButtonDisabled as aq, resolveNonFieldDisabled as ar, resolveNonFieldHidden as as, resolveSubmitButtonDisabled as at, resolveTokens as au, rowFieldMapper as av, textFieldMapper as aw, toReadonlyFieldTree as ax, withEventFormValue as ay, withLoggerConfig as az, DfFieldOutlet as b, computeContainerHostClasses as c, derivedFromDeferred as d, DynamicTextPipe as e, dynamicTextToObservable as f, AppendArrayItemEvent as g, ArrayFieldComponent as h, injectFieldRegistry as i, ContainerFieldComponent as j, DEFAULT_PROPS as k, DEFAULT_VALIDATION_MESSAGES as l, DEFAULT_WRAPPERS as m, DynamicForm as n, DynamicFormError as o, EventDispatcher as p, FIELD_SIGNAL_CONTEXT as q, FORM_OPTIONS as r, setupContainerInitEffect as s, FormClearEvent as t, FormResetEvent as u, validatePageNesting as v, InsertArrayItemEvent as w, NoopLogger as x, PopArrayItemEvent as y, PrependArrayItemEvent as z };
16997
+ //# sourceMappingURL=ng-forge-dynamic-forms-ng-forge-dynamic-forms-D276M2vh.mjs.map