@ng-forge/dynamic-forms 1.2.0-next.6 → 1.2.0-next.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/{ng-forge-dynamic-forms-array-field.component-NTHLPjau.mjs → ng-forge-dynamic-forms-array-field.component-DK7dAQv7.mjs} +6 -10
- package/fesm2022/ng-forge-dynamic-forms-array-field.component-DK7dAQv7.mjs.map +1 -0
- package/fesm2022/{ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-CNCeWAeJ.mjs → ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-BfxYqAQA.mjs} +29 -7
- package/fesm2022/ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-BfxYqAQA.mjs.map +1 -0
- package/fesm2022/{ng-forge-dynamic-forms-container-field.component-ByGdzAsk.mjs → ng-forge-dynamic-forms-container-field.component-Cj2msZp_.mjs} +7 -11
- package/fesm2022/ng-forge-dynamic-forms-container-field.component-Cj2msZp_.mjs.map +1 -0
- package/fesm2022/ng-forge-dynamic-forms-debounce-C9F4v1Ke.mjs +17 -0
- package/fesm2022/ng-forge-dynamic-forms-debounce-C9F4v1Ke.mjs.map +1 -0
- package/fesm2022/ng-forge-dynamic-forms-form-schema.service-C3cVSmft.mjs +24 -0
- package/fesm2022/ng-forge-dynamic-forms-form-schema.service-C3cVSmft.mjs.map +1 -0
- package/fesm2022/{ng-forge-dynamic-forms-group-field.component-DnLZdj8v.mjs → ng-forge-dynamic-forms-group-field.component-DN6X506r.mjs} +6 -10
- package/fesm2022/ng-forge-dynamic-forms-group-field.component-DN6X506r.mjs.map +1 -0
- package/fesm2022/ng-forge-dynamic-forms-internal.mjs +32 -5
- package/fesm2022/ng-forge-dynamic-forms-internal.mjs.map +1 -1
- package/fesm2022/{ng-forge-dynamic-forms-ng-forge-dynamic-forms-B2C_WAMP.mjs → ng-forge-dynamic-forms-ng-forge-dynamic-forms-DCX7tvaB.mjs} +1043 -1299
- package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-DCX7tvaB.mjs.map +1 -0
- package/fesm2022/{ng-forge-dynamic-forms-page-field.component-CwH22lRl.mjs → ng-forge-dynamic-forms-page-field.component-D16ey8r-.mjs} +54 -13
- package/fesm2022/ng-forge-dynamic-forms-page-field.component-D16ey8r-.mjs.map +1 -0
- package/fesm2022/ng-forge-dynamic-forms-schema-builder-DO-su9sF.mjs +570 -0
- package/fesm2022/ng-forge-dynamic-forms-schema-builder-DO-su9sF.mjs.map +1 -0
- package/fesm2022/ng-forge-dynamic-forms.mjs +1 -1
- package/package.json +2 -2
- package/types/ng-forge-dynamic-forms-internal.d.ts +10 -5
- package/types/ng-forge-dynamic-forms.d.ts +32 -16
- package/fesm2022/ng-forge-dynamic-forms-array-field.component-NTHLPjau.mjs.map +0 -1
- package/fesm2022/ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-CNCeWAeJ.mjs.map +0 -1
- package/fesm2022/ng-forge-dynamic-forms-container-field.component-ByGdzAsk.mjs.map +0 -1
- package/fesm2022/ng-forge-dynamic-forms-container-utils-BZFz6ztF.mjs +0 -24
- package/fesm2022/ng-forge-dynamic-forms-container-utils-BZFz6ztF.mjs.map +0 -1
- package/fesm2022/ng-forge-dynamic-forms-emit-initialization-DhHwU6FJ.mjs +0 -17
- package/fesm2022/ng-forge-dynamic-forms-emit-initialization-DhHwU6FJ.mjs.map +0 -1
- package/fesm2022/ng-forge-dynamic-forms-group-field.component-DnLZdj8v.mjs.map +0 -1
- package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-B2C_WAMP.mjs.map +0 -1
- package/fesm2022/ng-forge-dynamic-forms-page-field.component-CwH22lRl.mjs.map +0 -1
|
@@ -1,11 +1,10 @@
|
|
|
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
|
|
|
@@ -45,6 +44,8 @@ function needsErrorWrapper(field, composed, wrapperRegistry) {
|
|
|
45
44
|
return !composed.some((w) => w.type === FIELD_ERRORS_WRAPPER || wrapperRegistry.get(w.type)?.rendersFieldErrors);
|
|
46
45
|
}
|
|
47
46
|
|
|
47
|
+
const DEV_MODE = typeof ngDevMode === 'undefined' || !!ngDevMode;
|
|
48
|
+
|
|
48
49
|
const inputNamesCache = new WeakMap();
|
|
49
50
|
function getDeclaredInputs(componentType) {
|
|
50
51
|
let inputs = inputNamesCache.get(componentType);
|
|
@@ -83,7 +84,9 @@ function loadWrapperComponents(configs, registry, cache, logger) {
|
|
|
83
84
|
return of([]);
|
|
84
85
|
return forkJoin(configs.map((config) => from(loadWrapperComponent(config.type, registry, cache)).pipe(catchError(() => of(undefined)), map((component) => {
|
|
85
86
|
if (!component) {
|
|
86
|
-
|
|
87
|
+
if (DEV_MODE) {
|
|
88
|
+
logger.error(`Wrapper type '${config.type}' could not be loaded. Ensure it is registered via provideDynamicForm().`);
|
|
89
|
+
}
|
|
87
90
|
return null;
|
|
88
91
|
}
|
|
89
92
|
return { config, component };
|
|
@@ -123,9 +126,11 @@ function renderStep(slot, remaining, options, refs) {
|
|
|
123
126
|
ref.changeDetectorRef.detectChanges();
|
|
124
127
|
const inner = resolveInnerSlot(ref);
|
|
125
128
|
if (!inner) {
|
|
126
|
-
|
|
127
|
-
`
|
|
128
|
-
|
|
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
|
+
}
|
|
129
134
|
while (refs.length)
|
|
130
135
|
refs.pop().destroy();
|
|
131
136
|
return;
|
|
@@ -167,22 +172,28 @@ function createWrapperChainController(opts) {
|
|
|
167
172
|
const deps = injectChainDeps();
|
|
168
173
|
const state = createStateSignal(opts);
|
|
169
174
|
const mounted = { value: null };
|
|
175
|
+
const detached = { value: null };
|
|
170
176
|
let refs = [];
|
|
171
177
|
buildEmissionStream(state, deps)
|
|
172
178
|
.pipe(takeUntilDestroyed(deps.destroyRef))
|
|
173
179
|
.subscribe((emission) => {
|
|
174
180
|
try {
|
|
175
|
-
refs = applyEmission(emission, { opts, deps, mounted, refs });
|
|
181
|
+
refs = applyEmission(emission, { opts, deps, mounted, refs, detached });
|
|
176
182
|
}
|
|
177
183
|
catch (err) {
|
|
178
184
|
deps.logger.error('Wrapper chain render failed; tearing down partial state.', err);
|
|
179
185
|
opts.vcr().clear();
|
|
180
186
|
refs = [];
|
|
181
187
|
mounted.value = null;
|
|
188
|
+
detached.value?.destroy();
|
|
189
|
+
detached.value = null;
|
|
182
190
|
}
|
|
183
191
|
});
|
|
184
192
|
pushFieldInputsOnChange(opts, () => refs);
|
|
185
|
-
deps.destroyRef.onDestroy(() =>
|
|
193
|
+
deps.destroyRef.onDestroy(() => {
|
|
194
|
+
detached.value?.destroy();
|
|
195
|
+
opts.vcr().clear();
|
|
196
|
+
});
|
|
186
197
|
}
|
|
187
198
|
function injectChainDeps() {
|
|
188
199
|
return {
|
|
@@ -217,10 +228,21 @@ function applyEmission({ state, loaded }, ctx) {
|
|
|
217
228
|
const { opts, deps, mounted, refs } = ctx;
|
|
218
229
|
const vcr = opts.vcr();
|
|
219
230
|
const structurallyChanged = isStructurallyDifferent(mounted.value, state);
|
|
220
|
-
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;
|
|
221
239
|
return refs;
|
|
240
|
+
}
|
|
222
241
|
if (structurallyChanged && mounted.value !== null) {
|
|
223
|
-
|
|
242
|
+
const preservesDetachedOuterView = opts.beforeRebuild?.() === true;
|
|
243
|
+
if (ctx.detached.value && !preservesDetachedOuterView)
|
|
244
|
+
ctx.detached.value.destroy();
|
|
245
|
+
ctx.detached.value = null;
|
|
224
246
|
vcr.clear();
|
|
225
247
|
mounted.value = null;
|
|
226
248
|
}
|
|
@@ -322,13 +344,14 @@ class FieldComponentSlot {
|
|
|
322
344
|
detach() {
|
|
323
345
|
const current = this.state();
|
|
324
346
|
if (current.phase !== 'mounted')
|
|
325
|
-
return;
|
|
347
|
+
return false;
|
|
326
348
|
const focusSnapshot = this.captureFocus(current.ref);
|
|
327
349
|
const idx = current.slot.indexOf(current.ref.hostView);
|
|
328
350
|
if (idx >= 0)
|
|
329
351
|
current.slot.detach(idx);
|
|
330
352
|
this.lastInputs = null;
|
|
331
353
|
this.state.set(Object.freeze({ phase: 'detached', ref: current.ref, focusSnapshot }));
|
|
354
|
+
return idx < 0;
|
|
332
355
|
}
|
|
333
356
|
destroyOnTeardown() {
|
|
334
357
|
const current = this.state();
|
|
@@ -359,6 +382,74 @@ class FieldComponentSlot {
|
|
|
359
382
|
}
|
|
360
383
|
}
|
|
361
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
|
+
|
|
362
453
|
class DfFieldOutlet {
|
|
363
454
|
dfFieldOutlet = input.required(/* @ts-ignore */
|
|
364
455
|
...(ngDevMode ? [{ debugName: "dfFieldOutlet" }] : /* istanbul ignore next */ []));
|
|
@@ -371,11 +462,15 @@ class DfFieldOutlet {
|
|
|
371
462
|
wrapperRegistry = inject(WRAPPER_REGISTRY);
|
|
372
463
|
defaultWrappersSignal = inject(DEFAULT_WRAPPERS, { optional: true });
|
|
373
464
|
readonlyFieldCache = inject(READONLY_FIELD_TREE_CACHE);
|
|
465
|
+
eventBus = inject(EventBus, { optional: true });
|
|
466
|
+
injector = inject(Injector);
|
|
374
467
|
fieldComponent = new FieldComponentSlot();
|
|
375
468
|
componentIdentity = computed(() => this.dfFieldOutlet().component, /* @ts-ignore */
|
|
376
469
|
...(ngDevMode ? [{ debugName: "componentIdentity" }] : /* istanbul ignore next */ []));
|
|
377
470
|
renderReady = computed(() => this.dfFieldOutlet().renderReady() && !this.dfFieldOutlet().hidden(), /* @ts-ignore */
|
|
378
471
|
...(ngDevMode ? [{ debugName: "renderReady" }] : /* istanbul ignore next */ []));
|
|
472
|
+
hidden = computed(() => this.dfFieldOutlet().hidden(), /* @ts-ignore */
|
|
473
|
+
...(ngDevMode ? [{ debugName: "hidden" }] : /* istanbul ignore next */ []));
|
|
379
474
|
rawInputs = computed(() => this.dfFieldOutlet().inputs(), /* @ts-ignore */
|
|
380
475
|
...(ngDevMode ? [{ debugName: "rawInputs" }] : /* istanbul ignore next */ []));
|
|
381
476
|
wrappers = computed(() => resolveWrappers(this.dfFieldOutlet().fieldDef, this.defaultWrappersSignal?.(), this.wrapperAutoAssociations, this.wrapperRegistry), { ...(ngDevMode ? { debugName: "wrappers" } : /* istanbul ignore next */ {}), equal: isSameWrapperChain });
|
|
@@ -404,6 +499,15 @@ class DfFieldOutlet {
|
|
|
404
499
|
},
|
|
405
500
|
});
|
|
406
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
|
+
});
|
|
407
511
|
this.destroyRef.onDestroy(() => this.fieldComponent.destroyOnTeardown());
|
|
408
512
|
}
|
|
409
513
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: DfFieldOutlet, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
@@ -484,462 +588,72 @@ function clampWindowSize(value, fallback) {
|
|
|
484
588
|
return Math.max(0, Math.floor(value));
|
|
485
589
|
}
|
|
486
590
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
globalPreloadWindow = inject(PAGE_PRELOAD_WINDOW);
|
|
493
|
-
pageFields = input.required(/* @ts-ignore */
|
|
494
|
-
...(ngDevMode ? [{ debugName: "pageFields" }] : /* istanbul ignore next */ []));
|
|
495
|
-
form = input.required(/* @ts-ignore */
|
|
496
|
-
...(ngDevMode ? [{ debugName: "form" }] : /* istanbul ignore next */ []));
|
|
497
|
-
fieldSignalContext = input.required(/* @ts-ignore */
|
|
498
|
-
...(ngDevMode ? [{ debugName: "fieldSignalContext" }] : /* istanbul ignore next */ []));
|
|
499
|
-
pageHiddenStates = computed(() => {
|
|
500
|
-
const pages = this.pageFields();
|
|
501
|
-
return pages.map((page) => this.evaluatePageHidden(page));
|
|
502
|
-
}, /* @ts-ignore */
|
|
503
|
-
...(ngDevMode ? [{ debugName: "pageHiddenStates" }] : /* istanbul ignore next */ []));
|
|
504
|
-
visiblePageIndices = computed(() => {
|
|
505
|
-
const hiddenStates = this.pageHiddenStates();
|
|
506
|
-
return hiddenStates
|
|
507
|
-
.map((hidden, index) => ({ index, hidden }))
|
|
508
|
-
.filter((item) => !item.hidden)
|
|
509
|
-
.map((item) => item.index);
|
|
510
|
-
}, /* @ts-ignore */
|
|
511
|
-
...(ngDevMode ? [{ debugName: "visiblePageIndices" }] : /* istanbul ignore next */ []));
|
|
512
|
-
currentPageIndex = linkedSignal(() => {
|
|
513
|
-
const totalPages = this.pageFields().length;
|
|
514
|
-
if (totalPages === 0)
|
|
515
|
-
return 0;
|
|
516
|
-
return untracked(() => this.resolveInitialLanding());
|
|
517
|
-
}, /* @ts-ignore */
|
|
518
|
-
...(ngDevMode ? [{ debugName: "currentPageIndex" }] : /* istanbul ignore next */ []));
|
|
519
|
-
resolveInitialLanding() {
|
|
520
|
-
const { index, validate } = this.initialPage();
|
|
521
|
-
const visible = this.visiblePageIndices();
|
|
522
|
-
if (index === 0 || visible.length === 0)
|
|
523
|
-
return 0;
|
|
524
|
-
const target = visible.includes(index) ? index : this.findNearestVisiblePage(index, visible);
|
|
525
|
-
if (target <= 0)
|
|
526
|
-
return Math.max(target, 0);
|
|
527
|
-
if (!validate)
|
|
528
|
-
return target;
|
|
529
|
-
const firstInvalid = visible.filter((i) => i < target).find((i) => !this.isPageValid(i));
|
|
530
|
-
return firstInvalid ?? target;
|
|
531
|
-
}
|
|
532
|
-
initialPage = computed(() => {
|
|
533
|
-
const raw = this.formOptions?.()?.initialPage;
|
|
534
|
-
const totalPages = this.pageFields().length;
|
|
535
|
-
const requested = typeof raw === 'number' ? raw : raw?.index;
|
|
536
|
-
const validate = typeof raw === 'number' ? false : (raw?.validate ?? false);
|
|
537
|
-
if (typeof requested !== 'number' || !Number.isFinite(requested) || requested < 0 || totalPages === 0) {
|
|
538
|
-
return { index: 0, validate: false };
|
|
539
|
-
}
|
|
540
|
-
return { index: Math.min(Math.trunc(requested), totalPages - 1), validate };
|
|
541
|
-
}, /* @ts-ignore */
|
|
542
|
-
...(ngDevMode ? [{ debugName: "initialPage" }] : /* istanbul ignore next */ []));
|
|
543
|
-
state = computed(() => {
|
|
544
|
-
const currentIndex = this.currentPageIndex();
|
|
545
|
-
const totalPages = this.pageFields().length;
|
|
546
|
-
const visibleIndices = this.visiblePageIndices();
|
|
547
|
-
const currentVisiblePosition = visibleIndices.indexOf(currentIndex);
|
|
548
|
-
const isFirstVisiblePage = currentVisiblePosition === 0 || currentVisiblePosition === -1;
|
|
549
|
-
const isLastVisiblePage = currentVisiblePosition >= visibleIndices.length - 1;
|
|
550
|
-
return {
|
|
551
|
-
currentPageIndex: currentIndex,
|
|
552
|
-
totalPages,
|
|
553
|
-
isFirstPage: isFirstVisiblePage,
|
|
554
|
-
isLastPage: isLastVisiblePage,
|
|
555
|
-
};
|
|
556
|
-
}, /* @ts-ignore */
|
|
557
|
-
...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
|
|
558
|
-
currentPageValid = computed(() => this.isPageValid(this.currentPageIndex()), /* @ts-ignore */
|
|
559
|
-
...(ngDevMode ? [{ debugName: "currentPageValid" }] : /* istanbul ignore next */ []));
|
|
560
|
-
isPageValid(pageIndex) {
|
|
561
|
-
const pages = this.pageFields();
|
|
562
|
-
const form = this.form();
|
|
563
|
-
if (pageIndex < 0 || pageIndex >= pages.length) {
|
|
564
|
-
return true;
|
|
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));
|
|
565
596
|
}
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
const field = form[fieldKey];
|
|
569
|
-
if (field && typeof field === 'function') {
|
|
570
|
-
const fieldState = field();
|
|
571
|
-
if (fieldState && typeof fieldState.valid === 'function' && !fieldState.valid()) {
|
|
572
|
-
return false;
|
|
573
|
-
}
|
|
574
|
-
}
|
|
597
|
+
else if (field.key) {
|
|
598
|
+
keys.push(field.key);
|
|
575
599
|
}
|
|
576
|
-
return true;
|
|
577
|
-
}
|
|
578
|
-
extendedFieldSignalContext = computed(() => ({
|
|
579
|
-
...this.fieldSignalContext(),
|
|
580
|
-
currentPageValid: this.currentPageValid,
|
|
581
|
-
}), /* @ts-ignore */
|
|
582
|
-
...(ngDevMode ? [{ debugName: "extendedFieldSignalContext" }] : /* istanbul ignore next */ []));
|
|
583
|
-
preloadWindow = computed(() => {
|
|
584
|
-
const perForm = this.formOptions?.()?.pagePreloadWindow;
|
|
585
|
-
return clampWindowSize(perForm, this.globalPreloadWindow);
|
|
586
|
-
}, /* @ts-ignore */
|
|
587
|
-
...(ngDevMode ? [{ debugName: "preloadWindow" }] : /* istanbul ignore next */ []));
|
|
588
|
-
isWithinPreloadWindow(i) {
|
|
589
|
-
return Math.abs(i - this.state().currentPageIndex) <= this.preloadWindow();
|
|
590
|
-
}
|
|
591
|
-
constructor() {
|
|
592
|
-
this.setupEventListeners();
|
|
593
|
-
explicitEffect([this.state, this.visiblePageIndices], ([state, visibleIndices]) => {
|
|
594
|
-
const currentVisiblePosition = visibleIndices.indexOf(state.currentPageIndex);
|
|
595
|
-
if (currentVisiblePosition === -1 && visibleIndices.length > 0) {
|
|
596
|
-
const nearest = this.findNearestVisiblePage(state.currentPageIndex, visibleIndices);
|
|
597
|
-
if (nearest !== -1) {
|
|
598
|
-
this.executePageChange(nearest);
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
});
|
|
602
600
|
}
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
error: 'Already on the last visible page',
|
|
619
|
-
};
|
|
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) }];
|
|
620
616
|
}
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
return {
|
|
624
|
-
success: false,
|
|
625
|
-
newPageIndex: currentState.currentPageIndex,
|
|
626
|
-
error: 'No next visible page available',
|
|
627
|
-
};
|
|
617
|
+
if (isArrayField(field) && !isSimplifiedArrayField(field)) {
|
|
618
|
+
const normalizedItems = field.fields.map((item) => Array.isArray(item) ? normalizeSimplifiedArrays(item) : item);
|
|
619
|
+
return [{ ...field, fields: normalizedItems }];
|
|
628
620
|
}
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
navigateToPreviousPage() {
|
|
633
|
-
const currentState = this.state();
|
|
634
|
-
const visibleIndices = this.visiblePageIndices();
|
|
635
|
-
if (currentState.isFirstPage) {
|
|
636
|
-
return {
|
|
637
|
-
success: false,
|
|
638
|
-
newPageIndex: currentState.currentPageIndex,
|
|
639
|
-
error: 'Already on the first visible page',
|
|
640
|
-
};
|
|
621
|
+
if (isSimplifiedArrayField(field)) {
|
|
622
|
+
const { arrayField, addButton } = expandSimplifiedArray(field);
|
|
623
|
+
return addButton ? [arrayField, addButton] : [arrayField];
|
|
641
624
|
}
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
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
|
+
}
|
|
649
639
|
}
|
|
650
|
-
const prevVisiblePageIndex = visibleIndices[currentVisiblePosition - 1];
|
|
651
|
-
return this.executePageChange(prevVisiblePageIndex);
|
|
652
640
|
}
|
|
653
|
-
|
|
654
|
-
const
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
}
|
|
659
|
-
if (options?.validate === false) {
|
|
660
|
-
return this.executePageChange(pageIndex);
|
|
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.`);
|
|
661
646
|
}
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
return {
|
|
673
|
-
success: false,
|
|
674
|
-
newPageIndex: firstInvalidPage,
|
|
675
|
-
error: `Cannot navigate to page ${pageIndex}: page ${firstInvalidPage} has invalid fields`,
|
|
676
|
-
};
|
|
677
|
-
}
|
|
678
|
-
executePageChange(pageIndex) {
|
|
679
|
-
const currentState = this.state();
|
|
680
|
-
const totalPages = currentState.totalPages;
|
|
681
|
-
if (pageIndex < 0 || pageIndex >= totalPages) {
|
|
682
|
-
return {
|
|
683
|
-
success: false,
|
|
684
|
-
newPageIndex: currentState.currentPageIndex,
|
|
685
|
-
error: `Invalid page index: ${pageIndex}. Valid range is 0 to ${totalPages - 1}`,
|
|
686
|
-
};
|
|
687
|
-
}
|
|
688
|
-
const visibleIndices = this.visiblePageIndices();
|
|
689
|
-
if (!visibleIndices.includes(pageIndex)) {
|
|
690
|
-
return {
|
|
691
|
-
success: false,
|
|
692
|
-
newPageIndex: currentState.currentPageIndex,
|
|
693
|
-
error: `Cannot navigate to hidden page at index ${pageIndex}. Visible pages: [${visibleIndices.join(', ')}]`,
|
|
694
|
-
};
|
|
695
|
-
}
|
|
696
|
-
if (pageIndex === currentState.currentPageIndex) {
|
|
697
|
-
return {
|
|
698
|
-
success: true,
|
|
699
|
-
newPageIndex: pageIndex,
|
|
700
|
-
};
|
|
701
|
-
}
|
|
702
|
-
const previousIndex = currentState.currentPageIndex;
|
|
703
|
-
this.currentPageIndex.set(pageIndex);
|
|
704
|
-
this.eventBus.dispatch(PageChangeEvent, pageIndex, totalPages, previousIndex);
|
|
705
|
-
return {
|
|
706
|
-
success: true,
|
|
707
|
-
newPageIndex: pageIndex,
|
|
708
|
-
};
|
|
709
|
-
}
|
|
710
|
-
findNearestVisiblePage(currentIndex, visibleIndices) {
|
|
711
|
-
let nearest = -1;
|
|
712
|
-
let minDistance = Infinity;
|
|
713
|
-
for (const idx of visibleIndices) {
|
|
714
|
-
const distance = Math.abs(idx - currentIndex);
|
|
715
|
-
if (distance < minDistance || (distance === minDistance && idx > nearest)) {
|
|
716
|
-
minDistance = distance;
|
|
717
|
-
nearest = idx;
|
|
718
|
-
}
|
|
719
|
-
}
|
|
720
|
-
return nearest;
|
|
721
|
-
}
|
|
722
|
-
setupEventListeners() {
|
|
723
|
-
this.eventBus
|
|
724
|
-
.on('next-page')
|
|
725
|
-
.pipe(takeUntilDestroyed())
|
|
726
|
-
.subscribe(() => {
|
|
727
|
-
this.navigateToNextPage();
|
|
728
|
-
});
|
|
729
|
-
this.eventBus
|
|
730
|
-
.on('previous-page')
|
|
731
|
-
.pipe(takeUntilDestroyed())
|
|
732
|
-
.subscribe(() => {
|
|
733
|
-
this.navigateToPreviousPage();
|
|
734
|
-
});
|
|
735
|
-
this.eventBus
|
|
736
|
-
.on('go-to-page')
|
|
737
|
-
.pipe(takeUntilDestroyed())
|
|
738
|
-
.subscribe((event) => {
|
|
739
|
-
this.navigateToPage(event.pageIndex, event.options);
|
|
740
|
-
});
|
|
741
|
-
explicitEffect([this.state], ([state]) => this.eventBus.dispatch(PagerStateEvent, state));
|
|
742
|
-
}
|
|
743
|
-
evaluatePageHidden(page) {
|
|
744
|
-
if (!page.logic || page.logic.length === 0) {
|
|
745
|
-
return false;
|
|
746
|
-
}
|
|
747
|
-
const hiddenLogic = page.logic.filter((l) => l.type === 'hidden');
|
|
748
|
-
if (hiddenLogic.length === 0) {
|
|
749
|
-
return false;
|
|
750
|
-
}
|
|
751
|
-
for (const logic of hiddenLogic) {
|
|
752
|
-
if (typeof logic.condition === 'boolean') {
|
|
753
|
-
if (logic.condition) {
|
|
754
|
-
return true;
|
|
755
|
-
}
|
|
756
|
-
continue;
|
|
757
|
-
}
|
|
758
|
-
const condition = logic.condition;
|
|
759
|
-
const context = this.fieldContextRegistry.createDisplayOnlyContext(page.key || '', this.functionRegistry.getCustomFunctions());
|
|
760
|
-
if (evaluateCondition(condition, context)) {
|
|
761
|
-
return true;
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
|
-
return false;
|
|
765
|
-
}
|
|
766
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: PageOrchestratorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
767
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", type: PageOrchestratorComponent, isStandalone: true, selector: "div[page-orchestrator]", inputs: { pageFields: { classPropertyName: "pageFields", publicName: "pageFields", isSignal: true, isRequired: true, transformFunction: null }, form: { classPropertyName: "form", publicName: "form", isSignal: true, isRequired: true, transformFunction: null }, fieldSignalContext: { classPropertyName: "fieldSignalContext", publicName: "fieldSignalContext", isSignal: true, isRequired: true, transformFunction: null } }, host: { properties: { "attr.data-current-page": "state().currentPageIndex", "attr.data-total-pages": "state().totalPages" }, classAttribute: "df-page-orchestrator" }, providers: [
|
|
768
|
-
{
|
|
769
|
-
provide: FIELD_SIGNAL_CONTEXT,
|
|
770
|
-
useFactory: (orchestrator) => orchestrator.extendedFieldSignalContext(),
|
|
771
|
-
deps: [PageOrchestratorComponent],
|
|
772
|
-
},
|
|
773
|
-
], ngImport: i0, template: `
|
|
774
|
-
@for (pageField of pageFields(); track pageField.key; let i = $index) {
|
|
775
|
-
<!--
|
|
776
|
-
Skip pages hidden by 'hidden' logic conditions entirely. FormStateManager
|
|
777
|
-
derives schema/value/validators from config (not from mounted components),
|
|
778
|
-
so unmounting a hidden page has no effect on form state — only on render +
|
|
779
|
-
CD cost. This is the page-level equivalent of the existing field-level
|
|
780
|
-
\`@if (!field.hidden())\` gate in page-field.component.ts.
|
|
781
|
-
-->
|
|
782
|
-
@if (!pageHiddenStates()[i]) {
|
|
783
|
-
@if (isWithinPreloadWindow(i)) {
|
|
784
|
-
<!-- Current and preloaded neighbour pages: render immediately for flicker-free navigation -->
|
|
785
|
-
@defer (on immediate) {
|
|
786
|
-
<section
|
|
787
|
-
page-field
|
|
788
|
-
[field]="pageField"
|
|
789
|
-
[key]="pageField.key"
|
|
790
|
-
[pageIndex]="i"
|
|
791
|
-
[isVisible]="i === state().currentPageIndex"
|
|
792
|
-
></section>
|
|
793
|
-
} @placeholder {
|
|
794
|
-
<div class="df-page-placeholder" [attr.data-page-index]="i" [attr.data-page-key]="pageField.key"></div>
|
|
795
|
-
}
|
|
796
|
-
} @else {
|
|
797
|
-
<!--
|
|
798
|
-
Pages outside the preload window render only a lightweight placeholder
|
|
799
|
-
— they are NOT mounted. Mounting every page eagerly (previously via
|
|
800
|
-
\`@defer (on idle)\`, which fires almost immediately post-load) put all
|
|
801
|
-
fields of every page in the DOM at once: O(all-fields) initial render
|
|
802
|
-
blocking + O(all-fields) change detection on every keystroke, with no
|
|
803
|
-
visible benefit since only one page is shown at a time.
|
|
804
|
-
|
|
805
|
-
Form state (schema/value/validators) derives from config, not from
|
|
806
|
-
mounted components, so leaving a page unmounted does not affect
|
|
807
|
-
validity, derivations, or navigation. A page mounts the moment
|
|
808
|
-
navigation brings it into the preload window (above), which is
|
|
809
|
-
flicker-free for sequential next/previous navigation. The window size
|
|
810
|
-
is configurable via \`withPagePreload(n)\` or \`FormOptions.pagePreloadWindow\`.
|
|
811
|
-
-->
|
|
812
|
-
<div class="df-page-placeholder" [attr.data-page-index]="i" [attr.data-page-key]="pageField.key"></div>
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
}
|
|
816
|
-
`, isInline: true, styles: [":host{display:block;position:relative;width:100%}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, deferBlockDependencies: [() => [/* @ts-ignore */
|
|
817
|
-
import('./ng-forge-dynamic-forms-page-field.component-CwH22lRl.mjs').then(m => m.default)]] });
|
|
818
|
-
}
|
|
819
|
-
i0.ɵɵngDeclareClassMetadataAsync({ minVersion: "18.0.0", version: "22.0.1", ngImport: i0, type: PageOrchestratorComponent, resolveDeferredDeps: () => [/* @ts-ignore */
|
|
820
|
-
import('./ng-forge-dynamic-forms-page-field.component-CwH22lRl.mjs').then(m => m.default)], resolveMetadata: PageFieldComponent => ({ decorators: [{
|
|
821
|
-
type: Component,
|
|
822
|
-
args: [{ selector: 'div[page-orchestrator]', imports: [PageFieldComponent], template: `
|
|
823
|
-
@for (pageField of pageFields(); track pageField.key; let i = $index) {
|
|
824
|
-
<!--
|
|
825
|
-
Skip pages hidden by 'hidden' logic conditions entirely. FormStateManager
|
|
826
|
-
derives schema/value/validators from config (not from mounted components),
|
|
827
|
-
so unmounting a hidden page has no effect on form state — only on render +
|
|
828
|
-
CD cost. This is the page-level equivalent of the existing field-level
|
|
829
|
-
\`@if (!field.hidden())\` gate in page-field.component.ts.
|
|
830
|
-
-->
|
|
831
|
-
@if (!pageHiddenStates()[i]) {
|
|
832
|
-
@if (isWithinPreloadWindow(i)) {
|
|
833
|
-
<!-- Current and preloaded neighbour pages: render immediately for flicker-free navigation -->
|
|
834
|
-
@defer (on immediate) {
|
|
835
|
-
<section
|
|
836
|
-
page-field
|
|
837
|
-
[field]="pageField"
|
|
838
|
-
[key]="pageField.key"
|
|
839
|
-
[pageIndex]="i"
|
|
840
|
-
[isVisible]="i === state().currentPageIndex"
|
|
841
|
-
></section>
|
|
842
|
-
} @placeholder {
|
|
843
|
-
<div class="df-page-placeholder" [attr.data-page-index]="i" [attr.data-page-key]="pageField.key"></div>
|
|
844
|
-
}
|
|
845
|
-
} @else {
|
|
846
|
-
<!--
|
|
847
|
-
Pages outside the preload window render only a lightweight placeholder
|
|
848
|
-
— they are NOT mounted. Mounting every page eagerly (previously via
|
|
849
|
-
\`@defer (on idle)\`, which fires almost immediately post-load) put all
|
|
850
|
-
fields of every page in the DOM at once: O(all-fields) initial render
|
|
851
|
-
blocking + O(all-fields) change detection on every keystroke, with no
|
|
852
|
-
visible benefit since only one page is shown at a time.
|
|
853
|
-
|
|
854
|
-
Form state (schema/value/validators) derives from config, not from
|
|
855
|
-
mounted components, so leaving a page unmounted does not affect
|
|
856
|
-
validity, derivations, or navigation. A page mounts the moment
|
|
857
|
-
navigation brings it into the preload window (above), which is
|
|
858
|
-
flicker-free for sequential next/previous navigation. The window size
|
|
859
|
-
is configurable via \`withPagePreload(n)\` or \`FormOptions.pagePreloadWindow\`.
|
|
860
|
-
-->
|
|
861
|
-
<div class="df-page-placeholder" [attr.data-page-index]="i" [attr.data-page-key]="pageField.key"></div>
|
|
862
|
-
}
|
|
863
|
-
}
|
|
864
|
-
}
|
|
865
|
-
`, host: {
|
|
866
|
-
class: 'df-page-orchestrator',
|
|
867
|
-
'[attr.data-current-page]': 'state().currentPageIndex',
|
|
868
|
-
'[attr.data-total-pages]': 'state().totalPages',
|
|
869
|
-
}, providers: [
|
|
870
|
-
{
|
|
871
|
-
provide: FIELD_SIGNAL_CONTEXT,
|
|
872
|
-
useFactory: (orchestrator) => orchestrator.extendedFieldSignalContext(),
|
|
873
|
-
deps: [PageOrchestratorComponent],
|
|
874
|
-
},
|
|
875
|
-
], changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;position:relative;width:100%}\n"] }]
|
|
876
|
-
}], ctorParameters: () => [], propDecorators: { pageFields: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageFields", required: true }] }], form: [{ type: i0.Input, args: [{ isSignal: true, alias: "form", required: true }] }], fieldSignalContext: [{ type: i0.Input, args: [{ isSignal: true, alias: "fieldSignalContext", required: true }] }] } }) });
|
|
877
|
-
function collectLeafFieldKeys(fields) {
|
|
878
|
-
const keys = [];
|
|
879
|
-
for (const field of fields) {
|
|
880
|
-
if (isRowField(field) || isGenericContainerField(field)) {
|
|
881
|
-
keys.push(...collectLeafFieldKeys(field.fields));
|
|
882
|
-
}
|
|
883
|
-
else if (field.key) {
|
|
884
|
-
keys.push(field.key);
|
|
885
|
-
}
|
|
886
|
-
}
|
|
887
|
-
return keys;
|
|
888
|
-
}
|
|
889
|
-
|
|
890
|
-
const NORMALIZED_ARRAY_METADATA = Symbol('normalizedArrayMetadata');
|
|
891
|
-
function setNormalizedArrayMetadata(arrayField, metadata) {
|
|
892
|
-
arrayField[NORMALIZED_ARRAY_METADATA] = metadata;
|
|
893
|
-
}
|
|
894
|
-
function getNormalizedArrayMetadata(arrayField) {
|
|
895
|
-
return arrayField[NORMALIZED_ARRAY_METADATA];
|
|
896
|
-
}
|
|
897
|
-
|
|
898
|
-
function normalizeSimplifiedArrays(fields) {
|
|
899
|
-
return fields.flatMap((field) => {
|
|
900
|
-
if (isContainerField(field) && !isArrayField(field) && hasChildFields(field)) {
|
|
901
|
-
return [{ ...field, fields: normalizeSimplifiedArrays(field.fields) }];
|
|
902
|
-
}
|
|
903
|
-
if (isArrayField(field) && !isSimplifiedArrayField(field)) {
|
|
904
|
-
const normalizedItems = field.fields.map((item) => Array.isArray(item) ? normalizeSimplifiedArrays(item) : item);
|
|
905
|
-
return [{ ...field, fields: normalizedItems }];
|
|
906
|
-
}
|
|
907
|
-
if (isSimplifiedArrayField(field)) {
|
|
908
|
-
const { arrayField, addButton } = expandSimplifiedArray(field);
|
|
909
|
-
return addButton ? [arrayField, addButton] : [arrayField];
|
|
910
|
-
}
|
|
911
|
-
return [field];
|
|
912
|
-
});
|
|
913
|
-
}
|
|
914
|
-
const INVALID_ARRAY_CHILD_TYPES = new Set(['page', 'array']);
|
|
915
|
-
function validateSimplifiedTemplate(field) {
|
|
916
|
-
const { template, key } = field;
|
|
917
|
-
const isObjectTemplate = Array.isArray(template);
|
|
918
|
-
if (isObjectTemplate) {
|
|
919
|
-
const templateFields = template;
|
|
920
|
-
for (const tmpl of templateFields) {
|
|
921
|
-
if (INVALID_ARRAY_CHILD_TYPES.has(tmpl.type)) {
|
|
922
|
-
throw new DynamicFormError(`Simplified array "${key}" template contains a '${tmpl.type}' field (key: '${tmpl.key}'). ` +
|
|
923
|
-
`Only leaf fields, rows, and groups are allowed as array children.`);
|
|
924
|
-
}
|
|
925
|
-
}
|
|
926
|
-
}
|
|
927
|
-
else {
|
|
928
|
-
const singleTemplate = template;
|
|
929
|
-
if (INVALID_ARRAY_CHILD_TYPES.has(singleTemplate.type)) {
|
|
930
|
-
throw new DynamicFormError(`Simplified array "${key}" template has type '${singleTemplate.type}'. ` +
|
|
931
|
-
`Only leaf fields, rows, and groups are allowed as array children.`);
|
|
932
|
-
}
|
|
933
|
-
}
|
|
934
|
-
}
|
|
935
|
-
function expandSimplifiedArray(field) {
|
|
936
|
-
const { template, value = [], addButton, removeButton, key, logic, minLength, maxLength, wrappers, skipAutoWrappers, skipDefaultWrappers, validators, validationMessages, required, validateWhenHidden, } = field;
|
|
937
|
-
const isObjectTemplate = Array.isArray(template);
|
|
938
|
-
const values = value;
|
|
939
|
-
validateSimplifiedTemplate(field);
|
|
940
|
-
const items = values.map((v) => {
|
|
941
|
-
if (isObjectTemplate) {
|
|
942
|
-
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);
|
|
943
657
|
}
|
|
944
658
|
return buildPrimitiveItem(template, v);
|
|
945
659
|
});
|
|
@@ -1107,520 +821,104 @@ class SchemaRegistryService {
|
|
|
1107
821
|
registerSchema(schema) {
|
|
1108
822
|
this.registeredSchemas.set(schema.name, schema);
|
|
1109
823
|
}
|
|
1110
|
-
getSchema(name) {
|
|
1111
|
-
return this.registeredSchemas.get(name);
|
|
1112
|
-
}
|
|
1113
|
-
getAllSchemas() {
|
|
1114
|
-
return new Map(this.registeredSchemas);
|
|
1115
|
-
}
|
|
1116
|
-
clearSchemas() {
|
|
1117
|
-
this.registeredSchemas.clear();
|
|
1118
|
-
}
|
|
1119
|
-
resolveSchema(schema) {
|
|
1120
|
-
if (typeof schema === 'string') {
|
|
1121
|
-
return this.getSchema(schema) || null;
|
|
1122
|
-
}
|
|
1123
|
-
return schema;
|
|
1124
|
-
}
|
|
1125
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: SchemaRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1126
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: SchemaRegistryService });
|
|
1127
|
-
}
|
|
1128
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: SchemaRegistryService, decorators: [{
|
|
1129
|
-
type: Injectable
|
|
1130
|
-
}] });
|
|
1131
|
-
|
|
1132
|
-
const DEV_MODE = typeof ngDevMode === 'undefined' || !!ngDevMode;
|
|
1133
|
-
|
|
1134
|
-
const DEFAULT_DEBOUNCE_MS = 500;
|
|
1135
|
-
function createDebouncedEffect(source, callback, options) {
|
|
1136
|
-
const { ms = DEFAULT_DEBOUNCE_MS, injector } = options;
|
|
1137
|
-
const source$ = toObservable(source, { injector }).pipe(debounceTime(ms), distinctUntilChanged(), tap((value) => callback(value)));
|
|
1138
|
-
return toSignal(source$, { injector });
|
|
1139
|
-
}
|
|
1140
|
-
function createDebouncedSignal(source, options) {
|
|
1141
|
-
const { ms = DEFAULT_DEBOUNCE_MS, injector } = options;
|
|
1142
|
-
const source$ = toObservable(source, { injector }).pipe(debounceTime(ms), distinctUntilChanged());
|
|
1143
|
-
return toSignal(source$, { injector });
|
|
1144
|
-
}
|
|
1145
|
-
|
|
1146
|
-
function getConfigTrigger(config) {
|
|
1147
|
-
if (!isStateLogicConfig(config)) {
|
|
1148
|
-
return 'onChange';
|
|
1149
|
-
}
|
|
1150
|
-
return config.trigger ?? 'onChange';
|
|
1151
|
-
}
|
|
1152
|
-
function getConfigDebounceMs(config) {
|
|
1153
|
-
if (!isStateLogicConfig(config)) {
|
|
1154
|
-
return undefined;
|
|
1155
|
-
}
|
|
1156
|
-
if (config.trigger === 'debounced') {
|
|
1157
|
-
return config.debounceMs;
|
|
1158
|
-
}
|
|
1159
|
-
return undefined;
|
|
1160
|
-
}
|
|
1161
|
-
function applyLogic(config, fieldPath) {
|
|
1162
|
-
if (isDerivationLogicConfig(config))
|
|
1163
|
-
return;
|
|
1164
|
-
if (!isStateLogicConfig(config)) {
|
|
1165
|
-
if (DEV_MODE) {
|
|
1166
|
-
console.warn(`[Dynamic Forms] Unrecognized logic config type '${config.type}' in applyLogic. ` +
|
|
1167
|
-
'This config will be ignored. If this is a new logic type, ensure it is handled explicitly.');
|
|
1168
|
-
}
|
|
1169
|
-
return;
|
|
1170
|
-
}
|
|
1171
|
-
const path = fieldPath;
|
|
1172
|
-
const trigger = getConfigTrigger(config);
|
|
1173
|
-
const debounceMs = getConfigDebounceMs(config) ?? DEFAULT_DEBOUNCE_MS;
|
|
1174
|
-
if (typeof config.condition === 'boolean') {
|
|
1175
|
-
applyLogicFn(config.type, path, () => config.condition);
|
|
1176
|
-
return;
|
|
1177
|
-
}
|
|
1178
|
-
if (isFormStateCondition(config.condition)) {
|
|
1179
|
-
if (config.condition === 'formInvalid' || config.condition === 'pageInvalid') {
|
|
1180
|
-
const reason = config.condition === 'formInvalid'
|
|
1181
|
-
? `it would create a validation cycle against the form's own validity`
|
|
1182
|
-
: `a value field has no page context for it to resolve against`;
|
|
1183
|
-
throw new DynamicFormError(`The '${config.condition}' form-state condition is not supported on value-field logic because ${reason}. ` +
|
|
1184
|
-
`Use 'formSubmitting', or apply this condition to a button or container instead.`);
|
|
1185
|
-
}
|
|
1186
|
-
applyLogicFn(config.type, path, createFormStateLogicFunction(config.condition));
|
|
1187
|
-
return;
|
|
1188
|
-
}
|
|
1189
|
-
const logicFn = trigger === 'debounced'
|
|
1190
|
-
? createDebouncedLogicFunction(config.condition, debounceMs)
|
|
1191
|
-
: createLogicFunction(config.condition);
|
|
1192
|
-
applyLogicFn(config.type, path, logicFn);
|
|
1193
|
-
}
|
|
1194
|
-
function applyLogicFn(type, path, logicFn) {
|
|
1195
|
-
switch (type) {
|
|
1196
|
-
case 'hidden':
|
|
1197
|
-
hidden(path, logicFn);
|
|
1198
|
-
break;
|
|
1199
|
-
case 'readonly':
|
|
1200
|
-
readonly(path, logicFn);
|
|
1201
|
-
break;
|
|
1202
|
-
case 'disabled':
|
|
1203
|
-
disabled(path, logicFn);
|
|
1204
|
-
break;
|
|
1205
|
-
case 'required':
|
|
1206
|
-
required(path, { when: logicFn });
|
|
1207
|
-
break;
|
|
1208
|
-
default: {
|
|
1209
|
-
const _exhaustive = type;
|
|
1210
|
-
throw new DynamicFormError(`Unhandled state logic type: ${_exhaustive}`);
|
|
1211
|
-
}
|
|
1212
|
-
}
|
|
1213
|
-
}
|
|
1214
|
-
function applyMultipleLogic(configs, fieldPath) {
|
|
1215
|
-
configs.forEach((config) => applyLogic(config, fieldPath));
|
|
1216
|
-
}
|
|
1217
|
-
|
|
1218
|
-
function createTypePredicateFunction(predicate, logger) {
|
|
1219
|
-
return (value) => {
|
|
1220
|
-
try {
|
|
1221
|
-
const result = ExpressionParser.evaluate(predicate, { value });
|
|
1222
|
-
return Boolean(result);
|
|
1223
|
-
}
|
|
1224
|
-
catch (error) {
|
|
1225
|
-
logger.error('Error evaluating type predicate:', predicate, error);
|
|
1226
|
-
return false;
|
|
1227
|
-
}
|
|
1228
|
-
};
|
|
1229
|
-
}
|
|
1230
|
-
|
|
1231
|
-
function applySchema(config, fieldPath) {
|
|
1232
|
-
const logger = inject(DynamicFormLogger);
|
|
1233
|
-
const schemaRegistry = inject(SchemaRegistryService);
|
|
1234
|
-
const schema = schemaRegistry.resolveSchema(config.schema);
|
|
1235
|
-
if (!schema) {
|
|
1236
|
-
const availableSchemas = Array.from(schemaRegistry.getAllSchemas().keys()).join(', ') || '<none>';
|
|
1237
|
-
logger.error(`Schema not found: '${config.schema}'. ` +
|
|
1238
|
-
`Available schemas: ${availableSchemas}. ` +
|
|
1239
|
-
`Ensure the schema is registered in your schema registry.`);
|
|
1240
|
-
return;
|
|
1241
|
-
}
|
|
1242
|
-
const schemaFn = createSchemaFunction(schema, logger);
|
|
1243
|
-
const path = fieldPath;
|
|
1244
|
-
switch (config.type) {
|
|
1245
|
-
case 'apply':
|
|
1246
|
-
apply(path, schemaFn);
|
|
1247
|
-
break;
|
|
1248
|
-
case 'applyWhen':
|
|
1249
|
-
if (config.condition) {
|
|
1250
|
-
const conditionFn = createLogicFunction(config.condition);
|
|
1251
|
-
applyWhen(path, conditionFn, schemaFn);
|
|
1252
|
-
}
|
|
1253
|
-
break;
|
|
1254
|
-
case 'applyWhenValue':
|
|
1255
|
-
if (config.typePredicate) {
|
|
1256
|
-
const predicate = createTypePredicateFunction(config.typePredicate, logger);
|
|
1257
|
-
applyWhenValue(path, predicate, schemaFn);
|
|
1258
|
-
}
|
|
1259
|
-
break;
|
|
1260
|
-
case 'applyEach':
|
|
1261
|
-
applyEach(path, schemaFn);
|
|
1262
|
-
break;
|
|
1263
|
-
}
|
|
1264
|
-
}
|
|
1265
|
-
function createSchemaFunction(schema, logger) {
|
|
1266
|
-
return (path) => {
|
|
1267
|
-
schema.validators?.forEach((validatorConfig) => {
|
|
1268
|
-
applyValidator(validatorConfig, path);
|
|
1269
|
-
});
|
|
1270
|
-
schema.logic?.forEach((logicConfig) => {
|
|
1271
|
-
applyLogic(logicConfig, path);
|
|
1272
|
-
});
|
|
1273
|
-
schema.subSchemas?.forEach((subSchemaConfig) => {
|
|
1274
|
-
applySchema(subSchemaConfig, path);
|
|
1275
|
-
});
|
|
1276
|
-
};
|
|
1277
|
-
}
|
|
1278
|
-
|
|
1279
|
-
function combineAncestorHiddenLogics(logics) {
|
|
1280
|
-
if (logics.length === 0)
|
|
1281
|
-
return undefined;
|
|
1282
|
-
if (logics.length === 1)
|
|
1283
|
-
return logics[0];
|
|
1284
|
-
return (ctx) => logics.some((fn) => fn(ctx));
|
|
1285
|
-
}
|
|
1286
|
-
function resolveFieldOwnContext(fieldDef, parentContext) {
|
|
1287
|
-
if (fieldDef.validateWhenHidden === undefined)
|
|
1288
|
-
return parentContext;
|
|
1289
|
-
if (fieldDef.validateWhenHidden === parentContext.validateWhenHidden)
|
|
1290
|
-
return parentContext;
|
|
1291
|
-
return {
|
|
1292
|
-
...parentContext,
|
|
1293
|
-
validateWhenHidden: fieldDef.validateWhenHidden,
|
|
1294
|
-
};
|
|
1295
|
-
}
|
|
1296
|
-
function resolveDescendantContext(fieldDef, ownContext) {
|
|
1297
|
-
let ancestorAlwaysHidden = ownContext.ancestorAlwaysHidden;
|
|
1298
|
-
let ancestorHiddenLogics = ownContext.ancestorHiddenLogics;
|
|
1299
|
-
let mutated = false;
|
|
1300
|
-
let ancestorRequired = ownContext.ancestorRequired;
|
|
1301
|
-
if (fieldDef.required !== undefined && fieldDef.required !== ancestorRequired) {
|
|
1302
|
-
ancestorRequired = fieldDef.required;
|
|
1303
|
-
mutated = true;
|
|
1304
|
-
}
|
|
1305
|
-
if (fieldDef.hidden === true && !ancestorAlwaysHidden) {
|
|
1306
|
-
ancestorAlwaysHidden = true;
|
|
1307
|
-
mutated = true;
|
|
1308
|
-
}
|
|
1309
|
-
if (fieldDef.logic) {
|
|
1310
|
-
for (const logic of fieldDef.logic) {
|
|
1311
|
-
if (!isStateLogicConfig(logic) || logic.type !== 'hidden')
|
|
1312
|
-
continue;
|
|
1313
|
-
const condition = logic.condition;
|
|
1314
|
-
if (condition === true) {
|
|
1315
|
-
if (!ancestorAlwaysHidden) {
|
|
1316
|
-
ancestorAlwaysHidden = true;
|
|
1317
|
-
mutated = true;
|
|
1318
|
-
}
|
|
1319
|
-
continue;
|
|
1320
|
-
}
|
|
1321
|
-
if (condition === false)
|
|
1322
|
-
continue;
|
|
1323
|
-
if (typeof condition === 'string')
|
|
1324
|
-
continue;
|
|
1325
|
-
const fn = createLogicFunction(condition);
|
|
1326
|
-
ancestorHiddenLogics = ancestorHiddenLogics.length === 0 ? [fn] : [...ancestorHiddenLogics, fn];
|
|
1327
|
-
mutated = true;
|
|
1328
|
-
}
|
|
1329
|
-
}
|
|
1330
|
-
if (!mutated)
|
|
1331
|
-
return ownContext;
|
|
1332
|
-
return {
|
|
1333
|
-
validateWhenHidden: ownContext.validateWhenHidden,
|
|
1334
|
-
ancestorAlwaysHidden,
|
|
1335
|
-
ancestorHiddenLogics,
|
|
1336
|
-
ancestorRequired,
|
|
1337
|
-
};
|
|
1338
|
-
}
|
|
1339
|
-
|
|
1340
|
-
const DEFAULT_FIELD_TREE_MAPPING_CONTEXT = {
|
|
1341
|
-
validateWhenHidden: false,
|
|
1342
|
-
ancestorAlwaysHidden: false,
|
|
1343
|
-
ancestorHiddenLogics: [],
|
|
1344
|
-
};
|
|
1345
|
-
function applyStringValidators(path, config) {
|
|
1346
|
-
if (config.email) {
|
|
1347
|
-
email(path);
|
|
1348
|
-
}
|
|
1349
|
-
if (config.minLength !== undefined) {
|
|
1350
|
-
minLength(path, config.minLength);
|
|
1351
|
-
}
|
|
1352
|
-
if (config.maxLength !== undefined) {
|
|
1353
|
-
maxLength(path, config.maxLength);
|
|
1354
|
-
}
|
|
1355
|
-
if (config.pattern) {
|
|
1356
|
-
let regex;
|
|
1357
|
-
if (typeof config.pattern === 'string') {
|
|
1358
|
-
try {
|
|
1359
|
-
regex = new RegExp(config.pattern);
|
|
1360
|
-
}
|
|
1361
|
-
catch (e) {
|
|
1362
|
-
throw new DynamicFormError(`Invalid regex pattern: '${config.pattern}' — ${e instanceof Error ? e.message : String(e)}`);
|
|
1363
|
-
}
|
|
1364
|
-
}
|
|
1365
|
-
else {
|
|
1366
|
-
regex = config.pattern;
|
|
1367
|
-
}
|
|
1368
|
-
pattern(path, regex);
|
|
1369
|
-
}
|
|
1370
|
-
}
|
|
1371
|
-
function applyNumberValidators(path, config) {
|
|
1372
|
-
if (config.min !== undefined) {
|
|
1373
|
-
min(path, config.min);
|
|
1374
|
-
}
|
|
1375
|
-
if (config.max !== undefined) {
|
|
1376
|
-
max(path, config.max);
|
|
1377
|
-
}
|
|
1378
|
-
}
|
|
1379
|
-
function getStringValidationConfig(fieldDef) {
|
|
1380
|
-
if (!fieldDef.email && fieldDef.minLength === undefined && fieldDef.maxLength === undefined && !fieldDef.pattern) {
|
|
1381
|
-
return undefined;
|
|
1382
|
-
}
|
|
1383
|
-
return {
|
|
1384
|
-
email: fieldDef.email,
|
|
1385
|
-
minLength: fieldDef.minLength,
|
|
1386
|
-
maxLength: fieldDef.maxLength,
|
|
1387
|
-
pattern: fieldDef.pattern,
|
|
1388
|
-
};
|
|
1389
|
-
}
|
|
1390
|
-
function getNumberValidationConfig(fieldDef) {
|
|
1391
|
-
const minVal = fieldDef.min ?? fieldDef.minValue;
|
|
1392
|
-
const maxVal = fieldDef.max ?? fieldDef.maxValue;
|
|
1393
|
-
if (minVal === undefined && maxVal === undefined) {
|
|
1394
|
-
return undefined;
|
|
1395
|
-
}
|
|
1396
|
-
return { min: minVal, max: maxVal };
|
|
1397
|
-
}
|
|
1398
|
-
function mapFieldToForm(fieldDef, fieldPath, inheritedContext = DEFAULT_FIELD_TREE_MAPPING_CONTEXT) {
|
|
1399
|
-
const ownContext = resolveFieldOwnContext(fieldDef, inheritedContext);
|
|
1400
|
-
if (isPageField(fieldDef) || isRowField(fieldDef) || isGenericContainerField(fieldDef)) {
|
|
1401
|
-
const descendantContext = resolveDescendantContext(fieldDef, ownContext);
|
|
1402
|
-
mapContainerChildren(fieldDef.fields, fieldPath, descendantContext);
|
|
1403
|
-
return;
|
|
1404
|
-
}
|
|
1405
|
-
if (isGroupField(fieldDef)) {
|
|
1406
|
-
const descendantContext = resolveDescendantContext(fieldDef, ownContext);
|
|
1407
|
-
applyContainerValidators(fieldDef, fieldPath, descendantContext);
|
|
1408
|
-
mapContainerChildren(fieldDef.fields, fieldPath, descendantContext);
|
|
1409
|
-
return;
|
|
1410
|
-
}
|
|
1411
|
-
if (isArrayField(fieldDef)) {
|
|
1412
|
-
const descendantContext = resolveDescendantContext(fieldDef, ownContext);
|
|
1413
|
-
mapArrayFieldToForm(fieldDef, fieldPath, descendantContext);
|
|
1414
|
-
return;
|
|
1415
|
-
}
|
|
1416
|
-
mapLeafField(fieldDef, fieldPath, ownContext);
|
|
1417
|
-
}
|
|
1418
|
-
function mapContainerChildren(fields, parentPath, inheritedContext) {
|
|
1419
|
-
if (!fields)
|
|
1420
|
-
return;
|
|
1421
|
-
const pathRecord = parentPath;
|
|
1422
|
-
for (const field of fields) {
|
|
1423
|
-
if (isPageField(field) || isRowField(field) || isGenericContainerField(field)) {
|
|
1424
|
-
const layoutOwn = resolveFieldOwnContext(field, inheritedContext);
|
|
1425
|
-
const layoutDescendant = resolveDescendantContext(field, layoutOwn);
|
|
1426
|
-
mapContainerChildren(field.fields, parentPath, layoutDescendant);
|
|
1427
|
-
continue;
|
|
1428
|
-
}
|
|
1429
|
-
if (!field.key)
|
|
1430
|
-
continue;
|
|
1431
|
-
const childPath = pathRecord[field.key];
|
|
1432
|
-
if (childPath) {
|
|
1433
|
-
mapFieldToForm(field, childPath, inheritedContext);
|
|
1434
|
-
}
|
|
1435
|
-
}
|
|
1436
|
-
}
|
|
1437
|
-
function isValidationStateLogic(config) {
|
|
1438
|
-
return isStateLogicConfig(config) && config.type === 'required';
|
|
1439
|
-
}
|
|
1440
|
-
function mapLeafField(fieldDef, fieldPath, context) {
|
|
1441
|
-
const validationField = fieldDef;
|
|
1442
|
-
const path = fieldPath;
|
|
1443
|
-
applyFieldState(fieldDef, path);
|
|
1444
|
-
if (validationField.logic) {
|
|
1445
|
-
for (const config of validationField.logic) {
|
|
1446
|
-
if (!isValidationStateLogic(config)) {
|
|
1447
|
-
applyLogic(config, fieldPath);
|
|
1448
|
-
}
|
|
1449
|
-
}
|
|
1450
|
-
}
|
|
1451
|
-
if (validationField.schemas) {
|
|
1452
|
-
for (const config of validationField.schemas) {
|
|
1453
|
-
applySchema(config, fieldPath);
|
|
1454
|
-
}
|
|
1455
|
-
}
|
|
1456
|
-
const applyValidation = (validationPath) => {
|
|
1457
|
-
applySimpleValidationRules(validationField, validationPath, context);
|
|
1458
|
-
if (validationField.validators) {
|
|
1459
|
-
for (const config of validationField.validators) {
|
|
1460
|
-
applyValidator(config, validationPath);
|
|
1461
|
-
}
|
|
1462
|
-
}
|
|
1463
|
-
if (validationField.logic) {
|
|
1464
|
-
for (const config of validationField.logic) {
|
|
1465
|
-
if (isValidationStateLogic(config)) {
|
|
1466
|
-
applyLogic(config, validationPath);
|
|
1467
|
-
}
|
|
1468
|
-
}
|
|
1469
|
-
}
|
|
1470
|
-
};
|
|
1471
|
-
applyHiddenGatedValidation(path, context, applyValidation);
|
|
1472
|
-
}
|
|
1473
|
-
function applyHiddenGatedValidation(path, context, apply) {
|
|
1474
|
-
if (context.validateWhenHidden) {
|
|
1475
|
-
apply(path);
|
|
1476
|
-
return;
|
|
1477
|
-
}
|
|
1478
|
-
if (context.ancestorAlwaysHidden) {
|
|
1479
|
-
return;
|
|
1480
|
-
}
|
|
1481
|
-
const ancestorHiddenLogic = combineAncestorHiddenLogics(context.ancestorHiddenLogics);
|
|
1482
|
-
applyWhen(path, (ctx) => {
|
|
1483
|
-
if (ctx.state.hidden())
|
|
1484
|
-
return false;
|
|
1485
|
-
if (ancestorHiddenLogic && ancestorHiddenLogic(ctx))
|
|
1486
|
-
return false;
|
|
1487
|
-
return true;
|
|
1488
|
-
}, schema((subPath) => apply(subPath)));
|
|
1489
|
-
}
|
|
1490
|
-
function applyContainerValidators(fieldDef, fieldPath, context) {
|
|
1491
|
-
const validators = fieldDef.validators;
|
|
1492
|
-
if (!validators || validators.length === 0)
|
|
1493
|
-
return;
|
|
1494
|
-
applyHiddenGatedValidation(fieldPath, context, (validationPath) => {
|
|
1495
|
-
for (const config of validators) {
|
|
1496
|
-
applyValidator(config, validationPath, 'tree');
|
|
1497
|
-
}
|
|
1498
|
-
});
|
|
1499
|
-
}
|
|
1500
|
-
function applySimpleValidationRules(fieldDef, path, context) {
|
|
1501
|
-
const declaresRequiredLogic = fieldDef.logic?.some(isValidationStateLogic) ?? false;
|
|
1502
|
-
if (fieldDef.required ?? (declaresRequiredLogic ? false : context.ancestorRequired)) {
|
|
1503
|
-
required(path);
|
|
1504
|
-
}
|
|
1505
|
-
const stringConfig = getStringValidationConfig(fieldDef);
|
|
1506
|
-
if (stringConfig) {
|
|
1507
|
-
applyStringValidators(path, stringConfig);
|
|
1508
|
-
}
|
|
1509
|
-
const numberConfig = getNumberValidationConfig(fieldDef);
|
|
1510
|
-
if (numberConfig) {
|
|
1511
|
-
applyNumberValidators(path, numberConfig);
|
|
1512
|
-
}
|
|
1513
|
-
}
|
|
1514
|
-
function applyFieldState(fieldDef, path) {
|
|
1515
|
-
if (fieldDef.disabled) {
|
|
1516
|
-
disabled(path);
|
|
1517
|
-
}
|
|
1518
|
-
if (fieldDef.readonly) {
|
|
1519
|
-
readonly(path);
|
|
1520
|
-
}
|
|
1521
|
-
if (fieldDef.hidden) {
|
|
1522
|
-
hidden(path, () => true);
|
|
1523
|
-
}
|
|
1524
|
-
}
|
|
1525
|
-
function mapArrayFieldToForm(arrayField, fieldPath, context) {
|
|
1526
|
-
if (!isArrayField(arrayField)) {
|
|
1527
|
-
return;
|
|
824
|
+
getSchema(name) {
|
|
825
|
+
return this.registeredSchemas.get(name);
|
|
1528
826
|
}
|
|
1529
|
-
|
|
1530
|
-
|
|
827
|
+
getAllSchemas() {
|
|
828
|
+
return new Map(this.registeredSchemas);
|
|
1531
829
|
}
|
|
1532
|
-
|
|
1533
|
-
|
|
830
|
+
clearSchemas() {
|
|
831
|
+
this.registeredSchemas.clear();
|
|
1534
832
|
}
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
const metadataTemplate = getNormalizedArrayMetadata(arrayField)?.template;
|
|
1539
|
-
if (metadataTemplate) {
|
|
1540
|
-
itemDefinitions = [
|
|
1541
|
-
Array.isArray(metadataTemplate) ? [...metadataTemplate] : metadataTemplate,
|
|
1542
|
-
];
|
|
833
|
+
resolveSchema(schema) {
|
|
834
|
+
if (typeof schema === 'string') {
|
|
835
|
+
return this.getSchema(schema) || null;
|
|
1543
836
|
}
|
|
837
|
+
return schema;
|
|
1544
838
|
}
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
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;
|
|
1550
856
|
}
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
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
|
+
});
|
|
881
|
+
}
|
|
1556
882
|
}
|
|
1557
|
-
else if (
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
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)));
|
|
888
|
+
}
|
|
1561
889
|
}
|
|
1562
|
-
else {
|
|
1563
|
-
|
|
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
|
+
});
|
|
1564
898
|
}
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
return;
|
|
1571
|
-
}
|
|
1572
|
-
const itemSchema = schema((itemPath) => {
|
|
1573
|
-
const pathRecord = itemPath;
|
|
1574
|
-
for (const templateField of allObjectFields) {
|
|
1575
|
-
if (isRowField(templateField) || isPageField(templateField) || isGenericContainerField(templateField)) {
|
|
1576
|
-
const layoutOwn = resolveFieldOwnContext(templateField, context);
|
|
1577
|
-
const layoutDescendant = resolveDescendantContext(templateField, layoutOwn);
|
|
1578
|
-
mapContainerChildren(templateField.fields, itemPath, layoutDescendant);
|
|
1579
|
-
}
|
|
1580
|
-
else if (isGroupField(templateField)) {
|
|
1581
|
-
const groupKey = templateField.key;
|
|
1582
|
-
if (groupKey) {
|
|
1583
|
-
const groupPath = pathRecord[groupKey];
|
|
1584
|
-
if (groupPath) {
|
|
1585
|
-
mapFieldToForm(templateField, groupPath, context);
|
|
1586
|
-
}
|
|
1587
|
-
}
|
|
1588
|
-
else {
|
|
1589
|
-
const groupOwn = resolveFieldOwnContext(templateField, context);
|
|
1590
|
-
mapContainerChildren(templateField.fields, itemPath, resolveDescendantContext(templateField, groupOwn));
|
|
1591
|
-
}
|
|
1592
|
-
}
|
|
1593
|
-
else {
|
|
1594
|
-
const fieldKey = templateField.key;
|
|
1595
|
-
if (fieldKey) {
|
|
1596
|
-
const fieldPathForKey = pathRecord[fieldKey];
|
|
1597
|
-
if (fieldPathForKey) {
|
|
1598
|
-
mapFieldToForm(templateField, fieldPathForKey, context);
|
|
1599
|
-
}
|
|
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];
|
|
1600
904
|
}
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
for (const field of itemFields) {
|
|
1609
|
-
if (isRowField(field) || isPageField(field) || isGenericContainerField(field)) {
|
|
1610
|
-
const containerKey = `__container_${field.type}_${JSON.stringify(field.fields?.map((f) => f.key))}`;
|
|
1611
|
-
if (!seenKeys.has(containerKey)) {
|
|
1612
|
-
seenKeys.add(containerKey);
|
|
1613
|
-
allFields.push(field);
|
|
1614
|
-
}
|
|
905
|
+
return flattenFields([...itemDef], registry, options);
|
|
906
|
+
});
|
|
907
|
+
result.push({
|
|
908
|
+
...field,
|
|
909
|
+
fields: flattenedItemTemplates,
|
|
910
|
+
key: field.key || `auto_array_${autoKeyCounter++}`,
|
|
911
|
+
});
|
|
1615
912
|
}
|
|
1616
913
|
else {
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
}
|
|
914
|
+
const key = field.key || `auto_field_${autoKeyCounter++}`;
|
|
915
|
+
result.push({
|
|
916
|
+
...field,
|
|
917
|
+
key,
|
|
918
|
+
});
|
|
1622
919
|
}
|
|
1623
920
|
}
|
|
921
|
+
return result;
|
|
1624
922
|
}
|
|
1625
923
|
|
|
1626
924
|
function getFieldDefaultValue(field, registry) {
|
|
@@ -1726,160 +1024,6 @@ function getFieldDefaultValue(field, registry) {
|
|
|
1726
1024
|
return '';
|
|
1727
1025
|
}
|
|
1728
1026
|
|
|
1729
|
-
function applyFormLevelSchema(path, formLevelSchema) {
|
|
1730
|
-
if (isStandardSchemaMarker(formLevelSchema)) {
|
|
1731
|
-
validateStandardSchema(path, formLevelSchema.schema);
|
|
1732
|
-
}
|
|
1733
|
-
else if (typeof formLevelSchema === 'function') {
|
|
1734
|
-
formLevelSchema(path);
|
|
1735
|
-
}
|
|
1736
|
-
}
|
|
1737
|
-
function createFormLevelSchema(formLevelSchema) {
|
|
1738
|
-
return schema((path) => {
|
|
1739
|
-
applyFormLevelSchema(path, formLevelSchema);
|
|
1740
|
-
});
|
|
1741
|
-
}
|
|
1742
|
-
|
|
1743
|
-
const VALIDATION_EXECUTION_DEFAULTS = new InjectionToken('VALIDATION_EXECUTION_DEFAULTS', {
|
|
1744
|
-
providedIn: 'root',
|
|
1745
|
-
factory: () => ({
|
|
1746
|
-
validateWhenHidden: false,
|
|
1747
|
-
}),
|
|
1748
|
-
});
|
|
1749
|
-
|
|
1750
|
-
function createSchemaFromFields(fields, registry, options = {}) {
|
|
1751
|
-
const validationExecutionDefaults = inject(VALIDATION_EXECUTION_DEFAULTS);
|
|
1752
|
-
const { formLevelSchema } = options;
|
|
1753
|
-
const rootContext = {
|
|
1754
|
-
validateWhenHidden: options.validateWhenHidden ?? validationExecutionDefaults.validateWhenHidden,
|
|
1755
|
-
ancestorAlwaysHidden: false,
|
|
1756
|
-
ancestorHiddenLogics: [],
|
|
1757
|
-
};
|
|
1758
|
-
return schema((path) => {
|
|
1759
|
-
for (const fieldDef of fields) {
|
|
1760
|
-
const valueHandling = getFieldValueHandling(fieldDef.type, registry);
|
|
1761
|
-
if (valueHandling === 'exclude') {
|
|
1762
|
-
continue;
|
|
1763
|
-
}
|
|
1764
|
-
if (valueHandling === 'flatten' && hasChildFields(fieldDef)) {
|
|
1765
|
-
const flattenedOwn = resolveFieldOwnContext(fieldDef, rootContext);
|
|
1766
|
-
const flattenedContext = resolveDescendantContext(fieldDef, flattenedOwn);
|
|
1767
|
-
for (const childField of normalizeFieldsArray(fieldDef.fields)) {
|
|
1768
|
-
if (!childField.key)
|
|
1769
|
-
continue;
|
|
1770
|
-
const childPath = path[childField.key];
|
|
1771
|
-
if (childPath) {
|
|
1772
|
-
mapFieldToForm(childField, childPath, flattenedContext);
|
|
1773
|
-
}
|
|
1774
|
-
}
|
|
1775
|
-
continue;
|
|
1776
|
-
}
|
|
1777
|
-
const fieldPath = path[fieldDef.key];
|
|
1778
|
-
if (!fieldPath) {
|
|
1779
|
-
continue;
|
|
1780
|
-
}
|
|
1781
|
-
mapFieldToForm(fieldDef, fieldPath, rootContext);
|
|
1782
|
-
}
|
|
1783
|
-
if (formLevelSchema) {
|
|
1784
|
-
applyFormLevelSchema(path, formLevelSchema);
|
|
1785
|
-
}
|
|
1786
|
-
});
|
|
1787
|
-
}
|
|
1788
|
-
function fieldsToDefaultValues(fields, registry) {
|
|
1789
|
-
const defaultValues = {};
|
|
1790
|
-
for (const field of fields) {
|
|
1791
|
-
if (!field.key)
|
|
1792
|
-
continue;
|
|
1793
|
-
const valueHandling = getFieldValueHandling(field.type, registry);
|
|
1794
|
-
if (valueHandling === 'flatten') {
|
|
1795
|
-
continue;
|
|
1796
|
-
}
|
|
1797
|
-
const value = getFieldDefaultValue(field, registry);
|
|
1798
|
-
if (value !== undefined) {
|
|
1799
|
-
defaultValues[field.key] = value;
|
|
1800
|
-
}
|
|
1801
|
-
}
|
|
1802
|
-
return defaultValues;
|
|
1803
|
-
}
|
|
1804
|
-
|
|
1805
|
-
function inheritContainerExclusion(container, child) {
|
|
1806
|
-
const inheritedHidden = container.hidden === true || child.inheritedExclusionState?.hidden === true;
|
|
1807
|
-
const inheritedDisabled = container.disabled === true || child.inheritedExclusionState?.disabled === true;
|
|
1808
|
-
const inheritedReadonly = container.readonly === true || child.inheritedExclusionState?.readonly === true;
|
|
1809
|
-
const hasInheritedState = inheritedHidden || inheritedDisabled || inheritedReadonly;
|
|
1810
|
-
const containerExcludeConfig = container.excludeValueIfHidden !== undefined ||
|
|
1811
|
-
container.excludeValueIfDisabled !== undefined ||
|
|
1812
|
-
container.excludeValueIfReadonly !== undefined;
|
|
1813
|
-
if (!hasInheritedState && !containerExcludeConfig) {
|
|
1814
|
-
return child;
|
|
1815
|
-
}
|
|
1816
|
-
return {
|
|
1817
|
-
...child,
|
|
1818
|
-
...(hasInheritedState
|
|
1819
|
-
? { inheritedExclusionState: { hidden: inheritedHidden, disabled: inheritedDisabled, readonly: inheritedReadonly } }
|
|
1820
|
-
: {}),
|
|
1821
|
-
excludeValueIfHidden: child.excludeValueIfHidden ?? container.excludeValueIfHidden,
|
|
1822
|
-
excludeValueIfDisabled: child.excludeValueIfDisabled ?? container.excludeValueIfDisabled,
|
|
1823
|
-
excludeValueIfReadonly: child.excludeValueIfReadonly ?? container.excludeValueIfReadonly,
|
|
1824
|
-
};
|
|
1825
|
-
}
|
|
1826
|
-
function flattenFields(fields, registry, options = {}) {
|
|
1827
|
-
const result = [];
|
|
1828
|
-
let autoKeyCounter = 0;
|
|
1829
|
-
for (const field of fields) {
|
|
1830
|
-
const valueHandling = getFieldValueHandling(field.type, registry);
|
|
1831
|
-
if (options.preserveRows && (isRowField(field) || isGenericContainerField(field))) {
|
|
1832
|
-
if (field.fields) {
|
|
1833
|
-
const flattenedChildren = flattenFields(normalizeFieldsArray(field.fields), registry, options);
|
|
1834
|
-
const autoPrefix = isRowField(field) ? 'auto_row' : 'auto_container';
|
|
1835
|
-
result.push({
|
|
1836
|
-
...field,
|
|
1837
|
-
fields: flattenedChildren,
|
|
1838
|
-
key: field.key || `${autoPrefix}_${autoKeyCounter++}`,
|
|
1839
|
-
});
|
|
1840
|
-
}
|
|
1841
|
-
}
|
|
1842
|
-
else if (valueHandling === 'flatten' && 'fields' in field) {
|
|
1843
|
-
if (field.fields) {
|
|
1844
|
-
const fields = field.fields;
|
|
1845
|
-
const flattenedChildren = flattenFields(normalizeFieldsArray(fields), registry, options);
|
|
1846
|
-
result.push(...flattenedChildren.map((child) => inheritContainerExclusion(field, child)));
|
|
1847
|
-
}
|
|
1848
|
-
}
|
|
1849
|
-
else if (isGroupField(field)) {
|
|
1850
|
-
const childFieldsArray = Object.values(field.fields);
|
|
1851
|
-
const flattenedChildren = flattenFields(childFieldsArray, registry, options);
|
|
1852
|
-
result.push({
|
|
1853
|
-
...field,
|
|
1854
|
-
fields: flattenedChildren,
|
|
1855
|
-
key: field.key || `auto_group_${autoKeyCounter++}`,
|
|
1856
|
-
});
|
|
1857
|
-
}
|
|
1858
|
-
else if (isArrayField(field)) {
|
|
1859
|
-
const flattenedItemTemplates = field.fields.map((itemDef) => {
|
|
1860
|
-
if (!Array.isArray(itemDef)) {
|
|
1861
|
-
const flattened = flattenFields([itemDef], registry, options);
|
|
1862
|
-
return flattened[0];
|
|
1863
|
-
}
|
|
1864
|
-
return flattenFields([...itemDef], registry, options);
|
|
1865
|
-
});
|
|
1866
|
-
result.push({
|
|
1867
|
-
...field,
|
|
1868
|
-
fields: flattenedItemTemplates,
|
|
1869
|
-
key: field.key || `auto_array_${autoKeyCounter++}`,
|
|
1870
|
-
});
|
|
1871
|
-
}
|
|
1872
|
-
else {
|
|
1873
|
-
const key = field.key || `auto_field_${autoKeyCounter++}`;
|
|
1874
|
-
result.push({
|
|
1875
|
-
...field,
|
|
1876
|
-
key,
|
|
1877
|
-
});
|
|
1878
|
-
}
|
|
1879
|
-
}
|
|
1880
|
-
return result;
|
|
1881
|
-
}
|
|
1882
|
-
|
|
1883
1027
|
function createContainerFieldProcessors() {
|
|
1884
1028
|
const flattenCache = new WeakMap();
|
|
1885
1029
|
const memoizedFlattenFields = (fields, registry) => {
|
|
@@ -2318,9 +1462,14 @@ const COMPONENT_CACHE = new InjectionToken('COMPONENT_CACHE', {
|
|
|
2318
1462
|
providedIn: 'root',
|
|
2319
1463
|
factory: () => new Map(),
|
|
2320
1464
|
});
|
|
1465
|
+
const COMPONENT_LOAD_CACHE = new InjectionToken('COMPONENT_LOAD_CACHE', {
|
|
1466
|
+
providedIn: 'root',
|
|
1467
|
+
factory: () => new Map(),
|
|
1468
|
+
});
|
|
2321
1469
|
function injectFieldRegistry() {
|
|
2322
1470
|
const registry = inject(FIELD_REGISTRY);
|
|
2323
1471
|
const componentCache = inject(COMPONENT_CACHE);
|
|
1472
|
+
const componentLoadCache = inject(COMPONENT_LOAD_CACHE);
|
|
2324
1473
|
return {
|
|
2325
1474
|
getType(name) {
|
|
2326
1475
|
return registry.get(name);
|
|
@@ -2340,12 +1489,26 @@ function injectFieldRegistry() {
|
|
|
2340
1489
|
if (cached) {
|
|
2341
1490
|
return cached;
|
|
2342
1491
|
}
|
|
1492
|
+
const pending = componentLoadCache.get(name);
|
|
1493
|
+
if (pending) {
|
|
1494
|
+
return pending;
|
|
1495
|
+
}
|
|
2343
1496
|
try {
|
|
2344
|
-
const
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
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;
|
|
2349
1512
|
}
|
|
2350
1513
|
catch (error) {
|
|
2351
1514
|
throw new DynamicFormError(`Failed to load component for field type "${name}": ${error}`);
|
|
@@ -2414,7 +1577,9 @@ function validateRegexPattern(pattern, fieldKey, errors) {
|
|
|
2414
1577
|
new RegExp(pattern);
|
|
2415
1578
|
}
|
|
2416
1579
|
catch (e) {
|
|
2417
|
-
|
|
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}'`);
|
|
2418
1583
|
}
|
|
2419
1584
|
}
|
|
2420
1585
|
function validateNoDuplicateKeys(allKeys) {
|
|
@@ -2430,7 +1595,9 @@ function validateNoDuplicateKeys(allKeys) {
|
|
|
2430
1595
|
const duplicateList = Array.from(duplicates)
|
|
2431
1596
|
.map((k) => `'${k}'`)
|
|
2432
1597
|
.join(', ');
|
|
2433
|
-
|
|
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}.`);
|
|
2434
1601
|
}
|
|
2435
1602
|
}
|
|
2436
1603
|
function validateNoDomIdCollisions(domIds) {
|
|
@@ -2446,9 +1613,11 @@ function validateNoDomIdCollisions(domIds) {
|
|
|
2446
1613
|
const duplicateList = Array.from(duplicates)
|
|
2447
1614
|
.map((k) => `'${k}'`)
|
|
2448
1615
|
.join(', ');
|
|
2449
|
-
|
|
2450
|
-
`
|
|
2451
|
-
|
|
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}.`);
|
|
2452
1621
|
}
|
|
2453
1622
|
}
|
|
2454
1623
|
function validateFieldTypesRegistered(allTypes, registry, logger) {
|
|
@@ -2478,12 +1647,15 @@ function validateFormConfig(fields, registry, logger) {
|
|
|
2478
1647
|
const list = Array.from(new Set(data.dottedKeys))
|
|
2479
1648
|
.map((k) => `'${k}'`)
|
|
2480
1649
|
.join(', ');
|
|
2481
|
-
|
|
2482
|
-
`(
|
|
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}.`);
|
|
2483
1654
|
}
|
|
2484
1655
|
validateNoDuplicateKeys(data.keys);
|
|
2485
1656
|
validateNoDomIdCollisions(data.domIds);
|
|
2486
|
-
|
|
1657
|
+
if (DEV_MODE)
|
|
1658
|
+
validateFieldTypesRegistered(data.types, registry, logger);
|
|
2487
1659
|
if (data.regexErrors.length > 0) {
|
|
2488
1660
|
throw new DynamicFormError(data.regexErrors.length === 1
|
|
2489
1661
|
? data.regexErrors[0]
|
|
@@ -2698,6 +1870,33 @@ function resolveExclusionConfig(global, form, field) {
|
|
|
2698
1870
|
excludeValueIfReadonly: field?.excludeValueIfReadonly ?? form?.excludeValueIfReadonly ?? global.excludeValueIfReadonly,
|
|
2699
1871
|
};
|
|
2700
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
|
+
}
|
|
2701
1900
|
function shouldExcludeField(field, fieldState, config) {
|
|
2702
1901
|
const state = fieldState();
|
|
2703
1902
|
const inherited = field.inheritedExclusionState;
|
|
@@ -2715,7 +1914,10 @@ function shouldExcludeField(field, fieldState, config) {
|
|
|
2715
1914
|
}
|
|
2716
1915
|
return false;
|
|
2717
1916
|
}
|
|
2718
|
-
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
|
+
}
|
|
2719
1921
|
const result = {};
|
|
2720
1922
|
for (const field of schemaFields) {
|
|
2721
1923
|
const key = field.key;
|
|
@@ -2751,7 +1953,7 @@ function filterFormValue(rawValue, schemaFields, formTree, registry, globalDefau
|
|
|
2751
1953
|
if (groupValue && typeof groupValue === 'object') {
|
|
2752
1954
|
const childFields = Object.values(field.fields);
|
|
2753
1955
|
const nestedTree = fieldState;
|
|
2754
|
-
result[key] = filterFormValue(groupValue, childFields, nestedTree, registry, globalDefaults, formOptions);
|
|
1956
|
+
result[key] = filterFormValue(groupValue, childFields, nestedTree, registry, globalDefaults, formOptions, true);
|
|
2755
1957
|
}
|
|
2756
1958
|
else {
|
|
2757
1959
|
result[key] = groupValue;
|
|
@@ -3064,7 +2266,7 @@ class FormStateMachine {
|
|
|
3064
2266
|
if (!isTransitioningState(state) || state.phase !== Phase.Restoring) {
|
|
3065
2267
|
return { state, sideEffects: [] };
|
|
3066
2268
|
}
|
|
3067
|
-
|
|
2269
|
+
if (DEV_MODE && !state.pendingFormSetup) {
|
|
3068
2270
|
this.config.logger.warn('handleRestoreComplete: pendingFormSetup was not set — falling back to recomputing. ' +
|
|
3069
2271
|
'This indicates a bug in the transition flow.');
|
|
3070
2272
|
}
|
|
@@ -3127,7 +2329,7 @@ class FormStateMachine {
|
|
|
3127
2329
|
const state = untracked(() => this._state());
|
|
3128
2330
|
if (!isTransitioningState(state) || state.phase !== Phase.Restoring)
|
|
3129
2331
|
return;
|
|
3130
|
-
|
|
2332
|
+
if (DEV_MODE && !state.pendingFormSetup) {
|
|
3131
2333
|
this.config.logger.warn('RestoreValues effect: pendingFormSetup was not set — falling back to recomputing.');
|
|
3132
2334
|
}
|
|
3133
2335
|
const formSetup = state.pendingFormSetup ?? this.config.createFormSetup(state.pendingConfig);
|
|
@@ -3269,13 +2471,6 @@ class FormStateManager {
|
|
|
3269
2471
|
return detectFormMode(config.fields || []);
|
|
3270
2472
|
}, /* @ts-ignore */
|
|
3271
2473
|
...(ngDevMode ? [{ debugName: "formModeDetection" }] : /* istanbul ignore next */ []));
|
|
3272
|
-
formConfigValidation = computed(() => {
|
|
3273
|
-
const config = this.activeConfig();
|
|
3274
|
-
if (!config)
|
|
3275
|
-
return { isValid: true, errors: [], warnings: [] };
|
|
3276
|
-
return FormModeValidator.validateFormConfiguration(config.fields || []);
|
|
3277
|
-
}, /* @ts-ignore */
|
|
3278
|
-
...(ngDevMode ? [{ debugName: "formConfigValidation" }] : /* istanbul ignore next */ []));
|
|
3279
2474
|
effectiveFormOptions = computed(() => {
|
|
3280
2475
|
const config = this.activeConfig();
|
|
3281
2476
|
const configOptions = config?.options || {};
|
|
@@ -3283,6 +2478,8 @@ class FormStateManager {
|
|
|
3283
2478
|
return { ...configOptions, ...inputOptions };
|
|
3284
2479
|
}, /* @ts-ignore */
|
|
3285
2480
|
...(ngDevMode ? [{ debugName: "effectiveFormOptions" }] : /* istanbul ignore next */ []));
|
|
2481
|
+
pendingPageRequest = signal(null, /* @ts-ignore */
|
|
2482
|
+
...(ngDevMode ? [{ debugName: "pendingPageRequest" }] : /* istanbul ignore next */ []));
|
|
3286
2483
|
pageFieldDefinitions = computed(() => {
|
|
3287
2484
|
const config = this.activeConfig();
|
|
3288
2485
|
const mode = this.formModeDetection().mode;
|
|
@@ -3319,22 +2516,71 @@ class FormStateManager {
|
|
|
3319
2516
|
}
|
|
3320
2517
|
return this.createEmptyFormSetup(registry);
|
|
3321
2518
|
}, /* @ts-ignore */
|
|
3322
|
-
...(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 }) });
|
|
3323
2539
|
defaultValues = linkedSignal(() => this.formSetup().defaultValues, /* @ts-ignore */
|
|
3324
2540
|
...(ngDevMode ? [{ debugName: "defaultValues" }] : /* istanbul ignore next */ []));
|
|
3325
2541
|
validKeys = computed(() => {
|
|
3326
2542
|
const schemaFields = this.formSetup().schemaFields;
|
|
3327
2543
|
if (!schemaFields || schemaFields.length === 0)
|
|
3328
2544
|
return undefined;
|
|
3329
|
-
|
|
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;
|
|
3330
2552
|
}, /* @ts-ignore */
|
|
3331
2553
|
...(ngDevMode ? [{ debugName: "validKeys" }] : /* istanbul ignore next */ []));
|
|
3332
|
-
|
|
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(() => {
|
|
3333
2567
|
const setup = this.formSetup();
|
|
3334
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)
|
|
3335
2581
|
return {};
|
|
3336
2582
|
const snapshot = {};
|
|
3337
|
-
collectFieldStateSnapshot(
|
|
2583
|
+
collectFieldStateSnapshot(schemaFields, asFieldTreeRecord(this.form()), [], snapshot);
|
|
3338
2584
|
return snapshot;
|
|
3339
2585
|
}, /* @ts-ignore */
|
|
3340
2586
|
...(ngDevMode ? [{ debugName: "fieldStateSnapshot" }] : /* istanbul ignore next */ []));
|
|
@@ -3342,8 +2588,22 @@ class FormStateManager {
|
|
|
3342
2588
|
...(ngDevMode ? [{ debugName: "excludedValueStore" }] : /* istanbul ignore next */ []));
|
|
3343
2589
|
prevFieldStateSnapshot = signal({}, /* @ts-ignore */
|
|
3344
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 */ []));
|
|
3345
2604
|
entity = linkedSignal(() => {
|
|
3346
|
-
|
|
2605
|
+
this.externalValue();
|
|
2606
|
+
const inputValue = untracked(() => this.deps.value());
|
|
3347
2607
|
const defaults = this.defaultValues();
|
|
3348
2608
|
const keys = this.validKeys();
|
|
3349
2609
|
const saved = this.excludedValueStore();
|
|
@@ -3363,22 +2623,62 @@ class FormStateManager {
|
|
|
3363
2623
|
debugName: 'FormStateManager.entity',
|
|
3364
2624
|
equal: isEqual,
|
|
3365
2625
|
});
|
|
3366
|
-
|
|
2626
|
+
loadFormSchemaService = injectAsync(() => import('./ng-forge-dynamic-forms-form-schema.service-C3cVSmft.mjs'));
|
|
2627
|
+
formSchemaRequest = computed(() => {
|
|
3367
2628
|
const setup = this.formSetup();
|
|
3368
2629
|
const config = this.activeConfig();
|
|
3369
|
-
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;
|
|
3370
2650
|
return undefined;
|
|
3371
|
-
if (setup.schemaFields?.length) {
|
|
3372
|
-
return createSchemaFromFields(setup.schemaFields, setup.registry, {
|
|
3373
|
-
formLevelSchema: config.schema,
|
|
3374
|
-
validateWhenHidden: this.effectiveFormOptions().validateWhenHidden,
|
|
3375
|
-
});
|
|
3376
2651
|
}
|
|
3377
|
-
if (
|
|
3378
|
-
|
|
2652
|
+
if (this.formSchemaResource.status() === 'error') {
|
|
2653
|
+
this.schemaCache.current = undefined;
|
|
2654
|
+
return undefined;
|
|
3379
2655
|
}
|
|
3380
|
-
|
|
3381
|
-
|
|
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 */
|
|
3382
2682
|
...(ngDevMode ? [{ debugName: "formSchema" }] : /* istanbul ignore next */ []));
|
|
3383
2683
|
form = computed(() => {
|
|
3384
2684
|
const schema = this.formSchema();
|
|
@@ -3409,13 +2709,16 @@ class FormStateManager {
|
|
|
3409
2709
|
...(ngDevMode ? [{ debugName: "fieldSignalContext" }] : /* istanbul ignore next */ []));
|
|
3410
2710
|
formInstance = computed(() => this.form()(), /* @ts-ignore */
|
|
3411
2711
|
...(ngDevMode ? [{ debugName: "formInstance" }] : /* istanbul ignore next */ []));
|
|
3412
|
-
|
|
2712
|
+
modelValue = computed(() => this.formInstance().value(), /* @ts-ignore */
|
|
2713
|
+
...(ngDevMode ? [{ debugName: "modelValue" }] : /* istanbul ignore next */ []));
|
|
2714
|
+
formValue = computed(() => this.modelValue(), /* @ts-ignore */
|
|
3413
2715
|
...(ngDevMode ? [{ debugName: "formValue" }] : /* istanbul ignore next */ []));
|
|
3414
2716
|
filteredFormValue = computed(() => {
|
|
3415
|
-
const rawValue = this.
|
|
2717
|
+
const rawValue = this.modelValue();
|
|
3416
2718
|
const setup = this.formSetup();
|
|
2719
|
+
const schemaFields = setup.schemaFields;
|
|
3417
2720
|
const options = this.effectiveFormOptions();
|
|
3418
|
-
if (!
|
|
2721
|
+
if (!this.formSchemaReady() || !schemaFields || schemaFields.length === 0) {
|
|
3419
2722
|
return rawValue;
|
|
3420
2723
|
}
|
|
3421
2724
|
const formTree = this.form();
|
|
@@ -3425,14 +2728,15 @@ class FormStateManager {
|
|
|
3425
2728
|
excludeValueIfReadonly: options.excludeValueIfReadonly,
|
|
3426
2729
|
};
|
|
3427
2730
|
const fieldTreeRecord = asFieldTreeRecord(formTree);
|
|
3428
|
-
return filterFormValue(rawValue,
|
|
2731
|
+
return filterFormValue(rawValue, schemaFields, fieldTreeRecord, setup.registry, this.valueExclusionDefaults, formOptions, this.valueExclusionEnabled());
|
|
3429
2732
|
}, /* @ts-ignore */
|
|
3430
2733
|
...(ngDevMode ? [{ debugName: "filteredFormValue" }] : /* istanbul ignore next */ []));
|
|
3431
2734
|
boundFormValue = computed(() => {
|
|
3432
|
-
const rawValue = this.
|
|
2735
|
+
const rawValue = this.modelValue();
|
|
3433
2736
|
const setup = this.formSetup();
|
|
2737
|
+
const schemaFields = setup.schemaFields;
|
|
3434
2738
|
const options = this.effectiveFormOptions();
|
|
3435
|
-
if (!
|
|
2739
|
+
if (!this.formSchemaReady() || !schemaFields || schemaFields.length === 0) {
|
|
3436
2740
|
return rawValue;
|
|
3437
2741
|
}
|
|
3438
2742
|
const formTree = this.form();
|
|
@@ -3442,26 +2746,26 @@ class FormStateManager {
|
|
|
3442
2746
|
excludeValueIfReadonly: options.excludeValueIfReadonly,
|
|
3443
2747
|
};
|
|
3444
2748
|
const fieldTreeRecord = asFieldTreeRecord(formTree);
|
|
3445
|
-
return filterFormValue(rawValue,
|
|
2749
|
+
return filterFormValue(rawValue, schemaFields, fieldTreeRecord, setup.registry, BOUND_VALUE_EXCLUSION_BASELINE, formOptions, this.boundValueExclusionEnabled());
|
|
3446
2750
|
}, /* @ts-ignore */
|
|
3447
2751
|
...(ngDevMode ? [{ debugName: "boundFormValue" }] : /* istanbul ignore next */ []));
|
|
3448
|
-
|
|
2752
|
+
valid = computed(() => this.formSchemaReady() && this.formSchemaResource.status() !== 'error' && this.formInstance().valid(), /* @ts-ignore */
|
|
3449
2753
|
...(ngDevMode ? [{ debugName: "valid" }] : /* istanbul ignore next */ []));
|
|
3450
|
-
invalid = computed(() => this.formInstance().invalid(), /* @ts-ignore */
|
|
2754
|
+
invalid = computed(() => !this.formSchemaReady() || this.formSchemaResource.status() === 'error' || this.formInstance().invalid(), /* @ts-ignore */
|
|
3451
2755
|
...(ngDevMode ? [{ debugName: "invalid" }] : /* istanbul ignore next */ []));
|
|
3452
|
-
dirty = computed(() => this.formInstance().dirty(), /* @ts-ignore */
|
|
2756
|
+
dirty = computed(() => this.formSchemaReady() && this.formInstance().dirty(), /* @ts-ignore */
|
|
3453
2757
|
...(ngDevMode ? [{ debugName: "dirty" }] : /* istanbul ignore next */ []));
|
|
3454
|
-
touched = computed(() => this.formInstance().touched(), /* @ts-ignore */
|
|
2758
|
+
touched = computed(() => this.formSchemaReady() && this.formInstance().touched(), /* @ts-ignore */
|
|
3455
2759
|
...(ngDevMode ? [{ debugName: "touched" }] : /* istanbul ignore next */ []));
|
|
3456
|
-
errors = computed(() => this.formInstance().errors(), /* @ts-ignore */
|
|
2760
|
+
errors = computed(() => (this.formSchemaReady() ? this.formInstance().errors() : []), /* @ts-ignore */
|
|
3457
2761
|
...(ngDevMode ? [{ debugName: "errors" }] : /* istanbul ignore next */ []));
|
|
3458
2762
|
disabled = computed(() => {
|
|
3459
2763
|
const optionsDisabled = this.effectiveFormOptions().disabled;
|
|
3460
|
-
const formDisabled = this.formInstance().disabled();
|
|
2764
|
+
const formDisabled = this.formSchemaReady() && this.formInstance().disabled();
|
|
3461
2765
|
return optionsDisabled ?? formDisabled;
|
|
3462
2766
|
}, /* @ts-ignore */
|
|
3463
2767
|
...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
|
|
3464
|
-
submitting = computed(() => this.formInstance().submitting(), /* @ts-ignore */
|
|
2768
|
+
submitting = computed(() => this.formSchemaReady() && this.formInstance().submitting(), /* @ts-ignore */
|
|
3465
2769
|
...(ngDevMode ? [{ debugName: "submitting" }] : /* istanbul ignore next */ []));
|
|
3466
2770
|
fieldsSource = computed(() => {
|
|
3467
2771
|
const state = this.machine.state();
|
|
@@ -3576,6 +2880,10 @@ class FormStateManager {
|
|
|
3576
2880
|
}), takeUntilDestroyed(this.destroyRef));
|
|
3577
2881
|
this.reset$ = this.eventBus.on('form-reset').pipe(map(() => undefined), takeUntilDestroyed(this.destroyRef));
|
|
3578
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 }));
|
|
3579
2887
|
this.machine = createFormStateMachine({
|
|
3580
2888
|
injector: this.injector,
|
|
3581
2889
|
destroyRef: this.destroyRef,
|
|
@@ -3639,6 +2947,11 @@ class FormStateManager {
|
|
|
3639
2947
|
this.eventBus.dispatch(FormSubmitEvent);
|
|
3640
2948
|
}
|
|
3641
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
|
+
});
|
|
3642
2955
|
explicitEffect([this.deps.config], ([config]) => {
|
|
3643
2956
|
this.fieldLoadingErrors.set([]);
|
|
3644
2957
|
const state = this.machine.currentState;
|
|
@@ -3690,17 +3003,24 @@ class FormStateManager {
|
|
|
3690
3003
|
explicitEffect([this.boundFormValue], ([currentBound]) => {
|
|
3691
3004
|
const currentValue = this.deps.value();
|
|
3692
3005
|
if (!isEqual(currentBound, currentValue)) {
|
|
3006
|
+
this.lastOutwardValue.current = currentBound;
|
|
3007
|
+
this.lastOutwardValue.pending = true;
|
|
3693
3008
|
this.deps.value.set(currentBound);
|
|
3694
3009
|
}
|
|
3695
3010
|
});
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
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
|
+
}
|
|
3704
3024
|
}
|
|
3705
3025
|
setupEventHandlers() {
|
|
3706
3026
|
this.eventBus
|
|
@@ -3718,74 +3038,479 @@ class FormStateManager {
|
|
|
3718
3038
|
this.schemaRegistry.registerSchema(schema);
|
|
3719
3039
|
});
|
|
3720
3040
|
}
|
|
3721
|
-
if (!customFnConfig) {
|
|
3722
|
-
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
|
+
};
|
|
3723
3309
|
}
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
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
|
+
};
|
|
3728
3317
|
}
|
|
3729
|
-
|
|
3730
|
-
|
|
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);
|
|
3731
3326
|
}
|
|
3732
|
-
|
|
3733
|
-
this.
|
|
3327
|
+
if (options?.validate === false) {
|
|
3328
|
+
return this.executePageChange(pageIndex);
|
|
3734
3329
|
}
|
|
3735
|
-
|
|
3736
|
-
|
|
3330
|
+
const disableWhenPageInvalid = this.formOptions?.()?.nextButton?.disableWhenPageInvalid ?? true;
|
|
3331
|
+
if (!disableWhenPageInvalid) {
|
|
3332
|
+
return this.executePageChange(pageIndex);
|
|
3737
3333
|
}
|
|
3738
|
-
|
|
3739
|
-
this.
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
createFormSetupFromConfig(fields, mode, registry) {
|
|
3743
|
-
const normalizedFields = normalizeSimplifiedArrays(fields);
|
|
3744
|
-
validateFormConfig(normalizedFields, registry, this.logger);
|
|
3745
|
-
const source = this.deps.source?.() ?? 'inline';
|
|
3746
|
-
const { fields: sanitizedFields, warnings: addonWarnings } = walkAndValidateAddons(normalizedFields, registry, this.addonKindRegistry, source);
|
|
3747
|
-
const nextKeys = new Set();
|
|
3748
|
-
for (const w of addonWarnings) {
|
|
3749
|
-
const key = addonWarningKey(w);
|
|
3750
|
-
nextKeys.add(key);
|
|
3751
|
-
if (!this.lastAddonWarningKeys.has(key)) {
|
|
3752
|
-
this.logger.warn(formatAddonWarning(w));
|
|
3753
|
-
}
|
|
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);
|
|
3754
3338
|
}
|
|
3755
|
-
|
|
3756
|
-
const validatedFields = sanitizedFields;
|
|
3757
|
-
const flattenedFields = this.fieldProcessors.memoizedFlattenFields(validatedFields, registry);
|
|
3758
|
-
const flattenedFieldsForRendering = this.fieldProcessors.memoizedFlattenFieldsForRendering(validatedFields, registry);
|
|
3759
|
-
const fieldsById = this.fieldProcessors.memoizedKeyBy(flattenedFields);
|
|
3760
|
-
const defaultValues = this.fieldProcessors.memoizedDefaultValues(fieldsById, registry);
|
|
3761
|
-
const fieldsToRender = mode === 'paged' ? [] : flattenedFieldsForRendering;
|
|
3339
|
+
this.executePageChange(firstInvalidPage);
|
|
3762
3340
|
return {
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
defaultValues,
|
|
3767
|
-
mode,
|
|
3768
|
-
registry,
|
|
3341
|
+
success: false,
|
|
3342
|
+
newPageIndex: firstInvalidPage,
|
|
3343
|
+
error: `Cannot navigate to page ${pageIndex}: page ${firstInvalidPage} has invalid fields`,
|
|
3769
3344
|
};
|
|
3770
3345
|
}
|
|
3771
|
-
|
|
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);
|
|
3772
3373
|
return {
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
defaultValues: {},
|
|
3776
|
-
mode: 'non-paged',
|
|
3777
|
-
registry,
|
|
3374
|
+
success: true,
|
|
3375
|
+
newPageIndex: pageIndex,
|
|
3778
3376
|
};
|
|
3779
3377
|
}
|
|
3780
|
-
|
|
3781
|
-
|
|
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;
|
|
3782
3389
|
}
|
|
3783
|
-
|
|
3784
|
-
|
|
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)]] });
|
|
3785
3471
|
}
|
|
3786
|
-
i0.ɵɵ
|
|
3787
|
-
|
|
3788
|
-
|
|
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 }] }] } }) });
|
|
3789
3514
|
|
|
3790
3515
|
function traverseFieldsWithContext(fields, context, visitor, hooks) {
|
|
3791
3516
|
for (const field of fields) {
|
|
@@ -3864,7 +3589,7 @@ function createDerivationRenderGate() {
|
|
|
3864
3589
|
if (!has || engine() !== 'idle')
|
|
3865
3590
|
return;
|
|
3866
3591
|
engine.set('loading');
|
|
3867
|
-
pendingTasks.run(() => import('./ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-
|
|
3592
|
+
pendingTasks.run(() => import('./ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-BfxYqAQA.mjs')
|
|
3868
3593
|
.then(({ bootstrapDerivationOrchestrator }) => {
|
|
3869
3594
|
runInInjectionContext(injector, () => bootstrapDerivationOrchestrator());
|
|
3870
3595
|
engine.set('wired');
|
|
@@ -4171,22 +3896,30 @@ class DynamicForm {
|
|
|
4171
3896
|
submitting = this.stateManager.submitting;
|
|
4172
3897
|
fieldLoadingErrors = this.stateManager.fieldLoadingErrors;
|
|
4173
3898
|
derivationReady = inject(DERIVATION_RENDER_GATE);
|
|
4174
|
-
shouldRender = computed(() => this.stateManager.shouldRender() && this.derivationReady(), /* @ts-ignore */
|
|
3899
|
+
shouldRender = computed(() => this.stateManager.shouldRender() && this.stateManager.formSchemaReady() && this.derivationReady(), /* @ts-ignore */
|
|
4175
3900
|
...(ngDevMode ? [{ debugName: "shouldRender" }] : /* istanbul ignore next */ []));
|
|
4176
3901
|
resolvedFields = this.stateManager.resolvedFields;
|
|
4177
3902
|
fieldWindowing = computed(() => resolveFieldWindowing(this.globalFieldWindowing, this.effectiveFormOptions()?.fieldWindowing), /* @ts-ignore */
|
|
4178
3903
|
...(ngDevMode ? [{ debugName: "fieldWindowing" }] : /* istanbul ignore next */ []));
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
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));
|
|
4182
3920
|
}, /* @ts-ignore */
|
|
4183
|
-
...(ngDevMode ? [{ debugName: "
|
|
4184
|
-
|
|
4185
|
-
eventBus: this.eventBus,
|
|
4186
|
-
totalComponentsCount: this.totalComponentsCount,
|
|
4187
|
-
injector: this.injector,
|
|
4188
|
-
componentId: this.componentId,
|
|
4189
|
-
});
|
|
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 }));
|
|
4190
3923
|
validityChange = outputFromObservable(toObservable(this.valid));
|
|
4191
3924
|
dirtyChange = outputFromObservable(toObservable(this.dirty));
|
|
4192
3925
|
submitted = outputFromObservable(this.stateManager.submitted$);
|
|
@@ -4194,9 +3927,27 @@ class DynamicForm {
|
|
|
4194
3927
|
cleared = outputFromObservable(this.eventBus.on('form-clear'));
|
|
4195
3928
|
events = outputFromObservable(this.eventBus.events$);
|
|
4196
3929
|
initialized = outputFromObservable(this.initialized$);
|
|
3930
|
+
activePageInitialized = outputFromObservable(this.eventBus.on('active-page-initialized'));
|
|
4197
3931
|
onPageChange = outputFromObservable(this.eventBus.on('page-change'));
|
|
4198
3932
|
onPageNavigationStateChange = outputFromObservable(this.eventBus.on('pager-state'));
|
|
4199
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));
|
|
4200
3951
|
this.dispatcher?.connect(this.eventBus);
|
|
4201
3952
|
this.destroyRef.onDestroy(() => this.dispatcher?.disconnect());
|
|
4202
3953
|
inject(FORM_INITIALIZER, { optional: true });
|
|
@@ -4221,8 +3972,8 @@ class DynamicForm {
|
|
|
4221
3972
|
this.eventBus.dispatch(ComponentInitializedEvent, 'dynamic-form', this.componentId);
|
|
4222
3973
|
}
|
|
4223
3974
|
});
|
|
4224
|
-
explicitEffect([this.resolvedFields, this.formModeDetection], ([
|
|
4225
|
-
if (mode === 'non-paged'
|
|
3975
|
+
explicitEffect([this.resolvedFields, this.formModeDetection, this.activeConfig], ([, { mode }, config]) => {
|
|
3976
|
+
if (config && mode === 'non-paged') {
|
|
4226
3977
|
afterNextRender(() => {
|
|
4227
3978
|
this.eventBus.dispatch(ComponentInitializedEvent, 'dynamic-form', this.componentId);
|
|
4228
3979
|
}, { injector: this.injector });
|
|
@@ -4258,7 +4009,7 @@ class DynamicForm {
|
|
|
4258
4009
|
});
|
|
4259
4010
|
}
|
|
4260
4011
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: DynamicForm, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4261
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", type: DynamicForm, isStandalone: true, selector: "form[dynamic-form]", inputs: { config: { classPropertyName: "config", publicName: "dynamic-form", isSignal: true, isRequired: true, transformFunction: null }, formOptions: { classPropertyName: "formOptions", publicName: "formOptions", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, source: { classPropertyName: "source", publicName: "source", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", validityChange: "validityChange", dirtyChange: "dirtyChange", submitted: "submitted", reset: "reset", cleared: "cleared", events: "events", initialized: "initialized", onPageChange: "onPageChange", onPageNavigationStateChange: "onPageNavigationStateChange" }, host: { attributes: { "novalidate": "" }, listeners: { "submit": "onNativeSubmit($event)" }, properties: { "class.disabled": "disabled()", "class.df-form-paged": "formModeDetection().mode === \"paged\"", "class.df-form-non-paged": "formModeDetection().mode === \"non-paged\"", "attr.data-form-mode": "formModeDetection().mode", "attr.id": "idPrefix() || null" }, classAttribute: "df-dynamic-form df-form" }, providers: [
|
|
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: [
|
|
4262
4013
|
provideDynamicFormDI(),
|
|
4263
4014
|
DfFieldTemplateRegistry,
|
|
4264
4015
|
{ provide: DF_FIELD_TEMPLATES, useFactory: () => inject(DfFieldTemplateRegistry).map },
|
|
@@ -4270,12 +4021,8 @@ class DynamicForm {
|
|
|
4270
4021
|
}
|
|
4271
4022
|
@case ('non-paged') {
|
|
4272
4023
|
@for (field of resolvedFields(); track field.key; let i = $index) {
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
the directive's gate stops a same-CD-pass mount before [formField] can warn.
|
|
4276
|
-
Don't dedupe one without the other — see DfFieldOutlet.renderReady. -->
|
|
4277
|
-
@if (!field.hidden()) {
|
|
4278
|
-
@if (windowsField(field, i)) {
|
|
4024
|
+
@if (windowsField(field, i)) {
|
|
4025
|
+
@if (!field.hidden()) {
|
|
4279
4026
|
@defer (on viewport) {
|
|
4280
4027
|
<ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
|
|
4281
4028
|
} @placeholder {
|
|
@@ -4286,9 +4033,11 @@ class DynamicForm {
|
|
|
4286
4033
|
aria-hidden="true"
|
|
4287
4034
|
></div>
|
|
4288
4035
|
}
|
|
4289
|
-
} @else {
|
|
4290
|
-
<ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
|
|
4291
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" />
|
|
4292
4041
|
}
|
|
4293
4042
|
}
|
|
4294
4043
|
}
|
|
@@ -4309,12 +4058,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
|
|
|
4309
4058
|
}
|
|
4310
4059
|
@case ('non-paged') {
|
|
4311
4060
|
@for (field of resolvedFields(); track field.key; let i = $index) {
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
the directive's gate stops a same-CD-pass mount before [formField] can warn.
|
|
4315
|
-
Don't dedupe one without the other — see DfFieldOutlet.renderReady. -->
|
|
4316
|
-
@if (!field.hidden()) {
|
|
4317
|
-
@if (windowsField(field, i)) {
|
|
4061
|
+
@if (windowsField(field, i)) {
|
|
4062
|
+
@if (!field.hidden()) {
|
|
4318
4063
|
@defer (on viewport) {
|
|
4319
4064
|
<ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
|
|
4320
4065
|
} @placeholder {
|
|
@@ -4325,9 +4070,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
|
|
|
4325
4070
|
aria-hidden="true"
|
|
4326
4071
|
></div>
|
|
4327
4072
|
}
|
|
4328
|
-
} @else {
|
|
4329
|
-
<ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
|
|
4330
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" />
|
|
4331
4078
|
}
|
|
4332
4079
|
}
|
|
4333
4080
|
}
|
|
@@ -4349,29 +4096,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
|
|
|
4349
4096
|
'[attr.id]': 'idPrefix() || null',
|
|
4350
4097
|
'(submit)': 'onNativeSubmit($event)',
|
|
4351
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"] }]
|
|
4352
|
-
}], ctorParameters: () => [], propDecorators: { config: [{ type: i0.Input, args: [{ isSignal: true, alias: "dynamic-form", required: true }] }], formOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "formOptions", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], source: [{ type: i0.Input, args: [{ isSignal: true, alias: "source", required: false }] }], _projectedTemplates: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => DfTemplate), { isSignal: true }] }], validityChange: [{ type: i0.Output, args: ["validityChange"] }], dirtyChange: [{ type: i0.Output, args: ["dirtyChange"] }], submitted: [{ type: i0.Output, args: ["submitted"] }], reset: [{ type: i0.Output, args: ["reset"] }], cleared: [{ type: i0.Output, args: ["cleared"] }], events: [{ type: i0.Output, args: ["events"] }], initialized: [{ type: i0.Output, args: ["initialized"] }], onPageChange: [{ type: i0.Output, args: ["onPageChange"] }], onPageNavigationStateChange: [{ type: i0.Output, args: ["onPageNavigationStateChange"] }] } });
|
|
4353
|
-
function countContainersRecursive(fields) {
|
|
4354
|
-
let count = 0;
|
|
4355
|
-
for (const field of fields) {
|
|
4356
|
-
if (isContainerField(field)) {
|
|
4357
|
-
count += 1;
|
|
4358
|
-
if (hasChildFields(field)) {
|
|
4359
|
-
const children = field.fields;
|
|
4360
|
-
if (Array.isArray(children)) {
|
|
4361
|
-
for (const child of children) {
|
|
4362
|
-
if (Array.isArray(child)) {
|
|
4363
|
-
count += countContainersRecursive(child);
|
|
4364
|
-
}
|
|
4365
|
-
else if (child != null && isContainerField(child)) {
|
|
4366
|
-
count += countContainersRecursive([child]);
|
|
4367
|
-
}
|
|
4368
|
-
}
|
|
4369
|
-
}
|
|
4370
|
-
}
|
|
4371
|
-
}
|
|
4372
|
-
}
|
|
4373
|
-
return count;
|
|
4374
|
-
}
|
|
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"] }] } });
|
|
4375
4100
|
|
|
4376
4101
|
const BUILT_IN_ADDON_TYPES = [
|
|
4377
4102
|
{
|
|
@@ -4419,28 +4144,28 @@ const DISPLAY_FIELD_TYPES_BASE = {
|
|
|
4419
4144
|
const BUILT_IN_FIELDS = [
|
|
4420
4145
|
{
|
|
4421
4146
|
name: 'row',
|
|
4422
|
-
loadComponent: () => import('./ng-forge-dynamic-forms-container-field.component-
|
|
4147
|
+
loadComponent: () => import('./ng-forge-dynamic-forms-container-field.component-Cj2msZp_.mjs'),
|
|
4423
4148
|
mapper: rowFieldMapper,
|
|
4424
4149
|
valueHandling: 'flatten',
|
|
4425
4150
|
...CONTAINER_FIELD_TYPES_BASE,
|
|
4426
4151
|
},
|
|
4427
4152
|
{
|
|
4428
4153
|
name: 'group',
|
|
4429
|
-
loadComponent: () => import('./ng-forge-dynamic-forms-group-field.component-
|
|
4154
|
+
loadComponent: () => import('./ng-forge-dynamic-forms-group-field.component-DN6X506r.mjs'),
|
|
4430
4155
|
mapper: groupFieldMapper,
|
|
4431
4156
|
valueHandling: 'include',
|
|
4432
4157
|
...CONTAINER_FIELD_TYPES_BASE,
|
|
4433
4158
|
},
|
|
4434
4159
|
{
|
|
4435
4160
|
name: 'array',
|
|
4436
|
-
loadComponent: () => import('./ng-forge-dynamic-forms-array-field.component-
|
|
4161
|
+
loadComponent: () => import('./ng-forge-dynamic-forms-array-field.component-DK7dAQv7.mjs'),
|
|
4437
4162
|
mapper: arrayFieldMapper,
|
|
4438
4163
|
valueHandling: 'include',
|
|
4439
4164
|
...CONTAINER_FIELD_TYPES_BASE,
|
|
4440
4165
|
},
|
|
4441
4166
|
{
|
|
4442
4167
|
name: 'page',
|
|
4443
|
-
loadComponent: () => import('./ng-forge-dynamic-forms-page-field.component-
|
|
4168
|
+
loadComponent: () => import('./ng-forge-dynamic-forms-page-field.component-D16ey8r-.mjs'),
|
|
4444
4169
|
mapper: pageFieldMapper,
|
|
4445
4170
|
valueHandling: 'flatten',
|
|
4446
4171
|
...CONTAINER_FIELD_TYPES_BASE,
|
|
@@ -4458,7 +4183,7 @@ const BUILT_IN_FIELDS = [
|
|
|
4458
4183
|
},
|
|
4459
4184
|
{
|
|
4460
4185
|
name: 'container',
|
|
4461
|
-
loadComponent: () => import('./ng-forge-dynamic-forms-container-field.component-
|
|
4186
|
+
loadComponent: () => import('./ng-forge-dynamic-forms-container-field.component-Cj2msZp_.mjs'),
|
|
4462
4187
|
mapper: containerFieldMapper,
|
|
4463
4188
|
valueHandling: 'flatten',
|
|
4464
4189
|
...CONTAINER_FIELD_TYPES_BASE,
|
|
@@ -4553,7 +4278,7 @@ function provideDynamicForm(...items) {
|
|
|
4553
4278
|
const logger = inject(DynamicFormLogger);
|
|
4554
4279
|
const registry = new Map();
|
|
4555
4280
|
fields.forEach((fieldType) => {
|
|
4556
|
-
|
|
4281
|
+
if (DEV_MODE && registry.has(fieldType.name)) {
|
|
4557
4282
|
logger.warn(`Field type "${fieldType.name}" is already registered. Overwriting.`);
|
|
4558
4283
|
}
|
|
4559
4284
|
registry.set(fieldType.name, fieldType);
|
|
@@ -4581,13 +4306,15 @@ function provideDynamicForm(...items) {
|
|
|
4581
4306
|
const registry = new Map();
|
|
4582
4307
|
const builtInNames = new Set(BUILT_IN_WRAPPERS.map((w) => w.wrapperName));
|
|
4583
4308
|
BUILT_IN_WRAPPERS.forEach((wrapperType) => registry.set(wrapperType.wrapperName, wrapperType));
|
|
4584
|
-
|
|
4309
|
+
const seenCustom = DEV_MODE ? new Set() : null;
|
|
4585
4310
|
customWrappers.forEach((wrapperType) => {
|
|
4586
4311
|
const name = wrapperType.wrapperName;
|
|
4587
|
-
if (seenCustom
|
|
4588
|
-
|
|
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);
|
|
4589
4317
|
}
|
|
4590
|
-
seenCustom.add(name);
|
|
4591
4318
|
registry.set(name, wrapperType);
|
|
4592
4319
|
});
|
|
4593
4320
|
return registry;
|
|
@@ -4619,7 +4346,7 @@ function provideDynamicForm(...items) {
|
|
|
4619
4346
|
registry.set(def.type, def);
|
|
4620
4347
|
}
|
|
4621
4348
|
for (const def of contributed) {
|
|
4622
|
-
|
|
4349
|
+
if (DEV_MODE && registry.has(def.type)) {
|
|
4623
4350
|
logger.warn(`Addon type "${def.type}" is already registered. Overwriting.`);
|
|
4624
4351
|
}
|
|
4625
4352
|
registry.set(def.type, def);
|
|
@@ -4681,6 +4408,13 @@ function withFieldWindowing(config) {
|
|
|
4681
4408
|
return createFeature('field-windowing', [{ provide: FIELD_WINDOWING, useValue: resolved }]);
|
|
4682
4409
|
}
|
|
4683
4410
|
|
|
4411
|
+
const VALIDATION_EXECUTION_DEFAULTS = new InjectionToken('VALIDATION_EXECUTION_DEFAULTS', {
|
|
4412
|
+
providedIn: 'root',
|
|
4413
|
+
factory: () => ({
|
|
4414
|
+
validateWhenHidden: false,
|
|
4415
|
+
}),
|
|
4416
|
+
});
|
|
4417
|
+
|
|
4684
4418
|
function withValidationExecutionDefaults(config) {
|
|
4685
4419
|
const resolved = {
|
|
4686
4420
|
validateWhenHidden: config?.validateWhenHidden ?? false,
|
|
@@ -4871,6 +4605,16 @@ function sanitizeFormConfigPure(config, fieldRegistry, typeRegistry, options = {
|
|
|
4871
4605
|
return { sanitized: { ...config, fields: fields }, warnings };
|
|
4872
4606
|
}
|
|
4873
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
|
+
|
|
4874
4618
|
class GoToPageEvent {
|
|
4875
4619
|
pageIndex;
|
|
4876
4620
|
options;
|
|
@@ -4998,5 +4742,5 @@ function arrayEvent(arrayKey) {
|
|
|
4998
4742
|
|
|
4999
4743
|
|
|
5000
4744
|
|
|
5001
|
-
export {
|
|
5002
|
-
//# 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
|