@ng-forge/dynamic-forms 0.10.0-next.3 → 0.10.0-next.4
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-D6GEsH72.mjs → ng-forge-dynamic-forms-array-field.component-BfRH3TBm.mjs} +4 -3
- package/fesm2022/ng-forge-dynamic-forms-array-field.component-BfRH3TBm.mjs.map +1 -0
- package/fesm2022/{ng-forge-dynamic-forms-container-field.component-BluTT44A.mjs → ng-forge-dynamic-forms-container-field.component-bA-8ICHF.mjs} +4 -3
- package/fesm2022/ng-forge-dynamic-forms-container-field.component-bA-8ICHF.mjs.map +1 -0
- package/fesm2022/{ng-forge-dynamic-forms-container-utils-diYAWMa_.mjs → ng-forge-dynamic-forms-container-utils-BvXn8T_i.mjs} +2 -2
- package/fesm2022/ng-forge-dynamic-forms-container-utils-BvXn8T_i.mjs.map +1 -0
- package/fesm2022/{ng-forge-dynamic-forms-css-wrapper.component-CgMQOaG7.mjs → ng-forge-dynamic-forms-css-wrapper.component-DjhcONFh.mjs} +2 -2
- package/fesm2022/ng-forge-dynamic-forms-css-wrapper.component-DjhcONFh.mjs.map +1 -0
- package/fesm2022/{ng-forge-dynamic-forms-emit-initialization-CB86IMsQ.mjs → ng-forge-dynamic-forms-emit-initialization-DhHwU6FJ.mjs} +2 -2
- package/fesm2022/ng-forge-dynamic-forms-emit-initialization-DhHwU6FJ.mjs.map +1 -0
- package/fesm2022/{ng-forge-dynamic-forms-group-field.component-2bWHql4u.mjs → ng-forge-dynamic-forms-group-field.component-D0_nmXf2.mjs} +4 -3
- package/fesm2022/ng-forge-dynamic-forms-group-field.component-D0_nmXf2.mjs.map +1 -0
- package/fesm2022/ng-forge-dynamic-forms-integration.mjs +14 -10
- package/fesm2022/ng-forge-dynamic-forms-integration.mjs.map +1 -1
- package/fesm2022/ng-forge-dynamic-forms-internal.mjs +3372 -0
- package/fesm2022/ng-forge-dynamic-forms-internal.mjs.map +1 -0
- package/fesm2022/{ng-forge-dynamic-forms-ng-forge-dynamic-forms-CPkVjU_N.mjs → ng-forge-dynamic-forms-ng-forge-dynamic-forms-CNc4a_Xv.mjs} +635 -3887
- package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-CNc4a_Xv.mjs.map +1 -0
- package/fesm2022/{ng-forge-dynamic-forms-page-field.component-zF-vEv5V.mjs → ng-forge-dynamic-forms-page-field.component-BJRNnoz-.mjs} +4 -3
- package/fesm2022/ng-forge-dynamic-forms-page-field.component-BJRNnoz-.mjs.map +1 -0
- package/fesm2022/ng-forge-dynamic-forms-row-wrapper.component-CwkYn5yl.mjs.map +1 -1
- package/fesm2022/{ng-forge-dynamic-forms-text-field.component-_8_vVeH0.mjs → ng-forge-dynamic-forms-text-field.component-DABkT0O7.mjs} +2 -2
- package/fesm2022/ng-forge-dynamic-forms-text-field.component-DABkT0O7.mjs.map +1 -0
- package/fesm2022/ng-forge-dynamic-forms.mjs +2 -1
- package/fesm2022/ng-forge-dynamic-forms.mjs.map +1 -1
- package/package.json +14 -1
- package/schematics/collection.json +10 -0
- package/schematics/generators.json +12 -0
- package/schematics/ng-add/index.js +87 -0
- package/schematics/ng-add/index.js.map +1 -0
- package/schematics/ng-add/nx-wrapper.js +5 -0
- package/schematics/ng-add/nx-wrapper.js.map +1 -0
- package/schematics/ng-add/recipes.js +60 -0
- package/schematics/ng-add/recipes.js.map +1 -0
- package/schematics/ng-add/rules/add-packages.js +73 -0
- package/schematics/ng-add/rules/add-packages.js.map +1 -0
- package/schematics/ng-add/rules/add-providers.js +132 -0
- package/schematics/ng-add/rules/add-providers.js.map +1 -0
- package/schematics/ng-add/rules/add-styles.js +123 -0
- package/schematics/ng-add/rules/add-styles.js.map +1 -0
- package/schematics/ng-add/schema.json +48 -0
- package/schematics/package.json +1 -0
- package/schematics/versions.js +33 -0
- package/schematics/versions.js.map +1 -0
- package/types/ng-forge-dynamic-forms-integration.d.ts +4 -2
- package/types/ng-forge-dynamic-forms-internal.d.ts +3508 -0
- package/types/ng-forge-dynamic-forms.d.ts +14 -2733
- package/fesm2022/ng-forge-dynamic-forms-array-field.component-D6GEsH72.mjs.map +0 -1
- package/fesm2022/ng-forge-dynamic-forms-container-field.component-BluTT44A.mjs.map +0 -1
- package/fesm2022/ng-forge-dynamic-forms-container-utils-diYAWMa_.mjs.map +0 -1
- package/fesm2022/ng-forge-dynamic-forms-css-wrapper.component-CgMQOaG7.mjs.map +0 -1
- package/fesm2022/ng-forge-dynamic-forms-emit-initialization-CB86IMsQ.mjs.map +0 -1
- package/fesm2022/ng-forge-dynamic-forms-group-field.component-2bWHql4u.mjs.map +0 -1
- package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-CPkVjU_N.mjs.map +0 -1
- package/fesm2022/ng-forge-dynamic-forms-page-field.component-zF-vEv5V.mjs.map +0 -1
- package/fesm2022/ng-forge-dynamic-forms-text-field.component-_8_vVeH0.mjs.map +0 -1
|
@@ -1,58 +1,16 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import {
|
|
2
|
+
import { reflectComponentType, Injector, inject, EnvironmentInjector, DestroyRef, computed, signal, input, ViewContainerRef, Directive, linkedSignal, ChangeDetectionStrategy, Component, InjectionToken, Injectable, isSignal, runInInjectionContext, afterNextRender, untracked, ElementRef, PLATFORM_ID, isDevMode, isWritableSignal, TemplateRef, model, contentChildren, makeEnvironmentProviders } from '@angular/core';
|
|
3
3
|
import { explicitEffect } from 'ngxtension/explicit-effect';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { HttpClient } from '@angular/common/http';
|
|
4
|
+
import { DynamicFormLogger, WRAPPER_COMPONENT_CACHE, WRAPPER_REGISTRY, toReadonlyFieldTreeCached, writeToFieldValue, WRAPPER_AUTO_ASSOCIATIONS, DEFAULT_WRAPPERS, READONLY_FIELD_TREE_CACHE, EventBus, FieldContextRegistryService, FunctionRegistryService, FORM_OPTIONS, evaluateCondition, FIELD_SIGNAL_CONTEXT, isRowField, isContainerField, isArrayField, hasChildFields, isSimplifiedArrayField, DynamicFormError, isStateLogicConfig, isDerivationLogicConfig, createDebouncedLogicFunction, createLogicFunction, ExpressionParser, applyValidator, isPageField, isContainerTypedField, isGroupField, getFieldValueHandling, normalizeFieldsArray, getNestedValue, isCrossFieldValidator, extractStringDependencies, isCrossFieldBuiltInValidator, hasCrossFieldWhenCondition, extractExpressionDependencies, isCrossFieldStateLogic, isCrossFieldSchema, keyBy, mapValues, isEqual, hasTargetProperty, ARRAY_CONTEXT, GROUP_CONTEXT, FORM_ID_PREFIX, baseFieldMapper, detectFormMode, validatePageNesting, FIELD_REGISTRY, assertNever, ADDON_KIND_REGISTRY, deepMergeDefaults, derivedFromDeferred, createWarningTracker, createFormFieldStateMap, readFieldStateInfo, getChangedKeys, resolveHttpRequest, createDefaultDerivationLogConfig, shouldLog, NoopLogger, RootFormRegistryService, DEFAULT_PROPS, DEFAULT_VALIDATION_MESSAGES, EXTERNAL_DATA, DEPRECATION_WARNING_TRACKER, HttpConditionCache, HTTP_CONDITION_CACHE, HttpConditionFunctionCacheService, AsyncConditionFunctionCacheService, DynamicValueFunctionCacheService, LogicFunctionCacheService, setupInitializationTracking, ComponentInitializedEvent, DF_FIELD_TEMPLATES, rowFieldMapper, groupFieldMapper, arrayFieldMapper, pageFieldMapper, textFieldMapper, containerFieldMapper, isWrapperTypeDefinition, EMIT_FORM_VALUE_ON_EVENTS, resolveDynamicValue, DynamicTextPipe } from '@ng-forge/dynamic-forms/internal';
|
|
5
|
+
import { takeUntilDestroyed, toObservable, toSignal, outputFromObservable } from '@angular/core/rxjs-interop';
|
|
6
|
+
import { of, forkJoin, from, catchError, map, switchMap, isObservable, firstValueFrom, exhaustMap, EMPTY, debounceTime, distinctUntilChanged, tap, pipe, scan, Observable, Subject, concatMap, filter, take, startWith, pairwise, merge, combineLatestWith, auditTime, scheduled, queueScheduler, timer } from 'rxjs';
|
|
7
|
+
import { submit, required, disabled, readonly, hidden, applyEach, applyWhenValue, applyWhen, apply, email, minLength, maxLength, pattern, min, max, schema, validateStandardSchema, validateTree, form, provideSignalFormsConfig } from '@angular/forms/signals';
|
|
9
8
|
import { isStandardSchemaMarker } from '@ng-forge/dynamic-forms/schema';
|
|
10
9
|
import { isPlatformBrowser, NgComponentOutlet, AsyncPipe, NgTemplateOutlet } from '@angular/common';
|
|
10
|
+
import { HttpClient } from '@angular/common/http';
|
|
11
|
+
import { catchError as catchError$1 } from 'rxjs/operators';
|
|
11
12
|
import { NG_STATUS_CLASSES } from '@angular/forms/signals/compat';
|
|
12
13
|
|
|
13
|
-
function isWrapperTypeDefinition(value) {
|
|
14
|
-
return typeof value === 'object' && value !== null && 'wrapperName' in value;
|
|
15
|
-
}
|
|
16
|
-
const WRAPPER_REGISTRY = new InjectionToken('WRAPPER_REGISTRY', {
|
|
17
|
-
providedIn: 'root',
|
|
18
|
-
factory: () => new Map(),
|
|
19
|
-
});
|
|
20
|
-
const WRAPPER_COMPONENT_CACHE = new InjectionToken('WRAPPER_COMPONENT_CACHE', {
|
|
21
|
-
providedIn: 'root',
|
|
22
|
-
factory: () => new Map(),
|
|
23
|
-
});
|
|
24
|
-
const WRAPPER_AUTO_ASSOCIATIONS = new InjectionToken('WRAPPER_AUTO_ASSOCIATIONS', {
|
|
25
|
-
providedIn: 'root',
|
|
26
|
-
factory: () => new Map(),
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
class DynamicFormError extends Error {
|
|
30
|
-
static PREFIX = '[Dynamic Forms]';
|
|
31
|
-
constructor(message) {
|
|
32
|
-
super(`${DynamicFormError.PREFIX} ${message}`);
|
|
33
|
-
this.name = 'DynamicFormError';
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const DEFAULT_WRAPPERS = new InjectionToken('DEFAULT_WRAPPERS');
|
|
38
|
-
const FIELD_SIGNAL_CONTEXT = new InjectionToken('FIELD_SIGNAL_CONTEXT');
|
|
39
|
-
function injectFieldSignalContext() {
|
|
40
|
-
const ctx = inject(FIELD_SIGNAL_CONTEXT, { optional: true });
|
|
41
|
-
if (!ctx) {
|
|
42
|
-
throw new DynamicFormError('FIELD_SIGNAL_CONTEXT is not provided. This consumer must run inside a <df-dynamic-form> component tree.');
|
|
43
|
-
}
|
|
44
|
-
return ctx;
|
|
45
|
-
}
|
|
46
|
-
const ARRAY_CONTEXT = new InjectionToken('ARRAY_CONTEXT');
|
|
47
|
-
const GROUP_CONTEXT = new InjectionToken('GROUP_CONTEXT');
|
|
48
|
-
const DEFAULT_PROPS = new InjectionToken('DEFAULT_PROPS');
|
|
49
|
-
const DEFAULT_VALIDATION_MESSAGES = new InjectionToken('DEFAULT_VALIDATION_MESSAGES');
|
|
50
|
-
const FORM_OPTIONS = new InjectionToken('FORM_OPTIONS');
|
|
51
|
-
const EXTERNAL_DATA = new InjectionToken('EXTERNAL_DATA');
|
|
52
|
-
const FORM_ID_PREFIX = new InjectionToken('FORM_ID_PREFIX');
|
|
53
|
-
|
|
54
|
-
const DynamicFormLogger = new InjectionToken('DynamicFormLogger');
|
|
55
|
-
|
|
56
14
|
const EMPTY_WRAPPERS = Object.freeze([]);
|
|
57
15
|
function isSameWrapperChain(a, b) {
|
|
58
16
|
if (a === b)
|
|
@@ -283,47 +241,6 @@ function pushFieldInputsOnChange(opts, getRefs) {
|
|
|
283
241
|
});
|
|
284
242
|
}
|
|
285
243
|
|
|
286
|
-
function getArrayLength(arrayFieldTree) {
|
|
287
|
-
const value = arrayFieldTree().value();
|
|
288
|
-
return Array.isArray(value) ? value.length : 0;
|
|
289
|
-
}
|
|
290
|
-
function toReadonlyFieldTree(field) {
|
|
291
|
-
const state = field();
|
|
292
|
-
return {
|
|
293
|
-
value: state.value,
|
|
294
|
-
valid: state.valid,
|
|
295
|
-
invalid: state.invalid,
|
|
296
|
-
touched: state.touched,
|
|
297
|
-
dirty: state.dirty,
|
|
298
|
-
required: state.required,
|
|
299
|
-
disabled: state.disabled,
|
|
300
|
-
hidden: state.hidden,
|
|
301
|
-
errors: state.errors,
|
|
302
|
-
};
|
|
303
|
-
}
|
|
304
|
-
function writeToFieldValue(value, next, logger) {
|
|
305
|
-
const candidate = value;
|
|
306
|
-
if (typeof candidate.set !== 'function') {
|
|
307
|
-
logger?.warn('writeToFieldValue: FieldState.value is not a WritableSignal — Signal Forms contract changed.');
|
|
308
|
-
return false;
|
|
309
|
-
}
|
|
310
|
-
candidate.set(next);
|
|
311
|
-
return true;
|
|
312
|
-
}
|
|
313
|
-
const READONLY_FIELD_TREE_CACHE = new InjectionToken('READONLY_FIELD_TREE_CACHE', {
|
|
314
|
-
providedIn: 'root',
|
|
315
|
-
factory: () => new WeakMap(),
|
|
316
|
-
});
|
|
317
|
-
function toReadonlyFieldTreeCached(cache, field) {
|
|
318
|
-
const key = field;
|
|
319
|
-
const cached = cache.get(key);
|
|
320
|
-
if (cached)
|
|
321
|
-
return cached;
|
|
322
|
-
const view = toReadonlyFieldTree(field);
|
|
323
|
-
cache.set(key, view);
|
|
324
|
-
return view;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
244
|
function buildFieldInputs(rawInputs, cache, fieldType) {
|
|
328
245
|
const fieldTreeCandidate = rawInputs['field'];
|
|
329
246
|
const hasFieldTree = fieldTreeCandidate !== undefined && typeof fieldTreeCandidate === 'function';
|
|
@@ -498,212 +415,10 @@ function createSubmissionHandler(options) {
|
|
|
498
415
|
}));
|
|
499
416
|
}
|
|
500
417
|
|
|
501
|
-
const EMIT_FORM_VALUE_ON_EVENTS = new InjectionToken('EMIT_FORM_VALUE_ON_EVENTS', {
|
|
502
|
-
providedIn: 'root',
|
|
503
|
-
factory: () => false,
|
|
504
|
-
});
|
|
505
|
-
|
|
506
|
-
class RootFormRegistryService {
|
|
507
|
-
formValue;
|
|
508
|
-
rootForm;
|
|
509
|
-
constructor(formValue, rootForm) {
|
|
510
|
-
this.formValue = formValue;
|
|
511
|
-
this.rootForm = rootForm;
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
class NoopLogger {
|
|
516
|
-
debug() {
|
|
517
|
-
}
|
|
518
|
-
info() {
|
|
519
|
-
}
|
|
520
|
-
warn() {
|
|
521
|
-
}
|
|
522
|
-
error() {
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
function safeInjectToken(token, defaultValue) {
|
|
527
|
-
try {
|
|
528
|
-
return inject(token, { optional: true }) ?? defaultValue;
|
|
529
|
-
}
|
|
530
|
-
catch {
|
|
531
|
-
return defaultValue;
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
function safeInjectClass(token, defaultValue) {
|
|
535
|
-
try {
|
|
536
|
-
return inject(token, { optional: true }) ?? defaultValue;
|
|
537
|
-
}
|
|
538
|
-
catch {
|
|
539
|
-
return defaultValue;
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
function attachFormValue(event, formValue) {
|
|
543
|
-
const prototype = Object.getPrototypeOf(event);
|
|
544
|
-
return Object.assign(Object.create(prototype), event, { formValue });
|
|
545
|
-
}
|
|
546
|
-
const FALLBACK_LOGGER = new NoopLogger();
|
|
547
|
-
class EventBus {
|
|
548
|
-
pipeline$ = new Subject();
|
|
549
|
-
globalEmitFormValue = safeInjectToken(EMIT_FORM_VALUE_ON_EVENTS, false);
|
|
550
|
-
rootFormRegistry = safeInjectClass(RootFormRegistryService, null);
|
|
551
|
-
formOptions = safeInjectToken(FORM_OPTIONS, null);
|
|
552
|
-
logger = safeInjectToken(DynamicFormLogger, FALLBACK_LOGGER);
|
|
553
|
-
events$ = this.pipeline$.asObservable();
|
|
554
|
-
dispatch(eventOrConstructor, ...args) {
|
|
555
|
-
if (typeof eventOrConstructor !== 'function' && 'type' in eventOrConstructor) {
|
|
556
|
-
this.emit(eventOrConstructor);
|
|
557
|
-
}
|
|
558
|
-
else {
|
|
559
|
-
this.emit(new eventOrConstructor(...args));
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
emitInstance(event) {
|
|
563
|
-
this.emit(event);
|
|
564
|
-
}
|
|
565
|
-
emit(event) {
|
|
566
|
-
if (this.shouldEmitFormValue()) {
|
|
567
|
-
const formValue = this.rootFormRegistry?.formValue();
|
|
568
|
-
if (formValue && Object.keys(formValue).length > 0) {
|
|
569
|
-
this.safeEmit(attachFormValue(event, formValue));
|
|
570
|
-
return;
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
this.safeEmit(event);
|
|
574
|
-
}
|
|
575
|
-
safeEmit(event) {
|
|
576
|
-
try {
|
|
577
|
-
this.pipeline$.next(event);
|
|
578
|
-
}
|
|
579
|
-
catch (err) {
|
|
580
|
-
this.logger.error('Exception in EventBus subscriber during dispatch', err);
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
shouldEmitFormValue() {
|
|
584
|
-
const formLevelSetting = this.formOptions?.()?.emitFormValueOnEvents;
|
|
585
|
-
return formLevelSetting ?? this.globalEmitFormValue;
|
|
586
|
-
}
|
|
587
|
-
on(eventType) {
|
|
588
|
-
if (Array.isArray(eventType)) {
|
|
589
|
-
return this.pipeline$.pipe(filter((event) => eventType.includes(event.type)));
|
|
590
|
-
}
|
|
591
|
-
return this.pipeline$.pipe(filter((event) => event.type === eventType));
|
|
592
|
-
}
|
|
593
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: EventBus, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
594
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: EventBus });
|
|
595
|
-
}
|
|
596
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: EventBus, decorators: [{
|
|
597
|
-
type: Injectable
|
|
598
|
-
}] });
|
|
599
|
-
|
|
600
418
|
class SubmitEvent {
|
|
601
419
|
type = 'submit';
|
|
602
420
|
}
|
|
603
421
|
|
|
604
|
-
class ComponentInitializedEvent {
|
|
605
|
-
componentType;
|
|
606
|
-
componentId;
|
|
607
|
-
type = 'component-initialized';
|
|
608
|
-
constructor(componentType, componentId) {
|
|
609
|
-
this.componentType = componentType;
|
|
610
|
-
this.componentId = componentId;
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
const INITIALIZATION_TIMEOUT_MS = new InjectionToken('INITIALIZATION_TIMEOUT_MS', {
|
|
615
|
-
providedIn: 'root',
|
|
616
|
-
factory: () => 10_000,
|
|
617
|
-
});
|
|
618
|
-
function createInitializationTracker(eventBus, expectedCount) {
|
|
619
|
-
return eventBus.on('component-initialized').pipe(scan((count) => count + 1, 0), map$1((currentCount) => currentCount >= expectedCount), filter$1((isComplete) => isComplete));
|
|
620
|
-
}
|
|
621
|
-
function setupInitializationTracking(options) {
|
|
622
|
-
const { eventBus, totalComponentsCount, injector, componentId } = options;
|
|
623
|
-
const timeoutMs = injector.get(INITIALIZATION_TIMEOUT_MS);
|
|
624
|
-
const logger = injector.get(DynamicFormLogger);
|
|
625
|
-
return toObservable(totalComponentsCount, { injector }).pipe(take(1), switchMap$1((count) => {
|
|
626
|
-
const tracking$ = count === 1
|
|
627
|
-
? eventBus.on('component-initialized').pipe(filter$1((event) => event.componentType === 'dynamic-form' && event.componentId === componentId), map$1(() => true), take(1))
|
|
628
|
-
: createInitializationTracker(eventBus, count);
|
|
629
|
-
return tracking$.pipe(timeout(timeoutMs), catchError$1((error) => {
|
|
630
|
-
if (error instanceof TimeoutError) {
|
|
631
|
-
logger.warn(`Initialization timed out after ${timeoutMs}ms. ` +
|
|
632
|
-
`Expected ${count} component(s) to initialize but not all reported in time. ` +
|
|
633
|
-
'This may indicate a container component threw during initialization. ' +
|
|
634
|
-
'Emitting (initialized) as best-effort.');
|
|
635
|
-
return of(true);
|
|
636
|
-
}
|
|
637
|
-
return throwError(() => error);
|
|
638
|
-
}));
|
|
639
|
-
}), shareReplay({ bufferSize: 1, refCount: false }));
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
function isRowField(field) {
|
|
643
|
-
return field.type === 'row' && 'fields' in field && Array.isArray(field.fields);
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
function isGroupField(field) {
|
|
647
|
-
return field.type === 'group' && 'fields' in field;
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
function isContainerTypedField(field) {
|
|
651
|
-
return (field.type === 'container' &&
|
|
652
|
-
'wrappers' in field &&
|
|
653
|
-
'fields' in field &&
|
|
654
|
-
Array.isArray(field.wrappers) &&
|
|
655
|
-
Array.isArray(field.fields));
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
function isPageField(field) {
|
|
659
|
-
return field.type === 'page' && 'fields' in field && Array.isArray(field.fields);
|
|
660
|
-
}
|
|
661
|
-
function validatePageNesting(pageField) {
|
|
662
|
-
return !hasNestedPages(pageField.fields);
|
|
663
|
-
}
|
|
664
|
-
function isContainerWithFields$1(field) {
|
|
665
|
-
return ((isRowField(field) || isGroupField(field) || isContainerTypedField(field)) &&
|
|
666
|
-
'fields' in field &&
|
|
667
|
-
Array.isArray(field.fields));
|
|
668
|
-
}
|
|
669
|
-
function hasNestedPages(fields) {
|
|
670
|
-
for (const field of fields) {
|
|
671
|
-
if (isPageField(field)) {
|
|
672
|
-
return true;
|
|
673
|
-
}
|
|
674
|
-
if (isContainerWithFields$1(field)) {
|
|
675
|
-
if (hasNestedPages(field.fields)) {
|
|
676
|
-
return true;
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
return false;
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
function isArrayField(field) {
|
|
684
|
-
return field.type === 'array' && 'fields' in field && Array.isArray(field.fields);
|
|
685
|
-
}
|
|
686
|
-
function isSimplifiedArrayField(field) {
|
|
687
|
-
return field.type === 'array' && 'template' in field;
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
function hasChildFields(field) {
|
|
691
|
-
return 'fields' in field && field.fields != null;
|
|
692
|
-
}
|
|
693
|
-
const CONTAINER_TYPES$1 = new Set(['page', 'row', 'group', 'array', 'container']);
|
|
694
|
-
function isContainerField(field) {
|
|
695
|
-
return CONTAINER_TYPES$1.has(field.type);
|
|
696
|
-
}
|
|
697
|
-
function isLeafField(field) {
|
|
698
|
-
return !isContainerField(field);
|
|
699
|
-
}
|
|
700
|
-
function isValueBearingField(field) {
|
|
701
|
-
return 'value' in field;
|
|
702
|
-
}
|
|
703
|
-
function isDisplayOnlyField(field) {
|
|
704
|
-
return field.type === 'text' || isContainerField(field);
|
|
705
|
-
}
|
|
706
|
-
|
|
707
422
|
class NextPageEvent {
|
|
708
423
|
type = 'next-page';
|
|
709
424
|
}
|
|
@@ -827,2941 +542,561 @@ class PageNavigationStateChangeEvent {
|
|
|
827
542
|
}
|
|
828
543
|
}
|
|
829
544
|
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
(
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
TokenType["UNDEFINED"] = "UNDEFINED";
|
|
887
|
-
TokenType["IDENTIFIER"] = "IDENTIFIER";
|
|
888
|
-
TokenType["PLUS"] = "PLUS";
|
|
889
|
-
TokenType["MINUS"] = "MINUS";
|
|
890
|
-
TokenType["MULTIPLY"] = "MULTIPLY";
|
|
891
|
-
TokenType["DIVIDE"] = "DIVIDE";
|
|
892
|
-
TokenType["MODULO"] = "MODULO";
|
|
893
|
-
TokenType["EQUAL"] = "EQUAL";
|
|
894
|
-
TokenType["NOT_EQUAL"] = "NOT_EQUAL";
|
|
895
|
-
TokenType["GREATER"] = "GREATER";
|
|
896
|
-
TokenType["LESS"] = "LESS";
|
|
897
|
-
TokenType["GREATER_EQUAL"] = "GREATER_EQUAL";
|
|
898
|
-
TokenType["LESS_EQUAL"] = "LESS_EQUAL";
|
|
899
|
-
TokenType["AND"] = "AND";
|
|
900
|
-
TokenType["OR"] = "OR";
|
|
901
|
-
TokenType["NOT"] = "NOT";
|
|
902
|
-
TokenType["DOT"] = "DOT";
|
|
903
|
-
TokenType["COMMA"] = "COMMA";
|
|
904
|
-
TokenType["COLON"] = "COLON";
|
|
905
|
-
TokenType["LPAREN"] = "LPAREN";
|
|
906
|
-
TokenType["RPAREN"] = "RPAREN";
|
|
907
|
-
TokenType["LBRACKET"] = "LBRACKET";
|
|
908
|
-
TokenType["RBRACKET"] = "RBRACKET";
|
|
909
|
-
TokenType["LBRACE"] = "LBRACE";
|
|
910
|
-
TokenType["RBRACE"] = "RBRACE";
|
|
911
|
-
TokenType["ARROW"] = "ARROW";
|
|
912
|
-
TokenType["QUESTION"] = "QUESTION";
|
|
913
|
-
TokenType["OPTIONAL_DOT"] = "OPTIONAL_DOT";
|
|
914
|
-
TokenType["EOF"] = "EOF";
|
|
915
|
-
})(TokenType || (TokenType = {}));
|
|
916
|
-
class ExpressionParserError extends DynamicFormError {
|
|
917
|
-
position;
|
|
918
|
-
expression;
|
|
919
|
-
constructor(message, position, expression) {
|
|
920
|
-
super(`${message} at position ${position} in expression: ${expression}`);
|
|
921
|
-
this.position = position;
|
|
922
|
-
this.expression = expression;
|
|
923
|
-
this.name = 'ExpressionParserError';
|
|
924
|
-
}
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
class Tokenizer {
|
|
928
|
-
position = 0;
|
|
929
|
-
expression;
|
|
930
|
-
constructor(expression) {
|
|
931
|
-
this.expression = expression;
|
|
932
|
-
}
|
|
933
|
-
tokenize() {
|
|
934
|
-
const tokens = [];
|
|
935
|
-
while (this.position < this.expression.length) {
|
|
936
|
-
this.skipWhitespace();
|
|
937
|
-
if (this.position >= this.expression.length) {
|
|
938
|
-
break;
|
|
939
|
-
}
|
|
940
|
-
const token = this.nextToken();
|
|
941
|
-
tokens.push(token);
|
|
942
|
-
}
|
|
943
|
-
tokens.push({ type: TokenType.EOF, value: '', position: this.position });
|
|
944
|
-
return tokens;
|
|
945
|
-
}
|
|
946
|
-
skipWhitespace() {
|
|
947
|
-
while (this.position < this.expression.length && /\s/.test(this.expression[this.position])) {
|
|
948
|
-
this.position++;
|
|
949
|
-
}
|
|
950
|
-
}
|
|
951
|
-
nextToken() {
|
|
952
|
-
const char = this.expression[this.position];
|
|
953
|
-
if (char === '"' || char === "'") {
|
|
954
|
-
return this.readString(char);
|
|
955
|
-
}
|
|
956
|
-
if (/[0-9]/.test(char)) {
|
|
957
|
-
return this.readNumber();
|
|
958
|
-
}
|
|
959
|
-
if (/[a-zA-Z_$]/.test(char)) {
|
|
960
|
-
return this.readIdentifierOrKeyword();
|
|
961
|
-
}
|
|
962
|
-
return this.readOperatorOrPunctuation();
|
|
963
|
-
}
|
|
964
|
-
readString(quote) {
|
|
965
|
-
const start = this.position;
|
|
966
|
-
this.position++; let value = '';
|
|
967
|
-
while (this.position < this.expression.length) {
|
|
968
|
-
const char = this.expression[this.position];
|
|
969
|
-
if (char === quote) {
|
|
970
|
-
this.position++; return { type: TokenType.STRING, value, position: start };
|
|
971
|
-
}
|
|
972
|
-
if (char === '\\' && this.position + 1 < this.expression.length) {
|
|
973
|
-
this.position++;
|
|
974
|
-
const nextChar = this.expression[this.position];
|
|
975
|
-
switch (nextChar) {
|
|
976
|
-
case 'n':
|
|
977
|
-
value += '\n';
|
|
978
|
-
break;
|
|
979
|
-
case 't':
|
|
980
|
-
value += '\t';
|
|
981
|
-
break;
|
|
982
|
-
case 'r':
|
|
983
|
-
value += '\r';
|
|
984
|
-
break;
|
|
985
|
-
case '\\':
|
|
986
|
-
value += '\\';
|
|
987
|
-
break;
|
|
988
|
-
case quote:
|
|
989
|
-
value += quote;
|
|
990
|
-
break;
|
|
991
|
-
default:
|
|
992
|
-
value += nextChar;
|
|
545
|
+
class PageOrchestratorComponent {
|
|
546
|
+
eventBus = inject(EventBus);
|
|
547
|
+
fieldContextRegistry = inject(FieldContextRegistryService);
|
|
548
|
+
functionRegistry = inject(FunctionRegistryService);
|
|
549
|
+
formOptions = inject(FORM_OPTIONS, { optional: true });
|
|
550
|
+
pageFields = input.required(...(ngDevMode ? [{ debugName: "pageFields" }] : /* istanbul ignore next */ []));
|
|
551
|
+
form = input.required(...(ngDevMode ? [{ debugName: "form" }] : /* istanbul ignore next */ []));
|
|
552
|
+
fieldSignalContext = input.required(...(ngDevMode ? [{ debugName: "fieldSignalContext" }] : /* istanbul ignore next */ []));
|
|
553
|
+
pageHiddenStates = computed(() => {
|
|
554
|
+
const pages = this.pageFields();
|
|
555
|
+
return pages.map((page) => this.evaluatePageHidden(page));
|
|
556
|
+
}, ...(ngDevMode ? [{ debugName: "pageHiddenStates" }] : /* istanbul ignore next */ []));
|
|
557
|
+
visiblePageIndices = computed(() => {
|
|
558
|
+
const hiddenStates = this.pageHiddenStates();
|
|
559
|
+
return hiddenStates
|
|
560
|
+
.map((hidden, index) => ({ index, hidden }))
|
|
561
|
+
.filter((item) => !item.hidden)
|
|
562
|
+
.map((item) => item.index);
|
|
563
|
+
}, ...(ngDevMode ? [{ debugName: "visiblePageIndices" }] : /* istanbul ignore next */ []));
|
|
564
|
+
currentPageIndex = linkedSignal(() => {
|
|
565
|
+
const totalPages = this.pageFields().length;
|
|
566
|
+
if (totalPages === 0)
|
|
567
|
+
return 0;
|
|
568
|
+
return 0;
|
|
569
|
+
}, ...(ngDevMode ? [{ debugName: "currentPageIndex" }] : /* istanbul ignore next */ []));
|
|
570
|
+
state = computed(() => {
|
|
571
|
+
const currentIndex = this.currentPageIndex();
|
|
572
|
+
const totalPages = this.pageFields().length;
|
|
573
|
+
const visibleIndices = this.visiblePageIndices();
|
|
574
|
+
const currentVisiblePosition = visibleIndices.indexOf(currentIndex);
|
|
575
|
+
const isFirstVisiblePage = currentVisiblePosition === 0 || currentVisiblePosition === -1;
|
|
576
|
+
const isLastVisiblePage = currentVisiblePosition >= visibleIndices.length - 1;
|
|
577
|
+
return {
|
|
578
|
+
currentPageIndex: currentIndex,
|
|
579
|
+
totalPages,
|
|
580
|
+
isFirstPage: isFirstVisiblePage,
|
|
581
|
+
isLastPage: isLastVisiblePage,
|
|
582
|
+
navigationDisabled: false,
|
|
583
|
+
};
|
|
584
|
+
}, ...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
|
|
585
|
+
currentPageValid = computed(() => {
|
|
586
|
+
const currentIndex = this.currentPageIndex();
|
|
587
|
+
const pages = this.pageFields();
|
|
588
|
+
const form = this.form();
|
|
589
|
+
if (pages.length === 0 || currentIndex >= pages.length) {
|
|
590
|
+
return true;
|
|
591
|
+
}
|
|
592
|
+
const currentPage = pages[currentIndex];
|
|
593
|
+
const pageFields = currentPage.fields || [];
|
|
594
|
+
const leafKeys = collectLeafFieldKeys(pageFields);
|
|
595
|
+
for (const fieldKey of leafKeys) {
|
|
596
|
+
const field = form[fieldKey];
|
|
597
|
+
if (field && typeof field === 'function') {
|
|
598
|
+
const fieldState = field();
|
|
599
|
+
if (fieldState && typeof fieldState.valid === 'function' && !fieldState.valid()) {
|
|
600
|
+
return false;
|
|
993
601
|
}
|
|
994
|
-
this.position++;
|
|
995
602
|
}
|
|
996
|
-
else {
|
|
997
|
-
value += char;
|
|
998
|
-
this.position++;
|
|
999
|
-
}
|
|
1000
|
-
}
|
|
1001
|
-
throw new ExpressionParserError('Unterminated string literal', start, this.expression);
|
|
1002
|
-
}
|
|
1003
|
-
readNumber() {
|
|
1004
|
-
const start = this.position;
|
|
1005
|
-
let value = '';
|
|
1006
|
-
while (this.position < this.expression.length && /[0-9.]/.test(this.expression[this.position])) {
|
|
1007
|
-
value += this.expression[this.position];
|
|
1008
|
-
this.position++;
|
|
1009
|
-
}
|
|
1010
|
-
if (!/^\d+(\.\d+)?$/.test(value)) {
|
|
1011
|
-
throw new ExpressionParserError(`Invalid number: ${value}`, start, this.expression);
|
|
1012
|
-
}
|
|
1013
|
-
return { type: TokenType.NUMBER, value, position: start };
|
|
1014
|
-
}
|
|
1015
|
-
readIdentifierOrKeyword() {
|
|
1016
|
-
const start = this.position;
|
|
1017
|
-
let value = '';
|
|
1018
|
-
while (this.position < this.expression.length && /[a-zA-Z0-9_$]/.test(this.expression[this.position])) {
|
|
1019
|
-
value += this.expression[this.position];
|
|
1020
|
-
this.position++;
|
|
1021
|
-
}
|
|
1022
|
-
const type = this.getKeywordType(value);
|
|
1023
|
-
return { type, value, position: start };
|
|
1024
|
-
}
|
|
1025
|
-
getKeywordType(value) {
|
|
1026
|
-
switch (value) {
|
|
1027
|
-
case 'true':
|
|
1028
|
-
return TokenType.TRUE;
|
|
1029
|
-
case 'false':
|
|
1030
|
-
return TokenType.FALSE;
|
|
1031
|
-
case 'null':
|
|
1032
|
-
return TokenType.NULL;
|
|
1033
|
-
case 'undefined':
|
|
1034
|
-
return TokenType.UNDEFINED;
|
|
1035
|
-
default:
|
|
1036
|
-
return TokenType.IDENTIFIER;
|
|
1037
|
-
}
|
|
1038
|
-
}
|
|
1039
|
-
readOperatorOrPunctuation() {
|
|
1040
|
-
const start = this.position;
|
|
1041
|
-
const char = this.expression[this.position];
|
|
1042
|
-
const nextChar = this.position + 1 < this.expression.length ? this.expression[this.position + 1] : '';
|
|
1043
|
-
const thirdChar = this.position + 2 < this.expression.length ? this.expression[this.position + 2] : '';
|
|
1044
|
-
const threeChar = char + nextChar + thirdChar;
|
|
1045
|
-
if (threeChar === '===') {
|
|
1046
|
-
this.position += 3;
|
|
1047
|
-
return { type: TokenType.EQUAL, value: '===', position: start };
|
|
1048
|
-
}
|
|
1049
|
-
if (threeChar === '!==') {
|
|
1050
|
-
this.position += 3;
|
|
1051
|
-
return { type: TokenType.NOT_EQUAL, value: '!==', position: start };
|
|
1052
|
-
}
|
|
1053
|
-
const twoChar = char + nextChar;
|
|
1054
|
-
switch (twoChar) {
|
|
1055
|
-
case '==':
|
|
1056
|
-
this.position += 2;
|
|
1057
|
-
return { type: TokenType.EQUAL, value: '==', position: start };
|
|
1058
|
-
case '!=':
|
|
1059
|
-
this.position += 2;
|
|
1060
|
-
return { type: TokenType.NOT_EQUAL, value: '!=', position: start };
|
|
1061
|
-
case '>=':
|
|
1062
|
-
this.position += 2;
|
|
1063
|
-
return { type: TokenType.GREATER_EQUAL, value: '>=', position: start };
|
|
1064
|
-
case '<=':
|
|
1065
|
-
this.position += 2;
|
|
1066
|
-
return { type: TokenType.LESS_EQUAL, value: '<=', position: start };
|
|
1067
|
-
case '&&':
|
|
1068
|
-
this.position += 2;
|
|
1069
|
-
return { type: TokenType.AND, value: '&&', position: start };
|
|
1070
|
-
case '||':
|
|
1071
|
-
this.position += 2;
|
|
1072
|
-
return { type: TokenType.OR, value: '||', position: start };
|
|
1073
|
-
case '=>':
|
|
1074
|
-
this.position += 2;
|
|
1075
|
-
return { type: TokenType.ARROW, value: '=>', position: start };
|
|
1076
|
-
case '?.':
|
|
1077
|
-
this.position += 2;
|
|
1078
|
-
return { type: TokenType.OPTIONAL_DOT, value: '?.', position: start };
|
|
1079
|
-
}
|
|
1080
|
-
this.position++;
|
|
1081
|
-
switch (char) {
|
|
1082
|
-
case '+':
|
|
1083
|
-
return { type: TokenType.PLUS, value: '+', position: start };
|
|
1084
|
-
case '-':
|
|
1085
|
-
return { type: TokenType.MINUS, value: '-', position: start };
|
|
1086
|
-
case '*':
|
|
1087
|
-
return { type: TokenType.MULTIPLY, value: '*', position: start };
|
|
1088
|
-
case '/':
|
|
1089
|
-
return { type: TokenType.DIVIDE, value: '/', position: start };
|
|
1090
|
-
case '%':
|
|
1091
|
-
return { type: TokenType.MODULO, value: '%', position: start };
|
|
1092
|
-
case '>':
|
|
1093
|
-
return { type: TokenType.GREATER, value: '>', position: start };
|
|
1094
|
-
case '<':
|
|
1095
|
-
return { type: TokenType.LESS, value: '<', position: start };
|
|
1096
|
-
case '!':
|
|
1097
|
-
return { type: TokenType.NOT, value: '!', position: start };
|
|
1098
|
-
case '.':
|
|
1099
|
-
return { type: TokenType.DOT, value: '.', position: start };
|
|
1100
|
-
case ',':
|
|
1101
|
-
return { type: TokenType.COMMA, value: ',', position: start };
|
|
1102
|
-
case ':':
|
|
1103
|
-
return { type: TokenType.COLON, value: ':', position: start };
|
|
1104
|
-
case '(':
|
|
1105
|
-
return { type: TokenType.LPAREN, value: '(', position: start };
|
|
1106
|
-
case ')':
|
|
1107
|
-
return { type: TokenType.RPAREN, value: ')', position: start };
|
|
1108
|
-
case '[':
|
|
1109
|
-
return { type: TokenType.LBRACKET, value: '[', position: start };
|
|
1110
|
-
case ']':
|
|
1111
|
-
return { type: TokenType.RBRACKET, value: ']', position: start };
|
|
1112
|
-
case '{':
|
|
1113
|
-
return { type: TokenType.LBRACE, value: '{', position: start };
|
|
1114
|
-
case '}':
|
|
1115
|
-
return { type: TokenType.RBRACE, value: '}', position: start };
|
|
1116
|
-
case '?':
|
|
1117
|
-
return { type: TokenType.QUESTION, value: '?', position: start };
|
|
1118
|
-
default:
|
|
1119
|
-
throw new ExpressionParserError(`Unexpected character: ${char}`, start, this.expression);
|
|
1120
|
-
}
|
|
1121
|
-
}
|
|
1122
|
-
}
|
|
1123
|
-
|
|
1124
|
-
class Parser {
|
|
1125
|
-
tokens = [];
|
|
1126
|
-
current = 0;
|
|
1127
|
-
expression;
|
|
1128
|
-
constructor(expression) {
|
|
1129
|
-
this.expression = expression;
|
|
1130
|
-
}
|
|
1131
|
-
parse() {
|
|
1132
|
-
const tokenizer = new Tokenizer(this.expression);
|
|
1133
|
-
this.tokens = tokenizer.tokenize();
|
|
1134
|
-
this.current = 0;
|
|
1135
|
-
if (this.isAtEnd()) {
|
|
1136
|
-
throw new ExpressionParserError('Empty expression', 0, this.expression);
|
|
1137
|
-
}
|
|
1138
|
-
const result = this.parseExpression();
|
|
1139
|
-
if (!this.isAtEnd()) {
|
|
1140
|
-
const token = this.peek();
|
|
1141
|
-
throw new ExpressionParserError(`Unexpected token '${token.value}' at position ${token.position} — expression has trailing content`, token.position, this.expression);
|
|
1142
603
|
}
|
|
1143
|
-
return
|
|
1144
|
-
}
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
const alternate = this.parseExpression();
|
|
1158
|
-
return { type: 'Conditional', test, consequent, alternate };
|
|
1159
|
-
}
|
|
1160
|
-
tryParseArrowFunction() {
|
|
1161
|
-
const savedPos = this.current;
|
|
1162
|
-
if (this.check(TokenType.IDENTIFIER) && this.checkAt(this.current + 1, TokenType.ARROW)) {
|
|
1163
|
-
const param = this.advance().value;
|
|
1164
|
-
this.advance(); const body = this.parseExpression();
|
|
1165
|
-
return { type: 'ArrowFunction', params: [param], body };
|
|
1166
|
-
}
|
|
1167
|
-
if (this.check(TokenType.LPAREN)) {
|
|
1168
|
-
this.advance(); const params = [];
|
|
1169
|
-
if (this.check(TokenType.RPAREN)) {
|
|
1170
|
-
this.advance(); }
|
|
1171
|
-
else if (this.check(TokenType.IDENTIFIER)) {
|
|
1172
|
-
params.push(this.advance().value);
|
|
1173
|
-
while (this.check(TokenType.COMMA)) {
|
|
1174
|
-
this.advance(); if (!this.check(TokenType.IDENTIFIER)) {
|
|
1175
|
-
this.current = savedPos;
|
|
1176
|
-
return null;
|
|
1177
|
-
}
|
|
1178
|
-
params.push(this.advance().value);
|
|
1179
|
-
}
|
|
1180
|
-
if (!this.check(TokenType.RPAREN)) {
|
|
1181
|
-
this.current = savedPos;
|
|
1182
|
-
return null;
|
|
604
|
+
return true;
|
|
605
|
+
}, ...(ngDevMode ? [{ debugName: "currentPageValid" }] : /* istanbul ignore next */ []));
|
|
606
|
+
extendedFieldSignalContext = computed(() => ({
|
|
607
|
+
...this.fieldSignalContext(),
|
|
608
|
+
currentPageValid: this.currentPageValid,
|
|
609
|
+
}), ...(ngDevMode ? [{ debugName: "extendedFieldSignalContext" }] : /* istanbul ignore next */ []));
|
|
610
|
+
constructor() {
|
|
611
|
+
this.setupEventListeners();
|
|
612
|
+
explicitEffect([this.state, this.visiblePageIndices], ([state, visibleIndices]) => {
|
|
613
|
+
const currentVisiblePosition = visibleIndices.indexOf(state.currentPageIndex);
|
|
614
|
+
if (currentVisiblePosition === -1 && visibleIndices.length > 0) {
|
|
615
|
+
const nearest = this.findNearestVisiblePage(state.currentPageIndex, visibleIndices);
|
|
616
|
+
if (nearest !== -1) {
|
|
617
|
+
this.navigateToPage(nearest);
|
|
1183
618
|
}
|
|
1184
|
-
this.advance(); }
|
|
1185
|
-
else {
|
|
1186
|
-
this.current = savedPos;
|
|
1187
|
-
return null;
|
|
1188
619
|
}
|
|
1189
|
-
|
|
1190
|
-
this.current = savedPos;
|
|
1191
|
-
return null;
|
|
1192
|
-
}
|
|
1193
|
-
this.advance(); const body = this.parseExpression();
|
|
1194
|
-
return { type: 'ArrowFunction', params, body };
|
|
1195
|
-
}
|
|
1196
|
-
return null;
|
|
1197
|
-
}
|
|
1198
|
-
checkAt(index, type) {
|
|
1199
|
-
if (index < 0 || index >= this.tokens.length)
|
|
1200
|
-
return false;
|
|
1201
|
-
return this.tokens[index].type === type;
|
|
1202
|
-
}
|
|
1203
|
-
parseLogicalOr() {
|
|
1204
|
-
let node = this.parseLogicalAnd();
|
|
1205
|
-
while (this.match(TokenType.OR)) {
|
|
1206
|
-
const operator = this.previous().value;
|
|
1207
|
-
const right = this.parseLogicalAnd();
|
|
1208
|
-
node = { type: 'BinaryOp', operator, left: node, right };
|
|
1209
|
-
}
|
|
1210
|
-
return node;
|
|
1211
|
-
}
|
|
1212
|
-
parseLogicalAnd() {
|
|
1213
|
-
let node = this.parseEquality();
|
|
1214
|
-
while (this.match(TokenType.AND)) {
|
|
1215
|
-
const operator = this.previous().value;
|
|
1216
|
-
const right = this.parseEquality();
|
|
1217
|
-
node = { type: 'BinaryOp', operator, left: node, right };
|
|
1218
|
-
}
|
|
1219
|
-
return node;
|
|
620
|
+
});
|
|
1220
621
|
}
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
622
|
+
navigateToNextPage() {
|
|
623
|
+
const disableWhenPageInvalid = this.formOptions?.()?.nextButton?.disableWhenPageInvalid ?? true;
|
|
624
|
+
if (disableWhenPageInvalid && !this.currentPageValid()) {
|
|
625
|
+
return {
|
|
626
|
+
success: false,
|
|
627
|
+
newPageIndex: this.state().currentPageIndex,
|
|
628
|
+
error: 'Current page has invalid fields',
|
|
629
|
+
};
|
|
1227
630
|
}
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
631
|
+
const currentState = this.state();
|
|
632
|
+
const visibleIndices = this.visiblePageIndices();
|
|
633
|
+
if (currentState.isLastPage) {
|
|
634
|
+
return {
|
|
635
|
+
success: false,
|
|
636
|
+
newPageIndex: currentState.currentPageIndex,
|
|
637
|
+
error: 'Already on the last visible page',
|
|
638
|
+
};
|
|
1236
639
|
}
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
const right = this.parseMultiplication();
|
|
1244
|
-
node = { type: 'BinaryOp', operator, left: node, right };
|
|
640
|
+
if (currentState.navigationDisabled) {
|
|
641
|
+
return {
|
|
642
|
+
success: false,
|
|
643
|
+
newPageIndex: currentState.currentPageIndex,
|
|
644
|
+
error: 'Navigation is currently disabled',
|
|
645
|
+
};
|
|
1245
646
|
}
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
node = { type: 'BinaryOp', operator, left: node, right };
|
|
1254
|
-
}
|
|
1255
|
-
return node;
|
|
1256
|
-
}
|
|
1257
|
-
parseUnary() {
|
|
1258
|
-
if (this.match(TokenType.NOT, TokenType.MINUS, TokenType.PLUS)) {
|
|
1259
|
-
const operator = this.previous().value;
|
|
1260
|
-
const operand = this.parseUnary();
|
|
1261
|
-
return { type: 'UnaryOp', operator, operand };
|
|
1262
|
-
}
|
|
1263
|
-
return this.parsePostfix();
|
|
1264
|
-
}
|
|
1265
|
-
parsePostfix() {
|
|
1266
|
-
let node = this.parsePrimary();
|
|
1267
|
-
while (true) {
|
|
1268
|
-
if (this.match(TokenType.DOT)) {
|
|
1269
|
-
if (!this.check(TokenType.IDENTIFIER)) {
|
|
1270
|
-
throw new ExpressionParserError('Expected property name after "."', this.peek().position, this.expression);
|
|
1271
|
-
}
|
|
1272
|
-
const property = this.advance().value;
|
|
1273
|
-
node = { type: 'MemberAccess', object: node, property };
|
|
1274
|
-
}
|
|
1275
|
-
else if (this.match(TokenType.OPTIONAL_DOT)) {
|
|
1276
|
-
if (this.match(TokenType.LBRACKET)) {
|
|
1277
|
-
const property = this.parseExpression();
|
|
1278
|
-
this.consume(TokenType.RBRACKET, 'Expected "]" after computed property');
|
|
1279
|
-
node = { type: 'ComputedMemberAccess', object: node, property, optional: true };
|
|
1280
|
-
}
|
|
1281
|
-
else if (this.match(TokenType.LPAREN)) {
|
|
1282
|
-
const args = this.parseArgumentList();
|
|
1283
|
-
this.consume(TokenType.RPAREN, 'Expected ")" after arguments');
|
|
1284
|
-
node = { type: 'CallExpression', callee: node, arguments: args, optional: true };
|
|
1285
|
-
}
|
|
1286
|
-
else if (this.check(TokenType.IDENTIFIER)) {
|
|
1287
|
-
const property = this.advance().value;
|
|
1288
|
-
node = { type: 'MemberAccess', object: node, property, optional: true };
|
|
1289
|
-
}
|
|
1290
|
-
else {
|
|
1291
|
-
throw new ExpressionParserError('Expected property name, "[", or "(" after "?."', this.peek().position, this.expression);
|
|
1292
|
-
}
|
|
1293
|
-
}
|
|
1294
|
-
else if (this.match(TokenType.LBRACKET)) {
|
|
1295
|
-
const property = this.parseExpression();
|
|
1296
|
-
this.consume(TokenType.RBRACKET, 'Expected "]" after computed property');
|
|
1297
|
-
node = { type: 'ComputedMemberAccess', object: node, property };
|
|
1298
|
-
}
|
|
1299
|
-
else if (this.match(TokenType.LPAREN)) {
|
|
1300
|
-
const args = this.parseArgumentList();
|
|
1301
|
-
this.consume(TokenType.RPAREN, 'Expected ")" after arguments');
|
|
1302
|
-
node = { type: 'CallExpression', callee: node, arguments: args };
|
|
1303
|
-
}
|
|
1304
|
-
else {
|
|
1305
|
-
break;
|
|
1306
|
-
}
|
|
647
|
+
const currentVisiblePosition = visibleIndices.indexOf(currentState.currentPageIndex);
|
|
648
|
+
if (currentVisiblePosition === -1 || currentVisiblePosition >= visibleIndices.length - 1) {
|
|
649
|
+
return {
|
|
650
|
+
success: false,
|
|
651
|
+
newPageIndex: currentState.currentPageIndex,
|
|
652
|
+
error: 'No next visible page available',
|
|
653
|
+
};
|
|
1307
654
|
}
|
|
1308
|
-
|
|
655
|
+
const nextVisiblePageIndex = visibleIndices[currentVisiblePosition + 1];
|
|
656
|
+
return this.navigateToPage(nextVisiblePageIndex);
|
|
1309
657
|
}
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
}
|
|
1320
|
-
if (this.match(TokenType.UNDEFINED)) {
|
|
1321
|
-
return { type: 'Literal', value: undefined };
|
|
1322
|
-
}
|
|
1323
|
-
if (this.match(TokenType.NUMBER)) {
|
|
1324
|
-
return { type: 'Literal', value: parseFloat(this.previous().value) };
|
|
658
|
+
navigateToPreviousPage() {
|
|
659
|
+
const currentState = this.state();
|
|
660
|
+
const visibleIndices = this.visiblePageIndices();
|
|
661
|
+
if (currentState.isFirstPage) {
|
|
662
|
+
return {
|
|
663
|
+
success: false,
|
|
664
|
+
newPageIndex: currentState.currentPageIndex,
|
|
665
|
+
error: 'Already on the first visible page',
|
|
666
|
+
};
|
|
1325
667
|
}
|
|
1326
|
-
if (
|
|
1327
|
-
return {
|
|
668
|
+
if (currentState.navigationDisabled) {
|
|
669
|
+
return {
|
|
670
|
+
success: false,
|
|
671
|
+
newPageIndex: currentState.currentPageIndex,
|
|
672
|
+
error: 'Navigation is currently disabled',
|
|
673
|
+
};
|
|
1328
674
|
}
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
675
|
+
const currentVisiblePosition = visibleIndices.indexOf(currentState.currentPageIndex);
|
|
676
|
+
if (currentVisiblePosition <= 0) {
|
|
677
|
+
return {
|
|
678
|
+
success: false,
|
|
679
|
+
newPageIndex: currentState.currentPageIndex,
|
|
680
|
+
error: 'No previous visible page available',
|
|
681
|
+
};
|
|
1333
682
|
}
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
683
|
+
const prevVisiblePageIndex = visibleIndices[currentVisiblePosition - 1];
|
|
684
|
+
return this.navigateToPage(prevVisiblePageIndex);
|
|
685
|
+
}
|
|
686
|
+
navigateToPage(pageIndex) {
|
|
687
|
+
const currentState = this.state();
|
|
688
|
+
const totalPages = currentState.totalPages;
|
|
689
|
+
if (pageIndex < 0 || pageIndex >= totalPages) {
|
|
690
|
+
return {
|
|
691
|
+
success: false,
|
|
692
|
+
newPageIndex: currentState.currentPageIndex,
|
|
693
|
+
error: `Invalid page index: ${pageIndex}. Valid range is 0 to ${totalPages - 1}`,
|
|
694
|
+
};
|
|
1338
695
|
}
|
|
1339
|
-
|
|
1340
|
-
|
|
696
|
+
const visibleIndices = this.visiblePageIndices();
|
|
697
|
+
if (!visibleIndices.includes(pageIndex)) {
|
|
698
|
+
return {
|
|
699
|
+
success: false,
|
|
700
|
+
newPageIndex: currentState.currentPageIndex,
|
|
701
|
+
error: `Cannot navigate to hidden page at index ${pageIndex}. Visible pages: [${visibleIndices.join(', ')}]`,
|
|
702
|
+
};
|
|
1341
703
|
}
|
|
1342
|
-
if (
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
704
|
+
if (pageIndex === currentState.currentPageIndex) {
|
|
705
|
+
return {
|
|
706
|
+
success: true,
|
|
707
|
+
newPageIndex: pageIndex,
|
|
708
|
+
};
|
|
1346
709
|
}
|
|
1347
|
-
|
|
710
|
+
const previousIndex = currentState.currentPageIndex;
|
|
711
|
+
this.currentPageIndex.set(pageIndex);
|
|
712
|
+
this.eventBus.dispatch(PageChangeEvent, pageIndex, totalPages, previousIndex);
|
|
713
|
+
return {
|
|
714
|
+
success: true,
|
|
715
|
+
newPageIndex: pageIndex,
|
|
716
|
+
};
|
|
1348
717
|
}
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
718
|
+
findNearestVisiblePage(currentIndex, visibleIndices) {
|
|
719
|
+
let nearest = -1;
|
|
720
|
+
let minDistance = Infinity;
|
|
721
|
+
for (const idx of visibleIndices) {
|
|
722
|
+
const distance = Math.abs(idx - currentIndex);
|
|
723
|
+
if (distance < minDistance || (distance === minDistance && idx > nearest)) {
|
|
724
|
+
minDistance = distance;
|
|
725
|
+
nearest = idx;
|
|
726
|
+
}
|
|
1355
727
|
}
|
|
1356
|
-
return
|
|
728
|
+
return nearest;
|
|
1357
729
|
}
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
}
|
|
1365
|
-
|
|
730
|
+
setupEventListeners() {
|
|
731
|
+
this.eventBus
|
|
732
|
+
.on('next-page')
|
|
733
|
+
.pipe(takeUntilDestroyed())
|
|
734
|
+
.subscribe(() => {
|
|
735
|
+
this.navigateToNextPage();
|
|
736
|
+
});
|
|
737
|
+
this.eventBus
|
|
738
|
+
.on('previous-page')
|
|
739
|
+
.pipe(takeUntilDestroyed())
|
|
740
|
+
.subscribe(() => {
|
|
741
|
+
this.navigateToPreviousPage();
|
|
742
|
+
});
|
|
743
|
+
explicitEffect([this.state], ([state]) => this.eventBus.dispatch(PageNavigationStateChangeEvent, state));
|
|
1366
744
|
}
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
return properties;
|
|
745
|
+
evaluatePageHidden(page) {
|
|
746
|
+
if (!page.logic || page.logic.length === 0) {
|
|
747
|
+
return false;
|
|
1371
748
|
}
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
749
|
+
const hiddenLogic = page.logic.filter((l) => l.type === 'hidden');
|
|
750
|
+
if (hiddenLogic.length === 0) {
|
|
751
|
+
return false;
|
|
752
|
+
}
|
|
753
|
+
for (const logic of hiddenLogic) {
|
|
754
|
+
if (typeof logic.condition === 'boolean') {
|
|
755
|
+
if (logic.condition) {
|
|
756
|
+
return true;
|
|
757
|
+
}
|
|
758
|
+
continue;
|
|
1378
759
|
}
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
this.consume(TokenType.COLON, 'Expected ":" after property name in object literal');
|
|
1383
|
-
const value = this.parseExpression();
|
|
1384
|
-
properties.push({ key, value });
|
|
1385
|
-
} while (this.match(TokenType.COMMA));
|
|
1386
|
-
return properties;
|
|
1387
|
-
}
|
|
1388
|
-
match(...types) {
|
|
1389
|
-
for (const type of types) {
|
|
1390
|
-
if (this.check(type)) {
|
|
1391
|
-
this.advance();
|
|
760
|
+
const condition = logic.condition;
|
|
761
|
+
const context = this.fieldContextRegistry.createDisplayOnlyContext(page.key || '', this.functionRegistry.getCustomFunctions());
|
|
762
|
+
if (evaluateCondition(condition, context)) {
|
|
1392
763
|
return true;
|
|
1393
764
|
}
|
|
1394
765
|
}
|
|
1395
766
|
return false;
|
|
1396
767
|
}
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
768
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PageOrchestratorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
769
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", 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: [
|
|
770
|
+
{
|
|
771
|
+
provide: FIELD_SIGNAL_CONTEXT,
|
|
772
|
+
useFactory: (orchestrator) => orchestrator.extendedFieldSignalContext(),
|
|
773
|
+
deps: [PageOrchestratorComponent],
|
|
774
|
+
},
|
|
775
|
+
], ngImport: i0, template: `
|
|
776
|
+
@for (pageField of pageFields(); track pageField.key; let i = $index) {
|
|
777
|
+
<!--
|
|
778
|
+
Skip pages hidden by 'hidden' logic conditions entirely. FormStateManager
|
|
779
|
+
derives schema/value/validators from config (not from mounted components),
|
|
780
|
+
so unmounting a hidden page has no effect on form state — only on render +
|
|
781
|
+
CD cost. This is the page-level equivalent of the existing field-level
|
|
782
|
+
\`@if (!field.hidden())\` gate in page-field.component.ts.
|
|
783
|
+
-->
|
|
784
|
+
@if (!pageHiddenStates()[i]) {
|
|
785
|
+
@if (i === state().currentPageIndex || i === state().currentPageIndex + 1 || i === state().currentPageIndex - 1) {
|
|
786
|
+
<!-- Current and adjacent pages (±1): render immediately for flicker-free navigation -->
|
|
787
|
+
@defer (on immediate) {
|
|
788
|
+
<section
|
|
789
|
+
page-field
|
|
790
|
+
[field]="pageField"
|
|
791
|
+
[key]="pageField.key"
|
|
792
|
+
[pageIndex]="i"
|
|
793
|
+
[isVisible]="i === state().currentPageIndex"
|
|
794
|
+
></section>
|
|
795
|
+
} @placeholder {
|
|
796
|
+
<div class="df-page-placeholder" [attr.data-page-index]="i" [attr.data-page-key]="pageField.key"></div>
|
|
797
|
+
}
|
|
798
|
+
} @else {
|
|
799
|
+
<!-- Distant pages: defer until browser is idle for memory savings -->
|
|
800
|
+
@defer (on idle) {
|
|
801
|
+
<section page-field [field]="pageField" [key]="pageField.key" [pageIndex]="i" [isVisible]="false"></section>
|
|
802
|
+
} @placeholder {
|
|
803
|
+
<div class="df-page-placeholder" [attr.data-page-index]="i" [attr.data-page-key]="pageField.key"></div>
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
}
|
|
1420
807
|
}
|
|
808
|
+
`, isInline: true, styles: [":host{display:block;position:relative;width:100%}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, deferBlockDependencies: [() => [import('./ng-forge-dynamic-forms-page-field.component-BJRNnoz-.mjs').then(m => m.default)], () => [import('./ng-forge-dynamic-forms-page-field.component-BJRNnoz-.mjs').then(m => m.default)]] });
|
|
1421
809
|
}
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
'findIndex',
|
|
1455
|
-
'flat',
|
|
1456
|
-
'flatMap',
|
|
1457
|
-
'includes',
|
|
1458
|
-
'indexOf',
|
|
1459
|
-
'join',
|
|
1460
|
-
'lastIndexOf',
|
|
1461
|
-
'map',
|
|
1462
|
-
'reduce',
|
|
1463
|
-
'reduceRight',
|
|
1464
|
-
'slice',
|
|
1465
|
-
'some',
|
|
1466
|
-
'toString',
|
|
1467
|
-
'entries',
|
|
1468
|
-
'keys',
|
|
1469
|
-
'values',
|
|
1470
|
-
];
|
|
1471
|
-
const DATE_SAFE_METHODS = [
|
|
1472
|
-
'getDate',
|
|
1473
|
-
'getDay',
|
|
1474
|
-
'getFullYear',
|
|
1475
|
-
'getHours',
|
|
1476
|
-
'getMilliseconds',
|
|
1477
|
-
'getMinutes',
|
|
1478
|
-
'getMonth',
|
|
1479
|
-
'getSeconds',
|
|
1480
|
-
'getTime',
|
|
1481
|
-
'getTimezoneOffset',
|
|
1482
|
-
'getUTCDate',
|
|
1483
|
-
'getUTCDay',
|
|
1484
|
-
'getUTCFullYear',
|
|
1485
|
-
'getUTCHours',
|
|
1486
|
-
'getUTCMilliseconds',
|
|
1487
|
-
'getUTCMinutes',
|
|
1488
|
-
'getUTCMonth',
|
|
1489
|
-
'getUTCSeconds',
|
|
1490
|
-
'toDateString',
|
|
1491
|
-
'toISOString',
|
|
1492
|
-
'toJSON',
|
|
1493
|
-
'toString',
|
|
1494
|
-
'toTimeString',
|
|
1495
|
-
'toUTCString',
|
|
1496
|
-
];
|
|
1497
|
-
const SAFE_METHODS = {
|
|
1498
|
-
string: new Set(STRING_SAFE_METHODS),
|
|
1499
|
-
number: new Set(NUMBER_SAFE_METHODS),
|
|
1500
|
-
array: new Set(ARRAY_SAFE_METHODS),
|
|
1501
|
-
date: new Set(DATE_SAFE_METHODS),
|
|
1502
|
-
};
|
|
1503
|
-
const BLOCKED_PROPERTIES = new Set([
|
|
1504
|
-
'constructor',
|
|
1505
|
-
'__proto__',
|
|
1506
|
-
'prototype',
|
|
1507
|
-
'__defineGetter__',
|
|
1508
|
-
'__defineSetter__',
|
|
1509
|
-
'__lookupGetter__',
|
|
1510
|
-
'__lookupSetter__',
|
|
1511
|
-
]);
|
|
1512
|
-
class Evaluator {
|
|
1513
|
-
scope;
|
|
1514
|
-
expression;
|
|
1515
|
-
constructor(scope, expression) {
|
|
1516
|
-
this.scope = scope;
|
|
1517
|
-
this.expression = expression;
|
|
1518
|
-
}
|
|
1519
|
-
evaluate(node) {
|
|
1520
|
-
switch (node.type) {
|
|
1521
|
-
case 'Literal':
|
|
1522
|
-
return node.value;
|
|
1523
|
-
case 'Identifier':
|
|
1524
|
-
return this.evaluateIdentifier(node.name);
|
|
1525
|
-
case 'MemberAccess':
|
|
1526
|
-
return this.evaluateMemberAccess(node);
|
|
1527
|
-
case 'ComputedMemberAccess':
|
|
1528
|
-
return this.evaluateComputedMemberAccess(node);
|
|
1529
|
-
case 'BinaryOp':
|
|
1530
|
-
return this.evaluateBinaryOp(node);
|
|
1531
|
-
case 'UnaryOp':
|
|
1532
|
-
return this.evaluateUnaryOp(node);
|
|
1533
|
-
case 'CallExpression':
|
|
1534
|
-
return this.evaluateCallExpression(node);
|
|
1535
|
-
case 'Conditional':
|
|
1536
|
-
return this.evaluate(node.test) ? this.evaluate(node.consequent) : this.evaluate(node.alternate);
|
|
1537
|
-
case 'ArrayLiteral':
|
|
1538
|
-
return this.evaluateArrayLiteral(node);
|
|
1539
|
-
case 'ObjectLiteral':
|
|
1540
|
-
return this.evaluateObjectLiteral(node);
|
|
1541
|
-
case 'ArrowFunction':
|
|
1542
|
-
return this.evaluateArrowFunction(node);
|
|
1543
|
-
default:
|
|
1544
|
-
throw new ExpressionParserError(`Unknown node type: ${node.type}`, 0, this.expression);
|
|
810
|
+
i0.ɵɵngDeclareClassMetadataAsync({ minVersion: "18.0.0", version: "21.2.14", ngImport: i0, type: PageOrchestratorComponent, resolveDeferredDeps: () => [import('./ng-forge-dynamic-forms-page-field.component-BJRNnoz-.mjs').then(m => m.default)], resolveMetadata: PageFieldComponent => ({ decorators: [{
|
|
811
|
+
type: Component,
|
|
812
|
+
args: [{ selector: 'div[page-orchestrator]', imports: [PageFieldComponent], template: `
|
|
813
|
+
@for (pageField of pageFields(); track pageField.key; let i = $index) {
|
|
814
|
+
<!--
|
|
815
|
+
Skip pages hidden by 'hidden' logic conditions entirely. FormStateManager
|
|
816
|
+
derives schema/value/validators from config (not from mounted components),
|
|
817
|
+
so unmounting a hidden page has no effect on form state — only on render +
|
|
818
|
+
CD cost. This is the page-level equivalent of the existing field-level
|
|
819
|
+
\`@if (!field.hidden())\` gate in page-field.component.ts.
|
|
820
|
+
-->
|
|
821
|
+
@if (!pageHiddenStates()[i]) {
|
|
822
|
+
@if (i === state().currentPageIndex || i === state().currentPageIndex + 1 || i === state().currentPageIndex - 1) {
|
|
823
|
+
<!-- Current and adjacent pages (±1): render immediately for flicker-free navigation -->
|
|
824
|
+
@defer (on immediate) {
|
|
825
|
+
<section
|
|
826
|
+
page-field
|
|
827
|
+
[field]="pageField"
|
|
828
|
+
[key]="pageField.key"
|
|
829
|
+
[pageIndex]="i"
|
|
830
|
+
[isVisible]="i === state().currentPageIndex"
|
|
831
|
+
></section>
|
|
832
|
+
} @placeholder {
|
|
833
|
+
<div class="df-page-placeholder" [attr.data-page-index]="i" [attr.data-page-key]="pageField.key"></div>
|
|
834
|
+
}
|
|
835
|
+
} @else {
|
|
836
|
+
<!-- Distant pages: defer until browser is idle for memory savings -->
|
|
837
|
+
@defer (on idle) {
|
|
838
|
+
<section page-field [field]="pageField" [key]="pageField.key" [pageIndex]="i" [isVisible]="false"></section>
|
|
839
|
+
} @placeholder {
|
|
840
|
+
<div class="df-page-placeholder" [attr.data-page-index]="i" [attr.data-page-key]="pageField.key"></div>
|
|
841
|
+
}
|
|
1545
842
|
}
|
|
843
|
+
}
|
|
1546
844
|
}
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
845
|
+
`, host: {
|
|
846
|
+
class: 'df-page-orchestrator',
|
|
847
|
+
'[attr.data-current-page]': 'state().currentPageIndex',
|
|
848
|
+
'[attr.data-total-pages]': 'state().totalPages',
|
|
849
|
+
}, providers: [
|
|
850
|
+
{
|
|
851
|
+
provide: FIELD_SIGNAL_CONTEXT,
|
|
852
|
+
useFactory: (orchestrator) => orchestrator.extendedFieldSignalContext(),
|
|
853
|
+
deps: [PageOrchestratorComponent],
|
|
854
|
+
},
|
|
855
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;position:relative;width:100%}\n"] }]
|
|
856
|
+
}], 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 }] }] } }) });
|
|
857
|
+
function collectLeafFieldKeys(fields) {
|
|
858
|
+
const keys = [];
|
|
859
|
+
for (const field of fields) {
|
|
860
|
+
if (isRowField(field)) {
|
|
861
|
+
keys.push(...collectLeafFieldKeys(field.fields));
|
|
862
|
+
}
|
|
863
|
+
else if (field.key) {
|
|
864
|
+
keys.push(field.key);
|
|
1550
865
|
}
|
|
1551
|
-
return undefined;
|
|
1552
866
|
}
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
867
|
+
return keys;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
const FORM_INITIALIZER = new InjectionToken('FORM_INITIALIZER');
|
|
871
|
+
|
|
872
|
+
const NORMALIZED_ARRAY_METADATA = Symbol('normalizedArrayMetadata');
|
|
873
|
+
function setNormalizedArrayMetadata(arrayField, metadata) {
|
|
874
|
+
arrayField[NORMALIZED_ARRAY_METADATA] = metadata;
|
|
875
|
+
}
|
|
876
|
+
function getNormalizedArrayMetadata(arrayField) {
|
|
877
|
+
return arrayField[NORMALIZED_ARRAY_METADATA];
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
function normalizeSimplifiedArrays(fields) {
|
|
881
|
+
return fields.flatMap((field) => {
|
|
882
|
+
if (isContainerField(field) && !isArrayField(field) && hasChildFields(field)) {
|
|
883
|
+
return [{ ...field, fields: normalizeSimplifiedArrays(field.fields) }];
|
|
1557
884
|
}
|
|
1558
|
-
if (
|
|
1559
|
-
|
|
885
|
+
if (isArrayField(field) && !isSimplifiedArrayField(field)) {
|
|
886
|
+
const normalizedItems = field.fields.map((item) => Array.isArray(item) ? normalizeSimplifiedArrays(item) : item);
|
|
887
|
+
return [{ ...field, fields: normalizedItems }];
|
|
1560
888
|
}
|
|
1561
|
-
if (
|
|
1562
|
-
|
|
889
|
+
if (isSimplifiedArrayField(field)) {
|
|
890
|
+
const { arrayField, addButton } = expandSimplifiedArray(field);
|
|
891
|
+
return addButton ? [arrayField, addButton] : [arrayField];
|
|
1563
892
|
}
|
|
1564
|
-
|
|
1565
|
-
}
|
|
1566
|
-
evaluateComputedMemberAccess(node) {
|
|
1567
|
-
const obj = this.evaluate(node.object);
|
|
1568
|
-
if (obj === null || obj === undefined) {
|
|
1569
|
-
return undefined;
|
|
1570
|
-
}
|
|
1571
|
-
const rawKey = this.evaluate(node.property);
|
|
1572
|
-
if (rawKey === null || rawKey === undefined)
|
|
1573
|
-
return undefined;
|
|
1574
|
-
const key = String(rawKey);
|
|
1575
|
-
if (BLOCKED_PROPERTIES.has(key)) {
|
|
1576
|
-
throw new ExpressionParserError(`Property "${key}" is not accessible for security reasons`, 0, this.expression);
|
|
1577
|
-
}
|
|
1578
|
-
if (typeof obj === 'object' || typeof obj === 'string' || typeof obj === 'number') {
|
|
1579
|
-
return obj[key];
|
|
1580
|
-
}
|
|
1581
|
-
return undefined;
|
|
1582
|
-
}
|
|
1583
|
-
evaluateBinaryOp(node) {
|
|
1584
|
-
if (node.operator === '&&') {
|
|
1585
|
-
const left = this.evaluate(node.left);
|
|
1586
|
-
return left ? this.evaluate(node.right) : left;
|
|
1587
|
-
}
|
|
1588
|
-
if (node.operator === '||') {
|
|
1589
|
-
const left = this.evaluate(node.left);
|
|
1590
|
-
return left ? left : this.evaluate(node.right);
|
|
1591
|
-
}
|
|
1592
|
-
const left = this.evaluate(node.left);
|
|
1593
|
-
const right = this.evaluate(node.right);
|
|
1594
|
-
switch (node.operator) {
|
|
1595
|
-
case '+':
|
|
1596
|
-
return left + right;
|
|
1597
|
-
case '-':
|
|
1598
|
-
return left - right;
|
|
1599
|
-
case '*':
|
|
1600
|
-
return left * right;
|
|
1601
|
-
case '/':
|
|
1602
|
-
if (right === 0)
|
|
1603
|
-
return null;
|
|
1604
|
-
return left / right;
|
|
1605
|
-
case '%':
|
|
1606
|
-
if (right === 0)
|
|
1607
|
-
return null;
|
|
1608
|
-
return left % right;
|
|
1609
|
-
case '==':
|
|
1610
|
-
return left == right;
|
|
1611
|
-
case '===':
|
|
1612
|
-
return left === right;
|
|
1613
|
-
case '!=':
|
|
1614
|
-
return left != right;
|
|
1615
|
-
case '!==':
|
|
1616
|
-
return left !== right;
|
|
1617
|
-
case '>':
|
|
1618
|
-
return left > right;
|
|
1619
|
-
case '<':
|
|
1620
|
-
return left < right;
|
|
1621
|
-
case '>=':
|
|
1622
|
-
return left >= right;
|
|
1623
|
-
case '<=':
|
|
1624
|
-
return left <= right;
|
|
1625
|
-
default:
|
|
1626
|
-
throw new ExpressionParserError(`Unknown binary operator: ${node.operator}`, 0, this.expression);
|
|
1627
|
-
}
|
|
1628
|
-
}
|
|
1629
|
-
evaluateUnaryOp(node) {
|
|
1630
|
-
const operand = this.evaluate(node.operand);
|
|
1631
|
-
switch (node.operator) {
|
|
1632
|
-
case '!':
|
|
1633
|
-
return !operand;
|
|
1634
|
-
case '-':
|
|
1635
|
-
return -operand;
|
|
1636
|
-
case '+':
|
|
1637
|
-
return +operand;
|
|
1638
|
-
default:
|
|
1639
|
-
throw new ExpressionParserError(`Unknown unary operator: ${node.operator}`, 0, this.expression);
|
|
1640
|
-
}
|
|
1641
|
-
}
|
|
1642
|
-
evaluateCallExpression(node) {
|
|
1643
|
-
if (node.callee.type !== 'MemberAccess') {
|
|
1644
|
-
throw new ExpressionParserError('Only method calls are allowed, not arbitrary function calls', 0, this.expression);
|
|
1645
|
-
}
|
|
1646
|
-
const obj = this.evaluate(node.callee.object);
|
|
1647
|
-
if (obj === null || obj === undefined) {
|
|
1648
|
-
return undefined;
|
|
1649
|
-
}
|
|
1650
|
-
const methodName = node.callee.property;
|
|
1651
|
-
if (BLOCKED_PROPERTIES.has(methodName)) {
|
|
1652
|
-
throw new ExpressionParserError(`Property "${methodName}" is not accessible for security reasons`, 0, this.expression);
|
|
1653
|
-
}
|
|
1654
|
-
if (!this.isMethodSafe(obj, methodName)) {
|
|
1655
|
-
throw new ExpressionParserError(`Method "${methodName}" is not allowed for security reasons`, 0, this.expression);
|
|
1656
|
-
}
|
|
1657
|
-
const args = node.arguments.map((arg) => this.evaluate(arg));
|
|
1658
|
-
const method = obj[methodName];
|
|
1659
|
-
if (typeof method === 'function') {
|
|
1660
|
-
return method.call(obj, ...args);
|
|
1661
|
-
}
|
|
1662
|
-
return undefined;
|
|
1663
|
-
}
|
|
1664
|
-
evaluateArrayLiteral(node) {
|
|
1665
|
-
return node.elements.map((element) => this.evaluate(element));
|
|
1666
|
-
}
|
|
1667
|
-
evaluateObjectLiteral(node) {
|
|
1668
|
-
const result = {};
|
|
1669
|
-
for (const { key, value } of node.properties) {
|
|
1670
|
-
if (BLOCKED_PROPERTIES.has(key)) {
|
|
1671
|
-
throw new ExpressionParserError(`Property "${key}" is not allowed in object literals for security reasons`, 0, this.expression);
|
|
1672
|
-
}
|
|
1673
|
-
result[key] = this.evaluate(value);
|
|
1674
|
-
}
|
|
1675
|
-
return result;
|
|
1676
|
-
}
|
|
1677
|
-
evaluateArrowFunction(node) {
|
|
1678
|
-
const parentScope = this.scope;
|
|
1679
|
-
const expression = this.expression;
|
|
1680
|
-
return (...args) => {
|
|
1681
|
-
const childScope = { ...parentScope };
|
|
1682
|
-
for (let i = 0; i < node.params.length; i++) {
|
|
1683
|
-
childScope[node.params[i]] = args[i];
|
|
1684
|
-
}
|
|
1685
|
-
const childEvaluator = new Evaluator(childScope, expression);
|
|
1686
|
-
return childEvaluator.evaluate(node.body);
|
|
1687
|
-
};
|
|
1688
|
-
}
|
|
1689
|
-
isMethodSafe(obj, methodName) {
|
|
1690
|
-
if (obj === null || obj === undefined) {
|
|
1691
|
-
return false;
|
|
1692
|
-
}
|
|
1693
|
-
const type = Array.isArray(obj) ? 'array' : obj instanceof Date ? 'date' : typeof obj;
|
|
1694
|
-
const safeMethods = SAFE_METHODS[type];
|
|
1695
|
-
if (!safeMethods) {
|
|
1696
|
-
return false;
|
|
1697
|
-
}
|
|
1698
|
-
return safeMethods.has(methodName);
|
|
1699
|
-
}
|
|
1700
|
-
}
|
|
1701
|
-
|
|
1702
|
-
const MAX_AST_CACHE_SIZE$1 = 1000;
|
|
1703
|
-
class LRUCache {
|
|
1704
|
-
cache = new Map();
|
|
1705
|
-
maxSize;
|
|
1706
|
-
constructor(maxSize) {
|
|
1707
|
-
this.maxSize = maxSize;
|
|
1708
|
-
}
|
|
1709
|
-
get(key) {
|
|
1710
|
-
const value = this.cache.get(key);
|
|
1711
|
-
if (value !== undefined) {
|
|
1712
|
-
this.cache.delete(key);
|
|
1713
|
-
this.cache.set(key, value);
|
|
1714
|
-
}
|
|
1715
|
-
return value;
|
|
1716
|
-
}
|
|
1717
|
-
set(key, value) {
|
|
1718
|
-
if (this.cache.has(key)) {
|
|
1719
|
-
this.cache.delete(key);
|
|
1720
|
-
}
|
|
1721
|
-
this.cache.set(key, value);
|
|
1722
|
-
if (this.cache.size > this.maxSize) {
|
|
1723
|
-
const firstKey = this.cache.keys().next().value;
|
|
1724
|
-
if (firstKey !== undefined) {
|
|
1725
|
-
this.cache.delete(firstKey);
|
|
1726
|
-
}
|
|
1727
|
-
}
|
|
1728
|
-
}
|
|
1729
|
-
clear() {
|
|
1730
|
-
this.cache.clear();
|
|
1731
|
-
}
|
|
1732
|
-
get size() {
|
|
1733
|
-
return this.cache.size;
|
|
1734
|
-
}
|
|
1735
|
-
}
|
|
1736
|
-
const astCache = new LRUCache(MAX_AST_CACHE_SIZE$1);
|
|
1737
|
-
class ExpressionParser {
|
|
1738
|
-
static parse(expression) {
|
|
1739
|
-
const cached = astCache.get(expression);
|
|
1740
|
-
if (cached) {
|
|
1741
|
-
return cached;
|
|
1742
|
-
}
|
|
1743
|
-
const parser = new Parser(expression);
|
|
1744
|
-
const ast = parser.parse();
|
|
1745
|
-
astCache.set(expression, ast);
|
|
1746
|
-
return ast;
|
|
1747
|
-
}
|
|
1748
|
-
static evaluate(expression, scope) {
|
|
1749
|
-
try {
|
|
1750
|
-
const ast = this.parse(expression);
|
|
1751
|
-
const evaluator = new Evaluator(scope, expression);
|
|
1752
|
-
return evaluator.evaluate(ast);
|
|
1753
|
-
}
|
|
1754
|
-
catch (error) {
|
|
1755
|
-
if (error instanceof ExpressionParserError) {
|
|
1756
|
-
throw error;
|
|
1757
|
-
}
|
|
1758
|
-
throw new ExpressionParserError(`Error evaluating expression: ${error instanceof Error ? error.message : String(error)}`, 0, expression);
|
|
1759
|
-
}
|
|
1760
|
-
}
|
|
1761
|
-
static clearCache() {
|
|
1762
|
-
astCache.clear();
|
|
1763
|
-
}
|
|
1764
|
-
static getCacheStats() {
|
|
1765
|
-
return {
|
|
1766
|
-
size: astCache.size,
|
|
1767
|
-
maxSize: MAX_AST_CACHE_SIZE$1,
|
|
1768
|
-
};
|
|
1769
|
-
}
|
|
1770
|
-
}
|
|
1771
|
-
|
|
1772
|
-
function evaluateCondition(expression, context) {
|
|
1773
|
-
switch (expression.type) {
|
|
1774
|
-
case 'fieldValue':
|
|
1775
|
-
return evaluateFieldValueCondition(expression, context);
|
|
1776
|
-
case 'javascript':
|
|
1777
|
-
return evaluateJavaScriptExpression(expression, context);
|
|
1778
|
-
case 'custom': {
|
|
1779
|
-
const bothSet = expression;
|
|
1780
|
-
const inlineFn = bothSet.fn;
|
|
1781
|
-
const registeredName = bothSet.functionName;
|
|
1782
|
-
if (inlineFn && registeredName) {
|
|
1783
|
-
context.logger.warn('Both "fn" and "functionName" are set on custom condition. Inline "fn" takes precedence; "functionName" is ignored.');
|
|
1784
|
-
}
|
|
1785
|
-
const customFn = inlineFn ?? (registeredName ? context.customFunctions?.[registeredName] : undefined);
|
|
1786
|
-
if (!customFn) {
|
|
1787
|
-
context.logger.error('Custom function not found:', registeredName);
|
|
1788
|
-
return false;
|
|
1789
|
-
}
|
|
1790
|
-
try {
|
|
1791
|
-
return !!customFn(context);
|
|
1792
|
-
}
|
|
1793
|
-
catch (error) {
|
|
1794
|
-
context.logger.error('Error executing custom function:', registeredName ?? '<inline>', error);
|
|
1795
|
-
return false;
|
|
1796
|
-
}
|
|
1797
|
-
}
|
|
1798
|
-
case 'and':
|
|
1799
|
-
return evaluateAndCondition(expression, context);
|
|
1800
|
-
case 'or':
|
|
1801
|
-
return evaluateOrCondition(expression, context);
|
|
1802
|
-
case 'http':
|
|
1803
|
-
context.logger.warn('HTTP conditions are resolved asynchronously via createHttpConditionLogicFunction(). ' +
|
|
1804
|
-
'When used inside and/or composites, the HTTP result is not available synchronously. Returning false.');
|
|
1805
|
-
return false;
|
|
1806
|
-
case 'async':
|
|
1807
|
-
context.logger.warn('Async Condition - resolved asynchronously via createAsyncConditionLogicFunction(). ' +
|
|
1808
|
-
'When used inside and/or composites, the async result is not available synchronously. Returning false.');
|
|
1809
|
-
return false;
|
|
1810
|
-
default:
|
|
1811
|
-
return false;
|
|
1812
|
-
}
|
|
1813
|
-
}
|
|
1814
|
-
function evaluateFieldValueCondition(expression, context) {
|
|
1815
|
-
if (!expression.fieldPath)
|
|
1816
|
-
return false;
|
|
1817
|
-
const fieldValue = hasNestedProperty(context.formValue, expression.fieldPath)
|
|
1818
|
-
? getNestedValue(context.formValue, expression.fieldPath)
|
|
1819
|
-
: context.rootFormValue
|
|
1820
|
-
? getNestedValue(context.rootFormValue, expression.fieldPath)
|
|
1821
|
-
: undefined;
|
|
1822
|
-
return compareValues(fieldValue, expression.value, expression.operator);
|
|
1823
|
-
}
|
|
1824
|
-
function evaluateJavaScriptExpression(expression, context) {
|
|
1825
|
-
try {
|
|
1826
|
-
const result = ExpressionParser.evaluate(expression.expression, context);
|
|
1827
|
-
return !!result;
|
|
1828
|
-
}
|
|
1829
|
-
catch (error) {
|
|
1830
|
-
context.logger.error('Error evaluating JavaScript expression:', expression.expression, error);
|
|
1831
|
-
return false;
|
|
1832
|
-
}
|
|
1833
|
-
}
|
|
1834
|
-
function evaluateAndCondition(expression, context) {
|
|
1835
|
-
if (expression.conditions.length === 0)
|
|
1836
|
-
return false;
|
|
1837
|
-
return expression.conditions.every((condition) => evaluateCondition(condition, context));
|
|
1838
|
-
}
|
|
1839
|
-
function evaluateOrCondition(expression, context) {
|
|
1840
|
-
if (expression.conditions.length === 0)
|
|
1841
|
-
return false;
|
|
1842
|
-
return expression.conditions.some((condition) => evaluateCondition(condition, context));
|
|
1843
|
-
}
|
|
1844
|
-
|
|
1845
|
-
class FunctionRegistryService {
|
|
1846
|
-
customFunctions = new Map();
|
|
1847
|
-
customFunctionScopes = new Map();
|
|
1848
|
-
derivationFunctions = new Map();
|
|
1849
|
-
asyncDerivationFunctions = new Map();
|
|
1850
|
-
asyncConditionFunctions = new Map();
|
|
1851
|
-
validators = new Map();
|
|
1852
|
-
asyncValidators = new Map();
|
|
1853
|
-
httpValidators = new Map();
|
|
1854
|
-
registerCustomFunction(name, fn, options) {
|
|
1855
|
-
this.customFunctions.set(name, fn);
|
|
1856
|
-
this.customFunctionScopes.set(name, options?.scope ?? 'form');
|
|
1857
|
-
}
|
|
1858
|
-
getCustomFunctionScope(name) {
|
|
1859
|
-
return this.customFunctionScopes.get(name);
|
|
1860
|
-
}
|
|
1861
|
-
isFieldScopedFunction(name) {
|
|
1862
|
-
return this.customFunctionScopes.get(name) === 'field';
|
|
1863
|
-
}
|
|
1864
|
-
getCustomFunctions() {
|
|
1865
|
-
return Object.fromEntries(this.customFunctions);
|
|
1866
|
-
}
|
|
1867
|
-
clearCustomFunctions() {
|
|
1868
|
-
this.customFunctions.clear();
|
|
1869
|
-
this.customFunctionScopes.clear();
|
|
1870
|
-
}
|
|
1871
|
-
registerDerivationFunction(name, fn) {
|
|
1872
|
-
this.derivationFunctions.set(name, fn);
|
|
1873
|
-
}
|
|
1874
|
-
getDerivationFunction(name) {
|
|
1875
|
-
return this.derivationFunctions.get(name);
|
|
1876
|
-
}
|
|
1877
|
-
getDerivationFunctions() {
|
|
1878
|
-
return Object.fromEntries(this.derivationFunctions);
|
|
1879
|
-
}
|
|
1880
|
-
setDerivationFunctions(derivations) {
|
|
1881
|
-
this.setRegistryIfChanged(this.derivationFunctions, derivations);
|
|
1882
|
-
}
|
|
1883
|
-
clearDerivationFunctions() {
|
|
1884
|
-
this.derivationFunctions.clear();
|
|
1885
|
-
}
|
|
1886
|
-
registerAsyncDerivationFunction(name, fn) {
|
|
1887
|
-
this.asyncDerivationFunctions.set(name, fn);
|
|
1888
|
-
}
|
|
1889
|
-
getAsyncDerivationFunction(name) {
|
|
1890
|
-
return this.asyncDerivationFunctions.get(name);
|
|
1891
|
-
}
|
|
1892
|
-
getAsyncDerivationFunctions() {
|
|
1893
|
-
return Object.fromEntries(this.asyncDerivationFunctions);
|
|
1894
|
-
}
|
|
1895
|
-
setAsyncDerivationFunctions(fns) {
|
|
1896
|
-
this.setRegistryIfChanged(this.asyncDerivationFunctions, fns);
|
|
1897
|
-
}
|
|
1898
|
-
clearAsyncDerivationFunctions() {
|
|
1899
|
-
this.asyncDerivationFunctions.clear();
|
|
1900
|
-
}
|
|
1901
|
-
registerAsyncConditionFunction(name, fn) {
|
|
1902
|
-
this.asyncConditionFunctions.set(name, fn);
|
|
1903
|
-
}
|
|
1904
|
-
getAsyncConditionFunction(name) {
|
|
1905
|
-
return this.asyncConditionFunctions.get(name);
|
|
1906
|
-
}
|
|
1907
|
-
getAsyncConditionFunctions() {
|
|
1908
|
-
return Object.fromEntries(this.asyncConditionFunctions);
|
|
1909
|
-
}
|
|
1910
|
-
setAsyncConditionFunctions(fns) {
|
|
1911
|
-
this.setRegistryIfChanged(this.asyncConditionFunctions, fns);
|
|
1912
|
-
}
|
|
1913
|
-
clearAsyncConditionFunctions() {
|
|
1914
|
-
this.asyncConditionFunctions.clear();
|
|
1915
|
-
}
|
|
1916
|
-
registerValidator(name, fn) {
|
|
1917
|
-
this.validators.set(name, fn);
|
|
1918
|
-
}
|
|
1919
|
-
getValidator(name) {
|
|
1920
|
-
return this.validators.get(name);
|
|
1921
|
-
}
|
|
1922
|
-
clearValidators() {
|
|
1923
|
-
this.validators.clear();
|
|
1924
|
-
}
|
|
1925
|
-
registerAsyncValidator(name, fn) {
|
|
1926
|
-
this.asyncValidators.set(name, fn);
|
|
1927
|
-
}
|
|
1928
|
-
getAsyncValidator(name) {
|
|
1929
|
-
return this.asyncValidators.get(name);
|
|
1930
|
-
}
|
|
1931
|
-
clearAsyncValidators() {
|
|
1932
|
-
this.asyncValidators.clear();
|
|
1933
|
-
}
|
|
1934
|
-
registerHttpValidator(name, config) {
|
|
1935
|
-
this.httpValidators.set(name, config);
|
|
1936
|
-
}
|
|
1937
|
-
getHttpValidator(name) {
|
|
1938
|
-
return this.httpValidators.get(name);
|
|
1939
|
-
}
|
|
1940
|
-
clearHttpValidators() {
|
|
1941
|
-
this.httpValidators.clear();
|
|
1942
|
-
}
|
|
1943
|
-
setRegistryIfChanged(registry, values) {
|
|
1944
|
-
if (!values)
|
|
1945
|
-
return;
|
|
1946
|
-
const entries = Object.entries(values);
|
|
1947
|
-
const hasChanges = entries.some(([name, value]) => registry.get(name) !== value);
|
|
1948
|
-
if (hasChanges) {
|
|
1949
|
-
entries.forEach(([name, value]) => registry.set(name, value));
|
|
1950
|
-
}
|
|
1951
|
-
}
|
|
1952
|
-
setValidators(validators) {
|
|
1953
|
-
this.setRegistryIfChanged(this.validators, validators);
|
|
1954
|
-
}
|
|
1955
|
-
setAsyncValidators(asyncValidators) {
|
|
1956
|
-
this.setRegistryIfChanged(this.asyncValidators, asyncValidators);
|
|
1957
|
-
}
|
|
1958
|
-
setHttpValidators(httpValidators) {
|
|
1959
|
-
this.setRegistryIfChanged(this.httpValidators, httpValidators);
|
|
1960
|
-
}
|
|
1961
|
-
clearAll() {
|
|
1962
|
-
this.clearCustomFunctions();
|
|
1963
|
-
this.clearDerivationFunctions();
|
|
1964
|
-
this.clearAsyncDerivationFunctions();
|
|
1965
|
-
this.clearAsyncConditionFunctions();
|
|
1966
|
-
this.clearValidators();
|
|
1967
|
-
this.clearAsyncValidators();
|
|
1968
|
-
this.clearHttpValidators();
|
|
1969
|
-
}
|
|
1970
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: FunctionRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1971
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: FunctionRegistryService });
|
|
1972
|
-
}
|
|
1973
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: FunctionRegistryService, decorators: [{
|
|
1974
|
-
type: Injectable
|
|
1975
|
-
}] });
|
|
1976
|
-
|
|
1977
|
-
const DEPRECATION_WARNING_TRACKER = new InjectionToken('DeprecationWarningTracker');
|
|
1978
|
-
|
|
1979
|
-
const STATE_PROPERTIES = ['touched', 'dirty', 'valid', 'invalid', 'pending', 'hidden', 'readonly', 'disabled'];
|
|
1980
|
-
function readStateSignal(fieldState, prop, reactive) {
|
|
1981
|
-
const signalFn = fieldState[prop];
|
|
1982
|
-
if (!signalFn || !isSignal(signalFn))
|
|
1983
|
-
return false;
|
|
1984
|
-
return reactive ? !!signalFn() : !!untracked(signalFn);
|
|
1985
|
-
}
|
|
1986
|
-
function resolveFieldState(fieldSource, reactive) {
|
|
1987
|
-
if (!fieldSource)
|
|
1988
|
-
return undefined;
|
|
1989
|
-
if (typeof fieldSource === 'function') {
|
|
1990
|
-
return reactive ? fieldSource() : untracked(fieldSource);
|
|
1991
|
-
}
|
|
1992
|
-
if (typeof fieldSource === 'object') {
|
|
1993
|
-
return fieldSource;
|
|
1994
|
-
}
|
|
1995
|
-
return undefined;
|
|
1996
|
-
}
|
|
1997
|
-
function readFieldStateInfo(fieldSource, reactive) {
|
|
1998
|
-
const state = resolveFieldState(fieldSource, reactive);
|
|
1999
|
-
if (!state || typeof state !== 'object')
|
|
2000
|
-
return undefined;
|
|
2001
|
-
const knownProperties = new Set([...STATE_PROPERTIES, 'pristine']);
|
|
2002
|
-
return new Proxy({}, {
|
|
2003
|
-
get(_target, prop) {
|
|
2004
|
-
if (prop === 'pristine') {
|
|
2005
|
-
return !readStateSignal(state, 'dirty', reactive);
|
|
2006
|
-
}
|
|
2007
|
-
if (STATE_PROPERTIES.includes(prop)) {
|
|
2008
|
-
return readStateSignal(state, prop, reactive);
|
|
2009
|
-
}
|
|
2010
|
-
return false;
|
|
2011
|
-
},
|
|
2012
|
-
has(_target, prop) {
|
|
2013
|
-
return knownProperties.has(prop);
|
|
2014
|
-
},
|
|
2015
|
-
});
|
|
2016
|
-
}
|
|
2017
|
-
function createFormFieldStateMap(rootForm, reactive) {
|
|
2018
|
-
const cache = new Map();
|
|
2019
|
-
return new Proxy({}, {
|
|
2020
|
-
get(_target, key) {
|
|
2021
|
-
if (cache.has(key)) {
|
|
2022
|
-
return cache.get(key);
|
|
2023
|
-
}
|
|
2024
|
-
const fieldAccessor = navigateToFieldAccessor(rootForm, key);
|
|
2025
|
-
const snapshot = readFieldStateInfo(fieldAccessor, reactive);
|
|
2026
|
-
cache.set(key, snapshot);
|
|
2027
|
-
return snapshot;
|
|
2028
|
-
},
|
|
2029
|
-
has(_target, key) {
|
|
2030
|
-
return navigateToFieldAccessor(rootForm, key) !== undefined;
|
|
2031
|
-
},
|
|
2032
|
-
});
|
|
2033
|
-
}
|
|
2034
|
-
function navigateToFieldAccessor(rootForm, keyPath) {
|
|
2035
|
-
if (!keyPath)
|
|
2036
|
-
return undefined;
|
|
2037
|
-
const parts = keyPath.split('.');
|
|
2038
|
-
let current = rootForm;
|
|
2039
|
-
for (let i = 0; i < parts.length - 1; i++) {
|
|
2040
|
-
const next = current[parts[i]];
|
|
2041
|
-
if (!next)
|
|
2042
|
-
return undefined;
|
|
2043
|
-
current = next;
|
|
2044
|
-
}
|
|
2045
|
-
return current[parts[parts.length - 1]];
|
|
2046
|
-
}
|
|
2047
|
-
|
|
2048
|
-
function safeReadPathKeys(fieldContext) {
|
|
2049
|
-
if (!('pathKeys' in fieldContext) || typeof fieldContext.pathKeys !== 'function') {
|
|
2050
|
-
return [];
|
|
2051
|
-
}
|
|
2052
|
-
return untracked(() => fieldContext.pathKeys());
|
|
2053
|
-
}
|
|
2054
|
-
|
|
2055
|
-
function isChildFieldContext(context) {
|
|
2056
|
-
return 'key' in context && isSignal(context.key);
|
|
2057
|
-
}
|
|
2058
|
-
function extractFieldState(fieldContext) {
|
|
2059
|
-
return untracked(() => {
|
|
2060
|
-
if (!fieldContext || !('state' in fieldContext))
|
|
2061
|
-
return undefined;
|
|
2062
|
-
return fieldContext.state;
|
|
2063
|
-
});
|
|
2064
|
-
}
|
|
2065
|
-
function detectArrayScope(pathKeys) {
|
|
2066
|
-
if (pathKeys.length < 3)
|
|
2067
|
-
return undefined;
|
|
2068
|
-
for (let i = pathKeys.length - 2; i >= 1; i--) {
|
|
2069
|
-
const maybeIndex = Number(pathKeys[i]);
|
|
2070
|
-
if (Number.isInteger(maybeIndex) && maybeIndex >= 0) {
|
|
2071
|
-
return {
|
|
2072
|
-
arrayKey: pathKeys.slice(0, i).join('.'),
|
|
2073
|
-
index: maybeIndex,
|
|
2074
|
-
localKey: pathKeys[pathKeys.length - 1],
|
|
2075
|
-
};
|
|
2076
|
-
}
|
|
2077
|
-
}
|
|
2078
|
-
return undefined;
|
|
2079
|
-
}
|
|
2080
|
-
class FieldContextRegistryService {
|
|
2081
|
-
rootFormRegistry = inject(RootFormRegistryService);
|
|
2082
|
-
logger = inject(DynamicFormLogger);
|
|
2083
|
-
deprecationTracker = inject(DEPRECATION_WARNING_TRACKER, { optional: true });
|
|
2084
|
-
externalDataSignal = inject(EXTERNAL_DATA, { optional: true });
|
|
2085
|
-
createEvaluationContext(fieldContext, customFunctions) {
|
|
2086
|
-
const fieldValue = untracked(() => fieldContext.value());
|
|
2087
|
-
const rootFormValue = untracked(() => this.rootFormRegistry.formValue());
|
|
2088
|
-
const localKey = this.extractFieldPath(fieldContext);
|
|
2089
|
-
const pathKeys = safeReadPathKeys(fieldContext);
|
|
2090
|
-
const arrayScope = detectArrayScope(pathKeys);
|
|
2091
|
-
if (arrayScope) {
|
|
2092
|
-
return this.buildArrayScopedContext(rootFormValue, arrayScope, fieldValue, customFunctions, false, fieldContext);
|
|
2093
|
-
}
|
|
2094
|
-
const rootFormSignal = this.rootFormRegistry.rootForm;
|
|
2095
|
-
return {
|
|
2096
|
-
fieldValue,
|
|
2097
|
-
formValue: rootFormValue,
|
|
2098
|
-
fieldPath: localKey,
|
|
2099
|
-
customFunctions: customFunctions || {},
|
|
2100
|
-
externalData: this.resolveExternalData(false),
|
|
2101
|
-
logger: this.logger,
|
|
2102
|
-
deprecationTracker: this.deprecationTracker ?? undefined,
|
|
2103
|
-
get fieldState() {
|
|
2104
|
-
return readFieldStateInfo(extractFieldState(fieldContext), false);
|
|
2105
|
-
},
|
|
2106
|
-
get formFieldState() {
|
|
2107
|
-
return createFormFieldStateMap(untracked(rootFormSignal), false);
|
|
2108
|
-
},
|
|
2109
|
-
};
|
|
2110
|
-
}
|
|
2111
|
-
extractFieldPath(fieldContext) {
|
|
2112
|
-
if (isChildFieldContext(fieldContext)) {
|
|
2113
|
-
try {
|
|
2114
|
-
return String(fieldContext.key());
|
|
2115
|
-
}
|
|
2116
|
-
catch (error) {
|
|
2117
|
-
this.logger.warn('Unable to extract field key:', error);
|
|
2118
|
-
}
|
|
2119
|
-
}
|
|
2120
|
-
return '';
|
|
2121
|
-
}
|
|
2122
|
-
buildArrayScopedContext(rootFormValue, arrayScope, fieldValue, customFunctions, reactive, fieldContext) {
|
|
2123
|
-
const { arrayKey, index, localKey } = arrayScope;
|
|
2124
|
-
const arrayData = getNestedValue(rootFormValue, arrayKey);
|
|
2125
|
-
let scopedFormValue;
|
|
2126
|
-
if (Array.isArray(arrayData) && index >= 0 && index < arrayData.length) {
|
|
2127
|
-
const item = arrayData[index];
|
|
2128
|
-
if (item != null && typeof item === 'object') {
|
|
2129
|
-
scopedFormValue = item;
|
|
2130
|
-
}
|
|
2131
|
-
}
|
|
2132
|
-
const rootFormSignal = this.rootFormRegistry.rootForm;
|
|
2133
|
-
const fieldStateGetter = fieldContext ? () => readFieldStateInfo(extractFieldState(fieldContext), reactive) : () => undefined;
|
|
2134
|
-
const formFieldStateGetter = () => createFormFieldStateMap((reactive ? rootFormSignal() : untracked(rootFormSignal)), reactive);
|
|
2135
|
-
if (!scopedFormValue) {
|
|
2136
|
-
return {
|
|
2137
|
-
fieldValue,
|
|
2138
|
-
formValue: rootFormValue,
|
|
2139
|
-
fieldPath: localKey,
|
|
2140
|
-
customFunctions: customFunctions || {},
|
|
2141
|
-
externalData: this.resolveExternalData(reactive),
|
|
2142
|
-
logger: this.logger,
|
|
2143
|
-
deprecationTracker: this.deprecationTracker ?? undefined,
|
|
2144
|
-
get fieldState() {
|
|
2145
|
-
return fieldStateGetter();
|
|
2146
|
-
},
|
|
2147
|
-
get formFieldState() {
|
|
2148
|
-
return formFieldStateGetter();
|
|
2149
|
-
},
|
|
2150
|
-
};
|
|
2151
|
-
}
|
|
2152
|
-
return {
|
|
2153
|
-
fieldValue,
|
|
2154
|
-
formValue: scopedFormValue,
|
|
2155
|
-
rootFormValue,
|
|
2156
|
-
arrayIndex: index,
|
|
2157
|
-
arrayPath: arrayKey,
|
|
2158
|
-
fieldPath: `${arrayKey}.${index}.${localKey}`,
|
|
2159
|
-
customFunctions: customFunctions || {},
|
|
2160
|
-
externalData: this.resolveExternalData(reactive),
|
|
2161
|
-
logger: this.logger,
|
|
2162
|
-
deprecationTracker: this.deprecationTracker ?? undefined,
|
|
2163
|
-
get fieldState() {
|
|
2164
|
-
return fieldStateGetter();
|
|
2165
|
-
},
|
|
2166
|
-
get formFieldState() {
|
|
2167
|
-
return formFieldStateGetter();
|
|
2168
|
-
},
|
|
2169
|
-
};
|
|
2170
|
-
}
|
|
2171
|
-
resolveExternalData(reactive) {
|
|
2172
|
-
const externalDataSignal = this.externalDataSignal;
|
|
2173
|
-
if (!externalDataSignal)
|
|
2174
|
-
return undefined;
|
|
2175
|
-
const externalDataRecord = reactive ? externalDataSignal() : untracked(() => externalDataSignal());
|
|
2176
|
-
if (!externalDataRecord) {
|
|
2177
|
-
return undefined;
|
|
2178
|
-
}
|
|
2179
|
-
const resolved = {};
|
|
2180
|
-
for (const [key, value] of Object.entries(externalDataRecord)) {
|
|
2181
|
-
if (!isSignal(value)) {
|
|
2182
|
-
throw new DynamicFormError(`externalData["${key}"] must be a Signal. Got: ${typeof value}. Wrap it with signal(yourValue).`);
|
|
2183
|
-
}
|
|
2184
|
-
resolved[key] = reactive ? value() : untracked(() => value());
|
|
2185
|
-
}
|
|
2186
|
-
return resolved;
|
|
2187
|
-
}
|
|
2188
|
-
createReactiveEvaluationContext(fieldContext, customFunctions) {
|
|
2189
|
-
const fieldValue = fieldContext.value();
|
|
2190
|
-
const rootFormValue = this.rootFormRegistry.formValue();
|
|
2191
|
-
const pathKeys = safeReadPathKeys(fieldContext);
|
|
2192
|
-
const arrayScope = detectArrayScope(pathKeys);
|
|
2193
|
-
if (arrayScope) {
|
|
2194
|
-
return this.buildArrayScopedContext(rootFormValue, arrayScope, fieldValue, customFunctions, true, fieldContext);
|
|
2195
|
-
}
|
|
2196
|
-
const localKey = this.extractFieldPath(fieldContext);
|
|
2197
|
-
const rootFormSignal = this.rootFormRegistry.rootForm;
|
|
2198
|
-
return {
|
|
2199
|
-
fieldValue,
|
|
2200
|
-
formValue: rootFormValue,
|
|
2201
|
-
fieldPath: localKey,
|
|
2202
|
-
customFunctions: customFunctions || {},
|
|
2203
|
-
externalData: this.resolveExternalData(true),
|
|
2204
|
-
logger: this.logger,
|
|
2205
|
-
deprecationTracker: this.deprecationTracker ?? undefined,
|
|
2206
|
-
get fieldState() {
|
|
2207
|
-
return readFieldStateInfo(extractFieldState(fieldContext), true);
|
|
2208
|
-
},
|
|
2209
|
-
get formFieldState() {
|
|
2210
|
-
return createFormFieldStateMap(rootFormSignal(), true);
|
|
2211
|
-
},
|
|
2212
|
-
};
|
|
2213
|
-
}
|
|
2214
|
-
createDisplayOnlyContext(fieldPath, customFunctions) {
|
|
2215
|
-
const formValue = this.rootFormRegistry.formValue();
|
|
2216
|
-
const rootFormSignal = this.rootFormRegistry.rootForm;
|
|
2217
|
-
return {
|
|
2218
|
-
fieldValue: undefined,
|
|
2219
|
-
formValue,
|
|
2220
|
-
fieldPath,
|
|
2221
|
-
customFunctions: customFunctions || {},
|
|
2222
|
-
externalData: this.resolveExternalData(true),
|
|
2223
|
-
logger: this.logger,
|
|
2224
|
-
deprecationTracker: this.deprecationTracker ?? undefined,
|
|
2225
|
-
get formFieldState() {
|
|
2226
|
-
return createFormFieldStateMap(rootFormSignal(), true);
|
|
2227
|
-
},
|
|
2228
|
-
};
|
|
2229
|
-
}
|
|
2230
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: FieldContextRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
2231
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: FieldContextRegistryService });
|
|
2232
|
-
}
|
|
2233
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: FieldContextRegistryService, decorators: [{
|
|
2234
|
-
type: Injectable
|
|
2235
|
-
}] });
|
|
2236
|
-
|
|
2237
|
-
class PageOrchestratorComponent {
|
|
2238
|
-
eventBus = inject(EventBus);
|
|
2239
|
-
fieldContextRegistry = inject(FieldContextRegistryService);
|
|
2240
|
-
functionRegistry = inject(FunctionRegistryService);
|
|
2241
|
-
formOptions = inject(FORM_OPTIONS, { optional: true });
|
|
2242
|
-
pageFields = input.required(...(ngDevMode ? [{ debugName: "pageFields" }] : /* istanbul ignore next */ []));
|
|
2243
|
-
form = input.required(...(ngDevMode ? [{ debugName: "form" }] : /* istanbul ignore next */ []));
|
|
2244
|
-
fieldSignalContext = input.required(...(ngDevMode ? [{ debugName: "fieldSignalContext" }] : /* istanbul ignore next */ []));
|
|
2245
|
-
pageHiddenStates = computed(() => {
|
|
2246
|
-
const pages = this.pageFields();
|
|
2247
|
-
return pages.map((page) => this.evaluatePageHidden(page));
|
|
2248
|
-
}, ...(ngDevMode ? [{ debugName: "pageHiddenStates" }] : /* istanbul ignore next */ []));
|
|
2249
|
-
visiblePageIndices = computed(() => {
|
|
2250
|
-
const hiddenStates = this.pageHiddenStates();
|
|
2251
|
-
return hiddenStates
|
|
2252
|
-
.map((hidden, index) => ({ index, hidden }))
|
|
2253
|
-
.filter((item) => !item.hidden)
|
|
2254
|
-
.map((item) => item.index);
|
|
2255
|
-
}, ...(ngDevMode ? [{ debugName: "visiblePageIndices" }] : /* istanbul ignore next */ []));
|
|
2256
|
-
currentPageIndex = linkedSignal(() => {
|
|
2257
|
-
const totalPages = this.pageFields().length;
|
|
2258
|
-
if (totalPages === 0)
|
|
2259
|
-
return 0;
|
|
2260
|
-
return 0;
|
|
2261
|
-
}, ...(ngDevMode ? [{ debugName: "currentPageIndex" }] : /* istanbul ignore next */ []));
|
|
2262
|
-
state = computed(() => {
|
|
2263
|
-
const currentIndex = this.currentPageIndex();
|
|
2264
|
-
const totalPages = this.pageFields().length;
|
|
2265
|
-
const visibleIndices = this.visiblePageIndices();
|
|
2266
|
-
const currentVisiblePosition = visibleIndices.indexOf(currentIndex);
|
|
2267
|
-
const isFirstVisiblePage = currentVisiblePosition === 0 || currentVisiblePosition === -1;
|
|
2268
|
-
const isLastVisiblePage = currentVisiblePosition >= visibleIndices.length - 1;
|
|
2269
|
-
return {
|
|
2270
|
-
currentPageIndex: currentIndex,
|
|
2271
|
-
totalPages,
|
|
2272
|
-
isFirstPage: isFirstVisiblePage,
|
|
2273
|
-
isLastPage: isLastVisiblePage,
|
|
2274
|
-
navigationDisabled: false,
|
|
2275
|
-
};
|
|
2276
|
-
}, ...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
|
|
2277
|
-
currentPageValid = computed(() => {
|
|
2278
|
-
const currentIndex = this.currentPageIndex();
|
|
2279
|
-
const pages = this.pageFields();
|
|
2280
|
-
const form = this.form();
|
|
2281
|
-
if (pages.length === 0 || currentIndex >= pages.length) {
|
|
2282
|
-
return true;
|
|
2283
|
-
}
|
|
2284
|
-
const currentPage = pages[currentIndex];
|
|
2285
|
-
const pageFields = currentPage.fields || [];
|
|
2286
|
-
const leafKeys = collectLeafFieldKeys(pageFields);
|
|
2287
|
-
for (const fieldKey of leafKeys) {
|
|
2288
|
-
const field = form[fieldKey];
|
|
2289
|
-
if (field && typeof field === 'function') {
|
|
2290
|
-
const fieldState = field();
|
|
2291
|
-
if (fieldState && typeof fieldState.valid === 'function' && !fieldState.valid()) {
|
|
2292
|
-
return false;
|
|
2293
|
-
}
|
|
2294
|
-
}
|
|
2295
|
-
}
|
|
2296
|
-
return true;
|
|
2297
|
-
}, ...(ngDevMode ? [{ debugName: "currentPageValid" }] : /* istanbul ignore next */ []));
|
|
2298
|
-
extendedFieldSignalContext = computed(() => ({
|
|
2299
|
-
...this.fieldSignalContext(),
|
|
2300
|
-
currentPageValid: this.currentPageValid,
|
|
2301
|
-
}), ...(ngDevMode ? [{ debugName: "extendedFieldSignalContext" }] : /* istanbul ignore next */ []));
|
|
2302
|
-
constructor() {
|
|
2303
|
-
this.setupEventListeners();
|
|
2304
|
-
explicitEffect([this.state, this.visiblePageIndices], ([state, visibleIndices]) => {
|
|
2305
|
-
const currentVisiblePosition = visibleIndices.indexOf(state.currentPageIndex);
|
|
2306
|
-
if (currentVisiblePosition === -1 && visibleIndices.length > 0) {
|
|
2307
|
-
const nearest = this.findNearestVisiblePage(state.currentPageIndex, visibleIndices);
|
|
2308
|
-
if (nearest !== -1) {
|
|
2309
|
-
this.navigateToPage(nearest);
|
|
2310
|
-
}
|
|
2311
|
-
}
|
|
2312
|
-
});
|
|
2313
|
-
}
|
|
2314
|
-
navigateToNextPage() {
|
|
2315
|
-
const disableWhenPageInvalid = this.formOptions?.()?.nextButton?.disableWhenPageInvalid ?? true;
|
|
2316
|
-
if (disableWhenPageInvalid && !this.currentPageValid()) {
|
|
2317
|
-
return {
|
|
2318
|
-
success: false,
|
|
2319
|
-
newPageIndex: this.state().currentPageIndex,
|
|
2320
|
-
error: 'Current page has invalid fields',
|
|
2321
|
-
};
|
|
2322
|
-
}
|
|
2323
|
-
const currentState = this.state();
|
|
2324
|
-
const visibleIndices = this.visiblePageIndices();
|
|
2325
|
-
if (currentState.isLastPage) {
|
|
2326
|
-
return {
|
|
2327
|
-
success: false,
|
|
2328
|
-
newPageIndex: currentState.currentPageIndex,
|
|
2329
|
-
error: 'Already on the last visible page',
|
|
2330
|
-
};
|
|
2331
|
-
}
|
|
2332
|
-
if (currentState.navigationDisabled) {
|
|
2333
|
-
return {
|
|
2334
|
-
success: false,
|
|
2335
|
-
newPageIndex: currentState.currentPageIndex,
|
|
2336
|
-
error: 'Navigation is currently disabled',
|
|
2337
|
-
};
|
|
2338
|
-
}
|
|
2339
|
-
const currentVisiblePosition = visibleIndices.indexOf(currentState.currentPageIndex);
|
|
2340
|
-
if (currentVisiblePosition === -1 || currentVisiblePosition >= visibleIndices.length - 1) {
|
|
2341
|
-
return {
|
|
2342
|
-
success: false,
|
|
2343
|
-
newPageIndex: currentState.currentPageIndex,
|
|
2344
|
-
error: 'No next visible page available',
|
|
2345
|
-
};
|
|
2346
|
-
}
|
|
2347
|
-
const nextVisiblePageIndex = visibleIndices[currentVisiblePosition + 1];
|
|
2348
|
-
return this.navigateToPage(nextVisiblePageIndex);
|
|
2349
|
-
}
|
|
2350
|
-
navigateToPreviousPage() {
|
|
2351
|
-
const currentState = this.state();
|
|
2352
|
-
const visibleIndices = this.visiblePageIndices();
|
|
2353
|
-
if (currentState.isFirstPage) {
|
|
2354
|
-
return {
|
|
2355
|
-
success: false,
|
|
2356
|
-
newPageIndex: currentState.currentPageIndex,
|
|
2357
|
-
error: 'Already on the first visible page',
|
|
2358
|
-
};
|
|
2359
|
-
}
|
|
2360
|
-
if (currentState.navigationDisabled) {
|
|
2361
|
-
return {
|
|
2362
|
-
success: false,
|
|
2363
|
-
newPageIndex: currentState.currentPageIndex,
|
|
2364
|
-
error: 'Navigation is currently disabled',
|
|
2365
|
-
};
|
|
2366
|
-
}
|
|
2367
|
-
const currentVisiblePosition = visibleIndices.indexOf(currentState.currentPageIndex);
|
|
2368
|
-
if (currentVisiblePosition <= 0) {
|
|
2369
|
-
return {
|
|
2370
|
-
success: false,
|
|
2371
|
-
newPageIndex: currentState.currentPageIndex,
|
|
2372
|
-
error: 'No previous visible page available',
|
|
2373
|
-
};
|
|
2374
|
-
}
|
|
2375
|
-
const prevVisiblePageIndex = visibleIndices[currentVisiblePosition - 1];
|
|
2376
|
-
return this.navigateToPage(prevVisiblePageIndex);
|
|
2377
|
-
}
|
|
2378
|
-
navigateToPage(pageIndex) {
|
|
2379
|
-
const currentState = this.state();
|
|
2380
|
-
const totalPages = currentState.totalPages;
|
|
2381
|
-
if (pageIndex < 0 || pageIndex >= totalPages) {
|
|
2382
|
-
return {
|
|
2383
|
-
success: false,
|
|
2384
|
-
newPageIndex: currentState.currentPageIndex,
|
|
2385
|
-
error: `Invalid page index: ${pageIndex}. Valid range is 0 to ${totalPages - 1}`,
|
|
2386
|
-
};
|
|
2387
|
-
}
|
|
2388
|
-
const visibleIndices = this.visiblePageIndices();
|
|
2389
|
-
if (!visibleIndices.includes(pageIndex)) {
|
|
2390
|
-
return {
|
|
2391
|
-
success: false,
|
|
2392
|
-
newPageIndex: currentState.currentPageIndex,
|
|
2393
|
-
error: `Cannot navigate to hidden page at index ${pageIndex}. Visible pages: [${visibleIndices.join(', ')}]`,
|
|
2394
|
-
};
|
|
2395
|
-
}
|
|
2396
|
-
if (pageIndex === currentState.currentPageIndex) {
|
|
2397
|
-
return {
|
|
2398
|
-
success: true,
|
|
2399
|
-
newPageIndex: pageIndex,
|
|
2400
|
-
};
|
|
2401
|
-
}
|
|
2402
|
-
const previousIndex = currentState.currentPageIndex;
|
|
2403
|
-
this.currentPageIndex.set(pageIndex);
|
|
2404
|
-
this.eventBus.dispatch(PageChangeEvent, pageIndex, totalPages, previousIndex);
|
|
2405
|
-
return {
|
|
2406
|
-
success: true,
|
|
2407
|
-
newPageIndex: pageIndex,
|
|
2408
|
-
};
|
|
2409
|
-
}
|
|
2410
|
-
findNearestVisiblePage(currentIndex, visibleIndices) {
|
|
2411
|
-
let nearest = -1;
|
|
2412
|
-
let minDistance = Infinity;
|
|
2413
|
-
for (const idx of visibleIndices) {
|
|
2414
|
-
const distance = Math.abs(idx - currentIndex);
|
|
2415
|
-
if (distance < minDistance || (distance === minDistance && idx > nearest)) {
|
|
2416
|
-
minDistance = distance;
|
|
2417
|
-
nearest = idx;
|
|
2418
|
-
}
|
|
2419
|
-
}
|
|
2420
|
-
return nearest;
|
|
2421
|
-
}
|
|
2422
|
-
setupEventListeners() {
|
|
2423
|
-
this.eventBus
|
|
2424
|
-
.on('next-page')
|
|
2425
|
-
.pipe(takeUntilDestroyed())
|
|
2426
|
-
.subscribe(() => {
|
|
2427
|
-
this.navigateToNextPage();
|
|
2428
|
-
});
|
|
2429
|
-
this.eventBus
|
|
2430
|
-
.on('previous-page')
|
|
2431
|
-
.pipe(takeUntilDestroyed())
|
|
2432
|
-
.subscribe(() => {
|
|
2433
|
-
this.navigateToPreviousPage();
|
|
2434
|
-
});
|
|
2435
|
-
explicitEffect([this.state], ([state]) => this.eventBus.dispatch(PageNavigationStateChangeEvent, state));
|
|
2436
|
-
}
|
|
2437
|
-
evaluatePageHidden(page) {
|
|
2438
|
-
if (!page.logic || page.logic.length === 0) {
|
|
2439
|
-
return false;
|
|
2440
|
-
}
|
|
2441
|
-
const hiddenLogic = page.logic.filter((l) => l.type === 'hidden');
|
|
2442
|
-
if (hiddenLogic.length === 0) {
|
|
2443
|
-
return false;
|
|
2444
|
-
}
|
|
2445
|
-
for (const logic of hiddenLogic) {
|
|
2446
|
-
if (typeof logic.condition === 'boolean') {
|
|
2447
|
-
if (logic.condition) {
|
|
2448
|
-
return true;
|
|
2449
|
-
}
|
|
2450
|
-
continue;
|
|
2451
|
-
}
|
|
2452
|
-
const condition = logic.condition;
|
|
2453
|
-
const context = this.fieldContextRegistry.createDisplayOnlyContext(page.key || '', this.functionRegistry.getCustomFunctions());
|
|
2454
|
-
if (evaluateCondition(condition, context)) {
|
|
2455
|
-
return true;
|
|
2456
|
-
}
|
|
2457
|
-
}
|
|
2458
|
-
return false;
|
|
2459
|
-
}
|
|
2460
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PageOrchestratorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2461
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", 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: [
|
|
2462
|
-
{
|
|
2463
|
-
provide: FIELD_SIGNAL_CONTEXT,
|
|
2464
|
-
useFactory: (orchestrator) => orchestrator.extendedFieldSignalContext(),
|
|
2465
|
-
deps: [PageOrchestratorComponent],
|
|
2466
|
-
},
|
|
2467
|
-
], ngImport: i0, template: `
|
|
2468
|
-
@for (pageField of pageFields(); track pageField.key; let i = $index) {
|
|
2469
|
-
<!--
|
|
2470
|
-
Skip pages hidden by 'hidden' logic conditions entirely. FormStateManager
|
|
2471
|
-
derives schema/value/validators from config (not from mounted components),
|
|
2472
|
-
so unmounting a hidden page has no effect on form state — only on render +
|
|
2473
|
-
CD cost. This is the page-level equivalent of the existing field-level
|
|
2474
|
-
\`@if (!field.hidden())\` gate in page-field.component.ts.
|
|
2475
|
-
-->
|
|
2476
|
-
@if (!pageHiddenStates()[i]) {
|
|
2477
|
-
@if (i === state().currentPageIndex || i === state().currentPageIndex + 1 || i === state().currentPageIndex - 1) {
|
|
2478
|
-
<!-- Current and adjacent pages (±1): render immediately for flicker-free navigation -->
|
|
2479
|
-
@defer (on immediate) {
|
|
2480
|
-
<section
|
|
2481
|
-
page-field
|
|
2482
|
-
[field]="pageField"
|
|
2483
|
-
[key]="pageField.key"
|
|
2484
|
-
[pageIndex]="i"
|
|
2485
|
-
[isVisible]="i === state().currentPageIndex"
|
|
2486
|
-
></section>
|
|
2487
|
-
} @placeholder {
|
|
2488
|
-
<div class="df-page-placeholder" [attr.data-page-index]="i" [attr.data-page-key]="pageField.key"></div>
|
|
2489
|
-
}
|
|
2490
|
-
} @else {
|
|
2491
|
-
<!-- Distant pages: defer until browser is idle for memory savings -->
|
|
2492
|
-
@defer (on idle) {
|
|
2493
|
-
<section page-field [field]="pageField" [key]="pageField.key" [pageIndex]="i" [isVisible]="false"></section>
|
|
2494
|
-
} @placeholder {
|
|
2495
|
-
<div class="df-page-placeholder" [attr.data-page-index]="i" [attr.data-page-key]="pageField.key"></div>
|
|
2496
|
-
}
|
|
2497
|
-
}
|
|
2498
|
-
}
|
|
2499
|
-
}
|
|
2500
|
-
`, isInline: true, styles: [":host{display:block;position:relative;width:100%}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, deferBlockDependencies: [() => [import('./ng-forge-dynamic-forms-page-field.component-zF-vEv5V.mjs').then(m => m.default)], () => [import('./ng-forge-dynamic-forms-page-field.component-zF-vEv5V.mjs').then(m => m.default)]] });
|
|
2501
|
-
}
|
|
2502
|
-
i0.ɵɵngDeclareClassMetadataAsync({ minVersion: "18.0.0", version: "21.2.14", ngImport: i0, type: PageOrchestratorComponent, resolveDeferredDeps: () => [import('./ng-forge-dynamic-forms-page-field.component-zF-vEv5V.mjs').then(m => m.default)], resolveMetadata: PageFieldComponent => ({ decorators: [{
|
|
2503
|
-
type: Component,
|
|
2504
|
-
args: [{ selector: 'div[page-orchestrator]', imports: [PageFieldComponent], template: `
|
|
2505
|
-
@for (pageField of pageFields(); track pageField.key; let i = $index) {
|
|
2506
|
-
<!--
|
|
2507
|
-
Skip pages hidden by 'hidden' logic conditions entirely. FormStateManager
|
|
2508
|
-
derives schema/value/validators from config (not from mounted components),
|
|
2509
|
-
so unmounting a hidden page has no effect on form state — only on render +
|
|
2510
|
-
CD cost. This is the page-level equivalent of the existing field-level
|
|
2511
|
-
\`@if (!field.hidden())\` gate in page-field.component.ts.
|
|
2512
|
-
-->
|
|
2513
|
-
@if (!pageHiddenStates()[i]) {
|
|
2514
|
-
@if (i === state().currentPageIndex || i === state().currentPageIndex + 1 || i === state().currentPageIndex - 1) {
|
|
2515
|
-
<!-- Current and adjacent pages (±1): render immediately for flicker-free navigation -->
|
|
2516
|
-
@defer (on immediate) {
|
|
2517
|
-
<section
|
|
2518
|
-
page-field
|
|
2519
|
-
[field]="pageField"
|
|
2520
|
-
[key]="pageField.key"
|
|
2521
|
-
[pageIndex]="i"
|
|
2522
|
-
[isVisible]="i === state().currentPageIndex"
|
|
2523
|
-
></section>
|
|
2524
|
-
} @placeholder {
|
|
2525
|
-
<div class="df-page-placeholder" [attr.data-page-index]="i" [attr.data-page-key]="pageField.key"></div>
|
|
2526
|
-
}
|
|
2527
|
-
} @else {
|
|
2528
|
-
<!-- Distant pages: defer until browser is idle for memory savings -->
|
|
2529
|
-
@defer (on idle) {
|
|
2530
|
-
<section page-field [field]="pageField" [key]="pageField.key" [pageIndex]="i" [isVisible]="false"></section>
|
|
2531
|
-
} @placeholder {
|
|
2532
|
-
<div class="df-page-placeholder" [attr.data-page-index]="i" [attr.data-page-key]="pageField.key"></div>
|
|
2533
|
-
}
|
|
2534
|
-
}
|
|
2535
|
-
}
|
|
2536
|
-
}
|
|
2537
|
-
`, host: {
|
|
2538
|
-
class: 'df-page-orchestrator',
|
|
2539
|
-
'[attr.data-current-page]': 'state().currentPageIndex',
|
|
2540
|
-
'[attr.data-total-pages]': 'state().totalPages',
|
|
2541
|
-
}, providers: [
|
|
2542
|
-
{
|
|
2543
|
-
provide: FIELD_SIGNAL_CONTEXT,
|
|
2544
|
-
useFactory: (orchestrator) => orchestrator.extendedFieldSignalContext(),
|
|
2545
|
-
deps: [PageOrchestratorComponent],
|
|
2546
|
-
},
|
|
2547
|
-
], changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;position:relative;width:100%}\n"] }]
|
|
2548
|
-
}], 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 }] }] } }) });
|
|
2549
|
-
function collectLeafFieldKeys(fields) {
|
|
2550
|
-
const keys = [];
|
|
2551
|
-
for (const field of fields) {
|
|
2552
|
-
if (isRowField(field)) {
|
|
2553
|
-
keys.push(...collectLeafFieldKeys(field.fields));
|
|
2554
|
-
}
|
|
2555
|
-
else if (field.key) {
|
|
2556
|
-
keys.push(field.key);
|
|
2557
|
-
}
|
|
2558
|
-
}
|
|
2559
|
-
return keys;
|
|
2560
|
-
}
|
|
2561
|
-
|
|
2562
|
-
const FORM_INITIALIZER = new InjectionToken('FORM_INITIALIZER');
|
|
2563
|
-
|
|
2564
|
-
const NORMALIZED_ARRAY_METADATA = Symbol('normalizedArrayMetadata');
|
|
2565
|
-
function setNormalizedArrayMetadata(arrayField, metadata) {
|
|
2566
|
-
arrayField[NORMALIZED_ARRAY_METADATA] = metadata;
|
|
2567
|
-
}
|
|
2568
|
-
function getNormalizedArrayMetadata(arrayField) {
|
|
2569
|
-
return arrayField[NORMALIZED_ARRAY_METADATA];
|
|
2570
|
-
}
|
|
2571
|
-
|
|
2572
|
-
function normalizeSimplifiedArrays(fields) {
|
|
2573
|
-
return fields.flatMap((field) => {
|
|
2574
|
-
if (isContainerField(field) && !isArrayField(field) && hasChildFields(field)) {
|
|
2575
|
-
return [{ ...field, fields: normalizeSimplifiedArrays(field.fields) }];
|
|
2576
|
-
}
|
|
2577
|
-
if (isArrayField(field) && !isSimplifiedArrayField(field)) {
|
|
2578
|
-
const normalizedItems = field.fields.map((item) => Array.isArray(item) ? normalizeSimplifiedArrays(item) : item);
|
|
2579
|
-
return [{ ...field, fields: normalizedItems }];
|
|
2580
|
-
}
|
|
2581
|
-
if (isSimplifiedArrayField(field)) {
|
|
2582
|
-
const { arrayField, addButton } = expandSimplifiedArray(field);
|
|
2583
|
-
return addButton ? [arrayField, addButton] : [arrayField];
|
|
2584
|
-
}
|
|
2585
|
-
return [field];
|
|
2586
|
-
});
|
|
2587
|
-
}
|
|
2588
|
-
const INVALID_ARRAY_CHILD_TYPES = new Set(['page', 'array']);
|
|
2589
|
-
function validateSimplifiedTemplate(field) {
|
|
2590
|
-
const { template, key } = field;
|
|
2591
|
-
const isObjectTemplate = Array.isArray(template);
|
|
2592
|
-
if (isObjectTemplate) {
|
|
2593
|
-
const templateFields = template;
|
|
2594
|
-
for (const tmpl of templateFields) {
|
|
2595
|
-
if (INVALID_ARRAY_CHILD_TYPES.has(tmpl.type)) {
|
|
2596
|
-
throw new DynamicFormError(`Simplified array "${key}" template contains a '${tmpl.type}' field (key: '${tmpl.key}'). ` +
|
|
2597
|
-
`Only leaf fields, rows, and groups are allowed as array children.`);
|
|
2598
|
-
}
|
|
2599
|
-
}
|
|
2600
|
-
}
|
|
2601
|
-
else {
|
|
2602
|
-
const singleTemplate = template;
|
|
2603
|
-
if (INVALID_ARRAY_CHILD_TYPES.has(singleTemplate.type)) {
|
|
2604
|
-
throw new DynamicFormError(`Simplified array "${key}" template has type '${singleTemplate.type}'. ` +
|
|
2605
|
-
`Only leaf fields, rows, and groups are allowed as array children.`);
|
|
2606
|
-
}
|
|
2607
|
-
}
|
|
2608
|
-
}
|
|
2609
|
-
function expandSimplifiedArray(field) {
|
|
2610
|
-
const { template, value = [], addButton, removeButton, key, logic, minLength, maxLength, wrappers, skipAutoWrappers, skipDefaultWrappers, } = field;
|
|
2611
|
-
const isObjectTemplate = Array.isArray(template);
|
|
2612
|
-
const values = value;
|
|
2613
|
-
validateSimplifiedTemplate(field);
|
|
2614
|
-
const items = values.map((v) => {
|
|
2615
|
-
if (isObjectTemplate) {
|
|
2616
|
-
return buildObjectItem(template, v, removeButton);
|
|
2617
|
-
}
|
|
2618
|
-
return buildPrimitiveItem(template, v);
|
|
2619
|
-
});
|
|
2620
|
-
const addTemplate = isObjectTemplate
|
|
2621
|
-
? buildObjectItemTemplate(template, removeButton)
|
|
2622
|
-
: template;
|
|
2623
|
-
const hasAutoRemove = !isObjectTemplate && removeButton !== false;
|
|
2624
|
-
const arrayFieldObj = {
|
|
2625
|
-
key,
|
|
2626
|
-
type: 'array',
|
|
2627
|
-
fields: items,
|
|
2628
|
-
};
|
|
2629
|
-
if (logic) {
|
|
2630
|
-
arrayFieldObj['logic'] = logic;
|
|
2631
|
-
}
|
|
2632
|
-
if (minLength !== undefined) {
|
|
2633
|
-
arrayFieldObj['minLength'] = minLength;
|
|
2634
|
-
}
|
|
2635
|
-
if (maxLength !== undefined) {
|
|
2636
|
-
arrayFieldObj['maxLength'] = maxLength;
|
|
2637
|
-
}
|
|
2638
|
-
if (wrappers !== undefined) {
|
|
2639
|
-
arrayFieldObj['wrappers'] = wrappers;
|
|
2640
|
-
}
|
|
2641
|
-
if (skipAutoWrappers !== undefined) {
|
|
2642
|
-
arrayFieldObj['skipAutoWrappers'] = skipAutoWrappers;
|
|
2643
|
-
}
|
|
2644
|
-
if (skipDefaultWrappers !== undefined) {
|
|
2645
|
-
arrayFieldObj['skipDefaultWrappers'] = skipDefaultWrappers;
|
|
2646
|
-
}
|
|
2647
|
-
const arrayField = arrayFieldObj;
|
|
2648
|
-
const primitiveFieldKey = !isObjectTemplate ? template.key : undefined;
|
|
2649
|
-
const metadataTemplate = isObjectTemplate ? buildObjectItemTemplate(template, removeButton) : template;
|
|
2650
|
-
setNormalizedArrayMetadata(arrayFieldObj, {
|
|
2651
|
-
...(hasAutoRemove && { autoRemoveButton: buildRemoveButton(removeButton) }),
|
|
2652
|
-
...(primitiveFieldKey && { primitiveFieldKey }),
|
|
2653
|
-
template: metadataTemplate,
|
|
2654
|
-
});
|
|
2655
|
-
let addButtonField;
|
|
2656
|
-
if (addButton !== false) {
|
|
2657
|
-
const buttonConfig = (typeof addButton === 'object' ? addButton : {});
|
|
2658
|
-
addButtonField = {
|
|
2659
|
-
key: `${key}__add`,
|
|
2660
|
-
type: 'addArrayItem',
|
|
2661
|
-
label: buttonConfig.label ?? 'Add',
|
|
2662
|
-
arrayKey: key,
|
|
2663
|
-
template: addTemplate,
|
|
2664
|
-
...(buttonConfig.props && { props: buttonConfig.props }),
|
|
2665
|
-
...(logic && { logic }),
|
|
2666
|
-
};
|
|
2667
|
-
}
|
|
2668
|
-
return { arrayField, addButton: addButtonField };
|
|
2669
|
-
}
|
|
2670
|
-
function buildPrimitiveItem(template, value) {
|
|
2671
|
-
return { ...template, value };
|
|
2672
|
-
}
|
|
2673
|
-
function buildObjectItem(template, valueObj, removeButton) {
|
|
2674
|
-
const fieldsWithValues = template.map((templateField) => ({
|
|
2675
|
-
...templateField,
|
|
2676
|
-
...(Object.hasOwn(valueObj, templateField.key) && { value: valueObj[templateField.key] }),
|
|
2677
|
-
}));
|
|
2678
|
-
if (removeButton === false) {
|
|
2679
|
-
return fieldsWithValues;
|
|
2680
|
-
}
|
|
2681
|
-
return [...fieldsWithValues, buildRemoveButton(removeButton)];
|
|
2682
|
-
}
|
|
2683
|
-
function buildObjectItemTemplate(template, removeButton) {
|
|
2684
|
-
if (removeButton === false) {
|
|
2685
|
-
return template;
|
|
2686
|
-
}
|
|
2687
|
-
return [...template, buildRemoveButton(removeButton)];
|
|
2688
|
-
}
|
|
2689
|
-
function buildRemoveButton(config) {
|
|
2690
|
-
const buttonConfig = (typeof config === 'object' ? config : {});
|
|
2691
|
-
return {
|
|
2692
|
-
key: '__remove',
|
|
2693
|
-
type: 'removeArrayItem',
|
|
2694
|
-
label: buttonConfig.label ?? 'Remove',
|
|
2695
|
-
...(buttonConfig.props && { props: buttonConfig.props }),
|
|
2696
|
-
};
|
|
2697
|
-
}
|
|
2698
|
-
|
|
2699
|
-
function isContainerWithFields(field) {
|
|
2700
|
-
return ((field.type === 'row' || field.type === 'group' || field.type === 'page' || field.type === 'container') &&
|
|
2701
|
-
'fields' in field &&
|
|
2702
|
-
Array.isArray(field.fields));
|
|
2703
|
-
}
|
|
2704
|
-
function isPagedForm(fields) {
|
|
2705
|
-
if (!fields || fields.length === 0) {
|
|
2706
|
-
return false;
|
|
2707
|
-
}
|
|
2708
|
-
return fields.every((field) => isPageField(field));
|
|
2709
|
-
}
|
|
2710
|
-
function isNonPagedForm(fields) {
|
|
2711
|
-
if (!fields || fields.length === 0) {
|
|
2712
|
-
return true; }
|
|
2713
|
-
return !hasAnyPageFields(fields);
|
|
2714
|
-
}
|
|
2715
|
-
function detectFormMode(fields) {
|
|
2716
|
-
if (!fields || fields.length === 0) {
|
|
2717
|
-
return {
|
|
2718
|
-
mode: 'non-paged',
|
|
2719
|
-
isValid: true,
|
|
2720
|
-
errors: [],
|
|
2721
|
-
};
|
|
2722
|
-
}
|
|
2723
|
-
const hasPageFields = fields.some((field) => isPageField(field));
|
|
2724
|
-
const allPagesAtRoot = fields.every((field) => isPageField(field));
|
|
2725
|
-
const hasNestedPages = hasNestedPageFields(fields);
|
|
2726
|
-
if (hasPageFields) {
|
|
2727
|
-
const errors = [];
|
|
2728
|
-
if (!allPagesAtRoot) {
|
|
2729
|
-
errors.push('Mixed page and non-page fields at root level. In paged forms, ALL root-level fields must be of type "page".');
|
|
2730
|
-
}
|
|
2731
|
-
if (hasNestedPages) {
|
|
2732
|
-
errors.push('Page fields cannot contain nested page fields at any level.');
|
|
2733
|
-
}
|
|
2734
|
-
return {
|
|
2735
|
-
mode: 'paged',
|
|
2736
|
-
isValid: errors.length === 0,
|
|
2737
|
-
errors,
|
|
2738
|
-
};
|
|
2739
|
-
}
|
|
2740
|
-
else {
|
|
2741
|
-
const errors = [];
|
|
2742
|
-
if (hasAnyPageFields(fields)) {
|
|
2743
|
-
errors.push('Page fields are not allowed in non-paged forms.');
|
|
2744
|
-
}
|
|
2745
|
-
return {
|
|
2746
|
-
mode: 'non-paged',
|
|
2747
|
-
isValid: errors.length === 0,
|
|
2748
|
-
errors,
|
|
2749
|
-
};
|
|
2750
|
-
}
|
|
2751
|
-
}
|
|
2752
|
-
function hasAnyPageFields(fields) {
|
|
2753
|
-
for (const field of fields) {
|
|
2754
|
-
if (isPageField(field)) {
|
|
2755
|
-
return true;
|
|
2756
|
-
}
|
|
2757
|
-
if (!isPageField(field) && isContainerWithFields(field)) {
|
|
2758
|
-
if (hasAnyPageFields(field.fields)) {
|
|
2759
|
-
return true;
|
|
2760
|
-
}
|
|
2761
|
-
}
|
|
2762
|
-
}
|
|
2763
|
-
return false;
|
|
2764
|
-
}
|
|
2765
|
-
function hasNestedPageFields(fields) {
|
|
2766
|
-
for (const field of fields) {
|
|
2767
|
-
if (isPageField(field) && isContainerWithFields(field)) {
|
|
2768
|
-
if (hasAnyPageFields(field.fields)) {
|
|
2769
|
-
return true;
|
|
2770
|
-
}
|
|
2771
|
-
}
|
|
2772
|
-
if (!isPageField(field) && isContainerWithFields(field)) {
|
|
2773
|
-
if (hasNestedPageFields(field.fields)) {
|
|
2774
|
-
return true;
|
|
2775
|
-
}
|
|
2776
|
-
}
|
|
2777
|
-
}
|
|
2778
|
-
return false;
|
|
2779
|
-
}
|
|
2780
|
-
function isValidPagedForm(fields) {
|
|
2781
|
-
const result = detectFormMode(fields);
|
|
2782
|
-
return result.mode === 'paged' && result.isValid;
|
|
2783
|
-
}
|
|
2784
|
-
function isValidNonPagedForm(fields) {
|
|
2785
|
-
const result = detectFormMode(fields);
|
|
2786
|
-
return result.mode === 'non-paged' && result.isValid;
|
|
2787
|
-
}
|
|
2788
|
-
|
|
2789
|
-
function createSlot() {
|
|
2790
|
-
let counter = 0;
|
|
2791
|
-
const templates = new Map();
|
|
2792
|
-
const itemOrder = [];
|
|
2793
|
-
const primitiveFieldKey = signal(undefined, ...(ngDevMode ? [{ debugName: "primitiveFieldKey" }] : /* istanbul ignore next */ []));
|
|
2794
|
-
return {
|
|
2795
|
-
templates,
|
|
2796
|
-
itemOrder,
|
|
2797
|
-
primitiveFieldKey,
|
|
2798
|
-
nextId: () => `item-${counter++}`,
|
|
2799
|
-
setTemplate(itemId, t) {
|
|
2800
|
-
templates.set(itemId, t);
|
|
2801
|
-
},
|
|
2802
|
-
getTemplate(itemId) {
|
|
2803
|
-
return templates.get(itemId);
|
|
2804
|
-
},
|
|
2805
|
-
hasTemplate(itemId) {
|
|
2806
|
-
return templates.has(itemId);
|
|
2807
|
-
},
|
|
2808
|
-
setIdAt(index, itemId) {
|
|
2809
|
-
itemOrder[index] = itemId;
|
|
2810
|
-
},
|
|
2811
|
-
removeAt(index) {
|
|
2812
|
-
const [removed] = itemOrder.splice(index, 1);
|
|
2813
|
-
if (removed !== undefined)
|
|
2814
|
-
templates.delete(removed);
|
|
2815
|
-
},
|
|
2816
|
-
moveItem(fromIndex, toIndex) {
|
|
2817
|
-
const [id] = itemOrder.splice(fromIndex, 1);
|
|
2818
|
-
if (id !== undefined)
|
|
2819
|
-
itemOrder.splice(toIndex, 0, id);
|
|
2820
|
-
},
|
|
2821
|
-
truncate(length) {
|
|
2822
|
-
const removed = itemOrder.splice(length);
|
|
2823
|
-
for (const id of removed)
|
|
2824
|
-
templates.delete(id);
|
|
2825
|
-
},
|
|
2826
|
-
clear() {
|
|
2827
|
-
templates.clear();
|
|
2828
|
-
itemOrder.length = 0;
|
|
2829
|
-
counter = 0;
|
|
2830
|
-
primitiveFieldKey.set(undefined);
|
|
2831
|
-
},
|
|
2832
|
-
};
|
|
2833
|
-
}
|
|
2834
|
-
class ArrayItemRegistryService {
|
|
2835
|
-
slots = new Map();
|
|
2836
|
-
slotFor(arrayPath) {
|
|
2837
|
-
let slot = this.slots.get(arrayPath);
|
|
2838
|
-
if (!slot) {
|
|
2839
|
-
slot = createSlot();
|
|
2840
|
-
this.slots.set(arrayPath, slot);
|
|
2841
|
-
}
|
|
2842
|
-
return slot;
|
|
2843
|
-
}
|
|
2844
|
-
clearAll() {
|
|
2845
|
-
for (const slot of this.slots.values())
|
|
2846
|
-
slot.clear();
|
|
2847
|
-
this.slots.clear();
|
|
2848
|
-
}
|
|
2849
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ArrayItemRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
2850
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ArrayItemRegistryService });
|
|
2851
|
-
}
|
|
2852
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ArrayItemRegistryService, decorators: [{
|
|
2853
|
-
type: Injectable
|
|
2854
|
-
}] });
|
|
2855
|
-
|
|
2856
|
-
class SchemaRegistryService {
|
|
2857
|
-
registeredSchemas = new Map();
|
|
2858
|
-
registerSchema(schema) {
|
|
2859
|
-
this.registeredSchemas.set(schema.name, schema);
|
|
2860
|
-
}
|
|
2861
|
-
getSchema(name) {
|
|
2862
|
-
return this.registeredSchemas.get(name);
|
|
2863
|
-
}
|
|
2864
|
-
getAllSchemas() {
|
|
2865
|
-
return new Map(this.registeredSchemas);
|
|
2866
|
-
}
|
|
2867
|
-
clearSchemas() {
|
|
2868
|
-
this.registeredSchemas.clear();
|
|
2869
|
-
}
|
|
2870
|
-
resolveSchema(schema) {
|
|
2871
|
-
if (typeof schema === 'string') {
|
|
2872
|
-
return this.getSchema(schema) || null;
|
|
2873
|
-
}
|
|
2874
|
-
return schema;
|
|
2875
|
-
}
|
|
2876
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: SchemaRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
2877
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: SchemaRegistryService });
|
|
2878
|
-
}
|
|
2879
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: SchemaRegistryService, decorators: [{
|
|
2880
|
-
type: Injectable
|
|
2881
|
-
}] });
|
|
2882
|
-
|
|
2883
|
-
function isFunctionHttpValidator(config) {
|
|
2884
|
-
const c = config;
|
|
2885
|
-
return typeof c.functionName === 'string' || (c.fn !== null && typeof c.fn === 'object');
|
|
2886
|
-
}
|
|
2887
|
-
|
|
2888
|
-
function resolveHttpRequest(config, context) {
|
|
2889
|
-
let url = config.url;
|
|
2890
|
-
if (config.params) {
|
|
2891
|
-
for (const [key, expression] of Object.entries(config.params)) {
|
|
2892
|
-
const value = ExpressionParser.evaluate(expression, context);
|
|
2893
|
-
if (value == null) {
|
|
2894
|
-
context.logger.debug(`HTTP request suppressed: path param '${key}' resolved to ${String(value)} ` +
|
|
2895
|
-
`(expression: '${expression}'). The request will not be sent.`);
|
|
2896
|
-
return null;
|
|
2897
|
-
}
|
|
2898
|
-
url = url.replace(`:${key}`, encodeURIComponent(String(value)));
|
|
2899
|
-
}
|
|
2900
|
-
}
|
|
2901
|
-
if (config.queryParams) {
|
|
2902
|
-
const params = new URLSearchParams();
|
|
2903
|
-
for (const [key, expression] of Object.entries(config.queryParams)) {
|
|
2904
|
-
const value = ExpressionParser.evaluate(expression, context);
|
|
2905
|
-
params.set(key, value != null ? String(value) : '');
|
|
2906
|
-
}
|
|
2907
|
-
const queryString = params.toString();
|
|
2908
|
-
if (queryString) {
|
|
2909
|
-
url += (url.includes('?') ? '&' : '?') + queryString;
|
|
2910
|
-
}
|
|
2911
|
-
}
|
|
2912
|
-
const request = {
|
|
2913
|
-
url,
|
|
2914
|
-
method: config.method,
|
|
2915
|
-
};
|
|
2916
|
-
if (config.body) {
|
|
2917
|
-
if (config.evaluateBodyExpressions) {
|
|
2918
|
-
const resolvedBody = {};
|
|
2919
|
-
for (const [key, value] of Object.entries(config.body)) {
|
|
2920
|
-
if (typeof value === 'string') {
|
|
2921
|
-
resolvedBody[key] = ExpressionParser.evaluate(value, context);
|
|
2922
|
-
}
|
|
2923
|
-
else {
|
|
2924
|
-
resolvedBody[key] = value;
|
|
2925
|
-
}
|
|
2926
|
-
}
|
|
2927
|
-
request.body = resolvedBody;
|
|
2928
|
-
}
|
|
2929
|
-
else {
|
|
2930
|
-
request.body = config.body;
|
|
2931
|
-
}
|
|
2932
|
-
}
|
|
2933
|
-
if (config.headers) {
|
|
2934
|
-
request.headers = config.headers;
|
|
2935
|
-
}
|
|
2936
|
-
return request;
|
|
2937
|
-
}
|
|
2938
|
-
|
|
2939
|
-
function evaluateHttpValidationResponse(response, mapping, logger) {
|
|
2940
|
-
const scope = { response };
|
|
2941
|
-
try {
|
|
2942
|
-
const result = ExpressionParser.evaluate(mapping.validWhen, scope);
|
|
2943
|
-
if (typeof result !== 'boolean') {
|
|
2944
|
-
logger.warn(`validWhen expression "${mapping.validWhen}" returned non-boolean value:`, result, 'Expected true or false.');
|
|
2945
|
-
}
|
|
2946
|
-
if (result === true) {
|
|
2947
|
-
return null;
|
|
2948
|
-
}
|
|
2949
|
-
const error = { kind: mapping.errorKind };
|
|
2950
|
-
if (mapping.errorParams) {
|
|
2951
|
-
for (const [key, expression] of Object.entries(mapping.errorParams)) {
|
|
2952
|
-
try {
|
|
2953
|
-
error[key] = ExpressionParser.evaluate(expression, scope);
|
|
2954
|
-
}
|
|
2955
|
-
catch (err) {
|
|
2956
|
-
logger.warn(`Error evaluating errorParam "${key}":`, expression, err);
|
|
2957
|
-
}
|
|
2958
|
-
}
|
|
2959
|
-
}
|
|
2960
|
-
return error;
|
|
2961
|
-
}
|
|
2962
|
-
catch (err) {
|
|
2963
|
-
logger.warn('Error evaluating validWhen expression:', mapping.validWhen, err);
|
|
2964
|
-
return { kind: mapping.errorKind };
|
|
2965
|
-
}
|
|
2966
|
-
}
|
|
2967
|
-
|
|
2968
|
-
function stableStringify(value) {
|
|
2969
|
-
if (value === null || value === undefined) {
|
|
2970
|
-
return String(value);
|
|
2971
|
-
}
|
|
2972
|
-
if (typeof value !== 'object') {
|
|
2973
|
-
return JSON.stringify(value);
|
|
2974
|
-
}
|
|
2975
|
-
if (Array.isArray(value)) {
|
|
2976
|
-
return '[' + value.map(stableStringify).join(',') + ']';
|
|
2977
|
-
}
|
|
2978
|
-
const obj = value;
|
|
2979
|
-
const sortedKeys = Object.keys(obj).sort();
|
|
2980
|
-
const pairs = sortedKeys.map((key) => JSON.stringify(key) + ':' + stableStringify(obj[key]));
|
|
2981
|
-
return '{' + pairs.join(',') + '}';
|
|
893
|
+
return [field];
|
|
894
|
+
});
|
|
2982
895
|
}
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
return undefined;
|
|
2994
|
-
if (Date.now() > entry.expiresAt) {
|
|
2995
|
-
this.cache.delete(key);
|
|
2996
|
-
return undefined;
|
|
2997
|
-
}
|
|
2998
|
-
return entry.value;
|
|
2999
|
-
}
|
|
3000
|
-
set(key, value, ttlMs) {
|
|
3001
|
-
while (this.cache.size >= this.maxEntries && !this.cache.has(key)) {
|
|
3002
|
-
const oldestKey = this.cache.keys().next().value;
|
|
3003
|
-
if (oldestKey !== undefined) {
|
|
3004
|
-
this.cache.delete(oldestKey);
|
|
896
|
+
const INVALID_ARRAY_CHILD_TYPES = new Set(['page', 'array']);
|
|
897
|
+
function validateSimplifiedTemplate(field) {
|
|
898
|
+
const { template, key } = field;
|
|
899
|
+
const isObjectTemplate = Array.isArray(template);
|
|
900
|
+
if (isObjectTemplate) {
|
|
901
|
+
const templateFields = template;
|
|
902
|
+
for (const tmpl of templateFields) {
|
|
903
|
+
if (INVALID_ARRAY_CHILD_TYPES.has(tmpl.type)) {
|
|
904
|
+
throw new DynamicFormError(`Simplified array "${key}" template contains a '${tmpl.type}' field (key: '${tmpl.key}'). ` +
|
|
905
|
+
`Only leaf fields, rows, and groups are allowed as array children.`);
|
|
3005
906
|
}
|
|
3006
907
|
}
|
|
3007
|
-
this.cache.set(key, { value, expiresAt: Date.now() + ttlMs });
|
|
3008
|
-
}
|
|
3009
|
-
clear() {
|
|
3010
|
-
this.cache.clear();
|
|
3011
908
|
}
|
|
3012
|
-
|
|
3013
|
-
const
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
}
|
|
3018
|
-
|
|
3019
|
-
function isSignalArray(value) {
|
|
3020
|
-
return Array.isArray(value) && value.every((item) => isSignal(item));
|
|
3021
|
-
}
|
|
3022
|
-
function isSignalRecord(value) {
|
|
3023
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value) && Object.values(value).every((item) => isSignal(item));
|
|
3024
|
-
}
|
|
3025
|
-
function derivedFromDeferred(source, operator, options) {
|
|
3026
|
-
const deferred$ = defer(() => {
|
|
3027
|
-
if (isSignal(source)) {
|
|
3028
|
-
return toObservable(source, { injector: options.injector });
|
|
3029
|
-
}
|
|
3030
|
-
if (isSignalArray(source)) {
|
|
3031
|
-
const observables = source.map((s) => toObservable(s, { injector: options.injector }));
|
|
3032
|
-
return combineLatest(observables);
|
|
3033
|
-
}
|
|
3034
|
-
if (isSignalRecord(source)) {
|
|
3035
|
-
const keys = Object.keys(source);
|
|
3036
|
-
const observables = keys.map((key) => toObservable(source[key], { injector: options.injector }));
|
|
3037
|
-
return combineLatest(observables, (...values) => {
|
|
3038
|
-
const result = {};
|
|
3039
|
-
keys.forEach((key, index) => {
|
|
3040
|
-
result[key] = values[index];
|
|
3041
|
-
});
|
|
3042
|
-
return result;
|
|
3043
|
-
});
|
|
3044
|
-
}
|
|
3045
|
-
throw new Error('Invalid source type. Expected a Signal, array of Signals, or object of Signals.');
|
|
3046
|
-
}).pipe(operator);
|
|
3047
|
-
return toSignal(deferred$, { initialValue: options.initialValue, injector: options.injector });
|
|
3048
|
-
}
|
|
3049
|
-
|
|
3050
|
-
function withPreviousValue(input) {
|
|
3051
|
-
const derived = linkedSignal({ ...(ngDevMode ? { debugName: "derived" } : /* istanbul ignore next */ {}), source: input.snapshot,
|
|
3052
|
-
computation: (snapshot, previous) => {
|
|
3053
|
-
if ((snapshot.status === 'loading' || snapshot.status === 'reloading') &&
|
|
3054
|
-
previous !== undefined &&
|
|
3055
|
-
previous.value.status !== 'error') {
|
|
3056
|
-
return { status: snapshot.status, value: previous.value.value };
|
|
3057
|
-
}
|
|
3058
|
-
return snapshot;
|
|
3059
|
-
} });
|
|
3060
|
-
return resourceFromSnapshots(derived);
|
|
3061
|
-
}
|
|
3062
|
-
|
|
3063
|
-
function createDebouncedResourceLogicFn(config) {
|
|
3064
|
-
const perFunctionSignalStore = new Map();
|
|
3065
|
-
return (ctx) => {
|
|
3066
|
-
const contextKey = safeReadPathKeys(ctx).join('.');
|
|
3067
|
-
let handle = perFunctionSignalStore.get(contextKey);
|
|
3068
|
-
if (!handle) {
|
|
3069
|
-
const trigger = signal(undefined, ...(ngDevMode ? [{ debugName: "trigger" }] : /* istanbul ignore next */ []));
|
|
3070
|
-
const resultResource = untracked(() => {
|
|
3071
|
-
const debouncedTrigger = derivedFromDeferred(trigger, pipe(debounceTime(config.debounceMs),
|
|
3072
|
-
distinctUntilChanged((a, b) => a?.key === b?.key)), { initialValue: undefined, injector: config.injector });
|
|
3073
|
-
const resource = rxResource({
|
|
3074
|
-
params: () => debouncedTrigger(),
|
|
3075
|
-
stream: ({ params }) => config.buildStream(params.payload),
|
|
3076
|
-
defaultValue: config.pendingValue,
|
|
3077
|
-
injector: config.injector,
|
|
3078
|
-
});
|
|
3079
|
-
return withPreviousValue(resource);
|
|
3080
|
-
});
|
|
3081
|
-
handle = { trigger, resultResource, refetchVersion: 0 };
|
|
3082
|
-
perFunctionSignalStore.set(contextKey, handle);
|
|
3083
|
-
}
|
|
3084
|
-
const resolved = config.resolve(ctx);
|
|
3085
|
-
if (resolved.kind === 'returnEarly') {
|
|
3086
|
-
handle.refetchVersion++;
|
|
3087
|
-
return resolved.value;
|
|
3088
|
-
}
|
|
3089
|
-
if (resolved.kind === 'holdPrevious') {
|
|
3090
|
-
return handle.resultResource.value();
|
|
3091
|
-
}
|
|
3092
|
-
const dedupKey = `${resolved.key}::${handle.refetchVersion}`;
|
|
3093
|
-
const newTrigger = { key: dedupKey, payload: resolved.payload };
|
|
3094
|
-
untracked(() => {
|
|
3095
|
-
const current = handle.trigger();
|
|
3096
|
-
if (current?.key !== newTrigger.key) {
|
|
3097
|
-
handle.trigger.set(newTrigger);
|
|
3098
|
-
}
|
|
3099
|
-
});
|
|
3100
|
-
return handle.resultResource.value();
|
|
3101
|
-
};
|
|
3102
|
-
}
|
|
3103
|
-
|
|
3104
|
-
function extractBoolean(response, responseExpression, pendingValue, logger) {
|
|
3105
|
-
if (responseExpression) {
|
|
3106
|
-
try {
|
|
3107
|
-
const result = ExpressionParser.evaluate(responseExpression, { response });
|
|
3108
|
-
if (typeof result !== 'boolean') {
|
|
3109
|
-
logger.warn(`responseExpression "${responseExpression}" returned non-boolean value:`, result, 'Consider returning a boolean explicitly.');
|
|
3110
|
-
}
|
|
3111
|
-
return !!result;
|
|
3112
|
-
}
|
|
3113
|
-
catch (error) {
|
|
3114
|
-
logger.warn(`Failed to evaluate responseExpression '${responseExpression}':`, error);
|
|
3115
|
-
return pendingValue;
|
|
909
|
+
else {
|
|
910
|
+
const singleTemplate = template;
|
|
911
|
+
if (INVALID_ARRAY_CHILD_TYPES.has(singleTemplate.type)) {
|
|
912
|
+
throw new DynamicFormError(`Simplified array "${key}" template has type '${singleTemplate.type}'. ` +
|
|
913
|
+
`Only leaf fields, rows, and groups are allowed as array children.`);
|
|
3116
914
|
}
|
|
3117
915
|
}
|
|
3118
|
-
return !!response;
|
|
3119
|
-
}
|
|
3120
|
-
function createHttpConditionLogicFunction(condition) {
|
|
3121
|
-
const httpClient = inject(HttpClient, { optional: true });
|
|
3122
|
-
if (!httpClient) {
|
|
3123
|
-
throw new DynamicFormError('HttpClient is required for HTTP conditions. Add provideHttpClient() to your providers.');
|
|
3124
|
-
}
|
|
3125
|
-
const fieldContextRegistry = inject(FieldContextRegistryService);
|
|
3126
|
-
const functionRegistry = inject(FunctionRegistryService);
|
|
3127
|
-
const injector = inject(Injector);
|
|
3128
|
-
const cache = inject(HTTP_CONDITION_CACHE);
|
|
3129
|
-
const logger = inject(DynamicFormLogger);
|
|
3130
|
-
const cacheService = inject(HttpConditionFunctionCacheService);
|
|
3131
|
-
const pendingValue = condition.pendingValue ?? false;
|
|
3132
|
-
const cacheDurationMs = condition.cacheDurationMs ?? 30000;
|
|
3133
|
-
const debounceMs = condition.debounceMs ?? 300;
|
|
3134
|
-
const cacheKey = stableStringify(condition);
|
|
3135
|
-
const cached = cacheService.httpConditionFunctionCache.get(cacheKey);
|
|
3136
|
-
if (cached) {
|
|
3137
|
-
return cached;
|
|
3138
|
-
}
|
|
3139
|
-
const fn = createDebouncedResourceLogicFn({
|
|
3140
|
-
pendingValue,
|
|
3141
|
-
debounceMs,
|
|
3142
|
-
injector,
|
|
3143
|
-
resolve: (ctx) => {
|
|
3144
|
-
const evaluationContext = fieldContextRegistry.createReactiveEvaluationContext(ctx, functionRegistry.getCustomFunctions());
|
|
3145
|
-
const resolved = resolveHttpRequest(condition.http, evaluationContext);
|
|
3146
|
-
if (!resolved)
|
|
3147
|
-
return { kind: 'holdPrevious' };
|
|
3148
|
-
const requestKey = stableStringify(resolved);
|
|
3149
|
-
const cachedResult = cache.get(requestKey);
|
|
3150
|
-
if (cachedResult !== undefined)
|
|
3151
|
-
return { kind: 'returnEarly', value: cachedResult };
|
|
3152
|
-
return { kind: 'trigger', key: requestKey, payload: resolved };
|
|
3153
|
-
},
|
|
3154
|
-
buildStream: (request) => {
|
|
3155
|
-
const method = request.method ?? 'GET';
|
|
3156
|
-
const options = {};
|
|
3157
|
-
if (request.body !== undefined)
|
|
3158
|
-
options['body'] = request.body;
|
|
3159
|
-
if (request.headers)
|
|
3160
|
-
options['headers'] = request.headers;
|
|
3161
|
-
return httpClient.request(method, request.url, options).pipe(map((response) => extractBoolean(response, condition.responseExpression, pendingValue, logger)), tap((value) => {
|
|
3162
|
-
const requestKey = stableStringify(request);
|
|
3163
|
-
cache.set(requestKey, value, cacheDurationMs);
|
|
3164
|
-
}), catchError((error) => {
|
|
3165
|
-
logger.warn('HTTP condition request failed:', error);
|
|
3166
|
-
return of(pendingValue);
|
|
3167
|
-
}));
|
|
3168
|
-
},
|
|
3169
|
-
});
|
|
3170
|
-
cacheService.httpConditionFunctionCache.set(cacheKey, fn);
|
|
3171
|
-
return fn;
|
|
3172
916
|
}
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
const injector = inject(Injector);
|
|
3182
|
-
const logger = inject(DynamicFormLogger);
|
|
3183
|
-
const cacheService = inject(AsyncConditionFunctionCacheService);
|
|
3184
|
-
const pendingValue = condition.pendingValue ?? false;
|
|
3185
|
-
const debounceMs = condition.debounceMs ?? 300;
|
|
3186
|
-
if (condition.asyncFn && condition.asyncFunctionName) {
|
|
3187
|
-
logger.warn('Both "asyncFn" and "asyncFunctionName" are set on async condition. Inline "asyncFn" takes precedence; "asyncFunctionName" is ignored.');
|
|
3188
|
-
}
|
|
3189
|
-
const cacheKey = condition.asyncFn ? undefined : stableStringify(condition);
|
|
3190
|
-
if (cacheKey !== undefined) {
|
|
3191
|
-
const cached = cacheService.asyncConditionFunctionCache.get(cacheKey);
|
|
3192
|
-
if (cached) {
|
|
3193
|
-
return cached;
|
|
917
|
+
function expandSimplifiedArray(field) {
|
|
918
|
+
const { template, value = [], addButton, removeButton, key, logic, minLength, maxLength, wrappers, skipAutoWrappers, skipDefaultWrappers, } = field;
|
|
919
|
+
const isObjectTemplate = Array.isArray(template);
|
|
920
|
+
const values = value;
|
|
921
|
+
validateSimplifiedTemplate(field);
|
|
922
|
+
const items = values.map((v) => {
|
|
923
|
+
if (isObjectTemplate) {
|
|
924
|
+
return buildObjectItem(template, v, removeButton);
|
|
3194
925
|
}
|
|
3195
|
-
|
|
3196
|
-
const fn = createDebouncedResourceLogicFn({
|
|
3197
|
-
pendingValue,
|
|
3198
|
-
debounceMs,
|
|
3199
|
-
injector,
|
|
3200
|
-
resolve: (ctx) => {
|
|
3201
|
-
const evaluationContext = fieldContextRegistry.createReactiveEvaluationContext(ctx, functionRegistry.getCustomFunctions());
|
|
3202
|
-
const key = stableStringify({
|
|
3203
|
-
formValue: evaluationContext.formValue,
|
|
3204
|
-
fieldValue: evaluationContext.fieldValue,
|
|
3205
|
-
externalData: evaluationContext.externalData,
|
|
3206
|
-
});
|
|
3207
|
-
return { kind: 'trigger', key, payload: ctx };
|
|
3208
|
-
},
|
|
3209
|
-
buildStream: (capturedCtx) => {
|
|
3210
|
-
const asyncFn = condition.asyncFn ??
|
|
3211
|
-
(condition.asyncFunctionName ? functionRegistry.getAsyncConditionFunction(condition.asyncFunctionName) : undefined);
|
|
3212
|
-
if (!asyncFn) {
|
|
3213
|
-
logger.warn(`Async Condition - function '${condition.asyncFunctionName}' not found. ` + `Register it in customFnConfig.asyncConditions.`);
|
|
3214
|
-
return of(pendingValue);
|
|
3215
|
-
}
|
|
3216
|
-
const evaluationContext = fieldContextRegistry.createReactiveEvaluationContext(capturedCtx, functionRegistry.getCustomFunctions());
|
|
3217
|
-
return from(asyncFn(evaluationContext)).pipe(map((result) => !!result), catchError((error) => {
|
|
3218
|
-
logger.warn('Async Condition - function failed:', error);
|
|
3219
|
-
return of(pendingValue);
|
|
3220
|
-
}));
|
|
3221
|
-
},
|
|
926
|
+
return buildPrimitiveItem(template, v);
|
|
3222
927
|
});
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
debouncedLogicFunctionCache = new Map();
|
|
3232
|
-
debouncedSignalStore = new Map();
|
|
3233
|
-
}
|
|
3234
|
-
|
|
3235
|
-
function validateNoNestedHttpConditions(expression) {
|
|
3236
|
-
if (expression.type === 'and' || expression.type === 'or') {
|
|
3237
|
-
for (const sub of expression.conditions) {
|
|
3238
|
-
if (sub.type === 'http' || sub.type === 'async') {
|
|
3239
|
-
const label = sub.type === 'http' ? 'HTTP' : 'Async';
|
|
3240
|
-
throw new DynamicFormError(`${label} conditions cannot be nested inside '${expression.type}' composites. ` +
|
|
3241
|
-
`Move the ${label} condition to a separate logic entry on the field.`);
|
|
3242
|
-
}
|
|
3243
|
-
validateNoNestedHttpConditions(sub);
|
|
3244
|
-
}
|
|
3245
|
-
}
|
|
3246
|
-
}
|
|
3247
|
-
function createLogicFunction(expression) {
|
|
3248
|
-
if (expression?.type === 'async') {
|
|
3249
|
-
return createAsyncConditionLogicFunction(expression);
|
|
3250
|
-
}
|
|
3251
|
-
if (expression?.type === 'http') {
|
|
3252
|
-
return createHttpConditionLogicFunction(expression);
|
|
3253
|
-
}
|
|
3254
|
-
if (expression) {
|
|
3255
|
-
validateNoNestedHttpConditions(expression);
|
|
3256
|
-
}
|
|
3257
|
-
const functionRegistry = inject(FunctionRegistryService);
|
|
3258
|
-
const fieldContextRegistry = inject(FieldContextRegistryService);
|
|
3259
|
-
const cacheService = inject(LogicFunctionCacheService);
|
|
3260
|
-
const cacheKey = stableStringify(expression);
|
|
3261
|
-
const cached = cacheService.logicFunctionCache.get(cacheKey);
|
|
3262
|
-
if (cached) {
|
|
3263
|
-
return cached;
|
|
3264
|
-
}
|
|
3265
|
-
const fn = (ctx) => {
|
|
3266
|
-
const evaluationContext = fieldContextRegistry.createReactiveEvaluationContext(ctx, functionRegistry.getCustomFunctions());
|
|
3267
|
-
return evaluateCondition(expression, evaluationContext);
|
|
3268
|
-
};
|
|
3269
|
-
cacheService.logicFunctionCache.set(cacheKey, fn);
|
|
3270
|
-
return fn;
|
|
3271
|
-
}
|
|
3272
|
-
function createDebouncedLogicFunction(expression, debounceMs) {
|
|
3273
|
-
if (expression?.type === 'async') {
|
|
3274
|
-
return createAsyncConditionLogicFunction(expression);
|
|
3275
|
-
}
|
|
3276
|
-
if (expression?.type === 'http') {
|
|
3277
|
-
return createHttpConditionLogicFunction(expression);
|
|
3278
|
-
}
|
|
3279
|
-
if (expression) {
|
|
3280
|
-
validateNoNestedHttpConditions(expression);
|
|
3281
|
-
}
|
|
3282
|
-
const functionRegistry = inject(FunctionRegistryService);
|
|
3283
|
-
const fieldContextRegistry = inject(FieldContextRegistryService);
|
|
3284
|
-
const injector = inject(Injector);
|
|
3285
|
-
const cacheService = inject(LogicFunctionCacheService);
|
|
3286
|
-
const cacheKey = `${stableStringify(expression)}:${debounceMs}`;
|
|
3287
|
-
const cached = cacheService.debouncedLogicFunctionCache.get(cacheKey);
|
|
3288
|
-
if (cached) {
|
|
3289
|
-
return cached;
|
|
3290
|
-
}
|
|
3291
|
-
const fn = (ctx) => {
|
|
3292
|
-
const contextKey = safeReadPathKeys(ctx).join('.');
|
|
3293
|
-
let signalPair = cacheService.debouncedSignalStore.get(contextKey);
|
|
3294
|
-
if (!signalPair) {
|
|
3295
|
-
const immediateValue = signal(false, ...(ngDevMode ? [{ debugName: "immediateValue" }] : /* istanbul ignore next */ []));
|
|
3296
|
-
const debouncedValue = untracked(() => {
|
|
3297
|
-
const immediateValue$ = toObservable(immediateValue, { injector }).pipe(debounceTime(debounceMs), distinctUntilChanged(), startWith(false));
|
|
3298
|
-
return toSignal(immediateValue$, { injector, initialValue: false });
|
|
3299
|
-
});
|
|
3300
|
-
signalPair = { immediateValue, debouncedValue };
|
|
3301
|
-
cacheService.debouncedSignalStore.set(contextKey, signalPair);
|
|
3302
|
-
}
|
|
3303
|
-
const evaluationContext = fieldContextRegistry.createReactiveEvaluationContext(ctx, functionRegistry.getCustomFunctions());
|
|
3304
|
-
const result = evaluateCondition(expression, evaluationContext);
|
|
3305
|
-
const { immediateValue, debouncedValue } = signalPair;
|
|
3306
|
-
untracked(() => {
|
|
3307
|
-
if (immediateValue() !== result) {
|
|
3308
|
-
immediateValue.set(result);
|
|
3309
|
-
}
|
|
3310
|
-
});
|
|
3311
|
-
return debouncedValue() ?? false;
|
|
3312
|
-
};
|
|
3313
|
-
cacheService.debouncedLogicFunctionCache.set(cacheKey, fn);
|
|
3314
|
-
return fn;
|
|
3315
|
-
}
|
|
3316
|
-
|
|
3317
|
-
class DynamicValueFunctionCacheService {
|
|
3318
|
-
dynamicValueFunctionCache = new Map();
|
|
3319
|
-
}
|
|
3320
|
-
|
|
3321
|
-
function createDynamicValueFunction(expression) {
|
|
3322
|
-
const fieldContextRegistry = inject(FieldContextRegistryService);
|
|
3323
|
-
const logger = inject(DynamicFormLogger);
|
|
3324
|
-
const cacheService = inject(DynamicValueFunctionCacheService);
|
|
3325
|
-
const cached = cacheService.dynamicValueFunctionCache.get(expression);
|
|
3326
|
-
if (cached) {
|
|
3327
|
-
return cached;
|
|
3328
|
-
}
|
|
3329
|
-
const fn = (ctx) => {
|
|
3330
|
-
const evaluationContext = fieldContextRegistry.createEvaluationContext(ctx);
|
|
3331
|
-
try {
|
|
3332
|
-
return ExpressionParser.evaluate(expression, evaluationContext);
|
|
3333
|
-
}
|
|
3334
|
-
catch (error) {
|
|
3335
|
-
logger.error('Error evaluating dynamic expression:', expression, error);
|
|
3336
|
-
return undefined;
|
|
3337
|
-
}
|
|
3338
|
-
};
|
|
3339
|
-
cacheService.dynamicValueFunctionCache.set(expression, fn);
|
|
3340
|
-
return fn;
|
|
3341
|
-
}
|
|
3342
|
-
|
|
3343
|
-
function createDefaultDerivationLogConfig() {
|
|
3344
|
-
return {
|
|
3345
|
-
level: 'none',
|
|
928
|
+
const addTemplate = isObjectTemplate
|
|
929
|
+
? buildObjectItemTemplate(template, removeButton)
|
|
930
|
+
: template;
|
|
931
|
+
const hasAutoRemove = !isObjectTemplate && removeButton !== false;
|
|
932
|
+
const arrayFieldObj = {
|
|
933
|
+
key,
|
|
934
|
+
type: 'array',
|
|
935
|
+
fields: items,
|
|
3346
936
|
};
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
if (config.level === 'none')
|
|
3350
|
-
return false;
|
|
3351
|
-
if (minLevel === 'summary')
|
|
3352
|
-
return config.level === 'summary' || config.level === 'verbose';
|
|
3353
|
-
return config.level === 'verbose';
|
|
3354
|
-
}
|
|
3355
|
-
function isFormStateCondition(condition) {
|
|
3356
|
-
return typeof condition === 'string' && ['formInvalid', 'formSubmitting', 'pageInvalid'].includes(condition);
|
|
3357
|
-
}
|
|
3358
|
-
function isStateLogicConfig(config) {
|
|
3359
|
-
return config.type === 'hidden' || config.type === 'readonly' || config.type === 'disabled' || config.type === 'required';
|
|
3360
|
-
}
|
|
3361
|
-
function isDerivationLogicConfig(config) {
|
|
3362
|
-
return config.type === 'derivation';
|
|
3363
|
-
}
|
|
3364
|
-
function hasTargetProperty(config) {
|
|
3365
|
-
return 'targetProperty' in config && typeof config.targetProperty === 'string' && config.targetProperty.length > 0;
|
|
3366
|
-
}
|
|
3367
|
-
|
|
3368
|
-
const FORM_VALUE_ACCESS_PATTERN = /\bformValue\s*(?:\.|\[)/;
|
|
3369
|
-
const FORM_VALUE_PATTERN = /\bformValue\s*(?:\.([\w.]+)|\[\s*'([\w.-]+)'\s*\]|\[\s*"([\w.-]+)"\s*\])/g;
|
|
3370
|
-
function isCrossFieldExpression(expr, context) {
|
|
3371
|
-
if (expr === undefined || typeof expr === 'boolean' || isFormStateCondition(expr)) {
|
|
3372
|
-
return false;
|
|
3373
|
-
}
|
|
3374
|
-
switch (expr.type) {
|
|
3375
|
-
case 'fieldValue':
|
|
3376
|
-
return !!expr.fieldPath;
|
|
3377
|
-
case 'javascript':
|
|
3378
|
-
return FORM_VALUE_ACCESS_PATTERN.test(expr.expression || '');
|
|
3379
|
-
case 'custom': {
|
|
3380
|
-
const functionName = expr.functionName;
|
|
3381
|
-
if (context?.getFunctionScope && functionName) {
|
|
3382
|
-
const scope = context.getFunctionScope(functionName);
|
|
3383
|
-
if (scope === 'field') {
|
|
3384
|
-
return false;
|
|
3385
|
-
}
|
|
3386
|
-
}
|
|
3387
|
-
return true;
|
|
3388
|
-
}
|
|
3389
|
-
case 'async':
|
|
3390
|
-
return false;
|
|
3391
|
-
case 'and':
|
|
3392
|
-
case 'or':
|
|
3393
|
-
return (expr.conditions || []).some((c) => isCrossFieldExpression(c, context));
|
|
3394
|
-
default:
|
|
3395
|
-
return false;
|
|
3396
|
-
}
|
|
3397
|
-
}
|
|
3398
|
-
function extractExpressionDependencies(expr) {
|
|
3399
|
-
if (expr === undefined || typeof expr === 'boolean') {
|
|
3400
|
-
return [];
|
|
3401
|
-
}
|
|
3402
|
-
const deps = new Set();
|
|
3403
|
-
switch (expr.type) {
|
|
3404
|
-
case 'fieldValue':
|
|
3405
|
-
if (expr.fieldPath) {
|
|
3406
|
-
deps.add(expr.fieldPath.split('.')[0]);
|
|
3407
|
-
}
|
|
3408
|
-
break;
|
|
3409
|
-
case 'javascript':
|
|
3410
|
-
extractFromExpressionString(expr.expression || '', deps);
|
|
3411
|
-
break;
|
|
3412
|
-
case 'custom':
|
|
3413
|
-
deps.add('*');
|
|
3414
|
-
break;
|
|
3415
|
-
case 'async':
|
|
3416
|
-
break;
|
|
3417
|
-
case 'and':
|
|
3418
|
-
case 'or':
|
|
3419
|
-
for (const condition of expr.conditions || []) {
|
|
3420
|
-
extractExpressionDependencies(condition).forEach((d) => deps.add(d));
|
|
3421
|
-
}
|
|
3422
|
-
break;
|
|
3423
|
-
}
|
|
3424
|
-
return Array.from(deps);
|
|
3425
|
-
}
|
|
3426
|
-
function extractStringDependencies(expression) {
|
|
3427
|
-
const deps = new Set();
|
|
3428
|
-
extractFromExpressionString(expression, deps);
|
|
3429
|
-
return Array.from(deps);
|
|
3430
|
-
}
|
|
3431
|
-
function extractFromExpressionString(expression, deps) {
|
|
3432
|
-
for (const match of expression.matchAll(FORM_VALUE_PATTERN)) {
|
|
3433
|
-
const fullPath = match[1] ?? match[2] ?? match[3];
|
|
3434
|
-
if (!fullPath)
|
|
3435
|
-
continue;
|
|
3436
|
-
const rootField = fullPath.split('.')[0];
|
|
3437
|
-
deps.add(rootField);
|
|
3438
|
-
if (fullPath.includes('.')) {
|
|
3439
|
-
deps.add(fullPath);
|
|
3440
|
-
}
|
|
3441
|
-
}
|
|
3442
|
-
}
|
|
3443
|
-
function isCrossFieldValidator(config) {
|
|
3444
|
-
if (!config.expression) {
|
|
3445
|
-
return false;
|
|
3446
|
-
}
|
|
3447
|
-
return FORM_VALUE_ACCESS_PATTERN.test(config.expression);
|
|
3448
|
-
}
|
|
3449
|
-
function isCrossFieldBuiltInValidator(config) {
|
|
3450
|
-
if (config.type === 'custom') {
|
|
3451
|
-
return false; }
|
|
3452
|
-
if ('expression' in config && typeof config.expression === 'string') {
|
|
3453
|
-
return FORM_VALUE_ACCESS_PATTERN.test(config.expression);
|
|
3454
|
-
}
|
|
3455
|
-
return false;
|
|
3456
|
-
}
|
|
3457
|
-
function isResourceBasedValidator(config) {
|
|
3458
|
-
return config.type === 'async' || config.type === 'http';
|
|
3459
|
-
}
|
|
3460
|
-
function hasCrossFieldWhenCondition(config, context) {
|
|
3461
|
-
if (!('when' in config) || !config.when) {
|
|
3462
|
-
return false;
|
|
3463
|
-
}
|
|
3464
|
-
return isCrossFieldExpression(config.when, context);
|
|
3465
|
-
}
|
|
3466
|
-
function isCrossFieldStateLogic(config, context) {
|
|
3467
|
-
return isCrossFieldExpression(config.condition, context);
|
|
3468
|
-
}
|
|
3469
|
-
function isCrossFieldSchema(config, context) {
|
|
3470
|
-
if (config.type !== 'applyWhen' || !config.condition) {
|
|
3471
|
-
return false;
|
|
3472
|
-
}
|
|
3473
|
-
return isCrossFieldExpression(config.condition, context);
|
|
3474
|
-
}
|
|
3475
|
-
|
|
3476
|
-
function applyEmailValidator(path) {
|
|
3477
|
-
email(path);
|
|
3478
|
-
}
|
|
3479
|
-
function applyMinValidator(path, value, expression) {
|
|
3480
|
-
if (expression) {
|
|
3481
|
-
min(path, createDynamicValueFunction(expression));
|
|
3482
|
-
}
|
|
3483
|
-
else {
|
|
3484
|
-
min(path, value);
|
|
3485
|
-
}
|
|
3486
|
-
}
|
|
3487
|
-
function applyMaxValidator(path, value, expression) {
|
|
3488
|
-
if (expression) {
|
|
3489
|
-
max(path, createDynamicValueFunction(expression));
|
|
3490
|
-
}
|
|
3491
|
-
else {
|
|
3492
|
-
max(path, value);
|
|
3493
|
-
}
|
|
3494
|
-
}
|
|
3495
|
-
function applyMinLengthValidator(path, value, expression) {
|
|
3496
|
-
if (expression) {
|
|
3497
|
-
minLength(path, createDynamicValueFunction(expression));
|
|
3498
|
-
}
|
|
3499
|
-
else {
|
|
3500
|
-
minLength(path, value);
|
|
3501
|
-
}
|
|
3502
|
-
}
|
|
3503
|
-
function applyMaxLengthValidator(path, value, expression) {
|
|
3504
|
-
if (expression) {
|
|
3505
|
-
maxLength(path, createDynamicValueFunction(expression));
|
|
3506
|
-
}
|
|
3507
|
-
else {
|
|
3508
|
-
maxLength(path, value);
|
|
3509
|
-
}
|
|
3510
|
-
}
|
|
3511
|
-
function applyPatternValidator(path, value, expression) {
|
|
3512
|
-
if (expression) {
|
|
3513
|
-
pattern(path, createDynamicValueFunction(expression));
|
|
3514
|
-
}
|
|
3515
|
-
else {
|
|
3516
|
-
pattern(path, value);
|
|
3517
|
-
}
|
|
3518
|
-
}
|
|
3519
|
-
function createConditionalLogic(when) {
|
|
3520
|
-
return when ? createLogicFunction(when) : undefined;
|
|
3521
|
-
}
|
|
3522
|
-
function applyValidator(config, fieldPath) {
|
|
3523
|
-
const path = fieldPath;
|
|
3524
|
-
if (!isResourceBasedValidator(config) && (isCrossFieldBuiltInValidator(config) || hasCrossFieldWhenCondition(config))) {
|
|
3525
|
-
return;
|
|
3526
|
-
}
|
|
3527
|
-
switch (config.type) {
|
|
3528
|
-
case 'required':
|
|
3529
|
-
if (config.when) {
|
|
3530
|
-
required(path, { when: createLogicFunction(config.when) });
|
|
3531
|
-
}
|
|
3532
|
-
else {
|
|
3533
|
-
required(path);
|
|
3534
|
-
}
|
|
3535
|
-
break;
|
|
3536
|
-
case 'email':
|
|
3537
|
-
applyEmailValidator(fieldPath);
|
|
3538
|
-
break;
|
|
3539
|
-
case 'min':
|
|
3540
|
-
if (typeof config.value === 'number') {
|
|
3541
|
-
applyMinValidator(fieldPath, config.value, config.expression);
|
|
3542
|
-
}
|
|
3543
|
-
break;
|
|
3544
|
-
case 'max':
|
|
3545
|
-
if (typeof config.value === 'number') {
|
|
3546
|
-
applyMaxValidator(fieldPath, config.value, config.expression);
|
|
3547
|
-
}
|
|
3548
|
-
break;
|
|
3549
|
-
case 'minLength':
|
|
3550
|
-
if (typeof config.value === 'number') {
|
|
3551
|
-
applyMinLengthValidator(fieldPath, config.value, config.expression);
|
|
3552
|
-
}
|
|
3553
|
-
break;
|
|
3554
|
-
case 'maxLength':
|
|
3555
|
-
if (typeof config.value === 'number') {
|
|
3556
|
-
applyMaxLengthValidator(fieldPath, config.value, config.expression);
|
|
3557
|
-
}
|
|
3558
|
-
break;
|
|
3559
|
-
case 'pattern':
|
|
3560
|
-
if (config.value instanceof RegExp || typeof config.value === 'string') {
|
|
3561
|
-
let regexPattern;
|
|
3562
|
-
if (typeof config.value === 'string') {
|
|
3563
|
-
try {
|
|
3564
|
-
regexPattern = new RegExp(config.value);
|
|
3565
|
-
}
|
|
3566
|
-
catch (e) {
|
|
3567
|
-
throw new DynamicFormError(`Invalid regex pattern in validator: '${config.value}' — ${e instanceof Error ? e.message : String(e)}`);
|
|
3568
|
-
}
|
|
3569
|
-
}
|
|
3570
|
-
else {
|
|
3571
|
-
regexPattern = config.value;
|
|
3572
|
-
}
|
|
3573
|
-
applyPatternValidator(fieldPath, regexPattern, config.expression);
|
|
3574
|
-
}
|
|
3575
|
-
break;
|
|
3576
|
-
case 'custom':
|
|
3577
|
-
applyCustomValidator(config, path);
|
|
3578
|
-
break;
|
|
3579
|
-
case 'async':
|
|
3580
|
-
applyAsyncValidator(config, path);
|
|
3581
|
-
break;
|
|
3582
|
-
case 'http':
|
|
3583
|
-
applyUnifiedHttpValidator(config, path);
|
|
3584
|
-
break;
|
|
3585
|
-
}
|
|
3586
|
-
}
|
|
3587
|
-
function applyCustomValidator(config, fieldPath) {
|
|
3588
|
-
if (isCrossFieldValidator(config)) {
|
|
3589
|
-
return;
|
|
937
|
+
if (logic) {
|
|
938
|
+
arrayFieldObj['logic'] = logic;
|
|
3590
939
|
}
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
validatorFn = createExpressionValidator(config);
|
|
940
|
+
if (minLength !== undefined) {
|
|
941
|
+
arrayFieldObj['minLength'] = minLength;
|
|
3594
942
|
}
|
|
3595
|
-
|
|
3596
|
-
|
|
943
|
+
if (maxLength !== undefined) {
|
|
944
|
+
arrayFieldObj['maxLength'] = maxLength;
|
|
3597
945
|
}
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
logger.warn('Custom validator must have one of "expression", "functionName", or "fn"');
|
|
3601
|
-
return;
|
|
946
|
+
if (wrappers !== undefined) {
|
|
947
|
+
arrayFieldObj['wrappers'] = wrappers;
|
|
3602
948
|
}
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
if (whenLogic && !whenLogic(ctx))
|
|
3606
|
-
return null;
|
|
3607
|
-
return validatorFn(ctx);
|
|
3608
|
-
});
|
|
3609
|
-
}
|
|
3610
|
-
function createFunctionValidator(config) {
|
|
3611
|
-
const logger = inject(DynamicFormLogger);
|
|
3612
|
-
if (config.fn && config.functionName) {
|
|
3613
|
-
logger.warn('Both "fn" and "functionName" are set on custom validator. Inline "fn" takes precedence; "functionName" is ignored.');
|
|
949
|
+
if (skipAutoWrappers !== undefined) {
|
|
950
|
+
arrayFieldObj['skipAutoWrappers'] = skipAutoWrappers;
|
|
3614
951
|
}
|
|
3615
|
-
if (
|
|
3616
|
-
|
|
3617
|
-
return (ctx) => inlineFn(ctx, config.params);
|
|
952
|
+
if (skipDefaultWrappers !== undefined) {
|
|
953
|
+
arrayFieldObj['skipDefaultWrappers'] = skipDefaultWrappers;
|
|
3618
954
|
}
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
955
|
+
const arrayField = arrayFieldObj;
|
|
956
|
+
const primitiveFieldKey = !isObjectTemplate ? template.key : undefined;
|
|
957
|
+
const metadataTemplate = isObjectTemplate ? buildObjectItemTemplate(template, removeButton) : template;
|
|
958
|
+
setNormalizedArrayMetadata(arrayFieldObj, {
|
|
959
|
+
...(hasAutoRemove && { autoRemoveButton: buildRemoveButton(removeButton) }),
|
|
960
|
+
...(primitiveFieldKey && { primitiveFieldKey }),
|
|
961
|
+
template: metadataTemplate,
|
|
962
|
+
});
|
|
963
|
+
let addButtonField;
|
|
964
|
+
if (addButton !== false) {
|
|
965
|
+
const buttonConfig = (typeof addButton === 'object' ? addButton : {});
|
|
966
|
+
addButtonField = {
|
|
967
|
+
key: `${key}__add`,
|
|
968
|
+
type: 'addArrayItem',
|
|
969
|
+
label: buttonConfig.label ?? 'Add',
|
|
970
|
+
arrayKey: key,
|
|
971
|
+
template: addTemplate,
|
|
972
|
+
...(buttonConfig.props && { props: buttonConfig.props }),
|
|
973
|
+
...(logic && { logic }),
|
|
974
|
+
};
|
|
3623
975
|
}
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
976
|
+
return { arrayField, addButton: addButtonField };
|
|
977
|
+
}
|
|
978
|
+
function buildPrimitiveItem(template, value) {
|
|
979
|
+
return { ...template, value };
|
|
980
|
+
}
|
|
981
|
+
function buildObjectItem(template, valueObj, removeButton) {
|
|
982
|
+
const fieldsWithValues = template.map((templateField) => ({
|
|
983
|
+
...templateField,
|
|
984
|
+
...(Object.hasOwn(valueObj, templateField.key) && { value: valueObj[templateField.key] }),
|
|
985
|
+
}));
|
|
986
|
+
if (removeButton === false) {
|
|
987
|
+
return fieldsWithValues;
|
|
3628
988
|
}
|
|
3629
|
-
return
|
|
989
|
+
return [...fieldsWithValues, buildRemoveButton(removeButton)];
|
|
3630
990
|
}
|
|
3631
|
-
function
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
if (!expression) {
|
|
3635
|
-
logger.warn('Custom validator missing expression');
|
|
3636
|
-
return () => null;
|
|
991
|
+
function buildObjectItemTemplate(template, removeButton) {
|
|
992
|
+
if (removeButton === false) {
|
|
993
|
+
return template;
|
|
3637
994
|
}
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
const kind = config.kind || 'custom';
|
|
3648
|
-
const validationError = { kind };
|
|
3649
|
-
if (config.errorParams) {
|
|
3650
|
-
Object.entries(config.errorParams).forEach(([key, expression]) => {
|
|
3651
|
-
try {
|
|
3652
|
-
validationError[key] = ExpressionParser.evaluate(expression, evaluationContext);
|
|
3653
|
-
}
|
|
3654
|
-
catch (err) {
|
|
3655
|
-
logger.warn(`Error evaluating errorParam "${key}":`, expression, err);
|
|
3656
|
-
}
|
|
3657
|
-
});
|
|
3658
|
-
}
|
|
3659
|
-
return validationError;
|
|
3660
|
-
}
|
|
3661
|
-
catch (error) {
|
|
3662
|
-
logger.error('Error evaluating custom validator expression:', expression, error);
|
|
3663
|
-
return { kind: config.kind || 'custom' };
|
|
3664
|
-
}
|
|
995
|
+
return [...template, buildRemoveButton(removeButton)];
|
|
996
|
+
}
|
|
997
|
+
function buildRemoveButton(config) {
|
|
998
|
+
const buttonConfig = (typeof config === 'object' ? config : {});
|
|
999
|
+
return {
|
|
1000
|
+
key: '__remove',
|
|
1001
|
+
type: 'removeArrayItem',
|
|
1002
|
+
label: buttonConfig.label ?? 'Remove',
|
|
1003
|
+
...(buttonConfig.props && { props: buttonConfig.props }),
|
|
3665
1004
|
};
|
|
3666
1005
|
}
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
1006
|
+
|
|
1007
|
+
function createSlot() {
|
|
1008
|
+
let counter = 0;
|
|
1009
|
+
const templates = new Map();
|
|
1010
|
+
const itemOrder = [];
|
|
1011
|
+
const primitiveFieldKey = signal(undefined, ...(ngDevMode ? [{ debugName: "primitiveFieldKey" }] : /* istanbul ignore next */ []));
|
|
1012
|
+
return {
|
|
1013
|
+
templates,
|
|
1014
|
+
itemOrder,
|
|
1015
|
+
primitiveFieldKey,
|
|
1016
|
+
nextId: () => `item-${counter++}`,
|
|
1017
|
+
setTemplate(itemId, t) {
|
|
1018
|
+
templates.set(itemId, t);
|
|
1019
|
+
},
|
|
1020
|
+
getTemplate(itemId) {
|
|
1021
|
+
return templates.get(itemId);
|
|
1022
|
+
},
|
|
1023
|
+
hasTemplate(itemId) {
|
|
1024
|
+
return templates.has(itemId);
|
|
1025
|
+
},
|
|
1026
|
+
setIdAt(index, itemId) {
|
|
1027
|
+
itemOrder[index] = itemId;
|
|
1028
|
+
},
|
|
1029
|
+
removeAt(index) {
|
|
1030
|
+
const [removed] = itemOrder.splice(index, 1);
|
|
1031
|
+
if (removed !== undefined)
|
|
1032
|
+
templates.delete(removed);
|
|
1033
|
+
},
|
|
1034
|
+
moveItem(fromIndex, toIndex) {
|
|
1035
|
+
const [id] = itemOrder.splice(fromIndex, 1);
|
|
1036
|
+
if (id !== undefined)
|
|
1037
|
+
itemOrder.splice(toIndex, 0, id);
|
|
1038
|
+
},
|
|
1039
|
+
truncate(length) {
|
|
1040
|
+
const removed = itemOrder.splice(length);
|
|
1041
|
+
for (const id of removed)
|
|
1042
|
+
templates.delete(id);
|
|
1043
|
+
},
|
|
1044
|
+
clear() {
|
|
1045
|
+
templates.clear();
|
|
1046
|
+
itemOrder.length = 0;
|
|
1047
|
+
counter = 0;
|
|
1048
|
+
primitiveFieldKey.set(undefined);
|
|
3692
1049
|
},
|
|
3693
|
-
factory: validatorConfig.factory,
|
|
3694
|
-
onSuccess: validatorConfig.onSuccess,
|
|
3695
|
-
onError: validatorConfig.onError,
|
|
3696
1050
|
};
|
|
3697
|
-
validateAsync(fieldPath, asyncOptions);
|
|
3698
1051
|
}
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
1052
|
+
class ArrayItemRegistryService {
|
|
1053
|
+
slots = new Map();
|
|
1054
|
+
slotFor(arrayPath) {
|
|
1055
|
+
let slot = this.slots.get(arrayPath);
|
|
1056
|
+
if (!slot) {
|
|
1057
|
+
slot = createSlot();
|
|
1058
|
+
this.slots.set(arrayPath, slot);
|
|
1059
|
+
}
|
|
1060
|
+
return slot;
|
|
3703
1061
|
}
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
1062
|
+
clearAll() {
|
|
1063
|
+
for (const slot of this.slots.values())
|
|
1064
|
+
slot.clear();
|
|
1065
|
+
this.slots.clear();
|
|
3707
1066
|
}
|
|
3708
|
-
|
|
1067
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ArrayItemRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1068
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ArrayItemRegistryService });
|
|
3709
1069
|
}
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
1070
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ArrayItemRegistryService, decorators: [{
|
|
1071
|
+
type: Injectable
|
|
1072
|
+
}] });
|
|
1073
|
+
|
|
1074
|
+
class SchemaRegistryService {
|
|
1075
|
+
registeredSchemas = new Map();
|
|
1076
|
+
registerSchema(schema) {
|
|
1077
|
+
this.registeredSchemas.set(schema.name, schema);
|
|
3714
1078
|
}
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
httpValidatorConfig = config.fn;
|
|
1079
|
+
getSchema(name) {
|
|
1080
|
+
return this.registeredSchemas.get(name);
|
|
3718
1081
|
}
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
httpValidatorConfig = registry.getHttpValidator(config.functionName);
|
|
3722
|
-
if (!httpValidatorConfig) {
|
|
3723
|
-
throw new DynamicFormError(`HTTP validator "${config.functionName}" not found. Register it with customFnConfig.httpValidators.`);
|
|
3724
|
-
}
|
|
1082
|
+
getAllSchemas() {
|
|
1083
|
+
return new Map(this.registeredSchemas);
|
|
3725
1084
|
}
|
|
3726
|
-
|
|
3727
|
-
|
|
1085
|
+
clearSchemas() {
|
|
1086
|
+
this.registeredSchemas.clear();
|
|
3728
1087
|
}
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
onError: httpValidatorConfig.onError,
|
|
3738
|
-
};
|
|
3739
|
-
validateHttp(fieldPath, httpOptions);
|
|
3740
|
-
}
|
|
3741
|
-
function applyDeclarativeHttpValidator(config, fieldPath) {
|
|
3742
|
-
const fieldContextRegistry = inject(FieldContextRegistryService);
|
|
3743
|
-
const functionRegistry = inject(FunctionRegistryService);
|
|
3744
|
-
const logger = inject(DynamicFormLogger);
|
|
3745
|
-
const whenLogic = createConditionalLogic(config.when);
|
|
3746
|
-
validateHttp(fieldPath, {
|
|
3747
|
-
request: (ctx) => {
|
|
3748
|
-
if (whenLogic && !whenLogic(ctx))
|
|
3749
|
-
return undefined;
|
|
3750
|
-
const evalCtx = fieldContextRegistry.createReactiveEvaluationContext(ctx, functionRegistry.getCustomFunctions());
|
|
3751
|
-
return resolveHttpRequest(config.http, evalCtx) ?? undefined;
|
|
3752
|
-
},
|
|
3753
|
-
onSuccess: (response) => {
|
|
3754
|
-
return evaluateHttpValidationResponse(response, config.responseMapping, logger);
|
|
3755
|
-
},
|
|
3756
|
-
onError: (error) => {
|
|
3757
|
-
logger.warn('HTTP validator request failed:', error);
|
|
3758
|
-
return { kind: config.responseMapping.errorKind };
|
|
3759
|
-
},
|
|
3760
|
-
});
|
|
3761
|
-
}
|
|
3762
|
-
function applyValidators(configs, fieldPath) {
|
|
3763
|
-
configs.forEach((config) => applyValidator(config, fieldPath));
|
|
1088
|
+
resolveSchema(schema) {
|
|
1089
|
+
if (typeof schema === 'string') {
|
|
1090
|
+
return this.getSchema(schema) || null;
|
|
1091
|
+
}
|
|
1092
|
+
return schema;
|
|
1093
|
+
}
|
|
1094
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: SchemaRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1095
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: SchemaRegistryService });
|
|
3764
1096
|
}
|
|
1097
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: SchemaRegistryService, decorators: [{
|
|
1098
|
+
type: Injectable
|
|
1099
|
+
}] });
|
|
3765
1100
|
|
|
3766
1101
|
const DEV_MODE = typeof ngDevMode === 'undefined' || !!ngDevMode;
|
|
3767
1102
|
|
|
@@ -4225,15 +1560,6 @@ function collectFieldsFromObjectItem(itemFields, allFields) {
|
|
|
4225
1560
|
}
|
|
4226
1561
|
}
|
|
4227
1562
|
|
|
4228
|
-
const FIELD_REGISTRY = new InjectionToken('FIELD_REGISTRY', {
|
|
4229
|
-
providedIn: 'root',
|
|
4230
|
-
factory: () => new Map(),
|
|
4231
|
-
});
|
|
4232
|
-
function getFieldValueHandling(fieldType, registry) {
|
|
4233
|
-
const definition = registry.get(fieldType);
|
|
4234
|
-
return definition?.valueHandling ?? 'include';
|
|
4235
|
-
}
|
|
4236
|
-
|
|
4237
1563
|
function getFieldDefaultValue(field, registry) {
|
|
4238
1564
|
const valueHandling = getFieldValueHandling(field.type, registry);
|
|
4239
1565
|
if (valueHandling === 'exclude') {
|
|
@@ -4304,227 +1630,37 @@ function getFieldDefaultValue(field, registry) {
|
|
|
4304
1630
|
if (!itemDefinitions || itemDefinitions.length === 0) {
|
|
4305
1631
|
return [];
|
|
4306
1632
|
}
|
|
4307
|
-
return itemDefinitions.map((itemDef) => {
|
|
4308
|
-
if (!Array.isArray(itemDef)) {
|
|
4309
|
-
return getFieldDefaultValue(itemDef, registry);
|
|
4310
|
-
}
|
|
4311
|
-
const itemFields = itemDef;
|
|
4312
|
-
let itemValue = {};
|
|
4313
|
-
for (const templateField of itemFields) {
|
|
4314
|
-
const fieldValue = getFieldDefaultValue(templateField, registry);
|
|
4315
|
-
const fieldValueHandling = getFieldValueHandling(templateField.type, registry);
|
|
4316
|
-
if (templateField.type === 'group' && 'key' in templateField && templateField.key) {
|
|
4317
|
-
itemValue[templateField.key] = fieldValue;
|
|
4318
|
-
}
|
|
4319
|
-
else if (templateField.type === 'row' || templateField.type === 'container') {
|
|
4320
|
-
if (fieldValue && typeof fieldValue === 'object') {
|
|
4321
|
-
itemValue = { ...itemValue, ...fieldValue };
|
|
4322
|
-
}
|
|
4323
|
-
}
|
|
4324
|
-
else if (fieldValueHandling === 'include' && 'key' in templateField && templateField.key) {
|
|
4325
|
-
itemValue[templateField.key] = fieldValue;
|
|
4326
|
-
}
|
|
4327
|
-
}
|
|
4328
|
-
return itemValue;
|
|
4329
|
-
});
|
|
4330
|
-
}
|
|
4331
|
-
if (field.type === 'input' && 'props' in field) {
|
|
4332
|
-
const props = field.props;
|
|
4333
|
-
if (props?.type === 'number') {
|
|
4334
|
-
return NaN;
|
|
4335
|
-
}
|
|
4336
|
-
}
|
|
4337
|
-
return '';
|
|
4338
|
-
}
|
|
4339
|
-
|
|
4340
|
-
function isEqual(a, b) {
|
|
4341
|
-
return isEqualInternal(a, b, new WeakMap(), new WeakMap());
|
|
4342
|
-
}
|
|
4343
|
-
function isEqualInternal(a, b, seenA, seenB) {
|
|
4344
|
-
if (a === b)
|
|
4345
|
-
return true;
|
|
4346
|
-
if (typeof a === 'number' && typeof b === 'number') {
|
|
4347
|
-
return Object.is(a, b);
|
|
4348
|
-
}
|
|
4349
|
-
if (a == null || b == null)
|
|
4350
|
-
return false;
|
|
4351
|
-
if (typeof a !== typeof b)
|
|
4352
|
-
return false;
|
|
4353
|
-
if (typeof a !== 'object') {
|
|
4354
|
-
return Object.is(a, b);
|
|
4355
|
-
}
|
|
4356
|
-
const objA = a;
|
|
4357
|
-
const objB = b;
|
|
4358
|
-
if (seenA.has(objA) || seenB.has(objB)) {
|
|
4359
|
-
return seenA.get(objA) === objB && seenB.get(objB) === objA;
|
|
4360
|
-
}
|
|
4361
|
-
seenA.set(objA, objB);
|
|
4362
|
-
seenB.set(objB, objA);
|
|
4363
|
-
if (objA.constructor !== objB.constructor)
|
|
4364
|
-
return false;
|
|
4365
|
-
if (a instanceof Date && b instanceof Date) {
|
|
4366
|
-
return a.getTime() === b.getTime();
|
|
4367
|
-
}
|
|
4368
|
-
if (a instanceof RegExp && b instanceof RegExp) {
|
|
4369
|
-
return a.source === b.source && a.flags === b.flags;
|
|
4370
|
-
}
|
|
4371
|
-
if (a instanceof Map && b instanceof Map) {
|
|
4372
|
-
if (a.size !== b.size)
|
|
4373
|
-
return false;
|
|
4374
|
-
for (const [key, value] of a) {
|
|
4375
|
-
if (!b.has(key) || !isEqualInternal(value, b.get(key), seenA, seenB)) {
|
|
4376
|
-
return false;
|
|
4377
|
-
}
|
|
4378
|
-
}
|
|
4379
|
-
return true;
|
|
4380
|
-
}
|
|
4381
|
-
if (a instanceof Set && b instanceof Set) {
|
|
4382
|
-
if (a.size !== b.size)
|
|
4383
|
-
return false;
|
|
4384
|
-
for (const value of a) {
|
|
4385
|
-
if (!b.has(value)) {
|
|
4386
|
-
if (typeof value === 'object' && value !== null) {
|
|
4387
|
-
let found = false;
|
|
4388
|
-
for (const bValue of b) {
|
|
4389
|
-
if (typeof bValue === 'object' && bValue !== null && isEqualInternal(value, bValue, seenA, seenB)) {
|
|
4390
|
-
found = true;
|
|
4391
|
-
break;
|
|
4392
|
-
}
|
|
4393
|
-
}
|
|
4394
|
-
if (!found)
|
|
4395
|
-
return false;
|
|
4396
|
-
}
|
|
4397
|
-
else {
|
|
4398
|
-
return false;
|
|
4399
|
-
}
|
|
4400
|
-
}
|
|
4401
|
-
}
|
|
4402
|
-
return true;
|
|
4403
|
-
}
|
|
4404
|
-
if (Array.isArray(a) && Array.isArray(b)) {
|
|
4405
|
-
if (a.length !== b.length)
|
|
4406
|
-
return false;
|
|
4407
|
-
return a.every((item, index) => isEqualInternal(item, b[index], seenA, seenB));
|
|
4408
|
-
}
|
|
4409
|
-
const recordA = a;
|
|
4410
|
-
const recordB = b;
|
|
4411
|
-
const keysA = [...Object.keys(recordA), ...Object.getOwnPropertySymbols(recordA)];
|
|
4412
|
-
const keysB = [...Object.keys(recordB), ...Object.getOwnPropertySymbols(recordB)];
|
|
4413
|
-
if (keysA.length !== keysB.length)
|
|
4414
|
-
return false;
|
|
4415
|
-
const keySetB = new Set(keysB);
|
|
4416
|
-
return keysA.every((key) => keySetB.has(key) && isEqualInternal(recordA[key], recordB[key], seenA, seenB));
|
|
4417
|
-
}
|
|
4418
|
-
function omit(obj, keys) {
|
|
4419
|
-
const result = { ...obj };
|
|
4420
|
-
keys.forEach((key) => delete result[key]);
|
|
4421
|
-
return result;
|
|
4422
|
-
}
|
|
4423
|
-
function keyBy(array, key) {
|
|
4424
|
-
return array.reduce((acc, item) => {
|
|
4425
|
-
acc[String(item[key])] = item;
|
|
4426
|
-
return acc;
|
|
4427
|
-
}, {});
|
|
4428
|
-
}
|
|
4429
|
-
function mapValues(obj, fn) {
|
|
4430
|
-
return Object.entries(obj).reduce((acc, [key, value]) => {
|
|
4431
|
-
acc[key] = fn(value, key);
|
|
4432
|
-
return acc;
|
|
4433
|
-
}, {});
|
|
4434
|
-
}
|
|
4435
|
-
function deepMergeDefaults(defaults, value) {
|
|
4436
|
-
if (value == null)
|
|
4437
|
-
return { ...defaults };
|
|
4438
|
-
const result = { ...defaults };
|
|
4439
|
-
for (const key of Object.keys(value)) {
|
|
4440
|
-
if (key === '__proto__' || key === 'constructor' || key === 'prototype')
|
|
4441
|
-
continue;
|
|
4442
|
-
const incoming = value[key];
|
|
4443
|
-
const existing = result[key];
|
|
4444
|
-
if (isPlainObject(existing) && isPlainObject(incoming)) {
|
|
4445
|
-
result[key] = deepMergeDefaults(existing, incoming);
|
|
4446
|
-
}
|
|
4447
|
-
else if (Array.isArray(existing) && Array.isArray(incoming)) {
|
|
4448
|
-
result[key] = mergeArrayDefaults(existing, incoming);
|
|
4449
|
-
}
|
|
4450
|
-
else {
|
|
4451
|
-
result[key] = incoming;
|
|
4452
|
-
}
|
|
4453
|
-
}
|
|
4454
|
-
return result;
|
|
4455
|
-
}
|
|
4456
|
-
function mergeArrayDefaults(defaults, value) {
|
|
4457
|
-
if (defaults.length !== value.length)
|
|
4458
|
-
return value;
|
|
4459
|
-
if (!value.some((item, i) => isPlainObject(defaults[i]) && isPlainObject(item)))
|
|
4460
|
-
return value;
|
|
4461
|
-
return value.map((item, i) => {
|
|
4462
|
-
const defaultItem = defaults[i];
|
|
4463
|
-
if (isPlainObject(defaultItem) && isPlainObject(item)) {
|
|
4464
|
-
return deepMergeDefaults(defaultItem, item);
|
|
4465
|
-
}
|
|
4466
|
-
return item;
|
|
4467
|
-
});
|
|
4468
|
-
}
|
|
4469
|
-
function isPlainObject(value) {
|
|
4470
|
-
if (typeof value !== 'object' || value === null)
|
|
4471
|
-
return false;
|
|
4472
|
-
const proto = Object.getPrototypeOf(value);
|
|
4473
|
-
return proto === null || proto === Object.prototype;
|
|
4474
|
-
}
|
|
4475
|
-
const DEFAULT_MEMOIZE_MAX_SIZE = 100;
|
|
4476
|
-
function memoize(fn, resolverOrOptions) {
|
|
4477
|
-
const options = typeof resolverOrOptions === 'function' ? { resolver: resolverOrOptions } : (resolverOrOptions ?? {});
|
|
4478
|
-
const { resolver, maxSize = DEFAULT_MEMOIZE_MAX_SIZE } = options;
|
|
4479
|
-
const cache = new Map();
|
|
4480
|
-
return ((...args) => {
|
|
4481
|
-
const key = resolver ? resolver(...args) : JSON.stringify(args);
|
|
4482
|
-
const cachedValue = cache.get(key);
|
|
4483
|
-
if (cachedValue !== undefined) {
|
|
4484
|
-
cache.delete(key);
|
|
4485
|
-
cache.set(key, cachedValue);
|
|
4486
|
-
return cachedValue;
|
|
4487
|
-
}
|
|
4488
|
-
const result = fn(...args);
|
|
4489
|
-
if (cache.size >= maxSize) {
|
|
4490
|
-
const oldestKey = cache.keys().next().value;
|
|
4491
|
-
if (oldestKey !== undefined) {
|
|
4492
|
-
cache.delete(oldestKey);
|
|
4493
|
-
}
|
|
4494
|
-
}
|
|
4495
|
-
cache.set(key, result);
|
|
4496
|
-
return result;
|
|
4497
|
-
});
|
|
4498
|
-
}
|
|
4499
|
-
function normalizeFieldsArray(fields) {
|
|
4500
|
-
return Array.isArray(fields) ? [...fields] : Object.values(fields);
|
|
4501
|
-
}
|
|
4502
|
-
function getChangedKeys(previous, current) {
|
|
4503
|
-
const changedKeys = new Set();
|
|
4504
|
-
if (!previous && !current)
|
|
4505
|
-
return changedKeys;
|
|
4506
|
-
if (!previous) {
|
|
4507
|
-
return new Set(Object.keys(current ?? {}));
|
|
4508
|
-
}
|
|
4509
|
-
if (!current) {
|
|
4510
|
-
return new Set(Object.keys(previous));
|
|
4511
|
-
}
|
|
4512
|
-
const allKeys = new Set([...Object.keys(previous), ...Object.keys(current)]);
|
|
4513
|
-
for (const key of allKeys) {
|
|
4514
|
-
const prevValue = previous[key];
|
|
4515
|
-
const currValue = current[key];
|
|
4516
|
-
if (!(key in previous) || !(key in current)) {
|
|
4517
|
-
changedKeys.add(key);
|
|
4518
|
-
continue;
|
|
4519
|
-
}
|
|
4520
|
-
if (!isEqual(prevValue, currValue)) {
|
|
4521
|
-
changedKeys.add(key);
|
|
1633
|
+
return itemDefinitions.map((itemDef) => {
|
|
1634
|
+
if (!Array.isArray(itemDef)) {
|
|
1635
|
+
return getFieldDefaultValue(itemDef, registry);
|
|
1636
|
+
}
|
|
1637
|
+
const itemFields = itemDef;
|
|
1638
|
+
let itemValue = {};
|
|
1639
|
+
for (const templateField of itemFields) {
|
|
1640
|
+
const fieldValue = getFieldDefaultValue(templateField, registry);
|
|
1641
|
+
const fieldValueHandling = getFieldValueHandling(templateField.type, registry);
|
|
1642
|
+
if (templateField.type === 'group' && 'key' in templateField && templateField.key) {
|
|
1643
|
+
itemValue[templateField.key] = fieldValue;
|
|
1644
|
+
}
|
|
1645
|
+
else if (templateField.type === 'row' || templateField.type === 'container') {
|
|
1646
|
+
if (fieldValue && typeof fieldValue === 'object') {
|
|
1647
|
+
itemValue = { ...itemValue, ...fieldValue };
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
else if (fieldValueHandling === 'include' && 'key' in templateField && templateField.key) {
|
|
1651
|
+
itemValue[templateField.key] = fieldValue;
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
return itemValue;
|
|
1655
|
+
});
|
|
1656
|
+
}
|
|
1657
|
+
if (field.type === 'input' && 'props' in field) {
|
|
1658
|
+
const props = field.props;
|
|
1659
|
+
if (props?.type === 'number') {
|
|
1660
|
+
return NaN;
|
|
4522
1661
|
}
|
|
4523
1662
|
}
|
|
4524
|
-
return
|
|
4525
|
-
}
|
|
4526
|
-
function assertNever(value) {
|
|
4527
|
-
throw new DynamicFormError(`Unexpected value: ${JSON.stringify(value)}`);
|
|
1663
|
+
return '';
|
|
4528
1664
|
}
|
|
4529
1665
|
|
|
4530
1666
|
function applyFormLevelSchema(path, formLevelSchema) {
|
|
@@ -4958,6 +2094,27 @@ function convertBuiltInToCustomValidator(config) {
|
|
|
4958
2094
|
};
|
|
4959
2095
|
}
|
|
4960
2096
|
|
|
2097
|
+
function inheritContainerExclusion(container, child) {
|
|
2098
|
+
const inheritedHidden = container.hidden === true || child.inheritedExclusionState?.hidden === true;
|
|
2099
|
+
const inheritedDisabled = container.disabled === true || child.inheritedExclusionState?.disabled === true;
|
|
2100
|
+
const inheritedReadonly = container.readonly === true || child.inheritedExclusionState?.readonly === true;
|
|
2101
|
+
const hasInheritedState = inheritedHidden || inheritedDisabled || inheritedReadonly;
|
|
2102
|
+
const containerExcludeConfig = container.excludeValueIfHidden !== undefined ||
|
|
2103
|
+
container.excludeValueIfDisabled !== undefined ||
|
|
2104
|
+
container.excludeValueIfReadonly !== undefined;
|
|
2105
|
+
if (!hasInheritedState && !containerExcludeConfig) {
|
|
2106
|
+
return child;
|
|
2107
|
+
}
|
|
2108
|
+
return {
|
|
2109
|
+
...child,
|
|
2110
|
+
...(hasInheritedState
|
|
2111
|
+
? { inheritedExclusionState: { hidden: inheritedHidden, disabled: inheritedDisabled, readonly: inheritedReadonly } }
|
|
2112
|
+
: {}),
|
|
2113
|
+
excludeValueIfHidden: child.excludeValueIfHidden ?? container.excludeValueIfHidden,
|
|
2114
|
+
excludeValueIfDisabled: child.excludeValueIfDisabled ?? container.excludeValueIfDisabled,
|
|
2115
|
+
excludeValueIfReadonly: child.excludeValueIfReadonly ?? container.excludeValueIfReadonly,
|
|
2116
|
+
};
|
|
2117
|
+
}
|
|
4961
2118
|
function flattenFields(fields, registry, options = {}) {
|
|
4962
2119
|
const result = [];
|
|
4963
2120
|
let autoKeyCounter = 0;
|
|
@@ -4978,7 +2135,7 @@ function flattenFields(fields, registry, options = {}) {
|
|
|
4978
2135
|
if (field.fields) {
|
|
4979
2136
|
const fields = field.fields;
|
|
4980
2137
|
const flattenedChildren = flattenFields(normalizeFieldsArray(fields), registry, options);
|
|
4981
|
-
|
|
2138
|
+
result.push(...flattenedChildren.map((child) => inheritContainerExclusion(field, child)));
|
|
4982
2139
|
}
|
|
4983
2140
|
}
|
|
4984
2141
|
else if (isGroupField(field)) {
|
|
@@ -5063,64 +2220,6 @@ const CONTAINER_FIELD_PROCESSORS = new InjectionToken('CONTAINER_FIELD_PROCESSOR
|
|
|
5063
2220
|
factory: () => createContainerFieldProcessors(),
|
|
5064
2221
|
});
|
|
5065
2222
|
|
|
5066
|
-
function getGridClassString(fieldDef) {
|
|
5067
|
-
const col = fieldDef.col;
|
|
5068
|
-
if (typeof col === 'number' && Number.isInteger(col) && col > 0 && col <= 12) {
|
|
5069
|
-
return `df-col-${col}`;
|
|
5070
|
-
}
|
|
5071
|
-
return '';
|
|
5072
|
-
}
|
|
5073
|
-
function buildClassName(fieldDef) {
|
|
5074
|
-
const gridClass = getGridClassString(fieldDef);
|
|
5075
|
-
const userClass = fieldDef.className;
|
|
5076
|
-
const classes = [];
|
|
5077
|
-
if (gridClass) {
|
|
5078
|
-
classes.push(gridClass);
|
|
5079
|
-
}
|
|
5080
|
-
if (userClass) {
|
|
5081
|
-
classes.push(userClass);
|
|
5082
|
-
}
|
|
5083
|
-
return classes.length > 0 ? classes.join(' ') : undefined;
|
|
5084
|
-
}
|
|
5085
|
-
|
|
5086
|
-
function buildBaseInputs(fieldDef, defaultProps) {
|
|
5087
|
-
const { key, label, className, tabIndex, props, meta, addons } = fieldDef;
|
|
5088
|
-
const inputs = {};
|
|
5089
|
-
inputs['key'] = key;
|
|
5090
|
-
if (label !== undefined) {
|
|
5091
|
-
inputs['label'] = label;
|
|
5092
|
-
}
|
|
5093
|
-
const gridClassString = getGridClassString(fieldDef);
|
|
5094
|
-
const allClasses = [];
|
|
5095
|
-
if (gridClassString) {
|
|
5096
|
-
allClasses.push(gridClassString);
|
|
5097
|
-
}
|
|
5098
|
-
if (className) {
|
|
5099
|
-
allClasses.push(className);
|
|
5100
|
-
}
|
|
5101
|
-
if (allClasses.length > 0) {
|
|
5102
|
-
inputs['className'] = allClasses.join(' ');
|
|
5103
|
-
}
|
|
5104
|
-
if (tabIndex !== undefined) {
|
|
5105
|
-
inputs['tabIndex'] = tabIndex;
|
|
5106
|
-
}
|
|
5107
|
-
const fieldProps = props;
|
|
5108
|
-
const mergedProps = fieldProps !== undefined || defaultProps !== undefined ? { ...(defaultProps ?? {}), ...(fieldProps ?? {}) } : undefined;
|
|
5109
|
-
if (mergedProps !== undefined) {
|
|
5110
|
-
inputs['props'] = mergedProps;
|
|
5111
|
-
}
|
|
5112
|
-
if (meta !== undefined) {
|
|
5113
|
-
inputs['meta'] = meta;
|
|
5114
|
-
}
|
|
5115
|
-
if (addons !== undefined) {
|
|
5116
|
-
inputs['addons'] = addons;
|
|
5117
|
-
}
|
|
5118
|
-
return inputs;
|
|
5119
|
-
}
|
|
5120
|
-
function baseFieldMapper(fieldDef) {
|
|
5121
|
-
return computed(() => buildBaseInputs(fieldDef));
|
|
5122
|
-
}
|
|
5123
|
-
|
|
5124
2223
|
const EMPTY_OVERRIDES = Object.freeze({});
|
|
5125
2224
|
function createPropertyOverrideStore() {
|
|
5126
2225
|
const store = new Map();
|
|
@@ -5452,7 +2551,7 @@ function createFieldResolutionPipe(getContext) {
|
|
|
5452
2551
|
}
|
|
5453
2552
|
const context = getContext();
|
|
5454
2553
|
return forkJoin(fields.map((f) => resolveField(f, context)));
|
|
5455
|
-
}), map((fields) => fields.filter((f) => f !== undefined)), scan
|
|
2554
|
+
}), map((fields) => fields.filter((f) => f !== undefined)), scan(reconcileFields, []));
|
|
5456
2555
|
}
|
|
5457
2556
|
|
|
5458
2557
|
class FormModeValidator {
|
|
@@ -5565,6 +2664,9 @@ function collectFieldData(fields, data, registry, pathPrefix, collectKeys = true
|
|
|
5565
2664
|
for (const field of fields) {
|
|
5566
2665
|
const valueHandling = field.type ? getFieldValueHandling(field.type, registry) : 'include';
|
|
5567
2666
|
const participatesInValue = valueHandling !== 'exclude' && valueHandling !== 'flatten';
|
|
2667
|
+
if (field.key && field.key.includes('.') && participatesInValue) {
|
|
2668
|
+
data.dottedKeys.push(field.key);
|
|
2669
|
+
}
|
|
5568
2670
|
if (collectKeys && field.key && participatesInValue) {
|
|
5569
2671
|
const scopedKey = pathPrefix ? `${pathPrefix}.${field.key}` : field.key;
|
|
5570
2672
|
data.keys.push(scopedKey);
|
|
@@ -5661,8 +2763,16 @@ function validateFormConfig(fields, registry, logger) {
|
|
|
5661
2763
|
domIds: [],
|
|
5662
2764
|
types: new Set(),
|
|
5663
2765
|
regexErrors: [],
|
|
2766
|
+
dottedKeys: [],
|
|
5664
2767
|
};
|
|
5665
2768
|
collectFieldData(fields, data, registry, '');
|
|
2769
|
+
if (data.dottedKeys.length > 0) {
|
|
2770
|
+
const list = Array.from(new Set(data.dottedKeys))
|
|
2771
|
+
.map((k) => `'${k}'`)
|
|
2772
|
+
.join(', ');
|
|
2773
|
+
throw new DynamicFormError(`Field key(s) contain a dot: ${list}. Dotted keys are not supported — express nesting with 'group' fields ` +
|
|
2774
|
+
`(e.g. { type: 'group', key: 'address', fields: [{ type: 'input', key: 'city' }] }).`);
|
|
2775
|
+
}
|
|
5666
2776
|
validateNoDuplicateKeys(data.keys);
|
|
5667
2777
|
validateNoDomIdCollisions(data.domIds);
|
|
5668
2778
|
validateFieldTypesRegistered(data.types, registry, logger);
|
|
@@ -5864,11 +2974,6 @@ function formatAddonWarning(w) {
|
|
|
5864
2974
|
}
|
|
5865
2975
|
}
|
|
5866
2976
|
|
|
5867
|
-
const ADDON_KIND_REGISTRY = new InjectionToken('ADDON_KIND_REGISTRY', {
|
|
5868
|
-
providedIn: 'root',
|
|
5869
|
-
factory: () => new Map(),
|
|
5870
|
-
});
|
|
5871
|
-
|
|
5872
2977
|
const VALUE_EXCLUSION_DEFAULTS = new InjectionToken('VALUE_EXCLUSION_DEFAULTS', {
|
|
5873
2978
|
providedIn: 'root',
|
|
5874
2979
|
factory: () => ({
|
|
@@ -5887,15 +2992,16 @@ function resolveExclusionConfig(global, form, field) {
|
|
|
5887
2992
|
}
|
|
5888
2993
|
function shouldExcludeField(field, fieldState, config) {
|
|
5889
2994
|
const state = fieldState();
|
|
5890
|
-
const
|
|
2995
|
+
const inherited = field.inheritedExclusionState;
|
|
2996
|
+
const isHidden = state.hidden() || field.hidden === true || inherited?.hidden === true;
|
|
5891
2997
|
if (config.excludeValueIfHidden && isHidden) {
|
|
5892
2998
|
return true;
|
|
5893
2999
|
}
|
|
5894
|
-
const isDisabled = state.disabled() || field.disabled === true;
|
|
3000
|
+
const isDisabled = state.disabled() || field.disabled === true || inherited?.disabled === true;
|
|
5895
3001
|
if (config.excludeValueIfDisabled && isDisabled) {
|
|
5896
3002
|
return true;
|
|
5897
3003
|
}
|
|
5898
|
-
const isReadonly = state.readonly() || field.readonly === true;
|
|
3004
|
+
const isReadonly = state.readonly() || field.readonly === true || inherited?.readonly === true;
|
|
5899
3005
|
if (config.excludeValueIfReadonly && isReadonly) {
|
|
5900
3006
|
return true;
|
|
5901
3007
|
}
|
|
@@ -5920,9 +3026,10 @@ function filterFormValue(rawValue, schemaFields, formTree, registry, globalDefau
|
|
|
5920
3026
|
const resolvedConfig = resolveExclusionConfig(globalDefaults, formOptions, fieldExclusion);
|
|
5921
3027
|
const fieldState = formTree[key];
|
|
5922
3028
|
if (!fieldState || typeof fieldState !== 'function') {
|
|
5923
|
-
const
|
|
5924
|
-
|
|
5925
|
-
(resolvedConfig.
|
|
3029
|
+
const inherited = field.inheritedExclusionState;
|
|
3030
|
+
const staticallyExcluded = (resolvedConfig.excludeValueIfHidden && (field.hidden === true || inherited?.hidden === true)) ||
|
|
3031
|
+
(resolvedConfig.excludeValueIfDisabled && (field.disabled === true || inherited?.disabled === true)) ||
|
|
3032
|
+
(resolvedConfig.excludeValueIfReadonly && (field.readonly === true || inherited?.readonly === true));
|
|
5926
3033
|
if (!staticallyExcluded) {
|
|
5927
3034
|
result[key] = rawValue[key];
|
|
5928
3035
|
}
|
|
@@ -6315,7 +3422,16 @@ class FormStateMachine {
|
|
|
6315
3422
|
this.config.logger.warn('RestoreValues effect: pendingFormSetup was not set — falling back to recomputing.');
|
|
6316
3423
|
}
|
|
6317
3424
|
const formSetup = state.pendingFormSetup ?? this.config.createFormSetup(state.pendingConfig);
|
|
6318
|
-
|
|
3425
|
+
const oldTypeByKey = new Map();
|
|
3426
|
+
for (const f of state.currentFormSetup.schemaFields) {
|
|
3427
|
+
if (f.key !== undefined)
|
|
3428
|
+
oldTypeByKey.set(f.key, f.type);
|
|
3429
|
+
}
|
|
3430
|
+
const validKeys = new Set();
|
|
3431
|
+
for (const f of formSetup.schemaFields) {
|
|
3432
|
+
if (f.key !== undefined && oldTypeByKey.get(f.key) === f.type)
|
|
3433
|
+
validKeys.add(f.key);
|
|
3434
|
+
}
|
|
6319
3435
|
this.config.restoreValue(effect.values, validKeys);
|
|
6320
3436
|
this.dispatch({ type: Action.RestoreComplete });
|
|
6321
3437
|
});
|
|
@@ -6641,7 +3757,7 @@ class FormStateManager {
|
|
|
6641
3757
|
reset$;
|
|
6642
3758
|
cleared$;
|
|
6643
3759
|
get ready$() {
|
|
6644
|
-
return this.machine.state$.pipe(filter(isReadyState), take
|
|
3760
|
+
return this.machine.state$.pipe(filter(isReadyState), take(1), map(() => undefined));
|
|
6645
3761
|
}
|
|
6646
3762
|
constructor() {
|
|
6647
3763
|
this.resolvedFields = derivedFromDeferred(this.fieldsSource, pipe(switchMap((fields) => {
|
|
@@ -6698,7 +3814,7 @@ class FormStateManager {
|
|
|
6698
3814
|
}
|
|
6699
3815
|
return results;
|
|
6700
3816
|
}));
|
|
6701
|
-
}), map((fields) => fields.filter((f) => f !== undefined)), scan
|
|
3817
|
+
}), map((fields) => fields.filter((f) => f !== undefined)), scan(reconcileFields, [])), { initialValue: [], injector: this.injector });
|
|
6702
3818
|
this.isFieldPipelineSettled = computed(() => {
|
|
6703
3819
|
return this.fieldsSource().length === this.resolvedFields().length;
|
|
6704
3820
|
}, ...(ngDevMode ? [{ debugName: "isFieldPipelineSettled" }] : /* istanbul ignore next */ []));
|
|
@@ -6741,9 +3857,14 @@ class FormStateManager {
|
|
|
6741
3857
|
filtered[key] = val;
|
|
6742
3858
|
}
|
|
6743
3859
|
}
|
|
6744
|
-
|
|
6745
|
-
|
|
6746
|
-
|
|
3860
|
+
this.deps.value.update((current) => {
|
|
3861
|
+
const next = { ...current, ...filtered };
|
|
3862
|
+
for (const key of Object.keys(values)) {
|
|
3863
|
+
if (!validKeys.has(key))
|
|
3864
|
+
delete next[key];
|
|
3865
|
+
}
|
|
3866
|
+
return next;
|
|
3867
|
+
});
|
|
6747
3868
|
},
|
|
6748
3869
|
onTransition: (transition) => {
|
|
6749
3870
|
this.logger.debug('State transition:', transition.from.type, '→', transition.to.type, transition.action.type);
|
|
@@ -7010,10 +4131,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
7010
4131
|
type: Injectable
|
|
7011
4132
|
}], ctorParameters: () => [] });
|
|
7012
4133
|
|
|
7013
|
-
function createWarningTracker() {
|
|
7014
|
-
return { warnedKeys: new Set() };
|
|
7015
|
-
}
|
|
7016
|
-
|
|
7017
4134
|
const DERIVATION_WARNING_TRACKER = new InjectionToken('DerivationWarningTracker', {
|
|
7018
4135
|
providedIn: null,
|
|
7019
4136
|
factory: createWarningTracker,
|
|
@@ -7126,6 +4243,12 @@ function parseDerivationKey(key) {
|
|
|
7126
4243
|
return { fieldKey: key };
|
|
7127
4244
|
}
|
|
7128
4245
|
|
|
4246
|
+
function isThenableOrObservable(value) {
|
|
4247
|
+
if (value === null || typeof value !== 'object')
|
|
4248
|
+
return false;
|
|
4249
|
+
const candidate = value;
|
|
4250
|
+
return typeof candidate.then === 'function' || typeof candidate.subscribe === 'function';
|
|
4251
|
+
}
|
|
7129
4252
|
function computeValueFromEntry(entry, evalContext, options) {
|
|
7130
4253
|
if (entry.value !== undefined) {
|
|
7131
4254
|
return entry.value;
|
|
@@ -7142,7 +4265,13 @@ function computeValueFromEntry(entry, evalContext, options) {
|
|
|
7142
4265
|
const kind = options.functionKind ?? 'Derivation function';
|
|
7143
4266
|
throw new DynamicFormError(`${kind} '${entry.functionName}' not found in customFnConfig.derivations`);
|
|
7144
4267
|
}
|
|
7145
|
-
|
|
4268
|
+
const result = fn(evalContext);
|
|
4269
|
+
if (isThenableOrObservable(result)) {
|
|
4270
|
+
evalContext.logger.warn(`Derivation for '${options.subject}' returned a Promise or Observable. ` +
|
|
4271
|
+
`Synchronous derivations must return a value; use 'asyncDerivations' or an HTTP derivation for asynchronous logic. Skipping.`);
|
|
4272
|
+
return undefined;
|
|
4273
|
+
}
|
|
4274
|
+
return result;
|
|
7146
4275
|
}
|
|
7147
4276
|
throw new DynamicFormError(`Derivation for ${options.subject} has no value source. Specify 'value', 'expression', 'functionName', or 'fn'.`);
|
|
7148
4277
|
}
|
|
@@ -8252,8 +5381,19 @@ function createHttpDerivationStream(entry, formValue$, context) {
|
|
|
8252
5381
|
subscriber.complete();
|
|
8253
5382
|
return;
|
|
8254
5383
|
}
|
|
8255
|
-
|
|
8256
|
-
|
|
5384
|
+
const currentForm = untracked(() => context.form());
|
|
5385
|
+
if (entry.stopOnUserOverride && readFieldDirty(currentForm, entry.fieldKey)) {
|
|
5386
|
+
const derivationLogger = untracked(() => context.derivationLogger());
|
|
5387
|
+
derivationLogger.evaluation({
|
|
5388
|
+
debugName: entry.debugName,
|
|
5389
|
+
fieldKey: entry.fieldKey,
|
|
5390
|
+
result: 'skipped',
|
|
5391
|
+
skipReason: 'user-override',
|
|
5392
|
+
});
|
|
5393
|
+
subscriber.complete();
|
|
5394
|
+
return;
|
|
5395
|
+
}
|
|
5396
|
+
applyValueToForm(entry.fieldKey, newValue, currentForm, context.logger, context.warningTracker);
|
|
8257
5397
|
const derivationLogger = untracked(() => context.derivationLogger());
|
|
8258
5398
|
derivationLogger.evaluation({
|
|
8259
5399
|
debugName: entry.debugName,
|
|
@@ -8391,8 +5531,19 @@ function createAsyncDerivationStream(entry, formValue$, context) {
|
|
|
8391
5531
|
subscriber.complete();
|
|
8392
5532
|
return;
|
|
8393
5533
|
}
|
|
8394
|
-
|
|
8395
|
-
|
|
5534
|
+
const currentForm = untracked(() => context.form());
|
|
5535
|
+
if (entry.stopOnUserOverride && readFieldDirty(currentForm, entry.fieldKey)) {
|
|
5536
|
+
const derivationLogger = untracked(() => context.derivationLogger());
|
|
5537
|
+
derivationLogger.evaluation({
|
|
5538
|
+
debugName: entry.debugName,
|
|
5539
|
+
fieldKey: entry.fieldKey,
|
|
5540
|
+
result: 'skipped',
|
|
5541
|
+
skipReason: 'user-override',
|
|
5542
|
+
});
|
|
5543
|
+
subscriber.complete();
|
|
5544
|
+
return;
|
|
5545
|
+
}
|
|
5546
|
+
applyValueToForm(entry.fieldKey, newValue, currentForm, context.logger, context.warningTracker);
|
|
8396
5547
|
const derivationLogger = untracked(() => context.derivationLogger());
|
|
8397
5548
|
derivationLogger.evaluation({
|
|
8398
5549
|
debugName: entry.debugName,
|
|
@@ -9562,8 +6713,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
9562
6713
|
type: Injectable
|
|
9563
6714
|
}] });
|
|
9564
6715
|
|
|
9565
|
-
const DF_FIELD_TEMPLATES = new InjectionToken('DF_FIELD_TEMPLATES');
|
|
9566
|
-
|
|
9567
6716
|
class DynamicForm {
|
|
9568
6717
|
config = input.required({ ...(ngDevMode ? { debugName: "config" } : /* istanbul ignore next */ {}), alias: 'dynamic-form' });
|
|
9569
6718
|
formOptions = input(undefined, ...(ngDevMode ? [{ debugName: "formOptions" }] : /* istanbul ignore next */ []));
|
|
@@ -9848,240 +6997,6 @@ const BUILT_IN_ADDON_KINDS = [
|
|
|
9848
6997
|
},
|
|
9849
6998
|
];
|
|
9850
6999
|
|
|
9851
|
-
const noOpLogger = {
|
|
9852
|
-
debug: () => { },
|
|
9853
|
-
info: () => { },
|
|
9854
|
-
warn: () => { },
|
|
9855
|
-
error: () => { },
|
|
9856
|
-
};
|
|
9857
|
-
const DEFAULT_SUBMIT_BUTTON_OPTIONS = {
|
|
9858
|
-
disableWhenInvalid: true,
|
|
9859
|
-
disableWhileSubmitting: true,
|
|
9860
|
-
};
|
|
9861
|
-
const DEFAULT_NEXT_BUTTON_OPTIONS = {
|
|
9862
|
-
disableWhenPageInvalid: true,
|
|
9863
|
-
disableWhileSubmitting: true,
|
|
9864
|
-
};
|
|
9865
|
-
function evaluateFormStateCondition(condition, ctx) {
|
|
9866
|
-
const form = ctx.form();
|
|
9867
|
-
switch (condition) {
|
|
9868
|
-
case 'formInvalid':
|
|
9869
|
-
return !form.valid();
|
|
9870
|
-
case 'formSubmitting':
|
|
9871
|
-
return form.submitting();
|
|
9872
|
-
case 'pageInvalid':
|
|
9873
|
-
return ctx.currentPageValid ? !ctx.currentPageValid() : false;
|
|
9874
|
-
default:
|
|
9875
|
-
return false;
|
|
9876
|
-
}
|
|
9877
|
-
}
|
|
9878
|
-
function evaluateLogicCondition(condition, ctx) {
|
|
9879
|
-
if (typeof condition === 'boolean') {
|
|
9880
|
-
return condition;
|
|
9881
|
-
}
|
|
9882
|
-
if (isFormStateCondition(condition)) {
|
|
9883
|
-
return evaluateFormStateCondition(condition, ctx);
|
|
9884
|
-
}
|
|
9885
|
-
const formValue = (ctx.formValue ?? ctx.form().value());
|
|
9886
|
-
const evaluationContext = {
|
|
9887
|
-
fieldValue: undefined,
|
|
9888
|
-
formValue,
|
|
9889
|
-
fieldPath: '',
|
|
9890
|
-
logger: ctx.logger ?? noOpLogger,
|
|
9891
|
-
};
|
|
9892
|
-
return evaluateCondition(condition, evaluationContext);
|
|
9893
|
-
}
|
|
9894
|
-
function hasFieldLevelDisabledCondition(fieldLogic, ctx) {
|
|
9895
|
-
if (!fieldLogic || fieldLogic.length === 0) {
|
|
9896
|
-
return false;
|
|
9897
|
-
}
|
|
9898
|
-
return fieldLogic.filter((logic) => logic.type === 'disabled').some((logic) => evaluateLogicCondition(logic.condition, ctx));
|
|
9899
|
-
}
|
|
9900
|
-
function hasCustomLogicOfType(fieldLogic, logicType) {
|
|
9901
|
-
if (!fieldLogic || fieldLogic.length === 0) {
|
|
9902
|
-
return false;
|
|
9903
|
-
}
|
|
9904
|
-
return fieldLogic.some((logic) => logic.type === logicType);
|
|
9905
|
-
}
|
|
9906
|
-
function evaluateLogicOfType(fieldLogic, logicType, ctx) {
|
|
9907
|
-
if (!fieldLogic || fieldLogic.length === 0) {
|
|
9908
|
-
return false;
|
|
9909
|
-
}
|
|
9910
|
-
const buttonCtx = {
|
|
9911
|
-
form: ctx.form,
|
|
9912
|
-
formValue: ctx.formValue,
|
|
9913
|
-
logger: ctx.logger,
|
|
9914
|
-
};
|
|
9915
|
-
return fieldLogic.filter((logic) => logic.type === logicType).some((logic) => evaluateLogicCondition(logic.condition, buttonCtx));
|
|
9916
|
-
}
|
|
9917
|
-
function resolveSubmitButtonDisabled(ctx) {
|
|
9918
|
-
return computed(() => {
|
|
9919
|
-
if (ctx.explicitlyDisabled) {
|
|
9920
|
-
return true;
|
|
9921
|
-
}
|
|
9922
|
-
if (hasCustomLogicOfType(ctx.fieldLogic, 'disabled')) {
|
|
9923
|
-
return hasFieldLevelDisabledCondition(ctx.fieldLogic, ctx);
|
|
9924
|
-
}
|
|
9925
|
-
const options = {
|
|
9926
|
-
...DEFAULT_SUBMIT_BUTTON_OPTIONS,
|
|
9927
|
-
...ctx.formOptions?.submitButton,
|
|
9928
|
-
};
|
|
9929
|
-
const form = ctx.form();
|
|
9930
|
-
if (options.disableWhenInvalid && !form.valid()) {
|
|
9931
|
-
return true;
|
|
9932
|
-
}
|
|
9933
|
-
if (options.disableWhileSubmitting && form.submitting()) {
|
|
9934
|
-
return true;
|
|
9935
|
-
}
|
|
9936
|
-
return false;
|
|
9937
|
-
});
|
|
9938
|
-
}
|
|
9939
|
-
function resolveNextButtonDisabled(ctx) {
|
|
9940
|
-
return computed(() => {
|
|
9941
|
-
if (ctx.explicitlyDisabled) {
|
|
9942
|
-
return true;
|
|
9943
|
-
}
|
|
9944
|
-
if (hasCustomLogicOfType(ctx.fieldLogic, 'disabled')) {
|
|
9945
|
-
return hasFieldLevelDisabledCondition(ctx.fieldLogic, ctx);
|
|
9946
|
-
}
|
|
9947
|
-
const options = {
|
|
9948
|
-
...DEFAULT_NEXT_BUTTON_OPTIONS,
|
|
9949
|
-
...ctx.formOptions?.nextButton,
|
|
9950
|
-
};
|
|
9951
|
-
const form = ctx.form();
|
|
9952
|
-
if (options.disableWhenPageInvalid && ctx.currentPageValid && !ctx.currentPageValid()) {
|
|
9953
|
-
return true;
|
|
9954
|
-
}
|
|
9955
|
-
if (options.disableWhileSubmitting && form.submitting()) {
|
|
9956
|
-
return true;
|
|
9957
|
-
}
|
|
9958
|
-
return false;
|
|
9959
|
-
});
|
|
9960
|
-
}
|
|
9961
|
-
function evaluateNonFieldHidden(ctx) {
|
|
9962
|
-
if (ctx.explicitValue) {
|
|
9963
|
-
return true;
|
|
9964
|
-
}
|
|
9965
|
-
if (hasCustomLogicOfType(ctx.fieldLogic, 'hidden')) {
|
|
9966
|
-
return evaluateLogicOfType(ctx.fieldLogic, 'hidden', {
|
|
9967
|
-
form: ctx.form,
|
|
9968
|
-
formValue: ctx.formValue,
|
|
9969
|
-
logger: ctx.logger,
|
|
9970
|
-
});
|
|
9971
|
-
}
|
|
9972
|
-
return false;
|
|
9973
|
-
}
|
|
9974
|
-
function resolveNonFieldHidden(ctx) {
|
|
9975
|
-
return computed(() => evaluateNonFieldHidden(ctx));
|
|
9976
|
-
}
|
|
9977
|
-
function evaluateNonFieldDisabled(ctx) {
|
|
9978
|
-
if (ctx.explicitValue) {
|
|
9979
|
-
return true;
|
|
9980
|
-
}
|
|
9981
|
-
if (hasCustomLogicOfType(ctx.fieldLogic, 'disabled')) {
|
|
9982
|
-
return evaluateLogicOfType(ctx.fieldLogic, 'disabled', {
|
|
9983
|
-
form: ctx.form,
|
|
9984
|
-
formValue: ctx.formValue,
|
|
9985
|
-
logger: ctx.logger,
|
|
9986
|
-
});
|
|
9987
|
-
}
|
|
9988
|
-
return false;
|
|
9989
|
-
}
|
|
9990
|
-
function resolveNonFieldDisabled(ctx) {
|
|
9991
|
-
return computed(() => evaluateNonFieldDisabled(ctx));
|
|
9992
|
-
}
|
|
9993
|
-
|
|
9994
|
-
function applyHiddenLogic(inputs, fieldDef, rootFormRegistry) {
|
|
9995
|
-
const rootForm = rootFormRegistry.rootForm();
|
|
9996
|
-
if (rootForm && (fieldDef.hidden === true || fieldDef.logic?.some((l) => l.type === 'hidden'))) {
|
|
9997
|
-
inputs['hidden'] = evaluateNonFieldHidden({
|
|
9998
|
-
form: rootForm,
|
|
9999
|
-
fieldLogic: fieldDef.logic,
|
|
10000
|
-
explicitValue: fieldDef.hidden,
|
|
10001
|
-
formValue: rootFormRegistry.formValue(),
|
|
10002
|
-
});
|
|
10003
|
-
}
|
|
10004
|
-
}
|
|
10005
|
-
|
|
10006
|
-
function arrayFieldMapper(fieldDef) {
|
|
10007
|
-
const rootFormRegistry = inject(RootFormRegistryService);
|
|
10008
|
-
const className = buildClassName(fieldDef);
|
|
10009
|
-
return computed(() => {
|
|
10010
|
-
const inputs = {
|
|
10011
|
-
key: fieldDef.key,
|
|
10012
|
-
field: fieldDef,
|
|
10013
|
-
...(className !== undefined && { className }),
|
|
10014
|
-
};
|
|
10015
|
-
applyHiddenLogic(inputs, fieldDef, rootFormRegistry);
|
|
10016
|
-
return inputs;
|
|
10017
|
-
});
|
|
10018
|
-
}
|
|
10019
|
-
|
|
10020
|
-
function groupFieldMapper(fieldDef) {
|
|
10021
|
-
const rootFormRegistry = inject(RootFormRegistryService);
|
|
10022
|
-
const className = buildClassName(fieldDef);
|
|
10023
|
-
return computed(() => {
|
|
10024
|
-
const inputs = {
|
|
10025
|
-
key: fieldDef.key,
|
|
10026
|
-
field: fieldDef,
|
|
10027
|
-
...(className !== undefined && { className }),
|
|
10028
|
-
};
|
|
10029
|
-
applyHiddenLogic(inputs, fieldDef, rootFormRegistry);
|
|
10030
|
-
return inputs;
|
|
10031
|
-
});
|
|
10032
|
-
}
|
|
10033
|
-
|
|
10034
|
-
const ROW_WRAPPERS = [{ type: 'row' }];
|
|
10035
|
-
function rowFieldMapper(fieldDef) {
|
|
10036
|
-
const rootFormRegistry = inject(RootFormRegistryService);
|
|
10037
|
-
const className = buildClassName(fieldDef);
|
|
10038
|
-
return computed(() => {
|
|
10039
|
-
const containerField = { ...fieldDef, type: 'container', wrappers: ROW_WRAPPERS };
|
|
10040
|
-
const inputs = {
|
|
10041
|
-
key: fieldDef.key,
|
|
10042
|
-
field: containerField,
|
|
10043
|
-
...(className !== undefined && { className }),
|
|
10044
|
-
};
|
|
10045
|
-
applyHiddenLogic(inputs, fieldDef, rootFormRegistry);
|
|
10046
|
-
return inputs;
|
|
10047
|
-
});
|
|
10048
|
-
}
|
|
10049
|
-
|
|
10050
|
-
function pageFieldMapper(fieldDef) {
|
|
10051
|
-
const className = buildClassName(fieldDef);
|
|
10052
|
-
return computed(() => ({
|
|
10053
|
-
key: fieldDef.key,
|
|
10054
|
-
field: fieldDef,
|
|
10055
|
-
...(className !== undefined && { className }),
|
|
10056
|
-
}));
|
|
10057
|
-
}
|
|
10058
|
-
|
|
10059
|
-
function textFieldMapper(fieldDef) {
|
|
10060
|
-
const rootFormRegistry = inject(RootFormRegistryService);
|
|
10061
|
-
const defaultProps = inject(DEFAULT_PROPS);
|
|
10062
|
-
return computed(() => {
|
|
10063
|
-
const baseInputs = buildBaseInputs(fieldDef, defaultProps());
|
|
10064
|
-
const inputs = { ...baseInputs };
|
|
10065
|
-
applyHiddenLogic(inputs, fieldDef, rootFormRegistry);
|
|
10066
|
-
return inputs;
|
|
10067
|
-
});
|
|
10068
|
-
}
|
|
10069
|
-
|
|
10070
|
-
function containerFieldMapper(fieldDef) {
|
|
10071
|
-
const rootFormRegistry = inject(RootFormRegistryService);
|
|
10072
|
-
const className = buildClassName(fieldDef);
|
|
10073
|
-
return computed(() => {
|
|
10074
|
-
const containerField = { ...fieldDef, wrappers: undefined, skipAutoWrappers: undefined, skipDefaultWrappers: undefined };
|
|
10075
|
-
const inputs = {
|
|
10076
|
-
key: fieldDef.key,
|
|
10077
|
-
field: containerField,
|
|
10078
|
-
...(className !== undefined && { className }),
|
|
10079
|
-
};
|
|
10080
|
-
applyHiddenLogic(inputs, fieldDef, rootFormRegistry);
|
|
10081
|
-
return inputs;
|
|
10082
|
-
});
|
|
10083
|
-
}
|
|
10084
|
-
|
|
10085
7000
|
const CONTAINER_FIELD_TYPES_BASE = {
|
|
10086
7001
|
renderReadyWhen: ['field'],
|
|
10087
7002
|
};
|
|
@@ -10091,35 +7006,35 @@ const DISPLAY_FIELD_TYPES_BASE = {
|
|
|
10091
7006
|
const BUILT_IN_FIELDS = [
|
|
10092
7007
|
{
|
|
10093
7008
|
name: 'row',
|
|
10094
|
-
loadComponent: () => import('./ng-forge-dynamic-forms-container-field.component-
|
|
7009
|
+
loadComponent: () => import('./ng-forge-dynamic-forms-container-field.component-bA-8ICHF.mjs'),
|
|
10095
7010
|
mapper: rowFieldMapper,
|
|
10096
7011
|
valueHandling: 'flatten',
|
|
10097
7012
|
...CONTAINER_FIELD_TYPES_BASE,
|
|
10098
7013
|
},
|
|
10099
7014
|
{
|
|
10100
7015
|
name: 'group',
|
|
10101
|
-
loadComponent: () => import('./ng-forge-dynamic-forms-group-field.component-
|
|
7016
|
+
loadComponent: () => import('./ng-forge-dynamic-forms-group-field.component-D0_nmXf2.mjs'),
|
|
10102
7017
|
mapper: groupFieldMapper,
|
|
10103
7018
|
valueHandling: 'include',
|
|
10104
7019
|
...CONTAINER_FIELD_TYPES_BASE,
|
|
10105
7020
|
},
|
|
10106
7021
|
{
|
|
10107
7022
|
name: 'array',
|
|
10108
|
-
loadComponent: () => import('./ng-forge-dynamic-forms-array-field.component-
|
|
7023
|
+
loadComponent: () => import('./ng-forge-dynamic-forms-array-field.component-BfRH3TBm.mjs'),
|
|
10109
7024
|
mapper: arrayFieldMapper,
|
|
10110
7025
|
valueHandling: 'include',
|
|
10111
7026
|
...CONTAINER_FIELD_TYPES_BASE,
|
|
10112
7027
|
},
|
|
10113
7028
|
{
|
|
10114
7029
|
name: 'page',
|
|
10115
|
-
loadComponent: () => import('./ng-forge-dynamic-forms-page-field.component-
|
|
7030
|
+
loadComponent: () => import('./ng-forge-dynamic-forms-page-field.component-BJRNnoz-.mjs'),
|
|
10116
7031
|
mapper: pageFieldMapper,
|
|
10117
7032
|
valueHandling: 'flatten',
|
|
10118
7033
|
...CONTAINER_FIELD_TYPES_BASE,
|
|
10119
7034
|
},
|
|
10120
7035
|
{
|
|
10121
7036
|
name: 'text',
|
|
10122
|
-
loadComponent: () => import('./ng-forge-dynamic-forms-text-field.component-
|
|
7037
|
+
loadComponent: () => import('./ng-forge-dynamic-forms-text-field.component-DABkT0O7.mjs'),
|
|
10123
7038
|
mapper: textFieldMapper,
|
|
10124
7039
|
valueHandling: 'exclude',
|
|
10125
7040
|
...DISPLAY_FIELD_TYPES_BASE,
|
|
@@ -10130,7 +7045,7 @@ const BUILT_IN_FIELDS = [
|
|
|
10130
7045
|
},
|
|
10131
7046
|
{
|
|
10132
7047
|
name: 'container',
|
|
10133
|
-
loadComponent: () => import('./ng-forge-dynamic-forms-container-field.component-
|
|
7048
|
+
loadComponent: () => import('./ng-forge-dynamic-forms-container-field.component-bA-8ICHF.mjs'),
|
|
10134
7049
|
mapper: containerFieldMapper,
|
|
10135
7050
|
valueHandling: 'flatten',
|
|
10136
7051
|
...CONTAINER_FIELD_TYPES_BASE,
|
|
@@ -10139,7 +7054,7 @@ const BUILT_IN_FIELDS = [
|
|
|
10139
7054
|
const BUILT_IN_WRAPPERS = [
|
|
10140
7055
|
{
|
|
10141
7056
|
wrapperName: 'css',
|
|
10142
|
-
loadComponent: () => import('./ng-forge-dynamic-forms-css-wrapper.component-
|
|
7057
|
+
loadComponent: () => import('./ng-forge-dynamic-forms-css-wrapper.component-DjhcONFh.mjs'),
|
|
10143
7058
|
},
|
|
10144
7059
|
{
|
|
10145
7060
|
wrapperName: 'row',
|
|
@@ -10255,10 +7170,6 @@ function withEventFormValue() {
|
|
|
10255
7170
|
return createFeature('event-form-value', [{ provide: EMIT_FORM_VALUE_ON_EVENTS, useValue: true }]);
|
|
10256
7171
|
}
|
|
10257
7172
|
|
|
10258
|
-
function hasFormValue(event) {
|
|
10259
|
-
return 'formValue' in event && event.formValue !== undefined;
|
|
10260
|
-
}
|
|
10261
|
-
|
|
10262
7173
|
function withValueExclusionDefaults(config) {
|
|
10263
7174
|
const resolved = {
|
|
10264
7175
|
excludeValueIfHidden: config?.excludeValueIfHidden ?? true,
|
|
@@ -10283,23 +7194,6 @@ function wrapperProps() {
|
|
|
10283
7194
|
return undefined;
|
|
10284
7195
|
}
|
|
10285
7196
|
|
|
10286
|
-
function isFieldBoundContext(ctx) {
|
|
10287
|
-
return ctx.form !== null;
|
|
10288
|
-
}
|
|
10289
|
-
|
|
10290
|
-
function resolveDynamicValue(value, fallback, injector) {
|
|
10291
|
-
if (value === undefined) {
|
|
10292
|
-
return computed(() => fallback);
|
|
10293
|
-
}
|
|
10294
|
-
if (isSignal(value)) {
|
|
10295
|
-
return value;
|
|
10296
|
-
}
|
|
10297
|
-
if (isObservable(value)) {
|
|
10298
|
-
return toSignal(value, { initialValue: fallback, injector });
|
|
10299
|
-
}
|
|
10300
|
-
return computed(() => value);
|
|
10301
|
-
}
|
|
10302
|
-
|
|
10303
7197
|
class DfAddonSlot {
|
|
10304
7198
|
registry = injectAddonKindRegistry();
|
|
10305
7199
|
logger = inject(DynamicFormLogger);
|
|
@@ -10381,27 +7275,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
10381
7275
|
}]
|
|
10382
7276
|
}], ctorParameters: () => [], propDecorators: { addon: [{ type: i0.Input, args: [{ isSignal: true, alias: "addon", required: true }] }], fieldInputs: [{ type: i0.Input, args: [{ isSignal: true, alias: "fieldInputs", required: false }] }], hidden: [{ type: i0.Input, args: [{ isSignal: true, alias: "hidden", required: false }] }] } });
|
|
10383
7277
|
|
|
10384
|
-
class DynamicTextPipe {
|
|
10385
|
-
injector = inject(Injector);
|
|
10386
|
-
transform(value) {
|
|
10387
|
-
if (isObservable(value)) {
|
|
10388
|
-
return value;
|
|
10389
|
-
}
|
|
10390
|
-
if (isSignal(value)) {
|
|
10391
|
-
return toObservable(value, { injector: this.injector });
|
|
10392
|
-
}
|
|
10393
|
-
return of(value || '');
|
|
10394
|
-
}
|
|
10395
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DynamicTextPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
10396
|
-
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.2.14", ngImport: i0, type: DynamicTextPipe, isStandalone: true, name: "dynamicText" });
|
|
10397
|
-
}
|
|
10398
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DynamicTextPipe, decorators: [{
|
|
10399
|
-
type: Pipe,
|
|
10400
|
-
args: [{
|
|
10401
|
-
name: 'dynamicText',
|
|
10402
|
-
}]
|
|
10403
|
-
}] });
|
|
10404
|
-
|
|
10405
7278
|
class TextAddonComponent {
|
|
10406
7279
|
addon = input.required(...(ngDevMode ? [{ debugName: "addon" }] : /* istanbul ignore next */ []));
|
|
10407
7280
|
fieldInputs = input(...(ngDevMode ? [undefined, { debugName: "fieldInputs" }] : /* istanbul ignore next */ []));
|
|
@@ -10524,6 +7397,10 @@ async function runPresetAction(preset, ctx, collaborators, adapterLabel, fieldLa
|
|
|
10524
7397
|
if (ctx.form === null && preset !== 'copy' && preset !== 'paste') {
|
|
10525
7398
|
collaborators.logger.warn(`preset '${String(preset)}' fired without a host field context — ignoring.`);
|
|
10526
7399
|
return;
|
|
7400
|
+
}
|
|
7401
|
+
if (ctx.form !== null && (preset === 'clear' || preset === 'reset' || preset === 'paste') && ctx.form.disabled()) {
|
|
7402
|
+
collaborators.logger.warn(`preset '${String(preset)}' skipped — host '${fieldLabel}' is disabled.`);
|
|
7403
|
+
return;
|
|
10527
7404
|
}
|
|
10528
7405
|
switch (preset) {
|
|
10529
7406
|
case 'clear': {
|
|
@@ -10641,14 +7518,6 @@ function sanitizeFormConfigPure(config, fieldRegistry, kindRegistry, options = {
|
|
|
10641
7518
|
return { sanitized: { ...config, fields: fields }, warnings };
|
|
10642
7519
|
}
|
|
10643
7520
|
|
|
10644
|
-
function isValueField(field) {
|
|
10645
|
-
return 'value' in field || 'nullable' in field;
|
|
10646
|
-
}
|
|
10647
|
-
|
|
10648
|
-
function isCheckedField(field) {
|
|
10649
|
-
return field.type === 'checkbox' || field.type === 'toggle';
|
|
10650
|
-
}
|
|
10651
|
-
|
|
10652
7521
|
function arrayEvent(arrayKey) {
|
|
10653
7522
|
return {
|
|
10654
7523
|
append: (template) => new AppendArrayItemEvent(arrayKey, template),
|
|
@@ -10661,28 +7530,6 @@ function arrayEvent(arrayKey) {
|
|
|
10661
7530
|
};
|
|
10662
7531
|
}
|
|
10663
7532
|
|
|
10664
|
-
function resolveTokens(args, context) {
|
|
10665
|
-
return args.map((arg) => {
|
|
10666
|
-
if (typeof arg !== 'string') {
|
|
10667
|
-
return arg;
|
|
10668
|
-
}
|
|
10669
|
-
switch (arg) {
|
|
10670
|
-
case '$key':
|
|
10671
|
-
return context.key;
|
|
10672
|
-
case '$index':
|
|
10673
|
-
return context.index;
|
|
10674
|
-
case '$arrayKey':
|
|
10675
|
-
return context.arrayKey;
|
|
10676
|
-
case '$template':
|
|
10677
|
-
return context.template;
|
|
10678
|
-
case 'formValue':
|
|
10679
|
-
return context.formValue;
|
|
10680
|
-
default:
|
|
10681
|
-
return arg;
|
|
10682
|
-
}
|
|
10683
|
-
});
|
|
10684
|
-
}
|
|
10685
|
-
|
|
10686
7533
|
|
|
10687
7534
|
function formConfig(config) {
|
|
10688
7535
|
return config;
|
|
@@ -10771,106 +7618,7 @@ function createField(type, config) {
|
|
|
10771
7618
|
}
|
|
10772
7619
|
const field = createField;
|
|
10773
7620
|
|
|
10774
|
-
function dynamicTextToObservable(value, injector) {
|
|
10775
|
-
if (value === undefined) {
|
|
10776
|
-
return of('');
|
|
10777
|
-
}
|
|
10778
|
-
if (isObservable(value)) {
|
|
10779
|
-
return value;
|
|
10780
|
-
}
|
|
10781
|
-
if (isSignal(value)) {
|
|
10782
|
-
return toObservable(value, { injector });
|
|
10783
|
-
}
|
|
10784
|
-
return of(String(value));
|
|
10785
|
-
}
|
|
10786
|
-
|
|
10787
|
-
function applyMetaToElement(element, meta, previouslyApplied) {
|
|
10788
|
-
const newApplied = new Set();
|
|
10789
|
-
for (const attr of previouslyApplied) {
|
|
10790
|
-
if (!meta || !(attr in meta) || meta[attr] === undefined) {
|
|
10791
|
-
element.removeAttribute(attr);
|
|
10792
|
-
}
|
|
10793
|
-
}
|
|
10794
|
-
if (!meta)
|
|
10795
|
-
return newApplied;
|
|
10796
|
-
for (const [key, value] of Object.entries(meta)) {
|
|
10797
|
-
if (value === undefined || value === null)
|
|
10798
|
-
continue;
|
|
10799
|
-
element.setAttribute(key, String(value));
|
|
10800
|
-
newApplied.add(key);
|
|
10801
|
-
}
|
|
10802
|
-
return newApplied;
|
|
10803
|
-
}
|
|
10804
|
-
|
|
10805
|
-
function interpolateParams(message, error) {
|
|
10806
|
-
let result = message;
|
|
10807
|
-
const params = extractErrorParams(error);
|
|
10808
|
-
Object.entries(params).forEach(([key, value]) => {
|
|
10809
|
-
const placeholder = new RegExp(`{{\\s*${key}\\s*}}`, 'g');
|
|
10810
|
-
result = result.replace(placeholder, safeToString(value));
|
|
10811
|
-
});
|
|
10812
|
-
return result;
|
|
10813
|
-
}
|
|
10814
|
-
function safeToString(value) {
|
|
10815
|
-
if (value === null || value === undefined) {
|
|
10816
|
-
return '';
|
|
10817
|
-
}
|
|
10818
|
-
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
|
|
10819
|
-
return String(value);
|
|
10820
|
-
}
|
|
10821
|
-
if (value instanceof RegExp) {
|
|
10822
|
-
return value.toString();
|
|
10823
|
-
}
|
|
10824
|
-
if (value instanceof Date) {
|
|
10825
|
-
return value.toISOString();
|
|
10826
|
-
}
|
|
10827
|
-
if (Array.isArray(value)) {
|
|
10828
|
-
return value.join(', ');
|
|
10829
|
-
}
|
|
10830
|
-
try {
|
|
10831
|
-
if (typeof value === 'object' && value.toString !== Object.prototype.toString) {
|
|
10832
|
-
return value.toString();
|
|
10833
|
-
}
|
|
10834
|
-
return JSON.stringify(value);
|
|
10835
|
-
}
|
|
10836
|
-
catch {
|
|
10837
|
-
return '[object]';
|
|
10838
|
-
}
|
|
10839
|
-
}
|
|
10840
|
-
function extractErrorParams(error) {
|
|
10841
|
-
const params = {};
|
|
10842
|
-
if ('min' in error)
|
|
10843
|
-
params.min = error.min;
|
|
10844
|
-
if ('max' in error)
|
|
10845
|
-
params.max = error.max;
|
|
10846
|
-
if ('pattern' in error)
|
|
10847
|
-
params.pattern = error.pattern;
|
|
10848
|
-
if ('actual' in error)
|
|
10849
|
-
params.actual = error.actual;
|
|
10850
|
-
if ('expected' in error)
|
|
10851
|
-
params.expected = error.expected;
|
|
10852
|
-
if ('requiredLength' in error) {
|
|
10853
|
-
params.requiredLength = error.requiredLength;
|
|
10854
|
-
}
|
|
10855
|
-
else if ('minLength' in error) {
|
|
10856
|
-
params.requiredLength = error.minLength;
|
|
10857
|
-
params.minLength = error.minLength;
|
|
10858
|
-
}
|
|
10859
|
-
else if ('maxLength' in error) {
|
|
10860
|
-
params.requiredLength = error.maxLength;
|
|
10861
|
-
params.maxLength = error.maxLength;
|
|
10862
|
-
}
|
|
10863
|
-
if ('actualLength' in error)
|
|
10864
|
-
params.actualLength = error.actualLength;
|
|
10865
|
-
Object.keys(error).forEach((key) => {
|
|
10866
|
-
if (key !== 'kind' && !(key in params)) {
|
|
10867
|
-
params[key] = error[key];
|
|
10868
|
-
}
|
|
10869
|
-
});
|
|
10870
|
-
return params;
|
|
10871
|
-
}
|
|
10872
|
-
|
|
10873
7621
|
|
|
10874
7622
|
|
|
10875
|
-
export {
|
|
10876
|
-
//# sourceMappingURL=ng-forge-dynamic-forms-ng-forge-dynamic-forms-
|
|
7623
|
+
export { sanitizeFormConfig as $, ADDON_ACTION_HANDLERS as A, BUILT_IN_FIELDS as B, CONTAINER_FIELD_PROCESSORS as C, DfAddonSlot as D, EventDispatcher as E, FormClearEvent as F, formatAddonWarning as G, getFieldDefaultValue as H, InsertArrayItemEvent as I, getNormalizedArrayMetadata as J, injectAddonKindRegistry as K, injectFieldRegistry as L, MoveArrayItemEvent as M, NextPageEvent as N, injectFieldsSupportingAddons as O, PageChangeEvent as P, isSameWrapperChain as Q, RemoveAtIndexEvent as R, ShiftArrayItemEvent as S, TemplateAddonComponent as T, isWrappersBundle as U, logAddonWarnings as V, mapFieldToInputs as W, provideAddonActions as X, provideDynamicForm as Y, resolveWrappers as Z, runPresetAction as _, ADDON_ACTION_REGISTRY as a, sanitizeFormConfigPure as a0, validateFieldAddons as a1, walkAndValidateAddons as a2, withCustomAddon as a3, withEventFormValue as a4, withLegacyStatusClasses as a5, withLoggerConfig as a6, withValidationExecutionDefaults as a7, withValueExclusionDefaults as a8, wrapperProps as a9, ADDON_KIND_COMPONENT_CACHE as b, ADDON_KIND_DEFINITIONS as c, AppendArrayItemEvent as d, ArrayItemRegistryService as e, ComponentAddonComponent as f, ConsoleLogger as g, DfFieldOutlet as h, DfTemplate as i, DynamicForm as j, FormResetEvent as k, PopArrayItemEvent as l, PrependArrayItemEvent as m, PreviousPageEvent as n, SubmitEvent as o, TextAddonComponent as p, arrayEvent as q, createField as r, createFieldResolutionPipe as s, createHiddenSignal as t, createRenderReadySignal as u, createSchemaFromFields as v, createWrapperChainController as w, createWrappers as x, field as y, formConfig as z };
|
|
7624
|
+
//# sourceMappingURL=ng-forge-dynamic-forms-ng-forge-dynamic-forms-CNc4a_Xv.mjs.map
|