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

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 (23) hide show
  1. package/fesm2022/{ng-forge-dynamic-forms-array-field.component-CvjDqYyq.mjs → ng-forge-dynamic-forms-array-field.component-BVnXnaNw.mjs} +2 -2
  2. package/fesm2022/{ng-forge-dynamic-forms-array-field.component-CvjDqYyq.mjs.map → ng-forge-dynamic-forms-array-field.component-BVnXnaNw.mjs.map} +1 -1
  3. package/fesm2022/{ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-CcoUnroD.mjs → ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-CkqvXXs_.mjs} +2 -2
  4. package/fesm2022/{ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-CcoUnroD.mjs.map → ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-CkqvXXs_.mjs.map} +1 -1
  5. package/fesm2022/{ng-forge-dynamic-forms-container-field.component-DZfVWjiM.mjs → ng-forge-dynamic-forms-container-field.component-CM1pY9v5.mjs} +2 -2
  6. package/fesm2022/{ng-forge-dynamic-forms-container-field.component-DZfVWjiM.mjs.map → ng-forge-dynamic-forms-container-field.component-CM1pY9v5.mjs.map} +1 -1
  7. package/fesm2022/{ng-forge-dynamic-forms-group-field.component-CPFgu3pM.mjs → ng-forge-dynamic-forms-group-field.component-D4JhDffB.mjs} +2 -2
  8. package/fesm2022/{ng-forge-dynamic-forms-group-field.component-CPFgu3pM.mjs.map → ng-forge-dynamic-forms-group-field.component-D4JhDffB.mjs.map} +1 -1
  9. package/fesm2022/ng-forge-dynamic-forms-integration.mjs +6 -2
  10. package/fesm2022/ng-forge-dynamic-forms-integration.mjs.map +1 -1
  11. package/fesm2022/ng-forge-dynamic-forms-internal.mjs +149 -28
  12. package/fesm2022/ng-forge-dynamic-forms-internal.mjs.map +1 -1
  13. package/fesm2022/{ng-forge-dynamic-forms-ng-forge-dynamic-forms-Du3V5AOb.mjs → ng-forge-dynamic-forms-ng-forge-dynamic-forms-ZiNaCWHl.mjs} +148 -304
  14. package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-ZiNaCWHl.mjs.map +1 -0
  15. package/fesm2022/{ng-forge-dynamic-forms-page-field.component-amWhCdHL.mjs → ng-forge-dynamic-forms-page-field.component-Wbz6S8iH.mjs} +47 -9
  16. package/fesm2022/ng-forge-dynamic-forms-page-field.component-Wbz6S8iH.mjs.map +1 -0
  17. package/fesm2022/ng-forge-dynamic-forms.mjs +1 -1
  18. package/package.json +1 -1
  19. package/types/ng-forge-dynamic-forms-integration.d.ts +8 -2
  20. package/types/ng-forge-dynamic-forms-internal.d.ts +63 -11
  21. package/types/ng-forge-dynamic-forms.d.ts +124 -24
  22. package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-Du3V5AOb.mjs.map +0 -1
  23. package/fesm2022/ng-forge-dynamic-forms-page-field.component-amWhCdHL.mjs.map +0 -1
@@ -1,10 +1,10 @@
1
1
  import * as i0 from '@angular/core';
2
- import { reflectComponentType, Injector, inject, EnvironmentInjector, DestroyRef, computed, signal, input, ViewContainerRef, Directive, linkedSignal, ChangeDetectionStrategy, Component, Injectable, InjectionToken, isSignal, runInInjectionContext, afterNextRender, untracked, PendingTasks, ElementRef, PLATFORM_ID, isDevMode, TemplateRef, model, contentChildren, makeEnvironmentProviders } from '@angular/core';
2
+ import { reflectComponentType, Injector, inject, EnvironmentInjector, DestroyRef, computed, signal, input, ViewContainerRef, Directive, InjectionToken, linkedSignal, ChangeDetectionStrategy, Component, Injectable, isSignal, runInInjectionContext, afterNextRender, untracked, PendingTasks, ElementRef, PLATFORM_ID, isDevMode, TemplateRef, model, contentChildren, makeEnvironmentProviders } from '@angular/core';
3
3
  import { explicitEffect } from 'ngxtension/explicit-effect';
4
- import { DynamicFormLogger, WRAPPER_COMPONENT_CACHE, WRAPPER_REGISTRY, toReadonlyFieldTreeCached, writeToFieldValue, WRAPPER_AUTO_ASSOCIATIONS, DEFAULT_WRAPPERS, READONLY_FIELD_TREE_CACHE, getGridClassString, EventBus, FieldContextRegistryService, FunctionRegistryService, FORM_OPTIONS, evaluateCondition, FIELD_SIGNAL_CONTEXT, isRowField, isContainerField, isArrayField, hasChildFields, isSimplifiedArrayField, DynamicFormError, isStateLogicConfig, isDerivationLogicConfig, isFormStateCondition, createFormStateLogicFunction, createDebouncedLogicFunction, createLogicFunction, ExpressionParser, requiresTreeValidation, applyValidator, isPageField, isGenericContainerField, isGroupField, getFieldValueHandling, normalizeFieldsArray, getNestedValue, extractStringDependencies, isCrossFieldStateLogic, extractExpressionDependencies, isCrossFieldSchema, keyBy, mapValues, isEqual, hasTargetProperty, ARRAY_CONTEXT, GROUP_CONTEXT, FORM_ID_PREFIX, baseFieldMapper, detectFormMode, validatePageNesting, FIELD_REGISTRY, assertNever, ADDON_TYPE_REGISTRY, deepMergeDefaults, derivedFromDeferred, createWarningTracker, RootFormRegistryService, DEFAULT_PROPS, DEFAULT_VALIDATION_MESSAGES, EXTERNAL_DATA, DEPRECATION_WARNING_TRACKER, createAddonActionRegistry, ADDON_ACTION_REGISTRY, HttpConditionCache, HTTP_CONDITION_CACHE, HttpConditionFunctionCacheService, AsyncConditionFunctionCacheService, DynamicValueFunctionCacheService, LogicFunctionCacheService, setupInitializationTracking, ComponentInitializedEvent, DF_FIELD_TEMPLATES, rowFieldMapper, groupFieldMapper, arrayFieldMapper, pageFieldMapper, textFieldMapper, containerFieldMapper, isWrapperTypeDefinition, ADDON_TYPE_COMPONENT_CACHE, ADDON_TYPE_DEFINITIONS, createDefaultDerivationLogConfig, NoopLogger, EMIT_FORM_VALUE_ON_EVENTS, DynamicTextPipe, ADDON_ACTION_HANDLERS } from '@ng-forge/dynamic-forms/internal';
4
+ import { DynamicFormLogger, WRAPPER_COMPONENT_CACHE, WRAPPER_REGISTRY, toReadonlyFieldTreeCached, writeToFieldValue, WRAPPER_AUTO_ASSOCIATIONS, DEFAULT_WRAPPERS, READONLY_FIELD_TREE_CACHE, getGridClassString, EventBus, FieldContextRegistryService, FunctionRegistryService, FORM_OPTIONS, evaluateCondition, FIELD_SIGNAL_CONTEXT, isRowField, isContainerField, isArrayField, hasChildFields, isSimplifiedArrayField, DynamicFormError, isStateLogicConfig, isDerivationLogicConfig, isFormStateCondition, createFormStateLogicFunction, createDebouncedLogicFunction, createLogicFunction, ExpressionParser, applyValidator, isPageField, isGenericContainerField, isGroupField, getFieldValueHandling, normalizeFieldsArray, keyBy, mapValues, isEqual, hasTargetProperty, ARRAY_CONTEXT, GROUP_CONTEXT, FORM_ID_PREFIX, baseFieldMapper, detectFormMode, validatePageNesting, FIELD_REGISTRY, assertNever, ADDON_TYPE_REGISTRY, deepMergeDefaults, derivedFromDeferred, createWarningTracker, RootFormRegistryService, DEFAULT_PROPS, DEFAULT_VALIDATION_MESSAGES, EXTERNAL_DATA, DEPRECATION_WARNING_TRACKER, createAddonActionRegistry, ADDON_ACTION_REGISTRY, HttpConditionCache, HTTP_CONDITION_CACHE, HttpConditionFunctionCacheService, AsyncConditionFunctionCacheService, DynamicValueFunctionCacheService, LogicFunctionCacheService, setupInitializationTracking, ComponentInitializedEvent, DF_FIELD_TEMPLATES, rowFieldMapper, groupFieldMapper, arrayFieldMapper, pageFieldMapper, textFieldMapper, containerFieldMapper, isWrapperTypeDefinition, ADDON_TYPE_COMPONENT_CACHE, ADDON_TYPE_DEFINITIONS, createDefaultDerivationLogConfig, NoopLogger, EMIT_FORM_VALUE_ON_EVENTS, DynamicTextPipe, ADDON_ACTION_HANDLERS } from '@ng-forge/dynamic-forms/internal';
5
5
  import { takeUntilDestroyed, toObservable, toSignal, outputFromObservable } from '@angular/core/rxjs-interop';
