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

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 (18) hide show
  1. package/README.md +11 -11
  2. package/fesm2022/{ng-forge-dynamic-forms-array-field.component-B9OfjKPI.mjs → ng-forge-dynamic-forms-array-field.component-D-VWBM-t.mjs} +2 -2
  3. package/fesm2022/{ng-forge-dynamic-forms-array-field.component-B9OfjKPI.mjs.map → ng-forge-dynamic-forms-array-field.component-D-VWBM-t.mjs.map} +1 -1
  4. package/fesm2022/{ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-VJBOH393.mjs → ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-OBDy6qd9.mjs} +2 -2
  5. package/fesm2022/{ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-VJBOH393.mjs.map → ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-OBDy6qd9.mjs.map} +1 -1
  6. package/fesm2022/{ng-forge-dynamic-forms-container-field.component-o49q6akV.mjs → ng-forge-dynamic-forms-container-field.component-BpeFyhAt.mjs} +2 -2
  7. package/fesm2022/{ng-forge-dynamic-forms-container-field.component-o49q6akV.mjs.map → ng-forge-dynamic-forms-container-field.component-BpeFyhAt.mjs.map} +1 -1
  8. package/fesm2022/{ng-forge-dynamic-forms-group-field.component-DA-FW30T.mjs → ng-forge-dynamic-forms-group-field.component-BNjYMTqT.mjs} +2 -2
  9. package/fesm2022/{ng-forge-dynamic-forms-group-field.component-DA-FW30T.mjs.map → ng-forge-dynamic-forms-group-field.component-BNjYMTqT.mjs.map} +1 -1
  10. package/fesm2022/{ng-forge-dynamic-forms-ng-forge-dynamic-forms-CUihkwGC.mjs → ng-forge-dynamic-forms-ng-forge-dynamic-forms-DSXZJHcu.mjs} +194 -144
  11. package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-DSXZJHcu.mjs.map +1 -0
  12. package/fesm2022/{ng-forge-dynamic-forms-page-field.component-P9O4eNvS.mjs → ng-forge-dynamic-forms-page-field.component-B92kLZkH.mjs} +2 -2
  13. package/fesm2022/{ng-forge-dynamic-forms-page-field.component-P9O4eNvS.mjs.map → ng-forge-dynamic-forms-page-field.component-B92kLZkH.mjs.map} +1 -1
  14. package/fesm2022/ng-forge-dynamic-forms.mjs +1 -1
  15. package/package.json +1 -1
  16. package/types/ng-forge-dynamic-forms-internal.d.ts +30 -1
  17. package/types/ng-forge-dynamic-forms.d.ts +55 -15
  18. package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-CUihkwGC.mjs.map +0 -1
@@ -1,6 +1,6 @@
1
1
  import * as i0 from '@angular/core';
2
2
  import { inject, DestroyRef, Injector, EnvironmentInjector, viewChild, TemplateRef, ViewContainerRef, input, computed, ChangeDetectionStrategy, Component } from '@angular/core';
3
- import { i as injectFieldRegistry, c as createFieldResolutionPipe, r as resolveWrappers, a as isSameWrapperChain, b as createWrapperChainController, D as DfFieldOutlet } from './ng-forge-dynamic-forms-ng-forge-dynamic-forms-CUihkwGC.mjs';
3
+ import { i as injectFieldRegistry, c as createFieldResolutionPipe, r as resolveWrappers, a as isSameWrapperChain, b as createWrapperChainController, D as DfFieldOutlet } from './ng-forge-dynamic-forms-ng-forge-dynamic-forms-DSXZJHcu.mjs';
4
4
  import { EventBus, DynamicFormLogger, WRAPPER_AUTO_ASSOCIATIONS, DEFAULT_WRAPPERS, derivedFromDeferred } from '@ng-forge/dynamic-forms/internal';
5
5
  import { c as computeContainerHostClasses, s as setupContainerInitEffect } from './ng-forge-dynamic-forms-container-utils-BZFz6ztF.mjs';
6
6
 
@@ -86,4 +86,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
86
86
  }], ctorParameters: () => [], propDecorators: { childrenTpl: [{ type: i0.ViewChild, args: ['childrenTpl', { ...{ read: TemplateRef }, isSignal: true }] }], wrapperContainer: [{ type: i0.ViewChild, args: ['wrapperContainer', { ...{ read: ViewContainerRef }, isSignal: true }] }], field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: true }] }], key: [{ type: i0.Input, args: [{ isSignal: true, alias: "key", required: true }] }], className: [{ type: i0.Input, args: [{ isSignal: true, alias: "className", required: false }] }], hidden: [{ type: i0.Input, args: [{ isSignal: true, alias: "hidden", required: false }] }] } });
87
87
 
88
88
  export { ContainerFieldComponent, ContainerFieldComponent as default };
