@vuu-ui/vuu-table-extras 3.3.2 → 3.3.4

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/README.md CHANGED
@@ -0,0 +1,32 @@
1
+ # @vuu-ui/vuu-table-extras
2
+
3
+ Extended components and utilities for Vuu tables, including CSV export, CSV upload and validation, column settings, column pickers, custom cell renderers, and table footer controls.
4
+
5
+ ---
6
+
7
+ ## Features
8
+
9
+ ### [CSV Export](./src/csv-export/README.md)
10
+ Standalone utilities and a React hook for exporting table data and template schemas to CSV:
11
+ - `exportToCsv(dataSource, options)`: Subscribes to a session table, collects rows in memory in index order, formats values, and triggers browser downloads.
12
+ - `exportCsvTemplate(dataSource, options)`: Generates a header-only CSV template for import workflows.
13
+ - `useCsvExport(dataSource)`: React hook with built-in `isExporting` state, error handling, and helper methods.
14
+
15
+ ### [CSV Upload](./src/csv-upload/README.md)
16
+ Dialog and hook workflow for importing CSV data into Vuu tables via server-side session tables:
17
+ - `CsvUpload`: Complete dialog component with file drag-and-drop, client-side validation, error summaries, and staging controls.
18
+ - `useCsvUpload`: Headless hook managing file parsing, schema validation, session table staging, and RPC row batching.
19
+ - `DataUploadPreview`: Inline editable table preview for inspecting and correcting invalid staged rows before commit.
20
+
21
+ ### Column Management
22
+ - `ColumnMenu` & `useColumnActions`: Context menus for sorting, grouping, filtering, and column configuration.
23
+ - `ColumnPicker` & `useTableColumnPicker`: Dialog and drawer controls for selecting and reordering visible columns.
24
+ - `CalculatedColumnPanel`: UI panel for defining custom expression-based columns.
25
+
26
+ ### Cell Renderers & Formatters
27
+ - Pre-built cell renderers including `BackgroundCell`, `DropdownCell`, `IconButtonCell`, and undo support.
28
+ - Cell edit validators for ensuring valid data entry.
29
+
30
+ ### Table Footer & Status Controls
31
+ - `TableFooter` & `TableFooterTray`: Status bar and pagination trays.
32
+ - `DataSourceStats`: Live indicators for row count, connection state, freeze status, and filter metrics.
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "3.3.2",
2
+ "version": "3.3.4",
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.2",
15
- "@vuu-ui/vuu-protocol-types": "3.3.2"
14
+ "@vuu-ui/vuu-filter-types": "3.3.4",
15
+ "@vuu-ui/vuu-protocol-types": "3.3.4"
16
16
  },