6
6
  import { of, forkJoin, from, catchError, map, switchMap, isObservable, firstValueFrom, exhaustMap, EMPTY, debounceTime, distinctUntilChanged, tap, pipe, scan, Observable, Subject, concatMap, filter, take } from 'rxjs';
7
- import { submit, required, disabled, readonly, hidden, applyEach, applyWhenValue, applyWhen, apply, email, minLength, maxLength, pattern, min, max, schema, validateStandardSchema, validateTree, form, provideSignalFormsConfig } from '@angular/forms/signals';
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
8
  import { isStandardSchemaMarker } from '@ng-forge/dynamic-forms/schema';
9
9
  import { isPlatformBrowser, AsyncPipe, NgTemplateOutlet, NgComponentOutlet } from '@angular/common';
10
10
  import { NG_STATUS_CLASSES } from '@angular/forms/signals/compat';
@@ -559,11 +559,23 @@ class PageNavigationStateChangeEvent {
559
559
  }
560
560
  }
561
561
 
562
+ const PAGE_PRELOAD_WINDOW = new InjectionToken('PAGE_PRELOAD_WINDOW', {
563
+ providedIn: 'root',
564
+ factory: () => 1,
565
+ });
566
+
567
+ function clampWindowSize(value, fallback) {
568
+ if (value === undefined || !Number.isFinite(value))
569
+ return fallback;
570
+ return Math.max(0, Math.floor(value));
571
+ }
572
+
562
573
  class PageOrchestratorComponent {
563
574
  eventBus = inject(EventBus);
564
575
  fieldContextRegistry = inject(FieldContextRegistryService);
565
576
  functionRegistry = inject(FunctionRegistryService);
566
577
  formOptions = inject(FORM_OPTIONS, { optional: true });
578
+ globalPreloadWindow = inject(PAGE_PRELOAD_WINDOW);
567
579
  pageFields = input.required(/* @ts-ignore */
568
580
  ...(ngDevMode ? [{ debugName: "pageFields" }] : /* istanbul ignore next */ []));
569
581
  form = input.required(/* @ts-ignore */
@@ -633,6 +645,14 @@ class PageOrchestratorComponent {
633
645
  currentPageValid: this.currentPageValid,
634
646
  }), /* @ts-ignore */
635
647
  ...(ngDevMode ? [{ debugName: "extendedFieldSignalContext" }] : /* istanbul ignore next */ []));
648
+ preloadWindow = computed(() => {
649
+ const perForm = this.formOptions?.()?.pagePreloadWindow;
650
+ return clampWindowSize(perForm, this.globalPreloadWindow);
651
+ }, /* @ts-ignore */
652
+ ...(ngDevMode ? [{ debugName: "preloadWindow" }] : /* istanbul ignore next */ []));
653
+ isWithinPreloadWindow(i) {
654
+ return Math.abs(i - this.state().currentPageIndex) <= this.preloadWindow();
655
+ }
636
656
  constructor() {
637
657
  this.setupEventListeners();
638
658
  explicitEffect([this.state, this.visiblePageIndices], ([state, visibleIndices]) => {
@@ -808,8 +828,8 @@ class PageOrchestratorComponent {
808
828
  \`@if (!field.hidden())\` gate in page-field.component.ts.
809
829
  -->
810
830
  @if (!pageHiddenStates()[i]) {
811
- @if (i === state().currentPageIndex || i === state().currentPageIndex + 1 || i === state().currentPageIndex - 1) {
812
- <!-- Current and adjacent pages (±1): render immediately for flicker-free navigation -->
831
+ @if (isWithinPreloadWindow(i)) {
832
+ <!-- Current and preloaded neighbour pages: render immediately for flicker-free navigation -->
813
833
  @defer (on immediate) {
814
834
  <section
815
835
  page-field
@@ -822,21 +842,30 @@ class PageOrchestratorComponent {
822
842
  <div class="df-page-placeholder" [attr.data-page-index]="i" [attr.data-page-key]="pageField.key"></div>
823
843
  }
824
844
  } @else {
825
- <!-- Distant pages: defer until browser is idle for memory savings -->
826
- @defer (on idle) {
827
- <section page-field [field]="pageField" [key]="pageField.key" [pageIndex]="i" [isVisible]="false"></section>
828
- } @placeholder {
829
- <div class="df-page-placeholder" [attr.data-page-index]="i" [attr.data-page-key]="pageField.key"></div>
830
- }
845
+ <!--
846
+ Pages outside the preload window render only a lightweight placeholder
847
+ — they are NOT mounted. Mounting every page eagerly (previously via
848
+ \`@defer (on idle)\`, which fires almost immediately post-load) put all
849
+ fields of every page in the DOM at once: O(all-fields) initial render
850
+ blocking + O(all-fields) change detection on every keystroke, with no
851
+ visible benefit since only one page is shown at a time.
852
+
853
+ Form state (schema/value/validators) derives from config, not from
854
+ mounted components, so leaving a page unmounted does not affect
855
+ validity, derivations, or navigation. A page mounts the moment
856
+ navigation brings it into the preload window (above), which is
857
+ flicker-free for sequential next/previous navigation. The window size
858
+ is configurable via \`withPagePreload(n)\` or \`FormOptions.pagePreloadWindow\`.
859
+ -->
860
+ <div class="df-page-placeholder" [attr.data-page-index]="i" [attr.data-page-key]="pageField.key"></div>
831
861
  }
832
862
  }
833
863
  }
834
864
  `, isInline: true, styles: [":host{display:block;position:relative;width:100%}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, deferBlockDependencies: [() => [/* @ts-ignore */
835
- import('./ng-forge-dynamic-forms-page-field.component-amWhCdHL.mjs').then(m => m.default)], () => [/* @ts-ignore */
836
- import('./ng-forge-dynamic-forms-page-field.component-amWhCdHL.mjs').then(m => m.default)]] });
865
+ import('./ng-forge-dynamic-forms-page-field.component-Wbz6S8iH.mjs').then(m => m.default)]] });
837
866
  }
