@syncfusion/ej2-react-form-builder 0.1.0-beta → 35.1.37

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.
Files changed (160) hide show
  1. package/README.md +81 -36
  2. package/diConfig.json +148 -0
  3. package/dist/ej2-react-form-builder.min.js +10 -0
  4. package/dist/ej2-react-form-builder.umd.min.js +11 -0
  5. package/dist/ej2-react-form-builder.umd.min.js.map +1 -0
  6. package/dist/es6/ej2-react-form-builder.es2015.js +60 -0
  7. package/dist/es6/ej2-react-form-builder.es2015.js.map +1 -0
  8. package/form-builder.d.ts +4 -0
  9. package/form-builder.js +4 -0
  10. package/index.d.ts +2 -4
  11. package/index.js +2 -4
  12. package/license +6 -13
  13. package/package.json +19 -43
  14. package/src/form-builder/formbuilder.component.d.ts +37 -0
  15. package/src/form-builder/formbuilder.component.js +50 -0
  16. package/src/form-builder/index.d.ts +2 -0
  17. package/src/form-builder/index.js +2 -0
  18. package/src/form-builder/toolboxitems-directive.d.ts +28 -0
  19. package/src/form-builder/toolboxitems-directive.js +47 -0
  20. package/src/global.js +1 -0
  21. package/src/index.d.ts +2 -16
  22. package/src/index.js +2 -6
  23. package/Common/CheckboxGroup.d.ts +0 -13
  24. package/Common/CheckboxGroup.js +0 -50
  25. package/Common/ConditionalRuleEngine.d.ts +0 -296
  26. package/Common/ConditionalRuleEngine.js +0 -566
  27. package/Common/CustomTabComponent.d.ts +0 -17
  28. package/Common/CustomTabComponent.js +0 -30
  29. package/Common/CustomValidationEngine.d.ts +0 -58
  30. package/Common/CustomValidationEngine.js +0 -146
  31. package/Common/DataGridComponent.d.ts +0 -20
  32. package/Common/DataGridComponent.js +0 -273
  33. package/Common/FormValidator.d.ts +0 -485
  34. package/Common/FormValidator.js +0 -575
  35. package/Common/MinMaxRuleEngine.d.ts +0 -105
  36. package/Common/MinMaxRuleEngine.js +0 -365
  37. package/Common/StaticHtmlRenderer.d.ts +0 -14
  38. package/Common/StaticHtmlRenderer.js +0 -24
  39. package/Common/expressions/DependencyTracker.d.ts +0 -30
  40. package/Common/expressions/DependencyTracker.js +0 -128
  41. package/Common/expressions/ExpressionEngine.d.ts +0 -95
  42. package/Common/expressions/ExpressionEngine.js +0 -506
  43. package/Common/expressions/ExpressionParser.d.ts +0 -41
  44. package/Common/expressions/ExpressionParser.js +0 -269
  45. package/Common/expressions/useGridExpressions.d.ts +0 -12
  46. package/Common/expressions/useGridExpressions.js +0 -71
  47. package/Common/globalFormId.d.ts +0 -26
  48. package/Common/globalFormId.js +0 -45
  49. package/Common/json-converter.d.ts +0 -444
  50. package/Common/json-converter.js +0 -1184
  51. package/Common/parser.d.ts +0 -98
  52. package/Common/parser.js +0 -753
  53. package/Common/utils.d.ts +0 -130
  54. package/Common/utils.js +0 -574
  55. package/src/FormBuilder.d.ts +0 -211
  56. package/src/FormBuilder.js +0 -1299
  57. package/src/components/Database/ConnectionForm.d.ts +0 -8
  58. package/src/components/Database/ConnectionForm.js +0 -28
  59. package/src/components/TopBar.d.ts +0 -10
  60. package/src/components/TopBar.js +0 -7
  61. package/src/components/icons/ComponentIcons.d.ts +0 -44
  62. package/src/components/icons/ComponentIcons.js +0 -40
  63. package/src/components/layout/FormFieldWrapper.d.ts +0 -41
  64. package/src/components/layout/FormFieldWrapper.js +0 -112
  65. package/src/components/layout/LeftPane.d.ts +0 -23
  66. package/src/components/layout/LeftPane.js +0 -310
  67. package/src/components/layout/ListBoxPane.d.ts +0 -24
  68. package/src/components/layout/ListBoxPane.js +0 -94
  69. package/src/components/layout/MainSection.d.ts +0 -39
  70. package/src/components/layout/MainSection.js +0 -1766
  71. package/src/components/layout/Preview.d.ts +0 -12
  72. package/src/components/layout/Preview.js +0 -337
  73. package/src/components/layout/RightPane.d.ts +0 -22
  74. package/src/components/layout/RightPane.js +0 -370
  75. package/src/components/theme/ThemeSwitcher.d.ts +0 -14
  76. package/src/components/theme/ThemeSwitcher.js +0 -24
  77. package/src/components/theme/theme-constant.d.ts +0 -37
  78. package/src/components/theme/theme-constant.js +0 -23
  79. package/src/config/component-properties.json +0 -4929
  80. package/src/config/form-setting.json +0 -56
  81. package/src/config/locale.d.ts +0 -449
  82. package/src/config/locale.js +0 -449
  83. package/src/context/FormSchemaContext.d.ts +0 -32
  84. package/src/context/FormSchemaContext.js +0 -155
  85. package/src/context/LocalizationContext.d.ts +0 -27
  86. package/src/context/LocalizationContext.js +0 -43
  87. package/src/context/ThemeContext.d.ts +0 -23
  88. package/src/context/ThemeContext.js +0 -161
  89. package/src/context/contexts.d.ts +0 -6
  90. package/src/context/contexts.js +0 -6
  91. package/src/context/defaultFormSchema.d.ts +0 -6
  92. package/src/context/defaultFormSchema.js +0 -20
  93. package/src/defaultPalette.d.ts +0 -9
  94. package/src/defaultPalette.js +0 -45
  95. package/src/defaultTemplate.d.ts +0 -3
  96. package/src/defaultTemplate.js +0 -185
  97. package/src/import/schemaImportUtils.d.ts +0 -24
  98. package/src/import/schemaImportUtils.js +0 -121
  99. package/src/types/ActionButtons.d.ts +0 -44
  100. package/src/types/ActionButtons.js +0 -27
  101. package/src/types/ComponentProperties.d.ts +0 -33
  102. package/src/types/ComponentProperties.js +0 -2
  103. package/src/types/FormSchema.d.ts +0 -585
  104. package/src/types/FormSchema.js +0 -147
  105. package/src/types/connection.d.ts +0 -19
  106. package/src/types/connection.js +0 -9
  107. package/src/utils/AttributesEditor.d.ts +0 -13
  108. package/src/utils/AttributesEditor.js +0 -117
  109. package/src/utils/ButtonGroupEditor.d.ts +0 -15
  110. package/src/utils/ButtonGroupEditor.js +0 -141
  111. package/src/utils/ChoiceBasedFieldEditor.d.ts +0 -16
  112. package/src/utils/ChoiceBasedFieldEditor.js +0 -87
  113. package/src/utils/ClipboardHelper.d.ts +0 -26
  114. package/src/utils/ClipboardHelper.js +0 -83
  115. package/src/utils/ColumnBuilder.d.ts +0 -11
  116. package/src/utils/ColumnBuilder.js +0 -153
  117. package/src/utils/ColumnSpanEditor.d.ts +0 -8
  118. package/src/utils/ColumnSpanEditor.js +0 -98
  119. package/src/utils/ConditionDataEditor.d.ts +0 -29
  120. package/src/utils/ConditionDataEditor.js +0 -177
  121. package/src/utils/ConditionalEditorUtils.d.ts +0 -27
  122. package/src/utils/ConditionalEditorUtils.js +0 -310
  123. package/src/utils/ConditionsEditor.d.ts +0 -20
  124. package/src/utils/ConditionsEditor.js +0 -571
  125. package/src/utils/CustomAccordion.d.ts +0 -13
  126. package/src/utils/CustomAccordion.js +0 -38
  127. package/src/utils/CustomValidationEditor.d.ts +0 -11
  128. package/src/utils/CustomValidationEditor.js +0 -208
  129. package/src/utils/DataSourceCollector.d.ts +0 -3
  130. package/src/utils/DataSourceCollector.js +0 -252
  131. package/src/utils/ExpressionEditor.d.ts +0 -13
  132. package/src/utils/ExpressionEditor.js +0 -99
  133. package/src/utils/FormSettingsPanel.d.ts +0 -9
  134. package/src/utils/FormSettingsPanel.js +0 -60
  135. package/src/utils/LookupConfig.d.ts +0 -11
  136. package/src/utils/LookupConfig.js +0 -159
  137. package/src/utils/MinMaxValueEditor.d.ts +0 -25
  138. package/src/utils/MinMaxValueEditor.js +0 -188
  139. package/src/utils/OptionsEditor.d.ts +0 -8
  140. package/src/utils/OptionsEditor.js +0 -79
  141. package/src/utils/PropertyRenderer.d.ts +0 -22
  142. package/src/utils/PropertyRenderer.js +0 -210
  143. package/src/utils/ValueRenderer.d.ts +0 -2
  144. package/src/utils/ValueRenderer.js +0 -91
  145. package/src/utils/ej2DbAdaptor.d.ts +0 -6
  146. package/src/utils/ej2DbAdaptor.js +0 -38
  147. package/src/utils/mappingHeuristics.d.ts +0 -10
  148. package/src/utils/mappingHeuristics.js +0 -33
  149. package/src/utils/renderComponent.d.ts +0 -18
  150. package/src/utils/renderComponent.js +0 -300
  151. package/styles/tailwind3.css +0 -67
  152. package/styles/themes/action-buttons.css +0 -110
  153. package/styles/themes/datagrid.css +0 -380
  154. package/styles/themes/drag-reorder.css +0 -248
  155. package/styles/themes/import-dialog.css +0 -292
  156. package/styles/themes/index.css +0 -658
  157. package/styles/themes/left-pane.css +0 -253
  158. package/styles/themes/options-editor.css +0 -63
  159. package/styles/themes/preview.css +0 -702
  160. package/styles/themes/property-dialog.css +0 -371
