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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +11 -11
  2. package/fesm2022/{ng-forge-dynamic-forms-array-field.component-CvjDqYyq.mjs → ng-forge-dynamic-forms-array-field.component-CSCKq_1K.mjs} +9 -13
  3. package/fesm2022/ng-forge-dynamic-forms-array-field.component-CSCKq_1K.mjs.map +1 -0
  4. package/fesm2022/{ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-CcoUnroD.mjs → ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-CTOTwzzw.mjs} +29 -7
  5. package/fesm2022/ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-CTOTwzzw.mjs.map +1 -0
  6. package/fesm2022/ng-forge-dynamic-forms-bootstrap-web-mcp-BtjqeJOj.mjs +1054 -0
  7. package/fesm2022/ng-forge-dynamic-forms-bootstrap-web-mcp-BtjqeJOj.mjs.map +1 -0
  8. package/fesm2022/{ng-forge-dynamic-forms-container-field.component-DZfVWjiM.mjs → ng-forge-dynamic-forms-container-field.component-Dc3ROhHL.mjs} +14 -17
  9. package/fesm2022/ng-forge-dynamic-forms-container-field.component-Dc3ROhHL.mjs.map +1 -0
  10. package/fesm2022/{ng-forge-dynamic-forms-css-wrapper.component-D_yRuLpu.mjs → ng-forge-dynamic-forms-css-wrapper.component-BzgX8lAD.mjs} +5 -5
  11. package/fesm2022/ng-forge-dynamic-forms-css-wrapper.component-BzgX8lAD.mjs.map +1 -0
  12. package/fesm2022/ng-forge-dynamic-forms-debounce-C9F4v1Ke.mjs +17 -0
  13. package/fesm2022/ng-forge-dynamic-forms-debounce-C9F4v1Ke.mjs.map +1 -0
  14. package/fesm2022/ng-forge-dynamic-forms-field-errors-wrapper.component-CIKbWqYA.mjs +25 -0
  15. package/fesm2022/ng-forge-dynamic-forms-field-errors-wrapper.component-CIKbWqYA.mjs.map +1 -0
  16. package/fesm2022/ng-forge-dynamic-forms-form-schema.service-Dx5Dlb15.mjs +24 -0
  17. package/fesm2022/ng-forge-dynamic-forms-form-schema.service-Dx5Dlb15.mjs.map +1 -0
  18. package/fesm2022/{ng-forge-dynamic-forms-group-field.component-CPFgu3pM.mjs → ng-forge-dynamic-forms-group-field.component-DAC3P9DC.mjs} +9 -13
  19. package/fesm2022/ng-forge-dynamic-forms-group-field.component-DAC3P9DC.mjs.map +1 -0
  20. package/fesm2022/ng-forge-dynamic-forms-integration.mjs +49 -107
  21. package/fesm2022/ng-forge-dynamic-forms-integration.mjs.map +1 -1
  22. package/fesm2022/ng-forge-dynamic-forms-internal.mjs +434 -139
  23. package/fesm2022/ng-forge-dynamic-forms-internal.mjs.map +1 -1
  24. package/fesm2022/{ng-forge-dynamic-forms-ng-forge-dynamic-forms-Du3V5AOb.mjs → ng-forge-dynamic-forms-ng-forge-dynamic-forms-CXQe_5td.mjs} +1930 -1847
  25. package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-CXQe_5td.mjs.map +1 -0
  26. package/fesm2022/{ng-forge-dynamic-forms-page-field.component-amWhCdHL.mjs → ng-forge-dynamic-forms-page-field.component-DPKJcwUO.mjs} +94 -15
  27. package/fesm2022/ng-forge-dynamic-forms-page-field.component-DPKJcwUO.mjs.map +1 -0
  28. package/fesm2022/{ng-forge-dynamic-forms-row-wrapper.component-BBWmqQA_.mjs → ng-forge-dynamic-forms-row-wrapper.component-DMEeKZny.mjs} +5 -5
  29. package/fesm2022/ng-forge-dynamic-forms-row-wrapper.component-DMEeKZny.mjs.map +1 -0
  30. package/fesm2022/ng-forge-dynamic-forms-schema-builder-BvDTzfdM.mjs +570 -0
  31. package/fesm2022/ng-forge-dynamic-forms-schema-builder-BvDTzfdM.mjs.map +1 -0
  32. package/fesm2022/{ng-forge-dynamic-forms-text-field.component-C-f80sbk.mjs → ng-forge-dynamic-forms-text-field.component-BB-jutBq.mjs} +4 -4
  33. package/fesm2022/{ng-forge-dynamic-forms-text-field.component-C-f80sbk.mjs.map → ng-forge-dynamic-forms-text-field.component-BB-jutBq.mjs.map} +1 -1
  34. package/fesm2022/ng-forge-dynamic-forms.mjs +2 -2
  35. package/package.json +2 -2
  36. package/types/ng-forge-dynamic-forms-integration.d.ts +15 -33
  37. package/types/ng-forge-dynamic-forms-internal.d.ts +705 -292
  38. package/types/ng-forge-dynamic-forms.d.ts +400 -46
  39. package/fesm2022/ng-forge-dynamic-forms-array-field.component-CvjDqYyq.mjs.map +0 -1
  40. package/fesm2022/ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-CcoUnroD.mjs.map +0 -1
  41. package/fesm2022/ng-forge-dynamic-forms-container-field.component-DZfVWjiM.mjs.map +0 -1
  42. package/fesm2022/ng-forge-dynamic-forms-container-utils-BZFz6ztF.mjs +0 -24
  43. package/fesm2022/ng-forge-dynamic-forms-container-utils-BZFz6ztF.mjs.map +0 -1
  44. package/fesm2022/ng-forge-dynamic-forms-css-wrapper.component-D_yRuLpu.mjs.map +0 -1
  45. package/fesm2022/ng-forge-dynamic-forms-emit-initialization-DhHwU6FJ.mjs +0 -17
  46. package/fesm2022/ng-forge-dynamic-forms-emit-initialization-DhHwU6FJ.mjs.map +0 -1
  47. package/fesm2022/ng-forge-dynamic-forms-group-field.component-CPFgu3pM.mjs.map +0 -1
  48. package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-Du3V5AOb.mjs.map +0 -1
  49. package/fesm2022/ng-forge-dynamic-forms-page-field.component-amWhCdHL.mjs.map +0 -1
  50. package/fesm2022/ng-forge-dynamic-forms-row-wrapper.component-BBWmqQA_.mjs.map +0 -1
