@vuu-ui/vuu-table-extras 2.1.19-beta.1 → 2.1.19-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/package.json +13 -14
  2. package/src/calculated-column/CalculatedColumnPanel.js +95 -0
  3. package/src/calculated-column/useCalculatedColumnPanel.js +60 -0
  4. package/src/calculated-column/useEditCalculatedColumn.js +104 -0
  5. package/src/cell-edit-validators/CaseValidator.js +21 -0
  6. package/src/cell-edit-validators/PatternValidator.js +23 -0
  7. package/src/cell-edit-validators/index.js +2 -0
  8. package/src/cell-renderers/background-cell/BackgroundCell.css.js +103 -0
  9. package/src/cell-renderers/background-cell/BackgroundCell.js +76 -0
  10. package/src/cell-renderers/background-cell/BackgroundCellConfigurationEditor.css.js +9 -0
  11. package/src/cell-renderers/background-cell/BackgroundCellConfigurationEditor.js +71 -0
  12. package/src/cell-renderers/background-cell/BackgroundKeyframes.css.js +187 -0
  13. package/src/cell-renderers/background-cell/index.js +2 -0
  14. package/src/cell-renderers/background-cell/useDirection.js +25 -0
  15. package/src/cell-renderers/button-cell/IconButtonCell.css.js +23 -0
  16. package/src/cell-renderers/button-cell/IconButtonCell.js +33 -0
  17. package/src/cell-renderers/dropdown-cell/DropdownCell.css.js +36 -0
  18. package/src/cell-renderers/dropdown-cell/DropdownCell.js +84 -0
  19. package/src/cell-renderers/dropdown-cell/index.js +1 -0
  20. package/src/cell-renderers/index.js +6 -0
  21. package/src/cell-renderers/lookup-cell/LookupCell.js +15 -0
  22. package/src/cell-renderers/lookup-cell/index.js +1 -0
  23. package/src/cell-renderers/pct-progress-cell/PctProgressCell.css.js +31 -0
  24. package/src/cell-renderers/pct-progress-cell/PctProgressCell.js +49 -0
  25. package/src/cell-renderers/pct-progress-cell/index.js +1 -0
  26. package/src/cell-renderers/progress-cell/ProgressCell.css.js +43 -0
  27. package/src/cell-renderers/progress-cell/ProgressCell.js +74 -0
  28. package/src/cell-renderers/progress-cell/index.js +1 -0
  29. package/src/column-expression-input/ColumnExpressionInput.css.js +94 -0
  30. package/src/column-expression-input/ColumnExpressionInput.js +28 -0
  31. package/src/column-expression-input/column-function-descriptors.js +281 -0
  32. package/src/column-expression-input/column-language-parser/ColumnExpressionLanguage.js +18 -0
  33. package/src/column-expression-input/column-language-parser/ColumnExpressionTreeWalker.js +381 -0
  34. package/src/column-expression-input/column-language-parser/column-expression-parse-utils.js +56 -0
  35. package/src/column-expression-input/column-language-parser/index.js +3 -0
  36. package/src/column-expression-input/functionDocInfo.js +24 -0
  37. package/src/column-expression-input/highlighting.js +18 -0
  38. package/src/column-expression-input/index.js +4 -0
  39. package/src/column-expression-input/theme.js +57 -0
  40. package/src/column-expression-input/useColumnAutoComplete.js +307 -0
  41. package/src/column-expression-input/useColumnExpressionEditor.js +133 -0
  42. package/src/column-expression-input/useColumnExpressionSuggestionProvider.js +211 -0
  43. package/src/column-formatting-settings/BaseNumericFormattingSettings.js +96 -0
  44. package/src/column-formatting-settings/ColumnFormattingPanel.js +112 -0
  45. package/src/column-formatting-settings/DateTimeFormattingSettings.js +116 -0
  46. package/src/column-formatting-settings/LongTypeFormattingSettings.css.js +11 -0
  47. package/src/column-formatting-settings/LongTypeFormattingSettings.js +54 -0
  48. package/src/column-formatting-settings/index.js +3 -0
  49. package/src/column-menu/ColumnMenu.css.js +25 -0
  50. package/src/column-menu/ColumnMenu.js +69 -0
  51. package/src/column-menu/column-action-types.js +0 -0
  52. package/src/column-menu/column-menu-utils.js +318 -0
  53. package/src/column-menu/useColumnActions.js +77 -0
  54. package/src/column-picker/ColumnModel.js +113 -0
  55. package/src/column-picker/ColumnPicker.css.js +96 -0
  56. package/src/column-picker/ColumnPicker.js +204 -0
  57. package/src/column-picker/ColumnPickerAction.js +24 -0
  58. package/src/column-picker/useColumnPicker.js +42 -0
  59. package/src/column-picker/useTableColumnPicker.js +27 -0
  60. package/src/column-picker/useTableColumnPickerDeprecated.js +87 -0
  61. package/src/column-settings-panel/ColumnNameLabel.css.js +18 -0
  62. package/src/column-settings-panel/ColumnNameLabel.js +54 -0
  63. package/src/column-settings-panel/ColumnSettingsPanel.css.js +70 -0
  64. package/src/column-settings-panel/ColumnSettingsPanel.js +173 -0
  65. package/src/column-settings-panel/useColumnSettings.js +205 -0
  66. package/src/csv-upload/CsvUpload.css.js +28 -0
  67. package/src/csv-upload/CsvUpload.js +95 -0
  68. package/src/csv-upload/index.js +7 -0
  69. package/src/csv-upload/parse/csv-constants.js +3 -0
  70. package/src/csv-upload/parse/csv-errors.js +54 -0
  71. package/src/csv-upload/parse/csv-parse.js +79 -0
  72. package/src/csv-upload/parse/csv-schema-validation.js +45 -0
  73. package/src/csv-upload/parse/csv-upload-utils.js +59 -0
  74. package/src/csv-upload/parse/index.js +5 -0
  75. package/src/csv-upload/useCsvUpload.js +260 -0
  76. package/src/csv-upload/useCsvUploadSessionPreview.js +70 -0
  77. package/src/datasource-stats/DatasourceStats.css.js +56 -0
  78. package/src/datasource-stats/DatasourceStats.js +89 -0
  79. package/src/datasource-stats/useDatasourceStats.js +48 -0
  80. package/src/freeze-control/FreezeControl.css.js +108 -0
  81. package/src/freeze-control/FreezeControl.js +96 -0
  82. package/src/freeze-control/FrozenBanner.css.js +16 -0
  83. package/src/freeze-control/FrozenBanner.js +48 -0
  84. package/src/freeze-control/freezeControlBadge.js +9 -0
  85. package/src/freeze-control/useFreezeControl.js +68 -0
  86. package/src/index.js +22 -0
  87. package/src/tabbed-table-config-panel/TabbedTableConfigPanel.css.js +23 -0
  88. package/src/tabbed-table-config-panel/TabbedTableConfigPanel.js +99 -0
  89. package/src/tabbed-table-config-panel/TabbedTableSettingsAction.js +28 -0
  90. package/src/tabbed-table-config-panel/useTabbedTableConfigPanel.js +29 -0
  91. package/src/table-footer/TableFooter.css.js +22 -0
  92. package/src/table-footer/TableFooter.js +30 -0
  93. package/src/table-provider/TableProvider.js +33 -0
  94. package/src/table-settings-panel/TableSettingsPanel.css.js +70 -0
  95. package/src/table-settings-panel/TableSettingsPanel.js +128 -0
  96. package/src/table-settings-panel/useTableSettings.js +50 -0
