@ng-forge/dynamic-forms 1.2.0-next.6 → 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 (34) hide show
  1. package/fesm2022/{ng-forge-dynamic-forms-array-field.component-NTHLPjau.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-CNCeWAeJ.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-ByGdzAsk.mjs → ng-forge-dynamic-forms-container-field.component-Cj2msZp_.mjs} +7 -11
  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-form-schema.service-C3cVSmft.mjs +24 -0
  10. package/fesm2022/ng-forge-dynamic-forms-form-schema.service-C3cVSmft.mjs.map +1 -0
  11. package/fesm2022/{ng-forge-dynamic-forms-group-field.component-DnLZdj8v.mjs → ng-forge-dynamic-forms-group-field.component-DN6X506r.mjs} +6 -10
  12. package/fesm2022/ng-forge-dynamic-forms-group-field.component-DN6X506r.mjs.map +1 -0
  13. package/fesm2022/ng-forge-dynamic-forms-internal.mjs +32 -5
  14. package/fesm2022/ng-forge-dynamic-forms-internal.mjs.map +1 -1
  15. package/fesm2022/{ng-forge-dynamic-forms-ng-forge-dynamic-forms-B2C_WAMP.mjs → ng-forge-dynamic-forms-ng-forge-dynamic-forms-DCX7tvaB.mjs} +1043 -1299
  16. package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-DCX7tvaB.mjs.map +1 -0
  17. package/fesm2022/{ng-forge-dynamic-forms-page-field.component-CwH22lRl.mjs → ng-forge-dynamic-forms-page-field.component-D16ey8r-.mjs} +54 -13
  18. package/fesm2022/ng-forge-dynamic-forms-page-field.component-D16ey8r-.mjs.map +1 -0
  19. package/fesm2022/ng-forge-dynamic-forms-schema-builder-DO-su9sF.mjs +570 -0
  20. package/fesm2022/ng-forge-dynamic-forms-schema-builder-DO-su9sF.mjs.map +1 -0
  21. package/fesm2022/ng-forge-dynamic-forms.mjs +1 -1
  22. package/package.json +2 -2
  23. package/types/ng-forge-dynamic-forms-internal.d.ts +10 -5
  24. package/types/ng-forge-dynamic-forms.d.ts +32 -16
  25. package/fesm2022/ng-forge-dynamic-forms-array-field.component-NTHLPjau.mjs.map +0 -1
  26. package/fesm2022/ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-CNCeWAeJ.mjs.map +0 -1
  27. package/fesm2022/ng-forge-dynamic-forms-container-field.component-ByGdzAsk.mjs.map +0 -1
  28. package/fesm2022/ng-forge-dynamic-forms-container-utils-BZFz6ztF.mjs +0 -24
  29. package/fesm2022/ng-forge-dynamic-forms-container-utils-BZFz6ztF.mjs.map +0 -1
  30. package/fesm2022/ng-forge-dynamic-forms-emit-initialization-DhHwU6FJ.mjs +0 -17
  31. package/fesm2022/ng-forge-dynamic-forms-emit-initialization-DhHwU6FJ.mjs.map +0 -1
  32. package/fesm2022/ng-forge-dynamic-forms-group-field.component-DnLZdj8v.mjs.map +0 -1
  33. package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-B2C_WAMP.mjs.map +0 -1
  34. package/fesm2022/ng-forge-dynamic-forms-page-field.component-CwH22lRl.mjs.map +0 -1
@@ -1,11 +1,10 @@
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
 
@@ -45,6 +44,8 @@ function needsErrorWrapper(field, composed, wrapperRegistry) {
45
44
  return !composed.some((w) => w.type === FIELD_ERRORS_WRAPPER || wrapperRegistry.get(w.type)?.rendersFieldErrors);
46
45
  }
47
46
 
47
+ const DEV_MODE = typeof ngDevMode === 'undefined' || !!ngDevMode;
48
+
48
49
  const inputNamesCache = new WeakMap();