@@ -1,32 +1,52 @@
1
1
  import * as rxjs from 'rxjs';
2
- import * as _angular_core from '@angular/core';
3
- import { Type, Injector, Signal, EnvironmentInjector, WritableSignal, Provider, EnvironmentProviders, TemplateRef } from '@angular/core';
4
- import * as _ng_forge_dynamic_forms_internal from '@ng-forge/dynamic-forms/internal';
5
- import { FieldDef, FormEvent, RegisteredFieldTypes, InferFormModel, FormConfig, FormOptions, EventBus, LazyComponentLoader, WrapperTypeDefinition, FieldTypeDefinition, InferFormValue as InferFormValue$1, DerivationLogLevel, Logger, ValueExclusionConfig, ValidationExecutionConfig, TextAddon, WrapperFieldInputs, TemplateAddon, ComponentAddon, BaseAddon, AddonTypeDefinition, AddonActionHandler, ArrayItemDefinition } from '@ng-forge/dynamic-forms/internal';
6
- export { AddonActionContext, AddonActionPreset, AddonSlot, AddonTypeDefinition, AnyAddon, ArrayAllowedChildren, ArrayButtonConfig, ArrayField, ArrayItemTemplate, AsyncCondition, AsyncConditionFunction, AsyncCustomValidator, AsyncDerivationFunction, AsyncValidatorConfig, AvailableFieldTypes, BaseAddon, BaseCheckedField, BaseValidatorConfig, BaseValueField, BuiltInValidatorConfig, CheckedFieldComponent, CommonAddonActionPreset, CommonAddonSlot, ComponentAddon, ConditionalExpression, ContainerAllowedChildren, ContainerField, ContainerFieldTypes, CssWrapper, CustomFnConfig, CustomFunction, CustomValidator, CustomValidatorConfig, DF_FIELD_TEMPLATES, DeclarativeHttpValidatorConfig, DynamicFormActionRegistry, DynamicFormAddonActionPresetRegistry, DynamicFormAddonRegistry, DynamicFormAddonSlotRegistry, DynamicFormError, DynamicFormFieldRegistry, DynamicFormLogger, DynamicText, DynamicValue, EvaluationContext, ExtractField, FieldBoundAddonActionContext, FieldComponent, FieldDef, FieldMeta, FieldOption, FieldPathAccess, FieldRegistryContainers, FieldRegistryLeaves, FieldRegistryWrappers, FieldWithValidation, FieldWrapper, FormConfig, FormEvent, FormEventConstructor, FormMode, FormModeDetectionResult, FormOptions, FormStateCondition, GroupAllowedChildren, GroupField, HiddenField, HiddenScalar, HiddenValue, HttpCondition, HttpCustomValidator, HttpRequestConfig, HttpValidationResponseMapping, InferFormValue, LeafFieldTypes, Logger, LogicConfig, NarrowField, NarrowFields, NextButtonOptions, NoopLogger, OrphanAddonActionContext, PageAllowedChildren, PageField, RegisteredActionRef, RegisteredFieldTypes, ResolvedValidationExecutionConfig, ResolvedValueExclusionConfig, RowAllowedChildren, RowField, SchemaApplicationConfig, SchemaDefinition, SimplifiedArrayField, StateLogicConfig, SubmissionActionResult, SubmissionConfig, SubmitButtonOptions, TemplateAddon, TextAddon, TextElementType, TextField, TextProps, TokenContext, ValidationError, ValidationExecutionConfig, ValidationMessage, ValidationMessageResolver, ValidationMessages, ValidatorConfig, ValueExclusionConfig, ValueFieldComponent, ValueType, WithInputSignals, WrapperConfig, WrapperTypeDefinition, hasFormValue, isArrayField, isContainerField, isDisplayOnlyField, isFieldBoundContext, isFormStateCondition, isGroupField, isHiddenField, isLeafField, isPageField, isRowField, isSimplifiedArrayField, isValueBearingField, isWrapperTypeDefinition, resolveDynamicValue } from '@ng-forge/dynamic-forms/internal';
7
2
  import * as _angular_forms_signals from '@angular/forms/signals';
8
3
  import { FieldTree } from '@angular/forms/signals';
