@ng-forge/dynamic-forms 1.2.0-next.1 → 1.2.0-next.10

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 (50) hide show
  1. package/README.md +11 -11
  2. package/fesm2022/{ng-forge-dynamic-forms-array-field.component-BVnXnaNw.mjs → ng-forge-dynamic-forms-array-field.component-CSCKq_1K.mjs} +9 -13
  3. package/fesm2022/ng-forge-dynamic-forms-array-field.component-CSCKq_1K.mjs.map +1 -0
  4. package/fesm2022/{ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-CkqvXXs_.mjs → ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-CTOTwzzw.mjs} +29 -7
  5. package/fesm2022/ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-CTOTwzzw.mjs.map +1 -0
  6. package/fesm2022/ng-forge-dynamic-forms-bootstrap-web-mcp-BtjqeJOj.mjs +1054 -0
  7. package/fesm2022/ng-forge-dynamic-forms-bootstrap-web-mcp-BtjqeJOj.mjs.map +1 -0
  8. package/fesm2022/{ng-forge-dynamic-forms-container-field.component-CM1pY9v5.mjs → ng-forge-dynamic-forms-container-field.component-Dc3ROhHL.mjs} +14 -17
  9. package/fesm2022/ng-forge-dynamic-forms-container-field.component-Dc3ROhHL.mjs.map +1 -0
  10. package/fesm2022/{ng-forge-dynamic-forms-css-wrapper.component-D_yRuLpu.mjs → ng-forge-dynamic-forms-css-wrapper.component-BzgX8lAD.mjs} +5 -5
  11. package/fesm2022/ng-forge-dynamic-forms-css-wrapper.component-BzgX8lAD.mjs.map +1 -0
  12. package/fesm2022/ng-forge-dynamic-forms-debounce-C9F4v1Ke.mjs +17 -0
  13. package/fesm2022/ng-forge-dynamic-forms-debounce-C9F4v1Ke.mjs.map +1 -0
  14. package/fesm2022/ng-forge-dynamic-forms-field-errors-wrapper.component-CIKbWqYA.mjs +25 -0
  15. package/fesm2022/ng-forge-dynamic-forms-field-errors-wrapper.component-CIKbWqYA.mjs.map +1 -0
  16. package/fesm2022/ng-forge-dynamic-forms-form-schema.service-Dx5Dlb15.mjs +24 -0
  17. package/fesm2022/ng-forge-dynamic-forms-form-schema.service-Dx5Dlb15.mjs.map +1 -0
  18. package/fesm2022/{ng-forge-dynamic-forms-group-field.component-D4JhDffB.mjs → ng-forge-dynamic-forms-group-field.component-DAC3P9DC.mjs} +9 -13
  19. package/fesm2022/ng-forge-dynamic-forms-group-field.component-DAC3P9DC.mjs.map +1 -0
  20. package/fesm2022/ng-forge-dynamic-forms-integration.mjs +43 -105
  21. package/fesm2022/ng-forge-dynamic-forms-integration.mjs.map +1 -1
  22. package/fesm2022/ng-forge-dynamic-forms-internal.mjs +290 -116
  23. package/fesm2022/ng-forge-dynamic-forms-internal.mjs.map +1 -1
  24. package/fesm2022/{ng-forge-dynamic-forms-ng-forge-dynamic-forms-ZiNaCWHl.mjs → ng-forge-dynamic-forms-ng-forge-dynamic-forms-CXQe_5td.mjs} +1871 -1632
  25. package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-CXQe_5td.mjs.map +1 -0
  26. package/fesm2022/{ng-forge-dynamic-forms-page-field.component-Wbz6S8iH.mjs → ng-forge-dynamic-forms-page-field.component-DPKJcwUO.mjs} +57 -16
  27. package/fesm2022/ng-forge-dynamic-forms-page-field.component-DPKJcwUO.mjs.map +1 -0
  28. package/fesm2022/{ng-forge-dynamic-forms-row-wrapper.component-BBWmqQA_.mjs → ng-forge-dynamic-forms-row-wrapper.component-DMEeKZny.mjs} +5 -5
  29. package/fesm2022/ng-forge-dynamic-forms-row-wrapper.component-DMEeKZny.mjs.map +1 -0
  30. package/fesm2022/ng-forge-dynamic-forms-schema-builder-BvDTzfdM.mjs +570 -0
  31. package/fesm2022/ng-forge-dynamic-forms-schema-builder-BvDTzfdM.mjs.map +1 -0
  32. package/fesm2022/{ng-forge-dynamic-forms-text-field.component-C-f80sbk.mjs → ng-forge-dynamic-forms-text-field.component-BB-jutBq.mjs} +4 -4
  33. package/fesm2022/{ng-forge-dynamic-forms-text-field.component-C-f80sbk.mjs.map → ng-forge-dynamic-forms-text-field.component-BB-jutBq.mjs.map} +1 -1
  34. package/fesm2022/ng-forge-dynamic-forms.mjs +2 -2
  35. package/package.json +2 -2
  36. package/types/ng-forge-dynamic-forms-integration.d.ts +7 -31
  37. package/types/ng-forge-dynamic-forms-internal.d.ts +669 -308
  38. package/types/ng-forge-dynamic-forms.d.ts +287 -33
  39. package/fesm2022/ng-forge-dynamic-forms-array-field.component-BVnXnaNw.mjs.map +0 -1
  40. package/fesm2022/ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-CkqvXXs_.mjs.map +0 -1
  41. package/fesm2022/ng-forge-dynamic-forms-container-field.component-CM1pY9v5.mjs.map +0 -1
  42. package/fesm2022/ng-forge-dynamic-forms-container-utils-BZFz6ztF.mjs +0 -24
  43. package/fesm2022/ng-forge-dynamic-forms-container-utils-BZFz6ztF.mjs.map +0 -1
  44. package/fesm2022/ng-forge-dynamic-forms-css-wrapper.component-D_yRuLpu.mjs.map +0 -1
  45. package/fesm2022/ng-forge-dynamic-forms-emit-initialization-DhHwU6FJ.mjs +0 -17
  46. package/fesm2022/ng-forge-dynamic-forms-emit-initialization-DhHwU6FJ.mjs.map +0 -1
  47. package/fesm2022/ng-forge-dynamic-forms-group-field.component-D4JhDffB.mjs.map +0 -1
  48. package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-ZiNaCWHl.mjs.map +0 -1
  49. package/fesm2022/ng-forge-dynamic-forms-page-field.component-Wbz6S8iH.mjs.map +0 -1
  50. package/fesm2022/ng-forge-dynamic-forms-row-wrapper.component-BBWmqQA_.mjs.map +0 -1
@@ -1,15 +1,135 @@
1
1
  import * as i0 from '@angular/core';
2
- import { reflectComponentType, Injector, inject, EnvironmentInjector, DestroyRef, computed, signal, input, ViewContainerRef, Directive, InjectionToken, linkedSignal, ChangeDetectionStrategy, Component, Injectable, isSignal, runInInjectionContext, afterNextRender, untracked, 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, isContainerField, isArrayField, hasChildFields, isSimplifiedArrayField, DynamicFormError, isStateLogicConfig, isDerivationLogicConfig, isFormStateCondition, createFormStateLogicFunction, createDebouncedLogicFunction, createLogicFunction, ExpressionParser, applyValidator, isPageField, isGenericContainerField, 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, defer, tap, 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.1.5", ngImport: i0, type: FieldViewportObserver, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
124
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: FieldViewportObserver });
125
+ }
126
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: FieldViewportObserver, decorators: [{
127
+ type: Injectable
128
+ }], ctorParameters: () => [] });
129
+
12
130
  const EMPTY_WRAPPERS = Object.freeze([]);
131
+ const FIELD_ERRORS_WRAPPER = 'field-errors';
132
+ const FIELD_ERRORS_CONFIG = Object.freeze({ type: FIELD_ERRORS_WRAPPER });
13
133
  function isSameWrapperChain(a, b) {
14
134
  if (a === b)
15
135
  return true;
@@ -21,19 +141,30 @@ function isSameWrapperChain(a, b) {
21
141
  }
22
142
  return true;
23
143
  }
