@syncfusion/ej2-react-form-renderer 0.1.0-beta → 0.1.1-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/package.json +2 -2
- package/src/CheckboxGroup.js +1 -1
- package/src/ConditionalRuleEngine.d.ts +253 -0
- package/src/ConditionalRuleEngine.js +489 -0
- package/src/DataGridComponent.d.ts +0 -1
- package/src/DataGridComponent.js +3 -19
- package/src/FormRenderer.d.ts +29 -2
- package/src/FormRenderer.js +122 -40
- package/src/json-converter.d.ts +10 -0
- package/src/json-converter.js +181 -32
- package/src/parser.js +18 -17
- package/src/renderFormField.d.ts +2 -1
- package/src/renderFormField.js +44 -43
- package/src/types/FormSchema.d.ts +29 -1
- package/src/utils.js +86 -0
- package/styles/form-renderer.base.css +39 -23
package/src/json-converter.js
CHANGED
|
@@ -6,6 +6,10 @@ export const generateId = (type) => {
|
|
|
6
6
|
const random = Math.floor(Math.random() * 1000);
|
|
7
7
|
return `${type}_${timestamp}_${random}`;
|
|
8
8
|
};
|
|
9
|
+
export const generateName = (type) => {
|
|
10
|
+
const random = Math.floor(Math.random() * 1000);
|
|
11
|
+
return `${type}_${random}`;
|
|
12
|
+
};
|
|
9
13
|
// ============================================================================
|
|
10
14
|
// LABEL TO CAMELCASE CONVERTER
|
|
11
15
|
// ============================================================================
|
|
@@ -76,36 +80,36 @@ const UI_TYPE_TO_SEMANTIC_TYPE = {
|
|
|
76
80
|
'url': { type: 'string', format: 'url' },
|
|
77
81
|
'phone': { type: 'string' },
|
|
78
82
|
'password': { type: 'string' },
|
|
79
|
-
'
|
|
83
|
+
'inputMask': { type: 'string' },
|
|
80
84
|
// Numeric types
|
|
81
85
|
'number': { type: 'number' },
|
|
82
|
-
'
|
|
86
|
+
'rangeSlider': { type: 'number' },
|
|
83
87
|
'rating': { type: 'number' },
|
|
84
88
|
// Boolean types
|
|
85
89
|
'checkbox': { type: 'boolean' },
|
|
86
90
|
'switch': { type: 'boolean' },
|
|
87
91
|
// Date/Time types
|
|
88
|
-
'
|
|
89
|
-
'
|
|
90
|
-
'
|
|
91
|
-
'
|
|
92
|
+
'date': { type: 'string', format: 'date' },
|
|
93
|
+
'dateTime': { type: 'string', format: 'date-time' },
|
|
94
|
+
'time': { type: 'string', format: 'time' },
|
|
95
|
+
'dateRange': { type: 'string', format: 'date' },
|
|
92
96
|
// Array types
|
|
93
97
|
'multiselect': { type: 'array' },
|
|
94
|
-
'
|
|
98
|
+
'checkboxGroup': { type: 'array' },
|
|
95
99
|
// Single-value selection (stored as string)
|
|
96
100
|
'dropdown': { type: 'string' },
|
|
97
101
|
'radio': { type: 'string' },
|
|
98
|
-
'
|
|
102
|
+
'splitButton': { type: 'string' },
|
|
99
103
|
// Special types (excluded from data schema)
|
|
100
104
|
'button': { type: 'button' },
|
|
101
105
|
'message': { type: 'message' },
|
|
102
106
|
'signature': { type: 'sign' },
|
|
103
|
-
'
|
|
104
|
-
'
|
|
105
|
-
'
|
|
106
|
-
'
|
|
107
|
+
'imageEditor': { type: 'image' },
|
|
108
|
+
'fileUpload': { type: 'file' },
|
|
109
|
+
'colorPicker': { type: 'color' },
|
|
110
|
+
'dataGrid': { type: 'grid' },
|
|
107
111
|
// Layout types (excluded from data schema)
|
|
108
|
-
'
|
|
112
|
+
'staticHtml': { type: 'string' },
|
|
109
113
|
'table': null,
|
|
110
114
|
'panel': null,
|
|
111
115
|
'tabs': null,
|
|
@@ -126,6 +130,73 @@ function stripConfiguredFlag(properties) {
|
|
|
126
130
|
return out;
|
|
127
131
|
}
|
|
128
132
|
// ============================================================================
|
|
133
|
+
// CONDITION VALIDATION HELPERS
|
|
134
|
+
// ============================================================================
|
|
135
|
+
/**
|
|
136
|
+
* Clean condition rules that reference non-existent field IDs
|
|
137
|
+
*
|
|
138
|
+
* Validates condition rules during split to remove references to deleted fields.
|
|
139
|
+
* Returns undefined if condition becomes empty after cleanup.
|
|
140
|
+
*
|
|
141
|
+
* @param condition - Condition object (visibleWhen/readOnlyWhen/disabledWhen)
|
|
142
|
+
* @param validFieldIds - Set of valid field IDs in current schema
|
|
143
|
+
* @returns Cleaned condition or undefined if empty
|
|
144
|
+
*/
|
|
145
|
+
function cleanConditionRules(condition, validFieldIds) {
|
|
146
|
+
if (!condition || !condition.rules || !Array.isArray(condition.rules)) {
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
const cleanedRules = condition.rules
|
|
150
|
+
.map((rule) => {
|
|
151
|
+
// If rule has nested rules (group), recursively clean
|
|
152
|
+
if (rule.rules && Array.isArray(rule.rules)) {
|
|
153
|
+
return cleanConditionRules(rule, validFieldIds);
|
|
154
|
+
}
|
|
155
|
+
// If rule references non-existent field, exclude it
|
|
156
|
+
if (rule.field && !validFieldIds.has(rule.field)) {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
return rule;
|
|
160
|
+
})
|
|
161
|
+
.filter((rule) => rule !== null && rule !== undefined);
|
|
162
|
+
// If no rules remain, return undefined
|
|
163
|
+
if (cleanedRules.length === 0) {
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
...condition,
|
|
168
|
+
rules: cleanedRules
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Get all valid field IDs from components
|
|
173
|
+
*
|
|
174
|
+
* @param components - Form components array
|
|
175
|
+
* @returns Set of field IDs
|
|
176
|
+
*/
|
|
177
|
+
function getAllComponentIds(components) {
|
|
178
|
+
const ids = new Set();
|
|
179
|
+
const stack = [...components];
|
|
180
|
+
while (stack.length) {
|
|
181
|
+
const comp = stack.pop();
|
|
182
|
+
ids.add(comp.id);
|
|
183
|
+
if (comp.children)
|
|
184
|
+
stack.push(...comp.children);
|
|
185
|
+
if (comp.tabItems) {
|
|
186
|
+
comp.tabItems.forEach(tab => {
|
|
187
|
+
if (tab.content)
|
|
188
|
+
stack.push(...tab.content);
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
if (comp.tableCells) {
|
|
192
|
+
comp.tableCells.forEach(row => {
|
|
193
|
+
row.forEach(cell => stack.push(...cell));
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return ids;
|
|
198
|
+
}
|
|
199
|
+
// ============================================================================
|
|
129
200
|
// SPLIT ALGORITHM
|
|
130
201
|
// ============================================================================
|
|
131
202
|
export function splitSchema(heartSchema) {
|
|
@@ -134,6 +205,8 @@ export function splitSchema(heartSchema) {
|
|
|
134
205
|
const layout = [];
|
|
135
206
|
const usedFieldKeys = new Set(); // Track unique field keys
|
|
136
207
|
const seenComponentIds = new Set(); // Track component IDs to prevent duplicates
|
|
208
|
+
// ✅ Get all valid field IDs for condition validation
|
|
209
|
+
const validFieldIds = getAllComponentIds(heartSchema.components);
|
|
137
210
|
// Stack for iterative traversal (avoid recursion)
|
|
138
211
|
const stack = heartSchema.components.map(c => ({
|
|
139
212
|
component: c,
|
|
@@ -142,6 +215,58 @@ export function splitSchema(heartSchema) {
|
|
|
142
215
|
while (stack.length > 0) {
|
|
143
216
|
// Use shift() instead of pop() to maintain order (FIFO instead of LIFO)
|
|
144
217
|
const { component, layoutParent } = stack.shift();
|
|
218
|
+
// ✅ Clean conditions before processing
|
|
219
|
+
if (component.conditions) {
|
|
220
|
+
const cleanedConditions = {};
|
|
221
|
+
let hasValidConditions = false;
|
|
222
|
+
if (component.conditions.visibleWhen) {
|
|
223
|
+
const cleaned = cleanConditionRules(component.conditions.visibleWhen, validFieldIds);
|
|
224
|
+
if (cleaned) {
|
|
225
|
+
cleanedConditions.visibleWhen = cleaned;
|
|
226
|
+
hasValidConditions = true;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
if (component.conditions.hideWhen) {
|
|
230
|
+
const cleaned = cleanConditionRules(component.conditions.hideWhen, validFieldIds);
|
|
231
|
+
if (cleaned) {
|
|
232
|
+
cleanedConditions.hideWhen = cleaned;
|
|
233
|
+
hasValidConditions = true;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
if (component.conditions.readOnlyWhen) {
|
|
237
|
+
const cleaned = cleanConditionRules(component.conditions.readOnlyWhen, validFieldIds);
|
|
238
|
+
if (cleaned) {
|
|
239
|
+
cleanedConditions.readOnlyWhen = cleaned;
|
|
240
|
+
hasValidConditions = true;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (component.conditions.disabledWhen) {
|
|
244
|
+
const cleaned = cleanConditionRules(component.conditions.disabledWhen, validFieldIds);
|
|
245
|
+
if (cleaned) {
|
|
246
|
+
cleanedConditions.disabledWhen = cleaned;
|
|
247
|
+
hasValidConditions = true;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
if (component.conditions.requiredWhen) {
|
|
251
|
+
const cleaned = cleanConditionRules(component.conditions.requiredWhen, validFieldIds);
|
|
252
|
+
if (cleaned) {
|
|
253
|
+
cleanedConditions.requiredWhen = cleaned;
|
|
254
|
+
hasValidConditions = true;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
if (component.conditions.setValueWhen) {
|
|
258
|
+
const cleaned = cleanConditionRules(component.conditions.setValueWhen.condition, validFieldIds);
|
|
259
|
+
if (cleaned && component.conditions.setValueWhen.value !== undefined && component.conditions.setValueWhen.value !== '') {
|
|
260
|
+
cleanedConditions.setValueWhen = {
|
|
261
|
+
condition: cleaned,
|
|
262
|
+
value: component.conditions.setValueWhen.value
|
|
263
|
+
};
|
|
264
|
+
hasValidConditions = true;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
// Update component conditions (or remove if empty)
|
|
268
|
+
component.conditions = hasValidConditions ? cleanedConditions : undefined;
|
|
269
|
+
}
|
|
145
270
|
// Get semantic type mapping (may be null for presentation-only widgets)
|
|
146
271
|
const semanticType = UI_TYPE_TO_SEMANTIC_TYPE[component.type];
|
|
147
272
|
// Handle layout containers
|
|
@@ -149,13 +274,15 @@ export function splitSchema(heartSchema) {
|
|
|
149
274
|
const tableNode = {
|
|
150
275
|
type: 'table',
|
|
151
276
|
id: component.id,
|
|
277
|
+
name: component.name,
|
|
152
278
|
label: component.label,
|
|
153
279
|
cssClass: component.cssClass,
|
|
154
280
|
hideBorders: component.hideBorders,
|
|
155
281
|
rows: component.tableCells?.length || 0,
|
|
156
282
|
cols: component.tableCells?.[0]?.length || 0,
|
|
157
283
|
cells: [],
|
|
158
|
-
columnWidths: component.columnWidths
|
|
284
|
+
columnWidths: component.columnWidths,
|
|
285
|
+
...(component.conditions ? { conditions: component.conditions } : {})
|
|
159
286
|
};
|
|
160
287
|
// Process table cells
|
|
161
288
|
component.tableCells?.forEach((row, rowIndex) => {
|
|
@@ -177,11 +304,13 @@ export function splitSchema(heartSchema) {
|
|
|
177
304
|
const panelNode = {
|
|
178
305
|
type: 'panel',
|
|
179
306
|
id: component.id,
|
|
307
|
+
name: component.name,
|
|
180
308
|
label: component.label,
|
|
181
309
|
cssClass: component.cssClass,
|
|
182
310
|
hideBorders: component.hideBorders,
|
|
183
311
|
hideLabel: component.hideLabel,
|
|
184
|
-
children: []
|
|
312
|
+
children: [],
|
|
313
|
+
...(component.conditions ? { conditions: component.conditions } : {})
|
|
185
314
|
};
|
|
186
315
|
// Process panel children
|
|
187
316
|
// Reverse children and use unshift() to maintain order with depth-first processing
|
|
@@ -194,11 +323,13 @@ export function splitSchema(heartSchema) {
|
|
|
194
323
|
if (component.type === 'tabs') {
|
|
195
324
|
const tabsNode = {
|
|
196
325
|
type: 'tabs',
|
|
326
|
+
name: component.name,
|
|
197
327
|
id: component.id,
|
|
198
328
|
label: component.label,
|
|
199
329
|
cssClass: component.cssClass,
|
|
200
330
|
tabOptions: component.tabOptions,
|
|
201
|
-
tabs: []
|
|
331
|
+
tabs: [],
|
|
332
|
+
...(component.conditions ? { conditions: component.conditions } : {})
|
|
202
333
|
};
|
|
203
334
|
// Process tabs
|
|
204
335
|
component.tabItems?.forEach((tab) => {
|
|
@@ -216,6 +347,7 @@ export function splitSchema(heartSchema) {
|
|
|
216
347
|
const cardNode = {
|
|
217
348
|
type: 'card',
|
|
218
349
|
id: component.id,
|
|
350
|
+
name: component.name,
|
|
219
351
|
label: component.label,
|
|
220
352
|
cssClass: component.cssClass,
|
|
221
353
|
hideLabel: component.hideLabel,
|
|
@@ -224,7 +356,8 @@ export function splitSchema(heartSchema) {
|
|
|
224
356
|
enableAction: component.enableAction,
|
|
225
357
|
actionButtonLabel: component.actionButtonLabel,
|
|
226
358
|
actionButtonType: component.actionButtonType,
|
|
227
|
-
children: []
|
|
359
|
+
children: [],
|
|
360
|
+
...(component.conditions ? { conditions: component.conditions } : {})
|
|
228
361
|
};
|
|
229
362
|
// Process card children
|
|
230
363
|
// Reverse children and use unshift() to maintain order with depth-first processing
|
|
@@ -267,6 +400,7 @@ export function splitSchema(heartSchema) {
|
|
|
267
400
|
function extractDataField(component, semanticType) {
|
|
268
401
|
const dataField = {
|
|
269
402
|
id: component.id,
|
|
403
|
+
name: component.name,
|
|
270
404
|
type: semanticType.type,
|
|
271
405
|
};
|
|
272
406
|
// Add format if present
|
|
@@ -324,7 +458,7 @@ function extractUIField(component) {
|
|
|
324
458
|
};
|
|
325
459
|
// Copy all properties EXCEPT those in data schema allowed list
|
|
326
460
|
const dataSchemaProps = new Set([
|
|
327
|
-
'id', 'type', 'label', 'format', 'defaultValue', 'required', 'options',
|
|
461
|
+
'id', 'type', 'label', 'format', 'defaultValue', 'required', 'options', 'name',
|
|
328
462
|
'minLength', 'maxLength', 'minimum', 'maximum', 'minValue', 'maxValue',
|
|
329
463
|
'minDate', 'maxDate', 'minTime', 'maxTime', 'regex', 'pattern', 'content', 'buttonType', 'textboxType'
|
|
330
464
|
]);
|
|
@@ -333,8 +467,8 @@ function extractUIField(component) {
|
|
|
333
467
|
uiField[key] = component[key];
|
|
334
468
|
}
|
|
335
469
|
});
|
|
336
|
-
// Special handling: Filter
|
|
337
|
-
if (component.type === '
|
|
470
|
+
// Special handling: Filter dataGrid columns to keep only required properties
|
|
471
|
+
if (component.type === 'dataGrid' && uiField.columns && Array.isArray(uiField.columns)) {
|
|
338
472
|
const requiredColumnProps = [
|
|
339
473
|
'field',
|
|
340
474
|
'headerText',
|
|
@@ -405,6 +539,7 @@ export function mergeSchema(split, existingUISchema) {
|
|
|
405
539
|
// Generate fallback data field
|
|
406
540
|
const fallbackDataField = {
|
|
407
541
|
id: fieldNode.propertyId,
|
|
542
|
+
name: generateName(uiField?.widget || ''),
|
|
408
543
|
type: 'any',
|
|
409
544
|
label: uiField?.widget || 'Unknown Field'
|
|
410
545
|
};
|
|
@@ -418,6 +553,7 @@ export function mergeSchema(split, existingUISchema) {
|
|
|
418
553
|
const tableNode = node;
|
|
419
554
|
const tableComponent = {
|
|
420
555
|
id: tableNode.id,
|
|
556
|
+
name: tableNode.name,
|
|
421
557
|
type: 'table',
|
|
422
558
|
label: tableNode.label || 'Table',
|
|
423
559
|
cssClass: tableNode.cssClass,
|
|
@@ -426,7 +562,8 @@ export function mergeSchema(split, existingUISchema) {
|
|
|
426
562
|
columns: tableNode.cols,
|
|
427
563
|
columnWidths: tableNode.columnWidths,
|
|
428
564
|
tableCells: [],
|
|
429
|
-
_configured: true
|
|
565
|
+
_configured: true,
|
|
566
|
+
...(tableNode.conditions ? { conditions: tableNode.conditions } : {})
|
|
430
567
|
};
|
|
431
568
|
// Process table cells recursively
|
|
432
569
|
tableNode.cells.forEach((row) => {
|
|
@@ -445,12 +582,14 @@ export function mergeSchema(split, existingUISchema) {
|
|
|
445
582
|
const panelComponent = {
|
|
446
583
|
id: panelNode.id,
|
|
447
584
|
type: 'panel',
|
|
585
|
+
name: panelNode.name,
|
|
448
586
|
cssClass: panelNode.cssClass,
|
|
449
587
|
hideBorders: panelNode.hideBorders,
|
|
450
588
|
hideLabel: panelNode.hideLabel,
|
|
451
589
|
label: panelNode.label || 'Panel',
|
|
452
590
|
children: processLayoutNodes(panelNode.children),
|
|
453
|
-
_configured: true
|
|
591
|
+
_configured: true,
|
|
592
|
+
...(panelNode.conditions ? { conditions: panelNode.conditions } : {})
|
|
454
593
|
};
|
|
455
594
|
components.push(panelComponent);
|
|
456
595
|
}
|
|
@@ -459,11 +598,13 @@ export function mergeSchema(split, existingUISchema) {
|
|
|
459
598
|
const tabsComponent = {
|
|
460
599
|
id: tabsNode.id,
|
|
461
600
|
type: 'tabs',
|
|
601
|
+
name: tabsNode.name,
|
|
462
602
|
label: tabsNode.label || 'Tabs',
|
|
463
603
|
cssClass: tabsNode.cssClass,
|
|
464
604
|
tabOptions: tabsNode.tabOptions,
|
|
465
605
|
tabItems: [],
|
|
466
|
-
_configured: true
|
|
606
|
+
_configured: true,
|
|
607
|
+
...(tabsNode.conditions ? { conditions: tabsNode.conditions } : {})
|
|
467
608
|
};
|
|
468
609
|
// Process tabs recursively
|
|
469
610
|
tabsNode.tabs.forEach((tab) => {
|
|
@@ -479,11 +620,13 @@ export function mergeSchema(split, existingUISchema) {
|
|
|
479
620
|
const cardComponent = {
|
|
480
621
|
id: cardNode.id,
|
|
481
622
|
type: 'card',
|
|
623
|
+
name: cardNode.name,
|
|
482
624
|
label: cardNode.label || 'Card',
|
|
483
625
|
cssClass: cardNode.cssClass,
|
|
484
626
|
hideLabel: cardNode.hideLabel,
|
|
485
627
|
children: processLayoutNodes(cardNode.children),
|
|
486
|
-
_configured: true
|
|
628
|
+
_configured: true,
|
|
629
|
+
...(cardNode.conditions ? { conditions: cardNode.conditions } : {})
|
|
487
630
|
};
|
|
488
631
|
// Add card-specific properties
|
|
489
632
|
if (cardNode.cardTitle !== undefined)
|
|
@@ -527,6 +670,7 @@ function mergeField(dataField, uiField) {
|
|
|
527
670
|
const hasOptions = dataField.options !== undefined && dataField.options.length > 0;
|
|
528
671
|
const component = {
|
|
529
672
|
id: dataField.id,
|
|
673
|
+
name: dataField.name,
|
|
530
674
|
type: (uiField?.widget || inferWidgetFromType(dataField.type, dataField.format, hasOptions)),
|
|
531
675
|
label: dataField.label || dataField.id,
|
|
532
676
|
};
|
|
@@ -577,11 +721,11 @@ function mergeField(dataField, uiField) {
|
|
|
577
721
|
export function inferWidgetFromType(type, format, hasOptions) {
|
|
578
722
|
if (type === 'string') {
|
|
579
723
|
if (format === 'date')
|
|
580
|
-
return '
|
|
724
|
+
return 'date';
|
|
581
725
|
if (format === 'date-time')
|
|
582
|
-
return '
|
|
726
|
+
return 'dateTime';
|
|
583
727
|
if (format === 'time')
|
|
584
|
-
return '
|
|
728
|
+
return 'time';
|
|
585
729
|
if (format === 'email')
|
|
586
730
|
return 'email';
|
|
587
731
|
if (format === 'url')
|
|
@@ -606,13 +750,13 @@ export function inferWidgetFromType(type, format, hasOptions) {
|
|
|
606
750
|
if (type === 'sign')
|
|
607
751
|
return 'signature';
|
|
608
752
|
if (type === 'image')
|
|
609
|
-
return '
|
|
753
|
+
return 'imageEditor';
|
|
610
754
|
if (type === 'file')
|
|
611
|
-
return '
|
|
755
|
+
return 'fileUpload';
|
|
612
756
|
if (type === 'color')
|
|
613
|
-
return '
|
|
757
|
+
return 'colorPicker';
|
|
614
758
|
if (type === 'grid')
|
|
615
|
-
return '
|
|
759
|
+
return 'dataGrid';
|
|
616
760
|
if (type === 'any')
|
|
617
761
|
return 'textbox';
|
|
618
762
|
return 'textbox';
|
|
@@ -1071,6 +1215,7 @@ export function convertComponentsToUISchema(components) {
|
|
|
1071
1215
|
return {
|
|
1072
1216
|
type: 'table',
|
|
1073
1217
|
id: comp.id,
|
|
1218
|
+
name: comp.name,
|
|
1074
1219
|
label: comp.label,
|
|
1075
1220
|
cssClass: comp.cssClass,
|
|
1076
1221
|
hideBorders: comp.hideBorders,
|
|
@@ -1084,6 +1229,7 @@ export function convertComponentsToUISchema(components) {
|
|
|
1084
1229
|
return {
|
|
1085
1230
|
type: 'panel',
|
|
1086
1231
|
id: comp.id,
|
|
1232
|
+
name: comp.name,
|
|
1087
1233
|
label: comp.label,
|
|
1088
1234
|
cssClass: comp.cssClass,
|
|
1089
1235
|
hideBorders: comp.hideBorders,
|
|
@@ -1100,6 +1246,7 @@ export function convertComponentsToUISchema(components) {
|
|
|
1100
1246
|
return {
|
|
1101
1247
|
type: 'tabs',
|
|
1102
1248
|
id: comp.id,
|
|
1249
|
+
name: comp.name,
|
|
1103
1250
|
cssClass: comp.cssClass,
|
|
1104
1251
|
tabs
|
|
1105
1252
|
};
|
|
@@ -1108,6 +1255,7 @@ export function convertComponentsToUISchema(components) {
|
|
|
1108
1255
|
return {
|
|
1109
1256
|
type: 'card',
|
|
1110
1257
|
id: comp.id,
|
|
1258
|
+
name: comp.name,
|
|
1111
1259
|
label: comp.label,
|
|
1112
1260
|
cssClass: comp.cssClass,
|
|
1113
1261
|
hideLabel: comp.hideLabel,
|
|
@@ -1170,11 +1318,12 @@ export function generateDataSchemaFromUI(uiSchema) {
|
|
|
1170
1318
|
dataProperties[fieldId] = {
|
|
1171
1319
|
id: fieldId,
|
|
1172
1320
|
type: 'string',
|
|
1321
|
+
name: generateName(uiField.label),
|
|
1173
1322
|
label: uiField.label || fieldId
|
|
1174
1323
|
};
|
|
1175
1324
|
});
|
|
1176
1325
|
return {
|
|
1177
|
-
version: '1.0
|
|
1326
|
+
version: '0.1.0',
|
|
1178
1327
|
properties: dataProperties
|
|
1179
1328
|
};
|
|
1180
1329
|
}
|
package/src/parser.js
CHANGED
|
@@ -91,7 +91,7 @@ export class UniversalJsonParser {
|
|
|
91
91
|
});
|
|
92
92
|
}
|
|
93
93
|
const schema = {
|
|
94
|
-
version: input.version || "1.0
|
|
94
|
+
version: input.version || "0.1.0",
|
|
95
95
|
components
|
|
96
96
|
};
|
|
97
97
|
const schemaValidation = validateFormSchema(schema);
|
|
@@ -253,7 +253,7 @@ export class UniversalJsonParser {
|
|
|
253
253
|
return { type: 'textbox', textboxType: 'url' };
|
|
254
254
|
}
|
|
255
255
|
if (/^\d{4}-\d{2}-\d{2}/.test(value)) {
|
|
256
|
-
return { type: '
|
|
256
|
+
return { type: 'date' };
|
|
257
257
|
}
|
|
258
258
|
// For generic strings, check field name patterns
|
|
259
259
|
const fieldNameMatch = this.matchFieldNamePattern(fieldName);
|
|
@@ -284,7 +284,7 @@ export class UniversalJsonParser {
|
|
|
284
284
|
if (/email|mail|e-?mail/.test(normalized))
|
|
285
285
|
return { type: 'textbox', textboxType: 'email' };
|
|
286
286
|
if (/date|birth|birthday|dob|created|updated/.test(normalized))
|
|
287
|
-
return { type: '
|
|
287
|
+
return { type: 'date' };
|
|
288
288
|
if (/phone|tel|mobile|cell|cellphone|msisdn/.test(normalized))
|
|
289
289
|
return { type: 'textbox', textboxType: 'number' };
|
|
290
290
|
if (/url|link|uri|website/.test(normalized))
|
|
@@ -396,7 +396,7 @@ export class UniversalJsonParser {
|
|
|
396
396
|
defaultValue: element.defaultValue,
|
|
397
397
|
...Object.keys(validation).length > 0 ? validation : undefined
|
|
398
398
|
};
|
|
399
|
-
if (type === 'dropdown' || type === 'multiselect' || type === 'radio' || type === '
|
|
399
|
+
if (type === 'dropdown' || type === 'multiselect' || type === 'radio' || type === 'splitButton') {
|
|
400
400
|
if (element.choices && Array.isArray(element.choices)) {
|
|
401
401
|
component.options = element.choices;
|
|
402
402
|
}
|
|
@@ -449,7 +449,7 @@ export class UniversalJsonParser {
|
|
|
449
449
|
component.regex = VALIDATION_REGEX['URL'];
|
|
450
450
|
}
|
|
451
451
|
}
|
|
452
|
-
if (typeInference.type === '
|
|
452
|
+
if (typeInference.type === 'date' && component.defaultValue !== undefined && component.defaultValue !== null) {
|
|
453
453
|
const formatted = this.formatDateTimeValue(component.defaultValue);
|
|
454
454
|
if (formatted)
|
|
455
455
|
component.defaultValue = formatted;
|
|
@@ -487,6 +487,7 @@ export class UniversalJsonParser {
|
|
|
487
487
|
}
|
|
488
488
|
const component = {
|
|
489
489
|
id: propDef.id || this.generateComponentId(typeInfo.type),
|
|
490
|
+
name: propDef.name,
|
|
490
491
|
type: typeInfo.type,
|
|
491
492
|
label: propDef.title || propDef.label || this.humanizeFieldName(fieldName),
|
|
492
493
|
defaultValue: propDef.default || propDef.defaultValue || undefined,
|
|
@@ -511,12 +512,12 @@ export class UniversalJsonParser {
|
|
|
511
512
|
}
|
|
512
513
|
}
|
|
513
514
|
}
|
|
514
|
-
if (typeInfo.type === '
|
|
515
|
+
if (typeInfo.type === 'date' && component.defaultValue !== undefined && component.defaultValue !== null) {
|
|
515
516
|
const formatted = this.formatDateTimeValue(component.defaultValue);
|
|
516
517
|
if (formatted)
|
|
517
518
|
component.defaultValue = formatted;
|
|
518
519
|
}
|
|
519
|
-
if ((typeInfo.type === '
|
|
520
|
+
if ((typeInfo.type === 'splitButton' || typeInfo.type === 'dropdown' || typeInfo.type === 'multiselect' || typeInfo.type === 'radio') && (propDef.enum || propDef.options)) {
|
|
520
521
|
component.options = propDef.enum || propDef.options;
|
|
521
522
|
}
|
|
522
523
|
return component;
|
|
@@ -586,7 +587,7 @@ export class UniversalJsonParser {
|
|
|
586
587
|
if (/password|pwd|pass|security|secret|key/.test(fmt) || /password|pwd|pass|security|secret|key/.test(text))
|
|
587
588
|
return { type: 'textbox', textboxType: 'password' };
|
|
588
589
|
if (fmt === 'date' || fmt === 'date-time' || /date|birthday|dob/.test(text))
|
|
589
|
-
return { type: '
|
|
590
|
+
return { type: 'date' };
|
|
590
591
|
if (propDef.enum)
|
|
591
592
|
return { type: 'dropdown' };
|
|
592
593
|
if (propDef.options)
|
|
@@ -606,14 +607,14 @@ export class UniversalJsonParser {
|
|
|
606
607
|
return { type: 'button' };
|
|
607
608
|
if (schemaType === 'signature' || schemaType === 'sign')
|
|
608
609
|
return { type: 'signature' };
|
|
609
|
-
if (schemaType === 'image' || schemaType === '
|
|
610
|
-
return { type: '
|
|
611
|
-
if (schemaType === 'file' || schemaType === '
|
|
612
|
-
return { type: '
|
|
613
|
-
if (schemaType === 'color' || schemaType === '
|
|
614
|
-
return { type: '
|
|
615
|
-
if (schemaType === '
|
|
616
|
-
return { type: '
|
|
610
|
+
if (schemaType === 'image' || schemaType === 'imageEditor')
|
|
611
|
+
return { type: 'imageEditor' };
|
|
612
|
+
if (schemaType === 'file' || schemaType === 'fileUpload')
|
|
613
|
+
return { type: 'fileUpload' };
|
|
614
|
+
if (schemaType === 'color' || schemaType === 'colorPicker')
|
|
615
|
+
return { type: 'colorPicker' };
|
|
616
|
+
if (schemaType === 'splitButton')
|
|
617
|
+
return { type: 'splitButton' };
|
|
617
618
|
return { type: this.config.defaultTextType };
|
|
618
619
|
}
|
|
619
620
|
generateComponentId(type) {
|
|
@@ -716,7 +717,7 @@ export const validateFormSchema = (schema) => {
|
|
|
716
717
|
*/
|
|
717
718
|
export const sanitizeFormSchema = (schema) => {
|
|
718
719
|
const sanitized = {
|
|
719
|
-
version: schema.version || "1.0
|
|
720
|
+
version: schema.version || "0.1.0",
|
|
720
721
|
components: schema.components || []
|
|
721
722
|
};
|
|
722
723
|
const sanitizeComponent = (comp) => {
|
package/src/renderFormField.d.ts
CHANGED
|
@@ -12,8 +12,9 @@ export interface RenderFormFieldProps {
|
|
|
12
12
|
onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
|
|
13
13
|
ref?: any;
|
|
14
14
|
hasError?: boolean;
|
|
15
|
+
locale: string;
|
|
15
16
|
}
|
|
16
17
|
/**
|
|
17
18
|
* Renders a single form field component
|
|
18
19
|
*/
|
|
19
|
-
export declare const renderFormField: ({ component, value, onChange, onBlur, ref, hasError, onClick }: RenderFormFieldProps) => React.ReactNode;
|
|
20
|
+
export declare const renderFormField: ({ component, value, onChange, onBlur, ref, hasError, onClick, locale }: RenderFormFieldProps) => React.ReactNode;
|