@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.
- package/README.md +11 -11
- package/fesm2022/{ng-forge-dynamic-forms-array-field.component-B9OfjKPI.mjs → ng-forge-dynamic-forms-array-field.component-D-VWBM-t.mjs} +2 -2
- 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
- package/fesm2022/{ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-VJBOH393.mjs → ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-OBDy6qd9.mjs} +2 -2
- package/fesm2022/{ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-VJBOH393.mjs.map → ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-OBDy6qd9.mjs.map} +1 -1
- package/fesm2022/{ng-forge-dynamic-forms-container-field.component-o49q6akV.mjs → ng-forge-dynamic-forms-container-field.component-BpeFyhAt.mjs} +2 -2
- package/fesm2022/{ng-forge-dynamic-forms-container-field.component-o49q6akV.mjs.map → ng-forge-dynamic-forms-container-field.component-BpeFyhAt.mjs.map} +1 -1
- package/fesm2022/{ng-forge-dynamic-forms-group-field.component-DA-FW30T.mjs → ng-forge-dynamic-forms-group-field.component-BNjYMTqT.mjs} +2 -2
- 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
- package/fesm2022/{ng-forge-dynamic-forms-ng-forge-dynamic-forms-CUihkwGC.mjs → ng-forge-dynamic-forms-ng-forge-dynamic-forms-DSXZJHcu.mjs} +194 -144
- package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-DSXZJHcu.mjs.map +1 -0
- package/fesm2022/{ng-forge-dynamic-forms-page-field.component-P9O4eNvS.mjs → ng-forge-dynamic-forms-page-field.component-B92kLZkH.mjs} +2 -2
- package/fesm2022/{ng-forge-dynamic-forms-page-field.component-P9O4eNvS.mjs.map → ng-forge-dynamic-forms-page-field.component-B92kLZkH.mjs.map} +1 -1
- package/fesm2022/ng-forge-dynamic-forms.mjs +1 -1
- package/package.json +1 -1
- package/types/ng-forge-dynamic-forms-internal.d.ts +30 -1
- package/types/ng-forge-dynamic-forms.d.ts +55 -15
- 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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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,
|
|
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
|
|
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 = '
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
550
|
+
if (pageIndex < 0 || pageIndex >= pages.length) {
|
|
627
551
|
return true;
|
|
628
552
|
}
|
|
629
|
-
|
|
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
|
-
}
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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-
|
|
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-
|
|
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-
|
|
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('
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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 {
|
|
4895
|
-
//# sourceMappingURL=ng-forge-dynamic-forms-ng-forge-dynamic-forms-
|
|
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
|