@vuu-ui/vuu-table-extras 4.0.0-alpha.2 → 4.0.0-alpha.21

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 (79) hide show
  1. package/package.json +18 -17
  2. package/src/calculated-column/CalculatedColumnPanel.js +2 -2
  3. package/src/calculated-column/useEditCalculatedColumn.js +1 -1
  4. package/src/cell-renderers/pct-progress-cell/PctProgressCell.css.js +2 -2
  5. package/src/cell-renderers/progress-cell/ProgressCell.css.js +2 -2
  6. package/src/column-expression-input/ColumnExpressionInput.css.js +5 -5
  7. package/src/column-expression-input/theme.js +1 -1
  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 +14 -25
  13. package/src/column-picker/ColumnPicker.css.js +0 -86
  14. package/src/column-picker/ColumnPicker.js +12 -180
  15. package/src/column-picker/useColumnPicker.js +28 -22
  16. package/src/column-settings-panel/ColumnNameLabel.css.js +1 -1
  17. package/src/csv-export/export-utils.js +76 -20
  18. package/src/csv-export/index.js +1 -1
  19. package/src/csv-export/useCsvExport.js +9 -1
  20. package/src/csv-upload/CsvUpload.css.js +36 -2
  21. package/src/csv-upload/CsvUpload.js +107 -63
  22. package/src/csv-upload/parse/csv-errors.js +1 -1
  23. package/src/csv-upload/parse/csv-parse.js +1 -1
  24. package/src/csv-upload/parse/csv-schema-validation.js +46 -7
  25. package/src/csv-upload/parse/csv-upload-utils.js +47 -2
  26. package/src/csv-upload/session/index.js +2 -0
  27. package/src/csv-upload/session/useImportSchema.js +44 -0
  28. package/src/csv-upload/session/useImportSession.js +204 -0
  29. package/src/csv-upload/useCsvUpload.js +54 -199
  30. package/src/freeze-control/FreezeControl.css.js +4 -4
  31. package/src/freeze-control/FrozenBanner.css.js +2 -2
  32. package/src/tabbed-table-config-panel/TabbedTableConfigPanel.js +17 -8
  33. package/src/tabbed-table-config-panel/useTabbedTableConfigPanel.js +1 -1
  34. package/src/table-footer/TableFooter.js +3 -3
  35. package/types/calculated-column/CalculatedColumnPanel.d.ts +3 -3
  36. package/types/cell-renderers/background-cell/BackgroundCell.d.ts +1 -1
  37. package/types/cell-renderers/background-cell/BackgroundCellConfigurationEditor.d.ts +1 -1
  38. package/types/cell-renderers/button-cell/IconButtonCell.d.ts +1 -1
  39. package/types/cell-renderers/dropdown-cell/DropdownCell.d.ts +1 -1
  40. package/types/cell-renderers/lookup-cell/LookupCell.d.ts +1 -1
  41. package/types/cell-renderers/pct-progress-cell/PctProgressCell.d.ts +1 -1
  42. package/types/column-expression-input/ColumnExpressionInput.d.ts +1 -1
  43. package/types/column-expression-input/column-language-parser/test.d.mts +1 -1
  44. package/types/column-formatting-settings/BaseNumericFormattingSettings.d.ts +1 -1
  45. package/types/column-formatting-settings/ColumnFormattingPanel.d.ts +1 -1
  46. package/types/column-formatting-settings/DateTimeFormattingSettings.d.ts +1 -1
  47. package/types/column-formatting-settings/LongTypeFormattingSettings.d.ts +1 -2
  48. package/types/column-menu/ColumnMenu.d.ts +1 -1
  49. package/types/column-picker/ColumnModel.d.ts +4 -3
  50. package/types/column-picker/ColumnPicker.d.ts +4 -6
  51. package/types/column-picker/ColumnPickerAction.d.ts +1 -1
  52. package/types/column-picker/useColumnPicker.d.ts +7 -10
  53. package/types/column-settings-panel/ColumnNameLabel.d.ts +1 -1
  54. package/types/column-settings-panel/ColumnSettingsPanel.d.ts +1 -1
  55. package/types/column-settings-panel/useColumnSettings.d.ts +3 -3
  56. package/types/csv-export/export-utils.d.ts +22 -9
  57. package/types/csv-export/index.d.ts +1 -1
  58. package/types/csv-export/useCsvExport.d.ts +5 -2
  59. package/types/csv-upload/CsvUpload.d.ts +2 -6
  60. package/types/csv-upload/data-upload-preview/DataUploadPreview.d.ts +1 -1
  61. package/types/csv-upload/parse/csv-errors.d.ts +1 -1
  62. package/types/csv-upload/parse/csv-schema-validation.d.ts +8 -2
  63. package/types/csv-upload/parse/csv-upload-utils.d.ts +9 -0
  64. package/types/csv-upload/session/index.d.ts +2 -0
  65. package/types/csv-upload/session/useImportSchema.d.ts +13 -0
  66. package/types/csv-upload/session/useImportSession.d.ts +26 -0
  67. package/types/csv-upload/useCsvUpload.d.ts +3 -3
  68. package/types/datasource-stats/DatasourceStats.d.ts +1 -1
  69. package/types/freeze-control/FreezeControl.d.ts +1 -1
  70. package/types/freeze-control/FrozenBanner.d.ts +1 -1
  71. package/types/index.d.ts +0 -1
  72. package/types/inline-add-row/InlineAddRow.d.ts +1 -1
  73. package/types/inline-add-row/useInlineAddRow.d.ts +2 -1
  74. package/types/tabbed-table-config-panel/TabbedTableConfigPanel.d.ts +1 -1
  75. package/types/tabbed-table-config-panel/TabbedTableSettingsAction.d.ts +1 -1
  76. package/types/tabbed-table-config-panel/useTabbedTableConfigPanel.d.ts +4 -3
  77. package/types/table-footer/TableFooter.d.ts +4 -4
  78. package/types/table-provider/TableProvider.d.ts +1 -1
  79. package/types/table-settings-panel/TableSettingsPanel.d.ts +1 -1
