@vuu-ui/vuu-table-extras 3.5.9 → 3.5.11

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.5.9",
2
+ "version": "3.5.11",
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.5.9",
15
- "@vuu-ui/vuu-protocol-types": "3.5.9"
14
+ "@vuu-ui/vuu-filter-types": "3.5.11",
15
+ "@vuu-ui/vuu-protocol-types": "3.5.11"
16
16
  },
17
17
  "dependencies": {
18
- "@vuu-ui/vuu-codemirror": "3.5.9",
19
- "@vuu-ui/vuu-data-editing": "3.5.9",
20
- "@vuu-ui/vuu-data-react": "3.5.9",
21
- "@vuu-ui/vuu-data-types": "3.5.9",
22
- "@vuu-ui/vuu-table-types": "3.5.9",
23
- "@vuu-ui/vuu-popups": "3.5.9",
24
- "@vuu-ui/vuu-table": "3.5.9",
25
- "@vuu-ui/vuu-utils": "3.5.9",
26
- "@vuu-ui/vuu-ui-controls": "3.5.9",
18
+ "@vuu-ui/vuu-codemirror": "3.5.11",
19
+ "@vuu-ui/vuu-data-editing": "3.5.11",
20
+ "@vuu-ui/vuu-data-react": "3.5.11",
21
+ "@vuu-ui/vuu-data-types": "3.5.11",
22
+ "@vuu-ui/vuu-table-types": "3.5.11",
23
+ "@vuu-ui/vuu-popups": "3.5.11",
24
+ "@vuu-ui/vuu-table": "3.5.11",
25
+ "@vuu-ui/vuu-utils": "3.5.11",
26
+ "@vuu-ui/vuu-ui-controls": "3.5.11",
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",
@@ -8,6 +8,23 @@ const MAX_EXPORT_ROWS = 10000;
8
8
  const CHUNK_SIZE = 1000;
9
9
  const DEFAULT_EXPORT_TIMEOUT = 30000;
10
10
  const DEFAULT_TEMPLATE_TIMEOUT = 10000;
11
+ const resolveHeaderLabel = (colName, headerMapping, fallbackLabel)=>{
12
+ if (headerMapping) if ("function" == typeof headerMapping) {
13
+ const mapped = headerMapping(colName);
14
+ if (mapped) return mapped;
15
+ } else {
16
+ if (headerMapping[colName]) return headerMapping[colName];
17
+ const reverseKey = Object.keys(headerMapping).find((key)=>headerMapping[key] === colName);
18
+ if (reverseKey) return reverseKey;
19
+ }
20
+ return fallbackLabel ?? colName;
21
+ };
22
+ const resolveSchemaColumn = (colName, headerMapping)=>{
23
+ if (headerMapping && "function" != typeof headerMapping) {
24
+ if (headerMapping[colName]) return headerMapping[colName];
25
+ }
26
+ return colName;
27
+ };
11
28
  const csvCell = (value)=>{
12
29
  const s = null == value ? "" : String(value);
13
30
  return s.includes(",") || s.includes('"') || s.includes("\n") || s.includes("\r") ? `"${s.replace(/"/g, '""')}"` : s;
@@ -49,11 +66,16 @@ const isExportableColumn = (descriptor)=>{
49
66
  return true;
50
67
  };
51
68
  const exportCsvTemplate = async (dataSource, options = {})=>{
52
- const { filename = "template.csv", excludeColumns = [], columns, overrides, timeout = DEFAULT_TEMPLATE_TIMEOUT, onError, onSuccess } = options;
69
+ const { filename = "template.csv", excludeColumns = [], columns, headerMapping, overrides, timeout = DEFAULT_TEMPLATE_TIMEOUT, onError, onSuccess } = options;
70
+ const labelMap = new Map();
71
+ if (columns) {
72
+ for (const col of columns)if ("string" != typeof col && col.label) labelMap.set(col.name, col.label);
73
+ }
53
74
  const targetColumnNames = columns ? columns.filter((col)=>"string" == typeof col ? true : isExportableColumn(col) && "client" !== col.source).map((col)=>"string" == typeof col ? col : col.name) : void 0;
54
75
  const targetColumns = targetColumnNames ?? overrides?.columns;
55
- const sessionOverrides = overrides ?? (targetColumns ? {
56
- columns: targetColumns
76
+ const serverTargetColumns = targetColumns?.map((col)=>resolveSchemaColumn(col, headerMapping));
77
+ const sessionOverrides = overrides ?? (serverTargetColumns ? {
78
+ columns: serverTargetColumns
57
79
  } : void 0);
58
80
  if (!isSessionTable(dataSource.table) && dataSource.createSessionDataSource && "initialising" !== dataSource.status && "unsubscribed" !== dataSource.status) try {
59
81
  const sessionDataSource = await dataSource.createSessionDataSource("Empty", "export", sessionOverrides);
@@ -91,7 +113,7 @@ const exportCsvTemplate = async (dataSource, options = {})=>{
91
113
  } else reject(error);
92
114
  return;
93
115
  }
94
- const header = exportCols.map(csvCell).join(",");
116
+ const header = exportCols.map((colName)=>resolveHeaderLabel(colName, headerMapping, labelMap.get(colName))).map(csvCell).join(",");
95
117
  triggerCsvDownload(`${header}\r\n`, filename);
96
118
  cleanup();
97
119
  onSuccess?.();
@@ -124,12 +146,12 @@ const exportCsvTemplate = async (dataSource, options = {})=>{
124
146
  if (onError) return void onError(error);
125
147
  throw error;
126
148
  }
127
- const header = exportCols.map(csvCell).join(",");
149
+ const header = exportCols.map((colName)=>resolveHeaderLabel(colName, headerMapping, labelMap.get(colName))).map(csvCell).join(",");
128
150
  triggerCsvDownload(`${header}\r\n`, filename);
129
151
  onSuccess?.();
130
152
  };
131
153
  const exportSessionTableToCsv = async (dataSource, options = {})=>{
132
- const { filename = "export.csv", copyOption = "All", excludeColumns = [], maxRows = MAX_EXPORT_ROWS, columnDescriptors: rawColumnDescriptors, columns: rawColumns, overrides, timeout = DEFAULT_EXPORT_TIMEOUT, onError, onSuccess } = options;
154
+ const { filename = "export.csv", copyOption = "All", excludeColumns = [], maxRows = MAX_EXPORT_ROWS, columnDescriptors: rawColumnDescriptors, columns: rawColumns, headerMapping, overrides, timeout = DEFAULT_EXPORT_TIMEOUT, onError, onSuccess } = options;
133
155
  const rawInputColumns = rawColumns ?? rawColumnDescriptors;
134
156
  const columnDescriptors = rawInputColumns?.map((col)=>"string" == typeof col ? {
135
157
  name: col
@@ -215,7 +237,7 @@ const exportSessionTableToCsv = async (dataSource, options = {})=>{
215
237
  totalSize = message.size ?? 0;
216
238
  if (0 === totalSize) {
217
239
  cleanup();
218
- const header = exportCols.map((name)=>csvCell(descriptorMap[name]?.label ?? name)).join(",");
240
+ const header = exportCols.map((name)=>csvCell(resolveHeaderLabel(name, headerMapping, descriptorMap[name]?.label))).join(",");
219
241
  triggerCsvDownload(`${header}\r\n`, filename);
220
242
  onSuccess?.();
221
243
  resolve();
@@ -233,7 +255,7 @@ const exportSessionTableToCsv = async (dataSource, options = {})=>{
233
255
  if (collectedRows.size >= totalSize) {
234
256
  cleanup();
235
257
  const lines = [
236
- exportCols.map((name)=>csvCell(descriptorMap[name]?.label ?? name)).join(",")
258
+ exportCols.map((name)=>csvCell(resolveHeaderLabel(name, headerMapping, descriptorMap[name]?.label))).join(",")
237
259
  ];
238
260
  for(let i = 0; i < totalSize; i++){
239
261
  const row = collectedRows.get(i);
@@ -274,4 +296,4 @@ const exportSessionTableToCsv = async (dataSource, options = {})=>{
274
296
  });
275
297
  };
276
298
  const exportToCsv = async (dataSource, options)=>exportSessionTableToCsv(dataSource, options);
277
- export { exportCsvTemplate, exportSessionTableToCsv, exportToCsv, isExportableColumn };
299
+ export { exportCsvTemplate, exportSessionTableToCsv, exportToCsv, isExportableColumn, resolveHeaderLabel, resolveSchemaColumn };
@@ -1,2 +1,2 @@
1
- export { exportCsvTemplate, exportSessionTableToCsv, exportToCsv, isExportableColumn } from "./export-utils.js";
1
+ export { exportCsvTemplate, exportSessionTableToCsv, exportToCsv, isExportableColumn, resolveHeaderLabel, resolveSchemaColumn } from "./export-utils.js";
2
2
  export { useCsvExport } from "./useCsvExport.js";
@@ -6,7 +6,7 @@ function useCsvExport(propsOrDataSource) {
6
6
  const config = propsOrDataSource && "table" in propsOrDataSource ? {
7
7
  dataSource: propsOrDataSource
8
8
  } : propsOrDataSource ?? {};
9
- const { dataSource: defaultDataSource, tableConfig, columns: defaultColumns, onError, onSuccess } = config;
9
+ const { dataSource: defaultDataSource, tableConfig, columns: defaultColumns, headerMapping: defaultHeaderMapping, onError, onSuccess } = config;
10
10
  const exportCsv = useCallback(async (options, overrideDataSource)=>{
11
11
  const activeDataSource = overrideDataSource ?? defaultDataSource;
12
12
  if (!activeDataSource) {
@@ -20,8 +20,12 @@ function useCsvExport(propsOrDataSource) {
20
20
  setError(null);
21
21
  try {
22
22
  const columnsToExport = options?.columns ?? options?.columnDescriptors ?? defaultColumns ?? tableConfig?.columns;
23
+ const effectiveHeaderMapping = options?.headerMapping ?? defaultHeaderMapping;
23
24
  await exportToCsv(activeDataSource, {
24
25
  columns: columnsToExport,
26
+ ...void 0 !== effectiveHeaderMapping ? {
27
+ headerMapping: effectiveHeaderMapping
28
+ } : {},
25
29
  ...options,
26
30
  onError: (err)=>{
27
31
  setError(err);
@@ -45,6 +49,7 @@ function useCsvExport(propsOrDataSource) {
45
49
  }, [
46
50
  defaultDataSource,
47
51
  defaultColumns,
52
+ defaultHeaderMapping,
48
53
  tableConfig,
49
54
  onError,
50
55
  onSuccess
@@ -62,8 +67,12 @@ function useCsvExport(propsOrDataSource) {
62
67
  setError(null);
63
68
  try {
64
69
  const templateColumns = options?.columns ?? defaultColumns ?? tableConfig?.columns;
70
+ const effectiveHeaderMapping = options?.headerMapping ?? defaultHeaderMapping;
65
71
  await exportCsvTemplate(activeDataSource, {
66
72
  columns: templateColumns,
73
+ ...void 0 !== effectiveHeaderMapping ? {
74
+ headerMapping: effectiveHeaderMapping
75
+ } : {},
67
76
  ...options,
68
77
  onError: (err)=>{
69
78
  setError(err);
@@ -87,6 +96,7 @@ function useCsvExport(propsOrDataSource) {
87
96
  }, [
88
97
  defaultDataSource,
89
98
  defaultColumns,
99
+ defaultHeaderMapping,
90
100
  tableConfig,
91
101
  onError,
92
102
  onSuccess
@@ -5,3 +5,4 @@ export { validateCsvAgainstSchema } from "./parse/csv-schema-validation.js";
5
5
  export { parseCsv } from "./parse/csv-parse.js";
6
6
  export { CsvParseErrorEnum, CsvValidationErrorEnum } from "./parse/csv-errors.js";
7
7
  export { CSV_FIRST_DATA_ROW_NUMBER, CSV_HEADER_ROW_NUMBER } from "./parse/csv-constants.js";
8
+ export { mapCsvHeaders } from "./parse/csv-upload-utils.js";
@@ -55,7 +55,16 @@ const parseCsv = (csvText, options)=>{
55
55
  addCsvFileError(errorState, "*", CsvParseErrorEnum.EMPTY_FILE, "CSV file is empty.");
56
56
  return withError("CSV file is empty.", errorState.errorMap, errorState.errors);
57
57
  }
58
- const header = records[0].map((value)=>value.trim());
58
+ let header;
59
+ let rows;
60
+ if (options?.customHeaders && options.customHeaders.length > 0) {
61
+ header = options.customHeaders.map((value)=>value.trim());
62
+ const hasHeaderRow = options.hasHeaderRow ?? true;
63
+ rows = hasHeaderRow ? records.slice(1) : records;
64
+ } else {
65
+ header = records[0].map((value)=>value.trim());
66
+ rows = records.slice(1);
67
+ }
59
68
  if (0 === header.length) addCsvFileError(errorState, "*", CsvParseErrorEnum.EMPTY_HEADER_COLUMN, "CSV header contains empty column names.");
60
69
  header.forEach((col, columnIndex)=>{
61
70
  if (0 === col.length) addCsvFileError(errorState, `column_${columnIndex + 1}`, CsvParseErrorEnum.EMPTY_HEADER_COLUMN, "CSV header contains empty column names.");
@@ -66,9 +75,9 @@ const parseCsv = (csvText, options)=>{
66
75
  addCsvFileError(errorState, duplicateHeader, CsvParseErrorEnum.DUPLICATE_HEADER_COLUMN, `CSV header contains duplicate column '${duplicateHeader}'.`, duplicateHeader);
67
76
  return withError(`CSV header contains duplicate column '${duplicateHeader}'.`, errorState.errorMap, errorState.errors, header, []);
68
77
  }
69
- const rows = records.slice(1);
78
+ const firstDataRowNumber = options?.customHeaders && false === options.hasHeaderRow ? 1 : CSV_FIRST_DATA_ROW_NUMBER;
70
79
  rows.forEach((row, rowIndex)=>{
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(","));
80
+ if (row.length !== header.length) addCsvRowError(errorState, rowIndex + firstDataRowNumber, "*", 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
81
  });
73
82
  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
83
  return {
@@ -1,5 +1,11 @@
1
1
  import { isRpcError, metadataKeys } from "@vuu-ui/vuu-utils";
2
2
  import { CSV_FIRST_DATA_ROW_NUMBER } from "./csv-constants.js";
3
+ const mapCsvHeaders = (headers, headerMapping)=>{
4
+ if (!headerMapping) return [
5
+ ...headers
6
+ ];
7
+ return headers.map((col)=>"function" == typeof headerMapping ? headerMapping(col) : headerMapping[col] ?? col);
8
+ };
3
9
  const isRecord = (value)=>"object" == typeof value && null !== value;
4
10
  const toErrorMessage = (error)=>error instanceof Error ? error.message : String(error);
5
11
  const isCsvParseError = (value)=>isRecord(value) && "message" in value && "string" == typeof value.message && "errors" in value && Array.isArray(value.errors) && "errorMap" in value && isRecord(value.errorMap);
@@ -101,4 +107,4 @@ const waitForSessionErrors = (isRemote, sessionErrors, receivedRowKeys, expected
101
107
  }, timeoutMs);
102
108
  });
103
109
  };
104
- export { buildRowErrorMessage, createUploadError, executeBatchRpcCalls, getInlineRowErrorMessage, getRowVuuMsgError, hasFileParseErrors, isCsvParseError, mergeValidationWithParseErrors, normalizeTableData, toErrorMessage, toValidationErrorsFromParseRowErrors, waitForSessionErrors };
110
+ export { buildRowErrorMessage, createUploadError, executeBatchRpcCalls, getInlineRowErrorMessage, getRowVuuMsgError, hasFileParseErrors, isCsvParseError, mapCsvHeaders, mergeValidationWithParseErrors, normalizeTableData, toErrorMessage, toValidationErrorsFromParseRowErrors, waitForSessionErrors };
@@ -2,10 +2,10 @@ import { Range, isSessionTable } from "@vuu-ui/vuu-utils";
2
2
  import { useCallback, useMemo, useRef, useState } from "react";
3
3
  import { parseCsv } from "./parse/csv-parse.js";
4
4
  import { CsvValidationErrorEnum, validateCsvAgainstSchema } from "./parse/csv-schema-validation.js";
5
- import { createUploadError, hasFileParseErrors, isCsvParseError, mergeValidationWithParseErrors, toErrorMessage } from "./parse/csv-upload-utils.js";
5
+ import { createUploadError, hasFileParseErrors, isCsvParseError, mapCsvHeaders, mergeValidationWithParseErrors, toErrorMessage } from "./parse/csv-upload-utils.js";
6
6
  import { useImportSchema } from "./session/useImportSchema.js";
7
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 })=>{
8
+ const useCsvUpload = ({ dataSource, importMode = "direct", importSchema, importTable, onImportSessionEnded, onImportSessionReady, onError, onImported, onPreview, onProcessingStarted, maxRows, parseOptions, customHeaders, hasHeaderRow, headerMapping, rowDefaults, validators })=>{
9
9
  const [validation, setValidation] = useState();
10
10
  const [isProcessingFile, setIsProcessingFile] = useState(false);
11
11
  const [isImporting, setIsImporting] = useState(false);
@@ -58,9 +58,18 @@ const useCsvUpload = ({ dataSource, importMode = "direct", importSchema, importT
58
58
  }
59
59
  if (void 0 === schema) throw Error("Table schema is not yet available.");
60
60
  if (void 0 === table) throw Error("CsvUpload requires dataSource.table to be defined.");
61
+ const effectiveParseOptions = {
62
+ ...parseOptions,
63
+ ...void 0 !== customHeaders ? {
64
+ customHeaders
65
+ } : {},
66
+ ...void 0 !== hasHeaderRow ? {
67
+ hasHeaderRow
68
+ } : {}
69
+ };
61
70
  const fileContents = await file.text();
62
71
  if (operationId !== operationIdRef.current) return;
63
- const parsedCsv = parseCsv(fileContents, parseOptions);
72
+ let parsedCsv = parseCsv(fileContents, effectiveParseOptions);
64
73
  if (parsedCsv.error && hasFileParseErrors(parsedCsv.error)) {
65
74
  setValidation(void 0);
66
75
  handleError({
@@ -70,6 +79,10 @@ const useCsvUpload = ({ dataSource, importMode = "direct", importSchema, importT
70
79
  });
71
80
  return;
72
81
  }
82
+ if (headerMapping) parsedCsv = {
83
+ ...parsedCsv,
84
+ header: mapCsvHeaders(parsedCsv.header, headerMapping)
85
+ };
73
86
  const schemaValidation = validateCsvAgainstSchema(parsedCsv, schema, {
74
87
  maxRows,
75
88
  validators
@@ -1,44 +1,89 @@
1
1
  import type { CopyOption, DataSource, SessionDataSourceOverrides } from "@vuu-ui/vuu-data-types";
2
2
  import type { ColumnDescriptor, RowAccessor } from "@vuu-ui/vuu-table-types";
3
- export type { RowAccessor };
3
+ import type { CsvHeaderMapping } from "../csv-upload/parse/csv-upload-utils";
4
+ export type { RowAccessor, CsvHeaderMapping };
4
5
  export type ExportServerColumnDescriptor = Omit<ColumnDescriptor, "source" | "exportFormatter"> & {
6
+ /** Unique identifier of the column. */
5
7
  name: string;
8
+ /** Marks the column as backed by the server. */
6
9
  source?: "server";
10
+ /** Display label for the CSV header row. If omitted, `name` is used. */
7
11
  label?: string;
12
+ /** Whether to include this column in the CSV export. Defaults to true. */
8
13
  exportable?: boolean;
14
+ /** Custom formatter function for transforming cell values during export. */
9
15
  exportFormatter?: (value: unknown, row: RowAccessor) => string;
10
16
  };
11
17
  export type ExportClientColumnDescriptor = Omit<ColumnDescriptor, "source" | "exportFormatter"> & {
18
+ /** Unique identifier of the column. */
12
19
  name: string;
20
+ /** Marks the column as client-side calculated. */
13
21
  source: "client";
22
+ /** Display label for the CSV header row. If omitted, `name` is used. */
14
23
  label?: string;
24
+ /** Whether to include this column in the CSV export. Defaults to true. */
15
25
  exportable?: boolean;
26
+ /** Required formatter function for deriving client column values from row data. */
16
27
  exportFormatter: (value: unknown, row: RowAccessor) => string;
17
28
  };
18
29
  export type ExportColumnDescriptor = ExportServerColumnDescriptor | ExportClientColumnDescriptor;
19
30
  export interface ExportToCsvOptions {
31
+ /** Downloaded filename. Defaults to `"export.csv"`. */
20
32
  filename?: string;
33
+ /** Row copy mode: `"All"` exports every row, `"Selected"` exports only selected rows. Defaults to `"All"`. */
21
34
  copyOption?: CopyOption;
35
+ /** Specific column names to exclude from export in addition to internal session columns (`vuuMsg`, `vuuAction`, `vuuRowNum`). */
22
36
  excludeColumns?: string[];
37
+ /** Maximum number of data rows allowed to export. Throws if the server reports more rows. Defaults to `10_000`. */
23
38
  maxRows?: number;
39
+ /** Explicit column definitions or overrides. Alias for `columns`. */
24
40
  columnDescriptors?: readonly (ExportColumnDescriptor | ColumnDescriptor)[];
41
+ /** Specific subset, order, or descriptors of columns to include in the export. */
25
42
  columns?: readonly (string | ExportColumnDescriptor | ColumnDescriptor)[];
43
+ /**
44
+ * Optional mapping from schema column name to exported header name,
45
+ * or a resolver function `(col: string) => string`.
46
+ */
47
+ headerMapping?: CsvHeaderMapping;
48
+ /** Session datasource overrides passed to `createSessionDataSource`. */
26
49
  overrides?: SessionDataSourceOverrides;
27
50
  /** Timeout in milliseconds before the export times out. Default: 30_000ms (0 to disable). */
28
51
  timeout?: number;
52
+ /** Callback invoked when an export error occurs. */
29
53
  onError?: (error: Error) => void;
54
+ /** Callback invoked once the CSV download is triggered in the browser. */
30
55
  onSuccess?: () => void;
31
56
  }
32
57
  export interface ExportCsvTemplateOptions {
58
+ /** Downloaded filename. Defaults to `"template.csv"`. */
33
59
  filename?: string;
60
+ /** Specific column names to exclude from the template. */
34
61
  excludeColumns?: string[];
62
+ /** Specific subset and order of columns to include in the template. When descriptors are passed, `label` is used as the header. */
35
63
  columns?: readonly (string | ColumnDescriptor)[];
64
+ /**
65
+ * Optional mapping from schema column name to exported header name,
66
+ * or a resolver function `(col: string) => string`.
67
+ */
68
+ headerMapping?: CsvHeaderMapping;
69
+ /** Divergent table schema overrides passed to `createSessionDataSource`. */
36
70
  overrides?: SessionDataSourceOverrides;
37
71
  /** Timeout in milliseconds before template generation times out. Default: 10_000ms (0 to disable). */
38
72
  timeout?: number;
73
+ /** Callback invoked when a template export error occurs. */
39
74
  onError?: (error: Error) => void;
75
+ /** Callback invoked once the template download is triggered in the browser. */
40
76
  onSuccess?: () => void;
41
77
  }
78
+ /**
79
+ * Resolves an exported CSV column header label, checking headerMapping (direct or reverse)
80
+ * before falling back to the column descriptor's label or column name.
81
+ */
82
+ export declare const resolveHeaderLabel: (colName: string, headerMapping?: CsvHeaderMapping, fallbackLabel?: string) => string;
83
+ /**
84
+ * Maps a column name to its underlying schema column name if present in headerMapping.
85
+ */
86
+ export declare const resolveSchemaColumn: (colName: string, headerMapping?: CsvHeaderMapping) => string;
42
87
  export declare const isExportableColumn: (descriptor?: ExportColumnDescriptor | ColumnDescriptor) => boolean;
43
88
  /** Downloads a single-row CSV containing only the column headers, for use as an import template. */
44
89
  export declare const exportCsvTemplate: (dataSource: DataSource, options?: ExportCsvTemplateOptions) => Promise<void>;
@@ -1,2 +1,2 @@
1
- export { exportCsvTemplate, exportSessionTableToCsv, exportToCsv, isExportableColumn, type ExportClientColumnDescriptor, type ExportColumnDescriptor, type ExportCsvTemplateOptions, type ExportServerColumnDescriptor, type ExportToCsvOptions, type RowAccessor, } from "./export-utils";
1
+ export { exportCsvTemplate, exportSessionTableToCsv, exportToCsv, isExportableColumn, resolveHeaderLabel, resolveSchemaColumn, type ExportClientColumnDescriptor, type ExportColumnDescriptor, type ExportCsvTemplateOptions, type ExportServerColumnDescriptor, type ExportToCsvOptions, type RowAccessor, } from "./export-utils";
2
2
  export { useCsvExport, type UseCsvExportProps, type UseCsvExportResult, } from "./useCsvExport";
@@ -1,17 +1,41 @@
1
1
  import type { DataSource } from "@vuu-ui/vuu-data-types";
2
2
  import type { ColumnDescriptor, TableConfig } from "@vuu-ui/vuu-table-types";
3
- import { type ExportColumnDescriptor, type ExportCsvTemplateOptions, type ExportToCsvOptions } from "./export-utils";
3
+ import { type CsvHeaderMapping, type ExportColumnDescriptor, type ExportCsvTemplateOptions, type ExportToCsvOptions } from "./export-utils";
4
4
  export interface UseCsvExportProps {
5
+ /** The target Vuu DataSource to export data or templates from. */
5
6
  dataSource?: DataSource;
7
+ /** Optional table configuration used to derive visible columns, order, and formatters. */
6
8
  tableConfig?: TableConfig;
9
+ /** Explicit columns to export when not derived from tableConfig or options. */
7
10
  columns?: readonly (string | ExportColumnDescriptor | ColumnDescriptor)[];
11
+ /**
12
+ * Optional mapping from schema column name to exported header name,
13
+ * or a resolver function `(col: string) => string`.
14
+ */
15
+ headerMapping?: CsvHeaderMapping;
16
+ /** Default error callback for export operations. */
8
17
  onError?: (error: Error) => void;
18
+ /** Default success callback when an export or template download is initiated. */
9
19
  onSuccess?: () => void;
10
20
  }
11
21
  export interface UseCsvExportResult {
22
+ /** True while an export or template download operation is in progress. */
12
23
  isExporting: boolean;
24
+ /** The most recent export error, or null if no error occurred. */
13
25
  error: Error | null;
26
+ /**
27
+ * Exports table data to CSV and triggers a file download in the browser.
28
+ *
29
+ * @param options - Export options including filename, columns, and limits.
30
+ * @param overrideDataSource - Optional DataSource to export instead of the default.
31
+ */
14
32
  exportCsv: (options?: ExportToCsvOptions, overrideDataSource?: DataSource) => Promise<void>;
33
+ /**
34
+ * Exports a header-only CSV template suitable for importing back into the table.
35
+ *
36
+ * @param options - Template options including filename, columns, and header mapping.
37
+ * @param overrideDataSource - Optional DataSource to export template for instead of the default.
38
+ */
15
39
  exportTemplate: (options?: ExportCsvTemplateOptions, overrideDataSource?: DataSource) => Promise<void>;
16
40
  }
17
41
  export declare function useCsvExport(propsOrDataSource?: DataSource | UseCsvExportProps): UseCsvExportResult;
@@ -4,39 +4,59 @@ import type { CsvParseError, CsvParseOptions } from "./parse/csv-parse";
4
4
  import type { CsvValidationStructuredError, CsvColumnValidator } from "./parse/csv-schema-validation";
5
5
  import type { DataSource, TableSchema } from "@vuu-ui/vuu-data-types";
6
6
  import type { VuuTable } from "@vuu-ui/vuu-protocol-types";
7
- import type { CsvUploadTableData } from "./parse/csv-upload-utils";
7
+ import type { CsvUploadTableData, CsvHeaderMapping } from "./parse/csv-upload-utils";
8
8
  export type CsvUploadImportedResult = {
9
+ /** Normalized table column names and 2D row data imported into the table. */
9
10
  tableData: CsvUploadTableData;
10
11
  };
11
12
  export type CsvUploadPreviewResult = {
13
+ /** The live session DataSource containing the staged rows. */
12
14
  dataSource: DataSource;
15
+ /** The active EditSession managing the staged import. */
13
16
  editSession: EditSession;
17
+ /** Normalized table column names and 2D row data staged for import. */
14
18
  tableData: CsvUploadTableData;
15
19
  };
16
20
  export type CsvUploadErrors = {
21
+ /** Error when CSV columns do not match the target or import table schema. */
17
22
  schemaError?: CsvUploadError;
23
+ /** Error when CSV syntax or cell values fail type coercion or custom validation. */
18
24
  validationError?: CsvUploadError;
25
+ /** Error during server-side row staging or session commitment. */
19
26
  importError?: CsvUploadError;
20
27
  };
21
28
  export type CsvUploadError = {
29
+ /** Human-readable error description. */
22
30
  message: string;
31
+ /** Raw CSV parser error details, if the failure occurred during file parsing. */
23
32
  parseError?: CsvParseError;
33
+ /** Structured validation error map, if the failure occurred during schema/row validation. */
24
34
  validationError?: CsvValidationStructuredError;
35
+ /** Origin phase of the error. */
25
36
  source: "schema" | "validation" | "import";
26
37
  };
27
38
  export type CsvUploadErrorResult = {
39
+ /** Container of populated upload errors by category. */
28
40
  errors: CsvUploadErrors;
29
41
  };
30
42
  export type CsvUploadSessionTable = VuuTable;
31
43
  export type CsvUploadSessionEndReason = "saved" | "discarded" | "failed";
32
44
  export type CsvUploadSessionEndResult = {
45
+ /** Outcome reason for ending the session: `"saved"`, `"discarded"`, or `"failed"`. */
33
46
  reason: CsvUploadSessionEndReason;
47
+ /** The Vuu session table descriptor that was closed, if one was active. */
34
48
  sessionTable?: CsvUploadSessionTable;
35
49
  };
36
50
  export type CsvUploadPhase = "idle" | "processing" | "preview-ready" | "importing" | "imported" | "failed";
37
51
  export interface CsvUploadProps {
52
+ /** Optional content rendered below the drop zone inside the dialog content area (e.g., error preview table). */
38
53
  children?: ReactNode;
54
+ /**
55
+ * The Vuu DataSource for the target table. Must be subscribed (`status: "subscribed"`)
56
+ * and support session creation, row insertion, and edit session completion.
57
+ */
39
58
  dataSource: DataSource;
59
+ /** Renders the upload content inline without a Dialog wrapper. Defaults to `false`. */
40
60
  embedded?: boolean;
41
61
  /**
42
62
  * Schema of the import table, where it differs from the target table. Used to validate
@@ -46,20 +66,41 @@ export interface CsvUploadProps {
46
66
  importSchema?: TableSchema;
47
67
  /** Expected import table, used to validate the session table returned by the server. */
48
68
  importTable?: VuuTable;
69
+ /** Fired when the server-side session is fully hydrated and a preview session DataSource is available. */
49
70
  onImportSessionReady?: (dataSource: DataSource) => void;
71
+ /** Fired when the import session ends (whether by save, discard, or failure). */
50
72
  onImportSessionEnded?: (result: CsvUploadSessionEndResult) => void;
73
+ /** Fired when any error occurs, or called with `undefined` when errors are cleared. */
51
74
  onError?: (result: CsvUploadErrorResult | undefined) => void;
75
+ /** Fired after a successful import commit with the normalized imported table data. */
52
76
  onImported?: (result: CsvUploadImportedResult) => void;
77
+ /** In preview mode, fired when Import is pressed with the active EditSession and session DataSource. */
53
78
  onPreview?: (result: CsvUploadPreviewResult) => void;
79
+ /** Fired when file parsing and validation starts. */
54
80
  onProcessingStarted?: () => void;
81
+ /** Header title text displayed in the dialog header. Defaults to `"Import CSV"`. */
55
82
  dialogTitle?: string;
83
+ /** Maximum number of data rows permitted in the uploaded CSV. Defaults to `10000`. */
56
84
  maxRows?: number;
85
+ /** Fired when the user clicks Cancel. */
57
86
  onCancel?: () => void;
87
+ /** Fired after a successful import has completed. */
58
88
  onClose?: () => void;
89
+ /** Controls dialog open state. When provided, the dialog is fully controlled. */
59
90
  open?: boolean;
91
+ /** Parser configuration options such as requiring quotes or custom headers. */
60
92
  parseOptions?: CsvParseOptions;
93
+ /** Optional custom header columns to use instead of reading headers from the CSV file. */
94
+ customHeaders?: string[];
95
+ /** Whether the CSV contains a header row. Defaults to true. When false and customHeaders is provided, row 0 is treated as data. */
96
+ hasHeaderRow?: boolean;
97
+ /** Optional mapping or resolver function from CSV header column names to schema column names. */
98
+ headerMapping?: CsvHeaderMapping;
99
+ /** Import mode: `"direct"` commits immediately on Import; `"preview"` returns the session via `onPreview`. Defaults to `"direct"`. */
61
100
  importMode?: "direct" | "preview";
101
+ /** Default column values merged into rows where a column is omitted in the CSV. Pass a stable reference. */
62
102
  rowDefaults?: RowDefaultDataItemValues;
103
+ /** Custom per-column validation functions. */
63
104
  validators?: Record<string, CsvColumnValidator>;
64
105
  }
65
106
  export declare const CsvUpload: (props: CsvUploadProps) => import("react/jsx-runtime").JSX.Element;
@@ -5,3 +5,4 @@ export { validateCsvAgainstSchema, type CsvColumnValidator, type CsvValidationOp
5
5
  export { parseCsv, type CsvParseOptions, type CsvParseResult, } from "./parse/csv-parse";
6
6
  export { CsvValidationErrorEnum, CsvParseErrorEnum, type CsvError, type CsvErrorMap, type CsvStructuredError, type CsvValidationError, type CsvValidationErrorMap, type CsvValidationErrorType, type CsvValidationStructuredError, type CsvParseError, type CsvParseErrorMap, } from "./parse/csv-errors";
7
7
  export { CSV_FIRST_DATA_ROW_NUMBER, CSV_HEADER_ROW_NUMBER, } from "./parse/csv-constants";
8
+ export { mapCsvHeaders, type CsvHeaderMapping, type CsvUploadTableData, } from "./parse/csv-upload-utils";
@@ -1,11 +1,28 @@
1
1
  import { type CsvParseError } from "./csv-errors";
2
2
  export { CsvParseErrorEnum, type CsvError, type CsvErrorMap, type CsvStructuredError, type CsvParseError, type CsvParseErrorMap, } from "./csv-errors";
3
3
  export type CsvParseResult = {
4
+ /** Error object if file parsing or structure validation failed. */
4
5
  error?: CsvParseError;
6
+ /** Resolved header column names. */
5
7
  header: string[];
8
+ /** 2D array of string cell values for data rows. */
6
9
  rows: string[][];
7
10
  };
8
11
  export type CsvParseOptions = {
12
+ /**
13
+ * When true, any field containing commas, quotes, or newlines must be enclosed in double quotes.
14
+ */
9
15
  requireQuotedValues?: boolean;
16
+ /**
17
+ * Optional custom column headers to use. If provided and hasHeaderRow is false,
18
+ * all rows in the CSV are treated as data rows. If hasHeaderRow is true (or omitted),
19
+ * the first row is discarded and customHeaders are used.
20
+ */
21
+ customHeaders?: string[];
22
+ /**
23
+ * Whether the CSV file contains a header row. Defaults to true.
24
+ * When false, customHeaders must be provided.
25
+ */
26
+ hasHeaderRow?: boolean;
10
27
  };
11
28
  export declare const parseCsv: (csvText: string, options?: CsvParseOptions) => CsvParseResult;
@@ -4,21 +4,38 @@ import type { CsvValidationError, CsvValidationErrorMap, CsvParseError } from ".
4
4
  import type { CsvParseResult } from "./csv-parse";
5
5
  export { CsvValidationErrorEnum, type CsvValidationError, type CsvValidationErrorMap, type CsvValidationErrorType, type CsvValidationStructuredError, } from "./csv-errors";
6
6
  export interface ImportSchemaColumn extends SchemaColumn {
7
+ /** When true, flags an error if a cell in this column is left empty in the CSV. */
7
8
  required?: boolean;
8
9
  }
9
10
  export interface ImportTableSchema extends Omit<TableSchema, "columns"> {
11
+ /** Columns definition for the import table schema. */
10
12
  readonly columns: readonly ImportSchemaColumn[];
11
13
  }
14
+ /**
15
+ * Custom validation function for a specific column cell value.
16
+ *
17
+ * @param value - The raw string value from the CSV cell.
18
+ * @param columnName - The schema column name.
19
+ * @param row - Key-value map of all raw column values in the same row.
20
+ * @returns `true` if valid, `false` for generic error, or an error message string.
21
+ */
12
22
  export type CsvColumnValidator = (value: string, columnName: string, row: Record<string, string>) => boolean | string;
13
23
  export type CsvValidationResult = {
24
+ /** Validated column names from the CSV header. */
14
25
  columns: string[];
26
+ /** Two-level map of errors (fileErrors and rowErrors). */
15
27
  errorMap: CsvValidationErrorMap;
28
+ /** Flat array of all validation error descriptors. */
16
29
  errors: CsvValidationError[];
30
+ /** Parse error from the raw CSV parser, if any occurred. */
17
31
  parseRowError?: CsvParseError;
32
+ /** Parsed rows coerced to typed Vuu values. */
18
33
  rows: Record<string, VuuRowDataItemType>[];
19
34
  };
20
35
  export type CsvValidationOptions = {
36
+ /** Maximum number of data rows permitted in the CSV. Defaults to `10000`. */
21
37
  maxRows?: number;
38
+ /** Custom validation functions keyed by column name. */
22
39
  validators?: Record<string, CsvColumnValidator>;
23
40
  };
24
41
  export declare const validateCsvAgainstSchema: (parsed: CsvParseResult, tableSchema: TableSchema | ImportTableSchema, options?: CsvValidationOptions) => CsvValidationResult;
@@ -7,6 +7,16 @@ export type CsvUploadTableData = {
7
7
  columns: string[];
8
8
  rows: VuuRowDataItemType[][];
9
9
  };
10
+ /**
11
+ * Optional mapping from CSV header column names to schema column names,
12
+ * or a resolver function `(header: string) => string`.
13
+ */
14
+ export type CsvHeaderMapping = Record<string, string> | ((header: string) => string);
15
+ /**
16
+ * Maps raw CSV header column names to their corresponding schema column names
17
+ * using a mapping dictionary or resolver function.
18
+ */
19
+ export declare const mapCsvHeaders: (headers: readonly string[], headerMapping?: CsvHeaderMapping) => string[];
10
20
  export declare const toErrorMessage: (error: unknown) => string;
11
21
  export declare const isCsvParseError: (value: unknown) => value is CsvParseError;
12
22
  export declare const hasFileParseErrors: (parseError: CsvParseError) => boolean;
@@ -3,9 +3,12 @@ import type { RowDefaultDataItemValues } from "@vuu-ui/vuu-data-editing";
3
3
  import type { VuuTable } from "@vuu-ui/vuu-protocol-types";
4
4
  import { type CsvParseOptions } from "./parse/csv-parse";
5
5
  import { type CsvValidationResult, type CsvColumnValidator } from "./parse/csv-schema-validation";
6
+ import { type CsvHeaderMapping } from "./parse/csv-upload-utils";
6
7
  import type { CsvUploadErrorResult, CsvUploadImportedResult, CsvUploadPreviewResult, CsvUploadSessionEndResult, CsvUploadSessionTable } from "./CsvUpload";
7
8
  export interface CsvUploadHookProps {
9
+ /** The target Vuu DataSource to upload CSV data into. */
8
10
  dataSource: DataSource;
11
+ /** Import mode: `"direct"` commits immediately; `"preview"` emits session for caller review. Defaults to `"direct"`. */
9
12
  importMode?: "direct" | "preview";
10
13
  /**
11
14
  * Schema of the import table, where it differs from the target table. Used to validate
@@ -15,29 +18,55 @@ export interface CsvUploadHookProps {
15
18
  importSchema?: TableSchema;
16
19
  /** Expected import table, used to validate the session table returned by the server. */
17
20
  importTable?: VuuTable;
21
+ /** Maximum number of data rows permitted in the CSV. Defaults to `10000`. */
18
22
  maxRows?: number;
23
+ /** Callback fired when the import session ends (by save, discard, or failure). */
19
24
  onImportSessionEnded?: (result: CsvUploadSessionEndResult) => void;
25
+ /** Callback fired when the staged session DataSource is ready for preview. */
20
26
  onImportSessionReady?: (dataSource: DataSource) => void;
27
+ /** Callback fired when an error occurs, or with `undefined` when errors clear. */
21
28
  onError?: (result: CsvUploadErrorResult | undefined) => void;
29
+ /** Callback fired after a successful import commit with the normalized table data. */
22
30
  onImported?: (result: CsvUploadImportedResult) => void;
31
+ /** In preview mode, callback fired when Import is triggered with the live EditSession. */
23
32
  onPreview?: (result: CsvUploadPreviewResult) => void;
33
+ /** Callback fired when a dropped or selected file begins processing. */
24
34
  onProcessingStarted?: () => void;
35
+ /** Options passed to the CSV parser (e.g., requireQuotedValues, customHeaders, hasHeaderRow). */
25
36
  parseOptions?: CsvParseOptions;
37
+ /** Optional custom header columns to use instead of reading headers from the CSV file. */
38
+ customHeaders?: string[];
39
+ /** Whether the CSV contains a header row. Defaults to true. When false and customHeaders is provided, row 0 is treated as data. */
40
+ hasHeaderRow?: boolean;
41
+ /** Optional mapping or resolver function from CSV header column names to schema column names. */
42
+ headerMapping?: CsvHeaderMapping;
26
43
  /** Default column values applied to every addRow call. Pass a stable reference — a new object triggers EditSession recreation. */
27
44
  rowDefaults?: RowDefaultDataItemValues;
45
+ /** Custom per-column validation functions. */
28
46
  validators?: Record<string, CsvColumnValidator>;
29
47
  }
30
48
  export type UseCsvUploadReturn = {
49
+ /** True when the CSV passed validation, rows staged without server errors, and import can be committed. */
31
50
  canImport: boolean;
51
+ /** Discards the current edit session and resets upload state. */
32
52
  cancelImport: () => Promise<void>;
53
+ /** Commits the staged session (in direct mode) or fires onPreview (in preview mode). */
33
54
  importData: () => Promise<boolean>;
55
+ /** True while the import commit is in progress. */
34
56
  isImporting: boolean;
57
+ /** True while parsing the CSV and staging rows on the server. */
35
58
  isProcessingFile: boolean;
59
+ /** Event handler for file drop zone elements. */
36
60
  onDrop: (_event: React.DragEvent<HTMLDivElement>, files: File[]) => void;
61
+ /** Event handler for file input change elements. */
37
62
  onTriggerChange: (_event: React.ChangeEvent<HTMLInputElement>, files: File[]) => void;
63
+ /** Active session table descriptor from the server. */
38
64
  sessionTable: CsvUploadSessionTable | undefined;
65
+ /** Active table schema used for validation. */
39
66
  schema: TableSchema | undefined;
67
+ /** Current validation result including parsed rows and errors. */
40
68
  validation: CsvValidationResult | undefined;
69
+ /** Active error result, if any. */
41
70
  error: CsvUploadErrorResult | undefined;
42
71
  };
43
- export declare const useCsvUpload: ({ dataSource, importMode, importSchema, importTable, onImportSessionEnded, onImportSessionReady, onError, onImported, onPreview, onProcessingStarted, maxRows, parseOptions, rowDefaults, validators, }: CsvUploadHookProps) => UseCsvUploadReturn;
72
+ export declare const useCsvUpload: ({ dataSource, importMode, importSchema, importTable, onImportSessionEnded, onImportSessionReady, onError, onImported, onPreview, onProcessingStarted, maxRows, parseOptions, customHeaders, hasHeaderRow, headerMapping, rowDefaults, validators, }: CsvUploadHookProps) => UseCsvUploadReturn;