@syncfusion/ej2-react-form-renderer 0.1.2-beta → 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.
Files changed (181) hide show
  1. package/README.md +88 -105
  2. package/diConfig.json +85 -0
  3. package/dist/ej2-react-form-renderer.min.js +10 -0
  4. package/dist/ej2-react-form-renderer.umd.min.js +11 -0
  5. package/dist/ej2-react-form-renderer.umd.min.js.map +1 -0
  6. package/dist/es6/ej2-react-form-renderer.es2015.js +37 -0
  7. package/dist/es6/ej2-react-form-renderer.es2015.js.map +1 -0
  8. package/form-renderer.d.ts +4 -0
  9. package/form-renderer.js +4 -0
  10. package/index.d.ts +2 -4
  11. package/index.js +2 -4
  12. package/license +6 -13
  13. package/package.json +19 -38
  14. package/src/form-renderer/formrenderer.component.d.ts +34 -0
  15. package/src/form-renderer/formrenderer.component.js +49 -0
  16. package/src/form-renderer/index.d.ts +1 -0
  17. package/src/form-renderer/index.js +1 -0
  18. package/src/global.js +1 -0
  19. package/src/index.d.ts +2 -6
  20. package/src/index.js +2 -6
  21. package/styles/bds-lite.css +1 -0
  22. package/styles/bds-lite.scss +1 -0
  23. package/styles/bds.css +1 -0
  24. package/styles/bds.scss +1 -0
  25. package/styles/bootstrap-dark-lite.css +1 -0
  26. package/styles/bootstrap-dark-lite.scss +1 -0
  27. package/styles/bootstrap-dark.css +1 -26
  28. package/styles/bootstrap-dark.scss +1 -0
  29. package/styles/bootstrap-lite.css +1 -0
  30. package/styles/bootstrap-lite.scss +1 -0
  31. package/styles/bootstrap.css +1 -26
  32. package/styles/bootstrap.scss +1 -0
  33. package/styles/bootstrap4-lite.css +1 -0
  34. package/styles/bootstrap4-lite.scss +1 -0
  35. package/styles/bootstrap4.css +1 -26
  36. package/styles/bootstrap4.scss +1 -0
  37. package/styles/bootstrap5-dark-lite.css +1 -0
  38. package/styles/bootstrap5-dark-lite.scss +1 -0
  39. package/styles/bootstrap5-dark.css +1 -26
  40. package/styles/bootstrap5-dark.scss +1 -0
  41. package/styles/bootstrap5-lite.css +1 -0
  42. package/styles/bootstrap5-lite.scss +1 -0
  43. package/styles/bootstrap5.3-lite.css +1 -0
  44. package/styles/bootstrap5.3-lite.scss +1 -0
  45. package/styles/bootstrap5.3.css +1 -26
  46. package/styles/bootstrap5.3.scss +1 -0
  47. package/styles/bootstrap5.css +1 -26
  48. package/styles/bootstrap5.scss +1 -0
  49. package/styles/fabric-dark-lite.css +1 -0
  50. package/styles/fabric-dark-lite.scss +1 -0
  51. package/styles/fabric-dark.css +1 -0
  52. package/styles/fabric-dark.scss +1 -0
  53. package/styles/fabric-lite.css +1 -0
  54. package/styles/fabric-lite.scss +1 -0
  55. package/styles/fabric.css +1 -0
  56. package/styles/fabric.scss +1 -0
  57. package/styles/fluent-dark-lite.css +1 -0
  58. package/styles/fluent-dark-lite.scss +1 -0
  59. package/styles/fluent-dark.css +1 -26
  60. package/styles/fluent-dark.scss +1 -0
  61. package/styles/fluent-lite.css +1 -0
  62. package/styles/fluent-lite.scss +1 -0
  63. package/styles/fluent.css +1 -26
  64. package/styles/fluent.scss +1 -0
  65. package/styles/fluent2-lite.css +1 -0
  66. package/styles/fluent2-lite.scss +1 -0
  67. package/styles/fluent2.css +1 -26
  68. package/styles/fluent2.scss +1 -0
  69. package/styles/form-renderer/bds.css +1 -0
  70. package/styles/form-renderer/bds.scss +1 -0
  71. package/styles/form-renderer/bootstrap-dark.css +1 -0
  72. package/styles/form-renderer/bootstrap-dark.scss +1 -0
  73. package/styles/form-renderer/bootstrap.css +1 -0
  74. package/styles/form-renderer/bootstrap.scss +1 -0
  75. package/styles/form-renderer/bootstrap4.css +1 -0
  76. package/styles/form-renderer/bootstrap4.scss +1 -0
  77. package/styles/form-renderer/bootstrap5-dark.css +1 -0
  78. package/styles/form-renderer/bootstrap5-dark.scss +1 -0
  79. package/styles/form-renderer/bootstrap5.3.css +1 -0
  80. package/styles/form-renderer/bootstrap5.3.scss +1 -0
  81. package/styles/form-renderer/bootstrap5.css +1 -0
  82. package/styles/form-renderer/bootstrap5.scss +1 -0
  83. package/styles/form-renderer/fabric-dark.css +1 -0
  84. package/styles/form-renderer/fabric-dark.scss +1 -0
  85. package/styles/form-renderer/fabric.css +1 -0
  86. package/styles/form-renderer/fabric.scss +1 -0
  87. package/styles/form-renderer/fluent-dark.css +1 -0
  88. package/styles/form-renderer/fluent-dark.scss +1 -0
  89. package/styles/form-renderer/fluent.css +1 -0
  90. package/styles/form-renderer/fluent.scss +1 -0
  91. package/styles/form-renderer/fluent2.css +1 -0
  92. package/styles/form-renderer/fluent2.scss +1 -0
  93. package/styles/form-renderer/highcontrast-light.css +1 -0
  94. package/styles/form-renderer/highcontrast-light.scss +1 -0
  95. package/styles/form-renderer/highcontrast.css +1 -0
  96. package/styles/form-renderer/highcontrast.scss +1 -0
  97. package/styles/form-renderer/material-dark.css +1 -0
  98. package/styles/form-renderer/material-dark.scss +1 -0
  99. package/styles/form-renderer/material.css +1 -0
  100. package/styles/form-renderer/material.scss +1 -0
  101. package/styles/form-renderer/material3-dark.css +1 -0
  102. package/styles/form-renderer/material3-dark.scss +1 -0
  103. package/styles/form-renderer/material3.css +1 -0
  104. package/styles/form-renderer/material3.scss +1 -0
  105. package/styles/form-renderer/tailwind-dark.css +1 -0
  106. package/styles/form-renderer/tailwind-dark.scss +1 -0
  107. package/styles/form-renderer/tailwind.css +1 -0
  108. package/styles/form-renderer/tailwind.scss +1 -0
  109. package/styles/form-renderer/tailwind3.css +1 -0
  110. package/styles/form-renderer/tailwind3.scss +1 -0
  111. package/styles/highcontrast-light-lite.css +1 -0
  112. package/styles/highcontrast-light-lite.scss +1 -0
  113. package/styles/highcontrast-light.css +1 -0
  114. package/styles/highcontrast-light.scss +1 -0
  115. package/styles/highcontrast-lite.css +1 -0
  116. package/styles/highcontrast-lite.scss +1 -0
  117. package/styles/highcontrast.css +1 -0
  118. package/styles/highcontrast.scss +1 -0
  119. package/styles/material-dark-lite.css +1 -0
  120. package/styles/material-dark-lite.scss +1 -0
  121. package/styles/material-dark.css +1 -26
  122. package/styles/material-dark.scss +1 -0
  123. package/styles/material-lite.css +1 -0
  124. package/styles/material-lite.scss +1 -0
  125. package/styles/material.css +1 -26
  126. package/styles/material.scss +1 -0
  127. package/styles/material3-dark-lite.css +1 -0
  128. package/styles/material3-dark-lite.scss +1 -0
  129. package/styles/material3-dark.css +1 -26
  130. package/styles/material3-dark.scss +1 -0
  131. package/styles/material3-lite.css +1 -0
  132. package/styles/material3-lite.scss +1 -0
  133. package/styles/material3.css +1 -26
  134. package/styles/material3.scss +1 -0
  135. package/styles/tailwind-dark-lite.css +1 -0
  136. package/styles/tailwind-dark-lite.scss +1 -0
  137. package/styles/tailwind-dark.css +1 -26
  138. package/styles/tailwind-dark.scss +1 -0
  139. package/styles/tailwind-lite.css +1 -0
  140. package/styles/tailwind-lite.scss +1 -0
  141. package/styles/tailwind.css +1 -26
  142. package/styles/tailwind.scss +1 -0
  143. package/styles/tailwind3-lite.css +1 -0
  144. package/styles/tailwind3-lite.scss +1 -0
  145. package/styles/tailwind3.css +1 -26
  146. package/styles/tailwind3.scss +1 -0
  147. package/src/CheckboxGroup.d.ts +0 -12
  148. package/src/CheckboxGroup.js +0 -49
  149. package/src/ConditionalRuleEngine.d.ts +0 -295
  150. package/src/ConditionalRuleEngine.js +0 -566
  151. package/src/CustomTabComponent.d.ts +0 -13
  152. package/src/CustomTabComponent.js +0 -35
  153. package/src/DataGridComponent.d.ts +0 -28
  154. package/src/DataGridComponent.js +0 -147
  155. package/src/FormRenderer.d.ts +0 -84
  156. package/src/FormRenderer.js +0 -707
  157. package/src/FormValidator.d.ts +0 -479
  158. package/src/FormValidator.js +0 -579
  159. package/src/MinMaxRuleEngine.d.ts +0 -95
  160. package/src/MinMaxRuleEngine.js +0 -361
  161. package/src/StaticHtmlRenderer.d.ts +0 -8
  162. package/src/StaticHtmlRenderer.js +0 -21
  163. package/src/expressions/DependencyTracker.d.ts +0 -30
  164. package/src/expressions/DependencyTracker.js +0 -128
  165. package/src/expressions/ExpressionEngine.d.ts +0 -81
  166. package/src/expressions/ExpressionEngine.js +0 -503
  167. package/src/expressions/ExpressionParser.d.ts +0 -39
  168. package/src/expressions/ExpressionParser.js +0 -270
  169. package/src/json-converter.d.ts +0 -171
  170. package/src/json-converter.js +0 -1209
  171. package/src/parser.d.ts +0 -99
  172. package/src/parser.js +0 -753
  173. package/src/renderFormField.d.ts +0 -21
  174. package/src/renderFormField.js +0 -226
  175. package/src/types/FormSchema.d.ts +0 -211
  176. package/src/types/FormSchema.js +0 -1
  177. package/src/utils.js +0 -408
  178. package/styles/bootstrap5.3-dark.css +0 -26
  179. package/styles/fluent2-dark.css +0 -26
  180. package/styles/form-renderer.base.css +0 -499
  181. 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
- };