@ng-forge/dynamic-forms 1.2.0-next.6 → 1.2.0-next.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/{ng-forge-dynamic-forms-array-field.component-NTHLPjau.mjs → ng-forge-dynamic-forms-array-field.component-DK7dAQv7.mjs} +6 -10
- package/fesm2022/ng-forge-dynamic-forms-array-field.component-DK7dAQv7.mjs.map +1 -0
- package/fesm2022/{ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-CNCeWAeJ.mjs → ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-BfxYqAQA.mjs} +29 -7
- package/fesm2022/ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-BfxYqAQA.mjs.map +1 -0
- package/fesm2022/{ng-forge-dynamic-forms-container-field.component-ByGdzAsk.mjs → ng-forge-dynamic-forms-container-field.component-Cj2msZp_.mjs} +7 -11
- package/fesm2022/ng-forge-dynamic-forms-container-field.component-Cj2msZp_.mjs.map +1 -0
- package/fesm2022/ng-forge-dynamic-forms-debounce-C9F4v1Ke.mjs +17 -0
- package/fesm2022/ng-forge-dynamic-forms-debounce-C9F4v1Ke.mjs.map +1 -0
- package/fesm2022/ng-forge-dynamic-forms-form-schema.service-C3cVSmft.mjs +24 -0
- package/fesm2022/ng-forge-dynamic-forms-form-schema.service-C3cVSmft.mjs.map +1 -0
- package/fesm2022/{ng-forge-dynamic-forms-group-field.component-DnLZdj8v.mjs → ng-forge-dynamic-forms-group-field.component-DN6X506r.mjs} +6 -10
- package/fesm2022/ng-forge-dynamic-forms-group-field.component-DN6X506r.mjs.map +1 -0
- package/fesm2022/ng-forge-dynamic-forms-internal.mjs +32 -5
- package/fesm2022/ng-forge-dynamic-forms-internal.mjs.map +1 -1
- package/fesm2022/{ng-forge-dynamic-forms-ng-forge-dynamic-forms-B2C_WAMP.mjs → ng-forge-dynamic-forms-ng-forge-dynamic-forms-DCX7tvaB.mjs} +1043 -1299
- package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-DCX7tvaB.mjs.map +1 -0
- package/fesm2022/{ng-forge-dynamic-forms-page-field.component-CwH22lRl.mjs → ng-forge-dynamic-forms-page-field.component-D16ey8r-.mjs} +54 -13
- package/fesm2022/ng-forge-dynamic-forms-page-field.component-D16ey8r-.mjs.map +1 -0
- package/fesm2022/ng-forge-dynamic-forms-schema-builder-DO-su9sF.mjs +570 -0
- package/fesm2022/ng-forge-dynamic-forms-schema-builder-DO-su9sF.mjs.map +1 -0
- package/fesm2022/ng-forge-dynamic-forms.mjs +1 -1
- package/package.json +2 -2
- package/types/ng-forge-dynamic-forms-internal.d.ts +10 -5
- package/types/ng-forge-dynamic-forms.d.ts +32 -16
- package/fesm2022/ng-forge-dynamic-forms-array-field.component-NTHLPjau.mjs.map +0 -1
- package/fesm2022/ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-CNCeWAeJ.mjs.map +0 -1
- package/fesm2022/ng-forge-dynamic-forms-container-field.component-ByGdzAsk.mjs.map +0 -1
- package/fesm2022/ng-forge-dynamic-forms-container-utils-BZFz6ztF.mjs +0 -24
- package/fesm2022/ng-forge-dynamic-forms-container-utils-BZFz6ztF.mjs.map +0 -1
- package/fesm2022/ng-forge-dynamic-forms-emit-initialization-DhHwU6FJ.mjs +0 -17
- package/fesm2022/ng-forge-dynamic-forms-emit-initialization-DhHwU6FJ.mjs.map +0 -1
- package/fesm2022/ng-forge-dynamic-forms-group-field.component-DnLZdj8v.mjs.map +0 -1
- package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-B2C_WAMP.mjs.map +0 -1
- package/fesm2022/ng-forge-dynamic-forms-page-field.component-CwH22lRl.mjs.map +0 -1
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { inject, DestroyRef, Injector, EnvironmentInjector, input, computed, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
3
|
-
import { i as injectFieldRegistry, F as FIELD_WINDOWING,
|
|
4
|
-
import { outputFromObservable } from '@angular/core/rxjs-interop';
|
|
2
|
+
import { inject, DestroyRef, Injector, EnvironmentInjector, input, computed, signal, afterRenderEffect, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
3
|
+
import { i as injectFieldRegistry, F as FIELD_WINDOWING, c as computeContainerHostClasses, l as resolveFieldWindowing, a as createFieldResolutionPipe, n as collectInitializingContainerKeys, s as setupContainerInitEffect, o as ActivePageInitializedEvent, D as DfFieldOutlet, p as initializationComponentKey } from './ng-forge-dynamic-forms-ng-forge-dynamic-forms-DCX7tvaB.mjs';
|
|
4
|
+
import { outputFromObservable, takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
5
5
|
import { explicitEffect } from 'ngxtension/explicit-effect';
|
|
6
6
|
import { EventBus, DynamicFormLogger, FORM_OPTIONS, validatePageNesting, derivedFromDeferred, isContainerField, getGridClassString } from '@ng-forge/dynamic-forms/internal';
|
|
7
|
-
import { c as computeContainerHostClasses, s as setupContainerInitEffect } from './ng-forge-dynamic-forms-container-utils-BZFz6ztF.mjs';
|
|
8
7
|
|
|
9
8
|
class PageFieldComponent {
|
|
10
9
|
destroyRef = inject(DestroyRef);
|
|
@@ -56,7 +55,33 @@ class PageFieldComponent {
|
|
|
56
55
|
`within page '${this.field().key}'. Ensure the field type is registered in your field registry.`, error);
|
|
57
56
|
},
|
|
58
57
|
})), { initialValue: [], injector: this.injector });
|
|
58
|
+
initializedContainers = signal(new Set(), /* @ts-ignore */
|
|
59
|
+
...(ngDevMode ? [{ debugName: "initializedContainers" }] : /* istanbul ignore next */ []));
|
|
60
|
+
expectedContainerKeys = computed(() => collectInitializingContainerKeys(this.fieldsSource()), /* @ts-ignore */
|
|
61
|
+
...(ngDevMode ? [{ debugName: "expectedContainerKeys" }] : /* istanbul ignore next */ []));
|
|
62
|
+
renderReady = computed(() => {
|
|
63
|
+
const source = this.fieldsSource();
|
|
64
|
+
const fields = this.resolvedFields();
|
|
65
|
+
const initialized = this.initializedContainers();
|
|
66
|
+
return (fields.length === source.length &&
|
|
67
|
+
fields.every((field) => field.renderReady()) &&
|
|
68
|
+
this.expectedContainerKeys().every((key) => initialized.has(key)));
|
|
69
|
+
}, /* @ts-ignore */
|
|
70
|
+
...(ngDevMode ? [{ debugName: "renderReady" }] : /* istanbul ignore next */ []));
|
|
59
71
|
constructor() {
|
|
72
|
+
this.eventBus
|
|
73
|
+
.on('component-initialized')
|
|
74
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
75
|
+
.subscribe((event) => {
|
|
76
|
+
const key = initializationComponentKey(event.componentType, event.componentId);
|
|
77
|
+
this.initializedContainers.update((current) => {
|
|
78
|
+
if (current.has(key))
|
|
79
|
+
return current;
|
|
80
|
+
const next = new Set(current);
|
|
81
|
+
next.add(key);
|
|
82
|
+
return next;
|
|
83
|
+
});
|
|
84
|
+
});
|
|
60
85
|
this.setupEffects();
|
|
61
86
|
}
|
|
62
87
|
windowsField(field, index) {
|
|
@@ -69,6 +94,22 @@ class PageFieldComponent {
|
|
|
69
94
|
}
|
|
70
95
|
setupEffects() {
|
|
71
96
|
setupContainerInitEffect(this.resolvedFields, this.eventBus, 'page', () => this.field().key, this.injector);
|
|
97
|
+
let announced = null;
|
|
98
|
+
afterRenderEffect({
|
|
99
|
+
write: () => {
|
|
100
|
+
if (!this.isVisible()) {
|
|
101
|
+
announced = null;
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (!this.renderReady())
|
|
105
|
+
return;
|
|
106
|
+
const visit = `${this.pageIndex()}:${this.key()}`;
|
|
107
|
+
if (announced === visit)
|
|
108
|
+
return;
|
|
109
|
+
announced = visit;
|
|
110
|
+
this.eventBus.dispatch(ActivePageInitializedEvent, this.pageIndex(), this.key());
|
|
111
|
+
},
|
|
112
|
+
});
|
|
72
113
|
explicitEffect([this.isValid, this.field], ([valid, pageField]) => {
|
|
73
114
|
if (!valid) {
|
|
74
115
|
this.logger.error(`Invalid configuration: Page '${pageField.key}' contains nested page fields. ` +
|
|
@@ -79,8 +120,8 @@ class PageFieldComponent {
|
|
|
79
120
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: PageFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
80
121
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", type: PageFieldComponent, isStandalone: true, selector: "section[page-field]", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: true, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, pageIndex: { classPropertyName: "pageIndex", publicName: "pageIndex", isSignal: true, isRequired: true, transformFunction: null }, isVisible: { classPropertyName: "isVisible", publicName: "isVisible", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { nextPage: "nextPage", previousPage: "previousPage", pageChange: "pageChange" }, host: { properties: { "class": "hostClasses()", "class.disabled": "disabled()", "class.df-page-visible": "isVisible()", "class.df-page-hidden": "!isVisible()", "attr.aria-hidden": "!isVisible()", "attr.data-page-index": "pageIndex()", "id": "`${key()}`", "attr.data-testid": "key()" } }, ngImport: i0, template: `
|
|
81
122
|
@for (field of resolvedFields(); track field.key; let i = $index) {
|
|
82
|
-
@if (
|
|
83
|
-
@if (
|
|
123
|
+
@if (windowsField(field, i)) {
|
|
124
|
+
@if (!field.hidden()) {
|
|
84
125
|
@defer (on viewport) {
|
|
85
126
|
<ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
|
|
86
127
|
} @placeholder {
|
|
@@ -91,9 +132,9 @@ class PageFieldComponent {
|
|
|
91
132
|
aria-hidden="true"
|
|
92
133
|
></div>
|
|
93
134
|
}
|
|
94
|
-
} @else {
|
|
95
|
-
<ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
|
|
96
135
|
}
|
|
136
|
+
} @else {
|
|
137
|
+
<ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
|
|
97
138
|
}
|
|
98
139
|
}
|
|
99
140
|
`, isInline: true, styles: [".df-form{display:grid;grid-template-columns:1fr;gap:var(--df-grid-row-gap, .5rem);width:100%}.df-form>*{grid-column:1/-1}.df-row{display:grid;grid-template-columns:repeat(var(--df-grid-columns, 12),1fr);gap:var(--df-grid-gap, .5rem);align-items:start;width:100%}.df-row>*:not([class*=df-col-]){grid-column:1/-1}.df-col-1{grid-column:span 1}.df-col-2{grid-column:span 2}.df-col-3{grid-column:span 3}.df-col-4{grid-column:span 4}.df-col-5{grid-column:span 5}.df-col-6{grid-column:span 6}.df-col-7{grid-column:span 7}.df-col-8{grid-column:span 8}.df-col-9{grid-column:span 9}.df-col-10{grid-column:span 10}.df-col-11{grid-column:span 11}.df-col-12{grid-column:span 12}.df-col-auto{grid-column:span auto;width:auto}.df-col-full{grid-column:1/-1}.df-col-start-1{grid-column-start:1}.df-col-start-2{grid-column-start:2}.df-col-start-3{grid-column-start:3}.df-col-start-4{grid-column-start:4}.df-col-start-5{grid-column-start:5}.df-col-start-6{grid-column-start:6}.df-col-start-7{grid-column-start:7}.df-col-start-8{grid-column-start:8}.df-col-start-9{grid-column-start:9}.df-col-start-10{grid-column-start:10}.df-col-start-11{grid-column-start:11}.df-col-start-12{grid-column-start:12}.df-col-end-1{grid-column-end:1}.df-col-end-2{grid-column-end:2}.df-col-end-3{grid-column-end:3}.df-col-end-4{grid-column-end:4}.df-col-end-5{grid-column-end:5}.df-col-end-6{grid-column-end:6}.df-col-end-7{grid-column-end:7}.df-col-end-8{grid-column-end:8}.df-col-end-9{grid-column-end:9}.df-col-end-10{grid-column-end:10}.df-col-end-11{grid-column-end:11}.df-col-end-12{grid-column-end:12}.df-col-end-13{grid-column-end:13}@media(max-width:576px){.df-form{--df-grid-gap: var(--df-grid-gap-sm, .5rem);--df-grid-row-gap: var(--df-grid-row-gap-sm, .5rem)}.df-row{grid-template-columns:1fr}.df-row>*{grid-column:1/-1!important}.df-row.df-row-mobile-keep-cols{grid-template-columns:repeat(var(--df-grid-columns, 12),1fr)}.df-row.df-row-mobile-keep-cols>*{grid-column:revert!important}}@media(min-width:577px)and (max-width:768px){.df-form{--df-grid-gap: var(--df-grid-gap-md, .5rem);--df-grid-row-gap: var(--df-grid-row-gap-md, .5rem)}.df-row{--df-grid-columns: 6}.df-col-sm-1{grid-column:span 1}.df-col-sm-2{grid-column:span 2}.df-col-sm-3{grid-column:span 3}.df-col-sm-4{grid-column:span 4}.df-col-sm-5{grid-column:span 5}.df-col-sm-6{grid-column:span 6}.df-col-sm-full{grid-column:1/-1}}@media(min-width:769px)and (max-width:992px){.df-form{--df-grid-gap: var(--df-grid-gap-lg, .5rem);--df-grid-row-gap: var(--df-grid-row-gap-lg, .5rem)}.df-col-md-1{grid-column:span 1}.df-col-md-2{grid-column:span 2}.df-col-md-3{grid-column:span 3}.df-col-md-4{grid-column:span 4}.df-col-md-5{grid-column:span 5}.df-col-md-6{grid-column:span 6}.df-col-md-7{grid-column:span 7}.df-col-md-8{grid-column:span 8}.df-col-md-9{grid-column:span 9}.df-col-md-10{grid-column:span 10}.df-col-md-11{grid-column:span 11}.df-col-md-12{grid-column:span 12}.df-col-md-full{grid-column:1/-1}}@media(min-width:993px){.df-form{--df-grid-gap: var(--df-grid-gap-xl, .5rem);--df-grid-row-gap: var(--df-grid-row-gap-xl, .5rem)}.df-col-lg-1{grid-column:span 1}.df-col-lg-2{grid-column:span 2}.df-col-lg-3{grid-column:span 3}.df-col-lg-4{grid-column:span 4}.df-col-lg-5{grid-column:span 5}.df-col-lg-6{grid-column:span 6}.df-col-lg-7{grid-column:span 7}.df-col-lg-8{grid-column:span 8}.df-col-lg-9{grid-column:span 9}.df-col-lg-10{grid-column:span 10}.df-col-lg-11{grid-column:span 11}.df-col-lg-12{grid-column:span 12}.df-col-lg-full{grid-column:1/-1}}.df-gap-none{--df-grid-gap: 0}.df-gap-xs{--df-grid-gap: .25rem}.df-gap-sm{--df-grid-gap: .5rem}.df-gap-md{--df-grid-gap: 1rem}.df-gap-lg{--df-grid-gap: 1.5rem}.df-gap-xl{--df-grid-gap: 2rem}.df-row-gap-none{--df-grid-row-gap: 0}.df-row-gap-xs{--df-grid-row-gap: .25rem}.df-row-gap-sm{--df-grid-row-gap: .5rem}.df-row-gap-md{--df-grid-row-gap: 1rem}.df-row-gap-lg{--df-grid-row-gap: 1.5rem}.df-row-gap-xl{--df-grid-row-gap: 2rem}.df-field{display:block;width:100%;min-width:0;overflow:hidden;margin:0}.df-group,.df-page{display:block;width:100%}.df-form.disabled,.df-row.disabled,.df-field.disabled{opacity:.6;pointer-events:none}.df-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.df-sr-only-focusable:focus,.df-sr-only-focusable:active{position:static;width:auto;height:auto;padding:inherit;margin:inherit;overflow:visible;clip:auto;white-space:normal}.df-live-region{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.df-form :focus-visible{outline:2px solid var(--df-focus-color, #005fcc);outline-offset:2px}.df-group:focus-within{outline:1px dashed var(--df-focus-color, #005fcc);outline-offset:4px}:host{display:grid;grid-template-columns:1fr;width:100%;gap:var(--df-grid-row-gap, .5rem);transition:opacity .2s ease-in-out}:host.df-page-spacing-custom{--df-grid-row-gap: var(--df-page-custom-spacing, 1.5rem)}:host.df-page-visible{opacity:1}:host.df-page-hidden{display:none;opacity:0;pointer-events:none}:host[aria-hidden=true]{pointer-events:none;visibility:hidden}@media(prefers-reduced-motion:no-preference){:host{transition:opacity var(--df-page-transition-duration, .3s) var(--df-page-transition-timing, ease-in-out),transform var(--df-page-transition-duration, .3s) var(--df-page-transition-timing, ease-in-out)}:host.df-page-visible{transform:translate(0)}:host.df-page-hidden{transform:translate(var(--df-page-slide-offset, -10px))}}@media(prefers-reduced-motion:reduce){:host{transition:none}}.df-field-placeholder{display:block}\n"], dependencies: [{ kind: "directive", type: DfFieldOutlet, selector: "[dfFieldOutlet]", inputs: ["dfFieldOutlet", "dfFieldOutletEnvironmentInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, deferBlockDependencies: [() => [DfFieldOutlet]] });
|
|
@@ -102,8 +143,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
|
|
|
102
143
|
type: Component,
|
|
103
144
|
args: [{ selector: 'section[page-field]', imports: [DfFieldOutlet], template: `
|
|
104
145
|
@for (field of resolvedFields(); track field.key; let i = $index) {
|
|
105
|
-
@if (
|
|
106
|
-
@if (
|
|
146
|
+
@if (windowsField(field, i)) {
|
|
147
|
+
@if (!field.hidden()) {
|
|
107
148
|
@defer (on viewport) {
|
|
108
149
|
<ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
|
|
109
150
|
} @placeholder {
|
|
@@ -114,9 +155,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
|
|
|
114
155
|
aria-hidden="true"
|
|
115
156
|
></div>
|
|
116
157
|
}
|
|
117
|
-
} @else {
|
|
118
|
-
<ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
|
|
119
158
|
}
|
|
159
|
+
} @else {
|
|
160
|
+
<ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
|
|
120
161
|
}
|
|
121
162
|
}
|
|
122
163
|
`, host: {
|
|
@@ -132,4 +173,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
|
|
|
132
173
|
}], 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 }] }], pageIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageIndex", required: true }] }], isVisible: [{ type: i0.Input, args: [{ isSignal: true, alias: "isVisible", required: true }] }], nextPage: [{ type: i0.Output, args: ["nextPage"] }], previousPage: [{ type: i0.Output, args: ["previousPage"] }], pageChange: [{ type: i0.Output, args: ["pageChange"] }] } });
|
|
133
174
|
|
|
134
175
|
export { PageFieldComponent as default };
|
|
135
|
-
//# sourceMappingURL=ng-forge-dynamic-forms-page-field.component-
|
|
176
|
+
//# sourceMappingURL=ng-forge-dynamic-forms-page-field.component-D16ey8r-.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ng-forge-dynamic-forms-page-field.component-D16ey8r-.mjs","mappings":";;;;;;;AA0Ec;YAKK;;AAEA;AACE;AACF;AACA;;AAEA;;;;;AAQjB;;;;;;YAQS;;AAEA;;AAEA;;AAET,IAKmB;;;;YAUV;AACA;AACA;YAMQ;AACf;AACE;;;;;AAKe;AAGf;AACA;;;AAGA;AACE;;;;AAOH;AAIc;;AAEA;;AAEA;AACf;AACA;AACA;AACA;AAEE;AACA;;;YAQJ;AACE;;AAEG;AACA;AACC;;AAEE;AAAsB;AACtB;AACA;AACA;AACF;AACF;;;AAIJ;AAOE;AACA;;IAIQ;;;;;;;AAgBR;;AAEI;;;;AAIA;;AACA;;;;AAGA;;AAEH;AAED;;;;;AAQA;;;AAxKiB;;;;;;;;;;;;;;;;;;;;;;;AAnCT;;;;;;;;;;;;;;;;;;;;AAwBR;AACA;AACA;AACA;AACA;AACA;AACA;AACA","names":[],"ignoreList":[],"sources":["../../../../packages/dynamic-forms/src/lib/fields/page/page-field.component.ts"],"sourcesContent":["import {\n afterRenderEffect,\n ChangeDetectionStrategy,\n Component,\n computed,\n DestroyRef,\n EnvironmentInjector,\n inject,\n Injector,\n input,\n signal,\n} from '@angular/core';\nimport { DfFieldOutlet } from '../../directives/df-field-outlet/df-field-outlet.directive';\nimport { outputFromObservable, takeUntilDestroyed } 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 {\n collectInitializingContainerKeys,\n computeContainerHostClasses,\n initializationComponentKey,\n setupContainerInitEffect,\n} from '../../utils/container-utils/container-utils';\nimport { PageField, validatePageNesting } 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 { NextPageEvent, PageChangeEvent, PreviousPageEvent } from '../../events/constants';\nimport { FieldDef } from '@ng-forge/dynamic-forms/internal';\nimport { DynamicFormLogger } from '@ng-forge/dynamic-forms/internal';\nimport { FORM_OPTIONS } from '@ng-forge/dynamic-forms/internal';\nimport { getGridClassString } from '@ng-forge/dynamic-forms/internal';\nimport { isContainerField } from '@ng-forge/dynamic-forms/internal';\nimport { FIELD_WINDOWING } from '../../providers/features/field-windowing/field-windowing.token';\nimport { resolveFieldWindowing } from '../../providers/features/field-windowing/resolve-field-windowing';\nimport { ActivePageInitializedEvent } from '../../events/constants/active-page-initialized.event';\nimport { ComponentInitializedEvent } from '@ng-forge/dynamic-forms/internal';\n\n/** Renders a single page in multi-page (wizard) forms. */\n@Component({\n selector: 'section[page-field]',\n imports: [DfFieldOutlet],\n template: `\n @for (field of resolvedFields(); track field.key; let i = $index) {\n @if (windowsField(field, i)) {\n @if (!field.hidden()) {\n @defer (on viewport) {\n <ng-container *dfFieldOutlet=\"field; environmentInjector: environmentInjector\" />\n } @placeholder {\n <div\n [class]=\"placeholderGridClass(field)\"\n [style.min-height]=\"fieldWindowing().placeholderHeight\"\n [attr.data-field-key]=\"field.key\"\n aria-hidden=\"true\"\n ></div>\n }\n }\n } @else {\n <ng-container *dfFieldOutlet=\"field; environmentInjector: environmentInjector\" />\n }\n }\n `,\n styleUrl: './page-field.component.scss',\n host: {\n '[class]': 'hostClasses()',\n '[class.disabled]': 'disabled()',\n '[class.df-page-visible]': 'isVisible()',\n '[class.df-page-hidden]': '!isVisible()',\n '[attr.aria-hidden]': '!isVisible()',\n '[attr.data-page-index]': 'pageIndex()',\n '[id]': '`${key()}`',\n '[attr.data-testid]': 'key()',\n },\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport default class PageFieldComponent {\n // ─────────────────────────────────────────────────────────────────────────────\n // Dependencies\n // ─────────────────────────────────────────────────────────────────────────────\n\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 formOptions = inject(FORM_OPTIONS, { optional: true });\n private readonly globalFieldWindowing = inject(FIELD_WINDOWING);\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Inputs\n // ─────────────────────────────────────────────────────────────────────────────\n\n field = input.required<PageField>();\n key = input.required<string>();\n className = input<string>();\n pageIndex = input.required<number>();\n isVisible = input.required<boolean>();\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Computed Signals\n // ─────────────────────────────────────────────────────────────────────────────\n\n readonly hostClasses = computed(() => computeContainerHostClasses('page-field', this.className()));\n\n readonly disabled = computed(() => this.field().disabled || false);\n\n readonly isValid = computed(() => validatePageNesting(this.field()));\n\n /**\n * Effective field-windowing config for this page. Per-form\n * `FormOptions.fieldWindowing` wins over the global `withFieldWindowing()`\n * default; both fall back to disabled (fully eager).\n */\n protected readonly fieldWindowing = computed(() =>\n resolveFieldWindowing(this.globalFieldWindowing, this.formOptions?.()?.fieldWindowing),\n );\n\n private readonly rawFieldRegistry = computed(() => this.fieldRegistry.raw);\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Outputs\n // ─────────────────────────────────────────────────────────────────────────────\n\n readonly nextPage = outputFromObservable(this.eventBus.on<NextPageEvent>('next-page'));\n readonly previousPage = outputFromObservable(this.eventBus.on<PreviousPageEvent>('previous-page'));\n readonly pageChange = outputFromObservable(this.eventBus.on<PageChangeEvent>('page-change'));\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Field Resolution\n // ─────────────────────────────────────────────────────────────────────────────\n\n private readonly fieldsSource = computed(() => {\n if (!this.isValid()) {\n return [];\n }\n return this.field().fields || [];\n });\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 this.logger.error(\n `Failed to load component for field type '${fieldDef.type}' (key: ${fieldKey}) ` +\n `within page '${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 private readonly initializedContainers = signal<ReadonlySet<string>>(new Set());\n\n private readonly expectedContainerKeys = computed(() => collectInitializingContainerKeys(this.fieldsSource()));\n\n private readonly renderReady = computed(() => {\n const source = this.fieldsSource();\n const fields = this.resolvedFields();\n const initialized = this.initializedContainers();\n return (\n fields.length === source.length &&\n fields.every((field) => field.renderReady()) &&\n this.expectedContainerKeys().every((key) => initialized.has(key))\n );\n });\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Constructor\n // ─────────────────────────────────────────────────────────────────────────────\n\n constructor() {\n this.eventBus\n .on<ComponentInitializedEvent>('component-initialized')\n .pipe(takeUntilDestroyed(this.destroyRef))\n .subscribe((event) => {\n const key = initializationComponentKey(event.componentType, event.componentId);\n this.initializedContainers.update((current) => {\n if (current.has(key)) return current;\n const next = new Set(current);\n next.add(key);\n return next;\n });\n });\n this.setupEffects();\n }\n\n /**\n * Whether a field should be deferred by field windowing. Containers are never\n * windowed: they emit `ComponentInitializedEvent`, which the form's init\n * tracker counts, so deferring one would stall `initialized` until it scrolls\n * into view. Leaf fields carry no such count, so they defer freely.\n */\n protected windowsField(field: ResolvedField, index: number): boolean {\n const w = this.fieldWindowing();\n return w.enabled && index >= w.eager && !isContainerField(field.fieldDef);\n }\n\n /** Class string for a windowed field's placeholder — matches the real field's grid column. */\n protected placeholderGridClass(field: ResolvedField): string {\n const grid = getGridClassString(field.fieldDef);\n return grid ? `df-field-placeholder ${grid}` : 'df-field-placeholder';\n }\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Private Methods\n // ─────────────────────────────────────────────────────────────────────────────\n\n private setupEffects(): void {\n setupContainerInitEffect(this.resolvedFields, this.eventBus, 'page', () => this.field().key, this.injector);\n\n // Announced once per visit. `renderReady` tracks `resolvedFields`, which moves on every\n // reconciliation, so without this an event named \"initialized\" re-fires for one page.\n // Cleared when the page goes away, so returning to it announces again.\n let announced: string | null = null;\n afterRenderEffect({\n write: () => {\n if (!this.isVisible()) {\n announced = null;\n return;\n }\n if (!this.renderReady()) return;\n const visit = `${this.pageIndex()}:${this.key()}`;\n if (announced === visit) return;\n announced = visit;\n this.eventBus.dispatch(ActivePageInitializedEvent, this.pageIndex(), this.key());\n },\n });\n\n explicitEffect([this.isValid, this.field], ([valid, pageField]) => {\n if (!valid) {\n this.logger.error(\n `Invalid configuration: Page '${pageField.key}' contains nested page fields. ` +\n `Pages cannot contain other pages. Consider using groups or rows for nested structure.`,\n pageField,\n );\n }\n });\n }\n}\n"]}
|