@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.
Files changed (56) hide show
  1. package/fesm2022/{ng-forge-dynamic-forms-array-field.component-D6GEsH72.mjs → ng-forge-dynamic-forms-array-field.component-BfRH3TBm.mjs} +4 -3
  2. package/fesm2022/ng-forge-dynamic-forms-array-field.component-BfRH3TBm.mjs.map +1 -0
  3. package/fesm2022/{ng-forge-dynamic-forms-container-field.component-BluTT44A.mjs → ng-forge-dynamic-forms-container-field.component-bA-8ICHF.mjs} +4 -3
  4. package/fesm2022/ng-forge-dynamic-forms-container-field.component-bA-8ICHF.mjs.map +1 -0
  5. package/fesm2022/{ng-forge-dynamic-forms-container-utils-diYAWMa_.mjs → ng-forge-dynamic-forms-container-utils-BvXn8T_i.mjs} +2 -2
  6. package/fesm2022/ng-forge-dynamic-forms-container-utils-BvXn8T_i.mjs.map +1 -0
  7. package/fesm2022/{ng-forge-dynamic-forms-css-wrapper.component-CgMQOaG7.mjs → ng-forge-dynamic-forms-css-wrapper.component-DjhcONFh.mjs} +2 -2
  8. package/fesm2022/ng-forge-dynamic-forms-css-wrapper.component-DjhcONFh.mjs.map +1 -0
  9. package/fesm2022/{ng-forge-dynamic-forms-emit-initialization-CB86IMsQ.mjs → ng-forge-dynamic-forms-emit-initialization-DhHwU6FJ.mjs} +2 -2
  10. package/fesm2022/ng-forge-dynamic-forms-emit-initialization-DhHwU6FJ.mjs.map +1 -0
  11. package/fesm2022/{ng-forge-dynamic-forms-group-field.component-2bWHql4u.mjs → ng-forge-dynamic-forms-group-field.component-D0_nmXf2.mjs} +4 -3
  12. package/fesm2022/ng-forge-dynamic-forms-group-field.component-D0_nmXf2.mjs.map +1 -0
  13. package/fesm2022/ng-forge-dynamic-forms-integration.mjs +14 -10
  14. package/fesm2022/ng-forge-dynamic-forms-integration.mjs.map +1 -1
  15. package/fesm2022/ng-forge-dynamic-forms-internal.mjs +3372 -0
  16. package/fesm2022/ng-forge-dynamic-forms-internal.mjs.map +1 -0
  17. 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
  18. package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-CNc4a_Xv.mjs.map +1 -0
  19. package/fesm2022/{ng-forge-dynamic-forms-page-field.component-zF-vEv5V.mjs → ng-forge-dynamic-forms-page-field.component-BJRNnoz-.mjs} +4 -3
  20. package/fesm2022/ng-forge-dynamic-forms-page-field.component-BJRNnoz-.mjs.map +1 -0
  21. package/fesm2022/ng-forge-dynamic-forms-row-wrapper.component-CwkYn5yl.mjs.map +1 -1
  22. package/fesm2022/{ng-forge-dynamic-forms-text-field.component-_8_vVeH0.mjs → ng-forge-dynamic-forms-text-field.component-DABkT0O7.mjs} +2 -2
  23. package/fesm2022/ng-forge-dynamic-forms-text-field.component-DABkT0O7.mjs.map +1 -0
  24. package/fesm2022/ng-forge-dynamic-forms.mjs +2 -1
  25. package/fesm2022/ng-forge-dynamic-forms.mjs.map +1 -1
  26. package/package.json +14 -1
  27. package/schematics/collection.json +10 -0
  28. package/schematics/generators.json +12 -0
  29. package/schematics/ng-add/index.js +87 -0
  30. package/schematics/ng-add/index.js.map +1 -0
  31. package/schematics/ng-add/nx-wrapper.js +5 -0
  32. package/schematics/ng-add/nx-wrapper.js.map +1 -0
  33. package/schematics/ng-add/recipes.js +60 -0
  34. package/schematics/ng-add/recipes.js.map +1 -0
  35. package/schematics/ng-add/rules/add-packages.js +73 -0
  36. package/schematics/ng-add/rules/add-packages.js.map +1 -0
  37. package/schematics/ng-add/rules/add-providers.js +132 -0
  38. package/schematics/ng-add/rules/add-providers.js.map +1 -0
  39. package/schematics/ng-add/rules/add-styles.js +123 -0
  40. package/schematics/ng-add/rules/add-styles.js.map +1 -0
  41. package/schematics/ng-add/schema.json +48 -0
  42. package/schematics/package.json +1 -0
  43. package/schematics/versions.js +33 -0
  44. package/schematics/versions.js.map +1 -0
  45. package/types/ng-forge-dynamic-forms-integration.d.ts +4 -2
  46. package/types/ng-forge-dynamic-forms-internal.d.ts +3508 -0
  47. package/types/ng-forge-dynamic-forms.d.ts +14 -2733
  48. package/fesm2022/ng-forge-dynamic-forms-array-field.component-D6GEsH72.mjs.map +0 -1
  49. package/fesm2022/ng-forge-dynamic-forms-container-field.component-BluTT44A.mjs.map +0 -1
  50. package/fesm2022/ng-forge-dynamic-forms-container-utils-diYAWMa_.mjs.map +0 -1
  51. package/fesm2022/ng-forge-dynamic-forms-css-wrapper.component-CgMQOaG7.mjs.map +0 -1
  52. package/fesm2022/ng-forge-dynamic-forms-emit-initialization-CB86IMsQ.mjs.map +0 -1
  53. package/fesm2022/ng-forge-dynamic-forms-group-field.component-2bWHql4u.mjs.map +0 -1
  54. package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-CPkVjU_N.mjs.map +0 -1
  55. package/fesm2022/ng-forge-dynamic-forms-page-field.component-zF-vEv5V.mjs.map +0 -1
  56. 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 { InjectionToken, inject, reflectComponentType, Injector, EnvironmentInjector, DestroyRef, computed, signal, input, ViewContainerRef, Directive, Injectable, isSignal, untracked, linkedSignal, ChangeDetectionStrategy, Component, resourceFromSnapshots, runInInjectionContext, afterNextRender, ElementRef, PLATFORM_ID, isDevMode, isWritableSignal, TemplateRef, model, contentChildren, makeEnvironmentProviders, Pipe } from '@angular/core';
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 { takeUntilDestroyed, toObservable, toSignal, rxResource, outputFromObservable } from '@angular/core/rxjs-interop';
5
- import { of, forkJoin, from, catchError, map, switchMap, isObservable, firstValueFrom, exhaustMap, EMPTY, Subject, filter, TimeoutError, throwError, defer, combineLatest, pipe, debounceTime, distinctUntilChanged, tap, startWith, scan as scan$1, Observable, concatMap, take as take$1, pairwise, merge, combineLatestWith, auditTime, scheduled, queueScheduler, timer } from 'rxjs';
6
- import { submit, email, min, max, minLength, maxLength, pattern, required, validate, validateAsync, validateHttp, disabled, readonly, hidden, applyEach, applyWhenValue, applyWhen, apply, schema, validateStandardSchema, validateTree, form, provideSignalFormsConfig } from '@angular/forms/signals';
7
- import { scan, map as map$1, filter as filter$1, take, switchMap as switchMap$1, timeout, catchError as catchError$1, shareReplay } from 'rxjs/operators';
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
- function compareValues(actual, expected, operator) {
831
- switch (operator) {
832
- case 'equals':
833
- return actual === expected;
834
- case 'notEquals':
835
- return actual !== expected;
836
- case 'greater':
837
- return Number(actual) > Number(expected);
838
- case 'less':
839
- return Number(actual) < Number(expected);
840
- case 'greaterOrEqual':
841
- return Number(actual) >= Number(expected);
842
- case 'lessOrEqual':
843
- return Number(actual) <= Number(expected);
844
- case 'contains':
845
- return String(actual).includes(String(expected));
846
- case 'startsWith':
847
- return String(actual).startsWith(String(expected));
848
- case 'endsWith':
849
- return String(actual).endsWith(String(expected));
850
- case 'matches':
851
- try {
852
- return new RegExp(String(expected)).test(String(actual));
853
- }
854
- catch {
855
- return false;
856
- }
857
- default:
858
- return false;
859
- }
860
- }
861
- function getNestedValue(obj, path) {
862
- return path.split('.').reduce((current, key) => {
863
- return current && typeof current === 'object' ? current[key] : undefined;
864
- }, obj);
865
- }
866
- function hasNestedProperty(obj, path) {
867
- const keys = path.split('.');
868
- let current = obj;
869
- for (let i = 0; i < keys.length - 1; i++) {
870
- if (!current || typeof current !== 'object')
871
- return false;
872
- current = current[keys[i]];
873
- }
874
- if (!current || typeof current !== 'object')
875
- return false;
876
- return keys[keys.length - 1] in current;
877
- }
878
-
879
- var TokenType;
880
- (function (TokenType) {
881
- TokenType["NUMBER"] = "NUMBER";
882
- TokenType["STRING"] = "STRING";
883
- TokenType["TRUE"] = "TRUE";
884
- TokenType["FALSE"] = "FALSE";
885
- TokenType["NULL"] = "NULL";
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 result;
1144
- }
1145
- parseExpression() {
1146
- const arrow = this.tryParseArrowFunction();
1147
- if (arrow)
1148
- return arrow;
1149
- return this.parseConditional();
1150
- }
1151
- parseConditional() {
1152
- const test = this.parseLogicalOr();
1153
- if (!this.match(TokenType.QUESTION))
1154
- return test;
1155
- const consequent = this.parseExpression();
1156
- this.consume(TokenType.COLON, 'Expected ":" between ternary branches');
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
- if (!this.check(TokenType.ARROW)) {
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
- parseEquality() {
1222
- let node = this.parseComparison();
1223
- while (this.match(TokenType.EQUAL, TokenType.NOT_EQUAL)) {
1224
- const operator = this.previous().value;
1225
- const right = this.parseComparison();
1226
- node = { type: 'BinaryOp', operator, left: node, right };
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
- return node;
1229
- }
1230
- parseComparison() {
1231
- let node = this.parseAddition();
1232
- while (this.match(TokenType.GREATER, TokenType.GREATER_EQUAL, TokenType.LESS, TokenType.LESS_EQUAL)) {
1233
- const operator = this.previous().value;
1234
- const right = this.parseAddition();
1235
- node = { type: 'BinaryOp', operator, left: node, right };
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
- return node;
1238
- }
1239
- parseAddition() {
1240
- let node = this.parseMultiplication();
1241
- while (this.match(TokenType.PLUS, TokenType.MINUS)) {
1242
- const operator = this.previous().value;
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
- return node;
1247
- }
1248
- parseMultiplication() {
1249
- let node = this.parseUnary();
1250
- while (this.match(TokenType.MULTIPLY, TokenType.DIVIDE, TokenType.MODULO)) {
1251
- const operator = this.previous().value;
1252
- const right = this.parseUnary();
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
- return node;
655
+ const nextVisiblePageIndex = visibleIndices[currentVisiblePosition + 1];
656
+ return this.navigateToPage(nextVisiblePageIndex);
1309
657
  }
1310
- parsePrimary() {
1311
- if (this.match(TokenType.TRUE)) {
1312
- return { type: 'Literal', value: true };
1313
- }
1314
- if (this.match(TokenType.FALSE)) {
1315
- return { type: 'Literal', value: false };
1316
- }
1317
- if (this.match(TokenType.NULL)) {
1318
- return { type: 'Literal', value: null };
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 (this.match(TokenType.STRING)) {
1327
- return { type: 'Literal', value: this.previous().value };
668
+ if (currentState.navigationDisabled) {
669
+ return {
670
+ success: false,
671
+ newPageIndex: currentState.currentPageIndex,
672
+ error: 'Navigation is currently disabled',
673
+ };
1328
674
  }
1329
- if (this.match(TokenType.LBRACKET)) {
1330
- const elements = this.parseArrayElements();
1331
- this.consume(TokenType.RBRACKET, 'Expected "]" after array elements');
1332
- return { type: 'ArrayLiteral', elements };
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
- if (this.match(TokenType.LBRACE)) {
1335
- const properties = this.parseObjectProperties();
1336
- this.consume(TokenType.RBRACE, 'Expected "}" after object properties');
1337
- return { type: 'ObjectLiteral', properties };
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
- if (this.match(TokenType.IDENTIFIER)) {
1340
- return { type: 'Identifier', name: this.previous().value };
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 (this.match(TokenType.LPAREN)) {
1343
- const expr = this.parseExpression();
1344
- this.consume(TokenType.RPAREN, 'Expected ")" after expression');
1345
- return expr;
704
+ if (pageIndex === currentState.currentPageIndex) {
705
+ return {
706
+ success: true,
707
+ newPageIndex: pageIndex,
708
+ };
1346
709
  }
1347
- throw new ExpressionParserError(`Unexpected token: ${this.peek().value}`, this.peek().position, this.expression);
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
- parseArgumentList() {
1350
- const args = [];
1351
- if (!this.check(TokenType.RPAREN)) {
1352
- do {
1353
- args.push(this.parseExpression());
1354
- } while (this.match(TokenType.COMMA));
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 args;
728
+ return nearest;
1357
729
  }
1358
- parseArrayElements() {
1359
- const elements = [];
1360
- if (!this.check(TokenType.RBRACKET)) {
1361
- do {
1362
- elements.push(this.parseExpression());
1363
- } while (this.match(TokenType.COMMA));
1364
- }
1365
- return elements;
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
- parseObjectProperties() {
1368
- const properties = [];
1369
- if (this.check(TokenType.RBRACE)) {
1370
- return properties;
745
+ evaluatePageHidden(page) {
746
+ if (!page.logic || page.logic.length === 0) {
747
+ return false;
1371
748
  }
1372
- do {
1373
- if (this.check(TokenType.RBRACE))
1374
- break;
1375
- let key;
1376
- if (this.check(TokenType.IDENTIFIER) || this.check(TokenType.STRING)) {
1377
- key = this.advance().value;
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
- else {
1380
- throw new ExpressionParserError(`Expected property name (identifier or string) in object literal, got '${this.peek().value}'`, this.peek().position, this.expression);
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
- check(type) {
1398
- if (this.isAtEnd())
1399
- return false;
1400
- return this.peek().type === type;
1401
- }
1402
- advance() {
1403
- if (!this.isAtEnd())
1404
- this.current++;
1405
- return this.previous();
1406
- }
1407
- isAtEnd() {
1408
- return this.peek().type === TokenType.EOF;
1409
- }
1410
- peek() {
1411
- return this.tokens[this.current];
1412
- }
1413
- previous() {
1414
- return this.tokens[this.current - 1];
1415
- }
1416
- consume(type, message) {
1417
- if (this.check(type))
1418
- return this.advance();
1419
- throw new ExpressionParserError(message, this.peek().position, this.expression);
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
- const STRING_SAFE_METHODS = [
1424
- 'charAt',
1425
- 'charCodeAt',
1426
- 'concat',
1427
- 'endsWith',
1428
- 'includes',
1429
- 'indexOf',
1430
- 'lastIndexOf',
1431
- 'match',
1432
- 'padEnd',
1433
- 'padStart',
1434
- 'repeat',
1435
- 'replace',
1436
- 'search',
1437
- 'slice',
1438
- 'split',
1439
- 'startsWith',
1440
- 'substring',
1441
- 'toLowerCase',
1442
- 'toUpperCase',
1443
- 'trim',
1444
- 'trimEnd',
1445
- 'trimStart',
1446
- 'toString',
1447
- ];
1448
- const NUMBER_SAFE_METHODS = ['toExponential', 'toFixed', 'toPrecision', 'toString'];
1449
- const ARRAY_SAFE_METHODS = [
1450
- 'concat',
1451
- 'every',
1452
- 'filter',
1453
- 'find',
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
- evaluateIdentifier(name) {
1548
- if (name in this.scope) {
1549
- return this.scope[name];
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
- evaluateMemberAccess(node) {
1554
- const obj = this.evaluate(node.object);
1555
- if (obj === null || obj === undefined) {
1556
- return undefined;
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 (BLOCKED_PROPERTIES.has(node.property)) {
1559
- throw new ExpressionParserError(`Property "${node.property}" is not accessible for security reasons`, 0, this.expression);
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 (typeof obj === 'object' || typeof obj === 'string' || typeof obj === 'number') {
1562
- return obj[node.property];
889
+ if (isSimplifiedArrayField(field)) {
890
+ const { arrayField, addButton } = expandSimplifiedArray(field);
891
+ return addButton ? [arrayField, addButton] : [arrayField];
1563
892
  }
1564
- return undefined;
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
- class HttpConditionCache {
2985
- cache = new Map();
2986
- maxEntries;
2987
- constructor(maxEntries = 100) {
2988
- this.maxEntries = Math.max(1, maxEntries);
2989
- }
2990
- get(key) {
2991
- const entry = this.cache.get(key);
2992
- if (!entry)
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 HTTP_CONDITION_CACHE = new InjectionToken('HttpConditionCache');
3014
-
3015
- class HttpConditionFunctionCacheService {
3016
- httpConditionFunctionCache = new Map();
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
- class AsyncConditionFunctionCacheService {
3175
- asyncConditionFunctionCache = new Map();
3176
- }
3177
-
3178
- function createAsyncConditionLogicFunction(condition) {
3179
- const fieldContextRegistry = inject(FieldContextRegistryService);
3180
- const functionRegistry = inject(FunctionRegistryService);
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
- if (cacheKey !== undefined) {
3224
- cacheService.asyncConditionFunctionCache.set(cacheKey, fn);
3225
- }
3226
- return fn;
3227
- }
3228
-
3229
- class LogicFunctionCacheService {
3230
- logicFunctionCache = new Map();
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
- function shouldLog(config, minLevel) {
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
- let validatorFn;
3592
- if (config.expression) {
3593
- validatorFn = createExpressionValidator(config);
940
+ if (minLength !== undefined) {
941
+ arrayFieldObj['minLength'] = minLength;
3594
942
  }
3595
- else if (config.fn || config.functionName) {
3596
- validatorFn = createFunctionValidator(config);
943
+ if (maxLength !== undefined) {
944
+ arrayFieldObj['maxLength'] = maxLength;
3597
945
  }
3598
- else {
3599
- const logger = inject(DynamicFormLogger);
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
- const whenLogic = createConditionalLogic(config.when);
3604
- validate(fieldPath, (ctx) => {
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 (config.fn) {
3616
- const inlineFn = config.fn;
3617
- return (ctx) => inlineFn(ctx, config.params);
952
+ if (skipDefaultWrappers !== undefined) {
953
+ arrayFieldObj['skipDefaultWrappers'] = skipDefaultWrappers;
3618
954
  }
3619
- const functionName = config.functionName;
3620
- if (!functionName) {
3621
- logger.warn('Custom validator missing functionName');
3622
- return () => null;
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
- const registry = inject(FunctionRegistryService);
3625
- const validatorFn = registry.getValidator(functionName);
3626
- if (!validatorFn) {
3627
- throw new DynamicFormError(`Custom validator "${functionName}" not found. Register it with customFnConfig.validators.`);
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 (ctx) => validatorFn(ctx, config.params);
989
+ return [...fieldsWithValues, buildRemoveButton(removeButton)];
3630
990
  }
3631
- function createExpressionValidator(config) {
3632
- const logger = inject(DynamicFormLogger);
3633
- const expression = config.expression;
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
- const fieldContextRegistry = inject(FieldContextRegistryService);
3639
- const functionRegistry = inject(FunctionRegistryService);
3640
- return (ctx) => {
3641
- try {
3642
- const evaluationContext = fieldContextRegistry.createEvaluationContext(ctx, functionRegistry.getCustomFunctions());
3643
- const result = ExpressionParser.evaluate(expression, evaluationContext);
3644
- if (result) {
3645
- return null;
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
- function applyAsyncValidator(config, fieldPath) {
3668
- if (config.fn && config.functionName) {
3669
- const logger = inject(DynamicFormLogger);
3670
- logger.warn('Both "fn" and "functionName" are set on async validator. Inline "fn" takes precedence; "functionName" is ignored.');
3671
- }
3672
- let validatorConfig;
3673
- if (config.fn) {
3674
- validatorConfig = config.fn;
3675
- }
3676
- else if (config.functionName) {
3677
- const registry = inject(FunctionRegistryService);
3678
- validatorConfig = registry.getAsyncValidator(config.functionName);
3679
- if (!validatorConfig) {
3680
- throw new DynamicFormError(`Async validator "${config.functionName}" not found. Register it with customFnConfig.asyncValidators.`);
3681
- }
3682
- }
3683
- else {
3684
- throw new DynamicFormError('Async validator requires one of "functionName" or "fn".');
3685
- }
3686
- const whenLogic = createConditionalLogic(config.when);
3687
- const asyncOptions = {
3688
- params: (ctx) => {
3689
- if (whenLogic && !whenLogic(ctx))
3690
- return undefined;
3691
- return validatorConfig.params(ctx, config.params);
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
- function applyUnifiedHttpValidator(config, fieldPath) {
3700
- if (isFunctionHttpValidator(config)) {
3701
- applyFunctionHttpValidator(config, fieldPath);
3702
- return;
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
- const declarative = config;
3705
- if (!declarative.http || !declarative.responseMapping) {
3706
- throw new DynamicFormError('HTTP validator requires one of "functionName" / "fn" (function-based) or "http" + "responseMapping" (declarative).');
1062
+ clearAll() {
1063
+ for (const slot of this.slots.values())
1064
+ slot.clear();
1065
+ this.slots.clear();
3707
1066
  }
3708
- applyDeclarativeHttpValidator(config, fieldPath);
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
- function applyFunctionHttpValidator(config, fieldPath) {
3711
- if (config.fn && config.functionName) {
3712
- const logger = inject(DynamicFormLogger);
3713
- logger.warn('Both "fn" and "functionName" are set on HTTP validator. Inline "fn" takes precedence; "functionName" is ignored.');
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
- let httpValidatorConfig;
3716
- if (config.fn) {
3717
- httpValidatorConfig = config.fn;
1079
+ getSchema(name) {
1080
+ return this.registeredSchemas.get(name);
3718
1081
  }
3719
- else if (config.functionName) {
3720
- const registry = inject(FunctionRegistryService);
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
- else {
3727
- throw new DynamicFormError('HTTP validator requires one of "functionName" or "fn".');
1085
+ clearSchemas() {
1086
+ this.registeredSchemas.clear();
3728
1087
  }
3729
- const whenLogic = createConditionalLogic(config.when);
3730
- const httpOptions = {
3731
- request: (ctx) => {
3732
- if (whenLogic && !whenLogic(ctx))
3733
- return undefined;
3734
- return httpValidatorConfig.request(ctx, config.params);
3735
- },
3736
- onSuccess: httpValidatorConfig.onSuccess,
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 changedKeys;
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
- result.push(...flattenedChildren);
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$1(reconcileFields, []));
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 isHidden = state.hidden() || field.hidden === true;
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 staticallyExcluded = (resolvedConfig.excludeValueIfHidden && field.hidden === true) ||
5924
- (resolvedConfig.excludeValueIfDisabled && field.disabled === true) ||
5925
- (resolvedConfig.excludeValueIfReadonly && field.readonly === true);
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
- const validKeys = new Set(formSetup.schemaFields.map((f) => f.key).filter((key) => key !== undefined));
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$1(1), map(() => undefined));
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$1(reconcileFields, [])), { initialValue: [], injector: this.injector });
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
- if (Object.keys(filtered).length > 0) {
6745
- this.deps.value.update((current) => ({ ...current, ...filtered }));
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
- return fn(evalContext);
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
- const currentForm = untracked(() => context.form());
8256
- applyValueToForm(entry.fieldKey, newValue, currentForm, context.logger, context.warningTracker);
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
- const currentForm = untracked(() => context.form());
8395
- applyValueToForm(entry.fieldKey, newValue, currentForm, context.logger, context.warningTracker);
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-BluTT44A.mjs'),
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-2bWHql4u.mjs'),
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-D6GEsH72.mjs'),
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-zF-vEv5V.mjs'),
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-_8_vVeH0.mjs'),
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-BluTT44A.mjs'),
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-CgMQOaG7.mjs'),
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 { arrayFieldMapper as $, ADDON_ACTION_HANDLERS as A, BUILT_IN_FIELDS as B, CONTAINER_FIELD_PROCESSORS as C, DEFAULT_PROPS as D, EventBus as E, FIELD_REGISTRY as F, GROUP_CONTEXT as G, NoopLogger as H, INITIALIZATION_TIMEOUT_MS as I, PopArrayItemEvent as J, PrependArrayItemEvent as K, PreviousPageEvent as L, MoveArrayItemEvent as M, NextPageEvent as N, RootFormRegistryService as O, PageChangeEvent as P, SubmitEvent as Q, RemoveAtIndexEvent as R, ShiftArrayItemEvent as S, TemplateAddonComponent as T, TextAddonComponent as U, WRAPPER_REGISTRY as V, WRAPPER_AUTO_ASSOCIATIONS as W, applyMetaToElement as X, applyValidator as Y, applyValidators as Z, arrayEvent as _, ADDON_ACTION_REGISTRY as a, textFieldMapper as a$, baseFieldMapper as a0, buildBaseInputs as a1, containerFieldMapper as a2, createField as a3, createFieldResolutionPipe as a4, createHiddenSignal as a5, createRenderReadySignal as a6, createSchemaFromFields as a7, createWrapperChainController as a8, createWrappers as a9, isGroupField as aA, isLeafField as aB, isPageField as aC, isRowField as aD, isSameWrapperChain as aE, isSimplifiedArrayField as aF, isValueBearingField as aG, isValueField as aH, isWrapperTypeDefinition as aI, isWrappersBundle as aJ, logAddonWarnings as aK, mapFieldToInputs as aL, omit as aM, pageFieldMapper as aN, provideAddonActions as aO, provideDynamicForm as aP, resolveDynamicValue as aQ, resolveNextButtonDisabled as aR, resolveNonFieldDisabled as aS, resolveNonFieldHidden as aT, resolveSubmitButtonDisabled as aU, resolveTokens as aV, resolveWrappers as aW, rowFieldMapper as aX, runPresetAction as aY, sanitizeFormConfig as aZ, sanitizeFormConfigPure as a_, derivedFromDeferred as aa, dynamicTextToObservable as ab, evaluateNonFieldDisabled as ac, evaluateNonFieldHidden as ad, field as ae, formConfig as af, formatAddonWarning as ag, getArrayLength as ah, getFieldDefaultValue as ai, getFieldValueHandling as aj, getNormalizedArrayMetadata as ak, groupFieldMapper as al, hasFormValue as am, injectAddonKindRegistry as an, injectFieldRegistry as ao, injectFieldSignalContext as ap, injectFieldsSupportingAddons as aq, interpolateParams as ar, isArrayField as as, isCheckedField as at, isContainerField as au, isContainerTypedField as av, isDisplayOnlyField as aw, isEqual as ax, isFieldBoundContext as ay, isFormStateCondition as az, ADDON_KIND_COMPONENT_CACHE as b, toReadonlyFieldTree as b0, validateFieldAddons as b1, validatePageNesting as b2, walkAndValidateAddons as b3, withCustomAddon as b4, withEventFormValue as b5, withLegacyStatusClasses as b6, withLoggerConfig as b7, withPreviousValue as b8, withValidationExecutionDefaults as b9, withValueExclusionDefaults as ba, wrapperProps as bb, writeToFieldValue as bc, ADDON_KIND_DEFINITIONS as c, ADDON_KIND_REGISTRY as d, ARRAY_CONTEXT as e, AppendArrayItemEvent as f, ArrayItemRegistryService as g, ComponentAddonComponent as h, ComponentInitializedEvent as i, ConsoleLogger as j, DEFAULT_VALIDATION_MESSAGES as k, DEFAULT_WRAPPERS as l, DF_FIELD_TEMPLATES as m, DfAddonSlot as n, DfFieldOutlet as o, DfTemplate as p, DynamicForm as q, DynamicFormError as r, DynamicFormLogger as s, DynamicTextPipe as t, EventDispatcher as u, FIELD_SIGNAL_CONTEXT as v, FORM_OPTIONS as w, FormClearEvent as x, FormResetEvent as y, InsertArrayItemEvent as z };
10876
- //# sourceMappingURL=ng-forge-dynamic-forms-ng-forge-dynamic-forms-CPkVjU_N.mjs.map
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