@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.
- package/dist/custom-elements.json +389 -4
- package/dist/dts/form.d.ts +100 -1
- package/dist/dts/form.d.ts.map +1 -1
- package/dist/dts/form.styles.d.ts.map +1 -1
- package/dist/dts/form.template.d.ts.map +1 -1
- package/dist/dts/jsonforms/json-forms.d.ts +13 -0
- package/dist/dts/jsonforms/json-forms.d.ts.map +1 -1
- package/dist/dts/jsonforms/renderers/ArrayListWrapperRenderer.d.ts +5 -0
- package/dist/dts/jsonforms/renderers/ArrayListWrapperRenderer.d.ts.map +1 -1
- package/dist/dts/jsonforms/renderers/BooleanControlRenderer.d.ts.map +1 -1
- package/dist/dts/jsonforms/renderers/ConnectedMultiselectControlRenderer.d.ts.map +1 -1
- package/dist/dts/jsonforms/renderers/ControlWrapperRenderer.d.ts.map +1 -1
- package/dist/dts/jsonforms/renderers/EnumControlRenderer.d.ts.map +1 -1
- package/dist/dts/jsonforms/renderers/LayoutFormGridRenderer.d.ts +3 -0
- package/dist/dts/jsonforms/renderers/LayoutFormGridRenderer.d.ts.map +1 -0
- package/dist/dts/jsonforms/renderers/RenderersRanks.d.ts +1 -0
- package/dist/dts/jsonforms/renderers/RenderersRanks.d.ts.map +1 -1
- package/dist/dts/jsonforms/testers/isOneOfOptionMultiselect.d.ts.map +1 -1
- package/dist/dts/types.d.ts +89 -2
- package/dist/dts/types.d.ts.map +1 -1
- package/dist/dts/utils/csv-parser.d.ts +85 -0
- package/dist/dts/utils/csv-parser.d.ts.map +1 -0
- package/dist/dts/utils/index.d.ts +1 -0
- package/dist/dts/utils/index.d.ts.map +1 -1
- package/dist/dts/utils/schema-utils.d.ts +46 -0
- package/dist/dts/utils/schema-utils.d.ts.map +1 -0
- package/dist/dts/utils/validation.d.ts +2 -0
- package/dist/dts/utils/validation.d.ts.map +1 -1
- package/dist/esm/form.js +423 -5
- package/dist/esm/form.styles.js +41 -1
- package/dist/esm/form.template.js +33 -1
- package/dist/esm/jsonforms/json-forms.js +30 -0
- package/dist/esm/jsonforms/renderers/ArrayListWrapperRenderer.js +223 -22
- package/dist/esm/jsonforms/renderers/BooleanControlRenderer.js +1 -2
- package/dist/esm/jsonforms/renderers/ConnectedMultiselectControlRenderer.js +13 -2
- package/dist/esm/jsonforms/renderers/ControlWrapperRenderer.js +25 -4
- package/dist/esm/jsonforms/renderers/EnumControlRenderer.js +14 -5
- package/dist/esm/jsonforms/renderers/LayoutFormGridRenderer.js +39 -0
- package/dist/esm/jsonforms/renderers/RenderersRanks.js +1 -0
- package/dist/esm/jsonforms/testers/isOneOfOptionMultiselect.js +1 -1
- package/dist/esm/utils/csv-parser.js +486 -0
- package/dist/esm/utils/index.js +1 -0
- package/dist/esm/utils/schema-utils.js +120 -0
- package/dist/esm/utils/validation.js +2 -0
- package/dist/foundation-forms.api.json +1028 -34
- package/dist/foundation-forms.d.ts +285 -2
- package/docs/api/foundation-forms.arrayrendereroptions.md +2 -2
- package/docs/api/foundation-forms.bulkrowstatus.md +22 -0
- package/docs/api/foundation-forms.bulkrowsubmitstatus.md +13 -0
- package/docs/api/foundation-forms.bulksubmitfaileditem.md +20 -0
- package/docs/api/foundation-forms.bulksubmitresult.md +18 -0
- package/docs/api/foundation-forms.bulksubmitsuccessitem.md +17 -0
- package/docs/api/foundation-forms.childuischemaresolver.md +15 -0
- package/docs/api/foundation-forms.csvmappingresult.mappedrows.md +13 -0
- package/docs/api/foundation-forms.csvmappingresult.md +77 -0
- package/docs/api/foundation-forms.csvmappingresult.unmappedcolumns.md +13 -0
- package/docs/api/foundation-forms.csvparseresult.errors.md +13 -0
- package/docs/api/foundation-forms.csvparseresult.headers.md +13 -0
- package/docs/api/foundation-forms.csvparseresult.md +96 -0
- package/docs/api/foundation-forms.csvparseresult.rows.md +13 -0
- package/docs/api/foundation-forms.downloadcsvtemplate.md +74 -0
- package/docs/api/foundation-forms.form.bulkinsert.md +13 -0
- package/docs/api/foundation-forms.form.bulkinsertmaxitems.md +13 -0
- package/docs/api/foundation-forms.form.bulkinsertminitems.md +13 -0
- package/docs/api/foundation-forms.form.clearrowsubmitstatuses.md +17 -0
- package/docs/api/foundation-forms.form.downloadcsvtemplate.md +17 -0
- package/docs/api/foundation-forms.form.handlecsvfileselected.md +54 -0
- package/docs/api/foundation-forms.form.md +132 -0
- package/docs/api/foundation-forms.form.rowsubmitstatuses.md +13 -0
- package/docs/api/foundation-forms.form.submitsinglerow.md +56 -0
- package/docs/api/foundation-forms.generatecsvtemplate.md +104 -0
- package/docs/api/foundation-forms.mapcsvtoschema.md +88 -0
- package/docs/api/foundation-forms.md +147 -0
- package/docs/api/foundation-forms.parsecsv.md +56 -0
- package/docs/api/foundation-forms.uischemaelementtype.md +1 -1
- package/docs/api-report.md.api.md +87 -4
- 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
|
+
}
|
package/dist/esm/utils/index.js
CHANGED
|
@@ -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;
|