@ng-forge/dynamic-forms 1.2.0-next.5 → 1.2.0-next.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/{ng-forge-dynamic-forms-array-field.component-D-VWBM-t.mjs → ng-forge-dynamic-forms-array-field.component-DK7dAQv7.mjs} +6 -10
- package/fesm2022/ng-forge-dynamic-forms-array-field.component-DK7dAQv7.mjs.map +1 -0
- package/fesm2022/{ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-OBDy6qd9.mjs → ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-BfxYqAQA.mjs} +29 -7
- package/fesm2022/ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-BfxYqAQA.mjs.map +1 -0
- package/fesm2022/{ng-forge-dynamic-forms-container-field.component-BpeFyhAt.mjs → ng-forge-dynamic-forms-container-field.component-Cj2msZp_.mjs} +9 -12
- package/fesm2022/ng-forge-dynamic-forms-container-field.component-Cj2msZp_.mjs.map +1 -0
- package/fesm2022/ng-forge-dynamic-forms-debounce-C9F4v1Ke.mjs +17 -0
- package/fesm2022/ng-forge-dynamic-forms-debounce-C9F4v1Ke.mjs.map +1 -0
- package/fesm2022/ng-forge-dynamic-forms-field-errors-wrapper.component-ZdaC4Bit.mjs +25 -0
- package/fesm2022/ng-forge-dynamic-forms-field-errors-wrapper.component-ZdaC4Bit.mjs.map +1 -0
- package/fesm2022/ng-forge-dynamic-forms-form-schema.service-C3cVSmft.mjs +24 -0
- package/fesm2022/ng-forge-dynamic-forms-form-schema.service-C3cVSmft.mjs.map +1 -0
- package/fesm2022/{ng-forge-dynamic-forms-group-field.component-BNjYMTqT.mjs → ng-forge-dynamic-forms-group-field.component-DN6X506r.mjs} +6 -10
- package/fesm2022/ng-forge-dynamic-forms-group-field.component-DN6X506r.mjs.map +1 -0
- package/fesm2022/ng-forge-dynamic-forms-integration.mjs +10 -72
- package/fesm2022/ng-forge-dynamic-forms-integration.mjs.map +1 -1
- package/fesm2022/ng-forge-dynamic-forms-internal.mjs +252 -95
- package/fesm2022/ng-forge-dynamic-forms-internal.mjs.map +1 -1
- package/fesm2022/{ng-forge-dynamic-forms-ng-forge-dynamic-forms-DSXZJHcu.mjs → ng-forge-dynamic-forms-ng-forge-dynamic-forms-DCX7tvaB.mjs} +1089 -1288
- package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-DCX7tvaB.mjs.map +1 -0
- package/fesm2022/{ng-forge-dynamic-forms-page-field.component-B92kLZkH.mjs → ng-forge-dynamic-forms-page-field.component-D16ey8r-.mjs} +54 -13
- package/fesm2022/ng-forge-dynamic-forms-page-field.component-D16ey8r-.mjs.map +1 -0
- package/fesm2022/ng-forge-dynamic-forms-schema-builder-DO-su9sF.mjs +570 -0
- package/fesm2022/ng-forge-dynamic-forms-schema-builder-DO-su9sF.mjs.map +1 -0
- package/fesm2022/ng-forge-dynamic-forms.mjs +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 +429 -222
- package/types/ng-forge-dynamic-forms.d.ts +33 -17
- package/fesm2022/ng-forge-dynamic-forms-array-field.component-D-VWBM-t.mjs.map +0 -1
- package/fesm2022/ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-OBDy6qd9.mjs.map +0 -1
- package/fesm2022/ng-forge-dynamic-forms-container-field.component-BpeFyhAt.mjs.map +0 -1
- package/fesm2022/ng-forge-dynamic-forms-container-utils-BZFz6ztF.mjs +0 -24
- package/fesm2022/ng-forge-dynamic-forms-container-utils-BZFz6ztF.mjs.map +0 -1
- package/fesm2022/ng-forge-dynamic-forms-emit-initialization-DhHwU6FJ.mjs +0 -17
- package/fesm2022/ng-forge-dynamic-forms-emit-initialization-DhHwU6FJ.mjs.map +0 -1
- package/fesm2022/ng-forge-dynamic-forms-group-field.component-BNjYMTqT.mjs.map +0 -1
- package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-DSXZJHcu.mjs.map +0 -1
- package/fesm2022/ng-forge-dynamic-forms-page-field.component-B92kLZkH.mjs.map +0 -1
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { reflectComponentType, Injector, inject, EnvironmentInjector, DestroyRef, computed, signal, input, ViewContainerRef, Directive, InjectionToken,
|
|
2
|
+
import { reflectComponentType, Injector, inject, EnvironmentInjector, DestroyRef, computed, signal, afterNextRender, input, ViewContainerRef, Directive, InjectionToken, Injectable, isSignal, runInInjectionContext, untracked, linkedSignal, injectAsync, resource, ChangeDetectionStrategy, Component, PendingTasks, ElementRef, PLATFORM_ID, isDevMode, TemplateRef, model, contentChildren, makeEnvironmentProviders } from '@angular/core';
|
|
3
3
|
import { explicitEffect } from 'ngxtension/explicit-effect';
|
|
4
|
-
import { DynamicFormLogger, WRAPPER_COMPONENT_CACHE, WRAPPER_REGISTRY, toReadonlyFieldTreeCached, writeToFieldValue,
|
|
5
|
-
import { takeUntilDestroyed, toObservable,
|
|
6
|
-
import { of, forkJoin, from, catchError, map, switchMap, isObservable, firstValueFrom, exhaustMap, EMPTY,
|
|
7
|
-
import { submit,
|
|
8
|
-
import { isStandardSchemaMarker } from '@ng-forge/dynamic-forms/schema';
|
|
4
|
+
import { DynamicFormLogger, 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, GROUP_CONTEXT, isContainerField, isSimplifiedArrayField, DynamicFormError, getFieldValueHandling, keyBy, mapValues, isEqual, isDerivationLogicConfig, hasTargetProperty, ARRAY_CONTEXT, FORM_ID_PREFIX, baseFieldMapper, detectFormMode, validatePageNesting, FIELD_REGISTRY, assertNever, FunctionRegistryService, ADDON_TYPE_REGISTRY, deepMergeDefaults, FIELD_SIGNAL_CONTEXT, derivedFromDeferred, FieldContextRegistryService, FORM_OPTIONS, 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';
|
|
5
|
+
import { takeUntilDestroyed, toObservable, outputFromObservable } from '@angular/core/rxjs-interop';
|
|
6
|
+
import { of, forkJoin, from, catchError, map, switchMap, isObservable, firstValueFrom, exhaustMap, EMPTY, pipe, scan, Observable, Subject, concatMap, filter, take, shareReplay } from 'rxjs';
|
|
7
|
+
import { submit, form, provideSignalFormsConfig } from '@angular/forms/signals';
|
|
9
8
|
import { isPlatformBrowser, AsyncPipe, NgTemplateOutlet, NgComponentOutlet } from '@angular/common';
|
|
10
9
|
import { NG_STATUS_CLASSES } from '@angular/forms/signals/compat';
|
|
11
10
|
|
|
12
11
|
const EMPTY_WRAPPERS = Object.freeze([]);
|
|
12
|
+
const FIELD_ERRORS_WRAPPER = 'field-errors';
|
|
13
|
+
const FIELD_ERRORS_CONFIG = Object.freeze({ type: FIELD_ERRORS_WRAPPER });
|
|
13
14
|
function isSameWrapperChain(a, b) {
|
|
14
15
|
if (a === b)
|
|
15
16
|
return true;
|
|
@@ -21,19 +22,30 @@ function isSameWrapperChain(a, b) {
|
|
|
21
22
|
}
|
|
22
23
|
return true;
|
|
23
24
|
}
|
|
24
|
-
function resolveWrappers(field, defaultWrappers, autoAssociations) {
|
|
25
|
-
|
|
25
|
+
function resolveWrappers(field, defaultWrappers, autoAssociations, wrapperRegistry) {
|
|
26
|
+
if (field.wrappers === null) {
|
|
26
27
|
return EMPTY_WRAPPERS;
|
|
27
28
|
}
|
|
28
29
|
const autoWrappers = field.skipAutoWrappers ? EMPTY_WRAPPERS : (autoAssociations.get(field.type) ?? EMPTY_WRAPPERS);
|
|
29
30
|
const defaults = field.skipDefaultWrappers ? EMPTY_WRAPPERS : (defaultWrappers ?? EMPTY_WRAPPERS);
|
|
30
31
|
const fieldLevel = field.wrappers ?? EMPTY_WRAPPERS;
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
const composed = [...autoWrappers, ...defaults, ...fieldLevel];
|
|
33
|
+
if (needsErrorWrapper(field, composed, wrapperRegistry)) {
|
|
34
|
+
composed.push(FIELD_ERRORS_CONFIG);
|
|
33
35
|
}
|
|
34
|
-
return
|
|
36
|
+
return composed.length === 0 ? EMPTY_WRAPPERS : composed;
|
|
37
|
+
}
|
|
38
|
+
const VALIDATABLE_CONTAINERS = new Set(['group', 'array']);
|
|
39
|
+
function needsErrorWrapper(field, composed, wrapperRegistry) {
|
|
40
|
+
if (!VALIDATABLE_CONTAINERS.has(field.type))
|
|
41
|
+
return false;
|
|
42
|
+
if (!field.validators || field.validators.length === 0)
|
|
43
|
+
return false;
|
|
44
|
+
return !composed.some((w) => w.type === FIELD_ERRORS_WRAPPER || wrapperRegistry.get(w.type)?.rendersFieldErrors);
|
|
35
45
|
}
|
|
36
46
|
|
|
47
|
+
const DEV_MODE = typeof ngDevMode === 'undefined' || !!ngDevMode;
|
|
48
|
+
|
|
37
49
|
const inputNamesCache = new WeakMap();
|
|
38
50
|
function getDeclaredInputs(componentType) {
|
|
39
51
|
let inputs = inputNamesCache.get(componentType);
|
|
@@ -72,7 +84,9 @@ function loadWrapperComponents(configs, registry, cache, logger) {
|
|
|
72
84
|
return of([]);
|
|
73
85
|
return forkJoin(configs.map((config) => from(loadWrapperComponent(config.type, registry, cache)).pipe(catchError(() => of(undefined)), map((component) => {
|
|
74
86
|
if (!component) {
|
|
75
|
-
|
|
87
|
+
if (DEV_MODE) {
|
|
88
|
+
logger.error(`Wrapper type '${config.type}' could not be loaded. Ensure it is registered via provideDynamicForm().`);
|
|
89
|
+
}
|
|
76
90
|
return null;
|
|
77
91
|
}
|
|
78
92
|
return { config, component };
|
|
@@ -112,9 +126,11 @@ function renderStep(slot, remaining, options, refs) {
|
|
|
112
126
|
ref.changeDetectorRef.detectChanges();
|
|
113
127
|
const inner = resolveInnerSlot(ref);
|
|
114
128
|
if (!inner) {
|
|
115
|
-
|
|
116
|
-
`
|
|
117
|
-
|
|
129
|
+
if (DEV_MODE) {
|
|
130
|
+
options.logger.error(`Wrapper component for type '${wrapper.config.type}' does not provide a 'fieldComponent' ViewContainerRef. ` +
|
|
131
|
+
`Ensure the wrapper component has a viewChild('fieldComponent', { read: ViewContainerRef }) query ` +
|
|
132
|
+
`and that #fieldComponent is not inside a conditional (@if, @defer).`);
|
|
133
|
+
}
|
|
118
134
|
while (refs.length)
|
|
119
135
|
refs.pop().destroy();
|
|
120
136
|
return;
|
|
@@ -156,22 +172,28 @@ function createWrapperChainController(opts) {
|
|
|
156
172
|
const deps = injectChainDeps();
|
|
157
173
|
const state = createStateSignal(opts);
|
|
158
174
|
const mounted = { value: null };
|
|
175
|
+
const detached = { value: null };
|
|
159
176
|
let refs = [];
|
|
160
177
|
buildEmissionStream(state, deps)
|
|
161
178
|
.pipe(takeUntilDestroyed(deps.destroyRef))
|
|
162
179
|
.subscribe((emission) => {
|
|
163
180
|
try {
|
|
164
|
-
refs = applyEmission(emission, { opts, deps, mounted, refs });
|
|
181
|
+
refs = applyEmission(emission, { opts, deps, mounted, refs, detached });
|
|
165
182
|
}
|
|
166
183
|
catch (err) {
|
|
167
184
|
deps.logger.error('Wrapper chain render failed; tearing down partial state.', err);
|
|
168
185
|
opts.vcr().clear();
|
|
169
186
|
refs = [];
|
|
170
187
|
mounted.value = null;
|
|
188
|
+
detached.value?.destroy();
|
|
189
|
+
detached.value = null;
|
|
171
190
|
}
|
|
172
191
|
});
|
|
173
192
|
pushFieldInputsOnChange(opts, () => refs);
|
|
174
|
-
deps.destroyRef.onDestroy(() =>
|
|
193
|
+
deps.destroyRef.onDestroy(() => {
|
|
194
|
+
detached.value?.destroy();
|
|
195
|
+
opts.vcr().clear();
|
|
196
|
+
});
|
|
175
197
|
}
|
|
176
198
|
function injectChainDeps() {
|
|
177
199
|
return {
|
|
@@ -206,10 +228,21 @@ function applyEmission({ state, loaded }, ctx) {
|
|
|
206
228
|
const { opts, deps, mounted, refs } = ctx;
|
|
207
229
|
const vcr = opts.vcr();
|
|
208
230
|
const structurallyChanged = isStructurallyDifferent(mounted.value, state);
|
|
209
|
-
if (!structurallyChanged && !state.open)
|
|
231
|
+
if (!structurallyChanged && !state.open) {
|
|
232
|
+
if (vcr.length > 0)
|
|
233
|
+
ctx.detached.value = vcr.detach(0) ?? ctx.detached.value;
|
|
234
|
+
return refs;
|
|
235
|
+
}
|
|
236
|
+
if (state.open && !structurallyChanged && mounted.value !== null && ctx.detached.value) {
|
|
237
|
+
vcr.insert(ctx.detached.value);
|
|
238
|
+
ctx.detached.value = null;
|
|
210
239
|
return refs;
|
|
240
|
+
}
|
|
211
241
|
if (structurallyChanged && mounted.value !== null) {
|
|
212
|
-
|
|
242
|
+
const preservesDetachedOuterView = opts.beforeRebuild?.() === true;
|
|
243
|
+
if (ctx.detached.value && !preservesDetachedOuterView)
|
|
244
|
+
ctx.detached.value.destroy();
|
|
245
|
+
ctx.detached.value = null;
|
|
213
246
|
vcr.clear();
|
|
214
247
|
mounted.value = null;
|
|
215
248
|
}
|
|
@@ -246,7 +279,7 @@ function pushFieldInputsOnChange(opts, getRefs) {
|
|
|
246
279
|
});
|
|
247
280
|
}
|
|
248
281
|
|
|
249
|
-
function buildFieldInputs(rawInputs, cache, fieldType) {
|
|
282
|
+
function buildFieldInputs(rawInputs, cache, fieldType, validationMessages) {
|
|
250
283
|
const fieldTreeCandidate = rawInputs['field'];
|
|
251
284
|
const hasFieldTree = fieldTreeCandidate !== undefined && typeof fieldTreeCandidate === 'function';
|
|
252
285
|
const readonlyField = hasFieldTree ? toReadonlyFieldTreeCached(cache, fieldTreeCandidate) : undefined;
|
|
@@ -258,6 +291,7 @@ function buildFieldInputs(rawInputs, cache, fieldType) {
|
|
|
258
291
|
type: fieldType,
|
|
259
292
|
field: readonlyField,
|
|
260
293
|
setValue,
|
|
294
|
+
...(validationMessages !== undefined && { validationMessages }),
|
|
261
295
|
};
|
|
262
296
|
}
|
|
263
297
|
|
|
@@ -310,13 +344,14 @@ class FieldComponentSlot {
|
|
|
310
344
|
detach() {
|
|
311
345
|
const current = this.state();
|
|
312
346
|
if (current.phase !== 'mounted')
|
|
313
|
-
return;
|
|
347
|
+
return false;
|
|
314
348
|
const focusSnapshot = this.captureFocus(current.ref);
|
|
315
349
|
const idx = current.slot.indexOf(current.ref.hostView);
|
|
316
350
|
if (idx >= 0)
|
|
317
351
|
current.slot.detach(idx);
|
|
318
352
|
this.lastInputs = null;
|
|
319
353
|
this.state.set(Object.freeze({ phase: 'detached', ref: current.ref, focusSnapshot }));
|
|
354
|
+
return idx < 0;
|
|
320
355
|
}
|
|
321
356
|
destroyOnTeardown() {
|
|
322
357
|
const current = this.state();
|
|
@@ -347,6 +382,74 @@ class FieldComponentSlot {
|
|
|
347
382
|
}
|
|
348
383
|
}
|
|
349
384
|
|
|
385
|
+
function emitComponentInitialized(eventBus, componentType, componentKey, injector) {
|
|
386
|
+
const logger = injector.get(DynamicFormLogger);
|
|
387
|
+
afterNextRender(() => {
|
|
388
|
+
try {
|
|
389
|
+
eventBus.dispatch(ComponentInitializedEvent, componentType, componentKey);
|
|
390
|
+
}
|
|
391
|
+
catch (error) {
|
|
392
|
+
logger.error(`Failed to emit initialization event for ${componentType} '${componentKey}'`, error);
|
|
393
|
+
}
|
|
394
|
+
}, { injector });
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function initializationComponentKey(type, key) {
|
|
398
|
+
return `${type}:${key}`;
|
|
399
|
+
}
|
|
400
|
+
function initializationComponentPath(key, groupPath) {
|
|
401
|
+
return groupPath ? `${groupPath}.${key}` : key;
|
|
402
|
+
}
|
|
403
|
+
function initializationComponentType(field) {
|
|
404
|
+
if (isPageField(field))
|
|
405
|
+
return 'page';
|
|
406
|
+
if (isGroupField(field))
|
|
407
|
+
return 'group';
|
|
408
|
+
if (isArrayField(field))
|
|
409
|
+
return 'array';
|
|
410
|
+
if (isRowField(field) || isGenericContainerField(field))
|
|
411
|
+
return 'container';
|
|
412
|
+
return undefined;
|
|
413
|
+
}
|
|
414
|
+
function collectInitializingContainers(fields, groupPath = '') {
|
|
415
|
+
const identities = [];
|
|
416
|
+
for (const field of fields) {
|
|
417
|
+
if (field.hidden === true)
|
|
418
|
+
continue;
|
|
419
|
+
const type = initializationComponentType(field);
|
|
420
|
+
const componentPath = initializationComponentPath(field.key, groupPath);
|
|
421
|
+
if (type && field.key)
|
|
422
|
+
identities.push({ type, path: componentPath });
|
|
423
|
+
if (!hasChildFields(field))
|
|
424
|
+
continue;
|
|
425
|
+
const childGroupPath = isGroupField(field) ? componentPath : groupPath;
|
|
426
|
+
for (const child of normalizeFieldsArray(field.fields)) {
|
|
427
|
+
identities.push(...collectInitializingContainers((Array.isArray(child) ? child : [child]), childGroupPath));
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
return identities;
|
|
431
|
+
}
|
|
432
|
+
function collectInitializingContainerKeys(fields, groupPath = '') {
|
|
433
|
+
return collectInitializingContainers(fields, groupPath).map(({ type, path }) => initializationComponentKey(type, path));
|
|
434
|
+
}
|
|
435
|
+
function computeContainerHostClasses(containerType, className) {
|
|
436
|
+
const base = `df-field df-${containerType}`;
|
|
437
|
+
return className ? `${base} ${className}` : base;
|
|
438
|
+
}
|
|
439
|
+
function setupContainerInitEffect(resolvedFields, eventBus, componentType, fieldKeyFn, injector) {
|
|
440
|
+
const allReady = computed(() => {
|
|
441
|
+
const fields = resolvedFields();
|
|
442
|
+
return fields.length > 0 && fields.every((field) => field.renderReady());
|
|
443
|
+
}, /* @ts-ignore */
|
|
444
|
+
...(ngDevMode ? [{ debugName: "allReady" }] : /* istanbul ignore next */ []));
|
|
445
|
+
explicitEffect([allReady], ([ready]) => {
|
|
446
|
+
if (ready) {
|
|
447
|
+
const type = typeof componentType === 'function' ? componentType() : componentType;
|
|
448
|
+
emitComponentInitialized(eventBus, type, fieldKeyFn(), injector);
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
|
|
350
453
|
class DfFieldOutlet {
|
|
351
454
|
dfFieldOutlet = input.required(/* @ts-ignore */
|
|
352
455
|
...(ngDevMode ? [{ debugName: "dfFieldOutlet" }] : /* istanbul ignore next */ []));
|
|
@@ -356,17 +459,22 @@ class DfFieldOutlet {
|
|
|
356
459
|
vcr = signal(this.vcrRef).asReadonly();
|
|
357
460
|
destroyRef = inject(DestroyRef);
|
|
358
461
|
wrapperAutoAssociations = inject(WRAPPER_AUTO_ASSOCIATIONS);
|
|
462
|
+
wrapperRegistry = inject(WRAPPER_REGISTRY);
|
|
359
463
|
defaultWrappersSignal = inject(DEFAULT_WRAPPERS, { optional: true });
|
|
360
464
|
readonlyFieldCache = inject(READONLY_FIELD_TREE_CACHE);
|
|
465
|
+
eventBus = inject(EventBus, { optional: true });
|
|
466
|
+
injector = inject(Injector);
|
|
361
467
|
fieldComponent = new FieldComponentSlot();
|
|
362
468
|
componentIdentity = computed(() => this.dfFieldOutlet().component, /* @ts-ignore */
|
|
363
469
|
...(ngDevMode ? [{ debugName: "componentIdentity" }] : /* istanbul ignore next */ []));
|
|
364
470
|
renderReady = computed(() => this.dfFieldOutlet().renderReady() && !this.dfFieldOutlet().hidden(), /* @ts-ignore */
|
|
365
471
|
...(ngDevMode ? [{ debugName: "renderReady" }] : /* istanbul ignore next */ []));
|
|
472
|
+
hidden = computed(() => this.dfFieldOutlet().hidden(), /* @ts-ignore */
|
|
473
|
+
...(ngDevMode ? [{ debugName: "hidden" }] : /* istanbul ignore next */ []));
|
|
366
474
|
rawInputs = computed(() => this.dfFieldOutlet().inputs(), /* @ts-ignore */
|
|
367
475
|
...(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 */
|
|
476
|
+
wrappers = computed(() => resolveWrappers(this.dfFieldOutlet().fieldDef, this.defaultWrappersSignal?.(), this.wrapperAutoAssociations, this.wrapperRegistry), { ...(ngDevMode ? { debugName: "wrappers" } : /* istanbul ignore next */ {}), equal: isSameWrapperChain });
|
|
477
|
+
fieldInputs = computed(() => buildFieldInputs(this.rawInputs(), this.readonlyFieldCache, this.dfFieldOutlet().fieldDef.type, this.dfFieldOutlet().fieldDef.validationMessages), /* @ts-ignore */
|
|
370
478
|
...(ngDevMode ? [{ debugName: "fieldInputs" }] : /* istanbul ignore next */ []));
|
|
371
479
|
defaultEnvInjector = inject(EnvironmentInjector);
|
|
372
480
|
fieldEnvInjector = computed(() => this.dfFieldOutletEnvironmentInjector() ?? this.defaultEnvInjector, /* @ts-ignore */
|
|
@@ -391,6 +499,15 @@ class DfFieldOutlet {
|
|
|
391
499
|
},
|
|
392
500
|
});
|
|
393
501
|
explicitEffect([this.rawInputs, this.fieldInputs], ([rawInputs, fieldInputs]) => this.fieldComponent.pushInputs(rawInputs, fieldInputs));
|
|
502
|
+
explicitEffect([this.hidden], ([hidden]) => {
|
|
503
|
+
if (!hidden || !this.eventBus)
|
|
504
|
+
return;
|
|
505
|
+
const resolved = this.dfFieldOutlet();
|
|
506
|
+
const groupContext = resolved.injector.get(GROUP_CONTEXT, null);
|
|
507
|
+
for (const { type, path } of collectInitializingContainers([resolved.fieldDef], groupContext?.groupPath())) {
|
|
508
|
+
emitComponentInitialized(this.eventBus, type, path, this.injector);
|
|
509
|
+
}
|
|
510
|
+
});
|
|
394
511
|
this.destroyRef.onDestroy(() => this.fieldComponent.destroyOnTeardown());
|
|
395
512
|
}
|
|
396
513
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: DfFieldOutlet, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
@@ -471,462 +588,72 @@ function clampWindowSize(value, fallback) {
|
|
|
471
588
|
return Math.max(0, Math.floor(value));
|
|
472
589
|
}
|
|
473
590
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
globalPreloadWindow = inject(PAGE_PRELOAD_WINDOW);
|
|
480
|
-
pageFields = input.required(/* @ts-ignore */
|
|
481
|
-
...(ngDevMode ? [{ debugName: "pageFields" }] : /* istanbul ignore next */ []));
|
|
482
|
-
form = input.required(/* @ts-ignore */
|
|
483
|
-
...(ngDevMode ? [{ debugName: "form" }] : /* istanbul ignore next */ []));
|
|
484
|
-
fieldSignalContext = input.required(/* @ts-ignore */
|
|
485
|
-
...(ngDevMode ? [{ debugName: "fieldSignalContext" }] : /* istanbul ignore next */ []));
|
|
486
|
-
pageHiddenStates = computed(() => {
|
|
487
|
-
const pages = this.pageFields();
|
|
488
|
-
return pages.map((page) => this.evaluatePageHidden(page));
|
|
489
|
-
}, /* @ts-ignore */
|
|
490
|
-
...(ngDevMode ? [{ debugName: "pageHiddenStates" }] : /* istanbul ignore next */ []));
|
|
491
|
-
visiblePageIndices = computed(() => {
|
|
492
|
-
const hiddenStates = this.pageHiddenStates();
|
|
493
|
-
return hiddenStates
|
|
494
|
-
.map((hidden, index) => ({ index, hidden }))
|
|
495
|
-
.filter((item) => !item.hidden)
|
|
496
|
-
.map((item) => item.index);
|
|
497
|
-
}, /* @ts-ignore */
|
|
498
|
-
...(ngDevMode ? [{ debugName: "visiblePageIndices" }] : /* istanbul ignore next */ []));
|
|
499
|
-
currentPageIndex = linkedSignal(() => {
|
|
500
|
-
const totalPages = this.pageFields().length;
|
|
501
|
-
if (totalPages === 0)
|
|
502
|
-
return 0;
|
|
503
|
-
return untracked(() => this.resolveInitialLanding());
|
|
504
|
-
}, /* @ts-ignore */
|
|
505
|
-
...(ngDevMode ? [{ debugName: "currentPageIndex" }] : /* istanbul ignore next */ []));
|
|
506
|
-
resolveInitialLanding() {
|
|
507
|
-
const { index, validate } = this.initialPage();
|
|
508
|
-
const visible = this.visiblePageIndices();
|
|
509
|
-
if (index === 0 || visible.length === 0)
|
|
510
|
-
return 0;
|
|
511
|
-
const target = visible.includes(index) ? index : this.findNearestVisiblePage(index, visible);
|
|
512
|
-
if (target <= 0)
|
|
513
|
-
return Math.max(target, 0);
|
|
514
|
-
if (!validate)
|
|
515
|
-
return target;
|
|
516
|
-
const firstInvalid = visible.filter((i) => i < target).find((i) => !this.isPageValid(i));
|
|
517
|
-
return firstInvalid ?? target;
|
|
518
|
-
}
|
|
519
|
-
initialPage = computed(() => {
|
|
520
|
-
const raw = this.formOptions?.()?.initialPage;
|
|
521
|
-
const totalPages = this.pageFields().length;
|
|
522
|
-
const requested = typeof raw === 'number' ? raw : raw?.index;
|
|
523
|
-
const validate = typeof raw === 'number' ? false : (raw?.validate ?? false);
|
|
524
|
-
if (typeof requested !== 'number' || !Number.isFinite(requested) || requested < 0 || totalPages === 0) {
|
|
525
|
-
return { index: 0, validate: false };
|
|
526
|
-
}
|
|
527
|
-
return { index: Math.min(Math.trunc(requested), totalPages - 1), validate };
|
|
528
|
-
}, /* @ts-ignore */
|
|
529
|
-
...(ngDevMode ? [{ debugName: "initialPage" }] : /* istanbul ignore next */ []));
|
|
530
|
-
state = computed(() => {
|
|
531
|
-
const currentIndex = this.currentPageIndex();
|
|
532
|
-
const totalPages = this.pageFields().length;
|
|
533
|
-
const visibleIndices = this.visiblePageIndices();
|
|
534
|
-
const currentVisiblePosition = visibleIndices.indexOf(currentIndex);
|
|
535
|
-
const isFirstVisiblePage = currentVisiblePosition === 0 || currentVisiblePosition === -1;
|
|
536
|
-
const isLastVisiblePage = currentVisiblePosition >= visibleIndices.length - 1;
|
|
537
|
-
return {
|
|
538
|
-
currentPageIndex: currentIndex,
|
|
539
|
-
totalPages,
|
|
540
|
-
isFirstPage: isFirstVisiblePage,
|
|
541
|
-
isLastPage: isLastVisiblePage,
|
|
542
|
-
};
|
|
543
|
-
}, /* @ts-ignore */
|
|
544
|
-
...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
|
|
545
|
-
currentPageValid = computed(() => this.isPageValid(this.currentPageIndex()), /* @ts-ignore */
|
|
546
|
-
...(ngDevMode ? [{ debugName: "currentPageValid" }] : /* istanbul ignore next */ []));
|
|
547
|
-
isPageValid(pageIndex) {
|
|
548
|
-
const pages = this.pageFields();
|
|
549
|
-
const form = this.form();
|
|
550
|
-
if (pageIndex < 0 || pageIndex >= pages.length) {
|
|
551
|
-
return true;
|
|
591
|
+
function collectLeafFieldKeys(fields) {
|
|
592
|
+
const keys = [];
|
|
593
|
+
for (const field of fields) {
|
|
594
|
+
if (isRowField(field) || isGenericContainerField(field)) {
|
|
595
|
+
keys.push(...collectLeafFieldKeys(field.fields));
|
|
552
596
|
}
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
const field = form[fieldKey];
|
|
556
|
-
if (field && typeof field === 'function') {
|
|
557
|
-
const fieldState = field();
|
|
558
|
-
if (fieldState && typeof fieldState.valid === 'function' && !fieldState.valid()) {
|
|
559
|
-
return false;
|
|
560
|
-
}
|
|
561
|
-
}
|
|
597
|
+
else if (field.key) {
|
|
598
|
+
keys.push(field.key);
|
|
562
599
|
}
|
|
563
|
-
return true;
|
|
564
|
-
}
|
|
565
|
-
extendedFieldSignalContext = computed(() => ({
|
|
566
|
-
...this.fieldSignalContext(),
|
|
567
|
-
currentPageValid: this.currentPageValid,
|
|
568
|
-
}), /* @ts-ignore */
|
|
569
|
-
...(ngDevMode ? [{ debugName: "extendedFieldSignalContext" }] : /* istanbul ignore next */ []));
|
|
570
|
-
preloadWindow = computed(() => {
|
|
571
|
-
const perForm = this.formOptions?.()?.pagePreloadWindow;
|
|
572
|
-
return clampWindowSize(perForm, this.globalPreloadWindow);
|
|
573
|
-
}, /* @ts-ignore */
|
|
574
|
-
...(ngDevMode ? [{ debugName: "preloadWindow" }] : /* istanbul ignore next */ []));
|
|
575
|
-
isWithinPreloadWindow(i) {
|
|
576
|
-
return Math.abs(i - this.state().currentPageIndex) <= this.preloadWindow();
|
|
577
|
-
}
|
|
578
|
-
constructor() {
|
|
579
|
-
this.setupEventListeners();
|
|
580
|
-
explicitEffect([this.state, this.visiblePageIndices], ([state, visibleIndices]) => {
|
|
581
|
-
const currentVisiblePosition = visibleIndices.indexOf(state.currentPageIndex);
|
|
582
|
-
if (currentVisiblePosition === -1 && visibleIndices.length > 0) {
|
|
583
|
-
const nearest = this.findNearestVisiblePage(state.currentPageIndex, visibleIndices);
|
|
584
|
-
if (nearest !== -1) {
|
|
585
|
-
this.executePageChange(nearest);
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
});
|
|
589
600
|
}
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
error: 'Already on the last visible page',
|
|
606
|
-
};
|
|
601
|
+
return keys;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
const NORMALIZED_ARRAY_METADATA = Symbol('normalizedArrayMetadata');
|
|
605
|
+
function setNormalizedArrayMetadata(arrayField, metadata) {
|
|
606
|
+
arrayField[NORMALIZED_ARRAY_METADATA] = metadata;
|
|
607
|
+
}
|
|
608
|
+
function getNormalizedArrayMetadata(arrayField) {
|
|
609
|
+
return arrayField[NORMALIZED_ARRAY_METADATA];
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
function normalizeSimplifiedArrays(fields) {
|
|
613
|
+
return fields.flatMap((field) => {
|
|
614
|
+
if (isContainerField(field) && !isArrayField(field) && hasChildFields(field)) {
|
|
615
|
+
return [{ ...field, fields: normalizeSimplifiedArrays(field.fields) }];
|
|
607
616
|
}
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
return {
|
|
611
|
-
success: false,
|
|
612
|
-
newPageIndex: currentState.currentPageIndex,
|
|
613
|
-
error: 'No next visible page available',
|
|
614
|
-
};
|
|
617
|
+
if (isArrayField(field) && !isSimplifiedArrayField(field)) {
|
|
618
|
+
const normalizedItems = field.fields.map((item) => Array.isArray(item) ? normalizeSimplifiedArrays(item) : item);
|
|
619
|
+
return [{ ...field, fields: normalizedItems }];
|
|
615
620
|
}
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
navigateToPreviousPage() {
|
|
620
|
-
const currentState = this.state();
|
|
621
|
-
const visibleIndices = this.visiblePageIndices();
|
|
622
|
-
if (currentState.isFirstPage) {
|
|
623
|
-
return {
|
|
624
|
-
success: false,
|
|
625
|
-
newPageIndex: currentState.currentPageIndex,
|
|
626
|
-
error: 'Already on the first visible page',
|
|
627
|
-
};
|
|
621
|
+
if (isSimplifiedArrayField(field)) {
|
|
622
|
+
const { arrayField, addButton } = expandSimplifiedArray(field);
|
|
623
|
+
return addButton ? [arrayField, addButton] : [arrayField];
|
|
628
624
|
}
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
625
|
+
return [field];
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
const INVALID_ARRAY_CHILD_TYPES = new Set(['page', 'array']);
|
|
629
|
+
function validateSimplifiedTemplate(field) {
|
|
630
|
+
const { template, key } = field;
|
|
631
|
+
const isObjectTemplate = Array.isArray(template);
|
|
632
|
+
if (isObjectTemplate) {
|
|
633
|
+
const templateFields = template;
|
|
634
|
+
for (const tmpl of templateFields) {
|
|
635
|
+
if (INVALID_ARRAY_CHILD_TYPES.has(tmpl.type)) {
|
|
636
|
+
throw new DynamicFormError(`Simplified array "${key}" template contains a '${tmpl.type}' field (key: '${tmpl.key}'). ` +
|
|
637
|
+
`Only leaf fields, rows, and groups are allowed as array children.`);
|
|
638
|
+
}
|
|
636
639
|
}
|
|
637
|
-
const prevVisiblePageIndex = visibleIndices[currentVisiblePosition - 1];
|
|
638
|
-
return this.executePageChange(prevVisiblePageIndex);
|
|
639
640
|
}
|
|
640
|
-
|
|
641
|
-
const
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
641
|
+
else {
|
|
642
|
+
const singleTemplate = template;
|
|
643
|
+
if (INVALID_ARRAY_CHILD_TYPES.has(singleTemplate.type)) {
|
|
644
|
+
throw new DynamicFormError(`Simplified array "${key}" template has type '${singleTemplate.type}'. ` +
|
|
645
|
+
`Only leaf fields, rows, and groups are allowed as array children.`);
|
|
645
646
|
}
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
const
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
return this.executePageChange(pageIndex);
|
|
657
|
-
}
|
|
658
|
-
this.executePageChange(firstInvalidPage);
|
|
659
|
-
return {
|
|
660
|
-
success: false,
|
|
661
|
-
newPageIndex: firstInvalidPage,
|
|
662
|
-
error: `Cannot navigate to page ${pageIndex}: page ${firstInvalidPage} has invalid fields`,
|
|
663
|
-
};
|
|
664
|
-
}
|
|
665
|
-
executePageChange(pageIndex) {
|
|
666
|
-
const currentState = this.state();
|
|
667
|
-
const totalPages = currentState.totalPages;
|
|
668
|
-
if (pageIndex < 0 || pageIndex >= totalPages) {
|
|
669
|
-
return {
|
|
670
|
-
success: false,
|
|
671
|
-
newPageIndex: currentState.currentPageIndex,
|
|
672
|
-
error: `Invalid page index: ${pageIndex}. Valid range is 0 to ${totalPages - 1}`,
|
|
673
|
-
};
|
|
674
|
-
}
|
|
675
|
-
const visibleIndices = this.visiblePageIndices();
|
|
676
|
-
if (!visibleIndices.includes(pageIndex)) {
|
|
677
|
-
return {
|
|
678
|
-
success: false,
|
|
679
|
-
newPageIndex: currentState.currentPageIndex,
|
|
680
|
-
error: `Cannot navigate to hidden page at index ${pageIndex}. Visible pages: [${visibleIndices.join(', ')}]`,
|
|
681
|
-
};
|
|
682
|
-
}
|
|
683
|
-
if (pageIndex === currentState.currentPageIndex) {
|
|
684
|
-
return {
|
|
685
|
-
success: true,
|
|
686
|
-
newPageIndex: pageIndex,
|
|
687
|
-
};
|
|
688
|
-
}
|
|
689
|
-
const previousIndex = currentState.currentPageIndex;
|
|
690
|
-
this.currentPageIndex.set(pageIndex);
|
|
691
|
-
this.eventBus.dispatch(PageChangeEvent, pageIndex, totalPages, previousIndex);
|
|
692
|
-
return {
|
|
693
|
-
success: true,
|
|
694
|
-
newPageIndex: pageIndex,
|
|
695
|
-
};
|
|
696
|
-
}
|
|
697
|
-
findNearestVisiblePage(currentIndex, visibleIndices) {
|
|
698
|
-
let nearest = -1;
|
|
699
|
-
let minDistance = Infinity;
|
|
700
|
-
for (const idx of visibleIndices) {
|
|
701
|
-
const distance = Math.abs(idx - currentIndex);
|
|
702
|
-
if (distance < minDistance || (distance === minDistance && idx > nearest)) {
|
|
703
|
-
minDistance = distance;
|
|
704
|
-
nearest = idx;
|
|
705
|
-
}
|
|
706
|
-
}
|
|
707
|
-
return nearest;
|
|
708
|
-
}
|
|
709
|
-
setupEventListeners() {
|
|
710
|
-
this.eventBus
|
|
711
|
-
.on('next-page')
|
|
712
|
-
.pipe(takeUntilDestroyed())
|
|
713
|
-
.subscribe(() => {
|
|
714
|
-
this.navigateToNextPage();
|
|
715
|
-
});
|
|
716
|
-
this.eventBus
|
|
717
|
-
.on('previous-page')
|
|
718
|
-
.pipe(takeUntilDestroyed())
|
|
719
|
-
.subscribe(() => {
|
|
720
|
-
this.navigateToPreviousPage();
|
|
721
|
-
});
|
|
722
|
-
this.eventBus
|
|
723
|
-
.on('go-to-page')
|
|
724
|
-
.pipe(takeUntilDestroyed())
|
|
725
|
-
.subscribe((event) => {
|
|
726
|
-
this.navigateToPage(event.pageIndex, event.options);
|
|
727
|
-
});
|
|
728
|
-
explicitEffect([this.state], ([state]) => this.eventBus.dispatch(PagerStateEvent, state));
|
|
729
|
-
}
|
|
730
|
-
evaluatePageHidden(page) {
|
|
731
|
-
if (!page.logic || page.logic.length === 0) {
|
|
732
|
-
return false;
|
|
733
|
-
}
|
|
734
|
-
const hiddenLogic = page.logic.filter((l) => l.type === 'hidden');
|
|
735
|
-
if (hiddenLogic.length === 0) {
|
|
736
|
-
return false;
|
|
737
|
-
}
|
|
738
|
-
for (const logic of hiddenLogic) {
|
|
739
|
-
if (typeof logic.condition === 'boolean') {
|
|
740
|
-
if (logic.condition) {
|
|
741
|
-
return true;
|
|
742
|
-
}
|
|
743
|
-
continue;
|
|
744
|
-
}
|
|
745
|
-
const condition = logic.condition;
|
|
746
|
-
const context = this.fieldContextRegistry.createDisplayOnlyContext(page.key || '', this.functionRegistry.getCustomFunctions());
|
|
747
|
-
if (evaluateCondition(condition, context)) {
|
|
748
|
-
return true;
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
return false;
|
|
752
|
-
}
|
|
753
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: PageOrchestratorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
754
|
-
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: [
|
|
755
|
-
{
|
|
756
|
-
provide: FIELD_SIGNAL_CONTEXT,
|
|
757
|
-
useFactory: (orchestrator) => orchestrator.extendedFieldSignalContext(),
|
|
758
|
-
deps: [PageOrchestratorComponent],
|
|
759
|
-
},
|
|
760
|
-
], ngImport: i0, template: `
|
|
761
|
-
@for (pageField of pageFields(); track pageField.key; let i = $index) {
|
|
762
|
-
<!--
|
|
763
|
-
Skip pages hidden by 'hidden' logic conditions entirely. FormStateManager
|
|
764
|
-
derives schema/value/validators from config (not from mounted components),
|
|
765
|
-
so unmounting a hidden page has no effect on form state — only on render +
|
|
766
|
-
CD cost. This is the page-level equivalent of the existing field-level
|
|
767
|
-
\`@if (!field.hidden())\` gate in page-field.component.ts.
|
|
768
|
-
-->
|
|
769
|
-
@if (!pageHiddenStates()[i]) {
|
|
770
|
-
@if (isWithinPreloadWindow(i)) {
|
|
771
|
-
<!-- Current and preloaded neighbour pages: render immediately for flicker-free navigation -->
|
|
772
|
-
@defer (on immediate) {
|
|
773
|
-
<section
|
|
774
|
-
page-field
|
|
775
|
-
[field]="pageField"
|
|
776
|
-
[key]="pageField.key"
|
|
777
|
-
[pageIndex]="i"
|
|
778
|
-
[isVisible]="i === state().currentPageIndex"
|
|
779
|
-
></section>
|
|
780
|
-
} @placeholder {
|
|
781
|
-
<div class="df-page-placeholder" [attr.data-page-index]="i" [attr.data-page-key]="pageField.key"></div>
|
|
782
|
-
}
|
|
783
|
-
} @else {
|
|
784
|
-
<!--
|
|
785
|
-
Pages outside the preload window render only a lightweight placeholder
|
|
786
|
-
— they are NOT mounted. Mounting every page eagerly (previously via
|
|
787
|
-
\`@defer (on idle)\`, which fires almost immediately post-load) put all
|
|
788
|
-
fields of every page in the DOM at once: O(all-fields) initial render
|
|
789
|
-
blocking + O(all-fields) change detection on every keystroke, with no
|
|
790
|
-
visible benefit since only one page is shown at a time.
|
|
791
|
-
|
|
792
|
-
Form state (schema/value/validators) derives from config, not from
|
|
793
|
-
mounted components, so leaving a page unmounted does not affect
|
|
794
|
-
validity, derivations, or navigation. A page mounts the moment
|
|
795
|
-
navigation brings it into the preload window (above), which is
|
|
796
|
-
flicker-free for sequential next/previous navigation. The window size
|
|
797
|
-
is configurable via \`withPagePreload(n)\` or \`FormOptions.pagePreloadWindow\`.
|
|
798
|
-
-->
|
|
799
|
-
<div class="df-page-placeholder" [attr.data-page-index]="i" [attr.data-page-key]="pageField.key"></div>
|
|
800
|
-
}
|
|
801
|
-
}
|
|
802
|
-
}
|
|
803
|
-
`, isInline: true, styles: [":host{display:block;position:relative;width:100%}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, deferBlockDependencies: [() => [/* @ts-ignore */
|
|
804
|
-
import('./ng-forge-dynamic-forms-page-field.component-B92kLZkH.mjs').then(m => m.default)]] });
|
|
805
|
-
}
|
|
806
|
-
i0.ɵɵngDeclareClassMetadataAsync({ minVersion: "18.0.0", version: "22.0.1", ngImport: i0, type: PageOrchestratorComponent, resolveDeferredDeps: () => [/* @ts-ignore */
|
|
807
|
-
import('./ng-forge-dynamic-forms-page-field.component-B92kLZkH.mjs').then(m => m.default)], resolveMetadata: PageFieldComponent => ({ decorators: [{
|
|
808
|
-
type: Component,
|
|
809
|
-
args: [{ selector: 'div[page-orchestrator]', imports: [PageFieldComponent], template: `
|
|
810
|
-
@for (pageField of pageFields(); track pageField.key; let i = $index) {
|
|
811
|
-
<!--
|
|
812
|
-
Skip pages hidden by 'hidden' logic conditions entirely. FormStateManager
|
|
813
|
-
derives schema/value/validators from config (not from mounted components),
|
|
814
|
-
so unmounting a hidden page has no effect on form state — only on render +
|
|
815
|
-
CD cost. This is the page-level equivalent of the existing field-level
|
|
816
|
-
\`@if (!field.hidden())\` gate in page-field.component.ts.
|
|
817
|
-
-->
|
|
818
|
-
@if (!pageHiddenStates()[i]) {
|
|
819
|
-
@if (isWithinPreloadWindow(i)) {
|
|
820
|
-
<!-- Current and preloaded neighbour pages: render immediately for flicker-free navigation -->
|
|
821
|
-
@defer (on immediate) {
|
|
822
|
-
<section
|
|
823
|
-
page-field
|
|
824
|
-
[field]="pageField"
|
|
825
|
-
[key]="pageField.key"
|
|
826
|
-
[pageIndex]="i"
|
|
827
|
-
[isVisible]="i === state().currentPageIndex"
|
|
828
|
-
></section>
|
|
829
|
-
} @placeholder {
|
|
830
|
-
<div class="df-page-placeholder" [attr.data-page-index]="i" [attr.data-page-key]="pageField.key"></div>
|
|
831
|
-
}
|
|
832
|
-
} @else {
|
|
833
|
-
<!--
|
|
834
|
-
Pages outside the preload window render only a lightweight placeholder
|
|
835
|
-
— they are NOT mounted. Mounting every page eagerly (previously via
|
|
836
|
-
\`@defer (on idle)\`, which fires almost immediately post-load) put all
|
|
837
|
-
fields of every page in the DOM at once: O(all-fields) initial render
|
|
838
|
-
blocking + O(all-fields) change detection on every keystroke, with no
|
|
839
|
-
visible benefit since only one page is shown at a time.
|
|
840
|
-
|
|
841
|
-
Form state (schema/value/validators) derives from config, not from
|
|
842
|
-
mounted components, so leaving a page unmounted does not affect
|
|
843
|
-
validity, derivations, or navigation. A page mounts the moment
|
|
844
|
-
navigation brings it into the preload window (above), which is
|
|
845
|
-
flicker-free for sequential next/previous navigation. The window size
|
|
846
|
-
is configurable via \`withPagePreload(n)\` or \`FormOptions.pagePreloadWindow\`.
|
|
847
|
-
-->
|
|
848
|
-
<div class="df-page-placeholder" [attr.data-page-index]="i" [attr.data-page-key]="pageField.key"></div>
|
|
849
|
-
}
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
|
-
`, host: {
|
|
853
|
-
class: 'df-page-orchestrator',
|
|
854
|
-
'[attr.data-current-page]': 'state().currentPageIndex',
|
|
855
|
-
'[attr.data-total-pages]': 'state().totalPages',
|
|
856
|
-
}, providers: [
|
|
857
|
-
{
|
|
858
|
-
provide: FIELD_SIGNAL_CONTEXT,
|
|
859
|
-
useFactory: (orchestrator) => orchestrator.extendedFieldSignalContext(),
|
|
860
|
-
deps: [PageOrchestratorComponent],
|
|
861
|
-
},
|
|
862
|
-
], changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;position:relative;width:100%}\n"] }]
|
|
863
|
-
}], 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 }] }] } }) });
|
|
864
|
-
function collectLeafFieldKeys(fields) {
|
|
865
|
-
const keys = [];
|
|
866
|
-
for (const field of fields) {
|
|
867
|
-
if (isRowField(field) || isGenericContainerField(field)) {
|
|
868
|
-
keys.push(...collectLeafFieldKeys(field.fields));
|
|
869
|
-
}
|
|
870
|
-
else if (field.key) {
|
|
871
|
-
keys.push(field.key);
|
|
872
|
-
}
|
|
873
|
-
}
|
|
874
|
-
return keys;
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
const NORMALIZED_ARRAY_METADATA = Symbol('normalizedArrayMetadata');
|
|
878
|
-
function setNormalizedArrayMetadata(arrayField, metadata) {
|
|
879
|
-
arrayField[NORMALIZED_ARRAY_METADATA] = metadata;
|
|
880
|
-
}
|
|
881
|
-
function getNormalizedArrayMetadata(arrayField) {
|
|
882
|
-
return arrayField[NORMALIZED_ARRAY_METADATA];
|
|
883
|
-
}
|
|
884
|
-
|
|
885
|
-
function normalizeSimplifiedArrays(fields) {
|
|
886
|
-
return fields.flatMap((field) => {
|
|
887
|
-
if (isContainerField(field) && !isArrayField(field) && hasChildFields(field)) {
|
|
888
|
-
return [{ ...field, fields: normalizeSimplifiedArrays(field.fields) }];
|
|
889
|
-
}
|
|
890
|
-
if (isArrayField(field) && !isSimplifiedArrayField(field)) {
|
|
891
|
-
const normalizedItems = field.fields.map((item) => Array.isArray(item) ? normalizeSimplifiedArrays(item) : item);
|
|
892
|
-
return [{ ...field, fields: normalizedItems }];
|
|
893
|
-
}
|
|
894
|
-
if (isSimplifiedArrayField(field)) {
|
|
895
|
-
const { arrayField, addButton } = expandSimplifiedArray(field);
|
|
896
|
-
return addButton ? [arrayField, addButton] : [arrayField];
|
|
897
|
-
}
|
|
898
|
-
return [field];
|
|
899
|
-
});
|
|
900
|
-
}
|
|
901
|
-
const INVALID_ARRAY_CHILD_TYPES = new Set(['page', 'array']);
|
|
902
|
-
function validateSimplifiedTemplate(field) {
|
|
903
|
-
const { template, key } = field;
|
|
904
|
-
const isObjectTemplate = Array.isArray(template);
|
|
905
|
-
if (isObjectTemplate) {
|
|
906
|
-
const templateFields = template;
|
|
907
|
-
for (const tmpl of templateFields) {
|
|
908
|
-
if (INVALID_ARRAY_CHILD_TYPES.has(tmpl.type)) {
|
|
909
|
-
throw new DynamicFormError(`Simplified array "${key}" template contains a '${tmpl.type}' field (key: '${tmpl.key}'). ` +
|
|
910
|
-
`Only leaf fields, rows, and groups are allowed as array children.`);
|
|
911
|
-
}
|
|
912
|
-
}
|
|
913
|
-
}
|
|
914
|
-
else {
|
|
915
|
-
const singleTemplate = template;
|
|
916
|
-
if (INVALID_ARRAY_CHILD_TYPES.has(singleTemplate.type)) {
|
|
917
|
-
throw new DynamicFormError(`Simplified array "${key}" template has type '${singleTemplate.type}'. ` +
|
|
918
|
-
`Only leaf fields, rows, and groups are allowed as array children.`);
|
|
919
|
-
}
|
|
920
|
-
}
|
|
921
|
-
}
|
|
922
|
-
function expandSimplifiedArray(field) {
|
|
923
|
-
const { template, value = [], addButton, removeButton, key, logic, minLength, maxLength, wrappers, skipAutoWrappers, skipDefaultWrappers, } = field;
|
|
924
|
-
const isObjectTemplate = Array.isArray(template);
|
|
925
|
-
const values = value;
|
|
926
|
-
validateSimplifiedTemplate(field);
|
|
927
|
-
const items = values.map((v) => {
|
|
928
|
-
if (isObjectTemplate) {
|
|
929
|
-
return buildObjectItem(template, v, removeButton);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
function expandSimplifiedArray(field) {
|
|
650
|
+
const { template, value = [], addButton, removeButton, key, logic, minLength, maxLength, wrappers, skipAutoWrappers, skipDefaultWrappers, validators, validationMessages, required, validateWhenHidden, } = field;
|
|
651
|
+
const isObjectTemplate = Array.isArray(template);
|
|
652
|
+
const values = value;
|
|
653
|
+
validateSimplifiedTemplate(field);
|
|
654
|
+
const items = values.map((v) => {
|
|
655
|
+
if (isObjectTemplate) {
|
|
656
|
+
return buildObjectItem(template, v, removeButton);
|
|
930
657
|
}
|
|
931
658
|
return buildPrimitiveItem(template, v);
|
|
932
659
|
});
|
|
@@ -956,6 +683,18 @@ function expandSimplifiedArray(field) {
|
|
|
956
683
|
}
|
|
957
684
|
if (skipDefaultWrappers !== undefined) {
|
|
958
685
|
arrayFieldObj['skipDefaultWrappers'] = skipDefaultWrappers;
|
|
686
|
+
}
|
|
687
|
+
if (validators !== undefined) {
|
|
688
|
+
arrayFieldObj['validators'] = validators;
|
|
689
|
+
}
|
|
690
|
+
if (validationMessages !== undefined) {
|
|
691
|
+
arrayFieldObj['validationMessages'] = validationMessages;
|
|
692
|
+
}
|
|
693
|
+
if (required !== undefined) {
|
|
694
|
+
arrayFieldObj['required'] = required;
|
|
695
|
+
}
|
|
696
|
+
if (validateWhenHidden !== undefined) {
|
|
697
|
+
arrayFieldObj['validateWhenHidden'] = validateWhenHidden;
|
|
959
698
|
}
|
|
960
699
|
const arrayField = arrayFieldObj;
|
|
961
700
|
const primitiveFieldKey = !isObjectTemplate ? template.key : undefined;
|
|
@@ -1082,499 +821,104 @@ class SchemaRegistryService {
|
|
|
1082
821
|
registerSchema(schema) {
|
|
1083
822
|
this.registeredSchemas.set(schema.name, schema);
|
|
1084
823
|
}
|
|
1085
|
-
getSchema(name) {
|
|
1086
|
-
return this.registeredSchemas.get(name);
|
|
1087
|
-
}
|
|
1088
|
-
getAllSchemas() {
|
|
1089
|
-
return new Map(this.registeredSchemas);
|
|
1090
|
-
}
|
|
1091
|
-
clearSchemas() {
|
|
1092
|
-
this.registeredSchemas.clear();
|
|
1093
|
-
}
|
|
1094
|
-
resolveSchema(schema) {
|
|
1095
|
-
if (typeof schema === 'string') {
|
|
1096
|
-
return this.getSchema(schema) || null;
|
|
1097
|
-
}
|
|
1098
|
-
return schema;
|
|
1099
|
-
}
|
|
1100
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: SchemaRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1101
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: SchemaRegistryService });
|
|
1102
|
-
}
|
|
1103
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: SchemaRegistryService, decorators: [{
|
|
1104
|
-
type: Injectable
|
|
1105
|
-
}] });
|
|
1106
|
-
|
|
1107
|
-
const DEV_MODE = typeof ngDevMode === 'undefined' || !!ngDevMode;
|
|
1108
|
-
|
|
1109
|
-
const DEFAULT_DEBOUNCE_MS = 500;
|
|
1110
|
-
function createDebouncedEffect(source, callback, options) {
|
|
1111
|
-
const { ms = DEFAULT_DEBOUNCE_MS, injector } = options;
|
|
1112
|
-
const source$ = toObservable(source, { injector }).pipe(debounceTime(ms), distinctUntilChanged(), tap((value) => callback(value)));
|
|
1113
|
-
return toSignal(source$, { injector });
|
|
1114
|
-
}
|
|
1115
|
-
function createDebouncedSignal(source, options) {
|
|
1116
|
-
const { ms = DEFAULT_DEBOUNCE_MS, injector } = options;
|
|
1117
|
-
const source$ = toObservable(source, { injector }).pipe(debounceTime(ms), distinctUntilChanged());
|
|
1118
|
-
return toSignal(source$, { injector });
|
|
1119
|
-
}
|
|
1120
|
-
|
|
1121
|
-
function getConfigTrigger(config) {
|
|
1122
|
-
if (!isStateLogicConfig(config)) {
|
|
1123
|
-
return 'onChange';
|
|
1124
|
-
}
|
|
1125
|
-
return config.trigger ?? 'onChange';
|
|
1126
|
-
}
|
|
1127
|
-
function getConfigDebounceMs(config) {
|
|
1128
|
-
if (!isStateLogicConfig(config)) {
|
|
1129
|
-
return undefined;
|
|
1130
|
-
}
|
|
1131
|
-
if (config.trigger === 'debounced') {
|
|
1132
|
-
return config.debounceMs;
|
|
1133
|
-
}
|
|
1134
|
-
return undefined;
|
|
1135
|
-
}
|
|
1136
|
-
function applyLogic(config, fieldPath) {
|
|
1137
|
-
if (isDerivationLogicConfig(config))
|
|
1138
|
-
return;
|
|
1139
|
-
if (!isStateLogicConfig(config)) {
|
|
1140
|
-
if (DEV_MODE) {
|
|
1141
|
-
console.warn(`[Dynamic Forms] Unrecognized logic config type '${config.type}' in applyLogic. ` +
|
|
1142
|
-
'This config will be ignored. If this is a new logic type, ensure it is handled explicitly.');
|
|
1143
|
-
}
|
|
1144
|
-
return;
|
|
1145
|
-
}
|
|
1146
|
-
const path = fieldPath;
|
|
1147
|
-
const trigger = getConfigTrigger(config);
|
|
1148
|
-
const debounceMs = getConfigDebounceMs(config) ?? DEFAULT_DEBOUNCE_MS;
|
|
1149
|
-
if (typeof config.condition === 'boolean') {
|
|
1150
|
-
applyLogicFn(config.type, path, () => config.condition);
|
|
1151
|
-
return;
|
|
1152
|
-
}
|
|
1153
|
-
if (isFormStateCondition(config.condition)) {
|
|
1154
|
-
if (config.condition === 'formInvalid' || config.condition === 'pageInvalid') {
|
|
1155
|
-
const reason = config.condition === 'formInvalid'
|
|
1156
|
-
? `it would create a validation cycle against the form's own validity`
|
|
1157
|
-
: `a value field has no page context for it to resolve against`;
|
|
1158
|
-
throw new DynamicFormError(`The '${config.condition}' form-state condition is not supported on value-field logic because ${reason}. ` +
|
|
1159
|
-
`Use 'formSubmitting', or apply this condition to a button or container instead.`);
|
|
1160
|
-
}
|
|
1161
|
-
applyLogicFn(config.type, path, createFormStateLogicFunction(config.condition));
|
|
1162
|
-
return;
|
|
1163
|
-
}
|
|
1164
|
-
const logicFn = trigger === 'debounced'
|
|
1165
|
-
? createDebouncedLogicFunction(config.condition, debounceMs)
|
|
1166
|
-
: createLogicFunction(config.condition);
|
|
1167
|
-
applyLogicFn(config.type, path, logicFn);
|
|
1168
|
-
}
|
|
1169
|
-
function applyLogicFn(type, path, logicFn) {
|
|
1170
|
-
switch (type) {
|
|
1171
|
-
case 'hidden':
|
|
1172
|
-
hidden(path, logicFn);
|
|
1173
|
-
break;
|
|
1174
|
-
case 'readonly':
|
|
1175
|
-
readonly(path, logicFn);
|
|
1176
|
-
break;
|
|
1177
|
-
case 'disabled':
|
|
1178
|
-
disabled(path, logicFn);
|
|
1179
|
-
break;
|
|
1180
|
-
case 'required':
|
|
1181
|
-
required(path, { when: logicFn });
|
|
1182
|
-
break;
|
|
1183
|
-
default: {
|
|
1184
|
-
const _exhaustive = type;
|
|
1185
|
-
throw new DynamicFormError(`Unhandled state logic type: ${_exhaustive}`);
|
|
1186
|
-
}
|
|
1187
|
-
}
|
|
1188
|
-
}
|
|
1189
|
-
function applyMultipleLogic(configs, fieldPath) {
|
|
1190
|
-
configs.forEach((config) => applyLogic(config, fieldPath));
|
|
1191
|
-
}
|
|
1192
|
-
|
|
1193
|
-
function createTypePredicateFunction(predicate, logger) {
|
|
1194
|
-
return (value) => {
|
|
1195
|
-
try {
|
|
1196
|
-
const result = ExpressionParser.evaluate(predicate, { value });
|
|
1197
|
-
return Boolean(result);
|
|
1198
|
-
}
|
|
1199
|
-
catch (error) {
|
|
1200
|
-
logger.error('Error evaluating type predicate:', predicate, error);
|
|
1201
|
-
return false;
|
|
1202
|
-
}
|
|
1203
|
-
};
|
|
1204
|
-
}
|
|
1205
|
-
|
|
1206
|
-
function applySchema(config, fieldPath) {
|
|
1207
|
-
const logger = inject(DynamicFormLogger);
|
|
1208
|
-
const schemaRegistry = inject(SchemaRegistryService);
|
|
1209
|
-
const schema = schemaRegistry.resolveSchema(config.schema);
|
|
1210
|
-
if (!schema) {
|
|
1211
|
-
const availableSchemas = Array.from(schemaRegistry.getAllSchemas().keys()).join(', ') || '<none>';
|
|
1212
|
-
logger.error(`Schema not found: '${config.schema}'. ` +
|
|
1213
|
-
`Available schemas: ${availableSchemas}. ` +
|
|
1214
|
-
`Ensure the schema is registered in your schema registry.`);
|
|
1215
|
-
return;
|
|
1216
|
-
}
|
|
1217
|
-
const schemaFn = createSchemaFunction(schema, logger);
|
|
1218
|
-
const path = fieldPath;
|
|
1219
|
-
switch (config.type) {
|
|
1220
|
-
case 'apply':
|
|
1221
|
-
apply(path, schemaFn);
|
|
1222
|
-
break;
|
|
1223
|
-
case 'applyWhen':
|
|
1224
|
-
if (config.condition) {
|
|
1225
|
-
const conditionFn = createLogicFunction(config.condition);
|
|
1226
|
-
applyWhen(path, conditionFn, schemaFn);
|
|
1227
|
-
}
|
|
1228
|
-
break;
|
|
1229
|
-
case 'applyWhenValue':
|
|
1230
|
-
if (config.typePredicate) {
|
|
1231
|
-
const predicate = createTypePredicateFunction(config.typePredicate, logger);
|
|
1232
|
-
applyWhenValue(path, predicate, schemaFn);
|
|
1233
|
-
}
|
|
1234
|
-
break;
|
|
1235
|
-
case 'applyEach':
|
|
1236
|
-
applyEach(path, schemaFn);
|
|
1237
|
-
break;
|
|
1238
|
-
}
|
|
1239
|
-
}
|
|
1240
|
-
function createSchemaFunction(schema, logger) {
|
|
1241
|
-
return (path) => {
|
|
1242
|
-
schema.validators?.forEach((validatorConfig) => {
|
|
1243
|
-
applyValidator(validatorConfig, path);
|
|
1244
|
-
});
|
|
1245
|
-
schema.logic?.forEach((logicConfig) => {
|
|
1246
|
-
applyLogic(logicConfig, path);
|
|
1247
|
-
});
|
|
1248
|
-
schema.subSchemas?.forEach((subSchemaConfig) => {
|
|
1249
|
-
applySchema(subSchemaConfig, path);
|
|
1250
|
-
});
|
|
1251
|
-
};
|
|
1252
|
-
}
|
|
1253
|
-
|
|
1254
|
-
function combineAncestorHiddenLogics(logics) {
|
|
1255
|
-
if (logics.length === 0)
|
|
1256
|
-
return undefined;
|
|
1257
|
-
if (logics.length === 1)
|
|
1258
|
-
return logics[0];
|
|
1259
|
-
return (ctx) => logics.some((fn) => fn(ctx));
|
|
1260
|
-
}
|
|
1261
|
-
function resolveFieldOwnContext(fieldDef, parentContext) {
|
|
1262
|
-
if (fieldDef.validateWhenHidden === undefined)
|
|
1263
|
-
return parentContext;
|
|
1264
|
-
if (fieldDef.validateWhenHidden === parentContext.validateWhenHidden)
|
|
1265
|
-
return parentContext;
|
|
1266
|
-
return {
|
|
1267
|
-
...parentContext,
|
|
1268
|
-
validateWhenHidden: fieldDef.validateWhenHidden,
|
|
1269
|
-
};
|
|
1270
|
-
}
|
|
1271
|
-
function resolveDescendantContext(fieldDef, ownContext) {
|
|
1272
|
-
let ancestorAlwaysHidden = ownContext.ancestorAlwaysHidden;
|
|
1273
|
-
let ancestorHiddenLogics = ownContext.ancestorHiddenLogics;
|
|
1274
|
-
let mutated = false;
|
|
1275
|
-
if (fieldDef.hidden === true && !ancestorAlwaysHidden) {
|
|
1276
|
-
ancestorAlwaysHidden = true;
|
|
1277
|
-
mutated = true;
|
|
1278
|
-
}
|
|
1279
|
-
if (fieldDef.logic) {
|
|
1280
|
-
for (const logic of fieldDef.logic) {
|
|
1281
|
-
if (!isStateLogicConfig(logic) || logic.type !== 'hidden')
|
|
1282
|
-
continue;
|
|
1283
|
-
const condition = logic.condition;
|
|
1284
|
-
if (condition === true) {
|
|
1285
|
-
if (!ancestorAlwaysHidden) {
|
|
1286
|
-
ancestorAlwaysHidden = true;
|
|
1287
|
-
mutated = true;
|
|
1288
|
-
}
|
|
1289
|
-
continue;
|
|
1290
|
-
}
|
|
1291
|
-
if (condition === false)
|
|
1292
|
-
continue;
|
|
1293
|
-
if (typeof condition === 'string')
|
|
1294
|
-
continue;
|
|
1295
|
-
const fn = createLogicFunction(condition);
|
|
1296
|
-
ancestorHiddenLogics = ancestorHiddenLogics.length === 0 ? [fn] : [...ancestorHiddenLogics, fn];
|
|
1297
|
-
mutated = true;
|
|
1298
|
-
}
|
|
1299
|
-
}
|
|
1300
|
-
if (!mutated)
|
|
1301
|
-
return ownContext;
|
|
1302
|
-
return {
|
|
1303
|
-
validateWhenHidden: ownContext.validateWhenHidden,
|
|
1304
|
-
ancestorAlwaysHidden,
|
|
1305
|
-
ancestorHiddenLogics,
|
|
1306
|
-
};
|
|
1307
|
-
}
|
|
1308
|
-
|
|
1309
|
-
const DEFAULT_FIELD_TREE_MAPPING_CONTEXT = {
|
|
1310
|
-
validateWhenHidden: false,
|
|
1311
|
-
ancestorAlwaysHidden: false,
|
|
1312
|
-
ancestorHiddenLogics: [],
|
|
1313
|
-
};
|
|
1314
|
-
function applyStringValidators(path, config) {
|
|
1315
|
-
if (config.email) {
|
|
1316
|
-
email(path);
|
|
1317
|
-
}
|
|
1318
|
-
if (config.minLength !== undefined) {
|
|
1319
|
-
minLength(path, config.minLength);
|
|
1320
|
-
}
|
|
1321
|
-
if (config.maxLength !== undefined) {
|
|
1322
|
-
maxLength(path, config.maxLength);
|
|
1323
|
-
}
|
|
1324
|
-
if (config.pattern) {
|
|
1325
|
-
let regex;
|
|
1326
|
-
if (typeof config.pattern === 'string') {
|
|
1327
|
-
try {
|
|
1328
|
-
regex = new RegExp(config.pattern);
|
|
1329
|
-
}
|
|
1330
|
-
catch (e) {
|
|
1331
|
-
throw new DynamicFormError(`Invalid regex pattern: '${config.pattern}' — ${e instanceof Error ? e.message : String(e)}`);
|
|
1332
|
-
}
|
|
1333
|
-
}
|
|
1334
|
-
else {
|
|
1335
|
-
regex = config.pattern;
|
|
1336
|
-
}
|
|
1337
|
-
pattern(path, regex);
|
|
1338
|
-
}
|
|
1339
|
-
}
|
|
1340
|
-
function applyNumberValidators(path, config) {
|
|
1341
|
-
if (config.min !== undefined) {
|
|
1342
|
-
min(path, config.min);
|
|
1343
|
-
}
|
|
1344
|
-
if (config.max !== undefined) {
|
|
1345
|
-
max(path, config.max);
|
|
1346
|
-
}
|
|
1347
|
-
}
|
|
1348
|
-
function getStringValidationConfig(fieldDef) {
|
|
1349
|
-
if (!fieldDef.email && fieldDef.minLength === undefined && fieldDef.maxLength === undefined && !fieldDef.pattern) {
|
|
1350
|
-
return undefined;
|
|
1351
|
-
}
|
|
1352
|
-
return {
|
|
1353
|
-
email: fieldDef.email,
|
|
1354
|
-
minLength: fieldDef.minLength,
|
|
1355
|
-
maxLength: fieldDef.maxLength,
|
|
1356
|
-
pattern: fieldDef.pattern,
|
|
1357
|
-
};
|
|
1358
|
-
}
|
|
1359
|
-
function getNumberValidationConfig(fieldDef) {
|
|
1360
|
-
const minVal = fieldDef.min ?? fieldDef.minValue;
|
|
1361
|
-
const maxVal = fieldDef.max ?? fieldDef.maxValue;
|
|
1362
|
-
if (minVal === undefined && maxVal === undefined) {
|
|
1363
|
-
return undefined;
|
|
1364
|
-
}
|
|
1365
|
-
return { min: minVal, max: maxVal };
|
|
1366
|
-
}
|
|
1367
|
-
function mapFieldToForm(fieldDef, fieldPath, inheritedContext = DEFAULT_FIELD_TREE_MAPPING_CONTEXT) {
|
|
1368
|
-
const ownContext = resolveFieldOwnContext(fieldDef, inheritedContext);
|
|
1369
|
-
if (isPageField(fieldDef) || isRowField(fieldDef) || isGenericContainerField(fieldDef)) {
|
|
1370
|
-
const descendantContext = resolveDescendantContext(fieldDef, ownContext);
|
|
1371
|
-
mapContainerChildren(fieldDef.fields, fieldPath, descendantContext);
|
|
1372
|
-
return;
|
|
1373
|
-
}
|
|
1374
|
-
if (isGroupField(fieldDef)) {
|
|
1375
|
-
const descendantContext = resolveDescendantContext(fieldDef, ownContext);
|
|
1376
|
-
mapContainerChildren(fieldDef.fields, fieldPath, descendantContext);
|
|
1377
|
-
return;
|
|
1378
|
-
}
|
|
1379
|
-
if (isArrayField(fieldDef)) {
|
|
1380
|
-
const descendantContext = resolveDescendantContext(fieldDef, ownContext);
|
|
1381
|
-
mapArrayFieldToForm(fieldDef, fieldPath, descendantContext);
|
|
1382
|
-
return;
|
|
1383
|
-
}
|
|
1384
|
-
mapLeafField(fieldDef, fieldPath, ownContext);
|
|
1385
|
-
}
|
|
1386
|
-
function mapContainerChildren(fields, parentPath, inheritedContext) {
|
|
1387
|
-
if (!fields)
|
|
1388
|
-
return;
|
|
1389
|
-
const pathRecord = parentPath;
|
|
1390
|
-
for (const field of fields) {
|
|
1391
|
-
if (isPageField(field) || isRowField(field) || isGenericContainerField(field)) {
|
|
1392
|
-
const layoutOwn = resolveFieldOwnContext(field, inheritedContext);
|
|
1393
|
-
const layoutDescendant = resolveDescendantContext(field, layoutOwn);
|
|
1394
|
-
mapContainerChildren(field.fields, parentPath, layoutDescendant);
|
|
1395
|
-
continue;
|
|
1396
|
-
}
|
|
1397
|
-
if (!field.key)
|
|
1398
|
-
continue;
|
|
1399
|
-
const childPath = pathRecord[field.key];
|
|
1400
|
-
if (childPath) {
|
|
1401
|
-
mapFieldToForm(field, childPath, inheritedContext);
|
|
1402
|
-
}
|
|
1403
|
-
}
|
|
1404
|
-
}
|
|
1405
|
-
function isValidationStateLogic(config) {
|
|
1406
|
-
return isStateLogicConfig(config) && config.type === 'required';
|
|
1407
|
-
}
|
|
1408
|
-
function mapLeafField(fieldDef, fieldPath, context) {
|
|
1409
|
-
const validationField = fieldDef;
|
|
1410
|
-
const path = fieldPath;
|
|
1411
|
-
applyFieldState(fieldDef, path);
|
|
1412
|
-
if (validationField.logic) {
|
|
1413
|
-
for (const config of validationField.logic) {
|
|
1414
|
-
if (!isValidationStateLogic(config)) {
|
|
1415
|
-
applyLogic(config, fieldPath);
|
|
1416
|
-
}
|
|
1417
|
-
}
|
|
1418
|
-
}
|
|
1419
|
-
if (validationField.schemas) {
|
|
1420
|
-
for (const config of validationField.schemas) {
|
|
1421
|
-
applySchema(config, fieldPath);
|
|
1422
|
-
}
|
|
1423
|
-
}
|
|
1424
|
-
const applyValidation = (validationPath) => {
|
|
1425
|
-
applySimpleValidationRules(validationField, validationPath);
|
|
1426
|
-
if (validationField.validators) {
|
|
1427
|
-
for (const config of validationField.validators) {
|
|
1428
|
-
applyValidator(config, validationPath);
|
|
1429
|
-
}
|
|
1430
|
-
}
|
|
1431
|
-
if (validationField.logic) {
|
|
1432
|
-
for (const config of validationField.logic) {
|
|
1433
|
-
if (isValidationStateLogic(config)) {
|
|
1434
|
-
applyLogic(config, validationPath);
|
|
1435
|
-
}
|
|
1436
|
-
}
|
|
1437
|
-
}
|
|
1438
|
-
};
|
|
1439
|
-
if (context.validateWhenHidden) {
|
|
1440
|
-
applyValidation(path);
|
|
1441
|
-
return;
|
|
1442
|
-
}
|
|
1443
|
-
if (context.ancestorAlwaysHidden) {
|
|
1444
|
-
return;
|
|
1445
|
-
}
|
|
1446
|
-
const ancestorHiddenLogic = combineAncestorHiddenLogics(context.ancestorHiddenLogics);
|
|
1447
|
-
applyWhen(path, (ctx) => {
|
|
1448
|
-
if (ctx.state.hidden())
|
|
1449
|
-
return false;
|
|
1450
|
-
if (ancestorHiddenLogic && ancestorHiddenLogic(ctx))
|
|
1451
|
-
return false;
|
|
1452
|
-
return true;
|
|
1453
|
-
}, schema((subPath) => applyValidation(subPath)));
|
|
1454
|
-
}
|
|
1455
|
-
function applySimpleValidationRules(fieldDef, path) {
|
|
1456
|
-
if (fieldDef.required) {
|
|
1457
|
-
required(path);
|
|
1458
|
-
}
|
|
1459
|
-
const stringConfig = getStringValidationConfig(fieldDef);
|
|
1460
|
-
if (stringConfig) {
|
|
1461
|
-
applyStringValidators(path, stringConfig);
|
|
1462
|
-
}
|
|
1463
|
-
const numberConfig = getNumberValidationConfig(fieldDef);
|
|
1464
|
-
if (numberConfig) {
|
|
1465
|
-
applyNumberValidators(path, numberConfig);
|
|
1466
|
-
}
|
|
1467
|
-
}
|
|
1468
|
-
function applyFieldState(fieldDef, path) {
|
|
1469
|
-
if (fieldDef.disabled) {
|
|
1470
|
-
disabled(path);
|
|
1471
|
-
}
|
|
1472
|
-
if (fieldDef.readonly) {
|
|
1473
|
-
readonly(path);
|
|
1474
|
-
}
|
|
1475
|
-
if (fieldDef.hidden) {
|
|
1476
|
-
hidden(path, () => true);
|
|
1477
|
-
}
|
|
1478
|
-
}
|
|
1479
|
-
function mapArrayFieldToForm(arrayField, fieldPath, context) {
|
|
1480
|
-
if (!isArrayField(arrayField)) {
|
|
1481
|
-
return;
|
|
1482
|
-
}
|
|
1483
|
-
if (arrayField.minLength !== undefined) {
|
|
1484
|
-
minLength(fieldPath, arrayField.minLength);
|
|
1485
|
-
}
|
|
1486
|
-
if (arrayField.maxLength !== undefined) {
|
|
1487
|
-
maxLength(fieldPath, arrayField.maxLength);
|
|
824
|
+
getSchema(name) {
|
|
825
|
+
return this.registeredSchemas.get(name);
|
|
1488
826
|
}
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
const metadataTemplate = getNormalizedArrayMetadata(arrayField)?.template;
|
|
1492
|
-
if (metadataTemplate) {
|
|
1493
|
-
itemDefinitions = [
|
|
1494
|
-
Array.isArray(metadataTemplate) ? [...metadataTemplate] : metadataTemplate,
|
|
1495
|
-
];
|
|
1496
|
-
}
|
|
827
|
+
getAllSchemas() {
|
|
828
|
+
return new Map(this.registeredSchemas);
|
|
1497
829
|
}
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
});
|
|
1501
|
-
applyEach(fieldPath, emptyItemSchema);
|
|
1502
|
-
return;
|
|
830
|
+
clearSchemas() {
|
|
831
|
+
this.registeredSchemas.clear();
|
|
1503
832
|
}
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
if (Array.isArray(itemDef)) {
|
|
1508
|
-
collectFieldsFromObjectItem(itemDef, allObjectFields);
|
|
1509
|
-
}
|
|
1510
|
-
else if (isPageField(itemDef) ||
|
|
1511
|
-
isRowField(itemDef) ||
|
|
1512
|
-
isGenericContainerField(itemDef)) {
|
|
1513
|
-
collectFieldsFromObjectItem([itemDef], allObjectFields);
|
|
1514
|
-
}
|
|
1515
|
-
else {
|
|
1516
|
-
hasPrimitiveItems = true;
|
|
833
|
+
resolveSchema(schema) {
|
|
834
|
+
if (typeof schema === 'string') {
|
|
835
|
+
return this.getSchema(schema) || null;
|
|
1517
836
|
}
|
|
837
|
+
return schema;
|
|
1518
838
|
}
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
839
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: SchemaRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
840
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: SchemaRegistryService });
|
|
841
|
+
}
|
|
842
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: SchemaRegistryService, decorators: [{
|
|
843
|
+
type: Injectable
|
|
844
|
+
}] });
|
|
845
|
+
|
|
846
|
+
function inheritContainerExclusion(container, child) {
|
|
847
|
+
const inheritedHidden = container.hidden === true || child.inheritedExclusionState?.hidden === true;
|
|
848
|
+
const inheritedDisabled = container.disabled === true || child.inheritedExclusionState?.disabled === true;
|
|
849
|
+
const inheritedReadonly = container.readonly === true || child.inheritedExclusionState?.readonly === true;
|
|
850
|
+
const hasInheritedState = inheritedHidden || inheritedDisabled || inheritedReadonly;
|
|
851
|
+
const containerExcludeConfig = container.excludeValueIfHidden !== undefined ||
|
|
852
|
+
container.excludeValueIfDisabled !== undefined ||
|
|
853
|
+
container.excludeValueIfReadonly !== undefined;
|
|
854
|
+
if (!hasInheritedState && !containerExcludeConfig) {
|
|
855
|
+
return child;
|
|
1524
856
|
}
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
if (fieldKey) {
|
|
1550
|
-
const fieldPathForKey = pathRecord[fieldKey];
|
|
1551
|
-
if (fieldPathForKey) {
|
|
1552
|
-
mapFieldToForm(templateField, fieldPathForKey, context);
|
|
1553
|
-
}
|
|
1554
|
-
}
|
|
857
|
+
return {
|
|
858
|
+
...child,
|
|
859
|
+
...(hasInheritedState
|
|
860
|
+
? { inheritedExclusionState: { hidden: inheritedHidden, disabled: inheritedDisabled, readonly: inheritedReadonly } }
|
|
861
|
+
: {}),
|
|
862
|
+
excludeValueIfHidden: child.excludeValueIfHidden ?? container.excludeValueIfHidden,
|
|
863
|
+
excludeValueIfDisabled: child.excludeValueIfDisabled ?? container.excludeValueIfDisabled,
|
|
864
|
+
excludeValueIfReadonly: child.excludeValueIfReadonly ?? container.excludeValueIfReadonly,
|
|
865
|
+
};
|
|
866
|
+
}
|
|
867
|
+
function flattenFields(fields, registry, options = {}) {
|
|
868
|
+
const result = [];
|
|
869
|
+
let autoKeyCounter = 0;
|
|
870
|
+
for (const field of fields) {
|
|
871
|
+
const valueHandling = getFieldValueHandling(field.type, registry);
|
|
872
|
+
if (options.preserveRows && (isRowField(field) || isGenericContainerField(field))) {
|
|
873
|
+
if (field.fields) {
|
|
874
|
+
const flattenedChildren = flattenFields(normalizeFieldsArray(field.fields), registry, options);
|
|
875
|
+
const autoPrefix = isRowField(field) ? 'auto_row' : 'auto_container';
|
|
876
|
+
result.push({
|
|
877
|
+
...field,
|
|
878
|
+
fields: flattenedChildren,
|
|
879
|
+
key: field.key || `${autoPrefix}_${autoKeyCounter++}`,
|
|
880
|
+
});
|
|
1555
881
|
}
|
|
1556
882
|
}
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
for (const field of itemFields) {
|
|
1563
|
-
if (isRowField(field) || isPageField(field) || isGenericContainerField(field)) {
|
|
1564
|
-
const containerKey = `__container_${field.type}_${JSON.stringify(field.fields?.map((f) => f.key))}`;
|
|
1565
|
-
if (!seenKeys.has(containerKey)) {
|
|
1566
|
-
seenKeys.add(containerKey);
|
|
1567
|
-
allFields.push(field);
|
|
883
|
+
else if (valueHandling === 'flatten' && 'fields' in field) {
|
|
884
|
+
if (field.fields) {
|
|
885
|
+
const fields = field.fields;
|
|
886
|
+
const flattenedChildren = flattenFields(normalizeFieldsArray(fields), registry, options);
|
|
887
|
+
result.push(...flattenedChildren.map((child) => inheritContainerExclusion(field, child)));
|
|
1568
888
|
}
|
|
1569
889
|
}
|
|
890
|
+
else if (isGroupField(field)) {
|
|
891
|
+
const childFieldsArray = Object.values(field.fields);
|
|
892
|
+
const flattenedChildren = flattenFields(childFieldsArray, registry, options);
|
|
893
|
+
result.push({
|
|
894
|
+
...field,
|
|
895
|
+
fields: flattenedChildren,
|
|
896
|
+
key: field.key || `auto_group_${autoKeyCounter++}`,
|
|
897
|
+
});
|
|
898
|
+
}
|
|
899
|
+
else if (isArrayField(field)) {
|
|
900
|
+
const flattenedItemTemplates = field.fields.map((itemDef) => {
|
|
901
|
+
if (!Array.isArray(itemDef)) {
|
|
902
|
+
const flattened = flattenFields([itemDef], registry, options);
|
|
903
|
+
return flattened[0];
|
|
904
|
+
}
|
|
905
|
+
return flattenFields([...itemDef], registry, options);
|
|
906
|
+
});
|
|
907
|
+
result.push({
|
|
908
|
+
...field,
|
|
909
|
+
fields: flattenedItemTemplates,
|
|
910
|
+
key: field.key || `auto_array_${autoKeyCounter++}`,
|
|
911
|
+
});
|
|
912
|
+
}
|
|
1570
913
|
else {
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
}
|
|
914
|
+
const key = field.key || `auto_field_${autoKeyCounter++}`;
|
|
915
|
+
result.push({
|
|
916
|
+
...field,
|
|
917
|
+
key,
|
|
918
|
+
});
|
|
1576
919
|
}
|
|
1577
920
|
}
|
|
921
|
+
return result;
|
|
1578
922
|
}
|
|
1579
923
|
|
|
1580
924
|
function getFieldDefaultValue(field, registry) {
|
|
@@ -1680,160 +1024,6 @@ function getFieldDefaultValue(field, registry) {
|
|
|
1680
1024
|
return '';
|
|
1681
1025
|
}
|
|
1682
1026
|
|
|
1683
|
-
function applyFormLevelSchema(path, formLevelSchema) {
|
|
1684
|
-
if (isStandardSchemaMarker(formLevelSchema)) {
|
|
1685
|
-
validateStandardSchema(path, formLevelSchema.schema);
|
|
1686
|
-
}
|
|
1687
|
-
else if (typeof formLevelSchema === 'function') {
|
|
1688
|
-
formLevelSchema(path);
|
|
1689
|
-
}
|
|
1690
|
-
}
|
|
1691
|
-
function createFormLevelSchema(formLevelSchema) {
|
|
1692
|
-
return schema((path) => {
|
|
1693
|
-
applyFormLevelSchema(path, formLevelSchema);
|
|
1694
|
-
});
|
|
1695
|
-
}
|
|
1696
|
-
|
|
1697
|
-
const VALIDATION_EXECUTION_DEFAULTS = new InjectionToken('VALIDATION_EXECUTION_DEFAULTS', {
|
|
1698
|
-
providedIn: 'root',
|
|
1699
|
-
factory: () => ({
|
|
1700
|
-
validateWhenHidden: false,
|
|
1701
|
-
}),
|
|
1702
|
-
});
|
|
1703
|
-
|
|
1704
|
-
function createSchemaFromFields(fields, registry, options = {}) {
|
|
1705
|
-
const validationExecutionDefaults = inject(VALIDATION_EXECUTION_DEFAULTS);
|
|
1706
|
-
const { formLevelSchema } = options;
|
|
1707
|
-
const rootContext = {
|
|
1708
|
-
validateWhenHidden: options.validateWhenHidden ?? validationExecutionDefaults.validateWhenHidden,
|
|
1709
|
-
ancestorAlwaysHidden: false,
|
|
1710
|
-
ancestorHiddenLogics: [],
|
|
1711
|
-
};
|
|
1712
|
-
return schema((path) => {
|
|
1713
|
-
for (const fieldDef of fields) {
|
|
1714
|
-
const valueHandling = getFieldValueHandling(fieldDef.type, registry);
|
|
1715
|
-
if (valueHandling === 'exclude') {
|
|
1716
|
-
continue;
|
|
1717
|
-
}
|
|
1718
|
-
if (valueHandling === 'flatten' && hasChildFields(fieldDef)) {
|
|
1719
|
-
const flattenedOwn = resolveFieldOwnContext(fieldDef, rootContext);
|
|
1720
|
-
const flattenedContext = resolveDescendantContext(fieldDef, flattenedOwn);
|
|
1721
|
-
for (const childField of normalizeFieldsArray(fieldDef.fields)) {
|
|
1722
|
-
if (!childField.key)
|
|
1723
|
-
continue;
|
|
1724
|
-
const childPath = path[childField.key];
|
|
1725
|
-
if (childPath) {
|
|
1726
|
-
mapFieldToForm(childField, childPath, flattenedContext);
|
|
1727
|
-
}
|
|
1728
|
-
}
|
|
1729
|
-
continue;
|
|
1730
|
-
}
|
|
1731
|
-
const fieldPath = path[fieldDef.key];
|
|
1732
|
-
if (!fieldPath) {
|
|
1733
|
-
continue;
|
|
1734
|
-
}
|
|
1735
|
-
mapFieldToForm(fieldDef, fieldPath, rootContext);
|
|
1736
|
-
}
|
|
1737
|
-
if (formLevelSchema) {
|
|
1738
|
-
applyFormLevelSchema(path, formLevelSchema);
|
|
1739
|
-
}
|
|
1740
|
-
});
|
|
1741
|
-
}
|
|
1742
|
-
function fieldsToDefaultValues(fields, registry) {
|
|
1743
|
-
const defaultValues = {};
|
|
1744
|
-
for (const field of fields) {
|
|
1745
|
-
if (!field.key)
|
|
1746
|
-
continue;
|
|
1747
|
-
const valueHandling = getFieldValueHandling(field.type, registry);
|
|
1748
|
-
if (valueHandling === 'flatten') {
|
|
1749
|
-
continue;
|
|
1750
|
-
}
|
|
1751
|
-
const value = getFieldDefaultValue(field, registry);
|
|
1752
|
-
if (value !== undefined) {
|
|
1753
|
-
defaultValues[field.key] = value;
|
|
1754
|
-
}
|
|
1755
|
-
}
|
|
1756
|
-
return defaultValues;
|
|
1757
|
-
}
|
|
1758
|
-
|
|
1759
|
-
function inheritContainerExclusion(container, child) {
|
|
1760
|
-
const inheritedHidden = container.hidden === true || child.inheritedExclusionState?.hidden === true;
|
|
1761
|
-
const inheritedDisabled = container.disabled === true || child.inheritedExclusionState?.disabled === true;
|
|
1762
|
-
const inheritedReadonly = container.readonly === true || child.inheritedExclusionState?.readonly === true;
|
|
1763
|
-
const hasInheritedState = inheritedHidden || inheritedDisabled || inheritedReadonly;
|
|
1764
|
-
const containerExcludeConfig = container.excludeValueIfHidden !== undefined ||
|
|
1765
|
-
container.excludeValueIfDisabled !== undefined ||
|
|
1766
|
-
container.excludeValueIfReadonly !== undefined;
|
|
1767
|
-
if (!hasInheritedState && !containerExcludeConfig) {
|
|
1768
|
-
return child;
|
|
1769
|
-
}
|
|
1770
|
-
return {
|
|
1771
|
-
...child,
|
|
1772
|
-
...(hasInheritedState
|
|
1773
|
-
? { inheritedExclusionState: { hidden: inheritedHidden, disabled: inheritedDisabled, readonly: inheritedReadonly } }
|
|
1774
|
-
: {}),
|
|
1775
|
-
excludeValueIfHidden: child.excludeValueIfHidden ?? container.excludeValueIfHidden,
|
|
1776
|
-
excludeValueIfDisabled: child.excludeValueIfDisabled ?? container.excludeValueIfDisabled,
|
|
1777
|
-
excludeValueIfReadonly: child.excludeValueIfReadonly ?? container.excludeValueIfReadonly,
|
|
1778
|
-
};
|
|
1779
|
-
}
|
|
1780
|
-
function flattenFields(fields, registry, options = {}) {
|
|
1781
|
-
const result = [];
|
|
1782
|
-
let autoKeyCounter = 0;
|
|
1783
|
-
for (const field of fields) {
|
|
1784
|
-
const valueHandling = getFieldValueHandling(field.type, registry);
|
|
1785
|
-
if (options.preserveRows && (isRowField(field) || isGenericContainerField(field))) {
|
|
1786
|
-
if (field.fields) {
|
|
1787
|
-
const flattenedChildren = flattenFields(normalizeFieldsArray(field.fields), registry, options);
|
|
1788
|
-
const autoPrefix = isRowField(field) ? 'auto_row' : 'auto_container';
|
|
1789
|
-
result.push({
|
|
1790
|
-
...field,
|
|
1791
|
-
fields: flattenedChildren,
|
|
1792
|
-
key: field.key || `${autoPrefix}_${autoKeyCounter++}`,
|
|
1793
|
-
});
|
|
1794
|
-
}
|
|
1795
|
-
}
|
|
1796
|
-
else if (valueHandling === 'flatten' && 'fields' in field) {
|
|
1797
|
-
if (field.fields) {
|
|
1798
|
-
const fields = field.fields;
|
|
1799
|
-
const flattenedChildren = flattenFields(normalizeFieldsArray(fields), registry, options);
|
|
1800
|
-
result.push(...flattenedChildren.map((child) => inheritContainerExclusion(field, child)));
|
|
1801
|
-
}
|
|
1802
|
-
}
|
|
1803
|
-
else if (isGroupField(field)) {
|
|
1804
|
-
const childFieldsArray = Object.values(field.fields);
|
|
1805
|
-
const flattenedChildren = flattenFields(childFieldsArray, registry, options);
|
|
1806
|
-
result.push({
|
|
1807
|
-
...field,
|
|
1808
|
-
fields: flattenedChildren,
|
|
1809
|
-
key: field.key || `auto_group_${autoKeyCounter++}`,
|
|
1810
|
-
});
|
|
1811
|
-
}
|
|
1812
|
-
else if (isArrayField(field)) {
|
|
1813
|
-
const flattenedItemTemplates = field.fields.map((itemDef) => {
|
|
1814
|
-
if (!Array.isArray(itemDef)) {
|
|
1815
|
-
const flattened = flattenFields([itemDef], registry, options);
|
|
1816
|
-
return flattened[0];
|
|
1817
|
-
}
|
|
1818
|
-
return flattenFields([...itemDef], registry, options);
|
|
1819
|
-
});
|
|
1820
|
-
result.push({
|
|
1821
|
-
...field,
|
|
1822
|
-
fields: flattenedItemTemplates,
|
|
1823
|
-
key: field.key || `auto_array_${autoKeyCounter++}`,
|
|
1824
|
-
});
|
|
1825
|
-
}
|
|
1826
|
-
else {
|
|
1827
|
-
const key = field.key || `auto_field_${autoKeyCounter++}`;
|
|
1828
|
-
result.push({
|
|
1829
|
-
...field,
|
|
1830
|
-
key,
|
|
1831
|
-
});
|
|
1832
|
-
}
|
|
1833
|
-
}
|
|
1834
|
-
return result;
|
|
1835
|
-
}
|
|
1836
|
-
|
|
1837
1027
|
function createContainerFieldProcessors() {
|
|
1838
1028
|
const flattenCache = new WeakMap();
|
|
1839
1029
|
const memoizedFlattenFields = (fields, registry) => {
|
|
@@ -2272,9 +1462,14 @@ const COMPONENT_CACHE = new InjectionToken('COMPONENT_CACHE', {
|
|
|
2272
1462
|
providedIn: 'root',
|
|
2273
1463
|
factory: () => new Map(),
|
|
2274
1464
|
});
|
|
1465
|
+
const COMPONENT_LOAD_CACHE = new InjectionToken('COMPONENT_LOAD_CACHE', {
|
|
1466
|
+
providedIn: 'root',
|
|
1467
|
+
factory: () => new Map(),
|
|
1468
|
+
});
|
|
2275
1469
|
function injectFieldRegistry() {
|
|
2276
1470
|
const registry = inject(FIELD_REGISTRY);
|
|
2277
1471
|
const componentCache = inject(COMPONENT_CACHE);
|
|
1472
|
+
const componentLoadCache = inject(COMPONENT_LOAD_CACHE);
|
|
2278
1473
|
return {
|
|
2279
1474
|
getType(name) {
|
|
2280
1475
|
return registry.get(name);
|
|
@@ -2294,12 +1489,26 @@ function injectFieldRegistry() {
|
|
|
2294
1489
|
if (cached) {
|
|
2295
1490
|
return cached;
|
|
2296
1491
|
}
|
|
1492
|
+
const pending = componentLoadCache.get(name);
|
|
1493
|
+
if (pending) {
|
|
1494
|
+
return pending;
|
|
1495
|
+
}
|
|
2297
1496
|
try {
|
|
2298
|
-
const
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
1497
|
+
const load = fieldType
|
|
1498
|
+
.loadComponent()
|
|
1499
|
+
.then(resolveDefaultExport)
|
|
1500
|
+
.then((component) => {
|
|
1501
|
+
if (component) {
|
|
1502
|
+
componentCache.set(name, component);
|
|
1503
|
+
}
|
|
1504
|
+
return component;
|
|
1505
|
+
})
|
|
1506
|
+
.catch((error) => {
|
|
1507
|
+
throw new DynamicFormError(`Failed to load component for field type "${name}": ${error}`);
|
|
1508
|
+
})
|
|
1509
|
+
.finally(() => componentLoadCache.delete(name));
|
|
1510
|
+
componentLoadCache.set(name, load);
|
|
1511
|
+
return load;
|
|
2303
1512
|
}
|
|
2304
1513
|
catch (error) {
|
|
2305
1514
|
throw new DynamicFormError(`Failed to load component for field type "${name}": ${error}`);
|
|
@@ -2368,7 +1577,9 @@ function validateRegexPattern(pattern, fieldKey, errors) {
|
|
|
2368
1577
|
new RegExp(pattern);
|
|
2369
1578
|
}
|
|
2370
1579
|
catch (e) {
|
|
2371
|
-
|
|
1580
|
+
errors.push(DEV_MODE
|
|
1581
|
+
? `Invalid regex pattern in validator for field '${fieldKey}': '${pattern}' — ${e instanceof Error ? e.message : String(e)}`
|
|
1582
|
+
: `Invalid regex pattern: '${pattern}'`);
|
|
2372
1583
|
}
|
|
2373
1584
|
}
|
|
2374
1585
|
function validateNoDuplicateKeys(allKeys) {
|
|
@@ -2384,7 +1595,9 @@ function validateNoDuplicateKeys(allKeys) {
|
|
|
2384
1595
|
const duplicateList = Array.from(duplicates)
|
|
2385
1596
|
.map((k) => `'${k}'`)
|
|
2386
1597
|
.join(', ');
|
|
2387
|
-
|
|
1598
|
+
throw new DynamicFormError(DEV_MODE
|
|
1599
|
+
? `Duplicate field keys detected: ${duplicateList}. Field keys must be unique within their group scope (the same key may appear inside different groups).`
|
|
1600
|
+
: `Duplicate field keys detected: ${duplicateList}.`);
|
|
2388
1601
|
}
|
|
2389
1602
|
}
|
|
2390
1603
|
function validateNoDomIdCollisions(domIds) {
|
|
@@ -2400,9 +1613,11 @@ function validateNoDomIdCollisions(domIds) {
|
|
|
2400
1613
|
const duplicateList = Array.from(duplicates)
|
|
2401
1614
|
.map((k) => `'${k}'`)
|
|
2402
1615
|
.join(', ');
|
|
2403
|
-
|
|
2404
|
-
`
|
|
2405
|
-
|
|
1616
|
+
throw new DynamicFormError(DEV_MODE
|
|
1617
|
+
? `DOM id collision detected for: ${duplicateList}. Group-nested keys are joined with '_' to form DOM ids ` +
|
|
1618
|
+
`(e.g. group 'foo' + leaf 'bar' renders as id='foo_bar'). A separate top-level key like 'foo_bar' or a different ` +
|
|
1619
|
+
`group/leaf pair that underscores to the same string will produce duplicate ids. Rename one of the colliding keys.`
|
|
1620
|
+
: `DOM id collision detected for: ${duplicateList}.`);
|
|
2406
1621
|
}
|
|
2407
1622
|
}
|
|
2408
1623
|
function validateFieldTypesRegistered(allTypes, registry, logger) {
|
|
@@ -2432,12 +1647,15 @@ function validateFormConfig(fields, registry, logger) {
|
|
|
2432
1647
|
const list = Array.from(new Set(data.dottedKeys))
|
|
2433
1648
|
.map((k) => `'${k}'`)
|
|
2434
1649
|
.join(', ');
|
|
2435
|
-
|
|
2436
|
-
`(
|
|
1650
|
+
throw new DynamicFormError(DEV_MODE
|
|
1651
|
+
? `Field key(s) contain a dot: ${list}. Dotted keys are not supported — express nesting with 'group' fields ` +
|
|
1652
|
+
`(e.g. { type: 'group', key: 'address', fields: [{ type: 'input', key: 'city' }] }).`
|
|
1653
|
+
: `Field key(s) contain a dot: ${list}.`);
|
|
2437
1654
|
}
|
|
2438
1655
|
validateNoDuplicateKeys(data.keys);
|
|
2439
1656
|
validateNoDomIdCollisions(data.domIds);
|
|
2440
|
-
|
|
1657
|
+
if (DEV_MODE)
|
|
1658
|
+
validateFieldTypesRegistered(data.types, registry, logger);
|
|
2441
1659
|
if (data.regexErrors.length > 0) {
|
|
2442
1660
|
throw new DynamicFormError(data.regexErrors.length === 1
|
|
2443
1661
|
? data.regexErrors[0]
|
|
@@ -2652,6 +1870,33 @@ function resolveExclusionConfig(global, form, field) {
|
|
|
2652
1870
|
excludeValueIfReadonly: field?.excludeValueIfReadonly ?? form?.excludeValueIfReadonly ?? global.excludeValueIfReadonly,
|
|
2653
1871
|
};
|
|
2654
1872
|
}
|
|
1873
|
+
function hasEnabledValueExclusion(fields, global, form) {
|
|
1874
|
+
return fields.some((field) => {
|
|
1875
|
+
const resolved = resolveExclusionConfig(global, form, field);
|
|
1876
|
+
if (resolved.excludeValueIfHidden || resolved.excludeValueIfDisabled || resolved.excludeValueIfReadonly) {
|
|
1877
|
+
return true;
|
|
1878
|
+
}
|
|
1879
|
+
return (isGroupField(field) && !!field.fields && hasEnabledValueExclusion(Object.values(field.fields), global, form));
|
|
1880
|
+
});
|
|
1881
|
+
}
|
|
1882
|
+
function filterDeclaredValueShape(rawValue, fields, registry) {
|
|
1883
|
+
const kept = {};
|
|
1884
|
+
for (const field of fields) {
|
|
1885
|
+
const key = field.key;
|
|
1886
|
+
if (!key || !(key in rawValue))
|
|
1887
|
+
continue;
|
|
1888
|
+
const valueHandling = getFieldValueHandling(field.type, registry);
|
|
1889
|
+
if (valueHandling === 'exclude' || valueHandling === 'flatten')
|
|
1890
|
+
continue;
|
|
1891
|
+
const value = rawValue[key];
|
|
1892
|
+
if (isGroupField(field) && field.fields && value !== null && typeof value === 'object' && !Array.isArray(value)) {
|
|
1893
|
+
kept[key] = filterDeclaredValueShape(value, Object.values(field.fields), registry);
|
|
1894
|
+
continue;
|
|
1895
|
+
}
|
|
1896
|
+
kept[key] = value;
|
|
1897
|
+
}
|
|
1898
|
+
return kept;
|
|
1899
|
+
}
|
|
2655
1900
|
function shouldExcludeField(field, fieldState, config) {
|
|
2656
1901
|
const state = fieldState();
|
|
2657
1902
|
const inherited = field.inheritedExclusionState;
|
|
@@ -2669,7 +1914,10 @@ function shouldExcludeField(field, fieldState, config) {
|
|
|
2669
1914
|
}
|
|
2670
1915
|
return false;
|
|
2671
1916
|
}
|
|
2672
|
-
function filterFormValue(rawValue, schemaFields, formTree, registry, globalDefaults, formOptions) {
|
|
1917
|
+
function filterFormValue(rawValue, schemaFields, formTree, registry, globalDefaults, formOptions, exclusionEnabled = hasEnabledValueExclusion(schemaFields, globalDefaults, formOptions)) {
|
|
1918
|
+
if (!exclusionEnabled) {
|
|
1919
|
+
return filterDeclaredValueShape(rawValue, schemaFields, registry);
|
|
1920
|
+
}
|
|
2673
1921
|
const result = {};
|
|
2674
1922
|
for (const field of schemaFields) {
|
|
2675
1923
|
const key = field.key;
|
|
@@ -2705,7 +1953,7 @@ function filterFormValue(rawValue, schemaFields, formTree, registry, globalDefau
|
|
|
2705
1953
|
if (groupValue && typeof groupValue === 'object') {
|
|
2706
1954
|
const childFields = Object.values(field.fields);
|
|
2707
1955
|
const nestedTree = fieldState;
|
|
2708
|
-
result[key] = filterFormValue(groupValue, childFields, nestedTree, registry, globalDefaults, formOptions);
|
|
1956
|
+
result[key] = filterFormValue(groupValue, childFields, nestedTree, registry, globalDefaults, formOptions, true);
|
|
2709
1957
|
}
|
|
2710
1958
|
else {
|
|
2711
1959
|
result[key] = groupValue;
|
|
@@ -3018,7 +2266,7 @@ class FormStateMachine {
|
|
|
3018
2266
|
if (!isTransitioningState(state) || state.phase !== Phase.Restoring) {
|
|
3019
2267
|
return { state, sideEffects: [] };
|
|
3020
2268
|
}
|
|
3021
|
-
|
|
2269
|
+
if (DEV_MODE && !state.pendingFormSetup) {
|
|
3022
2270
|
this.config.logger.warn('handleRestoreComplete: pendingFormSetup was not set — falling back to recomputing. ' +
|
|
3023
2271
|
'This indicates a bug in the transition flow.');
|
|
3024
2272
|
}
|
|
@@ -3081,7 +2329,7 @@ class FormStateMachine {
|
|
|
3081
2329
|
const state = untracked(() => this._state());
|
|
3082
2330
|
if (!isTransitioningState(state) || state.phase !== Phase.Restoring)
|
|
3083
2331
|
return;
|
|
3084
|
-
|
|
2332
|
+
if (DEV_MODE && !state.pendingFormSetup) {
|
|
3085
2333
|
this.config.logger.warn('RestoreValues effect: pendingFormSetup was not set — falling back to recomputing.');
|
|
3086
2334
|
}
|
|
3087
2335
|
const formSetup = state.pendingFormSetup ?? this.config.createFormSetup(state.pendingConfig);
|
|
@@ -3223,13 +2471,6 @@ class FormStateManager {
|
|
|
3223
2471
|
return detectFormMode(config.fields || []);
|
|
3224
2472
|
}, /* @ts-ignore */
|
|
3225
2473
|
...(ngDevMode ? [{ debugName: "formModeDetection" }] : /* istanbul ignore next */ []));
|
|
3226
|
-
formConfigValidation = computed(() => {
|
|
3227
|
-
const config = this.activeConfig();
|
|
3228
|
-
if (!config)
|
|
3229
|
-
return { isValid: true, errors: [], warnings: [] };
|
|
3230
|
-
return FormModeValidator.validateFormConfiguration(config.fields || []);
|
|
3231
|
-
}, /* @ts-ignore */
|
|
3232
|
-
...(ngDevMode ? [{ debugName: "formConfigValidation" }] : /* istanbul ignore next */ []));
|
|
3233
2474
|
effectiveFormOptions = computed(() => {
|
|
3234
2475
|
const config = this.activeConfig();
|
|
3235
2476
|
const configOptions = config?.options || {};
|
|
@@ -3237,6 +2478,8 @@ class FormStateManager {
|
|
|
3237
2478
|
return { ...configOptions, ...inputOptions };
|
|
3238
2479
|
}, /* @ts-ignore */
|
|
3239
2480
|
...(ngDevMode ? [{ debugName: "effectiveFormOptions" }] : /* istanbul ignore next */ []));
|
|
2481
|
+
pendingPageRequest = signal(null, /* @ts-ignore */
|
|
2482
|
+
...(ngDevMode ? [{ debugName: "pendingPageRequest" }] : /* istanbul ignore next */ []));
|
|
3240
2483
|
pageFieldDefinitions = computed(() => {
|
|
3241
2484
|
const config = this.activeConfig();
|
|
3242
2485
|
const mode = this.formModeDetection().mode;
|
|
@@ -3273,22 +2516,71 @@ class FormStateManager {
|
|
|
3273
2516
|
}
|
|
3274
2517
|
return this.createEmptyFormSetup(registry);
|
|
3275
2518
|
}, /* @ts-ignore */
|
|
3276
|
-
...(ngDevMode ? [{ debugName: "formSetup" }] : /* istanbul ignore next */ []));
|
|
2519
|
+
...(ngDevMode ? [{ debugName: "formSetup" }] : /* istanbul ignore next */ []));
|
|
2520
|
+
formSetupConfig = computed(() => {
|
|
2521
|
+
const state = this.machine.state();
|
|
2522
|
+
if (isReadyState(state))
|
|
2523
|
+
return state.config;
|
|
2524
|
+
if (isTransitioningState(state)) {
|
|
2525
|
+
if (state.phase === Phase.Restoring && state.pendingFormSetup) {
|
|
2526
|
+
return state.pendingConfig;
|
|
2527
|
+
}
|
|
2528
|
+
return state.currentConfig;
|
|
2529
|
+
}
|
|
2530
|
+
if (state.type === LifecycleState.Initializing)
|
|
2531
|
+
return state.config;
|
|
2532
|
+
if (state.type === LifecycleState.Uninitialized)
|
|
2533
|
+
return this.activeConfig();
|
|
2534
|
+
return undefined;
|
|
2535
|
+
}, /* @ts-ignore */
|
|
2536
|
+
...(ngDevMode ? [{ debugName: "formSetupConfig" }] : /* istanbul ignore next */ []));
|
|
2537
|
+
activePageState = linkedSignal({ ...(ngDevMode ? { debugName: "activePageState" } : /* istanbul ignore next */ {}), source: this.activeConfig,
|
|
2538
|
+
computation: (config) => ({ config, initialized: false, index: 0 }) });
|
|
3277
2539
|
defaultValues = linkedSignal(() => this.formSetup().defaultValues, /* @ts-ignore */
|
|
3278
2540
|
...(ngDevMode ? [{ debugName: "defaultValues" }] : /* istanbul ignore next */ []));
|
|
3279
2541
|
validKeys = computed(() => {
|
|
3280
2542
|
const schemaFields = this.formSetup().schemaFields;
|
|
3281
2543
|
if (!schemaFields || schemaFields.length === 0)
|
|
3282
2544
|
return undefined;
|
|
3283
|
-
|
|
2545
|
+
const keys = new Set();
|
|
2546
|
+
for (const field of schemaFields) {
|
|
2547
|
+
if (field.key === undefined)
|
|
2548
|
+
continue;
|
|
2549
|
+
keys.add(field.key);
|
|
2550
|
+
}
|
|
2551
|
+
return keys;
|
|
3284
2552
|
}, /* @ts-ignore */
|
|
3285
2553
|
...(ngDevMode ? [{ debugName: "validKeys" }] : /* istanbul ignore next */ []));
|
|
3286
|
-
|
|
2554
|
+
boundValueExclusionEnabled = computed(() => {
|
|
2555
|
+
const setup = this.formSetup();
|
|
2556
|
+
if (!setup.schemaFields || setup.schemaFields.length === 0)
|
|
2557
|
+
return false;
|
|
2558
|
+
const options = this.effectiveFormOptions();
|
|
2559
|
+
return hasEnabledValueExclusion(setup.schemaFields, BOUND_VALUE_EXCLUSION_BASELINE, {
|
|
2560
|
+
excludeValueIfHidden: options.excludeValueIfHidden,
|
|
2561
|
+
excludeValueIfDisabled: options.excludeValueIfDisabled,
|
|
2562
|
+
excludeValueIfReadonly: options.excludeValueIfReadonly,
|
|
2563
|
+
});
|
|
2564
|
+
}, /* @ts-ignore */
|
|
2565
|
+
...(ngDevMode ? [{ debugName: "boundValueExclusionEnabled" }] : /* istanbul ignore next */ []));
|
|
2566
|
+
valueExclusionEnabled = computed(() => {
|
|
3287
2567
|
const setup = this.formSetup();
|
|
3288
2568
|
if (!setup.schemaFields || setup.schemaFields.length === 0)
|
|
2569
|
+
return false;
|
|
2570
|
+
const options = this.effectiveFormOptions();
|
|
2571
|
+
return hasEnabledValueExclusion(setup.schemaFields, this.valueExclusionDefaults, {
|
|
2572
|
+
excludeValueIfHidden: options.excludeValueIfHidden,
|
|
2573
|
+
excludeValueIfDisabled: options.excludeValueIfDisabled,
|
|
2574
|
+
excludeValueIfReadonly: options.excludeValueIfReadonly,
|
|
2575
|
+
});
|
|
2576
|
+
}, /* @ts-ignore */
|
|
2577
|
+
...(ngDevMode ? [{ debugName: "valueExclusionEnabled" }] : /* istanbul ignore next */ []));
|
|
2578
|
+
fieldStateSnapshot = computed(() => {
|
|
2579
|
+
const schemaFields = this.formSetup().schemaFields;
|
|
2580
|
+
if (!this.formSchemaReady() || !this.boundValueExclusionEnabled() || !schemaFields || schemaFields.length === 0)
|
|
3289
2581
|
return {};
|
|
3290
2582
|
const snapshot = {};
|
|
3291
|
-
collectFieldStateSnapshot(
|
|
2583
|
+
collectFieldStateSnapshot(schemaFields, asFieldTreeRecord(this.form()), [], snapshot);
|
|
3292
2584
|
return snapshot;
|
|
3293
2585
|
}, /* @ts-ignore */
|
|
3294
2586
|
...(ngDevMode ? [{ debugName: "fieldStateSnapshot" }] : /* istanbul ignore next */ []));
|
|
@@ -3296,8 +2588,22 @@ class FormStateManager {
|
|
|
3296
2588
|
...(ngDevMode ? [{ debugName: "excludedValueStore" }] : /* istanbul ignore next */ []));
|
|
3297
2589
|
prevFieldStateSnapshot = signal({}, /* @ts-ignore */
|
|
3298
2590
|
...(ngDevMode ? [{ debugName: "prevFieldStateSnapshot" }] : /* istanbul ignore next */ []));
|
|
2591
|
+
lastOutwardValue = { current: undefined, pending: false };
|
|
2592
|
+
lastExternalValue = { current: undefined };
|
|
2593
|
+
externalValue = computed(() => {
|
|
2594
|
+
const incoming = this.deps.value();
|
|
2595
|
+
if (this.lastOutwardValue.pending && incoming === this.lastOutwardValue.current) {
|
|
2596
|
+
this.lastOutwardValue.pending = false;
|
|
2597
|
+
return this.lastExternalValue.current;
|
|
2598
|
+
}
|
|
2599
|
+
this.lastOutwardValue.pending = false;
|
|
2600
|
+
this.lastExternalValue.current = incoming;
|
|
2601
|
+
return incoming;
|
|
2602
|
+
}, /* @ts-ignore */
|
|
2603
|
+
...(ngDevMode ? [{ debugName: "externalValue" }] : /* istanbul ignore next */ []));
|
|
3299
2604
|
entity = linkedSignal(() => {
|
|
3300
|
-
|
|
2605
|
+
this.externalValue();
|
|
2606
|
+
const inputValue = untracked(() => this.deps.value());
|
|
3301
2607
|
const defaults = this.defaultValues();
|
|
3302
2608
|
const keys = this.validKeys();
|
|
3303
2609
|
const saved = this.excludedValueStore();
|
|
@@ -3317,22 +2623,62 @@ class FormStateManager {
|
|
|
3317
2623
|
debugName: 'FormStateManager.entity',
|
|
3318
2624
|
equal: isEqual,
|
|
3319
2625
|
});
|
|
3320
|
-
|
|
2626
|
+
loadFormSchemaService = injectAsync(() => import('./ng-forge-dynamic-forms-form-schema.service-C3cVSmft.mjs'));
|
|
2627
|
+
formSchemaRequest = computed(() => {
|
|
3321
2628
|
const setup = this.formSetup();
|
|
3322
2629
|
const config = this.activeConfig();
|
|
3323
|
-
if (!config)
|
|
2630
|
+
if (!config || (!(setup.schemaFields?.length > 0) && !config.schema))
|
|
2631
|
+
return undefined;
|
|
2632
|
+
return {
|
|
2633
|
+
fields: setup.schemaFields ?? [],
|
|
2634
|
+
registry: setup.registry,
|
|
2635
|
+
formLevelSchema: config.schema,
|
|
2636
|
+
validateWhenHidden: this.effectiveFormOptions().validateWhenHidden,
|
|
2637
|
+
};
|
|
2638
|
+
}, /* @ts-ignore */
|
|
2639
|
+
...(ngDevMode ? [{ debugName: "formSchemaRequest" }] : /* istanbul ignore next */ []));
|
|
2640
|
+
formSchemaResource = resource({ ...(ngDevMode ? { debugName: "formSchemaResource" } : /* istanbul ignore next */ {}), params: () => this.formSchemaRequest(),
|
|
2641
|
+
loader: async ({ params }) => {
|
|
2642
|
+
const schemaService = await this.loadFormSchemaService();
|
|
2643
|
+
return runInInjectionContext(this.injector, () => schemaService.create(params));
|
|
2644
|
+
} });
|
|
2645
|
+
schemaCache = { current: undefined };
|
|
2646
|
+
trackSchemaCache = computed(() => {
|
|
2647
|
+
const request = this.formSchemaRequest();
|
|
2648
|
+
if (!request) {
|
|
2649
|
+
this.schemaCache.current = undefined;
|
|
3324
2650
|
return undefined;
|
|
3325
|
-
if (setup.schemaFields?.length) {
|
|
3326
|
-
return createSchemaFromFields(setup.schemaFields, setup.registry, {
|
|
3327
|
-
formLevelSchema: config.schema,
|
|
3328
|
-
validateWhenHidden: this.effectiveFormOptions().validateWhenHidden,
|
|
3329
|
-
});
|
|
3330
2651
|
}
|
|
3331
|
-
if (
|
|
3332
|
-
|
|
2652
|
+
if (this.formSchemaResource.status() === 'error') {
|
|
2653
|
+
this.schemaCache.current = undefined;
|
|
2654
|
+
return undefined;
|
|
3333
2655
|
}
|
|
3334
|
-
|
|
3335
|
-
|
|
2656
|
+
if (this.formSchemaResource.hasValue()) {
|
|
2657
|
+
this.schemaCache.current = { schema: this.formSchemaResource.value(), request };
|
|
2658
|
+
}
|
|
2659
|
+
return this.schemaCache.current;
|
|
2660
|
+
}, /* @ts-ignore */
|
|
2661
|
+
...(ngDevMode ? [{ debugName: "trackSchemaCache" }] : /* istanbul ignore next */ []));
|
|
2662
|
+
isSchemaCurrent = computed(() => {
|
|
2663
|
+
if (this.formSetupConfig() !== this.activeConfig())
|
|
2664
|
+
return false;
|
|
2665
|
+
const request = this.formSchemaRequest();
|
|
2666
|
+
if (!request)
|
|
2667
|
+
return true;
|
|
2668
|
+
return this.trackSchemaCache()?.request === request;
|
|
2669
|
+
}, /* @ts-ignore */
|
|
2670
|
+
...(ngDevMode ? [{ debugName: "isSchemaCurrent" }] : /* istanbul ignore next */ []));
|
|
2671
|
+
formSchemaReady = computed(() => {
|
|
2672
|
+
if (this.formSetupConfig() !== this.activeConfig())
|
|
2673
|
+
return false;
|
|
2674
|
+
if (!this.formSchemaRequest())
|
|
2675
|
+
return true;
|
|
2676
|
+
if (this.formSchemaResource.status() === 'error')
|
|
2677
|
+
return true;
|
|
2678
|
+
return this.trackSchemaCache() !== undefined;
|
|
2679
|
+
}, /* @ts-ignore */
|
|
2680
|
+
...(ngDevMode ? [{ debugName: "formSchemaReady" }] : /* istanbul ignore next */ []));
|
|
2681
|
+
formSchema = computed(() => this.trackSchemaCache()?.schema, /* @ts-ignore */
|
|
3336
2682
|
...(ngDevMode ? [{ debugName: "formSchema" }] : /* istanbul ignore next */ []));
|
|
3337
2683
|
form = computed(() => {
|
|
3338
2684
|
const schema = this.formSchema();
|
|
@@ -3363,13 +2709,16 @@ class FormStateManager {
|
|
|
3363
2709
|
...(ngDevMode ? [{ debugName: "fieldSignalContext" }] : /* istanbul ignore next */ []));
|
|
3364
2710
|
formInstance = computed(() => this.form()(), /* @ts-ignore */
|
|
3365
2711
|
...(ngDevMode ? [{ debugName: "formInstance" }] : /* istanbul ignore next */ []));
|
|
3366
|
-
|
|
2712
|
+
modelValue = computed(() => this.formInstance().value(), /* @ts-ignore */
|
|
2713
|
+
...(ngDevMode ? [{ debugName: "modelValue" }] : /* istanbul ignore next */ []));
|
|
2714
|
+
formValue = computed(() => this.modelValue(), /* @ts-ignore */
|
|
3367
2715
|
...(ngDevMode ? [{ debugName: "formValue" }] : /* istanbul ignore next */ []));
|
|
3368
2716
|
filteredFormValue = computed(() => {
|
|
3369
|
-
const rawValue = this.
|
|
2717
|
+
const rawValue = this.modelValue();
|
|
3370
2718
|
const setup = this.formSetup();
|
|
2719
|
+
const schemaFields = setup.schemaFields;
|
|
3371
2720
|
const options = this.effectiveFormOptions();
|
|
3372
|
-
if (!
|
|
2721
|
+
if (!this.formSchemaReady() || !schemaFields || schemaFields.length === 0) {
|
|
3373
2722
|
return rawValue;
|
|
3374
2723
|
}
|
|
3375
2724
|
const formTree = this.form();
|
|
@@ -3379,14 +2728,15 @@ class FormStateManager {
|
|
|
3379
2728
|
excludeValueIfReadonly: options.excludeValueIfReadonly,
|
|
3380
2729
|
};
|
|
3381
2730
|
const fieldTreeRecord = asFieldTreeRecord(formTree);
|
|
3382
|
-
return filterFormValue(rawValue,
|
|
2731
|
+
return filterFormValue(rawValue, schemaFields, fieldTreeRecord, setup.registry, this.valueExclusionDefaults, formOptions, this.valueExclusionEnabled());
|
|
3383
2732
|
}, /* @ts-ignore */
|
|
3384
2733
|
...(ngDevMode ? [{ debugName: "filteredFormValue" }] : /* istanbul ignore next */ []));
|
|
3385
2734
|
boundFormValue = computed(() => {
|
|
3386
|
-
const rawValue = this.
|
|
2735
|
+
const rawValue = this.modelValue();
|
|
3387
2736
|
const setup = this.formSetup();
|
|
2737
|
+
const schemaFields = setup.schemaFields;
|
|
3388
2738
|
const options = this.effectiveFormOptions();
|
|
3389
|
-
if (!
|
|
2739
|
+
if (!this.formSchemaReady() || !schemaFields || schemaFields.length === 0) {
|
|
3390
2740
|
return rawValue;
|
|
3391
2741
|
}
|
|
3392
2742
|
const formTree = this.form();
|
|
@@ -3396,26 +2746,26 @@ class FormStateManager {
|
|
|
3396
2746
|
excludeValueIfReadonly: options.excludeValueIfReadonly,
|
|
3397
2747
|
};
|
|
3398
2748
|
const fieldTreeRecord = asFieldTreeRecord(formTree);
|
|
3399
|
-
return filterFormValue(rawValue,
|
|
2749
|
+
return filterFormValue(rawValue, schemaFields, fieldTreeRecord, setup.registry, BOUND_VALUE_EXCLUSION_BASELINE, formOptions, this.boundValueExclusionEnabled());
|
|
3400
2750
|
}, /* @ts-ignore */
|
|
3401
2751
|
...(ngDevMode ? [{ debugName: "boundFormValue" }] : /* istanbul ignore next */ []));
|
|
3402
|
-
|
|
2752
|
+
valid = computed(() => this.formSchemaReady() && this.formSchemaResource.status() !== 'error' && this.formInstance().valid(), /* @ts-ignore */
|
|
3403
2753
|
...(ngDevMode ? [{ debugName: "valid" }] : /* istanbul ignore next */ []));
|
|
3404
|
-
invalid = computed(() => this.formInstance().invalid(), /* @ts-ignore */
|
|
2754
|
+
invalid = computed(() => !this.formSchemaReady() || this.formSchemaResource.status() === 'error' || this.formInstance().invalid(), /* @ts-ignore */
|
|
3405
2755
|
...(ngDevMode ? [{ debugName: "invalid" }] : /* istanbul ignore next */ []));
|
|
3406
|
-
dirty = computed(() => this.formInstance().dirty(), /* @ts-ignore */
|
|
2756
|
+
dirty = computed(() => this.formSchemaReady() && this.formInstance().dirty(), /* @ts-ignore */
|
|
3407
2757
|
...(ngDevMode ? [{ debugName: "dirty" }] : /* istanbul ignore next */ []));
|
|
3408
|
-
touched = computed(() => this.formInstance().touched(), /* @ts-ignore */
|
|
2758
|
+
touched = computed(() => this.formSchemaReady() && this.formInstance().touched(), /* @ts-ignore */
|
|
3409
2759
|
...(ngDevMode ? [{ debugName: "touched" }] : /* istanbul ignore next */ []));
|
|
3410
|
-
errors = computed(() => this.formInstance().errors(), /* @ts-ignore */
|
|
2760
|
+
errors = computed(() => (this.formSchemaReady() ? this.formInstance().errors() : []), /* @ts-ignore */
|
|
3411
2761
|
...(ngDevMode ? [{ debugName: "errors" }] : /* istanbul ignore next */ []));
|
|
3412
2762
|
disabled = computed(() => {
|
|
3413
2763
|
const optionsDisabled = this.effectiveFormOptions().disabled;
|
|
3414
|
-
const formDisabled = this.formInstance().disabled();
|
|
2764
|
+
const formDisabled = this.formSchemaReady() && this.formInstance().disabled();
|
|
3415
2765
|
return optionsDisabled ?? formDisabled;
|
|
3416
2766
|
}, /* @ts-ignore */
|
|
3417
2767
|
...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
|
|
3418
|
-
submitting = computed(() => this.formInstance().submitting(), /* @ts-ignore */
|
|
2768
|
+
submitting = computed(() => this.formSchemaReady() && this.formInstance().submitting(), /* @ts-ignore */
|
|
3419
2769
|
...(ngDevMode ? [{ debugName: "submitting" }] : /* istanbul ignore next */ []));
|
|
3420
2770
|
fieldsSource = computed(() => {
|
|
3421
2771
|
const state = this.machine.state();
|
|
@@ -3530,6 +2880,10 @@ class FormStateManager {
|
|
|
3530
2880
|
}), takeUntilDestroyed(this.destroyRef));
|
|
3531
2881
|
this.reset$ = this.eventBus.on('form-reset').pipe(map(() => undefined), takeUntilDestroyed(this.destroyRef));
|
|
3532
2882
|
this.cleared$ = this.eventBus.on('form-clear').pipe(map(() => undefined), takeUntilDestroyed(this.destroyRef));
|
|
2883
|
+
this.eventBus
|
|
2884
|
+
.on('go-to-page')
|
|
2885
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
2886
|
+
.subscribe((event) => this.pendingPageRequest.set({ index: event.pageIndex, options: event.options }));
|
|
3533
2887
|
this.machine = createFormStateMachine({
|
|
3534
2888
|
injector: this.injector,
|
|
3535
2889
|
destroyRef: this.destroyRef,
|
|
@@ -3593,6 +2947,11 @@ class FormStateManager {
|
|
|
3593
2947
|
this.eventBus.dispatch(FormSubmitEvent);
|
|
3594
2948
|
}
|
|
3595
2949
|
setupEffects() {
|
|
2950
|
+
explicitEffect([this.formSchemaResource.error], ([error]) => {
|
|
2951
|
+
if (error) {
|
|
2952
|
+
this.logger.error('[Dynamic Forms] Failed to compile the form schema; rendering without validation and blocking submission.', error);
|
|
2953
|
+
}
|
|
2954
|
+
});
|
|
3596
2955
|
explicitEffect([this.deps.config], ([config]) => {
|
|
3597
2956
|
this.fieldLoadingErrors.set([]);
|
|
3598
2957
|
const state = this.machine.currentState;
|
|
@@ -3644,17 +3003,24 @@ class FormStateManager {
|
|
|
3644
3003
|
explicitEffect([this.boundFormValue], ([currentBound]) => {
|
|
3645
3004
|
const currentValue = this.deps.value();
|
|
3646
3005
|
if (!isEqual(currentBound, currentValue)) {
|
|
3006
|
+
this.lastOutwardValue.current = currentBound;
|
|
3007
|
+
this.lastOutwardValue.pending = true;
|
|
3647
3008
|
this.deps.value.set(currentBound);
|
|
3648
3009
|
}
|
|
3649
3010
|
});
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3011
|
+
if (DEV_MODE) {
|
|
3012
|
+
explicitEffect([this.activeConfig], ([config]) => {
|
|
3013
|
+
if (!config)
|
|
3014
|
+
return;
|
|
3015
|
+
const validation = FormModeValidator.validateFormConfiguration(config.fields || []);
|
|
3016
|
+
if (!validation.isValid) {
|
|
3017
|
+
this.logger.error('Invalid form configuration:', validation.errors);
|
|
3018
|
+
}
|
|
3019
|
+
if (validation.warnings.length > 0) {
|
|
3020
|
+
this.logger.warn('Form configuration warnings:', validation.warnings);
|
|
3021
|
+
}
|
|
3022
|
+
});
|
|
3023
|
+
}
|
|
3658
3024
|
}
|
|
3659
3025
|
setupEventHandlers() {
|
|
3660
3026
|
this.eventBus
|
|
@@ -3672,74 +3038,479 @@ class FormStateManager {
|
|
|
3672
3038
|
this.schemaRegistry.registerSchema(schema);
|
|
3673
3039
|
});
|
|
3674
3040
|
}
|
|
3675
|
-
if (!customFnConfig) {
|
|
3676
|
-
return;
|
|
3041
|
+
if (!customFnConfig) {
|
|
3042
|
+
return;
|
|
3043
|
+
}
|
|
3044
|
+
if (customFnConfig.customFunctions) {
|
|
3045
|
+
Object.entries(customFnConfig.customFunctions).forEach(([name, fn]) => {
|
|
3046
|
+
this.functionRegistry.registerCustomFunction(name, fn);
|
|
3047
|
+
});
|
|
3048
|
+
}
|
|
3049
|
+
if (customFnConfig.derivations) {
|
|
3050
|
+
this.functionRegistry.setDerivationFunctions(customFnConfig.derivations);
|
|
3051
|
+
}
|
|
3052
|
+
if (customFnConfig.asyncDerivations) {
|
|
3053
|
+
this.functionRegistry.setAsyncDerivationFunctions(customFnConfig.asyncDerivations);
|
|
3054
|
+
}
|
|
3055
|
+
if (customFnConfig.asyncConditions) {
|
|
3056
|
+
this.functionRegistry.setAsyncConditionFunctions(customFnConfig.asyncConditions);
|
|
3057
|
+
}
|
|
3058
|
+
this.functionRegistry.setValidators(customFnConfig.validators);
|
|
3059
|
+
this.functionRegistry.setAsyncValidators(customFnConfig.asyncValidators);
|
|
3060
|
+
this.functionRegistry.setHttpValidators(customFnConfig.httpValidators);
|
|
3061
|
+
}
|
|
3062
|
+
createFormSetupFromConfig(fields, mode, registry) {
|
|
3063
|
+
const normalizedFields = normalizeSimplifiedArrays(fields);
|
|
3064
|
+
validateFormConfig(normalizedFields, registry, this.logger);
|
|
3065
|
+
const source = this.deps.source?.() ?? 'inline';
|
|
3066
|
+
const { fields: sanitizedFields, warnings: addonWarnings } = walkAndValidateAddons(normalizedFields, registry, this.addonKindRegistry, source);
|
|
3067
|
+
if (DEV_MODE) {
|
|
3068
|
+
const nextKeys = new Set();
|
|
3069
|
+
for (const w of addonWarnings) {
|
|
3070
|
+
const key = addonWarningKey(w);
|
|
3071
|
+
nextKeys.add(key);
|
|
3072
|
+
if (!this.lastAddonWarningKeys.has(key)) {
|
|
3073
|
+
this.logger.warn(formatAddonWarning(w));
|
|
3074
|
+
}
|
|
3075
|
+
}
|
|
3076
|
+
this.lastAddonWarningKeys = nextKeys;
|
|
3077
|
+
}
|
|
3078
|
+
const validatedFields = sanitizedFields;
|
|
3079
|
+
const flattenedFields = this.fieldProcessors.memoizedFlattenFields(validatedFields, registry);
|
|
3080
|
+
const flattenedFieldsForRendering = this.fieldProcessors.memoizedFlattenFieldsForRendering(validatedFields, registry);
|
|
3081
|
+
const fieldsById = this.fieldProcessors.memoizedKeyBy(flattenedFields);
|
|
3082
|
+
const defaultValues = this.fieldProcessors.memoizedDefaultValues(fieldsById, registry);
|
|
3083
|
+
const fieldsToRender = mode === 'paged' ? [] : flattenedFieldsForRendering;
|
|
3084
|
+
return {
|
|
3085
|
+
fields: fieldsToRender,
|
|
3086
|
+
schemaFields: flattenedFields,
|
|
3087
|
+
originalFields: fields,
|
|
3088
|
+
defaultValues,
|
|
3089
|
+
mode,
|
|
3090
|
+
registry,
|
|
3091
|
+
};
|
|
3092
|
+
}
|
|
3093
|
+
createEmptyFormSetup(registry) {
|
|
3094
|
+
return {
|
|
3095
|
+
fields: [],
|
|
3096
|
+
schemaFields: [],
|
|
3097
|
+
defaultValues: {},
|
|
3098
|
+
mode: 'non-paged',
|
|
3099
|
+
registry,
|
|
3100
|
+
};
|
|
3101
|
+
}
|
|
3102
|
+
ngOnDestroy() {
|
|
3103
|
+
this.machine.dispatch({ type: Action.Destroy });
|
|
3104
|
+
}
|
|
3105
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: FormStateManager, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
3106
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: FormStateManager });
|
|
3107
|
+
}
|
|
3108
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: FormStateManager, decorators: [{
|
|
3109
|
+
type: Injectable
|
|
3110
|
+
}], ctorParameters: () => [] });
|
|
3111
|
+
|
|
3112
|
+
class PageOrchestratorComponent {
|
|
3113
|
+
eventBus = inject(EventBus);
|
|
3114
|
+
fieldContextRegistry = inject(FieldContextRegistryService);
|
|
3115
|
+
functionRegistry = inject(FunctionRegistryService);
|
|
3116
|
+
formOptions = inject(FORM_OPTIONS, { optional: true });
|
|
3117
|
+
globalPreloadWindow = inject(PAGE_PRELOAD_WINDOW);
|
|
3118
|
+
stateManager = inject(FormStateManager, { optional: true });
|
|
3119
|
+
pendingPageRequest = computed(() => this.stateManager?.pendingPageRequest() ?? null, /* @ts-ignore */
|
|
3120
|
+
...(ngDevMode ? [{ debugName: "pendingPageRequest" }] : /* istanbul ignore next */ []));
|
|
3121
|
+
schemaCurrent = computed(() => this.stateManager?.isSchemaCurrent() ?? true, /* @ts-ignore */
|
|
3122
|
+
...(ngDevMode ? [{ debugName: "schemaCurrent" }] : /* istanbul ignore next */ []));
|
|
3123
|
+
configScope = computed(() => this.stateManager?.activeConfig(), /* @ts-ignore */
|
|
3124
|
+
...(ngDevMode ? [{ debugName: "configScope" }] : /* istanbul ignore next */ []));
|
|
3125
|
+
pageFields = input.required(/* @ts-ignore */
|
|
3126
|
+
...(ngDevMode ? [{ debugName: "pageFields" }] : /* istanbul ignore next */ []));
|
|
3127
|
+
form = input.required(/* @ts-ignore */
|
|
3128
|
+
...(ngDevMode ? [{ debugName: "form" }] : /* istanbul ignore next */ []));
|
|
3129
|
+
fieldSignalContext = input.required(/* @ts-ignore */
|
|
3130
|
+
...(ngDevMode ? [{ debugName: "fieldSignalContext" }] : /* istanbul ignore next */ []));
|
|
3131
|
+
pageHiddenStates = computed(() => {
|
|
3132
|
+
const pages = this.pageFields();
|
|
3133
|
+
return pages.map((page) => this.evaluatePageHidden(page));
|
|
3134
|
+
}, /* @ts-ignore */
|
|
3135
|
+
...(ngDevMode ? [{ debugName: "pageHiddenStates" }] : /* istanbul ignore next */ []));
|
|
3136
|
+
visiblePageIndices = computed(() => {
|
|
3137
|
+
const hiddenStates = this.pageHiddenStates();
|
|
3138
|
+
return hiddenStates
|
|
3139
|
+
.map((hidden, index) => ({ index, hidden }))
|
|
3140
|
+
.filter((item) => !item.hidden)
|
|
3141
|
+
.map((item) => item.index);
|
|
3142
|
+
}, /* @ts-ignore */
|
|
3143
|
+
...(ngDevMode ? [{ debugName: "visiblePageIndices" }] : /* istanbul ignore next */ []));
|
|
3144
|
+
activePageOwnership = linkedSignal({ ...(ngDevMode ? { debugName: "activePageOwnership" } : /* istanbul ignore next */ {}), source: this.configScope,
|
|
3145
|
+
computation: (config, previous) => {
|
|
3146
|
+
const totalPages = this.pageFields().length;
|
|
3147
|
+
if (totalPages === 0)
|
|
3148
|
+
return { config, initialized: true, index: 0 };
|
|
3149
|
+
return untracked(() => {
|
|
3150
|
+
const owned = this.stateManager?.activePageState();
|
|
3151
|
+
const mayRestore = previous === undefined || previous.source === config;
|
|
3152
|
+
if (mayRestore &&
|
|
3153
|
+
owned !== undefined &&
|
|
3154
|
+
owned.config === config &&
|
|
3155
|
+
owned.initialized &&
|
|
3156
|
+
owned.index >= 0 &&
|
|
3157
|
+
owned.index < totalPages) {
|
|
3158
|
+
return { config, initialized: true, index: owned.index };
|
|
3159
|
+
}
|
|
3160
|
+
if (this.initialPage().validate && !this.schemaCurrent()) {
|
|
3161
|
+
return { config, initialized: false, index: 0 };
|
|
3162
|
+
}
|
|
3163
|
+
return { config, initialized: true, index: this.resolveInitialLanding() };
|
|
3164
|
+
});
|
|
3165
|
+
} });
|
|
3166
|
+
currentPageIndex = computed(() => this.activePageOwnership().index, /* @ts-ignore */
|
|
3167
|
+
...(ngDevMode ? [{ debugName: "currentPageIndex" }] : /* istanbul ignore next */ []));
|
|
3168
|
+
setCurrentPageIndex(index) {
|
|
3169
|
+
this.activePageOwnership.update((ownership) => ({ ...ownership, initialized: true, index }));
|
|
3170
|
+
}
|
|
3171
|
+
resolveInitialLanding() {
|
|
3172
|
+
const { index, validate } = this.initialPage();
|
|
3173
|
+
const visible = this.visiblePageIndices();
|
|
3174
|
+
if (index === 0 || visible.length === 0)
|
|
3175
|
+
return 0;
|
|
3176
|
+
const target = visible.includes(index) ? index : this.findNearestVisiblePage(index, visible);
|
|
3177
|
+
if (target <= 0)
|
|
3178
|
+
return Math.max(target, 0);
|
|
3179
|
+
if (!validate)
|
|
3180
|
+
return target;
|
|
3181
|
+
const firstInvalid = visible.filter((i) => i < target).find((i) => !this.isPageValid(i));
|
|
3182
|
+
return firstInvalid ?? target;
|
|
3183
|
+
}
|
|
3184
|
+
initialPage = computed(() => {
|
|
3185
|
+
const raw = this.formOptions?.()?.initialPage;
|
|
3186
|
+
const totalPages = this.pageFields().length;
|
|
3187
|
+
const requested = typeof raw === 'number' ? raw : raw?.index;
|
|
3188
|
+
const validate = typeof raw === 'number' ? false : (raw?.validate ?? false);
|
|
3189
|
+
if (typeof requested !== 'number' || !Number.isFinite(requested) || requested < 0 || totalPages === 0) {
|
|
3190
|
+
return { index: 0, validate: false };
|
|
3191
|
+
}
|
|
3192
|
+
return { index: Math.min(Math.trunc(requested), totalPages - 1), validate };
|
|
3193
|
+
}, /* @ts-ignore */
|
|
3194
|
+
...(ngDevMode ? [{ debugName: "initialPage" }] : /* istanbul ignore next */ []));
|
|
3195
|
+
state = computed(() => {
|
|
3196
|
+
const currentIndex = this.currentPageIndex();
|
|
3197
|
+
const totalPages = this.pageFields().length;
|
|
3198
|
+
const visibleIndices = this.visiblePageIndices();
|
|
3199
|
+
const currentVisiblePosition = visibleIndices.indexOf(currentIndex);
|
|
3200
|
+
const isFirstVisiblePage = currentVisiblePosition === 0 || currentVisiblePosition === -1;
|
|
3201
|
+
const isLastVisiblePage = currentVisiblePosition >= visibleIndices.length - 1;
|
|
3202
|
+
return {
|
|
3203
|
+
currentPageIndex: currentIndex,
|
|
3204
|
+
totalPages,
|
|
3205
|
+
isFirstPage: isFirstVisiblePage,
|
|
3206
|
+
isLastPage: isLastVisiblePage,
|
|
3207
|
+
};
|
|
3208
|
+
}, /* @ts-ignore */
|
|
3209
|
+
...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
|
|
3210
|
+
currentPageValid = computed(() => (this.stateManager?.isSchemaCurrent() ?? true) && this.isPageValid(this.currentPageIndex()), /* @ts-ignore */
|
|
3211
|
+
...(ngDevMode ? [{ debugName: "currentPageValid" }] : /* istanbul ignore next */ []));
|
|
3212
|
+
isPageValid(pageIndex) {
|
|
3213
|
+
const pages = this.pageFields();
|
|
3214
|
+
const form = this.form();
|
|
3215
|
+
if (pageIndex < 0 || pageIndex >= pages.length) {
|
|
3216
|
+
return true;
|
|
3217
|
+
}
|
|
3218
|
+
const leafKeys = collectLeafFieldKeys(pages[pageIndex].fields || []);
|
|
3219
|
+
for (const fieldKey of leafKeys) {
|
|
3220
|
+
const field = form[fieldKey];
|
|
3221
|
+
if (field && typeof field === 'function') {
|
|
3222
|
+
const fieldState = field();
|
|
3223
|
+
if (fieldState && typeof fieldState.valid === 'function' && !fieldState.valid()) {
|
|
3224
|
+
return false;
|
|
3225
|
+
}
|
|
3226
|
+
}
|
|
3227
|
+
}
|
|
3228
|
+
return true;
|
|
3229
|
+
}
|
|
3230
|
+
extendedFieldSignalContext = computed(() => ({
|
|
3231
|
+
...this.fieldSignalContext(),
|
|
3232
|
+
currentPageValid: this.currentPageValid,
|
|
3233
|
+
}), /* @ts-ignore */
|
|
3234
|
+
...(ngDevMode ? [{ debugName: "extendedFieldSignalContext" }] : /* istanbul ignore next */ []));
|
|
3235
|
+
preloadWindow = computed(() => {
|
|
3236
|
+
const perForm = this.formOptions?.()?.pagePreloadWindow;
|
|
3237
|
+
return clampWindowSize(perForm, this.globalPreloadWindow);
|
|
3238
|
+
}, /* @ts-ignore */
|
|
3239
|
+
...(ngDevMode ? [{ debugName: "preloadWindow" }] : /* istanbul ignore next */ []));
|
|
3240
|
+
pageRenderStates = computed(() => {
|
|
3241
|
+
const activeIndex = this.currentPageIndex();
|
|
3242
|
+
const hiddenStates = this.pageHiddenStates();
|
|
3243
|
+
const preloadWindow = this.preloadWindow();
|
|
3244
|
+
return this.pageFields().map((field, index) => ({
|
|
3245
|
+
field,
|
|
3246
|
+
index,
|
|
3247
|
+
hidden: hiddenStates[index],
|
|
3248
|
+
active: index === activeIndex,
|
|
3249
|
+
preload: index !== activeIndex && Math.abs(index - activeIndex) <= preloadWindow,
|
|
3250
|
+
}));
|
|
3251
|
+
}, /* @ts-ignore */
|
|
3252
|
+
...(ngDevMode ? [{ debugName: "pageRenderStates" }] : /* istanbul ignore next */ []));
|
|
3253
|
+
constructor() {
|
|
3254
|
+
this.setupEventListeners();
|
|
3255
|
+
explicitEffect([this.schemaCurrent, this.pageFields], ([schemaCurrent]) => {
|
|
3256
|
+
const ownership = this.activePageOwnership();
|
|
3257
|
+
if (!schemaCurrent || ownership.initialized)
|
|
3258
|
+
return;
|
|
3259
|
+
this.activePageOwnership.set({ ...ownership, initialized: true, index: this.resolveInitialLanding() });
|
|
3260
|
+
});
|
|
3261
|
+
explicitEffect([this.state, this.visiblePageIndices], ([state, visibleIndices]) => {
|
|
3262
|
+
const currentVisiblePosition = visibleIndices.indexOf(state.currentPageIndex);
|
|
3263
|
+
if (currentVisiblePosition === -1 && visibleIndices.length > 0) {
|
|
3264
|
+
const nearest = this.findNearestVisiblePage(state.currentPageIndex, visibleIndices);
|
|
3265
|
+
if (nearest !== -1) {
|
|
3266
|
+
this.executePageChange(nearest);
|
|
3267
|
+
}
|
|
3268
|
+
}
|
|
3269
|
+
});
|
|
3270
|
+
}
|
|
3271
|
+
navigateToNextPage() {
|
|
3272
|
+
const disableWhenPageInvalid = this.formOptions?.()?.nextButton?.disableWhenPageInvalid ?? true;
|
|
3273
|
+
if (disableWhenPageInvalid && !this.currentPageValid()) {
|
|
3274
|
+
return {
|
|
3275
|
+
success: false,
|
|
3276
|
+
newPageIndex: this.state().currentPageIndex,
|
|
3277
|
+
error: 'Current page has invalid fields',
|
|
3278
|
+
};
|
|
3279
|
+
}
|
|
3280
|
+
const currentState = this.state();
|
|
3281
|
+
const visibleIndices = this.visiblePageIndices();
|
|
3282
|
+
if (currentState.isLastPage) {
|
|
3283
|
+
return {
|
|
3284
|
+
success: false,
|
|
3285
|
+
newPageIndex: currentState.currentPageIndex,
|
|
3286
|
+
error: 'Already on the last visible page',
|
|
3287
|
+
};
|
|
3288
|
+
}
|
|
3289
|
+
const currentVisiblePosition = visibleIndices.indexOf(currentState.currentPageIndex);
|
|
3290
|
+
if (currentVisiblePosition === -1 || currentVisiblePosition >= visibleIndices.length - 1) {
|
|
3291
|
+
return {
|
|
3292
|
+
success: false,
|
|
3293
|
+
newPageIndex: currentState.currentPageIndex,
|
|
3294
|
+
error: 'No next visible page available',
|
|
3295
|
+
};
|
|
3296
|
+
}
|
|
3297
|
+
const nextVisiblePageIndex = visibleIndices[currentVisiblePosition + 1];
|
|
3298
|
+
return this.executePageChange(nextVisiblePageIndex);
|
|
3299
|
+
}
|
|
3300
|
+
navigateToPreviousPage() {
|
|
3301
|
+
const currentState = this.state();
|
|
3302
|
+
const visibleIndices = this.visiblePageIndices();
|
|
3303
|
+
if (currentState.isFirstPage) {
|
|
3304
|
+
return {
|
|
3305
|
+
success: false,
|
|
3306
|
+
newPageIndex: currentState.currentPageIndex,
|
|
3307
|
+
error: 'Already on the first visible page',
|
|
3308
|
+
};
|
|
3677
3309
|
}
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3310
|
+
const currentVisiblePosition = visibleIndices.indexOf(currentState.currentPageIndex);
|
|
3311
|
+
if (currentVisiblePosition <= 0) {
|
|
3312
|
+
return {
|
|
3313
|
+
success: false,
|
|
3314
|
+
newPageIndex: currentState.currentPageIndex,
|
|
3315
|
+
error: 'No previous visible page available',
|
|
3316
|
+
};
|
|
3682
3317
|
}
|
|
3683
|
-
|
|
3684
|
-
|
|
3318
|
+
const prevVisiblePageIndex = visibleIndices[currentVisiblePosition - 1];
|
|
3319
|
+
return this.executePageChange(prevVisiblePageIndex);
|
|
3320
|
+
}
|
|
3321
|
+
navigateToPage(pageIndex, options) {
|
|
3322
|
+
const currentIndex = this.state().currentPageIndex;
|
|
3323
|
+
const visibleIndices = this.visiblePageIndices();
|
|
3324
|
+
if (pageIndex <= currentIndex || !visibleIndices.includes(pageIndex)) {
|
|
3325
|
+
return this.executePageChange(pageIndex);
|
|
3685
3326
|
}
|
|
3686
|
-
|
|
3687
|
-
this.
|
|
3327
|
+
if (options?.validate === false) {
|
|
3328
|
+
return this.executePageChange(pageIndex);
|
|
3688
3329
|
}
|
|
3689
|
-
|
|
3690
|
-
|
|
3330
|
+
const disableWhenPageInvalid = this.formOptions?.()?.nextButton?.disableWhenPageInvalid ?? true;
|
|
3331
|
+
if (!disableWhenPageInvalid) {
|
|
3332
|
+
return this.executePageChange(pageIndex);
|
|
3691
3333
|
}
|
|
3692
|
-
|
|
3693
|
-
this.
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
createFormSetupFromConfig(fields, mode, registry) {
|
|
3697
|
-
const normalizedFields = normalizeSimplifiedArrays(fields);
|
|
3698
|
-
validateFormConfig(normalizedFields, registry, this.logger);
|
|
3699
|
-
const source = this.deps.source?.() ?? 'inline';
|
|
3700
|
-
const { fields: sanitizedFields, warnings: addonWarnings } = walkAndValidateAddons(normalizedFields, registry, this.addonKindRegistry, source);
|
|
3701
|
-
const nextKeys = new Set();
|
|
3702
|
-
for (const w of addonWarnings) {
|
|
3703
|
-
const key = addonWarningKey(w);
|
|
3704
|
-
nextKeys.add(key);
|
|
3705
|
-
if (!this.lastAddonWarningKeys.has(key)) {
|
|
3706
|
-
this.logger.warn(formatAddonWarning(w));
|
|
3707
|
-
}
|
|
3334
|
+
const crossedPages = visibleIndices.filter((index) => index >= currentIndex && index < pageIndex);
|
|
3335
|
+
const firstInvalidPage = crossedPages.find((index) => !this.isPageValid(index));
|
|
3336
|
+
if (firstInvalidPage === undefined) {
|
|
3337
|
+
return this.executePageChange(pageIndex);
|
|
3708
3338
|
}
|
|
3709
|
-
|
|
3710
|
-
const validatedFields = sanitizedFields;
|
|
3711
|
-
const flattenedFields = this.fieldProcessors.memoizedFlattenFields(validatedFields, registry);
|
|
3712
|
-
const flattenedFieldsForRendering = this.fieldProcessors.memoizedFlattenFieldsForRendering(validatedFields, registry);
|
|
3713
|
-
const fieldsById = this.fieldProcessors.memoizedKeyBy(flattenedFields);
|
|
3714
|
-
const defaultValues = this.fieldProcessors.memoizedDefaultValues(fieldsById, registry);
|
|
3715
|
-
const fieldsToRender = mode === 'paged' ? [] : flattenedFieldsForRendering;
|
|
3339
|
+
this.executePageChange(firstInvalidPage);
|
|
3716
3340
|
return {
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
defaultValues,
|
|
3721
|
-
mode,
|
|
3722
|
-
registry,
|
|
3341
|
+
success: false,
|
|
3342
|
+
newPageIndex: firstInvalidPage,
|
|
3343
|
+
error: `Cannot navigate to page ${pageIndex}: page ${firstInvalidPage} has invalid fields`,
|
|
3723
3344
|
};
|
|
3724
3345
|
}
|
|
3725
|
-
|
|
3346
|
+
executePageChange(pageIndex) {
|
|
3347
|
+
const currentState = this.state();
|
|
3348
|
+
const totalPages = currentState.totalPages;
|
|
3349
|
+
if (pageIndex < 0 || pageIndex >= totalPages) {
|
|
3350
|
+
return {
|
|
3351
|
+
success: false,
|
|
3352
|
+
newPageIndex: currentState.currentPageIndex,
|
|
3353
|
+
error: `Invalid page index: ${pageIndex}. Valid range is 0 to ${totalPages - 1}`,
|
|
3354
|
+
};
|
|
3355
|
+
}
|
|
3356
|
+
const visibleIndices = this.visiblePageIndices();
|
|
3357
|
+
if (!visibleIndices.includes(pageIndex)) {
|
|
3358
|
+
return {
|
|
3359
|
+
success: false,
|
|
3360
|
+
newPageIndex: currentState.currentPageIndex,
|
|
3361
|
+
error: `Cannot navigate to hidden page at index ${pageIndex}. Visible pages: [${visibleIndices.join(', ')}]`,
|
|
3362
|
+
};
|
|
3363
|
+
}
|
|
3364
|
+
if (pageIndex === currentState.currentPageIndex) {
|
|
3365
|
+
return {
|
|
3366
|
+
success: true,
|
|
3367
|
+
newPageIndex: pageIndex,
|
|
3368
|
+
};
|
|
3369
|
+
}
|
|
3370
|
+
const previousIndex = currentState.currentPageIndex;
|
|
3371
|
+
this.setCurrentPageIndex(pageIndex);
|
|
3372
|
+
this.eventBus.dispatch(PageChangeEvent, pageIndex, totalPages, previousIndex);
|
|
3726
3373
|
return {
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
defaultValues: {},
|
|
3730
|
-
mode: 'non-paged',
|
|
3731
|
-
registry,
|
|
3374
|
+
success: true,
|
|
3375
|
+
newPageIndex: pageIndex,
|
|
3732
3376
|
};
|
|
3733
3377
|
}
|
|
3734
|
-
|
|
3735
|
-
|
|
3378
|
+
findNearestVisiblePage(currentIndex, visibleIndices) {
|
|
3379
|
+
let nearest = -1;
|
|
3380
|
+
let minDistance = Infinity;
|
|
3381
|
+
for (const idx of visibleIndices) {
|
|
3382
|
+
const distance = Math.abs(idx - currentIndex);
|
|
3383
|
+
if (distance < minDistance || (distance === minDistance && idx > nearest)) {
|
|
3384
|
+
minDistance = distance;
|
|
3385
|
+
nearest = idx;
|
|
3386
|
+
}
|
|
3387
|
+
}
|
|
3388
|
+
return nearest;
|
|
3736
3389
|
}
|
|
3737
|
-
|
|
3738
|
-
|
|
3390
|
+
setupEventListeners() {
|
|
3391
|
+
this.eventBus
|
|
3392
|
+
.on('next-page')
|
|
3393
|
+
.pipe(takeUntilDestroyed())
|
|
3394
|
+
.subscribe(() => {
|
|
3395
|
+
this.navigateToNextPage();
|
|
3396
|
+
});
|
|
3397
|
+
this.eventBus
|
|
3398
|
+
.on('previous-page')
|
|
3399
|
+
.pipe(takeUntilDestroyed())
|
|
3400
|
+
.subscribe(() => {
|
|
3401
|
+
this.navigateToPreviousPage();
|
|
3402
|
+
});
|
|
3403
|
+
explicitEffect([this.pendingPageRequest], ([request]) => {
|
|
3404
|
+
if (!request)
|
|
3405
|
+
return;
|
|
3406
|
+
this.stateManager?.pendingPageRequest.set(null);
|
|
3407
|
+
this.navigateToPage(request.index, request.options);
|
|
3408
|
+
});
|
|
3409
|
+
explicitEffect([this.state], ([state]) => this.eventBus.dispatch(PagerStateEvent, state));
|
|
3410
|
+
explicitEffect([this.activePageOwnership], ([ownership]) => this.stateManager?.activePageState.set(ownership));
|
|
3411
|
+
}
|
|
3412
|
+
evaluatePageHidden(page) {
|
|
3413
|
+
if (!page.logic || page.logic.length === 0) {
|
|
3414
|
+
return false;
|
|
3415
|
+
}
|
|
3416
|
+
const hiddenLogic = page.logic.filter((l) => l.type === 'hidden');
|
|
3417
|
+
if (hiddenLogic.length === 0) {
|
|
3418
|
+
return false;
|
|
3419
|
+
}
|
|
3420
|
+
for (const logic of hiddenLogic) {
|
|
3421
|
+
if (typeof logic.condition === 'boolean') {
|
|
3422
|
+
if (logic.condition) {
|
|
3423
|
+
return true;
|
|
3424
|
+
}
|
|
3425
|
+
continue;
|
|
3426
|
+
}
|
|
3427
|
+
const condition = logic.condition;
|
|
3428
|
+
const context = this.fieldContextRegistry.createDisplayOnlyContext(page.key || '', this.functionRegistry.getCustomFunctions());
|
|
3429
|
+
if (evaluateCondition(condition, context)) {
|
|
3430
|
+
return true;
|
|
3431
|
+
}
|
|
3432
|
+
}
|
|
3433
|
+
return false;
|
|
3434
|
+
}
|
|
3435
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: PageOrchestratorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3436
|
+
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: [
|
|
3437
|
+
{
|
|
3438
|
+
provide: FIELD_SIGNAL_CONTEXT,
|
|
3439
|
+
useFactory: (orchestrator) => orchestrator.extendedFieldSignalContext(),
|
|
3440
|
+
deps: [PageOrchestratorComponent],
|
|
3441
|
+
},
|
|
3442
|
+
], ngImport: i0, template: `
|
|
3443
|
+
@for (page of pageRenderStates(); track page.field.key) {
|
|
3444
|
+
<!--
|
|
3445
|
+
Skip pages hidden by 'hidden' logic conditions entirely. FormStateManager
|
|
3446
|
+
derives schema/value/validators from config (not from mounted components),
|
|
3447
|
+
so unmounting a hidden page has no effect on form state — only on render +
|
|
3448
|
+
CD cost. This is the page-level equivalent of the existing field-level
|
|
3449
|
+
\`@if (!field.hidden())\` gate in page-field.component.ts.
|
|
3450
|
+
-->
|
|
3451
|
+
@if (!page.hidden) {
|
|
3452
|
+
@if (page.active || page.preload) {
|
|
3453
|
+
@defer (when page.active; on idle) {
|
|
3454
|
+
<section page-field [field]="page.field" [key]="page.field.key" [pageIndex]="page.index" [isVisible]="page.active"></section>
|
|
3455
|
+
} @placeholder {
|
|
3456
|
+
<div class="df-page-placeholder" [attr.data-page-index]="page.index" [attr.data-page-key]="page.field.key"></div>
|
|
3457
|
+
}
|
|
3458
|
+
} @else {
|
|
3459
|
+
<!--
|
|
3460
|
+
Pages outside the preload window remain lightweight placeholders.
|
|
3461
|
+
Form state derives from config, so leaving their components unmounted
|
|
3462
|
+
does not affect validity, derivations, or navigation. The active page
|
|
3463
|
+
renders directly; configured neighbours preload declaratively on idle.
|
|
3464
|
+
-->
|
|
3465
|
+
<div class="df-page-placeholder" [attr.data-page-index]="page.index" [attr.data-page-key]="page.field.key"></div>
|
|
3466
|
+
}
|
|
3467
|
+
}
|
|
3468
|
+
}
|
|
3469
|
+
`, isInline: true, styles: [":host{display:block;position:relative;width:100%}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, deferBlockDependencies: [() => [/* @ts-ignore */
|
|
3470
|
+
import('./ng-forge-dynamic-forms-page-field.component-D16ey8r-.mjs').then(m => m.default)]] });
|
|
3739
3471
|
}
|
|
3740
|
-
i0.ɵɵ
|
|
3741
|
-
|
|
3742
|
-
|
|
3472
|
+
i0.ɵɵngDeclareClassMetadataAsync({ minVersion: "18.0.0", version: "22.0.1", ngImport: i0, type: PageOrchestratorComponent, resolveDeferredDeps: () => [/* @ts-ignore */
|
|
3473
|
+
import('./ng-forge-dynamic-forms-page-field.component-D16ey8r-.mjs').then(m => m.default)], resolveMetadata: PageFieldComponent => ({ decorators: [{
|
|
3474
|
+
type: Component,
|
|
3475
|
+
args: [{ selector: 'div[page-orchestrator]', imports: [PageFieldComponent], template: `
|
|
3476
|
+
@for (page of pageRenderStates(); track page.field.key) {
|
|
3477
|
+
<!--
|
|
3478
|
+
Skip pages hidden by 'hidden' logic conditions entirely. FormStateManager
|
|
3479
|
+
derives schema/value/validators from config (not from mounted components),
|
|
3480
|
+
so unmounting a hidden page has no effect on form state — only on render +
|
|
3481
|
+
CD cost. This is the page-level equivalent of the existing field-level
|
|
3482
|
+
\`@if (!field.hidden())\` gate in page-field.component.ts.
|
|
3483
|
+
-->
|
|
3484
|
+
@if (!page.hidden) {
|
|
3485
|
+
@if (page.active || page.preload) {
|
|
3486
|
+
@defer (when page.active; on idle) {
|
|
3487
|
+
<section page-field [field]="page.field" [key]="page.field.key" [pageIndex]="page.index" [isVisible]="page.active"></section>
|
|
3488
|
+
} @placeholder {
|
|
3489
|
+
<div class="df-page-placeholder" [attr.data-page-index]="page.index" [attr.data-page-key]="page.field.key"></div>
|
|
3490
|
+
}
|
|
3491
|
+
} @else {
|
|
3492
|
+
<!--
|
|
3493
|
+
Pages outside the preload window remain lightweight placeholders.
|
|
3494
|
+
Form state derives from config, so leaving their components unmounted
|
|
3495
|
+
does not affect validity, derivations, or navigation. The active page
|
|
3496
|
+
renders directly; configured neighbours preload declaratively on idle.
|
|
3497
|
+
-->
|
|
3498
|
+
<div class="df-page-placeholder" [attr.data-page-index]="page.index" [attr.data-page-key]="page.field.key"></div>
|
|
3499
|
+
}
|
|
3500
|
+
}
|
|
3501
|
+
}
|
|
3502
|
+
`, host: {
|
|
3503
|
+
class: 'df-page-orchestrator',
|
|
3504
|
+
'[attr.data-current-page]': 'state().currentPageIndex',
|
|
3505
|
+
'[attr.data-total-pages]': 'state().totalPages',
|
|
3506
|
+
}, providers: [
|
|
3507
|
+
{
|
|
3508
|
+
provide: FIELD_SIGNAL_CONTEXT,
|
|
3509
|
+
useFactory: (orchestrator) => orchestrator.extendedFieldSignalContext(),
|
|
3510
|
+
deps: [PageOrchestratorComponent],
|
|
3511
|
+
},
|
|
3512
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;position:relative;width:100%}\n"] }]
|
|
3513
|
+
}], 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 }] }] } }) });
|
|
3743
3514
|
|
|
3744
3515
|
function traverseFieldsWithContext(fields, context, visitor, hooks) {
|
|
3745
3516
|
for (const field of fields) {
|
|
@@ -3818,7 +3589,7 @@ function createDerivationRenderGate() {
|
|
|
3818
3589
|
if (!has || engine() !== 'idle')
|
|
3819
3590
|
return;
|
|
3820
3591
|
engine.set('loading');
|
|
3821
|
-
pendingTasks.run(() => import('./ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-
|
|
3592
|
+
pendingTasks.run(() => import('./ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-BfxYqAQA.mjs')
|
|
3822
3593
|
.then(({ bootstrapDerivationOrchestrator }) => {
|
|
3823
3594
|
runInInjectionContext(injector, () => bootstrapDerivationOrchestrator());
|
|
3824
3595
|
engine.set('wired');
|
|
@@ -4125,22 +3896,30 @@ class DynamicForm {
|
|
|
4125
3896
|
submitting = this.stateManager.submitting;
|
|
4126
3897
|
fieldLoadingErrors = this.stateManager.fieldLoadingErrors;
|
|
4127
3898
|
derivationReady = inject(DERIVATION_RENDER_GATE);
|
|
4128
|
-
shouldRender = computed(() => this.stateManager.shouldRender() && this.derivationReady(), /* @ts-ignore */
|
|
3899
|
+
shouldRender = computed(() => this.stateManager.shouldRender() && this.stateManager.formSchemaReady() && this.derivationReady(), /* @ts-ignore */
|
|
4129
3900
|
...(ngDevMode ? [{ debugName: "shouldRender" }] : /* istanbul ignore next */ []));
|
|
4130
3901
|
resolvedFields = this.stateManager.resolvedFields;
|
|
4131
3902
|
fieldWindowing = computed(() => resolveFieldWindowing(this.globalFieldWindowing, this.effectiveFormOptions()?.fieldWindowing), /* @ts-ignore */
|
|
4132
3903
|
...(ngDevMode ? [{ debugName: "fieldWindowing" }] : /* istanbul ignore next */ []));
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
3904
|
+
initializedComponents = signal(new Set(), /* @ts-ignore */
|
|
3905
|
+
...(ngDevMode ? [{ debugName: "initializedComponents" }] : /* istanbul ignore next */ []));
|
|
3906
|
+
initialPageReady = signal(false, /* @ts-ignore */
|
|
3907
|
+
...(ngDevMode ? [{ debugName: "initialPageReady" }] : /* istanbul ignore next */ []));
|
|
3908
|
+
initializationReady = computed(() => {
|
|
3909
|
+
const config = this.activeConfig();
|
|
3910
|
+
if (!config)
|
|
3911
|
+
return false;
|
|
3912
|
+
if (this.formModeDetection().mode === 'paged')
|
|
3913
|
+
return this.initialPageReady();
|
|
3914
|
+
const initialized = this.initializedComponents();
|
|
3915
|
+
const expected = [
|
|
3916
|
+
initializationComponentKey('dynamic-form', this.componentId),
|
|
3917
|
+
...collectInitializingContainerKeys(config.fields ?? []),
|
|
3918
|
+
];
|
|
3919
|
+
return expected.every((key) => initialized.has(key));
|
|
4136
3920
|
}, /* @ts-ignore */
|
|
4137
|
-
...(ngDevMode ? [{ debugName: "
|
|
4138
|
-
|
|
4139
|
-
eventBus: this.eventBus,
|
|
4140
|
-
totalComponentsCount: this.totalComponentsCount,
|
|
4141
|
-
injector: this.injector,
|
|
4142
|
-
componentId: this.componentId,
|
|
4143
|
-
});
|
|
3921
|
+
...(ngDevMode ? [{ debugName: "initializationReady" }] : /* istanbul ignore next */ []));
|
|
3922
|
+
initialized$ = toObservable(this.initializationReady, { injector: this.injector }).pipe(filter((ready) => ready), take(1), shareReplay({ bufferSize: 1, refCount: false }));
|
|
4144
3923
|
validityChange = outputFromObservable(toObservable(this.valid));
|
|
4145
3924
|
dirtyChange = outputFromObservable(toObservable(this.dirty));
|
|
4146
3925
|
submitted = outputFromObservable(this.stateManager.submitted$);
|
|
@@ -4148,9 +3927,27 @@ class DynamicForm {
|
|
|
4148
3927
|
cleared = outputFromObservable(this.eventBus.on('form-clear'));
|
|
4149
3928
|
events = outputFromObservable(this.eventBus.events$);
|
|
4150
3929
|
initialized = outputFromObservable(this.initialized$);
|
|
3930
|
+
activePageInitialized = outputFromObservable(this.eventBus.on('active-page-initialized'));
|
|
4151
3931
|
onPageChange = outputFromObservable(this.eventBus.on('page-change'));
|
|
4152
3932
|
onPageNavigationStateChange = outputFromObservable(this.eventBus.on('pager-state'));
|
|
4153
3933
|
constructor() {
|
|
3934
|
+
this.eventBus
|
|
3935
|
+
.on('component-initialized')
|
|
3936
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
3937
|
+
.subscribe((event) => {
|
|
3938
|
+
const key = initializationComponentKey(event.componentType, event.componentId);
|
|
3939
|
+
this.initializedComponents.update((current) => {
|
|
3940
|
+
if (current.has(key))
|
|
3941
|
+
return current;
|
|
3942
|
+
const next = new Set(current);
|
|
3943
|
+
next.add(key);
|
|
3944
|
+
return next;
|
|
3945
|
+
});
|
|
3946
|
+
});
|
|
3947
|
+
this.eventBus
|
|
3948
|
+
.on('active-page-initialized')
|
|
3949
|
+
.pipe(takeUntilDestroyed(this.destroyRef), take(1))
|
|
3950
|
+
.subscribe(() => this.initialPageReady.set(true));
|
|
4154
3951
|
this.dispatcher?.connect(this.eventBus);
|
|
4155
3952
|
this.destroyRef.onDestroy(() => this.dispatcher?.disconnect());
|
|
4156
3953
|
inject(FORM_INITIALIZER, { optional: true });
|
|
@@ -4175,8 +3972,8 @@ class DynamicForm {
|
|
|
4175
3972
|
this.eventBus.dispatch(ComponentInitializedEvent, 'dynamic-form', this.componentId);
|
|
4176
3973
|
}
|
|
4177
3974
|
});
|
|
4178
|
-
explicitEffect([this.resolvedFields, this.formModeDetection], ([
|
|
4179
|
-
if (mode === 'non-paged'
|
|
3975
|
+
explicitEffect([this.resolvedFields, this.formModeDetection, this.activeConfig], ([, { mode }, config]) => {
|
|
3976
|
+
if (config && mode === 'non-paged') {
|
|
4180
3977
|
afterNextRender(() => {
|
|
4181
3978
|
this.eventBus.dispatch(ComponentInitializedEvent, 'dynamic-form', this.componentId);
|
|
4182
3979
|
}, { injector: this.injector });
|
|
@@ -4212,7 +4009,7 @@ class DynamicForm {
|
|
|
4212
4009
|
});
|
|
4213
4010
|
}
|
|
4214
4011
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: DynamicForm, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4215
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", type: DynamicForm, isStandalone: true, selector: "form[dynamic-form]", inputs: { config: { classPropertyName: "config", publicName: "dynamic-form", isSignal: true, isRequired: true, transformFunction: null }, formOptions: { classPropertyName: "formOptions", publicName: "formOptions", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, source: { classPropertyName: "source", publicName: "source", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", validityChange: "validityChange", dirtyChange: "dirtyChange", submitted: "submitted", reset: "reset", cleared: "cleared", events: "events", initialized: "initialized", onPageChange: "onPageChange", onPageNavigationStateChange: "onPageNavigationStateChange" }, host: { attributes: { "novalidate": "" }, listeners: { "submit": "onNativeSubmit($event)" }, properties: { "class.disabled": "disabled()", "class.df-form-paged": "formModeDetection().mode === \"paged\"", "class.df-form-non-paged": "formModeDetection().mode === \"non-paged\"", "attr.data-form-mode": "formModeDetection().mode", "attr.id": "idPrefix() || null" }, classAttribute: "df-dynamic-form df-form" }, providers: [
|
|
4012
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", type: DynamicForm, isStandalone: true, selector: "form[dynamic-form]", inputs: { config: { classPropertyName: "config", publicName: "dynamic-form", isSignal: true, isRequired: true, transformFunction: null }, formOptions: { classPropertyName: "formOptions", publicName: "formOptions", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, source: { classPropertyName: "source", publicName: "source", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", validityChange: "validityChange", dirtyChange: "dirtyChange", submitted: "submitted", reset: "reset", cleared: "cleared", events: "events", initialized: "initialized", activePageInitialized: "activePageInitialized", onPageChange: "onPageChange", onPageNavigationStateChange: "onPageNavigationStateChange" }, host: { attributes: { "novalidate": "" }, listeners: { "submit": "onNativeSubmit($event)" }, properties: { "class.disabled": "disabled()", "class.df-form-paged": "formModeDetection().mode === \"paged\"", "class.df-form-non-paged": "formModeDetection().mode === \"non-paged\"", "attr.data-form-mode": "formModeDetection().mode", "attr.id": "idPrefix() || null" }, classAttribute: "df-dynamic-form df-form" }, providers: [
|
|
4216
4013
|
provideDynamicFormDI(),
|
|
4217
4014
|
DfFieldTemplateRegistry,
|
|
4218
4015
|
{ provide: DF_FIELD_TEMPLATES, useFactory: () => inject(DfFieldTemplateRegistry).map },
|
|
@@ -4224,12 +4021,8 @@ class DynamicForm {
|
|
|
4224
4021
|
}
|
|
4225
4022
|
@case ('non-paged') {
|
|
4226
4023
|
@for (field of resolvedFields(); track field.key; let i = $index) {
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
the directive's gate stops a same-CD-pass mount before [formField] can warn.
|
|
4230
|
-
Don't dedupe one without the other — see DfFieldOutlet.renderReady. -->
|
|
4231
|
-
@if (!field.hidden()) {
|
|
4232
|
-
@if (windowsField(field, i)) {
|
|
4024
|
+
@if (windowsField(field, i)) {
|
|
4025
|
+
@if (!field.hidden()) {
|
|
4233
4026
|
@defer (on viewport) {
|
|
4234
4027
|
<ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
|
|
4235
4028
|
} @placeholder {
|
|
@@ -4240,9 +4033,11 @@ class DynamicForm {
|
|
|
4240
4033
|
aria-hidden="true"
|
|
4241
4034
|
></div>
|
|
4242
4035
|
}
|
|
4243
|
-
} @else {
|
|
4244
|
-
<ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
|
|
4245
4036
|
}
|
|
4037
|
+
} @else {
|
|
4038
|
+
<!-- DfFieldOutlet owns the hidden gate so an already-mounted field can be detached
|
|
4039
|
+
and restored without recreating its component and wrapper chain. -->
|
|
4040
|
+
<ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
|
|
4246
4041
|
}
|
|
4247
4042
|
}
|
|
4248
4043
|
}
|
|
@@ -4263,12 +4058,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
|
|
|
4263
4058
|
}
|
|
4264
4059
|
@case ('non-paged') {
|
|
4265
4060
|
@for (field of resolvedFields(); track field.key; let i = $index) {
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
the directive's gate stops a same-CD-pass mount before [formField] can warn.
|
|
4269
|
-
Don't dedupe one without the other — see DfFieldOutlet.renderReady. -->
|
|
4270
|
-
@if (!field.hidden()) {
|
|
4271
|
-
@if (windowsField(field, i)) {
|
|
4061
|
+
@if (windowsField(field, i)) {
|
|
4062
|
+
@if (!field.hidden()) {
|
|
4272
4063
|
@defer (on viewport) {
|
|
4273
4064
|
<ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
|
|
4274
4065
|
} @placeholder {
|
|
@@ -4279,9 +4070,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
|
|
|
4279
4070
|
aria-hidden="true"
|
|
4280
4071
|
></div>
|
|
4281
4072
|
}
|
|
4282
|
-
} @else {
|
|
4283
|
-
<ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
|
|
4284
4073
|
}
|
|
4074
|
+
} @else {
|
|
4075
|
+
<!-- DfFieldOutlet owns the hidden gate so an already-mounted field can be detached
|
|
4076
|
+
and restored without recreating its component and wrapper chain. -->
|
|
4077
|
+
<ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
|
|
4285
4078
|
}
|
|
4286
4079
|
}
|
|
4287
4080
|
}
|
|
@@ -4303,29 +4096,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
|
|
|
4303
4096
|
'[attr.id]': 'idPrefix() || null',
|
|
4304
4097
|
'(submit)': 'onNativeSubmit($event)',
|
|
4305
4098
|
}, 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"] }]
|
|
4306
|
-
}], 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"] }] } });
|
|
4307
|
-
function countContainersRecursive(fields) {
|
|
4308
|
-
let count = 0;
|
|
4309
|
-
for (const field of fields) {
|
|
4310
|
-
if (isContainerField(field)) {
|
|
4311
|
-
count += 1;
|
|
4312
|
-
if (hasChildFields(field)) {
|
|
4313
|
-
const children = field.fields;
|
|
4314
|
-
if (Array.isArray(children)) {
|
|
4315
|
-
for (const child of children) {
|
|
4316
|
-
if (Array.isArray(child)) {
|
|
4317
|
-
count += countContainersRecursive(child);
|
|
4318
|
-
}
|
|
4319
|
-
else if (child != null && isContainerField(child)) {
|
|
4320
|
-
count += countContainersRecursive([child]);
|
|
4321
|
-
}
|
|
4322
|
-
}
|
|
4323
|
-
}
|
|
4324
|
-
}
|
|
4325
|
-
}
|
|
4326
|
-
}
|
|
4327
|
-
return count;
|
|
4328
|
-
}
|
|
4099
|
+
}], 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"] }] } });
|
|
4329
4100
|
|
|
4330
4101
|
const BUILT_IN_ADDON_TYPES = [
|
|
4331
4102
|
{
|
|
@@ -4373,28 +4144,28 @@ const DISPLAY_FIELD_TYPES_BASE = {
|
|
|
4373
4144
|
const BUILT_IN_FIELDS = [
|
|
4374
4145
|
{
|
|
4375
4146
|
name: 'row',
|
|
4376
|
-
loadComponent: () => import('./ng-forge-dynamic-forms-container-field.component-
|
|
4147
|
+
loadComponent: () => import('./ng-forge-dynamic-forms-container-field.component-Cj2msZp_.mjs'),
|
|
4377
4148
|
mapper: rowFieldMapper,
|
|
4378
4149
|
valueHandling: 'flatten',
|
|
4379
4150
|
...CONTAINER_FIELD_TYPES_BASE,
|
|
4380
4151
|
},
|
|
4381
4152
|
{
|
|
4382
4153
|
name: 'group',
|
|
4383
|
-
loadComponent: () => import('./ng-forge-dynamic-forms-group-field.component-
|
|
4154
|
+
loadComponent: () => import('./ng-forge-dynamic-forms-group-field.component-DN6X506r.mjs'),
|
|
4384
4155
|
mapper: groupFieldMapper,
|
|
4385
4156
|
valueHandling: 'include',
|
|
4386
4157
|
...CONTAINER_FIELD_TYPES_BASE,
|
|
4387
4158
|
},
|
|
4388
4159
|
{
|
|
4389
4160
|
name: 'array',
|
|
4390
|
-
loadComponent: () => import('./ng-forge-dynamic-forms-array-field.component-
|
|
4161
|
+
loadComponent: () => import('./ng-forge-dynamic-forms-array-field.component-DK7dAQv7.mjs'),
|
|
4391
4162
|
mapper: arrayFieldMapper,
|
|
4392
4163
|
valueHandling: 'include',
|
|
4393
4164
|
...CONTAINER_FIELD_TYPES_BASE,
|
|
4394
4165
|
},
|
|
4395
4166
|
{
|
|
4396
4167
|
name: 'page',
|
|
4397
|
-
loadComponent: () => import('./ng-forge-dynamic-forms-page-field.component-
|
|
4168
|
+
loadComponent: () => import('./ng-forge-dynamic-forms-page-field.component-D16ey8r-.mjs'),
|
|
4398
4169
|
mapper: pageFieldMapper,
|
|
4399
4170
|
valueHandling: 'flatten',
|
|
4400
4171
|
...CONTAINER_FIELD_TYPES_BASE,
|
|
@@ -4412,7 +4183,7 @@ const BUILT_IN_FIELDS = [
|
|
|
4412
4183
|
},
|
|
4413
4184
|
{
|
|
4414
4185
|
name: 'container',
|
|
4415
|
-
loadComponent: () => import('./ng-forge-dynamic-forms-container-field.component-
|
|
4186
|
+
loadComponent: () => import('./ng-forge-dynamic-forms-container-field.component-Cj2msZp_.mjs'),
|
|
4416
4187
|
mapper: containerFieldMapper,
|
|
4417
4188
|
valueHandling: 'flatten',
|
|
4418
4189
|
...CONTAINER_FIELD_TYPES_BASE,
|
|
@@ -4427,6 +4198,11 @@ const BUILT_IN_WRAPPERS = [
|
|
|
4427
4198
|
wrapperName: 'row',
|
|
4428
4199
|
loadComponent: () => import('./ng-forge-dynamic-forms-row-wrapper.component-BBWmqQA_.mjs'),
|
|
4429
4200
|
},
|
|
4201
|
+
{
|
|
4202
|
+
wrapperName: 'field-errors',
|
|
4203
|
+
loadComponent: () => import('./ng-forge-dynamic-forms-field-errors-wrapper.component-ZdaC4Bit.mjs'),
|
|
4204
|
+
rendersFieldErrors: true,
|
|
4205
|
+
},
|
|
4430
4206
|
];
|
|
4431
4207
|
|
|
4432
4208
|
function isDynamicFormFeature(value) {
|
|
@@ -4486,7 +4262,8 @@ function provideDynamicForm(...items) {
|
|
|
4486
4262
|
const wrapperBundles = items.filter(isWrappersBundle);
|
|
4487
4263
|
const features = items.filter(isDynamicFormFeature);
|
|
4488
4264
|
const fields = [...BUILT_IN_FIELDS, ...fieldTypes];
|
|
4489
|
-
const
|
|
4265
|
+
const customWrappers = [...wrapperTypes, ...wrapperBundles.flatMap((bundle) => bundle.ɵdefinitions)];
|
|
4266
|
+
const wrappers = [...BUILT_IN_WRAPPERS, ...customWrappers];
|
|
4490
4267
|
const featureProviders = [];
|
|
4491
4268
|
const hasLoggerFeature = features.some((feature) => feature.ɵkind === 'logger');
|
|
4492
4269
|
features.forEach((feature) => {
|
|
@@ -4501,7 +4278,7 @@ function provideDynamicForm(...items) {
|
|
|
4501
4278
|
const logger = inject(DynamicFormLogger);
|
|
4502
4279
|
const registry = new Map();
|
|
4503
4280
|
fields.forEach((fieldType) => {
|
|
4504
|
-
|
|
4281
|
+
if (DEV_MODE && registry.has(fieldType.name)) {
|
|
4505
4282
|
logger.warn(`Field type "${fieldType.name}" is already registered. Overwriting.`);
|
|
4506
4283
|
}
|
|
4507
4284
|
registry.set(fieldType.name, fieldType);
|
|
@@ -4527,11 +4304,18 @@ function provideDynamicForm(...items) {
|
|
|
4527
4304
|
useFactory: () => {
|
|
4528
4305
|
const logger = inject(DynamicFormLogger);
|
|
4529
4306
|
const registry = new Map();
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
|
|
4307
|
+
const builtInNames = new Set(BUILT_IN_WRAPPERS.map((w) => w.wrapperName));
|
|
4308
|
+
BUILT_IN_WRAPPERS.forEach((wrapperType) => registry.set(wrapperType.wrapperName, wrapperType));
|
|
4309
|
+
const seenCustom = DEV_MODE ? new Set() : null;
|
|
4310
|
+
customWrappers.forEach((wrapperType) => {
|
|
4311
|
+
const name = wrapperType.wrapperName;
|
|
4312
|
+
if (seenCustom) {
|
|
4313
|
+
if (seenCustom.has(name) && !builtInNames.has(name)) {
|
|
4314
|
+
logger.warn(`Wrapper type "${name}" is already registered. Overwriting.`);
|
|
4315
|
+
}
|
|
4316
|
+
seenCustom.add(name);
|
|
4533
4317
|
}
|
|
4534
|
-
registry.set(
|
|
4318
|
+
registry.set(name, wrapperType);
|
|
4535
4319
|
});
|
|
4536
4320
|
return registry;
|
|
4537
4321
|
},
|
|
@@ -4562,7 +4346,7 @@ function provideDynamicForm(...items) {
|
|
|
4562
4346
|
registry.set(def.type, def);
|
|
4563
4347
|
}
|
|
4564
4348
|
for (const def of contributed) {
|
|
4565
|
-
|
|
4349
|
+
if (DEV_MODE && registry.has(def.type)) {
|
|
4566
4350
|
logger.warn(`Addon type "${def.type}" is already registered. Overwriting.`);
|
|
4567
4351
|
}
|
|
4568
4352
|
registry.set(def.type, def);
|
|
@@ -4624,6 +4408,13 @@ function withFieldWindowing(config) {
|
|
|
4624
4408
|
return createFeature('field-windowing', [{ provide: FIELD_WINDOWING, useValue: resolved }]);
|
|
4625
4409
|
}
|
|
4626
4410
|
|
|
4411
|
+
const VALIDATION_EXECUTION_DEFAULTS = new InjectionToken('VALIDATION_EXECUTION_DEFAULTS', {
|
|
4412
|
+
providedIn: 'root',
|
|
4413
|
+
factory: () => ({
|
|
4414
|
+
validateWhenHidden: false,
|
|
4415
|
+
}),
|
|
4416
|
+
});
|
|
4417
|
+
|
|
4627
4418
|
function withValidationExecutionDefaults(config) {
|
|
4628
4419
|
const resolved = {
|
|
4629
4420
|
validateWhenHidden: config?.validateWhenHidden ?? false,
|
|
@@ -4814,6 +4605,16 @@ function sanitizeFormConfigPure(config, fieldRegistry, typeRegistry, options = {
|
|
|
4814
4605
|
return { sanitized: { ...config, fields: fields }, warnings };
|
|
4815
4606
|
}
|
|
4816
4607
|
|
|
4608
|
+
class ActivePageInitializedEvent {
|
|
4609
|
+
pageIndex;
|
|
4610
|
+
pageKey;
|
|
4611
|
+
type = 'active-page-initialized';
|
|
4612
|
+
constructor(pageIndex, pageKey) {
|
|
4613
|
+
this.pageIndex = pageIndex;
|
|
4614
|
+
this.pageKey = pageKey;
|
|
4615
|
+
}
|
|
4616
|
+
}
|
|
4617
|
+
|
|
4817
4618
|
class GoToPageEvent {
|
|
4818
4619
|
pageIndex;
|
|
4819
4620
|
options;
|
|
@@ -4941,5 +4742,5 @@ function arrayEvent(arrayKey) {
|
|
|
4941
4742
|
|
|
4942
4743
|
|
|
4943
4744
|
|
|
4944
|
-
export {
|
|
4945
|
-
//# sourceMappingURL=ng-forge-dynamic-forms-ng-forge-dynamic-forms-
|
|
4745
|
+
export { arrayEvent as $, ArrayItemRegistryService as A, BUILT_IN_FIELDS as B, CONTAINER_FIELD_PROCESSORS as C, DfFieldOutlet as D, DfTemplate as E, FIELD_WINDOWING as F, DynamicForm as G, EventDispatcher as H, FormClearEvent as I, FormResetEvent as J, FormSubmitEvent as K, GoToPageEvent as L, InsertArrayItemEvent as M, MoveArrayItemEvent as N, NextPageEvent as O, PROPERTY_OVERRIDE_STORE as P, PageChangeEvent as Q, PagerStateEvent as R, SchemaRegistryService as S, PopArrayItemEvent as T, PrependArrayItemEvent as U, VALIDATION_EXECUTION_DEFAULTS as V, PreviousPageEvent as W, RemoveAtIndexEvent as X, ShiftArrayItemEvent as Y, TemplateAddonComponent as Z, TextAddonComponent as _, createFieldResolutionPipe as a, createWrappers as a0, isWrappersBundle as a1, provideDynamicForm as a2, sanitizeFormConfig as a3, withAddonActions as a4, withCustomAddon as a5, withEventFormValue as a6, withFieldWindowing as a7, withLegacyStatusClasses as a8, withLoggerConfig as a9, withPagePreload as aa, withValidationExecutionDefaults as ab, withValueExclusionDefaults as ac, wrapperProps as ad, isSameWrapperChain as b, computeContainerHostClasses as c, initializationComponentPath as d, createWrapperChainController as e, createHiddenSignal as f, createRenderReadySignal as g, getNormalizedArrayMetadata as h, injectFieldRegistry as i, emitComponentInitialized as j, getFieldDefaultValue as k, resolveFieldWindowing as l, mapFieldToInputs as m, collectInitializingContainerKeys as n, ActivePageInitializedEvent as o, initializationComponentKey as p, DEV_MODE as q, resolveWrappers as r, setupContainerInitEffect as s, traverseFieldsWithContext as t, DERIVATION_WARNING_TRACKER as u, FormStateManager as v, DERIVATION_LOG_CONFIG as w, AppendArrayItemEvent as x, ComponentAddonComponent as y, ConsoleLogger as z };
|
|
4746
|
+
//# sourceMappingURL=ng-forge-dynamic-forms-ng-forge-dynamic-forms-DCX7tvaB.mjs.map
|