17
17
  "dependencies": {
18
- "@vuu-ui/vuu-codemirror": "3.3.2",
19
- "@vuu-ui/vuu-data-editing": "3.3.2",
20
- "@vuu-ui/vuu-data-react": "3.3.2",
21
- "@vuu-ui/vuu-data-types": "3.3.2",
22
- "@vuu-ui/vuu-table-types": "3.3.2",
23
- "@vuu-ui/vuu-popups": "3.3.2",
24
- "@vuu-ui/vuu-table": "3.3.2",
25
- "@vuu-ui/vuu-utils": "3.3.2",
26
- "@vuu-ui/vuu-ui-controls": "3.3.2",
18
+ "@vuu-ui/vuu-codemirror": "3.3.4",
19
+ "@vuu-ui/vuu-data-editing": "3.3.4",
20
+ "@vuu-ui/vuu-data-react": "3.3.4",
21
+ "@vuu-ui/vuu-data-types": "3.3.4",
22
+ "@vuu-ui/vuu-table-types": "3.3.4",
23
+ "@vuu-ui/vuu-popups": "3.3.4",
24
+ "@vuu-ui/vuu-table": "3.3.4",
25
+ "@vuu-ui/vuu-utils": "3.3.4",
26
+ "@vuu-ui/vuu-ui-controls": "3.3.4",
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",
@@ -6,9 +6,11 @@ const EXPORT_EXCLUDED_COLUMNS = new Set([
6
6
  ]);
7
7
  const MAX_EXPORT_ROWS = 10000;
8
8
  const CHUNK_SIZE = 1000;
9
+ const DEFAULT_EXPORT_TIMEOUT = 30000;
10
+ const DEFAULT_TEMPLATE_TIMEOUT = 10000;
9
11
  const csvCell = (value)=>{
10
12
  const s = null == value ? "" : String(value);
11
- return s.includes(",") || s.includes('"') || s.includes("\n") ? `"${s.replace(/"/g, '""')}` : s;
13
+ return s.includes(",") || s.includes('"') || s.includes("\n") || s.includes("\r") ? `"${s.replace(/"/g, '""')}"` : s;
12
14
  };
13
15
  const triggerCsvDownload = (csv, filename)=>{
14
16
  const blob = new Blob([
@@ -26,13 +28,27 @@ const triggerCsvDownload = (csv, filename)=>{
26
28
  document.body.removeChild(a);
27
29
  setTimeout(()=>URL.revokeObjectURL(url), 10000);
28
30
  };
29
- const exportCsvTemplate = async (dataSource, filename = "template.csv", excludeColumns = [], columns, overrides)=>{
31
+ const exportCsvTemplate = async (dataSource, options = {})=>{
32
+ const { filename = "template.csv", excludeColumns = [], columns, overrides, timeout = DEFAULT_TEMPLATE_TIMEOUT, onError, onSuccess } = options;
30
33
  const sessionOverrides = overrides ?? (columns ? {
31
34
  columns
32
35
  } : void 0);
33
- if (!isSessionTable(dataSource.table) && dataSource.createSessionDataSource) try {
36
+ if (!isSessionTable(dataSource.table) && dataSource.createSessionDataSource && "initialising" !== dataSource.status && "unsubscribed" !== dataSource.status) try {
34
37
  const sessionDataSource = await dataSource.createSessionDataSource("Empty", "export", sessionOverrides);
35
- if (sessionDataSource) return new Promise((resolve)=>{
38
+ if (sessionDataSource) return new Promise((resolve, reject)=>{
39
+ let timer;
40
+ const cleanup = ()=>{
41
+ if (timer) clearTimeout(timer);
42
+ sessionDataSource.unsubscribe();
43
+ };
44
+ if (timeout > 0) timer = setTimeout(()=>{
45
+ cleanup();
46
+ const err = new Error(`exportCsvTemplate: timed out after ${timeout}ms waiting for subscription`);
47
+ if (onError) {
48
+ onError(err);
49
+ resolve();
50
+ } else reject(err);
51
+ }, timeout);
36
52
  const excluded = new Set([
37
53
  ...EXPORT_EXCLUDED_COLUMNS,
38
54
  ...excludeColumns
@@ -46,7 +62,8 @@ const exportCsvTemplate = async (dataSource, filename = "template.csv", excludeC
46
62
  const exportCols = subColumns.filter((name)=>!excluded.has(name));
47
63
  const header = exportCols.map(csvCell).join(",");
48
64
  triggerCsvDownload(`${header}\r\n`, filename);
49
- sessionDataSource.unsubscribe();
65
+ cleanup();
66
+ onSuccess?.();
50
67
  resolve();
51
68
  }
52
69
  });
@@ -62,26 +79,47 @@ const exportCsvTemplate = async (dataSource, filename = "template.csv", excludeC
62
79
  const unknown = targetColumns.filter((name)=>!schemaColumnNames.has(name));
63
80
  if (unknown.length > 0) console.warn(`[exportCsvTemplate] unknown column(s) in view tableSchema: ${unknown.join(", ")}`);
64
81
  }
65
- } else if (!schema) throw Error("exportCsvTemplate: tableSchema not available on dataSource");
82
+ } else if (!schema) {
83
+ const error = new Error("exportCsvTemplate: tableSchema not available on dataSource");
84
+ if (onError) return void onError(error);
85
+ throw error;
86
+ }
66
87
  const excluded = new Set([
67
88
  ...EXPORT_EXCLUDED_COLUMNS,
68
89
  ...excludeColumns
69
90
  ]);
70
91
  const exportCols = targetColumns ? targetColumns.filter((name)=>!excluded.has(name)) : schema?.columns.filter((col)=>!excluded.has(col.name)).map((col)=>col.name) ?? [];
71
- if (0 === exportCols.length) throw Error("exportCsvTemplate: no columns available for export");
92
+ if (0 === exportCols.length) {
93
+ const error = new Error("exportCsvTemplate: no columns available for export");
94
+ if (onError) return void onError(error);
95
+ throw error;
96
+ }
72
97
  const header = exportCols.map(csvCell).join(",");
73
98
  triggerCsvDownload(`${header}\r\n`, filename);
99
+ onSuccess?.();
74
100
  };
75
- const exportSessionTableToCsv = async (dataSource, filename = "export.csv", excludeColumns = [], onError, onSuccess, maxRows = MAX_EXPORT_ROWS, columnDescriptors, copyOption = "All", overrides)=>{
101
+ const exportSessionTableToCsv = async (dataSource, options = {})=>{
102
+ const { filename = "export.csv", copyOption = "All", excludeColumns = [], maxRows = MAX_EXPORT_ROWS, columnDescriptors, overrides, timeout = DEFAULT_EXPORT_TIMEOUT, onError, onSuccess } = options;
103
+ const isRemote = !!dataSource.isRemote;
104
+ if (isRemote && !isSessionTable(dataSource.table) && ("initialising" === dataSource.status || "unsubscribed" === dataSource.status)) {
105
+ const error = new Error(`exportSessionTableToCsv: dataSource must be subscribed before exporting (current status: "${dataSource.status}")`);
106
+ if (onError) return void onError(error);
107
+ throw error;
108
+ }
76
109
  let sessionDataSource;
77
110
  if (!isSessionTable(dataSource.table) && dataSource.createSessionDataSource) try {
78
111
  sessionDataSource = await dataSource.createSessionDataSource(copyOption, "export", overrides);
79
112
  } catch (err) {
80
- onError?.(err instanceof Error ? err : new Error(String(err)));
81
- return;
113
+ const error = err instanceof Error ? err : new Error(String(err));
114
+ if (onError) return void onError(error);
115
+ throw error;
82
116
  }
83
117
  else sessionDataSource = dataSource;
84
- if (!sessionDataSource) return void onError?.(new Error("exportSessionTableToCsv: unable to obtain sessionDataSource"));
118
+ if (!sessionDataSource) {
119
+ const error = new Error("exportSessionTableToCsv: unable to obtain sessionDataSource");
120
+ if (onError) return void onError(error);
121
+ throw error;
122
+ }
85
123
  const activeSessionDataSource = sessionDataSource;
86
124
  const excluded = new Set([
87
125
  ...EXPORT_EXCLUDED_COLUMNS,
@@ -91,20 +129,31 @@ const exportSessionTableToCsv = async (dataSource, filename = "export.csv", excl
91
129
  let colNameToRowIdx = {};
92
130
  let descriptorMap = {};
93
131
  let totalSize = 0;
94
- const collectedRows = [];
95
- return new Promise((resolve)=>{
132
+ let nextRequestedFrom = 0;
133
+ const collectedRows = new Map();
134
+ return new Promise((resolve, reject)=>{
135
+ let timer;
136
+ const cleanup = ()=>{
137
+ if (timer) clearTimeout(timer);
138
+ activeSessionDataSource.unsubscribe();
139
+ };
140
+ const fail = (err)=>{
141
+ cleanup();
142
+ if (onError) {
143
+ onError(err);
144
+ resolve();
145
+ } else reject(err);
146
+ };
147
+ if (timeout > 0) timer = setTimeout(()=>{
148
+ fail(new Error(`exportToCsv: export timed out after ${timeout}ms waiting for data`));
149
+ }, timeout);
96
150
  const handleMessage = (message)=>{
97
151
  if ("subscribed" === message.type) {
98
152
  const { columns } = message;
99
153
  if (columnDescriptors) {
100
154
  const subscribedSet = new Set(columns);
101
155
  const unknown = columnDescriptors.filter((d)=>!excluded.has(d.name) && !subscribedSet.has(d.name)).map((d)=>d.name);
102
- if (unknown.length > 0) {
103
- activeSessionDataSource.unsubscribe();
104
- onError?.(new Error(`exportToCsv: unknown column(s) in columnDescriptors: ${unknown.join(", ")}`));
105
- resolve();
106
- return;
107
- }
156
+ if (unknown.length > 0) return void fail(new Error(`exportToCsv: unknown column(s) in columnDescriptors: ${unknown.join(", ")}`));
108
157
  descriptorMap = Object.fromEntries(columnDescriptors.map((d)=>[
109
158
  d.name,
110
159
  d
@@ -119,37 +168,45 @@ const exportSessionTableToCsv = async (dataSource, filename = "export.csv", excl
119
168
  if ("size-only" === message.mode) {
120
169
  totalSize = message.size ?? 0;
121
170
  if (0 === totalSize) {
122
- activeSessionDataSource.unsubscribe();
171
+ cleanup();
172
+ const header = exportCols.map((name)=>csvCell(descriptorMap[name]?.label ?? name)).join(",");
173
+ triggerCsvDownload(`${header}\r\n`, filename);
123
174
  onSuccess?.();
124
175
  resolve();
125
176
  return;
126
177
  }
127
- if (totalSize > maxRows) {
128
- activeSessionDataSource.unsubscribe();
129
- onError?.(new Error(`exportToCsv: row count ${totalSize} exceeds the ${maxRows} row limit`));
130
- resolve();
131
- return;
178
+ if (totalSize > maxRows) return void fail(new Error(`exportToCsv: row count ${totalSize} exceeds the ${maxRows} row limit`));
179
+ nextRequestedFrom = Math.min(CHUNK_SIZE, totalSize);
180
+ activeSessionDataSource.range = Range(0, nextRequestedFrom);
181
+ } else if (("batch" === message.mode || "update" === message.mode) && message.rows) {
182
+ for (const row of message.rows){
183
+ const rowIndex = row[metadataKeys.IDX];
184
+ if ("number" == typeof rowIndex) collectedRows.set(rowIndex, row);
132
185
  }
133
- activeSessionDataSource.range = Range(0, Math.min(CHUNK_SIZE, totalSize));
134
- } else if ("batch" === message.mode && message.rows) {
135
- collectedRows.push(...message.rows);
136
186
  if (void 0 !== message.size) totalSize = message.size;
137
- if (collectedRows.length >= totalSize) {
138
- activeSessionDataSource.unsubscribe();
187
+ if (collectedRows.size >= totalSize) {
188
+ cleanup();
139
189
  const lines = [
140
190
  exportCols.map((name)=>csvCell(descriptorMap[name]?.label ?? name)).join(",")
141
191
  ];
142
- for (const row of collectedRows)lines.push(exportCols.map((c)=>{
143
- const raw = row[colNameToRowIdx[c]];
144
- const formatter = descriptorMap[c]?.exportFormatter;
145
- return csvCell(formatter ? formatter(raw) : raw);
146
- }).join(","));
192
+ for(let i = 0; i < totalSize; i++){
193
+ const row = collectedRows.get(i);
194
+ if (row) lines.push(exportCols.map((c)=>{
195
+ const raw = row[colNameToRowIdx[c]];
196
+ const formatter = descriptorMap[c]?.exportFormatter;
197
+ return csvCell(formatter ? formatter(raw) : raw);
198
+ }).join(","));
199
+ }
147
200
  triggerCsvDownload(lines.join("\r\n"), filename);
148
201
  onSuccess?.();
149
202
  resolve();
150
- } else {
151
- const nextFrom = collectedRows.length;
152
- activeSessionDataSource.range = Range(nextFrom, Math.min(nextFrom + CHUNK_SIZE, totalSize));
203
+ } else if (collectedRows.size >= nextRequestedFrom && nextRequestedFrom < totalSize) {
204
+ const nextTo = Math.min(nextRequestedFrom + CHUNK_SIZE, totalSize);
205
+ const requestedFrom = nextRequestedFrom;
206
+ nextRequestedFrom = nextTo;
207
+ setTimeout(()=>{
208
+ activeSessionDataSource.range = Range(requestedFrom, nextTo);
209
+ }, 0);
153
210
  }
154
211
  }
155
212
  }
@@ -160,5 +217,5 @@ const exportSessionTableToCsv = async (dataSource, filename = "export.csv", excl
160
217
  }, handleMessage);
161
218
  });
162
219
  };
163
- const exportToCsv = async (dataSource, copyOption = "All", filename = "export.csv", excludeColumns = [], onError, onSuccess, maxRows = MAX_EXPORT_ROWS, columnDescriptors, overrides)=>exportSessionTableToCsv(dataSource, filename, excludeColumns, onError, onSuccess, maxRows, columnDescriptors, copyOption, overrides);
220
+ const exportToCsv = async (dataSource, options)=>exportSessionTableToCsv(dataSource, options);
164
221
  export { exportCsvTemplate, exportSessionTableToCsv, exportToCsv };
@@ -1 +1,2 @@
1
1
  export { exportCsvTemplate, exportSessionTableToCsv, exportToCsv } from "./export-utils.js";
2
+ export { useCsvExport } from "./useCsvExport.js";
@@ -0,0 +1,93 @@
1
+ import { useCallback, useState } from "react";
2
+ import { exportCsvTemplate, exportToCsv } from "./export-utils.js";
3
+ function useCsvExport(propsOrDataSource) {
4
+ const [isExporting, setIsExporting] = useState(false);
5
+ const [error, setError] = useState(null);
6
+ const config = propsOrDataSource && "table" in propsOrDataSource ? {
7
+ dataSource: propsOrDataSource
8
+ } : propsOrDataSource ?? {};
9
+ const { dataSource: defaultDataSource, onError, onSuccess } = config;
10
+ const exportCsv = useCallback(async (options, overrideDataSource)=>{
11
+ const activeDataSource = overrideDataSource ?? defaultDataSource;
12
+ if (!activeDataSource) {
13
+ const err = new Error("useCsvExport: dataSource is required to export to CSV");
14
+ setError(err);
15
+ onError?.(err);
16
+ options?.onError?.(err);
17
+ throw err;
18
+ }
19
+ setIsExporting(true);
20
+ setError(null);
21
+ try {
22
+ await exportToCsv(activeDataSource, {
23
+ ...options,
24
+ onError: (err)=>{
25
+ setError(err);
26
+ onError?.(err);
27
+ options?.onError?.(err);
28
+ },
29
+ onSuccess: ()=>{
30
+ onSuccess?.();
31
+ options?.onSuccess?.();
32
+ }
33
+ });
34
+ } catch (err) {
35
+ const catchedError = err instanceof Error ? err : new Error(String(err));
36
+ setError(catchedError);
37
+ onError?.(catchedError);
38
+ options?.onError?.(catchedError);
39
+ throw catchedError;
40
+ } finally{
41
+ setIsExporting(false);
42
+ }
43
+ }, [
44
+ defaultDataSource,
45
+ onError,
46
+ onSuccess
47
+ ]);
48
+ const exportTemplate = useCallback(async (options, overrideDataSource)=>{
49
+ const activeDataSource = overrideDataSource ?? defaultDataSource;
50
+ if (!activeDataSource) {
51
+ const err = new Error("useCsvExport: dataSource is required to export CSV template");
52
+ setError(err);
53
+ onError?.(err);
54
+ options?.onError?.(err);
55
+ throw err;
56
+ }
57
+ setIsExporting(true);
58
+ setError(null);
59
+ try {
60
+ await exportCsvTemplate(activeDataSource, {
61
+ ...options,
62
+ onError: (err)=>{
63
+ setError(err);
64
+ onError?.(err);
65
+ options?.onError?.(err);
66
+ },
67
+ onSuccess: ()=>{
68
+ onSuccess?.();
69
+ options?.onSuccess?.();
70
+ }
71
+ });
72
+ } catch (err) {
73
+ const catchedError = err instanceof Error ? err : new Error(String(err));
74
+ setError(catchedError);
75
+ onError?.(catchedError);
76
+ options?.onError?.(catchedError);
77
+ throw catchedError;
78
+ } finally{
79
+ setIsExporting(false);
80
+ }
81
+ }, [
82
+ defaultDataSource,
83
+ onError,
84
+ onSuccess
85
+ ]);
86
+ return {
87
+ isExporting,
88
+ error,
89
+ exportCsv,
90
+ exportTemplate
91
+ };
92
+ }
93
+ export { useCsvExport };
@@ -1,6 +1,9 @@
1
1
  import { getTypedValue } from "@vuu-ui/vuu-utils";
2
2
  import { CsvValidationErrorEnum, addCsvFileError, addCsvRowError, createCsvErrorState } from "./csv-errors.js";
3
3
  import { CSV_FIRST_DATA_ROW_NUMBER, MAX_ROWS_IN_CSV } from "./csv-constants.js";
4
+ const INTERNAL_KEY_COLUMNS = new Set([
5
+ "vuuRowNum"
6
+ ]);
4
7
  const validateCsvAgainstSchema = (parsed, tableSchema, options)=>{
5
8
  const schemaColumns = new Map(tableSchema.columns.map((col)=>[
6
9
  col.name,
@@ -8,7 +11,7 @@ const validateCsvAgainstSchema = (parsed, tableSchema, options)=>{
8
11
  ]));
9
12
  const maxRows = options?.maxRows ?? MAX_ROWS_IN_CSV;
10
13
  const errorState = createCsvErrorState();
11
- if (!parsed.header.includes(tableSchema.key)) addCsvFileError(errorState, tableSchema.key, CsvValidationErrorEnum.MISSING_KEY_COLUMN, `CSV must include key column '${tableSchema.key}'.`);
14
+ if (tableSchema.key && !INTERNAL_KEY_COLUMNS.has(tableSchema.key) && !parsed.header.includes(tableSchema.key)) addCsvFileError(errorState, tableSchema.key, CsvValidationErrorEnum.MISSING_KEY_COLUMN, `CSV must include key column '${tableSchema.key}'.`);
12
15
  parsed.header.forEach((column)=>{
13
16
  if (!schemaColumns.has(column)) addCsvFileError(errorState, column, CsvValidationErrorEnum.UNKNOWN_COLUMN, `Column ${column} is not present in table schema.`, column);
14
17
  });
@@ -189,6 +189,15 @@ const useCsvUpload = ({ dataSource, importMode = "direct", importSchema, importT
189
189
  onError?.(void 0);
190
190
  await closePendingEditSession(false);
191
191
  if (operationId !== operationIdRef.current) return;
192
+ if (!isSessionTable(dataSource.table) && ("initialising" === dataSource.status || "unsubscribed" === dataSource.status)) {
193
+ const errorMessage = `CsvUpload requires dataSource to be subscribed before uploading (current status: "${dataSource.status}").`;
194
+ onError?.({
195
+ errors: {
196
+ validationError: createUploadError("validation", errorMessage)
197
+ }
198
+ });
199
+ return;
200
+ }
192
201
  if (void 0 === schema) throw Error("Table schema is not yet available.");
193
202
  if (void 0 === table) throw Error("CsvUpload requires dataSource.table to be defined.");
194
203
  const fileContents = await file.text();
@@ -244,6 +253,8 @@ const useCsvUpload = ({ dataSource, importMode = "direct", importSchema, importT
244
253
  parseOptions,
245
254
  beginEditSession,
246
255
  closePendingEditSession,
256
+ dataSource.status,
257
+ dataSource.table,
247
258
  table,
248
259
  endEditSessionAndNotify,
249
260
  schema
@@ -52,7 +52,7 @@ const useInlineAddRow = ({ columns })=>{
52
52
  cell?.querySelector("input, button, [tabindex]")?.focus();
53
53
  }, []);
54
54
  useEffect(()=>{
55
- editSession.configureNewRow(visibleInsertColumns.map(({ name })=>name));
55
+ editSession.configureNewRow(visibleInsertColumns.map(({ name })=>name), visibleInsertColumns.filter((column)=>false !== column.required).map(({ name })=>name));
56
56
  }, [
57
57
  editSession,
58
58
  visibleInsertColumns
@@ -5,16 +5,38 @@ export type ExportColumnDescriptor<TName extends string = string> = {
5
5
  label?: string;
6
6
  exportFormatter?: (value: unknown) => string;
7
7
  };
8
+ export interface ExportToCsvOptions<TName extends string = string> {
9
+ filename?: string;
10
+ copyOption?: CopyOption;
11
+ excludeColumns?: string[];
12
+ maxRows?: number;
13
+ columnDescriptors?: ExportColumnDescriptor<TName>[];
14
+ overrides?: SessionDataSourceOverrides;
15
+ /** Timeout in milliseconds before the export times out. Default: 30_000ms (0 to disable). */
16
+ timeout?: number;
17
+ onError?: (error: Error) => void;
18
+ onSuccess?: () => void;
19
+ }
20
+ export interface ExportCsvTemplateOptions {
21
+ filename?: string;
22
+ excludeColumns?: string[];
23
+ columns?: string[];
24
+ overrides?: SessionDataSourceOverrides;
25
+ /** Timeout in milliseconds before template generation times out. Default: 10_000ms (0 to disable). */
26
+ timeout?: number;
27
+ onError?: (error: Error) => void;
28
+ onSuccess?: () => void;
29
+ }
8
30
  /** Downloads a single-row CSV containing only the column headers, for use as an import template. */
9
- export declare const exportCsvTemplate: (dataSource: DataSource, filename?: string, excludeColumns?: string[], columns?: string[], overrides?: SessionDataSourceOverrides) => Promise<void>;
31
+ export declare const exportCsvTemplate: (dataSource: DataSource, options?: ExportCsvTemplateOptions) => Promise<void>;
10
32
  /**
11
- * Subscribes to `dataSource` (creating an export session data source if a view data source is passed)
12
- * with a full-range request to drain all rows, serialises them to CSV (excluding internal session columns),
33
+ * Subscribes to `dataSource` (creating an export session data source if a view data source is passed),
34
+ * collects all rows in memory in index order, serialises them to CSV (excluding internal session columns),
13
35
  * then triggers a browser download. The session data source is unsubscribed automatically once the download
14
36
  * is initiated.
15
37
  */
16
- export declare const exportSessionTableToCsv: <TName extends string = string>(dataSource: DataSource, filename?: string, excludeColumns?: string[], onError?: (error: Error) => void, onSuccess?: () => void, maxRows?: number, columnDescriptors?: ExportColumnDescriptor<TName>[], copyOption?: CopyOption, overrides?: SessionDataSourceOverrides) => Promise<void>;
38
+ export declare const exportSessionTableToCsv: <TName extends string = string>(dataSource: DataSource, options?: ExportToCsvOptions<TName>) => Promise<void>;
17
39
  /**
18
- * Creates an export session table from `dataSource` then streams all rows to a CSV download.
40
+ * Creates an export session table from `dataSource`, collects all rows in memory, then triggers a CSV download.
19
41
  */
20
- export declare const exportToCsv: <TName extends string = string>(dataSource: DataSource, copyOption?: CopyOption, filename?: string, excludeColumns?: string[], onError?: (error: Error) => void, onSuccess?: () => void, maxRows?: number, columnDescriptors?: ExportColumnDescriptor<TName>[], overrides?: SessionDataSourceOverrides) => Promise<void>;
42
+ export declare const exportToCsv: <TName extends string = string>(dataSource: DataSource, options?: ExportToCsvOptions<TName>) => Promise<void>;
@@ -1 +1,2 @@
1
- export { exportCsvTemplate, exportSessionTableToCsv, exportToCsv, type ExportColumnDescriptor, } from "./export-utils";
1
+ export { exportCsvTemplate, exportSessionTableToCsv, exportToCsv, type ExportColumnDescriptor, type ExportCsvTemplateOptions, type ExportToCsvOptions, } from "./export-utils";
2
+ export { useCsvExport, type UseCsvExportProps, type UseCsvExportResult, } from "./useCsvExport";
@@ -0,0 +1,14 @@
1
+ import type { DataSource } from "@vuu-ui/vuu-data-types";
2
+ import { type ExportCsvTemplateOptions, type ExportToCsvOptions } from "./export-utils";
3
+ export interface UseCsvExportProps {
4
+ dataSource?: DataSource;
5
+ onError?: (error: Error) => void;
6
+ onSuccess?: () => void;
7
+ }
8
+ export interface UseCsvExportResult {
9
+ isExporting: boolean;
10
+ error: Error | null;
11
+ exportCsv: <TName extends string = string>(options?: ExportToCsvOptions<TName>, overrideDataSource?: DataSource) => Promise<void>;
12
+ exportTemplate: (options?: ExportCsvTemplateOptions, overrideDataSource?: DataSource) => Promise<void>;
13
+ }
14
+ export declare function useCsvExport(propsOrDataSource?: DataSource | UseCsvExportProps): UseCsvExportResult;
@@ -52,6 +52,7 @@ export declare const useInlineAddRow: ({ columns }: UseInlineAddRowProps) => {
52
52
  status?: import("@salt-ds/core").ValidationStatus;
53
53
  editableBulk?: import("@vuu-ui/vuu-data-types").BulkEdit;
54
54
  name: string;
55
+ required?: boolean;
55
56
  serverDataType?: import("@vuu-ui/vuu-protocol-types").VuuColumnDataType;
56
57
  type?: import("@vuu-ui/vuu-data-types").DataValueType;
57
58
  }[];