@syncfusion/ej2-react-form-renderer 0.1.2-beta → 34.2.3
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 +88 -105
- package/diConfig.json +85 -0
- package/dist/ej2-react-form-renderer.min.js +10 -0
- package/dist/ej2-react-form-renderer.umd.min.js +11 -0
- package/dist/ej2-react-form-renderer.umd.min.js.map +1 -0
- package/dist/es6/ej2-react-form-renderer.es2015.js +37 -0
- package/dist/es6/ej2-react-form-renderer.es2015.js.map +1 -0
- package/form-renderer.d.ts +4 -0
- package/form-renderer.js +4 -0
- package/index.d.ts +2 -4
- package/index.js +2 -4
- package/license +6 -13
- package/package.json +19 -38
- package/src/form-renderer/formrenderer.component.d.ts +34 -0
- package/src/form-renderer/formrenderer.component.js +49 -0
- package/src/form-renderer/index.d.ts +1 -0
- package/src/form-renderer/index.js +1 -0
- package/src/global.js +1 -0
- package/src/index.d.ts +2 -6
- package/src/index.js +2 -6
- package/styles/bds-lite.css +1 -0
- package/styles/bds-lite.scss +1 -0
- package/styles/bds.css +1 -0
- package/styles/bds.scss +1 -0
- package/styles/bootstrap-dark-lite.css +1 -0
- package/styles/bootstrap-dark-lite.scss +1 -0
- package/styles/bootstrap-dark.css +1 -26
- package/styles/bootstrap-dark.scss +1 -0
- package/styles/bootstrap-lite.css +1 -0
- package/styles/bootstrap-lite.scss +1 -0
- package/styles/bootstrap.css +1 -26
- package/styles/bootstrap.scss +1 -0
- package/styles/bootstrap4-lite.css +1 -0
- package/styles/bootstrap4-lite.scss +1 -0
- package/styles/bootstrap4.css +1 -26
- package/styles/bootstrap4.scss +1 -0
- package/styles/bootstrap5-dark-lite.css +1 -0
- package/styles/bootstrap5-dark-lite.scss +1 -0
- package/styles/bootstrap5-dark.css +1 -26
- package/styles/bootstrap5-dark.scss +1 -0
- package/styles/bootstrap5-lite.css +1 -0
- package/styles/bootstrap5-lite.scss +1 -0
- package/styles/bootstrap5.3-lite.css +1 -0
- package/styles/bootstrap5.3-lite.scss +1 -0
- package/styles/bootstrap5.3.css +1 -26
- package/styles/bootstrap5.3.scss +1 -0
- package/styles/bootstrap5.css +1 -26
- package/styles/bootstrap5.scss +1 -0
- package/styles/fabric-dark-lite.css +1 -0
- package/styles/fabric-dark-lite.scss +1 -0
- package/styles/fabric-dark.css +1 -0
- package/styles/fabric-dark.scss +1 -0
- package/styles/fabric-lite.css +1 -0
- package/styles/fabric-lite.scss +1 -0
- package/styles/fabric.css +1 -0
- package/styles/fabric.scss +1 -0
- package/styles/fluent-dark-lite.css +1 -0
- package/styles/fluent-dark-lite.scss +1 -0
- package/styles/fluent-dark.css +1 -26
- package/styles/fluent-dark.scss +1 -0
- package/styles/fluent-lite.css +1 -0
- package/styles/fluent-lite.scss +1 -0
- package/styles/fluent.css +1 -26
- package/styles/fluent.scss +1 -0
- package/styles/fluent2-lite.css +1 -0
- package/styles/fluent2-lite.scss +1 -0
- package/styles/fluent2.css +1 -26
- package/styles/fluent2.scss +1 -0
- package/styles/form-renderer/bds.css +1 -0
- package/styles/form-renderer/bds.scss +1 -0
- package/styles/form-renderer/bootstrap-dark.css +1 -0
- package/styles/form-renderer/bootstrap-dark.scss +1 -0
- package/styles/form-renderer/bootstrap.css +1 -0
- package/styles/form-renderer/bootstrap.scss +1 -0
- package/styles/form-renderer/bootstrap4.css +1 -0
- package/styles/form-renderer/bootstrap4.scss +1 -0
- package/styles/form-renderer/bootstrap5-dark.css +1 -0
- package/styles/form-renderer/bootstrap5-dark.scss +1 -0
- package/styles/form-renderer/bootstrap5.3.css +1 -0
- package/styles/form-renderer/bootstrap5.3.scss +1 -0
- package/styles/form-renderer/bootstrap5.css +1 -0
- package/styles/form-renderer/bootstrap5.scss +1 -0
- package/styles/form-renderer/fabric-dark.css +1 -0
- package/styles/form-renderer/fabric-dark.scss +1 -0
- package/styles/form-renderer/fabric.css +1 -0
- package/styles/form-renderer/fabric.scss +1 -0
- package/styles/form-renderer/fluent-dark.css +1 -0
- package/styles/form-renderer/fluent-dark.scss +1 -0
- package/styles/form-renderer/fluent.css +1 -0
- package/styles/form-renderer/fluent.scss +1 -0
- package/styles/form-renderer/fluent2.css +1 -0
- package/styles/form-renderer/fluent2.scss +1 -0
- package/styles/form-renderer/highcontrast-light.css +1 -0
- package/styles/form-renderer/highcontrast-light.scss +1 -0
- package/styles/form-renderer/highcontrast.css +1 -0
- package/styles/form-renderer/highcontrast.scss +1 -0
- package/styles/form-renderer/material-dark.css +1 -0
- package/styles/form-renderer/material-dark.scss +1 -0
- package/styles/form-renderer/material.css +1 -0
- package/styles/form-renderer/material.scss +1 -0
- package/styles/form-renderer/material3-dark.css +1 -0
- package/styles/form-renderer/material3-dark.scss +1 -0
- package/styles/form-renderer/material3.css +1 -0
- package/styles/form-renderer/material3.scss +1 -0
- package/styles/form-renderer/tailwind-dark.css +1 -0
- package/styles/form-renderer/tailwind-dark.scss +1 -0
- package/styles/form-renderer/tailwind.css +1 -0
- package/styles/form-renderer/tailwind.scss +1 -0
- package/styles/form-renderer/tailwind3.css +1 -0
- package/styles/form-renderer/tailwind3.scss +1 -0
- package/styles/highcontrast-light-lite.css +1 -0
- package/styles/highcontrast-light-lite.scss +1 -0
- package/styles/highcontrast-light.css +1 -0
- package/styles/highcontrast-light.scss +1 -0
- package/styles/highcontrast-lite.css +1 -0
- package/styles/highcontrast-lite.scss +1 -0
- package/styles/highcontrast.css +1 -0
- package/styles/highcontrast.scss +1 -0
- package/styles/material-dark-lite.css +1 -0
- package/styles/material-dark-lite.scss +1 -0
- package/styles/material-dark.css +1 -26
- package/styles/material-dark.scss +1 -0
- package/styles/material-lite.css +1 -0
- package/styles/material-lite.scss +1 -0
- package/styles/material.css +1 -26
- package/styles/material.scss +1 -0
- package/styles/material3-dark-lite.css +1 -0
- package/styles/material3-dark-lite.scss +1 -0
- package/styles/material3-dark.css +1 -26
- package/styles/material3-dark.scss +1 -0
- package/styles/material3-lite.css +1 -0
- package/styles/material3-lite.scss +1 -0
- package/styles/material3.css +1 -26
- package/styles/material3.scss +1 -0
- package/styles/tailwind-dark-lite.css +1 -0
- package/styles/tailwind-dark-lite.scss +1 -0
- package/styles/tailwind-dark.css +1 -26
- package/styles/tailwind-dark.scss +1 -0
- package/styles/tailwind-lite.css +1 -0
- package/styles/tailwind-lite.scss +1 -0
- package/styles/tailwind.css +1 -26
- package/styles/tailwind.scss +1 -0
- package/styles/tailwind3-lite.css +1 -0
- package/styles/tailwind3-lite.scss +1 -0
- package/styles/tailwind3.css +1 -26
- package/styles/tailwind3.scss +1 -0
- package/src/CheckboxGroup.d.ts +0 -12
- package/src/CheckboxGroup.js +0 -49
- package/src/ConditionalRuleEngine.d.ts +0 -295
- package/src/ConditionalRuleEngine.js +0 -566
- package/src/CustomTabComponent.d.ts +0 -13
- package/src/CustomTabComponent.js +0 -35
- package/src/DataGridComponent.d.ts +0 -28
- package/src/DataGridComponent.js +0 -147
- package/src/FormRenderer.d.ts +0 -84
- package/src/FormRenderer.js +0 -707
- package/src/FormValidator.d.ts +0 -479
- package/src/FormValidator.js +0 -579
- package/src/MinMaxRuleEngine.d.ts +0 -95
- package/src/MinMaxRuleEngine.js +0 -361
- package/src/StaticHtmlRenderer.d.ts +0 -8
- package/src/StaticHtmlRenderer.js +0 -21
- package/src/expressions/DependencyTracker.d.ts +0 -30
- package/src/expressions/DependencyTracker.js +0 -128
- package/src/expressions/ExpressionEngine.d.ts +0 -81
- package/src/expressions/ExpressionEngine.js +0 -503
- package/src/expressions/ExpressionParser.d.ts +0 -39
- package/src/expressions/ExpressionParser.js +0 -270
- package/src/json-converter.d.ts +0 -171
- package/src/json-converter.js +0 -1209
- package/src/parser.d.ts +0 -99
- package/src/parser.js +0 -753
- package/src/renderFormField.d.ts +0 -21
- package/src/renderFormField.js +0 -226
- package/src/types/FormSchema.d.ts +0 -211
- package/src/types/FormSchema.js +0 -1
- package/src/utils.js +0 -408
- package/styles/bootstrap5.3-dark.css +0 -26
- package/styles/fluent2-dark.css +0 -26
- package/styles/form-renderer.base.css +0 -499
- package/styles/tailwind3-dark.css +0 -10
package/src/parser.js
DELETED
|
@@ -1,753 +0,0 @@
|
|
|
1
|
-
import { VALIDATION_REGEX } from './FormValidator';
|
|
2
|
-
/**
|
|
3
|
-
* Default parser configuration
|
|
4
|
-
*/
|
|
5
|
-
const DEFAULT_CONFIG = {
|
|
6
|
-
strict: false,
|
|
7
|
-
maxInputSize: 5 * 1024 * 1024,
|
|
8
|
-
defaultTextType: 'textbox',
|
|
9
|
-
inferTypeFromValue: true,
|
|
10
|
-
generateIds: true,
|
|
11
|
-
includeMetadata: true,
|
|
12
|
-
customTypeMap: {},
|
|
13
|
-
validateUnknownFields: false
|
|
14
|
-
};
|
|
15
|
-
/**
|
|
16
|
-
* ==================== UNIVERSAL JSON PARSER CLASS ====================
|
|
17
|
-
*/
|
|
18
|
-
export class UniversalJsonParser {
|
|
19
|
-
config;
|
|
20
|
-
constructor(customConfig) {
|
|
21
|
-
this.config = { ...DEFAULT_CONFIG, ...customConfig };
|
|
22
|
-
}
|
|
23
|
-
parse(input) {
|
|
24
|
-
const errors = [];
|
|
25
|
-
const warnings = [];
|
|
26
|
-
// Handle string input
|
|
27
|
-
if (typeof input === 'string') {
|
|
28
|
-
if (input.length > this.config.maxInputSize) {
|
|
29
|
-
return this.formatError(errors, warnings, `Input exceeds maximum size of ${this.config.maxInputSize} bytes`);
|
|
30
|
-
}
|
|
31
|
-
const jsonValidation = validateJSON(input);
|
|
32
|
-
if (!jsonValidation.valid) {
|
|
33
|
-
return this.formatError(errors, warnings, jsonValidation.errors?.[0] || 'Invalid JSON');
|
|
34
|
-
}
|
|
35
|
-
try {
|
|
36
|
-
input = JSON.parse(input);
|
|
37
|
-
}
|
|
38
|
-
catch (error) {
|
|
39
|
-
return this.formatError(errors, warnings, `Failed to parse JSON: ${error.message}`);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
// Detect format
|
|
43
|
-
const detection = this.detectFormat(input);
|
|
44
|
-
// Add warnings for ambiguous format detection
|
|
45
|
-
if (detection.ambiguousFormats && detection.ambiguousFormats.length > 0) {
|
|
46
|
-
warnings.push(`Ambiguous format detected.`);
|
|
47
|
-
warnings.push(`Using ${detection.format} format (${detection.format === 'json-schema' ? 'properties priority' : 'pages priority'})`);
|
|
48
|
-
}
|
|
49
|
-
// Parse based on format
|
|
50
|
-
let components = [];
|
|
51
|
-
switch (detection.format) {
|
|
52
|
-
case 'configured-schema':
|
|
53
|
-
// Already in our standard format, just use directly
|
|
54
|
-
components = input.properties || [];
|
|
55
|
-
break;
|
|
56
|
-
case 'json-schema':
|
|
57
|
-
const jsonSchemaResult = this.parseJsonSchema(input);
|
|
58
|
-
components = jsonSchemaResult.components;
|
|
59
|
-
if (jsonSchemaResult.warnings) {
|
|
60
|
-
warnings.push(...jsonSchemaResult.warnings);
|
|
61
|
-
}
|
|
62
|
-
break;
|
|
63
|
-
case 'surveyjs':
|
|
64
|
-
components = this.parseSurveyJS(input);
|
|
65
|
-
break;
|
|
66
|
-
case 'openapi':
|
|
67
|
-
components = this.parseOpenAPI(input);
|
|
68
|
-
break;
|
|
69
|
-
case 'data-inference':
|
|
70
|
-
components = this.parseDataInference(input);
|
|
71
|
-
// Only warn about inferred types if components were actually created
|
|
72
|
-
if (components.length > 0) {
|
|
73
|
-
warnings.push('Field types inferred from data; please verify');
|
|
74
|
-
}
|
|
75
|
-
break;
|
|
76
|
-
default:
|
|
77
|
-
errors.push('Could not determine input format');
|
|
78
|
-
}
|
|
79
|
-
if (components.length === 0) {
|
|
80
|
-
errors.push('No form fields could be extracted from input');
|
|
81
|
-
}
|
|
82
|
-
// Add default submit button if not already present
|
|
83
|
-
const hasSubmitButton = components.some((comp) => comp.type === 'button' && comp.buttonType === 'submit');
|
|
84
|
-
if (!hasSubmitButton && components.length > 0) {
|
|
85
|
-
components.push({
|
|
86
|
-
id: this.generateComponentId('button'),
|
|
87
|
-
type: 'button',
|
|
88
|
-
label: 'Submit',
|
|
89
|
-
style: 'primary',
|
|
90
|
-
buttonType: 'submit',
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
const schema = {
|
|
94
|
-
version: input.version || "0.1.0",
|
|
95
|
-
components
|
|
96
|
-
};
|
|
97
|
-
const schemaValidation = validateFormSchema(schema);
|
|
98
|
-
if (!schemaValidation.valid && schemaValidation.errors) {
|
|
99
|
-
errors.push(...schemaValidation.errors);
|
|
100
|
-
}
|
|
101
|
-
if (schemaValidation.warnings) {
|
|
102
|
-
warnings.push(...schemaValidation.warnings);
|
|
103
|
-
}
|
|
104
|
-
if (this.config.strict && warnings.length > 0) {
|
|
105
|
-
errors.push(...warnings);
|
|
106
|
-
warnings.length = 0;
|
|
107
|
-
}
|
|
108
|
-
const sanitized = sanitizeFormSchema(schema);
|
|
109
|
-
return {
|
|
110
|
-
success: errors.length === 0,
|
|
111
|
-
schema: sanitized,
|
|
112
|
-
errors,
|
|
113
|
-
warnings,
|
|
114
|
-
metadata: {
|
|
115
|
-
detectedFormat: detection.format,
|
|
116
|
-
confidence: detection.confidence,
|
|
117
|
-
sourceHints: detection.hints
|
|
118
|
-
}
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
formatError(errors, warnings, message) {
|
|
122
|
-
errors.push(message);
|
|
123
|
-
return {
|
|
124
|
-
success: false,
|
|
125
|
-
schema: null,
|
|
126
|
-
errors,
|
|
127
|
-
warnings,
|
|
128
|
-
metadata: {
|
|
129
|
-
detectedFormat: 'unknown',
|
|
130
|
-
confidence: 'low',
|
|
131
|
-
sourceHints: []
|
|
132
|
-
}
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
detectFormat(input) {
|
|
136
|
-
const hints = [];
|
|
137
|
-
const ambiguousFormats = [];
|
|
138
|
-
if (!input || typeof input !== 'object') {
|
|
139
|
-
return {
|
|
140
|
-
format: 'unknown',
|
|
141
|
-
confidence: 'low',
|
|
142
|
-
hints: ['Input is not a valid object']
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
// Check for JSON Schema indicators
|
|
146
|
-
if (input.$schema && typeof input.$schema === 'string' && input.$schema.includes('json-schema')) {
|
|
147
|
-
hints.push('$schema property detected');
|
|
148
|
-
return { format: 'json-schema', confidence: 'high', hints };
|
|
149
|
-
}
|
|
150
|
-
// Detect ambiguous formats (Test Case 8)
|
|
151
|
-
const hasProperties = input.properties && typeof input.properties === 'object';
|
|
152
|
-
const hasPages = input.pages && Array.isArray(input.pages);
|
|
153
|
-
const hasElements = input.elements && Array.isArray(input.elements);
|
|
154
|
-
if (hasProperties && hasPages) {
|
|
155
|
-
hints.push('Multiple format patterns detected');
|
|
156
|
-
hints.push('Selected based on priority rules');
|
|
157
|
-
ambiguousFormats.push('properties format', 'pages format');
|
|
158
|
-
// JSON Schema has priority
|
|
159
|
-
return {
|
|
160
|
-
format: 'json-schema',
|
|
161
|
-
confidence: 'medium',
|
|
162
|
-
hints,
|
|
163
|
-
ambiguousFormats
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
// Check for SurveyJS with pages
|
|
167
|
-
if (hasPages) {
|
|
168
|
-
hints.push('pages array detected');
|
|
169
|
-
if (input.pages[0]?.elements)
|
|
170
|
-
hints.push('elements structure matched');
|
|
171
|
-
return { format: 'surveyjs', confidence: 'high', hints };
|
|
172
|
-
}
|
|
173
|
-
// Check for SurveyJS with elements at root
|
|
174
|
-
if (hasElements && !hasProperties) {
|
|
175
|
-
hints.push('elements array at root level');
|
|
176
|
-
return { format: 'surveyjs', confidence: 'medium', hints };
|
|
177
|
-
}
|
|
178
|
-
// Check for JSON Schema with properties
|
|
179
|
-
if (hasProperties) {
|
|
180
|
-
hints.push('properties object detected');
|
|
181
|
-
if (input.type === 'object')
|
|
182
|
-
hints.push('type: object matched');
|
|
183
|
-
if (input.required && Array.isArray(input.required))
|
|
184
|
-
hints.push('required array found');
|
|
185
|
-
// Check for API schema indicators (example fields pattern)
|
|
186
|
-
if (this.hasExampleFields(input.properties)) {
|
|
187
|
-
hints.push('example fields detected (API schema pattern)');
|
|
188
|
-
return { format: 'openapi', confidence: 'high', hints };
|
|
189
|
-
}
|
|
190
|
-
return { format: 'json-schema', confidence: 'high', hints };
|
|
191
|
-
}
|
|
192
|
-
// Check for Data Inference
|
|
193
|
-
if (this.isDataInferenceCandidate(input)) {
|
|
194
|
-
hints.push('Simple object with primitive values');
|
|
195
|
-
hints.push('No schema indicators found');
|
|
196
|
-
return { format: 'data-inference', confidence: 'medium', hints };
|
|
197
|
-
}
|
|
198
|
-
return {
|
|
199
|
-
format: 'unknown',
|
|
200
|
-
confidence: 'low',
|
|
201
|
-
hints: ['Could not identify format']
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
hasExampleFields(properties) {
|
|
205
|
-
if (!properties || typeof properties !== 'object')
|
|
206
|
-
return false;
|
|
207
|
-
return Object.values(properties).some((prop) => 'example' in prop);
|
|
208
|
-
}
|
|
209
|
-
isDataInferenceCandidate(input) {
|
|
210
|
-
if (!input || typeof input !== 'object')
|
|
211
|
-
return false;
|
|
212
|
-
if (input.$schema || input.properties || input.pages || input.elements)
|
|
213
|
-
return false;
|
|
214
|
-
return Object.values(input).every((val) => {
|
|
215
|
-
if (val === null || val === undefined)
|
|
216
|
-
return true;
|
|
217
|
-
if (typeof val === 'string' || typeof val === 'number' || typeof val === 'boolean')
|
|
218
|
-
return true;
|
|
219
|
-
if (Array.isArray(val)) {
|
|
220
|
-
return val.every((item) => typeof item === 'string' || typeof item === 'number' || typeof item === 'boolean');
|
|
221
|
-
}
|
|
222
|
-
return false;
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
|
-
inferComponentType(fieldName, value) {
|
|
226
|
-
const valueType = this.inferTypeFromValue(fieldName, value);
|
|
227
|
-
if (valueType.type) {
|
|
228
|
-
return { ...valueType, confidence: 'high', method: 'value-type' };
|
|
229
|
-
}
|
|
230
|
-
const patternMatch = this.matchFieldNamePattern(fieldName);
|
|
231
|
-
if (patternMatch.type) {
|
|
232
|
-
return { ...patternMatch, confidence: 'high', method: 'name-pattern' };
|
|
233
|
-
}
|
|
234
|
-
return {
|
|
235
|
-
type: this.config.defaultTextType,
|
|
236
|
-
confidence: 'low',
|
|
237
|
-
method: 'fallback'
|
|
238
|
-
};
|
|
239
|
-
}
|
|
240
|
-
inferTypeFromValue(fieldName, value) {
|
|
241
|
-
if (!this.config.inferTypeFromValue)
|
|
242
|
-
return { type: 'textbox' };
|
|
243
|
-
if (value === null || value === undefined) {
|
|
244
|
-
// Even with null/undefined values, check field name patterns
|
|
245
|
-
return this.matchFieldNamePattern(fieldName);
|
|
246
|
-
}
|
|
247
|
-
if (typeof value === 'string') {
|
|
248
|
-
// Check value-based patterns first (highest confidence)
|
|
249
|
-
if (value.includes('@') && value.includes('.')) {
|
|
250
|
-
return { type: 'textbox', textboxType: 'email' };
|
|
251
|
-
}
|
|
252
|
-
if (/^(https?:\/\/)?(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}/.test(value)) {
|
|
253
|
-
return { type: 'textbox', textboxType: 'url' };
|
|
254
|
-
}
|
|
255
|
-
if (/^\d{4}-\d{2}-\d{2}/.test(value)) {
|
|
256
|
-
return { type: 'date' };
|
|
257
|
-
}
|
|
258
|
-
// For generic strings, check field name patterns
|
|
259
|
-
const fieldNameMatch = this.matchFieldNamePattern(fieldName);
|
|
260
|
-
if (fieldNameMatch.type !== 'textbox' || fieldNameMatch.textboxType) {
|
|
261
|
-
return fieldNameMatch;
|
|
262
|
-
}
|
|
263
|
-
return { type: this.config.defaultTextType };
|
|
264
|
-
}
|
|
265
|
-
if (typeof value === 'number') {
|
|
266
|
-
return { type: 'number' };
|
|
267
|
-
}
|
|
268
|
-
if (typeof value === 'boolean') {
|
|
269
|
-
return { type: 'checkbox' };
|
|
270
|
-
}
|
|
271
|
-
if (Array.isArray(value)) {
|
|
272
|
-
return { type: 'multiselect' };
|
|
273
|
-
}
|
|
274
|
-
return { type: 'textbox' };
|
|
275
|
-
}
|
|
276
|
-
matchFieldNamePattern(fieldName) {
|
|
277
|
-
if (this.config.customTypeMap) {
|
|
278
|
-
const customType = this.config.customTypeMap[fieldName.toLowerCase()];
|
|
279
|
-
if (customType)
|
|
280
|
-
return { type: customType };
|
|
281
|
-
}
|
|
282
|
-
const lower = fieldName.toLowerCase();
|
|
283
|
-
const normalized = lower.replace(/[_\-\s]+/g, ' ');
|
|
284
|
-
if (/email|mail|e-?mail/.test(normalized))
|
|
285
|
-
return { type: 'textbox', textboxType: 'email' };
|
|
286
|
-
if (/date|birth|birthday|dob|created|updated/.test(normalized))
|
|
287
|
-
return { type: 'date' };
|
|
288
|
-
if (/phone|tel|mobile|cell|cellphone|msisdn/.test(normalized))
|
|
289
|
-
return { type: 'textbox', textboxType: 'number' };
|
|
290
|
-
if (/url|link|uri|website/.test(normalized))
|
|
291
|
-
return { type: 'textbox', textboxType: 'url' };
|
|
292
|
-
if (/password|pwd|pass|security|secret|key|token/.test(normalized))
|
|
293
|
-
return { type: 'textbox', textboxType: 'password' };
|
|
294
|
-
if (/\b(?:age|count|qty|quantity|amount|price|cost|salary|fee|rate|number|num)\b/.test(normalized))
|
|
295
|
-
return { type: 'number' };
|
|
296
|
-
if (/active|enabled|visible|deleted|archived|approved|confirmed/.test(normalized))
|
|
297
|
-
return { type: 'checkbox' };
|
|
298
|
-
if (/select|choice|option|type|status|category|kind/.test(normalized))
|
|
299
|
-
return { type: 'dropdown' };
|
|
300
|
-
if (/description|comment|note|bio|biography|about|message|content|summary|details/.test(normalized))
|
|
301
|
-
return { type: 'textarea' };
|
|
302
|
-
return { type: 'textbox' };
|
|
303
|
-
}
|
|
304
|
-
parseJsonSchema(schema) {
|
|
305
|
-
const components = [];
|
|
306
|
-
const warnings = [];
|
|
307
|
-
if (!schema.properties || typeof schema.properties !== 'object')
|
|
308
|
-
return { components, warnings };
|
|
309
|
-
const flattenedProperties = this.flattenNestedProperties(schema.properties, '', warnings);
|
|
310
|
-
Object.entries(flattenedProperties).forEach(([key, propDef]) => {
|
|
311
|
-
const typeInfo = this.mapJsonSchemaType(propDef);
|
|
312
|
-
components.push(this.createComponent(key, propDef, typeInfo, { required: schema.required?.includes(key) || false }));
|
|
313
|
-
});
|
|
314
|
-
return { components, warnings };
|
|
315
|
-
}
|
|
316
|
-
flattenNestedProperties(properties, prefix, warnings) {
|
|
317
|
-
const flattened = {};
|
|
318
|
-
let hasNested = false;
|
|
319
|
-
Object.entries(properties).forEach(([key, propDef]) => {
|
|
320
|
-
const fullKey = prefix ? `${prefix}_${key}` : key;
|
|
321
|
-
// Check if this is a nested object (Test Case 9)
|
|
322
|
-
if (propDef.type === 'object' && propDef.properties && typeof propDef.properties === 'object') {
|
|
323
|
-
hasNested = true;
|
|
324
|
-
const nestedFlattened = this.flattenNestedProperties(propDef.properties, fullKey, warnings);
|
|
325
|
-
Object.assign(flattened, nestedFlattened);
|
|
326
|
-
}
|
|
327
|
-
else {
|
|
328
|
-
flattened[fullKey] = propDef;
|
|
329
|
-
}
|
|
330
|
-
});
|
|
331
|
-
// Add warnings for nested objects
|
|
332
|
-
if (hasNested && !prefix) {
|
|
333
|
-
warnings.push('NESTED_OBJECTS_FLATTENED: Nested objects converted to flat structure');
|
|
334
|
-
Object.entries(properties).forEach(([key, propDef]) => {
|
|
335
|
-
if (propDef.type === 'object' && propDef.properties) {
|
|
336
|
-
this.collectFlattenedFieldNames(propDef.properties, key, warnings);
|
|
337
|
-
}
|
|
338
|
-
});
|
|
339
|
-
}
|
|
340
|
-
return flattened;
|
|
341
|
-
}
|
|
342
|
-
collectFlattenedFieldNames(properties, prefix, warnings) {
|
|
343
|
-
Object.entries(properties).forEach(([key, propDef]) => {
|
|
344
|
-
const fullKey = `${prefix}_${key}`;
|
|
345
|
-
warnings.push(`Field '${prefix}.${key}' flattened to '${fullKey}'`);
|
|
346
|
-
if (propDef.type === 'object' && propDef.properties) {
|
|
347
|
-
this.collectFlattenedFieldNames(propDef.properties, fullKey, warnings);
|
|
348
|
-
}
|
|
349
|
-
});
|
|
350
|
-
}
|
|
351
|
-
parseSurveyJS(schema) {
|
|
352
|
-
const components = [];
|
|
353
|
-
if (schema.pages && Array.isArray(schema.pages)) {
|
|
354
|
-
schema.pages.forEach((page) => {
|
|
355
|
-
if (page.elements && Array.isArray(page.elements)) {
|
|
356
|
-
page.elements.forEach((element) => {
|
|
357
|
-
components.push(this.createComponentFromSurveyJS(element));
|
|
358
|
-
});
|
|
359
|
-
}
|
|
360
|
-
});
|
|
361
|
-
}
|
|
362
|
-
if (!components.length && schema.elements && Array.isArray(schema.elements)) {
|
|
363
|
-
schema.elements.forEach((element) => {
|
|
364
|
-
components.push(this.createComponentFromSurveyJS(element));
|
|
365
|
-
});
|
|
366
|
-
}
|
|
367
|
-
return components;
|
|
368
|
-
}
|
|
369
|
-
createComponentFromSurveyJS(element) {
|
|
370
|
-
const type = this.mapSurveyJSType(element.type || 'text');
|
|
371
|
-
const validation = {};
|
|
372
|
-
// Only add required if explicitly true
|
|
373
|
-
if (element.isRequired === true) {
|
|
374
|
-
validation.required = true;
|
|
375
|
-
}
|
|
376
|
-
if (element.validators && Array.isArray(element.validators)) {
|
|
377
|
-
element.validators.forEach((validator) => {
|
|
378
|
-
if (validator.type === 'numeric') {
|
|
379
|
-
if (validator.minValue !== undefined)
|
|
380
|
-
validation.minValue = validator.minValue;
|
|
381
|
-
if (validator.maxValue !== undefined)
|
|
382
|
-
validation.maxValue = validator.maxValue;
|
|
383
|
-
}
|
|
384
|
-
if (validator.type === 'text') {
|
|
385
|
-
if (validator.minLength !== undefined)
|
|
386
|
-
validation.minLength = validator.minLength;
|
|
387
|
-
if (validator.maxLength !== undefined)
|
|
388
|
-
validation.maxLength = validator.maxLength;
|
|
389
|
-
}
|
|
390
|
-
});
|
|
391
|
-
}
|
|
392
|
-
const component = {
|
|
393
|
-
id: this.generateComponentId(type),
|
|
394
|
-
type: type,
|
|
395
|
-
label: element.title || element.label || this.humanizeFieldName(element.name),
|
|
396
|
-
defaultValue: element.defaultValue,
|
|
397
|
-
expressionValue: element.expressionValue,
|
|
398
|
-
...Object.keys(validation).length > 0 ? validation : undefined
|
|
399
|
-
};
|
|
400
|
-
if (type === 'dropdown' || type === 'multiselect' || type === 'radio' || type === 'splitButton') {
|
|
401
|
-
if (element.choices && Array.isArray(element.choices)) {
|
|
402
|
-
component.options = element.choices;
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
return component;
|
|
406
|
-
}
|
|
407
|
-
mapSurveyJSType(surveyType) {
|
|
408
|
-
const typeMap = {
|
|
409
|
-
text: 'textbox',
|
|
410
|
-
email: 'email',
|
|
411
|
-
number: 'number',
|
|
412
|
-
checkbox: 'checkbox',
|
|
413
|
-
radiogroup: 'radio',
|
|
414
|
-
dropdown: 'dropdown',
|
|
415
|
-
tagbox: 'multiselect',
|
|
416
|
-
multiselect: 'multiselect',
|
|
417
|
-
rating: 'number',
|
|
418
|
-
comment: 'textarea',
|
|
419
|
-
date: 'date'
|
|
420
|
-
};
|
|
421
|
-
return typeMap[surveyType] || 'textbox';
|
|
422
|
-
}
|
|
423
|
-
parseOpenAPI(schema) {
|
|
424
|
-
const components = [];
|
|
425
|
-
if (!schema.properties || typeof schema.properties !== 'object')
|
|
426
|
-
return components;
|
|
427
|
-
Object.entries(schema.properties).forEach(([key, propDef]) => {
|
|
428
|
-
const typeInfo = this.mapJsonSchemaType(propDef);
|
|
429
|
-
components.push(this.createComponent(key, propDef, typeInfo, { required: schema.required?.includes(key) || false }));
|
|
430
|
-
});
|
|
431
|
-
return components;
|
|
432
|
-
}
|
|
433
|
-
parseDataInference(data) {
|
|
434
|
-
const components = [];
|
|
435
|
-
Object.entries(data).forEach(([key, value]) => {
|
|
436
|
-
const typeInference = this.inferComponentType(key, value);
|
|
437
|
-
const component = {
|
|
438
|
-
id: this.generateComponentId(typeInference.type),
|
|
439
|
-
type: typeInference.type,
|
|
440
|
-
label: this.humanizeFieldName(key),
|
|
441
|
-
defaultValue: value,
|
|
442
|
-
expressionValue: undefined
|
|
443
|
-
};
|
|
444
|
-
if (typeInference.textboxType) {
|
|
445
|
-
component.textboxType = typeInference.textboxType;
|
|
446
|
-
// Add default regex patterns for email and url
|
|
447
|
-
if (typeInference.textboxType === 'email') {
|
|
448
|
-
component.regex = VALIDATION_REGEX['EMAIL'];
|
|
449
|
-
}
|
|
450
|
-
else if (typeInference.textboxType === 'url') {
|
|
451
|
-
component.regex = VALIDATION_REGEX['URL'];
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
if (typeInference.type === 'date' && component.defaultValue !== undefined && component.defaultValue !== null) {
|
|
455
|
-
const formatted = this.formatDateTimeValue(component.defaultValue);
|
|
456
|
-
if (formatted)
|
|
457
|
-
component.defaultValue = formatted;
|
|
458
|
-
}
|
|
459
|
-
if (Array.isArray(value) && value.length > 0) {
|
|
460
|
-
component.options = value;
|
|
461
|
-
}
|
|
462
|
-
components.push(component);
|
|
463
|
-
});
|
|
464
|
-
return components;
|
|
465
|
-
}
|
|
466
|
-
createComponent(fieldName, propDef, typeInfo, context) {
|
|
467
|
-
const validation = {};
|
|
468
|
-
// Only add required if explicitly true
|
|
469
|
-
const isRequired = propDef.required !== undefined ? propDef.required : context.required;
|
|
470
|
-
if (isRequired === true) {
|
|
471
|
-
validation.required = true;
|
|
472
|
-
}
|
|
473
|
-
if (propDef.minLength)
|
|
474
|
-
validation.minLength = propDef.minLength;
|
|
475
|
-
if (propDef.maxLength)
|
|
476
|
-
validation.maxLength = propDef.maxLength;
|
|
477
|
-
if (propDef.minDate)
|
|
478
|
-
validation.minDate = propDef.minDate;
|
|
479
|
-
if (propDef.maxDate)
|
|
480
|
-
validation.maxDate = propDef.maxDate;
|
|
481
|
-
if (propDef.minTime)
|
|
482
|
-
validation.minTime = propDef.minTime;
|
|
483
|
-
if (propDef.minValue)
|
|
484
|
-
validation.minValue = propDef.minValue;
|
|
485
|
-
if (propDef.maxValue)
|
|
486
|
-
validation.maxValue = propDef.maxValue;
|
|
487
|
-
if (propDef.pattern) {
|
|
488
|
-
validation.pattern = propDef.pattern;
|
|
489
|
-
}
|
|
490
|
-
const component = {
|
|
491
|
-
id: propDef.id || this.generateComponentId(typeInfo.type),
|
|
492
|
-
name: propDef.name,
|
|
493
|
-
type: typeInfo.type,
|
|
494
|
-
label: propDef.title || propDef.label || this.humanizeFieldName(fieldName),
|
|
495
|
-
defaultValue: propDef.default || propDef.defaultValue || undefined,
|
|
496
|
-
expressionValue: propDef.expressionValue || undefined,
|
|
497
|
-
content: propDef.content,
|
|
498
|
-
buttonType: propDef.buttonType,
|
|
499
|
-
textboxType: propDef.textboxType,
|
|
500
|
-
...Object.keys(validation).length > 0 ? validation : undefined
|
|
501
|
-
};
|
|
502
|
-
if (typeInfo.textboxType) {
|
|
503
|
-
component.textboxType = typeInfo.textboxType;
|
|
504
|
-
// Add default regex patterns for email and url
|
|
505
|
-
if (typeInfo.textboxType === 'email') {
|
|
506
|
-
if (!validation.pattern) {
|
|
507
|
-
validation.regex = '^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$'; // Standard email regex
|
|
508
|
-
validation.validationMessage = 'Please enter a valid email address';
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
else if (typeInfo.textboxType === 'url') {
|
|
512
|
-
if (!validation.pattern) {
|
|
513
|
-
validation.regex = '^(https?:\\/\\/)?(www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b([-a-zA-Z0-9()@:%_\\+.~#?&//=]*)$';
|
|
514
|
-
validation.validationMessage = 'Please enter a valid URL';
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
if (typeInfo.type === 'date' && component.defaultValue !== undefined && component.defaultValue !== null) {
|
|
519
|
-
const formatted = this.formatDateTimeValue(component.defaultValue);
|
|
520
|
-
if (formatted)
|
|
521
|
-
component.defaultValue = formatted;
|
|
522
|
-
}
|
|
523
|
-
if ((typeInfo.type === 'splitButton' || typeInfo.type === 'dropdown' || typeInfo.type === 'multiselect' || typeInfo.type === 'radio') && (propDef.enum || propDef.options)) {
|
|
524
|
-
component.options = propDef.enum || propDef.options;
|
|
525
|
-
}
|
|
526
|
-
return component;
|
|
527
|
-
}
|
|
528
|
-
parseDateFromValue(value) {
|
|
529
|
-
if (value == null)
|
|
530
|
-
return null;
|
|
531
|
-
// numeric timestamps
|
|
532
|
-
if (typeof value === 'number') {
|
|
533
|
-
// assume seconds if small
|
|
534
|
-
if (value < 1e12)
|
|
535
|
-
return new Date(value * 1000);
|
|
536
|
-
return new Date(value);
|
|
537
|
-
}
|
|
538
|
-
if (typeof value === 'string') {
|
|
539
|
-
const s = value.trim();
|
|
540
|
-
// Try ISO / RFC parse
|
|
541
|
-
const dIso = new Date(s);
|
|
542
|
-
if (!isNaN(dIso.getTime()))
|
|
543
|
-
return dIso;
|
|
544
|
-
// yyyy-mm-dd[ T hh:mm(:ss)]
|
|
545
|
-
const m = s.match(/^(\d{4})-(\d{2})-(\d{2})(?:[T ](\d{2}):(\d{2})(?::(\d{2}))?)?/);
|
|
546
|
-
if (m)
|
|
547
|
-
return new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3]), Number(m[4] || 0), Number(m[5] || 0), Number(m[6] || 0));
|
|
548
|
-
// mm/dd/yyyy or m/d/yyyy
|
|
549
|
-
const m2 = s.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})(?:[ T](\d{1,2}):(\d{2})(?:[:](\d{2}))?)?/);
|
|
550
|
-
if (m2)
|
|
551
|
-
return new Date(Number(m2[3]), Number(m2[1]) - 1, Number(m2[2]), Number(m2[4] || 0), Number(m2[5] || 0), Number(m2[6] || 0));
|
|
552
|
-
}
|
|
553
|
-
return null;
|
|
554
|
-
}
|
|
555
|
-
formatDate(d) {
|
|
556
|
-
const mm = (d.getMonth() + 1).toString().padStart(2, '0');
|
|
557
|
-
const dd = d.getDate().toString().padStart(2, '0');
|
|
558
|
-
const yyyy = d.getFullYear();
|
|
559
|
-
return `${mm}/${dd}/${yyyy}`;
|
|
560
|
-
}
|
|
561
|
-
// private formatTime(d: Date): string {
|
|
562
|
-
// let hours = d.getHours();
|
|
563
|
-
// const minutes = d.getMinutes().toString().padStart(2, '0');
|
|
564
|
-
// const ampm = hours >= 12 ? 'PM' : 'AM';
|
|
565
|
-
// hours = hours % 12;
|
|
566
|
-
// if (hours === 0) hours = 12;
|
|
567
|
-
// return `${hours.toString().padStart(2, '0')}:${minutes} ${ampm}`;
|
|
568
|
-
// }
|
|
569
|
-
formatDateTimeValue(value) {
|
|
570
|
-
const d = this.parseDateFromValue(value);
|
|
571
|
-
if (!d)
|
|
572
|
-
return undefined;
|
|
573
|
-
const datePart = this.formatDate(d);
|
|
574
|
-
const hasTime = d.getHours() !== 0 || d.getMinutes() !== 0 || /\dT\d{2}:\d{2}/.test(String(value));
|
|
575
|
-
if (hasTime)
|
|
576
|
-
return `${datePart}`;
|
|
577
|
-
return datePart;
|
|
578
|
-
}
|
|
579
|
-
mapJsonSchemaType(propDef) {
|
|
580
|
-
const schemaType = propDef.type;
|
|
581
|
-
if (schemaType === 'string') {
|
|
582
|
-
const fmt = (propDef.format || '').toString().toLowerCase();
|
|
583
|
-
const text = ((propDef.title || propDef.label || '') + ' ' + (propDef.description || '')).toLowerCase();
|
|
584
|
-
if (/email|mail|e-?mail/.test(fmt) || /email|mail|e-?mail/.test(text))
|
|
585
|
-
return { type: 'textbox', textboxType: 'email' };
|
|
586
|
-
if (/uri|url|link|website/.test(fmt) || /uri|url|link|website/.test(text))
|
|
587
|
-
return { type: 'textbox', textboxType: 'url' };
|
|
588
|
-
if (/phone|tel|mobile|cell|msisdn/.test(fmt) || /phone|tel|mobile|cell|msisdn/.test(text))
|
|
589
|
-
return { type: 'textbox', textboxType: 'number' };
|
|
590
|
-
if (/password|pwd|pass|security|secret|key/.test(fmt) || /password|pwd|pass|security|secret|key/.test(text))
|
|
591
|
-
return { type: 'textbox', textboxType: 'password' };
|
|
592
|
-
if (fmt === 'date' || fmt === 'date-time' || /date|birthday|dob/.test(text))
|
|
593
|
-
return { type: 'date' };
|
|
594
|
-
if (propDef.enum)
|
|
595
|
-
return { type: 'dropdown' };
|
|
596
|
-
if (propDef.options)
|
|
597
|
-
return { type: 'dropdown' };
|
|
598
|
-
return { type: this.config.defaultTextType };
|
|
599
|
-
}
|
|
600
|
-
if (schemaType === 'number' || schemaType === 'integer')
|
|
601
|
-
return { type: 'number' };
|
|
602
|
-
if (schemaType === 'boolean')
|
|
603
|
-
return { type: 'checkbox' };
|
|
604
|
-
if (schemaType === 'array')
|
|
605
|
-
return { type: 'multiselect' };
|
|
606
|
-
// Presentation / custom widget types
|
|
607
|
-
if (schemaType === 'message')
|
|
608
|
-
return { type: 'message' };
|
|
609
|
-
if (schemaType === 'button')
|
|
610
|
-
return { type: 'button' };
|
|
611
|
-
if (schemaType === 'signature' || schemaType === 'sign')
|
|
612
|
-
return { type: 'signature' };
|
|
613
|
-
if (schemaType === 'image' || schemaType === 'imageEditor')
|
|
614
|
-
return { type: 'imageEditor' };
|
|
615
|
-
if (schemaType === 'file' || schemaType === 'fileUpload')
|
|
616
|
-
return { type: 'fileUpload' };
|
|
617
|
-
if (schemaType === 'color' || schemaType === 'colorPicker')
|
|
618
|
-
return { type: 'colorPicker' };
|
|
619
|
-
if (schemaType === 'splitButton')
|
|
620
|
-
return { type: 'splitButton' };
|
|
621
|
-
return { type: this.config.defaultTextType };
|
|
622
|
-
}
|
|
623
|
-
generateComponentId(type) {
|
|
624
|
-
if (!this.config.generateIds)
|
|
625
|
-
return '';
|
|
626
|
-
const timestamp = Date.now();
|
|
627
|
-
const random = Math.floor(Math.random() * 1000);
|
|
628
|
-
return `${type}_${timestamp}_${random}`;
|
|
629
|
-
}
|
|
630
|
-
humanizeFieldName(fieldName) {
|
|
631
|
-
let result = fieldName.replace(/_/g, ' ');
|
|
632
|
-
result = result.replace(/([a-z])([A-Z])/g, '$1 $2');
|
|
633
|
-
return result.charAt(0).toUpperCase() + result.slice(1);
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
/**
|
|
637
|
-
* Validates JSON syntax
|
|
638
|
-
*/
|
|
639
|
-
export const validateJSON = (jsonString) => {
|
|
640
|
-
try {
|
|
641
|
-
JSON.parse(jsonString);
|
|
642
|
-
return { valid: true };
|
|
643
|
-
}
|
|
644
|
-
catch (error) {
|
|
645
|
-
return {
|
|
646
|
-
valid: false,
|
|
647
|
-
errors: [`Invalid JSON syntax: ${error.message}`]
|
|
648
|
-
};
|
|
649
|
-
}
|
|
650
|
-
};
|
|
651
|
-
/**
|
|
652
|
-
* Validates the structure of a FormSchema object
|
|
653
|
-
*/
|
|
654
|
-
export const validateFormSchema = (schema) => {
|
|
655
|
-
const errors = [];
|
|
656
|
-
const warnings = [];
|
|
657
|
-
if (!schema.components) {
|
|
658
|
-
errors.push('Missing required property: components');
|
|
659
|
-
}
|
|
660
|
-
else if (!Array.isArray(schema.components)) {
|
|
661
|
-
errors.push('Property "components" must be an array');
|
|
662
|
-
}
|
|
663
|
-
// Validate component structure
|
|
664
|
-
if (Array.isArray(schema.components)) {
|
|
665
|
-
const componentIds = new Set();
|
|
666
|
-
const validateComponent = (comp, path = 'root') => {
|
|
667
|
-
if (!comp.id) {
|
|
668
|
-
errors.push(`Component at ${path} is missing "id" property`);
|
|
669
|
-
}
|
|
670
|
-
else {
|
|
671
|
-
if (componentIds.has(comp.id)) {
|
|
672
|
-
errors.push(`Duplicate component ID found: ${comp.id}`);
|
|
673
|
-
}
|
|
674
|
-
componentIds.add(comp.id);
|
|
675
|
-
}
|
|
676
|
-
if (!comp.type) {
|
|
677
|
-
errors.push(`Component "${comp.id || 'unknown'}" is missing "type" property`);
|
|
678
|
-
}
|
|
679
|
-
if (comp.children && Array.isArray(comp.children)) {
|
|
680
|
-
comp.children.forEach((child, index) => {
|
|
681
|
-
validateComponent(child, `${path}.children[${index}]`);
|
|
682
|
-
});
|
|
683
|
-
}
|
|
684
|
-
if (comp.tabItems && Array.isArray(comp.tabItems)) {
|
|
685
|
-
comp.tabItems.forEach((tab, tabIndex) => {
|
|
686
|
-
if (tab.content && Array.isArray(tab.content)) {
|
|
687
|
-
tab.content.forEach((child, index) => {
|
|
688
|
-
validateComponent(child, `${path}.tabItems[${tabIndex}].content[${index}]`);
|
|
689
|
-
});
|
|
690
|
-
}
|
|
691
|
-
});
|
|
692
|
-
}
|
|
693
|
-
if (comp.tableCells && Array.isArray(comp.tableCells)) {
|
|
694
|
-
comp.tableCells.forEach((row, rowIndex) => {
|
|
695
|
-
if (Array.isArray(row)) {
|
|
696
|
-
row.forEach((cell, colIndex) => {
|
|
697
|
-
if (Array.isArray(cell)) {
|
|
698
|
-
cell.forEach((child, index) => {
|
|
699
|
-
validateComponent(child, `${path}.tableCells[${rowIndex}][${colIndex}][${index}]`);
|
|
700
|
-
});
|
|
701
|
-
}
|
|
702
|
-
});
|
|
703
|
-
}
|
|
704
|
-
});
|
|
705
|
-
}
|
|
706
|
-
};
|
|
707
|
-
schema.components.forEach((comp, index) => {
|
|
708
|
-
validateComponent(comp, `components[${index}]`);
|
|
709
|
-
});
|
|
710
|
-
}
|
|
711
|
-
return {
|
|
712
|
-
valid: errors.length === 0,
|
|
713
|
-
errors: errors.length > 0 ? errors : undefined,
|
|
714
|
-
warnings: warnings.length > 0 ? warnings : undefined
|
|
715
|
-
};
|
|
716
|
-
};
|
|
717
|
-
/**
|
|
718
|
-
* Sanitizes and normalizes a form schema
|
|
719
|
-
* Adds default values for missing optional properties
|
|
720
|
-
*/
|
|
721
|
-
export const sanitizeFormSchema = (schema) => {
|
|
722
|
-
const sanitized = {
|
|
723
|
-
version: schema.version || "0.1.0",
|
|
724
|
-
components: schema.components || []
|
|
725
|
-
};
|
|
726
|
-
const sanitizeComponent = (comp) => {
|
|
727
|
-
const sanitizedComp = {
|
|
728
|
-
...comp,
|
|
729
|
-
_configured: true
|
|
730
|
-
};
|
|
731
|
-
if (comp.children && Array.isArray(comp.children)) {
|
|
732
|
-
sanitizedComp.children = comp.children.map(sanitizeComponent);
|
|
733
|
-
}
|
|
734
|
-
if (comp.tabItems && Array.isArray(comp.tabItems)) {
|
|
735
|
-
sanitizedComp.tabItems = comp.tabItems.map((tab) => ({
|
|
736
|
-
...tab,
|
|
737
|
-
content: tab.content ? tab.content.map(sanitizeComponent) : []
|
|
738
|
-
}));
|
|
739
|
-
}
|
|
740
|
-
if (comp.tableCells && Array.isArray(comp.tableCells)) {
|
|
741
|
-
sanitizedComp.tableCells = comp.tableCells.map((row) => row.map((cell) => Array.isArray(cell) ? cell.map(sanitizeComponent) : []));
|
|
742
|
-
}
|
|
743
|
-
return sanitizedComp;
|
|
744
|
-
};
|
|
745
|
-
sanitized.components = sanitized.components.map(sanitizeComponent);
|
|
746
|
-
return sanitized;
|
|
747
|
-
};
|
|
748
|
-
/**
|
|
749
|
-
* Exports a FormSchema to formatted JSON string
|
|
750
|
-
*/
|
|
751
|
-
export const exportFormSchema = (schema) => {
|
|
752
|
-
return JSON.stringify(schema, null, 2);
|
|
753
|
-
};
|