49
50
  function getDeclaredInputs(componentType) {
50
51
  let inputs = inputNamesCache.get(componentType);
@@ -83,7 +84,9 @@ function loadWrapperComponents(configs, registry, cache, logger) {
83
84
  return of([]);
84
85
  return forkJoin(configs.map((config) => from(loadWrapperComponent(config.type, registry, cache)).pipe(catchError(() => of(undefined)), map((component) => {
85
86
  if (!component) {
86
- 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
+ }
87
90
  return null;
88
91
  }
89
92
  return { config, component };
@@ -123,9 +126,11 @@ function renderStep(slot, remaining, options, refs) {
123
126
  ref.changeDetectorRef.detectChanges();
124
127
  const inner = resolveInnerSlot(ref);
125
128
  if (!inner) {
126
- options.logger.error(`Wrapper component for type '${wrapper.config.type}' does not provide a 'fieldComponent' ViewContainerRef. ` +
127
- `Ensure the wrapper component has a viewChild('fieldComponent', { read: ViewContainerRef }) query ` +
128
- `and that #fieldComponent is not inside a conditional (@if, @defer).`);
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
+ }
129
134
  while (refs.length)
130
135
  refs.pop().destroy();
131
136
  return;
@@ -167,22 +172,28 @@ function createWrapperChainController(opts) {
167
172
  const deps = injectChainDeps();
168
173
  const state = createStateSignal(opts);
169
174
  const mounted = { value: null };
175
+ const detached = { value: null };
170
176
  let refs = [];
171
177
  buildEmissionStream(state, deps)
172
178
  .pipe(takeUntilDestroyed(deps.destroyRef))
173
179
  .subscribe((emission) => {
174
180
  try {
175
- refs = applyEmission(emission, { opts, deps, mounted, refs });
181
+ refs = applyEmission(emission, { opts, deps, mounted, refs, detached });
176
182
  }
177
183
  catch (err) {
178
184
  deps.logger.error('Wrapper chain render failed; tearing down partial state.', err);
179
185
  opts.vcr().clear();
180
186
  refs = [];
181
187
  mounted.value = null;
188
+ detached.value?.destroy();
189
+ detached.value = null;
182
190
  }
183
191
  });
184
192
  pushFieldInputsOnChange(opts, () => refs);
185
- deps.destroyRef.onDestroy(() => opts.vcr().clear());
193
+ deps.destroyRef.onDestroy(() => {
194
+ detached.value?.destroy();
195
+ opts.vcr().clear();
196
+ });
186
197
  }
187
198
  function injectChainDeps() {
188
199
  return {
@@ -217,10 +228,21 @@ function applyEmission({ state, loaded }, ctx) {
217
228
  const { opts, deps, mounted, refs } = ctx;
218
229
  const vcr = opts.vcr();
219
230
  const structurallyChanged = isStructurallyDifferent(mounted.value, state);
220
- 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;
221
239
  return refs;
240
+ }
222
241
  if (structurallyChanged && mounted.value !== null) {
223
- opts.beforeRebuild?.();
242
+ const preservesDetachedOuterView = opts.beforeRebuild?.() === true;
243
+ if (ctx.detached.value && !preservesDetachedOuterView)
244
+ ctx.detached.value.destroy();
245
+ ctx.detached.value = null;
224
246
  vcr.clear();
225
247
  mounted.value = null;
226
248
  }
@@ -322,13 +344,14 @@ class FieldComponentSlot {
322
344
  detach() {
323
345
  const current = this.state();
324
346
  if (current.phase !== 'mounted')
325
- return;
347
+ return false;
326
348
  const focusSnapshot = this.captureFocus(current.ref);
327
349
  const idx = current.slot.indexOf(current.ref.hostView);
328
350
  if (idx >= 0)
329
351
  current.slot.detach(idx);
330
352
  this.lastInputs = null;
331
353
  this.state.set(Object.freeze({ phase: 'detached', ref: current.ref, focusSnapshot }));
354
+ return idx < 0;
332
355
  }
333
356
  destroyOnTeardown() {
334
357
  const current = this.state();
@@ -359,6 +382,74 @@ class FieldComponentSlot {
359
382
  }
360
383
  }
361
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
+
362
453
  class DfFieldOutlet {
363
454
  dfFieldOutlet = input.required(/* @ts-ignore */
364
455
  ...(ngDevMode ? [{ debugName: "dfFieldOutlet" }] : /* istanbul ignore next */ []));
@@ -371,11 +462,15 @@ class DfFieldOutlet {
371
462
  wrapperRegistry = inject(WRAPPER_REGISTRY);
372
463
  defaultWrappersSignal = inject(DEFAULT_WRAPPERS, { optional: true });
373
464
  readonlyFieldCache = inject(READONLY_FIELD_TREE_CACHE);
465
+ eventBus = inject(EventBus, { optional: true });
466
+ injector = inject(Injector);
374
467
  fieldComponent = new FieldComponentSlot();
375
468
  componentIdentity = computed(() => this.dfFieldOutlet().component, /* @ts-ignore */
376
469
  ...(ngDevMode ? [{ debugName: "componentIdentity" }] : /* istanbul ignore next */ []));
377
470
  renderReady = computed(() => this.dfFieldOutlet().renderReady() && !this.dfFieldOutlet().hidden(), /* @ts-ignore */
378
471
  ...(ngDevMode ? [{ debugName: "renderReady" }] : /* istanbul ignore next */ []));
472
+ hidden = computed(() => this.dfFieldOutlet().hidden(), /* @ts-ignore */
473
+ ...(ngDevMode ? [{ debugName: "hidden" }] : /* istanbul ignore next */ []));
379
474
  rawInputs = computed(() => this.dfFieldOutlet().inputs(), /* @ts-ignore */
380
475
  ...(ngDevMode ? [{ debugName: "rawInputs" }] : /* istanbul ignore next */ []));
381
476
  wrappers = computed(() => resolveWrappers(this.dfFieldOutlet().fieldDef, this.defaultWrappersSignal?.(), this.wrapperAutoAssociations, this.wrapperRegistry), { ...(ngDevMode ? { debugName: "wrappers" } : /* istanbul ignore next */ {}), equal: isSameWrapperChain });
@@ -404,6 +499,15 @@ class DfFieldOutlet {
404
499
  },
405
500
  });
406
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
+ });
407
511
  this.destroyRef.onDestroy(() => this.fieldComponent.destroyOnTeardown());
408
512
  }
409
513
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: DfFieldOutlet, deps: [], target: i0.ɵɵFactoryTarget.Directive });
@@ -484,462 +588,72 @@ function clampWindowSize(value, fallback) {
484
588
  return Math.max(0, Math.floor(value));
485
589
  }
486
590
 
487
- class PageOrchestratorComponent {
488
- eventBus = inject(EventBus);
489
- fieldContextRegistry = inject(FieldContextRegistryService);
490
- functionRegistry = inject(FunctionRegistryService);
491
- formOptions = inject(FORM_OPTIONS, { optional: true });
492
- globalPreloadWindow = inject(PAGE_PRELOAD_WINDOW);
493
- pageFields = input.required(/* @ts-ignore */
494
- ...(ngDevMode ? [{ debugName: "pageFields" }] : /* istanbul ignore next */ []));
495
- form = input.required(/* @ts-ignore */
496
- ...(ngDevMode ? [{ debugName: "form" }] : /* istanbul ignore next */ []));
497
- fieldSignalContext = input.required(/* @ts-ignore */
498
- ...(ngDevMode ? [{ debugName: "fieldSignalContext" }] : /* istanbul ignore next */ []));
499
- pageHiddenStates = computed(() => {
500
- const pages = this.pageFields();
501
- return pages.map((page) => this.evaluatePageHidden(page));
502
- }, /* @ts-ignore */
503
- ...(ngDevMode ? [{ debugName: "pageHiddenStates" }] : /* istanbul ignore next */ []));
504
- visiblePageIndices = computed(() => {
505
- const hiddenStates = this.pageHiddenStates();
506
- return hiddenStates
507
- .map((hidden, index) => ({ index, hidden }))
508
- .filter((item) => !item.hidden)
509
- .map((item) => item.index);
510
- }, /* @ts-ignore */
511
- ...(ngDevMode ? [{ debugName: "visiblePageIndices" }] : /* istanbul ignore next */ []));
512
- currentPageIndex = linkedSignal(() => {
513
- const totalPages = this.pageFields().length;
514
- if (totalPages === 0)
515
- return 0;
516
- return untracked(() => this.resolveInitialLanding());
517
- }, /* @ts-ignore */
518
- ...(ngDevMode ? [{ debugName: "currentPageIndex" }] : /* istanbul ignore next */ []));
519
- resolveInitialLanding() {
520
- const { index, validate } = this.initialPage();
521
- const visible = this.visiblePageIndices();
522
- if (index === 0 || visible.length === 0)
523
- return 0;
524
- const target = visible.includes(index) ? index : this.findNearestVisiblePage(index, visible);
525
- if (target <= 0)
526
- return Math.max(target, 0);
527
- if (!validate)
528
- return target;
529
- const firstInvalid = visible.filter((i) => i < target).find((i) => !this.isPageValid(i));
530
- return firstInvalid ?? target;
531
- }
532
- initialPage = computed(() => {
533
- const raw = this.formOptions?.()?.initialPage;
534
- const totalPages = this.pageFields().length;
535
- const requested = typeof raw === 'number' ? raw : raw?.index;
536
- const validate = typeof raw === 'number' ? false : (raw?.validate ?? false);
537
- if (typeof requested !== 'number' || !Number.isFinite(requested) || requested < 0 || totalPages === 0) {
538
- return { index: 0, validate: false };
539
- }
540
- return { index: Math.min(Math.trunc(requested), totalPages - 1), validate };
541
- }, /* @ts-ignore */
542
- ...(ngDevMode ? [{ debugName: "initialPage" }] : /* istanbul ignore next */ []));
543
- state = computed(() => {
544
- const currentIndex = this.currentPageIndex();
545
- const totalPages = this.pageFields().length;
546
- const visibleIndices = this.visiblePageIndices();
547
- const currentVisiblePosition = visibleIndices.indexOf(currentIndex);
548
- const isFirstVisiblePage = currentVisiblePosition === 0 || currentVisiblePosition === -1;
549
- const isLastVisiblePage = currentVisiblePosition >= visibleIndices.length - 1;
550
- return {
551
- currentPageIndex: currentIndex,
552
- totalPages,
553
- isFirstPage: isFirstVisiblePage,
554
- isLastPage: isLastVisiblePage,
555
- };
556
- }, /* @ts-ignore */
557
- ...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
558
- currentPageValid = computed(() => this.isPageValid(this.currentPageIndex()), /* @ts-ignore */
559
- ...(ngDevMode ? [{ debugName: "currentPageValid" }] : /* istanbul ignore next */ []));
560
- isPageValid(pageIndex) {
561
- const pages = this.pageFields();
562
- const form = this.form();
563
- if (pageIndex < 0 || pageIndex >= pages.length) {
564
- return true;
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));
565
596
  }
566
- const leafKeys = collectLeafFieldKeys(pages[pageIndex].fields || []);
567
- for (const fieldKey of leafKeys) {
568
- const field = form[fieldKey];
569
- if (field && typeof field === 'function') {
570
- const fieldState = field();
571
- if (fieldState && typeof fieldState.valid === 'function' && !fieldState.valid()) {
572
- return false;
573
- }
574
- }
597
+ else if (field.key) {
598
+ keys.push(field.key);
575
599
  }
576
- return true;
577
- }
578
- extendedFieldSignalContext = computed(() => ({
579
- ...this.fieldSignalContext(),
580
- currentPageValid: this.currentPageValid,
581
- }), /* @ts-ignore */
582
- ...(ngDevMode ? [{ debugName: "extendedFieldSignalContext" }] : /* istanbul ignore next */ []));
583
- preloadWindow = computed(() => {
584
- const perForm = this.formOptions?.()?.pagePreloadWindow;
585
- return clampWindowSize(perForm, this.globalPreloadWindow);
586
- }, /* @ts-ignore */
587
- ...(ngDevMode ? [{ debugName: "preloadWindow" }] : /* istanbul ignore next */ []));
588
- isWithinPreloadWindow(i) {
589
- return Math.abs(i - this.state().currentPageIndex) <= this.preloadWindow();
590
- }
591
- constructor() {
592
- this.setupEventListeners();
593
- explicitEffect([this.state, this.visiblePageIndices], ([state, visibleIndices]) => {
594
- const currentVisiblePosition = visibleIndices.indexOf(state.currentPageIndex);
595
- if (currentVisiblePosition === -1 && visibleIndices.length > 0) {
596
- const nearest = this.findNearestVisiblePage(state.currentPageIndex, visibleIndices);
597
- if (nearest !== -1) {
598
- this.executePageChange(nearest);
599
- }
600
- }
601
- });
602
600
  }
603
- navigateToNextPage() {
604
- const disableWhenPageInvalid = this.formOptions?.()?.nextButton?.disableWhenPageInvalid ?? true;
605
- if (disableWhenPageInvalid && !this.currentPageValid()) {
606
- return {
607
- success: false,
608
- newPageIndex: this.state().currentPageIndex,
609
- error: 'Current page has invalid fields',
610
- };
611
- }
612
- const currentState = this.state();
613
- const visibleIndices = this.visiblePageIndices();
614
- if (currentState.isLastPage) {
615
- return {
616
- success: false,
617
- newPageIndex: currentState.currentPageIndex,
618
- error: 'Already on the last visible page',
619
- };
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) }];
620
616
  }
621
- const currentVisiblePosition = visibleIndices.indexOf(currentState.currentPageIndex);
622
- if (currentVisiblePosition === -1 || currentVisiblePosition >= visibleIndices.length - 1) {
623
- return {
624
- success: false,
625
- newPageIndex: currentState.currentPageIndex,
626
- error: 'No next visible page available',
627
- };
617
+ if (isArrayField(field) && !isSimplifiedArrayField(field)) {
618
+ const normalizedItems = field.fields.map((item) => Array.isArray(item) ? normalizeSimplifiedArrays(item) : item);
619
+ return [{ ...field, fields: normalizedItems }];
628
620
  }
629
- const nextVisiblePageIndex = visibleIndices[currentVisiblePosition + 1];
630
- return this.executePageChange(nextVisiblePageIndex);
631
- }
632
- navigateToPreviousPage() {
633
- const currentState = this.state();
634
- const visibleIndices = this.visiblePageIndices();
635
- if (currentState.isFirstPage) {
636
- return {
637
- success: false,
638
- newPageIndex: currentState.currentPageIndex,
639
- error: 'Already on the first visible page',
640
- };
621
+ if (isSimplifiedArrayField(field)) {
622
+ const { arrayField, addButton } = expandSimplifiedArray(field);
623
+ return addButton ? [arrayField, addButton] : [arrayField];
641
624
  }
642
- const currentVisiblePosition = visibleIndices.indexOf(currentState.currentPageIndex);
643
- if (currentVisiblePosition <= 0) {
644
- return {
645
- success: false,
646
- newPageIndex: currentState.currentPageIndex,
647
- error: 'No previous visible page available',
648
- };
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
+ }
649
639
  }
650
- const prevVisiblePageIndex = visibleIndices[currentVisiblePosition - 1];
651
- return this.executePageChange(prevVisiblePageIndex);
652
640
  }
653
- navigateToPage(pageIndex, options) {
654
- const currentIndex = this.state().currentPageIndex;
655
- const visibleIndices = this.visiblePageIndices();
656
- if (pageIndex <= currentIndex || !visibleIndices.includes(pageIndex)) {
657
- return this.executePageChange(pageIndex);
658
- }
659
- if (options?.validate === false) {
660
- return this.executePageChange(pageIndex);
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.`);
661
646
  }
662
- const disableWhenPageInvalid = this.formOptions?.()?.nextButton?.disableWhenPageInvalid ?? true;
663
- if (!disableWhenPageInvalid) {
664
- return this.executePageChange(pageIndex);
665
- }
666
- const crossedPages = visibleIndices.filter((index) => index >= currentIndex && index < pageIndex);
667
- const firstInvalidPage = crossedPages.find((index) => !this.isPageValid(index));
668
- if (firstInvalidPage === undefined) {
669
- return this.executePageChange(pageIndex);
670
- }
671
- this.executePageChange(firstInvalidPage);
672
- return {
673
- success: false,
674
- newPageIndex: firstInvalidPage,
675
- error: `Cannot navigate to page ${pageIndex}: page ${firstInvalidPage} has invalid fields`,
676
- };
677
- }
678
- executePageChange(pageIndex) {
679
- const currentState = this.state();
680
- const totalPages = currentState.totalPages;
681
- if (pageIndex < 0 || pageIndex >= totalPages) {
682
- return {
683
- success: false,
684
- newPageIndex: currentState.currentPageIndex,
685
- error: `Invalid page index: ${pageIndex}. Valid range is 0 to ${totalPages - 1}`,
686
- };
687
- }
688
- const visibleIndices = this.visiblePageIndices();
689
- if (!visibleIndices.includes(pageIndex)) {
690
- return {
691
- success: false,
692
- newPageIndex: currentState.currentPageIndex,
693
- error: `Cannot navigate to hidden page at index ${pageIndex}. Visible pages: [${visibleIndices.join(', ')}]`,
694
- };
695
- }
696
- if (pageIndex === currentState.currentPageIndex) {
697
- return {
698
- success: true,
699
- newPageIndex: pageIndex,
700
- };
701
- }
702
- const previousIndex = currentState.currentPageIndex;
703
- this.currentPageIndex.set(pageIndex);
704
- this.eventBus.dispatch(PageChangeEvent, pageIndex, totalPages, previousIndex);
705
- return {
706
- success: true,
707
- newPageIndex: pageIndex,
708
- };
709
- }
710
- findNearestVisiblePage(currentIndex, visibleIndices) {
711
- let nearest = -1;
712
- let minDistance = Infinity;
713
- for (const idx of visibleIndices) {
714
- const distance = Math.abs(idx - currentIndex);
715
- if (distance < minDistance || (distance === minDistance && idx > nearest)) {
716
- minDistance = distance;
717
- nearest = idx;
718
- }
719
- }
720
- return nearest;
721
- }
722
- setupEventListeners() {
723
- this.eventBus
724
- .on('next-page')
725
- .pipe(takeUntilDestroyed())
726
- .subscribe(() => {
727
- this.navigateToNextPage();
728
- });
729
- this.eventBus
730
- .on('previous-page')
731
- .pipe(takeUntilDestroyed())
732
- .subscribe(() => {
733
- this.navigateToPreviousPage();
734
- });
735
- this.eventBus
736
- .on('go-to-page')
737
- .pipe(takeUntilDestroyed())
738
- .subscribe((event) => {
739
- this.navigateToPage(event.pageIndex, event.options);
740
- });
741
- explicitEffect([this.state], ([state]) => this.eventBus.dispatch(PagerStateEvent, state));
742
- }
743
- evaluatePageHidden(page) {
744
- if (!page.logic || page.logic.length === 0) {
745
- return false;
746
- }
747
- const hiddenLogic = page.logic.filter((l) => l.type === 'hidden');
748
- if (hiddenLogic.length === 0) {
749
- return false;
750
- }
751
- for (const logic of hiddenLogic) {
752
- if (typeof logic.condition === 'boolean') {
753
- if (logic.condition) {
754
- return true;
755
- }
756
- continue;
757
- }
758
- const condition = logic.condition;
759
- const context = this.fieldContextRegistry.createDisplayOnlyContext(page.key || '', this.functionRegistry.getCustomFunctions());
760
- if (evaluateCondition(condition, context)) {
761
- return true;
762
- }
763
- }
764
- return false;
765
- }
766
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: PageOrchestratorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
767
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", type: PageOrchestratorComponent, isStandalone: true, selector: "div[page-orchestrator]", inputs: { pageFields: { classPropertyName: "pageFields", publicName: "pageFields", isSignal: true, isRequired: true, transformFunction: null }, form: { classPropertyName: "form", publicName: "form", isSignal: true, isRequired: true, transformFunction: null }, fieldSignalContext: { classPropertyName: "fieldSignalContext", publicName: "fieldSignalContext", isSignal: true, isRequired: true, transformFunction: null } }, host: { properties: { "attr.data-current-page": "state().currentPageIndex", "attr.data-total-pages": "state().totalPages" }, classAttribute: "df-page-orchestrator" }, providers: [
768
- {
769
- provide: FIELD_SIGNAL_CONTEXT,
770
- useFactory: (orchestrator) => orchestrator.extendedFieldSignalContext(),
771
- deps: [PageOrchestratorComponent],
772
- },
773
- ], ngImport: i0, template: `
774
- @for (pageField of pageFields(); track pageField.key; let i = $index) {
775
- <!--
776
- Skip pages hidden by 'hidden' logic conditions entirely. FormStateManager
777
- derives schema/value/validators from config (not from mounted components),
778
- so unmounting a hidden page has no effect on form state — only on render +
779
- CD cost. This is the page-level equivalent of the existing field-level
780
- \`@if (!field.hidden())\` gate in page-field.component.ts.
781
- -->
782
- @if (!pageHiddenStates()[i]) {
783
- @if (isWithinPreloadWindow(i)) {
784
- <!-- Current and preloaded neighbour pages: render immediately for flicker-free navigation -->
785
- @defer (on immediate) {
786
- <section
787
- page-field
788
- [field]="pageField"
789
- [key]="pageField.key"
790
- [pageIndex]="i"
791
- [isVisible]="i === state().currentPageIndex"
792
- ></section>
793
- } @placeholder {
794
- <div class="df-page-placeholder" [attr.data-page-index]="i" [attr.data-page-key]="pageField.key"></div>
795
- }
796
- } @else {
797
- <!--
798
- Pages outside the preload window render only a lightweight placeholder
799
- — they are NOT mounted. Mounting every page eagerly (previously via
800
- \`@defer (on idle)\`, which fires almost immediately post-load) put all
801
- fields of every page in the DOM at once: O(all-fields) initial render
802
- blocking + O(all-fields) change detection on every keystroke, with no
803
- visible benefit since only one page is shown at a time.
804
-
805
- Form state (schema/value/validators) derives from config, not from
806
- mounted components, so leaving a page unmounted does not affect
807
- validity, derivations, or navigation. A page mounts the moment
808
- navigation brings it into the preload window (above), which is
809
- flicker-free for sequential next/previous navigation. The window size
810
- is configurable via \`withPagePreload(n)\` or \`FormOptions.pagePreloadWindow\`.
811
- -->
812
- <div class="df-page-placeholder" [attr.data-page-index]="i" [attr.data-page-key]="pageField.key"></div>
813
- }
814
- }
815
- }
816
- `, isInline: true, styles: [":host{display:block;position:relative;width:100%}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, deferBlockDependencies: [() => [/* @ts-ignore */
817
- import('./ng-forge-dynamic-forms-page-field.component-CwH22lRl.mjs').then(m => m.default)]] });
818
- }
819
- i0.ɵɵngDeclareClassMetadataAsync({ minVersion: "18.0.0", version: "22.0.1", ngImport: i0, type: PageOrchestratorComponent, resolveDeferredDeps: () => [/* @ts-ignore */
820
- import('./ng-forge-dynamic-forms-page-field.component-CwH22lRl.mjs').then(m => m.default)], resolveMetadata: PageFieldComponent => ({ decorators: [{
821
- type: Component,
822
- args: [{ selector: 'div[page-orchestrator]', imports: [PageFieldComponent], template: `
823
- @for (pageField of pageFields(); track pageField.key; let i = $index) {
824
- <!--
825
- Skip pages hidden by 'hidden' logic conditions entirely. FormStateManager
826
- derives schema/value/validators from config (not from mounted components),
827
- so unmounting a hidden page has no effect on form state — only on render +
828
- CD cost. This is the page-level equivalent of the existing field-level
829
- \`@if (!field.hidden())\` gate in page-field.component.ts.
830
- -->
831
- @if (!pageHiddenStates()[i]) {
832
- @if (isWithinPreloadWindow(i)) {
833
- <!-- Current and preloaded neighbour pages: render immediately for flicker-free navigation -->
834
- @defer (on immediate) {
835
- <section
836
- page-field
837
- [field]="pageField"
838
- [key]="pageField.key"
839
- [pageIndex]="i"
840
- [isVisible]="i === state().currentPageIndex"
841
- ></section>
842
- } @placeholder {
843
- <div class="df-page-placeholder" [attr.data-page-index]="i" [attr.data-page-key]="pageField.key"></div>
844
- }
845
- } @else {
846
- <!--
847
- Pages outside the preload window render only a lightweight placeholder
848
- — they are NOT mounted. Mounting every page eagerly (previously via
849
- \`@defer (on idle)\`, which fires almost immediately post-load) put all
850
- fields of every page in the DOM at once: O(all-fields) initial render
851
- blocking + O(all-fields) change detection on every keystroke, with no
852
- visible benefit since only one page is shown at a time.
853
-
854
- Form state (schema/value/validators) derives from config, not from
855
- mounted components, so leaving a page unmounted does not affect
856
- validity, derivations, or navigation. A page mounts the moment
857
- navigation brings it into the preload window (above), which is
858
- flicker-free for sequential next/previous navigation. The window size
859
- is configurable via \`withPagePreload(n)\` or \`FormOptions.pagePreloadWindow\`.
860
- -->
861
- <div class="df-page-placeholder" [attr.data-page-index]="i" [attr.data-page-key]="pageField.key"></div>
862
- }
863
- }
864
- }
865
- `, host: {
866
- class: 'df-page-orchestrator',
867
- '[attr.data-current-page]': 'state().currentPageIndex',
868
- '[attr.data-total-pages]': 'state().totalPages',
869
- }, providers: [
870
- {
871
- provide: FIELD_SIGNAL_CONTEXT,
872
- useFactory: (orchestrator) => orchestrator.extendedFieldSignalContext(),
873
- deps: [PageOrchestratorComponent],
874
- },
875
- ], changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;position:relative;width:100%}\n"] }]
876
- }], ctorParameters: () => [], propDecorators: { pageFields: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageFields", required: true }] }], form: [{ type: i0.Input, args: [{ isSignal: true, alias: "form", required: true }] }], fieldSignalContext: [{ type: i0.Input, args: [{ isSignal: true, alias: "fieldSignalContext", required: true }] }] } }) });
877
- function collectLeafFieldKeys(fields) {
878
- const keys = [];
879
- for (const field of fields) {
880
- if (isRowField(field) || isGenericContainerField(field)) {
881
- keys.push(...collectLeafFieldKeys(field.fields));
882
- }
883
- else if (field.key) {
884
- keys.push(field.key);
885
- }
886
- }
887
- return keys;
888
- }
889
-
890
- const NORMALIZED_ARRAY_METADATA = Symbol('normalizedArrayMetadata');
891
- function setNormalizedArrayMetadata(arrayField, metadata) {
892
- arrayField[NORMALIZED_ARRAY_METADATA] = metadata;
893
- }
894
- function getNormalizedArrayMetadata(arrayField) {
895
- return arrayField[NORMALIZED_ARRAY_METADATA];
896
- }
897
-
898
- function normalizeSimplifiedArrays(fields) {
899
- return fields.flatMap((field) => {
900
- if (isContainerField(field) && !isArrayField(field) && hasChildFields(field)) {
901
- return [{ ...field, fields: normalizeSimplifiedArrays(field.fields) }];
902
- }
903
- if (isArrayField(field) && !isSimplifiedArrayField(field)) {
904
- const normalizedItems = field.fields.map((item) => Array.isArray(item) ? normalizeSimplifiedArrays(item) : item);
905
- return [{ ...field, fields: normalizedItems }];
906
- }
907
- if (isSimplifiedArrayField(field)) {
908
- const { arrayField, addButton } = expandSimplifiedArray(field);
909
- return addButton ? [arrayField, addButton] : [arrayField];
910
- }
911
- return [field];
912
- });
913
- }
914
- const INVALID_ARRAY_CHILD_TYPES = new Set(['page', 'array']);
915
- function validateSimplifiedTemplate(field) {
916
- const { template, key } = field;
917
- const isObjectTemplate = Array.isArray(template);
918
- if (isObjectTemplate) {
919
- const templateFields = template;
920
- for (const tmpl of templateFields) {
921
- if (INVALID_ARRAY_CHILD_TYPES.has(tmpl.type)) {
922
- throw new DynamicFormError(`Simplified array "${key}" template contains a '${tmpl.type}' field (key: '${tmpl.key}'). ` +
923
- `Only leaf fields, rows, and groups are allowed as array children.`);
924
- }
925
- }
926
- }
927
- else {
928
- const singleTemplate = template;
929
- if (INVALID_ARRAY_CHILD_TYPES.has(singleTemplate.type)) {
930
- throw new DynamicFormError(`Simplified array "${key}" template has type '${singleTemplate.type}'. ` +
931
- `Only leaf fields, rows, and groups are allowed as array children.`);
932
- }
933
- }
934
- }
935
- function expandSimplifiedArray(field) {
936
- const { template, value = [], addButton, removeButton, key, logic, minLength, maxLength, wrappers, skipAutoWrappers, skipDefaultWrappers, validators, validationMessages, required, validateWhenHidden, } = field;
937
- const isObjectTemplate = Array.isArray(template);
938
- const values = value;
939
- validateSimplifiedTemplate(field);
940
- const items = values.map((v) => {
941
- if (isObjectTemplate) {
942
- 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);
943
657
  }
944
658
  return buildPrimitiveItem(template, v);
945
659
  });
@@ -1107,520 +821,104 @@ class SchemaRegistryService {
1107
821
  registerSchema(schema) {
1108
822
  this.registeredSchemas.set(schema.name, schema);
1109
823
  }
1110
- getSchema(name) {
1111
- return this.registeredSchemas.get(name);
1112
- }
1113
- getAllSchemas() {
1114
- return new Map(this.registeredSchemas);
1115
- }
1116
- clearSchemas() {
1117
- this.registeredSchemas.clear();
1118
- }
1119
- resolveSchema(schema) {
1120
- if (typeof schema === 'string') {
1121
- return this.getSchema(schema) || null;
1122
- }
1123
- return schema;
1124
- }
1125
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: SchemaRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1126
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: SchemaRegistryService });
1127
- }
1128
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: SchemaRegistryService, decorators: [{
1129
- type: Injectable
1130
- }] });
1131
-
1132
- const DEV_MODE = typeof ngDevMode === 'undefined' || !!ngDevMode;
1133
-
1134
- const DEFAULT_DEBOUNCE_MS = 500;
1135
- function createDebouncedEffect(source, callback, options) {
1136
- const { ms = DEFAULT_DEBOUNCE_MS, injector } = options;
1137
- const source$ = toObservable(source, { injector }).pipe(debounceTime(ms), distinctUntilChanged(), tap((value) => callback(value)));
1138
- return toSignal(source$, { injector });
1139
- }
1140
- function createDebouncedSignal(source, options) {
1141
- const { ms = DEFAULT_DEBOUNCE_MS, injector } = options;
1142
- const source$ = toObservable(source, { injector }).pipe(debounceTime(ms), distinctUntilChanged());
1143
- return toSignal(source$, { injector });
1144
- }
1145
-
1146
- function getConfigTrigger(config) {
1147
- if (!isStateLogicConfig(config)) {
1148
- return 'onChange';
1149
- }
1150
- return config.trigger ?? 'onChange';
1151
- }
1152
- function getConfigDebounceMs(config) {
1153
- if (!isStateLogicConfig(config)) {
1154
- return undefined;
1155
- }
1156
- if (config.trigger === 'debounced') {
1157
- return config.debounceMs;
1158
- }
1159
- return undefined;
1160
- }
1161
- function applyLogic(config, fieldPath) {
1162
- if (isDerivationLogicConfig(config))
1163
- return;
1164
- if (!isStateLogicConfig(config)) {
1165
- if (DEV_MODE) {
1166
- console.warn(`[Dynamic Forms] Unrecognized logic config type '${config.type}' in applyLogic. ` +
1167
- 'This config will be ignored. If this is a new logic type, ensure it is handled explicitly.');
1168
- }
1169
- return;
1170
- }
1171
- const path = fieldPath;
1172
- const trigger = getConfigTrigger(config);
1173
- const debounceMs = getConfigDebounceMs(config) ?? DEFAULT_DEBOUNCE_MS;
1174
- if (typeof config.condition === 'boolean') {
1175
- applyLogicFn(config.type, path, () => config.condition);
1176
- return;
1177
- }
1178
- if (isFormStateCondition(config.condition)) {
1179
- if (config.condition === 'formInvalid' || config.condition === 'pageInvalid') {
1180
- const reason = config.condition === 'formInvalid'
1181
- ? `it would create a validation cycle against the form's own validity`
1182
- : `a value field has no page context for it to resolve against`;
1183
- throw new DynamicFormError(`The '${config.condition}' form-state condition is not supported on value-field logic because ${reason}. ` +
1184
- `Use 'formSubmitting', or apply this condition to a button or container instead.`);
1185
- }
1186
- applyLogicFn(config.type, path, createFormStateLogicFunction(config.condition));
1187
- return;
1188
- }
1189
- const logicFn = trigger === 'debounced'
1190
- ? createDebouncedLogicFunction(config.condition, debounceMs)
1191
- : createLogicFunction(config.condition);
1192
- applyLogicFn(config.type, path, logicFn);
1193
- }
1194
- function applyLogicFn(type, path, logicFn) {
1195
- switch (type) {
1196
- case 'hidden':
1197
- hidden(path, logicFn);
1198
- break;
1199
- case 'readonly':
1200
- readonly(path, logicFn);
1201
- break;
1202
- case 'disabled':
1203
- disabled(path, logicFn);
1204
- break;
1205
- case 'required':
1206
- required(path, { when: logicFn });
1207
- break;
1208
- default: {
1209
- const _exhaustive = type;
1210
- throw new DynamicFormError(`Unhandled state logic type: ${_exhaustive}`);
1211
- }
1212
- }
1213
- }
1214
- function applyMultipleLogic(configs, fieldPath) {
1215
- configs.forEach((config) => applyLogic(config, fieldPath));
1216
- }
1217
-
1218
- function createTypePredicateFunction(predicate, logger) {
1219
- return (value) => {
1220
- try {
1221
- const result = ExpressionParser.evaluate(predicate, { value });
1222
- return Boolean(result);
1223
- }
1224
- catch (error) {
1225
- logger.error('Error evaluating type predicate:', predicate, error);
1226
- return false;
1227
- }
1228
- };
1229
- }
1230
-
1231
- function applySchema(config, fieldPath) {
1232
- const logger = inject(DynamicFormLogger);
1233
- const schemaRegistry = inject(SchemaRegistryService);
1234
- const schema = schemaRegistry.resolveSchema(config.schema);
1235
- if (!schema) {
1236
- const availableSchemas = Array.from(schemaRegistry.getAllSchemas().keys()).join(', ') || '<none>';
1237
- logger.error(`Schema not found: '${config.schema}'. ` +
1238
- `Available schemas: ${availableSchemas}. ` +
1239
- `Ensure the schema is registered in your schema registry.`);
1240
- return;
1241
- }
1242
- const schemaFn = createSchemaFunction(schema, logger);
1243
- const path = fieldPath;
1244
- switch (config.type) {
1245
- case 'apply':
1246
- apply(path, schemaFn);
1247
- break;
1248
- case 'applyWhen':
1249
- if (config.condition) {
1250
- const conditionFn = createLogicFunction(config.condition);
1251
- applyWhen(path, conditionFn, schemaFn);
1252
- }
1253
- break;
1254
- case 'applyWhenValue':
1255
- if (config.typePredicate) {
1256
- const predicate = createTypePredicateFunction(config.typePredicate, logger);
1257
- applyWhenValue(path, predicate, schemaFn);
1258
- }
1259
- break;
1260
- case 'applyEach':
1261
- applyEach(path, schemaFn);
1262
- break;
1263
- }
1264
- }
1265
- function createSchemaFunction(schema, logger) {
1266
- return (path) => {
1267
- schema.validators?.forEach((validatorConfig) => {
1268
- applyValidator(validatorConfig, path);
1269
- });
1270
- schema.logic?.forEach((logicConfig) => {
1271
- applyLogic(logicConfig, path);
1272
- });
1273
- schema.subSchemas?.forEach((subSchemaConfig) => {
1274
- applySchema(subSchemaConfig, path);
1275
- });
1276
- };
1277
- }
1278
-
1279
- function combineAncestorHiddenLogics(logics) {
1280
- if (logics.length === 0)
1281
- return undefined;
1282
- if (logics.length === 1)
1283
- return logics[0];
1284
- return (ctx) => logics.some((fn) => fn(ctx));
1285
- }
1286
- function resolveFieldOwnContext(fieldDef, parentContext) {
1287
- if (fieldDef.validateWhenHidden === undefined)
1288
- return parentContext;
1289
- if (fieldDef.validateWhenHidden === parentContext.validateWhenHidden)
1290
- return parentContext;
1291
- return {
1292
- ...parentContext,
1293
- validateWhenHidden: fieldDef.validateWhenHidden,
1294
- };
1295
- }
1296
- function resolveDescendantContext(fieldDef, ownContext) {
1297
- let ancestorAlwaysHidden = ownContext.ancestorAlwaysHidden;
1298
- let ancestorHiddenLogics = ownContext.ancestorHiddenLogics;
1299
- let mutated = false;
1300
- let ancestorRequired = ownContext.ancestorRequired;
1301
- if (fieldDef.required !== undefined && fieldDef.required !== ancestorRequired) {
1302
- ancestorRequired = fieldDef.required;
1303
- mutated = true;
1304
- }
1305
- if (fieldDef.hidden === true && !ancestorAlwaysHidden) {
1306
- ancestorAlwaysHidden = true;
1307
- mutated = true;
1308
- }
1309
- if (fieldDef.logic) {
1310
- for (const logic of fieldDef.logic) {
1311
- if (!isStateLogicConfig(logic) || logic.type !== 'hidden')
1312
- continue;
1313
- const condition = logic.condition;
1314
- if (condition === true) {
1315
- if (!ancestorAlwaysHidden) {
1316
- ancestorAlwaysHidden = true;
1317
- mutated = true;
1318
- }
1319
- continue;
1320
- }
1321
- if (condition === false)
1322
- continue;
1323
- if (typeof condition === 'string')
1324
- continue;
1325
- const fn = createLogicFunction(condition);
1326
- ancestorHiddenLogics = ancestorHiddenLogics.length === 0 ? [fn] : [...ancestorHiddenLogics, fn];
1327
- mutated = true;
1328
- }
1329
- }
1330
- if (!mutated)
1331
- return ownContext;
1332
- return {
1333
- validateWhenHidden: ownContext.validateWhenHidden,
1334
- ancestorAlwaysHidden,
1335
- ancestorHiddenLogics,
1336
- ancestorRequired,
1337
- };
1338
- }
1339
-
1340
- const DEFAULT_FIELD_TREE_MAPPING_CONTEXT = {
1341
- validateWhenHidden: false,
1342
- ancestorAlwaysHidden: false,
1343
- ancestorHiddenLogics: [],
1344
- };
1345
- function applyStringValidators(path, config) {
1346
- if (config.email) {
1347
- email(path);
1348
- }
1349
- if (config.minLength !== undefined) {
1350
- minLength(path, config.minLength);
1351
- }
1352
- if (config.maxLength !== undefined) {
1353
- maxLength(path, config.maxLength);
1354
- }
1355
- if (config.pattern) {
1356
- let regex;
1357
- if (typeof config.pattern === 'string') {
1358
- try {
1359
- regex = new RegExp(config.pattern);
1360
- }
1361
- catch (e) {
1362
- throw new DynamicFormError(`Invalid regex pattern: '${config.pattern}' — ${e instanceof Error ? e.message : String(e)}`);
1363
- }
1364
- }
1365
- else {
1366
- regex = config.pattern;
1367
- }
1368
- pattern(path, regex);
1369
- }
1370
- }
1371
- function applyNumberValidators(path, config) {
1372
- if (config.min !== undefined) {
1373
- min(path, config.min);
1374
- }
1375
- if (config.max !== undefined) {
1376
- max(path, config.max);
1377
- }
1378
- }
1379
- function getStringValidationConfig(fieldDef) {
1380
- if (!fieldDef.email && fieldDef.minLength === undefined && fieldDef.maxLength === undefined && !fieldDef.pattern) {
1381
- return undefined;
1382
- }
1383
- return {
1384
- email: fieldDef.email,
1385
- minLength: fieldDef.minLength,
1386
- maxLength: fieldDef.maxLength,
1387
- pattern: fieldDef.pattern,
1388
- };
1389
- }
1390
- function getNumberValidationConfig(fieldDef) {
1391
- const minVal = fieldDef.min ?? fieldDef.minValue;
1392
- const maxVal = fieldDef.max ?? fieldDef.maxValue;
1393
- if (minVal === undefined && maxVal === undefined) {
1394
- return undefined;
1395
- }
1396
- return { min: minVal, max: maxVal };
1397
- }
1398
- function mapFieldToForm(fieldDef, fieldPath, inheritedContext = DEFAULT_FIELD_TREE_MAPPING_CONTEXT) {
1399
- const ownContext = resolveFieldOwnContext(fieldDef, inheritedContext);
1400
- if (isPageField(fieldDef) || isRowField(fieldDef) || isGenericContainerField(fieldDef)) {
1401
- const descendantContext = resolveDescendantContext(fieldDef, ownContext);
1402
- mapContainerChildren(fieldDef.fields, fieldPath, descendantContext);
1403
- return;
1404
- }
1405
- if (isGroupField(fieldDef)) {
1406
- const descendantContext = resolveDescendantContext(fieldDef, ownContext);
1407
- applyContainerValidators(fieldDef, fieldPath, descendantContext);
1408
- mapContainerChildren(fieldDef.fields, fieldPath, descendantContext);
1409
- return;
1410
- }
1411
- if (isArrayField(fieldDef)) {
1412
- const descendantContext = resolveDescendantContext(fieldDef, ownContext);
1413
- mapArrayFieldToForm(fieldDef, fieldPath, descendantContext);
1414
- return;
1415
- }
1416
- mapLeafField(fieldDef, fieldPath, ownContext);
1417
- }
1418
- function mapContainerChildren(fields, parentPath, inheritedContext) {
1419
- if (!fields)
1420
- return;
1421
- const pathRecord = parentPath;
1422
- for (const field of fields) {
1423
- if (isPageField(field) || isRowField(field) || isGenericContainerField(field)) {
1424
- const layoutOwn = resolveFieldOwnContext(field, inheritedContext);
1425
- const layoutDescendant = resolveDescendantContext(field, layoutOwn);
1426
- mapContainerChildren(field.fields, parentPath, layoutDescendant);
1427
- continue;
1428
- }
1429
- if (!field.key)
1430
- continue;
1431
- const childPath = pathRecord[field.key];
1432
- if (childPath) {
1433
- mapFieldToForm(field, childPath, inheritedContext);
1434
- }
1435
- }
1436
- }
1437
- function isValidationStateLogic(config) {
1438
- return isStateLogicConfig(config) && config.type === 'required';
1439
- }
1440
- function mapLeafField(fieldDef, fieldPath, context) {
1441
- const validationField = fieldDef;
1442
- const path = fieldPath;
1443
- applyFieldState(fieldDef, path);
1444
- if (validationField.logic) {
1445
- for (const config of validationField.logic) {
1446
- if (!isValidationStateLogic(config)) {
1447
- applyLogic(config, fieldPath);
1448
- }
1449
- }
1450
- }
1451
- if (validationField.schemas) {
1452
- for (const config of validationField.schemas) {
1453
- applySchema(config, fieldPath);
1454
- }
1455
- }
1456
- const applyValidation = (validationPath) => {
1457
- applySimpleValidationRules(validationField, validationPath, context);
1458
- if (validationField.validators) {
1459
- for (const config of validationField.validators) {
1460
- applyValidator(config, validationPath);
1461
- }
1462
- }
1463
- if (validationField.logic) {
1464
- for (const config of validationField.logic) {
1465
- if (isValidationStateLogic(config)) {
1466
- applyLogic(config, validationPath);
1467
- }
1468
- }
1469
- }
1470
- };
1471
- applyHiddenGatedValidation(path, context, applyValidation);
1472
- }
1473
- function applyHiddenGatedValidation(path, context, apply) {
1474
- if (context.validateWhenHidden) {
1475
- apply(path);
1476
- return;
1477
- }
1478
- if (context.ancestorAlwaysHidden) {
1479
- return;
1480
- }
1481
- const ancestorHiddenLogic = combineAncestorHiddenLogics(context.ancestorHiddenLogics);
1482
- applyWhen(path, (ctx) => {
1483
- if (ctx.state.hidden())
1484
- return false;
1485
- if (ancestorHiddenLogic && ancestorHiddenLogic(ctx))
1486
- return false;
1487
- return true;
1488
- }, schema((subPath) => apply(subPath)));
1489
- }
1490
- function applyContainerValidators(fieldDef, fieldPath, context) {
1491
- const validators = fieldDef.validators;
1492
- if (!validators || validators.length === 0)
1493
- return;
1494
- applyHiddenGatedValidation(fieldPath, context, (validationPath) => {
1495
- for (const config of validators) {
1496
- applyValidator(config, validationPath, 'tree');
1497
- }
1498
- });
1499
- }
1500
- function applySimpleValidationRules(fieldDef, path, context) {
1501
- const declaresRequiredLogic = fieldDef.logic?.some(isValidationStateLogic) ?? false;
1502
- if (fieldDef.required ?? (declaresRequiredLogic ? false : context.ancestorRequired)) {
1503
- required(path);
1504
- }
1505
- const stringConfig = getStringValidationConfig(fieldDef);
1506
- if (stringConfig) {
1507
- applyStringValidators(path, stringConfig);
1508
- }
1509
- const numberConfig = getNumberValidationConfig(fieldDef);
1510
- if (numberConfig) {
1511
- applyNumberValidators(path, numberConfig);
1512
- }
1513
- }
1514
- function applyFieldState(fieldDef, path) {
1515
- if (fieldDef.disabled) {
1516
- disabled(path);
1517
- }
1518
- if (fieldDef.readonly) {
1519
- readonly(path);
1520
- }
1521
- if (fieldDef.hidden) {
1522
- hidden(path, () => true);
1523
- }
1524
- }
1525
- function mapArrayFieldToForm(arrayField, fieldPath, context) {
1526
- if (!isArrayField(arrayField)) {
1527
- return;
824
+ getSchema(name) {
825
+ return this.registeredSchemas.get(name);
1528
826
  }
1529
- if (arrayField.minLength !== undefined) {
1530
- minLength(fieldPath, arrayField.minLength);
827
+ getAllSchemas() {
828
+ return new Map(this.registeredSchemas);
1531
829
  }
1532
- if (arrayField.maxLength !== undefined) {
1533
- maxLength(fieldPath, arrayField.maxLength);
830
+ clearSchemas() {
831
+ this.registeredSchemas.clear();
1534
832
  }
1535
- applyContainerValidators(arrayField, fieldPath, context);
1536
- let itemDefinitions = arrayField.fields;
1537
- if (!itemDefinitions || itemDefinitions.length === 0) {
1538
- const metadataTemplate = getNormalizedArrayMetadata(arrayField)?.template;
1539
- if (metadataTemplate) {
1540
- itemDefinitions = [
1541
- Array.isArray(metadataTemplate) ? [...metadataTemplate] : metadataTemplate,
1542
- ];
833
+ resolveSchema(schema) {
834
+ if (typeof schema === 'string') {
835
+ return this.getSchema(schema) || null;
1543
836
  }
837
+ return schema;
1544
838
  }
1545
- if (!itemDefinitions || itemDefinitions.length === 0) {
1546
- const emptyItemSchema = schema(() => {
1547
- });
1548
- applyEach(fieldPath, emptyItemSchema);
1549
- 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;
1550
856
  }
1551
- let hasPrimitiveItems = false;
1552
- const allObjectFields = [];
1553
- for (const itemDef of itemDefinitions) {
1554
- if (Array.isArray(itemDef)) {
1555
- collectFieldsFromObjectItem(itemDef, allObjectFields);
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
+ });
881
+ }
1556
882
  }
1557
- else if (isPageField(itemDef) ||
1558
- isRowField(itemDef) ||
1559
- isGenericContainerField(itemDef)) {
1560
- collectFieldsFromObjectItem([itemDef], allObjectFields);
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)));
888
+ }
1561
889
  }
1562
- else {
1563
- hasPrimitiveItems = true;
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
+ });
1564
898
  }
1565
- }
1566
- if (hasPrimitiveItems && allObjectFields.length === 0) {
1567
- const primitiveItemSchema = schema(() => {
1568
- });
1569
- applyEach(fieldPath, primitiveItemSchema);
1570
- return;
1571
- }
1572
- const itemSchema = schema((itemPath) => {
1573
- const pathRecord = itemPath;
1574
- for (const templateField of allObjectFields) {
1575
- if (isRowField(templateField) || isPageField(templateField) || isGenericContainerField(templateField)) {
1576
- const layoutOwn = resolveFieldOwnContext(templateField, context);
1577
- const layoutDescendant = resolveDescendantContext(templateField, layoutOwn);
1578
- mapContainerChildren(templateField.fields, itemPath, layoutDescendant);
1579
- }
1580
- else if (isGroupField(templateField)) {
1581
- const groupKey = templateField.key;
1582
- if (groupKey) {
1583
- const groupPath = pathRecord[groupKey];
1584
- if (groupPath) {
1585
- mapFieldToForm(templateField, groupPath, context);
1586
- }
1587
- }
1588
- else {
1589
- const groupOwn = resolveFieldOwnContext(templateField, context);
1590
- mapContainerChildren(templateField.fields, itemPath, resolveDescendantContext(templateField, groupOwn));
1591
- }
1592
- }
1593
- else {
1594
- const fieldKey = templateField.key;
1595
- if (fieldKey) {
1596
- const fieldPathForKey = pathRecord[fieldKey];
1597
- if (fieldPathForKey) {
1598
- mapFieldToForm(templateField, fieldPathForKey, context);
1599
- }
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];
1600
904
  }
1601
- }
1602
- }
1603
- });
1604
- applyEach(fieldPath, itemSchema);
1605
- }
1606
- function collectFieldsFromObjectItem(itemFields, allFields) {
1607
- const seenKeys = new Set(allFields.map((f) => f.key).filter(Boolean));
1608
- for (const field of itemFields) {
1609
- if (isRowField(field) || isPageField(field) || isGenericContainerField(field)) {
1610
- const containerKey = `__container_${field.type}_${JSON.stringify(field.fields?.map((f) => f.key))}`;
1611
- if (!seenKeys.has(containerKey)) {
1612
- seenKeys.add(containerKey);
1613
- allFields.push(field);
1614
- }
905
+ return flattenFields([...itemDef], registry, options);
906
+ });
907
+ result.push({
908
+ ...field,
909
+ fields: flattenedItemTemplates,
910
+ key: field.key || `auto_array_${autoKeyCounter++}`,
911
+ });
1615
912
  }
