@vuu-ui/vuu-table 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 (85) hide show
  1. package/package.json +12 -13
  2. package/src/CellFocusState.mjs +25 -0
  3. package/src/Row.mjs +83 -0
  4. package/src/Table.css.js +441 -0
  5. package/src/Table.mjs +297 -0
  6. package/src/VirtualColSpan.mjs +12 -0
  7. package/src/applyHighlighting.mjs +28 -0
  8. package/src/bulk-edit/BulkEditPanel.css.js +27 -0
  9. package/src/bulk-edit/BulkEditPanel.mjs +77 -0
  10. package/src/bulk-edit/ColumnCascadingUpdateEditor.css.js +86 -0
  11. package/src/bulk-edit/ColumnCascadingUpdateEditor.mjs +59 -0
  12. package/src/bulk-edit/InsertNewRowEditor.css.js +86 -0
  13. package/src/bulk-edit/InsertNewRowEditor.mjs +59 -0
  14. package/src/bulk-edit/useBulkEditPanel.mjs +61 -0
  15. package/src/bulk-edit/useColumnCascadingEditor.mjs +124 -0
  16. package/src/cell-block/CellBlock.css.js +95 -0
  17. package/src/cell-block/CellBlock.mjs +32 -0
  18. package/src/cell-block/cellblock-utils.mjs +114 -0
  19. package/src/cell-block/useCellBlockSelection.mjs +245 -0
  20. package/src/cell-renderers/checkbox-cell/CheckboxCell.css.js +16 -0
  21. package/src/cell-renderers/checkbox-cell/CheckboxCell.mjs +56 -0
  22. package/src/cell-renderers/checkbox-cell/index.mjs +1 -0
  23. package/src/cell-renderers/checkbox-row-selector/CheckboxRowSelectorCell.css.js +19 -0
  24. package/src/cell-renderers/checkbox-row-selector/CheckboxRowSelectorCell.mjs +34 -0
  25. package/src/cell-renderers/checkbox-row-selector/index.mjs +1 -0
  26. package/src/cell-renderers/index.mjs +4 -0
  27. package/src/cell-renderers/input-cell/InputCell.css.js +81 -0
  28. package/src/cell-renderers/input-cell/InputCell.mjs +73 -0
  29. package/src/cell-renderers/input-cell/index.mjs +1 -0
  30. package/src/cell-renderers/input-cell/useInputCell.mjs +199 -0
  31. package/src/cell-renderers/toggle-cell/ToggleCell.css.js +31 -0
  32. package/src/cell-renderers/toggle-cell/ToggleCell.mjs +47 -0
  33. package/src/cell-renderers/toggle-cell/index.mjs +1 -0
  34. package/src/column-header-pill/ColumnHeaderPill.css.js +34 -0
  35. package/src/column-header-pill/ColumnHeaderPill.mjs +39 -0
  36. package/src/column-header-pill/GroupColumnPill.css.js +10 -0
  37. package/src/column-header-pill/GroupColumnPill.mjs +33 -0
  38. package/src/column-header-pill/SortIndicator.css.js +15 -0
  39. package/src/column-header-pill/SortIndicator.mjs +29 -0
  40. package/src/column-header-pill/index.mjs +3 -0
  41. package/src/column-resizing/ColumnResizer.css.js +32 -0
  42. package/src/column-resizing/ColumnResizer.mjs +69 -0
  43. package/src/column-resizing/index.mjs +2 -0
  44. package/src/column-resizing/useTableColumnResize.mjs +57 -0
  45. package/src/data-row/DataRow.mjs +298 -0
  46. package/src/header-cell/GroupHeaderCell.css.js +91 -0
  47. package/src/header-cell/GroupHeaderCell.mjs +98 -0
  48. package/src/header-cell/HeaderCell.css.js +182 -0
  49. package/src/header-cell/HeaderCell.mjs +126 -0
  50. package/src/header-cell/index.mjs +2 -0
  51. package/src/index.mjs +20 -0
  52. package/src/pagination/PaginationControl.css.js +18 -0
  53. package/src/pagination/PaginationControl.mjs +34 -0
  54. package/src/pagination/index.mjs +1 -0
  55. package/src/pagination/usePagination.mjs +29 -0
  56. package/src/table-cell/TableCell.css.js +67 -0
  57. package/src/table-cell/TableCell.mjs +54 -0
  58. package/src/table-cell/TableGroupCell.css.js +91 -0
  59. package/src/table-cell/TableGroupCell.mjs +61 -0
  60. package/src/table-cell/index.mjs +2 -0
  61. package/src/table-config/useTableConfig.mjs +49 -0
  62. package/src/table-config.mjs +38 -0
  63. package/src/table-data-source/DataRowMovingWindow.mjs +66 -0
  64. package/src/table-data-source/useDataSource.mjs +177 -0
  65. package/src/table-dom-utils.mjs +162 -0
  66. package/src/table-header/HeaderProvider.mjs +14 -0
  67. package/src/table-header/TableHeader.mjs +182 -0
  68. package/src/table-header/index.mjs +2 -0
  69. package/src/table-header/useTableHeader.mjs +69 -0
  70. package/src/useCell.mjs +22 -0
  71. package/src/useCellEditing.mjs +58 -0
  72. package/src/useCellFocus.mjs +112 -0
  73. package/src/useControlledTableNavigation.mjs +34 -0
  74. package/src/useEditableCell.mjs +18 -0
  75. package/src/useInitialValue.mjs +6 -0
  76. package/src/useKeyboardNavigation.mjs +275 -0
  77. package/src/useMeasuredHeight.mjs +44 -0
  78. package/src/useResizeObserver.mjs +118 -0
  79. package/src/useRowClassNameGenerators.mjs +21 -0
  80. package/src/useSelection.mjs +101 -0
  81. package/src/useTable.mjs +588 -0
  82. package/src/useTableContextMenu.mjs +54 -0
  83. package/src/useTableModel.mjs +344 -0
  84. package/src/useTableScroll.mjs +382 -0
  85. package/src/useTableViewport.mjs +120 -0
