@ngx-formly/core 7.0.0 → 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/README.md +12 -12
- package/esm2022/json-schema/formly-json-schema.service.mjs +102 -2
- package/esm2022/lib/components/formly.form.mjs +10 -4
- package/esm2022/lib/components/formly.template.mjs +11 -3
- package/esm2022/lib/core.config.mjs +35 -5
- package/esm2022/lib/core.mjs +3 -3
- package/esm2022/lib/core.module.mjs +9 -9
- 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/esm2022/preset/preset-substitution.extension.mjs +2 -2
- package/fesm2022/ngx-formly-core-json-schema.mjs +101 -1
- package/fesm2022/ngx-formly-core-json-schema.mjs.map +1 -1
- package/fesm2022/ngx-formly-core-preset.mjs +1 -1
- package/fesm2022/ngx-formly-core-preset.mjs.map +1 -1
- package/fesm2022/ngx-formly-core.mjs +532 -17
- package/fesm2022/ngx-formly-core.mjs.map +1 -1
- package/json-schema/formly-json-schema.service.d.ts +3 -0
- package/lib/components/formly.form.d.ts +3 -2
- package/lib/components/formly.template.d.ts +5 -1
- package/lib/core.config.d.ts +1 -0
- package/lib/core.d.ts +3 -3
- package/lib/core.module.d.ts +1 -1
- 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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { Type, TemplateRef, ComponentRef, ChangeDetectorRef, ɵNoopNgZone as _NoopNgZone, VERSION, Input, ViewChildren, Directive, ChangeDetectionStrategy, Component, Injectable, ViewContainerRef, ViewChild, Optional, InjectionToken, Inject, EventEmitter, ContentChildren, Output, NgModule } from '@angular/core';
|
|
2
|
+
import { Type, TemplateRef, ComponentRef, ChangeDetectorRef, ɵNoopNgZone as _NoopNgZone, VERSION, Input, ViewChildren, Directive, ChangeDetectionStrategy, Component, Injectable, ViewContainerRef, ViewChild, Optional, InjectionToken, inject, Inject, EventEmitter, ContentChildren, Output, NgModule } from '@angular/core';
|
|
3
3
|
import { isObservable, Observable, Subject, of, merge } from 'rxjs';
|
|
4
4
|
import * as i2 from '@angular/forms';
|
|
5
5
|
import { AbstractControl, UntypedFormArray, UntypedFormGroup, FormControl, UntypedFormControl, Validators, NgControl } from '@angular/forms';
|
|
@@ -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 {
|
|
@@ -1405,15 +1877,23 @@ class FormlyTemplate {
|
|
|
1405
1877
|
this.name = this.name || 'formly-group';
|
|
1406
1878
|
}
|
|
1407
1879
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FormlyTemplate, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1408
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.13", type: FormlyTemplate, selector: "[formlyTemplate]", inputs: { name: ["formlyTemplate", "name"] }, usesOnChanges: true, ngImport: i0 }); }
|
|
1880
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.13", type: FormlyTemplate, isStandalone: true, selector: "[formlyTemplate]", inputs: { name: ["formlyTemplate", "name"] }, usesOnChanges: true, ngImport: i0 }); }
|
|
1409
1881
|
}
|
|
1410
1882
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FormlyTemplate, decorators: [{
|
|
1411
1883
|
type: Directive,
|
|
1412
|
-
args: [{ selector: '[formlyTemplate]' }]
|
|
1884
|
+
args: [{ selector: '[formlyTemplate]', standalone: true }]
|
|
1413
1885
|
}], ctorParameters: () => [{ type: i0.TemplateRef }], propDecorators: { name: [{
|
|
1414
1886
|
type: Input,
|
|
1415
1887
|
args: ['formlyTemplate']
|
|
1416
1888
|
}] } });
|
|
1889
|
+
class LegacyFormlyTemplate extends FormlyTemplate {
|
|
1890
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: LegacyFormlyTemplate, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1891
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.13", type: LegacyFormlyTemplate, selector: "[formlyTemplate]", usesInheritance: true, ngImport: i0 }); }
|
|
1892
|
+
}
|
|
1893
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: LegacyFormlyTemplate, decorators: [{
|
|
1894
|
+
type: Directive,
|
|
1895
|
+
args: [{ selector: '[formlyTemplate]', standalone: false }]
|
|
1896
|
+
}] });
|
|
1417
1897
|
// workarround for https://github.com/angular/angular/issues/43227#issuecomment-904173738
|
|
1418
1898
|
class FormlyFieldTemplates {
|
|
1419
1899
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FormlyFieldTemplates, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
@@ -1748,7 +2228,22 @@ function withDefaultConfig(config) {
|
|
|
1748
2228
|
{ name: 'core', extension: new CoreExtension(config), priority: -250 },
|
|
1749
2229
|
{ name: 'field-validation', extension: new FieldValidationExtension(config), priority: -200 },
|
|
1750
2230
|
{ name: 'field-form', extension: new FieldFormExtension(), priority: -150 },
|
|
1751
|
-
{
|
|
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
|
+
},
|
|
1752
2247
|
],
|
|
1753
2248
|
};
|
|
1754
2249
|
}
|
|
@@ -1759,7 +2254,21 @@ const provideFormlyCore = (configs = []) => {
|
|
|
1759
2254
|
];
|
|
1760
2255
|
};
|
|
1761
2256
|
const provideFormlyConfig = (configs = []) => {
|
|
1762
|
-
return
|
|
2257
|
+
return {
|
|
2258
|
+
provide: FORMLY_CONFIG,
|
|
2259
|
+
multi: true,
|
|
2260
|
+
useFactory: () => {
|
|
2261
|
+
const currentConfig = inject(FORMLY_CONFIG, {
|
|
2262
|
+
skipSelf: true,
|
|
2263
|
+
optional: true,
|
|
2264
|
+
});
|
|
2265
|
+
if (currentConfig) {
|
|
2266
|
+
currentConfig.push(configs);
|
|
2267
|
+
return currentConfig;
|
|
2268
|
+
}
|
|
2269
|
+
return configs;
|
|
2270
|
+
},
|
|
2271
|
+
};
|
|
1763
2272
|
};
|
|
1764
2273
|
|
|
1765
2274
|
class FormlyFormBuilder {
|
|
@@ -1991,8 +2500,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImpo
|
|
|
1991
2500
|
args: [FormlyTemplate]
|
|
1992
2501
|
}] } });
|
|
1993
2502
|
class LegacyFormlyForm extends FormlyForm {
|
|
2503
|
+
set templates(templates) {
|
|
2504
|
+
this.fieldTemplates.templates = templates;
|
|
2505
|
+
}
|
|
1994
2506
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: LegacyFormlyForm, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
1995
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: LegacyFormlyForm, selector: "formly-form", providers: [FormlyFormBuilder, FormlyFieldTemplates], usesInheritance: true, ngImport: i0, template: '<formly-field [field]="field"></formly-field>', isInline: true, dependencies: [{ kind: "component", type: LegacyFormlyField, selector: "formly-field" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
2507
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: LegacyFormlyForm, selector: "formly-form", providers: [FormlyFormBuilder, FormlyFieldTemplates], queries: [{ propertyName: "templates", predicate: LegacyFormlyTemplate }], usesInheritance: true, ngImport: i0, template: '<formly-field [field]="field"></formly-field>', isInline: true, dependencies: [{ kind: "component", type: LegacyFormlyField, selector: "formly-field" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
1996
2508
|
}
|
|
1997
2509
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: LegacyFormlyForm, decorators: [{
|
|
1998
2510
|
type: Component,
|
|
@@ -2003,7 +2515,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImpo
|
|
|
2003
2515
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
2004
2516
|
standalone: false,
|
|
2005
2517
|
}]
|
|
2006
|
-
}]
|
|
2518
|
+
}], propDecorators: { templates: [{
|
|
2519
|
+
type: ContentChildren,
|
|
2520
|
+
args: [LegacyFormlyTemplate]
|
|
2521
|
+
}] } });
|
|
2007
2522
|
|
|
2008
2523
|
/**
|
|
2009
2524
|
* Allow to link the `field` HTML attributes (`id`, `name` ...) and Event attributes (`focus`, `blur` ...) to an element in the DOM.
|
|
@@ -2379,7 +2894,7 @@ class FormlyModule {
|
|
|
2379
2894
|
ngModule: FormlyModule,
|
|
2380
2895
|
providers: [
|
|
2381
2896
|
{ provide: FORMLY_CONFIG, multi: true, useFactory: withDefaultConfig, deps: [FormlyConfig] },
|
|
2382
|
-
|
|
2897
|
+
provideFormlyConfig(config),
|
|
2383
2898
|
FormlyConfig,
|
|
2384
2899
|
FormlyFormBuilder,
|
|
2385
2900
|
],
|
|
@@ -2390,19 +2905,19 @@ class FormlyModule {
|
|
|
2390
2905
|
ngModule: FormlyModule,
|
|
2391
2906
|
providers: [
|
|
2392
2907
|
{ provide: FORMLY_CONFIG, multi: true, useFactory: withDefaultConfig, deps: [FormlyConfig] },
|
|
2393
|
-
|
|
2908
|
+
provideFormlyConfig(config),
|
|
2394
2909
|
FormlyFormBuilder,
|
|
2395
2910
|
],
|
|
2396
2911
|
};
|
|
2397
2912
|
}
|
|
2398
2913
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FormlyModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
|
|
2399
|
-
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "18.2.13", ngImport: i0, type: FormlyModule, declarations: [
|
|
2914
|
+
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "18.2.13", ngImport: i0, type: FormlyModule, declarations: [LegacyFormlyTemplate,
|
|
2400
2915
|
LegacyFormlyForm,
|
|
2401
2916
|
LegacyFormlyField,
|
|
2402
2917
|
LegacyFormlyAttributes,
|
|
2403
2918
|
LegacyFormlyValidationMessage,
|
|
2404
2919
|
FormlyGroup,
|
|
2405
|
-
FormlyTemplateType], imports: [CommonModule], exports: [
|
|
2920
|
+
FormlyTemplateType], imports: [CommonModule], exports: [LegacyFormlyTemplate,
|
|
2406
2921
|
LegacyFormlyForm,
|
|
2407
2922
|
LegacyFormlyField,
|
|
2408
2923
|
LegacyFormlyAttributes,
|
|
@@ -2414,7 +2929,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImpo
|
|
|
2414
2929
|
type: NgModule,
|
|
2415
2930
|
args: [{
|
|
2416
2931
|
declarations: [
|
|
2417
|
-
|
|
2932
|
+
LegacyFormlyTemplate,
|
|
2418
2933
|
LegacyFormlyForm,
|
|
2419
2934
|
LegacyFormlyField,
|
|
2420
2935
|
LegacyFormlyAttributes,
|
|
@@ -2423,7 +2938,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImpo
|
|
|
2423
2938
|
FormlyTemplateType,
|
|
2424
2939
|
],
|
|
2425
2940
|
exports: [
|
|
2426
|
-
|
|
2941
|
+
LegacyFormlyTemplate,
|
|
2427
2942
|
LegacyFormlyForm,
|
|
2428
2943
|
LegacyFormlyField,
|
|
2429
2944
|
LegacyFormlyAttributes,
|
|
@@ -2442,5 +2957,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImpo
|
|
|
2442
2957
|
* Generated bundle index. Do not edit.
|
|
2443
2958
|
*/
|
|
2444
2959
|
|
|
2445
|
-
export { FORMLY_CONFIG, FieldArrayType, FieldType, FieldWrapper, FormlyAttributes, FormlyConfig, FormlyField, FormlyForm, FormlyFormBuilder, FormlyModule, FormlyValidationMessage, LegacyFormlyAttributes, LegacyFormlyField, LegacyFormlyForm, LegacyFormlyValidationMessage, provideFormlyConfig, provideFormlyCore, FormlyGroup as ɵFormlyGroup,
|
|
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 };
|
|
2446
2961
|
//# sourceMappingURL=ngx-formly-core.mjs.map
|