@@ -0,0 +1,173 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { Button, FormField, FormFieldLabel, ToggleButton, ToggleButtonGroup } from "@salt-ds/core";
3
+ import { useComponentCssInjection } from "@salt-ds/styles";
4
+ import { useWindow } from "@salt-ds/window";
5
+ import { Icon, VuuInput } from "@vuu-ui/vuu-ui-controls";
6
+ import { getCalculatedColumnDetails, getDefaultAlignment, isCalculatedColumn } from "@vuu-ui/vuu-utils";
7
+ import { ColumnFormattingPanel } from "../column-formatting-settings/index.js";
8
+ import { ColumnNameLabel } from "./ColumnNameLabel.js";
9
+ import { useColumnSettings } from "./useColumnSettings.js";
10
+ import ColumnSettingsPanel from "./ColumnSettingsPanel.css";
11
+ const classBase = "vuuColumnSettingsPanel";
12
+ const getColumnLabel = (column)=>{
13
+ const { name, label } = column;
14
+ if (isCalculatedColumn(name)) return label ?? getCalculatedColumnDetails(column).name;
15
+ return label ?? name;
16
+ };
17
+ const ColumnSettingsPanel_ColumnSettingsPanel = ({ column: columnProp, columnModel, onClickEditCalculatedColumn, onConfigChange })=>{
18
+ const targetWindow = useWindow();
19
+ useComponentCssInjection({
20
+ testId: "vuu-column-settings-panel",
21
+ css: ColumnSettingsPanel,
22
+ window: targetWindow
23
+ });
24
+ const isNewCalculatedColumn = "::" === columnProp.name;
25
+ const { availableRenderers, column, navigateNextColumn, navigatePrevColumn, onChange, onChangeFormatting, onChangeRendering, onChangeToggleButton, onChangeType, onInputCommit } = useColumnSettings({
26
+ column: columnProp,
27
+ columnModel,
28
+ onConfigChange
29
+ });
30
+ const { serverDataType, align = getDefaultAlignment(serverDataType), pin, width = "" } = column;
31
+ return /*#__PURE__*/ jsxs("div", {
32
+ className: classBase,
33
+ children: [
34
+ /*#__PURE__*/ jsx("div", {
35
+ className: `${classBase}-header`,
36
+ children: /*#__PURE__*/ jsx(ColumnNameLabel, {
37
+ column: column,
38
+ onClick: onClickEditCalculatedColumn
39
+ })
40
+ }),
41
+ /*#__PURE__*/ jsxs(FormField, {
42
+ "data-field": "column-label",
43
+ children: [
44
+ /*#__PURE__*/ jsx(FormFieldLabel, {
45
+ children: "Column Label"
46
+ }),
47
+ /*#__PURE__*/ jsx(VuuInput, {
48
+ bordered: true,
49
+ className: "vuuInput",
50
+ "data-embedded": true,
51
+ onChange: onChange,
52
+ onCommit: onInputCommit,
53
+ value: getColumnLabel(column)
54
+ })
55
+ ]
56
+ }),
57
+ /*#__PURE__*/ jsxs(FormField, {
58
+ "data-field": "column-width",
59
+ children: [
60
+ /*#__PURE__*/ jsx(FormFieldLabel, {
61
+ children: "Column Width"
62
+ }),
63
+ /*#__PURE__*/ jsx(VuuInput, {
64
+ bordered: true,
65
+ className: "vuuInput",
66
+ "data-embedded": true,
67
+ onChange: onChange,
68
+ value: width,
69
+ onCommit: onInputCommit
70
+ })
71
+ ]
72
+ }),
73
+ /*#__PURE__*/ jsxs(FormField, {
74
+ "data-field": "column-alignment",
75
+ children: [
76
+ /*#__PURE__*/ jsx(FormFieldLabel, {
77
+ children: "Alignment"
78
+ }),
79
+ /*#__PURE__*/ jsxs(ToggleButtonGroup, {
80
+ onChange: onChangeToggleButton,
81
+ value: align,
82
+ children: [
83
+ /*#__PURE__*/ jsx(ToggleButton, {
84
+ value: "left",
85
+ children: /*#__PURE__*/ jsx(Icon, {
86
+ name: "align-left",
87
+ size: 16
88
+ })
89
+ }),
90
+ /*#__PURE__*/ jsx(ToggleButton, {
91
+ value: "right",
92
+ children: /*#__PURE__*/ jsx(Icon, {
93
+ name: "align-right",
94
+ size: 16
95
+ })
96
+ })
97
+ ]
98
+ })
99
+ ]
100
+ }),
101
+ /*#__PURE__*/ jsxs(FormField, {
102
+ "data-field": "column-pin",
103
+ children: [
104
+ /*#__PURE__*/ jsx(FormFieldLabel, {
105
+ children: "Pin Column"
106
+ }),
107
+ /*#__PURE__*/ jsxs(ToggleButtonGroup, {
108
+ onChange: onChangeToggleButton,
109
+ value: pin || "false",
110
+ children: [
111
+ /*#__PURE__*/ jsx(ToggleButton, {
112
+ value: "left",
113
+ children: /*#__PURE__*/ jsx(Icon, {
114
+ name: "pin-left",
115
+ size: 16
116
+ })
117
+ }),
118
+ /*#__PURE__*/ jsx(ToggleButton, {
119
+ value: "floating",
120
+ children: /*#__PURE__*/ jsx(Icon, {
121
+ name: "pin-float",
122
+ size: 16
123
+ })
124
+ }),
125
+ /*#__PURE__*/ jsx(ToggleButton, {
126
+ value: "right",
127
+ children: /*#__PURE__*/ jsx(Icon, {
128
+ name: "pin-right",
129
+ size: 16
130
+ })
131
+ }),
132
+ /*#__PURE__*/ jsx(ToggleButton, {
133
+ value: "",
134
+ children: /*#__PURE__*/ jsx(Icon, {
135
+ name: "cross-circle",
136
+ size: 16
137
+ })
138
+ })
139
+ ]
140
+ })
141
+ ]
142
+ }),
143
+ /*#__PURE__*/ jsx(ColumnFormattingPanel, {
144
+ availableRenderers: availableRenderers,
145
+ column: column,
146
+ onChangeFormatting: onChangeFormatting,
147
+ onChangeRendering: onChangeRendering,
148
+ onChangeColumnType: onChangeType
149
+ }),
150
+ /*#__PURE__*/ jsxs("div", {
151
+ className: `${classBase}-buttonBar`,
152
+ "data-align": isNewCalculatedColumn ? "right" : void 0,
153
+ children: [
154
+ /*#__PURE__*/ jsx(Button, {
155
+ className: `${classBase}-buttonNavPrev`,
156
+ variant: "secondary",
157
+ "data-icon": "arrow-left",
158
+ onClick: navigatePrevColumn,
159
+ children: "PREVIOUS"
160
+ }),
161
+ /*#__PURE__*/ jsx(Button, {
162
+ className: `${classBase}-buttonNavNext`,
163
+ variant: "secondary",
164
+ "data-icon": "arrow-right",
165
+ onClick: navigateNextColumn,
166
+ children: "NEXT"
167
+ })
168
+ ]
169
+ })
170
+ ]
171
+ });
172
+ };
173
+ export { ColumnSettingsPanel_ColumnSettingsPanel as ColumnSettingsPanel };
@@ -0,0 +1,205 @@
1
+ import { getFieldName, getRegisteredCellRenderers, getServerDataType, isValidColumnAlignment, isValidPinLocation, queryClosest, setCalculatedColumnName, updateColumnFormatting, updateColumnRenderProps, updateColumnType } from "@vuu-ui/vuu-utils";
2
+ import { useCallback, useEffect, useMemo, useState } from "react";
3
+ const integerCellRenderers = [
4
+ {
5
+ description: "Default formatter for columns with data type integer",
6
+ label: "Default Renderer (int, long)",
7
+ name: "default-int"
8
+ }
9
+ ];
10
+ const doubleCellRenderers = [
11
+ {
12
+ description: "Default formatter for columns with data type double",
13
+ label: "Default Renderer (double)",
14
+ name: "default-double"
15
+ }
16
+ ];
17
+ const decimalCellRenderers = [
18
+ {
19
+ description: "Default formatter for columns with data type decimal (2,4,6 or 8)",
20
+ label: "Default Renderer (decimal)",
21
+ name: "default-decimal"
22
+ }
23
+ ];
24
+ const stringCellRenderers = [
25
+ {
26
+ description: "Default formatter for columns with data type string",
27
+ label: "Default Renderer (string)",
28
+ name: "default-string"
29
+ }
30
+ ];
31
+ const booleanCellRenderers = [];
32
+ const getAvailableCellRenderers = (column)=>{
33
+ const serverDataType = getServerDataType(column);
34
+ switch(serverDataType){
35
+ case "char":
36
+ case "string":
37
+ return stringCellRenderers.concat(getRegisteredCellRenderers("string"));
38
+ case "int":
39
+ case "long":
40
+ return integerCellRenderers.concat(getRegisteredCellRenderers("int"));
41
+ case "double":
42
+ return doubleCellRenderers.concat(getRegisteredCellRenderers(column.serverDataType));
43
+ case "scaleddecimal2":
44
+ case "scaleddecimal4":
45
+ case "scaleddecimal6":
46
+ case "scaleddecimal8":
47
+ return decimalCellRenderers.concat(getRegisteredCellRenderers(column.serverDataType));
48
+ case "boolean":
49
+ return booleanCellRenderers.concat(getRegisteredCellRenderers("boolean"));
50
+ default:
51
+ return stringCellRenderers;
52
+ }
53
+ };
54
+ const useColumnSettings = ({ column: columnProp, columnModel })=>{
55
+ const [column, setColumn] = useState(columnModel.getColumn(columnProp.name));
56
+ useEffect(()=>{
57
+ setColumn(columnProp);
58
+ }, [
59
+ columnProp
60
+ ]);
61
+ const availableRenderers = useMemo(()=>getAvailableCellRenderers(column), [
62
+ column
63
+ ]);
64
+ const handleChangeToggleButton = useCallback((evt)=>{
65
+ const button = queryClosest(evt.target, "button");
66
+ if (button) {
67
+ const fieldName = getFieldName(button);
68
+ const { value } = button;
69
+ switch(fieldName){
70
+ case "column-alignment":
71
+ if (isValidColumnAlignment(value)) {
72
+ const newColumn = {
73
+ ...column,
74
+ align: value
75
+ };
76
+ setColumn(newColumn);
77
+ columnModel.updateColumn(newColumn);
78
+ }
79
+ break;
80
+ case "column-pin":
81
+ if (isValidPinLocation(value)) {
82
+ const newColumn = {
83
+ ...column,
84
+ pin: value || void 0
85
+ };
86
+ setColumn(newColumn);
87
+ columnModel.updateColumn(newColumn);
88
+ break;
89
+ }
90
+ }
91
+ }
92
+ }, [
93
+ column,
94
+ columnModel
95
+ ]);
96
+ const handleChange = useCallback((evt)=>{
97
+ const input = evt.target;
98
+ const fieldName = getFieldName(input);
99
+ const { value } = input;
100
+ switch(fieldName){
101
+ case "column-label":
102
+ setColumn((state)=>({
103
+ ...state,
104
+ label: value
105
+ }));
106
+ break;
107
+ case "column-name":
108
+ setColumn((state)=>setCalculatedColumnName(state, value));
109
+ break;
110
+ case "column-width":
111
+ {
112
+ const numericValue = parseInt(value, 10);
113
+ isNaN(numericValue) ? setColumn((state)=>({
114
+ ...state,
115
+ width: void 0
116
+ })) : setColumn((state)=>({
117
+ ...state,
118
+ width: numericValue
119
+ }));
120
+ }
121
+ break;
122
+ }
123
+ }, []);
124
+ const handleInputCommit = useCallback(()=>{
125
+ columnModel.updateColumn(column);
126
+ }, [
127
+ column,
128
+ columnModel
129
+ ]);
130
+ const handleChangeCalculatedColumnName = useCallback((name)=>{
131
+ setColumn((state)=>({
132
+ ...state,
133
+ name
134
+ }));
135
+ }, []);
136
+ const handleChangeRendering = useCallback((renderProps)=>{
137
+ if (renderProps) {
138
+ const newColumn = updateColumnRenderProps(column, renderProps);
139
+ setColumn(newColumn);
140
+ columnModel.updateColumn(newColumn);
141
+ }
142
+ }, [
143
+ column,
144
+ columnModel
145
+ ]);
146
+ const handleChangeFormatting = useCallback((formatting)=>{
147
+ const newColumn = updateColumnFormatting(column, formatting);
148
+ setColumn(newColumn);
149
+ columnModel.updateColumn(newColumn);
150
+ }, [
151
+ column,
152
+ columnModel
153
+ ]);
154
+ const handleChangeType = useCallback((type)=>{
155
+ const updatedColumn = updateColumnType(column, type);
156
+ setColumn(updatedColumn);
157
+ columnModel.updateColumn(updatedColumn);
158
+ }, [
159
+ column,
160
+ columnModel
161
+ ]);
162
+ const handleChangeServerDataType = useCallback((serverDataType)=>{
163
+ setColumn((col)=>({
164
+ ...col,
165
+ serverDataType
166
+ }));
167
+ }, []);
168
+ const navigateColumn = useCallback(({ moveBy })=>{
169
+ const index = columnModel.selectedColumns.indexOf(column) + moveBy;
170
+ const newColumn = columnModel.selectedColumns[index];
171
+ if (newColumn) setColumn(newColumn);
172
+ }, [
173
+ column,
174
+ columnModel
175
+ ]);
176
+ const navigateNextColumn = useCallback(()=>{
177
+ navigateColumn({
178
+ moveBy: 1
179
+ });
180
+ }, [
181
+ navigateColumn
182
+ ]);
183
+ const navigatePrevColumn = useCallback(()=>{
184
+ navigateColumn({
185
+ moveBy: -1
186
+ });
187
+ }, [
188
+ navigateColumn
189
+ ]);
190
+ return {
191
+ availableRenderers,
192
+ column,
193
+ navigateNextColumn,
194
+ navigatePrevColumn,
195
+ onChange: handleChange,
196
+ onChangeCalculatedColumnName: handleChangeCalculatedColumnName,
197
+ onChangeFormatting: handleChangeFormatting,
198
+ onChangeRendering: handleChangeRendering,
199
+ onChangeServerDataType: handleChangeServerDataType,
200
+ onChangeToggleButton: handleChangeToggleButton,
201
+ onChangeType: handleChangeType,
202
+ onInputCommit: handleInputCommit
203
+ };
204
+ };
205
+ export { useColumnSettings };
@@ -0,0 +1,28 @@
1
+ const css = `
2
+ .vuuCsvUpload {
3
+ gap: var(--salt-spacing-200);
4
+ display: grid;
5
+ }
6
+
7
+ .vuuCsvUpload-dropZone {
8
+ align-items: center;
9
+ gap: var(--salt-spacing-100);
10
+ min-height: calc(var(--salt-size-base) * 4);
11
+ padding: var(--salt-spacing-300);
12
+ text-align: center;
13
+ justify-items: center;
14
+ display: grid;
15
+ }
16
+
17
+ .vuuCsvUpload-errors {
18
+ border: solid 1px var(--salt-status-error-borderColor);
19
+ border-radius: var(--salt-size-borderRadius);
20
+ color: var(--salt-status-error-foreground);
21
+ gap: var(--salt-spacing-100);
22
+ padding: var(--salt-spacing-200);
23
+ display: grid;
24
+ }
25
+
26
+
27
+ `;
28
+ export default css;
@@ -0,0 +1,95 @@
1
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
+ import { Button, Dialog, DialogActions, DialogContent, DialogHeader, FileDropZone, FileDropZoneIcon, FileDropZoneTrigger } from "@salt-ds/core";
3
+ import { useComponentCssInjection } from "@salt-ds/styles";
4
+ import { useWindow } from "@salt-ds/window";
5
+ import { useCallback, useState } from "react";
6
+ import { useCsvUpload } from "./useCsvUpload.js";
7
+ import CsvUpload from "./CsvUpload.css";
8
+ const classBase = "vuuCsvUpload";
9
+ const CsvUpload_CsvUpload = (props)=>{
10
+ const { children, dialogTitle = "Import CSV", onCancel, onClose, open } = props;
11
+ const isControlledOpen = void 0 !== open;
12
+ const [internalOpen, setInternalOpen] = useState(open ?? true);
13
+ const handleCancel = useCallback(()=>{
14
+ if (!isControlledOpen) setInternalOpen(false);
15
+ onCancel?.();
16
+ }, [
17
+ isControlledOpen,
18
+ onCancel
19
+ ]);
20
+ const dialogOpen = isControlledOpen ? open : internalOpen;
21
+ const targetWindow = useWindow();
22
+ useComponentCssInjection({
23
+ testId: "vuu-csv-upload",
24
+ css: CsvUpload,
25
+ window: targetWindow
26
+ });
27
+ const { canImport, isProcessingFile, isImporting, importData, onDrop, onTriggerChange, schema, validation } = useCsvUpload(props);
28
+ const handleImport = useCallback(async ()=>{
29
+ await importData();
30
+ onClose?.();
31
+ }, [
32
+ importData,
33
+ onClose
34
+ ]);
35
+ return /*#__PURE__*/ jsxs(Dialog, {
36
+ open: dialogOpen,
37
+ children: [
38
+ /*#__PURE__*/ jsx(DialogHeader, {
39
+ header: dialogTitle
40
+ }),
41
+ /*#__PURE__*/ jsx(DialogContent, {
42
+ children: /*#__PURE__*/ jsxs("div", {
43
+ className: classBase,
44
+ children: [
45
+ /*#__PURE__*/ jsxs(FileDropZone, {
46
+ className: `${classBase}-dropZone`,
47
+ disabled: void 0 === schema || isProcessingFile || isImporting,
48
+ onDrop: onDrop,
49
+ status: validation && validation.errors.length > 0 ? "error" : void 0,
50
+ children: [
51
+ /*#__PURE__*/ jsx(FileDropZoneIcon, {}),
52
+ validation && validation.errors.length > 0 ? /*#__PURE__*/ jsxs(Fragment, {
53
+ children: [
54
+ /*#__PURE__*/ jsx("div", {
55
+ children: "Your file contains errors"
56
+ }),
57
+ /*#__PURE__*/ jsx("div", {
58
+ children: " Please rectify and reupload"
59
+ })
60
+ ]
61
+ }) : /*#__PURE__*/ jsx("div", {
62
+ children: "Drop a file here or"
63
+ }),
64
+ /*#__PURE__*/ jsx(FileDropZoneTrigger, {
65
+ accept: ".csv,text/csv",
66
+ onChange: onTriggerChange,
67
+ children: "BROWSE FILES"
68
+ })
69
+ ]
70
+ }),
71
+ children
72
+ ]
73
+ })
74
+ }),
75
+ /*#__PURE__*/ jsxs(DialogActions, {
76
+ children: [
77
+ /*#__PURE__*/ jsx(Button, {
78
+ appearance: "solid",
79
+ sentiment: "negative",
80
+ onClick: handleCancel,
81
+ children: "Cancel"
82
+ }),
83
+ /*#__PURE__*/ jsx(Button, {
84
+ disabled: !canImport,
85
+ appearance: "solid",
86
+ sentiment: "accented",
87
+ onClick: handleImport,
88
+ children: isProcessingFile ? "Validating..." : isImporting ? "Importing..." : "Import"
89
+ })
90
+ ]
91
+ })
92
+ ]
93
+ });
94
+ };
95
+ export { CsvUpload_CsvUpload as CsvUpload };
@@ -0,0 +1,7 @@
1
+ export { CsvUpload } from "./CsvUpload.js";
2
+ export { useCsvUpload } from "./useCsvUpload.js";
3
+ export { useCsvUploadSessionPreview } from "./useCsvUploadSessionPreview.js";
4
+ export { validateCsvAgainstSchema } from "./parse/csv-schema-validation.js";
5
+ export { parseCsv } from "./parse/csv-parse.js";
6
+ export { CsvParseErrorEnum, CsvValidationErrorEnum } from "./parse/csv-errors.js";
7
+ export { CSV_FIRST_DATA_ROW_NUMBER, CSV_HEADER_ROW_NUMBER } from "./parse/csv-constants.js";
@@ -0,0 +1,3 @@
1
+ const CSV_HEADER_ROW_NUMBER = 1;
2
+ const CSV_FIRST_DATA_ROW_NUMBER = 2;
3
+ export { CSV_FIRST_DATA_ROW_NUMBER, CSV_HEADER_ROW_NUMBER };
@@ -0,0 +1,54 @@
1
+ import { CSV_HEADER_ROW_NUMBER } from "./csv-constants.js";
2
+ var csv_errors_CsvParseErrorEnum = /*#__PURE__*/ function(CsvParseErrorEnum) {
3
+ CsvParseErrorEnum["EMPTY_FILE"] = "EMPTY_FILE";
4
+ CsvParseErrorEnum["INVALID_SEPARATOR"] = "INVALID_SEPARATOR";
5
+ CsvParseErrorEnum["INVALID_FORMAT"] = "INVALID_FORMAT";
6
+ CsvParseErrorEnum["UNQUOTED_VALUE"] = "UNQUOTED_VALUE";
7
+ CsvParseErrorEnum["EMPTY_HEADER_COLUMN"] = "EMPTY_HEADER_COLUMN";
8
+ CsvParseErrorEnum["DUPLICATE_HEADER_COLUMN"] = "DUPLICATE_HEADER_COLUMN";
9
+ CsvParseErrorEnum["ROW_COLUMN_COUNT_MISMATCH"] = "ROW_COLUMN_COUNT_MISMATCH";
10
+ return CsvParseErrorEnum;
11
+ }({});
12
+ var csv_errors_CsvValidationErrorEnum = /*#__PURE__*/ function(CsvValidationErrorEnum) {
13
+ CsvValidationErrorEnum["MISSING_KEY_COLUMN"] = "MISSING_KEY_COLUMN";
14
+ CsvValidationErrorEnum["UNKNOWN_COLUMN"] = "UNKNOWN_COLUMN";
15
+ CsvValidationErrorEnum["MAX_ROWS_EXCEEDED"] = "MAX_ROWS_EXCEEDED";
16
+ CsvValidationErrorEnum["EMPTY_NON_STRING_VALUE"] = "EMPTY_NON_STRING_VALUE";
17
+ CsvValidationErrorEnum["TYPE_MISMATCH"] = "TYPE_MISMATCH";
18
+ return CsvValidationErrorEnum;
19
+ }({});
20
+ const createCsvErrorState = ()=>({
21
+ errorMap: {
22
+ rowErrors: {},
23
+ fileErrors: {}
24
+ },
25
+ errors: []
26
+ });
27
+ const addCsvFileError = (state, columnName, error, message, value = "")=>{
28
+ const existing = state.errorMap.fileErrors[columnName] ?? [];
29
+ existing.push(error);
30
+ state.errorMap.fileErrors[columnName] = existing;
31
+ state.errors.push({
32
+ rowNum: CSV_HEADER_ROW_NUMBER,
33
+ column: columnName,
34
+ value,
35
+ message,
36
+ errorEnum: error
37
+ });
38
+ };
39
+ const addCsvRowError = (state, rowNum, columnName, error, message, value = "")=>{
40
+ const rowErrors = state.errorMap.rowErrors[rowNum] ?? {};
41
+ const existing = rowErrors[columnName] ?? [];
42
+ existing.push(error);
43
+ rowErrors[columnName] = existing;
44
+ state.errorMap.rowErrors[rowNum] = rowErrors;
45
+ state.errors.push({
46
+ rowNum,
47
+ column: columnName,
48
+ value,
49
+ message,
50
+ errorEnum: error
51
+ });
52
+ };
53
+ const hasCsvErrors = (errorMap)=>Object.keys(errorMap.fileErrors).length > 0 || Object.keys(errorMap.rowErrors).length > 0;
54
+ export { addCsvFileError, addCsvRowError, createCsvErrorState, csv_errors_CsvParseErrorEnum as CsvParseErrorEnum, csv_errors_CsvValidationErrorEnum as CsvValidationErrorEnum, hasCsvErrors };
@@ -0,0 +1,79 @@
1
+ import { parse } from "csv-parse/browser/esm/sync";
2
+ import { CsvParseErrorEnum, addCsvFileError, addCsvRowError, createCsvErrorState, hasCsvErrors } from "./csv-errors.js";
3
+ import { CSV_FIRST_DATA_ROW_NUMBER, CSV_HEADER_ROW_NUMBER } from "./csv-constants.js";
4
+ const normaliseLineEndings = (input)=>input.replace(/^\uFEFF/, "").replace(/\r\n/g, "\n").replace(/\r/g, "\n");
5
+ const quotedCsvLinePattern = /^\s*"(?:""|[^"])*"\s*(,\s*"(?:""|[^"])*"\s*)*$/;
6
+ const withError = (message, errorMap, errors, header = [], rows = [])=>({
7
+ error: {
8
+ errorMap,
9
+ errors,
10
+ message
11
+ },
12
+ header,
13
+ rows
14
+ });
15
+ const parseCsv = (csvText, options)=>{
16
+ const errorState = createCsvErrorState();
17
+ const text = normaliseLineEndings(csvText);
18
+ if (0 === text.trim().length) {
19
+ addCsvFileError(errorState, "*", CsvParseErrorEnum.EMPTY_FILE, "CSV file is empty.");
20
+ return withError("CSV file is empty.", errorState.errorMap, errorState.errors);
21
+ }
22
+ const allLines = text.split("\n");
23
+ const nonEmptyLines = allLines.filter((line)=>line.trim().length > 0);
24
+ if (0 === nonEmptyLines.length) {
25
+ addCsvFileError(errorState, "*", CsvParseErrorEnum.EMPTY_FILE, "CSV file is empty.");
26
+ return withError("CSV file is empty.", errorState.errorMap, errorState.errors);
27
+ }
28
+ const firstLine = nonEmptyLines[0];
29
+ if (firstLine.includes(";") && !firstLine.includes(",")) {
30
+ addCsvFileError(errorState, "*", CsvParseErrorEnum.INVALID_SEPARATOR, "CSV must use comma separators.", firstLine);
31
+ return withError("CSV must use comma separators.", errorState.errorMap, errorState.errors);
32
+ }
33
+ if (options?.requireQuotedValues === true) {
34
+ nonEmptyLines.forEach((line, lineIndex)=>{
35
+ if (!quotedCsvLinePattern.test(line)) if (0 === lineIndex) addCsvFileError(errorState, "*", CsvParseErrorEnum.UNQUOTED_VALUE, "CSV values must be enclosed in double quotes.", line);
36
+ else addCsvRowError(errorState, lineIndex + CSV_HEADER_ROW_NUMBER, "*", CsvParseErrorEnum.UNQUOTED_VALUE, "CSV values must be enclosed in double quotes.", line);
37
+ });
38
+ if (hasCsvErrors(errorState.errorMap)) return withError("CSV values must be enclosed in double quotes.", errorState.errorMap, errorState.errors);
39
+ }
40
+ let records;
41
+ try {
42
+ records = parse(text, {
43
+ bom: true,
44
+ delimiter: ",",
45
+ escape: '"',
46
+ quote: '"',
47
+ relax_column_count: true,
48
+ skip_empty_lines: true
49
+ });
50
+ } catch (err) {
51
+ addCsvFileError(errorState, "*", CsvParseErrorEnum.INVALID_FORMAT, err instanceof Error ? err.message : "Invalid CSV format.");
52
+ return withError("Invalid CSV format.", errorState.errorMap, errorState.errors);
53
+ }
54
+ if (0 === records.length) {
55
+ addCsvFileError(errorState, "*", CsvParseErrorEnum.EMPTY_FILE, "CSV file is empty.");
56
+ return withError("CSV file is empty.", errorState.errorMap, errorState.errors);
57
+ }
58
+ const header = records[0].map((value)=>value.trim());
59
+ if (0 === header.length) addCsvFileError(errorState, "*", CsvParseErrorEnum.EMPTY_HEADER_COLUMN, "CSV header contains empty column names.");
60
+ header.forEach((col, columnIndex)=>{
61
+ if (0 === col.length) addCsvFileError(errorState, `column_${columnIndex + 1}`, CsvParseErrorEnum.EMPTY_HEADER_COLUMN, "CSV header contains empty column names.");
62
+ });
63
+ if (hasCsvErrors(errorState.errorMap)) return withError("CSV header contains empty column names.", errorState.errorMap, errorState.errors);
64
+ const duplicateHeader = header.find((col, idx)=>header.indexOf(col) !== idx);
65
+ if (duplicateHeader) {
66
+ addCsvFileError(errorState, duplicateHeader, CsvParseErrorEnum.DUPLICATE_HEADER_COLUMN, `CSV header contains duplicate column '${duplicateHeader}'.`, duplicateHeader);
67
+ return withError(`CSV header contains duplicate column '${duplicateHeader}'.`, errorState.errorMap, errorState.errors, header, []);
68
+ }
69
+ const rows = records.slice(1);
70
+ rows.forEach((row, rowIndex)=>{
71
+ if (row.length !== header.length) addCsvRowError(errorState, rowIndex + CSV_FIRST_DATA_ROW_NUMBER, "*", CsvParseErrorEnum.ROW_COLUMN_COUNT_MISMATCH, "CSV rows must have the same number of columns as the header.", row.join(","));
72
+ });
73
+ 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
+ return {
75
+ header,
76
+ rows
77
+ };
78
+ };
79
+ export { CsvParseErrorEnum, parseCsv };