@@ -1,204 +1,36 @@
1
- import { jsx, jsxs } from "react/jsx-runtime";
1
+ import { jsx } from "react/jsx-runtime";
2
2
  import { useComponentCssInjection } from "@salt-ds/styles";
3
3
  import { useWindow } from "@salt-ds/window";
4
- import { DragDropProvider, getColumnLabel, isCalculatedColumn, useSortable } from "@vuu-ui/vuu-utils";
5
4
  import clsx from "clsx";
6
- import { forwardRef, useCallback, useMemo, useRef } from "react";
7
- import { Button, Input, ListBox, Option } from "@salt-ds/core";
8
- import { applyHighlighting } from "@vuu-ui/vuu-table";
9
- import { Icon, IconButton } from "@vuu-ui/vuu-ui-controls";
10
- import { ColumnChangeSource } from "./ColumnModel.js";
5
+ import { forwardRef } from "react";
11
6
  import { useColumnPicker } from "./useColumnPicker.js";
7
+ import { ItemPicker } from "@vuu-ui/vuu-ui-controls";
12
8
  import ColumnPicker_0 from "./ColumnPicker.css";
13
9
  const classBase = "vuuColumnPicker";
14
10
  const classBaseListItem = "vuuColumnPickerListItem";
15
- const searchIcon = /*#__PURE__*/ jsx("span", {
16
- "data-icon": "search"
17
- });
18
11
  const NO_SELECTION = [];
