@ng-forge/dynamic-forms 1.2.0-next.6 → 1.2.0-next.8

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 (34) hide show
  1. package/fesm2022/{ng-forge-dynamic-forms-array-field.component-NTHLPjau.mjs → ng-forge-dynamic-forms-array-field.component-6yyDriVB.mjs} +6 -10
  2. package/fesm2022/ng-forge-dynamic-forms-array-field.component-6yyDriVB.mjs.map +1 -0
  3. package/fesm2022/{ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-CNCeWAeJ.mjs → ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-BB8jAcY4.mjs} +29 -7
  4. package/fesm2022/ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-BB8jAcY4.mjs.map +1 -0
  5. package/fesm2022/{ng-forge-dynamic-forms-container-field.component-ByGdzAsk.mjs → ng-forge-dynamic-forms-container-field.component-DfQ0y-77.mjs} +7 -11
  6. package/fesm2022/ng-forge-dynamic-forms-container-field.component-DfQ0y-77.mjs.map +1 -0
  7. package/fesm2022/ng-forge-dynamic-forms-debounce-C9F4v1Ke.mjs +17 -0
  8. package/fesm2022/ng-forge-dynamic-forms-debounce-C9F4v1Ke.mjs.map +1 -0
  9. package/fesm2022/ng-forge-dynamic-forms-form-schema.service-BzbBv-r0.mjs +24 -0
  10. package/fesm2022/ng-forge-dynamic-forms-form-schema.service-BzbBv-r0.mjs.map +1 -0
  11. package/fesm2022/{ng-forge-dynamic-forms-group-field.component-DnLZdj8v.mjs → ng-forge-dynamic-forms-group-field.component-DxHD5I8E.mjs} +6 -10
  12. package/fesm2022/ng-forge-dynamic-forms-group-field.component-DxHD5I8E.mjs.map +1 -0
  13. package/fesm2022/ng-forge-dynamic-forms-internal.mjs +36 -5
  14. package/fesm2022/ng-forge-dynamic-forms-internal.mjs.map +1 -1
  15. package/fesm2022/{ng-forge-dynamic-forms-ng-forge-dynamic-forms-B2C_WAMP.mjs → ng-forge-dynamic-forms-ng-forge-dynamic-forms-tA8ZkP8i.mjs} +1399 -1354
  16. package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-tA8ZkP8i.mjs.map +1 -0
  17. package/fesm2022/{ng-forge-dynamic-forms-page-field.component-CwH22lRl.mjs → ng-forge-dynamic-forms-page-field.component-DCdBvJ-h.mjs} +54 -13
  18. package/fesm2022/ng-forge-dynamic-forms-page-field.component-DCdBvJ-h.mjs.map +1 -0
  19. package/fesm2022/ng-forge-dynamic-forms-schema-builder-IsjNBo0h.mjs +570 -0
  20. package/fesm2022/ng-forge-dynamic-forms-schema-builder-IsjNBo0h.mjs.map +1 -0
  21. package/fesm2022/ng-forge-dynamic-forms.mjs +1 -1
  22. package/package.json +2 -2
  23. package/types/ng-forge-dynamic-forms-internal.d.ts +25 -6
  24. package/types/ng-forge-dynamic-forms.d.ts +80 -19
  25. package/fesm2022/ng-forge-dynamic-forms-array-field.component-NTHLPjau.mjs.map +0 -1
  26. package/fesm2022/ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-CNCeWAeJ.mjs.map +0 -1
  27. package/fesm2022/ng-forge-dynamic-forms-container-field.component-ByGdzAsk.mjs.map +0 -1
  28. package/fesm2022/ng-forge-dynamic-forms-container-utils-BZFz6ztF.mjs +0 -24
  29. package/fesm2022/ng-forge-dynamic-forms-container-utils-BZFz6ztF.mjs.map +0 -1
  30. package/fesm2022/ng-forge-dynamic-forms-emit-initialization-DhHwU6FJ.mjs +0 -17
  31. package/fesm2022/ng-forge-dynamic-forms-emit-initialization-DhHwU6FJ.mjs.map +0 -1
  32. package/fesm2022/ng-forge-dynamic-forms-group-field.component-DnLZdj8v.mjs.map +0 -1
  33. package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-B2C_WAMP.mjs.map +0 -1
  34. package/fesm2022/ng-forge-dynamic-forms-page-field.component-CwH22lRl.mjs.map +0 -1
@@ -1,14 +1,132 @@
1
1
  import * as i0 from '@angular/core';
2
- import { reflectComponentType, Injector, inject, EnvironmentInjector, DestroyRef, computed, signal, input, ViewContainerRef, Directive, InjectionToken, linkedSignal, untracked, ChangeDetectionStrategy, Component, Injectable, isSignal, runInInjectionContext, afterNextRender, PendingTasks, ElementRef, PLATFORM_ID, isDevMode, TemplateRef, model, contentChildren, makeEnvironmentProviders } from '@angular/core';
2
+ import { InjectionToken, inject, PLATFORM_ID, DestroyRef, Injectable, reflectComponentType, Injector, EnvironmentInjector, computed, signal, afterNextRender, input, ViewContainerRef, Directive, isSignal, runInInjectionContext, untracked, linkedSignal, injectAsync, resource, ChangeDetectionStrategy, Component, PendingTasks, ElementRef, isDevMode, TemplateRef, model, contentChildren, makeEnvironmentProviders } from '@angular/core';
3
3
  import { explicitEffect } from 'ngxtension/explicit-effect';
4
- import { DynamicFormLogger, WRAPPER_COMPONENT_CACHE, WRAPPER_REGISTRY, toReadonlyFieldTreeCached, writeToFieldValue, WRAPPER_AUTO_ASSOCIATIONS, DEFAULT_WRAPPERS, READONLY_FIELD_TREE_CACHE, getGridClassString, EventBus, FieldContextRegistryService, FunctionRegistryService, FORM_OPTIONS, evaluateCondition, FIELD_SIGNAL_CONTEXT, isRowField, isGenericContainerField, isContainerField, isArrayField, hasChildFields, isSimplifiedArrayField, DynamicFormError, isStateLogicConfig, isDerivationLogicConfig, isFormStateCondition, createFormStateLogicFunction, createDebouncedLogicFunction, createLogicFunction, ExpressionParser, applyValidator, isPageField, isGroupField, getFieldValueHandling, normalizeFieldsArray, keyBy, mapValues, isEqual, hasTargetProperty, ARRAY_CONTEXT, GROUP_CONTEXT, FORM_ID_PREFIX, baseFieldMapper, detectFormMode, validatePageNesting, FIELD_REGISTRY, assertNever, ADDON_TYPE_REGISTRY, deepMergeDefaults, derivedFromDeferred, createWarningTracker, RootFormRegistryService, DEFAULT_PROPS, DEFAULT_VALIDATION_MESSAGES, EXTERNAL_DATA, DEPRECATION_WARNING_TRACKER, createAddonActionRegistry, ADDON_ACTION_REGISTRY, HttpConditionCache, HTTP_CONDITION_CACHE, HttpConditionFunctionCacheService, AsyncConditionFunctionCacheService, DynamicValueFunctionCacheService, LogicFunctionCacheService, setupInitializationTracking, ComponentInitializedEvent, DF_FIELD_TEMPLATES, rowFieldMapper, groupFieldMapper, arrayFieldMapper, pageFieldMapper, textFieldMapper, containerFieldMapper, isWrapperTypeDefinition, ADDON_TYPE_COMPONENT_CACHE, ADDON_TYPE_DEFINITIONS, createDefaultDerivationLogConfig, NoopLogger, EMIT_FORM_VALUE_ON_EVENTS, DynamicTextPipe, ADDON_ACTION_HANDLERS } from '@ng-forge/dynamic-forms/internal';
5
- import { takeUntilDestroyed, toObservable, toSignal, outputFromObservable } from '@angular/core/rxjs-interop';
6
- import { of, forkJoin, from, catchError, map, switchMap, isObservable, firstValueFrom, exhaustMap, EMPTY, debounceTime, distinctUntilChanged, tap, pipe, scan, Observable, Subject, concatMap, filter, take } from 'rxjs';
7
- import { submit, required, disabled, readonly, hidden, applyEach, applyWhenValue, applyWhen, apply, email, minLength, maxLength, pattern, min, max, schema, validateStandardSchema, form, provideSignalFormsConfig } from '@angular/forms/signals';
8
- import { isStandardSchemaMarker } from '@ng-forge/dynamic-forms/schema';
4
+ import { derivedFrom } from 'ngxtension/derived-from';
5
+ import { of, Observable, distinctUntilChanged, forkJoin, from, catchError, map, switchMap, pipe, isObservable, firstValueFrom, exhaustMap, EMPTY, scan, Subject, concatMap, filter, take, shareReplay } from 'rxjs';
6
+ import { DynamicFormLogger, WRAPPER_COMPONENT_LOAD_CACHE, WRAPPER_COMPONENT_CACHE, WRAPPER_REGISTRY, toReadonlyFieldTreeCached, writeToFieldValue, ComponentInitializedEvent, isPageField, isGroupField, isArrayField, isRowField, isGenericContainerField, hasChildFields, normalizeFieldsArray, WRAPPER_AUTO_ASSOCIATIONS, DEFAULT_WRAPPERS, READONLY_FIELD_TREE_CACHE, EventBus, getGridClassString, FORM_OPTIONS, GROUP_CONTEXT, FIELD_REGISTRY, DynamicFormError, isContainerField, isSimplifiedArrayField, getFieldValueHandling, keyBy, mapValues, isEqual, isDerivationLogicConfig, hasTargetProperty, ARRAY_CONTEXT, FORM_ID_PREFIX, baseFieldMapper, detectFormMode, validatePageNesting, assertNever, FunctionRegistryService, ADDON_TYPE_REGISTRY, deepMergeDefaults, FIELD_SIGNAL_CONTEXT, derivedFromDeferred, FieldContextRegistryService, evaluateCondition, createWarningTracker, RootFormRegistryService, DEFAULT_PROPS, DEFAULT_VALIDATION_MESSAGES, EXTERNAL_DATA, DEPRECATION_WARNING_TRACKER, createAddonActionRegistry, ADDON_ACTION_REGISTRY, HttpConditionCache, HTTP_CONDITION_CACHE, HttpConditionFunctionCacheService, AsyncConditionFunctionCacheService, DynamicValueFunctionCacheService, LogicFunctionCacheService, DF_FIELD_TEMPLATES, rowFieldMapper, groupFieldMapper, arrayFieldMapper, pageFieldMapper, textFieldMapper, containerFieldMapper, isWrapperTypeDefinition, ADDON_TYPE_COMPONENT_CACHE, ADDON_TYPE_DEFINITIONS, createDefaultDerivationLogConfig, NoopLogger, EMIT_FORM_VALUE_ON_EVENTS, DynamicTextPipe, ADDON_ACTION_HANDLERS } from '@ng-forge/dynamic-forms/internal';
9
7
  import { isPlatformBrowser, AsyncPipe, NgTemplateOutlet, NgComponentOutlet } from '@angular/common';
8
+ import { takeUntilDestroyed, toObservable, outputFromObservable } from '@angular/core/rxjs-interop';
9
+ import { submit, form, provideSignalFormsConfig } from '@angular/forms/signals';
10
10
  import { NG_STATUS_CLASSES } from '@angular/forms/signals/compat';
11
11
 
12
+ const FIELD_WINDOWING = new InjectionToken('FIELD_WINDOWING', {
13
+ providedIn: 'root',
14
+ factory: () => ({ enabled: false, eager: 12, placeholderHeight: '4rem', park: { enabled: false, margin: '100%' } }),
15
+ });
16
+
17
+ function clampWindowSize(value, fallback) {
18
+ if (value === undefined || !Number.isFinite(value))
19
+ return fallback;
20
+ return Math.max(0, Math.floor(value));
21
+ }
22
+
23
+ const DEFAULT_MARGIN = '100%';
24
+ const ROOT_MARGIN_PART = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?(?:px|%)$/i;
25
+ function normalizeFieldParkingMargin(value, fallback = DEFAULT_MARGIN) {
26
+ const normalizedFallback = isValidRootMargin(fallback) ? fallback.trim() : DEFAULT_MARGIN;
27
+ if (!value || !isValidRootMargin(value))
28
+ return normalizedFallback;
29
+ return value.trim().split(/\s+/).join(' ');
30
+ }
31
+ function isValidRootMargin(value) {
32
+ const parts = value.trim().split(/\s+/);
33
+ return parts.length >= 1 && parts.length <= 4 && parts.every((part) => ROOT_MARGIN_PART.test(part));
34
+ }
35
+
36
+ function resolveFieldWindowing(global, perForm) {
37
+ if (perForm === undefined) {
38
+ return global;
39
+ }
40
+ if (typeof perForm === 'boolean') {
41
+ return { ...global, enabled: perForm };
42
+ }
43
+ const configuresMounting = 'eager' in perForm || 'placeholderHeight' in perForm || Object.keys(perForm).length === 0;
44
+ return {
45
+ enabled: configuresMounting ? true : global.enabled,
46
+ eager: clampWindowSize(perForm.eager, global.eager),
47
+ placeholderHeight: perForm.placeholderHeight ?? global.placeholderHeight,
48
+ park: resolveFieldParking(global.park, perForm.park),
49
+ };
50
+ }
51
+ function resolveFieldParking(global, perForm) {
52
+ if (perForm === undefined)
53
+ return global;
54
+ if (typeof perForm === 'boolean')
55
+ return { ...global, enabled: perForm };
56
+ return { enabled: true, margin: normalizeFieldParkingMargin(perForm.margin, global.margin) };
57
+ }
58
+
59
+ const ALWAYS_VISIBLE = of(true);
60
+ class FieldViewportObserver {
61
+ isBrowser = isPlatformBrowser(inject(PLATFORM_ID)) && typeof IntersectionObserver !== 'undefined';
62
+ observers = new Map();
63
+ constructor() {
64
+ inject(DestroyRef).onDestroy(() => {
65
+ for (const { observer } of this.observers.values())
66
+ observer.disconnect();
67
+ this.observers.clear();
68
+ });
69
+ }
70
+ observe(el, rootMargin) {
71
+ if (!this.isBrowser)
72
+ return ALWAYS_VISIBLE;
73
+ rootMargin = normalizeFieldParkingMargin(rootMargin);
74
+ return new Observable((subscriber) => {
75
+ const listener = (visible) => subscriber.next(visible);
76
+ this.startObserving(el, rootMargin, listener);
77
+ subscriber.next(true);
78
+ return () => this.stopObserving(el, rootMargin, listener);
79
+ }).pipe(distinctUntilChanged());
80
+ }
81
+ startObserving(el, rootMargin, listener) {
82
+ const entry = this.observerFor(rootMargin);
83
+ const listeners = entry.listeners.get(el);
84
+ if (listeners) {
85
+ listeners.add(listener);
86
+ return;
87
+ }
88
+ entry.listeners.set(el, new Set([listener]));
89
+ entry.observer.observe(el);
90
+ }
91
+ stopObserving(el, rootMargin, listener) {
92
+ const entry = this.observers.get(rootMargin);
93
+ if (!entry)
94
+ return;
95
+ const listeners = entry.listeners.get(el);
96
+ if (!listeners)
97
+ return;
98
+ listeners.delete(listener);
99
+ if (listeners.size > 0)
100
+ return;
101
+ entry.observer.unobserve(el);
102
+ entry.listeners.delete(el);
103
+ if (entry.listeners.size > 0)
104
+ return;
105
+ entry.observer.disconnect();
106
+ this.observers.delete(rootMargin);
107
+ }
108
+ observerFor(rootMargin) {
109
+ const existing = this.observers.get(rootMargin);
110
+ if (existing)
111
+ return existing;
112
+ const listeners = new Map();
113
+ const observer = new IntersectionObserver((entries) => {
114
+ for (const entry of entries) {
115
+ for (const listener of listeners.get(entry.target) ?? [])
116
+ listener(entry.isIntersecting);
117
+ }
118
+ }, { rootMargin });
119
+ const entry = { observer, listeners };
120
+ this.observers.set(rootMargin, entry);
121
+ return entry;
122
+ }
123
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: FieldViewportObserver, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
124
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: FieldViewportObserver });
125
+ }
126
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: FieldViewportObserver, decorators: [{
127
+ type: Injectable
128
+ }], ctorParameters: () => [] });
129
+
12
130
  const EMPTY_WRAPPERS = Object.freeze([]);
13
131
  const FIELD_ERRORS_WRAPPER = 'field-errors';
14
132
  const FIELD_ERRORS_CONFIG = Object.freeze({ type: FIELD_ERRORS_WRAPPER });
@@ -45,6 +163,8 @@ function needsErrorWrapper(field, composed, wrapperRegistry) {
45
163
  return !composed.some((w) => w.type === FIELD_ERRORS_WRAPPER || wrapperRegistry.get(w.type)?.rendersFieldErrors);
46
164
  }
47
165
 
166
+ const DEV_MODE = typeof ngDevMode === 'undefined' || !!ngDevMode;
167
+
48
168
  const inputNamesCache = new WeakMap();
