@syncfusion/ej2-form-renderer 34.2.2
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/README.md +60 -0
- package/common.d.ts +4 -0
- package/common.js +4 -0
- package/dist/ej2-form-renderer.umd.min.js +11 -0
- package/dist/ej2-form-renderer.umd.min.js.map +1 -0
- package/dist/es6/ej2-form-renderer.es2015.js +6787 -0
- package/dist/es6/ej2-form-renderer.es2015.js.map +1 -0
- package/dist/es6/ej2-form-renderer.es5.js +7055 -0
- package/dist/es6/ej2-form-renderer.es5.js.map +1 -0
- package/dist/global/ej2-form-renderer.min.js +11 -0
- package/dist/global/ej2-form-renderer.min.js.map +1 -0
- package/dist/global/index.d.ts +14 -0
- package/form-renderer.d.ts +4 -0
- package/form-renderer.js +4 -0
- package/index.d.ts +4 -0
- package/index.js +4 -0
- package/license +10 -0
- package/package.json +41 -0
- package/src/common/checkbox-group.d.ts +55 -0
- package/src/common/checkbox-group.js +241 -0
- package/src/common/conditional-rule-engine.d.ts +241 -0
- package/src/common/conditional-rule-engine.js +505 -0
- package/src/common/custom-validation-engine.d.ts +57 -0
- package/src/common/custom-validation-engine.js +152 -0
- package/src/common/datagrid-component.d.ts +156 -0
- package/src/common/datagrid-component.js +410 -0
- package/src/common/expressions/dependency-tracker.d.ts +29 -0
- package/src/common/expressions/dependency-tracker.js +137 -0
- package/src/common/expressions/expression-engine.d.ts +94 -0
- package/src/common/expressions/expression-engine.js +514 -0
- package/src/common/expressions/expression-parser.d.ts +40 -0
- package/src/common/expressions/expression-parser.js +270 -0
- package/src/common/expressions/grid-expressions.d.ts +24 -0
- package/src/common/expressions/grid-expressions.js +92 -0
- package/src/common/expressions/index.d.ts +4 -0
- package/src/common/expressions/index.js +4 -0
- package/src/common/index.d.ts +10 -0
- package/src/common/index.js +10 -0
- package/src/common/json-converter.d.ts +945 -0
- package/src/common/json-converter.js +1110 -0
- package/src/common/minmax-rule-engine.d.ts +105 -0
- package/src/common/minmax-rule-engine.js +380 -0
- package/src/common/option-fetcher.d.ts +30 -0
- package/src/common/option-fetcher.js +139 -0
- package/src/common/parser.d.ts +98 -0
- package/src/common/parser.js +788 -0
- package/src/common/static-html-renderer.d.ts +45 -0
- package/src/common/static-html-renderer.js +77 -0
- package/src/common/utils.d.ts +116 -0
- package/src/common/utils.js +484 -0
- package/src/form-renderer/form-renderer-model.d.ts +65 -0
- package/src/form-renderer/form-renderer.d.ts +425 -0
- package/src/form-renderer/form-renderer.js +2277 -0
- package/src/form-renderer/index.d.ts +6 -0
- package/src/form-renderer/index.js +5 -0
- package/src/form-renderer/render-form-field.d.ts +31 -0
- package/src/form-renderer/render-form-field.js +816 -0
- package/src/form-renderer/types/form-schema.d.ts +452 -0
- package/src/form-renderer/types/form-schema.js +32 -0
- package/src/global.js +1 -0
- package/src/index.d.ts +4 -0
- package/src/index.js +4 -0
- package/styles/bds-lite.css +599 -0
- package/styles/bds-lite.scss +2 -0
- package/styles/bds.css +599 -0
- package/styles/bds.scss +2 -0
- package/styles/bootstrap-dark-lite.css +608 -0
- package/styles/bootstrap-dark-lite.scss +2 -0
- package/styles/bootstrap-dark.css +608 -0
- package/styles/bootstrap-dark.scss +2 -0
- package/styles/bootstrap-lite.css +595 -0
- package/styles/bootstrap-lite.scss +2 -0
- package/styles/bootstrap.css +595 -0
- package/styles/bootstrap.scss +2 -0
- package/styles/bootstrap4-lite.css +595 -0
- package/styles/bootstrap4-lite.scss +2 -0
- package/styles/bootstrap4.css +595 -0
- package/styles/bootstrap4.scss +2 -0
- package/styles/bootstrap5-dark-lite.css +595 -0
- package/styles/bootstrap5-dark-lite.scss +2 -0
- package/styles/bootstrap5-dark.css +595 -0
- package/styles/bootstrap5-dark.scss +2 -0
- package/styles/bootstrap5-lite.css +595 -0
- package/styles/bootstrap5-lite.scss +2 -0
- package/styles/bootstrap5.3-lite.css +679 -0
- package/styles/bootstrap5.3-lite.scss +2 -0
- package/styles/bootstrap5.3.css +679 -0
- package/styles/bootstrap5.3.scss +2 -0
- package/styles/bootstrap5.css +595 -0
- package/styles/bootstrap5.scss +2 -0
- package/styles/fabric-dark-lite.css +608 -0
- package/styles/fabric-dark-lite.scss +2 -0
- package/styles/fabric-dark.css +608 -0
- package/styles/fabric-dark.scss +2 -0
- package/styles/fabric-lite.css +595 -0
- package/styles/fabric-lite.scss +2 -0
- package/styles/fabric.css +595 -0
- package/styles/fabric.scss +2 -0
- package/styles/fluent-dark-lite.css +595 -0
- package/styles/fluent-dark-lite.scss +2 -0
- package/styles/fluent-dark.css +595 -0
- package/styles/fluent-dark.scss +2 -0
- package/styles/fluent-lite.css +595 -0
- package/styles/fluent-lite.scss +2 -0
- package/styles/fluent.css +595 -0
- package/styles/fluent.scss +2 -0
- package/styles/fluent2-lite.css +681 -0
- package/styles/fluent2-lite.scss +2 -0
- package/styles/fluent2.css +681 -0
- package/styles/fluent2.scss +2 -0
- package/styles/form-renderer/_all.scss +2 -0
- package/styles/form-renderer/_bds-definition.scss +8 -0
- package/styles/form-renderer/_bootstrap-dark-definition.scss +8 -0
- package/styles/form-renderer/_bootstrap-definition.scss +8 -0
- package/styles/form-renderer/_bootstrap4-definition.scss +8 -0
- package/styles/form-renderer/_bootstrap5-dark-definition.scss +8 -0
- package/styles/form-renderer/_bootstrap5-definition.scss +8 -0
- package/styles/form-renderer/_bootstrap5.3-definition.scss +8 -0
- package/styles/form-renderer/_fabric-dark-definition.scss +8 -0
- package/styles/form-renderer/_fabric-definition.scss +8 -0
- package/styles/form-renderer/_fluent-dark-definition.scss +8 -0
- package/styles/form-renderer/_fluent-definition.scss +8 -0
- package/styles/form-renderer/_fluent2-definition.scss +8 -0
- package/styles/form-renderer/_highcontrast-definition.scss +8 -0
- package/styles/form-renderer/_highcontrast-light-definition.scss +8 -0
- package/styles/form-renderer/_layout.scss +480 -0
- package/styles/form-renderer/_material-dark-definition.scss +8 -0
- package/styles/form-renderer/_material-definition.scss +8 -0
- package/styles/form-renderer/_material3-dark-definition.scss +8 -0
- package/styles/form-renderer/_material3-definition.scss +8 -0
- package/styles/form-renderer/_mixin.scss +26 -0
- package/styles/form-renderer/_tailwind-dark-definition.scss +8 -0
- package/styles/form-renderer/_tailwind-definition.scss +8 -0
- package/styles/form-renderer/_tailwind3-definition.scss +8 -0
- package/styles/form-renderer/_theme.scss +69 -0
- package/styles/form-renderer/_variables.scss +2 -0
- package/styles/form-renderer/bds.css +599 -0
- package/styles/form-renderer/bds.scss +2 -0
- package/styles/form-renderer/bootstrap-dark.css +608 -0
- package/styles/form-renderer/bootstrap-dark.scss +2 -0
- package/styles/form-renderer/bootstrap.css +595 -0
- package/styles/form-renderer/bootstrap.scss +2 -0
- package/styles/form-renderer/bootstrap4.css +595 -0
- package/styles/form-renderer/bootstrap4.scss +2 -0
- package/styles/form-renderer/bootstrap5-dark.css +595 -0
- package/styles/form-renderer/bootstrap5-dark.scss +2 -0
- package/styles/form-renderer/bootstrap5.3.css +679 -0
- package/styles/form-renderer/bootstrap5.3.scss +2 -0
- package/styles/form-renderer/bootstrap5.css +595 -0
- package/styles/form-renderer/bootstrap5.scss +2 -0
- package/styles/form-renderer/fabric-dark.css +608 -0
- package/styles/form-renderer/fabric-dark.scss +2 -0
- package/styles/form-renderer/fabric.css +595 -0
- package/styles/form-renderer/fabric.scss +2 -0
- package/styles/form-renderer/fluent-dark.css +595 -0
- package/styles/form-renderer/fluent-dark.scss +2 -0
- package/styles/form-renderer/fluent.css +595 -0
- package/styles/form-renderer/fluent.scss +2 -0
- package/styles/form-renderer/fluent2.css +681 -0
- package/styles/form-renderer/fluent2.scss +2 -0
- package/styles/form-renderer/highcontrast-light.css +595 -0
- package/styles/form-renderer/highcontrast-light.scss +2 -0
- package/styles/form-renderer/highcontrast.css +608 -0
- package/styles/form-renderer/highcontrast.scss +2 -0
- package/styles/form-renderer/material-dark.css +608 -0
- package/styles/form-renderer/material-dark.scss +2 -0
- package/styles/form-renderer/material.css +595 -0
- package/styles/form-renderer/material.scss +2 -0
- package/styles/form-renderer/material3-dark.css +677 -0
- package/styles/form-renderer/material3-dark.scss +2 -0
- package/styles/form-renderer/material3.css +679 -0
- package/styles/form-renderer/material3.scss +2 -0
- package/styles/form-renderer/tailwind-dark.css +595 -0
- package/styles/form-renderer/tailwind-dark.scss +2 -0
- package/styles/form-renderer/tailwind.css +595 -0
- package/styles/form-renderer/tailwind.scss +2 -0
- package/styles/form-renderer/tailwind3.css +669 -0
- package/styles/form-renderer/tailwind3.scss +2 -0
- package/styles/highcontrast-light-lite.css +595 -0
- package/styles/highcontrast-light-lite.scss +2 -0
- package/styles/highcontrast-light.css +595 -0
- package/styles/highcontrast-light.scss +2 -0
- package/styles/highcontrast-lite.css +608 -0
- package/styles/highcontrast-lite.scss +2 -0
- package/styles/highcontrast.css +608 -0
- package/styles/highcontrast.scss +2 -0
- package/styles/material-dark-lite.css +608 -0
- package/styles/material-dark-lite.scss +2 -0
- package/styles/material-dark.css +608 -0
- package/styles/material-dark.scss +2 -0
- package/styles/material-lite.css +595 -0
- package/styles/material-lite.scss +2 -0
- package/styles/material.css +595 -0
- package/styles/material.scss +2 -0
- package/styles/material3-dark-lite.css +677 -0
- package/styles/material3-dark-lite.scss +2 -0
- package/styles/material3-dark.css +677 -0
- package/styles/material3-dark.scss +2 -0
- package/styles/material3-lite.css +679 -0
- package/styles/material3-lite.scss +2 -0
- package/styles/material3.css +679 -0
- package/styles/material3.scss +2 -0
- package/styles/tailwind-dark-lite.css +595 -0
- package/styles/tailwind-dark-lite.scss +2 -0
- package/styles/tailwind-dark.css +595 -0
- package/styles/tailwind-dark.scss +2 -0
- package/styles/tailwind-lite.css +595 -0
- package/styles/tailwind-lite.scss +2 -0
- package/styles/tailwind.css +595 -0
- package/styles/tailwind.scss +2 -0
- package/styles/tailwind3-lite.css +669 -0
- package/styles/tailwind3-lite.scss +2 -0
- package/styles/tailwind3.css +669 -0
- package/styles/tailwind3.scss +2 -0
|
@@ -0,0 +1,1110 @@
|
|
|
1
|
+
var __assign = (this && this.__assign) || function () {
|
|
2
|
+
__assign = Object.assign || function(t) {
|
|
3
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
4
|
+
s = arguments[i];
|
|
5
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
6
|
+
t[p] = s[p];
|
|
7
|
+
}
|
|
8
|
+
return t;
|
|
9
|
+
};
|
|
10
|
+
return __assign.apply(this, arguments);
|
|
11
|
+
};
|
|
12
|
+
import { sanitizeId } from './utils';
|
|
13
|
+
/**
|
|
14
|
+
* @private
|
|
15
|
+
*/
|
|
16
|
+
export var generateId = function (type) {
|
|
17
|
+
var timestamp = Date.now();
|
|
18
|
+
var random = Math.floor(Math.random() * 1000);
|
|
19
|
+
return type + "_" + timestamp + "_" + random;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* @private
|
|
23
|
+
*/
|
|
24
|
+
export var generateName = function (type) {
|
|
25
|
+
var random = Math.floor(Math.random() * 1000);
|
|
26
|
+
return type + "_" + random;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* @private
|
|
30
|
+
*/
|
|
31
|
+
export function labelToFieldKey(label, fallbackId) {
|
|
32
|
+
if (!label || label.trim() === '') {
|
|
33
|
+
return fallbackId || 'field';
|
|
34
|
+
}
|
|
35
|
+
// Remove special characters, keep letters, numbers, and spaces
|
|
36
|
+
var cleaned = label
|
|
37
|
+
.replace(/[^\w\s]/g, '') // Remove special chars
|
|
38
|
+
.trim();
|
|
39
|
+
if (cleaned === '') {
|
|
40
|
+
return fallbackId || 'field';
|
|
41
|
+
}
|
|
42
|
+
// Split by spaces/underscores and convert to camelCase
|
|
43
|
+
var words = cleaned.split(/[\s_]+/);
|
|
44
|
+
if (words.length === 0) {
|
|
45
|
+
return fallbackId || 'field';
|
|
46
|
+
}
|
|
47
|
+
// First word lowercase, rest title case
|
|
48
|
+
var camelCase = words
|
|
49
|
+
.map(function (word, index) {
|
|
50
|
+
if (index === 0) {
|
|
51
|
+
return word.toLowerCase();
|
|
52
|
+
}
|
|
53
|
+
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
|
|
54
|
+
})
|
|
55
|
+
.join('');
|
|
56
|
+
return camelCase || fallbackId || 'field';
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* @private
|
|
60
|
+
*/
|
|
61
|
+
export function makeUniqueFieldKey(baseKey, existingKeys, originalId) {
|
|
62
|
+
if (!existingKeys.has(baseKey)) {
|
|
63
|
+
return baseKey;
|
|
64
|
+
}
|
|
65
|
+
// Try with original ID suffix first
|
|
66
|
+
if (originalId) {
|
|
67
|
+
var withId = baseKey + "_" + originalId.split('_').pop();
|
|
68
|
+
if (!existingKeys.has(withId)) {
|
|
69
|
+
return withId;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// Fall back to numeric suffix
|
|
73
|
+
var counter = 2;
|
|
74
|
+
while (existingKeys.has("" + baseKey + counter)) {
|
|
75
|
+
counter++;
|
|
76
|
+
}
|
|
77
|
+
return "" + baseKey + counter;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* @private
|
|
81
|
+
*/
|
|
82
|
+
export var UI_TYPE_TO_SEMANTIC_TYPE = {
|
|
83
|
+
// String types
|
|
84
|
+
'textbox': { type: 'string' },
|
|
85
|
+
'textarea': { type: 'string' },
|
|
86
|
+
'email': { type: 'string', format: 'email' },
|
|
87
|
+
'url': { type: 'string', format: 'url' },
|
|
88
|
+
'phone': { type: 'string' },
|
|
89
|
+
'password': { type: 'string' },
|
|
90
|
+
'inputMask': { type: 'string' },
|
|
91
|
+
// Numeric types
|
|
92
|
+
'number': { type: 'number' },
|
|
93
|
+
'rangeSlider': { type: 'number' },
|
|
94
|
+
'rating': { type: 'number' },
|
|
95
|
+
// Boolean types
|
|
96
|
+
'checkbox': { type: 'boolean' },
|
|
97
|
+
'switch': { type: 'boolean' },
|
|
98
|
+
// Date/Time types
|
|
99
|
+
'date': { type: 'string', format: 'date' },
|
|
100
|
+
'dateTime': { type: 'string', format: 'date-time' },
|
|
101
|
+
'time': { type: 'string', format: 'time' },
|
|
102
|
+
'dateRange': { type: 'string', format: 'date' },
|
|
103
|
+
// Array types
|
|
104
|
+
'multiselect': { type: 'array' },
|
|
105
|
+
'checkboxGroup': { type: 'array' },
|
|
106
|
+
// Single-value selection (stored as string)
|
|
107
|
+
'dropdown': { type: 'string' },
|
|
108
|
+
'radio': { type: 'string' },
|
|
109
|
+
'splitButton': { type: 'string' },
|
|
110
|
+
// Special types (excluded from data schema)
|
|
111
|
+
'button': { type: 'button' },
|
|
112
|
+
'message': { type: 'message' },
|
|
113
|
+
'signature': { type: 'sign' },
|
|
114
|
+
'imageEditor': { type: 'image' },
|
|
115
|
+
'fileUpload': { type: 'file' },
|
|
116
|
+
'colorPicker': { type: 'color' },
|
|
117
|
+
'dataGrid': { type: 'grid' },
|
|
118
|
+
// Layout types (excluded from data schema)
|
|
119
|
+
'staticHtml': { type: 'string' },
|
|
120
|
+
'table': null,
|
|
121
|
+
'panel': null,
|
|
122
|
+
'tabs': null,
|
|
123
|
+
'card': null
|
|
124
|
+
};
|
|
125
|
+
/**
|
|
126
|
+
* @private
|
|
127
|
+
*/
|
|
128
|
+
function stripConfiguredFlag(properties) {
|
|
129
|
+
var out = {};
|
|
130
|
+
var keys = Object.keys(properties || {});
|
|
131
|
+
for (var _i = 0, keys_1 = keys; _i < keys_1.length; _i++) {
|
|
132
|
+
var key = keys_1[_i];
|
|
133
|
+
var val = properties[key];
|
|
134
|
+
var copy = __assign({}, val);
|
|
135
|
+
if (copy && copy._configured !== undefined) {
|
|
136
|
+
delete copy._configured;
|
|
137
|
+
}
|
|
138
|
+
out[key] = copy;
|
|
139
|
+
}
|
|
140
|
+
return out;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* @private
|
|
144
|
+
*/
|
|
145
|
+
function cleanConditionRules(condition, validFieldIds) {
|
|
146
|
+
if (!condition || !condition.rules || !Array.isArray(condition.rules)) {
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
var cleanedRules = condition.rules
|
|
150
|
+
.map(function (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(function (rule) { return rule !== null && rule !== undefined; });
|
|
162
|
+
// If no rules remain, return undefined
|
|
163
|
+
if (cleanedRules.length === 0) {
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
return __assign({}, condition, { rules: cleanedRules });
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* @private
|
|
170
|
+
*/
|
|
171
|
+
function getAllComponentIds(components) {
|
|
172
|
+
var ids = new Set();
|
|
173
|
+
var stack = components.slice();
|
|
174
|
+
while (stack.length) {
|
|
175
|
+
var comp = stack.pop();
|
|
176
|
+
ids.add(comp.id);
|
|
177
|
+
if (comp.children)
|
|
178
|
+
stack.push.apply(stack, comp.children);
|
|
179
|
+
if (comp.tabItems) {
|
|
180
|
+
comp.tabItems.forEach(function (tab) {
|
|
181
|
+
if (tab.content)
|
|
182
|
+
stack.push.apply(stack, tab.content);
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
if (comp.tableCells) {
|
|
186
|
+
comp.tableCells.forEach(function (row) {
|
|
187
|
+
row.forEach(function (cell) { return stack.push.apply(stack, cell); });
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return ids;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* @private
|
|
195
|
+
*/
|
|
196
|
+
export function splitSchema(heartSchema) {
|
|
197
|
+
var dataFields = {};
|
|
198
|
+
var uiFields = {};
|
|
199
|
+
var layout = [];
|
|
200
|
+
var usedFieldKeys = new Set(); // Track unique field keys
|
|
201
|
+
var seenComponentIds = new Set(); // Track component IDs to prevent duplicates
|
|
202
|
+
// ✅ Get all valid field IDs for condition validation
|
|
203
|
+
var validFieldIds = getAllComponentIds(heartSchema.components);
|
|
204
|
+
// Stack for iterative traversal (avoid recursion)
|
|
205
|
+
var stack = heartSchema.components.map(function (c) { return ({
|
|
206
|
+
component: c,
|
|
207
|
+
layoutParent: layout
|
|
208
|
+
}); });
|
|
209
|
+
var _loop_1 = function () {
|
|
210
|
+
// Use shift() instead of pop() to maintain order (FIFO instead of LIFO)
|
|
211
|
+
var _a = stack.shift(), component = _a.component, layoutParent = _a.layoutParent;
|
|
212
|
+
// ✅ Clean conditions before processing
|
|
213
|
+
if (component.conditions) {
|
|
214
|
+
var cleanedConditions = {};
|
|
215
|
+
var hasValidConditions = false;
|
|
216
|
+
if (component.conditions.visibleWhen) {
|
|
217
|
+
var cleaned = cleanConditionRules(component.conditions.visibleWhen, validFieldIds);
|
|
218
|
+
if (cleaned) {
|
|
219
|
+
cleanedConditions.visibleWhen = cleaned;
|
|
220
|
+
hasValidConditions = true;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (component.conditions.hideWhen) {
|
|
224
|
+
var cleaned = cleanConditionRules(component.conditions.hideWhen, validFieldIds);
|
|
225
|
+
if (cleaned) {
|
|
226
|
+
cleanedConditions.hideWhen = cleaned;
|
|
227
|
+
hasValidConditions = true;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (component.conditions.readOnlyWhen) {
|
|
231
|
+
var cleaned = cleanConditionRules(component.conditions.readOnlyWhen, validFieldIds);
|
|
232
|
+
if (cleaned) {
|
|
233
|
+
cleanedConditions.readOnlyWhen = cleaned;
|
|
234
|
+
hasValidConditions = true;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
if (component.conditions.disabledWhen) {
|
|
238
|
+
var cleaned = cleanConditionRules(component.conditions.disabledWhen, validFieldIds);
|
|
239
|
+
if (cleaned) {
|
|
240
|
+
cleanedConditions.disabledWhen = cleaned;
|
|
241
|
+
hasValidConditions = true;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if (component.conditions.requiredWhen) {
|
|
245
|
+
var cleaned = cleanConditionRules(component.conditions.requiredWhen, validFieldIds);
|
|
246
|
+
if (cleaned) {
|
|
247
|
+
cleanedConditions.requiredWhen = cleaned;
|
|
248
|
+
hasValidConditions = true;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
if (component.conditions.setValueWhen) {
|
|
252
|
+
var cleaned = cleanConditionRules(component.conditions.setValueWhen.condition, validFieldIds);
|
|
253
|
+
if (cleaned && component.conditions.setValueWhen.value !== undefined && component.conditions.setValueWhen.value !== '') {
|
|
254
|
+
cleanedConditions.setValueWhen = {
|
|
255
|
+
condition: cleaned,
|
|
256
|
+
value: component.conditions.setValueWhen.value
|
|
257
|
+
};
|
|
258
|
+
hasValidConditions = true;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
if (component.conditions && component.conditions.choiceBasedField) {
|
|
262
|
+
var rule = component.conditions.choiceBasedField;
|
|
263
|
+
// Validate that primary field exists
|
|
264
|
+
if (rule.primaryFieldId && validFieldIds.has(rule.primaryFieldId)) {
|
|
265
|
+
cleanedConditions.choiceBasedField = rule;
|
|
266
|
+
hasValidConditions = true;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
if (component.conditions.conditionalData && Array.isArray(component.conditions.conditionalData)) {
|
|
270
|
+
var cleanedDataRules = component.conditions.conditionalData
|
|
271
|
+
.map(function (rule) {
|
|
272
|
+
var cleaned = cleanConditionRules(rule.condition, validFieldIds);
|
|
273
|
+
if (cleaned && rule.dataSource) {
|
|
274
|
+
return {
|
|
275
|
+
condition: cleaned,
|
|
276
|
+
dataSource: rule.dataSource,
|
|
277
|
+
columns: rule.columns,
|
|
278
|
+
includeColumns: rule.includeColumns
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
return null;
|
|
282
|
+
})
|
|
283
|
+
.filter(function (rule) { return rule !== null && rule !== undefined; });
|
|
284
|
+
if (cleanedDataRules.length > 0) {
|
|
285
|
+
cleanedConditions.conditionalData = cleanedDataRules;
|
|
286
|
+
hasValidConditions = true;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
// Update component conditions (or remove if empty)
|
|
290
|
+
component.conditions = hasValidConditions ? cleanedConditions : undefined;
|
|
291
|
+
}
|
|
292
|
+
// Get semantic type mapping (may be null for presentation-only widgets)
|
|
293
|
+
var semanticType = UI_TYPE_TO_SEMANTIC_TYPE[component.type];
|
|
294
|
+
// Handle layout containers
|
|
295
|
+
if (component.type === 'table') {
|
|
296
|
+
var safeId = sanitizeId(component.id, generateId(semanticType.type));
|
|
297
|
+
var tableNode_1 = __assign({ type: 'table', id: safeId, name: component.name, label: component.label, cssClass: component.cssClass, hideBorders: component.hideBorders, rows: (component.tableCells && component.tableCells.length) || 0, cols: (component.tableCells && component.tableCells[0] && component.tableCells[0].length) || 0, cells: [], columnWidths: component.columnWidths }, (component.conditions ? { conditions: component.conditions } : {}));
|
|
298
|
+
// Process table cells
|
|
299
|
+
if (component.tableCells) {
|
|
300
|
+
component.tableCells.forEach(function (row, rowIndex) {
|
|
301
|
+
var cellRow = [];
|
|
302
|
+
row.forEach(function (cellComponents, colIndex) {
|
|
303
|
+
var cellChildren = [];
|
|
304
|
+
// Reverse children and use unshift() to maintain order with depth-first processing
|
|
305
|
+
cellComponents.slice().reverse().forEach(function (comp) {
|
|
306
|
+
stack.unshift({ component: comp, layoutParent: cellChildren });
|
|
307
|
+
});
|
|
308
|
+
cellRow.push({ row: rowIndex, col: colIndex, children: cellChildren });
|
|
309
|
+
});
|
|
310
|
+
tableNode_1.cells.push(cellRow);
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
layoutParent.push(tableNode_1);
|
|
314
|
+
return "continue";
|
|
315
|
+
}
|
|
316
|
+
if (component.type === 'panel') {
|
|
317
|
+
var safeId = sanitizeId(component.id, generateId(semanticType.type));
|
|
318
|
+
var panelNode_1 = __assign({ type: 'panel', id: safeId, name: component.name, label: component.label, cssClass: component.cssClass, hideBorders: component.hideBorders, hideLabel: component.hideLabel, children: [] }, (component.conditions ? { conditions: component.conditions } : {}));
|
|
319
|
+
// Process panel children
|
|
320
|
+
// Reverse children and use unshift() to maintain order with depth-first processing
|
|
321
|
+
(component.children || []).slice().reverse().forEach(function (child) {
|
|
322
|
+
stack.unshift({ component: child, layoutParent: panelNode_1.children });
|
|
323
|
+
});
|
|
324
|
+
layoutParent.push(panelNode_1);
|
|
325
|
+
return "continue";
|
|
326
|
+
}
|
|
327
|
+
if (component.type === 'tabs') {
|
|
328
|
+
var safeId = sanitizeId(component.id, generateId(semanticType.type));
|
|
329
|
+
var tabsNode_1 = __assign({ type: 'tabs', name: component.name, id: safeId, label: component.label, cssClass: component.cssClass, tabOptions: component.tabOptions, tabs: [] }, (component.conditions ? { conditions: component.conditions } : {}));
|
|
330
|
+
// Process tabs
|
|
331
|
+
if (component.tabItems) {
|
|
332
|
+
component.tabItems.forEach(function (tab) {
|
|
333
|
+
var tabChildren = [];
|
|
334
|
+
// Reverse children and use unshift() to maintain order with depth-first processing
|
|
335
|
+
(tab.content || []).slice().reverse().forEach(function (comp) {
|
|
336
|
+
stack.unshift({ component: comp, layoutParent: tabChildren });
|
|
337
|
+
});
|
|
338
|
+
tabsNode_1.tabs.push({ header: tab.header, children: tabChildren });
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
layoutParent.push(tabsNode_1);
|
|
342
|
+
return "continue";
|
|
343
|
+
}
|
|
344
|
+
if (component.type === 'card') {
|
|
345
|
+
var safeId = sanitizeId(component.id, generateId(semanticType.type));
|
|
346
|
+
var cardNode_1 = __assign({ type: 'card', id: safeId, name: component.name, label: component.label, cssClass: component.cssClass, hideLabel: component.hideLabel, cardTitle: component.cardTitle, cardSubtitle: component.cardSubtitle, children: [] }, (component.conditions ? { conditions: component.conditions } : {}));
|
|
347
|
+
// Process card children
|
|
348
|
+
// Reverse children and use unshift() to maintain order with depth-first processing
|
|
349
|
+
(component.children || []).slice().reverse().forEach(function (child) {
|
|
350
|
+
stack.unshift({ component: child, layoutParent: cardNode_1.children });
|
|
351
|
+
});
|
|
352
|
+
layoutParent.push(cardNode_1);
|
|
353
|
+
return "continue";
|
|
354
|
+
}
|
|
355
|
+
// ✅ DEDUPLICATION: Skip if we've already processed this component ID
|
|
356
|
+
if (seenComponentIds.has(component.id)) {
|
|
357
|
+
return "continue";
|
|
358
|
+
}
|
|
359
|
+
seenComponentIds.add(component.id);
|
|
360
|
+
// Generate semantic field key from label
|
|
361
|
+
var baseFieldKey = labelToFieldKey(component.label || component.id, component.id);
|
|
362
|
+
var fieldKey = makeUniqueFieldKey(baseFieldKey, usedFieldKeys, component.id);
|
|
363
|
+
usedFieldKeys.add(fieldKey);
|
|
364
|
+
// Extract data field properties (all components, no skipping)
|
|
365
|
+
// If no semantic type mapping exists, use 'any' as the type
|
|
366
|
+
var dataField = extractDataField(component, semanticType || { type: 'any' });
|
|
367
|
+
dataFields[fieldKey] = dataField;
|
|
368
|
+
// Extract UI field properties (all components)
|
|
369
|
+
uiFields[fieldKey] = extractUIField(component);
|
|
370
|
+
// Add property reference to layout (use fieldKey instead of component ID)
|
|
371
|
+
layoutParent.push({ type: 'field', propertyId: fieldKey });
|
|
372
|
+
};
|
|
373
|
+
while (stack.length > 0) {
|
|
374
|
+
_loop_1();
|
|
375
|
+
}
|
|
376
|
+
// ✅ Merge data and UI fields into unified properties
|
|
377
|
+
var unifiedProperties = {};
|
|
378
|
+
Object.keys(dataFields).forEach(function (fieldKey) {
|
|
379
|
+
var dataField = dataFields[fieldKey];
|
|
380
|
+
var uiField = uiFields[fieldKey];
|
|
381
|
+
unifiedProperties[fieldKey] = __assign({}, dataField, uiField, {
|
|
382
|
+
// Ensure widget is always set
|
|
383
|
+
widget: (uiField && uiField.widget) || inferWidgetFromType(dataField.type, dataField.format, dataField.options !== undefined) });
|
|
384
|
+
});
|
|
385
|
+
var settings = heartSchema.settings ? __assign({}, heartSchema.settings) : {};
|
|
386
|
+
return __assign({ version: heartSchema.version, properties: stripConfiguredFlag(unifiedProperties), layout: layout }, (Object.keys(settings).length > 0 ? { settings: settings } : {}));
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* @private
|
|
390
|
+
*/
|
|
391
|
+
function extractDataField(component, semanticType) {
|
|
392
|
+
var safeId = sanitizeId(component.id, generateId(semanticType.type));
|
|
393
|
+
var dataField = {
|
|
394
|
+
id: safeId,
|
|
395
|
+
name: component.name,
|
|
396
|
+
type: semanticType.type,
|
|
397
|
+
};
|
|
398
|
+
// Add format if present
|
|
399
|
+
if (semanticType.format) {
|
|
400
|
+
dataField.format = semanticType.format;
|
|
401
|
+
}
|
|
402
|
+
// Extract allowed properties only
|
|
403
|
+
if (component.label !== undefined)
|
|
404
|
+
dataField.label = component.label;
|
|
405
|
+
if (component.defaultValue !== undefined)
|
|
406
|
+
dataField.defaultValue = component.defaultValue;
|
|
407
|
+
if (component.expressionValue !== undefined)
|
|
408
|
+
dataField.expressionValue = component.expressionValue;
|
|
409
|
+
if (component.content !== undefined)
|
|
410
|
+
dataField.content = component.content;
|
|
411
|
+
if (component.buttonType !== undefined)
|
|
412
|
+
dataField.buttonType = component.buttonType;
|
|
413
|
+
if (component.textboxType !== undefined)
|
|
414
|
+
dataField.textboxType = component.textboxType;
|
|
415
|
+
if (component.required !== undefined)
|
|
416
|
+
dataField.required = component.required;
|
|
417
|
+
// Extract options for selection fields (dropdown, radio, multiselect)
|
|
418
|
+
if (component.options !== undefined)
|
|
419
|
+
dataField.options = component.options;
|
|
420
|
+
if (component.minLength !== undefined)
|
|
421
|
+
dataField.minLength = component.minLength;
|
|
422
|
+
if (component.maxLength !== undefined)
|
|
423
|
+
dataField.maxLength = component.maxLength;
|
|
424
|
+
// Handle numeric validation (use canonical names)
|
|
425
|
+
if (component.minValue !== undefined)
|
|
426
|
+
dataField.minValue = component.minValue;
|
|
427
|
+
if (component.maxValue !== undefined)
|
|
428
|
+
dataField.maxValue = component.maxValue;
|
|
429
|
+
// Date/time validation
|
|
430
|
+
if (component.minDate !== undefined)
|
|
431
|
+
dataField.minDate = component.minDate;
|
|
432
|
+
if (component.maxDate !== undefined)
|
|
433
|
+
dataField.maxDate = component.maxDate;
|
|
434
|
+
if (component.minTime !== undefined)
|
|
435
|
+
dataField.minTime = component.minTime;
|
|
436
|
+
if (component.maxTime !== undefined)
|
|
437
|
+
dataField.maxTime = component.maxTime;
|
|
438
|
+
// Pattern validation (rename from regex, convert RegExp to string)
|
|
439
|
+
if (component.regex !== undefined) {
|
|
440
|
+
var regex = component.regex;
|
|
441
|
+
dataField.pattern = regex instanceof RegExp ? regex.source : regex;
|
|
442
|
+
}
|
|
443
|
+
if (component.pattern !== undefined) {
|
|
444
|
+
var pattern = component.pattern;
|
|
445
|
+
dataField.pattern = pattern instanceof RegExp ? pattern.source : pattern;
|
|
446
|
+
}
|
|
447
|
+
return dataField;
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* @private
|
|
451
|
+
*/
|
|
452
|
+
function extractUIField(component) {
|
|
453
|
+
var uiField = {
|
|
454
|
+
widget: component.type,
|
|
455
|
+
};
|
|
456
|
+
// Copy all properties EXCEPT those in data schema allowed list
|
|
457
|
+
var dataSchemaProps = new Set([
|
|
458
|
+
'id', 'type', 'label', 'format', 'defaultValue', 'required', 'options', 'name', 'expressionValue',
|
|
459
|
+
'minLength', 'maxLength', 'minimum', 'maximum', 'minValue', 'maxValue',
|
|
460
|
+
'minDate', 'maxDate', 'minTime', 'maxTime', 'regex', 'pattern', 'content', 'buttonType', 'textboxType'
|
|
461
|
+
]);
|
|
462
|
+
Object.keys(component).forEach(function (key) {
|
|
463
|
+
if (!dataSchemaProps.has(key) && key !== 'id' && key !== 'type') {
|
|
464
|
+
uiField[key] = component[key];
|
|
465
|
+
}
|
|
466
|
+
});
|
|
467
|
+
// Special handling: Filter dataGrid columns to keep only required properties
|
|
468
|
+
if (component.type === 'dataGrid' && uiField.columns && Array.isArray(uiField.columns)) {
|
|
469
|
+
var requiredColumnProps_1 = [
|
|
470
|
+
'field',
|
|
471
|
+
'headerText',
|
|
472
|
+
'type',
|
|
473
|
+
'width',
|
|
474
|
+
'format',
|
|
475
|
+
'editType',
|
|
476
|
+
'validationRules',
|
|
477
|
+
'allowEditing',
|
|
478
|
+
'expression',
|
|
479
|
+
'isPrimaryKey',
|
|
480
|
+
'customValidation'
|
|
481
|
+
];
|
|
482
|
+
uiField.columns = uiField.columns.map(function (col) {
|
|
483
|
+
var filteredCol = {};
|
|
484
|
+
requiredColumnProps_1.forEach(function (prop) {
|
|
485
|
+
if (col[prop] !== undefined) {
|
|
486
|
+
filteredCol[prop] = col[prop];
|
|
487
|
+
}
|
|
488
|
+
});
|
|
489
|
+
return filteredCol;
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
return uiField;
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* @private
|
|
496
|
+
*/
|
|
497
|
+
export function mergeSchema(unified) {
|
|
498
|
+
// Create lookup map for O(1) access to unified properties
|
|
499
|
+
var propertyMap = new Map();
|
|
500
|
+
var unifiedPropKeys = Object.keys(unified.properties);
|
|
501
|
+
for (var _i = 0, unifiedPropKeys_1 = unifiedPropKeys; _i < unifiedPropKeys_1.length; _i++) {
|
|
502
|
+
var id = unifiedPropKeys_1[_i];
|
|
503
|
+
propertyMap.set(id, unified.properties[id]);
|
|
504
|
+
}
|
|
505
|
+
// Track processed field IDs to prevent duplicates
|
|
506
|
+
var processedFieldIds = new Set();
|
|
507
|
+
// Recursive function to process layout nodes
|
|
508
|
+
var processLayoutNodes = function (layoutNodes) {
|
|
509
|
+
var components = [];
|
|
510
|
+
var _loop_2 = function (node) {
|
|
511
|
+
if (node.type === 'field') {
|
|
512
|
+
var fieldNode = node;
|
|
513
|
+
// ✅ DEDUPLICATION: Skip if we've already processed this field
|
|
514
|
+
if (processedFieldIds.has(fieldNode.propertyId)) {
|
|
515
|
+
return "continue";
|
|
516
|
+
}
|
|
517
|
+
processedFieldIds.add(fieldNode.propertyId);
|
|
518
|
+
var unifiedField = propertyMap.get(fieldNode.propertyId);
|
|
519
|
+
if (!unifiedField) {
|
|
520
|
+
// console.warn(`Field ${fieldNode.propertyId} in layout not found in schema`);
|
|
521
|
+
// Generate fallback component
|
|
522
|
+
var fallbackComponent = {
|
|
523
|
+
id: fieldNode.propertyId || generateId('textbox'),
|
|
524
|
+
name: generateName(''),
|
|
525
|
+
type: 'textbox',
|
|
526
|
+
label: 'Unknown Field',
|
|
527
|
+
_configured: true
|
|
528
|
+
};
|
|
529
|
+
components.push(fallbackComponent);
|
|
530
|
+
}
|
|
531
|
+
else {
|
|
532
|
+
components.push(mergeUnifiedField(unifiedField));
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
else if (node.type === 'table') {
|
|
536
|
+
var tableNode = node;
|
|
537
|
+
var tableComponent_1 = __assign({ id: tableNode.id || generateId('table'), name: tableNode.name, type: 'table', label: tableNode.label || 'Table', cssClass: tableNode.cssClass, hideBorders: tableNode.hideBorders, rows: tableNode.rows, columns: tableNode.cols, columnWidths: tableNode.columnWidths, tableCells: [], _configured: true }, (tableNode.conditions ? { conditions: tableNode.conditions } : {}));
|
|
538
|
+
// Process table cells recursively
|
|
539
|
+
tableNode.cells.forEach(function (row) {
|
|
540
|
+
var cellRow = [];
|
|
541
|
+
row.forEach(function (cell) {
|
|
542
|
+
var cellComponents = processLayoutNodes(cell.children);
|
|
543
|
+
cellRow.push(cellComponents);
|
|
544
|
+
});
|
|
545
|
+
tableComponent_1.tableCells.push(cellRow);
|
|
546
|
+
});
|
|
547
|
+
components.push(tableComponent_1);
|
|
548
|
+
}
|
|
549
|
+
else if (node.type === 'panel') {
|
|
550
|
+
var panelNode = node;
|
|
551
|
+
var panelComponent = __assign({ id: panelNode.id || generateId('panel'), type: 'panel', name: panelNode.name, cssClass: panelNode.cssClass, hideBorders: panelNode.hideBorders, hideLabel: panelNode.hideLabel, label: panelNode.label || 'Panel', children: processLayoutNodes(panelNode.children), _configured: true }, (panelNode.conditions ? { conditions: panelNode.conditions } : {}));
|
|
552
|
+
components.push(panelComponent);
|
|
553
|
+
}
|
|
554
|
+
else if (node.type === 'tabs') {
|
|
555
|
+
var tabsNode = node;
|
|
556
|
+
var tabsComponent_1 = __assign({ id: tabsNode.id || generateId('tabs'), type: 'tabs', name: tabsNode.name, label: tabsNode.label || 'Tabs', cssClass: tabsNode.cssClass, tabOptions: tabsNode.tabOptions, tabItems: [], _configured: true }, (tabsNode.conditions ? { conditions: tabsNode.conditions } : {}));
|
|
557
|
+
// Process tabs recursively
|
|
558
|
+
tabsNode.tabs.forEach(function (tab) {
|
|
559
|
+
tabsComponent_1.tabItems.push({
|
|
560
|
+
header: tab.header,
|
|
561
|
+
content: processLayoutNodes(tab.children)
|
|
562
|
+
});
|
|
563
|
+
});
|
|
564
|
+
components.push(tabsComponent_1);
|
|
565
|
+
}
|
|
566
|
+
else if (node.type === 'card') {
|
|
567
|
+
var cardNode = node;
|
|
568
|
+
var cardComponent = __assign({ id: cardNode.id || generateId('card'), type: 'card', name: cardNode.name, label: cardNode.label || 'Card', cssClass: cardNode.cssClass, hideLabel: cardNode.hideLabel, children: processLayoutNodes(cardNode.children), _configured: true }, (cardNode.conditions ? { conditions: cardNode.conditions } : {}));
|
|
569
|
+
// Add card-specific properties
|
|
570
|
+
if (cardNode.cardTitle !== undefined)
|
|
571
|
+
cardComponent.cardTitle = cardNode.cardTitle;
|
|
572
|
+
if (cardNode.cardSubtitle !== undefined)
|
|
573
|
+
cardComponent.cardSubtitle = cardNode.cardSubtitle;
|
|
574
|
+
components.push(cardComponent);
|
|
575
|
+
}
|
|
576
|
+
};
|
|
577
|
+
for (var _i = 0, layoutNodes_1 = layoutNodes; _i < layoutNodes_1.length; _i++) {
|
|
578
|
+
var node = layoutNodes_1[_i];
|
|
579
|
+
_loop_2(node);
|
|
580
|
+
}
|
|
581
|
+
return components;
|
|
582
|
+
};
|
|
583
|
+
// Process root layout nodes
|
|
584
|
+
var components;
|
|
585
|
+
if (!unified.layout || unified.layout.length === 0) {
|
|
586
|
+
// If layout is undefined or empty, generate a flat layout from properties
|
|
587
|
+
var flatPropKeys = Object.keys(unified.properties);
|
|
588
|
+
components = flatPropKeys.map(function (key) {
|
|
589
|
+
return mergeUnifiedField(unified.properties[key]);
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
else {
|
|
593
|
+
components = processLayoutNodes(unified.layout);
|
|
594
|
+
}
|
|
595
|
+
return __assign({ version: unified.version, components: components }, (unified.settings ? { settings: unified.settings } : {}));
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* @private
|
|
599
|
+
*/
|
|
600
|
+
function mergeUnifiedField(unifiedField) {
|
|
601
|
+
var hasOptions = unifiedField.options !== undefined && unifiedField.options.length > 0;
|
|
602
|
+
var component = {
|
|
603
|
+
id: unifiedField.id || generateId(unifiedField.widget || inferWidgetFromType(unifiedField.type, unifiedField.format, hasOptions)),
|
|
604
|
+
name: unifiedField.name,
|
|
605
|
+
type: (unifiedField.widget || inferWidgetFromType(unifiedField.type, unifiedField.format, hasOptions)),
|
|
606
|
+
label: unifiedField.label || unifiedField.id,
|
|
607
|
+
};
|
|
608
|
+
// Copy all data and UI properties from unified field
|
|
609
|
+
var dataSchemaProps = new Set([
|
|
610
|
+
'id', 'type', 'name', 'label', 'format', 'widget', 'defaultValue', 'required', 'options', 'expressionValue',
|
|
611
|
+
'minLength', 'maxLength', 'minimum', 'maximum', 'minValue', 'maxValue',
|
|
612
|
+
'minDate', 'maxDate', 'minTime', 'maxTime', 'regex', 'pattern', 'content', 'buttonType', 'textboxType'
|
|
613
|
+
]);
|
|
614
|
+
Object.keys(unifiedField).forEach(function (key) {
|
|
615
|
+
if (!dataSchemaProps.has(key) && key !== 'id' && key !== 'name' && key !== 'type' && key !== 'label') {
|
|
616
|
+
component[key] = unifiedField[key];
|
|
617
|
+
}
|
|
618
|
+
});
|
|
619
|
+
// Copy specific data properties
|
|
620
|
+
if (unifiedField.defaultValue !== undefined)
|
|
621
|
+
component.defaultValue = unifiedField.defaultValue;
|
|
622
|
+
if (unifiedField.expressionValue !== undefined)
|
|
623
|
+
component.expressionValue = unifiedField.expressionValue;
|
|
624
|
+
if (unifiedField.required !== undefined)
|
|
625
|
+
component.required = unifiedField.required;
|
|
626
|
+
if (unifiedField.content !== undefined)
|
|
627
|
+
component.content = unifiedField.content;
|
|
628
|
+
if (unifiedField.buttonType !== undefined)
|
|
629
|
+
component.buttonType = unifiedField.buttonType;
|
|
630
|
+
if (unifiedField.textboxType !== undefined)
|
|
631
|
+
component.textboxType = unifiedField.textboxType;
|
|
632
|
+
if (unifiedField.options !== undefined)
|
|
633
|
+
component.options = unifiedField.options;
|
|
634
|
+
if (unifiedField.minLength !== undefined)
|
|
635
|
+
component.minLength = unifiedField.minLength;
|
|
636
|
+
if (unifiedField.maxLength !== undefined)
|
|
637
|
+
component.maxLength = unifiedField.maxLength;
|
|
638
|
+
if (unifiedField.minValue !== undefined)
|
|
639
|
+
component.minValue = unifiedField.minValue;
|
|
640
|
+
if (unifiedField.maxValue !== undefined)
|
|
641
|
+
component.maxValue = unifiedField.maxValue;
|
|
642
|
+
if (unifiedField.minDate !== undefined)
|
|
643
|
+
component.minDate = unifiedField.minDate;
|
|
644
|
+
if (unifiedField.maxDate !== undefined)
|
|
645
|
+
component.maxDate = unifiedField.maxDate;
|
|
646
|
+
if (unifiedField.minTime !== undefined)
|
|
647
|
+
component.minTime = unifiedField.minTime;
|
|
648
|
+
if (unifiedField.maxTime !== undefined)
|
|
649
|
+
component.maxTime = unifiedField.maxTime;
|
|
650
|
+
if (unifiedField.pattern !== undefined)
|
|
651
|
+
component.regex = unifiedField.pattern;
|
|
652
|
+
return component;
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* @private
|
|
656
|
+
*/
|
|
657
|
+
export function inferWidgetFromType(type, format, hasOptions) {
|
|
658
|
+
if (type === 'string') {
|
|
659
|
+
if (format === 'date')
|
|
660
|
+
return 'date';
|
|
661
|
+
if (format === 'date-time')
|
|
662
|
+
return 'dateTime';
|
|
663
|
+
if (format === 'time')
|
|
664
|
+
return 'time';
|
|
665
|
+
if (format === 'email')
|
|
666
|
+
return 'email';
|
|
667
|
+
if (format === 'url')
|
|
668
|
+
return 'url';
|
|
669
|
+
// If options are present, default to dropdown for string types
|
|
670
|
+
if (hasOptions)
|
|
671
|
+
return 'dropdown';
|
|
672
|
+
return 'textbox';
|
|
673
|
+
}
|
|
674
|
+
if (type === 'number')
|
|
675
|
+
return 'number';
|
|
676
|
+
if (type === 'boolean')
|
|
677
|
+
return 'checkbox';
|
|
678
|
+
if (type === 'array') {
|
|
679
|
+
// For array type with options, use multiselect
|
|
680
|
+
return 'multiselect';
|
|
681
|
+
}
|
|
682
|
+
if (type === 'button')
|
|
683
|
+
return 'button';
|
|
684
|
+
if (type === 'message')
|
|
685
|
+
return 'message';
|
|
686
|
+
if (type === 'sign')
|
|
687
|
+
return 'signature';
|
|
688
|
+
if (type === 'image')
|
|
689
|
+
return 'imageEditor';
|
|
690
|
+
if (type === 'file')
|
|
691
|
+
return 'fileUpload';
|
|
692
|
+
if (type === 'color')
|
|
693
|
+
return 'colorPicker';
|
|
694
|
+
if (type === 'grid')
|
|
695
|
+
return 'dataGrid';
|
|
696
|
+
if (type === 'any')
|
|
697
|
+
return 'textbox';
|
|
698
|
+
return 'textbox';
|
|
699
|
+
}
|
|
700
|
+
/**
|
|
701
|
+
* @private
|
|
702
|
+
*/
|
|
703
|
+
function levenshteinDistance(str1, str2) {
|
|
704
|
+
var len1 = str1.length;
|
|
705
|
+
var len2 = str2.length;
|
|
706
|
+
var matrix = [];
|
|
707
|
+
// Initialize matrix
|
|
708
|
+
for (var i = 0; i <= len1; i++) {
|
|
709
|
+
matrix[i] = [i];
|
|
710
|
+
}
|
|
711
|
+
for (var j = 0; j <= len2; j++) {
|
|
712
|
+
matrix[0][j] = j;
|
|
713
|
+
}
|
|
714
|
+
// Fill matrix
|
|
715
|
+
for (var i = 1; i <= len1; i++) {
|
|
716
|
+
for (var j = 1; j <= len2; j++) {
|
|
717
|
+
var cost = str1[i - 1] === str2[j - 1] ? 0 : 1;
|
|
718
|
+
matrix[i][j] = Math.min(matrix[i - 1][j] + 1, // deletion
|
|
719
|
+
matrix[i][j - 1] + 1, // insertion
|
|
720
|
+
matrix[i - 1][j - 1] + cost // substitution
|
|
721
|
+
);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
return matrix[len1][len2];
|
|
725
|
+
}
|
|
726
|
+
/**
|
|
727
|
+
* @private
|
|
728
|
+
*/
|
|
729
|
+
function calculateSimilarity(str1, str2) {
|
|
730
|
+
var distance = levenshteinDistance(str1.toLowerCase(), str2.toLowerCase());
|
|
731
|
+
var maxLen = Math.max(str1.length, str2.length);
|
|
732
|
+
if (maxLen === 0)
|
|
733
|
+
return 100;
|
|
734
|
+
return Math.round(((maxLen - distance) / maxLen) * 100);
|
|
735
|
+
}
|
|
736
|
+
/**
|
|
737
|
+
* @private
|
|
738
|
+
*/
|
|
739
|
+
export function matchFieldWithIDPreservation(newDataField, existingUISchema) {
|
|
740
|
+
// 1. Try exact ID match first
|
|
741
|
+
if (existingUISchema.properties[newDataField.id]) {
|
|
742
|
+
return {
|
|
743
|
+
existingId: newDataField.id,
|
|
744
|
+
newDataId: newDataField.id,
|
|
745
|
+
confidence: 'exact',
|
|
746
|
+
method: 'id',
|
|
747
|
+
similarity: 100
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
// 2. Try fuzzy match by ID (check if existing ID starts with new ID or vice versa)
|
|
751
|
+
var bestIdMatch = null;
|
|
752
|
+
var idMatchKeys = Object.keys(existingUISchema.properties);
|
|
753
|
+
for (var _i = 0, idMatchKeys_1 = idMatchKeys; _i < idMatchKeys_1.length; _i++) {
|
|
754
|
+
var existingId = idMatchKeys_1[_i];
|
|
755
|
+
var existingUIField = existingUISchema.properties[existingId];
|
|
756
|
+
// Skip if this is a layout container
|
|
757
|
+
if (existingUIField.widget === 'table' || existingUIField.widget === 'panel' || existingUIField.widget === 'tabs') {
|
|
758
|
+
continue;
|
|
759
|
+
}
|
|
760
|
+
// Check if one ID is a prefix of the other (common case: radio_123 vs radio_123_456)
|
|
761
|
+
var newId = newDataField.id || '';
|
|
762
|
+
var isPrefix = existingId.indexOf(newId + '_') === 0 || newId.indexOf(existingId + '_') === 0;
|
|
763
|
+
if (isPrefix) {
|
|
764
|
+
var similarity = Math.round((Math.min(newId.length, existingId.length) / Math.max(newId.length, existingId.length)) * 100);
|
|
765
|
+
if (!bestIdMatch || (bestIdMatch.similarity && similarity > bestIdMatch.similarity)) {
|
|
766
|
+
bestIdMatch = {
|
|
767
|
+
existingId: existingId,
|
|
768
|
+
newDataId: newDataField.id,
|
|
769
|
+
confidence: similarity >= 90 ? 'high' : similarity >= 70 ? 'medium' : 'low',
|
|
770
|
+
method: 'id',
|
|
771
|
+
similarity: similarity
|
|
772
|
+
};
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
else {
|
|
776
|
+
// Try Levenshtein distance for ID similarity
|
|
777
|
+
var idSimilarity = calculateSimilarity(existingId, newId);
|
|
778
|
+
// Only consider as potential match if very similar (≥85%)
|
|
779
|
+
if (idSimilarity >= 85) {
|
|
780
|
+
if (!bestIdMatch || (bestIdMatch.similarity && idSimilarity > bestIdMatch.similarity)) {
|
|
781
|
+
bestIdMatch = {
|
|
782
|
+
existingId: existingId,
|
|
783
|
+
newDataId: newDataField.id,
|
|
784
|
+
confidence: idSimilarity >= 90 ? 'high' : 'medium',
|
|
785
|
+
method: 'id',
|
|
786
|
+
similarity: idSimilarity
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
// If we found a good ID match, use it
|
|
793
|
+
if (bestIdMatch && bestIdMatch.similarity && bestIdMatch.similarity >= 70) {
|
|
794
|
+
//console.log(`ID fuzzy match: "${newDataField.id}" → "${bestIdMatch.existingId}" (${bestIdMatch.similarity}% similar)`);
|
|
795
|
+
return bestIdMatch;
|
|
796
|
+
}
|
|
797
|
+
// 3. Try fuzzy match by label
|
|
798
|
+
var bestLabelMatch = null;
|
|
799
|
+
var labelMatchKeys = Object.keys(existingUISchema.properties);
|
|
800
|
+
for (var _a = 0, labelMatchKeys_1 = labelMatchKeys; _a < labelMatchKeys_1.length; _a++) {
|
|
801
|
+
var existingId = labelMatchKeys_1[_a];
|
|
802
|
+
var existingUIField = existingUISchema.properties[existingId];
|
|
803
|
+
// Skip if this is a layout container
|
|
804
|
+
if (existingUIField.widget === 'table' || existingUIField.widget === 'panel' || existingUIField.widget === 'tabs') {
|
|
805
|
+
continue;
|
|
806
|
+
}
|
|
807
|
+
// Get label from UI field or try to extract from ID
|
|
808
|
+
var existingLabel = existingUIField.label || existingId;
|
|
809
|
+
var newLabel = newDataField.label || newDataField.id;
|
|
810
|
+
if (!existingLabel || !newLabel)
|
|
811
|
+
continue;
|
|
812
|
+
var similarity = calculateSimilarity(existingLabel, newLabel);
|
|
813
|
+
// Update best match if this is better
|
|
814
|
+
if (!bestLabelMatch || (bestLabelMatch.similarity && similarity > bestLabelMatch.similarity)) {
|
|
815
|
+
bestLabelMatch = {
|
|
816
|
+
existingId: existingId,
|
|
817
|
+
newDataId: newDataField.id,
|
|
818
|
+
confidence: similarity >= 90 ? 'high' : similarity >= 70 ? 'medium' : 'low',
|
|
819
|
+
method: similarity === 100 ? 'label-exact' : 'label-fuzzy',
|
|
820
|
+
similarity: similarity
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
// 4. Return best label match if similarity is good enough (≥70%)
|
|
825
|
+
if (bestLabelMatch && bestLabelMatch.similarity && bestLabelMatch.similarity >= 70) {
|
|
826
|
+
//console.log(`Label fuzzy match: "${newDataField.label}" → "${bestLabelMatch.existingId}" (${bestLabelMatch.similarity}% similar)`);
|
|
827
|
+
return bestLabelMatch;
|
|
828
|
+
}
|
|
829
|
+
// 5. No match found - use new ID
|
|
830
|
+
return {
|
|
831
|
+
existingId: newDataField.id,
|
|
832
|
+
newDataId: newDataField.id,
|
|
833
|
+
confidence: 'exact',
|
|
834
|
+
method: 'new',
|
|
835
|
+
similarity: 100
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
/**
|
|
839
|
+
* @private
|
|
840
|
+
*/
|
|
841
|
+
export function mergeWithIDRemapping(newDataSchema, existingUISchema) {
|
|
842
|
+
var idMap = new Map(); // newId → canonicalId
|
|
843
|
+
var matchResults = [];
|
|
844
|
+
// Build ID mapping table
|
|
845
|
+
var newDataPropKeys = Object.keys(newDataSchema.properties);
|
|
846
|
+
for (var _i = 0, newDataPropKeys_1 = newDataPropKeys; _i < newDataPropKeys_1.length; _i++) {
|
|
847
|
+
var newId = newDataPropKeys_1[_i];
|
|
848
|
+
var dataField = newDataSchema.properties[newId];
|
|
849
|
+
var match = matchFieldWithIDPreservation(dataField, existingUISchema);
|
|
850
|
+
matchResults.push(match);
|
|
851
|
+
idMap.set(newId, match.existingId || generateId(labelToFieldKey(dataField.label || '')));
|
|
852
|
+
// Log remapping if IDs differ
|
|
853
|
+
if (match.newDataId !== match.existingId) {
|
|
854
|
+
//console.log(`ID remapped: ${match.newDataId} → ${match.existingId} (${match.method}, ${match.similarity}% similar)`);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
// Remap all data field IDs to canonical IDs
|
|
858
|
+
var remappedDataFields = {};
|
|
859
|
+
var newDataPropKeys2 = Object.keys(newDataSchema.properties);
|
|
860
|
+
for (var _a = 0, newDataPropKeys2_1 = newDataPropKeys2; _a < newDataPropKeys2_1.length; _a++) {
|
|
861
|
+
var newId = newDataPropKeys2_1[_a];
|
|
862
|
+
var dataField = newDataSchema.properties[newId];
|
|
863
|
+
var canonicalId = idMap.get(newId) || newId;
|
|
864
|
+
remappedDataFields[canonicalId] = __assign({}, dataField, { id: canonicalId // Use canonical ID
|
|
865
|
+
});
|
|
866
|
+
}
|
|
867
|
+
// Get existing field IDs from the layout
|
|
868
|
+
var existingFieldIds = extractFieldIdsFromLayout(existingUISchema.layout);
|
|
869
|
+
var newFieldIds = Object.keys(remappedDataFields).filter(function (id) { return !existingFieldIds.has(id); });
|
|
870
|
+
//console.log(`📋 Existing fields in layout: ${existingFieldIds.size}, New fields: ${newFieldIds.length}`);
|
|
871
|
+
// ✅ CHECK IF SUBMIT BUTTON EXISTS: Prevent duplicate submit buttons
|
|
872
|
+
var existingUIValues = Object.keys(existingUISchema.properties).map(function (k) { return existingUISchema.properties[k]; });
|
|
873
|
+
var hasExistingSubmitButton = existingUIValues.some(function (uiField) { return uiField.widget === 'button'; });
|
|
874
|
+
// Filter out new submit buttons if one already exists
|
|
875
|
+
var filteredNewFieldIds = newFieldIds.filter(function (id) {
|
|
876
|
+
var dataField = remappedDataFields[id];
|
|
877
|
+
if ((dataField.type === 'button' && dataField.buttonType === 'submit') && hasExistingSubmitButton) {
|
|
878
|
+
//console.log(`⚠️ Skipping new button "${dataField.label}" - submit button already exists in layout`);
|
|
879
|
+
return false;
|
|
880
|
+
}
|
|
881
|
+
return true;
|
|
882
|
+
});
|
|
883
|
+
//console.log(`📋 New fields after filtering: ${filteredNewFieldIds.length}`);
|
|
884
|
+
// Update layout references to use canonical IDs
|
|
885
|
+
var remappedLayout = remapLayoutIds(existingUISchema.layout, idMap);
|
|
886
|
+
// Create UI properties for new fields that don't have them yet
|
|
887
|
+
// ✅ Ensure all existing UI properties have _configured flag (add if missing from imported JSON)
|
|
888
|
+
var enhancedUIProperties = {};
|
|
889
|
+
var existingUIKeys = Object.keys(existingUISchema.properties);
|
|
890
|
+
for (var _b = 0, existingUIKeys_1 = existingUIKeys; _b < existingUIKeys_1.length; _b++) {
|
|
891
|
+
var key = existingUIKeys_1[_b];
|
|
892
|
+
var uiField = existingUISchema.properties[key];
|
|
893
|
+
enhancedUIProperties[key] = __assign({}, uiField, { _configured: uiField._configured !== undefined ? uiField._configured : true });
|
|
894
|
+
}
|
|
895
|
+
if (filteredNewFieldIds.length > 0) {
|
|
896
|
+
//console.log(`➕ Adding ${filteredNewFieldIds.length} new fields to layout`);
|
|
897
|
+
// Check if submit button exists and is at the last position (not moved by user)
|
|
898
|
+
var lastNode = remappedLayout[remappedLayout.length - 1];
|
|
899
|
+
var isSubmitAtEnd = lastNode && lastNode.type === 'field' &&
|
|
900
|
+
enhancedUIProperties[lastNode.propertyId] && enhancedUIProperties[lastNode.propertyId].widget === 'button';
|
|
901
|
+
var newFieldNodes_1 = [];
|
|
902
|
+
// Generate default UI properties for new fields
|
|
903
|
+
filteredNewFieldIds.forEach(function (id) {
|
|
904
|
+
var dataField = remappedDataFields[id];
|
|
905
|
+
var hasOptions = dataField.options !== undefined && dataField.options.length > 0;
|
|
906
|
+
var widget = inferWidgetFromType(dataField.type, dataField.format, hasOptions);
|
|
907
|
+
// ✅ Add default button styling
|
|
908
|
+
var uiProperties = {
|
|
909
|
+
widget: widget,
|
|
910
|
+
label: dataField.label,
|
|
911
|
+
_configured: true
|
|
912
|
+
};
|
|
913
|
+
// Add primary styling for buttons
|
|
914
|
+
if (widget === 'button') {
|
|
915
|
+
uiProperties.isPrimary = true;
|
|
916
|
+
uiProperties.name = 'defaultFormSubmit';
|
|
917
|
+
uiProperties.cssClass = 'e-primary';
|
|
918
|
+
}
|
|
919
|
+
enhancedUIProperties[id] = uiProperties;
|
|
920
|
+
newFieldNodes_1.push({
|
|
921
|
+
type: 'field',
|
|
922
|
+
propertyId: id
|
|
923
|
+
});
|
|
924
|
+
});
|
|
925
|
+
// Apply same logic as App.tsx: insert before submit if it's at the end
|
|
926
|
+
if (isSubmitAtEnd) {
|
|
927
|
+
// Insert new fields BEFORE the submit button
|
|
928
|
+
remappedLayout = remappedLayout.slice(0, -1).concat(newFieldNodes_1, [remappedLayout[remappedLayout.length - 1]]);
|
|
929
|
+
//console.log(`📐 Layout rebuilt: new fields inserted before submit button at end`);
|
|
930
|
+
}
|
|
931
|
+
else {
|
|
932
|
+
// Submit moved or doesn't exist - add new fields at end
|
|
933
|
+
remappedLayout = remappedLayout.concat(newFieldNodes_1);
|
|
934
|
+
//console.log(`📐 Layout rebuilt: new fields added at end`);
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
// Merge to FormSchema using unified schema
|
|
938
|
+
var unifiedProperties = {};
|
|
939
|
+
Object.keys(remappedDataFields).forEach(function (fieldKey) {
|
|
940
|
+
var dataField = remappedDataFields[fieldKey];
|
|
941
|
+
var uiField = enhancedUIProperties[fieldKey];
|
|
942
|
+
unifiedProperties[fieldKey] = __assign({}, dataField, uiField, { widget: (uiField && uiField.widget) || inferWidgetFromType(dataField.type, dataField.format, dataField.options !== undefined) });
|
|
943
|
+
});
|
|
944
|
+
var mergedSchema = mergeSchema(__assign({ version: newDataSchema.version, properties: unifiedProperties, layout: remappedLayout }, (newDataSchema.settings ? { settings: newDataSchema.settings } : {})));
|
|
945
|
+
return mergedSchema;
|
|
946
|
+
}
|
|
947
|
+
/**
|
|
948
|
+
* @private
|
|
949
|
+
*/
|
|
950
|
+
function remapLayoutIds(layout, idMap) {
|
|
951
|
+
return layout.map(function (node) {
|
|
952
|
+
if (node.type === 'field') {
|
|
953
|
+
var fieldNode = node;
|
|
954
|
+
var canonicalId = idMap.get(fieldNode.propertyId) || fieldNode.propertyId;
|
|
955
|
+
return __assign({}, fieldNode, { propertyId: canonicalId });
|
|
956
|
+
}
|
|
957
|
+
else if (node.type === 'table') {
|
|
958
|
+
var tableNode = node;
|
|
959
|
+
return __assign({}, tableNode, { cells: tableNode.cells.map(function (row) {
|
|
960
|
+
return row.map(function (cell) { return (__assign({}, cell, { children: remapLayoutIds(cell.children, idMap) })); });
|
|
961
|
+
}) });
|
|
962
|
+
}
|
|
963
|
+
else if (node.type === 'panel') {
|
|
964
|
+
var panelNode = node;
|
|
965
|
+
return __assign({}, panelNode, { children: remapLayoutIds(panelNode.children, idMap) });
|
|
966
|
+
}
|
|
967
|
+
else if (node.type === 'tabs') {
|
|
968
|
+
var tabsNode = node;
|
|
969
|
+
return __assign({}, tabsNode, { tabs: tabsNode.tabs.map(function (tab) { return (__assign({}, tab, { children: remapLayoutIds(tab.children, idMap) })); }) });
|
|
970
|
+
}
|
|
971
|
+
else if (node.type === 'card') {
|
|
972
|
+
var cardNode = node;
|
|
973
|
+
return __assign({}, cardNode, { children: remapLayoutIds(cardNode.children, idMap) });
|
|
974
|
+
}
|
|
975
|
+
return node;
|
|
976
|
+
});
|
|
977
|
+
}
|
|
978
|
+
/**
|
|
979
|
+
* @private
|
|
980
|
+
*/
|
|
981
|
+
export function extractFieldIdsFromLayout(layout) {
|
|
982
|
+
var fieldIds = new Set();
|
|
983
|
+
var stack = layout.slice();
|
|
984
|
+
while (stack.length > 0) {
|
|
985
|
+
var node = stack.shift();
|
|
986
|
+
if (node.type === 'field') {
|
|
987
|
+
fieldIds.add(node.propertyId);
|
|
988
|
+
}
|
|
989
|
+
else if (node.type === 'table') {
|
|
990
|
+
node.cells.forEach(function (row) {
|
|
991
|
+
return row.forEach(function (cell) { return stack.push.apply(stack, cell.children); });
|
|
992
|
+
});
|
|
993
|
+
}
|
|
994
|
+
else if (node.type === 'panel') {
|
|
995
|
+
stack.push.apply(stack, node.children);
|
|
996
|
+
}
|
|
997
|
+
else if (node.type === 'tabs') {
|
|
998
|
+
node.tabs.forEach(function (tab) { return stack.push.apply(stack, tab.children); });
|
|
999
|
+
}
|
|
1000
|
+
else if (node.type === 'card') {
|
|
1001
|
+
stack.push.apply(stack, node.children);
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
return fieldIds;
|
|
1005
|
+
}
|
|
1006
|
+
/**
|
|
1007
|
+
* @private
|
|
1008
|
+
*/
|
|
1009
|
+
export function convertComponentsToDataSchema(components) {
|
|
1010
|
+
var properties = {};
|
|
1011
|
+
var seenComponentIds = new Set(); // Track component IDs to prevent duplicates
|
|
1012
|
+
// Flatten all components (including nested ones)
|
|
1013
|
+
var stack = components.slice();
|
|
1014
|
+
while (stack.length > 0) {
|
|
1015
|
+
var comp = stack.shift();
|
|
1016
|
+
// Infer semantic type from component
|
|
1017
|
+
var semanticType = UI_TYPE_TO_SEMANTIC_TYPE[comp.type];
|
|
1018
|
+
// Skip presentation-only widgets (button, message, etc.) - they have null semantic type
|
|
1019
|
+
if (semanticType === null) {
|
|
1020
|
+
// Still process nested children for layout containers
|
|
1021
|
+
if (comp.children && comp.children.length) {
|
|
1022
|
+
stack.unshift.apply(stack, comp.children);
|
|
1023
|
+
}
|
|
1024
|
+
if (comp.tabItems && comp.tabItems.length) {
|
|
1025
|
+
for (var _i = 0, _a = comp.tabItems; _i < _a.length; _i++) {
|
|
1026
|
+
var tab = _a[_i];
|
|
1027
|
+
if (tab.content && tab.content.length) {
|
|
1028
|
+
stack.unshift.apply(stack, tab.content);
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
if (comp.tableCells && comp.tableCells.length) {
|
|
1033
|
+
for (var _b = 0, _c = comp.tableCells; _b < _c.length; _b++) {
|
|
1034
|
+
var row = _c[_b];
|
|
1035
|
+
for (var _d = 0, row_1 = row; _d < row_1.length; _d++) {
|
|
1036
|
+
var cell = row_1[_d];
|
|
1037
|
+
if (cell.length) {
|
|
1038
|
+
stack.unshift.apply(stack, cell);
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
continue; // Skip adding to data schema
|
|
1044
|
+
}
|
|
1045
|
+
// ✅ DEDUPLICATION: Skip if we've already processed this component ID
|
|
1046
|
+
if (seenComponentIds.has(comp.id)) {
|
|
1047
|
+
//console.log(`⚠️ Skipping duplicate component: ${comp.id} (${comp.label})`);
|
|
1048
|
+
// Still process nested children
|
|
1049
|
+
if (comp.children && comp.children.length) {
|
|
1050
|
+
stack.unshift.apply(stack, comp.children);
|
|
1051
|
+
}
|
|
1052
|
+
if (comp.tabItems && comp.tabItems.length) {
|
|
1053
|
+
for (var _e = 0, _f = comp.tabItems; _e < _f.length; _e++) {
|
|
1054
|
+
var tab = _f[_e];
|
|
1055
|
+
if (tab.content && tab.content.length) {
|
|
1056
|
+
stack.unshift.apply(stack, tab.content);
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
if (comp.tableCells && comp.tableCells.length) {
|
|
1061
|
+
for (var _g = 0, _h = comp.tableCells; _g < _h.length; _g++) {
|
|
1062
|
+
var row = _h[_g];
|
|
1063
|
+
for (var _j = 0, row_2 = row; _j < row_2.length; _j++) {
|
|
1064
|
+
var cell = row_2[_j];
|
|
1065
|
+
if (cell.length) {
|
|
1066
|
+
stack.unshift.apply(stack, cell);
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
continue;
|
|
1072
|
+
}
|
|
1073
|
+
seenComponentIds.add(comp.id);
|
|
1074
|
+
// Generate fieldKey from label or ID
|
|
1075
|
+
var baseFieldKey = labelToFieldKey(comp.label || comp.id, comp.id);
|
|
1076
|
+
var fieldKey = makeUniqueFieldKey(baseFieldKey, new Set(Object.keys(properties)), comp.id);
|
|
1077
|
+
var dataField = extractDataField(comp, semanticType || { type: 'any' });
|
|
1078
|
+
properties[fieldKey] = dataField;
|
|
1079
|
+
// Process nested children
|
|
1080
|
+
if (comp.children && comp.children.length) {
|
|
1081
|
+
stack.unshift.apply(stack, comp.children);
|
|
1082
|
+
}
|
|
1083
|
+
// Process tab items
|
|
1084
|
+
if (comp.tabItems && comp.tabItems.length) {
|
|
1085
|
+
for (var _k = 0, _l = comp.tabItems; _k < _l.length; _k++) {
|
|
1086
|
+
var tab = _l[_k];
|
|
1087
|
+
if (tab.content && tab.content.length) {
|
|
1088
|
+
stack.unshift.apply(stack, tab.content);
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
// Process table cells
|
|
1093
|
+
if (comp.tableCells && comp.tableCells.length) {
|
|
1094
|
+
for (var _m = 0, _o = comp.tableCells; _m < _o.length; _m++) {
|
|
1095
|
+
var row = _o[_m];
|
|
1096
|
+
for (var _p = 0, row_3 = row; _p < row_3.length; _p++) {
|
|
1097
|
+
var cell = row_3[_p];
|
|
1098
|
+
// cell is FormNode[], not an object with .components
|
|
1099
|
+
if (cell.length) {
|
|
1100
|
+
stack.unshift.apply(stack, cell);
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
return {
|
|
1107
|
+
version: '1.0',
|
|
1108
|
+
properties: properties
|
|
1109
|
+
};
|
|
1110
|
+
}
|