89
- //# sourceMappingURL=ng-forge-dynamic-forms-container-field.component-o49q6akV.mjs.map
89
+ //# sourceMappingURL=ng-forge-dynamic-forms-container-field.component-BpeFyhAt.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"ng-forge-dynamic-forms-container-field.component-o49q6akV.mjs","mappings":";;;;;;AAoDc;AACK;;AAEA;AACE;AACF;AACA;AACA;;AAGA;AACA;;;;;AAIjB;;;;AAGS;;AACA;;;;AAGQ;;AAEE;AAGf;AACA;;;AAGA;AACE;;;AAKI;;AAIP;AAIc;AAIjB;;AAGE;;;AAGE;AACD;;;AAxDgB;;;;;;;;;AAZlB;;;;AAXS;;;;;;;;;;AAcR;AACA;AACA;AACA;AACA;AACA;;AAcgD","names":[],"ignoreList":[],"sources":["../../../../packages/dynamic-forms/src/lib/fields/container/container-field.component.ts"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n computed,\n DestroyRef,\n EnvironmentInjector,\n inject,\n Injector,\n input,\n TemplateRef,\n viewChild,\n ViewContainerRef,\n} from '@angular/core';\nimport { DfFieldOutlet } from '../../directives/df-field-outlet/df-field-outlet.directive';\nimport { derivedFromDeferred } from '@ng-forge/dynamic-forms/internal';\nimport { createFieldResolutionPipe, ResolvedField } from '../../utils/resolve-field/resolve-field';\nimport { computeContainerHostClasses, setupContainerInitEffect } from '../../utils/container-utils/container-utils';\nimport { ContainerField } from '@ng-forge/dynamic-forms/internal';\nimport { injectFieldRegistry } from '../../utils/inject-field-registry/inject-field-registry';\nimport { EventBus } from '@ng-forge/dynamic-forms/internal';\nimport { FieldDef } from '@ng-forge/dynamic-forms/internal';\nimport { DynamicFormLogger } from '@ng-forge/dynamic-forms/internal';\nimport { WRAPPER_AUTO_ASSOCIATIONS } from '@ng-forge/dynamic-forms/internal';\nimport { DEFAULT_WRAPPERS } from '@ng-forge/dynamic-forms/internal';\nimport { isSameWrapperChain, resolveWrappers } from '../../utils/resolve-wrappers/resolve-wrappers';\nimport { createWrapperChainController } from '../../utils/wrapper-chain/wrapper-chain-controller';\n\n/** Layout container that wraps child fields with UI chrome. */\n@Component({\n selector: 'div[container-field]',\n imports: [DfFieldOutlet],\n template: `\n <ng-template #childrenTpl>\n @for (field of resolvedFields(); track field.key) {\n @if (!field.hidden()) {\n <ng-container *dfFieldOutlet=\"field; environmentInjector: environmentInjector\" />\n }\n }\n </ng-template>\n <ng-container #wrapperContainer></ng-container>\n `,\n styleUrl: './container-field.component.scss',\n host: {\n '[class]': 'hostClasses()',\n '[class.disabled]': 'disabled()',\n '[class.df-container-hidden]': 'hidden()',\n '[attr.aria-hidden]': 'hidden() || null',\n '[id]': '`${key()}`',\n '[attr.data-testid]': 'key()',\n },\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport default class ContainerFieldComponent {\n private readonly destroyRef = inject(DestroyRef);\n private readonly fieldRegistry = injectFieldRegistry();\n private readonly injector = inject(Injector);\n protected readonly environmentInjector = inject(EnvironmentInjector);\n private readonly eventBus = inject(EventBus);\n private readonly logger = inject(DynamicFormLogger);\n private readonly wrapperAutoAssociations = inject(WRAPPER_AUTO_ASSOCIATIONS);\n private readonly defaultWrappersSignal = inject(DEFAULT_WRAPPERS, { optional: true });\n\n private readonly childrenTpl = viewChild.required('childrenTpl', { read: TemplateRef });\n private readonly wrapperContainer = viewChild.required('wrapperContainer', { read: ViewContainerRef });\n\n field = input.required<ContainerField>();\n key = input.required<string>();\n className = input<string>();\n hidden = input(false);\n\n readonly hostClasses = computed(() => computeContainerHostClasses('container', this.className()));\n readonly disabled = computed(() => this.field().disabled || false);\n\n private readonly rawFieldRegistry = computed(() => this.fieldRegistry.raw);\n private readonly fieldsSource = computed(() => (this.field().fields || []) as FieldDef<unknown>[]);\n\n protected readonly resolvedFields = derivedFromDeferred(\n this.fieldsSource,\n createFieldResolutionPipe(() => ({\n loadTypeComponent: (type: string) => this.fieldRegistry.loadTypeComponent(type),\n registry: this.rawFieldRegistry(),\n injector: this.injector,\n destroyRef: this.destroyRef,\n onError: (fieldDef: FieldDef<unknown>, error: unknown) => {\n const fieldKey = fieldDef.key || '<no key>';\n\n const containerKey = this.field().key || '<no key>';\n this.logger.error(\n `Failed to load component for field type '${fieldDef.type}' (key: ${fieldKey}) ` +\n `within container '${containerKey}'. Ensure the field type is registered in your field registry.`,\n error,\n );\n },\n })),\n { initialValue: [] as ResolvedField[], injector: this.injector },\n );\n\n private readonly wrappers = computed(() => resolveWrappers(this.field(), this.defaultWrappersSignal?.(), this.wrapperAutoAssociations), {\n equal: isSameWrapperChain,\n });\n\n constructor() {\n setupContainerInitEffect(this.resolvedFields, this.eventBus, 'container', () => this.field().key, this.injector);\n\n createWrapperChainController({\n vcr: this.wrapperContainer,\n wrappers: this.wrappers,\n renderInnermost: (slot) => slot.createEmbeddedView(this.childrenTpl()),\n });\n }\n}\n\nexport { ContainerFieldComponent };\n"]}
1
+ {"version":3,"file":"ng-forge-dynamic-forms-container-field.component-BpeFyhAt.mjs","mappings":";;;;;;AAoDc;AACK;;AAEA;AACE;AACF;AACA;AACA;;AAGA;AACA;;;;;AAIjB;;;;AAGS;;AACA;;;;AAGQ;;AAEE;AAGf;AACA;;;AAGA;AACE;;;AAKI;;AAIP;AAIc;AAIjB;;AAGE;;;AAGE;AACD;;;AAxDgB;;;;;;;;;AAZlB;;;;AAXS;;;;;;;;;;AAcR;AACA;AACA;AACA;AACA;AACA;;AAcgD","names":[],"ignoreList":[],"sources":["../../../../packages/dynamic-forms/src/lib/fields/container/container-field.component.ts"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n computed,\n DestroyRef,\n EnvironmentInjector,\n inject,\n Injector,\n input,\n TemplateRef,\n viewChild,\n ViewContainerRef,\n} from '@angular/core';\nimport { DfFieldOutlet } from '../../directives/df-field-outlet/df-field-outlet.directive';\nimport { derivedFromDeferred } from '@ng-forge/dynamic-forms/internal';\nimport { createFieldResolutionPipe, ResolvedField } from '../../utils/resolve-field/resolve-field';\nimport { computeContainerHostClasses, setupContainerInitEffect } from '../../utils/container-utils/container-utils';\nimport { ContainerField } from '@ng-forge/dynamic-forms/internal';\nimport { injectFieldRegistry } from '../../utils/inject-field-registry/inject-field-registry';\nimport { EventBus } from '@ng-forge/dynamic-forms/internal';\nimport { FieldDef } from '@ng-forge/dynamic-forms/internal';\nimport { DynamicFormLogger } from '@ng-forge/dynamic-forms/internal';\nimport { WRAPPER_AUTO_ASSOCIATIONS } from '@ng-forge/dynamic-forms/internal';\nimport { DEFAULT_WRAPPERS } from '@ng-forge/dynamic-forms/internal';\nimport { isSameWrapperChain, resolveWrappers } from '../../utils/resolve-wrappers/resolve-wrappers';\nimport { createWrapperChainController } from '../../utils/wrapper-chain/wrapper-chain-controller';\n\n/** Layout container that wraps child fields with UI chrome. */\n@Component({\n selector: 'div[container-field]',\n imports: [DfFieldOutlet],\n template: `\n <ng-template #childrenTpl>\n @for (field of resolvedFields(); track field.key) {\n @if (!field.hidden()) {\n <ng-container *dfFieldOutlet=\"field; environmentInjector: environmentInjector\" />\n }\n }\n </ng-template>\n <ng-container #wrapperContainer></ng-container>\n `,\n styleUrl: './container-field.component.scss',\n host: {\n '[class]': 'hostClasses()',\n '[class.disabled]': 'disabled()',\n '[class.df-container-hidden]': 'hidden()',\n '[attr.aria-hidden]': 'hidden() || null',\n '[id]': '`${key()}`',\n '[attr.data-testid]': 'key()',\n },\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport default class ContainerFieldComponent {\n private readonly destroyRef = inject(DestroyRef);\n private readonly fieldRegistry = injectFieldRegistry();\n private readonly injector = inject(Injector);\n protected readonly environmentInjector = inject(EnvironmentInjector);\n private readonly eventBus = inject(EventBus);\n private readonly logger = inject(DynamicFormLogger);\n private readonly wrapperAutoAssociations = inject(WRAPPER_AUTO_ASSOCIATIONS);\n private readonly defaultWrappersSignal = inject(DEFAULT_WRAPPERS, { optional: true });\n\n private readonly childrenTpl = viewChild.required('childrenTpl', { read: TemplateRef });\n private readonly wrapperContainer = viewChild.required('wrapperContainer', { read: ViewContainerRef });\n\n field = input.required<ContainerField>();\n key = input.required<string>();\n className = input<string>();\n hidden = input(false);\n\n readonly hostClasses = computed(() => computeContainerHostClasses('container', this.className()));\n readonly disabled = computed(() => this.field().disabled || false);\n\n private readonly rawFieldRegistry = computed(() => this.fieldRegistry.raw);\n private readonly fieldsSource = computed(() => (this.field().fields || []) as FieldDef<unknown>[]);\n\n protected readonly resolvedFields = derivedFromDeferred(\n this.fieldsSource,\n createFieldResolutionPipe(() => ({\n loadTypeComponent: (type: string) => this.fieldRegistry.loadTypeComponent(type),\n registry: this.rawFieldRegistry(),\n injector: this.injector,\n destroyRef: this.destroyRef,\n onError: (fieldDef: FieldDef<unknown>, error: unknown) => {\n const fieldKey = fieldDef.key || '<no key>';\n\n const containerKey = this.field().key || '<no key>';\n this.logger.error(\n `Failed to load component for field type '${fieldDef.type}' (key: ${fieldKey}) ` +\n `within container '${containerKey}'. Ensure the field type is registered in your field registry.`,\n error,\n );\n },\n })),\n { initialValue: [] as ResolvedField[], injector: this.injector },\n );\n\n private readonly wrappers = computed(() => resolveWrappers(this.field(), this.defaultWrappersSignal?.(), this.wrapperAutoAssociations), {\n equal: isSameWrapperChain,\n });\n\n constructor() {\n setupContainerInitEffect(this.resolvedFields, this.eventBus, 'container', () => this.field().key, this.injector);\n\n createWrapperChainController({\n vcr: this.wrapperContainer,\n wrappers: this.wrappers,\n renderInnermost: (slot) => slot.createEmbeddedView(this.childrenTpl()),\n });\n }\n}\n\nexport { ContainerFieldComponent };\n"]}
@@ -1,6 +1,6 @@
1
1
  import * as i0 from '@angular/core';
2
2
  import { inject, DestroyRef, Injector, EnvironmentInjector, input, computed, linkedSignal, runInInjectionContext, untracked, ChangeDetectionStrategy, Component } from '@angular/core';
3
- import { i as injectFieldRegistry, C as CONTAINER_FIELD_PROCESSORS, d as createSchemaFromFields, c as createFieldResolutionPipe, D as DfFieldOutlet } from './ng-forge-dynamic-forms-ng-forge-dynamic-forms-CUihkwGC.mjs';
3
+ import { i as injectFieldRegistry, C as CONTAINER_FIELD_PROCESSORS, d as createSchemaFromFields, c as createFieldResolutionPipe, D as DfFieldOutlet } from './ng-forge-dynamic-forms-ng-forge-dynamic-forms-DSXZJHcu.mjs';
4
4
  import { outputFromObservable, toObservable } from '@angular/core/rxjs-interop';
5
5
  import { explicitEffect } from 'ngxtension/explicit-effect';
6
6
  import { injectFieldSignalContext, GROUP_CONTEXT, EventBus, DynamicFormLogger, isEqual, FIELD_SIGNAL_CONTEXT, derivedFromDeferred } from '@ng-forge/dynamic-forms/internal';
@@ -178,4 +178,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
178
178
  }], ctorParameters: () => [], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: true }] }], key: [{ type: i0.Input, args: [{ isSignal: true, alias: "key", required: true }] }], className: [{ type: i0.Input, args: [{ isSignal: true, alias: "className", required: false }] }], hidden: [{ type: i0.Input, args: [{ isSignal: true, alias: "hidden", required: false }] }], validityChange: [{ type: i0.Output, args: ["validityChange"] }], dirtyChange: [{ type: i0.Output, args: ["dirtyChange"] }], submitted: [{ type: i0.Output, args: ["submitted"] }] } });
