@vuu-ui/vuu-table-extras 4.0.0-alpha.9 → 4.0.0-beta.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 (158) hide show
  1. package/package.json +18 -18
  2. package/src/cell-renderers/pct-progress-cell/PctProgressCell.css.js +2 -2
  3. package/src/cell-renderers/progress-cell/ProgressCell.css.js +2 -2
  4. package/src/column-expression-input/ColumnExpressionInput.css.js +5 -5
  5. package/src/column-expression-input/column-language-parser/column-expression-parse-utils.js +1 -1
  6. package/src/column-expression-input/theme.js +1 -1
  7. package/src/column-expression-input/useColumnExpressionEditor.js +1 -0
  8. package/src/column-expression-input/useColumnExpressionSuggestionProvider.js +2 -2
  9. package/src/column-formatting-settings/ColumnFormattingPanel.js +12 -9
  10. package/src/column-formatting-settings/DateTimeFormattingSettings.js +107 -3
  11. package/src/column-formatting-settings/LongTypeFormattingSettings.js +8 -3
  12. package/src/column-picker/ColumnModel.js +3 -2
  13. package/src/column-picker/ColumnPicker.js +1 -3
  14. package/src/column-settings-panel/ColumnNameLabel.css.js +1 -1
  15. package/src/csv-export/export-utils.js +76 -20
  16. package/src/csv-export/index.js +1 -1
  17. package/src/csv-export/useCsvExport.js +9 -1
  18. package/src/csv-upload/CsvUpload.css.js +36 -2
  19. package/src/csv-upload/CsvUpload.js +107 -63
  20. package/src/csv-upload/data-upload-preview/DataUploadPreview.js +1 -1
  21. package/src/csv-upload/parse/csv-errors.js +1 -1
  22. package/src/csv-upload/parse/csv-parse.js +1 -1
  23. package/src/csv-upload/parse/csv-schema-validation.js +46 -7
  24. package/src/csv-upload/parse/csv-upload-utils.js +47 -2
  25. package/src/csv-upload/session/index.js +2 -0
  26. package/src/csv-upload/session/useImportSchema.js +44 -0
  27. package/src/csv-upload/session/useImportSession.js +204 -0
  28. package/src/csv-upload/useCsvUpload.js +54 -199
  29. package/src/freeze-control/FreezeControl.css.js +4 -4
  30. package/src/freeze-control/FrozenBanner.css.js +2 -2
  31. package/src/table-footer/TableFooter.js +3 -3
  32. package/types/calculated-column/CalculatedColumnPanel.d.ts +1 -1
  33. package/types/cell-renderers/background-cell/BackgroundCell.d.ts +1 -1
  34. package/types/cell-renderers/background-cell/BackgroundCellConfigurationEditor.d.ts +1 -1
  35. package/types/cell-renderers/button-cell/IconButtonCell.d.ts +1 -1
  36. package/types/cell-renderers/dropdown-cell/DropdownCell.d.ts +1 -1
  37. package/types/cell-renderers/lookup-cell/LookupCell.d.ts +1 -1
  38. package/types/cell-renderers/pct-progress-cell/PctProgressCell.d.ts +1 -1
  39. package/types/column-expression-input/ColumnExpressionInput.d.ts +1 -1
  40. package/types/column-expression-input/column-language-parser/column-expression-parse-utils.d.ts +1 -1
  41. package/types/column-expression-input/column-language-parser/test.d.mts +1 -1
  42. package/types/column-expression-input/useColumnExpressionEditor.d.ts +4 -4
  43. package/types/column-formatting-settings/BaseNumericFormattingSettings.d.ts +1 -1
  44. package/types/column-formatting-settings/ColumnFormattingPanel.d.ts +1 -1
  45. package/types/column-formatting-settings/DateTimeFormattingSettings.d.ts +1 -1
  46. package/types/column-formatting-settings/LongTypeFormattingSettings.d.ts +1 -2
  47. package/types/column-menu/ColumnMenu.d.ts +1 -1
  48. package/types/column-picker/ColumnPicker.d.ts +4 -4
  49. package/types/column-picker/ColumnPickerAction.d.ts +1 -1
  50. package/types/column-settings-panel/ColumnNameLabel.d.ts +1 -1
  51. package/types/column-settings-panel/ColumnSettingsPanel.d.ts +1 -1
  52. package/types/csv-export/export-utils.d.ts +22 -9
  53. package/types/csv-export/index.d.ts +1 -1
  54. package/types/csv-export/useCsvExport.d.ts +5 -2
  55. package/types/csv-upload/CsvUpload.d.ts +2 -6
  56. package/types/csv-upload/data-upload-preview/DataUploadPreview.d.ts +1 -1
  57. package/types/csv-upload/parse/csv-errors.d.ts +1 -1
  58. package/types/csv-upload/parse/csv-schema-validation.d.ts +8 -2
  59. package/types/csv-upload/parse/csv-upload-utils.d.ts +9 -0
  60. package/types/csv-upload/session/index.d.ts +2 -0
  61. package/types/csv-upload/session/useImportSchema.d.ts +13 -0
  62. package/types/csv-upload/session/useImportSession.d.ts +26 -0
  63. package/types/csv-upload/useCsvUpload.d.ts +3 -3
  64. package/types/datasource-stats/DatasourceStats.d.ts +1 -1
  65. package/types/freeze-control/FreezeControl.d.ts +1 -1
  66. package/types/freeze-control/FrozenBanner.d.ts +1 -1
  67. package/types/inline-add-row/InlineAddRow.d.ts +1 -1
  68. package/types/inline-add-row/useInlineAddRow.d.ts +2 -1
  69. package/types/tabbed-table-config-panel/TabbedTableConfigPanel.d.ts +5 -5
  70. package/types/tabbed-table-config-panel/TabbedTableSettingsAction.d.ts +1 -1
  71. package/types/table-footer/TableFooter.d.ts +4 -4
  72. package/types/table-provider/TableProvider.d.ts +1 -1
  73. package/types/table-settings-panel/TableSettingsPanel.d.ts +1 -1
  74. package/types/src/calculated-column/CalculatedColumnPanel.d.ts +0 -12
  75. package/types/src/calculated-column/useCalculatedColumnPanel.d.ts +0 -10
  76. package/types/src/calculated-column/useEditCalculatedColumn.d.ts +0 -13
  77. package/types/src/cell-edit-validators/CaseValidator.d.ts +0 -2
  78. package/types/src/cell-edit-validators/PatternValidator.d.ts +0 -2
  79. package/types/src/cell-edit-validators/index.d.ts +0 -2
  80. package/types/src/cell-renderers/background-cell/BackgroundCell.d.ts +0 -2
  81. package/types/src/cell-renderers/background-cell/BackgroundCellConfigurationEditor.d.ts +0 -2
  82. package/types/src/cell-renderers/background-cell/index.d.ts +0 -2
  83. package/types/src/cell-renderers/background-cell/useDirection.d.ts +0 -3
  84. package/types/src/cell-renderers/button-cell/IconButtonCell.d.ts +0 -6
  85. package/types/src/cell-renderers/dropdown-cell/DropdownCell.d.ts +0 -2
  86. package/types/src/cell-renderers/dropdown-cell/index.d.ts +0 -1
  87. package/types/src/cell-renderers/index.d.ts +0 -6
  88. package/types/src/cell-renderers/lookup-cell/LookupCell.d.ts +0 -2
  89. package/types/src/cell-renderers/lookup-cell/index.d.ts +0 -1
  90. package/types/src/cell-renderers/pct-progress-cell/PctProgressCell.d.ts +0 -2
  91. package/types/src/cell-renderers/pct-progress-cell/index.d.ts +0 -1
  92. package/types/src/cell-renderers/progress-cell/ProgressCell.d.ts +0 -1
  93. package/types/src/cell-renderers/progress-cell/index.d.ts +0 -1
  94. package/types/src/column-expression-input/ColumnExpressionInput.d.ts +0 -10
  95. package/types/src/column-expression-input/column-function-descriptors.d.ts +0 -15
  96. package/types/src/column-expression-input/column-language-parser/ColumnExpressionLanguage.d.ts +0 -2
  97. package/types/src/column-expression-input/column-language-parser/ColumnExpressionTreeWalker.d.ts +0 -57
  98. package/types/src/column-expression-input/column-language-parser/column-expression-parse-utils.d.ts +0 -5
  99. package/types/src/column-expression-input/column-language-parser/generated/column-parser.d.ts +0 -2
  100. package/types/src/column-expression-input/column-language-parser/generated/column-parser.terms.d.ts +0 -1
  101. package/types/src/column-expression-input/column-language-parser/index.d.ts +0 -3
  102. package/types/src/column-expression-input/column-language-parser/test.d.mts +0 -1
  103. package/types/src/column-expression-input/functionDocInfo.d.ts +0 -2
  104. package/types/src/column-expression-input/highlighting.d.ts +0 -1
  105. package/types/src/column-expression-input/index.d.ts +0 -4
  106. package/types/src/column-expression-input/theme.d.ts +0 -1
  107. package/types/src/column-expression-input/useColumnAutoComplete.d.ts +0 -6
  108. package/types/src/column-expression-input/useColumnExpressionEditor.d.ts +0 -25
  109. package/types/src/column-expression-input/useColumnExpressionSuggestionProvider.d.ts +0 -8
  110. package/types/src/column-formatting-settings/BaseNumericFormattingSettings.d.ts +0 -2
  111. package/types/src/column-formatting-settings/ColumnFormattingPanel.d.ts +0 -9
  112. package/types/src/column-formatting-settings/DateTimeFormattingSettings.d.ts +0 -3
  113. package/types/src/column-formatting-settings/LongTypeFormattingSettings.d.ts +0 -3
  114. package/types/src/column-formatting-settings/index.d.ts +0 -3
  115. package/types/src/column-menu/ColumnMenu.d.ts +0 -10
  116. package/types/src/column-menu/column-action-types.d.ts +0 -11
  117. package/types/src/column-menu/column-menu-utils.d.ts +0 -19
  118. package/types/src/column-menu/useColumnActions.d.ts +0 -16
  119. package/types/src/column-picker/ColumnModel.d.ts +0 -81
  120. package/types/src/column-picker/ColumnPicker.d.ts +0 -10
  121. package/types/src/column-picker/ColumnPickerAction.d.ts +0 -5
  122. package/types/src/column-picker/useColumnPicker.d.ts +0 -16
  123. package/types/src/column-picker/useTableColumnPicker.d.ts +0 -7
  124. package/types/src/column-picker/useTableColumnPickerDeprecated.d.ts +0 -14
  125. package/types/src/column-settings-panel/ColumnNameLabel.d.ts +0 -7
  126. package/types/src/column-settings-panel/ColumnSettingsPanel.d.ts +0 -5
  127. package/types/src/column-settings-panel/useColumnSettings.d.ts +0 -30
  128. package/types/src/csv-export/export-utils.d.ts +0 -42
  129. package/types/src/csv-export/index.d.ts +0 -2
  130. package/types/src/csv-export/useCsvExport.d.ts +0 -14
  131. package/types/src/csv-upload/CsvUpload.d.ts +0 -69
  132. package/types/src/csv-upload/data-upload-preview/DataUploadPreview.d.ts +0 -10
  133. package/types/src/csv-upload/data-upload-preview/useDataUploadPreview.d.ts +0 -17
  134. package/types/src/csv-upload/index.d.ts +0 -7
  135. package/types/src/csv-upload/parse/csv-constants.d.ts +0 -3
  136. package/types/src/csv-upload/parse/csv-errors.d.ts +0 -47
  137. package/types/src/csv-upload/parse/csv-parse.d.ts +0 -11
  138. package/types/src/csv-upload/parse/csv-schema-validation.d.ts +0 -18
  139. package/types/src/csv-upload/parse/csv-upload-utils.d.ts +0 -21
  140. package/types/src/csv-upload/parse/index.d.ts +0 -5
  141. package/types/src/csv-upload/useCsvUpload.d.ts +0 -43
  142. package/types/src/datasource-stats/DatasourceStats.d.ts +0 -17
  143. package/types/src/datasource-stats/useDatasourceStats.d.ts +0 -15
  144. package/types/src/freeze-control/FreezeControl.d.ts +0 -15
  145. package/types/src/freeze-control/FrozenBanner.d.ts +0 -6
  146. package/types/src/freeze-control/freezeControlBadge.d.ts +0 -11
  147. package/types/src/freeze-control/useFreezeControl.d.ts +0 -10
  148. package/types/src/index.d.ts +0 -26
  149. package/types/src/inline-add-row/InlineAddRow.d.ts +0 -3
  150. package/types/src/inline-add-row/index.d.ts +0 -2
  151. package/types/src/inline-add-row/useInlineAddRow.d.ts +0 -59
  152. package/types/src/tabbed-table-config-panel/TabbedTableConfigPanel.d.ts +0 -17
  153. package/types/src/tabbed-table-config-panel/TabbedTableSettingsAction.d.ts +0 -7
  154. package/types/src/tabbed-table-config-panel/useTabbedTableConfigPanel.d.ts +0 -13
  155. package/types/src/table-footer/TableFooter.d.ts +0 -10
  156. package/types/src/table-provider/TableProvider.d.ts +0 -18
  157. package/types/src/table-settings-panel/TableSettingsPanel.d.ts +0 -22
  158. package/types/src/table-settings-panel/useTableSettings.d.ts +0 -14
