@genesislcap/foundation-forms 14.397.2 → 14.398.0

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 (77) hide show
  1. package/dist/custom-elements.json +389 -4
  2. package/dist/dts/form.d.ts +100 -1
  3. package/dist/dts/form.d.ts.map +1 -1
  4. package/dist/dts/form.styles.d.ts.map +1 -1
  5. package/dist/dts/form.template.d.ts.map +1 -1
  6. package/dist/dts/jsonforms/json-forms.d.ts +13 -0
  7. package/dist/dts/jsonforms/json-forms.d.ts.map +1 -1
  8. package/dist/dts/jsonforms/renderers/ArrayListWrapperRenderer.d.ts +5 -0
  9. package/dist/dts/jsonforms/renderers/ArrayListWrapperRenderer.d.ts.map +1 -1
  10. package/dist/dts/jsonforms/renderers/BooleanControlRenderer.d.ts.map +1 -1
  11. package/dist/dts/jsonforms/renderers/ConnectedMultiselectControlRenderer.d.ts.map +1 -1
  12. package/dist/dts/jsonforms/renderers/ControlWrapperRenderer.d.ts.map +1 -1
  13. package/dist/dts/jsonforms/renderers/EnumControlRenderer.d.ts.map +1 -1
  14. package/dist/dts/jsonforms/renderers/LayoutFormGridRenderer.d.ts +3 -0
  15. package/dist/dts/jsonforms/renderers/LayoutFormGridRenderer.d.ts.map +1 -0
  16. package/dist/dts/jsonforms/renderers/RenderersRanks.d.ts +1 -0
  17. package/dist/dts/jsonforms/renderers/RenderersRanks.d.ts.map +1 -1
  18. package/dist/dts/jsonforms/testers/isOneOfOptionMultiselect.d.ts.map +1 -1
  19. package/dist/dts/types.d.ts +89 -2
  20. package/dist/dts/types.d.ts.map +1 -1
  21. package/dist/dts/utils/csv-parser.d.ts +85 -0
  22. package/dist/dts/utils/csv-parser.d.ts.map +1 -0
  23. package/dist/dts/utils/index.d.ts +1 -0
  24. package/dist/dts/utils/index.d.ts.map +1 -1
  25. package/dist/dts/utils/schema-utils.d.ts +46 -0
  26. package/dist/dts/utils/schema-utils.d.ts.map +1 -0
  27. package/dist/dts/utils/validation.d.ts +2 -0
  28. package/dist/dts/utils/validation.d.ts.map +1 -1
  29. package/dist/esm/form.js +423 -5
  30. package/dist/esm/form.styles.js +41 -1
  31. package/dist/esm/form.template.js +33 -1
  32. package/dist/esm/jsonforms/json-forms.js +30 -0
  33. package/dist/esm/jsonforms/renderers/ArrayListWrapperRenderer.js +223 -22
  34. package/dist/esm/jsonforms/renderers/BooleanControlRenderer.js +1 -2
  35. package/dist/esm/jsonforms/renderers/ConnectedMultiselectControlRenderer.js +13 -2
  36. package/dist/esm/jsonforms/renderers/ControlWrapperRenderer.js +25 -4
  37. package/dist/esm/jsonforms/renderers/EnumControlRenderer.js +14 -5
  38. package/dist/esm/jsonforms/renderers/LayoutFormGridRenderer.js +39 -0
  39. package/dist/esm/jsonforms/renderers/RenderersRanks.js +1 -0
  40. package/dist/esm/jsonforms/testers/isOneOfOptionMultiselect.js +1 -1
  41. package/dist/esm/utils/csv-parser.js +486 -0
  42. package/dist/esm/utils/index.js +1 -0
  43. package/dist/esm/utils/schema-utils.js +120 -0
  44. package/dist/esm/utils/validation.js +2 -0
  45. package/dist/foundation-forms.api.json +1028 -34
  46. package/dist/foundation-forms.d.ts +285 -2
  47. package/docs/api/foundation-forms.arrayrendereroptions.md +2 -2
  48. package/docs/api/foundation-forms.bulkrowstatus.md +22 -0
  49. package/docs/api/foundation-forms.bulkrowsubmitstatus.md +13 -0
  50. package/docs/api/foundation-forms.bulksubmitfaileditem.md +20 -0
  51. package/docs/api/foundation-forms.bulksubmitresult.md +18 -0
  52. package/docs/api/foundation-forms.bulksubmitsuccessitem.md +17 -0
  53. package/docs/api/foundation-forms.childuischemaresolver.md +15 -0
  54. package/docs/api/foundation-forms.csvmappingresult.mappedrows.md +13 -0
  55. package/docs/api/foundation-forms.csvmappingresult.md +77 -0
  56. package/docs/api/foundation-forms.csvmappingresult.unmappedcolumns.md +13 -0
  57. package/docs/api/foundation-forms.csvparseresult.errors.md +13 -0
  58. package/docs/api/foundation-forms.csvparseresult.headers.md +13 -0
  59. package/docs/api/foundation-forms.csvparseresult.md +96 -0
  60. package/docs/api/foundation-forms.csvparseresult.rows.md +13 -0
  61. package/docs/api/foundation-forms.downloadcsvtemplate.md +74 -0
  62. package/docs/api/foundation-forms.form.bulkinsert.md +13 -0
  63. package/docs/api/foundation-forms.form.bulkinsertmaxitems.md +13 -0
  64. package/docs/api/foundation-forms.form.bulkinsertminitems.md +13 -0
  65. package/docs/api/foundation-forms.form.clearrowsubmitstatuses.md +17 -0
  66. package/docs/api/foundation-forms.form.downloadcsvtemplate.md +17 -0
  67. package/docs/api/foundation-forms.form.handlecsvfileselected.md +54 -0
  68. package/docs/api/foundation-forms.form.md +132 -0
  69. package/docs/api/foundation-forms.form.rowsubmitstatuses.md +13 -0
  70. package/docs/api/foundation-forms.form.submitsinglerow.md +56 -0
  71. package/docs/api/foundation-forms.generatecsvtemplate.md +104 -0
  72. package/docs/api/foundation-forms.mapcsvtoschema.md +88 -0
  73. package/docs/api/foundation-forms.md +147 -0
  74. package/docs/api/foundation-forms.parsecsv.md +56 -0
  75. package/docs/api/foundation-forms.uischemaelementtype.md +1 -1
  76. package/docs/api-report.md.api.md +87 -4
  77. package/package.json +19 -17
