@ng-forge/dynamic-forms 1.2.0-next.1 → 1.2.0-next.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -11
- package/fesm2022/{ng-forge-dynamic-forms-array-field.component-BVnXnaNw.mjs → ng-forge-dynamic-forms-array-field.component-CSCKq_1K.mjs} +9 -13
- package/fesm2022/ng-forge-dynamic-forms-array-field.component-CSCKq_1K.mjs.map +1 -0
- package/fesm2022/{ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-CkqvXXs_.mjs → ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-CTOTwzzw.mjs} +29 -7
- package/fesm2022/ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-CTOTwzzw.mjs.map +1 -0
- package/fesm2022/ng-forge-dynamic-forms-bootstrap-web-mcp-BtjqeJOj.mjs +1054 -0
- package/fesm2022/ng-forge-dynamic-forms-bootstrap-web-mcp-BtjqeJOj.mjs.map +1 -0
- package/fesm2022/{ng-forge-dynamic-forms-container-field.component-CM1pY9v5.mjs → ng-forge-dynamic-forms-container-field.component-Dc3ROhHL.mjs} +14 -17
- package/fesm2022/ng-forge-dynamic-forms-container-field.component-Dc3ROhHL.mjs.map +1 -0
- package/fesm2022/{ng-forge-dynamic-forms-css-wrapper.component-D_yRuLpu.mjs → ng-forge-dynamic-forms-css-wrapper.component-BzgX8lAD.mjs} +5 -5
- package/fesm2022/ng-forge-dynamic-forms-css-wrapper.component-BzgX8lAD.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-field-errors-wrapper.component-CIKbWqYA.mjs +25 -0
- package/fesm2022/ng-forge-dynamic-forms-field-errors-wrapper.component-CIKbWqYA.mjs.map +1 -0
- package/fesm2022/ng-forge-dynamic-forms-form-schema.service-Dx5Dlb15.mjs +24 -0
- package/fesm2022/ng-forge-dynamic-forms-form-schema.service-Dx5Dlb15.mjs.map +1 -0
- package/fesm2022/{ng-forge-dynamic-forms-group-field.component-D4JhDffB.mjs → ng-forge-dynamic-forms-group-field.component-DAC3P9DC.mjs} +9 -13
- package/fesm2022/ng-forge-dynamic-forms-group-field.component-DAC3P9DC.mjs.map +1 -0
- package/fesm2022/ng-forge-dynamic-forms-integration.mjs +43 -105
- package/fesm2022/ng-forge-dynamic-forms-integration.mjs.map +1 -1
- package/fesm2022/ng-forge-dynamic-forms-internal.mjs +290 -116
- package/fesm2022/ng-forge-dynamic-forms-internal.mjs.map +1 -1
- package/fesm2022/{ng-forge-dynamic-forms-ng-forge-dynamic-forms-ZiNaCWHl.mjs → ng-forge-dynamic-forms-ng-forge-dynamic-forms-CXQe_5td.mjs} +1871 -1632
- package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-CXQe_5td.mjs.map +1 -0
- package/fesm2022/{ng-forge-dynamic-forms-page-field.component-Wbz6S8iH.mjs → ng-forge-dynamic-forms-page-field.component-DPKJcwUO.mjs} +57 -16
- package/fesm2022/ng-forge-dynamic-forms-page-field.component-DPKJcwUO.mjs.map +1 -0
- package/fesm2022/{ng-forge-dynamic-forms-row-wrapper.component-BBWmqQA_.mjs → ng-forge-dynamic-forms-row-wrapper.component-DMEeKZny.mjs} +5 -5
- package/fesm2022/ng-forge-dynamic-forms-row-wrapper.component-DMEeKZny.mjs.map +1 -0
- package/fesm2022/ng-forge-dynamic-forms-schema-builder-BvDTzfdM.mjs +570 -0
- package/fesm2022/ng-forge-dynamic-forms-schema-builder-BvDTzfdM.mjs.map +1 -0
- package/fesm2022/{ng-forge-dynamic-forms-text-field.component-C-f80sbk.mjs → ng-forge-dynamic-forms-text-field.component-BB-jutBq.mjs} +4 -4
- package/fesm2022/{ng-forge-dynamic-forms-text-field.component-C-f80sbk.mjs.map → ng-forge-dynamic-forms-text-field.component-BB-jutBq.mjs.map} +1 -1
- package/fesm2022/ng-forge-dynamic-forms.mjs +2 -2
- package/package.json +2 -2
- package/types/ng-forge-dynamic-forms-integration.d.ts +7 -31
- package/types/ng-forge-dynamic-forms-internal.d.ts +669 -308
- package/types/ng-forge-dynamic-forms.d.ts +287 -33
- package/fesm2022/ng-forge-dynamic-forms-array-field.component-BVnXnaNw.mjs.map +0 -1
- package/fesm2022/ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-CkqvXXs_.mjs.map +0 -1
- package/fesm2022/ng-forge-dynamic-forms-container-field.component-CM1pY9v5.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-css-wrapper.component-D_yRuLpu.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-D4JhDffB.mjs.map +0 -1
- package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-ZiNaCWHl.mjs.map +0 -1
- package/fesm2022/ng-forge-dynamic-forms-page-field.component-Wbz6S8iH.mjs.map +0 -1
- package/fesm2022/ng-forge-dynamic-forms-row-wrapper.component-BBWmqQA_.mjs.map +0 -1
|
@@ -1,15 +1,135 @@
|
|
|
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, defer, tap, 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.1.5", ngImport: i0, type: FieldViewportObserver, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
124
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: FieldViewportObserver });
|
|
125
|
+
}
|
|
126
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: FieldViewportObserver, decorators: [{
|
|
127
|
+
type: Injectable
|
|
128
|
+
}], ctorParameters: () => [] });
|
|
129
|
+
|
|
12
130
|
const EMPTY_WRAPPERS = Object.freeze([]);
|
|
131
|
+
const FIELD_ERRORS_WRAPPER = 'field-errors';
|
|
132
|
+
const FIELD_ERRORS_CONFIG = Object.freeze({ type: FIELD_ERRORS_WRAPPER });
|
|
13
133
|
function isSameWrapperChain(a, b) {
|
|
14
134
|
if (a === b)
|
|
15
135
|
return true;
|
|
@@ -21,19 +141,30 @@ function isSameWrapperChain(a, b) {
|
|
|
21
141
|
}
|
|
22
142
|
return true;
|
|
23
143
|
}
|
|
24
|
-
function resolveWrappers(field, defaultWrappers, autoAssociations) {
|
|
25
|
-
|
|
144
|
+
function resolveWrappers(field, defaultWrappers, autoAssociations, wrapperRegistry) {
|
|
145
|
+
if (field.wrappers === null) {
|
|
26
146
|
return EMPTY_WRAPPERS;
|
|
27
147
|
}
|
|
28
148
|
const autoWrappers = field.skipAutoWrappers ? EMPTY_WRAPPERS : (autoAssociations.get(field.type) ?? EMPTY_WRAPPERS);
|
|
29
149
|
const defaults = field.skipDefaultWrappers ? EMPTY_WRAPPERS : (defaultWrappers ?? EMPTY_WRAPPERS);
|
|
30
150
|
const fieldLevel = field.wrappers ?? EMPTY_WRAPPERS;
|
|
31
|
-
|
|
32
|
-
|
|
151
|
+
const composed = [...autoWrappers, ...defaults, ...fieldLevel];
|
|
152
|
+
if (needsErrorWrapper(field, composed, wrapperRegistry)) {
|
|
153
|
+
composed.push(FIELD_ERRORS_CONFIG);
|
|
33
154
|
}
|
|
34
|
-
return
|
|
155
|
+
return composed.length === 0 ? EMPTY_WRAPPERS : composed;
|
|
156
|
+
}
|
|
157
|
+
const VALIDATABLE_CONTAINERS = new Set(['group', 'array']);
|
|
158
|
+
function needsErrorWrapper(field, composed, wrapperRegistry) {
|
|
159
|
+
if (!VALIDATABLE_CONTAINERS.has(field.type))
|
|
160
|
+
return false;
|
|
161
|
+
if (!field.validators || field.validators.length === 0)
|
|
162
|
+
return false;
|
|
163
|
+
return !composed.some((w) => w.type === FIELD_ERRORS_WRAPPER || wrapperRegistry.get(w.type)?.rendersFieldErrors);
|
|
35
164
|
}
|
|
36
165
|
|
|
166
|
+
const DEV_MODE = typeof ngDevMode === 'undefined' || !!ngDevMode;
|
|
167
|
+
|
|
37
168
|
const inputNamesCache = new WeakMap();
|
|
38
169
|
function getDeclaredInputs(componentType) {
|
|
39
170
|
let inputs = inputNamesCache.get(componentType);
|
|
@@ -55,24 +186,33 @@ function hasDefaultExport(value) {
|
|
|
55
186
|
function resolveDefaultExport(result) {
|
|
56
187
|
return hasDefaultExport(result) ? result.default : result;
|
|
57
188
|
}
|
|
58
|
-
async function loadWrapperComponent(type, registry, cache) {
|
|
189
|
+
async function loadWrapperComponent(type, registry, cache, loadCache) {
|
|
59
190
|
const cached = cache.get(type);
|
|
60
191
|
if (cached)
|
|
61
192
|
return cached;
|
|
193
|
+
const pending = loadCache?.get(type);
|
|
194
|
+
if (pending)
|
|
195
|
+
return pending;
|
|
62
196
|
const definition = registry.get(type);
|
|
63
197
|
if (!definition)
|
|
64
198
|
return undefined;
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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) {
|
|
71
209
|
if (configs.length === 0)
|
|
72
210
|
return of([]);
|
|
73
|
-
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) => {
|
|
74
212
|
if (!component) {
|
|
75
|
-
|
|
213
|
+
if (DEV_MODE) {
|
|
214
|
+
logger.error(`Wrapper type '${config.type}' could not be loaded. Ensure it is registered via provideDynamicForm().`);
|
|
215
|
+
}
|
|
76
216
|
return null;
|
|
77
217
|
}
|
|
78
218
|
return { config, component };
|
|
@@ -112,9 +252,11 @@ function renderStep(slot, remaining, options, refs) {
|
|
|
112
252
|
ref.changeDetectorRef.detectChanges();
|
|
113
253
|
const inner = resolveInnerSlot(ref);
|
|
114
254
|
if (!inner) {
|
|
115
|
-
|
|
116
|
-
`
|
|
117
|
-
|
|
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
|
+
}
|
|
118
260
|
while (refs.length)
|
|
119
261
|
refs.pop().destroy();
|
|
120
262
|
return;
|
|
@@ -156,27 +298,34 @@ function createWrapperChainController(opts) {
|
|
|
156
298
|
const deps = injectChainDeps();
|
|
157
299
|
const state = createStateSignal(opts);
|
|
158
300
|
const mounted = { value: null };
|
|
301
|
+
const detached = { value: null };
|
|
159
302
|
let refs = [];
|
|
160
303
|
buildEmissionStream(state, deps)
|
|
161
304
|
.pipe(takeUntilDestroyed(deps.destroyRef))
|
|
162
305
|
.subscribe((emission) => {
|
|
163
306
|
try {
|
|
164
|
-
refs = applyEmission(emission, { opts, deps, mounted, refs });
|
|
307
|
+
refs = applyEmission(emission, { opts, deps, mounted, refs, detached });
|
|
165
308
|
}
|
|
166
309
|
catch (err) {
|
|
167
310
|
deps.logger.error('Wrapper chain render failed; tearing down partial state.', err);
|
|
168
311
|
opts.vcr().clear();
|
|
169
312
|
refs = [];
|
|
170
313
|
mounted.value = null;
|
|
314
|
+
detached.value?.destroy();
|
|
315
|
+
detached.value = null;
|
|
171
316
|
}
|
|
172
317
|
});
|
|
173
318
|
pushFieldInputsOnChange(opts, () => refs);
|
|
174
|
-
deps.destroyRef.onDestroy(() =>
|
|
319
|
+
deps.destroyRef.onDestroy(() => {
|
|
320
|
+
detached.value?.destroy();
|
|
321
|
+
opts.vcr().clear();
|
|
322
|
+
});
|
|
175
323
|
}
|
|
176
324
|
function injectChainDeps() {
|
|
177
325
|
return {
|
|
178
326
|
registry: inject(WRAPPER_REGISTRY),
|
|
179
327
|
cache: inject(WRAPPER_COMPONENT_CACHE),
|
|
328
|
+
loadCache: inject(WRAPPER_COMPONENT_LOAD_CACHE),
|
|
180
329
|
logger: inject(DynamicFormLogger),
|
|
181
330
|
destroyRef: inject(DestroyRef),
|
|
182
331
|
environmentInjector: inject(EnvironmentInjector),
|
|
@@ -200,16 +349,27 @@ function resolveLoadedWrappers(state, deps) {
|
|
|
200
349
|
const loaded = state.wrappers.map((config) => ({ config, component: deps.cache.get(config.type) }));
|
|
201
350
|
return of({ state, loaded });
|
|
202
351
|
}
|
|
203
|
-
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 })));
|
|
204
353
|
}
|
|
205
354
|
function applyEmission({ state, loaded }, ctx) {
|
|
206
355
|
const { opts, deps, mounted, refs } = ctx;
|
|
207
356
|
const vcr = opts.vcr();
|
|
208
357
|
const structurallyChanged = isStructurallyDifferent(mounted.value, state);
|
|
209
|
-
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;
|
|
210
366
|
return refs;
|
|
367
|
+
}
|
|
211
368
|
if (structurallyChanged && mounted.value !== null) {
|
|
212
|
-
|
|
369
|
+
const preservesDetachedOuterView = opts.beforeRebuild?.() === true;
|
|
370
|
+
if (ctx.detached.value && !preservesDetachedOuterView)
|
|
371
|
+
ctx.detached.value.destroy();
|
|
372
|
+
ctx.detached.value = null;
|
|
213
373
|
vcr.clear();
|
|
214
374
|
mounted.value = null;
|
|
215
375
|
}
|
|
@@ -246,7 +406,7 @@ function pushFieldInputsOnChange(opts, getRefs) {
|
|
|
246
406
|
});
|
|
247
407
|
}
|
|
248
408
|
|
|
249
|
-
function buildFieldInputs(rawInputs, cache, fieldType) {
|
|
409
|
+
function buildFieldInputs(rawInputs, cache, fieldType, validationMessages) {
|
|
250
410
|
const fieldTreeCandidate = rawInputs['field'];
|
|
251
411
|
const hasFieldTree = fieldTreeCandidate !== undefined && typeof fieldTreeCandidate === 'function';
|
|
252
412
|
const readonlyField = hasFieldTree ? toReadonlyFieldTreeCached(cache, fieldTreeCandidate) : undefined;
|
|
@@ -258,6 +418,7 @@ function buildFieldInputs(rawInputs, cache, fieldType) {
|
|
|
258
418
|
type: fieldType,
|
|
259
419
|
field: readonlyField,
|
|
260
420
|
setValue,
|
|
421
|
+
...(validationMessages !== undefined && { validationMessages }),
|
|
261
422
|
};
|
|
262
423
|
}
|
|
263
424
|
|
|
@@ -266,14 +427,19 @@ class FieldComponentSlot {
|
|
|
266
427
|
state = signal(EMPTY_STATE, /* @ts-ignore */
|
|
267
428
|
...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
|
|
268
429
|
lastInputs = null;
|
|
430
|
+
_parked = signal(false, /* @ts-ignore */
|
|
431
|
+
...(ngDevMode ? [{ debugName: "_parked" }] : /* istanbul ignore next */ []));
|
|
269
432
|
phase = () => this.state().phase;
|
|
270
433
|
snapshot = () => this.state();
|
|
434
|
+
parked = this._parked.asReadonly();
|
|
271
435
|
mountOrReuse(slot, component, fieldInjector, environmentInjector, rawInputs, fieldInputs) {
|
|
272
436
|
const current = this.state();
|
|
273
437
|
if (current.phase !== 'empty' && current.ref.componentType === component) {
|
|
274
438
|
slot.insert(current.ref.hostView);
|
|
275
439
|
this.lastInputs = null;
|
|
276
440
|
this.state.set(Object.freeze({ phase: 'mounted', ref: current.ref, slot }));
|
|
441
|
+
if (this._parked())
|
|
442
|
+
current.ref.changeDetectorRef.detach();
|
|
277
443
|
this.pushInputs(rawInputs, fieldInputs);
|
|
278
444
|
if (current.phase === 'detached')
|
|
279
445
|
this.replayFocus(current.focusSnapshot);
|
|
@@ -282,6 +448,7 @@ class FieldComponentSlot {
|
|
|
282
448
|
if (current.phase !== 'empty')
|
|
283
449
|
current.ref.destroy();
|
|
284
450
|
this.lastInputs = null;
|
|
451
|
+
this._parked.set(false);
|
|
285
452
|
this.state.set(EMPTY_STATE);
|
|
286
453
|
const ref = slot.createComponent(component, {
|
|
287
454
|
environmentInjector,
|
|
@@ -310,19 +477,42 @@ class FieldComponentSlot {
|
|
|
310
477
|
detach() {
|
|
311
478
|
const current = this.state();
|
|
312
479
|
if (current.phase !== 'mounted')
|
|
313
|
-
return;
|
|
480
|
+
return false;
|
|
314
481
|
const focusSnapshot = this.captureFocus(current.ref);
|
|
315
482
|
const idx = current.slot.indexOf(current.ref.hostView);
|
|
316
483
|
if (idx >= 0)
|
|
317
484
|
current.slot.detach(idx);
|
|
318
485
|
this.lastInputs = null;
|
|
319
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);
|
|
320
509
|
}
|
|
321
510
|
destroyOnTeardown() {
|
|
322
511
|
const current = this.state();
|
|
323
512
|
if (current.phase === 'detached')
|
|
324
513
|
current.ref.destroy();
|
|
325
514
|
this.lastInputs = null;
|
|
515
|
+
this._parked.set(false);
|
|
326
516
|
this.state.set(EMPTY_STATE);
|
|
327
517
|
}
|
|
328
518
|
captureFocus(ref) {
|
|
@@ -347,6 +537,100 @@ class FieldComponentSlot {
|
|
|
347
537
|
}
|
|
348
538
|
}
|
|
349
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
|
+
|
|
350
634
|
class DfFieldOutlet {
|
|
351
635
|
dfFieldOutlet = input.required(/* @ts-ignore */
|
|
352
636
|
...(ngDevMode ? [{ debugName: "dfFieldOutlet" }] : /* istanbul ignore next */ []));
|
|
@@ -356,25 +640,53 @@ class DfFieldOutlet {
|
|
|
356
640
|
vcr = signal(this.vcrRef).asReadonly();
|
|
357
641
|
destroyRef = inject(DestroyRef);
|
|
358
642
|
wrapperAutoAssociations = inject(WRAPPER_AUTO_ASSOCIATIONS);
|
|
643
|
+
wrapperRegistry = inject(WRAPPER_REGISTRY);
|
|
359
644
|
defaultWrappersSignal = inject(DEFAULT_WRAPPERS, { optional: true });
|
|
360
645
|
readonlyFieldCache = inject(READONLY_FIELD_TREE_CACHE);
|
|
361
|
-
|
|
646
|
+
eventBus = inject(EventBus, { optional: true });
|
|
647
|
+
injector = inject(Injector);
|
|
648
|
+
fieldComponent = new FieldComponentSlot();
|
|
362
649
|
componentIdentity = computed(() => this.dfFieldOutlet().component, /* @ts-ignore */
|
|
363
650
|
...(ngDevMode ? [{ debugName: "componentIdentity" }] : /* istanbul ignore next */ []));
|
|
364
651
|
renderReady = computed(() => this.dfFieldOutlet().renderReady() && !this.dfFieldOutlet().hidden(), /* @ts-ignore */
|
|
365
652
|
...(ngDevMode ? [{ debugName: "renderReady" }] : /* istanbul ignore next */ []));
|
|
653
|
+
hidden = computed(() => this.dfFieldOutlet().hidden(), /* @ts-ignore */
|
|
654
|
+
...(ngDevMode ? [{ debugName: "hidden" }] : /* istanbul ignore next */ []));
|
|
366
655
|
rawInputs = computed(() => this.dfFieldOutlet().inputs(), /* @ts-ignore */
|
|
367
656
|
...(ngDevMode ? [{ debugName: "rawInputs" }] : /* istanbul ignore next */ []));
|
|
368
|
-
wrappers = computed(() => resolveWrappers(this.dfFieldOutlet().fieldDef, this.defaultWrappersSignal?.(), this.wrapperAutoAssociations), { ...(ngDevMode ? { debugName: "wrappers" } : /* istanbul ignore next */ {}), equal: isSameWrapperChain });
|
|
369
|
-
fieldInputs = computed(() => buildFieldInputs(this.rawInputs(), this.readonlyFieldCache, this.dfFieldOutlet().fieldDef.type), /* @ts-ignore */
|
|
657
|
+
wrappers = computed(() => resolveWrappers(this.dfFieldOutlet().fieldDef, this.defaultWrappersSignal?.(), this.wrapperAutoAssociations, this.wrapperRegistry), { ...(ngDevMode ? { debugName: "wrappers" } : /* istanbul ignore next */ {}), equal: isSameWrapperChain });
|
|
658
|
+
fieldInputs = computed(() => buildFieldInputs(this.rawInputs(), this.readonlyFieldCache, this.dfFieldOutlet().fieldDef.type, this.dfFieldOutlet().fieldDef.validationMessages), /* @ts-ignore */
|
|
370
659
|
...(ngDevMode ? [{ debugName: "fieldInputs" }] : /* istanbul ignore next */ []));
|
|
371
660
|
defaultEnvInjector = inject(EnvironmentInjector);
|
|
372
|
-
|
|
661
|
+
fieldEnvInjector = computed(() => this.dfFieldOutletEnvironmentInjector() ?? this.defaultEnvInjector, /* @ts-ignore */
|
|
373
662
|
...(ngDevMode ? [{ debugName: "fieldEnvInjector" }] : /* istanbul ignore next */ []));
|
|
374
|
-
|
|
663
|
+
fieldInjector = computed(() => this.dfFieldOutlet().injector, /* @ts-ignore */
|
|
375
664
|
...(ngDevMode ? [{ debugName: "fieldInjector" }] : /* istanbul ignore next */ []));
|
|
376
665
|
outermostHostClasses = computed(() => getGridClassString(this.dfFieldOutlet().fieldDef) || undefined, /* @ts-ignore */
|
|
377
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 });
|
|
378
690
|
constructor() {
|
|
379
691
|
createWrapperChainController({
|
|
380
692
|
vcr: this.vcr,
|
|
@@ -390,170 +702,432 @@ class DfFieldOutlet {
|
|
|
390
702
|
this.fieldComponent.mountOrReuse(slot, resolved.component, resolved.injector, this.fieldEnvInjector(), this.rawInputs(), this.fieldInputs());
|
|
391
703
|
},
|
|
392
704
|
});
|
|
393
|
-
|
|
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
|
+
});
|
|
394
740
|
this.destroyRef.onDestroy(() => this.fieldComponent.destroyOnTeardown());
|
|
395
741
|
}
|
|
396
|
-
|
|
397
|
-
|
|
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;
|
|
749
|
+
}
|
|
750
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: DfFieldOutlet, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
751
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.5", 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 });
|
|
398
752
|
}
|
|
399
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.
|
|
753
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: DfFieldOutlet, decorators: [{
|
|
400
754
|
type: Directive,
|
|
401
755
|
args: [{
|
|
402
756
|
selector: '[dfFieldOutlet]',
|
|
403
757
|
}]
|
|
404
758
|
}], ctorParameters: () => [], propDecorators: { dfFieldOutlet: [{ type: i0.Input, args: [{ isSignal: true, alias: "dfFieldOutlet", required: true }] }], dfFieldOutletEnvironmentInjector: [{ type: i0.Input, args: [{ isSignal: true, alias: "dfFieldOutletEnvironmentInjector", required: false }] }] } });
|
|
405
759
|
|
|
760
|
+
function isPending(formSignal) {
|
|
761
|
+
return formSignal()().pending();
|
|
762
|
+
}
|
|
763
|
+
function isTreeValidationErrors(result) {
|
|
764
|
+
const isError = (v) => typeof v === 'object' && v !== null && 'kind' in v;
|
|
765
|
+
return Array.isArray(result) ? result.every(isError) && result.length > 0 : isError(result);
|
|
766
|
+
}
|
|
406
767
|
function wrapSubmissionAction(action) {
|
|
407
768
|
return async (formTree) => {
|
|
408
769
|
const result = action(formTree);
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
return;
|
|
412
|
-
}
|
|
413
|
-
await Promise.resolve(result);
|
|
770
|
+
const resolved = isObservable(result) ? await firstValueFrom(result) : await Promise.resolve(result);
|
|
771
|
+
return isTreeValidationErrors(resolved) ? resolved : undefined;
|
|
414
772
|
};
|
|
415
773
|
}
|
|
416
774
|
function createSubmissionHandler(options) {
|
|
417
775
|
const { eventBus, configSignal, formSignal, validSignal, logger } = options;
|
|
418
|
-
return eventBus.on('submit').pipe(exhaustMap(() => {
|
|
776
|
+
return eventBus.on('submit').pipe(exhaustMap((event) => {
|
|
777
|
+
const reply = event.reply;
|
|
778
|
+
reply?.accept();
|
|
419
779
|
const submissionConfig = configSignal().submission;
|
|
420
780
|
if (!submissionConfig?.action) {
|
|
781
|
+
reply?.settle(validSignal() ? { status: 'dispatched' } : { status: 'validation-failed' });
|
|
421
782
|
return EMPTY;
|
|
422
783
|
}
|
|
423
784
|
if (!validSignal()) {
|
|
424
785
|
logger.debug('Submission action skipped: form is not valid (invalid or pending async validators)');
|
|
786
|
+
reply?.settle(isPending(formSignal) ? { status: 'pending-validation' } : { status: 'validation-failed' });
|
|
425
787
|
return EMPTY;
|
|
426
788
|
}
|
|
427
789
|
const wrappedAction = wrapSubmissionAction(submissionConfig.action);
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
return
|
|
431
|
-
|
|
790
|
+
return defer(() => {
|
|
791
|
+
const formTree = formSignal();
|
|
792
|
+
return from(submit(formTree, wrappedAction)).pipe(
|
|
793
|
+
tap(() => {
|
|
794
|
+
reply?.settle(formTree().errorSummary().length ? { status: 'server-errors' } : { status: 'success' });
|
|
795
|
+
}), catchError((error) => {
|
|
796
|
+
logger.error('Submission action failed:', error);
|
|
797
|
+
reply?.settle({ status: 'action-failed', error });
|
|
798
|
+
return EMPTY;
|
|
799
|
+
}));
|
|
800
|
+
});
|
|
432
801
|
}));
|
|
433
802
|
}
|
|
434
803
|
|
|
435
804
|
class FormSubmitEvent {
|
|
805
|
+
reply;
|
|
436
806
|
type = 'submit';
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
class NextPageEvent {
|
|
440
|
-
type = 'next-page';
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
class PageChangeEvent {
|
|
444
|
-
currentPageIndex;
|
|
445
|
-
totalPages;
|
|
446
|
-
previousPageIndex;
|
|
447
|
-
type = 'page-change';
|
|
448
|
-
constructor(
|
|
449
|
-
currentPageIndex,
|
|
450
|
-
totalPages,
|
|
451
|
-
previousPageIndex) {
|
|
452
|
-
this.currentPageIndex = currentPageIndex;
|
|
453
|
-
this.totalPages = totalPages;
|
|
454
|
-
this.previousPageIndex = previousPageIndex;
|
|
807
|
+
constructor(reply) {
|
|
808
|
+
this.reply = reply;
|
|
455
809
|
}
|
|
456
810
|
}
|
|
457
811
|
|
|
458
|
-
|
|
459
|
-
|
|
812
|
+
const COMPONENT_CACHE = new InjectionToken('COMPONENT_CACHE', {
|
|
813
|
+
providedIn: 'root',
|
|
814
|
+
factory: () => new Map(),
|
|
815
|
+
});
|
|
816
|
+
const COMPONENT_LOAD_CACHE = new InjectionToken('COMPONENT_LOAD_CACHE', {
|
|
817
|
+
providedIn: 'root',
|
|
818
|
+
factory: () => new Map(),
|
|
819
|
+
});
|
|
820
|
+
function injectFieldRegistry() {
|
|
821
|
+
const registry = inject(FIELD_REGISTRY);
|
|
822
|
+
const componentCache = inject(COMPONENT_CACHE);
|
|
823
|
+
const componentLoadCache = inject(COMPONENT_LOAD_CACHE);
|
|
824
|
+
return {
|
|
825
|
+
getType(name) {
|
|
826
|
+
return registry.get(name);
|
|
827
|
+
},
|
|
828
|
+
hasType(name) {
|
|
829
|
+
return registry.has(name);
|
|
830
|
+
},
|
|
831
|
+
async loadTypeComponent(name) {
|
|
832
|
+
const fieldType = registry.get(name);
|
|
833
|
+
if (!fieldType) {
|
|
834
|
+
throw new DynamicFormError(`Field type "${name}" is not registered`);
|
|
835
|
+
}
|
|
836
|
+
if (!fieldType.loadComponent) {
|
|
837
|
+
return undefined;
|
|
838
|
+
}
|
|
839
|
+
const cached = componentCache.get(name);
|
|
840
|
+
if (cached) {
|
|
841
|
+
return cached;
|
|
842
|
+
}
|
|
843
|
+
const pending = componentLoadCache.get(name);
|
|
844
|
+
if (pending) {
|
|
845
|
+
return pending;
|
|
846
|
+
}
|
|
847
|
+
try {
|
|
848
|
+
const load = fieldType
|
|
849
|
+
.loadComponent()
|
|
850
|
+
.then(resolveDefaultExport)
|
|
851
|
+
.then((component) => {
|
|
852
|
+
if (component) {
|
|
853
|
+
componentCache.set(name, component);
|
|
854
|
+
}
|
|
855
|
+
return component;
|
|
856
|
+
})
|
|
857
|
+
.catch((error) => {
|
|
858
|
+
throw new DynamicFormError(`Failed to load component for field type "${name}": ${error}`);
|
|
859
|
+
})
|
|
860
|
+
.finally(() => componentLoadCache.delete(name));
|
|
861
|
+
componentLoadCache.set(name, load);
|
|
862
|
+
return load;
|
|
863
|
+
}
|
|
864
|
+
catch (error) {
|
|
865
|
+
throw new DynamicFormError(`Failed to load component for field type "${name}": ${error}`);
|
|
866
|
+
}
|
|
867
|
+
},
|
|
868
|
+
getLoadedComponent(name) {
|
|
869
|
+
return componentCache.get(name);
|
|
870
|
+
},
|
|
871
|
+
getTypes() {
|
|
872
|
+
return Array.from(registry.values());
|
|
873
|
+
},
|
|
874
|
+
registerTypes(types) {
|
|
875
|
+
types.forEach((type) => {
|
|
876
|
+
registry.set(type.name, type);
|
|
877
|
+
});
|
|
878
|
+
},
|
|
879
|
+
get raw() {
|
|
880
|
+
return registry;
|
|
881
|
+
},
|
|
882
|
+
};
|
|
460
883
|
}
|
|
461
884
|
|
|
462
|
-
|
|
463
|
-
|
|
885
|
+
const NORMALIZED_ARRAY_METADATA = Symbol('normalizedArrayMetadata');
|
|
886
|
+
function setNormalizedArrayMetadata(arrayField, metadata) {
|
|
887
|
+
arrayField[NORMALIZED_ARRAY_METADATA] = metadata;
|
|
464
888
|
}
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
type = 'form-clear';
|
|
889
|
+
function getNormalizedArrayMetadata(arrayField) {
|
|
890
|
+
return arrayField[NORMALIZED_ARRAY_METADATA];
|
|
468
891
|
}
|
|
469
892
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
893
|
+
function normalizeSimplifiedArrays(fields) {
|
|
894
|
+
return fields.flatMap((field) => {
|
|
895
|
+
if (isContainerField(field) && !isArrayField(field) && hasChildFields(field)) {
|
|
896
|
+
return [{ ...field, fields: normalizeSimplifiedArrays(field.fields) }];
|
|
897
|
+
}
|
|
898
|
+
if (isArrayField(field) && !isSimplifiedArrayField(field)) {
|
|
899
|
+
const normalizedItems = field.fields.map((item) => Array.isArray(item) ? normalizeSimplifiedArrays(item) : item);
|
|
900
|
+
return [{ ...field, fields: normalizedItems }];
|
|
901
|
+
}
|
|
902
|
+
if (isSimplifiedArrayField(field)) {
|
|
903
|
+
const { arrayField, addButton } = expandSimplifiedArray(field);
|
|
904
|
+
return addButton ? [arrayField, addButton] : [arrayField];
|
|
905
|
+
}
|
|
906
|
+
return [field];
|
|
907
|
+
});
|
|
480
908
|
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
template;
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
909
|
+
const INVALID_ARRAY_CHILD_TYPES = new Set(['page', 'array']);
|
|
910
|
+
function validateSimplifiedTemplate(field) {
|
|
911
|
+
const { template, key } = field;
|
|
912
|
+
const isObjectTemplate = Array.isArray(template);
|
|
913
|
+
if (isObjectTemplate) {
|
|
914
|
+
const templateFields = template;
|
|
915
|
+
for (const tmpl of templateFields) {
|
|
916
|
+
if (INVALID_ARRAY_CHILD_TYPES.has(tmpl.type)) {
|
|
917
|
+
throw new DynamicFormError(`Simplified array "${key}" template contains a '${tmpl.type}' field (key: '${tmpl.key}'). ` +
|
|
918
|
+
`Only leaf fields, rows, and groups are allowed as array children.`);
|
|
919
|
+
}
|
|
920
|
+
}
|
|
491
921
|
}
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
type = 'insert-array-item';
|
|
499
|
-
constructor(
|
|
500
|
-
arrayKey,
|
|
501
|
-
index,
|
|
502
|
-
template) {
|
|
503
|
-
this.arrayKey = arrayKey;
|
|
504
|
-
this.index = index;
|
|
505
|
-
this.template = template;
|
|
922
|
+
else {
|
|
923
|
+
const singleTemplate = template;
|
|
924
|
+
if (INVALID_ARRAY_CHILD_TYPES.has(singleTemplate.type)) {
|
|
925
|
+
throw new DynamicFormError(`Simplified array "${key}" template has type '${singleTemplate.type}'. ` +
|
|
926
|
+
`Only leaf fields, rows, and groups are allowed as array children.`);
|
|
927
|
+
}
|
|
506
928
|
}
|
|
507
929
|
}
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
930
|
+
function expandSimplifiedArray(field) {
|
|
931
|
+
const { template, value = [], addButton, removeButton, key, logic, minLength, maxLength, wrappers, skipAutoWrappers, skipDefaultWrappers, validators, validationMessages, required, validateWhenHidden, } = field;
|
|
932
|
+
const isObjectTemplate = Array.isArray(template);
|
|
933
|
+
const values = value;
|
|
934
|
+
validateSimplifiedTemplate(field);
|
|
935
|
+
const items = values.map((v) => {
|
|
936
|
+
if (isObjectTemplate) {
|
|
937
|
+
return buildObjectItem(template, v, removeButton);
|
|
938
|
+
}
|
|
939
|
+
return buildPrimitiveItem(template, v);
|
|
940
|
+
});
|
|
941
|
+
const addTemplate = isObjectTemplate
|
|
942
|
+
? buildObjectItemTemplate(template, removeButton)
|
|
943
|
+
: template;
|
|
944
|
+
const hasAutoRemove = !isObjectTemplate && removeButton !== false;
|
|
945
|
+
const arrayFieldObj = {
|
|
946
|
+
key,
|
|
947
|
+
type: 'array',
|
|
948
|
+
fields: items,
|
|
949
|
+
};
|
|
950
|
+
if (logic) {
|
|
951
|
+
arrayFieldObj['logic'] = logic;
|
|
952
|
+
}
|
|
953
|
+
if (minLength !== undefined) {
|
|
954
|
+
arrayFieldObj['minLength'] = minLength;
|
|
955
|
+
}
|
|
956
|
+
if (maxLength !== undefined) {
|
|
957
|
+
arrayFieldObj['maxLength'] = maxLength;
|
|
958
|
+
}
|
|
959
|
+
if (wrappers !== undefined) {
|
|
960
|
+
arrayFieldObj['wrappers'] = wrappers;
|
|
961
|
+
}
|
|
962
|
+
if (skipAutoWrappers !== undefined) {
|
|
963
|
+
arrayFieldObj['skipAutoWrappers'] = skipAutoWrappers;
|
|
964
|
+
}
|
|
965
|
+
if (skipDefaultWrappers !== undefined) {
|
|
966
|
+
arrayFieldObj['skipDefaultWrappers'] = skipDefaultWrappers;
|
|
967
|
+
}
|
|
968
|
+
if (validators !== undefined) {
|
|
969
|
+
arrayFieldObj['validators'] = validators;
|
|
970
|
+
}
|
|
971
|
+
if (validationMessages !== undefined) {
|
|
972
|
+
arrayFieldObj['validationMessages'] = validationMessages;
|
|
973
|
+
}
|
|
974
|
+
if (required !== undefined) {
|
|
975
|
+
arrayFieldObj['required'] = required;
|
|
976
|
+
}
|
|
977
|
+
if (validateWhenHidden !== undefined) {
|
|
978
|
+
arrayFieldObj['validateWhenHidden'] = validateWhenHidden;
|
|
515
979
|
}
|
|
980
|
+
const arrayField = arrayFieldObj;
|
|
981
|
+
const primitiveFieldKey = !isObjectTemplate ? template.key : undefined;
|
|
982
|
+
const metadataTemplate = isObjectTemplate ? buildObjectItemTemplate(template, removeButton) : template;
|
|
983
|
+
setNormalizedArrayMetadata(arrayFieldObj, {
|
|
984
|
+
...(hasAutoRemove && { autoRemoveButton: buildRemoveButton(removeButton) }),
|
|
985
|
+
...(primitiveFieldKey && { primitiveFieldKey }),
|
|
986
|
+
template: metadataTemplate,
|
|
987
|
+
});
|
|
988
|
+
let addButtonField;
|
|
989
|
+
if (addButton !== false) {
|
|
990
|
+
const buttonConfig = (typeof addButton === 'object' ? addButton : {});
|
|
991
|
+
addButtonField = {
|
|
992
|
+
key: `${key}__add`,
|
|
993
|
+
type: 'add-array-item',
|
|
994
|
+
label: buttonConfig.label ?? 'Add',
|
|
995
|
+
arrayKey: key,
|
|
996
|
+
template: addTemplate,
|
|
997
|
+
...(buttonConfig.props && { props: buttonConfig.props }),
|
|
998
|
+
...(logic && { logic }),
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
return { arrayField, addButton: addButtonField };
|
|
516
1002
|
}
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
1003
|
+
function buildPrimitiveItem(template, value) {
|
|
1004
|
+
return { ...template, value };
|
|
1005
|
+
}
|
|
1006
|
+
function buildObjectItem(template, valueObj, removeButton) {
|
|
1007
|
+
const fieldsWithValues = template.map((templateField) => ({
|
|
1008
|
+
...templateField,
|
|
1009
|
+
...(Object.hasOwn(valueObj, templateField.key) && { value: valueObj[templateField.key] }),
|
|
1010
|
+
}));
|
|
1011
|
+
if (removeButton === false) {
|
|
1012
|
+
return fieldsWithValues;
|
|
524
1013
|
}
|
|
1014
|
+
return [...fieldsWithValues, buildRemoveButton(removeButton)];
|
|
525
1015
|
}
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
fromIndex;
|
|
530
|
-
toIndex;
|
|
531
|
-
type = 'move-array-item';
|
|
532
|
-
constructor(
|
|
533
|
-
arrayKey,
|
|
534
|
-
fromIndex,
|
|
535
|
-
toIndex) {
|
|
536
|
-
this.arrayKey = arrayKey;
|
|
537
|
-
this.fromIndex = fromIndex;
|
|
538
|
-
this.toIndex = toIndex;
|
|
1016
|
+
function buildObjectItemTemplate(template, removeButton) {
|
|
1017
|
+
if (removeButton === false) {
|
|
1018
|
+
return template;
|
|
539
1019
|
}
|
|
1020
|
+
return [...template, buildRemoveButton(removeButton)];
|
|
1021
|
+
}
|
|
1022
|
+
function buildRemoveButton(config) {
|
|
1023
|
+
const buttonConfig = (typeof config === 'object' ? config : {});
|
|
1024
|
+
return {
|
|
1025
|
+
key: '__remove',
|
|
1026
|
+
type: 'remove-array-item',
|
|
1027
|
+
label: buttonConfig.label ?? 'Remove',
|
|
1028
|
+
...(buttonConfig.props && { props: buttonConfig.props }),
|
|
1029
|
+
};
|
|
540
1030
|
}
|
|
541
1031
|
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
1032
|
+
function collectFieldDefs(fields, out = []) {
|
|
1033
|
+
for (const field of fields ?? []) {
|
|
1034
|
+
out.push(field);
|
|
1035
|
+
const children = field.fields;
|
|
1036
|
+
if (Array.isArray(children)) {
|
|
1037
|
+
for (const child of children) {
|
|
1038
|
+
if (Array.isArray(child))
|
|
1039
|
+
collectFieldDefs(child, out);
|
|
1040
|
+
else
|
|
1041
|
+
collectFieldDefs([child], out);
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
const metadata = getNormalizedArrayMetadata(field);
|
|
1045
|
+
const template = metadata?.template;
|
|
1046
|
+
if (Array.isArray(template))
|
|
1047
|
+
collectFieldDefs(template, out);
|
|
1048
|
+
else if (template)
|
|
1049
|
+
collectFieldDefs([template], out);
|
|
1050
|
+
if (metadata?.autoRemoveButton)
|
|
1051
|
+
collectFieldDefs([metadata.autoRemoveButton], out);
|
|
1052
|
+
}
|
|
1053
|
+
return out;
|
|
1054
|
+
}
|
|
1055
|
+
function injectFormComponentPreloader() {
|
|
1056
|
+
const fieldRegistry = injectFieldRegistry();
|
|
1057
|
+
const wrapperRegistry = inject(WRAPPER_REGISTRY);
|
|
1058
|
+
const wrapperCache = inject(WRAPPER_COMPONENT_CACHE);
|
|
1059
|
+
const wrapperLoadCache = inject(WRAPPER_COMPONENT_LOAD_CACHE);
|
|
1060
|
+
const autoAssociations = inject(WRAPPER_AUTO_ASSOCIATIONS);
|
|
1061
|
+
const defaultWrappers = inject(DEFAULT_WRAPPERS, { optional: true });
|
|
1062
|
+
const preloadedConfigs = new WeakSet();
|
|
1063
|
+
const preloadedFieldRoots = new WeakSet();
|
|
1064
|
+
const preloadFields = (fields) => {
|
|
1065
|
+
const unseenFields = fields.filter((field) => {
|
|
1066
|
+
if (preloadedFieldRoots.has(field))
|
|
1067
|
+
return false;
|
|
1068
|
+
preloadedFieldRoots.add(field);
|
|
1069
|
+
return true;
|
|
1070
|
+
});
|
|
1071
|
+
if (unseenFields.length === 0)
|
|
1072
|
+
return;
|
|
1073
|
+
const normalizedFields = normalizeSimplifiedArrays(unseenFields);
|
|
1074
|
+
const defs = collectFieldDefs(normalizedFields);
|
|
1075
|
+
if (defs.length === 0)
|
|
1076
|
+
return;
|
|
1077
|
+
const fieldTypes = new Set();
|
|
1078
|
+
const wrapperTypes = new Set();
|
|
1079
|
+
for (const def of defs) {
|
|
1080
|
+
if (def.type)
|
|
1081
|
+
fieldTypes.add(def.type);
|
|
1082
|
+
for (const wrapper of resolveWrappers(def, defaultWrappers?.(), autoAssociations, wrapperRegistry)) {
|
|
1083
|
+
wrapperTypes.add(wrapper.type);
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
const started = [];
|
|
1087
|
+
for (const type of fieldTypes) {
|
|
1088
|
+
if (fieldRegistry.getLoadedComponent(type))
|
|
1089
|
+
continue;
|
|
1090
|
+
started.push(Promise.resolve(fieldRegistry.loadTypeComponent(type)).catch(() => undefined));
|
|
1091
|
+
}
|
|
1092
|
+
for (const type of wrapperTypes) {
|
|
1093
|
+
if (wrapperCache.has(type))
|
|
1094
|
+
continue;
|
|
1095
|
+
started.push(loadWrapperComponent(type, wrapperRegistry, wrapperCache, wrapperLoadCache).catch(() => undefined));
|
|
1096
|
+
}
|
|
1097
|
+
void Promise.allSettled(started);
|
|
1098
|
+
};
|
|
1099
|
+
return {
|
|
1100
|
+
preloadConfig: (config) => {
|
|
1101
|
+
if (preloadedConfigs.has(config))
|
|
1102
|
+
return;
|
|
1103
|
+
preloadedConfigs.add(config);
|
|
1104
|
+
const fields = config.fields;
|
|
1105
|
+
if (fields.length > 0 && fields.every(isPageField))
|
|
1106
|
+
return;
|
|
1107
|
+
preloadFields(fields);
|
|
1108
|
+
},
|
|
1109
|
+
preloadFields,
|
|
1110
|
+
};
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
class PageChangeEvent {
|
|
1114
|
+
currentPageIndex;
|
|
1115
|
+
totalPages;
|
|
1116
|
+
previousPageIndex;
|
|
1117
|
+
type = 'page-change';
|
|
546
1118
|
constructor(
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
this.
|
|
1119
|
+
currentPageIndex,
|
|
1120
|
+
totalPages,
|
|
1121
|
+
previousPageIndex) {
|
|
1122
|
+
this.currentPageIndex = currentPageIndex;
|
|
1123
|
+
this.totalPages = totalPages;
|
|
1124
|
+
this.previousPageIndex = previousPageIndex;
|
|
551
1125
|
}
|
|
552
1126
|
}
|
|
553
1127
|
|
|
554
|
-
class
|
|
1128
|
+
class PagerStateEvent {
|
|
555
1129
|
state;
|
|
556
|
-
type = '
|
|
1130
|
+
type = 'pager-state';
|
|
557
1131
|
constructor(state) {
|
|
558
1132
|
this.state = state;
|
|
559
1133
|
}
|
|
@@ -564,512 +1138,19 @@ const PAGE_PRELOAD_WINDOW = new InjectionToken('PAGE_PRELOAD_WINDOW', {
|
|
|
564
1138
|
factory: () => 1,
|
|
565
1139
|
});
|
|
566
1140
|
|
|
567
|
-
function
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
class PageOrchestratorComponent {
|
|
574
|
-
eventBus = inject(EventBus);
|
|
575
|
-
fieldContextRegistry = inject(FieldContextRegistryService);
|
|
576
|
-
functionRegistry = inject(FunctionRegistryService);
|
|
577
|
-
formOptions = inject(FORM_OPTIONS, { optional: true });
|
|
578
|
-
globalPreloadWindow = inject(PAGE_PRELOAD_WINDOW);
|
|
579
|
-
pageFields = input.required(/* @ts-ignore */
|
|
580
|
-
...(ngDevMode ? [{ debugName: "pageFields" }] : /* istanbul ignore next */ []));
|
|
581
|
-
form = input.required(/* @ts-ignore */
|
|
582
|
-
...(ngDevMode ? [{ debugName: "form" }] : /* istanbul ignore next */ []));
|
|
583
|
-
fieldSignalContext = input.required(/* @ts-ignore */
|
|
584
|
-
...(ngDevMode ? [{ debugName: "fieldSignalContext" }] : /* istanbul ignore next */ []));
|
|
585
|
-
pageHiddenStates = computed(() => {
|
|
586
|
-
const pages = this.pageFields();
|
|
587
|
-
return pages.map((page) => this.evaluatePageHidden(page));
|
|
588
|
-
}, /* @ts-ignore */
|
|
589
|
-
...(ngDevMode ? [{ debugName: "pageHiddenStates" }] : /* istanbul ignore next */ []));
|
|
590
|
-
visiblePageIndices = computed(() => {
|
|
591
|
-
const hiddenStates = this.pageHiddenStates();
|
|
592
|
-
return hiddenStates
|
|
593
|
-
.map((hidden, index) => ({ index, hidden }))
|
|
594
|
-
.filter((item) => !item.hidden)
|
|
595
|
-
.map((item) => item.index);
|
|
596
|
-
}, /* @ts-ignore */
|
|
597
|
-
...(ngDevMode ? [{ debugName: "visiblePageIndices" }] : /* istanbul ignore next */ []));
|
|
598
|
-
currentPageIndex = linkedSignal(() => {
|
|
599
|
-
const totalPages = this.pageFields().length;
|
|
600
|
-
if (totalPages === 0)
|
|
601
|
-
return 0;
|
|
602
|
-
return 0;
|
|
603
|
-
}, /* @ts-ignore */
|
|
604
|
-
...(ngDevMode ? [{ debugName: "currentPageIndex" }] : /* istanbul ignore next */ []));
|
|
605
|
-
state = computed(() => {
|
|
606
|
-
const currentIndex = this.currentPageIndex();
|
|
607
|
-
const totalPages = this.pageFields().length;
|
|
608
|
-
const visibleIndices = this.visiblePageIndices();
|
|
609
|
-
const currentVisiblePosition = visibleIndices.indexOf(currentIndex);
|
|
610
|
-
const isFirstVisiblePage = currentVisiblePosition === 0 || currentVisiblePosition === -1;
|
|
611
|
-
const isLastVisiblePage = currentVisiblePosition >= visibleIndices.length - 1;
|
|
612
|
-
return {
|
|
613
|
-
currentPageIndex: currentIndex,
|
|
614
|
-
totalPages,
|
|
615
|
-
isFirstPage: isFirstVisiblePage,
|
|
616
|
-
isLastPage: isLastVisiblePage,
|
|
617
|
-
navigationDisabled: false,
|
|
618
|
-
};
|
|
619
|
-
}, /* @ts-ignore */
|
|
620
|
-
...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
|
|
621
|
-
currentPageValid = computed(() => {
|
|
622
|
-
const currentIndex = this.currentPageIndex();
|
|
623
|
-
const pages = this.pageFields();
|
|
624
|
-
const form = this.form();
|
|
625
|
-
if (pages.length === 0 || currentIndex >= pages.length) {
|
|
626
|
-
return true;
|
|
1141
|
+
function collectLeafFieldKeys(fields) {
|
|
1142
|
+
const keys = [];
|
|
1143
|
+
for (const field of fields) {
|
|
1144
|
+
if (isRowField(field) || isGenericContainerField(field)) {
|
|
1145
|
+
keys.push(...collectLeafFieldKeys(field.fields));
|
|
627
1146
|
}
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
const leafKeys = collectLeafFieldKeys(pageFields);
|
|
631
|
-
for (const fieldKey of leafKeys) {
|
|
632
|
-
const field = form[fieldKey];
|
|
633
|
-
if (field && typeof field === 'function') {
|
|
634
|
-
const fieldState = field();
|
|
635
|
-
if (fieldState && typeof fieldState.valid === 'function' && !fieldState.valid()) {
|
|
636
|
-
return false;
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
return true;
|
|
641
|
-
}, /* @ts-ignore */
|
|
642
|
-
...(ngDevMode ? [{ debugName: "currentPageValid" }] : /* istanbul ignore next */ []));
|
|
643
|
-
extendedFieldSignalContext = computed(() => ({
|
|
644
|
-
...this.fieldSignalContext(),
|
|
645
|
-
currentPageValid: this.currentPageValid,
|
|
646
|
-
}), /* @ts-ignore */
|
|
647
|
-
...(ngDevMode ? [{ debugName: "extendedFieldSignalContext" }] : /* istanbul ignore next */ []));
|
|
648
|
-
preloadWindow = computed(() => {
|
|
649
|
-
const perForm = this.formOptions?.()?.pagePreloadWindow;
|
|
650
|
-
return clampWindowSize(perForm, this.globalPreloadWindow);
|
|
651
|
-
}, /* @ts-ignore */
|
|
652
|
-
...(ngDevMode ? [{ debugName: "preloadWindow" }] : /* istanbul ignore next */ []));
|
|
653
|
-
isWithinPreloadWindow(i) {
|
|
654
|
-
return Math.abs(i - this.state().currentPageIndex) <= this.preloadWindow();
|
|
655
|
-
}
|
|
656
|
-
constructor() {
|
|
657
|
-
this.setupEventListeners();
|
|
658
|
-
explicitEffect([this.state, this.visiblePageIndices], ([state, visibleIndices]) => {
|
|
659
|
-
const currentVisiblePosition = visibleIndices.indexOf(state.currentPageIndex);
|
|
660
|
-
if (currentVisiblePosition === -1 && visibleIndices.length > 0) {
|
|
661
|
-
const nearest = this.findNearestVisiblePage(state.currentPageIndex, visibleIndices);
|
|
662
|
-
if (nearest !== -1) {
|
|
663
|
-
this.navigateToPage(nearest);
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
});
|
|
667
|
-
}
|
|
668
|
-
navigateToNextPage() {
|
|
669
|
-
const disableWhenPageInvalid = this.formOptions?.()?.nextButton?.disableWhenPageInvalid ?? true;
|
|
670
|
-
if (disableWhenPageInvalid && !this.currentPageValid()) {
|
|
671
|
-
return {
|
|
672
|
-
success: false,
|
|
673
|
-
newPageIndex: this.state().currentPageIndex,
|
|
674
|
-
error: 'Current page has invalid fields',
|
|
675
|
-
};
|
|
676
|
-
}
|
|
677
|
-
const currentState = this.state();
|
|
678
|
-
const visibleIndices = this.visiblePageIndices();
|
|
679
|
-
if (currentState.isLastPage) {
|
|
680
|
-
return {
|
|
681
|
-
success: false,
|
|
682
|
-
newPageIndex: currentState.currentPageIndex,
|
|
683
|
-
error: 'Already on the last visible page',
|
|
684
|
-
};
|
|
685
|
-
}
|
|
686
|
-
if (currentState.navigationDisabled) {
|
|
687
|
-
return {
|
|
688
|
-
success: false,
|
|
689
|
-
newPageIndex: currentState.currentPageIndex,
|
|
690
|
-
error: 'Navigation is currently disabled',
|
|
691
|
-
};
|
|
692
|
-
}
|
|
693
|
-
const currentVisiblePosition = visibleIndices.indexOf(currentState.currentPageIndex);
|
|
694
|
-
if (currentVisiblePosition === -1 || currentVisiblePosition >= visibleIndices.length - 1) {
|
|
695
|
-
return {
|
|
696
|
-
success: false,
|
|
697
|
-
newPageIndex: currentState.currentPageIndex,
|
|
698
|
-
error: 'No next visible page available',
|
|
699
|
-
};
|
|
700
|
-
}
|
|
701
|
-
const nextVisiblePageIndex = visibleIndices[currentVisiblePosition + 1];
|
|
702
|
-
return this.navigateToPage(nextVisiblePageIndex);
|
|
703
|
-
}
|
|
704
|
-
navigateToPreviousPage() {
|
|
705
|
-
const currentState = this.state();
|
|
706
|
-
const visibleIndices = this.visiblePageIndices();
|
|
707
|
-
if (currentState.isFirstPage) {
|
|
708
|
-
return {
|
|
709
|
-
success: false,
|
|
710
|
-
newPageIndex: currentState.currentPageIndex,
|
|
711
|
-
error: 'Already on the first visible page',
|
|
712
|
-
};
|
|
713
|
-
}
|
|
714
|
-
if (currentState.navigationDisabled) {
|
|
715
|
-
return {
|
|
716
|
-
success: false,
|
|
717
|
-
newPageIndex: currentState.currentPageIndex,
|
|
718
|
-
error: 'Navigation is currently disabled',
|
|
719
|
-
};
|
|
720
|
-
}
|
|
721
|
-
const currentVisiblePosition = visibleIndices.indexOf(currentState.currentPageIndex);
|
|
722
|
-
if (currentVisiblePosition <= 0) {
|
|
723
|
-
return {
|
|
724
|
-
success: false,
|
|
725
|
-
newPageIndex: currentState.currentPageIndex,
|
|
726
|
-
error: 'No previous visible page available',
|
|
727
|
-
};
|
|
728
|
-
}
|
|
729
|
-
const prevVisiblePageIndex = visibleIndices[currentVisiblePosition - 1];
|
|
730
|
-
return this.navigateToPage(prevVisiblePageIndex);
|
|
731
|
-
}
|
|
732
|
-
navigateToPage(pageIndex) {
|
|
733
|
-
const currentState = this.state();
|
|
734
|
-
const totalPages = currentState.totalPages;
|
|
735
|
-
if (pageIndex < 0 || pageIndex >= totalPages) {
|
|
736
|
-
return {
|
|
737
|
-
success: false,
|
|
738
|
-
newPageIndex: currentState.currentPageIndex,
|
|
739
|
-
error: `Invalid page index: ${pageIndex}. Valid range is 0 to ${totalPages - 1}`,
|
|
740
|
-
};
|
|
741
|
-
}
|
|
742
|
-
const visibleIndices = this.visiblePageIndices();
|
|
743
|
-
if (!visibleIndices.includes(pageIndex)) {
|
|
744
|
-
return {
|
|
745
|
-
success: false,
|
|
746
|
-
newPageIndex: currentState.currentPageIndex,
|
|
747
|
-
error: `Cannot navigate to hidden page at index ${pageIndex}. Visible pages: [${visibleIndices.join(', ')}]`,
|
|
748
|
-
};
|
|
749
|
-
}
|
|
750
|
-
if (pageIndex === currentState.currentPageIndex) {
|
|
751
|
-
return {
|
|
752
|
-
success: true,
|
|
753
|
-
newPageIndex: pageIndex,
|
|
754
|
-
};
|
|
755
|
-
}
|
|
756
|
-
const previousIndex = currentState.currentPageIndex;
|
|
757
|
-
this.currentPageIndex.set(pageIndex);
|
|
758
|
-
this.eventBus.dispatch(PageChangeEvent, pageIndex, totalPages, previousIndex);
|
|
759
|
-
return {
|
|
760
|
-
success: true,
|
|
761
|
-
newPageIndex: pageIndex,
|
|
762
|
-
};
|
|
763
|
-
}
|
|
764
|
-
findNearestVisiblePage(currentIndex, visibleIndices) {
|
|
765
|
-
let nearest = -1;
|
|
766
|
-
let minDistance = Infinity;
|
|
767
|
-
for (const idx of visibleIndices) {
|
|
768
|
-
const distance = Math.abs(idx - currentIndex);
|
|
769
|
-
if (distance < minDistance || (distance === minDistance && idx > nearest)) {
|
|
770
|
-
minDistance = distance;
|
|
771
|
-
nearest = idx;
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
return nearest;
|
|
775
|
-
}
|
|
776
|
-
setupEventListeners() {
|
|
777
|
-
this.eventBus
|
|
778
|
-
.on('next-page')
|
|
779
|
-
.pipe(takeUntilDestroyed())
|
|
780
|
-
.subscribe(() => {
|
|
781
|
-
this.navigateToNextPage();
|
|
782
|
-
});
|
|
783
|
-
this.eventBus
|
|
784
|
-
.on('previous-page')
|
|
785
|
-
.pipe(takeUntilDestroyed())
|
|
786
|
-
.subscribe(() => {
|
|
787
|
-
this.navigateToPreviousPage();
|
|
788
|
-
});
|
|
789
|
-
explicitEffect([this.state], ([state]) => this.eventBus.dispatch(PageNavigationStateChangeEvent, state));
|
|
790
|
-
}
|
|
791
|
-
evaluatePageHidden(page) {
|
|
792
|
-
if (!page.logic || page.logic.length === 0) {
|
|
793
|
-
return false;
|
|
794
|
-
}
|
|
795
|
-
const hiddenLogic = page.logic.filter((l) => l.type === 'hidden');
|
|
796
|
-
if (hiddenLogic.length === 0) {
|
|
797
|
-
return false;
|
|
798
|
-
}
|
|
799
|
-
for (const logic of hiddenLogic) {
|
|
800
|
-
if (typeof logic.condition === 'boolean') {
|
|
801
|
-
if (logic.condition) {
|
|
802
|
-
return true;
|
|
803
|
-
}
|
|
804
|
-
continue;
|
|
805
|
-
}
|
|
806
|
-
const condition = logic.condition;
|
|
807
|
-
const context = this.fieldContextRegistry.createDisplayOnlyContext(page.key || '', this.functionRegistry.getCustomFunctions());
|
|
808
|
-
if (evaluateCondition(condition, context)) {
|
|
809
|
-
return true;
|
|
810
|
-
}
|
|
811
|
-
}
|
|
812
|
-
return false;
|
|
813
|
-
}
|
|
814
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: PageOrchestratorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
815
|
-
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: [
|
|
816
|
-
{
|
|
817
|
-
provide: FIELD_SIGNAL_CONTEXT,
|
|
818
|
-
useFactory: (orchestrator) => orchestrator.extendedFieldSignalContext(),
|
|
819
|
-
deps: [PageOrchestratorComponent],
|
|
820
|
-
},
|
|
821
|
-
], ngImport: i0, template: `
|
|
822
|
-
@for (pageField of pageFields(); track pageField.key; let i = $index) {
|
|
823
|
-
<!--
|
|
824
|
-
Skip pages hidden by 'hidden' logic conditions entirely. FormStateManager
|
|
825
|
-
derives schema/value/validators from config (not from mounted components),
|
|
826
|
-
so unmounting a hidden page has no effect on form state — only on render +
|
|
827
|
-
CD cost. This is the page-level equivalent of the existing field-level
|
|
828
|
-
\`@if (!field.hidden())\` gate in page-field.component.ts.
|
|
829
|
-
-->
|
|
830
|
-
@if (!pageHiddenStates()[i]) {
|
|
831
|
-
@if (isWithinPreloadWindow(i)) {
|
|
832
|
-
<!-- Current and preloaded neighbour pages: render immediately for flicker-free navigation -->
|
|
833
|
-
@defer (on immediate) {
|
|
834
|
-
<section
|
|
835
|
-
page-field
|
|
836
|
-
[field]="pageField"
|
|
837
|
-
[key]="pageField.key"
|
|
838
|
-
[pageIndex]="i"
|
|
839
|
-
[isVisible]="i === state().currentPageIndex"
|
|
840
|
-
></section>
|
|
841
|
-
} @placeholder {
|
|
842
|
-
<div class="df-page-placeholder" [attr.data-page-index]="i" [attr.data-page-key]="pageField.key"></div>
|
|
843
|
-
}
|
|
844
|
-
} @else {
|
|
845
|
-
<!--
|
|
846
|
-
Pages outside the preload window render only a lightweight placeholder
|
|
847
|
-
— they are NOT mounted. Mounting every page eagerly (previously via
|
|
848
|
-
\`@defer (on idle)\`, which fires almost immediately post-load) put all
|
|
849
|
-
fields of every page in the DOM at once: O(all-fields) initial render
|
|
850
|
-
blocking + O(all-fields) change detection on every keystroke, with no
|
|
851
|
-
visible benefit since only one page is shown at a time.
|
|
852
|
-
|
|
853
|
-
Form state (schema/value/validators) derives from config, not from
|
|
854
|
-
mounted components, so leaving a page unmounted does not affect
|
|
855
|
-
validity, derivations, or navigation. A page mounts the moment
|
|
856
|
-
navigation brings it into the preload window (above), which is
|
|
857
|
-
flicker-free for sequential next/previous navigation. The window size
|
|
858
|
-
is configurable via \`withPagePreload(n)\` or \`FormOptions.pagePreloadWindow\`.
|
|
859
|
-
-->
|
|
860
|
-
<div class="df-page-placeholder" [attr.data-page-index]="i" [attr.data-page-key]="pageField.key"></div>
|
|
861
|
-
}
|
|
862
|
-
}
|
|
863
|
-
}
|
|
864
|
-
`, isInline: true, styles: [":host{display:block;position:relative;width:100%}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, deferBlockDependencies: [() => [/* @ts-ignore */
|
|
865
|
-
import('./ng-forge-dynamic-forms-page-field.component-Wbz6S8iH.mjs').then(m => m.default)]] });
|
|
866
|
-
}
|
|
867
|
-
i0.ɵɵngDeclareClassMetadataAsync({ minVersion: "18.0.0", version: "22.0.1", ngImport: i0, type: PageOrchestratorComponent, resolveDeferredDeps: () => [/* @ts-ignore */
|
|
868
|
-
import('./ng-forge-dynamic-forms-page-field.component-Wbz6S8iH.mjs').then(m => m.default)], resolveMetadata: PageFieldComponent => ({ decorators: [{
|
|
869
|
-
type: Component,
|
|
870
|
-
args: [{ selector: 'div[page-orchestrator]', imports: [PageFieldComponent], template: `
|
|
871
|
-
@for (pageField of pageFields(); track pageField.key; let i = $index) {
|
|
872
|
-
<!--
|
|
873
|
-
Skip pages hidden by 'hidden' logic conditions entirely. FormStateManager
|
|
874
|
-
derives schema/value/validators from config (not from mounted components),
|
|
875
|
-
so unmounting a hidden page has no effect on form state — only on render +
|
|
876
|
-
CD cost. This is the page-level equivalent of the existing field-level
|
|
877
|
-
\`@if (!field.hidden())\` gate in page-field.component.ts.
|
|
878
|
-
-->
|
|
879
|
-
@if (!pageHiddenStates()[i]) {
|
|
880
|
-
@if (isWithinPreloadWindow(i)) {
|
|
881
|
-
<!-- Current and preloaded neighbour pages: render immediately for flicker-free navigation -->
|
|
882
|
-
@defer (on immediate) {
|
|
883
|
-
<section
|
|
884
|
-
page-field
|
|
885
|
-
[field]="pageField"
|
|
886
|
-
[key]="pageField.key"
|
|
887
|
-
[pageIndex]="i"
|
|
888
|
-
[isVisible]="i === state().currentPageIndex"
|
|
889
|
-
></section>
|
|
890
|
-
} @placeholder {
|
|
891
|
-
<div class="df-page-placeholder" [attr.data-page-index]="i" [attr.data-page-key]="pageField.key"></div>
|
|
892
|
-
}
|
|
893
|
-
} @else {
|
|
894
|
-
<!--
|
|
895
|
-
Pages outside the preload window render only a lightweight placeholder
|
|
896
|
-
— they are NOT mounted. Mounting every page eagerly (previously via
|
|
897
|
-
\`@defer (on idle)\`, which fires almost immediately post-load) put all
|
|
898
|
-
fields of every page in the DOM at once: O(all-fields) initial render
|
|
899
|
-
blocking + O(all-fields) change detection on every keystroke, with no
|
|
900
|
-
visible benefit since only one page is shown at a time.
|
|
901
|
-
|
|
902
|
-
Form state (schema/value/validators) derives from config, not from
|
|
903
|
-
mounted components, so leaving a page unmounted does not affect
|
|
904
|
-
validity, derivations, or navigation. A page mounts the moment
|
|
905
|
-
navigation brings it into the preload window (above), which is
|
|
906
|
-
flicker-free for sequential next/previous navigation. The window size
|
|
907
|
-
is configurable via \`withPagePreload(n)\` or \`FormOptions.pagePreloadWindow\`.
|
|
908
|
-
-->
|
|
909
|
-
<div class="df-page-placeholder" [attr.data-page-index]="i" [attr.data-page-key]="pageField.key"></div>
|
|
910
|
-
}
|
|
911
|
-
}
|
|
912
|
-
}
|
|
913
|
-
`, host: {
|
|
914
|
-
class: 'df-page-orchestrator',
|
|
915
|
-
'[attr.data-current-page]': 'state().currentPageIndex',
|
|
916
|
-
'[attr.data-total-pages]': 'state().totalPages',
|
|
917
|
-
}, providers: [
|
|
918
|
-
{
|
|
919
|
-
provide: FIELD_SIGNAL_CONTEXT,
|
|
920
|
-
useFactory: (orchestrator) => orchestrator.extendedFieldSignalContext(),
|
|
921
|
-
deps: [PageOrchestratorComponent],
|
|
922
|
-
},
|
|
923
|
-
], changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;position:relative;width:100%}\n"] }]
|
|
924
|
-
}], 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 }] }] } }) });
|
|
925
|
-
function collectLeafFieldKeys(fields) {
|
|
926
|
-
const keys = [];
|
|
927
|
-
for (const field of fields) {
|
|
928
|
-
if (isRowField(field)) {
|
|
929
|
-
keys.push(...collectLeafFieldKeys(field.fields));
|
|
930
|
-
}
|
|
931
|
-
else if (field.key) {
|
|
932
|
-
keys.push(field.key);
|
|
1147
|
+
else if (field.key) {
|
|
1148
|
+
keys.push(field.key);
|
|
933
1149
|
}
|
|
934
1150
|
}
|
|
935
1151
|
return keys;
|
|
936
1152
|
}
|
|
937
1153
|
|
|
938
|
-
const NORMALIZED_ARRAY_METADATA = Symbol('normalizedArrayMetadata');
|
|
939
|
-
function setNormalizedArrayMetadata(arrayField, metadata) {
|
|
940
|
-
arrayField[NORMALIZED_ARRAY_METADATA] = metadata;
|
|
941
|
-
}
|
|
942
|
-
function getNormalizedArrayMetadata(arrayField) {
|
|
943
|
-
return arrayField[NORMALIZED_ARRAY_METADATA];
|
|
944
|
-
}
|
|
945
|
-
|
|
946
|
-
function normalizeSimplifiedArrays(fields) {
|
|
947
|
-
return fields.flatMap((field) => {
|
|
948
|
-
if (isContainerField(field) && !isArrayField(field) && hasChildFields(field)) {
|
|
949
|
-
return [{ ...field, fields: normalizeSimplifiedArrays(field.fields) }];
|
|
950
|
-
}
|
|
951
|
-
if (isArrayField(field) && !isSimplifiedArrayField(field)) {
|
|
952
|
-
const normalizedItems = field.fields.map((item) => Array.isArray(item) ? normalizeSimplifiedArrays(item) : item);
|
|
953
|
-
return [{ ...field, fields: normalizedItems }];
|
|
954
|
-
}
|
|
955
|
-
if (isSimplifiedArrayField(field)) {
|
|
956
|
-
const { arrayField, addButton } = expandSimplifiedArray(field);
|
|
957
|
-
return addButton ? [arrayField, addButton] : [arrayField];
|
|
958
|
-
}
|
|
959
|
-
return [field];
|
|
960
|
-
});
|
|
961
|
-
}
|
|
962
|
-
const INVALID_ARRAY_CHILD_TYPES = new Set(['page', 'array']);
|
|
963
|
-
function validateSimplifiedTemplate(field) {
|
|
964
|
-
const { template, key } = field;
|
|
965
|
-
const isObjectTemplate = Array.isArray(template);
|
|
966
|
-
if (isObjectTemplate) {
|
|
967
|
-
const templateFields = template;
|
|
968
|
-
for (const tmpl of templateFields) {
|
|
969
|
-
if (INVALID_ARRAY_CHILD_TYPES.has(tmpl.type)) {
|
|
970
|
-
throw new DynamicFormError(`Simplified array "${key}" template contains a '${tmpl.type}' field (key: '${tmpl.key}'). ` +
|
|
971
|
-
`Only leaf fields, rows, and groups are allowed as array children.`);
|
|
972
|
-
}
|
|
973
|
-
}
|
|
974
|
-
}
|
|
975
|
-
else {
|
|
976
|
-
const singleTemplate = template;
|
|
977
|
-
if (INVALID_ARRAY_CHILD_TYPES.has(singleTemplate.type)) {
|
|
978
|
-
throw new DynamicFormError(`Simplified array "${key}" template has type '${singleTemplate.type}'. ` +
|
|
979
|
-
`Only leaf fields, rows, and groups are allowed as array children.`);
|
|
980
|
-
}
|
|
981
|
-
}
|
|
982
|
-
}
|
|
983
|
-
function expandSimplifiedArray(field) {
|
|
984
|
-
const { template, value = [], addButton, removeButton, key, logic, minLength, maxLength, wrappers, skipAutoWrappers, skipDefaultWrappers, } = field;
|
|
985
|
-
const isObjectTemplate = Array.isArray(template);
|
|
986
|
-
const values = value;
|
|
987
|
-
validateSimplifiedTemplate(field);
|
|
988
|
-
const items = values.map((v) => {
|
|
989
|
-
if (isObjectTemplate) {
|
|
990
|
-
return buildObjectItem(template, v, removeButton);
|
|
991
|
-
}
|
|
992
|
-
return buildPrimitiveItem(template, v);
|
|
993
|
-
});
|
|
994
|
-
const addTemplate = isObjectTemplate
|
|
995
|
-
? buildObjectItemTemplate(template, removeButton)
|
|
996
|
-
: template;
|
|
997
|
-
const hasAutoRemove = !isObjectTemplate && removeButton !== false;
|
|
998
|
-
const arrayFieldObj = {
|
|
999
|
-
key,
|
|
1000
|
-
type: 'array',
|
|
1001
|
-
fields: items,
|
|
1002
|
-
};
|
|
1003
|
-
if (logic) {
|
|
1004
|
-
arrayFieldObj['logic'] = logic;
|
|
1005
|
-
}
|
|
1006
|
-
if (minLength !== undefined) {
|
|
1007
|
-
arrayFieldObj['minLength'] = minLength;
|
|
1008
|
-
}
|
|
1009
|
-
if (maxLength !== undefined) {
|
|
1010
|
-
arrayFieldObj['maxLength'] = maxLength;
|
|
1011
|
-
}
|
|
1012
|
-
if (wrappers !== undefined) {
|
|
1013
|
-
arrayFieldObj['wrappers'] = wrappers;
|
|
1014
|
-
}
|
|
1015
|
-
if (skipAutoWrappers !== undefined) {
|
|
1016
|
-
arrayFieldObj['skipAutoWrappers'] = skipAutoWrappers;
|
|
1017
|
-
}
|
|
1018
|
-
if (skipDefaultWrappers !== undefined) {
|
|
1019
|
-
arrayFieldObj['skipDefaultWrappers'] = skipDefaultWrappers;
|
|
1020
|
-
}
|
|
1021
|
-
const arrayField = arrayFieldObj;
|
|
1022
|
-
const primitiveFieldKey = !isObjectTemplate ? template.key : undefined;
|
|
1023
|
-
const metadataTemplate = isObjectTemplate ? buildObjectItemTemplate(template, removeButton) : template;
|
|
1024
|
-
setNormalizedArrayMetadata(arrayFieldObj, {
|
|
1025
|
-
...(hasAutoRemove && { autoRemoveButton: buildRemoveButton(removeButton) }),
|
|
1026
|
-
...(primitiveFieldKey && { primitiveFieldKey }),
|
|
1027
|
-
template: metadataTemplate,
|
|
1028
|
-
});
|
|
1029
|
-
let addButtonField;
|
|
1030
|
-
if (addButton !== false) {
|
|
1031
|
-
const buttonConfig = (typeof addButton === 'object' ? addButton : {});
|
|
1032
|
-
addButtonField = {
|
|
1033
|
-
key: `${key}__add`,
|
|
1034
|
-
type: 'add-array-item',
|
|
1035
|
-
label: buttonConfig.label ?? 'Add',
|
|
1036
|
-
arrayKey: key,
|
|
1037
|
-
template: addTemplate,
|
|
1038
|
-
...(buttonConfig.props && { props: buttonConfig.props }),
|
|
1039
|
-
...(logic && { logic }),
|
|
1040
|
-
};
|
|
1041
|
-
}
|
|
1042
|
-
return { arrayField, addButton: addButtonField };
|
|
1043
|
-
}
|
|
1044
|
-
function buildPrimitiveItem(template, value) {
|
|
1045
|
-
return { ...template, value };
|
|
1046
|
-
}
|
|
1047
|
-
function buildObjectItem(template, valueObj, removeButton) {
|
|
1048
|
-
const fieldsWithValues = template.map((templateField) => ({
|
|
1049
|
-
...templateField,
|
|
1050
|
-
...(Object.hasOwn(valueObj, templateField.key) && { value: valueObj[templateField.key] }),
|
|
1051
|
-
}));
|
|
1052
|
-
if (removeButton === false) {
|
|
1053
|
-
return fieldsWithValues;
|
|
1054
|
-
}
|
|
1055
|
-
return [...fieldsWithValues, buildRemoveButton(removeButton)];
|
|
1056
|
-
}
|
|
1057
|
-
function buildObjectItemTemplate(template, removeButton) {
|
|
1058
|
-
if (removeButton === false) {
|
|
1059
|
-
return template;
|
|
1060
|
-
}
|
|
1061
|
-
return [...template, buildRemoveButton(removeButton)];
|
|
1062
|
-
}
|
|
1063
|
-
function buildRemoveButton(config) {
|
|
1064
|
-
const buttonConfig = (typeof config === 'object' ? config : {});
|
|
1065
|
-
return {
|
|
1066
|
-
key: '__remove',
|
|
1067
|
-
type: 'remove-array-item',
|
|
1068
|
-
label: buttonConfig.label ?? 'Remove',
|
|
1069
|
-
...(buttonConfig.props && { props: buttonConfig.props }),
|
|
1070
|
-
};
|
|
1071
|
-
}
|
|
1072
|
-
|
|
1073
1154
|
function createSlot() {
|
|
1074
1155
|
let counter = 0;
|
|
1075
1156
|
const templates = new Map();
|
|
@@ -1113,529 +1194,134 @@ function createSlot() {
|
|
|
1113
1194
|
itemOrder.length = 0;
|
|
1114
1195
|
counter = 0;
|
|
1115
1196
|
primitiveFieldKey.set(undefined);
|
|
1116
|
-
},
|
|
1117
|
-
};
|
|
1118
|
-
}
|
|
1119
|
-
class ArrayItemRegistryService {
|
|
1120
|
-
slots = new Map();
|
|
1121
|
-
slotFor(arrayPath) {
|
|
1122
|
-
let slot = this.slots.get(arrayPath);
|
|
1123
|
-
if (!slot) {
|
|
1124
|
-
slot = createSlot();
|
|
1125
|
-
this.slots.set(arrayPath, slot);
|
|
1126
|
-
}
|
|
1127
|
-
return slot;
|
|
1128
|
-
}
|
|
1129
|
-
clearAll() {
|
|
1130
|
-
for (const slot of this.slots.values())
|
|
1131
|
-
slot.clear();
|
|
1132
|
-
this.slots.clear();
|
|
1133
|
-
}
|
|
1134
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: ArrayItemRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1135
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: ArrayItemRegistryService });
|
|
1136
|
-
}
|
|
1137
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: ArrayItemRegistryService, decorators: [{
|
|
1138
|
-
type: Injectable
|
|
1139
|
-
}] });
|
|
1140
|
-
|
|
1141
|
-
class SchemaRegistryService {
|
|
1142
|
-
registeredSchemas = new Map();
|
|
1143
|
-
registerSchema(schema) {
|
|
1144
|
-
this.registeredSchemas.set(schema.name, schema);
|
|
1145
|
-
}
|
|
1146
|
-
getSchema(name) {
|
|
1147
|
-
return this.registeredSchemas.get(name);
|
|
1148
|
-
}
|
|
1149
|
-
getAllSchemas() {
|
|
1150
|
-
return new Map(this.registeredSchemas);
|
|
1151
|
-
}
|
|
1152
|
-
clearSchemas() {
|
|
1153
|
-
this.registeredSchemas.clear();
|
|
1154
|
-
}
|
|
1155
|
-
resolveSchema(schema) {
|
|
1156
|
-
if (typeof schema === 'string') {
|
|
1157
|
-
return this.getSchema(schema) || null;
|
|
1158
|
-
}
|
|
1159
|
-
return schema;
|
|
1160
|
-
}
|
|
1161
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: SchemaRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1162
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: SchemaRegistryService });
|
|
1163
|
-
}
|
|
1164
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: SchemaRegistryService, decorators: [{
|
|
1165
|
-
type: Injectable
|
|
1166
|
-
}] });
|
|
1167
|
-
|
|
1168
|
-
const DEV_MODE = typeof ngDevMode === 'undefined' || !!ngDevMode;
|
|
1169
|
-
|
|
1170
|
-
const DEFAULT_DEBOUNCE_MS = 500;
|
|
1171
|
-
function createDebouncedEffect(source, callback, options) {
|
|
1172
|
-
const { ms = DEFAULT_DEBOUNCE_MS, injector } = options;
|
|
1173
|
-
const source$ = toObservable(source, { injector }).pipe(debounceTime(ms), distinctUntilChanged(), tap((value) => callback(value)));
|
|
1174
|
-
return toSignal(source$, { injector });
|
|
1175
|
-
}
|
|
1176
|
-
function createDebouncedSignal(source, options) {
|
|
1177
|
-
const { ms = DEFAULT_DEBOUNCE_MS, injector } = options;
|
|
1178
|
-
const source$ = toObservable(source, { injector }).pipe(debounceTime(ms), distinctUntilChanged());
|
|
1179
|
-
return toSignal(source$, { injector });
|
|
1180
|
-
}
|
|
1181
|
-
|
|
1182
|
-
function getConfigTrigger(config) {
|
|
1183
|
-
if (!isStateLogicConfig(config)) {
|
|
1184
|
-
return 'onChange';
|
|
1185
|
-
}
|
|
1186
|
-
return config.trigger ?? 'onChange';
|
|
1187
|
-
}
|
|
1188
|
-
function getConfigDebounceMs(config) {
|
|
1189
|
-
if (!isStateLogicConfig(config)) {
|
|
1190
|
-
return undefined;
|
|
1191
|
-
}
|
|
1192
|
-
if (config.trigger === 'debounced') {
|
|
1193
|
-
return config.debounceMs;
|
|
1194
|
-
}
|
|
1195
|
-
return undefined;
|
|
1196
|
-
}
|
|
1197
|
-
function applyLogic(config, fieldPath) {
|
|
1198
|
-
if (isDerivationLogicConfig(config))
|
|
1199
|
-
return;
|
|
1200
|
-
if (!isStateLogicConfig(config)) {
|
|
1201
|
-
if (DEV_MODE) {
|
|
1202
|
-
console.warn(`[Dynamic Forms] Unrecognized logic config type '${config.type}' in applyLogic. ` +
|
|
1203
|
-
'This config will be ignored. If this is a new logic type, ensure it is handled explicitly.');
|
|
1204
|
-
}
|
|
1205
|
-
return;
|
|
1206
|
-
}
|
|
1207
|
-
const path = fieldPath;
|
|
1208
|
-
const trigger = getConfigTrigger(config);
|
|
1209
|
-
const debounceMs = getConfigDebounceMs(config) ?? DEFAULT_DEBOUNCE_MS;
|
|
1210
|
-
if (typeof config.condition === 'boolean') {
|
|
1211
|
-
applyLogicFn(config.type, path, () => config.condition);
|
|
1212
|
-
return;
|
|
1213
|
-
}
|
|
1214
|
-
if (isFormStateCondition(config.condition)) {
|
|
1215
|
-
if (config.condition === 'formInvalid' || config.condition === 'pageInvalid') {
|
|
1216
|
-
const reason = config.condition === 'formInvalid'
|
|
1217
|
-
? `it would create a validation cycle against the form's own validity`
|
|
1218
|
-
: `a value field has no page context for it to resolve against`;
|
|
1219
|
-
throw new DynamicFormError(`The '${config.condition}' form-state condition is not supported on value-field logic because ${reason}. ` +
|
|
1220
|
-
`Use 'formSubmitting', or apply this condition to a button or container instead.`);
|
|
1221
|
-
}
|
|
1222
|
-
applyLogicFn(config.type, path, createFormStateLogicFunction(config.condition));
|
|
1223
|
-
return;
|
|
1224
|
-
}
|
|
1225
|
-
const logicFn = trigger === 'debounced'
|
|
1226
|
-
? createDebouncedLogicFunction(config.condition, debounceMs)
|
|
1227
|
-
: createLogicFunction(config.condition);
|
|
1228
|
-
applyLogicFn(config.type, path, logicFn);
|
|
1229
|
-
}
|
|
1230
|
-
function applyLogicFn(type, path, logicFn) {
|
|
1231
|
-
switch (type) {
|
|
1232
|
-
case 'hidden':
|
|
1233
|
-
hidden(path, logicFn);
|
|
1234
|
-
break;
|
|
1235
|
-
case 'readonly':
|
|
1236
|
-
readonly(path, logicFn);
|
|
1237
|
-
break;
|
|
1238
|
-
case 'disabled':
|
|
1239
|
-
disabled(path, logicFn);
|
|
1240
|
-
break;
|
|
1241
|
-
case 'required':
|
|
1242
|
-
required(path, { when: logicFn });
|
|
1243
|
-
break;
|
|
1244
|
-
default: {
|
|
1245
|
-
const _exhaustive = type;
|
|
1246
|
-
throw new DynamicFormError(`Unhandled state logic type: ${_exhaustive}`);
|
|
1247
|
-
}
|
|
1248
|
-
}
|
|
1249
|
-
}
|
|
1250
|
-
function applyMultipleLogic(configs, fieldPath) {
|
|
1251
|
-
configs.forEach((config) => applyLogic(config, fieldPath));
|
|
1252
|
-
}
|
|
1253
|
-
|
|
1254
|
-
function createTypePredicateFunction(predicate, logger) {
|
|
1255
|
-
return (value) => {
|
|
1256
|
-
try {
|
|
1257
|
-
const result = ExpressionParser.evaluate(predicate, { value });
|
|
1258
|
-
return Boolean(result);
|
|
1259
|
-
}
|
|
1260
|
-
catch (error) {
|
|
1261
|
-
logger.error('Error evaluating type predicate:', predicate, error);
|
|
1262
|
-
return false;
|
|
1263
|
-
}
|
|
1264
|
-
};
|
|
1265
|
-
}
|
|
1266
|
-
|
|
1267
|
-
function applySchema(config, fieldPath) {
|
|
1268
|
-
const logger = inject(DynamicFormLogger);
|
|
1269
|
-
const schemaRegistry = inject(SchemaRegistryService);
|
|
1270
|
-
const schema = schemaRegistry.resolveSchema(config.schema);
|
|
1271
|
-
if (!schema) {
|
|
1272
|
-
const availableSchemas = Array.from(schemaRegistry.getAllSchemas().keys()).join(', ') || '<none>';
|
|
1273
|
-
logger.error(`Schema not found: '${config.schema}'. ` +
|
|
1274
|
-
`Available schemas: ${availableSchemas}. ` +
|
|
1275
|
-
`Ensure the schema is registered in your schema registry.`);
|
|
1276
|
-
return;
|
|
1277
|
-
}
|
|
1278
|
-
const schemaFn = createSchemaFunction(schema, logger);
|
|
1279
|
-
const path = fieldPath;
|
|
1280
|
-
switch (config.type) {
|
|
1281
|
-
case 'apply':
|
|
1282
|
-
apply(path, schemaFn);
|
|
1283
|
-
break;
|
|
1284
|
-
case 'applyWhen':
|
|
1285
|
-
if (config.condition) {
|
|
1286
|
-
const conditionFn = createLogicFunction(config.condition);
|
|
1287
|
-
applyWhen(path, conditionFn, schemaFn);
|
|
1288
|
-
}
|
|
1289
|
-
break;
|
|
1290
|
-
case 'applyWhenValue':
|
|
1291
|
-
if (config.typePredicate) {
|
|
1292
|
-
const predicate = createTypePredicateFunction(config.typePredicate, logger);
|
|
1293
|
-
applyWhenValue(path, predicate, schemaFn);
|
|
1294
|
-
}
|
|
1295
|
-
break;
|
|
1296
|
-
case 'applyEach':
|
|
1297
|
-
applyEach(path, schemaFn);
|
|
1298
|
-
break;
|
|
1299
|
-
}
|
|
1300
|
-
}
|
|
1301
|
-
function createSchemaFunction(schema, logger) {
|
|
1302
|
-
return (path) => {
|
|
1303
|
-
schema.validators?.forEach((validatorConfig) => {
|
|
1304
|
-
applyValidator(validatorConfig, path);
|
|
1305
|
-
});
|
|
1306
|
-
schema.logic?.forEach((logicConfig) => {
|
|
1307
|
-
applyLogic(logicConfig, path);
|
|
1308
|
-
});
|
|
1309
|
-
schema.subSchemas?.forEach((subSchemaConfig) => {
|
|
1310
|
-
applySchema(subSchemaConfig, path);
|
|
1311
|
-
});
|
|
1312
|
-
};
|
|
1313
|
-
}
|
|
1314
|
-
|
|
1315
|
-
function combineAncestorHiddenLogics(logics) {
|
|
1316
|
-
if (logics.length === 0)
|
|
1317
|
-
return undefined;
|
|
1318
|
-
if (logics.length === 1)
|
|
1319
|
-
return logics[0];
|
|
1320
|
-
return (ctx) => logics.some((fn) => fn(ctx));
|
|
1321
|
-
}
|
|
1322
|
-
function resolveFieldOwnContext(fieldDef, parentContext) {
|
|
1323
|
-
if (fieldDef.validateWhenHidden === undefined)
|
|
1324
|
-
return parentContext;
|
|
1325
|
-
if (fieldDef.validateWhenHidden === parentContext.validateWhenHidden)
|
|
1326
|
-
return parentContext;
|
|
1327
|
-
return {
|
|
1328
|
-
...parentContext,
|
|
1329
|
-
validateWhenHidden: fieldDef.validateWhenHidden,
|
|
1330
|
-
};
|
|
1331
|
-
}
|
|
1332
|
-
function resolveDescendantContext(fieldDef, ownContext) {
|
|
1333
|
-
let ancestorAlwaysHidden = ownContext.ancestorAlwaysHidden;
|
|
1334
|
-
let ancestorHiddenLogics = ownContext.ancestorHiddenLogics;
|
|
1335
|
-
let mutated = false;
|
|
1336
|
-
if (fieldDef.hidden === true && !ancestorAlwaysHidden) {
|
|
1337
|
-
ancestorAlwaysHidden = true;
|
|
1338
|
-
mutated = true;
|
|
1339
|
-
}
|
|
1340
|
-
if (fieldDef.logic) {
|
|
1341
|
-
for (const logic of fieldDef.logic) {
|
|
1342
|
-
if (!isStateLogicConfig(logic) || logic.type !== 'hidden')
|
|
1343
|
-
continue;
|
|
1344
|
-
const condition = logic.condition;
|
|
1345
|
-
if (condition === true) {
|
|
1346
|
-
if (!ancestorAlwaysHidden) {
|
|
1347
|
-
ancestorAlwaysHidden = true;
|
|
1348
|
-
mutated = true;
|
|
1349
|
-
}
|
|
1350
|
-
continue;
|
|
1351
|
-
}
|
|
1352
|
-
if (condition === false)
|
|
1353
|
-
continue;
|
|
1354
|
-
if (typeof condition === 'string')
|
|
1355
|
-
continue;
|
|
1356
|
-
const fn = createLogicFunction(condition);
|
|
1357
|
-
ancestorHiddenLogics = ancestorHiddenLogics.length === 0 ? [fn] : [...ancestorHiddenLogics, fn];
|
|
1358
|
-
mutated = true;
|
|
1359
|
-
}
|
|
1360
|
-
}
|
|
1361
|
-
if (!mutated)
|
|
1362
|
-
return ownContext;
|
|
1363
|
-
return {
|
|
1364
|
-
validateWhenHidden: ownContext.validateWhenHidden,
|
|
1365
|
-
ancestorAlwaysHidden,
|
|
1366
|
-
ancestorHiddenLogics,
|
|
1367
|
-
};
|
|
1368
|
-
}
|
|
1369
|
-
|
|
1370
|
-
const DEFAULT_FIELD_TREE_MAPPING_CONTEXT = {
|
|
1371
|
-
validateWhenHidden: false,
|
|
1372
|
-
ancestorAlwaysHidden: false,
|
|
1373
|
-
ancestorHiddenLogics: [],
|
|
1374
|
-
};
|
|
1375
|
-
function applyStringValidators(path, config) {
|
|
1376
|
-
if (config.email) {
|
|
1377
|
-
email(path);
|
|
1378
|
-
}
|
|
1379
|
-
if (config.minLength !== undefined) {
|
|
1380
|
-
minLength(path, config.minLength);
|
|
1381
|
-
}
|
|
1382
|
-
if (config.maxLength !== undefined) {
|
|
1383
|
-
maxLength(path, config.maxLength);
|
|
1384
|
-
}
|
|
1385
|
-
if (config.pattern) {
|
|
1386
|
-
let regex;
|
|
1387
|
-
if (typeof config.pattern === 'string') {
|
|
1388
|
-
try {
|
|
1389
|
-
regex = new RegExp(config.pattern);
|
|
1390
|
-
}
|
|
1391
|
-
catch (e) {
|
|
1392
|
-
throw new DynamicFormError(`Invalid regex pattern: '${config.pattern}' — ${e instanceof Error ? e.message : String(e)}`);
|
|
1393
|
-
}
|
|
1394
|
-
}
|
|
1395
|
-
else {
|
|
1396
|
-
regex = config.pattern;
|
|
1397
|
-
}
|
|
1398
|
-
pattern(path, regex);
|
|
1399
|
-
}
|
|
1400
|
-
}
|
|
1401
|
-
function applyNumberValidators(path, config) {
|
|
1402
|
-
if (config.min !== undefined) {
|
|
1403
|
-
min(path, config.min);
|
|
1404
|
-
}
|
|
1405
|
-
if (config.max !== undefined) {
|
|
1406
|
-
max(path, config.max);
|
|
1407
|
-
}
|
|
1408
|
-
}
|
|
1409
|
-
function getStringValidationConfig(fieldDef) {
|
|
1410
|
-
if (!fieldDef.email && fieldDef.minLength === undefined && fieldDef.maxLength === undefined && !fieldDef.pattern) {
|
|
1411
|
-
return undefined;
|
|
1412
|
-
}
|
|
1413
|
-
return {
|
|
1414
|
-
email: fieldDef.email,
|
|
1415
|
-
minLength: fieldDef.minLength,
|
|
1416
|
-
maxLength: fieldDef.maxLength,
|
|
1417
|
-
pattern: fieldDef.pattern,
|
|
1418
|
-
};
|
|
1419
|
-
}
|
|
1420
|
-
function getNumberValidationConfig(fieldDef) {
|
|
1421
|
-
const minVal = fieldDef.min ?? fieldDef.minValue;
|
|
1422
|
-
const maxVal = fieldDef.max ?? fieldDef.maxValue;
|
|
1423
|
-
if (minVal === undefined && maxVal === undefined) {
|
|
1424
|
-
return undefined;
|
|
1425
|
-
}
|
|
1426
|
-
return { min: minVal, max: maxVal };
|
|
1427
|
-
}
|
|
1428
|
-
function mapFieldToForm(fieldDef, fieldPath, inheritedContext = DEFAULT_FIELD_TREE_MAPPING_CONTEXT) {
|
|
1429
|
-
const ownContext = resolveFieldOwnContext(fieldDef, inheritedContext);
|
|
1430
|
-
if (isPageField(fieldDef) || isRowField(fieldDef) || isGenericContainerField(fieldDef)) {
|
|
1431
|
-
const descendantContext = resolveDescendantContext(fieldDef, ownContext);
|
|
1432
|
-
mapContainerChildren(fieldDef.fields, fieldPath, descendantContext);
|
|
1433
|
-
return;
|
|
1434
|
-
}
|
|
1435
|
-
if (isGroupField(fieldDef)) {
|
|
1436
|
-
const descendantContext = resolveDescendantContext(fieldDef, ownContext);
|
|
1437
|
-
mapContainerChildren(fieldDef.fields, fieldPath, descendantContext);
|
|
1438
|
-
return;
|
|
1439
|
-
}
|
|
1440
|
-
if (isArrayField(fieldDef)) {
|
|
1441
|
-
const descendantContext = resolveDescendantContext(fieldDef, ownContext);
|
|
1442
|
-
mapArrayFieldToForm(fieldDef, fieldPath, descendantContext);
|
|
1443
|
-
return;
|
|
1444
|
-
}
|
|
1445
|
-
mapLeafField(fieldDef, fieldPath, ownContext);
|
|
1446
|
-
}
|
|
1447
|
-
function mapContainerChildren(fields, parentPath, inheritedContext) {
|
|
1448
|
-
if (!fields)
|
|
1449
|
-
return;
|
|
1450
|
-
const pathRecord = parentPath;
|
|
1451
|
-
for (const field of fields) {
|
|
1452
|
-
if (isPageField(field) || isRowField(field) || isGenericContainerField(field)) {
|
|
1453
|
-
const layoutOwn = resolveFieldOwnContext(field, inheritedContext);
|
|
1454
|
-
const layoutDescendant = resolveDescendantContext(field, layoutOwn);
|
|
1455
|
-
mapContainerChildren(field.fields, parentPath, layoutDescendant);
|
|
1456
|
-
continue;
|
|
1457
|
-
}
|
|
1458
|
-
if (!field.key)
|
|
1459
|
-
continue;
|
|
1460
|
-
const childPath = pathRecord[field.key];
|
|
1461
|
-
if (childPath) {
|
|
1462
|
-
mapFieldToForm(field, childPath, inheritedContext);
|
|
1463
|
-
}
|
|
1464
|
-
}
|
|
1465
|
-
}
|
|
1466
|
-
function isValidationStateLogic(config) {
|
|
1467
|
-
return isStateLogicConfig(config) && config.type === 'required';
|
|
1468
|
-
}
|
|
1469
|
-
function mapLeafField(fieldDef, fieldPath, context) {
|
|
1470
|
-
const validationField = fieldDef;
|
|
1471
|
-
const path = fieldPath;
|
|
1472
|
-
applyFieldState(fieldDef, path);
|
|
1473
|
-
if (validationField.logic) {
|
|
1474
|
-
for (const config of validationField.logic) {
|
|
1475
|
-
if (!isValidationStateLogic(config)) {
|
|
1476
|
-
applyLogic(config, fieldPath);
|
|
1477
|
-
}
|
|
1478
|
-
}
|
|
1479
|
-
}
|
|
1480
|
-
if (validationField.schemas) {
|
|
1481
|
-
for (const config of validationField.schemas) {
|
|
1482
|
-
applySchema(config, fieldPath);
|
|
1483
|
-
}
|
|
1484
|
-
}
|
|
1485
|
-
const applyValidation = (validationPath) => {
|
|
1486
|
-
applySimpleValidationRules(validationField, validationPath);
|
|
1487
|
-
if (validationField.validators) {
|
|
1488
|
-
for (const config of validationField.validators) {
|
|
1489
|
-
applyValidator(config, validationPath);
|
|
1490
|
-
}
|
|
1491
|
-
}
|
|
1492
|
-
if (validationField.logic) {
|
|
1493
|
-
for (const config of validationField.logic) {
|
|
1494
|
-
if (isValidationStateLogic(config)) {
|
|
1495
|
-
applyLogic(config, validationPath);
|
|
1496
|
-
}
|
|
1497
|
-
}
|
|
1498
|
-
}
|
|
1197
|
+
},
|
|
1499
1198
|
};
|
|
1500
|
-
if (context.validateWhenHidden) {
|
|
1501
|
-
applyValidation(path);
|
|
1502
|
-
return;
|
|
1503
|
-
}
|
|
1504
|
-
if (context.ancestorAlwaysHidden) {
|
|
1505
|
-
return;
|
|
1506
|
-
}
|
|
1507
|
-
const ancestorHiddenLogic = combineAncestorHiddenLogics(context.ancestorHiddenLogics);
|
|
1508
|
-
applyWhen(path, (ctx) => {
|
|
1509
|
-
if (ctx.state.hidden())
|
|
1510
|
-
return false;
|
|
1511
|
-
if (ancestorHiddenLogic && ancestorHiddenLogic(ctx))
|
|
1512
|
-
return false;
|
|
1513
|
-
return true;
|
|
1514
|
-
}, schema((subPath) => applyValidation(subPath)));
|
|
1515
1199
|
}
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1200
|
+
class ArrayItemRegistryService {
|
|
1201
|
+
slots = new Map();
|
|
1202
|
+
slotFor(arrayPath) {
|
|
1203
|
+
let slot = this.slots.get(arrayPath);
|
|
1204
|
+
if (!slot) {
|
|
1205
|
+
slot = createSlot();
|
|
1206
|
+
this.slots.set(arrayPath, slot);
|
|
1207
|
+
}
|
|
1208
|
+
return slot;
|
|
1523
1209
|
}
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1210
|
+
clearAll() {
|
|
1211
|
+
for (const slot of this.slots.values())
|
|
1212
|
+
slot.clear();
|
|
1213
|
+
this.slots.clear();
|
|
1527
1214
|
}
|
|
1215
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: ArrayItemRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1216
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: ArrayItemRegistryService });
|
|
1528
1217
|
}
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
hidden(path, () => true);
|
|
1218
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: ArrayItemRegistryService, decorators: [{
|
|
1219
|
+
type: Injectable
|
|
1220
|
+
}] });
|
|
1221
|
+
|
|
1222
|
+
class SchemaRegistryService {
|
|
1223
|
+
registeredSchemas = new Map();
|
|
1224
|
+
registerSchema(schema) {
|
|
1225
|
+
this.registeredSchemas.set(schema.name, schema);
|
|
1538
1226
|
}
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
if (!isArrayField(arrayField)) {
|
|
1542
|
-
return;
|
|
1227
|
+
getSchema(name) {
|
|
1228
|
+
return this.registeredSchemas.get(name);
|
|
1543
1229
|
}
|
|
1544
|
-
|
|
1545
|
-
|
|
1230
|
+
getAllSchemas() {
|
|
1231
|
+
return new Map(this.registeredSchemas);
|
|
1546
1232
|
}
|
|
1547
|
-
|
|
1548
|
-
|
|
1233
|
+
clearSchemas() {
|
|
1234
|
+
this.registeredSchemas.clear();
|
|
1549
1235
|
}
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
if (metadataTemplate) {
|
|
1554
|
-
itemDefinitions = [
|
|
1555
|
-
Array.isArray(metadataTemplate) ? [...metadataTemplate] : metadataTemplate,
|
|
1556
|
-
];
|
|
1236
|
+
resolveSchema(schema) {
|
|
1237
|
+
if (typeof schema === 'string') {
|
|
1238
|
+
return this.getSchema(schema) || null;
|
|
1557
1239
|
}
|
|
1240
|
+
return schema;
|
|
1558
1241
|
}
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1242
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: SchemaRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1243
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: SchemaRegistryService });
|
|
1244
|
+
}
|
|
1245
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: SchemaRegistryService, decorators: [{
|
|
1246
|
+
type: Injectable
|
|
1247
|
+
}] });
|
|
1248
|
+
|
|
1249
|
+
function inheritContainerExclusion(container, child) {
|
|
1250
|
+
const inheritedHidden = container.hidden === true || child.inheritedExclusionState?.hidden === true;
|
|
1251
|
+
const inheritedDisabled = container.disabled === true || child.inheritedExclusionState?.disabled === true;
|
|
1252
|
+
const inheritedReadonly = container.readonly === true || child.inheritedExclusionState?.readonly === true;
|
|
1253
|
+
const hasInheritedState = inheritedHidden || inheritedDisabled || inheritedReadonly;
|
|
1254
|
+
const containerExcludeConfig = container.excludeValueIfHidden !== undefined ||
|
|
1255
|
+
container.excludeValueIfDisabled !== undefined ||
|
|
1256
|
+
container.excludeValueIfReadonly !== undefined;
|
|
1257
|
+
if (!hasInheritedState && !containerExcludeConfig) {
|
|
1258
|
+
return child;
|
|
1564
1259
|
}
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1260
|
+
return {
|
|
1261
|
+
...child,
|
|
1262
|
+
...(hasInheritedState
|
|
1263
|
+
? { inheritedExclusionState: { hidden: inheritedHidden, disabled: inheritedDisabled, readonly: inheritedReadonly } }
|
|
1264
|
+
: {}),
|
|
1265
|
+
excludeValueIfHidden: child.excludeValueIfHidden ?? container.excludeValueIfHidden,
|
|
1266
|
+
excludeValueIfDisabled: child.excludeValueIfDisabled ?? container.excludeValueIfDisabled,
|
|
1267
|
+
excludeValueIfReadonly: child.excludeValueIfReadonly ?? container.excludeValueIfReadonly,
|
|
1268
|
+
};
|
|
1269
|
+
}
|
|
1270
|
+
function flattenFields(fields, registry, options = {}) {
|
|
1271
|
+
const result = [];
|
|
1272
|
+
let autoKeyCounter = 0;
|
|
1273
|
+
for (const field of fields) {
|
|
1274
|
+
const valueHandling = getFieldValueHandling(field.type, registry);
|
|
1275
|
+
if (options.preserveRows && (isRowField(field) || isGenericContainerField(field))) {
|
|
1276
|
+
if (field.fields) {
|
|
1277
|
+
const flattenedChildren = flattenFields(normalizeFieldsArray(field.fields), registry, options);
|
|
1278
|
+
const autoPrefix = isRowField(field) ? 'auto_row' : 'auto_container';
|
|
1279
|
+
result.push({
|
|
1280
|
+
...field,
|
|
1281
|
+
fields: flattenedChildren,
|
|
1282
|
+
key: field.key || `${autoPrefix}_${autoKeyCounter++}`,
|
|
1283
|
+
});
|
|
1284
|
+
}
|
|
1570
1285
|
}
|
|
1571
|
-
else if (
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1286
|
+
else if (valueHandling === 'flatten' && 'fields' in field) {
|
|
1287
|
+
if (field.fields) {
|
|
1288
|
+
const fields = field.fields;
|
|
1289
|
+
const flattenedChildren = flattenFields(normalizeFieldsArray(fields), registry, options);
|
|
1290
|
+
result.push(...flattenedChildren.map((child) => inheritContainerExclusion(field, child)));
|
|
1291
|
+
}
|
|
1575
1292
|
}
|
|
1576
|
-
else {
|
|
1577
|
-
|
|
1293
|
+
else if (isGroupField(field)) {
|
|
1294
|
+
const childFieldsArray = Object.values(field.fields);
|
|
1295
|
+
const flattenedChildren = flattenFields(childFieldsArray, registry, options);
|
|
1296
|
+
result.push({
|
|
1297
|
+
...field,
|
|
1298
|
+
fields: flattenedChildren,
|
|
1299
|
+
key: field.key || `auto_group_${autoKeyCounter++}`,
|
|
1300
|
+
});
|
|
1578
1301
|
}
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
return;
|
|
1585
|
-
}
|
|
1586
|
-
const itemSchema = schema((itemPath) => {
|
|
1587
|
-
const pathRecord = itemPath;
|
|
1588
|
-
for (const templateField of allObjectFields) {
|
|
1589
|
-
if (isRowField(templateField) || isPageField(templateField) || isGenericContainerField(templateField)) {
|
|
1590
|
-
const layoutOwn = resolveFieldOwnContext(templateField, context);
|
|
1591
|
-
const layoutDescendant = resolveDescendantContext(templateField, layoutOwn);
|
|
1592
|
-
mapContainerChildren(templateField.fields, itemPath, layoutDescendant);
|
|
1593
|
-
}
|
|
1594
|
-
else if (isGroupField(templateField)) {
|
|
1595
|
-
const groupKey = templateField.key;
|
|
1596
|
-
const groupOwn = resolveFieldOwnContext(templateField, context);
|
|
1597
|
-
const groupDescendant = resolveDescendantContext(templateField, groupOwn);
|
|
1598
|
-
if (groupKey) {
|
|
1599
|
-
const groupPath = pathRecord[groupKey];
|
|
1600
|
-
if (groupPath) {
|
|
1601
|
-
mapContainerChildren(templateField.fields, groupPath, groupDescendant);
|
|
1602
|
-
}
|
|
1603
|
-
}
|
|
1604
|
-
else {
|
|
1605
|
-
mapContainerChildren(templateField.fields, itemPath, groupDescendant);
|
|
1606
|
-
}
|
|
1607
|
-
}
|
|
1608
|
-
else {
|
|
1609
|
-
const fieldKey = templateField.key;
|
|
1610
|
-
if (fieldKey) {
|
|
1611
|
-
const fieldPathForKey = pathRecord[fieldKey];
|
|
1612
|
-
if (fieldPathForKey) {
|
|
1613
|
-
mapFieldToForm(templateField, fieldPathForKey, context);
|
|
1614
|
-
}
|
|
1302
|
+
else if (isArrayField(field)) {
|
|
1303
|
+
const flattenedItemTemplates = field.fields.map((itemDef) => {
|
|
1304
|
+
if (!Array.isArray(itemDef)) {
|
|
1305
|
+
const flattened = flattenFields([itemDef], registry, options);
|
|
1306
|
+
return flattened[0];
|
|
1615
1307
|
}
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
for (const field of itemFields) {
|
|
1624
|
-
if (isRowField(field) || isPageField(field) || isGenericContainerField(field)) {
|
|
1625
|
-
const containerKey = `__container_${field.type}_${JSON.stringify(field.fields?.map((f) => f.key))}`;
|
|
1626
|
-
if (!seenKeys.has(containerKey)) {
|
|
1627
|
-
seenKeys.add(containerKey);
|
|
1628
|
-
allFields.push(field);
|
|
1629
|
-
}
|
|
1308
|
+
return flattenFields([...itemDef], registry, options);
|
|
1309
|
+
});
|
|
1310
|
+
result.push({
|
|
1311
|
+
...field,
|
|
1312
|
+
fields: flattenedItemTemplates,
|
|
1313
|
+
key: field.key || `auto_array_${autoKeyCounter++}`,
|
|
1314
|
+
});
|
|
1630
1315
|
}
|
|
1631
1316
|
else {
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
}
|
|
1317
|
+
const key = field.key || `auto_field_${autoKeyCounter++}`;
|
|
1318
|
+
result.push({
|
|
1319
|
+
...field,
|
|
1320
|
+
key,
|
|
1321
|
+
});
|
|
1637
1322
|
}
|
|
1638
1323
|
}
|
|
1324
|
+
return result;
|
|
1639
1325
|
}
|
|
1640
1326
|
|
|
1641
1327
|
function getFieldDefaultValue(field, registry) {
|
|
@@ -1730,169 +1416,15 @@ function getFieldDefaultValue(field, registry) {
|
|
|
1730
1416
|
}
|
|
1731
1417
|
}
|
|
1732
1418
|
return itemValue;
|
|
1733
|
-
});
|
|
1734
|
-
}
|
|
1735
|
-
if (field.type === 'input' && 'props' in field) {
|
|
1736
|
-
const props = field.props;
|
|
1737
|
-
if (props?.type === 'number') {
|
|
1738
|
-
return NaN;
|
|
1739
|
-
}
|
|
1740
|
-
}
|
|
1741
|
-
return '';
|
|
1742
|
-
}
|
|
1743
|
-
|
|
1744
|
-
function applyFormLevelSchema(path, formLevelSchema) {
|
|
1745
|
-
if (isStandardSchemaMarker(formLevelSchema)) {
|
|
1746
|
-
validateStandardSchema(path, formLevelSchema.schema);
|
|
1747
|
-
}
|
|
1748
|
-
else if (typeof formLevelSchema === 'function') {
|
|
1749
|
-
formLevelSchema(path);
|
|
1750
|
-
}
|
|
1751
|
-
}
|
|
1752
|
-
function createFormLevelSchema(formLevelSchema) {
|
|
1753
|
-
return schema((path) => {
|
|
1754
|
-
applyFormLevelSchema(path, formLevelSchema);
|
|
1755
|
-
});
|
|
1756
|
-
}
|
|
1757
|
-
|
|
1758
|
-
const VALIDATION_EXECUTION_DEFAULTS = new InjectionToken('VALIDATION_EXECUTION_DEFAULTS', {
|
|
1759
|
-
providedIn: 'root',
|
|
1760
|
-
factory: () => ({
|
|
1761
|
-
validateWhenHidden: false,
|
|
1762
|
-
}),
|
|
1763
|
-
});
|
|
1764
|
-
|
|
1765
|
-
function createSchemaFromFields(fields, registry, options = {}) {
|
|
1766
|
-
const validationExecutionDefaults = inject(VALIDATION_EXECUTION_DEFAULTS);
|
|
1767
|
-
const { formLevelSchema } = options;
|
|
1768
|
-
const rootContext = {
|
|
1769
|
-
validateWhenHidden: options.validateWhenHidden ?? validationExecutionDefaults.validateWhenHidden,
|
|
1770
|
-
ancestorAlwaysHidden: false,
|
|
1771
|
-
ancestorHiddenLogics: [],
|
|
1772
|
-
};
|
|
1773
|
-
return schema((path) => {
|
|
1774
|
-
for (const fieldDef of fields) {
|
|
1775
|
-
const valueHandling = getFieldValueHandling(fieldDef.type, registry);
|
|
1776
|
-
if (valueHandling === 'exclude') {
|
|
1777
|
-
continue;
|
|
1778
|
-
}
|
|
1779
|
-
if (valueHandling === 'flatten' && hasChildFields(fieldDef)) {
|
|
1780
|
-
const flattenedOwn = resolveFieldOwnContext(fieldDef, rootContext);
|
|
1781
|
-
const flattenedContext = resolveDescendantContext(fieldDef, flattenedOwn);
|
|
1782
|
-
for (const childField of normalizeFieldsArray(fieldDef.fields)) {
|
|
1783
|
-
if (!childField.key)
|
|
1784
|
-
continue;
|
|
1785
|
-
const childPath = path[childField.key];
|
|
1786
|
-
if (childPath) {
|
|
1787
|
-
mapFieldToForm(childField, childPath, flattenedContext);
|
|
1788
|
-
}
|
|
1789
|
-
}
|
|
1790
|
-
continue;
|
|
1791
|
-
}
|
|
1792
|
-
const fieldPath = path[fieldDef.key];
|
|
1793
|
-
if (!fieldPath) {
|
|
1794
|
-
continue;
|
|
1795
|
-
}
|
|
1796
|
-
mapFieldToForm(fieldDef, fieldPath, rootContext);
|
|
1797
|
-
}
|
|
1798
|
-
if (formLevelSchema) {
|
|
1799
|
-
applyFormLevelSchema(path, formLevelSchema);
|
|
1800
|
-
}
|
|
1801
|
-
});
|
|
1802
|
-
}
|
|
1803
|
-
function fieldsToDefaultValues(fields, registry) {
|
|
1804
|
-
const defaultValues = {};
|
|
1805
|
-
for (const field of fields) {
|
|
1806
|
-
if (!field.key)
|
|
1807
|
-
continue;
|
|
1808
|
-
const valueHandling = getFieldValueHandling(field.type, registry);
|
|
1809
|
-
if (valueHandling === 'flatten') {
|
|
1810
|
-
continue;
|
|
1811
|
-
}
|
|
1812
|
-
const value = getFieldDefaultValue(field, registry);
|
|
1813
|
-
if (value !== undefined) {
|
|
1814
|
-
defaultValues[field.key] = value;
|
|
1815
|
-
}
|
|
1816
|
-
}
|
|
1817
|
-
return defaultValues;
|
|
1818
|
-
}
|
|
1819
|
-
|
|
1820
|
-
function inheritContainerExclusion(container, child) {
|
|
1821
|
-
const inheritedHidden = container.hidden === true || child.inheritedExclusionState?.hidden === true;
|
|
1822
|
-
const inheritedDisabled = container.disabled === true || child.inheritedExclusionState?.disabled === true;
|
|
1823
|
-
const inheritedReadonly = container.readonly === true || child.inheritedExclusionState?.readonly === true;
|
|
1824
|
-
const hasInheritedState = inheritedHidden || inheritedDisabled || inheritedReadonly;
|
|
1825
|
-
const containerExcludeConfig = container.excludeValueIfHidden !== undefined ||
|
|
1826
|
-
container.excludeValueIfDisabled !== undefined ||
|
|
1827
|
-
container.excludeValueIfReadonly !== undefined;
|
|
1828
|
-
if (!hasInheritedState && !containerExcludeConfig) {
|
|
1829
|
-
return child;
|
|
1830
|
-
}
|
|
1831
|
-
return {
|
|
1832
|
-
...child,
|
|
1833
|
-
...(hasInheritedState
|
|
1834
|
-
? { inheritedExclusionState: { hidden: inheritedHidden, disabled: inheritedDisabled, readonly: inheritedReadonly } }
|
|
1835
|
-
: {}),
|
|
1836
|
-
excludeValueIfHidden: child.excludeValueIfHidden ?? container.excludeValueIfHidden,
|
|
1837
|
-
excludeValueIfDisabled: child.excludeValueIfDisabled ?? container.excludeValueIfDisabled,
|
|
1838
|
-
excludeValueIfReadonly: child.excludeValueIfReadonly ?? container.excludeValueIfReadonly,
|
|
1839
|
-
};
|
|
1840
|
-
}
|
|
1841
|
-
function flattenFields(fields, registry, options = {}) {
|
|
1842
|
-
const result = [];
|
|
1843
|
-
let autoKeyCounter = 0;
|
|
1844
|
-
for (const field of fields) {
|
|
1845
|
-
const valueHandling = getFieldValueHandling(field.type, registry);
|
|
1846
|
-
if (options.preserveRows && (isRowField(field) || isGenericContainerField(field))) {
|
|
1847
|
-
if (field.fields) {
|
|
1848
|
-
const flattenedChildren = flattenFields(normalizeFieldsArray(field.fields), registry, options);
|
|
1849
|
-
const autoPrefix = isRowField(field) ? 'auto_row' : 'auto_container';
|
|
1850
|
-
result.push({
|
|
1851
|
-
...field,
|
|
1852
|
-
fields: flattenedChildren,
|
|
1853
|
-
key: field.key || `${autoPrefix}_${autoKeyCounter++}`,
|
|
1854
|
-
});
|
|
1855
|
-
}
|
|
1856
|
-
}
|
|
1857
|
-
else if (valueHandling === 'flatten' && 'fields' in field) {
|
|
1858
|
-
if (field.fields) {
|
|
1859
|
-
const fields = field.fields;
|
|
1860
|
-
const flattenedChildren = flattenFields(normalizeFieldsArray(fields), registry, options);
|
|
1861
|
-
result.push(...flattenedChildren.map((child) => inheritContainerExclusion(field, child)));
|
|
1862
|
-
}
|
|
1863
|
-
}
|
|
1864
|
-
else if (isGroupField(field)) {
|
|
1865
|
-
const childFieldsArray = Object.values(field.fields);
|
|
1866
|
-
const flattenedChildren = flattenFields(childFieldsArray, registry, options);
|
|
1867
|
-
result.push({
|
|
1868
|
-
...field,
|
|
1869
|
-
fields: flattenedChildren,
|
|
1870
|
-
key: field.key || `auto_group_${autoKeyCounter++}`,
|
|
1871
|
-
});
|
|
1872
|
-
}
|
|
1873
|
-
else if (isArrayField(field)) {
|
|
1874
|
-
const flattenedItemTemplates = field.fields.map((itemDef) => {
|
|
1875
|
-
if (!Array.isArray(itemDef)) {
|
|
1876
|
-
const flattened = flattenFields([itemDef], registry, options);
|
|
1877
|
-
return flattened[0];
|
|
1878
|
-
}
|
|
1879
|
-
return flattenFields([...itemDef], registry, options);
|
|
1880
|
-
});
|
|
1881
|
-
result.push({
|
|
1882
|
-
...field,
|
|
1883
|
-
fields: flattenedItemTemplates,
|
|
1884
|
-
key: field.key || `auto_array_${autoKeyCounter++}`,
|
|
1885
|
-
});
|
|
1886
|
-
}
|
|
1887
|
-
else {
|
|
1888
|
-
const key = field.key || `auto_field_${autoKeyCounter++}`;
|
|
1889
|
-
result.push({
|
|
1890
|
-
...field,
|
|
1891
|
-
key,
|
|
1892
|
-
});
|
|
1419
|
+
});
|
|
1420
|
+
}
|
|
1421
|
+
if (field.type === 'input' && 'props' in field) {
|
|
1422
|
+
const props = field.props;
|
|
1423
|
+
if (props?.type === 'number') {
|
|
1424
|
+
return NaN;
|
|
1893
1425
|
}
|
|
1894
1426
|
}
|
|
1895
|
-
return
|
|
1427
|
+
return '';
|
|
1896
1428
|
}
|
|
1897
1429
|
|
|
1898
1430
|
function createContainerFieldProcessors() {
|
|
@@ -2329,60 +1861,6 @@ function isValidFormConfiguration(fields) {
|
|
|
2329
1861
|
return FormModeValidator.validateFormConfiguration(fields).isValid;
|
|
2330
1862
|
}
|
|
2331
1863
|
|
|
2332
|
-
const COMPONENT_CACHE = new InjectionToken('COMPONENT_CACHE', {
|
|
2333
|
-
providedIn: 'root',
|
|
2334
|
-
factory: () => new Map(),
|
|
2335
|
-
});
|
|
2336
|
-
function injectFieldRegistry() {
|
|
2337
|
-
const registry = inject(FIELD_REGISTRY);
|
|
2338
|
-
const componentCache = inject(COMPONENT_CACHE);
|
|
2339
|
-
return {
|
|
2340
|
-
getType(name) {
|
|
2341
|
-
return registry.get(name);
|
|
2342
|
-
},
|
|
2343
|
-
hasType(name) {
|
|
2344
|
-
return registry.has(name);
|
|
2345
|
-
},
|
|
2346
|
-
async loadTypeComponent(name) {
|
|
2347
|
-
const fieldType = registry.get(name);
|
|
2348
|
-
if (!fieldType) {
|
|
2349
|
-
throw new DynamicFormError(`Field type "${name}" is not registered`);
|
|
2350
|
-
}
|
|
2351
|
-
if (!fieldType.loadComponent) {
|
|
2352
|
-
return undefined;
|
|
2353
|
-
}
|
|
2354
|
-
const cached = componentCache.get(name);
|
|
2355
|
-
if (cached) {
|
|
2356
|
-
return cached;
|
|
2357
|
-
}
|
|
2358
|
-
try {
|
|
2359
|
-
const component = resolveDefaultExport(await fieldType.loadComponent());
|
|
2360
|
-
if (component) {
|
|
2361
|
-
componentCache.set(name, component);
|
|
2362
|
-
}
|
|
2363
|
-
return component;
|
|
2364
|
-
}
|
|
2365
|
-
catch (error) {
|
|
2366
|
-
throw new DynamicFormError(`Failed to load component for field type "${name}": ${error}`);
|
|
2367
|
-
}
|
|
2368
|
-
},
|
|
2369
|
-
getLoadedComponent(name) {
|
|
2370
|
-
return componentCache.get(name);
|
|
2371
|
-
},
|
|
2372
|
-
getTypes() {
|
|
2373
|
-
return Array.from(registry.values());
|
|
2374
|
-
},
|
|
2375
|
-
registerTypes(types) {
|
|
2376
|
-
types.forEach((type) => {
|
|
2377
|
-
registry.set(type.name, type);
|
|
2378
|
-
});
|
|
2379
|
-
},
|
|
2380
|
-
get raw() {
|
|
2381
|
-
return registry;
|
|
2382
|
-
},
|
|
2383
|
-
};
|
|
2384
|
-
}
|
|
2385
|
-
|
|
2386
1864
|
function collectFieldData(fields, data, registry, pathPrefix, collectKeys = true) {
|
|
2387
1865
|
for (const field of fields) {
|
|
2388
1866
|
const valueHandling = field.type ? getFieldValueHandling(field.type, registry) : 'include';
|
|
@@ -2429,7 +1907,9 @@ function validateRegexPattern(pattern, fieldKey, errors) {
|
|
|
2429
1907
|
new RegExp(pattern);
|
|
2430
1908
|
}
|
|
2431
1909
|
catch (e) {
|
|
2432
|
-
|
|
1910
|
+
errors.push(DEV_MODE
|
|
1911
|
+
? `Invalid regex pattern in validator for field '${fieldKey}': '${pattern}' — ${e instanceof Error ? e.message : String(e)}`
|
|
1912
|
+
: `Invalid regex pattern: '${pattern}'`);
|
|
2433
1913
|
}
|
|
2434
1914
|
}
|
|
2435
1915
|
function validateNoDuplicateKeys(allKeys) {
|
|
@@ -2445,7 +1925,9 @@ function validateNoDuplicateKeys(allKeys) {
|
|
|
2445
1925
|
const duplicateList = Array.from(duplicates)
|
|
2446
1926
|
.map((k) => `'${k}'`)
|
|
2447
1927
|
.join(', ');
|
|
2448
|
-
|
|
1928
|
+
throw new DynamicFormError(DEV_MODE
|
|
1929
|
+
? `Duplicate field keys detected: ${duplicateList}. Field keys must be unique within their group scope (the same key may appear inside different groups).`
|
|
1930
|
+
: `Duplicate field keys detected: ${duplicateList}.`);
|
|
2449
1931
|
}
|
|
2450
1932
|
}
|
|
2451
1933
|
function validateNoDomIdCollisions(domIds) {
|
|
@@ -2461,9 +1943,11 @@ function validateNoDomIdCollisions(domIds) {
|
|
|
2461
1943
|
const duplicateList = Array.from(duplicates)
|
|
2462
1944
|
.map((k) => `'${k}'`)
|
|
2463
1945
|
.join(', ');
|
|
2464
|
-
|
|
2465
|
-
`
|
|
2466
|
-
|
|
1946
|
+
throw new DynamicFormError(DEV_MODE
|
|
1947
|
+
? `DOM id collision detected for: ${duplicateList}. Group-nested keys are joined with '_' to form DOM ids ` +
|
|
1948
|
+
`(e.g. group 'foo' + leaf 'bar' renders as id='foo_bar'). A separate top-level key like 'foo_bar' or a different ` +
|
|
1949
|
+
`group/leaf pair that underscores to the same string will produce duplicate ids. Rename one of the colliding keys.`
|
|
1950
|
+
: `DOM id collision detected for: ${duplicateList}.`);
|
|
2467
1951
|
}
|
|
2468
1952
|
}
|
|
2469
1953
|
function validateFieldTypesRegistered(allTypes, registry, logger) {
|
|
@@ -2493,12 +1977,15 @@ function validateFormConfig(fields, registry, logger) {
|
|
|
2493
1977
|
const list = Array.from(new Set(data.dottedKeys))
|
|
2494
1978
|
.map((k) => `'${k}'`)
|
|
2495
1979
|
.join(', ');
|
|
2496
|
-
|
|
2497
|
-
`(
|
|
1980
|
+
throw new DynamicFormError(DEV_MODE
|
|
1981
|
+
? `Field key(s) contain a dot: ${list}. Dotted keys are not supported — express nesting with 'group' fields ` +
|
|
1982
|
+
`(e.g. { type: 'group', key: 'address', fields: [{ type: 'input', key: 'city' }] }).`
|
|
1983
|
+
: `Field key(s) contain a dot: ${list}.`);
|
|
2498
1984
|
}
|
|
2499
1985
|
validateNoDuplicateKeys(data.keys);
|
|
2500
1986
|
validateNoDomIdCollisions(data.domIds);
|
|
2501
|
-
|
|
1987
|
+
if (DEV_MODE)
|
|
1988
|
+
validateFieldTypesRegistered(data.types, registry, logger);
|
|
2502
1989
|
if (data.regexErrors.length > 0) {
|
|
2503
1990
|
throw new DynamicFormError(data.regexErrors.length === 1
|
|
2504
1991
|
? data.regexErrors[0]
|
|
@@ -2713,6 +2200,33 @@ function resolveExclusionConfig(global, form, field) {
|
|
|
2713
2200
|
excludeValueIfReadonly: field?.excludeValueIfReadonly ?? form?.excludeValueIfReadonly ?? global.excludeValueIfReadonly,
|
|
2714
2201
|
};
|
|
2715
2202
|
}
|
|
2203
|
+
function hasEnabledValueExclusion(fields, global, form) {
|
|
2204
|
+
return fields.some((field) => {
|
|
2205
|
+
const resolved = resolveExclusionConfig(global, form, field);
|
|
2206
|
+
if (resolved.excludeValueIfHidden || resolved.excludeValueIfDisabled || resolved.excludeValueIfReadonly) {
|
|
2207
|
+
return true;
|
|
2208
|
+
}
|
|
2209
|
+
return (isGroupField(field) && !!field.fields && hasEnabledValueExclusion(Object.values(field.fields), global, form));
|
|
2210
|
+
});
|
|
2211
|
+
}
|
|
2212
|
+
function filterDeclaredValueShape(rawValue, fields, registry) {
|
|
2213
|
+
const kept = {};
|
|
2214
|
+
for (const field of fields) {
|
|
2215
|
+
const key = field.key;
|
|
2216
|
+
if (!key || !(key in rawValue))
|
|
2217
|
+
continue;
|
|
2218
|
+
const valueHandling = getFieldValueHandling(field.type, registry);
|
|
2219
|
+
if (valueHandling === 'exclude' || valueHandling === 'flatten')
|
|
2220
|
+
continue;
|
|
2221
|
+
const value = rawValue[key];
|
|
2222
|
+
if (isGroupField(field) && field.fields && value !== null && typeof value === 'object' && !Array.isArray(value)) {
|
|
2223
|
+
kept[key] = filterDeclaredValueShape(value, Object.values(field.fields), registry);
|
|
2224
|
+
continue;
|
|
2225
|
+
}
|
|
2226
|
+
kept[key] = value;
|
|
2227
|
+
}
|
|
2228
|
+
return kept;
|
|
2229
|
+
}
|
|
2716
2230
|
function shouldExcludeField(field, fieldState, config) {
|
|
2717
2231
|
const state = fieldState();
|
|
2718
2232
|
const inherited = field.inheritedExclusionState;
|
|
@@ -2730,7 +2244,10 @@ function shouldExcludeField(field, fieldState, config) {
|
|
|
2730
2244
|
}
|
|
2731
2245
|
return false;
|
|
2732
2246
|
}
|
|
2733
|
-
function filterFormValue(rawValue, schemaFields, formTree, registry, globalDefaults, formOptions) {
|
|
2247
|
+
function filterFormValue(rawValue, schemaFields, formTree, registry, globalDefaults, formOptions, exclusionEnabled = hasEnabledValueExclusion(schemaFields, globalDefaults, formOptions)) {
|
|
2248
|
+
if (!exclusionEnabled) {
|
|
2249
|
+
return filterDeclaredValueShape(rawValue, schemaFields, registry);
|
|
2250
|
+
}
|
|
2734
2251
|
const result = {};
|
|
2735
2252
|
for (const field of schemaFields) {
|
|
2736
2253
|
const key = field.key;
|
|
@@ -2766,7 +2283,7 @@ function filterFormValue(rawValue, schemaFields, formTree, registry, globalDefau
|
|
|
2766
2283
|
if (groupValue && typeof groupValue === 'object') {
|
|
2767
2284
|
const childFields = Object.values(field.fields);
|
|
2768
2285
|
const nestedTree = fieldState;
|
|
2769
|
-
result[key] = filterFormValue(groupValue, childFields, nestedTree, registry, globalDefaults, formOptions);
|
|
2286
|
+
result[key] = filterFormValue(groupValue, childFields, nestedTree, registry, globalDefaults, formOptions, true);
|
|
2770
2287
|
}
|
|
2771
2288
|
else {
|
|
2772
2289
|
result[key] = groupValue;
|
|
@@ -3079,7 +2596,7 @@ class FormStateMachine {
|
|
|
3079
2596
|
if (!isTransitioningState(state) || state.phase !== Phase.Restoring) {
|
|
3080
2597
|
return { state, sideEffects: [] };
|
|
3081
2598
|
}
|
|
3082
|
-
|
|
2599
|
+
if (DEV_MODE && !state.pendingFormSetup) {
|
|
3083
2600
|
this.config.logger.warn('handleRestoreComplete: pendingFormSetup was not set — falling back to recomputing. ' +
|
|
3084
2601
|
'This indicates a bug in the transition flow.');
|
|
3085
2602
|
}
|
|
@@ -3142,7 +2659,7 @@ class FormStateMachine {
|
|
|
3142
2659
|
const state = untracked(() => this._state());
|
|
3143
2660
|
if (!isTransitioningState(state) || state.phase !== Phase.Restoring)
|
|
3144
2661
|
return;
|
|
3145
|
-
|
|
2662
|
+
if (DEV_MODE && !state.pendingFormSetup) {
|
|
3146
2663
|
this.config.logger.warn('RestoreValues effect: pendingFormSetup was not set — falling back to recomputing.');
|
|
3147
2664
|
}
|
|
3148
2665
|
const formSetup = state.pendingFormSetup ?? this.config.createFormSetup(state.pendingConfig);
|
|
@@ -3284,13 +2801,6 @@ class FormStateManager {
|
|
|
3284
2801
|
return detectFormMode(config.fields || []);
|
|
3285
2802
|
}, /* @ts-ignore */
|
|
3286
2803
|
...(ngDevMode ? [{ debugName: "formModeDetection" }] : /* istanbul ignore next */ []));
|
|
3287
|
-
formConfigValidation = computed(() => {
|
|
3288
|
-
const config = this.activeConfig();
|
|
3289
|
-
if (!config)
|
|
3290
|
-
return { isValid: true, errors: [], warnings: [] };
|
|
3291
|
-
return FormModeValidator.validateFormConfiguration(config.fields || []);
|
|
3292
|
-
}, /* @ts-ignore */
|
|
3293
|
-
...(ngDevMode ? [{ debugName: "formConfigValidation" }] : /* istanbul ignore next */ []));
|
|
3294
2804
|
effectiveFormOptions = computed(() => {
|
|
3295
2805
|
const config = this.activeConfig();
|
|
3296
2806
|
const configOptions = config?.options || {};
|
|
@@ -3298,6 +2808,8 @@ class FormStateManager {
|
|
|
3298
2808
|
return { ...configOptions, ...inputOptions };
|
|
3299
2809
|
}, /* @ts-ignore */
|
|
3300
2810
|
...(ngDevMode ? [{ debugName: "effectiveFormOptions" }] : /* istanbul ignore next */ []));
|
|
2811
|
+
pendingPageRequest = signal(null, /* @ts-ignore */
|
|
2812
|
+
...(ngDevMode ? [{ debugName: "pendingPageRequest" }] : /* istanbul ignore next */ []));
|
|
3301
2813
|
pageFieldDefinitions = computed(() => {
|
|
3302
2814
|
const config = this.activeConfig();
|
|
3303
2815
|
const mode = this.formModeDetection().mode;
|
|
@@ -3335,21 +2847,70 @@ class FormStateManager {
|
|
|
3335
2847
|
return this.createEmptyFormSetup(registry);
|
|
3336
2848
|
}, /* @ts-ignore */
|
|
3337
2849
|
...(ngDevMode ? [{ debugName: "formSetup" }] : /* istanbul ignore next */ []));
|
|
2850
|
+
formSetupConfig = computed(() => {
|
|
2851
|
+
const state = this.machine.state();
|
|
2852
|
+
if (isReadyState(state))
|
|
2853
|
+
return state.config;
|
|
2854
|
+
if (isTransitioningState(state)) {
|
|
2855
|
+
if (state.phase === Phase.Restoring && state.pendingFormSetup) {
|
|
2856
|
+
return state.pendingConfig;
|
|
2857
|
+
}
|
|
2858
|
+
return state.currentConfig;
|
|
2859
|
+
}
|
|
2860
|
+
if (state.type === LifecycleState.Initializing)
|
|
2861
|
+
return state.config;
|
|
2862
|
+
if (state.type === LifecycleState.Uninitialized)
|
|
2863
|
+
return this.activeConfig();
|
|
2864
|
+
return undefined;
|
|
2865
|
+
}, /* @ts-ignore */
|
|
2866
|
+
...(ngDevMode ? [{ debugName: "formSetupConfig" }] : /* istanbul ignore next */ []));
|
|
2867
|
+
activePageState = linkedSignal({ ...(ngDevMode ? { debugName: "activePageState" } : /* istanbul ignore next */ {}), source: this.activeConfig,
|
|
2868
|
+
computation: (config) => ({ config, initialized: false, index: 0 }) });
|
|
3338
2869
|
defaultValues = linkedSignal(() => this.formSetup().defaultValues, /* @ts-ignore */
|
|
3339
2870
|
...(ngDevMode ? [{ debugName: "defaultValues" }] : /* istanbul ignore next */ []));
|
|
3340
2871
|
validKeys = computed(() => {
|
|
3341
2872
|
const schemaFields = this.formSetup().schemaFields;
|
|
3342
2873
|
if (!schemaFields || schemaFields.length === 0)
|
|
3343
2874
|
return undefined;
|
|
3344
|
-
|
|
2875
|
+
const keys = new Set();
|
|
2876
|
+
for (const field of schemaFields) {
|
|
2877
|
+
if (field.key === undefined)
|
|
2878
|
+
continue;
|
|
2879
|
+
keys.add(field.key);
|
|
2880
|
+
}
|
|
2881
|
+
return keys;
|
|
3345
2882
|
}, /* @ts-ignore */
|
|
3346
2883
|
...(ngDevMode ? [{ debugName: "validKeys" }] : /* istanbul ignore next */ []));
|
|
3347
|
-
|
|
2884
|
+
boundValueExclusionEnabled = computed(() => {
|
|
2885
|
+
const setup = this.formSetup();
|
|
2886
|
+
if (!setup.schemaFields || setup.schemaFields.length === 0)
|
|
2887
|
+
return false;
|
|
2888
|
+
const options = this.effectiveFormOptions();
|
|
2889
|
+
return hasEnabledValueExclusion(setup.schemaFields, BOUND_VALUE_EXCLUSION_BASELINE, {
|
|
2890
|
+
excludeValueIfHidden: options.excludeValueIfHidden,
|
|
2891
|
+
excludeValueIfDisabled: options.excludeValueIfDisabled,
|
|
2892
|
+
excludeValueIfReadonly: options.excludeValueIfReadonly,
|
|
2893
|
+
});
|
|
2894
|
+
}, /* @ts-ignore */
|
|
2895
|
+
...(ngDevMode ? [{ debugName: "boundValueExclusionEnabled" }] : /* istanbul ignore next */ []));
|
|
2896
|
+
valueExclusionEnabled = computed(() => {
|
|
3348
2897
|
const setup = this.formSetup();
|
|
3349
2898
|
if (!setup.schemaFields || setup.schemaFields.length === 0)
|
|
2899
|
+
return false;
|
|
2900
|
+
const options = this.effectiveFormOptions();
|
|
2901
|
+
return hasEnabledValueExclusion(setup.schemaFields, this.valueExclusionDefaults, {
|
|
2902
|
+
excludeValueIfHidden: options.excludeValueIfHidden,
|
|
2903
|
+
excludeValueIfDisabled: options.excludeValueIfDisabled,
|
|
2904
|
+
excludeValueIfReadonly: options.excludeValueIfReadonly,
|
|
2905
|
+
});
|
|
2906
|
+
}, /* @ts-ignore */
|
|
2907
|
+
...(ngDevMode ? [{ debugName: "valueExclusionEnabled" }] : /* istanbul ignore next */ []));
|
|
2908
|
+
fieldStateSnapshot = computed(() => {
|
|
2909
|
+
const schemaFields = this.formSetup().schemaFields;
|
|
2910
|
+
if (!this.formSchemaReady() || !this.boundValueExclusionEnabled() || !schemaFields || schemaFields.length === 0)
|
|
3350
2911
|
return {};
|
|
3351
2912
|
const snapshot = {};
|
|
3352
|
-
collectFieldStateSnapshot(
|
|
2913
|
+
collectFieldStateSnapshot(schemaFields, asFieldTreeRecord(this.form()), [], snapshot);
|
|
3353
2914
|
return snapshot;
|
|
3354
2915
|
}, /* @ts-ignore */
|
|
3355
2916
|
...(ngDevMode ? [{ debugName: "fieldStateSnapshot" }] : /* istanbul ignore next */ []));
|
|
@@ -3357,8 +2918,22 @@ class FormStateManager {
|
|
|
3357
2918
|
...(ngDevMode ? [{ debugName: "excludedValueStore" }] : /* istanbul ignore next */ []));
|
|
3358
2919
|
prevFieldStateSnapshot = signal({}, /* @ts-ignore */
|
|
3359
2920
|
...(ngDevMode ? [{ debugName: "prevFieldStateSnapshot" }] : /* istanbul ignore next */ []));
|
|
2921
|
+
lastOutwardValue = { current: undefined, pending: false };
|
|
2922
|
+
lastExternalValue = { current: undefined };
|
|
2923
|
+
externalValue = computed(() => {
|
|
2924
|
+
const incoming = this.deps.value();
|
|
2925
|
+
if (this.lastOutwardValue.pending && incoming === this.lastOutwardValue.current) {
|
|
2926
|
+
this.lastOutwardValue.pending = false;
|
|
2927
|
+
return this.lastExternalValue.current;
|
|
2928
|
+
}
|
|
2929
|
+
this.lastOutwardValue.pending = false;
|
|
2930
|
+
this.lastExternalValue.current = incoming;
|
|
2931
|
+
return incoming;
|
|
2932
|
+
}, /* @ts-ignore */
|
|
2933
|
+
...(ngDevMode ? [{ debugName: "externalValue" }] : /* istanbul ignore next */ []));
|
|
3360
2934
|
entity = linkedSignal(() => {
|
|
3361
|
-
|
|
2935
|
+
this.externalValue();
|
|
2936
|
+
const inputValue = untracked(() => this.deps.value());
|
|
3362
2937
|
const defaults = this.defaultValues();
|
|
3363
2938
|
const keys = this.validKeys();
|
|
3364
2939
|
const saved = this.excludedValueStore();
|
|
@@ -3378,22 +2953,62 @@ class FormStateManager {
|
|
|
3378
2953
|
debugName: 'FormStateManager.entity',
|
|
3379
2954
|
equal: isEqual,
|
|
3380
2955
|
});
|
|
3381
|
-
|
|
2956
|
+
loadFormSchemaService = injectAsync(() => import('./ng-forge-dynamic-forms-form-schema.service-Dx5Dlb15.mjs'));
|
|
2957
|
+
formSchemaRequest = computed(() => {
|
|
3382
2958
|
const setup = this.formSetup();
|
|
3383
2959
|
const config = this.activeConfig();
|
|
3384
|
-
if (!config)
|
|
2960
|
+
if (!config || (!(setup.schemaFields?.length > 0) && !config.schema))
|
|
2961
|
+
return undefined;
|
|
2962
|
+
return {
|
|
2963
|
+
fields: setup.schemaFields ?? [],
|
|
2964
|
+
registry: setup.registry,
|
|
2965
|
+
formLevelSchema: config.schema,
|
|
2966
|
+
validateWhenHidden: this.effectiveFormOptions().validateWhenHidden,
|
|
2967
|
+
};
|
|
2968
|
+
}, /* @ts-ignore */
|
|
2969
|
+
...(ngDevMode ? [{ debugName: "formSchemaRequest" }] : /* istanbul ignore next */ []));
|
|
2970
|
+
formSchemaResource = resource({ ...(ngDevMode ? { debugName: "formSchemaResource" } : /* istanbul ignore next */ {}), params: () => this.formSchemaRequest(),
|
|
2971
|
+
loader: async ({ params }) => {
|
|
2972
|
+
const schemaService = await this.loadFormSchemaService();
|
|
2973
|
+
return runInInjectionContext(this.injector, () => schemaService.create(params));
|
|
2974
|
+
} });
|
|
2975
|
+
schemaCache = { current: undefined };
|
|
2976
|
+
trackSchemaCache = computed(() => {
|
|
2977
|
+
const request = this.formSchemaRequest();
|
|
2978
|
+
if (!request) {
|
|
2979
|
+
this.schemaCache.current = undefined;
|
|
3385
2980
|
return undefined;
|
|
3386
|
-
if (setup.schemaFields?.length) {
|
|
3387
|
-
return createSchemaFromFields(setup.schemaFields, setup.registry, {
|
|
3388
|
-
formLevelSchema: config.schema,
|
|
3389
|
-
validateWhenHidden: this.effectiveFormOptions().validateWhenHidden,
|
|
3390
|
-
});
|
|
3391
2981
|
}
|
|
3392
|
-
if (
|
|
3393
|
-
|
|
2982
|
+
if (this.formSchemaResource.status() === 'error') {
|
|
2983
|
+
this.schemaCache.current = undefined;
|
|
2984
|
+
return undefined;
|
|
3394
2985
|
}
|
|
3395
|
-
|
|
3396
|
-
|
|
2986
|
+
if (this.formSchemaResource.hasValue()) {
|
|
2987
|
+
this.schemaCache.current = { schema: this.formSchemaResource.value(), request };
|
|
2988
|
+
}
|
|
2989
|
+
return this.schemaCache.current;
|
|
2990
|
+
}, /* @ts-ignore */
|
|
2991
|
+
...(ngDevMode ? [{ debugName: "trackSchemaCache" }] : /* istanbul ignore next */ []));
|
|
2992
|
+
isSchemaCurrent = computed(() => {
|
|
2993
|
+
if (this.formSetupConfig() !== this.activeConfig())
|
|
2994
|
+
return false;
|
|
2995
|
+
const request = this.formSchemaRequest();
|
|
2996
|
+
if (!request)
|
|
2997
|
+
return true;
|
|
2998
|
+
return this.trackSchemaCache()?.request === request;
|
|
2999
|
+
}, /* @ts-ignore */
|
|
3000
|
+
...(ngDevMode ? [{ debugName: "isSchemaCurrent" }] : /* istanbul ignore next */ []));
|
|
3001
|
+
formSchemaReady = computed(() => {
|
|
3002
|
+
if (this.formSetupConfig() !== this.activeConfig())
|
|
3003
|
+
return false;
|
|
3004
|
+
if (!this.formSchemaRequest())
|
|
3005
|
+
return true;
|
|
3006
|
+
if (this.formSchemaResource.status() === 'error')
|
|
3007
|
+
return true;
|
|
3008
|
+
return this.trackSchemaCache() !== undefined;
|
|
3009
|
+
}, /* @ts-ignore */
|
|
3010
|
+
...(ngDevMode ? [{ debugName: "formSchemaReady" }] : /* istanbul ignore next */ []));
|
|
3011
|
+
formSchema = computed(() => this.trackSchemaCache()?.schema, /* @ts-ignore */
|
|
3397
3012
|
...(ngDevMode ? [{ debugName: "formSchema" }] : /* istanbul ignore next */ []));
|
|
3398
3013
|
form = computed(() => {
|
|
3399
3014
|
const schema = this.formSchema();
|
|
@@ -3424,13 +3039,16 @@ class FormStateManager {
|
|
|
3424
3039
|
...(ngDevMode ? [{ debugName: "fieldSignalContext" }] : /* istanbul ignore next */ []));
|
|
3425
3040
|
formInstance = computed(() => this.form()(), /* @ts-ignore */
|
|
3426
3041
|
...(ngDevMode ? [{ debugName: "formInstance" }] : /* istanbul ignore next */ []));
|
|
3427
|
-
|
|
3042
|
+
modelValue = computed(() => this.formInstance().value(), /* @ts-ignore */
|
|
3043
|
+
...(ngDevMode ? [{ debugName: "modelValue" }] : /* istanbul ignore next */ []));
|
|
3044
|
+
formValue = computed(() => this.modelValue(), /* @ts-ignore */
|
|
3428
3045
|
...(ngDevMode ? [{ debugName: "formValue" }] : /* istanbul ignore next */ []));
|
|
3429
3046
|
filteredFormValue = computed(() => {
|
|
3430
|
-
const rawValue = this.
|
|
3047
|
+
const rawValue = this.modelValue();
|
|
3431
3048
|
const setup = this.formSetup();
|
|
3049
|
+
const schemaFields = setup.schemaFields;
|
|
3432
3050
|
const options = this.effectiveFormOptions();
|
|
3433
|
-
if (!
|
|
3051
|
+
if (!this.formSchemaReady() || !schemaFields || schemaFields.length === 0) {
|
|
3434
3052
|
return rawValue;
|
|
3435
3053
|
}
|
|
3436
3054
|
const formTree = this.form();
|
|
@@ -3440,14 +3058,15 @@ class FormStateManager {
|
|
|
3440
3058
|
excludeValueIfReadonly: options.excludeValueIfReadonly,
|
|
3441
3059
|
};
|
|
3442
3060
|
const fieldTreeRecord = asFieldTreeRecord(formTree);
|
|
3443
|
-
return filterFormValue(rawValue,
|
|
3061
|
+
return filterFormValue(rawValue, schemaFields, fieldTreeRecord, setup.registry, this.valueExclusionDefaults, formOptions, this.valueExclusionEnabled());
|
|
3444
3062
|
}, /* @ts-ignore */
|
|
3445
3063
|
...(ngDevMode ? [{ debugName: "filteredFormValue" }] : /* istanbul ignore next */ []));
|
|
3446
3064
|
boundFormValue = computed(() => {
|
|
3447
|
-
const rawValue = this.
|
|
3065
|
+
const rawValue = this.modelValue();
|
|
3448
3066
|
const setup = this.formSetup();
|
|
3067
|
+
const schemaFields = setup.schemaFields;
|
|
3449
3068
|
const options = this.effectiveFormOptions();
|
|
3450
|
-
if (!
|
|
3069
|
+
if (!this.formSchemaReady() || !schemaFields || schemaFields.length === 0) {
|
|
3451
3070
|
return rawValue;
|
|
3452
3071
|
}
|
|
3453
3072
|
const formTree = this.form();
|
|
@@ -3457,26 +3076,26 @@ class FormStateManager {
|
|
|
3457
3076
|
excludeValueIfReadonly: options.excludeValueIfReadonly,
|
|
3458
3077
|
};
|
|
3459
3078
|
const fieldTreeRecord = asFieldTreeRecord(formTree);
|
|
3460
|
-
return filterFormValue(rawValue,
|
|
3079
|
+
return filterFormValue(rawValue, schemaFields, fieldTreeRecord, setup.registry, BOUND_VALUE_EXCLUSION_BASELINE, formOptions, this.boundValueExclusionEnabled());
|
|
3461
3080
|
}, /* @ts-ignore */
|
|
3462
3081
|
...(ngDevMode ? [{ debugName: "boundFormValue" }] : /* istanbul ignore next */ []));
|
|
3463
|
-
|
|
3082
|
+
valid = computed(() => this.formSchemaReady() && this.formSchemaResource.status() !== 'error' && this.formInstance().valid(), /* @ts-ignore */
|
|
3464
3083
|
...(ngDevMode ? [{ debugName: "valid" }] : /* istanbul ignore next */ []));
|
|
3465
|
-
invalid = computed(() => this.formInstance().invalid(), /* @ts-ignore */
|
|
3084
|
+
invalid = computed(() => !this.formSchemaReady() || this.formSchemaResource.status() === 'error' || this.formInstance().invalid(), /* @ts-ignore */
|
|
3466
3085
|
...(ngDevMode ? [{ debugName: "invalid" }] : /* istanbul ignore next */ []));
|
|
3467
|
-
dirty = computed(() => this.formInstance().dirty(), /* @ts-ignore */
|
|
3086
|
+
dirty = computed(() => this.formSchemaReady() && this.formInstance().dirty(), /* @ts-ignore */
|
|
3468
3087
|
...(ngDevMode ? [{ debugName: "dirty" }] : /* istanbul ignore next */ []));
|
|
3469
|
-
touched = computed(() => this.formInstance().touched(), /* @ts-ignore */
|
|
3088
|
+
touched = computed(() => this.formSchemaReady() && this.formInstance().touched(), /* @ts-ignore */
|
|
3470
3089
|
...(ngDevMode ? [{ debugName: "touched" }] : /* istanbul ignore next */ []));
|
|
3471
|
-
errors = computed(() => this.formInstance().errors(), /* @ts-ignore */
|
|
3090
|
+
errors = computed(() => (this.formSchemaReady() ? this.formInstance().errors() : []), /* @ts-ignore */
|
|
3472
3091
|
...(ngDevMode ? [{ debugName: "errors" }] : /* istanbul ignore next */ []));
|
|
3473
3092
|
disabled = computed(() => {
|
|
3474
3093
|
const optionsDisabled = this.effectiveFormOptions().disabled;
|
|
3475
|
-
const formDisabled = this.formInstance().disabled();
|
|
3094
|
+
const formDisabled = this.formSchemaReady() && this.formInstance().disabled();
|
|
3476
3095
|
return optionsDisabled ?? formDisabled;
|
|
3477
3096
|
}, /* @ts-ignore */
|
|
3478
3097
|
...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
|
|
3479
|
-
submitting = computed(() => this.formInstance().submitting(), /* @ts-ignore */
|
|
3098
|
+
submitting = computed(() => this.formSchemaReady() && this.formInstance().submitting(), /* @ts-ignore */
|
|
3480
3099
|
...(ngDevMode ? [{ debugName: "submitting" }] : /* istanbul ignore next */ []));
|
|
3481
3100
|
fieldsSource = computed(() => {
|
|
3482
3101
|
const state = this.machine.state();
|
|
@@ -3591,6 +3210,10 @@ class FormStateManager {
|
|
|
3591
3210
|
}), takeUntilDestroyed(this.destroyRef));
|
|
3592
3211
|
this.reset$ = this.eventBus.on('form-reset').pipe(map(() => undefined), takeUntilDestroyed(this.destroyRef));
|
|
3593
3212
|
this.cleared$ = this.eventBus.on('form-clear').pipe(map(() => undefined), takeUntilDestroyed(this.destroyRef));
|
|
3213
|
+
this.eventBus
|
|
3214
|
+
.on('go-to-page')
|
|
3215
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
3216
|
+
.subscribe((event) => this.pendingPageRequest.set({ index: event.pageIndex, options: event.options }));
|
|
3594
3217
|
this.machine = createFormStateMachine({
|
|
3595
3218
|
injector: this.injector,
|
|
3596
3219
|
destroyRef: this.destroyRef,
|
|
@@ -3654,6 +3277,11 @@ class FormStateManager {
|
|
|
3654
3277
|
this.eventBus.dispatch(FormSubmitEvent);
|
|
3655
3278
|
}
|
|
3656
3279
|
setupEffects() {
|
|
3280
|
+
explicitEffect([this.formSchemaResource.error], ([error]) => {
|
|
3281
|
+
if (error) {
|
|
3282
|
+
this.logger.error('Failed to compile the form schema; rendering without validation and blocking submission.', error);
|
|
3283
|
+
}
|
|
3284
|
+
});
|
|
3657
3285
|
explicitEffect([this.deps.config], ([config]) => {
|
|
3658
3286
|
this.fieldLoadingErrors.set([]);
|
|
3659
3287
|
const state = this.machine.currentState;
|
|
@@ -3680,127 +3308,543 @@ class FormStateManager {
|
|
|
3680
3308
|
}
|
|
3681
3309
|
}
|
|
3682
3310
|
}
|
|
3683
|
-
this.prevFieldStateSnapshot.set({ ...snapshot });
|
|
3684
|
-
if (newSaved !== currentSaved && !isEqual(newSaved, currentSaved)) {
|
|
3685
|
-
this.excludedValueStore.set(newSaved);
|
|
3686
|
-
}
|
|
3687
|
-
});
|
|
3688
|
-
let warnedAboutMissingArrayRegistry = false;
|
|
3689
|
-
explicitEffect([this.formSetup], () => {
|
|
3690
|
-
this.prevFieldStateSnapshot.set({});
|
|
3691
|
-
if (Object.keys(this.excludedValueStore()).length > 0) {
|
|
3692
|
-
this.excludedValueStore.set({});
|
|
3693
|
-
}
|
|
3694
|
-
if (this.arrayItemRegistry) {
|
|
3695
|
-
this.arrayItemRegistry.clearAll();
|
|
3696
|
-
}
|
|
3697
|
-
else if (DEV_MODE && !warnedAboutMissingArrayRegistry) {
|
|
3698
|
-
warnedAboutMissingArrayRegistry = true;
|
|
3699
|
-
this.logger.warn('ArrayItemRegistryService was not provided to FormStateManager. ' +
|
|
3700
|
-
'Array fields will lose dynamically-added item templates across hide/show cycles. ' +
|
|
3701
|
-
'This usually means DynamicForm was bootstrapped without provideDynamicFormDI() — ' +
|
|
3702
|
-
'check your providers chain.');
|
|
3703
|
-
}
|
|
3311
|
+
this.prevFieldStateSnapshot.set({ ...snapshot });
|
|
3312
|
+
if (newSaved !== currentSaved && !isEqual(newSaved, currentSaved)) {
|
|
3313
|
+
this.excludedValueStore.set(newSaved);
|
|
3314
|
+
}
|
|
3315
|
+
});
|
|
3316
|
+
let warnedAboutMissingArrayRegistry = false;
|
|
3317
|
+
explicitEffect([this.formSetup], () => {
|
|
3318
|
+
this.prevFieldStateSnapshot.set({});
|
|
3319
|
+
if (Object.keys(this.excludedValueStore()).length > 0) {
|
|
3320
|
+
this.excludedValueStore.set({});
|
|
3321
|
+
}
|
|
3322
|
+
if (this.arrayItemRegistry) {
|
|
3323
|
+
this.arrayItemRegistry.clearAll();
|
|
3324
|
+
}
|
|
3325
|
+
else if (DEV_MODE && !warnedAboutMissingArrayRegistry) {
|
|
3326
|
+
warnedAboutMissingArrayRegistry = true;
|
|
3327
|
+
this.logger.warn('ArrayItemRegistryService was not provided to FormStateManager. ' +
|
|
3328
|
+
'Array fields will lose dynamically-added item templates across hide/show cycles. ' +
|
|
3329
|
+
'This usually means DynamicForm was bootstrapped without provideDynamicFormDI() — ' +
|
|
3330
|
+
'check your providers chain.');
|
|
3331
|
+
}
|
|
3332
|
+
});
|
|
3333
|
+
explicitEffect([this.boundFormValue], ([currentBound]) => {
|
|
3334
|
+
const currentValue = this.deps.value();
|
|
3335
|
+
if (!isEqual(currentBound, currentValue)) {
|
|
3336
|
+
this.lastOutwardValue.current = currentBound;
|
|
3337
|
+
this.lastOutwardValue.pending = true;
|
|
3338
|
+
this.deps.value.set(currentBound);
|
|
3339
|
+
}
|
|
3340
|
+
});
|
|
3341
|
+
if (DEV_MODE) {
|
|
3342
|
+
explicitEffect([this.activeConfig], ([config]) => {
|
|
3343
|
+
if (!config)
|
|
3344
|
+
return;
|
|
3345
|
+
const validation = FormModeValidator.validateFormConfiguration(config.fields || []);
|
|
3346
|
+
if (!validation.isValid) {
|
|
3347
|
+
this.logger.error('Invalid form configuration:', validation.errors);
|
|
3348
|
+
}
|
|
3349
|
+
if (validation.warnings.length > 0) {
|
|
3350
|
+
this.logger.warn('Form configuration warnings:', validation.warnings);
|
|
3351
|
+
}
|
|
3352
|
+
});
|
|
3353
|
+
}
|
|
3354
|
+
}
|
|
3355
|
+
setupEventHandlers() {
|
|
3356
|
+
this.eventBus
|
|
3357
|
+
.on('form-reset')
|
|
3358
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
3359
|
+
.subscribe(() => this.reset());
|
|
3360
|
+
this.eventBus
|
|
3361
|
+
.on('form-clear')
|
|
3362
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
3363
|
+
.subscribe(() => this.clear());
|
|
3364
|
+
}
|
|
3365
|
+
registerValidatorsFromConfig({ customFnConfig, schemas }) {
|
|
3366
|
+
if (schemas) {
|
|
3367
|
+
schemas.forEach((schema) => {
|
|
3368
|
+
this.schemaRegistry.registerSchema(schema);
|
|
3369
|
+
});
|
|
3370
|
+
}
|
|
3371
|
+
if (!customFnConfig) {
|
|
3372
|
+
return;
|
|
3373
|
+
}
|
|
3374
|
+
if (customFnConfig.customFunctions) {
|
|
3375
|
+
Object.entries(customFnConfig.customFunctions).forEach(([name, fn]) => {
|
|
3376
|
+
this.functionRegistry.registerCustomFunction(name, fn);
|
|
3377
|
+
});
|
|
3378
|
+
}
|
|
3379
|
+
if (customFnConfig.derivations) {
|
|
3380
|
+
this.functionRegistry.setDerivationFunctions(customFnConfig.derivations);
|
|
3381
|
+
}
|
|
3382
|
+
if (customFnConfig.asyncDerivations) {
|
|
3383
|
+
this.functionRegistry.setAsyncDerivationFunctions(customFnConfig.asyncDerivations);
|
|
3384
|
+
}
|
|
3385
|
+
if (customFnConfig.asyncConditions) {
|
|
3386
|
+
this.functionRegistry.setAsyncConditionFunctions(customFnConfig.asyncConditions);
|
|
3387
|
+
}
|
|
3388
|
+
this.functionRegistry.setValidators(customFnConfig.validators);
|
|
3389
|
+
this.functionRegistry.setAsyncValidators(customFnConfig.asyncValidators);
|
|
3390
|
+
this.functionRegistry.setHttpValidators(customFnConfig.httpValidators);
|
|
3391
|
+
}
|
|
3392
|
+
createFormSetupFromConfig(fields, mode, registry) {
|
|
3393
|
+
const normalizedFields = normalizeSimplifiedArrays(fields);
|
|
3394
|
+
validateFormConfig(normalizedFields, registry, this.logger);
|
|
3395
|
+
const source = this.deps.source?.() ?? 'inline';
|
|
3396
|
+
const { fields: sanitizedFields, warnings: addonWarnings } = walkAndValidateAddons(normalizedFields, registry, this.addonKindRegistry, source);
|
|
3397
|
+
if (DEV_MODE) {
|
|
3398
|
+
const nextKeys = new Set();
|
|
3399
|
+
for (const w of addonWarnings) {
|
|
3400
|
+
const key = addonWarningKey(w);
|
|
3401
|
+
nextKeys.add(key);
|
|
3402
|
+
if (!this.lastAddonWarningKeys.has(key)) {
|
|
3403
|
+
this.logger.warn(formatAddonWarning(w));
|
|
3404
|
+
}
|
|
3405
|
+
}
|
|
3406
|
+
this.lastAddonWarningKeys = nextKeys;
|
|
3407
|
+
}
|
|
3408
|
+
const validatedFields = sanitizedFields;
|
|
3409
|
+
const flattenedFields = this.fieldProcessors.memoizedFlattenFields(validatedFields, registry);
|
|
3410
|
+
const flattenedFieldsForRendering = this.fieldProcessors.memoizedFlattenFieldsForRendering(validatedFields, registry);
|
|
3411
|
+
const fieldsById = this.fieldProcessors.memoizedKeyBy(flattenedFields);
|
|
3412
|
+
const defaultValues = this.fieldProcessors.memoizedDefaultValues(fieldsById, registry);
|
|
3413
|
+
const fieldsToRender = mode === 'paged' ? [] : flattenedFieldsForRendering;
|
|
3414
|
+
return {
|
|
3415
|
+
fields: fieldsToRender,
|
|
3416
|
+
schemaFields: flattenedFields,
|
|
3417
|
+
originalFields: fields,
|
|
3418
|
+
defaultValues,
|
|
3419
|
+
mode,
|
|
3420
|
+
registry,
|
|
3421
|
+
};
|
|
3422
|
+
}
|
|
3423
|
+
createEmptyFormSetup(registry) {
|
|
3424
|
+
return {
|
|
3425
|
+
fields: [],
|
|
3426
|
+
schemaFields: [],
|
|
3427
|
+
defaultValues: {},
|
|
3428
|
+
mode: 'non-paged',
|
|
3429
|
+
registry,
|
|
3430
|
+
};
|
|
3431
|
+
}
|
|
3432
|
+
ngOnDestroy() {
|
|
3433
|
+
this.machine.dispatch({ type: Action.Destroy });
|
|
3434
|
+
}
|
|
3435
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: FormStateManager, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
3436
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: FormStateManager });
|
|
3437
|
+
}
|
|
3438
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: FormStateManager, decorators: [{
|
|
3439
|
+
type: Injectable
|
|
3440
|
+
}], ctorParameters: () => [] });
|
|
3441
|
+
|
|
3442
|
+
class PageOrchestratorComponent {
|
|
3443
|
+
eventBus = inject(EventBus);
|
|
3444
|
+
fieldContextRegistry = inject(FieldContextRegistryService);
|
|
3445
|
+
functionRegistry = inject(FunctionRegistryService);
|
|
3446
|
+
formOptions = inject(FORM_OPTIONS, { optional: true });
|
|
3447
|
+
globalPreloadWindow = inject(PAGE_PRELOAD_WINDOW);
|
|
3448
|
+
stateManager = inject(FormStateManager, { optional: true });
|
|
3449
|
+
pendingPageRequest = computed(() => this.stateManager?.pendingPageRequest() ?? null, /* @ts-ignore */
|
|
3450
|
+
...(ngDevMode ? [{ debugName: "pendingPageRequest" }] : /* istanbul ignore next */ []));
|
|
3451
|
+
schemaCurrent = computed(() => this.stateManager?.isSchemaCurrent() ?? true, /* @ts-ignore */
|
|
3452
|
+
...(ngDevMode ? [{ debugName: "schemaCurrent" }] : /* istanbul ignore next */ []));
|
|
3453
|
+
configScope = computed(() => this.stateManager?.activeConfig(), /* @ts-ignore */
|
|
3454
|
+
...(ngDevMode ? [{ debugName: "configScope" }] : /* istanbul ignore next */ []));
|
|
3455
|
+
pageFields = input.required(/* @ts-ignore */
|
|
3456
|
+
...(ngDevMode ? [{ debugName: "pageFields" }] : /* istanbul ignore next */ []));
|
|
3457
|
+
form = input.required(/* @ts-ignore */
|
|
3458
|
+
...(ngDevMode ? [{ debugName: "form" }] : /* istanbul ignore next */ []));
|
|
3459
|
+
fieldSignalContext = input.required(/* @ts-ignore */
|
|
3460
|
+
...(ngDevMode ? [{ debugName: "fieldSignalContext" }] : /* istanbul ignore next */ []));
|
|
3461
|
+
pageHiddenStates = computed(() => {
|
|
3462
|
+
const pages = this.pageFields();
|
|
3463
|
+
return pages.map((page) => this.evaluatePageHidden(page));
|
|
3464
|
+
}, /* @ts-ignore */
|
|
3465
|
+
...(ngDevMode ? [{ debugName: "pageHiddenStates" }] : /* istanbul ignore next */ []));
|
|
3466
|
+
visiblePageIndices = computed(() => {
|
|
3467
|
+
const hiddenStates = this.pageHiddenStates();
|
|
3468
|
+
return hiddenStates
|
|
3469
|
+
.map((hidden, index) => ({ index, hidden }))
|
|
3470
|
+
.filter((item) => !item.hidden)
|
|
3471
|
+
.map((item) => item.index);
|
|
3472
|
+
}, /* @ts-ignore */
|
|
3473
|
+
...(ngDevMode ? [{ debugName: "visiblePageIndices" }] : /* istanbul ignore next */ []));
|
|
3474
|
+
activePageOwnership = linkedSignal({ ...(ngDevMode ? { debugName: "activePageOwnership" } : /* istanbul ignore next */ {}), source: this.configScope,
|
|
3475
|
+
computation: (config, previous) => {
|
|
3476
|
+
const totalPages = this.pageFields().length;
|
|
3477
|
+
if (totalPages === 0)
|
|
3478
|
+
return { config, initialized: true, index: 0 };
|
|
3479
|
+
return untracked(() => {
|
|
3480
|
+
const owned = this.stateManager?.activePageState();
|
|
3481
|
+
const mayRestore = previous === undefined || previous.source === config;
|
|
3482
|
+
if (mayRestore &&
|
|
3483
|
+
owned !== undefined &&
|
|
3484
|
+
owned.config === config &&
|
|
3485
|
+
owned.initialized &&
|
|
3486
|
+
owned.index >= 0 &&
|
|
3487
|
+
owned.index < totalPages) {
|
|
3488
|
+
return { config, initialized: true, index: owned.index };
|
|
3489
|
+
}
|
|
3490
|
+
if (this.initialPage().validate && !this.schemaCurrent()) {
|
|
3491
|
+
return { config, initialized: false, index: 0 };
|
|
3492
|
+
}
|
|
3493
|
+
return { config, initialized: true, index: this.resolveInitialLanding() };
|
|
3494
|
+
});
|
|
3495
|
+
} });
|
|
3496
|
+
currentPageIndex = computed(() => this.activePageOwnership().index, /* @ts-ignore */
|
|
3497
|
+
...(ngDevMode ? [{ debugName: "currentPageIndex" }] : /* istanbul ignore next */ []));
|
|
3498
|
+
setCurrentPageIndex(index) {
|
|
3499
|
+
this.activePageOwnership.update((ownership) => ({ ...ownership, initialized: true, index }));
|
|
3500
|
+
}
|
|
3501
|
+
resolveInitialLanding() {
|
|
3502
|
+
const { index, validate } = this.initialPage();
|
|
3503
|
+
const visible = this.visiblePageIndices();
|
|
3504
|
+
if (index === 0 || visible.length === 0)
|
|
3505
|
+
return 0;
|
|
3506
|
+
const target = visible.includes(index) ? index : this.findNearestVisiblePage(index, visible);
|
|
3507
|
+
if (target <= 0)
|
|
3508
|
+
return Math.max(target, 0);
|
|
3509
|
+
if (!validate)
|
|
3510
|
+
return target;
|
|
3511
|
+
const firstInvalid = visible.filter((i) => i < target).find((i) => !this.isPageValid(i));
|
|
3512
|
+
return firstInvalid ?? target;
|
|
3513
|
+
}
|
|
3514
|
+
initialPage = computed(() => {
|
|
3515
|
+
const raw = this.formOptions?.()?.initialPage;
|
|
3516
|
+
const totalPages = this.pageFields().length;
|
|
3517
|
+
const requested = typeof raw === 'number' ? raw : raw?.index;
|
|
3518
|
+
const validate = typeof raw === 'number' ? false : (raw?.validate ?? false);
|
|
3519
|
+
if (typeof requested !== 'number' || !Number.isFinite(requested) || requested < 0 || totalPages === 0) {
|
|
3520
|
+
return { index: 0, validate: false };
|
|
3521
|
+
}
|
|
3522
|
+
return { index: Math.min(Math.trunc(requested), totalPages - 1), validate };
|
|
3523
|
+
}, /* @ts-ignore */
|
|
3524
|
+
...(ngDevMode ? [{ debugName: "initialPage" }] : /* istanbul ignore next */ []));
|
|
3525
|
+
state = computed(() => {
|
|
3526
|
+
const currentIndex = this.currentPageIndex();
|
|
3527
|
+
const totalPages = this.pageFields().length;
|
|
3528
|
+
const visibleIndices = this.visiblePageIndices();
|
|
3529
|
+
const currentVisiblePosition = visibleIndices.indexOf(currentIndex);
|
|
3530
|
+
const isFirstVisiblePage = currentVisiblePosition === 0 || currentVisiblePosition === -1;
|
|
3531
|
+
const isLastVisiblePage = currentVisiblePosition >= visibleIndices.length - 1;
|
|
3532
|
+
return {
|
|
3533
|
+
currentPageIndex: currentIndex,
|
|
3534
|
+
totalPages,
|
|
3535
|
+
isFirstPage: isFirstVisiblePage,
|
|
3536
|
+
isLastPage: isLastVisiblePage,
|
|
3537
|
+
};
|
|
3538
|
+
}, /* @ts-ignore */
|
|
3539
|
+
...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
|
|
3540
|
+
currentPageValid = computed(() => (this.stateManager?.isSchemaCurrent() ?? true) && this.isPageValid(this.currentPageIndex()), /* @ts-ignore */
|
|
3541
|
+
...(ngDevMode ? [{ debugName: "currentPageValid" }] : /* istanbul ignore next */ []));
|
|
3542
|
+
isPageValid(pageIndex) {
|
|
3543
|
+
const pages = this.pageFields();
|
|
3544
|
+
const form = this.form();
|
|
3545
|
+
if (pageIndex < 0 || pageIndex >= pages.length) {
|
|
3546
|
+
return true;
|
|
3547
|
+
}
|
|
3548
|
+
const leafKeys = collectLeafFieldKeys(pages[pageIndex].fields || []);
|
|
3549
|
+
for (const fieldKey of leafKeys) {
|
|
3550
|
+
const field = form[fieldKey];
|
|
3551
|
+
if (field && typeof field === 'function') {
|
|
3552
|
+
const fieldState = field();
|
|
3553
|
+
if (fieldState && typeof fieldState.valid === 'function' && !fieldState.valid()) {
|
|
3554
|
+
return false;
|
|
3555
|
+
}
|
|
3556
|
+
}
|
|
3557
|
+
}
|
|
3558
|
+
return true;
|
|
3559
|
+
}
|
|
3560
|
+
extendedFieldSignalContext = computed(() => ({
|
|
3561
|
+
...this.fieldSignalContext(),
|
|
3562
|
+
currentPageValid: this.currentPageValid,
|
|
3563
|
+
}), /* @ts-ignore */
|
|
3564
|
+
...(ngDevMode ? [{ debugName: "extendedFieldSignalContext" }] : /* istanbul ignore next */ []));
|
|
3565
|
+
preloadWindow = computed(() => {
|
|
3566
|
+
const perForm = this.formOptions?.()?.pagePreloadWindow;
|
|
3567
|
+
return clampWindowSize(perForm, this.globalPreloadWindow);
|
|
3568
|
+
}, /* @ts-ignore */
|
|
3569
|
+
...(ngDevMode ? [{ debugName: "preloadWindow" }] : /* istanbul ignore next */ []));
|
|
3570
|
+
pageRenderStates = computed(() => {
|
|
3571
|
+
const activeIndex = this.currentPageIndex();
|
|
3572
|
+
const hiddenStates = this.pageHiddenStates();
|
|
3573
|
+
const preloadWindow = this.preloadWindow();
|
|
3574
|
+
return this.pageFields().map((field, index) => ({
|
|
3575
|
+
field,
|
|
3576
|
+
index,
|
|
3577
|
+
hidden: hiddenStates[index],
|
|
3578
|
+
active: index === activeIndex,
|
|
3579
|
+
preload: index !== activeIndex && Math.abs(index - activeIndex) <= preloadWindow,
|
|
3580
|
+
}));
|
|
3581
|
+
}, /* @ts-ignore */
|
|
3582
|
+
...(ngDevMode ? [{ debugName: "pageRenderStates" }] : /* istanbul ignore next */ []));
|
|
3583
|
+
constructor() {
|
|
3584
|
+
this.setupEventListeners();
|
|
3585
|
+
const preloader = injectFormComponentPreloader();
|
|
3586
|
+
explicitEffect([this.pageRenderStates], ([states]) => {
|
|
3587
|
+
preloader.preloadFields(states.filter((page) => !page.hidden && (page.active || page.preload)).map((page) => page.field));
|
|
3704
3588
|
});
|
|
3705
|
-
explicitEffect([this.
|
|
3706
|
-
const
|
|
3707
|
-
if (!
|
|
3708
|
-
|
|
3709
|
-
}
|
|
3589
|
+
explicitEffect([this.schemaCurrent, this.pageFields], ([schemaCurrent]) => {
|
|
3590
|
+
const ownership = this.activePageOwnership();
|
|
3591
|
+
if (!schemaCurrent || ownership.initialized)
|
|
3592
|
+
return;
|
|
3593
|
+
this.activePageOwnership.set({ ...ownership, initialized: true, index: this.resolveInitialLanding() });
|
|
3710
3594
|
});
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3595
|
+
explicitEffect([this.state, this.visiblePageIndices], ([state, visibleIndices]) => {
|
|
3596
|
+
const currentVisiblePosition = visibleIndices.indexOf(state.currentPageIndex);
|
|
3597
|
+
if (currentVisiblePosition === -1 && visibleIndices.length > 0) {
|
|
3598
|
+
const nearest = this.findNearestVisiblePage(state.currentPageIndex, visibleIndices);
|
|
3599
|
+
if (nearest !== -1) {
|
|
3600
|
+
this.executePageChange(nearest);
|
|
3601
|
+
}
|
|
3717
3602
|
}
|
|
3718
3603
|
});
|
|
3719
3604
|
}
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3605
|
+
navigateToNextPage() {
|
|
3606
|
+
const disableWhenPageInvalid = this.formOptions?.()?.nextButton?.disableWhenPageInvalid ?? true;
|
|
3607
|
+
if (disableWhenPageInvalid && !this.currentPageValid()) {
|
|
3608
|
+
return {
|
|
3609
|
+
success: false,
|
|
3610
|
+
newPageIndex: this.state().currentPageIndex,
|
|
3611
|
+
error: 'Current page has invalid fields',
|
|
3612
|
+
};
|
|
3613
|
+
}
|
|
3614
|
+
const currentState = this.state();
|
|
3615
|
+
const visibleIndices = this.visiblePageIndices();
|
|
3616
|
+
if (currentState.isLastPage) {
|
|
3617
|
+
return {
|
|
3618
|
+
success: false,
|
|
3619
|
+
newPageIndex: currentState.currentPageIndex,
|
|
3620
|
+
error: 'Already on the last visible page',
|
|
3621
|
+
};
|
|
3622
|
+
}
|
|
3623
|
+
const currentVisiblePosition = visibleIndices.indexOf(currentState.currentPageIndex);
|
|
3624
|
+
if (currentVisiblePosition === -1 || currentVisiblePosition >= visibleIndices.length - 1) {
|
|
3625
|
+
return {
|
|
3626
|
+
success: false,
|
|
3627
|
+
newPageIndex: currentState.currentPageIndex,
|
|
3628
|
+
error: 'No next visible page available',
|
|
3629
|
+
};
|
|
3630
|
+
}
|
|
3631
|
+
const nextVisiblePageIndex = visibleIndices[currentVisiblePosition + 1];
|
|
3632
|
+
return this.executePageChange(nextVisiblePageIndex);
|
|
3729
3633
|
}
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3634
|
+
navigateToPreviousPage() {
|
|
3635
|
+
const currentState = this.state();
|
|
3636
|
+
const visibleIndices = this.visiblePageIndices();
|
|
3637
|
+
if (currentState.isFirstPage) {
|
|
3638
|
+
return {
|
|
3639
|
+
success: false,
|
|
3640
|
+
newPageIndex: currentState.currentPageIndex,
|
|
3641
|
+
error: 'Already on the first visible page',
|
|
3642
|
+
};
|
|
3735
3643
|
}
|
|
3736
|
-
|
|
3737
|
-
|
|
3644
|
+
const currentVisiblePosition = visibleIndices.indexOf(currentState.currentPageIndex);
|
|
3645
|
+
if (currentVisiblePosition <= 0) {
|
|
3646
|
+
return {
|
|
3647
|
+
success: false,
|
|
3648
|
+
newPageIndex: currentState.currentPageIndex,
|
|
3649
|
+
error: 'No previous visible page available',
|
|
3650
|
+
};
|
|
3738
3651
|
}
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3652
|
+
const prevVisiblePageIndex = visibleIndices[currentVisiblePosition - 1];
|
|
3653
|
+
return this.executePageChange(prevVisiblePageIndex);
|
|
3654
|
+
}
|
|
3655
|
+
navigateToPage(pageIndex, options) {
|
|
3656
|
+
const currentIndex = this.state().currentPageIndex;
|
|
3657
|
+
const visibleIndices = this.visiblePageIndices();
|
|
3658
|
+
if (pageIndex <= currentIndex || !visibleIndices.includes(pageIndex)) {
|
|
3659
|
+
return this.executePageChange(pageIndex);
|
|
3743
3660
|
}
|
|
3744
|
-
|
|
3745
|
-
this.
|
|
3661
|
+
if (options?.validate === false) {
|
|
3662
|
+
return this.executePageChange(pageIndex);
|
|
3746
3663
|
}
|
|
3747
|
-
|
|
3748
|
-
|
|
3664
|
+
const disableWhenPageInvalid = this.formOptions?.()?.nextButton?.disableWhenPageInvalid ?? true;
|
|
3665
|
+
if (!disableWhenPageInvalid) {
|
|
3666
|
+
return this.executePageChange(pageIndex);
|
|
3749
3667
|
}
|
|
3750
|
-
|
|
3751
|
-
|
|
3668
|
+
const crossedPages = visibleIndices.filter((index) => index >= currentIndex && index < pageIndex);
|
|
3669
|
+
const firstInvalidPage = crossedPages.find((index) => !this.isPageValid(index));
|
|
3670
|
+
if (firstInvalidPage === undefined) {
|
|
3671
|
+
return this.executePageChange(pageIndex);
|
|
3752
3672
|
}
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3673
|
+
this.executePageChange(firstInvalidPage);
|
|
3674
|
+
return {
|
|
3675
|
+
success: false,
|
|
3676
|
+
newPageIndex: firstInvalidPage,
|
|
3677
|
+
error: `Cannot navigate to page ${pageIndex}: page ${firstInvalidPage} has invalid fields`,
|
|
3678
|
+
};
|
|
3756
3679
|
}
|
|
3757
|
-
|
|
3758
|
-
const
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
this.
|
|
3680
|
+
executePageChange(pageIndex) {
|
|
3681
|
+
const currentState = this.state();
|
|
3682
|
+
const totalPages = currentState.totalPages;
|
|
3683
|
+
if (pageIndex < 0 || pageIndex >= totalPages) {
|
|
3684
|
+
return {
|
|
3685
|
+
success: false,
|
|
3686
|
+
newPageIndex: currentState.currentPageIndex,
|
|
3687
|
+
error: `Invalid page index: ${pageIndex}. Valid range is 0 to ${totalPages - 1}`,
|
|
3688
|
+
};
|
|
3689
|
+
}
|
|
3690
|
+
const visibleIndices = this.visiblePageIndices();
|
|
3691
|
+
if (!visibleIndices.includes(pageIndex)) {
|
|
3692
|
+
return {
|
|
3693
|
+
success: false,
|
|
3694
|
+
newPageIndex: currentState.currentPageIndex,
|
|
3695
|
+
error: `Cannot navigate to hidden page at index ${pageIndex}. Visible pages: [${visibleIndices.join(', ')}]`,
|
|
3696
|
+
};
|
|
3697
|
+
}
|
|
3698
|
+
if (pageIndex === currentState.currentPageIndex) {
|
|
3699
|
+
return {
|
|
3700
|
+
success: true,
|
|
3701
|
+
newPageIndex: pageIndex,
|
|
3702
|
+
};
|
|
3703
|
+
}
|
|
3704
|
+
const previousIndex = currentState.currentPageIndex;
|
|
3705
|
+
this.setCurrentPageIndex(pageIndex);
|
|
3706
|
+
this.eventBus.dispatch(PageChangeEvent, pageIndex, totalPages, previousIndex);
|
|
3707
|
+
return {
|
|
3708
|
+
success: true,
|
|
3709
|
+
newPageIndex: pageIndex,
|
|
3710
|
+
};
|
|
3711
|
+
}
|
|
3712
|
+
findNearestVisiblePage(currentIndex, visibleIndices) {
|
|
3713
|
+
let nearest = -1;
|
|
3714
|
+
let minDistance = Infinity;
|
|
3715
|
+
for (const idx of visibleIndices) {
|
|
3716
|
+
const distance = Math.abs(idx - currentIndex);
|
|
3717
|
+
if (distance < minDistance || (distance === minDistance && idx > nearest)) {
|
|
3718
|
+
minDistance = distance;
|
|
3719
|
+
nearest = idx;
|
|
3720
|
+
}
|
|
3721
|
+
}
|
|
3722
|
+
return nearest;
|
|
3723
|
+
}
|
|
3724
|
+
setupEventListeners() {
|
|
3725
|
+
this.eventBus
|
|
3726
|
+
.on('next-page')
|
|
3727
|
+
.pipe(takeUntilDestroyed())
|
|
3728
|
+
.subscribe(() => {
|
|
3729
|
+
this.navigateToNextPage();
|
|
3730
|
+
});
|
|
3731
|
+
this.eventBus
|
|
3732
|
+
.on('previous-page')
|
|
3733
|
+
.pipe(takeUntilDestroyed())
|
|
3734
|
+
.subscribe(() => {
|
|
3735
|
+
this.navigateToPreviousPage();
|
|
3736
|
+
});
|
|
3737
|
+
explicitEffect([this.pendingPageRequest], ([request]) => {
|
|
3738
|
+
if (!request)
|
|
3739
|
+
return;
|
|
3740
|
+
this.stateManager?.pendingPageRequest.set(null);
|
|
3741
|
+
this.navigateToPage(request.index, request.options);
|
|
3742
|
+
});
|
|
3743
|
+
explicitEffect([this.state], ([state]) => this.eventBus.dispatch(PagerStateEvent, state));
|
|
3744
|
+
explicitEffect([this.activePageOwnership], ([ownership]) => this.stateManager?.activePageState.set(ownership));
|
|
3745
|
+
}
|
|
3746
|
+
evaluatePageHidden(page) {
|
|
3747
|
+
if (!page.logic || page.logic.length === 0) {
|
|
3748
|
+
return false;
|
|
3749
|
+
}
|
|
3750
|
+
const hiddenLogic = page.logic.filter((l) => l.type === 'hidden');
|
|
3751
|
+
if (hiddenLogic.length === 0) {
|
|
3752
|
+
return false;
|
|
3753
|
+
}
|
|
3754
|
+
for (const logic of hiddenLogic) {
|
|
3755
|
+
if (typeof logic.condition === 'boolean') {
|
|
3756
|
+
if (logic.condition) {
|
|
3757
|
+
return true;
|
|
3758
|
+
}
|
|
3759
|
+
continue;
|
|
3760
|
+
}
|
|
3761
|
+
const condition = logic.condition;
|
|
3762
|
+
const context = this.fieldContextRegistry.createDisplayOnlyContext(page.key || '', this.functionRegistry.getCustomFunctions());
|
|
3763
|
+
if (evaluateCondition(condition, context)) {
|
|
3764
|
+
return true;
|
|
3768
3765
|
}
|
|
3769
3766
|
}
|
|
3770
|
-
|
|
3771
|
-
const validatedFields = sanitizedFields;
|
|
3772
|
-
const flattenedFields = this.fieldProcessors.memoizedFlattenFields(validatedFields, registry);
|
|
3773
|
-
const flattenedFieldsForRendering = this.fieldProcessors.memoizedFlattenFieldsForRendering(validatedFields, registry);
|
|
3774
|
-
const fieldsById = this.fieldProcessors.memoizedKeyBy(flattenedFields);
|
|
3775
|
-
const defaultValues = this.fieldProcessors.memoizedDefaultValues(fieldsById, registry);
|
|
3776
|
-
const fieldsToRender = mode === 'paged' ? [] : flattenedFieldsForRendering;
|
|
3777
|
-
return {
|
|
3778
|
-
fields: fieldsToRender,
|
|
3779
|
-
schemaFields: flattenedFields,
|
|
3780
|
-
originalFields: fields,
|
|
3781
|
-
defaultValues,
|
|
3782
|
-
mode,
|
|
3783
|
-
registry,
|
|
3784
|
-
};
|
|
3785
|
-
}
|
|
3786
|
-
createEmptyFormSetup(registry) {
|
|
3787
|
-
return {
|
|
3788
|
-
fields: [],
|
|
3789
|
-
schemaFields: [],
|
|
3790
|
-
defaultValues: {},
|
|
3791
|
-
mode: 'non-paged',
|
|
3792
|
-
registry,
|
|
3793
|
-
};
|
|
3767
|
+
return false;
|
|
3794
3768
|
}
|
|
3795
|
-
|
|
3796
|
-
|
|
3769
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: PageOrchestratorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3770
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.5", 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: [
|
|
3771
|
+
{
|
|
3772
|
+
provide: FIELD_SIGNAL_CONTEXT,
|
|
3773
|
+
useFactory: (orchestrator) => orchestrator.extendedFieldSignalContext(),
|
|
3774
|
+
deps: [PageOrchestratorComponent],
|
|
3775
|
+
},
|
|
3776
|
+
], ngImport: i0, template: `
|
|
3777
|
+
@for (page of pageRenderStates(); track page.field.key) {
|
|
3778
|
+
<!--
|
|
3779
|
+
Skip pages hidden by 'hidden' logic conditions entirely. FormStateManager
|
|
3780
|
+
derives schema/value/validators from config (not from mounted components),
|
|
3781
|
+
so unmounting a hidden page has no effect on form state — only on render +
|
|
3782
|
+
CD cost. This is the page-level equivalent of the existing field-level
|
|
3783
|
+
\`@if (!field.hidden())\` gate in page-field.component.ts.
|
|
3784
|
+
-->
|
|
3785
|
+
@if (!page.hidden) {
|
|
3786
|
+
@if (page.active || page.preload) {
|
|
3787
|
+
@defer (when page.active; on idle) {
|
|
3788
|
+
<section page-field [field]="page.field" [key]="page.field.key" [pageIndex]="page.index" [isVisible]="page.active"></section>
|
|
3789
|
+
} @placeholder {
|
|
3790
|
+
<div class="df-page-placeholder" [attr.data-page-index]="page.index" [attr.data-page-key]="page.field.key"></div>
|
|
3791
|
+
}
|
|
3792
|
+
} @else {
|
|
3793
|
+
<!--
|
|
3794
|
+
Pages outside the preload window remain lightweight placeholders.
|
|
3795
|
+
Form state derives from config, so leaving their components unmounted
|
|
3796
|
+
does not affect validity, derivations, or navigation. The active page
|
|
3797
|
+
renders directly; configured neighbours preload declaratively on idle.
|
|
3798
|
+
-->
|
|
3799
|
+
<div class="df-page-placeholder" [attr.data-page-index]="page.index" [attr.data-page-key]="page.field.key"></div>
|
|
3800
|
+
}
|
|
3801
|
+
}
|
|
3797
3802
|
}
|
|
3798
|
-
|
|
3799
|
-
|
|
3803
|
+
`, isInline: true, styles: [":host{display:block;position:relative;width:100%}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, deferBlockDependencies: [() => [/* @ts-ignore */
|
|
3804
|
+
import('./ng-forge-dynamic-forms-page-field.component-DPKJcwUO.mjs').then(m => m.default)]] });
|
|
3800
3805
|
}
|
|
3801
|
-
i0.ɵɵ
|
|
3802
|
-
|
|
3803
|
-
|
|
3806
|
+
i0.ɵɵngDeclareClassMetadataAsync({ minVersion: "18.0.0", version: "22.1.5", ngImport: i0, type: PageOrchestratorComponent, resolveDeferredDeps: () => [/* @ts-ignore */
|
|
3807
|
+
import('./ng-forge-dynamic-forms-page-field.component-DPKJcwUO.mjs').then(m => m.default)], resolveMetadata: PageFieldComponent => ({ decorators: [{
|
|
3808
|
+
type: Component,
|
|
3809
|
+
args: [{ selector: 'div[page-orchestrator]', imports: [PageFieldComponent], template: `
|
|
3810
|
+
@for (page of pageRenderStates(); track page.field.key) {
|
|
3811
|
+
<!--
|
|
3812
|
+
Skip pages hidden by 'hidden' logic conditions entirely. FormStateManager
|
|
3813
|
+
derives schema/value/validators from config (not from mounted components),
|
|
3814
|
+
so unmounting a hidden page has no effect on form state — only on render +
|
|
3815
|
+
CD cost. This is the page-level equivalent of the existing field-level
|
|
3816
|
+
\`@if (!field.hidden())\` gate in page-field.component.ts.
|
|
3817
|
+
-->
|
|
3818
|
+
@if (!page.hidden) {
|
|
3819
|
+
@if (page.active || page.preload) {
|
|
3820
|
+
@defer (when page.active; on idle) {
|
|
3821
|
+
<section page-field [field]="page.field" [key]="page.field.key" [pageIndex]="page.index" [isVisible]="page.active"></section>
|
|
3822
|
+
} @placeholder {
|
|
3823
|
+
<div class="df-page-placeholder" [attr.data-page-index]="page.index" [attr.data-page-key]="page.field.key"></div>
|
|
3824
|
+
}
|
|
3825
|
+
} @else {
|
|
3826
|
+
<!--
|
|
3827
|
+
Pages outside the preload window remain lightweight placeholders.
|
|
3828
|
+
Form state derives from config, so leaving their components unmounted
|
|
3829
|
+
does not affect validity, derivations, or navigation. The active page
|
|
3830
|
+
renders directly; configured neighbours preload declaratively on idle.
|
|
3831
|
+
-->
|
|
3832
|
+
<div class="df-page-placeholder" [attr.data-page-index]="page.index" [attr.data-page-key]="page.field.key"></div>
|
|
3833
|
+
}
|
|
3834
|
+
}
|
|
3835
|
+
}
|
|
3836
|
+
`, host: {
|
|
3837
|
+
class: 'df-page-orchestrator',
|
|
3838
|
+
'[attr.data-current-page]': 'state().currentPageIndex',
|
|
3839
|
+
'[attr.data-total-pages]': 'state().totalPages',
|
|
3840
|
+
}, providers: [
|
|
3841
|
+
{
|
|
3842
|
+
provide: FIELD_SIGNAL_CONTEXT,
|
|
3843
|
+
useFactory: (orchestrator) => orchestrator.extendedFieldSignalContext(),
|
|
3844
|
+
deps: [PageOrchestratorComponent],
|
|
3845
|
+
},
|
|
3846
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;position:relative;width:100%}\n"] }]
|
|
3847
|
+
}], 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 }] }] } }) });
|
|
3804
3848
|
|
|
3805
3849
|
function traverseFieldsWithContext(fields, context, visitor, hooks) {
|
|
3806
3850
|
for (const field of fields) {
|
|
@@ -3879,14 +3923,14 @@ function createDerivationRenderGate() {
|
|
|
3879
3923
|
if (!has || engine() !== 'idle')
|
|
3880
3924
|
return;
|
|
3881
3925
|
engine.set('loading');
|
|
3882
|
-
pendingTasks.run(() => import('./ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-
|
|
3926
|
+
pendingTasks.run(() => import('./ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-CTOTwzzw.mjs')
|
|
3883
3927
|
.then(({ bootstrapDerivationOrchestrator }) => {
|
|
3884
3928
|
runInInjectionContext(injector, () => bootstrapDerivationOrchestrator());
|
|
3885
3929
|
engine.set('wired');
|
|
3886
3930
|
})
|
|
3887
3931
|
.catch((error) => {
|
|
3888
3932
|
engine.set('failed');
|
|
3889
|
-
logger.error('
|
|
3933
|
+
logger.error('Failed to load the derivation engine; derivations will not apply.', error);
|
|
3890
3934
|
}));
|
|
3891
3935
|
});
|
|
3892
3936
|
return computed(() => {
|
|
@@ -3897,6 +3941,68 @@ function createDerivationRenderGate() {
|
|
|
3897
3941
|
});
|
|
3898
3942
|
}
|
|
3899
3943
|
|
|
3944
|
+
const WEB_MCP_ENABLED = new InjectionToken('WEB_MCP_ENABLED', {
|
|
3945
|
+
providedIn: 'root',
|
|
3946
|
+
factory: () => false,
|
|
3947
|
+
});
|
|
3948
|
+
|
|
3949
|
+
const WEB_MCP_GATE = new InjectionToken('WEB_MCP_GATE');
|
|
3950
|
+
function createWebMcpGate() {
|
|
3951
|
+
const enabled = inject(WEB_MCP_ENABLED);
|
|
3952
|
+
const isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
|
|
3953
|
+
const stateManager = inject(FormStateManager);
|
|
3954
|
+
const injector = inject(Injector);
|
|
3955
|
+
const destroyRef = inject(DestroyRef);
|
|
3956
|
+
const logger = inject(DynamicFormLogger);
|
|
3957
|
+
if (!enabled || !isBrowser)
|
|
3958
|
+
return computed(() => 'disabled');
|
|
3959
|
+
const registration = computed(() => {
|
|
3960
|
+
const options = stateManager.effectiveFormOptions().webMcp;
|
|
3961
|
+
return options ? { options: options, setup: stateManager.formSetup() } : undefined;
|
|
3962
|
+
}, /* @ts-ignore */
|
|
3963
|
+
...(ngDevMode ? [{ debugName: "registration" }] : /* istanbul ignore next */ []));
|
|
3964
|
+
const status = signal('idle', /* @ts-ignore */
|
|
3965
|
+
...(ngDevMode ? [{ debugName: "status" }] : /* istanbul ignore next */ []));
|
|
3966
|
+
let epoch;
|
|
3967
|
+
let epochId = 0;
|
|
3968
|
+
const closeEpoch = () => {
|
|
3969
|
+
epoch?.abort();
|
|
3970
|
+
epoch = undefined;
|
|
3971
|
+
};
|
|
3972
|
+
destroyRef.onDestroy(closeEpoch);
|
|
3973
|
+
explicitEffect([registration], ([input]) => {
|
|
3974
|
+
closeEpoch();
|
|
3975
|
+
epochId += 1;
|
|
3976
|
+
const thisEpoch = epochId;
|
|
3977
|
+
if (!input) {
|
|
3978
|
+
status.set('idle');
|
|
3979
|
+
return;
|
|
3980
|
+
}
|
|
3981
|
+
const controller = new AbortController();
|
|
3982
|
+
epoch = controller;
|
|
3983
|
+
status.set('registering');
|
|
3984
|
+
void import('./ng-forge-dynamic-forms-bootstrap-web-mcp-BtjqeJOj.mjs')
|
|
3985
|
+
.then(({ bootstrapWebMcp }) => {
|
|
3986
|
+
if (thisEpoch !== epochId || controller.signal.aborted)
|
|
3987
|
+
return;
|
|
3988
|
+
return runInInjectionContext(injector, () => bootstrapWebMcp(input.options, controller.signal));
|
|
3989
|
+
})
|
|
3990
|
+
.then((result) => {
|
|
3991
|
+
if (thisEpoch !== epochId || controller.signal.aborted)
|
|
3992
|
+
return;
|
|
3993
|
+
if (result)
|
|
3994
|
+
status.set(result);
|
|
3995
|
+
})
|
|
3996
|
+
.catch((error) => {
|
|
3997
|
+
if (thisEpoch !== epochId)
|
|
3998
|
+
return;
|
|
3999
|
+
status.set('failed');
|
|
4000
|
+
logger.error('Failed to load the WebMCP registrar; this form will not be exposed to agents.', error);
|
|
4001
|
+
});
|
|
4002
|
+
});
|
|
4003
|
+
return status.asReadonly();
|
|
4004
|
+
}
|
|
4005
|
+
|
|
3900
4006
|
const FORM_INITIALIZER = new InjectionToken('FORM_INITIALIZER');
|
|
3901
4007
|
|
|
3902
4008
|
const AUTO_ID_PREFIX = 'df-';
|
|
@@ -3929,10 +4035,10 @@ class DynamicFormInstanceRegistry {
|
|
|
3929
4035
|
this.slots.delete(Number(id.slice(AUTO_ID_PREFIX.length)));
|
|
3930
4036
|
this._membership.update((m) => m + 1);
|
|
3931
4037
|
}
|
|
3932
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.
|
|
3933
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.
|
|
4038
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: DynamicFormInstanceRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
4039
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: DynamicFormInstanceRegistry, providedIn: 'root' });
|
|
3934
4040
|
}
|
|
3935
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.
|
|
4041
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: DynamicFormInstanceRegistry, decorators: [{
|
|
3936
4042
|
type: Injectable,
|
|
3937
4043
|
args: [{ providedIn: 'root' }]
|
|
3938
4044
|
}] });
|
|
@@ -3983,10 +4089,10 @@ class FormIdPrefixService {
|
|
|
3983
4089
|
});
|
|
3984
4090
|
}
|
|
3985
4091
|
}
|
|
3986
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.
|
|
3987
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.
|
|
4092
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: FormIdPrefixService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
4093
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: FormIdPrefixService });
|
|
3988
4094
|
}
|
|
3989
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.
|
|
4095
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: FormIdPrefixService, decorators: [{
|
|
3990
4096
|
type: Injectable
|
|
3991
4097
|
}], ctorParameters: () => [] });
|
|
3992
4098
|
|
|
@@ -4039,6 +4145,7 @@ function coreProviders() {
|
|
|
4039
4145
|
deps: [FormStateManager],
|
|
4040
4146
|
},
|
|
4041
4147
|
FormIdPrefixService,
|
|
4148
|
+
FieldViewportObserver,
|
|
4042
4149
|
{
|
|
4043
4150
|
provide: FORM_ID_PREFIX,
|
|
4044
4151
|
useFactory: (svc) => svc.prefix,
|
|
@@ -4078,6 +4185,7 @@ function provideDynamicFormDI() {
|
|
|
4078
4185
|
...asyncExpressionProviders(),
|
|
4079
4186
|
...propertyDerivationProviders(),
|
|
4080
4187
|
...derivationProviders(),
|
|
4188
|
+
{ provide: WEB_MCP_GATE, useFactory: createWebMcpGate },
|
|
4081
4189
|
];
|
|
4082
4190
|
}
|
|
4083
4191
|
|
|
@@ -4092,10 +4200,10 @@ class EventDispatcher {
|
|
|
4092
4200
|
disconnect() {
|
|
4093
4201
|
this.bus = undefined;
|
|
4094
4202
|
}
|
|
4095
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.
|
|
4096
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.
|
|
4203
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: EventDispatcher, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
4204
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: EventDispatcher });
|
|
4097
4205
|
}
|
|
4098
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.
|
|
4206
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: EventDispatcher, decorators: [{
|
|
4099
4207
|
type: Injectable
|
|
4100
4208
|
}] });
|
|
4101
4209
|
|
|
@@ -4103,10 +4211,10 @@ class DfTemplate {
|
|
|
4103
4211
|
dfTemplate = input.required(/* @ts-ignore */
|
|
4104
4212
|
...(ngDevMode ? [{ debugName: "dfTemplate" }] : /* istanbul ignore next */ []));
|
|
4105
4213
|
templateRef = inject(TemplateRef);
|
|
4106
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.
|
|
4107
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.
|
|
4214
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: DfTemplate, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
4215
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.5", type: DfTemplate, isStandalone: true, selector: "ng-template[dfTemplate]", inputs: { dfTemplate: { classPropertyName: "dfTemplate", publicName: "dfTemplate", isSignal: true, isRequired: true, transformFunction: null } }, exportAs: ["dfTemplate"], ngImport: i0 });
|
|
4108
4216
|
}
|
|
4109
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.
|
|
4217
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: DfTemplate, decorators: [{
|
|
4110
4218
|
type: Directive,
|
|
4111
4219
|
args: [{
|
|
4112
4220
|
selector: 'ng-template[dfTemplate]',
|
|
@@ -4120,32 +4228,13 @@ class DfFieldTemplateRegistry {
|
|
|
4120
4228
|
set(value) {
|
|
4121
4229
|
this._map.set(value);
|
|
4122
4230
|
}
|
|
4123
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.
|
|
4124
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.
|
|
4231
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: DfFieldTemplateRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
4232
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: DfFieldTemplateRegistry });
|
|
4125
4233
|
}
|
|
4126
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.
|
|
4234
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: DfFieldTemplateRegistry, decorators: [{
|
|
4127
4235
|
type: Injectable
|
|
4128
4236
|
}] });
|
|
4129
4237
|
|
|
4130
|
-
const FIELD_WINDOWING = new InjectionToken('FIELD_WINDOWING', {
|
|
4131
|
-
providedIn: 'root',
|
|
4132
|
-
factory: () => ({ enabled: false, eager: 12, placeholderHeight: '4rem' }),
|
|
4133
|
-
});
|
|
4134
|
-
|
|
4135
|
-
function resolveFieldWindowing(global, perForm) {
|
|
4136
|
-
if (perForm === undefined) {
|
|
4137
|
-
return global;
|
|
4138
|
-
}
|
|
4139
|
-
if (typeof perForm === 'boolean') {
|
|
4140
|
-
return { ...global, enabled: perForm };
|
|
4141
|
-
}
|
|
4142
|
-
return {
|
|
4143
|
-
enabled: true,
|
|
4144
|
-
eager: clampWindowSize(perForm.eager, global.eager),
|
|
4145
|
-
placeholderHeight: perForm.placeholderHeight ?? global.placeholderHeight,
|
|
4146
|
-
};
|
|
4147
|
-
}
|
|
4148
|
-
|
|
4149
4238
|
class DynamicForm {
|
|
4150
4239
|
config = input.required({ ...(ngDevMode ? { debugName: "config" } : /* istanbul ignore next */ {}), alias: 'dynamic-form' });
|
|
4151
4240
|
formOptions = input(undefined, /* @ts-ignore */
|
|
@@ -4186,22 +4275,31 @@ class DynamicForm {
|
|
|
4186
4275
|
submitting = this.stateManager.submitting;
|
|
4187
4276
|
fieldLoadingErrors = this.stateManager.fieldLoadingErrors;
|
|
4188
4277
|
derivationReady = inject(DERIVATION_RENDER_GATE);
|
|
4189
|
-
shouldRender = computed(() => this.stateManager.shouldRender() && this.derivationReady(), /* @ts-ignore */
|
|
4278
|
+
shouldRender = computed(() => this.stateManager.shouldRender() && this.stateManager.formSchemaReady() && this.derivationReady(), /* @ts-ignore */
|
|
4190
4279
|
...(ngDevMode ? [{ debugName: "shouldRender" }] : /* istanbul ignore next */ []));
|
|
4280
|
+
webMcpStatus = inject(WEB_MCP_GATE);
|
|
4191
4281
|
resolvedFields = this.stateManager.resolvedFields;
|
|
4192
4282
|
fieldWindowing = computed(() => resolveFieldWindowing(this.globalFieldWindowing, this.effectiveFormOptions()?.fieldWindowing), /* @ts-ignore */
|
|
4193
4283
|
...(ngDevMode ? [{ debugName: "fieldWindowing" }] : /* istanbul ignore next */ []));
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4284
|
+
initializedComponents = signal(new Set(), /* @ts-ignore */
|
|
4285
|
+
...(ngDevMode ? [{ debugName: "initializedComponents" }] : /* istanbul ignore next */ []));
|
|
4286
|
+
initialPageReady = signal(false, /* @ts-ignore */
|
|
4287
|
+
...(ngDevMode ? [{ debugName: "initialPageReady" }] : /* istanbul ignore next */ []));
|
|
4288
|
+
initializationReady = computed(() => {
|
|
4289
|
+
const config = this.activeConfig();
|
|
4290
|
+
if (!config)
|
|
4291
|
+
return false;
|
|
4292
|
+
if (this.formModeDetection().mode === 'paged')
|
|
4293
|
+
return this.initialPageReady();
|
|
4294
|
+
const initialized = this.initializedComponents();
|
|
4295
|
+
const expected = [
|
|
4296
|
+
initializationComponentKey('dynamic-form', this.componentId),
|
|
4297
|
+
...collectInitializingContainerKeys(config.fields ?? []),
|
|
4298
|
+
];
|
|
4299
|
+
return expected.every((key) => initialized.has(key));
|
|
4197
4300
|
}, /* @ts-ignore */
|
|
4198
|
-
...(ngDevMode ? [{ debugName: "
|
|
4199
|
-
|
|
4200
|
-
eventBus: this.eventBus,
|
|
4201
|
-
totalComponentsCount: this.totalComponentsCount,
|
|
4202
|
-
injector: this.injector,
|
|
4203
|
-
componentId: this.componentId,
|
|
4204
|
-
});
|
|
4301
|
+
...(ngDevMode ? [{ debugName: "initializationReady" }] : /* istanbul ignore next */ []));
|
|
4302
|
+
initialized$ = toObservable(this.initializationReady, { injector: this.injector }).pipe(filter((ready) => ready), take(1), shareReplay({ bufferSize: 1, refCount: false }));
|
|
4205
4303
|
validityChange = outputFromObservable(toObservable(this.valid));
|
|
4206
4304
|
dirtyChange = outputFromObservable(toObservable(this.dirty));
|
|
4207
4305
|
submitted = outputFromObservable(this.stateManager.submitted$);
|
|
@@ -4209,12 +4307,32 @@ class DynamicForm {
|
|
|
4209
4307
|
cleared = outputFromObservable(this.eventBus.on('form-clear'));
|
|
4210
4308
|
events = outputFromObservable(this.eventBus.events$);
|
|
4211
4309
|
initialized = outputFromObservable(this.initialized$);
|
|
4310
|
+
activePageInitialized = outputFromObservable(this.eventBus.on('active-page-initialized'));
|
|
4212
4311
|
onPageChange = outputFromObservable(this.eventBus.on('page-change'));
|
|
4213
|
-
onPageNavigationStateChange = outputFromObservable(this.eventBus.on('
|
|
4312
|
+
onPageNavigationStateChange = outputFromObservable(this.eventBus.on('pager-state'));
|
|
4214
4313
|
constructor() {
|
|
4314
|
+
this.eventBus
|
|
4315
|
+
.on('component-initialized')
|
|
4316
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
4317
|
+
.subscribe((event) => {
|
|
4318
|
+
const key = initializationComponentKey(event.componentType, event.componentId);
|
|
4319
|
+
this.initializedComponents.update((current) => {
|
|
4320
|
+
if (current.has(key))
|
|
4321
|
+
return current;
|
|
4322
|
+
const next = new Set(current);
|
|
4323
|
+
next.add(key);
|
|
4324
|
+
return next;
|
|
4325
|
+
});
|
|
4326
|
+
});
|
|
4327
|
+
this.eventBus
|
|
4328
|
+
.on('active-page-initialized')
|
|
4329
|
+
.pipe(takeUntilDestroyed(this.destroyRef), take(1))
|
|
4330
|
+
.subscribe(() => this.initialPageReady.set(true));
|
|
4215
4331
|
this.dispatcher?.connect(this.eventBus);
|
|
4216
4332
|
this.destroyRef.onDestroy(() => this.dispatcher?.disconnect());
|
|
4217
4333
|
inject(FORM_INITIALIZER, { optional: true });
|
|
4334
|
+
const preload = injectFormComponentPreloader();
|
|
4335
|
+
explicitEffect([this.config], ([config]) => preload.preloadConfig(config));
|
|
4218
4336
|
this.setupEffects();
|
|
4219
4337
|
this.setupEventHandlers();
|
|
4220
4338
|
}
|
|
@@ -4236,8 +4354,8 @@ class DynamicForm {
|
|
|
4236
4354
|
this.eventBus.dispatch(ComponentInitializedEvent, 'dynamic-form', this.componentId);
|
|
4237
4355
|
}
|
|
4238
4356
|
});
|
|
4239
|
-
explicitEffect([this.resolvedFields, this.formModeDetection], ([
|
|
4240
|
-
if (mode === 'non-paged'
|
|
4357
|
+
explicitEffect([this.resolvedFields, this.formModeDetection, this.activeConfig], ([, { mode }, config]) => {
|
|
4358
|
+
if (config && mode === 'non-paged') {
|
|
4241
4359
|
afterNextRender(() => {
|
|
4242
4360
|
this.eventBus.dispatch(ComponentInitializedEvent, 'dynamic-form', this.componentId);
|
|
4243
4361
|
}, { injector: this.injector });
|
|
@@ -4272,8 +4390,8 @@ class DynamicForm {
|
|
|
4272
4390
|
error: (err) => this.logger.error('Submission handler error', err),
|
|
4273
4391
|
});
|
|
4274
4392
|
}
|
|
4275
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.
|
|
4276
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.
|
|
4393
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: DynamicForm, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4394
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.5", 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: [
|
|
4277
4395
|
provideDynamicFormDI(),
|
|
4278
4396
|
DfFieldTemplateRegistry,
|
|
4279
4397
|
{ provide: DF_FIELD_TEMPLATES, useFactory: () => inject(DfFieldTemplateRegistry).map },
|
|
@@ -4285,12 +4403,8 @@ class DynamicForm {
|
|
|
4285
4403
|
}
|
|
4286
4404
|
@case ('non-paged') {
|
|
4287
4405
|
@for (field of resolvedFields(); track field.key; let i = $index) {
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
the directive's gate stops a same-CD-pass mount before [formField] can warn.
|
|
4291
|
-
Don't dedupe one without the other — see DfFieldOutlet.renderReady. -->
|
|
4292
|
-
@if (!field.hidden()) {
|
|
4293
|
-
@if (windowsField(field, i)) {
|
|
4406
|
+
@if (windowsField(field, i)) {
|
|
4407
|
+
@if (!field.hidden()) {
|
|
4294
4408
|
@defer (on viewport) {
|
|
4295
4409
|
<ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
|
|
4296
4410
|
} @placeholder {
|
|
@@ -4301,9 +4415,11 @@ class DynamicForm {
|
|
|
4301
4415
|
aria-hidden="true"
|
|
4302
4416
|
></div>
|
|
4303
4417
|
}
|
|
4304
|
-
} @else {
|
|
4305
|
-
<ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
|
|
4306
4418
|
}
|
|
4419
|
+
} @else {
|
|
4420
|
+
<!-- DfFieldOutlet owns the hidden gate so an already-mounted field can be detached
|
|
4421
|
+
and restored without recreating its component and wrapper chain. -->
|
|
4422
|
+
<ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
|
|
4307
4423
|
}
|
|
4308
4424
|
}
|
|
4309
4425
|
}
|
|
@@ -4314,7 +4430,7 @@ class DynamicForm {
|
|
|
4314
4430
|
}
|
|
4315
4431
|
`, isInline: true, styles: [".df-form{display:grid;grid-template-columns:1fr;gap:var(--df-grid-row-gap, .5rem);width:100%}.df-form>*{grid-column:1/-1}.df-row{display:grid;grid-template-columns:repeat(var(--df-grid-columns, 12),1fr);gap:var(--df-grid-gap, .5rem);align-items:start;width:100%}.df-row>*:not([class*=df-col-]){grid-column:1/-1}.df-col-1{grid-column:span 1}.df-col-2{grid-column:span 2}.df-col-3{grid-column:span 3}.df-col-4{grid-column:span 4}.df-col-5{grid-column:span 5}.df-col-6{grid-column:span 6}.df-col-7{grid-column:span 7}.df-col-8{grid-column:span 8}.df-col-9{grid-column:span 9}.df-col-10{grid-column:span 10}.df-col-11{grid-column:span 11}.df-col-12{grid-column:span 12}.df-col-auto{grid-column:span auto;width:auto}.df-col-full{grid-column:1/-1}.df-col-start-1{grid-column-start:1}.df-col-start-2{grid-column-start:2}.df-col-start-3{grid-column-start:3}.df-col-start-4{grid-column-start:4}.df-col-start-5{grid-column-start:5}.df-col-start-6{grid-column-start:6}.df-col-start-7{grid-column-start:7}.df-col-start-8{grid-column-start:8}.df-col-start-9{grid-column-start:9}.df-col-start-10{grid-column-start:10}.df-col-start-11{grid-column-start:11}.df-col-start-12{grid-column-start:12}.df-col-end-1{grid-column-end:1}.df-col-end-2{grid-column-end:2}.df-col-end-3{grid-column-end:3}.df-col-end-4{grid-column-end:4}.df-col-end-5{grid-column-end:5}.df-col-end-6{grid-column-end:6}.df-col-end-7{grid-column-end:7}.df-col-end-8{grid-column-end:8}.df-col-end-9{grid-column-end:9}.df-col-end-10{grid-column-end:10}.df-col-end-11{grid-column-end:11}.df-col-end-12{grid-column-end:12}.df-col-end-13{grid-column-end:13}@media(max-width:576px){.df-form{--df-grid-gap: var(--df-grid-gap-sm, .5rem);--df-grid-row-gap: var(--df-grid-row-gap-sm, .5rem)}.df-row{grid-template-columns:1fr}.df-row>*{grid-column:1/-1!important}.df-row.df-row-mobile-keep-cols{grid-template-columns:repeat(var(--df-grid-columns, 12),1fr)}.df-row.df-row-mobile-keep-cols>*{grid-column:revert!important}}@media(min-width:577px)and (max-width:768px){.df-form{--df-grid-gap: var(--df-grid-gap-md, .5rem);--df-grid-row-gap: var(--df-grid-row-gap-md, .5rem)}.df-row{--df-grid-columns: 6}.df-col-sm-1{grid-column:span 1}.df-col-sm-2{grid-column:span 2}.df-col-sm-3{grid-column:span 3}.df-col-sm-4{grid-column:span 4}.df-col-sm-5{grid-column:span 5}.df-col-sm-6{grid-column:span 6}.df-col-sm-full{grid-column:1/-1}}@media(min-width:769px)and (max-width:992px){.df-form{--df-grid-gap: var(--df-grid-gap-lg, .5rem);--df-grid-row-gap: var(--df-grid-row-gap-lg, .5rem)}.df-col-md-1{grid-column:span 1}.df-col-md-2{grid-column:span 2}.df-col-md-3{grid-column:span 3}.df-col-md-4{grid-column:span 4}.df-col-md-5{grid-column:span 5}.df-col-md-6{grid-column:span 6}.df-col-md-7{grid-column:span 7}.df-col-md-8{grid-column:span 8}.df-col-md-9{grid-column:span 9}.df-col-md-10{grid-column:span 10}.df-col-md-11{grid-column:span 11}.df-col-md-12{grid-column:span 12}.df-col-md-full{grid-column:1/-1}}@media(min-width:993px){.df-form{--df-grid-gap: var(--df-grid-gap-xl, .5rem);--df-grid-row-gap: var(--df-grid-row-gap-xl, .5rem)}.df-col-lg-1{grid-column:span 1}.df-col-lg-2{grid-column:span 2}.df-col-lg-3{grid-column:span 3}.df-col-lg-4{grid-column:span 4}.df-col-lg-5{grid-column:span 5}.df-col-lg-6{grid-column:span 6}.df-col-lg-7{grid-column:span 7}.df-col-lg-8{grid-column:span 8}.df-col-lg-9{grid-column:span 9}.df-col-lg-10{grid-column:span 10}.df-col-lg-11{grid-column:span 11}.df-col-lg-12{grid-column:span 12}.df-col-lg-full{grid-column:1/-1}}.df-gap-none{--df-grid-gap: 0}.df-gap-xs{--df-grid-gap: .25rem}.df-gap-sm{--df-grid-gap: .5rem}.df-gap-md{--df-grid-gap: 1rem}.df-gap-lg{--df-grid-gap: 1.5rem}.df-gap-xl{--df-grid-gap: 2rem}.df-row-gap-none{--df-grid-row-gap: 0}.df-row-gap-xs{--df-grid-row-gap: .25rem}.df-row-gap-sm{--df-grid-row-gap: .5rem}.df-row-gap-md{--df-grid-row-gap: 1rem}.df-row-gap-lg{--df-grid-row-gap: 1.5rem}.df-row-gap-xl{--df-grid-row-gap: 2rem}.df-field{display:block;width:100%;min-width:0;overflow:hidden;margin:0}.df-group,.df-page{display:block;width:100%}.df-form.disabled,.df-row.disabled,.df-field.disabled{opacity:.6;pointer-events:none}.df-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.df-sr-only-focusable:focus,.df-sr-only-focusable:active{position:static;width:auto;height:auto;padding:inherit;margin:inherit;overflow:visible;clip:auto;white-space:normal}.df-live-region{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.df-form :focus-visible{outline:2px solid var(--df-focus-color, #005fcc);outline-offset:2px}.df-group:focus-within{outline:1px dashed var(--df-focus-color, #005fcc);outline-offset:4px}:host{display:grid;grid-template-columns:1fr;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"], dependencies: [{ kind: "directive", type: DfFieldOutlet, selector: "[dfFieldOutlet]", inputs: ["dfFieldOutlet", "dfFieldOutletEnvironmentInjector"] }, { kind: "component", type: PageOrchestratorComponent, selector: "div[page-orchestrator]", inputs: ["pageFields", "form", "fieldSignalContext"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, deferBlockDependencies: [() => [DfFieldOutlet]] });
|
|
4316
4432
|
}
|
|
4317
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.
|
|
4433
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: DynamicForm, decorators: [{
|
|
4318
4434
|
type: Component,
|
|
4319
4435
|
args: [{ selector: 'form[dynamic-form]', imports: [DfFieldOutlet, PageOrchestratorComponent], template: `
|
|
4320
4436
|
@if (shouldRender()) {
|
|
@@ -4324,12 +4440,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
|
|
|
4324
4440
|
}
|
|
4325
4441
|
@case ('non-paged') {
|
|
4326
4442
|
@for (field of resolvedFields(); track field.key; let i = $index) {
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
the directive's gate stops a same-CD-pass mount before [formField] can warn.
|
|
4330
|
-
Don't dedupe one without the other — see DfFieldOutlet.renderReady. -->
|
|
4331
|
-
@if (!field.hidden()) {
|
|
4332
|
-
@if (windowsField(field, i)) {
|
|
4443
|
+
@if (windowsField(field, i)) {
|
|
4444
|
+
@if (!field.hidden()) {
|
|
4333
4445
|
@defer (on viewport) {
|
|
4334
4446
|
<ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
|
|
4335
4447
|
} @placeholder {
|
|
@@ -4340,9 +4452,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
|
|
|
4340
4452
|
aria-hidden="true"
|
|
4341
4453
|
></div>
|
|
4342
4454
|
}
|
|
4343
|
-
} @else {
|
|
4344
|
-
<ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
|
|
4345
4455
|
}
|
|
4456
|
+
} @else {
|
|
4457
|
+
<!-- DfFieldOutlet owns the hidden gate so an already-mounted field can be detached
|
|
4458
|
+
and restored without recreating its component and wrapper chain. -->
|
|
4459
|
+
<ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
|
|
4346
4460
|
}
|
|
4347
4461
|
}
|
|
4348
4462
|
}
|
|
@@ -4364,29 +4478,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
|
|
|
4364
4478
|
'[attr.id]': 'idPrefix() || null',
|
|
4365
4479
|
'(submit)': 'onNativeSubmit($event)',
|
|
4366
4480
|
}, 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"] }]
|
|
4367
|
-
}], 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"] }] } });
|
|
4368
|
-
function countContainersRecursive(fields) {
|
|
4369
|
-
let count = 0;
|
|
4370
|
-
for (const field of fields) {
|
|
4371
|
-
if (isContainerField(field)) {
|
|
4372
|
-
count += 1;
|
|
4373
|
-
if (hasChildFields(field)) {
|
|
4374
|
-
const children = field.fields;
|
|
4375
|
-
if (Array.isArray(children)) {
|
|
4376
|
-
for (const child of children) {
|
|
4377
|
-
if (Array.isArray(child)) {
|
|
4378
|
-
count += countContainersRecursive(child);
|
|
4379
|
-
}
|
|
4380
|
-
else if (child != null && isContainerField(child)) {
|
|
4381
|
-
count += countContainersRecursive([child]);
|
|
4382
|
-
}
|
|
4383
|
-
}
|
|
4384
|
-
}
|
|
4385
|
-
}
|
|
4386
|
-
}
|
|
4387
|
-
}
|
|
4388
|
-
return count;
|
|
4389
|
-
}
|
|
4481
|
+
}], 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"] }] } });
|
|
4390
4482
|
|
|
4391
4483
|
const BUILT_IN_ADDON_TYPES = [
|
|
4392
4484
|
{
|
|
@@ -4434,35 +4526,35 @@ const DISPLAY_FIELD_TYPES_BASE = {
|
|
|
4434
4526
|
const BUILT_IN_FIELDS = [
|
|
4435
4527
|
{
|
|
4436
4528
|
name: 'row',
|
|
4437
|
-
loadComponent: () => import('./ng-forge-dynamic-forms-container-field.component-
|
|
4529
|
+
loadComponent: () => import('./ng-forge-dynamic-forms-container-field.component-Dc3ROhHL.mjs'),
|
|
4438
4530
|
mapper: rowFieldMapper,
|
|
4439
4531
|
valueHandling: 'flatten',
|
|
4440
4532
|
...CONTAINER_FIELD_TYPES_BASE,
|
|
4441
4533
|
},
|
|
4442
4534
|
{
|
|
4443
4535
|
name: 'group',
|
|
4444
|
-
loadComponent: () => import('./ng-forge-dynamic-forms-group-field.component-
|
|
4536
|
+
loadComponent: () => import('./ng-forge-dynamic-forms-group-field.component-DAC3P9DC.mjs'),
|
|
4445
4537
|
mapper: groupFieldMapper,
|
|
4446
4538
|
valueHandling: 'include',
|
|
4447
4539
|
...CONTAINER_FIELD_TYPES_BASE,
|
|
4448
4540
|
},
|
|
4449
4541
|
{
|
|
4450
4542
|
name: 'array',
|
|
4451
|
-
loadComponent: () => import('./ng-forge-dynamic-forms-array-field.component-
|
|
4543
|
+
loadComponent: () => import('./ng-forge-dynamic-forms-array-field.component-CSCKq_1K.mjs'),
|
|
4452
4544
|
mapper: arrayFieldMapper,
|
|
4453
4545
|
valueHandling: 'include',
|
|
4454
4546
|
...CONTAINER_FIELD_TYPES_BASE,
|
|
4455
4547
|
},
|
|
4456
4548
|
{
|
|
4457
4549
|
name: 'page',
|
|
4458
|
-
loadComponent: () => import('./ng-forge-dynamic-forms-page-field.component-
|
|
4550
|
+
loadComponent: () => import('./ng-forge-dynamic-forms-page-field.component-DPKJcwUO.mjs'),
|
|
4459
4551
|
mapper: pageFieldMapper,
|
|
4460
4552
|
valueHandling: 'flatten',
|
|
4461
4553
|
...CONTAINER_FIELD_TYPES_BASE,
|
|
4462
4554
|
},
|
|
4463
4555
|
{
|
|
4464
4556
|
name: 'text',
|
|
4465
|
-
loadComponent: () => import('./ng-forge-dynamic-forms-text-field.component-
|
|
4557
|
+
loadComponent: () => import('./ng-forge-dynamic-forms-text-field.component-BB-jutBq.mjs'),
|
|
4466
4558
|
mapper: textFieldMapper,
|
|
4467
4559
|
valueHandling: 'exclude',
|
|
4468
4560
|
...DISPLAY_FIELD_TYPES_BASE,
|
|
@@ -4473,7 +4565,7 @@ const BUILT_IN_FIELDS = [
|
|
|
4473
4565
|
},
|
|
4474
4566
|
{
|
|
4475
4567
|
name: 'container',
|
|
4476
|
-
loadComponent: () => import('./ng-forge-dynamic-forms-container-field.component-
|
|
4568
|
+
loadComponent: () => import('./ng-forge-dynamic-forms-container-field.component-Dc3ROhHL.mjs'),
|
|
4477
4569
|
mapper: containerFieldMapper,
|
|
4478
4570
|
valueHandling: 'flatten',
|
|
4479
4571
|
...CONTAINER_FIELD_TYPES_BASE,
|
|
@@ -4482,11 +4574,16 @@ const BUILT_IN_FIELDS = [
|
|
|
4482
4574
|
const BUILT_IN_WRAPPERS = [
|
|
4483
4575
|
{
|
|
4484
4576
|
wrapperName: 'css',
|
|
4485
|
-
loadComponent: () => import('./ng-forge-dynamic-forms-css-wrapper.component-
|
|
4577
|
+
loadComponent: () => import('./ng-forge-dynamic-forms-css-wrapper.component-BzgX8lAD.mjs'),
|
|
4486
4578
|
},
|
|
4487
4579
|
{
|
|
4488
4580
|
wrapperName: 'row',
|
|
4489
|
-
loadComponent: () => import('./ng-forge-dynamic-forms-row-wrapper.component-
|
|
4581
|
+
loadComponent: () => import('./ng-forge-dynamic-forms-row-wrapper.component-DMEeKZny.mjs'),
|
|
4582
|
+
},
|
|
4583
|
+
{
|
|
4584
|
+
wrapperName: 'field-errors',
|
|
4585
|
+
loadComponent: () => import('./ng-forge-dynamic-forms-field-errors-wrapper.component-CIKbWqYA.mjs'),
|
|
4586
|
+
rendersFieldErrors: true,
|
|
4490
4587
|
},
|
|
4491
4588
|
];
|
|
4492
4589
|
|
|
@@ -4547,7 +4644,8 @@ function provideDynamicForm(...items) {
|
|
|
4547
4644
|
const wrapperBundles = items.filter(isWrappersBundle);
|
|
4548
4645
|
const features = items.filter(isDynamicFormFeature);
|
|
4549
4646
|
const fields = [...BUILT_IN_FIELDS, ...fieldTypes];
|
|
4550
|
-
const
|
|
4647
|
+
const customWrappers = [...wrapperTypes, ...wrapperBundles.flatMap((bundle) => bundle.ɵdefinitions)];
|
|
4648
|
+
const wrappers = [...BUILT_IN_WRAPPERS, ...customWrappers];
|
|
4551
4649
|
const featureProviders = [];
|
|
4552
4650
|
const hasLoggerFeature = features.some((feature) => feature.ɵkind === 'logger');
|
|
4553
4651
|
features.forEach((feature) => {
|
|
@@ -4562,7 +4660,7 @@ function provideDynamicForm(...items) {
|
|
|
4562
4660
|
const logger = inject(DynamicFormLogger);
|
|
4563
4661
|
const registry = new Map();
|
|
4564
4662
|
fields.forEach((fieldType) => {
|
|
4565
|
-
|
|
4663
|
+
if (DEV_MODE && registry.has(fieldType.name)) {
|
|
4566
4664
|
logger.warn(`Field type "${fieldType.name}" is already registered. Overwriting.`);
|
|
4567
4665
|
}
|
|
4568
4666
|
registry.set(fieldType.name, fieldType);
|
|
@@ -4588,11 +4686,18 @@ function provideDynamicForm(...items) {
|
|
|
4588
4686
|
useFactory: () => {
|
|
4589
4687
|
const logger = inject(DynamicFormLogger);
|
|
4590
4688
|
const registry = new Map();
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
|
|
4689
|
+
const builtInNames = new Set(BUILT_IN_WRAPPERS.map((w) => w.wrapperName));
|
|
4690
|
+
BUILT_IN_WRAPPERS.forEach((wrapperType) => registry.set(wrapperType.wrapperName, wrapperType));
|
|
4691
|
+
const seenCustom = DEV_MODE ? new Set() : null;
|
|
4692
|
+
customWrappers.forEach((wrapperType) => {
|
|
4693
|
+
const name = wrapperType.wrapperName;
|
|
4694
|
+
if (seenCustom) {
|
|
4695
|
+
if (seenCustom.has(name) && !builtInNames.has(name)) {
|
|
4696
|
+
logger.warn(`Wrapper type "${name}" is already registered. Overwriting.`);
|
|
4697
|
+
}
|
|
4698
|
+
seenCustom.add(name);
|
|
4594
4699
|
}
|
|
4595
|
-
registry.set(
|
|
4700
|
+
registry.set(name, wrapperType);
|
|
4596
4701
|
});
|
|
4597
4702
|
return registry;
|
|
4598
4703
|
},
|
|
@@ -4623,7 +4728,7 @@ function provideDynamicForm(...items) {
|
|
|
4623
4728
|
registry.set(def.type, def);
|
|
4624
4729
|
}
|
|
4625
4730
|
for (const def of contributed) {
|
|
4626
|
-
|
|
4731
|
+
if (DEV_MODE && registry.has(def.type)) {
|
|
4627
4732
|
logger.warn(`Addon type "${def.type}" is already registered. Overwriting.`);
|
|
4628
4733
|
}
|
|
4629
4734
|
registry.set(def.type, def);
|
|
@@ -4681,10 +4786,22 @@ function withFieldWindowing(config) {
|
|
|
4681
4786
|
enabled: true,
|
|
4682
4787
|
eager: clampWindowSize(config?.eager, 12),
|
|
4683
4788
|
placeholderHeight: config?.placeholderHeight ?? '4rem',
|
|
4789
|
+
park: resolveFieldParking({ enabled: true, margin: '100%' }, config?.park),
|
|
4684
4790
|
};
|
|
4685
4791
|
return createFeature('field-windowing', [{ provide: FIELD_WINDOWING, useValue: resolved }]);
|
|
4686
4792
|
}
|
|
4687
4793
|
|
|
4794
|
+
function withExperimentalWebMcp() {
|
|
4795
|
+
return createFeature('web-mcp', [{ provide: WEB_MCP_ENABLED, useValue: true }]);
|
|
4796
|
+
}
|
|
4797
|
+
|
|
4798
|
+
const VALIDATION_EXECUTION_DEFAULTS = new InjectionToken('VALIDATION_EXECUTION_DEFAULTS', {
|
|
4799
|
+
providedIn: 'root',
|
|
4800
|
+
factory: () => ({
|
|
4801
|
+
validateWhenHidden: false,
|
|
4802
|
+
}),
|
|
4803
|
+
});
|
|
4804
|
+
|
|
4688
4805
|
function withValidationExecutionDefaults(config) {
|
|
4689
4806
|
const resolved = {
|
|
4690
4807
|
validateWhenHidden: config?.validateWhenHidden ?? false,
|
|
@@ -4705,10 +4822,10 @@ class TextAddonComponent {
|
|
|
4705
4822
|
...(ngDevMode ? [{ debugName: "addon" }] : /* istanbul ignore next */ []));
|
|
4706
4823
|
fieldInputs = input(/* @ts-ignore */
|
|
4707
4824
|
...(ngDevMode ? [undefined, { debugName: "fieldInputs" }] : /* istanbul ignore next */ []));
|
|
4708
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.
|
|
4709
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.
|
|
4825
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: TextAddonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4826
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.5", type: TextAddonComponent, isStandalone: true, selector: "df-text-addon", inputs: { addon: { classPropertyName: "addon", publicName: "addon", isSignal: true, isRequired: true, transformFunction: null }, fieldInputs: { classPropertyName: "fieldInputs", publicName: "fieldInputs", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.aria-hidden": "\"true\"" } }, ngImport: i0, template: `<span>{{ addon().text | dynamicText | async }}</span>`, isInline: true, dependencies: [{ kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "pipe", type: DynamicTextPipe, name: "dynamicText" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4710
4827
|
}
|
|
4711
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.
|
|
4828
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: TextAddonComponent, decorators: [{
|
|
4712
4829
|
type: Component,
|
|
4713
4830
|
args: [{
|
|
4714
4831
|
selector: 'df-text-addon',
|
|
@@ -4748,14 +4865,14 @@ class TemplateAddonComponent {
|
|
|
4748
4865
|
}
|
|
4749
4866
|
});
|
|
4750
4867
|
}
|
|
4751
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.
|
|
4752
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.
|
|
4868
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: TemplateAddonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4869
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.5", type: TemplateAddonComponent, isStandalone: true, selector: "df-template-addon", inputs: { addon: { classPropertyName: "addon", publicName: "addon", isSignal: true, isRequired: true, transformFunction: null }, fieldInputs: { classPropertyName: "fieldInputs", publicName: "fieldInputs", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
|
|
4753
4870
|
@if (template(); as tpl) {
|
|
4754
4871
|
<ng-container *ngTemplateOutlet="tpl; context: outletContext()" />
|
|
4755
4872
|
}
|
|
4756
4873
|
`, isInline: true, dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4757
4874
|
}
|
|
4758
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.
|
|
4875
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: TemplateAddonComponent, decorators: [{
|
|
4759
4876
|
type: Component,
|
|
4760
4877
|
args: [{
|
|
4761
4878
|
selector: 'df-template-addon',
|
|
@@ -4803,14 +4920,14 @@ class ComponentAddonComponent {
|
|
|
4803
4920
|
});
|
|
4804
4921
|
});
|
|
4805
4922
|
}
|
|
4806
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.
|
|
4807
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.
|
|
4923
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: ComponentAddonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4924
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.5", type: ComponentAddonComponent, isStandalone: true, selector: "df-component-addon", inputs: { addon: { classPropertyName: "addon", publicName: "addon", isSignal: true, isRequired: true, transformFunction: null }, fieldInputs: { classPropertyName: "fieldInputs", publicName: "fieldInputs", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
|
|
4808
4925
|
@if (resolvedComponent(); as cmp) {
|
|
4809
4926
|
<ng-container *ngComponentOutlet="cmp; inputs: inputs()" />
|
|
4810
4927
|
}
|
|
4811
4928
|
`, isInline: true, dependencies: [{ kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4812
4929
|
}
|
|
4813
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.
|
|
4930
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: ComponentAddonComponent, decorators: [{
|
|
4814
4931
|
type: Component,
|
|
4815
4932
|
args: [{
|
|
4816
4933
|
selector: 'df-component-addon',
|
|
@@ -4875,6 +4992,128 @@ function sanitizeFormConfigPure(config, fieldRegistry, typeRegistry, options = {
|
|
|
4875
4992
|
return { sanitized: { ...config, fields: fields }, warnings };
|
|
4876
4993
|
}
|
|
4877
4994
|
|
|
4995
|
+
class ActivePageInitializedEvent {
|
|
4996
|
+
pageIndex;
|
|
4997
|
+
pageKey;
|
|
4998
|
+
type = 'active-page-initialized';
|
|
4999
|
+
constructor(pageIndex, pageKey) {
|
|
5000
|
+
this.pageIndex = pageIndex;
|
|
5001
|
+
this.pageKey = pageKey;
|
|
5002
|
+
}
|
|
5003
|
+
}
|
|
5004
|
+
|
|
5005
|
+
class GoToPageEvent {
|
|
5006
|
+
pageIndex;
|
|
5007
|
+
options;
|
|
5008
|
+
type = 'go-to-page';
|
|
5009
|
+
constructor(
|
|
5010
|
+
pageIndex,
|
|
5011
|
+
options) {
|
|
5012
|
+
this.pageIndex = pageIndex;
|
|
5013
|
+
this.options = options;
|
|
5014
|
+
}
|
|
5015
|
+
}
|
|
5016
|
+
|
|
5017
|
+
class NextPageEvent {
|
|
5018
|
+
type = 'next-page';
|
|
5019
|
+
}
|
|
5020
|
+
|
|
5021
|
+
class PreviousPageEvent {
|
|
5022
|
+
type = 'previous-page';
|
|
5023
|
+
}
|
|
5024
|
+
|
|
5025
|
+
class FormResetEvent {
|
|
5026
|
+
type = 'form-reset';
|
|
5027
|
+
}
|
|
5028
|
+
|
|
5029
|
+
class FormClearEvent {
|
|
5030
|
+
type = 'form-clear';
|
|
5031
|
+
}
|
|
5032
|
+
|
|
5033
|
+
class AppendArrayItemEvent {
|
|
5034
|
+
arrayKey;
|
|
5035
|
+
template;
|
|
5036
|
+
type = 'append-array-item';
|
|
5037
|
+
constructor(
|
|
5038
|
+
arrayKey,
|
|
5039
|
+
template) {
|
|
5040
|
+
this.arrayKey = arrayKey;
|
|
5041
|
+
this.template = template;
|
|
5042
|
+
}
|
|
5043
|
+
}
|
|
5044
|
+
|
|
5045
|
+
class PrependArrayItemEvent {
|
|
5046
|
+
arrayKey;
|
|
5047
|
+
template;
|
|
5048
|
+
type = 'prepend-array-item';
|
|
5049
|
+
constructor(
|
|
5050
|
+
arrayKey,
|
|
5051
|
+
template) {
|
|
5052
|
+
this.arrayKey = arrayKey;
|
|
5053
|
+
this.template = template;
|
|
5054
|
+
}
|
|
5055
|
+
}
|
|
5056
|
+
|
|
5057
|
+
class InsertArrayItemEvent {
|
|
5058
|
+
arrayKey;
|
|
5059
|
+
index;
|
|
5060
|
+
template;
|
|
5061
|
+
type = 'insert-array-item';
|
|
5062
|
+
constructor(
|
|
5063
|
+
arrayKey,
|
|
5064
|
+
index,
|
|
5065
|
+
template) {
|
|
5066
|
+
this.arrayKey = arrayKey;
|
|
5067
|
+
this.index = index;
|
|
5068
|
+
this.template = template;
|
|
5069
|
+
}
|
|
5070
|
+
}
|
|
5071
|
+
|
|
5072
|
+
class PopArrayItemEvent {
|
|
5073
|
+
arrayKey;
|
|
5074
|
+
type = 'pop-array-item';
|
|
5075
|
+
constructor(
|
|
5076
|
+
arrayKey) {
|
|
5077
|
+
this.arrayKey = arrayKey;
|
|
5078
|
+
}
|
|
5079
|
+
}
|
|
5080
|
+
|
|
5081
|
+
class ShiftArrayItemEvent {
|
|
5082
|
+
arrayKey;
|
|
5083
|
+
type = 'shift-array-item';
|
|
5084
|
+
constructor(
|
|
5085
|
+
arrayKey) {
|
|
5086
|
+
this.arrayKey = arrayKey;
|
|
5087
|
+
}
|
|
5088
|
+
}
|
|
5089
|
+
|
|
5090
|
+
class MoveArrayItemEvent {
|
|
5091
|
+
arrayKey;
|
|
5092
|
+
fromIndex;
|
|
5093
|
+
toIndex;
|
|
5094
|
+
type = 'move-array-item';
|
|
5095
|
+
constructor(
|
|
5096
|
+
arrayKey,
|
|
5097
|
+
fromIndex,
|
|
5098
|
+
toIndex) {
|
|
5099
|
+
this.arrayKey = arrayKey;
|
|
5100
|
+
this.fromIndex = fromIndex;
|
|
5101
|
+
this.toIndex = toIndex;
|
|
5102
|
+
}
|
|
5103
|
+
}
|
|
5104
|
+
|
|
5105
|
+
class RemoveAtIndexEvent {
|
|
5106
|
+
arrayKey;
|
|
5107
|
+
index;
|
|
5108
|
+
type = 'remove-at-index';
|
|
5109
|
+
constructor(
|
|
5110
|
+
arrayKey,
|
|
5111
|
+
index) {
|
|
5112
|
+
this.arrayKey = arrayKey;
|
|
5113
|
+
this.index = index;
|
|
5114
|
+
}
|
|
5115
|
+
}
|
|
5116
|
+
|
|
4878
5117
|
function arrayEvent(arrayKey) {
|
|
4879
5118
|
return {
|
|
4880
5119
|
append: (template) => new AppendArrayItemEvent(arrayKey, template),
|
|
@@ -4890,5 +5129,5 @@ function arrayEvent(arrayKey) {
|
|
|
4890
5129
|
|
|
4891
5130
|
|
|
4892
5131
|
|
|
4893
|
-
export {
|
|
4894
|
-
//# sourceMappingURL=ng-forge-dynamic-forms-ng-forge-dynamic-forms-
|
|
5132
|
+
export { arrayEvent as $, ArrayItemRegistryService as A, BUILT_IN_FIELDS as B, CONTAINER_FIELD_PROCESSORS as C, DfFieldOutlet as D, ConsoleLogger as E, FIELD_WINDOWING as F, DfTemplate as G, DynamicForm as H, EventDispatcher as I, FormClearEvent as J, FormResetEvent 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, withExperimentalWebMcp as a7, withFieldWindowing as a8, withLegacyStatusClasses as a9, withLoggerConfig as aa, withPagePreload as ab, withValidationExecutionDefaults as ac, withValueExclusionDefaults as ad, wrapperProps as ae, 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, FormSubmitEvent as x, AppendArrayItemEvent as y, ComponentAddonComponent as z };
|
|
5133
|
+
//# sourceMappingURL=ng-forge-dynamic-forms-ng-forge-dynamic-forms-CXQe_5td.mjs.map
|