24
- function resolveWrappers(field, defaultWrappers, autoAssociations) {
25
- if (field.wrappers === null) {
144
+ function resolveWrappers(field, defaultWrappers, autoAssociations, wrapperRegistry) {
145
+ if (field.wrappers === null) {
26
146
  return EMPTY_WRAPPERS;
27
147
  }
28
148
  const autoWrappers = field.skipAutoWrappers ? EMPTY_WRAPPERS : (autoAssociations.get(field.type) ?? EMPTY_WRAPPERS);
29
149
  const defaults = field.skipDefaultWrappers ? EMPTY_WRAPPERS : (defaultWrappers ?? EMPTY_WRAPPERS);
30
150
  const fieldLevel = field.wrappers ?? EMPTY_WRAPPERS;
31
- if (autoWrappers.length === 0 && defaults.length === 0 && fieldLevel.length === 0) {
32
- return EMPTY_WRAPPERS;
151
+ const composed = [...autoWrappers, ...defaults, ...fieldLevel];
152
+ if (needsErrorWrapper(field, composed, wrapperRegistry)) {
153
+ composed.push(FIELD_ERRORS_CONFIG);
33
154
  }
34
- return [...autoWrappers, ...defaults, ...fieldLevel];
155
+ return composed.length === 0 ? EMPTY_WRAPPERS : composed;
156
+ }
157
+ const VALIDATABLE_CONTAINERS = new Set(['group', 'array']);
158
+ function needsErrorWrapper(field, composed, wrapperRegistry) {
159
+ if (!VALIDATABLE_CONTAINERS.has(field.type))
160
+ return false;
161
+ if (!field.validators || field.validators.length === 0)
162
+ return false;
163
+ return !composed.some((w) => w.type === FIELD_ERRORS_WRAPPER || wrapperRegistry.get(w.type)?.rendersFieldErrors);
35
164
  }
36
165
 
166
+ const DEV_MODE = typeof ngDevMode === 'undefined' || !!ngDevMode;
167
+
37
168
  const inputNamesCache = new WeakMap();
38
169
  function getDeclaredInputs(componentType) {
39
170
  let inputs = inputNamesCache.get(componentType);
@@ -55,24 +186,33 @@ function hasDefaultExport(value) {
55
186
  function resolveDefaultExport(result) {
56
187
  return hasDefaultExport(result) ? result.default : result;
57
188
  }
58
- async function loadWrapperComponent(type, registry, cache) {
189
+ async function loadWrapperComponent(type, registry, cache, loadCache) {
59
190
  const cached = cache.get(type);
60
191
  if (cached)
61
192
  return cached;
193
+ const pending = loadCache?.get(type);
194
+ if (pending)
195
+ return pending;
62
196
  const definition = registry.get(type);
63
197
  if (!definition)
64
198
  return undefined;
65
- const component = resolveDefaultExport(await definition.loadComponent());
66
- if (component)
67
- cache.set(type, component);
68
- return component;
69
- }
70
- 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) {
71
209
  if (configs.length === 0)
72
210
  return of([]);
73
- 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) => {
74
212
  if (!component) {
75
- 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
+ }
76
216
  return null;
77
217
  }
78
218
  return { config, component };
@@ -112,9 +252,11 @@ function renderStep(slot, remaining, options, refs) {
112
252
  ref.changeDetectorRef.detectChanges();
113
253
  const inner = resolveInnerSlot(ref);
114
254
  if (!inner) {
115
- options.logger.error(`Wrapper component for type '${wrapper.config.type}' does not provide a 'fieldComponent' ViewContainerRef. ` +
116
- `Ensure the wrapper component has a viewChild('fieldComponent', { read: ViewContainerRef }) query ` +
117
- `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
+ }
118
260
  while (refs.length)
119
261
  refs.pop().destroy();
120
262
  return;
@@ -156,27 +298,34 @@ function createWrapperChainController(opts) {
156
298
  const deps = injectChainDeps();
157
299
  const state = createStateSignal(opts);
158
300
  const mounted = { value: null };
301
+ const detached = { value: null };
159
302
  let refs = [];
160
303
  buildEmissionStream(state, deps)
161
304
  .pipe(takeUntilDestroyed(deps.destroyRef))
162
305
  .subscribe((emission) => {
163
306
  try {
164
- refs = applyEmission(emission, { opts, deps, mounted, refs });
307
+ refs = applyEmission(emission, { opts, deps, mounted, refs, detached });
165
308
  }
166
309
  catch (err) {
167
310
  deps.logger.error('Wrapper chain render failed; tearing down partial state.', err);
168
311
  opts.vcr().clear();
169
312
  refs = [];
170
313
  mounted.value = null;
314
+ detached.value?.destroy();
315
+ detached.value = null;
171
316
  }
172
317
  });
173
318
  pushFieldInputsOnChange(opts, () => refs);
174
- deps.destroyRef.onDestroy(() => opts.vcr().clear());
319
+ deps.destroyRef.onDestroy(() => {
320
+ detached.value?.destroy();
321
+ opts.vcr().clear();
322
+ });
175
323
  }
176
324
  function injectChainDeps() {
177
325
  return {
178
326
  registry: inject(WRAPPER_REGISTRY),
179
327
  cache: inject(WRAPPER_COMPONENT_CACHE),
328
+ loadCache: inject(WRAPPER_COMPONENT_LOAD_CACHE),
180
329
  logger: inject(DynamicFormLogger),
181
330
  destroyRef: inject(DestroyRef),
182
331
  environmentInjector: inject(EnvironmentInjector),
@@ -200,16 +349,27 @@ function resolveLoadedWrappers(state, deps) {
200
349
  const loaded = state.wrappers.map((config) => ({ config, component: deps.cache.get(config.type) }));
201
350
  return of({ state, loaded });
202
351
  }
203
- 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 })));
204
353
  }
205
354
  function applyEmission({ state, loaded }, ctx) {
206
355
  const { opts, deps, mounted, refs } = ctx;
207
356
  const vcr = opts.vcr();
208
357
  const structurallyChanged = isStructurallyDifferent(mounted.value, state);
209
- 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;
210
366
  return refs;
367
+ }
211
368
  if (structurallyChanged && mounted.value !== null) {
212
- opts.beforeRebuild?.();
369
+ const preservesDetachedOuterView = opts.beforeRebuild?.() === true;
370
+ if (ctx.detached.value && !preservesDetachedOuterView)
371
+ ctx.detached.value.destroy();
372
+ ctx.detached.value = null;
213
373
  vcr.clear();
214
374
  mounted.value = null;
215
375
  }
@@ -246,7 +406,7 @@ function pushFieldInputsOnChange(opts, getRefs) {
246
406
  });
247
407
  }
248
408
 
249
- function buildFieldInputs(rawInputs, cache, fieldType) {
409
+ function buildFieldInputs(rawInputs, cache, fieldType, validationMessages) {
250
410
  const fieldTreeCandidate = rawInputs['field'];
251
411
  const hasFieldTree = fieldTreeCandidate !== undefined && typeof fieldTreeCandidate === 'function';
252
412
  const readonlyField = hasFieldTree ? toReadonlyFieldTreeCached(cache, fieldTreeCandidate) : undefined;
@@ -258,6 +418,7 @@ function buildFieldInputs(rawInputs, cache, fieldType) {
258
418
  type: fieldType,
259
419
  field: readonlyField,
260
420
  setValue,
421
+ ...(validationMessages !== undefined && { validationMessages }),
261
422
  };
262
423
  }
263
424
 
@@ -266,14 +427,19 @@ class FieldComponentSlot {
266
427
  state = signal(EMPTY_STATE, /* @ts-ignore */
267
428
  ...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
268
429
  lastInputs = null;
430
+ _parked = signal(false, /* @ts-ignore */
431
+ ...(ngDevMode ? [{ debugName: "_parked" }] : /* istanbul ignore next */ []));
269
432
  phase = () => this.state().phase;
270
433
  snapshot = () => this.state();
434
+ parked = this._parked.asReadonly();
271
435
  mountOrReuse(slot, component, fieldInjector, environmentInjector, rawInputs, fieldInputs) {
272
436
  const current = this.state();
273
437
  if (current.phase !== 'empty' && current.ref.componentType === component) {
274
438
  slot.insert(current.ref.hostView);
275
439
  this.lastInputs = null;
276
440
  this.state.set(Object.freeze({ phase: 'mounted', ref: current.ref, slot }));
441
+ if (this._parked())
442
+ current.ref.changeDetectorRef.detach();
277
443
  this.pushInputs(rawInputs, fieldInputs);
278
444
  if (current.phase === 'detached')
279
445
  this.replayFocus(current.focusSnapshot);
@@ -282,6 +448,7 @@ class FieldComponentSlot {
282
448
  if (current.phase !== 'empty')
283
449
  current.ref.destroy();
284
450
  this.lastInputs = null;
451
+ this._parked.set(false);
285
452
  this.state.set(EMPTY_STATE);
286
453
  const ref = slot.createComponent(component, {
287
454
  environmentInjector,
@@ -310,19 +477,42 @@ class FieldComponentSlot {
310
477
  detach() {
311
478
  const current = this.state();
312
479
  if (current.phase !== 'mounted')
313
- return;
480
+ return false;
314
481
  const focusSnapshot = this.captureFocus(current.ref);
315
482
  const idx = current.slot.indexOf(current.ref.hostView);
316
483
  if (idx >= 0)
317
484
  current.slot.detach(idx);
318
485
  this.lastInputs = null;
319
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);
320
509
  }
321
510
  destroyOnTeardown() {
322
511
  const current = this.state();
323
512
  if (current.phase === 'detached')
324
513
  current.ref.destroy();
325
514
  this.lastInputs = null;
515
+ this._parked.set(false);
326
516
  this.state.set(EMPTY_STATE);
327
517
  }
328
518
  captureFocus(ref) {
@@ -347,6 +537,100 @@ class FieldComponentSlot {
347
537
  }
348
538
  }
349
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
+
350
634
  class DfFieldOutlet {
351
635
  dfFieldOutlet = input.required(/* @ts-ignore */
352
636
  ...(ngDevMode ? [{ debugName: "dfFieldOutlet" }] : /* istanbul ignore next */ []));
@@ -356,25 +640,53 @@ class DfFieldOutlet {
356
640
  vcr = signal(this.vcrRef).asReadonly();
357
641
  destroyRef = inject(DestroyRef);
358
642
  wrapperAutoAssociations = inject(WRAPPER_AUTO_ASSOCIATIONS);
643
+ wrapperRegistry = inject(WRAPPER_REGISTRY);
359
644
  defaultWrappersSignal = inject(DEFAULT_WRAPPERS, { optional: true });
360
645
  readonlyFieldCache = inject(READONLY_FIELD_TREE_CACHE);
361
- fieldComponent = new FieldComponentSlot();
646
+ eventBus = inject(EventBus, { optional: true });
647
+ injector = inject(Injector);
648
+ fieldComponent = new FieldComponentSlot();
362
649
  componentIdentity = computed(() => this.dfFieldOutlet().component, /* @ts-ignore */
363
650
  ...(ngDevMode ? [{ debugName: "componentIdentity" }] : /* istanbul ignore next */ []));
364
651
  renderReady = computed(() => this.dfFieldOutlet().renderReady() && !this.dfFieldOutlet().hidden(), /* @ts-ignore */
365
652
  ...(ngDevMode ? [{ debugName: "renderReady" }] : /* istanbul ignore next */ []));
653
+ hidden = computed(() => this.dfFieldOutlet().hidden(), /* @ts-ignore */
654
+ ...(ngDevMode ? [{ debugName: "hidden" }] : /* istanbul ignore next */ []));
366
655
  rawInputs = computed(() => this.dfFieldOutlet().inputs(), /* @ts-ignore */
367
656
  ...(ngDevMode ? [{ debugName: "rawInputs" }] : /* istanbul ignore next */ []));
368
- wrappers = computed(() => resolveWrappers(this.dfFieldOutlet().fieldDef, this.defaultWrappersSignal?.(), this.wrapperAutoAssociations), { ...(ngDevMode ? { debugName: "wrappers" } : /* istanbul ignore next */ {}), equal: isSameWrapperChain });
369
- fieldInputs = computed(() => buildFieldInputs(this.rawInputs(), this.readonlyFieldCache, this.dfFieldOutlet().fieldDef.type), /* @ts-ignore */
657
+ wrappers = computed(() => resolveWrappers(this.dfFieldOutlet().fieldDef, this.defaultWrappersSignal?.(), this.wrapperAutoAssociations, this.wrapperRegistry), { ...(ngDevMode ? { debugName: "wrappers" } : /* istanbul ignore next */ {}), equal: isSameWrapperChain });
658
+ fieldInputs = computed(() => buildFieldInputs(this.rawInputs(), this.readonlyFieldCache, this.dfFieldOutlet().fieldDef.type, this.dfFieldOutlet().fieldDef.validationMessages), /* @ts-ignore */
370
659
  ...(ngDevMode ? [{ debugName: "fieldInputs" }] : /* istanbul ignore next */ []));
371
660
  defaultEnvInjector = inject(EnvironmentInjector);
372
- fieldEnvInjector = computed(() => this.dfFieldOutletEnvironmentInjector() ?? this.defaultEnvInjector, /* @ts-ignore */
661
+ fieldEnvInjector = computed(() => this.dfFieldOutletEnvironmentInjector() ?? this.defaultEnvInjector, /* @ts-ignore */
373
662
  ...(ngDevMode ? [{ debugName: "fieldEnvInjector" }] : /* istanbul ignore next */ []));
374
- fieldInjector = computed(() => this.dfFieldOutlet().injector, /* @ts-ignore */
663
+ fieldInjector = computed(() => this.dfFieldOutlet().injector, /* @ts-ignore */
375
664
  ...(ngDevMode ? [{ debugName: "fieldInjector" }] : /* istanbul ignore next */ []));
376
665
  outermostHostClasses = computed(() => getGridClassString(this.dfFieldOutlet().fieldDef) || undefined, /* @ts-ignore */
377
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 });
378
690
  constructor() {
379
691
  createWrapperChainController({
380
692
  vcr: this.vcr,
@@ -390,170 +702,432 @@ class DfFieldOutlet {
390
702
  this.fieldComponent.mountOrReuse(slot, resolved.component, resolved.injector, this.fieldEnvInjector(), this.rawInputs(), this.fieldInputs());
391
703
  },
392
704
  });
393
- 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
+ });
394
740
  this.destroyRef.onDestroy(() => this.fieldComponent.destroyOnTeardown());
395
741
  }
396
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: DfFieldOutlet, deps: [], target: i0.ɵɵFactoryTarget.Directive });
397
- 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 });
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;
749
+ }
750
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: DfFieldOutlet, deps: [], target: i0.ɵɵFactoryTarget.Directive });
751
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.5", 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 });
398
752
  }
399
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: DfFieldOutlet, decorators: [{
753
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: DfFieldOutlet, decorators: [{
400
754
  type: Directive,
401
755
  args: [{
402
756
  selector: '[dfFieldOutlet]',
403
757
  }]
404
758
  }], ctorParameters: () => [], propDecorators: { dfFieldOutlet: [{ type: i0.Input, args: [{ isSignal: true, alias: "dfFieldOutlet", required: true }] }], dfFieldOutletEnvironmentInjector: [{ type: i0.Input, args: [{ isSignal: true, alias: "dfFieldOutletEnvironmentInjector", required: false }] }] } });
405
759
 
760
+ function isPending(formSignal) {
761
+ return formSignal()().pending();
762
+ }
763
+ function isTreeValidationErrors(result) {
764
+ const isError = (v) => typeof v === 'object' && v !== null && 'kind' in v;
765
+ return Array.isArray(result) ? result.every(isError) && result.length > 0 : isError(result);
766
+ }
406
767
  function wrapSubmissionAction(action) {
407
768
  return async (formTree) => {
408
769
  const result = action(formTree);
409
- if (isObservable(result)) {
410
- await firstValueFrom(result);
411
- return;
412
- }
413
- await Promise.resolve(result);
770
+ const resolved = isObservable(result) ? await firstValueFrom(result) : await Promise.resolve(result);
771
+ return isTreeValidationErrors(resolved) ? resolved : undefined;
414
772
  };
415
773
  }
416
774
  function createSubmissionHandler(options) {
417
775
  const { eventBus, configSignal, formSignal, validSignal, logger } = options;
418
- return eventBus.on('submit').pipe(exhaustMap(() => {
776
+ return eventBus.on('submit').pipe(exhaustMap((event) => {
777
+ const reply = event.reply;
778
+ reply?.accept();
419
779
  const submissionConfig = configSignal().submission;
420
780
  if (!submissionConfig?.action) {
781
+ reply?.settle(validSignal() ? { status: 'dispatched' } : { status: 'validation-failed' });
421
782
  return EMPTY;
422
783
  }
423
784
  if (!validSignal()) {
424
785
  logger.debug('Submission action skipped: form is not valid (invalid or pending async validators)');
786
+ reply?.settle(isPending(formSignal) ? { status: 'pending-validation' } : { status: 'validation-failed' });
425
787
  return EMPTY;
426
788
  }
427
789
  const wrappedAction = wrapSubmissionAction(submissionConfig.action);
428
- return from(submit(formSignal(), wrappedAction)).pipe(catchError((error) => {
429
- logger.error('Submission action failed:', error);
430
- return EMPTY;
431
- }));
790
+ return defer(() => {
791
+ const formTree = formSignal();
792
+ return from(submit(formTree, wrappedAction)).pipe(
793
+ tap(() => {
794
+ reply?.settle(formTree().errorSummary().length ? { status: 'server-errors' } : { status: 'success' });
795
+ }), catchError((error) => {
796
+ logger.error('Submission action failed:', error);
797
+ reply?.settle({ status: 'action-failed', error });
798
+ return EMPTY;
799
+ }));
800
+ });
432
801
  }));
433
802
  }
434
803
 
435
804
  class FormSubmitEvent {
805
+ reply;
436
806
  type = 'submit';
437
- }
438
-
439
- class NextPageEvent {
440
- type = 'next-page';
441
- }
442
-
443
- class PageChangeEvent {
444
- currentPageIndex;
445
- totalPages;
446
- previousPageIndex;
447
- type = 'page-change';
448
- constructor(
449
- currentPageIndex,
450
- totalPages,
451
- previousPageIndex) {
452
- this.currentPageIndex = currentPageIndex;
453
- this.totalPages = totalPages;
454
- this.previousPageIndex = previousPageIndex;
807
+ constructor(reply) {
808
+ this.reply = reply;
455
809
  }
456
810
  }
457
811
 
458
- class PreviousPageEvent {
459
- type = 'previous-page';
812
+ const COMPONENT_CACHE = new InjectionToken('COMPONENT_CACHE', {
813
+ providedIn: 'root',
814
+ factory: () => new Map(),
815
+ });
816
+ const COMPONENT_LOAD_CACHE = new InjectionToken('COMPONENT_LOAD_CACHE', {
817
+ providedIn: 'root',
818
+ factory: () => new Map(),
819
+ });
820
+ function injectFieldRegistry() {
821
+ const registry = inject(FIELD_REGISTRY);
822
+ const componentCache = inject(COMPONENT_CACHE);
823
+ const componentLoadCache = inject(COMPONENT_LOAD_CACHE);
824
+ return {
825
+ getType(name) {
826
+ return registry.get(name);
827
+ },
828
+ hasType(name) {
829
+ return registry.has(name);
830
+ },
831
+ async loadTypeComponent(name) {
832
+ const fieldType = registry.get(name);
833
+ if (!fieldType) {
834
+ throw new DynamicFormError(`Field type "${name}" is not registered`);
835
+ }
836
+ if (!fieldType.loadComponent) {
837
+ return undefined;
838
+ }
839
+ const cached = componentCache.get(name);
840
+ if (cached) {
841
+ return cached;
842
+ }
843
+ const pending = componentLoadCache.get(name);
844
+ if (pending) {
845
+ return pending;
846
+ }
847
+ try {
848
+ const load = fieldType
849
+ .loadComponent()
850
+ .then(resolveDefaultExport)
851
+ .then((component) => {
852
+ if (component) {
853
+ componentCache.set(name, component);
854
+ }
855
+ return component;
856
+ })
857
+ .catch((error) => {
858
+ throw new DynamicFormError(`Failed to load component for field type "${name}": ${error}`);
859
+ })
860
+ .finally(() => componentLoadCache.delete(name));
861
+ componentLoadCache.set(name, load);
862
+ return load;
863
+ }
864
+ catch (error) {
865
+ throw new DynamicFormError(`Failed to load component for field type "${name}": ${error}`);
866
+ }
867
+ },
868
+ getLoadedComponent(name) {
869
+ return componentCache.get(name);
870
+ },
871
+ getTypes() {
872
+ return Array.from(registry.values());
873
+ },
874
+ registerTypes(types) {
875
+ types.forEach((type) => {
876
+ registry.set(type.name, type);
877
+ });
878
+ },
879
+ get raw() {
880
+ return registry;
881
+ },
882
+ };
460
883
  }
461
884
 
462
- class FormResetEvent {
463
- type = 'form-reset';
885
+ const NORMALIZED_ARRAY_METADATA = Symbol('normalizedArrayMetadata');
886
+ function setNormalizedArrayMetadata(arrayField, metadata) {
887
+ arrayField[NORMALIZED_ARRAY_METADATA] = metadata;
464
888
  }
465
-
466
- class FormClearEvent {
467
- type = 'form-clear';
889
+ function getNormalizedArrayMetadata(arrayField) {
890
+ return arrayField[NORMALIZED_ARRAY_METADATA];
468
891
  }
469
892
 
470
- class AppendArrayItemEvent {
471
- arrayKey;
472
- template;
473
- type = 'append-array-item';
474
- constructor(
475
- arrayKey,
476
- template) {
477
- this.arrayKey = arrayKey;
478
- this.template = template;
479
- }
893
+ function normalizeSimplifiedArrays(fields) {
894
+ return fields.flatMap((field) => {
895
+ if (isContainerField(field) && !isArrayField(field) && hasChildFields(field)) {
896
+ return [{ ...field, fields: normalizeSimplifiedArrays(field.fields) }];
897
+ }
898
+ if (isArrayField(field) && !isSimplifiedArrayField(field)) {
899
+ const normalizedItems = field.fields.map((item) => Array.isArray(item) ? normalizeSimplifiedArrays(item) : item);
900
+ return [{ ...field, fields: normalizedItems }];
901
+ }
902
+ if (isSimplifiedArrayField(field)) {
903
+ const { arrayField, addButton } = expandSimplifiedArray(field);
904
+ return addButton ? [arrayField, addButton] : [arrayField];
905
+ }
906
+ return [field];
907
+ });
480
908
  }
481
-
482
- class PrependArrayItemEvent {
483
- arrayKey;
484
- template;
485
- type = 'prepend-array-item';
486
- constructor(
487
- arrayKey,
488
- template) {
489
- this.arrayKey = arrayKey;
490
- this.template = template;
909
+ const INVALID_ARRAY_CHILD_TYPES = new Set(['page', 'array']);
910
+ function validateSimplifiedTemplate(field) {
911
+ const { template, key } = field;
912
+ const isObjectTemplate = Array.isArray(template);
913
+ if (isObjectTemplate) {
914
+ const templateFields = template;
915
+ for (const tmpl of templateFields) {
916
+ if (INVALID_ARRAY_CHILD_TYPES.has(tmpl.type)) {
917
+ throw new DynamicFormError(`Simplified array "${key}" template contains a '${tmpl.type}' field (key: '${tmpl.key}'). ` +
918
+ `Only leaf fields, rows, and groups are allowed as array children.`);
919
+ }
920
+ }
491
921
  }
492
- }
493
-
494
- class InsertArrayItemEvent {
495
- arrayKey;
496
- index;
497
- template;
498
- type = 'insert-array-item';
499
- constructor(
500
- arrayKey,
501
- index,
502
- template) {
503
- this.arrayKey = arrayKey;
504
- this.index = index;
505
- this.template = template;
922
+ else {
923
+ const singleTemplate = template;
924
+ if (INVALID_ARRAY_CHILD_TYPES.has(singleTemplate.type)) {
925
+ throw new DynamicFormError(`Simplified array "${key}" template has type '${singleTemplate.type}'. ` +
926
+ `Only leaf fields, rows, and groups are allowed as array children.`);
927
+ }
506
928
  }
507
929
  }
508
-
509
- class PopArrayItemEvent {
510
- arrayKey;
511
- type = 'pop-array-item';
512
- constructor(
513
- arrayKey) {
514
- this.arrayKey = arrayKey;
930
+ function expandSimplifiedArray(field) {
931
+ const { template, value = [], addButton, removeButton, key, logic, minLength, maxLength, wrappers, skipAutoWrappers, skipDefaultWrappers, validators, validationMessages, required, validateWhenHidden, } = field;
932
+ const isObjectTemplate = Array.isArray(template);
933
+ const values = value;
934
+ validateSimplifiedTemplate(field);
935
+ const items = values.map((v) => {
936
+ if (isObjectTemplate) {
937
+ return buildObjectItem(template, v, removeButton);
938
+ }
939
+ return buildPrimitiveItem(template, v);
940
+ });
941
+ const addTemplate = isObjectTemplate
942
+ ? buildObjectItemTemplate(template, removeButton)
943
+ : template;
944
+ const hasAutoRemove = !isObjectTemplate && removeButton !== false;
945
+ const arrayFieldObj = {
946
+ key,
947
+ type: 'array',
948
+ fields: items,
949
+ };
950
+ if (logic) {
951
+ arrayFieldObj['logic'] = logic;
952
+ }
953
+ if (minLength !== undefined) {
954
+ arrayFieldObj['minLength'] = minLength;
955
+ }
956
+ if (maxLength !== undefined) {
957
+ arrayFieldObj['maxLength'] = maxLength;
958
+ }
959
+ if (wrappers !== undefined) {
960
+ arrayFieldObj['wrappers'] = wrappers;
961
+ }
962
+ if (skipAutoWrappers !== undefined) {
963
+ arrayFieldObj['skipAutoWrappers'] = skipAutoWrappers;
964
+ }
965
+ if (skipDefaultWrappers !== undefined) {
966
+ arrayFieldObj['skipDefaultWrappers'] = skipDefaultWrappers;
967
+ }
968
+ if (validators !== undefined) {
969
+ arrayFieldObj['validators'] = validators;
970
+ }
971
+ if (validationMessages !== undefined) {
972
+ arrayFieldObj['validationMessages'] = validationMessages;
973
+ }
974
+ if (required !== undefined) {
975
+ arrayFieldObj['required'] = required;
976
+ }
977
+ if (validateWhenHidden !== undefined) {
978
+ arrayFieldObj['validateWhenHidden'] = validateWhenHidden;
515
979
  }
980
+ const arrayField = arrayFieldObj;
981
+ const primitiveFieldKey = !isObjectTemplate ? template.key : undefined;
982
+ const metadataTemplate = isObjectTemplate ? buildObjectItemTemplate(template, removeButton) : template;
983
+ setNormalizedArrayMetadata(arrayFieldObj, {
984
+ ...(hasAutoRemove && { autoRemoveButton: buildRemoveButton(removeButton) }),
985
+ ...(primitiveFieldKey && { primitiveFieldKey }),
986
+ template: metadataTemplate,
987
+ });
988
+ let addButtonField;
989
+ if (addButton !== false) {
990
+ const buttonConfig = (typeof addButton === 'object' ? addButton : {});
991
+ addButtonField = {
992
+ key: `${key}__add`,
993
+ type: 'add-array-item',
994
+ label: buttonConfig.label ?? 'Add',
995
+ arrayKey: key,
996
+ template: addTemplate,
997
+ ...(buttonConfig.props && { props: buttonConfig.props }),
998
+ ...(logic && { logic }),
999
+ };
1000
+ }
1001
+ return { arrayField, addButton: addButtonField };
516
1002
  }
517
-
518
- class ShiftArrayItemEvent {
519
- arrayKey;
520
- type = 'shift-array-item';
521
- constructor(
522
- arrayKey) {
523
- this.arrayKey = arrayKey;
1003
+ function buildPrimitiveItem(template, value) {
1004
+ return { ...template, value };
1005
+ }
1006
+ function buildObjectItem(template, valueObj, removeButton) {
1007
+ const fieldsWithValues = template.map((templateField) => ({
1008
+ ...templateField,
1009
+ ...(Object.hasOwn(valueObj, templateField.key) && { value: valueObj[templateField.key] }),
1010
+ }));
1011
+ if (removeButton === false) {
1012
+ return fieldsWithValues;
524
1013
  }
1014
+ return [...fieldsWithValues, buildRemoveButton(removeButton)];
525
1015
  }
526
-
527
- class MoveArrayItemEvent {
528
- arrayKey;
529
- fromIndex;
530
- toIndex;
531
- type = 'move-array-item';
532
- constructor(
533
- arrayKey,
534
- fromIndex,
535
- toIndex) {
536
- this.arrayKey = arrayKey;
537
- this.fromIndex = fromIndex;
538
- this.toIndex = toIndex;
1016
+ function buildObjectItemTemplate(template, removeButton) {
1017
+ if (removeButton === false) {
1018
+ return template;
539
1019
  }
1020
+ return [...template, buildRemoveButton(removeButton)];
1021
+ }
1022
+ function buildRemoveButton(config) {
1023
+ const buttonConfig = (typeof config === 'object' ? config : {});
1024
+ return {
1025
+ key: '__remove',
1026
+ type: 'remove-array-item',
1027
+ label: buttonConfig.label ?? 'Remove',
1028
+ ...(buttonConfig.props && { props: buttonConfig.props }),
1029
+ };
540
1030
  }
541
1031
 
542
- class RemoveAtIndexEvent {
543
- arrayKey;
544
- index;
545
- type = 'remove-at-index';
1032
+ function collectFieldDefs(fields, out = []) {
1033
+ for (const field of fields ?? []) {
1034
+ out.push(field);
1035
+ const children = field.fields;
1036
+ if (Array.isArray(children)) {
1037
+ for (const child of children) {
1038
+ if (Array.isArray(child))
1039
+ collectFieldDefs(child, out);
1040
+ else
1041
+ collectFieldDefs([child], out);
1042
+ }
1043
+ }
1044
+ const metadata = getNormalizedArrayMetadata(field);
1045
+ const template = metadata?.template;
1046
+ if (Array.isArray(template))
1047
+ collectFieldDefs(template, out);
1048
+ else if (template)
1049
+ collectFieldDefs([template], out);
1050
+ if (metadata?.autoRemoveButton)
1051
+ collectFieldDefs([metadata.autoRemoveButton], out);
1052
+ }
1053
+ return out;
1054
+ }
1055
+ function injectFormComponentPreloader() {
1056
+ const fieldRegistry = injectFieldRegistry();
1057
+ const wrapperRegistry = inject(WRAPPER_REGISTRY);
1058
+ const wrapperCache = inject(WRAPPER_COMPONENT_CACHE);
1059
+ const wrapperLoadCache = inject(WRAPPER_COMPONENT_LOAD_CACHE);
1060
+ const autoAssociations = inject(WRAPPER_AUTO_ASSOCIATIONS);
1061
+ const defaultWrappers = inject(DEFAULT_WRAPPERS, { optional: true });
1062
+ const preloadedConfigs = new WeakSet();
1063
+ const preloadedFieldRoots = new WeakSet();
1064
+ const preloadFields = (fields) => {
1065
+ const unseenFields = fields.filter((field) => {
1066
+ if (preloadedFieldRoots.has(field))
1067
+ return false;
1068
+ preloadedFieldRoots.add(field);
1069
+ return true;
1070
+ });
1071
+ if (unseenFields.length === 0)
1072
+ return;
1073
+ const normalizedFields = normalizeSimplifiedArrays(unseenFields);
1074
+ const defs = collectFieldDefs(normalizedFields);
1075
+ if (defs.length === 0)
1076
+ return;
1077
+ const fieldTypes = new Set();
1078
+ const wrapperTypes = new Set();
1079
+ for (const def of defs) {
1080
+ if (def.type)
1081
+ fieldTypes.add(def.type);
1082
+ for (const wrapper of resolveWrappers(def, defaultWrappers?.(), autoAssociations, wrapperRegistry)) {
1083
+ wrapperTypes.add(wrapper.type);
1084
+ }
1085
+ }
1086
+ const started = [];
1087
+ for (const type of fieldTypes) {
1088
+ if (fieldRegistry.getLoadedComponent(type))
1089
+ continue;
1090
+ started.push(Promise.resolve(fieldRegistry.loadTypeComponent(type)).catch(() => undefined));
1091
+ }
1092
+ for (const type of wrapperTypes) {
1093
+ if (wrapperCache.has(type))
1094
+ continue;
1095
+ started.push(loadWrapperComponent(type, wrapperRegistry, wrapperCache, wrapperLoadCache).catch(() => undefined));
1096
+ }
1097
+ void Promise.allSettled(started);
1098
+ };
1099
+ return {
1100
+ preloadConfig: (config) => {
1101
+ if (preloadedConfigs.has(config))
1102
+ return;
1103
+ preloadedConfigs.add(config);
1104
+ const fields = config.fields;
1105
+ if (fields.length > 0 && fields.every(isPageField))
1106
+ return;
1107
+ preloadFields(fields);
1108
+ },
1109
+ preloadFields,
1110
+ };
1111
+ }
1112
+
1113
+ class PageChangeEvent {
1114
+ currentPageIndex;
1115
+ totalPages;
1116
+ previousPageIndex;
1117
+ type = 'page-change';
546
1118
  constructor(
547
- arrayKey,
548
- index) {
549
- this.arrayKey = arrayKey;
550
- this.index = index;
1119
+ currentPageIndex,
1120
+ totalPages,
1121
+ previousPageIndex) {
1122
+ this.currentPageIndex = currentPageIndex;
1123
+ this.totalPages = totalPages;
1124
+ this.previousPageIndex = previousPageIndex;
551
1125
  }
552
1126
  }
553
1127
 
554
- class PageNavigationStateChangeEvent {
1128
+ class PagerStateEvent {
555
1129
  state;
556
- type = 'page-navigation-state-change';
1130
+ type = 'pager-state';
557
1131
  constructor(state) {
558
1132
  this.state = state;
559
1133
  }
@@ -564,512 +1138,19 @@ const PAGE_PRELOAD_WINDOW = new InjectionToken('PAGE_PRELOAD_WINDOW', {
564
1138
  factory: () => 1,
565
1139
  });
566
1140
 
567
- function clampWindowSize(value, fallback) {
568
- if (value === undefined || !Number.isFinite(value))
569
- return fallback;
570
- return Math.max(0, Math.floor(value));
571
- }
572
-
573
- class PageOrchestratorComponent {
574
- eventBus = inject(EventBus);
575
- fieldContextRegistry = inject(FieldContextRegistryService);
576
- functionRegistry = inject(FunctionRegistryService);
577
- formOptions = inject(FORM_OPTIONS, { optional: true });
578
- globalPreloadWindow = inject(PAGE_PRELOAD_WINDOW);
579
- pageFields = input.required(/* @ts-ignore */
580
- ...(ngDevMode ? [{ debugName: "pageFields" }] : /* istanbul ignore next */ []));
581
- form = input.required(/* @ts-ignore */
582
- ...(ngDevMode ? [{ debugName: "form" }] : /* istanbul ignore next */ []));
583
- fieldSignalContext = input.required(/* @ts-ignore */
584
- ...(ngDevMode ? [{ debugName: "fieldSignalContext" }] : /* istanbul ignore next */ []));
585
- pageHiddenStates = computed(() => {
586
- const pages = this.pageFields();
587
- return pages.map((page) => this.evaluatePageHidden(page));
588
- }, /* @ts-ignore */
589
- ...(ngDevMode ? [{ debugName: "pageHiddenStates" }] : /* istanbul ignore next */ []));
590
- visiblePageIndices = computed(() => {
591
- const hiddenStates = this.pageHiddenStates();
592
- return hiddenStates
593
- .map((hidden, index) => ({ index, hidden }))
594
- .filter((item) => !item.hidden)
595
- .map((item) => item.index);
596
- }, /* @ts-ignore */
597
- ...(ngDevMode ? [{ debugName: "visiblePageIndices" }] : /* istanbul ignore next */ []));
598
- currentPageIndex = linkedSignal(() => {
599
- const totalPages = this.pageFields().length;
600
- if (totalPages === 0)
601
- return 0;
602
- return 0;
603
- }, /* @ts-ignore */
604
- ...(ngDevMode ? [{ debugName: "currentPageIndex" }] : /* istanbul ignore next */ []));
605
- state = computed(() => {
606
- const currentIndex = this.currentPageIndex();
607
- const totalPages = this.pageFields().length;
608
- const visibleIndices = this.visiblePageIndices();
609
- const currentVisiblePosition = visibleIndices.indexOf(currentIndex);
610
- const isFirstVisiblePage = currentVisiblePosition === 0 || currentVisiblePosition === -1;
611
- const isLastVisiblePage = currentVisiblePosition >= visibleIndices.length - 1;
612
- return {
613
- currentPageIndex: currentIndex,
614
- totalPages,
615
- isFirstPage: isFirstVisiblePage,
616
- isLastPage: isLastVisiblePage,
617
- navigationDisabled: false,
618
- };
619
- }, /* @ts-ignore */
620
- ...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
621
- currentPageValid = computed(() => {
622
- const currentIndex = this.currentPageIndex();
623
- const pages = this.pageFields();
624
- const form = this.form();
625
- if (pages.length === 0 || currentIndex >= pages.length) {
626
- return true;
1141
+ function collectLeafFieldKeys(fields) {
1142
+ const keys = [];
1143
+ for (const field of fields) {
1144
+ if (isRowField(field) || isGenericContainerField(field)) {
1145
+ keys.push(...collectLeafFieldKeys(field.fields));
627
1146
  }
628
- const currentPage = pages[currentIndex];
629
- const pageFields = currentPage.fields || [];
630
- const leafKeys = collectLeafFieldKeys(pageFields);
631
- for (const fieldKey of leafKeys) {
632
- const field = form[fieldKey];
633
- if (field && typeof field === 'function') {
634
- const fieldState = field();
635
- if (fieldState && typeof fieldState.valid === 'function' && !fieldState.valid()) {
636
- return false;
637
- }
638
- }
639
- }
640
- return true;
641
- }, /* @ts-ignore */
642
- ...(ngDevMode ? [{ debugName: "currentPageValid" }] : /* istanbul ignore next */ []));
643
- extendedFieldSignalContext = computed(() => ({
644
- ...this.fieldSignalContext(),
645
- currentPageValid: this.currentPageValid,
646
- }), /* @ts-ignore */
647
- ...(ngDevMode ? [{ debugName: "extendedFieldSignalContext" }] : /* istanbul ignore next */ []));
648
- preloadWindow = computed(() => {
649
- const perForm = this.formOptions?.()?.pagePreloadWindow;
650
- return clampWindowSize(perForm, this.globalPreloadWindow);
651
- }, /* @ts-ignore */
652
- ...(ngDevMode ? [{ debugName: "preloadWindow" }] : /* istanbul ignore next */ []));
653
- isWithinPreloadWindow(i) {
654
- return Math.abs(i - this.state().currentPageIndex) <= this.preloadWindow();
655
- }
656
- constructor() {
657
- this.setupEventListeners();
658
- explicitEffect([this.state, this.visiblePageIndices], ([state, visibleIndices]) => {
659
- const currentVisiblePosition = visibleIndices.indexOf(state.currentPageIndex);
660
- if (currentVisiblePosition === -1 && visibleIndices.length > 0) {
661
- const nearest = this.findNearestVisiblePage(state.currentPageIndex, visibleIndices);
662
- if (nearest !== -1) {
663
- this.navigateToPage(nearest);
664
- }
665
- }
666
- });
667
- }
668
- navigateToNextPage() {
669
- const disableWhenPageInvalid = this.formOptions?.()?.nextButton?.disableWhenPageInvalid ?? true;
670
- if (disableWhenPageInvalid && !this.currentPageValid()) {
671
- return {
672
- success: false,
673
- newPageIndex: this.state().currentPageIndex,
674
- error: 'Current page has invalid fields',
675
- };
676
- }
677
- const currentState = this.state();
678
- const visibleIndices = this.visiblePageIndices();
679
- if (currentState.isLastPage) {
680
- return {
681
- success: false,
682
- newPageIndex: currentState.currentPageIndex,
683
- error: 'Already on the last visible page',
684
- };
685
- }
686
- if (currentState.navigationDisabled) {
687
- return {
688
- success: false,
689
- newPageIndex: currentState.currentPageIndex,
690
- error: 'Navigation is currently disabled',
691
- };
692
- }
693
- const currentVisiblePosition = visibleIndices.indexOf(currentState.currentPageIndex);
694
- if (currentVisiblePosition === -1 || currentVisiblePosition >= visibleIndices.length - 1) {
695
- return {
696
- success: false,
697
- newPageIndex: currentState.currentPageIndex,
698
- error: 'No next visible page available',
699
- };
700
- }
701
- const nextVisiblePageIndex = visibleIndices[currentVisiblePosition + 1];
702
- return this.navigateToPage(nextVisiblePageIndex);
703
- }
704
- navigateToPreviousPage() {
705
- const currentState = this.state();
706
- const visibleIndices = this.visiblePageIndices();
707
- if (currentState.isFirstPage) {
708
- return {
709
- success: false,
710
- newPageIndex: currentState.currentPageIndex,
711
- error: 'Already on the first visible page',
712
- };
713
- }
714
- if (currentState.navigationDisabled) {
715
- return {
716
- success: false,
717
- newPageIndex: currentState.currentPageIndex,
718
- error: 'Navigation is currently disabled',
719
- };
720
- }
721
- const currentVisiblePosition = visibleIndices.indexOf(currentState.currentPageIndex);
722
- if (currentVisiblePosition <= 0) {
723
- return {
724
- success: false,
725
- newPageIndex: currentState.currentPageIndex,
726
- error: 'No previous visible page available',
727
- };
728
- }
729
- const prevVisiblePageIndex = visibleIndices[currentVisiblePosition - 1];
730
- return this.navigateToPage(prevVisiblePageIndex);
731
- }
732
- navigateToPage(pageIndex) {
733
- const currentState = this.state();
734
- const totalPages = currentState.totalPages;
735
- if (pageIndex < 0 || pageIndex >= totalPages) {
736
- return {
737
- success: false,
738
- newPageIndex: currentState.currentPageIndex,
739
- error: `Invalid page index: ${pageIndex}. Valid range is 0 to ${totalPages - 1}`,
740
- };
741
- }
742
- const visibleIndices = this.visiblePageIndices();
743
- if (!visibleIndices.includes(pageIndex)) {
744
- return {
745
- success: false,
746
- newPageIndex: currentState.currentPageIndex,
747
- error: `Cannot navigate to hidden page at index ${pageIndex}. Visible pages: [${visibleIndices.join(', ')}]`,
748
- };
749
- }
750
- if (pageIndex === currentState.currentPageIndex) {
751
- return {
752
- success: true,
753
- newPageIndex: pageIndex,
754
- };
755
- }
756
- const previousIndex = currentState.currentPageIndex;
757
- this.currentPageIndex.set(pageIndex);
758
- this.eventBus.dispatch(PageChangeEvent, pageIndex, totalPages, previousIndex);
759
- return {
760
- success: true,
761
- newPageIndex: pageIndex,
762
- };
763
- }
764
- findNearestVisiblePage(currentIndex, visibleIndices) {
765
- let nearest = -1;
766
- let minDistance = Infinity;
767
- for (const idx of visibleIndices) {
768
- const distance = Math.abs(idx - currentIndex);
769
- if (distance < minDistance || (distance === minDistance && idx > nearest)) {
770
- minDistance = distance;
771
- nearest = idx;
772
- }
773
- }
774
- return nearest;
775
- }
776
- setupEventListeners() {
777
- this.eventBus
778
- .on('next-page')
779
- .pipe(takeUntilDestroyed())
780
- .subscribe(() => {
781
- this.navigateToNextPage();
782
- });
783
- this.eventBus
784
- .on('previous-page')
785
- .pipe(takeUntilDestroyed())
786
- .subscribe(() => {
787
- this.navigateToPreviousPage();
788
- });
789
- explicitEffect([this.state], ([state]) => this.eventBus.dispatch(PageNavigationStateChangeEvent, state));
790
- }
791
- evaluatePageHidden(page) {
792
- if (!page.logic || page.logic.length === 0) {
793
- return false;
794
- }
795
- const hiddenLogic = page.logic.filter((l) => l.type === 'hidden');
796
- if (hiddenLogic.length === 0) {
797
- return false;
798
- }
799
- for (const logic of hiddenLogic) {
800
- if (typeof logic.condition === 'boolean') {
801
- if (logic.condition) {
802
- return true;
803
- }
804
- continue;
805
- }
806
- const condition = logic.condition;
807
- const context = this.fieldContextRegistry.createDisplayOnlyContext(page.key || '', this.functionRegistry.getCustomFunctions());
808
- if (evaluateCondition(condition, context)) {
809
- return true;
810
- }
811
- }
812
- return false;
813
- }
814
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: PageOrchestratorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
815
- 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: [
816
- {
817
- provide: FIELD_SIGNAL_CONTEXT,
818
- useFactory: (orchestrator) => orchestrator.extendedFieldSignalContext(),
819
- deps: [PageOrchestratorComponent],
820
- },
821
- ], ngImport: i0, template: `
822
- @for (pageField of pageFields(); track pageField.key; let i = $index) {
823
- <!--
824
- Skip pages hidden by 'hidden' logic conditions entirely. FormStateManager
825
- derives schema/value/validators from config (not from mounted components),
826
- so unmounting a hidden page has no effect on form state — only on render +
827
- CD cost. This is the page-level equivalent of the existing field-level
828
- \`@if (!field.hidden())\` gate in page-field.component.ts.
829
- -->
830
- @if (!pageHiddenStates()[i]) {
831
- @if (isWithinPreloadWindow(i)) {
832
- <!-- Current and preloaded neighbour pages: render immediately for flicker-free navigation -->
833
- @defer (on immediate) {
834
- <section
835
- page-field
836
- [field]="pageField"
837
- [key]="pageField.key"
838
- [pageIndex]="i"
839
- [isVisible]="i === state().currentPageIndex"
840
- ></section>
841
- } @placeholder {
842
- <div class="df-page-placeholder" [attr.data-page-index]="i" [attr.data-page-key]="pageField.key"></div>
843
- }
844
- } @else {
845
- <!--
846
- Pages outside the preload window render only a lightweight placeholder
847
- — they are NOT mounted. Mounting every page eagerly (previously via
848
- \`@defer (on idle)\`, which fires almost immediately post-load) put all
849
- fields of every page in the DOM at once: O(all-fields) initial render
850
- blocking + O(all-fields) change detection on every keystroke, with no
851
- visible benefit since only one page is shown at a time.
852
-
853
- Form state (schema/value/validators) derives from config, not from
854
- mounted components, so leaving a page unmounted does not affect
855
- validity, derivations, or navigation. A page mounts the moment
856
- navigation brings it into the preload window (above), which is
857
- flicker-free for sequential next/previous navigation. The window size
858
- is configurable via \`withPagePreload(n)\` or \`FormOptions.pagePreloadWindow\`.
859
- -->
860
- <div class="df-page-placeholder" [attr.data-page-index]="i" [attr.data-page-key]="pageField.key"></div>
861
- }
862
- }
863
- }
864
- `, isInline: true, styles: [":host{display:block;position:relative;width:100%}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, deferBlockDependencies: [() => [/* @ts-ignore */
865
- import('./ng-forge-dynamic-forms-page-field.component-Wbz6S8iH.mjs').then(m => m.default)]] });
866
- }
867
- i0.ɵɵngDeclareClassMetadataAsync({ minVersion: "18.0.0", version: "22.0.1", ngImport: i0, type: PageOrchestratorComponent, resolveDeferredDeps: () => [/* @ts-ignore */
868
- import('./ng-forge-dynamic-forms-page-field.component-Wbz6S8iH.mjs').then(m => m.default)], resolveMetadata: PageFieldComponent => ({ decorators: [{
869
- type: Component,
870
- args: [{ selector: 'div[page-orchestrator]', imports: [PageFieldComponent], template: `
871
- @for (pageField of pageFields(); track pageField.key; let i = $index) {
872
- <!--
873
- Skip pages hidden by 'hidden' logic conditions entirely. FormStateManager
874
- derives schema/value/validators from config (not from mounted components),
875
- so unmounting a hidden page has no effect on form state — only on render +
876
- CD cost. This is the page-level equivalent of the existing field-level
877
- \`@if (!field.hidden())\` gate in page-field.component.ts.
878
- -->
879
- @if (!pageHiddenStates()[i]) {
880
- @if (isWithinPreloadWindow(i)) {
881
- <!-- Current and preloaded neighbour pages: render immediately for flicker-free navigation -->
882
- @defer (on immediate) {
883
- <section
884
- page-field
885
- [field]="pageField"
886
- [key]="pageField.key"
887
- [pageIndex]="i"
888
- [isVisible]="i === state().currentPageIndex"
889
- ></section>
890
- } @placeholder {
891
- <div class="df-page-placeholder" [attr.data-page-index]="i" [attr.data-page-key]="pageField.key"></div>
892
- }
893
- } @else {
894
- <!--
895
- Pages outside the preload window render only a lightweight placeholder
896
- — they are NOT mounted. Mounting every page eagerly (previously via
897
- \`@defer (on idle)\`, which fires almost immediately post-load) put all
898
- fields of every page in the DOM at once: O(all-fields) initial render
899
- blocking + O(all-fields) change detection on every keystroke, with no
900
- visible benefit since only one page is shown at a time.
901
-
902
- Form state (schema/value/validators) derives from config, not from
903
- mounted components, so leaving a page unmounted does not affect
904
- validity, derivations, or navigation. A page mounts the moment
905
- navigation brings it into the preload window (above), which is
906
- flicker-free for sequential next/previous navigation. The window size
907
- is configurable via \`withPagePreload(n)\` or \`FormOptions.pagePreloadWindow\`.
908
- -->
909
- <div class="df-page-placeholder" [attr.data-page-index]="i" [attr.data-page-key]="pageField.key"></div>
910
- }
911
- }
912
- }
913
- `, host: {
914
- class: 'df-page-orchestrator',
915
- '[attr.data-current-page]': 'state().currentPageIndex',
916
- '[attr.data-total-pages]': 'state().totalPages',
917
- }, providers: [
918
- {
919
- provide: FIELD_SIGNAL_CONTEXT,
920
- useFactory: (orchestrator) => orchestrator.extendedFieldSignalContext(),
921
- deps: [PageOrchestratorComponent],
922
- },
923
- ], changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;position:relative;width:100%}\n"] }]
924
- }], 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 }] }] } }) });
925
- function collectLeafFieldKeys(fields) {
926
- const keys = [];
927
- for (const field of fields) {
928
- if (isRowField(field)) {
929
- keys.push(...collectLeafFieldKeys(field.fields));
930
- }
931
- else if (field.key) {
932
- keys.push(field.key);
1147
+ else if (field.key) {
1148
+ keys.push(field.key);
933
1149
  }
934
1150
  }
935
1151
  return keys;
936
1152
  }
937
1153
 
938
- const NORMALIZED_ARRAY_METADATA = Symbol('normalizedArrayMetadata');
939
- function setNormalizedArrayMetadata(arrayField, metadata) {
940
- arrayField[NORMALIZED_ARRAY_METADATA] = metadata;
941
- }
942
- function getNormalizedArrayMetadata(arrayField) {
943
- return arrayField[NORMALIZED_ARRAY_METADATA];
944
- }
945
-
946
- function normalizeSimplifiedArrays(fields) {
947
- return fields.flatMap((field) => {
948
- if (isContainerField(field) && !isArrayField(field) && hasChildFields(field)) {
949
- return [{ ...field, fields: normalizeSimplifiedArrays(field.fields) }];
950
- }
951
- if (isArrayField(field) && !isSimplifiedArrayField(field)) {
952
- const normalizedItems = field.fields.map((item) => Array.isArray(item) ? normalizeSimplifiedArrays(item) : item);
953
- return [{ ...field, fields: normalizedItems }];
954
- }
955
- if (isSimplifiedArrayField(field)) {
956
- const { arrayField, addButton } = expandSimplifiedArray(field);
957
- return addButton ? [arrayField, addButton] : [arrayField];
958
- }
959
- return [field];
960
- });
961
- }
962
- const INVALID_ARRAY_CHILD_TYPES = new Set(['page', 'array']);
963
- function validateSimplifiedTemplate(field) {
964
- const { template, key } = field;
965
- const isObjectTemplate = Array.isArray(template);
966
- if (isObjectTemplate) {
967
- const templateFields = template;
968
- for (const tmpl of templateFields) {
969
- if (INVALID_ARRAY_CHILD_TYPES.has(tmpl.type)) {
970
- throw new DynamicFormError(`Simplified array "${key}" template contains a '${tmpl.type}' field (key: '${tmpl.key}'). ` +
971
- `Only leaf fields, rows, and groups are allowed as array children.`);
972
- }
973
- }
974
- }
975
- else {
976
- const singleTemplate = template;
977
- if (INVALID_ARRAY_CHILD_TYPES.has(singleTemplate.type)) {
978
- throw new DynamicFormError(`Simplified array "${key}" template has type '${singleTemplate.type}'. ` +
979
- `Only leaf fields, rows, and groups are allowed as array children.`);
980
- }
981
- }
982
- }
983
- function expandSimplifiedArray(field) {
984
- const { template, value = [], addButton, removeButton, key, logic, minLength, maxLength, wrappers, skipAutoWrappers, skipDefaultWrappers, } = field;
985
- const isObjectTemplate = Array.isArray(template);
986
- const values = value;
987
- validateSimplifiedTemplate(field);
988
- const items = values.map((v) => {
989
- if (isObjectTemplate) {
990
- return buildObjectItem(template, v, removeButton);
991
- }
992
- return buildPrimitiveItem(template, v);
993
- });
994
- const addTemplate = isObjectTemplate
995
- ? buildObjectItemTemplate(template, removeButton)
996
- : template;
997
- const hasAutoRemove = !isObjectTemplate && removeButton !== false;
998
- const arrayFieldObj = {
999
- key,
1000
- type: 'array',
1001
- fields: items,
1002
- };
1003
- if (logic) {
1004
- arrayFieldObj['logic'] = logic;
1005
- }
1006
- if (minLength !== undefined) {
1007
- arrayFieldObj['minLength'] = minLength;
1008
- }
1009
- if (maxLength !== undefined) {
1010
- arrayFieldObj['maxLength'] = maxLength;
1011
- }
1012
- if (wrappers !== undefined) {
1013
- arrayFieldObj['wrappers'] = wrappers;
1014
- }
1015
- if (skipAutoWrappers !== undefined) {
1016
- arrayFieldObj['skipAutoWrappers'] = skipAutoWrappers;
1017
- }
1018
- if (skipDefaultWrappers !== undefined) {
1019
- arrayFieldObj['skipDefaultWrappers'] = skipDefaultWrappers;
1020
- }
1021
- const arrayField = arrayFieldObj;
1022
- const primitiveFieldKey = !isObjectTemplate ? template.key : undefined;
1023
- const metadataTemplate = isObjectTemplate ? buildObjectItemTemplate(template, removeButton) : template;
1024
- setNormalizedArrayMetadata(arrayFieldObj, {
1025
- ...(hasAutoRemove && { autoRemoveButton: buildRemoveButton(removeButton) }),
1026
- ...(primitiveFieldKey && { primitiveFieldKey }),
1027
- template: metadataTemplate,
1028
- });
1029
- let addButtonField;
1030
- if (addButton !== false) {
1031
- const buttonConfig = (typeof addButton === 'object' ? addButton : {});
1032
- addButtonField = {
1033
- key: `${key}__add`,
1034
- type: 'add-array-item',
1035
- label: buttonConfig.label ?? 'Add',
1036
- arrayKey: key,
1037
- template: addTemplate,
1038
- ...(buttonConfig.props && { props: buttonConfig.props }),
1039
- ...(logic && { logic }),
1040
- };
1041
- }
1042
- return { arrayField, addButton: addButtonField };
1043
- }
1044
- function buildPrimitiveItem(template, value) {
1045
- return { ...template, value };
1046
- }
1047
- function buildObjectItem(template, valueObj, removeButton) {
1048
- const fieldsWithValues = template.map((templateField) => ({
1049
- ...templateField,
1050
- ...(Object.hasOwn(valueObj, templateField.key) && { value: valueObj[templateField.key] }),
1051
- }));
1052
- if (removeButton === false) {
1053
- return fieldsWithValues;
1054
- }
1055
- return [...fieldsWithValues, buildRemoveButton(removeButton)];
1056
- }
1057
- function buildObjectItemTemplate(template, removeButton) {
1058
- if (removeButton === false) {
1059
- return template;
1060
- }
1061
- return [...template, buildRemoveButton(removeButton)];
1062
- }
1063
- function buildRemoveButton(config) {
1064
- const buttonConfig = (typeof config === 'object' ? config : {});
1065
- return {
1066
- key: '__remove',
1067
- type: 'remove-array-item',
1068
- label: buttonConfig.label ?? 'Remove',
1069
- ...(buttonConfig.props && { props: buttonConfig.props }),
1070
- };
1071
- }
1072
-
1073
1154
  function createSlot() {
1074
1155
  let counter = 0;
1075
1156
  const templates = new Map();
@@ -1113,529 +1194,134 @@ function createSlot() {
1113
1194
  itemOrder.length = 0;
1114
1195
  counter = 0;
1115
1196
  primitiveFieldKey.set(undefined);
1116
- },
1117
- };
1118
- }
1119
- class ArrayItemRegistryService {
1120
- slots = new Map();
1121
- slotFor(arrayPath) {
1122
- let slot = this.slots.get(arrayPath);
1123
- if (!slot) {
1124
- slot = createSlot();
1125
- this.slots.set(arrayPath, slot);
1126
- }
1127
- return slot;
1128
- }
1129
- clearAll() {
1130
- for (const slot of this.slots.values())
1131
- slot.clear();
1132
- this.slots.clear();
1133
- }
1134
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: ArrayItemRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1135
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: ArrayItemRegistryService });
1136
- }
1137
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: ArrayItemRegistryService, decorators: [{
1138
- type: Injectable
1139
- }] });
1140
-
1141
- class SchemaRegistryService {
1142
- registeredSchemas = new Map();
1143
- registerSchema(schema) {
1144
- this.registeredSchemas.set(schema.name, schema);
1145
- }
1146
- getSchema(name) {
1147
- return this.registeredSchemas.get(name);
1148
- }
1149
- getAllSchemas() {
1150
- return new Map(this.registeredSchemas);
1151
- }
1152
- clearSchemas() {
1153
- this.registeredSchemas.clear();
1154
- }
1155
- resolveSchema(schema) {
1156
- if (typeof schema === 'string') {
1157
- return this.getSchema(schema) || null;
1158
- }
1159
- return schema;
1160
- }
1161
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: SchemaRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1162
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: SchemaRegistryService });
1163
- }
1164
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: SchemaRegistryService, decorators: [{
1165
- type: Injectable
1166
- }] });
1167
-
1168
- const DEV_MODE = typeof ngDevMode === 'undefined' || !!ngDevMode;
1169
-
1170
- const DEFAULT_DEBOUNCE_MS = 500;
1171
- function createDebouncedEffect(source, callback, options) {
1172
- const { ms = DEFAULT_DEBOUNCE_MS, injector } = options;
1173
- const source$ = toObservable(source, { injector }).pipe(debounceTime(ms), distinctUntilChanged(), tap((value) => callback(value)));
1174
- return toSignal(source$, { injector });
1175
- }
1176
- function createDebouncedSignal(source, options) {
1177
- const { ms = DEFAULT_DEBOUNCE_MS, injector } = options;
1178
- const source$ = toObservable(source, { injector }).pipe(debounceTime(ms), distinctUntilChanged());
1179
- return toSignal(source$, { injector });
1180
- }
1181
-
1182
- function getConfigTrigger(config) {
1183
- if (!isStateLogicConfig(config)) {
1184
- return 'onChange';
1185
- }
1186
- return config.trigger ?? 'onChange';
1187
- }
1188
- function getConfigDebounceMs(config) {
1189
- if (!isStateLogicConfig(config)) {
1190
- return undefined;
1191
- }
1192
- if (config.trigger === 'debounced') {
1193
- return config.debounceMs;
1194
- }
1195
- return undefined;
1196
- }
1197
- function applyLogic(config, fieldPath) {
1198
- if (isDerivationLogicConfig(config))
1199
- return;
1200
- if (!isStateLogicConfig(config)) {
1201
- if (DEV_MODE) {
1202
- console.warn(`[Dynamic Forms] Unrecognized logic config type '${config.type}' in applyLogic. ` +
1203
- 'This config will be ignored. If this is a new logic type, ensure it is handled explicitly.');
1204
- }
1205
- return;
1206
- }
1207
- const path = fieldPath;
1208
- const trigger = getConfigTrigger(config);
1209
- const debounceMs = getConfigDebounceMs(config) ?? DEFAULT_DEBOUNCE_MS;
1210
- if (typeof config.condition === 'boolean') {
1211
- applyLogicFn(config.type, path, () => config.condition);
1212
- return;
1213
- }
1214
- if (isFormStateCondition(config.condition)) {
1215
- if (config.condition === 'formInvalid' || config.condition === 'pageInvalid') {
1216
- const reason = config.condition === 'formInvalid'
1217
- ? `it would create a validation cycle against the form's own validity`
1218
- : `a value field has no page context for it to resolve against`;
1219
- throw new DynamicFormError(`The '${config.condition}' form-state condition is not supported on value-field logic because ${reason}. ` +
1220
- `Use 'formSubmitting', or apply this condition to a button or container instead.`);
1221
- }
1222
- applyLogicFn(config.type, path, createFormStateLogicFunction(config.condition));
1223
- return;
1224
- }
1225
- const logicFn = trigger === 'debounced'
1226
- ? createDebouncedLogicFunction(config.condition, debounceMs)
1227
- : createLogicFunction(config.condition);
1228
- applyLogicFn(config.type, path, logicFn);
1229
- }
1230
- function applyLogicFn(type, path, logicFn) {
1231
- switch (type) {
1232
- case 'hidden':
1233
- hidden(path, logicFn);
1234
- break;
1235
- case 'readonly':
1236
- readonly(path, logicFn);
1237
- break;
1238
- case 'disabled':
1239
- disabled(path, logicFn);
1240
- break;
1241
- case 'required':
1242
- required(path, { when: logicFn });
1243
- break;
1244
- default: {
1245
- const _exhaustive = type;
1246
- throw new DynamicFormError(`Unhandled state logic type: ${_exhaustive}`);
1247
- }
1248
- }
1249
- }
1250
- function applyMultipleLogic(configs, fieldPath) {
1251
- configs.forEach((config) => applyLogic(config, fieldPath));
1252
- }
1253
-
1254
- function createTypePredicateFunction(predicate, logger) {
1255
- return (value) => {
1256
- try {
1257
- const result = ExpressionParser.evaluate(predicate, { value });
1258
- return Boolean(result);
1259
- }
1260
- catch (error) {
1261
- logger.error('Error evaluating type predicate:', predicate, error);
1262
- return false;
1263
- }
1264
- };
1265
- }
1266
-
1267
- function applySchema(config, fieldPath) {
1268
- const logger = inject(DynamicFormLogger);
1269
- const schemaRegistry = inject(SchemaRegistryService);
1270
- const schema = schemaRegistry.resolveSchema(config.schema);
1271
- if (!schema) {
1272
- const availableSchemas = Array.from(schemaRegistry.getAllSchemas().keys()).join(', ') || '<none>';
1273
- logger.error(`Schema not found: '${config.schema}'. ` +
1274
- `Available schemas: ${availableSchemas}. ` +
1275
- `Ensure the schema is registered in your schema registry.`);
1276
- return;
1277
- }
1278
- const schemaFn = createSchemaFunction(schema, logger);
1279
- const path = fieldPath;
1280
- switch (config.type) {
1281
- case 'apply':
1282
- apply(path, schemaFn);
1283
- break;
1284
- case 'applyWhen':
1285
- if (config.condition) {
1286
- const conditionFn = createLogicFunction(config.condition);
1287
- applyWhen(path, conditionFn, schemaFn);
1288
- }
1289
- break;
1290
- case 'applyWhenValue':
1291
- if (config.typePredicate) {
1292
- const predicate = createTypePredicateFunction(config.typePredicate, logger);
1293
- applyWhenValue(path, predicate, schemaFn);
1294
- }
1295
- break;
1296
- case 'applyEach':
1297
- applyEach(path, schemaFn);
1298
- break;
1299
- }
1300
- }
1301
- function createSchemaFunction(schema, logger) {
1302
- return (path) => {
1303
- schema.validators?.forEach((validatorConfig) => {
1304
- applyValidator(validatorConfig, path);
1305
- });
1306
- schema.logic?.forEach((logicConfig) => {
1307
- applyLogic(logicConfig, path);
1308
- });
1309
- schema.subSchemas?.forEach((subSchemaConfig) => {
1310
- applySchema(subSchemaConfig, path);
1311
- });
1312
- };
1313
- }
1314
-
1315
- function combineAncestorHiddenLogics(logics) {
1316
- if (logics.length === 0)
1317
- return undefined;
1318
- if (logics.length === 1)
1319
- return logics[0];
1320
- return (ctx) => logics.some((fn) => fn(ctx));
1321
- }
1322
- function resolveFieldOwnContext(fieldDef, parentContext) {
1323
- if (fieldDef.validateWhenHidden === undefined)
1324
- return parentContext;
1325
- if (fieldDef.validateWhenHidden === parentContext.validateWhenHidden)
1326
- return parentContext;
1327
- return {
1328
- ...parentContext,
1329
- validateWhenHidden: fieldDef.validateWhenHidden,
1330
- };
1331
- }
1332
- function resolveDescendantContext(fieldDef, ownContext) {
1333
- let ancestorAlwaysHidden = ownContext.ancestorAlwaysHidden;
1334
- let ancestorHiddenLogics = ownContext.ancestorHiddenLogics;
1335
- let mutated = false;
1336
- if (fieldDef.hidden === true && !ancestorAlwaysHidden) {
1337
- ancestorAlwaysHidden = true;
1338
- mutated = true;
1339
- }
1340
- if (fieldDef.logic) {
1341
- for (const logic of fieldDef.logic) {
1342
- if (!isStateLogicConfig(logic) || logic.type !== 'hidden')
1343
- continue;
1344
- const condition = logic.condition;
1345
- if (condition === true) {
1346
- if (!ancestorAlwaysHidden) {
1347
- ancestorAlwaysHidden = true;
1348
- mutated = true;
1349
- }
1350
- continue;
1351
- }
1352
- if (condition === false)
1353
- continue;
1354
- if (typeof condition === 'string')
1355
- continue;
1356
- const fn = createLogicFunction(condition);
1357
- ancestorHiddenLogics = ancestorHiddenLogics.length === 0 ? [fn] : [...ancestorHiddenLogics, fn];
1358
- mutated = true;
1359
- }
1360
- }
1361
- if (!mutated)
1362
- return ownContext;
1363
- return {
1364
- validateWhenHidden: ownContext.validateWhenHidden,
1365
- ancestorAlwaysHidden,
1366
- ancestorHiddenLogics,
1367
- };
1368
- }
1369
-
1370
- const DEFAULT_FIELD_TREE_MAPPING_CONTEXT = {
1371
- validateWhenHidden: false,
1372
- ancestorAlwaysHidden: false,
1373
- ancestorHiddenLogics: [],
1374
- };
1375
- function applyStringValidators(path, config) {
1376
- if (config.email) {
1377
- email(path);
1378
- }
1379
- if (config.minLength !== undefined) {
1380
- minLength(path, config.minLength);
1381
- }
1382
- if (config.maxLength !== undefined) {
1383
- maxLength(path, config.maxLength);
1384
- }
1385
- if (config.pattern) {
1386
- let regex;
1387
- if (typeof config.pattern === 'string') {
1388
- try {
1389
- regex = new RegExp(config.pattern);
1390
- }
1391
- catch (e) {
1392
- throw new DynamicFormError(`Invalid regex pattern: '${config.pattern}' — ${e instanceof Error ? e.message : String(e)}`);
1393
- }
1394
- }
1395
- else {
1396
- regex = config.pattern;
1397
- }
1398
- pattern(path, regex);
1399
- }
1400
- }
1401
- function applyNumberValidators(path, config) {
1402
- if (config.min !== undefined) {
1403
- min(path, config.min);
1404
- }
1405
- if (config.max !== undefined) {
1406
- max(path, config.max);
1407
- }
1408
- }
1409
- function getStringValidationConfig(fieldDef) {
1410
- if (!fieldDef.email && fieldDef.minLength === undefined && fieldDef.maxLength === undefined && !fieldDef.pattern) {
1411
- return undefined;
1412
- }
1413
- return {
1414
- email: fieldDef.email,
1415
- minLength: fieldDef.minLength,
1416
- maxLength: fieldDef.maxLength,
1417
- pattern: fieldDef.pattern,
1418
- };
1419
- }
1420
- function getNumberValidationConfig(fieldDef) {
1421
- const minVal = fieldDef.min ?? fieldDef.minValue;
1422
- const maxVal = fieldDef.max ?? fieldDef.maxValue;
1423
- if (minVal === undefined && maxVal === undefined) {
1424
- return undefined;
1425
- }
1426
- return { min: minVal, max: maxVal };
1427
- }
1428
- function mapFieldToForm(fieldDef, fieldPath, inheritedContext = DEFAULT_FIELD_TREE_MAPPING_CONTEXT) {
1429
- const ownContext = resolveFieldOwnContext(fieldDef, inheritedContext);
1430
- if (isPageField(fieldDef) || isRowField(fieldDef) || isGenericContainerField(fieldDef)) {
1431
- const descendantContext = resolveDescendantContext(fieldDef, ownContext);
1432
- mapContainerChildren(fieldDef.fields, fieldPath, descendantContext);
1433
- return;
1434
- }
1435
- if (isGroupField(fieldDef)) {
1436
- const descendantContext = resolveDescendantContext(fieldDef, ownContext);
1437
- mapContainerChildren(fieldDef.fields, fieldPath, descendantContext);
1438
- return;
1439
- }
1440
- if (isArrayField(fieldDef)) {
1441
- const descendantContext = resolveDescendantContext(fieldDef, ownContext);
1442
- mapArrayFieldToForm(fieldDef, fieldPath, descendantContext);
1443
- return;
1444
- }
1445
- mapLeafField(fieldDef, fieldPath, ownContext);
1446
- }
1447
- function mapContainerChildren(fields, parentPath, inheritedContext) {
1448
- if (!fields)
1449
- return;
1450
- const pathRecord = parentPath;
1451
- for (const field of fields) {
1452
- if (isPageField(field) || isRowField(field) || isGenericContainerField(field)) {
1453
- const layoutOwn = resolveFieldOwnContext(field, inheritedContext);
1454
- const layoutDescendant = resolveDescendantContext(field, layoutOwn);
1455
- mapContainerChildren(field.fields, parentPath, layoutDescendant);
1456
- continue;
1457
- }
1458
- if (!field.key)
1459
- continue;
1460
- const childPath = pathRecord[field.key];
1461
- if (childPath) {
1462
- mapFieldToForm(field, childPath, inheritedContext);
1463
- }
1464
- }
1465
- }
1466
- function isValidationStateLogic(config) {
1467
- return isStateLogicConfig(config) && config.type === 'required';
1468
- }
1469
- function mapLeafField(fieldDef, fieldPath, context) {
1470
- const validationField = fieldDef;
1471
- const path = fieldPath;
1472
- applyFieldState(fieldDef, path);
1473
- if (validationField.logic) {
1474
- for (const config of validationField.logic) {
1475
- if (!isValidationStateLogic(config)) {
1476
- applyLogic(config, fieldPath);
1477
- }
1478
- }
1479
- }
1480
- if (validationField.schemas) {
1481
- for (const config of validationField.schemas) {
1482
- applySchema(config, fieldPath);
1483
- }
1484
- }
1485
- const applyValidation = (validationPath) => {
1486
- applySimpleValidationRules(validationField, validationPath);
1487
- if (validationField.validators) {
1488
- for (const config of validationField.validators) {
1489
- applyValidator(config, validationPath);
1490
- }
1491
- }
1492
- if (validationField.logic) {
1493
- for (const config of validationField.logic) {
1494
- if (isValidationStateLogic(config)) {
1495
- applyLogic(config, validationPath);
1496
- }
1497
- }
1498
- }
1197
+ },
1499
1198
  };
