@ng-forge/dynamic-forms 1.2.0-next.6 → 1.2.0-next.8
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-6yyDriVB.mjs} +6 -10
- package/fesm2022/ng-forge-dynamic-forms-array-field.component-6yyDriVB.mjs.map +1 -0
- package/fesm2022/{ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-CNCeWAeJ.mjs → ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-BB8jAcY4.mjs} +29 -7
- package/fesm2022/ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-BB8jAcY4.mjs.map +1 -0
- package/fesm2022/{ng-forge-dynamic-forms-container-field.component-ByGdzAsk.mjs → ng-forge-dynamic-forms-container-field.component-DfQ0y-77.mjs} +7 -11
- package/fesm2022/ng-forge-dynamic-forms-container-field.component-DfQ0y-77.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-BzbBv-r0.mjs +24 -0
- package/fesm2022/ng-forge-dynamic-forms-form-schema.service-BzbBv-r0.mjs.map +1 -0
- package/fesm2022/{ng-forge-dynamic-forms-group-field.component-DnLZdj8v.mjs → ng-forge-dynamic-forms-group-field.component-DxHD5I8E.mjs} +6 -10
- package/fesm2022/ng-forge-dynamic-forms-group-field.component-DxHD5I8E.mjs.map +1 -0
- package/fesm2022/ng-forge-dynamic-forms-internal.mjs +36 -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-tA8ZkP8i.mjs} +1399 -1354
- package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-tA8ZkP8i.mjs.map +1 -0
- package/fesm2022/{ng-forge-dynamic-forms-page-field.component-CwH22lRl.mjs → ng-forge-dynamic-forms-page-field.component-DCdBvJ-h.mjs} +54 -13
- package/fesm2022/ng-forge-dynamic-forms-page-field.component-DCdBvJ-h.mjs.map +1 -0
- package/fesm2022/ng-forge-dynamic-forms-schema-builder-IsjNBo0h.mjs +570 -0
- package/fesm2022/ng-forge-dynamic-forms-schema-builder-IsjNBo0h.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 +25 -6
- package/types/ng-forge-dynamic-forms.d.ts +80 -19
- 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,14 +1,132 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import {
|
|
2
|
+
import { InjectionToken, inject, PLATFORM_ID, DestroyRef, Injectable, reflectComponentType, Injector, EnvironmentInjector, computed, signal, afterNextRender, input, ViewContainerRef, Directive, isSignal, runInInjectionContext, untracked, linkedSignal, injectAsync, resource, ChangeDetectionStrategy, Component, PendingTasks, ElementRef, isDevMode, TemplateRef, model, contentChildren, makeEnvironmentProviders } from '@angular/core';
|
|
3
3
|
import { explicitEffect } from 'ngxtension/explicit-effect';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { submit, required, disabled, readonly, hidden, applyEach, applyWhenValue, applyWhen, apply, email, minLength, maxLength, pattern, min, max, schema, validateStandardSchema, form, provideSignalFormsConfig } from '@angular/forms/signals';
|
|
8
|
-
import { isStandardSchemaMarker } from '@ng-forge/dynamic-forms/schema';
|
|
4
|
+
import { derivedFrom } from 'ngxtension/derived-from';
|
|
5
|
+
import { of, Observable, distinctUntilChanged, forkJoin, from, catchError, map, switchMap, pipe, isObservable, firstValueFrom, exhaustMap, EMPTY, scan, Subject, concatMap, filter, take, shareReplay } from 'rxjs';
|
|
6
|
+
import { DynamicFormLogger, WRAPPER_COMPONENT_LOAD_CACHE, WRAPPER_COMPONENT_CACHE, WRAPPER_REGISTRY, toReadonlyFieldTreeCached, writeToFieldValue, ComponentInitializedEvent, isPageField, isGroupField, isArrayField, isRowField, isGenericContainerField, hasChildFields, normalizeFieldsArray, WRAPPER_AUTO_ASSOCIATIONS, DEFAULT_WRAPPERS, READONLY_FIELD_TREE_CACHE, EventBus, getGridClassString, FORM_OPTIONS, GROUP_CONTEXT, FIELD_REGISTRY, DynamicFormError, isContainerField, isSimplifiedArrayField, getFieldValueHandling, keyBy, mapValues, isEqual, isDerivationLogicConfig, hasTargetProperty, ARRAY_CONTEXT, FORM_ID_PREFIX, baseFieldMapper, detectFormMode, validatePageNesting, assertNever, FunctionRegistryService, ADDON_TYPE_REGISTRY, deepMergeDefaults, FIELD_SIGNAL_CONTEXT, derivedFromDeferred, FieldContextRegistryService, evaluateCondition, createWarningTracker, RootFormRegistryService, DEFAULT_PROPS, DEFAULT_VALIDATION_MESSAGES, EXTERNAL_DATA, DEPRECATION_WARNING_TRACKER, createAddonActionRegistry, ADDON_ACTION_REGISTRY, HttpConditionCache, HTTP_CONDITION_CACHE, HttpConditionFunctionCacheService, AsyncConditionFunctionCacheService, DynamicValueFunctionCacheService, LogicFunctionCacheService, 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';
|
|
9
7
|
import { isPlatformBrowser, AsyncPipe, NgTemplateOutlet, NgComponentOutlet } from '@angular/common';
|
|
8
|
+
import { takeUntilDestroyed, toObservable, outputFromObservable } from '@angular/core/rxjs-interop';
|
|
9
|
+
import { submit, form, provideSignalFormsConfig } from '@angular/forms/signals';
|
|
10
10
|
import { NG_STATUS_CLASSES } from '@angular/forms/signals/compat';
|
|
11
11
|
|
|
12
|
+
const FIELD_WINDOWING = new InjectionToken('FIELD_WINDOWING', {
|
|
13
|
+
providedIn: 'root',
|
|
14
|
+
factory: () => ({ enabled: false, eager: 12, placeholderHeight: '4rem', park: { enabled: false, margin: '100%' } }),
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
function clampWindowSize(value, fallback) {
|
|
18
|
+
if (value === undefined || !Number.isFinite(value))
|
|
19
|
+
return fallback;
|
|
20
|
+
return Math.max(0, Math.floor(value));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const DEFAULT_MARGIN = '100%';
|
|
24
|
+
const ROOT_MARGIN_PART = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?(?:px|%)$/i;
|
|
25
|
+
function normalizeFieldParkingMargin(value, fallback = DEFAULT_MARGIN) {
|
|
26
|
+
const normalizedFallback = isValidRootMargin(fallback) ? fallback.trim() : DEFAULT_MARGIN;
|
|
27
|
+
if (!value || !isValidRootMargin(value))
|
|
28
|
+
return normalizedFallback;
|
|
29
|
+
return value.trim().split(/\s+/).join(' ');
|
|
30
|
+
}
|
|
31
|
+
function isValidRootMargin(value) {
|
|
32
|
+
const parts = value.trim().split(/\s+/);
|
|
33
|
+
return parts.length >= 1 && parts.length <= 4 && parts.every((part) => ROOT_MARGIN_PART.test(part));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function resolveFieldWindowing(global, perForm) {
|
|
37
|
+
if (perForm === undefined) {
|
|
38
|
+
return global;
|
|
39
|
+
}
|
|
40
|
+
if (typeof perForm === 'boolean') {
|
|
41
|
+
return { ...global, enabled: perForm };
|
|
42
|
+
}
|
|
43
|
+
const configuresMounting = 'eager' in perForm || 'placeholderHeight' in perForm || Object.keys(perForm).length === 0;
|
|
44
|
+
return {
|
|
45
|
+
enabled: configuresMounting ? true : global.enabled,
|
|
46
|
+
eager: clampWindowSize(perForm.eager, global.eager),
|
|
47
|
+
placeholderHeight: perForm.placeholderHeight ?? global.placeholderHeight,
|
|
48
|
+
park: resolveFieldParking(global.park, perForm.park),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function resolveFieldParking(global, perForm) {
|
|
52
|
+
if (perForm === undefined)
|
|
53
|
+
return global;
|
|
54
|
+
if (typeof perForm === 'boolean')
|
|
55
|
+
return { ...global, enabled: perForm };
|
|
56
|
+
return { enabled: true, margin: normalizeFieldParkingMargin(perForm.margin, global.margin) };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const ALWAYS_VISIBLE = of(true);
|
|
60
|
+
class FieldViewportObserver {
|
|
61
|
+
isBrowser = isPlatformBrowser(inject(PLATFORM_ID)) && typeof IntersectionObserver !== 'undefined';
|
|
62
|
+
observers = new Map();
|
|
63
|
+
constructor() {
|
|
64
|
+
inject(DestroyRef).onDestroy(() => {
|
|
65
|
+
for (const { observer } of this.observers.values())
|
|
66
|
+
observer.disconnect();
|
|
67
|
+
this.observers.clear();
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
observe(el, rootMargin) {
|
|
71
|
+
if (!this.isBrowser)
|
|
72
|
+
return ALWAYS_VISIBLE;
|
|
73
|
+
rootMargin = normalizeFieldParkingMargin(rootMargin);
|
|
74
|
+
return new Observable((subscriber) => {
|
|
75
|
+
const listener = (visible) => subscriber.next(visible);
|
|
76
|
+
this.startObserving(el, rootMargin, listener);
|
|
77
|
+
subscriber.next(true);
|
|
78
|
+
return () => this.stopObserving(el, rootMargin, listener);
|
|
79
|
+
}).pipe(distinctUntilChanged());
|
|
80
|
+
}
|
|
81
|
+
startObserving(el, rootMargin, listener) {
|
|
82
|
+
const entry = this.observerFor(rootMargin);
|
|
83
|
+
const listeners = entry.listeners.get(el);
|
|
84
|
+
if (listeners) {
|
|
85
|
+
listeners.add(listener);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
entry.listeners.set(el, new Set([listener]));
|
|
89
|
+
entry.observer.observe(el);
|
|
90
|
+
}
|
|
91
|
+
stopObserving(el, rootMargin, listener) {
|
|
92
|
+
const entry = this.observers.get(rootMargin);
|
|
93
|
+
if (!entry)
|
|
94
|
+
return;
|
|
95
|
+
const listeners = entry.listeners.get(el);
|
|
96
|
+
if (!listeners)
|
|
97
|
+
return;
|
|
98
|
+
listeners.delete(listener);
|
|
99
|
+
if (listeners.size > 0)
|
|
100
|
+
return;
|
|
101
|
+
entry.observer.unobserve(el);
|
|
102
|
+
entry.listeners.delete(el);
|
|
103
|
+
if (entry.listeners.size > 0)
|
|
104
|
+
return;
|
|
105
|
+
entry.observer.disconnect();
|
|
106
|
+
this.observers.delete(rootMargin);
|
|
107
|
+
}
|
|
108
|
+
observerFor(rootMargin) {
|
|
109
|
+
const existing = this.observers.get(rootMargin);
|
|
110
|
+
if (existing)
|
|
111
|
+
return existing;
|
|
112
|
+
const listeners = new Map();
|
|
113
|
+
const observer = new IntersectionObserver((entries) => {
|
|
114
|
+
for (const entry of entries) {
|
|
115
|
+
for (const listener of listeners.get(entry.target) ?? [])
|
|
116
|
+
listener(entry.isIntersecting);
|
|
117
|
+
}
|
|
118
|
+
}, { rootMargin });
|
|
119
|
+
const entry = { observer, listeners };
|
|
120
|
+
this.observers.set(rootMargin, entry);
|
|
121
|
+
return entry;
|
|
122
|
+
}
|
|
123
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: FieldViewportObserver, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
124
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: FieldViewportObserver });
|
|
125
|
+
}
|
|
126
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: FieldViewportObserver, decorators: [{
|
|
127
|
+
type: Injectable
|
|
128
|
+
}], ctorParameters: () => [] });
|
|
129
|
+
|
|
12
130
|
const EMPTY_WRAPPERS = Object.freeze([]);
|
|
13
131
|
const FIELD_ERRORS_WRAPPER = 'field-errors';
|
|
14
132
|
const FIELD_ERRORS_CONFIG = Object.freeze({ type: FIELD_ERRORS_WRAPPER });
|
|
@@ -45,6 +163,8 @@ function needsErrorWrapper(field, composed, wrapperRegistry) {
|
|
|
45
163
|
return !composed.some((w) => w.type === FIELD_ERRORS_WRAPPER || wrapperRegistry.get(w.type)?.rendersFieldErrors);
|
|
46
164
|
}
|
|
47
165
|
|
|
166
|
+
const DEV_MODE = typeof ngDevMode === 'undefined' || !!ngDevMode;
|
|
167
|
+
|
|
48
168
|
const inputNamesCache = new WeakMap();
|
|
49
169
|
function getDeclaredInputs(componentType) {
|
|
50
170
|
let inputs = inputNamesCache.get(componentType);
|
|
@@ -66,24 +186,33 @@ function hasDefaultExport(value) {
|
|
|
66
186
|
function resolveDefaultExport(result) {
|
|
67
187
|
return hasDefaultExport(result) ? result.default : result;
|
|
68
188
|
}
|
|
69
|
-
async function loadWrapperComponent(type, registry, cache) {
|
|
189
|
+
async function loadWrapperComponent(type, registry, cache, loadCache) {
|
|
70
190
|
const cached = cache.get(type);
|
|
71
191
|
if (cached)
|
|
72
192
|
return cached;
|
|
193
|
+
const pending = loadCache?.get(type);
|
|
194
|
+
if (pending)
|
|
195
|
+
return pending;
|
|
73
196
|
const definition = registry.get(type);
|
|
74
197
|
if (!definition)
|
|
75
198
|
return undefined;
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
199
|
+
const load = (async () => {
|
|
200
|
+
const component = resolveDefaultExport(await definition.loadComponent());
|
|
201
|
+
if (component)
|
|
202
|
+
cache.set(type, component);
|
|
203
|
+
return component;
|
|
204
|
+
})().finally(() => loadCache?.delete(type));
|
|
205
|
+
loadCache?.set(type, load);
|
|
206
|
+
return load;
|
|
207
|
+
}
|
|
208
|
+
function loadWrapperComponents(configs, registry, cache, logger, loadCache) {
|
|
82
209
|
if (configs.length === 0)
|
|
83
210
|
return of([]);
|
|
84
|
-
return forkJoin(configs.map((config) => from(loadWrapperComponent(config.type, registry, cache)).pipe(catchError(() => of(undefined)), map((component) => {
|
|
211
|
+
return forkJoin(configs.map((config) => from(loadWrapperComponent(config.type, registry, cache, loadCache)).pipe(catchError(() => of(undefined)), map((component) => {
|
|
85
212
|
if (!component) {
|
|
86
|
-
|
|
213
|
+
if (DEV_MODE) {
|
|
214
|
+
logger.error(`Wrapper type '${config.type}' could not be loaded. Ensure it is registered via provideDynamicForm().`);
|
|
215
|
+
}
|
|
87
216
|
return null;
|
|
88
217
|
}
|
|
89
218
|
return { config, component };
|
|
@@ -123,9 +252,11 @@ function renderStep(slot, remaining, options, refs) {
|
|
|
123
252
|
ref.changeDetectorRef.detectChanges();
|
|
124
253
|
const inner = resolveInnerSlot(ref);
|
|
125
254
|
if (!inner) {
|
|
126
|
-
|
|
127
|
-
`
|
|
128
|
-
|
|
255
|
+
if (DEV_MODE) {
|
|
256
|
+
options.logger.error(`Wrapper component for type '${wrapper.config.type}' does not provide a 'fieldComponent' ViewContainerRef. ` +
|
|
257
|
+
`Ensure the wrapper component has a viewChild('fieldComponent', { read: ViewContainerRef }) query ` +
|
|
258
|
+
`and that #fieldComponent is not inside a conditional (@if, @defer).`);
|
|
259
|
+
}
|
|
129
260
|
while (refs.length)
|
|
130
261
|
refs.pop().destroy();
|
|
131
262
|
return;
|
|
@@ -167,27 +298,34 @@ function createWrapperChainController(opts) {
|
|
|
167
298
|
const deps = injectChainDeps();
|
|
168
299
|
const state = createStateSignal(opts);
|
|
169
300
|
const mounted = { value: null };
|
|
301
|
+
const detached = { value: null };
|
|
170
302
|
let refs = [];
|
|
171
303
|
buildEmissionStream(state, deps)
|
|
172
304
|
.pipe(takeUntilDestroyed(deps.destroyRef))
|
|
173
305
|
.subscribe((emission) => {
|
|
174
306
|
try {
|
|
175
|
-
refs = applyEmission(emission, { opts, deps, mounted, refs });
|
|
307
|
+
refs = applyEmission(emission, { opts, deps, mounted, refs, detached });
|
|
176
308
|
}
|
|
177
309
|
catch (err) {
|
|
178
310
|
deps.logger.error('Wrapper chain render failed; tearing down partial state.', err);
|
|
179
311
|
opts.vcr().clear();
|
|
180
312
|
refs = [];
|
|
181
313
|
mounted.value = null;
|
|
314
|
+
detached.value?.destroy();
|
|
315
|
+
detached.value = null;
|
|
182
316
|
}
|
|
183
317
|
});
|
|
184
318
|
pushFieldInputsOnChange(opts, () => refs);
|
|
185
|
-
deps.destroyRef.onDestroy(() =>
|
|
319
|
+
deps.destroyRef.onDestroy(() => {
|
|
320
|
+
detached.value?.destroy();
|
|
321
|
+
opts.vcr().clear();
|
|
322
|
+
});
|
|
186
323
|
}
|
|
187
324
|
function injectChainDeps() {
|
|
188
325
|
return {
|
|
189
326
|
registry: inject(WRAPPER_REGISTRY),
|
|
190
327
|
cache: inject(WRAPPER_COMPONENT_CACHE),
|
|
328
|
+
loadCache: inject(WRAPPER_COMPONENT_LOAD_CACHE),
|
|
191
329
|
logger: inject(DynamicFormLogger),
|
|
192
330
|
destroyRef: inject(DestroyRef),
|
|
193
331
|
environmentInjector: inject(EnvironmentInjector),
|
|
@@ -211,16 +349,27 @@ function resolveLoadedWrappers(state, deps) {
|
|
|
211
349
|
const loaded = state.wrappers.map((config) => ({ config, component: deps.cache.get(config.type) }));
|
|
212
350
|
return of({ state, loaded });
|
|
213
351
|
}
|
|
214
|
-
return loadWrapperComponents(state.wrappers, deps.registry, deps.cache, deps.logger).pipe(map((loaded) => ({ state, loaded })));
|
|
352
|
+
return loadWrapperComponents(state.wrappers, deps.registry, deps.cache, deps.logger, deps.loadCache).pipe(map((loaded) => ({ state, loaded })));
|
|
215
353
|
}
|
|
216
354
|
function applyEmission({ state, loaded }, ctx) {
|
|
217
355
|
const { opts, deps, mounted, refs } = ctx;
|
|
218
356
|
const vcr = opts.vcr();
|
|
219
357
|
const structurallyChanged = isStructurallyDifferent(mounted.value, state);
|
|
220
|
-
if (!structurallyChanged && !state.open)
|
|
358
|
+
if (!structurallyChanged && !state.open) {
|
|
359
|
+
if (vcr.length > 0)
|
|
360
|
+
ctx.detached.value = vcr.detach(0) ?? ctx.detached.value;
|
|
361
|
+
return refs;
|
|
362
|
+
}
|
|
363
|
+
if (state.open && !structurallyChanged && mounted.value !== null && ctx.detached.value) {
|
|
364
|
+
vcr.insert(ctx.detached.value);
|
|
365
|
+
ctx.detached.value = null;
|
|
221
366
|
return refs;
|
|
367
|
+
}
|
|
222
368
|
if (structurallyChanged && mounted.value !== null) {
|
|
223
|
-
|
|
369
|
+
const preservesDetachedOuterView = opts.beforeRebuild?.() === true;
|
|
370
|
+
if (ctx.detached.value && !preservesDetachedOuterView)
|
|
371
|
+
ctx.detached.value.destroy();
|
|
372
|
+
ctx.detached.value = null;
|
|
224
373
|
vcr.clear();
|
|
225
374
|
mounted.value = null;
|
|
226
375
|
}
|
|
@@ -278,14 +427,19 @@ class FieldComponentSlot {
|
|
|
278
427
|
state = signal(EMPTY_STATE, /* @ts-ignore */
|
|
279
428
|
...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
|
|
280
429
|
lastInputs = null;
|
|
430
|
+
_parked = signal(false, /* @ts-ignore */
|
|
431
|
+
...(ngDevMode ? [{ debugName: "_parked" }] : /* istanbul ignore next */ []));
|
|
281
432
|
phase = () => this.state().phase;
|
|
282
433
|
snapshot = () => this.state();
|
|
434
|
+
parked = this._parked.asReadonly();
|
|
283
435
|
mountOrReuse(slot, component, fieldInjector, environmentInjector, rawInputs, fieldInputs) {
|
|
284
436
|
const current = this.state();
|
|
285
437
|
if (current.phase !== 'empty' && current.ref.componentType === component) {
|
|
286
438
|
slot.insert(current.ref.hostView);
|
|
287
439
|
this.lastInputs = null;
|
|
288
440
|
this.state.set(Object.freeze({ phase: 'mounted', ref: current.ref, slot }));
|
|
441
|
+
if (this._parked())
|
|
442
|
+
current.ref.changeDetectorRef.detach();
|
|
289
443
|
this.pushInputs(rawInputs, fieldInputs);
|
|
290
444
|
if (current.phase === 'detached')
|
|
291
445
|
this.replayFocus(current.focusSnapshot);
|
|
@@ -294,6 +448,7 @@ class FieldComponentSlot {
|
|
|
294
448
|
if (current.phase !== 'empty')
|
|
295
449
|
current.ref.destroy();
|
|
296
450
|
this.lastInputs = null;
|
|
451
|
+
this._parked.set(false);
|
|
297
452
|
this.state.set(EMPTY_STATE);
|
|
298
453
|
const ref = slot.createComponent(component, {
|
|
299
454
|
environmentInjector,
|
|
@@ -322,19 +477,42 @@ class FieldComponentSlot {
|
|
|
322
477
|
detach() {
|
|
323
478
|
const current = this.state();
|
|
324
479
|
if (current.phase !== 'mounted')
|
|
325
|
-
return;
|
|
480
|
+
return false;
|
|
326
481
|
const focusSnapshot = this.captureFocus(current.ref);
|
|
327
482
|
const idx = current.slot.indexOf(current.ref.hostView);
|
|
328
483
|
if (idx >= 0)
|
|
329
484
|
current.slot.detach(idx);
|
|
330
485
|
this.lastInputs = null;
|
|
331
486
|
this.state.set(Object.freeze({ phase: 'detached', ref: current.ref, focusSnapshot }));
|
|
487
|
+
return idx < 0;
|
|
488
|
+
}
|
|
489
|
+
park() {
|
|
490
|
+
const current = this.state();
|
|
491
|
+
if (current.phase === 'empty' || this._parked())
|
|
492
|
+
return;
|
|
493
|
+
current.ref.changeDetectorRef.detach();
|
|
494
|
+
this._parked.set(true);
|
|
495
|
+
}
|
|
496
|
+
refresh() {
|
|
497
|
+
const current = this.state();
|
|
498
|
+
if (current.phase === 'empty' || !this._parked())
|
|
499
|
+
return;
|
|
500
|
+
current.ref.changeDetectorRef.detectChanges();
|
|
501
|
+
}
|
|
502
|
+
unpark() {
|
|
503
|
+
const current = this.state();
|
|
504
|
+
if (current.phase === 'empty' || !this._parked())
|
|
505
|
+
return;
|
|
506
|
+
current.ref.changeDetectorRef.reattach();
|
|
507
|
+
current.ref.changeDetectorRef.markForCheck();
|
|
508
|
+
this._parked.set(false);
|
|
332
509
|
}
|
|
333
510
|
destroyOnTeardown() {
|
|
334
511
|
const current = this.state();
|
|
335
512
|
if (current.phase === 'detached')
|
|
336
513
|
current.ref.destroy();
|
|
337
514
|
this.lastInputs = null;
|
|
515
|
+
this._parked.set(false);
|
|
338
516
|
this.state.set(EMPTY_STATE);
|
|
339
517
|
}
|
|
340
518
|
captureFocus(ref) {
|
|
@@ -359,6 +537,100 @@ class FieldComponentSlot {
|
|
|
359
537
|
}
|
|
360
538
|
}
|
|
361
539
|
|
|
540
|
+
function emitComponentInitialized(eventBus, componentType, componentKey, injector) {
|
|
541
|
+
const logger = injector.get(DynamicFormLogger);
|
|
542
|
+
afterNextRender(() => {
|
|
543
|
+
try {
|
|
544
|
+
eventBus.dispatch(ComponentInitializedEvent, componentType, componentKey);
|
|
545
|
+
}
|
|
546
|
+
catch (error) {
|
|
547
|
+
logger.error(`Failed to emit initialization event for ${componentType} '${componentKey}'`, error);
|
|
548
|
+
}
|
|
549
|
+
}, { injector });
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
function initializationComponentKey(type, key) {
|
|
553
|
+
return `${type}:${key}`;
|
|
554
|
+
}
|
|
555
|
+
function initializationComponentPath(key, groupPath) {
|
|
556
|
+
return groupPath ? `${groupPath}.${key}` : key;
|
|
557
|
+
}
|
|
558
|
+
function initializationComponentType(field) {
|
|
559
|
+
if (isPageField(field))
|
|
560
|
+
return 'page';
|
|
561
|
+
if (isGroupField(field))
|
|
562
|
+
return 'group';
|
|
563
|
+
if (isArrayField(field))
|
|
564
|
+
return 'array';
|
|
565
|
+
if (isRowField(field) || isGenericContainerField(field))
|
|
566
|
+
return 'container';
|
|
567
|
+
return undefined;
|
|
568
|
+
}
|
|
569
|
+
function collectInitializingContainers(fields, groupPath = '') {
|
|
570
|
+
const identities = [];
|
|
571
|
+
for (const field of fields) {
|
|
572
|
+
if (field.hidden === true)
|
|
573
|
+
continue;
|
|
574
|
+
const type = initializationComponentType(field);
|
|
575
|
+
const componentPath = initializationComponentPath(field.key, groupPath);
|
|
576
|
+
if (type && field.key)
|
|
577
|
+
identities.push({ type, path: componentPath });
|
|
578
|
+
if (!hasChildFields(field))
|
|
579
|
+
continue;
|
|
580
|
+
const childGroupPath = isGroupField(field) ? componentPath : groupPath;
|
|
581
|
+
for (const child of normalizeFieldsArray(field.fields)) {
|
|
582
|
+
identities.push(...collectInitializingContainers((Array.isArray(child) ? child : [child]), childGroupPath));
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
return identities;
|
|
586
|
+
}
|
|
587
|
+
function collectInitializingContainerKeys(fields, groupPath = '') {
|
|
588
|
+
return collectInitializingContainers(fields, groupPath).map(({ type, path }) => initializationComponentKey(type, path));
|
|
589
|
+
}
|
|
590
|
+
function computeContainerHostClasses(containerType, className) {
|
|
591
|
+
const base = `df-field df-${containerType}`;
|
|
592
|
+
return className ? `${base} ${className}` : base;
|
|
593
|
+
}
|
|
594
|
+
function setupContainerInitEffect(resolvedFields, eventBus, componentType, fieldKeyFn, injector) {
|
|
595
|
+
const allReady = computed(() => {
|
|
596
|
+
const fields = resolvedFields();
|
|
597
|
+
return fields.length > 0 && fields.every((field) => field.renderReady());
|
|
598
|
+
}, /* @ts-ignore */
|
|
599
|
+
...(ngDevMode ? [{ debugName: "allReady" }] : /* istanbul ignore next */ []));
|
|
600
|
+
explicitEffect([allReady], ([ready]) => {
|
|
601
|
+
if (ready) {
|
|
602
|
+
const type = typeof componentType === 'function' ? componentType() : componentType;
|
|
603
|
+
emitComponentInitialized(eventBus, type, fieldKeyFn(), injector);
|
|
604
|
+
}
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
const EMPTY_ERRORS = Object.freeze([]);
|
|
609
|
+
function snapshotParkedDomState(fieldTree) {
|
|
610
|
+
const state = fieldTree?.();
|
|
611
|
+
if (!state)
|
|
612
|
+
return null;
|
|
613
|
+
return {
|
|
614
|
+
disabled: state.disabled?.() ?? false,
|
|
615
|
+
readonly: state.readonly?.() ?? false,
|
|
616
|
+
required: state.required?.() ?? false,
|
|
617
|
+
touched: state.touched?.() ?? false,
|
|
618
|
+
dirty: state.dirty?.() ?? false,
|
|
619
|
+
errors: state.errors?.() ?? EMPTY_ERRORS,
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
function isSameParkedDomState(a, b) {
|
|
623
|
+
return (a === b ||
|
|
624
|
+
(a !== null &&
|
|
625
|
+
b !== null &&
|
|
626
|
+
a.disabled === b.disabled &&
|
|
627
|
+
a.readonly === b.readonly &&
|
|
628
|
+
a.required === b.required &&
|
|
629
|
+
a.touched === b.touched &&
|
|
630
|
+
a.dirty === b.dirty &&
|
|
631
|
+
a.errors === b.errors));
|
|
632
|
+
}
|
|
633
|
+
|
|
362
634
|
class DfFieldOutlet {
|
|
363
635
|
dfFieldOutlet = input.required(/* @ts-ignore */
|
|
364
636
|
...(ngDevMode ? [{ debugName: "dfFieldOutlet" }] : /* istanbul ignore next */ []));
|
|
@@ -371,23 +643,50 @@ class DfFieldOutlet {
|
|
|
371
643
|
wrapperRegistry = inject(WRAPPER_REGISTRY);
|
|
372
644
|
defaultWrappersSignal = inject(DEFAULT_WRAPPERS, { optional: true });
|
|
373
645
|
readonlyFieldCache = inject(READONLY_FIELD_TREE_CACHE);
|
|
374
|
-
|
|
646
|
+
eventBus = inject(EventBus, { optional: true });
|
|
647
|
+
injector = inject(Injector);
|
|
648
|
+
fieldComponent = new FieldComponentSlot();
|
|
375
649
|
componentIdentity = computed(() => this.dfFieldOutlet().component, /* @ts-ignore */
|
|
376
650
|
...(ngDevMode ? [{ debugName: "componentIdentity" }] : /* istanbul ignore next */ []));
|
|
377
651
|
renderReady = computed(() => this.dfFieldOutlet().renderReady() && !this.dfFieldOutlet().hidden(), /* @ts-ignore */
|
|
378
652
|
...(ngDevMode ? [{ debugName: "renderReady" }] : /* istanbul ignore next */ []));
|
|
653
|
+
hidden = computed(() => this.dfFieldOutlet().hidden(), /* @ts-ignore */
|
|
654
|
+
...(ngDevMode ? [{ debugName: "hidden" }] : /* istanbul ignore next */ []));
|
|
379
655
|
rawInputs = computed(() => this.dfFieldOutlet().inputs(), /* @ts-ignore */
|
|
380
656
|
...(ngDevMode ? [{ debugName: "rawInputs" }] : /* istanbul ignore next */ []));
|
|
381
657
|
wrappers = computed(() => resolveWrappers(this.dfFieldOutlet().fieldDef, this.defaultWrappersSignal?.(), this.wrapperAutoAssociations, this.wrapperRegistry), { ...(ngDevMode ? { debugName: "wrappers" } : /* istanbul ignore next */ {}), equal: isSameWrapperChain });
|
|
382
658
|
fieldInputs = computed(() => buildFieldInputs(this.rawInputs(), this.readonlyFieldCache, this.dfFieldOutlet().fieldDef.type, this.dfFieldOutlet().fieldDef.validationMessages), /* @ts-ignore */
|
|
383
659
|
...(ngDevMode ? [{ debugName: "fieldInputs" }] : /* istanbul ignore next */ []));
|
|
384
660
|
defaultEnvInjector = inject(EnvironmentInjector);
|
|
385
|
-
|
|
661
|
+
fieldEnvInjector = computed(() => this.dfFieldOutletEnvironmentInjector() ?? this.defaultEnvInjector, /* @ts-ignore */
|
|
386
662
|
...(ngDevMode ? [{ debugName: "fieldEnvInjector" }] : /* istanbul ignore next */ []));
|
|
387
|
-
|
|
663
|
+
fieldInjector = computed(() => this.dfFieldOutlet().injector, /* @ts-ignore */
|
|
388
664
|
...(ngDevMode ? [{ debugName: "fieldInjector" }] : /* istanbul ignore next */ []));
|
|
389
665
|
outermostHostClasses = computed(() => getGridClassString(this.dfFieldOutlet().fieldDef) || undefined, /* @ts-ignore */
|
|
390
666
|
...(ngDevMode ? [{ debugName: "outermostHostClasses" }] : /* istanbul ignore next */ []));
|
|
667
|
+
formOptions = inject(FORM_OPTIONS, { optional: true });
|
|
668
|
+
globalFieldWindowing = inject(FIELD_WINDOWING);
|
|
669
|
+
viewportObserver = inject(FieldViewportObserver, { optional: true });
|
|
670
|
+
parking = computed(() => resolveFieldWindowing(this.globalFieldWindowing, this.formOptions?.()?.fieldWindowing).park, /* @ts-ignore */
|
|
671
|
+
...(ngDevMode ? [{ debugName: "parking" }] : /* istanbul ignore next */ []));
|
|
672
|
+
observedElement = computed(() => {
|
|
673
|
+
const state = this.fieldComponent.snapshot();
|
|
674
|
+
return state.phase === 'empty' ? null : state.ref.location.nativeElement;
|
|
675
|
+
}, /* @ts-ignore */
|
|
676
|
+
...(ngDevMode ? [{ debugName: "observedElement" }] : /* istanbul ignore next */ []));
|
|
677
|
+
visibility = derivedFrom([this.observedElement, this.parking], pipe(switchMap(([element, parking]) => {
|
|
678
|
+
const observer = this.viewportObserver;
|
|
679
|
+
return parking.enabled && element && observer ? observer.observe(element, parking.margin) : of(true);
|
|
680
|
+
})), { initialValue: true });
|
|
681
|
+
focusEpoch = signal(0, /* @ts-ignore */
|
|
682
|
+
...(ngDevMode ? [{ debugName: "focusEpoch" }] : /* istanbul ignore next */ []));
|
|
683
|
+
parked = computed(() => this.parking().enabled && !this.visibility(), /* @ts-ignore */
|
|
684
|
+
...(ngDevMode ? [{ debugName: "parked" }] : /* istanbul ignore next */ []));
|
|
685
|
+
fieldTree = computed(() => {
|
|
686
|
+
const candidate = this.rawInputs()['field'];
|
|
687
|
+
return typeof candidate === 'function' ? candidate : undefined;
|
|
688
|
+
}, { ...(ngDevMode ? { debugName: "fieldTree" } : /* istanbul ignore next */ {}), equal: Object.is });
|
|
689
|
+
parkedDomState = computed(() => (this.parked() ? snapshotParkedDomState(this.fieldTree()) : null), { ...(ngDevMode ? { debugName: "parkedDomState" } : /* istanbul ignore next */ {}), equal: isSameParkedDomState });
|
|
391
690
|
constructor() {
|
|
392
691
|
createWrapperChainController({
|
|
393
692
|
vcr: this.vcr,
|
|
@@ -403,8 +702,50 @@ class DfFieldOutlet {
|
|
|
403
702
|
this.fieldComponent.mountOrReuse(slot, resolved.component, resolved.injector, this.fieldEnvInjector(), this.rawInputs(), this.fieldInputs());
|
|
404
703
|
},
|
|
405
704
|
});
|
|
406
|
-
|
|
705
|
+
explicitEffect([this.rawInputs, this.fieldInputs], ([rawInputs, fieldInputs]) => this.fieldComponent.pushInputs(rawInputs, fieldInputs));
|
|
706
|
+
explicitEffect([this.hidden], ([hidden]) => {
|
|
707
|
+
if (!hidden || !this.eventBus)
|
|
708
|
+
return;
|
|
709
|
+
const resolved = this.dfFieldOutlet();
|
|
710
|
+
const groupContext = resolved.injector.get(GROUP_CONTEXT, null);
|
|
711
|
+
for (const { type, path } of collectInitializingContainers([resolved.fieldDef], groupContext?.groupPath())) {
|
|
712
|
+
emitComponentInitialized(this.eventBus, type, path, this.injector);
|
|
713
|
+
}
|
|
714
|
+
});
|
|
715
|
+
explicitEffect([this.observedElement, this.parking], ([element, parking], onCleanup) => {
|
|
716
|
+
if (!parking.enabled || !element)
|
|
717
|
+
return;
|
|
718
|
+
const bumpEpoch = () => queueMicrotask(() => {
|
|
719
|
+
if (!this.destroyRef.destroyed)
|
|
720
|
+
this.focusEpoch.update((n) => n + 1);
|
|
721
|
+
});
|
|
722
|
+
element.addEventListener('focusin', bumpEpoch);
|
|
723
|
+
element.addEventListener('focusout', bumpEpoch);
|
|
724
|
+
onCleanup(() => {
|
|
725
|
+
element.removeEventListener('focusin', bumpEpoch);
|
|
726
|
+
element.removeEventListener('focusout', bumpEpoch);
|
|
727
|
+
});
|
|
728
|
+
});
|
|
729
|
+
explicitEffect([this.parked, this.focusEpoch, this.parkedDomState], ([parked, , domState]) => {
|
|
730
|
+
if (parked && this.canPark(domState)) {
|
|
731
|
+
const alreadyParked = this.fieldComponent.parked();
|
|
732
|
+
this.fieldComponent.park();
|
|
733
|
+
if (alreadyParked)
|
|
734
|
+
this.fieldComponent.refresh();
|
|
735
|
+
}
|
|
736
|
+
else {
|
|
737
|
+
this.fieldComponent.unpark();
|
|
738
|
+
}
|
|
739
|
+
});
|
|
407
740
|
this.destroyRef.onDestroy(() => this.fieldComponent.destroyOnTeardown());
|
|
741
|
+
}
|
|
742
|
+
canPark(domState) {
|
|
743
|
+
const element = this.observedElement();
|
|
744
|
+
if (!element)
|
|
745
|
+
return false;
|
|
746
|
+
if (element.contains(document.activeElement))
|
|
747
|
+
return false;
|
|
748
|
+
return (domState?.errors.length ?? 0) === 0;
|
|
408
749
|
}
|
|
409
750
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: DfFieldOutlet, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
410
751
|
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.1", type: DfFieldOutlet, isStandalone: true, selector: "[dfFieldOutlet]", inputs: { dfFieldOutlet: { classPropertyName: "dfFieldOutlet", publicName: "dfFieldOutlet", isSignal: true, isRequired: true, transformFunction: null }, dfFieldOutletEnvironmentInjector: { classPropertyName: "dfFieldOutletEnvironmentInjector", publicName: "dfFieldOutletEnvironmentInjector", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
|
|
@@ -450,455 +791,91 @@ class FormSubmitEvent {
|
|
|
450
791
|
type = 'submit';
|
|
451
792
|
}
|
|
452
793
|
|
|
453
|
-
|
|
454
|
-
currentPageIndex;
|
|
455
|
-
totalPages;
|
|
456
|
-
previousPageIndex;
|
|
457
|
-
type = 'page-change';
|
|
458
|
-
constructor(
|
|
459
|
-
currentPageIndex,
|
|
460
|
-
totalPages,
|
|
461
|
-
previousPageIndex) {
|
|
462
|
-
this.currentPageIndex = currentPageIndex;
|
|
463
|
-
this.totalPages = totalPages;
|
|
464
|
-
this.previousPageIndex = previousPageIndex;
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
class PagerStateEvent {
|
|
469
|
-
state;
|
|
470
|
-
type = 'pager-state';
|
|
471
|
-
constructor(state) {
|
|
472
|
-
this.state = state;
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
const PAGE_PRELOAD_WINDOW = new InjectionToken('PAGE_PRELOAD_WINDOW', {
|
|
794
|
+
const COMPONENT_CACHE = new InjectionToken('COMPONENT_CACHE', {
|
|
477
795
|
providedIn: 'root',
|
|
478
|
-
factory: () =>
|
|
796
|
+
factory: () => new Map(),
|
|
797
|
+
});
|
|
798
|
+
const COMPONENT_LOAD_CACHE = new InjectionToken('COMPONENT_LOAD_CACHE', {
|
|
799
|
+
providedIn: 'root',
|
|
800
|
+
factory: () => new Map(),
|
|
479
801
|
});
|
|
802
|
+
function injectFieldRegistry() {
|
|
803
|
+
const registry = inject(FIELD_REGISTRY);
|
|
804
|
+
const componentCache = inject(COMPONENT_CACHE);
|
|
805
|
+
const componentLoadCache = inject(COMPONENT_LOAD_CACHE);
|
|
806
|
+
return {
|
|
807
|
+
getType(name) {
|
|
808
|
+
return registry.get(name);
|
|
809
|
+
},
|
|
810
|
+
hasType(name) {
|
|
811
|
+
return registry.has(name);
|
|
812
|
+
},
|
|
813
|
+
async loadTypeComponent(name) {
|
|
814
|
+
const fieldType = registry.get(name);
|
|
815
|
+
if (!fieldType) {
|
|
816
|
+
throw new DynamicFormError(`Field type "${name}" is not registered`);
|
|
817
|
+
}
|
|
818
|
+
if (!fieldType.loadComponent) {
|
|
819
|
+
return undefined;
|
|
820
|
+
}
|
|
821
|
+
const cached = componentCache.get(name);
|
|
822
|
+
if (cached) {
|
|
823
|
+
return cached;
|
|
824
|
+
}
|
|
825
|
+
const pending = componentLoadCache.get(name);
|
|
826
|
+
if (pending) {
|
|
827
|
+
return pending;
|
|
828
|
+
}
|
|
829
|
+
try {
|
|
830
|
+
const load = fieldType
|
|
831
|
+
.loadComponent()
|
|
832
|
+
.then(resolveDefaultExport)
|
|
833
|
+
.then((component) => {
|
|
834
|
+
if (component) {
|
|
835
|
+
componentCache.set(name, component);
|
|
836
|
+
}
|
|
837
|
+
return component;
|
|
838
|
+
})
|
|
839
|
+
.catch((error) => {
|
|
840
|
+
throw new DynamicFormError(`Failed to load component for field type "${name}": ${error}`);
|
|
841
|
+
})
|
|
842
|
+
.finally(() => componentLoadCache.delete(name));
|
|
843
|
+
componentLoadCache.set(name, load);
|
|
844
|
+
return load;
|
|
845
|
+
}
|
|
846
|
+
catch (error) {
|
|
847
|
+
throw new DynamicFormError(`Failed to load component for field type "${name}": ${error}`);
|
|
848
|
+
}
|
|
849
|
+
},
|
|
850
|
+
getLoadedComponent(name) {
|
|
851
|
+
return componentCache.get(name);
|
|
852
|
+
},
|
|
853
|
+
getTypes() {
|
|
854
|
+
return Array.from(registry.values());
|
|
855
|
+
},
|
|
856
|
+
registerTypes(types) {
|
|
857
|
+
types.forEach((type) => {
|
|
858
|
+
registry.set(type.name, type);
|
|
859
|
+
});
|
|
860
|
+
},
|
|
861
|
+
get raw() {
|
|
862
|
+
return registry;
|
|
863
|
+
},
|
|
864
|
+
};
|
|
865
|
+
}
|
|
480
866
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
867
|
+
const NORMALIZED_ARRAY_METADATA = Symbol('normalizedArrayMetadata');
|
|
868
|
+
function setNormalizedArrayMetadata(arrayField, metadata) {
|
|
869
|
+
arrayField[NORMALIZED_ARRAY_METADATA] = metadata;
|
|
870
|
+
}
|
|
871
|
+
function getNormalizedArrayMetadata(arrayField) {
|
|
872
|
+
return arrayField[NORMALIZED_ARRAY_METADATA];
|
|
485
873
|
}
|
|
486
874
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
formOptions = inject(FORM_OPTIONS, { optional: true });
|
|
492
|
-
globalPreloadWindow = inject(PAGE_PRELOAD_WINDOW);
|
|
493
|
-
pageFields = input.required(/* @ts-ignore */
|
|
494
|
-
...(ngDevMode ? [{ debugName: "pageFields" }] : /* istanbul ignore next */ []));
|
|
495
|
-
form = input.required(/* @ts-ignore */
|
|
496
|
-
...(ngDevMode ? [{ debugName: "form" }] : /* istanbul ignore next */ []));
|
|
497
|
-
fieldSignalContext = input.required(/* @ts-ignore */
|
|
498
|
-
...(ngDevMode ? [{ debugName: "fieldSignalContext" }] : /* istanbul ignore next */ []));
|
|
499
|
-
pageHiddenStates = computed(() => {
|
|
500
|
-
const pages = this.pageFields();
|
|
501
|
-
return pages.map((page) => this.evaluatePageHidden(page));
|
|
502
|
-
}, /* @ts-ignore */
|
|
503
|
-
...(ngDevMode ? [{ debugName: "pageHiddenStates" }] : /* istanbul ignore next */ []));
|
|
504
|
-
visiblePageIndices = computed(() => {
|
|
505
|
-
const hiddenStates = this.pageHiddenStates();
|
|
506
|
-
return hiddenStates
|
|
507
|
-
.map((hidden, index) => ({ index, hidden }))
|
|
508
|
-
.filter((item) => !item.hidden)
|
|
509
|
-
.map((item) => item.index);
|
|
510
|
-
}, /* @ts-ignore */
|
|
511
|
-
...(ngDevMode ? [{ debugName: "visiblePageIndices" }] : /* istanbul ignore next */ []));
|
|
512
|
-
currentPageIndex = linkedSignal(() => {
|
|
513
|
-
const totalPages = this.pageFields().length;
|
|
514
|
-
if (totalPages === 0)
|
|
515
|
-
return 0;
|
|
516
|
-
return untracked(() => this.resolveInitialLanding());
|
|
517
|
-
}, /* @ts-ignore */
|
|
518
|
-
...(ngDevMode ? [{ debugName: "currentPageIndex" }] : /* istanbul ignore next */ []));
|
|
519
|
-
resolveInitialLanding() {
|
|
520
|
-
const { index, validate } = this.initialPage();
|
|
521
|
-
const visible = this.visiblePageIndices();
|
|
522
|
-
if (index === 0 || visible.length === 0)
|
|
523
|
-
return 0;
|
|
524
|
-
const target = visible.includes(index) ? index : this.findNearestVisiblePage(index, visible);
|
|
525
|
-
if (target <= 0)
|
|
526
|
-
return Math.max(target, 0);
|
|
527
|
-
if (!validate)
|
|
528
|
-
return target;
|
|
529
|
-
const firstInvalid = visible.filter((i) => i < target).find((i) => !this.isPageValid(i));
|
|
530
|
-
return firstInvalid ?? target;
|
|
531
|
-
}
|
|
532
|
-
initialPage = computed(() => {
|
|
533
|
-
const raw = this.formOptions?.()?.initialPage;
|
|
534
|
-
const totalPages = this.pageFields().length;
|
|
535
|
-
const requested = typeof raw === 'number' ? raw : raw?.index;
|
|
536
|
-
const validate = typeof raw === 'number' ? false : (raw?.validate ?? false);
|
|
537
|
-
if (typeof requested !== 'number' || !Number.isFinite(requested) || requested < 0 || totalPages === 0) {
|
|
538
|
-
return { index: 0, validate: false };
|
|
539
|
-
}
|
|
540
|
-
return { index: Math.min(Math.trunc(requested), totalPages - 1), validate };
|
|
541
|
-
}, /* @ts-ignore */
|
|
542
|
-
...(ngDevMode ? [{ debugName: "initialPage" }] : /* istanbul ignore next */ []));
|
|
543
|
-
state = computed(() => {
|
|
544
|
-
const currentIndex = this.currentPageIndex();
|
|
545
|
-
const totalPages = this.pageFields().length;
|
|
546
|
-
const visibleIndices = this.visiblePageIndices();
|
|
547
|
-
const currentVisiblePosition = visibleIndices.indexOf(currentIndex);
|
|
548
|
-
const isFirstVisiblePage = currentVisiblePosition === 0 || currentVisiblePosition === -1;
|
|
549
|
-
const isLastVisiblePage = currentVisiblePosition >= visibleIndices.length - 1;
|
|
550
|
-
return {
|
|
551
|
-
currentPageIndex: currentIndex,
|
|
552
|
-
totalPages,
|
|
553
|
-
isFirstPage: isFirstVisiblePage,
|
|
554
|
-
isLastPage: isLastVisiblePage,
|
|
555
|
-
};
|
|
556
|
-
}, /* @ts-ignore */
|
|
557
|
-
...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
|
|
558
|
-
currentPageValid = computed(() => this.isPageValid(this.currentPageIndex()), /* @ts-ignore */
|
|
559
|
-
...(ngDevMode ? [{ debugName: "currentPageValid" }] : /* istanbul ignore next */ []));
|
|
560
|
-
isPageValid(pageIndex) {
|
|
561
|
-
const pages = this.pageFields();
|
|
562
|
-
const form = this.form();
|
|
563
|
-
if (pageIndex < 0 || pageIndex >= pages.length) {
|
|
564
|
-
return true;
|
|
565
|
-
}
|
|
566
|
-
const leafKeys = collectLeafFieldKeys(pages[pageIndex].fields || []);
|
|
567
|
-
for (const fieldKey of leafKeys) {
|
|
568
|
-
const field = form[fieldKey];
|
|
569
|
-
if (field && typeof field === 'function') {
|
|
570
|
-
const fieldState = field();
|
|
571
|
-
if (fieldState && typeof fieldState.valid === 'function' && !fieldState.valid()) {
|
|
572
|
-
return false;
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
return true;
|
|
577
|
-
}
|
|
578
|
-
extendedFieldSignalContext = computed(() => ({
|
|
579
|
-
...this.fieldSignalContext(),
|
|
580
|
-
currentPageValid: this.currentPageValid,
|
|
581
|
-
}), /* @ts-ignore */
|
|
582
|
-
...(ngDevMode ? [{ debugName: "extendedFieldSignalContext" }] : /* istanbul ignore next */ []));
|
|
583
|
-
preloadWindow = computed(() => {
|
|
584
|
-
const perForm = this.formOptions?.()?.pagePreloadWindow;
|
|
585
|
-
return clampWindowSize(perForm, this.globalPreloadWindow);
|
|
586
|
-
}, /* @ts-ignore */
|
|
587
|
-
...(ngDevMode ? [{ debugName: "preloadWindow" }] : /* istanbul ignore next */ []));
|
|
588
|
-
isWithinPreloadWindow(i) {
|
|
589
|
-
return Math.abs(i - this.state().currentPageIndex) <= this.preloadWindow();
|
|
590
|
-
}
|
|
591
|
-
constructor() {
|
|
592
|
-
this.setupEventListeners();
|
|
593
|
-
explicitEffect([this.state, this.visiblePageIndices], ([state, visibleIndices]) => {
|
|
594
|
-
const currentVisiblePosition = visibleIndices.indexOf(state.currentPageIndex);
|
|
595
|
-
if (currentVisiblePosition === -1 && visibleIndices.length > 0) {
|
|
596
|
-
const nearest = this.findNearestVisiblePage(state.currentPageIndex, visibleIndices);
|
|
597
|
-
if (nearest !== -1) {
|
|
598
|
-
this.executePageChange(nearest);
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
});
|
|
602
|
-
}
|
|
603
|
-
navigateToNextPage() {
|
|
604
|
-
const disableWhenPageInvalid = this.formOptions?.()?.nextButton?.disableWhenPageInvalid ?? true;
|
|
605
|
-
if (disableWhenPageInvalid && !this.currentPageValid()) {
|
|
606
|
-
return {
|
|
607
|
-
success: false,
|
|
608
|
-
newPageIndex: this.state().currentPageIndex,
|
|
609
|
-
error: 'Current page has invalid fields',
|
|
610
|
-
};
|
|
611
|
-
}
|
|
612
|
-
const currentState = this.state();
|
|
613
|
-
const visibleIndices = this.visiblePageIndices();
|
|
614
|
-
if (currentState.isLastPage) {
|
|
615
|
-
return {
|
|
616
|
-
success: false,
|
|
617
|
-
newPageIndex: currentState.currentPageIndex,
|
|
618
|
-
error: 'Already on the last visible page',
|
|
619
|
-
};
|
|
620
|
-
}
|
|
621
|
-
const currentVisiblePosition = visibleIndices.indexOf(currentState.currentPageIndex);
|
|
622
|
-
if (currentVisiblePosition === -1 || currentVisiblePosition >= visibleIndices.length - 1) {
|
|
623
|
-
return {
|
|
624
|
-
success: false,
|
|
625
|
-
newPageIndex: currentState.currentPageIndex,
|
|
626
|
-
error: 'No next visible page available',
|
|
627
|
-
};
|
|
628
|
-
}
|
|
629
|
-
const nextVisiblePageIndex = visibleIndices[currentVisiblePosition + 1];
|
|
630
|
-
return this.executePageChange(nextVisiblePageIndex);
|
|
631
|
-
}
|
|
632
|
-
navigateToPreviousPage() {
|
|
633
|
-
const currentState = this.state();
|
|
634
|
-
const visibleIndices = this.visiblePageIndices();
|
|
635
|
-
if (currentState.isFirstPage) {
|
|
636
|
-
return {
|
|
637
|
-
success: false,
|
|
638
|
-
newPageIndex: currentState.currentPageIndex,
|
|
639
|
-
error: 'Already on the first visible page',
|
|
640
|
-
};
|
|
641
|
-
}
|
|
642
|
-
const currentVisiblePosition = visibleIndices.indexOf(currentState.currentPageIndex);
|
|
643
|
-
if (currentVisiblePosition <= 0) {
|
|
644
|
-
return {
|
|
645
|
-
success: false,
|
|
646
|
-
newPageIndex: currentState.currentPageIndex,
|
|
647
|
-
error: 'No previous visible page available',
|
|
648
|
-
};
|
|
649
|
-
}
|
|
650
|
-
const prevVisiblePageIndex = visibleIndices[currentVisiblePosition - 1];
|
|
651
|
-
return this.executePageChange(prevVisiblePageIndex);
|
|
652
|
-
}
|
|
653
|
-
navigateToPage(pageIndex, options) {
|
|
654
|
-
const currentIndex = this.state().currentPageIndex;
|
|
655
|
-
const visibleIndices = this.visiblePageIndices();
|
|
656
|
-
if (pageIndex <= currentIndex || !visibleIndices.includes(pageIndex)) {
|
|
657
|
-
return this.executePageChange(pageIndex);
|
|
658
|
-
}
|
|
659
|
-
if (options?.validate === false) {
|
|
660
|
-
return this.executePageChange(pageIndex);
|
|
661
|
-
}
|
|
662
|
-
const disableWhenPageInvalid = this.formOptions?.()?.nextButton?.disableWhenPageInvalid ?? true;
|
|
663
|
-
if (!disableWhenPageInvalid) {
|
|
664
|
-
return this.executePageChange(pageIndex);
|
|
665
|
-
}
|
|
666
|
-
const crossedPages = visibleIndices.filter((index) => index >= currentIndex && index < pageIndex);
|
|
667
|
-
const firstInvalidPage = crossedPages.find((index) => !this.isPageValid(index));
|
|
668
|
-
if (firstInvalidPage === undefined) {
|
|
669
|
-
return this.executePageChange(pageIndex);
|
|
670
|
-
}
|
|
671
|
-
this.executePageChange(firstInvalidPage);
|
|
672
|
-
return {
|
|
673
|
-
success: false,
|
|
674
|
-
newPageIndex: firstInvalidPage,
|
|
675
|
-
error: `Cannot navigate to page ${pageIndex}: page ${firstInvalidPage} has invalid fields`,
|
|
676
|
-
};
|
|
677
|
-
}
|
|
678
|
-
executePageChange(pageIndex) {
|
|
679
|
-
const currentState = this.state();
|
|
680
|
-
const totalPages = currentState.totalPages;
|
|
681
|
-
if (pageIndex < 0 || pageIndex >= totalPages) {
|
|
682
|
-
return {
|
|
683
|
-
success: false,
|
|
684
|
-
newPageIndex: currentState.currentPageIndex,
|
|
685
|
-
error: `Invalid page index: ${pageIndex}. Valid range is 0 to ${totalPages - 1}`,
|
|
686
|
-
};
|
|
687
|
-
}
|
|
688
|
-
const visibleIndices = this.visiblePageIndices();
|
|
689
|
-
if (!visibleIndices.includes(pageIndex)) {
|
|
690
|
-
return {
|
|
691
|
-
success: false,
|
|
692
|
-
newPageIndex: currentState.currentPageIndex,
|
|
693
|
-
error: `Cannot navigate to hidden page at index ${pageIndex}. Visible pages: [${visibleIndices.join(', ')}]`,
|
|
694
|
-
};
|
|
695
|
-
}
|
|
696
|
-
if (pageIndex === currentState.currentPageIndex) {
|
|
697
|
-
return {
|
|
698
|
-
success: true,
|
|
699
|
-
newPageIndex: pageIndex,
|
|
700
|
-
};
|
|
701
|
-
}
|
|
702
|
-
const previousIndex = currentState.currentPageIndex;
|
|
703
|
-
this.currentPageIndex.set(pageIndex);
|
|
704
|
-
this.eventBus.dispatch(PageChangeEvent, pageIndex, totalPages, previousIndex);
|
|
705
|
-
return {
|
|
706
|
-
success: true,
|
|
707
|
-
newPageIndex: pageIndex,
|
|
708
|
-
};
|
|
709
|
-
}
|
|
710
|
-
findNearestVisiblePage(currentIndex, visibleIndices) {
|
|
711
|
-
let nearest = -1;
|
|
712
|
-
let minDistance = Infinity;
|
|
713
|
-
for (const idx of visibleIndices) {
|
|
714
|
-
const distance = Math.abs(idx - currentIndex);
|
|
715
|
-
if (distance < minDistance || (distance === minDistance && idx > nearest)) {
|
|
716
|
-
minDistance = distance;
|
|
717
|
-
nearest = idx;
|
|
718
|
-
}
|
|
719
|
-
}
|
|
720
|
-
return nearest;
|
|
721
|
-
}
|
|
722
|
-
setupEventListeners() {
|
|
723
|
-
this.eventBus
|
|
724
|
-
.on('next-page')
|
|
725
|
-
.pipe(takeUntilDestroyed())
|
|
726
|
-
.subscribe(() => {
|
|
727
|
-
this.navigateToNextPage();
|
|
728
|
-
});
|
|
729
|
-
this.eventBus
|
|
730
|
-
.on('previous-page')
|
|
731
|
-
.pipe(takeUntilDestroyed())
|
|
732
|
-
.subscribe(() => {
|
|
733
|
-
this.navigateToPreviousPage();
|
|
734
|
-
});
|
|
735
|
-
this.eventBus
|
|
736
|
-
.on('go-to-page')
|
|
737
|
-
.pipe(takeUntilDestroyed())
|
|
738
|
-
.subscribe((event) => {
|
|
739
|
-
this.navigateToPage(event.pageIndex, event.options);
|
|
740
|
-
});
|
|
741
|
-
explicitEffect([this.state], ([state]) => this.eventBus.dispatch(PagerStateEvent, state));
|
|
742
|
-
}
|
|
743
|
-
evaluatePageHidden(page) {
|
|
744
|
-
if (!page.logic || page.logic.length === 0) {
|
|
745
|
-
return false;
|
|
746
|
-
}
|
|
747
|
-
const hiddenLogic = page.logic.filter((l) => l.type === 'hidden');
|
|
748
|
-
if (hiddenLogic.length === 0) {
|
|
749
|
-
return false;
|
|
750
|
-
}
|
|
751
|
-
for (const logic of hiddenLogic) {
|
|
752
|
-
if (typeof logic.condition === 'boolean') {
|
|
753
|
-
if (logic.condition) {
|
|
754
|
-
return true;
|
|
755
|
-
}
|
|
756
|
-
continue;
|
|
757
|
-
}
|
|
758
|
-
const condition = logic.condition;
|
|
759
|
-
const context = this.fieldContextRegistry.createDisplayOnlyContext(page.key || '', this.functionRegistry.getCustomFunctions());
|
|
760
|
-
if (evaluateCondition(condition, context)) {
|
|
761
|
-
return true;
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
|
-
return false;
|
|
765
|
-
}
|
|
766
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: PageOrchestratorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
767
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", type: PageOrchestratorComponent, isStandalone: true, selector: "div[page-orchestrator]", inputs: { pageFields: { classPropertyName: "pageFields", publicName: "pageFields", isSignal: true, isRequired: true, transformFunction: null }, form: { classPropertyName: "form", publicName: "form", isSignal: true, isRequired: true, transformFunction: null }, fieldSignalContext: { classPropertyName: "fieldSignalContext", publicName: "fieldSignalContext", isSignal: true, isRequired: true, transformFunction: null } }, host: { properties: { "attr.data-current-page": "state().currentPageIndex", "attr.data-total-pages": "state().totalPages" }, classAttribute: "df-page-orchestrator" }, providers: [
|
|
768
|
-
{
|
|
769
|
-
provide: FIELD_SIGNAL_CONTEXT,
|
|
770
|
-
useFactory: (orchestrator) => orchestrator.extendedFieldSignalContext(),
|
|
771
|
-
deps: [PageOrchestratorComponent],
|
|
772
|
-
},
|
|
773
|
-
], ngImport: i0, template: `
|
|
774
|
-
@for (pageField of pageFields(); track pageField.key; let i = $index) {
|
|
775
|
-
<!--
|
|
776
|
-
Skip pages hidden by 'hidden' logic conditions entirely. FormStateManager
|
|
777
|
-
derives schema/value/validators from config (not from mounted components),
|
|
778
|
-
so unmounting a hidden page has no effect on form state — only on render +
|
|
779
|
-
CD cost. This is the page-level equivalent of the existing field-level
|
|
780
|
-
\`@if (!field.hidden())\` gate in page-field.component.ts.
|
|
781
|
-
-->
|
|
782
|
-
@if (!pageHiddenStates()[i]) {
|
|
783
|
-
@if (isWithinPreloadWindow(i)) {
|
|
784
|
-
<!-- Current and preloaded neighbour pages: render immediately for flicker-free navigation -->
|
|
785
|
-
@defer (on immediate) {
|
|
786
|
-
<section
|
|
787
|
-
page-field
|
|
788
|
-
[field]="pageField"
|
|
789
|
-
[key]="pageField.key"
|
|
790
|
-
[pageIndex]="i"
|
|
791
|
-
[isVisible]="i === state().currentPageIndex"
|
|
792
|
-
></section>
|
|
793
|
-
} @placeholder {
|
|
794
|
-
<div class="df-page-placeholder" [attr.data-page-index]="i" [attr.data-page-key]="pageField.key"></div>
|
|
795
|
-
}
|
|
796
|
-
} @else {
|
|
797
|
-
<!--
|
|
798
|
-
Pages outside the preload window render only a lightweight placeholder
|
|
799
|
-
— they are NOT mounted. Mounting every page eagerly (previously via
|
|
800
|
-
\`@defer (on idle)\`, which fires almost immediately post-load) put all
|
|
801
|
-
fields of every page in the DOM at once: O(all-fields) initial render
|
|
802
|
-
blocking + O(all-fields) change detection on every keystroke, with no
|
|
803
|
-
visible benefit since only one page is shown at a time.
|
|
804
|
-
|
|
805
|
-
Form state (schema/value/validators) derives from config, not from
|
|
806
|
-
mounted components, so leaving a page unmounted does not affect
|
|
807
|
-
validity, derivations, or navigation. A page mounts the moment
|
|
808
|
-
navigation brings it into the preload window (above), which is
|
|
809
|
-
flicker-free for sequential next/previous navigation. The window size
|
|
810
|
-
is configurable via \`withPagePreload(n)\` or \`FormOptions.pagePreloadWindow\`.
|
|
811
|
-
-->
|
|
812
|
-
<div class="df-page-placeholder" [attr.data-page-index]="i" [attr.data-page-key]="pageField.key"></div>
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
}
|
|
816
|
-
`, isInline: true, styles: [":host{display:block;position:relative;width:100%}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, deferBlockDependencies: [() => [/* @ts-ignore */
|
|
817
|
-
import('./ng-forge-dynamic-forms-page-field.component-CwH22lRl.mjs').then(m => m.default)]] });
|
|
818
|
-
}
|
|
819
|
-
i0.ɵɵngDeclareClassMetadataAsync({ minVersion: "18.0.0", version: "22.0.1", ngImport: i0, type: PageOrchestratorComponent, resolveDeferredDeps: () => [/* @ts-ignore */
|
|
820
|
-
import('./ng-forge-dynamic-forms-page-field.component-CwH22lRl.mjs').then(m => m.default)], resolveMetadata: PageFieldComponent => ({ decorators: [{
|
|
821
|
-
type: Component,
|
|
822
|
-
args: [{ selector: 'div[page-orchestrator]', imports: [PageFieldComponent], template: `
|
|
823
|
-
@for (pageField of pageFields(); track pageField.key; let i = $index) {
|
|
824
|
-
<!--
|
|
825
|
-
Skip pages hidden by 'hidden' logic conditions entirely. FormStateManager
|
|
826
|
-
derives schema/value/validators from config (not from mounted components),
|
|
827
|
-
so unmounting a hidden page has no effect on form state — only on render +
|
|
828
|
-
CD cost. This is the page-level equivalent of the existing field-level
|
|
829
|
-
\`@if (!field.hidden())\` gate in page-field.component.ts.
|
|
830
|
-
-->
|
|
831
|
-
@if (!pageHiddenStates()[i]) {
|
|
832
|
-
@if (isWithinPreloadWindow(i)) {
|
|
833
|
-
<!-- Current and preloaded neighbour pages: render immediately for flicker-free navigation -->
|
|
834
|
-
@defer (on immediate) {
|
|
835
|
-
<section
|
|
836
|
-
page-field
|
|
837
|
-
[field]="pageField"
|
|
838
|
-
[key]="pageField.key"
|
|
839
|
-
[pageIndex]="i"
|
|
840
|
-
[isVisible]="i === state().currentPageIndex"
|
|
841
|
-
></section>
|
|
842
|
-
} @placeholder {
|
|
843
|
-
<div class="df-page-placeholder" [attr.data-page-index]="i" [attr.data-page-key]="pageField.key"></div>
|
|
844
|
-
}
|
|
845
|
-
} @else {
|
|
846
|
-
<!--
|
|
847
|
-
Pages outside the preload window render only a lightweight placeholder
|
|
848
|
-
— they are NOT mounted. Mounting every page eagerly (previously via
|
|
849
|
-
\`@defer (on idle)\`, which fires almost immediately post-load) put all
|
|
850
|
-
fields of every page in the DOM at once: O(all-fields) initial render
|
|
851
|
-
blocking + O(all-fields) change detection on every keystroke, with no
|
|
852
|
-
visible benefit since only one page is shown at a time.
|
|
853
|
-
|
|
854
|
-
Form state (schema/value/validators) derives from config, not from
|
|
855
|
-
mounted components, so leaving a page unmounted does not affect
|
|
856
|
-
validity, derivations, or navigation. A page mounts the moment
|
|
857
|
-
navigation brings it into the preload window (above), which is
|
|
858
|
-
flicker-free for sequential next/previous navigation. The window size
|
|
859
|
-
is configurable via \`withPagePreload(n)\` or \`FormOptions.pagePreloadWindow\`.
|
|
860
|
-
-->
|
|
861
|
-
<div class="df-page-placeholder" [attr.data-page-index]="i" [attr.data-page-key]="pageField.key"></div>
|
|
862
|
-
}
|
|
863
|
-
}
|
|
864
|
-
}
|
|
865
|
-
`, host: {
|
|
866
|
-
class: 'df-page-orchestrator',
|
|
867
|
-
'[attr.data-current-page]': 'state().currentPageIndex',
|
|
868
|
-
'[attr.data-total-pages]': 'state().totalPages',
|
|
869
|
-
}, providers: [
|
|
870
|
-
{
|
|
871
|
-
provide: FIELD_SIGNAL_CONTEXT,
|
|
872
|
-
useFactory: (orchestrator) => orchestrator.extendedFieldSignalContext(),
|
|
873
|
-
deps: [PageOrchestratorComponent],
|
|
874
|
-
},
|
|
875
|
-
], changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;position:relative;width:100%}\n"] }]
|
|
876
|
-
}], ctorParameters: () => [], propDecorators: { pageFields: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageFields", required: true }] }], form: [{ type: i0.Input, args: [{ isSignal: true, alias: "form", required: true }] }], fieldSignalContext: [{ type: i0.Input, args: [{ isSignal: true, alias: "fieldSignalContext", required: true }] }] } }) });
|
|
877
|
-
function collectLeafFieldKeys(fields) {
|
|
878
|
-
const keys = [];
|
|
879
|
-
for (const field of fields) {
|
|
880
|
-
if (isRowField(field) || isGenericContainerField(field)) {
|
|
881
|
-
keys.push(...collectLeafFieldKeys(field.fields));
|
|
882
|
-
}
|
|
883
|
-
else if (field.key) {
|
|
884
|
-
keys.push(field.key);
|
|
885
|
-
}
|
|
886
|
-
}
|
|
887
|
-
return keys;
|
|
888
|
-
}
|
|
889
|
-
|
|
890
|
-
const NORMALIZED_ARRAY_METADATA = Symbol('normalizedArrayMetadata');
|
|
891
|
-
function setNormalizedArrayMetadata(arrayField, metadata) {
|
|
892
|
-
arrayField[NORMALIZED_ARRAY_METADATA] = metadata;
|
|
893
|
-
}
|
|
894
|
-
function getNormalizedArrayMetadata(arrayField) {
|
|
895
|
-
return arrayField[NORMALIZED_ARRAY_METADATA];
|
|
896
|
-
}
|
|
897
|
-
|
|
898
|
-
function normalizeSimplifiedArrays(fields) {
|
|
899
|
-
return fields.flatMap((field) => {
|
|
900
|
-
if (isContainerField(field) && !isArrayField(field) && hasChildFields(field)) {
|
|
901
|
-
return [{ ...field, fields: normalizeSimplifiedArrays(field.fields) }];
|
|
875
|
+
function normalizeSimplifiedArrays(fields) {
|
|
876
|
+
return fields.flatMap((field) => {
|
|
877
|
+
if (isContainerField(field) && !isArrayField(field) && hasChildFields(field)) {
|
|
878
|
+
return [{ ...field, fields: normalizeSimplifiedArrays(field.fields) }];
|
|
902
879
|
}
|
|
903
880
|
if (isArrayField(field) && !isSimplifiedArrayField(field)) {
|
|
904
881
|
const normalizedItems = field.fields.map((item) => Array.isArray(item) ? normalizeSimplifiedArrays(item) : item);
|
|
@@ -1034,6 +1011,128 @@ function buildRemoveButton(config) {
|
|
|
1034
1011
|
};
|
|
1035
1012
|
}
|
|
1036
1013
|
|
|
1014
|
+
function collectFieldDefs(fields, out = []) {
|
|
1015
|
+
for (const field of fields ?? []) {
|
|
1016
|
+
out.push(field);
|
|
1017
|
+
const children = field.fields;
|
|
1018
|
+
if (Array.isArray(children)) {
|
|
1019
|
+
for (const child of children) {
|
|
1020
|
+
if (Array.isArray(child))
|
|
1021
|
+
collectFieldDefs(child, out);
|
|
1022
|
+
else
|
|
1023
|
+
collectFieldDefs([child], out);
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
const metadata = getNormalizedArrayMetadata(field);
|
|
1027
|
+
const template = metadata?.template;
|
|
1028
|
+
if (Array.isArray(template))
|
|
1029
|
+
collectFieldDefs(template, out);
|
|
1030
|
+
else if (template)
|
|
1031
|
+
collectFieldDefs([template], out);
|
|
1032
|
+
if (metadata?.autoRemoveButton)
|
|
1033
|
+
collectFieldDefs([metadata.autoRemoveButton], out);
|
|
1034
|
+
}
|
|
1035
|
+
return out;
|
|
1036
|
+
}
|
|
1037
|
+
function injectFormComponentPreloader() {
|
|
1038
|
+
const fieldRegistry = injectFieldRegistry();
|
|
1039
|
+
const wrapperRegistry = inject(WRAPPER_REGISTRY);
|
|
1040
|
+
const wrapperCache = inject(WRAPPER_COMPONENT_CACHE);
|
|
1041
|
+
const wrapperLoadCache = inject(WRAPPER_COMPONENT_LOAD_CACHE);
|
|
1042
|
+
const autoAssociations = inject(WRAPPER_AUTO_ASSOCIATIONS);
|
|
1043
|
+
const defaultWrappers = inject(DEFAULT_WRAPPERS, { optional: true });
|
|
1044
|
+
const preloadedConfigs = new WeakSet();
|
|
1045
|
+
const preloadedFieldRoots = new WeakSet();
|
|
1046
|
+
const preloadFields = (fields) => {
|
|
1047
|
+
const unseenFields = fields.filter((field) => {
|
|
1048
|
+
if (preloadedFieldRoots.has(field))
|
|
1049
|
+
return false;
|
|
1050
|
+
preloadedFieldRoots.add(field);
|
|
1051
|
+
return true;
|
|
1052
|
+
});
|
|
1053
|
+
if (unseenFields.length === 0)
|
|
1054
|
+
return;
|
|
1055
|
+
const normalizedFields = normalizeSimplifiedArrays(unseenFields);
|
|
1056
|
+
const defs = collectFieldDefs(normalizedFields);
|
|
1057
|
+
if (defs.length === 0)
|
|
1058
|
+
return;
|
|
1059
|
+
const fieldTypes = new Set();
|
|
1060
|
+
const wrapperTypes = new Set();
|
|
1061
|
+
for (const def of defs) {
|
|
1062
|
+
if (def.type)
|
|
1063
|
+
fieldTypes.add(def.type);
|
|
1064
|
+
for (const wrapper of resolveWrappers(def, defaultWrappers?.(), autoAssociations, wrapperRegistry)) {
|
|
1065
|
+
wrapperTypes.add(wrapper.type);
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
const started = [];
|
|
1069
|
+
for (const type of fieldTypes) {
|
|
1070
|
+
if (fieldRegistry.getLoadedComponent(type))
|
|
1071
|
+
continue;
|
|
1072
|
+
started.push(Promise.resolve(fieldRegistry.loadTypeComponent(type)).catch(() => undefined));
|
|
1073
|
+
}
|
|
1074
|
+
for (const type of wrapperTypes) {
|
|
1075
|
+
if (wrapperCache.has(type))
|
|
1076
|
+
continue;
|
|
1077
|
+
started.push(loadWrapperComponent(type, wrapperRegistry, wrapperCache, wrapperLoadCache).catch(() => undefined));
|
|
1078
|
+
}
|
|
1079
|
+
void Promise.allSettled(started);
|
|
1080
|
+
};
|
|
1081
|
+
return {
|
|
1082
|
+
preloadConfig: (config) => {
|
|
1083
|
+
if (preloadedConfigs.has(config))
|
|
1084
|
+
return;
|
|
1085
|
+
preloadedConfigs.add(config);
|
|
1086
|
+
const fields = config.fields;
|
|
1087
|
+
if (fields.length > 0 && fields.every(isPageField))
|
|
1088
|
+
return;
|
|
1089
|
+
preloadFields(fields);
|
|
1090
|
+
},
|
|
1091
|
+
preloadFields,
|
|
1092
|
+
};
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
class PageChangeEvent {
|
|
1096
|
+
currentPageIndex;
|
|
1097
|
+
totalPages;
|
|
1098
|
+
previousPageIndex;
|
|
1099
|
+
type = 'page-change';
|
|
1100
|
+
constructor(
|
|
1101
|
+
currentPageIndex,
|
|
1102
|
+
totalPages,
|
|
1103
|
+
previousPageIndex) {
|
|
1104
|
+
this.currentPageIndex = currentPageIndex;
|
|
1105
|
+
this.totalPages = totalPages;
|
|
1106
|
+
this.previousPageIndex = previousPageIndex;
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
class PagerStateEvent {
|
|
1111
|
+
state;
|
|
1112
|
+
type = 'pager-state';
|
|
1113
|
+
constructor(state) {
|
|
1114
|
+
this.state = state;
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
const PAGE_PRELOAD_WINDOW = new InjectionToken('PAGE_PRELOAD_WINDOW', {
|
|
1119
|
+
providedIn: 'root',
|
|
1120
|
+
factory: () => 1,
|
|
1121
|
+
});
|
|
1122
|
+
|
|
1123
|
+
function collectLeafFieldKeys(fields) {
|
|
1124
|
+
const keys = [];
|
|
1125
|
+
for (const field of fields) {
|
|
1126
|
+
if (isRowField(field) || isGenericContainerField(field)) {
|
|
1127
|
+
keys.push(...collectLeafFieldKeys(field.fields));
|
|
1128
|
+
}
|
|
1129
|
+
else if (field.key) {
|
|
1130
|
+
keys.push(field.key);
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
return keys;
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1037
1136
|
function createSlot() {
|
|
1038
1137
|
let counter = 0;
|
|
1039
1138
|
const templates = new Map();
|
|
@@ -1129,498 +1228,82 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
|
|
|
1129
1228
|
type: Injectable
|
|
1130
1229
|
}] });
|
|
1131
1230
|
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
const
|
|
1135
|
-
|
|
1136
|
-
const
|
|
1137
|
-
const
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
const source$ = toObservable(source, { injector }).pipe(debounceTime(ms), distinctUntilChanged());
|
|
1143
|
-
return toSignal(source$, { injector });
|
|
1144
|
-
}
|
|
1145
|
-
|
|
1146
|
-
function getConfigTrigger(config) {
|
|
1147
|
-
if (!isStateLogicConfig(config)) {
|
|
1148
|
-
return 'onChange';
|
|
1149
|
-
}
|
|
1150
|
-
return config.trigger ?? 'onChange';
|
|
1151
|
-
}
|
|
1152
|
-
function getConfigDebounceMs(config) {
|
|
1153
|
-
if (!isStateLogicConfig(config)) {
|
|
1154
|
-
return undefined;
|
|
1155
|
-
}
|
|
1156
|
-
if (config.trigger === 'debounced') {
|
|
1157
|
-
return config.debounceMs;
|
|
1158
|
-
}
|
|
1159
|
-
return undefined;
|
|
1160
|
-
}
|
|
1161
|
-
function applyLogic(config, fieldPath) {
|
|
1162
|
-
if (isDerivationLogicConfig(config))
|
|
1163
|
-
return;
|
|
1164
|
-
if (!isStateLogicConfig(config)) {
|
|
1165
|
-
if (DEV_MODE) {
|
|
1166
|
-
console.warn(`[Dynamic Forms] Unrecognized logic config type '${config.type}' in applyLogic. ` +
|
|
1167
|
-
'This config will be ignored. If this is a new logic type, ensure it is handled explicitly.');
|
|
1168
|
-
}
|
|
1169
|
-
return;
|
|
1170
|
-
}
|
|
1171
|
-
const path = fieldPath;
|
|
1172
|
-
const trigger = getConfigTrigger(config);
|
|
1173
|
-
const debounceMs = getConfigDebounceMs(config) ?? DEFAULT_DEBOUNCE_MS;
|
|
1174
|
-
if (typeof config.condition === 'boolean') {
|
|
1175
|
-
applyLogicFn(config.type, path, () => config.condition);
|
|
1176
|
-
return;
|
|
1177
|
-
}
|
|
1178
|
-
if (isFormStateCondition(config.condition)) {
|
|
1179
|
-
if (config.condition === 'formInvalid' || config.condition === 'pageInvalid') {
|
|
1180
|
-
const reason = config.condition === 'formInvalid'
|
|
1181
|
-
? `it would create a validation cycle against the form's own validity`
|
|
1182
|
-
: `a value field has no page context for it to resolve against`;
|
|
1183
|
-
throw new DynamicFormError(`The '${config.condition}' form-state condition is not supported on value-field logic because ${reason}. ` +
|
|
1184
|
-
`Use 'formSubmitting', or apply this condition to a button or container instead.`);
|
|
1185
|
-
}
|
|
1186
|
-
applyLogicFn(config.type, path, createFormStateLogicFunction(config.condition));
|
|
1187
|
-
return;
|
|
1188
|
-
}
|
|
1189
|
-
const logicFn = trigger === 'debounced'
|
|
1190
|
-
? createDebouncedLogicFunction(config.condition, debounceMs)
|
|
1191
|
-
: createLogicFunction(config.condition);
|
|
1192
|
-
applyLogicFn(config.type, path, logicFn);
|
|
1193
|
-
}
|
|
1194
|
-
function applyLogicFn(type, path, logicFn) {
|
|
1195
|
-
switch (type) {
|
|
1196
|
-
case 'hidden':
|
|
1197
|
-
hidden(path, logicFn);
|
|
1198
|
-
break;
|
|
1199
|
-
case 'readonly':
|
|
1200
|
-
readonly(path, logicFn);
|
|
1201
|
-
break;
|
|
1202
|
-
case 'disabled':
|
|
1203
|
-
disabled(path, logicFn);
|
|
1204
|
-
break;
|
|
1205
|
-
case 'required':
|
|
1206
|
-
required(path, { when: logicFn });
|
|
1207
|
-
break;
|
|
1208
|
-
default: {
|
|
1209
|
-
const _exhaustive = type;
|
|
1210
|
-
throw new DynamicFormError(`Unhandled state logic type: ${_exhaustive}`);
|
|
1211
|
-
}
|
|
1212
|
-
}
|
|
1213
|
-
}
|
|
1214
|
-
function applyMultipleLogic(configs, fieldPath) {
|
|
1215
|
-
configs.forEach((config) => applyLogic(config, fieldPath));
|
|
1216
|
-
}
|
|
1217
|
-
|
|
1218
|
-
function createTypePredicateFunction(predicate, logger) {
|
|
1219
|
-
return (value) => {
|
|
1220
|
-
try {
|
|
1221
|
-
const result = ExpressionParser.evaluate(predicate, { value });
|
|
1222
|
-
return Boolean(result);
|
|
1223
|
-
}
|
|
1224
|
-
catch (error) {
|
|
1225
|
-
logger.error('Error evaluating type predicate:', predicate, error);
|
|
1226
|
-
return false;
|
|
1227
|
-
}
|
|
1228
|
-
};
|
|
1229
|
-
}
|
|
1230
|
-
|
|
1231
|
-
function applySchema(config, fieldPath) {
|
|
1232
|
-
const logger = inject(DynamicFormLogger);
|
|
1233
|
-
const schemaRegistry = inject(SchemaRegistryService);
|
|
1234
|
-
const schema = schemaRegistry.resolveSchema(config.schema);
|
|
1235
|
-
if (!schema) {
|
|
1236
|
-
const availableSchemas = Array.from(schemaRegistry.getAllSchemas().keys()).join(', ') || '<none>';
|
|
1237
|
-
logger.error(`Schema not found: '${config.schema}'. ` +
|
|
1238
|
-
`Available schemas: ${availableSchemas}. ` +
|
|
1239
|
-
`Ensure the schema is registered in your schema registry.`);
|
|
1240
|
-
return;
|
|
1241
|
-
}
|
|
1242
|
-
const schemaFn = createSchemaFunction(schema, logger);
|
|
1243
|
-
const path = fieldPath;
|
|
1244
|
-
switch (config.type) {
|
|
1245
|
-
case 'apply':
|
|
1246
|
-
apply(path, schemaFn);
|
|
1247
|
-
break;
|
|
1248
|
-
case 'applyWhen':
|
|
1249
|
-
if (config.condition) {
|
|
1250
|
-
const conditionFn = createLogicFunction(config.condition);
|
|
1251
|
-
applyWhen(path, conditionFn, schemaFn);
|
|
1252
|
-
}
|
|
1253
|
-
break;
|
|
1254
|
-
case 'applyWhenValue':
|
|
1255
|
-
if (config.typePredicate) {
|
|
1256
|
-
const predicate = createTypePredicateFunction(config.typePredicate, logger);
|
|
1257
|
-
applyWhenValue(path, predicate, schemaFn);
|
|
1258
|
-
}
|
|
1259
|
-
break;
|
|
1260
|
-
case 'applyEach':
|
|
1261
|
-
applyEach(path, schemaFn);
|
|
1262
|
-
break;
|
|
1263
|
-
}
|
|
1264
|
-
}
|
|
1265
|
-
function createSchemaFunction(schema, logger) {
|
|
1266
|
-
return (path) => {
|
|
1267
|
-
schema.validators?.forEach((validatorConfig) => {
|
|
1268
|
-
applyValidator(validatorConfig, path);
|
|
1269
|
-
});
|
|
1270
|
-
schema.logic?.forEach((logicConfig) => {
|
|
1271
|
-
applyLogic(logicConfig, path);
|
|
1272
|
-
});
|
|
1273
|
-
schema.subSchemas?.forEach((subSchemaConfig) => {
|
|
1274
|
-
applySchema(subSchemaConfig, path);
|
|
1275
|
-
});
|
|
1276
|
-
};
|
|
1277
|
-
}
|
|
1278
|
-
|
|
1279
|
-
function combineAncestorHiddenLogics(logics) {
|
|
1280
|
-
if (logics.length === 0)
|
|
1281
|
-
return undefined;
|
|
1282
|
-
if (logics.length === 1)
|
|
1283
|
-
return logics[0];
|
|
1284
|
-
return (ctx) => logics.some((fn) => fn(ctx));
|
|
1285
|
-
}
|
|
1286
|
-
function resolveFieldOwnContext(fieldDef, parentContext) {
|
|
1287
|
-
if (fieldDef.validateWhenHidden === undefined)
|
|
1288
|
-
return parentContext;
|
|
1289
|
-
if (fieldDef.validateWhenHidden === parentContext.validateWhenHidden)
|
|
1290
|
-
return parentContext;
|
|
1291
|
-
return {
|
|
1292
|
-
...parentContext,
|
|
1293
|
-
validateWhenHidden: fieldDef.validateWhenHidden,
|
|
1294
|
-
};
|
|
1295
|
-
}
|
|
1296
|
-
function resolveDescendantContext(fieldDef, ownContext) {
|
|
1297
|
-
let ancestorAlwaysHidden = ownContext.ancestorAlwaysHidden;
|
|
1298
|
-
let ancestorHiddenLogics = ownContext.ancestorHiddenLogics;
|
|
1299
|
-
let mutated = false;
|
|
1300
|
-
let ancestorRequired = ownContext.ancestorRequired;
|
|
1301
|
-
if (fieldDef.required !== undefined && fieldDef.required !== ancestorRequired) {
|
|
1302
|
-
ancestorRequired = fieldDef.required;
|
|
1303
|
-
mutated = true;
|
|
1304
|
-
}
|
|
1305
|
-
if (fieldDef.hidden === true && !ancestorAlwaysHidden) {
|
|
1306
|
-
ancestorAlwaysHidden = true;
|
|
1307
|
-
mutated = true;
|
|
1308
|
-
}
|
|
1309
|
-
if (fieldDef.logic) {
|
|
1310
|
-
for (const logic of fieldDef.logic) {
|
|
1311
|
-
if (!isStateLogicConfig(logic) || logic.type !== 'hidden')
|
|
1312
|
-
continue;
|
|
1313
|
-
const condition = logic.condition;
|
|
1314
|
-
if (condition === true) {
|
|
1315
|
-
if (!ancestorAlwaysHidden) {
|
|
1316
|
-
ancestorAlwaysHidden = true;
|
|
1317
|
-
mutated = true;
|
|
1318
|
-
}
|
|
1319
|
-
continue;
|
|
1320
|
-
}
|
|
1321
|
-
if (condition === false)
|
|
1322
|
-
continue;
|
|
1323
|
-
if (typeof condition === 'string')
|
|
1324
|
-
continue;
|
|
1325
|
-
const fn = createLogicFunction(condition);
|
|
1326
|
-
ancestorHiddenLogics = ancestorHiddenLogics.length === 0 ? [fn] : [...ancestorHiddenLogics, fn];
|
|
1327
|
-
mutated = true;
|
|
1328
|
-
}
|
|
1329
|
-
}
|
|
1330
|
-
if (!mutated)
|
|
1331
|
-
return ownContext;
|
|
1332
|
-
return {
|
|
1333
|
-
validateWhenHidden: ownContext.validateWhenHidden,
|
|
1334
|
-
ancestorAlwaysHidden,
|
|
1335
|
-
ancestorHiddenLogics,
|
|
1336
|
-
ancestorRequired,
|
|
1337
|
-
};
|
|
1338
|
-
}
|
|
1339
|
-
|
|
1340
|
-
const DEFAULT_FIELD_TREE_MAPPING_CONTEXT = {
|
|
1341
|
-
validateWhenHidden: false,
|
|
1342
|
-
ancestorAlwaysHidden: false,
|
|
1343
|
-
ancestorHiddenLogics: [],
|
|
1344
|
-
};
|
|
1345
|
-
function applyStringValidators(path, config) {
|
|
1346
|
-
if (config.email) {
|
|
1347
|
-
email(path);
|
|
1348
|
-
}
|
|
1349
|
-
if (config.minLength !== undefined) {
|
|
1350
|
-
minLength(path, config.minLength);
|
|
1351
|
-
}
|
|
1352
|
-
if (config.maxLength !== undefined) {
|
|
1353
|
-
maxLength(path, config.maxLength);
|
|
1354
|
-
}
|
|
1355
|
-
if (config.pattern) {
|
|
1356
|
-
let regex;
|
|
1357
|
-
if (typeof config.pattern === 'string') {
|
|
1358
|
-
try {
|
|
1359
|
-
regex = new RegExp(config.pattern);
|
|
1360
|
-
}
|
|
1361
|
-
catch (e) {
|
|
1362
|
-
throw new DynamicFormError(`Invalid regex pattern: '${config.pattern}' — ${e instanceof Error ? e.message : String(e)}`);
|
|
1363
|
-
}
|
|
1364
|
-
}
|
|
1365
|
-
else {
|
|
1366
|
-
regex = config.pattern;
|
|
1367
|
-
}
|
|
1368
|
-
pattern(path, regex);
|
|
1369
|
-
}
|
|
1370
|
-
}
|
|
1371
|
-
function applyNumberValidators(path, config) {
|
|
1372
|
-
if (config.min !== undefined) {
|
|
1373
|
-
min(path, config.min);
|
|
1374
|
-
}
|
|
1375
|
-
if (config.max !== undefined) {
|
|
1376
|
-
max(path, config.max);
|
|
1377
|
-
}
|
|
1378
|
-
}
|
|
1379
|
-
function getStringValidationConfig(fieldDef) {
|
|
1380
|
-
if (!fieldDef.email && fieldDef.minLength === undefined && fieldDef.maxLength === undefined && !fieldDef.pattern) {
|
|
1381
|
-
return undefined;
|
|
1231
|
+
function inheritContainerExclusion(container, child) {
|
|
1232
|
+
const inheritedHidden = container.hidden === true || child.inheritedExclusionState?.hidden === true;
|
|
1233
|
+
const inheritedDisabled = container.disabled === true || child.inheritedExclusionState?.disabled === true;
|
|
1234
|
+
const inheritedReadonly = container.readonly === true || child.inheritedExclusionState?.readonly === true;
|
|
1235
|
+
const hasInheritedState = inheritedHidden || inheritedDisabled || inheritedReadonly;
|
|
1236
|
+
const containerExcludeConfig = container.excludeValueIfHidden !== undefined ||
|
|
1237
|
+
container.excludeValueIfDisabled !== undefined ||
|
|
1238
|
+
container.excludeValueIfReadonly !== undefined;
|
|
1239
|
+
if (!hasInheritedState && !containerExcludeConfig) {
|
|
1240
|
+
return child;
|
|
1382
1241
|
}
|
|
1383
1242
|
return {
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
const minVal = fieldDef.min ?? fieldDef.minValue;
|
|
1392
|
-
const maxVal = fieldDef.max ?? fieldDef.maxValue;
|
|
1393
|
-
if (minVal === undefined && maxVal === undefined) {
|
|
1394
|
-
return undefined;
|
|
1395
|
-
}
|
|
1396
|
-
return { min: minVal, max: maxVal };
|
|
1397
|
-
}
|
|
1398
|
-
function mapFieldToForm(fieldDef, fieldPath, inheritedContext = DEFAULT_FIELD_TREE_MAPPING_CONTEXT) {
|
|
1399
|
-
const ownContext = resolveFieldOwnContext(fieldDef, inheritedContext);
|
|
1400
|
-
if (isPageField(fieldDef) || isRowField(fieldDef) || isGenericContainerField(fieldDef)) {
|
|
1401
|
-
const descendantContext = resolveDescendantContext(fieldDef, ownContext);
|
|
1402
|
-
mapContainerChildren(fieldDef.fields, fieldPath, descendantContext);
|
|
1403
|
-
return;
|
|
1404
|
-
}
|
|
1405
|
-
if (isGroupField(fieldDef)) {
|
|
1406
|
-
const descendantContext = resolveDescendantContext(fieldDef, ownContext);
|
|
1407
|
-
applyContainerValidators(fieldDef, fieldPath, descendantContext);
|
|
1408
|
-
mapContainerChildren(fieldDef.fields, fieldPath, descendantContext);
|
|
1409
|
-
return;
|
|
1410
|
-
}
|
|
1411
|
-
if (isArrayField(fieldDef)) {
|
|
1412
|
-
const descendantContext = resolveDescendantContext(fieldDef, ownContext);
|
|
1413
|
-
mapArrayFieldToForm(fieldDef, fieldPath, descendantContext);
|
|
1414
|
-
return;
|
|
1415
|
-
}
|
|
1416
|
-
mapLeafField(fieldDef, fieldPath, ownContext);
|
|
1417
|
-
}
|
|
1418
|
-
function mapContainerChildren(fields, parentPath, inheritedContext) {
|
|
1419
|
-
if (!fields)
|
|
1420
|
-
return;
|
|
1421
|
-
const pathRecord = parentPath;
|
|
1422
|
-
for (const field of fields) {
|
|
1423
|
-
if (isPageField(field) || isRowField(field) || isGenericContainerField(field)) {
|
|
1424
|
-
const layoutOwn = resolveFieldOwnContext(field, inheritedContext);
|
|
1425
|
-
const layoutDescendant = resolveDescendantContext(field, layoutOwn);
|
|
1426
|
-
mapContainerChildren(field.fields, parentPath, layoutDescendant);
|
|
1427
|
-
continue;
|
|
1428
|
-
}
|
|
1429
|
-
if (!field.key)
|
|
1430
|
-
continue;
|
|
1431
|
-
const childPath = pathRecord[field.key];
|
|
1432
|
-
if (childPath) {
|
|
1433
|
-
mapFieldToForm(field, childPath, inheritedContext);
|
|
1434
|
-
}
|
|
1435
|
-
}
|
|
1436
|
-
}
|
|
1437
|
-
function isValidationStateLogic(config) {
|
|
1438
|
-
return isStateLogicConfig(config) && config.type === 'required';
|
|
1439
|
-
}
|
|
1440
|
-
function mapLeafField(fieldDef, fieldPath, context) {
|
|
1441
|
-
const validationField = fieldDef;
|
|
1442
|
-
const path = fieldPath;
|
|
1443
|
-
applyFieldState(fieldDef, path);
|
|
1444
|
-
if (validationField.logic) {
|
|
1445
|
-
for (const config of validationField.logic) {
|
|
1446
|
-
if (!isValidationStateLogic(config)) {
|
|
1447
|
-
applyLogic(config, fieldPath);
|
|
1448
|
-
}
|
|
1449
|
-
}
|
|
1450
|
-
}
|
|
1451
|
-
if (validationField.schemas) {
|
|
1452
|
-
for (const config of validationField.schemas) {
|
|
1453
|
-
applySchema(config, fieldPath);
|
|
1454
|
-
}
|
|
1455
|
-
}
|
|
1456
|
-
const applyValidation = (validationPath) => {
|
|
1457
|
-
applySimpleValidationRules(validationField, validationPath, context);
|
|
1458
|
-
if (validationField.validators) {
|
|
1459
|
-
for (const config of validationField.validators) {
|
|
1460
|
-
applyValidator(config, validationPath);
|
|
1461
|
-
}
|
|
1462
|
-
}
|
|
1463
|
-
if (validationField.logic) {
|
|
1464
|
-
for (const config of validationField.logic) {
|
|
1465
|
-
if (isValidationStateLogic(config)) {
|
|
1466
|
-
applyLogic(config, validationPath);
|
|
1467
|
-
}
|
|
1468
|
-
}
|
|
1469
|
-
}
|
|
1243
|
+
...child,
|
|
1244
|
+
...(hasInheritedState
|
|
1245
|
+
? { inheritedExclusionState: { hidden: inheritedHidden, disabled: inheritedDisabled, readonly: inheritedReadonly } }
|
|
1246
|
+
: {}),
|
|
1247
|
+
excludeValueIfHidden: child.excludeValueIfHidden ?? container.excludeValueIfHidden,
|
|
1248
|
+
excludeValueIfDisabled: child.excludeValueIfDisabled ?? container.excludeValueIfDisabled,
|
|
1249
|
+
excludeValueIfReadonly: child.excludeValueIfReadonly ?? container.excludeValueIfReadonly,
|
|
1470
1250
|
};
|
|
1471
|
-
applyHiddenGatedValidation(path, context, applyValidation);
|
|
1472
|
-
}
|
|
1473
|
-
function applyHiddenGatedValidation(path, context, apply) {
|
|
1474
|
-
if (context.validateWhenHidden) {
|
|
1475
|
-
apply(path);
|
|
1476
|
-
return;
|
|
1477
|
-
}
|
|
1478
|
-
if (context.ancestorAlwaysHidden) {
|
|
1479
|
-
return;
|
|
1480
|
-
}
|
|
1481
|
-
const ancestorHiddenLogic = combineAncestorHiddenLogics(context.ancestorHiddenLogics);
|
|
1482
|
-
applyWhen(path, (ctx) => {
|
|
1483
|
-
if (ctx.state.hidden())
|
|
1484
|
-
return false;
|
|
1485
|
-
if (ancestorHiddenLogic && ancestorHiddenLogic(ctx))
|
|
1486
|
-
return false;
|
|
1487
|
-
return true;
|
|
1488
|
-
}, schema((subPath) => apply(subPath)));
|
|
1489
|
-
}
|
|
1490
|
-
function applyContainerValidators(fieldDef, fieldPath, context) {
|
|
1491
|
-
const validators = fieldDef.validators;
|
|
1492
|
-
if (!validators || validators.length === 0)
|
|
1493
|
-
return;
|
|
1494
|
-
applyHiddenGatedValidation(fieldPath, context, (validationPath) => {
|
|
1495
|
-
for (const config of validators) {
|
|
1496
|
-
applyValidator(config, validationPath, 'tree');
|
|
1497
|
-
}
|
|
1498
|
-
});
|
|
1499
|
-
}
|
|
1500
|
-
function applySimpleValidationRules(fieldDef, path, context) {
|
|
1501
|
-
const declaresRequiredLogic = fieldDef.logic?.some(isValidationStateLogic) ?? false;
|
|
1502
|
-
if (fieldDef.required ?? (declaresRequiredLogic ? false : context.ancestorRequired)) {
|
|
1503
|
-
required(path);
|
|
1504
|
-
}
|
|
1505
|
-
const stringConfig = getStringValidationConfig(fieldDef);
|
|
1506
|
-
if (stringConfig) {
|
|
1507
|
-
applyStringValidators(path, stringConfig);
|
|
1508
|
-
}
|
|
1509
|
-
const numberConfig = getNumberValidationConfig(fieldDef);
|
|
1510
|
-
if (numberConfig) {
|
|
1511
|
-
applyNumberValidators(path, numberConfig);
|
|
1512
|
-
}
|
|
1513
|
-
}
|
|
1514
|
-
function applyFieldState(fieldDef, path) {
|
|
1515
|
-
if (fieldDef.disabled) {
|
|
1516
|
-
disabled(path);
|
|
1517
|
-
}
|
|
1518
|
-
if (fieldDef.readonly) {
|
|
1519
|
-
readonly(path);
|
|
1520
|
-
}
|
|
1521
|
-
if (fieldDef.hidden) {
|
|
1522
|
-
hidden(path, () => true);
|
|
1523
|
-
}
|
|
1524
1251
|
}
|
|
1525
|
-
function
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
itemDefinitions = [
|
|
1541
|
-
Array.isArray(metadataTemplate) ? [...metadataTemplate] : metadataTemplate,
|
|
1542
|
-
];
|
|
1543
|
-
}
|
|
1544
|
-
}
|
|
1545
|
-
if (!itemDefinitions || itemDefinitions.length === 0) {
|
|
1546
|
-
const emptyItemSchema = schema(() => {
|
|
1547
|
-
});
|
|
1548
|
-
applyEach(fieldPath, emptyItemSchema);
|
|
1549
|
-
return;
|
|
1550
|
-
}
|
|
1551
|
-
let hasPrimitiveItems = false;
|
|
1552
|
-
const allObjectFields = [];
|
|
1553
|
-
for (const itemDef of itemDefinitions) {
|
|
1554
|
-
if (Array.isArray(itemDef)) {
|
|
1555
|
-
collectFieldsFromObjectItem(itemDef, allObjectFields);
|
|
1252
|
+
function flattenFields(fields, registry, options = {}) {
|
|
1253
|
+
const result = [];
|
|
1254
|
+
let autoKeyCounter = 0;
|
|
1255
|
+
for (const field of fields) {
|
|
1256
|
+
const valueHandling = getFieldValueHandling(field.type, registry);
|
|
1257
|
+
if (options.preserveRows && (isRowField(field) || isGenericContainerField(field))) {
|
|
1258
|
+
if (field.fields) {
|
|
1259
|
+
const flattenedChildren = flattenFields(normalizeFieldsArray(field.fields), registry, options);
|
|
1260
|
+
const autoPrefix = isRowField(field) ? 'auto_row' : 'auto_container';
|
|
1261
|
+
result.push({
|
|
1262
|
+
...field,
|
|
1263
|
+
fields: flattenedChildren,
|
|
1264
|
+
key: field.key || `${autoPrefix}_${autoKeyCounter++}`,
|
|
1265
|
+
});
|
|
1266
|
+
}
|
|
1556
1267
|
}
|
|
1557
|
-
else if (
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1268
|
+
else if (valueHandling === 'flatten' && 'fields' in field) {
|
|
1269
|
+
if (field.fields) {
|
|
1270
|
+
const fields = field.fields;
|
|
1271
|
+
const flattenedChildren = flattenFields(normalizeFieldsArray(fields), registry, options);
|
|
1272
|
+
result.push(...flattenedChildren.map((child) => inheritContainerExclusion(field, child)));
|
|
1273
|
+
}
|
|
1561
1274
|
}
|
|
1562
|
-
else {
|
|
1563
|
-
|
|
1275
|
+
else if (isGroupField(field)) {
|
|
1276
|
+
const childFieldsArray = Object.values(field.fields);
|
|
1277
|
+
const flattenedChildren = flattenFields(childFieldsArray, registry, options);
|
|
1278
|
+
result.push({
|
|
1279
|
+
...field,
|
|
1280
|
+
fields: flattenedChildren,
|
|
1281
|
+
key: field.key || `auto_group_${autoKeyCounter++}`,
|
|
1282
|
+
});
|
|
1564
1283
|
}
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
return;
|
|
1571
|
-
}
|
|
1572
|
-
const itemSchema = schema((itemPath) => {
|
|
1573
|
-
const pathRecord = itemPath;
|
|
1574
|
-
for (const templateField of allObjectFields) {
|
|
1575
|
-
if (isRowField(templateField) || isPageField(templateField) || isGenericContainerField(templateField)) {
|
|
1576
|
-
const layoutOwn = resolveFieldOwnContext(templateField, context);
|
|
1577
|
-
const layoutDescendant = resolveDescendantContext(templateField, layoutOwn);
|
|
1578
|
-
mapContainerChildren(templateField.fields, itemPath, layoutDescendant);
|
|
1579
|
-
}
|
|
1580
|
-
else if (isGroupField(templateField)) {
|
|
1581
|
-
const groupKey = templateField.key;
|
|
1582
|
-
if (groupKey) {
|
|
1583
|
-
const groupPath = pathRecord[groupKey];
|
|
1584
|
-
if (groupPath) {
|
|
1585
|
-
mapFieldToForm(templateField, groupPath, context);
|
|
1586
|
-
}
|
|
1587
|
-
}
|
|
1588
|
-
else {
|
|
1589
|
-
const groupOwn = resolveFieldOwnContext(templateField, context);
|
|
1590
|
-
mapContainerChildren(templateField.fields, itemPath, resolveDescendantContext(templateField, groupOwn));
|
|
1591
|
-
}
|
|
1592
|
-
}
|
|
1593
|
-
else {
|
|
1594
|
-
const fieldKey = templateField.key;
|
|
1595
|
-
if (fieldKey) {
|
|
1596
|
-
const fieldPathForKey = pathRecord[fieldKey];
|
|
1597
|
-
if (fieldPathForKey) {
|
|
1598
|
-
mapFieldToForm(templateField, fieldPathForKey, context);
|
|
1599
|
-
}
|
|
1284
|
+
else if (isArrayField(field)) {
|
|
1285
|
+
const flattenedItemTemplates = field.fields.map((itemDef) => {
|
|
1286
|
+
if (!Array.isArray(itemDef)) {
|
|
1287
|
+
const flattened = flattenFields([itemDef], registry, options);
|
|
1288
|
+
return flattened[0];
|
|
1600
1289
|
}
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
for (const field of itemFields) {
|
|
1609
|
-
if (isRowField(field) || isPageField(field) || isGenericContainerField(field)) {
|
|
1610
|
-
const containerKey = `__container_${field.type}_${JSON.stringify(field.fields?.map((f) => f.key))}`;
|
|
1611
|
-
if (!seenKeys.has(containerKey)) {
|
|
1612
|
-
seenKeys.add(containerKey);
|
|
1613
|
-
allFields.push(field);
|
|
1614
|
-
}
|
|
1290
|
+
return flattenFields([...itemDef], registry, options);
|
|
1291
|
+
});
|
|
1292
|
+
result.push({
|
|
1293
|
+
...field,
|
|
1294
|
+
fields: flattenedItemTemplates,
|
|
1295
|
+
key: field.key || `auto_array_${autoKeyCounter++}`,
|
|
1296
|
+
});
|
|
1615
1297
|
}
|
|
1616
1298
|
else {
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
}
|
|
1299
|
+
const key = field.key || `auto_field_${autoKeyCounter++}`;
|
|
1300
|
+
result.push({
|
|
1301
|
+
...field,
|
|
1302
|
+
key,
|
|
1303
|
+
});
|
|
1622
1304
|
}
|
|
1623
1305
|
}
|
|
1306
|
+
return result;
|
|
1624
1307
|
}
|
|
1625
1308
|
|
|
1626
1309
|
function getFieldDefaultValue(field, registry) {
|
|
@@ -1726,160 +1409,6 @@ function getFieldDefaultValue(field, registry) {
|
|
|
1726
1409
|
return '';
|
|
1727
1410
|
}
|
|
1728
1411
|
|
|
1729
|
-
function applyFormLevelSchema(path, formLevelSchema) {
|
|
1730
|
-
if (isStandardSchemaMarker(formLevelSchema)) {
|
|
1731
|
-
validateStandardSchema(path, formLevelSchema.schema);
|
|
1732
|
-
}
|
|
1733
|
-
else if (typeof formLevelSchema === 'function') {
|
|
1734
|
-
formLevelSchema(path);
|
|
1735
|
-
}
|
|
1736
|
-
}
|
|
1737
|
-
function createFormLevelSchema(formLevelSchema) {
|
|
1738
|
-
return schema((path) => {
|
|
1739
|
-
applyFormLevelSchema(path, formLevelSchema);
|
|
1740
|
-
});
|
|
1741
|
-
}
|
|
1742
|
-
|
|
1743
|
-
const VALIDATION_EXECUTION_DEFAULTS = new InjectionToken('VALIDATION_EXECUTION_DEFAULTS', {
|
|
1744
|
-
providedIn: 'root',
|
|
1745
|
-
factory: () => ({
|
|
1746
|
-
validateWhenHidden: false,
|
|
1747
|
-
}),
|
|
1748
|
-
});
|
|
1749
|
-
|
|
1750
|
-
function createSchemaFromFields(fields, registry, options = {}) {
|
|
1751
|
-
const validationExecutionDefaults = inject(VALIDATION_EXECUTION_DEFAULTS);
|
|
1752
|
-
const { formLevelSchema } = options;
|
|
1753
|
-
const rootContext = {
|
|
1754
|
-
validateWhenHidden: options.validateWhenHidden ?? validationExecutionDefaults.validateWhenHidden,
|
|
1755
|
-
ancestorAlwaysHidden: false,
|
|
1756
|
-
ancestorHiddenLogics: [],
|
|
1757
|
-
};
|
|
1758
|
-
return schema((path) => {
|
|
1759
|
-
for (const fieldDef of fields) {
|
|
1760
|
-
const valueHandling = getFieldValueHandling(fieldDef.type, registry);
|
|
1761
|
-
if (valueHandling === 'exclude') {
|
|
1762
|
-
continue;
|
|
1763
|
-
}
|
|
1764
|
-
if (valueHandling === 'flatten' && hasChildFields(fieldDef)) {
|
|
1765
|
-
const flattenedOwn = resolveFieldOwnContext(fieldDef, rootContext);
|
|
1766
|
-
const flattenedContext = resolveDescendantContext(fieldDef, flattenedOwn);
|
|
1767
|
-
for (const childField of normalizeFieldsArray(fieldDef.fields)) {
|
|
1768
|
-
if (!childField.key)
|
|
1769
|
-
continue;
|
|
1770
|
-
const childPath = path[childField.key];
|
|
1771
|
-
if (childPath) {
|
|
1772
|
-
mapFieldToForm(childField, childPath, flattenedContext);
|
|
1773
|
-
}
|
|
1774
|
-
}
|
|
1775
|
-
continue;
|
|
1776
|
-
}
|
|
1777
|
-
const fieldPath = path[fieldDef.key];
|
|
1778
|
-
if (!fieldPath) {
|
|
1779
|
-
continue;
|
|
1780
|
-
}
|
|
1781
|
-
mapFieldToForm(fieldDef, fieldPath, rootContext);
|
|
1782
|
-
}
|
|
1783
|
-
if (formLevelSchema) {
|
|
1784
|
-
applyFormLevelSchema(path, formLevelSchema);
|
|
1785
|
-
}
|
|
1786
|
-
});
|
|
1787
|
-
}
|
|
1788
|
-
function fieldsToDefaultValues(fields, registry) {
|
|
1789
|
-
const defaultValues = {};
|
|
1790
|
-
for (const field of fields) {
|
|
1791
|
-
if (!field.key)
|
|
1792
|
-
continue;
|
|
1793
|
-
const valueHandling = getFieldValueHandling(field.type, registry);
|
|
1794
|
-
if (valueHandling === 'flatten') {
|
|
1795
|
-
continue;
|
|
1796
|
-
}
|
|
1797
|
-
const value = getFieldDefaultValue(field, registry);
|
|
1798
|
-
if (value !== undefined) {
|
|
1799
|
-
defaultValues[field.key] = value;
|
|
1800
|
-
}
|
|
1801
|
-
}
|
|
1802
|
-
return defaultValues;
|
|
1803
|
-
}
|
|
1804
|
-
|
|
1805
|
-
function inheritContainerExclusion(container, child) {
|
|
1806
|
-
const inheritedHidden = container.hidden === true || child.inheritedExclusionState?.hidden === true;
|
|
1807
|
-
const inheritedDisabled = container.disabled === true || child.inheritedExclusionState?.disabled === true;
|
|
1808
|
-
const inheritedReadonly = container.readonly === true || child.inheritedExclusionState?.readonly === true;
|
|
1809
|
-
const hasInheritedState = inheritedHidden || inheritedDisabled || inheritedReadonly;
|
|
1810
|
-
const containerExcludeConfig = container.excludeValueIfHidden !== undefined ||
|
|
1811
|
-
container.excludeValueIfDisabled !== undefined ||
|
|
1812
|
-
container.excludeValueIfReadonly !== undefined;
|
|
1813
|
-
if (!hasInheritedState && !containerExcludeConfig) {
|
|
1814
|
-
return child;
|
|
1815
|
-
}
|
|
1816
|
-
return {
|
|
1817
|
-
...child,
|
|
1818
|
-
...(hasInheritedState
|
|
1819
|
-
? { inheritedExclusionState: { hidden: inheritedHidden, disabled: inheritedDisabled, readonly: inheritedReadonly } }
|
|
1820
|
-
: {}),
|
|
1821
|
-
excludeValueIfHidden: child.excludeValueIfHidden ?? container.excludeValueIfHidden,
|
|
1822
|
-
excludeValueIfDisabled: child.excludeValueIfDisabled ?? container.excludeValueIfDisabled,
|
|
1823
|
-
excludeValueIfReadonly: child.excludeValueIfReadonly ?? container.excludeValueIfReadonly,
|
|
1824
|
-
};
|
|
1825
|
-
}
|
|
1826
|
-
function flattenFields(fields, registry, options = {}) {
|
|
1827
|
-
const result = [];
|
|
1828
|
-
let autoKeyCounter = 0;
|
|
1829
|
-
for (const field of fields) {
|
|
1830
|
-
const valueHandling = getFieldValueHandling(field.type, registry);
|
|
1831
|
-
if (options.preserveRows && (isRowField(field) || isGenericContainerField(field))) {
|
|
1832
|
-
if (field.fields) {
|
|
1833
|
-
const flattenedChildren = flattenFields(normalizeFieldsArray(field.fields), registry, options);
|
|
1834
|
-
const autoPrefix = isRowField(field) ? 'auto_row' : 'auto_container';
|
|
1835
|
-
result.push({
|
|
1836
|
-
...field,
|
|
1837
|
-
fields: flattenedChildren,
|
|
1838
|
-
key: field.key || `${autoPrefix}_${autoKeyCounter++}`,
|
|
1839
|
-
});
|
|
1840
|
-
}
|
|
1841
|
-
}
|
|
1842
|
-
else if (valueHandling === 'flatten' && 'fields' in field) {
|
|
1843
|
-
if (field.fields) {
|
|
1844
|
-
const fields = field.fields;
|
|
1845
|
-
const flattenedChildren = flattenFields(normalizeFieldsArray(fields), registry, options);
|
|
1846
|
-
result.push(...flattenedChildren.map((child) => inheritContainerExclusion(field, child)));
|
|
1847
|
-
}
|
|
1848
|
-
}
|
|
1849
|
-
else if (isGroupField(field)) {
|
|
1850
|
-
const childFieldsArray = Object.values(field.fields);
|
|
1851
|
-
const flattenedChildren = flattenFields(childFieldsArray, registry, options);
|
|
1852
|
-
result.push({
|
|
1853
|
-
...field,
|
|
1854
|
-
fields: flattenedChildren,
|
|
1855
|
-
key: field.key || `auto_group_${autoKeyCounter++}`,
|
|
1856
|
-
});
|
|
1857
|
-
}
|
|
1858
|
-
else if (isArrayField(field)) {
|
|
1859
|
-
const flattenedItemTemplates = field.fields.map((itemDef) => {
|
|
1860
|
-
if (!Array.isArray(itemDef)) {
|
|
1861
|
-
const flattened = flattenFields([itemDef], registry, options);
|
|
1862
|
-
return flattened[0];
|
|
1863
|
-
}
|
|
1864
|
-
return flattenFields([...itemDef], registry, options);
|
|
1865
|
-
});
|
|
1866
|
-
result.push({
|
|
1867
|
-
...field,
|
|
1868
|
-
fields: flattenedItemTemplates,
|
|
1869
|
-
key: field.key || `auto_array_${autoKeyCounter++}`,
|
|
1870
|
-
});
|
|
1871
|
-
}
|
|
1872
|
-
else {
|
|
1873
|
-
const key = field.key || `auto_field_${autoKeyCounter++}`;
|
|
1874
|
-
result.push({
|
|
1875
|
-
...field,
|
|
1876
|
-
key,
|
|
1877
|
-
});
|
|
1878
|
-
}
|
|
1879
|
-
}
|
|
1880
|
-
return result;
|
|
1881
|
-
}
|
|
1882
|
-
|
|
1883
1412
|
function createContainerFieldProcessors() {
|
|
1884
1413
|
const flattenCache = new WeakMap();
|
|
1885
1414
|
const memoizedFlattenFields = (fields, registry) => {
|
|
@@ -2314,60 +1843,6 @@ function isValidFormConfiguration(fields) {
|
|
|
2314
1843
|
return FormModeValidator.validateFormConfiguration(fields).isValid;
|
|
2315
1844
|
}
|
|
2316
1845
|
|
|
2317
|
-
const COMPONENT_CACHE = new InjectionToken('COMPONENT_CACHE', {
|
|
2318
|
-
providedIn: 'root',
|
|
2319
|
-
factory: () => new Map(),
|
|
2320
|
-
});
|
|
2321
|
-
function injectFieldRegistry() {
|
|
2322
|
-
const registry = inject(FIELD_REGISTRY);
|
|
2323
|
-
const componentCache = inject(COMPONENT_CACHE);
|
|
2324
|
-
return {
|
|
2325
|
-
getType(name) {
|
|
2326
|
-
return registry.get(name);
|
|
2327
|
-
},
|
|
2328
|
-
hasType(name) {
|
|
2329
|
-
return registry.has(name);
|
|
2330
|
-
},
|
|
2331
|
-
async loadTypeComponent(name) {
|
|
2332
|
-
const fieldType = registry.get(name);
|
|
2333
|
-
if (!fieldType) {
|
|
2334
|
-
throw new DynamicFormError(`Field type "${name}" is not registered`);
|
|
2335
|
-
}
|
|
2336
|
-
if (!fieldType.loadComponent) {
|
|
2337
|
-
return undefined;
|
|
2338
|
-
}
|
|
2339
|
-
const cached = componentCache.get(name);
|
|
2340
|
-
if (cached) {
|
|
2341
|
-
return cached;
|
|
2342
|
-
}
|
|
2343
|
-
try {
|
|
2344
|
-
const component = resolveDefaultExport(await fieldType.loadComponent());
|
|
2345
|
-
if (component) {
|
|
2346
|
-
componentCache.set(name, component);
|
|
2347
|
-
}
|
|
2348
|
-
return component;
|
|
2349
|
-
}
|
|
2350
|
-
catch (error) {
|
|
2351
|
-
throw new DynamicFormError(`Failed to load component for field type "${name}": ${error}`);
|
|
2352
|
-
}
|
|
2353
|
-
},
|
|
2354
|
-
getLoadedComponent(name) {
|
|
2355
|
-
return componentCache.get(name);
|
|
2356
|
-
},
|
|
2357
|
-
getTypes() {
|
|
2358
|
-
return Array.from(registry.values());
|
|
2359
|
-
},
|
|
2360
|
-
registerTypes(types) {
|
|
2361
|
-
types.forEach((type) => {
|
|
2362
|
-
registry.set(type.name, type);
|
|
2363
|
-
});
|
|
2364
|
-
},
|
|
2365
|
-
get raw() {
|
|
2366
|
-
return registry;
|
|
2367
|
-
},
|
|
2368
|
-
};
|
|
2369
|
-
}
|
|
2370
|
-
|
|
2371
1846
|
function collectFieldData(fields, data, registry, pathPrefix, collectKeys = true) {
|
|
2372
1847
|
for (const field of fields) {
|
|
2373
1848
|
const valueHandling = field.type ? getFieldValueHandling(field.type, registry) : 'include';
|
|
@@ -2414,7 +1889,9 @@ function validateRegexPattern(pattern, fieldKey, errors) {
|
|
|
2414
1889
|
new RegExp(pattern);
|
|
2415
1890
|
}
|
|
2416
1891
|
catch (e) {
|
|
2417
|
-
|
|
1892
|
+
errors.push(DEV_MODE
|
|
1893
|
+
? `Invalid regex pattern in validator for field '${fieldKey}': '${pattern}' — ${e instanceof Error ? e.message : String(e)}`
|
|
1894
|
+
: `Invalid regex pattern: '${pattern}'`);
|
|
2418
1895
|
}
|
|
2419
1896
|
}
|
|
2420
1897
|
function validateNoDuplicateKeys(allKeys) {
|
|
@@ -2430,7 +1907,9 @@ function validateNoDuplicateKeys(allKeys) {
|
|
|
2430
1907
|
const duplicateList = Array.from(duplicates)
|
|
2431
1908
|
.map((k) => `'${k}'`)
|
|
2432
1909
|
.join(', ');
|
|
2433
|
-
|
|
1910
|
+
throw new DynamicFormError(DEV_MODE
|
|
1911
|
+
? `Duplicate field keys detected: ${duplicateList}. Field keys must be unique within their group scope (the same key may appear inside different groups).`
|
|
1912
|
+
: `Duplicate field keys detected: ${duplicateList}.`);
|
|
2434
1913
|
}
|
|
2435
1914
|
}
|
|
2436
1915
|
function validateNoDomIdCollisions(domIds) {
|
|
@@ -2446,9 +1925,11 @@ function validateNoDomIdCollisions(domIds) {
|
|
|
2446
1925
|
const duplicateList = Array.from(duplicates)
|
|
2447
1926
|
.map((k) => `'${k}'`)
|
|
2448
1927
|
.join(', ');
|
|
2449
|
-
|
|
2450
|
-
`
|
|
2451
|
-
|
|
1928
|
+
throw new DynamicFormError(DEV_MODE
|
|
1929
|
+
? `DOM id collision detected for: ${duplicateList}. Group-nested keys are joined with '_' to form DOM ids ` +
|
|
1930
|
+
`(e.g. group 'foo' + leaf 'bar' renders as id='foo_bar'). A separate top-level key like 'foo_bar' or a different ` +
|
|
1931
|
+
`group/leaf pair that underscores to the same string will produce duplicate ids. Rename one of the colliding keys.`
|
|
1932
|
+
: `DOM id collision detected for: ${duplicateList}.`);
|
|
2452
1933
|
}
|
|
2453
1934
|
}
|
|
2454
1935
|
function validateFieldTypesRegistered(allTypes, registry, logger) {
|
|
@@ -2478,12 +1959,15 @@ function validateFormConfig(fields, registry, logger) {
|
|
|
2478
1959
|
const list = Array.from(new Set(data.dottedKeys))
|
|
2479
1960
|
.map((k) => `'${k}'`)
|
|
2480
1961
|
.join(', ');
|
|
2481
|
-
|
|
2482
|
-
`(
|
|
1962
|
+
throw new DynamicFormError(DEV_MODE
|
|
1963
|
+
? `Field key(s) contain a dot: ${list}. Dotted keys are not supported — express nesting with 'group' fields ` +
|
|
1964
|
+
`(e.g. { type: 'group', key: 'address', fields: [{ type: 'input', key: 'city' }] }).`
|
|
1965
|
+
: `Field key(s) contain a dot: ${list}.`);
|
|
2483
1966
|
}
|
|
2484
1967
|
validateNoDuplicateKeys(data.keys);
|
|
2485
1968
|
validateNoDomIdCollisions(data.domIds);
|
|
2486
|
-
|
|
1969
|
+
if (DEV_MODE)
|
|
1970
|
+
validateFieldTypesRegistered(data.types, registry, logger);
|
|
2487
1971
|
if (data.regexErrors.length > 0) {
|
|
2488
1972
|
throw new DynamicFormError(data.regexErrors.length === 1
|
|
2489
1973
|
? data.regexErrors[0]
|
|
@@ -2698,6 +2182,33 @@ function resolveExclusionConfig(global, form, field) {
|
|
|
2698
2182
|
excludeValueIfReadonly: field?.excludeValueIfReadonly ?? form?.excludeValueIfReadonly ?? global.excludeValueIfReadonly,
|
|
2699
2183
|
};
|
|
2700
2184
|
}
|
|
2185
|
+
function hasEnabledValueExclusion(fields, global, form) {
|
|
2186
|
+
return fields.some((field) => {
|
|
2187
|
+
const resolved = resolveExclusionConfig(global, form, field);
|
|
2188
|
+
if (resolved.excludeValueIfHidden || resolved.excludeValueIfDisabled || resolved.excludeValueIfReadonly) {
|
|
2189
|
+
return true;
|
|
2190
|
+
}
|
|
2191
|
+
return (isGroupField(field) && !!field.fields && hasEnabledValueExclusion(Object.values(field.fields), global, form));
|
|
2192
|
+
});
|
|
2193
|
+
}
|
|
2194
|
+
function filterDeclaredValueShape(rawValue, fields, registry) {
|
|
2195
|
+
const kept = {};
|
|
2196
|
+
for (const field of fields) {
|
|
2197
|
+
const key = field.key;
|
|
2198
|
+
if (!key || !(key in rawValue))
|
|
2199
|
+
continue;
|
|
2200
|
+
const valueHandling = getFieldValueHandling(field.type, registry);
|
|
2201
|
+
if (valueHandling === 'exclude' || valueHandling === 'flatten')
|
|
2202
|
+
continue;
|
|
2203
|
+
const value = rawValue[key];
|
|
2204
|
+
if (isGroupField(field) && field.fields && value !== null && typeof value === 'object' && !Array.isArray(value)) {
|
|
2205
|
+
kept[key] = filterDeclaredValueShape(value, Object.values(field.fields), registry);
|
|
2206
|
+
continue;
|
|
2207
|
+
}
|
|
2208
|
+
kept[key] = value;
|
|
2209
|
+
}
|
|
2210
|
+
return kept;
|
|
2211
|
+
}
|
|
2701
2212
|
function shouldExcludeField(field, fieldState, config) {
|
|
2702
2213
|
const state = fieldState();
|
|
2703
2214
|
const inherited = field.inheritedExclusionState;
|
|
@@ -2715,7 +2226,10 @@ function shouldExcludeField(field, fieldState, config) {
|
|
|
2715
2226
|
}
|
|
2716
2227
|
return false;
|
|
2717
2228
|
}
|
|
2718
|
-
function filterFormValue(rawValue, schemaFields, formTree, registry, globalDefaults, formOptions) {
|
|
2229
|
+
function filterFormValue(rawValue, schemaFields, formTree, registry, globalDefaults, formOptions, exclusionEnabled = hasEnabledValueExclusion(schemaFields, globalDefaults, formOptions)) {
|
|
2230
|
+
if (!exclusionEnabled) {
|
|
2231
|
+
return filterDeclaredValueShape(rawValue, schemaFields, registry);
|
|
2232
|
+
}
|
|
2719
2233
|
const result = {};
|
|
2720
2234
|
for (const field of schemaFields) {
|
|
2721
2235
|
const key = field.key;
|
|
@@ -2751,7 +2265,7 @@ function filterFormValue(rawValue, schemaFields, formTree, registry, globalDefau
|
|
|
2751
2265
|
if (groupValue && typeof groupValue === 'object') {
|
|
2752
2266
|
const childFields = Object.values(field.fields);
|
|
2753
2267
|
const nestedTree = fieldState;
|
|
2754
|
-
result[key] = filterFormValue(groupValue, childFields, nestedTree, registry, globalDefaults, formOptions);
|
|
2268
|
+
result[key] = filterFormValue(groupValue, childFields, nestedTree, registry, globalDefaults, formOptions, true);
|
|
2755
2269
|
}
|
|
2756
2270
|
else {
|
|
2757
2271
|
result[key] = groupValue;
|
|
@@ -3064,7 +2578,7 @@ class FormStateMachine {
|
|
|
3064
2578
|
if (!isTransitioningState(state) || state.phase !== Phase.Restoring) {
|
|
3065
2579
|
return { state, sideEffects: [] };
|
|
3066
2580
|
}
|
|
3067
|
-
|
|
2581
|
+
if (DEV_MODE && !state.pendingFormSetup) {
|
|
3068
2582
|
this.config.logger.warn('handleRestoreComplete: pendingFormSetup was not set — falling back to recomputing. ' +
|
|
3069
2583
|
'This indicates a bug in the transition flow.');
|
|
3070
2584
|
}
|
|
@@ -3127,7 +2641,7 @@ class FormStateMachine {
|
|
|
3127
2641
|
const state = untracked(() => this._state());
|
|
3128
2642
|
if (!isTransitioningState(state) || state.phase !== Phase.Restoring)
|
|
3129
2643
|
return;
|
|
3130
|
-
|
|
2644
|
+
if (DEV_MODE && !state.pendingFormSetup) {
|
|
3131
2645
|
this.config.logger.warn('RestoreValues effect: pendingFormSetup was not set — falling back to recomputing.');
|
|
3132
2646
|
}
|
|
3133
2647
|
const formSetup = state.pendingFormSetup ?? this.config.createFormSetup(state.pendingConfig);
|
|
@@ -3269,13 +2783,6 @@ class FormStateManager {
|
|
|
3269
2783
|
return detectFormMode(config.fields || []);
|
|
3270
2784
|
}, /* @ts-ignore */
|
|
3271
2785
|
...(ngDevMode ? [{ debugName: "formModeDetection" }] : /* istanbul ignore next */ []));
|
|
3272
|
-
formConfigValidation = computed(() => {
|
|
3273
|
-
const config = this.activeConfig();
|
|
3274
|
-
if (!config)
|
|
3275
|
-
return { isValid: true, errors: [], warnings: [] };
|
|
3276
|
-
return FormModeValidator.validateFormConfiguration(config.fields || []);
|
|
3277
|
-
}, /* @ts-ignore */
|
|
3278
|
-
...(ngDevMode ? [{ debugName: "formConfigValidation" }] : /* istanbul ignore next */ []));
|
|
3279
2786
|
effectiveFormOptions = computed(() => {
|
|
3280
2787
|
const config = this.activeConfig();
|
|
3281
2788
|
const configOptions = config?.options || {};
|
|
@@ -3283,6 +2790,8 @@ class FormStateManager {
|
|
|
3283
2790
|
return { ...configOptions, ...inputOptions };
|
|
3284
2791
|
}, /* @ts-ignore */
|
|
3285
2792
|
...(ngDevMode ? [{ debugName: "effectiveFormOptions" }] : /* istanbul ignore next */ []));
|
|
2793
|
+
pendingPageRequest = signal(null, /* @ts-ignore */
|
|
2794
|
+
...(ngDevMode ? [{ debugName: "pendingPageRequest" }] : /* istanbul ignore next */ []));
|
|
3286
2795
|
pageFieldDefinitions = computed(() => {
|
|
3287
2796
|
const config = this.activeConfig();
|
|
3288
2797
|
const mode = this.formModeDetection().mode;
|
|
@@ -3320,21 +2829,70 @@ class FormStateManager {
|
|
|
3320
2829
|
return this.createEmptyFormSetup(registry);
|
|
3321
2830
|
}, /* @ts-ignore */
|
|
3322
2831
|
...(ngDevMode ? [{ debugName: "formSetup" }] : /* istanbul ignore next */ []));
|
|
2832
|
+
formSetupConfig = computed(() => {
|
|
2833
|
+
const state = this.machine.state();
|
|
2834
|
+
if (isReadyState(state))
|
|
2835
|
+
return state.config;
|
|
2836
|
+
if (isTransitioningState(state)) {
|
|
2837
|
+
if (state.phase === Phase.Restoring && state.pendingFormSetup) {
|
|
2838
|
+
return state.pendingConfig;
|
|
2839
|
+
}
|
|
2840
|
+
return state.currentConfig;
|
|
2841
|
+
}
|
|
2842
|
+
if (state.type === LifecycleState.Initializing)
|
|
2843
|
+
return state.config;
|
|
2844
|
+
if (state.type === LifecycleState.Uninitialized)
|
|
2845
|
+
return this.activeConfig();
|
|
2846
|
+
return undefined;
|
|
2847
|
+
}, /* @ts-ignore */
|
|
2848
|
+
...(ngDevMode ? [{ debugName: "formSetupConfig" }] : /* istanbul ignore next */ []));
|
|
2849
|
+
activePageState = linkedSignal({ ...(ngDevMode ? { debugName: "activePageState" } : /* istanbul ignore next */ {}), source: this.activeConfig,
|
|
2850
|
+
computation: (config) => ({ config, initialized: false, index: 0 }) });
|
|
3323
2851
|
defaultValues = linkedSignal(() => this.formSetup().defaultValues, /* @ts-ignore */
|
|
3324
2852
|
...(ngDevMode ? [{ debugName: "defaultValues" }] : /* istanbul ignore next */ []));
|
|
3325
2853
|
validKeys = computed(() => {
|
|
3326
2854
|
const schemaFields = this.formSetup().schemaFields;
|
|
3327
2855
|
if (!schemaFields || schemaFields.length === 0)
|
|
3328
2856
|
return undefined;
|
|
3329
|
-
|
|
2857
|
+
const keys = new Set();
|
|
2858
|
+
for (const field of schemaFields) {
|
|
2859
|
+
if (field.key === undefined)
|
|
2860
|
+
continue;
|
|
2861
|
+
keys.add(field.key);
|
|
2862
|
+
}
|
|
2863
|
+
return keys;
|
|
3330
2864
|
}, /* @ts-ignore */
|
|
3331
2865
|
...(ngDevMode ? [{ debugName: "validKeys" }] : /* istanbul ignore next */ []));
|
|
3332
|
-
|
|
2866
|
+
boundValueExclusionEnabled = computed(() => {
|
|
2867
|
+
const setup = this.formSetup();
|
|
2868
|
+
if (!setup.schemaFields || setup.schemaFields.length === 0)
|
|
2869
|
+
return false;
|
|
2870
|
+
const options = this.effectiveFormOptions();
|
|
2871
|
+
return hasEnabledValueExclusion(setup.schemaFields, BOUND_VALUE_EXCLUSION_BASELINE, {
|
|
2872
|
+
excludeValueIfHidden: options.excludeValueIfHidden,
|
|
2873
|
+
excludeValueIfDisabled: options.excludeValueIfDisabled,
|
|
2874
|
+
excludeValueIfReadonly: options.excludeValueIfReadonly,
|
|
2875
|
+
});
|
|
2876
|
+
}, /* @ts-ignore */
|
|
2877
|
+
...(ngDevMode ? [{ debugName: "boundValueExclusionEnabled" }] : /* istanbul ignore next */ []));
|
|
2878
|
+
valueExclusionEnabled = computed(() => {
|
|
3333
2879
|
const setup = this.formSetup();
|
|
3334
2880
|
if (!setup.schemaFields || setup.schemaFields.length === 0)
|
|
2881
|
+
return false;
|
|
2882
|
+
const options = this.effectiveFormOptions();
|
|
2883
|
+
return hasEnabledValueExclusion(setup.schemaFields, this.valueExclusionDefaults, {
|
|
2884
|
+
excludeValueIfHidden: options.excludeValueIfHidden,
|
|
2885
|
+
excludeValueIfDisabled: options.excludeValueIfDisabled,
|
|
2886
|
+
excludeValueIfReadonly: options.excludeValueIfReadonly,
|
|
2887
|
+
});
|
|
2888
|
+
}, /* @ts-ignore */
|
|
2889
|
+
...(ngDevMode ? [{ debugName: "valueExclusionEnabled" }] : /* istanbul ignore next */ []));
|
|
2890
|
+
fieldStateSnapshot = computed(() => {
|
|
2891
|
+
const schemaFields = this.formSetup().schemaFields;
|
|
2892
|
+
if (!this.formSchemaReady() || !this.boundValueExclusionEnabled() || !schemaFields || schemaFields.length === 0)
|
|
3335
2893
|
return {};
|
|
3336
2894
|
const snapshot = {};
|
|
3337
|
-
collectFieldStateSnapshot(
|
|
2895
|
+
collectFieldStateSnapshot(schemaFields, asFieldTreeRecord(this.form()), [], snapshot);
|
|
3338
2896
|
return snapshot;
|
|
3339
2897
|
}, /* @ts-ignore */
|
|
3340
2898
|
...(ngDevMode ? [{ debugName: "fieldStateSnapshot" }] : /* istanbul ignore next */ []));
|
|
@@ -3342,8 +2900,22 @@ class FormStateManager {
|
|
|
3342
2900
|
...(ngDevMode ? [{ debugName: "excludedValueStore" }] : /* istanbul ignore next */ []));
|
|
3343
2901
|
prevFieldStateSnapshot = signal({}, /* @ts-ignore */
|
|
3344
2902
|
...(ngDevMode ? [{ debugName: "prevFieldStateSnapshot" }] : /* istanbul ignore next */ []));
|
|
2903
|
+
lastOutwardValue = { current: undefined, pending: false };
|
|
2904
|
+
lastExternalValue = { current: undefined };
|
|
2905
|
+
externalValue = computed(() => {
|
|
2906
|
+
const incoming = this.deps.value();
|
|
2907
|
+
if (this.lastOutwardValue.pending && incoming === this.lastOutwardValue.current) {
|
|
2908
|
+
this.lastOutwardValue.pending = false;
|
|
2909
|
+
return this.lastExternalValue.current;
|
|
2910
|
+
}
|
|
2911
|
+
this.lastOutwardValue.pending = false;
|
|
2912
|
+
this.lastExternalValue.current = incoming;
|
|
2913
|
+
return incoming;
|
|
2914
|
+
}, /* @ts-ignore */
|
|
2915
|
+
...(ngDevMode ? [{ debugName: "externalValue" }] : /* istanbul ignore next */ []));
|
|
3345
2916
|
entity = linkedSignal(() => {
|
|
3346
|
-
|
|
2917
|
+
this.externalValue();
|
|
2918
|
+
const inputValue = untracked(() => this.deps.value());
|
|
3347
2919
|
const defaults = this.defaultValues();
|
|
3348
2920
|
const keys = this.validKeys();
|
|
3349
2921
|
const saved = this.excludedValueStore();
|
|
@@ -3363,22 +2935,62 @@ class FormStateManager {
|
|
|
3363
2935
|
debugName: 'FormStateManager.entity',
|
|
3364
2936
|
equal: isEqual,
|
|
3365
2937
|
});
|
|
3366
|
-
|
|
2938
|
+
loadFormSchemaService = injectAsync(() => import('./ng-forge-dynamic-forms-form-schema.service-BzbBv-r0.mjs'));
|
|
2939
|
+
formSchemaRequest = computed(() => {
|
|
3367
2940
|
const setup = this.formSetup();
|
|
3368
2941
|
const config = this.activeConfig();
|
|
3369
|
-
if (!config)
|
|
2942
|
+
if (!config || (!(setup.schemaFields?.length > 0) && !config.schema))
|
|
2943
|
+
return undefined;
|
|
2944
|
+
return {
|
|
2945
|
+
fields: setup.schemaFields ?? [],
|
|
2946
|
+
registry: setup.registry,
|
|
2947
|
+
formLevelSchema: config.schema,
|
|
2948
|
+
validateWhenHidden: this.effectiveFormOptions().validateWhenHidden,
|
|
2949
|
+
};
|
|
2950
|
+
}, /* @ts-ignore */
|
|
2951
|
+
...(ngDevMode ? [{ debugName: "formSchemaRequest" }] : /* istanbul ignore next */ []));
|
|
2952
|
+
formSchemaResource = resource({ ...(ngDevMode ? { debugName: "formSchemaResource" } : /* istanbul ignore next */ {}), params: () => this.formSchemaRequest(),
|
|
2953
|
+
loader: async ({ params }) => {
|
|
2954
|
+
const schemaService = await this.loadFormSchemaService();
|
|
2955
|
+
return runInInjectionContext(this.injector, () => schemaService.create(params));
|
|
2956
|
+
} });
|
|
2957
|
+
schemaCache = { current: undefined };
|
|
2958
|
+
trackSchemaCache = computed(() => {
|
|
2959
|
+
const request = this.formSchemaRequest();
|
|
2960
|
+
if (!request) {
|
|
2961
|
+
this.schemaCache.current = undefined;
|
|
3370
2962
|
return undefined;
|
|
3371
|
-
if (setup.schemaFields?.length) {
|
|
3372
|
-
return createSchemaFromFields(setup.schemaFields, setup.registry, {
|
|
3373
|
-
formLevelSchema: config.schema,
|
|
3374
|
-
validateWhenHidden: this.effectiveFormOptions().validateWhenHidden,
|
|
3375
|
-
});
|
|
3376
2963
|
}
|
|
3377
|
-
if (
|
|
3378
|
-
|
|
2964
|
+
if (this.formSchemaResource.status() === 'error') {
|
|
2965
|
+
this.schemaCache.current = undefined;
|
|
2966
|
+
return undefined;
|
|
3379
2967
|
}
|
|
3380
|
-
|
|
3381
|
-
|
|
2968
|
+
if (this.formSchemaResource.hasValue()) {
|
|
2969
|
+
this.schemaCache.current = { schema: this.formSchemaResource.value(), request };
|
|
2970
|
+
}
|
|
2971
|
+
return this.schemaCache.current;
|
|
2972
|
+
}, /* @ts-ignore */
|
|
2973
|
+
...(ngDevMode ? [{ debugName: "trackSchemaCache" }] : /* istanbul ignore next */ []));
|
|
2974
|
+
isSchemaCurrent = computed(() => {
|
|
2975
|
+
if (this.formSetupConfig() !== this.activeConfig())
|
|
2976
|
+
return false;
|
|
2977
|
+
const request = this.formSchemaRequest();
|
|
2978
|
+
if (!request)
|
|
2979
|
+
return true;
|
|
2980
|
+
return this.trackSchemaCache()?.request === request;
|
|
2981
|
+
}, /* @ts-ignore */
|
|
2982
|
+
...(ngDevMode ? [{ debugName: "isSchemaCurrent" }] : /* istanbul ignore next */ []));
|
|
2983
|
+
formSchemaReady = computed(() => {
|
|
2984
|
+
if (this.formSetupConfig() !== this.activeConfig())
|
|
2985
|
+
return false;
|
|
2986
|
+
if (!this.formSchemaRequest())
|
|
2987
|
+
return true;
|
|
2988
|
+
if (this.formSchemaResource.status() === 'error')
|
|
2989
|
+
return true;
|
|
2990
|
+
return this.trackSchemaCache() !== undefined;
|
|
2991
|
+
}, /* @ts-ignore */
|
|
2992
|
+
...(ngDevMode ? [{ debugName: "formSchemaReady" }] : /* istanbul ignore next */ []));
|
|
2993
|
+
formSchema = computed(() => this.trackSchemaCache()?.schema, /* @ts-ignore */
|
|
3382
2994
|
...(ngDevMode ? [{ debugName: "formSchema" }] : /* istanbul ignore next */ []));
|
|
3383
2995
|
form = computed(() => {
|
|
3384
2996
|
const schema = this.formSchema();
|
|
@@ -3409,13 +3021,16 @@ class FormStateManager {
|
|
|
3409
3021
|
...(ngDevMode ? [{ debugName: "fieldSignalContext" }] : /* istanbul ignore next */ []));
|
|
3410
3022
|
formInstance = computed(() => this.form()(), /* @ts-ignore */
|
|
3411
3023
|
...(ngDevMode ? [{ debugName: "formInstance" }] : /* istanbul ignore next */ []));
|
|
3412
|
-
|
|
3024
|
+
modelValue = computed(() => this.formInstance().value(), /* @ts-ignore */
|
|
3025
|
+
...(ngDevMode ? [{ debugName: "modelValue" }] : /* istanbul ignore next */ []));
|
|
3026
|
+
formValue = computed(() => this.modelValue(), /* @ts-ignore */
|
|
3413
3027
|
...(ngDevMode ? [{ debugName: "formValue" }] : /* istanbul ignore next */ []));
|
|
3414
3028
|
filteredFormValue = computed(() => {
|
|
3415
|
-
const rawValue = this.
|
|
3029
|
+
const rawValue = this.modelValue();
|
|
3416
3030
|
const setup = this.formSetup();
|
|
3031
|
+
const schemaFields = setup.schemaFields;
|
|
3417
3032
|
const options = this.effectiveFormOptions();
|
|
3418
|
-
if (!
|
|
3033
|
+
if (!this.formSchemaReady() || !schemaFields || schemaFields.length === 0) {
|
|
3419
3034
|
return rawValue;
|
|
3420
3035
|
}
|
|
3421
3036
|
const formTree = this.form();
|
|
@@ -3425,14 +3040,15 @@ class FormStateManager {
|
|
|
3425
3040
|
excludeValueIfReadonly: options.excludeValueIfReadonly,
|
|
3426
3041
|
};
|
|
3427
3042
|
const fieldTreeRecord = asFieldTreeRecord(formTree);
|
|
3428
|
-
return filterFormValue(rawValue,
|
|
3043
|
+
return filterFormValue(rawValue, schemaFields, fieldTreeRecord, setup.registry, this.valueExclusionDefaults, formOptions, this.valueExclusionEnabled());
|
|
3429
3044
|
}, /* @ts-ignore */
|
|
3430
3045
|
...(ngDevMode ? [{ debugName: "filteredFormValue" }] : /* istanbul ignore next */ []));
|
|
3431
3046
|
boundFormValue = computed(() => {
|
|
3432
|
-
const rawValue = this.
|
|
3047
|
+
const rawValue = this.modelValue();
|
|
3433
3048
|
const setup = this.formSetup();
|
|
3049
|
+
const schemaFields = setup.schemaFields;
|
|
3434
3050
|
const options = this.effectiveFormOptions();
|
|
3435
|
-
if (!
|
|
3051
|
+
if (!this.formSchemaReady() || !schemaFields || schemaFields.length === 0) {
|
|
3436
3052
|
return rawValue;
|
|
3437
3053
|
}
|
|
3438
3054
|
const formTree = this.form();
|
|
@@ -3442,26 +3058,26 @@ class FormStateManager {
|
|
|
3442
3058
|
excludeValueIfReadonly: options.excludeValueIfReadonly,
|
|
3443
3059
|
};
|
|
3444
3060
|
const fieldTreeRecord = asFieldTreeRecord(formTree);
|
|
3445
|
-
return filterFormValue(rawValue,
|
|
3061
|
+
return filterFormValue(rawValue, schemaFields, fieldTreeRecord, setup.registry, BOUND_VALUE_EXCLUSION_BASELINE, formOptions, this.boundValueExclusionEnabled());
|
|
3446
3062
|
}, /* @ts-ignore */
|
|
3447
3063
|
...(ngDevMode ? [{ debugName: "boundFormValue" }] : /* istanbul ignore next */ []));
|
|
3448
|
-
|
|
3064
|
+
valid = computed(() => this.formSchemaReady() && this.formSchemaResource.status() !== 'error' && this.formInstance().valid(), /* @ts-ignore */
|
|
3449
3065
|
...(ngDevMode ? [{ debugName: "valid" }] : /* istanbul ignore next */ []));
|
|
3450
|
-
invalid = computed(() => this.formInstance().invalid(), /* @ts-ignore */
|
|
3066
|
+
invalid = computed(() => !this.formSchemaReady() || this.formSchemaResource.status() === 'error' || this.formInstance().invalid(), /* @ts-ignore */
|
|
3451
3067
|
...(ngDevMode ? [{ debugName: "invalid" }] : /* istanbul ignore next */ []));
|
|
3452
|
-
dirty = computed(() => this.formInstance().dirty(), /* @ts-ignore */
|
|
3068
|
+
dirty = computed(() => this.formSchemaReady() && this.formInstance().dirty(), /* @ts-ignore */
|
|
3453
3069
|
...(ngDevMode ? [{ debugName: "dirty" }] : /* istanbul ignore next */ []));
|
|
3454
|
-
touched = computed(() => this.formInstance().touched(), /* @ts-ignore */
|
|
3070
|
+
touched = computed(() => this.formSchemaReady() && this.formInstance().touched(), /* @ts-ignore */
|
|
3455
3071
|
...(ngDevMode ? [{ debugName: "touched" }] : /* istanbul ignore next */ []));
|
|
3456
|
-
errors = computed(() => this.formInstance().errors(), /* @ts-ignore */
|
|
3072
|
+
errors = computed(() => (this.formSchemaReady() ? this.formInstance().errors() : []), /* @ts-ignore */
|
|
3457
3073
|
...(ngDevMode ? [{ debugName: "errors" }] : /* istanbul ignore next */ []));
|
|
3458
3074
|
disabled = computed(() => {
|
|
3459
3075
|
const optionsDisabled = this.effectiveFormOptions().disabled;
|
|
3460
|
-
const formDisabled = this.formInstance().disabled();
|
|
3076
|
+
const formDisabled = this.formSchemaReady() && this.formInstance().disabled();
|
|
3461
3077
|
return optionsDisabled ?? formDisabled;
|
|
3462
3078
|
}, /* @ts-ignore */
|
|
3463
3079
|
...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
|
|
3464
|
-
submitting = computed(() => this.formInstance().submitting(), /* @ts-ignore */
|
|
3080
|
+
submitting = computed(() => this.formSchemaReady() && this.formInstance().submitting(), /* @ts-ignore */
|
|
3465
3081
|
...(ngDevMode ? [{ debugName: "submitting" }] : /* istanbul ignore next */ []));
|
|
3466
3082
|
fieldsSource = computed(() => {
|
|
3467
3083
|
const state = this.machine.state();
|
|
@@ -3576,6 +3192,10 @@ class FormStateManager {
|
|
|
3576
3192
|
}), takeUntilDestroyed(this.destroyRef));
|
|
3577
3193
|
this.reset$ = this.eventBus.on('form-reset').pipe(map(() => undefined), takeUntilDestroyed(this.destroyRef));
|
|
3578
3194
|
this.cleared$ = this.eventBus.on('form-clear').pipe(map(() => undefined), takeUntilDestroyed(this.destroyRef));
|
|
3195
|
+
this.eventBus
|
|
3196
|
+
.on('go-to-page')
|
|
3197
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
3198
|
+
.subscribe((event) => this.pendingPageRequest.set({ index: event.pageIndex, options: event.options }));
|
|
3579
3199
|
this.machine = createFormStateMachine({
|
|
3580
3200
|
injector: this.injector,
|
|
3581
3201
|
destroyRef: this.destroyRef,
|
|
@@ -3639,6 +3259,11 @@ class FormStateManager {
|
|
|
3639
3259
|
this.eventBus.dispatch(FormSubmitEvent);
|
|
3640
3260
|
}
|
|
3641
3261
|
setupEffects() {
|
|
3262
|
+
explicitEffect([this.formSchemaResource.error], ([error]) => {
|
|
3263
|
+
if (error) {
|
|
3264
|
+
this.logger.error('[Dynamic Forms] Failed to compile the form schema; rendering without validation and blocking submission.', error);
|
|
3265
|
+
}
|
|
3266
|
+
});
|
|
3642
3267
|
explicitEffect([this.deps.config], ([config]) => {
|
|
3643
3268
|
this.fieldLoadingErrors.set([]);
|
|
3644
3269
|
const state = this.machine.currentState;
|
|
@@ -3690,17 +3315,24 @@ class FormStateManager {
|
|
|
3690
3315
|
explicitEffect([this.boundFormValue], ([currentBound]) => {
|
|
3691
3316
|
const currentValue = this.deps.value();
|
|
3692
3317
|
if (!isEqual(currentBound, currentValue)) {
|
|
3318
|
+
this.lastOutwardValue.current = currentBound;
|
|
3319
|
+
this.lastOutwardValue.pending = true;
|
|
3693
3320
|
this.deps.value.set(currentBound);
|
|
3694
3321
|
}
|
|
3695
3322
|
});
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3323
|
+
if (DEV_MODE) {
|
|
3324
|
+
explicitEffect([this.activeConfig], ([config]) => {
|
|
3325
|
+
if (!config)
|
|
3326
|
+
return;
|
|
3327
|
+
const validation = FormModeValidator.validateFormConfiguration(config.fields || []);
|
|
3328
|
+
if (!validation.isValid) {
|
|
3329
|
+
this.logger.error('Invalid form configuration:', validation.errors);
|
|
3330
|
+
}
|
|
3331
|
+
if (validation.warnings.length > 0) {
|
|
3332
|
+
this.logger.warn('Form configuration warnings:', validation.warnings);
|
|
3333
|
+
}
|
|
3334
|
+
});
|
|
3335
|
+
}
|
|
3704
3336
|
}
|
|
3705
3337
|
setupEventHandlers() {
|
|
3706
3338
|
this.eventBus
|
|
@@ -3721,71 +3353,480 @@ class FormStateManager {
|
|
|
3721
3353
|
if (!customFnConfig) {
|
|
3722
3354
|
return;
|
|
3723
3355
|
}
|
|
3724
|
-
if (customFnConfig.customFunctions) {
|
|
3725
|
-
Object.entries(customFnConfig.customFunctions).forEach(([name, fn]) => {
|
|
3726
|
-
this.functionRegistry.registerCustomFunction(name, fn);
|
|
3727
|
-
});
|
|
3356
|
+
if (customFnConfig.customFunctions) {
|
|
3357
|
+
Object.entries(customFnConfig.customFunctions).forEach(([name, fn]) => {
|
|
3358
|
+
this.functionRegistry.registerCustomFunction(name, fn);
|
|
3359
|
+
});
|
|
3360
|
+
}
|
|
3361
|
+
if (customFnConfig.derivations) {
|
|
3362
|
+
this.functionRegistry.setDerivationFunctions(customFnConfig.derivations);
|
|
3363
|
+
}
|
|
3364
|
+
if (customFnConfig.asyncDerivations) {
|
|
3365
|
+
this.functionRegistry.setAsyncDerivationFunctions(customFnConfig.asyncDerivations);
|
|
3366
|
+
}
|
|
3367
|
+
if (customFnConfig.asyncConditions) {
|
|
3368
|
+
this.functionRegistry.setAsyncConditionFunctions(customFnConfig.asyncConditions);
|
|
3369
|
+
}
|
|
3370
|
+
this.functionRegistry.setValidators(customFnConfig.validators);
|
|
3371
|
+
this.functionRegistry.setAsyncValidators(customFnConfig.asyncValidators);
|
|
3372
|
+
this.functionRegistry.setHttpValidators(customFnConfig.httpValidators);
|
|
3373
|
+
}
|
|
3374
|
+
createFormSetupFromConfig(fields, mode, registry) {
|
|
3375
|
+
const normalizedFields = normalizeSimplifiedArrays(fields);
|
|
3376
|
+
validateFormConfig(normalizedFields, registry, this.logger);
|
|
3377
|
+
const source = this.deps.source?.() ?? 'inline';
|
|
3378
|
+
const { fields: sanitizedFields, warnings: addonWarnings } = walkAndValidateAddons(normalizedFields, registry, this.addonKindRegistry, source);
|
|
3379
|
+
if (DEV_MODE) {
|
|
3380
|
+
const nextKeys = new Set();
|
|
3381
|
+
for (const w of addonWarnings) {
|
|
3382
|
+
const key = addonWarningKey(w);
|
|
3383
|
+
nextKeys.add(key);
|
|
3384
|
+
if (!this.lastAddonWarningKeys.has(key)) {
|
|
3385
|
+
this.logger.warn(formatAddonWarning(w));
|
|
3386
|
+
}
|
|
3387
|
+
}
|
|
3388
|
+
this.lastAddonWarningKeys = nextKeys;
|
|
3389
|
+
}
|
|
3390
|
+
const validatedFields = sanitizedFields;
|
|
3391
|
+
const flattenedFields = this.fieldProcessors.memoizedFlattenFields(validatedFields, registry);
|
|
3392
|
+
const flattenedFieldsForRendering = this.fieldProcessors.memoizedFlattenFieldsForRendering(validatedFields, registry);
|
|
3393
|
+
const fieldsById = this.fieldProcessors.memoizedKeyBy(flattenedFields);
|
|
3394
|
+
const defaultValues = this.fieldProcessors.memoizedDefaultValues(fieldsById, registry);
|
|
3395
|
+
const fieldsToRender = mode === 'paged' ? [] : flattenedFieldsForRendering;
|
|
3396
|
+
return {
|
|
3397
|
+
fields: fieldsToRender,
|
|
3398
|
+
schemaFields: flattenedFields,
|
|
3399
|
+
originalFields: fields,
|
|
3400
|
+
defaultValues,
|
|
3401
|
+
mode,
|
|
3402
|
+
registry,
|
|
3403
|
+
};
|
|
3404
|
+
}
|
|
3405
|
+
createEmptyFormSetup(registry) {
|
|
3406
|
+
return {
|
|
3407
|
+
fields: [],
|
|
3408
|
+
schemaFields: [],
|
|
3409
|
+
defaultValues: {},
|
|
3410
|
+
mode: 'non-paged',
|
|
3411
|
+
registry,
|
|
3412
|
+
};
|
|
3413
|
+
}
|
|
3414
|
+
ngOnDestroy() {
|
|
3415
|
+
this.machine.dispatch({ type: Action.Destroy });
|
|
3416
|
+
}
|
|
3417
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: FormStateManager, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
3418
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: FormStateManager });
|
|
3419
|
+
}
|
|
3420
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: FormStateManager, decorators: [{
|
|
3421
|
+
type: Injectable
|
|
3422
|
+
}], ctorParameters: () => [] });
|
|
3423
|
+
|
|
3424
|
+
class PageOrchestratorComponent {
|
|
3425
|
+
eventBus = inject(EventBus);
|
|
3426
|
+
fieldContextRegistry = inject(FieldContextRegistryService);
|
|
3427
|
+
functionRegistry = inject(FunctionRegistryService);
|
|
3428
|
+
formOptions = inject(FORM_OPTIONS, { optional: true });
|
|
3429
|
+
globalPreloadWindow = inject(PAGE_PRELOAD_WINDOW);
|
|
3430
|
+
stateManager = inject(FormStateManager, { optional: true });
|
|
3431
|
+
pendingPageRequest = computed(() => this.stateManager?.pendingPageRequest() ?? null, /* @ts-ignore */
|
|
3432
|
+
...(ngDevMode ? [{ debugName: "pendingPageRequest" }] : /* istanbul ignore next */ []));
|
|
3433
|
+
schemaCurrent = computed(() => this.stateManager?.isSchemaCurrent() ?? true, /* @ts-ignore */
|
|
3434
|
+
...(ngDevMode ? [{ debugName: "schemaCurrent" }] : /* istanbul ignore next */ []));
|
|
3435
|
+
configScope = computed(() => this.stateManager?.activeConfig(), /* @ts-ignore */
|
|
3436
|
+
...(ngDevMode ? [{ debugName: "configScope" }] : /* istanbul ignore next */ []));
|
|
3437
|
+
pageFields = input.required(/* @ts-ignore */
|
|
3438
|
+
...(ngDevMode ? [{ debugName: "pageFields" }] : /* istanbul ignore next */ []));
|
|
3439
|
+
form = input.required(/* @ts-ignore */
|
|
3440
|
+
...(ngDevMode ? [{ debugName: "form" }] : /* istanbul ignore next */ []));
|
|
3441
|
+
fieldSignalContext = input.required(/* @ts-ignore */
|
|
3442
|
+
...(ngDevMode ? [{ debugName: "fieldSignalContext" }] : /* istanbul ignore next */ []));
|
|
3443
|
+
pageHiddenStates = computed(() => {
|
|
3444
|
+
const pages = this.pageFields();
|
|
3445
|
+
return pages.map((page) => this.evaluatePageHidden(page));
|
|
3446
|
+
}, /* @ts-ignore */
|
|
3447
|
+
...(ngDevMode ? [{ debugName: "pageHiddenStates" }] : /* istanbul ignore next */ []));
|
|
3448
|
+
visiblePageIndices = computed(() => {
|
|
3449
|
+
const hiddenStates = this.pageHiddenStates();
|
|
3450
|
+
return hiddenStates
|
|
3451
|
+
.map((hidden, index) => ({ index, hidden }))
|
|
3452
|
+
.filter((item) => !item.hidden)
|
|
3453
|
+
.map((item) => item.index);
|
|
3454
|
+
}, /* @ts-ignore */
|
|
3455
|
+
...(ngDevMode ? [{ debugName: "visiblePageIndices" }] : /* istanbul ignore next */ []));
|
|
3456
|
+
activePageOwnership = linkedSignal({ ...(ngDevMode ? { debugName: "activePageOwnership" } : /* istanbul ignore next */ {}), source: this.configScope,
|
|
3457
|
+
computation: (config, previous) => {
|
|
3458
|
+
const totalPages = this.pageFields().length;
|
|
3459
|
+
if (totalPages === 0)
|
|
3460
|
+
return { config, initialized: true, index: 0 };
|
|
3461
|
+
return untracked(() => {
|
|
3462
|
+
const owned = this.stateManager?.activePageState();
|
|
3463
|
+
const mayRestore = previous === undefined || previous.source === config;
|
|
3464
|
+
if (mayRestore &&
|
|
3465
|
+
owned !== undefined &&
|
|
3466
|
+
owned.config === config &&
|
|
3467
|
+
owned.initialized &&
|
|
3468
|
+
owned.index >= 0 &&
|
|
3469
|
+
owned.index < totalPages) {
|
|
3470
|
+
return { config, initialized: true, index: owned.index };
|
|
3471
|
+
}
|
|
3472
|
+
if (this.initialPage().validate && !this.schemaCurrent()) {
|
|
3473
|
+
return { config, initialized: false, index: 0 };
|
|
3474
|
+
}
|
|
3475
|
+
return { config, initialized: true, index: this.resolveInitialLanding() };
|
|
3476
|
+
});
|
|
3477
|
+
} });
|
|
3478
|
+
currentPageIndex = computed(() => this.activePageOwnership().index, /* @ts-ignore */
|
|
3479
|
+
...(ngDevMode ? [{ debugName: "currentPageIndex" }] : /* istanbul ignore next */ []));
|
|
3480
|
+
setCurrentPageIndex(index) {
|
|
3481
|
+
this.activePageOwnership.update((ownership) => ({ ...ownership, initialized: true, index }));
|
|
3482
|
+
}
|
|
3483
|
+
resolveInitialLanding() {
|
|
3484
|
+
const { index, validate } = this.initialPage();
|
|
3485
|
+
const visible = this.visiblePageIndices();
|
|
3486
|
+
if (index === 0 || visible.length === 0)
|
|
3487
|
+
return 0;
|
|
3488
|
+
const target = visible.includes(index) ? index : this.findNearestVisiblePage(index, visible);
|
|
3489
|
+
if (target <= 0)
|
|
3490
|
+
return Math.max(target, 0);
|
|
3491
|
+
if (!validate)
|
|
3492
|
+
return target;
|
|
3493
|
+
const firstInvalid = visible.filter((i) => i < target).find((i) => !this.isPageValid(i));
|
|
3494
|
+
return firstInvalid ?? target;
|
|
3495
|
+
}
|
|
3496
|
+
initialPage = computed(() => {
|
|
3497
|
+
const raw = this.formOptions?.()?.initialPage;
|
|
3498
|
+
const totalPages = this.pageFields().length;
|
|
3499
|
+
const requested = typeof raw === 'number' ? raw : raw?.index;
|
|
3500
|
+
const validate = typeof raw === 'number' ? false : (raw?.validate ?? false);
|
|
3501
|
+
if (typeof requested !== 'number' || !Number.isFinite(requested) || requested < 0 || totalPages === 0) {
|
|
3502
|
+
return { index: 0, validate: false };
|
|
3503
|
+
}
|
|
3504
|
+
return { index: Math.min(Math.trunc(requested), totalPages - 1), validate };
|
|
3505
|
+
}, /* @ts-ignore */
|
|
3506
|
+
...(ngDevMode ? [{ debugName: "initialPage" }] : /* istanbul ignore next */ []));
|
|
3507
|
+
state = computed(() => {
|
|
3508
|
+
const currentIndex = this.currentPageIndex();
|
|
3509
|
+
const totalPages = this.pageFields().length;
|
|
3510
|
+
const visibleIndices = this.visiblePageIndices();
|
|
3511
|
+
const currentVisiblePosition = visibleIndices.indexOf(currentIndex);
|
|
3512
|
+
const isFirstVisiblePage = currentVisiblePosition === 0 || currentVisiblePosition === -1;
|
|
3513
|
+
const isLastVisiblePage = currentVisiblePosition >= visibleIndices.length - 1;
|
|
3514
|
+
return {
|
|
3515
|
+
currentPageIndex: currentIndex,
|
|
3516
|
+
totalPages,
|
|
3517
|
+
isFirstPage: isFirstVisiblePage,
|
|
3518
|
+
isLastPage: isLastVisiblePage,
|
|
3519
|
+
};
|
|
3520
|
+
}, /* @ts-ignore */
|
|
3521
|
+
...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
|
|
3522
|
+
currentPageValid = computed(() => (this.stateManager?.isSchemaCurrent() ?? true) && this.isPageValid(this.currentPageIndex()), /* @ts-ignore */
|
|
3523
|
+
...(ngDevMode ? [{ debugName: "currentPageValid" }] : /* istanbul ignore next */ []));
|
|
3524
|
+
isPageValid(pageIndex) {
|
|
3525
|
+
const pages = this.pageFields();
|
|
3526
|
+
const form = this.form();
|
|
3527
|
+
if (pageIndex < 0 || pageIndex >= pages.length) {
|
|
3528
|
+
return true;
|
|
3529
|
+
}
|
|
3530
|
+
const leafKeys = collectLeafFieldKeys(pages[pageIndex].fields || []);
|
|
3531
|
+
for (const fieldKey of leafKeys) {
|
|
3532
|
+
const field = form[fieldKey];
|
|
3533
|
+
if (field && typeof field === 'function') {
|
|
3534
|
+
const fieldState = field();
|
|
3535
|
+
if (fieldState && typeof fieldState.valid === 'function' && !fieldState.valid()) {
|
|
3536
|
+
return false;
|
|
3537
|
+
}
|
|
3538
|
+
}
|
|
3539
|
+
}
|
|
3540
|
+
return true;
|
|
3541
|
+
}
|
|
3542
|
+
extendedFieldSignalContext = computed(() => ({
|
|
3543
|
+
...this.fieldSignalContext(),
|
|
3544
|
+
currentPageValid: this.currentPageValid,
|
|
3545
|
+
}), /* @ts-ignore */
|
|
3546
|
+
...(ngDevMode ? [{ debugName: "extendedFieldSignalContext" }] : /* istanbul ignore next */ []));
|
|
3547
|
+
preloadWindow = computed(() => {
|
|
3548
|
+
const perForm = this.formOptions?.()?.pagePreloadWindow;
|
|
3549
|
+
return clampWindowSize(perForm, this.globalPreloadWindow);
|
|
3550
|
+
}, /* @ts-ignore */
|
|
3551
|
+
...(ngDevMode ? [{ debugName: "preloadWindow" }] : /* istanbul ignore next */ []));
|
|
3552
|
+
pageRenderStates = computed(() => {
|
|
3553
|
+
const activeIndex = this.currentPageIndex();
|
|
3554
|
+
const hiddenStates = this.pageHiddenStates();
|
|
3555
|
+
const preloadWindow = this.preloadWindow();
|
|
3556
|
+
return this.pageFields().map((field, index) => ({
|
|
3557
|
+
field,
|
|
3558
|
+
index,
|
|
3559
|
+
hidden: hiddenStates[index],
|
|
3560
|
+
active: index === activeIndex,
|
|
3561
|
+
preload: index !== activeIndex && Math.abs(index - activeIndex) <= preloadWindow,
|
|
3562
|
+
}));
|
|
3563
|
+
}, /* @ts-ignore */
|
|
3564
|
+
...(ngDevMode ? [{ debugName: "pageRenderStates" }] : /* istanbul ignore next */ []));
|
|
3565
|
+
constructor() {
|
|
3566
|
+
this.setupEventListeners();
|
|
3567
|
+
const preloader = injectFormComponentPreloader();
|
|
3568
|
+
explicitEffect([this.pageRenderStates], ([states]) => {
|
|
3569
|
+
preloader.preloadFields(states.filter((page) => !page.hidden && (page.active || page.preload)).map((page) => page.field));
|
|
3570
|
+
});
|
|
3571
|
+
explicitEffect([this.schemaCurrent, this.pageFields], ([schemaCurrent]) => {
|
|
3572
|
+
const ownership = this.activePageOwnership();
|
|
3573
|
+
if (!schemaCurrent || ownership.initialized)
|
|
3574
|
+
return;
|
|
3575
|
+
this.activePageOwnership.set({ ...ownership, initialized: true, index: this.resolveInitialLanding() });
|
|
3576
|
+
});
|
|
3577
|
+
explicitEffect([this.state, this.visiblePageIndices], ([state, visibleIndices]) => {
|
|
3578
|
+
const currentVisiblePosition = visibleIndices.indexOf(state.currentPageIndex);
|
|
3579
|
+
if (currentVisiblePosition === -1 && visibleIndices.length > 0) {
|
|
3580
|
+
const nearest = this.findNearestVisiblePage(state.currentPageIndex, visibleIndices);
|
|
3581
|
+
if (nearest !== -1) {
|
|
3582
|
+
this.executePageChange(nearest);
|
|
3583
|
+
}
|
|
3584
|
+
}
|
|
3585
|
+
});
|
|
3586
|
+
}
|
|
3587
|
+
navigateToNextPage() {
|
|
3588
|
+
const disableWhenPageInvalid = this.formOptions?.()?.nextButton?.disableWhenPageInvalid ?? true;
|
|
3589
|
+
if (disableWhenPageInvalid && !this.currentPageValid()) {
|
|
3590
|
+
return {
|
|
3591
|
+
success: false,
|
|
3592
|
+
newPageIndex: this.state().currentPageIndex,
|
|
3593
|
+
error: 'Current page has invalid fields',
|
|
3594
|
+
};
|
|
3595
|
+
}
|
|
3596
|
+
const currentState = this.state();
|
|
3597
|
+
const visibleIndices = this.visiblePageIndices();
|
|
3598
|
+
if (currentState.isLastPage) {
|
|
3599
|
+
return {
|
|
3600
|
+
success: false,
|
|
3601
|
+
newPageIndex: currentState.currentPageIndex,
|
|
3602
|
+
error: 'Already on the last visible page',
|
|
3603
|
+
};
|
|
3604
|
+
}
|
|
3605
|
+
const currentVisiblePosition = visibleIndices.indexOf(currentState.currentPageIndex);
|
|
3606
|
+
if (currentVisiblePosition === -1 || currentVisiblePosition >= visibleIndices.length - 1) {
|
|
3607
|
+
return {
|
|
3608
|
+
success: false,
|
|
3609
|
+
newPageIndex: currentState.currentPageIndex,
|
|
3610
|
+
error: 'No next visible page available',
|
|
3611
|
+
};
|
|
3612
|
+
}
|
|
3613
|
+
const nextVisiblePageIndex = visibleIndices[currentVisiblePosition + 1];
|
|
3614
|
+
return this.executePageChange(nextVisiblePageIndex);
|
|
3615
|
+
}
|
|
3616
|
+
navigateToPreviousPage() {
|
|
3617
|
+
const currentState = this.state();
|
|
3618
|
+
const visibleIndices = this.visiblePageIndices();
|
|
3619
|
+
if (currentState.isFirstPage) {
|
|
3620
|
+
return {
|
|
3621
|
+
success: false,
|
|
3622
|
+
newPageIndex: currentState.currentPageIndex,
|
|
3623
|
+
error: 'Already on the first visible page',
|
|
3624
|
+
};
|
|
3625
|
+
}
|
|
3626
|
+
const currentVisiblePosition = visibleIndices.indexOf(currentState.currentPageIndex);
|
|
3627
|
+
if (currentVisiblePosition <= 0) {
|
|
3628
|
+
return {
|
|
3629
|
+
success: false,
|
|
3630
|
+
newPageIndex: currentState.currentPageIndex,
|
|
3631
|
+
error: 'No previous visible page available',
|
|
3632
|
+
};
|
|
3728
3633
|
}
|
|
3729
|
-
|
|
3730
|
-
|
|
3634
|
+
const prevVisiblePageIndex = visibleIndices[currentVisiblePosition - 1];
|
|
3635
|
+
return this.executePageChange(prevVisiblePageIndex);
|
|
3636
|
+
}
|
|
3637
|
+
navigateToPage(pageIndex, options) {
|
|
3638
|
+
const currentIndex = this.state().currentPageIndex;
|
|
3639
|
+
const visibleIndices = this.visiblePageIndices();
|
|
3640
|
+
if (pageIndex <= currentIndex || !visibleIndices.includes(pageIndex)) {
|
|
3641
|
+
return this.executePageChange(pageIndex);
|
|
3731
3642
|
}
|
|
3732
|
-
|
|
3733
|
-
this.
|
|
3643
|
+
if (options?.validate === false) {
|
|
3644
|
+
return this.executePageChange(pageIndex);
|
|
3734
3645
|
}
|
|
3735
|
-
|
|
3736
|
-
|
|
3646
|
+
const disableWhenPageInvalid = this.formOptions?.()?.nextButton?.disableWhenPageInvalid ?? true;
|
|
3647
|
+
if (!disableWhenPageInvalid) {
|
|
3648
|
+
return this.executePageChange(pageIndex);
|
|
3737
3649
|
}
|
|
3738
|
-
|
|
3739
|
-
this.
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
createFormSetupFromConfig(fields, mode, registry) {
|
|
3743
|
-
const normalizedFields = normalizeSimplifiedArrays(fields);
|
|
3744
|
-
validateFormConfig(normalizedFields, registry, this.logger);
|
|
3745
|
-
const source = this.deps.source?.() ?? 'inline';
|
|
3746
|
-
const { fields: sanitizedFields, warnings: addonWarnings } = walkAndValidateAddons(normalizedFields, registry, this.addonKindRegistry, source);
|
|
3747
|
-
const nextKeys = new Set();
|
|
3748
|
-
for (const w of addonWarnings) {
|
|
3749
|
-
const key = addonWarningKey(w);
|
|
3750
|
-
nextKeys.add(key);
|
|
3751
|
-
if (!this.lastAddonWarningKeys.has(key)) {
|
|
3752
|
-
this.logger.warn(formatAddonWarning(w));
|
|
3753
|
-
}
|
|
3650
|
+
const crossedPages = visibleIndices.filter((index) => index >= currentIndex && index < pageIndex);
|
|
3651
|
+
const firstInvalidPage = crossedPages.find((index) => !this.isPageValid(index));
|
|
3652
|
+
if (firstInvalidPage === undefined) {
|
|
3653
|
+
return this.executePageChange(pageIndex);
|
|
3754
3654
|
}
|
|
3755
|
-
|
|
3756
|
-
const validatedFields = sanitizedFields;
|
|
3757
|
-
const flattenedFields = this.fieldProcessors.memoizedFlattenFields(validatedFields, registry);
|
|
3758
|
-
const flattenedFieldsForRendering = this.fieldProcessors.memoizedFlattenFieldsForRendering(validatedFields, registry);
|
|
3759
|
-
const fieldsById = this.fieldProcessors.memoizedKeyBy(flattenedFields);
|
|
3760
|
-
const defaultValues = this.fieldProcessors.memoizedDefaultValues(fieldsById, registry);
|
|
3761
|
-
const fieldsToRender = mode === 'paged' ? [] : flattenedFieldsForRendering;
|
|
3655
|
+
this.executePageChange(firstInvalidPage);
|
|
3762
3656
|
return {
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
defaultValues,
|
|
3767
|
-
mode,
|
|
3768
|
-
registry,
|
|
3657
|
+
success: false,
|
|
3658
|
+
newPageIndex: firstInvalidPage,
|
|
3659
|
+
error: `Cannot navigate to page ${pageIndex}: page ${firstInvalidPage} has invalid fields`,
|
|
3769
3660
|
};
|
|
3770
3661
|
}
|
|
3771
|
-
|
|
3662
|
+
executePageChange(pageIndex) {
|
|
3663
|
+
const currentState = this.state();
|
|
3664
|
+
const totalPages = currentState.totalPages;
|
|
3665
|
+
if (pageIndex < 0 || pageIndex >= totalPages) {
|
|
3666
|
+
return {
|
|
3667
|
+
success: false,
|
|
3668
|
+
newPageIndex: currentState.currentPageIndex,
|
|
3669
|
+
error: `Invalid page index: ${pageIndex}. Valid range is 0 to ${totalPages - 1}`,
|
|
3670
|
+
};
|
|
3671
|
+
}
|
|
3672
|
+
const visibleIndices = this.visiblePageIndices();
|
|
3673
|
+
if (!visibleIndices.includes(pageIndex)) {
|
|
3674
|
+
return {
|
|
3675
|
+
success: false,
|
|
3676
|
+
newPageIndex: currentState.currentPageIndex,
|
|
3677
|
+
error: `Cannot navigate to hidden page at index ${pageIndex}. Visible pages: [${visibleIndices.join(', ')}]`,
|
|
3678
|
+
};
|
|
3679
|
+
}
|
|
3680
|
+
if (pageIndex === currentState.currentPageIndex) {
|
|
3681
|
+
return {
|
|
3682
|
+
success: true,
|
|
3683
|
+
newPageIndex: pageIndex,
|
|
3684
|
+
};
|
|
3685
|
+
}
|
|
3686
|
+
const previousIndex = currentState.currentPageIndex;
|
|
3687
|
+
this.setCurrentPageIndex(pageIndex);
|
|
3688
|
+
this.eventBus.dispatch(PageChangeEvent, pageIndex, totalPages, previousIndex);
|
|
3772
3689
|
return {
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
defaultValues: {},
|
|
3776
|
-
mode: 'non-paged',
|
|
3777
|
-
registry,
|
|
3690
|
+
success: true,
|
|
3691
|
+
newPageIndex: pageIndex,
|
|
3778
3692
|
};
|
|
3779
3693
|
}
|
|
3780
|
-
|
|
3781
|
-
|
|
3694
|
+
findNearestVisiblePage(currentIndex, visibleIndices) {
|
|
3695
|
+
let nearest = -1;
|
|
3696
|
+
let minDistance = Infinity;
|
|
3697
|
+
for (const idx of visibleIndices) {
|
|
3698
|
+
const distance = Math.abs(idx - currentIndex);
|
|
3699
|
+
if (distance < minDistance || (distance === minDistance && idx > nearest)) {
|
|
3700
|
+
minDistance = distance;
|
|
3701
|
+
nearest = idx;
|
|
3702
|
+
}
|
|
3703
|
+
}
|
|
3704
|
+
return nearest;
|
|
3782
3705
|
}
|
|
3783
|
-
|
|
3784
|
-
|
|
3706
|
+
setupEventListeners() {
|
|
3707
|
+
this.eventBus
|
|
3708
|
+
.on('next-page')
|
|
3709
|
+
.pipe(takeUntilDestroyed())
|
|
3710
|
+
.subscribe(() => {
|
|
3711
|
+
this.navigateToNextPage();
|
|
3712
|
+
});
|
|
3713
|
+
this.eventBus
|
|
3714
|
+
.on('previous-page')
|
|
3715
|
+
.pipe(takeUntilDestroyed())
|
|
3716
|
+
.subscribe(() => {
|
|
3717
|
+
this.navigateToPreviousPage();
|
|
3718
|
+
});
|
|
3719
|
+
explicitEffect([this.pendingPageRequest], ([request]) => {
|
|
3720
|
+
if (!request)
|
|
3721
|
+
return;
|
|
3722
|
+
this.stateManager?.pendingPageRequest.set(null);
|
|
3723
|
+
this.navigateToPage(request.index, request.options);
|
|
3724
|
+
});
|
|
3725
|
+
explicitEffect([this.state], ([state]) => this.eventBus.dispatch(PagerStateEvent, state));
|
|
3726
|
+
explicitEffect([this.activePageOwnership], ([ownership]) => this.stateManager?.activePageState.set(ownership));
|
|
3727
|
+
}
|
|
3728
|
+
evaluatePageHidden(page) {
|
|
3729
|
+
if (!page.logic || page.logic.length === 0) {
|
|
3730
|
+
return false;
|
|
3731
|
+
}
|
|
3732
|
+
const hiddenLogic = page.logic.filter((l) => l.type === 'hidden');
|
|
3733
|
+
if (hiddenLogic.length === 0) {
|
|
3734
|
+
return false;
|
|
3735
|
+
}
|
|
3736
|
+
for (const logic of hiddenLogic) {
|
|
3737
|
+
if (typeof logic.condition === 'boolean') {
|
|
3738
|
+
if (logic.condition) {
|
|
3739
|
+
return true;
|
|
3740
|
+
}
|
|
3741
|
+
continue;
|
|
3742
|
+
}
|
|
3743
|
+
const condition = logic.condition;
|
|
3744
|
+
const context = this.fieldContextRegistry.createDisplayOnlyContext(page.key || '', this.functionRegistry.getCustomFunctions());
|
|
3745
|
+
if (evaluateCondition(condition, context)) {
|
|
3746
|
+
return true;
|
|
3747
|
+
}
|
|
3748
|
+
}
|
|
3749
|
+
return false;
|
|
3750
|
+
}
|
|
3751
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: PageOrchestratorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3752
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", type: PageOrchestratorComponent, isStandalone: true, selector: "div[page-orchestrator]", inputs: { pageFields: { classPropertyName: "pageFields", publicName: "pageFields", isSignal: true, isRequired: true, transformFunction: null }, form: { classPropertyName: "form", publicName: "form", isSignal: true, isRequired: true, transformFunction: null }, fieldSignalContext: { classPropertyName: "fieldSignalContext", publicName: "fieldSignalContext", isSignal: true, isRequired: true, transformFunction: null } }, host: { properties: { "attr.data-current-page": "state().currentPageIndex", "attr.data-total-pages": "state().totalPages" }, classAttribute: "df-page-orchestrator" }, providers: [
|
|
3753
|
+
{
|
|
3754
|
+
provide: FIELD_SIGNAL_CONTEXT,
|
|
3755
|
+
useFactory: (orchestrator) => orchestrator.extendedFieldSignalContext(),
|
|
3756
|
+
deps: [PageOrchestratorComponent],
|
|
3757
|
+
},
|
|
3758
|
+
], ngImport: i0, template: `
|
|
3759
|
+
@for (page of pageRenderStates(); track page.field.key) {
|
|
3760
|
+
<!--
|
|
3761
|
+
Skip pages hidden by 'hidden' logic conditions entirely. FormStateManager
|
|
3762
|
+
derives schema/value/validators from config (not from mounted components),
|
|
3763
|
+
so unmounting a hidden page has no effect on form state — only on render +
|
|
3764
|
+
CD cost. This is the page-level equivalent of the existing field-level
|
|
3765
|
+
\`@if (!field.hidden())\` gate in page-field.component.ts.
|
|
3766
|
+
-->
|
|
3767
|
+
@if (!page.hidden) {
|
|
3768
|
+
@if (page.active || page.preload) {
|
|
3769
|
+
@defer (when page.active; on idle) {
|
|
3770
|
+
<section page-field [field]="page.field" [key]="page.field.key" [pageIndex]="page.index" [isVisible]="page.active"></section>
|
|
3771
|
+
} @placeholder {
|
|
3772
|
+
<div class="df-page-placeholder" [attr.data-page-index]="page.index" [attr.data-page-key]="page.field.key"></div>
|
|
3773
|
+
}
|
|
3774
|
+
} @else {
|
|
3775
|
+
<!--
|
|
3776
|
+
Pages outside the preload window remain lightweight placeholders.
|
|
3777
|
+
Form state derives from config, so leaving their components unmounted
|
|
3778
|
+
does not affect validity, derivations, or navigation. The active page
|
|
3779
|
+
renders directly; configured neighbours preload declaratively on idle.
|
|
3780
|
+
-->
|
|
3781
|
+
<div class="df-page-placeholder" [attr.data-page-index]="page.index" [attr.data-page-key]="page.field.key"></div>
|
|
3782
|
+
}
|
|
3783
|
+
}
|
|
3784
|
+
}
|
|
3785
|
+
`, isInline: true, styles: [":host{display:block;position:relative;width:100%}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, deferBlockDependencies: [() => [/* @ts-ignore */
|
|
3786
|
+
import('./ng-forge-dynamic-forms-page-field.component-DCdBvJ-h.mjs').then(m => m.default)]] });
|
|
3785
3787
|
}
|
|
3786
|
-
i0.ɵɵ
|
|
3787
|
-
|
|
3788
|
-
|
|
3788
|
+
i0.ɵɵngDeclareClassMetadataAsync({ minVersion: "18.0.0", version: "22.0.1", ngImport: i0, type: PageOrchestratorComponent, resolveDeferredDeps: () => [/* @ts-ignore */
|
|
3789
|
+
import('./ng-forge-dynamic-forms-page-field.component-DCdBvJ-h.mjs').then(m => m.default)], resolveMetadata: PageFieldComponent => ({ decorators: [{
|
|
3790
|
+
type: Component,
|
|
3791
|
+
args: [{ selector: 'div[page-orchestrator]', imports: [PageFieldComponent], template: `
|
|
3792
|
+
@for (page of pageRenderStates(); track page.field.key) {
|
|
3793
|
+
<!--
|
|
3794
|
+
Skip pages hidden by 'hidden' logic conditions entirely. FormStateManager
|
|
3795
|
+
derives schema/value/validators from config (not from mounted components),
|
|
3796
|
+
so unmounting a hidden page has no effect on form state — only on render +
|
|
3797
|
+
CD cost. This is the page-level equivalent of the existing field-level
|
|
3798
|
+
\`@if (!field.hidden())\` gate in page-field.component.ts.
|
|
3799
|
+
-->
|
|
3800
|
+
@if (!page.hidden) {
|
|
3801
|
+
@if (page.active || page.preload) {
|
|
3802
|
+
@defer (when page.active; on idle) {
|
|
3803
|
+
<section page-field [field]="page.field" [key]="page.field.key" [pageIndex]="page.index" [isVisible]="page.active"></section>
|
|
3804
|
+
} @placeholder {
|
|
3805
|
+
<div class="df-page-placeholder" [attr.data-page-index]="page.index" [attr.data-page-key]="page.field.key"></div>
|
|
3806
|
+
}
|
|
3807
|
+
} @else {
|
|
3808
|
+
<!--
|
|
3809
|
+
Pages outside the preload window remain lightweight placeholders.
|
|
3810
|
+
Form state derives from config, so leaving their components unmounted
|
|
3811
|
+
does not affect validity, derivations, or navigation. The active page
|
|
3812
|
+
renders directly; configured neighbours preload declaratively on idle.
|
|
3813
|
+
-->
|
|
3814
|
+
<div class="df-page-placeholder" [attr.data-page-index]="page.index" [attr.data-page-key]="page.field.key"></div>
|
|
3815
|
+
}
|
|
3816
|
+
}
|
|
3817
|
+
}
|
|
3818
|
+
`, host: {
|
|
3819
|
+
class: 'df-page-orchestrator',
|
|
3820
|
+
'[attr.data-current-page]': 'state().currentPageIndex',
|
|
3821
|
+
'[attr.data-total-pages]': 'state().totalPages',
|
|
3822
|
+
}, providers: [
|
|
3823
|
+
{
|
|
3824
|
+
provide: FIELD_SIGNAL_CONTEXT,
|
|
3825
|
+
useFactory: (orchestrator) => orchestrator.extendedFieldSignalContext(),
|
|
3826
|
+
deps: [PageOrchestratorComponent],
|
|
3827
|
+
},
|
|
3828
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;position:relative;width:100%}\n"] }]
|
|
3829
|
+
}], ctorParameters: () => [], propDecorators: { pageFields: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageFields", required: true }] }], form: [{ type: i0.Input, args: [{ isSignal: true, alias: "form", required: true }] }], fieldSignalContext: [{ type: i0.Input, args: [{ isSignal: true, alias: "fieldSignalContext", required: true }] }] } }) });
|
|
3789
3830
|
|
|
3790
3831
|
function traverseFieldsWithContext(fields, context, visitor, hooks) {
|
|
3791
3832
|
for (const field of fields) {
|
|
@@ -3864,7 +3905,7 @@ function createDerivationRenderGate() {
|
|
|
3864
3905
|
if (!has || engine() !== 'idle')
|
|
3865
3906
|
return;
|
|
3866
3907
|
engine.set('loading');
|
|
3867
|
-
pendingTasks.run(() => import('./ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-
|
|
3908
|
+
pendingTasks.run(() => import('./ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-BB8jAcY4.mjs')
|
|
3868
3909
|
.then(({ bootstrapDerivationOrchestrator }) => {
|
|
3869
3910
|
runInInjectionContext(injector, () => bootstrapDerivationOrchestrator());
|
|
3870
3911
|
engine.set('wired');
|
|
@@ -4024,6 +4065,7 @@ function coreProviders() {
|
|
|
4024
4065
|
deps: [FormStateManager],
|
|
4025
4066
|
},
|
|
4026
4067
|
FormIdPrefixService,
|
|
4068
|
+
FieldViewportObserver,
|
|
4027
4069
|
{
|
|
4028
4070
|
provide: FORM_ID_PREFIX,
|
|
4029
4071
|
useFactory: (svc) => svc.prefix,
|
|
@@ -4112,25 +4154,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
|
|
|
4112
4154
|
type: Injectable
|
|
4113
4155
|
}] });
|
|
4114
4156
|
|
|
4115
|
-
const FIELD_WINDOWING = new InjectionToken('FIELD_WINDOWING', {
|
|
4116
|
-
providedIn: 'root',
|
|
4117
|
-
factory: () => ({ enabled: false, eager: 12, placeholderHeight: '4rem' }),
|
|
4118
|
-
});
|
|
4119
|
-
|
|
4120
|
-
function resolveFieldWindowing(global, perForm) {
|
|
4121
|
-
if (perForm === undefined) {
|
|
4122
|
-
return global;
|
|
4123
|
-
}
|
|
4124
|
-
if (typeof perForm === 'boolean') {
|
|
4125
|
-
return { ...global, enabled: perForm };
|
|
4126
|
-
}
|
|
4127
|
-
return {
|
|
4128
|
-
enabled: true,
|
|
4129
|
-
eager: clampWindowSize(perForm.eager, global.eager),
|
|
4130
|
-
placeholderHeight: perForm.placeholderHeight ?? global.placeholderHeight,
|
|
4131
|
-
};
|
|
4132
|
-
}
|
|
4133
|
-
|
|
4134
4157
|
class DynamicForm {
|
|
4135
4158
|
config = input.required({ ...(ngDevMode ? { debugName: "config" } : /* istanbul ignore next */ {}), alias: 'dynamic-form' });
|
|
4136
4159
|
formOptions = input(undefined, /* @ts-ignore */
|
|
@@ -4171,22 +4194,30 @@ class DynamicForm {
|
|
|
4171
4194
|
submitting = this.stateManager.submitting;
|
|
4172
4195
|
fieldLoadingErrors = this.stateManager.fieldLoadingErrors;
|
|
4173
4196
|
derivationReady = inject(DERIVATION_RENDER_GATE);
|
|
4174
|
-
shouldRender = computed(() => this.stateManager.shouldRender() && this.derivationReady(), /* @ts-ignore */
|
|
4197
|
+
shouldRender = computed(() => this.stateManager.shouldRender() && this.stateManager.formSchemaReady() && this.derivationReady(), /* @ts-ignore */
|
|
4175
4198
|
...(ngDevMode ? [{ debugName: "shouldRender" }] : /* istanbul ignore next */ []));
|
|
4176
4199
|
resolvedFields = this.stateManager.resolvedFields;
|
|
4177
4200
|
fieldWindowing = computed(() => resolveFieldWindowing(this.globalFieldWindowing, this.effectiveFormOptions()?.fieldWindowing), /* @ts-ignore */
|
|
4178
4201
|
...(ngDevMode ? [{ debugName: "fieldWindowing" }] : /* istanbul ignore next */ []));
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4202
|
+
initializedComponents = signal(new Set(), /* @ts-ignore */
|
|
4203
|
+
...(ngDevMode ? [{ debugName: "initializedComponents" }] : /* istanbul ignore next */ []));
|
|
4204
|
+
initialPageReady = signal(false, /* @ts-ignore */
|
|
4205
|
+
...(ngDevMode ? [{ debugName: "initialPageReady" }] : /* istanbul ignore next */ []));
|
|
4206
|
+
initializationReady = computed(() => {
|
|
4207
|
+
const config = this.activeConfig();
|
|
4208
|
+
if (!config)
|
|
4209
|
+
return false;
|
|
4210
|
+
if (this.formModeDetection().mode === 'paged')
|
|
4211
|
+
return this.initialPageReady();
|
|
4212
|
+
const initialized = this.initializedComponents();
|
|
4213
|
+
const expected = [
|
|
4214
|
+
initializationComponentKey('dynamic-form', this.componentId),
|
|
4215
|
+
...collectInitializingContainerKeys(config.fields ?? []),
|
|
4216
|
+
];
|
|
4217
|
+
return expected.every((key) => initialized.has(key));
|
|
4182
4218
|
}, /* @ts-ignore */
|
|
4183
|
-
...(ngDevMode ? [{ debugName: "
|
|
4184
|
-
|
|
4185
|
-
eventBus: this.eventBus,
|
|
4186
|
-
totalComponentsCount: this.totalComponentsCount,
|
|
4187
|
-
injector: this.injector,
|
|
4188
|
-
componentId: this.componentId,
|
|
4189
|
-
});
|
|
4219
|
+
...(ngDevMode ? [{ debugName: "initializationReady" }] : /* istanbul ignore next */ []));
|
|
4220
|
+
initialized$ = toObservable(this.initializationReady, { injector: this.injector }).pipe(filter((ready) => ready), take(1), shareReplay({ bufferSize: 1, refCount: false }));
|
|
4190
4221
|
validityChange = outputFromObservable(toObservable(this.valid));
|
|
4191
4222
|
dirtyChange = outputFromObservable(toObservable(this.dirty));
|
|
4192
4223
|
submitted = outputFromObservable(this.stateManager.submitted$);
|
|
@@ -4194,12 +4225,32 @@ class DynamicForm {
|
|
|
4194
4225
|
cleared = outputFromObservable(this.eventBus.on('form-clear'));
|
|
4195
4226
|
events = outputFromObservable(this.eventBus.events$);
|
|
4196
4227
|
initialized = outputFromObservable(this.initialized$);
|
|
4228
|
+
activePageInitialized = outputFromObservable(this.eventBus.on('active-page-initialized'));
|
|
4197
4229
|
onPageChange = outputFromObservable(this.eventBus.on('page-change'));
|
|
4198
4230
|
onPageNavigationStateChange = outputFromObservable(this.eventBus.on('pager-state'));
|
|
4199
4231
|
constructor() {
|
|
4232
|
+
this.eventBus
|
|
4233
|
+
.on('component-initialized')
|
|
4234
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
4235
|
+
.subscribe((event) => {
|
|
4236
|
+
const key = initializationComponentKey(event.componentType, event.componentId);
|
|
4237
|
+
this.initializedComponents.update((current) => {
|
|
4238
|
+
if (current.has(key))
|
|
4239
|
+
return current;
|
|
4240
|
+
const next = new Set(current);
|
|
4241
|
+
next.add(key);
|
|
4242
|
+
return next;
|
|
4243
|
+
});
|
|
4244
|
+
});
|
|
4245
|
+
this.eventBus
|
|
4246
|
+
.on('active-page-initialized')
|
|
4247
|
+
.pipe(takeUntilDestroyed(this.destroyRef), take(1))
|
|
4248
|
+
.subscribe(() => this.initialPageReady.set(true));
|
|
4200
4249
|
this.dispatcher?.connect(this.eventBus);
|
|
4201
4250
|
this.destroyRef.onDestroy(() => this.dispatcher?.disconnect());
|
|
4202
4251
|
inject(FORM_INITIALIZER, { optional: true });
|
|
4252
|
+
const preload = injectFormComponentPreloader();
|
|
4253
|
+
explicitEffect([this.config], ([config]) => preload.preloadConfig(config));
|
|
4203
4254
|
this.setupEffects();
|
|
4204
4255
|
this.setupEventHandlers();
|
|
4205
4256
|
}
|
|
@@ -4221,8 +4272,8 @@ class DynamicForm {
|
|
|
4221
4272
|
this.eventBus.dispatch(ComponentInitializedEvent, 'dynamic-form', this.componentId);
|
|
4222
4273
|
}
|
|
4223
4274
|
});
|
|
4224
|
-
explicitEffect([this.resolvedFields, this.formModeDetection], ([
|
|
4225
|
-
if (mode === 'non-paged'
|
|
4275
|
+
explicitEffect([this.resolvedFields, this.formModeDetection, this.activeConfig], ([, { mode }, config]) => {
|
|
4276
|
+
if (config && mode === 'non-paged') {
|
|
4226
4277
|
afterNextRender(() => {
|
|
4227
4278
|
this.eventBus.dispatch(ComponentInitializedEvent, 'dynamic-form', this.componentId);
|
|
4228
4279
|
}, { injector: this.injector });
|
|
@@ -4258,7 +4309,7 @@ class DynamicForm {
|
|
|
4258
4309
|
});
|
|
4259
4310
|
}
|
|
4260
4311
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: DynamicForm, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4261
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", type: DynamicForm, isStandalone: true, selector: "form[dynamic-form]", inputs: { config: { classPropertyName: "config", publicName: "dynamic-form", isSignal: true, isRequired: true, transformFunction: null }, formOptions: { classPropertyName: "formOptions", publicName: "formOptions", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, source: { classPropertyName: "source", publicName: "source", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", validityChange: "validityChange", dirtyChange: "dirtyChange", submitted: "submitted", reset: "reset", cleared: "cleared", events: "events", initialized: "initialized", onPageChange: "onPageChange", onPageNavigationStateChange: "onPageNavigationStateChange" }, host: { attributes: { "novalidate": "" }, listeners: { "submit": "onNativeSubmit($event)" }, properties: { "class.disabled": "disabled()", "class.df-form-paged": "formModeDetection().mode === \"paged\"", "class.df-form-non-paged": "formModeDetection().mode === \"non-paged\"", "attr.data-form-mode": "formModeDetection().mode", "attr.id": "idPrefix() || null" }, classAttribute: "df-dynamic-form df-form" }, providers: [
|
|
4312
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", type: DynamicForm, isStandalone: true, selector: "form[dynamic-form]", inputs: { config: { classPropertyName: "config", publicName: "dynamic-form", isSignal: true, isRequired: true, transformFunction: null }, formOptions: { classPropertyName: "formOptions", publicName: "formOptions", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, source: { classPropertyName: "source", publicName: "source", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", validityChange: "validityChange", dirtyChange: "dirtyChange", submitted: "submitted", reset: "reset", cleared: "cleared", events: "events", initialized: "initialized", activePageInitialized: "activePageInitialized", onPageChange: "onPageChange", onPageNavigationStateChange: "onPageNavigationStateChange" }, host: { attributes: { "novalidate": "" }, listeners: { "submit": "onNativeSubmit($event)" }, properties: { "class.disabled": "disabled()", "class.df-form-paged": "formModeDetection().mode === \"paged\"", "class.df-form-non-paged": "formModeDetection().mode === \"non-paged\"", "attr.data-form-mode": "formModeDetection().mode", "attr.id": "idPrefix() || null" }, classAttribute: "df-dynamic-form df-form" }, providers: [
|
|
4262
4313
|
provideDynamicFormDI(),
|
|
4263
4314
|
DfFieldTemplateRegistry,
|
|
4264
4315
|
{ provide: DF_FIELD_TEMPLATES, useFactory: () => inject(DfFieldTemplateRegistry).map },
|
|
@@ -4270,12 +4321,8 @@ class DynamicForm {
|
|
|
4270
4321
|
}
|
|
4271
4322
|
@case ('non-paged') {
|
|
4272
4323
|
@for (field of resolvedFields(); track field.key; let i = $index) {
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
the directive's gate stops a same-CD-pass mount before [formField] can warn.
|
|
4276
|
-
Don't dedupe one without the other — see DfFieldOutlet.renderReady. -->
|
|
4277
|
-
@if (!field.hidden()) {
|
|
4278
|
-
@if (windowsField(field, i)) {
|
|
4324
|
+
@if (windowsField(field, i)) {
|
|
4325
|
+
@if (!field.hidden()) {
|
|
4279
4326
|
@defer (on viewport) {
|
|
4280
4327
|
<ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
|
|
4281
4328
|
} @placeholder {
|
|
@@ -4286,9 +4333,11 @@ class DynamicForm {
|
|
|
4286
4333
|
aria-hidden="true"
|
|
4287
4334
|
></div>
|
|
4288
4335
|
}
|
|
4289
|
-
} @else {
|
|
4290
|
-
<ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
|
|
4291
4336
|
}
|
|
4337
|
+
} @else {
|
|
4338
|
+
<!-- DfFieldOutlet owns the hidden gate so an already-mounted field can be detached
|
|
4339
|
+
and restored without recreating its component and wrapper chain. -->
|
|
4340
|
+
<ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
|
|
4292
4341
|
}
|
|
4293
4342
|
}
|
|
4294
4343
|
}
|
|
@@ -4309,12 +4358,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
|
|
|
4309
4358
|
}
|
|
4310
4359
|
@case ('non-paged') {
|
|
4311
4360
|
@for (field of resolvedFields(); track field.key; let i = $index) {
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
the directive's gate stops a same-CD-pass mount before [formField] can warn.
|
|
4315
|
-
Don't dedupe one without the other — see DfFieldOutlet.renderReady. -->
|
|
4316
|
-
@if (!field.hidden()) {
|
|
4317
|
-
@if (windowsField(field, i)) {
|
|
4361
|
+
@if (windowsField(field, i)) {
|
|
4362
|
+
@if (!field.hidden()) {
|
|
4318
4363
|
@defer (on viewport) {
|
|
4319
4364
|
<ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
|
|
4320
4365
|
} @placeholder {
|
|
@@ -4325,9 +4370,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
|
|
|
4325
4370
|
aria-hidden="true"
|
|
4326
4371
|
></div>
|
|
4327
4372
|
}
|
|
4328
|
-
} @else {
|
|
4329
|
-
<ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
|
|
4330
4373
|
}
|
|
4374
|
+
} @else {
|
|
4375
|
+
<!-- DfFieldOutlet owns the hidden gate so an already-mounted field can be detached
|
|
4376
|
+
and restored without recreating its component and wrapper chain. -->
|
|
4377
|
+
<ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
|
|
4331
4378
|
}
|
|
4332
4379
|
}
|
|
4333
4380
|
}
|
|
@@ -4349,29 +4396,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
|
|
|
4349
4396
|
'[attr.id]': 'idPrefix() || null',
|
|
4350
4397
|
'(submit)': 'onNativeSubmit($event)',
|
|
4351
4398
|
}, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".df-form{display:grid;grid-template-columns:1fr;gap:var(--df-grid-row-gap, .5rem);width:100%}.df-form>*{grid-column:1/-1}.df-row{display:grid;grid-template-columns:repeat(var(--df-grid-columns, 12),1fr);gap:var(--df-grid-gap, .5rem);align-items:start;width:100%}.df-row>*:not([class*=df-col-]){grid-column:1/-1}.df-col-1{grid-column:span 1}.df-col-2{grid-column:span 2}.df-col-3{grid-column:span 3}.df-col-4{grid-column:span 4}.df-col-5{grid-column:span 5}.df-col-6{grid-column:span 6}.df-col-7{grid-column:span 7}.df-col-8{grid-column:span 8}.df-col-9{grid-column:span 9}.df-col-10{grid-column:span 10}.df-col-11{grid-column:span 11}.df-col-12{grid-column:span 12}.df-col-auto{grid-column:span auto;width:auto}.df-col-full{grid-column:1/-1}.df-col-start-1{grid-column-start:1}.df-col-start-2{grid-column-start:2}.df-col-start-3{grid-column-start:3}.df-col-start-4{grid-column-start:4}.df-col-start-5{grid-column-start:5}.df-col-start-6{grid-column-start:6}.df-col-start-7{grid-column-start:7}.df-col-start-8{grid-column-start:8}.df-col-start-9{grid-column-start:9}.df-col-start-10{grid-column-start:10}.df-col-start-11{grid-column-start:11}.df-col-start-12{grid-column-start:12}.df-col-end-1{grid-column-end:1}.df-col-end-2{grid-column-end:2}.df-col-end-3{grid-column-end:3}.df-col-end-4{grid-column-end:4}.df-col-end-5{grid-column-end:5}.df-col-end-6{grid-column-end:6}.df-col-end-7{grid-column-end:7}.df-col-end-8{grid-column-end:8}.df-col-end-9{grid-column-end:9}.df-col-end-10{grid-column-end:10}.df-col-end-11{grid-column-end:11}.df-col-end-12{grid-column-end:12}.df-col-end-13{grid-column-end:13}@media(max-width:576px){.df-form{--df-grid-gap: var(--df-grid-gap-sm, .5rem);--df-grid-row-gap: var(--df-grid-row-gap-sm, .5rem)}.df-row{grid-template-columns:1fr}.df-row>*{grid-column:1/-1!important}.df-row.df-row-mobile-keep-cols{grid-template-columns:repeat(var(--df-grid-columns, 12),1fr)}.df-row.df-row-mobile-keep-cols>*{grid-column:revert!important}}@media(min-width:577px)and (max-width:768px){.df-form{--df-grid-gap: var(--df-grid-gap-md, .5rem);--df-grid-row-gap: var(--df-grid-row-gap-md, .5rem)}.df-row{--df-grid-columns: 6}.df-col-sm-1{grid-column:span 1}.df-col-sm-2{grid-column:span 2}.df-col-sm-3{grid-column:span 3}.df-col-sm-4{grid-column:span 4}.df-col-sm-5{grid-column:span 5}.df-col-sm-6{grid-column:span 6}.df-col-sm-full{grid-column:1/-1}}@media(min-width:769px)and (max-width:992px){.df-form{--df-grid-gap: var(--df-grid-gap-lg, .5rem);--df-grid-row-gap: var(--df-grid-row-gap-lg, .5rem)}.df-col-md-1{grid-column:span 1}.df-col-md-2{grid-column:span 2}.df-col-md-3{grid-column:span 3}.df-col-md-4{grid-column:span 4}.df-col-md-5{grid-column:span 5}.df-col-md-6{grid-column:span 6}.df-col-md-7{grid-column:span 7}.df-col-md-8{grid-column:span 8}.df-col-md-9{grid-column:span 9}.df-col-md-10{grid-column:span 10}.df-col-md-11{grid-column:span 11}.df-col-md-12{grid-column:span 12}.df-col-md-full{grid-column:1/-1}}@media(min-width:993px){.df-form{--df-grid-gap: var(--df-grid-gap-xl, .5rem);--df-grid-row-gap: var(--df-grid-row-gap-xl, .5rem)}.df-col-lg-1{grid-column:span 1}.df-col-lg-2{grid-column:span 2}.df-col-lg-3{grid-column:span 3}.df-col-lg-4{grid-column:span 4}.df-col-lg-5{grid-column:span 5}.df-col-lg-6{grid-column:span 6}.df-col-lg-7{grid-column:span 7}.df-col-lg-8{grid-column:span 8}.df-col-lg-9{grid-column:span 9}.df-col-lg-10{grid-column:span 10}.df-col-lg-11{grid-column:span 11}.df-col-lg-12{grid-column:span 12}.df-col-lg-full{grid-column:1/-1}}.df-gap-none{--df-grid-gap: 0}.df-gap-xs{--df-grid-gap: .25rem}.df-gap-sm{--df-grid-gap: .5rem}.df-gap-md{--df-grid-gap: 1rem}.df-gap-lg{--df-grid-gap: 1.5rem}.df-gap-xl{--df-grid-gap: 2rem}.df-row-gap-none{--df-grid-row-gap: 0}.df-row-gap-xs{--df-grid-row-gap: .25rem}.df-row-gap-sm{--df-grid-row-gap: .5rem}.df-row-gap-md{--df-grid-row-gap: 1rem}.df-row-gap-lg{--df-grid-row-gap: 1.5rem}.df-row-gap-xl{--df-grid-row-gap: 2rem}.df-field{display:block;width:100%;min-width:0;overflow:hidden;margin:0}.df-group,.df-page{display:block;width:100%}.df-form.disabled,.df-row.disabled,.df-field.disabled{opacity:.6;pointer-events:none}.df-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.df-sr-only-focusable:focus,.df-sr-only-focusable:active{position:static;width:auto;height:auto;padding:inherit;margin:inherit;overflow:visible;clip:auto;white-space:normal}.df-live-region{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.df-form :focus-visible{outline:2px solid var(--df-focus-color, #005fcc);outline-offset:2px}.df-group:focus-within{outline:1px dashed var(--df-focus-color, #005fcc);outline-offset:4px}:host{display:grid;grid-template-columns:1fr;gap:var(--df-grid-row-gap, .5rem);width:100%}:host>*{grid-column:1/-1}:host.df-form-spacing-custom{--df-grid-row-gap: var(--df-form-custom-spacing, 1.5rem)}:host.df-form-gap-none{--df-grid-row-gap: 0}:host.df-form-gap-xs{--df-grid-row-gap: .25rem}:host.df-form-gap-sm{--df-grid-row-gap: .5rem}:host.df-form-gap-md{--df-grid-row-gap: 1rem}:host.df-form-gap-lg{--df-grid-row-gap: 1.5rem}:host.df-form-gap-xl{--df-grid-row-gap: 2rem}:host.df-form-paged{gap:0}.df-field-placeholder{display:block}\n"] }]
|
|
4352
|
-
}], ctorParameters: () => [], propDecorators: { config: [{ type: i0.Input, args: [{ isSignal: true, alias: "dynamic-form", required: true }] }], formOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "formOptions", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], source: [{ type: i0.Input, args: [{ isSignal: true, alias: "source", required: false }] }], _projectedTemplates: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => DfTemplate), { isSignal: true }] }], validityChange: [{ type: i0.Output, args: ["validityChange"] }], dirtyChange: [{ type: i0.Output, args: ["dirtyChange"] }], submitted: [{ type: i0.Output, args: ["submitted"] }], reset: [{ type: i0.Output, args: ["reset"] }], cleared: [{ type: i0.Output, args: ["cleared"] }], events: [{ type: i0.Output, args: ["events"] }], initialized: [{ type: i0.Output, args: ["initialized"] }], onPageChange: [{ type: i0.Output, args: ["onPageChange"] }], onPageNavigationStateChange: [{ type: i0.Output, args: ["onPageNavigationStateChange"] }] } });
|
|
4353
|
-
function countContainersRecursive(fields) {
|
|
4354
|
-
let count = 0;
|
|
4355
|
-
for (const field of fields) {
|
|
4356
|
-
if (isContainerField(field)) {
|
|
4357
|
-
count += 1;
|
|
4358
|
-
if (hasChildFields(field)) {
|
|
4359
|
-
const children = field.fields;
|
|
4360
|
-
if (Array.isArray(children)) {
|
|
4361
|
-
for (const child of children) {
|
|
4362
|
-
if (Array.isArray(child)) {
|
|
4363
|
-
count += countContainersRecursive(child);
|
|
4364
|
-
}
|
|
4365
|
-
else if (child != null && isContainerField(child)) {
|
|
4366
|
-
count += countContainersRecursive([child]);
|
|
4367
|
-
}
|
|
4368
|
-
}
|
|
4369
|
-
}
|
|
4370
|
-
}
|
|
4371
|
-
}
|
|
4372
|
-
}
|
|
4373
|
-
return count;
|
|
4374
|
-
}
|
|
4399
|
+
}], ctorParameters: () => [], propDecorators: { config: [{ type: i0.Input, args: [{ isSignal: true, alias: "dynamic-form", required: true }] }], formOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "formOptions", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], source: [{ type: i0.Input, args: [{ isSignal: true, alias: "source", required: false }] }], _projectedTemplates: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => DfTemplate), { isSignal: true }] }], validityChange: [{ type: i0.Output, args: ["validityChange"] }], dirtyChange: [{ type: i0.Output, args: ["dirtyChange"] }], submitted: [{ type: i0.Output, args: ["submitted"] }], reset: [{ type: i0.Output, args: ["reset"] }], cleared: [{ type: i0.Output, args: ["cleared"] }], events: [{ type: i0.Output, args: ["events"] }], initialized: [{ type: i0.Output, args: ["initialized"] }], activePageInitialized: [{ type: i0.Output, args: ["activePageInitialized"] }], onPageChange: [{ type: i0.Output, args: ["onPageChange"] }], onPageNavigationStateChange: [{ type: i0.Output, args: ["onPageNavigationStateChange"] }] } });
|
|
4375
4400
|
|
|
4376
4401
|
const BUILT_IN_ADDON_TYPES = [
|
|
4377
4402
|
{
|
|
@@ -4419,28 +4444,28 @@ const DISPLAY_FIELD_TYPES_BASE = {
|
|
|
4419
4444
|
const BUILT_IN_FIELDS = [
|
|
4420
4445
|
{
|
|
4421
4446
|
name: 'row',
|
|
4422
|
-
loadComponent: () => import('./ng-forge-dynamic-forms-container-field.component-
|
|
4447
|
+
loadComponent: () => import('./ng-forge-dynamic-forms-container-field.component-DfQ0y-77.mjs'),
|
|
4423
4448
|
mapper: rowFieldMapper,
|
|
4424
4449
|
valueHandling: 'flatten',
|
|
4425
4450
|
...CONTAINER_FIELD_TYPES_BASE,
|
|
4426
4451
|
},
|
|
4427
4452
|
{
|
|
4428
4453
|
name: 'group',
|
|
4429
|
-
loadComponent: () => import('./ng-forge-dynamic-forms-group-field.component-
|
|
4454
|
+
loadComponent: () => import('./ng-forge-dynamic-forms-group-field.component-DxHD5I8E.mjs'),
|
|
4430
4455
|
mapper: groupFieldMapper,
|
|
4431
4456
|
valueHandling: 'include',
|
|
4432
4457
|
...CONTAINER_FIELD_TYPES_BASE,
|
|
4433
4458
|
},
|
|
4434
4459
|
{
|
|
4435
4460
|
name: 'array',
|
|
4436
|
-
loadComponent: () => import('./ng-forge-dynamic-forms-array-field.component-
|
|
4461
|
+
loadComponent: () => import('./ng-forge-dynamic-forms-array-field.component-6yyDriVB.mjs'),
|
|
4437
4462
|
mapper: arrayFieldMapper,
|
|
4438
4463
|
valueHandling: 'include',
|
|
4439
4464
|
...CONTAINER_FIELD_TYPES_BASE,
|
|
4440
4465
|
},
|
|
4441
4466
|
{
|
|
4442
4467
|
name: 'page',
|
|
4443
|
-
loadComponent: () => import('./ng-forge-dynamic-forms-page-field.component-
|
|
4468
|
+
loadComponent: () => import('./ng-forge-dynamic-forms-page-field.component-DCdBvJ-h.mjs'),
|
|
4444
4469
|
mapper: pageFieldMapper,
|
|
4445
4470
|
valueHandling: 'flatten',
|
|
4446
4471
|
...CONTAINER_FIELD_TYPES_BASE,
|
|
@@ -4458,7 +4483,7 @@ const BUILT_IN_FIELDS = [
|
|
|
4458
4483
|
},
|
|
4459
4484
|
{
|
|
4460
4485
|
name: 'container',
|
|
4461
|
-
loadComponent: () => import('./ng-forge-dynamic-forms-container-field.component-
|
|
4486
|
+
loadComponent: () => import('./ng-forge-dynamic-forms-container-field.component-DfQ0y-77.mjs'),
|
|
4462
4487
|
mapper: containerFieldMapper,
|
|
4463
4488
|
valueHandling: 'flatten',
|
|
4464
4489
|
...CONTAINER_FIELD_TYPES_BASE,
|
|
@@ -4553,7 +4578,7 @@ function provideDynamicForm(...items) {
|
|
|
4553
4578
|
const logger = inject(DynamicFormLogger);
|
|
4554
4579
|
const registry = new Map();
|
|
4555
4580
|
fields.forEach((fieldType) => {
|
|
4556
|
-
|
|
4581
|
+
if (DEV_MODE && registry.has(fieldType.name)) {
|
|
4557
4582
|
logger.warn(`Field type "${fieldType.name}" is already registered. Overwriting.`);
|
|
4558
4583
|
}
|
|
4559
4584
|
registry.set(fieldType.name, fieldType);
|
|
@@ -4581,13 +4606,15 @@ function provideDynamicForm(...items) {
|
|
|
4581
4606
|
const registry = new Map();
|
|
4582
4607
|
const builtInNames = new Set(BUILT_IN_WRAPPERS.map((w) => w.wrapperName));
|
|
4583
4608
|
BUILT_IN_WRAPPERS.forEach((wrapperType) => registry.set(wrapperType.wrapperName, wrapperType));
|
|
4584
|
-
|
|
4609
|
+
const seenCustom = DEV_MODE ? new Set() : null;
|
|
4585
4610
|
customWrappers.forEach((wrapperType) => {
|
|
4586
4611
|
const name = wrapperType.wrapperName;
|
|
4587
|
-
if (seenCustom
|
|
4588
|
-
|
|
4612
|
+
if (seenCustom) {
|
|
4613
|
+
if (seenCustom.has(name) && !builtInNames.has(name)) {
|
|
4614
|
+
logger.warn(`Wrapper type "${name}" is already registered. Overwriting.`);
|
|
4615
|
+
}
|
|
4616
|
+
seenCustom.add(name);
|
|
4589
4617
|
}
|
|
4590
|
-
seenCustom.add(name);
|
|
4591
4618
|
registry.set(name, wrapperType);
|
|
4592
4619
|
});
|
|
4593
4620
|
return registry;
|
|
@@ -4619,7 +4646,7 @@ function provideDynamicForm(...items) {
|
|
|
4619
4646
|
registry.set(def.type, def);
|
|
4620
4647
|
}
|
|
4621
4648
|
for (const def of contributed) {
|
|
4622
|
-
|
|
4649
|
+
if (DEV_MODE && registry.has(def.type)) {
|
|
4623
4650
|
logger.warn(`Addon type "${def.type}" is already registered. Overwriting.`);
|
|
4624
4651
|
}
|
|
4625
4652
|
registry.set(def.type, def);
|
|
@@ -4677,10 +4704,18 @@ function withFieldWindowing(config) {
|
|
|
4677
4704
|
enabled: true,
|
|
4678
4705
|
eager: clampWindowSize(config?.eager, 12),
|
|
4679
4706
|
placeholderHeight: config?.placeholderHeight ?? '4rem',
|
|
4707
|
+
park: resolveFieldParking({ enabled: true, margin: '100%' }, config?.park),
|
|
4680
4708
|
};
|
|
4681
4709
|
return createFeature('field-windowing', [{ provide: FIELD_WINDOWING, useValue: resolved }]);
|
|
4682
4710
|
}
|
|
4683
4711
|
|
|
4712
|
+
const VALIDATION_EXECUTION_DEFAULTS = new InjectionToken('VALIDATION_EXECUTION_DEFAULTS', {
|
|
4713
|
+
providedIn: 'root',
|
|
4714
|
+
factory: () => ({
|
|
4715
|
+
validateWhenHidden: false,
|
|
4716
|
+
}),
|
|
4717
|
+
});
|
|
4718
|
+
|
|
4684
4719
|
function withValidationExecutionDefaults(config) {
|
|
4685
4720
|
const resolved = {
|
|
4686
4721
|
validateWhenHidden: config?.validateWhenHidden ?? false,
|
|
@@ -4871,6 +4906,16 @@ function sanitizeFormConfigPure(config, fieldRegistry, typeRegistry, options = {
|
|
|
4871
4906
|
return { sanitized: { ...config, fields: fields }, warnings };
|
|
4872
4907
|
}
|
|
4873
4908
|
|
|
4909
|
+
class ActivePageInitializedEvent {
|
|
4910
|
+
pageIndex;
|
|
4911
|
+
pageKey;
|
|
4912
|
+
type = 'active-page-initialized';
|
|
4913
|
+
constructor(pageIndex, pageKey) {
|
|
4914
|
+
this.pageIndex = pageIndex;
|
|
4915
|
+
this.pageKey = pageKey;
|
|
4916
|
+
}
|
|
4917
|
+
}
|
|
4918
|
+
|
|
4874
4919
|
class GoToPageEvent {
|
|
4875
4920
|
pageIndex;
|
|
4876
4921
|
options;
|
|
@@ -4998,5 +5043,5 @@ function arrayEvent(arrayKey) {
|
|
|
4998
5043
|
|
|
4999
5044
|
|
|
5000
5045
|
|
|
5001
|
-
export {
|
|
5002
|
-
//# sourceMappingURL=ng-forge-dynamic-forms-ng-forge-dynamic-forms-
|
|
5046
|
+
export { arrayEvent as $, ArrayItemRegistryService as A, BUILT_IN_FIELDS as B, CONTAINER_FIELD_PROCESSORS as C, DfFieldOutlet as D, DfTemplate as E, FIELD_WINDOWING as F, DynamicForm as G, EventDispatcher as H, FormClearEvent as I, FormResetEvent as J, FormSubmitEvent as K, GoToPageEvent as L, InsertArrayItemEvent as M, MoveArrayItemEvent as N, NextPageEvent as O, PROPERTY_OVERRIDE_STORE as P, PageChangeEvent as Q, PagerStateEvent as R, SchemaRegistryService as S, PopArrayItemEvent as T, PrependArrayItemEvent as U, VALIDATION_EXECUTION_DEFAULTS as V, PreviousPageEvent as W, RemoveAtIndexEvent as X, ShiftArrayItemEvent as Y, TemplateAddonComponent as Z, TextAddonComponent as _, createFieldResolutionPipe as a, createWrappers as a0, isWrappersBundle as a1, provideDynamicForm as a2, sanitizeFormConfig as a3, withAddonActions as a4, withCustomAddon as a5, withEventFormValue as a6, withFieldWindowing as a7, withLegacyStatusClasses as a8, withLoggerConfig as a9, withPagePreload as aa, withValidationExecutionDefaults as ab, withValueExclusionDefaults as ac, wrapperProps as ad, isSameWrapperChain as b, computeContainerHostClasses as c, initializationComponentPath as d, createWrapperChainController as e, createHiddenSignal as f, createRenderReadySignal as g, getNormalizedArrayMetadata as h, injectFieldRegistry as i, emitComponentInitialized as j, getFieldDefaultValue as k, resolveFieldWindowing as l, mapFieldToInputs as m, collectInitializingContainerKeys as n, ActivePageInitializedEvent as o, initializationComponentKey as p, DEV_MODE as q, resolveWrappers as r, setupContainerInitEffect as s, traverseFieldsWithContext as t, DERIVATION_WARNING_TRACKER as u, FormStateManager as v, DERIVATION_LOG_CONFIG as w, AppendArrayItemEvent as x, ComponentAddonComponent as y, ConsoleLogger as z };
|
|
5047
|
+
//# sourceMappingURL=ng-forge-dynamic-forms-ng-forge-dynamic-forms-tA8ZkP8i.mjs.map
|