@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,365 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extract min/max constraints from minMaxRange array
|
|
3
|
+
* @private
|
|
4
|
+
*/
|
|
5
|
+
export class MinMaxRuleEngine {
|
|
6
|
+
/**
|
|
7
|
+
* Extract min/max constraints from minMaxRange array
|
|
8
|
+
* Handles three cases:
|
|
9
|
+
* 1. Direct values (enabled: false)
|
|
10
|
+
* 2. Single field reference (enabled: true, 1 range)
|
|
11
|
+
* 3. Multiple ranges (enabled: true, 2+ ranges) - takes UTMOST
|
|
12
|
+
* @private
|
|
13
|
+
* @param minMaxRange - Array of range configurations
|
|
14
|
+
* @param formValues - Current form state values (id → value)
|
|
15
|
+
* @param componentType - 'number' | 'date' | 'dateRange'
|
|
16
|
+
* @returns Constraints object with appropriate keys
|
|
17
|
+
*/
|
|
18
|
+
static getConstraints(minMaxRange, formValues, componentType = 'number') {
|
|
19
|
+
// No ranges provided
|
|
20
|
+
if (!minMaxRange || minMaxRange.length === 0) {
|
|
21
|
+
return this.getEmptyConstraints(componentType);
|
|
22
|
+
}
|
|
23
|
+
// First, check for CASE 1: Direct values (enabled: false)
|
|
24
|
+
const directValueRange = minMaxRange.find(range => range.enabled === false);
|
|
25
|
+
if (directValueRange) {
|
|
26
|
+
// Use direct values without field lookup
|
|
27
|
+
const minValue = this.resolveValue(directValueRange.minValue, {});
|
|
28
|
+
const maxValue = this.resolveValue(directValueRange.maxValue, {});
|
|
29
|
+
if (componentType === 'date' || componentType === 'dateRange') {
|
|
30
|
+
return {
|
|
31
|
+
minValue: undefined,
|
|
32
|
+
maxValue: undefined,
|
|
33
|
+
minDate: minValue,
|
|
34
|
+
maxDate: maxValue,
|
|
35
|
+
minTime: undefined,
|
|
36
|
+
maxTime: undefined
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
if (componentType === 'time') {
|
|
40
|
+
return {
|
|
41
|
+
minValue: undefined,
|
|
42
|
+
maxValue: undefined,
|
|
43
|
+
minDate: undefined,
|
|
44
|
+
maxDate: undefined,
|
|
45
|
+
minTime: minValue,
|
|
46
|
+
maxTime: maxValue
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
minValue,
|
|
51
|
+
maxValue,
|
|
52
|
+
minDate: undefined,
|
|
53
|
+
maxDate: undefined,
|
|
54
|
+
minTime: undefined,
|
|
55
|
+
maxTime: undefined
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
// CASE 2 & 3: Field references (enabled: true)
|
|
59
|
+
const enabledRanges = minMaxRange.filter(range => range.enabled === true);
|
|
60
|
+
// No enabled ranges - return empty
|
|
61
|
+
if (enabledRanges.length === 0) {
|
|
62
|
+
return this.getEmptyConstraints(componentType);
|
|
63
|
+
}
|
|
64
|
+
// Extract resolved values from each enabled range
|
|
65
|
+
const resolvedRanges = enabledRanges
|
|
66
|
+
.map(range => ({
|
|
67
|
+
min: this.resolveValue(range.minValue, formValues),
|
|
68
|
+
max: this.resolveValue(range.maxValue, formValues)
|
|
69
|
+
}))
|
|
70
|
+
.filter(range => range.min !== undefined || range.max !== undefined);
|
|
71
|
+
// No valid ranges
|
|
72
|
+
if (resolvedRanges.length === 0) {
|
|
73
|
+
return this.getEmptyConstraints(componentType);
|
|
74
|
+
}
|
|
75
|
+
// Combine multiple ranges using UTMOST (most permissive)
|
|
76
|
+
const finalMin = this.getPermissiveMin(resolvedRanges, componentType);
|
|
77
|
+
const finalMax = this.getPermissiveMax(resolvedRanges, componentType);
|
|
78
|
+
// Return appropriate keys based on component type
|
|
79
|
+
if (componentType === 'date' || componentType === 'dateRange') {
|
|
80
|
+
return {
|
|
81
|
+
minValue: undefined,
|
|
82
|
+
maxValue: undefined,
|
|
83
|
+
minDate: finalMin,
|
|
84
|
+
maxDate: finalMax,
|
|
85
|
+
minTime: undefined,
|
|
86
|
+
maxTime: undefined
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
if (componentType === 'time') {
|
|
90
|
+
return {
|
|
91
|
+
minValue: undefined,
|
|
92
|
+
maxValue: undefined,
|
|
93
|
+
minDate: undefined,
|
|
94
|
+
maxDate: undefined,
|
|
95
|
+
minTime: finalMin,
|
|
96
|
+
maxTime: finalMax
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
minValue: finalMin,
|
|
101
|
+
maxValue: finalMax,
|
|
102
|
+
minDate: undefined,
|
|
103
|
+
maxDate: undefined,
|
|
104
|
+
minTime: undefined,
|
|
105
|
+
maxTime: undefined
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Resolve a single value - either direct or field reference
|
|
110
|
+
*
|
|
111
|
+
* @param value - Direct value or field reference object
|
|
112
|
+
* @param formValues - Form state values for field lookup
|
|
113
|
+
* @returns Resolved value or undefined
|
|
114
|
+
*/
|
|
115
|
+
static resolveValue(value, formValues) {
|
|
116
|
+
// Direct value (number or string)
|
|
117
|
+
if (typeof value === 'number') {
|
|
118
|
+
return value;
|
|
119
|
+
}
|
|
120
|
+
if (typeof value === 'string') {
|
|
121
|
+
return value;
|
|
122
|
+
}
|
|
123
|
+
// Field reference object
|
|
124
|
+
if (value && typeof value === 'object' && value.field) {
|
|
125
|
+
const fieldId = value.field;
|
|
126
|
+
const fieldValue = formValues[fieldId];
|
|
127
|
+
return fieldValue !== undefined ? fieldValue : undefined;
|
|
128
|
+
}
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Get the most permissive (widest) minimum value
|
|
133
|
+
* For numbers: MIN of all mins
|
|
134
|
+
* For dates: earliest date (MIN string comparison on ISO format)
|
|
135
|
+
* For times: earliest time (MIN string comparison on 24-hour format)
|
|
136
|
+
*
|
|
137
|
+
* @param ranges - Array of {min, max} values
|
|
138
|
+
* @param componentType - Type of component
|
|
139
|
+
* @returns Most permissive minimum value
|
|
140
|
+
*/
|
|
141
|
+
static getPermissiveMin(ranges, componentType) {
|
|
142
|
+
if (ranges.length === 0)
|
|
143
|
+
return undefined;
|
|
144
|
+
if (componentType === 'date' || componentType === 'dateRange') {
|
|
145
|
+
// For dates, find earliest (minimum) date
|
|
146
|
+
return ranges.reduce((earliest, current) => {
|
|
147
|
+
if (!current.min)
|
|
148
|
+
return earliest;
|
|
149
|
+
if (!earliest)
|
|
150
|
+
return current.min;
|
|
151
|
+
const earliestIso = this.convertToISO(earliest);
|
|
152
|
+
const currentIso = this.convertToISO(current.min);
|
|
153
|
+
return currentIso < earliestIso ? current.min : earliest;
|
|
154
|
+
}, undefined);
|
|
155
|
+
}
|
|
156
|
+
if (componentType === 'time') {
|
|
157
|
+
// For times, find earliest (minimum) time
|
|
158
|
+
return ranges.reduce((earliest, current) => {
|
|
159
|
+
if (!current.min)
|
|
160
|
+
return earliest;
|
|
161
|
+
if (!earliest)
|
|
162
|
+
return current.min;
|
|
163
|
+
const earliest24 = this.convertTimeTo24Hour(earliest);
|
|
164
|
+
const current24 = this.convertTimeTo24Hour(current.min);
|
|
165
|
+
return current24 < earliest24 ? current.min : earliest;
|
|
166
|
+
}, undefined);
|
|
167
|
+
}
|
|
168
|
+
// For numbers, find minimum (skip undefined values)
|
|
169
|
+
const minValues = ranges.map(r => r.min).filter(v => v !== undefined);
|
|
170
|
+
return minValues.length > 0 ? Math.min(...minValues) : undefined;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Get the most permissive (widest) maximum value
|
|
174
|
+
* For numbers: MAX of all maxs
|
|
175
|
+
* For dates: latest date (MAX string comparison on ISO format)
|
|
176
|
+
* For times: latest time (MAX string comparison on 24-hour format)
|
|
177
|
+
*
|
|
178
|
+
* @param ranges - Array of {min, max} values
|
|
179
|
+
* @param componentType - Type of component
|
|
180
|
+
* @returns Most permissive maximum value
|
|
181
|
+
*/
|
|
182
|
+
static getPermissiveMax(ranges, componentType) {
|
|
183
|
+
if (ranges.length === 0)
|
|
184
|
+
return undefined;
|
|
185
|
+
if (componentType === 'date' || componentType === 'dateRange') {
|
|
186
|
+
// For dates, find latest (maximum) date
|
|
187
|
+
return ranges.reduce((latest, current) => {
|
|
188
|
+
if (!current.max)
|
|
189
|
+
return latest;
|
|
190
|
+
if (!latest)
|
|
191
|
+
return current.max;
|
|
192
|
+
const latestIso = this.convertToISO(latest);
|
|
193
|
+
const currentIso = this.convertToISO(current.max);
|
|
194
|
+
return currentIso > latestIso ? current.max : latest;
|
|
195
|
+
}, undefined);
|
|
196
|
+
}
|
|
197
|
+
if (componentType === 'time') {
|
|
198
|
+
// For times, find latest (maximum) time
|
|
199
|
+
return ranges.reduce((latest, current) => {
|
|
200
|
+
if (!current.max)
|
|
201
|
+
return latest;
|
|
202
|
+
if (!latest)
|
|
203
|
+
return current.max;
|
|
204
|
+
const latest24 = this.convertTimeTo24Hour(latest);
|
|
205
|
+
const current24 = this.convertTimeTo24Hour(current.max);
|
|
206
|
+
return current24 > latest24 ? current.max : latest;
|
|
207
|
+
}, undefined);
|
|
208
|
+
}
|
|
209
|
+
// For numbers, find maximum (skip undefined values)
|
|
210
|
+
const maxValues = ranges.map(r => r.max).filter(v => v !== undefined);
|
|
211
|
+
return maxValues.length > 0 ? Math.max(...maxValues) : undefined;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Convert MM/DD/YYYY date format to ISO (YYYY-MM-DD) for comparison
|
|
215
|
+
* Handles both formats gracefully
|
|
216
|
+
*
|
|
217
|
+
* @param dateString - Date in MM/DD/YYYY or ISO format
|
|
218
|
+
* @returns ISO date string (YYYY-MM-DD)
|
|
219
|
+
*/
|
|
220
|
+
static convertToISO(dateString) {
|
|
221
|
+
if (!dateString)
|
|
222
|
+
return '';
|
|
223
|
+
// Already ISO format (YYYY-MM-DD)
|
|
224
|
+
if (dateString.includes('-') && dateString.length === 10) {
|
|
225
|
+
return dateString;
|
|
226
|
+
}
|
|
227
|
+
// MM/DD/YYYY format
|
|
228
|
+
if (dateString.includes('/')) {
|
|
229
|
+
const [month, day, year] = dateString.split('/');
|
|
230
|
+
if (!month || !day || !year)
|
|
231
|
+
return dateString;
|
|
232
|
+
return `${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')}`;
|
|
233
|
+
}
|
|
234
|
+
return dateString;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Convert 12-hour time format to 24-hour format for comparison
|
|
238
|
+
* Handles both 12-hour (hh:mm a) and 24-hour (HH:mm) formats
|
|
239
|
+
*
|
|
240
|
+
* @param timeString - Time in 12-hour (hh:mm a/p) or 24-hour (HH:mm) format
|
|
241
|
+
* @returns 24-hour format time string (HH:mm)
|
|
242
|
+
*/
|
|
243
|
+
static convertTimeTo24Hour(timeString) {
|
|
244
|
+
if (!timeString)
|
|
245
|
+
return '';
|
|
246
|
+
// Check if it contains AM/PM (12-hour format)
|
|
247
|
+
const isPM = /PM/i.test(timeString);
|
|
248
|
+
const isAM = /AM/i.test(timeString);
|
|
249
|
+
if (isAM || isPM) {
|
|
250
|
+
// Extract time part (before AM/PM)
|
|
251
|
+
const timePart = timeString.replace(/\s*(AM|PM)/i, '').trim();
|
|
252
|
+
const [hours, minutes] = timePart.split(':');
|
|
253
|
+
if (!hours || !minutes)
|
|
254
|
+
return timeString;
|
|
255
|
+
let hour = parseInt(hours, 10);
|
|
256
|
+
if (isPM && hour !== 12) {
|
|
257
|
+
hour += 12; // Convert PM hours (except 12) to 24-hour
|
|
258
|
+
}
|
|
259
|
+
else if (isAM && hour === 12) {
|
|
260
|
+
hour = 0; // Convert 12 AM to 00:00
|
|
261
|
+
}
|
|
262
|
+
return `${String(hour).padStart(2, '0')}:${minutes}`;
|
|
263
|
+
}
|
|
264
|
+
// Already in 24-hour format or unknown format
|
|
265
|
+
return timeString;
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Get empty constraints object based on component type
|
|
269
|
+
*/
|
|
270
|
+
static getEmptyConstraints(componentType) {
|
|
271
|
+
if (componentType === 'date' || componentType === 'dateRange') {
|
|
272
|
+
return {
|
|
273
|
+
minValue: undefined,
|
|
274
|
+
maxValue: undefined,
|
|
275
|
+
minDate: undefined,
|
|
276
|
+
maxDate: undefined,
|
|
277
|
+
minTime: undefined,
|
|
278
|
+
maxTime: undefined
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
if (componentType === 'time') {
|
|
282
|
+
return {
|
|
283
|
+
minValue: undefined,
|
|
284
|
+
maxValue: undefined,
|
|
285
|
+
minDate: undefined,
|
|
286
|
+
maxDate: undefined,
|
|
287
|
+
minTime: undefined,
|
|
288
|
+
maxTime: undefined
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
return {
|
|
292
|
+
minValue: undefined,
|
|
293
|
+
maxValue: undefined,
|
|
294
|
+
minDate: undefined,
|
|
295
|
+
maxDate: undefined,
|
|
296
|
+
minTime: undefined,
|
|
297
|
+
maxTime: undefined
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Validate if a value passes all min/max constraints (optional)
|
|
302
|
+
* Can be used for client-side validation before submission
|
|
303
|
+
*
|
|
304
|
+
* @param value - Value to validate
|
|
305
|
+
* @param constraints - Constraints to check against
|
|
306
|
+
* @param componentType - Type of component
|
|
307
|
+
* @returns Validation result with error message if invalid
|
|
308
|
+
*/
|
|
309
|
+
static validateValue(value, constraints, componentType) {
|
|
310
|
+
if (value === undefined || value === null || value === '') {
|
|
311
|
+
return { isValid: true }; // Empty values pass (required handled separately)
|
|
312
|
+
}
|
|
313
|
+
if (componentType === 'date' || componentType === 'dateRange') {
|
|
314
|
+
if (constraints.minDate) {
|
|
315
|
+
const minIso = this.convertToISO(constraints.minDate);
|
|
316
|
+
const valueIso = this.convertToISO(value);
|
|
317
|
+
if (valueIso < minIso) {
|
|
318
|
+
return { isValid: false, error: `Date must be on or after ${constraints.minDate}` };
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
if (constraints.maxDate) {
|
|
322
|
+
const maxIso = this.convertToISO(constraints.maxDate);
|
|
323
|
+
const valueIso = this.convertToISO(value);
|
|
324
|
+
if (valueIso > maxIso) {
|
|
325
|
+
return { isValid: false, error: `Date must be on or before ${constraints.maxDate}` };
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
else if (componentType === 'time') {
|
|
330
|
+
if (constraints.minTime) {
|
|
331
|
+
if (value < constraints.minTime) {
|
|
332
|
+
return { isValid: false, error: `Time must be on or after ${constraints.minTime}` };
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
if (constraints.maxTime) {
|
|
336
|
+
if (value > constraints.maxTime) {
|
|
337
|
+
return { isValid: false, error: `Time must be on or before ${constraints.maxTime}` };
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
// Number type
|
|
343
|
+
const numValue = Number(value);
|
|
344
|
+
if (constraints.minValue !== undefined) {
|
|
345
|
+
const minNum = Number(constraints.minValue);
|
|
346
|
+
if (numValue < minNum) {
|
|
347
|
+
return {
|
|
348
|
+
isValid: false,
|
|
349
|
+
error: `Value must be at least ${constraints.minValue}`
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
if (constraints.maxValue !== undefined) {
|
|
354
|
+
const maxNum = Number(constraints.maxValue);
|
|
355
|
+
if (numValue > maxNum) {
|
|
356
|
+
return {
|
|
357
|
+
isValid: false,
|
|
358
|
+
error: `Value must be at most ${constraints.maxValue}`
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return { isValid: true };
|
|
364
|
+
}
|
|
365
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* @private
|
|
4
|
+
*/
|
|
5
|
+
export interface StaticHtmlRendererProps {
|
|
6
|
+
content: string;
|
|
7
|
+
id?: string;
|
|
8
|
+
enableHtmlSanitizer?: boolean;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* @private
|
|
12
|
+
*/
|
|
13
|
+
export declare const StaticHtmlRenderer: React.FC<StaticHtmlRendererProps>;
|
|
14
|
+
export default StaticHtmlRenderer;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { SanitizeHtmlHelper } from '@syncfusion/ej2-base';
|
|
3
|
+
/**
|
|
4
|
+
* @private
|
|
5
|
+
*/
|
|
6
|
+
export const StaticHtmlRenderer = ({ content, id, enableHtmlSanitizer }) => {
|
|
7
|
+
const sanitizedHtml = React.useMemo(() => {
|
|
8
|
+
try {
|
|
9
|
+
if (enableHtmlSanitizer) {
|
|
10
|
+
return SanitizeHtmlHelper.sanitize(content);
|
|
11
|
+
}
|
|
12
|
+
return content;
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
console.error('HTML sanitization error:', error);
|
|
16
|
+
return '';
|
|
17
|
+
}
|
|
18
|
+
}, [content]);
|
|
19
|
+
return React.createElement('div', {
|
|
20
|
+
id,
|
|
21
|
+
dangerouslySetInnerHTML: { __html: sanitizedHtml }
|
|
22
|
+
});
|
|
23
|
+
};
|
|
24
|
+
export default StaticHtmlRenderer;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DependencyTracker - Extract and track field dependencies
|
|
3
|
+
*
|
|
4
|
+
* Analyzes expressions to find field references and build dependency graphs
|
|
5
|
+
* Detects circular dependencies and calculates evaluation order
|
|
6
|
+
* @private
|
|
7
|
+
* @spec Expression_Engine.md
|
|
8
|
+
*/
|
|
9
|
+
export declare class DependencyTracker {
|
|
10
|
+
private escapedBracePattern;
|
|
11
|
+
/**
|
|
12
|
+
* Extract field names referenced in expression
|
|
13
|
+
*/
|
|
14
|
+
extractFieldReferences(expression: string): string[];
|
|
15
|
+
/**
|
|
16
|
+
* Build dependency graph from multiple expressions
|
|
17
|
+
*/
|
|
18
|
+
buildGraph(expressions: Record<string, string>): Record<string, string[]>;
|
|
19
|
+
/**
|
|
20
|
+
* Detect circular dependencies using DFS
|
|
21
|
+
*/
|
|
22
|
+
detectCircularDependencies(graph: Record<string, string[]>): string[][];
|
|
23
|
+
/**
|
|
24
|
+
* Get topological sort order for evaluation
|
|
25
|
+
*/
|
|
26
|
+
getTopologicalOrder(graph: Record<string, string[]>): string[];
|
|
27
|
+
private isFunctionCall;
|
|
28
|
+
private isOperator;
|
|
29
|
+
private isKeyword;
|
|
30
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DependencyTracker - Extract and track field dependencies
|
|
3
|
+
*
|
|
4
|
+
* Analyzes expressions to find field references and build dependency graphs
|
|
5
|
+
* Detects circular dependencies and calculates evaluation order
|
|
6
|
+
* @private
|
|
7
|
+
* @spec Expression_Engine.md
|
|
8
|
+
*/
|
|
9
|
+
export class DependencyTracker {
|
|
10
|
+
escapedBracePattern = /\\{.*?\\}/g;
|
|
11
|
+
/**
|
|
12
|
+
* Extract field names referenced in expression
|
|
13
|
+
*/
|
|
14
|
+
extractFieldReferences(expression) {
|
|
15
|
+
if (!expression)
|
|
16
|
+
return [];
|
|
17
|
+
const refs = new Set();
|
|
18
|
+
let match;
|
|
19
|
+
// Remove escaped braces first
|
|
20
|
+
const cleanExpression = expression.replace(this.escapedBracePattern, '');
|
|
21
|
+
// Extract field references from {field} syntax
|
|
22
|
+
const fieldRefPattern = /\{([^{}]+)\}/g;
|
|
23
|
+
while ((match = fieldRefPattern.exec(cleanExpression)) !== null) {
|
|
24
|
+
const ref = match[1].trim();
|
|
25
|
+
// Skip if it's a function call or operator
|
|
26
|
+
if (!this.isFunctionCall(ref) && !this.isOperator(ref)) {
|
|
27
|
+
// Extract field name from nested paths
|
|
28
|
+
const fieldName = ref.split(/[.\[\]]/)[0].trim();
|
|
29
|
+
if (fieldName && !this.isKeyword(fieldName)) {
|
|
30
|
+
refs.add(fieldName);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
// Extract identifiers from ${...} math expressions
|
|
35
|
+
const mathExprPattern = /\$\{([^}]+)\}/g;
|
|
36
|
+
while ((match = mathExprPattern.exec(cleanExpression)) !== null) {
|
|
37
|
+
const mathExpr = match[1];
|
|
38
|
+
// Extract all identifiers from the math expression
|
|
39
|
+
// Match word boundaries followed by identifier characters
|
|
40
|
+
// But skip if followed by ( or . (those are function calls or property access)
|
|
41
|
+
const identPattern = /\b([a-zA-Z_][a-zA-Z0-9_]*)\b(?!\s*[(\.])/g;
|
|
42
|
+
let idMatch;
|
|
43
|
+
while ((idMatch = identPattern.exec(mathExpr)) !== null) {
|
|
44
|
+
const identifier = idMatch[1];
|
|
45
|
+
// Skip keywords and constants
|
|
46
|
+
if (!this.isKeyword(identifier) && identifier !== 'Math') {
|
|
47
|
+
refs.add(identifier);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return Array.from(refs);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Build dependency graph from multiple expressions
|
|
55
|
+
*/
|
|
56
|
+
buildGraph(expressions) {
|
|
57
|
+
const graph = {};
|
|
58
|
+
for (const [fieldName, expression] of Object.entries(expressions)) {
|
|
59
|
+
const deps = this.extractFieldReferences(expression);
|
|
60
|
+
graph[fieldName] = deps;
|
|
61
|
+
}
|
|
62
|
+
return graph;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Detect circular dependencies using DFS
|
|
66
|
+
*/
|
|
67
|
+
detectCircularDependencies(graph) {
|
|
68
|
+
const visited = new Set();
|
|
69
|
+
const recursionStack = new Set();
|
|
70
|
+
const cycles = [];
|
|
71
|
+
const visit = (node, path) => {
|
|
72
|
+
visited.add(node);
|
|
73
|
+
recursionStack.add(node);
|
|
74
|
+
path.push(node);
|
|
75
|
+
const deps = graph[node] || [];
|
|
76
|
+
for (const dep of deps) {
|
|
77
|
+
if (!visited.has(dep)) {
|
|
78
|
+
visit(dep, [...path]);
|
|
79
|
+
}
|
|
80
|
+
else if (recursionStack.has(dep)) {
|
|
81
|
+
const cycleStart = path.indexOf(dep);
|
|
82
|
+
cycles.push([...path.slice(cycleStart), dep]);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
recursionStack.delete(node);
|
|
86
|
+
};
|
|
87
|
+
for (const node of Object.keys(graph)) {
|
|
88
|
+
if (!visited.has(node)) {
|
|
89
|
+
visit(node, []);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return cycles;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Get topological sort order for evaluation
|
|
96
|
+
*/
|
|
97
|
+
getTopologicalOrder(graph) {
|
|
98
|
+
const visited = new Set();
|
|
99
|
+
const order = [];
|
|
100
|
+
const visit = (node) => {
|
|
101
|
+
if (visited.has(node))
|
|
102
|
+
return;
|
|
103
|
+
visited.add(node);
|
|
104
|
+
const deps = graph[node] || [];
|
|
105
|
+
for (const dep of deps) {
|
|
106
|
+
if (graph[dep]) {
|
|
107
|
+
visit(dep);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
order.push(node);
|
|
111
|
+
};
|
|
112
|
+
for (const node of Object.keys(graph)) {
|
|
113
|
+
visit(node);
|
|
114
|
+
}
|
|
115
|
+
return order;
|
|
116
|
+
}
|
|
117
|
+
// Private helpers
|
|
118
|
+
isFunctionCall(ref) {
|
|
119
|
+
return /^\w+\s*\(/.test(ref);
|
|
120
|
+
}
|
|
121
|
+
isOperator(ref) {
|
|
122
|
+
return /^[+\-*/()=!<>&|]/.test(ref);
|
|
123
|
+
}
|
|
124
|
+
isKeyword(name) {
|
|
125
|
+
const keywords = ['PI', 'TRUE', 'FALSE', 'NULL', 'UNDEFINED', 'and', 'or', 'in'];
|
|
126
|
+
return keywords.includes(name.toUpperCase());
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @private
|
|
3
|
+
*/
|
|
4
|
+
export type FormValueType = string | number | boolean | Date | null | undefined | any[];
|
|
5
|
+
/**
|
|
6
|
+
* @private
|
|
7
|
+
*/
|
|
8
|
+
export interface EvaluationOptions {
|
|
9
|
+
timeout?: number;
|
|
10
|
+
maxDepth?: number;
|
|
11
|
+
locale?: string;
|
|
12
|
+
currencyCode?: string;
|
|
13
|
+
throwOnError?: boolean;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* @private
|
|
17
|
+
*/
|
|
18
|
+
export type DependencyGraph = Record<string, string[]>;
|
|
19
|
+
/**
|
|
20
|
+
* ExpressionEngine - Core evaluation engine
|
|
21
|
+
*
|
|
22
|
+
* Safely evaluates user-defined expressions against form state
|
|
23
|
+
* Supports field references, math operations, functions, and conditionals
|
|
24
|
+
* @private
|
|
25
|
+
*/
|
|
26
|
+
export declare class ExpressionEngine {
|
|
27
|
+
private parser;
|
|
28
|
+
private dependencyTracker;
|
|
29
|
+
private evaluationCache;
|
|
30
|
+
private compiledFunctions;
|
|
31
|
+
constructor();
|
|
32
|
+
/**
|
|
33
|
+
* Evaluate expression against form values
|
|
34
|
+
*/
|
|
35
|
+
evaluate(expression: string, formValues: Record<string, FormValueType>, options?: EvaluationOptions, isCustomValidation?: boolean): any;
|
|
36
|
+
/**
|
|
37
|
+
* Extract field names referenced in expression
|
|
38
|
+
*/
|
|
39
|
+
extractDependencies(expression: string): string[];
|
|
40
|
+
/**
|
|
41
|
+
* Build dependency graph for all expressions
|
|
42
|
+
*/
|
|
43
|
+
buildDependencyGraph(components: any[]): DependencyGraph;
|
|
44
|
+
/**
|
|
45
|
+
* Detect circular dependencies
|
|
46
|
+
*/
|
|
47
|
+
detectCircularDependencies(graph: DependencyGraph): string[][];
|
|
48
|
+
/**
|
|
49
|
+
* Get topological order for evaluation
|
|
50
|
+
*/
|
|
51
|
+
getTopologicalOrder(graph: DependencyGraph): string[];
|
|
52
|
+
/**
|
|
53
|
+
* Clear internal caches
|
|
54
|
+
*/
|
|
55
|
+
clearCache(): void;
|
|
56
|
+
private getCacheKey;
|
|
57
|
+
private buildContext;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* ExpressionValidator - Validates expressions before runtime
|
|
61
|
+
*
|
|
62
|
+
* Checks syntax, validates field references, and detects circular dependencies
|
|
63
|
+
* @private
|
|
64
|
+
* @spec Expression_Engine.md
|
|
65
|
+
*/
|
|
66
|
+
export declare class ExpressionValidator {
|
|
67
|
+
private supportedFunctions;
|
|
68
|
+
private keywords;
|
|
69
|
+
/**
|
|
70
|
+
* Validate expression against available fields
|
|
71
|
+
*/
|
|
72
|
+
validate(expression: string, availableFields: string[] | any[]): ValidationError[];
|
|
73
|
+
/**
|
|
74
|
+
* Validate field operations - check for invalid operators on string/date types
|
|
75
|
+
*/
|
|
76
|
+
private validateFieldOperations;
|
|
77
|
+
private validateSyntax;
|
|
78
|
+
/**
|
|
79
|
+
* @private
|
|
80
|
+
*/
|
|
81
|
+
validateFieldReferences(expression: string, availableFields: string[] | any[]): ValidationError[];
|
|
82
|
+
private validateFunctionCalls;
|
|
83
|
+
private isFunctionCall;
|
|
84
|
+
private isMathExpression;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* @private
|
|
88
|
+
*/
|
|
89
|
+
export interface ValidationError {
|
|
90
|
+
type: 'SyntaxError' | 'UndefinedField' | 'InvalidFunction' | 'CircularDependency' | 'InvalidOperation';
|
|
91
|
+
message: string;
|
|
92
|
+
field?: string;
|
|
93
|
+
line?: number;
|
|
94
|
+
column?: number;
|
|
95
|
+
}
|