1500
- if (context.validateWhenHidden) {
1501
- applyValidation(path);
1502
- return;
1503
- }
1504
- if (context.ancestorAlwaysHidden) {
1505
- return;
1506
- }
1507
- const ancestorHiddenLogic = combineAncestorHiddenLogics(context.ancestorHiddenLogics);
1508
- applyWhen(path, (ctx) => {
1509
- if (ctx.state.hidden())
1510
- return false;
1511
- if (ancestorHiddenLogic && ancestorHiddenLogic(ctx))
1512
- return false;
1513
- return true;
1514
- }, schema((subPath) => applyValidation(subPath)));
1515
1199
  }
1516
- function applySimpleValidationRules(fieldDef, path) {
1517
- if (fieldDef.required) {
1518
- required(path);
1519
- }
1520
- const stringConfig = getStringValidationConfig(fieldDef);
1521
- if (stringConfig) {
1522
- applyStringValidators(path, stringConfig);
1200
+ class ArrayItemRegistryService {
1201
+ slots = new Map();
1202
+ slotFor(arrayPath) {
1203
+ let slot = this.slots.get(arrayPath);
1204
+ if (!slot) {
1205
+ slot = createSlot();
1206
+ this.slots.set(arrayPath, slot);
1207
+ }
1208
+ return slot;
1523
1209
  }
1524
- const numberConfig = getNumberValidationConfig(fieldDef);
1525
- if (numberConfig) {
1526
- applyNumberValidators(path, numberConfig);
1210
+ clearAll() {
1211
+ for (const slot of this.slots.values())
1212
+ slot.clear();
1213
+ this.slots.clear();
1527
1214
  }
1215
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: ArrayItemRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1216
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: ArrayItemRegistryService });
1528
1217
  }
1529
- function applyFieldState(fieldDef, path) {
1530
- if (fieldDef.disabled) {
1531
- disabled(path);
1532
- }
1533
- if (fieldDef.readonly) {
1534
- readonly(path);
1535
- }
1536
- if (fieldDef.hidden) {
1537
- hidden(path, () => true);
1218
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: ArrayItemRegistryService, decorators: [{
1219
+ type: Injectable
1220
+ }] });
1221
+
1222
+ class SchemaRegistryService {
1223
+ registeredSchemas = new Map();
1224
+ registerSchema(schema) {
1225
+ this.registeredSchemas.set(schema.name, schema);
1538
1226
  }
1539
- }
1540
- function mapArrayFieldToForm(arrayField, fieldPath, context) {
1541
- if (!isArrayField(arrayField)) {
1542
- return;
1227
+ getSchema(name) {
1228
+ return this.registeredSchemas.get(name);
1543
1229
  }
1544
- if (arrayField.minLength !== undefined) {
1545
- minLength(fieldPath, arrayField.minLength);
1230
+ getAllSchemas() {
1231
+ return new Map(this.registeredSchemas);
1546
1232
  }
1547
- if (arrayField.maxLength !== undefined) {
1548
- maxLength(fieldPath, arrayField.maxLength);
1233
+ clearSchemas() {
1234
+ this.registeredSchemas.clear();
1549
1235
  }
1550
- let itemDefinitions = arrayField.fields;
1551
- if (!itemDefinitions || itemDefinitions.length === 0) {
1552
- const metadataTemplate = getNormalizedArrayMetadata(arrayField)?.template;
1553
- if (metadataTemplate) {
1554
- itemDefinitions = [
1555
- Array.isArray(metadataTemplate) ? [...metadataTemplate] : metadataTemplate,
1556
- ];
1236
+ resolveSchema(schema) {
1237
+ if (typeof schema === 'string') {
1238
+ return this.getSchema(schema) || null;
1557
1239
  }
1240
+ return schema;
1558
1241
  }
1559
- if (!itemDefinitions || itemDefinitions.length === 0) {
1560
- const emptyItemSchema = schema(() => {
1561
- });
1562
- applyEach(fieldPath, emptyItemSchema);
1563
- return;
1242
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: SchemaRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1243
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: SchemaRegistryService });
1244
+ }
1245
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: SchemaRegistryService, decorators: [{
1246
+ type: Injectable
1247
+ }] });
1248
+
1249
+ function inheritContainerExclusion(container, child) {
1250
+ const inheritedHidden = container.hidden === true || child.inheritedExclusionState?.hidden === true;
1251
+ const inheritedDisabled = container.disabled === true || child.inheritedExclusionState?.disabled === true;
1252
+ const inheritedReadonly = container.readonly === true || child.inheritedExclusionState?.readonly === true;
1253
+ const hasInheritedState = inheritedHidden || inheritedDisabled || inheritedReadonly;
1254
+ const containerExcludeConfig = container.excludeValueIfHidden !== undefined ||
1255
+ container.excludeValueIfDisabled !== undefined ||
1256
+ container.excludeValueIfReadonly !== undefined;
1257
+ if (!hasInheritedState && !containerExcludeConfig) {
1258
+ return child;
1564
1259
  }
1565
- let hasPrimitiveItems = false;
1566
- const allObjectFields = [];
1567
- for (const itemDef of itemDefinitions) {
1568
- if (Array.isArray(itemDef)) {
1569
- collectFieldsFromObjectItem(itemDef, allObjectFields);
1260
+ return {
1261
+ ...child,
1262
+ ...(hasInheritedState
1263
+ ? { inheritedExclusionState: { hidden: inheritedHidden, disabled: inheritedDisabled, readonly: inheritedReadonly } }
1264
+ : {}),
1265
+ excludeValueIfHidden: child.excludeValueIfHidden ?? container.excludeValueIfHidden,
1266
+ excludeValueIfDisabled: child.excludeValueIfDisabled ?? container.excludeValueIfDisabled,
1267
+ excludeValueIfReadonly: child.excludeValueIfReadonly ?? container.excludeValueIfReadonly,
1268
+ };
1269
+ }
1270
+ function flattenFields(fields, registry, options = {}) {
1271
+ const result = [];
1272
+ let autoKeyCounter = 0;
1273
+ for (const field of fields) {
1274
+ const valueHandling = getFieldValueHandling(field.type, registry);
1275
+ if (options.preserveRows && (isRowField(field) || isGenericContainerField(field))) {
1276
+ if (field.fields) {
1277
+ const flattenedChildren = flattenFields(normalizeFieldsArray(field.fields), registry, options);
1278
+ const autoPrefix = isRowField(field) ? 'auto_row' : 'auto_container';
1279
+ result.push({
1280
+ ...field,
1281
+ fields: flattenedChildren,
1282
+ key: field.key || `${autoPrefix}_${autoKeyCounter++}`,
1283
+ });
1284
+ }
1570
1285
  }
1571
- else if (isPageField(itemDef) ||
1572
- isRowField(itemDef) ||
1573
- isGenericContainerField(itemDef)) {
1574
- collectFieldsFromObjectItem([itemDef], allObjectFields);
1286
+ else if (valueHandling === 'flatten' && 'fields' in field) {
1287
+ if (field.fields) {
1288
+ const fields = field.fields;
1289
+ const flattenedChildren = flattenFields(normalizeFieldsArray(fields), registry, options);
1290
+ result.push(...flattenedChildren.map((child) => inheritContainerExclusion(field, child)));
1291
+ }
1575
1292
  }
1576
- else {
1577
- hasPrimitiveItems = true;
1293
+ else if (isGroupField(field)) {
1294
+ const childFieldsArray = Object.values(field.fields);
1295
+ const flattenedChildren = flattenFields(childFieldsArray, registry, options);
1296
+ result.push({
1297
+ ...field,
1298
+ fields: flattenedChildren,
1299
+ key: field.key || `auto_group_${autoKeyCounter++}`,
1300
+ });
1578
1301
  }
1579
- }
1580
- if (hasPrimitiveItems && allObjectFields.length === 0) {
1581
- const primitiveItemSchema = schema(() => {
1582
- });
1583
- applyEach(fieldPath, primitiveItemSchema);
1584
- return;
1585
- }
1586
- const itemSchema = schema((itemPath) => {
1587
- const pathRecord = itemPath;
1588
- for (const templateField of allObjectFields) {
1589
- if (isRowField(templateField) || isPageField(templateField) || isGenericContainerField(templateField)) {
1590
- const layoutOwn = resolveFieldOwnContext(templateField, context);
1591
- const layoutDescendant = resolveDescendantContext(templateField, layoutOwn);
1592
- mapContainerChildren(templateField.fields, itemPath, layoutDescendant);
1593
- }
1594
- else if (isGroupField(templateField)) {
1595
- const groupKey = templateField.key;
1596
- const groupOwn = resolveFieldOwnContext(templateField, context);
1597
- const groupDescendant = resolveDescendantContext(templateField, groupOwn);
1598
- if (groupKey) {
1599
- const groupPath = pathRecord[groupKey];
1600
- if (groupPath) {
1601
- mapContainerChildren(templateField.fields, groupPath, groupDescendant);
1602
- }
1603
- }
1604
- else {
1605
- mapContainerChildren(templateField.fields, itemPath, groupDescendant);
1606
- }
1607
- }
1608
- else {
1609
- const fieldKey = templateField.key;
1610
- if (fieldKey) {
1611
- const fieldPathForKey = pathRecord[fieldKey];
1612
- if (fieldPathForKey) {
1613
- mapFieldToForm(templateField, fieldPathForKey, context);
1614
- }
1302
+ else if (isArrayField(field)) {
1303
+ const flattenedItemTemplates = field.fields.map((itemDef) => {
1304
+ if (!Array.isArray(itemDef)) {
1305
+ const flattened = flattenFields([itemDef], registry, options);
1306
+ return flattened[0];
1615
1307
  }
1616
- }
1617
- }
1618
- });
1619
- applyEach(fieldPath, itemSchema);
1620
- }
1621
- function collectFieldsFromObjectItem(itemFields, allFields) {
1622
- const seenKeys = new Set(allFields.map((f) => f.key).filter(Boolean));
1623
- for (const field of itemFields) {
1624
- if (isRowField(field) || isPageField(field) || isGenericContainerField(field)) {
1625
- const containerKey = `__container_${field.type}_${JSON.stringify(field.fields?.map((f) => f.key))}`;
1626
- if (!seenKeys.has(containerKey)) {
1627
- seenKeys.add(containerKey);
1628
- allFields.push(field);
1629
- }
1308
+ return flattenFields([...itemDef], registry, options);
1309
+ });
1310
+ result.push({
1311
+ ...field,
1312
+ fields: flattenedItemTemplates,
1313
+ key: field.key || `auto_array_${autoKeyCounter++}`,
1314
+ });
1630
1315
  }
1631
1316
  else {
1632
- const key = field.key;
1633
- if (key && !seenKeys.has(key)) {
1634
- seenKeys.add(key);
1635
- allFields.push(field);
1636
- }
1317
+ const key = field.key || `auto_field_${autoKeyCounter++}`;
1318
+ result.push({
1319
+ ...field,
1320
+ key,
1321
+ });
1637
1322
  }
1638
1323
  }
1324
+ return result;
1639
1325
  }
1640
1326
 
1641
1327
  function getFieldDefaultValue(field, registry) {
@@ -1730,169 +1416,15 @@ function getFieldDefaultValue(field, registry) {
1730
1416
  }
1731
1417
  }
1732
1418
  return itemValue;
1733
- });
1734
- }
1735
- if (field.type === 'input' && 'props' in field) {
1736
- const props = field.props;
1737
- if (props?.type === 'number') {
1738
- return NaN;
1739
- }
1740
- }
1741
- return '';
1742
- }
1743
-
1744
- function applyFormLevelSchema(path, formLevelSchema) {
1745
- if (isStandardSchemaMarker(formLevelSchema)) {
1746
- validateStandardSchema(path, formLevelSchema.schema);
1747
- }
1748
- else if (typeof formLevelSchema === 'function') {
1749
- formLevelSchema(path);
1750
- }
1751
- }
1752
- function createFormLevelSchema(formLevelSchema) {
1753
- return schema((path) => {
1754
- applyFormLevelSchema(path, formLevelSchema);
1755
- });
1756
- }
1757
-
1758
- const VALIDATION_EXECUTION_DEFAULTS = new InjectionToken('VALIDATION_EXECUTION_DEFAULTS', {
1759
- providedIn: 'root',
1760
- factory: () => ({
1761
- validateWhenHidden: false,
1762
- }),
1763
- });
1764
-
1765
- function createSchemaFromFields(fields, registry, options = {}) {
1766
- const validationExecutionDefaults = inject(VALIDATION_EXECUTION_DEFAULTS);
1767
- const { formLevelSchema } = options;
1768
- const rootContext = {
1769
- validateWhenHidden: options.validateWhenHidden ?? validationExecutionDefaults.validateWhenHidden,
1770
- ancestorAlwaysHidden: false,
1771
- ancestorHiddenLogics: [],
1772
- };
1773
- return schema((path) => {
1774
- for (const fieldDef of fields) {
1775
- const valueHandling = getFieldValueHandling(fieldDef.type, registry);
1776
- if (valueHandling === 'exclude') {
1777
- continue;
1778
- }
1779
- if (valueHandling === 'flatten' && hasChildFields(fieldDef)) {
1780
- const flattenedOwn = resolveFieldOwnContext(fieldDef, rootContext);
1781
- const flattenedContext = resolveDescendantContext(fieldDef, flattenedOwn);
1782
- for (const childField of normalizeFieldsArray(fieldDef.fields)) {
1783
- if (!childField.key)
1784
- continue;
1785
- const childPath = path[childField.key];
1786
- if (childPath) {
1787
- mapFieldToForm(childField, childPath, flattenedContext);
1788
- }
1789
- }
1790
- continue;
1791
- }
1792
- const fieldPath = path[fieldDef.key];
1793
- if (!fieldPath) {
1794
- continue;
1795
- }
1796
- mapFieldToForm(fieldDef, fieldPath, rootContext);
1797
- }
1798
- if (formLevelSchema) {
1799
- applyFormLevelSchema(path, formLevelSchema);
1800
- }
1801
- });
1802
- }
1803
- function fieldsToDefaultValues(fields, registry) {
1804
- const defaultValues = {};
1805
- for (const field of fields) {
1806
- if (!field.key)
1807
- continue;
1808
- const valueHandling = getFieldValueHandling(field.type, registry);
1809
- if (valueHandling === 'flatten') {
1810
- continue;
1811
- }
1812
- const value = getFieldDefaultValue(field, registry);
1813
- if (value !== undefined) {
1814
- defaultValues[field.key] = value;
1815
- }
1816
- }
1817
- return defaultValues;
1818
- }
1819
-
1820
- function inheritContainerExclusion(container, child) {
1821
- const inheritedHidden = container.hidden === true || child.inheritedExclusionState?.hidden === true;
1822
- const inheritedDisabled = container.disabled === true || child.inheritedExclusionState?.disabled === true;
1823
- const inheritedReadonly = container.readonly === true || child.inheritedExclusionState?.readonly === true;
1824
- const hasInheritedState = inheritedHidden || inheritedDisabled || inheritedReadonly;
1825
- const containerExcludeConfig = container.excludeValueIfHidden !== undefined ||
1826
- container.excludeValueIfDisabled !== undefined ||
1827
- container.excludeValueIfReadonly !== undefined;
1828
- if (!hasInheritedState && !containerExcludeConfig) {
1829
- return child;
1830
- }
1831
- return {
1832
- ...child,
1833
- ...(hasInheritedState
1834
- ? { inheritedExclusionState: { hidden: inheritedHidden, disabled: inheritedDisabled, readonly: inheritedReadonly } }
1835
- : {}),
1836
- excludeValueIfHidden: child.excludeValueIfHidden ?? container.excludeValueIfHidden,
1837
- excludeValueIfDisabled: child.excludeValueIfDisabled ?? container.excludeValueIfDisabled,
1838
- excludeValueIfReadonly: child.excludeValueIfReadonly ?? container.excludeValueIfReadonly,
1839
- };
1840
- }
1841
- function flattenFields(fields, registry, options = {}) {
1842
- const result = [];
1843
- let autoKeyCounter = 0;
1844
- for (const field of fields) {
1845
- const valueHandling = getFieldValueHandling(field.type, registry);
1846
- if (options.preserveRows && (isRowField(field) || isGenericContainerField(field))) {
1847
- if (field.fields) {
1848
- const flattenedChildren = flattenFields(normalizeFieldsArray(field.fields), registry, options);
1849
- const autoPrefix = isRowField(field) ? 'auto_row' : 'auto_container';
1850
- result.push({
1851
- ...field,
1852
- fields: flattenedChildren,
1853
- key: field.key || `${autoPrefix}_${autoKeyCounter++}`,
1854
- });
1855
- }
1856
- }
1857
- else if (valueHandling === 'flatten' && 'fields' in field) {
1858
- if (field.fields) {
1859
- const fields = field.fields;
1860
- const flattenedChildren = flattenFields(normalizeFieldsArray(fields), registry, options);
1861
- result.push(...flattenedChildren.map((child) => inheritContainerExclusion(field, child)));
1862
- }
1863
- }
1864
- else if (isGroupField(field)) {
1865
- const childFieldsArray = Object.values(field.fields);
1866
- const flattenedChildren = flattenFields(childFieldsArray, registry, options);
1867
- result.push({
1868
- ...field,
1869
- fields: flattenedChildren,
1870
- key: field.key || `auto_group_${autoKeyCounter++}`,
1871
- });
1872
- }
1873
- else if (isArrayField(field)) {
1874
- const flattenedItemTemplates = field.fields.map((itemDef) => {
1875
- if (!Array.isArray(itemDef)) {
1876
- const flattened = flattenFields([itemDef], registry, options);
1877
- return flattened[0];
1878
- }
1879
- return flattenFields([...itemDef], registry, options);
1880
- });
1881
- result.push({
1882
- ...field,
1883
- fields: flattenedItemTemplates,
1884
- key: field.key || `auto_array_${autoKeyCounter++}`,
1885
- });
1886
- }
1887
- else {
1888
- const key = field.key || `auto_field_${autoKeyCounter++}`;
1889
- result.push({
1890
- ...field,
1891
- key,
1892
- });
1419
+ });
1420
+ }
1421
+ if (field.type === 'input' && 'props' in field) {
1422
+ const props = field.props;
1423
+ if (props?.type === 'number') {
1424
+ return NaN;
1893
1425
  }
1894
1426
  }
1895
- return result;
1427
+ return '';
1896
1428
  }
1897
1429
 
1898
1430
  function createContainerFieldProcessors() {
@@ -2329,60 +1861,6 @@ function isValidFormConfiguration(fields) {
2329
1861
  return FormModeValidator.validateFormConfiguration(fields).isValid;
2330
1862
  }
2331
1863
 
2332
- const COMPONENT_CACHE = new InjectionToken('COMPONENT_CACHE', {
2333
- providedIn: 'root',
2334
- factory: () => new Map(),
2335
- });
2336
- function injectFieldRegistry() {
2337
- const registry = inject(FIELD_REGISTRY);
2338
- const componentCache = inject(COMPONENT_CACHE);
2339
- return {
2340
- getType(name) {
2341
- return registry.get(name);
2342
- },
2343
- hasType(name) {
2344
- return registry.has(name);
2345
- },
2346
- async loadTypeComponent(name) {
2347
- const fieldType = registry.get(name);
2348
- if (!fieldType) {
2349
- throw new DynamicFormError(`Field type "${name}" is not registered`);
2350
- }
2351
- if (!fieldType.loadComponent) {
2352
- return undefined;
2353
- }
2354
- const cached = componentCache.get(name);
2355
- if (cached) {
2356
- return cached;
2357
- }
2358
- try {
2359
- const component = resolveDefaultExport(await fieldType.loadComponent());
2360
- if (component) {
2361
- componentCache.set(name, component);
2362
- }
2363
- return component;
2364
- }
2365
- catch (error) {
2366
- throw new DynamicFormError(`Failed to load component for field type "${name}": ${error}`);
2367
- }
2368
- },
2369
- getLoadedComponent(name) {
2370
- return componentCache.get(name);
2371
- },
2372
- getTypes() {
2373
- return Array.from(registry.values());
2374
- },
2375
- registerTypes(types) {
2376
- types.forEach((type) => {
2377
- registry.set(type.name, type);
2378
- });
2379
- },
2380
- get raw() {
2381
- return registry;
2382
- },
2383
- };
2384
- }
2385
-
2386
1864
  function collectFieldData(fields, data, registry, pathPrefix, collectKeys = true) {
2387
1865
  for (const field of fields) {
2388
1866
  const valueHandling = field.type ? getFieldValueHandling(field.type, registry) : 'include';
@@ -2429,7 +1907,9 @@ function validateRegexPattern(pattern, fieldKey, errors) {
2429
1907
  new RegExp(pattern);
2430
1908
  }
2431
1909
  catch (e) {
2432
- errors.push(`Invalid regex pattern in validator for field '${fieldKey}': '${pattern}' — ${e instanceof Error ? e.message : String(e)}`);
1910
+ errors.push(DEV_MODE
1911
+ ? `Invalid regex pattern in validator for field '${fieldKey}': '${pattern}' — ${e instanceof Error ? e.message : String(e)}`
1912
+ : `Invalid regex pattern: '${pattern}'`);
2433
1913
  }
2434
1914
  }
2435
1915
  function validateNoDuplicateKeys(allKeys) {
@@ -2445,7 +1925,9 @@ function validateNoDuplicateKeys(allKeys) {
2445
1925
  const duplicateList = Array.from(duplicates)
2446
1926
  .map((k) => `'${k}'`)
2447
1927
  .join(', ');
2448
- 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).`);
1928
+ throw new DynamicFormError(DEV_MODE
1929
+ ? `Duplicate field keys detected: ${duplicateList}. Field keys must be unique within their group scope (the same key may appear inside different groups).`
1930
+ : `Duplicate field keys detected: ${duplicateList}.`);
2449
1931
  }
2450
1932
  }
2451
1933
  function validateNoDomIdCollisions(domIds) {
@@ -2461,9 +1943,11 @@ function validateNoDomIdCollisions(domIds) {
2461
1943
  const duplicateList = Array.from(duplicates)
2462
1944
  .map((k) => `'${k}'`)
2463
1945
  .join(', ');
2464
- throw new DynamicFormError(`DOM id collision detected for: ${duplicateList}. Group-nested keys are joined with '_' to form DOM ids ` +
2465
- `(e.g. group 'foo' + leaf 'bar' renders as id='foo_bar'). A separate top-level key like 'foo_bar' or a different ` +
2466
- `group/leaf pair that underscores to the same string will produce duplicate ids. Rename one of the colliding keys.`);
1946
+ throw new DynamicFormError(DEV_MODE
1947
+ ? `DOM id collision detected for: ${duplicateList}. Group-nested keys are joined with '_' to form DOM ids ` +
1948
+ `(e.g. group 'foo' + leaf 'bar' renders as id='foo_bar'). A separate top-level key like 'foo_bar' or a different ` +
1949
+ `group/leaf pair that underscores to the same string will produce duplicate ids. Rename one of the colliding keys.`
1950
+ : `DOM id collision detected for: ${duplicateList}.`);
2467
1951
  }
2468
1952
  }
2469
1953
  function validateFieldTypesRegistered(allTypes, registry, logger) {
@@ -2493,12 +1977,15 @@ function validateFormConfig(fields, registry, logger) {
2493
1977
  const list = Array.from(new Set(data.dottedKeys))
2494
1978
  .map((k) => `'${k}'`)
2495
1979
  .join(', ');
2496
- throw new DynamicFormError(`Field key(s) contain a dot: ${list}. Dotted keys are not supported — express nesting with 'group' fields ` +
2497
- `(e.g. { type: 'group', key: 'address', fields: [{ type: 'input', key: 'city' }] }).`);
1980
+ throw new DynamicFormError(DEV_MODE
1981
+ ? `Field key(s) contain a dot: ${list}. Dotted keys are not supported — express nesting with 'group' fields ` +
1982
+ `(e.g. { type: 'group', key: 'address', fields: [{ type: 'input', key: 'city' }] }).`
1983
+ : `Field key(s) contain a dot: ${list}.`);
2498
1984
  }
2499
1985
  validateNoDuplicateKeys(data.keys);
2500
1986
  validateNoDomIdCollisions(data.domIds);
2501
- validateFieldTypesRegistered(data.types, registry, logger);
1987
+ if (DEV_MODE)
1988
+ validateFieldTypesRegistered(data.types, registry, logger);
2502
1989
  if (data.regexErrors.length > 0) {
2503
1990
  throw new DynamicFormError(data.regexErrors.length === 1
2504
1991
  ? data.regexErrors[0]
@@ -2713,6 +2200,33 @@ function resolveExclusionConfig(global, form, field) {
2713
2200
  excludeValueIfReadonly: field?.excludeValueIfReadonly ?? form?.excludeValueIfReadonly ?? global.excludeValueIfReadonly,
2714
2201
  };
2715
2202
  }
2203
+ function hasEnabledValueExclusion(fields, global, form) {
2204
+ return fields.some((field) => {
2205
+ const resolved = resolveExclusionConfig(global, form, field);
2206
+ if (resolved.excludeValueIfHidden || resolved.excludeValueIfDisabled || resolved.excludeValueIfReadonly) {
2207
+ return true;
2208
+ }
2209
+ return (isGroupField(field) && !!field.fields && hasEnabledValueExclusion(Object.values(field.fields), global, form));
2210
+ });
2211
+ }
2212
+ function filterDeclaredValueShape(rawValue, fields, registry) {
2213
+ const kept = {};
2214
+ for (const field of fields) {
2215
+ const key = field.key;
2216
+ if (!key || !(key in rawValue))
2217
+ continue;
2218
+ const valueHandling = getFieldValueHandling(field.type, registry);
2219
+ if (valueHandling === 'exclude' || valueHandling === 'flatten')
2220
+ continue;
2221
+ const value = rawValue[key];
2222
+ if (isGroupField(field) && field.fields && value !== null && typeof value === 'object' && !Array.isArray(value)) {
2223
+ kept[key] = filterDeclaredValueShape(value, Object.values(field.fields), registry);
2224
+ continue;
2225
+ }
2226
+ kept[key] = value;
2227
+ }
2228
+ return kept;
2229
+ }
2716
2230
  function shouldExcludeField(field, fieldState, config) {
2717
2231
  const state = fieldState();
2718
2232
  const inherited = field.inheritedExclusionState;
@@ -2730,7 +2244,10 @@ function shouldExcludeField(field, fieldState, config) {
2730
2244
  }
2731
2245
  return false;
2732
2246
  }
2733
- function filterFormValue(rawValue, schemaFields, formTree, registry, globalDefaults, formOptions) {
2247
+ function filterFormValue(rawValue, schemaFields, formTree, registry, globalDefaults, formOptions, exclusionEnabled = hasEnabledValueExclusion(schemaFields, globalDefaults, formOptions)) {
2248
+ if (!exclusionEnabled) {
2249
+ return filterDeclaredValueShape(rawValue, schemaFields, registry);
2250
+ }
2734
2251
  const result = {};
2735
2252
  for (const field of schemaFields) {
2736
2253
  const key = field.key;
@@ -2766,7 +2283,7 @@ function filterFormValue(rawValue, schemaFields, formTree, registry, globalDefau
2766
2283
  if (groupValue && typeof groupValue === 'object') {
2767
2284
  const childFields = Object.values(field.fields);
2768
2285
  const nestedTree = fieldState;
2769
- result[key] = filterFormValue(groupValue, childFields, nestedTree, registry, globalDefaults, formOptions);
2286
+ result[key] = filterFormValue(groupValue, childFields, nestedTree, registry, globalDefaults, formOptions, true);
2770
2287
  }
2771
2288
  else {
2772
2289
  result[key] = groupValue;
@@ -3079,7 +2596,7 @@ class FormStateMachine {
3079
2596
  if (!isTransitioningState(state) || state.phase !== Phase.Restoring) {
3080
2597
  return { state, sideEffects: [] };
3081
2598
  }
3082
- if (!state.pendingFormSetup) {
2599
+ if (DEV_MODE && !state.pendingFormSetup) {
3083
2600
  this.config.logger.warn('handleRestoreComplete: pendingFormSetup was not set — falling back to recomputing. ' +
3084
2601
  'This indicates a bug in the transition flow.');
3085
2602
  }
@@ -3142,7 +2659,7 @@ class FormStateMachine {
3142
2659
  const state = untracked(() => this._state());
3143
2660
  if (!isTransitioningState(state) || state.phase !== Phase.Restoring)
3144
2661
  return;
3145
- if (!state.pendingFormSetup) {
2662
+ if (DEV_MODE && !state.pendingFormSetup) {
3146
2663
  this.config.logger.warn('RestoreValues effect: pendingFormSetup was not set — falling back to recomputing.');
3147
2664
  }
3148
2665
  const formSetup = state.pendingFormSetup ?? this.config.createFormSetup(state.pendingConfig);
@@ -3284,13 +2801,6 @@ class FormStateManager {
3284
2801
  return detectFormMode(config.fields || []);
3285
2802
  }, /* @ts-ignore */
3286
2803
  ...(ngDevMode ? [{ debugName: "formModeDetection" }] : /* istanbul ignore next */ []));
3287
- formConfigValidation = computed(() => {
3288
- const config = this.activeConfig();
3289
- if (!config)
3290
- return { isValid: true, errors: [], warnings: [] };
3291
- return FormModeValidator.validateFormConfiguration(config.fields || []);
3292
- }, /* @ts-ignore */
3293
- ...(ngDevMode ? [{ debugName: "formConfigValidation" }] : /* istanbul ignore next */ []));
3294
2804
  effectiveFormOptions = computed(() => {
3295
2805
  const config = this.activeConfig();
3296
2806
  const configOptions = config?.options || {};
@@ -3298,6 +2808,8 @@ class FormStateManager {
3298
2808
  return { ...configOptions, ...inputOptions };
3299
2809
  }, /* @ts-ignore */
3300
2810
  ...(ngDevMode ? [{ debugName: "effectiveFormOptions" }] : /* istanbul ignore next */ []));
2811
+ pendingPageRequest = signal(null, /* @ts-ignore */
2812
+ ...(ngDevMode ? [{ debugName: "pendingPageRequest" }] : /* istanbul ignore next */ []));
3301
2813
  pageFieldDefinitions = computed(() => {
3302
2814
  const config = this.activeConfig();
3303
2815
  const mode = this.formModeDetection().mode;
@@ -3335,21 +2847,70 @@ class FormStateManager {
3335
2847
  return this.createEmptyFormSetup(registry);
3336
2848
  }, /* @ts-ignore */
3337
2849
  ...(ngDevMode ? [{ debugName: "formSetup" }] : /* istanbul ignore next */ []));
2850
+ formSetupConfig = computed(() => {
2851
+ const state = this.machine.state();
2852
+ if (isReadyState(state))
2853
+ return state.config;
2854
+ if (isTransitioningState(state)) {
2855
+ if (state.phase === Phase.Restoring && state.pendingFormSetup) {
2856
+ return state.pendingConfig;
2857
+ }
2858
+ return state.currentConfig;
2859
+ }
2860
+ if (state.type === LifecycleState.Initializing)
2861
+ return state.config;
2862
+ if (state.type === LifecycleState.Uninitialized)
2863
+ return this.activeConfig();
2864
+ return undefined;
2865
+ }, /* @ts-ignore */
2866
+ ...(ngDevMode ? [{ debugName: "formSetupConfig" }] : /* istanbul ignore next */ []));
2867
+ activePageState = linkedSignal({ ...(ngDevMode ? { debugName: "activePageState" } : /* istanbul ignore next */ {}), source: this.activeConfig,
2868
+ computation: (config) => ({ config, initialized: false, index: 0 }) });
3338
2869
  defaultValues = linkedSignal(() => this.formSetup().defaultValues, /* @ts-ignore */
3339
2870
  ...(ngDevMode ? [{ debugName: "defaultValues" }] : /* istanbul ignore next */ []));
3340
2871
  validKeys = computed(() => {
3341
2872
  const schemaFields = this.formSetup().schemaFields;
3342
2873
  if (!schemaFields || schemaFields.length === 0)
3343
2874
  return undefined;
3344
- return new Set(schemaFields.map((f) => f.key).filter((key) => key !== undefined));
2875
+ const keys = new Set();
2876
+ for (const field of schemaFields) {
2877
+ if (field.key === undefined)
2878
+ continue;
2879
+ keys.add(field.key);
2880
+ }
2881
+ return keys;
3345
2882
  }, /* @ts-ignore */
3346
2883
  ...(ngDevMode ? [{ debugName: "validKeys" }] : /* istanbul ignore next */ []));
3347
- fieldStateSnapshot = computed(() => {
2884
+ boundValueExclusionEnabled = computed(() => {
2885
+ const setup = this.formSetup();
2886
+ if (!setup.schemaFields || setup.schemaFields.length === 0)
2887
+ return false;
2888
+ const options = this.effectiveFormOptions();
2889
+ return hasEnabledValueExclusion(setup.schemaFields, BOUND_VALUE_EXCLUSION_BASELINE, {
2890
+ excludeValueIfHidden: options.excludeValueIfHidden,
2891
+ excludeValueIfDisabled: options.excludeValueIfDisabled,
2892
+ excludeValueIfReadonly: options.excludeValueIfReadonly,
2893
+ });
2894
+ }, /* @ts-ignore */
2895
+ ...(ngDevMode ? [{ debugName: "boundValueExclusionEnabled" }] : /* istanbul ignore next */ []));
2896
+ valueExclusionEnabled = computed(() => {
3348
2897
  const setup = this.formSetup();
3349
2898
  if (!setup.schemaFields || setup.schemaFields.length === 0)
2899
+ return false;
2900
+ const options = this.effectiveFormOptions();
2901
+ return hasEnabledValueExclusion(setup.schemaFields, this.valueExclusionDefaults, {
2902
+ excludeValueIfHidden: options.excludeValueIfHidden,
2903
+ excludeValueIfDisabled: options.excludeValueIfDisabled,
2904
+ excludeValueIfReadonly: options.excludeValueIfReadonly,
2905
+ });
2906
+ }, /* @ts-ignore */
2907
+ ...(ngDevMode ? [{ debugName: "valueExclusionEnabled" }] : /* istanbul ignore next */ []));
2908
+ fieldStateSnapshot = computed(() => {
2909
+ const schemaFields = this.formSetup().schemaFields;
2910
+ if (!this.formSchemaReady() || !this.boundValueExclusionEnabled() || !schemaFields || schemaFields.length === 0)
3350
2911
  return {};
3351
2912
  const snapshot = {};
3352
- collectFieldStateSnapshot(setup.schemaFields, asFieldTreeRecord(this.form()), [], snapshot);
2913
+ collectFieldStateSnapshot(schemaFields, asFieldTreeRecord(this.form()), [], snapshot);
3353
2914
  return snapshot;
3354
2915
  }, /* @ts-ignore */
3355
2916
  ...(ngDevMode ? [{ debugName: "fieldStateSnapshot" }] : /* istanbul ignore next */ []));
@@ -3357,8 +2918,22 @@ class FormStateManager {
3357
2918
  ...(ngDevMode ? [{ debugName: "excludedValueStore" }] : /* istanbul ignore next */ []));
3358
2919
  prevFieldStateSnapshot = signal({}, /* @ts-ignore */
3359
2920
  ...(ngDevMode ? [{ debugName: "prevFieldStateSnapshot" }] : /* istanbul ignore next */ []));
2921
+ lastOutwardValue = { current: undefined, pending: false };
2922
+ lastExternalValue = { current: undefined };
2923
+ externalValue = computed(() => {
2924
+ const incoming = this.deps.value();
2925
+ if (this.lastOutwardValue.pending && incoming === this.lastOutwardValue.current) {
2926
+ this.lastOutwardValue.pending = false;
2927
+ return this.lastExternalValue.current;
2928
+ }
2929
+ this.lastOutwardValue.pending = false;
2930
+ this.lastExternalValue.current = incoming;
2931
+ return incoming;
2932
+ }, /* @ts-ignore */
2933
+ ...(ngDevMode ? [{ debugName: "externalValue" }] : /* istanbul ignore next */ []));
3360
2934
  entity = linkedSignal(() => {
3361
- const inputValue = this.deps.value();
2935
+ this.externalValue();
2936
+ const inputValue = untracked(() => this.deps.value());
3362
2937
  const defaults = this.defaultValues();
3363
2938
  const keys = this.validKeys();
3364
2939
  const saved = this.excludedValueStore();
@@ -3378,22 +2953,62 @@ class FormStateManager {
3378
2953
  debugName: 'FormStateManager.entity',
3379
2954
  equal: isEqual,
3380
2955
  });
3381
- formSchema = computed(() => runInInjectionContext(this.injector, () => {
2956
+ loadFormSchemaService = injectAsync(() => import('./ng-forge-dynamic-forms-form-schema.service-Dx5Dlb15.mjs'));
2957
+ formSchemaRequest = computed(() => {
3382
2958
  const setup = this.formSetup();
3383
2959
  const config = this.activeConfig();
3384
- if (!config)
2960
+ if (!config || (!(setup.schemaFields?.length > 0) && !config.schema))
2961
+ return undefined;
2962
+ return {
2963
+ fields: setup.schemaFields ?? [],
2964
+ registry: setup.registry,
2965
+ formLevelSchema: config.schema,
2966
+ validateWhenHidden: this.effectiveFormOptions().validateWhenHidden,
2967
+ };
2968
+ }, /* @ts-ignore */
2969
+ ...(ngDevMode ? [{ debugName: "formSchemaRequest" }] : /* istanbul ignore next */ []));
2970
+ formSchemaResource = resource({ ...(ngDevMode ? { debugName: "formSchemaResource" } : /* istanbul ignore next */ {}), params: () => this.formSchemaRequest(),
2971
+ loader: async ({ params }) => {
2972
+ const schemaService = await this.loadFormSchemaService();
2973
+ return runInInjectionContext(this.injector, () => schemaService.create(params));
2974
+ } });
2975
+ schemaCache = { current: undefined };
2976
+ trackSchemaCache = computed(() => {
2977
+ const request = this.formSchemaRequest();
2978
+ if (!request) {
2979
+ this.schemaCache.current = undefined;
3385
2980
  return undefined;
3386
- if (setup.schemaFields?.length) {
3387
- return createSchemaFromFields(setup.schemaFields, setup.registry, {
3388
- formLevelSchema: config.schema,
3389
- validateWhenHidden: this.effectiveFormOptions().validateWhenHidden,
3390
- });
3391
2981
  }
3392
- if (config.schema) {
3393
- return createFormLevelSchema(config.schema);
2982
+ if (this.formSchemaResource.status() === 'error') {
2983
+ this.schemaCache.current = undefined;
2984
+ return undefined;
3394
2985
  }
3395
- return undefined;
3396
- }), /* @ts-ignore */
2986
+ if (this.formSchemaResource.hasValue()) {
2987
+ this.schemaCache.current = { schema: this.formSchemaResource.value(), request };
2988
+ }
2989
+ return this.schemaCache.current;
2990
+ }, /* @ts-ignore */
2991
+ ...(ngDevMode ? [{ debugName: "trackSchemaCache" }] : /* istanbul ignore next */ []));
2992
+ isSchemaCurrent = computed(() => {
2993
+ if (this.formSetupConfig() !== this.activeConfig())
2994
+ return false;
2995
+ const request = this.formSchemaRequest();
2996
+ if (!request)
2997
+ return true;
2998
+ return this.trackSchemaCache()?.request === request;
2999
+ }, /* @ts-ignore */
3000
+ ...(ngDevMode ? [{ debugName: "isSchemaCurrent" }] : /* istanbul ignore next */ []));
3001
+ formSchemaReady = computed(() => {
3002
+ if (this.formSetupConfig() !== this.activeConfig())
3003
+ return false;
3004
+ if (!this.formSchemaRequest())
3005
+ return true;
3006
+ if (this.formSchemaResource.status() === 'error')
3007
+ return true;
3008
+ return this.trackSchemaCache() !== undefined;
3009
+ }, /* @ts-ignore */
3010
+ ...(ngDevMode ? [{ debugName: "formSchemaReady" }] : /* istanbul ignore next */ []));
3011
+ formSchema = computed(() => this.trackSchemaCache()?.schema, /* @ts-ignore */
3397
3012
  ...(ngDevMode ? [{ debugName: "formSchema" }] : /* istanbul ignore next */ []));
3398
3013
  form = computed(() => {
3399
3014
  const schema = this.formSchema();
@@ -3424,13 +3039,16 @@ class FormStateManager {
3424
3039
  ...(ngDevMode ? [{ debugName: "fieldSignalContext" }] : /* istanbul ignore next */ []));
3425
3040
  formInstance = computed(() => this.form()(), /* @ts-ignore */
3426
3041
  ...(ngDevMode ? [{ debugName: "formInstance" }] : /* istanbul ignore next */ []));
3427
- formValue = computed(() => this.formInstance().value(), /* @ts-ignore */
3042
+ modelValue = computed(() => this.formInstance().value(), /* @ts-ignore */
3043
+ ...(ngDevMode ? [{ debugName: "modelValue" }] : /* istanbul ignore next */ []));
3044
+ formValue = computed(() => this.modelValue(), /* @ts-ignore */
3428
3045
  ...(ngDevMode ? [{ debugName: "formValue" }] : /* istanbul ignore next */ []));
3429
3046
  filteredFormValue = computed(() => {
3430
- const rawValue = this.formValue();
3047
+ const rawValue = this.modelValue();
3431
3048
  const setup = this.formSetup();
3049
+ const schemaFields = setup.schemaFields;
3432
3050
  const options = this.effectiveFormOptions();
3433
- if (!setup.schemaFields || setup.schemaFields.length === 0) {
3051
+ if (!this.formSchemaReady() || !schemaFields || schemaFields.length === 0) {
3434
3052
  return rawValue;
3435
3053
  }
3436
3054
  const formTree = this.form();
@@ -3440,14 +3058,15 @@ class FormStateManager {
3440
3058
  excludeValueIfReadonly: options.excludeValueIfReadonly,
3441
3059
  };
3442
3060
  const fieldTreeRecord = asFieldTreeRecord(formTree);
3443
- return filterFormValue(rawValue, setup.schemaFields, fieldTreeRecord, setup.registry, this.valueExclusionDefaults, formOptions);
3061
+ return filterFormValue(rawValue, schemaFields, fieldTreeRecord, setup.registry, this.valueExclusionDefaults, formOptions, this.valueExclusionEnabled());
3444
3062
  }, /* @ts-ignore */
3445
3063
  ...(ngDevMode ? [{ debugName: "filteredFormValue" }] : /* istanbul ignore next */ []));
3446
3064
  boundFormValue = computed(() => {
3447
- const rawValue = this.formValue();
3065
+ const rawValue = this.modelValue();
3448
3066
  const setup = this.formSetup();
3067
+ const schemaFields = setup.schemaFields;
3449
3068
  const options = this.effectiveFormOptions();
3450
- if (!setup.schemaFields || setup.schemaFields.length === 0) {
3069
+ if (!this.formSchemaReady() || !schemaFields || schemaFields.length === 0) {
3451
3070
  return rawValue;
3452
3071
  }
3453
3072
  const formTree = this.form();
@@ -3457,26 +3076,26 @@ class FormStateManager {
3457
3076
  excludeValueIfReadonly: options.excludeValueIfReadonly,
3458
3077
  };
3459
3078
  const fieldTreeRecord = asFieldTreeRecord(formTree);
3460
- return filterFormValue(rawValue, setup.schemaFields, fieldTreeRecord, setup.registry, BOUND_VALUE_EXCLUSION_BASELINE, formOptions);
3079
+ return filterFormValue(rawValue, schemaFields, fieldTreeRecord, setup.registry, BOUND_VALUE_EXCLUSION_BASELINE, formOptions, this.boundValueExclusionEnabled());
3461
3080
  }, /* @ts-ignore */
3462
3081
  ...(ngDevMode ? [{ debugName: "boundFormValue" }] : /* istanbul ignore next */ []));
3463
- valid = computed(() => this.formInstance().valid(), /* @ts-ignore */
3082
+ valid = computed(() => this.formSchemaReady() && this.formSchemaResource.status() !== 'error' && this.formInstance().valid(), /* @ts-ignore */
3464
3083
  ...(ngDevMode ? [{ debugName: "valid" }] : /* istanbul ignore next */ []));
3465
- invalid = computed(() => this.formInstance().invalid(), /* @ts-ignore */
3084
+ invalid = computed(() => !this.formSchemaReady() || this.formSchemaResource.status() === 'error' || this.formInstance().invalid(), /* @ts-ignore */
3466
3085
  ...(ngDevMode ? [{ debugName: "invalid" }] : /* istanbul ignore next */ []));
3467
- dirty = computed(() => this.formInstance().dirty(), /* @ts-ignore */
3086
+ dirty = computed(() => this.formSchemaReady() && this.formInstance().dirty(), /* @ts-ignore */
3468
3087
  ...(ngDevMode ? [{ debugName: "dirty" }] : /* istanbul ignore next */ []));
3469
- touched = computed(() => this.formInstance().touched(), /* @ts-ignore */
3088
+ touched = computed(() => this.formSchemaReady() && this.formInstance().touched(), /* @ts-ignore */
3470
3089
  ...(ngDevMode ? [{ debugName: "touched" }] : /* istanbul ignore next */ []));
3471
- errors = computed(() => this.formInstance().errors(), /* @ts-ignore */
3090
+ errors = computed(() => (this.formSchemaReady() ? this.formInstance().errors() : []), /* @ts-ignore */
3472
3091
  ...(ngDevMode ? [{ debugName: "errors" }] : /* istanbul ignore next */ []));
3473
3092
  disabled = computed(() => {
3474
3093
  const optionsDisabled = this.effectiveFormOptions().disabled;
3475
- const formDisabled = this.formInstance().disabled();
3094
+ const formDisabled = this.formSchemaReady() && this.formInstance().disabled();
3476
3095
  return optionsDisabled ?? formDisabled;
3477
3096
  }, /* @ts-ignore */
3478
3097
  ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
3479
- submitting = computed(() => this.formInstance().submitting(), /* @ts-ignore */
3098
+ submitting = computed(() => this.formSchemaReady() && this.formInstance().submitting(), /* @ts-ignore */
3480
3099
  ...(ngDevMode ? [{ debugName: "submitting" }] : /* istanbul ignore next */ []));
3481
3100
  fieldsSource = computed(() => {
3482
3101
  const state = this.machine.state();
@@ -3591,6 +3210,10 @@ class FormStateManager {
3591
3210
  }), takeUntilDestroyed(this.destroyRef));
3592
3211
  this.reset$ = this.eventBus.on('form-reset').pipe(map(() => undefined), takeUntilDestroyed(this.destroyRef));
3593
3212
  this.cleared$ = this.eventBus.on('form-clear').pipe(map(() => undefined), takeUntilDestroyed(this.destroyRef));
3213
+ this.eventBus
3214
+ .on('go-to-page')
3215
+ .pipe(takeUntilDestroyed(this.destroyRef))
3216
+ .subscribe((event) => this.pendingPageRequest.set({ index: event.pageIndex, options: event.options }));
3594
3217
  this.machine = createFormStateMachine({
3595
3218
  injector: this.injector,
3596
3219
  destroyRef: this.destroyRef,
@@ -3654,6 +3277,11 @@ class FormStateManager {
3654
3277
  this.eventBus.dispatch(FormSubmitEvent);
3655
3278
  }
3656
3279
  setupEffects() {
3280
+ explicitEffect([this.formSchemaResource.error], ([error]) => {
3281
+ if (error) {
3282
+ this.logger.error('Failed to compile the form schema; rendering without validation and blocking submission.', error);
3283
+ }
3284
+ });
3657
3285
  explicitEffect([this.deps.config], ([config]) => {
3658
3286
  this.fieldLoadingErrors.set([]);
3659
3287
  const state = this.machine.currentState;
@@ -3680,127 +3308,543 @@ class FormStateManager {
3680
3308
  }
3681
3309
  }
3682
3310
  }
3683
- this.prevFieldStateSnapshot.set({ ...snapshot });
3684
- if (newSaved !== currentSaved && !isEqual(newSaved, currentSaved)) {
3685
- this.excludedValueStore.set(newSaved);
3686
- }
3687
- });
3688
- let warnedAboutMissingArrayRegistry = false;
3689
- explicitEffect([this.formSetup], () => {
3690
- this.prevFieldStateSnapshot.set({});
3691
- if (Object.keys(this.excludedValueStore()).length > 0) {
3692
- this.excludedValueStore.set({});
3693
- }
3694
- if (this.arrayItemRegistry) {
3695
- this.arrayItemRegistry.clearAll();
3696
- }
3697
- else if (DEV_MODE && !warnedAboutMissingArrayRegistry) {
3698
- warnedAboutMissingArrayRegistry = true;
3699
- this.logger.warn('ArrayItemRegistryService was not provided to FormStateManager. ' +
3700
- 'Array fields will lose dynamically-added item templates across hide/show cycles. ' +
3701
- 'This usually means DynamicForm was bootstrapped without provideDynamicFormDI() — ' +
3702
- 'check your providers chain.');
3703
- }
3311
+ this.prevFieldStateSnapshot.set({ ...snapshot });
3312
+ if (newSaved !== currentSaved && !isEqual(newSaved, currentSaved)) {
3313
+ this.excludedValueStore.set(newSaved);
3314
+ }
3315
+ });
3316
+ let warnedAboutMissingArrayRegistry = false;
3317
+ explicitEffect([this.formSetup], () => {
3318
+ this.prevFieldStateSnapshot.set({});
3319
+ if (Object.keys(this.excludedValueStore()).length > 0) {
3320
+ this.excludedValueStore.set({});
3321
+ }
3322
+ if (this.arrayItemRegistry) {
3323
+ this.arrayItemRegistry.clearAll();
3324
+ }
3325
+ else if (DEV_MODE && !warnedAboutMissingArrayRegistry) {
3326
+ warnedAboutMissingArrayRegistry = true;
3327
+ this.logger.warn('ArrayItemRegistryService was not provided to FormStateManager. ' +
3328
+ 'Array fields will lose dynamically-added item templates across hide/show cycles. ' +
3329
+ 'This usually means DynamicForm was bootstrapped without provideDynamicFormDI() — ' +
3330
+ 'check your providers chain.');
3331
+ }
3332
+ });
3333
+ explicitEffect([this.boundFormValue], ([currentBound]) => {
3334
+ const currentValue = this.deps.value();
3335
+ if (!isEqual(currentBound, currentValue)) {
3336
+ this.lastOutwardValue.current = currentBound;
3337
+ this.lastOutwardValue.pending = true;
3338
+ this.deps.value.set(currentBound);
3339
+ }
3340
+ });
3341
+ if (DEV_MODE) {
3342
+ explicitEffect([this.activeConfig], ([config]) => {
3343
+ if (!config)
3344
+ return;
3345
+ const validation = FormModeValidator.validateFormConfiguration(config.fields || []);
3346
+ if (!validation.isValid) {
3347
+ this.logger.error('Invalid form configuration:', validation.errors);
3348
+ }
3349
+ if (validation.warnings.length > 0) {
3350
+ this.logger.warn('Form configuration warnings:', validation.warnings);
3351
+ }
3352
+ });
3353
+ }
3354
+ }
3355
+ setupEventHandlers() {
3356
+ this.eventBus
3357
+ .on('form-reset')
3358
+ .pipe(takeUntilDestroyed(this.destroyRef))
3359
+ .subscribe(() => this.reset());
3360
+ this.eventBus
3361
+ .on('form-clear')
3362
+ .pipe(takeUntilDestroyed(this.destroyRef))
3363
+ .subscribe(() => this.clear());
3364
+ }
3365
+ registerValidatorsFromConfig({ customFnConfig, schemas }) {
3366
+ if (schemas) {
3367
+ schemas.forEach((schema) => {
3368
+ this.schemaRegistry.registerSchema(schema);
3369
+ });
3370
+ }
3371
+ if (!customFnConfig) {
3372
+ return;
3373
+ }
3374
+ if (customFnConfig.customFunctions) {
3375
+ Object.entries(customFnConfig.customFunctions).forEach(([name, fn]) => {
3376
+ this.functionRegistry.registerCustomFunction(name, fn);
3377
+ });
3378
+ }
3379
+ if (customFnConfig.derivations) {
3380
+ this.functionRegistry.setDerivationFunctions(customFnConfig.derivations);
3381
+ }
3382
+ if (customFnConfig.asyncDerivations) {
3383
+ this.functionRegistry.setAsyncDerivationFunctions(customFnConfig.asyncDerivations);
3384
+ }
3385
+ if (customFnConfig.asyncConditions) {
3386
+ this.functionRegistry.setAsyncConditionFunctions(customFnConfig.asyncConditions);
3387
+ }
3388
+ this.functionRegistry.setValidators(customFnConfig.validators);
3389
+ this.functionRegistry.setAsyncValidators(customFnConfig.asyncValidators);
3390
+ this.functionRegistry.setHttpValidators(customFnConfig.httpValidators);
3391
+ }
3392
+ createFormSetupFromConfig(fields, mode, registry) {
3393
+ const normalizedFields = normalizeSimplifiedArrays(fields);
3394
+ validateFormConfig(normalizedFields, registry, this.logger);
3395
+ const source = this.deps.source?.() ?? 'inline';
3396
+ const { fields: sanitizedFields, warnings: addonWarnings } = walkAndValidateAddons(normalizedFields, registry, this.addonKindRegistry, source);
3397
+ if (DEV_MODE) {
3398
+ const nextKeys = new Set();
3399
+ for (const w of addonWarnings) {
3400
+ const key = addonWarningKey(w);
3401
+ nextKeys.add(key);
3402
+ if (!this.lastAddonWarningKeys.has(key)) {
3403
+ this.logger.warn(formatAddonWarning(w));
3404
+ }
3405
+ }
3406
+ this.lastAddonWarningKeys = nextKeys;
3407
+ }
3408
+ const validatedFields = sanitizedFields;
3409
+ const flattenedFields = this.fieldProcessors.memoizedFlattenFields(validatedFields, registry);
3410
+ const flattenedFieldsForRendering = this.fieldProcessors.memoizedFlattenFieldsForRendering(validatedFields, registry);
3411
+ const fieldsById = this.fieldProcessors.memoizedKeyBy(flattenedFields);
3412
+ const defaultValues = this.fieldProcessors.memoizedDefaultValues(fieldsById, registry);
3413
+ const fieldsToRender = mode === 'paged' ? [] : flattenedFieldsForRendering;
3414
+ return {
3415
+ fields: fieldsToRender,
3416
+ schemaFields: flattenedFields,
3417
+ originalFields: fields,
3418
+ defaultValues,
3419
+ mode,
3420
+ registry,
3421
+ };
3422
+ }
3423
+ createEmptyFormSetup(registry) {
3424
+ return {
3425
+ fields: [],
3426
+ schemaFields: [],
3427
+ defaultValues: {},
3428
+ mode: 'non-paged',
3429
+ registry,
3430
+ };
3431
+ }
3432
+ ngOnDestroy() {
3433
+ this.machine.dispatch({ type: Action.Destroy });
3434
+ }
3435
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: FormStateManager, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3436
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: FormStateManager });
3437
+ }
3438
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: FormStateManager, decorators: [{
3439
+ type: Injectable
3440
+ }], ctorParameters: () => [] });
3441
+
3442
+ class PageOrchestratorComponent {
3443
+ eventBus = inject(EventBus);
3444
+ fieldContextRegistry = inject(FieldContextRegistryService);
3445
+ functionRegistry = inject(FunctionRegistryService);
3446
+ formOptions = inject(FORM_OPTIONS, { optional: true });
3447
+ globalPreloadWindow = inject(PAGE_PRELOAD_WINDOW);
3448
+ stateManager = inject(FormStateManager, { optional: true });
3449
+ pendingPageRequest = computed(() => this.stateManager?.pendingPageRequest() ?? null, /* @ts-ignore */
3450
+ ...(ngDevMode ? [{ debugName: "pendingPageRequest" }] : /* istanbul ignore next */ []));
3451
+ schemaCurrent = computed(() => this.stateManager?.isSchemaCurrent() ?? true, /* @ts-ignore */
3452
+ ...(ngDevMode ? [{ debugName: "schemaCurrent" }] : /* istanbul ignore next */ []));
3453
+ configScope = computed(() => this.stateManager?.activeConfig(), /* @ts-ignore */
3454
+ ...(ngDevMode ? [{ debugName: "configScope" }] : /* istanbul ignore next */ []));
3455
+ pageFields = input.required(/* @ts-ignore */
3456
+ ...(ngDevMode ? [{ debugName: "pageFields" }] : /* istanbul ignore next */ []));
3457
+ form = input.required(/* @ts-ignore */
3458
+ ...(ngDevMode ? [{ debugName: "form" }] : /* istanbul ignore next */ []));
3459
+ fieldSignalContext = input.required(/* @ts-ignore */
3460
+ ...(ngDevMode ? [{ debugName: "fieldSignalContext" }] : /* istanbul ignore next */ []));
3461
+ pageHiddenStates = computed(() => {
3462
+ const pages = this.pageFields();
3463
+ return pages.map((page) => this.evaluatePageHidden(page));
3464
+ }, /* @ts-ignore */
3465
+ ...(ngDevMode ? [{ debugName: "pageHiddenStates" }] : /* istanbul ignore next */ []));
3466
+ visiblePageIndices = computed(() => {
3467
+ const hiddenStates = this.pageHiddenStates();
3468
+ return hiddenStates
3469
+ .map((hidden, index) => ({ index, hidden }))
3470
+ .filter((item) => !item.hidden)
3471
+ .map((item) => item.index);
3472
+ }, /* @ts-ignore */
3473
+ ...(ngDevMode ? [{ debugName: "visiblePageIndices" }] : /* istanbul ignore next */ []));
3474
+ activePageOwnership = linkedSignal({ ...(ngDevMode ? { debugName: "activePageOwnership" } : /* istanbul ignore next */ {}), source: this.configScope,
3475
+ computation: (config, previous) => {
3476
+ const totalPages = this.pageFields().length;
3477
+ if (totalPages === 0)
3478
+ return { config, initialized: true, index: 0 };
3479
+ return untracked(() => {
3480
+ const owned = this.stateManager?.activePageState();
3481
+ const mayRestore = previous === undefined || previous.source === config;
3482
+ if (mayRestore &&
3483
+ owned !== undefined &&
3484
+ owned.config === config &&
3485
+ owned.initialized &&
3486
+ owned.index >= 0 &&
3487
+ owned.index < totalPages) {
3488
+ return { config, initialized: true, index: owned.index };
3489
+ }
3490
+ if (this.initialPage().validate && !this.schemaCurrent()) {
3491
+ return { config, initialized: false, index: 0 };
3492
+ }
3493
+ return { config, initialized: true, index: this.resolveInitialLanding() };
3494
+ });
3495
+ } });
3496
+ currentPageIndex = computed(() => this.activePageOwnership().index, /* @ts-ignore */
3497
+ ...(ngDevMode ? [{ debugName: "currentPageIndex" }] : /* istanbul ignore next */ []));
3498
+ setCurrentPageIndex(index) {
3499
+ this.activePageOwnership.update((ownership) => ({ ...ownership, initialized: true, index }));
3500
+ }
3501
+ resolveInitialLanding() {
3502
+ const { index, validate } = this.initialPage();
3503
+ const visible = this.visiblePageIndices();
3504
+ if (index === 0 || visible.length === 0)
3505
+ return 0;
3506
+ const target = visible.includes(index) ? index : this.findNearestVisiblePage(index, visible);
3507
+ if (target <= 0)
3508
+ return Math.max(target, 0);
3509
+ if (!validate)
3510
+ return target;
3511
+ const firstInvalid = visible.filter((i) => i < target).find((i) => !this.isPageValid(i));
3512
+ return firstInvalid ?? target;
3513
+ }
3514
+ initialPage = computed(() => {
3515
+ const raw = this.formOptions?.()?.initialPage;
3516
+ const totalPages = this.pageFields().length;
3517
+ const requested = typeof raw === 'number' ? raw : raw?.index;
3518
+ const validate = typeof raw === 'number' ? false : (raw?.validate ?? false);
3519
+ if (typeof requested !== 'number' || !Number.isFinite(requested) || requested < 0 || totalPages === 0) {
3520
+ return { index: 0, validate: false };
3521
+ }
3522
+ return { index: Math.min(Math.trunc(requested), totalPages - 1), validate };
3523
+ }, /* @ts-ignore */
3524
+ ...(ngDevMode ? [{ debugName: "initialPage" }] : /* istanbul ignore next */ []));
3525
+ state = computed(() => {
3526
+ const currentIndex = this.currentPageIndex();
3527
+ const totalPages = this.pageFields().length;
3528
+ const visibleIndices = this.visiblePageIndices();
3529
+ const currentVisiblePosition = visibleIndices.indexOf(currentIndex);
3530
+ const isFirstVisiblePage = currentVisiblePosition === 0 || currentVisiblePosition === -1;
3531
+ const isLastVisiblePage = currentVisiblePosition >= visibleIndices.length - 1;
3532
+ return {
3533
+ currentPageIndex: currentIndex,
3534
+ totalPages,
3535
+ isFirstPage: isFirstVisiblePage,
3536
+ isLastPage: isLastVisiblePage,
3537
+ };
3538
+ }, /* @ts-ignore */
3539
+ ...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
3540
+ currentPageValid = computed(() => (this.stateManager?.isSchemaCurrent() ?? true) && this.isPageValid(this.currentPageIndex()), /* @ts-ignore */
3541
+ ...(ngDevMode ? [{ debugName: "currentPageValid" }] : /* istanbul ignore next */ []));
3542
+ isPageValid(pageIndex) {
3543
+ const pages = this.pageFields();
3544
+ const form = this.form();
3545
+ if (pageIndex < 0 || pageIndex >= pages.length) {
3546
+ return true;
3547
+ }
3548
+ const leafKeys = collectLeafFieldKeys(pages[pageIndex].fields || []);
3549
+ for (const fieldKey of leafKeys) {
3550
+ const field = form[fieldKey];
3551
+ if (field && typeof field === 'function') {
3552
+ const fieldState = field();
3553
+ if (fieldState && typeof fieldState.valid === 'function' && !fieldState.valid()) {
3554
+ return false;
3555
+ }
3556
+ }
3557
+ }
3558
+ return true;
3559
+ }
3560
+ extendedFieldSignalContext = computed(() => ({
3561
+ ...this.fieldSignalContext(),
3562
+ currentPageValid: this.currentPageValid,
3563
+ }), /* @ts-ignore */
3564
+ ...(ngDevMode ? [{ debugName: "extendedFieldSignalContext" }] : /* istanbul ignore next */ []));
3565
+ preloadWindow = computed(() => {
3566
+ const perForm = this.formOptions?.()?.pagePreloadWindow;
3567
+ return clampWindowSize(perForm, this.globalPreloadWindow);
3568
+ }, /* @ts-ignore */
3569
+ ...(ngDevMode ? [{ debugName: "preloadWindow" }] : /* istanbul ignore next */ []));
3570
+ pageRenderStates = computed(() => {
3571
+ const activeIndex = this.currentPageIndex();
3572
+ const hiddenStates = this.pageHiddenStates();
3573
+ const preloadWindow = this.preloadWindow();
3574
+ return this.pageFields().map((field, index) => ({
3575
+ field,
3576
+ index,
3577
+ hidden: hiddenStates[index],
3578
+ active: index === activeIndex,
3579
+ preload: index !== activeIndex && Math.abs(index - activeIndex) <= preloadWindow,
3580
+ }));
3581
+ }, /* @ts-ignore */
3582
+ ...(ngDevMode ? [{ debugName: "pageRenderStates" }] : /* istanbul ignore next */ []));
3583
+ constructor() {
3584
+ this.setupEventListeners();
3585
+ const preloader = injectFormComponentPreloader();
3586
+ explicitEffect([this.pageRenderStates], ([states]) => {
3587
+ preloader.preloadFields(states.filter((page) => !page.hidden && (page.active || page.preload)).map((page) => page.field));
3704
3588
  });
3705
- explicitEffect([this.boundFormValue], ([currentBound]) => {
3706
- const currentValue = this.deps.value();
3707
- if (!isEqual(currentBound, currentValue)) {
3708
- this.deps.value.set(currentBound);
3709
- }
3589
+ explicitEffect([this.schemaCurrent, this.pageFields], ([schemaCurrent]) => {
3590
+ const ownership = this.activePageOwnership();
3591
+ if (!schemaCurrent || ownership.initialized)
3592
+ return;
3593
+ this.activePageOwnership.set({ ...ownership, initialized: true, index: this.resolveInitialLanding() });
3710
3594
  });
3711
- explicitEffect([this.formConfigValidation], ([validation]) => {
3712
- if (!validation.isValid) {
3713
- this.logger.error('Invalid form configuration:', validation.errors);
3714
- }
3715
- if (validation.warnings.length > 0) {
3716
- this.logger.warn('Form configuration warnings:', validation.warnings);
3595
+ explicitEffect([this.state, this.visiblePageIndices], ([state, visibleIndices]) => {
3596
+ const currentVisiblePosition = visibleIndices.indexOf(state.currentPageIndex);
3597
+ if (currentVisiblePosition === -1 && visibleIndices.length > 0) {
3598
+ const nearest = this.findNearestVisiblePage(state.currentPageIndex, visibleIndices);
3599
+ if (nearest !== -1) {
3600
+ this.executePageChange(nearest);
3601
+ }
3717
3602
  }
3718
3603
  });
3719
3604
  }
3720
- setupEventHandlers() {
3721
- this.eventBus
3722
- .on('form-reset')
3723
- .pipe(takeUntilDestroyed(this.destroyRef))
3724
- .subscribe(() => this.reset());
3725
- this.eventBus
3726
- .on('form-clear')
3727
- .pipe(takeUntilDestroyed(this.destroyRef))
3728
- .subscribe(() => this.clear());
3605
+ navigateToNextPage() {
3606
+ const disableWhenPageInvalid = this.formOptions?.()?.nextButton?.disableWhenPageInvalid ?? true;
3607
+ if (disableWhenPageInvalid && !this.currentPageValid()) {
3608
+ return {
3609
+ success: false,
3610
+ newPageIndex: this.state().currentPageIndex,
3611
+ error: 'Current page has invalid fields',
3612
+ };
3613
+ }
3614
+ const currentState = this.state();
3615
+ const visibleIndices = this.visiblePageIndices();
3616
+ if (currentState.isLastPage) {
3617
+ return {
3618
+ success: false,
3619
+ newPageIndex: currentState.currentPageIndex,
3620
+ error: 'Already on the last visible page',
3621
+ };
3622
+ }
3623
+ const currentVisiblePosition = visibleIndices.indexOf(currentState.currentPageIndex);
3624
+ if (currentVisiblePosition === -1 || currentVisiblePosition >= visibleIndices.length - 1) {
3625
+ return {
3626
+ success: false,
3627
+ newPageIndex: currentState.currentPageIndex,
3628
+ error: 'No next visible page available',
3629
+ };
3630
+ }
3631
+ const nextVisiblePageIndex = visibleIndices[currentVisiblePosition + 1];
3632
+ return this.executePageChange(nextVisiblePageIndex);
3729
3633
  }
3730
- registerValidatorsFromConfig({ customFnConfig, schemas }) {
3731
- if (schemas) {
3732
- schemas.forEach((schema) => {
3733
- this.schemaRegistry.registerSchema(schema);
3734
- });
3634
+ navigateToPreviousPage() {
3635
+ const currentState = this.state();
3636
+ const visibleIndices = this.visiblePageIndices();
3637
+ if (currentState.isFirstPage) {
3638
+ return {
3639
+ success: false,
3640
+ newPageIndex: currentState.currentPageIndex,
3641
+ error: 'Already on the first visible page',
3642
+ };
3735
3643
  }
3736
- if (!customFnConfig) {
3737
- return;
3644
+ const currentVisiblePosition = visibleIndices.indexOf(currentState.currentPageIndex);
3645
+ if (currentVisiblePosition <= 0) {
3646
+ return {
3647
+ success: false,
3648
+ newPageIndex: currentState.currentPageIndex,
3649
+ error: 'No previous visible page available',
3650
+ };
3738
3651
  }
3739
- if (customFnConfig.customFunctions) {
3740
- Object.entries(customFnConfig.customFunctions).forEach(([name, fn]) => {
3741
- this.functionRegistry.registerCustomFunction(name, fn);
3742
- });
3652
+ const prevVisiblePageIndex = visibleIndices[currentVisiblePosition - 1];
3653
+ return this.executePageChange(prevVisiblePageIndex);
3654
+ }
3655
+ navigateToPage(pageIndex, options) {
3656
+ const currentIndex = this.state().currentPageIndex;
3657
+ const visibleIndices = this.visiblePageIndices();
3658
+ if (pageIndex <= currentIndex || !visibleIndices.includes(pageIndex)) {
3659
+ return this.executePageChange(pageIndex);
3743
3660
  }
3744
- if (customFnConfig.derivations) {
3745
- this.functionRegistry.setDerivationFunctions(customFnConfig.derivations);
3661
+ if (options?.validate === false) {
3662
+ return this.executePageChange(pageIndex);
3746
3663
  }
3747
- if (customFnConfig.asyncDerivations) {
3748
- this.functionRegistry.setAsyncDerivationFunctions(customFnConfig.asyncDerivations);
3664
+ const disableWhenPageInvalid = this.formOptions?.()?.nextButton?.disableWhenPageInvalid ?? true;
3665
+ if (!disableWhenPageInvalid) {
3666
+ return this.executePageChange(pageIndex);
3749
3667
  }
3750
- if (customFnConfig.asyncConditions) {
3751
- this.functionRegistry.setAsyncConditionFunctions(customFnConfig.asyncConditions);
3668
+ const crossedPages = visibleIndices.filter((index) => index >= currentIndex && index < pageIndex);
3669
+ const firstInvalidPage = crossedPages.find((index) => !this.isPageValid(index));
3670
+ if (firstInvalidPage === undefined) {
3671
+ return this.executePageChange(pageIndex);
3752
3672
  }
3753
- this.functionRegistry.setValidators(customFnConfig.validators);
3754
- this.functionRegistry.setAsyncValidators(customFnConfig.asyncValidators);
3755
- this.functionRegistry.setHttpValidators(customFnConfig.httpValidators);
3673
+ this.executePageChange(firstInvalidPage);
3674
+ return {
3675
+ success: false,
3676
+ newPageIndex: firstInvalidPage,
3677
+ error: `Cannot navigate to page ${pageIndex}: page ${firstInvalidPage} has invalid fields`,
3678
+ };
3756
3679
  }
3757
- createFormSetupFromConfig(fields, mode, registry) {
3758
- const normalizedFields = normalizeSimplifiedArrays(fields);
3759
- validateFormConfig(normalizedFields, registry, this.logger);
3760
- const source = this.deps.source?.() ?? 'inline';
3761
- const { fields: sanitizedFields, warnings: addonWarnings } = walkAndValidateAddons(normalizedFields, registry, this.addonKindRegistry, source);
3762
- const nextKeys = new Set();
3763
- for (const w of addonWarnings) {
3764
- const key = addonWarningKey(w);
3765
- nextKeys.add(key);
3766
- if (!this.lastAddonWarningKeys.has(key)) {
3767
- this.logger.warn(formatAddonWarning(w));
3680
+ executePageChange(pageIndex) {
3681
+ const currentState = this.state();
3682
+ const totalPages = currentState.totalPages;
3683
+ if (pageIndex < 0 || pageIndex >= totalPages) {
3684
+ return {
3685
+ success: false,
3686
+ newPageIndex: currentState.currentPageIndex,
3687
+ error: `Invalid page index: ${pageIndex}. Valid range is 0 to ${totalPages - 1}`,
3688
+ };
3689
+ }
3690
+ const visibleIndices = this.visiblePageIndices();
3691
+ if (!visibleIndices.includes(pageIndex)) {
3692
+ return {
3693
+ success: false,
3694
+ newPageIndex: currentState.currentPageIndex,
3695
+ error: `Cannot navigate to hidden page at index ${pageIndex}. Visible pages: [${visibleIndices.join(', ')}]`,
3696
+ };
3697
+ }
3698
+ if (pageIndex === currentState.currentPageIndex) {
3699
+ return {
3700
+ success: true,
3701
+ newPageIndex: pageIndex,
3702
+ };
3703
+ }
3704
+ const previousIndex = currentState.currentPageIndex;
3705
+ this.setCurrentPageIndex(pageIndex);
3706
+ this.eventBus.dispatch(PageChangeEvent, pageIndex, totalPages, previousIndex);
3707
+ return {
3708
+ success: true,
3709
+ newPageIndex: pageIndex,
3710
+ };
3711
+ }
3712
+ findNearestVisiblePage(currentIndex, visibleIndices) {
3713
+ let nearest = -1;
3714
+ let minDistance = Infinity;
3715
+ for (const idx of visibleIndices) {
3716
+ const distance = Math.abs(idx - currentIndex);
3717
+ if (distance < minDistance || (distance === minDistance && idx > nearest)) {
3718
+ minDistance = distance;
3719
+ nearest = idx;
3720
+ }
3721
+ }
3722
+ return nearest;
3723
+ }
3724
+ setupEventListeners() {
3725
+ this.eventBus
3726
+ .on('next-page')
3727
+ .pipe(takeUntilDestroyed())
3728
+ .subscribe(() => {
3729
+ this.navigateToNextPage();
3730
+ });
3731
+ this.eventBus
3732
+ .on('previous-page')
3733
+ .pipe(takeUntilDestroyed())
3734
+ .subscribe(() => {
3735
+ this.navigateToPreviousPage();
3736
+ });
3737
+ explicitEffect([this.pendingPageRequest], ([request]) => {
3738
+ if (!request)
3739
+ return;
3740
+ this.stateManager?.pendingPageRequest.set(null);
3741
+ this.navigateToPage(request.index, request.options);
3742
+ });
3743
+ explicitEffect([this.state], ([state]) => this.eventBus.dispatch(PagerStateEvent, state));
3744
+ explicitEffect([this.activePageOwnership], ([ownership]) => this.stateManager?.activePageState.set(ownership));
3745
+ }
3746
+ evaluatePageHidden(page) {
3747
+ if (!page.logic || page.logic.length === 0) {
3748
+ return false;
3749
+ }
3750
+ const hiddenLogic = page.logic.filter((l) => l.type === 'hidden');
3751
+ if (hiddenLogic.length === 0) {
3752
+ return false;
3753
+ }
3754
+ for (const logic of hiddenLogic) {
3755
+ if (typeof logic.condition === 'boolean') {
3756
+ if (logic.condition) {
3757
+ return true;
3758
+ }
3759
+ continue;
3760
+ }
3761
+ const condition = logic.condition;
3762
+ const context = this.fieldContextRegistry.createDisplayOnlyContext(page.key || '', this.functionRegistry.getCustomFunctions());
3763
+ if (evaluateCondition(condition, context)) {
3764
+ return true;
3768
3765
  }
3769
3766
  }
3770
- this.lastAddonWarningKeys = nextKeys;
3771
- const validatedFields = sanitizedFields;
3772
- const flattenedFields = this.fieldProcessors.memoizedFlattenFields(validatedFields, registry);
3773
- const flattenedFieldsForRendering = this.fieldProcessors.memoizedFlattenFieldsForRendering(validatedFields, registry);
3774
- const fieldsById = this.fieldProcessors.memoizedKeyBy(flattenedFields);
3775
- const defaultValues = this.fieldProcessors.memoizedDefaultValues(fieldsById, registry);
3776
- const fieldsToRender = mode === 'paged' ? [] : flattenedFieldsForRendering;
3777
- return {
3778
- fields: fieldsToRender,
3779
- schemaFields: flattenedFields,
3780
- originalFields: fields,
3781
- defaultValues,
3782
- mode,
3783
- registry,
3784
- };
3785
- }
3786
- createEmptyFormSetup(registry) {
3787
- return {
3788
- fields: [],
3789
- schemaFields: [],
3790
- defaultValues: {},
3791
- mode: 'non-paged',
3792
- registry,
3793
- };
3767
+ return false;
3794
3768
  }
3795
- ngOnDestroy() {
3796
- this.machine.dispatch({ type: Action.Destroy });
3769
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: PageOrchestratorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3770
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.5", 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: [
3771
+ {
3772
+ provide: FIELD_SIGNAL_CONTEXT,
3773
+ useFactory: (orchestrator) => orchestrator.extendedFieldSignalContext(),
3774
+ deps: [PageOrchestratorComponent],
3775
+ },
3776
+ ], ngImport: i0, template: `
3777
+ @for (page of pageRenderStates(); track page.field.key) {
3778
+ <!--
3779
+ Skip pages hidden by 'hidden' logic conditions entirely. FormStateManager
3780
+ derives schema/value/validators from config (not from mounted components),
3781
+ so unmounting a hidden page has no effect on form state — only on render +
3782
+ CD cost. This is the page-level equivalent of the existing field-level
3783
+ \`@if (!field.hidden())\` gate in page-field.component.ts.
3784
+ -->
3785
+ @if (!page.hidden) {
3786
+ @if (page.active || page.preload) {
3787
+ @defer (when page.active; on idle) {
3788
+ <section page-field [field]="page.field" [key]="page.field.key" [pageIndex]="page.index" [isVisible]="page.active"></section>
3789
+ } @placeholder {
3790
+ <div class="df-page-placeholder" [attr.data-page-index]="page.index" [attr.data-page-key]="page.field.key"></div>
3791
+ }
3792
+ } @else {
3793
+ <!--
3794
+ Pages outside the preload window remain lightweight placeholders.
3795
+ Form state derives from config, so leaving their components unmounted
3796
+ does not affect validity, derivations, or navigation. The active page
3797
+ renders directly; configured neighbours preload declaratively on idle.
3798
+ -->
3799
+ <div class="df-page-placeholder" [attr.data-page-index]="page.index" [attr.data-page-key]="page.field.key"></div>
3800
+ }
3801
+ }
3797
3802
  }
3798
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: FormStateManager, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3799
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: FormStateManager });
3803
+ `, isInline: true, styles: [":host{display:block;position:relative;width:100%}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, deferBlockDependencies: [() => [/* @ts-ignore */
3804
+ import('./ng-forge-dynamic-forms-page-field.component-DPKJcwUO.mjs').then(m => m.default)]] });
3800
3805
  }
3801
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: FormStateManager, decorators: [{
3802
- type: Injectable
3803
- }], ctorParameters: () => [] });
3806
+ i0.ɵɵngDeclareClassMetadataAsync({ minVersion: "18.0.0", version: "22.1.5", ngImport: i0, type: PageOrchestratorComponent, resolveDeferredDeps: () => [/* @ts-ignore */
3807
+ import('./ng-forge-dynamic-forms-page-field.component-DPKJcwUO.mjs').then(m => m.default)], resolveMetadata: PageFieldComponent => ({ decorators: [{
3808
+ type: Component,
3809
+ args: [{ selector: 'div[page-orchestrator]', imports: [PageFieldComponent], template: `
3810
+ @for (page of pageRenderStates(); track page.field.key) {
3811
+ <!--
3812
+ Skip pages hidden by 'hidden' logic conditions entirely. FormStateManager
3813
+ derives schema/value/validators from config (not from mounted components),
3814
+ so unmounting a hidden page has no effect on form state — only on render +
3815
+ CD cost. This is the page-level equivalent of the existing field-level
3816
+ \`@if (!field.hidden())\` gate in page-field.component.ts.
3817
+ -->
3818
+ @if (!page.hidden) {
3819
+ @if (page.active || page.preload) {
3820
+ @defer (when page.active; on idle) {
3821
+ <section page-field [field]="page.field" [key]="page.field.key" [pageIndex]="page.index" [isVisible]="page.active"></section>
3822
+ } @placeholder {
3823
+ <div class="df-page-placeholder" [attr.data-page-index]="page.index" [attr.data-page-key]="page.field.key"></div>
3824
+ }
3825
+ } @else {
3826
+ <!--
3827
+ Pages outside the preload window remain lightweight placeholders.
3828
+ Form state derives from config, so leaving their components unmounted
3829
+ does not affect validity, derivations, or navigation. The active page
3830
+ renders directly; configured neighbours preload declaratively on idle.
3831
+ -->
3832
+ <div class="df-page-placeholder" [attr.data-page-index]="page.index" [attr.data-page-key]="page.field.key"></div>
3833
+ }
3834
+ }
3835
+ }
3836
+ `, host: {
3837
+ class: 'df-page-orchestrator',
3838
+ '[attr.data-current-page]': 'state().currentPageIndex',
3839
+ '[attr.data-total-pages]': 'state().totalPages',
3840
+ }, providers: [
3841
+ {
3842
+ provide: FIELD_SIGNAL_CONTEXT,
3843
+ useFactory: (orchestrator) => orchestrator.extendedFieldSignalContext(),
3844
+ deps: [PageOrchestratorComponent],
3845
+ },
3846
+ ], changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;position:relative;width:100%}\n"] }]
3847
+ }], 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 }] }] } }) });
3804
3848
 
