@ng-forge/dynamic-forms 1.0.0-rc.4 → 1.1.0-next.1

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 (29) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +25 -32
  3. package/fesm2022/{ng-forge-dynamic-forms-array-field.component-DZWGHZjj.mjs → ng-forge-dynamic-forms-array-field.component-CIheZh11.mjs} +5 -5
  4. package/fesm2022/{ng-forge-dynamic-forms-array-field.component-DZWGHZjj.mjs.map → ng-forge-dynamic-forms-array-field.component-CIheZh11.mjs.map} +1 -1
  5. package/fesm2022/{ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-ueIHE70C.mjs → ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-C9aVachF.mjs} +2 -2
  6. package/fesm2022/{ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-ueIHE70C.mjs.map → ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-C9aVachF.mjs.map} +1 -1
  7. package/fesm2022/{ng-forge-dynamic-forms-container-field.component-Dt5yfGQ7.mjs → ng-forge-dynamic-forms-container-field.component-DNafCSO8.mjs} +5 -5
  8. package/fesm2022/{ng-forge-dynamic-forms-container-field.component-Dt5yfGQ7.mjs.map → ng-forge-dynamic-forms-container-field.component-DNafCSO8.mjs.map} +1 -1
  9. package/fesm2022/{ng-forge-dynamic-forms-css-wrapper.component-AScQAJTQ.mjs → ng-forge-dynamic-forms-css-wrapper.component-BmHGlmES.mjs} +4 -4
  10. package/fesm2022/{ng-forge-dynamic-forms-css-wrapper.component-AScQAJTQ.mjs.map → ng-forge-dynamic-forms-css-wrapper.component-BmHGlmES.mjs.map} +1 -1
  11. package/fesm2022/{ng-forge-dynamic-forms-group-field.component-BgoDlVYp.mjs → ng-forge-dynamic-forms-group-field.component-CKVIw2K5.mjs} +5 -5
  12. package/fesm2022/{ng-forge-dynamic-forms-group-field.component-BgoDlVYp.mjs.map → ng-forge-dynamic-forms-group-field.component-CKVIw2K5.mjs.map} +1 -1
  13. package/fesm2022/ng-forge-dynamic-forms-integration.mjs +70 -51
  14. package/fesm2022/ng-forge-dynamic-forms-integration.mjs.map +1 -1
  15. package/fesm2022/ng-forge-dynamic-forms-internal.mjs +1430 -1408
  16. package/fesm2022/ng-forge-dynamic-forms-internal.mjs.map +1 -1
  17. package/fesm2022/{ng-forge-dynamic-forms-ng-forge-dynamic-forms-CRCXs4f8.mjs → ng-forge-dynamic-forms-ng-forge-dynamic-forms-CBawLbtp.mjs} +70 -57
  18. package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-CBawLbtp.mjs.map +1 -0
  19. package/fesm2022/{ng-forge-dynamic-forms-page-field.component-W9f5VyQJ.mjs → ng-forge-dynamic-forms-page-field.component-BRixkgyS.mjs} +5 -5
  20. package/fesm2022/{ng-forge-dynamic-forms-page-field.component-W9f5VyQJ.mjs.map → ng-forge-dynamic-forms-page-field.component-BRixkgyS.mjs.map} +1 -1
  21. package/fesm2022/{ng-forge-dynamic-forms-row-wrapper.component-BlVa801t.mjs → ng-forge-dynamic-forms-row-wrapper.component-BBWmqQA_.mjs} +4 -4
  22. package/fesm2022/{ng-forge-dynamic-forms-row-wrapper.component-BlVa801t.mjs.map → ng-forge-dynamic-forms-row-wrapper.component-BBWmqQA_.mjs.map} +1 -1
  23. package/fesm2022/{ng-forge-dynamic-forms-text-field.component-CNCpmf5_.mjs → ng-forge-dynamic-forms-text-field.component-C-f80sbk.mjs} +4 -4
  24. package/fesm2022/{ng-forge-dynamic-forms-text-field.component-CNCpmf5_.mjs.map → ng-forge-dynamic-forms-text-field.component-C-f80sbk.mjs.map} +1 -1
  25. package/fesm2022/ng-forge-dynamic-forms.mjs +1 -1
  26. package/package.json +5 -5
  27. package/types/ng-forge-dynamic-forms-integration.d.ts +29 -3
  28. package/types/ng-forge-dynamic-forms-internal.d.ts +50 -201
  29. package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-CRCXs4f8.mjs.map +0 -1
@@ -3,7 +3,7 @@ import { InjectionToken, computed, isSignal, inject, Injector, input, signal, Ch
3
3
  import { NgComponentOutlet } from '@angular/common';
4
4
  import { explicitEffect } from 'ngxtension/explicit-effect';
5
5
  import { toSignal, toObservable, rxResource } from '@angular/core/rxjs-interop';
6
- import { isObservable, defer, combineLatest, pipe, debounceTime, distinctUntilChanged, of, from, map, catchError, tap, startWith, Subject, filter, TimeoutError, throwError } from 'rxjs';
6
+ import { isObservable, defer, combineLatest, pipe, debounceTime, distinctUntilChanged, map, tap, catchError, of, from, startWith, Subject, filter, TimeoutError, throwError } from 'rxjs';
7
7
  import { HttpClient } from '@angular/common/http';
8
8
  import { email, min, max, minLength, maxLength, pattern, required, validate, validateAsync, validateHttp } from '@angular/forms/signals';
9
9
  import { scan, map as map$1, filter as filter$1, take, switchMap, timeout, catchError as catchError$1, shareReplay } from 'rxjs/operators';
@@ -155,14 +155,14 @@ class DfAddonSlot {
155
155
  });
156
156
  });
157
157
  }
158
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: DfAddonSlot, deps: [], target: i0.ɵɵFactoryTarget.Component });
159
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.0", type: DfAddonSlot, isStandalone: true, selector: "df-addon-slot", inputs: { addon: { classPropertyName: "addon", publicName: "addon", isSignal: true, isRequired: true, transformFunction: null }, fieldInputs: { classPropertyName: "fieldInputs", publicName: "fieldInputs", isSignal: true, isRequired: false, transformFunction: null }, hidden: { classPropertyName: "hidden", publicName: "hidden", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.slot": "slotAttr()", "class": "className()", "style.display": "isHidden() ? \"none\" : null" } }, ngImport: i0, template: `
158
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: DfAddonSlot, deps: [], target: i0.ɵɵFactoryTarget.Component });
159
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", type: DfAddonSlot, isStandalone: true, selector: "df-addon-slot", inputs: { addon: { classPropertyName: "addon", publicName: "addon", isSignal: true, isRequired: true, transformFunction: null }, fieldInputs: { classPropertyName: "fieldInputs", publicName: "fieldInputs", isSignal: true, isRequired: false, transformFunction: null }, hidden: { classPropertyName: "hidden", publicName: "hidden", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.slot": "slotAttr()", "class": "className()", "style.display": "isHidden() ? \"none\" : null" } }, ngImport: i0, template: `
160
160
  @if (resolvedComponent(); as cmp) {
161
161
  <ng-container *ngComponentOutlet="cmp; inputs: typeInputs()" />
162
162
  }
