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

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 (34) hide show
  1. package/package.json +12 -12
  2. package/src/calculated-column/CalculatedColumnPanel.js +2 -2
  3. package/src/column-picker/ColumnModel.js +13 -25
  4. package/src/column-picker/ColumnPicker.css.js +0 -86
  5. package/src/column-picker/ColumnPicker.js +11 -181
  6. package/src/column-picker/useColumnPicker.js +28 -22
  7. package/src/csv-upload/CsvUpload.js +48 -31
  8. package/src/csv-upload/parse/csv-constants.js +2 -2
  9. package/src/csv-upload/parse/csv-errors.js +1 -0
  10. package/src/csv-upload/parse/csv-schema-validation.js +9 -0
  11. package/src/csv-upload/useCsvUpload.js +7 -5
  12. package/src/tabbed-table-config-panel/TabbedTableConfigPanel.js +17 -7
  13. package/src/tabbed-table-config-panel/useTabbedTableConfigPanel.js +1 -1
  14. package/types/calculated-column/CalculatedColumnPanel.d.ts +2 -2
  15. package/types/column-picker/ColumnModel.d.ts +4 -3
  16. package/types/column-picker/ColumnPicker.d.ts +1 -3
  17. package/types/column-picker/useColumnPicker.d.ts +7 -10
  18. package/types/column-settings-panel/useColumnSettings.d.ts +3 -3
  19. package/types/csv-upload/CsvUpload.d.ts +2 -1
  20. package/types/csv-upload/index.d.ts +1 -1
  21. package/types/csv-upload/parse/csv-constants.d.ts +2 -2
  22. package/types/csv-upload/parse/csv-errors.d.ts +2 -1
  23. package/types/csv-upload/parse/csv-schema-validation.d.ts +2 -0
  24. package/types/csv-upload/parse/index.d.ts +1 -1
  25. package/types/csv-upload/useCsvUpload.d.ts +3 -2
  26. package/types/index.d.ts +0 -1
  27. package/types/src/csv-upload/CsvUpload.d.ts +2 -1
  28. package/types/src/csv-upload/index.d.ts +1 -1
  29. package/types/src/csv-upload/parse/csv-constants.d.ts +2 -2
  30. package/types/src/csv-upload/parse/csv-errors.d.ts +2 -1
  31. package/types/src/csv-upload/parse/csv-schema-validation.d.ts +2 -0
  32. package/types/src/csv-upload/parse/index.d.ts +1 -1
  33. package/types/src/csv-upload/useCsvUpload.d.ts +3 -2
  34. package/types/tabbed-table-config-panel/useTabbedTableConfigPanel.d.ts +4 -3
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "3.3.10",
2
+ "version": "3.3.12",
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.10",
15
- "@vuu-ui/vuu-protocol-types": "3.3.10"
14
+ "@vuu-ui/vuu-filter-types": "3.3.12",
15
+ "@vuu-ui/vuu-protocol-types": "3.3.12"
16
16
  },
