@syncfusion/ej2-react-form-builder 0.1.0-beta
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/Common/CheckboxGroup.d.ts +13 -0
- package/Common/CheckboxGroup.js +50 -0
- package/Common/ConditionalRuleEngine.d.ts +296 -0
- package/Common/ConditionalRuleEngine.js +566 -0
- package/Common/CustomTabComponent.d.ts +17 -0
- package/Common/CustomTabComponent.js +30 -0
- package/Common/CustomValidationEngine.d.ts +58 -0
- package/Common/CustomValidationEngine.js +146 -0
- package/Common/DataGridComponent.d.ts +20 -0
- package/Common/DataGridComponent.js +273 -0
- package/Common/FormValidator.d.ts +485 -0
- package/Common/FormValidator.js +575 -0
- package/Common/MinMaxRuleEngine.d.ts +105 -0
- package/Common/MinMaxRuleEngine.js +365 -0
- package/Common/StaticHtmlRenderer.d.ts +14 -0
- package/Common/StaticHtmlRenderer.js +24 -0
- package/Common/expressions/DependencyTracker.d.ts +30 -0
- package/Common/expressions/DependencyTracker.js +128 -0
- package/Common/expressions/ExpressionEngine.d.ts +95 -0
- package/Common/expressions/ExpressionEngine.js +506 -0
- package/Common/expressions/ExpressionParser.d.ts +41 -0
- package/Common/expressions/ExpressionParser.js +269 -0
- package/Common/expressions/useGridExpressions.d.ts +12 -0
- package/Common/expressions/useGridExpressions.js +71 -0
- package/Common/globalFormId.d.ts +26 -0
- package/Common/globalFormId.js +45 -0
- package/Common/json-converter.d.ts +444 -0
- package/Common/json-converter.js +1184 -0
- package/Common/parser.d.ts +98 -0
- package/Common/parser.js +753 -0
- package/Common/utils.d.ts +130 -0
- package/Common/utils.js +574 -0
- package/README.md +53 -0
- package/index.d.ts +6 -0
- package/index.js +6 -0
- package/license +13 -0
- package/package.json +54 -0
- package/src/FormBuilder.d.ts +211 -0
- package/src/FormBuilder.js +1299 -0
- package/src/components/Database/ConnectionForm.d.ts +8 -0
- package/src/components/Database/ConnectionForm.js +28 -0
- package/src/components/TopBar.d.ts +10 -0
- package/src/components/TopBar.js +7 -0
- package/src/components/icons/ComponentIcons.d.ts +44 -0
- package/src/components/icons/ComponentIcons.js +40 -0
- package/src/components/layout/FormFieldWrapper.d.ts +41 -0
- package/src/components/layout/FormFieldWrapper.js +112 -0
- package/src/components/layout/LeftPane.d.ts +23 -0
- package/src/components/layout/LeftPane.js +310 -0
- package/src/components/layout/ListBoxPane.d.ts +24 -0
- package/src/components/layout/ListBoxPane.js +94 -0
- package/src/components/layout/MainSection.d.ts +39 -0
- package/src/components/layout/MainSection.js +1766 -0
- package/src/components/layout/Preview.d.ts +12 -0
- package/src/components/layout/Preview.js +337 -0
- package/src/components/layout/RightPane.d.ts +22 -0
- package/src/components/layout/RightPane.js +370 -0
- package/src/components/theme/ThemeSwitcher.d.ts +14 -0
- package/src/components/theme/ThemeSwitcher.js +24 -0
- package/src/components/theme/theme-constant.d.ts +37 -0
- package/src/components/theme/theme-constant.js +23 -0
- package/src/config/component-properties.json +4929 -0
- package/src/config/form-setting.json +56 -0
- package/src/config/locale.d.ts +449 -0
- package/src/config/locale.js +449 -0
- package/src/context/FormSchemaContext.d.ts +32 -0
- package/src/context/FormSchemaContext.js +155 -0
- package/src/context/LocalizationContext.d.ts +27 -0
- package/src/context/LocalizationContext.js +43 -0
- package/src/context/ThemeContext.d.ts +23 -0
- package/src/context/ThemeContext.js +161 -0
- package/src/context/contexts.d.ts +6 -0
- package/src/context/contexts.js +6 -0
- package/src/context/defaultFormSchema.d.ts +6 -0
- package/src/context/defaultFormSchema.js +20 -0
- package/src/defaultPalette.d.ts +9 -0
- package/src/defaultPalette.js +45 -0
- package/src/defaultTemplate.d.ts +3 -0
- package/src/defaultTemplate.js +185 -0
- package/src/import/schemaImportUtils.d.ts +24 -0
- package/src/import/schemaImportUtils.js +121 -0
- package/src/index.d.ts +16 -0
- package/src/index.js +6 -0
- package/src/types/ActionButtons.d.ts +44 -0
- package/src/types/ActionButtons.js +27 -0
- package/src/types/ComponentProperties.d.ts +33 -0
- package/src/types/ComponentProperties.js +2 -0
- package/src/types/FormSchema.d.ts +585 -0
- package/src/types/FormSchema.js +147 -0
- package/src/types/connection.d.ts +19 -0
- package/src/types/connection.js +9 -0
- package/src/utils/AttributesEditor.d.ts +13 -0
- package/src/utils/AttributesEditor.js +117 -0
- package/src/utils/ButtonGroupEditor.d.ts +15 -0
- package/src/utils/ButtonGroupEditor.js +141 -0
- package/src/utils/ChoiceBasedFieldEditor.d.ts +16 -0
- package/src/utils/ChoiceBasedFieldEditor.js +87 -0
- package/src/utils/ClipboardHelper.d.ts +26 -0
- package/src/utils/ClipboardHelper.js +83 -0
- package/src/utils/ColumnBuilder.d.ts +11 -0
- package/src/utils/ColumnBuilder.js +153 -0
- package/src/utils/ColumnSpanEditor.d.ts +8 -0
- package/src/utils/ColumnSpanEditor.js +98 -0
- package/src/utils/ConditionDataEditor.d.ts +29 -0
- package/src/utils/ConditionDataEditor.js +177 -0
- package/src/utils/ConditionalEditorUtils.d.ts +27 -0
- package/src/utils/ConditionalEditorUtils.js +310 -0
- package/src/utils/ConditionsEditor.d.ts +20 -0
- package/src/utils/ConditionsEditor.js +571 -0
- package/src/utils/CustomAccordion.d.ts +13 -0
- package/src/utils/CustomAccordion.js +38 -0
- package/src/utils/CustomValidationEditor.d.ts +11 -0
- package/src/utils/CustomValidationEditor.js +208 -0
- package/src/utils/DataSourceCollector.d.ts +3 -0
- package/src/utils/DataSourceCollector.js +252 -0
- package/src/utils/ExpressionEditor.d.ts +13 -0
- package/src/utils/ExpressionEditor.js +99 -0
- package/src/utils/FormSettingsPanel.d.ts +9 -0
- package/src/utils/FormSettingsPanel.js +60 -0
- package/src/utils/LookupConfig.d.ts +11 -0
- package/src/utils/LookupConfig.js +159 -0
- package/src/utils/MinMaxValueEditor.d.ts +25 -0
- package/src/utils/MinMaxValueEditor.js +188 -0
- package/src/utils/OptionsEditor.d.ts +8 -0
- package/src/utils/OptionsEditor.js +79 -0
- package/src/utils/PropertyRenderer.d.ts +22 -0
- package/src/utils/PropertyRenderer.js +210 -0
- package/src/utils/ValueRenderer.d.ts +2 -0
- package/src/utils/ValueRenderer.js +91 -0
- package/src/utils/ej2DbAdaptor.d.ts +6 -0
- package/src/utils/ej2DbAdaptor.js +38 -0
- package/src/utils/mappingHeuristics.d.ts +10 -0
- package/src/utils/mappingHeuristics.js +33 -0
- package/src/utils/renderComponent.d.ts +18 -0
- package/src/utils/renderComponent.js +300 -0
- package/styles/tailwind3.css +67 -0
- package/styles/themes/action-buttons.css +110 -0
- package/styles/themes/datagrid.css +380 -0
- package/styles/themes/drag-reorder.css +248 -0
- package/styles/themes/import-dialog.css +292 -0
- package/styles/themes/index.css +658 -0
- package/styles/themes/left-pane.css +253 -0
- package/styles/themes/options-editor.css +63 -0
- package/styles/themes/preview.css +702 -0
- package/styles/themes/property-dialog.css +371 -0
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ExpressionParser - Parse and compile expressions
|
|
3
|
+
*
|
|
4
|
+
* Converts string expressions into safe, compiled functions
|
|
5
|
+
* Supports field references, operators, functions, and conditionals
|
|
6
|
+
* @private
|
|
7
|
+
* @spec Expression_Engine.md
|
|
8
|
+
*/
|
|
9
|
+
export class ExpressionParser {
|
|
10
|
+
supportedFunctions = [
|
|
11
|
+
'SUM', 'AVG', 'MIN', 'MAX', 'COUNT',
|
|
12
|
+
'ROUND', 'FLOOR', 'CEIL', 'ABS', 'SQRT',
|
|
13
|
+
'UPPER', 'LOWER', 'CONCAT', 'LENGTH', 'TRIM', 'SUBSTRING',
|
|
14
|
+
'TODAY', 'AGE', 'DATEFORMAT', 'DATEDIFF', 'ADDDAYS', 'SUBDAYS', 'INCLUDES', 'CONTAINS', 'ISNAN', 'SUPPORTEDVALUESOF', 'TOSTRING', 'DATE', 'GETTIME',
|
|
15
|
+
'EVERY', 'SOME'
|
|
16
|
+
];
|
|
17
|
+
availableFields = new Map();
|
|
18
|
+
/**
|
|
19
|
+
* Initialize parser with available fields for type validation
|
|
20
|
+
*/
|
|
21
|
+
setAvailableFields(fields) {
|
|
22
|
+
this.availableFields.clear();
|
|
23
|
+
fields.forEach(field => {
|
|
24
|
+
this.availableFields.set(field.name, field);
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
validateFieldOperations(expression) {
|
|
28
|
+
const errors = [];
|
|
29
|
+
// Skip if no fields available
|
|
30
|
+
if (this.availableFields.size === 0) {
|
|
31
|
+
return errors;
|
|
32
|
+
}
|
|
33
|
+
// Invalid operators for string fields (allow only + for concatenation)
|
|
34
|
+
const stringInvalidOps = ['-', '*', '/', '%', '^'];
|
|
35
|
+
// All operators invalid for date fields
|
|
36
|
+
const dateInvalidOps = ['-', '*', '/', '%', '^', '+'];
|
|
37
|
+
// Track reported operator positions to avoid duplicates
|
|
38
|
+
const reportedOperators = new Set();
|
|
39
|
+
// Extract all field references and their surrounding operators
|
|
40
|
+
const fieldRefPattern = /(\{[^}]+\})/g;
|
|
41
|
+
let match;
|
|
42
|
+
while ((match = fieldRefPattern.exec(expression)) !== null) {
|
|
43
|
+
const fieldRef = match[0];
|
|
44
|
+
const fieldName = fieldRef.slice(1, -1); // Remove { }
|
|
45
|
+
const field = this.availableFields.get(fieldName);
|
|
46
|
+
if (!field)
|
|
47
|
+
continue; // Field not found in available fields, skip validation
|
|
48
|
+
const fieldIndex = match.index;
|
|
49
|
+
const fieldEndIndex = fieldIndex + fieldRef.length;
|
|
50
|
+
// Get context around field reference (including spaces)
|
|
51
|
+
const textBefore = expression.substring(Math.max(0, fieldIndex - 10), fieldIndex);
|
|
52
|
+
const textAfter = expression.substring(fieldEndIndex, Math.min(expression.length, fieldEndIndex + 10));
|
|
53
|
+
// Extract the operator if present (skip spaces)
|
|
54
|
+
const operatorBefore = textBefore.trimEnd().slice(-1);
|
|
55
|
+
const operatorAfter = textAfter.trimStart().charAt(0);
|
|
56
|
+
// Calculate actual position of operators in the original expression
|
|
57
|
+
const operatorBeforePos = fieldIndex - (textBefore.length - textBefore.trimEnd().length) - 1;
|
|
58
|
+
const operatorAfterPos = fieldEndIndex + (textAfter.length - textAfter.trimStart().length);
|
|
59
|
+
// For STRING fields: reject all operators except +
|
|
60
|
+
if (field.type === 'string' && !field.format) {
|
|
61
|
+
if (operatorBefore && stringInvalidOps.includes(operatorBefore)) {
|
|
62
|
+
const key = `${operatorBeforePos}:${operatorBefore}`;
|
|
63
|
+
if (!reportedOperators.has(key)) {
|
|
64
|
+
errors.push(`Cannot perform '${operatorBefore}' operation on string field '{${fieldName}}'`);
|
|
65
|
+
reportedOperators.add(key);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (operatorAfter && stringInvalidOps.includes(operatorAfter)) {
|
|
69
|
+
const key = `${operatorAfterPos}:${operatorAfter}`;
|
|
70
|
+
if (!reportedOperators.has(key)) {
|
|
71
|
+
errors.push(`Cannot perform '${operatorAfter}' operation on string field '{${fieldName}}'`);
|
|
72
|
+
reportedOperators.add(key);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// For DATE fields: reject ALL operators including +
|
|
77
|
+
if (field.format === 'date' || field.format === 'date-time' || field.format === 'time') {
|
|
78
|
+
if (operatorBefore && dateInvalidOps.includes(operatorBefore)) {
|
|
79
|
+
const key = `${operatorBeforePos}:${operatorBefore}`;
|
|
80
|
+
if (!reportedOperators.has(key)) {
|
|
81
|
+
errors.push(`Cannot perform '${operatorBefore}' operation on date field '{${fieldName}}'`);
|
|
82
|
+
reportedOperators.add(key);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (operatorAfter && dateInvalidOps.includes(operatorAfter)) {
|
|
86
|
+
const key = `${operatorAfterPos}:${operatorAfter}`;
|
|
87
|
+
if (!reportedOperators.has(key)) {
|
|
88
|
+
errors.push(`Cannot perform '${operatorAfter}' operation on date field '{${fieldName}}'`);
|
|
89
|
+
reportedOperators.add(key);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return errors;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Validate expression syntax
|
|
98
|
+
*/
|
|
99
|
+
validate(expression) {
|
|
100
|
+
const errors = [];
|
|
101
|
+
if (!expression || expression.trim() === '') {
|
|
102
|
+
return { valid: true, errors: [] };
|
|
103
|
+
}
|
|
104
|
+
// Check for unbalanced braces
|
|
105
|
+
const openBraces = (expression.match(/\{/g) || []).length;
|
|
106
|
+
const closeBraces = (expression.match(/\}/g) || []).length;
|
|
107
|
+
if (openBraces !== closeBraces) {
|
|
108
|
+
errors.push('Unbalanced braces in expression');
|
|
109
|
+
}
|
|
110
|
+
// Check for unbalanced parentheses
|
|
111
|
+
const openParens = (expression.match(/\(/g) || []).length;
|
|
112
|
+
const closeParens = (expression.match(/\)/g) || []).length;
|
|
113
|
+
if (openParens !== closeParens) {
|
|
114
|
+
errors.push('Unbalanced parentheses in expression');
|
|
115
|
+
}
|
|
116
|
+
// Check for invalid function calls
|
|
117
|
+
const funcPattern = /(\w+)\s*\(/g;
|
|
118
|
+
let match;
|
|
119
|
+
while ((match = funcPattern.exec(expression)) !== null) {
|
|
120
|
+
const funcName = match[1].toUpperCase();
|
|
121
|
+
// Only validate uppercase-named functions
|
|
122
|
+
if (funcName === funcName.toUpperCase() && !this.supportedFunctions.includes(funcName)) {
|
|
123
|
+
errors.push(`Unknown function: ${funcName}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
// Validate field operations based on field types
|
|
127
|
+
const fieldOpErrors = this.validateFieldOperations(expression);
|
|
128
|
+
errors.push(...fieldOpErrors);
|
|
129
|
+
return {
|
|
130
|
+
valid: errors.length === 0,
|
|
131
|
+
errors
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Compile expression to executable function
|
|
136
|
+
*/
|
|
137
|
+
compile(expression, options = {}) {
|
|
138
|
+
try {
|
|
139
|
+
// Set available fields for validation if provided
|
|
140
|
+
if (options.availableFields && Array.isArray(options.availableFields)) {
|
|
141
|
+
this.setAvailableFields(options.availableFields);
|
|
142
|
+
}
|
|
143
|
+
// Validate expression before compiling
|
|
144
|
+
const validation = this.validate(expression);
|
|
145
|
+
if (!validation.valid) {
|
|
146
|
+
console.error('Expression validation errors:', validation.errors, { expression });
|
|
147
|
+
return () => null;
|
|
148
|
+
}
|
|
149
|
+
// Replace field references with context lookups
|
|
150
|
+
let compiled = expression;
|
|
151
|
+
// Handle field references FIRST (this includes refs inside ${...})
|
|
152
|
+
compiled = this.compileFieldReferences(compiled);
|
|
153
|
+
compiled = options.isCustomValidation ? this.compileInputReferences(compiled) : compiled;
|
|
154
|
+
// Handle bare function calls SECOND (e.g., SUM([...]) without braces)
|
|
155
|
+
compiled = this.compileBareFunctionCalls(compiled);
|
|
156
|
+
// Handle math expressions THIRD (now ${} and {} contain already-replaced refs)
|
|
157
|
+
compiled = this.compileMatrixExpressions(compiled);
|
|
158
|
+
// Create function that safely evaluates the expression
|
|
159
|
+
return (context) => {
|
|
160
|
+
try {
|
|
161
|
+
// This uses Function constructor with controlled context binding
|
|
162
|
+
// which is safer than eval() but still allows expressions
|
|
163
|
+
// Note: compiled expression already uses context['field'] syntax
|
|
164
|
+
const func = new Function('context', `
|
|
165
|
+
try {
|
|
166
|
+
return ${compiled};
|
|
167
|
+
} catch (e) {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
`);
|
|
171
|
+
return func(context);
|
|
172
|
+
}
|
|
173
|
+
catch (e) {
|
|
174
|
+
console.error('Expression execution error:', e, { expression, compiled });
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
console.error('Expression compilation error:', error, { expression });
|
|
181
|
+
return () => null;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
// Private helpers
|
|
185
|
+
compileMatrixExpressions(expression) {
|
|
186
|
+
let compiled = expression;
|
|
187
|
+
const constants = ['PI', 'TRUE', 'FALSE', 'NULL', 'UNDEFINED', 'Math'];
|
|
188
|
+
const functions = [
|
|
189
|
+
'SUM', 'AVG', 'MIN', 'MAX', 'COUNT',
|
|
190
|
+
'ROUND', 'FLOOR', 'CEIL', 'ABS', 'SQRT',
|
|
191
|
+
'UPPER', 'LOWER', 'CONCAT', 'LENGTH', 'TRIM', 'SUBSTRING',
|
|
192
|
+
'TODAY', 'AGE', 'DATEFORMAT', 'DATEDIFF', 'ADDDAYS', 'SUBDAYS'
|
|
193
|
+
];
|
|
194
|
+
// Create fresh regex each time to avoid lastIndex issues with g flag
|
|
195
|
+
const mathExprPattern = /\$?\{([^}]+)\}/g;
|
|
196
|
+
// Process ${...} and {...} blocks: replace bare identifiers with context lookups
|
|
197
|
+
compiled = compiled.replace(mathExprPattern, (_match, content) => {
|
|
198
|
+
let transformed = content;
|
|
199
|
+
// STEP 1: Handle function calls first
|
|
200
|
+
// Match function names followed by ( and prefix with context.
|
|
201
|
+
// Use negative lookbehind to avoid double-replacing (e.g., context.SUM)
|
|
202
|
+
functions.forEach(fn => {
|
|
203
|
+
const funcCallRegex = new RegExp(`(?<!context\\.)\\b${fn}\\b(?=\\s*\\()`, 'g');
|
|
204
|
+
transformed = transformed.replace(funcCallRegex, `context.${fn}`);
|
|
205
|
+
});
|
|
206
|
+
constants.forEach(constant => {
|
|
207
|
+
const constantRegex = new RegExp(`\\b${constant}\\b`, 'g');
|
|
208
|
+
transformed = transformed.replace(constantRegex, `context.${constant}`);
|
|
209
|
+
});
|
|
210
|
+
// Return as parenthesized expression
|
|
211
|
+
return `(${transformed})`;
|
|
212
|
+
});
|
|
213
|
+
return compiled;
|
|
214
|
+
}
|
|
215
|
+
compileBareFunctionCalls(expression) {
|
|
216
|
+
let compiled = expression;
|
|
217
|
+
const functions = [
|
|
218
|
+
'SUM', 'AVG', 'MIN', 'MAX', 'COUNT',
|
|
219
|
+
'ROUND', 'FLOOR', 'CEIL', 'ABS', 'SQRT',
|
|
220
|
+
'UPPER', 'LOWER', 'CONCAT', 'LENGTH', 'TRIM', 'SUBSTRING',
|
|
221
|
+
'TODAY', 'AGE', 'DATEFORMAT', 'DATEDIFF', 'ADDDAYS', 'SUBDAYS'
|
|
222
|
+
];
|
|
223
|
+
// Handle bare function calls like SUM([...]) that are not inside ${...} or {...}
|
|
224
|
+
// Use negative lookbehind to avoid matching if already preceded by context.
|
|
225
|
+
functions.forEach(fn => {
|
|
226
|
+
const funcCallRegex = new RegExp(`(?<!context\\.)\\b${fn}\\b(?=\\s*\\()`, 'g');
|
|
227
|
+
compiled = compiled.replace(funcCallRegex, `context.${fn}`);
|
|
228
|
+
});
|
|
229
|
+
return compiled;
|
|
230
|
+
}
|
|
231
|
+
compileFieldReferences(expression) {
|
|
232
|
+
let compiled = expression;
|
|
233
|
+
// Create fresh regex each time to avoid lastIndex issues with g flag
|
|
234
|
+
const fieldRefPattern = /\{([^{}]+)\}/g;
|
|
235
|
+
// Replace {fieldName} with context['fieldName'] or field access
|
|
236
|
+
compiled = compiled.replace(fieldRefPattern, (match, ref) => {
|
|
237
|
+
// Handle escaped braces
|
|
238
|
+
if (ref.startsWith('\\') && ref.endsWith('\\')) {
|
|
239
|
+
return match; // Keep as is
|
|
240
|
+
}
|
|
241
|
+
// Handle nested paths like {customer.name}
|
|
242
|
+
return `context['${ref.replace(/\./g, '\'].[')}']`;
|
|
243
|
+
});
|
|
244
|
+
return compiled;
|
|
245
|
+
}
|
|
246
|
+
compileInputReferences(expression) {
|
|
247
|
+
const compiled = expression;
|
|
248
|
+
// First, protect string literals to avoid replacing 'input' inside strings
|
|
249
|
+
const strings = [];
|
|
250
|
+
let stringProtected = compiled;
|
|
251
|
+
stringProtected = stringProtected.replace(/(['"`])(?:(?=(\\?))\2.)*?\1/g, (match) => {
|
|
252
|
+
strings.push(match);
|
|
253
|
+
return `__STRING_${strings.length - 1}__`;
|
|
254
|
+
});
|
|
255
|
+
// Case 1: Replace input.property with context['input'].property
|
|
256
|
+
const inputPropertyPattern = /(?<!context\[')\binput\./g;
|
|
257
|
+
stringProtected = stringProtected.replace(inputPropertyPattern, 'context[\'input\'].');
|
|
258
|
+
// Case 2: Replace bare 'input' with context['input']
|
|
259
|
+
// Use negative lookbehind to avoid replacing if already preceded by context.'
|
|
260
|
+
const inputPattern = /(?<!context\[')\binput\b(?!\s*[(\.])/g;
|
|
261
|
+
stringProtected = stringProtected.replace(inputPattern, 'context[\'input\']');
|
|
262
|
+
// Restore string literals
|
|
263
|
+
let result = stringProtected;
|
|
264
|
+
strings.forEach((str, idx) => {
|
|
265
|
+
result = result.replace(`__STRING_${idx}__`, str);
|
|
266
|
+
});
|
|
267
|
+
return result;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ExpressionEngine } from './ExpressionEngine';
|
|
2
|
+
/**
|
|
3
|
+
* Hook to evaluate expressions for grid rows
|
|
4
|
+
* Returns a function that calculates all expression columns for a given row
|
|
5
|
+
* @private
|
|
6
|
+
*/
|
|
7
|
+
export declare const useGridExpressions: () => {
|
|
8
|
+
evaluateRowExpressions: (row: Record<string, any>, columns: any[]) => Record<string, any>;
|
|
9
|
+
extractCalculatedColumns: (columns: any[]) => any[];
|
|
10
|
+
engine: ExpressionEngine;
|
|
11
|
+
parseColumnValue: (value: any, columnType?: string) => any;
|
|
12
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
import { ExpressionEngine } from './ExpressionEngine';
|
|
3
|
+
/**
|
|
4
|
+
* Hook to evaluate expressions for grid rows
|
|
5
|
+
* Returns a function that calculates all expression columns for a given row
|
|
6
|
+
* @private
|
|
7
|
+
*/
|
|
8
|
+
export const useGridExpressions = () => {
|
|
9
|
+
const engine = new ExpressionEngine();
|
|
10
|
+
/**
|
|
11
|
+
* Convert column values to proper types (especially numbers)
|
|
12
|
+
* Form inputs return strings, so we need to parse numeric values
|
|
13
|
+
*/
|
|
14
|
+
const parseColumnValue = useCallback((value, columnType) => {
|
|
15
|
+
// Handle null/undefined
|
|
16
|
+
if (value === null || value === undefined || value === '') {
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
19
|
+
// Parse number types
|
|
20
|
+
if (columnType === 'number') {
|
|
21
|
+
const parsed = parseFloat(String(value));
|
|
22
|
+
return isNaN(parsed) ? 0 : parsed;
|
|
23
|
+
}
|
|
24
|
+
// Parse boolean types
|
|
25
|
+
if (columnType === 'boolean') {
|
|
26
|
+
if (typeof value === 'boolean')
|
|
27
|
+
return value;
|
|
28
|
+
return value === 'true' || value === '1' || value === 1 || value === true;
|
|
29
|
+
}
|
|
30
|
+
// Return as-is for string and other types
|
|
31
|
+
return value;
|
|
32
|
+
}, []);
|
|
33
|
+
const evaluateRowExpressions = useCallback((row, columns) => {
|
|
34
|
+
const calculatedRow = { ...row };
|
|
35
|
+
// Find all columns with expressions
|
|
36
|
+
const calculatedColumns = columns.filter(col => col.expression && col.expression.trim());
|
|
37
|
+
for (const column of calculatedColumns) {
|
|
38
|
+
if (!column.expression)
|
|
39
|
+
continue;
|
|
40
|
+
try {
|
|
41
|
+
// Prepare row values with proper type conversion
|
|
42
|
+
const typedRow = {};
|
|
43
|
+
columns.forEach((col) => {
|
|
44
|
+
const value = calculatedRow[col.field];
|
|
45
|
+
typedRow[col.field] = parseColumnValue(value, col.type);
|
|
46
|
+
});
|
|
47
|
+
// Evaluate expression with current row as context
|
|
48
|
+
const result = engine.evaluate(column.expression, typedRow, {
|
|
49
|
+
throwOnError: false
|
|
50
|
+
});
|
|
51
|
+
// Update row with calculated value
|
|
52
|
+
calculatedRow[column.field] = result;
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
console.error(`Failed to evaluate expression for column "${column.field}":`, error);
|
|
56
|
+
// Keep original value or set to null on error
|
|
57
|
+
calculatedRow[column.field] = null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return calculatedRow;
|
|
61
|
+
}, [parseColumnValue]);
|
|
62
|
+
const extractCalculatedColumns = useCallback((columns) => {
|
|
63
|
+
return columns.filter(col => col.expression && col.expression.trim());
|
|
64
|
+
}, []);
|
|
65
|
+
return {
|
|
66
|
+
evaluateRowExpressions,
|
|
67
|
+
extractCalculatedColumns,
|
|
68
|
+
engine,
|
|
69
|
+
parseColumnValue
|
|
70
|
+
};
|
|
71
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @private
|
|
3
|
+
*/
|
|
4
|
+
export declare const setFormId: (id: string | null) => void;
|
|
5
|
+
/**
|
|
6
|
+
* @private
|
|
7
|
+
*/
|
|
8
|
+
export declare const getFormId: () => string | null;
|
|
9
|
+
/**
|
|
10
|
+
* @private
|
|
11
|
+
*/
|
|
12
|
+
export declare const subscribeFormId: (cb: (id: string | null) => void) => () => boolean;
|
|
13
|
+
/**
|
|
14
|
+
* @private
|
|
15
|
+
*/
|
|
16
|
+
export declare const useFormId: () => string;
|
|
17
|
+
/**
|
|
18
|
+
* @private
|
|
19
|
+
*/
|
|
20
|
+
declare const _default: {
|
|
21
|
+
setFormId: (id: string | null) => void;
|
|
22
|
+
getFormId: () => string | null;
|
|
23
|
+
subscribeFormId: (cb: (id: string | null) => void) => () => boolean;
|
|
24
|
+
useFormId: () => string;
|
|
25
|
+
};
|
|
26
|
+
export default _default;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
let formId = null;
|
|
3
|
+
const listeners = new Set();
|
|
4
|
+
/**
|
|
5
|
+
* @private
|
|
6
|
+
*/
|
|
7
|
+
export const setFormId = (id) => {
|
|
8
|
+
formId = id;
|
|
9
|
+
listeners.forEach((cb) => cb(id));
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* @private
|
|
13
|
+
*/
|
|
14
|
+
export const getFormId = () => formId;
|
|
15
|
+
/**
|
|
16
|
+
* @private
|
|
17
|
+
*/
|
|
18
|
+
export const subscribeFormId = (cb) => {
|
|
19
|
+
listeners.add(cb);
|
|
20
|
+
return () => listeners.delete(cb);
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* @private
|
|
24
|
+
*/
|
|
25
|
+
export const useFormId = () => {
|
|
26
|
+
const [, setTick] = React.useState(0);
|
|
27
|
+
const [id, setId] = React.useState(formId);
|
|
28
|
+
React.useEffect(() => {
|
|
29
|
+
const unsub = subscribeFormId((newId) => {
|
|
30
|
+
setId(newId);
|
|
31
|
+
setTick((t) => t + 1);
|
|
32
|
+
});
|
|
33
|
+
return unsub;
|
|
34
|
+
}, []);
|
|
35
|
+
return id;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* @private
|
|
39
|
+
*/
|
|
40
|
+
export default {
|
|
41
|
+
setFormId,
|
|
42
|
+
getFormId,
|
|
43
|
+
subscribeFormId,
|
|
44
|
+
useFormId,
|
|
45
|
+
};
|