1616
913
  else {
1617
- const key = field.key;
1618
- if (key && !seenKeys.has(key)) {
1619
- seenKeys.add(key);
1620
- allFields.push(field);
1621
- }
914
+ const key = field.key || `auto_field_${autoKeyCounter++}`;
915
+ result.push({
916
+ ...field,
917
+ key,
918
+ });
1622
919
  }
1623
920
  }
921
+ return result;
1624
922
  }
1625
923
 
1626
924
  function getFieldDefaultValue(field, registry) {
@@ -1726,160 +1024,6 @@ function getFieldDefaultValue(field, registry) {
1726
1024
  return '';
1727
1025
  }
1728
1026
 
1729
- function applyFormLevelSchema(path, formLevelSchema) {
1730
- if (isStandardSchemaMarker(formLevelSchema)) {
1731
- validateStandardSchema(path, formLevelSchema.schema);
1732
- }
1733
- else if (typeof formLevelSchema === 'function') {
1734
- formLevelSchema(path);
1735
- }
1736
- }
1737
- function createFormLevelSchema(formLevelSchema) {
1738
- return schema((path) => {
1739
- applyFormLevelSchema(path, formLevelSchema);
1740
- });
1741
- }
1742
-
1743
- const VALIDATION_EXECUTION_DEFAULTS = new InjectionToken('VALIDATION_EXECUTION_DEFAULTS', {
1744
- providedIn: 'root',
1745
- factory: () => ({
1746
- validateWhenHidden: false,
1747
- }),
1748
- });
1749
-
1750
- function createSchemaFromFields(fields, registry, options = {}) {
1751
- const validationExecutionDefaults = inject(VALIDATION_EXECUTION_DEFAULTS);
1752
- const { formLevelSchema } = options;
1753
- const rootContext = {
1754
- validateWhenHidden: options.validateWhenHidden ?? validationExecutionDefaults.validateWhenHidden,
1755
- ancestorAlwaysHidden: false,
1756
- ancestorHiddenLogics: [],
1757
- };
1758
- return schema((path) => {
1759
- for (const fieldDef of fields) {
1760
- const valueHandling = getFieldValueHandling(fieldDef.type, registry);
1761
- if (valueHandling === 'exclude') {
1762
- continue;
1763
- }
1764
- if (valueHandling === 'flatten' && hasChildFields(fieldDef)) {
1765
- const flattenedOwn = resolveFieldOwnContext(fieldDef, rootContext);
1766
- const flattenedContext = resolveDescendantContext(fieldDef, flattenedOwn);
1767
- for (const childField of normalizeFieldsArray(fieldDef.fields)) {
1768
- if (!childField.key)
1769
- continue;
1770
- const childPath = path[childField.key];
1771
- if (childPath) {
1772
- mapFieldToForm(childField, childPath, flattenedContext);
1773
- }
1774
- }
1775
- continue;
1776
- }
1777
- const fieldPath = path[fieldDef.key];
1778
- if (!fieldPath) {
1779
- continue;
1780
- }
1781
- mapFieldToForm(fieldDef, fieldPath, rootContext);
1782
- }
1783
- if (formLevelSchema) {
1784
- applyFormLevelSchema(path, formLevelSchema);
1785
- }
1786
- });
1787
- }
1788
- function fieldsToDefaultValues(fields, registry) {
1789
- const defaultValues = {};
1790
- for (const field of fields) {
1791
- if (!field.key)
1792
- continue;
1793
- const valueHandling = getFieldValueHandling(field.type, registry);
1794
- if (valueHandling === 'flatten') {
1795
- continue;
1796
- }
1797
- const value = getFieldDefaultValue(field, registry);
1798
- if (value !== undefined) {
1799
- defaultValues[field.key] = value;
1800
- }
1801
- }
1802
- return defaultValues;
1803
- }
1804
-
1805
- function inheritContainerExclusion(container, child) {
1806
- const inheritedHidden = container.hidden === true || child.inheritedExclusionState?.hidden === true;
1807
- const inheritedDisabled = container.disabled === true || child.inheritedExclusionState?.disabled === true;
1808
- const inheritedReadonly = container.readonly === true || child.inheritedExclusionState?.readonly === true;
1809
- const hasInheritedState = inheritedHidden || inheritedDisabled || inheritedReadonly;
1810
- const containerExcludeConfig = container.excludeValueIfHidden !== undefined ||
1811
- container.excludeValueIfDisabled !== undefined ||
1812
- container.excludeValueIfReadonly !== undefined;
1813
- if (!hasInheritedState && !containerExcludeConfig) {
1814
- return child;
1815
- }
1816
- return {
1817
- ...child,
1818
- ...(hasInheritedState
1819
- ? { inheritedExclusionState: { hidden: inheritedHidden, disabled: inheritedDisabled, readonly: inheritedReadonly } }
1820
- : {}),
1821
- excludeValueIfHidden: child.excludeValueIfHidden ?? container.excludeValueIfHidden,
1822
- excludeValueIfDisabled: child.excludeValueIfDisabled ?? container.excludeValueIfDisabled,
1823
- excludeValueIfReadonly: child.excludeValueIfReadonly ?? container.excludeValueIfReadonly,
1824
- };
1825
- }
1826
- function flattenFields(fields, registry, options = {}) {
1827
- const result = [];
1828
- let autoKeyCounter = 0;
1829
- for (const field of fields) {
1830
- const valueHandling = getFieldValueHandling(field.type, registry);
1831
- if (options.preserveRows && (isRowField(field) || isGenericContainerField(field))) {
1832
- if (field.fields) {
1833
- const flattenedChildren = flattenFields(normalizeFieldsArray(field.fields), registry, options);
1834
- const autoPrefix = isRowField(field) ? 'auto_row' : 'auto_container';
1835
- result.push({
1836
- ...field,
1837
- fields: flattenedChildren,
1838
- key: field.key || `${autoPrefix}_${autoKeyCounter++}`,
1839
- });
1840
- }
1841
- }
1842
- else if (valueHandling === 'flatten' && 'fields' in field) {
1843
- if (field.fields) {
1844
- const fields = field.fields;
1845
- const flattenedChildren = flattenFields(normalizeFieldsArray(fields), registry, options);
1846
- result.push(...flattenedChildren.map((child) => inheritContainerExclusion(field, child)));
1847
- }
1848
- }
1849
- else if (isGroupField(field)) {
1850
- const childFieldsArray = Object.values(field.fields);
1851
- const flattenedChildren = flattenFields(childFieldsArray, registry, options);
1852
- result.push({
1853
- ...field,
1854
- fields: flattenedChildren,
1855
- key: field.key || `auto_group_${autoKeyCounter++}`,
1856
- });
1857
- }
1858
- else if (isArrayField(field)) {
1859
- const flattenedItemTemplates = field.fields.map((itemDef) => {
1860
- if (!Array.isArray(itemDef)) {
1861
- const flattened = flattenFields([itemDef], registry, options);
1862
- return flattened[0];
1863
- }
1864
- return flattenFields([...itemDef], registry, options);
1865
- });
1866
- result.push({
1867
- ...field,
1868
- fields: flattenedItemTemplates,
1869
- key: field.key || `auto_array_${autoKeyCounter++}`,
1870
- });
1871
- }
1872
- else {
1873
- const key = field.key || `auto_field_${autoKeyCounter++}`;
1874
- result.push({
1875
- ...field,
1876
- key,
1877
- });
1878
- }
1879
- }
1880
- return result;
1881
- }
1882
-
1883
1027
  function createContainerFieldProcessors() {
1884
1028
  const flattenCache = new WeakMap();
1885
1029
  const memoizedFlattenFields = (fields, registry) => {
@@ -2318,9 +1462,14 @@ const COMPONENT_CACHE = new InjectionToken('COMPONENT_CACHE', {
2318
1462
  providedIn: 'root',
2319
1463
  factory: () => new Map(),
2320
1464
  });
1465
+ const COMPONENT_LOAD_CACHE = new InjectionToken('COMPONENT_LOAD_CACHE', {
1466
+ providedIn: 'root',
1467
+ factory: () => new Map(),
1468
+ });
2321
1469
  function injectFieldRegistry() {
2322
1470
  const registry = inject(FIELD_REGISTRY);
2323
1471
  const componentCache = inject(COMPONENT_CACHE);
1472
+ const componentLoadCache = inject(COMPONENT_LOAD_CACHE);
2324
1473
  return {
2325
1474
  getType(name) {
2326
1475
  return registry.get(name);
@@ -2340,12 +1489,26 @@ function injectFieldRegistry() {
2340
1489
  if (cached) {
2341
1490
  return cached;
2342
1491
  }
1492
+ const pending = componentLoadCache.get(name);
1493
+ if (pending) {
1494
+ return pending;
1495
+ }
2343
1496
  try {
2344
- const component = resolveDefaultExport(await fieldType.loadComponent());
2345
- if (component) {
2346
- componentCache.set(name, component);
2347
- }
2348
- 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;
2349
1512
  }
2350
1513
  catch (error) {
2351
1514
  throw new DynamicFormError(`Failed to load component for field type "${name}": ${error}`);
@@ -2414,7 +1577,9 @@ function validateRegexPattern(pattern, fieldKey, errors) {
2414
1577
  new RegExp(pattern);
2415
1578
  }
2416
1579
  catch (e) {
2417
- 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}'`);
2418
1583
  }
2419
1584
  }
2420
1585
  function validateNoDuplicateKeys(allKeys) {
@@ -2430,7 +1595,9 @@ function validateNoDuplicateKeys(allKeys) {
2430
1595
  const duplicateList = Array.from(duplicates)
2431
1596
  .map((k) => `'${k}'`)
2432
1597
  .join(', ');
2433
- throw new DynamicFormError(`Duplicate field keys detected: ${duplicateList}. Field keys must be unique within their group scope (the same key may appear inside different groups).`);
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}.`);
2434
1601
  }
2435
1602
  }
2436
1603
  function validateNoDomIdCollisions(domIds) {
@@ -2446,9 +1613,11 @@ function validateNoDomIdCollisions(domIds) {
2446
1613
  const duplicateList = Array.from(duplicates)
2447
1614
  .map((k) => `'${k}'`)
2448
1615
  .join(', ');
2449
- throw new DynamicFormError(`DOM id collision detected for: ${duplicateList}. Group-nested keys are joined with '_' to form DOM ids ` +
2450
- `(e.g. group 'foo' + leaf 'bar' renders as id='foo_bar'). A separate top-level key like 'foo_bar' or a different ` +
2451
- `group/leaf pair that underscores to the same string will produce duplicate ids. Rename one of the colliding keys.`);
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}.`);
2452
1621
  }
2453
1622
  }
2454
1623
  function validateFieldTypesRegistered(allTypes, registry, logger) {
@@ -2478,12 +1647,15 @@ function validateFormConfig(fields, registry, logger) {
2478
1647
  const list = Array.from(new Set(data.dottedKeys))
2479
1648
  .map((k) => `'${k}'`)
2480
1649
  .join(', ');
2481
- throw new DynamicFormError(`Field key(s) contain a dot: ${list}. Dotted keys are not supported — express nesting with 'group' fields ` +
2482
- `(e.g. { type: 'group', key: 'address', fields: [{ type: 'input', key: 'city' }] }).`);
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}.`);
2483
1654
  }
2484
1655
  validateNoDuplicateKeys(data.keys);
2485
1656
  validateNoDomIdCollisions(data.domIds);
2486
- validateFieldTypesRegistered(data.types, registry, logger);
1657
+ if (DEV_MODE)
1658
+ validateFieldTypesRegistered(data.types, registry, logger);
2487
1659
  if (data.regexErrors.length > 0) {
2488
1660
  throw new DynamicFormError(data.regexErrors.length === 1
2489
1661
  ? data.regexErrors[0]
@@ -2698,6 +1870,33 @@ function resolveExclusionConfig(global, form, field) {
2698
1870
  excludeValueIfReadonly: field?.excludeValueIfReadonly ?? form?.excludeValueIfReadonly ?? global.excludeValueIfReadonly,
2699
1871
  };
2700
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
+ }
2701
1900
  function shouldExcludeField(field, fieldState, config) {
2702
1901
  const state = fieldState();
2703
1902
  const inherited = field.inheritedExclusionState;
@@ -2715,7 +1914,10 @@ function shouldExcludeField(field, fieldState, config) {
2715
1914
  }
2716
1915
  return false;
2717
1916
  }
2718
- 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
+ }
2719
1921
  const result = {};
2720
1922
  for (const field of schemaFields) {
2721
1923
  const key = field.key;
@@ -2751,7 +1953,7 @@ function filterFormValue(rawValue, schemaFields, formTree, registry, globalDefau
2751
1953
  if (groupValue && typeof groupValue === 'object') {
2752
1954
  const childFields = Object.values(field.fields);
2753
1955
  const nestedTree = fieldState;
2754
- result[key] = filterFormValue(groupValue, childFields, nestedTree, registry, globalDefaults, formOptions);
1956
+ result[key] = filterFormValue(groupValue, childFields, nestedTree, registry, globalDefaults, formOptions, true);
2755
1957
  }
2756
1958
  else {
2757
1959
  result[key] = groupValue;
@@ -3064,7 +2266,7 @@ class FormStateMachine {
3064
2266
  if (!isTransitioningState(state) || state.phase !== Phase.Restoring) {
3065
2267
  return { state, sideEffects: [] };
3066
2268
  }
3067
- if (!state.pendingFormSetup) {
2269
+ if (DEV_MODE && !state.pendingFormSetup) {
3068
2270
  this.config.logger.warn('handleRestoreComplete: pendingFormSetup was not set — falling back to recomputing. ' +
3069
2271
  'This indicates a bug in the transition flow.');
3070
2272
  }
@@ -3127,7 +2329,7 @@ class FormStateMachine {
3127
2329
  const state = untracked(() => this._state());
3128
2330
  if (!isTransitioningState(state) || state.phase !== Phase.Restoring)
3129
2331
  return;
3130
- if (!state.pendingFormSetup) {
2332
+ if (DEV_MODE && !state.pendingFormSetup) {
3131
2333
  this.config.logger.warn('RestoreValues effect: pendingFormSetup was not set — falling back to recomputing.');
3132
2334
  }
3133
2335
  const formSetup = state.pendingFormSetup ?? this.config.createFormSetup(state.pendingConfig);
@@ -3269,13 +2471,6 @@ class FormStateManager {
3269
2471
  return detectFormMode(config.fields || []);
3270
2472
  }, /* @ts-ignore */
3271
2473
  ...(ngDevMode ? [{ debugName: "formModeDetection" }] : /* istanbul ignore next */ []));
3272
- formConfigValidation = computed(() => {
3273
- const config = this.activeConfig();
3274
- if (!config)
3275
- return { isValid: true, errors: [], warnings: [] };
3276
- return FormModeValidator.validateFormConfiguration(config.fields || []);
3277
- }, /* @ts-ignore */
3278
- ...(ngDevMode ? [{ debugName: "formConfigValidation" }] : /* istanbul ignore next */ []));
3279
2474
  effectiveFormOptions = computed(() => {
3280
2475
  const config = this.activeConfig();
3281
2476
  const configOptions = config?.options || {};
@@ -3283,6 +2478,8 @@ class FormStateManager {
3283
2478
  return { ...configOptions, ...inputOptions };
3284
2479
  }, /* @ts-ignore */
3285
2480
  ...(ngDevMode ? [{ debugName: "effectiveFormOptions" }] : /* istanbul ignore next */ []));
2481
+ pendingPageRequest = signal(null, /* @ts-ignore */
2482
+ ...(ngDevMode ? [{ debugName: "pendingPageRequest" }] : /* istanbul ignore next */ []));
3286
2483
  pageFieldDefinitions = computed(() => {
3287
2484
  const config = this.activeConfig();
3288
2485
  const mode = this.formModeDetection().mode;
@@ -3319,22 +2516,71 @@ class FormStateManager {
3319
2516
  }
3320
2517
  return this.createEmptyFormSetup(registry);
3321
2518
  }, /* @ts-ignore */
3322
- ...(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 }) });
3323
2539
  defaultValues = linkedSignal(() => this.formSetup().defaultValues, /* @ts-ignore */
3324
2540
  ...(ngDevMode ? [{ debugName: "defaultValues" }] : /* istanbul ignore next */ []));
3325
2541
  validKeys = computed(() => {
3326
2542
  const schemaFields = this.formSetup().schemaFields;
3327
2543
  if (!schemaFields || schemaFields.length === 0)
3328
2544
  return undefined;
3329
- 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;
3330
2552
  }, /* @ts-ignore */
3331
2553
  ...(ngDevMode ? [{ debugName: "validKeys" }] : /* istanbul ignore next */ []));
3332
- 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(() => {
3333
2567
  const setup = this.formSetup();
3334
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)
3335
2581
  return {};
3336
2582
  const snapshot = {};
3337
- collectFieldStateSnapshot(setup.schemaFields, asFieldTreeRecord(this.form()), [], snapshot);
2583
+ collectFieldStateSnapshot(schemaFields, asFieldTreeRecord(this.form()), [], snapshot);
3338
2584
  return snapshot;
3339
2585
  }, /* @ts-ignore */
3340
2586
  ...(ngDevMode ? [{ debugName: "fieldStateSnapshot" }] : /* istanbul ignore next */ []));
@@ -3342,8 +2588,22 @@ class FormStateManager {
3342
2588
  ...(ngDevMode ? [{ debugName: "excludedValueStore" }] : /* istanbul ignore next */ []));
3343
2589
  prevFieldStateSnapshot = signal({}, /* @ts-ignore */
3344
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 */ []));
3345
2604
  entity = linkedSignal(() => {
3346
- const inputValue = this.deps.value();
2605
+ this.externalValue();
2606
+ const inputValue = untracked(() => this.deps.value());
3347
2607
  const defaults = this.defaultValues();
3348
2608
  const keys = this.validKeys();
3349
2609
  const saved = this.excludedValueStore();
@@ -3363,22 +2623,62 @@ class FormStateManager {
3363
2623
  debugName: 'FormStateManager.entity',
3364
2624
  equal: isEqual,
3365
2625
  });
3366
- formSchema = computed(() => runInInjectionContext(this.injector, () => {
2626
+ loadFormSchemaService = injectAsync(() => import('./ng-forge-dynamic-forms-form-schema.service-C3cVSmft.mjs'));
2627
+ formSchemaRequest = computed(() => {
3367
2628
  const setup = this.formSetup();
3368
2629
  const config = this.activeConfig();
3369
- 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;
3370
2650
  return undefined;
3371
- if (setup.schemaFields?.length) {
3372
- return createSchemaFromFields(setup.schemaFields, setup.registry, {
3373
- formLevelSchema: config.schema,
3374
- validateWhenHidden: this.effectiveFormOptions().validateWhenHidden,
3375
- });
3376
2651
  }
3377
- if (config.schema) {
3378
- return createFormLevelSchema(config.schema);
2652
+ if (this.formSchemaResource.status() === 'error') {
2653
+ this.schemaCache.current = undefined;
2654
+ return undefined;
3379
2655
  }
3380
- return undefined;
3381
- }), /* @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 */
3382
2682
  ...(ngDevMode ? [{ debugName: "formSchema" }] : /* istanbul ignore next */ []));
3383
2683
  form = computed(() => {
3384
2684
  const schema = this.formSchema();
@@ -3409,13 +2709,16 @@ class FormStateManager {
3409
2709
  ...(ngDevMode ? [{ debugName: "fieldSignalContext" }] : /* istanbul ignore next */ []));
3410
2710
  formInstance = computed(() => this.form()(), /* @ts-ignore */
3411
2711
  ...(ngDevMode ? [{ debugName: "formInstance" }] : /* istanbul ignore next */ []));
3412
- 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 */
3413
2715
  ...(ngDevMode ? [{ debugName: "formValue" }] : /* istanbul ignore next */ []));
3414
2716
  filteredFormValue = computed(() => {
3415
- const rawValue = this.formValue();
2717
+ const rawValue = this.modelValue();
3416
2718
  const setup = this.formSetup();
2719
+ const schemaFields = setup.schemaFields;
3417
2720
  const options = this.effectiveFormOptions();
3418
- if (!setup.schemaFields || setup.schemaFields.length === 0) {
2721
+ if (!this.formSchemaReady() || !schemaFields || schemaFields.length === 0) {
3419
2722
  return rawValue;
3420
2723
  }
3421
2724
  const formTree = this.form();
@@ -3425,14 +2728,15 @@ class FormStateManager {
3425
2728
  excludeValueIfReadonly: options.excludeValueIfReadonly,
3426
2729
  };
3427
2730
  const fieldTreeRecord = asFieldTreeRecord(formTree);
3428
- return filterFormValue(rawValue, setup.schemaFields, fieldTreeRecord, setup.registry, this.valueExclusionDefaults, formOptions);
2731
+ return filterFormValue(rawValue, schemaFields, fieldTreeRecord, setup.registry, this.valueExclusionDefaults, formOptions, this.valueExclusionEnabled());
3429
2732
  }, /* @ts-ignore */
3430
2733
  ...(ngDevMode ? [{ debugName: "filteredFormValue" }] : /* istanbul ignore next */ []));
3431
2734
  boundFormValue = computed(() => {
3432
- const rawValue = this.formValue();
2735
+ const rawValue = this.modelValue();
3433
2736
  const setup = this.formSetup();
2737
+ const schemaFields = setup.schemaFields;
3434
2738
  const options = this.effectiveFormOptions();
3435
- if (!setup.schemaFields || setup.schemaFields.length === 0) {
2739
+ if (!this.formSchemaReady() || !schemaFields || schemaFields.length === 0) {
3436
2740
  return rawValue;
3437
2741
  }
3438
2742
  const formTree = this.form();
@@ -3442,26 +2746,26 @@ class FormStateManager {
3442
2746
  excludeValueIfReadonly: options.excludeValueIfReadonly,
3443
2747
  };
3444
2748
  const fieldTreeRecord = asFieldTreeRecord(formTree);
3445
- 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());
3446
2750
  }, /* @ts-ignore */
3447
2751
  ...(ngDevMode ? [{ debugName: "boundFormValue" }] : /* istanbul ignore next */ []));
3448
- valid = computed(() => this.formInstance().valid(), /* @ts-ignore */
2752
+ valid = computed(() => this.formSchemaReady() && this.formSchemaResource.status() !== 'error' && this.formInstance().valid(), /* @ts-ignore */
3449
2753
  ...(ngDevMode ? [{ debugName: "valid" }] : /* istanbul ignore next */ []));
3450
- invalid = computed(() => this.formInstance().invalid(), /* @ts-ignore */
2754
+ invalid = computed(() => !this.formSchemaReady() || this.formSchemaResource.status() === 'error' || this.formInstance().invalid(), /* @ts-ignore */
3451
2755
  ...(ngDevMode ? [{ debugName: "invalid" }] : /* istanbul ignore next */ []));
3452
- dirty = computed(() => this.formInstance().dirty(), /* @ts-ignore */
2756
+ dirty = computed(() => this.formSchemaReady() && this.formInstance().dirty(), /* @ts-ignore */
3453
2757
  ...(ngDevMode ? [{ debugName: "dirty" }] : /* istanbul ignore next */ []));
3454
- touched = computed(() => this.formInstance().touched(), /* @ts-ignore */
2758
+ touched = computed(() => this.formSchemaReady() && this.formInstance().touched(), /* @ts-ignore */
3455
2759
  ...(ngDevMode ? [{ debugName: "touched" }] : /* istanbul ignore next */ []));
3456
- errors = computed(() => this.formInstance().errors(), /* @ts-ignore */
2760
+ errors = computed(() => (this.formSchemaReady() ? this.formInstance().errors() : []), /* @ts-ignore */
3457
2761
  ...(ngDevMode ? [{ debugName: "errors" }] : /* istanbul ignore next */ []));
3458
2762
  disabled = computed(() => {
3459
2763
  const optionsDisabled = this.effectiveFormOptions().disabled;
3460
- const formDisabled = this.formInstance().disabled();
2764
+ const formDisabled = this.formSchemaReady() && this.formInstance().disabled();
3461
2765
  return optionsDisabled ?? formDisabled;
3462
2766
  }, /* @ts-ignore */
3463
2767
  ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
3464
- submitting = computed(() => this.formInstance().submitting(), /* @ts-ignore */
2768
+ submitting = computed(() => this.formSchemaReady() && this.formInstance().submitting(), /* @ts-ignore */
3465
2769
  ...(ngDevMode ? [{ debugName: "submitting" }] : /* istanbul ignore next */ []));
3466
2770
  fieldsSource = computed(() => {
3467
2771
  const state = this.machine.state();
@@ -3576,6 +2880,10 @@ class FormStateManager {
3576
2880
  }), takeUntilDestroyed(this.destroyRef));
3577
2881
  this.reset$ = this.eventBus.on('form-reset').pipe(map(() => undefined), takeUntilDestroyed(this.destroyRef));
3578
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 }));
3579
2887
  this.machine = createFormStateMachine({
3580
2888
  injector: this.injector,
3581
2889
  destroyRef: this.destroyRef,
@@ -3639,6 +2947,11 @@ class FormStateManager {
3639
2947
  this.eventBus.dispatch(FormSubmitEvent);
3640
2948
  }
3641
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
+ });
3642
2955
  explicitEffect([this.deps.config], ([config]) => {
3643
2956
  this.fieldLoadingErrors.set([]);
3644
2957
  const state = this.machine.currentState;
@@ -3690,17 +3003,24 @@ class FormStateManager {
3690
3003
  explicitEffect([this.boundFormValue], ([currentBound]) => {
3691
3004
  const currentValue = this.deps.value();
3692
3005
  if (!isEqual(currentBound, currentValue)) {
3006
+ this.lastOutwardValue.current = currentBound;
3007
+ this.lastOutwardValue.pending = true;
3693
3008
  this.deps.value.set(currentBound);
3694
3009
  }
3695
3010
  });
3696
- explicitEffect([this.formConfigValidation], ([validation]) => {
3697
- if (!validation.isValid) {
3698
- this.logger.error('Invalid form configuration:', validation.errors);
3699
- }
3700
- if (validation.warnings.length > 0) {
3701
- this.logger.warn('Form configuration warnings:', validation.warnings);
3702
- }
3703
- });
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
+ }
3704
3024
  }
3705
3025
  setupEventHandlers() {
3706
3026
  this.eventBus
@@ -3718,74 +3038,479 @@ class FormStateManager {
3718
3038
  this.schemaRegistry.registerSchema(schema);
3719
3039
  });
3720
3040
  }
3721
- if (!customFnConfig) {
3722
- 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
+ };
3723
3309
  }
3724
- if (customFnConfig.customFunctions) {
3725
- Object.entries(customFnConfig.customFunctions).forEach(([name, fn]) => {
3726
- this.functionRegistry.registerCustomFunction(name, fn);
3727
- });
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
+ };
3728
3317
  }
3729
- if (customFnConfig.derivations) {
3730
- 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);
3731
3326
  }
3732
- if (customFnConfig.asyncDerivations) {
3733
- this.functionRegistry.setAsyncDerivationFunctions(customFnConfig.asyncDerivations);
3327
+ if (options?.validate === false) {
3328
+ return this.executePageChange(pageIndex);
3734
3329
  }
3735
- if (customFnConfig.asyncConditions) {
3736
- this.functionRegistry.setAsyncConditionFunctions(customFnConfig.asyncConditions);
3330
+ const disableWhenPageInvalid = this.formOptions?.()?.nextButton?.disableWhenPageInvalid ?? true;
3331
+ if (!disableWhenPageInvalid) {
3332
+ return this.executePageChange(pageIndex);
3737
3333
  }
3738
- this.functionRegistry.setValidators(customFnConfig.validators);
3739
- this.functionRegistry.setAsyncValidators(customFnConfig.asyncValidators);
3740
- this.functionRegistry.setHttpValidators(customFnConfig.httpValidators);
3741
- }
3742
- createFormSetupFromConfig(fields, mode, registry) {
3743
- const normalizedFields = normalizeSimplifiedArrays(fields);
3744
- validateFormConfig(normalizedFields, registry, this.logger);
3745
- const source = this.deps.source?.() ?? 'inline';
3746
- const { fields: sanitizedFields, warnings: addonWarnings } = walkAndValidateAddons(normalizedFields, registry, this.addonKindRegistry, source);
3747
- const nextKeys = new Set();
3748
- for (const w of addonWarnings) {
3749
- const key = addonWarningKey(w);
3750
- nextKeys.add(key);
3751
- if (!this.lastAddonWarningKeys.has(key)) {
3752
- this.logger.warn(formatAddonWarning(w));
3753
- }
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);
3754
3338
  }
3755
- this.lastAddonWarningKeys = nextKeys;
3756
- const validatedFields = sanitizedFields;
3757
- const flattenedFields = this.fieldProcessors.memoizedFlattenFields(validatedFields, registry);
3758
- const flattenedFieldsForRendering = this.fieldProcessors.memoizedFlattenFieldsForRendering(validatedFields, registry);
3759
- const fieldsById = this.fieldProcessors.memoizedKeyBy(flattenedFields);
3760
- const defaultValues = this.fieldProcessors.memoizedDefaultValues(fieldsById, registry);
3761
- const fieldsToRender = mode === 'paged' ? [] : flattenedFieldsForRendering;
3339
+ this.executePageChange(firstInvalidPage);
3762
3340
  return {
3763
- fields: fieldsToRender,
3764
- schemaFields: flattenedFields,
3765
- originalFields: fields,
3766
- defaultValues,
3767
- mode,
3768
- registry,
3341
+ success: false,
3342
+ newPageIndex: firstInvalidPage,
3343
+ error: `Cannot navigate to page ${pageIndex}: page ${firstInvalidPage} has invalid fields`,
3769
3344
  };
3770
3345
  }
3771
- 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);
3772
3373
  return {
3773
- fields: [],
3774
- schemaFields: [],
3775
- defaultValues: {},
3776
- mode: 'non-paged',
3777
- registry,
3374
+ success: true,
3375
+ newPageIndex: pageIndex,
3778
3376
  };
3779
3377
  }
3780
- ngOnDestroy() {
3781
- 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;
3782
3389
  }
3783
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: FormStateManager, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3784
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: FormStateManager });
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)]] });
3785
3471
  }
3786
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: FormStateManager, decorators: [{
3787
- type: Injectable
3788
- }], 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 }] }] } }) });
3789
3514
 
3790
3515
  function traverseFieldsWithContext(fields, context, visitor, hooks) {
3791
3516
  for (const field of fields) {
@@ -3864,7 +3589,7 @@ function createDerivationRenderGate() {
3864
3589
  if (!has || engine() !== 'idle')
3865
3590
  return;
3866
3591
  engine.set('loading');
3867
- pendingTasks.run(() => import('./ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-CNCeWAeJ.mjs')
3592
+ pendingTasks.run(() => import('./ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-BfxYqAQA.mjs')
3868
3593
  .then(({ bootstrapDerivationOrchestrator }) => {
3869
3594
  runInInjectionContext(injector, () => bootstrapDerivationOrchestrator());
3870
3595
  engine.set('wired');
@@ -4171,22 +3896,30 @@ class DynamicForm {
4171
3896
  submitting = this.stateManager.submitting;
4172
3897
  fieldLoadingErrors = this.stateManager.fieldLoadingErrors;
4173
3898
  derivationReady = inject(DERIVATION_RENDER_GATE);
4174
- shouldRender = computed(() => this.stateManager.shouldRender() && this.derivationReady(), /* @ts-ignore */
3899
+ shouldRender = computed(() => this.stateManager.shouldRender() && this.stateManager.formSchemaReady() && this.derivationReady(), /* @ts-ignore */
4175
3900
  ...(ngDevMode ? [{ debugName: "shouldRender" }] : /* istanbul ignore next */ []));
4176
3901
  resolvedFields = this.stateManager.resolvedFields;
4177
3902
  fieldWindowing = computed(() => resolveFieldWindowing(this.globalFieldWindowing, this.effectiveFormOptions()?.fieldWindowing), /* @ts-ignore */
4178
3903
  ...(ngDevMode ? [{ debugName: "fieldWindowing" }] : /* istanbul ignore next */ []));
4179
- totalComponentsCount = computed(() => {
4180
- const fields = this.stateManager.formSetup()?.fields ?? [];
4181
- 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));
4182
3920
  }, /* @ts-ignore */
4183
- ...(ngDevMode ? [{ debugName: "totalComponentsCount" }] : /* istanbul ignore next */ []));
4184
- initialized$ = setupInitializationTracking({
4185
- eventBus: this.eventBus,
4186
- totalComponentsCount: this.totalComponentsCount,
4187
- injector: this.injector,
4188
- componentId: this.componentId,
4189
- });
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 }));
4190
3923
  validityChange = outputFromObservable(toObservable(this.valid));
4191
3924
  dirtyChange = outputFromObservable(toObservable(this.dirty));
4192
3925
  submitted = outputFromObservable(this.stateManager.submitted$);
@@ -4194,9 +3927,27 @@ class DynamicForm {
4194
3927
  cleared = outputFromObservable(this.eventBus.on('form-clear'));
4195
3928
  events = outputFromObservable(this.eventBus.events$);
4196
3929
  initialized = outputFromObservable(this.initialized$);
3930
+ activePageInitialized = outputFromObservable(this.eventBus.on('active-page-initialized'));
4197
3931
  onPageChange = outputFromObservable(this.eventBus.on('page-change'));
4198
3932
  onPageNavigationStateChange = outputFromObservable(this.eventBus.on('pager-state'));
4199
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));
4200
3951
  this.dispatcher?.connect(this.eventBus);
4201
3952
  this.destroyRef.onDestroy(() => this.dispatcher?.disconnect());
4202
3953
  inject(FORM_INITIALIZER, { optional: true });
@@ -4221,8 +3972,8 @@ class DynamicForm {
4221
3972
  this.eventBus.dispatch(ComponentInitializedEvent, 'dynamic-form', this.componentId);
4222
3973
  }
4223
3974
  });
4224
- explicitEffect([this.resolvedFields, this.formModeDetection], ([fields, { mode }]) => {
4225
- if (mode === 'non-paged' && fields.length > 0) {
3975
+ explicitEffect([this.resolvedFields, this.formModeDetection, this.activeConfig], ([, { mode }, config]) => {
3976
+ if (config && mode === 'non-paged') {
4226
3977
  afterNextRender(() => {
4227
3978
  this.eventBus.dispatch(ComponentInitializedEvent, 'dynamic-form', this.componentId);
4228
3979
  }, { injector: this.injector });
@@ -4258,7 +4009,7 @@ class DynamicForm {
4258
4009
  });
4259
4010
  }
4260
4011
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: DynamicForm, deps: [], target: i0.ɵɵFactoryTarget.Component });
4261
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", type: DynamicForm, isStandalone: true, selector: "form[dynamic-form]", inputs: { config: { classPropertyName: "config", publicName: "dynamic-form", isSignal: true, isRequired: true, transformFunction: null }, formOptions: { classPropertyName: "formOptions", publicName: "formOptions", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, source: { classPropertyName: "source", publicName: "source", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", validityChange: "validityChange", dirtyChange: "dirtyChange", submitted: "submitted", reset: "reset", cleared: "cleared", events: "events", initialized: "initialized", onPageChange: "onPageChange", onPageNavigationStateChange: "onPageNavigationStateChange" }, host: { attributes: { "novalidate": "" }, listeners: { "submit": "onNativeSubmit($event)" }, properties: { "class.disabled": "disabled()", "class.df-form-paged": "formModeDetection().mode === \"paged\"", "class.df-form-non-paged": "formModeDetection().mode === \"non-paged\"", "attr.data-form-mode": "formModeDetection().mode", "attr.id": "idPrefix() || null" }, classAttribute: "df-dynamic-form df-form" }, providers: [
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: [
4262
4013
  provideDynamicFormDI(),
4263
4014
  DfFieldTemplateRegistry,
4264
4015
  { provide: DF_FIELD_TEMPLATES, useFactory: () => inject(DfFieldTemplateRegistry).map },
@@ -4270,12 +4021,8 @@ class DynamicForm {
4270
4021
  }
4271
4022
  @case ('non-paged') {
4272
4023
  @for (field of resolvedFields(); track field.key; let i = $index) {
4273
- <!-- @if + DfFieldOutlet's own renderReady-&-!hidden gate together silence NG01916:
4274
- the template @if removes the host from the DOM (Angular's prescribed pattern), and
4275
- the directive's gate stops a same-CD-pass mount before [formField] can warn.
4276
- Don't dedupe one without the other — see DfFieldOutlet.renderReady. -->
4277
- @if (!field.hidden()) {
4278
- @if (windowsField(field, i)) {
4024
+ @if (windowsField(field, i)) {
4025
+ @if (!field.hidden()) {
4279
4026
  @defer (on viewport) {
4280
4027
  <ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
4281
4028
  } @placeholder {
@@ -4286,9 +4033,11 @@ class DynamicForm {
4286
4033
  aria-hidden="true"
4287
4034
  ></div>
4288
4035
  }
4289
- } @else {
4290
- <ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
4291
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" />
4292
4041
  }
4293
4042
  }
4294
4043
  }
@@ -4309,12 +4058,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
4309
4058
  }
4310
4059
  @case ('non-paged') {
4311
4060
  @for (field of resolvedFields(); track field.key; let i = $index) {
4312
- <!-- @if + DfFieldOutlet's own renderReady-&-!hidden gate together silence NG01916:
4313
- the template @if removes the host from the DOM (Angular's prescribed pattern), and
4314
- the directive's gate stops a same-CD-pass mount before [formField] can warn.
4315
- Don't dedupe one without the other — see DfFieldOutlet.renderReady. -->
4316
- @if (!field.hidden()) {
4317
- @if (windowsField(field, i)) {
4061
+ @if (windowsField(field, i)) {
4062
+ @if (!field.hidden()) {
4318
4063
  @defer (on viewport) {
4319
4064
  <ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
4320
4065
  } @placeholder {
@@ -4325,9 +4070,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
4325
4070
  aria-hidden="true"
4326
4071
  ></div>
4327
4072
  }
4328
- } @else {
4329
- <ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
4330
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" />
4331
4078
  }
4332
4079
  }
4333
4080
  }
@@ -4349,29 +4096,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
4349
4096
  '[attr.id]': 'idPrefix() || null',
4350
4097
  '(submit)': 'onNativeSubmit($event)',
4351
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"] }]
4352
- }], ctorParameters: () => [], propDecorators: { config: [{ type: i0.Input, args: [{ isSignal: true, alias: "dynamic-form", required: true }] }], formOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "formOptions", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], source: [{ type: i0.Input, args: [{ isSignal: true, alias: "source", required: false }] }], _projectedTemplates: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => DfTemplate), { isSignal: true }] }], validityChange: [{ type: i0.Output, args: ["validityChange"] }], dirtyChange: [{ type: i0.Output, args: ["dirtyChange"] }], submitted: [{ type: i0.Output, args: ["submitted"] }], reset: [{ type: i0.Output, args: ["reset"] }], cleared: [{ type: i0.Output, args: ["cleared"] }], events: [{ type: i0.Output, args: ["events"] }], initialized: [{ type: i0.Output, args: ["initialized"] }], onPageChange: [{ type: i0.Output, args: ["onPageChange"] }], onPageNavigationStateChange: [{ type: i0.Output, args: ["onPageNavigationStateChange"] }] } });
4353
- function countContainersRecursive(fields) {
4354
- let count = 0;
4355
- for (const field of fields) {
4356
- if (isContainerField(field)) {
4357
- count += 1;
4358
- if (hasChildFields(field)) {
4359
- const children = field.fields;
4360
- if (Array.isArray(children)) {
4361
- for (const child of children) {
4362
- if (Array.isArray(child)) {
4363
- count += countContainersRecursive(child);
4364
- }
4365
- else if (child != null && isContainerField(child)) {
4366
- count += countContainersRecursive([child]);
4367
- }
4368
- }
4369
- }
4370
- }
4371
- }
4372
- }
4373
- return count;
4374
- }
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"] }] } });
4375
4100
 
4376
4101
  const BUILT_IN_ADDON_TYPES = [
4377
4102
  {
@@ -4419,28 +4144,28 @@ const DISPLAY_FIELD_TYPES_BASE = {
4419
4144
  const BUILT_IN_FIELDS = [
4420
4145
  {
4421
4146
  name: 'row',
4422
- loadComponent: () => import('./ng-forge-dynamic-forms-container-field.component-ByGdzAsk.mjs'),
4147
+ loadComponent: () => import('./ng-forge-dynamic-forms-container-field.component-Cj2msZp_.mjs'),
4423
4148
  mapper: rowFieldMapper,
4424
4149
  valueHandling: 'flatten',
4425
4150
  ...CONTAINER_FIELD_TYPES_BASE,
4426
4151
  },
4427
4152
  {
4428
4153
  name: 'group',
4429
- loadComponent: () => import('./ng-forge-dynamic-forms-group-field.component-DnLZdj8v.mjs'),
4154
+ loadComponent: () => import('./ng-forge-dynamic-forms-group-field.component-DN6X506r.mjs'),
4430
4155
  mapper: groupFieldMapper,
4431
4156
  valueHandling: 'include',
4432
4157
  ...CONTAINER_FIELD_TYPES_BASE,
4433
4158
  },
4434
4159
  {
4435
4160
  name: 'array',
4436
- loadComponent: () => import('./ng-forge-dynamic-forms-array-field.component-NTHLPjau.mjs'),
4161
+ loadComponent: () => import('./ng-forge-dynamic-forms-array-field.component-DK7dAQv7.mjs'),
4437
4162
  mapper: arrayFieldMapper,
4438
4163
  valueHandling: 'include',
4439
4164
  ...CONTAINER_FIELD_TYPES_BASE,
4440
4165
  },
4441
4166
  {
4442
4167
  name: 'page',
4443
- loadComponent: () => import('./ng-forge-dynamic-forms-page-field.component-CwH22lRl.mjs'),
4168
+ loadComponent: () => import('./ng-forge-dynamic-forms-page-field.component-D16ey8r-.mjs'),
4444
4169
  mapper: pageFieldMapper,
4445
4170
  valueHandling: 'flatten',
4446
4171
  ...CONTAINER_FIELD_TYPES_BASE,
@@ -4458,7 +4183,7 @@ const BUILT_IN_FIELDS = [
4458
4183
  },
4459
4184
  {
4460
4185
  name: 'container',
4461
- loadComponent: () => import('./ng-forge-dynamic-forms-container-field.component-ByGdzAsk.mjs'),
4186
+ loadComponent: () => import('./ng-forge-dynamic-forms-container-field.component-Cj2msZp_.mjs'),
4462
4187
  mapper: containerFieldMapper,
4463
4188
  valueHandling: 'flatten',
4464
4189
  ...CONTAINER_FIELD_TYPES_BASE,
@@ -4553,7 +4278,7 @@ function provideDynamicForm(...items) {
4553
4278
  const logger = inject(DynamicFormLogger);
4554
4279
  const registry = new Map();
4555
4280
  fields.forEach((fieldType) => {
4556
- if (registry.has(fieldType.name)) {
4281
+ if (DEV_MODE && registry.has(fieldType.name)) {
4557
4282
  logger.warn(`Field type "${fieldType.name}" is already registered. Overwriting.`);
4558
4283
  }
4559
4284
  registry.set(fieldType.name, fieldType);
@@ -4581,13 +4306,15 @@ function provideDynamicForm(...items) {
4581
4306
  const registry = new Map();
4582
4307
  const builtInNames = new Set(BUILT_IN_WRAPPERS.map((w) => w.wrapperName));
4583
4308
  BUILT_IN_WRAPPERS.forEach((wrapperType) => registry.set(wrapperType.wrapperName, wrapperType));
4584
- const seenCustom = new Set();
4309
+ const seenCustom = DEV_MODE ? new Set() : null;
4585
4310
  customWrappers.forEach((wrapperType) => {
4586
4311
  const name = wrapperType.wrapperName;
4587
- if (seenCustom.has(name) && !builtInNames.has(name)) {
4588
- logger.warn(`Wrapper type "${name}" is already registered. Overwriting.`);
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);
4589
4317
  }
4590
- seenCustom.add(name);
4591
4318
  registry.set(name, wrapperType);
4592
4319
  });
4593
4320
  return registry;
@@ -4619,7 +4346,7 @@ function provideDynamicForm(...items) {
4619
4346
  registry.set(def.type, def);
4620
4347
  }
4621
4348
  for (const def of contributed) {
4622
- if (registry.has(def.type)) {
4349
+ if (DEV_MODE && registry.has(def.type)) {
4623
4350
  logger.warn(`Addon type "${def.type}" is already registered. Overwriting.`);
4624
4351
  }
4625
4352
  registry.set(def.type, def);
@@ -4681,6 +4408,13 @@ function withFieldWindowing(config) {
4681
4408
  return createFeature('field-windowing', [{ provide: FIELD_WINDOWING, useValue: resolved }]);
4682
4409
  }
4683
4410
 
4411
+ const VALIDATION_EXECUTION_DEFAULTS = new InjectionToken('VALIDATION_EXECUTION_DEFAULTS', {
4412
+ providedIn: 'root',
4413
+ factory: () => ({
4414
+ validateWhenHidden: false,
4415
+ }),
4416
+ });
4417
+
4684
4418
  function withValidationExecutionDefaults(config) {
4685
4419
  const resolved = {
4686
4420
  validateWhenHidden: config?.validateWhenHidden ?? false,
@@ -4871,6 +4605,16 @@ function sanitizeFormConfigPure(config, fieldRegistry, typeRegistry, options = {
4871
4605
  return { sanitized: { ...config, fields: fields }, warnings };
4872
4606
  }
4873
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
+
4874
4618
  class GoToPageEvent {
4875
4619
  pageIndex;
4876
4620
  options;
@@ -4998,5 +4742,5 @@ function arrayEvent(arrayKey) {
4998
4742
 
4999
4743
 
5000
4744
 
5001
- export { withEventFormValue as $, ArrayItemRegistryService as A, BUILT_IN_FIELDS as B, CONTAINER_FIELD_PROCESSORS as C, DfFieldOutlet as D, EventDispatcher as E, FIELD_WINDOWING as F, GoToPageEvent as G, PageChangeEvent as H, InsertArrayItemEvent as I, PagerStateEvent as J, PopArrayItemEvent as K, PrependArrayItemEvent as L, MoveArrayItemEvent as M, NextPageEvent as N, PreviousPageEvent as O, PROPERTY_OVERRIDE_STORE as P, TextAddonComponent as Q, RemoveAtIndexEvent as R, ShiftArrayItemEvent as S, TemplateAddonComponent as T, arrayEvent as U, createWrappers as V, isWrappersBundle as W, provideDynamicForm as X, sanitizeFormConfig as Y, withAddonActions as Z, withCustomAddon as _, isSameWrapperChain as a, withFieldWindowing as a0, withLegacyStatusClasses as a1, withLoggerConfig as a2, withPagePreload as a3, withValidationExecutionDefaults as a4, withValueExclusionDefaults as a5, wrapperProps as a6, createWrapperChainController as b, createFieldResolutionPipe as c, createSchemaFromFields as d, createHiddenSignal as e, createRenderReadySignal as f, getNormalizedArrayMetadata as g, getFieldDefaultValue as h, injectFieldRegistry as i, resolveFieldWindowing as j, DEV_MODE as k, DEFAULT_DEBOUNCE_MS as l, mapFieldToInputs as m, DERIVATION_WARNING_TRACKER as n, FormStateManager as o, DERIVATION_LOG_CONFIG as p, AppendArrayItemEvent as q, resolveWrappers as r, ComponentAddonComponent as s, traverseFieldsWithContext as t, ConsoleLogger as u, DfTemplate as v, DynamicForm as w, FormClearEvent as x, FormResetEvent as y, FormSubmitEvent as z };
5002
- //# sourceMappingURL=ng-forge-dynamic-forms-ng-forge-dynamic-forms-B2C_WAMP.mjs.map
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