@ng-forge/dynamic-forms 1.2.0-next.5 → 1.2.0-next.7

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 (39) hide show
  1. package/fesm2022/{ng-forge-dynamic-forms-array-field.component-D-VWBM-t.mjs → ng-forge-dynamic-forms-array-field.component-DK7dAQv7.mjs} +6 -10
  2. package/fesm2022/ng-forge-dynamic-forms-array-field.component-DK7dAQv7.mjs.map +1 -0
  3. package/fesm2022/{ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-OBDy6qd9.mjs → ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-BfxYqAQA.mjs} +29 -7
  4. package/fesm2022/ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-BfxYqAQA.mjs.map +1 -0
  5. package/fesm2022/{ng-forge-dynamic-forms-container-field.component-BpeFyhAt.mjs → ng-forge-dynamic-forms-container-field.component-Cj2msZp_.mjs} +9 -12
  6. package/fesm2022/ng-forge-dynamic-forms-container-field.component-Cj2msZp_.mjs.map +1 -0
  7. package/fesm2022/ng-forge-dynamic-forms-debounce-C9F4v1Ke.mjs +17 -0
  8. package/fesm2022/ng-forge-dynamic-forms-debounce-C9F4v1Ke.mjs.map +1 -0
  9. package/fesm2022/ng-forge-dynamic-forms-field-errors-wrapper.component-ZdaC4Bit.mjs +25 -0
  10. package/fesm2022/ng-forge-dynamic-forms-field-errors-wrapper.component-ZdaC4Bit.mjs.map +1 -0
  11. package/fesm2022/ng-forge-dynamic-forms-form-schema.service-C3cVSmft.mjs +24 -0
  12. package/fesm2022/ng-forge-dynamic-forms-form-schema.service-C3cVSmft.mjs.map +1 -0
  13. package/fesm2022/{ng-forge-dynamic-forms-group-field.component-BNjYMTqT.mjs → ng-forge-dynamic-forms-group-field.component-DN6X506r.mjs} +6 -10
  14. package/fesm2022/ng-forge-dynamic-forms-group-field.component-DN6X506r.mjs.map +1 -0
  15. package/fesm2022/ng-forge-dynamic-forms-integration.mjs +10 -72
  16. package/fesm2022/ng-forge-dynamic-forms-integration.mjs.map +1 -1
  17. package/fesm2022/ng-forge-dynamic-forms-internal.mjs +252 -95
  18. package/fesm2022/ng-forge-dynamic-forms-internal.mjs.map +1 -1
  19. package/fesm2022/{ng-forge-dynamic-forms-ng-forge-dynamic-forms-DSXZJHcu.mjs → ng-forge-dynamic-forms-ng-forge-dynamic-forms-DCX7tvaB.mjs} +1089 -1288
  20. package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-DCX7tvaB.mjs.map +1 -0
  21. package/fesm2022/{ng-forge-dynamic-forms-page-field.component-B92kLZkH.mjs → ng-forge-dynamic-forms-page-field.component-D16ey8r-.mjs} +54 -13
  22. package/fesm2022/ng-forge-dynamic-forms-page-field.component-D16ey8r-.mjs.map +1 -0
  23. package/fesm2022/ng-forge-dynamic-forms-schema-builder-DO-su9sF.mjs +570 -0
  24. package/fesm2022/ng-forge-dynamic-forms-schema-builder-DO-su9sF.mjs.map +1 -0
  25. package/fesm2022/ng-forge-dynamic-forms.mjs +2 -2
  26. package/package.json +2 -2
  27. package/types/ng-forge-dynamic-forms-integration.d.ts +7 -31
  28. package/types/ng-forge-dynamic-forms-internal.d.ts +429 -222
  29. package/types/ng-forge-dynamic-forms.d.ts +33 -17
  30. package/fesm2022/ng-forge-dynamic-forms-array-field.component-D-VWBM-t.mjs.map +0 -1
  31. package/fesm2022/ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-OBDy6qd9.mjs.map +0 -1
  32. package/fesm2022/ng-forge-dynamic-forms-container-field.component-BpeFyhAt.mjs.map +0 -1
  33. package/fesm2022/ng-forge-dynamic-forms-container-utils-BZFz6ztF.mjs +0 -24
  34. package/fesm2022/ng-forge-dynamic-forms-container-utils-BZFz6ztF.mjs.map +0 -1
  35. package/fesm2022/ng-forge-dynamic-forms-emit-initialization-DhHwU6FJ.mjs +0 -17
  36. package/fesm2022/ng-forge-dynamic-forms-emit-initialization-DhHwU6FJ.mjs.map +0 -1
  37. package/fesm2022/ng-forge-dynamic-forms-group-field.component-BNjYMTqT.mjs.map +0 -1
  38. package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-DSXZJHcu.mjs.map +0 -1
  39. package/fesm2022/ng-forge-dynamic-forms-page-field.component-B92kLZkH.mjs.map +0 -1
@@ -1,15 +1,16 @@
1
1
  import * as i0 from '@angular/core';
2
- import { reflectComponentType, Injector, inject, EnvironmentInjector, DestroyRef, computed, signal, input, ViewContainerRef, Directive, InjectionToken, linkedSignal, untracked, ChangeDetectionStrategy, Component, Injectable, isSignal, runInInjectionContext, afterNextRender, PendingTasks, ElementRef, PLATFORM_ID, isDevMode, TemplateRef, model, contentChildren, makeEnvironmentProviders } from '@angular/core';
2
+ import { reflectComponentType, Injector, inject, EnvironmentInjector, DestroyRef, computed, signal, afterNextRender, input, ViewContainerRef, Directive, InjectionToken, Injectable, isSignal, runInInjectionContext, untracked, linkedSignal, injectAsync, resource, ChangeDetectionStrategy, Component, PendingTasks, ElementRef, PLATFORM_ID, isDevMode, TemplateRef, model, contentChildren, makeEnvironmentProviders } from '@angular/core';
3
3
  import { explicitEffect } from 'ngxtension/explicit-effect';