@@ -0,0 +1,204 @@
1
+ import { EditSession } from "@vuu-ui/vuu-data-editing";
2
+ import { Range, buildColumnMap, isRpcError, isSessionTable, metadataKeys } from "@vuu-ui/vuu-utils";
3
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
4
+ import { buildRowErrorMessage, getInlineRowErrorMessage, getRowVuuMsgError, toErrorMessage, waitForSessionErrors } from "../parse/csv-upload-utils.js";
5
+ import { CSV_FIRST_DATA_ROW_NUMBER } from "../parse/csv-constants.js";
6
+ const useImportSession = ({ dataSource, sessionOverrides, rowDefaults, processingPromiseRef, onImportSessionEnded })=>{
7
+ const sessionErrorsRef = useRef(new Map());
8
+ const receivedRowKeysRef = useRef(new Set());
9
+ const sessionRowUpdateListenersRef = useRef(new Set());
10
+ const importDataSource = useMemo(()=>({
11
+ tableSchema: dataSource.tableSchema,
12
+ createSessionDataSource: async (copyOption, sessionType)=>{
13
+ if (!dataSource.createSessionDataSource) throw Error("[useCsvUpload] dataSource does not support createSessionDataSource");
14
+ const sessionDs = await dataSource.createSessionDataSource(copyOption, sessionType, sessionOverrides);
15
+ if (!sessionDs) throw Error("[useCsvUpload] Failed to create session datasource");
16
+ if (!sessionDs.columns.includes("vuuMsg")) sessionDs.columns = sessionDs.columns.concat("vuuMsg");
17
+ const columnMap = buildColumnMap(sessionDs.columns);
18
+ const vuuMsgDataIndex = columnMap.vuuMsg ?? -1;
19
+ const vuuRowNumDataIndex = columnMap.vuuRowNum ?? -1;
20
+ const handleSessionMessage = (message)=>{
21
+ if ("viewport-update" === message.type && message.rows) {
22
+ for (const row of message.rows){
23
+ const rowKey = row[metadataKeys.KEY] ?? row[metadataKeys.IDX];
24
+ receivedRowKeysRef.current.add(rowKey);
25
+ const error = getRowVuuMsgError(row, vuuMsgDataIndex, vuuRowNumDataIndex);
26
+ if (error) sessionErrorsRef.current.set(rowKey, error);
27
+ }
28
+ for (const listener of sessionRowUpdateListenersRef.current)listener();
29
+ } else if ("viewport-clear" === message.type) {
30
+ sessionErrorsRef.current.clear();
31
+ receivedRowKeysRef.current.clear();
32
+ }
33
+ };
34
+ if (sessionDs.isRemote) return new Promise((resolve, reject)=>{
35
+ const handleSubscribed = ()=>{
36
+ sessionDs.removeListener("subscribed", handleSubscribed);
37
+ resolve(sessionDs);
38
+ };
39
+ sessionDs.on("subscribed", handleSubscribed);
40
+ try {
41
+ sessionDs.subscribe({
42
+ range: Range(0, 0)
43
+ }, handleSessionMessage);
44
+ } catch (err) {
45
+ sessionDs.removeListener("subscribed", handleSubscribed);
46
+ reject(err);
47
+ }
48
+ });
49
+ if ("function" == typeof sessionDs.subscribe && "initialising" === sessionDs.status) try {
50
+ sessionDs.subscribe({
51
+ range: Range(0, 0)
52
+ }, handleSessionMessage);
53
+ } catch {}
54
+ return sessionDs;
55
+ }
56
+ }), [
57
+ dataSource,
58
+ sessionOverrides
59
+ ]);
60
+ const editSession = useMemo(()=>new EditSession({
61
+ dataSource: importDataSource,
62
+ editSessionApi: "createSessionDataSource",
63
+ rowDefaults
64
+ }), [
65
+ importDataSource,
66
+ rowDefaults
67
+ ]);
68
+ const ownsEditSessionRef = useRef(true);
69
+ const sessionDataSourceRef = useRef(void 0);
70
+ const [sessionTable, setSessionTable] = useState();
71
+ useEffect(()=>()=>{
72
+ const processingPromise = processingPromiseRef?.current;
73
+ (async ()=>{
74
+ try {
75
+ await processingPromise;
76
+ } finally{
77
+ if (ownsEditSessionRef.current) try {
78
+ const sessionDs = sessionDataSourceRef.current ?? editSession.sessionDataSource;
79
+ if (sessionDs?.status !== "unsubscribed") await editSession.end(false);
80
+ } catch (error) {
81
+ console.error("[useCsvUpload] failed to discard edit session during cleanup", error);
82
+ }
83
+ }
84
+ })();
85
+ }, [
86
+ editSession,
87
+ processingPromiseRef
88
+ ]);
89
+ const setActiveSessionDataSource = useCallback((sessionDataSource)=>{
90
+ sessionDataSourceRef.current = sessionDataSource;
91
+ const table = sessionDataSource?.table;
92
+ setSessionTable(table && isSessionTable(table) ? table : void 0);
93
+ }, []);
94
+ const endEditSessionAndNotify = useCallback(async (save, reason)=>{
95
+ ownsEditSessionRef.current = false;
96
+ const sessionDataSource = sessionDataSourceRef.current;
97
+ if (!sessionDataSource) throw Error("CsvUpload has no active edit session.");
98
+ if (!sessionDataSource.endEditSession) throw Error("CsvUpload requires the session datasource to support endEditSession.");
99
+ const currentSessionTable = sessionDataSource.table;
100
+ try {
101
+ await editSession.end(save);
102
+ } finally{
103
+ if ("unsubscribed" !== sessionDataSource.status && "function" == typeof sessionDataSource.unsubscribe) sessionDataSource.unsubscribe();
104
+ setActiveSessionDataSource(void 0);
105
+ }
106
+ onImportSessionEnded?.({
107
+ reason,
108
+ sessionTable: currentSessionTable && isSessionTable(currentSessionTable) ? currentSessionTable : void 0
109
+ });
110
+ }, [
111
+ editSession,
112
+ onImportSessionEnded,
113
+ setActiveSessionDataSource
114
+ ]);
115
+ const beginEditSession = useCallback(async ()=>{
116
+ sessionErrorsRef.current.clear();
117
+ receivedRowKeysRef.current.clear();
118
+ ownsEditSessionRef.current = true;
119
+ const sessionDataSource = await editSession.begin("Empty", "import");
120
+ const sessionVuuTable = sessionDataSource?.table;
121
+ if (void 0 === sessionDataSource || void 0 === sessionVuuTable || !isSessionTable(sessionVuuTable)) throw Error("CsvUpload createSessionDataSource returned no session datasource.");
122
+ setActiveSessionDataSource(sessionDataSource);
123
+ return sessionDataSource;
124
+ }, [
125
+ editSession,
126
+ setActiveSessionDataSource
127
+ ]);
128
+ const closePendingEditSession = useCallback(async (save)=>{
129
+ if (void 0 === sessionDataSourceRef.current) return;
130
+ await endEditSessionAndNotify(save, save ? "saved" : "discarded");
131
+ }, [
132
+ endEditSessionAndNotify
133
+ ]);
134
+ const releaseEditSessionOwnership = useCallback(()=>{
135
+ ownsEditSessionRef.current = false;
136
+ }, []);
137
+ const addAllRows = useCallback(async (mergedValidation, isCancelled)=>{
138
+ const vuuMsgByRow = new Map();
139
+ for (const { rowNum, column, message } of mergedValidation.errors){
140
+ if (rowNum < CSV_FIRST_DATA_ROW_NUMBER) continue;
141
+ const existing = vuuMsgByRow.get(rowNum);
142
+ const columnError = `${column}: ${message}`;
143
+ vuuMsgByRow.set(rowNum, existing ? `${existing}; ${columnError}` : columnError);
144
+ }
145
+ const parsedRowCount = mergedValidation.rows.length;
146
+ const unparsedErrorRows = [
147
+ ...vuuMsgByRow.keys()
148
+ ].filter((rowNum)=>rowNum - CSV_FIRST_DATA_ROW_NUMBER >= parsedRowCount).map((rowNum)=>({
149
+ rowNum,
150
+ rowData: {},
151
+ vuuMsg: vuuMsgByRow.get(rowNum) ?? ""
152
+ }));
153
+ const allRows = [
154
+ ...mergedValidation.rows.map((rowData, idx)=>({
155
+ rowNum: idx + CSV_FIRST_DATA_ROW_NUMBER,
156
+ rowData: rowData ?? {},
157
+ vuuMsg: vuuMsgByRow.get(idx + CSV_FIRST_DATA_ROW_NUMBER) ?? ""
158
+ })),
159
+ ...unparsedErrorRows
160
+ ];
161
+ const rpcErrors = [];
162
+ for (const { rowNum, rowData, vuuMsg } of allRows){
163
+ if (isCancelled()) return false;
164
+ try {
165
+ const payload = vuuMsg ? {
166
+ vuuRowNum: rowNum,
167
+ vuuMsg
168
+ } : {
169
+ ...rowData,
170
+ vuuRowNum: rowNum,
171
+ vuuMsg
172
+ };
173
+ const result = await editSession.addRow(payload);
174
+ if (isRpcError(result)) throw Error(result.errorMessage);
175
+ if ("string" == typeof result) throw Error(result);
176
+ const inlineMsg = getInlineRowErrorMessage(result);
177
+ if (inlineMsg) sessionErrorsRef.current.set(rowNum, {
178
+ rowNum,
179
+ message: inlineMsg
180
+ });
181
+ } catch (error) {
182
+ rpcErrors.push(`${0 === rowNum ? "Header" : `Row ${rowNum}`}: ${toErrorMessage(error)}`);
183
+ }
184
+ }
185
+ if (rpcErrors.length > 0) throw Error(buildRowErrorMessage("Import failed", rpcErrors));
186
+ return allRows.length;
187
+ }, [
188
+ editSession
189
+ ]);
190
+ const checkSessionTableErrors = useCallback(async (sessionDs, expectedRowCount)=>waitForSessionErrors(sessionDs.isRemote ?? false, sessionErrorsRef.current, receivedRowKeysRef.current, expectedRowCount, sessionRowUpdateListenersRef.current), []);
191
+ return {
192
+ addAllRows,
193
+ beginEditSession,
194
+ checkSessionTableErrors,
195
+ closePendingEditSession,
196
+ editSession,
197
+ endEditSessionAndNotify,
198
+ releaseEditSessionOwnership,
199
+ sessionDataSourceRef,
200
+ sessionTable,
201
+ setActiveSessionDataSource
202
+ };
203
+ };
204
+ export { useImportSession };
@@ -1,205 +1,36 @@
1
- import { EditSession } from "@vuu-ui/vuu-data-editing";
2
- import { Range, isRpcError, isSessionTable, useData } from "@vuu-ui/vuu-utils";
3
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
1
+ import { Range, isSessionTable } from "@vuu-ui/vuu-utils";
2
+ import { useCallback, useMemo, useRef, useState } from "react";
4
3
  import { parseCsv } from "./parse/csv-parse.js";