3805
3849
  function traverseFieldsWithContext(fields, context, visitor, hooks) {
3806
3850
  for (const field of fields) {
@@ -3879,14 +3923,14 @@ function createDerivationRenderGate() {
3879
3923
  if (!has || engine() !== 'idle')
3880
3924
  return;
3881
3925
  engine.set('loading');
3882
- pendingTasks.run(() => import('./ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-CkqvXXs_.mjs')
3926
+ pendingTasks.run(() => import('./ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-CTOTwzzw.mjs')
3883
3927
  .then(({ bootstrapDerivationOrchestrator }) => {
3884
3928
  runInInjectionContext(injector, () => bootstrapDerivationOrchestrator());
3885
3929
  engine.set('wired');
3886
3930
  })
3887
3931
  .catch((error) => {
3888
3932
  engine.set('failed');
3889
- logger.error('[Dynamic Forms] Failed to load the derivation engine; derivations will not apply.', error);
3933
+ logger.error('Failed to load the derivation engine; derivations will not apply.', error);
3890
3934
  }));
3891
3935
  });
3892
3936
  return computed(() => {
@@ -3897,6 +3941,68 @@ function createDerivationRenderGate() {
3897
3941
  });
3898
3942
  }
3899
3943
 
3944
+ const WEB_MCP_ENABLED = new InjectionToken('WEB_MCP_ENABLED', {
3945
+ providedIn: 'root',
3946
+ factory: () => false,
3947
+ });
3948
+
3949
+ const WEB_MCP_GATE = new InjectionToken('WEB_MCP_GATE');
3950
+ function createWebMcpGate() {
3951
+ const enabled = inject(WEB_MCP_ENABLED);
3952
+ const isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
3953
+ const stateManager = inject(FormStateManager);
3954
+ const injector = inject(Injector);
3955
+ const destroyRef = inject(DestroyRef);
3956
+ const logger = inject(DynamicFormLogger);
3957
+ if (!enabled || !isBrowser)
3958
+ return computed(() => 'disabled');
3959
+ const registration = computed(() => {
3960
+ const options = stateManager.effectiveFormOptions().webMcp;
3961
+ return options ? { options: options, setup: stateManager.formSetup() } : undefined;
3962
+ }, /* @ts-ignore */
3963
+ ...(ngDevMode ? [{ debugName: "registration" }] : /* istanbul ignore next */ []));
3964
+ const status = signal('idle', /* @ts-ignore */
3965
+ ...(ngDevMode ? [{ debugName: "status" }] : /* istanbul ignore next */ []));
3966
+ let epoch;
3967
+ let epochId = 0;
3968
+ const closeEpoch = () => {
3969
+ epoch?.abort();
3970
+ epoch = undefined;
3971
+ };
3972
+ destroyRef.onDestroy(closeEpoch);
3973
+ explicitEffect([registration], ([input]) => {
3974
+ closeEpoch();
3975
+ epochId += 1;
3976
+ const thisEpoch = epochId;
3977
+ if (!input) {
3978
+ status.set('idle');
3979
+ return;
3980
+ }
3981
+ const controller = new AbortController();
3982
+ epoch = controller;
3983
+ status.set('registering');
3984
+ void import('./ng-forge-dynamic-forms-bootstrap-web-mcp-BtjqeJOj.mjs')
3985
+ .then(({ bootstrapWebMcp }) => {
3986
+ if (thisEpoch !== epochId || controller.signal.aborted)
3987
+ return;
3988
+ return runInInjectionContext(injector, () => bootstrapWebMcp(input.options, controller.signal));
3989
+ })
3990
+ .then((result) => {
3991
+ if (thisEpoch !== epochId || controller.signal.aborted)
3992
+ return;
3993
+ if (result)
3994
+ status.set(result);
3995
+ })
3996
+ .catch((error) => {
3997
+ if (thisEpoch !== epochId)
3998
+ return;
3999
+ status.set('failed');
4000
+ logger.error('Failed to load the WebMCP registrar; this form will not be exposed to agents.', error);
4001
+ });
4002
+ });
4003
+ return status.asReadonly();
4004
+ }
4005
+
3900
4006
  const FORM_INITIALIZER = new InjectionToken('FORM_INITIALIZER');
3901
4007
 
3902
4008
  const AUTO_ID_PREFIX = 'df-';
@@ -3929,10 +4035,10 @@ class DynamicFormInstanceRegistry {
3929
4035
  this.slots.delete(Number(id.slice(AUTO_ID_PREFIX.length)));
3930
4036
  this._membership.update((m) => m + 1);
3931
4037
  }
3932
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: DynamicFormInstanceRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3933
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: DynamicFormInstanceRegistry, providedIn: 'root' });
4038
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: DynamicFormInstanceRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
4039
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: DynamicFormInstanceRegistry, providedIn: 'root' });
3934
4040
  }
