@ng-forge/dynamic-forms 1.1.0 → 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-CvjDqYyq.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-CcoUnroD.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-DZfVWjiM.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-CPFgu3pM.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 +49 -107
  21. package/fesm2022/ng-forge-dynamic-forms-integration.mjs.map +1 -1
  22. package/fesm2022/ng-forge-dynamic-forms-internal.mjs +434 -139
  23. package/fesm2022/ng-forge-dynamic-forms-internal.mjs.map +1 -1
  24. package/fesm2022/{ng-forge-dynamic-forms-ng-forge-dynamic-forms-Du3V5AOb.mjs → ng-forge-dynamic-forms-ng-forge-dynamic-forms-CXQe_5td.mjs} +1930 -1847
  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-amWhCdHL.mjs → ng-forge-dynamic-forms-page-field.component-DPKJcwUO.mjs} +94 -15
  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 +15 -33
  37. package/types/ng-forge-dynamic-forms-internal.d.ts +705 -292
  38. package/types/ng-forge-dynamic-forms.d.ts +400 -46
  39. package/fesm2022/ng-forge-dynamic-forms-array-field.component-CvjDqYyq.mjs.map +0 -1
  40. package/fesm2022/ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-CcoUnroD.mjs.map +0 -1
  41. package/fesm2022/ng-forge-dynamic-forms-container-field.component-DZfVWjiM.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-CPFgu3pM.mjs.map +0 -1
  48. package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-Du3V5AOb.mjs.map +0 -1
  49. package/fesm2022/ng-forge-dynamic-forms-page-field.component-amWhCdHL.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, linkedSignal, ChangeDetectionStrategy, Component, Injectable, InjectionToken, 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, requiresTreeValidation, applyValidator, isPageField, isGenericContainerField, isGroupField, getFieldValueHandling, normalizeFieldsArray, getNestedValue, extractStringDependencies, isCrossFieldStateLogic, extractExpressionDependencies, isCrossFieldSchema, 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, validateTree, 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,18 +141,29 @@ 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;
35
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);
164
+ }
165
+
166
+ const DEV_MODE = typeof ngDevMode === 'undefined' || !!ngDevMode;
36
167
 
37
168
  const inputNamesCache = new WeakMap();
38
169
  function getDeclaredInputs(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,1656 +702,549 @@ 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
+ }
921
+ }
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
+ }
491
928
  }
