@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.
@@ -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
- function evalStringExpression(expression, argNames) {
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 = evalStringExpression(expr, ['model', 'formState', 'field']);
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
- { name: 'field-expression', extension: new FieldExpressionExtension(), priority: -100 },
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