@ng-forge/dynamic-forms 1.0.0-rc.4 → 1.1.0-next.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +25 -32
- package/fesm2022/{ng-forge-dynamic-forms-array-field.component-DZWGHZjj.mjs → ng-forge-dynamic-forms-array-field.component-CIheZh11.mjs} +5 -5
- package/fesm2022/{ng-forge-dynamic-forms-array-field.component-DZWGHZjj.mjs.map → ng-forge-dynamic-forms-array-field.component-CIheZh11.mjs.map} +1 -1
- package/fesm2022/{ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-ueIHE70C.mjs → ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-C9aVachF.mjs} +2 -2
- package/fesm2022/{ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-ueIHE70C.mjs.map → ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-C9aVachF.mjs.map} +1 -1
- package/fesm2022/{ng-forge-dynamic-forms-container-field.component-Dt5yfGQ7.mjs → ng-forge-dynamic-forms-container-field.component-DNafCSO8.mjs} +5 -5
- package/fesm2022/{ng-forge-dynamic-forms-container-field.component-Dt5yfGQ7.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-BgoDlVYp.mjs → ng-forge-dynamic-forms-group-field.component-CKVIw2K5.mjs} +5 -5
- package/fesm2022/{ng-forge-dynamic-forms-group-field.component-BgoDlVYp.mjs.map → ng-forge-dynamic-forms-group-field.component-CKVIw2K5.mjs.map} +1 -1
- package/fesm2022/ng-forge-dynamic-forms-integration.mjs +70 -51
- package/fesm2022/ng-forge-dynamic-forms-integration.mjs.map +1 -1
- package/fesm2022/ng-forge-dynamic-forms-internal.mjs +1430 -1408
- package/fesm2022/ng-forge-dynamic-forms-internal.mjs.map +1 -1
- package/fesm2022/{ng-forge-dynamic-forms-ng-forge-dynamic-forms-CRCXs4f8.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-W9f5VyQJ.mjs → ng-forge-dynamic-forms-page-field.component-BRixkgyS.mjs} +5 -5
- package/fesm2022/{ng-forge-dynamic-forms-page-field.component-W9f5VyQJ.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 +5 -5
- package/types/ng-forge-dynamic-forms-integration.d.ts +29 -3
- package/types/ng-forge-dynamic-forms-internal.d.ts +50 -201
- package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-CRCXs4f8.mjs.map +0 -1
|
@@ -3,7 +3,7 @@ import { InjectionToken, computed, isSignal, inject, Injector, input, signal, Ch
|
|
|
3
3
|
import { NgComponentOutlet } from '@angular/common';
|
|
4
4
|
import { explicitEffect } from 'ngxtension/explicit-effect';
|
|
5
5
|
import { toSignal, toObservable, rxResource } from '@angular/core/rxjs-interop';
|
|
6
|
-
import { isObservable, defer, combineLatest, pipe, debounceTime, distinctUntilChanged,
|
|
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,1355 @@ 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
|
-
};
|
|
697
|
-
}
|
|
698
|
-
resolveExternalData(reactive) {
|
|
699
|
-
const externalDataSignal = this.externalDataSignal;
|
|
700
|
-
if (!externalDataSignal)
|
|
701
|
-
return undefined;
|
|
702
|
-
const externalDataRecord = reactive ? externalDataSignal() : untracked(() => externalDataSignal());
|
|
703
|
-
if (!externalDataRecord) {
|
|
704
|
-
return undefined;
|
|
705
|
-
}
|
|
706
|
-
const resolved = {};
|
|
707
|
-
for (const [key, value] of Object.entries(externalDataRecord)) {
|
|
708
|
-
if (!isSignal(value)) {
|
|
709
|
-
throw new DynamicFormError(`externalData["${key}"] must be a Signal. Got: ${typeof value}. Wrap it with signal(yourValue).`);
|
|
710
|
-
}
|
|
711
|
-
resolved[key] = reactive ? value() : untracked(() => value());
|
|
712
|
-
}
|
|
713
|
-
return resolved;
|
|
665
|
+
return { type: TokenType.NUMBER, value, position: start };
|
|
714
666
|
}
|
|
715
|
-
|
|
716
|
-
const
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
return this.buildArrayScopedContext(rootFormValue, arrayScope, fieldValue, customFunctions, true, fieldContext);
|
|
667
|
+
readIdentifierOrKeyword() {
|
|
668
|
+
const start = this.position;
|
|
669
|
+
let value = '';
|
|
670
|
+
while (this.position < this.expression.length && /[a-zA-Z0-9_$]/.test(this.expression[this.position])) {
|
|
671
|
+
value += this.expression[this.position];
|
|
672
|
+
this.position++;
|
|
722
673
|
}
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
return {
|
|
726
|
-
fieldValue,
|
|
727
|
-
formValue: rootFormValue,
|
|
728
|
-
fieldPath: localKey,
|
|
729
|
-
customFunctions: customFunctions || {},
|
|
730
|
-
externalData: this.resolveExternalData(true),
|
|
731
|
-
logger: this.logger,
|
|
732
|
-
deprecationTracker: this.deprecationTracker ?? undefined,
|
|
733
|
-
get fieldState() {
|
|
734
|
-
return readFieldStateInfo(extractFieldState(fieldContext), true);
|
|
735
|
-
},
|
|
736
|
-
get formFieldState() {
|
|
737
|
-
return createFormFieldStateMap(rootFormSignal(), true);
|
|
738
|
-
},
|
|
739
|
-
};
|
|
740
|
-
}
|
|
741
|
-
createDisplayOnlyContext(fieldPath, customFunctions) {
|
|
742
|
-
const formValue = this.rootFormRegistry.formValue();
|
|
743
|
-
const rootFormSignal = this.rootFormRegistry.rootForm;
|
|
744
|
-
return {
|
|
745
|
-
fieldValue: undefined,
|
|
746
|
-
formValue,
|
|
747
|
-
fieldPath,
|
|
748
|
-
customFunctions: customFunctions || {},
|
|
749
|
-
externalData: this.resolveExternalData(true),
|
|
750
|
-
logger: this.logger,
|
|
751
|
-
deprecationTracker: this.deprecationTracker ?? undefined,
|
|
752
|
-
get formFieldState() {
|
|
753
|
-
return createFormFieldStateMap(rootFormSignal(), true);
|
|
754
|
-
},
|
|
755
|
-
};
|
|
756
|
-
}
|
|
757
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: FieldContextRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
758
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: FieldContextRegistryService });
|
|
759
|
-
}
|
|
760
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: FieldContextRegistryService, decorators: [{
|
|
761
|
-
type: Injectable
|
|
762
|
-
}] });
|
|
763
|
-
|
|
764
|
-
class FunctionRegistryService {
|
|
765
|
-
customFunctions = new Map();
|
|
766
|
-
customFunctionScopes = new Map();
|
|
767
|
-
derivationFunctions = new Map();
|
|
768
|
-
asyncDerivationFunctions = new Map();
|
|
769
|
-
asyncConditionFunctions = new Map();
|
|
770
|
-
validators = new Map();
|
|
771
|
-
asyncValidators = new Map();
|
|
772
|
-
httpValidators = new Map();
|
|
773
|
-
registerCustomFunction(name, fn, options) {
|
|
774
|
-
this.customFunctions.set(name, fn);
|
|
775
|
-
this.customFunctionScopes.set(name, options?.scope ?? 'form');
|
|
776
|
-
}
|
|
777
|
-
getCustomFunctionScope(name) {
|
|
778
|
-
return this.customFunctionScopes.get(name);
|
|
779
|
-
}
|
|
780
|
-
isFieldScopedFunction(name) {
|
|
781
|
-
return this.customFunctionScopes.get(name) === 'field';
|
|
782
|
-
}
|
|
783
|
-
getCustomFunctions() {
|
|
784
|
-
return Object.fromEntries(this.customFunctions);
|
|
785
|
-
}
|
|
786
|
-
clearCustomFunctions() {
|
|
787
|
-
this.customFunctions.clear();
|
|
788
|
-
this.customFunctionScopes.clear();
|
|
789
|
-
}
|
|
790
|
-
registerDerivationFunction(name, fn) {
|
|
791
|
-
this.derivationFunctions.set(name, fn);
|
|
792
|
-
}
|
|
793
|
-
getDerivationFunction(name) {
|
|
794
|
-
return this.derivationFunctions.get(name);
|
|
795
|
-
}
|
|
796
|
-
getDerivationFunctions() {
|
|
797
|
-
return Object.fromEntries(this.derivationFunctions);
|
|
798
|
-
}
|
|
799
|
-
setDerivationFunctions(derivations) {
|
|
800
|
-
this.setRegistryIfChanged(this.derivationFunctions, derivations);
|
|
801
|
-
}
|
|
802
|
-
clearDerivationFunctions() {
|
|
803
|
-
this.derivationFunctions.clear();
|
|
804
|
-
}
|
|
805
|
-
registerAsyncDerivationFunction(name, fn) {
|
|
806
|
-
this.asyncDerivationFunctions.set(name, fn);
|
|
807
|
-
}
|
|
808
|
-
getAsyncDerivationFunction(name) {
|
|
809
|
-
return this.asyncDerivationFunctions.get(name);
|
|
810
|
-
}
|
|
811
|
-
getAsyncDerivationFunctions() {
|
|
812
|
-
return Object.fromEntries(this.asyncDerivationFunctions);
|
|
813
|
-
}
|
|
814
|
-
setAsyncDerivationFunctions(fns) {
|
|
815
|
-
this.setRegistryIfChanged(this.asyncDerivationFunctions, fns);
|
|
816
|
-
}
|
|
817
|
-
clearAsyncDerivationFunctions() {
|
|
818
|
-
this.asyncDerivationFunctions.clear();
|
|
819
|
-
}
|
|
820
|
-
registerAsyncConditionFunction(name, fn) {
|
|
821
|
-
this.asyncConditionFunctions.set(name, fn);
|
|
822
|
-
}
|
|
823
|
-
getAsyncConditionFunction(name) {
|
|
824
|
-
return this.asyncConditionFunctions.get(name);
|
|
825
|
-
}
|
|
826
|
-
getAsyncConditionFunctions() {
|
|
827
|
-
return Object.fromEntries(this.asyncConditionFunctions);
|
|
828
|
-
}
|
|
829
|
-
setAsyncConditionFunctions(fns) {
|
|
830
|
-
this.setRegistryIfChanged(this.asyncConditionFunctions, fns);
|
|
831
|
-
}
|
|
832
|
-
clearAsyncConditionFunctions() {
|
|
833
|
-
this.asyncConditionFunctions.clear();
|
|
834
|
-
}
|
|
835
|
-
registerValidator(name, fn) {
|
|
836
|
-
this.validators.set(name, fn);
|
|
837
|
-
}
|
|
838
|
-
getValidator(name) {
|
|
839
|
-
return this.validators.get(name);
|
|
840
|
-
}
|
|
841
|
-
clearValidators() {
|
|
842
|
-
this.validators.clear();
|
|
843
|
-
}
|
|
844
|
-
registerAsyncValidator(name, fn) {
|
|
845
|
-
this.asyncValidators.set(name, fn);
|
|
846
|
-
}
|
|
847
|
-
getAsyncValidator(name) {
|
|
848
|
-
return this.asyncValidators.get(name);
|
|
849
|
-
}
|
|
850
|
-
clearAsyncValidators() {
|
|
851
|
-
this.asyncValidators.clear();
|
|
852
|
-
}
|
|
853
|
-
registerHttpValidator(name, config) {
|
|
854
|
-
this.httpValidators.set(name, config);
|
|
855
|
-
}
|
|
856
|
-
getHttpValidator(name) {
|
|
857
|
-
return this.httpValidators.get(name);
|
|
858
|
-
}
|
|
859
|
-
clearHttpValidators() {
|
|
860
|
-
this.httpValidators.clear();
|
|
674
|
+
const type = this.getKeywordType(value);
|
|
675
|
+
return { type, value, position: start };
|
|
861
676
|
}
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
677
|
+
getKeywordType(value) {
|
|
678
|
+
switch (value) {
|
|
679
|
+
case 'true':
|
|
680
|
+
return TokenType.TRUE;
|
|
681
|
+
case 'false':
|
|
682
|
+
return TokenType.FALSE;
|
|
683
|
+
case 'null':
|
|
684
|
+
return TokenType.NULL;
|
|
685
|
+
case 'undefined':
|
|
686
|
+
return TokenType.UNDEFINED;
|
|
687
|
+
default:
|
|
688
|
+
return TokenType.IDENTIFIER;
|
|
869
689
|
}
|
|
870
690
|
}
|
|
871
|
-
|
|
872
|
-
this.
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
this.
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
clearAll() {
|
|
881
|
-
this.clearCustomFunctions();
|
|
882
|
-
this.clearDerivationFunctions();
|
|
883
|
-
this.clearAsyncDerivationFunctions();
|
|
884
|
-
this.clearAsyncConditionFunctions();
|
|
885
|
-
this.clearValidators();
|
|
886
|
-
this.clearAsyncValidators();
|
|
887
|
-
this.clearHttpValidators();
|
|
888
|
-
}
|
|
889
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: FunctionRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
890
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: FunctionRegistryService });
|
|
891
|
-
}
|
|
892
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: FunctionRegistryService, decorators: [{
|
|
893
|
-
type: Injectable
|
|
894
|
-
}] });
|
|
895
|
-
|
|
896
|
-
function isSignalArray(value) {
|
|
897
|
-
return Array.isArray(value) && value.every((item) => isSignal(item));
|
|
898
|
-
}
|
|
899
|
-
function isSignalRecord(value) {
|
|
900
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value) && Object.values(value).every((item) => isSignal(item));
|
|
901
|
-
}
|
|
902
|
-
function derivedFromDeferred(source, operator, options) {
|
|
903
|
-
const deferred$ = defer(() => {
|
|
904
|
-
if (isSignal(source)) {
|
|
905
|
-
return toObservable(source, { injector: options.injector });
|
|
906
|
-
}
|
|
907
|
-
if (isSignalArray(source)) {
|
|
908
|
-
const observables = source.map((s) => toObservable(s, { injector: options.injector }));
|
|
909
|
-
return combineLatest(observables);
|
|
910
|
-
}
|
|
911
|
-
if (isSignalRecord(source)) {
|
|
912
|
-
const keys = Object.keys(source);
|
|
913
|
-
const observables = keys.map((key) => toObservable(source[key], { injector: options.injector }));
|
|
914
|
-
return combineLatest(observables, (...values) => {
|
|
915
|
-
const result = {};
|
|
916
|
-
keys.forEach((key, index) => {
|
|
917
|
-
result[key] = values[index];
|
|
918
|
-
});
|
|
919
|
-
return result;
|
|
920
|
-
});
|
|
921
|
-
}
|
|
922
|
-
throw new Error('Invalid source type. Expected a Signal, array of Signals, or object of Signals.');
|
|
923
|
-
}).pipe(operator);
|
|
924
|
-
return toSignal(deferred$, { initialValue: options.initialValue, injector: options.injector });
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
function withPreviousValue(input) {
|
|
928
|
-
const derived = linkedSignal({ ...(ngDevMode ? { debugName: "derived" } : /* istanbul ignore next */ {}), source: input.snapshot,
|
|
929
|
-
computation: (snapshot, previous) => {
|
|
930
|
-
if ((snapshot.status === 'loading' || snapshot.status === 'reloading') &&
|
|
931
|
-
previous !== undefined &&
|
|
932
|
-
previous.value.status !== 'error') {
|
|
933
|
-
return { status: snapshot.status, value: previous.value.value };
|
|
934
|
-
}
|
|
935
|
-
return snapshot;
|
|
936
|
-
} });
|
|
937
|
-
return resourceFromSnapshots(derived);
|
|
938
|
-
}
|
|
939
|
-
|
|
940
|
-
function createDebouncedResourceLogicFn(config) {
|
|
941
|
-
const perFunctionSignalStore = new Map();
|
|
942
|
-
return (ctx) => {
|
|
943
|
-
const contextKey = safeReadPathKeys(ctx).join('.');
|
|
944
|
-
let handle = perFunctionSignalStore.get(contextKey);
|
|
945
|
-
if (!handle) {
|
|
946
|
-
const trigger = signal(undefined, /* @ts-ignore */
|
|
947
|
-
...(ngDevMode ? [{ debugName: "trigger" }] : /* istanbul ignore next */ []));
|
|
948
|
-
const resultResource = untracked(() => {
|
|
949
|
-
const debouncedTrigger = derivedFromDeferred(trigger, pipe(debounceTime(config.debounceMs),
|
|
950
|
-
distinctUntilChanged((a, b) => a?.key === b?.key)), { initialValue: undefined, injector: config.injector });
|
|
951
|
-
const resource = rxResource({
|
|
952
|
-
params: () => debouncedTrigger(),
|
|
953
|
-
stream: ({ params }) => config.buildStream(params.payload),
|
|
954
|
-
defaultValue: config.pendingValue,
|
|
955
|
-
injector: config.injector,
|
|
956
|
-
});
|
|
957
|
-
return withPreviousValue(resource);
|
|
958
|
-
});
|
|
959
|
-
handle = { trigger, resultResource, refetchVersion: 0 };
|
|
960
|
-
perFunctionSignalStore.set(contextKey, handle);
|
|
961
|
-
}
|
|
962
|
-
const resolved = config.resolve(ctx);
|
|
963
|
-
if (resolved.kind === 'returnEarly') {
|
|
964
|
-
handle.refetchVersion++;
|
|
965
|
-
return resolved.value;
|
|
691
|
+
readOperatorOrPunctuation() {
|
|
692
|
+
const start = this.position;
|
|
693
|
+
const char = this.expression[this.position];
|
|
694
|
+
const nextChar = this.position + 1 < this.expression.length ? this.expression[this.position + 1] : '';
|
|
695
|
+
const thirdChar = this.position + 2 < this.expression.length ? this.expression[this.position + 2] : '';
|
|
696
|
+
const threeChar = char + nextChar + thirdChar;
|
|
697
|
+
if (threeChar === '===') {
|
|
698
|
+
this.position += 3;
|
|
699
|
+
return { type: TokenType.EQUAL, value: '===', position: start };
|
|
966
700
|
}
|
|
967
|
-
if (
|
|
968
|
-
|
|
701
|
+
if (threeChar === '!==') {
|
|
702
|
+
this.position += 3;
|
|
703
|
+
return { type: TokenType.NOT_EQUAL, value: '!==', position: start };
|
|
969
704
|
}
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
}
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
705
|
+
const twoChar = char + nextChar;
|
|
706
|
+
switch (twoChar) {
|
|
707
|
+
case '==':
|
|
708
|
+
this.position += 2;
|
|
709
|
+
return { type: TokenType.EQUAL, value: '==', position: start };
|
|
710
|
+
case '!=':
|
|
711
|
+
this.position += 2;
|
|
712
|
+
return { type: TokenType.NOT_EQUAL, value: '!=', position: start };
|
|
713
|
+
case '>=':
|
|
714
|
+
this.position += 2;
|
|
715
|
+
return { type: TokenType.GREATER_EQUAL, value: '>=', position: start };
|
|
716
|
+
case '<=':
|
|
717
|
+
this.position += 2;
|
|
718
|
+
return { type: TokenType.LESS_EQUAL, value: '<=', position: start };
|
|
719
|
+
case '&&':
|
|
720
|
+
this.position += 2;
|
|
721
|
+
return { type: TokenType.AND, value: '&&', position: start };
|
|
722
|
+
case '||':
|
|
723
|
+
this.position += 2;
|
|
724
|
+
return { type: TokenType.OR, value: '||', position: start };
|
|
725
|
+
case '=>':
|
|
726
|
+
this.position += 2;
|
|
727
|
+
return { type: TokenType.ARROW, value: '=>', position: start };
|
|
728
|
+
case '?.':
|
|
729
|
+
this.position += 2;
|
|
730
|
+
return { type: TokenType.OPTIONAL_DOT, value: '?.', position: start };
|
|
731
|
+
}
|
|
732
|
+
this.position++;
|
|
733
|
+
switch (char) {
|
|
734
|
+
case '+':
|
|
735
|
+
return { type: TokenType.PLUS, value: '+', position: start };
|
|
736
|
+
case '-':
|
|
737
|
+
return { type: TokenType.MINUS, value: '-', position: start };
|
|
738
|
+
case '*':
|
|
739
|
+
return { type: TokenType.MULTIPLY, value: '*', position: start };
|
|
740
|
+
case '/':
|
|
741
|
+
return { type: TokenType.DIVIDE, value: '/', position: start };
|
|
742
|
+
case '%':
|
|
743
|
+
return { type: TokenType.MODULO, value: '%', position: start };
|
|
744
|
+
case '>':
|
|
745
|
+
return { type: TokenType.GREATER, value: '>', position: start };
|
|
746
|
+
case '<':
|
|
747
|
+
return { type: TokenType.LESS, value: '<', position: start };
|
|
748
|
+
case '!':
|
|
749
|
+
return { type: TokenType.NOT, value: '!', position: start };
|
|
750
|
+
case '.':
|
|
751
|
+
return { type: TokenType.DOT, value: '.', position: start };
|
|
752
|
+
case ',':
|
|
753
|
+
return { type: TokenType.COMMA, value: ',', position: start };
|
|
754
|
+
case ':':
|
|
755
|
+
return { type: TokenType.COLON, value: ':', position: start };
|
|
756
|
+
case '(':
|
|
757
|
+
return { type: TokenType.LPAREN, value: '(', position: start };
|
|
758
|
+
case ')':
|
|
759
|
+
return { type: TokenType.RPAREN, value: ')', position: start };
|
|
760
|
+
case '[':
|
|
761
|
+
return { type: TokenType.LBRACKET, value: '[', position: start };
|
|
762
|
+
case ']':
|
|
763
|
+
return { type: TokenType.RBRACKET, value: ']', position: start };
|
|
764
|
+
case '{':
|
|
765
|
+
return { type: TokenType.LBRACE, value: '{', position: start };
|
|
766
|
+
case '}':
|
|
767
|
+
return { type: TokenType.RBRACE, value: '}', position: start };
|
|
768
|
+
case '?':
|
|
769
|
+
return { type: TokenType.QUESTION, value: '?', position: start };
|
|
770
|
+
default:
|
|
771
|
+
throw new ExpressionParserError(`Unexpected character: ${char}`, start, this.expression);
|
|
998
772
|
}
|
|
999
|
-
}
|
|
1000
|
-
const fn = createDebouncedResourceLogicFn({
|
|
1001
|
-
pendingValue,
|
|
1002
|
-
debounceMs,
|
|
1003
|
-
injector,
|
|
1004
|
-
resolve: (ctx) => {
|
|
1005
|
-
const evaluationContext = fieldContextRegistry.createReactiveEvaluationContext(ctx, functionRegistry.getCustomFunctions());
|
|
1006
|
-
const key = stableStringify({
|
|
1007
|
-
formValue: evaluationContext.formValue,
|
|
1008
|
-
fieldValue: evaluationContext.fieldValue,
|
|
1009
|
-
externalData: evaluationContext.externalData,
|
|
1010
|
-
});
|
|
1011
|
-
return { kind: 'trigger', key, payload: ctx };
|
|
1012
|
-
},
|
|
1013
|
-
buildStream: (capturedCtx) => {
|
|
1014
|
-
const asyncFn = condition.asyncFn ??
|
|
1015
|
-
(condition.asyncFunctionName ? functionRegistry.getAsyncConditionFunction(condition.asyncFunctionName) : undefined);
|
|
1016
|
-
if (!asyncFn) {
|
|
1017
|
-
logger.warn(`Async Condition - function '${condition.asyncFunctionName}' not found. ` + `Register it in customFnConfig.asyncConditions.`);
|
|
1018
|
-
return of(pendingValue);
|
|
1019
|
-
}
|
|
1020
|
-
const evaluationContext = fieldContextRegistry.createReactiveEvaluationContext(capturedCtx, functionRegistry.getCustomFunctions());
|
|
1021
|
-
return from(asyncFn(evaluationContext)).pipe(map((result) => !!result), catchError((error) => {
|
|
1022
|
-
logger.warn('Async Condition - function failed:', error);
|
|
1023
|
-
return of(pendingValue);
|
|
1024
|
-
}));
|
|
1025
|
-
},
|
|
1026
|
-
});
|
|
1027
|
-
if (cacheKey !== undefined) {
|
|
1028
|
-
cacheService.asyncConditionFunctionCache.set(cacheKey, fn);
|
|
1029
|
-
}
|
|
1030
|
-
return fn;
|
|
1031
|
-
}
|
|
1032
|
-
|
|
1033
|
-
var TokenType;
|
|
1034
|
-
(function (TokenType) {
|
|
1035
|
-
TokenType["NUMBER"] = "NUMBER";
|
|
1036
|
-
TokenType["STRING"] = "STRING";
|
|
1037
|
-
TokenType["TRUE"] = "TRUE";
|
|
1038
|
-
TokenType["FALSE"] = "FALSE";
|
|
1039
|
-
TokenType["NULL"] = "NULL";
|
|
1040
|
-
TokenType["UNDEFINED"] = "UNDEFINED";
|
|
1041
|
-
TokenType["IDENTIFIER"] = "IDENTIFIER";
|
|
1042
|
-
TokenType["PLUS"] = "PLUS";
|
|
1043
|
-
TokenType["MINUS"] = "MINUS";
|
|
1044
|
-
TokenType["MULTIPLY"] = "MULTIPLY";
|
|
1045
|
-
TokenType["DIVIDE"] = "DIVIDE";
|
|
1046
|
-
TokenType["MODULO"] = "MODULO";
|
|
1047
|
-
TokenType["EQUAL"] = "EQUAL";
|
|
1048
|
-
TokenType["NOT_EQUAL"] = "NOT_EQUAL";
|
|
1049
|
-
TokenType["GREATER"] = "GREATER";
|
|
1050
|
-
TokenType["LESS"] = "LESS";
|
|
1051
|
-
TokenType["GREATER_EQUAL"] = "GREATER_EQUAL";
|
|
1052
|
-
TokenType["LESS_EQUAL"] = "LESS_EQUAL";
|
|
1053
|
-
TokenType["AND"] = "AND";
|
|
1054
|
-
TokenType["OR"] = "OR";
|
|
1055
|
-
TokenType["NOT"] = "NOT";
|
|
1056
|
-
TokenType["DOT"] = "DOT";
|
|
1057
|
-
TokenType["COMMA"] = "COMMA";
|
|
1058
|
-
TokenType["COLON"] = "COLON";
|
|
1059
|
-
TokenType["LPAREN"] = "LPAREN";
|
|
1060
|
-
TokenType["RPAREN"] = "RPAREN";
|
|
1061
|
-
TokenType["LBRACKET"] = "LBRACKET";
|
|
1062
|
-
TokenType["RBRACKET"] = "RBRACKET";
|
|
1063
|
-
TokenType["LBRACE"] = "LBRACE";
|
|
1064
|
-
TokenType["RBRACE"] = "RBRACE";
|
|
1065
|
-
TokenType["ARROW"] = "ARROW";
|
|
1066
|
-
TokenType["QUESTION"] = "QUESTION";
|
|
1067
|
-
TokenType["OPTIONAL_DOT"] = "OPTIONAL_DOT";
|
|
1068
|
-
TokenType["EOF"] = "EOF";
|
|
1069
|
-
})(TokenType || (TokenType = {}));
|
|
1070
|
-
class ExpressionParserError extends DynamicFormError {
|
|
1071
|
-
position;
|
|
1072
|
-
expression;
|
|
1073
|
-
constructor(message, position, expression) {
|
|
1074
|
-
super(`${message} at position ${position} in expression: ${expression}`);
|
|
1075
|
-
this.position = position;
|
|
1076
|
-
this.expression = expression;
|
|
1077
|
-
this.name = 'ExpressionParserError';
|
|
1078
773
|
}
|
|
1079
774
|
}
|
|
1080
775
|
|
|
1081
|
-
class
|
|
1082
|
-
|
|
776
|
+
class Parser {
|
|
777
|
+
tokens = [];
|
|
778
|
+
current = 0;
|
|
1083
779
|
expression;
|
|
1084
780
|
constructor(expression) {
|
|
1085
781
|
this.expression = expression;
|
|
1086
782
|
}
|
|
1087
|
-
|
|
1088
|
-
const
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
783
|
+
parse() {
|
|
784
|
+
const tokenizer = new Tokenizer(this.expression);
|
|
785
|
+
this.tokens = tokenizer.tokenize();
|
|
786
|
+
this.current = 0;
|
|
787
|
+
if (this.isAtEnd()) {
|
|
788
|
+
throw new ExpressionParserError('Empty expression', 0, this.expression);
|
|
789
|
+
}
|
|
790
|
+
const result = this.parseExpression();
|
|
791
|
+
if (!this.isAtEnd()) {
|
|
792
|
+
const token = this.peek();
|
|
793
|
+
throw new ExpressionParserError(`Unexpected token '${token.value}' at position ${token.position} — expression has trailing content`, token.position, this.expression);
|
|
794
|
+
}
|
|
795
|
+
return result;
|
|
796
|
+
}
|
|
797
|
+
parseExpression() {
|
|
798
|
+
const arrow = this.tryParseArrowFunction();
|
|
799
|
+
if (arrow)
|
|
800
|
+
return arrow;
|
|
801
|
+
return this.parseConditional();
|
|
802
|
+
}
|
|
803
|
+
parseConditional() {
|
|
804
|
+
const test = this.parseLogicalOr();
|
|
805
|
+
if (!this.match(TokenType.QUESTION))
|
|
806
|
+
return test;
|
|
807
|
+
const consequent = this.parseExpression();
|
|
808
|
+
this.consume(TokenType.COLON, 'Expected ":" between ternary branches');
|
|
809
|
+
const alternate = this.parseExpression();
|
|
810
|
+
return { type: 'Conditional', test, consequent, alternate };
|
|
811
|
+
}
|
|
812
|
+
tryParseArrowFunction() {
|
|
813
|
+
const savedPos = this.current;
|
|
814
|
+
if (this.check(TokenType.IDENTIFIER) && this.checkAt(this.current + 1, TokenType.ARROW)) {
|
|
815
|
+
const param = this.advance().value;
|
|
816
|
+
this.advance(); const body = this.parseExpression();
|
|
817
|
+
return { type: 'ArrowFunction', params: [param], body };
|
|
818
|
+
}
|
|
819
|
+
if (this.check(TokenType.LPAREN)) {
|
|
820
|
+
this.advance(); const params = [];
|
|
821
|
+
if (this.check(TokenType.RPAREN)) {
|
|
822
|
+
this.advance(); }
|
|
823
|
+
else if (this.check(TokenType.IDENTIFIER)) {
|
|
824
|
+
params.push(this.advance().value);
|
|
825
|
+
while (this.check(TokenType.COMMA)) {
|
|
826
|
+
this.advance(); if (!this.check(TokenType.IDENTIFIER)) {
|
|
827
|
+
this.current = savedPos;
|
|
828
|
+
return null;
|
|
829
|
+
}
|
|
830
|
+
params.push(this.advance().value);
|
|
831
|
+
}
|
|
832
|
+
if (!this.check(TokenType.RPAREN)) {
|
|
833
|
+
this.current = savedPos;
|
|
834
|
+
return null;
|
|
835
|
+
}
|
|
836
|
+
this.advance(); }
|
|
837
|
+
else {
|
|
838
|
+
this.current = savedPos;
|
|
839
|
+
return null;
|
|
1093
840
|
}
|
|
1094
|
-
|
|
1095
|
-
|
|
841
|
+
if (!this.check(TokenType.ARROW)) {
|
|
842
|
+
this.current = savedPos;
|
|
843
|
+
return null;
|
|
844
|
+
}
|
|
845
|
+
this.advance(); const body = this.parseExpression();
|
|
846
|
+
return { type: 'ArrowFunction', params, body };
|
|
1096
847
|
}
|
|
1097
|
-
|
|
1098
|
-
return tokens;
|
|
848
|
+
return null;
|
|
1099
849
|
}
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
850
|
+
checkAt(index, type) {
|
|
851
|
+
if (index < 0 || index >= this.tokens.length)
|
|
852
|
+
return false;
|
|
853
|
+
return this.tokens[index].type === type;
|
|
854
|
+
}
|
|
855
|
+
parseLogicalOr() {
|
|
856
|
+
let node = this.parseLogicalAnd();
|
|
857
|
+
while (this.match(TokenType.OR)) {
|
|
858
|
+
const operator = this.previous().value;
|
|
859
|
+
const right = this.parseLogicalAnd();
|
|
860
|
+
node = { type: 'BinaryOp', operator, left: node, right };
|
|
1103
861
|
}
|
|
862
|
+
return node;
|
|
1104
863
|
}
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
864
|
+
parseLogicalAnd() {
|
|
865
|
+
let node = this.parseEquality();
|
|
866
|
+
while (this.match(TokenType.AND)) {
|
|
867
|
+
const operator = this.previous().value;
|
|
868
|
+
const right = this.parseEquality();
|
|
869
|
+
node = { type: 'BinaryOp', operator, left: node, right };
|
|
1109
870
|
}
|
|
1110
|
-
|
|
1111
|
-
|
|
871
|
+
return node;
|
|
872
|
+
}
|
|
873
|
+
parseEquality() {
|
|
874
|
+
let node = this.parseComparison();
|
|
875
|
+
while (this.match(TokenType.EQUAL, TokenType.NOT_EQUAL)) {
|
|
876
|
+
const operator = this.previous().value;
|
|
877
|
+
const right = this.parseComparison();
|
|
878
|
+
node = { type: 'BinaryOp', operator, left: node, right };
|
|
1112
879
|
}
|
|
1113
|
-
|
|
1114
|
-
|
|
880
|
+
return node;
|
|
881
|
+
}
|
|
882
|
+
parseComparison() {
|
|
883
|
+
let node = this.parseAddition();
|
|
884
|
+
while (this.match(TokenType.GREATER, TokenType.GREATER_EQUAL, TokenType.LESS, TokenType.LESS_EQUAL)) {
|
|
885
|
+
const operator = this.previous().value;
|
|
886
|
+
const right = this.parseAddition();
|
|
887
|
+
node = { type: 'BinaryOp', operator, left: node, right };
|
|
1115
888
|
}
|
|
1116
|
-
|
|
889
|
+
return node;
|
|
1117
890
|
}
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
this.
|
|
1121
|
-
|
|
1122
|
-
const
|
|
1123
|
-
|
|
1124
|
-
|
|
891
|
+
parseAddition() {
|
|
892
|
+
let node = this.parseMultiplication();
|
|
893
|
+
while (this.match(TokenType.PLUS, TokenType.MINUS)) {
|
|
894
|
+
const operator = this.previous().value;
|
|
895
|
+
const right = this.parseMultiplication();
|
|
896
|
+
node = { type: 'BinaryOp', operator, left: node, right };
|
|
897
|
+
}
|
|
898
|
+
return node;
|
|
899
|
+
}
|
|
900
|
+
parseMultiplication() {
|
|
901
|
+
let node = this.parseUnary();
|
|
902
|
+
while (this.match(TokenType.MULTIPLY, TokenType.DIVIDE, TokenType.MODULO)) {
|
|
903
|
+
const operator = this.previous().value;
|
|
904
|
+
const right = this.parseUnary();
|
|
905
|
+
node = { type: 'BinaryOp', operator, left: node, right };
|
|
906
|
+
}
|
|
907
|
+
return node;
|
|
908
|
+
}
|
|
909
|
+
parseUnary() {
|
|
910
|
+
if (this.match(TokenType.NOT, TokenType.MINUS, TokenType.PLUS)) {
|
|
911
|
+
const operator = this.previous().value;
|
|
912
|
+
const operand = this.parseUnary();
|
|
913
|
+
return { type: 'UnaryOp', operator, operand };
|
|
914
|
+
}
|
|
915
|
+
return this.parsePostfix();
|
|
916
|
+
}
|
|
917
|
+
parsePostfix() {
|
|
918
|
+
let node = this.parsePrimary();
|
|
919
|
+
while (true) {
|
|
920
|
+
if (this.match(TokenType.DOT)) {
|
|
921
|
+
if (!this.check(TokenType.IDENTIFIER)) {
|
|
922
|
+
throw new ExpressionParserError('Expected property name after "."', this.peek().position, this.expression);
|
|
923
|
+
}
|
|
924
|
+
const property = this.advance().value;
|
|
925
|
+
node = { type: 'MemberAccess', object: node, property };
|
|
1125
926
|
}
|
|
1126
|
-
if (
|
|
1127
|
-
this.
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
value += '\n';
|
|
1132
|
-
break;
|
|
1133
|
-
case 't':
|
|
1134
|
-
value += '\t';
|
|
1135
|
-
break;
|
|
1136
|
-
case 'r':
|
|
1137
|
-
value += '\r';
|
|
1138
|
-
break;
|
|
1139
|
-
case '\\':
|
|
1140
|
-
value += '\\';
|
|
1141
|
-
break;
|
|
1142
|
-
case quote:
|
|
1143
|
-
value += quote;
|
|
1144
|
-
break;
|
|
1145
|
-
default:
|
|
1146
|
-
value += nextChar;
|
|
927
|
+
else if (this.match(TokenType.OPTIONAL_DOT)) {
|
|
928
|
+
if (this.match(TokenType.LBRACKET)) {
|
|
929
|
+
const property = this.parseExpression();
|
|
930
|
+
this.consume(TokenType.RBRACKET, 'Expected "]" after computed property');
|
|
931
|
+
node = { type: 'ComputedMemberAccess', object: node, property, optional: true };
|
|
1147
932
|
}
|
|
1148
|
-
this.
|
|
933
|
+
else if (this.match(TokenType.LPAREN)) {
|
|
934
|
+
const args = this.parseArgumentList();
|
|
935
|
+
this.consume(TokenType.RPAREN, 'Expected ")" after arguments');
|
|
936
|
+
node = { type: 'CallExpression', callee: node, arguments: args, optional: true };
|
|
937
|
+
}
|
|
938
|
+
else if (this.check(TokenType.IDENTIFIER)) {
|
|
939
|
+
const property = this.advance().value;
|
|
940
|
+
node = { type: 'MemberAccess', object: node, property, optional: true };
|
|
941
|
+
}
|
|
942
|
+
else {
|
|
943
|
+
throw new ExpressionParserError('Expected property name, "[", or "(" after "?."', this.peek().position, this.expression);
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
else if (this.match(TokenType.LBRACKET)) {
|
|
947
|
+
const property = this.parseExpression();
|
|
948
|
+
this.consume(TokenType.RBRACKET, 'Expected "]" after computed property');
|
|
949
|
+
node = { type: 'ComputedMemberAccess', object: node, property };
|
|
950
|
+
}
|
|
951
|
+
else if (this.match(TokenType.LPAREN)) {
|
|
952
|
+
const args = this.parseArgumentList();
|
|
953
|
+
this.consume(TokenType.RPAREN, 'Expected ")" after arguments');
|
|
954
|
+
node = { type: 'CallExpression', callee: node, arguments: args };
|
|
1149
955
|
}
|
|
1150
956
|
else {
|
|
1151
|
-
|
|
1152
|
-
this.position++;
|
|
957
|
+
break;
|
|
1153
958
|
}
|
|
1154
959
|
}
|
|
1155
|
-
|
|
1156
|
-
}
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
960
|
+
return node;
|
|
961
|
+
}
|
|
962
|
+
parsePrimary() {
|
|
963
|
+
if (this.match(TokenType.TRUE)) {
|
|
964
|
+
return { type: 'Literal', value: true };
|
|
965
|
+
}
|
|
966
|
+
if (this.match(TokenType.FALSE)) {
|
|
967
|
+
return { type: 'Literal', value: false };
|
|
968
|
+
}
|
|
969
|
+
if (this.match(TokenType.NULL)) {
|
|
970
|
+
return { type: 'Literal', value: null };
|
|
1163
971
|
}
|
|
1164
|
-
if (
|
|
1165
|
-
|
|
972
|
+
if (this.match(TokenType.UNDEFINED)) {
|
|
973
|
+
return { type: 'Literal', value: undefined };
|
|
1166
974
|
}
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
readIdentifierOrKeyword() {
|
|
1170
|
-
const start = this.position;
|
|
1171
|
-
let value = '';
|
|
1172
|
-
while (this.position < this.expression.length && /[a-zA-Z0-9_$]/.test(this.expression[this.position])) {
|
|
1173
|
-
value += this.expression[this.position];
|
|
1174
|
-
this.position++;
|
|
975
|
+
if (this.match(TokenType.NUMBER)) {
|
|
976
|
+
return { type: 'Literal', value: parseFloat(this.previous().value) };
|
|
1175
977
|
}
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
978
|
+
if (this.match(TokenType.STRING)) {
|
|
979
|
+
return { type: 'Literal', value: this.previous().value };
|
|
980
|
+
}
|
|
981
|
+
if (this.match(TokenType.LBRACKET)) {
|
|
982
|
+
const elements = this.parseArrayElements();
|
|
983
|
+
this.consume(TokenType.RBRACKET, 'Expected "]" after array elements');
|
|
984
|
+
return { type: 'ArrayLiteral', elements };
|
|
985
|
+
}
|
|
986
|
+
if (this.match(TokenType.LBRACE)) {
|
|
987
|
+
const properties = this.parseObjectProperties();
|
|
988
|
+
this.consume(TokenType.RBRACE, 'Expected "}" after object properties');
|
|
989
|
+
return { type: 'ObjectLiteral', properties };
|
|
990
|
+
}
|
|
991
|
+
if (this.match(TokenType.IDENTIFIER)) {
|
|
992
|
+
return { type: 'Identifier', name: this.previous().value };
|
|
993
|
+
}
|
|
994
|
+
if (this.match(TokenType.LPAREN)) {
|
|
995
|
+
const expr = this.parseExpression();
|
|
996
|
+
this.consume(TokenType.RPAREN, 'Expected ")" after expression');
|
|
997
|
+
return expr;
|
|
1191
998
|
}
|
|
999
|
+
throw new ExpressionParserError(`Unexpected token: ${this.peek().value}`, this.peek().position, this.expression);
|
|
1192
1000
|
}
|
|
1193
|
-
|
|
1194
|
-
const
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
if (threeChar === '===') {
|
|
1200
|
-
this.position += 3;
|
|
1201
|
-
return { type: TokenType.EQUAL, value: '===', position: start };
|
|
1001
|
+
parseArgumentList() {
|
|
1002
|
+
const args = [];
|
|
1003
|
+
if (!this.check(TokenType.RPAREN)) {
|
|
1004
|
+
do {
|
|
1005
|
+
args.push(this.parseExpression());
|
|
1006
|
+
} while (this.match(TokenType.COMMA));
|
|
1202
1007
|
}
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1008
|
+
return args;
|
|
1009
|
+
}
|
|
1010
|
+
parseArrayElements() {
|
|
1011
|
+
const elements = [];
|
|
1012
|
+
if (!this.check(TokenType.RBRACKET)) {
|
|
1013
|
+
do {
|
|
1014
|
+
elements.push(this.parseExpression());
|
|
1015
|
+
} while (this.match(TokenType.COMMA));
|
|
1206
1016
|
}
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
this.position += 2;
|
|
1214
|
-
return { type: TokenType.NOT_EQUAL, value: '!=', position: start };
|
|
1215
|
-
case '>=':
|
|
1216
|
-
this.position += 2;
|
|
1217
|
-
return { type: TokenType.GREATER_EQUAL, value: '>=', position: start };
|
|
1218
|
-
case '<=':
|
|
1219
|
-
this.position += 2;
|
|
1220
|
-
return { type: TokenType.LESS_EQUAL, value: '<=', position: start };
|
|
1221
|
-
case '&&':
|
|
1222
|
-
this.position += 2;
|
|
1223
|
-
return { type: TokenType.AND, value: '&&', position: start };
|
|
1224
|
-
case '||':
|
|
1225
|
-
this.position += 2;
|
|
1226
|
-
return { type: TokenType.OR, value: '||', position: start };
|
|
1227
|
-
case '=>':
|
|
1228
|
-
this.position += 2;
|
|
1229
|
-
return { type: TokenType.ARROW, value: '=>', position: start };
|
|
1230
|
-
case '?.':
|
|
1231
|
-
this.position += 2;
|
|
1232
|
-
return { type: TokenType.OPTIONAL_DOT, value: '?.', position: start };
|
|
1017
|
+
return elements;
|
|
1018
|
+
}
|
|
1019
|
+
parseObjectProperties() {
|
|
1020
|
+
const properties = [];
|
|
1021
|
+
if (this.check(TokenType.RBRACE)) {
|
|
1022
|
+
return properties;
|
|
1233
1023
|
}
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
case ':':
|
|
1257
|
-
return { type: TokenType.COLON, value: ':', position: start };
|
|
1258
|
-
case '(':
|
|
1259
|
-
return { type: TokenType.LPAREN, value: '(', position: start };
|
|
1260
|
-
case ')':
|
|
1261
|
-
return { type: TokenType.RPAREN, value: ')', position: start };
|
|
1262
|
-
case '[':
|
|
1263
|
-
return { type: TokenType.LBRACKET, value: '[', position: start };
|
|
1264
|
-
case ']':
|
|
1265
|
-
return { type: TokenType.RBRACKET, value: ']', position: start };
|
|
1266
|
-
case '{':
|
|
1267
|
-
return { type: TokenType.LBRACE, value: '{', position: start };
|
|
1268
|
-
case '}':
|
|
1269
|
-
return { type: TokenType.RBRACE, value: '}', position: start };
|
|
1270
|
-
case '?':
|
|
1271
|
-
return { type: TokenType.QUESTION, value: '?', position: start };
|
|
1272
|
-
default:
|
|
1273
|
-
throw new ExpressionParserError(`Unexpected character: ${char}`, start, this.expression);
|
|
1024
|
+
do {
|
|
1025
|
+
if (this.check(TokenType.RBRACE))
|
|
1026
|
+
break;
|
|
1027
|
+
let key;
|
|
1028
|
+
if (this.check(TokenType.IDENTIFIER) || this.check(TokenType.STRING)) {
|
|
1029
|
+
key = this.advance().value;
|
|
1030
|
+
}
|
|
1031
|
+
else {
|
|
1032
|
+
throw new ExpressionParserError(`Expected property name (identifier or string) in object literal, got '${this.peek().value}'`, this.peek().position, this.expression);
|
|
1033
|
+
}
|
|
1034
|
+
this.consume(TokenType.COLON, 'Expected ":" after property name in object literal');
|
|
1035
|
+
const value = this.parseExpression();
|
|
1036
|
+
properties.push({ key, value });
|
|
1037
|
+
} while (this.match(TokenType.COMMA));
|
|
1038
|
+
return properties;
|
|
1039
|
+
}
|
|
1040
|
+
match(...types) {
|
|
1041
|
+
for (const type of types) {
|
|
1042
|
+
if (this.check(type)) {
|
|
1043
|
+
this.advance();
|
|
1044
|
+
return true;
|
|
1045
|
+
}
|
|
1274
1046
|
}
|
|
1047
|
+
return false;
|
|
1048
|
+
}
|
|
1049
|
+
check(type) {
|
|
1050
|
+
if (this.isAtEnd())
|
|
1051
|
+
return false;
|
|
1052
|
+
return this.peek().type === type;
|
|
1053
|
+
}
|
|
1054
|
+
advance() {
|
|
1055
|
+
if (!this.isAtEnd())
|
|
1056
|
+
this.current++;
|
|
1057
|
+
return this.previous();
|
|
1058
|
+
}
|
|
1059
|
+
isAtEnd() {
|
|
1060
|
+
return this.peek().type === TokenType.EOF;
|
|
1061
|
+
}
|
|
1062
|
+
peek() {
|
|
1063
|
+
return this.tokens[this.current];
|
|
1064
|
+
}
|
|
1065
|
+
previous() {
|
|
1066
|
+
return this.tokens[this.current - 1];
|
|
1067
|
+
}
|
|
1068
|
+
consume(type, message) {
|
|
1069
|
+
if (this.check(type))
|
|
1070
|
+
return this.advance();
|
|
1071
|
+
throw new ExpressionParserError(message, this.peek().position, this.expression);
|
|
1275
1072
|
}
|
|
1276
1073
|
}
|
|
1277
1074
|
|
|
1278
|
-
|
|
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
|
-
let node = this.parseUnary();
|
|
1404
|
-
while (this.match(TokenType.MULTIPLY, TokenType.DIVIDE, TokenType.MODULO)) {
|
|
1405
|
-
const operator = this.previous().value;
|
|
1406
|
-
const right = this.parseUnary();
|
|
1407
|
-
node = { type: 'BinaryOp', operator, left: node, right };
|
|
1240
|
+
if (node.operator === '||') {
|
|
1241
|
+
const left = this.evaluate(node.left);
|
|
1242
|
+
return left ? left : this.evaluate(node.right);
|
|
1408
1243
|
}
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1244
|
+
const left = this.evaluate(node.left);
|
|
1245
|
+
const right = this.evaluate(node.right);
|
|
1246
|
+
switch (node.operator) {
|
|
1247
|
+
case '+':
|
|
1248
|
+
return left + right;
|
|
1249
|
+
case '-':
|
|
1250
|
+
return left - right;
|
|
1251
|
+
case '*':
|
|
1252
|
+
return left * right;
|
|
1253
|
+
case '/':
|
|
1254
|
+
if (right === 0)
|
|
1255
|
+
return null;
|
|
1256
|
+
return left / right;
|
|
1257
|
+
case '%':
|
|
1258
|
+
if (right === 0)
|
|
1259
|
+
return null;
|
|
1260
|
+
return left % right;
|
|
1261
|
+
case '==':
|
|
1262
|
+
return left == right;
|
|
1263
|
+
case '===':
|
|
1264
|
+
return left === right;
|
|
1265
|
+
case '!=':
|
|
1266
|
+
return left != right;
|
|
1267
|
+
case '!==':
|
|
1268
|
+
return left !== right;
|
|
1269
|
+
case '>':
|
|
1270
|
+
return left > right;
|
|
1271
|
+
case '<':
|
|
1272
|
+
return left < right;
|
|
1273
|
+
case '>=':
|
|
1274
|
+
return left >= right;
|
|
1275
|
+
case '<=':
|
|
1276
|
+
return left <= right;
|
|
1277
|
+
default:
|
|
1278
|
+
throw new ExpressionParserError(`Unknown binary operator: ${node.operator}`, 0, this.expression);
|
|
1416
1279
|
}
|
|
1417
|
-
return this.parsePostfix();
|
|
1418
1280
|
}
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
if (this.match(TokenType.LBRACKET)) {
|
|
1431
|
-
const property = this.parseExpression();
|
|
1432
|
-
this.consume(TokenType.RBRACKET, 'Expected "]" after computed property');
|
|
1433
|
-
node = { type: 'ComputedMemberAccess', object: node, property, optional: true };
|
|
1434
|
-
}
|
|
1435
|
-
else if (this.match(TokenType.LPAREN)) {
|
|
1436
|
-
const args = this.parseArgumentList();
|
|
1437
|
-
this.consume(TokenType.RPAREN, 'Expected ")" after arguments');
|
|
1438
|
-
node = { type: 'CallExpression', callee: node, arguments: args, optional: true };
|
|
1439
|
-
}
|
|
1440
|
-
else if (this.check(TokenType.IDENTIFIER)) {
|
|
1441
|
-
const property = this.advance().value;
|
|
1442
|
-
node = { type: 'MemberAccess', object: node, property, optional: true };
|
|
1443
|
-
}
|
|
1444
|
-
else {
|
|
1445
|
-
throw new ExpressionParserError('Expected property name, "[", or "(" after "?."', this.peek().position, this.expression);
|
|
1446
|
-
}
|
|
1447
|
-
}
|
|
1448
|
-
else if (this.match(TokenType.LBRACKET)) {
|
|
1449
|
-
const property = this.parseExpression();
|
|
1450
|
-
this.consume(TokenType.RBRACKET, 'Expected "]" after computed property');
|
|
1451
|
-
node = { type: 'ComputedMemberAccess', object: node, property };
|
|
1452
|
-
}
|
|
1453
|
-
else if (this.match(TokenType.LPAREN)) {
|
|
1454
|
-
const args = this.parseArgumentList();
|
|
1455
|
-
this.consume(TokenType.RPAREN, 'Expected ")" after arguments');
|
|
1456
|
-
node = { type: 'CallExpression', callee: node, arguments: args };
|
|
1457
|
-
}
|
|
1458
|
-
else {
|
|
1459
|
-
break;
|
|
1460
|
-
}
|
|
1281
|
+
evaluateUnaryOp(node) {
|
|
1282
|
+
const operand = this.evaluate(node.operand);
|
|
1283
|
+
switch (node.operator) {
|
|
1284
|
+
case '!':
|
|
1285
|
+
return !operand;
|
|
1286
|
+
case '-':
|
|
1287
|
+
return -operand;
|
|
1288
|
+
case '+':
|
|
1289
|
+
return +operand;
|
|
1290
|
+
default:
|
|
1291
|
+
throw new ExpressionParserError(`Unknown unary operator: ${node.operator}`, 0, this.expression);
|
|
1461
1292
|
}
|
|
1462
|
-
return node;
|
|
1463
1293
|
}
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
}
|
|
1468
|
-
if (this.match(TokenType.FALSE)) {
|
|
1469
|
-
return { type: 'Literal', value: false };
|
|
1294
|
+
evaluateCallExpression(node) {
|
|
1295
|
+
if (node.callee.type !== 'MemberAccess') {
|
|
1296
|
+
throw new ExpressionParserError('Only method calls are allowed, not arbitrary function calls', 0, this.expression);
|
|
1470
1297
|
}
|
|
1471
|
-
|
|
1472
|
-
|
|
1298
|
+
const obj = this.evaluate(node.callee.object);
|
|
1299
|
+
if (obj === null || obj === undefined) {
|
|
1300
|
+
return undefined;
|
|
1473
1301
|
}
|
|
1474
|
-
|
|
1475
|
-
|
|
1302
|
+
const methodName = node.callee.property;
|
|
1303
|
+
if (BLOCKED_PROPERTIES.has(methodName)) {
|
|
1304
|
+
throw new ExpressionParserError(`Property "${methodName}" is not accessible for security reasons`, 0, this.expression);
|
|
1476
1305
|
}
|
|
1477
|
-
|
|
1478
|
-
|
|
1306
|
+
if (!this.isMethodSafe(obj, methodName)) {
|
|
1307
|
+
throw new ExpressionParserError(`Method "${methodName}" is not allowed for security reasons`, 0, this.expression);
|
|
1479
1308
|
}
|
|
1480
|
-
|
|
1481
|
-
|
|
1309
|
+
const args = node.arguments.map((arg) => this.evaluate(arg));
|
|
1310
|
+
const method = obj[methodName];
|
|
1311
|
+
if (typeof method === 'function') {
|
|
1312
|
+
return method.call(obj, ...args);
|
|
1482
1313
|
}
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1314
|
+
return undefined;
|
|
1315
|
+
}
|
|
1316
|
+
evaluateArrayLiteral(node) {
|
|
1317
|
+
return node.elements.map((element) => this.evaluate(element));
|
|
1318
|
+
}
|
|
1319
|
+
evaluateObjectLiteral(node) {
|
|
1320
|
+
const result = {};
|
|
1321
|
+
for (const { key, value } of node.properties) {
|
|
1322
|
+
if (BLOCKED_PROPERTIES.has(key)) {
|
|
1323
|
+
throw new ExpressionParserError(`Property "${key}" is not allowed in object literals for security reasons`, 0, this.expression);
|
|
1324
|
+
}
|
|
1325
|
+
result[key] = this.evaluate(value);
|
|
1487
1326
|
}
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1327
|
+
return result;
|
|
1328
|
+
}
|
|
1329
|
+
evaluateArrowFunction(node) {
|
|
1330
|
+
const parentScope = this.scope;
|
|
1331
|
+
const expression = this.expression;
|
|
1332
|
+
return (...args) => {
|
|
1333
|
+
const childScope = { ...parentScope };
|
|
1334
|
+
for (let i = 0; i < node.params.length; i++) {
|
|
1335
|
+
childScope[node.params[i]] = args[i];
|
|
1336
|
+
}
|
|
1337
|
+
const childEvaluator = new Evaluator(childScope, expression);
|
|
1338
|
+
return childEvaluator.evaluate(node.body);
|
|
1339
|
+
};
|
|
1340
|
+
}
|
|
1341
|
+
isMethodSafe(obj, methodName) {
|
|
1342
|
+
if (obj === null || obj === undefined) {
|
|
1343
|
+
return false;
|
|
1492
1344
|
}
|
|
1493
|
-
|
|
1494
|
-
|
|
1345
|
+
const type = Array.isArray(obj) ? 'array' : obj instanceof Date ? 'date' : typeof obj;
|
|
1346
|
+
const safeMethods = SAFE_METHODS[type];
|
|
1347
|
+
if (!safeMethods) {
|
|
1348
|
+
return false;
|
|
1495
1349
|
}
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1350
|
+
return safeMethods.has(methodName);
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
const MAX_AST_CACHE_SIZE = 1000;
|
|
1355
|
+
class LRUCache {
|
|
1356
|
+
cache = new Map();
|
|
1357
|
+
maxSize;
|
|
1358
|
+
constructor(maxSize) {
|
|
1359
|
+
this.maxSize = maxSize;
|
|
1360
|
+
}
|
|
1361
|
+
get(key) {
|
|
1362
|
+
const value = this.cache.get(key);
|
|
1363
|
+
if (value !== undefined) {
|
|
1364
|
+
this.cache.delete(key);
|
|
1365
|
+
this.cache.set(key, value);
|
|
1500
1366
|
}
|
|
1501
|
-
|
|
1367
|
+
return value;
|
|
1502
1368
|
}
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1369
|
+
set(key, value) {
|
|
1370
|
+
if (this.cache.has(key)) {
|
|
1371
|
+
this.cache.delete(key);
|
|
1372
|
+
}
|
|
1373
|
+
this.cache.set(key, value);
|
|
1374
|
+
if (this.cache.size > this.maxSize) {
|
|
1375
|
+
const firstKey = this.cache.keys().next().value;
|
|
1376
|
+
if (firstKey !== undefined) {
|
|
1377
|
+
this.cache.delete(firstKey);
|
|
1378
|
+
}
|
|
1509
1379
|
}
|
|
1510
|
-
return args;
|
|
1511
1380
|
}
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1381
|
+
clear() {
|
|
1382
|
+
this.cache.clear();
|
|
1383
|
+
}
|
|
1384
|
+
get size() {
|
|
1385
|
+
return this.cache.size;
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
const astCache = new LRUCache(MAX_AST_CACHE_SIZE);
|
|
1389
|
+
class ExpressionParser {
|
|
1390
|
+
static parse(expression) {
|
|
1391
|
+
const cached = astCache.get(expression);
|
|
1392
|
+
if (cached) {
|
|
1393
|
+
return cached;
|
|
1518
1394
|
}
|
|
1519
|
-
|
|
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);
|
|
1697
|
+
createReactiveEvaluationContext(fieldContext, customFunctions) {
|
|
1698
|
+
const fieldValue = fieldContext.value();
|
|
1699
|
+
const rootFormValue = this.rootFormRegistry.formValue();
|
|
1700
|
+
const pathKeys = safeReadPathKeys(fieldContext);
|
|
1701
|
+
const arrayScope = detectArrayScope(pathKeys);
|
|
1702
|
+
if (arrayScope) {
|
|
1703
|
+
return this.buildArrayScopedContext(rootFormValue, arrayScope, fieldValue, customFunctions, true, fieldContext);
|
|
1745
1704
|
}
|
|
1746
|
-
|
|
1747
|
-
const
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
return left != right;
|
|
1769
|
-
case '!==':
|
|
1770
|
-
return left !== right;
|
|
1771
|
-
case '>':
|
|
1772
|
-
return left > right;
|
|
1773
|
-
case '<':
|
|
1774
|
-
return left < right;
|
|
1775
|
-
case '>=':
|
|
1776
|
-
return left >= right;
|
|
1777
|
-
case '<=':
|
|
1778
|
-
return left <= right;
|
|
1779
|
-
default:
|
|
1780
|
-
throw new ExpressionParserError(`Unknown binary operator: ${node.operator}`, 0, this.expression);
|
|
1705
|
+
const localKey = this.extractFieldPath(fieldContext);
|
|
1706
|
+
const rootFormSignal = this.rootFormRegistry.rootForm;
|
|
1707
|
+
return {
|
|
1708
|
+
fieldValue,
|
|
1709
|
+
formValue: rootFormValue,
|
|
1710
|
+
fieldPath: localKey,
|
|
1711
|
+
customFunctions: customFunctions || {},
|
|
1712
|
+
externalData: this.resolveExternalData(true),
|
|
1713
|
+
logger: this.logger,
|
|
1714
|
+
deprecationTracker: this.deprecationTracker ?? undefined,
|
|
1715
|
+
get fieldState() {
|
|
1716
|
+
return readFieldStateInfo(extractFieldState(fieldContext), true);
|
|
1717
|
+
},
|
|
1718
|
+
get formFieldState() {
|
|
1719
|
+
return createFormFieldStateMap(rootFormSignal(), true);
|
|
1720
|
+
},
|
|
1721
|
+
};
|
|
1722
|
+
}
|
|
1723
|
+
createDisplayOnlyContext(fieldPath, customFunctions, arrayScope) {
|
|
1724
|
+
const rootFormValue = this.rootFormRegistry.formValue();
|
|
1725
|
+
if (arrayScope) {
|
|
1726
|
+
return this.buildArrayScopedContext(rootFormValue, arrayScope, undefined, customFunctions, true);
|
|
1781
1727
|
}
|
|
1728
|
+
const rootFormSignal = this.rootFormRegistry.rootForm;
|
|
1729
|
+
return {
|
|
1730
|
+
fieldValue: undefined,
|
|
1731
|
+
formValue: rootFormValue,
|
|
1732
|
+
fieldPath,
|
|
1733
|
+
customFunctions: customFunctions || {},
|
|
1734
|
+
externalData: this.resolveExternalData(true),
|
|
1735
|
+
logger: this.logger,
|
|
1736
|
+
deprecationTracker: this.deprecationTracker ?? undefined,
|
|
1737
|
+
get formFieldState() {
|
|
1738
|
+
return createFormFieldStateMap(rootFormSignal(), true);
|
|
1739
|
+
},
|
|
1740
|
+
};
|
|
1741
|
+
}
|
|
1742
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: FieldContextRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1743
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: FieldContextRegistryService });
|
|
1744
|
+
}
|
|
1745
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: FieldContextRegistryService, decorators: [{
|
|
1746
|
+
type: Injectable
|
|
1747
|
+
}] });
|
|
1748
|
+
|
|
1749
|
+
class FunctionRegistryService {
|
|
1750
|
+
customFunctions = new Map();
|
|
1751
|
+
customFunctionScopes = new Map();
|
|
1752
|
+
derivationFunctions = new Map();
|
|
1753
|
+
asyncDerivationFunctions = new Map();
|
|
1754
|
+
asyncConditionFunctions = new Map();
|
|
1755
|
+
validators = new Map();
|
|
1756
|
+
asyncValidators = new Map();
|
|
1757
|
+
httpValidators = new Map();
|
|
1758
|
+
registerCustomFunction(name, fn, options) {
|
|
1759
|
+
this.customFunctions.set(name, fn);
|
|
1760
|
+
this.customFunctionScopes.set(name, options?.scope ?? 'form');
|
|
1761
|
+
}
|
|
1762
|
+
getCustomFunctionScope(name) {
|
|
1763
|
+
return this.customFunctionScopes.get(name);
|
|
1764
|
+
}
|
|
1765
|
+
isFieldScopedFunction(name) {
|
|
1766
|
+
return this.customFunctionScopes.get(name) === 'field';
|
|
1767
|
+
}
|
|
1768
|
+
getCustomFunctions() {
|
|
1769
|
+
return Object.fromEntries(this.customFunctions);
|
|
1770
|
+
}
|
|
1771
|
+
clearCustomFunctions() {
|
|
1772
|
+
this.customFunctions.clear();
|
|
1773
|
+
this.customFunctionScopes.clear();
|
|
1774
|
+
}
|
|
1775
|
+
registerDerivationFunction(name, fn) {
|
|
1776
|
+
this.derivationFunctions.set(name, fn);
|
|
1777
|
+
}
|
|
1778
|
+
getDerivationFunction(name) {
|
|
1779
|
+
return this.derivationFunctions.get(name);
|
|
1782
1780
|
}
|
|
1783
|
-
|
|
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
|
-
}
|
|
1781
|
+
getDerivationFunctions() {
|
|
1782
|
+
return Object.fromEntries(this.derivationFunctions);
|
|
1795
1783
|
}
|
|
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;
|
|
1784
|
+
setDerivationFunctions(derivations) {
|
|
1785
|
+
this.setRegistryIfChanged(this.derivationFunctions, derivations);
|
|
1817
1786
|
}
|
|
1818
|
-
|
|
1819
|
-
|
|
1787
|
+
clearDerivationFunctions() {
|
|
1788
|
+
this.derivationFunctions.clear();
|
|
1820
1789
|
}
|
|
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;
|
|
1790
|
+
registerAsyncDerivationFunction(name, fn) {
|
|
1791
|
+
this.asyncDerivationFunctions.set(name, fn);
|
|
1830
1792
|
}
|
|
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
|
-
};
|
|
1793
|
+
getAsyncDerivationFunction(name) {
|
|
1794
|
+
return this.asyncDerivationFunctions.get(name);
|
|
1842
1795
|
}
|
|
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);
|
|
1796
|
+
getAsyncDerivationFunctions() {
|
|
1797
|
+
return Object.fromEntries(this.asyncDerivationFunctions);
|
|
1853
1798
|
}
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
const MAX_AST_CACHE_SIZE = 1000;
|
|
1857
|
-
class LRUCache {
|
|
1858
|
-
cache = new Map();
|
|
1859
|
-
maxSize;
|
|
1860
|
-
constructor(maxSize) {
|
|
1861
|
-
this.maxSize = maxSize;
|
|
1799
|
+
setAsyncDerivationFunctions(fns) {
|
|
1800
|
+
this.setRegistryIfChanged(this.asyncDerivationFunctions, fns);
|
|
1862
1801
|
}
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
if (value !== undefined) {
|
|
1866
|
-
this.cache.delete(key);
|
|
1867
|
-
this.cache.set(key, value);
|
|
1868
|
-
}
|
|
1869
|
-
return value;
|
|
1802
|
+
clearAsyncDerivationFunctions() {
|
|
1803
|
+
this.asyncDerivationFunctions.clear();
|
|
1870
1804
|
}
|
|
1871
|
-
|
|
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
|
-
}
|
|
1805
|
+
registerAsyncConditionFunction(name, fn) {
|
|
1806
|
+
this.asyncConditionFunctions.set(name, fn);
|
|
1882
1807
|
}
|
|
1883
|
-
|
|
1884
|
-
this.
|
|
1808
|
+
getAsyncConditionFunction(name) {
|
|
1809
|
+
return this.asyncConditionFunctions.get(name);
|
|
1885
1810
|
}
|
|
1886
|
-
|
|
1887
|
-
return this.
|
|
1811
|
+
getAsyncConditionFunctions() {
|
|
1812
|
+
return Object.fromEntries(this.asyncConditionFunctions);
|
|
1888
1813
|
}
|
|
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;
|
|
1814
|
+
setAsyncConditionFunctions(fns) {
|
|
1815
|
+
this.setRegistryIfChanged(this.asyncConditionFunctions, fns);
|
|
1901
1816
|
}
|
|
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
|
-
}
|
|
1817
|
+
clearAsyncConditionFunctions() {
|
|
1818
|
+
this.asyncConditionFunctions.clear();
|
|
1914
1819
|
}
|
|
1915
|
-
|
|
1916
|
-
|
|
1820
|
+
registerValidator(name, fn) {
|
|
1821
|
+
this.validators.set(name, fn);
|
|
1917
1822
|
}
|
|
1918
|
-
|
|
1919
|
-
return
|
|
1920
|
-
size: astCache.size,
|
|
1921
|
-
maxSize: MAX_AST_CACHE_SIZE,
|
|
1922
|
-
};
|
|
1823
|
+
getValidator(name) {
|
|
1824
|
+
return this.validators.get(name);
|
|
1923
1825
|
}
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
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
|
-
if (isThenableOrObservable(result)) {
|
|
1953
|
-
context.logger.warn(`Custom condition '${registeredName ?? '<inline>'}' returned a Promise or Observable. ` +
|
|
1954
|
-
`Synchronous conditions must return a value; use an 'async' or 'http' condition type for asynchronous logic. Treating as false.`);
|
|
1955
|
-
return false;
|
|
1956
|
-
}
|
|
1957
|
-
return !!result;
|
|
1958
|
-
}
|
|
1959
|
-
catch (error) {
|
|
1960
|
-
context.logger.error('Error executing custom function:', registeredName ?? '<inline>', error);
|
|
1961
|
-
return false;
|
|
1962
|
-
}
|
|
1826
|
+
clearValidators() {
|
|
1827
|
+
this.validators.clear();
|
|
1828
|
+
}
|
|
1829
|
+
registerAsyncValidator(name, fn) {
|
|
1830
|
+
this.asyncValidators.set(name, fn);
|
|
1831
|
+
}
|
|
1832
|
+
getAsyncValidator(name) {
|
|
1833
|
+
return this.asyncValidators.get(name);
|
|
1834
|
+
}
|
|
1835
|
+
clearAsyncValidators() {
|
|
1836
|
+
this.asyncValidators.clear();
|
|
1837
|
+
}
|
|
1838
|
+
registerHttpValidator(name, config) {
|
|
1839
|
+
this.httpValidators.set(name, config);
|
|
1840
|
+
}
|
|
1841
|
+
getHttpValidator(name) {
|
|
1842
|
+
return this.httpValidators.get(name);
|
|
1843
|
+
}
|
|
1844
|
+
clearHttpValidators() {
|
|
1845
|
+
this.httpValidators.clear();
|
|
1846
|
+
}
|
|
1847
|
+
setRegistryIfChanged(registry, values) {
|
|
1848
|
+
if (!values)
|
|
1849
|
+
return;
|
|
1850
|
+
const entries = Object.entries(values);
|
|
1851
|
+
const hasChanges = entries.some(([name, value]) => registry.get(name) !== value);
|
|
1852
|
+
if (hasChanges) {
|
|
1853
|
+
entries.forEach(([name, value]) => registry.set(name, value));
|
|
1963
1854
|
}
|
|
1964
|
-
case 'and':
|
|
1965
|
-
return evaluateAndCondition(expression, context);
|
|
1966
|
-
case 'or':
|
|
1967
|
-
return evaluateOrCondition(expression, context);
|
|
1968
|
-
case 'http':
|
|
1969
|
-
context.logger.warn('HTTP conditions are resolved asynchronously via createHttpConditionLogicFunction(). ' +
|
|
1970
|
-
'When used inside and/or composites, the HTTP result is not available synchronously. Returning false.');
|
|
1971
|
-
return false;
|
|
1972
|
-
case 'async':
|
|
1973
|
-
context.logger.warn('Async Condition - resolved asynchronously via createAsyncConditionLogicFunction(). ' +
|
|
1974
|
-
'When used inside and/or composites, the async result is not available synchronously. Returning false.');
|
|
1975
|
-
return false;
|
|
1976
|
-
default:
|
|
1977
|
-
return false;
|
|
1978
1855
|
}
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
? getNestedValue(context.formValue, expression.fieldPath)
|
|
1985
|
-
: context.rootFormValue
|
|
1986
|
-
? getNestedValue(context.rootFormValue, expression.fieldPath)
|
|
1987
|
-
: undefined;
|
|
1988
|
-
return compareValues(fieldValue, expression.value, expression.operator);
|
|
1989
|
-
}
|
|
1990
|
-
function evaluateJavaScriptExpression(expression, context) {
|
|
1991
|
-
try {
|
|
1992
|
-
const result = ExpressionParser.evaluate(expression.expression, context);
|
|
1993
|
-
return !!result;
|
|
1856
|
+
setValidators(validators) {
|
|
1857
|
+
this.setRegistryIfChanged(this.validators, validators);
|
|
1858
|
+
}
|
|
1859
|
+
setAsyncValidators(asyncValidators) {
|
|
1860
|
+
this.setRegistryIfChanged(this.asyncValidators, asyncValidators);
|
|
1994
1861
|
}
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
return false;
|
|
1862
|
+
setHttpValidators(httpValidators) {
|
|
1863
|
+
this.setRegistryIfChanged(this.httpValidators, httpValidators);
|
|
1998
1864
|
}
|
|
1865
|
+
clearAll() {
|
|
1866
|
+
this.clearCustomFunctions();
|
|
1867
|
+
this.clearDerivationFunctions();
|
|
1868
|
+
this.clearAsyncDerivationFunctions();
|
|
1869
|
+
this.clearAsyncConditionFunctions();
|
|
1870
|
+
this.clearValidators();
|
|
1871
|
+
this.clearAsyncValidators();
|
|
1872
|
+
this.clearHttpValidators();
|
|
1873
|
+
}
|
|
1874
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: FunctionRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1875
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: FunctionRegistryService });
|
|
1999
1876
|
}
|
|
2000
|
-
|
|
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
|
-
}
|
|
1877
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: FunctionRegistryService, decorators: [{
|
|
1878
|
+
type: Injectable
|
|
1879
|
+
}] });
|
|
2014
1880
|
|
|
2015
1881
|
class HttpConditionCache {
|
|
2016
1882
|
cache = new Map();
|
|
@@ -2094,6 +1960,92 @@ function resolveHttpRequest(config, context) {
|
|
|
2094
1960
|
return request;
|
|
2095
1961
|
}
|
|
2096
1962
|
|
|
1963
|
+
function isSignalArray(value) {
|
|
1964
|
+
return Array.isArray(value) && value.every((item) => isSignal(item));
|
|
1965
|
+
}
|
|
1966
|
+
function isSignalRecord(value) {
|
|
1967
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value) && Object.values(value).every((item) => isSignal(item));
|
|
1968
|
+
}
|
|
1969
|
+
function derivedFromDeferred(source, operator, options) {
|
|
1970
|
+
const deferred$ = defer(() => {
|
|
1971
|
+
if (isSignal(source)) {
|
|
1972
|
+
return toObservable(source, { injector: options.injector });
|
|
1973
|
+
}
|
|
1974
|
+
if (isSignalArray(source)) {
|
|
1975
|
+
const observables = source.map((s) => toObservable(s, { injector: options.injector }));
|
|
1976
|
+
return combineLatest(observables);
|
|
1977
|
+
}
|
|
1978
|
+
if (isSignalRecord(source)) {
|
|
1979
|
+
const keys = Object.keys(source);
|
|
1980
|
+
const observables = keys.map((key) => toObservable(source[key], { injector: options.injector }));
|
|
1981
|
+
return combineLatest(observables, (...values) => {
|
|
1982
|
+
const result = {};
|
|
1983
|
+
keys.forEach((key, index) => {
|
|
1984
|
+
result[key] = values[index];
|
|
1985
|
+
});
|
|
1986
|
+
return result;
|
|
1987
|
+
});
|
|
1988
|
+
}
|
|
1989
|
+
throw new Error('Invalid source type. Expected a Signal, array of Signals, or object of Signals.');
|
|
1990
|
+
}).pipe(operator);
|
|
1991
|
+
return toSignal(deferred$, { initialValue: options.initialValue, injector: options.injector });
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
function withPreviousValue(input) {
|
|
1995
|
+
const derived = linkedSignal({ ...(ngDevMode ? { debugName: "derived" } : /* istanbul ignore next */ {}), source: input.snapshot,
|
|
1996
|
+
computation: (snapshot, previous) => {
|
|
1997
|
+
if ((snapshot.status === 'loading' || snapshot.status === 'reloading') &&
|
|
1998
|
+
previous !== undefined &&
|
|
1999
|
+
previous.value.status !== 'error') {
|
|
2000
|
+
return { status: snapshot.status, value: previous.value.value };
|
|
2001
|
+
}
|
|
2002
|
+
return snapshot;
|
|
2003
|
+
} });
|
|
2004
|
+
return resourceFromSnapshots(derived);
|
|
2005
|
+
}
|
|
2006
|
+
|
|
2007
|
+
function createDebouncedResourceLogicFn(config) {
|
|
2008
|
+
const perFunctionSignalStore = new Map();
|
|
2009
|
+
return (ctx) => {
|
|
2010
|
+
const contextKey = safeReadPathKeys(ctx).join('.');
|
|
2011
|
+
let handle = perFunctionSignalStore.get(contextKey);
|
|
2012
|
+
if (!handle) {
|
|
2013
|
+
const trigger = signal(undefined, /* @ts-ignore */
|
|
2014
|
+
...(ngDevMode ? [{ debugName: "trigger" }] : /* istanbul ignore next */ []));
|
|
2015
|
+
const resultResource = untracked(() => {
|
|
2016
|
+
const debouncedTrigger = derivedFromDeferred(trigger, pipe(debounceTime(config.debounceMs),
|
|
2017
|
+
distinctUntilChanged((a, b) => a?.key === b?.key)), { initialValue: undefined, injector: config.injector });
|
|
2018
|
+
const resource = rxResource({
|
|
2019
|
+
params: () => debouncedTrigger(),
|
|
2020
|
+
stream: ({ params }) => config.buildStream(params.payload),
|
|
2021
|
+
defaultValue: config.pendingValue,
|
|
2022
|
+
injector: config.injector,
|
|
2023
|
+
});
|
|
2024
|
+
return withPreviousValue(resource);
|
|
2025
|
+
});
|
|
2026
|
+
handle = { trigger, resultResource, refetchVersion: 0 };
|
|
2027
|
+
perFunctionSignalStore.set(contextKey, handle);
|
|
2028
|
+
}
|
|
2029
|
+
const resolved = config.resolve(ctx);
|
|
2030
|
+
if (resolved.kind === 'returnEarly') {
|
|
2031
|
+
handle.refetchVersion++;
|
|
2032
|
+
return resolved.value;
|
|
2033
|
+
}
|
|
2034
|
+
if (resolved.kind === 'holdPrevious') {
|
|
2035
|
+
return handle.resultResource.value();
|
|
2036
|
+
}
|
|
2037
|
+
const dedupKey = `${resolved.key}::${handle.refetchVersion}`;
|
|
2038
|
+
const newTrigger = { key: dedupKey, payload: resolved.payload };
|
|
2039
|
+
untracked(() => {
|
|
2040
|
+
const current = handle.trigger();
|
|
2041
|
+
if (current?.key !== newTrigger.key) {
|
|
2042
|
+
handle.trigger.set(newTrigger);
|
|
2043
|
+
}
|
|
2044
|
+
});
|
|
2045
|
+
return handle.resultResource.value();
|
|
2046
|
+
};
|
|
2047
|
+
}
|
|
2048
|
+
|
|
2097
2049
|
function extractBoolean(response, responseExpression, pendingValue, logger) {
|
|
2098
2050
|
if (responseExpression) {
|
|
2099
2051
|
try {
|
|
@@ -2164,6 +2116,57 @@ function createHttpConditionLogicFunction(condition) {
|
|
|
2164
2116
|
return fn;
|
|
2165
2117
|
}
|
|
2166
2118
|
|
|
2119
|
+
function createAsyncConditionLogicFunction(condition) {
|
|
2120
|
+
const fieldContextRegistry = inject(FieldContextRegistryService);
|
|
2121
|
+
const functionRegistry = inject(FunctionRegistryService);
|
|
2122
|
+
const injector = inject(Injector);
|
|
2123
|
+
const logger = inject(DynamicFormLogger);
|
|
2124
|
+
const cacheService = inject(AsyncConditionFunctionCacheService);
|
|
2125
|
+
const pendingValue = condition.pendingValue ?? false;
|
|
2126
|
+
const debounceMs = condition.debounceMs ?? 300;
|
|
2127
|
+
if (condition.asyncFn && condition.asyncFunctionName) {
|
|
2128
|
+
logger.warn('Both "asyncFn" and "asyncFunctionName" are set on async condition. Inline "asyncFn" takes precedence; "asyncFunctionName" is ignored.');
|
|
2129
|
+
}
|
|
2130
|
+
const cacheKey = condition.asyncFn ? undefined : stableStringify(condition);
|
|
2131
|
+
if (cacheKey !== undefined) {
|
|
2132
|
+
const cached = cacheService.asyncConditionFunctionCache.get(cacheKey);
|
|
2133
|
+
if (cached) {
|
|
2134
|
+
return cached;
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2137
|
+
const fn = createDebouncedResourceLogicFn({
|
|
2138
|
+
pendingValue,
|
|
2139
|
+
debounceMs,
|
|
2140
|
+
injector,
|
|
2141
|
+
resolve: (ctx) => {
|
|
2142
|
+
const evaluationContext = fieldContextRegistry.createReactiveEvaluationContext(ctx, functionRegistry.getCustomFunctions());
|
|
2143
|
+
const key = stableStringify({
|
|
2144
|
+
formValue: evaluationContext.formValue,
|
|
2145
|
+
fieldValue: evaluationContext.fieldValue,
|
|
2146
|
+
externalData: evaluationContext.externalData,
|
|
2147
|
+
});
|
|
2148
|
+
return { kind: 'trigger', key, payload: ctx };
|
|
2149
|
+
},
|
|
2150
|
+
buildStream: (capturedCtx) => {
|
|
2151
|
+
const asyncFn = condition.asyncFn ??
|
|
2152
|
+
(condition.asyncFunctionName ? functionRegistry.getAsyncConditionFunction(condition.asyncFunctionName) : undefined);
|
|
2153
|
+
if (!asyncFn) {
|
|
2154
|
+
logger.warn(`Async Condition - function '${condition.asyncFunctionName}' not found. ` + `Register it in customFnConfig.asyncConditions.`);
|
|
2155
|
+
return of(pendingValue);
|
|
2156
|
+
}
|
|
2157
|
+
const evaluationContext = fieldContextRegistry.createReactiveEvaluationContext(capturedCtx, functionRegistry.getCustomFunctions());
|
|
2158
|
+
return from(asyncFn(evaluationContext)).pipe(map((result) => !!result), catchError((error) => {
|
|
2159
|
+
logger.warn('Async Condition - function failed:', error);
|
|
2160
|
+
return of(pendingValue);
|
|
2161
|
+
}));
|
|
2162
|
+
},
|
|
2163
|
+
});
|
|
2164
|
+
if (cacheKey !== undefined) {
|
|
2165
|
+
cacheService.asyncConditionFunctionCache.set(cacheKey, fn);
|
|
2166
|
+
}
|
|
2167
|
+
return fn;
|
|
2168
|
+
}
|
|
2169
|
+
|
|
2167
2170
|
class LogicFunctionCacheService {
|
|
2168
2171
|
logicFunctionCache = new Map();
|
|
2169
2172
|
debouncedLogicFunctionCache = new Map();
|
|
@@ -2294,35 +2297,6 @@ function toReadonlyFieldTreeCached(cache, field) {
|
|
|
2294
2297
|
return view;
|
|
2295
2298
|
}
|
|
2296
2299
|
|
|
2297
|
-
function evaluateHttpValidationResponse(response, mapping, logger) {
|
|
2298
|
-
const scope = { response };
|
|
2299
|
-
try {
|
|
2300
|
-
const result = ExpressionParser.evaluate(mapping.validWhen, scope);
|
|
2301
|
-
if (typeof result !== 'boolean') {
|
|
2302
|
-
logger.warn(`validWhen expression "${mapping.validWhen}" returned non-boolean value:`, result, 'Expected true or false.');
|
|
2303
|
-
}
|
|
2304
|
-
if (result === true) {
|
|
2305
|
-
return null;
|
|
2306
|
-
}
|
|
2307
|
-
const error = { kind: mapping.errorKind };
|
|
2308
|
-
if (mapping.errorParams) {
|
|
2309
|
-
for (const [key, expression] of Object.entries(mapping.errorParams)) {
|
|
2310
|
-
try {
|
|
2311
|
-
error[key] = ExpressionParser.evaluate(expression, scope);
|
|
2312
|
-
}
|
|
2313
|
-
catch (err) {
|
|
2314
|
-
logger.warn(`Error evaluating errorParam "${key}":`, expression, err);
|
|
2315
|
-
}
|
|
2316
|
-
}
|
|
2317
|
-
}
|
|
2318
|
-
return error;
|
|
2319
|
-
}
|
|
2320
|
-
catch (err) {
|
|
2321
|
-
logger.warn('Error evaluating validWhen expression:', mapping.validWhen, err);
|
|
2322
|
-
return { kind: mapping.errorKind };
|
|
2323
|
-
}
|
|
2324
|
-
}
|
|
2325
|
-
|
|
2326
2300
|
const noOpLogger = {
|
|
2327
2301
|
debug: () => { },
|
|
2328
2302
|
info: () => { },
|
|
@@ -2357,7 +2331,10 @@ function evaluateLogicCondition(condition, ctx) {
|
|
|
2357
2331
|
if (isFormStateCondition(condition)) {
|
|
2358
2332
|
return evaluateFormStateCondition(condition, ctx);
|
|
2359
2333
|
}
|
|
2360
|
-
|
|
2334
|
+
if (ctx.evaluationContext) {
|
|
2335
|
+
return evaluateCondition(condition, ctx.evaluationContext());
|
|
2336
|
+
}
|
|
2337
|
+
const formValue = (ctx.formValue ?? ctx.form().value());
|
|
2361
2338
|
const evaluationContext = {
|
|
2362
2339
|
fieldValue: undefined,
|
|
2363
2340
|
formValue,
|
|
@@ -2385,6 +2362,7 @@ function evaluateLogicOfType(fieldLogic, logicType, ctx) {
|
|
|
2385
2362
|
const buttonCtx = {
|
|
2386
2363
|
form: ctx.form,
|
|
2387
2364
|
formValue: ctx.formValue,
|
|
2365
|
+
evaluationContext: ctx.evaluationContext,
|
|
2388
2366
|
logger: ctx.logger,
|
|
2389
2367
|
};
|
|
2390
2368
|
return fieldLogic.filter((logic) => logic.type === logicType).some((logic) => evaluateLogicCondition(logic.condition, buttonCtx));
|
|
@@ -2441,6 +2419,7 @@ function evaluateNonFieldHidden(ctx) {
|
|
|
2441
2419
|
return evaluateLogicOfType(ctx.fieldLogic, 'hidden', {
|
|
2442
2420
|
form: ctx.form,
|
|
2443
2421
|
formValue: ctx.formValue,
|
|
2422
|
+
evaluationContext: ctx.evaluationContext,
|
|
2444
2423
|
logger: ctx.logger,
|
|
2445
2424
|
});
|
|
2446
2425
|
}
|
|
@@ -2457,6 +2436,7 @@ function evaluateNonFieldDisabled(ctx) {
|
|
|
2457
2436
|
return evaluateLogicOfType(ctx.fieldLogic, 'disabled', {
|
|
2458
2437
|
form: ctx.form,
|
|
2459
2438
|
formValue: ctx.formValue,
|
|
2439
|
+
evaluationContext: ctx.evaluationContext,
|
|
2460
2440
|
logger: ctx.logger,
|
|
2461
2441
|
});
|
|
2462
2442
|
}
|
|
@@ -2465,12 +2445,52 @@ function evaluateNonFieldDisabled(ctx) {
|
|
|
2465
2445
|
function resolveNonFieldDisabled(ctx) {
|
|
2466
2446
|
return computed(() => evaluateNonFieldDisabled(ctx));
|
|
2467
2447
|
}
|
|
2448
|
+
function injectNonFieldEvaluationContext(fieldDef, options = {}) {
|
|
2449
|
+
const fieldContextRegistry = inject(FieldContextRegistryService);
|
|
2450
|
+
const functionRegistry = inject(FunctionRegistryService);
|
|
2451
|
+
const arrayContext = options.scopeToArrayItem ? inject(ARRAY_CONTEXT, { optional: true }) : null;
|
|
2452
|
+
return () => {
|
|
2453
|
+
const arrayScope = arrayContext
|
|
2454
|
+
? { arrayKey: arrayContext.arrayKey, index: arrayContext.index(), localKey: fieldDef.key ?? '' }
|
|
2455
|
+
: undefined;
|
|
2456
|
+
return fieldContextRegistry.createDisplayOnlyContext(fieldDef.key ?? '', functionRegistry.getCustomFunctions(), arrayScope);
|
|
2457
|
+
};
|
|
2458
|
+
}
|
|
2468
2459
|
|
|
2469
2460
|
function isFunctionHttpValidator(config) {
|
|
2470
2461
|
const c = config;
|
|
2471
2462
|
return typeof c.functionName === 'string' || (c.fn !== null && typeof c.fn === 'object');
|
|
2472
2463
|
}
|
|
2473
2464
|
|
|
2465
|
+
function evaluateHttpValidationResponse(response, mapping, logger) {
|
|
2466
|
+
const scope = { response };
|
|
2467
|
+
try {
|
|
2468
|
+
const result = ExpressionParser.evaluate(mapping.validWhen, scope);
|
|
2469
|
+
if (typeof result !== 'boolean') {
|
|
2470
|
+
logger.warn(`validWhen expression "${mapping.validWhen}" returned non-boolean value:`, result, 'Expected true or false.');
|
|
2471
|
+
}
|
|
2472
|
+
if (result === true) {
|
|
2473
|
+
return null;
|
|
2474
|
+
}
|
|
2475
|
+
const error = { kind: mapping.errorKind };
|
|
2476
|
+
if (mapping.errorParams) {
|
|
2477
|
+
for (const [key, expression] of Object.entries(mapping.errorParams)) {
|
|
2478
|
+
try {
|
|
2479
|
+
error[key] = ExpressionParser.evaluate(expression, scope);
|
|
2480
|
+
}
|
|
2481
|
+
catch (err) {
|
|
2482
|
+
logger.warn(`Error evaluating errorParam "${key}":`, expression, err);
|
|
2483
|
+
}
|
|
2484
|
+
}
|
|
2485
|
+
}
|
|
2486
|
+
return error;
|
|
2487
|
+
}
|
|
2488
|
+
catch (err) {
|
|
2489
|
+
logger.warn('Error evaluating validWhen expression:', mapping.validWhen, err);
|
|
2490
|
+
return { kind: mapping.errorKind };
|
|
2491
|
+
}
|
|
2492
|
+
}
|
|
2493
|
+
|
|
2474
2494
|
class DynamicValueFunctionCacheService {
|
|
2475
2495
|
dynamicValueFunctionCache = new Map();
|
|
2476
2496
|
}
|
|
@@ -2620,9 +2640,7 @@ function applyCustomValidator(config, fieldPath) {
|
|
|
2620
2640
|
validatorFn = createFunctionValidator(config);
|
|
2621
2641
|
}
|
|
2622
2642
|
else {
|
|
2623
|
-
|
|
2624
|
-
logger.warn('Custom validator must have one of "expression", "functionName", or "fn"');
|
|
2625
|
-
return;
|
|
2643
|
+
throw new DynamicFormError('Custom validator requires one of "expression", "functionName", or "fn".');
|
|
2626
2644
|
}
|
|
2627
2645
|
const whenLogic = createConditionalLogic(config.when);
|
|
2628
2646
|
validate(fieldPath, (ctx) => {
|
|
@@ -2642,8 +2660,7 @@ function createFunctionValidator(config) {
|
|
|
2642
2660
|
}
|
|
2643
2661
|
const functionName = config.functionName;
|
|
2644
2662
|
if (!functionName) {
|
|
2645
|
-
|
|
2646
|
-
return () => null;
|
|
2663
|
+
throw new DynamicFormError('Custom validator requires one of "functionName" or "fn".');
|
|
2647
2664
|
}
|
|
2648
2665
|
const registry = inject(FunctionRegistryService);
|
|
2649
2666
|
const validatorFn = registry.getValidator(functionName);
|
|
@@ -2656,8 +2673,7 @@ function createExpressionValidator(config) {
|
|
|
2656
2673
|
const logger = inject(DynamicFormLogger);
|
|
2657
2674
|
const expression = config.expression;
|
|
2658
2675
|
if (!expression) {
|
|
2659
|
-
|
|
2660
|
-
return () => null;
|
|
2676
|
+
throw new DynamicFormError('Custom validator requires a non-empty "expression".');
|
|
2661
2677
|
}
|
|
2662
2678
|
const fieldContextRegistry = inject(FieldContextRegistryService);
|
|
2663
2679
|
const functionRegistry = inject(FunctionRegistryService);
|
|
@@ -2940,10 +2956,10 @@ class EventBus {
|
|
|
2940
2956
|
}
|
|
2941
2957
|
return this.pipeline$.pipe(filter((event) => event.type === eventType));
|
|
2942
2958
|
}
|
|
2943
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.
|
|
2944
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.
|
|
2959
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: EventBus, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
2960
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: EventBus });
|
|
2945
2961
|
}
|
|
2946
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.
|
|
2962
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: EventBus, decorators: [{
|
|
2947
2963
|
type: Injectable
|
|
2948
2964
|
}] });
|
|
2949
2965
|
|
|
@@ -2973,18 +2989,6 @@ function resolveTokens(args, context) {
|
|
|
2973
2989
|
});
|
|
2974
2990
|
}
|
|
2975
2991
|
|
|
2976
|
-
function applyHiddenLogic(inputs, fieldDef, rootFormRegistry) {
|
|
2977
|
-
const rootForm = rootFormRegistry.rootForm();
|
|
2978
|
-
if (rootForm && (fieldDef.hidden === true || fieldDef.logic?.some((l) => l.type === 'hidden'))) {
|
|
2979
|
-
inputs['hidden'] = evaluateNonFieldHidden({
|
|
2980
|
-
form: rootForm,
|
|
2981
|
-
fieldLogic: fieldDef.logic,
|
|
2982
|
-
explicitValue: fieldDef.hidden,
|
|
2983
|
-
formValue: rootFormRegistry.formValue(),
|
|
2984
|
-
});
|
|
2985
|
-
}
|
|
2986
|
-
}
|
|
2987
|
-
|
|
2988
2992
|
function getGridClassString(fieldDef) {
|
|
2989
2993
|
const col = fieldDef.col;
|
|
2990
2994
|
if (typeof col === 'number' && Number.isInteger(col) && col > 0 && col <= 12) {
|
|
@@ -3005,8 +3009,22 @@ function buildClassName(fieldDef) {
|
|
|
3005
3009
|
return classes.length > 0 ? classes.join(' ') : undefined;
|
|
3006
3010
|
}
|
|
3007
3011
|
|
|
3012
|
+
function applyHiddenLogic(inputs, fieldDef, rootFormRegistry, evaluationContext) {
|
|
3013
|
+
const rootForm = rootFormRegistry.rootForm();
|
|
3014
|
+
if (rootForm && (fieldDef.hidden === true || fieldDef.logic?.some((l) => l.type === 'hidden'))) {
|
|
3015
|
+
inputs['hidden'] = evaluateNonFieldHidden({
|
|
3016
|
+
form: rootForm,
|
|
3017
|
+
fieldLogic: fieldDef.logic,
|
|
3018
|
+
explicitValue: fieldDef.hidden,
|
|
3019
|
+
formValue: rootFormRegistry.formValue(),
|
|
3020
|
+
evaluationContext,
|
|
3021
|
+
});
|
|
3022
|
+
}
|
|
3023
|
+
}
|
|
3024
|
+
|
|
3008
3025
|
function arrayFieldMapper(fieldDef) {
|
|
3009
3026
|
const rootFormRegistry = inject(RootFormRegistryService);
|
|
3027
|
+
const evaluationContext = injectNonFieldEvaluationContext(fieldDef, { scopeToArrayItem: true });
|
|
3010
3028
|
const className = buildClassName(fieldDef);
|
|
3011
3029
|
return computed(() => {
|
|
3012
3030
|
const inputs = {
|
|
@@ -3014,7 +3032,7 @@ function arrayFieldMapper(fieldDef) {
|
|
|
3014
3032
|
field: fieldDef,
|
|
3015
3033
|
...(className !== undefined && { className }),
|
|
3016
3034
|
};
|
|
3017
|
-
applyHiddenLogic(inputs, fieldDef, rootFormRegistry);
|
|
3035
|
+
applyHiddenLogic(inputs, fieldDef, rootFormRegistry, evaluationContext);
|
|
3018
3036
|
return inputs;
|
|
3019
3037
|
});
|
|
3020
3038
|
}
|
|
@@ -3059,6 +3077,7 @@ function baseFieldMapper(fieldDef) {
|
|
|
3059
3077
|
|
|
3060
3078
|
function containerFieldMapper(fieldDef) {
|
|
3061
3079
|
const rootFormRegistry = inject(RootFormRegistryService);
|
|
3080
|
+
const evaluationContext = injectNonFieldEvaluationContext(fieldDef, { scopeToArrayItem: true });
|
|
3062
3081
|
const className = buildClassName(fieldDef);
|
|
3063
3082
|
return computed(() => {
|
|
3064
3083
|
const containerField = { ...fieldDef, wrappers: undefined, skipAutoWrappers: undefined, skipDefaultWrappers: undefined };
|
|
@@ -3067,13 +3086,14 @@ function containerFieldMapper(fieldDef) {
|
|
|
3067
3086
|
field: containerField,
|
|
3068
3087
|
...(className !== undefined && { className }),
|
|
3069
3088
|
};
|
|
3070
|
-
applyHiddenLogic(inputs, fieldDef, rootFormRegistry);
|
|
3089
|
+
applyHiddenLogic(inputs, fieldDef, rootFormRegistry, evaluationContext);
|
|
3071
3090
|
return inputs;
|
|
3072
3091
|
});
|
|
3073
3092
|
}
|
|
3074
3093
|
|
|
3075
3094
|
function groupFieldMapper(fieldDef) {
|
|
3076
3095
|
const rootFormRegistry = inject(RootFormRegistryService);
|
|
3096
|
+
const evaluationContext = injectNonFieldEvaluationContext(fieldDef, { scopeToArrayItem: true });
|
|
3077
3097
|
const className = buildClassName(fieldDef);
|
|
3078
3098
|
return computed(() => {
|
|
3079
3099
|
const inputs = {
|
|
@@ -3081,7 +3101,7 @@ function groupFieldMapper(fieldDef) {
|
|
|
3081
3101
|
field: fieldDef,
|
|
3082
3102
|
...(className !== undefined && { className }),
|
|
3083
3103
|
};
|
|
3084
|
-
applyHiddenLogic(inputs, fieldDef, rootFormRegistry);
|
|
3104
|
+
applyHiddenLogic(inputs, fieldDef, rootFormRegistry, evaluationContext);
|
|
3085
3105
|
return inputs;
|
|
3086
3106
|
});
|
|
3087
3107
|
}
|
|
@@ -3089,6 +3109,7 @@ function groupFieldMapper(fieldDef) {
|
|
|
3089
3109
|
const ROW_WRAPPERS = [{ type: 'row' }];
|
|
3090
3110
|
function rowFieldMapper(fieldDef) {
|
|
3091
3111
|
const rootFormRegistry = inject(RootFormRegistryService);
|
|
3112
|
+
const evaluationContext = injectNonFieldEvaluationContext(fieldDef, { scopeToArrayItem: true });
|
|
3092
3113
|
const className = buildClassName(fieldDef);
|
|
3093
3114
|
return computed(() => {
|
|
3094
3115
|
const containerField = { ...fieldDef, type: 'container', wrappers: ROW_WRAPPERS };
|
|
@@ -3097,7 +3118,7 @@ function rowFieldMapper(fieldDef) {
|
|
|
3097
3118
|
field: containerField,
|
|
3098
3119
|
...(className !== undefined && { className }),
|
|
3099
3120
|
};
|
|
3100
|
-
applyHiddenLogic(inputs, fieldDef, rootFormRegistry);
|
|
3121
|
+
applyHiddenLogic(inputs, fieldDef, rootFormRegistry, evaluationContext);
|
|
3101
3122
|
return inputs;
|
|
3102
3123
|
});
|
|
3103
3124
|
}
|
|
@@ -3113,11 +3134,12 @@ function pageFieldMapper(fieldDef) {
|
|
|
3113
3134
|
|
|
3114
3135
|
function textFieldMapper(fieldDef) {
|
|
3115
3136
|
const rootFormRegistry = inject(RootFormRegistryService);
|
|
3137
|
+
const evaluationContext = injectNonFieldEvaluationContext(fieldDef, { scopeToArrayItem: true });
|
|
3116
3138
|
const defaultProps = inject(DEFAULT_PROPS);
|
|
3117
3139
|
return computed(() => {
|
|
3118
3140
|
const baseInputs = buildBaseInputs(fieldDef, defaultProps());
|
|
3119
3141
|
const inputs = { ...baseInputs };
|
|
3120
|
-
applyHiddenLogic(inputs, fieldDef, rootFormRegistry);
|
|
3142
|
+
applyHiddenLogic(inputs, fieldDef, rootFormRegistry, evaluationContext);
|
|
3121
3143
|
return inputs;
|
|
3122
3144
|
});
|
|
3123
3145
|
}
|
|
@@ -3276,10 +3298,10 @@ class DynamicTextPipe {
|
|
|
3276
3298
|
}
|
|
3277
3299
|
return of(value || '');
|
|
3278
3300
|
}
|
|
3279
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.
|
|
3280
|
-
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "22.0.
|
|
3301
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: DynamicTextPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
3302
|
+
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "22.0.1", ngImport: i0, type: DynamicTextPipe, isStandalone: true, name: "dynamicText" });
|
|
3281
3303
|
}
|
|
3282
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.
|
|
3304
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: DynamicTextPipe, decorators: [{
|
|
3283
3305
|
type: Pipe,
|
|
3284
3306
|
args: [{
|
|
3285
3307
|
name: 'dynamicText',
|
|
@@ -3609,5 +3631,5 @@ function createWarningTracker() {
|
|
|
3609
3631
|
|
|
3610
3632
|
|
|
3611
3633
|
|
|
3612
|
-
export { ADDON_ACTION_HANDLERS, ADDON_ACTION_REGISTRY, ADDON_TYPE_COMPONENT_CACHE, ADDON_TYPE_DEFINITIONS, ADDON_TYPE_REGISTRY, ARRAY_CONTEXT, AsyncConditionFunctionCacheService, ComponentInitializedEvent, DEFAULT_PROPS, DEFAULT_VALIDATION_MESSAGES, DEFAULT_WRAPPERS, DEPRECATION_WARNING_TRACKER, DF_FIELD_TEMPLATES, DfAddonSlot, DynamicFormError, DynamicFormLogger, DynamicTextPipe, DynamicValueFunctionCacheService, EMIT_FORM_VALUE_ON_EVENTS, EXTERNAL_DATA,
|
|
3634
|
+
export { ADDON_ACTION_HANDLERS, ADDON_ACTION_REGISTRY, ADDON_TYPE_COMPONENT_CACHE, ADDON_TYPE_DEFINITIONS, ADDON_TYPE_REGISTRY, ARRAY_CONTEXT, AsyncConditionFunctionCacheService, ComponentInitializedEvent, DEFAULT_PROPS, DEFAULT_VALIDATION_MESSAGES, DEFAULT_WRAPPERS, DEPRECATION_WARNING_TRACKER, DF_FIELD_TEMPLATES, DfAddonSlot, DynamicFormError, DynamicFormLogger, DynamicTextPipe, DynamicValueFunctionCacheService, EMIT_FORM_VALUE_ON_EVENTS, EXTERNAL_DATA, EventBus, ExpressionParser, ExpressionParserError, FIELD_REGISTRY, FIELD_SIGNAL_CONTEXT, FORM_ID_PREFIX, FORM_OPTIONS, FieldContextRegistryService, FunctionRegistryService, GROUP_CONTEXT, HTTP_CONDITION_CACHE, HttpConditionCache, HttpConditionFunctionCacheService, INITIALIZATION_TIMEOUT_MS, LogicFunctionCacheService, NoopLogger, READONLY_FIELD_TREE_CACHE, RootFormRegistryService, TokenType, WRAPPER_AUTO_ASSOCIATIONS, WRAPPER_COMPONENT_CACHE, WRAPPER_REGISTRY, applyMetaToElement, applyValidator, applyValidators, arrayFieldMapper, assertNever, baseFieldMapper, buildBaseInputs, compareValues, containerFieldMapper, createAddonActionRegistry, createDebouncedLogicFunction, createDefaultDerivationLogConfig, createDynamicValueFunction, createFormFieldStateMap, createInitializationTracker, createLogicFunction, createWarningTracker, deepMergeDefaults, derivedFromDeferred, detectFormMode, dynamicTextToObservable, evaluateCondition, evaluateNonFieldDisabled, evaluateNonFieldHidden, extractExpressionDependencies, extractStringDependencies, getArrayLength, getChangedKeys, getFieldValueHandling, getNestedValue, groupFieldMapper, hasChildFields, hasCrossFieldWhenCondition, hasFormValue, hasNestedProperty, hasTargetProperty, injectAddonTypeRegistry, injectFieldSignalContext, injectFieldsSupportingAddons, injectNonFieldEvaluationContext, interpolateParams, isArrayField, isCheckedField, isContainerField, isCrossFieldBuiltInValidator, isCrossFieldExpression, isCrossFieldSchema, isCrossFieldStateLogic, isCrossFieldValidator, isDerivationLogicConfig, isDisplayOnlyField, isEqual, isFieldBoundContext, isFormStateCondition, isFunctionHttpValidator, isGenericContainerField, isGroupField, isHiddenField, isLeafField, isNonPagedForm, isPageField, isPagedForm, isResourceBasedValidator, isRowField, isSimplifiedArrayField, isStateLogicConfig, isValidNonPagedForm, isValidPagedForm, isValueBearingField, isValueField, isWrapperTypeDefinition, keyBy, mapValues, memoize, normalizeFieldsArray, omit, pageFieldMapper, readFieldStateInfo, resolveDynamicValue, resolveHttpRequest, resolveNextButtonDisabled, resolveNonFieldDisabled, resolveNonFieldHidden, resolveSubmitButtonDisabled, resolveTokens, rowFieldMapper, runPresetAction, setupInitializationTracking, shouldLog, textFieldMapper, toReadonlyFieldTree, toReadonlyFieldTreeCached, validatePageNesting, withPreviousValue, writeToFieldValue };
|
|
3613
3635
|
//# sourceMappingURL=ng-forge-dynamic-forms-internal.mjs.map
|