@@ -0,0 +1,486 @@
1
+ import Papa from 'papaparse';
2
+ /**
3
+ * Parses a CSV string into headers and rows using PapaParse.
4
+ * Handles quoted fields, escaped quotes, empty values, and mixed line endings.
5
+ * @param content - The CSV content as a string
6
+ * @returns The parsed result with headers, rows, and any errors
7
+ * @public
8
+ */
9
+ export function parseCsv(content) {
10
+ var _a, _b, _c;
11
+ const errors = [];
12
+ if (!(content === null || content === void 0 ? void 0 : content.trim())) {
13
+ errors.push('CSV file is empty');
14
+ return { headers: [], rows: [], errors };
15
+ }
16
+ const result = Papa.parse(content, {
17
+ header: true,
18
+ skipEmptyLines: true,
19
+ dynamicTyping: false, // Keep all values as strings; mapCsvToSchema handles type conversion
20
+ });
21
+ const headers = (_a = result.meta.fields) !== null && _a !== void 0 ? _a : [];
22
+ if (headers.length === 0) {
23
+ errors.push('CSV file has no headers');
24
+ return { headers: [], rows: [], errors };
25
+ }
26
+ // Add PapaParse errors
27
+ for (const err of result.errors) {
28
+ errors.push((_b = err.message) !== null && _b !== void 0 ? _b : `Parse error at row ${(_c = err.row) !== null && _c !== void 0 ? _c : '?'}`);
29
+ }
30
+ // Ensure all row values are strings (PapaParse may return undefined for missing cells)
31
+ const rows = result.data.map((row) => {
32
+ const stringRow = {};
33
+ for (const header of headers) {
34
+ const val = row[header];
35
+ stringRow[header] = val === undefined || val === null ? '' : String(val);
36
+ }
37
+ return stringRow;
38
+ });
39
+ return { headers, rows, errors };
40
+ }
41
+ /**
42
+ * Maps CSV rows to schema fields with case-insensitive matching.
43
+ * When uiSchema is provided, CSV headers can also match UI schema labels.
44
+ * Converts values to appropriate types based on schema definition.
45
+ * @param csvRows - The parsed CSV rows
46
+ * @param schema - The JSON schema defining the fields
47
+ * @param uiSchema - Optional UI schema to map labels to field names on import
48
+ * @returns Mapped rows and list of unmapped columns
49
+ * @public
50
+ */
51
+ export function mapCsvToSchema(csvRows, schema, uiSchema) {
52
+ var _a, _b;
53
+ if (!(schema === null || schema === void 0 ? void 0 : schema.properties) || csvRows.length === 0) {
54
+ return { mappedRows: [], unmappedColumns: [] };
55
+ }
56
+ const schemaFields = Object.keys(schema.properties);
57
+ // Build label -> fieldName mapping from UI schema (case-insensitive for matching)
58
+ const labelToField = new Map();
59
+ if (uiSchema) {
60
+ const fields = extractFieldsFromUiSchema(uiSchema);
61
+ for (const { fieldName, label } of fields) {
62
+ if (label && label.trim() && schema.properties[fieldName]) {
63
+ labelToField.set(label.trim().toLowerCase(), fieldName);
64
+ }
65
+ }
66
+ }
67
+ // Create case-insensitive mapping from CSV headers to schema fields
68
+ const headerMapping = new Map();
69
+ const unmappedColumns = [];
70
+ if (csvRows.length > 0) {
71
+ const csvHeaders = Object.keys((_a = csvRows[0]) !== null && _a !== void 0 ? _a : {});
72
+ for (const csvHeader of csvHeaders) {
73
+ const normalizedCsvHeader = csvHeader.toUpperCase();
74
+ let matchedField = schemaFields.find((field) => field.toUpperCase() === normalizedCsvHeader);
75
+ if (!matchedField && uiSchema) {
76
+ matchedField = (_b = labelToField.get(csvHeader.trim().toLowerCase())) !== null && _b !== void 0 ? _b : undefined;
77
+ }
78
+ if (matchedField) {
79
+ headerMapping.set(csvHeader, matchedField);
80
+ }
81
+ else {
82
+ unmappedColumns.push(csvHeader);
83
+ }
84
+ }
85
+ }
86
+ // Map each row
87
+ const mappedRows = csvRows.map((row) => {
88
+ var _a;
89
+ const mappedRow = {};
90
+ for (const [csvHeader, schemaField] of headerMapping) {
91
+ const rawValue = row[csvHeader];
92
+ const fieldSchema = (_a = schema.properties) === null || _a === void 0 ? void 0 : _a[schemaField];
93
+ mappedRow[schemaField] = convertValue(rawValue, fieldSchema);
94
+ }
95
+ return mappedRow;
96
+ });
97
+ return { mappedRows, unmappedColumns };
98
+ }
99
+ /**
100
+ * Converts a string value to the appropriate type based on schema.
101
+ * @param value - The string value from CSV
102
+ * @param fieldSchema - The JSON schema for this field
103
+ * @returns The converted value
104
+ */
105
+ function convertValue(value, fieldSchema) {
106
+ if (value === '') {
107
+ return value;
108
+ }
109
+ if (!fieldSchema) {
110
+ return value;
111
+ }
112
+ const fieldType = getFieldType(fieldSchema);
113
+ switch (fieldType) {
114
+ case 'integer':
115
+ case 'number': {
116
+ const num = parseFloat(value);
117
+ return isNaN(num) ? value : num;
118
+ }
119
+ case 'boolean': {
120
+ const lowerValue = value.toLowerCase();
121
+ if (['true', 'yes', '1'].includes(lowerValue)) {
122
+ return true;
123
+ }
124
+ if (['false', 'no', '0'].includes(lowerValue)) {
125
+ return false;
126
+ }
127
+ return value;
128
+ }
129
+ default:
130
+ return value;
131
+ }
132
+ }
133
+ /**
134
+ * Gets the type from a JSON schema, handling oneOf/anyOf cases.
135
+ * @param schema - The JSON schema
136
+ * @returns The field type
137
+ */
138
+ function getFieldType(schema) {
139
+ if (schema.type) {
140
+ return Array.isArray(schema.type) ? schema.type[0] : schema.type;
141
+ }
142
+ // Handle oneOf/anyOf by finding the first non-null type
143
+ const alternatives = schema.oneOf || schema.anyOf;
144
+ if (alternatives) {
145
+ for (const alt of alternatives) {
146
+ if (typeof alt === 'object' && alt.type && alt.type !== 'null') {
147
+ return Array.isArray(alt.type) ? alt.type[0] : alt.type;
148
+ }
149
+ }
150
+ }
151
+ return undefined;
152
+ }
153
+ /**
154
+ * Generates a CSV template string with headers and sample data based on JSON schema.
155
+ * If a UI schema is provided, it will be used to determine which fields to include
156
+ * and in what order. Hidden fields in the UI schema will be excluded.
157
+ * @param schema - The JSON schema defining the fields
158
+ * @param uiSchema - Optional UI schema to determine field order and visibility
159
+ * @param includeOptionalFields - Whether to include optional fields when no UI schema (default: true)
160
+ * @param includeBom - Whether to prepend UTF-8 BOM for Excel compatibility (default: true)
161
+ * @returns The CSV template string with headers and one sample row
162
+ * @public
163
+ */
164
+ export function generateCsvTemplate(schema, uiSchema, includeOptionalFields = true, includeBom = true) {
165
+ if (!(schema === null || schema === void 0 ? void 0 : schema.properties)) {
166
+ return '';
167
+ }
168
+ const requiredFields = new Set(schema.required || []);
169
+ const headers = [];
170
+ const sampleValues = [];
171
+ // Get field order from UI schema if provided
172
+ const fieldsFromUiSchema = uiSchema ? extractFieldsFromUiSchema(uiSchema) : null;
173
+ if (fieldsFromUiSchema && fieldsFromUiSchema.length > 0) {
174
+ // Use UI schema order, filtering out hidden fields
175
+ for (const { fieldName, label, isHidden } of fieldsFromUiSchema) {
176
+ // Skip hidden fields
177
+ if (isHidden) {
178
+ continue;
179
+ }
180
+ // Check if field exists in JSON schema
181
+ const fieldSchema = schema.properties[fieldName];
182
+ if (!fieldSchema) {
183
+ continue;
184
+ }
185
+ headers.push(label && label.trim() ? label.trim() : fieldName);
186
+ const isRequired = requiredFields.has(fieldName);
187
+ const sampleValue = generateSampleValue(fieldName, fieldSchema, isRequired);
188
+ sampleValues.push(escapeCsvValue(sampleValue));
189
+ }
190
+ }
191
+ else {
192
+ // Fall back to JSON schema order
193
+ for (const [fieldName, fieldSchema] of Object.entries(schema.properties)) {
194
+ const isRequired = requiredFields.has(fieldName);
195
+ // Skip optional fields if not including them
196
+ if (!includeOptionalFields && !isRequired) {
197
+ continue;
198
+ }
199
+ headers.push(fieldName);
200
+ const fieldDef = fieldSchema;
201
+ const sampleValue = generateSampleValue(fieldName, fieldDef, isRequired);
202
+ sampleValues.push(escapeCsvValue(sampleValue));
203
+ }
204
+ }
205
+ if (headers.length === 0) {
206
+ return '';
207
+ }
208
+ // Build CSV content (escape headers in case they contain commas/quotes)
209
+ const headerRow = headers.map((h) => escapeCsvValue(h)).join(',');
210
+ const sampleRow = sampleValues.join(',');
211
+ const content = `${headerRow}\n${sampleRow}`;
212
+ return includeBom ? '\uFEFF' + content : content;
213
+ }
214
+ /**
215
+ * Extracts field names from a UI schema in the order they appear.
216
+ * @param uiSchema - The UI schema to extract fields from
217
+ * @returns Array of field info with name, label, and hidden status
218
+ * @internal
219
+ */
220
+ export function extractFieldsFromUiSchema(uiSchema) {
221
+ const fields = [];
222
+ function processElement(element) {
223
+ var _a, _b, _c;
224
+ if (element.type === 'Control' && element.scope) {
225
+ const fieldName = extractFieldNameFromScope(element.scope);
226
+ if (fieldName) {
227
+ // Bulk layout: scope #/properties/items - extract from childUiSchema for item-level fields
228
+ if (fieldName === 'items' && ((_a = element.options) === null || _a === void 0 ? void 0 : _a.childUiSchema)) {
229
+ const childSchema = element.options.childUiSchema;
230
+ const resolvedSchema = typeof childSchema === 'function'
231
+ ? childSchema(0, undefined, undefined)
232
+ : childSchema;
233
+ if (resolvedSchema) {
234
+ const childFields = extractFieldsFromUiSchema(resolvedSchema);
235
+ fields.push(...childFields);
236
+ }
237
+ return;
238
+ }
239
+ const isHidden = ((_b = element.options) === null || _b === void 0 ? void 0 : _b.hidden) === true;
240
+ const label = element.label;
241
+ fields.push({ fieldName, label, isHidden });
242
+ }
243
+ }
244
+ if (element.elements) {
245
+ for (const child of element.elements) {
246
+ processElement(child);
247
+ }
248
+ }
249
+ if ((_c = element.options) === null || _c === void 0 ? void 0 : _c.childElements) {
250
+ for (const child of element.options.childElements) {
251
+ processElement(child);
252
+ }
253
+ }
254
+ }
255
+ if (uiSchema.elements) {
256
+ for (const element of uiSchema.elements) {
257
+ processElement(element);
258
+ }
259
+ }
260
+ const schemaOptions = uiSchema.options;
261
+ const topLevelChildElements = schemaOptions === null || schemaOptions === void 0 ? void 0 : schemaOptions.childElements;
262
+ if (topLevelChildElements) {
263
+ for (const element of topLevelChildElements) {
264
+ processElement(element);
265
+ }
266
+ }
267
+ return fields;
268
+ }
269
+ /**
270
+ * Extracts the field name from a JSON schema scope string.
271
+ * @param scope - The scope string (e.g., "#/properties/FIELD_NAME")
272
+ * @returns The field name or null if not a valid scope
273
+ * @internal
274
+ */
275
+ function extractFieldNameFromScope(scope) {
276
+ const match = scope.match(/^#\/properties\/(.+)$/);
277
+ return match ? match[1] : null;
278
+ }
279
+ /**
280
+ * Generates a sample value for a field based on its schema type.
281
+ * @param fieldName - The name of the field
282
+ * @param fieldSchema - The JSON schema for this field
283
+ * @param isRequired - Whether the field is required
284
+ * @returns A sample value string
285
+ */
286
+ function generateSampleValue(fieldName, fieldSchema, isRequired) {
287
+ const fieldType = getFieldType(fieldSchema);
288
+ // Check for enum values first
289
+ if (fieldSchema.enum && fieldSchema.enum.length > 0) {
290
+ return String(fieldSchema.enum[0]);
291
+ }
292
+ // Check for const value
293
+ if (fieldSchema.const !== undefined) {
294
+ return String(fieldSchema.const);
295
+ }
296
+ // Check for default value
297
+ if (fieldSchema.default !== undefined) {
298
+ return String(fieldSchema.default);
299
+ }
300
+ // Check for examples
301
+ if (fieldSchema.examples &&
302
+ Array.isArray(fieldSchema.examples) &&
303
+ fieldSchema.examples.length > 0) {
304
+ return String(fieldSchema.examples[0]);
305
+ }
306
+ // Generate sample based on type
307
+ switch (fieldType) {
308
+ case 'string':
309
+ return generateStringSample(fieldName, fieldSchema);
310
+ case 'integer':
311
+ return generateIntegerSample(fieldSchema);
312
+ case 'number':
313
+ return generateNumberSample(fieldSchema);
314
+ case 'boolean':
315
+ return 'true';
316
+ case 'array':
317
+ return ''; // Arrays typically need special handling
318
+ case 'object':
319
+ return ''; // Objects typically need special handling
320
+ default:
321
+ // Default to a simple string sample
322
+ return isRequired ? `sample_${fieldName.toLowerCase()}` : '';
323
+ }
324
+ }
325
+ /**
326
+ * Generates a sample string value based on field name and schema.
327
+ * Uses format hints first, then heuristic inference from field name.
328
+ * @param fieldName - The name of the field
329
+ * @param fieldSchema - The JSON schema for this field
330
+ * @returns A sample string value
331
+ */
332
+ function generateStringSample(fieldName, fieldSchema) {
333
+ const lowerName = fieldName.toLowerCase();
334
+ // Check for format hints
335
+ if (fieldSchema.format) {
336
+ switch (fieldSchema.format) {
337
+ case 'date':
338
+ return '2024-01-15';
339
+ case 'date-time':
340
+ return '2024-01-15T10:30:00Z';
341
+ case 'time':
342
+ return '10:30:00';
343
+ case 'email':
344
+ return 'user@example.com';
345
+ case 'uri':
346
+ case 'url':
347
+ return 'https://example.com';
348
+ case 'uuid':
349
+ return '550e8400-e29b-41d4-a716-446655440000';
350
+ case 'hostname':
351
+ return 'example.com';
352
+ case 'ipv4':
353
+ return '192.168.1.1';
354
+ case 'ipv6':
355
+ return '2001:0db8:85a3:0000:0000:8a2e:0370:7334';
356
+ }
357
+ }
358
+ // Infer from field name
359
+ if (lowerName.includes('email')) {
360
+ return 'user@example.com';
361
+ }
362
+ if (lowerName.includes('phone') || lowerName.includes('tel')) {
363
+ return '+1234567890';
364
+ }
365
+ if (lowerName.includes('date') || lowerName.includes('time')) {
366
+ if (lowerName.includes('time') && !lowerName.includes('date')) {
367
+ return '10:30:00';
368
+ }
369
+ return '2024-01-15';
370
+ }
371
+ if (lowerName.includes('url') || lowerName.includes('link') || lowerName.includes('website')) {
372
+ return 'https://example.com';
373
+ }
374
+ if (lowerName.includes('name')) {
375
+ if (lowerName.includes('first')) {
376
+ return 'John';
377
+ }
378
+ if (lowerName.includes('last')) {
379
+ return 'Doe';
380
+ }
381
+ return 'Sample Name';
382
+ }
383
+ if (lowerName.includes('address')) {
384
+ return '123 Main Street';
385
+ }
386
+ if (lowerName.includes('city')) {
387
+ return 'New York';
388
+ }
389
+ if (lowerName.includes('country')) {
390
+ return 'USA';
391
+ }
392
+ if (lowerName.includes('description') ||
393
+ lowerName.includes('comment') ||
394
+ lowerName.includes('notes')) {
395
+ return 'Sample description';
396
+ }
397
+ if (lowerName.includes('id') || lowerName.includes('code')) {
398
+ return 'ABC123';
399
+ }
400
+ // Check for minLength/maxLength constraints
401
+ const minLen = fieldSchema.minLength || 0;
402
+ const DEFAULT_MAX_LENGTH = 50;
403
+ const maxLen = fieldSchema.maxLength || DEFAULT_MAX_LENGTH;
404
+ let sample = `Sample ${fieldName}`;
405
+ if (sample.length < minLen) {
406
+ sample = sample.padEnd(minLen, '_');
407
+ }
408
+ if (sample.length > maxLen) {
409
+ sample = sample.substring(0, maxLen);
410
+ }
411
+ return sample;
412
+ }
413
+ /**
414
+ * Generates a sample integer value based on schema constraints.
415
+ * @param fieldSchema - The JSON schema for this field
416
+ * @returns A sample integer string
417
+ */
418
+ function generateIntegerSample(fieldSchema) {
419
+ var _a, _b;
420
+ const min = (_a = fieldSchema.minimum) !== null && _a !== void 0 ? _a : 0;
421
+ const max = (_b = fieldSchema.maximum) !== null && _b !== void 0 ? _b : 100;
422
+ // Use minimum if specified, otherwise use a value in range
423
+ if (fieldSchema.minimum !== undefined) {
424
+ return String(Math.ceil(min));
425
+ }
426
+ // Return a sensible default within range
427
+ const value = Math.min(Math.max(1, min), max);
428
+ return String(Math.floor(value));
429
+ }
430
+ /**
431
+ * Generates a sample number value based on schema constraints.
432
+ * @param fieldSchema - The JSON schema for this field
433
+ * @returns A sample number string
434
+ */
435
+ function generateNumberSample(fieldSchema) {
436
+ var _a, _b;
437
+ const min = (_a = fieldSchema.minimum) !== null && _a !== void 0 ? _a : 0;
438
+ const max = (_b = fieldSchema.maximum) !== null && _b !== void 0 ? _b : 100;
439
+ // Use minimum if specified, otherwise use a value in range
440
+ if (fieldSchema.minimum !== undefined) {
441
+ return String(min);
442
+ }
443
+ // Return a sensible default within range
444
+ const DEFAULT_NUMERIC_VALUE = 1.5;
445
+ const value = Math.min(Math.max(DEFAULT_NUMERIC_VALUE, min), max);
446
+ return String(value);
447
+ }
448
+ /**
449
+ * Escapes a value for CSV output.
450
+ * Values containing commas, quotes, or newlines are wrapped in quotes.
451
+ * Quotes within values are escaped by doubling them.
452
+ * @param value - The value to escape
453
+ * @returns The escaped value
454
+ */
455
+ function escapeCsvValue(value) {
456
+ if (!value) {
457
+ return '';
458
+ }
459
+ // Check if escaping is needed
460
+ const needsEscaping = value.includes(',') || value.includes('"') || value.includes('\n') || value.includes('\r');
461
+ if (needsEscaping) {
462
+ // Escape quotes by doubling them and wrap in quotes
463
+ const escaped = value.replace(/"/g, '""');
464
+ return `"${escaped}"`;
465
+ }
466
+ return value;
467
+ }
468
+ /**
469
+ * Triggers a download of the CSV template file in the browser.
470
+ * @param csvContent - The CSV content to download
471
+ * @param fileName - The name for the downloaded file (default: 'template.csv')
472
+ * @remarks Browser-only; uses document and URL.createObjectURL. Will throw in Node.js.
473
+ * @public
474
+ */
475
+ export function downloadCsvTemplate(csvContent, fileName = 'template.csv') {
476
+ const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
477
+ const url = URL.createObjectURL(blob);
478
+ const link = document.createElement('a');
479
+ link.href = url;
480
+ link.download = fileName;
481
+ link.style.display = 'none';
482
+ document.body.appendChild(link);
483
+ link.click();
484
+ document.body.removeChild(link);
485
+ URL.revokeObjectURL(url);
486
+ }
@@ -1,3 +1,4 @@
1
+ export * from './csv-parser';
1
2
  export * from './filters';
2
3
  export * from './logger';
3
4
  export * from './translation';
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Extracts the field name from a JSON schema scope string.
3
+ * @param scope - The scope string (e.g. "#/properties/FIELD_NAME" or "#/properties/obj/properties/FIELD")
4
+ * @returns The field name or null if not a valid scope
5
+ * @internal
6
+ */
7
+ function extractFieldNameFromScope(scope) {
8
+ const match = scope.match(/^#\/properties\/(.+)$/);
9
+ return match ? match[1] : null;
10
+ }
11
+ /**
12
+ * Recursively extracts Control elements from a UI schema in display order.
13
+ * Used for grid-style array layouts where labels are shown only in the header row.
14
+ *
15
+ * @param uiSchema - The UI schema to extract controls from
16
+ * @returns Array of control info with uischema, label, and field name
17
+ * @internal
18
+ */
19
+ export function extractControlsFromUiSchema(uiSchema) {
20
+ var _a;
21
+ const controls = [];
22
+ function processElement(element) {
23
+ var _a, _b, _c;
24
+ if (element.type === 'Control' && element.scope) {
25
+ const fieldName = extractFieldNameFromScope(element.scope);
26
+ if (fieldName) {
27
+ const isHidden = ((_a = element.options) === null || _a === void 0 ? void 0 : _a.hidden) === true;
28
+ if (!isHidden) {
29
+ const label = (_b = element.label) !== null && _b !== void 0 ? _b : fieldName;
30
+ controls.push({
31
+ uischema: element,
32
+ label: String(label),
33
+ fieldName,
34
+ });
35
+ }
36
+ }
37
+ }
38
+ if (element.elements) {
39
+ for (const child of element.elements) {
40
+ processElement(child);
41
+ }
42
+ }
43
+ if ((_c = element.options) === null || _c === void 0 ? void 0 : _c.childElements) {
44
+ for (const child of element.options.childElements) {
45
+ processElement(child);
46
+ }
47
+ }
48
+ }
49
+ if (uiSchema.elements) {
50
+ for (const element of uiSchema.elements) {
51
+ processElement(element);
52
+ }
53
+ }
54
+ if ((_a = uiSchema.options) === null || _a === void 0 ? void 0 : _a.childElements) {
55
+ for (const element of uiSchema.options.childElements) {
56
+ processElement(element);
57
+ }
58
+ }
59
+ return controls;
60
+ }
61
+ /**
62
+ * Creates a deep copy of the UI schema with gridView option set to true on all Control elements.
63
+ * Used for grid-style array layouts so child controls can adapt their behavior.
64
+ *
65
+ * @param uiSchema - The UI schema to modify
66
+ * @returns A new UI schema with gridView option on all controls
67
+ * @internal
68
+ */
69
+ export function withGridView(uiSchema) {
70
+ const clone = structuredClone(uiSchema);
71
+ function processElement(element) {
72
+ var _a;
73
+ if ((element === null || element === void 0 ? void 0 : element.type) === 'Control') {
74
+ element.options = Object.assign(Object.assign({}, element.options), { gridView: true });
75
+ }
76
+ if (element === null || element === void 0 ? void 0 : element.elements) {
77
+ element.elements.forEach(processElement);
78
+ }
79
+ if ((_a = element === null || element === void 0 ? void 0 : element.options) === null || _a === void 0 ? void 0 : _a.childElements) {
80
+ element.options.childElements.forEach(processElement);
81
+ }
82
+ }
83
+ processElement(clone);
84
+ return clone;
85
+ }
86
+ /**
87
+ * Checks if the provided UI schema is already configured for bulk insert mode.
88
+ * @param uischema - The UI schema to check
89
+ * @returns true if the schema has a Control element for #/properties/items
90
+ * @internal
91
+ */
92
+ export function isBulkUiSchema(uischema) {
93
+ var _a;
94
+ return (_a = uischema === null || uischema === void 0 ? void 0 : uischema.elements) === null || _a === void 0 ? void 0 : _a.some((element) => element.scope === '#/properties/items' && element.type === 'Control');
95
+ }
96
+ /**
97
+ * Generates a UI schema suitable for bulk insert mode with array control.
98
+ * @param userProvidedUiSchema - Optional user-provided UI schema to use as childUiSchema
99
+ * @returns A UI schema configured for bulk insert
100
+ * @internal
101
+ */
102
+ export function generateBulkUiSchema(userProvidedUiSchema) {
103
+ const options = {
104
+ addLabel: 'Add another',
105
+ deleteLabel: '',
106
+ };
107
+ if (userProvidedUiSchema) {
108
+ options.childUiSchema = userProvidedUiSchema;
109
+ }
110
+ return {
111
+ type: 'VerticalLayout',
112
+ elements: [
113
+ {
114
+ type: 'Control',
115
+ scope: '#/properties/items',
116
+ options,
117
+ },
118
+ ],
119
+ };
120
+ }
@@ -48,6 +48,8 @@ const describeError = (prop, parentProps) => {
48
48
  /**
49
49
  * Computes a human-friendly anyOf error message for a given control path.
50
50
  * Prefers UI schema custom message, then JSON schema errorMessage.anyOf, then a constructed fallback.
51
+ *
52
+ * @public
51
53
  */
52
54
  export const getAnyOfErrorMessage = (errors, schema, controlPath, uiCustomMsg) => {
53
55
  var _a, _b, _c;