@sapui5/sap.fe.base 1.143.0 → 1.145.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.
@@ -1,10 +1,17 @@
1
1
  import type {
2
2
  ActionParameter,
3
+ AddAnnotationExpression,
4
+ AddConditionalExpression,
3
5
  AndAnnotationExpression,
4
6
  AndConditionalExpression,
5
7
  ApplyAnnotationExpression,
6
8
  Boolean,
7
9
  ConditionalCheckOrValue,
10
+ ConvertedMetadata,
11
+ DivAnnotationExpression,
12
+ DivByAnnotationExpression,
13
+ DivByConditionalExpression,
14
+ DivConditionalExpression,
8
15
  EntitySet,
9
16
  EntityType,
10
17
  EqAnnotationExpression,
@@ -13,14 +20,24 @@ import type {
13
20
  GeConditionalExpression,
14
21
  GtAnnotationExpression,
15
22
  GtConditionalExpression,
23
+ HasAnnotationExpression,
24
+ HasConditionalExpression,
16
25
  IfAnnotationExpression,
17
26
  IfAnnotationExpressionValue,
27
+ InAnnotationExpression,
28
+ InConditionalExpression,
18
29
  LeAnnotationExpression,
19
30
  LeConditionalExpression,
20
31
  LtAnnotationExpression,
21
32
  LtConditionalExpression,
33
+ ModAnnotationExpression,
34
+ ModConditionalExpression,
35
+ MulAnnotationExpression,
36
+ MulConditionalExpression,
22
37
  NeAnnotationExpression,
23
38
  NeConditionalExpression,
39
+ NegAnnotationExpression,
40
+ NegConditionalExpression,
24
41
  NotAnnotationExpression,
25
42
  NotConditionalExpression,
26
43
  OrAnnotationExpression,
@@ -29,6 +46,8 @@ import type {
29
46
  PathConditionExpression,
30
47
  Property,
31
48
  PropertyAnnotationValue,
49
+ SubAnnotationExpression,
50
+ SubConditionalExpression,
32
51
  ConstantExpression as VocabConstantExpression
33
52
  } from "@sap-ux/vocabularies-types";
34
53
  import { CommonAnnotationTerms } from "@sap-ux/vocabularies-types/vocabularies/Common";
@@ -37,11 +56,22 @@ import type { BindingInfoHolder } from "sap/fe/base/ClassSupport";
37
56
 
38
57
  export type PrimitiveType = string | number | bigint | boolean | object | null | undefined;
39
58
  type DefinedPrimitiveType = string | number | bigint | boolean | object;
59
+ /**
60
+ * Returns either the value of the object or the object itself if it is not a primitive object.
61
+ */
40
62
  type PrimitiveTypeCast<P> = P extends String ? string : P extends Boolean ? boolean : P extends Number ? number : P;
41
63
  type BaseExpression<_T> = {
42
64
  _type: string;
43
65
  };
44
66
 
67
+ /**
68
+ * Collection of functions to support the manipulation of UI5 bindings
69
+ * @public
70
+ * @namespace
71
+ * @ui5-experimental-since 1.145.0
72
+ */
73
+ export const bindingToolkitNamespace = "sap.fe.base.BindingToolkit";
74
+
45
75
  export type FormatOptions = Partial<{
46
76
  customUnits: Record<string, object>;
47
77
  customCurrencies: Record<string, object>;
@@ -157,7 +187,7 @@ export type EmbeddedUI5ExpressionBindingExpression<T> = BaseExpression<T> & {
157
187
  value: string;
158
188
  };
159
189
 
160
- export type ComparisonOperator = "===" | "!==" | ">=" | ">" | "<=" | "<";
190
+ export type ComparisonOperator = "===" | "!==" | ">=" | ">" | "<=" | "<" | "Has" | "In";
161
191
  export type ComparisonExpression = BaseExpression<boolean> & {
162
192
  _type: "Comparison";
163
193
  operator: ComparisonOperator;
@@ -165,6 +195,13 @@ export type ComparisonExpression = BaseExpression<boolean> & {
165
195
  operand2: BindingToolkitExpression<unknown>;
166
196
  };
167
197
 
198
+ export type ArithmeticOperator = "+" | "-" | "*" | "/" | "%" | "Neg" | "Floor";
199
+ export type ArithmeticExpression = BaseExpression<number> & {
200
+ _type: "Arithmetic";
201
+ operator: ArithmeticOperator;
202
+ operand1: BindingToolkitExpression<unknown>;
203
+ operand2: BindingToolkitExpression<unknown>;
204
+ };
168
205
  export type IfElseExpression<T> = BaseExpression<T> & {
169
206
  _type: "IfElse";
170
207
  condition: BindingToolkitExpression<boolean>;
@@ -174,10 +211,14 @@ export type IfElseExpression<T> = BaseExpression<T> & {
174
211
  export function isProperty(serviceObject: unknown): serviceObject is Property {
175
212
  return (serviceObject as Property)?._type === "Property";
176
213
  }
214
+ function isActionParameter(serviceObject: unknown): serviceObject is ActionParameter {
215
+ return (serviceObject as ActionParameter)?._type === "ActionParameter";
216
+ }
177
217
 
178
218
  /**
179
- * An expression that evaluates to type T.
180
- * @typedef BindingToolkitExpression
219
+ * A binding expression that evaluates to type T.
220
+ * @public
221
+ * @ui5-experimental-since 1.145.0
181
222
  */
182
223
  export type BindingToolkitExpression<T> =
183
224
  | UnresolvablePathExpression
@@ -195,7 +236,8 @@ export type BindingToolkitExpression<T> =
195
236
  | FormatterExpression<T>
196
237
  | ComplexTypeExpression<T>
197
238
  | ReferenceExpression
198
- | FunctionExpression<T>;
239
+ | FunctionExpression<T>
240
+ | ArithmeticExpression;
199
241
 
200
242
  type TypeMappingDefinition = {
201
243
  type: string;
@@ -293,7 +335,12 @@ export function _checkExpressionsAreEqual<T>(a: BindingToolkitExpression<T>, b:
293
335
  case "EmbeddedBinding":
294
336
  case "EmbeddedExpressionBinding":
295
337
  return a.value === (b as ConstantExpression<T>).value;
296
-
338
+ case "Arithmetic":
339
+ return (
340
+ a.operator === (b as ArithmeticExpression).operator &&
341
+ _checkExpressionsAreEqual(a.operand1, (b as ArithmeticExpression).operand1) &&
342
+ _checkExpressionsAreEqual(a.operand2, (b as ArithmeticExpression).operand2)
343
+ );
297
344
  case "Not":
298
345
  return _checkExpressionsAreEqual(a.operand, (b as NotExpression).operand);
299
346
  case "Truthy":
@@ -499,6 +546,138 @@ export function or(...operands: ExpressionOrPrimitive<boolean>[]): BindingToolki
499
546
  }
500
547
  }
501
548
 
549
+ export function add<T extends PrimitiveType>(
550
+ leftOperand: ExpressionOrPrimitive<T>,
551
+ rightOperand: ExpressionOrPrimitive<T>
552
+ ): BindingToolkitExpression<number> {
553
+ return arithmetic("+", leftOperand, rightOperand);
554
+ }
555
+
556
+ export function subtract<T extends PrimitiveType>(
557
+ leftOperand: ExpressionOrPrimitive<T>,
558
+ rightOperand: ExpressionOrPrimitive<T>
559
+ ): BindingToolkitExpression<number> {
560
+ return arithmetic("-", leftOperand, rightOperand);
561
+ }
562
+
563
+ export function multiply<T extends PrimitiveType>(
564
+ leftOperand: ExpressionOrPrimitive<T>,
565
+ rightOperand: ExpressionOrPrimitive<T>
566
+ ): BindingToolkitExpression<number> {
567
+ return arithmetic("*", leftOperand, rightOperand);
568
+ }
569
+
570
+ export function divide<T extends PrimitiveType>(
571
+ leftOperand: ExpressionOrPrimitive<T>,
572
+ rightOperand: ExpressionOrPrimitive<T>
573
+ ): BindingToolkitExpression<number> {
574
+ return arithmetic("/", leftOperand, rightOperand);
575
+ }
576
+
577
+ export function modulo<T extends PrimitiveType>(
578
+ leftOperand: ExpressionOrPrimitive<T>,
579
+ rightOperand: ExpressionOrPrimitive<T>
580
+ ): BindingToolkitExpression<number> {
581
+ return arithmetic("%", leftOperand, rightOperand);
582
+ }
583
+
584
+ export function negate<T extends PrimitiveType>(operand: ExpressionOrPrimitive<T>): BindingToolkitExpression<number> {
585
+ return arithmetic("Neg", operand, null);
586
+ }
587
+
588
+ export function floor<T extends PrimitiveType>(operand: ExpressionOrPrimitive<T>): BindingToolkitExpression<number> {
589
+ return arithmetic("Floor", operand, null);
590
+ }
591
+
592
+ /**
593
+ * Arithmetic expression.
594
+ * @param operator
595
+ * @param leftOperand
596
+ * @param rightOperand
597
+ * @returns Expression evaluating to number
598
+ */
599
+ export function arithmetic<T extends PrimitiveType>(
600
+ operator: ArithmeticOperator,
601
+ leftOperand: ExpressionOrPrimitive<T>,
602
+ rightOperand: ExpressionOrPrimitive<T>
603
+ ): BindingToolkitExpression<number> {
604
+ const leftExpression = wrapPrimitive(leftOperand);
605
+ const rightExpression = wrapPrimitive(rightOperand);
606
+ if (hasUnresolvableExpression(leftExpression, rightExpression)) {
607
+ return unresolvableExpression;
608
+ }
609
+ if (isConstant(leftExpression) && isConstant(rightExpression)) {
610
+ switch (operator) {
611
+ case "+":
612
+ if (leftExpression.value === null || leftExpression.value === undefined) {
613
+ return constant(rightExpression.value as number);
614
+ } else if (rightExpression.value === null || rightExpression.value === undefined) {
615
+ return constant(leftExpression.value as number);
616
+ }
617
+ const sum = (leftExpression.value as number) + (rightExpression.value as number);
618
+ return constant(sum);
619
+ case "-":
620
+ if (leftExpression.value === null || leftExpression.value === undefined) {
621
+ return constant(rightExpression.value as number);
622
+ } else if (rightExpression.value === null || rightExpression.value === undefined) {
623
+ return constant(leftExpression.value as number);
624
+ }
625
+ const diff = (leftExpression.value as number) - (rightExpression.value as number);
626
+ return constant(diff);
627
+ case "/":
628
+ if (
629
+ leftExpression.value === null ||
630
+ leftExpression.value === undefined ||
631
+ rightExpression.value === null ||
632
+ rightExpression.value === undefined
633
+ ) {
634
+ return constant(0);
635
+ }
636
+ const div = (leftExpression.value as number) / (rightExpression.value as number);
637
+ return constant(div);
638
+ case "Neg":
639
+ if (leftExpression.value === null || leftExpression.value === undefined) {
640
+ return constant(0);
641
+ }
642
+ return constant(-leftExpression.value);
643
+ case "%":
644
+ if (
645
+ leftExpression.value === null ||
646
+ leftExpression.value === undefined ||
647
+ rightExpression.value === null ||
648
+ rightExpression.value === undefined
649
+ ) {
650
+ return constant(0);
651
+ }
652
+ const mod = (leftExpression.value as number) % (rightExpression.value as number);
653
+ return constant(mod);
654
+ case "Floor":
655
+ if (leftExpression.value === null || leftExpression.value === undefined) {
656
+ return constant(0);
657
+ }
658
+ return constant(Math.floor(leftExpression.value as number));
659
+ case "*":
660
+ if (
661
+ leftExpression.value === null ||
662
+ leftExpression.value === undefined ||
663
+ rightExpression.value === null ||
664
+ rightExpression.value === undefined
665
+ ) {
666
+ return constant(0);
667
+ }
668
+ const mult = (leftExpression.value as number) * (rightExpression.value as number);
669
+ return constant(mult);
670
+ }
671
+ } else {
672
+ return {
673
+ _type: "Arithmetic",
674
+ operator: operator,
675
+ operand1: leftExpression,
676
+ operand2: rightExpression
677
+ };
678
+ }
679
+ }
680
+
502
681
  /**
503
682
  * Logical `not` operator.
504
683
  * @param operand The expression to reverse
@@ -539,6 +718,11 @@ export function not(operand: ExpressionOrPrimitive<boolean>): BindingToolkitExpr
539
718
  return { ...operand, operator: "<=" };
540
719
  case ">=":
541
720
  return { ...operand, operator: "<" };
721
+ default:
722
+ return {
723
+ _type: "Not",
724
+ operand: operand
725
+ };
542
726
  }
543
727
  } else if (operand._type === "Not") {
544
728
  return operand.operand;
@@ -849,6 +1033,15 @@ type ComplexAnnotationExpression<P> =
849
1033
  | GeAnnotationExpression<P>
850
1034
  | LeAnnotationExpression<P>
851
1035
  | LtAnnotationExpression<P>
1036
+ | HasAnnotationExpression<P>
1037
+ | InAnnotationExpression<P>
1038
+ | AddAnnotationExpression<P>
1039
+ | SubAnnotationExpression<P>
1040
+ | MulAnnotationExpression<P>
1041
+ | DivAnnotationExpression<P>
1042
+ | ModAnnotationExpression<P>
1043
+ | DivByAnnotationExpression<P>
1044
+ | NegAnnotationExpression<P>
852
1045
  | VocabConstantExpression<P>;
853
1046
 
854
1047
  function isPrimitiveObject(objectType: object): boolean {
@@ -871,6 +1064,11 @@ function isComplexAnnotationExpression<T>(annotationValue: PropertyAnnotationVal
871
1064
  return typeof annotationValue === "object" && !isPrimitiveObject(annotationValue as object);
872
1065
  }
873
1066
 
1067
+ let CONVERTER_ROOT_SYMBOL: symbol | undefined;
1068
+ export function setConverterRootSymbol(converterRootSymbol: symbol): void {
1069
+ CONVERTER_ROOT_SYMBOL = converterRootSymbol;
1070
+ }
1071
+
874
1072
  /**
875
1073
  * Generate the corresponding annotationValue for a given annotation annotationValue.
876
1074
  * @template T The target type
@@ -886,6 +1084,11 @@ export function getExpressionFromAnnotation<T extends PrimitiveType>(
886
1084
  defaultValue?: ExpressionOrPrimitive<T>,
887
1085
  pathVisitor?: Function
888
1086
  ): BindingToolkitExpression<PrimitiveTypeCast<T>> {
1087
+ let converterRoot: ConvertedMetadata | undefined;
1088
+ if (CONVERTER_ROOT_SYMBOL) {
1089
+ converterRoot = (annotationValue?.[CONVERTER_ROOT_SYMBOL as never] as unknown as Function)?.() as ConvertedMetadata | undefined;
1090
+ }
1091
+
889
1092
  if (annotationValue === undefined) {
890
1093
  return wrapPrimitive(defaultValue as PrimitiveTypeCast<T>);
891
1094
  }
@@ -898,55 +1101,106 @@ export function getExpressionFromAnnotation<T extends PrimitiveType>(
898
1101
  case "Path":
899
1102
  return pathInModel(annotationValue.path, undefined, visitedNavigationPaths, pathVisitor);
900
1103
  case "If":
901
- return annotationIfExpression(annotationValue.$If, visitedNavigationPaths, pathVisitor) as BindingToolkitExpression<
902
- PrimitiveTypeCast<T>
903
- >;
1104
+ return annotationIfExpression(
1105
+ annotationValue.$If,
1106
+ visitedNavigationPaths,
1107
+ pathVisitor,
1108
+ converterRoot
1109
+ ) as BindingToolkitExpression<PrimitiveTypeCast<T>>;
904
1110
  case "Not":
905
- return not(parseAnnotationCondition(annotationValue.$Not, visitedNavigationPaths, pathVisitor)) as BindingToolkitExpression<
906
- PrimitiveTypeCast<T>
907
- >;
1111
+ return not(
1112
+ parseAnnotationCondition(annotationValue.$Not, visitedNavigationPaths, pathVisitor, converterRoot)
1113
+ ) as BindingToolkitExpression<PrimitiveTypeCast<T>>;
908
1114
  case "Eq":
909
1115
  return equal(
910
- parseAnnotationCondition(annotationValue.$Eq[0], visitedNavigationPaths, pathVisitor),
911
- parseAnnotationCondition(annotationValue.$Eq[1], visitedNavigationPaths, pathVisitor)
1116
+ parseAnnotationCondition(annotationValue.$Eq[0], visitedNavigationPaths, pathVisitor, converterRoot),
1117
+ parseAnnotationCondition(annotationValue.$Eq[1], visitedNavigationPaths, pathVisitor, converterRoot)
912
1118
  ) as BindingToolkitExpression<PrimitiveTypeCast<T>>;
913
1119
  case "Ne":
914
1120
  return notEqual(
915
- parseAnnotationCondition(annotationValue.$Ne[0], visitedNavigationPaths, pathVisitor),
916
- parseAnnotationCondition(annotationValue.$Ne[1], visitedNavigationPaths, pathVisitor)
1121
+ parseAnnotationCondition(annotationValue.$Ne[0], visitedNavigationPaths, pathVisitor, converterRoot),
1122
+ parseAnnotationCondition(annotationValue.$Ne[1], visitedNavigationPaths, pathVisitor, converterRoot)
917
1123
  ) as BindingToolkitExpression<PrimitiveTypeCast<T>>;
918
1124
  case "Gt":
919
1125
  return greaterThan(
920
- parseAnnotationCondition(annotationValue.$Gt[0], visitedNavigationPaths, pathVisitor),
921
- parseAnnotationCondition(annotationValue.$Gt[1], visitedNavigationPaths, pathVisitor)
1126
+ parseAnnotationCondition(annotationValue.$Gt[0], visitedNavigationPaths, pathVisitor, converterRoot),
1127
+ parseAnnotationCondition(annotationValue.$Gt[1], visitedNavigationPaths, pathVisitor, converterRoot)
922
1128
  ) as BindingToolkitExpression<PrimitiveTypeCast<T>>;
923
1129
  case "Ge":
924
1130
  return greaterOrEqual(
925
- parseAnnotationCondition(annotationValue.$Ge[0], visitedNavigationPaths, pathVisitor),
926
- parseAnnotationCondition(annotationValue.$Ge[1], visitedNavigationPaths, pathVisitor)
1131
+ parseAnnotationCondition(annotationValue.$Ge[0], visitedNavigationPaths, pathVisitor, converterRoot),
1132
+ parseAnnotationCondition(annotationValue.$Ge[1], visitedNavigationPaths, pathVisitor, converterRoot)
927
1133
  ) as BindingToolkitExpression<PrimitiveTypeCast<T>>;
928
1134
  case "Lt":
929
1135
  return lessThan(
930
- parseAnnotationCondition(annotationValue.$Lt[0], visitedNavigationPaths, pathVisitor),
931
- parseAnnotationCondition(annotationValue.$Lt[1], visitedNavigationPaths, pathVisitor)
1136
+ parseAnnotationCondition(annotationValue.$Lt[0], visitedNavigationPaths, pathVisitor, converterRoot),
1137
+ parseAnnotationCondition(annotationValue.$Lt[1], visitedNavigationPaths, pathVisitor, converterRoot)
932
1138
  ) as BindingToolkitExpression<PrimitiveTypeCast<T>>;
933
1139
  case "Le":
934
1140
  return lessOrEqual(
935
- parseAnnotationCondition(annotationValue.$Le[0], visitedNavigationPaths, pathVisitor),
936
- parseAnnotationCondition(annotationValue.$Le[1], visitedNavigationPaths, pathVisitor)
1141
+ parseAnnotationCondition(annotationValue.$Le[0], visitedNavigationPaths, pathVisitor, converterRoot),
1142
+ parseAnnotationCondition(annotationValue.$Le[1], visitedNavigationPaths, pathVisitor, converterRoot)
937
1143
  ) as BindingToolkitExpression<PrimitiveTypeCast<T>>;
938
1144
  case "Or":
939
1145
  return or(
940
1146
  ...annotationValue.$Or.map(function (orCondition) {
941
- return parseAnnotationCondition<boolean>(orCondition, visitedNavigationPaths, pathVisitor);
1147
+ return parseAnnotationCondition<boolean>(orCondition, visitedNavigationPaths, pathVisitor, converterRoot);
942
1148
  })
943
1149
  ) as BindingToolkitExpression<PrimitiveTypeCast<T>>;
944
1150
  case "And":
945
1151
  return and(
946
1152
  ...annotationValue.$And.map(function (andCondition) {
947
- return parseAnnotationCondition<boolean>(andCondition, visitedNavigationPaths, pathVisitor);
1153
+ return parseAnnotationCondition<boolean>(andCondition, visitedNavigationPaths, pathVisitor, converterRoot);
948
1154
  })
949
1155
  ) as BindingToolkitExpression<PrimitiveTypeCast<T>>;
1156
+ case "In":
1157
+ return comparison(
1158
+ "In",
1159
+ parseAnnotationCondition(annotationValue.$In[0], visitedNavigationPaths, pathVisitor, converterRoot),
1160
+ parseAnnotationCondition(annotationValue.$In[1], visitedNavigationPaths, pathVisitor, converterRoot)
1161
+ ) as BindingToolkitExpression<PrimitiveTypeCast<T>>;
1162
+ case "Has":
1163
+ return comparison(
1164
+ "Has",
1165
+ parseAnnotationCondition(annotationValue.$Has[0], visitedNavigationPaths, pathVisitor, converterRoot),
1166
+ parseAnnotationCondition(annotationValue.$Has[1], visitedNavigationPaths, pathVisitor, converterRoot)
1167
+ ) as BindingToolkitExpression<PrimitiveTypeCast<T>>;
1168
+ case "Add":
1169
+ return add(
1170
+ parseAnnotationCondition(annotationValue.$Add[0], visitedNavigationPaths, pathVisitor, converterRoot),
1171
+ parseAnnotationCondition(annotationValue.$Add[1], visitedNavigationPaths, pathVisitor, converterRoot)
1172
+ ) as BindingToolkitExpression<PrimitiveTypeCast<T>>;
1173
+ case "Sub":
1174
+ return subtract(
1175
+ parseAnnotationCondition(annotationValue.$Sub[0], visitedNavigationPaths, pathVisitor, converterRoot),
1176
+ parseAnnotationCondition(annotationValue.$Sub[1], visitedNavigationPaths, pathVisitor, converterRoot)
1177
+ ) as BindingToolkitExpression<PrimitiveTypeCast<T>>;
1178
+ case "Mul":
1179
+ return multiply(
1180
+ parseAnnotationCondition(annotationValue.$Mul[0], visitedNavigationPaths, pathVisitor, converterRoot),
1181
+ parseAnnotationCondition(annotationValue.$Mul[1], visitedNavigationPaths, pathVisitor, converterRoot)
1182
+ ) as BindingToolkitExpression<PrimitiveTypeCast<T>>;
1183
+ case "Div":
1184
+ return floor(
1185
+ divide(
1186
+ parseAnnotationCondition(annotationValue.$Div[0], visitedNavigationPaths, pathVisitor, converterRoot),
1187
+ parseAnnotationCondition(annotationValue.$Div[1], visitedNavigationPaths, pathVisitor, converterRoot)
1188
+ )
1189
+ ) as BindingToolkitExpression<PrimitiveTypeCast<T>>;
1190
+ case "DivBy":
1191
+ return divide(
1192
+ parseAnnotationCondition(annotationValue.$DivBy[0], visitedNavigationPaths, pathVisitor, converterRoot),
1193
+ parseAnnotationCondition(annotationValue.$DivBy[1], visitedNavigationPaths, pathVisitor, converterRoot)
1194
+ ) as BindingToolkitExpression<PrimitiveTypeCast<T>>;
1195
+ case "Mod":
1196
+ return modulo(
1197
+ parseAnnotationCondition(annotationValue.$Mod[0], visitedNavigationPaths, pathVisitor, converterRoot),
1198
+ parseAnnotationCondition(annotationValue.$Mod[1], visitedNavigationPaths, pathVisitor, converterRoot)
1199
+ ) as BindingToolkitExpression<PrimitiveTypeCast<T>>;
1200
+ case "Neg":
1201
+ return negate(
1202
+ parseAnnotationCondition(annotationValue.$Neg[0], visitedNavigationPaths, pathVisitor, converterRoot)
1203
+ ) as BindingToolkitExpression<PrimitiveTypeCast<T>>;
950
1204
  case "Apply":
951
1205
  return annotationApplyExpression(
952
1206
  annotationValue as ApplyAnnotationExpression<string>,
@@ -966,60 +1220,122 @@ export function getExpressionFromAnnotation<T extends PrimitiveType>(
966
1220
  * @param annotationValue The condition or value from the annotation
967
1221
  * @param visitedNavigationPaths The path from the root entity set
968
1222
  * @param pathVisitor A function to modify the resulting path
1223
+ * @param converterRoot The root converted metadata
969
1224
  * @returns An equivalent expression
970
1225
  */
971
1226
  function parseAnnotationCondition<T extends PrimitiveType>(
972
1227
  annotationValue: ConditionalCheckOrValue,
973
1228
  visitedNavigationPaths: string[] = [],
974
- pathVisitor?: Function
1229
+ pathVisitor?: Function,
1230
+ converterRoot?: ConvertedMetadata
975
1231
  ): BindingToolkitExpression<T> {
976
1232
  if (annotationValue === null || typeof annotationValue !== "object") {
977
1233
  return constant(annotationValue as T);
978
1234
  } else if (annotationValue.hasOwnProperty("$Or")) {
979
1235
  return or(
980
1236
  ...((annotationValue as OrConditionalExpression).$Or.map(function (orCondition) {
981
- return parseAnnotationCondition(orCondition, visitedNavigationPaths, pathVisitor);
1237
+ return parseAnnotationCondition(orCondition, visitedNavigationPaths, pathVisitor, converterRoot);
982
1238
  }) as unknown as BindingToolkitExpression<boolean>[])
983
1239
  ) as BindingToolkitExpression<T>;
984
1240
  } else if (annotationValue.hasOwnProperty("$And")) {
985
1241
  return and(
986
1242
  ...((annotationValue as AndConditionalExpression).$And.map(function (andCondition) {
987
- return parseAnnotationCondition(andCondition, visitedNavigationPaths, pathVisitor);
1243
+ return parseAnnotationCondition(andCondition, visitedNavigationPaths, pathVisitor, converterRoot);
988
1244
  }) as unknown as BindingToolkitExpression<boolean>[])
989
1245
  ) as BindingToolkitExpression<T>;
990
1246
  } else if (annotationValue.hasOwnProperty("$Not")) {
991
1247
  return not(
992
- parseAnnotationCondition((annotationValue as NotConditionalExpression).$Not, visitedNavigationPaths, pathVisitor)
1248
+ parseAnnotationCondition((annotationValue as NotConditionalExpression).$Not, visitedNavigationPaths, pathVisitor, converterRoot)
993
1249
  ) as BindingToolkitExpression<T>;
994
1250
  } else if (annotationValue.hasOwnProperty("$Eq")) {
995
1251
  return equal(
996
- parseAnnotationCondition((annotationValue as EqConditionalExpression).$Eq[0], visitedNavigationPaths, pathVisitor),
997
- parseAnnotationCondition((annotationValue as EqConditionalExpression).$Eq[1], visitedNavigationPaths, pathVisitor)
1252
+ parseAnnotationCondition(
1253
+ (annotationValue as EqConditionalExpression).$Eq[0],
1254
+ visitedNavigationPaths,
1255
+ pathVisitor,
1256
+ converterRoot
1257
+ ),
1258
+ parseAnnotationCondition(
1259
+ (annotationValue as EqConditionalExpression).$Eq[1],
1260
+ visitedNavigationPaths,
1261
+ pathVisitor,
1262
+ converterRoot
1263
+ )
998
1264
  ) as BindingToolkitExpression<T>;
999
1265
  } else if (annotationValue.hasOwnProperty("$Ne")) {
1000
1266
  return notEqual(
1001
- parseAnnotationCondition((annotationValue as NeConditionalExpression).$Ne[0], visitedNavigationPaths, pathVisitor),
1002
- parseAnnotationCondition((annotationValue as NeConditionalExpression).$Ne[1], visitedNavigationPaths, pathVisitor)
1267
+ parseAnnotationCondition(
1268
+ (annotationValue as NeConditionalExpression).$Ne[0],
1269
+ visitedNavigationPaths,
1270
+ pathVisitor,
1271
+ converterRoot
1272
+ ),
1273
+ parseAnnotationCondition(
1274
+ (annotationValue as NeConditionalExpression).$Ne[1],
1275
+ visitedNavigationPaths,
1276
+ pathVisitor,
1277
+ converterRoot
1278
+ )
1003
1279
  ) as BindingToolkitExpression<T>;
1004
1280
  } else if (annotationValue.hasOwnProperty("$Gt")) {
1005
1281
  return greaterThan(
1006
- parseAnnotationCondition((annotationValue as GtConditionalExpression).$Gt[0], visitedNavigationPaths, pathVisitor),
1007
- parseAnnotationCondition((annotationValue as GtConditionalExpression).$Gt[1], visitedNavigationPaths, pathVisitor)
1282
+ parseAnnotationCondition(
1283
+ (annotationValue as GtConditionalExpression).$Gt[0],
1284
+ visitedNavigationPaths,
1285
+ pathVisitor,
1286
+ converterRoot
1287
+ ),
1288
+ parseAnnotationCondition(
1289
+ (annotationValue as GtConditionalExpression).$Gt[1],
1290
+ visitedNavigationPaths,
1291
+ pathVisitor,
1292
+ converterRoot
1293
+ )
1008
1294
  ) as BindingToolkitExpression<T>;
1009
1295
  } else if (annotationValue.hasOwnProperty("$Ge")) {
1010
1296
  return greaterOrEqual(
1011
- parseAnnotationCondition((annotationValue as GeConditionalExpression).$Ge[0], visitedNavigationPaths, pathVisitor),
1012
- parseAnnotationCondition((annotationValue as GeConditionalExpression).$Ge[1], visitedNavigationPaths, pathVisitor)
1297
+ parseAnnotationCondition(
1298
+ (annotationValue as GeConditionalExpression).$Ge[0],
1299
+ visitedNavigationPaths,
1300
+ pathVisitor,
1301
+ converterRoot
1302
+ ),
1303
+ parseAnnotationCondition(
1304
+ (annotationValue as GeConditionalExpression).$Ge[1],
1305
+ visitedNavigationPaths,
1306
+ pathVisitor,
1307
+ converterRoot
1308
+ )
1013
1309
  ) as BindingToolkitExpression<T>;
1014
1310
  } else if (annotationValue.hasOwnProperty("$Lt")) {
1015
1311
  return lessThan(
1016
- parseAnnotationCondition((annotationValue as LtConditionalExpression).$Lt[0], visitedNavigationPaths, pathVisitor),
1017
- parseAnnotationCondition((annotationValue as LtConditionalExpression).$Lt[1], visitedNavigationPaths, pathVisitor)
1312
+ parseAnnotationCondition(
1313
+ (annotationValue as LtConditionalExpression).$Lt[0],
1314
+ visitedNavigationPaths,
1315
+ pathVisitor,
1316
+ converterRoot
1317
+ ),
1318
+ parseAnnotationCondition(
1319
+ (annotationValue as LtConditionalExpression).$Lt[1],
1320
+ visitedNavigationPaths,
1321
+ pathVisitor,
1322
+ converterRoot
1323
+ )
1018
1324
  ) as BindingToolkitExpression<T>;
1019
1325
  } else if (annotationValue.hasOwnProperty("$Le")) {
1020
1326
  return lessOrEqual(
1021
- parseAnnotationCondition((annotationValue as LeConditionalExpression).$Le[0], visitedNavigationPaths, pathVisitor),
1022
- parseAnnotationCondition((annotationValue as LeConditionalExpression).$Le[1], visitedNavigationPaths, pathVisitor)
1327
+ parseAnnotationCondition(
1328
+ (annotationValue as LeConditionalExpression).$Le[0],
1329
+ visitedNavigationPaths,
1330
+ pathVisitor,
1331
+ converterRoot
1332
+ ),
1333
+ parseAnnotationCondition(
1334
+ (annotationValue as LeConditionalExpression).$Le[1],
1335
+ visitedNavigationPaths,
1336
+ pathVisitor,
1337
+ converterRoot
1338
+ )
1023
1339
  ) as BindingToolkitExpression<T>;
1024
1340
  } else if (annotationValue.hasOwnProperty("$Path")) {
1025
1341
  return pathInModel((annotationValue as PathConditionExpression<T>).$Path, undefined, visitedNavigationPaths, pathVisitor);
@@ -1045,9 +1361,137 @@ function parseAnnotationCondition<T extends PrimitiveType>(
1045
1361
  pathVisitor
1046
1362
  ) as BindingToolkitExpression<T>;
1047
1363
  } else if (annotationValue.hasOwnProperty("$EnumMember")) {
1048
- return constant(resolveEnumValue((annotationValue as { $EnumMember?: string }).$EnumMember) as T);
1364
+ return constant(resolveEnumValue((annotationValue as { $EnumMember?: string }).$EnumMember, converterRoot) as T);
1365
+ } else if (annotationValue.hasOwnProperty("$In")) {
1366
+ return comparison(
1367
+ "In",
1368
+ parseAnnotationCondition(
1369
+ (annotationValue as InConditionalExpression).$In[0],
1370
+ visitedNavigationPaths,
1371
+ pathVisitor,
1372
+ converterRoot
1373
+ ),
1374
+ parseAnnotationCondition(
1375
+ (annotationValue as InConditionalExpression).$In[1],
1376
+ visitedNavigationPaths,
1377
+ pathVisitor,
1378
+ converterRoot
1379
+ )
1380
+ ) as BindingToolkitExpression<T>;
1381
+ } else if (annotationValue.hasOwnProperty("$Has")) {
1382
+ return comparison(
1383
+ "Has",
1384
+ parseAnnotationCondition(
1385
+ (annotationValue as HasConditionalExpression).$Has[0],
1386
+ visitedNavigationPaths,
1387
+ pathVisitor,
1388
+ converterRoot
1389
+ ),
1390
+ parseAnnotationCondition(
1391
+ (annotationValue as HasConditionalExpression).$Has[1],
1392
+ visitedNavigationPaths,
1393
+ pathVisitor,
1394
+ converterRoot
1395
+ )
1396
+ ) as BindingToolkitExpression<T>;
1397
+ } else if (annotationValue.hasOwnProperty("$Add")) {
1398
+ return add(
1399
+ parseAnnotationCondition(
1400
+ (annotationValue as AddConditionalExpression).$Add[0],
1401
+ visitedNavigationPaths,
1402
+ pathVisitor,
1403
+ converterRoot
1404
+ ),
1405
+ parseAnnotationCondition(
1406
+ (annotationValue as AddConditionalExpression).$Add[1],
1407
+ visitedNavigationPaths,
1408
+ pathVisitor,
1409
+ converterRoot
1410
+ )
1411
+ ) as BindingToolkitExpression<T>;
1412
+ } else if (annotationValue.hasOwnProperty("$Sub")) {
1413
+ return subtract(
1414
+ parseAnnotationCondition(
1415
+ (annotationValue as SubConditionalExpression).$Sub[0],
1416
+ visitedNavigationPaths,
1417
+ pathVisitor,
1418
+ converterRoot
1419
+ ),
1420
+ parseAnnotationCondition(
1421
+ (annotationValue as SubConditionalExpression).$Sub[1],
1422
+ visitedNavigationPaths,
1423
+ pathVisitor,
1424
+ converterRoot
1425
+ )
1426
+ ) as BindingToolkitExpression<T>;
1427
+ } else if (annotationValue.hasOwnProperty("$Mul")) {
1428
+ return multiply(
1429
+ parseAnnotationCondition(
1430
+ (annotationValue as MulConditionalExpression).$Mul[0],
1431
+ visitedNavigationPaths,
1432
+ pathVisitor,
1433
+ converterRoot
1434
+ ),
1435
+ parseAnnotationCondition(
1436
+ (annotationValue as MulConditionalExpression).$Mul[1],
1437
+ visitedNavigationPaths,
1438
+ pathVisitor,
1439
+ converterRoot
1440
+ )
1441
+ ) as BindingToolkitExpression<T>;
1442
+ } else if (annotationValue.hasOwnProperty("$Div")) {
1443
+ return floor(
1444
+ divide(
1445
+ parseAnnotationCondition(
1446
+ (annotationValue as DivConditionalExpression).$Div[0],
1447
+ visitedNavigationPaths,
1448
+ pathVisitor,
1449
+ converterRoot
1450
+ ),
1451
+ parseAnnotationCondition(
1452
+ (annotationValue as DivConditionalExpression).$Div[1],
1453
+ visitedNavigationPaths,
1454
+ pathVisitor,
1455
+ converterRoot
1456
+ )
1457
+ )
1458
+ ) as BindingToolkitExpression<T>;
1459
+ } else if (annotationValue.hasOwnProperty("$DivBy")) {
1460
+ return divide(
1461
+ parseAnnotationCondition(
1462
+ (annotationValue as DivByConditionalExpression).$DivBy[0],
1463
+ visitedNavigationPaths,
1464
+ pathVisitor,
1465
+ converterRoot
1466
+ ),
1467
+ parseAnnotationCondition(
1468
+ (annotationValue as DivByConditionalExpression).$DivBy[1],
1469
+ visitedNavigationPaths,
1470
+ pathVisitor,
1471
+ converterRoot
1472
+ )
1473
+ ) as BindingToolkitExpression<T>;
1474
+ } else if (annotationValue.hasOwnProperty("$Mod")) {
1475
+ return modulo(
1476
+ parseAnnotationCondition(
1477
+ (annotationValue as ModConditionalExpression).$Mod[0],
1478
+ visitedNavigationPaths,
1479
+ pathVisitor,
1480
+ converterRoot
1481
+ ),
1482
+ parseAnnotationCondition(
1483
+ (annotationValue as ModConditionalExpression).$Mod[1],
1484
+ visitedNavigationPaths,
1485
+ pathVisitor,
1486
+ converterRoot
1487
+ )
1488
+ ) as BindingToolkitExpression<T>;
1489
+ } else if (annotationValue.hasOwnProperty("$Neg")) {
1490
+ return negate(
1491
+ parseAnnotationCondition((annotationValue as NegConditionalExpression).$Neg, visitedNavigationPaths, pathVisitor, converterRoot)
1492
+ ) as BindingToolkitExpression<T>;
1049
1493
  }
1050
- return constant(false as T);
1494
+ return constant(annotationValue as T);
1051
1495
  }
1052
1496
 
1053
1497
  /**
@@ -1056,17 +1500,19 @@ function parseAnnotationCondition<T extends PrimitiveType>(
1056
1500
  * @param annotationValue An If expression returning the type T
1057
1501
  * @param visitedNavigationPaths The path from the root entity set
1058
1502
  * @param pathVisitor A function to modify the resulting path
1503
+ * @param converterRoot The root converted metadata
1059
1504
  * @returns The equivalent ifElse expression
1060
1505
  */
1061
1506
  export function annotationIfExpression<T extends PrimitiveType>(
1062
1507
  annotationValue: IfAnnotationExpressionValue<T>,
1063
1508
  visitedNavigationPaths: string[] = [],
1064
- pathVisitor?: Function
1509
+ pathVisitor?: Function,
1510
+ converterRoot?: ConvertedMetadata
1065
1511
  ): BindingToolkitExpression<T> {
1066
1512
  return ifElse(
1067
- parseAnnotationCondition(annotationValue[0], visitedNavigationPaths, pathVisitor),
1068
- parseAnnotationCondition(annotationValue[1] as ConditionalCheckOrValue, visitedNavigationPaths, pathVisitor),
1069
- parseAnnotationCondition(annotationValue[2] as ConditionalCheckOrValue, visitedNavigationPaths, pathVisitor)
1513
+ parseAnnotationCondition(annotationValue[0], visitedNavigationPaths, pathVisitor, converterRoot),
1514
+ parseAnnotationCondition(annotationValue[1] as ConditionalCheckOrValue, visitedNavigationPaths, pathVisitor, converterRoot),
1515
+ parseAnnotationCondition(annotationValue[2] as ConditionalCheckOrValue, visitedNavigationPaths, pathVisitor, converterRoot)
1070
1516
  );
1071
1517
  }
1072
1518
  // This type is not recursively transformed from the metamodel content, as such we have some ugly things there
@@ -1205,6 +1651,43 @@ function comparison<T extends PrimitiveType>(
1205
1651
  return constant(false);
1206
1652
  }
1207
1653
  return constant(leftExpression.value >= rightExpression.value);
1654
+
1655
+ case "Has": {
1656
+ if (
1657
+ leftExpression.value === null ||
1658
+ leftExpression.value === undefined ||
1659
+ rightExpression.value === null ||
1660
+ rightExpression.value === undefined
1661
+ ) {
1662
+ return constant(false);
1663
+ }
1664
+ const leftValueInt = leftExpression.value as number;
1665
+ const rightValueInt = rightExpression.value as number;
1666
+ return constant(!!(leftValueInt & rightValueInt));
1667
+ }
1668
+ case "In": {
1669
+ if (
1670
+ leftExpression.value === null ||
1671
+ leftExpression.value === undefined ||
1672
+ rightExpression.value === null ||
1673
+ rightExpression.value === undefined
1674
+ ) {
1675
+ return constant(false);
1676
+ }
1677
+ // Check all the children of the right expression are constants
1678
+ const allChildrenConstants = (rightExpression.value as T[]).every((item) => isConstant(item));
1679
+ if (allChildrenConstants) {
1680
+ const rightValues = (rightExpression.value as ConstantExpression<T>[]).map((item) => item.value);
1681
+ return constant(rightValues.includes(leftExpression.value));
1682
+ } else {
1683
+ return {
1684
+ _type: "Comparison",
1685
+ operator: operator,
1686
+ operand1: leftExpression,
1687
+ operand2: rightExpression
1688
+ };
1689
+ }
1690
+ }
1208
1691
  }
1209
1692
  } else {
1210
1693
  return {
@@ -1359,6 +1842,20 @@ export function lessThan<T extends DefinedPrimitiveType>(
1359
1842
  return comparison("<", leftOperand, rightOperand);
1360
1843
  }
1361
1844
 
1845
+ /**
1846
+ * Comparison: "Include" (In).
1847
+ * @template T The target type
1848
+ * @param leftOperand The operand on the left side
1849
+ * @param rightOperand The operand on the right side of the comparison
1850
+ * @returns An expression representing the comparison
1851
+ */
1852
+ export function includes<T extends DefinedPrimitiveType>(
1853
+ leftOperand: ExpressionOrPrimitive<T>,
1854
+ rightOperand: ExpressionOrPrimitive<T[]>
1855
+ ): BindingToolkitExpression<boolean> {
1856
+ return comparison("In", leftOperand, rightOperand as ExpressionOrPrimitive<T>);
1857
+ }
1858
+
1362
1859
  /**
1363
1860
  * If-then-else expression.
1364
1861
  *
@@ -1584,7 +2081,7 @@ export function formatResult<T, U extends Fn<T>>(
1584
2081
  }
1585
2082
 
1586
2083
  type BindingConstraints = {
1587
- scale?: number;
2084
+ scale?: number | string;
1588
2085
  precision?: number;
1589
2086
  maxLength?: number;
1590
2087
  nullable?: boolean;
@@ -1664,16 +2161,18 @@ export function formatWithTypeInformation<T>(
1664
2161
  parseAsString: false
1665
2162
  });
1666
2163
  }
1667
- if (outExpression.type === "sap.ui.model.odata.type.String" && isProperty(property)) {
2164
+ if (outExpression.type === "sap.ui.model.odata.type.String" && (isProperty(property) || isActionParameter(property))) {
1668
2165
  if (outExpression.constraints?.isDigitSequence || (outExpression.constraints?.nullable !== false && property.nullable === true)) {
1669
2166
  outExpression.formatOptions.parseKeepsEmptyString = false;
1670
2167
  } else {
1671
2168
  outExpression.formatOptions.parseKeepsEmptyString = true;
1672
2169
  }
1673
- const fiscalType = getFiscalType(property);
1674
- if (fiscalType) {
1675
- outExpression.formatOptions.fiscalType = fiscalType;
1676
- outExpression.type = "sap.fe.core.type.FiscalDate";
2170
+ if (isProperty(property)) {
2171
+ const fiscalType = getFiscalType(property);
2172
+ if (fiscalType) {
2173
+ outExpression.formatOptions.fiscalType = fiscalType;
2174
+ outExpression.type = "sap.fe.core.type.FiscalDate";
2175
+ }
1677
2176
  }
1678
2177
  }
1679
2178
  const typesSupportingParseEmptyValueToZero = [
@@ -1899,7 +2398,57 @@ export function isEmpty(expression: BindingToolkitExpression<string>): BindingTo
1899
2398
  });
1900
2399
  return and(...aBindings);
1901
2400
  }
2401
+ /**
2402
+ * Shortcut function to determine if a binding can be statically resolvable.
2403
+ * @param exp The expression to evaluate
2404
+ * @returns A Boolean evaluating if there is no relative path dependent on the current context
2405
+ */
2406
+ export function isExpressionStaticallyResolvable<T extends PrimitiveType>(exp: BindingToolkitExpression<T>): boolean {
2407
+ const referencedPaths: string[] = [];
2408
+ transformRecursively(
2409
+ exp,
2410
+ "PathInModel",
2411
+ (pathExp) => {
2412
+ if (pathExp.modelName === undefined && typeof pathExp.path === "string") {
2413
+ referencedPaths.push(pathExp.path);
2414
+ }
2415
+ return pathExp;
2416
+ },
2417
+ true
2418
+ );
2419
+ if (referencedPaths.length === 0) {
2420
+ return true;
2421
+ }
2422
+ return referencedPaths.every((p) => p.startsWith("/"));
2423
+ }
2424
+
2425
+ export function getStaticallyResolvableExpression<T extends PrimitiveType>(
2426
+ exp: BindingToolkitExpression<T>,
2427
+ defaultValue: BindingToolkitExpression<T>
2428
+ ): BindingToolkitExpression<T> {
2429
+ if (exp._type === "Set") {
2430
+ const newOperands = exp.operands.map((operand: BindingToolkitExpression<boolean>) => {
2431
+ if (isExpressionStaticallyResolvable(operand) || isConstant(operand)) {
2432
+ return operand;
2433
+ } else {
2434
+ return defaultValue;
2435
+ }
2436
+ });
2437
+
2438
+ if (exp.operator === "||") {
2439
+ return or(...(newOperands as unknown as BindingToolkitExpression<boolean>[])) as unknown as BindingToolkitExpression<T>;
2440
+ } else {
2441
+ return and(...(newOperands as unknown as BindingToolkitExpression<boolean>[])) as unknown as BindingToolkitExpression<T>;
2442
+ }
2443
+ }
2444
+ return isExpressionStaticallyResolvable(exp) ? exp : defaultValue;
2445
+ }
1902
2446
 
2447
+ /**
2448
+ * Function used to concatenate multiple expressions.
2449
+ * @param inExpressions The expressions to concatenate
2450
+ * @returns The concatenated expression
2451
+ */
1903
2452
  export function concat(...inExpressions: ExpressionOrPrimitive<string>[]): BindingToolkitExpression<string> {
1904
2453
  const expressions = inExpressions.map(wrapPrimitive);
1905
2454
  if (hasUnresolvableExpression(...expressions)) {
@@ -1943,6 +2492,7 @@ type ExpressionTypeToExpression<T extends ExpressionType, V> = T extends "Consta
1943
2492
 
1944
2493
  export type TransformFunction<T, V> = (expressionPart: T) => BindingToolkitExpression<V>;
1945
2494
  export type ExpressionType = Pick<BindingToolkitExpression<unknown>, "_type">["_type"];
2495
+
1946
2496
  export function transformRecursively<T extends PrimitiveType | unknown, ET extends ExpressionType>(
1947
2497
  inExpression: BindingToolkitExpression<T>,
1948
2498
  expressionType: ET,
@@ -2048,6 +2598,21 @@ export function transformRecursively<T extends PrimitiveType | unknown, ET exten
2048
2598
  expression = comparison(expression.operator, operand1, operand2) as BindingToolkitExpression<T>;
2049
2599
  }
2050
2600
  break;
2601
+ case "Arithmetic":
2602
+ const operand1 = transformRecursively(
2603
+ expression.operand1 as BindingToolkitExpression<T>,
2604
+ expressionType,
2605
+ transformFunction,
2606
+ includeAllExpression
2607
+ );
2608
+ const operand2 = transformRecursively(
2609
+ expression.operand2 as BindingToolkitExpression<T>,
2610
+ expressionType,
2611
+ transformFunction,
2612
+ includeAllExpression
2613
+ );
2614
+ expression = arithmetic(expression.operator, operand1, operand2) as BindingToolkitExpression<T>;
2615
+ break;
2051
2616
  case "Constant":
2052
2617
  const constantValue: Record<string, BindingToolkitExpression<unknown>> = expression.value as Record<
2053
2618
  string,
@@ -2079,6 +2644,10 @@ export function transformRecursively<T extends PrimitiveType | unknown, ET exten
2079
2644
  return expression;
2080
2645
  }
2081
2646
 
2647
+ /**
2648
+ * Compiled Binding Toolkit Expression
2649
+ * This is a type representing the compiled form of a Binding Toolkit expression as a binding string.
2650
+ */
2082
2651
  export type CompiledBindingToolkitExpression = string | undefined;
2083
2652
 
2084
2653
  const needParenthesis = function <T extends PrimitiveType>(expr: ExpressionOrPrimitive<T>): boolean {
@@ -2364,6 +2933,9 @@ export function compileExpression<T extends PrimitiveType>(
2364
2933
  case "Not":
2365
2934
  const notExpression = `!${compileExpression(expr.operand, true)}`;
2366
2935
  return wrapBindingExpression(notExpression, embeddedInBinding);
2936
+ case "Arithmetic":
2937
+ const arithmeticExpression = compileArithmeticExpression(expr);
2938
+ return wrapBindingExpression(arithmeticExpression, embeddedInBinding);
2367
2939
 
2368
2940
  case "Truthy":
2369
2941
  const truthyExpression = `!!${compileExpression(expr.operand, true)}`;
@@ -2382,6 +2954,25 @@ export function compileExpression<T extends PrimitiveType>(
2382
2954
  }
2383
2955
  }
2384
2956
 
2957
+ function compileArithmeticExpression(expression: ArithmeticExpression): string {
2958
+ function compileOperand(operand: BindingToolkitExpression<unknown>): CompiledBindingToolkitExpression {
2959
+ const compiledOperand = compileExpression(operand, true) ?? "undefined";
2960
+ return wrapBindingExpression(compiledOperand, true, needParenthesis(operand));
2961
+ }
2962
+ switch (expression.operator) {
2963
+ case "Neg":
2964
+ return `-(${compileOperand(expression.operand1)})`;
2965
+ case "Floor":
2966
+ return `Math.floor(${compileOperand(expression.operand1)})`;
2967
+ case "%":
2968
+ case "/":
2969
+ case "*":
2970
+ case "+":
2971
+ case "-":
2972
+ return `${compileOperand(expression.operand1)} ${expression.operator} ${compileOperand(expression.operand2)}`;
2973
+ }
2974
+ }
2975
+
2385
2976
  /**
2386
2977
  * Compile a comparison expression.
2387
2978
  * @param expression The comparison expression.
@@ -2392,8 +2983,25 @@ function compileComparisonExpression(expression: ComparisonExpression): string {
2392
2983
  const compiledOperand = compileExpression(operand, true) ?? "undefined";
2393
2984
  return wrapBindingExpression(compiledOperand, true, needParenthesis(operand));
2394
2985
  }
2395
-
2396
- return `${compileOperand(expression.operand1)} ${expression.operator} ${compileOperand(expression.operand2)}`;
2986
+ switch (expression.operator) {
2987
+ case "In": {
2988
+ const path = compileOperand(expression.operand1);
2989
+ const collection = compileOperand(expression.operand2) ?? [];
2990
+ return `${collection}.includes(${path})`;
2991
+ }
2992
+ case "Has": {
2993
+ const path = compileOperand(expression.operand1);
2994
+ const flags = compileOperand(expression.operand2) ?? [];
2995
+ return `(${path} & ${flags}) > 0`;
2996
+ }
2997
+ case "===":
2998
+ case "!==":
2999
+ case ">=":
3000
+ case ">":
3001
+ case "<=":
3002
+ case "<":
3003
+ return `${compileOperand(expression.operand1)} ${expression.operator} ${compileOperand(expression.operand2)}`;
3004
+ }
2397
3005
  }
2398
3006
 
2399
3007
  /**