9
- import * as _angular_forms from '@angular/forms';
4
+ import * as _ng_forge_dynamic_forms_internal from '@ng-forge/dynamic-forms/internal';
5
+ import { FormEvent, FieldDef, RegisteredFieldTypes, InferFormModel, FormConfig, FormOptions, EventBus, LazyComponentLoader, WrapperTypeDefinition, FieldTypeDefinition, InferFormValue as InferFormValue$1, DerivationLogLevel, Logger, ValueExclusionConfig, ValidationExecutionConfig, TextAddon, WrapperFieldInputs, TemplateAddon, ComponentAddon, BaseAddon, AddonTypeDefinition, AddonActionHandler, ArrayItemDefinition } from '@ng-forge/dynamic-forms/internal';
6
+ export { AddonActionContext, AddonActionPreset, AddonSlot, AddonTypeDefinition, AnyAddon, ArrayAllowedChildren, ArrayButtonConfig, ArrayField, ArrayItemTemplate, AsyncCondition, AsyncConditionFunction, AsyncCustomValidator, AsyncDerivationFunction, AsyncValidatorConfig, AvailableFieldTypes, BaseAddon, BaseCheckedField, BaseValidatorConfig, BaseValueField, BuiltInValidatorConfig, CheckedFieldComponent, CommonAddonActionPreset, CommonAddonSlot, ComponentAddon, ConditionalExpression, ContainerAllowedChildren, ContainerField, ContainerFieldTypes, CssWrapper, CustomFnConfig, CustomFunction, CustomValidator, CustomValidatorConfig, DF_FIELD_TEMPLATES, DeclarativeHttpValidatorConfig, DynamicFormActionRegistry, DynamicFormAddonActionPresetRegistry, DynamicFormAddonRegistry, DynamicFormAddonSlotRegistry, DynamicFormError, DynamicFormFieldRegistry, DynamicFormLogger, DynamicText, DynamicValue, EvaluationContext, ExtractField, FieldBoundAddonActionContext, FieldComponent, FieldDef, FieldMeta, FieldOption, FieldPathAccess, FieldRegistryContainers, FieldRegistryLeaves, FieldRegistryWrappers, FieldWithValidation, FieldWrapper, FormConfig, FormEvent, FormEventConstructor, FormMode, FormModeDetectionResult, FormOptions, FormStateCondition, GroupAllowedChildren, GroupField, HiddenField, HiddenScalar, HiddenValue, HttpCondition, HttpCustomValidator, HttpRequestConfig, HttpValidationResponseMapping, InferFormValue, InitialPageConfig, LeafFieldTypes, Logger, LogicConfig, NarrowField, NarrowFields, NextButtonOptions, NoopLogger, OrphanAddonActionContext, PageAllowedChildren, PageField, RegisteredActionRef, RegisteredFieldTypes, ResolvedValidationExecutionConfig, ResolvedValueExclusionConfig, RowAllowedChildren, RowErrorSpec, RowField, SchemaApplicationConfig, SchemaDefinition, SimplifiedArrayField, StateLogicConfig, SubmissionActionResult, SubmissionConfig, SubmitButtonOptions, TemplateAddon, TextAddon, TextElementType, TextField, TextProps, TokenContext, ValidationError, ValidationExecutionConfig, ValidationMessage, ValidationMessageResolver, ValidationMessages, ValidatorConfig, ValueExclusionConfig, ValueFieldComponent, ValueType, WebMcpToolOptions, WithInputSignals, WrapperConfig, WrapperTypeDefinition, hasFormValue, isArrayField, isContainerField, isDisplayOnlyField, isFieldBoundContext, isFormStateCondition, isGroupField, isHiddenField, isLeafField, isPageField, isRowField, isSimplifiedArrayField, isValueBearingField, isWrapperTypeDefinition, resolveDynamicValue, rowError } from '@ng-forge/dynamic-forms/internal';
7
+ import * as _angular_core from '@angular/core';
8
+ import { Type, Injector, Signal, EnvironmentInjector, WritableSignal, Provider, EnvironmentProviders, TemplateRef } from '@angular/core';
10
9
  import * as _ng_forge_dynamic_forms from '@ng-forge/dynamic-forms';
11
10
 
12
- /** Resolved field ready for rendering with ngComponentOutlet / DfFieldOutlet. */
13
- interface ResolvedField {
14
- key: string;
15
- /** The original field definition (used by DfFieldOutlet to resolve wrappers). */
16
- fieldDef: FieldDef<unknown>;
17
- component: Type<unknown>;
18
- injector: Injector;
19
- inputs: Signal<Record<string, unknown>>;
20
- renderReady: Signal<boolean>;
21
- /**
22
- * Whether the field is currently hidden. Sourced from the FieldTree's
23
- * `state.hidden()` for value-bearing fields (set by Angular Signal Forms in
24
- * response to `hidden(path, …)` calls), or from the mapper's explicit
25
- * `hidden` input for non-value fields (button, text, container, …). Consumers
26
- * must `@if (!field.hidden())` around `*dfFieldOutlet` so hidden fields are
27
- * removed from the DOM — Angular Signal Forms warns (NG01916) otherwise.
28
- */
29
- hidden: Signal<boolean>;
11
+ /**
12
+ * Global default configuration for progressive field mounting ("field
13
+ * windowing"): fields beyond `eager` render as `@defer (on viewport)`
14
+ * placeholders instead of mounting immediately, and mount only once scrolled
15
+ * near/into view.
16
+ *
17
+ * @internal
18
+ */
19
+ interface FieldWindowingConfig {
20
+ /** Whether field windowing is active. */
21
+ readonly enabled: boolean;
22
+ /** Number of leading fields mounted eagerly (not windowed). */
23
+ readonly eager: number;
24
+ /** CSS `min-height` reserved for each unmounted field's placeholder. */
25
+ readonly placeholderHeight: string;
26
+ /** Parking: hold scrolled-away fields out of change detection, DOM intact. */
27
+ readonly park: FieldParkingConfig;
28
+ }
29
+ /**
30
+ * Parking config. `@defer (on viewport)` only ever mounts — it never unmounts —
31
+ * so without parking a form that has been scrolled through ends up with every
32
+ * field live again, and the windowing win is handed back for the rest of the
33
+ * session. Parking closes that: a field that scrolls away leaves change
34
+ * detection but keeps its DOM, so it stays findable, autofillable and
35
+ * reachable by assistive tech.
36
+ *
37
+ * Off in this default, on whenever `withFieldWindowing()` is used. Parking
38
+ * suspends model → DOM updates for a scrolled-away field until it returns, so
39
+ * it is a rendering behaviour change and is not something a form should get
40
+ * without asking. It exists to bound what windowing leaves mounted, which is
41
+ * why enabling windowing enables it too.
42
+ *
43
+ * @internal
44
+ */
45
+ interface FieldParkingConfig {
46
+ /** Whether scrolled-away fields are held out of change detection. */
47
+ readonly enabled: boolean;
48
+ /** `IntersectionObserver` rootMargin — how far outside the viewport stays live. */
49
+ readonly margin: string;
30
50
  }
31
51
 
32
52
  /** Error from async field component loading. */
@@ -64,24 +84,58 @@ declare class PageChangeEvent implements FormEvent {
64
84
  previousPageIndex?: number | undefined);
65
85
  }