179
179
 
180
180
  export { GroupFieldComponent, GroupFieldComponent as default };
181
- //# sourceMappingURL=ng-forge-dynamic-forms-group-field.component-DA-FW30T.mjs.map
181
+ //# sourceMappingURL=ng-forge-dynamic-forms-group-field.component-BNjYMTqT.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"ng-forge-dynamic-forms-group-field.component-DA-FW30T.mjs","mappings":";;;;;;;;;AA0Dc;YAKK;;;;AAIA;AACE;AACF;AACA;YAMA;;;;;AAQjB;;;;YAOS;;;;AAIQ;AACf;AACA;AAEA;AACE;AACA;;AAEA;;AAGE;AACA;;;;;;;AAQF;AACA;AACA;AACA;;;;;;;AAOJ,IAKiB;;;AAIb;;AAEA;AACF;AAEE;AACA;AACD;AAGc;AACf;AACE;;AAGE;AACA;;AAGF;AACF;;;;;AAQO;;AACA;;AACA;;AACA;;AACA;;AACA;;AAEQ;AACf;;AAEA;;;;AAIA;;;AAGE;;AAEA;AACE;;;;AAKN,IAMiB;AACf;;;AAGE;AACF;;AAGe;;AAEf;;;AAGC;AACF;;;AAQQ;;;AAQU;AAGf;AACA;;;AAGA;AACE;;;;AAOH;YAQH;;;;;AAWE;;AAIM;;AAIJ;;;AA/Me;;;;;;AAhBlB;;;;AARS;;;;;;;AAWR;AACA;AACA;AACA;AACA;wEAIA;AACA","names":[],"ignoreList":[],"sources":["../../../../packages/dynamic-forms/src/lib/fields/group/group-field.component.ts"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n computed,\n DestroyRef,\n EnvironmentInjector,\n inject,\n Injector,\n input,\n linkedSignal,\n runInInjectionContext,\n untracked,\n} from '@angular/core';\nimport { DfFieldOutlet } from '../../directives/df-field-outlet/df-field-outlet.directive';\nimport { outputFromObservable, toObservable } from '@angular/core/rxjs-interop';\nimport { explicitEffect } from 'ngxtension/explicit-effect';\nimport { derivedFromDeferred } from '@ng-forge/dynamic-forms/internal';\nimport { createFieldResolutionPipe, ResolvedField } from '../../utils/resolve-field/resolve-field';\nimport { computeContainerHostClasses, setupContainerInitEffect } from '../../utils/container-utils/container-utils';\nimport { CONTAINER_FIELD_PROCESSORS } from '../../utils/container-utils/container-field-processors';\nimport { isEqual } from '@ng-forge/dynamic-forms/internal';\nimport { DynamicFormLogger } from '@ng-forge/dynamic-forms/internal';\nimport { GroupField } from '@ng-forge/dynamic-forms/internal';\nimport { injectFieldRegistry } from '../../utils/inject-field-registry/inject-field-registry';\nimport { FieldTree, form } from '@angular/forms/signals';\nimport { FieldDef } from '@ng-forge/dynamic-forms/internal';\nimport { FieldSignalContext, GroupContext } from '@ng-forge/dynamic-forms/internal';\nimport { FIELD_SIGNAL_CONTEXT, GROUP_CONTEXT, injectFieldSignalContext } from '@ng-forge/dynamic-forms/internal';\nimport { createSchemaFromFields } from '../../core/schema-builder';\nimport { EventBus } from '@ng-forge/dynamic-forms/internal';\nimport { FormSubmitEvent } from '../../events/constants/submit.event';\n\n/** Container component for rendering nested form groups. */\n@Component({\n selector: 'fieldset[group-field]',\n imports: [DfFieldOutlet],\n template: `\n @for (field of resolvedFields(); track field.key) {\n @if (!field.hidden()) {\n <ng-container *dfFieldOutlet=\"field; environmentInjector: environmentInjector\" />\n }\n }\n `,\n styleUrl: './group-field.component.scss',\n host: {\n '[class]': 'hostClasses()',\n role: 'group',\n '[class.disabled]': 'disabled()',\n '[class.df-container-hidden]': 'hidden()',\n '[attr.aria-hidden]': 'hidden() || null',\n // The group element's own id is the plain `key()` — only its DESCENDANTS\n // compose scoped ids via GROUP_CONTEXT (e.g. children of an `address`\n // group render as `address_street`). The group selector is `#address`.\n '[id]': '`${key()}`',\n '[attr.data-testid]': 'key()',\n },\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport default class GroupFieldComponent<TModel extends Record<string, unknown> = Record<string, unknown>> {\n // ─────────────────────────────────────────────────────────────────────────────\n // Dependencies\n // ─────────────────────────────────────────────────────────────────────────────\n\n private readonly destroyRef = inject(DestroyRef);\n private readonly fieldRegistry = injectFieldRegistry();\n private readonly parentFieldSignalContext = injectFieldSignalContext<TModel>();\n private readonly parentGroupContext = inject(GROUP_CONTEXT, { optional: true });\n private readonly injector = inject(Injector);\n protected readonly environmentInjector = inject(EnvironmentInjector);\n private readonly eventBus = inject(EventBus);\n private readonly logger = inject(DynamicFormLogger);\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Memoized Functions\n // ─────────────────────────────────────────────────────────────────────────────\n\n private readonly fieldProcessors = inject(CONTAINER_FIELD_PROCESSORS);\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Inputs\n // ─────────────────────────────────────────────────────────────────────────────\n\n field = input.required<GroupField>();\n key = input.required<string>();\n className = input<string>();\n hidden = input(false);\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Computed Signals\n // ─────────────────────────────────────────────────────────────────────────────\n\n readonly hostClasses = computed(() => computeContainerHostClasses('group', this.className()));\n\n private readonly rawFieldRegistry = computed(() => this.fieldRegistry.raw);\n\n private readonly formSetup = computed(() => {\n const groupField = this.field();\n const registry = this.rawFieldRegistry();\n\n if (groupField.fields && groupField.fields.length > 0) {\n const flattenedFields = this.fieldProcessors.memoizedFlattenFields(groupField.fields, registry);\n const flattenedFieldsForRendering = this.fieldProcessors.memoizedFlattenFieldsForRendering(groupField.fields, registry);\n const fieldsById = this.fieldProcessors.memoizedKeyBy(flattenedFields);\n const defaultValues = this.fieldProcessors.memoizedDefaultValues(fieldsById, registry);\n\n return {\n fields: flattenedFieldsForRendering,\n schemaFields: flattenedFields,\n originalFields: groupField.fields,\n defaultValues,\n registry,\n };\n }\n\n return {\n fields: [],\n schemaFields: [],\n originalFields: [],\n defaultValues: {},\n registry,\n };\n });\n\n readonly defaultValues = linkedSignal(() => this.formSetup().defaultValues);\n\n /**\n * Entity computed from parent value, group key, and defaults.\n * Uses deep equality check to prevent unnecessary updates when\n * object spread creates new references with identical values.\n */\n private readonly entity = linkedSignal(\n () => {\n const parentValue = this.parentFieldSignalContext.value();\n const groupKey = this.field().key;\n const defaults = this.defaultValues();\n const groupValue = (parentValue as Record<string, unknown>)?.[groupKey] || {};\n return { ...defaults, ...groupValue };\n },\n {\n debugName: 'GroupFieldComponent.entity',\n equal: isEqual,\n },\n );\n\n private readonly form = computed(() => {\n return runInInjectionContext(this.injector, () => {\n const setup = this.formSetup();\n\n if (setup.schemaFields.length > 0) {\n const schema = createSchemaFromFields(setup.schemaFields, setup.registry);\n return untracked(() => form(this.entity, schema));\n }\n\n return untracked(() => form(this.entity));\n });\n });\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Public State Signals\n // ─────────────────────────────────────────────────────────────────────────────\n\n readonly formValue = computed(() => this.entity());\n readonly valid = computed(() => this.form()().valid());\n readonly invalid = computed(() => this.form()().invalid());\n readonly dirty = computed(() => this.form()().dirty());\n readonly touched = computed(() => this.form()().touched());\n readonly errors = computed(() => this.form()().errors());\n readonly disabled = computed(() => this.form()().disabled());\n\n private readonly nestedFieldTree = computed((): FieldTree<Record<string, unknown>> | undefined => {\n const parentForm = this.parentFieldSignalContext.form as Record<string, FieldTree<Record<string, unknown>>>;\n const groupKey = this.field().key;\n return parentForm[groupKey];\n });\n\n private readonly groupFieldSignalContext: FieldSignalContext<Record<string, unknown>> = (() => {\n const nestedFieldTree = this.nestedFieldTree;\n return {\n injector: this.injector,\n value: this.parentFieldSignalContext.value,\n defaultValues: this.defaultValues,\n get form() {\n return nestedFieldTree() ?? ({} as FieldTree<Record<string, unknown>>);\n },\n };\n })();\n\n /**\n * Group context propagated to descendants — carries the dot-separated path of\n * group ancestors so child fields can scope their DOM IDs (issue #401).\n * Reactive via `computed()`: a nested group composes its path from the parent\n * context signal + its own key, so the chain stays consistent if either changes.\n */\n private readonly groupContext: GroupContext = {\n groupPath: computed(() => {\n const ownKey = this.field().key;\n const parentPath = this.parentGroupContext?.groupPath();\n return parentPath ? `${parentPath}.${ownKey}` : ownKey;\n }),\n };\n\n private readonly groupInjector = Injector.create({\n parent: this.injector,\n providers: [\n { provide: FIELD_SIGNAL_CONTEXT, useValue: this.groupFieldSignalContext },\n { provide: GROUP_CONTEXT, useValue: this.groupContext },\n ],\n });\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Outputs\n // ─────────────────────────────────────────────────────────────────────────────\n\n readonly validityChange = outputFromObservable(toObservable(this.valid));\n readonly dirtyChange = outputFromObservable(toObservable(this.dirty));\n readonly submitted = outputFromObservable(this.eventBus.on<FormSubmitEvent>('submit'));\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Field Resolution\n // ─────────────────────────────────────────────────────────────────────────────\n\n private readonly fieldsSource = computed(() => this.formSetup().fields);\n\n protected readonly resolvedFields = derivedFromDeferred(\n this.fieldsSource,\n createFieldResolutionPipe(() => ({\n loadTypeComponent: (type: string) => this.fieldRegistry.loadTypeComponent(type),\n registry: this.rawFieldRegistry(),\n injector: this.groupInjector,\n destroyRef: this.destroyRef,\n onError: (fieldDef: FieldDef<unknown>, error: unknown) => {\n const fieldKey = fieldDef.key || '<no key>';\n this.logger.error(\n `Failed to load component for field type '${fieldDef.type}' (key: ${fieldKey}) ` +\n `within group '${this.field().key}'. Ensure the field type is registered in your field registry.`,\n error,\n );\n },\n })),\n { initialValue: [] as ResolvedField[], injector: this.injector },\n );\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Constructor\n // ─────────────────────────────────────────────────────────────────────────────\n\n constructor() {\n this.setupEffects();\n }\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Private Methods\n // ─────────────────────────────────────────────────────────────────────────────\n\n private setupEffects(): void {\n setupContainerInitEffect(this.resolvedFields, this.eventBus, 'group', () => this.field().key, this.injector);\n\n explicitEffect(\n [this.nestedFieldTree, this.field],\n ([tree, field]) => {\n if (!tree) {\n this.logger.warn(\n `Group field \"${field.key}\" not found in parent form. ` + `Ensure the parent form schema includes this group field.`,\n );\n }\n },\n { defer: true },\n );\n }\n}\n\nexport { GroupFieldComponent };\n"]}
1
+ {"version":3,"file":"ng-forge-dynamic-forms-group-field.component-BNjYMTqT.mjs","mappings":";;;;;;;;;AA0Dc;YAKK;;;;AAIA;AACE;AACF;AACA;YAMA;;;;;AAQjB;;;;YAOS;;;;AAIQ;AACf;AACA;AAEA;AACE;AACA;;AAEA;;AAGE;AACA;;;;;;;AAQF;AACA;AACA;AACA;;;;;;;AAOJ,IAKiB;;;AAIb;;AAEA;AACF;AAEE;AACA;AACD;AAGc;AACf;AACE;;AAGE;AACA;;AAGF;AACF;;;;;AAQO;;AACA;;AACA;;AACA;;AACA;;AACA;;AAEQ;AACf;;AAEA;;;;AAIA;;;AAGE;;AAEA;AACE;;;;AAKN,IAMiB;AACf;;;AAGE;AACF;;AAGe;;AAEf;;;AAGC;AACF;;;AAQQ;;;AAQU;AAGf;AACA;;;AAGA;AACE;;;;AAOH;YAQH;;;;;AAWE;;AAIM;;AAIJ;;;AA/Me;;;;;;AAhBlB;;;;AARS;;;;;;;AAWR;AACA;AACA;AACA;AACA;wEAIA;AACA","names":[],"ignoreList":[],"sources":["../../../../packages/dynamic-forms/src/lib/fields/group/group-field.component.ts"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n computed,\n DestroyRef,\n EnvironmentInjector,\n inject,\n Injector,\n input,\n linkedSignal,\n runInInjectionContext,\n untracked,\n} from '@angular/core';\nimport { DfFieldOutlet } from '../../directives/df-field-outlet/df-field-outlet.directive';\nimport { outputFromObservable, toObservable } from '@angular/core/rxjs-interop';\nimport { explicitEffect } from 'ngxtension/explicit-effect';\nimport { derivedFromDeferred } from '@ng-forge/dynamic-forms/internal';\nimport { createFieldResolutionPipe, ResolvedField } from '../../utils/resolve-field/resolve-field';\nimport { computeContainerHostClasses, setupContainerInitEffect } from '../../utils/container-utils/container-utils';\nimport { CONTAINER_FIELD_PROCESSORS } from '../../utils/container-utils/container-field-processors';\nimport { isEqual } from '@ng-forge/dynamic-forms/internal';\nimport { DynamicFormLogger } from '@ng-forge/dynamic-forms/internal';\nimport { GroupField } from '@ng-forge/dynamic-forms/internal';\nimport { injectFieldRegistry } from '../../utils/inject-field-registry/inject-field-registry';\nimport { FieldTree, form } from '@angular/forms/signals';\nimport { FieldDef } from '@ng-forge/dynamic-forms/internal';\nimport { FieldSignalContext, GroupContext } from '@ng-forge/dynamic-forms/internal';\nimport { FIELD_SIGNAL_CONTEXT, GROUP_CONTEXT, injectFieldSignalContext } from '@ng-forge/dynamic-forms/internal';\nimport { createSchemaFromFields } from '../../core/schema-builder';\nimport { EventBus } from '@ng-forge/dynamic-forms/internal';\nimport { FormSubmitEvent } from '../../events/constants/submit.event';\n\n/** Container component for rendering nested form groups. */\n@Component({\n selector: 'fieldset[group-field]',\n imports: [DfFieldOutlet],\n template: `\n @for (field of resolvedFields(); track field.key) {\n @if (!field.hidden()) {\n <ng-container *dfFieldOutlet=\"field; environmentInjector: environmentInjector\" />\n }\n }\n `,\n styleUrl: './group-field.component.scss',\n host: {\n '[class]': 'hostClasses()',\n role: 'group',\n '[class.disabled]': 'disabled()',\n '[class.df-container-hidden]': 'hidden()',\n '[attr.aria-hidden]': 'hidden() || null',\n // The group element's own id is the plain `key()` — only its DESCENDANTS\n // compose scoped ids via GROUP_CONTEXT (e.g. children of an `address`\n // group render as `address_street`). The group selector is `#address`.\n '[id]': '`${key()}`',\n '[attr.data-testid]': 'key()',\n },\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport default class GroupFieldComponent<TModel extends Record<string, unknown> = Record<string, unknown>> {\n // ─────────────────────────────────────────────────────────────────────────────\n // Dependencies\n // ─────────────────────────────────────────────────────────────────────────────\n\n private readonly destroyRef = inject(DestroyRef);\n private readonly fieldRegistry = injectFieldRegistry();\n private readonly parentFieldSignalContext = injectFieldSignalContext<TModel>();\n private readonly parentGroupContext = inject(GROUP_CONTEXT, { optional: true });\n private readonly injector = inject(Injector);\n protected readonly environmentInjector = inject(EnvironmentInjector);\n private readonly eventBus = inject(EventBus);\n private readonly logger = inject(DynamicFormLogger);\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Memoized Functions\n // ─────────────────────────────────────────────────────────────────────────────\n\n private readonly fieldProcessors = inject(CONTAINER_FIELD_PROCESSORS);\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Inputs\n // ─────────────────────────────────────────────────────────────────────────────\n\n field = input.required<GroupField>();\n key = input.required<string>();\n className = input<string>();\n hidden = input(false);\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Computed Signals\n // ─────────────────────────────────────────────────────────────────────────────\n\n readonly hostClasses = computed(() => computeContainerHostClasses('group', this.className()));\n\n private readonly rawFieldRegistry = computed(() => this.fieldRegistry.raw);\n\n private readonly formSetup = computed(() => {\n const groupField = this.field();\n const registry = this.rawFieldRegistry();\n\n if (groupField.fields && groupField.fields.length > 0) {\n const flattenedFields = this.fieldProcessors.memoizedFlattenFields(groupField.fields, registry);\n const flattenedFieldsForRendering = this.fieldProcessors.memoizedFlattenFieldsForRendering(groupField.fields, registry);\n const fieldsById = this.fieldProcessors.memoizedKeyBy(flattenedFields);\n const defaultValues = this.fieldProcessors.memoizedDefaultValues(fieldsById, registry);\n\n return {\n fields: flattenedFieldsForRendering,\n schemaFields: flattenedFields,\n originalFields: groupField.fields,\n defaultValues,\n registry,\n };\n }\n\n return {\n fields: [],\n schemaFields: [],\n originalFields: [],\n defaultValues: {},\n registry,\n };\n });\n\n readonly defaultValues = linkedSignal(() => this.formSetup().defaultValues);\n\n /**\n * Entity computed from parent value, group key, and defaults.\n * Uses deep equality check to prevent unnecessary updates when\n * object spread creates new references with identical values.\n */\n private readonly entity = linkedSignal(\n () => {\n const parentValue = this.parentFieldSignalContext.value();\n const groupKey = this.field().key;\n const defaults = this.defaultValues();\n const groupValue = (parentValue as Record<string, unknown>)?.[groupKey] || {};\n return { ...defaults, ...groupValue };\n },\n {\n debugName: 'GroupFieldComponent.entity',\n equal: isEqual,\n },\n );\n\n private readonly form = computed(() => {\n return runInInjectionContext(this.injector, () => {\n const setup = this.formSetup();\n\n if (setup.schemaFields.length > 0) {\n const schema = createSchemaFromFields(setup.schemaFields, setup.registry);\n return untracked(() => form(this.entity, schema));\n }\n\n return untracked(() => form(this.entity));\n });\n });\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Public State Signals\n // ─────────────────────────────────────────────────────────────────────────────\n\n readonly formValue = computed(() => this.entity());\n readonly valid = computed(() => this.form()().valid());\n readonly invalid = computed(() => this.form()().invalid());\n readonly dirty = computed(() => this.form()().dirty());\n readonly touched = computed(() => this.form()().touched());\n readonly errors = computed(() => this.form()().errors());\n readonly disabled = computed(() => this.form()().disabled());\n\n private readonly nestedFieldTree = computed((): FieldTree<Record<string, unknown>> | undefined => {\n const parentForm = this.parentFieldSignalContext.form as Record<string, FieldTree<Record<string, unknown>>>;\n const groupKey = this.field().key;\n return parentForm[groupKey];\n });\n\n private readonly groupFieldSignalContext: FieldSignalContext<Record<string, unknown>> = (() => {\n const nestedFieldTree = this.nestedFieldTree;\n return {\n injector: this.injector,\n value: this.parentFieldSignalContext.value,\n defaultValues: this.defaultValues,\n get form() {\n return nestedFieldTree() ?? ({} as FieldTree<Record<string, unknown>>);\n },\n };\n })();\n\n /**\n * Group context propagated to descendants — carries the dot-separated path of\n * group ancestors so child fields can scope their DOM IDs (issue #401).\n * Reactive via `computed()`: a nested group composes its path from the parent\n * context signal + its own key, so the chain stays consistent if either changes.\n */\n private readonly groupContext: GroupContext = {\n groupPath: computed(() => {\n const ownKey = this.field().key;\n const parentPath = this.parentGroupContext?.groupPath();\n return parentPath ? `${parentPath}.${ownKey}` : ownKey;\n }),\n };\n\n private readonly groupInjector = Injector.create({\n parent: this.injector,\n providers: [\n { provide: FIELD_SIGNAL_CONTEXT, useValue: this.groupFieldSignalContext },\n { provide: GROUP_CONTEXT, useValue: this.groupContext },\n ],\n });\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Outputs\n // ─────────────────────────────────────────────────────────────────────────────\n\n readonly validityChange = outputFromObservable(toObservable(this.valid));\n readonly dirtyChange = outputFromObservable(toObservable(this.dirty));\n readonly submitted = outputFromObservable(this.eventBus.on<FormSubmitEvent>('submit'));\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Field Resolution\n // ─────────────────────────────────────────────────────────────────────────────\n\n private readonly fieldsSource = computed(() => this.formSetup().fields);\n\n protected readonly resolvedFields = derivedFromDeferred(\n this.fieldsSource,\n createFieldResolutionPipe(() => ({\n loadTypeComponent: (type: string) => this.fieldRegistry.loadTypeComponent(type),\n registry: this.rawFieldRegistry(),\n injector: this.groupInjector,\n destroyRef: this.destroyRef,\n onError: (fieldDef: FieldDef<unknown>, error: unknown) => {\n const fieldKey = fieldDef.key || '<no key>';\n this.logger.error(\n `Failed to load component for field type '${fieldDef.type}' (key: ${fieldKey}) ` +\n `within group '${this.field().key}'. Ensure the field type is registered in your field registry.`,\n error,\n );\n },\n })),\n { initialValue: [] as ResolvedField[], injector: this.injector },\n );\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Constructor\n // ─────────────────────────────────────────────────────────────────────────────\n\n constructor() {\n this.setupEffects();\n }\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Private Methods\n // ─────────────────────────────────────────────────────────────────────────────\n\n private setupEffects(): void {\n setupContainerInitEffect(this.resolvedFields, this.eventBus, 'group', () => this.field().key, this.injector);\n\n explicitEffect(\n [this.nestedFieldTree, this.field],\n ([tree, field]) => {\n if (!tree) {\n this.logger.warn(\n `Group field \"${field.key}\" not found in parent form. ` + `Ensure the parent form schema includes this group field.`,\n );\n }\n },\n { defer: true },\n );\n }\n}\n\nexport { GroupFieldComponent };\n"]}
@@ -1,5 +1,5 @@
1
1
  import * as i0 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';
2
+ import { reflectComponentType, Injector, inject, EnvironmentInjector, DestroyRef, computed, signal, input, ViewContainerRef, Directive, InjectionToken, linkedSignal, untracked, ChangeDetectionStrategy, Component, Injectable, isSignal, runInInjectionContext, afterNextRender, PendingTasks, ElementRef, PLATFORM_ID, isDevMode, TemplateRef, model, contentChildren, makeEnvironmentProviders } from '@angular/core';
3
3
  import { explicitEffect } from 'ngxtension/explicit-effect';
4
4
  import { DynamicFormLogger, WRAPPER_COMPONENT_CACHE, WRAPPER_REGISTRY, toReadonlyFieldTreeCached, writeToFieldValue, WRAPPER_AUTO_ASSOCIATIONS, DEFAULT_WRAPPERS, READONLY_FIELD_TREE_CACHE, getGridClassString, EventBus, FieldContextRegistryService, FunctionRegistryService, FORM_OPTIONS, evaluateCondition, FIELD_SIGNAL_CONTEXT, isRowField, isGenericContainerField, isContainerField, isArrayField, hasChildFields, isSimplifiedArrayField, DynamicFormError, isStateLogicConfig, isDerivationLogicConfig, isFormStateCondition, createFormStateLogicFunction, createDebouncedLogicFunction, createLogicFunction, ExpressionParser, applyValidator, isPageField, isGroupField, getFieldValueHandling, normalizeFieldsArray, keyBy, mapValues, isEqual, hasTargetProperty, ARRAY_CONTEXT, GROUP_CONTEXT, FORM_ID_PREFIX, baseFieldMapper, detectFormMode, validatePageNesting, FIELD_REGISTRY, assertNever, ADDON_TYPE_REGISTRY, deepMergeDefaults, derivedFromDeferred, createWarningTracker, RootFormRegistryService, DEFAULT_PROPS, DEFAULT_VALIDATION_MESSAGES, EXTERNAL_DATA, DEPRECATION_WARNING_TRACKER, createAddonActionRegistry, ADDON_ACTION_REGISTRY, HttpConditionCache, HTTP_CONDITION_CACHE, HttpConditionFunctionCacheService, AsyncConditionFunctionCacheService, DynamicValueFunctionCacheService, LogicFunctionCacheService, setupInitializationTracking, ComponentInitializedEvent, DF_FIELD_TEMPLATES, rowFieldMapper, groupFieldMapper, arrayFieldMapper, pageFieldMapper, textFieldMapper, containerFieldMapper, isWrapperTypeDefinition, ADDON_TYPE_COMPONENT_CACHE, ADDON_TYPE_DEFINITIONS, createDefaultDerivationLogConfig, NoopLogger, EMIT_FORM_VALUE_ON_EVENTS, DynamicTextPipe, ADDON_ACTION_HANDLERS } from '@ng-forge/dynamic-forms/internal';
5
5
  import { takeUntilDestroyed, toObservable, toSignal, outputFromObservable } from '@angular/core/rxjs-interop';
@@ -437,10 +437,6 @@ class FormSubmitEvent {
437
437
  type = 'submit';
438
438
  }
439
439
 
440
- class NextPageEvent {
441
- type = 'next-page';
442
- }
443
-
444
440
  class PageChangeEvent {
445
441
  currentPageIndex;
446
442
  totalPages;
@@ -456,105 +452,9 @@ class PageChangeEvent {
456
452
  }
457
453
  }
458
454
 
459
- class PreviousPageEvent {
460
- type = 'previous-page';
461
- }
462
-
463
- class FormResetEvent {
464
- type = 'form-reset';
465
- }
466
-
467
- class FormClearEvent {
468
- type = 'form-clear';
469
- }
470
-
471
- class AppendArrayItemEvent {
472
- arrayKey;
473
- template;
474
- type = 'append-array-item';
475
- constructor(
476
- arrayKey,
477
- template) {
478
- this.arrayKey = arrayKey;
479
- this.template = template;
480
- }
481
- }
482
-
483
- class PrependArrayItemEvent {
484
- arrayKey;
485
- template;
486
- type = 'prepend-array-item';
487
- constructor(
488
- arrayKey,
489
- template) {
490
- this.arrayKey = arrayKey;
491
- this.template = template;
492
- }
493
- }
494
-
495
- class InsertArrayItemEvent {
496
- arrayKey;
497
- index;
498
- template;
499
- type = 'insert-array-item';
500
- constructor(
501
- arrayKey,
502
- index,
503
- template) {
504
- this.arrayKey = arrayKey;
505
- this.index = index;
506
- this.template = template;
507
- }
508
- }
509
-
510
- class PopArrayItemEvent {
511
- arrayKey;
512
- type = 'pop-array-item';
513
- constructor(
514
- arrayKey) {
515
- this.arrayKey = arrayKey;
516
- }
517
- }
518
-
519
- class ShiftArrayItemEvent {
520
- arrayKey;
521
- type = 'shift-array-item';
522
- constructor(
523
- arrayKey) {
524
- this.arrayKey = arrayKey;
525
- }
526
- }
527
-
528
- class MoveArrayItemEvent {
529
- arrayKey;
530
- fromIndex;
531
- toIndex;
532
- type = 'move-array-item';
533
- constructor(
534
- arrayKey,
535
- fromIndex,
536
- toIndex) {
537
- this.arrayKey = arrayKey;
538
- this.fromIndex = fromIndex;
539
- this.toIndex = toIndex;
540
- }
541
- }
542
-
543
- class RemoveAtIndexEvent {
544
- arrayKey;
545
- index;
546
- type = 'remove-at-index';
547
- constructor(
548
- arrayKey,
549
- index) {
550
- this.arrayKey = arrayKey;
551
- this.index = index;
552
- }
553
- }
554
-
555
- class PageNavigationStateChangeEvent {
455
+ class PagerStateEvent {
556
456
  state;
557
- type = 'page-navigation-state-change';
457
+ type = 'pager-state';
558
458
  constructor(state) {
559
459
  this.state = state;
560
460
  }
@@ -600,9 +500,33 @@ class PageOrchestratorComponent {
600
500
  const totalPages = this.pageFields().length;
601
501
  if (totalPages === 0)
602
502
  return 0;
603
- return 0;
503
+ return untracked(() => this.resolveInitialLanding());
604
504
  }, /* @ts-ignore */
605
505
  ...(ngDevMode ? [{ debugName: "currentPageIndex" }] : /* istanbul ignore next */ []));
506
+ resolveInitialLanding() {
507
+ const { index, validate } = this.initialPage();
508
+ const visible = this.visiblePageIndices();
509
+ if (index === 0 || visible.length === 0)
510
+ return 0;
511
+ const target = visible.includes(index) ? index : this.findNearestVisiblePage(index, visible);
512
+ if (target <= 0)
513
+ return Math.max(target, 0);
514
+ if (!validate)
515
+ return target;
516
+ const firstInvalid = visible.filter((i) => i < target).find((i) => !this.isPageValid(i));
517
+ return firstInvalid ?? target;
518
+ }
519
+ initialPage = computed(() => {
520
+ const raw = this.formOptions?.()?.initialPage;
521
+ const totalPages = this.pageFields().length;
522
+ const requested = typeof raw === 'number' ? raw : raw?.index;
523
+ const validate = typeof raw === 'number' ? false : (raw?.validate ?? false);
524
+ if (typeof requested !== 'number' || !Number.isFinite(requested) || requested < 0 || totalPages === 0) {
525
+ return { index: 0, validate: false };
526
+ }
527
+ return { index: Math.min(Math.trunc(requested), totalPages - 1), validate };
528
+ }, /* @ts-ignore */
529
+ ...(ngDevMode ? [{ debugName: "initialPage" }] : /* istanbul ignore next */ []));
606
530
  state = computed(() => {
607
531
  const currentIndex = this.currentPageIndex();
608
532
  const totalPages = this.pageFields().length;
@@ -615,20 +539,18 @@ class PageOrchestratorComponent {
615
539
  totalPages,
616
540
  isFirstPage: isFirstVisiblePage,
617
541
  isLastPage: isLastVisiblePage,
618
- navigationDisabled: false,
619
542
  };
620
543
  }, /* @ts-ignore */
621
544
  ...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
622
- currentPageValid = computed(() => {
623
- const currentIndex = this.currentPageIndex();
545
+ currentPageValid = computed(() => this.isPageValid(this.currentPageIndex()), /* @ts-ignore */
546
+ ...(ngDevMode ? [{ debugName: "currentPageValid" }] : /* istanbul ignore next */ []));
547
+ isPageValid(pageIndex) {
624
548
  const pages = this.pageFields();
625
549
  const form = this.form();
626
- if (pages.length === 0 || currentIndex >= pages.length) {
550
+ if (pageIndex < 0 || pageIndex >= pages.length) {
627
551
  return true;
628
552
  }
629
- const currentPage = pages[currentIndex];
630
- const pageFields = currentPage.fields || [];
631
- const leafKeys = collectLeafFieldKeys(pageFields);
553
+ const leafKeys = collectLeafFieldKeys(pages[pageIndex].fields || []);
632
554
  for (const fieldKey of leafKeys) {
633
555
  const field = form[fieldKey];
634
556
  if (field && typeof field === 'function') {
@@ -639,8 +561,7 @@ class PageOrchestratorComponent {
639
561
  }
640
562
  }
641
563
  return true;
642
- }, /* @ts-ignore */
643
- ...(ngDevMode ? [{ debugName: "currentPageValid" }] : /* istanbul ignore next */ []));
564
+ }
644
565
  extendedFieldSignalContext = computed(() => ({
645
566
  ...this.fieldSignalContext(),
646
567
  currentPageValid: this.currentPageValid,
@@ -656,12 +577,12 @@ class PageOrchestratorComponent {
656
577
  }
657
578
  constructor() {
658
579
  this.setupEventListeners();
659
- explicitEffect([this.state, this.visiblePageIndices], ([state, visibleIndices]) => {
580
+ explicitEffect([this.state, this.visiblePageIndices], ([state, visibleIndices]) => {
660
581
  const currentVisiblePosition = visibleIndices.indexOf(state.currentPageIndex);
661
582
  if (currentVisiblePosition === -1 && visibleIndices.length > 0) {
662
583
  const nearest = this.findNearestVisiblePage(state.currentPageIndex, visibleIndices);
663
584
  if (nearest !== -1) {
664
- this.navigateToPage(nearest);
585
+ this.executePageChange(nearest);
665
586
  }
666
587
  }
667
588
  });
@@ -683,13 +604,6 @@ class PageOrchestratorComponent {
683
604
  newPageIndex: currentState.currentPageIndex,
684
605
  error: 'Already on the last visible page',
685
606
  };
686
- }
687
- if (currentState.navigationDisabled) {
688
- return {
689
- success: false,
690
- newPageIndex: currentState.currentPageIndex,
691
- error: 'Navigation is currently disabled',
692
- };
693
607
  }
694
608
  const currentVisiblePosition = visibleIndices.indexOf(currentState.currentPageIndex);
695
609
  if (currentVisiblePosition === -1 || currentVisiblePosition >= visibleIndices.length - 1) {
@@ -700,7 +614,7 @@ class PageOrchestratorComponent {
700
614
  };
701
615
  }
702
616
  const nextVisiblePageIndex = visibleIndices[currentVisiblePosition + 1];
703
- return this.navigateToPage(nextVisiblePageIndex);
617
+ return this.executePageChange(nextVisiblePageIndex);
704
618
  }
705
619
  navigateToPreviousPage() {
706
620
  const currentState = this.state();
@@ -711,13 +625,6 @@ class PageOrchestratorComponent {
711
625
  newPageIndex: currentState.currentPageIndex,
712
626
  error: 'Already on the first visible page',
713
627
  };
714
- }
715
- if (currentState.navigationDisabled) {
716
- return {
717
- success: false,
718
- newPageIndex: currentState.currentPageIndex,
719
- error: 'Navigation is currently disabled',
720
- };
721
628
  }
722
629
  const currentVisiblePosition = visibleIndices.indexOf(currentState.currentPageIndex);
723
630
  if (currentVisiblePosition <= 0) {
@@ -728,9 +635,34 @@ class PageOrchestratorComponent {
728
635
  };
729
636
  }
730
637
  const prevVisiblePageIndex = visibleIndices[currentVisiblePosition - 1];
731
- return this.navigateToPage(prevVisiblePageIndex);
638
+ return this.executePageChange(prevVisiblePageIndex);
732
639
  }
733
- navigateToPage(pageIndex) {
640
+ navigateToPage(pageIndex, options) {
641
+ const currentIndex = this.state().currentPageIndex;
642
+ const visibleIndices = this.visiblePageIndices();
643
+ if (pageIndex <= currentIndex || !visibleIndices.includes(pageIndex)) {
644
+ return this.executePageChange(pageIndex);
645
+ }
646
+ if (options?.validate === false) {
647
+ return this.executePageChange(pageIndex);
648
+ }
649
+ const disableWhenPageInvalid = this.formOptions?.()?.nextButton?.disableWhenPageInvalid ?? true;
650
+ if (!disableWhenPageInvalid) {
651
+ return this.executePageChange(pageIndex);
652
+ }
653
+ const crossedPages = visibleIndices.filter((index) => index >= currentIndex && index < pageIndex);
654
+ const firstInvalidPage = crossedPages.find((index) => !this.isPageValid(index));
655
+ if (firstInvalidPage === undefined) {
656
+ return this.executePageChange(pageIndex);
657
+ }
658
+ this.executePageChange(firstInvalidPage);
659
+ return {
660
+ success: false,
661
+ newPageIndex: firstInvalidPage,
662
+ error: `Cannot navigate to page ${pageIndex}: page ${firstInvalidPage} has invalid fields`,
663
+ };
664
+ }
665
+ executePageChange(pageIndex) {
734
666
  const currentState = this.state();
735
667
  const totalPages = currentState.totalPages;
736
668
  if (pageIndex < 0 || pageIndex >= totalPages) {
@@ -787,7 +719,13 @@ class PageOrchestratorComponent {
787
719
  .subscribe(() => {
788
720
  this.navigateToPreviousPage();
789
721
  });
790
- explicitEffect([this.state], ([state]) => this.eventBus.dispatch(PageNavigationStateChangeEvent, state));
722
+ this.eventBus
723
+ .on('go-to-page')
724
+ .pipe(takeUntilDestroyed())
725
+ .subscribe((event) => {
726
+ this.navigateToPage(event.pageIndex, event.options);
727
+ });
728
+ explicitEffect([this.state], ([state]) => this.eventBus.dispatch(PagerStateEvent, state));
791
729
  }
792
730
  evaluatePageHidden(page) {
793
731
  if (!page.logic || page.logic.length === 0) {
@@ -863,10 +801,10 @@ class PageOrchestratorComponent {
863
801
  }
864
802
  }
865
803
  `, isInline: true, styles: [":host{display:block;position:relative;width:100%}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, deferBlockDependencies: [() => [/* @ts-ignore */
866
- import('./ng-forge-dynamic-forms-page-field.component-P9O4eNvS.mjs').then(m => m.default)]] });
804
+ import('./ng-forge-dynamic-forms-page-field.component-B92kLZkH.mjs').then(m => m.default)]] });
867
805
  }
868
806
  i0.ɵɵngDeclareClassMetadataAsync({ minVersion: "18.0.0", version: "22.0.1", ngImport: i0, type: PageOrchestratorComponent, resolveDeferredDeps: () => [/* @ts-ignore */
869
- import('./ng-forge-dynamic-forms-page-field.component-P9O4eNvS.mjs').then(m => m.default)], resolveMetadata: PageFieldComponent => ({ decorators: [{
807
+ import('./ng-forge-dynamic-forms-page-field.component-B92kLZkH.mjs').then(m => m.default)], resolveMetadata: PageFieldComponent => ({ decorators: [{
870
808
  type: Component,
871
809
  args: [{ selector: 'div[page-orchestrator]', imports: [PageFieldComponent], template: `
872
810
  @for (pageField of pageFields(); track pageField.key; let i = $index) {
@@ -3880,7 +3818,7 @@ function createDerivationRenderGate() {
3880
3818
  if (!has || engine() !== 'idle')
3881
3819
  return;
3882
3820
  engine.set('loading');
3883
- pendingTasks.run(() => import('./ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-VJBOH393.mjs')
3821
+ pendingTasks.run(() => import('./ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-OBDy6qd9.mjs')
3884
3822
  .then(({ bootstrapDerivationOrchestrator }) => {
3885
3823
  runInInjectionContext(injector, () => bootstrapDerivationOrchestrator());
3886
3824
  engine.set('wired');
@@ -4211,7 +4149,7 @@ class DynamicForm {
4211
4149
  events = outputFromObservable(this.eventBus.events$);
4212
4150
  initialized = outputFromObservable(this.initialized$);
4213
4151
  onPageChange = outputFromObservable(this.eventBus.on('page-change'));
4214
- onPageNavigationStateChange = outputFromObservable(this.eventBus.on('page-navigation-state-change'));
4152
+ onPageNavigationStateChange = outputFromObservable(this.eventBus.on('pager-state'));
4215
4153
  constructor() {
4216
4154
  this.dispatcher?.connect(this.eventBus);
4217
4155
  this.destroyRef.onDestroy(() => this.dispatcher?.disconnect());
@@ -4435,28 +4373,28 @@ const DISPLAY_FIELD_TYPES_BASE = {
4435
4373
  const BUILT_IN_FIELDS = [
4436
4374
  {
4437
4375
  name: 'row',
4438
- loadComponent: () => import('./ng-forge-dynamic-forms-container-field.component-o49q6akV.mjs'),
4376
+ loadComponent: () => import('./ng-forge-dynamic-forms-container-field.component-BpeFyhAt.mjs'),
4439
4377
  mapper: rowFieldMapper,
4440
4378
  valueHandling: 'flatten',
4441
4379
  ...CONTAINER_FIELD_TYPES_BASE,
4442
4380
  },
4443
4381
  {
4444
4382
  name: 'group',
4445
- loadComponent: () => import('./ng-forge-dynamic-forms-group-field.component-DA-FW30T.mjs'),
4383
+ loadComponent: () => import('./ng-forge-dynamic-forms-group-field.component-BNjYMTqT.mjs'),
4446
4384
  mapper: groupFieldMapper,
4447
4385
  valueHandling: 'include',
4448
4386
  ...CONTAINER_FIELD_TYPES_BASE,
4449
4387
  },
4450
4388
  {
4451
4389
  name: 'array',
4452
- loadComponent: () => import('./ng-forge-dynamic-forms-array-field.component-B9OfjKPI.mjs'),
4390
+ loadComponent: () => import('./ng-forge-dynamic-forms-array-field.component-D-VWBM-t.mjs'),
4453
4391
  mapper: arrayFieldMapper,
4454
4392
  valueHandling: 'include',
4455
4393
  ...CONTAINER_FIELD_TYPES_BASE,
4456
4394
  },
4457
4395
  {
4458
4396
  name: 'page',
4459
- loadComponent: () => import('./ng-forge-dynamic-forms-page-field.component-P9O4eNvS.mjs'),
4397
+ loadComponent: () => import('./ng-forge-dynamic-forms-page-field.component-B92kLZkH.mjs'),
4460
4398
  mapper: pageFieldMapper,
4461
4399
  valueHandling: 'flatten',
4462
4400
  ...CONTAINER_FIELD_TYPES_BASE,
@@ -4474,7 +4412,7 @@ const BUILT_IN_FIELDS = [
4474
4412
  },
4475
4413
  {
4476
4414
  name: 'container',
4477
- loadComponent: () => import('./ng-forge-dynamic-forms-container-field.component-o49q6akV.mjs'),
4415
+ loadComponent: () => import('./ng-forge-dynamic-forms-container-field.component-BpeFyhAt.mjs'),
4478
4416
  mapper: containerFieldMapper,
4479
4417
  valueHandling: 'flatten',
4480
4418
  ...CONTAINER_FIELD_TYPES_BASE,
@@ -4876,6 +4814,118 @@ function sanitizeFormConfigPure(config, fieldRegistry, typeRegistry, options = {
4876
4814
  return { sanitized: { ...config, fields: fields }, warnings };
4877
4815
  }
4878
4816
 
4817
+ class GoToPageEvent {
4818
+ pageIndex;
4819
+ options;
4820
+ type = 'go-to-page';
4821
+ constructor(
4822
+ pageIndex,
4823
+ options) {
4824
+ this.pageIndex = pageIndex;
4825
+ this.options = options;
4826
+ }
4827
+ }
4828
+
4829
+ class NextPageEvent {
4830
+ type = 'next-page';
4831
+ }
4832
+
4833
+ class PreviousPageEvent {
4834
+ type = 'previous-page';
4835
+ }
4836
+
4837
+ class FormResetEvent {
4838
+ type = 'form-reset';
4839
+ }
4840
+
4841
+ class FormClearEvent {
4842
+ type = 'form-clear';
4843
+ }
4844
+
4845
+ class AppendArrayItemEvent {
4846
+ arrayKey;
4847
+ template;
4848
+ type = 'append-array-item';
4849
+ constructor(
4850
+ arrayKey,
4851
+ template) {
4852
+ this.arrayKey = arrayKey;
4853
+ this.template = template;
4854
+ }
4855
+ }
4856
+
4857
+ class PrependArrayItemEvent {
4858
+ arrayKey;
4859
+ template;
4860
+ type = 'prepend-array-item';
4861
+ constructor(
4862
+ arrayKey,
4863
+ template) {
4864
+ this.arrayKey = arrayKey;
4865
+ this.template = template;
4866
+ }
4867
+ }
4868
+
4869
+ class InsertArrayItemEvent {
4870
+ arrayKey;
4871
+ index;
4872
+ template;
4873
+ type = 'insert-array-item';
4874
+ constructor(
4875
+ arrayKey,
4876
+ index,
4877
+ template) {
4878
+ this.arrayKey = arrayKey;
4879
+ this.index = index;
4880
+ this.template = template;
4881
+ }
4882
+ }
4883
+
4884
+ class PopArrayItemEvent {
4885
+ arrayKey;
4886
+ type = 'pop-array-item';
4887
+ constructor(
4888
+ arrayKey) {
4889
+ this.arrayKey = arrayKey;
4890
+ }
4891
+ }
4892
+
4893
+ class ShiftArrayItemEvent {
4894
+ arrayKey;
4895
+ type = 'shift-array-item';
4896
+ constructor(
4897
+ arrayKey) {
4898
+ this.arrayKey = arrayKey;
4899
+ }
4900
+ }
4901
+
4902
+ class MoveArrayItemEvent {
4903
+ arrayKey;
4904
+ fromIndex;
4905
+ toIndex;
4906
+ type = 'move-array-item';
4907
+ constructor(
4908
+ arrayKey,
4909
+ fromIndex,
4910
+ toIndex) {
4911
+ this.arrayKey = arrayKey;
4912
+ this.fromIndex = fromIndex;
4913
+ this.toIndex = toIndex;
4914
+ }
4915
+ }
4916
+
4917
+ class RemoveAtIndexEvent {
4918
+ arrayKey;
4919
+ index;
4920
+ type = 'remove-at-index';
4921
+ constructor(
4922
+ arrayKey,
4923
+ index) {
4924
+ this.arrayKey = arrayKey;
4925
+ this.index = index;
4926
+ }
4927
+ }
4928
+
4879
4929
  function arrayEvent(arrayKey) {
4880
4930
  return {
4881
4931
  append: (template) => new AppendArrayItemEvent(arrayKey, template),
@@ -4891,5 +4941,5 @@ function arrayEvent(arrayKey) {
4891
4941
 
4892
4942
 
4893
4943
 
4894
- 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 };
4895
- //# sourceMappingURL=ng-forge-dynamic-forms-ng-forge-dynamic-forms-CUihkwGC.mjs.map
4944
+ export { withEventFormValue as $, ArrayItemRegistryService as A, BUILT_IN_FIELDS as B, CONTAINER_FIELD_PROCESSORS as C, DfFieldOutlet as D, EventDispatcher as E, FIELD_WINDOWING as F, GoToPageEvent as G, PageChangeEvent as H, InsertArrayItemEvent as I, PagerStateEvent as J, PopArrayItemEvent as K, PrependArrayItemEvent as L, MoveArrayItemEvent as M, NextPageEvent as N, PreviousPageEvent as O, PROPERTY_OVERRIDE_STORE as P, TextAddonComponent as Q, RemoveAtIndexEvent as R, ShiftArrayItemEvent as S, TemplateAddonComponent as T, arrayEvent as U, createWrappers as V, isWrappersBundle as W, provideDynamicForm as X, sanitizeFormConfig as Y, withAddonActions as Z, withCustomAddon as _, isSameWrapperChain as a, withFieldWindowing as a0, withLegacyStatusClasses as a1, withLoggerConfig as a2, withPagePreload as a3, withValidationExecutionDefaults as a4, withValueExclusionDefaults as a5, wrapperProps as a6, createWrapperChainController as b, createFieldResolutionPipe as c, createSchemaFromFields as d, createHiddenSignal as e, createRenderReadySignal as f, getNormalizedArrayMetadata as g, getFieldDefaultValue as h, injectFieldRegistry as i, resolveFieldWindowing as j, DEV_MODE as k, DEFAULT_DEBOUNCE_MS as l, mapFieldToInputs as m, DERIVATION_WARNING_TRACKER as n, FormStateManager as o, DERIVATION_LOG_CONFIG as p, AppendArrayItemEvent as q, resolveWrappers as r, ComponentAddonComponent as s, traverseFieldsWithContext as t, ConsoleLogger as u, DfTemplate as v, DynamicForm as w, FormClearEvent as x, FormResetEvent as y, FormSubmitEvent as z };
4945
+ //# sourceMappingURL=ng-forge-dynamic-forms-ng-forge-dynamic-forms-DSXZJHcu.mjs.map