@ngx-formly/core 7.0.1 → 7.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm2022/json-schema/formly-json-schema.service.mjs +101 -1
- package/esm2022/lib/core.config.mjs +19 -3
- package/esm2022/lib/core.mjs +2 -2
- package/esm2022/lib/extensions/field-expression/field-expression.mjs +6 -244
- package/esm2022/lib/extensions/field-expression/utils.mjs +453 -11
- package/esm2022/lib/extensions/field-expression-legacy/field-expression.mjs +249 -0
- package/esm2022/lib/extensions/field-expression-legacy/utils.mjs +20 -0
- package/esm2022/lib/utils.mjs +2 -1
- package/fesm2022/ngx-formly-core-json-schema.mjs +100 -0
- package/fesm2022/ngx-formly-core-json-schema.mjs.map +1 -1
- package/fesm2022/ngx-formly-core.mjs +492 -5
- package/fesm2022/ngx-formly-core.mjs.map +1 -1
- package/json-schema/formly-json-schema.service.d.ts +3 -0
- package/lib/core.config.d.ts +1 -0
- package/lib/core.d.ts +2 -2
- package/lib/extensions/field-expression/field-expression.d.ts +3 -12
- package/lib/extensions/field-expression/utils.d.ts +4 -2
- package/lib/extensions/field-expression-legacy/field-expression.d.ts +16 -0
- package/lib/extensions/field-expression-legacy/utils.d.ts +5 -0
- package/package.json +1 -1
- package/esm2022/lib/extensions/index.mjs +0 -2
- package/lib/extensions/index.d.ts +0 -1
|
@@ -131,6 +131,7 @@ function isPromise(obj) {
|
|
|
131
131
|
function clone(value) {
|
|
132
132
|
if (!isObject(value) ||
|
|
133
133
|
isObservable(value) ||
|
|
134
|
+
isPromise(value) ||
|
|
134
135
|
value instanceof TemplateRef ||
|
|
135
136
|
/* instanceof SafeHtmlImpl */ value.changingThisBreaksApplicationSecurity ||
|
|
136
137
|
['RegExp', 'FileList', 'File', 'Blob'].indexOf(value.constructor?.name) !== -1) {
|
|
@@ -317,7 +318,10 @@ function isSignalRequired() {
|
|
|
317
318
|
return +VERSION.major > 18 || (+VERSION.major >= 18 && +VERSION.minor >= 1);
|
|
318
319
|
}
|
|
319
320
|
|
|
320
|
-
|
|
321
|
+
/**
|
|
322
|
+
* Legacy implementation
|
|
323
|
+
*/
|
|
324
|
+
function evalStringExpressionLegacy(expression, argNames) {
|
|
321
325
|
try {
|
|
322
326
|
return Function(...argNames, `return ${expression};`);
|
|
323
327
|
}
|
|
@@ -432,7 +436,7 @@ function clearControl(form) {
|
|
|
432
436
|
}
|
|
433
437
|
}
|
|
434
438
|
|
|
435
|
-
class FieldExpressionExtension {
|
|
439
|
+
let FieldExpressionExtension$1 = class FieldExpressionExtension {
|
|
436
440
|
onPopulate(field) {
|
|
437
441
|
if (field._expressions) {
|
|
438
442
|
return;
|
|
@@ -515,7 +519,7 @@ class FieldExpressionExtension {
|
|
|
515
519
|
}
|
|
516
520
|
expr = expr || (() => false);
|
|
517
521
|
if (typeof expr === 'string') {
|
|
518
|
-
expr =
|
|
522
|
+
expr = this._evalStringExpression(expr, ['model', 'formState', 'field']);
|
|
519
523
|
}
|
|
520
524
|
let currentValue;
|
|
521
525
|
return {
|
|
@@ -540,6 +544,9 @@ class FieldExpressionExtension {
|
|
|
540
544
|
},
|
|
541
545
|
};
|
|
542
546
|
}
|
|
547
|
+
_evalStringExpression(expression, argNames) {
|
|
548
|
+
return evalStringExpressionLegacy(expression, argNames);
|
|
549
|
+
}
|
|
543
550
|
checkExpressions(field, ignoreCache = false) {
|
|
544
551
|
if (!field) {
|
|
545
552
|
return false;
|
|
@@ -670,6 +677,471 @@ class FieldExpressionExtension {
|
|
|
670
677
|
}
|
|
671
678
|
return paths;
|
|
672
679
|
}
|
|
680
|
+
};
|
|
681
|
+
|
|
682
|
+
// CSP-compliant expression parser for Formly expressions
|
|
683
|
+
// Supports: model.path, formState.path, field.path, comparisons, logical operators, and negation
|
|
684
|
+
// Tokenizer
|
|
685
|
+
var TokenType;
|
|
686
|
+
(function (TokenType) {
|
|
687
|
+
TokenType["IDENTIFIER"] = "IDENTIFIER";
|
|
688
|
+
TokenType["DOT"] = "DOT";
|
|
689
|
+
TokenType["BRACKET_OPEN"] = "BRACKET_OPEN";
|
|
690
|
+
TokenType["BRACKET_CLOSE"] = "BRACKET_CLOSE";
|
|
691
|
+
TokenType["STRING"] = "STRING";
|
|
692
|
+
TokenType["NUMBER"] = "NUMBER";
|
|
693
|
+
TokenType["BOOLEAN"] = "BOOLEAN";
|
|
694
|
+
TokenType["NULL"] = "NULL";
|
|
695
|
+
TokenType["UNDEFINED"] = "UNDEFINED";
|
|
696
|
+
TokenType["OPERATOR"] = "OPERATOR";
|
|
697
|
+
TokenType["LOGICAL"] = "LOGICAL";
|
|
698
|
+
TokenType["NOT"] = "NOT";
|
|
699
|
+
TokenType["ARITHMETIC"] = "ARITHMETIC";
|
|
700
|
+
TokenType["PAREN_OPEN"] = "PAREN_OPEN";
|
|
701
|
+
TokenType["PAREN_CLOSE"] = "PAREN_CLOSE";
|
|
702
|
+
TokenType["TERNARY_QUESTION"] = "TERNARY_QUESTION";
|
|
703
|
+
TokenType["TERNARY_COLON"] = "TERNARY_COLON";
|
|
704
|
+
TokenType["EOF"] = "EOF";
|
|
705
|
+
})(TokenType || (TokenType = {}));
|
|
706
|
+
class Tokenizer {
|
|
707
|
+
constructor(input) {
|
|
708
|
+
this.pos = 0;
|
|
709
|
+
this.input = input.trim();
|
|
710
|
+
}
|
|
711
|
+
tokenize() {
|
|
712
|
+
const tokens = [];
|
|
713
|
+
while (this.pos < this.input.length) {
|
|
714
|
+
this.skipWhitespace();
|
|
715
|
+
if (this.pos >= this.input.length)
|
|
716
|
+
break;
|
|
717
|
+
const char = this.input[this.pos];
|
|
718
|
+
// String literals
|
|
719
|
+
if (char === '"' || char === "'") {
|
|
720
|
+
tokens.push(this.readString());
|
|
721
|
+
}
|
|
722
|
+
// Numbers
|
|
723
|
+
else if (/\d/.test(char)) {
|
|
724
|
+
tokens.push(this.readNumber());
|
|
725
|
+
}
|
|
726
|
+
// Identifiers and keywords
|
|
727
|
+
else if (/[a-zA-Z_$]/.test(char)) {
|
|
728
|
+
tokens.push(this.readIdentifier());
|
|
729
|
+
}
|
|
730
|
+
// Operators and symbols
|
|
731
|
+
else if (char === '.') {
|
|
732
|
+
tokens.push({ type: TokenType.DOT, value: '.' });
|
|
733
|
+
this.pos++;
|
|
734
|
+
}
|
|
735
|
+
else if (char === '[') {
|
|
736
|
+
tokens.push({ type: TokenType.BRACKET_OPEN, value: '[' });
|
|
737
|
+
this.pos++;
|
|
738
|
+
}
|
|
739
|
+
else if (char === ']') {
|
|
740
|
+
tokens.push({ type: TokenType.BRACKET_CLOSE, value: ']' });
|
|
741
|
+
this.pos++;
|
|
742
|
+
}
|
|
743
|
+
else if (char === '(') {
|
|
744
|
+
tokens.push({ type: TokenType.PAREN_OPEN, value: '(' });
|
|
745
|
+
this.pos++;
|
|
746
|
+
}
|
|
747
|
+
else if (char === ')') {
|
|
748
|
+
tokens.push({ type: TokenType.PAREN_CLOSE, value: ')' });
|
|
749
|
+
this.pos++;
|
|
750
|
+
}
|
|
751
|
+
else if (char === '?') {
|
|
752
|
+
tokens.push({ type: TokenType.TERNARY_QUESTION, value: '?' });
|
|
753
|
+
this.pos++;
|
|
754
|
+
}
|
|
755
|
+
else if (char === ':') {
|
|
756
|
+
tokens.push({ type: TokenType.TERNARY_COLON, value: ':' });
|
|
757
|
+
this.pos++;
|
|
758
|
+
}
|
|
759
|
+
else if (char === '!') {
|
|
760
|
+
if (this.peek() === '=') {
|
|
761
|
+
if (this.input[this.pos + 2] === '=') {
|
|
762
|
+
tokens.push({ type: TokenType.OPERATOR, value: '!==' });
|
|
763
|
+
this.pos += 3;
|
|
764
|
+
}
|
|
765
|
+
else {
|
|
766
|
+
tokens.push({ type: TokenType.OPERATOR, value: '!=' });
|
|
767
|
+
this.pos += 2;
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
else {
|
|
771
|
+
tokens.push({ type: TokenType.NOT, value: '!' });
|
|
772
|
+
this.pos++;
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
else if (char === '=' || char === '<' || char === '>') {
|
|
776
|
+
tokens.push(this.readOperator());
|
|
777
|
+
}
|
|
778
|
+
else if (char === '+' || char === '*' || char === '/' || char === '%') {
|
|
779
|
+
tokens.push({ type: TokenType.ARITHMETIC, value: char });
|
|
780
|
+
this.pos++;
|
|
781
|
+
}
|
|
782
|
+
else if (char === '-') {
|
|
783
|
+
// Check if this is a negative number or subtraction
|
|
784
|
+
// It's a negative number if preceded by an operator, opening paren, or at the start
|
|
785
|
+
const lastToken = tokens[tokens.length - 1];
|
|
786
|
+
const isNegativeNumber = tokens.length === 0 ||
|
|
787
|
+
lastToken?.type === TokenType.OPERATOR ||
|
|
788
|
+
lastToken?.type === TokenType.LOGICAL ||
|
|
789
|
+
lastToken?.type === TokenType.ARITHMETIC ||
|
|
790
|
+
lastToken?.type === TokenType.PAREN_OPEN ||
|
|
791
|
+
lastToken?.type === TokenType.TERNARY_QUESTION ||
|
|
792
|
+
lastToken?.type === TokenType.TERNARY_COLON;
|
|
793
|
+
if (isNegativeNumber && this.peek() && /\d/.test(this.peek())) {
|
|
794
|
+
tokens.push(this.readNumber());
|
|
795
|
+
}
|
|
796
|
+
else {
|
|
797
|
+
tokens.push({ type: TokenType.ARITHMETIC, value: '-' });
|
|
798
|
+
this.pos++;
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
else if (char === '&' && this.peek() === '&') {
|
|
802
|
+
tokens.push({ type: TokenType.LOGICAL, value: '&&' });
|
|
803
|
+
this.pos += 2;
|
|
804
|
+
}
|
|
805
|
+
else if (char === '|' && this.peek() === '|') {
|
|
806
|
+
tokens.push({ type: TokenType.LOGICAL, value: '||' });
|
|
807
|
+
this.pos += 2;
|
|
808
|
+
}
|
|
809
|
+
else {
|
|
810
|
+
throw new Error(`Unexpected character: ${char} at position ${this.pos}`);
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
tokens.push({ type: TokenType.EOF, value: null });
|
|
814
|
+
return tokens;
|
|
815
|
+
}
|
|
816
|
+
skipWhitespace() {
|
|
817
|
+
while (this.pos < this.input.length && /\s/.test(this.input[this.pos])) {
|
|
818
|
+
this.pos++;
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
peek(offset = 1) {
|
|
822
|
+
return this.input[this.pos + offset] || '';
|
|
823
|
+
}
|
|
824
|
+
readString() {
|
|
825
|
+
const quote = this.input[this.pos];
|
|
826
|
+
this.pos++;
|
|
827
|
+
let value = '';
|
|
828
|
+
while (this.pos < this.input.length && this.input[this.pos] !== quote) {
|
|
829
|
+
if (this.input[this.pos] === '\\') {
|
|
830
|
+
this.pos++;
|
|
831
|
+
if (this.pos < this.input.length) {
|
|
832
|
+
const escaped = this.input[this.pos];
|
|
833
|
+
switch (escaped) {
|
|
834
|
+
case 'n':
|
|
835
|
+
value += '\n';
|
|
836
|
+
break;
|
|
837
|
+
case 't':
|
|
838
|
+
value += '\t';
|
|
839
|
+
break;
|
|
840
|
+
case 'r':
|
|
841
|
+
value += '\r';
|
|
842
|
+
break;
|
|
843
|
+
default:
|
|
844
|
+
value += escaped;
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
else {
|
|
849
|
+
value += this.input[this.pos];
|
|
850
|
+
}
|
|
851
|
+
this.pos++;
|
|
852
|
+
}
|
|
853
|
+
this.pos++; // skip closing quote
|
|
854
|
+
return { type: TokenType.STRING, value };
|
|
855
|
+
}
|
|
856
|
+
readNumber() {
|
|
857
|
+
let value = '';
|
|
858
|
+
// Handle negatives
|
|
859
|
+
if (this.input[this.pos] === '-') {
|
|
860
|
+
value += '-';
|
|
861
|
+
this.pos++;
|
|
862
|
+
}
|
|
863
|
+
let hasDecimal = false;
|
|
864
|
+
while (this.pos < this.input.length && /[\d.]/.test(this.input[this.pos])) {
|
|
865
|
+
if (this.input[this.pos] === '.') {
|
|
866
|
+
if (hasDecimal) {
|
|
867
|
+
throw new Error(`Invalid number format: multiple decimal points at position ${this.pos}`);
|
|
868
|
+
}
|
|
869
|
+
hasDecimal = true;
|
|
870
|
+
}
|
|
871
|
+
value += this.input[this.pos];
|
|
872
|
+
this.pos++;
|
|
873
|
+
}
|
|
874
|
+
const parsed = parseFloat(value);
|
|
875
|
+
if (isNaN(parsed)) {
|
|
876
|
+
throw new Error(`Invalid number format: ${value}`);
|
|
877
|
+
}
|
|
878
|
+
return { type: TokenType.NUMBER, value: parsed };
|
|
879
|
+
}
|
|
880
|
+
readIdentifier() {
|
|
881
|
+
let value = '';
|
|
882
|
+
while (this.pos < this.input.length && /[a-zA-Z0-9_$]/.test(this.input[this.pos])) {
|
|
883
|
+
value += this.input[this.pos];
|
|
884
|
+
this.pos++;
|
|
885
|
+
}
|
|
886
|
+
// Check for keywords
|
|
887
|
+
if (value === 'true' || value === 'false') {
|
|
888
|
+
return { type: TokenType.BOOLEAN, value: value === 'true' };
|
|
889
|
+
}
|
|
890
|
+
if (value === 'null') {
|
|
891
|
+
return { type: TokenType.NULL, value: null };
|
|
892
|
+
}
|
|
893
|
+
if (value === 'undefined') {
|
|
894
|
+
return { type: TokenType.UNDEFINED, value: undefined };
|
|
895
|
+
}
|
|
896
|
+
return { type: TokenType.IDENTIFIER, value };
|
|
897
|
+
}
|
|
898
|
+
readOperator() {
|
|
899
|
+
let op = this.input[this.pos];
|
|
900
|
+
this.pos++;
|
|
901
|
+
if (this.pos < this.input.length) {
|
|
902
|
+
const next = this.input[this.pos];
|
|
903
|
+
if ((op === '=' && next === '=') ||
|
|
904
|
+
(op === '!' && next === '=') ||
|
|
905
|
+
(op === '<' && next === '=') ||
|
|
906
|
+
(op === '>' && next === '=')) {
|
|
907
|
+
op += next;
|
|
908
|
+
this.pos++;
|
|
909
|
+
// Check for === or !==
|
|
910
|
+
if (this.pos < this.input.length && this.input[this.pos] === '=') {
|
|
911
|
+
op += '=';
|
|
912
|
+
this.pos++;
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
return { type: TokenType.OPERATOR, value: op };
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
// Parser and Evaluator
|
|
920
|
+
class ExpressionParser {
|
|
921
|
+
constructor(tokens) {
|
|
922
|
+
this.pos = 0;
|
|
923
|
+
this.tokens = tokens;
|
|
924
|
+
}
|
|
925
|
+
parse() {
|
|
926
|
+
const expr = this.parseTernary();
|
|
927
|
+
return (context) => expr(context);
|
|
928
|
+
}
|
|
929
|
+
parseTernary() {
|
|
930
|
+
const expr = this.parseLogicalOr();
|
|
931
|
+
if (this.current().type === TokenType.TERNARY_QUESTION) {
|
|
932
|
+
this.consume(TokenType.TERNARY_QUESTION);
|
|
933
|
+
const trueExpr = this.parseLogicalOr();
|
|
934
|
+
this.consume(TokenType.TERNARY_COLON);
|
|
935
|
+
const falseExpr = this.parseTernary();
|
|
936
|
+
return (context) => {
|
|
937
|
+
return expr(context) ? trueExpr(context) : falseExpr(context);
|
|
938
|
+
};
|
|
939
|
+
}
|
|
940
|
+
return expr;
|
|
941
|
+
}
|
|
942
|
+
parseLogicalOr() {
|
|
943
|
+
let left = this.parseLogicalAnd();
|
|
944
|
+
while (this.current().type === TokenType.LOGICAL && this.current().value === '||') {
|
|
945
|
+
this.consume(TokenType.LOGICAL);
|
|
946
|
+
const right = this.parseLogicalAnd();
|
|
947
|
+
const prevLeft = left;
|
|
948
|
+
left = (context) => prevLeft(context) || right(context);
|
|
949
|
+
}
|
|
950
|
+
return left;
|
|
951
|
+
}
|
|
952
|
+
parseLogicalAnd() {
|
|
953
|
+
let left = this.parseComparison();
|
|
954
|
+
while (this.current().type === TokenType.LOGICAL && this.current().value === '&&') {
|
|
955
|
+
this.consume(TokenType.LOGICAL);
|
|
956
|
+
const right = this.parseComparison();
|
|
957
|
+
const prevLeft = left;
|
|
958
|
+
left = (context) => prevLeft(context) && right(context);
|
|
959
|
+
}
|
|
960
|
+
return left;
|
|
961
|
+
}
|
|
962
|
+
parseComparison() {
|
|
963
|
+
const left = this.parseAdditive();
|
|
964
|
+
if (this.current().type === TokenType.OPERATOR) {
|
|
965
|
+
const op = this.consume(TokenType.OPERATOR).value;
|
|
966
|
+
const right = this.parseAdditive();
|
|
967
|
+
return (context) => {
|
|
968
|
+
const leftVal = left(context);
|
|
969
|
+
const rightVal = right(context);
|
|
970
|
+
switch (op) {
|
|
971
|
+
case '===':
|
|
972
|
+
return leftVal === rightVal;
|
|
973
|
+
case '!==':
|
|
974
|
+
return leftVal !== rightVal;
|
|
975
|
+
case '==':
|
|
976
|
+
return leftVal == rightVal;
|
|
977
|
+
case '!=':
|
|
978
|
+
return leftVal != rightVal;
|
|
979
|
+
case '<':
|
|
980
|
+
return leftVal < rightVal;
|
|
981
|
+
case '<=':
|
|
982
|
+
return leftVal <= rightVal;
|
|
983
|
+
case '>':
|
|
984
|
+
return leftVal > rightVal;
|
|
985
|
+
case '>=':
|
|
986
|
+
return leftVal >= rightVal;
|
|
987
|
+
default:
|
|
988
|
+
throw new Error(`Unknown operator: ${op}`);
|
|
989
|
+
}
|
|
990
|
+
};
|
|
991
|
+
}
|
|
992
|
+
return left;
|
|
993
|
+
}
|
|
994
|
+
parseAdditive() {
|
|
995
|
+
let left = this.parseMultiplicative();
|
|
996
|
+
while (this.current().type === TokenType.ARITHMETIC &&
|
|
997
|
+
(this.current().value === '+' || this.current().value === '-')) {
|
|
998
|
+
const op = this.consume(TokenType.ARITHMETIC).value;
|
|
999
|
+
const right = this.parseMultiplicative();
|
|
1000
|
+
const prevLeft = left;
|
|
1001
|
+
if (op === '+') {
|
|
1002
|
+
left = (context) => prevLeft(context) + right(context);
|
|
1003
|
+
}
|
|
1004
|
+
else {
|
|
1005
|
+
left = (context) => prevLeft(context) - right(context);
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
return left;
|
|
1009
|
+
}
|
|
1010
|
+
parseMultiplicative() {
|
|
1011
|
+
let left = this.parseUnary();
|
|
1012
|
+
while (this.current().type === TokenType.ARITHMETIC &&
|
|
1013
|
+
(this.current().value === '*' || this.current().value === '/' || this.current().value === '%')) {
|
|
1014
|
+
const op = this.consume(TokenType.ARITHMETIC).value;
|
|
1015
|
+
const right = this.parseUnary();
|
|
1016
|
+
const prevLeft = left;
|
|
1017
|
+
if (op === '*') {
|
|
1018
|
+
left = (context) => prevLeft(context) * right(context);
|
|
1019
|
+
}
|
|
1020
|
+
else if (op === '/') {
|
|
1021
|
+
left = (context) => prevLeft(context) / right(context);
|
|
1022
|
+
}
|
|
1023
|
+
else {
|
|
1024
|
+
left = (context) => prevLeft(context) % right(context);
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
return left;
|
|
1028
|
+
}
|
|
1029
|
+
parseUnary() {
|
|
1030
|
+
if (this.current().type === TokenType.NOT) {
|
|
1031
|
+
this.consume(TokenType.NOT);
|
|
1032
|
+
const expr = this.parseUnary();
|
|
1033
|
+
return (context) => !expr(context);
|
|
1034
|
+
}
|
|
1035
|
+
return this.parsePrimary();
|
|
1036
|
+
}
|
|
1037
|
+
parsePrimary() {
|
|
1038
|
+
const token = this.current();
|
|
1039
|
+
// Parentheses
|
|
1040
|
+
if (token.type === TokenType.PAREN_OPEN) {
|
|
1041
|
+
this.consume(TokenType.PAREN_OPEN);
|
|
1042
|
+
const expr = this.parseTernary();
|
|
1043
|
+
this.consume(TokenType.PAREN_CLOSE);
|
|
1044
|
+
return expr;
|
|
1045
|
+
}
|
|
1046
|
+
// Literals
|
|
1047
|
+
if (token.type === TokenType.STRING ||
|
|
1048
|
+
token.type === TokenType.NUMBER ||
|
|
1049
|
+
token.type === TokenType.BOOLEAN ||
|
|
1050
|
+
token.type === TokenType.NULL ||
|
|
1051
|
+
token.type === TokenType.UNDEFINED) {
|
|
1052
|
+
const value = token.value;
|
|
1053
|
+
this.pos++;
|
|
1054
|
+
return () => value;
|
|
1055
|
+
}
|
|
1056
|
+
// Property access (model.field, formState.prop, etc)
|
|
1057
|
+
if (token.type === TokenType.IDENTIFIER) {
|
|
1058
|
+
return this.parsePropertyAccess();
|
|
1059
|
+
}
|
|
1060
|
+
throw new Error(`Unexpected token: ${JSON.stringify(token)}`);
|
|
1061
|
+
}
|
|
1062
|
+
parsePropertyAccess() {
|
|
1063
|
+
const path = [];
|
|
1064
|
+
// Read first identifier
|
|
1065
|
+
path.push(this.consume(TokenType.IDENTIFIER).value);
|
|
1066
|
+
// Read rest of path (dots and brackets)
|
|
1067
|
+
while (this.current().type === TokenType.DOT || this.current().type === TokenType.BRACKET_OPEN) {
|
|
1068
|
+
if (this.current().type === TokenType.DOT) {
|
|
1069
|
+
this.consume(TokenType.DOT);
|
|
1070
|
+
path.push(this.consume(TokenType.IDENTIFIER).value);
|
|
1071
|
+
}
|
|
1072
|
+
else {
|
|
1073
|
+
this.consume(TokenType.BRACKET_OPEN);
|
|
1074
|
+
// bracket notation can contain expressions
|
|
1075
|
+
if (this.current().type === TokenType.STRING) {
|
|
1076
|
+
path.push(this.consume(TokenType.STRING).value);
|
|
1077
|
+
}
|
|
1078
|
+
else if (this.current().type === TokenType.NUMBER) {
|
|
1079
|
+
path.push(String(this.consume(TokenType.NUMBER).value));
|
|
1080
|
+
}
|
|
1081
|
+
else {
|
|
1082
|
+
// dynamic property access
|
|
1083
|
+
const expr = this.parseTernary();
|
|
1084
|
+
path.push(expr);
|
|
1085
|
+
}
|
|
1086
|
+
this.consume(TokenType.BRACKET_CLOSE);
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
return (context) => {
|
|
1090
|
+
let value = context;
|
|
1091
|
+
for (const segment of path) {
|
|
1092
|
+
if (value === null || value === undefined) {
|
|
1093
|
+
return undefined;
|
|
1094
|
+
}
|
|
1095
|
+
if (typeof segment === 'function') {
|
|
1096
|
+
value = value[segment(context)];
|
|
1097
|
+
}
|
|
1098
|
+
else {
|
|
1099
|
+
value = value[segment];
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
return value;
|
|
1103
|
+
};
|
|
1104
|
+
}
|
|
1105
|
+
current() {
|
|
1106
|
+
return this.tokens[this.pos];
|
|
1107
|
+
}
|
|
1108
|
+
consume(expectedType) {
|
|
1109
|
+
const token = this.current();
|
|
1110
|
+
if (token.type !== expectedType) {
|
|
1111
|
+
throw new Error(`Expected ${expectedType}, got ${token.type}`);
|
|
1112
|
+
}
|
|
1113
|
+
this.pos++;
|
|
1114
|
+
return token;
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
/**
|
|
1118
|
+
* Uses CSP-safe implementation
|
|
1119
|
+
*/
|
|
1120
|
+
function parseExpression(expression, argNames) {
|
|
1121
|
+
try {
|
|
1122
|
+
const tokenizer = new Tokenizer(expression);
|
|
1123
|
+
const tokens = tokenizer.tokenize();
|
|
1124
|
+
const parser = new ExpressionParser(tokens);
|
|
1125
|
+
const evaluator = parser.parse();
|
|
1126
|
+
// Return a function that maps the args to the context
|
|
1127
|
+
return (...args) => {
|
|
1128
|
+
const context = {};
|
|
1129
|
+
argNames.forEach((name, i) => {
|
|
1130
|
+
context[name] = args[i];
|
|
1131
|
+
});
|
|
1132
|
+
return evaluator(context);
|
|
1133
|
+
};
|
|
1134
|
+
}
|
|
1135
|
+
catch (error) {
|
|
1136
|
+
console.error('Expression parse error:', error);
|
|
1137
|
+
return undefined;
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
class FieldExpressionExtension extends FieldExpressionExtension$1 {
|
|
1142
|
+
_evalStringExpression(expression, argNames) {
|
|
1143
|
+
return parseExpression(expression, argNames);
|
|
1144
|
+
}
|
|
673
1145
|
}
|
|
674
1146
|
|
|
675
1147
|
class CoreExtension {
|
|
@@ -1756,7 +2228,22 @@ function withDefaultConfig(config) {
|
|
|
1756
2228
|
{ name: 'core', extension: new CoreExtension(config), priority: -250 },
|
|
1757
2229
|
{ name: 'field-validation', extension: new FieldValidationExtension(config), priority: -200 },
|
|
1758
2230
|
{ name: 'field-form', extension: new FieldFormExtension(), priority: -150 },
|
|
1759
|
-
{
|
|
2231
|
+
{
|
|
2232
|
+
name: 'field-expression',
|
|
2233
|
+
extension: new FieldExpressionExtension$1(),
|
|
2234
|
+
priority: -100,
|
|
2235
|
+
},
|
|
2236
|
+
],
|
|
2237
|
+
};
|
|
2238
|
+
}
|
|
2239
|
+
function withFormlyFieldExpression() {
|
|
2240
|
+
return {
|
|
2241
|
+
extensions: [
|
|
2242
|
+
{
|
|
2243
|
+
name: 'field-expression',
|
|
2244
|
+
extension: new FieldExpressionExtension(),
|
|
2245
|
+
priority: -100,
|
|
2246
|
+
},
|
|
1760
2247
|
],
|
|
1761
2248
|
};
|
|
1762
2249
|
}
|
|
@@ -2470,5 +2957,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImpo
|
|
|
2470
2957
|
* Generated bundle index. Do not edit.
|
|
2471
2958
|
*/
|
|
2472
2959
|
|
|
2473
|
-
export { FORMLY_CONFIG, FieldArrayType, FieldType, FieldWrapper, FormlyAttributes, FormlyConfig, FormlyField, FormlyForm, FormlyFormBuilder, FormlyModule, FormlyTemplate, FormlyValidationMessage, LegacyFormlyAttributes, LegacyFormlyField, LegacyFormlyForm, LegacyFormlyValidationMessage, provideFormlyConfig, provideFormlyCore, FormlyGroup as ɵFormlyGroup, LegacyFormlyTemplate as ɵFormlyTemplate, clone as ɵclone, defineHiddenProp as ɵdefineHiddenProp, getFieldValue as ɵgetFieldValue, hasKey as ɵhasKey, observe as ɵobserve, reverseDeepMerge as ɵreverseDeepMerge };
|
|
2960
|
+
export { FORMLY_CONFIG, FieldArrayType, FieldType, FieldWrapper, FormlyAttributes, FormlyConfig, FormlyField, FormlyForm, FormlyFormBuilder, FormlyModule, FormlyTemplate, FormlyValidationMessage, LegacyFormlyAttributes, LegacyFormlyField, LegacyFormlyForm, LegacyFormlyValidationMessage, provideFormlyConfig, provideFormlyCore, withFormlyFieldExpression, FormlyGroup as ɵFormlyGroup, LegacyFormlyTemplate as ɵFormlyTemplate, clone as ɵclone, defineHiddenProp as ɵdefineHiddenProp, getFieldValue as ɵgetFieldValue, hasKey as ɵhasKey, observe as ɵobserve, reverseDeepMerge as ɵreverseDeepMerge };
|
|
2474
2961
|
//# sourceMappingURL=ngx-formly-core.mjs.map
|