5
- import { validateCsvAgainstSchema } from "./parse/csv-schema-validation.js";
6
- import { buildRowErrorMessage, createUploadError, hasFileParseErrors, isCsvParseError, mergeValidationWithParseErrors, toErrorMessage } from "./parse/csv-upload-utils.js";
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 })=>{
9
- const { getServerAPI } = useData();
4
+ import { CsvValidationErrorEnum, validateCsvAgainstSchema } from "./parse/csv-schema-validation.js";
5
+ import { createUploadError, hasFileParseErrors, isCsvParseError, mergeValidationWithParseErrors, toErrorMessage } from "./parse/csv-upload-utils.js";
6
+ import { useImportSchema } from "./session/useImportSchema.js";
7
+ import { useImportSession } from "./session/useImportSession.js";
8
+ const useCsvUpload = ({ dataSource, importMode = "direct", importSchema, importTable, onImportSessionEnded, onImportSessionReady, onError, onImported, onPreview, onProcessingStarted, maxRows, parseOptions, rowDefaults, validators })=>{
10
9
  const [validation, setValidation] = useState();
11
- const [sessionTable, setSessionTable] = useState();
12
10
  const [isProcessingFile, setIsProcessingFile] = useState(false);
13
11
  const [isImporting, setIsImporting] = useState(false);
14
- const [fetchedImportSchema, setFetchedImportSchema] = useState();
15
12
  const [error, setError] = useState();
13
+ const operationIdRef = useRef(0);
14
+ const processingPromiseRef = useRef(void 0);
16
15
  const handleError = useCallback((errResult)=>{
17
16
  setError(errResult);
18
17
  onError?.(errResult);
19
18
  }, [
20
19
  onError
21
20
  ]);
22
- useEffect(()=>{
23
- if (importSchema || void 0 === importTable) return void setFetchedImportSchema(void 0);
24
- let cancelled = false;
25
- (async ()=>{
26
- try {
27
- const server = await getServerAPI();
28
- const tableSchema = await server.getTableSchema(importTable);
29
- if (!cancelled) setFetchedImportSchema(tableSchema);
30
- } catch (error) {
31
- console.error("[useCsvUpload] failed to fetch import table schema", error);
32
- }
33
- })();
34
- return ()=>{
35
- cancelled = true;
36
- };
37
- }, [
38
- getServerAPI,
21
+ const { schema, sessionOverrides } = useImportSchema({
22
+ dataSource,
39
23
  importSchema,
40
24
  importTable
41
- ]);
42
- const resolvedImportSchema = importSchema ?? fetchedImportSchema;
43
- const sessionOverrides = useMemo(()=>{
44
- const columns = resolvedImportSchema?.columns.map(({ name })=>name);
45
- return columns || importTable ? {
46
- columns,
47
- table: importTable
48
- } : void 0;
49
- }, [
50
- resolvedImportSchema,
51
- importTable
52
- ]);
53
- const importDataSource = useMemo(()=>({
54
- tableSchema: dataSource.tableSchema,
55
- createSessionDataSource: async (copyOption, sessionType)=>{
56
- if (!dataSource.createSessionDataSource) throw Error("[useCsvUpload] dataSource does not support createSessionDataSource");
57
- const sessionDs = await dataSource.createSessionDataSource(copyOption, sessionType, sessionOverrides);
58
- if (!sessionDs) throw Error("[useCsvUpload] Failed to create session datasource");
59
- if (!sessionDs.columns.includes("vuuMsg")) sessionDs.columns = sessionDs.columns.concat("vuuMsg");
60
- if (sessionDs.isRemote) return new Promise((resolve, reject)=>{
61
- const handleSubscribed = ()=>{
62
- sessionDs.removeListener("subscribed", handleSubscribed);
63
- resolve(sessionDs);
64
- };
65
- sessionDs.on("subscribed", handleSubscribed);
66
- try {
67
- sessionDs.subscribe({
68
- range: Range(0, 0)
69
- }, ()=>{});
70
- } catch (err) {
71
- sessionDs.removeListener("subscribed", handleSubscribed);
72
- reject(err);
73
- }
74
- });
75
- return sessionDs;
76
- }
77
- }), [
25
+ });
26
+ const { addAllRows, beginEditSession, checkSessionTableErrors, closePendingEditSession, editSession, endEditSessionAndNotify, releaseEditSessionOwnership, sessionDataSourceRef, sessionTable, setActiveSessionDataSource } = useImportSession({
78
27
  dataSource,
79
- sessionOverrides
80
- ]);
81
- const editSession = useMemo(()=>new EditSession({
82
- dataSource: importDataSource,
83
- editSessionApi: "createSessionDataSource",
84
- rowDefaults
85
- }), [
86
- importDataSource,
87
- rowDefaults
88
- ]);
89
- const ownsEditSessionRef = useRef(true);
90
- const operationIdRef = useRef(0);
91
- const processingPromiseRef = useRef(void 0);
92
- const sessionDataSourceRef = useRef(void 0);
93
- useEffect(()=>()=>{
94
- operationIdRef.current++;
95
- const processingPromise = processingPromiseRef.current;
96
- (async ()=>{
97
- try {
98
- await processingPromise;
99
- } finally{
100
- if (ownsEditSessionRef.current) try {
101
- await editSession.end(false);
102
- } catch (error) {
103
- console.error("[useCsvUpload] failed to discard edit session during cleanup", error);
104
- }
105
- }
106
- })();
107
- }, [
108
- editSession
109
- ]);
110
- const setActiveSessionDataSource = useCallback((sessionDataSource)=>{
111
- sessionDataSourceRef.current = sessionDataSource;
112
- const table = sessionDataSource?.table;
113
- setSessionTable(table && isSessionTable(table) ? table : void 0);
114
- }, []);
115
- const endEditSessionAndNotify = useCallback(async (save, reason)=>{
116
- const sessionDataSource = sessionDataSourceRef.current;
117
- if (!sessionDataSource) throw Error("CsvUpload has no active edit session.");
118
- if (!sessionDataSource.endEditSession) throw Error("CsvUpload requires the session datasource to support endEditSession.");
119
- const currentSessionTable = sessionDataSource.table;
120
- try {
121
- await editSession.end(save);
122
- } finally{
123
- if ("unsubscribed" !== sessionDataSource.status) sessionDataSource.unsubscribe();
124
- setActiveSessionDataSource(void 0);
125
- }
126
- onImportSessionEnded?.({
127
- reason,
128
- sessionTable: currentSessionTable && isSessionTable(currentSessionTable) ? currentSessionTable : void 0
129
- });
130
- }, [
131
- editSession,
132
- onImportSessionEnded,
133
- setActiveSessionDataSource
134
- ]);
28
+ sessionOverrides,
29
+ rowDefaults,
30
+ processingPromiseRef,
31
+ onImportSessionEnded
32
+ });
135
33
  const table = dataSource.table;
136
- const schema = importTable ? resolvedImportSchema : resolvedImportSchema ?? dataSource.tableSchema;
137
- const addAllRows = useCallback(async (mergedValidation, operationId)=>{
138
- const vuuMsgByRow = new Map();
139
- for (const { rowNum, column, message } of mergedValidation.errors){
140
- if (rowNum < CSV_FIRST_DATA_ROW_NUMBER) continue;
141
- const existing = vuuMsgByRow.get(rowNum);
142
- const columnError = `${column}: ${message}`;
143
- vuuMsgByRow.set(rowNum, existing ? `${existing}; ${columnError}` : `${0 === rowNum ? "Header" : `Row ${rowNum}`}: ${columnError}`);
144
- }
145
- const parsedRowCount = mergedValidation.rows.length;
146
- const unparsedErrorRows = [
147
- ...vuuMsgByRow.keys()
148
- ].filter((rowNum)=>rowNum - CSV_FIRST_DATA_ROW_NUMBER >= parsedRowCount).map((rowNum)=>({
149
- rowNum,
150
- rowData: {},
151
- vuuMsg: vuuMsgByRow.get(rowNum) ?? ""
152
- }));
153
- const allRows = [
154
- ...mergedValidation.rows.map((rowData, idx)=>({
155
- rowNum: idx + CSV_FIRST_DATA_ROW_NUMBER,
156
- rowData: rowData ?? {},
157
- vuuMsg: vuuMsgByRow.get(idx + CSV_FIRST_DATA_ROW_NUMBER) ?? ""
158
- })),
159
- ...unparsedErrorRows
160
- ];
161
- const rpcErrors = [];
162
- for (const { rowNum, rowData, vuuMsg } of allRows){
163
- if (operationId !== operationIdRef.current) return false;
164
- try {
165
- const payload = vuuMsg ? {
166
- vuuRowNum: rowNum,
167
- vuuMsg
168
- } : {
169
- ...rowData,
170
- vuuRowNum: rowNum,
171
- vuuMsg
172
- };
173
- const result = await editSession.addRow(payload);
174
- if (isRpcError(result)) throw Error(result.errorMessage);
175
- if ("string" == typeof result) throw Error(result);
176
- } catch (error) {
177
- rpcErrors.push(`${0 === rowNum ? "Header" : `Row ${rowNum}`}: ${toErrorMessage(error)}`);
178
- }
179
- }
180
- if (rpcErrors.length > 0) throw Error(buildRowErrorMessage("Import failed", rpcErrors));
181
- return true;
182
- }, [
183
- editSession
184
- ]);
185
- const beginEditSession = useCallback(async ()=>{
186
- const sessionDataSource = await editSession.begin("Empty", "import");
187
- const sessionVuuTable = sessionDataSource?.table;
188
- if (void 0 === sessionDataSource || void 0 === sessionVuuTable || !isSessionTable(sessionVuuTable)) throw Error("CsvUpload createSessionDataSource returned no session datasource.");
189
- setActiveSessionDataSource(sessionDataSource);
190
- onImportSessionStarted?.(sessionDataSource);
191
- return sessionDataSource;
192
- }, [
193
- editSession,
194
- onImportSessionStarted,
195
- setActiveSessionDataSource
196
- ]);
197
- const closePendingEditSession = useCallback(async (save)=>{
198
- if (void 0 === sessionDataSourceRef.current) return;
199
- await endEditSessionAndNotify(save, save ? "saved" : "discarded");
200
- }, [
201
- endEditSessionAndNotify
202
- ]);
203
34
  const cancelImport = useCallback(async ()=>{
204
35
  operationIdRef.current++;
205
36
  processingPromiseRef.current = void 0;
@@ -245,13 +76,10 @@ const useCsvUpload = ({ dataSource, importMode = "direct", importSchema, importT
245
76
  });
246
77
  if (Object.keys(schemaValidation.errorMap.fileErrors).length > 0) {
247
78
  setValidation(schemaValidation);
79
+ const detailedMessage = `CSV validation failed: ${schemaValidation.errors.map((e)=>"*" === e.column ? e.message : `${e.column}: ${e.message}`).join("; ")}`;
248
80
  handleError({
249
81
  errors: {
250
- schemaError: createUploadError("schema", "CSV validation failed.", parsedCsv.error, {
251
- errorMap: schemaValidation.errorMap,
252
- errors: schemaValidation.errors,
253
- message: "CSV validation failed."
254
- })
82
+ schemaError: createUploadError("schema", detailedMessage, parsedCsv.error, schemaValidation)
255
83
  }
256
84
  });
257
85
  return;
@@ -259,10 +87,31 @@ const useCsvUpload = ({ dataSource, importMode = "direct", importSchema, importT
259
87
  const mergedValidation = mergeValidationWithParseErrors(schemaValidation, parsedCsv.error);
260
88
  setValidation(mergedValidation);
261
89
  if (mergedValidation.rows.length > 0) try {
262
- await beginEditSession();
90
+ const sessionDataSource = await beginEditSession();
263
91
  if (operationId !== operationIdRef.current) return;
264
- const rowsAdded = await addAllRows(mergedValidation, operationId);
265
- if (!rowsAdded) return;
92
+ const rowsAddedCount = await addAllRows(mergedValidation, ()=>operationId !== operationIdRef.current);
93
+ if (false === rowsAddedCount) return;
94
+ const expectedRowCount = rowsAddedCount;
95
+ sessionDataSource.range = Range(0, expectedRowCount);
96
+ const sessionErrors = await checkSessionTableErrors(sessionDataSource, expectedRowCount);
97
+ if (operationId !== operationIdRef.current) return;
98
+ if (sessionErrors.length > 0) {
99
+ const serverValidationErrors = sessionErrors.map(({ rowNum, message })=>({
100
+ rowNum,
101
+ column: "*",
102
+ message,
103
+ value: "",
104
+ errorEnum: CsvValidationErrorEnum.CUSTOM_VALIDATION
105
+ }));
106
+ setValidation((prev)=>prev ? {
107
+ ...prev,
108
+ errors: [
109
+ ...prev.errors,
110
+ ...serverValidationErrors
111
+ ]
112
+ } : void 0);
113
+ }
114
+ onImportSessionReady?.(sessionDataSource);
266
115
  } catch (error) {
267
116
  if (void 0 !== sessionDataSourceRef.current) await endEditSessionAndNotify(false, "failed");
268
117
  else try {
@@ -281,6 +130,7 @@ const useCsvUpload = ({ dataSource, importMode = "direct", importSchema, importT
281
130
  handleError,
282
131
  editSession,
283
132
  addAllRows,
133
+ checkSessionTableErrors,
284
134
  maxRows,
285
135
  parseOptions,
286
136
  beginEditSession,
@@ -290,7 +140,9 @@ const useCsvUpload = ({ dataSource, importMode = "direct", importSchema, importT
290
140
  table,
291
141
  endEditSessionAndNotify,
292
142
  schema,
293
- validators
143
+ validators,
144
+ onImportSessionReady,
145
+ sessionDataSourceRef
294
146
  ]);
295
147
  const handleFiles = useCallback(async (files)=>{
296
148
  const file = files[0];
@@ -333,10 +185,11 @@ const useCsvUpload = ({ dataSource, importMode = "direct", importSchema, importT
333
185
  }, [
334
186
  handleFiles
335
187
  ]);
336
- const canImport = useMemo(()=>void 0 !== validation && 0 === validation.errors.length && validation.rows.length > 0 && !isProcessingFile && !isImporting, [
188
+ const canImport = useMemo(()=>void 0 !== validation && 0 === validation.errors.length && validation.rows.length > 0 && !isProcessingFile && !isImporting && void 0 === error, [
337
189
  isImporting,
338
190
  isProcessingFile,
339
- validation
191
+ validation,
192
+ error
340
193
  ]);
341
194
  const importData = useCallback(async ()=>{
342
195
  if (!canImport || void 0 === validation) return false;
@@ -357,7 +210,7 @@ const useCsvUpload = ({ dataSource, importMode = "direct", importSchema, importT
357
210
  editSession,
358
211
  tableData
359
212
  });
360
- ownsEditSessionRef.current = false;
213
+ releaseEditSessionOwnership();
361
214
  } else {
362
215
  await endEditSessionAndNotify(true, "saved");
363
216
  onImported?.({
@@ -385,6 +238,8 @@ const useCsvUpload = ({ dataSource, importMode = "direct", importSchema, importT
385
238
  onImported,
386
239
  onPreview,
387
240
  importMode,
241
+ releaseEditSessionOwnership,
242
+ sessionDataSourceRef,
388
243
  validation
389
244
  ]);
390
245
  return {
@@ -1,8 +1,8 @@
1
1
  const css = `
2
2
  .vuuFreezeControl {
3
3
  --freeze-control-flash-duration: .25s;
4
- --freeze-control-active-button-color: var(--salt-palette-error-background);
5
- --freeze-control-badge-color: var(--salt-palette-success-background);
4
+ --freeze-control-active-button-color: var(--salt-status-error-background);
5
+ --freeze-control-badge-color: var(--salt-status-success-background);
6
6
  height: fit-content;
7
7
  display: inline-flex;
8
8
 
@@ -31,7 +31,7 @@ const css = `
31
31
 
32
32
  & .vuuFreezeControl-buttonWrapper-active .saltToggleButton {
33
33
  background-color: var(--freeze-control-active-button-color);
34
- color: var(--salt-actionable-secondary-foreground-active);
34
+ color: var(--salt-actionable-subtle-foreground-active);
35
35
  }
36
36
 
37
37
  & .saltToggleButtonGroup.vuuStateButtonGroup {
@@ -43,7 +43,7 @@ const css = `
43
43
  }
44
44
 
45
45
  & .vuuFreezeControl-customBadge {
46
- color: var(--salt-text-primary-foreground);
46
+ color: var(--salt-content-primary-foreground);
47
47
  font-size: var(--salt-text-fontSize);
48
48
  height: var(--salt-size-base);
49
49
  width: var(--salt-size-base);
@@ -1,8 +1,8 @@
1
1
  const css = `
2
2
  .FrozenBanner {
3
- --freeze-control-active-button-color: var(--salt-palette-error-background);
3
+ --freeze-control-active-button-color: var(--salt-status-error-background);
4
4
  background-color: var(--freeze-control-active-button-color);
5
- color: var(--salt-actionable-secondary-foreground-active);
5
+ color: var(--salt-actionable-subtle-foreground-active);
6
6
  font-size: var(--salt-text-fontSize);
7
7
  height: var(--salt-size-base);
8
8
  padding: 0 var(--salt-spacing-200);
@@ -4,10 +4,10 @@ import { useComponentCssInjection } from "@salt-ds/styles";
4
4
  import { useWindow } from "@salt-ds/window";
5
5
  import TableFooter from "./TableFooter.css";
6
6
  const classBase = "vuuTableFooter";
7
- const TableFooterTray = ({ children, position = 'end' })=>/*#__PURE__*/ jsx("div", {
7
+ const TableFooterTray = ({ children, position = "end" })=>/*#__PURE__*/ jsx("div", {
8
8
  className: clsx(`${classBase}Tray`, {
9
- [`${classBase}Tray-center`]: 'center' === position,
10
- [`${classBase}Tray-end`]: 'end' === position
9
+ [`${classBase}Tray-center`]: "center" === position,
10
+ [`${classBase}Tray-end`]: "end" === position
11
11
  }),
12
12
  children: children
13
13
  });
@@ -9,4 +9,4 @@ export interface CalculatedColumnPanelProps extends HTMLAttributes<HTMLDivElemen
9
9
  onChangeServerDataType?: (name: VuuColumnDataType) => void;
10
10
  vuuTable: VuuTable;
11
11
  }
12
- export declare const CalculatedColumnPanel: ({ column: columnProp, columnModel, onChangeColumn, onChangeServerDataType: onChangeServerDataTypeProp, vuuTable, }: CalculatedColumnPanelProps) => import("react").JSX.Element;
12
+ export declare const CalculatedColumnPanel: ({ column: columnProp, columnModel, onChangeColumn, onChangeServerDataType: onChangeServerDataTypeProp, vuuTable, }: CalculatedColumnPanelProps) => import("react/jsx-runtime").JSX.Element;
@@ -1,2 +1,2 @@
1
1
  import { TableCellRendererProps } from "@vuu-ui/vuu-table-types";
2
- export declare const BackgroundCell: import("react").MemoExoticComponent<({ column, dataRow, }: TableCellRendererProps) => import("react").JSX.Element>;
2
+ export declare const BackgroundCell: import("react").MemoExoticComponent<({ column, dataRow, }: TableCellRendererProps) => import("react/jsx-runtime").JSX.Element>;
@@ -1,2 +1,2 @@
1
1
  import { ConfigurationEditorProps } from "@vuu-ui/vuu-utils";
2
- export declare const BackgroundCellConfigurationEditor: ({ column, onChangeRendering, }: ConfigurationEditorProps) => import("react").JSX.Element;
2
+ export declare const BackgroundCellConfigurationEditor: ({ column, onChangeRendering, }: ConfigurationEditorProps) => import("react/jsx-runtime").JSX.Element;
@@ -3,4 +3,4 @@ import { TableCellRendererProps } from "@vuu-ui/vuu-table-types";
3
3
  * To assign an icon to the cell, give the columndescriptor
4
4
  * a className. In css, assign an icon variable to --vuu-svg-icon.
5
5
  */
6
- export declare const IconButtonCell: ({ column, dataRow }: TableCellRendererProps) => import("react").JSX.Element;
6
+ export declare const IconButtonCell: ({ column, dataRow }: TableCellRendererProps) => import("react/jsx-runtime").JSX.Element;
@@ -1,2 +1,2 @@
1
1
  import { TableCellRendererProps } from "@vuu-ui/vuu-table-types";
2
- export declare const DropdownCell: import("react").MemoExoticComponent<({ column, dataRow, editedDuringCurrentSession, onEdit, }: TableCellRendererProps) => import("react").JSX.Element>;
2
+ export declare const DropdownCell: import("react").MemoExoticComponent<({ column, dataRow, editedDuringCurrentSession, onEdit, }: TableCellRendererProps) => import("react/jsx-runtime").JSX.Element>;
@@ -1,2 +1,2 @@
1
1
  import { TableCellRendererProps } from "@vuu-ui/vuu-table-types";
2
- export declare const LookupCell: import("react").MemoExoticComponent<({ column, dataRow, }: TableCellRendererProps) => import("react").JSX.Element>;
2
+ export declare const LookupCell: import("react").MemoExoticComponent<({ column, dataRow, }: TableCellRendererProps) => import("react/jsx-runtime").JSX.Element>;
@@ -1,2 +1,2 @@
1
1
  import { TableCellProps } from "@vuu-ui/vuu-table-types";
2
- export declare const PctProgressCell: ({ column, dataRow }: TableCellProps) => import("react").JSX.Element;
2
+ export declare const PctProgressCell: ({ column, dataRow }: TableCellProps) => import("react/jsx-runtime").JSX.Element;
@@ -7,4 +7,4 @@ export interface ColumnExpressionInputProps extends ExpressionSuggestionConsumer
7
7
  onSubmitExpression?: ColumnExpressionSubmitHandler;
8
8
  source?: string;
9
9
  }
10
- export declare const ColumnExpressionInput: import("react").MemoExoticComponent<({ onChange, onSubmitExpression, source, suggestionProvider, }: ColumnExpressionInputProps) => import("react").JSX.Element>;
10
+ export declare const ColumnExpressionInput: import("react").MemoExoticComponent<({ onChange, onSubmitExpression, source, suggestionProvider, }: ColumnExpressionInputProps) => import("react/jsx-runtime").JSX.Element>;
@@ -2,4 +2,4 @@ import type { SyntaxNode } from "@vuu-ui/vuu-codemirror";
2
2
  export declare const ColumnNamedTerms: readonly string[];
3
3
  export declare const isCompleteExpression: (src: string) => boolean;
4
4
  export declare const lastNamedChild: (node: SyntaxNode) => SyntaxNode | null;
5
- export declare const isCompleteRelationalExpression: (node?: any) => boolean;
5
+ export declare const isCompleteRelationalExpression: (node?: SyntaxNode) => boolean;
@@ -1 +1 @@
1
- export function walkTree(tree: any, source: any): void;
1
+ export declare const walkTree: (tree: any, source: any) => void;