3935
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: DynamicFormInstanceRegistry, decorators: [{
4041
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: DynamicFormInstanceRegistry, decorators: [{
3936
4042
  type: Injectable,
3937
4043
  args: [{ providedIn: 'root' }]
3938
4044
  }] });
@@ -3983,10 +4089,10 @@ class FormIdPrefixService {
3983
4089
  });
3984
4090
  }
3985
4091
  }
3986
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: FormIdPrefixService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3987
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: FormIdPrefixService });
4092
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: FormIdPrefixService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
4093
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: FormIdPrefixService });
3988
4094
  }
3989
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: FormIdPrefixService, decorators: [{
4095
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: FormIdPrefixService, decorators: [{
3990
4096
  type: Injectable
3991
4097
  }], ctorParameters: () => [] });
3992
4098
 
@@ -4039,6 +4145,7 @@ function coreProviders() {
4039
4145
  deps: [FormStateManager],
4040
4146
  },
4041
4147
  FormIdPrefixService,
4148
+ FieldViewportObserver,
4042
4149
  {
4043
4150
  provide: FORM_ID_PREFIX,
4044
4151
  useFactory: (svc) => svc.prefix,
@@ -4078,6 +4185,7 @@ function provideDynamicFormDI() {
4078
4185
  ...asyncExpressionProviders(),
4079
4186
  ...propertyDerivationProviders(),
4080
4187
  ...derivationProviders(),
4188
+ { provide: WEB_MCP_GATE, useFactory: createWebMcpGate },
4081
4189
  ];
4082
4190
  }