@@ -1,506 +0,0 @@
1
- import { ExpressionParser } from './ExpressionParser';
2
- import { DependencyTracker } from './DependencyTracker';
3
- import { Internationalization } from '@syncfusion/ej2-base';
4
- /**
5
- * ExpressionEngine - Core evaluation engine
6
- *
7
- * Safely evaluates user-defined expressions against form state
8
- * Supports field references, math operations, functions, and conditionals
9
- * @private
10
- */
11
- export class ExpressionEngine {
12
- parser;
13
- dependencyTracker;
14
- evaluationCache = new Map();
15
- compiledFunctions = new Map();
16
- constructor() {
17
- this.parser = new ExpressionParser();
18
- this.dependencyTracker = new DependencyTracker();
19
- }
20
- /**
21
- * Evaluate expression against form values
22
- */
23
- evaluate(expression, formValues, options = {}, isCustomValidation = false) {
24
- try {
25
- if (!expression || expression.trim() === '') {
26
- return null;
27
- }
28
- const { timeout = 5000, maxDepth = 10, locale = 'en-US', throwOnError = false } = options;
29
- // Check cache
30
- const cacheKey = this.getCacheKey(expression, formValues);
31
- if (!isCustomValidation && this.evaluationCache.has(cacheKey)) {
32
- return this.evaluationCache.get(cacheKey);
33
- }
34
- // Get or compile function
35
- let fn = this.compiledFunctions.get(expression);
36
- if (!fn) {
37
- fn = this.parser.compile(expression, {
38
- timeout,
39
- maxDepth,
40
- locale,
41
- isCustomValidation
42
- });
43
- this.compiledFunctions.set(expression, fn);
44
- }
45
- // Execute with context
46
- const context = this.buildContext(formValues);
47
- const startTime = Date.now();
48
- const result = fn(context);
49
- // Validate timeout
50
- if (Date.now() - startTime > timeout) {
51
- const error = new Error(`Expression evaluation exceeded timeout of ${timeout}ms`);
52
- if (throwOnError)
53
- throw error;
54
- console.warn(error);
55
- return null;
56
- }
57
- // Cache result
58
- this.evaluationCache.set(cacheKey, result);
59
- return result;
60
- }
61
- catch (error) {
62
- const message = error instanceof Error ? error.message : String(error);
63
- console.error('❌ Expression evaluation error:', message, { expression, formValues });
64
- if (options.throwOnError) {
65
- throw error;
66
- }
67
- return null;
68
- }
69
- }
70
- /**
71
- * Extract field names referenced in expression
72
- */
73
- extractDependencies(expression) {
74
- if (!expression)
75
- return [];
76
- return this.dependencyTracker.extractFieldReferences(expression);
77
- }
78
- /**
79
- * Build dependency graph for all expressions
80
- */
81
- buildDependencyGraph(components) {
82
- const graph = {};
83
- components.forEach(comp => {
84
- if (comp.expressionValue) {
85
- const deps = this.extractDependencies(comp.expressionValue);
86
- graph[comp.name] = deps;
87
- }
88
- });
89
- return graph;
90
- }
91
- /**
92
- * Detect circular dependencies
93
- */
94
- detectCircularDependencies(graph) {
95
- const visited = new Set();
96
- const recursionStack = new Set();
97
- const cycles = [];
98
- const visit = (node, path) => {
99
- visited.add(node);
100
- recursionStack.add(node);
101
- path.push(node);
102
- const deps = graph[node] || [];
103
- for (const dep of deps) {
104
- if (!visited.has(dep)) {
105
- visit(dep, [...path]);
106
- }
107
- else if (recursionStack.has(dep)) {
108
- const cycleStart = path.indexOf(dep);
109
- cycles.push([...path.slice(cycleStart), dep]);
110
- }
111
- }
112
- recursionStack.delete(node);
113
- };
114
- for (const node of Object.keys(graph)) {
115
- if (!visited.has(node)) {
116
- visit(node, []);
117
- }
118
- }
119
- return cycles;
120
- }
121
- /**
122
- * Get topological order for evaluation
123
- */
124
- getTopologicalOrder(graph) {
125
- const visited = new Set();
126
- const order = [];
127
- const visit = (node) => {
128
- if (visited.has(node))
129
- return;
130
- visited.add(node);
131
- const deps = graph[node] || [];
132
- for (const dep of deps) {
133
- visit(dep);
134
- }
135
- order.push(node);
136
- };
137
- for (const node of Object.keys(graph)) {
138
- visit(node);
139
- }
140
- return order;
141
- }
142
- /**
143
- * Clear internal caches
144
- */
145
- clearCache() {
146
- this.evaluationCache.clear();
147
- this.compiledFunctions.clear();
148
- }
149
- // Private helper methods
150
- getCacheKey(expression, formValues) {
151
- // Simple cache key (in production, use better hashing)
152
- return `${expression}:${JSON.stringify(formValues)}`;
153
- }
154
- buildContext(formValues) {
155
- const intl = new Internationalization();
156
- const dFormatter = intl.getDateFormat({
157
- format: 'MM/dd/yyyy',
158
- type: 'date'
159
- });
160
- return {
161
- // Field values
162
- ...formValues,
163
- // Constants
164
- PI: Math.PI,
165
- TRUE: true,
166
- FALSE: false,
167
- NULL: null,
168
- UNDEFINED: undefined,
169
- Math: Math,
170
- // Aggregate Functions
171
- SUM: (values) => Array.isArray(values) ? values.reduce((a, b) => Number(a) + Number(b), 0) : 0,
172
- AVG: (values) => {
173
- if (!Array.isArray(values) || values.length === 0)
174
- return 0;
175
- const sum = values.reduce((a, b) => Number(a) + Number(b), 0);
176
- return sum / values.length;
177
- },
178
- MIN: (values) => Array.isArray(values) && values.length > 0 ? Math.min(...values.map(Number)) : 0,
179
- MAX: (values) => Array.isArray(values) && values.length > 0 ? Math.max(...values.map(Number)) : 0,
180
- COUNT: (values) => Array.isArray(values) ? values.length : 0,
181
- // Math Functions
182
- ROUND: (value, decimals = 0) => Math.round(value * Math.pow(10, decimals)) / Math.pow(10, decimals),
183
- FLOOR: Math.floor,
184
- CEIL: Math.ceil,
185
- ABS: Math.abs,
186
- SQRT: Math.sqrt,
187
- // String Functions
188
- UPPER: (text) => String(text).toUpperCase(),
189
- LOWER: (text) => String(text).toLowerCase(),
190
- CONCAT: (...strings) => strings.map(String).join(''),
191
- LENGTH: (text) => String(text).length,
192
- TRIM: (text) => String(text).trim(),
193
- SUBSTRING: (text, start, length) => length !== undefined
194
- ? String(text).substr(start, length)
195
- : String(text).substr(start),
196
- // Date Functions
197
- TODAY: () => {
198
- const value = dFormatter(new Date());
199
- return value;
200
- },
201
- AGE: (birthDate) => {
202
- const birth = typeof birthDate === 'string' ? new Date(birthDate) : birthDate;
203
- const today = new Date();
204
- let age = today.getFullYear() - birth.getFullYear();
205
- const monthDiff = today.getMonth() - birth.getMonth();
206
- if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birth.getDate())) {
207
- age--;
208
- }
209
- return age;
210
- },
211
- DATEFORMAT: (date, format) => {
212
- const d = typeof date === 'string' ? new Date(date) : date;
213
- const formatter = intl.getDateFormat({
214
- format: format,
215
- type: 'date'
216
- });
217
- // Simple date format implementation
218
- return formatter(d);
219
- },
220
- DATEDIFF: (date1, date2) => {
221
- const d1 = typeof date1 === 'string' ? new Date(date1) : date1;
222
- const d2 = typeof date2 === 'string' ? new Date(date2) : date2;
223
- return Math.floor((d2.getTime() - d1.getTime()) / (1000 * 60 * 60 * 24));
224
- },
225
- // Add days to a date (for date arithmetic like {date} + 5)
226
- ADDDAYS: (date, days) => {
227
- const d = typeof date === 'string' ? new Date(date) : new Date(date);
228
- d.setDate(d.getDate() + Number(days));
229
- return dFormatter(d);
230
- },
231
- // Add days to a date (for date arithmetic like {date} + 5)
232
- SUBDAYS: (date, days) => {
233
- const d = typeof date === 'string' ? new Date(date) : new Date(date);
234
- d.setDate(d.getDate() - Number(days));
235
- return dFormatter(d);
236
- }
237
- };
238
- }
239
- }
240
- /**
241
- * ExpressionValidator - Validates expressions before runtime
242
- *
243
- * Checks syntax, validates field references, and detects circular dependencies
244
- * @private
245
- * @spec Expression_Engine.md
246
- */
247
- export class ExpressionValidator {
248
- supportedFunctions = [
249
- 'SUM', 'AVG', 'MIN', 'MAX', 'COUNT',
250
- 'ROUND', 'FLOOR', 'CEIL', 'ABS', 'SQRT',
251
- 'UPPER', 'LOWER', 'CONCAT', 'LENGTH', 'TRIM', 'SUBSTRING',
252
- 'TODAY', 'AGE', 'DATEFORMAT', 'DATEDIFF', 'ADDDAYS', 'SUBDAYS'
253
- ];
254
- keywords = ['PI', 'TRUE', 'FALSE', 'NULL', 'UNDEFINED', 'and', 'or', 'in'];
255
- /**
256
- * Validate expression against available fields
257
- */
258
- validate(expression, availableFields) {
259
- const errors = [];
260
- if (!expression || expression.trim() === '') {
261
- return errors; // Empty expression is valid
262
- }
263
- // Check syntax
264
- const syntaxErrors = this.validateSyntax(expression);
265
- errors.push(...syntaxErrors);
266
- // Check field references
267
- const fieldErrors = this.validateFieldReferences(expression, availableFields);
268
- errors.push(...fieldErrors);
269
- // Check function calls
270
- const funcErrors = this.validateFunctionCalls(expression);
271
- errors.push(...funcErrors);
272
- // Check field operations based on types
273
- const opErrors = this.validateFieldOperations(expression, availableFields);
274
- errors.push(...opErrors);
275
- return errors;
276
- }
277
- /**
278
- * Validate field operations - check for invalid operators on string/date types
279
- */
280
- validateFieldOperations(expression, availableFields) {
281
- const errors = [];
282
- // Skip if not field objects with type info
283
- if (!Array.isArray(availableFields) || availableFields.length === 0) {
284
- return errors;
285
- }
286
- const firstField = availableFields[0];
287
- if (typeof firstField === 'string') {
288
- return errors; // No type information available
289
- }
290
- // Build field map from objects
291
- const fieldMap = new Map();
292
- availableFields.forEach(field => {
293
- if (field && field.name) {
294
- fieldMap.set(field.name, field);
295
- }
296
- });
297
- if (fieldMap.size === 0)
298
- return errors;
299
- // Track reported operator positions to avoid duplicates
300
- const reportedOperators = new Set();
301
- // Check each field reference for invalid operations
302
- const fieldRefPattern = /(\{[^}]+\})/g;
303
- let match;
304
- // Invalid operators for string fields (allow only + for concatenation)
305
- const stringInvalidOps = ['-', '*', '/', '%', '^'];
306
- // Operators invalid for date fields (allow + for date arithmetic with numbers)
307
- const dateInvalidOps = ['-', '*', '/', '%', '^', '+'];
308
- while ((match = fieldRefPattern.exec(expression)) !== null) {
309
- const fieldRef = match[0];
310
- const fieldName = fieldRef.slice(1, -1); // Remove { }
311
- const field = fieldMap.get(fieldName);
312
- if (!field)
313
- continue;
314
- const fieldIndex = match.index;
315
- const fieldEndIndex = fieldIndex + fieldRef.length;
316
- // Get context around field reference (including spaces)
317
- const textBefore = expression.substring(Math.max(0, fieldIndex - 10), fieldIndex);
318
- const textAfter = expression.substring(fieldEndIndex, Math.min(expression.length, fieldEndIndex + 10));
319
- // Extract the operator if present (skip spaces)
320
- const operatorBefore = textBefore.trimEnd().slice(-1);
321
- const operatorAfter = textAfter.trimStart().charAt(0);
322
- // Calculate actual position of operators in the original expression
323
- const operatorBeforePos = fieldIndex - (textBefore.length - textBefore.trimEnd().length) - 1;
324
- const operatorAfterPos = fieldEndIndex + (textAfter.length - textAfter.trimStart().length);
325
- // For STRING fields (without format)
326
- if (field.type === 'string' && !field.format) {
327
- if (operatorBefore && stringInvalidOps.includes(operatorBefore)) {
328
- const key = `${operatorBeforePos}:${operatorBefore}`;
329
- if (!reportedOperators.has(key)) {
330
- errors.push({
331
- type: 'InvalidOperation',
332
- message: `Cannot perform '${operatorBefore}' operation on string field '{${fieldName}}'`
333
- });
334
- reportedOperators.add(key);
335
- }
336
- }
337
- if (operatorAfter && stringInvalidOps.includes(operatorAfter)) {
338
- const key = `${operatorAfterPos}:${operatorAfter}`;
339
- if (!reportedOperators.has(key)) {
340
- errors.push({
341
- type: 'InvalidOperation',
342
- message: `Cannot perform '${operatorAfter}' operation on string field '{${fieldName}}'`
343
- });
344
- reportedOperators.add(key);
345
- }
346
- }
347
- }
348
- // For DATE fields - allow + with numbers, reject other operators
349
- if (field.format === 'date' || field.format === 'date-time' || field.format === 'time') {
350
- if (operatorBefore && dateInvalidOps.includes(operatorBefore)) {
351
- const key = `${operatorBeforePos}:${operatorBefore}`;
352
- if (!reportedOperators.has(key)) {
353
- errors.push({
354
- type: 'InvalidOperation',
355
- message: `Cannot perform '${operatorBefore}' operation on date field '{${fieldName}}'`
356
- });
357
- reportedOperators.add(key);
358
- }
359
- }
360
- if (operatorAfter && dateInvalidOps.includes(operatorAfter)) {
361
- const key = `${operatorAfterPos}:${operatorAfter}`;
362
- if (!reportedOperators.has(key)) {
363
- errors.push({
364
- type: 'InvalidOperation',
365
- message: `Cannot perform '${operatorAfter}' operation on date field '{${fieldName}}'`
366
- });
367
- reportedOperators.add(key);
368
- }
369
- }
370
- // Allow + only when combined with numbers or number fields
371
- if (operatorAfter === '+') {
372
- const operandAfter = textAfter.trimStart().substring(1).trim();
373
- const isNumberField = /^\{[^}]+\}/.test(operandAfter) && fieldMap.get(operandAfter.slice(1, -1))?.type === 'number';
374
- const isNumberLiteral = /^\d+/.test(operandAfter);
375
- if (!isNumberField && !isNumberLiteral) {
376
- const key = `${operatorAfterPos}:${operatorAfter}`;
377
- if (!reportedOperators.has(key)) {
378
- errors.push({
379
- type: 'InvalidOperation',
380
- message: `Date field '{${fieldName}}' can only be used with +number or +{numberField}, not '${operatorAfter}${operandAfter.substring(0, 10)}'`
381
- });
382
- reportedOperators.add(key);
383
- }
384
- }
385
- }
386
- if (operatorBefore === '+') {
387
- const operandBefore = textBefore.trimEnd().substring(0, textBefore.trimEnd().length - 1).trim();
388
- const isNumberField = /\{[^}]+\}$/.test(operandBefore) && fieldMap.get(operandBefore.slice(operandBefore.lastIndexOf('{') + 1, -1))?.type === 'number';
389
- const isNumberLiteral = /\d+$/.test(operandBefore);
390
- if (!isNumberField && !isNumberLiteral) {
391
- const key = `${operatorBeforePos}:${operatorBefore}`;
392
- if (!reportedOperators.has(key)) {
393
- errors.push({
394
- type: 'InvalidOperation',
395
- message: `Date field '{${fieldName}}' can only be used with +number or +{numberField}`
396
- });
397
- reportedOperators.add(key);
398
- }
399
- }
400
- }
401
- }
402
- }
403
- return errors;
404
- }
405
- validateSyntax(expression) {
406
- const errors = [];
407
- // Check balanced braces
408
- const openBraces = (expression.match(/\{/g) || []).length;
409
- const closeBraces = (expression.match(/\}/g) || []).length;
410
- if (openBraces !== closeBraces) {
411
- errors.push({
412
- type: 'SyntaxError',
413
- message: 'Unbalanced braces in expression'
414
- });
415
- }
416
- // Check balanced parentheses
417
- const openParens = (expression.match(/\(/g) || []).length;
418
- const closeParens = (expression.match(/\)/g) || []).length;
419
- if (openParens !== closeParens) {
420
- errors.push({
421
- type: 'SyntaxError',
422
- message: 'Unbalanced parentheses in expression'
423
- });
424
- }
425
- // Check balanced $ brackets
426
- const openDollar = (expression.match(/\$\{/g) || []).length;
427
- const closeDollar = (expression.match(/\}/g) || []).length;
428
- if (openDollar > closeDollar) {
429
- errors.push({
430
- type: 'SyntaxError',
431
- message: 'Unbalanced ${ } in expression'
432
- });
433
- }
434
- return errors;
435
- }
436
- /**
437
- * @private
438
- */
439
- validateFieldReferences(expression, availableFields) {
440
- const errors = [];
441
- // Skip field validation if availableFields is not properly populated
442
- // This allows expressions to work at runtime even if we can't validate field names at editor time
443
- if (!availableFields || availableFields.length === 0) {
444
- return errors;
445
- }
446
- const fieldRefPattern = /\{([^{}]+)\}/g;
447
- let match;
448
- // Handle both string[] and AvailableField[] formats
449
- let availableFieldsSet;
450
- if (typeof availableFields[0] === 'string') {
451
- availableFieldsSet = new Set(availableFields);
452
- }
453
- else {
454
- availableFieldsSet = new Set(availableFields.map(f => f.name));
455
- }
456
- while ((match = fieldRefPattern.exec(expression)) !== null) {
457
- const ref = match[1].trim();
458
- // Skip if it's a function call
459
- if (this.isFunctionCall(ref))
460
- continue;
461
- // Skip if it's a math expression (contains operators or numbers)
462
- // This distinguishes between {fieldName} and ${5 + 3}
463
- if (this.isMathExpression(ref))
464
- continue;
465
- // Extract field name from nested paths (e.g., "customer.name" -> "customer")
466
- const fieldName = ref.split(/[.\[\]]/)[0].trim();
467
- // Check if field exists
468
- if (fieldName && !this.keywords.includes(fieldName.toUpperCase())) {
469
- if (!availableFieldsSet.has(fieldName)) {
470
- errors.push({
471
- type: 'UndefinedField',
472
- message: `Field '${fieldName}' is not defined in the form`,
473
- field: fieldName
474
- });
475
- }
476
- }
477
- }
478
- return errors;
479
- }
480
- validateFunctionCalls(expression) {
481
- const errors = [];
482
- const funcPattern = /(\w+)\s*\(/g;
483
- let match;
484
- while ((match = funcPattern.exec(expression)) !== null) {
485
- const funcName = match[1];
486
- // Only validate uppercase functions (user-defined lowercase functions might be allowed)
487
- if (funcName === funcName.toUpperCase() && !this.supportedFunctions.includes(funcName)) {
488
- errors.push({
489
- type: 'InvalidFunction',
490
- message: `Unknown function: '${funcName}'. Supported functions: ${this.supportedFunctions.join(', ')}`
491
- });
492
- }
493
- }
494
- return errors;
495
- }
496
- isFunctionCall(ref) {
497
- return /^\w+\s*\(/.test(ref);
498
- }
499
- isMathExpression(ref) {
500
- // Check if the expression contains math operators, numbers, or other non-field-name patterns
501
- // Math expressions like: "5 + 3", "qty * 2", "age > 18", "sum([1,2,3])"
502
- // should be skipped, while field references like "fieldName" or "customer.name" should be validated
503
- const mathOperatorPattern = /[\+\-\*\/\%\=\<\>\!\&\|\^\~\s\d]/;
504
- return mathOperatorPattern.test(ref);
505
- }
506
- }
@@ -1,41 +0,0 @@
1
- /**
2
- * Field type information for validation
3
- * @private
4
- */
5
- export interface AvailableField {
6
- name: string;
7
- type: string;
8
- format?: string;
9
- }
10
- /**
11
- * ExpressionParser - Parse and compile expressions
12
- *
13
- * Converts string expressions into safe, compiled functions
14
- * Supports field references, operators, functions, and conditionals
15
- * @private
16
- * @spec Expression_Engine.md
17
- */
18
- export declare class ExpressionParser {
19
- private supportedFunctions;
20
- private availableFields;
21
- /**
22
- * Initialize parser with available fields for type validation
23
- */
24
- setAvailableFields(fields: AvailableField[]): void;
25
- private validateFieldOperations;
26
- /**
27
- * Validate expression syntax
28
- */
29
- validate(expression: string): {
30
- valid: boolean;
31
- errors: string[];
32
- };
33
- /**
34
- * Compile expression to executable function
35
- */
36
- compile(expression: string, options?: any): any;
37
- private compileMatrixExpressions;
38
- private compileBareFunctionCalls;
39
- private compileFieldReferences;
40
- private compileInputReferences;
41
- }