@ng-forge/dynamic-forms 1.0.0-rc.3 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +28 -34
- package/fesm2022/{ng-forge-dynamic-forms-array-field.component-D5qCzJsL.mjs → ng-forge-dynamic-forms-array-field.component-CIheZh11.mjs} +26 -20
- package/fesm2022/ng-forge-dynamic-forms-array-field.component-CIheZh11.mjs.map +1 -0
- package/fesm2022/{ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-D6ntLtW9.mjs → ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-C9aVachF.mjs} +39 -14
- package/fesm2022/ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-C9aVachF.mjs.map +1 -0
- package/fesm2022/{ng-forge-dynamic-forms-container-field.component-nB6KFqVs.mjs → ng-forge-dynamic-forms-container-field.component-DNafCSO8.mjs} +5 -5
- package/fesm2022/{ng-forge-dynamic-forms-container-field.component-nB6KFqVs.mjs.map → ng-forge-dynamic-forms-container-field.component-DNafCSO8.mjs.map} +1 -1
- package/fesm2022/{ng-forge-dynamic-forms-css-wrapper.component-AScQAJTQ.mjs → ng-forge-dynamic-forms-css-wrapper.component-BmHGlmES.mjs} +4 -4
- package/fesm2022/{ng-forge-dynamic-forms-css-wrapper.component-AScQAJTQ.mjs.map → ng-forge-dynamic-forms-css-wrapper.component-BmHGlmES.mjs.map} +1 -1
- package/fesm2022/{ng-forge-dynamic-forms-group-field.component-Bq6Sgd6Z.mjs → ng-forge-dynamic-forms-group-field.component-CKVIw2K5.mjs} +5 -5
- package/fesm2022/{ng-forge-dynamic-forms-group-field.component-Bq6Sgd6Z.mjs.map → ng-forge-dynamic-forms-group-field.component-CKVIw2K5.mjs.map} +1 -1
- package/fesm2022/ng-forge-dynamic-forms-integration.mjs +34 -34
- package/fesm2022/ng-forge-dynamic-forms-internal.mjs +1397 -1401
- package/fesm2022/ng-forge-dynamic-forms-internal.mjs.map +1 -1
- package/fesm2022/{ng-forge-dynamic-forms-ng-forge-dynamic-forms-C59pvc26.mjs → ng-forge-dynamic-forms-ng-forge-dynamic-forms-CBawLbtp.mjs} +70 -57
- package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-CBawLbtp.mjs.map +1 -0
- package/fesm2022/{ng-forge-dynamic-forms-page-field.component-DRaxbiqA.mjs → ng-forge-dynamic-forms-page-field.component-BRixkgyS.mjs} +5 -5
- package/fesm2022/{ng-forge-dynamic-forms-page-field.component-DRaxbiqA.mjs.map → ng-forge-dynamic-forms-page-field.component-BRixkgyS.mjs.map} +1 -1
- package/fesm2022/{ng-forge-dynamic-forms-row-wrapper.component-BlVa801t.mjs → ng-forge-dynamic-forms-row-wrapper.component-BBWmqQA_.mjs} +4 -4
- package/fesm2022/{ng-forge-dynamic-forms-row-wrapper.component-BlVa801t.mjs.map → ng-forge-dynamic-forms-row-wrapper.component-BBWmqQA_.mjs.map} +1 -1
- package/fesm2022/{ng-forge-dynamic-forms-text-field.component-CNCpmf5_.mjs → ng-forge-dynamic-forms-text-field.component-C-f80sbk.mjs} +4 -4
- 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
- package/fesm2022/ng-forge-dynamic-forms.mjs +1 -1
- package/package.json +6 -6
- package/types/ng-forge-dynamic-forms-integration.d.ts +1 -1
- package/types/ng-forge-dynamic-forms-internal.d.ts +21 -200
- package/fesm2022/ng-forge-dynamic-forms-array-field.component-D5qCzJsL.mjs.map +0 -1
- package/fesm2022/ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-D6ntLtW9.mjs.map +0 -1
- package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-C59pvc26.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,
|
|
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.
|
|
159
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.
|
|
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.
|
|
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,1352 @@ function hasNestedProperty(obj, path) {
|
|
|
572
528
|
return keys[keys.length - 1] in current;
|
|
573
529
|
}
|
|
574
530
|
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
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
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
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
|
-
|
|
606
|
-
|
|
607
|
-
|
|
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
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
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
|
-
|
|
644
|
-
|
|
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
|
-
|
|
653
|
+
throw new ExpressionParserError('Unterminated string literal', start, this.expression);
|
|
648
654
|
}
|
|
649
|
-
|
|
650
|
-
const
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
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
|
-
|
|
660
|
-
|
|
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
|
-
};
|
|
665
|
+
return { type: TokenType.NUMBER, value, position: start };
|
|
697
666
|
}
|
|
698
|
-
|
|
699
|
-
const
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
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());
|
|
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++;
|
|
712
673
|
}
|
|
713
|
-
|
|
674
|
+
const type = this.getKeywordType(value);
|
|
675
|
+
return { type, value, position: start };
|
|
714
676
|
}
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
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;
|
|
722
689
|
}
|
|
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
690
|
}
|
|
741
|
-
|
|
742
|
-
const
|
|
743
|
-
const
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
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 };
|
|
700
|
+
}
|
|
701
|
+
if (threeChar === '!==') {
|
|
702
|
+
this.position += 3;
|
|
703
|
+
return { type: TokenType.NOT_EQUAL, value: '!==', position: start };
|
|
704
|
+
}
|
|
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);
|
|
772
|
+
}
|
|
756
773
|
}
|
|
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
774
|
}
|
|
760
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: FieldContextRegistryService, decorators: [{
|
|
761
|
-
type: Injectable
|
|
762
|
-
}] });
|
|
763
775
|
|
|
764
|
-
class
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
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();
|
|
776
|
+
class Parser {
|
|
777
|
+
tokens = [];
|
|
778
|
+
current = 0;
|
|
779
|
+
expression;
|
|
780
|
+
constructor(expression) {
|
|
781
|
+
this.expression = expression;
|
|
861
782
|
}
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
entries.forEach(([name, value]) => registry.set(name, value));
|
|
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);
|
|
869
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;
|
|
870
796
|
}
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
this.
|
|
876
|
-
}
|
|
877
|
-
setHttpValidators(httpValidators) {
|
|
878
|
-
this.setRegistryIfChanged(this.httpValidators, httpValidators);
|
|
797
|
+
parseExpression() {
|
|
798
|
+
const arrow = this.tryParseArrowFunction();
|
|
799
|
+
if (arrow)
|
|
800
|
+
return arrow;
|
|
801
|
+
return this.parseConditional();
|
|
879
802
|
}
|
|
880
|
-
|
|
881
|
-
this.
|
|
882
|
-
this.
|
|
883
|
-
|
|
884
|
-
this.
|
|
885
|
-
this.
|
|
886
|
-
this.
|
|
887
|
-
|
|
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 };
|
|
888
811
|
}
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
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
|
-
});
|
|
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 };
|
|
921
818
|
}
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
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;
|
|
934
840
|
}
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
}
|
|
939
|
-
|
|
940
|
-
|
|
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);
|
|
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 };
|
|
961
847
|
}
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
848
|
+
return null;
|
|
849
|
+
}
|
|
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 };
|
|
861
|
+
}
|
|
862
|
+
return node;
|
|
863
|
+
}
|
|
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 };
|
|
966
870
|
}
|
|
967
|
-
|
|
968
|
-
|
|
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 };
|
|
969
879
|
}
|
|
970
|
-
|
|
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.');
|
|
880
|
+
return node;
|
|
992
881
|
}
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
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 };
|
|
998
888
|
}
|
|
889
|
+
return node;
|
|
999
890
|
}
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
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);
|
|
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;
|
|
1029
899
|
}
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
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';
|
|
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;
|
|
1078
908
|
}
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
this.
|
|
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();
|
|
1086
916
|
}
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
while (
|
|
1090
|
-
this.
|
|
1091
|
-
|
|
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 };
|
|
926
|
+
}
|
|
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 };
|
|
932
|
+
}
|
|
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 };
|
|
955
|
+
}
|
|
956
|
+
else {
|
|
1092
957
|
break;
|
|
1093
958
|
}
|
|
1094
|
-
const token = this.nextToken();
|
|
1095
|
-
tokens.push(token);
|
|
1096
959
|
}
|
|
1097
|
-
|
|
1098
|
-
return tokens;
|
|
960
|
+
return node;
|
|
1099
961
|
}
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
962
|
+
parsePrimary() {
|
|
963
|
+
if (this.match(TokenType.TRUE)) {
|
|
964
|
+
return { type: 'Literal', value: true };
|
|
1103
965
|
}
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
const char = this.expression[this.position];
|
|
1107
|
-
if (char === '"' || char === "'") {
|
|
1108
|
-
return this.readString(char);
|
|
966
|
+
if (this.match(TokenType.FALSE)) {
|
|
967
|
+
return { type: 'Literal', value: false };
|
|
1109
968
|
}
|
|
1110
|
-
|
|
1111
|
-
return
|
|
969
|
+
if (this.match(TokenType.NULL)) {
|
|
970
|
+
return { type: 'Literal', value: null };
|
|
971
|
+
}
|
|
972
|
+
if (this.match(TokenType.UNDEFINED)) {
|
|
973
|
+
return { type: 'Literal', value: undefined };
|
|
974
|
+
}
|
|
975
|
+
if (this.match(TokenType.NUMBER)) {
|
|
976
|
+
return { type: 'Literal', value: parseFloat(this.previous().value) };
|
|
977
|
+
}
|
|
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;
|
|
998
|
+
}
|
|
999
|
+
throw new ExpressionParserError(`Unexpected token: ${this.peek().value}`, this.peek().position, this.expression);
|
|
1000
|
+
}
|
|
1001
|
+
parseArgumentList() {
|
|
1002
|
+
const args = [];
|
|
1003
|
+
if (!this.check(TokenType.RPAREN)) {
|
|
1004
|
+
do {
|
|
1005
|
+
args.push(this.parseExpression());
|
|
1006
|
+
} while (this.match(TokenType.COMMA));
|
|
1112
1007
|
}
|
|
1113
|
-
|
|
1114
|
-
|
|
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));
|
|
1115
1016
|
}
|
|
1116
|
-
|
|
1017
|
+
return elements;
|
|
1117
1018
|
}
|
|
1118
|
-
|
|
1119
|
-
const
|
|
1120
|
-
this.
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
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;
|
|
1147
|
-
}
|
|
1148
|
-
this.position++;
|
|
1019
|
+
parseObjectProperties() {
|
|
1020
|
+
const properties = [];
|
|
1021
|
+
if (this.check(TokenType.RBRACE)) {
|
|
1022
|
+
return properties;
|
|
1023
|
+
}
|
|
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;
|
|
1149
1030
|
}
|
|
1150
1031
|
else {
|
|
1151
|
-
value
|
|
1152
|
-
this.position++;
|
|
1032
|
+
throw new ExpressionParserError(`Expected property name (identifier or string) in object literal, got '${this.peek().value}'`, this.peek().position, this.expression);
|
|
1153
1033
|
}
|
|
1154
|
-
|
|
1155
|
-
|
|
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;
|
|
1156
1039
|
}
|
|
1157
|
-
|
|
1158
|
-
const
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
}
|
|
1164
|
-
if (!/^\d+(\.\d+)?$/.test(value)) {
|
|
1165
|
-
throw new ExpressionParserError(`Invalid number: ${value}`, start, this.expression);
|
|
1040
|
+
match(...types) {
|
|
1041
|
+
for (const type of types) {
|
|
1042
|
+
if (this.check(type)) {
|
|
1043
|
+
this.advance();
|
|
1044
|
+
return true;
|
|
1045
|
+
}
|
|
1166
1046
|
}
|
|
1167
|
-
return
|
|
1047
|
+
return false;
|
|
1168
1048
|
}
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
value += this.expression[this.position];
|
|
1174
|
-
this.position++;
|
|
1175
|
-
}
|
|
1176
|
-
const type = this.getKeywordType(value);
|
|
1177
|
-
return { type, value, position: start };
|
|
1049
|
+
check(type) {
|
|
1050
|
+
if (this.isAtEnd())
|
|
1051
|
+
return false;
|
|
1052
|
+
return this.peek().type === type;
|
|
1178
1053
|
}
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
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;
|
|
1191
|
-
}
|
|
1054
|
+
advance() {
|
|
1055
|
+
if (!this.isAtEnd())
|
|
1056
|
+
this.current++;
|
|
1057
|
+
return this.previous();
|
|
1192
1058
|
}
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
if (
|
|
1204
|
-
this.
|
|
1205
|
-
|
|
1206
|
-
}
|
|
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 };
|
|
1233
|
-
}
|
|
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);
|
|
1274
|
-
}
|
|
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
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
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
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
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
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
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
|
-
|
|
1315
|
-
const
|
|
1316
|
-
|
|
1317
|
-
|
|
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 (
|
|
1322
|
-
|
|
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
|
-
|
|
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
|
|
1216
|
+
return undefined;
|
|
1365
1217
|
}
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
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
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
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
|
-
|
|
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
|
|
1233
|
+
return undefined;
|
|
1392
1234
|
}
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
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
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1240
|
+
if (node.operator === '||') {
|
|
1241
|
+
const left = this.evaluate(node.left);
|
|
1242
|
+
return left ? left : this.evaluate(node.right);
|
|
1243
|
+
}
|
|
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);
|
|
1408
1279
|
}
|
|
1409
|
-
return node;
|
|
1410
1280
|
}
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
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);
|
|
1416
1292
|
}
|
|
1417
|
-
return this.parsePostfix();
|
|
1418
1293
|
}
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
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
|
-
}
|
|
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);
|
|
1461
1297
|
}
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
if (this.match(TokenType.TRUE)) {
|
|
1466
|
-
return { type: 'Literal', value: true };
|
|
1298
|
+
const obj = this.evaluate(node.callee.object);
|
|
1299
|
+
if (obj === null || obj === undefined) {
|
|
1300
|
+
return undefined;
|
|
1467
1301
|
}
|
|
1468
|
-
|
|
1469
|
-
|
|
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);
|
|
1470
1305
|
}
|
|
1471
|
-
|
|
1472
|
-
|
|
1306
|
+
if (!this.isMethodSafe(obj, methodName)) {
|
|
1307
|
+
throw new ExpressionParserError(`Method "${methodName}" is not allowed for security reasons`, 0, this.expression);
|
|
1473
1308
|
}
|
|
1474
|
-
|
|
1475
|
-
|
|
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);
|
|
1476
1313
|
}
|
|
1477
|
-
|
|
1478
|
-
|
|
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);
|
|
1479
1326
|
}
|
|
1480
|
-
|
|
1481
|
-
|
|
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;
|
|
1482
1344
|
}
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
return
|
|
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;
|
|
1487
1349
|
}
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
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);
|
|
1492
1366
|
}
|
|
1493
|
-
|
|
1494
|
-
|
|
1367
|
+
return value;
|
|
1368
|
+
}
|
|
1369
|
+
set(key, value) {
|
|
1370
|
+
if (this.cache.has(key)) {
|
|
1371
|
+
this.cache.delete(key);
|
|
1495
1372
|
}
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
this.
|
|
1499
|
-
|
|
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
|
+
}
|
|
1500
1379
|
}
|
|
1501
|
-
throw new ExpressionParserError(`Unexpected token: ${this.peek().value}`, this.peek().position, this.expression);
|
|
1502
1380
|
}
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
if (!this.check(TokenType.RPAREN)) {
|
|
1506
|
-
do {
|
|
1507
|
-
args.push(this.parseExpression());
|
|
1508
|
-
} while (this.match(TokenType.COMMA));
|
|
1509
|
-
}
|
|
1510
|
-
return args;
|
|
1381
|
+
clear() {
|
|
1382
|
+
this.cache.clear();
|
|
1511
1383
|
}
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
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
|
-
|
|
1395
|
+
const parser = new Parser(expression);
|
|
1396
|
+
const ast = parser.parse();
|
|
1397
|
+
astCache.set(expression, ast);
|
|
1398
|
+
return ast;
|
|
1520
1399
|
}
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
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
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
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
|
-
|
|
1534
|
-
|
|
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
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
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
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
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
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
return
|
|
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
|
-
|
|
1562
|
-
return
|
|
1517
|
+
if (typeof value !== 'object') {
|
|
1518
|
+
return JSON.stringify(value);
|
|
1563
1519
|
}
|
|
1564
|
-
|
|
1565
|
-
return
|
|
1520
|
+
if (Array.isArray(value)) {
|
|
1521
|
+
return '[' + value.map(stableStringify).join(',') + ']';
|
|
1566
1522
|
}
|
|
1567
|
-
|
|
1568
|
-
|
|
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
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
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
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
'
|
|
1581
|
-
|
|
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
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
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
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
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
|
-
|
|
1713
|
-
|
|
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
|
-
|
|
1716
|
-
|
|
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
|
|
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
|
-
|
|
1721
|
-
const
|
|
1722
|
-
if (
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
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
|
-
|
|
1733
|
-
|
|
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
|
|
1695
|
+
return resolved;
|
|
1736
1696
|
}
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
if (
|
|
1743
|
-
|
|
1744
|
-
return left ? left : this.evaluate(node.right);
|
|
1745
|
-
}
|
|
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);
|
|
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);
|
|
1781
1704
|
}
|
|
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) {
|
|
1724
|
+
const formValue = this.rootFormRegistry.formValue();
|
|
1725
|
+
const rootFormSignal = this.rootFormRegistry.rootForm;
|
|
1726
|
+
return {
|
|
1727
|
+
fieldValue: undefined,
|
|
1728
|
+
formValue,
|
|
1729
|
+
fieldPath,
|
|
1730
|
+
customFunctions: customFunctions || {},
|
|
1731
|
+
externalData: this.resolveExternalData(true),
|
|
1732
|
+
logger: this.logger,
|
|
1733
|
+
deprecationTracker: this.deprecationTracker ?? undefined,
|
|
1734
|
+
get formFieldState() {
|
|
1735
|
+
return createFormFieldStateMap(rootFormSignal(), true);
|
|
1736
|
+
},
|
|
1737
|
+
};
|
|
1738
|
+
}
|
|
1739
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: FieldContextRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1740
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: FieldContextRegistryService });
|
|
1741
|
+
}
|
|
1742
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: FieldContextRegistryService, decorators: [{
|
|
1743
|
+
type: Injectable
|
|
1744
|
+
}] });
|
|
1745
|
+
|
|
1746
|
+
class FunctionRegistryService {
|
|
1747
|
+
customFunctions = new Map();
|
|
1748
|
+
customFunctionScopes = new Map();
|
|
1749
|
+
derivationFunctions = new Map();
|
|
1750
|
+
asyncDerivationFunctions = new Map();
|
|
1751
|
+
asyncConditionFunctions = new Map();
|
|
1752
|
+
validators = new Map();
|
|
1753
|
+
asyncValidators = new Map();
|
|
1754
|
+
httpValidators = new Map();
|
|
1755
|
+
registerCustomFunction(name, fn, options) {
|
|
1756
|
+
this.customFunctions.set(name, fn);
|
|
1757
|
+
this.customFunctionScopes.set(name, options?.scope ?? 'form');
|
|
1758
|
+
}
|
|
1759
|
+
getCustomFunctionScope(name) {
|
|
1760
|
+
return this.customFunctionScopes.get(name);
|
|
1761
|
+
}
|
|
1762
|
+
isFieldScopedFunction(name) {
|
|
1763
|
+
return this.customFunctionScopes.get(name) === 'field';
|
|
1764
|
+
}
|
|
1765
|
+
getCustomFunctions() {
|
|
1766
|
+
return Object.fromEntries(this.customFunctions);
|
|
1767
|
+
}
|
|
1768
|
+
clearCustomFunctions() {
|
|
1769
|
+
this.customFunctions.clear();
|
|
1770
|
+
this.customFunctionScopes.clear();
|
|
1771
|
+
}
|
|
1772
|
+
registerDerivationFunction(name, fn) {
|
|
1773
|
+
this.derivationFunctions.set(name, fn);
|
|
1782
1774
|
}
|
|
1783
|
-
|
|
1784
|
-
|
|
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
|
-
}
|
|
1775
|
+
getDerivationFunction(name) {
|
|
1776
|
+
return this.derivationFunctions.get(name);
|
|
1795
1777
|
}
|
|
1796
|
-
|
|
1797
|
-
|
|
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;
|
|
1778
|
+
getDerivationFunctions() {
|
|
1779
|
+
return Object.fromEntries(this.derivationFunctions);
|
|
1817
1780
|
}
|
|
1818
|
-
|
|
1819
|
-
|
|
1781
|
+
setDerivationFunctions(derivations) {
|
|
1782
|
+
this.setRegistryIfChanged(this.derivationFunctions, derivations);
|
|
1820
1783
|
}
|
|
1821
|
-
|
|
1822
|
-
|
|
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;
|
|
1784
|
+
clearDerivationFunctions() {
|
|
1785
|
+
this.derivationFunctions.clear();
|
|
1830
1786
|
}
|
|
1831
|
-
|
|
1832
|
-
|
|
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
|
-
};
|
|
1787
|
+
registerAsyncDerivationFunction(name, fn) {
|
|
1788
|
+
this.asyncDerivationFunctions.set(name, fn);
|
|
1842
1789
|
}
|
|
1843
|
-
|
|
1844
|
-
|
|
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);
|
|
1790
|
+
getAsyncDerivationFunction(name) {
|
|
1791
|
+
return this.asyncDerivationFunctions.get(name);
|
|
1853
1792
|
}
|
|
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;
|
|
1793
|
+
getAsyncDerivationFunctions() {
|
|
1794
|
+
return Object.fromEntries(this.asyncDerivationFunctions);
|
|
1862
1795
|
}
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
if (value !== undefined) {
|
|
1866
|
-
this.cache.delete(key);
|
|
1867
|
-
this.cache.set(key, value);
|
|
1868
|
-
}
|
|
1869
|
-
return value;
|
|
1796
|
+
setAsyncDerivationFunctions(fns) {
|
|
1797
|
+
this.setRegistryIfChanged(this.asyncDerivationFunctions, fns);
|
|
1870
1798
|
}
|
|
1871
|
-
|
|
1872
|
-
|
|
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
|
-
}
|
|
1799
|
+
clearAsyncDerivationFunctions() {
|
|
1800
|
+
this.asyncDerivationFunctions.clear();
|
|
1882
1801
|
}
|
|
1883
|
-
|
|
1884
|
-
this.
|
|
1802
|
+
registerAsyncConditionFunction(name, fn) {
|
|
1803
|
+
this.asyncConditionFunctions.set(name, fn);
|
|
1885
1804
|
}
|
|
1886
|
-
|
|
1887
|
-
return this.
|
|
1805
|
+
getAsyncConditionFunction(name) {
|
|
1806
|
+
return this.asyncConditionFunctions.get(name);
|
|
1888
1807
|
}
|
|
1889
|
-
|
|
1890
|
-
|
|
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;
|
|
1808
|
+
getAsyncConditionFunctions() {
|
|
1809
|
+
return Object.fromEntries(this.asyncConditionFunctions);
|
|
1901
1810
|
}
|
|
1902
|
-
|
|
1903
|
-
|
|
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
|
-
}
|
|
1811
|
+
setAsyncConditionFunctions(fns) {
|
|
1812
|
+
this.setRegistryIfChanged(this.asyncConditionFunctions, fns);
|
|
1914
1813
|
}
|
|
1915
|
-
|
|
1916
|
-
|
|
1814
|
+
clearAsyncConditionFunctions() {
|
|
1815
|
+
this.asyncConditionFunctions.clear();
|
|
1917
1816
|
}
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
size: astCache.size,
|
|
1921
|
-
maxSize: MAX_AST_CACHE_SIZE,
|
|
1922
|
-
};
|
|
1817
|
+
registerValidator(name, fn) {
|
|
1818
|
+
this.validators.set(name, fn);
|
|
1923
1819
|
}
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
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
|
-
}
|
|
1820
|
+
getValidator(name) {
|
|
1821
|
+
return this.validators.get(name);
|
|
1822
|
+
}
|
|
1823
|
+
clearValidators() {
|
|
1824
|
+
this.validators.clear();
|
|
1825
|
+
}
|
|
1826
|
+
registerAsyncValidator(name, fn) {
|
|
1827
|
+
this.asyncValidators.set(name, fn);
|
|
1828
|
+
}
|
|
1829
|
+
getAsyncValidator(name) {
|
|
1830
|
+
return this.asyncValidators.get(name);
|
|
1831
|
+
}
|
|
1832
|
+
clearAsyncValidators() {
|
|
1833
|
+
this.asyncValidators.clear();
|
|
1834
|
+
}
|
|
1835
|
+
registerHttpValidator(name, config) {
|
|
1836
|
+
this.httpValidators.set(name, config);
|
|
1837
|
+
}
|
|
1838
|
+
getHttpValidator(name) {
|
|
1839
|
+
return this.httpValidators.get(name);
|
|
1840
|
+
}
|
|
1841
|
+
clearHttpValidators() {
|
|
1842
|
+
this.httpValidators.clear();
|
|
1843
|
+
}
|
|
1844
|
+
setRegistryIfChanged(registry, values) {
|
|
1845
|
+
if (!values)
|
|
1846
|
+
return;
|
|
1847
|
+
const entries = Object.entries(values);
|
|
1848
|
+
const hasChanges = entries.some(([name, value]) => registry.get(name) !== value);
|
|
1849
|
+
if (hasChanges) {
|
|
1850
|
+
entries.forEach(([name, value]) => registry.set(name, value));
|
|
1963
1851
|
}
|
|
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
1852
|
}
|
|
1979
|
-
|
|
1980
|
-
|
|
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;
|
|
1853
|
+
setValidators(validators) {
|
|
1854
|
+
this.setRegistryIfChanged(this.validators, validators);
|
|
1994
1855
|
}
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
return false;
|
|
1856
|
+
setAsyncValidators(asyncValidators) {
|
|
1857
|
+
this.setRegistryIfChanged(this.asyncValidators, asyncValidators);
|
|
1998
1858
|
}
|
|
1859
|
+
setHttpValidators(httpValidators) {
|
|
1860
|
+
this.setRegistryIfChanged(this.httpValidators, httpValidators);
|
|
1861
|
+
}
|
|
1862
|
+
clearAll() {
|
|
1863
|
+
this.clearCustomFunctions();
|
|
1864
|
+
this.clearDerivationFunctions();
|
|
1865
|
+
this.clearAsyncDerivationFunctions();
|
|
1866
|
+
this.clearAsyncConditionFunctions();
|
|
1867
|
+
this.clearValidators();
|
|
1868
|
+
this.clearAsyncValidators();
|
|
1869
|
+
this.clearHttpValidators();
|
|
1870
|
+
}
|
|
1871
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: FunctionRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1872
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: FunctionRegistryService });
|
|
1999
1873
|
}
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
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
|
-
}
|
|
1874
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: FunctionRegistryService, decorators: [{
|
|
1875
|
+
type: Injectable
|
|
1876
|
+
}] });
|
|
2014
1877
|
|
|
2015
1878
|
class HttpConditionCache {
|
|
2016
1879
|
cache = new Map();
|
|
@@ -2094,6 +1957,92 @@ function resolveHttpRequest(config, context) {
|
|
|
2094
1957
|
return request;
|
|
2095
1958
|
}
|
|
2096
1959
|
|
|
1960
|
+
function isSignalArray(value) {
|
|
1961
|
+
return Array.isArray(value) && value.every((item) => isSignal(item));
|
|
1962
|
+
}
|
|
1963
|
+
function isSignalRecord(value) {
|
|
1964
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value) && Object.values(value).every((item) => isSignal(item));
|
|
1965
|
+
}
|
|
1966
|
+
function derivedFromDeferred(source, operator, options) {
|
|
1967
|
+
const deferred$ = defer(() => {
|
|
1968
|
+
if (isSignal(source)) {
|
|
1969
|
+
return toObservable(source, { injector: options.injector });
|
|
1970
|
+
}
|
|
1971
|
+
if (isSignalArray(source)) {
|
|
1972
|
+
const observables = source.map((s) => toObservable(s, { injector: options.injector }));
|
|
1973
|
+
return combineLatest(observables);
|
|
1974
|
+
}
|
|
1975
|
+
if (isSignalRecord(source)) {
|
|
1976
|
+
const keys = Object.keys(source);
|
|
1977
|
+
const observables = keys.map((key) => toObservable(source[key], { injector: options.injector }));
|
|
1978
|
+
return combineLatest(observables, (...values) => {
|
|
1979
|
+
const result = {};
|
|
1980
|
+
keys.forEach((key, index) => {
|
|
1981
|
+
result[key] = values[index];
|
|
1982
|
+
});
|
|
1983
|
+
return result;
|
|
1984
|
+
});
|
|
1985
|
+
}
|
|
1986
|
+
throw new Error('Invalid source type. Expected a Signal, array of Signals, or object of Signals.');
|
|
1987
|
+
}).pipe(operator);
|
|
1988
|
+
return toSignal(deferred$, { initialValue: options.initialValue, injector: options.injector });
|
|
1989
|
+
}
|
|
1990
|
+
|
|
1991
|
+
function withPreviousValue(input) {
|
|
1992
|
+
const derived = linkedSignal({ ...(ngDevMode ? { debugName: "derived" } : /* istanbul ignore next */ {}), source: input.snapshot,
|
|
1993
|
+
computation: (snapshot, previous) => {
|
|
1994
|
+
if ((snapshot.status === 'loading' || snapshot.status === 'reloading') &&
|
|
1995
|
+
previous !== undefined &&
|
|
1996
|
+
previous.value.status !== 'error') {
|
|
1997
|
+
return { status: snapshot.status, value: previous.value.value };
|
|
1998
|
+
}
|
|
1999
|
+
return snapshot;
|
|
2000
|
+
} });
|
|
2001
|
+
return resourceFromSnapshots(derived);
|
|
2002
|
+
}
|
|
2003
|
+
|
|
2004
|
+
function createDebouncedResourceLogicFn(config) {
|
|
2005
|
+
const perFunctionSignalStore = new Map();
|
|
2006
|
+
return (ctx) => {
|
|
2007
|
+
const contextKey = safeReadPathKeys(ctx).join('.');
|
|
2008
|
+
let handle = perFunctionSignalStore.get(contextKey);
|
|
2009
|
+
if (!handle) {
|
|
2010
|
+
const trigger = signal(undefined, /* @ts-ignore */
|
|
2011
|
+
...(ngDevMode ? [{ debugName: "trigger" }] : /* istanbul ignore next */ []));
|
|
2012
|
+
const resultResource = untracked(() => {
|
|
2013
|
+
const debouncedTrigger = derivedFromDeferred(trigger, pipe(debounceTime(config.debounceMs),
|
|
2014
|
+
distinctUntilChanged((a, b) => a?.key === b?.key)), { initialValue: undefined, injector: config.injector });
|
|
2015
|
+
const resource = rxResource({
|
|
2016
|
+
params: () => debouncedTrigger(),
|
|
2017
|
+
stream: ({ params }) => config.buildStream(params.payload),
|
|
2018
|
+
defaultValue: config.pendingValue,
|
|
2019
|
+
injector: config.injector,
|
|
2020
|
+
});
|
|
2021
|
+
return withPreviousValue(resource);
|
|
2022
|
+
});
|
|
2023
|
+
handle = { trigger, resultResource, refetchVersion: 0 };
|
|
2024
|
+
perFunctionSignalStore.set(contextKey, handle);
|
|
2025
|
+
}
|
|
2026
|
+
const resolved = config.resolve(ctx);
|
|
2027
|
+
if (resolved.kind === 'returnEarly') {
|
|
2028
|
+
handle.refetchVersion++;
|
|
2029
|
+
return resolved.value;
|
|
2030
|
+
}
|
|
2031
|
+
if (resolved.kind === 'holdPrevious') {
|
|
2032
|
+
return handle.resultResource.value();
|
|
2033
|
+
}
|
|
2034
|
+
const dedupKey = `${resolved.key}::${handle.refetchVersion}`;
|
|
2035
|
+
const newTrigger = { key: dedupKey, payload: resolved.payload };
|
|
2036
|
+
untracked(() => {
|
|
2037
|
+
const current = handle.trigger();
|
|
2038
|
+
if (current?.key !== newTrigger.key) {
|
|
2039
|
+
handle.trigger.set(newTrigger);
|
|
2040
|
+
}
|
|
2041
|
+
});
|
|
2042
|
+
return handle.resultResource.value();
|
|
2043
|
+
};
|
|
2044
|
+
}
|
|
2045
|
+
|
|
2097
2046
|
function extractBoolean(response, responseExpression, pendingValue, logger) {
|
|
2098
2047
|
if (responseExpression) {
|
|
2099
2048
|
try {
|
|
@@ -2164,6 +2113,57 @@ function createHttpConditionLogicFunction(condition) {
|
|
|
2164
2113
|
return fn;
|
|
2165
2114
|
}
|
|
2166
2115
|
|
|
2116
|
+
function createAsyncConditionLogicFunction(condition) {
|
|
2117
|
+
const fieldContextRegistry = inject(FieldContextRegistryService);
|
|
2118
|
+
const functionRegistry = inject(FunctionRegistryService);
|
|
2119
|
+
const injector = inject(Injector);
|
|
2120
|
+
const logger = inject(DynamicFormLogger);
|
|
2121
|
+
const cacheService = inject(AsyncConditionFunctionCacheService);
|
|
2122
|
+
const pendingValue = condition.pendingValue ?? false;
|
|
2123
|
+
const debounceMs = condition.debounceMs ?? 300;
|
|
2124
|
+
if (condition.asyncFn && condition.asyncFunctionName) {
|
|
2125
|
+
logger.warn('Both "asyncFn" and "asyncFunctionName" are set on async condition. Inline "asyncFn" takes precedence; "asyncFunctionName" is ignored.');
|
|
2126
|
+
}
|
|
2127
|
+
const cacheKey = condition.asyncFn ? undefined : stableStringify(condition);
|
|
2128
|
+
if (cacheKey !== undefined) {
|
|
2129
|
+
const cached = cacheService.asyncConditionFunctionCache.get(cacheKey);
|
|
2130
|
+
if (cached) {
|
|
2131
|
+
return cached;
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
const fn = createDebouncedResourceLogicFn({
|
|
2135
|
+
pendingValue,
|
|
2136
|
+
debounceMs,
|
|
2137
|
+
injector,
|
|
2138
|
+
resolve: (ctx) => {
|
|
2139
|
+
const evaluationContext = fieldContextRegistry.createReactiveEvaluationContext(ctx, functionRegistry.getCustomFunctions());
|
|
2140
|
+
const key = stableStringify({
|
|
2141
|
+
formValue: evaluationContext.formValue,
|
|
2142
|
+
fieldValue: evaluationContext.fieldValue,
|
|
2143
|
+
externalData: evaluationContext.externalData,
|
|
2144
|
+
});
|
|
2145
|
+
return { kind: 'trigger', key, payload: ctx };
|
|
2146
|
+
},
|
|
2147
|
+
buildStream: (capturedCtx) => {
|
|
2148
|
+
const asyncFn = condition.asyncFn ??
|
|
2149
|
+
(condition.asyncFunctionName ? functionRegistry.getAsyncConditionFunction(condition.asyncFunctionName) : undefined);
|
|
2150
|
+
if (!asyncFn) {
|
|
2151
|
+
logger.warn(`Async Condition - function '${condition.asyncFunctionName}' not found. ` + `Register it in customFnConfig.asyncConditions.`);
|
|
2152
|
+
return of(pendingValue);
|
|
2153
|
+
}
|
|
2154
|
+
const evaluationContext = fieldContextRegistry.createReactiveEvaluationContext(capturedCtx, functionRegistry.getCustomFunctions());
|
|
2155
|
+
return from(asyncFn(evaluationContext)).pipe(map((result) => !!result), catchError((error) => {
|
|
2156
|
+
logger.warn('Async Condition - function failed:', error);
|
|
2157
|
+
return of(pendingValue);
|
|
2158
|
+
}));
|
|
2159
|
+
},
|
|
2160
|
+
});
|
|
2161
|
+
if (cacheKey !== undefined) {
|
|
2162
|
+
cacheService.asyncConditionFunctionCache.set(cacheKey, fn);
|
|
2163
|
+
}
|
|
2164
|
+
return fn;
|
|
2165
|
+
}
|
|
2166
|
+
|
|
2167
2167
|
class LogicFunctionCacheService {
|
|
2168
2168
|
logicFunctionCache = new Map();
|
|
2169
2169
|
debouncedLogicFunctionCache = new Map();
|
|
@@ -2294,35 +2294,6 @@ function toReadonlyFieldTreeCached(cache, field) {
|
|
|
2294
2294
|
return view;
|
|
2295
2295
|
}
|
|
2296
2296
|
|
|
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
2297
|
const noOpLogger = {
|
|
2327
2298
|
debug: () => { },
|
|
2328
2299
|
info: () => { },
|
|
@@ -2471,6 +2442,35 @@ function isFunctionHttpValidator(config) {
|
|
|
2471
2442
|
return typeof c.functionName === 'string' || (c.fn !== null && typeof c.fn === 'object');
|
|
2472
2443
|
}
|
|
2473
2444
|
|
|
2445
|
+
function evaluateHttpValidationResponse(response, mapping, logger) {
|
|
2446
|
+
const scope = { response };
|
|
2447
|
+
try {
|
|
2448
|
+
const result = ExpressionParser.evaluate(mapping.validWhen, scope);
|
|
2449
|
+
if (typeof result !== 'boolean') {
|
|
2450
|
+
logger.warn(`validWhen expression "${mapping.validWhen}" returned non-boolean value:`, result, 'Expected true or false.');
|
|
2451
|
+
}
|
|
2452
|
+
if (result === true) {
|
|
2453
|
+
return null;
|
|
2454
|
+
}
|
|
2455
|
+
const error = { kind: mapping.errorKind };
|
|
2456
|
+
if (mapping.errorParams) {
|
|
2457
|
+
for (const [key, expression] of Object.entries(mapping.errorParams)) {
|
|
2458
|
+
try {
|
|
2459
|
+
error[key] = ExpressionParser.evaluate(expression, scope);
|
|
2460
|
+
}
|
|
2461
|
+
catch (err) {
|
|
2462
|
+
logger.warn(`Error evaluating errorParam "${key}":`, expression, err);
|
|
2463
|
+
}
|
|
2464
|
+
}
|
|
2465
|
+
}
|
|
2466
|
+
return error;
|
|
2467
|
+
}
|
|
2468
|
+
catch (err) {
|
|
2469
|
+
logger.warn('Error evaluating validWhen expression:', mapping.validWhen, err);
|
|
2470
|
+
return { kind: mapping.errorKind };
|
|
2471
|
+
}
|
|
2472
|
+
}
|
|
2473
|
+
|
|
2474
2474
|
class DynamicValueFunctionCacheService {
|
|
2475
2475
|
dynamicValueFunctionCache = new Map();
|
|
2476
2476
|
}
|
|
@@ -2620,9 +2620,7 @@ function applyCustomValidator(config, fieldPath) {
|
|
|
2620
2620
|
validatorFn = createFunctionValidator(config);
|
|
2621
2621
|
}
|
|
2622
2622
|
else {
|
|
2623
|
-
|
|
2624
|
-
logger.warn('Custom validator must have one of "expression", "functionName", or "fn"');
|
|
2625
|
-
return;
|
|
2623
|
+
throw new DynamicFormError('Custom validator requires one of "expression", "functionName", or "fn".');
|
|
2626
2624
|
}
|
|
2627
2625
|
const whenLogic = createConditionalLogic(config.when);
|
|
2628
2626
|
validate(fieldPath, (ctx) => {
|
|
@@ -2642,8 +2640,7 @@ function createFunctionValidator(config) {
|
|
|
2642
2640
|
}
|
|
2643
2641
|
const functionName = config.functionName;
|
|
2644
2642
|
if (!functionName) {
|
|
2645
|
-
|
|
2646
|
-
return () => null;
|
|
2643
|
+
throw new DynamicFormError('Custom validator requires one of "functionName" or "fn".');
|
|
2647
2644
|
}
|
|
2648
2645
|
const registry = inject(FunctionRegistryService);
|
|
2649
2646
|
const validatorFn = registry.getValidator(functionName);
|
|
@@ -2656,8 +2653,7 @@ function createExpressionValidator(config) {
|
|
|
2656
2653
|
const logger = inject(DynamicFormLogger);
|
|
2657
2654
|
const expression = config.expression;
|
|
2658
2655
|
if (!expression) {
|
|
2659
|
-
|
|
2660
|
-
return () => null;
|
|
2656
|
+
throw new DynamicFormError('Custom validator requires a non-empty "expression".');
|
|
2661
2657
|
}
|
|
2662
2658
|
const fieldContextRegistry = inject(FieldContextRegistryService);
|
|
2663
2659
|
const functionRegistry = inject(FunctionRegistryService);
|
|
@@ -2940,10 +2936,10 @@ class EventBus {
|
|
|
2940
2936
|
}
|
|
2941
2937
|
return this.pipeline$.pipe(filter((event) => event.type === eventType));
|
|
2942
2938
|
}
|
|
2943
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.
|
|
2944
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.
|
|
2939
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: EventBus, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
2940
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: EventBus });
|
|
2945
2941
|
}
|
|
2946
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.
|
|
2942
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: EventBus, decorators: [{
|
|
2947
2943
|
type: Injectable
|
|
2948
2944
|
}] });
|
|
2949
2945
|
|
|
@@ -2973,18 +2969,6 @@ function resolveTokens(args, context) {
|
|
|
2973
2969
|
});
|
|
2974
2970
|
}
|
|
2975
2971
|
|
|
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
2972
|
function getGridClassString(fieldDef) {
|
|
2989
2973
|
const col = fieldDef.col;
|
|
2990
2974
|
if (typeof col === 'number' && Number.isInteger(col) && col > 0 && col <= 12) {
|
|
@@ -3005,6 +2989,18 @@ function buildClassName(fieldDef) {
|
|
|
3005
2989
|
return classes.length > 0 ? classes.join(' ') : undefined;
|
|
3006
2990
|
}
|
|
3007
2991
|
|
|
2992
|
+
function applyHiddenLogic(inputs, fieldDef, rootFormRegistry) {
|
|
2993
|
+
const rootForm = rootFormRegistry.rootForm();
|
|
2994
|
+
if (rootForm && (fieldDef.hidden === true || fieldDef.logic?.some((l) => l.type === 'hidden'))) {
|
|
2995
|
+
inputs['hidden'] = evaluateNonFieldHidden({
|
|
2996
|
+
form: rootForm,
|
|
2997
|
+
fieldLogic: fieldDef.logic,
|
|
2998
|
+
explicitValue: fieldDef.hidden,
|
|
2999
|
+
formValue: rootFormRegistry.formValue(),
|
|
3000
|
+
});
|
|
3001
|
+
}
|
|
3002
|
+
}
|
|
3003
|
+
|
|
3008
3004
|
function arrayFieldMapper(fieldDef) {
|
|
3009
3005
|
const rootFormRegistry = inject(RootFormRegistryService);
|
|
3010
3006
|
const className = buildClassName(fieldDef);
|
|
@@ -3276,10 +3272,10 @@ class DynamicTextPipe {
|
|
|
3276
3272
|
}
|
|
3277
3273
|
return of(value || '');
|
|
3278
3274
|
}
|
|
3279
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.
|
|
3280
|
-
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "22.0.
|
|
3275
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: DynamicTextPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
3276
|
+
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "22.0.1", ngImport: i0, type: DynamicTextPipe, isStandalone: true, name: "dynamicText" });
|
|
3281
3277
|
}
|
|
3282
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.
|
|
3278
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: DynamicTextPipe, decorators: [{
|
|
3283
3279
|
type: Pipe,
|
|
3284
3280
|
args: [{
|
|
3285
3281
|
name: 'dynamicText',
|
|
@@ -3609,5 +3605,5 @@ function createWarningTracker() {
|
|
|
3609
3605
|
|
|
3610
3606
|
|
|
3611
3607
|
|
|
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,
|
|
3608
|
+
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, 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
3609
|
//# sourceMappingURL=ng-forge-dynamic-forms-internal.mjs.map
|