163
163
  `, isInline: true, dependencies: [{ kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
164
164
  }
165
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: DfAddonSlot, decorators: [{
165
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: DfAddonSlot, decorators: [{
166
166
  type: Component,
167
167
  args: [{
168
168
  selector: 'df-addon-slot',
@@ -475,50 +475,6 @@ class AsyncConditionFunctionCacheService {
475
475
  asyncConditionFunctionCache = new Map();
476
476
  }
477
477
 
478
- function stableStringify(value) {
479
- if (value === null || value === undefined) {
480
- return String(value);
481
- }
482
- if (typeof value !== 'object') {
483
- return JSON.stringify(value);
484
- }
485
- if (Array.isArray(value)) {
486
- return '[' + value.map(stableStringify).join(',') + ']';
487
- }
488
- const obj = value;
489
- const sortedKeys = Object.keys(obj).sort();
490
- const pairs = sortedKeys.map((key) => JSON.stringify(key) + ':' + stableStringify(obj[key]));
491
- return '{' + pairs.join(',') + '}';
492
- }
493
-
494
- const DEFAULT_WRAPPERS = new InjectionToken('DEFAULT_WRAPPERS');
495
- const FIELD_SIGNAL_CONTEXT = new InjectionToken('FIELD_SIGNAL_CONTEXT');
496
- function injectFieldSignalContext() {
497
- const ctx = inject(FIELD_SIGNAL_CONTEXT, { optional: true });
498
- if (!ctx) {
499
- throw new DynamicFormError('FIELD_SIGNAL_CONTEXT is not provided. This consumer must run inside a <df-dynamic-form> component tree.');
500
- }
501
- return ctx;
502
- }
503
- const ARRAY_CONTEXT = new InjectionToken('ARRAY_CONTEXT');
504
- const GROUP_CONTEXT = new InjectionToken('GROUP_CONTEXT');
505
- const DEFAULT_PROPS = new InjectionToken('DEFAULT_PROPS');
506
- const DEFAULT_VALIDATION_MESSAGES = new InjectionToken('DEFAULT_VALIDATION_MESSAGES');
507
- const FORM_OPTIONS = new InjectionToken('FORM_OPTIONS');
508
- const EXTERNAL_DATA = new InjectionToken('EXTERNAL_DATA');
509
- const FORM_ID_PREFIX = new InjectionToken('FORM_ID_PREFIX');
510
-
511
- class RootFormRegistryService {
512
- formValue;
513
- rootForm;
514
- constructor(formValue, rootForm) {
515
- this.formValue = formValue;
516
- this.rootForm = rootForm;
517
- }
518
- }
519
-
520
- const DEPRECATION_WARNING_TRACKER = new InjectionToken('DeprecationWarningTracker');
521
-
522
478
  function compareValues(actual, expected, operator) {
523
479
  switch (operator) {
524
480
  case 'equals':
@@ -572,1445 +528,1355 @@ function hasNestedProperty(obj, path) {
572
528
  return keys[keys.length - 1] in current;
573
529
  }
574
530
 
575
- function safeReadPathKeys(fieldContext) {
576
- if (!('pathKeys' in fieldContext) || typeof fieldContext.pathKeys !== 'function') {
577
- return [];
531
+ var TokenType;
532
+ (function (TokenType) {
533
+ TokenType["NUMBER"] = "NUMBER";
534
+ TokenType["STRING"] = "STRING";
535
+ TokenType["TRUE"] = "TRUE";
536
+ TokenType["FALSE"] = "FALSE";
537
+ TokenType["NULL"] = "NULL";
538
+ TokenType["UNDEFINED"] = "UNDEFINED";
539
+ TokenType["IDENTIFIER"] = "IDENTIFIER";
540
+ TokenType["PLUS"] = "PLUS";
541
+ TokenType["MINUS"] = "MINUS";
542
+ TokenType["MULTIPLY"] = "MULTIPLY";
543
+ TokenType["DIVIDE"] = "DIVIDE";
544
+ TokenType["MODULO"] = "MODULO";
545
+ TokenType["EQUAL"] = "EQUAL";
546
+ TokenType["NOT_EQUAL"] = "NOT_EQUAL";
547
+ TokenType["GREATER"] = "GREATER";
548
+ TokenType["LESS"] = "LESS";
549
+ TokenType["GREATER_EQUAL"] = "GREATER_EQUAL";
550
+ TokenType["LESS_EQUAL"] = "LESS_EQUAL";
551
+ TokenType["AND"] = "AND";
552
+ TokenType["OR"] = "OR";
553
+ TokenType["NOT"] = "NOT";
554
+ TokenType["DOT"] = "DOT";
555
+ TokenType["COMMA"] = "COMMA";
556
+ TokenType["COLON"] = "COLON";
557
+ TokenType["LPAREN"] = "LPAREN";
558
+ TokenType["RPAREN"] = "RPAREN";
559
+ TokenType["LBRACKET"] = "LBRACKET";
560
+ TokenType["RBRACKET"] = "RBRACKET";
561
+ TokenType["LBRACE"] = "LBRACE";
562
+ TokenType["RBRACE"] = "RBRACE";
563
+ TokenType["ARROW"] = "ARROW";
564
+ TokenType["QUESTION"] = "QUESTION";
565
+ TokenType["OPTIONAL_DOT"] = "OPTIONAL_DOT";
566
+ TokenType["EOF"] = "EOF";
567
+ })(TokenType || (TokenType = {}));
568
+ class ExpressionParserError extends DynamicFormError {
569
+ position;
570
+ expression;
571
+ constructor(message, position, expression) {
572
+ super(`${message} at position ${position} in expression: ${expression}`);
573
+ this.position = position;
574
+ this.expression = expression;
575
+ this.name = 'ExpressionParserError';
578
576
  }
579
- return untracked(() => fieldContext.pathKeys());
580
577
  }
581
578
 
582
- function isChildFieldContext(context) {
583
- return 'key' in context && isSignal(context.key);
584
- }
585
- function extractFieldState(fieldContext) {
586
- return untracked(() => {
587
- if (!fieldContext || !('state' in fieldContext))
588
- return undefined;
589
- return fieldContext.state;
590
- });
591
- }
592
- function detectArrayScope(pathKeys) {
593
- if (pathKeys.length < 3)
594
- return undefined;
595
- for (let i = pathKeys.length - 2; i >= 1; i--) {
596
- const maybeIndex = Number(pathKeys[i]);
597
- if (Number.isInteger(maybeIndex) && maybeIndex >= 0) {
598
- return {
599
- arrayKey: pathKeys.slice(0, i).join('.'),
600
- index: maybeIndex,
601
- localKey: pathKeys[pathKeys.length - 1],
602
- };
579
+ class Tokenizer {
580
+ position = 0;
581
+ expression;
582
+ constructor(expression) {
583
+ this.expression = expression;
584
+ }
585
+ tokenize() {
586
+ const tokens = [];
587
+ while (this.position < this.expression.length) {
588
+ this.skipWhitespace();
589
+ if (this.position >= this.expression.length) {
590
+ break;
591
+ }
592
+ const token = this.nextToken();
593
+ tokens.push(token);
603
594
  }
595
+ tokens.push({ type: TokenType.EOF, value: '', position: this.position });
596
+ return tokens;
604
597
  }
605
- return undefined;
606
- }
607
- class FieldContextRegistryService {
608
- rootFormRegistry = inject(RootFormRegistryService);
609
- logger = inject(DynamicFormLogger);
610
- deprecationTracker = inject(DEPRECATION_WARNING_TRACKER, { optional: true });
611
- externalDataSignal = inject(EXTERNAL_DATA, { optional: true });
612
- createEvaluationContext(fieldContext, customFunctions) {
613
- const fieldValue = untracked(() => fieldContext.value());
614
- const rootFormValue = untracked(() => this.rootFormRegistry.formValue());
615
- const localKey = this.extractFieldPath(fieldContext);
616
- const pathKeys = safeReadPathKeys(fieldContext);
617
- const arrayScope = detectArrayScope(pathKeys);
618
- if (arrayScope) {
619
- return this.buildArrayScopedContext(rootFormValue, arrayScope, fieldValue, customFunctions, false, fieldContext);
598
+ skipWhitespace() {
599
+ while (this.position < this.expression.length && /\s/.test(this.expression[this.position])) {
600
+ this.position++;
620
601
  }
621
- const rootFormSignal = this.rootFormRegistry.rootForm;
622
- return {
623
- fieldValue,
624
- formValue: rootFormValue,
625
- fieldPath: localKey,
626
- customFunctions: customFunctions || {},
627
- externalData: this.resolveExternalData(false),
628
- logger: this.logger,
629
- deprecationTracker: this.deprecationTracker ?? undefined,
630
- get fieldState() {
631
- return readFieldStateInfo(extractFieldState(fieldContext), false);
632
- },
633
- get formFieldState() {
634
- return createFormFieldStateMap(untracked(rootFormSignal), false);
635
- },
636
- };
637
602
  }
638
- extractFieldPath(fieldContext) {
639
- if (isChildFieldContext(fieldContext)) {
640
- try {
641
- return String(fieldContext.key());
603
+ nextToken() {
604
+ const char = this.expression[this.position];
605
+ if (char === '"' || char === "'") {
606
+ return this.readString(char);
607
+ }
608
+ if (/[0-9]/.test(char)) {
609
+ return this.readNumber();
610
+ }
611
+ if (/[a-zA-Z_$]/.test(char)) {
612
+ return this.readIdentifierOrKeyword();
613
+ }
614
+ return this.readOperatorOrPunctuation();
615
+ }
616
+ readString(quote) {
617
+ const start = this.position;
618
+ this.position++; let value = '';
619
+ while (this.position < this.expression.length) {
620
+ const char = this.expression[this.position];
621
+ if (char === quote) {
622
+ this.position++; return { type: TokenType.STRING, value, position: start };
642
623
  }
643
- catch (error) {
644
- this.logger.warn('Unable to extract field key:', error);
624
+ if (char === '\\' && this.position + 1 < this.expression.length) {
625
+ this.position++;
626
+ const nextChar = this.expression[this.position];
627
+ switch (nextChar) {
628
+ case 'n':
629
+ value += '\n';
630
+ break;
631
+ case 't':
632
+ value += '\t';
633
+ break;
634
+ case 'r':
635
+ value += '\r';
636
+ break;
637
+ case '\\':
638
+ value += '\\';
639
+ break;
640
+ case quote:
641
+ value += quote;
642
+ break;
643
+ default:
644
+ value += nextChar;
645
+ }
646
+ this.position++;
647
+ }
648
+ else {
649
+ value += char;
650
+ this.position++;
645
651
  }
646
652
  }
647
- return '';
653
+ throw new ExpressionParserError('Unterminated string literal', start, this.expression);
648
654
  }
649
- buildArrayScopedContext(rootFormValue, arrayScope, fieldValue, customFunctions, reactive, fieldContext) {
650
- const { arrayKey, index, localKey } = arrayScope;
651
- const arrayData = getNestedValue(rootFormValue, arrayKey);
652
- let scopedFormValue;
653
- if (Array.isArray(arrayData) && index >= 0 && index < arrayData.length) {
654
- const item = arrayData[index];
655
- if (item != null && typeof item === 'object') {
656
- scopedFormValue = item;
657
- }
655
+ readNumber() {
656
+ const start = this.position;
657
+ let value = '';
658
+ while (this.position < this.expression.length && /[0-9.]/.test(this.expression[this.position])) {
659
+ value += this.expression[this.position];
660
+ this.position++;
658
661
  }
659
- const rootFormSignal = this.rootFormRegistry.rootForm;
660
- const fieldStateGetter = fieldContext ? () => readFieldStateInfo(extractFieldState(fieldContext), reactive) : () => undefined;
661
- const formFieldStateGetter = () => createFormFieldStateMap((reactive ? rootFormSignal() : untracked(rootFormSignal)), reactive);
662
- if (!scopedFormValue) {
663
- return {
664
- fieldValue,
665
- formValue: rootFormValue,
666
- fieldPath: localKey,
667
- customFunctions: customFunctions || {},
668
- externalData: this.resolveExternalData(reactive),
669
- logger: this.logger,
670
- deprecationTracker: this.deprecationTracker ?? undefined,
671
- get fieldState() {
672
- return fieldStateGetter();
673
- },
674
- get formFieldState() {
675
- return formFieldStateGetter();
676
- },
677
- };
662
+ if (!/^\d+(\.\d+)?$/.test(value)) {
663
+ throw new ExpressionParserError(`Invalid number: ${value}`, start, this.expression);
678
664
  }
679
- return {
680
- fieldValue,
681
- formValue: scopedFormValue,
682
- rootFormValue,
683
- arrayIndex: index,
684
- arrayPath: arrayKey,
685
- fieldPath: `${arrayKey}.${index}.${localKey}`,
686
- customFunctions: customFunctions || {},
687
- externalData: this.resolveExternalData(reactive),
688
- logger: this.logger,
689
- deprecationTracker: this.deprecationTracker ?? undefined,
690
- get fieldState() {
691
- return fieldStateGetter();
692
- },
693
- get formFieldState() {
694
- return formFieldStateGetter();
695
- },
696
- };
697
- }
698
- resolveExternalData(reactive) {
699
- const externalDataSignal = this.externalDataSignal;
700
- if (!externalDataSignal)
701
- return undefined;
702
- const externalDataRecord = reactive ? externalDataSignal() : untracked(() => externalDataSignal());
703
- if (!externalDataRecord) {
704
- return undefined;
705
- }
706
- const resolved = {};
707
- for (const [key, value] of Object.entries(externalDataRecord)) {
708
- if (!isSignal(value)) {
709
- throw new DynamicFormError(`externalData["${key}"] must be a Signal. Got: ${typeof value}. Wrap it with signal(yourValue).`);
710
- }
711
- resolved[key] = reactive ? value() : untracked(() => value());
712
- }
713
- return resolved;
665
+ return { type: TokenType.NUMBER, value, position: start };
714
666
  }
715
- createReactiveEvaluationContext(fieldContext, customFunctions) {
716
- const fieldValue = fieldContext.value();
717
- const rootFormValue = this.rootFormRegistry.formValue();
718
- const pathKeys = safeReadPathKeys(fieldContext);
719
- const arrayScope = detectArrayScope(pathKeys);
720
- if (arrayScope) {
721
- return this.buildArrayScopedContext(rootFormValue, arrayScope, fieldValue, customFunctions, true, fieldContext);
667
+ readIdentifierOrKeyword() {
668
+ const start = this.position;
669
+ let value = '';
670
+ while (this.position < this.expression.length && /[a-zA-Z0-9_$]/.test(this.expression[this.position])) {
671
+ value += this.expression[this.position];
672
+ this.position++;
722
673
  }
723
- const localKey = this.extractFieldPath(fieldContext);
724
- const rootFormSignal = this.rootFormRegistry.rootForm;
725
- return {
726
- fieldValue,
727
- formValue: rootFormValue,
728
- fieldPath: localKey,
729
- customFunctions: customFunctions || {},
730
- externalData: this.resolveExternalData(true),
731
- logger: this.logger,
732
- deprecationTracker: this.deprecationTracker ?? undefined,
733
- get fieldState() {
734
- return readFieldStateInfo(extractFieldState(fieldContext), true);
735
- },
736
- get formFieldState() {
737
- return createFormFieldStateMap(rootFormSignal(), true);
738
- },
739
- };
740
- }
741
- createDisplayOnlyContext(fieldPath, customFunctions) {
742
- const formValue = this.rootFormRegistry.formValue();
743
- const rootFormSignal = this.rootFormRegistry.rootForm;
744
- return {
745
- fieldValue: undefined,
746
- formValue,
747
- fieldPath,
748
- customFunctions: customFunctions || {},
749
- externalData: this.resolveExternalData(true),
750
- logger: this.logger,
751
- deprecationTracker: this.deprecationTracker ?? undefined,
752
- get formFieldState() {
753
- return createFormFieldStateMap(rootFormSignal(), true);
754
- },
755
- };
756
- }
757
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: FieldContextRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
758
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: FieldContextRegistryService });
759
- }
760
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: FieldContextRegistryService, decorators: [{
761
- type: Injectable
762
- }] });
763
-
764
- class FunctionRegistryService {
765
- customFunctions = new Map();
766
- customFunctionScopes = new Map();
767
- derivationFunctions = new Map();
768
- asyncDerivationFunctions = new Map();
769
- asyncConditionFunctions = new Map();
770
- validators = new Map();
771
- asyncValidators = new Map();
772
- httpValidators = new Map();
773
- registerCustomFunction(name, fn, options) {
774
- this.customFunctions.set(name, fn);
775
- this.customFunctionScopes.set(name, options?.scope ?? 'form');
776
- }
777
- getCustomFunctionScope(name) {
778
- return this.customFunctionScopes.get(name);
779
- }
780
- isFieldScopedFunction(name) {
781
- return this.customFunctionScopes.get(name) === 'field';
782
- }
783
- getCustomFunctions() {
784
- return Object.fromEntries(this.customFunctions);
785
- }
786
- clearCustomFunctions() {
787
- this.customFunctions.clear();
788
- this.customFunctionScopes.clear();
789
- }
790
- registerDerivationFunction(name, fn) {
791
- this.derivationFunctions.set(name, fn);
792
- }
793
- getDerivationFunction(name) {
794
- return this.derivationFunctions.get(name);
795
- }
796
- getDerivationFunctions() {
797
- return Object.fromEntries(this.derivationFunctions);
798
- }
799
- setDerivationFunctions(derivations) {
800
- this.setRegistryIfChanged(this.derivationFunctions, derivations);
801
- }
802
- clearDerivationFunctions() {
803
- this.derivationFunctions.clear();
804
- }
805
- registerAsyncDerivationFunction(name, fn) {
806
- this.asyncDerivationFunctions.set(name, fn);
807
- }
808
- getAsyncDerivationFunction(name) {
809
- return this.asyncDerivationFunctions.get(name);
810
- }
811
- getAsyncDerivationFunctions() {
812
- return Object.fromEntries(this.asyncDerivationFunctions);
813
- }
814
- setAsyncDerivationFunctions(fns) {
815
- this.setRegistryIfChanged(this.asyncDerivationFunctions, fns);
816
- }
817
- clearAsyncDerivationFunctions() {
818
- this.asyncDerivationFunctions.clear();
819
- }
820
- registerAsyncConditionFunction(name, fn) {
821
- this.asyncConditionFunctions.set(name, fn);
822
- }
823
- getAsyncConditionFunction(name) {
824
- return this.asyncConditionFunctions.get(name);
825
- }
826
- getAsyncConditionFunctions() {
827
- return Object.fromEntries(this.asyncConditionFunctions);
828
- }
829
- setAsyncConditionFunctions(fns) {
830
- this.setRegistryIfChanged(this.asyncConditionFunctions, fns);
831
- }
832
- clearAsyncConditionFunctions() {
833
- this.asyncConditionFunctions.clear();
834
- }
835
- registerValidator(name, fn) {
836
- this.validators.set(name, fn);
837
- }
838
- getValidator(name) {
839
- return this.validators.get(name);
840
- }
841
- clearValidators() {
842
- this.validators.clear();
843
- }
844
- registerAsyncValidator(name, fn) {
845
- this.asyncValidators.set(name, fn);
846
- }
847
- getAsyncValidator(name) {
848
- return this.asyncValidators.get(name);
849
- }
850
- clearAsyncValidators() {
851
- this.asyncValidators.clear();
852
- }
853
- registerHttpValidator(name, config) {
854
- this.httpValidators.set(name, config);
855
- }
856
- getHttpValidator(name) {
857
- return this.httpValidators.get(name);
858
- }
859
- clearHttpValidators() {
860
- this.httpValidators.clear();
674
+ const type = this.getKeywordType(value);
675
+ return { type, value, position: start };
861
676
  }
862
- setRegistryIfChanged(registry, values) {
863
- if (!values)
864
- return;
865
- const entries = Object.entries(values);
866
- const hasChanges = entries.some(([name, value]) => registry.get(name) !== value);
867
- if (hasChanges) {
868
- entries.forEach(([name, value]) => registry.set(name, value));
677
+ getKeywordType(value) {
678
+ switch (value) {
679
+ case 'true':
680
+ return TokenType.TRUE;
681
+ case 'false':
682
+ return TokenType.FALSE;
683
+ case 'null':
684
+ return TokenType.NULL;
685
+ case 'undefined':
686
+ return TokenType.UNDEFINED;
687
+ default:
688
+ return TokenType.IDENTIFIER;
869
689
  }
870
690
  }
871
- setValidators(validators) {
872
- this.setRegistryIfChanged(this.validators, validators);
873
- }
874
- setAsyncValidators(asyncValidators) {
875
- this.setRegistryIfChanged(this.asyncValidators, asyncValidators);
876
- }
877
- setHttpValidators(httpValidators) {
878
- this.setRegistryIfChanged(this.httpValidators, httpValidators);
879
- }
880
- clearAll() {
881
- this.clearCustomFunctions();
882
- this.clearDerivationFunctions();
883
- this.clearAsyncDerivationFunctions();
884
- this.clearAsyncConditionFunctions();
885
- this.clearValidators();
886
- this.clearAsyncValidators();
887
- this.clearHttpValidators();
888
- }
889
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: FunctionRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
890
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: FunctionRegistryService });
891
- }
892
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: FunctionRegistryService, decorators: [{
893
- type: Injectable
894
- }] });
895
-
896
- function isSignalArray(value) {
897
- return Array.isArray(value) && value.every((item) => isSignal(item));
898
- }
899
- function isSignalRecord(value) {
900
- return typeof value === 'object' && value !== null && !Array.isArray(value) && Object.values(value).every((item) => isSignal(item));
901
- }
902
- function derivedFromDeferred(source, operator, options) {
903
- const deferred$ = defer(() => {
904
- if (isSignal(source)) {
905
- return toObservable(source, { injector: options.injector });
906
- }
907
- if (isSignalArray(source)) {
908
- const observables = source.map((s) => toObservable(s, { injector: options.injector }));
909
- return combineLatest(observables);
910
- }
911
- if (isSignalRecord(source)) {
912
- const keys = Object.keys(source);
913
- const observables = keys.map((key) => toObservable(source[key], { injector: options.injector }));
914
- return combineLatest(observables, (...values) => {
915
- const result = {};
916
- keys.forEach((key, index) => {
917
- result[key] = values[index];
918
- });
919
- return result;
920
- });
921
- }
922
- throw new Error('Invalid source type. Expected a Signal, array of Signals, or object of Signals.');
923
- }).pipe(operator);
924
- return toSignal(deferred$, { initialValue: options.initialValue, injector: options.injector });
925
- }
926
-
927
- function withPreviousValue(input) {
928
- const derived = linkedSignal({ ...(ngDevMode ? { debugName: "derived" } : /* istanbul ignore next */ {}), source: input.snapshot,
929
- computation: (snapshot, previous) => {
930
- if ((snapshot.status === 'loading' || snapshot.status === 'reloading') &&
931
- previous !== undefined &&
932
- previous.value.status !== 'error') {
933
- return { status: snapshot.status, value: previous.value.value };
934
- }
935
- return snapshot;
936
- } });
937
- return resourceFromSnapshots(derived);
938
- }
939
-
940
- function createDebouncedResourceLogicFn(config) {
941
- const perFunctionSignalStore = new Map();
942
- return (ctx) => {
943
- const contextKey = safeReadPathKeys(ctx).join('.');
944
- let handle = perFunctionSignalStore.get(contextKey);
945
- if (!handle) {
946
- const trigger = signal(undefined, /* @ts-ignore */
947
- ...(ngDevMode ? [{ debugName: "trigger" }] : /* istanbul ignore next */ []));
948
- const resultResource = untracked(() => {
949
- const debouncedTrigger = derivedFromDeferred(trigger, pipe(debounceTime(config.debounceMs),
950
- distinctUntilChanged((a, b) => a?.key === b?.key)), { initialValue: undefined, injector: config.injector });
951
- const resource = rxResource({
952
- params: () => debouncedTrigger(),
953
- stream: ({ params }) => config.buildStream(params.payload),
954
- defaultValue: config.pendingValue,
955
- injector: config.injector,
956
- });
957
- return withPreviousValue(resource);
958
- });
959
- handle = { trigger, resultResource, refetchVersion: 0 };
960
- perFunctionSignalStore.set(contextKey, handle);
961
- }
962
- const resolved = config.resolve(ctx);
963
- if (resolved.kind === 'returnEarly') {
964
- handle.refetchVersion++;
965
- return resolved.value;
691
+ readOperatorOrPunctuation() {
692
+ const start = this.position;
693
+ const char = this.expression[this.position];
694
+ const nextChar = this.position + 1 < this.expression.length ? this.expression[this.position + 1] : '';
695
+ const thirdChar = this.position + 2 < this.expression.length ? this.expression[this.position + 2] : '';
696
+ const threeChar = char + nextChar + thirdChar;
697
+ if (threeChar === '===') {
698
+ this.position += 3;
699
+ return { type: TokenType.EQUAL, value: '===', position: start };
966
700
  }
967
- if (resolved.kind === 'holdPrevious') {
968
- return handle.resultResource.value();
701
+ if (threeChar === '!==') {
702
+ this.position += 3;
703
+ return { type: TokenType.NOT_EQUAL, value: '!==', position: start };
969
704
  }
970
- const dedupKey = `${resolved.key}::${handle.refetchVersion}`;
971
- const newTrigger = { key: dedupKey, payload: resolved.payload };
972
- untracked(() => {
973
- const current = handle.trigger();
974
- if (current?.key !== newTrigger.key) {
975
- handle.trigger.set(newTrigger);
976
- }
977
- });
978
- return handle.resultResource.value();
979
- };
980
- }
981
-
982
- function createAsyncConditionLogicFunction(condition) {
983
- const fieldContextRegistry = inject(FieldContextRegistryService);
984
- const functionRegistry = inject(FunctionRegistryService);
985
- const injector = inject(Injector);
986
- const logger = inject(DynamicFormLogger);
987
- const cacheService = inject(AsyncConditionFunctionCacheService);
988
- const pendingValue = condition.pendingValue ?? false;
989
- const debounceMs = condition.debounceMs ?? 300;
990
- if (condition.asyncFn && condition.asyncFunctionName) {
991
- logger.warn('Both "asyncFn" and "asyncFunctionName" are set on async condition. Inline "asyncFn" takes precedence; "asyncFunctionName" is ignored.');
992
- }
993
- const cacheKey = condition.asyncFn ? undefined : stableStringify(condition);
994
- if (cacheKey !== undefined) {
995
- const cached = cacheService.asyncConditionFunctionCache.get(cacheKey);
996
- if (cached) {
997
- return cached;
705
+ const twoChar = char + nextChar;
706
+ switch (twoChar) {
707
+ case '==':
708
+ this.position += 2;
709
+ return { type: TokenType.EQUAL, value: '==', position: start };
710
+ case '!=':
711
+ this.position += 2;
712
+ return { type: TokenType.NOT_EQUAL, value: '!=', position: start };
713
+ case '>=':
714
+ this.position += 2;
715
+ return { type: TokenType.GREATER_EQUAL, value: '>=', position: start };
716
+ case '<=':
717
+ this.position += 2;
718
+ return { type: TokenType.LESS_EQUAL, value: '<=', position: start };
719
+ case '&&':
720
+ this.position += 2;
721
+ return { type: TokenType.AND, value: '&&', position: start };
722
+ case '||':
723
+ this.position += 2;
724
+ return { type: TokenType.OR, value: '||', position: start };
725
+ case '=>':
726
+ this.position += 2;
727
+ return { type: TokenType.ARROW, value: '=>', position: start };
728
+ case '?.':
729
+ this.position += 2;
730
+ return { type: TokenType.OPTIONAL_DOT, value: '?.', position: start };
731
+ }
732
+ this.position++;
733
+ switch (char) {
734
+ case '+':
735
+ return { type: TokenType.PLUS, value: '+', position: start };
736
+ case '-':
737
+ return { type: TokenType.MINUS, value: '-', position: start };
738
+ case '*':
739
+ return { type: TokenType.MULTIPLY, value: '*', position: start };
740
+ case '/':
741
+ return { type: TokenType.DIVIDE, value: '/', position: start };
742
+ case '%':
743
+ return { type: TokenType.MODULO, value: '%', position: start };
744
+ case '>':
745
+ return { type: TokenType.GREATER, value: '>', position: start };
746
+ case '<':
747
+ return { type: TokenType.LESS, value: '<', position: start };
748
+ case '!':
749
+ return { type: TokenType.NOT, value: '!', position: start };
750
+ case '.':
751
+ return { type: TokenType.DOT, value: '.', position: start };
752
+ case ',':
753
+ return { type: TokenType.COMMA, value: ',', position: start };
754
+ case ':':
755
+ return { type: TokenType.COLON, value: ':', position: start };
756
+ case '(':
757
+ return { type: TokenType.LPAREN, value: '(', position: start };
758
+ case ')':
759
+ return { type: TokenType.RPAREN, value: ')', position: start };
760
+ case '[':
761
+ return { type: TokenType.LBRACKET, value: '[', position: start };
762
+ case ']':
763
+ return { type: TokenType.RBRACKET, value: ']', position: start };
764
+ case '{':
765
+ return { type: TokenType.LBRACE, value: '{', position: start };
766
+ case '}':
767
+ return { type: TokenType.RBRACE, value: '}', position: start };
768
+ case '?':
769
+ return { type: TokenType.QUESTION, value: '?', position: start };
770
+ default:
771
+ throw new ExpressionParserError(`Unexpected character: ${char}`, start, this.expression);
998
772
  }
999
- }
1000
- const fn = createDebouncedResourceLogicFn({
1001
- pendingValue,
1002
- debounceMs,
1003
- injector,
1004
- resolve: (ctx) => {
1005
- const evaluationContext = fieldContextRegistry.createReactiveEvaluationContext(ctx, functionRegistry.getCustomFunctions());
1006
- const key = stableStringify({
1007
- formValue: evaluationContext.formValue,
1008
- fieldValue: evaluationContext.fieldValue,
1009
- externalData: evaluationContext.externalData,
1010
- });
1011
- return { kind: 'trigger', key, payload: ctx };
1012
- },
1013
- buildStream: (capturedCtx) => {
1014
- const asyncFn = condition.asyncFn ??
1015
- (condition.asyncFunctionName ? functionRegistry.getAsyncConditionFunction(condition.asyncFunctionName) : undefined);
1016
- if (!asyncFn) {
1017
- logger.warn(`Async Condition - function '${condition.asyncFunctionName}' not found. ` + `Register it in customFnConfig.asyncConditions.`);
1018
- return of(pendingValue);
1019
- }
1020
- const evaluationContext = fieldContextRegistry.createReactiveEvaluationContext(capturedCtx, functionRegistry.getCustomFunctions());
1021
- return from(asyncFn(evaluationContext)).pipe(map((result) => !!result), catchError((error) => {
1022
- logger.warn('Async Condition - function failed:', error);
1023
- return of(pendingValue);
1024
- }));
1025
- },
1026
- });
1027
- if (cacheKey !== undefined) {
1028
- cacheService.asyncConditionFunctionCache.set(cacheKey, fn);
1029
- }
1030
- return fn;
1031
- }
1032
-
1033
- var TokenType;
1034
- (function (TokenType) {
1035
- TokenType["NUMBER"] = "NUMBER";
1036
- TokenType["STRING"] = "STRING";
1037
- TokenType["TRUE"] = "TRUE";
1038
- TokenType["FALSE"] = "FALSE";
1039
- TokenType["NULL"] = "NULL";
1040
- TokenType["UNDEFINED"] = "UNDEFINED";
1041
- TokenType["IDENTIFIER"] = "IDENTIFIER";
1042
- TokenType["PLUS"] = "PLUS";
1043
- TokenType["MINUS"] = "MINUS";
1044
- TokenType["MULTIPLY"] = "MULTIPLY";
1045
- TokenType["DIVIDE"] = "DIVIDE";
1046
- TokenType["MODULO"] = "MODULO";
1047
- TokenType["EQUAL"] = "EQUAL";
1048
- TokenType["NOT_EQUAL"] = "NOT_EQUAL";
1049
- TokenType["GREATER"] = "GREATER";
1050
- TokenType["LESS"] = "LESS";
1051
- TokenType["GREATER_EQUAL"] = "GREATER_EQUAL";
1052
- TokenType["LESS_EQUAL"] = "LESS_EQUAL";
1053
- TokenType["AND"] = "AND";
1054
- TokenType["OR"] = "OR";
1055
- TokenType["NOT"] = "NOT";
1056
- TokenType["DOT"] = "DOT";
1057
- TokenType["COMMA"] = "COMMA";
1058
- TokenType["COLON"] = "COLON";
1059
- TokenType["LPAREN"] = "LPAREN";
1060
- TokenType["RPAREN"] = "RPAREN";
1061
- TokenType["LBRACKET"] = "LBRACKET";
1062
- TokenType["RBRACKET"] = "RBRACKET";
1063
- TokenType["LBRACE"] = "LBRACE";
1064
- TokenType["RBRACE"] = "RBRACE";
1065
- TokenType["ARROW"] = "ARROW";
1066
- TokenType["QUESTION"] = "QUESTION";
1067
- TokenType["OPTIONAL_DOT"] = "OPTIONAL_DOT";
1068
- TokenType["EOF"] = "EOF";
1069
- })(TokenType || (TokenType = {}));
1070
- class ExpressionParserError extends DynamicFormError {
1071
- position;
1072
- expression;
1073
- constructor(message, position, expression) {
1074
- super(`${message} at position ${position} in expression: ${expression}`);
1075
- this.position = position;
1076
- this.expression = expression;
1077
- this.name = 'ExpressionParserError';
1078
773
  }
1079
774
  }
1080
775
 
1081
- class Tokenizer {
1082
- position = 0;
776
+ class Parser {
777
+ tokens = [];
778
+ current = 0;
1083
779
  expression;
1084
780
  constructor(expression) {
1085
781
  this.expression = expression;
1086
782
  }
1087
- tokenize() {
1088
- const tokens = [];
1089
- while (this.position < this.expression.length) {
1090
- this.skipWhitespace();
1091
- if (this.position >= this.expression.length) {
1092
- break;
783
+ parse() {
784
+ const tokenizer = new Tokenizer(this.expression);
785
+ this.tokens = tokenizer.tokenize();
786
+ this.current = 0;
787
+ if (this.isAtEnd()) {
788
+ throw new ExpressionParserError('Empty expression', 0, this.expression);
789
+ }
790
+ const result = this.parseExpression();
791
+ if (!this.isAtEnd()) {
792
+ const token = this.peek();
793
+ throw new ExpressionParserError(`Unexpected token '${token.value}' at position ${token.position} — expression has trailing content`, token.position, this.expression);
794
+ }
795
+ return result;
796
+ }
797
+ parseExpression() {
798
+ const arrow = this.tryParseArrowFunction();
799
+ if (arrow)
800
+ return arrow;
801
+ return this.parseConditional();
802
+ }
803
+ parseConditional() {
804
+ const test = this.parseLogicalOr();
805
+ if (!this.match(TokenType.QUESTION))
806
+ return test;
807
+ const consequent = this.parseExpression();
808
+ this.consume(TokenType.COLON, 'Expected ":" between ternary branches');
809
+ const alternate = this.parseExpression();
810
+ return { type: 'Conditional', test, consequent, alternate };
811
+ }
812
+ tryParseArrowFunction() {
813
+ const savedPos = this.current;
814
+ if (this.check(TokenType.IDENTIFIER) && this.checkAt(this.current + 1, TokenType.ARROW)) {
815
+ const param = this.advance().value;
816
+ this.advance(); const body = this.parseExpression();
817
+ return { type: 'ArrowFunction', params: [param], body };
818
+ }
819
+ if (this.check(TokenType.LPAREN)) {
820
+ this.advance(); const params = [];
821
+ if (this.check(TokenType.RPAREN)) {
822
+ this.advance(); }
823
+ else if (this.check(TokenType.IDENTIFIER)) {
824
+ params.push(this.advance().value);
825
+ while (this.check(TokenType.COMMA)) {
826
+ this.advance(); if (!this.check(TokenType.IDENTIFIER)) {
827
+ this.current = savedPos;
828
+ return null;
829
+ }
830
+ params.push(this.advance().value);
831
+ }
832
+ if (!this.check(TokenType.RPAREN)) {
833
+ this.current = savedPos;
834
+ return null;
835
+ }
836
+ this.advance(); }
837
+ else {
838
+ this.current = savedPos;
839
+ return null;
1093
840
  }
1094
- const token = this.nextToken();
1095
- tokens.push(token);
841
+ if (!this.check(TokenType.ARROW)) {
842
+ this.current = savedPos;
843
+ return null;
844
+ }
845
+ this.advance(); const body = this.parseExpression();
846
+ return { type: 'ArrowFunction', params, body };
1096
847
  }
1097
- tokens.push({ type: TokenType.EOF, value: '', position: this.position });
1098
- return tokens;
848
+ return null;
1099
849
  }
1100
- skipWhitespace() {
1101
- while (this.position < this.expression.length && /\s/.test(this.expression[this.position])) {
1102
- this.position++;
850
+ checkAt(index, type) {
851
+ if (index < 0 || index >= this.tokens.length)
852
+ return false;
853
+ return this.tokens[index].type === type;
854
+ }
855
+ parseLogicalOr() {
856
+ let node = this.parseLogicalAnd();
857
+ while (this.match(TokenType.OR)) {
858
+ const operator = this.previous().value;
859
+ const right = this.parseLogicalAnd();
860
+ node = { type: 'BinaryOp', operator, left: node, right };
1103
861
  }
862
+ return node;
1104
863
  }
1105
- nextToken() {
1106
- const char = this.expression[this.position];
1107
- if (char === '"' || char === "'") {
1108
- return this.readString(char);
864
+ parseLogicalAnd() {
865
+ let node = this.parseEquality();
866
+ while (this.match(TokenType.AND)) {
867
+ const operator = this.previous().value;
868
+ const right = this.parseEquality();
869
+ node = { type: 'BinaryOp', operator, left: node, right };
1109
870
  }
1110
- if (/[0-9]/.test(char)) {
1111
- return this.readNumber();
871
+ return node;
872
+ }
873
+ parseEquality() {
874
+ let node = this.parseComparison();
875
+ while (this.match(TokenType.EQUAL, TokenType.NOT_EQUAL)) {
876
+ const operator = this.previous().value;
877
+ const right = this.parseComparison();
878
+ node = { type: 'BinaryOp', operator, left: node, right };
1112
879
  }
1113
- if (/[a-zA-Z_$]/.test(char)) {
1114
- return this.readIdentifierOrKeyword();
880
+ return node;
881
+ }
882
+ parseComparison() {
883
+ let node = this.parseAddition();
884
+ while (this.match(TokenType.GREATER, TokenType.GREATER_EQUAL, TokenType.LESS, TokenType.LESS_EQUAL)) {
885
+ const operator = this.previous().value;
886
+ const right = this.parseAddition();
887
+ node = { type: 'BinaryOp', operator, left: node, right };
1115
888
  }
1116
- return this.readOperatorOrPunctuation();
889
+ return node;
1117
890
  }
1118
- readString(quote) {
1119
- const start = this.position;
1120
- this.position++; let value = '';
1121
- while (this.position < this.expression.length) {
1122
- const char = this.expression[this.position];
1123
- if (char === quote) {
1124
- this.position++; return { type: TokenType.STRING, value, position: start };
891
+ parseAddition() {
892
+ let node = this.parseMultiplication();
893
+ while (this.match(TokenType.PLUS, TokenType.MINUS)) {
894
+ const operator = this.previous().value;
895
+ const right = this.parseMultiplication();
896
+ node = { type: 'BinaryOp', operator, left: node, right };
897
+ }
898
+ return node;
899
+ }
900
+ parseMultiplication() {
901
+ let node = this.parseUnary();
902
+ while (this.match(TokenType.MULTIPLY, TokenType.DIVIDE, TokenType.MODULO)) {
903
+ const operator = this.previous().value;
904
+ const right = this.parseUnary();
905
+ node = { type: 'BinaryOp', operator, left: node, right };
906
+ }
907
+ return node;
908
+ }
909
+ parseUnary() {
910
+ if (this.match(TokenType.NOT, TokenType.MINUS, TokenType.PLUS)) {
911
+ const operator = this.previous().value;
912
+ const operand = this.parseUnary();
913
+ return { type: 'UnaryOp', operator, operand };
914
+ }
915
+ return this.parsePostfix();
916
+ }
917
+ parsePostfix() {
918
+ let node = this.parsePrimary();
919
+ while (true) {
920
+ if (this.match(TokenType.DOT)) {
921
+ if (!this.check(TokenType.IDENTIFIER)) {
922
+ throw new ExpressionParserError('Expected property name after "."', this.peek().position, this.expression);
923
+ }
924
+ const property = this.advance().value;
925
+ node = { type: 'MemberAccess', object: node, property };
1125
926
  }
1126
- if (char === '\\' && this.position + 1 < this.expression.length) {
1127
- this.position++;
1128
- const nextChar = this.expression[this.position];
1129
- switch (nextChar) {
1130
- case 'n':
1131
- value += '\n';
1132
- break;
1133
- case 't':
1134
- value += '\t';
1135
- break;
1136
- case 'r':
1137
- value += '\r';
1138
- break;
1139
- case '\\':
1140
- value += '\\';
1141
- break;
1142
- case quote:
1143
- value += quote;
1144
- break;
1145
- default:
1146
- value += nextChar;
927
+ else if (this.match(TokenType.OPTIONAL_DOT)) {
928
+ if (this.match(TokenType.LBRACKET)) {
929
+ const property = this.parseExpression();
930
+ this.consume(TokenType.RBRACKET, 'Expected "]" after computed property');
931
+ node = { type: 'ComputedMemberAccess', object: node, property, optional: true };
1147
932
  }
1148
- this.position++;
933
+ else if (this.match(TokenType.LPAREN)) {
934
+ const args = this.parseArgumentList();
935
+ this.consume(TokenType.RPAREN, 'Expected ")" after arguments');
936
+ node = { type: 'CallExpression', callee: node, arguments: args, optional: true };
937
+ }
938
+ else if (this.check(TokenType.IDENTIFIER)) {
939
+ const property = this.advance().value;
940
+ node = { type: 'MemberAccess', object: node, property, optional: true };
941
+ }
942
+ else {
943
+ throw new ExpressionParserError('Expected property name, "[", or "(" after "?."', this.peek().position, this.expression);
944
+ }
945
+ }
946
+ else if (this.match(TokenType.LBRACKET)) {
947
+ const property = this.parseExpression();
948
+ this.consume(TokenType.RBRACKET, 'Expected "]" after computed property');
949
+ node = { type: 'ComputedMemberAccess', object: node, property };
950
+ }
951
+ else if (this.match(TokenType.LPAREN)) {
952
+ const args = this.parseArgumentList();
953
+ this.consume(TokenType.RPAREN, 'Expected ")" after arguments');
954
+ node = { type: 'CallExpression', callee: node, arguments: args };
1149
955
  }
1150
956
  else {
1151
- value += char;
1152
- this.position++;
957
+ break;
1153
958
  }
1154
959
  }
1155
- throw new ExpressionParserError('Unterminated string literal', start, this.expression);
1156
- }
1157
- readNumber() {
1158
- const start = this.position;
1159
- let value = '';
1160
- while (this.position < this.expression.length && /[0-9.]/.test(this.expression[this.position])) {
1161
- value += this.expression[this.position];
1162
- this.position++;
960
+ return node;
961
+ }
962
+ parsePrimary() {
963
+ if (this.match(TokenType.TRUE)) {
964
+ return { type: 'Literal', value: true };
965
+ }
966
+ if (this.match(TokenType.FALSE)) {
967
+ return { type: 'Literal', value: false };
968
+ }
969
+ if (this.match(TokenType.NULL)) {
970
+ return { type: 'Literal', value: null };
1163
971
  }
1164
- if (!/^\d+(\.\d+)?$/.test(value)) {
1165
- throw new ExpressionParserError(`Invalid number: ${value}`, start, this.expression);
972
+ if (this.match(TokenType.UNDEFINED)) {
973
+ return { type: 'Literal', value: undefined };
1166
974
  }
1167
- return { type: TokenType.NUMBER, value, position: start };
1168
- }
1169
- readIdentifierOrKeyword() {
1170
- const start = this.position;
1171
- let value = '';
1172
- while (this.position < this.expression.length && /[a-zA-Z0-9_$]/.test(this.expression[this.position])) {
1173
- value += this.expression[this.position];
1174
- this.position++;
975
+ if (this.match(TokenType.NUMBER)) {
976
+ return { type: 'Literal', value: parseFloat(this.previous().value) };
1175
977
  }
1176
- const type = this.getKeywordType(value);
1177
- return { type, value, position: start };
1178
- }
1179
- getKeywordType(value) {
1180
- switch (value) {
1181
- case 'true':
1182
- return TokenType.TRUE;
1183
- case 'false':
1184
- return TokenType.FALSE;
1185
- case 'null':
1186
- return TokenType.NULL;
1187
- case 'undefined':
1188
- return TokenType.UNDEFINED;
1189
- default:
1190
- return TokenType.IDENTIFIER;
978
+ if (this.match(TokenType.STRING)) {
979
+ return { type: 'Literal', value: this.previous().value };
980
+ }
981
+ if (this.match(TokenType.LBRACKET)) {
982
+ const elements = this.parseArrayElements();
983
+ this.consume(TokenType.RBRACKET, 'Expected "]" after array elements');
984
+ return { type: 'ArrayLiteral', elements };
985
+ }
986
+ if (this.match(TokenType.LBRACE)) {
987
+ const properties = this.parseObjectProperties();
988
+ this.consume(TokenType.RBRACE, 'Expected "}" after object properties');
989
+ return { type: 'ObjectLiteral', properties };
990
+ }
991
+ if (this.match(TokenType.IDENTIFIER)) {
992
+ return { type: 'Identifier', name: this.previous().value };
993
+ }
994
+ if (this.match(TokenType.LPAREN)) {
995
+ const expr = this.parseExpression();
996
+ this.consume(TokenType.RPAREN, 'Expected ")" after expression');
997
+ return expr;
1191
998
  }
999
+ throw new ExpressionParserError(`Unexpected token: ${this.peek().value}`, this.peek().position, this.expression);
1192
1000
  }
1193
- readOperatorOrPunctuation() {
1194
- const start = this.position;
1195
- const char = this.expression[this.position];
1196
- const nextChar = this.position + 1 < this.expression.length ? this.expression[this.position + 1] : '';
1197
- const thirdChar = this.position + 2 < this.expression.length ? this.expression[this.position + 2] : '';
1198
- const threeChar = char + nextChar + thirdChar;
1199
- if (threeChar === '===') {
1200
- this.position += 3;
1201
- return { type: TokenType.EQUAL, value: '===', position: start };
1001
+ parseArgumentList() {
1002
+ const args = [];
1003
+ if (!this.check(TokenType.RPAREN)) {
1004
+ do {
1005
+ args.push(this.parseExpression());
1006
+ } while (this.match(TokenType.COMMA));
1202
1007
  }
1203
- if (threeChar === '!==') {
1204
- this.position += 3;
1205
- return { type: TokenType.NOT_EQUAL, value: '!==', position: start };
1008
+ return args;
1009
+ }
1010
+ parseArrayElements() {
1011
+ const elements = [];
1012
+ if (!this.check(TokenType.RBRACKET)) {
1013
+ do {
1014
+ elements.push(this.parseExpression());
1015
+ } while (this.match(TokenType.COMMA));
1206
1016
  }
1207
- const twoChar = char + nextChar;
1208
- switch (twoChar) {
1209
- case '==':
1210
- this.position += 2;
1211
- return { type: TokenType.EQUAL, value: '==', position: start };
1212
- case '!=':
1213
- this.position += 2;
1214
- return { type: TokenType.NOT_EQUAL, value: '!=', position: start };
1215
- case '>=':
1216
- this.position += 2;
1217
- return { type: TokenType.GREATER_EQUAL, value: '>=', position: start };
1218
- case '<=':
1219
- this.position += 2;
1220
- return { type: TokenType.LESS_EQUAL, value: '<=', position: start };
1221
- case '&&':
1222
- this.position += 2;
1223
- return { type: TokenType.AND, value: '&&', position: start };
1224
- case '||':
1225
- this.position += 2;
1226
- return { type: TokenType.OR, value: '||', position: start };
1227
- case '=>':
1228
- this.position += 2;
1229
- return { type: TokenType.ARROW, value: '=>', position: start };
1230
- case '?.':
1231
- this.position += 2;
1232
- return { type: TokenType.OPTIONAL_DOT, value: '?.', position: start };
1017
+ return elements;
1018
+ }
1019
+ parseObjectProperties() {
1020
+ const properties = [];
1021
+ if (this.check(TokenType.RBRACE)) {
1022
+ return properties;
1233
1023
  }
1234
- this.position++;
1235
- switch (char) {
1236
- case '+':
1237
- return { type: TokenType.PLUS, value: '+', position: start };
1238
- case '-':
1239
- return { type: TokenType.MINUS, value: '-', position: start };
1240
- case '*':
1241
- return { type: TokenType.MULTIPLY, value: '*', position: start };
1242
- case '/':
1243
- return { type: TokenType.DIVIDE, value: '/', position: start };
1244
- case '%':
1245
- return { type: TokenType.MODULO, value: '%', position: start };
1246
- case '>':
1247
- return { type: TokenType.GREATER, value: '>', position: start };
1248
- case '<':
1249
- return { type: TokenType.LESS, value: '<', position: start };
1250
- case '!':
1251
- return { type: TokenType.NOT, value: '!', position: start };
1252
- case '.':
1253
- return { type: TokenType.DOT, value: '.', position: start };
1254
- case ',':
1255
- return { type: TokenType.COMMA, value: ',', position: start };
1256
- case ':':
1257
- return { type: TokenType.COLON, value: ':', position: start };
1258
- case '(':
1259
- return { type: TokenType.LPAREN, value: '(', position: start };
1260
- case ')':
1261
- return { type: TokenType.RPAREN, value: ')', position: start };
1262
- case '[':
1263
- return { type: TokenType.LBRACKET, value: '[', position: start };
1264
- case ']':
1265
- return { type: TokenType.RBRACKET, value: ']', position: start };
1266
- case '{':
1267
- return { type: TokenType.LBRACE, value: '{', position: start };
1268
- case '}':
1269
- return { type: TokenType.RBRACE, value: '}', position: start };
1270
- case '?':
1271
- return { type: TokenType.QUESTION, value: '?', position: start };
1272
- default:
1273
- throw new ExpressionParserError(`Unexpected character: ${char}`, start, this.expression);
1024
+ do {
1025
+ if (this.check(TokenType.RBRACE))
1026
+ break;
1027
+ let key;
1028
+ if (this.check(TokenType.IDENTIFIER) || this.check(TokenType.STRING)) {
1029
+ key = this.advance().value;
1030
+ }
1031
+ else {
1032
+ throw new ExpressionParserError(`Expected property name (identifier or string) in object literal, got '${this.peek().value}'`, this.peek().position, this.expression);
1033
+ }
1034
+ this.consume(TokenType.COLON, 'Expected ":" after property name in object literal');
1035
+ const value = this.parseExpression();
1036
+ properties.push({ key, value });
1037
+ } while (this.match(TokenType.COMMA));
1038
+ return properties;
1039
+ }
1040
+ match(...types) {
1041
+ for (const type of types) {
1042
+ if (this.check(type)) {
1043
+ this.advance();
1044
+ return true;
1045
+ }
1274
1046
  }
1047
+ return false;
1048
+ }
1049
+ check(type) {
1050
+ if (this.isAtEnd())
1051
+ return false;
1052
+ return this.peek().type === type;
1053
+ }
1054
+ advance() {
1055
+ if (!this.isAtEnd())
1056
+ this.current++;
1057
+ return this.previous();
1058
+ }
1059
+ isAtEnd() {
1060
+ return this.peek().type === TokenType.EOF;
1061
+ }
1062
+ peek() {
1063
+ return this.tokens[this.current];
1064
+ }
1065
+ previous() {
1066
+ return this.tokens[this.current - 1];
1067
+ }
1068
+ consume(type, message) {
1069
+ if (this.check(type))
1070
+ return this.advance();
1071
+ throw new ExpressionParserError(message, this.peek().position, this.expression);
1275
1072
  }
1276
1073
  }
1277
1074
 
1278
- class Parser {
1279
- tokens = [];
1280
- current = 0;
1075
+ const STRING_SAFE_METHODS = [
1076
+ 'charAt',
1077
+ 'charCodeAt',
1078
+ 'concat',
1079
+ 'endsWith',
1080
+ 'includes',
1081
+ 'indexOf',
1082
+ 'lastIndexOf',
1083
+ 'match',
1084
+ 'padEnd',
1085
+ 'padStart',
1086
+ 'repeat',
1087
+ 'replace',
1088
+ 'search',
1089
+ 'slice',
1090
+ 'split',
1091
+ 'startsWith',
1092
+ 'substring',
1093
+ 'toLowerCase',
1094
+ 'toUpperCase',
1095
+ 'trim',
1096
+ 'trimEnd',
1097
+ 'trimStart',
1098
+ 'toString',
1099
+ ];
1100
+ const NUMBER_SAFE_METHODS = ['toExponential', 'toFixed', 'toPrecision', 'toString'];
1101
+ const ARRAY_SAFE_METHODS = [
1102
+ 'concat',
1103
+ 'every',
1104
+ 'filter',
1105
+ 'find',
1106
+ 'findIndex',
1107
+ 'flat',
1108
+ 'flatMap',
1109
+ 'includes',
1110
+ 'indexOf',
1111
+ 'join',
1112
+ 'lastIndexOf',
1113
+ 'map',
1114
+ 'reduce',
1115
+ 'reduceRight',
1116
+ 'slice',
1117
+ 'some',
1118
+ 'toString',
1119
+ 'entries',
1120
+ 'keys',
1121
+ 'values',
1122
+ ];
1123
+ const DATE_SAFE_METHODS = [
1124
+ 'getDate',
1125
+ 'getDay',
1126
+ 'getFullYear',
1127
+ 'getHours',
1128
+ 'getMilliseconds',
1129
+ 'getMinutes',
1130
+ 'getMonth',
1131
+ 'getSeconds',
1132
+ 'getTime',
1133
+ 'getTimezoneOffset',
1134
+ 'getUTCDate',
1135
+ 'getUTCDay',
1136
+ 'getUTCFullYear',
1137
+ 'getUTCHours',
1138
+ 'getUTCMilliseconds',
1139
+ 'getUTCMinutes',
1140
+ 'getUTCMonth',
1141
+ 'getUTCSeconds',
1142
+ 'toDateString',
1143
+ 'toISOString',
1144
+ 'toJSON',
1145
+ 'toString',
1146
+ 'toTimeString',
1147
+ 'toUTCString',
1148
+ ];
1149
+ const SAFE_METHODS = {
1150
+ string: new Set(STRING_SAFE_METHODS),
1151
+ number: new Set(NUMBER_SAFE_METHODS),
1152
+ array: new Set(ARRAY_SAFE_METHODS),
1153
+ date: new Set(DATE_SAFE_METHODS),
1154
+ };
1155
+ const BLOCKED_PROPERTIES = new Set([
1156
+ 'constructor',
1157
+ '__proto__',
1158
+ 'prototype',
1159
+ '__defineGetter__',
1160
+ '__defineSetter__',
1161
+ '__lookupGetter__',
1162
+ '__lookupSetter__',
1163
+ ]);
1164
+ class Evaluator {
1165
+ scope;
1281
1166
  expression;
1282
- constructor(expression) {
1167
+ constructor(scope, expression) {
1168
+ this.scope = scope;
1283
1169
  this.expression = expression;
1284
1170
  }
1285
- parse() {
1286
- const tokenizer = new Tokenizer(this.expression);
1287
- this.tokens = tokenizer.tokenize();
1288
- this.current = 0;
1289
- if (this.isAtEnd()) {
1290
- throw new ExpressionParserError('Empty expression', 0, this.expression);
1291
- }
1292
- const result = this.parseExpression();
1293
- if (!this.isAtEnd()) {
1294
- const token = this.peek();
1295
- throw new ExpressionParserError(`Unexpected token '${token.value}' at position ${token.position} — expression has trailing content`, token.position, this.expression);
1171
+ evaluate(node) {
1172
+ switch (node.type) {
1173
+ case 'Literal':
1174
+ return node.value;
1175
+ case 'Identifier':
1176
+ return this.evaluateIdentifier(node.name);
1177
+ case 'MemberAccess':
1178
+ return this.evaluateMemberAccess(node);
1179
+ case 'ComputedMemberAccess':
1180
+ return this.evaluateComputedMemberAccess(node);
1181
+ case 'BinaryOp':
1182
+ return this.evaluateBinaryOp(node);
1183
+ case 'UnaryOp':
1184
+ return this.evaluateUnaryOp(node);
1185
+ case 'CallExpression':
1186
+ return this.evaluateCallExpression(node);
1187
+ case 'Conditional':
1188
+ return this.evaluate(node.test) ? this.evaluate(node.consequent) : this.evaluate(node.alternate);
1189
+ case 'ArrayLiteral':
1190
+ return this.evaluateArrayLiteral(node);
1191
+ case 'ObjectLiteral':
1192
+ return this.evaluateObjectLiteral(node);
1193
+ case 'ArrowFunction':
1194
+ return this.evaluateArrowFunction(node);
1195
+ default:
1196
+ throw new ExpressionParserError(`Unknown node type: ${node.type}`, 0, this.expression);
1296
1197
  }
1297
- return result;
1298
- }
1299
- parseExpression() {
1300
- const arrow = this.tryParseArrowFunction();
1301
- if (arrow)
1302
- return arrow;
1303
- return this.parseConditional();
1304
1198
  }
1305
- parseConditional() {
1306
- const test = this.parseLogicalOr();
1307
- if (!this.match(TokenType.QUESTION))
1308
- return test;
1309
- const consequent = this.parseExpression();
1310
- this.consume(TokenType.COLON, 'Expected ":" between ternary branches');
1311
- const alternate = this.parseExpression();
1312
- return { type: 'Conditional', test, consequent, alternate };
1199
+ evaluateIdentifier(name) {
1200
+ if (name in this.scope) {
1201
+ return this.scope[name];
1202
+ }
1203
+ return undefined;
1313
1204
  }
1314
- tryParseArrowFunction() {
1315
- const savedPos = this.current;
1316
- if (this.check(TokenType.IDENTIFIER) && this.checkAt(this.current + 1, TokenType.ARROW)) {
1317
- const param = this.advance().value;
1318
- this.advance(); const body = this.parseExpression();
1319
- return { type: 'ArrowFunction', params: [param], body };
1205
+ evaluateMemberAccess(node) {
1206
+ const obj = this.evaluate(node.object);
1207
+ if (obj === null || obj === undefined) {
1208
+ return undefined;
1320
1209
  }
1321
- if (this.check(TokenType.LPAREN)) {
1322
- this.advance(); const params = [];
1323
- if (this.check(TokenType.RPAREN)) {
1324
- this.advance(); }
1325
- else if (this.check(TokenType.IDENTIFIER)) {
1326
- params.push(this.advance().value);
1327
- while (this.check(TokenType.COMMA)) {
1328
- this.advance(); if (!this.check(TokenType.IDENTIFIER)) {
1329
- this.current = savedPos;
1330
- return null;
1331
- }
1332
- params.push(this.advance().value);
1333
- }
1334
- if (!this.check(TokenType.RPAREN)) {
1335
- this.current = savedPos;
1336
- return null;
1337
- }
1338
- this.advance(); }
1339
- else {
1340
- this.current = savedPos;
1341
- return null;
1342
- }
1343
- if (!this.check(TokenType.ARROW)) {
1344
- this.current = savedPos;
1345
- return null;
1346
- }
1347
- this.advance(); const body = this.parseExpression();
1348
- return { type: 'ArrowFunction', params, body };
1210
+ if (BLOCKED_PROPERTIES.has(node.property)) {
1211
+ throw new ExpressionParserError(`Property "${node.property}" is not accessible for security reasons`, 0, this.expression);
1349
1212
  }
1350
- return null;
1351
- }
1352
- checkAt(index, type) {
1353
- if (index < 0 || index >= this.tokens.length)
1354
- return false;
1355
- return this.tokens[index].type === type;
1356
- }
1357
- parseLogicalOr() {
1358
- let node = this.parseLogicalAnd();
1359
- while (this.match(TokenType.OR)) {
1360
- const operator = this.previous().value;
1361
- const right = this.parseLogicalAnd();
1362
- node = { type: 'BinaryOp', operator, left: node, right };
1213
+ if (typeof obj === 'object' || typeof obj === 'string' || typeof obj === 'number') {
1214
+ return obj[node.property];
1363
1215
  }
1364
- return node;
1216
+ return undefined;
1365
1217
  }
1366
- parseLogicalAnd() {
1367
- let node = this.parseEquality();
1368
- while (this.match(TokenType.AND)) {
1369
- const operator = this.previous().value;
1370
- const right = this.parseEquality();
1371
- node = { type: 'BinaryOp', operator, left: node, right };
1218
+ evaluateComputedMemberAccess(node) {
1219
+ const obj = this.evaluate(node.object);
1220
+ if (obj === null || obj === undefined) {
1221
+ return undefined;
1372
1222
  }
1373
- return node;
1374
- }
1375
- parseEquality() {
1376
- let node = this.parseComparison();
1377
- while (this.match(TokenType.EQUAL, TokenType.NOT_EQUAL)) {
1378
- const operator = this.previous().value;
1379
- const right = this.parseComparison();
1380
- node = { type: 'BinaryOp', operator, left: node, right };
1223
+ const rawKey = this.evaluate(node.property);
1224
+ if (rawKey === null || rawKey === undefined)
1225
+ return undefined;
1226
+ const key = String(rawKey);
1227
+ if (BLOCKED_PROPERTIES.has(key)) {
1228
+ throw new ExpressionParserError(`Property "${key}" is not accessible for security reasons`, 0, this.expression);
1381
1229
  }
1382
- return node;
1383
- }
1384
- parseComparison() {
1385
- let node = this.parseAddition();
1386
- while (this.match(TokenType.GREATER, TokenType.GREATER_EQUAL, TokenType.LESS, TokenType.LESS_EQUAL)) {
1387
- const operator = this.previous().value;
1388
- const right = this.parseAddition();
1389
- node = { type: 'BinaryOp', operator, left: node, right };
1230
+ if (typeof obj === 'object' || typeof obj === 'string' || typeof obj === 'number') {
1231
+ return obj[key];
1390
1232
  }
1391
- return node;
1233
+ return undefined;
1392
1234
  }
1393
- parseAddition() {
1394
- let node = this.parseMultiplication();
1395
- while (this.match(TokenType.PLUS, TokenType.MINUS)) {
1396
- const operator = this.previous().value;
1397
- const right = this.parseMultiplication();
1398
- node = { type: 'BinaryOp', operator, left: node, right };
1235
+ evaluateBinaryOp(node) {
1236
+ if (node.operator === '&&') {
1237
+ const left = this.evaluate(node.left);
1238
+ return left ? this.evaluate(node.right) : left;
1399
1239
  }
1400
- return node;
1401
- }
1402
- parseMultiplication() {
1403
- let node = this.parseUnary();
1404
- while (this.match(TokenType.MULTIPLY, TokenType.DIVIDE, TokenType.MODULO)) {
1405
- const operator = this.previous().value;
1406
- const right = this.parseUnary();
1407
- node = { type: 'BinaryOp', operator, left: node, right };
1240
+ if (node.operator === '||') {
1241
+ const left = this.evaluate(node.left);
1242
+ return left ? left : this.evaluate(node.right);
1408
1243
  }
1409
- return node;
1410
- }
1411
- parseUnary() {
1412
- if (this.match(TokenType.NOT, TokenType.MINUS, TokenType.PLUS)) {
1413
- const operator = this.previous().value;
1414
- const operand = this.parseUnary();
1415
- return { type: 'UnaryOp', operator, operand };
1244
+ const left = this.evaluate(node.left);
1245
+ const right = this.evaluate(node.right);
1246
+ switch (node.operator) {
1247
+ case '+':
1248
+ return left + right;
1249
+ case '-':
1250
+ return left - right;
1251
+ case '*':
1252
+ return left * right;
1253
+ case '/':
1254
+ if (right === 0)
1255
+ return null;
1256
+ return left / right;
1257
+ case '%':
1258
+ if (right === 0)
1259
+ return null;
1260
+ return left % right;
1261
+ case '==':
1262
+ return left == right;
1263
+ case '===':
1264
+ return left === right;
1265
+ case '!=':
1266
+ return left != right;
1267
+ case '!==':
1268
+ return left !== right;
1269
+ case '>':
1270
+ return left > right;
1271
+ case '<':
1272
+ return left < right;
1273
+ case '>=':
1274
+ return left >= right;
1275
+ case '<=':
1276
+ return left <= right;
1277
+ default:
1278
+ throw new ExpressionParserError(`Unknown binary operator: ${node.operator}`, 0, this.expression);
1416
1279
  }
1417
- return this.parsePostfix();
1418
1280
  }
1419
- parsePostfix() {
1420
- let node = this.parsePrimary();
1421
- while (true) {
1422
- if (this.match(TokenType.DOT)) {
1423
- if (!this.check(TokenType.IDENTIFIER)) {
1424
- throw new ExpressionParserError('Expected property name after "."', this.peek().position, this.expression);
1425
- }
1426
- const property = this.advance().value;
1427
- node = { type: 'MemberAccess', object: node, property };
1428
- }
1429
- else if (this.match(TokenType.OPTIONAL_DOT)) {
1430
- if (this.match(TokenType.LBRACKET)) {
1431
- const property = this.parseExpression();
1432
- this.consume(TokenType.RBRACKET, 'Expected "]" after computed property');
1433
- node = { type: 'ComputedMemberAccess', object: node, property, optional: true };
1434
- }
1435
- else if (this.match(TokenType.LPAREN)) {
1436
- const args = this.parseArgumentList();
1437
- this.consume(TokenType.RPAREN, 'Expected ")" after arguments');
1438
- node = { type: 'CallExpression', callee: node, arguments: args, optional: true };
1439
- }
1440
- else if (this.check(TokenType.IDENTIFIER)) {
1441
- const property = this.advance().value;
1442
- node = { type: 'MemberAccess', object: node, property, optional: true };
1443
- }
1444
- else {
1445
- throw new ExpressionParserError('Expected property name, "[", or "(" after "?."', this.peek().position, this.expression);
1446
- }
1447
- }
1448
- else if (this.match(TokenType.LBRACKET)) {
1449
- const property = this.parseExpression();
1450
- this.consume(TokenType.RBRACKET, 'Expected "]" after computed property');
1451
- node = { type: 'ComputedMemberAccess', object: node, property };
1452
- }
1453
- else if (this.match(TokenType.LPAREN)) {
1454
- const args = this.parseArgumentList();
1455
- this.consume(TokenType.RPAREN, 'Expected ")" after arguments');
1456
- node = { type: 'CallExpression', callee: node, arguments: args };
1457
- }
1458
- else {
1459
- break;
1460
- }
1281
+ evaluateUnaryOp(node) {
1282
+ const operand = this.evaluate(node.operand);
1283
+ switch (node.operator) {
1284
+ case '!':
1285
+ return !operand;
1286
+ case '-':
1287
+ return -operand;
1288
+ case '+':
1289
+ return +operand;
1290
+ default:
1291
+ throw new ExpressionParserError(`Unknown unary operator: ${node.operator}`, 0, this.expression);
1461
1292
  }
1462
- return node;
1463
1293
  }
1464
- parsePrimary() {
1465
- if (this.match(TokenType.TRUE)) {
1466
- return { type: 'Literal', value: true };
1467
- }
1468
- if (this.match(TokenType.FALSE)) {
1469
- return { type: 'Literal', value: false };
1294
+ evaluateCallExpression(node) {
1295
+ if (node.callee.type !== 'MemberAccess') {
1296
+ throw new ExpressionParserError('Only method calls are allowed, not arbitrary function calls', 0, this.expression);
1470
1297
  }
1471
- if (this.match(TokenType.NULL)) {
1472
- return { type: 'Literal', value: null };
1298
+ const obj = this.evaluate(node.callee.object);
1299
+ if (obj === null || obj === undefined) {
1300
+ return undefined;
1473
1301
  }
1474
- if (this.match(TokenType.UNDEFINED)) {
1475
- return { type: 'Literal', value: undefined };
1302
+ const methodName = node.callee.property;
1303
+ if (BLOCKED_PROPERTIES.has(methodName)) {
1304
+ throw new ExpressionParserError(`Property "${methodName}" is not accessible for security reasons`, 0, this.expression);
1476
1305
  }
1477
- if (this.match(TokenType.NUMBER)) {
1478
- return { type: 'Literal', value: parseFloat(this.previous().value) };
1306
+ if (!this.isMethodSafe(obj, methodName)) {
1307
+ throw new ExpressionParserError(`Method "${methodName}" is not allowed for security reasons`, 0, this.expression);
1479
1308
  }
1480
- if (this.match(TokenType.STRING)) {
1481
- return { type: 'Literal', value: this.previous().value };
1309
+ const args = node.arguments.map((arg) => this.evaluate(arg));
1310
+ const method = obj[methodName];
1311
+ if (typeof method === 'function') {
1312
+ return method.call(obj, ...args);
1482
1313
  }
1483
- if (this.match(TokenType.LBRACKET)) {
1484
- const elements = this.parseArrayElements();
1485
- this.consume(TokenType.RBRACKET, 'Expected "]" after array elements');
1486
- return { type: 'ArrayLiteral', elements };
1314
+ return undefined;
1315
+ }
1316
+ evaluateArrayLiteral(node) {
1317
+ return node.elements.map((element) => this.evaluate(element));
1318
+ }
1319
+ evaluateObjectLiteral(node) {
1320
+ const result = {};
1321
+ for (const { key, value } of node.properties) {
1322
+ if (BLOCKED_PROPERTIES.has(key)) {
1323
+ throw new ExpressionParserError(`Property "${key}" is not allowed in object literals for security reasons`, 0, this.expression);
1324
+ }
1325
+ result[key] = this.evaluate(value);
1487
1326
  }
1488
- if (this.match(TokenType.LBRACE)) {
1489
- const properties = this.parseObjectProperties();
1490
- this.consume(TokenType.RBRACE, 'Expected "}" after object properties');
1491
- return { type: 'ObjectLiteral', properties };
1327
+ return result;
1328
+ }
1329
+ evaluateArrowFunction(node) {
1330
+ const parentScope = this.scope;
1331
+ const expression = this.expression;
1332
+ return (...args) => {
1333
+ const childScope = { ...parentScope };
1334
+ for (let i = 0; i < node.params.length; i++) {
1335
+ childScope[node.params[i]] = args[i];
1336
+ }
1337
+ const childEvaluator = new Evaluator(childScope, expression);
1338
+ return childEvaluator.evaluate(node.body);
1339
+ };
1340
+ }
1341
+ isMethodSafe(obj, methodName) {
1342
+ if (obj === null || obj === undefined) {
1343
+ return false;
1492
1344
  }
1493
- if (this.match(TokenType.IDENTIFIER)) {
1494
- return { type: 'Identifier', name: this.previous().value };
1345
+ const type = Array.isArray(obj) ? 'array' : obj instanceof Date ? 'date' : typeof obj;
1346
+ const safeMethods = SAFE_METHODS[type];
1347
+ if (!safeMethods) {
1348
+ return false;
1495
1349
  }
1496
- if (this.match(TokenType.LPAREN)) {
1497
- const expr = this.parseExpression();
1498
- this.consume(TokenType.RPAREN, 'Expected ")" after expression');
1499
- return expr;
1350
+ return safeMethods.has(methodName);
1351
+ }
1352
+ }
1353
+
1354
+ const MAX_AST_CACHE_SIZE = 1000;
1355
+ class LRUCache {
1356
+ cache = new Map();
1357
+ maxSize;
1358
+ constructor(maxSize) {
1359
+ this.maxSize = maxSize;
1360
+ }
1361
+ get(key) {
1362
+ const value = this.cache.get(key);
1363
+ if (value !== undefined) {
1364
+ this.cache.delete(key);
1365
+ this.cache.set(key, value);
1500
1366
  }
1501
- throw new ExpressionParserError(`Unexpected token: ${this.peek().value}`, this.peek().position, this.expression);
1367
+ return value;
1502
1368
  }
1503
- parseArgumentList() {
1504
- const args = [];
1505
- if (!this.check(TokenType.RPAREN)) {
1506
- do {
1507
- args.push(this.parseExpression());
1508
- } while (this.match(TokenType.COMMA));
1369
+ set(key, value) {
1370
+ if (this.cache.has(key)) {
1371
+ this.cache.delete(key);
1372
+ }
1373
+ this.cache.set(key, value);
1374
+ if (this.cache.size > this.maxSize) {
1375
+ const firstKey = this.cache.keys().next().value;
1376
+ if (firstKey !== undefined) {
1377
+ this.cache.delete(firstKey);
1378
+ }
1509
1379
  }
1510
- return args;
1511
1380
  }
1512
- parseArrayElements() {
1513
- const elements = [];
1514
- if (!this.check(TokenType.RBRACKET)) {
1515
- do {
1516
- elements.push(this.parseExpression());
1517
- } while (this.match(TokenType.COMMA));
1381
+ clear() {
1382
+ this.cache.clear();
1383
+ }
1384
+ get size() {
1385
+ return this.cache.size;
1386
+ }
1387
+ }
1388
+ const astCache = new LRUCache(MAX_AST_CACHE_SIZE);
1389
+ class ExpressionParser {
1390
+ static parse(expression) {
1391
+ const cached = astCache.get(expression);
1392
+ if (cached) {
1393
+ return cached;
1518
1394
  }
1519
- return elements;
1395
+ const parser = new Parser(expression);
1396
+ const ast = parser.parse();
1397
+ astCache.set(expression, ast);
1398
+ return ast;
1520
1399
  }
1521
- parseObjectProperties() {
1522
- const properties = [];
1523
- if (this.check(TokenType.RBRACE)) {
1524
- return properties;
1400
+ static evaluate(expression, scope) {
1401
+ try {
1402
+ const ast = this.parse(expression);
1403
+ const evaluator = new Evaluator(scope, expression);
1404
+ return evaluator.evaluate(ast);
1525
1405
  }
1526
- do {
1527
- if (this.check(TokenType.RBRACE))
1528
- break;
1529
- let key;
1530
- if (this.check(TokenType.IDENTIFIER) || this.check(TokenType.STRING)) {
1531
- key = this.advance().value;
1406
+ catch (error) {
1407
+ if (error instanceof ExpressionParserError) {
1408
+ throw error;
1532
1409
  }
1533
- else {
1534
- throw new ExpressionParserError(`Expected property name (identifier or string) in object literal, got '${this.peek().value}'`, this.peek().position, this.expression);
1410
+ throw new ExpressionParserError(`Error evaluating expression: ${error instanceof Error ? error.message : String(error)}`, 0, expression);
1411
+ }
1412
+ }
1413
+ static clearCache() {
1414
+ astCache.clear();
1415
+ }
1416
+ static getCacheStats() {
1417
+ return {
1418
+ size: astCache.size,
1419
+ maxSize: MAX_AST_CACHE_SIZE,
1420
+ };
1421
+ }
1422
+ }
1423
+
1424
+ function isThenableOrObservable(value) {
1425
+ if (value === null || typeof value !== 'object')
1426
+ return false;
1427
+ const candidate = value;
1428
+ return typeof candidate.then === 'function' || typeof candidate.subscribe === 'function';
1429
+ }
1430
+ function evaluateCondition(expression, context) {
1431
+ switch (expression.type) {
1432
+ case 'fieldValue':
1433
+ return evaluateFieldValueCondition(expression, context);
1434
+ case 'javascript':
1435
+ return evaluateJavaScriptExpression(expression, context);
1436
+ case 'custom': {
1437
+ const bothSet = expression;
1438
+ const inlineFn = bothSet.fn;
1439
+ const registeredName = bothSet.functionName;
1440
+ if (inlineFn && registeredName) {
1441
+ context.logger.warn('Both "fn" and "functionName" are set on custom condition. Inline "fn" takes precedence; "functionName" is ignored.');
1442
+ }
1443
+ const customFn = inlineFn ?? (registeredName ? context.customFunctions?.[registeredName] : undefined);
1444
+ if (!customFn) {
1445
+ context.logger.error('Custom function not found:', registeredName);
1446
+ return false;
1447
+ }
1448
+ try {
1449
+ const result = customFn(context);
1450
+ if (isThenableOrObservable(result)) {
1451
+ context.logger.warn(`Custom condition '${registeredName ?? '<inline>'}' returned a Promise or Observable. ` +
1452
+ `Synchronous conditions must return a value; use an 'async' or 'http' condition type for asynchronous logic. Treating as false.`);
1453
+ return false;
1454
+ }
1455
+ return !!result;
1535
1456
  }
1536
- this.consume(TokenType.COLON, 'Expected ":" after property name in object literal');
1537
- const value = this.parseExpression();
1538
- properties.push({ key, value });
1539
- } while (this.match(TokenType.COMMA));
1540
- return properties;
1541
- }
1542
- match(...types) {
1543
- for (const type of types) {
1544
- if (this.check(type)) {
1545
- this.advance();
1546
- return true;
1457
+ catch (error) {
1458
+ context.logger.error('Error executing custom function:', registeredName ?? '<inline>', error);
1459
+ return false;
1547
1460
  }
1548
1461
  }
1462
+ case 'and':
1463
+ return evaluateAndCondition(expression, context);
1464
+ case 'or':
1465
+ return evaluateOrCondition(expression, context);
1466
+ case 'http':
1467
+ context.logger.warn('HTTP conditions are resolved asynchronously via createHttpConditionLogicFunction(). ' +
1468
+ 'When used inside and/or composites, the HTTP result is not available synchronously. Returning false.');
1469
+ return false;
1470
+ case 'async':
1471
+ context.logger.warn('Async Condition - resolved asynchronously via createAsyncConditionLogicFunction(). ' +
1472
+ 'When used inside and/or composites, the async result is not available synchronously. Returning false.');
1473
+ return false;
1474
+ default:
1475
+ return false;
1476
+ }
1477
+ }
1478
+ function evaluateFieldValueCondition(expression, context) {
1479
+ if (!expression.fieldPath)
1549
1480
  return false;
1481
+ const fieldValue = hasNestedProperty(context.formValue, expression.fieldPath)
1482
+ ? getNestedValue(context.formValue, expression.fieldPath)
1483
+ : context.rootFormValue
1484
+ ? getNestedValue(context.rootFormValue, expression.fieldPath)
1485
+ : undefined;
1486
+ return compareValues(fieldValue, expression.value, expression.operator);
1487
+ }
1488
+ function evaluateJavaScriptExpression(expression, context) {
1489
+ try {
1490
+ const result = ExpressionParser.evaluate(expression.expression, context);
1491
+ return !!result;
1550
1492
  }
1551
- check(type) {
1552
- if (this.isAtEnd())
1553
- return false;
1554
- return this.peek().type === type;
1493
+ catch (error) {
1494
+ context.logger.error('Error evaluating JavaScript expression:', expression.expression, error);
1495
+ return false;
1555
1496
  }
1556
- advance() {
1557
- if (!this.isAtEnd())
1558
- this.current++;
1559
- return this.previous();
1497
+ }
1498
+ function evaluateAndCondition(expression, context) {
1499
+ if (expression.conditions.length === 0)
1500
+ return false;
1501
+ return expression.conditions.every((condition) => evaluateCondition(condition, context));
1502
+ }
1503
+ function evaluateOrCondition(expression, context) {
1504
+ if (expression.conditions.length === 0)
1505
+ return false;
1506
+ return expression.conditions.some((condition) => evaluateCondition(condition, context));
1507
+ }
1508
+
1509
+ class HttpConditionFunctionCacheService {
1510
+ httpConditionFunctionCache = new Map();
1511
+ }
1512
+
1513
+ function stableStringify(value) {
1514
+ if (value === null || value === undefined) {
1515
+ return String(value);
1560
1516
  }
1561
- isAtEnd() {
1562
- return this.peek().type === TokenType.EOF;
1517
+ if (typeof value !== 'object') {
1518
+ return JSON.stringify(value);
1563
1519
  }
1564
- peek() {
1565
- return this.tokens[this.current];
1520
+ if (Array.isArray(value)) {
1521
+ return '[' + value.map(stableStringify).join(',') + ']';
1566
1522
  }
1567
- previous() {
1568
- return this.tokens[this.current - 1];
1523
+ const obj = value;
1524
+ const sortedKeys = Object.keys(obj).sort();
1525
+ const pairs = sortedKeys.map((key) => JSON.stringify(key) + ':' + stableStringify(obj[key]));
1526
+ return '{' + pairs.join(',') + '}';
1527
+ }
1528
+
1529
+ const DEFAULT_WRAPPERS = new InjectionToken('DEFAULT_WRAPPERS');
1530
+ const FIELD_SIGNAL_CONTEXT = new InjectionToken('FIELD_SIGNAL_CONTEXT');
1531
+ function injectFieldSignalContext() {
1532
+ const ctx = inject(FIELD_SIGNAL_CONTEXT, { optional: true });
1533
+ if (!ctx) {
1534
+ throw new DynamicFormError('FIELD_SIGNAL_CONTEXT is not provided. This consumer must run inside a <df-dynamic-form> component tree.');
1569
1535
  }
1570
- consume(type, message) {
1571
- if (this.check(type))
1572
- return this.advance();
1573
- throw new ExpressionParserError(message, this.peek().position, this.expression);
1536
+ return ctx;
1537
+ }
1538
+ const ARRAY_CONTEXT = new InjectionToken('ARRAY_CONTEXT');
1539
+ const GROUP_CONTEXT = new InjectionToken('GROUP_CONTEXT');
1540
+ const DEFAULT_PROPS = new InjectionToken('DEFAULT_PROPS');
1541
+ const DEFAULT_VALIDATION_MESSAGES = new InjectionToken('DEFAULT_VALIDATION_MESSAGES');
1542
+ const FORM_OPTIONS = new InjectionToken('FORM_OPTIONS');
1543
+ const EXTERNAL_DATA = new InjectionToken('EXTERNAL_DATA');
1544
+ const FORM_ID_PREFIX = new InjectionToken('FORM_ID_PREFIX');
1545
+
1546
+ class RootFormRegistryService {
1547
+ formValue;
1548
+ rootForm;
1549
+ constructor(formValue, rootForm) {
1550
+ this.formValue = formValue;
1551
+ this.rootForm = rootForm;
1574
1552
  }
1575
1553
  }
1576
1554
 
1577
- const STRING_SAFE_METHODS = [
1578
- 'charAt',
1579
- 'charCodeAt',
1580
- 'concat',
1581
- 'endsWith',
1582
- 'includes',
1583
- 'indexOf',
1584
- 'lastIndexOf',
1585
- 'match',
1586
- 'padEnd',
1587
- 'padStart',
1588
- 'repeat',
1589
- 'replace',
1590
- 'search',
1591
- 'slice',
1592
- 'split',
1593
- 'startsWith',
1594
- 'substring',
1595
- 'toLowerCase',
1596
- 'toUpperCase',
1597
- 'trim',
1598
- 'trimEnd',
1599
- 'trimStart',
1600
- 'toString',
1601
- ];
1602
- const NUMBER_SAFE_METHODS = ['toExponential', 'toFixed', 'toPrecision', 'toString'];
1603
- const ARRAY_SAFE_METHODS = [
1604
- 'concat',
1605
- 'every',
1606
- 'filter',
1607
- 'find',
1608
- 'findIndex',
1609
- 'flat',
1610
- 'flatMap',
1611
- 'includes',
1612
- 'indexOf',
1613
- 'join',
1614
- 'lastIndexOf',
1615
- 'map',
1616
- 'reduce',
1617
- 'reduceRight',
1618
- 'slice',
1619
- 'some',
1620
- 'toString',
1621
- 'entries',
1622
- 'keys',
1623
- 'values',
1624
- ];
1625
- const DATE_SAFE_METHODS = [
1626
- 'getDate',
1627
- 'getDay',
1628
- 'getFullYear',
1629
- 'getHours',
1630
- 'getMilliseconds',
1631
- 'getMinutes',
1632
- 'getMonth',
1633
- 'getSeconds',
1634
- 'getTime',
1635
- 'getTimezoneOffset',
1636
- 'getUTCDate',
1637
- 'getUTCDay',
1638
- 'getUTCFullYear',
1639
- 'getUTCHours',
1640
- 'getUTCMilliseconds',
1641
- 'getUTCMinutes',
1642
- 'getUTCMonth',
1643
- 'getUTCSeconds',
1644
- 'toDateString',
1645
- 'toISOString',
1646
- 'toJSON',
1647
- 'toString',
1648
- 'toTimeString',
1649
- 'toUTCString',
1650
- ];
1651
- const SAFE_METHODS = {
1652
- string: new Set(STRING_SAFE_METHODS),
1653
- number: new Set(NUMBER_SAFE_METHODS),
1654
- array: new Set(ARRAY_SAFE_METHODS),
1655
- date: new Set(DATE_SAFE_METHODS),
1656
- };
1657
- const BLOCKED_PROPERTIES = new Set([
1658
- 'constructor',
1659
- '__proto__',
1660
- 'prototype',
1661
- '__defineGetter__',
1662
- '__defineSetter__',
1663
- '__lookupGetter__',
1664
- '__lookupSetter__',
1665
- ]);
1666
- class Evaluator {
1667
- scope;
1668
- expression;
1669
- constructor(scope, expression) {
1670
- this.scope = scope;
1671
- this.expression = expression;
1555
+ const DEPRECATION_WARNING_TRACKER = new InjectionToken('DeprecationWarningTracker');
1556
+
1557
+ function safeReadPathKeys(fieldContext) {
1558
+ if (!('pathKeys' in fieldContext) || typeof fieldContext.pathKeys !== 'function') {
1559
+ return [];
1672
1560
  }
1673
- evaluate(node) {
1674
- switch (node.type) {
1675
- case 'Literal':
1676
- return node.value;
1677
- case 'Identifier':
1678
- return this.evaluateIdentifier(node.name);
1679
- case 'MemberAccess':
1680
- return this.evaluateMemberAccess(node);
1681
- case 'ComputedMemberAccess':
1682
- return this.evaluateComputedMemberAccess(node);
1683
- case 'BinaryOp':
1684
- return this.evaluateBinaryOp(node);
1685
- case 'UnaryOp':
1686
- return this.evaluateUnaryOp(node);
1687
- case 'CallExpression':
1688
- return this.evaluateCallExpression(node);
1689
- case 'Conditional':
1690
- return this.evaluate(node.test) ? this.evaluate(node.consequent) : this.evaluate(node.alternate);
1691
- case 'ArrayLiteral':
1692
- return this.evaluateArrayLiteral(node);
1693
- case 'ObjectLiteral':
1694
- return this.evaluateObjectLiteral(node);
1695
- case 'ArrowFunction':
1696
- return this.evaluateArrowFunction(node);
1697
- default:
1698
- throw new ExpressionParserError(`Unknown node type: ${node.type}`, 0, this.expression);
1561
+ return untracked(() => fieldContext.pathKeys());
1562
+ }
1563
+
1564
+ function isChildFieldContext(context) {
1565
+ return 'key' in context && isSignal(context.key);
1566
+ }
1567
+ function extractFieldState(fieldContext) {
1568
+ return untracked(() => {
1569
+ if (!fieldContext || !('state' in fieldContext))
1570
+ return undefined;
1571
+ return fieldContext.state;
1572
+ });
1573
+ }
1574
+ function detectArrayScope(pathKeys) {
1575
+ if (pathKeys.length < 3)
1576
+ return undefined;
1577
+ for (let i = pathKeys.length - 2; i >= 1; i--) {
1578
+ const maybeIndex = Number(pathKeys[i]);
1579
+ if (Number.isInteger(maybeIndex) && maybeIndex >= 0) {
1580
+ return {
1581
+ arrayKey: pathKeys.slice(0, i).join('.'),
1582
+ index: maybeIndex,
1583
+ localKey: pathKeys[pathKeys.length - 1],
1584
+ };
1699
1585
  }
1700
1586
  }
1701
- evaluateIdentifier(name) {
1702
- if (name in this.scope) {
1703
- return this.scope[name];
1587
+ return undefined;
1588
+ }
1589
+ class FieldContextRegistryService {
1590
+ rootFormRegistry = inject(RootFormRegistryService);
1591
+ logger = inject(DynamicFormLogger);
1592
+ deprecationTracker = inject(DEPRECATION_WARNING_TRACKER, { optional: true });
1593
+ externalDataSignal = inject(EXTERNAL_DATA, { optional: true });
1594
+ createEvaluationContext(fieldContext, customFunctions) {
1595
+ const fieldValue = untracked(() => fieldContext.value());
1596
+ const rootFormValue = untracked(() => this.rootFormRegistry.formValue());
1597
+ const localKey = this.extractFieldPath(fieldContext);
1598
+ const pathKeys = safeReadPathKeys(fieldContext);
1599
+ const arrayScope = detectArrayScope(pathKeys);
1600
+ if (arrayScope) {
1601
+ return this.buildArrayScopedContext(rootFormValue, arrayScope, fieldValue, customFunctions, false, fieldContext);
1704
1602
  }
1705
- return undefined;
1603
+ const rootFormSignal = this.rootFormRegistry.rootForm;
1604
+ return {
1605
+ fieldValue,
1606
+ formValue: rootFormValue,
1607
+ fieldPath: localKey,
1608
+ customFunctions: customFunctions || {},
1609
+ externalData: this.resolveExternalData(false),
1610
+ logger: this.logger,
1611
+ deprecationTracker: this.deprecationTracker ?? undefined,
1612
+ get fieldState() {
1613
+ return readFieldStateInfo(extractFieldState(fieldContext), false);
1614
+ },
1615
+ get formFieldState() {
1616
+ return createFormFieldStateMap(untracked(rootFormSignal), false);
1617
+ },
1618
+ };
1706
1619
  }
1707
- evaluateMemberAccess(node) {
1708
- const obj = this.evaluate(node.object);
1709
- if (obj === null || obj === undefined) {
1710
- return undefined;
1620
+ extractFieldPath(fieldContext) {
1621
+ if (isChildFieldContext(fieldContext)) {
1622
+ try {
1623
+ return String(fieldContext.key());
1624
+ }
1625
+ catch (error) {
1626
+ this.logger.warn('Unable to extract field key:', error);
1627
+ }
1711
1628
  }
1712
- if (BLOCKED_PROPERTIES.has(node.property)) {
1713
- throw new ExpressionParserError(`Property "${node.property}" is not accessible for security reasons`, 0, this.expression);
1629
+ return '';
1630
+ }
1631
+ buildArrayScopedContext(rootFormValue, arrayScope, fieldValue, customFunctions, reactive, fieldContext) {
1632
+ const { arrayKey, index, localKey } = arrayScope;
1633
+ const arrayData = getNestedValue(rootFormValue, arrayKey);
1634
+ let scopedFormValue;
1635
+ if (Array.isArray(arrayData) && index >= 0 && index < arrayData.length) {
1636
+ const item = arrayData[index];
1637
+ if (item != null && typeof item === 'object') {
1638
+ scopedFormValue = item;
1639
+ }
1714
1640
  }
1715
- if (typeof obj === 'object' || typeof obj === 'string' || typeof obj === 'number') {
1716
- return obj[node.property];
1641
+ const rootFormSignal = this.rootFormRegistry.rootForm;
1642
+ const fieldStateGetter = fieldContext ? () => readFieldStateInfo(extractFieldState(fieldContext), reactive) : () => undefined;
1643
+ const formFieldStateGetter = () => createFormFieldStateMap((reactive ? rootFormSignal() : untracked(rootFormSignal)), reactive);
1644
+ if (!scopedFormValue) {
1645
+ return {
1646
+ fieldValue,
1647
+ formValue: rootFormValue,
1648
+ fieldPath: localKey,
1649
+ customFunctions: customFunctions || {},
1650
+ externalData: this.resolveExternalData(reactive),
1651
+ logger: this.logger,
1652
+ deprecationTracker: this.deprecationTracker ?? undefined,
1653
+ get fieldState() {
1654
+ return fieldStateGetter();
1655
+ },
1656
+ get formFieldState() {
1657
+ return formFieldStateGetter();
1658
+ },
1659
+ };
1717
1660
  }
1718
- return undefined;
1661
+ return {
1662
+ fieldValue,
1663
+ formValue: scopedFormValue,
1664
+ rootFormValue,
1665
+ arrayIndex: index,
1666
+ arrayPath: arrayKey,
1667
+ fieldPath: `${arrayKey}.${index}.${localKey}`,
1668
+ customFunctions: customFunctions || {},
1669
+ externalData: this.resolveExternalData(reactive),
1670
+ logger: this.logger,
1671
+ deprecationTracker: this.deprecationTracker ?? undefined,
1672
+ get fieldState() {
1673
+ return fieldStateGetter();
1674
+ },
1675
+ get formFieldState() {
1676
+ return formFieldStateGetter();
1677
+ },
1678
+ };
1719
1679
  }
1720
- evaluateComputedMemberAccess(node) {
1721
- const obj = this.evaluate(node.object);
1722
- if (obj === null || obj === undefined) {
1723
- return undefined;
1724
- }
1725
- const rawKey = this.evaluate(node.property);
1726
- if (rawKey === null || rawKey === undefined)
1680
+ resolveExternalData(reactive) {
1681
+ const externalDataSignal = this.externalDataSignal;
1682
+ if (!externalDataSignal)
1683
+ return undefined;
1684
+ const externalDataRecord = reactive ? externalDataSignal() : untracked(() => externalDataSignal());
1685
+ if (!externalDataRecord) {
1727
1686
  return undefined;
1728
- const key = String(rawKey);
1729
- if (BLOCKED_PROPERTIES.has(key)) {
1730
- throw new ExpressionParserError(`Property "${key}" is not accessible for security reasons`, 0, this.expression);
1731
1687
  }
1732
- if (typeof obj === 'object' || typeof obj === 'string' || typeof obj === 'number') {
1733
- return obj[key];
1688
+ const resolved = {};
1689
+ for (const [key, value] of Object.entries(externalDataRecord)) {
1690
+ if (!isSignal(value)) {
1691
+ throw new DynamicFormError(`externalData["${key}"] must be a Signal. Got: ${typeof value}. Wrap it with signal(yourValue).`);
1692
+ }
1693
+ resolved[key] = reactive ? value() : untracked(() => value());
1734
1694
  }
1735
- return undefined;
1695
+ return resolved;
1736
1696
  }
1737
- evaluateBinaryOp(node) {
1738
- if (node.operator === '&&') {
1739
- const left = this.evaluate(node.left);
1740
- return left ? this.evaluate(node.right) : left;
1741
- }
1742
- if (node.operator === '||') {
1743
- const left = this.evaluate(node.left);
1744
- return left ? left : this.evaluate(node.right);
1697
+ createReactiveEvaluationContext(fieldContext, customFunctions) {
1698
+ const fieldValue = fieldContext.value();
1699
+ const rootFormValue = this.rootFormRegistry.formValue();
1700
+ const pathKeys = safeReadPathKeys(fieldContext);
1701
+ const arrayScope = detectArrayScope(pathKeys);
1702
+ if (arrayScope) {
1703
+ return this.buildArrayScopedContext(rootFormValue, arrayScope, fieldValue, customFunctions, true, fieldContext);
1745
1704
  }
1746
- const left = this.evaluate(node.left);
1747
- const right = this.evaluate(node.right);
1748
- switch (node.operator) {
1749
- case '+':
1750
- return left + right;
1751
- case '-':
1752
- return left - right;
1753
- case '*':
1754
- return left * right;
1755
- case '/':
1756
- if (right === 0)
1757
- return null;
1758
- return left / right;
1759
- case '%':
1760
- if (right === 0)
1761
- return null;
1762
- return left % right;
1763
- case '==':
1764
- return left == right;
1765
- case '===':
1766
- return left === right;
1767
- case '!=':
1768
- return left != right;
1769
- case '!==':
1770
- return left !== right;
1771
- case '>':
1772
- return left > right;
1773
- case '<':
1774
- return left < right;
1775
- case '>=':
1776
- return left >= right;
1777
- case '<=':
1778
- return left <= right;
1779
- default:
1780
- throw new ExpressionParserError(`Unknown binary operator: ${node.operator}`, 0, this.expression);
1705
+ const localKey = this.extractFieldPath(fieldContext);
1706
+ const rootFormSignal = this.rootFormRegistry.rootForm;
1707
+ return {
1708
+ fieldValue,
1709
+ formValue: rootFormValue,
1710
+ fieldPath: localKey,
1711
+ customFunctions: customFunctions || {},
1712
+ externalData: this.resolveExternalData(true),
1713
+ logger: this.logger,
1714
+ deprecationTracker: this.deprecationTracker ?? undefined,
1715
+ get fieldState() {
1716
+ return readFieldStateInfo(extractFieldState(fieldContext), true);
1717
+ },
1718
+ get formFieldState() {
1719
+ return createFormFieldStateMap(rootFormSignal(), true);
1720
+ },
1721
+ };
1722
+ }
1723
+ createDisplayOnlyContext(fieldPath, customFunctions, arrayScope) {
1724
+ const rootFormValue = this.rootFormRegistry.formValue();
1725
+ if (arrayScope) {
1726
+ return this.buildArrayScopedContext(rootFormValue, arrayScope, undefined, customFunctions, true);
1781
1727
  }
1728
+ const rootFormSignal = this.rootFormRegistry.rootForm;
1729
+ return {
1730
+ fieldValue: undefined,
1731
+ formValue: rootFormValue,
1732
+ fieldPath,
1733
+ customFunctions: customFunctions || {},
1734
+ externalData: this.resolveExternalData(true),
1735
+ logger: this.logger,
1736
+ deprecationTracker: this.deprecationTracker ?? undefined,
1737
+ get formFieldState() {
1738
+ return createFormFieldStateMap(rootFormSignal(), true);
1739
+ },
1740
+ };
1741
+ }
1742
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: FieldContextRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1743
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: FieldContextRegistryService });
1744
+ }
1745
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: FieldContextRegistryService, decorators: [{
1746
+ type: Injectable
1747
+ }] });
1748
+
1749
+ class FunctionRegistryService {
1750
+ customFunctions = new Map();
1751
+ customFunctionScopes = new Map();
1752
+ derivationFunctions = new Map();
1753
+ asyncDerivationFunctions = new Map();
1754
+ asyncConditionFunctions = new Map();
1755
+ validators = new Map();
1756
+ asyncValidators = new Map();
1757
+ httpValidators = new Map();
1758
+ registerCustomFunction(name, fn, options) {
1759
+ this.customFunctions.set(name, fn);
1760
+ this.customFunctionScopes.set(name, options?.scope ?? 'form');
1761
+ }
1762
+ getCustomFunctionScope(name) {
1763
+ return this.customFunctionScopes.get(name);
1764
+ }
1765
+ isFieldScopedFunction(name) {
1766
+ return this.customFunctionScopes.get(name) === 'field';
1767
+ }
1768
+ getCustomFunctions() {
1769
+ return Object.fromEntries(this.customFunctions);
1770
+ }
1771
+ clearCustomFunctions() {
1772
+ this.customFunctions.clear();
1773
+ this.customFunctionScopes.clear();
1774
+ }
1775
+ registerDerivationFunction(name, fn) {
1776
+ this.derivationFunctions.set(name, fn);
1777
+ }
1778
+ getDerivationFunction(name) {
1779
+ return this.derivationFunctions.get(name);
1782
1780
  }
1783
- evaluateUnaryOp(node) {
1784
- const operand = this.evaluate(node.operand);
1785
- switch (node.operator) {
1786
- case '!':
1787
- return !operand;
1788
- case '-':
1789
- return -operand;
1790
- case '+':
1791
- return +operand;
1792
- default:
1793
- throw new ExpressionParserError(`Unknown unary operator: ${node.operator}`, 0, this.expression);
1794
- }
1781
+ getDerivationFunctions() {
1782
+ return Object.fromEntries(this.derivationFunctions);
1795
1783
  }
1796
- evaluateCallExpression(node) {
1797
- if (node.callee.type !== 'MemberAccess') {
1798
- throw new ExpressionParserError('Only method calls are allowed, not arbitrary function calls', 0, this.expression);
1799
- }
1800
- const obj = this.evaluate(node.callee.object);
1801
- if (obj === null || obj === undefined) {
1802
- return undefined;
1803
- }
1804
- const methodName = node.callee.property;
1805
- if (BLOCKED_PROPERTIES.has(methodName)) {
1806
- throw new ExpressionParserError(`Property "${methodName}" is not accessible for security reasons`, 0, this.expression);
1807
- }
1808
- if (!this.isMethodSafe(obj, methodName)) {
1809
- throw new ExpressionParserError(`Method "${methodName}" is not allowed for security reasons`, 0, this.expression);
1810
- }
1811
- const args = node.arguments.map((arg) => this.evaluate(arg));
1812
- const method = obj[methodName];
1813
- if (typeof method === 'function') {
1814
- return method.call(obj, ...args);
1815
- }
1816
- return undefined;
1784
+ setDerivationFunctions(derivations) {
1785
+ this.setRegistryIfChanged(this.derivationFunctions, derivations);
1817
1786
  }
1818
- evaluateArrayLiteral(node) {
1819
- return node.elements.map((element) => this.evaluate(element));
1787
+ clearDerivationFunctions() {
1788
+ this.derivationFunctions.clear();
1820
1789
  }
1821
- evaluateObjectLiteral(node) {
1822
- const result = {};
1823
- for (const { key, value } of node.properties) {
1824
- if (BLOCKED_PROPERTIES.has(key)) {
1825
- throw new ExpressionParserError(`Property "${key}" is not allowed in object literals for security reasons`, 0, this.expression);
1826
- }
1827
- result[key] = this.evaluate(value);
1828
- }
1829
- return result;
1790
+ registerAsyncDerivationFunction(name, fn) {
1791
+ this.asyncDerivationFunctions.set(name, fn);
1830
1792
  }
1831
- evaluateArrowFunction(node) {
1832
- const parentScope = this.scope;
1833
- const expression = this.expression;
1834
- return (...args) => {
1835
- const childScope = { ...parentScope };
1836
- for (let i = 0; i < node.params.length; i++) {
1837
- childScope[node.params[i]] = args[i];
1838
- }
1839
- const childEvaluator = new Evaluator(childScope, expression);
1840
- return childEvaluator.evaluate(node.body);
1841
- };
1793
+ getAsyncDerivationFunction(name) {
1794
+ return this.asyncDerivationFunctions.get(name);
1842
1795
  }
1843
- isMethodSafe(obj, methodName) {
1844
- if (obj === null || obj === undefined) {
1845
- return false;
1846
- }
1847
- const type = Array.isArray(obj) ? 'array' : obj instanceof Date ? 'date' : typeof obj;
1848
- const safeMethods = SAFE_METHODS[type];
1849
- if (!safeMethods) {
1850
- return false;
1851
- }
1852
- return safeMethods.has(methodName);
1796
+ getAsyncDerivationFunctions() {
1797
+ return Object.fromEntries(this.asyncDerivationFunctions);
1853
1798
  }
1854
- }
1855
-
1856
- const MAX_AST_CACHE_SIZE = 1000;
1857
- class LRUCache {
1858
- cache = new Map();
1859
- maxSize;
1860
- constructor(maxSize) {
1861
- this.maxSize = maxSize;
1799
+ setAsyncDerivationFunctions(fns) {
1800
+ this.setRegistryIfChanged(this.asyncDerivationFunctions, fns);
1862
1801
  }
1863
- get(key) {
1864
- const value = this.cache.get(key);
1865
- if (value !== undefined) {
1866
- this.cache.delete(key);
1867
- this.cache.set(key, value);
1868
- }
1869
- return value;
1802
+ clearAsyncDerivationFunctions() {
1803
+ this.asyncDerivationFunctions.clear();
1870
1804
  }
1871
- set(key, value) {
1872
- if (this.cache.has(key)) {
1873
- this.cache.delete(key);
1874
- }
1875
- this.cache.set(key, value);
1876
- if (this.cache.size > this.maxSize) {
1877
- const firstKey = this.cache.keys().next().value;
1878
- if (firstKey !== undefined) {
1879
- this.cache.delete(firstKey);
1880
- }
1881
- }
1805
+ registerAsyncConditionFunction(name, fn) {
1806
+ this.asyncConditionFunctions.set(name, fn);
1882
1807
  }
1883
- clear() {
1884
- this.cache.clear();
1808
+ getAsyncConditionFunction(name) {
1809
+ return this.asyncConditionFunctions.get(name);
1885
1810
  }
1886
- get size() {
1887
- return this.cache.size;
1811
+ getAsyncConditionFunctions() {
1812
+ return Object.fromEntries(this.asyncConditionFunctions);
1888
1813
  }
1889
- }
1890
- const astCache = new LRUCache(MAX_AST_CACHE_SIZE);
1891
- class ExpressionParser {
1892
- static parse(expression) {
1893
- const cached = astCache.get(expression);
1894
- if (cached) {
1895
- return cached;
1896
- }
1897
- const parser = new Parser(expression);
1898
- const ast = parser.parse();
1899
- astCache.set(expression, ast);
1900
- return ast;
1814
+ setAsyncConditionFunctions(fns) {
1815
+ this.setRegistryIfChanged(this.asyncConditionFunctions, fns);
1901
1816
  }
1902
- static evaluate(expression, scope) {
1903
- try {
1904
- const ast = this.parse(expression);
1905
- const evaluator = new Evaluator(scope, expression);
1906
- return evaluator.evaluate(ast);
1907
- }
1908
- catch (error) {
1909
- if (error instanceof ExpressionParserError) {
1910
- throw error;
1911
- }
1912
- throw new ExpressionParserError(`Error evaluating expression: ${error instanceof Error ? error.message : String(error)}`, 0, expression);
1913
- }
1817
+ clearAsyncConditionFunctions() {
1818
+ this.asyncConditionFunctions.clear();
1914
1819
  }
1915
- static clearCache() {
1916
- astCache.clear();
1820
+ registerValidator(name, fn) {
1821
+ this.validators.set(name, fn);
1917
1822
  }
1918
- static getCacheStats() {
1919
- return {
1920
- size: astCache.size,
1921
- maxSize: MAX_AST_CACHE_SIZE,
1922
- };
1823
+ getValidator(name) {
1824
+ return this.validators.get(name);
1923
1825
  }
1924
- }
1925
-
1926
- function isThenableOrObservable(value) {
1927
- if (value === null || typeof value !== 'object')
1928
- return false;
1929
- const candidate = value;
1930
- return typeof candidate.then === 'function' || typeof candidate.subscribe === 'function';
1931
- }
1932
- function evaluateCondition(expression, context) {
1933
- switch (expression.type) {
1934
- case 'fieldValue':
1935
- return evaluateFieldValueCondition(expression, context);
1936
- case 'javascript':
1937
- return evaluateJavaScriptExpression(expression, context);
1938
- case 'custom': {
1939
- const bothSet = expression;
1940
- const inlineFn = bothSet.fn;
1941
- const registeredName = bothSet.functionName;
1942
- if (inlineFn && registeredName) {
1943
- context.logger.warn('Both "fn" and "functionName" are set on custom condition. Inline "fn" takes precedence; "functionName" is ignored.');
1944
- }
1945
- const customFn = inlineFn ?? (registeredName ? context.customFunctions?.[registeredName] : undefined);
1946
- if (!customFn) {
1947
- context.logger.error('Custom function not found:', registeredName);
1948
- return false;
1949
- }
1950
- try {
1951
- const result = customFn(context);
1952
- if (isThenableOrObservable(result)) {
1953
- context.logger.warn(`Custom condition '${registeredName ?? '<inline>'}' returned a Promise or Observable. ` +
1954
- `Synchronous conditions must return a value; use an 'async' or 'http' condition type for asynchronous logic. Treating as false.`);
1955
- return false;
1956
- }
1957
- return !!result;
1958
- }
1959
- catch (error) {
1960
- context.logger.error('Error executing custom function:', registeredName ?? '<inline>', error);
1961
- return false;
1962
- }
1826
+ clearValidators() {
1827
+ this.validators.clear();
1828
+ }
1829
+ registerAsyncValidator(name, fn) {
1830
+ this.asyncValidators.set(name, fn);
1831
+ }
1832
+ getAsyncValidator(name) {
1833
+ return this.asyncValidators.get(name);
1834
+ }
1835
+ clearAsyncValidators() {
1836
+ this.asyncValidators.clear();
1837
+ }
1838
+ registerHttpValidator(name, config) {
1839
+ this.httpValidators.set(name, config);
1840
+ }
1841
+ getHttpValidator(name) {
1842
+ return this.httpValidators.get(name);
1843
+ }
1844
+ clearHttpValidators() {
1845
+ this.httpValidators.clear();
1846
+ }
1847
+ setRegistryIfChanged(registry, values) {
1848
+ if (!values)
1849
+ return;
1850
+ const entries = Object.entries(values);
1851
+ const hasChanges = entries.some(([name, value]) => registry.get(name) !== value);
1852
+ if (hasChanges) {
1853
+ entries.forEach(([name, value]) => registry.set(name, value));
1963
1854
  }
1964
- case 'and':
1965
- return evaluateAndCondition(expression, context);
1966
- case 'or':
1967
- return evaluateOrCondition(expression, context);
1968
- case 'http':
1969
- context.logger.warn('HTTP conditions are resolved asynchronously via createHttpConditionLogicFunction(). ' +
1970
- 'When used inside and/or composites, the HTTP result is not available synchronously. Returning false.');
1971
- return false;
1972
- case 'async':
1973
- context.logger.warn('Async Condition - resolved asynchronously via createAsyncConditionLogicFunction(). ' +
1974
- 'When used inside and/or composites, the async result is not available synchronously. Returning false.');
1975
- return false;
1976
- default:
1977
- return false;
1978
1855
  }
1979
- }
1980
- function evaluateFieldValueCondition(expression, context) {
1981
- if (!expression.fieldPath)
1982
- return false;
1983
- const fieldValue = hasNestedProperty(context.formValue, expression.fieldPath)
1984
- ? getNestedValue(context.formValue, expression.fieldPath)
1985
- : context.rootFormValue
1986
- ? getNestedValue(context.rootFormValue, expression.fieldPath)
1987
- : undefined;
1988
- return compareValues(fieldValue, expression.value, expression.operator);
1989
- }
1990
- function evaluateJavaScriptExpression(expression, context) {
1991
- try {
1992
- const result = ExpressionParser.evaluate(expression.expression, context);
1993
- return !!result;
1856
+ setValidators(validators) {
1857
+ this.setRegistryIfChanged(this.validators, validators);
1858
+ }
1859
+ setAsyncValidators(asyncValidators) {
1860
+ this.setRegistryIfChanged(this.asyncValidators, asyncValidators);
1994
1861
  }
1995
- catch (error) {
1996
- context.logger.error('Error evaluating JavaScript expression:', expression.expression, error);
1997
- return false;
1862
+ setHttpValidators(httpValidators) {
1863
+ this.setRegistryIfChanged(this.httpValidators, httpValidators);
1998
1864
  }
1865
+ clearAll() {
1866
+ this.clearCustomFunctions();
1867
+ this.clearDerivationFunctions();
1868
+ this.clearAsyncDerivationFunctions();
1869
+ this.clearAsyncConditionFunctions();
1870
+ this.clearValidators();
1871
+ this.clearAsyncValidators();
1872
+ this.clearHttpValidators();
1873
+ }
1874
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: FunctionRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1875
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: FunctionRegistryService });
1999
1876
  }
2000
- function evaluateAndCondition(expression, context) {
2001
- if (expression.conditions.length === 0)
2002
- return false;
2003
- return expression.conditions.every((condition) => evaluateCondition(condition, context));
2004
- }
2005
- function evaluateOrCondition(expression, context) {
2006
- if (expression.conditions.length === 0)
2007
- return false;
2008
- return expression.conditions.some((condition) => evaluateCondition(condition, context));
2009
- }
2010
-
2011
- class HttpConditionFunctionCacheService {
2012
- httpConditionFunctionCache = new Map();
2013
- }
1877
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: FunctionRegistryService, decorators: [{
1878
+ type: Injectable
1879
+ }] });
2014
1880
 
2015
1881
  class HttpConditionCache {
2016
1882
  cache = new Map();
@@ -2094,6 +1960,92 @@ function resolveHttpRequest(config, context) {
2094
1960
  return request;
2095
1961
  }
2096
1962
 
1963
+ function isSignalArray(value) {
1964
+ return Array.isArray(value) && value.every((item) => isSignal(item));
1965
+ }
1966
+ function isSignalRecord(value) {
1967
+ return typeof value === 'object' && value !== null && !Array.isArray(value) && Object.values(value).every((item) => isSignal(item));
1968
+ }
1969
+ function derivedFromDeferred(source, operator, options) {
1970
+ const deferred$ = defer(() => {
1971
+ if (isSignal(source)) {
1972
+ return toObservable(source, { injector: options.injector });
1973
+ }
1974
+ if (isSignalArray(source)) {
1975
+ const observables = source.map((s) => toObservable(s, { injector: options.injector }));
1976
+ return combineLatest(observables);
1977
+ }
1978
+ if (isSignalRecord(source)) {
1979
+ const keys = Object.keys(source);
1980
+ const observables = keys.map((key) => toObservable(source[key], { injector: options.injector }));
1981
+ return combineLatest(observables, (...values) => {
1982
+ const result = {};
1983
+ keys.forEach((key, index) => {
1984
+ result[key] = values[index];
1985
+ });
1986
+ return result;
1987
+ });
1988
+ }
1989
+ throw new Error('Invalid source type. Expected a Signal, array of Signals, or object of Signals.');
1990
+ }).pipe(operator);
1991
+ return toSignal(deferred$, { initialValue: options.initialValue, injector: options.injector });
1992
+ }
1993
+
1994
+ function withPreviousValue(input) {
1995
+ const derived = linkedSignal({ ...(ngDevMode ? { debugName: "derived" } : /* istanbul ignore next */ {}), source: input.snapshot,
1996
+ computation: (snapshot, previous) => {
1997
+ if ((snapshot.status === 'loading' || snapshot.status === 'reloading') &&
1998
+ previous !== undefined &&
1999
+ previous.value.status !== 'error') {
2000
+ return { status: snapshot.status, value: previous.value.value };
2001
+ }
2002
+ return snapshot;
2003
+ } });
2004
+ return resourceFromSnapshots(derived);
2005
+ }
2006
+
2007
+ function createDebouncedResourceLogicFn(config) {
2008
+ const perFunctionSignalStore = new Map();
2009
+ return (ctx) => {
2010
+ const contextKey = safeReadPathKeys(ctx).join('.');
2011
+ let handle = perFunctionSignalStore.get(contextKey);
2012
+ if (!handle) {
2013
+ const trigger = signal(undefined, /* @ts-ignore */
2014
+ ...(ngDevMode ? [{ debugName: "trigger" }] : /* istanbul ignore next */ []));
2015
+ const resultResource = untracked(() => {
2016
+ const debouncedTrigger = derivedFromDeferred(trigger, pipe(debounceTime(config.debounceMs),
2017
+ distinctUntilChanged((a, b) => a?.key === b?.key)), { initialValue: undefined, injector: config.injector });
2018
+ const resource = rxResource({
2019
+ params: () => debouncedTrigger(),
2020
+ stream: ({ params }) => config.buildStream(params.payload),
2021
+ defaultValue: config.pendingValue,
2022
+ injector: config.injector,
2023
+ });
2024
+ return withPreviousValue(resource);
2025
+ });
2026
+ handle = { trigger, resultResource, refetchVersion: 0 };
2027
+ perFunctionSignalStore.set(contextKey, handle);
2028
+ }
2029
+ const resolved = config.resolve(ctx);
2030
+ if (resolved.kind === 'returnEarly') {
2031
+ handle.refetchVersion++;
2032
+ return resolved.value;
2033
+ }
2034
+ if (resolved.kind === 'holdPrevious') {
2035
+ return handle.resultResource.value();
2036
+ }
2037
+ const dedupKey = `${resolved.key}::${handle.refetchVersion}`;
2038
+ const newTrigger = { key: dedupKey, payload: resolved.payload };
2039
+ untracked(() => {
2040
+ const current = handle.trigger();
2041
+ if (current?.key !== newTrigger.key) {
2042
+ handle.trigger.set(newTrigger);
2043
+ }
2044
+ });
2045
+ return handle.resultResource.value();
2046
+ };
2047
+ }
2048
+
2097
2049
  function extractBoolean(response, responseExpression, pendingValue, logger) {
2098
2050
  if (responseExpression) {
2099
2051
  try {
@@ -2164,6 +2116,57 @@ function createHttpConditionLogicFunction(condition) {
2164
2116
  return fn;
2165
2117
  }
2166
2118
 
2119
+ function createAsyncConditionLogicFunction(condition) {
2120
+ const fieldContextRegistry = inject(FieldContextRegistryService);
2121
+ const functionRegistry = inject(FunctionRegistryService);
2122
+ const injector = inject(Injector);
2123
+ const logger = inject(DynamicFormLogger);
2124
+ const cacheService = inject(AsyncConditionFunctionCacheService);
2125
+ const pendingValue = condition.pendingValue ?? false;
2126
+ const debounceMs = condition.debounceMs ?? 300;
2127
+ if (condition.asyncFn && condition.asyncFunctionName) {
2128
+ logger.warn('Both "asyncFn" and "asyncFunctionName" are set on async condition. Inline "asyncFn" takes precedence; "asyncFunctionName" is ignored.');
2129
+ }
2130
+ const cacheKey = condition.asyncFn ? undefined : stableStringify(condition);
2131
+ if (cacheKey !== undefined) {
2132
+ const cached = cacheService.asyncConditionFunctionCache.get(cacheKey);
2133
+ if (cached) {
2134
+ return cached;
2135
+ }
2136
+ }
2137
+ const fn = createDebouncedResourceLogicFn({
2138
+ pendingValue,
2139
+ debounceMs,
2140
+ injector,
2141
+ resolve: (ctx) => {
2142
+ const evaluationContext = fieldContextRegistry.createReactiveEvaluationContext(ctx, functionRegistry.getCustomFunctions());
2143
+ const key = stableStringify({
2144
+ formValue: evaluationContext.formValue,
2145
+ fieldValue: evaluationContext.fieldValue,
2146
+ externalData: evaluationContext.externalData,
2147
+ });
2148
+ return { kind: 'trigger', key, payload: ctx };
2149
+ },
2150
+ buildStream: (capturedCtx) => {
2151
+ const asyncFn = condition.asyncFn ??
2152
+ (condition.asyncFunctionName ? functionRegistry.getAsyncConditionFunction(condition.asyncFunctionName) : undefined);
2153
+ if (!asyncFn) {
2154
+ logger.warn(`Async Condition - function '${condition.asyncFunctionName}' not found. ` + `Register it in customFnConfig.asyncConditions.`);
2155
+ return of(pendingValue);
2156
+ }
2157
+ const evaluationContext = fieldContextRegistry.createReactiveEvaluationContext(capturedCtx, functionRegistry.getCustomFunctions());
2158
+ return from(asyncFn(evaluationContext)).pipe(map((result) => !!result), catchError((error) => {
2159
+ logger.warn('Async Condition - function failed:', error);
2160
+ return of(pendingValue);
2161
+ }));
2162
+ },
2163
+ });
2164
+ if (cacheKey !== undefined) {
2165
+ cacheService.asyncConditionFunctionCache.set(cacheKey, fn);
2166
+ }
2167
+ return fn;
2168
+ }
2169
+
2167
2170
  class LogicFunctionCacheService {
2168
2171
  logicFunctionCache = new Map();
2169
2172
  debouncedLogicFunctionCache = new Map();
@@ -2294,35 +2297,6 @@ function toReadonlyFieldTreeCached(cache, field) {
2294
2297
  return view;
2295
2298
  }
2296
2299
 
2297
- function evaluateHttpValidationResponse(response, mapping, logger) {
2298
- const scope = { response };
2299
- try {
2300
- const result = ExpressionParser.evaluate(mapping.validWhen, scope);
2301
- if (typeof result !== 'boolean') {
2302
- logger.warn(`validWhen expression "${mapping.validWhen}" returned non-boolean value:`, result, 'Expected true or false.');
2303
- }
2304
- if (result === true) {
2305
- return null;
2306
- }
2307
- const error = { kind: mapping.errorKind };
2308
- if (mapping.errorParams) {
2309
- for (const [key, expression] of Object.entries(mapping.errorParams)) {
2310
- try {
2311
- error[key] = ExpressionParser.evaluate(expression, scope);
2312
- }
2313
- catch (err) {
2314
- logger.warn(`Error evaluating errorParam "${key}":`, expression, err);
2315
- }
2316
- }
2317
- }
2318
- return error;
2319
- }
2320
- catch (err) {
2321
- logger.warn('Error evaluating validWhen expression:', mapping.validWhen, err);
2322
- return { kind: mapping.errorKind };
2323
- }
2324
- }
2325
-
2326
2300
  const noOpLogger = {
2327
2301
  debug: () => { },
2328
2302
  info: () => { },
@@ -2357,7 +2331,10 @@ function evaluateLogicCondition(condition, ctx) {
2357
2331
  if (isFormStateCondition(condition)) {
2358
2332
  return evaluateFormStateCondition(condition, ctx);
2359
2333
  }
2360
- const formValue = (ctx.formValue ?? ctx.form().value());
2334
+ if (ctx.evaluationContext) {
2335
+ return evaluateCondition(condition, ctx.evaluationContext());
2336
+ }
2337
+ const formValue = (ctx.formValue ?? ctx.form().value());
2361
2338
  const evaluationContext = {
2362
2339
  fieldValue: undefined,
2363
2340
  formValue,
@@ -2385,6 +2362,7 @@ function evaluateLogicOfType(fieldLogic, logicType, ctx) {
2385
2362
  const buttonCtx = {
2386
2363
  form: ctx.form,
2387
2364
  formValue: ctx.formValue,
2365
+ evaluationContext: ctx.evaluationContext,
2388
2366
  logger: ctx.logger,
2389
2367
  };
2390
2368
  return fieldLogic.filter((logic) => logic.type === logicType).some((logic) => evaluateLogicCondition(logic.condition, buttonCtx));
@@ -2441,6 +2419,7 @@ function evaluateNonFieldHidden(ctx) {
2441
2419
  return evaluateLogicOfType(ctx.fieldLogic, 'hidden', {
2442
2420
  form: ctx.form,
2443
2421
  formValue: ctx.formValue,
2422
+ evaluationContext: ctx.evaluationContext,
2444
2423
  logger: ctx.logger,
2445
2424
  });
2446
2425
  }
@@ -2457,6 +2436,7 @@ function evaluateNonFieldDisabled(ctx) {
2457
2436
  return evaluateLogicOfType(ctx.fieldLogic, 'disabled', {
2458
2437
  form: ctx.form,
2459
2438
  formValue: ctx.formValue,
2439
+ evaluationContext: ctx.evaluationContext,
2460
2440
  logger: ctx.logger,
2461
2441
  });
2462
2442
  }
@@ -2465,12 +2445,52 @@ function evaluateNonFieldDisabled(ctx) {
2465
2445
  function resolveNonFieldDisabled(ctx) {
2466
2446
  return computed(() => evaluateNonFieldDisabled(ctx));
2467
2447
  }
2448
+ function injectNonFieldEvaluationContext(fieldDef, options = {}) {
2449
+ const fieldContextRegistry = inject(FieldContextRegistryService);
2450
+ const functionRegistry = inject(FunctionRegistryService);
2451
+ const arrayContext = options.scopeToArrayItem ? inject(ARRAY_CONTEXT, { optional: true }) : null;
2452
+ return () => {
2453
+ const arrayScope = arrayContext
2454
+ ? { arrayKey: arrayContext.arrayKey, index: arrayContext.index(), localKey: fieldDef.key ?? '' }
2455
+ : undefined;
2456
+ return fieldContextRegistry.createDisplayOnlyContext(fieldDef.key ?? '', functionRegistry.getCustomFunctions(), arrayScope);
2457
+ };
2458
+ }
2468
2459
 
2469
2460
  function isFunctionHttpValidator(config) {
2470
2461
  const c = config;
2471
2462
  return typeof c.functionName === 'string' || (c.fn !== null && typeof c.fn === 'object');
2472
2463
  }
2473
2464
 
2465
+ function evaluateHttpValidationResponse(response, mapping, logger) {
2466
+ const scope = { response };
2467
+ try {
2468
+ const result = ExpressionParser.evaluate(mapping.validWhen, scope);
2469
+ if (typeof result !== 'boolean') {
2470
+ logger.warn(`validWhen expression "${mapping.validWhen}" returned non-boolean value:`, result, 'Expected true or false.');
2471
+ }
2472
+ if (result === true) {
2473
+ return null;
2474
+ }
2475
+ const error = { kind: mapping.errorKind };
2476
+ if (mapping.errorParams) {
2477
+ for (const [key, expression] of Object.entries(mapping.errorParams)) {
2478
+ try {
2479
+ error[key] = ExpressionParser.evaluate(expression, scope);
2480
+ }
2481
+ catch (err) {
2482
+ logger.warn(`Error evaluating errorParam "${key}":`, expression, err);
2483
+ }
2484
+ }
2485
+ }
2486
+ return error;
2487
+ }
2488
+ catch (err) {
2489
+ logger.warn('Error evaluating validWhen expression:', mapping.validWhen, err);
2490
+ return { kind: mapping.errorKind };
2491
+ }
2492
+ }
2493
+
2474
2494
  class DynamicValueFunctionCacheService {
2475
2495
  dynamicValueFunctionCache = new Map();
2476
2496
  }
@@ -2620,9 +2640,7 @@ function applyCustomValidator(config, fieldPath) {
2620
2640
  validatorFn = createFunctionValidator(config);
2621
2641
  }
2622
2642
  else {
2623
- const logger = inject(DynamicFormLogger);
2624
- logger.warn('Custom validator must have one of "expression", "functionName", or "fn"');
2625
- return;
2643
+ throw new DynamicFormError('Custom validator requires one of "expression", "functionName", or "fn".');
2626
2644
  }
2627
2645
  const whenLogic = createConditionalLogic(config.when);
2628
2646
  validate(fieldPath, (ctx) => {
@@ -2642,8 +2660,7 @@ function createFunctionValidator(config) {
2642
2660
  }
2643
2661
  const functionName = config.functionName;
2644
2662
  if (!functionName) {
2645
- logger.warn('Custom validator missing functionName');
2646
- return () => null;
2663
+ throw new DynamicFormError('Custom validator requires one of "functionName" or "fn".');
2647
2664
  }
2648
2665
  const registry = inject(FunctionRegistryService);
2649
2666
  const validatorFn = registry.getValidator(functionName);
@@ -2656,8 +2673,7 @@ function createExpressionValidator(config) {
2656
2673
  const logger = inject(DynamicFormLogger);
2657
2674
  const expression = config.expression;
2658
2675
  if (!expression) {
2659
- logger.warn('Custom validator missing expression');
2660
- return () => null;
2676
+ throw new DynamicFormError('Custom validator requires a non-empty "expression".');
2661
2677
  }
2662
2678
  const fieldContextRegistry = inject(FieldContextRegistryService);
2663
2679
  const functionRegistry = inject(FunctionRegistryService);
@@ -2940,10 +2956,10 @@ class EventBus {
2940
2956
  }
2941
2957
  return this.pipeline$.pipe(filter((event) => event.type === eventType));
2942
2958
  }
2943
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: EventBus, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2944
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: EventBus });
2959
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: EventBus, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2960
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: EventBus });
2945
2961
  }
2946
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: EventBus, decorators: [{
2962
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: EventBus, decorators: [{
2947
2963
  type: Injectable
2948
2964
  }] });
2949
2965
 
@@ -2973,18 +2989,6 @@ function resolveTokens(args, context) {
2973
2989
  });
2974
2990
  }
2975
2991
 
2976
- function applyHiddenLogic(inputs, fieldDef, rootFormRegistry) {
2977
- const rootForm = rootFormRegistry.rootForm();
2978
- if (rootForm && (fieldDef.hidden === true || fieldDef.logic?.some((l) => l.type === 'hidden'))) {
2979
- inputs['hidden'] = evaluateNonFieldHidden({
2980
- form: rootForm,
2981
- fieldLogic: fieldDef.logic,
2982
- explicitValue: fieldDef.hidden,
2983
- formValue: rootFormRegistry.formValue(),
2984
- });
2985
- }
2986
- }
2987
-
2988
2992
  function getGridClassString(fieldDef) {
2989
2993
  const col = fieldDef.col;
2990
2994
  if (typeof col === 'number' && Number.isInteger(col) && col > 0 && col <= 12) {
@@ -3005,8 +3009,22 @@ function buildClassName(fieldDef) {
3005
3009
  return classes.length > 0 ? classes.join(' ') : undefined;
3006
3010
  }
3007
3011
 
3012
+ function applyHiddenLogic(inputs, fieldDef, rootFormRegistry, evaluationContext) {
3013
+ const rootForm = rootFormRegistry.rootForm();
3014
+ if (rootForm && (fieldDef.hidden === true || fieldDef.logic?.some((l) => l.type === 'hidden'))) {
3015
+ inputs['hidden'] = evaluateNonFieldHidden({
3016
+ form: rootForm,
3017
+ fieldLogic: fieldDef.logic,
3018
+ explicitValue: fieldDef.hidden,
3019
+ formValue: rootFormRegistry.formValue(),
3020
+ evaluationContext,
3021
+ });
3022
+ }
3023
+ }
3024
+
3008
3025
  function arrayFieldMapper(fieldDef) {
3009
3026
  const rootFormRegistry = inject(RootFormRegistryService);
3027
+ const evaluationContext = injectNonFieldEvaluationContext(fieldDef, { scopeToArrayItem: true });
3010
3028
  const className = buildClassName(fieldDef);
3011
3029
  return computed(() => {
3012
3030
  const inputs = {
@@ -3014,7 +3032,7 @@ function arrayFieldMapper(fieldDef) {
3014
3032
  field: fieldDef,
3015
3033
  ...(className !== undefined && { className }),
3016
3034
  };
3017
- applyHiddenLogic(inputs, fieldDef, rootFormRegistry);
3035
+ applyHiddenLogic(inputs, fieldDef, rootFormRegistry, evaluationContext);
3018
3036
  return inputs;
3019
3037
  });
3020
3038
  }
@@ -3059,6 +3077,7 @@ function baseFieldMapper(fieldDef) {
3059
3077
 
3060
3078
  function containerFieldMapper(fieldDef) {
3061
3079
  const rootFormRegistry = inject(RootFormRegistryService);
3080
+ const evaluationContext = injectNonFieldEvaluationContext(fieldDef, { scopeToArrayItem: true });
3062
3081
  const className = buildClassName(fieldDef);
3063
3082
  return computed(() => {
3064
3083
  const containerField = { ...fieldDef, wrappers: undefined, skipAutoWrappers: undefined, skipDefaultWrappers: undefined };
@@ -3067,13 +3086,14 @@ function containerFieldMapper(fieldDef) {
3067
3086
  field: containerField,
3068
3087
  ...(className !== undefined && { className }),
3069
3088
  };
3070
- applyHiddenLogic(inputs, fieldDef, rootFormRegistry);
3089
+ applyHiddenLogic(inputs, fieldDef, rootFormRegistry, evaluationContext);
3071
3090
  return inputs;
3072
3091
  });
3073
3092
  }
3074
3093
 
3075
3094
  function groupFieldMapper(fieldDef) {
3076
3095
  const rootFormRegistry = inject(RootFormRegistryService);
3096
+ const evaluationContext = injectNonFieldEvaluationContext(fieldDef, { scopeToArrayItem: true });
3077
3097
  const className = buildClassName(fieldDef);
3078
3098
  return computed(() => {
3079
3099
  const inputs = {
@@ -3081,7 +3101,7 @@ function groupFieldMapper(fieldDef) {
3081
3101
  field: fieldDef,
3082
3102
  ...(className !== undefined && { className }),
3083
3103
  };
3084
- applyHiddenLogic(inputs, fieldDef, rootFormRegistry);
3104
+ applyHiddenLogic(inputs, fieldDef, rootFormRegistry, evaluationContext);
3085
3105
  return inputs;
3086
3106
  });
3087
3107
  }
@@ -3089,6 +3109,7 @@ function groupFieldMapper(fieldDef) {
3089
3109
  const ROW_WRAPPERS = [{ type: 'row' }];
3090
3110
  function rowFieldMapper(fieldDef) {
3091
3111
  const rootFormRegistry = inject(RootFormRegistryService);
3112
+ const evaluationContext = injectNonFieldEvaluationContext(fieldDef, { scopeToArrayItem: true });
3092
3113
  const className = buildClassName(fieldDef);
3093
3114
  return computed(() => {
3094
3115
  const containerField = { ...fieldDef, type: 'container', wrappers: ROW_WRAPPERS };
@@ -3097,7 +3118,7 @@ function rowFieldMapper(fieldDef) {
3097
3118
  field: containerField,
3098
3119
  ...(className !== undefined && { className }),
3099
3120
  };
3100
- applyHiddenLogic(inputs, fieldDef, rootFormRegistry);
3121
+ applyHiddenLogic(inputs, fieldDef, rootFormRegistry, evaluationContext);
3101
3122
  return inputs;
3102
3123
  });
3103
3124
  }
@@ -3113,11 +3134,12 @@ function pageFieldMapper(fieldDef) {
3113
3134
 
3114
3135
  function textFieldMapper(fieldDef) {
3115
3136
  const rootFormRegistry = inject(RootFormRegistryService);
3137
+ const evaluationContext = injectNonFieldEvaluationContext(fieldDef, { scopeToArrayItem: true });
3116
3138
  const defaultProps = inject(DEFAULT_PROPS);
3117
3139
  return computed(() => {
3118
3140
  const baseInputs = buildBaseInputs(fieldDef, defaultProps());
3119
3141
  const inputs = { ...baseInputs };
3120
- applyHiddenLogic(inputs, fieldDef, rootFormRegistry);
3142
+ applyHiddenLogic(inputs, fieldDef, rootFormRegistry, evaluationContext);
3121
3143
  return inputs;
3122
3144
  });
3123
3145
  }
@@ -3276,10 +3298,10 @@ class DynamicTextPipe {
3276
3298
  }
3277
3299
  return of(value || '');
3278
3300
  }
3279
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: DynamicTextPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
3280
- static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "22.0.0", ngImport: i0, type: DynamicTextPipe, isStandalone: true, name: "dynamicText" });
3301
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: DynamicTextPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
3302
+ static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "22.0.1", ngImport: i0, type: DynamicTextPipe, isStandalone: true, name: "dynamicText" });
3281
3303
  }
3282
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: DynamicTextPipe, decorators: [{
3304
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: DynamicTextPipe, decorators: [{
3283
3305
  type: Pipe,
3284
3306
  args: [{
3285
3307
  name: 'dynamicText',
@@ -3609,5 +3631,5 @@ function createWarningTracker() {
3609
3631
 
3610
3632
 
3611
3633
 
3612
- export { ADDON_ACTION_HANDLERS, ADDON_ACTION_REGISTRY, ADDON_TYPE_COMPONENT_CACHE, ADDON_TYPE_DEFINITIONS, ADDON_TYPE_REGISTRY, ARRAY_CONTEXT, AsyncConditionFunctionCacheService, ComponentInitializedEvent, DEFAULT_PROPS, DEFAULT_VALIDATION_MESSAGES, DEFAULT_WRAPPERS, DEPRECATION_WARNING_TRACKER, DF_FIELD_TEMPLATES, DfAddonSlot, DynamicFormError, DynamicFormLogger, DynamicTextPipe, DynamicValueFunctionCacheService, EMIT_FORM_VALUE_ON_EVENTS, EXTERNAL_DATA, Evaluator, EventBus, ExpressionParser, ExpressionParserError, FIELD_REGISTRY, FIELD_SIGNAL_CONTEXT, FORM_ID_PREFIX, FORM_OPTIONS, FieldContextRegistryService, FunctionRegistryService, GROUP_CONTEXT, HTTP_CONDITION_CACHE, HttpConditionCache, HttpConditionFunctionCacheService, INITIALIZATION_TIMEOUT_MS, LogicFunctionCacheService, NoopLogger, Parser, READONLY_FIELD_TREE_CACHE, RootFormRegistryService, TokenType, Tokenizer, WRAPPER_AUTO_ASSOCIATIONS, WRAPPER_COMPONENT_CACHE, WRAPPER_REGISTRY, applyHiddenLogic, applyMetaToElement, applyValidator, applyValidators, arrayFieldMapper, assertNever, baseFieldMapper, buildBaseInputs, buildClassName, compareValues, containerFieldMapper, createAddonActionRegistry, createAsyncConditionLogicFunction, createDebouncedLogicFunction, createDebouncedResourceLogicFn, createDefaultDerivationLogConfig, createDynamicValueFunction, createFormFieldStateMap, createHttpConditionLogicFunction, createInitializationTracker, createLogicFunction, createWarningTracker, deepMergeDefaults, derivedFromDeferred, detectFormMode, dynamicTextToObservable, evaluateCondition, evaluateHttpValidationResponse, evaluateNonFieldDisabled, evaluateNonFieldHidden, extractExpressionDependencies, extractStringDependencies, getArrayLength, getChangedKeys, getFieldValueHandling, getGridClassString, getNestedValue, groupFieldMapper, hasChildFields, hasCrossFieldWhenCondition, hasFormValue, hasNestedProperty, hasTargetProperty, injectAddonTypeRegistry, injectFieldSignalContext, injectFieldsSupportingAddons, interpolateParams, isArrayField, isCheckedField, isContainerField, isCrossFieldBuiltInValidator, isCrossFieldExpression, isCrossFieldSchema, isCrossFieldStateLogic, isCrossFieldValidator, isDerivationLogicConfig, isDisplayOnlyField, isEqual, isFieldBoundContext, isFormStateCondition, isFunctionHttpValidator, isGenericContainerField, isGroupField, isHiddenField, isLeafField, isNonPagedForm, isPageField, isPagedForm, isResourceBasedValidator, isRowField, isSimplifiedArrayField, isStateLogicConfig, isValidNonPagedForm, isValidPagedForm, isValueBearingField, isValueField, isWrapperTypeDefinition, keyBy, mapValues, memoize, normalizeFieldsArray, omit, pageFieldMapper, readFieldStateInfo, resolveDynamicValue, resolveHttpRequest, resolveNextButtonDisabled, resolveNonFieldDisabled, resolveNonFieldHidden, resolveSubmitButtonDisabled, resolveTokens, rowFieldMapper, runPresetAction, safeReadPathKeys, setupInitializationTracking, shouldLog, stableStringify, textFieldMapper, toReadonlyFieldTree, toReadonlyFieldTreeCached, validatePageNesting, withPreviousValue, writeToFieldValue };
3634
+ export { ADDON_ACTION_HANDLERS, ADDON_ACTION_REGISTRY, ADDON_TYPE_COMPONENT_CACHE, ADDON_TYPE_DEFINITIONS, ADDON_TYPE_REGISTRY, ARRAY_CONTEXT, AsyncConditionFunctionCacheService, ComponentInitializedEvent, DEFAULT_PROPS, DEFAULT_VALIDATION_MESSAGES, DEFAULT_WRAPPERS, DEPRECATION_WARNING_TRACKER, DF_FIELD_TEMPLATES, DfAddonSlot, DynamicFormError, DynamicFormLogger, DynamicTextPipe, DynamicValueFunctionCacheService, EMIT_FORM_VALUE_ON_EVENTS, EXTERNAL_DATA, EventBus, ExpressionParser, ExpressionParserError, FIELD_REGISTRY, FIELD_SIGNAL_CONTEXT, FORM_ID_PREFIX, FORM_OPTIONS, FieldContextRegistryService, FunctionRegistryService, GROUP_CONTEXT, HTTP_CONDITION_CACHE, HttpConditionCache, HttpConditionFunctionCacheService, INITIALIZATION_TIMEOUT_MS, LogicFunctionCacheService, NoopLogger, READONLY_FIELD_TREE_CACHE, RootFormRegistryService, TokenType, WRAPPER_AUTO_ASSOCIATIONS, WRAPPER_COMPONENT_CACHE, WRAPPER_REGISTRY, applyMetaToElement, applyValidator, applyValidators, arrayFieldMapper, assertNever, baseFieldMapper, buildBaseInputs, compareValues, containerFieldMapper, createAddonActionRegistry, createDebouncedLogicFunction, createDefaultDerivationLogConfig, createDynamicValueFunction, createFormFieldStateMap, createInitializationTracker, createLogicFunction, createWarningTracker, deepMergeDefaults, derivedFromDeferred, detectFormMode, dynamicTextToObservable, evaluateCondition, evaluateNonFieldDisabled, evaluateNonFieldHidden, extractExpressionDependencies, extractStringDependencies, getArrayLength, getChangedKeys, getFieldValueHandling, getNestedValue, groupFieldMapper, hasChildFields, hasCrossFieldWhenCondition, hasFormValue, hasNestedProperty, hasTargetProperty, injectAddonTypeRegistry, injectFieldSignalContext, injectFieldsSupportingAddons, injectNonFieldEvaluationContext, interpolateParams, isArrayField, isCheckedField, isContainerField, isCrossFieldBuiltInValidator, isCrossFieldExpression, isCrossFieldSchema, isCrossFieldStateLogic, isCrossFieldValidator, isDerivationLogicConfig, isDisplayOnlyField, isEqual, isFieldBoundContext, isFormStateCondition, isFunctionHttpValidator, isGenericContainerField, isGroupField, isHiddenField, isLeafField, isNonPagedForm, isPageField, isPagedForm, isResourceBasedValidator, isRowField, isSimplifiedArrayField, isStateLogicConfig, isValidNonPagedForm, isValidPagedForm, isValueBearingField, isValueField, isWrapperTypeDefinition, keyBy, mapValues, memoize, normalizeFieldsArray, omit, pageFieldMapper, readFieldStateInfo, resolveDynamicValue, resolveHttpRequest, resolveNextButtonDisabled, resolveNonFieldDisabled, resolveNonFieldHidden, resolveSubmitButtonDisabled, resolveTokens, rowFieldMapper, runPresetAction, setupInitializationTracking, shouldLog, textFieldMapper, toReadonlyFieldTree, toReadonlyFieldTreeCached, validatePageNesting, withPreviousValue, writeToFieldValue };
3613
3635
  //# sourceMappingURL=ng-forge-dynamic-forms-internal.mjs.map