@vuu-ui/vuu-table-extras 3.3.12 → 3.3.14

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/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "3.3.12",
2
+ "version": "3.3.14",
3
3
  "author": "heswell",
4
4
  "main": "./src/index.js",
5
5
  "license": "Apache-2.0",
@@ -11,19 +11,19 @@
11
11
  "lezer-generate:column": "lezer-generator --output ./src/column-expression-input/column-language-parser/generated/column-parser.js ./src/column-expression-input/column-language-parser/grammar/column.grammar"
12
12
  },
13
13
  "devDependencies": {
14
- "@vuu-ui/vuu-filter-types": "3.3.12",
15
- "@vuu-ui/vuu-protocol-types": "3.3.12"
14
+ "@vuu-ui/vuu-filter-types": "3.3.14",
15
+ "@vuu-ui/vuu-protocol-types": "3.3.14"
16
16
  },
17
17
  "dependencies": {
18
- "@vuu-ui/vuu-codemirror": "3.3.12",
19
- "@vuu-ui/vuu-data-editing": "3.3.12",
20
- "@vuu-ui/vuu-data-react": "3.3.12",
21
- "@vuu-ui/vuu-data-types": "3.3.12",
22
- "@vuu-ui/vuu-table-types": "3.3.12",
23
- "@vuu-ui/vuu-popups": "3.3.12",
24
- "@vuu-ui/vuu-table": "3.3.12",
25
- "@vuu-ui/vuu-utils": "3.3.12",
26
- "@vuu-ui/vuu-ui-controls": "3.3.12",
18
+ "@vuu-ui/vuu-codemirror": "3.3.14",
19
+ "@vuu-ui/vuu-data-editing": "3.3.14",
20
+ "@vuu-ui/vuu-data-react": "3.3.14",
21
+ "@vuu-ui/vuu-data-types": "3.3.14",
22
+ "@vuu-ui/vuu-table-types": "3.3.14",
23
+ "@vuu-ui/vuu-popups": "3.3.14",
24
+ "@vuu-ui/vuu-table": "3.3.14",
25
+ "@vuu-ui/vuu-utils": "3.3.14",
26
+ "@vuu-ui/vuu-ui-controls": "3.3.14",
27
27
  "@lezer/lr": "1.4.2",
28
28
  "@salt-ds/core": "1.54.1",
29
29
  "@salt-ds/lab": "1.0.0-alpha.83",
@@ -15,6 +15,7 @@ const isColumnAdded = (change)=>change?.type === SelectedColumnChangeType.Column
15
15
  const isColumnRemoved = (change)=>change?.type === SelectedColumnChangeType.ColumnRemoved;
16
16
  const isColumnUpdated = (change)=>change?.type === SelectedColumnChangeType.ColumnUpdated;
17
17
  const isColumnsReordered = (change)=>change?.type === SelectedColumnChangeType.ColumnsReordered;
18
+ const columnDescriptorComparator = (col1, col2)=>col1.name === col2.name;
18
19
  class ColumnModel extends EventEmitter {
19
20
  allColumns;
20
21
  #selectedColumns;
@@ -36,10 +37,10 @@ class ColumnModel extends EventEmitter {
36
37
  }
37
38
  addRemoveOrReorderSelectedColumns(newSelectedColumns, source) {
38
39
  if (!itemsOrOrderChanged(this.#selectedColumns, newSelectedColumns)) throw Error("[ColumnModel] addRemoveOrReorderSelectedColumns no change detected between current and new selected columns");
39
- const addedColumns = getAddedItems(this.#selectedColumns, newSelectedColumns);
40
+ const addedColumns = getAddedItems(this.#selectedColumns, newSelectedColumns, columnDescriptorComparator);
40
41
  if (addedColumns.length > 0) if (1 == addedColumns.length) return void this.addItemToSelectedColumns(addedColumns[0].name, source);
41
42
  else throw Error("[ColumnModel] addRemoveOrReorderSelectedColumns attempt to add multiple selected columns in a single call");
42
- const removedColumns = getRemovedItems(this.#selectedColumns, newSelectedColumns);
43
+ const removedColumns = getRemovedItems(this.#selectedColumns, newSelectedColumns, columnDescriptorComparator);
43
44
  if (removedColumns.length > 0) if (1 == removedColumns.length) return void this.removeItemFromSelectedColumns(removedColumns[0].name, source);
44
45
  else throw Error("[ColumnModel] addRemoveOrReorderSelectedColumns attempt to remove multiple selected columns in a single call");
45
46
  this.reorderSelectedColumns(newSelectedColumns.map(({ name })=>name), source);
@@ -14,6 +14,40 @@ const css = `
14
14
  display: grid;
15
15
  }
16
16
 
17
+ .vuuCsvUpload-errorContainer {
18
+ margin-top: var(--salt-spacing-100);
19
+ width: 100%;
20
+ max-width: 480px;
21
+ max-height: 120px;
22
+ padding: var(--salt-spacing-100) var(--salt-spacing-200);
23
+ background-color: rgba(var(--salt-palette-interact-error-background), .05);
24
+ border-radius: var(--salt-size-borderRadius);
25
+ text-align: left;
26
+ overflow-y: auto;
27
+ }
28
+
29
+ .vuuCsvUpload-errorList {
30
+ padding-left: var(--salt-spacing-200);
31
+ margin: 0;
32
+ list-style-type: disc;
33
+ }
34
+
35
+ .vuuCsvUpload-errorItem {
36
+ font-size: var(--salt-text-fontSize-small);
37
+ color: var(--salt-status-error-foreground);
38
+ line-height: var(--salt-text-lineHeight-small);
39
+ word-break: break-word;
40
+ margin-bottom: var(--salt-spacing-50);
41
+ }
42
+
43
+ .vuuCsvUpload-importErrorItem {
44
+ text-align: center;
45
+ }
46
+
47
+ .vuuCsvUpload-rectifyPrompt {
48
+ margin-top: var(--salt-spacing-100);
49
+ }
50
+
17
51
  .vuuCsvUpload-errors {
18
52
  border: solid 1px var(--salt-status-error-borderColor);
19
53
  border-radius: var(--salt-size-borderRadius);
@@ -2,12 +2,12 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
2
  import { Button, Dialog, DialogActions, DialogContent, DialogHeader, FileDropZone, FileDropZoneIcon, FileDropZoneTrigger, Text } from "@salt-ds/core";
3
3
  import { useComponentCssInjection } from "@salt-ds/styles";
4
4
  import { useWindow } from "@salt-ds/window";
5
- import { useCallback, useState } from "react";
5
+ import { useCallback, useMemo, useState } from "react";
6
6
  import { useCsvUpload } from "./useCsvUpload.js";
7
7
  import CsvUpload from "./CsvUpload.css";
8
8
  const classBase = "vuuCsvUpload";
9
9
  const CsvUpload_CsvUpload = (props)=>{
10
- const { children, dialogTitle = "Import CSV", embedded = false, onCancel, onClose, open, renderError } = props;
10
+ const { children, dialogTitle = "Import CSV", embedded = false, onCancel, onClose, open } = props;
11
11
  const isControlledOpen = void 0 !== open;
12
12
  const [internalOpen, setInternalOpen] = useState(open ?? true);
13
13
  const dialogOpen = isControlledOpen ? open : internalOpen;
@@ -18,7 +18,23 @@ const CsvUpload_CsvUpload = (props)=>{
18
18
  window: targetWindow
19
19
  });
20
20
  const { canImport, cancelImport, isProcessingFile, isImporting, importData, onDrop, onTriggerChange, schema, validation, error } = useCsvUpload(props);
21
- const fileErrors = validation?.errors.filter((e)=>e.column in validation.errorMap.fileErrors) ?? [];
21
+ const errorsToRender = useMemo(()=>{
22
+ if (validation?.errors && validation.errors.length > 0) return validation.errors;
23
+ const parseErrors = error?.errors.schemaError?.parseError?.errors || error?.errors.validationError?.parseError?.errors;
24
+ if (parseErrors && parseErrors.length > 0) return parseErrors;
25
+ return [];
26
+ }, [
27
+ validation,
28
+ error
29
+ ]);
30
+ const parsedSchemaErrors = useMemo(()=>{
31
+ const schemaErrorMsg = error?.errors.schemaError?.message || error?.errors.validationError?.message;
32
+ if (!schemaErrorMsg) return [];
33
+ const cleanedMsg = schemaErrorMsg.replace(/^CSV validation failed:\s*/, "");
34
+ return cleanedMsg.split(";").map((item)=>item.trim()).filter((item)=>item.length > 0);
35
+ }, [
36
+ error
37
+ ]);
22
38
  const hasErrors = !!error || validation && validation.errors.length > 0;
23
39
  const isUploaded = canImport || isImporting;
24
40
  const status = hasErrors ? "error" : isUploaded ? "success" : void 0;
@@ -37,73 +53,101 @@ const CsvUpload_CsvUpload = (props)=>{
37
53
  importData,
38
54
  onClose
39
55
  ]);
40
- const content = /*#__PURE__*/ jsxs("div", {
41
- className: classBase,
42
- children: [
43
- /*#__PURE__*/ jsx(FileDropZone, {
44
- className: `${classBase}-dropZone`,
45
- disabled: void 0 === schema || isProcessingFile || isImporting,
46
- onDrop: onDrop,
47
- status: status,
48
- children: isUploaded ? /*#__PURE__*/ jsxs(Fragment, {
49
- children: [
50
- /*#__PURE__*/ jsx(FileDropZoneIcon, {
51
- status: "success"
52
- }),
53
- /*#__PURE__*/ jsx("strong", {
54
- children: "Upload completed"
55
- })
56
- ]
57
- }) : /*#__PURE__*/ jsxs(Fragment, {
58
- children: [
59
- /*#__PURE__*/ jsx(FileDropZoneIcon, {
60
- status: hasErrors ? "error" : void 0
61
- }),
62
- validation && validation.errors.length > 0 ? /*#__PURE__*/ jsxs(Fragment, {
63
- children: [
64
- /*#__PURE__*/ jsx("div", {
65
- children: "Your file contains errors"
66
- }),
67
- fileErrors.length > 0 && /*#__PURE__*/ jsx("ul", {
68
- className: `${classBase}-errorList`,
69
- children: fileErrors.map((error, i)=>/*#__PURE__*/ jsx("li", {
70
- className: `${classBase}-errorItem`,
71
- children: error.message
72
- }, `${error.column}-${error.message}-${i}`))
73
- }),
74
- /*#__PURE__*/ jsx("div", {
75
- children: "Please rectify and reupload"
76
- })
77
- ]
78
- }) : /*#__PURE__*/ jsx("div", {
79
- children: "Drop a file here or"
80
- }),
81
- /*#__PURE__*/ jsx(FileDropZoneTrigger, {
82
- accept: ".csv,text/csv",
83
- onChange: onTriggerChange,
84
- children: "BROWSE FILES"
85
- }),
86
- /*#__PURE__*/ jsx(Text, {
87
- children: "Only .csv files"
88
- }),
89
- children
90
- ]
56
+ const renderErrorsContent = useMemo(()=>{
57
+ if (error?.errors.importError) return /*#__PURE__*/ jsxs(Fragment, {
58
+ children: [
59
+ /*#__PURE__*/ jsx("strong", {
60
+ children: "Import failed"
61
+ }),
62
+ /*#__PURE__*/ jsx("div", {
63
+ className: `${classBase}-errorContainer`,
64
+ children: /*#__PURE__*/ jsx("div", {
65
+ className: `${classBase}-errorItem ${classBase}-importErrorItem`,
66
+ children: error.errors.importError.message
67
+ })
91
68
  })
92
- }),
93
- error && (renderError ? renderError(error) : /*#__PURE__*/ jsxs("div", {
94
- className: `${classBase}-errors`,
69
+ ]
70
+ });
71
+ if (hasErrors) {
72
+ const headingText = error ? "Validation failed" : "Your file contains errors";
73
+ return /*#__PURE__*/ jsxs(Fragment, {
95
74
  children: [
75
+ /*#__PURE__*/ jsx("strong", {
76
+ children: headingText
77
+ }),
96
78
  /*#__PURE__*/ jsx("div", {
97
- children: /*#__PURE__*/ jsx("strong", {
98
- children: "Import Error:"
79
+ className: `${classBase}-errorContainer`,
80
+ children: errorsToRender.length > 0 ? /*#__PURE__*/ jsx("ul", {
81
+ className: `${classBase}-errorList`,
82
+ children: errorsToRender.map((error, i)=>/*#__PURE__*/ jsxs("li", {
83
+ className: `${classBase}-errorItem`,
84
+ children: [
85
+ error.rowNum > 0 ? `Row ${error.rowNum}: ` : "",
86
+ "*" === error.column ? "" : `${error.column}: `,
87
+ error.message
88
+ ]
89
+ }, `${error.column}-${error.message}-${i}`))
90
+ }) : parsedSchemaErrors.length > 0 ? /*#__PURE__*/ jsx("ul", {
91
+ className: `${classBase}-errorList`,
92
+ children: parsedSchemaErrors.map((errMsg)=>/*#__PURE__*/ jsx("li", {
93
+ className: `${classBase}-errorItem`,
94
+ children: errMsg
95
+ }, errMsg))
96
+ }) : /*#__PURE__*/ jsx("div", {
97
+ className: `${classBase}-errorItem ${classBase}-importErrorItem`,
98
+ children: error?.errors.schemaError?.message || error?.errors.validationError?.message || "An unexpected error occurred during import"
99
99
  })
100
+ })
101
+ ]
102
+ });
103
+ }
104
+ return /*#__PURE__*/ jsx("div", {
105
+ children: "Drop a file here or"
106
+ });
107
+ }, [
108
+ error,
109
+ hasErrors,
110
+ errorsToRender,
111
+ parsedSchemaErrors
112
+ ]);
113
+ const content = /*#__PURE__*/ jsx("div", {
114
+ className: classBase,
115
+ children: /*#__PURE__*/ jsx(FileDropZone, {
116
+ className: `${classBase}-dropZone`,
117
+ disabled: void 0 === schema || isProcessingFile || isImporting,
118
+ onDrop: onDrop,
119
+ status: status,
120
+ children: isUploaded ? /*#__PURE__*/ jsxs(Fragment, {
121
+ children: [
122
+ /*#__PURE__*/ jsx(FileDropZoneIcon, {
123
+ status: "success"
100
124
  }),
101
- /*#__PURE__*/ jsx("div", {
102
- children: error.errors.importError?.message || error.errors.schemaError?.message || error.errors.validationError?.message || "An unexpected error occurred during import."
125
+ /*#__PURE__*/ jsx("strong", {
126
+ children: "Upload completed"
103
127
  })
104
128
  ]
105
- }))
106
- ]
129
+ }) : /*#__PURE__*/ jsxs(Fragment, {
130
+ children: [
131
+ /*#__PURE__*/ jsx(FileDropZoneIcon, {
132
+ status: hasErrors ? "error" : void 0
133
+ }),
134
+ renderErrorsContent,
135
+ hasErrors && /*#__PURE__*/ jsx(Text, {
136
+ className: `${classBase}-rectifyPrompt`,
137
+ children: "Please rectify and reupload"
138
+ }),
139
+ /*#__PURE__*/ jsx(FileDropZoneTrigger, {
140
+ accept: ".csv,text/csv",
141
+ onChange: onTriggerChange,
142
+ children: "BROWSE FILES"
143
+ }),
144
+ /*#__PURE__*/ jsx(Text, {
145
+ children: "Only .csv files"
146
+ }),
147
+ children
148
+ ]
149
+ })
150
+ })
107
151
  });
108
152
  const actions = /*#__PURE__*/ jsxs(DialogActions, {
109
153
  children: [
@@ -68,7 +68,7 @@ const parseCsv = (csvText, options)=>{
68
68
  }
69
69
  const rows = records.slice(1);
70
70
  rows.forEach((row, rowIndex)=>{
71
- if (row.length !== header.length) addCsvRowError(errorState, rowIndex + CSV_FIRST_DATA_ROW_NUMBER, "*", CsvParseErrorEnum.ROW_COLUMN_COUNT_MISMATCH, "CSV rows must have the same number of columns as the header.", row.join(","));
71
+ if (row.length !== header.length) addCsvRowError(errorState, rowIndex + CSV_FIRST_DATA_ROW_NUMBER, "*", CsvParseErrorEnum.ROW_COLUMN_COUNT_MISMATCH, "This row has too many or too few columns compared to the header line. Check for extra or missing commas.", row.join(","));
72
72
  });
73
73
  if (hasCsvErrors(errorState.errorMap)) return withError("CSV rows must have the same number of columns as the header.", errorState.errorMap, errorState.errors, header, rows);
74
74
  return {
@@ -4,6 +4,40 @@ import { CSV_FIRST_DATA_ROW_NUMBER, MAX_ROWS_IN_CSV } from "./csv-constants.js";
4
4
  const INTERNAL_KEY_COLUMNS = new Set([
5
5
  "vuuRowNum"
6
6
  ]);
7
+ const getFriendlyTypeName = (type)=>{
8
+ switch(type){
9
+ case "int":
10
+ case "long":
11
+ return "whole number";
12
+ case "double":
13
+ case "number":
14
+ case "decimal":
15
+ case "scaleddecimal":
16
+ case "scaleddecimal2":
17
+ case "scaleddecimal4":
18
+ case "scaleddecimal6":
19
+ case "scaleddecimal8":
20
+ return "decimal or number";
21
+ case "time":
22
+ return "time in 'HH:MM:SS' format";
23
+ case "epochtimestamp":
24
+ case "epochtimestampnano":
25
+ case "date/time":
26
+ return "date/time";
27
+ case "char":
28
+ return "single character";
29
+ case "boolean":
30
+ return "boolean (true/false)";
31
+ case "json":
32
+ return "JSON value";
33
+ default:
34
+ return type;
35
+ }
36
+ };
37
+ const getFriendlyErrorMessage = (errMessage, rawValue, toType)=>{
38
+ if (errMessage.includes("is not a valid")) return `'${rawValue}' is not a valid ${getFriendlyTypeName(toType)}`;
39
+ return errMessage;
40
+ };
7
41
  const validateCsvAgainstSchema = (parsed, tableSchema, options)=>{
8
42
  const schemaColumns = new Map(tableSchema.columns.map((col)=>[
9
43
  col.name,
@@ -21,22 +55,22 @@ const validateCsvAgainstSchema = (parsed, tableSchema, options)=>{
21
55
  const rowNum = rowIndex + CSV_FIRST_DATA_ROW_NUMBER;
22
56
  const typedRow = {};
23
57
  const rawRow = parsed.header.reduce((acc, headerCol, idx)=>{
24
- acc[headerCol] = rowValues[idx];
58
+ acc[headerCol] = rowValues[idx] ?? "";
25
59
  return acc;
26
60
  }, {});
27
61
  parsed.header.forEach((columnName, columnIndex)=>{
28
- const rawValue = rowValues[columnIndex];
62
+ const rawValue = rowValues[columnIndex] ?? "";
29
63
  const schemaType = schemaColumns.get(columnName);
30
- if (0 === rawValue.length && "string" !== schemaType) return void addCsvRowError(errorState, rowNum, columnName, CsvValidationErrorEnum.EMPTY_NON_STRING_VALUE, "Empty value is not allowed for non-string columns.", rawValue);
64
+ if (0 === rawValue.length && "string" !== schemaType) return void addCsvRowError(errorState, rowNum, columnName, CsvValidationErrorEnum.EMPTY_NON_STRING_VALUE, "This field is required and cannot be left blank", rawValue);
31
65
  let value;
32
66
  const toType = schemaType ?? "string";
33
67
  try {
34
68
  value = getTypedValue(rawValue, toType);
35
69
  } catch (err) {
36
- addCsvRowError(errorState, rowNum, columnName, CsvValidationErrorEnum.TYPE_MISMATCH, err instanceof Error ? err.message : String(err), rawValue);
70
+ addCsvRowError(errorState, rowNum, columnName, CsvValidationErrorEnum.TYPE_MISMATCH, getFriendlyErrorMessage(err instanceof Error ? err.message : String(err), rawValue, toType), rawValue);
37
71
  return;
38
72
  }
39
- if (void 0 === value) return void addCsvRowError(errorState, rowNum, columnName, CsvValidationErrorEnum.TYPE_MISMATCH, `Value '${rawValue}' is not a valid ${toType}`, rawValue);
73
+ if (void 0 === value) return void addCsvRowError(errorState, rowNum, columnName, CsvValidationErrorEnum.TYPE_MISMATCH, `'${rawValue}' is not a valid ${getFriendlyTypeName(toType)}`, rawValue);
40
74
  const customValidator = options?.validators?.[columnName];
41
75
  if (customValidator) {
42
76
  const validationResult = customValidator(rawValue, columnName, rawRow);
@@ -5,7 +5,7 @@ import { parseCsv } from "./parse/csv-parse.js";
5
5
  import { validateCsvAgainstSchema } from "./parse/csv-schema-validation.js";
6
6
  import { buildRowErrorMessage, createUploadError, hasFileParseErrors, isCsvParseError, mergeValidationWithParseErrors, toErrorMessage } from "./parse/csv-upload-utils.js";
7
7
  import { CSV_FIRST_DATA_ROW_NUMBER } from "./parse/csv-constants.js";
8
- const useCsvUpload = ({ dataSource, importMode = "direct", importSchema, importTable, onImportSessionEnded, onImportSessionStarted, onError, onImported, onPreview, onProcessingStarted, maxRows, parseOptions, rowDefaults, validators })=>{
8
+ const useCsvUpload = ({ dataSource, importMode = "direct", importSchema, importTable, onImportSessionEnded, onImportSessionReady, onError, onImported, onPreview, onProcessingStarted, maxRows, parseOptions, rowDefaults, validators })=>{
9
9
  const { getServerAPI } = useData();
10
10
  const [validation, setValidation] = useState();
11
11
  const [sessionTable, setSessionTable] = useState();
@@ -120,7 +120,7 @@ const useCsvUpload = ({ dataSource, importMode = "direct", importSchema, importT
120
120
  try {
121
121
  await editSession.end(save);
122
122
  } finally{
123
- if ("unsubscribed" !== sessionDataSource.status) sessionDataSource.unsubscribe();
123
+ if ("unsubscribed" !== sessionDataSource.status && "function" == typeof sessionDataSource.unsubscribe) sessionDataSource.unsubscribe();
124
124
  setActiveSessionDataSource(void 0);
125
125
  }
126
126
  onImportSessionEnded?.({
@@ -140,7 +140,7 @@ const useCsvUpload = ({ dataSource, importMode = "direct", importSchema, importT
140
140
  if (rowNum < CSV_FIRST_DATA_ROW_NUMBER) continue;
141
141
  const existing = vuuMsgByRow.get(rowNum);
142
142
  const columnError = `${column}: ${message}`;
143
- vuuMsgByRow.set(rowNum, existing ? `${existing}; ${columnError}` : `${0 === rowNum ? "Header" : `Row ${rowNum}`}: ${columnError}`);
143
+ vuuMsgByRow.set(rowNum, existing ? `${existing}; ${columnError}` : columnError);
144
144
  }
145
145
  const parsedRowCount = mergedValidation.rows.length;
146
146
  const unparsedErrorRows = [
@@ -187,11 +187,9 @@ const useCsvUpload = ({ dataSource, importMode = "direct", importSchema, importT
187
187
  const sessionVuuTable = sessionDataSource?.table;
188
188
  if (void 0 === sessionDataSource || void 0 === sessionVuuTable || !isSessionTable(sessionVuuTable)) throw Error("CsvUpload createSessionDataSource returned no session datasource.");
189
189
  setActiveSessionDataSource(sessionDataSource);
190
- onImportSessionStarted?.(sessionDataSource);
191
190
  return sessionDataSource;
192
191
  }, [
193
192
  editSession,
194
- onImportSessionStarted,
195
193
  setActiveSessionDataSource
196
194
  ]);
197
195
  const closePendingEditSession = useCallback(async (save)=>{
@@ -245,13 +243,10 @@ const useCsvUpload = ({ dataSource, importMode = "direct", importSchema, importT
245
243
  });
246
244
  if (Object.keys(schemaValidation.errorMap.fileErrors).length > 0) {
247
245
  setValidation(schemaValidation);
246
+ const detailedMessage = `CSV validation failed: ${schemaValidation.errors.map((e)=>"*" === e.column ? e.message : `${e.column}: ${e.message}`).join("; ")}`;
248
247
  handleError({
249
248
  errors: {
250
- schemaError: createUploadError("schema", "CSV validation failed.", parsedCsv.error, {
251
- errorMap: schemaValidation.errorMap,
252
- errors: schemaValidation.errors,
253
- message: "CSV validation failed."
254
- })
249
+ schemaError: createUploadError("schema", detailedMessage, parsedCsv.error, schemaValidation)
255
250
  }
256
251
  });
257
252
  return;
@@ -259,10 +254,11 @@ const useCsvUpload = ({ dataSource, importMode = "direct", importSchema, importT
259
254
  const mergedValidation = mergeValidationWithParseErrors(schemaValidation, parsedCsv.error);
260
255
  setValidation(mergedValidation);
261
256
  if (mergedValidation.rows.length > 0) try {
262
- await beginEditSession();
257
+ const sessionDataSource = await beginEditSession();
263
258
  if (operationId !== operationIdRef.current) return;
264
259
  const rowsAdded = await addAllRows(mergedValidation, operationId);
265
260
  if (!rowsAdded) return;
261
+ onImportSessionReady?.(sessionDataSource);
266
262
  } catch (error) {
267
263
  if (void 0 !== sessionDataSourceRef.current) await endEditSessionAndNotify(false, "failed");
268
264
  else try {
@@ -290,7 +286,8 @@ const useCsvUpload = ({ dataSource, importMode = "direct", importSchema, importT
290
286
  table,
291
287
  endEditSessionAndNotify,
292
288
  schema,
293
- validators
289
+ validators,
290
+ onImportSessionReady
294
291
  ]);
295
292
  const handleFiles = useCallback(async (files)=>{
296
293
  const file = files[0];
@@ -333,10 +330,11 @@ const useCsvUpload = ({ dataSource, importMode = "direct", importSchema, importT
333
330
  }, [
334
331
  handleFiles
335
332
  ]);
336
- const canImport = useMemo(()=>void 0 !== validation && 0 === validation.errors.length && validation.rows.length > 0 && !isProcessingFile && !isImporting, [
333
+ const canImport = useMemo(()=>void 0 !== validation && 0 === validation.errors.length && validation.rows.length > 0 && !isProcessingFile && !isImporting && void 0 === error, [
337
334
  isImporting,
338
335
  isProcessingFile,
339
- validation
336
+ validation,
337
+ error
340
338
  ]);
341
339
  const importData = useCallback(async ()=>{
342
340
  if (!canImport || void 0 === validation) return false;
@@ -46,7 +46,7 @@ export interface CsvUploadProps {
46
46
  importSchema?: TableSchema;
47
47
  /** Expected import table, used to validate the session table returned by the server. */
48
48
  importTable?: VuuTable;
49
- onImportSessionStarted?: (dataSource: DataSource) => void;
49
+ onImportSessionReady?: (dataSource: DataSource) => void;
50
50
  onImportSessionEnded?: (result: CsvUploadSessionEndResult) => void;
51
51
  onError?: (result: CsvUploadErrorResult | undefined) => void;
52
52
  onImported?: (result: CsvUploadImportedResult) => void;
@@ -61,9 +61,5 @@ export interface CsvUploadProps {
61
61
  importMode?: "direct" | "preview";
62
62
  rowDefaults?: RowDefaultDataItemValues;
63
63
  validators?: Record<string, CsvColumnValidator>;
64
- /**
65
- * Custom renderer for the error panel.
66
- */
67
- renderError?: (error: CsvUploadErrorResult) => ReactNode;
68
64
  }
69
65
  export declare const CsvUpload: (props: CsvUploadProps) => import("react/jsx-runtime").JSX.Element;
@@ -17,7 +17,7 @@ export interface CsvUploadHookProps {
17
17
  importTable?: VuuTable;
18
18
  maxRows?: number;
19
19
  onImportSessionEnded?: (result: CsvUploadSessionEndResult) => void;
20
- onImportSessionStarted?: (dataSource: DataSource) => void;
20
+ onImportSessionReady?: (dataSource: DataSource) => void;
21
21
  onError?: (result: CsvUploadErrorResult | undefined) => void;
22
22
  onImported?: (result: CsvUploadImportedResult) => void;
23
23
  onPreview?: (result: CsvUploadPreviewResult) => void;
@@ -40,4 +40,4 @@ export type UseCsvUploadReturn = {
40
40
  validation: CsvValidationResult | undefined;
41
41
  error: CsvUploadErrorResult | undefined;
42
42
  };
43
- export declare const useCsvUpload: ({ dataSource, importMode, importSchema, importTable, onImportSessionEnded, onImportSessionStarted, onError, onImported, onPreview, onProcessingStarted, maxRows, parseOptions, rowDefaults, validators, }: CsvUploadHookProps) => UseCsvUploadReturn;
43
+ export declare const useCsvUpload: ({ dataSource, importMode, importSchema, importTable, onImportSessionEnded, onImportSessionReady, onError, onImported, onPreview, onProcessingStarted, maxRows, parseOptions, rowDefaults, validators, }: CsvUploadHookProps) => UseCsvUploadReturn;