838
867
  i0.ɵɵngDeclareClassMetadataAsync({ minVersion: "18.0.0", version: "22.0.1", ngImport: i0, type: PageOrchestratorComponent, resolveDeferredDeps: () => [/* @ts-ignore */
839
- import('./ng-forge-dynamic-forms-page-field.component-amWhCdHL.mjs').then(m => m.default)], resolveMetadata: PageFieldComponent => ({ decorators: [{
868
+ import('./ng-forge-dynamic-forms-page-field.component-Wbz6S8iH.mjs').then(m => m.default)], resolveMetadata: PageFieldComponent => ({ decorators: [{
840
869
  type: Component,
841
870
  args: [{ selector: 'div[page-orchestrator]', imports: [PageFieldComponent], template: `
842
871
  @for (pageField of pageFields(); track pageField.key; let i = $index) {
@@ -848,8 +877,8 @@ i0.ɵɵngDeclareClassMetadataAsync({ minVersion: "18.0.0", version: "22.0.1", ng
848
877
  \`@if (!field.hidden())\` gate in page-field.component.ts.
849
878
  -->
850
879
  @if (!pageHiddenStates()[i]) {
851
- @if (i === state().currentPageIndex || i === state().currentPageIndex + 1 || i === state().currentPageIndex - 1) {
852
- <!-- Current and adjacent pages (±1): render immediately for flicker-free navigation -->
880
+ @if (isWithinPreloadWindow(i)) {
881
+ <!-- Current and preloaded neighbour pages: render immediately for flicker-free navigation -->
853
882
  @defer (on immediate) {
854
883
  <section
855
884
  page-field
@@ -862,12 +891,22 @@ i0.ɵɵngDeclareClassMetadataAsync({ minVersion: "18.0.0", version: "22.0.1", ng
862
891
  <div class="df-page-placeholder" [attr.data-page-index]="i" [attr.data-page-key]="pageField.key"></div>
863
892
  }
864
893
  } @else {
865
- <!-- Distant pages: defer until browser is idle for memory savings -->
866
- @defer (on idle) {
867
- <section page-field [field]="pageField" [key]="pageField.key" [pageIndex]="i" [isVisible]="false"></section>
868
- } @placeholder {
869
- <div class="df-page-placeholder" [attr.data-page-index]="i" [attr.data-page-key]="pageField.key"></div>
870
- }
894
+ <!--
895
+ Pages outside the preload window render only a lightweight placeholder
896
+ — they are NOT mounted. Mounting every page eagerly (previously via
897
+ \`@defer (on idle)\`, which fires almost immediately post-load) put all
898
+ fields of every page in the DOM at once: O(all-fields) initial render
899
+ blocking + O(all-fields) change detection on every keystroke, with no
900
+ visible benefit since only one page is shown at a time.
901
+
902
+ Form state (schema/value/validators) derives from config, not from
903
+ mounted components, so leaving a page unmounted does not affect
904
+ validity, derivations, or navigation. A page mounts the moment
905
+ navigation brings it into the preload window (above), which is
906
+ flicker-free for sequential next/previous navigation. The window size
907
+ is configurable via \`withPagePreload(n)\` or \`FormOptions.pagePreloadWindow\`.
908
+ -->
909
+ <div class="df-page-placeholder" [attr.data-page-index]="i" [attr.data-page-key]="pageField.key"></div>
871
910
  }
872
911
  }
873
912
  }
@@ -1262,11 +1301,6 @@ function applySchema(config, fieldPath) {
1262
1301
  function createSchemaFunction(schema, logger) {
1263
1302
  return (path) => {
1264
1303
  schema.validators?.forEach((validatorConfig) => {
1265
- if (requiresTreeValidation(validatorConfig)) {
1266
- logger?.warn(`Validator "${validatorConfig.type}" in schema definition "${schema.name}" uses a cross-field expression, ` +
1267
- `which is not supported on schema definition validators; it was skipped.`);
1268
- return;
1269
- }
1270
1304
  applyValidator(validatorConfig, path);
1271
1305
  });
1272
1306
  schema.logic?.forEach((logicConfig) => {
@@ -1728,28 +1762,9 @@ const VALIDATION_EXECUTION_DEFAULTS = new InjectionToken('VALIDATION_EXECUTION_D
1728
1762
  }),
1729
1763
  });
1730
1764
 
1731
- function getFieldTreeByKey(ctx, key) {
1732
- if (!key.includes('.')) {
1733
- return ctx.fieldTree[key];
1734
- }
1735
- const parts = key.split('.');
1736
- let current = ctx.fieldTree;
1737
- for (const part of parts) {
1738
- if (!current || (typeof current !== 'object' && typeof current !== 'function')) {
1739
- return undefined;
1740
- }
1741
- current = current[part];
1742
- }
1743
- return current;
1744
- }
1745
- function createSchemaFromFields(fields, registry, optionsOrValidators) {
1746
- const functionRegistry = inject(FunctionRegistryService);
1747
- const logger = inject(DynamicFormLogger);
1748
- const validationExecutionDefaults = inject(VALIDATION_EXECUTION_DEFAULTS);
1749
- const options = Array.isArray(optionsOrValidators)
1750
- ? { crossFieldValidators: optionsOrValidators }
1751
- : (optionsOrValidators ?? {});
1752
- const { crossFieldValidators, formLevelSchema } = options;
1765
+ function createSchemaFromFields(fields, registry, options = {}) {
1766
+ const validationExecutionDefaults = inject(VALIDATION_EXECUTION_DEFAULTS);
1767
+ const { formLevelSchema } = options;
1753
1768
  const rootContext = {
1754
1769
  validateWhenHidden: options.validateWhenHidden ?? validationExecutionDefaults.validateWhenHidden,
1755
1770
  ancestorAlwaysHidden: false,
@@ -1779,105 +1794,12 @@ function createSchemaFromFields(fields, registry, optionsOrValidators) {
1779
1794
  continue;
1780
1795
  }
1781
1796
  mapFieldToForm(fieldDef, fieldPath, rootContext);
1782
- }
1783
- if (crossFieldValidators && crossFieldValidators.length > 0) {
1784
- applyCrossFieldTreeValidator(path, crossFieldValidators, functionRegistry, logger);
1785
1797
  }
1786
1798
  if (formLevelSchema) {
1787
1799
  applyFormLevelSchema(path, formLevelSchema);
1788
1800
  }
1789
1801
  });
1790
1802
  }
1791
- function applyCrossFieldTreeValidator(rootPath, validators, functionRegistry, logger) {
1792
- const customFunctions = functionRegistry.getCustomFunctions();
1793
- const customValidators = validators.filter((entry) => {
1794
- if (entry.config.type !== 'custom') {
1795
- logger.warn(`Unexpected non-custom validator in cross-field tree for "${entry.sourceFieldKey}"; ignoring.`);
1796
- return false;
1797
- }
1798
- return true;
1799
- });
1800
- validateTree(rootPath, (ctx) => {
1801
- if (customValidators.length === 0) {
1802
- return null; }
1803
- const formValue = ctx.value();
1804
- const errors = [];
1805
- for (const entry of customValidators) {
1806
- const { sourceFieldKey, config } = entry;
1807
- try {
1808
- const error = evaluateCrossFieldValidator(entry, formValue, sourceFieldKey, ctx, customFunctions, logger);
1809
- if (error) {
1810
- errors.push(error);
1811
- }
1812
- }
1813
- catch (err) {
1814
- logger.error(`Error evaluating cross-field validator for ${sourceFieldKey}:`, err);
1815
- const targetField = getFieldTreeByKey(ctx, sourceFieldKey);
1816
- if (targetField) {
1817
- const customConfig = config;
1818
- errors.push({
1819
- kind: customConfig.kind || config.type || 'custom',
1820
- fieldTree: targetField,
1821
- });
1822
- }
1823
- }
1824
- }
1825
- return errors.length > 0 ? errors : null;
1826
- });
1827
- }
1828
- function evaluateCrossFieldValidator(entry, formValue, sourceFieldKey, ctx, customFunctions, logger) {
1829
- const { config } = entry;
1830
- const fieldValue = getNestedValue(formValue, sourceFieldKey);
1831
- const evaluationContext = {
1832
- fieldValue,
1833
- formValue,
1834
- fieldPath: sourceFieldKey,
1835
- customFunctions,
1836
- logger,
1837
- };
1838
- return evaluateCustomCrossFieldValidator(config, evaluationContext, sourceFieldKey, ctx);
1839
- }
1840
- function evaluateCustomCrossFieldValidator(config, evaluationContext, sourceFieldKey, ctx) {
1841
- if (!config.expression) {
1842
- return null;
1843
- }
1844
- const { fieldValue, formValue, logger, customFunctions } = evaluationContext;
1845
- if (config.when) {
1846
- const conditionMet = evaluateCondition(config.when, {
1847
- fieldValue,
1848
- formValue,
1849
- fieldPath: sourceFieldKey,
1850
- customFunctions: customFunctions || {},
1851
- logger,
1852
- });
1853
- if (!conditionMet) {
1854
- return null; }
1855
- }
1856
- const result = ExpressionParser.evaluate(config.expression, evaluationContext);
1857
- if (result) {
1858
- return null;
1859
- }
1860
- const targetField = getFieldTreeByKey(ctx, sourceFieldKey);
1861
- if (!targetField) {
1862
- evaluationContext.logger.warn(`Cross-field validator references non-existent field "${sourceFieldKey}"`);
1863
- return null;
1864
- }
1865
- const errorObj = {
1866
- kind: config.kind || 'custom',
1867
- fieldTree: targetField,
1868
- };
1869
- if (config.errorParams) {
1870
- for (const [key, expression] of Object.entries(config.errorParams)) {
1871
- try {
1872
- errorObj[key] = ExpressionParser.evaluate(expression, evaluationContext);
1873
- }
1874
- catch (error) {
1875
- evaluationContext.logger.warn(`Failed to evaluate error param expression "${key}": ${expression}`, error);
1876
- }
1877
- }
1878
- }
1879
- return errorObj;
1880
- }
1881
1803
  function fieldsToDefaultValues(fields, registry) {
1882
1804
  const defaultValues = {};
1883
1805
  for (const field of fields) {
@@ -1895,152 +1817,6 @@ function fieldsToDefaultValues(fields, registry) {
1895
1817
  return defaultValues;
1896
1818
  }
1897
1819
 
1898
- function createEmptyCollection() {
1899
- return {
1900
- validators: [],
1901
- logic: [],
1902
- schemas: [],
1903
- };
1904
- }
1905
- function collectCrossFieldEntries(fields) {
1906
- const collection = createEmptyCollection();
1907
- traverseFields(fields, collection, '');
1908
- return collection;
1909
- }
1910
- function traverseFields(fields, collection, pathPrefix) {
1911
- for (const field of fields) {
1912
- collectFromField(field, collection, pathPrefix);
1913
- if (hasChildFields(field)) {
1914
- const groupKey = isGroupField(field) ? field.key : undefined;
1915
- const childPrefix = groupKey ? (pathPrefix ? `${pathPrefix}.${groupKey}` : groupKey) : pathPrefix;
1916
- traverseFields(normalizeFieldsArray(field.fields), collection, childPrefix);
1917
- }
1918
- }
1919
- }
1920
- function collectFromField(field, collection, pathPrefix) {
1921
- const fieldKey = field.key;
1922
- if (!fieldKey)
1923
- return;
1924
- const fullKey = pathPrefix ? `${pathPrefix}.${fieldKey}` : fieldKey;
1925
- const validationField = field;
1926
- if (validationField.validators) {
1927
- for (const config of validationField.validators) {
1928
- const entry = tryCreateValidatorEntry(fullKey, config);
1929
- if (entry) {
1930
- collection.validators.push(entry);
1931
- }
1932
- }
1933
- }
1934
- if (validationField.logic) {
1935
- for (const config of validationField.logic) {
1936
- const entry = tryCreateLogicEntry(fullKey, config);
1937
- if (entry) {
1938
- collection.logic.push(entry);
1939
- }
1940
- }
1941
- }
1942
- if (validationField.schemas) {
1943
- for (const config of validationField.schemas) {
1944
- const entry = tryCreateSchemaEntry(fullKey, config);
1945
- if (entry) {
1946
- collection.schemas.push(entry);
1947
- }
1948
- }
1949
- }
1950
- }
1951
- function tryCreateValidatorEntry(fieldKey, config) {
1952
- if (!requiresTreeValidation(config)) {
1953
- return null; }
1954
- if (config.type === 'custom') {
1955
- const customConfig = config;
1956
- return {
1957
- sourceFieldKey: fieldKey,
1958
- config,
1959
- dependsOn: extractStringDependencies(customConfig.expression || ''),
1960
- category: 'validator',
1961
- };
1962
- }
1963
- const builtInConfig = config;
1964
- return {
1965
- sourceFieldKey: fieldKey,
1966
- config: convertBuiltInToCustomValidator(builtInConfig),
1967
- validatorType: config.type,
1968
- dependsOn: extractStringDependencies(builtInConfig.expression || ''),
1969
- category: 'validator',
1970
- };
1971
- }
1972
- function tryCreateLogicEntry(fieldKey, config) {
1973
- if (!isStateLogicConfig(config)) {
1974
- return null;
1975
- }
1976
- if (!isCrossFieldStateLogic(config)) {
1977
- return null;
1978
- }
1979
- const condition = config.condition;
1980
- return {
1981
- sourceFieldKey: fieldKey,
1982
- logicType: config.type,
1983
- condition,
1984
- config: config,
1985
- dependsOn: extractExpressionDependencies(condition),
1986
- category: 'logic',
1987
- };
1988
- }
1989
- function tryCreateSchemaEntry(fieldKey, config) {
1990
- if (!isCrossFieldSchema(config)) {
1991
- return null;
1992
- }
1993
- const condition = config.condition;
1994
- return {
1995
- sourceFieldKey: fieldKey,
1996
- config,
1997
- condition,
1998
- dependsOn: extractExpressionDependencies(condition),
1999
- category: 'schema',
2000
- };
2001
- }
2002
- function convertBuiltInToCustomValidator(config) {
2003
- const expression = config.expression;
2004
- if (!expression) {
2005
- throw new DynamicFormError(`Built-in validator ${config.type} missing required expression for cross-field conversion`);
2006
- }
2007
- let validationExpression;
2008
- switch (config.type) {
2009
- case 'min':
2010
- validationExpression = `fieldValue == null || fieldValue >= (${expression})`;
2011
- break;
2012
- case 'max':
2013
- validationExpression = `fieldValue == null || fieldValue <= (${expression})`;
2014
- break;
2015
- case 'minLength':
2016
- validationExpression = `fieldValue == null || (typeof fieldValue !== 'string' && !Array.isArray(fieldValue)) || fieldValue.length >= (${expression})`;
2017
- break;
2018
- case 'maxLength':
2019
- validationExpression = `fieldValue == null || (typeof fieldValue !== 'string' && !Array.isArray(fieldValue)) || fieldValue.length <= (${expression})`;
2020
- break;
2021
- case 'pattern':
2022
- try {
2023
- new RegExp(expression);
2024
- }
2025
- catch (e) {
2026
- throw new DynamicFormError(`Invalid regex pattern in cross-field validator: '${expression}' — ${e instanceof Error ? e.message : String(e)}`);
2027
- }
2028
- validationExpression = `fieldValue == null || new RegExp(${JSON.stringify(expression)}).test(fieldValue)`;
2029
- break;
2030
- default:
2031
- throw new DynamicFormError(`Cannot convert ${config.type} validator to custom validator`);
2032
- }
2033
- return {
2034
- type: 'custom',
2035
- expression: validationExpression,
2036
- kind: config.type,
2037
- when: config.when,
2038
- errorParams: {
2039
- [config.type]: expression,
2040
- },
2041
- };
2042
- }
2043
-
2044
1820
  function inheritContainerExclusion(container, child) {
2045
1821
  const inheritedHidden = container.hidden === true || child.inheritedExclusionState?.hidden === true;
2046
1822
  const inheritedDisabled = container.disabled === true || child.inheritedExclusionState?.disabled === true;
@@ -3608,9 +3384,7 @@ class FormStateManager {
3608
3384
  if (!config)
3609
3385
  return undefined;
3610
3386
  if (setup.schemaFields?.length) {
3611
- const crossFieldCollection = collectCrossFieldEntries(setup.schemaFields);
3612
3387
  return createSchemaFromFields(setup.schemaFields, setup.registry, {
3613
- crossFieldValidators: crossFieldCollection.validators,
3614
3388
  formLevelSchema: config.schema,
3615
3389
  validateWhenHidden: this.effectiveFormOptions().validateWhenHidden,
3616
3390
  });
@@ -4105,7 +3879,7 @@ function createDerivationRenderGate() {
4105
3879
  if (!has || engine() !== 'idle')
4106
3880
  return;
4107
3881
  engine.set('loading');
4108
- pendingTasks.run(() => import('./ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-CcoUnroD.mjs')
3882
+ pendingTasks.run(() => import('./ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-CkqvXXs_.mjs')
4109
3883
  .then(({ bootstrapDerivationOrchestrator }) => {
4110
3884
  runInInjectionContext(injector, () => bootstrapDerivationOrchestrator());
4111
3885
  engine.set('wired');
@@ -4353,6 +4127,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
4353
4127
  type: Injectable
4354
4128
  }] });
4355
4129
 
4130
+ const FIELD_WINDOWING = new InjectionToken('FIELD_WINDOWING', {
4131
+ providedIn: 'root',
4132
+ factory: () => ({ enabled: false, eager: 12, placeholderHeight: '4rem' }),
4133
+ });
4134
+
4135
+ function resolveFieldWindowing(global, perForm) {
4136
+ if (perForm === undefined) {
4137
+ return global;
4138
+ }
4139
+ if (typeof perForm === 'boolean') {
4140
+ return { ...global, enabled: perForm };
4141
+ }
4142
+ return {
4143
+ enabled: true,
4144
+ eager: clampWindowSize(perForm.eager, global.eager),
4145
+ placeholderHeight: perForm.placeholderHeight ?? global.placeholderHeight,
4146
+ };
4147
+ }
4148
+
4356
4149
  class DynamicForm {
4357
4150
  config = input.required({ ...(ngDevMode ? { debugName: "config" } : /* istanbul ignore next */ {}), alias: 'dynamic-form' });
4358
4151
  formOptions = input(undefined, /* @ts-ignore */
@@ -4368,6 +4161,7 @@ class DynamicForm {
4368
4161
  logger = inject(DynamicFormLogger);
4369
4162
  dispatcher = inject(EventDispatcher, { optional: true });
4370
4163
  templateRegistry = inject(DfFieldTemplateRegistry);
4164
+ globalFieldWindowing = inject(FIELD_WINDOWING);
4371
4165
  _projectedTemplates = contentChildren(DfTemplate, /* @ts-ignore */
4372
4166
  ...(ngDevMode ? [{ debugName: "_projectedTemplates" }] : /* istanbul ignore next */ []));
4373
4167
  stateManager = this.connectDeps();
@@ -4395,6 +4189,8 @@ class DynamicForm {
4395
4189
  shouldRender = computed(() => this.stateManager.shouldRender() && this.derivationReady(), /* @ts-ignore */
4396
4190
  ...(ngDevMode ? [{ debugName: "shouldRender" }] : /* istanbul ignore next */ []));
4397
4191
  resolvedFields = this.stateManager.resolvedFields;
4192
+ fieldWindowing = computed(() => resolveFieldWindowing(this.globalFieldWindowing, this.effectiveFormOptions()?.fieldWindowing), /* @ts-ignore */
4193
+ ...(ngDevMode ? [{ debugName: "fieldWindowing" }] : /* istanbul ignore next */ []));
4398
4194
  totalComponentsCount = computed(() => {
4399
4195
  const fields = this.stateManager.formSetup()?.fields ?? [];
4400
4196
  return countContainersRecursive(fields) + 1;
@@ -4425,6 +4221,14 @@ class DynamicForm {
4425
4221
  onNativeSubmit(event) {
4426
4222
  event.preventDefault();
4427
4223
  this.eventBus.dispatch(FormSubmitEvent);
4224
+ }
4225
+ windowsField(field, index) {
4226
+ const w = this.fieldWindowing();
4227
+ return w.enabled && index >= w.eager && !isContainerField(field.fieldDef);
4228
+ }
4229
+ placeholderGridClass(field) {
4230
+ const grid = getGridClassString(field.fieldDef);
4231
+ return grid ? `df-field-placeholder ${grid}` : 'df-field-placeholder';
4428
4232
  }
4429
4233
  setupEffects() {
4430
4234
  explicitEffect([this.formModeDetection, this.pageFieldDefinitions], ([{ mode }, pages]) => {
@@ -4480,13 +4284,26 @@ class DynamicForm {
4480
4284
  <div page-orchestrator [pageFields]="pageFieldDefinitions()" [form]="form()" [fieldSignalContext]="fieldSignalContext()"></div>
4481
4285
  }
4482
4286
  @case ('non-paged') {
4483
- @for (field of resolvedFields(); track field.key) {
4287
+ @for (field of resolvedFields(); track field.key; let i = $index) {
4484
4288
  <!-- @if + DfFieldOutlet's own renderReady-&-!hidden gate together silence NG01916:
4485
4289
  the template @if removes the host from the DOM (Angular's prescribed pattern), and
4486
4290
  the directive's gate stops a same-CD-pass mount before [formField] can warn.
4487
4291
  Don't dedupe one without the other — see DfFieldOutlet.renderReady. -->
4488
4292
  @if (!field.hidden()) {
4489
- <ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
4293
+ @if (windowsField(field, i)) {
4294
+ @defer (on viewport) {
4295
+ <ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
4296
+ } @placeholder {
4297
+ <div
4298
+ [class]="placeholderGridClass(field)"
4299
+ [style.min-height]="fieldWindowing().placeholderHeight"
4300
+ [attr.data-field-key]="field.key"
4301
+ aria-hidden="true"
4302
+ ></div>
4303
+ }
4304
+ } @else {
4305
+ <ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
4306
+ }
4490
4307
  }
4491
4308
  }
4492
4309
  }
@@ -4495,7 +4312,7 @@ class DynamicForm {
4495
4312
  }
4496
4313
  }
4497
4314
  }
4498
- `, isInline: true, styles: [".df-form{display:grid;grid-template-columns:1fr;gap:var(--df-grid-row-gap, .5rem);width:100%}.df-form>*{grid-column:1/-1}.df-row{display:grid;grid-template-columns:repeat(var(--df-grid-columns, 12),1fr);gap:var(--df-grid-gap, .5rem);align-items:start;width:100%}.df-row>*:not([class*=df-col-]){grid-column:1/-1}.df-col-1{grid-column:span 1}.df-col-2{grid-column:span 2}.df-col-3{grid-column:span 3}.df-col-4{grid-column:span 4}.df-col-5{grid-column:span 5}.df-col-6{grid-column:span 6}.df-col-7{grid-column:span 7}.df-col-8{grid-column:span 8}.df-col-9{grid-column:span 9}.df-col-10{grid-column:span 10}.df-col-11{grid-column:span 11}.df-col-12{grid-column:span 12}.df-col-auto{grid-column:span auto;width:auto}.df-col-full{grid-column:1/-1}.df-col-start-1{grid-column-start:1}.df-col-start-2{grid-column-start:2}.df-col-start-3{grid-column-start:3}.df-col-start-4{grid-column-start:4}.df-col-start-5{grid-column-start:5}.df-col-start-6{grid-column-start:6}.df-col-start-7{grid-column-start:7}.df-col-start-8{grid-column-start:8}.df-col-start-9{grid-column-start:9}.df-col-start-10{grid-column-start:10}.df-col-start-11{grid-column-start:11}.df-col-start-12{grid-column-start:12}.df-col-end-1{grid-column-end:1}.df-col-end-2{grid-column-end:2}.df-col-end-3{grid-column-end:3}.df-col-end-4{grid-column-end:4}.df-col-end-5{grid-column-end:5}.df-col-end-6{grid-column-end:6}.df-col-end-7{grid-column-end:7}.df-col-end-8{grid-column-end:8}.df-col-end-9{grid-column-end:9}.df-col-end-10{grid-column-end:10}.df-col-end-11{grid-column-end:11}.df-col-end-12{grid-column-end:12}.df-col-end-13{grid-column-end:13}@media(max-width:576px){.df-form{--df-grid-gap: var(--df-grid-gap-sm, .5rem);--df-grid-row-gap: var(--df-grid-row-gap-sm, .5rem)}.df-row{grid-template-columns:1fr}.df-row>*{grid-column:1/-1!important}.df-row.df-row-mobile-keep-cols{grid-template-columns:repeat(var(--df-grid-columns, 12),1fr)}.df-row.df-row-mobile-keep-cols>*{grid-column:revert!important}}@media(min-width:577px)and (max-width:768px){.df-form{--df-grid-gap: var(--df-grid-gap-md, .5rem);--df-grid-row-gap: var(--df-grid-row-gap-md, .5rem)}.df-row{--df-grid-columns: 6}.df-col-sm-1{grid-column:span 1}.df-col-sm-2{grid-column:span 2}.df-col-sm-3{grid-column:span 3}.df-col-sm-4{grid-column:span 4}.df-col-sm-5{grid-column:span 5}.df-col-sm-6{grid-column:span 6}.df-col-sm-full{grid-column:1/-1}}@media(min-width:769px)and (max-width:992px){.df-form{--df-grid-gap: var(--df-grid-gap-lg, .5rem);--df-grid-row-gap: var(--df-grid-row-gap-lg, .5rem)}.df-col-md-1{grid-column:span 1}.df-col-md-2{grid-column:span 2}.df-col-md-3{grid-column:span 3}.df-col-md-4{grid-column:span 4}.df-col-md-5{grid-column:span 5}.df-col-md-6{grid-column:span 6}.df-col-md-7{grid-column:span 7}.df-col-md-8{grid-column:span 8}.df-col-md-9{grid-column:span 9}.df-col-md-10{grid-column:span 10}.df-col-md-11{grid-column:span 11}.df-col-md-12{grid-column:span 12}.df-col-md-full{grid-column:1/-1}}@media(min-width:993px){.df-form{--df-grid-gap: var(--df-grid-gap-xl, .5rem);--df-grid-row-gap: var(--df-grid-row-gap-xl, .5rem)}.df-col-lg-1{grid-column:span 1}.df-col-lg-2{grid-column:span 2}.df-col-lg-3{grid-column:span 3}.df-col-lg-4{grid-column:span 4}.df-col-lg-5{grid-column:span 5}.df-col-lg-6{grid-column:span 6}.df-col-lg-7{grid-column:span 7}.df-col-lg-8{grid-column:span 8}.df-col-lg-9{grid-column:span 9}.df-col-lg-10{grid-column:span 10}.df-col-lg-11{grid-column:span 11}.df-col-lg-12{grid-column:span 12}.df-col-lg-full{grid-column:1/-1}}.df-gap-none{--df-grid-gap: 0}.df-gap-xs{--df-grid-gap: .25rem}.df-gap-sm{--df-grid-gap: .5rem}.df-gap-md{--df-grid-gap: 1rem}.df-gap-lg{--df-grid-gap: 1.5rem}.df-gap-xl{--df-grid-gap: 2rem}.df-row-gap-none{--df-grid-row-gap: 0}.df-row-gap-xs{--df-grid-row-gap: .25rem}.df-row-gap-sm{--df-grid-row-gap: .5rem}.df-row-gap-md{--df-grid-row-gap: 1rem}.df-row-gap-lg{--df-grid-row-gap: 1.5rem}.df-row-gap-xl{--df-grid-row-gap: 2rem}.df-field{display:block;width:100%;min-width:0;overflow:hidden;margin:0}.df-group,.df-page{display:block;width:100%}.df-form.disabled,.df-row.disabled,.df-field.disabled{opacity:.6;pointer-events:none}.df-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.df-sr-only-focusable:focus,.df-sr-only-focusable:active{position:static;width:auto;height:auto;padding:inherit;margin:inherit;overflow:visible;clip:auto;white-space:normal}.df-live-region{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.df-form :focus-visible{outline:2px solid var(--df-focus-color, #005fcc);outline-offset:2px}.df-group:focus-within{outline:1px dashed var(--df-focus-color, #005fcc);outline-offset:4px}:host{display:grid;grid-template-columns:1fr;gap:var(--df-grid-row-gap, .5rem);width:100%}:host>*{grid-column:1/-1}:host.df-form-spacing-custom{--df-grid-row-gap: var(--df-form-custom-spacing, 1.5rem)}:host.df-form-gap-none{--df-grid-row-gap: 0}:host.df-form-gap-xs{--df-grid-row-gap: .25rem}:host.df-form-gap-sm{--df-grid-row-gap: .5rem}:host.df-form-gap-md{--df-grid-row-gap: 1rem}:host.df-form-gap-lg{--df-grid-row-gap: 1.5rem}:host.df-form-gap-xl{--df-grid-row-gap: 2rem}:host.df-form-paged{gap:0}\n"], dependencies: [{ kind: "directive", type: DfFieldOutlet, selector: "[dfFieldOutlet]", inputs: ["dfFieldOutlet", "dfFieldOutletEnvironmentInjector"] }, { kind: "component", type: PageOrchestratorComponent, selector: "div[page-orchestrator]", inputs: ["pageFields", "form", "fieldSignalContext"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4315
+ `, isInline: true, styles: [".df-form{display:grid;grid-template-columns:1fr;gap:var(--df-grid-row-gap, .5rem);width:100%}.df-form>*{grid-column:1/-1}.df-row{display:grid;grid-template-columns:repeat(var(--df-grid-columns, 12),1fr);gap:var(--df-grid-gap, .5rem);align-items:start;width:100%}.df-row>*:not([class*=df-col-]){grid-column:1/-1}.df-col-1{grid-column:span 1}.df-col-2{grid-column:span 2}.df-col-3{grid-column:span 3}.df-col-4{grid-column:span 4}.df-col-5{grid-column:span 5}.df-col-6{grid-column:span 6}.df-col-7{grid-column:span 7}.df-col-8{grid-column:span 8}.df-col-9{grid-column:span 9}.df-col-10{grid-column:span 10}.df-col-11{grid-column:span 11}.df-col-12{grid-column:span 12}.df-col-auto{grid-column:span auto;width:auto}.df-col-full{grid-column:1/-1}.df-col-start-1{grid-column-start:1}.df-col-start-2{grid-column-start:2}.df-col-start-3{grid-column-start:3}.df-col-start-4{grid-column-start:4}.df-col-start-5{grid-column-start:5}.df-col-start-6{grid-column-start:6}.df-col-start-7{grid-column-start:7}.df-col-start-8{grid-column-start:8}.df-col-start-9{grid-column-start:9}.df-col-start-10{grid-column-start:10}.df-col-start-11{grid-column-start:11}.df-col-start-12{grid-column-start:12}.df-col-end-1{grid-column-end:1}.df-col-end-2{grid-column-end:2}.df-col-end-3{grid-column-end:3}.df-col-end-4{grid-column-end:4}.df-col-end-5{grid-column-end:5}.df-col-end-6{grid-column-end:6}.df-col-end-7{grid-column-end:7}.df-col-end-8{grid-column-end:8}.df-col-end-9{grid-column-end:9}.df-col-end-10{grid-column-end:10}.df-col-end-11{grid-column-end:11}.df-col-end-12{grid-column-end:12}.df-col-end-13{grid-column-end:13}@media(max-width:576px){.df-form{--df-grid-gap: var(--df-grid-gap-sm, .5rem);--df-grid-row-gap: var(--df-grid-row-gap-sm, .5rem)}.df-row{grid-template-columns:1fr}.df-row>*{grid-column:1/-1!important}.df-row.df-row-mobile-keep-cols{grid-template-columns:repeat(var(--df-grid-columns, 12),1fr)}.df-row.df-row-mobile-keep-cols>*{grid-column:revert!important}}@media(min-width:577px)and (max-width:768px){.df-form{--df-grid-gap: var(--df-grid-gap-md, .5rem);--df-grid-row-gap: var(--df-grid-row-gap-md, .5rem)}.df-row{--df-grid-columns: 6}.df-col-sm-1{grid-column:span 1}.df-col-sm-2{grid-column:span 2}.df-col-sm-3{grid-column:span 3}.df-col-sm-4{grid-column:span 4}.df-col-sm-5{grid-column:span 5}.df-col-sm-6{grid-column:span 6}.df-col-sm-full{grid-column:1/-1}}@media(min-width:769px)and (max-width:992px){.df-form{--df-grid-gap: var(--df-grid-gap-lg, .5rem);--df-grid-row-gap: var(--df-grid-row-gap-lg, .5rem)}.df-col-md-1{grid-column:span 1}.df-col-md-2{grid-column:span 2}.df-col-md-3{grid-column:span 3}.df-col-md-4{grid-column:span 4}.df-col-md-5{grid-column:span 5}.df-col-md-6{grid-column:span 6}.df-col-md-7{grid-column:span 7}.df-col-md-8{grid-column:span 8}.df-col-md-9{grid-column:span 9}.df-col-md-10{grid-column:span 10}.df-col-md-11{grid-column:span 11}.df-col-md-12{grid-column:span 12}.df-col-md-full{grid-column:1/-1}}@media(min-width:993px){.df-form{--df-grid-gap: var(--df-grid-gap-xl, .5rem);--df-grid-row-gap: var(--df-grid-row-gap-xl, .5rem)}.df-col-lg-1{grid-column:span 1}.df-col-lg-2{grid-column:span 2}.df-col-lg-3{grid-column:span 3}.df-col-lg-4{grid-column:span 4}.df-col-lg-5{grid-column:span 5}.df-col-lg-6{grid-column:span 6}.df-col-lg-7{grid-column:span 7}.df-col-lg-8{grid-column:span 8}.df-col-lg-9{grid-column:span 9}.df-col-lg-10{grid-column:span 10}.df-col-lg-11{grid-column:span 11}.df-col-lg-12{grid-column:span 12}.df-col-lg-full{grid-column:1/-1}}.df-gap-none{--df-grid-gap: 0}.df-gap-xs{--df-grid-gap: .25rem}.df-gap-sm{--df-grid-gap: .5rem}.df-gap-md{--df-grid-gap: 1rem}.df-gap-lg{--df-grid-gap: 1.5rem}.df-gap-xl{--df-grid-gap: 2rem}.df-row-gap-none{--df-grid-row-gap: 0}.df-row-gap-xs{--df-grid-row-gap: .25rem}.df-row-gap-sm{--df-grid-row-gap: .5rem}.df-row-gap-md{--df-grid-row-gap: 1rem}.df-row-gap-lg{--df-grid-row-gap: 1.5rem}.df-row-gap-xl{--df-grid-row-gap: 2rem}.df-field{display:block;width:100%;min-width:0;overflow:hidden;margin:0}.df-group,.df-page{display:block;width:100%}.df-form.disabled,.df-row.disabled,.df-field.disabled{opacity:.6;pointer-events:none}.df-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.df-sr-only-focusable:focus,.df-sr-only-focusable:active{position:static;width:auto;height:auto;padding:inherit;margin:inherit;overflow:visible;clip:auto;white-space:normal}.df-live-region{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.df-form :focus-visible{outline:2px solid var(--df-focus-color, #005fcc);outline-offset:2px}.df-group:focus-within{outline:1px dashed var(--df-focus-color, #005fcc);outline-offset:4px}:host{display:grid;grid-template-columns:1fr;gap:var(--df-grid-row-gap, .5rem);width:100%}:host>*{grid-column:1/-1}:host.df-form-spacing-custom{--df-grid-row-gap: var(--df-form-custom-spacing, 1.5rem)}:host.df-form-gap-none{--df-grid-row-gap: 0}:host.df-form-gap-xs{--df-grid-row-gap: .25rem}:host.df-form-gap-sm{--df-grid-row-gap: .5rem}:host.df-form-gap-md{--df-grid-row-gap: 1rem}:host.df-form-gap-lg{--df-grid-row-gap: 1.5rem}:host.df-form-gap-xl{--df-grid-row-gap: 2rem}:host.df-form-paged{gap:0}.df-field-placeholder{display:block}\n"], dependencies: [{ kind: "directive", type: DfFieldOutlet, selector: "[dfFieldOutlet]", inputs: ["dfFieldOutlet", "dfFieldOutletEnvironmentInjector"] }, { kind: "component", type: PageOrchestratorComponent, selector: "div[page-orchestrator]", inputs: ["pageFields", "form", "fieldSignalContext"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, deferBlockDependencies: [() => [DfFieldOutlet]] });
4499
4316
  }
4500
4317
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: DynamicForm, decorators: [{
4501
4318
  type: Component,
@@ -4506,13 +4323,26 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
4506
4323
  <div page-orchestrator [pageFields]="pageFieldDefinitions()" [form]="form()" [fieldSignalContext]="fieldSignalContext()"></div>
4507
4324
  }
4508
4325
  @case ('non-paged') {
4509
- @for (field of resolvedFields(); track field.key) {
4326
+ @for (field of resolvedFields(); track field.key; let i = $index) {
4510
4327
  <!-- @if + DfFieldOutlet's own renderReady-&-!hidden gate together silence NG01916:
4511
4328
  the template @if removes the host from the DOM (Angular's prescribed pattern), and
4512
4329
  the directive's gate stops a same-CD-pass mount before [formField] can warn.
4513
4330
  Don't dedupe one without the other — see DfFieldOutlet.renderReady. -->
4514
4331
  @if (!field.hidden()) {
4515
- <ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
4332
+ @if (windowsField(field, i)) {
4333
+ @defer (on viewport) {
4334
+ <ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
4335
+ } @placeholder {
4336
+ <div
4337
+ [class]="placeholderGridClass(field)"
4338
+ [style.min-height]="fieldWindowing().placeholderHeight"
4339
+ [attr.data-field-key]="field.key"
4340
+ aria-hidden="true"
4341
+ ></div>
4342
+ }
4343
+ } @else {
4344
+ <ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
4345
+ }
4516
4346
  }
4517
4347
  }
4518
4348
  }
@@ -4533,7 +4363,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
4533
4363
  '[attr.data-form-mode]': 'formModeDetection().mode',
4534
4364
  '[attr.id]': 'idPrefix() || null',
4535
4365
  '(submit)': 'onNativeSubmit($event)',
4536
- }, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".df-form{display:grid;grid-template-columns:1fr;gap:var(--df-grid-row-gap, .5rem);width:100%}.df-form>*{grid-column:1/-1}.df-row{display:grid;grid-template-columns:repeat(var(--df-grid-columns, 12),1fr);gap:var(--df-grid-gap, .5rem);align-items:start;width:100%}.df-row>*:not([class*=df-col-]){grid-column:1/-1}.df-col-1{grid-column:span 1}.df-col-2{grid-column:span 2}.df-col-3{grid-column:span 3}.df-col-4{grid-column:span 4}.df-col-5{grid-column:span 5}.df-col-6{grid-column:span 6}.df-col-7{grid-column:span 7}.df-col-8{grid-column:span 8}.df-col-9{grid-column:span 9}.df-col-10{grid-column:span 10}.df-col-11{grid-column:span 11}.df-col-12{grid-column:span 12}.df-col-auto{grid-column:span auto;width:auto}.df-col-full{grid-column:1/-1}.df-col-start-1{grid-column-start:1}.df-col-start-2{grid-column-start:2}.df-col-start-3{grid-column-start:3}.df-col-start-4{grid-column-start:4}.df-col-start-5{grid-column-start:5}.df-col-start-6{grid-column-start:6}.df-col-start-7{grid-column-start:7}.df-col-start-8{grid-column-start:8}.df-col-start-9{grid-column-start:9}.df-col-start-10{grid-column-start:10}.df-col-start-11{grid-column-start:11}.df-col-start-12{grid-column-start:12}.df-col-end-1{grid-column-end:1}.df-col-end-2{grid-column-end:2}.df-col-end-3{grid-column-end:3}.df-col-end-4{grid-column-end:4}.df-col-end-5{grid-column-end:5}.df-col-end-6{grid-column-end:6}.df-col-end-7{grid-column-end:7}.df-col-end-8{grid-column-end:8}.df-col-end-9{grid-column-end:9}.df-col-end-10{grid-column-end:10}.df-col-end-11{grid-column-end:11}.df-col-end-12{grid-column-end:12}.df-col-end-13{grid-column-end:13}@media(max-width:576px){.df-form{--df-grid-gap: var(--df-grid-gap-sm, .5rem);--df-grid-row-gap: var(--df-grid-row-gap-sm, .5rem)}.df-row{grid-template-columns:1fr}.df-row>*{grid-column:1/-1!important}.df-row.df-row-mobile-keep-cols{grid-template-columns:repeat(var(--df-grid-columns, 12),1fr)}.df-row.df-row-mobile-keep-cols>*{grid-column:revert!important}}@media(min-width:577px)and (max-width:768px){.df-form{--df-grid-gap: var(--df-grid-gap-md, .5rem);--df-grid-row-gap: var(--df-grid-row-gap-md, .5rem)}.df-row{--df-grid-columns: 6}.df-col-sm-1{grid-column:span 1}.df-col-sm-2{grid-column:span 2}.df-col-sm-3{grid-column:span 3}.df-col-sm-4{grid-column:span 4}.df-col-sm-5{grid-column:span 5}.df-col-sm-6{grid-column:span 6}.df-col-sm-full{grid-column:1/-1}}@media(min-width:769px)and (max-width:992px){.df-form{--df-grid-gap: var(--df-grid-gap-lg, .5rem);--df-grid-row-gap: var(--df-grid-row-gap-lg, .5rem)}.df-col-md-1{grid-column:span 1}.df-col-md-2{grid-column:span 2}.df-col-md-3{grid-column:span 3}.df-col-md-4{grid-column:span 4}.df-col-md-5{grid-column:span 5}.df-col-md-6{grid-column:span 6}.df-col-md-7{grid-column:span 7}.df-col-md-8{grid-column:span 8}.df-col-md-9{grid-column:span 9}.df-col-md-10{grid-column:span 10}.df-col-md-11{grid-column:span 11}.df-col-md-12{grid-column:span 12}.df-col-md-full{grid-column:1/-1}}@media(min-width:993px){.df-form{--df-grid-gap: var(--df-grid-gap-xl, .5rem);--df-grid-row-gap: var(--df-grid-row-gap-xl, .5rem)}.df-col-lg-1{grid-column:span 1}.df-col-lg-2{grid-column:span 2}.df-col-lg-3{grid-column:span 3}.df-col-lg-4{grid-column:span 4}.df-col-lg-5{grid-column:span 5}.df-col-lg-6{grid-column:span 6}.df-col-lg-7{grid-column:span 7}.df-col-lg-8{grid-column:span 8}.df-col-lg-9{grid-column:span 9}.df-col-lg-10{grid-column:span 10}.df-col-lg-11{grid-column:span 11}.df-col-lg-12{grid-column:span 12}.df-col-lg-full{grid-column:1/-1}}.df-gap-none{--df-grid-gap: 0}.df-gap-xs{--df-grid-gap: .25rem}.df-gap-sm{--df-grid-gap: .5rem}.df-gap-md{--df-grid-gap: 1rem}.df-gap-lg{--df-grid-gap: 1.5rem}.df-gap-xl{--df-grid-gap: 2rem}.df-row-gap-none{--df-grid-row-gap: 0}.df-row-gap-xs{--df-grid-row-gap: .25rem}.df-row-gap-sm{--df-grid-row-gap: .5rem}.df-row-gap-md{--df-grid-row-gap: 1rem}.df-row-gap-lg{--df-grid-row-gap: 1.5rem}.df-row-gap-xl{--df-grid-row-gap: 2rem}.df-field{display:block;width:100%;min-width:0;overflow:hidden;margin:0}.df-group,.df-page{display:block;width:100%}.df-form.disabled,.df-row.disabled,.df-field.disabled{opacity:.6;pointer-events:none}.df-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.df-sr-only-focusable:focus,.df-sr-only-focusable:active{position:static;width:auto;height:auto;padding:inherit;margin:inherit;overflow:visible;clip:auto;white-space:normal}.df-live-region{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.df-form :focus-visible{outline:2px solid var(--df-focus-color, #005fcc);outline-offset:2px}.df-group:focus-within{outline:1px dashed var(--df-focus-color, #005fcc);outline-offset:4px}:host{display:grid;grid-template-columns:1fr;gap:var(--df-grid-row-gap, .5rem);width:100%}:host>*{grid-column:1/-1}:host.df-form-spacing-custom{--df-grid-row-gap: var(--df-form-custom-spacing, 1.5rem)}:host.df-form-gap-none{--df-grid-row-gap: 0}:host.df-form-gap-xs{--df-grid-row-gap: .25rem}:host.df-form-gap-sm{--df-grid-row-gap: .5rem}:host.df-form-gap-md{--df-grid-row-gap: 1rem}:host.df-form-gap-lg{--df-grid-row-gap: 1.5rem}:host.df-form-gap-xl{--df-grid-row-gap: 2rem}:host.df-form-paged{gap:0}\n"] }]
4366
+ }, 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"] }]
4537
4367
  }], ctorParameters: () => [], propDecorators: { config: [{ type: i0.Input, args: [{ isSignal: true, alias: "dynamic-form", required: true }] }], formOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "formOptions", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], source: [{ type: i0.Input, args: [{ isSignal: true, alias: "source", required: false }] }], _projectedTemplates: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => DfTemplate), { isSignal: true }] }], validityChange: [{ type: i0.Output, args: ["validityChange"] }], dirtyChange: [{ type: i0.Output, args: ["dirtyChange"] }], submitted: [{ type: i0.Output, args: ["submitted"] }], reset: [{ type: i0.Output, args: ["reset"] }], cleared: [{ type: i0.Output, args: ["cleared"] }], events: [{ type: i0.Output, args: ["events"] }], initialized: [{ type: i0.Output, args: ["initialized"] }], onPageChange: [{ type: i0.Output, args: ["onPageChange"] }], onPageNavigationStateChange: [{ type: i0.Output, args: ["onPageNavigationStateChange"] }] } });
4538
4368
  function countContainersRecursive(fields) {
4539
4369
  let count = 0;
@@ -4604,28 +4434,28 @@ const DISPLAY_FIELD_TYPES_BASE = {
4604
4434
  const BUILT_IN_FIELDS = [
4605
4435
  {
4606
4436
  name: 'row',
4607
- loadComponent: () => import('./ng-forge-dynamic-forms-container-field.component-DZfVWjiM.mjs'),
4437
+ loadComponent: () => import('./ng-forge-dynamic-forms-container-field.component-CM1pY9v5.mjs'),
4608
4438
  mapper: rowFieldMapper,
4609
4439
  valueHandling: 'flatten',
4610
4440
  ...CONTAINER_FIELD_TYPES_BASE,
4611
4441
  },
4612
4442
  {
4613
4443
  name: 'group',
4614
- loadComponent: () => import('./ng-forge-dynamic-forms-group-field.component-CPFgu3pM.mjs'),
4444
+ loadComponent: () => import('./ng-forge-dynamic-forms-group-field.component-D4JhDffB.mjs'),
4615
4445
  mapper: groupFieldMapper,
4616
4446
  valueHandling: 'include',
4617
4447
  ...CONTAINER_FIELD_TYPES_BASE,
4618
4448
  },
4619
4449
  {
4620
4450
  name: 'array',
4621
- loadComponent: () => import('./ng-forge-dynamic-forms-array-field.component-CvjDqYyq.mjs'),
4451
+ loadComponent: () => import('./ng-forge-dynamic-forms-array-field.component-BVnXnaNw.mjs'),
4622
4452
  mapper: arrayFieldMapper,
4623
4453
  valueHandling: 'include',
4624
4454
  ...CONTAINER_FIELD_TYPES_BASE,
4625
4455
  },
4626
4456
  {
4627
4457
  name: 'page',
4628
- loadComponent: () => import('./ng-forge-dynamic-forms-page-field.component-amWhCdHL.mjs'),
4458
+ loadComponent: () => import('./ng-forge-dynamic-forms-page-field.component-Wbz6S8iH.mjs'),
4629
4459
  mapper: pageFieldMapper,
4630
4460
  valueHandling: 'flatten',
4631
4461
  ...CONTAINER_FIELD_TYPES_BASE,
@@ -4643,7 +4473,7 @@ const BUILT_IN_FIELDS = [
4643
4473
  },
4644
4474
  {
4645
4475
  name: 'container',
4646
- loadComponent: () => import('./ng-forge-dynamic-forms-container-field.component-DZfVWjiM.mjs'),
4476
+ loadComponent: () => import('./ng-forge-dynamic-forms-container-field.component-CM1pY9v5.mjs'),
4647
4477
  mapper: containerFieldMapper,
4648
4478
  valueHandling: 'flatten',
4649
4479
  ...CONTAINER_FIELD_TYPES_BASE,
@@ -4841,6 +4671,20 @@ function withValueExclusionDefaults(config) {
4841
4671
  return createFeature('value-exclusion', [{ provide: VALUE_EXCLUSION_DEFAULTS, useValue: resolved }]);
4842
4672
  }
4843
4673
 
4674
+ function withPagePreload(window) {
4675
+ const resolved = clampWindowSize(window, 1);
4676
+ return createFeature('page-preload', [{ provide: PAGE_PRELOAD_WINDOW, useValue: resolved }]);
4677
+ }
4678
+
4679
+ function withFieldWindowing(config) {
4680
+ const resolved = {
4681
+ enabled: true,
4682
+ eager: clampWindowSize(config?.eager, 12),
4683
+ placeholderHeight: config?.placeholderHeight ?? '4rem',
4684
+ };
4685
+ return createFeature('field-windowing', [{ provide: FIELD_WINDOWING, useValue: resolved }]);
4686
+ }
4687
+
4844
4688
  function withValidationExecutionDefaults(config) {
4845
4689
  const resolved = {
4846
4690
  validateWhenHidden: config?.validateWhenHidden ?? false,
@@ -5046,5 +4890,5 @@ function arrayEvent(arrayKey) {
5046
4890
 
5047
4891
 
5048
4892
 
5049
- export { withValueExclusionDefaults as $, ArrayItemRegistryService as A, BUILT_IN_FIELDS as B, CONTAINER_FIELD_PROCESSORS as C, DfFieldOutlet as D, EventDispatcher as E, FormStateManager as F, PrependArrayItemEvent as G, PreviousPageEvent as H, InsertArrayItemEvent as I, TextAddonComponent as J, arrayEvent as K, createWrappers as L, MoveArrayItemEvent as M, NextPageEvent as N, isWrappersBundle as O, PROPERTY_OVERRIDE_STORE as P, provideDynamicForm as Q, RemoveAtIndexEvent as R, ShiftArrayItemEvent as S, TemplateAddonComponent as T, sanitizeFormConfig as U, withAddonActions as V, withCustomAddon as W, withEventFormValue as X, withLegacyStatusClasses as Y, withLoggerConfig as Z, withValidationExecutionDefaults as _, isSameWrapperChain as a, wrapperProps as a0, createWrapperChainController as b, createFieldResolutionPipe as c, createSchemaFromFields as d, createHiddenSignal as e, createRenderReadySignal as f, getNormalizedArrayMetadata as g, getFieldDefaultValue as h, injectFieldRegistry as i, DEV_MODE as j, DEFAULT_DEBOUNCE_MS as k, DERIVATION_WARNING_TRACKER as l, mapFieldToInputs as m, DERIVATION_LOG_CONFIG as n, AppendArrayItemEvent as o, ComponentAddonComponent as p, ConsoleLogger as q, resolveWrappers as r, DfTemplate as s, traverseFieldsWithContext as t, DynamicForm as u, FormClearEvent as v, FormResetEvent as w, FormSubmitEvent as x, PageChangeEvent as y, PopArrayItemEvent as z };
5050
- //# sourceMappingURL=ng-forge-dynamic-forms-ng-forge-dynamic-forms-Du3V5AOb.mjs.map
4893
+ export { withLegacyStatusClasses as $, ArrayItemRegistryService as A, BUILT_IN_FIELDS as B, CONTAINER_FIELD_PROCESSORS as C, DfFieldOutlet as D, EventDispatcher as E, FIELD_WINDOWING as F, PageChangeEvent as G, PopArrayItemEvent as H, InsertArrayItemEvent as I, PrependArrayItemEvent as J, PreviousPageEvent as K, TextAddonComponent as L, MoveArrayItemEvent as M, NextPageEvent as N, arrayEvent as O, PROPERTY_OVERRIDE_STORE as P, createWrappers as Q, RemoveAtIndexEvent as R, ShiftArrayItemEvent as S, TemplateAddonComponent as T, isWrappersBundle as U, provideDynamicForm as V, sanitizeFormConfig as W, withAddonActions as X, withCustomAddon as Y, withEventFormValue as Z, withFieldWindowing as _, isSameWrapperChain as a, withLoggerConfig as a0, withPagePreload as a1, withValidationExecutionDefaults as a2, withValueExclusionDefaults as a3, wrapperProps as a4, createWrapperChainController as b, createFieldResolutionPipe as c, createSchemaFromFields as d, createHiddenSignal as e, createRenderReadySignal as f, getNormalizedArrayMetadata as g, getFieldDefaultValue as h, injectFieldRegistry as i, resolveFieldWindowing as j, DEV_MODE as k, DEFAULT_DEBOUNCE_MS as l, mapFieldToInputs as m, DERIVATION_WARNING_TRACKER as n, FormStateManager as o, DERIVATION_LOG_CONFIG as p, AppendArrayItemEvent as q, resolveWrappers as r, ComponentAddonComponent as s, traverseFieldsWithContext as t, ConsoleLogger as u, DfTemplate as v, DynamicForm as w, FormClearEvent as x, FormResetEvent as y, FormSubmitEvent as z };
4894
+ //# sourceMappingURL=ng-forge-dynamic-forms-ng-forge-dynamic-forms-ZiNaCWHl.mjs.map