66
86
 
67
- /** State interface for the page orchestrator */
68
- interface PageOrchestratorState {
87
+ /** Navigation state of a paged form, as emitted by `PagerStateEvent`. */
88
+ interface PagerState {
69
89
  /** Current page index (0-based) */
70
90
  currentPageIndex: number;
71
91
  /** Total number of pages */
72
92
  totalPages: number;
73
- /** Whether the current page is the first page */
93
+ /** Whether the current page is the first visible page */
74
94
  isFirstPage: boolean;
75
- /** Whether the current page is the last page */
95
+ /** Whether the current page is the last visible page */
76
96
  isLastPage: boolean;
77
- /** Whether navigation is currently disabled */
78
- navigationDisabled: boolean;
79
97
  }
80
98
 
81
- declare class PageNavigationStateChangeEvent implements FormEvent {
82
- state: PageOrchestratorState;
83
- type: "page-navigation-state-change";
84
- constructor(state: PageOrchestratorState);
99
+ /**
100
+ * Event emitted whenever the pager state of a paged form changes.
101
+ *
102
+ * Unlike `PageChangeEvent`, which fires only when the active page changed, this
103
+ * fires on any change to the derived state — including when `isFirstPage` /
104
+ * `isLastPage` flip because a page's `hidden` logic toggled and the set of
105
+ * visible pages shifted, with the active page never moving.
106
+ */
107
+ declare class PagerStateEvent implements FormEvent {
108
+ state: PagerState;
109
+ readonly type: "pager-state";
110
+ constructor(state: PagerState);
111
+ }
112
+
113
+ /** Emitted after the active page and all of its visible fields have rendered. */
114
+ declare class ActivePageInitializedEvent implements FormEvent {
115
+ readonly pageIndex: number;
116
+ readonly pageKey: string;
117
+ readonly type: "active-page-initialized";
118
+ constructor(pageIndex: number, pageKey: string);
119
+ }
120
+
121
+ /** Resolved field ready for rendering with ngComponentOutlet / DfFieldOutlet. */
122
+ interface ResolvedField {
123
+ key: string;
124
+ /** The original field definition (used by DfFieldOutlet to resolve wrappers). */
125
+ fieldDef: FieldDef<unknown>;
126
+ component: Type<unknown>;
127
+ injector: Injector;
128
+ inputs: Signal<Record<string, unknown>>;
129
+ renderReady: Signal<boolean>;
130
+ /**
131
+ * Whether the field is currently hidden. Sourced from the FieldTree's
132
+ * `state.hidden()` for value-bearing fields (set by Angular Signal Forms in
133
+ * response to `hidden(path, …)` calls), or from the mapper's explicit
134
+ * `hidden` input for non-value fields (button, text, container, …). Consumers
135
+ * must `@if (!field.hidden())` around `*dfFieldOutlet` so hidden fields are
136
+ * removed from the DOM — Angular Signal Forms warns (NG01916) otherwise.
137
+ */
138
+ hidden: Signal<boolean>;
85
139
  }
86
140
 
87
141
  /**
@@ -135,6 +189,7 @@ declare class DynamicForm<TFields extends RegisteredFieldTypes[] = RegisteredFie
135
189
  private logger;
136
190
  private dispatcher;
137
191
  private templateRegistry;
192
+ private globalFieldWindowing;
138
193
  /** All `<ng-template dfTemplate="...">` instances projected into this form. */
139
194
  private readonly _projectedTemplates;
140
195
  private readonly stateManager;
@@ -162,11 +217,21 @@ declare class DynamicForm<TFields extends RegisteredFieldTypes[] = RegisteredFie
162
217
  maxDerivationIterations?: number;
163
218
  submitButton?: _ng_forge_dynamic_forms.SubmitButtonOptions;
164
219
  nextButton?: _ng_forge_dynamic_forms.NextButtonOptions;
220
+ initialPage?: number | _ng_forge_dynamic_forms.InitialPageConfig;
221
+ pagePreloadWindow?: number;
222
+ fieldWindowing?: boolean | {
223
+ eager?: number;
224
+ placeholderHeight?: string;
225
+ park?: boolean | {
226
+ margin?: string;
227
+ };
228
+ };
165
229
  excludeValueIfHidden?: boolean;
166
230
  excludeValueIfDisabled?: boolean;
167
231
  excludeValueIfReadonly?: boolean;
168
232
  validateWhenHidden?: boolean;
169
233
  emitFormValueOnEvents?: boolean;
234
+ webMcp?: _ng_forge_dynamic_forms.WebMcpToolOptions;
170
235
  }>;
171
236
  /** Field signal context for injection into child components */
172
237
  fieldSignalContext: Signal<_ng_forge_dynamic_forms_internal.FieldSignalContext<TModel>>;
@@ -175,7 +240,7 @@ declare class DynamicForm<TFields extends RegisteredFieldTypes[] = RegisteredFie
175
240
  /** The Angular Signal Form instance */
176
241
  form: Signal<FieldTree<TModel, string | number, "writable">>;
177
242
  /** Current form values (reactive) */
178
- formValue: Signal<TModel | (TModel extends infer T ? T extends TModel ? T extends _angular_forms.AbstractControl<unknown, infer TValue extends unknown, any> ? TValue : never : never : never)>;
243
+ formValue: Signal<TModel>;
179
244
  /** Whether the form is currently valid */
180
245
  valid: Signal<boolean>;
181
246
  /** Whether the form is currently invalid */
@@ -195,13 +260,33 @@ declare class DynamicForm<TFields extends RegisteredFieldTypes[] = RegisteredFie
195
260
  /** Whether to render the form template */
196
261
  private derivationReady;
197
262
  shouldRender: Signal<boolean>;
263
+ /**
264
+ * What the experimental WebMCP layer is doing for this form.
265
+ *
266
+ * Injecting it is also what instantiates the gate (component providers are
267
+ * lazy), which is what keeps the registrar lazily loaded. Intentionally absent
268
+ * from `shouldRender`: agent visibility must never gate render.
269
+ *
270
+ * `'disabled'` unless `withExperimentalWebMcp()` is provided; `'idle'` until a
271
+ * config declares `options.webMcp`; `'active'` once its tools are registered.
272
+ * `'failed'` and `'unsupported'` are the two ways registration does not
273
+ * happen, and are worth surfacing rather than guessing at.
274
+ *
275
+ * @experimental
276
+ */
277
+ readonly webMcpStatus: Signal<_ng_forge_dynamic_forms.WebMcpStatus>;
198
278
  /** Resolved fields ready for rendering */
199
279
  protected resolvedFields: Signal<ResolvedField[]>;
200
280
  /**
201
- * Recursively counts container components that will emit ComponentInitializedEvent.
202
- * Includes the dynamic-form component itself (+1).
281
+ * Effective field-windowing config for non-paged forms. Per-form
282
+ * `FormOptions.fieldWindowing` wins over the global `withFieldWindowing()`
283
+ * default; both fall back to disabled (fully eager).
203
284
  */
204
- private totalComponentsCount;
285
+ protected readonly fieldWindowing: Signal<FieldWindowingConfig>;
286
+ private readonly initializedComponents;
287
+ private readonly initialPageReady;
288
+ private readonly initializationReady;
289
+ /** Replayable readiness so late subscribers cannot miss hot EventBus announcements. */
205
290
  initialized$: rxjs.Observable<boolean>;
206
291
  /** Emits when form validity changes. */
207
292
  validityChange: _angular_core.OutputRef<boolean>;
@@ -216,14 +301,23 @@ declare class DynamicForm<TFields extends RegisteredFieldTypes[] = RegisteredFie
216
301
  /** Emits all form events for custom event handling. */
217
302
  events: _angular_core.OutputRef<_ng_forge_dynamic_forms.FormEvent>;
218
303
  /**
219
- * Emits when all form components are initialized and ready for interaction.
220
- * Useful for E2E testing to ensure the form is fully rendered before interaction.
304
+ * Emits when a non-paged form is fully initialized, or when the initially active page of a
305
+ * paged form is ready for interaction. Virtualized inactive pages are not part of initial
306
+ * readiness because they intentionally remain unmounted.
221
307
  */
222
308
  initialized: _angular_core.OutputRef<boolean>;
309
+ /**
310
+ * EXPERIMENTAL. Added alongside page preloading; the emission contract may change.
311
+ *
312
+ * Emits after the currently visible page and all of its visible fields render. Announced
313
+ * once per visit to a page, and again when that page is returned to. Use this when work must
314
+ * run after every page transition; `initialized` emits only for the initial page.
315
+ */
316
+ activePageInitialized: _angular_core.OutputRef<ActivePageInitializedEvent>;
223
317
  /** Emits when the current page changes in paged forms. */
224
318
  onPageChange: _angular_core.OutputRef<PageChangeEvent>;
225
- /** Emits when page navigation state changes (canGoNext, canGoPrevious, etc.). */
226
- onPageNavigationStateChange: _angular_core.OutputRef<PageNavigationStateChangeEvent>;
319
+ /** Emits when pager state changes (currentPageIndex, totalPages, isFirstPage, isLastPage). */
320
+ onPageNavigationStateChange: _angular_core.OutputRef<PagerStateEvent>;
227
321
  constructor();
228
322
  /**
229
323
  * Handles native form submission triggered by:
@@ -234,6 +328,15 @@ declare class DynamicForm<TFields extends RegisteredFieldTypes[] = RegisteredFie
234
328
  * @param event - The native submit event from the form element
235
329
  */
236
330
  protected onNativeSubmit(event: Event): void;
331
+ /**
332
+ * Whether a field should be deferred by field windowing. Containers are never
333
+ * windowed: they emit `ComponentInitializedEvent`, which the form's init
334
+ * tracker counts, so deferring one would stall `initialized` until it scrolls
335
+ * into view. Leaf fields carry no such count, so they defer freely.
336
+ */
337
+ protected windowsField(field: ResolvedField, index: number): boolean;
338
+ /** Class string for a windowed field's placeholder — matches the real field's grid column. */
339
+ protected placeholderGridClass(field: ResolvedField): string;
237
340
  private setupEffects;
238
341
  /**
239
342
  * Populates FORM_STATE_DEPS with this component's input signals, then injects
@@ -244,7 +347,7 @@ declare class DynamicForm<TFields extends RegisteredFieldTypes[] = RegisteredFie
244
347
  private connectDeps;
245
348
  private setupEventHandlers;
246
349
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<DynamicForm<any, any>, never>;
247
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<DynamicForm<any, any>, "form[dynamic-form]", never, { "config": { "alias": "dynamic-form"; "required": true; "isSignal": true; }; "formOptions": { "alias": "formOptions"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; "source": { "alias": "source"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; "validityChange": "validityChange"; "dirtyChange": "dirtyChange"; "submitted": "submitted"; "reset": "reset"; "cleared": "cleared"; "events": "events"; "initialized": "initialized"; "onPageChange": "onPageChange"; "onPageNavigationStateChange": "onPageNavigationStateChange"; }, ["_projectedTemplates"], never, true, never>;
350
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<DynamicForm<any, any>, "form[dynamic-form]", never, { "config": { "alias": "dynamic-form"; "required": true; "isSignal": true; }; "formOptions": { "alias": "formOptions"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; "source": { "alias": "source"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; "validityChange": "validityChange"; "dirtyChange": "dirtyChange"; "submitted": "submitted"; "reset": "reset"; "cleared": "cleared"; "events": "events"; "initialized": "initialized"; "activePageInitialized": "activePageInitialized"; "onPageChange": "onPageChange"; "onPageNavigationStateChange": "onPageNavigationStateChange"; }, ["_projectedTemplates"], never, true, never>;
248
351
  }
249
352
 
250
353
  /**
@@ -450,6 +553,216 @@ declare function withEventFormValue(): DynamicFormFeature<'event-form-value'>;
450
553
  */
451
554
  declare function withValueExclusionDefaults(config?: Partial<ValueExclusionConfig>): DynamicFormFeature<'value-exclusion'>;
452
555
 
556
+ /**
557
+ * Configures the global page preload window for paged forms — how many pages on
558
+ * each side of the current page are mounted during browser idle time.
559
+ *
560
+ * @remarks
561
+ * Paged forms only ever display one page at a time, so mounting every page (the
562
+ * fields, containers, and change-detection of pages the user may never reach)
563
+ * is wasted work: it inflates initial-render time and makes every keystroke pay
564
+ * change detection for off-screen pages. The preload window bounds how much is
565
+ * mounted around the current page. The active page always renders directly;
566
+ * eligible neighbours render on idle. Pages outside the window remain lightweight
567
+ * placeholders until navigation or a window change makes them eligible.
568
+ *
569
+ * **Precedence:**
570
+ * 1. Per-form `pagePreloadWindow` on `FormOptions` — wins for that form
571
+ * 2. Global `withPagePreload(n)` — baseline default for all forms
572
+ * 3. No feature — token default (`1`, i.e. current page ±1)
573
+ *
574
+ * @param window - Pages to preload on idle on each side of the current page. `0`
575
+ * mounts only the current page; `1` (default) mounts ±1 for flicker-free sequential
576
+ * navigation; higher values pre-warm more pages for jump navigation. Negative
577
+ * values are clamped to `0`.
578
+ * @returns A DynamicFormFeature that configures the global preload window
579
+ *
580
+ * @example
581
+ * ```typescript
582
+ * provideDynamicForm(...withMaterialFields(), withPagePreload(2));
583
+ * ```
584
+ */
585
+ declare function withPagePreload(window: number): DynamicFormFeature<'page-preload'>;
586
+
587
+ /** Per-form override shape for parking. `false` opts out; an object tunes the margin. */
588
+ type FieldParkingOption = boolean | {
589
+ /** IntersectionObserver root margin. Supports one to four `px` or `%` values. */
590
+ margin?: string;
591
+ };
592
+
593
+ /**
594
+ * Configures global progressive field mounting ("field windowing") for large
595
+ * flat (single-page) forms.
596
+ *
597
+ * @remarks
598
+ * A flat form mounts every field eagerly, so a large form pays the full
599
+ * initial-render cost up front and every keystroke walks all N live views —
600
+ * even for fields far off-screen. Field windowing bounds how much is mounted:
601
+ * the first `eager` fields render immediately, and the rest render as a
602
+ * lightweight placeholder that mounts the moment it scrolls near/into view.
603
+ * Validators, derivations, and values live at the Signal Forms schema level
604
+ * (not the DOM), so unmounted fields keep full form semantics.
605
+ *
606
+ * Mounting is one-way — `@defer (on viewport)` never unmounts — so on its own
607
+ * that bound is temporary: scroll a long form once and every field is live
608
+ * again for the rest of the session. **Parking** closes the gap and comes on
609
+ * with this feature (it is off when the feature is not used, since it changes
610
+ * rendering behaviour). A field scrolled outside `park.margin` leaves change detection but
611
+ * keeps its DOM, so it costs nothing per keystroke while staying findable by
612
+ * browser search, fillable by autofill, and reachable by assistive tech. Its
613
+ * own edits still reach the model: Angular binds `input` / `blur` with plain
614
+ * DOM listeners, which keep firing on a detached view.
615
+ *
616
+ * **Precedence:**
617
+ * 1. Per-form `fieldWindowing` on `FormOptions` — wins for that form
618
+ * 2. Global `withFieldWindowing(...)` — baseline default for all forms
619
+ * 3. No feature — token default (disabled, i.e. fully eager)
620
+ *
621
+ * @param config - `eager` (default `12`), `placeholderHeight` (default `'4rem'`)
622
+ * and `park` (default on, margin `'100%'`) overrides. `eager` is clamped to a
623
+ * non-negative integer.
624
+ * @returns A DynamicFormFeature that enables the global field windowing default
625
+ *
626
+ * @example
627
+ * ```typescript
628
+ * provideDynamicForm(...withMaterialFields(), withFieldWindowing({ eager: 20 }));
629
+ * ```
630
+ */
631
+ declare function withFieldWindowing(config?: {
632
+ eager?: number;
633
+ placeholderHeight?: string;
634
+ park?: FieldParkingOption;
635
+ }): DynamicFormFeature<'field-windowing'>;
636
+
637
+ /**
638
+ * Enables WebMCP tool registration for forms that declare `options.webMcp`.
639
+ *
640
+ * @experimental
641
+ *
642
+ * @remarks
643
+ * WebMCP lets an AI agent running in the browser discover and drive a form
644
+ * through structured tools rather than by simulating clicks. Each opted-in form
645
+ * registers `fill_{name}`, which applies a partial patch of values to the form
646
+ * and reports back what it set, which fields apply, which are still empty, and
647
+ * any validation errors. Submission is a separate, per-form opt-in
648
+ * (`options.webMcp.allowSubmit`); without it the agent stages values and a human
649
+ * presses the button.
650
+ *
651
+ * The tool schema is generated from the form config, so agents see labels,
652
+ * option enums with their human titles, and static validator constraints rather
653
+ * than the bare types value-shape inference would produce. Agent arguments are
654
+ * validated in code before anything is written, and a call that does not parse
655
+ * is rejected whole rather than half-applied.
656
+ *
657
+ * Nothing is registered unless a form opts in, and the registrar module is
658
+ * dynamically imported, so a form without `options.webMcp` never loads it — the
659
+ * feature itself, the token and the form-scoped hook that decides are part of
660
+ * the main bundle. Registration is browser-only and no-ops where the page
661
+ * exposes no model context.
662
+ *
663
+ * Named `experimental` on purpose: WebMCP is a proposed standard, in an origin
664
+ * trial as of Chrome 149, and the browser surface underneath this may still
665
+ * change. Expect breaking changes outside a major version.
666
+ *
667
+ * @returns A DynamicFormFeature enabling WebMCP registration.
668
+ *
669
+ * @example
670
+ * ```typescript
671
+ * provideDynamicForm(...withMaterialFields(), withExperimentalWebMcp());
672
+ *
673
+ * const config = {
674
+ * options: { webMcp: { name: 'signup', description: 'Sign a new user up.' } },
675
+ * fields: [...],
676
+ * } as const satisfies FormConfig;
677
+ * ```
678
+ */
679
+ declare function withExperimentalWebMcp(): DynamicFormFeature<'web-mcp'>;
680
+
681
+ /**
682
+ * What the WebMCP layer is currently doing for this form. Reported by
683
+ * `DynamicForm.webMcpStatus()`; `'active'` is the only state in which an agent
684
+ * can see the form.
685
+ *
686
+ * @experimental
687
+ */
688
+ type WebMcpStatus =
689
+ /** `withExperimentalWebMcp()` is not provided, or this is the server. */
690
+ 'disabled'
691
+ /** Enabled, but the current config does not declare `options.webMcp`. */
692
+ | 'idle'
693
+ /** Loading the registrar or awaiting `registerTool()`. */
694
+ | 'registering'
695
+ /** Tools are registered against the browser's model context. */
696
+ | 'active'
697
+ /** The browser exposes no model context, so there is nothing to register against. */
698
+ | 'unsupported'
699
+ /** Registration was attempted and rejected. See the console for the reason. */
700
+ | 'failed';
701
+
702
+ /**
703
+ * How a submission ended.
704
+ *
705
+ * Dispatching `FormSubmitEvent` is fire-and-forget: the submission pipeline
706
+ * drops a second submission through `exhaustMap`, skips submission entirely when
707
+ * the form is invalid or async validators are still pending, and swallows an
708
+ * asynchronous action failure to keep the stream alive. None of that is visible
709
+ * to the caller, which is fine for a button click — the user is watching the
710
+ * page — and wrong for anything that has to report back, an agent tool call
711
+ * above all.
712
+ *
713
+ * @experimental
714
+ */
715
+ type SubmissionOutcome = {
716
+ readonly status: 'success';
717
+ }
718
+ /** No `submission.action` is configured; the `(submitted)` output received the value. */
719
+ | {
720
+ readonly status: 'dispatched';
721
+ }
722
+ /** The form was invalid, so nothing was submitted. */
723
+ | {
724
+ readonly status: 'validation-failed';
725
+ }
726
+ /** Async validators had not resolved, so submission was skipped. */
727
+ | {
728
+ readonly status: 'pending-validation';
729
+ }
730
+ /** The action ran and came back with server-side validation errors. */
731
+ | {
732
+ readonly status: 'server-errors';
733
+ }
734
+ /** The action threw or rejected. */
735
+ | {
736
+ readonly status: 'action-failed';
737
+ readonly error: unknown;
738
+ }
739
+ /** Another submission was already in flight and this one was dropped. */
740
+ | {
741
+ readonly status: 'busy';
742
+ }
743
+ /** The form went away before the submission settled. */
744
+ | {
745
+ readonly status: 'cancelled';
746
+ };
747
+ /**
748
+ * The reply channel a caller attaches to `FormSubmitEvent` when it needs to know
749
+ * what happened.
750
+ *
751
+ * `accept()` is called by the submission handler the moment it takes the event.
752
+ * Because the event bus dispatches synchronously, a caller that finds its reply
753
+ * un-accepted once `dispatch()` returns knows the pipeline dropped the
754
+ * submission rather than merely not having finished it yet — which is the only
755
+ * way to tell "busy" apart from "still running".
756
+ *
757
+ * @experimental
758
+ */
759
+ interface SubmissionReply {
760
+ /** Marks the submission as taken by the pipeline. */
761
+ accept(): void;
762
+ /** Reports the final outcome. The first call wins; later calls are ignored. */
763
+ settle(outcome: SubmissionOutcome): void;
764
+ }
765
+
453
766
  /**
454
767
  * Configures global validation execution defaults — when validators should run
455
768
  * relative to a field's reactive state.
@@ -635,9 +948,50 @@ interface SanitizedFormConfig {
635
948
  */
636
949
  declare function sanitizeFormConfig(config: FormConfig, options?: SanitizeFormConfigOptions): SanitizedFormConfig;
637
950
 
638
- /** Event dispatched to submit the form (the action behind a `submit` button). */
951
+ /** Per-dispatch overrides for a page jump. */
952
+ interface PageNavigationOptions {
953
+ /**
954
+ * Whether to apply the forward-jump validity gate.
955
+ *
956
+ * Set `false` to land on the target regardless of earlier pages, which is what
957
+ * restoring a saved session wants. Bounds and hidden-page checks still apply.
958
+ *
959
+ * @default true
960
+ */
961
+ validate?: boolean;
962
+ }
963
+ /**
964
+ * Event dispatched to move a paged form to a specific page.
965
+ *
966
+ * Backward jumps are unconditional. Forward jumps validate every visible page
967
+ * between the current one and the target (the target itself is not validated);
968
+ * if one is invalid, navigation stops on the first invalid page. Pass
969
+ * `{ validate: false }` to skip that gate and land on the target.
970
+ */
971
+ declare class GoToPageEvent implements FormEvent {
972
+ /** The target page index (0-based) */
973
+ readonly pageIndex: number;
974
+ /** Per-dispatch overrides; omit for the default gated behavior. */
975
+ readonly options?: PageNavigationOptions | undefined;
976
+ readonly type: "go-to-page";
977
+ constructor(
978
+ /** The target page index (0-based) */
979
+ pageIndex: number,
980
+ /** Per-dispatch overrides; omit for the default gated behavior. */
981
+ options?: PageNavigationOptions | undefined);
982
+ }
983
+
984
+ /**
985
+ * Event dispatched to submit the form (the action behind a `submit` button).
986
+ *
987
+ * A caller that needs to know how the submission ended passes a
988
+ * {@link SubmissionReply}; the submission handler reports back through it. A
989
+ * button click passes nothing and stays fire-and-forget, as before.
990
+ */
639
991
  declare class FormSubmitEvent implements FormEvent {
992
+ readonly reply?: SubmissionReply | undefined;
640
993
  readonly type: "submit";
994
+ constructor(reply?: SubmissionReply | undefined);
641
995
  }
642
996
 
643
997
  /** Event dispatched to advance a paged form to the next page. */
@@ -878,5 +1232,5 @@ interface ArrayItemContext {
878
1232
  formValue?: unknown;
879
1233
  }
880
1234
 
881
- export { AppendArrayItemEvent, BUILT_IN_FIELDS, ComponentAddonComponent, ConsoleLogger, DfTemplate, DynamicForm, EventDispatcher, FormClearEvent, FormResetEvent, FormSubmitEvent, InsertArrayItemEvent, MoveArrayItemEvent, NextPageEvent, PageChangeEvent, PopArrayItemEvent, PrependArrayItemEvent, PreviousPageEvent, RemoveAtIndexEvent, ShiftArrayItemEvent, TemplateAddonComponent, TextAddonComponent, arrayEvent, createWrappers, isWrappersBundle, provideDynamicForm, sanitizeFormConfig, withAddonActions, withCustomAddon, withEventFormValue, withLegacyStatusClasses, withLoggerConfig, withValidationExecutionDefaults, withValueExclusionDefaults, wrapperProps };
882
- export type { AddonActionsFeature, AddonWarning, ArrayItemContext, ArrayItemDefinitionTemplate, ExtractFieldDefs, ExtractFormValue, InferWrapperRegistry, SanitizeFormConfigOptions, SanitizedFormConfig, WrapperRegistration, WrappersBundle };
1235
+ export { ActivePageInitializedEvent, AppendArrayItemEvent, BUILT_IN_FIELDS, ComponentAddonComponent, ConsoleLogger, DfTemplate, DynamicForm, EventDispatcher, FormClearEvent, FormResetEvent, FormSubmitEvent, GoToPageEvent, InsertArrayItemEvent, MoveArrayItemEvent, NextPageEvent, PageChangeEvent, PagerStateEvent, PopArrayItemEvent, PrependArrayItemEvent, PreviousPageEvent, RemoveAtIndexEvent, ShiftArrayItemEvent, TemplateAddonComponent, TextAddonComponent, arrayEvent, createWrappers, isWrappersBundle, provideDynamicForm, sanitizeFormConfig, withAddonActions, withCustomAddon, withEventFormValue, withExperimentalWebMcp, withFieldWindowing, withLegacyStatusClasses, withLoggerConfig, withPagePreload, withValidationExecutionDefaults, withValueExclusionDefaults, wrapperProps };
1236
+ export type { AddonActionsFeature, AddonWarning, ArrayItemContext, ArrayItemDefinitionTemplate, ExtractFieldDefs, ExtractFormValue, FieldParkingOption, InferWrapperRegistry, PageNavigationOptions, PagerState, SanitizeFormConfigOptions, SanitizedFormConfig, SubmissionOutcome, SubmissionReply, WebMcpStatus, WrapperRegistration, WrappersBundle };