492
929
  }
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;
506
- }
507
- }
508
-
509
- class PopArrayItemEvent {
510
- arrayKey;
511
- type = 'pop-array-item';
512
- constructor(
513
- arrayKey) {
514
- this.arrayKey = arrayKey;
515
- }
516
- }
517
-
518
- class ShiftArrayItemEvent {
519
- arrayKey;
520
- type = 'shift-array-item';
521
- constructor(
522
- arrayKey) {
523
- this.arrayKey = arrayKey;
524
- }
525
- }
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;
539
- }
540
- }
541
-
542
- class RemoveAtIndexEvent {
543
- arrayKey;
544
- index;
545
- type = 'remove-at-index';
546
- constructor(
547
- arrayKey,
548
- index) {
549
- this.arrayKey = arrayKey;
550
- this.index = index;
551
- }
552
- }
553
-
554
- class PageNavigationStateChangeEvent {
555
- state;
556
- type = 'page-navigation-state-change';
557
- constructor(state) {
558
- this.state = state;
559
- }
560
- }
561
-
562
- class PageOrchestratorComponent {
563
- eventBus = inject(EventBus);
564
- fieldContextRegistry = inject(FieldContextRegistryService);
565
- functionRegistry = inject(FunctionRegistryService);
566
- formOptions = inject(FORM_OPTIONS, { optional: true });
567
- pageFields = input.required(/* @ts-ignore */
568
- ...(ngDevMode ? [{ debugName: "pageFields" }] : /* istanbul ignore next */ []));
569
- form = input.required(/* @ts-ignore */
570
- ...(ngDevMode ? [{ debugName: "form" }] : /* istanbul ignore next */ []));
571
- fieldSignalContext = input.required(/* @ts-ignore */
572
- ...(ngDevMode ? [{ debugName: "fieldSignalContext" }] : /* istanbul ignore next */ []));
573
- pageHiddenStates = computed(() => {
574
- const pages = this.pageFields();
575
- return pages.map((page) => this.evaluatePageHidden(page));
576
- }, /* @ts-ignore */
577
- ...(ngDevMode ? [{ debugName: "pageHiddenStates" }] : /* istanbul ignore next */ []));
578
- visiblePageIndices = computed(() => {
579
- const hiddenStates = this.pageHiddenStates();
580
- return hiddenStates
581
- .map((hidden, index) => ({ index, hidden }))
582
- .filter((item) => !item.hidden)
583
- .map((item) => item.index);
584
- }, /* @ts-ignore */
585
- ...(ngDevMode ? [{ debugName: "visiblePageIndices" }] : /* istanbul ignore next */ []));
586
- currentPageIndex = linkedSignal(() => {
587
- const totalPages = this.pageFields().length;
588
- if (totalPages === 0)
589
- return 0;
590
- return 0;
591
- }, /* @ts-ignore */
592
- ...(ngDevMode ? [{ debugName: "currentPageIndex" }] : /* istanbul ignore next */ []));
593
- state = computed(() => {
594
- const currentIndex = this.currentPageIndex();
595
- const totalPages = this.pageFields().length;
596
- const visibleIndices = this.visiblePageIndices();
597
- const currentVisiblePosition = visibleIndices.indexOf(currentIndex);
598
- const isFirstVisiblePage = currentVisiblePosition === 0 || currentVisiblePosition === -1;
599
- const isLastVisiblePage = currentVisiblePosition >= visibleIndices.length - 1;
600
- return {
601
- currentPageIndex: currentIndex,
602
- totalPages,
603
- isFirstPage: isFirstVisiblePage,
604
- isLastPage: isLastVisiblePage,
605
- navigationDisabled: false,
606
- };
607
- }, /* @ts-ignore */
608
- ...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
609
- currentPageValid = computed(() => {
610
- const currentIndex = this.currentPageIndex();
611
- const pages = this.pageFields();
612
- const form = this.form();
613
- if (pages.length === 0 || currentIndex >= pages.length) {
614
- return true;
615
- }
616
- const currentPage = pages[currentIndex];
617
- const pageFields = currentPage.fields || [];
618
- const leafKeys = collectLeafFieldKeys(pageFields);
619
- for (const fieldKey of leafKeys) {
620
- const field = form[fieldKey];
621
- if (field && typeof field === 'function') {
622
- const fieldState = field();
623
- if (fieldState && typeof fieldState.valid === 'function' && !fieldState.valid()) {
624
- return false;
625
- }
626
- }
627
- }
628
- return true;
629
- }, /* @ts-ignore */
630
- ...(ngDevMode ? [{ debugName: "currentPageValid" }] : /* istanbul ignore next */ []));
631
- extendedFieldSignalContext = computed(() => ({
632
- ...this.fieldSignalContext(),
633
- currentPageValid: this.currentPageValid,
634
- }), /* @ts-ignore */
635
- ...(ngDevMode ? [{ debugName: "extendedFieldSignalContext" }] : /* istanbul ignore next */ []));
636
- constructor() {
637
- this.setupEventListeners();
638
- explicitEffect([this.state, this.visiblePageIndices], ([state, visibleIndices]) => {
639
- const currentVisiblePosition = visibleIndices.indexOf(state.currentPageIndex);
640
- if (currentVisiblePosition === -1 && visibleIndices.length > 0) {
641
- const nearest = this.findNearestVisiblePage(state.currentPageIndex, visibleIndices);
642
- if (nearest !== -1) {
643
- this.navigateToPage(nearest);
644
- }
645
- }
646
- });
647
- }
648
- navigateToNextPage() {
649
- const disableWhenPageInvalid = this.formOptions?.()?.nextButton?.disableWhenPageInvalid ?? true;
650
- if (disableWhenPageInvalid && !this.currentPageValid()) {
651
- return {
652
- success: false,
653
- newPageIndex: this.state().currentPageIndex,
654
- error: 'Current page has invalid fields',
655
- };
656
- }
657
- const currentState = this.state();
658
- const visibleIndices = this.visiblePageIndices();
659
- if (currentState.isLastPage) {
660
- return {
661
- success: false,
662
- newPageIndex: currentState.currentPageIndex,
663
- error: 'Already on the last visible page',
664
- };
665
- }
666
- if (currentState.navigationDisabled) {
667
- return {
668
- success: false,
669
- newPageIndex: currentState.currentPageIndex,
670
- error: 'Navigation is currently disabled',
671
- };
672
- }
673
- const currentVisiblePosition = visibleIndices.indexOf(currentState.currentPageIndex);
674
- if (currentVisiblePosition === -1 || currentVisiblePosition >= visibleIndices.length - 1) {
675
- return {
676
- success: false,
677
- newPageIndex: currentState.currentPageIndex,
678
- error: 'No next visible page available',
679
- };
680
- }
681
- const nextVisiblePageIndex = visibleIndices[currentVisiblePosition + 1];
682
- return this.navigateToPage(nextVisiblePageIndex);
683
- }
684
- navigateToPreviousPage() {
685
- const currentState = this.state();
686
- const visibleIndices = this.visiblePageIndices();
687
- if (currentState.isFirstPage) {
688
- return {
689
- success: false,
690
- newPageIndex: currentState.currentPageIndex,
691
- error: 'Already on the first visible page',
692
- };
693
- }
694
- if (currentState.navigationDisabled) {
695
- return {
696
- success: false,
697
- newPageIndex: currentState.currentPageIndex,
698
- error: 'Navigation is currently disabled',
699
- };
700
- }
701
- const currentVisiblePosition = visibleIndices.indexOf(currentState.currentPageIndex);
702
- if (currentVisiblePosition <= 0) {
703
- return {
704
- success: false,
705
- newPageIndex: currentState.currentPageIndex,
706
- error: 'No previous visible page available',
707
- };
708
- }
709
- const prevVisiblePageIndex = visibleIndices[currentVisiblePosition - 1];
710
- return this.navigateToPage(prevVisiblePageIndex);
711
- }
712
- navigateToPage(pageIndex) {
713
- const currentState = this.state();
714
- const totalPages = currentState.totalPages;
715
- if (pageIndex < 0 || pageIndex >= totalPages) {
716
- return {
717
- success: false,
718
- newPageIndex: currentState.currentPageIndex,
719
- error: `Invalid page index: ${pageIndex}. Valid range is 0 to ${totalPages - 1}`,
720
- };
721
- }
722
- const visibleIndices = this.visiblePageIndices();
723
- if (!visibleIndices.includes(pageIndex)) {
724
- return {
725
- success: false,
726
- newPageIndex: currentState.currentPageIndex,
727
- error: `Cannot navigate to hidden page at index ${pageIndex}. Visible pages: [${visibleIndices.join(', ')}]`,
728
- };
729
- }
730
- if (pageIndex === currentState.currentPageIndex) {
731
- return {
732
- success: true,
733
- newPageIndex: pageIndex,
734
- };
735
- }
736
- const previousIndex = currentState.currentPageIndex;
737
- this.currentPageIndex.set(pageIndex);
738
- this.eventBus.dispatch(PageChangeEvent, pageIndex, totalPages, previousIndex);
739
- return {
740
- success: true,
741
- newPageIndex: pageIndex,
742
- };
743
- }
744
- findNearestVisiblePage(currentIndex, visibleIndices) {
745
- let nearest = -1;
746
- let minDistance = Infinity;
747
- for (const idx of visibleIndices) {
748
- const distance = Math.abs(idx - currentIndex);
749
- if (distance < minDistance || (distance === minDistance && idx > nearest)) {
750
- minDistance = distance;
751
- nearest = idx;
752
- }
753
- }
754
- return nearest;
755
- }
756
- setupEventListeners() {
757
- this.eventBus
758
- .on('next-page')
759
- .pipe(takeUntilDestroyed())
760
- .subscribe(() => {
761
- this.navigateToNextPage();
762
- });
763
- this.eventBus
764
- .on('previous-page')
765
- .pipe(takeUntilDestroyed())
766
- .subscribe(() => {
767
- this.navigateToPreviousPage();
768
- });
769
- explicitEffect([this.state], ([state]) => this.eventBus.dispatch(PageNavigationStateChangeEvent, state));
770
- }
771
- evaluatePageHidden(page) {
772
- if (!page.logic || page.logic.length === 0) {
773
- return false;
774
- }
775
- const hiddenLogic = page.logic.filter((l) => l.type === 'hidden');
776
- if (hiddenLogic.length === 0) {
777
- return false;
778
- }
779
- for (const logic of hiddenLogic) {
780
- if (typeof logic.condition === 'boolean') {
781
- if (logic.condition) {
782
- return true;
783
- }
784
- continue;
785
- }
786
- const condition = logic.condition;
787
- const context = this.fieldContextRegistry.createDisplayOnlyContext(page.key || '', this.functionRegistry.getCustomFunctions());
788
- if (evaluateCondition(condition, context)) {
789
- return true;
790
- }
791
- }
792
- return false;
793
- }
794
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: PageOrchestratorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
795
- 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: [
796
- {
797
- provide: FIELD_SIGNAL_CONTEXT,
798
- useFactory: (orchestrator) => orchestrator.extendedFieldSignalContext(),
799
- deps: [PageOrchestratorComponent],
800
- },
801
- ], ngImport: i0, template: `
802
- @for (pageField of pageFields(); track pageField.key; let i = $index) {
803
- <!--
804
- Skip pages hidden by 'hidden' logic conditions entirely. FormStateManager
805
- derives schema/value/validators from config (not from mounted components),
806
- so unmounting a hidden page has no effect on form state — only on render +
807
- CD cost. This is the page-level equivalent of the existing field-level
808
- \`@if (!field.hidden())\` gate in page-field.component.ts.
809
- -->
810
- @if (!pageHiddenStates()[i]) {
811
- @if (i === state().currentPageIndex || i === state().currentPageIndex + 1 || i === state().currentPageIndex - 1) {
812
- <!-- Current and adjacent pages (±1): render immediately for flicker-free navigation -->
813
- @defer (on immediate) {
814
- <section
815
- page-field
816
- [field]="pageField"
817
- [key]="pageField.key"
818
- [pageIndex]="i"
819
- [isVisible]="i === state().currentPageIndex"
820
- ></section>
821
- } @placeholder {
822
- <div class="df-page-placeholder" [attr.data-page-index]="i" [attr.data-page-key]="pageField.key"></div>
823
- }
824
- } @else {
825
- <!-- Distant pages: defer until browser is idle for memory savings -->
826
- @defer (on idle) {
827
- <section page-field [field]="pageField" [key]="pageField.key" [pageIndex]="i" [isVisible]="false"></section>
828
- } @placeholder {
829
- <div class="df-page-placeholder" [attr.data-page-index]="i" [attr.data-page-key]="pageField.key"></div>
830
- }
831
- }
832
- }
833
- }
834
- `, isInline: true, styles: [":host{display:block;position:relative;width:100%}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, deferBlockDependencies: [() => [/* @ts-ignore */
835
- import('./ng-forge-dynamic-forms-page-field.component-amWhCdHL.mjs').then(m => m.default)], () => [/* @ts-ignore */
836
- import('./ng-forge-dynamic-forms-page-field.component-amWhCdHL.mjs').then(m => m.default)]] });
837
- }
838
- i0.ɵɵngDeclareClassMetadataAsync({ minVersion: "18.0.0", version: "22.0.1", ngImport: i0, type: PageOrchestratorComponent, resolveDeferredDeps: () => [/* @ts-ignore */
839
- import('./ng-forge-dynamic-forms-page-field.component-amWhCdHL.mjs').then(m => m.default)], resolveMetadata: PageFieldComponent => ({ decorators: [{
840
- type: Component,
841
- args: [{ selector: 'div[page-orchestrator]', imports: [PageFieldComponent], template: `
842
- @for (pageField of pageFields(); track pageField.key; let i = $index) {
843
- <!--
844
- Skip pages hidden by 'hidden' logic conditions entirely. FormStateManager
845
- derives schema/value/validators from config (not from mounted components),
846
- so unmounting a hidden page has no effect on form state — only on render +
847
- CD cost. This is the page-level equivalent of the existing field-level
848
- \`@if (!field.hidden())\` gate in page-field.component.ts.
849
- -->
850
- @if (!pageHiddenStates()[i]) {
851
- @if (i === state().currentPageIndex || i === state().currentPageIndex + 1 || i === state().currentPageIndex - 1) {
852
- <!-- Current and adjacent pages (±1): render immediately for flicker-free navigation -->
853
- @defer (on immediate) {
854
- <section
855
- page-field
856
- [field]="pageField"
857
- [key]="pageField.key"
858
- [pageIndex]="i"
859
- [isVisible]="i === state().currentPageIndex"
860
- ></section>
861
- } @placeholder {
862
- <div class="df-page-placeholder" [attr.data-page-index]="i" [attr.data-page-key]="pageField.key"></div>
863
- }
864
- } @else {
865
- <!-- Distant pages: defer until browser is idle for memory savings -->
866
- @defer (on idle) {
867
- <section page-field [field]="pageField" [key]="pageField.key" [pageIndex]="i" [isVisible]="false"></section>
868
- } @placeholder {
869
- <div class="df-page-placeholder" [attr.data-page-index]="i" [attr.data-page-key]="pageField.key"></div>
870
- }
871
- }
872
- }
873
- }
874
- `, host: {
875
- class: 'df-page-orchestrator',
876
- '[attr.data-current-page]': 'state().currentPageIndex',
877
- '[attr.data-total-pages]': 'state().totalPages',
878
- }, providers: [
879
- {
880
- provide: FIELD_SIGNAL_CONTEXT,
881
- useFactory: (orchestrator) => orchestrator.extendedFieldSignalContext(),
882
- deps: [PageOrchestratorComponent],
883
- },
884
- ], changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;position:relative;width:100%}\n"] }]
885
- }], 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 }] }] } }) });
886
- function collectLeafFieldKeys(fields) {
887
- const keys = [];
888
- for (const field of fields) {
889
- if (isRowField(field)) {
890
- keys.push(...collectLeafFieldKeys(field.fields));
891
- }
892
- else if (field.key) {
893
- keys.push(field.key);
894
- }
895
- }
896
- return keys;
897
- }
898
-
899
- const NORMALIZED_ARRAY_METADATA = Symbol('normalizedArrayMetadata');
900
- function setNormalizedArrayMetadata(arrayField, metadata) {
901
- arrayField[NORMALIZED_ARRAY_METADATA] = metadata;
902
- }
903
- function getNormalizedArrayMetadata(arrayField) {
904
- return arrayField[NORMALIZED_ARRAY_METADATA];
905
- }
906
-
907
- function normalizeSimplifiedArrays(fields) {
908
- return fields.flatMap((field) => {
909
- if (isContainerField(field) && !isArrayField(field) && hasChildFields(field)) {
910
- return [{ ...field, fields: normalizeSimplifiedArrays(field.fields) }];
911
- }
912
- if (isArrayField(field) && !isSimplifiedArrayField(field)) {
913
- const normalizedItems = field.fields.map((item) => Array.isArray(item) ? normalizeSimplifiedArrays(item) : item);
914
- return [{ ...field, fields: normalizedItems }];
915
- }
916
- if (isSimplifiedArrayField(field)) {
917
- const { arrayField, addButton } = expandSimplifiedArray(field);
918
- return addButton ? [arrayField, addButton] : [arrayField];
919
- }
920
- return [field];
921
- });
922
- }
923
- const INVALID_ARRAY_CHILD_TYPES = new Set(['page', 'array']);
924
- function validateSimplifiedTemplate(field) {
925
- const { template, key } = field;
926
- const isObjectTemplate = Array.isArray(template);
927
- if (isObjectTemplate) {
928
- const templateFields = template;
929
- for (const tmpl of templateFields) {
930
- if (INVALID_ARRAY_CHILD_TYPES.has(tmpl.type)) {
931
- throw new DynamicFormError(`Simplified array "${key}" template contains a '${tmpl.type}' field (key: '${tmpl.key}'). ` +
932
- `Only leaf fields, rows, and groups are allowed as array children.`);
933
- }
934
- }
935
- }
936
- else {
937
- const singleTemplate = template;
938
- if (INVALID_ARRAY_CHILD_TYPES.has(singleTemplate.type)) {
939
- throw new DynamicFormError(`Simplified array "${key}" template has type '${singleTemplate.type}'. ` +
940
- `Only leaf fields, rows, and groups are allowed as array children.`);
941
- }
942
- }
943
- }
944
- function expandSimplifiedArray(field) {
945
- const { template, value = [], addButton, removeButton, key, logic, minLength, maxLength, wrappers, skipAutoWrappers, skipDefaultWrappers, } = field;
946
- const isObjectTemplate = Array.isArray(template);
947
- const values = value;
948
- validateSimplifiedTemplate(field);
949
- const items = values.map((v) => {
950
- if (isObjectTemplate) {
951
- return buildObjectItem(template, v, removeButton);
952
- }
953
- return buildPrimitiveItem(template, v);
954
- });
955
- const addTemplate = isObjectTemplate
956
- ? buildObjectItemTemplate(template, removeButton)
957
- : template;
958
- const hasAutoRemove = !isObjectTemplate && removeButton !== false;
959
- const arrayFieldObj = {
960
- key,
961
- type: 'array',
962
- fields: items,
963
- };
964
- if (logic) {
965
- arrayFieldObj['logic'] = logic;
966
- }
967
- if (minLength !== undefined) {
968
- arrayFieldObj['minLength'] = minLength;
969
- }
970
- if (maxLength !== undefined) {
971
- arrayFieldObj['maxLength'] = maxLength;
972
- }
973
- if (wrappers !== undefined) {
974
- arrayFieldObj['wrappers'] = wrappers;
975
- }
976
- if (skipAutoWrappers !== undefined) {
977
- arrayFieldObj['skipAutoWrappers'] = skipAutoWrappers;
978
- }
979
- if (skipDefaultWrappers !== undefined) {
980
- arrayFieldObj['skipDefaultWrappers'] = skipDefaultWrappers;
981
- }
982
- const arrayField = arrayFieldObj;
983
- const primitiveFieldKey = !isObjectTemplate ? template.key : undefined;
984
- const metadataTemplate = isObjectTemplate ? buildObjectItemTemplate(template, removeButton) : template;
985
- setNormalizedArrayMetadata(arrayFieldObj, {
986
- ...(hasAutoRemove && { autoRemoveButton: buildRemoveButton(removeButton) }),
987
- ...(primitiveFieldKey && { primitiveFieldKey }),
988
- template: metadataTemplate,
989
- });
990
- let addButtonField;
991
- if (addButton !== false) {
992
- const buttonConfig = (typeof addButton === 'object' ? addButton : {});
993
- addButtonField = {
994
- key: `${key}__add`,
995
- type: 'add-array-item',
996
- label: buttonConfig.label ?? 'Add',
997
- arrayKey: key,
998
- template: addTemplate,
999
- ...(buttonConfig.props && { props: buttonConfig.props }),
1000
- ...(logic && { logic }),
1001
- };
1002
- }
1003
- return { arrayField, addButton: addButtonField };
1004
- }
1005
- function buildPrimitiveItem(template, value) {
1006
- return { ...template, value };
1007
- }
1008
- function buildObjectItem(template, valueObj, removeButton) {
1009
- const fieldsWithValues = template.map((templateField) => ({
1010
- ...templateField,
1011
- ...(Object.hasOwn(valueObj, templateField.key) && { value: valueObj[templateField.key] }),
1012
- }));
1013
- if (removeButton === false) {
1014
- return fieldsWithValues;
1015
- }
1016
- return [...fieldsWithValues, buildRemoveButton(removeButton)];
1017
- }
1018
- function buildObjectItemTemplate(template, removeButton) {
1019
- if (removeButton === false) {
1020
- return template;
1021
- }
1022
- return [...template, buildRemoveButton(removeButton)];
1023
- }
1024
- function buildRemoveButton(config) {
1025
- const buttonConfig = (typeof config === 'object' ? config : {});
1026
- return {
1027
- key: '__remove',
1028
- type: 'remove-array-item',
1029
- label: buttonConfig.label ?? 'Remove',
1030
- ...(buttonConfig.props && { props: buttonConfig.props }),
1031
- };
1032
- }
1033
-
1034
- function createSlot() {
1035
- let counter = 0;
1036
- const templates = new Map();
1037
- const itemOrder = [];
1038
- const primitiveFieldKey = signal(undefined, /* @ts-ignore */
1039
- ...(ngDevMode ? [{ debugName: "primitiveFieldKey" }] : /* istanbul ignore next */ []));
1040
- return {
1041
- templates,
1042
- itemOrder,
1043
- primitiveFieldKey,
1044
- nextId: () => `item-${counter++}`,
1045
- setTemplate(itemId, t) {
1046
- templates.set(itemId, t);
1047
- },
1048
- getTemplate(itemId) {
1049
- return templates.get(itemId);
1050
- },
1051
- hasTemplate(itemId) {
1052
- return templates.has(itemId);
1053
- },
1054
- setIdAt(index, itemId) {
1055
- itemOrder[index] = itemId;
1056
- },
1057
- removeAt(index) {
1058
- const [removed] = itemOrder.splice(index, 1);
1059
- if (removed !== undefined)
1060
- templates.delete(removed);
1061
- },
1062
- moveItem(fromIndex, toIndex) {
1063
- const [id] = itemOrder.splice(fromIndex, 1);
1064
- if (id !== undefined)
1065
- itemOrder.splice(toIndex, 0, id);
1066
- },
1067
- truncate(length) {
1068
- const removed = itemOrder.splice(length);
1069
- for (const id of removed)
1070
- templates.delete(id);
1071
- },
1072
- clear() {
1073
- templates.clear();
1074
- itemOrder.length = 0;
1075
- counter = 0;
1076
- primitiveFieldKey.set(undefined);
1077
- },
1078
- };
1079
- }
1080
- class ArrayItemRegistryService {
1081
- slots = new Map();
1082
- slotFor(arrayPath) {
1083
- let slot = this.slots.get(arrayPath);
1084
- if (!slot) {
1085
- slot = createSlot();
1086
- this.slots.set(arrayPath, slot);
1087
- }
1088
- return slot;
1089
- }
1090
- clearAll() {
1091
- for (const slot of this.slots.values())
1092
- slot.clear();
1093
- this.slots.clear();
1094
- }
1095
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: ArrayItemRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1096
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: ArrayItemRegistryService });
1097
- }
1098
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: ArrayItemRegistryService, decorators: [{
1099
- type: Injectable
1100
- }] });
1101
-
1102
- class SchemaRegistryService {
1103
- registeredSchemas = new Map();
1104
- registerSchema(schema) {
1105
- this.registeredSchemas.set(schema.name, schema);
1106
- }
1107
- getSchema(name) {
1108
- return this.registeredSchemas.get(name);
1109
- }
1110
- getAllSchemas() {
1111
- return new Map(this.registeredSchemas);
1112
- }
1113
- clearSchemas() {
1114
- this.registeredSchemas.clear();
1115
- }
1116
- resolveSchema(schema) {
1117
- if (typeof schema === 'string') {
1118
- return this.getSchema(schema) || null;
1119
- }
1120
- return schema;
1121
- }
1122
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: SchemaRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1123
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: SchemaRegistryService });
1124
- }
1125
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: SchemaRegistryService, decorators: [{
1126
- type: Injectable
1127
- }] });
1128
-
1129
- const DEV_MODE = typeof ngDevMode === 'undefined' || !!ngDevMode;
1130
-
1131
- const DEFAULT_DEBOUNCE_MS = 500;
1132
- function createDebouncedEffect(source, callback, options) {
1133
- const { ms = DEFAULT_DEBOUNCE_MS, injector } = options;
1134
- const source$ = toObservable(source, { injector }).pipe(debounceTime(ms), distinctUntilChanged(), tap((value) => callback(value)));
1135
- return toSignal(source$, { injector });
1136
- }
1137
- function createDebouncedSignal(source, options) {
1138
- const { ms = DEFAULT_DEBOUNCE_MS, injector } = options;
1139
- const source$ = toObservable(source, { injector }).pipe(debounceTime(ms), distinctUntilChanged());
1140
- return toSignal(source$, { injector });
1141
- }
1142
-
1143
- function getConfigTrigger(config) {
1144
- if (!isStateLogicConfig(config)) {
1145
- return 'onChange';
1146
- }
1147
- return config.trigger ?? 'onChange';
1148
- }
1149
- function getConfigDebounceMs(config) {
1150
- if (!isStateLogicConfig(config)) {
1151
- return undefined;
1152
- }
1153
- if (config.trigger === 'debounced') {
1154
- return config.debounceMs;
1155
- }
1156
- return undefined;
1157
- }
1158
- function applyLogic(config, fieldPath) {
1159
- if (isDerivationLogicConfig(config))
1160
- return;
1161
- if (!isStateLogicConfig(config)) {
1162
- if (DEV_MODE) {
1163
- console.warn(`[Dynamic Forms] Unrecognized logic config type '${config.type}' in applyLogic. ` +
1164
- 'This config will be ignored. If this is a new logic type, ensure it is handled explicitly.');
1165
- }
1166
- return;
1167
- }
1168
- const path = fieldPath;
1169
- const trigger = getConfigTrigger(config);
1170
- const debounceMs = getConfigDebounceMs(config) ?? DEFAULT_DEBOUNCE_MS;
1171
- if (typeof config.condition === 'boolean') {
1172
- applyLogicFn(config.type, path, () => config.condition);
1173
- return;
1174
- }
1175
- if (isFormStateCondition(config.condition)) {
1176
- if (config.condition === 'formInvalid' || config.condition === 'pageInvalid') {
1177
- const reason = config.condition === 'formInvalid'
1178
- ? `it would create a validation cycle against the form's own validity`
1179
- : `a value field has no page context for it to resolve against`;
1180
- throw new DynamicFormError(`The '${config.condition}' form-state condition is not supported on value-field logic because ${reason}. ` +
1181
- `Use 'formSubmitting', or apply this condition to a button or container instead.`);
1182
- }
1183
- applyLogicFn(config.type, path, createFormStateLogicFunction(config.condition));
1184
- return;
1185
- }
1186
- const logicFn = trigger === 'debounced'
1187
- ? createDebouncedLogicFunction(config.condition, debounceMs)
1188
- : createLogicFunction(config.condition);
1189
- applyLogicFn(config.type, path, logicFn);
1190
- }
1191
- function applyLogicFn(type, path, logicFn) {
1192
- switch (type) {
1193
- case 'hidden':
1194
- hidden(path, logicFn);
1195
- break;
1196
- case 'readonly':
1197
- readonly(path, logicFn);
1198
- break;
1199
- case 'disabled':
1200
- disabled(path, logicFn);
1201
- break;
1202
- case 'required':
1203
- required(path, { when: logicFn });
1204
- break;
1205
- default: {
1206
- const _exhaustive = type;
1207
- throw new DynamicFormError(`Unhandled state logic type: ${_exhaustive}`);
1208
- }
1209
- }
1210
- }
1211
- function applyMultipleLogic(configs, fieldPath) {
1212
- configs.forEach((config) => applyLogic(config, fieldPath));
1213
- }
1214
-
1215
- function createTypePredicateFunction(predicate, logger) {
1216
- return (value) => {
1217
- try {
1218
- const result = ExpressionParser.evaluate(predicate, { value });
1219
- return Boolean(result);
1220
- }
1221
- catch (error) {
1222
- logger.error('Error evaluating type predicate:', predicate, error);
1223
- return false;
1224
- }
1225
- };
1226
- }
1227
-
1228
- function applySchema(config, fieldPath) {
1229
- const logger = inject(DynamicFormLogger);
1230
- const schemaRegistry = inject(SchemaRegistryService);
1231
- const schema = schemaRegistry.resolveSchema(config.schema);
1232
- if (!schema) {
1233
- const availableSchemas = Array.from(schemaRegistry.getAllSchemas().keys()).join(', ') || '<none>';
1234
- logger.error(`Schema not found: '${config.schema}'. ` +
1235
- `Available schemas: ${availableSchemas}. ` +
1236
- `Ensure the schema is registered in your schema registry.`);
1237
- return;
1238
- }
1239
- const schemaFn = createSchemaFunction(schema, logger);
1240
- const path = fieldPath;
1241
- switch (config.type) {
1242
- case 'apply':
1243
- apply(path, schemaFn);
1244
- break;
1245
- case 'applyWhen':
1246
- if (config.condition) {
1247
- const conditionFn = createLogicFunction(config.condition);
1248
- applyWhen(path, conditionFn, schemaFn);
1249
- }
1250
- break;
1251
- case 'applyWhenValue':
1252
- if (config.typePredicate) {
1253
- const predicate = createTypePredicateFunction(config.typePredicate, logger);
1254
- applyWhenValue(path, predicate, schemaFn);
1255
- }
1256
- break;
1257
- case 'applyEach':
1258
- applyEach(path, schemaFn);
1259
- break;
1260
- }
1261
- }
1262
- function createSchemaFunction(schema, logger) {
1263
- return (path) => {
1264
- schema.validators?.forEach((validatorConfig) => {
1265
- if (requiresTreeValidation(validatorConfig)) {
1266
- logger?.warn(`Validator "${validatorConfig.type}" in schema definition "${schema.name}" uses a cross-field expression, ` +
1267
- `which is not supported on schema definition validators; it was skipped.`);
1268
- return;
1269
- }
1270
- applyValidator(validatorConfig, path);
1271
- });
1272
- schema.logic?.forEach((logicConfig) => {
1273
- applyLogic(logicConfig, path);
1274
- });
1275
- schema.subSchemas?.forEach((subSchemaConfig) => {
1276
- applySchema(subSchemaConfig, path);
1277
- });
1278
- };
1279
- }
1280
-
1281
- function combineAncestorHiddenLogics(logics) {
1282
- if (logics.length === 0)
1283
- return undefined;
1284
- if (logics.length === 1)
1285
- return logics[0];
1286
- return (ctx) => logics.some((fn) => fn(ctx));
1287
- }
1288
- function resolveFieldOwnContext(fieldDef, parentContext) {
1289
- if (fieldDef.validateWhenHidden === undefined)
1290
- return parentContext;
1291
- if (fieldDef.validateWhenHidden === parentContext.validateWhenHidden)
1292
- return parentContext;
1293
- return {
1294
- ...parentContext,
1295
- validateWhenHidden: fieldDef.validateWhenHidden,
1296
- };
1297
- }
1298
- function resolveDescendantContext(fieldDef, ownContext) {
1299
- let ancestorAlwaysHidden = ownContext.ancestorAlwaysHidden;
1300
- let ancestorHiddenLogics = ownContext.ancestorHiddenLogics;
1301
- let mutated = false;
1302
- if (fieldDef.hidden === true && !ancestorAlwaysHidden) {
1303
- ancestorAlwaysHidden = true;
1304
- mutated = true;
1305
- }
1306
- if (fieldDef.logic) {
1307
- for (const logic of fieldDef.logic) {
1308
- if (!isStateLogicConfig(logic) || logic.type !== 'hidden')
1309
- continue;
1310
- const condition = logic.condition;
1311
- if (condition === true) {
1312
- if (!ancestorAlwaysHidden) {
1313
- ancestorAlwaysHidden = true;
1314
- mutated = true;
1315
- }
1316
- continue;
1317
- }
1318
- if (condition === false)
1319
- continue;
1320
- if (typeof condition === 'string')
1321
- continue;
1322
- const fn = createLogicFunction(condition);
1323
- ancestorHiddenLogics = ancestorHiddenLogics.length === 0 ? [fn] : [...ancestorHiddenLogics, fn];
1324
- mutated = true;
1325
- }
1326
- }
1327
- if (!mutated)
1328
- return ownContext;
1329
- return {
1330
- validateWhenHidden: ownContext.validateWhenHidden,
1331
- ancestorAlwaysHidden,
1332
- ancestorHiddenLogics,
1333
- };
1334
- }
1335
-
1336
- const DEFAULT_FIELD_TREE_MAPPING_CONTEXT = {
1337
- validateWhenHidden: false,
1338
- ancestorAlwaysHidden: false,
1339
- ancestorHiddenLogics: [],
1340
- };
1341
- function applyStringValidators(path, config) {
1342
- if (config.email) {
1343
- email(path);
1344
- }
1345
- if (config.minLength !== undefined) {
1346
- minLength(path, config.minLength);
1347
- }
1348
- if (config.maxLength !== undefined) {
1349
- maxLength(path, config.maxLength);
1350
- }
1351
- if (config.pattern) {
1352
- let regex;
1353
- if (typeof config.pattern === 'string') {
1354
- try {
1355
- regex = new RegExp(config.pattern);
1356
- }
1357
- catch (e) {
1358
- throw new DynamicFormError(`Invalid regex pattern: '${config.pattern}' — ${e instanceof Error ? e.message : String(e)}`);
1359
- }
1360
- }
1361
- else {
1362
- regex = config.pattern;
1363
- }
1364
- pattern(path, regex);
1365
- }
1366
- }
1367
- function applyNumberValidators(path, config) {
1368
- if (config.min !== undefined) {
1369
- min(path, config.min);
1370
- }
1371
- if (config.max !== undefined) {
1372
- max(path, config.max);
1373
- }
1374
- }
1375
- function getStringValidationConfig(fieldDef) {
1376
- if (!fieldDef.email && fieldDef.minLength === undefined && fieldDef.maxLength === undefined && !fieldDef.pattern) {
1377
- return undefined;
1378
- }
1379
- return {
1380
- email: fieldDef.email,
1381
- minLength: fieldDef.minLength,
1382
- maxLength: fieldDef.maxLength,
1383
- pattern: fieldDef.pattern,
1384
- };
1385
- }
1386
- function getNumberValidationConfig(fieldDef) {
1387
- const minVal = fieldDef.min ?? fieldDef.minValue;
1388
- const maxVal = fieldDef.max ?? fieldDef.maxValue;
1389
- if (minVal === undefined && maxVal === undefined) {
1390
- return undefined;
1391
- }
1392
- return { min: minVal, max: maxVal };
1393
- }
1394
- function mapFieldToForm(fieldDef, fieldPath, inheritedContext = DEFAULT_FIELD_TREE_MAPPING_CONTEXT) {
1395
- const ownContext = resolveFieldOwnContext(fieldDef, inheritedContext);
1396
- if (isPageField(fieldDef) || isRowField(fieldDef) || isGenericContainerField(fieldDef)) {
1397
- const descendantContext = resolveDescendantContext(fieldDef, ownContext);
1398
- mapContainerChildren(fieldDef.fields, fieldPath, descendantContext);
1399
- return;
1400
- }
1401
- if (isGroupField(fieldDef)) {
1402
- const descendantContext = resolveDescendantContext(fieldDef, ownContext);
1403
- mapContainerChildren(fieldDef.fields, fieldPath, descendantContext);
1404
- return;
1405
- }
1406
- if (isArrayField(fieldDef)) {
1407
- const descendantContext = resolveDescendantContext(fieldDef, ownContext);
1408
- mapArrayFieldToForm(fieldDef, fieldPath, descendantContext);
1409
- return;
1410
- }
1411
- mapLeafField(fieldDef, fieldPath, ownContext);
1412
- }
1413
- function mapContainerChildren(fields, parentPath, inheritedContext) {
1414
- if (!fields)
1415
- return;
1416
- const pathRecord = parentPath;
1417
- for (const field of fields) {
1418
- if (isPageField(field) || isRowField(field) || isGenericContainerField(field)) {
1419
- const layoutOwn = resolveFieldOwnContext(field, inheritedContext);
1420
- const layoutDescendant = resolveDescendantContext(field, layoutOwn);
1421
- mapContainerChildren(field.fields, parentPath, layoutDescendant);
1422
- continue;
1423
- }
1424
- if (!field.key)
1425
- continue;
1426
- const childPath = pathRecord[field.key];
1427
- if (childPath) {
1428
- mapFieldToForm(field, childPath, inheritedContext);
1429
- }
1430
- }
1431
- }
1432
- function isValidationStateLogic(config) {
1433
- return isStateLogicConfig(config) && config.type === 'required';
1434
- }
1435
- function mapLeafField(fieldDef, fieldPath, context) {
1436
- const validationField = fieldDef;
1437
- const path = fieldPath;
1438
- applyFieldState(fieldDef, path);
1439
- if (validationField.logic) {
1440
- for (const config of validationField.logic) {
1441
- if (!isValidationStateLogic(config)) {
1442
- applyLogic(config, fieldPath);
1443
- }
1444
- }
1445
- }
1446
- if (validationField.schemas) {
1447
- for (const config of validationField.schemas) {
1448
- applySchema(config, fieldPath);
1449
- }
1450
- }
1451
- const applyValidation = (validationPath) => {
1452
- applySimpleValidationRules(validationField, validationPath);
1453
- if (validationField.validators) {
1454
- for (const config of validationField.validators) {
1455
- applyValidator(config, validationPath);
1456
- }
1457
- }
1458
- if (validationField.logic) {
1459
- for (const config of validationField.logic) {
1460
- if (isValidationStateLogic(config)) {
1461
- applyLogic(config, validationPath);
1462
- }
1463
- }
1464
- }
1465
- };
1466
- if (context.validateWhenHidden) {
1467
- applyValidation(path);
1468
- return;
1469
- }
1470
- if (context.ancestorAlwaysHidden) {
1471
- return;
1472
- }
1473
- const ancestorHiddenLogic = combineAncestorHiddenLogics(context.ancestorHiddenLogics);
1474
- applyWhen(path, (ctx) => {
1475
- if (ctx.state.hidden())
1476
- return false;
1477
- if (ancestorHiddenLogic && ancestorHiddenLogic(ctx))
1478
- return false;
1479
- return true;
1480
- }, schema((subPath) => applyValidation(subPath)));
1481
- }
1482
- function applySimpleValidationRules(fieldDef, path) {
1483
- if (fieldDef.required) {
1484
- required(path);
1485
- }
1486
- const stringConfig = getStringValidationConfig(fieldDef);
1487
- if (stringConfig) {
1488
- applyStringValidators(path, stringConfig);
1489
- }
1490
- const numberConfig = getNumberValidationConfig(fieldDef);
1491
- if (numberConfig) {
1492
- applyNumberValidators(path, numberConfig);
1493
- }
1494
- }
1495
- function applyFieldState(fieldDef, path) {
1496
- if (fieldDef.disabled) {
1497
- disabled(path);
1498
- }
1499
- if (fieldDef.readonly) {
1500
- readonly(path);
1501
- }
1502
- if (fieldDef.hidden) {
1503
- hidden(path, () => true);
1504
- }
1505
- }
1506
- function mapArrayFieldToForm(arrayField, fieldPath, context) {
1507
- if (!isArrayField(arrayField)) {
1508
- return;
1509
- }
1510
- if (arrayField.minLength !== undefined) {
1511
- minLength(fieldPath, arrayField.minLength);
1512
- }
1513
- if (arrayField.maxLength !== undefined) {
1514
- maxLength(fieldPath, arrayField.maxLength);
1515
- }
1516
- let itemDefinitions = arrayField.fields;
1517
- if (!itemDefinitions || itemDefinitions.length === 0) {
1518
- const metadataTemplate = getNormalizedArrayMetadata(arrayField)?.template;
1519
- if (metadataTemplate) {
1520
- itemDefinitions = [
1521
- Array.isArray(metadataTemplate) ? [...metadataTemplate] : metadataTemplate,
1522
- ];
1523
- }
1524
- }
1525
- if (!itemDefinitions || itemDefinitions.length === 0) {
1526
- const emptyItemSchema = schema(() => {
1527
- });
1528
- applyEach(fieldPath, emptyItemSchema);
1529
- return;
1530
- }
1531
- let hasPrimitiveItems = false;
1532
- const allObjectFields = [];
1533
- for (const itemDef of itemDefinitions) {
1534
- if (Array.isArray(itemDef)) {
1535
- collectFieldsFromObjectItem(itemDef, allObjectFields);
1536
- }
1537
- else if (isPageField(itemDef) ||
1538
- isRowField(itemDef) ||
1539
- isGenericContainerField(itemDef)) {
1540
- collectFieldsFromObjectItem([itemDef], allObjectFields);
1541
- }
1542
- else {
1543
- hasPrimitiveItems = true;
1544
- }
1545
- }
1546
- if (hasPrimitiveItems && allObjectFields.length === 0) {
1547
- const primitiveItemSchema = schema(() => {
1548
- });
1549
- applyEach(fieldPath, primitiveItemSchema);
1550
- return;
1551
- }
1552
- const itemSchema = schema((itemPath) => {
1553
- const pathRecord = itemPath;
1554
- for (const templateField of allObjectFields) {
1555
- if (isRowField(templateField) || isPageField(templateField) || isGenericContainerField(templateField)) {
1556
- const layoutOwn = resolveFieldOwnContext(templateField, context);
1557
- const layoutDescendant = resolveDescendantContext(templateField, layoutOwn);
1558
- mapContainerChildren(templateField.fields, itemPath, layoutDescendant);
1559
- }
1560
- else if (isGroupField(templateField)) {
1561
- const groupKey = templateField.key;
1562
- const groupOwn = resolveFieldOwnContext(templateField, context);
1563
- const groupDescendant = resolveDescendantContext(templateField, groupOwn);
1564
- if (groupKey) {
1565
- const groupPath = pathRecord[groupKey];
1566
- if (groupPath) {
1567
- mapContainerChildren(templateField.fields, groupPath, groupDescendant);
1568
- }
1569
- }
1570
- else {
1571
- mapContainerChildren(templateField.fields, itemPath, groupDescendant);
1572
- }
1573
- }
1574
- else {
1575
- const fieldKey = templateField.key;
1576
- if (fieldKey) {
1577
- const fieldPathForKey = pathRecord[fieldKey];
1578
- if (fieldPathForKey) {
1579
- mapFieldToForm(templateField, fieldPathForKey, context);
1580
- }
1581
- }
1582
- }
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);
1583
938
  }
939
+ return buildPrimitiveItem(template, v);
1584
940
  });