@@ -0,0 +1,54 @@
1
+ import { useContextMenu } from "@vuu-ui/vuu-context-menu";
2
+ import { columnByAriaIndex, getAriaColIndex, getAriaRowIndex } from "@vuu-ui/vuu-utils";
3
+ import { useCallback } from "react";
4
+ const NO_ROWS = [];
5
+ const isTableLocation = (location)=>[
6
+ "grid",
7
+ "header",
8
+ "filter"
9
+ ].includes(location);
10
+ const getDataSourceRow = (dataRows, rowIndex)=>{
11
+ const row = dataRows.find((dataRow)=>dataRow.index === rowIndex);
12
+ if (row) return row;
13
+ throw Error(`useTableContextMenu data row not found for rowIndex ${rowIndex}`);
14
+ };
15
+ const useTableContextMenu = ({ allowContextMenu = true, columns, dataRows, dataSource, getSelectedRows, headerCount = 1 })=>{
16
+ const showContextMenu = useContextMenu();
17
+ const onContextMenu = useCallback((evt)=>{
18
+ const target = evt.target;
19
+ const cellEl = target?.closest("div[role='cell']");
20
+ const rowEl = target?.closest("div[role='row']");
21
+ if (cellEl && rowEl) {
22
+ const { selectedRowsCount } = dataSource;
23
+ const rowIndex = getAriaRowIndex(rowEl) - headerCount - 1;
24
+ const ariaColIndex = getAriaColIndex(cellEl);
25
+ const dataRow = getDataSourceRow(dataRows, rowIndex);
26
+ const column = columnByAriaIndex(columns, ariaColIndex);
27
+ if (!column.isSystemColumn) {
28
+ const menuOptions = {
29
+ column,
30
+ columns,
31
+ dataRow,
32
+ selectedRows: 0 === selectedRowsCount ? NO_ROWS : getSelectedRows(),
33
+ viewport: dataSource.viewport
34
+ };
35
+ const menuShowing = showContextMenu(evt, "grid", menuOptions, {
36
+ onOpenChange: (isOpen)=>{
37
+ console.log(`[useTableContextMenu] onOpenChange ${isOpen}`);
38
+ cellEl.classList.remove("ContextOpen");
39
+ }
40
+ });
41
+ if (menuShowing) cellEl.classList.add("ContextOpen");
42
+ }
43
+ }
44
+ }, [
45
+ columns,
46
+ dataRows,
47
+ dataSource,
48
+ getSelectedRows,
49
+ headerCount,
50
+ showContextMenu
51
+ ]);
52
+ return allowContextMenu ? onContextMenu : void 0;
53
+ };
54
+ export { isTableLocation, useTableContextMenu };
@@ -0,0 +1,344 @@
1
+ import { buildValidationChecker } from "@vuu-ui/vuu-data-react";
2
+ import { applyFilterToColumns, applyGroupByToColumns, applyRuntimeColumnWidthsToConfig, applySortToColumns, applyWidthToColumns, assertAllColumnsAreIncludedInSubscription, checkConfirmationPending, existingSort, flattenColumnGroup, getCellRenderer, getColumnHeaderContentRenderer, getColumnHeaderLabelRenderer, getColumnLabel, getTableHeadings, getValueFormatter, hasValidationRules, isFilteredColumn, isGroupColumn, isNumericType, isPinned, logger, removeSort, replaceColumn, sortPinnedColumns, stripFilterFromColumns } from "@vuu-ui/vuu-utils";
3
+ import { useReducer } from "react";
4
+ const { info: info } = logger("useTableModel");
5
+ const DEFAULT_COLUMN_WIDTH = 100;
6
+ const columnWithoutDataType = ({ serverDataType })=>void 0 === serverDataType;
7
+ const getDataType = (column, tableSchema)=>{
8
+ const schemaColumn = tableSchema?.columns.find(({ name })=>name === column.name);
9
+ if (schemaColumn) return schemaColumn.serverDataType;
10
+ return column.serverDataType;
11
+ };
12
+ const CheckboxColumnDescriptor = (selectionModel, width = 25)=>({
13
+ allowColumnHeaderMenu: false,
14
+ className: "checkbox-disabled" === selectionModel ? "vuuDisabled" : void 0,
15
+ label: "",
16
+ name: "",
17
+ width,
18
+ resizeable: false,
19
+ sortable: false,
20
+ isSystemColumn: true,
21
+ type: {
22
+ name: "checkbox",
23
+ renderer: {
24
+ name: "checkbox-row-selector-cell"
25
+ }
26
+ }
27
+ });
28
+ const PinnedCheckboxColumnDescriptor = (selectionModel, width)=>({
29
+ ...CheckboxColumnDescriptor(selectionModel, width),
30
+ pin: "left"
31
+ });
32
+ const getDefaultAlignment = (serverDataType)=>void 0 === serverDataType ? void 0 : isNumericType(serverDataType) ? "right" : "left";
33
+ const tableModelReducer = (state, action)=>{
34
+ info?.(`TableModelReducer ${action.type}`);
35
+ switch(action.type){
36
+ case "init":
37
+ if ("manual" === state.tableConfig.columnLayout && "fit" === action.tableConfig.columnLayout) return init({
38
+ ...action,
39
+ tableConfig: applyRuntimeColumnWidthsToConfig(action.tableConfig, state.columns)
40
+ });
41
+ return init(action, state);
42
+ case "moveColumn":
43
+ return moveColumn(state, action);
44
+ case "resizeColumn":
45
+ return resizeColumn(state, action);
46
+ case "setTableSchema":
47
+ return setTableSchema(state, action);
48
+ case "hideColumns":
49
+ return hideColumns(state, action);
50
+ case "showColumns":
51
+ return showColumns(state, action);
52
+ case "updateColumnProp":
53
+ return updateColumnProp(state, action);
54
+ case "tableConfig":
55
+ return updateTableConfig(state, action);
56
+ default:
57
+ console.log(`unhandled action ${action.type}`);
58
+ return state;
59
+ }
60
+ };
61
+ const useTableModel = ({ config: tableConfigProp, dataSource, selectionModel, availableWidth })=>{
62
+ const [state, dispatchTableModelAction] = useReducer(tableModelReducer, {
63
+ availableWidth,
64
+ tableConfig: tableConfigProp,
65
+ dataSource,
66
+ selectionModel
67
+ }, init);
68
+ const { columns, headings, tableConfig, ...tableAttributes } = state;
69
+ return {
70
+ columns,
71
+ dispatchTableModelAction,
72
+ headings,
73
+ tableAttributes,
74
+ tableConfig
75
+ };
76
+ };
77
+ function init({ availableWidth, dataSource, selectionModel, tableConfig }, previousConfig) {
78
+ const { checkboxColumnWidth = 25, columns, ...tableAttributes } = tableConfig;
79
+ const { config: dataSourceConfig, tableSchema } = dataSource;
80
+ const toRuntimeColumnDescriptor = columnDescriptorToRuntimeColumDescriptor(tableAttributes, tableSchema);
81
+ const runtimeColumns = [];
82
+ let colIndex = selectionModel?.startsWith("checkbox") ? 2 : 1;
83
+ assertAllColumnsAreIncludedInSubscription(columns, dataSourceConfig.columns);
84
+ for (const column of columns){
85
+ runtimeColumns.push(toRuntimeColumnDescriptor(column, column.hidden ? -1 : colIndex));
86
+ if (!column.hidden) colIndex += 1;
87
+ }
88
+ if (selectionModel?.startsWith("checkbox")) {
89
+ const somePinnedLeft = runtimeColumns.some((col)=>"left" === col.pin);
90
+ runtimeColumns.splice(0, 0, toRuntimeColumnDescriptor(somePinnedLeft ? PinnedCheckboxColumnDescriptor(selectionModel, checkboxColumnWidth) : CheckboxColumnDescriptor(selectionModel, checkboxColumnWidth), 1));
91
+ }
92
+ const { columnLayout = "static", selectionBookendWidth = 4 } = tableConfig;
93
+ const runtimeColumnsWithLayout = applyWidthToColumns(runtimeColumns, {
94
+ availableWidth,
95
+ columnLayout
96
+ });
97
+ const columnsInRenderOrder = runtimeColumnsWithLayout.some(isPinned) ? sortPinnedColumns(runtimeColumnsWithLayout, selectionBookendWidth) : runtimeColumnsWithLayout;
98
+ let state = {
99
+ availableWidth,
100
+ columns: columnsInRenderOrder,
101
+ headings: getTableHeadings(columnsInRenderOrder),
102
+ tableConfig,
103
+ ...tableAttributes
104
+ };
105
+ if (dataSourceConfig) {
106
+ const { columns: _, ...rest } = dataSourceConfig;
107
+ state = updateTableConfig(state, {
108
+ type: "tableConfig",
109
+ ...rest,
110
+ confirmed: checkConfirmationPending(previousConfig)
111
+ });
112
+ }
113
+ return state;
114
+ }
115
+ const getLabel = (label, columnFormatHeader)=>{
116
+ if ("uppercase" === columnFormatHeader) return label.toUpperCase();
117
+ if ("capitalize" === columnFormatHeader) return label[0].toUpperCase() + label.slice(1).toLowerCase();
118
+ return label;
119
+ };
120
+ const columnDescriptorToRuntimeColumDescriptor = (tableAttributes, tableSchema)=>(column, ariaColIndex)=>{
121
+ const { columnDefaultWidth = DEFAULT_COLUMN_WIDTH, columnFormatHeader } = tableAttributes;
122
+ const serverDataType = getDataType(column, tableSchema);
123
+ const { align = getDefaultAlignment(serverDataType), name, label = getColumnLabel(column), source = "server", width = columnDefaultWidth, ...rest } = column;
124
+ const runtimeColumnWithDefaults = {
125
+ ...rest,
126
+ align,
127
+ ariaColIndex,
128
+ CellRenderer: getCellRenderer(column),
129
+ HeaderCellContentRenderer: getColumnHeaderContentRenderer(column),
130
+ HeaderCellLabelRenderer: getColumnHeaderLabelRenderer(column),
131
+ clientSideEditValidationCheck: hasValidationRules(column.type) ? buildValidationChecker(column.type.rules) : void 0,
132
+ label: getLabel(label, columnFormatHeader),
133
+ name,
134
+ originalIdx: ariaColIndex,
135
+ serverDataType,
136
+ source,
137
+ valueFormatter: getValueFormatter(column, serverDataType),
138
+ width
139
+ };
140
+ if (isGroupColumn(runtimeColumnWithDefaults)) runtimeColumnWithDefaults.columns = runtimeColumnWithDefaults.columns.map((col)=>columnDescriptorToRuntimeColumDescriptor(tableAttributes)(col, -1));
141
+ return runtimeColumnWithDefaults;
142
+ };
143
+ function moveColumn(state, { column, moveBy }) {
144
+ const { columns } = state;
145
+ if ("number" == typeof moveBy) {
146
+ const idx = columns.indexOf(column);
147
+ const newColumns = columns.slice();
148
+ const [movedColumns] = newColumns.splice(idx, 1);
149
+ newColumns.splice(idx + moveBy, 0, movedColumns);
150
+ return {
151
+ ...state,
152
+ columns: newColumns
153
+ };
154
+ }
155
+ return state;
156
+ }
157
+ function hideColumns(state, { columns }) {
158
+ console.log(`[useTableModel] hideColumns ${JSON.stringify(columns)}`);
159
+ if (columns.some((col)=>true !== col.hidden)) return columns.reduce((s, c)=>{
160
+ if (true !== c.hidden) return updateColumnProp(s, {
161
+ type: "updateColumnProp",
162
+ column: c,
163
+ hidden: true
164
+ });
165
+ return s;
166
+ }, state);
167
+ return state;
168
+ }
169
+ function showColumns(state, { columns }) {
170
+ if (columns.some((col)=>col.hidden)) return columns.reduce((s, c)=>{
171
+ if (c.hidden) return updateColumnProp(s, {
172
+ type: "updateColumnProp",
173
+ column: c,
174
+ hidden: false
175
+ });
176
+ return s;
177
+ }, state);
178
+ return state;
179
+ }
180
+ function resizeColumn(state, { column, phase, width }) {
181
+ const type = "updateColumnProp";
182
+ const resizing = "end" !== phase;
183
+ switch(phase){
184
+ case "begin":
185
+ return updateColumnProp(state, {
186
+ type,
187
+ column,
188
+ resizing
189
+ });
190
+ case "end":
191
+ {
192
+ const { tableConfig } = state;
193
+ const isFit = "fit" === tableConfig.columnLayout;
194
+ let newState = isFit ? {
195
+ ...state,
196
+ tableConfig: applyRuntimeColumnWidthsToConfig(tableConfig, state.columns)
197
+ } : state;
198
+ if (column.pin && !column.pinnedWidth && width) newState = adjustPinOffsets(newState, column, width);
199
+ return updateColumnProp(newState, {
200
+ type,
201
+ column,
202
+ resizing,
203
+ width
204
+ });
205
+ }
206
+ case "resize":
207
+ return updateColumnProp(state, {
208
+ type,
209
+ column,
210
+ width
211
+ });
212
+ default:
213
+ throw Error(`useTableModel.resizeColumn, invalid resizePhase ${phase}`);
214
+ }
215
+ }
216
+ function setTableSchema(state, { tableSchema }) {
217
+ const { columns } = state;
218
+ if (!columns.some(columnWithoutDataType)) return state;
219
+ {
220
+ const cols = columns.map((column)=>{
221
+ const serverDataType = getDataType(column, tableSchema);
222
+ return {
223
+ ...column,
224
+ align: column.align ?? getDefaultAlignment(serverDataType),
225
+ serverDataType
226
+ };
227
+ });
228
+ return {
229
+ ...state,
230
+ columns: cols
231
+ };
232
+ }
233
+ }
234
+ function adjustPinOffsets(state, column, width) {
235
+ const newColumns = state.columns.slice();
236
+ if ("left" === column.pin) {
237
+ const diff = width - column.width;
238
+ const colIndex = newColumns.findIndex((col)=>col.name === column.name);
239
+ for(let i = colIndex + 1; i < newColumns.length; i++){
240
+ const pinnedColumn = newColumns.at(i);
241
+ if (pinnedColumn?.pin === "left" && "number" == typeof pinnedColumn?.pinnedOffset) {
242
+ newColumns[i] = {
243
+ ...newColumns[i],
244
+ pinnedOffset: pinnedColumn.pinnedOffset + diff
245
+ };
246
+ if (pinnedColumn.pinnedWidth) {
247
+ newColumns[i] = {
248
+ ...newColumns[i],
249
+ pinnedWidth: pinnedColumn.pinnedWidth + diff
250
+ };
251
+ break;
252
+ }
253
+ } else throw Error("[useTableModel] adjustPinOffsets, invalid column pin, no endPin following pinned column");
254
+ }
255
+ } else if ("right" === column.pin) throw "whoaaargh";
256
+ else throw Error("[useTableModel] adjustPinOffsets, invalid param, column is not pinned ");
257
+ return {
258
+ ...state,
259
+ columns: newColumns
260
+ };
261
+ }
262
+ function updateColumnProp(state, action) {
263
+ let { columns, tableConfig } = state;
264
+ const { align, column, hidden, label, resizing, width } = action;
265
+ const targetColumn = columns.find((col)=>col.name === column.name);
266
+ if (targetColumn) {
267
+ if ("left" === align || "right" === align) columns = replaceColumn(columns, {
268
+ ...targetColumn,
269
+ align
270
+ });
271
+ if ("string" == typeof label) columns = replaceColumn(columns, {
272
+ ...targetColumn,
273
+ label
274
+ });
275
+ if ("boolean" == typeof resizing) columns = replaceColumn(columns, {
276
+ ...targetColumn,
277
+ resizing
278
+ });
279
+ if ("boolean" == typeof hidden) columns = replaceColumn(columns, {
280
+ ...targetColumn,
281
+ hidden
282
+ });
283
+ if ("number" == typeof width) {
284
+ columns = replaceColumn(columns, {
285
+ ...targetColumn,
286
+ width
287
+ });
288
+ const targetConfigColumn = tableConfig.columns.find((col)=>col.name === column.name);
289
+ if (targetConfigColumn) tableConfig = {
290
+ ...tableConfig,
291
+ columns: replaceColumn(tableConfig.columns, {
292
+ ...targetConfigColumn,
293
+ width
294
+ })
295
+ };
296
+ }
297
+ }
298
+ return {
299
+ ...state,
300
+ columns,
301
+ tableConfig
302
+ };
303
+ }
304
+ function updateTableConfig(state, { confirmed, filterSpec, groupBy, sort }) {
305
+ let result = state;
306
+ const { availableWidth, columnLayout = "static" } = state;
307
+ if (groupBy.length > 0) {
308
+ const groupedColumns = applyGroupByToColumns({
309
+ columns: result.columns,
310
+ groupBy,
311
+ confirmed,
312
+ availableWidth
313
+ });
314
+ const columns = applyWidthToColumns(groupedColumns, {
315
+ availableWidth,
316
+ columnLayout
317
+ });
318
+ result = {
319
+ ...state,
320
+ columns
321
+ };
322
+ } else if (result.columns.length > 0 && isGroupColumn(result.columns[0]) && confirmed) result = {
323
+ ...state,
324
+ columns: flattenColumnGroup(result.columns)
325
+ };
326
+ if (sort.sortDefs.length > 0) result = {
327
+ ...state,
328
+ columns: applySortToColumns(result.columns, sort)
329
+ };
330
+ else if (existingSort(result.columns)) result = {
331
+ ...state,
332
+ columns: removeSort(result.columns)
333
+ };
334
+ if (filterSpec.filter.length > 0) result = {
335
+ ...state,
336
+ columns: applyFilterToColumns(result.columns, filterSpec)
337
+ };
338
+ else if (result.columns.some(isFilteredColumn)) result = {
339
+ ...state,
340
+ columns: stripFilterFromColumns(result.columns)
341
+ };
342
+ return result;
343
+ }
344
+ export { useTableModel };