4083
4191
 
@@ -4092,10 +4200,10 @@ class EventDispatcher {
4092
4200
  disconnect() {
4093
4201
  this.bus = undefined;
4094
4202
  }
4095
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: EventDispatcher, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
4096
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: EventDispatcher });
4203
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: EventDispatcher, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
4204
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: EventDispatcher });
4097
4205
  }
4098
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: EventDispatcher, decorators: [{
4206
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: EventDispatcher, decorators: [{
4099
4207
  type: Injectable
4100
4208
  }] });
4101
4209
 
@@ -4103,10 +4211,10 @@ class DfTemplate {
4103
4211
  dfTemplate = input.required(/* @ts-ignore */
4104
4212
  ...(ngDevMode ? [{ debugName: "dfTemplate" }] : /* istanbul ignore next */ []));
4105
4213
  templateRef = inject(TemplateRef);
4106
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: DfTemplate, deps: [], target: i0.ɵɵFactoryTarget.Directive });
4107
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.1", type: DfTemplate, isStandalone: true, selector: "ng-template[dfTemplate]", inputs: { dfTemplate: { classPropertyName: "dfTemplate", publicName: "dfTemplate", isSignal: true, isRequired: true, transformFunction: null } }, exportAs: ["dfTemplate"], ngImport: i0 });
4214
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: DfTemplate, deps: [], target: i0.ɵɵFactoryTarget.Directive });
4215
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.5", type: DfTemplate, isStandalone: true, selector: "ng-template[dfTemplate]", inputs: { dfTemplate: { classPropertyName: "dfTemplate", publicName: "dfTemplate", isSignal: true, isRequired: true, transformFunction: null } }, exportAs: ["dfTemplate"], ngImport: i0 });
4108
4216
  }