49
169
  function getDeclaredInputs(componentType) {
50
170
  let inputs = inputNamesCache.get(componentType);
@@ -66,24 +186,33 @@ function hasDefaultExport(value) {
66
186
  function resolveDefaultExport(result) {
67
187
  return hasDefaultExport(result) ? result.default : result;
68
188
  }
69
- async function loadWrapperComponent(type, registry, cache) {
189
+ async function loadWrapperComponent(type, registry, cache, loadCache) {
70
190
  const cached = cache.get(type);
71
191
  if (cached)
72
192
  return cached;
193
+ const pending = loadCache?.get(type);
194
+ if (pending)
195
+ return pending;
73
196
  const definition = registry.get(type);
74
197
  if (!definition)
75
198
  return undefined;
76
- const component = resolveDefaultExport(await definition.loadComponent());
77
- if (component)
78
- cache.set(type, component);
79
- return component;
80
- }
81
- function loadWrapperComponents(configs, registry, cache, logger) {
199
+ const load = (async () => {
200
+ const component = resolveDefaultExport(await definition.loadComponent());
201
+ if (component)
202
+ cache.set(type, component);
203
+ return component;
204
+ })().finally(() => loadCache?.delete(type));
205
+ loadCache?.set(type, load);
206
+ return load;
207
+ }
208
+ function loadWrapperComponents(configs, registry, cache, logger, loadCache) {
82
209
  if (configs.length === 0)
83
210
  return of([]);
84
- return forkJoin(configs.map((config) => from(loadWrapperComponent(config.type, registry, cache)).pipe(catchError(() => of(undefined)), map((component) => {
211
+ return forkJoin(configs.map((config) => from(loadWrapperComponent(config.type, registry, cache, loadCache)).pipe(catchError(() => of(undefined)), map((component) => {
85
212
  if (!component) {
86
- logger.error(`Wrapper type '${config.type}' could not be loaded. Ensure it is registered via provideDynamicForm().`);
213
+ if (DEV_MODE) {
214
+ logger.error(`Wrapper type '${config.type}' could not be loaded. Ensure it is registered via provideDynamicForm().`);
215
+ }
87
216
  return null;
88
217
  }
89
218
  return { config, component };
@@ -123,9 +252,11 @@ function renderStep(slot, remaining, options, refs) {
123
252
  ref.changeDetectorRef.detectChanges();
124
253
  const inner = resolveInnerSlot(ref);
125
254
  if (!inner) {
126
- options.logger.error(`Wrapper component for type '${wrapper.config.type}' does not provide a 'fieldComponent' ViewContainerRef. ` +
127
- `Ensure the wrapper component has a viewChild('fieldComponent', { read: ViewContainerRef }) query ` +
128
- `and that #fieldComponent is not inside a conditional (@if, @defer).`);
255
+ if (DEV_MODE) {
256
+ options.logger.error(`Wrapper component for type '${wrapper.config.type}' does not provide a 'fieldComponent' ViewContainerRef. ` +
257
+ `Ensure the wrapper component has a viewChild('fieldComponent', { read: ViewContainerRef }) query ` +
258
+ `and that #fieldComponent is not inside a conditional (@if, @defer).`);
259
+ }
129
260
  while (refs.length)
130
261
  refs.pop().destroy();
131
262
  return;
@@ -167,27 +298,34 @@ function createWrapperChainController(opts) {
167
298
  const deps = injectChainDeps();
168
299
  const state = createStateSignal(opts);
169
300
  const mounted = { value: null };
301
+ const detached = { value: null };
170
302
  let refs = [];
171
303
  buildEmissionStream(state, deps)
172
304
  .pipe(takeUntilDestroyed(deps.destroyRef))
173
305
  .subscribe((emission) => {
174
306
  try {
175
- refs = applyEmission(emission, { opts, deps, mounted, refs });
307
+ refs = applyEmission(emission, { opts, deps, mounted, refs, detached });
176
308
  }
177
309
  catch (err) {
178
310
  deps.logger.error('Wrapper chain render failed; tearing down partial state.', err);
179
311
  opts.vcr().clear();
180
312
  refs = [];
181
313
  mounted.value = null;
314
+ detached.value?.destroy();
315
+ detached.value = null;
182
316
  }
183
317
  });
184
318
  pushFieldInputsOnChange(opts, () => refs);
185
- deps.destroyRef.onDestroy(() => opts.vcr().clear());
319
+ deps.destroyRef.onDestroy(() => {
320
+ detached.value?.destroy();
321
+ opts.vcr().clear();
322
+ });
186
323
  }
187
324
  function injectChainDeps() {
188
325
  return {
189
326
  registry: inject(WRAPPER_REGISTRY),
190
327
  cache: inject(WRAPPER_COMPONENT_CACHE),
328
+ loadCache: inject(WRAPPER_COMPONENT_LOAD_CACHE),
191
329
  logger: inject(DynamicFormLogger),
192
330
  destroyRef: inject(DestroyRef),
193
331
  environmentInjector: inject(EnvironmentInjector),
@@ -211,16 +349,27 @@ function resolveLoadedWrappers(state, deps) {
211
349
  const loaded = state.wrappers.map((config) => ({ config, component: deps.cache.get(config.type) }));
212
350
  return of({ state, loaded });
213
351
  }
214
- return loadWrapperComponents(state.wrappers, deps.registry, deps.cache, deps.logger).pipe(map((loaded) => ({ state, loaded })));
352
+ return loadWrapperComponents(state.wrappers, deps.registry, deps.cache, deps.logger, deps.loadCache).pipe(map((loaded) => ({ state, loaded })));
215
353
  }
216
354
  function applyEmission({ state, loaded }, ctx) {
217
355
  const { opts, deps, mounted, refs } = ctx;
218
356
  const vcr = opts.vcr();
219
357
  const structurallyChanged = isStructurallyDifferent(mounted.value, state);
220
- if (!structurallyChanged && !state.open)
358
+ if (!structurallyChanged && !state.open) {
359
+ if (vcr.length > 0)
360
+ ctx.detached.value = vcr.detach(0) ?? ctx.detached.value;
361
+ return refs;
362
+ }
363
+ if (state.open && !structurallyChanged && mounted.value !== null && ctx.detached.value) {
364
+ vcr.insert(ctx.detached.value);
365
+ ctx.detached.value = null;
221
366
  return refs;
367
+ }
222
368
  if (structurallyChanged && mounted.value !== null) {
223
- opts.beforeRebuild?.();
369
+ const preservesDetachedOuterView = opts.beforeRebuild?.() === true;
370
+ if (ctx.detached.value && !preservesDetachedOuterView)
371
+ ctx.detached.value.destroy();
372
+ ctx.detached.value = null;
224
373
  vcr.clear();
225
374
  mounted.value = null;
226
375
  }
@@ -278,14 +427,19 @@ class FieldComponentSlot {
278
427
  state = signal(EMPTY_STATE, /* @ts-ignore */
279
428
  ...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
280
429
  lastInputs = null;
430
+ _parked = signal(false, /* @ts-ignore */
431
+ ...(ngDevMode ? [{ debugName: "_parked" }] : /* istanbul ignore next */ []));
281
432
  phase = () => this.state().phase;
282
433
  snapshot = () => this.state();
434
+ parked = this._parked.asReadonly();
283
435
  mountOrReuse(slot, component, fieldInjector, environmentInjector, rawInputs, fieldInputs) {
284
436
  const current = this.state();
285
437
  if (current.phase !== 'empty' && current.ref.componentType === component) {
286
438
  slot.insert(current.ref.hostView);
287
439
  this.lastInputs = null;
288
440
  this.state.set(Object.freeze({ phase: 'mounted', ref: current.ref, slot }));
441
+ if (this._parked())
442
+ current.ref.changeDetectorRef.detach();
289
443
  this.pushInputs(rawInputs, fieldInputs);
290
444
  if (current.phase === 'detached')
291
445
  this.replayFocus(current.focusSnapshot);
@@ -294,6 +448,7 @@ class FieldComponentSlot {
294
448
  if (current.phase !== 'empty')
295
449
  current.ref.destroy();
296
450
  this.lastInputs = null;
451
+ this._parked.set(false);
297
452
  this.state.set(EMPTY_STATE);
298
453
  const ref = slot.createComponent(component, {
299
454
  environmentInjector,
@@ -322,19 +477,42 @@ class FieldComponentSlot {
322
477
  detach() {
323
478
  const current = this.state();
324
479
  if (current.phase !== 'mounted')
325
- return;
480
+ return false;
326
481
  const focusSnapshot = this.captureFocus(current.ref);
327
482
  const idx = current.slot.indexOf(current.ref.hostView);
328
483
  if (idx >= 0)
329
484
  current.slot.detach(idx);
330
485
  this.lastInputs = null;
331
486
  this.state.set(Object.freeze({ phase: 'detached', ref: current.ref, focusSnapshot }));
487
+ return idx < 0;
488
+ }
489
+ park() {
490
+ const current = this.state();
491
+ if (current.phase === 'empty' || this._parked())
492
+ return;
493
+ current.ref.changeDetectorRef.detach();
494
+ this._parked.set(true);
495
+ }
496
+ refresh() {
497
+ const current = this.state();
498
+ if (current.phase === 'empty' || !this._parked())
499
+ return;
500
+ current.ref.changeDetectorRef.detectChanges();
501
+ }
502
+ unpark() {
503
+ const current = this.state();
504
+ if (current.phase === 'empty' || !this._parked())
505
+ return;
506
+ current.ref.changeDetectorRef.reattach();
507
+ current.ref.changeDetectorRef.markForCheck();
508
+ this._parked.set(false);
332
509
  }
333
510
  destroyOnTeardown() {
334
511
  const current = this.state();
335
512
  if (current.phase === 'detached')
336
513
  current.ref.destroy();
337
514
  this.lastInputs = null;
515
+ this._parked.set(false);
338
516
  this.state.set(EMPTY_STATE);
339
517
  }
340
518
  captureFocus(ref) {
@@ -359,6 +537,100 @@ class FieldComponentSlot {
359
537
  }
360
538
  }
361
539
 
540
+ function emitComponentInitialized(eventBus, componentType, componentKey, injector) {
541
+ const logger = injector.get(DynamicFormLogger);
542
+ afterNextRender(() => {
543
+ try {
544
+ eventBus.dispatch(ComponentInitializedEvent, componentType, componentKey);
545
+ }
546
+ catch (error) {
547
+ logger.error(`Failed to emit initialization event for ${componentType} '${componentKey}'`, error);
548
+ }
549
+ }, { injector });
550
+ }
551
+
552
+ function initializationComponentKey(type, key) {
553
+ return `${type}:${key}`;
554
+ }
555
+ function initializationComponentPath(key, groupPath) {
556
+ return groupPath ? `${groupPath}.${key}` : key;
557
+ }
558
+ function initializationComponentType(field) {
559
+ if (isPageField(field))
560
+ return 'page';
561
+ if (isGroupField(field))
562
+ return 'group';
563
+ if (isArrayField(field))
564
+ return 'array';
565
+ if (isRowField(field) || isGenericContainerField(field))
566
+ return 'container';
567
+ return undefined;
568
+ }
569
+ function collectInitializingContainers(fields, groupPath = '') {
570
+ const identities = [];
571
+ for (const field of fields) {
572
+ if (field.hidden === true)
573
+ continue;
574
+ const type = initializationComponentType(field);
575
+ const componentPath = initializationComponentPath(field.key, groupPath);
576
+ if (type && field.key)
577
+ identities.push({ type, path: componentPath });
578
+ if (!hasChildFields(field))
579
+ continue;
580
+ const childGroupPath = isGroupField(field) ? componentPath : groupPath;
581
+ for (const child of normalizeFieldsArray(field.fields)) {
582
+ identities.push(...collectInitializingContainers((Array.isArray(child) ? child : [child]), childGroupPath));
583
+ }
584
+ }
585
+ return identities;
586
+ }
587
+ function collectInitializingContainerKeys(fields, groupPath = '') {
588
+ return collectInitializingContainers(fields, groupPath).map(({ type, path }) => initializationComponentKey(type, path));
589
+ }
590
+ function computeContainerHostClasses(containerType, className) {
591
+ const base = `df-field df-${containerType}`;
592
+ return className ? `${base} ${className}` : base;
593
+ }
594
+ function setupContainerInitEffect(resolvedFields, eventBus, componentType, fieldKeyFn, injector) {
595
+ const allReady = computed(() => {
596
+ const fields = resolvedFields();
597
+ return fields.length > 0 && fields.every((field) => field.renderReady());
598
+ }, /* @ts-ignore */
599
+ ...(ngDevMode ? [{ debugName: "allReady" }] : /* istanbul ignore next */ []));
600
+ explicitEffect([allReady], ([ready]) => {
601
+ if (ready) {
602
+ const type = typeof componentType === 'function' ? componentType() : componentType;
603
+ emitComponentInitialized(eventBus, type, fieldKeyFn(), injector);
604
+ }
605
+ });
606
+ }
607
+
608
+ const EMPTY_ERRORS = Object.freeze([]);
609
+ function snapshotParkedDomState(fieldTree) {
610
+ const state = fieldTree?.();
611
+ if (!state)
612
+ return null;
613
+ return {
614
+ disabled: state.disabled?.() ?? false,
615
+ readonly: state.readonly?.() ?? false,
616
+ required: state.required?.() ?? false,
617
+ touched: state.touched?.() ?? false,
618
+ dirty: state.dirty?.() ?? false,
619
+ errors: state.errors?.() ?? EMPTY_ERRORS,
620
+ };
621
+ }
622
+ function isSameParkedDomState(a, b) {
623
+ return (a === b ||
624
+ (a !== null &&
625
+ b !== null &&
626
+ a.disabled === b.disabled &&
627
+ a.readonly === b.readonly &&
628
+ a.required === b.required &&
629
+ a.touched === b.touched &&
630
+ a.dirty === b.dirty &&
631
+ a.errors === b.errors));
632
+ }
633
+
362
634
  class DfFieldOutlet {
363
635
  dfFieldOutlet = input.required(/* @ts-ignore */
364
636
  ...(ngDevMode ? [{ debugName: "dfFieldOutlet" }] : /* istanbul ignore next */ []));
@@ -371,23 +643,50 @@ class DfFieldOutlet {
371
643
  wrapperRegistry = inject(WRAPPER_REGISTRY);
372
644
  defaultWrappersSignal = inject(DEFAULT_WRAPPERS, { optional: true });
373
645
  readonlyFieldCache = inject(READONLY_FIELD_TREE_CACHE);
374
- fieldComponent = new FieldComponentSlot();
646
+ eventBus = inject(EventBus, { optional: true });
647
+ injector = inject(Injector);
648
+ fieldComponent = new FieldComponentSlot();
375
649
  componentIdentity = computed(() => this.dfFieldOutlet().component, /* @ts-ignore */
376
650
  ...(ngDevMode ? [{ debugName: "componentIdentity" }] : /* istanbul ignore next */ []));
377
651
  renderReady = computed(() => this.dfFieldOutlet().renderReady() && !this.dfFieldOutlet().hidden(), /* @ts-ignore */
378
652
  ...(ngDevMode ? [{ debugName: "renderReady" }] : /* istanbul ignore next */ []));
653
+ hidden = computed(() => this.dfFieldOutlet().hidden(), /* @ts-ignore */
654
+ ...(ngDevMode ? [{ debugName: "hidden" }] : /* istanbul ignore next */ []));
379
655
  rawInputs = computed(() => this.dfFieldOutlet().inputs(), /* @ts-ignore */
380
656
  ...(ngDevMode ? [{ debugName: "rawInputs" }] : /* istanbul ignore next */ []));
381
657
  wrappers = computed(() => resolveWrappers(this.dfFieldOutlet().fieldDef, this.defaultWrappersSignal?.(), this.wrapperAutoAssociations, this.wrapperRegistry), { ...(ngDevMode ? { debugName: "wrappers" } : /* istanbul ignore next */ {}), equal: isSameWrapperChain });
382
658
  fieldInputs = computed(() => buildFieldInputs(this.rawInputs(), this.readonlyFieldCache, this.dfFieldOutlet().fieldDef.type, this.dfFieldOutlet().fieldDef.validationMessages), /* @ts-ignore */
383
659
  ...(ngDevMode ? [{ debugName: "fieldInputs" }] : /* istanbul ignore next */ []));
384
660
  defaultEnvInjector = inject(EnvironmentInjector);
385
- fieldEnvInjector = computed(() => this.dfFieldOutletEnvironmentInjector() ?? this.defaultEnvInjector, /* @ts-ignore */
661
+ fieldEnvInjector = computed(() => this.dfFieldOutletEnvironmentInjector() ?? this.defaultEnvInjector, /* @ts-ignore */
386
662
  ...(ngDevMode ? [{ debugName: "fieldEnvInjector" }] : /* istanbul ignore next */ []));
387
- fieldInjector = computed(() => this.dfFieldOutlet().injector, /* @ts-ignore */
663
+ fieldInjector = computed(() => this.dfFieldOutlet().injector, /* @ts-ignore */
388
664
  ...(ngDevMode ? [{ debugName: "fieldInjector" }] : /* istanbul ignore next */ []));
389
665
  outermostHostClasses = computed(() => getGridClassString(this.dfFieldOutlet().fieldDef) || undefined, /* @ts-ignore */
390
666
  ...(ngDevMode ? [{ debugName: "outermostHostClasses" }] : /* istanbul ignore next */ []));
667
+ formOptions = inject(FORM_OPTIONS, { optional: true });
668
+ globalFieldWindowing = inject(FIELD_WINDOWING);
669
+ viewportObserver = inject(FieldViewportObserver, { optional: true });
670
+ parking = computed(() => resolveFieldWindowing(this.globalFieldWindowing, this.formOptions?.()?.fieldWindowing).park, /* @ts-ignore */
671
+ ...(ngDevMode ? [{ debugName: "parking" }] : /* istanbul ignore next */ []));
672
+ observedElement = computed(() => {
673
+ const state = this.fieldComponent.snapshot();
674
+ return state.phase === 'empty' ? null : state.ref.location.nativeElement;
675
+ }, /* @ts-ignore */
676
+ ...(ngDevMode ? [{ debugName: "observedElement" }] : /* istanbul ignore next */ []));
677
+ visibility = derivedFrom([this.observedElement, this.parking], pipe(switchMap(([element, parking]) => {
678
+ const observer = this.viewportObserver;
679
+ return parking.enabled && element && observer ? observer.observe(element, parking.margin) : of(true);
680
+ })), { initialValue: true });
681
+ focusEpoch = signal(0, /* @ts-ignore */
682
+ ...(ngDevMode ? [{ debugName: "focusEpoch" }] : /* istanbul ignore next */ []));
683
+ parked = computed(() => this.parking().enabled && !this.visibility(), /* @ts-ignore */
684
+ ...(ngDevMode ? [{ debugName: "parked" }] : /* istanbul ignore next */ []));
685
+ fieldTree = computed(() => {
686
+ const candidate = this.rawInputs()['field'];
687
+ return typeof candidate === 'function' ? candidate : undefined;
688
+ }, { ...(ngDevMode ? { debugName: "fieldTree" } : /* istanbul ignore next */ {}), equal: Object.is });
689
+ parkedDomState = computed(() => (this.parked() ? snapshotParkedDomState(this.fieldTree()) : null), { ...(ngDevMode ? { debugName: "parkedDomState" } : /* istanbul ignore next */ {}), equal: isSameParkedDomState });
391
690
  constructor() {
392
691
  createWrapperChainController({
393
692
  vcr: this.vcr,
@@ -403,8 +702,50 @@ class DfFieldOutlet {
403
702
  this.fieldComponent.mountOrReuse(slot, resolved.component, resolved.injector, this.fieldEnvInjector(), this.rawInputs(), this.fieldInputs());
404
703
  },
405
704
  });
406
- explicitEffect([this.rawInputs, this.fieldInputs], ([rawInputs, fieldInputs]) => this.fieldComponent.pushInputs(rawInputs, fieldInputs));
705
+ explicitEffect([this.rawInputs, this.fieldInputs], ([rawInputs, fieldInputs]) => this.fieldComponent.pushInputs(rawInputs, fieldInputs));
706
+ explicitEffect([this.hidden], ([hidden]) => {
707
+ if (!hidden || !this.eventBus)
708
+ return;
709
+ const resolved = this.dfFieldOutlet();
710
+ const groupContext = resolved.injector.get(GROUP_CONTEXT, null);
711
+ for (const { type, path } of collectInitializingContainers([resolved.fieldDef], groupContext?.groupPath())) {
712
+ emitComponentInitialized(this.eventBus, type, path, this.injector);
713
+ }
714
+ });
715
+ explicitEffect([this.observedElement, this.parking], ([element, parking], onCleanup) => {
716
+ if (!parking.enabled || !element)
717
+ return;
718
+ const bumpEpoch = () => queueMicrotask(() => {
719
+ if (!this.destroyRef.destroyed)
720
+ this.focusEpoch.update((n) => n + 1);
721
+ });
722
+ element.addEventListener('focusin', bumpEpoch);
723
+ element.addEventListener('focusout', bumpEpoch);
724
+ onCleanup(() => {
725
+ element.removeEventListener('focusin', bumpEpoch);
726
+ element.removeEventListener('focusout', bumpEpoch);
727
+ });
728
+ });
729
+ explicitEffect([this.parked, this.focusEpoch, this.parkedDomState], ([parked, , domState]) => {
730
+ if (parked && this.canPark(domState)) {
731
+ const alreadyParked = this.fieldComponent.parked();
732
+ this.fieldComponent.park();
733
+ if (alreadyParked)
734
+ this.fieldComponent.refresh();
735
+ }
736
+ else {
737
+ this.fieldComponent.unpark();
738
+ }
739
+ });
407
740
  this.destroyRef.onDestroy(() => this.fieldComponent.destroyOnTeardown());
741
+ }
742
+ canPark(domState) {
743
+ const element = this.observedElement();
744
+ if (!element)
745
+ return false;
746
+ if (element.contains(document.activeElement))
747
+ return false;
748
+ return (domState?.errors.length ?? 0) === 0;
408
749
  }
409
750
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: DfFieldOutlet, deps: [], target: i0.ɵɵFactoryTarget.Directive });
410
751
  static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.1", 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 });
@@ -450,455 +791,91 @@ class FormSubmitEvent {
450
791
  type = 'submit';
451
792
  }
452
793
 
453
- class PageChangeEvent {
454
- currentPageIndex;
455
- totalPages;
456
- previousPageIndex;
457
- type = 'page-change';
458
- constructor(
459
- currentPageIndex,
460
- totalPages,
461
- previousPageIndex) {
462
- this.currentPageIndex = currentPageIndex;
463
- this.totalPages = totalPages;
464
- this.previousPageIndex = previousPageIndex;
465
- }
466
- }
467
-
468
- class PagerStateEvent {
469
- state;
470
- type = 'pager-state';
471
- constructor(state) {
472
- this.state = state;
473
- }
474
- }
475
-
476
- const PAGE_PRELOAD_WINDOW = new InjectionToken('PAGE_PRELOAD_WINDOW', {
794
+ const COMPONENT_CACHE = new InjectionToken('COMPONENT_CACHE', {
477
795
  providedIn: 'root',
478
- factory: () => 1,
796
+ factory: () => new Map(),
797
+ });
798
+ const COMPONENT_LOAD_CACHE = new InjectionToken('COMPONENT_LOAD_CACHE', {
799
+ providedIn: 'root',
800
+ factory: () => new Map(),
479
801
  });
802
+ function injectFieldRegistry() {
803
+ const registry = inject(FIELD_REGISTRY);
804
+ const componentCache = inject(COMPONENT_CACHE);
805
+ const componentLoadCache = inject(COMPONENT_LOAD_CACHE);
806
+ return {
807
+ getType(name) {
808
+ return registry.get(name);
809
+ },
810
+ hasType(name) {
811
+ return registry.has(name);
812
+ },
813
+ async loadTypeComponent(name) {
814
+ const fieldType = registry.get(name);
815
+ if (!fieldType) {
816
+ throw new DynamicFormError(`Field type "${name}" is not registered`);
817
+ }
818
+ if (!fieldType.loadComponent) {
819
+ return undefined;
820
+ }
821
+ const cached = componentCache.get(name);
822
+ if (cached) {
823
+ return cached;
824
+ }
825
+ const pending = componentLoadCache.get(name);
826
+ if (pending) {
827
+ return pending;
828
+ }
829
+ try {
830
+ const load = fieldType
831
+ .loadComponent()
832
+ .then(resolveDefaultExport)
833
+ .then((component) => {
834
+ if (component) {
835
+ componentCache.set(name, component);
836
+ }
837
+ return component;
838
+ })
839
+ .catch((error) => {
840
+ throw new DynamicFormError(`Failed to load component for field type "${name}": ${error}`);
841
+ })
842
+ .finally(() => componentLoadCache.delete(name));
843
+ componentLoadCache.set(name, load);
844
+ return load;
845
+ }
846
+ catch (error) {
847
+ throw new DynamicFormError(`Failed to load component for field type "${name}": ${error}`);
848
+ }
849
+ },
850
+ getLoadedComponent(name) {
851
+ return componentCache.get(name);
852
+ },
853
+ getTypes() {
854
+ return Array.from(registry.values());
855
+ },
856
+ registerTypes(types) {
857
+ types.forEach((type) => {
858
+ registry.set(type.name, type);
859
+ });
860
+ },
861
+ get raw() {
862
+ return registry;
863
+ },
864
+ };
865
+ }
480
866
 
481
- function clampWindowSize(value, fallback) {
482
- if (value === undefined || !Number.isFinite(value))
483
- return fallback;
484
- return Math.max(0, Math.floor(value));
867
+ const NORMALIZED_ARRAY_METADATA = Symbol('normalizedArrayMetadata');
868
+ function setNormalizedArrayMetadata(arrayField, metadata) {
869
+ arrayField[NORMALIZED_ARRAY_METADATA] = metadata;
870
+ }
871
+ function getNormalizedArrayMetadata(arrayField) {
872
+ return arrayField[NORMALIZED_ARRAY_METADATA];
485
873
  }
486
874
 
487
- class PageOrchestratorComponent {
488
- eventBus = inject(EventBus);
489
- fieldContextRegistry = inject(FieldContextRegistryService);
490
- functionRegistry = inject(FunctionRegistryService);
491
- formOptions = inject(FORM_OPTIONS, { optional: true });
492
- globalPreloadWindow = inject(PAGE_PRELOAD_WINDOW);
493
- pageFields = input.required(/* @ts-ignore */
494
- ...(ngDevMode ? [{ debugName: "pageFields" }] : /* istanbul ignore next */ []));
495
- form = input.required(/* @ts-ignore */
496
- ...(ngDevMode ? [{ debugName: "form" }] : /* istanbul ignore next */ []));
497
- fieldSignalContext = input.required(/* @ts-ignore */
498
- ...(ngDevMode ? [{ debugName: "fieldSignalContext" }] : /* istanbul ignore next */ []));
499
- pageHiddenStates = computed(() => {
500
- const pages = this.pageFields();
501
- return pages.map((page) => this.evaluatePageHidden(page));
502
- }, /* @ts-ignore */
503
- ...(ngDevMode ? [{ debugName: "pageHiddenStates" }] : /* istanbul ignore next */ []));
504
- visiblePageIndices = computed(() => {
505
- const hiddenStates = this.pageHiddenStates();
506
- return hiddenStates
507
- .map((hidden, index) => ({ index, hidden }))
508
- .filter((item) => !item.hidden)
509
- .map((item) => item.index);
510
- }, /* @ts-ignore */
511
- ...(ngDevMode ? [{ debugName: "visiblePageIndices" }] : /* istanbul ignore next */ []));
512
- currentPageIndex = linkedSignal(() => {
513
- const totalPages = this.pageFields().length;
514
- if (totalPages === 0)
515
- return 0;
516
- return untracked(() => this.resolveInitialLanding());
517
- }, /* @ts-ignore */
518
- ...(ngDevMode ? [{ debugName: "currentPageIndex" }] : /* istanbul ignore next */ []));
519
- resolveInitialLanding() {
520
- const { index, validate } = this.initialPage();
521
- const visible = this.visiblePageIndices();
522
- if (index === 0 || visible.length === 0)
523
- return 0;
524
- const target = visible.includes(index) ? index : this.findNearestVisiblePage(index, visible);
525
- if (target <= 0)
526
- return Math.max(target, 0);
527
- if (!validate)
528
- return target;
529
- const firstInvalid = visible.filter((i) => i < target).find((i) => !this.isPageValid(i));
530
- return firstInvalid ?? target;
531
- }
532
- initialPage = computed(() => {
533
- const raw = this.formOptions?.()?.initialPage;
534
- const totalPages = this.pageFields().length;
535
- const requested = typeof raw === 'number' ? raw : raw?.index;
536
- const validate = typeof raw === 'number' ? false : (raw?.validate ?? false);
537
- if (typeof requested !== 'number' || !Number.isFinite(requested) || requested < 0 || totalPages === 0) {
538
- return { index: 0, validate: false };
539
- }
540
- return { index: Math.min(Math.trunc(requested), totalPages - 1), validate };
541
- }, /* @ts-ignore */
542
- ...(ngDevMode ? [{ debugName: "initialPage" }] : /* istanbul ignore next */ []));
543
- state = computed(() => {
544
- const currentIndex = this.currentPageIndex();
545
- const totalPages = this.pageFields().length;
546
- const visibleIndices = this.visiblePageIndices();
547
- const currentVisiblePosition = visibleIndices.indexOf(currentIndex);
548
- const isFirstVisiblePage = currentVisiblePosition === 0 || currentVisiblePosition === -1;
549
- const isLastVisiblePage = currentVisiblePosition >= visibleIndices.length - 1;
550
- return {
551
- currentPageIndex: currentIndex,
552
- totalPages,
553
- isFirstPage: isFirstVisiblePage,
554
- isLastPage: isLastVisiblePage,
555
- };
556
- }, /* @ts-ignore */
557
- ...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
558
- currentPageValid = computed(() => this.isPageValid(this.currentPageIndex()), /* @ts-ignore */
559
- ...(ngDevMode ? [{ debugName: "currentPageValid" }] : /* istanbul ignore next */ []));
560
- isPageValid(pageIndex) {
561
- const pages = this.pageFields();
562
- const form = this.form();
563
- if (pageIndex < 0 || pageIndex >= pages.length) {
564
- return true;
565
- }
566
- const leafKeys = collectLeafFieldKeys(pages[pageIndex].fields || []);
567
- for (const fieldKey of leafKeys) {
568
- const field = form[fieldKey];
569
- if (field && typeof field === 'function') {
570
- const fieldState = field();
571
- if (fieldState && typeof fieldState.valid === 'function' && !fieldState.valid()) {
572
- return false;
573
- }
574
- }
575
- }
576
- return true;
577
- }
578
- extendedFieldSignalContext = computed(() => ({
579
- ...this.fieldSignalContext(),
580
- currentPageValid: this.currentPageValid,
581
- }), /* @ts-ignore */
582
- ...(ngDevMode ? [{ debugName: "extendedFieldSignalContext" }] : /* istanbul ignore next */ []));
583
- preloadWindow = computed(() => {
584
- const perForm = this.formOptions?.()?.pagePreloadWindow;
585
- return clampWindowSize(perForm, this.globalPreloadWindow);
586
- }, /* @ts-ignore */
587
- ...(ngDevMode ? [{ debugName: "preloadWindow" }] : /* istanbul ignore next */ []));
588
- isWithinPreloadWindow(i) {
589
- return Math.abs(i - this.state().currentPageIndex) <= this.preloadWindow();
590
- }
591
- constructor() {
592
- this.setupEventListeners();
593
- explicitEffect([this.state, this.visiblePageIndices], ([state, visibleIndices]) => {
594
- const currentVisiblePosition = visibleIndices.indexOf(state.currentPageIndex);
595
- if (currentVisiblePosition === -1 && visibleIndices.length > 0) {
596
- const nearest = this.findNearestVisiblePage(state.currentPageIndex, visibleIndices);
597
- if (nearest !== -1) {
598
- this.executePageChange(nearest);
599
- }
600
- }
601
- });
602
- }
603
- navigateToNextPage() {
604
- const disableWhenPageInvalid = this.formOptions?.()?.nextButton?.disableWhenPageInvalid ?? true;
605
- if (disableWhenPageInvalid && !this.currentPageValid()) {
606
- return {
607
- success: false,
608
- newPageIndex: this.state().currentPageIndex,
609
- error: 'Current page has invalid fields',
610
- };
611
- }
612
- const currentState = this.state();
613
- const visibleIndices = this.visiblePageIndices();
614
- if (currentState.isLastPage) {
615
- return {
616
- success: false,
617
- newPageIndex: currentState.currentPageIndex,
618
- error: 'Already on the last visible page',
619
- };
620
- }
621
- const currentVisiblePosition = visibleIndices.indexOf(currentState.currentPageIndex);
622
- if (currentVisiblePosition === -1 || currentVisiblePosition >= visibleIndices.length - 1) {
623
- return {
624
- success: false,
625
- newPageIndex: currentState.currentPageIndex,
626
- error: 'No next visible page available',
627
- };
628
- }
629
- const nextVisiblePageIndex = visibleIndices[currentVisiblePosition + 1];
630
- return this.executePageChange(nextVisiblePageIndex);
631
- }
632
- navigateToPreviousPage() {
633
- const currentState = this.state();
634
- const visibleIndices = this.visiblePageIndices();
635
- if (currentState.isFirstPage) {
636
- return {
637
- success: false,
638
- newPageIndex: currentState.currentPageIndex,
639
- error: 'Already on the first visible page',
640
- };
641
- }
642
- const currentVisiblePosition = visibleIndices.indexOf(currentState.currentPageIndex);
643
- if (currentVisiblePosition <= 0) {
644
- return {
645
- success: false,
646
- newPageIndex: currentState.currentPageIndex,
647
- error: 'No previous visible page available',
648
- };
649
- }
650
- const prevVisiblePageIndex = visibleIndices[currentVisiblePosition - 1];
651
- return this.executePageChange(prevVisiblePageIndex);
652
- }
653
- navigateToPage(pageIndex, options) {
654
- const currentIndex = this.state().currentPageIndex;
655
- const visibleIndices = this.visiblePageIndices();
656
- if (pageIndex <= currentIndex || !visibleIndices.includes(pageIndex)) {
657
- return this.executePageChange(pageIndex);
658
- }
659
- if (options?.validate === false) {
660
- return this.executePageChange(pageIndex);
661
- }
662
- const disableWhenPageInvalid = this.formOptions?.()?.nextButton?.disableWhenPageInvalid ?? true;
663
- if (!disableWhenPageInvalid) {
664
- return this.executePageChange(pageIndex);
665
- }
666
- const crossedPages = visibleIndices.filter((index) => index >= currentIndex && index < pageIndex);
667
- const firstInvalidPage = crossedPages.find((index) => !this.isPageValid(index));
668
- if (firstInvalidPage === undefined) {
669
- return this.executePageChange(pageIndex);
670
- }
671
- this.executePageChange(firstInvalidPage);
672
- return {
673
- success: false,
674
- newPageIndex: firstInvalidPage,
675
- error: `Cannot navigate to page ${pageIndex}: page ${firstInvalidPage} has invalid fields`,
676
- };
677
- }
678
- executePageChange(pageIndex) {
679
- const currentState = this.state();
680
- const totalPages = currentState.totalPages;
681
- if (pageIndex < 0 || pageIndex >= totalPages) {
682
- return {
683
- success: false,
684
- newPageIndex: currentState.currentPageIndex,
685
- error: `Invalid page index: ${pageIndex}. Valid range is 0 to ${totalPages - 1}`,
686
- };
687
- }
688
- const visibleIndices = this.visiblePageIndices();
689
- if (!visibleIndices.includes(pageIndex)) {
690
- return {
691
- success: false,
692
- newPageIndex: currentState.currentPageIndex,
693
- error: `Cannot navigate to hidden page at index ${pageIndex}. Visible pages: [${visibleIndices.join(', ')}]`,
694
- };
695
- }
696
- if (pageIndex === currentState.currentPageIndex) {
697
- return {
698
- success: true,
699
- newPageIndex: pageIndex,
700
- };
701
- }
702
- const previousIndex = currentState.currentPageIndex;
703
- this.currentPageIndex.set(pageIndex);
704
- this.eventBus.dispatch(PageChangeEvent, pageIndex, totalPages, previousIndex);
705
- return {
706
- success: true,
707
- newPageIndex: pageIndex,
708
- };
709
- }
710
- findNearestVisiblePage(currentIndex, visibleIndices) {
711
- let nearest = -1;
712
- let minDistance = Infinity;
713
- for (const idx of visibleIndices) {
714
- const distance = Math.abs(idx - currentIndex);
715
- if (distance < minDistance || (distance === minDistance && idx > nearest)) {
716
- minDistance = distance;
717
- nearest = idx;
718
- }
719
- }
720
- return nearest;
721
- }
722
- setupEventListeners() {
723
- this.eventBus
724
- .on('next-page')
725
- .pipe(takeUntilDestroyed())
726
- .subscribe(() => {
727
- this.navigateToNextPage();
728
- });
729
- this.eventBus
730
- .on('previous-page')
731
- .pipe(takeUntilDestroyed())
732
- .subscribe(() => {
733
- this.navigateToPreviousPage();
734
- });
735
- this.eventBus
736
- .on('go-to-page')
737
- .pipe(takeUntilDestroyed())
738
- .subscribe((event) => {
739
- this.navigateToPage(event.pageIndex, event.options);
740
- });
741
- explicitEffect([this.state], ([state]) => this.eventBus.dispatch(PagerStateEvent, state));
742
- }
743
- evaluatePageHidden(page) {
744
- if (!page.logic || page.logic.length === 0) {
745
- return false;
746
- }
747
- const hiddenLogic = page.logic.filter((l) => l.type === 'hidden');
748
- if (hiddenLogic.length === 0) {
749
- return false;
750
- }
751
- for (const logic of hiddenLogic) {
752
- if (typeof logic.condition === 'boolean') {
753
- if (logic.condition) {
754
- return true;
755
- }
756
- continue;
757
- }
758
- const condition = logic.condition;
759
- const context = this.fieldContextRegistry.createDisplayOnlyContext(page.key || '', this.functionRegistry.getCustomFunctions());
760
- if (evaluateCondition(condition, context)) {
761
- return true;
762
- }
763
- }
764
- return false;
765
- }
766
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: PageOrchestratorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
767
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", 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: [
768
- {
769
- provide: FIELD_SIGNAL_CONTEXT,
770
- useFactory: (orchestrator) => orchestrator.extendedFieldSignalContext(),
771
- deps: [PageOrchestratorComponent],
772
- },
773
- ], ngImport: i0, template: `
774
- @for (pageField of pageFields(); track pageField.key; let i = $index) {
775
- <!--
776
- Skip pages hidden by 'hidden' logic conditions entirely. FormStateManager
777
- derives schema/value/validators from config (not from mounted components),
778
- so unmounting a hidden page has no effect on form state — only on render +
779
- CD cost. This is the page-level equivalent of the existing field-level
780
- \`@if (!field.hidden())\` gate in page-field.component.ts.
781
- -->
782
- @if (!pageHiddenStates()[i]) {
783
- @if (isWithinPreloadWindow(i)) {
784
- <!-- Current and preloaded neighbour pages: render immediately for flicker-free navigation -->
785
- @defer (on immediate) {
786
- <section
787
- page-field
788
- [field]="pageField"
789
- [key]="pageField.key"
790
- [pageIndex]="i"
791
- [isVisible]="i === state().currentPageIndex"
792
- ></section>
793
- } @placeholder {
794
- <div class="df-page-placeholder" [attr.data-page-index]="i" [attr.data-page-key]="pageField.key"></div>
795
- }
796
- } @else {
797
- <!--
798
- Pages outside the preload window render only a lightweight placeholder
799
- — they are NOT mounted. Mounting every page eagerly (previously via
800
- \`@defer (on idle)\`, which fires almost immediately post-load) put all
801
- fields of every page in the DOM at once: O(all-fields) initial render
802
- blocking + O(all-fields) change detection on every keystroke, with no
803
- visible benefit since only one page is shown at a time.
804
-
805
- Form state (schema/value/validators) derives from config, not from
806
- mounted components, so leaving a page unmounted does not affect
807
- validity, derivations, or navigation. A page mounts the moment
808
- navigation brings it into the preload window (above), which is
809
- flicker-free for sequential next/previous navigation. The window size
810
- is configurable via \`withPagePreload(n)\` or \`FormOptions.pagePreloadWindow\`.
811
- -->
812
- <div class="df-page-placeholder" [attr.data-page-index]="i" [attr.data-page-key]="pageField.key"></div>
813
- }
814
- }
815
- }
816
- `, isInline: true, styles: [":host{display:block;position:relative;width:100%}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, deferBlockDependencies: [() => [/* @ts-ignore */
817
- import('./ng-forge-dynamic-forms-page-field.component-CwH22lRl.mjs').then(m => m.default)]] });
818
- }
819
- i0.ɵɵngDeclareClassMetadataAsync({ minVersion: "18.0.0", version: "22.0.1", ngImport: i0, type: PageOrchestratorComponent, resolveDeferredDeps: () => [/* @ts-ignore */
820
- import('./ng-forge-dynamic-forms-page-field.component-CwH22lRl.mjs').then(m => m.default)], resolveMetadata: PageFieldComponent => ({ decorators: [{
821
- type: Component,
822
- args: [{ selector: 'div[page-orchestrator]', imports: [PageFieldComponent], template: `
823
- @for (pageField of pageFields(); track pageField.key; let i = $index) {
824
- <!--
825
- Skip pages hidden by 'hidden' logic conditions entirely. FormStateManager
826
- derives schema/value/validators from config (not from mounted components),
827
- so unmounting a hidden page has no effect on form state — only on render +
828
- CD cost. This is the page-level equivalent of the existing field-level
829
- \`@if (!field.hidden())\` gate in page-field.component.ts.
830
- -->
831
- @if (!pageHiddenStates()[i]) {
832
- @if (isWithinPreloadWindow(i)) {
833
- <!-- Current and preloaded neighbour pages: render immediately for flicker-free navigation -->
834
- @defer (on immediate) {
835
- <section
836
- page-field
837
- [field]="pageField"
838
- [key]="pageField.key"
839
- [pageIndex]="i"
840
- [isVisible]="i === state().currentPageIndex"
841
- ></section>
842
- } @placeholder {
843
- <div class="df-page-placeholder" [attr.data-page-index]="i" [attr.data-page-key]="pageField.key"></div>
844
- }
845
- } @else {
846
- <!--
847
- Pages outside the preload window render only a lightweight placeholder
848
- — they are NOT mounted. Mounting every page eagerly (previously via
849
- \`@defer (on idle)\`, which fires almost immediately post-load) put all
850
- fields of every page in the DOM at once: O(all-fields) initial render
851
- blocking + O(all-fields) change detection on every keystroke, with no
852
- visible benefit since only one page is shown at a time.
853
-
854
- Form state (schema/value/validators) derives from config, not from
855
- mounted components, so leaving a page unmounted does not affect
856
- validity, derivations, or navigation. A page mounts the moment
857
- navigation brings it into the preload window (above), which is
858
- flicker-free for sequential next/previous navigation. The window size
859
- is configurable via \`withPagePreload(n)\` or \`FormOptions.pagePreloadWindow\`.
860
- -->
861
- <div class="df-page-placeholder" [attr.data-page-index]="i" [attr.data-page-key]="pageField.key"></div>
862
- }
863
- }
864
- }
865
- `, host: {
866
- class: 'df-page-orchestrator',
867
- '[attr.data-current-page]': 'state().currentPageIndex',
868
- '[attr.data-total-pages]': 'state().totalPages',
869
- }, providers: [
870
- {
871
- provide: FIELD_SIGNAL_CONTEXT,
872
- useFactory: (orchestrator) => orchestrator.extendedFieldSignalContext(),
873
- deps: [PageOrchestratorComponent],
874
- },
875
- ], changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;position:relative;width:100%}\n"] }]
876
- }], ctorParameters: () => [], propDecorators: { pageFields: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageFields", required: true }] }], form: [{ type: i0.Input, args: [{ isSignal: true, alias: "form", required: true }] }], fieldSignalContext: [{ type: i0.Input, args: [{ isSignal: true, alias: "fieldSignalContext", required: true }] }] } }) });
877
- function collectLeafFieldKeys(fields) {
878
- const keys = [];
879
- for (const field of fields) {
880
- if (isRowField(field) || isGenericContainerField(field)) {
881
- keys.push(...collectLeafFieldKeys(field.fields));
882
- }
883
- else if (field.key) {
884
- keys.push(field.key);
885
- }
886
- }
887
- return keys;
888
- }
889
-
890
- const NORMALIZED_ARRAY_METADATA = Symbol('normalizedArrayMetadata');
891
- function setNormalizedArrayMetadata(arrayField, metadata) {
892
- arrayField[NORMALIZED_ARRAY_METADATA] = metadata;
893
- }
894
- function getNormalizedArrayMetadata(arrayField) {
895
- return arrayField[NORMALIZED_ARRAY_METADATA];
896
- }
897
-
898
- function normalizeSimplifiedArrays(fields) {
899
- return fields.flatMap((field) => {
900
- if (isContainerField(field) && !isArrayField(field) && hasChildFields(field)) {
901
- return [{ ...field, fields: normalizeSimplifiedArrays(field.fields) }];
875
+ function normalizeSimplifiedArrays(fields) {
876
+ return fields.flatMap((field) => {
877
+ if (isContainerField(field) && !isArrayField(field) && hasChildFields(field)) {
878
+ return [{ ...field, fields: normalizeSimplifiedArrays(field.fields) }];
902
879
  }
903
880
  if (isArrayField(field) && !isSimplifiedArrayField(field)) {
904
881
  const normalizedItems = field.fields.map((item) => Array.isArray(item) ? normalizeSimplifiedArrays(item) : item);
@@ -1034,6 +1011,128 @@ function buildRemoveButton(config) {
1034
1011
  };
1035
1012
  }
1036
1013
 
1014
+ function collectFieldDefs(fields, out = []) {
1015
+ for (const field of fields ?? []) {
1016
+ out.push(field);
1017
+ const children = field.fields;
1018
+ if (Array.isArray(children)) {
1019
+ for (const child of children) {
1020
+ if (Array.isArray(child))
1021
+ collectFieldDefs(child, out);
1022
+ else
1023
+ collectFieldDefs([child], out);
1024
+ }
1025
+ }
1026
+ const metadata = getNormalizedArrayMetadata(field);
1027
+ const template = metadata?.template;
1028
+ if (Array.isArray(template))
1029
+ collectFieldDefs(template, out);
1030
+ else if (template)
1031
+ collectFieldDefs([template], out);
1032
+ if (metadata?.autoRemoveButton)
1033
+ collectFieldDefs([metadata.autoRemoveButton], out);
1034
+ }
1035
+ return out;
1036
+ }
1037
+ function injectFormComponentPreloader() {
1038
+ const fieldRegistry = injectFieldRegistry();
1039
+ const wrapperRegistry = inject(WRAPPER_REGISTRY);
1040
+ const wrapperCache = inject(WRAPPER_COMPONENT_CACHE);
1041
+ const wrapperLoadCache = inject(WRAPPER_COMPONENT_LOAD_CACHE);
1042
+ const autoAssociations = inject(WRAPPER_AUTO_ASSOCIATIONS);
1043
+ const defaultWrappers = inject(DEFAULT_WRAPPERS, { optional: true });
1044
+ const preloadedConfigs = new WeakSet();
1045
+ const preloadedFieldRoots = new WeakSet();
1046
+ const preloadFields = (fields) => {
1047
+ const unseenFields = fields.filter((field) => {
1048
+ if (preloadedFieldRoots.has(field))
1049
+ return false;
1050
+ preloadedFieldRoots.add(field);
1051
+ return true;
1052
+ });
1053
+ if (unseenFields.length === 0)
1054
+ return;
1055
+ const normalizedFields = normalizeSimplifiedArrays(unseenFields);
1056
+ const defs = collectFieldDefs(normalizedFields);
1057
+ if (defs.length === 0)
1058
+ return;
1059
+ const fieldTypes = new Set();
1060
+ const wrapperTypes = new Set();
1061
+ for (const def of defs) {
1062
+ if (def.type)
1063
+ fieldTypes.add(def.type);
1064
+ for (const wrapper of resolveWrappers(def, defaultWrappers?.(), autoAssociations, wrapperRegistry)) {
1065
+ wrapperTypes.add(wrapper.type);
1066
+ }
1067
+ }
1068
+ const started = [];
1069
+ for (const type of fieldTypes) {
1070
+ if (fieldRegistry.getLoadedComponent(type))
1071
+ continue;
1072
+ started.push(Promise.resolve(fieldRegistry.loadTypeComponent(type)).catch(() => undefined));
1073
+ }
1074
+ for (const type of wrapperTypes) {
1075
+ if (wrapperCache.has(type))
1076
+ continue;
1077
+ started.push(loadWrapperComponent(type, wrapperRegistry, wrapperCache, wrapperLoadCache).catch(() => undefined));
1078
+ }
1079
+ void Promise.allSettled(started);
1080
+ };
1081
+ return {
1082
+ preloadConfig: (config) => {
1083
+ if (preloadedConfigs.has(config))
1084
+ return;
1085
+ preloadedConfigs.add(config);
1086
+ const fields = config.fields;
1087
+ if (fields.length > 0 && fields.every(isPageField))
1088
+ return;
1089
+ preloadFields(fields);
1090
+ },
1091
+ preloadFields,
1092
+ };
1093
+ }
1094
+
1095
+ class PageChangeEvent {
1096
+ currentPageIndex;
1097
+ totalPages;
1098
+ previousPageIndex;
1099
+ type = 'page-change';
1100
+ constructor(
1101
+ currentPageIndex,
1102
+ totalPages,
1103
+ previousPageIndex) {
1104
+ this.currentPageIndex = currentPageIndex;
1105
+ this.totalPages = totalPages;
1106
+ this.previousPageIndex = previousPageIndex;
1107
+ }
1108
+ }
1109
+
1110
+ class PagerStateEvent {
1111
+ state;
1112
+ type = 'pager-state';
1113
+ constructor(state) {
1114
+ this.state = state;
1115
+ }
1116
+ }
1117
+
1118
+ const PAGE_PRELOAD_WINDOW = new InjectionToken('PAGE_PRELOAD_WINDOW', {
1119
+ providedIn: 'root',
1120
+ factory: () => 1,
1121
+ });
1122
+
1123
+ function collectLeafFieldKeys(fields) {
1124
+ const keys = [];
1125
+ for (const field of fields) {
1126
+ if (isRowField(field) || isGenericContainerField(field)) {
1127
+ keys.push(...collectLeafFieldKeys(field.fields));
1128
+ }
1129
+ else if (field.key) {
1130
+ keys.push(field.key);
1131
+ }
1132
+ }
1133
+ return keys;
1134
+ }
1135
+
1037
1136
  function createSlot() {
1038
1137
  let counter = 0;
1039
1138
  const templates = new Map();
@@ -1129,498 +1228,82 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
1129
1228
  type: Injectable
1130
1229
  }] });
1131
1230
 
1132
- const DEV_MODE = typeof ngDevMode === 'undefined' || !!ngDevMode;
1133
-
1134
- const DEFAULT_DEBOUNCE_MS = 500;
1135
- function createDebouncedEffect(source, callback, options) {
1136
- const { ms = DEFAULT_DEBOUNCE_MS, injector } = options;
1137
- const source$ = toObservable(source, { injector }).pipe(debounceTime(ms), distinctUntilChanged(), tap((value) => callback(value)));
1138
- return toSignal(source$, { injector });
1139
- }
1140
- function createDebouncedSignal(source, options) {
1141
- const { ms = DEFAULT_DEBOUNCE_MS, injector } = options;
1142
- const source$ = toObservable(source, { injector }).pipe(debounceTime(ms), distinctUntilChanged());
1143
- return toSignal(source$, { injector });
1144
- }
1145
-
1146
- function getConfigTrigger(config) {
1147
- if (!isStateLogicConfig(config)) {
1148
- return 'onChange';
1149
- }
1150
- return config.trigger ?? 'onChange';
1151
- }
1152
- function getConfigDebounceMs(config) {
1153
- if (!isStateLogicConfig(config)) {
1154
- return undefined;
1155
- }
1156
- if (config.trigger === 'debounced') {
1157
- return config.debounceMs;
1158
- }
1159
- return undefined;
1160
- }
1161
- function applyLogic(config, fieldPath) {
1162
- if (isDerivationLogicConfig(config))
1163
- return;
1164
- if (!isStateLogicConfig(config)) {
1165
- if (DEV_MODE) {
1166
- console.warn(`[Dynamic Forms] Unrecognized logic config type '${config.type}' in applyLogic. ` +
1167
- 'This config will be ignored. If this is a new logic type, ensure it is handled explicitly.');
1168
- }
1169
- return;
1170
- }
1171
- const path = fieldPath;
1172
- const trigger = getConfigTrigger(config);
1173
- const debounceMs = getConfigDebounceMs(config) ?? DEFAULT_DEBOUNCE_MS;
1174
- if (typeof config.condition === 'boolean') {
1175
- applyLogicFn(config.type, path, () => config.condition);
1176
- return;
1177
- }
1178
- if (isFormStateCondition(config.condition)) {
1179
- if (config.condition === 'formInvalid' || config.condition === 'pageInvalid') {
1180
- const reason = config.condition === 'formInvalid'
1181
- ? `it would create a validation cycle against the form's own validity`
1182
- : `a value field has no page context for it to resolve against`;
1183
- throw new DynamicFormError(`The '${config.condition}' form-state condition is not supported on value-field logic because ${reason}. ` +
1184
- `Use 'formSubmitting', or apply this condition to a button or container instead.`);
1185
- }
1186
- applyLogicFn(config.type, path, createFormStateLogicFunction(config.condition));
1187
- return;
1188
- }
1189
- const logicFn = trigger === 'debounced'
1190
- ? createDebouncedLogicFunction(config.condition, debounceMs)
1191
- : createLogicFunction(config.condition);
1192
- applyLogicFn(config.type, path, logicFn);
1193
- }
1194
- function applyLogicFn(type, path, logicFn) {
1195
- switch (type) {
1196
- case 'hidden':
1197
- hidden(path, logicFn);
1198
- break;
1199
- case 'readonly':
1200
- readonly(path, logicFn);
1201
- break;
1202
- case 'disabled':
1203
- disabled(path, logicFn);
1204
- break;
1205
- case 'required':
1206
- required(path, { when: logicFn });
1207
- break;
1208
- default: {
1209
- const _exhaustive = type;
1210
- throw new DynamicFormError(`Unhandled state logic type: ${_exhaustive}`);
1211
- }
1212
- }
1213
- }
1214
- function applyMultipleLogic(configs, fieldPath) {
1215
- configs.forEach((config) => applyLogic(config, fieldPath));
1216
- }
1217
-
1218
- function createTypePredicateFunction(predicate, logger) {
1219
- return (value) => {
1220
- try {
1221
- const result = ExpressionParser.evaluate(predicate, { value });
1222
- return Boolean(result);
1223
- }
1224
- catch (error) {
1225
- logger.error('Error evaluating type predicate:', predicate, error);
1226
- return false;
1227
- }
1228
- };
1229
- }
1230
-
1231
- function applySchema(config, fieldPath) {
1232
- const logger = inject(DynamicFormLogger);
1233
- const schemaRegistry = inject(SchemaRegistryService);
1234
- const schema = schemaRegistry.resolveSchema(config.schema);
1235
- if (!schema) {
1236
- const availableSchemas = Array.from(schemaRegistry.getAllSchemas().keys()).join(', ') || '<none>';
1237
- logger.error(`Schema not found: '${config.schema}'. ` +
1238
- `Available schemas: ${availableSchemas}. ` +
1239
- `Ensure the schema is registered in your schema registry.`);
1240
- return;
1241
- }
1242
- const schemaFn = createSchemaFunction(schema, logger);
1243
- const path = fieldPath;
1244
- switch (config.type) {
1245
- case 'apply':
1246
- apply(path, schemaFn);
1247
- break;
1248
- case 'applyWhen':
1249
- if (config.condition) {
1250
- const conditionFn = createLogicFunction(config.condition);
1251
- applyWhen(path, conditionFn, schemaFn);
1252
- }
1253
- break;
1254
- case 'applyWhenValue':
1255
- if (config.typePredicate) {
1256
- const predicate = createTypePredicateFunction(config.typePredicate, logger);
1257
- applyWhenValue(path, predicate, schemaFn);
1258
- }
1259
- break;
1260
- case 'applyEach':
1261
- applyEach(path, schemaFn);
1262
- break;
1263
- }
1264
- }
1265
- function createSchemaFunction(schema, logger) {
1266
- return (path) => {
1267
- schema.validators?.forEach((validatorConfig) => {
1268
- applyValidator(validatorConfig, path);
1269
- });
1270
- schema.logic?.forEach((logicConfig) => {
1271
- applyLogic(logicConfig, path);
1272
- });
1273
- schema.subSchemas?.forEach((subSchemaConfig) => {
1274
- applySchema(subSchemaConfig, path);
1275
- });
1276
- };
1277
- }
1278
-
1279
- function combineAncestorHiddenLogics(logics) {
1280
- if (logics.length === 0)
1281
- return undefined;
1282
- if (logics.length === 1)
1283
- return logics[0];
1284
- return (ctx) => logics.some((fn) => fn(ctx));
1285
- }
1286
- function resolveFieldOwnContext(fieldDef, parentContext) {
1287
- if (fieldDef.validateWhenHidden === undefined)
1288
- return parentContext;
1289
- if (fieldDef.validateWhenHidden === parentContext.validateWhenHidden)
1290
- return parentContext;
1291
- return {
1292
- ...parentContext,
1293
- validateWhenHidden: fieldDef.validateWhenHidden,
1294
- };
1295
- }
1296
- function resolveDescendantContext(fieldDef, ownContext) {
1297
- let ancestorAlwaysHidden = ownContext.ancestorAlwaysHidden;
1298
- let ancestorHiddenLogics = ownContext.ancestorHiddenLogics;
1299
- let mutated = false;
1300
- let ancestorRequired = ownContext.ancestorRequired;
1301
- if (fieldDef.required !== undefined && fieldDef.required !== ancestorRequired) {
1302
- ancestorRequired = fieldDef.required;
1303
- mutated = true;
1304
- }
1305
- if (fieldDef.hidden === true && !ancestorAlwaysHidden) {
1306
- ancestorAlwaysHidden = true;
1307
- mutated = true;
1308
- }
1309
- if (fieldDef.logic) {
1310
- for (const logic of fieldDef.logic) {
1311
- if (!isStateLogicConfig(logic) || logic.type !== 'hidden')
1312
- continue;
1313
- const condition = logic.condition;
1314
- if (condition === true) {
1315
- if (!ancestorAlwaysHidden) {
1316
- ancestorAlwaysHidden = true;
1317
- mutated = true;
1318
- }
1319
- continue;
1320
- }
1321
- if (condition === false)
1322
- continue;
1323
- if (typeof condition === 'string')
1324
- continue;
1325
- const fn = createLogicFunction(condition);
1326
- ancestorHiddenLogics = ancestorHiddenLogics.length === 0 ? [fn] : [...ancestorHiddenLogics, fn];
1327
- mutated = true;
1328
- }
1329
- }
1330
- if (!mutated)
1331
- return ownContext;
1332
- return {
1333
- validateWhenHidden: ownContext.validateWhenHidden,
1334
- ancestorAlwaysHidden,
1335
- ancestorHiddenLogics,
1336
- ancestorRequired,
1337
- };
1338
- }
1339
-
1340
- const DEFAULT_FIELD_TREE_MAPPING_CONTEXT = {
1341
- validateWhenHidden: false,
1342
- ancestorAlwaysHidden: false,
1343
- ancestorHiddenLogics: [],
1344
- };
1345
- function applyStringValidators(path, config) {
1346
- if (config.email) {
1347
- email(path);
1348
- }
1349
- if (config.minLength !== undefined) {
1350
- minLength(path, config.minLength);
1351
- }
1352
- if (config.maxLength !== undefined) {
1353
- maxLength(path, config.maxLength);
1354
- }
1355
- if (config.pattern) {
1356
- let regex;
1357
- if (typeof config.pattern === 'string') {
1358
- try {
1359
- regex = new RegExp(config.pattern);
1360
- }
1361
- catch (e) {
1362
- throw new DynamicFormError(`Invalid regex pattern: '${config.pattern}' — ${e instanceof Error ? e.message : String(e)}`);
1363
- }
1364
- }
1365
- else {
1366
- regex = config.pattern;
1367
- }
1368
- pattern(path, regex);
1369
- }
1370
- }
1371
- function applyNumberValidators(path, config) {
1372
- if (config.min !== undefined) {
1373
- min(path, config.min);
1374
- }
1375
- if (config.max !== undefined) {
1376
- max(path, config.max);
1377
- }
1378
- }
1379
- function getStringValidationConfig(fieldDef) {
1380
- if (!fieldDef.email && fieldDef.minLength === undefined && fieldDef.maxLength === undefined && !fieldDef.pattern) {
1381
- return undefined;
1231
+ function inheritContainerExclusion(container, child) {
1232
+ const inheritedHidden = container.hidden === true || child.inheritedExclusionState?.hidden === true;
1233
+ const inheritedDisabled = container.disabled === true || child.inheritedExclusionState?.disabled === true;
1234
+ const inheritedReadonly = container.readonly === true || child.inheritedExclusionState?.readonly === true;
1235
+ const hasInheritedState = inheritedHidden || inheritedDisabled || inheritedReadonly;
1236
+ const containerExcludeConfig = container.excludeValueIfHidden !== undefined ||
1237
+ container.excludeValueIfDisabled !== undefined ||
1238
+ container.excludeValueIfReadonly !== undefined;
1239
+ if (!hasInheritedState && !containerExcludeConfig) {
1240
+ return child;
1382
1241
  }
1383
1242
  return {
1384
- email: fieldDef.email,
1385
- minLength: fieldDef.minLength,
1386
- maxLength: fieldDef.maxLength,
1387
- pattern: fieldDef.pattern,
1388
- };
1389
- }
1390
- function getNumberValidationConfig(fieldDef) {
1391
- const minVal = fieldDef.min ?? fieldDef.minValue;
1392
- const maxVal = fieldDef.max ?? fieldDef.maxValue;
1393
- if (minVal === undefined && maxVal === undefined) {
1394
- return undefined;
1395
- }
1396
- return { min: minVal, max: maxVal };
1397
- }
1398
- function mapFieldToForm(fieldDef, fieldPath, inheritedContext = DEFAULT_FIELD_TREE_MAPPING_CONTEXT) {
1399
- const ownContext = resolveFieldOwnContext(fieldDef, inheritedContext);
1400
- if (isPageField(fieldDef) || isRowField(fieldDef) || isGenericContainerField(fieldDef)) {
1401
- const descendantContext = resolveDescendantContext(fieldDef, ownContext);
1402
- mapContainerChildren(fieldDef.fields, fieldPath, descendantContext);
1403
- return;
1404
- }
1405
- if (isGroupField(fieldDef)) {
1406
- const descendantContext = resolveDescendantContext(fieldDef, ownContext);
1407
- applyContainerValidators(fieldDef, fieldPath, descendantContext);
1408
- mapContainerChildren(fieldDef.fields, fieldPath, descendantContext);
1409
- return;
1410
- }
1411
- if (isArrayField(fieldDef)) {
1412
- const descendantContext = resolveDescendantContext(fieldDef, ownContext);
1413
- mapArrayFieldToForm(fieldDef, fieldPath, descendantContext);
1414
- return;
1415
- }
1416
- mapLeafField(fieldDef, fieldPath, ownContext);
1417
- }
1418
- function mapContainerChildren(fields, parentPath, inheritedContext) {
1419
- if (!fields)
1420
- return;
1421
- const pathRecord = parentPath;
1422
- for (const field of fields) {
1423
- if (isPageField(field) || isRowField(field) || isGenericContainerField(field)) {
1424
- const layoutOwn = resolveFieldOwnContext(field, inheritedContext);
1425
- const layoutDescendant = resolveDescendantContext(field, layoutOwn);
1426
- mapContainerChildren(field.fields, parentPath, layoutDescendant);
1427
- continue;
1428
- }
1429
- if (!field.key)
1430
- continue;
1431
- const childPath = pathRecord[field.key];
1432
- if (childPath) {
1433
- mapFieldToForm(field, childPath, inheritedContext);
1434
- }
1435
- }
1436
- }
1437
- function isValidationStateLogic(config) {
1438
- return isStateLogicConfig(config) && config.type === 'required';
1439
- }
1440
- function mapLeafField(fieldDef, fieldPath, context) {
1441
- const validationField = fieldDef;
1442
- const path = fieldPath;
1443
- applyFieldState(fieldDef, path);
1444
- if (validationField.logic) {
1445
- for (const config of validationField.logic) {
1446
- if (!isValidationStateLogic(config)) {
1447
- applyLogic(config, fieldPath);
1448
- }
1449
- }
1450
- }
1451
- if (validationField.schemas) {
1452
- for (const config of validationField.schemas) {
1453
- applySchema(config, fieldPath);
1454
- }
1455
- }
1456
- const applyValidation = (validationPath) => {
1457
- applySimpleValidationRules(validationField, validationPath, context);
1458
- if (validationField.validators) {
1459
- for (const config of validationField.validators) {
1460
- applyValidator(config, validationPath);
1461
- }
1462
- }
1463
- if (validationField.logic) {
1464
- for (const config of validationField.logic) {
1465
- if (isValidationStateLogic(config)) {
1466
- applyLogic(config, validationPath);
1467
- }
1468
- }
1469
- }
1243
+ ...child,
1244
+ ...(hasInheritedState
1245
+ ? { inheritedExclusionState: { hidden: inheritedHidden, disabled: inheritedDisabled, readonly: inheritedReadonly } }
1246
+ : {}),
1247
+ excludeValueIfHidden: child.excludeValueIfHidden ?? container.excludeValueIfHidden,
1248
+ excludeValueIfDisabled: child.excludeValueIfDisabled ?? container.excludeValueIfDisabled,
1249
+ excludeValueIfReadonly: child.excludeValueIfReadonly ?? container.excludeValueIfReadonly,
1470
1250
  };
1471
- applyHiddenGatedValidation(path, context, applyValidation);
1472
- }
1473
- function applyHiddenGatedValidation(path, context, apply) {
1474
- if (context.validateWhenHidden) {
1475
- apply(path);
1476
- return;
1477
- }
1478
- if (context.ancestorAlwaysHidden) {
1479
- return;
1480
- }
1481
- const ancestorHiddenLogic = combineAncestorHiddenLogics(context.ancestorHiddenLogics);
1482
- applyWhen(path, (ctx) => {
1483
- if (ctx.state.hidden())
1484
- return false;
1485
- if (ancestorHiddenLogic && ancestorHiddenLogic(ctx))
1486
- return false;
1487
- return true;
1488
- }, schema((subPath) => apply(subPath)));
1489
- }
1490
- function applyContainerValidators(fieldDef, fieldPath, context) {
1491
- const validators = fieldDef.validators;
1492
- if (!validators || validators.length === 0)
1493
- return;
1494
- applyHiddenGatedValidation(fieldPath, context, (validationPath) => {
1495
- for (const config of validators) {
1496
- applyValidator(config, validationPath, 'tree');
1497
- }
1498
- });
1499
- }
1500
- function applySimpleValidationRules(fieldDef, path, context) {
1501
- const declaresRequiredLogic = fieldDef.logic?.some(isValidationStateLogic) ?? false;
1502
- if (fieldDef.required ?? (declaresRequiredLogic ? false : context.ancestorRequired)) {
1503
- required(path);
1504
- }
1505
- const stringConfig = getStringValidationConfig(fieldDef);
1506
- if (stringConfig) {
1507
- applyStringValidators(path, stringConfig);
1508
- }
1509
- const numberConfig = getNumberValidationConfig(fieldDef);
1510
- if (numberConfig) {
1511
- applyNumberValidators(path, numberConfig);
1512
- }
1513
- }
1514
- function applyFieldState(fieldDef, path) {
1515
- if (fieldDef.disabled) {
1516
- disabled(path);
1517
- }
1518
- if (fieldDef.readonly) {
1519
- readonly(path);
1520
- }
1521
- if (fieldDef.hidden) {
1522
- hidden(path, () => true);
1523
- }
1524
1251
  }
1525
- function mapArrayFieldToForm(arrayField, fieldPath, context) {
1526
- if (!isArrayField(arrayField)) {
1527
- return;
1528
- }
1529
- if (arrayField.minLength !== undefined) {
1530
- minLength(fieldPath, arrayField.minLength);
1531
- }
1532
- if (arrayField.maxLength !== undefined) {
1533
- maxLength(fieldPath, arrayField.maxLength);
1534
- }
1535
- applyContainerValidators(arrayField, fieldPath, context);
1536
- let itemDefinitions = arrayField.fields;
1537
- if (!itemDefinitions || itemDefinitions.length === 0) {
1538
- const metadataTemplate = getNormalizedArrayMetadata(arrayField)?.template;
1539
- if (metadataTemplate) {
1540
- itemDefinitions = [
1541
- Array.isArray(metadataTemplate) ? [...metadataTemplate] : metadataTemplate,
1542
- ];
1543
- }
1544
- }
1545
- if (!itemDefinitions || itemDefinitions.length === 0) {
1546
- const emptyItemSchema = schema(() => {
1547
- });
1548
- applyEach(fieldPath, emptyItemSchema);
1549
- return;
1550
- }
1551
- let hasPrimitiveItems = false;
1552
- const allObjectFields = [];
1553
- for (const itemDef of itemDefinitions) {
1554
- if (Array.isArray(itemDef)) {
1555
- collectFieldsFromObjectItem(itemDef, allObjectFields);
1252
+ function flattenFields(fields, registry, options = {}) {
1253
+ const result = [];
1254
+ let autoKeyCounter = 0;
1255
+ for (const field of fields) {
1256
+ const valueHandling = getFieldValueHandling(field.type, registry);
1257
+ if (options.preserveRows && (isRowField(field) || isGenericContainerField(field))) {
1258
+ if (field.fields) {
1259
+ const flattenedChildren = flattenFields(normalizeFieldsArray(field.fields), registry, options);
1260
+ const autoPrefix = isRowField(field) ? 'auto_row' : 'auto_container';
1261
+ result.push({
1262
+ ...field,
1263
+ fields: flattenedChildren,
1264
+ key: field.key || `${autoPrefix}_${autoKeyCounter++}`,
1265
+ });
1266
+ }
1556
1267
  }
1557
- else if (isPageField(itemDef) ||
1558
- isRowField(itemDef) ||
1559
- isGenericContainerField(itemDef)) {
1560
- collectFieldsFromObjectItem([itemDef], allObjectFields);
1268
+ else if (valueHandling === 'flatten' && 'fields' in field) {
1269
+ if (field.fields) {
1270
+ const fields = field.fields;
1271
+ const flattenedChildren = flattenFields(normalizeFieldsArray(fields), registry, options);
1272
+ result.push(...flattenedChildren.map((child) => inheritContainerExclusion(field, child)));
1273
+ }
1561
1274
  }
1562
- else {
1563
- hasPrimitiveItems = true;
1275
+ else if (isGroupField(field)) {
1276
+ const childFieldsArray = Object.values(field.fields);
1277
+ const flattenedChildren = flattenFields(childFieldsArray, registry, options);
1278
+ result.push({
1279
+ ...field,
1280
+ fields: flattenedChildren,
1281
+ key: field.key || `auto_group_${autoKeyCounter++}`,
1282
+ });
1564
1283
  }
1565
- }
1566
- if (hasPrimitiveItems && allObjectFields.length === 0) {
1567
- const primitiveItemSchema = schema(() => {
1568
- });
1569
- applyEach(fieldPath, primitiveItemSchema);
1570
- return;
1571
- }
1572
- const itemSchema = schema((itemPath) => {
1573
- const pathRecord = itemPath;
1574
- for (const templateField of allObjectFields) {
1575
- if (isRowField(templateField) || isPageField(templateField) || isGenericContainerField(templateField)) {
1576
- const layoutOwn = resolveFieldOwnContext(templateField, context);
1577
- const layoutDescendant = resolveDescendantContext(templateField, layoutOwn);
1578
- mapContainerChildren(templateField.fields, itemPath, layoutDescendant);
1579
- }
1580
- else if (isGroupField(templateField)) {
1581
- const groupKey = templateField.key;
1582
- if (groupKey) {
1583
- const groupPath = pathRecord[groupKey];
1584
- if (groupPath) {
1585
- mapFieldToForm(templateField, groupPath, context);
1586
- }
1587
- }
1588
- else {
1589
- const groupOwn = resolveFieldOwnContext(templateField, context);
1590
- mapContainerChildren(templateField.fields, itemPath, resolveDescendantContext(templateField, groupOwn));
1591
- }
1592
- }
1593
- else {
1594
- const fieldKey = templateField.key;
1595
- if (fieldKey) {
1596
- const fieldPathForKey = pathRecord[fieldKey];
1597
- if (fieldPathForKey) {
1598
- mapFieldToForm(templateField, fieldPathForKey, context);
1599
- }
1284
+ else if (isArrayField(field)) {
1285
+ const flattenedItemTemplates = field.fields.map((itemDef) => {
1286
+ if (!Array.isArray(itemDef)) {
1287
+ const flattened = flattenFields([itemDef], registry, options);
1288
+ return flattened[0];
1600
1289
  }
1601
- }
1602
- }
1603
- });
1604
- applyEach(fieldPath, itemSchema);
1605
- }
1606
- function collectFieldsFromObjectItem(itemFields, allFields) {
1607
- const seenKeys = new Set(allFields.map((f) => f.key).filter(Boolean));
1608
- for (const field of itemFields) {
1609
- if (isRowField(field) || isPageField(field) || isGenericContainerField(field)) {
1610
- const containerKey = `__container_${field.type}_${JSON.stringify(field.fields?.map((f) => f.key))}`;
1611
- if (!seenKeys.has(containerKey)) {
1612
- seenKeys.add(containerKey);
1613
- allFields.push(field);
1614
- }
1290
+ return flattenFields([...itemDef], registry, options);
1291
+ });
1292
+ result.push({
1293
+ ...field,
1294
+ fields: flattenedItemTemplates,
1295
+ key: field.key || `auto_array_${autoKeyCounter++}`,
1296
+ });
1615
1297
  }
1616
1298
  else {
1617
- const key = field.key;
1618
- if (key && !seenKeys.has(key)) {
1619
- seenKeys.add(key);
1620
- allFields.push(field);
1621
- }
1299
+ const key = field.key || `auto_field_${autoKeyCounter++}`;
1300
+ result.push({
1301
+ ...field,
1302
+ key,
1303
+ });
1622
1304
  }
1623
1305
  }
1306
+ return result;
1624
1307
  }
1625
1308
 
1626
1309
  function getFieldDefaultValue(field, registry) {
@@ -1726,160 +1409,6 @@ function getFieldDefaultValue(field, registry) {
1726
1409
  return '';
1727
1410
  }
1728
1411
 
1729
- function applyFormLevelSchema(path, formLevelSchema) {
1730
- if (isStandardSchemaMarker(formLevelSchema)) {
1731
- validateStandardSchema(path, formLevelSchema.schema);
1732
- }
1733
- else if (typeof formLevelSchema === 'function') {
1734
- formLevelSchema(path);
1735
- }
1736
- }
1737
- function createFormLevelSchema(formLevelSchema) {
1738
- return schema((path) => {
1739
- applyFormLevelSchema(path, formLevelSchema);
1740
- });
1741
- }
1742
-
1743
- const VALIDATION_EXECUTION_DEFAULTS = new InjectionToken('VALIDATION_EXECUTION_DEFAULTS', {
1744
- providedIn: 'root',
1745
- factory: () => ({
1746
- validateWhenHidden: false,
1747
- }),
1748
- });
1749
-
1750
- function createSchemaFromFields(fields, registry, options = {}) {
1751
- const validationExecutionDefaults = inject(VALIDATION_EXECUTION_DEFAULTS);
1752
- const { formLevelSchema } = options;
1753
- const rootContext = {
1754
- validateWhenHidden: options.validateWhenHidden ?? validationExecutionDefaults.validateWhenHidden,
1755
- ancestorAlwaysHidden: false,
1756
- ancestorHiddenLogics: [],
1757
- };
1758
- return schema((path) => {
1759
- for (const fieldDef of fields) {
1760
- const valueHandling = getFieldValueHandling(fieldDef.type, registry);
1761
- if (valueHandling === 'exclude') {
1762
- continue;
1763
- }
1764
- if (valueHandling === 'flatten' && hasChildFields(fieldDef)) {
1765
- const flattenedOwn = resolveFieldOwnContext(fieldDef, rootContext);
1766
- const flattenedContext = resolveDescendantContext(fieldDef, flattenedOwn);
1767
- for (const childField of normalizeFieldsArray(fieldDef.fields)) {
1768
- if (!childField.key)
1769
- continue;
1770
- const childPath = path[childField.key];
1771
- if (childPath) {
1772
- mapFieldToForm(childField, childPath, flattenedContext);
1773
- }
1774
- }
1775
- continue;
1776
- }
1777
- const fieldPath = path[fieldDef.key];
1778
- if (!fieldPath) {
1779
- continue;
1780
- }
1781
- mapFieldToForm(fieldDef, fieldPath, rootContext);
1782
- }
1783
- if (formLevelSchema) {
1784
- applyFormLevelSchema(path, formLevelSchema);
1785
- }
1786
- });
1787
- }
1788
- function fieldsToDefaultValues(fields, registry) {
1789
- const defaultValues = {};
1790
- for (const field of fields) {
1791
- if (!field.key)
1792
- continue;
1793
- const valueHandling = getFieldValueHandling(field.type, registry);
1794
- if (valueHandling === 'flatten') {
1795
- continue;
1796
- }
1797
- const value = getFieldDefaultValue(field, registry);
1798
- if (value !== undefined) {
1799
- defaultValues[field.key] = value;
1800
- }
1801
- }
1802
- return defaultValues;
1803
- }
1804
-
1805
- function inheritContainerExclusion(container, child) {
1806
- const inheritedHidden = container.hidden === true || child.inheritedExclusionState?.hidden === true;
1807
- const inheritedDisabled = container.disabled === true || child.inheritedExclusionState?.disabled === true;
1808
- const inheritedReadonly = container.readonly === true || child.inheritedExclusionState?.readonly === true;
1809
- const hasInheritedState = inheritedHidden || inheritedDisabled || inheritedReadonly;
1810
- const containerExcludeConfig = container.excludeValueIfHidden !== undefined ||
1811
- container.excludeValueIfDisabled !== undefined ||
1812
- container.excludeValueIfReadonly !== undefined;
1813
- if (!hasInheritedState && !containerExcludeConfig) {
1814
- return child;
1815
- }
1816
- return {
1817
- ...child,
1818
- ...(hasInheritedState
1819
- ? { inheritedExclusionState: { hidden: inheritedHidden, disabled: inheritedDisabled, readonly: inheritedReadonly } }
1820
- : {}),
1821
- excludeValueIfHidden: child.excludeValueIfHidden ?? container.excludeValueIfHidden,
1822
- excludeValueIfDisabled: child.excludeValueIfDisabled ?? container.excludeValueIfDisabled,
1823
- excludeValueIfReadonly: child.excludeValueIfReadonly ?? container.excludeValueIfReadonly,
1824
- };
1825
- }
1826
- function flattenFields(fields, registry, options = {}) {
1827
- const result = [];
1828
- let autoKeyCounter = 0;
1829
- for (const field of fields) {
1830
- const valueHandling = getFieldValueHandling(field.type, registry);
1831
- if (options.preserveRows && (isRowField(field) || isGenericContainerField(field))) {
1832
- if (field.fields) {
1833
- const flattenedChildren = flattenFields(normalizeFieldsArray(field.fields), registry, options);
1834
- const autoPrefix = isRowField(field) ? 'auto_row' : 'auto_container';
1835
- result.push({
1836
- ...field,
1837
- fields: flattenedChildren,
1838
- key: field.key || `${autoPrefix}_${autoKeyCounter++}`,
1839
- });
1840
- }
1841
- }
1842
- else if (valueHandling === 'flatten' && 'fields' in field) {
1843
- if (field.fields) {
1844
- const fields = field.fields;
1845
- const flattenedChildren = flattenFields(normalizeFieldsArray(fields), registry, options);
1846
- result.push(...flattenedChildren.map((child) => inheritContainerExclusion(field, child)));
1847
- }
1848
- }
1849
- else if (isGroupField(field)) {
1850
- const childFieldsArray = Object.values(field.fields);
1851
- const flattenedChildren = flattenFields(childFieldsArray, registry, options);
1852
- result.push({
1853
- ...field,
1854
- fields: flattenedChildren,
1855
- key: field.key || `auto_group_${autoKeyCounter++}`,
1856
- });
1857
- }
1858
- else if (isArrayField(field)) {
1859
- const flattenedItemTemplates = field.fields.map((itemDef) => {
1860
- if (!Array.isArray(itemDef)) {
1861
- const flattened = flattenFields([itemDef], registry, options);
1862
- return flattened[0];
1863
- }
1864
- return flattenFields([...itemDef], registry, options);
1865
- });
1866
- result.push({
1867
- ...field,
1868
- fields: flattenedItemTemplates,
1869
- key: field.key || `auto_array_${autoKeyCounter++}`,
1870
- });
1871
- }
1872
- else {
1873
- const key = field.key || `auto_field_${autoKeyCounter++}`;
1874
- result.push({
1875
- ...field,
1876
- key,
1877
- });
1878
- }
1879
- }
1880
- return result;
1881
- }
1882
-
1883
1412
  function createContainerFieldProcessors() {
1884
1413
  const flattenCache = new WeakMap();
1885
1414
  const memoizedFlattenFields = (fields, registry) => {
@@ -2314,60 +1843,6 @@ function isValidFormConfiguration(fields) {
2314
1843
  return FormModeValidator.validateFormConfiguration(fields).isValid;
2315
1844
  }
2316
1845
 
2317
- const COMPONENT_CACHE = new InjectionToken('COMPONENT_CACHE', {
2318
- providedIn: 'root',
2319
- factory: () => new Map(),
2320
- });
2321
- function injectFieldRegistry() {
2322
- const registry = inject(FIELD_REGISTRY);
2323
- const componentCache = inject(COMPONENT_CACHE);
2324
- return {
2325
- getType(name) {
2326
- return registry.get(name);
2327
- },
2328
- hasType(name) {
2329
- return registry.has(name);
2330
- },
2331
- async loadTypeComponent(name) {
2332
- const fieldType = registry.get(name);
2333
- if (!fieldType) {
2334
- throw new DynamicFormError(`Field type "${name}" is not registered`);
2335
- }
2336
- if (!fieldType.loadComponent) {
2337
- return undefined;
2338
- }
2339
- const cached = componentCache.get(name);
2340
- if (cached) {
2341
- return cached;
2342
- }
2343
- try {
2344
- const component = resolveDefaultExport(await fieldType.loadComponent());
2345
- if (component) {
2346
- componentCache.set(name, component);
2347
- }
2348
- return component;
2349
- }
2350
- catch (error) {
2351
- throw new DynamicFormError(`Failed to load component for field type "${name}": ${error}`);
2352
- }
2353
- },
2354
- getLoadedComponent(name) {
2355
- return componentCache.get(name);
2356
- },
2357
- getTypes() {
2358
- return Array.from(registry.values());
2359
- },
2360
- registerTypes(types) {
2361
- types.forEach((type) => {
2362
- registry.set(type.name, type);
2363
- });
2364
- },
2365
- get raw() {
2366
- return registry;
2367
- },
2368
- };
2369
- }
2370
-
2371
1846
  function collectFieldData(fields, data, registry, pathPrefix, collectKeys = true) {
2372
1847
  for (const field of fields) {
2373
1848
  const valueHandling = field.type ? getFieldValueHandling(field.type, registry) : 'include';
@@ -2414,7 +1889,9 @@ function validateRegexPattern(pattern, fieldKey, errors) {
2414
1889
  new RegExp(pattern);
2415
1890
  }
2416
1891
  catch (e) {
2417
- errors.push(`Invalid regex pattern in validator for field '${fieldKey}': '${pattern}' — ${e instanceof Error ? e.message : String(e)}`);
1892
+ errors.push(DEV_MODE
1893
+ ? `Invalid regex pattern in validator for field '${fieldKey}': '${pattern}' — ${e instanceof Error ? e.message : String(e)}`
1894
+ : `Invalid regex pattern: '${pattern}'`);
2418
1895
  }
2419
1896
  }
2420
1897
  function validateNoDuplicateKeys(allKeys) {
@@ -2430,7 +1907,9 @@ function validateNoDuplicateKeys(allKeys) {
2430
1907
  const duplicateList = Array.from(duplicates)
2431
1908
  .map((k) => `'${k}'`)
2432
1909
  .join(', ');
2433
- throw new DynamicFormError(`Duplicate field keys detected: ${duplicateList}. Field keys must be unique within their group scope (the same key may appear inside different groups).`);
1910
+ throw new DynamicFormError(DEV_MODE
1911
+ ? `Duplicate field keys detected: ${duplicateList}. Field keys must be unique within their group scope (the same key may appear inside different groups).`
1912
+ : `Duplicate field keys detected: ${duplicateList}.`);
2434
1913
  }
2435
1914
  }
2436
1915
  function validateNoDomIdCollisions(domIds) {
@@ -2446,9 +1925,11 @@ function validateNoDomIdCollisions(domIds) {
2446
1925
  const duplicateList = Array.from(duplicates)
2447
1926
  .map((k) => `'${k}'`)
2448
1927
  .join(', ');
2449
- throw new DynamicFormError(`DOM id collision detected for: ${duplicateList}. Group-nested keys are joined with '_' to form DOM ids ` +
2450
- `(e.g. group 'foo' + leaf 'bar' renders as id='foo_bar'). A separate top-level key like 'foo_bar' or a different ` +
2451
- `group/leaf pair that underscores to the same string will produce duplicate ids. Rename one of the colliding keys.`);
1928
+ throw new DynamicFormError(DEV_MODE
1929
+ ? `DOM id collision detected for: ${duplicateList}. Group-nested keys are joined with '_' to form DOM ids ` +
1930
+ `(e.g. group 'foo' + leaf 'bar' renders as id='foo_bar'). A separate top-level key like 'foo_bar' or a different ` +
1931
+ `group/leaf pair that underscores to the same string will produce duplicate ids. Rename one of the colliding keys.`
1932
+ : `DOM id collision detected for: ${duplicateList}.`);
2452
1933
  }
2453
1934
  }
2454
1935
  function validateFieldTypesRegistered(allTypes, registry, logger) {
@@ -2478,12 +1959,15 @@ function validateFormConfig(fields, registry, logger) {
2478
1959
  const list = Array.from(new Set(data.dottedKeys))
2479
1960
  .map((k) => `'${k}'`)
2480
1961
  .join(', ');
2481
- throw new DynamicFormError(`Field key(s) contain a dot: ${list}. Dotted keys are not supported — express nesting with 'group' fields ` +
2482
- `(e.g. { type: 'group', key: 'address', fields: [{ type: 'input', key: 'city' }] }).`);
1962
+ throw new DynamicFormError(DEV_MODE
1963
+ ? `Field key(s) contain a dot: ${list}. Dotted keys are not supported — express nesting with 'group' fields ` +
1964
+ `(e.g. { type: 'group', key: 'address', fields: [{ type: 'input', key: 'city' }] }).`
1965
+ : `Field key(s) contain a dot: ${list}.`);
2483
1966
  }
2484
1967
  validateNoDuplicateKeys(data.keys);
2485
1968
  validateNoDomIdCollisions(data.domIds);
2486
- validateFieldTypesRegistered(data.types, registry, logger);
1969
+ if (DEV_MODE)
1970
+ validateFieldTypesRegistered(data.types, registry, logger);
2487
1971
  if (data.regexErrors.length > 0) {
2488
1972
  throw new DynamicFormError(data.regexErrors.length === 1
2489
1973
  ? data.regexErrors[0]
@@ -2698,6 +2182,33 @@ function resolveExclusionConfig(global, form, field) {
2698
2182
  excludeValueIfReadonly: field?.excludeValueIfReadonly ?? form?.excludeValueIfReadonly ?? global.excludeValueIfReadonly,
2699
2183
  };
2700
2184
  }
2185
+ function hasEnabledValueExclusion(fields, global, form) {
2186
+ return fields.some((field) => {
2187
+ const resolved = resolveExclusionConfig(global, form, field);
2188
+ if (resolved.excludeValueIfHidden || resolved.excludeValueIfDisabled || resolved.excludeValueIfReadonly) {
2189
+ return true;
2190
+ }
2191
+ return (isGroupField(field) && !!field.fields && hasEnabledValueExclusion(Object.values(field.fields), global, form));
2192
+ });
2193
+ }
2194
+ function filterDeclaredValueShape(rawValue, fields, registry) {
2195
+ const kept = {};
2196
+ for (const field of fields) {
2197
+ const key = field.key;
2198
+ if (!key || !(key in rawValue))
2199
+ continue;
2200
+ const valueHandling = getFieldValueHandling(field.type, registry);
2201
+ if (valueHandling === 'exclude' || valueHandling === 'flatten')
2202
+ continue;
2203
+ const value = rawValue[key];
2204
+ if (isGroupField(field) && field.fields && value !== null && typeof value === 'object' && !Array.isArray(value)) {
2205
+ kept[key] = filterDeclaredValueShape(value, Object.values(field.fields), registry);
2206
+ continue;
2207
+ }
2208
+ kept[key] = value;
2209
+ }
2210
+ return kept;
2211
+ }
2701
2212
  function shouldExcludeField(field, fieldState, config) {
2702
2213
  const state = fieldState();
2703
2214
  const inherited = field.inheritedExclusionState;
@@ -2715,7 +2226,10 @@ function shouldExcludeField(field, fieldState, config) {
2715
2226
  }
2716
2227
  return false;
2717
2228
  }
2718
- function filterFormValue(rawValue, schemaFields, formTree, registry, globalDefaults, formOptions) {
2229
+ function filterFormValue(rawValue, schemaFields, formTree, registry, globalDefaults, formOptions, exclusionEnabled = hasEnabledValueExclusion(schemaFields, globalDefaults, formOptions)) {
2230
+ if (!exclusionEnabled) {
2231
+ return filterDeclaredValueShape(rawValue, schemaFields, registry);
2232
+ }
2719
2233
  const result = {};
2720
2234
  for (const field of schemaFields) {
2721
2235
  const key = field.key;
@@ -2751,7 +2265,7 @@ function filterFormValue(rawValue, schemaFields, formTree, registry, globalDefau
2751
2265
  if (groupValue && typeof groupValue === 'object') {
2752
2266
  const childFields = Object.values(field.fields);
2753
2267
  const nestedTree = fieldState;
2754
- result[key] = filterFormValue(groupValue, childFields, nestedTree, registry, globalDefaults, formOptions);
2268
+ result[key] = filterFormValue(groupValue, childFields, nestedTree, registry, globalDefaults, formOptions, true);
2755
2269
  }
2756
2270
  else {
2757
2271
  result[key] = groupValue;
@@ -3064,7 +2578,7 @@ class FormStateMachine {
3064
2578
  if (!isTransitioningState(state) || state.phase !== Phase.Restoring) {
3065
2579
  return { state, sideEffects: [] };
3066
2580
  }
3067
- if (!state.pendingFormSetup) {
2581
+ if (DEV_MODE && !state.pendingFormSetup) {
3068
2582
  this.config.logger.warn('handleRestoreComplete: pendingFormSetup was not set — falling back to recomputing. ' +
3069
2583
  'This indicates a bug in the transition flow.');
3070
2584
  }
@@ -3127,7 +2641,7 @@ class FormStateMachine {
3127
2641
  const state = untracked(() => this._state());
3128
2642
  if (!isTransitioningState(state) || state.phase !== Phase.Restoring)
3129
2643
  return;
3130
- if (!state.pendingFormSetup) {
2644
+ if (DEV_MODE && !state.pendingFormSetup) {
3131
2645
  this.config.logger.warn('RestoreValues effect: pendingFormSetup was not set — falling back to recomputing.');
3132
2646
  }
3133
2647
  const formSetup = state.pendingFormSetup ?? this.config.createFormSetup(state.pendingConfig);
@@ -3269,13 +2783,6 @@ class FormStateManager {
3269
2783
  return detectFormMode(config.fields || []);
3270
2784
  }, /* @ts-ignore */
3271
2785
  ...(ngDevMode ? [{ debugName: "formModeDetection" }] : /* istanbul ignore next */ []));
3272
- formConfigValidation = computed(() => {
3273
- const config = this.activeConfig();
3274
- if (!config)
3275
- return { isValid: true, errors: [], warnings: [] };
3276
- return FormModeValidator.validateFormConfiguration(config.fields || []);
3277
- }, /* @ts-ignore */
3278
- ...(ngDevMode ? [{ debugName: "formConfigValidation" }] : /* istanbul ignore next */ []));
3279
2786
  effectiveFormOptions = computed(() => {
3280
2787
  const config = this.activeConfig();
3281
2788
  const configOptions = config?.options || {};
@@ -3283,6 +2790,8 @@ class FormStateManager {
3283
2790
  return { ...configOptions, ...inputOptions };
3284
2791
  }, /* @ts-ignore */
3285
2792
  ...(ngDevMode ? [{ debugName: "effectiveFormOptions" }] : /* istanbul ignore next */ []));
2793
+ pendingPageRequest = signal(null, /* @ts-ignore */
2794
+ ...(ngDevMode ? [{ debugName: "pendingPageRequest" }] : /* istanbul ignore next */ []));
3286
2795
  pageFieldDefinitions = computed(() => {
3287
2796
  const config = this.activeConfig();
3288
2797
  const mode = this.formModeDetection().mode;
@@ -3320,21 +2829,70 @@ class FormStateManager {
3320
2829
  return this.createEmptyFormSetup(registry);
3321
2830
  }, /* @ts-ignore */
3322
2831
  ...(ngDevMode ? [{ debugName: "formSetup" }] : /* istanbul ignore next */ []));
2832
+ formSetupConfig = computed(() => {
2833
+ const state = this.machine.state();
2834
+ if (isReadyState(state))
2835
+ return state.config;
2836
+ if (isTransitioningState(state)) {
2837
+ if (state.phase === Phase.Restoring && state.pendingFormSetup) {
2838
+ return state.pendingConfig;
2839
+ }
2840
+ return state.currentConfig;
2841
+ }
2842
+ if (state.type === LifecycleState.Initializing)
2843
+ return state.config;
2844
+ if (state.type === LifecycleState.Uninitialized)
2845
+ return this.activeConfig();
2846
+ return undefined;
2847
+ }, /* @ts-ignore */
2848
+ ...(ngDevMode ? [{ debugName: "formSetupConfig" }] : /* istanbul ignore next */ []));
2849
+ activePageState = linkedSignal({ ...(ngDevMode ? { debugName: "activePageState" } : /* istanbul ignore next */ {}), source: this.activeConfig,
2850
+ computation: (config) => ({ config, initialized: false, index: 0 }) });
3323
2851
  defaultValues = linkedSignal(() => this.formSetup().defaultValues, /* @ts-ignore */
3324
2852
  ...(ngDevMode ? [{ debugName: "defaultValues" }] : /* istanbul ignore next */ []));
3325
2853
  validKeys = computed(() => {
3326
2854
  const schemaFields = this.formSetup().schemaFields;
3327
2855
  if (!schemaFields || schemaFields.length === 0)
3328
2856
  return undefined;
3329
- return new Set(schemaFields.map((f) => f.key).filter((key) => key !== undefined));
2857
+ const keys = new Set();
2858
+ for (const field of schemaFields) {
2859
+ if (field.key === undefined)
2860
+ continue;
2861
+ keys.add(field.key);
2862
+ }
2863
+ return keys;
3330
2864
  }, /* @ts-ignore */
3331
2865
  ...(ngDevMode ? [{ debugName: "validKeys" }] : /* istanbul ignore next */ []));
3332
- fieldStateSnapshot = computed(() => {
2866
+ boundValueExclusionEnabled = computed(() => {
2867
+ const setup = this.formSetup();
2868
+ if (!setup.schemaFields || setup.schemaFields.length === 0)
2869
+ return false;
2870
+ const options = this.effectiveFormOptions();
2871
+ return hasEnabledValueExclusion(setup.schemaFields, BOUND_VALUE_EXCLUSION_BASELINE, {
2872
+ excludeValueIfHidden: options.excludeValueIfHidden,
2873
+ excludeValueIfDisabled: options.excludeValueIfDisabled,
2874
+ excludeValueIfReadonly: options.excludeValueIfReadonly,
2875
+ });
2876
+ }, /* @ts-ignore */
2877
+ ...(ngDevMode ? [{ debugName: "boundValueExclusionEnabled" }] : /* istanbul ignore next */ []));
2878
+ valueExclusionEnabled = computed(() => {
3333
2879
  const setup = this.formSetup();
3334
2880
  if (!setup.schemaFields || setup.schemaFields.length === 0)
2881
+ return false;
2882
+ const options = this.effectiveFormOptions();
2883
+ return hasEnabledValueExclusion(setup.schemaFields, this.valueExclusionDefaults, {
2884
+ excludeValueIfHidden: options.excludeValueIfHidden,
2885
+ excludeValueIfDisabled: options.excludeValueIfDisabled,
2886
+ excludeValueIfReadonly: options.excludeValueIfReadonly,
2887
+ });
2888
+ }, /* @ts-ignore */
2889
+ ...(ngDevMode ? [{ debugName: "valueExclusionEnabled" }] : /* istanbul ignore next */ []));
2890
+ fieldStateSnapshot = computed(() => {
2891
+ const schemaFields = this.formSetup().schemaFields;
2892
+ if (!this.formSchemaReady() || !this.boundValueExclusionEnabled() || !schemaFields || schemaFields.length === 0)
3335
2893
  return {};
3336
2894
  const snapshot = {};
3337
- collectFieldStateSnapshot(setup.schemaFields, asFieldTreeRecord(this.form()), [], snapshot);
2895
+ collectFieldStateSnapshot(schemaFields, asFieldTreeRecord(this.form()), [], snapshot);
3338
2896
  return snapshot;
3339
2897
  }, /* @ts-ignore */
3340
2898
  ...(ngDevMode ? [{ debugName: "fieldStateSnapshot" }] : /* istanbul ignore next */ []));
@@ -3342,8 +2900,22 @@ class FormStateManager {
3342
2900
  ...(ngDevMode ? [{ debugName: "excludedValueStore" }] : /* istanbul ignore next */ []));
3343
2901
  prevFieldStateSnapshot = signal({}, /* @ts-ignore */
3344
2902
  ...(ngDevMode ? [{ debugName: "prevFieldStateSnapshot" }] : /* istanbul ignore next */ []));
2903
+ lastOutwardValue = { current: undefined, pending: false };
2904
+ lastExternalValue = { current: undefined };
2905
+ externalValue = computed(() => {
2906
+ const incoming = this.deps.value();
2907
+ if (this.lastOutwardValue.pending && incoming === this.lastOutwardValue.current) {
2908
+ this.lastOutwardValue.pending = false;
2909
+ return this.lastExternalValue.current;
2910
+ }
2911
+ this.lastOutwardValue.pending = false;
2912
+ this.lastExternalValue.current = incoming;
2913
+ return incoming;
2914
+ }, /* @ts-ignore */
2915
+ ...(ngDevMode ? [{ debugName: "externalValue" }] : /* istanbul ignore next */ []));
3345
2916
  entity = linkedSignal(() => {
3346
- const inputValue = this.deps.value();
2917
+ this.externalValue();
2918
+ const inputValue = untracked(() => this.deps.value());
3347
2919
  const defaults = this.defaultValues();
3348
2920
  const keys = this.validKeys();
3349
2921
  const saved = this.excludedValueStore();
@@ -3363,22 +2935,62 @@ class FormStateManager {
3363
2935
  debugName: 'FormStateManager.entity',
3364
2936
  equal: isEqual,
3365
2937
  });
3366
- formSchema = computed(() => runInInjectionContext(this.injector, () => {
2938
+ loadFormSchemaService = injectAsync(() => import('./ng-forge-dynamic-forms-form-schema.service-BzbBv-r0.mjs'));
2939
+ formSchemaRequest = computed(() => {
3367
2940
  const setup = this.formSetup();
3368
2941
  const config = this.activeConfig();
3369
- if (!config)
2942
+ if (!config || (!(setup.schemaFields?.length > 0) && !config.schema))
2943
+ return undefined;
2944
+ return {
2945
+ fields: setup.schemaFields ?? [],
2946
+ registry: setup.registry,
2947
+ formLevelSchema: config.schema,
2948
+ validateWhenHidden: this.effectiveFormOptions().validateWhenHidden,
2949
+ };
2950
+ }, /* @ts-ignore */
2951
+ ...(ngDevMode ? [{ debugName: "formSchemaRequest" }] : /* istanbul ignore next */ []));
2952
+ formSchemaResource = resource({ ...(ngDevMode ? { debugName: "formSchemaResource" } : /* istanbul ignore next */ {}), params: () => this.formSchemaRequest(),
2953
+ loader: async ({ params }) => {
2954
+ const schemaService = await this.loadFormSchemaService();
2955
+ return runInInjectionContext(this.injector, () => schemaService.create(params));
2956
+ } });
2957
+ schemaCache = { current: undefined };
2958
+ trackSchemaCache = computed(() => {
2959
+ const request = this.formSchemaRequest();
2960
+ if (!request) {
2961
+ this.schemaCache.current = undefined;
3370
2962
  return undefined;
3371
- if (setup.schemaFields?.length) {
3372
- return createSchemaFromFields(setup.schemaFields, setup.registry, {
3373
- formLevelSchema: config.schema,
3374
- validateWhenHidden: this.effectiveFormOptions().validateWhenHidden,
3375
- });
3376
2963
  }
3377
- if (config.schema) {
3378
- return createFormLevelSchema(config.schema);
2964
+ if (this.formSchemaResource.status() === 'error') {
2965
+ this.schemaCache.current = undefined;
2966
+ return undefined;
3379
2967
  }
3380
- return undefined;
3381
- }), /* @ts-ignore */
2968
+ if (this.formSchemaResource.hasValue()) {
2969
+ this.schemaCache.current = { schema: this.formSchemaResource.value(), request };
2970
+ }
2971
+ return this.schemaCache.current;
2972
+ }, /* @ts-ignore */
2973
+ ...(ngDevMode ? [{ debugName: "trackSchemaCache" }] : /* istanbul ignore next */ []));
2974
+ isSchemaCurrent = computed(() => {
2975
+ if (this.formSetupConfig() !== this.activeConfig())
2976
+ return false;
2977
+ const request = this.formSchemaRequest();
2978
+ if (!request)
2979
+ return true;
2980
+ return this.trackSchemaCache()?.request === request;
2981
+ }, /* @ts-ignore */
2982
+ ...(ngDevMode ? [{ debugName: "isSchemaCurrent" }] : /* istanbul ignore next */ []));
2983
+ formSchemaReady = computed(() => {
2984
+ if (this.formSetupConfig() !== this.activeConfig())
2985
+ return false;
2986
+ if (!this.formSchemaRequest())
2987
+ return true;
2988
+ if (this.formSchemaResource.status() === 'error')
2989
+ return true;
2990
+ return this.trackSchemaCache() !== undefined;
2991
+ }, /* @ts-ignore */
2992
+ ...(ngDevMode ? [{ debugName: "formSchemaReady" }] : /* istanbul ignore next */ []));
2993
+ formSchema = computed(() => this.trackSchemaCache()?.schema, /* @ts-ignore */
3382
2994
  ...(ngDevMode ? [{ debugName: "formSchema" }] : /* istanbul ignore next */ []));
3383
2995
  form = computed(() => {
3384
2996
  const schema = this.formSchema();
@@ -3409,13 +3021,16 @@ class FormStateManager {
3409
3021
  ...(ngDevMode ? [{ debugName: "fieldSignalContext" }] : /* istanbul ignore next */ []));
3410
3022
  formInstance = computed(() => this.form()(), /* @ts-ignore */
3411
3023
  ...(ngDevMode ? [{ debugName: "formInstance" }] : /* istanbul ignore next */ []));
3412
- formValue = computed(() => this.formInstance().value(), /* @ts-ignore */
3024
+ modelValue = computed(() => this.formInstance().value(), /* @ts-ignore */
3025
+ ...(ngDevMode ? [{ debugName: "modelValue" }] : /* istanbul ignore next */ []));
3026
+ formValue = computed(() => this.modelValue(), /* @ts-ignore */
3413
3027
  ...(ngDevMode ? [{ debugName: "formValue" }] : /* istanbul ignore next */ []));
3414
3028
  filteredFormValue = computed(() => {
3415
- const rawValue = this.formValue();
3029
+ const rawValue = this.modelValue();
3416
3030
  const setup = this.formSetup();
3031
+ const schemaFields = setup.schemaFields;
3417
3032
  const options = this.effectiveFormOptions();
3418
- if (!setup.schemaFields || setup.schemaFields.length === 0) {
3033
+ if (!this.formSchemaReady() || !schemaFields || schemaFields.length === 0) {
3419
3034
  return rawValue;
3420
3035
  }
3421
3036
  const formTree = this.form();
@@ -3425,14 +3040,15 @@ class FormStateManager {
3425
3040
  excludeValueIfReadonly: options.excludeValueIfReadonly,
3426
3041
  };
3427
3042
  const fieldTreeRecord = asFieldTreeRecord(formTree);
3428
- return filterFormValue(rawValue, setup.schemaFields, fieldTreeRecord, setup.registry, this.valueExclusionDefaults, formOptions);
3043
+ return filterFormValue(rawValue, schemaFields, fieldTreeRecord, setup.registry, this.valueExclusionDefaults, formOptions, this.valueExclusionEnabled());
3429
3044
  }, /* @ts-ignore */
3430
3045
  ...(ngDevMode ? [{ debugName: "filteredFormValue" }] : /* istanbul ignore next */ []));
3431
3046
  boundFormValue = computed(() => {
3432
- const rawValue = this.formValue();
3047
+ const rawValue = this.modelValue();
3433
3048
  const setup = this.formSetup();
3049
+ const schemaFields = setup.schemaFields;
3434
3050
  const options = this.effectiveFormOptions();
3435
- if (!setup.schemaFields || setup.schemaFields.length === 0) {
3051
+ if (!this.formSchemaReady() || !schemaFields || schemaFields.length === 0) {
3436
3052
  return rawValue;
3437
3053
  }
3438
3054
  const formTree = this.form();
@@ -3442,26 +3058,26 @@ class FormStateManager {
3442
3058
  excludeValueIfReadonly: options.excludeValueIfReadonly,
3443
3059
  };
3444
3060
  const fieldTreeRecord = asFieldTreeRecord(formTree);
3445
- return filterFormValue(rawValue, setup.schemaFields, fieldTreeRecord, setup.registry, BOUND_VALUE_EXCLUSION_BASELINE, formOptions);
3061
+ return filterFormValue(rawValue, schemaFields, fieldTreeRecord, setup.registry, BOUND_VALUE_EXCLUSION_BASELINE, formOptions, this.boundValueExclusionEnabled());
3446
3062
  }, /* @ts-ignore */
3447
3063
  ...(ngDevMode ? [{ debugName: "boundFormValue" }] : /* istanbul ignore next */ []));
3448
- valid = computed(() => this.formInstance().valid(), /* @ts-ignore */
3064
+ valid = computed(() => this.formSchemaReady() && this.formSchemaResource.status() !== 'error' && this.formInstance().valid(), /* @ts-ignore */
3449
3065
  ...(ngDevMode ? [{ debugName: "valid" }] : /* istanbul ignore next */ []));
3450
- invalid = computed(() => this.formInstance().invalid(), /* @ts-ignore */
3066
+ invalid = computed(() => !this.formSchemaReady() || this.formSchemaResource.status() === 'error' || this.formInstance().invalid(), /* @ts-ignore */
3451
3067
  ...(ngDevMode ? [{ debugName: "invalid" }] : /* istanbul ignore next */ []));
3452
- dirty = computed(() => this.formInstance().dirty(), /* @ts-ignore */
3068
+ dirty = computed(() => this.formSchemaReady() && this.formInstance().dirty(), /* @ts-ignore */
3453
3069
  ...(ngDevMode ? [{ debugName: "dirty" }] : /* istanbul ignore next */ []));
3454
- touched = computed(() => this.formInstance().touched(), /* @ts-ignore */
3070
+ touched = computed(() => this.formSchemaReady() && this.formInstance().touched(), /* @ts-ignore */
3455
3071
  ...(ngDevMode ? [{ debugName: "touched" }] : /* istanbul ignore next */ []));
3456
- errors = computed(() => this.formInstance().errors(), /* @ts-ignore */
3072
+ errors = computed(() => (this.formSchemaReady() ? this.formInstance().errors() : []), /* @ts-ignore */
3457
3073
  ...(ngDevMode ? [{ debugName: "errors" }] : /* istanbul ignore next */ []));
3458
3074
  disabled = computed(() => {
3459
3075
  const optionsDisabled = this.effectiveFormOptions().disabled;
3460
- const formDisabled = this.formInstance().disabled();
3076
+ const formDisabled = this.formSchemaReady() && this.formInstance().disabled();
3461
3077
  return optionsDisabled ?? formDisabled;
3462
3078
  }, /* @ts-ignore */
3463
3079
  ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
3464
- submitting = computed(() => this.formInstance().submitting(), /* @ts-ignore */
3080
+ submitting = computed(() => this.formSchemaReady() && this.formInstance().submitting(), /* @ts-ignore */
3465
3081
  ...(ngDevMode ? [{ debugName: "submitting" }] : /* istanbul ignore next */ []));
3466
3082
  fieldsSource = computed(() => {
3467
3083
  const state = this.machine.state();
@@ -3576,6 +3192,10 @@ class FormStateManager {
3576
3192
  }), takeUntilDestroyed(this.destroyRef));
3577
3193
  this.reset$ = this.eventBus.on('form-reset').pipe(map(() => undefined), takeUntilDestroyed(this.destroyRef));
3578
3194
  this.cleared$ = this.eventBus.on('form-clear').pipe(map(() => undefined), takeUntilDestroyed(this.destroyRef));
3195
+ this.eventBus
3196
+ .on('go-to-page')
3197
+ .pipe(takeUntilDestroyed(this.destroyRef))
3198
+ .subscribe((event) => this.pendingPageRequest.set({ index: event.pageIndex, options: event.options }));
3579
3199
  this.machine = createFormStateMachine({
3580
3200
  injector: this.injector,
3581
3201
  destroyRef: this.destroyRef,
@@ -3639,6 +3259,11 @@ class FormStateManager {
3639
3259
  this.eventBus.dispatch(FormSubmitEvent);
3640
3260
  }
3641
3261
  setupEffects() {
3262
+ explicitEffect([this.formSchemaResource.error], ([error]) => {
3263
+ if (error) {
3264
+ this.logger.error('[Dynamic Forms] Failed to compile the form schema; rendering without validation and blocking submission.', error);
3265
+ }
3266
+ });
3642
3267
  explicitEffect([this.deps.config], ([config]) => {
3643
3268
  this.fieldLoadingErrors.set([]);
3644
3269
  const state = this.machine.currentState;
@@ -3690,17 +3315,24 @@ class FormStateManager {
3690
3315
  explicitEffect([this.boundFormValue], ([currentBound]) => {
3691
3316
  const currentValue = this.deps.value();
3692
3317
  if (!isEqual(currentBound, currentValue)) {
3318
+ this.lastOutwardValue.current = currentBound;
3319
+ this.lastOutwardValue.pending = true;
3693
3320
  this.deps.value.set(currentBound);
3694
3321
  }
3695
3322
  });
3696
- explicitEffect([this.formConfigValidation], ([validation]) => {
3697
- if (!validation.isValid) {
3698
- this.logger.error('Invalid form configuration:', validation.errors);
3699
- }
3700
- if (validation.warnings.length > 0) {
3701
- this.logger.warn('Form configuration warnings:', validation.warnings);
3702
- }
3703
- });
3323
+ if (DEV_MODE) {
3324
+ explicitEffect([this.activeConfig], ([config]) => {
3325
+ if (!config)
3326
+ return;
3327
+ const validation = FormModeValidator.validateFormConfiguration(config.fields || []);
3328
+ if (!validation.isValid) {
3329
+ this.logger.error('Invalid form configuration:', validation.errors);
3330
+ }
3331
+ if (validation.warnings.length > 0) {
3332
+ this.logger.warn('Form configuration warnings:', validation.warnings);
3333
+ }
3334
+ });
3335
+ }
3704
3336
  }
3705
3337
  setupEventHandlers() {
3706
3338
  this.eventBus
@@ -3721,71 +3353,480 @@ class FormStateManager {
3721
3353
  if (!customFnConfig) {
3722
3354
  return;
3723
3355
  }
3724
- if (customFnConfig.customFunctions) {
3725
- Object.entries(customFnConfig.customFunctions).forEach(([name, fn]) => {
3726
- this.functionRegistry.registerCustomFunction(name, fn);
3727
- });
3356
+ if (customFnConfig.customFunctions) {
3357
+ Object.entries(customFnConfig.customFunctions).forEach(([name, fn]) => {
3358
+ this.functionRegistry.registerCustomFunction(name, fn);
3359
+ });
3360
+ }
3361
+ if (customFnConfig.derivations) {
3362
+ this.functionRegistry.setDerivationFunctions(customFnConfig.derivations);
3363
+ }
3364
+ if (customFnConfig.asyncDerivations) {
3365
+ this.functionRegistry.setAsyncDerivationFunctions(customFnConfig.asyncDerivations);
3366
+ }
3367
+ if (customFnConfig.asyncConditions) {
3368
+ this.functionRegistry.setAsyncConditionFunctions(customFnConfig.asyncConditions);
3369
+ }
3370
+ this.functionRegistry.setValidators(customFnConfig.validators);
3371
+ this.functionRegistry.setAsyncValidators(customFnConfig.asyncValidators);
3372
+ this.functionRegistry.setHttpValidators(customFnConfig.httpValidators);
3373
+ }
3374
+ createFormSetupFromConfig(fields, mode, registry) {
3375
+ const normalizedFields = normalizeSimplifiedArrays(fields);
3376
+ validateFormConfig(normalizedFields, registry, this.logger);
3377
+ const source = this.deps.source?.() ?? 'inline';
3378
+ const { fields: sanitizedFields, warnings: addonWarnings } = walkAndValidateAddons(normalizedFields, registry, this.addonKindRegistry, source);
3379
+ if (DEV_MODE) {
3380
+ const nextKeys = new Set();
3381
+ for (const w of addonWarnings) {
3382
+ const key = addonWarningKey(w);
3383
+ nextKeys.add(key);
3384
+ if (!this.lastAddonWarningKeys.has(key)) {
3385
+ this.logger.warn(formatAddonWarning(w));
3386
+ }
3387
+ }
3388
+ this.lastAddonWarningKeys = nextKeys;
3389
+ }
3390
+ const validatedFields = sanitizedFields;
3391
+ const flattenedFields = this.fieldProcessors.memoizedFlattenFields(validatedFields, registry);
3392
+ const flattenedFieldsForRendering = this.fieldProcessors.memoizedFlattenFieldsForRendering(validatedFields, registry);
3393
+ const fieldsById = this.fieldProcessors.memoizedKeyBy(flattenedFields);
3394
+ const defaultValues = this.fieldProcessors.memoizedDefaultValues(fieldsById, registry);
3395
+ const fieldsToRender = mode === 'paged' ? [] : flattenedFieldsForRendering;
3396
+ return {
3397
+ fields: fieldsToRender,
3398
+ schemaFields: flattenedFields,
3399
+ originalFields: fields,
3400
+ defaultValues,
3401
+ mode,
3402
+ registry,
3403
+ };
3404
+ }
3405
+ createEmptyFormSetup(registry) {
3406
+ return {
3407
+ fields: [],
3408
+ schemaFields: [],
3409
+ defaultValues: {},
3410
+ mode: 'non-paged',
3411
+ registry,
3412
+ };
3413
+ }
3414
+ ngOnDestroy() {
3415
+ this.machine.dispatch({ type: Action.Destroy });
3416
+ }
3417
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: FormStateManager, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3418
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: FormStateManager });
3419
+ }
3420
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: FormStateManager, decorators: [{
3421
+ type: Injectable
3422
+ }], ctorParameters: () => [] });
3423
+
3424
+ class PageOrchestratorComponent {
3425
+ eventBus = inject(EventBus);
3426
+ fieldContextRegistry = inject(FieldContextRegistryService);
3427
+ functionRegistry = inject(FunctionRegistryService);
3428
+ formOptions = inject(FORM_OPTIONS, { optional: true });
3429
+ globalPreloadWindow = inject(PAGE_PRELOAD_WINDOW);
3430
+ stateManager = inject(FormStateManager, { optional: true });
3431
+ pendingPageRequest = computed(() => this.stateManager?.pendingPageRequest() ?? null, /* @ts-ignore */
3432
+ ...(ngDevMode ? [{ debugName: "pendingPageRequest" }] : /* istanbul ignore next */ []));
3433
+ schemaCurrent = computed(() => this.stateManager?.isSchemaCurrent() ?? true, /* @ts-ignore */
3434
+ ...(ngDevMode ? [{ debugName: "schemaCurrent" }] : /* istanbul ignore next */ []));
3435
+ configScope = computed(() => this.stateManager?.activeConfig(), /* @ts-ignore */
3436
+ ...(ngDevMode ? [{ debugName: "configScope" }] : /* istanbul ignore next */ []));
3437
+ pageFields = input.required(/* @ts-ignore */
3438
+ ...(ngDevMode ? [{ debugName: "pageFields" }] : /* istanbul ignore next */ []));
3439
+ form = input.required(/* @ts-ignore */
3440
+ ...(ngDevMode ? [{ debugName: "form" }] : /* istanbul ignore next */ []));
3441
+ fieldSignalContext = input.required(/* @ts-ignore */
3442
+ ...(ngDevMode ? [{ debugName: "fieldSignalContext" }] : /* istanbul ignore next */ []));
3443
+ pageHiddenStates = computed(() => {
3444
+ const pages = this.pageFields();
3445
+ return pages.map((page) => this.evaluatePageHidden(page));
3446
+ }, /* @ts-ignore */
3447
+ ...(ngDevMode ? [{ debugName: "pageHiddenStates" }] : /* istanbul ignore next */ []));
3448
+ visiblePageIndices = computed(() => {
3449
+ const hiddenStates = this.pageHiddenStates();
3450
+ return hiddenStates
3451
+ .map((hidden, index) => ({ index, hidden }))
3452
+ .filter((item) => !item.hidden)
3453
+ .map((item) => item.index);
3454
+ }, /* @ts-ignore */
3455
+ ...(ngDevMode ? [{ debugName: "visiblePageIndices" }] : /* istanbul ignore next */ []));
3456
+ activePageOwnership = linkedSignal({ ...(ngDevMode ? { debugName: "activePageOwnership" } : /* istanbul ignore next */ {}), source: this.configScope,
3457
+ computation: (config, previous) => {
3458
+ const totalPages = this.pageFields().length;
3459
+ if (totalPages === 0)
3460
+ return { config, initialized: true, index: 0 };
3461
+ return untracked(() => {
3462
+ const owned = this.stateManager?.activePageState();
3463
+ const mayRestore = previous === undefined || previous.source === config;
3464
+ if (mayRestore &&
3465
+ owned !== undefined &&
3466
+ owned.config === config &&
3467
+ owned.initialized &&
3468
+ owned.index >= 0 &&
3469
+ owned.index < totalPages) {
3470
+ return { config, initialized: true, index: owned.index };
3471
+ }
3472
+ if (this.initialPage().validate && !this.schemaCurrent()) {
3473
+ return { config, initialized: false, index: 0 };
3474
+ }
3475
+ return { config, initialized: true, index: this.resolveInitialLanding() };
3476
+ });
3477
+ } });
3478
+ currentPageIndex = computed(() => this.activePageOwnership().index, /* @ts-ignore */
3479
+ ...(ngDevMode ? [{ debugName: "currentPageIndex" }] : /* istanbul ignore next */ []));
3480
+ setCurrentPageIndex(index) {
3481
+ this.activePageOwnership.update((ownership) => ({ ...ownership, initialized: true, index }));
3482
+ }
3483
+ resolveInitialLanding() {
3484
+ const { index, validate } = this.initialPage();
3485
+ const visible = this.visiblePageIndices();
3486
+ if (index === 0 || visible.length === 0)
3487
+ return 0;
3488
+ const target = visible.includes(index) ? index : this.findNearestVisiblePage(index, visible);
3489
+ if (target <= 0)
3490
+ return Math.max(target, 0);
3491
+ if (!validate)
3492
+ return target;
3493
+ const firstInvalid = visible.filter((i) => i < target).find((i) => !this.isPageValid(i));
3494
+ return firstInvalid ?? target;
3495
+ }
3496
+ initialPage = computed(() => {
3497
+ const raw = this.formOptions?.()?.initialPage;
3498
+ const totalPages = this.pageFields().length;
3499
+ const requested = typeof raw === 'number' ? raw : raw?.index;
3500
+ const validate = typeof raw === 'number' ? false : (raw?.validate ?? false);
3501
+ if (typeof requested !== 'number' || !Number.isFinite(requested) || requested < 0 || totalPages === 0) {
3502
+ return { index: 0, validate: false };
3503
+ }
3504
+ return { index: Math.min(Math.trunc(requested), totalPages - 1), validate };
3505
+ }, /* @ts-ignore */
3506
+ ...(ngDevMode ? [{ debugName: "initialPage" }] : /* istanbul ignore next */ []));
3507
+ state = computed(() => {
3508
+ const currentIndex = this.currentPageIndex();
3509
+ const totalPages = this.pageFields().length;
3510
+ const visibleIndices = this.visiblePageIndices();
3511
+ const currentVisiblePosition = visibleIndices.indexOf(currentIndex);
3512
+ const isFirstVisiblePage = currentVisiblePosition === 0 || currentVisiblePosition === -1;
3513
+ const isLastVisiblePage = currentVisiblePosition >= visibleIndices.length - 1;
3514
+ return {
3515
+ currentPageIndex: currentIndex,
3516
+ totalPages,
3517
+ isFirstPage: isFirstVisiblePage,
3518
+ isLastPage: isLastVisiblePage,
3519
+ };
3520
+ }, /* @ts-ignore */
3521
+ ...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
3522
+ currentPageValid = computed(() => (this.stateManager?.isSchemaCurrent() ?? true) && this.isPageValid(this.currentPageIndex()), /* @ts-ignore */
3523
+ ...(ngDevMode ? [{ debugName: "currentPageValid" }] : /* istanbul ignore next */ []));
3524
+ isPageValid(pageIndex) {
3525
+ const pages = this.pageFields();
3526
+ const form = this.form();
3527
+ if (pageIndex < 0 || pageIndex >= pages.length) {
3528
+ return true;
3529
+ }
3530
+ const leafKeys = collectLeafFieldKeys(pages[pageIndex].fields || []);
3531
+ for (const fieldKey of leafKeys) {
3532
+ const field = form[fieldKey];
3533
+ if (field && typeof field === 'function') {
3534
+ const fieldState = field();
3535
+ if (fieldState && typeof fieldState.valid === 'function' && !fieldState.valid()) {
3536
+ return false;
3537
+ }
3538
+ }
3539
+ }
3540
+ return true;
3541
+ }
3542
+ extendedFieldSignalContext = computed(() => ({
3543
+ ...this.fieldSignalContext(),
3544
+ currentPageValid: this.currentPageValid,
3545
+ }), /* @ts-ignore */
3546
+ ...(ngDevMode ? [{ debugName: "extendedFieldSignalContext" }] : /* istanbul ignore next */ []));
3547
+ preloadWindow = computed(() => {
3548
+ const perForm = this.formOptions?.()?.pagePreloadWindow;
3549
+ return clampWindowSize(perForm, this.globalPreloadWindow);
3550
+ }, /* @ts-ignore */
3551
+ ...(ngDevMode ? [{ debugName: "preloadWindow" }] : /* istanbul ignore next */ []));
3552
+ pageRenderStates = computed(() => {
3553
+ const activeIndex = this.currentPageIndex();
3554
+ const hiddenStates = this.pageHiddenStates();
3555
+ const preloadWindow = this.preloadWindow();
3556
+ return this.pageFields().map((field, index) => ({
3557
+ field,
3558
+ index,
3559
+ hidden: hiddenStates[index],
3560
+ active: index === activeIndex,
3561
+ preload: index !== activeIndex && Math.abs(index - activeIndex) <= preloadWindow,
3562
+ }));
3563
+ }, /* @ts-ignore */
3564
+ ...(ngDevMode ? [{ debugName: "pageRenderStates" }] : /* istanbul ignore next */ []));
3565
+ constructor() {
3566
+ this.setupEventListeners();
3567
+ const preloader = injectFormComponentPreloader();
3568
+ explicitEffect([this.pageRenderStates], ([states]) => {
3569
+ preloader.preloadFields(states.filter((page) => !page.hidden && (page.active || page.preload)).map((page) => page.field));
3570
+ });
3571
+ explicitEffect([this.schemaCurrent, this.pageFields], ([schemaCurrent]) => {
3572
+ const ownership = this.activePageOwnership();
3573
+ if (!schemaCurrent || ownership.initialized)
3574
+ return;
3575
+ this.activePageOwnership.set({ ...ownership, initialized: true, index: this.resolveInitialLanding() });
3576
+ });
3577
+ explicitEffect([this.state, this.visiblePageIndices], ([state, visibleIndices]) => {
3578
+ const currentVisiblePosition = visibleIndices.indexOf(state.currentPageIndex);
3579
+ if (currentVisiblePosition === -1 && visibleIndices.length > 0) {
3580
+ const nearest = this.findNearestVisiblePage(state.currentPageIndex, visibleIndices);
3581
+ if (nearest !== -1) {
3582
+ this.executePageChange(nearest);
3583
+ }
3584
+ }
3585
+ });
3586
+ }
3587
+ navigateToNextPage() {
3588
+ const disableWhenPageInvalid = this.formOptions?.()?.nextButton?.disableWhenPageInvalid ?? true;
3589
+ if (disableWhenPageInvalid && !this.currentPageValid()) {
3590
+ return {
3591
+ success: false,
3592
+ newPageIndex: this.state().currentPageIndex,
3593
+ error: 'Current page has invalid fields',
3594
+ };
3595
+ }
3596
+ const currentState = this.state();
3597
+ const visibleIndices = this.visiblePageIndices();
3598
+ if (currentState.isLastPage) {
3599
+ return {
3600
+ success: false,
3601
+ newPageIndex: currentState.currentPageIndex,
3602
+ error: 'Already on the last visible page',
3603
+ };
3604
+ }
3605
+ const currentVisiblePosition = visibleIndices.indexOf(currentState.currentPageIndex);
3606
+ if (currentVisiblePosition === -1 || currentVisiblePosition >= visibleIndices.length - 1) {
3607
+ return {
3608
+ success: false,
3609
+ newPageIndex: currentState.currentPageIndex,
3610
+ error: 'No next visible page available',
3611
+ };
3612
+ }
3613
+ const nextVisiblePageIndex = visibleIndices[currentVisiblePosition + 1];
3614
+ return this.executePageChange(nextVisiblePageIndex);
3615
+ }
3616
+ navigateToPreviousPage() {
3617
+ const currentState = this.state();
3618
+ const visibleIndices = this.visiblePageIndices();
3619
+ if (currentState.isFirstPage) {
3620
+ return {
3621
+ success: false,
3622
+ newPageIndex: currentState.currentPageIndex,
3623
+ error: 'Already on the first visible page',
3624
+ };
3625
+ }
3626
+ const currentVisiblePosition = visibleIndices.indexOf(currentState.currentPageIndex);
3627
+ if (currentVisiblePosition <= 0) {
3628
+ return {
3629
+ success: false,
3630
+ newPageIndex: currentState.currentPageIndex,
3631
+ error: 'No previous visible page available',
3632
+ };
3728
3633
  }
3729
- if (customFnConfig.derivations) {
3730
- this.functionRegistry.setDerivationFunctions(customFnConfig.derivations);
3634
+ const prevVisiblePageIndex = visibleIndices[currentVisiblePosition - 1];
3635
+ return this.executePageChange(prevVisiblePageIndex);
3636
+ }
3637
+ navigateToPage(pageIndex, options) {
3638
+ const currentIndex = this.state().currentPageIndex;
3639
+ const visibleIndices = this.visiblePageIndices();
3640
+ if (pageIndex <= currentIndex || !visibleIndices.includes(pageIndex)) {
3641
+ return this.executePageChange(pageIndex);
3731
3642
  }
3732
- if (customFnConfig.asyncDerivations) {
3733
- this.functionRegistry.setAsyncDerivationFunctions(customFnConfig.asyncDerivations);
3643
+ if (options?.validate === false) {
3644
+ return this.executePageChange(pageIndex);
3734
3645
  }
3735
- if (customFnConfig.asyncConditions) {
3736
- this.functionRegistry.setAsyncConditionFunctions(customFnConfig.asyncConditions);
3646
+ const disableWhenPageInvalid = this.formOptions?.()?.nextButton?.disableWhenPageInvalid ?? true;
3647
+ if (!disableWhenPageInvalid) {
3648
+ return this.executePageChange(pageIndex);
3737
3649
  }
3738
- this.functionRegistry.setValidators(customFnConfig.validators);
3739
- this.functionRegistry.setAsyncValidators(customFnConfig.asyncValidators);
3740
- this.functionRegistry.setHttpValidators(customFnConfig.httpValidators);
3741
- }
3742
- createFormSetupFromConfig(fields, mode, registry) {
3743
- const normalizedFields = normalizeSimplifiedArrays(fields);
3744
- validateFormConfig(normalizedFields, registry, this.logger);
3745
- const source = this.deps.source?.() ?? 'inline';
3746
- const { fields: sanitizedFields, warnings: addonWarnings } = walkAndValidateAddons(normalizedFields, registry, this.addonKindRegistry, source);
3747
- const nextKeys = new Set();
3748
- for (const w of addonWarnings) {
3749
- const key = addonWarningKey(w);
3750
- nextKeys.add(key);
3751
- if (!this.lastAddonWarningKeys.has(key)) {
3752
- this.logger.warn(formatAddonWarning(w));
3753
- }
3650
+ const crossedPages = visibleIndices.filter((index) => index >= currentIndex && index < pageIndex);
3651
+ const firstInvalidPage = crossedPages.find((index) => !this.isPageValid(index));
3652
+ if (firstInvalidPage === undefined) {
3653
+ return this.executePageChange(pageIndex);
3754
3654
  }
3755
- this.lastAddonWarningKeys = nextKeys;
3756
- const validatedFields = sanitizedFields;
3757
- const flattenedFields = this.fieldProcessors.memoizedFlattenFields(validatedFields, registry);
3758
- const flattenedFieldsForRendering = this.fieldProcessors.memoizedFlattenFieldsForRendering(validatedFields, registry);
3759
- const fieldsById = this.fieldProcessors.memoizedKeyBy(flattenedFields);
3760
- const defaultValues = this.fieldProcessors.memoizedDefaultValues(fieldsById, registry);
3761
- const fieldsToRender = mode === 'paged' ? [] : flattenedFieldsForRendering;
3655
+ this.executePageChange(firstInvalidPage);
3762
3656
  return {
3763
- fields: fieldsToRender,
3764
- schemaFields: flattenedFields,
3765
- originalFields: fields,
3766
- defaultValues,
3767
- mode,
3768
- registry,
3657
+ success: false,
3658
+ newPageIndex: firstInvalidPage,
3659
+ error: `Cannot navigate to page ${pageIndex}: page ${firstInvalidPage} has invalid fields`,
3769
3660
  };
3770
3661
  }
3771
- createEmptyFormSetup(registry) {
3662
+ executePageChange(pageIndex) {
3663
+ const currentState = this.state();
3664
+ const totalPages = currentState.totalPages;
3665
+ if (pageIndex < 0 || pageIndex >= totalPages) {
3666
+ return {
3667
+ success: false,
3668
+ newPageIndex: currentState.currentPageIndex,
3669
+ error: `Invalid page index: ${pageIndex}. Valid range is 0 to ${totalPages - 1}`,
3670
+ };
3671
+ }
3672
+ const visibleIndices = this.visiblePageIndices();
3673
+ if (!visibleIndices.includes(pageIndex)) {
3674
+ return {
3675
+ success: false,
3676
+ newPageIndex: currentState.currentPageIndex,
3677
+ error: `Cannot navigate to hidden page at index ${pageIndex}. Visible pages: [${visibleIndices.join(', ')}]`,
3678
+ };
3679
+ }
3680
+ if (pageIndex === currentState.currentPageIndex) {
3681
+ return {
3682
+ success: true,
3683
+ newPageIndex: pageIndex,
3684
+ };
3685
+ }
3686
+ const previousIndex = currentState.currentPageIndex;
3687
+ this.setCurrentPageIndex(pageIndex);
3688
+ this.eventBus.dispatch(PageChangeEvent, pageIndex, totalPages, previousIndex);
3772
3689
  return {
3773
- fields: [],
3774
- schemaFields: [],
3775
- defaultValues: {},
3776
- mode: 'non-paged',
3777
- registry,
3690
+ success: true,
3691
+ newPageIndex: pageIndex,
3778
3692
  };
3779
3693
  }
3780
- ngOnDestroy() {
3781
- this.machine.dispatch({ type: Action.Destroy });
3694
+ findNearestVisiblePage(currentIndex, visibleIndices) {
3695
+ let nearest = -1;
3696
+ let minDistance = Infinity;
3697
+ for (const idx of visibleIndices) {
3698
+ const distance = Math.abs(idx - currentIndex);
3699
+ if (distance < minDistance || (distance === minDistance && idx > nearest)) {
3700
+ minDistance = distance;
3701
+ nearest = idx;
3702
+ }
3703
+ }
3704
+ return nearest;
3782
3705
  }
3783
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: FormStateManager, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3784
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: FormStateManager });
3706
+ setupEventListeners() {
3707
+ this.eventBus
3708
+ .on('next-page')
3709
+ .pipe(takeUntilDestroyed())
3710
+ .subscribe(() => {
3711
+ this.navigateToNextPage();
3712
+ });
3713
+ this.eventBus
3714
+ .on('previous-page')
3715
+ .pipe(takeUntilDestroyed())
3716
+ .subscribe(() => {
3717
+ this.navigateToPreviousPage();
3718
+ });
3719
+ explicitEffect([this.pendingPageRequest], ([request]) => {
3720
+ if (!request)
3721
+ return;
3722
+ this.stateManager?.pendingPageRequest.set(null);
3723
+ this.navigateToPage(request.index, request.options);
3724
+ });
3725
+ explicitEffect([this.state], ([state]) => this.eventBus.dispatch(PagerStateEvent, state));
3726
+ explicitEffect([this.activePageOwnership], ([ownership]) => this.stateManager?.activePageState.set(ownership));
3727
+ }
3728
+ evaluatePageHidden(page) {
3729
+ if (!page.logic || page.logic.length === 0) {
3730
+ return false;
3731
+ }
3732
+ const hiddenLogic = page.logic.filter((l) => l.type === 'hidden');
3733
+ if (hiddenLogic.length === 0) {
3734
+ return false;
3735
+ }
3736
+ for (const logic of hiddenLogic) {
3737
+ if (typeof logic.condition === 'boolean') {
3738
+ if (logic.condition) {
3739
+ return true;
3740
+ }
3741
+ continue;
3742
+ }
3743
+ const condition = logic.condition;
3744
+ const context = this.fieldContextRegistry.createDisplayOnlyContext(page.key || '', this.functionRegistry.getCustomFunctions());
3745
+ if (evaluateCondition(condition, context)) {
3746
+ return true;
3747
+ }
3748
+ }
3749
+ return false;
3750
+ }
3751
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: PageOrchestratorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3752
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", 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: [
3753
+ {
3754
+ provide: FIELD_SIGNAL_CONTEXT,
3755
+ useFactory: (orchestrator) => orchestrator.extendedFieldSignalContext(),
3756
+ deps: [PageOrchestratorComponent],
3757
+ },
3758
+ ], ngImport: i0, template: `
3759
+ @for (page of pageRenderStates(); track page.field.key) {
3760
+ <!--
3761
+ Skip pages hidden by 'hidden' logic conditions entirely. FormStateManager
3762
+ derives schema/value/validators from config (not from mounted components),
3763
+ so unmounting a hidden page has no effect on form state — only on render +
3764
+ CD cost. This is the page-level equivalent of the existing field-level
3765
+ \`@if (!field.hidden())\` gate in page-field.component.ts.
3766
+ -->
3767
+ @if (!page.hidden) {
3768
+ @if (page.active || page.preload) {
3769
+ @defer (when page.active; on idle) {
3770
+ <section page-field [field]="page.field" [key]="page.field.key" [pageIndex]="page.index" [isVisible]="page.active"></section>
3771
+ } @placeholder {
3772
+ <div class="df-page-placeholder" [attr.data-page-index]="page.index" [attr.data-page-key]="page.field.key"></div>
3773
+ }
3774
+ } @else {
3775
+ <!--
3776
+ Pages outside the preload window remain lightweight placeholders.
3777
+ Form state derives from config, so leaving their components unmounted
3778
+ does not affect validity, derivations, or navigation. The active page
3779
+ renders directly; configured neighbours preload declaratively on idle.
3780
+ -->
3781
+ <div class="df-page-placeholder" [attr.data-page-index]="page.index" [attr.data-page-key]="page.field.key"></div>
3782
+ }
3783
+ }
3784
+ }
3785
+ `, isInline: true, styles: [":host{display:block;position:relative;width:100%}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, deferBlockDependencies: [() => [/* @ts-ignore */
3786
+ import('./ng-forge-dynamic-forms-page-field.component-DCdBvJ-h.mjs').then(m => m.default)]] });
3785
3787
  }
3786
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: FormStateManager, decorators: [{
3787
- type: Injectable
3788
- }], ctorParameters: () => [] });
3788
+ i0.ɵɵngDeclareClassMetadataAsync({ minVersion: "18.0.0", version: "22.0.1", ngImport: i0, type: PageOrchestratorComponent, resolveDeferredDeps: () => [/* @ts-ignore */
3789
+ import('./ng-forge-dynamic-forms-page-field.component-DCdBvJ-h.mjs').then(m => m.default)], resolveMetadata: PageFieldComponent => ({ decorators: [{
3790
+ type: Component,
3791
+ args: [{ selector: 'div[page-orchestrator]', imports: [PageFieldComponent], template: `
3792
+ @for (page of pageRenderStates(); track page.field.key) {
3793
+ <!--
3794
+ Skip pages hidden by 'hidden' logic conditions entirely. FormStateManager
3795
+ derives schema/value/validators from config (not from mounted components),
3796
+ so unmounting a hidden page has no effect on form state — only on render +
3797
+ CD cost. This is the page-level equivalent of the existing field-level
3798
+ \`@if (!field.hidden())\` gate in page-field.component.ts.
3799
+ -->
3800
+ @if (!page.hidden) {
3801
+ @if (page.active || page.preload) {
3802
+ @defer (when page.active; on idle) {
3803
+ <section page-field [field]="page.field" [key]="page.field.key" [pageIndex]="page.index" [isVisible]="page.active"></section>
3804
+ } @placeholder {
3805
+ <div class="df-page-placeholder" [attr.data-page-index]="page.index" [attr.data-page-key]="page.field.key"></div>
3806
+ }
3807
+ } @else {
3808
+ <!--
3809
+ Pages outside the preload window remain lightweight placeholders.
3810
+ Form state derives from config, so leaving their components unmounted
3811
+ does not affect validity, derivations, or navigation. The active page
3812
+ renders directly; configured neighbours preload declaratively on idle.
3813
+ -->
3814
+ <div class="df-page-placeholder" [attr.data-page-index]="page.index" [attr.data-page-key]="page.field.key"></div>
3815
+ }
3816
+ }
3817
+ }
3818
+ `, host: {
3819
+ class: 'df-page-orchestrator',
3820
+ '[attr.data-current-page]': 'state().currentPageIndex',
3821
+ '[attr.data-total-pages]': 'state().totalPages',
3822
+ }, providers: [
3823
+ {
3824
+ provide: FIELD_SIGNAL_CONTEXT,
3825
+ useFactory: (orchestrator) => orchestrator.extendedFieldSignalContext(),
3826
+ deps: [PageOrchestratorComponent],
3827
+ },
3828
+ ], changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;position:relative;width:100%}\n"] }]
3829
+ }], ctorParameters: () => [], propDecorators: { pageFields: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageFields", required: true }] }], form: [{ type: i0.Input, args: [{ isSignal: true, alias: "form", required: true }] }], fieldSignalContext: [{ type: i0.Input, args: [{ isSignal: true, alias: "fieldSignalContext", required: true }] }] } }) });
3789
3830
 
3790
3831
  function traverseFieldsWithContext(fields, context, visitor, hooks) {
3791
3832
  for (const field of fields) {
@@ -3864,7 +3905,7 @@ function createDerivationRenderGate() {
3864
3905
  if (!has || engine() !== 'idle')
3865
3906
  return;
3866
3907
  engine.set('loading');
3867
- pendingTasks.run(() => import('./ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-CNCeWAeJ.mjs')
3908
+ pendingTasks.run(() => import('./ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-BB8jAcY4.mjs')
3868
3909
  .then(({ bootstrapDerivationOrchestrator }) => {
3869
3910
  runInInjectionContext(injector, () => bootstrapDerivationOrchestrator());
3870
3911
  engine.set('wired');
@@ -4024,6 +4065,7 @@ function coreProviders() {
4024
4065
  deps: [FormStateManager],
4025
4066
  },
4026
4067
  FormIdPrefixService,
4068
+ FieldViewportObserver,
4027
4069
  {
4028
4070
  provide: FORM_ID_PREFIX,
4029
4071
  useFactory: (svc) => svc.prefix,
@@ -4112,25 +4154,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
4112
4154
  type: Injectable
4113
4155
  }] });
4114
4156
 
4115
- const FIELD_WINDOWING = new InjectionToken('FIELD_WINDOWING', {
4116
- providedIn: 'root',
4117
- factory: () => ({ enabled: false, eager: 12, placeholderHeight: '4rem' }),
4118
- });
4119
-
4120
- function resolveFieldWindowing(global, perForm) {
4121
- if (perForm === undefined) {
4122
- return global;
4123
- }
4124
- if (typeof perForm === 'boolean') {
4125
- return { ...global, enabled: perForm };
4126
- }
4127
- return {
4128
- enabled: true,
4129
- eager: clampWindowSize(perForm.eager, global.eager),
4130
- placeholderHeight: perForm.placeholderHeight ?? global.placeholderHeight,
4131
- };
4132
- }
4133
-
4134
4157
  class DynamicForm {
4135
4158
  config = input.required({ ...(ngDevMode ? { debugName: "config" } : /* istanbul ignore next */ {}), alias: 'dynamic-form' });
4136
4159
  formOptions = input(undefined, /* @ts-ignore */
@@ -4171,22 +4194,30 @@ class DynamicForm {
4171
4194
  submitting = this.stateManager.submitting;
4172
4195
  fieldLoadingErrors = this.stateManager.fieldLoadingErrors;
4173
4196
  derivationReady = inject(DERIVATION_RENDER_GATE);
4174
- shouldRender = computed(() => this.stateManager.shouldRender() && this.derivationReady(), /* @ts-ignore */
4197
+ shouldRender = computed(() => this.stateManager.shouldRender() && this.stateManager.formSchemaReady() && this.derivationReady(), /* @ts-ignore */
4175
4198
  ...(ngDevMode ? [{ debugName: "shouldRender" }] : /* istanbul ignore next */ []));
4176
4199
  resolvedFields = this.stateManager.resolvedFields;
4177
4200
  fieldWindowing = computed(() => resolveFieldWindowing(this.globalFieldWindowing, this.effectiveFormOptions()?.fieldWindowing), /* @ts-ignore */
4178
4201
  ...(ngDevMode ? [{ debugName: "fieldWindowing" }] : /* istanbul ignore next */ []));
4179
- totalComponentsCount = computed(() => {
4180
- const fields = this.stateManager.formSetup()?.fields ?? [];
4181
- return countContainersRecursive(fields) + 1;
4202
+ initializedComponents = signal(new Set(), /* @ts-ignore */
4203
+ ...(ngDevMode ? [{ debugName: "initializedComponents" }] : /* istanbul ignore next */ []));
4204
+ initialPageReady = signal(false, /* @ts-ignore */
4205
+ ...(ngDevMode ? [{ debugName: "initialPageReady" }] : /* istanbul ignore next */ []));
4206
+ initializationReady = computed(() => {
4207
+ const config = this.activeConfig();
4208
+ if (!config)
4209
+ return false;
4210
+ if (this.formModeDetection().mode === 'paged')
4211
+ return this.initialPageReady();
4212
+ const initialized = this.initializedComponents();
4213
+ const expected = [
4214
+ initializationComponentKey('dynamic-form', this.componentId),
4215
+ ...collectInitializingContainerKeys(config.fields ?? []),
4216
+ ];
4217
+ return expected.every((key) => initialized.has(key));
4182
4218
  }, /* @ts-ignore */
4183
- ...(ngDevMode ? [{ debugName: "totalComponentsCount" }] : /* istanbul ignore next */ []));
4184
- initialized$ = setupInitializationTracking({
4185
- eventBus: this.eventBus,
4186
- totalComponentsCount: this.totalComponentsCount,
4187
- injector: this.injector,
4188
- componentId: this.componentId,
4189
- });
4219
+ ...(ngDevMode ? [{ debugName: "initializationReady" }] : /* istanbul ignore next */ []));
4220
+ initialized$ = toObservable(this.initializationReady, { injector: this.injector }).pipe(filter((ready) => ready), take(1), shareReplay({ bufferSize: 1, refCount: false }));
4190
4221
  validityChange = outputFromObservable(toObservable(this.valid));
4191
4222
  dirtyChange = outputFromObservable(toObservable(this.dirty));
4192
4223
  submitted = outputFromObservable(this.stateManager.submitted$);
@@ -4194,12 +4225,32 @@ class DynamicForm {
4194
4225
  cleared = outputFromObservable(this.eventBus.on('form-clear'));
4195
4226
  events = outputFromObservable(this.eventBus.events$);
4196
4227
  initialized = outputFromObservable(this.initialized$);
4228
+ activePageInitialized = outputFromObservable(this.eventBus.on('active-page-initialized'));
4197
4229
  onPageChange = outputFromObservable(this.eventBus.on('page-change'));
4198
4230
  onPageNavigationStateChange = outputFromObservable(this.eventBus.on('pager-state'));
4199
4231
  constructor() {
4232
+ this.eventBus
4233
+ .on('component-initialized')
4234
+ .pipe(takeUntilDestroyed(this.destroyRef))
4235
+ .subscribe((event) => {
4236
+ const key = initializationComponentKey(event.componentType, event.componentId);
4237
+ this.initializedComponents.update((current) => {
4238
+ if (current.has(key))
4239
+ return current;
4240
+ const next = new Set(current);
4241
+ next.add(key);
4242
+ return next;
4243
+ });
4244
+ });
4245
+ this.eventBus
4246
+ .on('active-page-initialized')
4247
+ .pipe(takeUntilDestroyed(this.destroyRef), take(1))
4248
+ .subscribe(() => this.initialPageReady.set(true));
4200
4249
  this.dispatcher?.connect(this.eventBus);
4201
4250
  this.destroyRef.onDestroy(() => this.dispatcher?.disconnect());
4202
4251
  inject(FORM_INITIALIZER, { optional: true });
4252
+ const preload = injectFormComponentPreloader();
4253
+ explicitEffect([this.config], ([config]) => preload.preloadConfig(config));
4203
4254
  this.setupEffects();
4204
4255
  this.setupEventHandlers();
4205
4256
  }
@@ -4221,8 +4272,8 @@ class DynamicForm {
4221
4272
  this.eventBus.dispatch(ComponentInitializedEvent, 'dynamic-form', this.componentId);
4222
4273
  }
4223
4274
  });
4224
- explicitEffect([this.resolvedFields, this.formModeDetection], ([fields, { mode }]) => {
4225
- if (mode === 'non-paged' && fields.length > 0) {
4275
+ explicitEffect([this.resolvedFields, this.formModeDetection, this.activeConfig], ([, { mode }, config]) => {
4276
+ if (config && mode === 'non-paged') {
4226
4277
  afterNextRender(() => {
4227
4278
  this.eventBus.dispatch(ComponentInitializedEvent, 'dynamic-form', this.componentId);
4228
4279
  }, { injector: this.injector });
@@ -4258,7 +4309,7 @@ class DynamicForm {
4258
4309
  });
4259
4310
  }
4260
4311
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: DynamicForm, deps: [], target: i0.ɵɵFactoryTarget.Component });
4261
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", 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 }, source: { classPropertyName: "source", publicName: "source", 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", "attr.id": "idPrefix() || null" }, classAttribute: "df-dynamic-form df-form" }, providers: [
4312
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", 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 }, source: { classPropertyName: "source", publicName: "source", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", validityChange: "validityChange", dirtyChange: "dirtyChange", submitted: "submitted", reset: "reset", cleared: "cleared", events: "events", initialized: "initialized", activePageInitialized: "activePageInitialized", 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", "attr.id": "idPrefix() || null" }, classAttribute: "df-dynamic-form df-form" }, providers: [
4262
4313
  provideDynamicFormDI(),
4263
4314
  DfFieldTemplateRegistry,
4264
4315
  { provide: DF_FIELD_TEMPLATES, useFactory: () => inject(DfFieldTemplateRegistry).map },
@@ -4270,12 +4321,8 @@ class DynamicForm {
4270
4321
  }
4271
4322
  @case ('non-paged') {
4272
4323
  @for (field of resolvedFields(); track field.key; let i = $index) {
4273
- <!-- @if + DfFieldOutlet's own renderReady-&-!hidden gate together silence NG01916:
4274
- the template @if removes the host from the DOM (Angular's prescribed pattern), and
4275
- the directive's gate stops a same-CD-pass mount before [formField] can warn.
4276
- Don't dedupe one without the other — see DfFieldOutlet.renderReady. -->
4277
- @if (!field.hidden()) {
4278
- @if (windowsField(field, i)) {
4324
+ @if (windowsField(field, i)) {
4325
+ @if (!field.hidden()) {
4279
4326
  @defer (on viewport) {
4280
4327
  <ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
4281
4328
  } @placeholder {
@@ -4286,9 +4333,11 @@ class DynamicForm {
4286
4333
  aria-hidden="true"
4287
4334
  ></div>
4288
4335
  }
4289
- } @else {
4290
- <ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
4291
4336
  }
4337
+ } @else {
4338
+ <!-- DfFieldOutlet owns the hidden gate so an already-mounted field can be detached
4339
+ and restored without recreating its component and wrapper chain. -->
4340
+ <ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
4292
4341
  }
4293
4342
  }
4294
4343
  }
@@ -4309,12 +4358,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
4309
4358
  }
4310
4359
  @case ('non-paged') {
4311
4360
  @for (field of resolvedFields(); track field.key; let i = $index) {
4312
- <!-- @if + DfFieldOutlet's own renderReady-&-!hidden gate together silence NG01916:
4313
- the template @if removes the host from the DOM (Angular's prescribed pattern), and
4314
- the directive's gate stops a same-CD-pass mount before [formField] can warn.
4315
- Don't dedupe one without the other — see DfFieldOutlet.renderReady. -->
4316
- @if (!field.hidden()) {
4317
- @if (windowsField(field, i)) {
4361
+ @if (windowsField(field, i)) {
4362
+ @if (!field.hidden()) {
4318
4363
  @defer (on viewport) {
4319
4364
  <ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
4320
4365
  } @placeholder {
@@ -4325,9 +4370,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
4325
4370
  aria-hidden="true"
4326
4371
  ></div>
4327
4372
  }
4328
- } @else {
4329
- <ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
4330
4373
  }
4374
+ } @else {
4375
+ <!-- DfFieldOutlet owns the hidden gate so an already-mounted field can be detached
4376
+ and restored without recreating its component and wrapper chain. -->
4377
+ <ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
4331
4378
  }
4332
4379
  }
4333
4380
  }
@@ -4349,29 +4396,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
4349
4396
  '[attr.id]': 'idPrefix() || null',
4350
4397
  '(submit)': 'onNativeSubmit($event)',
4351
4398
  }, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".df-form{display:grid;grid-template-columns:1fr;gap:var(--df-grid-row-gap, .5rem);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, .5rem);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, .5rem);--df-grid-row-gap: var(--df-grid-row-gap-sm, .5rem)}.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, 12),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, .5rem);--df-grid-row-gap: var(--df-grid-row-gap-md, .5rem)}.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, .5rem);--df-grid-row-gap: var(--df-grid-row-gap-lg, .5rem)}.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, .5rem);--df-grid-row-gap: var(--df-grid-row-gap-xl, .5rem)}.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, .5rem);width:100%}:host>*{grid-column:1/-1}:host.df-form-spacing-custom{--df-grid-row-gap: var(--df-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{gap:0}.df-field-placeholder{display:block}\n"] }]
4352
- }], 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"] }], source: [{ type: i0.Input, args: [{ isSignal: true, alias: "source", required: false }] }], _projectedTemplates: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => DfTemplate), { isSignal: true }] }], 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"] }] } });
4353
- function countContainersRecursive(fields) {
4354
- let count = 0;
4355
- for (const field of fields) {
4356
- if (isContainerField(field)) {
4357
- count += 1;
4358
- if (hasChildFields(field)) {
4359
- const children = field.fields;
4360
- if (Array.isArray(children)) {
4361
- for (const child of children) {
4362
- if (Array.isArray(child)) {
4363
- count += countContainersRecursive(child);
4364
- }
4365
- else if (child != null && isContainerField(child)) {
4366
- count += countContainersRecursive([child]);
4367
- }
4368
- }
4369
- }
4370
- }
4371
- }
4372
- }
4373
- return count;
4374
- }
4399
+ }], 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"] }], source: [{ type: i0.Input, args: [{ isSignal: true, alias: "source", required: false }] }], _projectedTemplates: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => DfTemplate), { isSignal: true }] }], 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"] }], activePageInitialized: [{ type: i0.Output, args: ["activePageInitialized"] }], onPageChange: [{ type: i0.Output, args: ["onPageChange"] }], onPageNavigationStateChange: [{ type: i0.Output, args: ["onPageNavigationStateChange"] }] } });
4375
4400
 
4376
4401
  const BUILT_IN_ADDON_TYPES = [
4377
4402
  {
@@ -4419,28 +4444,28 @@ const DISPLAY_FIELD_TYPES_BASE = {
4419
4444
  const BUILT_IN_FIELDS = [
4420
4445
  {
4421
4446
  name: 'row',
4422
- loadComponent: () => import('./ng-forge-dynamic-forms-container-field.component-ByGdzAsk.mjs'),
4447
+ loadComponent: () => import('./ng-forge-dynamic-forms-container-field.component-DfQ0y-77.mjs'),
4423
4448
  mapper: rowFieldMapper,
4424
4449
  valueHandling: 'flatten',
4425
4450
  ...CONTAINER_FIELD_TYPES_BASE,
4426
4451
  },
4427
4452
  {
4428
4453
  name: 'group',
4429
- loadComponent: () => import('./ng-forge-dynamic-forms-group-field.component-DnLZdj8v.mjs'),
4454
+ loadComponent: () => import('./ng-forge-dynamic-forms-group-field.component-DxHD5I8E.mjs'),
4430
4455
  mapper: groupFieldMapper,
4431
4456
  valueHandling: 'include',
4432
4457
  ...CONTAINER_FIELD_TYPES_BASE,
4433
4458
  },
4434
4459
  {
4435
4460
  name: 'array',
4436
- loadComponent: () => import('./ng-forge-dynamic-forms-array-field.component-NTHLPjau.mjs'),
4461
+ loadComponent: () => import('./ng-forge-dynamic-forms-array-field.component-6yyDriVB.mjs'),
4437
4462
  mapper: arrayFieldMapper,
4438
4463
  valueHandling: 'include',
4439
4464
  ...CONTAINER_FIELD_TYPES_BASE,
4440
4465
  },
4441
4466
  {
4442
4467
  name: 'page',
4443
- loadComponent: () => import('./ng-forge-dynamic-forms-page-field.component-CwH22lRl.mjs'),
4468
+ loadComponent: () => import('./ng-forge-dynamic-forms-page-field.component-DCdBvJ-h.mjs'),
4444
4469
  mapper: pageFieldMapper,
4445
4470
  valueHandling: 'flatten',
4446
4471
  ...CONTAINER_FIELD_TYPES_BASE,
@@ -4458,7 +4483,7 @@ const BUILT_IN_FIELDS = [
4458
4483
  },
4459
4484
  {
4460
4485
  name: 'container',
4461
- loadComponent: () => import('./ng-forge-dynamic-forms-container-field.component-ByGdzAsk.mjs'),
4486
+ loadComponent: () => import('./ng-forge-dynamic-forms-container-field.component-DfQ0y-77.mjs'),
4462
4487
  mapper: containerFieldMapper,
4463
4488
  valueHandling: 'flatten',
4464
4489
  ...CONTAINER_FIELD_TYPES_BASE,
@@ -4553,7 +4578,7 @@ function provideDynamicForm(...items) {
4553
4578
  const logger = inject(DynamicFormLogger);
4554
4579
  const registry = new Map();
4555
4580
  fields.forEach((fieldType) => {
4556
- if (registry.has(fieldType.name)) {
4581
+ if (DEV_MODE && registry.has(fieldType.name)) {
4557
4582
  logger.warn(`Field type "${fieldType.name}" is already registered. Overwriting.`);
4558
4583
  }
4559
4584
  registry.set(fieldType.name, fieldType);
@@ -4581,13 +4606,15 @@ function provideDynamicForm(...items) {
4581
4606
  const registry = new Map();
4582
4607
  const builtInNames = new Set(BUILT_IN_WRAPPERS.map((w) => w.wrapperName));
4583
4608
  BUILT_IN_WRAPPERS.forEach((wrapperType) => registry.set(wrapperType.wrapperName, wrapperType));
4584
- const seenCustom = new Set();
4609
+ const seenCustom = DEV_MODE ? new Set() : null;
4585
4610
  customWrappers.forEach((wrapperType) => {
4586
4611
  const name = wrapperType.wrapperName;
4587
- if (seenCustom.has(name) && !builtInNames.has(name)) {
4588
- logger.warn(`Wrapper type "${name}" is already registered. Overwriting.`);
4612
+ if (seenCustom) {
4613
+ if (seenCustom.has(name) && !builtInNames.has(name)) {
4614
+ logger.warn(`Wrapper type "${name}" is already registered. Overwriting.`);
4615
+ }
4616
+ seenCustom.add(name);
4589
4617
  }
4590
- seenCustom.add(name);
4591
4618
  registry.set(name, wrapperType);
4592
4619
  });
4593
4620
  return registry;
@@ -4619,7 +4646,7 @@ function provideDynamicForm(...items) {
4619
4646
  registry.set(def.type, def);
4620
4647
  }
4621
4648
  for (const def of contributed) {
4622
- if (registry.has(def.type)) {
4649
+ if (DEV_MODE && registry.has(def.type)) {
4623
4650
  logger.warn(`Addon type "${def.type}" is already registered. Overwriting.`);
4624
4651
  }
4625
4652
  registry.set(def.type, def);
@@ -4677,10 +4704,18 @@ function withFieldWindowing(config) {
4677
4704
  enabled: true,
4678
4705
  eager: clampWindowSize(config?.eager, 12),
4679
4706
  placeholderHeight: config?.placeholderHeight ?? '4rem',
4707
+ park: resolveFieldParking({ enabled: true, margin: '100%' }, config?.park),
4680
4708
  };
4681
4709
  return createFeature('field-windowing', [{ provide: FIELD_WINDOWING, useValue: resolved }]);
4682
4710
  }
4683
4711
 
4712
+ const VALIDATION_EXECUTION_DEFAULTS = new InjectionToken('VALIDATION_EXECUTION_DEFAULTS', {
4713
+ providedIn: 'root',
4714
+ factory: () => ({
4715
+ validateWhenHidden: false,
4716
+ }),
4717
+ });
4718
+
4684
4719
  function withValidationExecutionDefaults(config) {
4685
4720
  const resolved = {
4686
4721
  validateWhenHidden: config?.validateWhenHidden ?? false,
@@ -4871,6 +4906,16 @@ function sanitizeFormConfigPure(config, fieldRegistry, typeRegistry, options = {
4871
4906
  return { sanitized: { ...config, fields: fields }, warnings };
4872
4907
  }
4873
4908
 
4909
+ class ActivePageInitializedEvent {
4910
+ pageIndex;
4911
+ pageKey;
4912
+ type = 'active-page-initialized';
4913
+ constructor(pageIndex, pageKey) {
4914
+ this.pageIndex = pageIndex;
4915
+ this.pageKey = pageKey;
4916
+ }
4917
+ }
4918
+
4874
4919
  class GoToPageEvent {
4875
4920
  pageIndex;
4876
4921
  options;
@@ -4998,5 +5043,5 @@ function arrayEvent(arrayKey) {
4998
5043
 
4999
5044
 
5000
5045
 
5001
- export { withEventFormValue as $, ArrayItemRegistryService as A, BUILT_IN_FIELDS as B, CONTAINER_FIELD_PROCESSORS as C, DfFieldOutlet as D, EventDispatcher as E, FIELD_WINDOWING as F, GoToPageEvent as G, PageChangeEvent as H, InsertArrayItemEvent as I, PagerStateEvent as J, PopArrayItemEvent as K, PrependArrayItemEvent as L, MoveArrayItemEvent as M, NextPageEvent as N, PreviousPageEvent as O, PROPERTY_OVERRIDE_STORE as P, TextAddonComponent as Q, RemoveAtIndexEvent as R, ShiftArrayItemEvent as S, TemplateAddonComponent as T, arrayEvent as U, createWrappers as V, isWrappersBundle as W, provideDynamicForm as X, sanitizeFormConfig as Y, withAddonActions as Z, withCustomAddon as _, isSameWrapperChain as a, withFieldWindowing as a0, withLegacyStatusClasses as a1, withLoggerConfig as a2, withPagePreload as a3, withValidationExecutionDefaults as a4, withValueExclusionDefaults as a5, wrapperProps as a6, createWrapperChainController as b, createFieldResolutionPipe as c, createSchemaFromFields as d, createHiddenSignal as e, createRenderReadySignal as f, getNormalizedArrayMetadata as g, getFieldDefaultValue as h, injectFieldRegistry as i, resolveFieldWindowing as j, DEV_MODE as k, DEFAULT_DEBOUNCE_MS as l, mapFieldToInputs as m, DERIVATION_WARNING_TRACKER as n, FormStateManager as o, DERIVATION_LOG_CONFIG as p, AppendArrayItemEvent as q, resolveWrappers as r, ComponentAddonComponent as s, traverseFieldsWithContext as t, ConsoleLogger as u, DfTemplate as v, DynamicForm as w, FormClearEvent as x, FormResetEvent as y, FormSubmitEvent as z };
5002
- //# sourceMappingURL=ng-forge-dynamic-forms-ng-forge-dynamic-forms-B2C_WAMP.mjs.map
5046
+ export { arrayEvent as $, ArrayItemRegistryService as A, BUILT_IN_FIELDS as B, CONTAINER_FIELD_PROCESSORS as C, DfFieldOutlet as D, DfTemplate as E, FIELD_WINDOWING as F, DynamicForm as G, EventDispatcher as H, FormClearEvent as I, FormResetEvent as J, FormSubmitEvent as K, GoToPageEvent as L, InsertArrayItemEvent as M, MoveArrayItemEvent as N, NextPageEvent as O, PROPERTY_OVERRIDE_STORE as P, PageChangeEvent as Q, PagerStateEvent as R, SchemaRegistryService as S, PopArrayItemEvent as T, PrependArrayItemEvent as U, VALIDATION_EXECUTION_DEFAULTS as V, PreviousPageEvent as W, RemoveAtIndexEvent as X, ShiftArrayItemEvent as Y, TemplateAddonComponent as Z, TextAddonComponent as _, createFieldResolutionPipe as a, createWrappers as a0, isWrappersBundle as a1, provideDynamicForm as a2, sanitizeFormConfig as a3, withAddonActions as a4, withCustomAddon as a5, withEventFormValue as a6, withFieldWindowing as a7, withLegacyStatusClasses as a8, withLoggerConfig as a9, withPagePreload as aa, withValidationExecutionDefaults as ab, withValueExclusionDefaults as ac, wrapperProps as ad, isSameWrapperChain as b, computeContainerHostClasses as c, initializationComponentPath as d, createWrapperChainController as e, createHiddenSignal as f, createRenderReadySignal as g, getNormalizedArrayMetadata as h, injectFieldRegistry as i, emitComponentInitialized as j, getFieldDefaultValue as k, resolveFieldWindowing as l, mapFieldToInputs as m, collectInitializingContainerKeys as n, ActivePageInitializedEvent as o, initializationComponentKey as p, DEV_MODE as q, resolveWrappers as r, setupContainerInitEffect as s, traverseFieldsWithContext as t, DERIVATION_WARNING_TRACKER as u, FormStateManager as v, DERIVATION_LOG_CONFIG as w, AppendArrayItemEvent as x, ComponentAddonComponent as y, ConsoleLogger as z };
5047
+ //# sourceMappingURL=ng-forge-dynamic-forms-ng-forge-dynamic-forms-tA8ZkP8i.mjs.map