19
- const useSorting = (id, index)=>{
20
- const { handleRef: sortableHandleRef, ref: sortableRef } = useSortable({
21
- id,
22
- index
23
- });
24
- const [handleRef, ref] = useMemo(()=>[
25
- sortableHandleRef,
26
- sortableRef
27
- ], [
28
- sortableHandleRef,
29
- sortableRef
30
- ]);
31
- return {
32
- handleRef,
33
- ref
34
- };
35
- };
36
- const SelectedColumnListItem = ({ className: classNameProp, index, column, onRemove, searchPattern = "", ...optionProps })=>{
37
- const { handleRef, ref } = useSorting(column.name, index);
38
- const value = getColumnLabel(column);
39
- const valueWithHighlighting = applyHighlighting(value, searchPattern);
40
- const handleRemoveButtonClick = useCallback((e)=>{
41
- e.stopPropagation();
42
- onRemove?.(e);
43
- }, [
44
- onRemove
45
- ]);
46
- return /*#__PURE__*/ jsxs(Option, {
47
- ...optionProps,
48
- className: clsx(classNameProp, classBaseListItem),
49
- "data-name": column.name,
50
- ref: ref,
51
- children: [
52
- /*#__PURE__*/ jsx(IconButton, {
53
- "data-embedded": true,
54
- appearance: "transparent",
55
- icon: "draggable",
56
- ref: handleRef,
57
- size: 16
58
- }),
59
- isCalculatedColumn(column.name) ? /*#__PURE__*/ jsx(Icon, {
60
- className: "vuuCalculatedColumnIcon",
61
- name: "function"
62
- }) : null,
63
- /*#__PURE__*/ jsx("span", {
64
- className: `${classBase}-text`,
65
- children: valueWithHighlighting
66
- }),
67
- /*#__PURE__*/ jsx(IconButton, {
68
- className: `${classBaseListItem}-action`,
69
- "data-embedded": true,
70
- appearance: "transparent",
71
- icon: "cross",
72
- onClick: handleRemoveButtonClick,
73
- size: 16
74
- })
75
- ]
76
- });
77
- };
78
- const AvailableColumnListItem = ({ className: classNameProp, index, column: item, onAdd, searchPattern = "", ...optionProps })=>{
79
- const value = getColumnLabel(item);
80
- const valueWithHighlighting = applyHighlighting(value, searchPattern);
81
- return /*#__PURE__*/ jsxs(Option, {
82
- ...optionProps,
83
- className: clsx(classNameProp, classBaseListItem),
84
- "data-name": item.name,
85
- children: [
86
- /*#__PURE__*/ jsx("span", {
87
- className: `${classBase}-text`,
88
- children: valueWithHighlighting
89
- }),
90
- /*#__PURE__*/ jsx(IconButton, {
91
- className: `${classBaseListItem}-action`,
92
- "data-embedded": true,
93
- appearance: "transparent",
94
- icon: "plus",
95
- onClick: onAdd,
96
- size: 16
97
- })
98
- ]
99
- });
100
- };
101
- const ColumnPicker_ColumnPicker = /*#__PURE__*/ forwardRef(function({ allowCreateCalculatedColumn, columnModel, className, onClickCreateCalculatedColumn, onSelectionChange, selected = NO_SELECTION, ...htmlAttributes }, forwardedRef) {
12
+ const ColumnPicker_ColumnPicker = /*#__PURE__*/ forwardRef(function({ columnModel, className, onSelectionChange, selected = NO_SELECTION, ...htmlAttributes }, forwardedRef) {
102
13
  const targetWindow = useWindow();
103
14
  useComponentCssInjection({
104
15
  testId: "vuu-column-picker",
105
16
  css: ColumnPicker_0,
106
17
  window: targetWindow
107
18
  });
108
- const searchCallbackRef = useCallback((el)=>{
109
- setTimeout(()=>{
110
- el?.querySelector("input")?.focus();
111
- }, 100);
112
- }, []);
113
- const { availableColumns, onAddItemToSelectedList, onChangeSearchInput, onRemoveItemFromSelectedList, searchText, selectedColumns } = useColumnPicker({
19
+ const { allItems, selectedItems, oneOrMoreColumnsIsCalculated, handleSelectedItemsChange } = useColumnPicker({
114
20
  columnModel: columnModel
115
21
  });
116
- const listRef = useRef(null);
117
- const getOptionName = (option)=>{
118
- if (option) {
119
- const { name } = option.dataset;
120
- if (name) return name;
121
- }
122
- throw Error("[ColumnPicker] list option has no data-name");
123
- };
124
- const handleDragEnd = useCallback(()=>{
125
- setTimeout(()=>{
126
- const listItems = listRef.current?.querySelectorAll(".saltOption");
127
- if (listItems) {
128
- const orderedColumnNames = Array.from(listItems).map(getOptionName);
129
- columnModel.reorderSelectedColumns(orderedColumnNames, ColumnChangeSource.ColumnPicker);
130
- }
131
- }, 300);
132
- }, [
133
- columnModel
134
- ]);
135
- const oneOrMoreColumnsIsCalculated = selectedColumns.some((column)=>isCalculatedColumn(column.name));
136
- return /*#__PURE__*/ jsxs("div", {
22
+ return /*#__PURE__*/ jsx(ItemPicker, {
137
23
  ...htmlAttributes,
138
24
  className: clsx(classBase, className, {
139
25
  [`${classBase}-withCalculated`]: oneOrMoreColumnsIsCalculated
140
26
  }),
27
+ allItems: allItems,
28
+ selectedItems: selectedItems,
29
+ itemTypeName: "column",
30
+ onSelectedItemsChange: handleSelectedItemsChange,
31
+ onSelectionChange: onSelectionChange,
141
32
  ref: forwardedRef,
142
- children: [
143
- /*#__PURE__*/ jsx("form", {
144
- className: `${classBase}-search`,
145
- role: "search",
146
- children: /*#__PURE__*/ jsx(Input, {
147
- startAdornment: searchIcon,
148
- placeholder: "Find column",
149
- ref: searchCallbackRef,
150
- value: searchText,
151
- onChange: onChangeSearchInput
152
- })
153
- }),
154
- /*#__PURE__*/ jsxs("div", {
155
- className: `${classBase}-scrollContainer vuuScrollable`,
156
- children: [
157
- /*#__PURE__*/ jsx("div", {
158
- className: `${classBase}-sectionHeader`,
159
- children: "Columns in view"
160
- }),
161
- /*#__PURE__*/ jsx(DragDropProvider, {
162
- onDragEnd: handleDragEnd,
163
- children: /*#__PURE__*/ jsx(ListBox, {
164
- className: `${classBase}-selectedList`,
165
- onSelectionChange: onSelectionChange,
166
- ref: listRef,
167
- selected: selected,
168
- children: selectedColumns.map((column, index)=>/*#__PURE__*/ jsx(SelectedColumnListItem, {
169
- column: column,
170
- index: index,
171
- onRemove: onRemoveItemFromSelectedList,
172
- searchPattern: searchText.toLowerCase(),
173
- value: column
174
- }, column.name))
175
- })
176
- }),
177
- /*#__PURE__*/ jsx("div", {
178
- className: clsx(`${classBase}-sectionHeader`, `${classBase}-availableHeader`),
179
- children: "Available columns"
180
- }),
181
- /*#__PURE__*/ jsx(ListBox, {
182
- className: `${classBase}-availableList`,
183
- selected: NO_SELECTION,
184
- children: availableColumns.map((column, index)=>/*#__PURE__*/ jsx(AvailableColumnListItem, {
185
- column: column,
186
- index: index,
187
- onAdd: onAddItemToSelectedList,
188
- searchPattern: searchText.toLowerCase(),
189
- value: column
190
- }, column.name))
191
- })
192
- ]
193
- }),
194
- allowCreateCalculatedColumn ? /*#__PURE__*/ jsx("div", {
195
- className: `${classBase}-column-buttons`,
196
- children: /*#__PURE__*/ jsx(Button, {
197
- onClick: onClickCreateCalculatedColumn,
198
- children: "Create calculated column"
199
- })
200
- }) : null
201
- ]
33
+ selected: selected
202
34
  });
203
35
  });
204
36
  export { ColumnPicker_ColumnPicker as ColumnPicker, classBaseListItem };
@@ -1,13 +1,7 @@
1
- import { queryClosest } from "@vuu-ui/vuu-utils";
2
- import { useCallback, useEffect, useState } from "react";
1
+ import { isCalculatedColumn } from "@vuu-ui/vuu-utils";
2
+ import { useCallback, useEffect, useMemo, useState } from "react";
3
3
  import { ColumnChangeSource } from "./ColumnModel.js";
4
4
  const SOURCE = ColumnChangeSource.ColumnPicker;
5
- const columnName = (target)=>{
6
- const listItem = queryClosest(target, ".saltOption", true);
7
- const { name } = listItem.dataset;
8
- if (name) return name;
9
- throw Error("[useColumnPicker] column name could not be identified, data-name attribute not found");
10
- };
11
5
  const useColumnPicker = ({ columnModel: model })=>{
12
6
  const [, forceRender] = useState({});
13
7
  useEffect(()=>{
@@ -18,25 +12,37 @@ const useColumnPicker = ({ columnModel: model })=>{
18
12
  }, [
19
13
  model
20
14
  ]);
21
- const handleChangeSearchInput = useCallback((evt)=>{
22
- const { value } = evt.target;
23
- model.searchPattern = value;
15
+ const allItems = useMemo(()=>{
16
+ const itemDescriptors = model.allColumns.map((column)=>({
17
+ name: column.name,
18
+ label: column.label,
19
+ icon: isCalculatedColumn(column.name) ? "vuuCalculatedColumnIcon" : void 0
20
+ }));
21
+ return itemDescriptors;
24
22
  }, [
25
- model
23
+ model.allColumns
26
24
  ]);
27
- const handleAddItemToSelectedList = useCallback((e)=>model.addItemToSelectedColumns(columnName(e.target), SOURCE), [
28
- model
25
+ const selectedItems = useMemo(()=>{
26
+ const itemDescriptors = model.selectedColumns.map((column)=>allItems.find((item)=>item.name === column.name));
27
+ return itemDescriptors;
28
+ }, [
29
+ model.selectedColumns,
30
+ allItems
29
31
  ]);
30
- const handleRemoveItemFromSelectedList = useCallback((e)=>model.removeItemFromSelectedColumns(columnName(e.target), SOURCE), [
31
- model
32
+ const oneOrMoreColumnsIsCalculated = useMemo(()=>model.selectedColumns.some((column)=>isCalculatedColumn(column.name)), [
33
+ model.selectedColumns
34
+ ]);
35
+ const handleSelectedItemsChange = useCallback((newSelectedItems)=>{
36
+ const newSelectedColumns = newSelectedItems.map((item)=>model.allColumns.find((column)=>column.name === item.name));
37
+ model.addRemoveOrReorderSelectedColumns(newSelectedColumns, SOURCE);
38
+ }, [
39
+ model.allColumns
32
40
  ]);
33
41
  return {
34
- availableColumns: model.availableColumns,
35
- onAddItemToSelectedList: handleAddItemToSelectedList,
36
- onRemoveItemFromSelectedList: handleRemoveItemFromSelectedList,
37
- onChangeSearchInput: handleChangeSearchInput,
38
- searchText: model.searchPattern,
39
- selectedColumns: model.selectedColumns
42
+ allItems,
43
+ selectedItems,
44
+ oneOrMoreColumnsIsCalculated,
45
+ handleSelectedItemsChange
40
46
  };
41
47
  };
42
48
  export { useColumnPicker };
@@ -10,7 +10,7 @@ const css = `
10
10
  }
11
11
 
12
12
  .vuuColumnNameLabel-placeholder {
13
- color: var(--vuu-color-gray-35);
13
+ color: var(--salt-color-gray-400);
14
14
  }
15
15
 
16
16
 
@@ -1,4 +1,4 @@
1
- import { Range, isSessionTable, metadataKeys } from "@vuu-ui/vuu-utils";
1
+ import { Range, buildColumnMap, getValueFormatter, isSessionTable, metadataKeys } from "@vuu-ui/vuu-utils";
2
2
  const EXPORT_EXCLUDED_COLUMNS = new Set([
3
3
  "vuuMsg",
4
4
  "vuuAction",
@@ -28,10 +28,32 @@ const triggerCsvDownload = (csv, filename)=>{
28
28
  document.body.removeChild(a);
29
29
  setTimeout(()=>URL.revokeObjectURL(url), 10000);
30
30
  };
31
+ const createRowAccessor = (row, columnMap)=>new Proxy({}, {
32
+ get (_, prop) {
33
+ if ("string" == typeof prop) {
34
+ const idx = columnMap[prop];
35
+ if (void 0 !== idx) return row[idx];
36
+ }
37
+ },
38
+ has (_, prop) {
39
+ return "string" == typeof prop && void 0 !== columnMap[prop];
40
+ }
41
+ });
42
+ const isExportableColumn = (descriptor)=>{
43
+ if (!descriptor) return true;
44
+ if (false === descriptor.exportable) return false;
45
+ if (descriptor.hidden) return false;
46
+ if (descriptor.isSystemColumn) return false;
47
+ if (true === descriptor.exportable) return true;
48
+ if ("client" === descriptor.source && !descriptor.exportFormatter) return false;
49
+ return true;
50
+ };
31
51
  const exportCsvTemplate = async (dataSource, options = {})=>{
32
52
  const { filename = "template.csv", excludeColumns = [], columns, overrides, timeout = DEFAULT_TEMPLATE_TIMEOUT, onError, onSuccess } = options;
33
- const sessionOverrides = overrides ?? (columns ? {
34
- columns
53
+ 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
+ const targetColumns = targetColumnNames ?? overrides?.columns;
55
+ const sessionOverrides = overrides ?? (targetColumns ? {
56
+ columns: targetColumns
35
57
  } : void 0);
36
58
  if (!isSessionTable(dataSource.table) && dataSource.createSessionDataSource && "initialising" !== dataSource.status && "unsubscribed" !== dataSource.status) try {
37
59
  const sessionDataSource = await dataSource.createSessionDataSource("Empty", "export", sessionOverrides);
@@ -59,7 +81,16 @@ const exportCsvTemplate = async (dataSource, options = {})=>{
59
81
  }, (message)=>{
60
82
  if ("subscribed" === message.type) {
61
83
  const { columns: subColumns } = message;
62
- const exportCols = subColumns.filter((name)=>!excluded.has(name));
84
+ const exportCols = targetColumns ? targetColumns.filter((name)=>!excluded.has(name)) : subColumns.filter((name)=>!excluded.has(name));
85
+ if (0 === exportCols.length) {
86
+ cleanup();
87
+ const error = new Error("exportCsvTemplate: no columns available for export");
88
+ if (onError) {
89
+ onError(error);
90
+ resolve();
91
+ } else reject(error);
92
+ return;
93
+ }
63
94
  const header = exportCols.map(csvCell).join(",");
64
95
  triggerCsvDownload(`${header}\r\n`, filename);
65
96
  cleanup();
@@ -72,7 +103,6 @@ const exportCsvTemplate = async (dataSource, options = {})=>{
72
103
  console.warn("[exportCsvTemplate] createSessionDataSource failed, falling back to tableSchema", error);
73
104
  }
74
105
  const schema = dataSource.tableSchema;
75
- const targetColumns = columns ?? overrides?.columns;
76
106
  if (void 0 !== targetColumns) {
77
107
  if (schema) {
78
108
  const schemaColumnNames = new Set(schema.columns.map((col)=>col.name));
@@ -99,7 +129,18 @@ const exportCsvTemplate = async (dataSource, options = {})=>{
99
129
  onSuccess?.();
100
130
  };
101
131
  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;
132
+ const { filename = "export.csv", copyOption = "All", excludeColumns = [], maxRows = MAX_EXPORT_ROWS, columnDescriptors: rawColumnDescriptors, columns: rawColumns, overrides, timeout = DEFAULT_EXPORT_TIMEOUT, onError, onSuccess } = options;
133
+ const rawInputColumns = rawColumns ?? rawColumnDescriptors;
134
+ const columnDescriptors = rawInputColumns?.map((col)=>"string" == typeof col ? {
135
+ name: col
136
+ } : col);
137
+ const sessionOverrides = overrides ? {
138
+ ...overrides,
139
+ columns: overrides.columns?.filter((name)=>{
140
+ const desc = columnDescriptors?.find((d)=>d.name === name);
141
+ return desc?.source !== "client";
142
+ })
143
+ } : void 0;
103
144
  const isRemote = !!dataSource.isRemote;
104
145
  if (isRemote && !isSessionTable(dataSource.table) && ("initialising" === dataSource.status || "unsubscribed" === dataSource.status)) {
105
146
  const error = new Error(`exportSessionTableToCsv: dataSource must be subscribed before exporting (current status: "${dataSource.status}")`);
@@ -108,7 +149,7 @@ const exportSessionTableToCsv = async (dataSource, options = {})=>{
108
149
  }
109
150
  let sessionDataSource;
110
151
  if (!isSessionTable(dataSource.table) && dataSource.createSessionDataSource) try {
111
- sessionDataSource = await dataSource.createSessionDataSource(copyOption, "export", overrides);
152
+ sessionDataSource = await dataSource.createSessionDataSource(copyOption, "export", sessionOverrides);
112
153
  } catch (err) {
113
154
  const error = err instanceof Error ? err : new Error(String(err));
114
155
  if (onError) return void onError(error);
@@ -152,18 +193,23 @@ const exportSessionTableToCsv = async (dataSource, options = {})=>{
152
193
  const { columns } = message;
153
194
  if (columnDescriptors) {
154
195
  const subscribedSet = new Set(columns);
155
- const unknown = columnDescriptors.filter((d)=>!excluded.has(d.name) && !subscribedSet.has(d.name)).map((d)=>d.name);
196
+ const unknown = columnDescriptors.filter((d)=>"client" !== d.source && !excluded.has(d.name) && !subscribedSet.has(d.name)).map((d)=>d.name);
156
197
  if (unknown.length > 0) return void fail(new Error(`exportToCsv: unknown column(s) in columnDescriptors: ${unknown.join(", ")}`));
157
198
  descriptorMap = Object.fromEntries(columnDescriptors.map((d)=>[
158
199
  d.name,
159
200
  d
160
201
  ]));
161
202
  }
162
- exportCols = columns.filter((name)=>!excluded.has(name));
163
- colNameToRowIdx = Object.fromEntries(columns.map((name, i)=>[
164
- name,
165
- metadataKeys.count + i
166
- ]));
203
+ if (rawColumns) exportCols = rawColumns.map((c)=>"string" == typeof c ? c : c.name).filter((name)=>!excluded.has(name) && isExportableColumn(descriptorMap[name]));
204
+ else if (columnDescriptors?.some((d)=>"client" === d.source)) {
205
+ const definedNames = new Set(columnDescriptors.map((d)=>d.name));
206
+ const remainingSubscribed = columns.filter((name)=>!excluded.has(name) && !definedNames.has(name) && isExportableColumn(descriptorMap[name]));
207
+ exportCols = [
208
+ ...columnDescriptors.filter((d)=>!excluded.has(d.name) && isExportableColumn(d)).map((d)=>d.name),
209
+ ...remainingSubscribed
210
+ ];
211
+ } else exportCols = columns.filter((name)=>!excluded.has(name) && isExportableColumn(descriptorMap[name]));
212
+ colNameToRowIdx = buildColumnMap(columns);
167
213
  } else if ("viewport-update" === message.type) {
168
214
  if ("size-only" === message.mode) {
169
215
  totalSize = message.size ?? 0;
@@ -191,11 +237,21 @@ const exportSessionTableToCsv = async (dataSource, options = {})=>{
191
237
  ];
192
238
  for(let i = 0; i < totalSize; i++){
193
239
  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(","));
240
+ if (row) {
241
+ const rowAccessor = createRowAccessor(row, colNameToRowIdx);
242
+ lines.push(exportCols.map((c)=>{
243
+ const descriptor = descriptorMap[c];
244
+ const isClientCol = descriptor?.source === "client";
245
+ const raw = isClientCol ? void 0 : void 0 !== colNameToRowIdx[c] ? row[colNameToRowIdx[c]] : void 0;
246
+ let formatted;
247
+ if (descriptor?.exportFormatter) formatted = descriptor.exportFormatter(raw, rowAccessor);
248
+ else if (descriptor && ("type" in descriptor || "serverDataType" in descriptor)) {
249
+ const tableFormatter = getValueFormatter(descriptor);
250
+ formatted = tableFormatter(raw);
251
+ } else formatted = raw;
252
+ return csvCell(formatted);
253
+ }).join(","));
254
+ }
199
255
  }
200
256
  triggerCsvDownload(lines.join("\r\n"), filename);
201
257
  onSuccess?.();
@@ -213,9 +269,9 @@ const exportSessionTableToCsv = async (dataSource, options = {})=>{
213
269
  };
214
270
  activeSessionDataSource.subscribe({
215
271
  range: Range(0, 0),
216
- columns: overrides?.columns
272
+ columns: sessionOverrides?.columns
217
273
  }, handleMessage);
218
274
  });
219
275
  };
220
276
  const exportToCsv = async (dataSource, options)=>exportSessionTableToCsv(dataSource, options);
221
- export { exportCsvTemplate, exportSessionTableToCsv, exportToCsv };
277
+ export { exportCsvTemplate, exportSessionTableToCsv, exportToCsv, isExportableColumn };
@@ -1,2 +1,2 @@
1
- export { exportCsvTemplate, exportSessionTableToCsv, exportToCsv } from "./export-utils.js";
1
+ export { exportCsvTemplate, exportSessionTableToCsv, exportToCsv, isExportableColumn } 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, onError, onSuccess } = config;
9
+ const { dataSource: defaultDataSource, tableConfig, columns: defaultColumns, onError, onSuccess } = config;
10
10
  const exportCsv = useCallback(async (options, overrideDataSource)=>{
11
11
  const activeDataSource = overrideDataSource ?? defaultDataSource;
12
12
  if (!activeDataSource) {
@@ -19,7 +19,9 @@ function useCsvExport(propsOrDataSource) {
19
19
  setIsExporting(true);
20
20
  setError(null);
21
21
  try {
22
+ const columnsToExport = options?.columns ?? options?.columnDescriptors ?? defaultColumns ?? tableConfig?.columns;
22
23
  await exportToCsv(activeDataSource, {
24
+ columns: columnsToExport,
23
25
  ...options,
24
26
  onError: (err)=>{
25
27
  setError(err);
@@ -42,6 +44,8 @@ function useCsvExport(propsOrDataSource) {
42
44
  }
43
45
  }, [
44
46
  defaultDataSource,
47
+ defaultColumns,
48
+ tableConfig,
45
49
  onError,
46
50
  onSuccess
47
51
  ]);
@@ -57,7 +61,9 @@ function useCsvExport(propsOrDataSource) {
57
61
  setIsExporting(true);
58
62
  setError(null);
59
63
  try {
64
+ const templateColumns = options?.columns ?? defaultColumns ?? tableConfig?.columns;
60
65
  await exportCsvTemplate(activeDataSource, {
66
+ columns: templateColumns,
61
67
  ...options,
62
68
  onError: (err)=>{
63
69
  setError(err);
@@ -80,6 +86,8 @@ function useCsvExport(propsOrDataSource) {
80
86
  }
81
87
  }, [
82
88
  defaultDataSource,
89
+ defaultColumns,
90
+ tableConfig,
83
91
  onError,
84
92
  onSuccess
85
93
  ]);
@@ -14,10 +14,44 @@ const css = `
14
14
  display: grid;
15
15
  }
16
16
 
17
+ .vuuCsvUpload-errorContainer {
18
+ margin-top: var(--salt-spacing-100);
19
+ width: 100%;
20
+ max-width: 480px;
21
+ max-height: 120px;
22
+ padding: var(--salt-spacing-100) var(--salt-spacing-200);
23
+ background-color: var(--salt-status-error-background);
24
+ border-radius: var(--salt-palette-corner);
25
+ text-align: left;
26
+ overflow-y: auto;
27
+ }
28
+
29
+ .vuuCsvUpload-errorList {
30
+ padding-left: var(--salt-spacing-200);
31
+ margin: 0;
32
+ list-style-type: disc;
33
+ }
34
+
35
+ .vuuCsvUpload-errorItem {
36
+ font-size: var(--salt-text-label-fontSize);
37
+ color: var(--salt-status-error-foreground-informative);
38
+ line-height: var(--salt-text-label-lineHeight);
39
+ word-break: break-word;
40
+ margin-bottom: var(--salt-spacing-50);
41
+ }
42
+
43
+ .vuuCsvUpload-importErrorItem {
44
+ text-align: center;
45
+ }
46
+
47
+ .vuuCsvUpload-rectifyPrompt {
48
+ margin-top: var(--salt-spacing-100);
49
+ }
50
+
17
51
  .vuuCsvUpload-errors {
18
52
  border: solid 1px var(--salt-status-error-borderColor);
19
- border-radius: var(--salt-size-borderRadius);
20
- color: var(--salt-status-error-foreground);
53
+ border-radius: var(--salt-palette-corner);
54
+ color: var(--salt-status-error-foreground-informative);
21
55
  gap: var(--salt-spacing-100);
22
56
  padding: var(--salt-spacing-200);
23
57
  display: grid;