17
17
  "dependencies": {
18
- "@vuu-ui/vuu-codemirror": "3.3.10",
19
- "@vuu-ui/vuu-data-editing": "3.3.10",
20
- "@vuu-ui/vuu-data-react": "3.3.10",
21
- "@vuu-ui/vuu-data-types": "3.3.10",
22
- "@vuu-ui/vuu-table-types": "3.3.10",
23
- "@vuu-ui/vuu-popups": "3.3.10",
24
- "@vuu-ui/vuu-table": "3.3.10",
25
- "@vuu-ui/vuu-utils": "3.3.10",
26
- "@vuu-ui/vuu-ui-controls": "3.3.10",
18
+ "@vuu-ui/vuu-codemirror": "3.3.12",
19
+ "@vuu-ui/vuu-data-editing": "3.3.12",
20
+ "@vuu-ui/vuu-data-react": "3.3.12",
21
+ "@vuu-ui/vuu-data-types": "3.3.12",
22
+ "@vuu-ui/vuu-table-types": "3.3.12",
23
+ "@vuu-ui/vuu-popups": "3.3.12",
24
+ "@vuu-ui/vuu-table": "3.3.12",
25
+ "@vuu-ui/vuu-utils": "3.3.12",
26
+ "@vuu-ui/vuu-ui-controls": "3.3.12",
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",
@@ -1,9 +1,9 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { Dropdown, FormField, FormFieldLabel, Input, Option } from "@salt-ds/core";
3
- import { useCalculatedColumnPanel } from "./useCalculatedColumnPanel.js";
4
- import { useCallback, useRef } from "react";
5
3
  import { getCalculatedColumnDetails } from "@vuu-ui/vuu-utils";
4
+ import { useCallback, useRef } from "react";
6
5
  import { ColumnExpressionInput, useColumnExpressionSuggestionProvider } from "../column-expression-input/index.js";
6
+ import { useCalculatedColumnPanel } from "./useCalculatedColumnPanel.js";
7
7
  const classBase = "vuuCalculatedColumnPanel";
8
8
  const CalculatedColumnPanel = ({ column: columnProp, columnModel, onChangeColumn, onChangeServerDataType: onChangeServerDataTypeProp, vuuTable })=>{
9
9
  const typeRef = useRef(null);
@@ -1,4 +1,4 @@
1
- import { EventEmitter, reorderItems } from "@vuu-ui/vuu-utils";
1
+ import { EventEmitter, getAddedItems, getRemovedItems, itemsOrOrderChanged, reorderItems } from "@vuu-ui/vuu-utils";
2
2
  const ColumnChangeSource = {
3
3
  ColumnPicker: "column-picker",
4
4
  ColumnSettings: "column-settings",
@@ -15,37 +15,15 @@ const isColumnAdded = (change)=>change?.type === SelectedColumnChangeType.Column
15
15
  const isColumnRemoved = (change)=>change?.type === SelectedColumnChangeType.ColumnRemoved;
16
16
  const isColumnUpdated = (change)=>change?.type === SelectedColumnChangeType.ColumnUpdated;
17
17
  const isColumnsReordered = (change)=>change?.type === SelectedColumnChangeType.ColumnsReordered;
18
- const byColumnName = ({ name: n1, label: l1 = n1 }, { name: n2, label: l2 = n2 })=>l1 > l2 ? 1 : l2 > l1 ? -1 : 0;
19
- const filterColumns = (columns, pattern)=>{
20
- if (!pattern) return columns;
21
- {
22
- const lowerCasePattern = pattern.toLowerCase();
23
- return columns.filter(({ name, label = name })=>-1 !== label.toLowerCase().indexOf(lowerCasePattern));
24
- }
25
- };
26
18
  class ColumnModel extends EventEmitter {
27
19
  allColumns;
28
- #searchPattern = "";
29
20
  #selectedColumns;
30
21
  constructor(allColumns, selectedColumns){
31
22
  super(), this.allColumns = allColumns;
32
23
  this.#selectedColumns = selectedColumns;
33
24
  }
34
- get availableColumns() {
35
- return filterColumns(this.allColumns, this.#searchPattern).filter(({ name })=>-1 === this.#selectedColumns.findIndex((c)=>c.name === name)).toSorted(byColumnName);
36
- }
37
- set searchPattern(pattern) {
38
- const searchPattern = pattern;
39
- if (searchPattern !== this.#searchPattern) {
40
- this.#searchPattern = searchPattern;
41
- this.emit("render", {});
42
- }
43
- }
44
- get searchPattern() {
45
- return this.#searchPattern ?? "";
46
- }
47
25
  get selectedColumns() {
48
- return filterColumns(this.#selectedColumns, this.#searchPattern);
26
+ return this.#selectedColumns;
49
27
  }
50
28
  setSelectedColumns(selectedColumns, source, changeDescriptor) {
51
29
  this.#selectedColumns = selectedColumns;
@@ -56,6 +34,16 @@ class ColumnModel extends EventEmitter {
56
34
  if (col) return col;
57
35
  throw Error(`[ColumnModel] columns does not contain column ${name}`);
58
36
  }
37
+ addRemoveOrReorderSelectedColumns(newSelectedColumns, source) {
38
+ if (!itemsOrOrderChanged(this.#selectedColumns, newSelectedColumns)) throw Error("[ColumnModel] addRemoveOrReorderSelectedColumns no change detected between current and new selected columns");
39
+ const addedColumns = getAddedItems(this.#selectedColumns, newSelectedColumns);
40
+ if (addedColumns.length > 0) if (1 == addedColumns.length) return void this.addItemToSelectedColumns(addedColumns[0].name, source);
41
+ else throw Error("[ColumnModel] addRemoveOrReorderSelectedColumns attempt to add multiple selected columns in a single call");
42
+ const removedColumns = getRemovedItems(this.#selectedColumns, newSelectedColumns);
43
+ if (removedColumns.length > 0) if (1 == removedColumns.length) return void this.removeItemFromSelectedColumns(removedColumns[0].name, source);
44
+ else throw Error("[ColumnModel] addRemoveOrReorderSelectedColumns attempt to remove multiple selected columns in a single call");
45
+ this.reorderSelectedColumns(newSelectedColumns.map(({ name })=>name), source);
46
+ }
59
47
  addItemToSelectedColumns(name, source) {
60
48
  const column = this.allColumns.find((col)=>col.name === name);
61
49
  if (column) {
@@ -97,7 +85,7 @@ class ColumnModel extends EventEmitter {
97
85
  }
98
86
  addColumn(column, addToSelectedColumns = false) {
99
87
  console.log(`[ColumnModel] add column ${JSON.stringify(column)}`);
100
- if (addToSelectedColumns) console.log("add it to selected coliumns");
88
+ if (addToSelectedColumns) console.log("add it to selected columns");
101
89
  this.allColumns = this.allColumns.concat(column);
102
90
  this.#selectedColumns = this.#selectedColumns.concat(column);
103
91
  this.notifyListeners(this.#selectedColumns, "column-picker", {
@@ -1,94 +1,8 @@
1
1
  const css = `
2
2
  .vuuColumnPicker {
3
- --label-margin-left: 0px;
4
- --columnpicker-background: var(--vuuColumnPicker-background, var(--salt-container-primary-background));
5
- background: var(--columnpicker-background);
6
- gap: var(--salt-spacing-300);
7
- height: var(--vuuColumnPicker-height, 100%);
8
- padding: var(--salt-spacing-300) 0;
9
- flex-direction: column;
10
- display: flex;
11
- overflow: hidden;
12
- container-type: inline-size;
13
-
14
3
  &.vuuColumnPicker-withCalculated {
15
4
  --label-margin-left: 8px;
16
5
  }
17
-
18
- & .saltListBox {
19
- background-color: inherit;
20
- }
21
-
22
- & .saltOption {
23
- background-color: inherit;
24
- border-bottom: var(--vuuColumnListItem-border, solid) 1px
25
- var(--salt-separable-tertiary-borderColor);
26
- align-items: center;
27
- gap: var(--salt-spacing-200);
28
- height: calc(var(--salt-size-base) + var(--salt-spacing-200));
29
-
30
- & .vuuIcon {
31
- --vuu-icon-color: var(--salt-content-secondary-foreground);
32
- }
33
-
34
- & .vuuCalculatedColumnIcon {
35
- --vuu-icon-color: var(--salt-content-secondary-foreground);
36
- position: absolute;
37
- top: 8px;
38
- left: 32px;
39
- }
40
-
41
- & .vuuHighlight {
42
- color: var(--vuu-color-blue-40);
43
- text-decoration: underline;
44
- }
45
-
46
- & .vuuColumnPicker-text {
47
- margin-left: var(--label-margin-left);
48
- }
49
- }
50
-
51
- & .vuuColumnPickerListItem-action {
52
- visibility: hidden;
53
- margin-left: auto;
54
- }
55
-
56
- & .saltOption:hover {
57
- & .vuuColumnPickerListItem-action {
58
- visibility: visible;
59
- }
60
- }
61
-
62
- & .vuuColumnPicker-search {
63
- --saltInput-paddingLeft: var(--salt-spacing-300);
64
- padding: var(--salt-spacing-200) var(--salt-spacing-300) 0 var(--salt-spacing-300);
65
- }
66
-
67
- & .vuuColumnPicker-scrollContainer {
68
- flex: 1 1 0;
69
- overflow: auto;
70
-
71
- & .vuuColumnPicker-sectionHeader {
72
- background: var(--columnpicker-background);
73
- height: 32px;
74
- padding: 0 var(--salt-spacing-300);
75
- z-index: 1;
76
- align-items: center;
77
- display: flex;
78
- position: sticky;
79
- top: 0;
80
- }
81
-
82
- & .vuuColumnPicker-availableHeader {
83
- top: 32px;
84
- }
85
-
86
- & .vuuColumnPicker-availableList {
87
- & .saltOption {
88
- padding-left: var(--salt-spacing-300);
89
- }
90
- }
91
- }
92
6
  }
93
7
 
94
8
 
@@ -1,204 +1,34 @@
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
  }),
141
- 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
- ]
27
+ allItems: allItems,
28
+ selectedItems: selectedItems,
29
+ itemTypeName: "column",
30
+ onSelectedItemsChange: handleSelectedItemsChange,
31
+ ref: forwardedRef
202
32
  });
203
33
  });
204
34
  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 };
@@ -1,5 +1,5 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
- import { Button, Dialog, DialogActions, DialogContent, DialogHeader, FileDropZone, FileDropZoneIcon, FileDropZoneTrigger } from "@salt-ds/core";
2
+ import { Button, Dialog, DialogActions, DialogContent, DialogHeader, FileDropZone, FileDropZoneIcon, FileDropZoneTrigger, Text } from "@salt-ds/core";
3
3
  import { useComponentCssInjection } from "@salt-ds/styles";
4
4
  import { useWindow } from "@salt-ds/window";
5
5
  import { useCallback, useState } from "react";
@@ -20,7 +20,8 @@ const CsvUpload_CsvUpload = (props)=>{
20
20
  const { canImport, cancelImport, isProcessingFile, isImporting, importData, onDrop, onTriggerChange, schema, validation, error } = useCsvUpload(props);
21
21
  const fileErrors = validation?.errors.filter((e)=>e.column in validation.errorMap.fileErrors) ?? [];
22
22
  const hasErrors = !!error || validation && validation.errors.length > 0;
23
- const status = hasErrors ? "error" : validation ? "success" : void 0;
23
+ const isUploaded = canImport || isImporting;
24
+ const status = hasErrors ? "error" : isUploaded ? "success" : void 0;
24
25
  const handleCancel = useCallback(async ()=>{
25
26
  await cancelImport();
26
27
  if (!isControlledOpen) setInternalOpen(false);
@@ -39,39 +40,55 @@ const CsvUpload_CsvUpload = (props)=>{
39
40
  const content = /*#__PURE__*/ jsxs("div", {
40
41
  className: classBase,
41
42
  children: [
42
- /*#__PURE__*/ jsxs(FileDropZone, {
43
+ /*#__PURE__*/ jsx(FileDropZone, {
43
44
  className: `${classBase}-dropZone`,
44
45
  disabled: void 0 === schema || isProcessingFile || isImporting,
45
46
  onDrop: onDrop,
46
47
  status: status,
47
- children: [
48
- /*#__PURE__*/ jsx(FileDropZoneIcon, {}),
49
- validation && validation.errors.length > 0 ? /*#__PURE__*/ jsxs(Fragment, {
50
- children: [
51
- /*#__PURE__*/ jsx("div", {
52
- children: "Your file contains errors"
53
- }),
54
- fileErrors.length > 0 && /*#__PURE__*/ jsx("ul", {
55
- className: `${classBase}-errorList`,
56
- children: fileErrors.map((error, i)=>/*#__PURE__*/ jsx("li", {
57
- className: `${classBase}-errorItem`,
58
- children: error.message
59
- }, `${error.column}-${error.message}-${i}`))
60
- }),
61
- /*#__PURE__*/ jsx("div", {
62
- children: "Please rectify and reupload"
63
- })
64
- ]
65
- }) : /*#__PURE__*/ jsx("div", {
66
- children: "Drop a file here or"
67
- }),
68
- /*#__PURE__*/ jsx(FileDropZoneTrigger, {
69
- accept: ".csv,text/csv",
70
- onChange: onTriggerChange,
71
- children: "BROWSE FILES"
72
- }),
73
- children
74
- ]
48
+ children: isUploaded ? /*#__PURE__*/ jsxs(Fragment, {
49
+ children: [
50
+ /*#__PURE__*/ jsx(FileDropZoneIcon, {
51
+ status: "success"
52
+ }),
53
+ /*#__PURE__*/ jsx("strong", {
54
+ children: "Upload completed"
55
+ })
56
+ ]
57
+ }) : /*#__PURE__*/ jsxs(Fragment, {
58
+ children: [
59
+ /*#__PURE__*/ jsx(FileDropZoneIcon, {
60
+ status: hasErrors ? "error" : void 0
61
+ }),
62
+ validation && validation.errors.length > 0 ? /*#__PURE__*/ jsxs(Fragment, {
63
+ children: [
64
+ /*#__PURE__*/ jsx("div", {
65
+ children: "Your file contains errors"
66
+ }),
67
+ fileErrors.length > 0 && /*#__PURE__*/ jsx("ul", {
68
+ className: `${classBase}-errorList`,
69
+ children: fileErrors.map((error, i)=>/*#__PURE__*/ jsx("li", {
70
+ className: `${classBase}-errorItem`,
71
+ children: error.message
72
+ }, `${error.column}-${error.message}-${i}`))
73
+ }),
74
+ /*#__PURE__*/ jsx("div", {
75
+ children: "Please rectify and reupload"
76
+ })
77
+ ]
78
+ }) : /*#__PURE__*/ jsx("div", {
79
+ children: "Drop a file here or"
80
+ }),
81
+ /*#__PURE__*/ jsx(FileDropZoneTrigger, {
82
+ accept: ".csv,text/csv",
83
+ onChange: onTriggerChange,
84
+ children: "BROWSE FILES"
85
+ }),
86
+ /*#__PURE__*/ jsx(Text, {
87
+ children: "Only .csv files"
88
+ }),
89
+ children
90
+ ]
91
+ })
75
92
  }),
76
93
  error && (renderError ? renderError(error) : /*#__PURE__*/ jsxs("div", {
77
94
  className: `${classBase}-errors`,
@@ -1,4 +1,4 @@
1
- const CSV_HEADER_ROW_NUMBER = 1;
2
- const CSV_FIRST_DATA_ROW_NUMBER = 2;
1
+ const CSV_HEADER_ROW_NUMBER = 0;
2
+ const CSV_FIRST_DATA_ROW_NUMBER = 1;
3
3
  const MAX_ROWS_IN_CSV = 10000;
4
4
  export { CSV_FIRST_DATA_ROW_NUMBER, CSV_HEADER_ROW_NUMBER, MAX_ROWS_IN_CSV };
@@ -15,6 +15,7 @@ var csv_errors_CsvValidationErrorEnum = /*#__PURE__*/ function(CsvValidationErro
15
15
  CsvValidationErrorEnum["MAX_ROWS_EXCEEDED"] = "MAX_ROWS_EXCEEDED";
16
16
  CsvValidationErrorEnum["EMPTY_NON_STRING_VALUE"] = "EMPTY_NON_STRING_VALUE";
17
17
  CsvValidationErrorEnum["TYPE_MISMATCH"] = "TYPE_MISMATCH";
18
+ CsvValidationErrorEnum["CUSTOM_VALIDATION"] = "CUSTOM_VALIDATION";
18
19
  return CsvValidationErrorEnum;
19
20
  }({});
20
21
  const createCsvErrorState = ()=>({
@@ -20,6 +20,10 @@ const validateCsvAgainstSchema = (parsed, tableSchema, options)=>{
20
20
  parsed.rows.forEach((rowValues, rowIndex)=>{
21
21
  const rowNum = rowIndex + CSV_FIRST_DATA_ROW_NUMBER;
22
22
  const typedRow = {};
23
+ const rawRow = parsed.header.reduce((acc, headerCol, idx)=>{
24
+ acc[headerCol] = rowValues[idx];
25
+ return acc;
26
+ }, {});
23
27
  parsed.header.forEach((columnName, columnIndex)=>{
24
28
  const rawValue = rowValues[columnIndex];
25
29
  const schemaType = schemaColumns.get(columnName);
@@ -33,6 +37,11 @@ const validateCsvAgainstSchema = (parsed, tableSchema, options)=>{
33
37
  return;
34
38
  }
35
39
  if (void 0 === value) return void addCsvRowError(errorState, rowNum, columnName, CsvValidationErrorEnum.TYPE_MISMATCH, `Value '${rawValue}' is not a valid ${toType}`, rawValue);
40
+ const customValidator = options?.validators?.[columnName];
41
+ if (customValidator) {
42
+ const validationResult = customValidator(rawValue, columnName, rawRow);
43
+ if (true !== validationResult) return void addCsvRowError(errorState, rowNum, columnName, CsvValidationErrorEnum.CUSTOM_VALIDATION, "string" == typeof validationResult ? validationResult : `Value '${rawValue}' failed custom validation.`, rawValue);
44
+ }
36
45
  typedRow[columnName] = value;
37
46
  });
38
47
  typedRows.push(typedRow);
@@ -5,7 +5,7 @@ import { parseCsv } from "./parse/csv-parse.js";
5
5
  import { validateCsvAgainstSchema } from "./parse/csv-schema-validation.js";
6
6
  import { buildRowErrorMessage, createUploadError, hasFileParseErrors, isCsvParseError, mergeValidationWithParseErrors, toErrorMessage } from "./parse/csv-upload-utils.js";
7
7
  import { CSV_FIRST_DATA_ROW_NUMBER } from "./parse/csv-constants.js";
8
- const useCsvUpload = ({ dataSource, importMode = "direct", importSchema, importTable, onImportSessionEnded, onImportSessionStarted, onError, onImported, onPreview, onProcessingStarted, maxRows, parseOptions, rowDefaults })=>{
8
+ const useCsvUpload = ({ dataSource, importMode = "direct", importSchema, importTable, onImportSessionEnded, onImportSessionStarted, onError, onImported, onPreview, onProcessingStarted, maxRows, parseOptions, rowDefaults, validators })=>{
9
9
  const { getServerAPI } = useData();
10
10
  const [validation, setValidation] = useState();
11
11
  const [sessionTable, setSessionTable] = useState();
@@ -140,7 +140,7 @@ const useCsvUpload = ({ dataSource, importMode = "direct", importSchema, importT
140
140
  if (rowNum < CSV_FIRST_DATA_ROW_NUMBER) continue;
141
141
  const existing = vuuMsgByRow.get(rowNum);
142
142
  const columnError = `${column}: ${message}`;
143
- vuuMsgByRow.set(rowNum, existing ? `${existing}; ${columnError}` : `Row ${rowNum}: ${columnError}`);
143
+ vuuMsgByRow.set(rowNum, existing ? `${existing}; ${columnError}` : `${0 === rowNum ? "Header" : `Row ${rowNum}`}: ${columnError}`);
144
144
  }
145
145
  const parsedRowCount = mergedValidation.rows.length;
146
146
  const unparsedErrorRows = [
@@ -174,7 +174,7 @@ const useCsvUpload = ({ dataSource, importMode = "direct", importSchema, importT
174
174
  if (isRpcError(result)) throw Error(result.errorMessage);
175
175
  if ("string" == typeof result) throw Error(result);
176
176
  } catch (error) {
177
- rpcErrors.push(`Row ${rowNum}: ${toErrorMessage(error)}`);
177
+ rpcErrors.push(`${0 === rowNum ? "Header" : `Row ${rowNum}`}: ${toErrorMessage(error)}`);
178
178
  }
179
179
  }
180
180
  if (rpcErrors.length > 0) throw Error(buildRowErrorMessage("Import failed", rpcErrors));
@@ -240,7 +240,8 @@ const useCsvUpload = ({ dataSource, importMode = "direct", importSchema, importT
240
240
  return;
241
241
  }
242
242
  const schemaValidation = validateCsvAgainstSchema(parsedCsv, schema, {
243
- maxRows
243
+ maxRows,
244
+ validators
244
245
  });
245
246
  if (Object.keys(schemaValidation.errorMap.fileErrors).length > 0) {
246
247
  setValidation(schemaValidation);
@@ -288,7 +289,8 @@ const useCsvUpload = ({ dataSource, importMode = "direct", importSchema, importT
288
289
  dataSource.table,
289
290
  table,
290
291
  endEditSessionAndNotify,
291
- schema
292
+ schema,
293
+ validators
292
294
  ]);
293
295
  const handleFiles = useCallback(async (files)=>{
294
296
  const file = files[0];
@@ -1,4 +1,4 @@
1
- import { jsx, jsxs } from "react/jsx-runtime";
1
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
2
  import { TabListNext, TabNext, TabNextPanel, TabNextTrigger, TabsNext } from "@salt-ds/lab";
3
3
  import { useComponentCssInjection } from "@salt-ds/styles";
4
4
  import { useWindow } from "@salt-ds/window";
@@ -8,6 +8,7 @@ import { useEditCalculatedColumn } from "../calculated-column/useEditCalculatedC
8
8
  import { ColumnPicker } from "../column-picker/ColumnPicker.js";
9
9
  import { ColumnSettingsPanel } from "../column-settings-panel/ColumnSettingsPanel.js";
10
10
  import { TableSettingsPanel } from "../table-settings-panel/TableSettingsPanel.js";
11
+ import { Button } from "@salt-ds/core";
11
12
  import TabbedTableConfigPanel from "./TabbedTableConfigPanel.css";
12
13
  const classBase = "vuuTabbedTableConfigPanel";
13
14
  const TabbedTableConfigPanel_TabbedTableConfigPanel = ({ allowCreateCalculatedColumn = false, className, columnModel, config, onDisplayAttributeChange, selectedTab = "table-settings", vuuTable, ...htmlAttributes })=>{
@@ -76,12 +77,21 @@ const TabbedTableConfigPanel_TabbedTableConfigPanel = ({ allowCreateCalculatedCo
76
77
  }),
77
78
  /*#__PURE__*/ jsx(TabNextPanel, {
78
79
  value: "table-columns",
79
- children: /*#__PURE__*/ jsx(ColumnPicker, {
80
- allowCreateCalculatedColumn: allowCreateCalculatedColumn,
81
- columnModel: columnModel,
82
- onClickCreateCalculatedColumn: onCreateCalculatedColumn,
83
- onSelectionChange: handleSelectionChange,
84
- selected: columns
80
+ children: /*#__PURE__*/ jsxs(Fragment, {
81
+ children: [
82
+ /*#__PURE__*/ jsx(ColumnPicker, {
83
+ columnModel: columnModel,
84
+ onSelectionChange: handleSelectionChange,
85
+ selected: columns
86
+ }),
87
+ allowCreateCalculatedColumn ? /*#__PURE__*/ jsx("div", {
88
+ className: `${classBase}-column-buttons`,
89
+ children: /*#__PURE__*/ jsx(Button, {
90
+ onClick: onCreateCalculatedColumn,
91
+ children: "Create calculated column"
92
+ })
93
+ }) : null
94
+ ]
85
95
  })
86
96
  }),
87
97
  /*#__PURE__*/ jsx(TabNextPanel, {
@@ -1,6 +1,6 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
- import { useCallback, useRef } from "react";
3
2
  import { useContextPanel } from "@vuu-ui/vuu-ui-controls";
3
+ import { useCallback, useRef } from "react";
4
4
  import { TabbedTableConfigPanel } from "./TabbedTableConfigPanel.js";
5
5
  const useTabbedTableConfigPanel = ({ allowCreateCalculatedColumn, columnModel, config, onDisplayAttributeChange, vuuTable })=>{
6
6
  const showContextPanel = useContextPanel();
@@ -1,6 +1,6 @@
1
- import { HTMLAttributes } from "react";
2
- import { ColumnDescriptor } from "@vuu-ui/vuu-table-types";
3
1
  import { VuuColumnDataType, VuuTable } from "@vuu-ui/vuu-protocol-types";
2
+ import { ColumnDescriptor } from "@vuu-ui/vuu-table-types";
3
+ import { HTMLAttributes } from "react";
4
4
  import { ColumnModel } from "../column-picker/ColumnModel";
5
5
  export interface CalculatedColumnPanelProps extends HTMLAttributes<HTMLDivElement> {
6
6
  column: ColumnDescriptor;
@@ -58,12 +58,13 @@ export declare class ColumnModel extends EventEmitter<ColumnEvents> {
58
58
  * Columns already selected and rendered in Table.
59
59
  */
60
60
  selectedColumns: readonly ColumnDescriptor[]);
61
- get availableColumns(): ColumnDescriptor[];
62
- set searchPattern(pattern: string);
63
- get searchPattern(): string;
64
61
  get selectedColumns(): readonly ColumnDescriptor[];
65
62
  setSelectedColumns(selectedColumns: readonly ColumnDescriptor[], source: ColumnChangeSource, changeDescriptor?: SelectedColumnChangeDescriptor): void;
66
63
  getColumn(name: string): ColumnDescriptor;
64
+ /**
65
+ * Introduced for use by ColumnPicker
66
+ */
67
+ addRemoveOrReorderSelectedColumns(newSelectedColumns: readonly ColumnDescriptor[], source: ColumnChangeSource): void;
67
68
  addItemToSelectedColumns(name: string, source: ColumnChangeSource): void;
68
69
  removeItemFromSelectedColumns(name: string, source: ColumnChangeSource): void;
69
70
  reorderSelectedColumns(orderedColumnNames: string[], source: ColumnChangeSource): void;
@@ -1,10 +1,8 @@
1
1
  import { ColumnDescriptor } from "@vuu-ui/vuu-table-types";
2
- import { HTMLAttributes, MouseEventHandler } from "react";
2
+ import { HTMLAttributes } from "react";
3
3
  import { ListBoxProps } from "@salt-ds/core";
4
4
  import { ColumnPickerHookProps } from "./useColumnPicker";
5
5
  export declare const classBaseListItem = "vuuColumnPickerListItem";
6
6
  export interface ColumnPickerProps extends ColumnPickerHookProps, Pick<ListBoxProps<ColumnDescriptor>, "selected" | "onSelectionChange">, HTMLAttributes<HTMLDivElement> {
7
- allowCreateCalculatedColumn?: boolean;
8
- onClickCreateCalculatedColumn?: MouseEventHandler<HTMLButtonElement>;
9
7
  }
10
8
  export declare const ColumnPicker: import("react").ForwardRefExoticComponent<ColumnPickerProps & import("react").RefAttributes<HTMLDivElement>>;
@@ -1,16 +1,13 @@
1
- import { ColumnDescriptor, TableSelectionModel } from "@vuu-ui/vuu-table-types";
2
- import { FormEventHandler, MouseEventHandler } from "react";
3
- import { ColumnModel, SelectedColumnChangeType } from "./ColumnModel";
1
+ import { TableSelectionModel } from "@vuu-ui/vuu-table-types";
2
+ import { ItemDescriptor } from "@vuu-ui/vuu-ui-controls";
3
+ import { ColumnModel } from "./ColumnModel";
4
4
  export type ColumnSelectionModel = Extract<TableSelectionModel, "none" | "single">;
5
- export type SelectedColumnsChangeHandler = (columns: ColumnDescriptor[], changeType: SelectedColumnChangeType) => void;
6
5
  export interface ColumnPickerHookProps {
7
6
  columnModel: ColumnModel;
8
7
  }
9
8
  export declare const useColumnPicker: ({ columnModel: model, }: ColumnPickerHookProps) => {
10
- availableColumns: ColumnDescriptor[];
11
- onAddItemToSelectedList: MouseEventHandler<HTMLButtonElement>;
12
- onRemoveItemFromSelectedList: MouseEventHandler<HTMLButtonElement>;
13
- onChangeSearchInput: FormEventHandler;
14
- searchText: string;
15
- selectedColumns: readonly ColumnDescriptor[];
9
+ allItems: ItemDescriptor[];
10
+ selectedItems: ItemDescriptor[];
11
+ oneOrMoreColumnsIsCalculated: boolean;
12
+ handleSelectedItemsChange: (newSelectedItems: readonly ItemDescriptor[]) => void;
16
13
  };
@@ -1,8 +1,8 @@
1
- import { ColumnDescriptor, TableConfig, ColumnTypeFormatting } from "@vuu-ui/vuu-table-types";
2
- import { CellRendererDescriptor, ColumnRenderPropsChangeHandler } from "@vuu-ui/vuu-utils";
1
+ import { ColumnDescriptor, ColumnTypeFormatting, TableConfig } from "@vuu-ui/vuu-table-types";
2
+ import { DataValueTypeSimple } from "@vuu-ui/vuu-data-types";
3
3
  import { VuuColumnDataType, VuuTable } from "@vuu-ui/vuu-protocol-types";
4
+ import { CellRendererDescriptor, ColumnRenderPropsChangeHandler } from "@vuu-ui/vuu-utils";
4
5
  import { FormEventHandler } from "react";
5
- import { DataValueTypeSimple } from "@vuu-ui/vuu-data-types";
6
6
  import { ColumnModel } from "../column-picker/ColumnModel";
7
7
  /**
8
8
  * Describes the props for a Column Configuration Editor, for which
@@ -1,7 +1,7 @@
1
1
  import { type ReactNode } from "react";
2
2
  import type { RowDefaultDataItemValues, EditSession } from "@vuu-ui/vuu-data-editing";
3
3
  import type { CsvParseError, CsvParseOptions } from "./parse/csv-parse";
4
- import type { CsvValidationStructuredError } from "./parse/csv-schema-validation";
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
7
  import type { CsvUploadTableData } from "./parse/csv-upload-utils";
@@ -60,6 +60,7 @@ export interface CsvUploadProps {
60
60
  parseOptions?: CsvParseOptions;
61
61
  importMode?: "direct" | "preview";
62
62
  rowDefaults?: RowDefaultDataItemValues;
63
+ validators?: Record<string, CsvColumnValidator>;
63
64
  /**
64
65
  * Custom renderer for the error panel.
65
66
  */
@@ -1,7 +1,7 @@
1
1
  export { CsvUpload, type CsvUploadError, type CsvUploadProps, type CsvUploadErrorResult, type CsvUploadErrors, type CsvUploadPhase, type CsvUploadImportedResult, type CsvUploadPreviewResult, type CsvUploadSessionEndReason, type CsvUploadSessionEndResult, type CsvUploadSessionTable, } from "./CsvUpload";
2
2
  export { useCsvUpload, type CsvUploadHookProps, type UseCsvUploadReturn, } from "./useCsvUpload";
3
3
  export { DataUploadPreview, type DataUploadPreviewProps, } from "./data-upload-preview/DataUploadPreview";
4
- export { validateCsvAgainstSchema, type CsvValidationOptions, type CsvValidationResult, } from "./parse/csv-schema-validation";
4
+ export { validateCsvAgainstSchema, type CsvColumnValidator, type CsvValidationOptions, type CsvValidationResult, } from "./parse/csv-schema-validation";
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";
@@ -1,3 +1,3 @@
1
- export declare const CSV_HEADER_ROW_NUMBER = 1;
2
- export declare const CSV_FIRST_DATA_ROW_NUMBER = 2;
1
+ export declare const CSV_HEADER_ROW_NUMBER = 0;
2
+ export declare const CSV_FIRST_DATA_ROW_NUMBER = 1;
3
3
  export declare const MAX_ROWS_IN_CSV = 10000;
@@ -12,7 +12,8 @@ export declare enum CsvValidationErrorEnum {
12
12
  UNKNOWN_COLUMN = "UNKNOWN_COLUMN",
13
13
  MAX_ROWS_EXCEEDED = "MAX_ROWS_EXCEEDED",
14
14
  EMPTY_NON_STRING_VALUE = "EMPTY_NON_STRING_VALUE",
15
- TYPE_MISMATCH = "TYPE_MISMATCH"
15
+ TYPE_MISMATCH = "TYPE_MISMATCH",
16
+ CUSTOM_VALIDATION = "CUSTOM_VALIDATION"
16
17
  }
17
18
  export type CsvErrorMap<TError extends string> = {
18
19
  rowErrors: Record<number, Record<string, TError[]>>;
@@ -3,6 +3,7 @@ import type { VuuRowDataItemType } from "@vuu-ui/vuu-protocol-types";
3
3
  import type { CsvValidationError, CsvValidationErrorMap, CsvParseError } from "./csv-errors";
4
4
  import type { CsvParseResult } from "./csv-parse";
5
5
  export { CsvValidationErrorEnum, type CsvValidationError, type CsvValidationErrorMap, type CsvValidationErrorType, type CsvValidationStructuredError, } from "./csv-errors";
6
+ export type CsvColumnValidator = (value: string, columnName: string, row: Record<string, string>) => boolean | string;
6
7
  export type CsvValidationResult = {
7
8
  columns: string[];
8
9
  errorMap: CsvValidationErrorMap;
@@ -12,5 +13,6 @@ export type CsvValidationResult = {
12
13
  };
13
14
  export type CsvValidationOptions = {
14
15
  maxRows?: number;
16
+ validators?: Record<string, CsvColumnValidator>;
15
17
  };
16
18
  export declare const validateCsvAgainstSchema: (parsed: CsvParseResult, tableSchema: TableSchema, options?: CsvValidationOptions) => CsvValidationResult;
@@ -1,5 +1,5 @@
1
1
  export { CSV_FIRST_DATA_ROW_NUMBER, CSV_HEADER_ROW_NUMBER, } from "./csv-constants";
2
2
  export { CsvParseErrorEnum, CsvValidationErrorEnum, type CsvError, type CsvErrorMap, type CsvErrorState, type CsvParseError, type CsvParseErrorMap, type CsvStructuredError, type CsvValidationError, type CsvValidationErrorMap, type CsvValidationErrorType, type CsvValidationStructuredError, addCsvFileError, addCsvRowError, createCsvErrorState, hasCsvErrors, } from "./csv-errors";
3
3
  export { parseCsv, type CsvParseOptions, type CsvParseResult, } from "./csv-parse";
4
- export { validateCsvAgainstSchema, type CsvValidationOptions, type CsvValidationResult, } from "./csv-schema-validation";
4
+ export { validateCsvAgainstSchema, type CsvColumnValidator, type CsvValidationOptions, type CsvValidationResult, } from "./csv-schema-validation";
5
5
  export { buildRowErrorMessage, executeBatchRpcCalls, hasFileParseErrors, isCsvParseError, mergeValidationWithParseErrors, normalizeTableData, toErrorMessage, toValidationErrorsFromParseRowErrors, type CsvUploadTableData, type RpcBatchResult, } from "./csv-upload-utils";
@@ -2,7 +2,7 @@ import type { DataSource, TableSchema } from "@vuu-ui/vuu-data-types";
2
2
  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
- import { type CsvValidationResult } from "./parse/csv-schema-validation";
5
+ import { type CsvValidationResult, type CsvColumnValidator } from "./parse/csv-schema-validation";
6
6
  import type { CsvUploadErrorResult, CsvUploadImportedResult, CsvUploadPreviewResult, CsvUploadSessionEndResult, CsvUploadSessionTable } from "./CsvUpload";
7
7
  export interface CsvUploadHookProps {
8
8
  dataSource: DataSource;
@@ -25,6 +25,7 @@ export interface CsvUploadHookProps {
25
25
  parseOptions?: CsvParseOptions;
26
26
  /** Default column values applied to every addRow call. Pass a stable reference — a new object triggers EditSession recreation. */
27
27
  rowDefaults?: RowDefaultDataItemValues;
28
+ validators?: Record<string, CsvColumnValidator>;
28
29
  }
29
30
  export type UseCsvUploadReturn = {
30
31
  canImport: boolean;
@@ -39,4 +40,4 @@ export type UseCsvUploadReturn = {
39
40
  validation: CsvValidationResult | undefined;
40
41
  error: CsvUploadErrorResult | undefined;
41
42
  };
42
- export declare const useCsvUpload: ({ dataSource, importMode, importSchema, importTable, onImportSessionEnded, onImportSessionStarted, onError, onImported, onPreview, onProcessingStarted, maxRows, parseOptions, rowDefaults, }: CsvUploadHookProps) => UseCsvUploadReturn;
43
+ export declare const useCsvUpload: ({ dataSource, importMode, importSchema, importTable, onImportSessionEnded, onImportSessionStarted, onError, onImported, onPreview, onProcessingStarted, maxRows, parseOptions, rowDefaults, validators, }: CsvUploadHookProps) => UseCsvUploadReturn;
package/types/index.d.ts CHANGED
@@ -11,7 +11,6 @@ export { useColumnActions } from "./column-menu/useColumnActions";
11
11
  export { ColumnChangeSource, ColumnModel, isColumnAdded, isColumnRemoved, isColumnsReordered, SelectedColumnChangeType, type ColumnEvents, type ColumnsChangeHandler, } from "./column-picker/ColumnModel";
12
12
  export { ColumnPicker, type ColumnPickerProps, } from "./column-picker/ColumnPicker";
13
13
  export { ColumnPickerAction } from "./column-picker/ColumnPickerAction";
14
- export type { SelectedColumnsChangeHandler } from "./column-picker/useColumnPicker";
15
14
  export { useTableColumnPicker } from "./column-picker/useTableColumnPicker";
16
15
  export { ColumnSettingsPanel } from "./column-settings-panel/ColumnSettingsPanel";
17
16
  export { useColumnSettings } from "./column-settings-panel/useColumnSettings";
@@ -1,7 +1,7 @@
1
1
  import { type ReactNode } from "react";
2
2
  import type { RowDefaultDataItemValues, EditSession } from "@vuu-ui/vuu-data-editing";
3
3
  import type { CsvParseError, CsvParseOptions } from "./parse/csv-parse";
4
- import type { CsvValidationStructuredError } from "./parse/csv-schema-validation";
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
7
  import type { CsvUploadTableData } from "./parse/csv-upload-utils";
@@ -60,6 +60,7 @@ export interface CsvUploadProps {
60
60
  parseOptions?: CsvParseOptions;
61
61
  importMode?: "direct" | "preview";
62
62
  rowDefaults?: RowDefaultDataItemValues;
63
+ validators?: Record<string, CsvColumnValidator>;
63
64
  /**
64
65
  * Custom renderer for the error panel.
65
66
  */
@@ -1,7 +1,7 @@
1
1
  export { CsvUpload, type CsvUploadError, type CsvUploadProps, type CsvUploadErrorResult, type CsvUploadErrors, type CsvUploadPhase, type CsvUploadImportedResult, type CsvUploadPreviewResult, type CsvUploadSessionEndReason, type CsvUploadSessionEndResult, type CsvUploadSessionTable, } from "./CsvUpload";
2
2
  export { useCsvUpload, type CsvUploadHookProps, type UseCsvUploadReturn, } from "./useCsvUpload";
3
3
  export { DataUploadPreview, type DataUploadPreviewProps, } from "./data-upload-preview/DataUploadPreview";
4
- export { validateCsvAgainstSchema, type CsvValidationOptions, type CsvValidationResult, } from "./parse/csv-schema-validation";
4
+ export { validateCsvAgainstSchema, type CsvColumnValidator, type CsvValidationOptions, type CsvValidationResult, } from "./parse/csv-schema-validation";
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";
@@ -1,3 +1,3 @@
1
- export declare const CSV_HEADER_ROW_NUMBER = 1;
2
- export declare const CSV_FIRST_DATA_ROW_NUMBER = 2;
1
+ export declare const CSV_HEADER_ROW_NUMBER = 0;
2
+ export declare const CSV_FIRST_DATA_ROW_NUMBER = 1;
3
3
  export declare const MAX_ROWS_IN_CSV = 10000;
@@ -12,7 +12,8 @@ export declare enum CsvValidationErrorEnum {
12
12
  UNKNOWN_COLUMN = "UNKNOWN_COLUMN",
13
13
  MAX_ROWS_EXCEEDED = "MAX_ROWS_EXCEEDED",
14
14
  EMPTY_NON_STRING_VALUE = "EMPTY_NON_STRING_VALUE",
15
- TYPE_MISMATCH = "TYPE_MISMATCH"
15
+ TYPE_MISMATCH = "TYPE_MISMATCH",
16
+ CUSTOM_VALIDATION = "CUSTOM_VALIDATION"
16
17
  }
17
18
  export type CsvErrorMap<TError extends string> = {
18
19
  rowErrors: Record<number, Record<string, TError[]>>;
@@ -3,6 +3,7 @@ import type { VuuRowDataItemType } from "@vuu-ui/vuu-protocol-types";
3
3
  import type { CsvValidationError, CsvValidationErrorMap, CsvParseError } from "./csv-errors";
4
4
  import type { CsvParseResult } from "./csv-parse";
5
5
  export { CsvValidationErrorEnum, type CsvValidationError, type CsvValidationErrorMap, type CsvValidationErrorType, type CsvValidationStructuredError, } from "./csv-errors";
6
+ export type CsvColumnValidator = (value: string, columnName: string, row: Record<string, string>) => boolean | string;
6
7
  export type CsvValidationResult = {
7
8
  columns: string[];
8
9
  errorMap: CsvValidationErrorMap;
@@ -12,5 +13,6 @@ export type CsvValidationResult = {
12
13
  };
13
14
  export type CsvValidationOptions = {
14
15
  maxRows?: number;
16
+ validators?: Record<string, CsvColumnValidator>;
15
17
  };
16
18
  export declare const validateCsvAgainstSchema: (parsed: CsvParseResult, tableSchema: TableSchema, options?: CsvValidationOptions) => CsvValidationResult;
@@ -1,5 +1,5 @@
1
1
  export { CSV_FIRST_DATA_ROW_NUMBER, CSV_HEADER_ROW_NUMBER, } from "./csv-constants";
2
2
  export { CsvParseErrorEnum, CsvValidationErrorEnum, type CsvError, type CsvErrorMap, type CsvErrorState, type CsvParseError, type CsvParseErrorMap, type CsvStructuredError, type CsvValidationError, type CsvValidationErrorMap, type CsvValidationErrorType, type CsvValidationStructuredError, addCsvFileError, addCsvRowError, createCsvErrorState, hasCsvErrors, } from "./csv-errors";
3
3
  export { parseCsv, type CsvParseOptions, type CsvParseResult, } from "./csv-parse";
4
- export { validateCsvAgainstSchema, type CsvValidationOptions, type CsvValidationResult, } from "./csv-schema-validation";
4
+ export { validateCsvAgainstSchema, type CsvColumnValidator, type CsvValidationOptions, type CsvValidationResult, } from "./csv-schema-validation";
5
5
  export { buildRowErrorMessage, executeBatchRpcCalls, hasFileParseErrors, isCsvParseError, mergeValidationWithParseErrors, normalizeTableData, toErrorMessage, toValidationErrorsFromParseRowErrors, type CsvUploadTableData, type RpcBatchResult, } from "./csv-upload-utils";
@@ -2,7 +2,7 @@ import type { DataSource, TableSchema } from "@vuu-ui/vuu-data-types";
2
2
  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
- import { type CsvValidationResult } from "./parse/csv-schema-validation";
5
+ import { type CsvValidationResult, type CsvColumnValidator } from "./parse/csv-schema-validation";
6
6
  import type { CsvUploadErrorResult, CsvUploadImportedResult, CsvUploadPreviewResult, CsvUploadSessionEndResult, CsvUploadSessionTable } from "./CsvUpload";
7
7
  export interface CsvUploadHookProps {
8
8
  dataSource: DataSource;
@@ -25,6 +25,7 @@ export interface CsvUploadHookProps {
25
25
  parseOptions?: CsvParseOptions;
26
26
  /** Default column values applied to every addRow call. Pass a stable reference — a new object triggers EditSession recreation. */
27
27
  rowDefaults?: RowDefaultDataItemValues;
28
+ validators?: Record<string, CsvColumnValidator>;
28
29
  }
29
30
  export type UseCsvUploadReturn = {
30
31
  canImport: boolean;
@@ -39,4 +40,4 @@ export type UseCsvUploadReturn = {
39
40
  validation: CsvValidationResult | undefined;
40
41
  error: CsvUploadErrorResult | undefined;
41
42
  };
42
- export declare const useCsvUpload: ({ dataSource, importMode, importSchema, importTable, onImportSessionEnded, onImportSessionStarted, onError, onImported, onPreview, onProcessingStarted, maxRows, parseOptions, rowDefaults, }: CsvUploadHookProps) => UseCsvUploadReturn;
43
+ export declare const useCsvUpload: ({ dataSource, importMode, importSchema, importTable, onImportSessionEnded, onImportSessionStarted, onError, onImported, onPreview, onProcessingStarted, maxRows, parseOptions, rowDefaults, validators, }: CsvUploadHookProps) => UseCsvUploadReturn;
@@ -1,12 +1,13 @@
1
- import { TableSettingsPanelProps } from "../table-settings-panel/TableSettingsPanel";
1
+ import { VuuTable } from "@vuu-ui/vuu-protocol-types";
2
2
  import { TableProps } from "@vuu-ui/vuu-table";
3
3
  import { ColumnPickerProps } from "../column-picker/ColumnPicker";
4
- import { VuuTable } from "@vuu-ui/vuu-protocol-types";
5
- export interface TabbedTableConfigPanelHookProps extends Pick<ColumnPickerProps, "allowCreateCalculatedColumn" | "columnModel">, Pick<TableSettingsPanelProps, "onDisplayAttributeChange">, Pick<TableProps, "config"> {
4
+ import { TableSettingsPanelProps } from "../table-settings-panel/TableSettingsPanel";
5
+ export interface TabbedTableConfigPanelHookProps extends Pick<ColumnPickerProps, "columnModel">, Pick<TableSettingsPanelProps, "onDisplayAttributeChange">, Pick<TableProps, "config"> {
6
6
  /**
7
7
  * only required for calculated columns
8
8
  */
9
9
  vuuTable?: VuuTable;
10
+ allowCreateCalculatedColumn?: boolean;
10
11
  }
11
12
  export declare const useTabbedTableConfigPanel: ({ allowCreateCalculatedColumn, columnModel, config, onDisplayAttributeChange, vuuTable, }: TabbedTableConfigPanelHookProps) => {
12
13
  showTabbedTableConfigPanel: (title?: string) => void;