1585
- applyEach(fieldPath, itemSchema);
1586
- }
1587
- function collectFieldsFromObjectItem(itemFields, allFields) {
1588
- const seenKeys = new Set(allFields.map((f) => f.key).filter(Boolean));
1589
- for (const field of itemFields) {
1590
- if (isRowField(field) || isPageField(field) || isGenericContainerField(field)) {
1591
- const containerKey = `__container_${field.type}_${JSON.stringify(field.fields?.map((f) => f.key))}`;
1592
- if (!seenKeys.has(containerKey)) {
1593
- seenKeys.add(containerKey);
1594
- allFields.push(field);
1595
- }
1596
- }
1597
- else {
1598
- const key = field.key;
1599
- if (key && !seenKeys.has(key)) {
1600
- seenKeys.add(key);
1601
- allFields.push(field);
1602
- }
1603
- }
1604
- }
1605
- }
1606
-
1607
- function getFieldDefaultValue(field, registry) {
1608
- const valueHandling = getFieldValueHandling(field.type, registry);
1609
- if (valueHandling === 'exclude') {
1610
- return undefined;
1611
- }
1612
- if (valueHandling === 'flatten' && 'fields' in field && field.fields) {
1613
- const childFields = field.fields;
1614
- const flattenedValues = {};
1615
- for (const childField of childFields) {
1616
- const childValueHandling = getFieldValueHandling(childField.type, registry);
1617
- if (childValueHandling === 'exclude')
1618
- continue;
1619
- const childValue = getFieldDefaultValue(childField, registry);
1620
- if (childValue === undefined)
1621
- continue;
1622
- if (childValueHandling === 'flatten' && childValue && typeof childValue === 'object' && !Array.isArray(childValue)) {
1623
- Object.assign(flattenedValues, childValue);
1624
- }
1625
- else if ('key' in childField && childField.key) {
1626
- flattenedValues[childField.key] = childValue;
1627
- }
1628
- }
1629
- return Object.keys(flattenedValues).length > 0 ? flattenedValues : undefined;
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;
1630
952
  }
1631
- if (field.type === 'group' && 'fields' in field) {
1632
- const fields = field.fields;
1633
- if (!fields || fields.length === 0) {
1634
- return undefined;
1635
- }
1636
- const groupDefaults = {};
1637
- for (const childField of fields) {
1638
- const childValueHandling = getFieldValueHandling(childField.type, registry);
1639
- if (childValueHandling === 'exclude')
1640
- continue;
1641
- const childValue = getFieldDefaultValue(childField, registry);
1642
- if (childValueHandling === 'flatten') {
1643
- if (childValue && typeof childValue === 'object' && !Array.isArray(childValue)) {
1644
- Object.assign(groupDefaults, childValue);
1645
- }
1646
- }
1647
- else if ('key' in childField && childField.key) {
1648
- if (childValue !== undefined) {
1649
- groupDefaults[childField.key] = childValue;
1650
- }
1651
- }
1652
- }
1653
- return groupDefaults;
953
+ if (minLength !== undefined) {
954
+ arrayFieldObj['minLength'] = minLength;
1654
955
  }
1655
- const valueField = field;
1656
- const isNullable = valueField.nullable === true;
1657
- if ('value' in field) {
1658
- if (valueField.value !== null && valueField.value !== undefined) {
1659
- return valueField.value;
1660
- }
1661
- if (valueField.value === null && isNullable) {
1662
- return null;
1663
- }
1664
- }
1665
- if (isNullable) {
1666
- return null;
956
+ if (maxLength !== undefined) {
957
+ arrayFieldObj['maxLength'] = maxLength;
1667
958
  }
1668
- if (field.type === 'checkbox') {
1669
- return false;
959
+ if (wrappers !== undefined) {
960
+ arrayFieldObj['wrappers'] = wrappers;
1670
961
  }
1671
- if (field.type === 'array') {
1672
- const arrayField = field;
1673
- const itemDefinitions = arrayField.fields;
1674
- if (!itemDefinitions || itemDefinitions.length === 0) {
1675
- return [];
1676
- }
1677
- return itemDefinitions.map((itemDef) => {
1678
- if (!Array.isArray(itemDef)) {
1679
- return getFieldDefaultValue(itemDef, registry);
1680
- }
1681
- const itemFields = itemDef;
1682
- let itemValue = {};
1683
- for (const templateField of itemFields) {
1684
- const fieldValue = getFieldDefaultValue(templateField, registry);
1685
- const fieldValueHandling = getFieldValueHandling(templateField.type, registry);
1686
- if (templateField.type === 'group' && 'key' in templateField && templateField.key) {
1687
- itemValue[templateField.key] = fieldValue;
1688
- }
1689
- else if (templateField.type === 'row' || templateField.type === 'container') {
1690
- if (fieldValue && typeof fieldValue === 'object') {
1691
- itemValue = { ...itemValue, ...fieldValue };
1692
- }
1693
- }
1694
- else if (fieldValueHandling === 'include' && 'key' in templateField && templateField.key) {
1695
- itemValue[templateField.key] = fieldValue;
1696
- }
1697
- }
1698
- return itemValue;
1699
- });
962
+ if (skipAutoWrappers !== undefined) {
963
+ arrayFieldObj['skipAutoWrappers'] = skipAutoWrappers;
1700
964
  }
1701
- if (field.type === 'input' && 'props' in field) {
1702
- const props = field.props;
1703
- if (props?.type === 'number') {
1704
- return NaN;
1705
- }
965
+ if (skipDefaultWrappers !== undefined) {
966
+ arrayFieldObj['skipDefaultWrappers'] = skipDefaultWrappers;
1706
967
  }
1707
- return '';
1708
- }
1709
-
1710
- function applyFormLevelSchema(path, formLevelSchema) {
1711
- if (isStandardSchemaMarker(formLevelSchema)) {
1712
- validateStandardSchema(path, formLevelSchema.schema);
968
+ if (validators !== undefined) {
969
+ arrayFieldObj['validators'] = validators;
1713
970
  }
1714
- else if (typeof formLevelSchema === 'function') {
1715
- formLevelSchema(path);
971
+ if (validationMessages !== undefined) {
972
+ arrayFieldObj['validationMessages'] = validationMessages;
1716
973
  }
1717
- }
1718
- function createFormLevelSchema(formLevelSchema) {
1719
- return schema((path) => {
1720
- applyFormLevelSchema(path, formLevelSchema);
974
+ if (required !== undefined) {
975
+ arrayFieldObj['required'] = required;
976
+ }
977
+ if (validateWhenHidden !== undefined) {
978
+ arrayFieldObj['validateWhenHidden'] = validateWhenHidden;
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,
1721
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 };
1722
1002
  }
1723
-
1724
- const VALIDATION_EXECUTION_DEFAULTS = new InjectionToken('VALIDATION_EXECUTION_DEFAULTS', {
1725
- providedIn: 'root',
1726
- factory: () => ({
1727
- validateWhenHidden: false,
1728
- }),
1729
- });
1730
-
1731
- function getFieldTreeByKey(ctx, key) {
1732
- if (!key.includes('.')) {
1733
- return ctx.fieldTree[key];
1734
- }
1735
- const parts = key.split('.');
1736
- let current = ctx.fieldTree;
1737
- for (const part of parts) {
1738
- if (!current || (typeof current !== 'object' && typeof current !== 'function')) {
1739
- return undefined;
1740
- }
1741
- current = current[part];
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;
1742
1013
  }
1743
- return current;
1014
+ return [...fieldsWithValues, buildRemoveButton(removeButton)];
1744
1015
  }
1745
- function createSchemaFromFields(fields, registry, optionsOrValidators) {
1746
- const functionRegistry = inject(FunctionRegistryService);
1747
- const logger = inject(DynamicFormLogger);
1748
- const validationExecutionDefaults = inject(VALIDATION_EXECUTION_DEFAULTS);
1749
- const options = Array.isArray(optionsOrValidators)
1750
- ? { crossFieldValidators: optionsOrValidators }
1751
- : (optionsOrValidators ?? {});
1752
- const { crossFieldValidators, formLevelSchema } = options;
1753
- const rootContext = {
1754
- validateWhenHidden: options.validateWhenHidden ?? validationExecutionDefaults.validateWhenHidden,
1755
- ancestorAlwaysHidden: false,
1756
- ancestorHiddenLogics: [],
1016
+ function buildObjectItemTemplate(template, removeButton) {
1017
+ if (removeButton === false) {
1018
+ return template;
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 }),
1757
1029
  };
1758
- return schema((path) => {
1759
- for (const fieldDef of fields) {
1760
- const valueHandling = getFieldValueHandling(fieldDef.type, registry);
1761
- if (valueHandling === 'exclude') {
1762
- continue;
1763
- }
1764
- if (valueHandling === 'flatten' && hasChildFields(fieldDef)) {
1765
- const flattenedOwn = resolveFieldOwnContext(fieldDef, rootContext);
1766
- const flattenedContext = resolveDescendantContext(fieldDef, flattenedOwn);
1767
- for (const childField of normalizeFieldsArray(fieldDef.fields)) {
1768
- if (!childField.key)
1769
- continue;
1770
- const childPath = path[childField.key];
1771
- if (childPath) {
1772
- mapFieldToForm(childField, childPath, flattenedContext);
1773
- }
1774
- }
1775
- continue;
1776
- }
1777
- const fieldPath = path[fieldDef.key];
1778
- if (!fieldPath) {
1779
- continue;
1030
+ }
1031
+
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);
1780
1042
  }
1781
- mapFieldToForm(fieldDef, fieldPath, rootContext);
1782
1043
  }
1783
- if (crossFieldValidators && crossFieldValidators.length > 0) {
1784
- applyCrossFieldTreeValidator(path, crossFieldValidators, functionRegistry, logger);
1785
- }
1786
- if (formLevelSchema) {
1787
- applyFormLevelSchema(path, formLevelSchema);
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
+ }
1788
1085
  }
1789
- });
1790
- }
1791
- function applyCrossFieldTreeValidator(rootPath, validators, functionRegistry, logger) {
1792
- const customFunctions = functionRegistry.getCustomFunctions();
1793
- const customValidators = validators.filter((entry) => {
1794
- if (entry.config.type !== 'custom') {
1795
- logger.warn(`Unexpected non-custom validator in cross-field tree for "${entry.sourceFieldKey}"; ignoring.`);
1796
- return false;
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));
1797
1091
  }
1798
- return true;
1799
- });
1800
- validateTree(rootPath, (ctx) => {
1801
- if (customValidators.length === 0) {
1802
- return null; }
1803
- const formValue = ctx.value();
1804
- const errors = [];
1805
- for (const entry of customValidators) {
1806
- const { sourceFieldKey, config } = entry;
1807
- try {
1808
- const error = evaluateCrossFieldValidator(entry, formValue, sourceFieldKey, ctx, customFunctions, logger);
1809
- if (error) {
1810
- errors.push(error);
1811
- }
1812
- }
1813
- catch (err) {
1814
- logger.error(`Error evaluating cross-field validator for ${sourceFieldKey}:`, err);
1815
- const targetField = getFieldTreeByKey(ctx, sourceFieldKey);
1816
- if (targetField) {
1817
- const customConfig = config;
1818
- errors.push({
1819
- kind: customConfig.kind || config.type || 'custom',
1820
- fieldTree: targetField,
1821
- });
1822
- }
1823
- }
1092
+ for (const type of wrapperTypes) {
1093
+ if (wrapperCache.has(type))
1094
+ continue;
1095
+ started.push(loadWrapperComponent(type, wrapperRegistry, wrapperCache, wrapperLoadCache).catch(() => undefined));
1824
1096
  }
1825
- return errors.length > 0 ? errors : null;
1826
- });
1827
- }
1828
- function evaluateCrossFieldValidator(entry, formValue, sourceFieldKey, ctx, customFunctions, logger) {
1829
- const { config } = entry;
1830
- const fieldValue = getNestedValue(formValue, sourceFieldKey);
1831
- const evaluationContext = {
1832
- fieldValue,
1833
- formValue,
1834
- fieldPath: sourceFieldKey,
1835
- customFunctions,
1836
- logger,
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,
1837
1110
  };
1838
- return evaluateCustomCrossFieldValidator(config, evaluationContext, sourceFieldKey, ctx);
1839
1111
  }
1840
- function evaluateCustomCrossFieldValidator(config, evaluationContext, sourceFieldKey, ctx) {
1841
- if (!config.expression) {
1842
- return null;
1843
- }
1844
- const { fieldValue, formValue, logger, customFunctions } = evaluationContext;
1845
- if (config.when) {
1846
- const conditionMet = evaluateCondition(config.when, {
1847
- fieldValue,
1848
- formValue,
1849
- fieldPath: sourceFieldKey,
1850
- customFunctions: customFunctions || {},
1851
- logger,
1852
- });
1853
- if (!conditionMet) {
1854
- return null; }
1855
- }
1856
- const result = ExpressionParser.evaluate(config.expression, evaluationContext);
1857
- if (result) {
1858
- return null;
1859
- }
1860
- const targetField = getFieldTreeByKey(ctx, sourceFieldKey);
1861
- if (!targetField) {
1862
- evaluationContext.logger.warn(`Cross-field validator references non-existent field "${sourceFieldKey}"`);
1863
- return null;
1112
+
1113
+ class PageChangeEvent {
1114
+ currentPageIndex;
1115
+ totalPages;
1116
+ previousPageIndex;
1117
+ type = 'page-change';
1118
+ constructor(
1119
+ currentPageIndex,
1120
+ totalPages,
1121
+ previousPageIndex) {
1122
+ this.currentPageIndex = currentPageIndex;
1123
+ this.totalPages = totalPages;
1124
+ this.previousPageIndex = previousPageIndex;
1864
1125
  }
1865
- const errorObj = {
1866
- kind: config.kind || 'custom',
1867
- fieldTree: targetField,
1868
- };
1869
- if (config.errorParams) {
1870
- for (const [key, expression] of Object.entries(config.errorParams)) {
1871
- try {
1872
- errorObj[key] = ExpressionParser.evaluate(expression, evaluationContext);
1873
- }
1874
- catch (error) {
1875
- evaluationContext.logger.warn(`Failed to evaluate error param expression "${key}": ${expression}`, error);
1876
- }
1877
- }
1126
+ }
1127
+
1128
+ class PagerStateEvent {
1129
+ state;
1130
+ type = 'pager-state';
1131
+ constructor(state) {
1132
+ this.state = state;
1878
1133
  }
1879
- return errorObj;
1880
1134
  }
1881
- function fieldsToDefaultValues(fields, registry) {
1882
- const defaultValues = {};
1135
+
1136
+ const PAGE_PRELOAD_WINDOW = new InjectionToken('PAGE_PRELOAD_WINDOW', {
1137
+ providedIn: 'root',
1138
+ factory: () => 1,
1139
+ });
1140
+
1141
+ function collectLeafFieldKeys(fields) {
1142
+ const keys = [];
1883
1143
  for (const field of fields) {
1884
- if (!field.key)
1885
- continue;
1886
- const valueHandling = getFieldValueHandling(field.type, registry);
1887
- if (valueHandling === 'flatten') {
1888
- continue;
1144
+ if (isRowField(field) || isGenericContainerField(field)) {
1145
+ keys.push(...collectLeafFieldKeys(field.fields));
1889
1146
  }
1890
- const value = getFieldDefaultValue(field, registry);
1891
- if (value !== undefined) {
1892
- defaultValues[field.key] = value;
1147
+ else if (field.key) {
1148
+ keys.push(field.key);
1893
1149
  }
1894
1150
  }
1895
- return defaultValues;
1151
+ return keys;
1896
1152
  }
1897
1153
 
1898
- function createEmptyCollection() {
1154
+ function createSlot() {
1155
+ let counter = 0;
1156
+ const templates = new Map();
1157
+ const itemOrder = [];
1158
+ const primitiveFieldKey = signal(undefined, /* @ts-ignore */
1159
+ ...(ngDevMode ? [{ debugName: "primitiveFieldKey" }] : /* istanbul ignore next */ []));
1899
1160
  return {
1900
- validators: [],
1901
- logic: [],
1902
- schemas: [],
1161
+ templates,
1162
+ itemOrder,
1163
+ primitiveFieldKey,
1164
+ nextId: () => `item-${counter++}`,
1165
+ setTemplate(itemId, t) {
1166
+ templates.set(itemId, t);
1167
+ },
1168
+ getTemplate(itemId) {
1169
+ return templates.get(itemId);
1170
+ },
1171
+ hasTemplate(itemId) {
1172
+ return templates.has(itemId);
1173
+ },
1174
+ setIdAt(index, itemId) {
1175
+ itemOrder[index] = itemId;
1176
+ },
1177
+ removeAt(index) {
1178
+ const [removed] = itemOrder.splice(index, 1);
1179
+ if (removed !== undefined)
1180
+ templates.delete(removed);
1181
+ },
1182
+ moveItem(fromIndex, toIndex) {
1183
+ const [id] = itemOrder.splice(fromIndex, 1);
1184
+ if (id !== undefined)
1185
+ itemOrder.splice(toIndex, 0, id);
1186
+ },
1187
+ truncate(length) {
1188
+ const removed = itemOrder.splice(length);
1189
+ for (const id of removed)
1190
+ templates.delete(id);
1191
+ },
1192
+ clear() {
1193
+ templates.clear();
1194
+ itemOrder.length = 0;
1195
+ counter = 0;
1196
+ primitiveFieldKey.set(undefined);
1197
+ },
1903
1198
  };
1904
1199
  }
1905
- function collectCrossFieldEntries(fields) {
1906
- const collection = createEmptyCollection();
1907
- traverseFields(fields, collection, '');
1908
- return collection;
1909
- }
1910
- function traverseFields(fields, collection, pathPrefix) {
1911
- for (const field of fields) {
1912
- collectFromField(field, collection, pathPrefix);
1913
- if (hasChildFields(field)) {
1914
- const groupKey = isGroupField(field) ? field.key : undefined;
1915
- const childPrefix = groupKey ? (pathPrefix ? `${pathPrefix}.${groupKey}` : groupKey) : pathPrefix;
1916
- traverseFields(normalizeFieldsArray(field.fields), collection, childPrefix);
1917
- }
1918
- }
1919
- }
1920
- function collectFromField(field, collection, pathPrefix) {
1921
- const fieldKey = field.key;
1922
- if (!fieldKey)
1923
- return;
1924
- const fullKey = pathPrefix ? `${pathPrefix}.${fieldKey}` : fieldKey;
1925
- const validationField = field;
1926
- if (validationField.validators) {
1927
- for (const config of validationField.validators) {
1928
- const entry = tryCreateValidatorEntry(fullKey, config);
1929
- if (entry) {
1930
- collection.validators.push(entry);
1931
- }
1932
- }
1933
- }
1934
- if (validationField.logic) {
1935
- for (const config of validationField.logic) {
1936
- const entry = tryCreateLogicEntry(fullKey, config);
1937
- if (entry) {
1938
- collection.logic.push(entry);
1939
- }
1940
- }
1941
- }
1942
- if (validationField.schemas) {
1943
- for (const config of validationField.schemas) {
1944
- const entry = tryCreateSchemaEntry(fullKey, config);
1945
- if (entry) {
1946
- collection.schemas.push(entry);
1947
- }
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);
1948
1207
  }
1208
+ return slot;
1949
1209
  }
1950
- }
1951
- function tryCreateValidatorEntry(fieldKey, config) {
1952
- if (!requiresTreeValidation(config)) {
1953
- return null; }
1954
- if (config.type === 'custom') {
1955
- const customConfig = config;
1956
- return {
1957
- sourceFieldKey: fieldKey,
1958
- config,
1959
- dependsOn: extractStringDependencies(customConfig.expression || ''),
1960
- category: 'validator',
1961
- };
1210
+ clearAll() {
1211
+ for (const slot of this.slots.values())
1212
+ slot.clear();
1213
+ this.slots.clear();
1962
1214
  }
1963
- const builtInConfig = config;
1964
- return {
1965
- sourceFieldKey: fieldKey,
1966
- config: convertBuiltInToCustomValidator(builtInConfig),
1967
- validatorType: config.type,
1968
- dependsOn: extractStringDependencies(builtInConfig.expression || ''),
1969
- category: 'validator',
1970
- };
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 });
1971
1217
  }
1972
- function tryCreateLogicEntry(fieldKey, config) {
1973
- if (!isStateLogicConfig(config)) {
1974
- return null;
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);
1975
1226
  }
1976
- if (!isCrossFieldStateLogic(config)) {
1977
- return null;
1227
+ getSchema(name) {
1228
+ return this.registeredSchemas.get(name);
1978
1229
  }
1979
- const condition = config.condition;
1980
- return {
1981
- sourceFieldKey: fieldKey,
1982
- logicType: config.type,
1983
- condition,
1984
- config: config,
1985
- dependsOn: extractExpressionDependencies(condition),
1986
- category: 'logic',
1987
- };
1988
- }
1989
- function tryCreateSchemaEntry(fieldKey, config) {
1990
- if (!isCrossFieldSchema(config)) {
1991
- return null;
1230
+ getAllSchemas() {
1231
+ return new Map(this.registeredSchemas);
1992
1232
  }
1993
- const condition = config.condition;
1994
- return {
1995
- sourceFieldKey: fieldKey,
1996
- config,
1997
- condition,
1998
- dependsOn: extractExpressionDependencies(condition),
1999
- category: 'schema',
2000
- };
2001
- }
2002
- function convertBuiltInToCustomValidator(config) {
2003
- const expression = config.expression;
2004
- if (!expression) {
2005
- throw new DynamicFormError(`Built-in validator ${config.type} missing required expression for cross-field conversion`);
2006
- }
2007
- let validationExpression;
2008
- switch (config.type) {
2009
- case 'min':
2010
- validationExpression = `fieldValue == null || fieldValue >= (${expression})`;
2011
- break;
2012
- case 'max':
2013
- validationExpression = `fieldValue == null || fieldValue <= (${expression})`;
2014
- break;
2015
- case 'minLength':
2016
- validationExpression = `fieldValue == null || (typeof fieldValue !== 'string' && !Array.isArray(fieldValue)) || fieldValue.length >= (${expression})`;
2017
- break;
2018
- case 'maxLength':
2019
- validationExpression = `fieldValue == null || (typeof fieldValue !== 'string' && !Array.isArray(fieldValue)) || fieldValue.length <= (${expression})`;
2020
- break;
2021
- case 'pattern':
2022
- try {
2023
- new RegExp(expression);
2024
- }
2025
- catch (e) {
2026
- throw new DynamicFormError(`Invalid regex pattern in cross-field validator: '${expression}' — ${e instanceof Error ? e.message : String(e)}`);
2027
- }
2028
- validationExpression = `fieldValue == null || new RegExp(${JSON.stringify(expression)}).test(fieldValue)`;
2029
- break;
2030
- default:
2031
- throw new DynamicFormError(`Cannot convert ${config.type} validator to custom validator`);
1233
+ clearSchemas() {
1234
+ this.registeredSchemas.clear();
2032
1235
  }
2033
- return {
2034
- type: 'custom',
2035
- expression: validationExpression,
2036
- kind: config.type,
2037
- when: config.when,
2038
- errorParams: {
2039
- [config.type]: expression,
2040
- },
2041
- };
1236
+ resolveSchema(schema) {
1237
+ if (typeof schema === 'string') {
1238
+ return this.getSchema(schema) || null;
1239
+ }
1240
+ return schema;
1241
+ }
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 });
2042
1244
  }
1245
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: SchemaRegistryService, decorators: [{
1246
+ type: Injectable
1247
+ }] });
2043
1248
 
2044
1249
  function inheritContainerExclusion(container, child) {
2045
1250
  const inheritedHidden = container.hidden === true || child.inheritedExclusionState?.hidden === true;
@@ -2119,6 +1324,109 @@ function flattenFields(fields, registry, options = {}) {
2119
1324
  return result;
2120
1325
  }
2121
1326
 
1327
+ function getFieldDefaultValue(field, registry) {
1328
+ const valueHandling = getFieldValueHandling(field.type, registry);
1329
+ if (valueHandling === 'exclude') {
1330
+ return undefined;
1331
+ }
1332
+ if (valueHandling === 'flatten' && 'fields' in field && field.fields) {
1333
+ const childFields = field.fields;
1334
+ const flattenedValues = {};
1335
+ for (const childField of childFields) {
1336
+ const childValueHandling = getFieldValueHandling(childField.type, registry);
1337
+ if (childValueHandling === 'exclude')
1338
+ continue;
1339
+ const childValue = getFieldDefaultValue(childField, registry);
1340
+ if (childValue === undefined)
1341
+ continue;
1342
+ if (childValueHandling === 'flatten' && childValue && typeof childValue === 'object' && !Array.isArray(childValue)) {
1343
+ Object.assign(flattenedValues, childValue);
1344
+ }
1345
+ else if ('key' in childField && childField.key) {
1346
+ flattenedValues[childField.key] = childValue;
1347
+ }
1348
+ }
1349
+ return Object.keys(flattenedValues).length > 0 ? flattenedValues : undefined;
1350
+ }
1351
+ if (field.type === 'group' && 'fields' in field) {
1352
+ const fields = field.fields;
1353
+ if (!fields || fields.length === 0) {
1354
+ return undefined;
1355
+ }
1356
+ const groupDefaults = {};
1357
+ for (const childField of fields) {
1358
+ const childValueHandling = getFieldValueHandling(childField.type, registry);
1359
+ if (childValueHandling === 'exclude')
1360
+ continue;
1361
+ const childValue = getFieldDefaultValue(childField, registry);
1362
+ if (childValueHandling === 'flatten') {
1363
+ if (childValue && typeof childValue === 'object' && !Array.isArray(childValue)) {
1364
+ Object.assign(groupDefaults, childValue);
1365
+ }
1366
+ }
1367
+ else if ('key' in childField && childField.key) {
1368
+ if (childValue !== undefined) {
1369
+ groupDefaults[childField.key] = childValue;
1370
+ }
1371
+ }
1372
+ }
1373
+ return groupDefaults;
1374
+ }
1375
+ const valueField = field;
1376
+ const isNullable = valueField.nullable === true;
1377
+ if ('value' in field) {
1378
+ if (valueField.value !== null && valueField.value !== undefined) {
1379
+ return valueField.value;
1380
+ }
1381
+ if (valueField.value === null && isNullable) {
1382
+ return null;
1383
+ }
1384
+ }
1385
+ if (isNullable) {
1386
+ return null;
1387
+ }
1388
+ if (field.type === 'checkbox') {
1389
+ return false;
1390
+ }
1391
+ if (field.type === 'array') {
1392
+ const arrayField = field;
1393
+ const itemDefinitions = arrayField.fields;
1394
+ if (!itemDefinitions || itemDefinitions.length === 0) {
1395
+ return [];
1396
+ }
1397
+ return itemDefinitions.map((itemDef) => {
1398
+ if (!Array.isArray(itemDef)) {
1399
+ return getFieldDefaultValue(itemDef, registry);
1400
+ }
1401
+ const itemFields = itemDef;
1402
+ let itemValue = {};
1403
+ for (const templateField of itemFields) {
1404
+ const fieldValue = getFieldDefaultValue(templateField, registry);
1405
+ const fieldValueHandling = getFieldValueHandling(templateField.type, registry);
1406
+ if (templateField.type === 'group' && 'key' in templateField && templateField.key) {
1407
+ itemValue[templateField.key] = fieldValue;
1408
+ }
1409
+ else if (templateField.type === 'row' || templateField.type === 'container') {
1410
+ if (fieldValue && typeof fieldValue === 'object') {
1411
+ itemValue = { ...itemValue, ...fieldValue };
1412
+ }
1413
+ }
1414
+ else if (fieldValueHandling === 'include' && 'key' in templateField && templateField.key) {
1415
+ itemValue[templateField.key] = fieldValue;
1416
+ }
1417
+ }
1418
+ return itemValue;
1419
+ });
1420
+ }
1421
+ if (field.type === 'input' && 'props' in field) {
1422
+ const props = field.props;
1423
+ if (props?.type === 'number') {
1424
+ return NaN;
1425
+ }
1426
+ }
1427
+ return '';
1428
+ }
1429
+
2122
1430
  function createContainerFieldProcessors() {
2123
1431
  const flattenCache = new WeakMap();
2124
1432
  const memoizedFlattenFields = (fields, registry) => {
@@ -2553,60 +1861,6 @@ function isValidFormConfiguration(fields) {
2553
1861
  return FormModeValidator.validateFormConfiguration(fields).isValid;
2554
1862
  }
2555
1863
 
2556
- const COMPONENT_CACHE = new InjectionToken('COMPONENT_CACHE', {
2557
- providedIn: 'root',
2558
- factory: () => new Map(),
2559
- });
2560
- function injectFieldRegistry() {
2561
- const registry = inject(FIELD_REGISTRY);
2562
- const componentCache = inject(COMPONENT_CACHE);
2563
- return {
2564
- getType(name) {
2565
- return registry.get(name);
2566
- },
2567
- hasType(name) {
2568
- return registry.has(name);
2569
- },
2570
- async loadTypeComponent(name) {
2571
- const fieldType = registry.get(name);
2572
- if (!fieldType) {
2573
- throw new DynamicFormError(`Field type "${name}" is not registered`);
2574
- }
2575
- if (!fieldType.loadComponent) {
2576
- return undefined;
2577
- }
2578
- const cached = componentCache.get(name);
2579
- if (cached) {
2580
- return cached;
2581
- }
2582
- try {
2583
- const component = resolveDefaultExport(await fieldType.loadComponent());
2584
- if (component) {
2585
- componentCache.set(name, component);
2586
- }
2587
- return component;
2588
- }
2589
- catch (error) {
2590
- throw new DynamicFormError(`Failed to load component for field type "${name}": ${error}`);
2591
- }
2592
- },
2593
- getLoadedComponent(name) {
2594
- return componentCache.get(name);
2595
- },
2596
- getTypes() {
2597
- return Array.from(registry.values());
2598
- },
2599
- registerTypes(types) {
2600
- types.forEach((type) => {
2601
- registry.set(type.name, type);
2602
- });
2603
- },
2604
- get raw() {
2605
- return registry;
2606
- },
2607
- };
2608
- }
2609
-
2610
1864
  function collectFieldData(fields, data, registry, pathPrefix, collectKeys = true) {
2611
1865
  for (const field of fields) {
2612
1866
  const valueHandling = field.type ? getFieldValueHandling(field.type, registry) : 'include';
@@ -2653,7 +1907,9 @@ function validateRegexPattern(pattern, fieldKey, errors) {
2653
1907
  new RegExp(pattern);
2654
1908
  }
2655
1909
  catch (e) {
2656
- 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}'`);
2657
1913
  }
2658
1914
  }
2659
1915
  function validateNoDuplicateKeys(allKeys) {
@@ -2669,7 +1925,9 @@ function validateNoDuplicateKeys(allKeys) {
2669
1925
  const duplicateList = Array.from(duplicates)
2670
1926
  .map((k) => `'${k}'`)
2671
1927
  .join(', ');
2672
- 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}.`);
2673
1931
  }
2674
1932
  }
2675
1933
  function validateNoDomIdCollisions(domIds) {
@@ -2685,9 +1943,11 @@ function validateNoDomIdCollisions(domIds) {
2685
1943
  const duplicateList = Array.from(duplicates)
2686
1944
  .map((k) => `'${k}'`)
2687
1945
  .join(', ');
2688
- throw new DynamicFormError(`DOM id collision detected for: ${duplicateList}. Group-nested keys are joined with '_' to form DOM ids ` +
2689
- `(e.g. group 'foo' + leaf 'bar' renders as id='foo_bar'). A separate top-level key like 'foo_bar' or a different ` +
2690
- `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}.`);
2691
1951
  }
2692
1952
  }
2693
1953
  function validateFieldTypesRegistered(allTypes, registry, logger) {
@@ -2717,12 +1977,15 @@ function validateFormConfig(fields, registry, logger) {
2717
1977
  const list = Array.from(new Set(data.dottedKeys))
2718
1978
  .map((k) => `'${k}'`)
2719
1979
  .join(', ');
2720
- throw new DynamicFormError(`Field key(s) contain a dot: ${list}. Dotted keys are not supported — express nesting with 'group' fields ` +
2721
- `(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}.`);
2722
1984
  }
2723
1985
  validateNoDuplicateKeys(data.keys);
2724
1986
  validateNoDomIdCollisions(data.domIds);
2725
- validateFieldTypesRegistered(data.types, registry, logger);
1987
+ if (DEV_MODE)
1988
+ validateFieldTypesRegistered(data.types, registry, logger);
2726
1989
  if (data.regexErrors.length > 0) {
2727
1990
  throw new DynamicFormError(data.regexErrors.length === 1
2728
1991
  ? data.regexErrors[0]
@@ -2937,6 +2200,33 @@ function resolveExclusionConfig(global, form, field) {
2937
2200
  excludeValueIfReadonly: field?.excludeValueIfReadonly ?? form?.excludeValueIfReadonly ?? global.excludeValueIfReadonly,
2938
2201
  };
2939
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
+ }
2940
2230
  function shouldExcludeField(field, fieldState, config) {
2941
2231
  const state = fieldState();
2942
2232
  const inherited = field.inheritedExclusionState;
@@ -2954,7 +2244,10 @@ function shouldExcludeField(field, fieldState, config) {
2954
2244
  }
2955
2245
  return false;
2956
2246
  }
2957
- 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
+ }
2958
2251
  const result = {};
2959
2252
  for (const field of schemaFields) {
2960
2253
  const key = field.key;
@@ -2990,7 +2283,7 @@ function filterFormValue(rawValue, schemaFields, formTree, registry, globalDefau
2990
2283
  if (groupValue && typeof groupValue === 'object') {
2991
2284
  const childFields = Object.values(field.fields);
2992
2285
  const nestedTree = fieldState;
2993
- result[key] = filterFormValue(groupValue, childFields, nestedTree, registry, globalDefaults, formOptions);
2286
+ result[key] = filterFormValue(groupValue, childFields, nestedTree, registry, globalDefaults, formOptions, true);
2994
2287
  }
2995
2288
  else {
2996
2289
  result[key] = groupValue;
@@ -3303,7 +2596,7 @@ class FormStateMachine {
3303
2596
  if (!isTransitioningState(state) || state.phase !== Phase.Restoring) {
3304
2597
  return { state, sideEffects: [] };
3305
2598
  }
3306
- if (!state.pendingFormSetup) {
2599
+ if (DEV_MODE && !state.pendingFormSetup) {
3307
2600
  this.config.logger.warn('handleRestoreComplete: pendingFormSetup was not set — falling back to recomputing. ' +
3308
2601
  'This indicates a bug in the transition flow.');
3309
2602
  }
@@ -3366,7 +2659,7 @@ class FormStateMachine {
3366
2659
  const state = untracked(() => this._state());
3367
2660
  if (!isTransitioningState(state) || state.phase !== Phase.Restoring)
3368
2661
  return;
3369
- if (!state.pendingFormSetup) {
2662
+ if (DEV_MODE && !state.pendingFormSetup) {
3370
2663
  this.config.logger.warn('RestoreValues effect: pendingFormSetup was not set — falling back to recomputing.');
3371
2664
  }
3372
2665
  const formSetup = state.pendingFormSetup ?? this.config.createFormSetup(state.pendingConfig);
@@ -3508,13 +2801,6 @@ class FormStateManager {
3508
2801
  return detectFormMode(config.fields || []);
3509
2802
  }, /* @ts-ignore */
3510
2803
  ...(ngDevMode ? [{ debugName: "formModeDetection" }] : /* istanbul ignore next */ []));
3511
- formConfigValidation = computed(() => {
3512
- const config = this.activeConfig();
3513
- if (!config)
3514
- return { isValid: true, errors: [], warnings: [] };
3515
- return FormModeValidator.validateFormConfiguration(config.fields || []);
3516
- }, /* @ts-ignore */
3517
- ...(ngDevMode ? [{ debugName: "formConfigValidation" }] : /* istanbul ignore next */ []));
3518
2804
  effectiveFormOptions = computed(() => {
3519
2805
  const config = this.activeConfig();
3520
2806
  const configOptions = config?.options || {};
@@ -3522,6 +2808,8 @@ class FormStateManager {
3522
2808
  return { ...configOptions, ...inputOptions };
3523
2809
  }, /* @ts-ignore */
3524
2810
  ...(ngDevMode ? [{ debugName: "effectiveFormOptions" }] : /* istanbul ignore next */ []));
2811
+ pendingPageRequest = signal(null, /* @ts-ignore */
2812
+ ...(ngDevMode ? [{ debugName: "pendingPageRequest" }] : /* istanbul ignore next */ []));
3525
2813
  pageFieldDefinitions = computed(() => {
3526
2814
  const config = this.activeConfig();
3527
2815
  const mode = this.formModeDetection().mode;
@@ -3559,21 +2847,70 @@ class FormStateManager {
3559
2847
  return this.createEmptyFormSetup(registry);
3560
2848
  }, /* @ts-ignore */
3561
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 }) });
3562
2869
  defaultValues = linkedSignal(() => this.formSetup().defaultValues, /* @ts-ignore */
3563
2870
  ...(ngDevMode ? [{ debugName: "defaultValues" }] : /* istanbul ignore next */ []));
3564
2871
  validKeys = computed(() => {
3565
2872
  const schemaFields = this.formSetup().schemaFields;
3566
2873
  if (!schemaFields || schemaFields.length === 0)
3567
2874
  return undefined;
3568
- 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;
3569
2882
  }, /* @ts-ignore */
3570
2883
  ...(ngDevMode ? [{ debugName: "validKeys" }] : /* istanbul ignore next */ []));
3571
- 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(() => {
3572
2897
  const setup = this.formSetup();
3573
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)
3574
2911
  return {};
3575
2912
  const snapshot = {};
3576
- collectFieldStateSnapshot(setup.schemaFields, asFieldTreeRecord(this.form()), [], snapshot);
2913
+ collectFieldStateSnapshot(schemaFields, asFieldTreeRecord(this.form()), [], snapshot);
3577
2914
  return snapshot;
3578
2915
  }, /* @ts-ignore */
3579
2916
  ...(ngDevMode ? [{ debugName: "fieldStateSnapshot" }] : /* istanbul ignore next */ []));
@@ -3581,8 +2918,22 @@ class FormStateManager {
3581
2918
  ...(ngDevMode ? [{ debugName: "excludedValueStore" }] : /* istanbul ignore next */ []));
3582
2919
  prevFieldStateSnapshot = signal({}, /* @ts-ignore */
3583
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 */ []));
3584
2934
  entity = linkedSignal(() => {
3585
- const inputValue = this.deps.value();
2935
+ this.externalValue();
2936
+ const inputValue = untracked(() => this.deps.value());
3586
2937
  const defaults = this.defaultValues();
3587
2938
  const keys = this.validKeys();
3588
2939
  const saved = this.excludedValueStore();
@@ -3602,24 +2953,62 @@ class FormStateManager {
3602
2953
  debugName: 'FormStateManager.entity',
3603
2954
  equal: isEqual,
3604
2955
  });
3605
- formSchema = computed(() => runInInjectionContext(this.injector, () => {
2956
+ loadFormSchemaService = injectAsync(() => import('./ng-forge-dynamic-forms-form-schema.service-Dx5Dlb15.mjs'));
2957
+ formSchemaRequest = computed(() => {
3606
2958
  const setup = this.formSetup();
3607
2959
  const config = this.activeConfig();
3608
- 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;
3609
2980
  return undefined;
3610
- if (setup.schemaFields?.length) {
3611
- const crossFieldCollection = collectCrossFieldEntries(setup.schemaFields);
3612
- return createSchemaFromFields(setup.schemaFields, setup.registry, {
3613
- crossFieldValidators: crossFieldCollection.validators,
3614
- formLevelSchema: config.schema,
3615
- validateWhenHidden: this.effectiveFormOptions().validateWhenHidden,
3616
- });
3617
2981
  }
3618
- if (config.schema) {
3619
- return createFormLevelSchema(config.schema);
2982
+ if (this.formSchemaResource.status() === 'error') {
2983
+ this.schemaCache.current = undefined;
2984
+ return undefined;
3620
2985
  }
3621
- return undefined;
3622
- }), /* @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 */
3623
3012
  ...(ngDevMode ? [{ debugName: "formSchema" }] : /* istanbul ignore next */ []));
3624
3013
  form = computed(() => {
3625
3014
  const schema = this.formSchema();
@@ -3650,13 +3039,16 @@ class FormStateManager {
3650
3039
  ...(ngDevMode ? [{ debugName: "fieldSignalContext" }] : /* istanbul ignore next */ []));
3651
3040
  formInstance = computed(() => this.form()(), /* @ts-ignore */
3652
3041
  ...(ngDevMode ? [{ debugName: "formInstance" }] : /* istanbul ignore next */ []));
3653
- 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 */
3654
3045
  ...(ngDevMode ? [{ debugName: "formValue" }] : /* istanbul ignore next */ []));
3655
3046
  filteredFormValue = computed(() => {
3656
- const rawValue = this.formValue();
3047
+ const rawValue = this.modelValue();
3657
3048
  const setup = this.formSetup();
3049
+ const schemaFields = setup.schemaFields;
3658
3050
  const options = this.effectiveFormOptions();
3659
- if (!setup.schemaFields || setup.schemaFields.length === 0) {
3051
+ if (!this.formSchemaReady() || !schemaFields || schemaFields.length === 0) {
3660
3052
  return rawValue;
3661
3053
  }
3662
3054
  const formTree = this.form();
@@ -3666,14 +3058,15 @@ class FormStateManager {
3666
3058
  excludeValueIfReadonly: options.excludeValueIfReadonly,
3667
3059
  };
3668
3060
  const fieldTreeRecord = asFieldTreeRecord(formTree);
3669
- return filterFormValue(rawValue, setup.schemaFields, fieldTreeRecord, setup.registry, this.valueExclusionDefaults, formOptions);
3061
+ return filterFormValue(rawValue, schemaFields, fieldTreeRecord, setup.registry, this.valueExclusionDefaults, formOptions, this.valueExclusionEnabled());
3670
3062
  }, /* @ts-ignore */
3671
3063
  ...(ngDevMode ? [{ debugName: "filteredFormValue" }] : /* istanbul ignore next */ []));
3672
3064
  boundFormValue = computed(() => {
3673
- const rawValue = this.formValue();
3065
+ const rawValue = this.modelValue();
3674
3066
  const setup = this.formSetup();
3067
+ const schemaFields = setup.schemaFields;
3675
3068
  const options = this.effectiveFormOptions();
3676
- if (!setup.schemaFields || setup.schemaFields.length === 0) {
3069
+ if (!this.formSchemaReady() || !schemaFields || schemaFields.length === 0) {
3677
3070
  return rawValue;
3678
3071
  }
3679
3072
  const formTree = this.form();
@@ -3683,26 +3076,26 @@ class FormStateManager {
3683
3076
  excludeValueIfReadonly: options.excludeValueIfReadonly,
3684
3077
  };
3685
3078
  const fieldTreeRecord = asFieldTreeRecord(formTree);
3686
- 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());
3687
3080
  }, /* @ts-ignore */
3688
3081
  ...(ngDevMode ? [{ debugName: "boundFormValue" }] : /* istanbul ignore next */ []));
3689
- valid = computed(() => this.formInstance().valid(), /* @ts-ignore */
3082
+ valid = computed(() => this.formSchemaReady() && this.formSchemaResource.status() !== 'error' && this.formInstance().valid(), /* @ts-ignore */
3690
3083
  ...(ngDevMode ? [{ debugName: "valid" }] : /* istanbul ignore next */ []));
3691
- invalid = computed(() => this.formInstance().invalid(), /* @ts-ignore */
3084
+ invalid = computed(() => !this.formSchemaReady() || this.formSchemaResource.status() === 'error' || this.formInstance().invalid(), /* @ts-ignore */
3692
3085
  ...(ngDevMode ? [{ debugName: "invalid" }] : /* istanbul ignore next */ []));
3693
- dirty = computed(() => this.formInstance().dirty(), /* @ts-ignore */
3086
+ dirty = computed(() => this.formSchemaReady() && this.formInstance().dirty(), /* @ts-ignore */
3694
3087
  ...(ngDevMode ? [{ debugName: "dirty" }] : /* istanbul ignore next */ []));
3695
- touched = computed(() => this.formInstance().touched(), /* @ts-ignore */
3088
+ touched = computed(() => this.formSchemaReady() && this.formInstance().touched(), /* @ts-ignore */
3696
3089
  ...(ngDevMode ? [{ debugName: "touched" }] : /* istanbul ignore next */ []));
3697
- errors = computed(() => this.formInstance().errors(), /* @ts-ignore */
3090
+ errors = computed(() => (this.formSchemaReady() ? this.formInstance().errors() : []), /* @ts-ignore */
3698
3091
  ...(ngDevMode ? [{ debugName: "errors" }] : /* istanbul ignore next */ []));
3699
3092
  disabled = computed(() => {
3700
3093
  const optionsDisabled = this.effectiveFormOptions().disabled;
3701
- const formDisabled = this.formInstance().disabled();
3094
+ const formDisabled = this.formSchemaReady() && this.formInstance().disabled();
3702
3095
  return optionsDisabled ?? formDisabled;
3703
3096
  }, /* @ts-ignore */
3704
3097
  ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
3705
- submitting = computed(() => this.formInstance().submitting(), /* @ts-ignore */
3098
+ submitting = computed(() => this.formSchemaReady() && this.formInstance().submitting(), /* @ts-ignore */
3706
3099
  ...(ngDevMode ? [{ debugName: "submitting" }] : /* istanbul ignore next */ []));
3707
3100
  fieldsSource = computed(() => {
3708
3101
  const state = this.machine.state();
@@ -3817,6 +3210,10 @@ class FormStateManager {
3817
3210
  }), takeUntilDestroyed(this.destroyRef));
3818
3211
  this.reset$ = this.eventBus.on('form-reset').pipe(map(() => undefined), takeUntilDestroyed(this.destroyRef));
3819
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 }));
3820
3217
  this.machine = createFormStateMachine({
3821
3218
  injector: this.injector,
3822
3219
  destroyRef: this.destroyRef,
@@ -3880,6 +3277,11 @@ class FormStateManager {
3880
3277
  this.eventBus.dispatch(FormSubmitEvent);
3881
3278
  }
3882
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
+ });
3883
3285
  explicitEffect([this.deps.config], ([config]) => {
3884
3286
  this.fieldLoadingErrors.set([]);
3885
3287
  const state = this.machine.currentState;
@@ -3931,17 +3333,24 @@ class FormStateManager {
3931
3333
  explicitEffect([this.boundFormValue], ([currentBound]) => {
3932
3334
  const currentValue = this.deps.value();
3933
3335
  if (!isEqual(currentBound, currentValue)) {
3336
+ this.lastOutwardValue.current = currentBound;
3337
+ this.lastOutwardValue.pending = true;
3934
3338
  this.deps.value.set(currentBound);
3935
3339
  }
3936
3340
  });
3937
- explicitEffect([this.formConfigValidation], ([validation]) => {
3938
- if (!validation.isValid) {
3939
- this.logger.error('Invalid form configuration:', validation.errors);
3940
- }
3941
- if (validation.warnings.length > 0) {
3942
- this.logger.warn('Form configuration warnings:', validation.warnings);
3943
- }
3944
- });
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
+ }
3945
3354
  }
3946
3355
  setupEventHandlers() {
3947
3356
  this.eventBus
@@ -3985,48 +3394,457 @@ class FormStateManager {
3985
3394
  validateFormConfig(normalizedFields, registry, this.logger);
3986
3395
  const source = this.deps.source?.() ?? 'inline';
3987
3396
  const { fields: sanitizedFields, warnings: addonWarnings } = walkAndValidateAddons(normalizedFields, registry, this.addonKindRegistry, source);
3988
- const nextKeys = new Set();
3989
- for (const w of addonWarnings) {
3990
- const key = addonWarningKey(w);
3991
- nextKeys.add(key);
3992
- if (!this.lastAddonWarningKeys.has(key)) {
3993
- this.logger.warn(formatAddonWarning(w));
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));
3588
+ });
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() });
3594
+ });
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
+ }
3602
+ }
3603
+ });
3604
+ }
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);
3633
+ }
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
+ };
3643
+ }
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
+ };
3651
+ }
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);
3660
+ }
3661
+ if (options?.validate === false) {
3662
+ return this.executePageChange(pageIndex);
3663
+ }
3664
+ const disableWhenPageInvalid = this.formOptions?.()?.nextButton?.disableWhenPageInvalid ?? true;
3665
+ if (!disableWhenPageInvalid) {
3666
+ return this.executePageChange(pageIndex);
3667
+ }
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);
3672
+ }
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
+ };
3679
+ }
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;
3994
3765
  }
3995
3766
  }
3996
- this.lastAddonWarningKeys = nextKeys;
3997
- const validatedFields = sanitizedFields;
3998
- const flattenedFields = this.fieldProcessors.memoizedFlattenFields(validatedFields, registry);
3999
- const flattenedFieldsForRendering = this.fieldProcessors.memoizedFlattenFieldsForRendering(validatedFields, registry);
4000
- const fieldsById = this.fieldProcessors.memoizedKeyBy(flattenedFields);
4001
- const defaultValues = this.fieldProcessors.memoizedDefaultValues(fieldsById, registry);
4002
- const fieldsToRender = mode === 'paged' ? [] : flattenedFieldsForRendering;
4003
- return {
4004
- fields: fieldsToRender,
4005
- schemaFields: flattenedFields,
4006
- originalFields: fields,
4007
- defaultValues,
4008
- mode,
4009
- registry,
4010
- };
4011
- }
4012
- createEmptyFormSetup(registry) {
4013
- return {
4014
- fields: [],
4015
- schemaFields: [],
4016
- defaultValues: {},
4017
- mode: 'non-paged',
4018
- registry,
4019
- };
3767
+ return false;
4020
3768
  }
4021
- ngOnDestroy() {
4022
- 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
+ }
4023
3802
  }
4024
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: FormStateManager, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
4025
- 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)]] });
4026
3805
  }
4027
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: FormStateManager, decorators: [{
4028
- type: Injectable
4029
- }], 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 }] }] } }) });
4030
3848
 
4031
3849
  function traverseFieldsWithContext(fields, context, visitor, hooks) {
4032
3850
  for (const field of fields) {
@@ -4105,14 +3923,14 @@ function createDerivationRenderGate() {
4105
3923
  if (!has || engine() !== 'idle')
4106
3924
  return;
4107
3925
  engine.set('loading');
4108
- pendingTasks.run(() => import('./ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-CcoUnroD.mjs')
3926
+ pendingTasks.run(() => import('./ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-CTOTwzzw.mjs')
4109
3927
  .then(({ bootstrapDerivationOrchestrator }) => {
4110
3928
  runInInjectionContext(injector, () => bootstrapDerivationOrchestrator());
4111
3929
  engine.set('wired');
4112
3930
  })
4113
3931
  .catch((error) => {
4114
3932
  engine.set('failed');
4115
- 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);
4116
3934
  }));
4117
3935
  });
4118
3936
  return computed(() => {
@@ -4123,6 +3941,68 @@ function createDerivationRenderGate() {
4123
3941
  });
4124
3942
  }
4125
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
+
4126
4006
  const FORM_INITIALIZER = new InjectionToken('FORM_INITIALIZER');
4127
4007
 
4128
4008
  const AUTO_ID_PREFIX = 'df-';
@@ -4155,10 +4035,10 @@ class DynamicFormInstanceRegistry {
4155
4035
  this.slots.delete(Number(id.slice(AUTO_ID_PREFIX.length)));
4156
4036
  this._membership.update((m) => m + 1);
4157
4037
  }
4158
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: DynamicFormInstanceRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
4159
- 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' });
4160
4040
  }
4161
- 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: [{
4162
4042
  type: Injectable,
4163
4043
  args: [{ providedIn: 'root' }]
4164
4044
  }] });
@@ -4209,10 +4089,10 @@ class FormIdPrefixService {
4209
4089
  });
4210
4090
  }
4211
4091
  }
4212
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: FormIdPrefixService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
4213
- 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 });
4214
4094
  }
4215
- 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: [{
4216
4096
  type: Injectable
4217
4097
  }], ctorParameters: () => [] });
4218
4098
 
@@ -4265,6 +4145,7 @@ function coreProviders() {
4265
4145
  deps: [FormStateManager],
4266
4146
  },
4267
4147
  FormIdPrefixService,
4148
+ FieldViewportObserver,
4268
4149
  {
4269
4150
  provide: FORM_ID_PREFIX,
4270
4151
  useFactory: (svc) => svc.prefix,
@@ -4304,6 +4185,7 @@ function provideDynamicFormDI() {
4304
4185
  ...asyncExpressionProviders(),
4305
4186
  ...propertyDerivationProviders(),
4306
4187
  ...derivationProviders(),
4188
+ { provide: WEB_MCP_GATE, useFactory: createWebMcpGate },
4307
4189
  ];
4308
4190
  }
4309
4191
 
@@ -4318,10 +4200,10 @@ class EventDispatcher {
4318
4200
  disconnect() {
4319
4201
  this.bus = undefined;
4320
4202
  }
4321
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: EventDispatcher, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
4322
- 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 });
4323
4205
  }
4324
- 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: [{
4325
4207
  type: Injectable
4326
4208
  }] });
4327
4209
 
@@ -4329,10 +4211,10 @@ class DfTemplate {
4329
4211
  dfTemplate = input.required(/* @ts-ignore */
4330
4212
  ...(ngDevMode ? [{ debugName: "dfTemplate" }] : /* istanbul ignore next */ []));
4331
4213
  templateRef = inject(TemplateRef);
4332
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: DfTemplate, deps: [], target: i0.ɵɵFactoryTarget.Directive });
4333
- 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 });
4334
4216
  }
4335
- 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: [{
4336
4218
  type: Directive,
4337
4219
  args: [{
4338
4220
  selector: 'ng-template[dfTemplate]',
@@ -4346,10 +4228,10 @@ class DfFieldTemplateRegistry {
4346
4228
  set(value) {
4347
4229
  this._map.set(value);
4348
4230
  }
4349
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: DfFieldTemplateRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
4350
- 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 });
4351
4233
  }
4352
- 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: [{
4353
4235
  type: Injectable
4354
4236
  }] });
4355
4237
 
@@ -4368,6 +4250,7 @@ class DynamicForm {
4368
4250
  logger = inject(DynamicFormLogger);
4369
4251
  dispatcher = inject(EventDispatcher, { optional: true });
4370
4252
  templateRegistry = inject(DfFieldTemplateRegistry);
4253
+ globalFieldWindowing = inject(FIELD_WINDOWING);
4371
4254
  _projectedTemplates = contentChildren(DfTemplate, /* @ts-ignore */
4372
4255
  ...(ngDevMode ? [{ debugName: "_projectedTemplates" }] : /* istanbul ignore next */ []));
4373
4256
  stateManager = this.connectDeps();
@@ -4392,20 +4275,31 @@ class DynamicForm {
4392
4275
  submitting = this.stateManager.submitting;
4393
4276
  fieldLoadingErrors = this.stateManager.fieldLoadingErrors;
4394
4277
  derivationReady = inject(DERIVATION_RENDER_GATE);
4395
- shouldRender = computed(() => this.stateManager.shouldRender() && this.derivationReady(), /* @ts-ignore */
4278
+ shouldRender = computed(() => this.stateManager.shouldRender() && this.stateManager.formSchemaReady() && this.derivationReady(), /* @ts-ignore */
4396
4279
  ...(ngDevMode ? [{ debugName: "shouldRender" }] : /* istanbul ignore next */ []));
4280
+ webMcpStatus = inject(WEB_MCP_GATE);
4397
4281
  resolvedFields = this.stateManager.resolvedFields;
4398
- totalComponentsCount = computed(() => {
4399
- const fields = this.stateManager.formSetup()?.fields ?? [];
4400
- return countContainersRecursive(fields) + 1;
4282
+ fieldWindowing = computed(() => resolveFieldWindowing(this.globalFieldWindowing, this.effectiveFormOptions()?.fieldWindowing), /* @ts-ignore */
4283
+ ...(ngDevMode ? [{ debugName: "fieldWindowing" }] : /* istanbul ignore next */ []));
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));
4401
4300
  }, /* @ts-ignore */
4402
- ...(ngDevMode ? [{ debugName: "totalComponentsCount" }] : /* istanbul ignore next */ []));
4403
- initialized$ = setupInitializationTracking({
4404
- eventBus: this.eventBus,
4405
- totalComponentsCount: this.totalComponentsCount,
4406
- injector: this.injector,
4407
- componentId: this.componentId,
4408
- });
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 }));
4409
4303
  validityChange = outputFromObservable(toObservable(this.valid));
4410
4304
  dirtyChange = outputFromObservable(toObservable(this.dirty));
4411
4305
  submitted = outputFromObservable(this.stateManager.submitted$);
@@ -4413,18 +4307,46 @@ class DynamicForm {
4413
4307
  cleared = outputFromObservable(this.eventBus.on('form-clear'));
4414
4308
  events = outputFromObservable(this.eventBus.events$);
4415
4309
  initialized = outputFromObservable(this.initialized$);
4310
+ activePageInitialized = outputFromObservable(this.eventBus.on('active-page-initialized'));
4416
4311
  onPageChange = outputFromObservable(this.eventBus.on('page-change'));
4417
- onPageNavigationStateChange = outputFromObservable(this.eventBus.on('page-navigation-state-change'));
4312
+ onPageNavigationStateChange = outputFromObservable(this.eventBus.on('pager-state'));
4418
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));
4419
4331
  this.dispatcher?.connect(this.eventBus);
4420
4332
  this.destroyRef.onDestroy(() => this.dispatcher?.disconnect());
4421
4333
  inject(FORM_INITIALIZER, { optional: true });
4334
+ const preload = injectFormComponentPreloader();
4335
+ explicitEffect([this.config], ([config]) => preload.preloadConfig(config));
4422
4336
  this.setupEffects();
4423
4337
  this.setupEventHandlers();
4424
4338
  }
4425
4339
  onNativeSubmit(event) {
4426
4340
  event.preventDefault();
4427
4341
  this.eventBus.dispatch(FormSubmitEvent);
4342
+ }
4343
+ windowsField(field, index) {
4344
+ const w = this.fieldWindowing();
4345
+ return w.enabled && index >= w.eager && !isContainerField(field.fieldDef);
4346
+ }
4347
+ placeholderGridClass(field) {
4348
+ const grid = getGridClassString(field.fieldDef);
4349
+ return grid ? `df-field-placeholder ${grid}` : 'df-field-placeholder';
4428
4350
  }
4429
4351
  setupEffects() {
4430
4352
  explicitEffect([this.formModeDetection, this.pageFieldDefinitions], ([{ mode }, pages]) => {
@@ -4432,8 +4354,8 @@ class DynamicForm {
4432
4354
  this.eventBus.dispatch(ComponentInitializedEvent, 'dynamic-form', this.componentId);
4433
4355
  }
4434
4356
  });
4435
- explicitEffect([this.resolvedFields, this.formModeDetection], ([fields, { mode }]) => {
4436
- if (mode === 'non-paged' && fields.length > 0) {
4357
+ explicitEffect([this.resolvedFields, this.formModeDetection, this.activeConfig], ([, { mode }, config]) => {
4358
+ if (config && mode === 'non-paged') {
4437
4359
  afterNextRender(() => {
4438
4360
  this.eventBus.dispatch(ComponentInitializedEvent, 'dynamic-form', this.componentId);
4439
4361
  }, { injector: this.injector });
@@ -4468,8 +4390,8 @@ class DynamicForm {
4468
4390
  error: (err) => this.logger.error('Submission handler error', err),
4469
4391
  });
4470
4392
  }
4471
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: DynamicForm, deps: [], target: i0.ɵɵFactoryTarget.Component });
4472
- 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: [
4473
4395
  provideDynamicFormDI(),
4474
4396
  DfFieldTemplateRegistry,
4475
4397
  { provide: DF_FIELD_TEMPLATES, useFactory: () => inject(DfFieldTemplateRegistry).map },
@@ -4480,12 +4402,23 @@ class DynamicForm {
4480
4402
  <div page-orchestrator [pageFields]="pageFieldDefinitions()" [form]="form()" [fieldSignalContext]="fieldSignalContext()"></div>
4481
4403
  }
4482
4404
  @case ('non-paged') {
4483
- @for (field of resolvedFields(); track field.key) {
4484
- <!-- @if + DfFieldOutlet's own renderReady-&-!hidden gate together silence NG01916:
4485
- the template @if removes the host from the DOM (Angular's prescribed pattern), and
4486
- the directive's gate stops a same-CD-pass mount before [formField] can warn.
4487
- Don't dedupe one without the other — see DfFieldOutlet.renderReady. -->
4488
- @if (!field.hidden()) {
4405
+ @for (field of resolvedFields(); track field.key; let i = $index) {
4406
+ @if (windowsField(field, i)) {
4407
+ @if (!field.hidden()) {
4408
+ @defer (on viewport) {
4409
+ <ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
4410
+ } @placeholder {
4411
+ <div
4412
+ [class]="placeholderGridClass(field)"
4413
+ [style.min-height]="fieldWindowing().placeholderHeight"
4414
+ [attr.data-field-key]="field.key"
4415
+ aria-hidden="true"
4416
+ ></div>
4417
+ }
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. -->
4489
4422
  <ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
4490
4423
  }
4491
4424
  }
@@ -4495,9 +4428,9 @@ class DynamicForm {
4495
4428
  }
4496
4429
  }
4497
4430
  }
4498
- `, 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}\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 });
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]] });
4499
4432
  }
4500
- 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: [{
4501
4434
  type: Component,
4502
4435
  args: [{ selector: 'form[dynamic-form]', imports: [DfFieldOutlet, PageOrchestratorComponent], template: `
4503
4436
  @if (shouldRender()) {
@@ -4506,12 +4439,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
4506
4439
  <div page-orchestrator [pageFields]="pageFieldDefinitions()" [form]="form()" [fieldSignalContext]="fieldSignalContext()"></div>
4507
4440
  }
4508
4441
  @case ('non-paged') {
4509
- @for (field of resolvedFields(); track field.key) {
4510
- <!-- @if + DfFieldOutlet's own renderReady-&-!hidden gate together silence NG01916:
4511
- the template @if removes the host from the DOM (Angular's prescribed pattern), and
4512
- the directive's gate stops a same-CD-pass mount before [formField] can warn.
4513
- Don't dedupe one without the other — see DfFieldOutlet.renderReady. -->
4514
- @if (!field.hidden()) {
4442
+ @for (field of resolvedFields(); track field.key; let i = $index) {
4443
+ @if (windowsField(field, i)) {
4444
+ @if (!field.hidden()) {
4445
+ @defer (on viewport) {
4446
+ <ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
4447
+ } @placeholder {
4448
+ <div
4449
+ [class]="placeholderGridClass(field)"
4450
+ [style.min-height]="fieldWindowing().placeholderHeight"
4451
+ [attr.data-field-key]="field.key"
4452
+ aria-hidden="true"
4453
+ ></div>
4454
+ }
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. -->
4515
4459
  <ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
4516
4460
  }
4517
4461
  }
@@ -4533,30 +4477,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
4533
4477
  '[attr.data-form-mode]': 'formModeDetection().mode',
4534
4478
  '[attr.id]': 'idPrefix() || null',
4535
4479
  '(submit)': 'onNativeSubmit($event)',
4536
- }, 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}\n"] }]
4537
- }], 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"] }] } });
4538
- function countContainersRecursive(fields) {
4539
- let count = 0;
4540
- for (const field of fields) {
4541
- if (isContainerField(field)) {
4542
- count += 1;
4543
- if (hasChildFields(field)) {
4544
- const children = field.fields;
4545
- if (Array.isArray(children)) {
4546
- for (const child of children) {
4547
- if (Array.isArray(child)) {
4548
- count += countContainersRecursive(child);
4549
- }
4550
- else if (child != null && isContainerField(child)) {
4551
- count += countContainersRecursive([child]);
4552
- }
4553
- }
4554
- }
4555
- }
4556
- }
4557
- }
4558
- return count;
4559
- }
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"] }]
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"] }] } });
4560
4482
 
4561
4483
  const BUILT_IN_ADDON_TYPES = [
4562
4484
  {
@@ -4604,35 +4526,35 @@ const DISPLAY_FIELD_TYPES_BASE = {
4604
4526
  const BUILT_IN_FIELDS = [
4605
4527
  {
4606
4528
  name: 'row',
4607
- loadComponent: () => import('./ng-forge-dynamic-forms-container-field.component-DZfVWjiM.mjs'),
4529
+ loadComponent: () => import('./ng-forge-dynamic-forms-container-field.component-Dc3ROhHL.mjs'),
4608
4530
  mapper: rowFieldMapper,
4609
4531
  valueHandling: 'flatten',
4610
4532
  ...CONTAINER_FIELD_TYPES_BASE,
4611
4533
  },
4612
4534
  {
4613
4535
  name: 'group',
4614
- loadComponent: () => import('./ng-forge-dynamic-forms-group-field.component-CPFgu3pM.mjs'),
4536
+ loadComponent: () => import('./ng-forge-dynamic-forms-group-field.component-DAC3P9DC.mjs'),
4615
4537
  mapper: groupFieldMapper,
4616
4538
  valueHandling: 'include',
4617
4539
  ...CONTAINER_FIELD_TYPES_BASE,
4618
4540
  },
4619
4541
  {
4620
4542
  name: 'array',
4621
- loadComponent: () => import('./ng-forge-dynamic-forms-array-field.component-CvjDqYyq.mjs'),
4543
+ loadComponent: () => import('./ng-forge-dynamic-forms-array-field.component-CSCKq_1K.mjs'),
4622
4544
  mapper: arrayFieldMapper,
4623
4545
  valueHandling: 'include',
4624
4546
  ...CONTAINER_FIELD_TYPES_BASE,
4625
4547
  },
4626
4548
  {
4627
4549
  name: 'page',
4628
- loadComponent: () => import('./ng-forge-dynamic-forms-page-field.component-amWhCdHL.mjs'),
4550
+ loadComponent: () => import('./ng-forge-dynamic-forms-page-field.component-DPKJcwUO.mjs'),
4629
4551
  mapper: pageFieldMapper,
4630
4552
  valueHandling: 'flatten',
4631
4553
  ...CONTAINER_FIELD_TYPES_BASE,
4632
4554
  },
4633
4555
  {
4634
4556
  name: 'text',
4635
- 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'),
4636
4558
  mapper: textFieldMapper,
4637
4559
  valueHandling: 'exclude',
4638
4560
  ...DISPLAY_FIELD_TYPES_BASE,
@@ -4643,7 +4565,7 @@ const BUILT_IN_FIELDS = [
4643
4565
  },
4644
4566
  {
4645
4567
  name: 'container',
4646
- loadComponent: () => import('./ng-forge-dynamic-forms-container-field.component-DZfVWjiM.mjs'),
4568
+ loadComponent: () => import('./ng-forge-dynamic-forms-container-field.component-Dc3ROhHL.mjs'),
4647
4569
  mapper: containerFieldMapper,
4648
4570
  valueHandling: 'flatten',
4649
4571
  ...CONTAINER_FIELD_TYPES_BASE,
@@ -4652,11 +4574,16 @@ const BUILT_IN_FIELDS = [
4652
4574
  const BUILT_IN_WRAPPERS = [
4653
4575
  {
4654
4576
  wrapperName: 'css',
4655
- loadComponent: () => import('./ng-forge-dynamic-forms-css-wrapper.component-D_yRuLpu.mjs'),
4577
+ loadComponent: () => import('./ng-forge-dynamic-forms-css-wrapper.component-BzgX8lAD.mjs'),
4656
4578
  },
4657
4579
  {
4658
4580
  wrapperName: 'row',
4659
- 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,
4660
4587
  },
4661
4588
  ];
4662
4589
 
@@ -4717,7 +4644,8 @@ function provideDynamicForm(...items) {
4717
4644
  const wrapperBundles = items.filter(isWrappersBundle);
4718
4645
  const features = items.filter(isDynamicFormFeature);
4719
4646
  const fields = [...BUILT_IN_FIELDS, ...fieldTypes];
4720
- 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];
4721
4649
  const featureProviders = [];
4722
4650
  const hasLoggerFeature = features.some((feature) => feature.ɵkind === 'logger');
4723
4651
  features.forEach((feature) => {
@@ -4732,7 +4660,7 @@ function provideDynamicForm(...items) {
4732
4660
  const logger = inject(DynamicFormLogger);
4733
4661
  const registry = new Map();
4734
4662
  fields.forEach((fieldType) => {
4735
- if (registry.has(fieldType.name)) {
4663
+ if (DEV_MODE && registry.has(fieldType.name)) {
4736
4664
  logger.warn(`Field type "${fieldType.name}" is already registered. Overwriting.`);
4737
4665
  }
4738
4666
  registry.set(fieldType.name, fieldType);
@@ -4758,11 +4686,18 @@ function provideDynamicForm(...items) {
4758
4686
  useFactory: () => {
4759
4687
  const logger = inject(DynamicFormLogger);
4760
4688
  const registry = new Map();
4761
- wrappers.forEach((wrapperType) => {
4762
- if (registry.has(wrapperType.wrapperName)) {
4763
- 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);
4764
4699
  }
4765
- registry.set(wrapperType.wrapperName, wrapperType);
4700
+ registry.set(name, wrapperType);
4766
4701
  });
4767
4702
  return registry;
4768
4703
  },
@@ -4793,7 +4728,7 @@ function provideDynamicForm(...items) {
4793
4728
  registry.set(def.type, def);
4794
4729
  }
4795
4730
  for (const def of contributed) {
4796
- if (registry.has(def.type)) {
4731
+ if (DEV_MODE && registry.has(def.type)) {
4797
4732
  logger.warn(`Addon type "${def.type}" is already registered. Overwriting.`);
4798
4733
  }
4799
4734
  registry.set(def.type, def);
@@ -4841,6 +4776,32 @@ function withValueExclusionDefaults(config) {
4841
4776
  return createFeature('value-exclusion', [{ provide: VALUE_EXCLUSION_DEFAULTS, useValue: resolved }]);
4842
4777
  }
4843
4778
 
4779
+ function withPagePreload(window) {
4780
+ const resolved = clampWindowSize(window, 1);
4781
+ return createFeature('page-preload', [{ provide: PAGE_PRELOAD_WINDOW, useValue: resolved }]);
4782
+ }
4783
+
4784
+ function withFieldWindowing(config) {
4785
+ const resolved = {
4786
+ enabled: true,
4787
+ eager: clampWindowSize(config?.eager, 12),
4788
+ placeholderHeight: config?.placeholderHeight ?? '4rem',
4789
+ park: resolveFieldParking({ enabled: true, margin: '100%' }, config?.park),
4790
+ };
4791
+ return createFeature('field-windowing', [{ provide: FIELD_WINDOWING, useValue: resolved }]);
4792
+ }
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
+
4844
4805
  function withValidationExecutionDefaults(config) {
4845
4806
  const resolved = {
4846
4807
  validateWhenHidden: config?.validateWhenHidden ?? false,
@@ -4861,10 +4822,10 @@ class TextAddonComponent {
4861
4822
  ...(ngDevMode ? [{ debugName: "addon" }] : /* istanbul ignore next */ []));
4862
4823
  fieldInputs = input(/* @ts-ignore */
4863
4824
  ...(ngDevMode ? [undefined, { debugName: "fieldInputs" }] : /* istanbul ignore next */ []));
4864
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: TextAddonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4865
- 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 });
4866
4827
  }
4867
- 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: [{
4868
4829
  type: Component,
4869
4830
  args: [{
4870
4831
  selector: 'df-text-addon',
@@ -4904,14 +4865,14 @@ class TemplateAddonComponent {
4904
4865
  }
4905
4866
  });
4906
4867
  }
4907
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: TemplateAddonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4908
- 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: `
4909
4870
  @if (template(); as tpl) {
4910
4871
  <ng-container *ngTemplateOutlet="tpl; context: outletContext()" />
4911
4872
  }
4912
4873
  `, isInline: true, dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4913
4874
  }
4914
- 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: [{
4915
4876
  type: Component,
4916
4877
  args: [{
4917
4878
  selector: 'df-template-addon',
@@ -4959,14 +4920,14 @@ class ComponentAddonComponent {
4959
4920
  });
4960
4921
  });
4961
4922
  }
4962
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: ComponentAddonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4963
- 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: `
4964
4925
  @if (resolvedComponent(); as cmp) {
4965
4926
  <ng-container *ngComponentOutlet="cmp; inputs: inputs()" />
4966
4927
  }
4967
4928
  `, isInline: true, dependencies: [{ kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4968
4929
  }
4969
- 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: [{
4970
4931
  type: Component,
4971
4932
  args: [{
4972
4933
  selector: 'df-component-addon',
@@ -5031,6 +4992,128 @@ function sanitizeFormConfigPure(config, fieldRegistry, typeRegistry, options = {
5031
4992
  return { sanitized: { ...config, fields: fields }, warnings };
5032
4993
  }
5033
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
+
5034
5117
  function arrayEvent(arrayKey) {
5035
5118
  return {
5036
5119
  append: (template) => new AppendArrayItemEvent(arrayKey, template),
@@ -5046,5 +5129,5 @@ function arrayEvent(arrayKey) {
5046
5129
 
5047
5130
 
5048
5131
 
5049
- export { withValueExclusionDefaults as $, ArrayItemRegistryService as A, BUILT_IN_FIELDS as B, CONTAINER_FIELD_PROCESSORS as C, DfFieldOutlet as D, EventDispatcher as E, FormStateManager as F, PrependArrayItemEvent as G, PreviousPageEvent as H, InsertArrayItemEvent as I, TextAddonComponent as J, arrayEvent as K, createWrappers as L, MoveArrayItemEvent as M, NextPageEvent as N, isWrappersBundle as O, PROPERTY_OVERRIDE_STORE as P, provideDynamicForm as Q, RemoveAtIndexEvent as R, ShiftArrayItemEvent as S, TemplateAddonComponent as T, sanitizeFormConfig as U, withAddonActions as V, withCustomAddon as W, withEventFormValue as X, withLegacyStatusClasses as Y, withLoggerConfig as Z, withValidationExecutionDefaults as _, isSameWrapperChain as a, wrapperProps as a0, createWrapperChainController as b, createFieldResolutionPipe as c, createSchemaFromFields as d, createHiddenSignal as e, createRenderReadySignal as f, getNormalizedArrayMetadata as g, getFieldDefaultValue as h, injectFieldRegistry as i, DEV_MODE as j, DEFAULT_DEBOUNCE_MS as k, DERIVATION_WARNING_TRACKER as l, mapFieldToInputs as m, DERIVATION_LOG_CONFIG as n, AppendArrayItemEvent as o, ComponentAddonComponent as p, ConsoleLogger as q, resolveWrappers as r, DfTemplate as s, traverseFieldsWithContext as t, DynamicForm as u, FormClearEvent as v, FormResetEvent as w, FormSubmitEvent as x, PageChangeEvent as y, PopArrayItemEvent as z };
5050
- //# sourceMappingURL=ng-forge-dynamic-forms-ng-forge-dynamic-forms-Du3V5AOb.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