4
- import { DynamicFormLogger, WRAPPER_COMPONENT_CACHE, WRAPPER_REGISTRY, toReadonlyFieldTreeCached, writeToFieldValue, WRAPPER_AUTO_ASSOCIATIONS, DEFAULT_WRAPPERS, READONLY_FIELD_TREE_CACHE, getGridClassString, EventBus, FieldContextRegistryService, FunctionRegistryService, FORM_OPTIONS, evaluateCondition, FIELD_SIGNAL_CONTEXT, isRowField, isGenericContainerField, isContainerField, isArrayField, hasChildFields, isSimplifiedArrayField, DynamicFormError, isStateLogicConfig, isDerivationLogicConfig, isFormStateCondition, createFormStateLogicFunction, createDebouncedLogicFunction, createLogicFunction, ExpressionParser, applyValidator, isPageField, isGroupField, getFieldValueHandling, normalizeFieldsArray, keyBy, mapValues, isEqual, hasTargetProperty, ARRAY_CONTEXT, GROUP_CONTEXT, FORM_ID_PREFIX, baseFieldMapper, detectFormMode, validatePageNesting, FIELD_REGISTRY, assertNever, ADDON_TYPE_REGISTRY, deepMergeDefaults, derivedFromDeferred, createWarningTracker, RootFormRegistryService, DEFAULT_PROPS, DEFAULT_VALIDATION_MESSAGES, EXTERNAL_DATA, DEPRECATION_WARNING_TRACKER, createAddonActionRegistry, ADDON_ACTION_REGISTRY, HttpConditionCache, HTTP_CONDITION_CACHE, HttpConditionFunctionCacheService, AsyncConditionFunctionCacheService, DynamicValueFunctionCacheService, LogicFunctionCacheService, setupInitializationTracking, ComponentInitializedEvent, DF_FIELD_TEMPLATES, rowFieldMapper, groupFieldMapper, arrayFieldMapper, pageFieldMapper, textFieldMapper, containerFieldMapper, isWrapperTypeDefinition, ADDON_TYPE_COMPONENT_CACHE, ADDON_TYPE_DEFINITIONS, createDefaultDerivationLogConfig, NoopLogger, EMIT_FORM_VALUE_ON_EVENTS, DynamicTextPipe, ADDON_ACTION_HANDLERS } from '@ng-forge/dynamic-forms/internal';
5
- import { takeUntilDestroyed, toObservable, toSignal, outputFromObservable } from '@angular/core/rxjs-interop';
6
- import { of, forkJoin, from, catchError, map, switchMap, isObservable, firstValueFrom, exhaustMap, EMPTY, debounceTime, distinctUntilChanged, tap, pipe, scan, Observable, Subject, concatMap, filter, take } from 'rxjs';
7
- import { submit, required, disabled, readonly, hidden, applyEach, applyWhenValue, applyWhen, apply, email, minLength, maxLength, pattern, min, max, schema, validateStandardSchema, form, provideSignalFormsConfig } from '@angular/forms/signals';
8
- import { isStandardSchemaMarker } from '@ng-forge/dynamic-forms/schema';
4
+ import { DynamicFormLogger, 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, GROUP_CONTEXT, isContainerField, isSimplifiedArrayField, DynamicFormError, getFieldValueHandling, keyBy, mapValues, isEqual, isDerivationLogicConfig, hasTargetProperty, ARRAY_CONTEXT, FORM_ID_PREFIX, baseFieldMapper, detectFormMode, validatePageNesting, FIELD_REGISTRY, assertNever, FunctionRegistryService, ADDON_TYPE_REGISTRY, deepMergeDefaults, FIELD_SIGNAL_CONTEXT, derivedFromDeferred, FieldContextRegistryService, FORM_OPTIONS, 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';
5
+ import { takeUntilDestroyed, toObservable, outputFromObservable } from '@angular/core/rxjs-interop';
6
+ import { of, forkJoin, from, catchError, map, switchMap, isObservable, firstValueFrom, exhaustMap, EMPTY, pipe, scan, Observable, Subject, concatMap, filter, take, shareReplay } from 'rxjs';
7
+ import { submit, form, provideSignalFormsConfig } from '@angular/forms/signals';
9
8
  import { isPlatformBrowser, AsyncPipe, NgTemplateOutlet, NgComponentOutlet } from '@angular/common';
10
9
  import { NG_STATUS_CLASSES } from '@angular/forms/signals/compat';
11
10
 
12
11
  const EMPTY_WRAPPERS = Object.freeze([]);
12
+ const FIELD_ERRORS_WRAPPER = 'field-errors';
13
+ const FIELD_ERRORS_CONFIG = Object.freeze({ type: FIELD_ERRORS_WRAPPER });
13
14
  function isSameWrapperChain(a, b) {
14
15
  if (a === b)
15
16
  return true;
@@ -21,19 +22,30 @@ function isSameWrapperChain(a, b) {
21
22
  }
22
23
  return true;
23
24
  }
24
- function resolveWrappers(field, defaultWrappers, autoAssociations) {
25
- if (field.wrappers === null) {
25
+ function resolveWrappers(field, defaultWrappers, autoAssociations, wrapperRegistry) {
26
+ if (field.wrappers === null) {
26
27
  return EMPTY_WRAPPERS;
27
28
  }
28
29
  const autoWrappers = field.skipAutoWrappers ? EMPTY_WRAPPERS : (autoAssociations.get(field.type) ?? EMPTY_WRAPPERS);
29
30
  const defaults = field.skipDefaultWrappers ? EMPTY_WRAPPERS : (defaultWrappers ?? EMPTY_WRAPPERS);
30
31
  const fieldLevel = field.wrappers ?? EMPTY_WRAPPERS;
31
- if (autoWrappers.length === 0 && defaults.length === 0 && fieldLevel.length === 0) {
32
- return EMPTY_WRAPPERS;
32
+ const composed = [...autoWrappers, ...defaults, ...fieldLevel];
33
+ if (needsErrorWrapper(field, composed, wrapperRegistry)) {
34
+ composed.push(FIELD_ERRORS_CONFIG);
33
35
  }
34
- return [...autoWrappers, ...defaults, ...fieldLevel];
36
+ return composed.length === 0 ? EMPTY_WRAPPERS : composed;
37
+ }
38
+ const VALIDATABLE_CONTAINERS = new Set(['group', 'array']);
39
+ function needsErrorWrapper(field, composed, wrapperRegistry) {
40
+ if (!VALIDATABLE_CONTAINERS.has(field.type))
41
+ return false;
42
+ if (!field.validators || field.validators.length === 0)
43
+ return false;
44
+ return !composed.some((w) => w.type === FIELD_ERRORS_WRAPPER || wrapperRegistry.get(w.type)?.rendersFieldErrors);
35
45
  }
36
46
 
47
+ const DEV_MODE = typeof ngDevMode === 'undefined' || !!ngDevMode;
48
+
37
49
  const inputNamesCache = new WeakMap();
38
50
  function getDeclaredInputs(componentType) {
39
51
  let inputs = inputNamesCache.get(componentType);
@@ -72,7 +84,9 @@ function loadWrapperComponents(configs, registry, cache, logger) {
72
84
  return of([]);
73
85
  return forkJoin(configs.map((config) => from(loadWrapperComponent(config.type, registry, cache)).pipe(catchError(() => of(undefined)), map((component) => {
74
86
  if (!component) {
75
- logger.error(`Wrapper type '${config.type}' could not be loaded. Ensure it is registered via provideDynamicForm().`);
87
+ if (DEV_MODE) {
88
+ logger.error(`Wrapper type '${config.type}' could not be loaded. Ensure it is registered via provideDynamicForm().`);
89
+ }
76
90
  return null;
77
91
  }
78
92
  return { config, component };
@@ -112,9 +126,11 @@ function renderStep(slot, remaining, options, refs) {
112
126
  ref.changeDetectorRef.detectChanges();
113
127
  const inner = resolveInnerSlot(ref);
114
128
  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).`);
129
+ if (DEV_MODE) {
130
+ options.logger.error(`Wrapper component for type '${wrapper.config.type}' does not provide a 'fieldComponent' ViewContainerRef. ` +
131
+ `Ensure the wrapper component has a viewChild('fieldComponent', { read: ViewContainerRef }) query ` +
132
+ `and that #fieldComponent is not inside a conditional (@if, @defer).`);
133
+ }
118
134
  while (refs.length)
119
135
  refs.pop().destroy();
120
136
  return;
@@ -156,22 +172,28 @@ function createWrapperChainController(opts) {
156
172
  const deps = injectChainDeps();
157
173
  const state = createStateSignal(opts);
158
174
  const mounted = { value: null };
175
+ const detached = { value: null };
159
176
  let refs = [];
160
177
  buildEmissionStream(state, deps)
161
178
  .pipe(takeUntilDestroyed(deps.destroyRef))
162
179
  .subscribe((emission) => {
163
180
  try {
164
- refs = applyEmission(emission, { opts, deps, mounted, refs });
181
+ refs = applyEmission(emission, { opts, deps, mounted, refs, detached });
165
182
  }
166
183
  catch (err) {
167
184
  deps.logger.error('Wrapper chain render failed; tearing down partial state.', err);
168
185
  opts.vcr().clear();
169
186
  refs = [];
170
187
  mounted.value = null;
188
+ detached.value?.destroy();
189
+ detached.value = null;
171
190
  }
172
191
  });
173
192
  pushFieldInputsOnChange(opts, () => refs);
174
- deps.destroyRef.onDestroy(() => opts.vcr().clear());
193
+ deps.destroyRef.onDestroy(() => {
194
+ detached.value?.destroy();
195
+ opts.vcr().clear();
196
+ });
175
197
  }
176
198
  function injectChainDeps() {
177
199
  return {
@@ -206,10 +228,21 @@ function applyEmission({ state, loaded }, ctx) {
206
228
  const { opts, deps, mounted, refs } = ctx;
207
229
  const vcr = opts.vcr();
208
230
  const structurallyChanged = isStructurallyDifferent(mounted.value, state);
209
- if (!structurallyChanged && !state.open)
231
+ if (!structurallyChanged && !state.open) {
232
+ if (vcr.length > 0)
233
+ ctx.detached.value = vcr.detach(0) ?? ctx.detached.value;
234
+ return refs;
235
+ }
236
+ if (state.open && !structurallyChanged && mounted.value !== null && ctx.detached.value) {
237
+ vcr.insert(ctx.detached.value);
238
+ ctx.detached.value = null;
210
239
  return refs;
240
+ }
211
241
  if (structurallyChanged && mounted.value !== null) {
212
- opts.beforeRebuild?.();
242
+ const preservesDetachedOuterView = opts.beforeRebuild?.() === true;
243
+ if (ctx.detached.value && !preservesDetachedOuterView)
244
+ ctx.detached.value.destroy();
245
+ ctx.detached.value = null;
213
246
  vcr.clear();
214
247
  mounted.value = null;
215
248
  }
@@ -246,7 +279,7 @@ function pushFieldInputsOnChange(opts, getRefs) {
246
279
  });
247
280
  }
248
281
 
249
- function buildFieldInputs(rawInputs, cache, fieldType) {
282
+ function buildFieldInputs(rawInputs, cache, fieldType, validationMessages) {
250
283
  const fieldTreeCandidate = rawInputs['field'];
251
284
  const hasFieldTree = fieldTreeCandidate !== undefined && typeof fieldTreeCandidate === 'function';
252
285
  const readonlyField = hasFieldTree ? toReadonlyFieldTreeCached(cache, fieldTreeCandidate) : undefined;
@@ -258,6 +291,7 @@ function buildFieldInputs(rawInputs, cache, fieldType) {
258
291
  type: fieldType,
259
292
  field: readonlyField,
260
293
  setValue,
294
+ ...(validationMessages !== undefined && { validationMessages }),
261
295
  };
262
296
  }
263
297
 
@@ -310,13 +344,14 @@ class FieldComponentSlot {
310
344
  detach() {
311
345
  const current = this.state();
312
346
  if (current.phase !== 'mounted')
313
- return;
347
+ return false;
314
348
  const focusSnapshot = this.captureFocus(current.ref);
315
349
  const idx = current.slot.indexOf(current.ref.hostView);
316
350
  if (idx >= 0)
317
351
  current.slot.detach(idx);
318
352
  this.lastInputs = null;
319
353
  this.state.set(Object.freeze({ phase: 'detached', ref: current.ref, focusSnapshot }));
354
+ return idx < 0;
320
355
  }
321
356
  destroyOnTeardown() {
322
357
  const current = this.state();
@@ -347,6 +382,74 @@ class FieldComponentSlot {
347
382
  }
348
383
  }
349
384
 
385
+ function emitComponentInitialized(eventBus, componentType, componentKey, injector) {
386
+ const logger = injector.get(DynamicFormLogger);
387
+ afterNextRender(() => {
388
+ try {
389
+ eventBus.dispatch(ComponentInitializedEvent, componentType, componentKey);
390
+ }
391
+ catch (error) {
392
+ logger.error(`Failed to emit initialization event for ${componentType} '${componentKey}'`, error);
393
+ }
394
+ }, { injector });
395
+ }
396
+
397
+ function initializationComponentKey(type, key) {
398
+ return `${type}:${key}`;
399
+ }
400
+ function initializationComponentPath(key, groupPath) {
401
+ return groupPath ? `${groupPath}.${key}` : key;
402
+ }
403
+ function initializationComponentType(field) {
404
+ if (isPageField(field))
405
+ return 'page';
406
+ if (isGroupField(field))
407
+ return 'group';
408
+ if (isArrayField(field))
409
+ return 'array';
410
+ if (isRowField(field) || isGenericContainerField(field))
411
+ return 'container';
412
+ return undefined;
413
+ }
414
+ function collectInitializingContainers(fields, groupPath = '') {
415
+ const identities = [];
416
+ for (const field of fields) {
417
+ if (field.hidden === true)
418
+ continue;
419
+ const type = initializationComponentType(field);
420
+ const componentPath = initializationComponentPath(field.key, groupPath);
421
+ if (type && field.key)
422
+ identities.push({ type, path: componentPath });
423
+ if (!hasChildFields(field))
424
+ continue;
425
+ const childGroupPath = isGroupField(field) ? componentPath : groupPath;
426
+ for (const child of normalizeFieldsArray(field.fields)) {
427
+ identities.push(...collectInitializingContainers((Array.isArray(child) ? child : [child]), childGroupPath));
428
+ }
429
+ }
430
+ return identities;
431
+ }
432
+ function collectInitializingContainerKeys(fields, groupPath = '') {
433
+ return collectInitializingContainers(fields, groupPath).map(({ type, path }) => initializationComponentKey(type, path));
434
+ }
435
+ function computeContainerHostClasses(containerType, className) {
436
+ const base = `df-field df-${containerType}`;
437
+ return className ? `${base} ${className}` : base;
438
+ }
439
+ function setupContainerInitEffect(resolvedFields, eventBus, componentType, fieldKeyFn, injector) {
440
+ const allReady = computed(() => {
441
+ const fields = resolvedFields();
442
+ return fields.length > 0 && fields.every((field) => field.renderReady());
443
+ }, /* @ts-ignore */
444
+ ...(ngDevMode ? [{ debugName: "allReady" }] : /* istanbul ignore next */ []));
445
+ explicitEffect([allReady], ([ready]) => {
446
+ if (ready) {
447
+ const type = typeof componentType === 'function' ? componentType() : componentType;
448
+ emitComponentInitialized(eventBus, type, fieldKeyFn(), injector);
449
+ }
450
+ });
451
+ }
452
+
350
453
  class DfFieldOutlet {
351
454
  dfFieldOutlet = input.required(/* @ts-ignore */
352
455
  ...(ngDevMode ? [{ debugName: "dfFieldOutlet" }] : /* istanbul ignore next */ []));
@@ -356,17 +459,22 @@ class DfFieldOutlet {
356
459
  vcr = signal(this.vcrRef).asReadonly();
357
460
  destroyRef = inject(DestroyRef);
358
461
  wrapperAutoAssociations = inject(WRAPPER_AUTO_ASSOCIATIONS);
462
+ wrapperRegistry = inject(WRAPPER_REGISTRY);
359
463
  defaultWrappersSignal = inject(DEFAULT_WRAPPERS, { optional: true });
360
464
  readonlyFieldCache = inject(READONLY_FIELD_TREE_CACHE);
465
+ eventBus = inject(EventBus, { optional: true });
466
+ injector = inject(Injector);
361
467
  fieldComponent = new FieldComponentSlot();
362
468
  componentIdentity = computed(() => this.dfFieldOutlet().component, /* @ts-ignore */
363
469
  ...(ngDevMode ? [{ debugName: "componentIdentity" }] : /* istanbul ignore next */ []));
364
470
  renderReady = computed(() => this.dfFieldOutlet().renderReady() && !this.dfFieldOutlet().hidden(), /* @ts-ignore */
365
471
  ...(ngDevMode ? [{ debugName: "renderReady" }] : /* istanbul ignore next */ []));
472
+ hidden = computed(() => this.dfFieldOutlet().hidden(), /* @ts-ignore */
473
+ ...(ngDevMode ? [{ debugName: "hidden" }] : /* istanbul ignore next */ []));
366
474
  rawInputs = computed(() => this.dfFieldOutlet().inputs(), /* @ts-ignore */
367
475
  ...(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 */
476
+ wrappers = computed(() => resolveWrappers(this.dfFieldOutlet().fieldDef, this.defaultWrappersSignal?.(), this.wrapperAutoAssociations, this.wrapperRegistry), { ...(ngDevMode ? { debugName: "wrappers" } : /* istanbul ignore next */ {}), equal: isSameWrapperChain });
477
+ fieldInputs = computed(() => buildFieldInputs(this.rawInputs(), this.readonlyFieldCache, this.dfFieldOutlet().fieldDef.type, this.dfFieldOutlet().fieldDef.validationMessages), /* @ts-ignore */
370
478
  ...(ngDevMode ? [{ debugName: "fieldInputs" }] : /* istanbul ignore next */ []));
371
479
  defaultEnvInjector = inject(EnvironmentInjector);
372
480
  fieldEnvInjector = computed(() => this.dfFieldOutletEnvironmentInjector() ?? this.defaultEnvInjector, /* @ts-ignore */
@@ -391,6 +499,15 @@ class DfFieldOutlet {
391
499
  },
392
500
  });
393
501
  explicitEffect([this.rawInputs, this.fieldInputs], ([rawInputs, fieldInputs]) => this.fieldComponent.pushInputs(rawInputs, fieldInputs));
502
+ explicitEffect([this.hidden], ([hidden]) => {
503
+ if (!hidden || !this.eventBus)
504
+ return;
505
+ const resolved = this.dfFieldOutlet();
506
+ const groupContext = resolved.injector.get(GROUP_CONTEXT, null);
507
+ for (const { type, path } of collectInitializingContainers([resolved.fieldDef], groupContext?.groupPath())) {
508
+ emitComponentInitialized(this.eventBus, type, path, this.injector);
509
+ }
510
+ });
394
511
  this.destroyRef.onDestroy(() => this.fieldComponent.destroyOnTeardown());
395
512
  }
396
513
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: DfFieldOutlet, deps: [], target: i0.ɵɵFactoryTarget.Directive });
@@ -471,462 +588,72 @@ function clampWindowSize(value, fallback) {
471
588
  return Math.max(0, Math.floor(value));
472
589
  }
473
590
 
474
- class PageOrchestratorComponent {
475
- eventBus = inject(EventBus);
476
- fieldContextRegistry = inject(FieldContextRegistryService);
477
- functionRegistry = inject(FunctionRegistryService);
478
- formOptions = inject(FORM_OPTIONS, { optional: true });
479
- globalPreloadWindow = inject(PAGE_PRELOAD_WINDOW);
480
- pageFields = input.required(/* @ts-ignore */
481
- ...(ngDevMode ? [{ debugName: "pageFields" }] : /* istanbul ignore next */ []));
482
- form = input.required(/* @ts-ignore */
483
- ...(ngDevMode ? [{ debugName: "form" }] : /* istanbul ignore next */ []));
484
- fieldSignalContext = input.required(/* @ts-ignore */
485
- ...(ngDevMode ? [{ debugName: "fieldSignalContext" }] : /* istanbul ignore next */ []));
486
- pageHiddenStates = computed(() => {
487
- const pages = this.pageFields();
488
- return pages.map((page) => this.evaluatePageHidden(page));
489
- }, /* @ts-ignore */
490
- ...(ngDevMode ? [{ debugName: "pageHiddenStates" }] : /* istanbul ignore next */ []));
491
- visiblePageIndices = computed(() => {
492
- const hiddenStates = this.pageHiddenStates();
493
- return hiddenStates
494
- .map((hidden, index) => ({ index, hidden }))
495
- .filter((item) => !item.hidden)
496
- .map((item) => item.index);
497
- }, /* @ts-ignore */
498
- ...(ngDevMode ? [{ debugName: "visiblePageIndices" }] : /* istanbul ignore next */ []));
499
- currentPageIndex = linkedSignal(() => {
500
- const totalPages = this.pageFields().length;
501
- if (totalPages === 0)
502
- return 0;
503
- return untracked(() => this.resolveInitialLanding());
504
- }, /* @ts-ignore */
505
- ...(ngDevMode ? [{ debugName: "currentPageIndex" }] : /* istanbul ignore next */ []));
506
- resolveInitialLanding() {
507
- const { index, validate } = this.initialPage();
508
- const visible = this.visiblePageIndices();
509
- if (index === 0 || visible.length === 0)
510
- return 0;
511
- const target = visible.includes(index) ? index : this.findNearestVisiblePage(index, visible);
512
- if (target <= 0)
513
- return Math.max(target, 0);
514
- if (!validate)
515
- return target;
516
- const firstInvalid = visible.filter((i) => i < target).find((i) => !this.isPageValid(i));
517
- return firstInvalid ?? target;
518
- }
519
- initialPage = computed(() => {
520
- const raw = this.formOptions?.()?.initialPage;
521
- const totalPages = this.pageFields().length;
522
- const requested = typeof raw === 'number' ? raw : raw?.index;
523
- const validate = typeof raw === 'number' ? false : (raw?.validate ?? false);
524
- if (typeof requested !== 'number' || !Number.isFinite(requested) || requested < 0 || totalPages === 0) {
525
- return { index: 0, validate: false };
526
- }
527
- return { index: Math.min(Math.trunc(requested), totalPages - 1), validate };
528
- }, /* @ts-ignore */
529
- ...(ngDevMode ? [{ debugName: "initialPage" }] : /* istanbul ignore next */ []));
530
- state = computed(() => {
531
- const currentIndex = this.currentPageIndex();
532
- const totalPages = this.pageFields().length;
533
- const visibleIndices = this.visiblePageIndices();
534
- const currentVisiblePosition = visibleIndices.indexOf(currentIndex);
535
- const isFirstVisiblePage = currentVisiblePosition === 0 || currentVisiblePosition === -1;
536
- const isLastVisiblePage = currentVisiblePosition >= visibleIndices.length - 1;
537
- return {
538
- currentPageIndex: currentIndex,
539
- totalPages,
540
- isFirstPage: isFirstVisiblePage,
541
- isLastPage: isLastVisiblePage,
542
- };
543
- }, /* @ts-ignore */
544
- ...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
545
- currentPageValid = computed(() => this.isPageValid(this.currentPageIndex()), /* @ts-ignore */
546
- ...(ngDevMode ? [{ debugName: "currentPageValid" }] : /* istanbul ignore next */ []));
547
- isPageValid(pageIndex) {
548
- const pages = this.pageFields();
549
- const form = this.form();
550
- if (pageIndex < 0 || pageIndex >= pages.length) {
551
- return true;
591
+ function collectLeafFieldKeys(fields) {
592
+ const keys = [];
593
+ for (const field of fields) {
594
+ if (isRowField(field) || isGenericContainerField(field)) {
595
+ keys.push(...collectLeafFieldKeys(field.fields));
552
596
  }
553
- const leafKeys = collectLeafFieldKeys(pages[pageIndex].fields || []);
554
- for (const fieldKey of leafKeys) {
555
- const field = form[fieldKey];
556
- if (field && typeof field === 'function') {
557
- const fieldState = field();
558
- if (fieldState && typeof fieldState.valid === 'function' && !fieldState.valid()) {
559
- return false;
560
- }
561
- }
597
+ else if (field.key) {
598
+ keys.push(field.key);
562
599
  }
563
- return true;
564
- }
565
- extendedFieldSignalContext = computed(() => ({
566
- ...this.fieldSignalContext(),
567
- currentPageValid: this.currentPageValid,
568
- }), /* @ts-ignore */
569
- ...(ngDevMode ? [{ debugName: "extendedFieldSignalContext" }] : /* istanbul ignore next */ []));
570
- preloadWindow = computed(() => {
571
- const perForm = this.formOptions?.()?.pagePreloadWindow;
572
- return clampWindowSize(perForm, this.globalPreloadWindow);
573
- }, /* @ts-ignore */
574
- ...(ngDevMode ? [{ debugName: "preloadWindow" }] : /* istanbul ignore next */ []));
575
- isWithinPreloadWindow(i) {
576
- return Math.abs(i - this.state().currentPageIndex) <= this.preloadWindow();
577
- }
578
- constructor() {
579
- this.setupEventListeners();
580
- explicitEffect([this.state, this.visiblePageIndices], ([state, visibleIndices]) => {
581
- const currentVisiblePosition = visibleIndices.indexOf(state.currentPageIndex);
582
- if (currentVisiblePosition === -1 && visibleIndices.length > 0) {
583
- const nearest = this.findNearestVisiblePage(state.currentPageIndex, visibleIndices);
584
- if (nearest !== -1) {
585
- this.executePageChange(nearest);
586
- }
587
- }
588
- });
589
600
  }
590
- navigateToNextPage() {
591
- const disableWhenPageInvalid = this.formOptions?.()?.nextButton?.disableWhenPageInvalid ?? true;
592
- if (disableWhenPageInvalid && !this.currentPageValid()) {
593
- return {
594
- success: false,
595
- newPageIndex: this.state().currentPageIndex,
596
- error: 'Current page has invalid fields',
597
- };
598
- }
599
- const currentState = this.state();
600
- const visibleIndices = this.visiblePageIndices();
601
- if (currentState.isLastPage) {
602
- return {
603
- success: false,
604
- newPageIndex: currentState.currentPageIndex,
605
- error: 'Already on the last visible page',
606
- };
601
+ return keys;
602
+ }
603
+
604
+ const NORMALIZED_ARRAY_METADATA = Symbol('normalizedArrayMetadata');
605
+ function setNormalizedArrayMetadata(arrayField, metadata) {
606
+ arrayField[NORMALIZED_ARRAY_METADATA] = metadata;
607
+ }
608
+ function getNormalizedArrayMetadata(arrayField) {
609
+ return arrayField[NORMALIZED_ARRAY_METADATA];
610
+ }
611
+
612
+ function normalizeSimplifiedArrays(fields) {
613
+ return fields.flatMap((field) => {
614
+ if (isContainerField(field) && !isArrayField(field) && hasChildFields(field)) {
615
+ return [{ ...field, fields: normalizeSimplifiedArrays(field.fields) }];
607
616
  }
608
- const currentVisiblePosition = visibleIndices.indexOf(currentState.currentPageIndex);
609
- if (currentVisiblePosition === -1 || currentVisiblePosition >= visibleIndices.length - 1) {
610
- return {
611
- success: false,
612
- newPageIndex: currentState.currentPageIndex,
613
- error: 'No next visible page available',
614
- };
617
+ if (isArrayField(field) && !isSimplifiedArrayField(field)) {
618
+ const normalizedItems = field.fields.map((item) => Array.isArray(item) ? normalizeSimplifiedArrays(item) : item);
619
+ return [{ ...field, fields: normalizedItems }];
615
620
  }
616
- const nextVisiblePageIndex = visibleIndices[currentVisiblePosition + 1];
617
- return this.executePageChange(nextVisiblePageIndex);
618
- }
619
- navigateToPreviousPage() {
620
- const currentState = this.state();
621
- const visibleIndices = this.visiblePageIndices();
622
- if (currentState.isFirstPage) {
623
- return {
624
- success: false,
625
- newPageIndex: currentState.currentPageIndex,
626
- error: 'Already on the first visible page',
627
- };
621
+ if (isSimplifiedArrayField(field)) {
622
+ const { arrayField, addButton } = expandSimplifiedArray(field);
623
+ return addButton ? [arrayField, addButton] : [arrayField];
628
624
  }
629
- const currentVisiblePosition = visibleIndices.indexOf(currentState.currentPageIndex);
630
- if (currentVisiblePosition <= 0) {
631
- return {
632
- success: false,
633
- newPageIndex: currentState.currentPageIndex,
634
- error: 'No previous visible page available',
635
- };
625
+ return [field];
626
+ });
627
+ }
628
+ const INVALID_ARRAY_CHILD_TYPES = new Set(['page', 'array']);
629
+ function validateSimplifiedTemplate(field) {
630
+ const { template, key } = field;
631
+ const isObjectTemplate = Array.isArray(template);
632
+ if (isObjectTemplate) {
633
+ const templateFields = template;
634
+ for (const tmpl of templateFields) {
635
+ if (INVALID_ARRAY_CHILD_TYPES.has(tmpl.type)) {
636
+ throw new DynamicFormError(`Simplified array "${key}" template contains a '${tmpl.type}' field (key: '${tmpl.key}'). ` +
637
+ `Only leaf fields, rows, and groups are allowed as array children.`);
638
+ }
636
639
  }
637
- const prevVisiblePageIndex = visibleIndices[currentVisiblePosition - 1];
638
- return this.executePageChange(prevVisiblePageIndex);
639
640
  }
640
- navigateToPage(pageIndex, options) {
641
- const currentIndex = this.state().currentPageIndex;
642
- const visibleIndices = this.visiblePageIndices();
643
- if (pageIndex <= currentIndex || !visibleIndices.includes(pageIndex)) {
644
- return this.executePageChange(pageIndex);
641
+ else {
642
+ const singleTemplate = template;
643
+ if (INVALID_ARRAY_CHILD_TYPES.has(singleTemplate.type)) {
644
+ throw new DynamicFormError(`Simplified array "${key}" template has type '${singleTemplate.type}'. ` +
645
+ `Only leaf fields, rows, and groups are allowed as array children.`);
645
646
  }
646
- if (options?.validate === false) {
647
- return this.executePageChange(pageIndex);
648
- }
649
- const disableWhenPageInvalid = this.formOptions?.()?.nextButton?.disableWhenPageInvalid ?? true;
650
- if (!disableWhenPageInvalid) {
651
- return this.executePageChange(pageIndex);
652
- }
653
- const crossedPages = visibleIndices.filter((index) => index >= currentIndex && index < pageIndex);
654
- const firstInvalidPage = crossedPages.find((index) => !this.isPageValid(index));
655
- if (firstInvalidPage === undefined) {
656
- return this.executePageChange(pageIndex);
657
- }
658
- this.executePageChange(firstInvalidPage);
659
- return {
660
- success: false,
661
- newPageIndex: firstInvalidPage,
662
- error: `Cannot navigate to page ${pageIndex}: page ${firstInvalidPage} has invalid fields`,
663
- };
664
- }
665
- executePageChange(pageIndex) {
666
- const currentState = this.state();
667
- const totalPages = currentState.totalPages;
668
- if (pageIndex < 0 || pageIndex >= totalPages) {
669
- return {
670
- success: false,
671
- newPageIndex: currentState.currentPageIndex,
672
- error: `Invalid page index: ${pageIndex}. Valid range is 0 to ${totalPages - 1}`,
673
- };
674
- }
675
- const visibleIndices = this.visiblePageIndices();
676
- if (!visibleIndices.includes(pageIndex)) {
677
- return {
678
- success: false,
679
- newPageIndex: currentState.currentPageIndex,
680
- error: `Cannot navigate to hidden page at index ${pageIndex}. Visible pages: [${visibleIndices.join(', ')}]`,
681
- };
682
- }
683
- if (pageIndex === currentState.currentPageIndex) {
684
- return {
685
- success: true,
686
- newPageIndex: pageIndex,
687
- };
688
- }
689
- const previousIndex = currentState.currentPageIndex;
690
- this.currentPageIndex.set(pageIndex);
691
- this.eventBus.dispatch(PageChangeEvent, pageIndex, totalPages, previousIndex);
692
- return {
693
- success: true,
694
- newPageIndex: pageIndex,
695
- };
696
- }
697
- findNearestVisiblePage(currentIndex, visibleIndices) {
698
- let nearest = -1;
699
- let minDistance = Infinity;
700
- for (const idx of visibleIndices) {
701
- const distance = Math.abs(idx - currentIndex);
702
- if (distance < minDistance || (distance === minDistance && idx > nearest)) {
703
- minDistance = distance;
704
- nearest = idx;
705
- }
706
- }
707
- return nearest;
708
- }
709
- setupEventListeners() {
710
- this.eventBus
711
- .on('next-page')
712
- .pipe(takeUntilDestroyed())
713
- .subscribe(() => {
714
- this.navigateToNextPage();
715
- });
716
- this.eventBus
717
- .on('previous-page')
718
- .pipe(takeUntilDestroyed())
719
- .subscribe(() => {
720
- this.navigateToPreviousPage();
721
- });
722
- this.eventBus
723
- .on('go-to-page')
724
- .pipe(takeUntilDestroyed())
725
- .subscribe((event) => {
726
- this.navigateToPage(event.pageIndex, event.options);
727
- });
728
- explicitEffect([this.state], ([state]) => this.eventBus.dispatch(PagerStateEvent, state));
729
- }
730
- evaluatePageHidden(page) {
731
- if (!page.logic || page.logic.length === 0) {
732
- return false;
733
- }
734
- const hiddenLogic = page.logic.filter((l) => l.type === 'hidden');
735
- if (hiddenLogic.length === 0) {
736
- return false;
737
- }
738
- for (const logic of hiddenLogic) {
739
- if (typeof logic.condition === 'boolean') {
740
- if (logic.condition) {
741
- return true;
742
- }
743
- continue;
744
- }
745
- const condition = logic.condition;
746
- const context = this.fieldContextRegistry.createDisplayOnlyContext(page.key || '', this.functionRegistry.getCustomFunctions());
747
- if (evaluateCondition(condition, context)) {
748
- return true;
749
- }
750
- }
751
- return false;
752
- }
753
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: PageOrchestratorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
754
- 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: [
755
- {
756
- provide: FIELD_SIGNAL_CONTEXT,
757
- useFactory: (orchestrator) => orchestrator.extendedFieldSignalContext(),
758
- deps: [PageOrchestratorComponent],
759
- },
760
- ], ngImport: i0, template: `
761
- @for (pageField of pageFields(); track pageField.key; let i = $index) {
762
- <!--
763
- Skip pages hidden by 'hidden' logic conditions entirely. FormStateManager
764
- derives schema/value/validators from config (not from mounted components),
765
- so unmounting a hidden page has no effect on form state — only on render +
766
- CD cost. This is the page-level equivalent of the existing field-level
767
- \`@if (!field.hidden())\` gate in page-field.component.ts.
768
- -->
769
- @if (!pageHiddenStates()[i]) {
770
- @if (isWithinPreloadWindow(i)) {
771
- <!-- Current and preloaded neighbour pages: render immediately for flicker-free navigation -->
772
- @defer (on immediate) {
773
- <section
774
- page-field
775
- [field]="pageField"
776
- [key]="pageField.key"
777
- [pageIndex]="i"
778
- [isVisible]="i === state().currentPageIndex"
779
- ></section>
780
- } @placeholder {
781
- <div class="df-page-placeholder" [attr.data-page-index]="i" [attr.data-page-key]="pageField.key"></div>
782
- }
783
- } @else {
784
- <!--
785
- Pages outside the preload window render only a lightweight placeholder
786
- — they are NOT mounted. Mounting every page eagerly (previously via
787
- \`@defer (on idle)\`, which fires almost immediately post-load) put all
788
- fields of every page in the DOM at once: O(all-fields) initial render
789
- blocking + O(all-fields) change detection on every keystroke, with no
790
- visible benefit since only one page is shown at a time.
791
-
792
- Form state (schema/value/validators) derives from config, not from
793
- mounted components, so leaving a page unmounted does not affect
794
- validity, derivations, or navigation. A page mounts the moment
795
- navigation brings it into the preload window (above), which is
796
- flicker-free for sequential next/previous navigation. The window size
797
- is configurable via \`withPagePreload(n)\` or \`FormOptions.pagePreloadWindow\`.
798
- -->
799
- <div class="df-page-placeholder" [attr.data-page-index]="i" [attr.data-page-key]="pageField.key"></div>
800
- }
801
- }
802
- }
803
- `, isInline: true, styles: [":host{display:block;position:relative;width:100%}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, deferBlockDependencies: [() => [/* @ts-ignore */
804
- import('./ng-forge-dynamic-forms-page-field.component-B92kLZkH.mjs').then(m => m.default)]] });
805
- }
806
- i0.ɵɵngDeclareClassMetadataAsync({ minVersion: "18.0.0", version: "22.0.1", ngImport: i0, type: PageOrchestratorComponent, resolveDeferredDeps: () => [/* @ts-ignore */
807
- import('./ng-forge-dynamic-forms-page-field.component-B92kLZkH.mjs').then(m => m.default)], resolveMetadata: PageFieldComponent => ({ decorators: [{
808
- type: Component,
809
- args: [{ selector: 'div[page-orchestrator]', imports: [PageFieldComponent], template: `
810
- @for (pageField of pageFields(); track pageField.key; let i = $index) {
811
- <!--
812
- Skip pages hidden by 'hidden' logic conditions entirely. FormStateManager
813
- derives schema/value/validators from config (not from mounted components),
814
- so unmounting a hidden page has no effect on form state — only on render +
815
- CD cost. This is the page-level equivalent of the existing field-level
816
- \`@if (!field.hidden())\` gate in page-field.component.ts.
817
- -->
818
- @if (!pageHiddenStates()[i]) {
819
- @if (isWithinPreloadWindow(i)) {
820
- <!-- Current and preloaded neighbour pages: render immediately for flicker-free navigation -->
821
- @defer (on immediate) {
822
- <section
823
- page-field
824
- [field]="pageField"
825
- [key]="pageField.key"
826
- [pageIndex]="i"
827
- [isVisible]="i === state().currentPageIndex"
828
- ></section>
829
- } @placeholder {
830
- <div class="df-page-placeholder" [attr.data-page-index]="i" [attr.data-page-key]="pageField.key"></div>
831
- }
832
- } @else {
833
- <!--
834
- Pages outside the preload window render only a lightweight placeholder
835
- — they are NOT mounted. Mounting every page eagerly (previously via
836
- \`@defer (on idle)\`, which fires almost immediately post-load) put all
837
- fields of every page in the DOM at once: O(all-fields) initial render
838
- blocking + O(all-fields) change detection on every keystroke, with no
839
- visible benefit since only one page is shown at a time.
840
-
841
- Form state (schema/value/validators) derives from config, not from
842
- mounted components, so leaving a page unmounted does not affect
843
- validity, derivations, or navigation. A page mounts the moment
844
- navigation brings it into the preload window (above), which is
845
- flicker-free for sequential next/previous navigation. The window size
846
- is configurable via \`withPagePreload(n)\` or \`FormOptions.pagePreloadWindow\`.
847
- -->
848
- <div class="df-page-placeholder" [attr.data-page-index]="i" [attr.data-page-key]="pageField.key"></div>
849
- }
850
- }
851
- }
852
- `, host: {
853
- class: 'df-page-orchestrator',
854
- '[attr.data-current-page]': 'state().currentPageIndex',
855
- '[attr.data-total-pages]': 'state().totalPages',
856
- }, providers: [
857
- {
858
- provide: FIELD_SIGNAL_CONTEXT,
859
- useFactory: (orchestrator) => orchestrator.extendedFieldSignalContext(),
860
- deps: [PageOrchestratorComponent],
861
- },
862
- ], changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;position:relative;width:100%}\n"] }]
863
- }], 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 }] }] } }) });
864
- function collectLeafFieldKeys(fields) {
865
- const keys = [];
866
- for (const field of fields) {
867
- if (isRowField(field) || isGenericContainerField(field)) {
868
- keys.push(...collectLeafFieldKeys(field.fields));
869
- }
870
- else if (field.key) {
871
- keys.push(field.key);
872
- }
873
- }
874
- return keys;
875
- }
876
-
877
- const NORMALIZED_ARRAY_METADATA = Symbol('normalizedArrayMetadata');
878
- function setNormalizedArrayMetadata(arrayField, metadata) {
879
- arrayField[NORMALIZED_ARRAY_METADATA] = metadata;
880
- }
881
- function getNormalizedArrayMetadata(arrayField) {
882
- return arrayField[NORMALIZED_ARRAY_METADATA];
883
- }
884
-
885
- function normalizeSimplifiedArrays(fields) {
886
- return fields.flatMap((field) => {
887
- if (isContainerField(field) && !isArrayField(field) && hasChildFields(field)) {
888
- return [{ ...field, fields: normalizeSimplifiedArrays(field.fields) }];
889
- }
890
- if (isArrayField(field) && !isSimplifiedArrayField(field)) {
891
- const normalizedItems = field.fields.map((item) => Array.isArray(item) ? normalizeSimplifiedArrays(item) : item);
892
- return [{ ...field, fields: normalizedItems }];
893
- }
894
- if (isSimplifiedArrayField(field)) {
895
- const { arrayField, addButton } = expandSimplifiedArray(field);
896
- return addButton ? [arrayField, addButton] : [arrayField];
897
- }
898
- return [field];
899
- });
900
- }
901
- const INVALID_ARRAY_CHILD_TYPES = new Set(['page', 'array']);
902
- function validateSimplifiedTemplate(field) {
903
- const { template, key } = field;
904
- const isObjectTemplate = Array.isArray(template);
905
- if (isObjectTemplate) {
906
- const templateFields = template;
907
- for (const tmpl of templateFields) {
908
- if (INVALID_ARRAY_CHILD_TYPES.has(tmpl.type)) {
909
- throw new DynamicFormError(`Simplified array "${key}" template contains a '${tmpl.type}' field (key: '${tmpl.key}'). ` +
910
- `Only leaf fields, rows, and groups are allowed as array children.`);
911
- }
912
- }
913
- }
914
- else {
915
- const singleTemplate = template;
916
- if (INVALID_ARRAY_CHILD_TYPES.has(singleTemplate.type)) {
917
- throw new DynamicFormError(`Simplified array "${key}" template has type '${singleTemplate.type}'. ` +
918
- `Only leaf fields, rows, and groups are allowed as array children.`);
919
- }
920
- }
921
- }
922
- function expandSimplifiedArray(field) {
923
- const { template, value = [], addButton, removeButton, key, logic, minLength, maxLength, wrappers, skipAutoWrappers, skipDefaultWrappers, } = field;
924
- const isObjectTemplate = Array.isArray(template);
925
- const values = value;
926
- validateSimplifiedTemplate(field);
927
- const items = values.map((v) => {
928
- if (isObjectTemplate) {
929
- return buildObjectItem(template, v, removeButton);
647
+ }
648
+ }
649
+ function expandSimplifiedArray(field) {
650
+ const { template, value = [], addButton, removeButton, key, logic, minLength, maxLength, wrappers, skipAutoWrappers, skipDefaultWrappers, validators, validationMessages, required, validateWhenHidden, } = field;
651
+ const isObjectTemplate = Array.isArray(template);
652
+ const values = value;
653
+ validateSimplifiedTemplate(field);
654
+ const items = values.map((v) => {
655
+ if (isObjectTemplate) {
656
+ return buildObjectItem(template, v, removeButton);
930
657
  }
931
658
  return buildPrimitiveItem(template, v);
932
659
  });
@@ -956,6 +683,18 @@ function expandSimplifiedArray(field) {
956
683
  }
957
684
  if (skipDefaultWrappers !== undefined) {
958
685
  arrayFieldObj['skipDefaultWrappers'] = skipDefaultWrappers;
686
+ }
687
+ if (validators !== undefined) {
688
+ arrayFieldObj['validators'] = validators;
689
+ }
690
+ if (validationMessages !== undefined) {
691
+ arrayFieldObj['validationMessages'] = validationMessages;
692
+ }
693
+ if (required !== undefined) {
694
+ arrayFieldObj['required'] = required;
695
+ }
696
+ if (validateWhenHidden !== undefined) {
697
+ arrayFieldObj['validateWhenHidden'] = validateWhenHidden;
959
698
  }
960
699
  const arrayField = arrayFieldObj;
961
700
  const primitiveFieldKey = !isObjectTemplate ? template.key : undefined;
@@ -1082,499 +821,104 @@ class SchemaRegistryService {
1082
821
  registerSchema(schema) {
1083
822
  this.registeredSchemas.set(schema.name, schema);
1084
823
  }
1085
- getSchema(name) {
1086
- return this.registeredSchemas.get(name);
1087
- }
1088
- getAllSchemas() {
1089
- return new Map(this.registeredSchemas);
1090
- }
1091
- clearSchemas() {
1092
- this.registeredSchemas.clear();
1093
- }
1094
- resolveSchema(schema) {
1095
- if (typeof schema === 'string') {
1096
- return this.getSchema(schema) || null;
1097
- }
1098
- return schema;
1099
- }
1100
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: SchemaRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1101
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: SchemaRegistryService });
1102
- }
1103
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: SchemaRegistryService, decorators: [{
1104
- type: Injectable
1105
- }] });
1106
-
1107
- const DEV_MODE = typeof ngDevMode === 'undefined' || !!ngDevMode;
1108
-
1109
- const DEFAULT_DEBOUNCE_MS = 500;
1110
- function createDebouncedEffect(source, callback, options) {
1111
- const { ms = DEFAULT_DEBOUNCE_MS, injector } = options;
1112
- const source$ = toObservable(source, { injector }).pipe(debounceTime(ms), distinctUntilChanged(), tap((value) => callback(value)));
1113
- return toSignal(source$, { injector });
1114
- }
1115
- function createDebouncedSignal(source, options) {
1116
- const { ms = DEFAULT_DEBOUNCE_MS, injector } = options;
1117
- const source$ = toObservable(source, { injector }).pipe(debounceTime(ms), distinctUntilChanged());
1118
- return toSignal(source$, { injector });
1119
- }
1120
-
1121
- function getConfigTrigger(config) {
1122
- if (!isStateLogicConfig(config)) {
1123
- return 'onChange';
1124
- }
1125
- return config.trigger ?? 'onChange';
1126
- }
1127
- function getConfigDebounceMs(config) {
1128
- if (!isStateLogicConfig(config)) {
1129
- return undefined;
1130
- }
1131
- if (config.trigger === 'debounced') {
1132
- return config.debounceMs;
1133
- }
1134
- return undefined;
1135
- }
1136
- function applyLogic(config, fieldPath) {
1137
- if (isDerivationLogicConfig(config))
1138
- return;
1139
- if (!isStateLogicConfig(config)) {
1140
- if (DEV_MODE) {
1141
- console.warn(`[Dynamic Forms] Unrecognized logic config type '${config.type}' in applyLogic. ` +
1142
- 'This config will be ignored. If this is a new logic type, ensure it is handled explicitly.');
1143
- }
1144
- return;
1145
- }
1146
- const path = fieldPath;
1147
- const trigger = getConfigTrigger(config);
1148
- const debounceMs = getConfigDebounceMs(config) ?? DEFAULT_DEBOUNCE_MS;
1149
- if (typeof config.condition === 'boolean') {
1150
- applyLogicFn(config.type, path, () => config.condition);
1151
- return;
1152
- }
1153
- if (isFormStateCondition(config.condition)) {
1154
- if (config.condition === 'formInvalid' || config.condition === 'pageInvalid') {
1155
- const reason = config.condition === 'formInvalid'
1156
- ? `it would create a validation cycle against the form's own validity`
1157
- : `a value field has no page context for it to resolve against`;
1158
- throw new DynamicFormError(`The '${config.condition}' form-state condition is not supported on value-field logic because ${reason}. ` +
1159
- `Use 'formSubmitting', or apply this condition to a button or container instead.`);
1160
- }
1161
- applyLogicFn(config.type, path, createFormStateLogicFunction(config.condition));
1162
- return;
1163
- }
1164
- const logicFn = trigger === 'debounced'
1165
- ? createDebouncedLogicFunction(config.condition, debounceMs)
1166
- : createLogicFunction(config.condition);
1167
- applyLogicFn(config.type, path, logicFn);
1168
- }
1169
- function applyLogicFn(type, path, logicFn) {
1170
- switch (type) {
1171
- case 'hidden':
1172
- hidden(path, logicFn);
1173
- break;
1174
- case 'readonly':
1175
- readonly(path, logicFn);
1176
- break;
1177
- case 'disabled':
1178
- disabled(path, logicFn);
1179
- break;
1180
- case 'required':
1181
- required(path, { when: logicFn });
1182
- break;
1183
- default: {
1184
- const _exhaustive = type;
1185
- throw new DynamicFormError(`Unhandled state logic type: ${_exhaustive}`);
1186
- }
1187
- }
1188
- }
1189
- function applyMultipleLogic(configs, fieldPath) {
1190
- configs.forEach((config) => applyLogic(config, fieldPath));
1191
- }
1192
-
1193
- function createTypePredicateFunction(predicate, logger) {
1194
- return (value) => {
1195
- try {
1196
- const result = ExpressionParser.evaluate(predicate, { value });
1197
- return Boolean(result);
1198
- }
1199
- catch (error) {
1200
- logger.error('Error evaluating type predicate:', predicate, error);
1201
- return false;
1202
- }
1203
- };
1204
- }
1205
-
1206
- function applySchema(config, fieldPath) {
1207
- const logger = inject(DynamicFormLogger);
1208
- const schemaRegistry = inject(SchemaRegistryService);
1209
- const schema = schemaRegistry.resolveSchema(config.schema);
1210
- if (!schema) {
1211
- const availableSchemas = Array.from(schemaRegistry.getAllSchemas().keys()).join(', ') || '<none>';
1212
- logger.error(`Schema not found: '${config.schema}'. ` +
1213
- `Available schemas: ${availableSchemas}. ` +
1214
- `Ensure the schema is registered in your schema registry.`);
1215
- return;
1216
- }
1217
- const schemaFn = createSchemaFunction(schema, logger);
1218
- const path = fieldPath;
1219
- switch (config.type) {
1220
- case 'apply':
1221
- apply(path, schemaFn);
1222
- break;
1223
- case 'applyWhen':
1224
- if (config.condition) {
1225
- const conditionFn = createLogicFunction(config.condition);
1226
- applyWhen(path, conditionFn, schemaFn);
1227
- }
1228
- break;
1229
- case 'applyWhenValue':
1230
- if (config.typePredicate) {
1231
- const predicate = createTypePredicateFunction(config.typePredicate, logger);
1232
- applyWhenValue(path, predicate, schemaFn);
1233
- }
1234
- break;
1235
- case 'applyEach':
1236
- applyEach(path, schemaFn);
1237
- break;
1238
- }
1239
- }
1240
- function createSchemaFunction(schema, logger) {
1241
- return (path) => {
1242
- schema.validators?.forEach((validatorConfig) => {
1243
- applyValidator(validatorConfig, path);
1244
- });
1245
- schema.logic?.forEach((logicConfig) => {
1246
- applyLogic(logicConfig, path);
1247
- });
1248
- schema.subSchemas?.forEach((subSchemaConfig) => {
1249
- applySchema(subSchemaConfig, path);
1250
- });
1251
- };
1252
- }
1253
-
1254
- function combineAncestorHiddenLogics(logics) {
1255
- if (logics.length === 0)
1256
- return undefined;
1257
- if (logics.length === 1)
1258
- return logics[0];
1259
- return (ctx) => logics.some((fn) => fn(ctx));
1260
- }
1261
- function resolveFieldOwnContext(fieldDef, parentContext) {
1262
- if (fieldDef.validateWhenHidden === undefined)
1263
- return parentContext;
1264
- if (fieldDef.validateWhenHidden === parentContext.validateWhenHidden)
1265
- return parentContext;
1266
- return {
1267
- ...parentContext,
1268
- validateWhenHidden: fieldDef.validateWhenHidden,
1269
- };
1270
- }
1271
- function resolveDescendantContext(fieldDef, ownContext) {
1272
- let ancestorAlwaysHidden = ownContext.ancestorAlwaysHidden;
1273
- let ancestorHiddenLogics = ownContext.ancestorHiddenLogics;
1274
- let mutated = false;
1275
- if (fieldDef.hidden === true && !ancestorAlwaysHidden) {
1276
- ancestorAlwaysHidden = true;
1277
- mutated = true;
1278
- }
1279
- if (fieldDef.logic) {
1280
- for (const logic of fieldDef.logic) {
1281
- if (!isStateLogicConfig(logic) || logic.type !== 'hidden')
1282
- continue;
1283
- const condition = logic.condition;
1284
- if (condition === true) {
1285
- if (!ancestorAlwaysHidden) {
1286
- ancestorAlwaysHidden = true;
1287
- mutated = true;
1288
- }
1289
- continue;
1290
- }
1291
- if (condition === false)
1292
- continue;
1293
- if (typeof condition === 'string')
1294
- continue;
1295
- const fn = createLogicFunction(condition);
1296
- ancestorHiddenLogics = ancestorHiddenLogics.length === 0 ? [fn] : [...ancestorHiddenLogics, fn];
1297
- mutated = true;
1298
- }
1299
- }
1300
- if (!mutated)
1301
- return ownContext;
1302
- return {
1303
- validateWhenHidden: ownContext.validateWhenHidden,
1304
- ancestorAlwaysHidden,
1305
- ancestorHiddenLogics,
1306
- };
1307
- }
1308
-
1309
- const DEFAULT_FIELD_TREE_MAPPING_CONTEXT = {
1310
- validateWhenHidden: false,
1311
- ancestorAlwaysHidden: false,
1312
- ancestorHiddenLogics: [],
1313
- };
1314
- function applyStringValidators(path, config) {
1315
- if (config.email) {
1316
- email(path);
1317
- }
1318
- if (config.minLength !== undefined) {
1319
- minLength(path, config.minLength);
1320
- }
1321
- if (config.maxLength !== undefined) {
1322
- maxLength(path, config.maxLength);
1323
- }
1324
- if (config.pattern) {
1325
- let regex;
1326
- if (typeof config.pattern === 'string') {
1327
- try {
1328
- regex = new RegExp(config.pattern);
1329
- }
1330
- catch (e) {
1331
- throw new DynamicFormError(`Invalid regex pattern: '${config.pattern}' — ${e instanceof Error ? e.message : String(e)}`);
1332
- }
1333
- }
1334
- else {
1335
- regex = config.pattern;
1336
- }
1337
- pattern(path, regex);
1338
- }
1339
- }
1340
- function applyNumberValidators(path, config) {
1341
- if (config.min !== undefined) {
1342
- min(path, config.min);
1343
- }
1344
- if (config.max !== undefined) {
1345
- max(path, config.max);
1346
- }
1347
- }
1348
- function getStringValidationConfig(fieldDef) {
1349
- if (!fieldDef.email && fieldDef.minLength === undefined && fieldDef.maxLength === undefined && !fieldDef.pattern) {
1350
- return undefined;
1351
- }
1352
- return {
1353
- email: fieldDef.email,
1354
- minLength: fieldDef.minLength,
1355
- maxLength: fieldDef.maxLength,
1356
- pattern: fieldDef.pattern,
1357
- };
1358
- }
1359
- function getNumberValidationConfig(fieldDef) {
1360
- const minVal = fieldDef.min ?? fieldDef.minValue;
1361
- const maxVal = fieldDef.max ?? fieldDef.maxValue;
1362
- if (minVal === undefined && maxVal === undefined) {
1363
- return undefined;
1364
- }
1365
- return { min: minVal, max: maxVal };
1366
- }
1367
- function mapFieldToForm(fieldDef, fieldPath, inheritedContext = DEFAULT_FIELD_TREE_MAPPING_CONTEXT) {
1368
- const ownContext = resolveFieldOwnContext(fieldDef, inheritedContext);
1369
- if (isPageField(fieldDef) || isRowField(fieldDef) || isGenericContainerField(fieldDef)) {
1370
- const descendantContext = resolveDescendantContext(fieldDef, ownContext);
1371
- mapContainerChildren(fieldDef.fields, fieldPath, descendantContext);
1372
- return;
1373
- }
1374
- if (isGroupField(fieldDef)) {
1375
- const descendantContext = resolveDescendantContext(fieldDef, ownContext);
1376
- mapContainerChildren(fieldDef.fields, fieldPath, descendantContext);
1377
- return;
1378
- }
1379
- if (isArrayField(fieldDef)) {
1380
- const descendantContext = resolveDescendantContext(fieldDef, ownContext);
1381
- mapArrayFieldToForm(fieldDef, fieldPath, descendantContext);
1382
- return;
1383
- }
1384
- mapLeafField(fieldDef, fieldPath, ownContext);
1385
- }
1386
- function mapContainerChildren(fields, parentPath, inheritedContext) {
1387
- if (!fields)
1388
- return;
1389
- const pathRecord = parentPath;
1390
- for (const field of fields) {
1391
- if (isPageField(field) || isRowField(field) || isGenericContainerField(field)) {
1392
- const layoutOwn = resolveFieldOwnContext(field, inheritedContext);
1393
- const layoutDescendant = resolveDescendantContext(field, layoutOwn);
1394
- mapContainerChildren(field.fields, parentPath, layoutDescendant);
1395
- continue;
1396
- }
1397
- if (!field.key)
1398
- continue;
1399
- const childPath = pathRecord[field.key];
1400
- if (childPath) {
1401
- mapFieldToForm(field, childPath, inheritedContext);
1402
- }
1403
- }
1404
- }
1405
- function isValidationStateLogic(config) {
1406
- return isStateLogicConfig(config) && config.type === 'required';
1407
- }
1408
- function mapLeafField(fieldDef, fieldPath, context) {
1409
- const validationField = fieldDef;
1410
- const path = fieldPath;
1411
- applyFieldState(fieldDef, path);
1412
- if (validationField.logic) {
1413
- for (const config of validationField.logic) {
1414
- if (!isValidationStateLogic(config)) {
1415
- applyLogic(config, fieldPath);
1416
- }
1417
- }
1418
- }
1419
- if (validationField.schemas) {
1420
- for (const config of validationField.schemas) {
1421
- applySchema(config, fieldPath);
1422
- }
1423
- }
1424
- const applyValidation = (validationPath) => {
1425
- applySimpleValidationRules(validationField, validationPath);
1426
- if (validationField.validators) {
1427
- for (const config of validationField.validators) {
1428
- applyValidator(config, validationPath);
1429
- }
1430
- }
1431
- if (validationField.logic) {
1432
- for (const config of validationField.logic) {
1433
- if (isValidationStateLogic(config)) {
1434
- applyLogic(config, validationPath);
1435
- }
1436
- }
1437
- }
1438
- };
1439
- if (context.validateWhenHidden) {
1440
- applyValidation(path);
1441
- return;
1442
- }
1443
- if (context.ancestorAlwaysHidden) {
1444
- return;
1445
- }
1446
- const ancestorHiddenLogic = combineAncestorHiddenLogics(context.ancestorHiddenLogics);
1447
- applyWhen(path, (ctx) => {
1448
- if (ctx.state.hidden())
1449
- return false;
1450
- if (ancestorHiddenLogic && ancestorHiddenLogic(ctx))
1451
- return false;
1452
- return true;
1453
- }, schema((subPath) => applyValidation(subPath)));
1454
- }
1455
- function applySimpleValidationRules(fieldDef, path) {
1456
- if (fieldDef.required) {
1457
- required(path);
1458
- }
1459
- const stringConfig = getStringValidationConfig(fieldDef);
1460
- if (stringConfig) {
1461
- applyStringValidators(path, stringConfig);
1462
- }
1463
- const numberConfig = getNumberValidationConfig(fieldDef);
1464
- if (numberConfig) {
1465
- applyNumberValidators(path, numberConfig);
1466
- }
1467
- }
1468
- function applyFieldState(fieldDef, path) {
1469
- if (fieldDef.disabled) {
1470
- disabled(path);
1471
- }
1472
- if (fieldDef.readonly) {
1473
- readonly(path);
1474
- }
1475
- if (fieldDef.hidden) {
1476
- hidden(path, () => true);
1477
- }
1478
- }
1479
- function mapArrayFieldToForm(arrayField, fieldPath, context) {
1480
- if (!isArrayField(arrayField)) {
1481
- return;
1482
- }
1483
- if (arrayField.minLength !== undefined) {
1484
- minLength(fieldPath, arrayField.minLength);
1485
- }
1486
- if (arrayField.maxLength !== undefined) {
1487
- maxLength(fieldPath, arrayField.maxLength);
824
+ getSchema(name) {
825
+ return this.registeredSchemas.get(name);
1488
826
  }
1489
- let itemDefinitions = arrayField.fields;
1490
- if (!itemDefinitions || itemDefinitions.length === 0) {
1491
- const metadataTemplate = getNormalizedArrayMetadata(arrayField)?.template;
1492
- if (metadataTemplate) {
1493
- itemDefinitions = [
1494
- Array.isArray(metadataTemplate) ? [...metadataTemplate] : metadataTemplate,
1495
- ];
1496
- }
827
+ getAllSchemas() {
828
+ return new Map(this.registeredSchemas);
1497
829
  }
1498
- if (!itemDefinitions || itemDefinitions.length === 0) {
1499
- const emptyItemSchema = schema(() => {
1500
- });
1501
- applyEach(fieldPath, emptyItemSchema);
1502
- return;
830
+ clearSchemas() {
831
+ this.registeredSchemas.clear();
1503
832
  }
1504
- let hasPrimitiveItems = false;
1505
- const allObjectFields = [];
1506
- for (const itemDef of itemDefinitions) {
1507
- if (Array.isArray(itemDef)) {
1508
- collectFieldsFromObjectItem(itemDef, allObjectFields);
1509
- }
1510
- else if (isPageField(itemDef) ||
1511
- isRowField(itemDef) ||
1512
- isGenericContainerField(itemDef)) {
1513
- collectFieldsFromObjectItem([itemDef], allObjectFields);
1514
- }
1515
- else {
1516
- hasPrimitiveItems = true;
833
+ resolveSchema(schema) {
834
+ if (typeof schema === 'string') {
835
+ return this.getSchema(schema) || null;
1517
836
  }
837
+ return schema;
1518
838
  }
1519
- if (hasPrimitiveItems && allObjectFields.length === 0) {
1520
- const primitiveItemSchema = schema(() => {
1521
- });
1522
- applyEach(fieldPath, primitiveItemSchema);
1523
- return;
839
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: SchemaRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
840
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: SchemaRegistryService });
841
+ }
842
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: SchemaRegistryService, decorators: [{
843
+ type: Injectable
844
+ }] });
845
+
846
+ function inheritContainerExclusion(container, child) {
847
+ const inheritedHidden = container.hidden === true || child.inheritedExclusionState?.hidden === true;
848
+ const inheritedDisabled = container.disabled === true || child.inheritedExclusionState?.disabled === true;
849
+ const inheritedReadonly = container.readonly === true || child.inheritedExclusionState?.readonly === true;
850
+ const hasInheritedState = inheritedHidden || inheritedDisabled || inheritedReadonly;
851
+ const containerExcludeConfig = container.excludeValueIfHidden !== undefined ||
852
+ container.excludeValueIfDisabled !== undefined ||
853
+ container.excludeValueIfReadonly !== undefined;
854
+ if (!hasInheritedState && !containerExcludeConfig) {
855
+ return child;
1524
856
  }
1525
- const itemSchema = schema((itemPath) => {
1526
- const pathRecord = itemPath;
1527
- for (const templateField of allObjectFields) {
1528
- if (isRowField(templateField) || isPageField(templateField) || isGenericContainerField(templateField)) {
1529
- const layoutOwn = resolveFieldOwnContext(templateField, context);
1530
- const layoutDescendant = resolveDescendantContext(templateField, layoutOwn);
1531
- mapContainerChildren(templateField.fields, itemPath, layoutDescendant);
1532
- }
1533
- else if (isGroupField(templateField)) {
1534
- const groupKey = templateField.key;
1535
- const groupOwn = resolveFieldOwnContext(templateField, context);
1536
- const groupDescendant = resolveDescendantContext(templateField, groupOwn);
1537
- if (groupKey) {
1538
- const groupPath = pathRecord[groupKey];
1539
- if (groupPath) {
1540
- mapContainerChildren(templateField.fields, groupPath, groupDescendant);
1541
- }
1542
- }
1543
- else {
1544
- mapContainerChildren(templateField.fields, itemPath, groupDescendant);
1545
- }
1546
- }
1547
- else {
1548
- const fieldKey = templateField.key;
1549
- if (fieldKey) {
1550
- const fieldPathForKey = pathRecord[fieldKey];
1551
- if (fieldPathForKey) {
1552
- mapFieldToForm(templateField, fieldPathForKey, context);
1553
- }
1554
- }
857
+ return {
858
+ ...child,
859
+ ...(hasInheritedState
860
+ ? { inheritedExclusionState: { hidden: inheritedHidden, disabled: inheritedDisabled, readonly: inheritedReadonly } }
861
+ : {}),
862
+ excludeValueIfHidden: child.excludeValueIfHidden ?? container.excludeValueIfHidden,
863
+ excludeValueIfDisabled: child.excludeValueIfDisabled ?? container.excludeValueIfDisabled,
864
+ excludeValueIfReadonly: child.excludeValueIfReadonly ?? container.excludeValueIfReadonly,
865
+ };
866
+ }
867
+ function flattenFields(fields, registry, options = {}) {
868
+ const result = [];
869
+ let autoKeyCounter = 0;
870
+ for (const field of fields) {
871
+ const valueHandling = getFieldValueHandling(field.type, registry);
872
+ if (options.preserveRows && (isRowField(field) || isGenericContainerField(field))) {
873
+ if (field.fields) {
874
+ const flattenedChildren = flattenFields(normalizeFieldsArray(field.fields), registry, options);
875
+ const autoPrefix = isRowField(field) ? 'auto_row' : 'auto_container';
876
+ result.push({
877
+ ...field,
878
+ fields: flattenedChildren,
879
+ key: field.key || `${autoPrefix}_${autoKeyCounter++}`,
880
+ });
1555
881
  }
1556
882
  }
1557
- });
1558
- applyEach(fieldPath, itemSchema);
1559
- }
1560
- function collectFieldsFromObjectItem(itemFields, allFields) {
1561
- const seenKeys = new Set(allFields.map((f) => f.key).filter(Boolean));
1562
- for (const field of itemFields) {
1563
- if (isRowField(field) || isPageField(field) || isGenericContainerField(field)) {
1564
- const containerKey = `__container_${field.type}_${JSON.stringify(field.fields?.map((f) => f.key))}`;
1565
- if (!seenKeys.has(containerKey)) {
1566
- seenKeys.add(containerKey);
1567
- allFields.push(field);
883
+ else if (valueHandling === 'flatten' && 'fields' in field) {
884
+ if (field.fields) {
885
+ const fields = field.fields;
886
+ const flattenedChildren = flattenFields(normalizeFieldsArray(fields), registry, options);
887
+ result.push(...flattenedChildren.map((child) => inheritContainerExclusion(field, child)));
1568
888
  }
1569
889
  }
890
+ else if (isGroupField(field)) {
891
+ const childFieldsArray = Object.values(field.fields);
892
+ const flattenedChildren = flattenFields(childFieldsArray, registry, options);
893
+ result.push({
894
+ ...field,
895
+ fields: flattenedChildren,
896
+ key: field.key || `auto_group_${autoKeyCounter++}`,
897
+ });
898
+ }
899
+ else if (isArrayField(field)) {
900
+ const flattenedItemTemplates = field.fields.map((itemDef) => {
901
+ if (!Array.isArray(itemDef)) {
902
+ const flattened = flattenFields([itemDef], registry, options);
903
+ return flattened[0];
904
+ }
905
+ return flattenFields([...itemDef], registry, options);
906
+ });
907
+ result.push({
908
+ ...field,
909
+ fields: flattenedItemTemplates,
910
+ key: field.key || `auto_array_${autoKeyCounter++}`,
911
+ });
912
+ }
1570
913
  else {
1571
- const key = field.key;
1572
- if (key && !seenKeys.has(key)) {
1573
- seenKeys.add(key);
1574
- allFields.push(field);
1575
- }
914
+ const key = field.key || `auto_field_${autoKeyCounter++}`;
915
+ result.push({
916
+ ...field,
917
+ key,
918
+ });
1576
919
  }
1577
920
  }
921
+ return result;
1578
922
  }
1579
923
 
1580
924
  function getFieldDefaultValue(field, registry) {
@@ -1680,160 +1024,6 @@ function getFieldDefaultValue(field, registry) {
1680
1024
  return '';
1681
1025
  }
1682
1026
 
1683
- function applyFormLevelSchema(path, formLevelSchema) {
1684
- if (isStandardSchemaMarker(formLevelSchema)) {
1685
- validateStandardSchema(path, formLevelSchema.schema);
1686
- }
1687
- else if (typeof formLevelSchema === 'function') {
1688
- formLevelSchema(path);
1689
- }
1690
- }
1691
- function createFormLevelSchema(formLevelSchema) {
1692
- return schema((path) => {
1693
- applyFormLevelSchema(path, formLevelSchema);
1694
- });
1695
- }
1696
-
1697
- const VALIDATION_EXECUTION_DEFAULTS = new InjectionToken('VALIDATION_EXECUTION_DEFAULTS', {
1698
- providedIn: 'root',
1699
- factory: () => ({
1700
- validateWhenHidden: false,
1701
- }),
1702
- });
1703
-
1704
- function createSchemaFromFields(fields, registry, options = {}) {
1705
- const validationExecutionDefaults = inject(VALIDATION_EXECUTION_DEFAULTS);
1706
- const { formLevelSchema } = options;
1707
- const rootContext = {
1708
- validateWhenHidden: options.validateWhenHidden ?? validationExecutionDefaults.validateWhenHidden,
1709
- ancestorAlwaysHidden: false,
1710
- ancestorHiddenLogics: [],
1711
- };
1712
- return schema((path) => {
1713
- for (const fieldDef of fields) {
1714
- const valueHandling = getFieldValueHandling(fieldDef.type, registry);
1715
- if (valueHandling === 'exclude') {
1716
- continue;
1717
- }
1718
- if (valueHandling === 'flatten' && hasChildFields(fieldDef)) {
1719
- const flattenedOwn = resolveFieldOwnContext(fieldDef, rootContext);
1720
- const flattenedContext = resolveDescendantContext(fieldDef, flattenedOwn);
1721
- for (const childField of normalizeFieldsArray(fieldDef.fields)) {
1722
- if (!childField.key)
1723
- continue;
1724
- const childPath = path[childField.key];
1725
- if (childPath) {
1726
- mapFieldToForm(childField, childPath, flattenedContext);
1727
- }
1728
- }
1729
- continue;
1730
- }
1731
- const fieldPath = path[fieldDef.key];
1732
- if (!fieldPath) {
1733
- continue;
1734
- }
1735
- mapFieldToForm(fieldDef, fieldPath, rootContext);
1736
- }
1737
- if (formLevelSchema) {
1738
- applyFormLevelSchema(path, formLevelSchema);
1739
- }
1740
- });
1741
- }
1742
- function fieldsToDefaultValues(fields, registry) {
1743
- const defaultValues = {};
1744
- for (const field of fields) {
1745
- if (!field.key)
1746
- continue;
1747
- const valueHandling = getFieldValueHandling(field.type, registry);
1748
- if (valueHandling === 'flatten') {
1749
- continue;
1750
- }
1751
- const value = getFieldDefaultValue(field, registry);
1752
- if (value !== undefined) {
1753
- defaultValues[field.key] = value;
1754
- }
1755
- }
1756
- return defaultValues;
1757
- }
1758
-
1759
- function inheritContainerExclusion(container, child) {
1760
- const inheritedHidden = container.hidden === true || child.inheritedExclusionState?.hidden === true;
1761
- const inheritedDisabled = container.disabled === true || child.inheritedExclusionState?.disabled === true;
1762
- const inheritedReadonly = container.readonly === true || child.inheritedExclusionState?.readonly === true;
1763
- const hasInheritedState = inheritedHidden || inheritedDisabled || inheritedReadonly;
1764
- const containerExcludeConfig = container.excludeValueIfHidden !== undefined ||
1765
- container.excludeValueIfDisabled !== undefined ||
1766
- container.excludeValueIfReadonly !== undefined;
1767
- if (!hasInheritedState && !containerExcludeConfig) {
1768
- return child;
1769
- }
1770
- return {
1771
- ...child,
1772
- ...(hasInheritedState
1773
- ? { inheritedExclusionState: { hidden: inheritedHidden, disabled: inheritedDisabled, readonly: inheritedReadonly } }
1774
- : {}),
1775
- excludeValueIfHidden: child.excludeValueIfHidden ?? container.excludeValueIfHidden,
1776
- excludeValueIfDisabled: child.excludeValueIfDisabled ?? container.excludeValueIfDisabled,
1777
- excludeValueIfReadonly: child.excludeValueIfReadonly ?? container.excludeValueIfReadonly,
1778
- };
1779
- }
1780
- function flattenFields(fields, registry, options = {}) {
1781
- const result = [];
1782
- let autoKeyCounter = 0;
1783
- for (const field of fields) {
1784
- const valueHandling = getFieldValueHandling(field.type, registry);
1785
- if (options.preserveRows && (isRowField(field) || isGenericContainerField(field))) {
1786
- if (field.fields) {
1787
- const flattenedChildren = flattenFields(normalizeFieldsArray(field.fields), registry, options);
1788
- const autoPrefix = isRowField(field) ? 'auto_row' : 'auto_container';
1789
- result.push({
1790
- ...field,
1791
- fields: flattenedChildren,
1792
- key: field.key || `${autoPrefix}_${autoKeyCounter++}`,
1793
- });
1794
- }
1795
- }
1796
- else if (valueHandling === 'flatten' && 'fields' in field) {
1797
- if (field.fields) {
1798
- const fields = field.fields;
1799
- const flattenedChildren = flattenFields(normalizeFieldsArray(fields), registry, options);
1800
- result.push(...flattenedChildren.map((child) => inheritContainerExclusion(field, child)));
1801
- }
1802
- }
1803
- else if (isGroupField(field)) {
1804
- const childFieldsArray = Object.values(field.fields);
1805
- const flattenedChildren = flattenFields(childFieldsArray, registry, options);
1806
- result.push({
1807
- ...field,
1808
- fields: flattenedChildren,
1809
- key: field.key || `auto_group_${autoKeyCounter++}`,
1810
- });
1811
- }
1812
- else if (isArrayField(field)) {
1813
- const flattenedItemTemplates = field.fields.map((itemDef) => {
1814
- if (!Array.isArray(itemDef)) {
1815
- const flattened = flattenFields([itemDef], registry, options);
1816
- return flattened[0];
1817
- }
1818
- return flattenFields([...itemDef], registry, options);
1819
- });
1820
- result.push({
1821
- ...field,
1822
- fields: flattenedItemTemplates,
1823
- key: field.key || `auto_array_${autoKeyCounter++}`,
1824
- });
1825
- }
1826
- else {
1827
- const key = field.key || `auto_field_${autoKeyCounter++}`;
1828
- result.push({
1829
- ...field,
1830
- key,
1831
- });
1832
- }
1833
- }
1834
- return result;
1835
- }
1836
-
1837
1027
  function createContainerFieldProcessors() {
1838
1028
  const flattenCache = new WeakMap();
1839
1029
  const memoizedFlattenFields = (fields, registry) => {
@@ -2272,9 +1462,14 @@ const COMPONENT_CACHE = new InjectionToken('COMPONENT_CACHE', {
2272
1462
  providedIn: 'root',
2273
1463
  factory: () => new Map(),
2274
1464
  });
1465
+ const COMPONENT_LOAD_CACHE = new InjectionToken('COMPONENT_LOAD_CACHE', {
1466
+ providedIn: 'root',
1467
+ factory: () => new Map(),
1468
+ });
2275
1469
  function injectFieldRegistry() {
2276
1470
  const registry = inject(FIELD_REGISTRY);
2277
1471
  const componentCache = inject(COMPONENT_CACHE);
1472
+ const componentLoadCache = inject(COMPONENT_LOAD_CACHE);
2278
1473
  return {
2279
1474
  getType(name) {
2280
1475
  return registry.get(name);
@@ -2294,12 +1489,26 @@ function injectFieldRegistry() {
2294
1489
  if (cached) {
2295
1490
  return cached;
2296
1491
  }
1492
+ const pending = componentLoadCache.get(name);
1493
+ if (pending) {
1494
+ return pending;
1495
+ }
2297
1496
  try {
2298
- const component = resolveDefaultExport(await fieldType.loadComponent());
2299
- if (component) {
2300
- componentCache.set(name, component);
2301
- }
2302
- return component;
1497
+ const load = fieldType
1498
+ .loadComponent()
1499
+ .then(resolveDefaultExport)
1500
+ .then((component) => {
1501
+ if (component) {
1502
+ componentCache.set(name, component);
1503
+ }
1504
+ return component;
1505
+ })
1506
+ .catch((error) => {
1507
+ throw new DynamicFormError(`Failed to load component for field type "${name}": ${error}`);
1508
+ })
1509
+ .finally(() => componentLoadCache.delete(name));
1510
+ componentLoadCache.set(name, load);
1511
+ return load;
2303
1512
  }
2304
1513
  catch (error) {
2305
1514
  throw new DynamicFormError(`Failed to load component for field type "${name}": ${error}`);
@@ -2368,7 +1577,9 @@ function validateRegexPattern(pattern, fieldKey, errors) {
2368
1577
  new RegExp(pattern);
2369
1578
  }
2370
1579
  catch (e) {
2371
- errors.push(`Invalid regex pattern in validator for field '${fieldKey}': '${pattern}' — ${e instanceof Error ? e.message : String(e)}`);
1580
+ errors.push(DEV_MODE
1581
+ ? `Invalid regex pattern in validator for field '${fieldKey}': '${pattern}' — ${e instanceof Error ? e.message : String(e)}`
1582
+ : `Invalid regex pattern: '${pattern}'`);
2372
1583
  }
2373
1584
  }
2374
1585
  function validateNoDuplicateKeys(allKeys) {
@@ -2384,7 +1595,9 @@ function validateNoDuplicateKeys(allKeys) {
2384
1595
  const duplicateList = Array.from(duplicates)
2385
1596
  .map((k) => `'${k}'`)
2386
1597
  .join(', ');
2387
- 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).`);
1598
+ throw new DynamicFormError(DEV_MODE
1599
+ ? `Duplicate field keys detected: ${duplicateList}. Field keys must be unique within their group scope (the same key may appear inside different groups).`
1600
+ : `Duplicate field keys detected: ${duplicateList}.`);
2388
1601
  }
2389
1602
  }
2390
1603
  function validateNoDomIdCollisions(domIds) {
@@ -2400,9 +1613,11 @@ function validateNoDomIdCollisions(domIds) {
2400
1613
  const duplicateList = Array.from(duplicates)
2401
1614
  .map((k) => `'${k}'`)
2402
1615
  .join(', ');
2403
- throw new DynamicFormError(`DOM id collision detected for: ${duplicateList}. Group-nested keys are joined with '_' to form DOM ids ` +
2404
- `(e.g. group 'foo' + leaf 'bar' renders as id='foo_bar'). A separate top-level key like 'foo_bar' or a different ` +
2405
- `group/leaf pair that underscores to the same string will produce duplicate ids. Rename one of the colliding keys.`);
1616
+ throw new DynamicFormError(DEV_MODE
1617
+ ? `DOM id collision detected for: ${duplicateList}. Group-nested keys are joined with '_' to form DOM ids ` +
1618
+ `(e.g. group 'foo' + leaf 'bar' renders as id='foo_bar'). A separate top-level key like 'foo_bar' or a different ` +
1619
+ `group/leaf pair that underscores to the same string will produce duplicate ids. Rename one of the colliding keys.`
1620
+ : `DOM id collision detected for: ${duplicateList}.`);
2406
1621
  }
2407
1622
  }
2408
1623
  function validateFieldTypesRegistered(allTypes, registry, logger) {
@@ -2432,12 +1647,15 @@ function validateFormConfig(fields, registry, logger) {
2432
1647
  const list = Array.from(new Set(data.dottedKeys))
2433
1648
  .map((k) => `'${k}'`)
2434
1649
  .join(', ');
2435
- throw new DynamicFormError(`Field key(s) contain a dot: ${list}. Dotted keys are not supported — express nesting with 'group' fields ` +
2436
- `(e.g. { type: 'group', key: 'address', fields: [{ type: 'input', key: 'city' }] }).`);
1650
+ throw new DynamicFormError(DEV_MODE
1651
+ ? `Field key(s) contain a dot: ${list}. Dotted keys are not supported — express nesting with 'group' fields ` +
1652
+ `(e.g. { type: 'group', key: 'address', fields: [{ type: 'input', key: 'city' }] }).`
1653
+ : `Field key(s) contain a dot: ${list}.`);
2437
1654
  }
2438
1655
  validateNoDuplicateKeys(data.keys);
2439
1656
  validateNoDomIdCollisions(data.domIds);
2440
- validateFieldTypesRegistered(data.types, registry, logger);
1657
+ if (DEV_MODE)
1658
+ validateFieldTypesRegistered(data.types, registry, logger);
2441
1659
  if (data.regexErrors.length > 0) {
2442
1660
  throw new DynamicFormError(data.regexErrors.length === 1
2443
1661
  ? data.regexErrors[0]
@@ -2652,6 +1870,33 @@ function resolveExclusionConfig(global, form, field) {
2652
1870
  excludeValueIfReadonly: field?.excludeValueIfReadonly ?? form?.excludeValueIfReadonly ?? global.excludeValueIfReadonly,
2653
1871
  };
2654
1872
  }
1873
+ function hasEnabledValueExclusion(fields, global, form) {
1874
+ return fields.some((field) => {
1875
+ const resolved = resolveExclusionConfig(global, form, field);
1876
+ if (resolved.excludeValueIfHidden || resolved.excludeValueIfDisabled || resolved.excludeValueIfReadonly) {
1877
+ return true;
1878
+ }
1879
+ return (isGroupField(field) && !!field.fields && hasEnabledValueExclusion(Object.values(field.fields), global, form));
1880
+ });
1881
+ }
1882
+ function filterDeclaredValueShape(rawValue, fields, registry) {
1883
+ const kept = {};
1884
+ for (const field of fields) {
1885
+ const key = field.key;
1886
+ if (!key || !(key in rawValue))
1887
+ continue;
1888
+ const valueHandling = getFieldValueHandling(field.type, registry);
1889
+ if (valueHandling === 'exclude' || valueHandling === 'flatten')
1890
+ continue;
1891
+ const value = rawValue[key];
1892
+ if (isGroupField(field) && field.fields && value !== null && typeof value === 'object' && !Array.isArray(value)) {
1893
+ kept[key] = filterDeclaredValueShape(value, Object.values(field.fields), registry);
1894
+ continue;
1895
+ }
1896
+ kept[key] = value;
1897
+ }
1898
+ return kept;
1899
+ }
2655
1900
  function shouldExcludeField(field, fieldState, config) {
2656
1901
  const state = fieldState();
2657
1902
  const inherited = field.inheritedExclusionState;
@@ -2669,7 +1914,10 @@ function shouldExcludeField(field, fieldState, config) {
2669
1914
  }
2670
1915
  return false;
2671
1916
  }
2672
- function filterFormValue(rawValue, schemaFields, formTree, registry, globalDefaults, formOptions) {
1917
+ function filterFormValue(rawValue, schemaFields, formTree, registry, globalDefaults, formOptions, exclusionEnabled = hasEnabledValueExclusion(schemaFields, globalDefaults, formOptions)) {
1918
+ if (!exclusionEnabled) {
1919
+ return filterDeclaredValueShape(rawValue, schemaFields, registry);
1920
+ }
2673
1921
  const result = {};
2674
1922
  for (const field of schemaFields) {
2675
1923
  const key = field.key;
@@ -2705,7 +1953,7 @@ function filterFormValue(rawValue, schemaFields, formTree, registry, globalDefau
2705
1953
  if (groupValue && typeof groupValue === 'object') {
2706
1954
  const childFields = Object.values(field.fields);
2707
1955
  const nestedTree = fieldState;
2708
- result[key] = filterFormValue(groupValue, childFields, nestedTree, registry, globalDefaults, formOptions);
1956
+ result[key] = filterFormValue(groupValue, childFields, nestedTree, registry, globalDefaults, formOptions, true);
2709
1957
  }
2710
1958
  else {
2711
1959
  result[key] = groupValue;
@@ -3018,7 +2266,7 @@ class FormStateMachine {
3018
2266
  if (!isTransitioningState(state) || state.phase !== Phase.Restoring) {
3019
2267
  return { state, sideEffects: [] };
3020
2268
  }
3021
- if (!state.pendingFormSetup) {
2269
+ if (DEV_MODE && !state.pendingFormSetup) {
3022
2270
  this.config.logger.warn('handleRestoreComplete: pendingFormSetup was not set — falling back to recomputing. ' +
3023
2271
  'This indicates a bug in the transition flow.');
3024
2272
  }
@@ -3081,7 +2329,7 @@ class FormStateMachine {
3081
2329
  const state = untracked(() => this._state());
3082
2330
  if (!isTransitioningState(state) || state.phase !== Phase.Restoring)
3083
2331
  return;
3084
- if (!state.pendingFormSetup) {
2332
+ if (DEV_MODE && !state.pendingFormSetup) {
3085
2333
  this.config.logger.warn('RestoreValues effect: pendingFormSetup was not set — falling back to recomputing.');
3086
2334
  }
3087
2335
  const formSetup = state.pendingFormSetup ?? this.config.createFormSetup(state.pendingConfig);
@@ -3223,13 +2471,6 @@ class FormStateManager {
3223
2471
  return detectFormMode(config.fields || []);
3224
2472
  }, /* @ts-ignore */
3225
2473
  ...(ngDevMode ? [{ debugName: "formModeDetection" }] : /* istanbul ignore next */ []));
3226
- formConfigValidation = computed(() => {
3227
- const config = this.activeConfig();
3228
- if (!config)
3229
- return { isValid: true, errors: [], warnings: [] };
3230
- return FormModeValidator.validateFormConfiguration(config.fields || []);
3231
- }, /* @ts-ignore */
3232
- ...(ngDevMode ? [{ debugName: "formConfigValidation" }] : /* istanbul ignore next */ []));
3233
2474
  effectiveFormOptions = computed(() => {
3234
2475
  const config = this.activeConfig();
3235
2476
  const configOptions = config?.options || {};
@@ -3237,6 +2478,8 @@ class FormStateManager {
3237
2478
  return { ...configOptions, ...inputOptions };
3238
2479
  }, /* @ts-ignore */
3239
2480
  ...(ngDevMode ? [{ debugName: "effectiveFormOptions" }] : /* istanbul ignore next */ []));
2481
+ pendingPageRequest = signal(null, /* @ts-ignore */
2482
+ ...(ngDevMode ? [{ debugName: "pendingPageRequest" }] : /* istanbul ignore next */ []));
3240
2483
  pageFieldDefinitions = computed(() => {
3241
2484
  const config = this.activeConfig();
3242
2485
  const mode = this.formModeDetection().mode;
@@ -3273,22 +2516,71 @@ class FormStateManager {
3273
2516
  }
3274
2517
  return this.createEmptyFormSetup(registry);
3275
2518
  }, /* @ts-ignore */
3276
- ...(ngDevMode ? [{ debugName: "formSetup" }] : /* istanbul ignore next */ []));
2519
+ ...(ngDevMode ? [{ debugName: "formSetup" }] : /* istanbul ignore next */ []));
2520
+ formSetupConfig = computed(() => {
2521
+ const state = this.machine.state();
2522
+ if (isReadyState(state))
2523
+ return state.config;
2524
+ if (isTransitioningState(state)) {
2525
+ if (state.phase === Phase.Restoring && state.pendingFormSetup) {
2526
+ return state.pendingConfig;
2527
+ }
2528
+ return state.currentConfig;
2529
+ }
2530
+ if (state.type === LifecycleState.Initializing)
2531
+ return state.config;
2532
+ if (state.type === LifecycleState.Uninitialized)
2533
+ return this.activeConfig();
2534
+ return undefined;
2535
+ }, /* @ts-ignore */
2536
+ ...(ngDevMode ? [{ debugName: "formSetupConfig" }] : /* istanbul ignore next */ []));
2537
+ activePageState = linkedSignal({ ...(ngDevMode ? { debugName: "activePageState" } : /* istanbul ignore next */ {}), source: this.activeConfig,
2538
+ computation: (config) => ({ config, initialized: false, index: 0 }) });
3277
2539
  defaultValues = linkedSignal(() => this.formSetup().defaultValues, /* @ts-ignore */
3278
2540
  ...(ngDevMode ? [{ debugName: "defaultValues" }] : /* istanbul ignore next */ []));
3279
2541
  validKeys = computed(() => {
3280
2542
  const schemaFields = this.formSetup().schemaFields;
3281
2543
  if (!schemaFields || schemaFields.length === 0)
3282
2544
  return undefined;
3283
- return new Set(schemaFields.map((f) => f.key).filter((key) => key !== undefined));
2545
+ const keys = new Set();
2546
+ for (const field of schemaFields) {
2547
+ if (field.key === undefined)
2548
+ continue;
2549
+ keys.add(field.key);
2550
+ }
2551
+ return keys;
3284
2552
  }, /* @ts-ignore */
3285
2553
  ...(ngDevMode ? [{ debugName: "validKeys" }] : /* istanbul ignore next */ []));
3286
- fieldStateSnapshot = computed(() => {
2554
+ boundValueExclusionEnabled = computed(() => {
2555
+ const setup = this.formSetup();
2556
+ if (!setup.schemaFields || setup.schemaFields.length === 0)
2557
+ return false;
2558
+ const options = this.effectiveFormOptions();
2559
+ return hasEnabledValueExclusion(setup.schemaFields, BOUND_VALUE_EXCLUSION_BASELINE, {
2560
+ excludeValueIfHidden: options.excludeValueIfHidden,
2561
+ excludeValueIfDisabled: options.excludeValueIfDisabled,
2562
+ excludeValueIfReadonly: options.excludeValueIfReadonly,
2563
+ });
2564
+ }, /* @ts-ignore */
2565
+ ...(ngDevMode ? [{ debugName: "boundValueExclusionEnabled" }] : /* istanbul ignore next */ []));
2566
+ valueExclusionEnabled = computed(() => {
3287
2567
  const setup = this.formSetup();
3288
2568
  if (!setup.schemaFields || setup.schemaFields.length === 0)
2569
+ return false;
2570
+ const options = this.effectiveFormOptions();
2571
+ return hasEnabledValueExclusion(setup.schemaFields, this.valueExclusionDefaults, {
2572
+ excludeValueIfHidden: options.excludeValueIfHidden,
2573
+ excludeValueIfDisabled: options.excludeValueIfDisabled,
2574
+ excludeValueIfReadonly: options.excludeValueIfReadonly,
2575
+ });
2576
+ }, /* @ts-ignore */
2577
+ ...(ngDevMode ? [{ debugName: "valueExclusionEnabled" }] : /* istanbul ignore next */ []));
2578
+ fieldStateSnapshot = computed(() => {
2579
+ const schemaFields = this.formSetup().schemaFields;
2580
+ if (!this.formSchemaReady() || !this.boundValueExclusionEnabled() || !schemaFields || schemaFields.length === 0)
3289
2581
  return {};
3290
2582
  const snapshot = {};
3291
- collectFieldStateSnapshot(setup.schemaFields, asFieldTreeRecord(this.form()), [], snapshot);
2583
+ collectFieldStateSnapshot(schemaFields, asFieldTreeRecord(this.form()), [], snapshot);
3292
2584
  return snapshot;
3293
2585
  }, /* @ts-ignore */
3294
2586
  ...(ngDevMode ? [{ debugName: "fieldStateSnapshot" }] : /* istanbul ignore next */ []));
@@ -3296,8 +2588,22 @@ class FormStateManager {
3296
2588
  ...(ngDevMode ? [{ debugName: "excludedValueStore" }] : /* istanbul ignore next */ []));
3297
2589
  prevFieldStateSnapshot = signal({}, /* @ts-ignore */
3298
2590
  ...(ngDevMode ? [{ debugName: "prevFieldStateSnapshot" }] : /* istanbul ignore next */ []));
2591
+ lastOutwardValue = { current: undefined, pending: false };
2592
+ lastExternalValue = { current: undefined };
2593
+ externalValue = computed(() => {
2594
+ const incoming = this.deps.value();
2595
+ if (this.lastOutwardValue.pending && incoming === this.lastOutwardValue.current) {
2596
+ this.lastOutwardValue.pending = false;
2597
+ return this.lastExternalValue.current;
2598
+ }
2599
+ this.lastOutwardValue.pending = false;
2600
+ this.lastExternalValue.current = incoming;
2601
+ return incoming;
2602
+ }, /* @ts-ignore */
2603
+ ...(ngDevMode ? [{ debugName: "externalValue" }] : /* istanbul ignore next */ []));
3299
2604
  entity = linkedSignal(() => {
3300
- const inputValue = this.deps.value();
2605
+ this.externalValue();
2606
+ const inputValue = untracked(() => this.deps.value());
3301
2607
  const defaults = this.defaultValues();
3302
2608
  const keys = this.validKeys();
3303
2609
  const saved = this.excludedValueStore();
@@ -3317,22 +2623,62 @@ class FormStateManager {
3317
2623
  debugName: 'FormStateManager.entity',
3318
2624
  equal: isEqual,
3319
2625
  });
3320
- formSchema = computed(() => runInInjectionContext(this.injector, () => {
2626
+ loadFormSchemaService = injectAsync(() => import('./ng-forge-dynamic-forms-form-schema.service-C3cVSmft.mjs'));
2627
+ formSchemaRequest = computed(() => {
3321
2628
  const setup = this.formSetup();
3322
2629
  const config = this.activeConfig();
3323
- if (!config)
2630
+ if (!config || (!(setup.schemaFields?.length > 0) && !config.schema))
2631
+ return undefined;
2632
+ return {
2633
+ fields: setup.schemaFields ?? [],
2634
+ registry: setup.registry,
2635
+ formLevelSchema: config.schema,
2636
+ validateWhenHidden: this.effectiveFormOptions().validateWhenHidden,
2637
+ };
2638
+ }, /* @ts-ignore */
2639
+ ...(ngDevMode ? [{ debugName: "formSchemaRequest" }] : /* istanbul ignore next */ []));
2640
+ formSchemaResource = resource({ ...(ngDevMode ? { debugName: "formSchemaResource" } : /* istanbul ignore next */ {}), params: () => this.formSchemaRequest(),
2641
+ loader: async ({ params }) => {
2642
+ const schemaService = await this.loadFormSchemaService();
2643
+ return runInInjectionContext(this.injector, () => schemaService.create(params));
2644
+ } });
2645
+ schemaCache = { current: undefined };
2646
+ trackSchemaCache = computed(() => {
2647
+ const request = this.formSchemaRequest();
2648
+ if (!request) {
2649
+ this.schemaCache.current = undefined;
3324
2650
  return undefined;
3325
- if (setup.schemaFields?.length) {
3326
- return createSchemaFromFields(setup.schemaFields, setup.registry, {
3327
- formLevelSchema: config.schema,
3328
- validateWhenHidden: this.effectiveFormOptions().validateWhenHidden,
3329
- });
3330
2651
  }
3331
- if (config.schema) {
3332
- return createFormLevelSchema(config.schema);
2652
+ if (this.formSchemaResource.status() === 'error') {
2653
+ this.schemaCache.current = undefined;
2654
+ return undefined;
3333
2655
  }
3334
- return undefined;
3335
- }), /* @ts-ignore */
2656
+ if (this.formSchemaResource.hasValue()) {
2657
+ this.schemaCache.current = { schema: this.formSchemaResource.value(), request };
2658
+ }
2659
+ return this.schemaCache.current;
2660
+ }, /* @ts-ignore */
2661
+ ...(ngDevMode ? [{ debugName: "trackSchemaCache" }] : /* istanbul ignore next */ []));
2662
+ isSchemaCurrent = computed(() => {
2663
+ if (this.formSetupConfig() !== this.activeConfig())
2664
+ return false;
2665
+ const request = this.formSchemaRequest();
2666
+ if (!request)
2667
+ return true;
2668
+ return this.trackSchemaCache()?.request === request;
2669
+ }, /* @ts-ignore */
2670
+ ...(ngDevMode ? [{ debugName: "isSchemaCurrent" }] : /* istanbul ignore next */ []));
2671
+ formSchemaReady = computed(() => {
2672
+ if (this.formSetupConfig() !== this.activeConfig())
2673
+ return false;
2674
+ if (!this.formSchemaRequest())
2675
+ return true;
2676
+ if (this.formSchemaResource.status() === 'error')
2677
+ return true;
2678
+ return this.trackSchemaCache() !== undefined;
2679
+ }, /* @ts-ignore */
2680
+ ...(ngDevMode ? [{ debugName: "formSchemaReady" }] : /* istanbul ignore next */ []));
2681
+ formSchema = computed(() => this.trackSchemaCache()?.schema, /* @ts-ignore */
3336
2682
  ...(ngDevMode ? [{ debugName: "formSchema" }] : /* istanbul ignore next */ []));
3337
2683
  form = computed(() => {
3338
2684
  const schema = this.formSchema();
@@ -3363,13 +2709,16 @@ class FormStateManager {
3363
2709
  ...(ngDevMode ? [{ debugName: "fieldSignalContext" }] : /* istanbul ignore next */ []));
3364
2710
  formInstance = computed(() => this.form()(), /* @ts-ignore */
3365
2711
  ...(ngDevMode ? [{ debugName: "formInstance" }] : /* istanbul ignore next */ []));
3366
- formValue = computed(() => this.formInstance().value(), /* @ts-ignore */
2712
+ modelValue = computed(() => this.formInstance().value(), /* @ts-ignore */
2713
+ ...(ngDevMode ? [{ debugName: "modelValue" }] : /* istanbul ignore next */ []));
2714
+ formValue = computed(() => this.modelValue(), /* @ts-ignore */
3367
2715
  ...(ngDevMode ? [{ debugName: "formValue" }] : /* istanbul ignore next */ []));
3368
2716
  filteredFormValue = computed(() => {
3369
- const rawValue = this.formValue();
2717
+ const rawValue = this.modelValue();
3370
2718
  const setup = this.formSetup();
2719
+ const schemaFields = setup.schemaFields;
3371
2720
  const options = this.effectiveFormOptions();
3372
- if (!setup.schemaFields || setup.schemaFields.length === 0) {
2721
+ if (!this.formSchemaReady() || !schemaFields || schemaFields.length === 0) {
3373
2722
  return rawValue;
3374
2723
  }
3375
2724
  const formTree = this.form();
@@ -3379,14 +2728,15 @@ class FormStateManager {
3379
2728
  excludeValueIfReadonly: options.excludeValueIfReadonly,
3380
2729
  };
3381
2730
  const fieldTreeRecord = asFieldTreeRecord(formTree);
3382
- return filterFormValue(rawValue, setup.schemaFields, fieldTreeRecord, setup.registry, this.valueExclusionDefaults, formOptions);
2731
+ return filterFormValue(rawValue, schemaFields, fieldTreeRecord, setup.registry, this.valueExclusionDefaults, formOptions, this.valueExclusionEnabled());
3383
2732
  }, /* @ts-ignore */
3384
2733
  ...(ngDevMode ? [{ debugName: "filteredFormValue" }] : /* istanbul ignore next */ []));
3385
2734
  boundFormValue = computed(() => {
3386
- const rawValue = this.formValue();
2735
+ const rawValue = this.modelValue();
3387
2736
  const setup = this.formSetup();
2737
+ const schemaFields = setup.schemaFields;
3388
2738
  const options = this.effectiveFormOptions();
3389
- if (!setup.schemaFields || setup.schemaFields.length === 0) {
2739
+ if (!this.formSchemaReady() || !schemaFields || schemaFields.length === 0) {
3390
2740
  return rawValue;
3391
2741
  }
3392
2742
  const formTree = this.form();
@@ -3396,26 +2746,26 @@ class FormStateManager {
3396
2746
  excludeValueIfReadonly: options.excludeValueIfReadonly,
3397
2747
  };
3398
2748
  const fieldTreeRecord = asFieldTreeRecord(formTree);
3399
- return filterFormValue(rawValue, setup.schemaFields, fieldTreeRecord, setup.registry, BOUND_VALUE_EXCLUSION_BASELINE, formOptions);
2749
+ return filterFormValue(rawValue, schemaFields, fieldTreeRecord, setup.registry, BOUND_VALUE_EXCLUSION_BASELINE, formOptions, this.boundValueExclusionEnabled());
3400
2750
  }, /* @ts-ignore */
3401
2751
  ...(ngDevMode ? [{ debugName: "boundFormValue" }] : /* istanbul ignore next */ []));
3402
- valid = computed(() => this.formInstance().valid(), /* @ts-ignore */
2752
+ valid = computed(() => this.formSchemaReady() && this.formSchemaResource.status() !== 'error' && this.formInstance().valid(), /* @ts-ignore */
3403
2753
  ...(ngDevMode ? [{ debugName: "valid" }] : /* istanbul ignore next */ []));
3404
- invalid = computed(() => this.formInstance().invalid(), /* @ts-ignore */
2754
+ invalid = computed(() => !this.formSchemaReady() || this.formSchemaResource.status() === 'error' || this.formInstance().invalid(), /* @ts-ignore */
3405
2755
  ...(ngDevMode ? [{ debugName: "invalid" }] : /* istanbul ignore next */ []));
3406
- dirty = computed(() => this.formInstance().dirty(), /* @ts-ignore */
2756
+ dirty = computed(() => this.formSchemaReady() && this.formInstance().dirty(), /* @ts-ignore */
3407
2757
  ...(ngDevMode ? [{ debugName: "dirty" }] : /* istanbul ignore next */ []));
3408
- touched = computed(() => this.formInstance().touched(), /* @ts-ignore */
2758
+ touched = computed(() => this.formSchemaReady() && this.formInstance().touched(), /* @ts-ignore */
3409
2759
  ...(ngDevMode ? [{ debugName: "touched" }] : /* istanbul ignore next */ []));
3410
- errors = computed(() => this.formInstance().errors(), /* @ts-ignore */
2760
+ errors = computed(() => (this.formSchemaReady() ? this.formInstance().errors() : []), /* @ts-ignore */
3411
2761
  ...(ngDevMode ? [{ debugName: "errors" }] : /* istanbul ignore next */ []));
3412
2762
  disabled = computed(() => {
3413
2763
  const optionsDisabled = this.effectiveFormOptions().disabled;
3414
- const formDisabled = this.formInstance().disabled();
2764
+ const formDisabled = this.formSchemaReady() && this.formInstance().disabled();
3415
2765
  return optionsDisabled ?? formDisabled;
3416
2766
  }, /* @ts-ignore */
3417
2767
  ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
3418
- submitting = computed(() => this.formInstance().submitting(), /* @ts-ignore */
2768
+ submitting = computed(() => this.formSchemaReady() && this.formInstance().submitting(), /* @ts-ignore */
3419
2769
  ...(ngDevMode ? [{ debugName: "submitting" }] : /* istanbul ignore next */ []));
3420
2770
  fieldsSource = computed(() => {
3421
2771
  const state = this.machine.state();
@@ -3530,6 +2880,10 @@ class FormStateManager {
3530
2880
  }), takeUntilDestroyed(this.destroyRef));
3531
2881
  this.reset$ = this.eventBus.on('form-reset').pipe(map(() => undefined), takeUntilDestroyed(this.destroyRef));
3532
2882
  this.cleared$ = this.eventBus.on('form-clear').pipe(map(() => undefined), takeUntilDestroyed(this.destroyRef));
2883
+ this.eventBus
2884
+ .on('go-to-page')
2885
+ .pipe(takeUntilDestroyed(this.destroyRef))
2886
+ .subscribe((event) => this.pendingPageRequest.set({ index: event.pageIndex, options: event.options }));
3533
2887
  this.machine = createFormStateMachine({
3534
2888
  injector: this.injector,
3535
2889
  destroyRef: this.destroyRef,
@@ -3593,6 +2947,11 @@ class FormStateManager {
3593
2947
  this.eventBus.dispatch(FormSubmitEvent);
3594
2948
  }
3595
2949
  setupEffects() {
2950
+ explicitEffect([this.formSchemaResource.error], ([error]) => {
2951
+ if (error) {
2952
+ this.logger.error('[Dynamic Forms] Failed to compile the form schema; rendering without validation and blocking submission.', error);
2953
+ }
2954
+ });
3596
2955
  explicitEffect([this.deps.config], ([config]) => {
3597
2956
  this.fieldLoadingErrors.set([]);
3598
2957
  const state = this.machine.currentState;
@@ -3644,17 +3003,24 @@ class FormStateManager {
3644
3003
  explicitEffect([this.boundFormValue], ([currentBound]) => {
3645
3004
  const currentValue = this.deps.value();
3646
3005
  if (!isEqual(currentBound, currentValue)) {
3006
+ this.lastOutwardValue.current = currentBound;
3007
+ this.lastOutwardValue.pending = true;
3647
3008
  this.deps.value.set(currentBound);
3648
3009
  }
3649
3010
  });
3650
- explicitEffect([this.formConfigValidation], ([validation]) => {
3651
- if (!validation.isValid) {
3652
- this.logger.error('Invalid form configuration:', validation.errors);
3653
- }
3654
- if (validation.warnings.length > 0) {
3655
- this.logger.warn('Form configuration warnings:', validation.warnings);
3656
- }
3657
- });
3011
+ if (DEV_MODE) {
3012
+ explicitEffect([this.activeConfig], ([config]) => {
3013
+ if (!config)
3014
+ return;
3015
+ const validation = FormModeValidator.validateFormConfiguration(config.fields || []);
3016
+ if (!validation.isValid) {
3017
+ this.logger.error('Invalid form configuration:', validation.errors);
3018
+ }
3019
+ if (validation.warnings.length > 0) {
3020
+ this.logger.warn('Form configuration warnings:', validation.warnings);
3021
+ }
3022
+ });
3023
+ }
3658
3024
  }
3659
3025
  setupEventHandlers() {
3660
3026
  this.eventBus
@@ -3672,74 +3038,479 @@ class FormStateManager {
3672
3038
  this.schemaRegistry.registerSchema(schema);
3673
3039
  });
3674
3040
  }
3675
- if (!customFnConfig) {
3676
- return;
3041
+ if (!customFnConfig) {
3042
+ return;
3043
+ }
3044
+ if (customFnConfig.customFunctions) {
3045
+ Object.entries(customFnConfig.customFunctions).forEach(([name, fn]) => {
3046
+ this.functionRegistry.registerCustomFunction(name, fn);
3047
+ });
3048
+ }
3049
+ if (customFnConfig.derivations) {
3050
+ this.functionRegistry.setDerivationFunctions(customFnConfig.derivations);
3051
+ }
3052
+ if (customFnConfig.asyncDerivations) {
3053
+ this.functionRegistry.setAsyncDerivationFunctions(customFnConfig.asyncDerivations);
3054
+ }
3055
+ if (customFnConfig.asyncConditions) {
3056
+ this.functionRegistry.setAsyncConditionFunctions(customFnConfig.asyncConditions);
3057
+ }
3058
+ this.functionRegistry.setValidators(customFnConfig.validators);
3059
+ this.functionRegistry.setAsyncValidators(customFnConfig.asyncValidators);
3060
+ this.functionRegistry.setHttpValidators(customFnConfig.httpValidators);
3061
+ }
3062
+ createFormSetupFromConfig(fields, mode, registry) {
3063
+ const normalizedFields = normalizeSimplifiedArrays(fields);
3064
+ validateFormConfig(normalizedFields, registry, this.logger);
3065
+ const source = this.deps.source?.() ?? 'inline';
3066
+ const { fields: sanitizedFields, warnings: addonWarnings } = walkAndValidateAddons(normalizedFields, registry, this.addonKindRegistry, source);
3067
+ if (DEV_MODE) {
3068
+ const nextKeys = new Set();
3069
+ for (const w of addonWarnings) {
3070
+ const key = addonWarningKey(w);
3071
+ nextKeys.add(key);
3072
+ if (!this.lastAddonWarningKeys.has(key)) {
3073
+ this.logger.warn(formatAddonWarning(w));
3074
+ }
3075
+ }
3076
+ this.lastAddonWarningKeys = nextKeys;
3077
+ }
3078
+ const validatedFields = sanitizedFields;
3079
+ const flattenedFields = this.fieldProcessors.memoizedFlattenFields(validatedFields, registry);
3080
+ const flattenedFieldsForRendering = this.fieldProcessors.memoizedFlattenFieldsForRendering(validatedFields, registry);
3081
+ const fieldsById = this.fieldProcessors.memoizedKeyBy(flattenedFields);
3082
+ const defaultValues = this.fieldProcessors.memoizedDefaultValues(fieldsById, registry);
3083
+ const fieldsToRender = mode === 'paged' ? [] : flattenedFieldsForRendering;
3084
+ return {
3085
+ fields: fieldsToRender,
3086
+ schemaFields: flattenedFields,
3087
+ originalFields: fields,
3088
+ defaultValues,
3089
+ mode,
3090
+ registry,
3091
+ };
3092
+ }
3093
+ createEmptyFormSetup(registry) {
3094
+ return {
3095
+ fields: [],
3096
+ schemaFields: [],
3097
+ defaultValues: {},
3098
+ mode: 'non-paged',
3099
+ registry,
3100
+ };
3101
+ }
3102
+ ngOnDestroy() {
3103
+ this.machine.dispatch({ type: Action.Destroy });
3104
+ }
3105
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: FormStateManager, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3106
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: FormStateManager });
3107
+ }
3108
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: FormStateManager, decorators: [{
3109
+ type: Injectable
3110
+ }], ctorParameters: () => [] });
3111
+
3112
+ class PageOrchestratorComponent {
3113
+ eventBus = inject(EventBus);
3114
+ fieldContextRegistry = inject(FieldContextRegistryService);
3115
+ functionRegistry = inject(FunctionRegistryService);
3116
+ formOptions = inject(FORM_OPTIONS, { optional: true });
3117
+ globalPreloadWindow = inject(PAGE_PRELOAD_WINDOW);
3118
+ stateManager = inject(FormStateManager, { optional: true });
3119
+ pendingPageRequest = computed(() => this.stateManager?.pendingPageRequest() ?? null, /* @ts-ignore */
3120
+ ...(ngDevMode ? [{ debugName: "pendingPageRequest" }] : /* istanbul ignore next */ []));
3121
+ schemaCurrent = computed(() => this.stateManager?.isSchemaCurrent() ?? true, /* @ts-ignore */
3122
+ ...(ngDevMode ? [{ debugName: "schemaCurrent" }] : /* istanbul ignore next */ []));
3123
+ configScope = computed(() => this.stateManager?.activeConfig(), /* @ts-ignore */
3124
+ ...(ngDevMode ? [{ debugName: "configScope" }] : /* istanbul ignore next */ []));
3125
+ pageFields = input.required(/* @ts-ignore */
3126
+ ...(ngDevMode ? [{ debugName: "pageFields" }] : /* istanbul ignore next */ []));
3127
+ form = input.required(/* @ts-ignore */
3128
+ ...(ngDevMode ? [{ debugName: "form" }] : /* istanbul ignore next */ []));
3129
+ fieldSignalContext = input.required(/* @ts-ignore */
3130
+ ...(ngDevMode ? [{ debugName: "fieldSignalContext" }] : /* istanbul ignore next */ []));
3131
+ pageHiddenStates = computed(() => {
3132
+ const pages = this.pageFields();
3133
+ return pages.map((page) => this.evaluatePageHidden(page));
3134
+ }, /* @ts-ignore */
3135
+ ...(ngDevMode ? [{ debugName: "pageHiddenStates" }] : /* istanbul ignore next */ []));
3136
+ visiblePageIndices = computed(() => {
3137
+ const hiddenStates = this.pageHiddenStates();
3138
+ return hiddenStates
3139
+ .map((hidden, index) => ({ index, hidden }))
3140
+ .filter((item) => !item.hidden)
3141
+ .map((item) => item.index);
3142
+ }, /* @ts-ignore */
3143
+ ...(ngDevMode ? [{ debugName: "visiblePageIndices" }] : /* istanbul ignore next */ []));
3144
+ activePageOwnership = linkedSignal({ ...(ngDevMode ? { debugName: "activePageOwnership" } : /* istanbul ignore next */ {}), source: this.configScope,
3145
+ computation: (config, previous) => {
3146
+ const totalPages = this.pageFields().length;
3147
+ if (totalPages === 0)
3148
+ return { config, initialized: true, index: 0 };
3149
+ return untracked(() => {
3150
+ const owned = this.stateManager?.activePageState();
3151
+ const mayRestore = previous === undefined || previous.source === config;
3152
+ if (mayRestore &&
3153
+ owned !== undefined &&
3154
+ owned.config === config &&
3155
+ owned.initialized &&
3156
+ owned.index >= 0 &&
3157
+ owned.index < totalPages) {
3158
+ return { config, initialized: true, index: owned.index };
3159
+ }
3160
+ if (this.initialPage().validate && !this.schemaCurrent()) {
3161
+ return { config, initialized: false, index: 0 };
3162
+ }
3163
+ return { config, initialized: true, index: this.resolveInitialLanding() };
3164
+ });
3165
+ } });
3166
+ currentPageIndex = computed(() => this.activePageOwnership().index, /* @ts-ignore */
3167
+ ...(ngDevMode ? [{ debugName: "currentPageIndex" }] : /* istanbul ignore next */ []));
3168
+ setCurrentPageIndex(index) {
3169
+ this.activePageOwnership.update((ownership) => ({ ...ownership, initialized: true, index }));
3170
+ }
3171
+ resolveInitialLanding() {
3172
+ const { index, validate } = this.initialPage();
3173
+ const visible = this.visiblePageIndices();
3174
+ if (index === 0 || visible.length === 0)
3175
+ return 0;
3176
+ const target = visible.includes(index) ? index : this.findNearestVisiblePage(index, visible);
3177
+ if (target <= 0)
3178
+ return Math.max(target, 0);
3179
+ if (!validate)
3180
+ return target;
3181
+ const firstInvalid = visible.filter((i) => i < target).find((i) => !this.isPageValid(i));
3182
+ return firstInvalid ?? target;
3183
+ }
3184
+ initialPage = computed(() => {
3185
+ const raw = this.formOptions?.()?.initialPage;
3186
+ const totalPages = this.pageFields().length;
3187
+ const requested = typeof raw === 'number' ? raw : raw?.index;
3188
+ const validate = typeof raw === 'number' ? false : (raw?.validate ?? false);
3189
+ if (typeof requested !== 'number' || !Number.isFinite(requested) || requested < 0 || totalPages === 0) {
3190
+ return { index: 0, validate: false };
3191
+ }
3192
+ return { index: Math.min(Math.trunc(requested), totalPages - 1), validate };
3193
+ }, /* @ts-ignore */
3194
+ ...(ngDevMode ? [{ debugName: "initialPage" }] : /* istanbul ignore next */ []));
3195
+ state = computed(() => {
3196
+ const currentIndex = this.currentPageIndex();
3197
+ const totalPages = this.pageFields().length;
3198
+ const visibleIndices = this.visiblePageIndices();
3199
+ const currentVisiblePosition = visibleIndices.indexOf(currentIndex);
3200
+ const isFirstVisiblePage = currentVisiblePosition === 0 || currentVisiblePosition === -1;
3201
+ const isLastVisiblePage = currentVisiblePosition >= visibleIndices.length - 1;
3202
+ return {
3203
+ currentPageIndex: currentIndex,
3204
+ totalPages,
3205
+ isFirstPage: isFirstVisiblePage,
3206
+ isLastPage: isLastVisiblePage,
3207
+ };
3208
+ }, /* @ts-ignore */
3209
+ ...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
3210
+ currentPageValid = computed(() => (this.stateManager?.isSchemaCurrent() ?? true) && this.isPageValid(this.currentPageIndex()), /* @ts-ignore */
3211
+ ...(ngDevMode ? [{ debugName: "currentPageValid" }] : /* istanbul ignore next */ []));
3212
+ isPageValid(pageIndex) {
3213
+ const pages = this.pageFields();
3214
+ const form = this.form();
3215
+ if (pageIndex < 0 || pageIndex >= pages.length) {
3216
+ return true;
3217
+ }
3218
+ const leafKeys = collectLeafFieldKeys(pages[pageIndex].fields || []);
3219
+ for (const fieldKey of leafKeys) {
3220
+ const field = form[fieldKey];
3221
+ if (field && typeof field === 'function') {
3222
+ const fieldState = field();
3223
+ if (fieldState && typeof fieldState.valid === 'function' && !fieldState.valid()) {
3224
+ return false;
3225
+ }
3226
+ }
3227
+ }
3228
+ return true;
3229
+ }
3230
+ extendedFieldSignalContext = computed(() => ({
3231
+ ...this.fieldSignalContext(),
3232
+ currentPageValid: this.currentPageValid,
3233
+ }), /* @ts-ignore */
3234
+ ...(ngDevMode ? [{ debugName: "extendedFieldSignalContext" }] : /* istanbul ignore next */ []));
3235
+ preloadWindow = computed(() => {
3236
+ const perForm = this.formOptions?.()?.pagePreloadWindow;
3237
+ return clampWindowSize(perForm, this.globalPreloadWindow);
3238
+ }, /* @ts-ignore */
3239
+ ...(ngDevMode ? [{ debugName: "preloadWindow" }] : /* istanbul ignore next */ []));
3240
+ pageRenderStates = computed(() => {
3241
+ const activeIndex = this.currentPageIndex();
3242
+ const hiddenStates = this.pageHiddenStates();
3243
+ const preloadWindow = this.preloadWindow();
3244
+ return this.pageFields().map((field, index) => ({
3245
+ field,
3246
+ index,
3247
+ hidden: hiddenStates[index],
3248
+ active: index === activeIndex,
3249
+ preload: index !== activeIndex && Math.abs(index - activeIndex) <= preloadWindow,
3250
+ }));
3251
+ }, /* @ts-ignore */
3252
+ ...(ngDevMode ? [{ debugName: "pageRenderStates" }] : /* istanbul ignore next */ []));
3253
+ constructor() {
3254
+ this.setupEventListeners();
3255
+ explicitEffect([this.schemaCurrent, this.pageFields], ([schemaCurrent]) => {
3256
+ const ownership = this.activePageOwnership();
3257
+ if (!schemaCurrent || ownership.initialized)
3258
+ return;
3259
+ this.activePageOwnership.set({ ...ownership, initialized: true, index: this.resolveInitialLanding() });
3260
+ });
3261
+ explicitEffect([this.state, this.visiblePageIndices], ([state, visibleIndices]) => {
3262
+ const currentVisiblePosition = visibleIndices.indexOf(state.currentPageIndex);
3263
+ if (currentVisiblePosition === -1 && visibleIndices.length > 0) {
3264
+ const nearest = this.findNearestVisiblePage(state.currentPageIndex, visibleIndices);
3265
+ if (nearest !== -1) {
3266
+ this.executePageChange(nearest);
3267
+ }
3268
+ }
3269
+ });
3270
+ }
3271
+ navigateToNextPage() {
3272
+ const disableWhenPageInvalid = this.formOptions?.()?.nextButton?.disableWhenPageInvalid ?? true;
3273
+ if (disableWhenPageInvalid && !this.currentPageValid()) {
3274
+ return {
3275
+ success: false,
3276
+ newPageIndex: this.state().currentPageIndex,
3277
+ error: 'Current page has invalid fields',
3278
+ };
3279
+ }
3280
+ const currentState = this.state();
3281
+ const visibleIndices = this.visiblePageIndices();
3282
+ if (currentState.isLastPage) {
3283
+ return {
3284
+ success: false,
3285
+ newPageIndex: currentState.currentPageIndex,
3286
+ error: 'Already on the last visible page',
3287
+ };
3288
+ }
3289
+ const currentVisiblePosition = visibleIndices.indexOf(currentState.currentPageIndex);
3290
+ if (currentVisiblePosition === -1 || currentVisiblePosition >= visibleIndices.length - 1) {
3291
+ return {
3292
+ success: false,
3293
+ newPageIndex: currentState.currentPageIndex,
3294
+ error: 'No next visible page available',
3295
+ };
3296
+ }
3297
+ const nextVisiblePageIndex = visibleIndices[currentVisiblePosition + 1];
3298
+ return this.executePageChange(nextVisiblePageIndex);
3299
+ }
3300
+ navigateToPreviousPage() {
3301
+ const currentState = this.state();
3302
+ const visibleIndices = this.visiblePageIndices();
3303
+ if (currentState.isFirstPage) {
3304
+ return {
3305
+ success: false,
3306
+ newPageIndex: currentState.currentPageIndex,
3307
+ error: 'Already on the first visible page',
3308
+ };
3677
3309
  }
3678
- if (customFnConfig.customFunctions) {
3679
- Object.entries(customFnConfig.customFunctions).forEach(([name, fn]) => {
3680
- this.functionRegistry.registerCustomFunction(name, fn);
3681
- });
3310
+ const currentVisiblePosition = visibleIndices.indexOf(currentState.currentPageIndex);
3311
+ if (currentVisiblePosition <= 0) {
3312
+ return {
3313
+ success: false,
3314
+ newPageIndex: currentState.currentPageIndex,
3315
+ error: 'No previous visible page available',
3316
+ };
3682
3317
  }
3683
- if (customFnConfig.derivations) {
3684
- this.functionRegistry.setDerivationFunctions(customFnConfig.derivations);
3318
+ const prevVisiblePageIndex = visibleIndices[currentVisiblePosition - 1];
3319
+ return this.executePageChange(prevVisiblePageIndex);
3320
+ }
3321
+ navigateToPage(pageIndex, options) {
3322
+ const currentIndex = this.state().currentPageIndex;
3323
+ const visibleIndices = this.visiblePageIndices();
3324
+ if (pageIndex <= currentIndex || !visibleIndices.includes(pageIndex)) {
3325
+ return this.executePageChange(pageIndex);
3685
3326
  }
3686
- if (customFnConfig.asyncDerivations) {
3687
- this.functionRegistry.setAsyncDerivationFunctions(customFnConfig.asyncDerivations);
3327
+ if (options?.validate === false) {
3328
+ return this.executePageChange(pageIndex);
3688
3329
  }
3689
- if (customFnConfig.asyncConditions) {
3690
- this.functionRegistry.setAsyncConditionFunctions(customFnConfig.asyncConditions);
3330
+ const disableWhenPageInvalid = this.formOptions?.()?.nextButton?.disableWhenPageInvalid ?? true;
3331
+ if (!disableWhenPageInvalid) {
3332
+ return this.executePageChange(pageIndex);
3691
3333
  }
3692
- this.functionRegistry.setValidators(customFnConfig.validators);
3693
- this.functionRegistry.setAsyncValidators(customFnConfig.asyncValidators);
3694
- this.functionRegistry.setHttpValidators(customFnConfig.httpValidators);
3695
- }
3696
- createFormSetupFromConfig(fields, mode, registry) {
3697
- const normalizedFields = normalizeSimplifiedArrays(fields);
3698
- validateFormConfig(normalizedFields, registry, this.logger);
3699
- const source = this.deps.source?.() ?? 'inline';
3700
- const { fields: sanitizedFields, warnings: addonWarnings } = walkAndValidateAddons(normalizedFields, registry, this.addonKindRegistry, source);
3701
- const nextKeys = new Set();
3702
- for (const w of addonWarnings) {
3703
- const key = addonWarningKey(w);
3704
- nextKeys.add(key);
3705
- if (!this.lastAddonWarningKeys.has(key)) {
3706
- this.logger.warn(formatAddonWarning(w));
3707
- }
3334
+ const crossedPages = visibleIndices.filter((index) => index >= currentIndex && index < pageIndex);
3335
+ const firstInvalidPage = crossedPages.find((index) => !this.isPageValid(index));
3336
+ if (firstInvalidPage === undefined) {
3337
+ return this.executePageChange(pageIndex);
3708
3338
  }
3709
- this.lastAddonWarningKeys = nextKeys;
3710
- const validatedFields = sanitizedFields;
3711
- const flattenedFields = this.fieldProcessors.memoizedFlattenFields(validatedFields, registry);
3712
- const flattenedFieldsForRendering = this.fieldProcessors.memoizedFlattenFieldsForRendering(validatedFields, registry);
3713
- const fieldsById = this.fieldProcessors.memoizedKeyBy(flattenedFields);
3714
- const defaultValues = this.fieldProcessors.memoizedDefaultValues(fieldsById, registry);
3715
- const fieldsToRender = mode === 'paged' ? [] : flattenedFieldsForRendering;
3339
+ this.executePageChange(firstInvalidPage);
3716
3340
  return {
3717
- fields: fieldsToRender,
3718
- schemaFields: flattenedFields,
3719
- originalFields: fields,
3720
- defaultValues,
3721
- mode,
3722
- registry,
3341
+ success: false,
3342
+ newPageIndex: firstInvalidPage,
3343
+ error: `Cannot navigate to page ${pageIndex}: page ${firstInvalidPage} has invalid fields`,
3723
3344
  };
3724
3345
  }
3725
- createEmptyFormSetup(registry) {
3346
+ executePageChange(pageIndex) {
3347
+ const currentState = this.state();
3348
+ const totalPages = currentState.totalPages;
3349
+ if (pageIndex < 0 || pageIndex >= totalPages) {
3350
+ return {
3351
+ success: false,
3352
+ newPageIndex: currentState.currentPageIndex,
3353
+ error: `Invalid page index: ${pageIndex}. Valid range is 0 to ${totalPages - 1}`,
3354
+ };
3355
+ }
3356
+ const visibleIndices = this.visiblePageIndices();
3357
+ if (!visibleIndices.includes(pageIndex)) {
3358
+ return {
3359
+ success: false,
3360
+ newPageIndex: currentState.currentPageIndex,
3361
+ error: `Cannot navigate to hidden page at index ${pageIndex}. Visible pages: [${visibleIndices.join(', ')}]`,
3362
+ };
3363
+ }
3364
+ if (pageIndex === currentState.currentPageIndex) {
3365
+ return {
3366
+ success: true,
3367
+ newPageIndex: pageIndex,
3368
+ };
3369
+ }
3370
+ const previousIndex = currentState.currentPageIndex;
3371
+ this.setCurrentPageIndex(pageIndex);
3372
+ this.eventBus.dispatch(PageChangeEvent, pageIndex, totalPages, previousIndex);
3726
3373
  return {
3727
- fields: [],
3728
- schemaFields: [],
3729
- defaultValues: {},
3730
- mode: 'non-paged',
3731
- registry,
3374
+ success: true,
3375
+ newPageIndex: pageIndex,
3732
3376
  };
3733
3377
  }
3734
- ngOnDestroy() {
3735
- this.machine.dispatch({ type: Action.Destroy });
3378
+ findNearestVisiblePage(currentIndex, visibleIndices) {
3379
+ let nearest = -1;
3380
+ let minDistance = Infinity;
3381
+ for (const idx of visibleIndices) {
3382
+ const distance = Math.abs(idx - currentIndex);
3383
+ if (distance < minDistance || (distance === minDistance && idx > nearest)) {
3384
+ minDistance = distance;
3385
+ nearest = idx;
3386
+ }
3387
+ }
3388
+ return nearest;
3736
3389
  }
3737
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: FormStateManager, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3738
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: FormStateManager });
3390
+ setupEventListeners() {
3391
+ this.eventBus
3392
+ .on('next-page')
3393
+ .pipe(takeUntilDestroyed())
3394
+ .subscribe(() => {
3395
+ this.navigateToNextPage();
3396
+ });
3397
+ this.eventBus
3398
+ .on('previous-page')
3399
+ .pipe(takeUntilDestroyed())
3400
+ .subscribe(() => {
3401
+ this.navigateToPreviousPage();
3402
+ });
3403
+ explicitEffect([this.pendingPageRequest], ([request]) => {
3404
+ if (!request)
3405
+ return;
3406
+ this.stateManager?.pendingPageRequest.set(null);
3407
+ this.navigateToPage(request.index, request.options);
3408
+ });
3409
+ explicitEffect([this.state], ([state]) => this.eventBus.dispatch(PagerStateEvent, state));
3410
+ explicitEffect([this.activePageOwnership], ([ownership]) => this.stateManager?.activePageState.set(ownership));
3411
+ }
3412
+ evaluatePageHidden(page) {
3413
+ if (!page.logic || page.logic.length === 0) {
3414
+ return false;
3415
+ }
3416
+ const hiddenLogic = page.logic.filter((l) => l.type === 'hidden');
3417
+ if (hiddenLogic.length === 0) {
3418
+ return false;
3419
+ }
3420
+ for (const logic of hiddenLogic) {
3421
+ if (typeof logic.condition === 'boolean') {
3422
+ if (logic.condition) {
3423
+ return true;
3424
+ }
3425
+ continue;
3426
+ }
3427
+ const condition = logic.condition;
3428
+ const context = this.fieldContextRegistry.createDisplayOnlyContext(page.key || '', this.functionRegistry.getCustomFunctions());
3429
+ if (evaluateCondition(condition, context)) {
3430
+ return true;
3431
+ }
3432
+ }
3433
+ return false;
3434
+ }
3435
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: PageOrchestratorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3436
+ 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: [
3437
+ {
3438
+ provide: FIELD_SIGNAL_CONTEXT,
3439
+ useFactory: (orchestrator) => orchestrator.extendedFieldSignalContext(),
3440
+ deps: [PageOrchestratorComponent],
3441
+ },
3442
+ ], ngImport: i0, template: `
3443
+ @for (page of pageRenderStates(); track page.field.key) {
3444
+ <!--
3445
+ Skip pages hidden by 'hidden' logic conditions entirely. FormStateManager
3446
+ derives schema/value/validators from config (not from mounted components),
3447
+ so unmounting a hidden page has no effect on form state — only on render +
3448
+ CD cost. This is the page-level equivalent of the existing field-level
3449
+ \`@if (!field.hidden())\` gate in page-field.component.ts.
3450
+ -->
3451
+ @if (!page.hidden) {
3452
+ @if (page.active || page.preload) {
3453
+ @defer (when page.active; on idle) {
3454
+ <section page-field [field]="page.field" [key]="page.field.key" [pageIndex]="page.index" [isVisible]="page.active"></section>
3455
+ } @placeholder {
3456
+ <div class="df-page-placeholder" [attr.data-page-index]="page.index" [attr.data-page-key]="page.field.key"></div>
3457
+ }
3458
+ } @else {
3459
+ <!--
3460
+ Pages outside the preload window remain lightweight placeholders.
3461
+ Form state derives from config, so leaving their components unmounted
3462
+ does not affect validity, derivations, or navigation. The active page
3463
+ renders directly; configured neighbours preload declaratively on idle.
3464
+ -->
3465
+ <div class="df-page-placeholder" [attr.data-page-index]="page.index" [attr.data-page-key]="page.field.key"></div>
3466
+ }
3467
+ }
3468
+ }
3469
+ `, isInline: true, styles: [":host{display:block;position:relative;width:100%}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, deferBlockDependencies: [() => [/* @ts-ignore */
3470
+ import('./ng-forge-dynamic-forms-page-field.component-D16ey8r-.mjs').then(m => m.default)]] });
3739
3471
  }
3740
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: FormStateManager, decorators: [{
3741
- type: Injectable
3742
- }], ctorParameters: () => [] });
3472
+ i0.ɵɵngDeclareClassMetadataAsync({ minVersion: "18.0.0", version: "22.0.1", ngImport: i0, type: PageOrchestratorComponent, resolveDeferredDeps: () => [/* @ts-ignore */
3473
+ import('./ng-forge-dynamic-forms-page-field.component-D16ey8r-.mjs').then(m => m.default)], resolveMetadata: PageFieldComponent => ({ decorators: [{
3474
+ type: Component,
3475
+ args: [{ selector: 'div[page-orchestrator]', imports: [PageFieldComponent], template: `
3476
+ @for (page of pageRenderStates(); track page.field.key) {
3477
+ <!--
3478
+ Skip pages hidden by 'hidden' logic conditions entirely. FormStateManager
3479
+ derives schema/value/validators from config (not from mounted components),
3480
+ so unmounting a hidden page has no effect on form state — only on render +
3481
+ CD cost. This is the page-level equivalent of the existing field-level
3482
+ \`@if (!field.hidden())\` gate in page-field.component.ts.
3483
+ -->
3484
+ @if (!page.hidden) {
3485
+ @if (page.active || page.preload) {
3486
+ @defer (when page.active; on idle) {
3487
+ <section page-field [field]="page.field" [key]="page.field.key" [pageIndex]="page.index" [isVisible]="page.active"></section>
3488
+ } @placeholder {
3489
+ <div class="df-page-placeholder" [attr.data-page-index]="page.index" [attr.data-page-key]="page.field.key"></div>
3490
+ }
3491
+ } @else {
3492
+ <!--
3493
+ Pages outside the preload window remain lightweight placeholders.
3494
+ Form state derives from config, so leaving their components unmounted
3495
+ does not affect validity, derivations, or navigation. The active page
3496
+ renders directly; configured neighbours preload declaratively on idle.
3497
+ -->
3498
+ <div class="df-page-placeholder" [attr.data-page-index]="page.index" [attr.data-page-key]="page.field.key"></div>
3499
+ }
3500
+ }
3501
+ }
3502
+ `, host: {
3503
+ class: 'df-page-orchestrator',
3504
+ '[attr.data-current-page]': 'state().currentPageIndex',
3505
+ '[attr.data-total-pages]': 'state().totalPages',
3506
+ }, providers: [
3507
+ {
3508
+ provide: FIELD_SIGNAL_CONTEXT,
3509
+ useFactory: (orchestrator) => orchestrator.extendedFieldSignalContext(),
3510
+ deps: [PageOrchestratorComponent],
3511
+ },
3512
+ ], changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;position:relative;width:100%}\n"] }]
3513
+ }], 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 }] }] } }) });
3743
3514
 
3744
3515
  function traverseFieldsWithContext(fields, context, visitor, hooks) {
3745
3516
  for (const field of fields) {
@@ -3818,7 +3589,7 @@ function createDerivationRenderGate() {
3818
3589
  if (!has || engine() !== 'idle')
3819
3590
  return;
3820
3591
  engine.set('loading');
3821
- pendingTasks.run(() => import('./ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-OBDy6qd9.mjs')
3592
+ pendingTasks.run(() => import('./ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-BfxYqAQA.mjs')
3822
3593
  .then(({ bootstrapDerivationOrchestrator }) => {
3823
3594
  runInInjectionContext(injector, () => bootstrapDerivationOrchestrator());
3824
3595
  engine.set('wired');
@@ -4125,22 +3896,30 @@ class DynamicForm {
4125
3896
  submitting = this.stateManager.submitting;
4126
3897
  fieldLoadingErrors = this.stateManager.fieldLoadingErrors;
4127
3898
  derivationReady = inject(DERIVATION_RENDER_GATE);
4128
- shouldRender = computed(() => this.stateManager.shouldRender() && this.derivationReady(), /* @ts-ignore */
3899
+ shouldRender = computed(() => this.stateManager.shouldRender() && this.stateManager.formSchemaReady() && this.derivationReady(), /* @ts-ignore */
4129
3900
  ...(ngDevMode ? [{ debugName: "shouldRender" }] : /* istanbul ignore next */ []));
4130
3901
  resolvedFields = this.stateManager.resolvedFields;
4131
3902
  fieldWindowing = computed(() => resolveFieldWindowing(this.globalFieldWindowing, this.effectiveFormOptions()?.fieldWindowing), /* @ts-ignore */
4132
3903
  ...(ngDevMode ? [{ debugName: "fieldWindowing" }] : /* istanbul ignore next */ []));
4133
- totalComponentsCount = computed(() => {
4134
- const fields = this.stateManager.formSetup()?.fields ?? [];
4135
- return countContainersRecursive(fields) + 1;
3904
+ initializedComponents = signal(new Set(), /* @ts-ignore */
3905
+ ...(ngDevMode ? [{ debugName: "initializedComponents" }] : /* istanbul ignore next */ []));
3906
+ initialPageReady = signal(false, /* @ts-ignore */
3907
+ ...(ngDevMode ? [{ debugName: "initialPageReady" }] : /* istanbul ignore next */ []));
3908
+ initializationReady = computed(() => {
3909
+ const config = this.activeConfig();
3910
+ if (!config)
3911
+ return false;
3912
+ if (this.formModeDetection().mode === 'paged')
3913
+ return this.initialPageReady();
3914
+ const initialized = this.initializedComponents();
3915
+ const expected = [
3916
+ initializationComponentKey('dynamic-form', this.componentId),
3917
+ ...collectInitializingContainerKeys(config.fields ?? []),
3918
+ ];
3919
+ return expected.every((key) => initialized.has(key));
4136
3920
  }, /* @ts-ignore */
4137
- ...(ngDevMode ? [{ debugName: "totalComponentsCount" }] : /* istanbul ignore next */ []));
4138
- initialized$ = setupInitializationTracking({
4139
- eventBus: this.eventBus,
4140
- totalComponentsCount: this.totalComponentsCount,
4141
- injector: this.injector,
4142
- componentId: this.componentId,
4143
- });
3921
+ ...(ngDevMode ? [{ debugName: "initializationReady" }] : /* istanbul ignore next */ []));
3922
+ initialized$ = toObservable(this.initializationReady, { injector: this.injector }).pipe(filter((ready) => ready), take(1), shareReplay({ bufferSize: 1, refCount: false }));
4144
3923
  validityChange = outputFromObservable(toObservable(this.valid));
4145
3924
  dirtyChange = outputFromObservable(toObservable(this.dirty));
4146
3925
  submitted = outputFromObservable(this.stateManager.submitted$);
@@ -4148,9 +3927,27 @@ class DynamicForm {
4148
3927
  cleared = outputFromObservable(this.eventBus.on('form-clear'));
4149
3928
  events = outputFromObservable(this.eventBus.events$);
4150
3929
  initialized = outputFromObservable(this.initialized$);
3930
+ activePageInitialized = outputFromObservable(this.eventBus.on('active-page-initialized'));
4151
3931
  onPageChange = outputFromObservable(this.eventBus.on('page-change'));
4152
3932
  onPageNavigationStateChange = outputFromObservable(this.eventBus.on('pager-state'));
4153
3933
  constructor() {
3934
+ this.eventBus
3935
+ .on('component-initialized')
3936
+ .pipe(takeUntilDestroyed(this.destroyRef))
3937
+ .subscribe((event) => {
3938
+ const key = initializationComponentKey(event.componentType, event.componentId);
3939
+ this.initializedComponents.update((current) => {
3940
+ if (current.has(key))
3941
+ return current;
3942
+ const next = new Set(current);
3943
+ next.add(key);
3944
+ return next;
3945
+ });
3946
+ });
3947
+ this.eventBus
3948
+ .on('active-page-initialized')
3949
+ .pipe(takeUntilDestroyed(this.destroyRef), take(1))
3950
+ .subscribe(() => this.initialPageReady.set(true));
4154
3951
  this.dispatcher?.connect(this.eventBus);
4155
3952
  this.destroyRef.onDestroy(() => this.dispatcher?.disconnect());
4156
3953
  inject(FORM_INITIALIZER, { optional: true });
@@ -4175,8 +3972,8 @@ class DynamicForm {
4175
3972
  this.eventBus.dispatch(ComponentInitializedEvent, 'dynamic-form', this.componentId);
4176
3973
  }
4177
3974
  });
4178
- explicitEffect([this.resolvedFields, this.formModeDetection], ([fields, { mode }]) => {
4179
- if (mode === 'non-paged' && fields.length > 0) {
3975
+ explicitEffect([this.resolvedFields, this.formModeDetection, this.activeConfig], ([, { mode }, config]) => {
3976
+ if (config && mode === 'non-paged') {
4180
3977
  afterNextRender(() => {
4181
3978
  this.eventBus.dispatch(ComponentInitializedEvent, 'dynamic-form', this.componentId);
4182
3979
  }, { injector: this.injector });
@@ -4212,7 +4009,7 @@ class DynamicForm {
4212
4009
  });
4213
4010
  }
4214
4011
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: DynamicForm, deps: [], target: i0.ɵɵFactoryTarget.Component });
4215
- 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: [
4012
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", type: DynamicForm, isStandalone: true, selector: "form[dynamic-form]", inputs: { config: { classPropertyName: "config", publicName: "dynamic-form", isSignal: true, isRequired: true, transformFunction: null }, formOptions: { classPropertyName: "formOptions", publicName: "formOptions", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, source: { classPropertyName: "source", publicName: "source", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", validityChange: "validityChange", dirtyChange: "dirtyChange", submitted: "submitted", reset: "reset", cleared: "cleared", events: "events", initialized: "initialized", activePageInitialized: "activePageInitialized", onPageChange: "onPageChange", onPageNavigationStateChange: "onPageNavigationStateChange" }, host: { attributes: { "novalidate": "" }, listeners: { "submit": "onNativeSubmit($event)" }, properties: { "class.disabled": "disabled()", "class.df-form-paged": "formModeDetection().mode === \"paged\"", "class.df-form-non-paged": "formModeDetection().mode === \"non-paged\"", "attr.data-form-mode": "formModeDetection().mode", "attr.id": "idPrefix() || null" }, classAttribute: "df-dynamic-form df-form" }, providers: [
4216
4013
  provideDynamicFormDI(),
4217
4014
  DfFieldTemplateRegistry,
4218
4015
  { provide: DF_FIELD_TEMPLATES, useFactory: () => inject(DfFieldTemplateRegistry).map },
@@ -4224,12 +4021,8 @@ class DynamicForm {
4224
4021
  }
4225
4022
  @case ('non-paged') {
4226
4023
  @for (field of resolvedFields(); track field.key; let i = $index) {
4227
- <!-- @if + DfFieldOutlet's own renderReady-&-!hidden gate together silence NG01916:
4228
- the template @if removes the host from the DOM (Angular's prescribed pattern), and
4229
- the directive's gate stops a same-CD-pass mount before [formField] can warn.
4230
- Don't dedupe one without the other — see DfFieldOutlet.renderReady. -->
4231
- @if (!field.hidden()) {
4232
- @if (windowsField(field, i)) {
4024
+ @if (windowsField(field, i)) {
4025
+ @if (!field.hidden()) {
4233
4026
  @defer (on viewport) {
4234
4027
  <ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
4235
4028
  } @placeholder {
@@ -4240,9 +4033,11 @@ class DynamicForm {
4240
4033
  aria-hidden="true"
4241
4034
  ></div>
4242
4035
  }
4243
- } @else {
4244
- <ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
4245
4036
  }
4037
+ } @else {
4038
+ <!-- DfFieldOutlet owns the hidden gate so an already-mounted field can be detached
4039
+ and restored without recreating its component and wrapper chain. -->
4040
+ <ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
4246
4041
  }
4247
4042
  }
4248
4043
  }
@@ -4263,12 +4058,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
4263
4058
  }
4264
4059
  @case ('non-paged') {
4265
4060
  @for (field of resolvedFields(); track field.key; let i = $index) {
4266
- <!-- @if + DfFieldOutlet's own renderReady-&-!hidden gate together silence NG01916:
4267
- the template @if removes the host from the DOM (Angular's prescribed pattern), and
4268
- the directive's gate stops a same-CD-pass mount before [formField] can warn.
4269
- Don't dedupe one without the other — see DfFieldOutlet.renderReady. -->
4270
- @if (!field.hidden()) {
4271
- @if (windowsField(field, i)) {
4061
+ @if (windowsField(field, i)) {
4062
+ @if (!field.hidden()) {
4272
4063
  @defer (on viewport) {
4273
4064
  <ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
4274
4065
  } @placeholder {
@@ -4279,9 +4070,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
4279
4070
  aria-hidden="true"
4280
4071
  ></div>
4281
4072
  }
4282
- } @else {
4283
- <ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
4284
4073
  }
4074
+ } @else {
4075
+ <!-- DfFieldOutlet owns the hidden gate so an already-mounted field can be detached
4076
+ and restored without recreating its component and wrapper chain. -->
4077
+ <ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
4285
4078
  }
4286
4079
  }
4287
4080
  }
@@ -4303,29 +4096,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
4303
4096
  '[attr.id]': 'idPrefix() || null',
4304
4097
  '(submit)': 'onNativeSubmit($event)',
4305
4098
  }, 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"] }]
4306
- }], 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"] }] } });
4307
- function countContainersRecursive(fields) {
4308
- let count = 0;
4309
- for (const field of fields) {
4310
- if (isContainerField(field)) {
4311
- count += 1;
4312
- if (hasChildFields(field)) {
4313
- const children = field.fields;
4314
- if (Array.isArray(children)) {
4315
- for (const child of children) {
4316
- if (Array.isArray(child)) {
4317
- count += countContainersRecursive(child);
4318
- }
4319
- else if (child != null && isContainerField(child)) {
4320
- count += countContainersRecursive([child]);
4321
- }
4322
- }
4323
- }
4324
- }
4325
- }
4326
- }
4327
- return count;
4328
- }
4099
+ }], 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"] }] } });
4329
4100
 
4330
4101
  const BUILT_IN_ADDON_TYPES = [
4331
4102
  {
@@ -4373,28 +4144,28 @@ const DISPLAY_FIELD_TYPES_BASE = {
4373
4144
  const BUILT_IN_FIELDS = [
4374
4145
  {
4375
4146
  name: 'row',
4376
- loadComponent: () => import('./ng-forge-dynamic-forms-container-field.component-BpeFyhAt.mjs'),
4147
+ loadComponent: () => import('./ng-forge-dynamic-forms-container-field.component-Cj2msZp_.mjs'),
4377
4148
  mapper: rowFieldMapper,
4378
4149
  valueHandling: 'flatten',
4379
4150
  ...CONTAINER_FIELD_TYPES_BASE,
4380
4151
  },
4381
4152
  {
4382
4153
  name: 'group',
4383
- loadComponent: () => import('./ng-forge-dynamic-forms-group-field.component-BNjYMTqT.mjs'),
4154
+ loadComponent: () => import('./ng-forge-dynamic-forms-group-field.component-DN6X506r.mjs'),
4384
4155
  mapper: groupFieldMapper,
4385
4156
  valueHandling: 'include',
4386
4157
  ...CONTAINER_FIELD_TYPES_BASE,
4387
4158
  },
4388
4159
  {
4389
4160
  name: 'array',
4390
- loadComponent: () => import('./ng-forge-dynamic-forms-array-field.component-D-VWBM-t.mjs'),
4161
+ loadComponent: () => import('./ng-forge-dynamic-forms-array-field.component-DK7dAQv7.mjs'),
4391
4162
  mapper: arrayFieldMapper,
4392
4163
  valueHandling: 'include',
4393
4164
  ...CONTAINER_FIELD_TYPES_BASE,
4394
4165
  },
4395
4166
  {
4396
4167
  name: 'page',
4397
- loadComponent: () => import('./ng-forge-dynamic-forms-page-field.component-B92kLZkH.mjs'),
4168
+ loadComponent: () => import('./ng-forge-dynamic-forms-page-field.component-D16ey8r-.mjs'),
4398
4169
  mapper: pageFieldMapper,
4399
4170
  valueHandling: 'flatten',
4400
4171
  ...CONTAINER_FIELD_TYPES_BASE,
@@ -4412,7 +4183,7 @@ const BUILT_IN_FIELDS = [
4412
4183
  },
4413
4184
  {
4414
4185
  name: 'container',
4415
- loadComponent: () => import('./ng-forge-dynamic-forms-container-field.component-BpeFyhAt.mjs'),
4186
+ loadComponent: () => import('./ng-forge-dynamic-forms-container-field.component-Cj2msZp_.mjs'),
4416
4187
  mapper: containerFieldMapper,
4417
4188
  valueHandling: 'flatten',
4418
4189
  ...CONTAINER_FIELD_TYPES_BASE,
@@ -4427,6 +4198,11 @@ const BUILT_IN_WRAPPERS = [
4427
4198
  wrapperName: 'row',
4428
4199
  loadComponent: () => import('./ng-forge-dynamic-forms-row-wrapper.component-BBWmqQA_.mjs'),
4429
4200
  },
4201
+ {
4202
+ wrapperName: 'field-errors',
4203
+ loadComponent: () => import('./ng-forge-dynamic-forms-field-errors-wrapper.component-ZdaC4Bit.mjs'),
4204
+ rendersFieldErrors: true,
4205
+ },
4430
4206
  ];
4431
4207
 
4432
4208
  function isDynamicFormFeature(value) {
@@ -4486,7 +4262,8 @@ function provideDynamicForm(...items) {
4486
4262
  const wrapperBundles = items.filter(isWrappersBundle);
4487
4263
  const features = items.filter(isDynamicFormFeature);
4488
4264
  const fields = [...BUILT_IN_FIELDS, ...fieldTypes];
4489
- const wrappers = [...BUILT_IN_WRAPPERS, ...wrapperTypes, ...wrapperBundles.flatMap((bundle) => bundle.ɵdefinitions)];
4265
+ const customWrappers = [...wrapperTypes, ...wrapperBundles.flatMap((bundle) => bundle.ɵdefinitions)];
4266
+ const wrappers = [...BUILT_IN_WRAPPERS, ...customWrappers];
4490
4267
  const featureProviders = [];
4491
4268
  const hasLoggerFeature = features.some((feature) => feature.ɵkind === 'logger');
4492
4269
  features.forEach((feature) => {
@@ -4501,7 +4278,7 @@ function provideDynamicForm(...items) {
4501
4278
  const logger = inject(DynamicFormLogger);
4502
4279
  const registry = new Map();
4503
4280
  fields.forEach((fieldType) => {
4504
- if (registry.has(fieldType.name)) {
4281
+ if (DEV_MODE && registry.has(fieldType.name)) {
4505
4282
  logger.warn(`Field type "${fieldType.name}" is already registered. Overwriting.`);
4506
4283
  }
4507
4284
  registry.set(fieldType.name, fieldType);
@@ -4527,11 +4304,18 @@ function provideDynamicForm(...items) {
4527
4304
  useFactory: () => {
4528
4305
  const logger = inject(DynamicFormLogger);
4529
4306
  const registry = new Map();
4530
- wrappers.forEach((wrapperType) => {
4531
- if (registry.has(wrapperType.wrapperName)) {
4532
- logger.warn(`Wrapper type "${wrapperType.wrapperName}" is already registered. Overwriting.`);
4307
+ const builtInNames = new Set(BUILT_IN_WRAPPERS.map((w) => w.wrapperName));
4308
+ BUILT_IN_WRAPPERS.forEach((wrapperType) => registry.set(wrapperType.wrapperName, wrapperType));
4309
+ const seenCustom = DEV_MODE ? new Set() : null;
4310
+ customWrappers.forEach((wrapperType) => {
4311
+ const name = wrapperType.wrapperName;
4312
+ if (seenCustom) {
4313
+ if (seenCustom.has(name) && !builtInNames.has(name)) {
4314
+ logger.warn(`Wrapper type "${name}" is already registered. Overwriting.`);
4315
+ }
4316
+ seenCustom.add(name);
4533
4317
  }
4534
- registry.set(wrapperType.wrapperName, wrapperType);
4318
+ registry.set(name, wrapperType);
4535
4319
  });
4536
4320
  return registry;
4537
4321
  },
@@ -4562,7 +4346,7 @@ function provideDynamicForm(...items) {
4562
4346
  registry.set(def.type, def);
4563
4347
  }
4564
4348
  for (const def of contributed) {
4565
- if (registry.has(def.type)) {
4349
+ if (DEV_MODE && registry.has(def.type)) {
4566
4350
  logger.warn(`Addon type "${def.type}" is already registered. Overwriting.`);
4567
4351
  }
4568
4352
  registry.set(def.type, def);
@@ -4624,6 +4408,13 @@ function withFieldWindowing(config) {
4624
4408
  return createFeature('field-windowing', [{ provide: FIELD_WINDOWING, useValue: resolved }]);
4625
4409
  }
4626
4410
 
4411
+ const VALIDATION_EXECUTION_DEFAULTS = new InjectionToken('VALIDATION_EXECUTION_DEFAULTS', {
4412
+ providedIn: 'root',
4413
+ factory: () => ({
4414
+ validateWhenHidden: false,
4415
+ }),
4416
+ });
4417
+
4627
4418
  function withValidationExecutionDefaults(config) {
4628
4419
  const resolved = {
4629
4420
  validateWhenHidden: config?.validateWhenHidden ?? false,
@@ -4814,6 +4605,16 @@ function sanitizeFormConfigPure(config, fieldRegistry, typeRegistry, options = {
4814
4605
  return { sanitized: { ...config, fields: fields }, warnings };
4815
4606
  }
4816
4607
 
4608
+ class ActivePageInitializedEvent {
4609
+ pageIndex;
4610
+ pageKey;
4611
+ type = 'active-page-initialized';
4612
+ constructor(pageIndex, pageKey) {
4613
+ this.pageIndex = pageIndex;
4614
+ this.pageKey = pageKey;
4615
+ }
4616
+ }
4617
+
4817
4618
  class GoToPageEvent {
4818
4619
  pageIndex;
4819
4620
  options;
@@ -4941,5 +4742,5 @@ function arrayEvent(arrayKey) {
4941
4742
 
4942
4743
 
4943
4744
 
4944
- export { withEventFormValue as $, ArrayItemRegistryService as A, BUILT_IN_FIELDS as B, CONTAINER_FIELD_PROCESSORS as C, DfFieldOutlet as D, EventDispatcher as E, FIELD_WINDOWING as F, GoToPageEvent as G, PageChangeEvent as H, InsertArrayItemEvent as I, PagerStateEvent as J, PopArrayItemEvent as K, PrependArrayItemEvent as L, MoveArrayItemEvent as M, NextPageEvent as N, PreviousPageEvent as O, PROPERTY_OVERRIDE_STORE as P, TextAddonComponent as Q, RemoveAtIndexEvent as R, ShiftArrayItemEvent as S, TemplateAddonComponent as T, arrayEvent as U, createWrappers as V, isWrappersBundle as W, provideDynamicForm as X, sanitizeFormConfig as Y, withAddonActions as Z, withCustomAddon as _, isSameWrapperChain as a, withFieldWindowing as a0, withLegacyStatusClasses as a1, withLoggerConfig as a2, withPagePreload as a3, withValidationExecutionDefaults as a4, withValueExclusionDefaults as a5, wrapperProps as a6, createWrapperChainController as b, createFieldResolutionPipe as c, createSchemaFromFields as d, createHiddenSignal as e, createRenderReadySignal as f, getNormalizedArrayMetadata as g, getFieldDefaultValue as h, injectFieldRegistry as i, resolveFieldWindowing as j, DEV_MODE as k, DEFAULT_DEBOUNCE_MS as l, mapFieldToInputs as m, DERIVATION_WARNING_TRACKER as n, FormStateManager as o, DERIVATION_LOG_CONFIG as p, AppendArrayItemEvent as q, resolveWrappers as r, ComponentAddonComponent as s, traverseFieldsWithContext as t, ConsoleLogger as u, DfTemplate as v, DynamicForm as w, FormClearEvent as x, FormResetEvent as y, FormSubmitEvent as z };
4945
- //# sourceMappingURL=ng-forge-dynamic-forms-ng-forge-dynamic-forms-DSXZJHcu.mjs.map
4745
+ export { arrayEvent as $, ArrayItemRegistryService as A, BUILT_IN_FIELDS as B, CONTAINER_FIELD_PROCESSORS as C, DfFieldOutlet as D, DfTemplate as E, FIELD_WINDOWING as F, DynamicForm as G, EventDispatcher as H, FormClearEvent as I, FormResetEvent as J, FormSubmitEvent as K, GoToPageEvent as L, InsertArrayItemEvent as M, MoveArrayItemEvent as N, NextPageEvent as O, PROPERTY_OVERRIDE_STORE as P, PageChangeEvent as Q, PagerStateEvent as R, SchemaRegistryService as S, PopArrayItemEvent as T, PrependArrayItemEvent as U, VALIDATION_EXECUTION_DEFAULTS as V, PreviousPageEvent as W, RemoveAtIndexEvent as X, ShiftArrayItemEvent as Y, TemplateAddonComponent as Z, TextAddonComponent as _, createFieldResolutionPipe as a, createWrappers as a0, isWrappersBundle as a1, provideDynamicForm as a2, sanitizeFormConfig as a3, withAddonActions as a4, withCustomAddon as a5, withEventFormValue as a6, withFieldWindowing as a7, withLegacyStatusClasses as a8, withLoggerConfig as a9, withPagePreload as aa, withValidationExecutionDefaults as ab, withValueExclusionDefaults as ac, wrapperProps as ad, isSameWrapperChain as b, computeContainerHostClasses as c, initializationComponentPath as d, createWrapperChainController as e, createHiddenSignal as f, createRenderReadySignal as g, getNormalizedArrayMetadata as h, injectFieldRegistry as i, emitComponentInitialized as j, getFieldDefaultValue as k, resolveFieldWindowing as l, mapFieldToInputs as m, collectInitializingContainerKeys as n, ActivePageInitializedEvent as o, initializationComponentKey as p, DEV_MODE as q, resolveWrappers as r, setupContainerInitEffect as s, traverseFieldsWithContext as t, DERIVATION_WARNING_TRACKER as u, FormStateManager as v, DERIVATION_LOG_CONFIG as w, AppendArrayItemEvent as x, ComponentAddonComponent as y, ConsoleLogger as z };
4746
+ //# sourceMappingURL=ng-forge-dynamic-forms-ng-forge-dynamic-forms-DCX7tvaB.mjs.map