4109
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: DfTemplate, decorators: [{
4217
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: DfTemplate, decorators: [{
4110
4218
  type: Directive,
4111
4219
  args: [{
4112
4220
  selector: 'ng-template[dfTemplate]',
@@ -4120,32 +4228,13 @@ class DfFieldTemplateRegistry {
4120
4228
  set(value) {
4121
4229
  this._map.set(value);
4122
4230
  }
4123
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: DfFieldTemplateRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
4124
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: DfFieldTemplateRegistry });
4231
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: DfFieldTemplateRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
4232
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: DfFieldTemplateRegistry });
4125
4233
  }
4126
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: DfFieldTemplateRegistry, decorators: [{
4234
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: DfFieldTemplateRegistry, decorators: [{
4127
4235
  type: Injectable
4128
4236
  }] });
4129
4237
 
4130
- const FIELD_WINDOWING = new InjectionToken('FIELD_WINDOWING', {
4131
- providedIn: 'root',
4132
- factory: () => ({ enabled: false, eager: 12, placeholderHeight: '4rem' }),
4133
- });
4134
-
4135
- function resolveFieldWindowing(global, perForm) {
4136
- if (perForm === undefined) {
4137
- return global;
4138
- }
4139
- if (typeof perForm === 'boolean') {
4140
- return { ...global, enabled: perForm };
4141
- }
4142
- return {
4143
- enabled: true,
4144
- eager: clampWindowSize(perForm.eager, global.eager),
4145
- placeholderHeight: perForm.placeholderHeight ?? global.placeholderHeight,
4146
- };
4147
- }
4148
-
4149
4238
  class DynamicForm {
4150
4239
  config = input.required({ ...(ngDevMode ? { debugName: "config" } : /* istanbul ignore next */ {}), alias: 'dynamic-form' });
4151
4240
  formOptions = input(undefined, /* @ts-ignore */
@@ -4186,22 +4275,31 @@ class DynamicForm {
4186
4275
  submitting = this.stateManager.submitting;
4187
4276
  fieldLoadingErrors = this.stateManager.fieldLoadingErrors;
4188
4277
  derivationReady = inject(DERIVATION_RENDER_GATE);
4189
- shouldRender = computed(() => this.stateManager.shouldRender() && this.derivationReady(), /* @ts-ignore */
4278
+ shouldRender = computed(() => this.stateManager.shouldRender() && this.stateManager.formSchemaReady() && this.derivationReady(), /* @ts-ignore */
4190
4279
  ...(ngDevMode ? [{ debugName: "shouldRender" }] : /* istanbul ignore next */ []));
4280
+ webMcpStatus = inject(WEB_MCP_GATE);
4191
4281
  resolvedFields = this.stateManager.resolvedFields;
4192
4282
  fieldWindowing = computed(() => resolveFieldWindowing(this.globalFieldWindowing, this.effectiveFormOptions()?.fieldWindowing), /* @ts-ignore */
4193
4283
  ...(ngDevMode ? [{ debugName: "fieldWindowing" }] : /* istanbul ignore next */ []));
4194
- totalComponentsCount = computed(() => {
4195
- const fields = this.stateManager.formSetup()?.fields ?? [];
4196
- return countContainersRecursive(fields) + 1;
4284
+ initializedComponents = signal(new Set(), /* @ts-ignore */
4285
+ ...(ngDevMode ? [{ debugName: "initializedComponents" }] : /* istanbul ignore next */ []));
4286
+ initialPageReady = signal(false, /* @ts-ignore */
4287
+ ...(ngDevMode ? [{ debugName: "initialPageReady" }] : /* istanbul ignore next */ []));
4288
+ initializationReady = computed(() => {
4289
+ const config = this.activeConfig();
4290
+ if (!config)
4291
+ return false;
4292
+ if (this.formModeDetection().mode === 'paged')
4293
+ return this.initialPageReady();
4294
+ const initialized = this.initializedComponents();
4295
+ const expected = [
4296
+ initializationComponentKey('dynamic-form', this.componentId),
4297
+ ...collectInitializingContainerKeys(config.fields ?? []),
4298
+ ];
4299
+ return expected.every((key) => initialized.has(key));
4197
4300
  }, /* @ts-ignore */
4198
- ...(ngDevMode ? [{ debugName: "totalComponentsCount" }] : /* istanbul ignore next */ []));
4199
- initialized$ = setupInitializationTracking({
4200
- eventBus: this.eventBus,
4201
- totalComponentsCount: this.totalComponentsCount,
4202
- injector: this.injector,
4203
- componentId: this.componentId,
4204
- });
4301
+ ...(ngDevMode ? [{ debugName: "initializationReady" }] : /* istanbul ignore next */ []));
4302
+ initialized$ = toObservable(this.initializationReady, { injector: this.injector }).pipe(filter((ready) => ready), take(1), shareReplay({ bufferSize: 1, refCount: false }));
4205
4303
  validityChange = outputFromObservable(toObservable(this.valid));
4206
4304
  dirtyChange = outputFromObservable(toObservable(this.dirty));
4207
4305
  submitted = outputFromObservable(this.stateManager.submitted$);
@@ -4209,12 +4307,32 @@ class DynamicForm {
4209
4307
  cleared = outputFromObservable(this.eventBus.on('form-clear'));
4210
4308
  events = outputFromObservable(this.eventBus.events$);
4211
4309
  initialized = outputFromObservable(this.initialized$);
4310
+ activePageInitialized = outputFromObservable(this.eventBus.on('active-page-initialized'));
4212
4311
  onPageChange = outputFromObservable(this.eventBus.on('page-change'));
4213
- onPageNavigationStateChange = outputFromObservable(this.eventBus.on('page-navigation-state-change'));
4312
+ onPageNavigationStateChange = outputFromObservable(this.eventBus.on('pager-state'));
4214
4313
  constructor() {
4314
+ this.eventBus
4315
+ .on('component-initialized')
4316
+ .pipe(takeUntilDestroyed(this.destroyRef))
4317
+ .subscribe((event) => {
4318
+ const key = initializationComponentKey(event.componentType, event.componentId);
4319
+ this.initializedComponents.update((current) => {
4320
+ if (current.has(key))
4321
+ return current;
4322
+ const next = new Set(current);
4323
+ next.add(key);
4324
+ return next;
4325
+ });
4326
+ });
4327
+ this.eventBus
4328
+ .on('active-page-initialized')
4329
+ .pipe(takeUntilDestroyed(this.destroyRef), take(1))
4330
+ .subscribe(() => this.initialPageReady.set(true));
4215
4331
  this.dispatcher?.connect(this.eventBus);
4216
4332
  this.destroyRef.onDestroy(() => this.dispatcher?.disconnect());
4217
4333
  inject(FORM_INITIALIZER, { optional: true });
4334
+ const preload = injectFormComponentPreloader();
4335
+ explicitEffect([this.config], ([config]) => preload.preloadConfig(config));
4218
4336
  this.setupEffects();
4219
4337
  this.setupEventHandlers();
4220
4338
  }
@@ -4236,8 +4354,8 @@ class DynamicForm {
4236
4354
  this.eventBus.dispatch(ComponentInitializedEvent, 'dynamic-form', this.componentId);
4237
4355
  }
4238
4356
  });
4239
- explicitEffect([this.resolvedFields, this.formModeDetection], ([fields, { mode }]) => {
4240
- if (mode === 'non-paged' && fields.length > 0) {
4357
+ explicitEffect([this.resolvedFields, this.formModeDetection, this.activeConfig], ([, { mode }, config]) => {
4358
+ if (config && mode === 'non-paged') {
4241
4359
  afterNextRender(() => {
4242
4360
  this.eventBus.dispatch(ComponentInitializedEvent, 'dynamic-form', this.componentId);
4243
4361
  }, { injector: this.injector });
@@ -4272,8 +4390,8 @@ class DynamicForm {
4272
4390
  error: (err) => this.logger.error('Submission handler error', err),
4273
4391
  });
4274
4392
  }
4275
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: DynamicForm, deps: [], target: i0.ɵɵFactoryTarget.Component });
4276
- 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: [
4393
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: DynamicForm, deps: [], target: i0.ɵɵFactoryTarget.Component });
4394
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.5", 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: [
4277
4395
  provideDynamicFormDI(),
4278
4396
  DfFieldTemplateRegistry,
4279
4397
  { provide: DF_FIELD_TEMPLATES, useFactory: () => inject(DfFieldTemplateRegistry).map },
@@ -4285,12 +4403,8 @@ class DynamicForm {
4285
4403
  }
4286
4404
  @case ('non-paged') {
4287
4405
  @for (field of resolvedFields(); track field.key; let i = $index) {
4288
- <!-- @if + DfFieldOutlet's own renderReady-&-!hidden gate together silence NG01916:
4289
- the template @if removes the host from the DOM (Angular's prescribed pattern), and
4290
- the directive's gate stops a same-CD-pass mount before [formField] can warn.
4291
- Don't dedupe one without the other — see DfFieldOutlet.renderReady. -->
4292
- @if (!field.hidden()) {
4293
- @if (windowsField(field, i)) {
4406
+ @if (windowsField(field, i)) {
4407
+ @if (!field.hidden()) {
4294
4408
  @defer (on viewport) {
4295
4409
  <ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
4296
4410
  } @placeholder {
@@ -4301,9 +4415,11 @@ class DynamicForm {
4301
4415
  aria-hidden="true"
4302
4416
  ></div>
4303
4417
  }
4304
- } @else {
4305
- <ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
4306
4418
  }
4419
+ } @else {
4420
+ <!-- DfFieldOutlet owns the hidden gate so an already-mounted field can be detached
4421
+ and restored without recreating its component and wrapper chain. -->
4422
+ <ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
4307
4423
  }
4308
4424
  }
4309
4425
  }
@@ -4314,7 +4430,7 @@ class DynamicForm {
4314
4430
  }
4315
4431
  `, isInline: true, 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"], dependencies: [{ kind: "directive", type: DfFieldOutlet, selector: "[dfFieldOutlet]", inputs: ["dfFieldOutlet", "dfFieldOutletEnvironmentInjector"] }, { kind: "component", type: PageOrchestratorComponent, selector: "div[page-orchestrator]", inputs: ["pageFields", "form", "fieldSignalContext"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, deferBlockDependencies: [() => [DfFieldOutlet]] });
4316
4432
  }
4317
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: DynamicForm, decorators: [{
4433
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: DynamicForm, decorators: [{
4318
4434
  type: Component,
4319
4435
  args: [{ selector: 'form[dynamic-form]', imports: [DfFieldOutlet, PageOrchestratorComponent], template: `
4320
4436
  @if (shouldRender()) {
@@ -4324,12 +4440,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
4324
4440
  }
4325
4441
  @case ('non-paged') {
4326
4442
  @for (field of resolvedFields(); track field.key; let i = $index) {
4327
- <!-- @if + DfFieldOutlet's own renderReady-&-!hidden gate together silence NG01916:
4328
- the template @if removes the host from the DOM (Angular's prescribed pattern), and
4329
- the directive's gate stops a same-CD-pass mount before [formField] can warn.
4330
- Don't dedupe one without the other — see DfFieldOutlet.renderReady. -->
4331
- @if (!field.hidden()) {
4332
- @if (windowsField(field, i)) {
4443
+ @if (windowsField(field, i)) {
4444
+ @if (!field.hidden()) {
4333
4445
  @defer (on viewport) {
4334
4446
  <ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
4335
4447
  } @placeholder {
@@ -4340,9 +4452,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
4340
4452
  aria-hidden="true"
4341
4453
  ></div>
4342
4454
  }
4343
- } @else {
4344
- <ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
4345
4455
  }
4456
+ } @else {
4457
+ <!-- DfFieldOutlet owns the hidden gate so an already-mounted field can be detached
4458
+ and restored without recreating its component and wrapper chain. -->
4459
+ <ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
4346
4460
  }
4347
4461
  }
4348
4462
  }
@@ -4364,29 +4478,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
4364
4478
  '[attr.id]': 'idPrefix() || null',
4365
4479
  '(submit)': 'onNativeSubmit($event)',
4366
4480
  }, 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"] }]
4367
- }], 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"] }] } });
4368
- function countContainersRecursive(fields) {
4369
- let count = 0;
4370
- for (const field of fields) {
4371
- if (isContainerField(field)) {
4372
- count += 1;
4373
- if (hasChildFields(field)) {
4374
- const children = field.fields;
4375
- if (Array.isArray(children)) {
4376
- for (const child of children) {
4377
- if (Array.isArray(child)) {
4378
- count += countContainersRecursive(child);
4379
- }
4380
- else if (child != null && isContainerField(child)) {
4381
- count += countContainersRecursive([child]);
4382
- }
4383
- }
4384
- }
4385
- }
4386
- }
4387
- }
4388
- return count;
4389
- }
4481
+ }], 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"] }] } });
4390
4482
 
4391
4483
  const BUILT_IN_ADDON_TYPES = [
4392
4484
  {
@@ -4434,35 +4526,35 @@ const DISPLAY_FIELD_TYPES_BASE = {
4434
4526
  const BUILT_IN_FIELDS = [
4435
4527
  {
4436
4528
  name: 'row',
4437
- loadComponent: () => import('./ng-forge-dynamic-forms-container-field.component-CM1pY9v5.mjs'),
4529
+ loadComponent: () => import('./ng-forge-dynamic-forms-container-field.component-Dc3ROhHL.mjs'),
4438
4530
  mapper: rowFieldMapper,
4439
4531
  valueHandling: 'flatten',
4440
4532
  ...CONTAINER_FIELD_TYPES_BASE,
4441
4533
  },
4442
4534
  {
4443
4535
  name: 'group',
4444
- loadComponent: () => import('./ng-forge-dynamic-forms-group-field.component-D4JhDffB.mjs'),
4536
+ loadComponent: () => import('./ng-forge-dynamic-forms-group-field.component-DAC3P9DC.mjs'),
4445
4537
  mapper: groupFieldMapper,
4446
4538
  valueHandling: 'include',
4447
4539
  ...CONTAINER_FIELD_TYPES_BASE,
4448
4540
  },
4449
4541
  {
4450
4542
  name: 'array',
4451
- loadComponent: () => import('./ng-forge-dynamic-forms-array-field.component-BVnXnaNw.mjs'),
4543
+ loadComponent: () => import('./ng-forge-dynamic-forms-array-field.component-CSCKq_1K.mjs'),
4452
4544
  mapper: arrayFieldMapper,
4453
4545
  valueHandling: 'include',
4454
4546
  ...CONTAINER_FIELD_TYPES_BASE,
4455
4547
  },
4456
4548
  {
4457
4549
  name: 'page',
4458
- loadComponent: () => import('./ng-forge-dynamic-forms-page-field.component-Wbz6S8iH.mjs'),
4550
+ loadComponent: () => import('./ng-forge-dynamic-forms-page-field.component-DPKJcwUO.mjs'),
4459
4551
  mapper: pageFieldMapper,
4460
4552
  valueHandling: 'flatten',
4461
4553
  ...CONTAINER_FIELD_TYPES_BASE,
4462
4554
  },
4463
4555
  {
4464
4556
  name: 'text',
4465
- loadComponent: () => import('./ng-forge-dynamic-forms-text-field.component-C-f80sbk.mjs'),
4557
+ loadComponent: () => import('./ng-forge-dynamic-forms-text-field.component-BB-jutBq.mjs'),
4466
4558
  mapper: textFieldMapper,
4467
4559
  valueHandling: 'exclude',
4468
4560
  ...DISPLAY_FIELD_TYPES_BASE,
@@ -4473,7 +4565,7 @@ const BUILT_IN_FIELDS = [
4473
4565
  },
4474
4566
  {
4475
4567
  name: 'container',
4476
- loadComponent: () => import('./ng-forge-dynamic-forms-container-field.component-CM1pY9v5.mjs'),
4568
+ loadComponent: () => import('./ng-forge-dynamic-forms-container-field.component-Dc3ROhHL.mjs'),
4477
4569
  mapper: containerFieldMapper,
4478
4570
  valueHandling: 'flatten',
4479
4571
  ...CONTAINER_FIELD_TYPES_BASE,
@@ -4482,11 +4574,16 @@ const BUILT_IN_FIELDS = [
4482
4574
  const BUILT_IN_WRAPPERS = [
4483
4575
  {
4484
4576
  wrapperName: 'css',
4485
- loadComponent: () => import('./ng-forge-dynamic-forms-css-wrapper.component-D_yRuLpu.mjs'),
4577
+ loadComponent: () => import('./ng-forge-dynamic-forms-css-wrapper.component-BzgX8lAD.mjs'),
4486
4578
  },
4487
4579
  {
4488
4580
  wrapperName: 'row',
4489
- loadComponent: () => import('./ng-forge-dynamic-forms-row-wrapper.component-BBWmqQA_.mjs'),
4581
+ loadComponent: () => import('./ng-forge-dynamic-forms-row-wrapper.component-DMEeKZny.mjs'),
4582
+ },
4583
+ {
4584
+ wrapperName: 'field-errors',
4585
+ loadComponent: () => import('./ng-forge-dynamic-forms-field-errors-wrapper.component-CIKbWqYA.mjs'),
4586
+ rendersFieldErrors: true,
4490
4587
  },
4491
4588
  ];
4492
4589
 
@@ -4547,7 +4644,8 @@ function provideDynamicForm(...items) {
4547
4644
  const wrapperBundles = items.filter(isWrappersBundle);
4548
4645
  const features = items.filter(isDynamicFormFeature);
4549
4646
  const fields = [...BUILT_IN_FIELDS, ...fieldTypes];
4550
- const wrappers = [...BUILT_IN_WRAPPERS, ...wrapperTypes, ...wrapperBundles.flatMap((bundle) => bundle.ɵdefinitions)];
4647
+ const customWrappers = [...wrapperTypes, ...wrapperBundles.flatMap((bundle) => bundle.ɵdefinitions)];
4648
+ const wrappers = [...BUILT_IN_WRAPPERS, ...customWrappers];
4551
4649
  const featureProviders = [];
4552
4650
  const hasLoggerFeature = features.some((feature) => feature.ɵkind === 'logger');
4553
4651
  features.forEach((feature) => {
@@ -4562,7 +4660,7 @@ function provideDynamicForm(...items) {
4562
4660
  const logger = inject(DynamicFormLogger);
4563
4661
  const registry = new Map();
4564
4662
  fields.forEach((fieldType) => {
4565
- if (registry.has(fieldType.name)) {
4663
+ if (DEV_MODE && registry.has(fieldType.name)) {
4566
4664
  logger.warn(`Field type "${fieldType.name}" is already registered. Overwriting.`);
4567
4665
  }
4568
4666
  registry.set(fieldType.name, fieldType);
@@ -4588,11 +4686,18 @@ function provideDynamicForm(...items) {
4588
4686
  useFactory: () => {
4589
4687
  const logger = inject(DynamicFormLogger);
4590
4688
  const registry = new Map();
4591
- wrappers.forEach((wrapperType) => {
4592
- if (registry.has(wrapperType.wrapperName)) {
4593
- logger.warn(`Wrapper type "${wrapperType.wrapperName}" is already registered. Overwriting.`);
4689
+ const builtInNames = new Set(BUILT_IN_WRAPPERS.map((w) => w.wrapperName));
4690
+ BUILT_IN_WRAPPERS.forEach((wrapperType) => registry.set(wrapperType.wrapperName, wrapperType));
4691
+ const seenCustom = DEV_MODE ? new Set() : null;
4692
+ customWrappers.forEach((wrapperType) => {
4693
+ const name = wrapperType.wrapperName;
4694
+ if (seenCustom) {
4695
+ if (seenCustom.has(name) && !builtInNames.has(name)) {
4696
+ logger.warn(`Wrapper type "${name}" is already registered. Overwriting.`);
4697
+ }
4698
+ seenCustom.add(name);
4594
4699
  }
4595
- registry.set(wrapperType.wrapperName, wrapperType);
4700
+ registry.set(name, wrapperType);
4596
4701
  });
4597
4702
  return registry;
4598
4703
  },
@@ -4623,7 +4728,7 @@ function provideDynamicForm(...items) {
4623
4728
  registry.set(def.type, def);
4624
4729
  }
4625
4730
  for (const def of contributed) {
4626
- if (registry.has(def.type)) {
4731
+ if (DEV_MODE && registry.has(def.type)) {
4627
4732
  logger.warn(`Addon type "${def.type}" is already registered. Overwriting.`);
4628
4733
  }
4629
4734
  registry.set(def.type, def);
@@ -4681,10 +4786,22 @@ function withFieldWindowing(config) {
4681
4786
  enabled: true,
4682
4787
  eager: clampWindowSize(config?.eager, 12),
4683
4788
  placeholderHeight: config?.placeholderHeight ?? '4rem',
4789
+ park: resolveFieldParking({ enabled: true, margin: '100%' }, config?.park),
4684
4790
  };
4685
4791
  return createFeature('field-windowing', [{ provide: FIELD_WINDOWING, useValue: resolved }]);
4686
4792
  }
4687
4793
 
4794
+ function withExperimentalWebMcp() {
4795
+ return createFeature('web-mcp', [{ provide: WEB_MCP_ENABLED, useValue: true }]);
4796
+ }
4797
+
4798
+ const VALIDATION_EXECUTION_DEFAULTS = new InjectionToken('VALIDATION_EXECUTION_DEFAULTS', {
4799
+ providedIn: 'root',
4800
+ factory: () => ({
4801
+ validateWhenHidden: false,
4802
+ }),
4803
+ });
4804
+
4688
4805
  function withValidationExecutionDefaults(config) {
4689
4806
  const resolved = {
4690
4807
  validateWhenHidden: config?.validateWhenHidden ?? false,
@@ -4705,10 +4822,10 @@ class TextAddonComponent {
4705
4822
  ...(ngDevMode ? [{ debugName: "addon" }] : /* istanbul ignore next */ []));
4706
4823
  fieldInputs = input(/* @ts-ignore */
4707
4824
  ...(ngDevMode ? [undefined, { debugName: "fieldInputs" }] : /* istanbul ignore next */ []));
4708
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: TextAddonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4709
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.1", type: TextAddonComponent, isStandalone: true, selector: "df-text-addon", inputs: { addon: { classPropertyName: "addon", publicName: "addon", isSignal: true, isRequired: true, transformFunction: null }, fieldInputs: { classPropertyName: "fieldInputs", publicName: "fieldInputs", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.aria-hidden": "\"true\"" } }, ngImport: i0, template: `<span>{{ addon().text | dynamicText | async }}</span>`, isInline: true, dependencies: [{ kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "pipe", type: DynamicTextPipe, name: "dynamicText" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4825
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: TextAddonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4826
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.5", type: TextAddonComponent, isStandalone: true, selector: "df-text-addon", inputs: { addon: { classPropertyName: "addon", publicName: "addon", isSignal: true, isRequired: true, transformFunction: null }, fieldInputs: { classPropertyName: "fieldInputs", publicName: "fieldInputs", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.aria-hidden": "\"true\"" } }, ngImport: i0, template: `<span>{{ addon().text | dynamicText | async }}</span>`, isInline: true, dependencies: [{ kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "pipe", type: DynamicTextPipe, name: "dynamicText" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4710
4827
  }
4711
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: TextAddonComponent, decorators: [{
4828
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: TextAddonComponent, decorators: [{
4712
4829
  type: Component,
4713
4830
  args: [{
4714
4831
  selector: 'df-text-addon',
@@ -4748,14 +4865,14 @@ class TemplateAddonComponent {
4748
4865
  }
4749
4866
  });
4750
4867
  }
4751
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: TemplateAddonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4752
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", type: TemplateAddonComponent, isStandalone: true, selector: "df-template-addon", inputs: { addon: { classPropertyName: "addon", publicName: "addon", isSignal: true, isRequired: true, transformFunction: null }, fieldInputs: { classPropertyName: "fieldInputs", publicName: "fieldInputs", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
4868
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: TemplateAddonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4869
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.5", type: TemplateAddonComponent, isStandalone: true, selector: "df-template-addon", inputs: { addon: { classPropertyName: "addon", publicName: "addon", isSignal: true, isRequired: true, transformFunction: null }, fieldInputs: { classPropertyName: "fieldInputs", publicName: "fieldInputs", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
4753
4870
  @if (template(); as tpl) {
4754
4871
  <ng-container *ngTemplateOutlet="tpl; context: outletContext()" />
4755
4872
  }
4756
4873
  `, isInline: true, dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4757
4874
  }
4758
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: TemplateAddonComponent, decorators: [{
4875
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: TemplateAddonComponent, decorators: [{
4759
4876
  type: Component,
4760
4877
  args: [{
4761
4878
  selector: 'df-template-addon',
@@ -4803,14 +4920,14 @@ class ComponentAddonComponent {
4803
4920
  });
4804
4921
  });
4805
4922
  }
4806
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: ComponentAddonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4807
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", type: ComponentAddonComponent, isStandalone: true, selector: "df-component-addon", inputs: { addon: { classPropertyName: "addon", publicName: "addon", isSignal: true, isRequired: true, transformFunction: null }, fieldInputs: { classPropertyName: "fieldInputs", publicName: "fieldInputs", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
4923
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: ComponentAddonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4924
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.5", type: ComponentAddonComponent, isStandalone: true, selector: "df-component-addon", inputs: { addon: { classPropertyName: "addon", publicName: "addon", isSignal: true, isRequired: true, transformFunction: null }, fieldInputs: { classPropertyName: "fieldInputs", publicName: "fieldInputs", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
4808
4925
  @if (resolvedComponent(); as cmp) {
4809
4926
  <ng-container *ngComponentOutlet="cmp; inputs: inputs()" />
4810
4927
  }
4811
4928
  `, isInline: true, dependencies: [{ kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4812
4929
  }
4813
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: ComponentAddonComponent, decorators: [{
4930
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: ComponentAddonComponent, decorators: [{
4814
4931
  type: Component,
4815
4932
  args: [{
4816
4933
  selector: 'df-component-addon',
@@ -4875,6 +4992,128 @@ function sanitizeFormConfigPure(config, fieldRegistry, typeRegistry, options = {
4875
4992
  return { sanitized: { ...config, fields: fields }, warnings };
4876
4993
  }
4877
4994
 
4995
+ class ActivePageInitializedEvent {
4996
+ pageIndex;
4997
+ pageKey;
4998
+ type = 'active-page-initialized';
4999
+ constructor(pageIndex, pageKey) {
5000
+ this.pageIndex = pageIndex;
5001
+ this.pageKey = pageKey;
5002
+ }
5003
+ }
5004
+
5005
+ class GoToPageEvent {
5006
+ pageIndex;
5007
+ options;
5008
+ type = 'go-to-page';
5009
+ constructor(
5010
+ pageIndex,
5011
+ options) {
5012
+ this.pageIndex = pageIndex;
5013
+ this.options = options;
5014
+ }
5015
+ }
5016
+
5017
+ class NextPageEvent {
5018
+ type = 'next-page';
5019
+ }
5020
+
5021
+ class PreviousPageEvent {
5022
+ type = 'previous-page';
5023
+ }
5024
+
5025
+ class FormResetEvent {
5026
+ type = 'form-reset';
5027
+ }
5028
+
5029
+ class FormClearEvent {
5030
+ type = 'form-clear';
5031
+ }
5032
+
5033
+ class AppendArrayItemEvent {
5034
+ arrayKey;
5035
+ template;
5036
+ type = 'append-array-item';
5037
+ constructor(
5038
+ arrayKey,
5039
+ template) {
5040
+ this.arrayKey = arrayKey;
5041
+ this.template = template;
5042
+ }
5043
+ }
5044
+
5045
+ class PrependArrayItemEvent {
5046
+ arrayKey;
5047
+ template;
5048
+ type = 'prepend-array-item';
5049
+ constructor(
5050
+ arrayKey,
5051
+ template) {
5052
+ this.arrayKey = arrayKey;
5053
+ this.template = template;
5054
+ }
5055
+ }
5056
+
5057
+ class InsertArrayItemEvent {
5058
+ arrayKey;
5059
+ index;
5060
+ template;
5061
+ type = 'insert-array-item';
5062
+ constructor(
5063
+ arrayKey,
5064
+ index,
5065
+ template) {
5066
+ this.arrayKey = arrayKey;
5067
+ this.index = index;
5068
+ this.template = template;
5069
+ }
5070
+ }
5071
+
5072
+ class PopArrayItemEvent {
5073
+ arrayKey;
5074
+ type = 'pop-array-item';
5075
+ constructor(
5076
+ arrayKey) {
5077
+ this.arrayKey = arrayKey;
5078
+ }
5079
+ }
5080
+
5081
+ class ShiftArrayItemEvent {
5082
+ arrayKey;
5083
+ type = 'shift-array-item';
5084
+ constructor(
5085
+ arrayKey) {
5086
+ this.arrayKey = arrayKey;
5087
+ }
5088
+ }
5089
+
5090
+ class MoveArrayItemEvent {
5091
+ arrayKey;
5092
+ fromIndex;
5093
+ toIndex;
5094
+ type = 'move-array-item';
5095
+ constructor(
5096
+ arrayKey,
5097
+ fromIndex,
5098
+ toIndex) {
5099
+ this.arrayKey = arrayKey;
5100
+ this.fromIndex = fromIndex;
5101
+ this.toIndex = toIndex;
5102
+ }
5103
+ }
5104
+
5105
+ class RemoveAtIndexEvent {
5106
+ arrayKey;
5107
+ index;
5108
+ type = 'remove-at-index';
5109
+ constructor(
5110
+ arrayKey,
5111
+ index) {
5112
+ this.arrayKey = arrayKey;
5113
+ this.index = index;
5114
+ }
5115
+ }
5116
+
4878
5117
  function arrayEvent(arrayKey) {
4879
5118
  return {
4880
5119
  append: (template) => new AppendArrayItemEvent(arrayKey, template),
@@ -4890,5 +5129,5 @@ function arrayEvent(arrayKey) {
4890
5129
 
4891
5130
 
4892
5131
 
4893
- export { withLegacyStatusClasses as $, ArrayItemRegistryService as A, BUILT_IN_FIELDS as B, CONTAINER_FIELD_PROCESSORS as C, DfFieldOutlet as D, EventDispatcher as E, FIELD_WINDOWING as F, PageChangeEvent as G, PopArrayItemEvent as H, InsertArrayItemEvent as I, PrependArrayItemEvent as J, PreviousPageEvent as K, TextAddonComponent as L, MoveArrayItemEvent as M, NextPageEvent as N, arrayEvent as O, PROPERTY_OVERRIDE_STORE as P, createWrappers as Q, RemoveAtIndexEvent as R, ShiftArrayItemEvent as S, TemplateAddonComponent as T, isWrappersBundle as U, provideDynamicForm as V, sanitizeFormConfig as W, withAddonActions as X, withCustomAddon as Y, withEventFormValue as Z, withFieldWindowing as _, isSameWrapperChain as a, withLoggerConfig as a0, withPagePreload as a1, withValidationExecutionDefaults as a2, withValueExclusionDefaults as a3, wrapperProps as a4, 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 };
4894
- //# sourceMappingURL=ng-forge-dynamic-forms-ng-forge-dynamic-forms-ZiNaCWHl.mjs.map
5132
+ export { arrayEvent as $, ArrayItemRegistryService as A, BUILT_IN_FIELDS as B, CONTAINER_FIELD_PROCESSORS as C, DfFieldOutlet as D, ConsoleLogger as E, FIELD_WINDOWING as F, DfTemplate as G, DynamicForm as H, EventDispatcher as I, FormClearEvent as J, FormResetEvent 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, withExperimentalWebMcp as a7, withFieldWindowing as a8, withLegacyStatusClasses as a9, withLoggerConfig as aa, withPagePreload as ab, withValidationExecutionDefaults as ac, withValueExclusionDefaults as ad, wrapperProps as ae, 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, FormSubmitEvent as x, AppendArrayItemEvent as y, ComponentAddonComponent as z };
5133
+ //# sourceMappingURL=ng-forge-dynamic-forms-ng-forge-dynamic-forms-CXQe_5td.mjs.map