@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.
- package/package.json +12 -13
- package/src/CellFocusState.mjs +25 -0
- package/src/Row.mjs +83 -0
- package/src/Table.css.js +441 -0
- package/src/Table.mjs +297 -0
- package/src/VirtualColSpan.mjs +12 -0
- package/src/applyHighlighting.mjs +28 -0
- package/src/bulk-edit/BulkEditPanel.css.js +27 -0
- package/src/bulk-edit/BulkEditPanel.mjs +77 -0
- package/src/bulk-edit/ColumnCascadingUpdateEditor.css.js +86 -0
- package/src/bulk-edit/ColumnCascadingUpdateEditor.mjs +59 -0
- package/src/bulk-edit/InsertNewRowEditor.css.js +86 -0
- package/src/bulk-edit/InsertNewRowEditor.mjs +59 -0
- package/src/bulk-edit/useBulkEditPanel.mjs +61 -0
- package/src/bulk-edit/useColumnCascadingEditor.mjs +124 -0
- package/src/cell-block/CellBlock.css.js +95 -0
- package/src/cell-block/CellBlock.mjs +32 -0
- package/src/cell-block/cellblock-utils.mjs +114 -0
- package/src/cell-block/useCellBlockSelection.mjs +245 -0
- package/src/cell-renderers/checkbox-cell/CheckboxCell.css.js +16 -0
- package/src/cell-renderers/checkbox-cell/CheckboxCell.mjs +56 -0
- package/src/cell-renderers/checkbox-cell/index.mjs +1 -0
- package/src/cell-renderers/checkbox-row-selector/CheckboxRowSelectorCell.css.js +19 -0
- package/src/cell-renderers/checkbox-row-selector/CheckboxRowSelectorCell.mjs +34 -0
- package/src/cell-renderers/checkbox-row-selector/index.mjs +1 -0
- package/src/cell-renderers/index.mjs +4 -0
- package/src/cell-renderers/input-cell/InputCell.css.js +81 -0
- package/src/cell-renderers/input-cell/InputCell.mjs +73 -0
- package/src/cell-renderers/input-cell/index.mjs +1 -0
- package/src/cell-renderers/input-cell/useInputCell.mjs +199 -0
- package/src/cell-renderers/toggle-cell/ToggleCell.css.js +31 -0
- package/src/cell-renderers/toggle-cell/ToggleCell.mjs +47 -0
- package/src/cell-renderers/toggle-cell/index.mjs +1 -0
- package/src/column-header-pill/ColumnHeaderPill.css.js +34 -0
- package/src/column-header-pill/ColumnHeaderPill.mjs +39 -0
- package/src/column-header-pill/GroupColumnPill.css.js +10 -0
- package/src/column-header-pill/GroupColumnPill.mjs +33 -0
- package/src/column-header-pill/SortIndicator.css.js +15 -0
- package/src/column-header-pill/SortIndicator.mjs +29 -0
- package/src/column-header-pill/index.mjs +3 -0
- package/src/column-resizing/ColumnResizer.css.js +32 -0
- package/src/column-resizing/ColumnResizer.mjs +69 -0
- package/src/column-resizing/index.mjs +2 -0
- package/src/column-resizing/useTableColumnResize.mjs +57 -0
- package/src/data-row/DataRow.mjs +298 -0
- package/src/header-cell/GroupHeaderCell.css.js +91 -0
- package/src/header-cell/GroupHeaderCell.mjs +98 -0
- package/src/header-cell/HeaderCell.css.js +182 -0
- package/src/header-cell/HeaderCell.mjs +126 -0
- package/src/header-cell/index.mjs +2 -0
- package/src/index.mjs +20 -0
- package/src/pagination/PaginationControl.css.js +18 -0
- package/src/pagination/PaginationControl.mjs +34 -0
- package/src/pagination/index.mjs +1 -0
- package/src/pagination/usePagination.mjs +29 -0
- package/src/table-cell/TableCell.css.js +67 -0
- package/src/table-cell/TableCell.mjs +54 -0
- package/src/table-cell/TableGroupCell.css.js +91 -0
- package/src/table-cell/TableGroupCell.mjs +61 -0
- package/src/table-cell/index.mjs +2 -0
- package/src/table-config/useTableConfig.mjs +49 -0
- package/src/table-config.mjs +38 -0
- package/src/table-data-source/DataRowMovingWindow.mjs +66 -0
- package/src/table-data-source/useDataSource.mjs +177 -0
- package/src/table-dom-utils.mjs +162 -0
- package/src/table-header/HeaderProvider.mjs +14 -0
- package/src/table-header/TableHeader.mjs +182 -0
- package/src/table-header/index.mjs +2 -0
- package/src/table-header/useTableHeader.mjs +69 -0
- package/src/useCell.mjs +22 -0
- package/src/useCellEditing.mjs +58 -0
- package/src/useCellFocus.mjs +112 -0
- package/src/useControlledTableNavigation.mjs +34 -0
- package/src/useEditableCell.mjs +18 -0
- package/src/useInitialValue.mjs +6 -0
- package/src/useKeyboardNavigation.mjs +275 -0
- package/src/useMeasuredHeight.mjs +44 -0
- package/src/useResizeObserver.mjs +118 -0
- package/src/useRowClassNameGenerators.mjs +21 -0
- package/src/useSelection.mjs +101 -0
- package/src/useTable.mjs +588 -0
- package/src/useTableContextMenu.mjs +54 -0
- package/src/useTableModel.mjs +344 -0
- package/src/useTableScroll.mjs +382 -0
- package/src/useTableViewport.mjs +120 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { Range } from "@vuu-ui/vuu-utils";
|
|
2
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
3
|
+
import { DataRowMovingWindow } from "./DataRowMovingWindow.mjs";
|
|
4
|
+
import { dataRowFactory } from "../data-row/DataRow.mjs";
|
|
5
|
+
const NullDataRow = ()=>({});
|
|
6
|
+
const useDataSource = ({ autoSelectFirstRow, autoSelectRowKey, dataSource, onSizeChange, onSubscribed, renderBufferSize = 0, revealSelected, onSelect, selectionModel, suspenseProps })=>{
|
|
7
|
+
const [, forceUpdate] = useState(null);
|
|
8
|
+
const dataRows = useRef([]);
|
|
9
|
+
const isMounted = useRef(true);
|
|
10
|
+
const hasUpdated = useRef(false);
|
|
11
|
+
const rangeRef = useRef(dataSource.range);
|
|
12
|
+
const dataRowRef = useRef(NullDataRow);
|
|
13
|
+
const setColumnsRef = useRef(void 0);
|
|
14
|
+
const totalRowCountRef = useRef(0);
|
|
15
|
+
const rowAutoSelected = useRef(false);
|
|
16
|
+
const autoSelect = autoSelectRowKey ?? (autoSelectFirstRow || "single-no-deselect" === selectionModel);
|
|
17
|
+
const handleConfigChange = useCallback((_config, _range, _confirmed, configChanges)=>{
|
|
18
|
+
if (configChanges?.filterChanged) rowAutoSelected.current = false;
|
|
19
|
+
}, []);
|
|
20
|
+
useEffect(()=>{
|
|
21
|
+
if (autoSelect) dataSource.on("config", handleConfigChange);
|
|
22
|
+
return ()=>{
|
|
23
|
+
if (autoSelect) dataSource.removeListener("config", handleConfigChange);
|
|
24
|
+
};
|
|
25
|
+
}, [
|
|
26
|
+
autoSelect,
|
|
27
|
+
dataSource,
|
|
28
|
+
handleConfigChange
|
|
29
|
+
]);
|
|
30
|
+
const dataRowWindow = useMemo(()=>new DataRowMovingWindow(rangeRef.current.withBuffer), []);
|
|
31
|
+
const handleResume = useCallback(()=>{
|
|
32
|
+
const { range } = dataSource;
|
|
33
|
+
if (0 !== range.to) dataRowWindow.setRange(dataSource.range.withBuffer);
|
|
34
|
+
}, [
|
|
35
|
+
dataRowWindow,
|
|
36
|
+
dataSource
|
|
37
|
+
]);
|
|
38
|
+
useEffect(()=>()=>{
|
|
39
|
+
dataSource.removeListener("resumed", handleResume);
|
|
40
|
+
}, [
|
|
41
|
+
dataSource,
|
|
42
|
+
handleResume
|
|
43
|
+
]);
|
|
44
|
+
const setData = useCallback((updates)=>{
|
|
45
|
+
const { current: DataRow } = dataRowRef;
|
|
46
|
+
for (const row of updates)dataRowWindow.add(DataRow(row));
|
|
47
|
+
dataRows.current = dataRowWindow.data;
|
|
48
|
+
if (isMounted.current) forceUpdate({});
|
|
49
|
+
}, [
|
|
50
|
+
dataRowWindow
|
|
51
|
+
]);
|
|
52
|
+
const selectRow = useCallback((dataRow)=>{
|
|
53
|
+
const rowKey = dataRow.key;
|
|
54
|
+
dataSource.select?.({
|
|
55
|
+
preserveExistingSelection: false,
|
|
56
|
+
rowKey,
|
|
57
|
+
type: "SELECT_ROW"
|
|
58
|
+
});
|
|
59
|
+
onSelect?.(dataRow);
|
|
60
|
+
}, [
|
|
61
|
+
dataSource,
|
|
62
|
+
onSelect
|
|
63
|
+
]);
|
|
64
|
+
const createDataRow = useCallback((columns, schemaColumns)=>{
|
|
65
|
+
const [DataRow, setColumns] = dataRowFactory(columns, schemaColumns);
|
|
66
|
+
dataRowRef.current = DataRow;
|
|
67
|
+
setColumnsRef.current = setColumns;
|
|
68
|
+
}, []);
|
|
69
|
+
const datasourceMessageHandler = useCallback((message)=>{
|
|
70
|
+
if ("subscribed" === message.type) {
|
|
71
|
+
createDataRow(message.columns, message.tableSchema.columns);
|
|
72
|
+
onSubscribed?.(message);
|
|
73
|
+
} else if ("subscribe-failed" === message.type) console.warn(`subscribe failed ${message.msg}`);
|
|
74
|
+
else if ("viewport-update" === message.type) {
|
|
75
|
+
if ("number" == typeof message.size) {
|
|
76
|
+
onSizeChange?.(message.size);
|
|
77
|
+
dataRowWindow.setRowCount(message.size);
|
|
78
|
+
totalRowCountRef.current = message.size;
|
|
79
|
+
}
|
|
80
|
+
if (message.rows) {
|
|
81
|
+
setData(message.rows);
|
|
82
|
+
if (autoSelect && false === rowAutoSelected.current) {
|
|
83
|
+
rowAutoSelected.current = true;
|
|
84
|
+
if ("string" == typeof autoSelect) {
|
|
85
|
+
const dataRow = dataRowWindow.getByKey(autoSelect);
|
|
86
|
+
if (dataRow) selectRow(dataRow);
|
|
87
|
+
else console.warn(`[useDataSource] autoSelect row key ${autoSelect} not in viewport`);
|
|
88
|
+
} else if (dataRowWindow.hasData) selectRow(dataRowWindow.firstRow);
|
|
89
|
+
}
|
|
90
|
+
} else if (0 === message.size) setData([]);
|
|
91
|
+
else if ("number" == typeof message.size) {
|
|
92
|
+
dataRows.current = dataRowWindow.data;
|
|
93
|
+
hasUpdated.current = true;
|
|
94
|
+
}
|
|
95
|
+
} else if ("viewport-clear" === message.type) {
|
|
96
|
+
onSizeChange?.(0);
|
|
97
|
+
dataRowWindow.setRowCount(0);
|
|
98
|
+
setData([]);
|
|
99
|
+
forceUpdate({});
|
|
100
|
+
} else console.log(`useDataSource unexpected message ${message.type}`);
|
|
101
|
+
}, [
|
|
102
|
+
autoSelect,
|
|
103
|
+
createDataRow,
|
|
104
|
+
dataRowWindow,
|
|
105
|
+
onSizeChange,
|
|
106
|
+
onSubscribed,
|
|
107
|
+
selectRow,
|
|
108
|
+
setData
|
|
109
|
+
]);
|
|
110
|
+
const getSelectedRows = useCallback(()=>dataRowWindow.getSelectedRows(), [
|
|
111
|
+
dataRowWindow
|
|
112
|
+
]);
|
|
113
|
+
useEffect(()=>{
|
|
114
|
+
if ("disabled" === dataSource.status) dataSource.enable?.(datasourceMessageHandler);
|
|
115
|
+
}, [
|
|
116
|
+
dataSource,
|
|
117
|
+
datasourceMessageHandler
|
|
118
|
+
]);
|
|
119
|
+
useMemo(()=>{
|
|
120
|
+
setColumnsRef.current?.(dataSource.columns);
|
|
121
|
+
}, [
|
|
122
|
+
dataSource.columns
|
|
123
|
+
]);
|
|
124
|
+
const setRange = useCallback((viewportRange)=>{
|
|
125
|
+
if (!rangeRef.current.equals(viewportRange)) {
|
|
126
|
+
const range = Range(viewportRange.from, viewportRange.to, renderBufferSize);
|
|
127
|
+
dataRowWindow.setRange(range.withBuffer);
|
|
128
|
+
if ("subscribed" !== dataSource.status && "subscribing" !== dataSource.status && "enabling" !== dataSource.status) dataSource?.subscribe({
|
|
129
|
+
range,
|
|
130
|
+
revealSelected,
|
|
131
|
+
selectedKeyValues: autoSelectRowKey ? [
|
|
132
|
+
autoSelectRowKey
|
|
133
|
+
] : void 0
|
|
134
|
+
}, datasourceMessageHandler);
|
|
135
|
+
else dataSource.range = rangeRef.current = range;
|
|
136
|
+
}
|
|
137
|
+
}, [
|
|
138
|
+
autoSelectRowKey,
|
|
139
|
+
dataRowWindow,
|
|
140
|
+
dataSource,
|
|
141
|
+
datasourceMessageHandler,
|
|
142
|
+
renderBufferSize,
|
|
143
|
+
revealSelected
|
|
144
|
+
]);
|
|
145
|
+
useEffect(()=>{
|
|
146
|
+
if ("initialising" !== dataSource.status) {
|
|
147
|
+
const { columns, tableSchema } = dataSource;
|
|
148
|
+
if (tableSchema) createDataRow(columns, tableSchema.columns);
|
|
149
|
+
else throw Error("[useDataSource] a resumed dataSource must have a tableSchema");
|
|
150
|
+
dataSource.resume?.(datasourceMessageHandler);
|
|
151
|
+
if (dataSource.range.from > 0) {
|
|
152
|
+
const { from, to } = rangeRef.current.reset;
|
|
153
|
+
setRange({
|
|
154
|
+
from,
|
|
155
|
+
to
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return ()=>{
|
|
160
|
+
dataSource.suspend?.(suspenseProps?.escalateToDisable, suspenseProps?.escalateDelay);
|
|
161
|
+
};
|
|
162
|
+
}, [
|
|
163
|
+
createDataRow,
|
|
164
|
+
dataSource,
|
|
165
|
+
datasourceMessageHandler,
|
|
166
|
+
setRange,
|
|
167
|
+
suspenseProps
|
|
168
|
+
]);
|
|
169
|
+
return {
|
|
170
|
+
dataRows: dataRows.current,
|
|
171
|
+
dataRowsRef: dataRows,
|
|
172
|
+
getSelectedRows,
|
|
173
|
+
range: rangeRef.current,
|
|
174
|
+
setRange
|
|
175
|
+
};
|
|
176
|
+
};
|
|
177
|
+
export { useDataSource };
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { getAriaColIndex, getAriaRowIndex } from "@vuu-ui/vuu-utils";
|
|
2
|
+
const headerCellQuery = (colIdx)=>`.vuuTable-col-headers .vuuTableHeaderCell[aria-colindex='${colIdx}']`;
|
|
3
|
+
const dataCellQuery = (ariaRowIdx, ariaColIdx)=>`.vuuTable-table [aria-rowindex='${ariaRowIdx}'] > [aria-colindex='${ariaColIdx}']`;
|
|
4
|
+
const getLevelUp = (containerRef, cellPos)=>{
|
|
5
|
+
const cell = getTableCell(containerRef, cellPos);
|
|
6
|
+
let row = cell?.parentElement;
|
|
7
|
+
const level = parseInt(row?.ariaLevel ?? "1");
|
|
8
|
+
if (level > 1) {
|
|
9
|
+
const targetLevel = `${level - 1}`;
|
|
10
|
+
while(null !== row && row.ariaLevel !== targetLevel)row = row.previousElementSibling;
|
|
11
|
+
if (row) {
|
|
12
|
+
const nextRowIndex = parseInt(row.ariaRowIndex ?? "- 1");
|
|
13
|
+
if (-1 !== nextRowIndex) return [
|
|
14
|
+
nextRowIndex,
|
|
15
|
+
1
|
|
16
|
+
];
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return cellPos;
|
|
20
|
+
};
|
|
21
|
+
const getTableCellElement = (containerRef, [rowIdx, colIdx])=>{
|
|
22
|
+
const cssQuery = dataCellQuery(rowIdx, colIdx);
|
|
23
|
+
return containerRef.current?.querySelector(cssQuery);
|
|
24
|
+
};
|
|
25
|
+
const getTableCell = (containerRef, cellPos)=>{
|
|
26
|
+
const cell = getTableCellElement(containerRef, cellPos);
|
|
27
|
+
if (!cellIsEditable(cell)) return cell;
|
|
28
|
+
{
|
|
29
|
+
const focusableContent = cell.querySelector('button,input[type="checkbox"]');
|
|
30
|
+
return focusableContent || cell;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
const getHeaderCell = (containerRef, columnName)=>containerRef.current?.querySelector(`.vuuTableHeaderCell[data-column-name="${columnName}"]`);
|
|
34
|
+
const getFocusedCell = (el)=>{
|
|
35
|
+
if (el?.role == "cell" || el?.role === "columnheader") return el;
|
|
36
|
+
return el?.closest("[role='columnHeader'],[role='cell']");
|
|
37
|
+
};
|
|
38
|
+
const cellIsEditable = (cell)=>cell?.classList.contains("vuuTableCell-editable");
|
|
39
|
+
const cellDropdownShowing = (cell)=>{
|
|
40
|
+
if (cellIsEditable(cell)) return cell?.querySelector('.saltDropdown[aria-expanded="true"]') !== null;
|
|
41
|
+
return false;
|
|
42
|
+
};
|
|
43
|
+
const cellIsGroupCell = (cell)=>cell?.classList.contains("vuuTableGroupCell");
|
|
44
|
+
const rowIsExpanded = (cell)=>{
|
|
45
|
+
switch(cell.parentElement?.ariaExpanded){
|
|
46
|
+
case "true":
|
|
47
|
+
return true;
|
|
48
|
+
case "false":
|
|
49
|
+
return false;
|
|
50
|
+
default:
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
const cellIsTextInput = (cell)=>null !== cell.querySelector(".vuuTableInputCell");
|
|
55
|
+
const getRowElementByAriaIndex = (container, rowIndex)=>{
|
|
56
|
+
if (-1 === rowIndex) return null;
|
|
57
|
+
{
|
|
58
|
+
const activeRow = container.querySelector(`[aria-rowindex="${rowIndex}"]`);
|
|
59
|
+
if (activeRow) return activeRow;
|
|
60
|
+
throw Error(`getRowElementAtIndex no row found for index index ${rowIndex}`);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
const getIndexFromCellElement = (cellElement)=>getAriaColIndex(cellElement);
|
|
64
|
+
const getAriaCellPos = (tableCell)=>{
|
|
65
|
+
const focusedRow = tableCell.closest("[role='row']");
|
|
66
|
+
return [
|
|
67
|
+
getAriaRowIndex(focusedRow),
|
|
68
|
+
getAriaColIndex(tableCell)
|
|
69
|
+
];
|
|
70
|
+
};
|
|
71
|
+
const closestRow = (el)=>el.closest('[role="row"]');
|
|
72
|
+
const closestRowIndex = (el)=>getAriaRowIndex(closestRow(el));
|
|
73
|
+
function getNextCellPos(key, [rowIdx, colIdx], columnCount, maxRowIndex) {
|
|
74
|
+
if ("ArrowUp" === key) {
|
|
75
|
+
if (rowIdx > -1) return [
|
|
76
|
+
rowIdx - 1,
|
|
77
|
+
colIdx
|
|
78
|
+
];
|
|
79
|
+
} else if ("ArrowDown" === key) {
|
|
80
|
+
if (-1 === rowIdx) return [
|
|
81
|
+
1,
|
|
82
|
+
colIdx
|
|
83
|
+
];
|
|
84
|
+
else if (rowIdx !== maxRowIndex) return [
|
|
85
|
+
rowIdx + 1,
|
|
86
|
+
colIdx
|
|
87
|
+
];
|
|
88
|
+
} else if ("ArrowRight" === key) {
|
|
89
|
+
if (colIdx < columnCount) return [
|
|
90
|
+
rowIdx,
|
|
91
|
+
colIdx + 1
|
|
92
|
+
];
|
|
93
|
+
} else if ("ArrowLeft" === key) {
|
|
94
|
+
if (colIdx > 1) return [
|
|
95
|
+
rowIdx,
|
|
96
|
+
colIdx - 1
|
|
97
|
+
];
|
|
98
|
+
}
|
|
99
|
+
return [
|
|
100
|
+
rowIdx,
|
|
101
|
+
colIdx
|
|
102
|
+
];
|
|
103
|
+
}
|
|
104
|
+
const getNextEditableCellPos = (containerRef, key, cellPos, columnCount, maxRowIndex)=>{
|
|
105
|
+
let [lastRowIdx, lastColIdx] = cellPos;
|
|
106
|
+
do {
|
|
107
|
+
const [nextRowIdx, nextColIdx] = getNextCellPos(key, [
|
|
108
|
+
lastRowIdx,
|
|
109
|
+
lastColIdx
|
|
110
|
+
], columnCount, maxRowIndex);
|
|
111
|
+
if (nextRowIdx === lastRowIdx && nextColIdx === lastColIdx) return cellPos;
|
|
112
|
+
const cell = getTableCellElement(containerRef, [
|
|
113
|
+
nextRowIdx,
|
|
114
|
+
nextColIdx
|
|
115
|
+
]);
|
|
116
|
+
if (cellIsEditable(cell)) return [
|
|
117
|
+
nextRowIdx,
|
|
118
|
+
nextColIdx
|
|
119
|
+
];
|
|
120
|
+
[lastRowIdx, lastColIdx] = [
|
|
121
|
+
nextRowIdx,
|
|
122
|
+
nextColIdx
|
|
123
|
+
];
|
|
124
|
+
}while (true)
|
|
125
|
+
};
|
|
126
|
+
const getTreeNodeOperation = (containerRef, navigationStyle, cellPos, key, shiftKey)=>{
|
|
127
|
+
const cell = getTableCell(containerRef, cellPos);
|
|
128
|
+
if ("cell" === navigationStyle && !cellIsGroupCell(cell)) return;
|
|
129
|
+
if ("cell" == navigationStyle && !shiftKey) return;
|
|
130
|
+
if (cellIsGroupCell(cell)) {
|
|
131
|
+
const isExpanded = rowIsExpanded(cell);
|
|
132
|
+
if (true === isExpanded) {
|
|
133
|
+
if ("ArrowLeft" === key) return "collapse";
|
|
134
|
+
} else if (false === isExpanded) {
|
|
135
|
+
if ("ArrowRight" === key) return "expand";
|
|
136
|
+
else if ("ArrowLeft" === key) return "level-up";
|
|
137
|
+
} else if ("ArrowLeft" === key) return "level-up";
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
const NO_SCROLL_NECESSARY = [
|
|
141
|
+
void 0,
|
|
142
|
+
void 0
|
|
143
|
+
];
|
|
144
|
+
const howFarIsRowOutsideViewport = (rowEl, totalHeaderHeight, contentContainer = rowEl.closest(".vuuTable-contentContainer"))=>{
|
|
145
|
+
if (contentContainer) {
|
|
146
|
+
const viewport = contentContainer?.getBoundingClientRect();
|
|
147
|
+
const upperBoundary = viewport.top + totalHeaderHeight;
|
|
148
|
+
const row = rowEl.getBoundingClientRect();
|
|
149
|
+
if (row) if (row.bottom > viewport.bottom) return [
|
|
150
|
+
"down",
|
|
151
|
+
row.bottom - viewport.bottom
|
|
152
|
+
];
|
|
153
|
+
else if (row.top < upperBoundary) return [
|
|
154
|
+
"up",
|
|
155
|
+
row.top - upperBoundary
|
|
156
|
+
];
|
|
157
|
+
else return NO_SCROLL_NECESSARY;
|
|
158
|
+
throw Error("Whats going on, row not found");
|
|
159
|
+
}
|
|
160
|
+
throw Error("Whats going on, scrollbar container not found");
|
|
161
|
+
};
|
|
162
|
+
export { cellDropdownShowing, cellIsEditable, cellIsTextInput, closestRowIndex, dataCellQuery, getAriaCellPos, getFocusedCell, getHeaderCell, getIndexFromCellElement, getLevelUp, getNextCellPos, getNextEditableCellPos, getRowElementByAriaIndex, getTableCell, getTableCellElement, getTreeNodeOperation, headerCellQuery, howFarIsRowOutsideViewport };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useContext } from "react";
|
|
3
|
+
const HeaderContext = /*#__PURE__*/ createContext({
|
|
4
|
+
columns: []
|
|
5
|
+
});
|
|
6
|
+
const HeaderProvider = ({ children, columns, virtualColSpan })=>/*#__PURE__*/ jsx(HeaderContext.Provider, {
|
|
7
|
+
value: {
|
|
8
|
+
columns,
|
|
9
|
+
virtualColSpan
|
|
10
|
+
},
|
|
11
|
+
children: children
|
|
12
|
+
});
|
|
13
|
+
const useHeaderProps = ()=>useContext(HeaderContext);
|
|
14
|
+
export { HeaderProvider, useHeaderProps };
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useComponentCssInjection } from "@salt-ds/styles";
|
|
3
|
+
import { useWindow } from "@salt-ds/window";
|
|
4
|
+
import { DragDropProvider, DragOverlay, KeyboardSensor, PointerSensor, RestrictToHorizontalAxis, isGroupColumn, isNotHidden } from "@vuu-ui/vuu-utils";
|
|
5
|
+
import { cloneElement, isValidElement, memo, useMemo } from "react";
|
|
6
|
+
import { GroupHeaderCell, HeaderCell as index_mjs_HeaderCell } from "../header-cell/index.mjs";
|
|
7
|
+
import { HeaderProvider } from "./HeaderProvider.mjs";
|
|
8
|
+
import { useTableHeader } from "./useTableHeader.mjs";
|
|
9
|
+
import header_cell_HeaderCell from "../header-cell/HeaderCell.css";
|
|
10
|
+
const isHeaderElement = (h)=>/*#__PURE__*/ isValidElement(h);
|
|
11
|
+
const classBase = "vuuTableHeader";
|
|
12
|
+
const TableHeader = /*#__PURE__*/ memo(({ HeaderCell = index_mjs_HeaderCell, allowDragColumnHeader, allowSelectAll, allRowsSelected, columns, customHeader, headings, onCheckBoxColumnHeaderClick, onHeightMeasured, onMoveColumn, onMoveGroupColumn, onRemoveGroupColumn, onResizeColumn, onSortColumn, showBookends, showColumnHeaderMenus, tableConfig, tableId, virtualColSpan = 0 })=>{
|
|
13
|
+
const targetWindow = useWindow();
|
|
14
|
+
useComponentCssInjection({
|
|
15
|
+
testId: "vuu-table-header-cell",
|
|
16
|
+
css: header_cell_HeaderCell,
|
|
17
|
+
window: targetWindow
|
|
18
|
+
});
|
|
19
|
+
const [customHeaders, customHeaderCount] = useMemo(()=>{
|
|
20
|
+
const offset = headings.length;
|
|
21
|
+
const createElement = (Component, index)=>/*#__PURE__*/ jsx(Component, {
|
|
22
|
+
ariaRowIndex: offset + index + 2,
|
|
23
|
+
ariaRole: "row",
|
|
24
|
+
columns: columns,
|
|
25
|
+
virtualColSpan: virtualColSpan
|
|
26
|
+
}, index);
|
|
27
|
+
const enrichElementWithAria = (el, rowIndex)=>{
|
|
28
|
+
const offset = headings.length;
|
|
29
|
+
return /*#__PURE__*/ cloneElement(el, {
|
|
30
|
+
"aria-rowindex": rowIndex + offset + 2,
|
|
31
|
+
ariaRole: "row"
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
if (void 0 === customHeader) return [
|
|
35
|
+
null,
|
|
36
|
+
0
|
|
37
|
+
];
|
|
38
|
+
if (Array.isArray(customHeader)) {
|
|
39
|
+
const header = /*#__PURE__*/ jsx(HeaderProvider, {
|
|
40
|
+
columns: columns,
|
|
41
|
+
virtualColSpan: virtualColSpan,
|
|
42
|
+
children: customHeader.map((header, i)=>isHeaderElement(header) ? enrichElementWithAria(header, i) : createElement(header, i))
|
|
43
|
+
});
|
|
44
|
+
return [
|
|
45
|
+
header,
|
|
46
|
+
customHeader.length
|
|
47
|
+
];
|
|
48
|
+
}
|
|
49
|
+
if (!isHeaderElement(customHeader)) return [
|
|
50
|
+
createElement(customHeader, 0),
|
|
51
|
+
1
|
|
52
|
+
];
|
|
53
|
+
{
|
|
54
|
+
const header = /*#__PURE__*/ jsx(HeaderProvider, {
|
|
55
|
+
columns: columns,
|
|
56
|
+
virtualColSpan: virtualColSpan,
|
|
57
|
+
children: enrichElementWithAria(customHeader, 0)
|
|
58
|
+
});
|
|
59
|
+
return [
|
|
60
|
+
header,
|
|
61
|
+
1
|
|
62
|
+
];
|
|
63
|
+
}
|
|
64
|
+
}, [
|
|
65
|
+
columns,
|
|
66
|
+
customHeader,
|
|
67
|
+
headings.length,
|
|
68
|
+
virtualColSpan
|
|
69
|
+
]);
|
|
70
|
+
const { dragColumn, onClick, onDragEnd, onDragStart, setContainerRef } = useTableHeader({
|
|
71
|
+
columns,
|
|
72
|
+
customHeaderCount,
|
|
73
|
+
headings,
|
|
74
|
+
onHeightMeasured,
|
|
75
|
+
onMoveColumn,
|
|
76
|
+
onSortColumn,
|
|
77
|
+
tableConfig
|
|
78
|
+
});
|
|
79
|
+
const visibleColumns = columns.filter(isNotHidden);
|
|
80
|
+
return /*#__PURE__*/ jsxs("div", {
|
|
81
|
+
className: classBase,
|
|
82
|
+
ref: setContainerRef,
|
|
83
|
+
role: "rowgroup",
|
|
84
|
+
children: [
|
|
85
|
+
headings.map((colHeaders, i)=>/*#__PURE__*/ jsx("div", {
|
|
86
|
+
className: "vuuTable-heading",
|
|
87
|
+
role: "row",
|
|
88
|
+
"aria-rowindex": i + 1,
|
|
89
|
+
children: colHeaders.map(({ label, width }, j)=>/*#__PURE__*/ jsx("div", {
|
|
90
|
+
className: "vuuTable-headingCell",
|
|
91
|
+
style: {
|
|
92
|
+
width
|
|
93
|
+
},
|
|
94
|
+
children: label
|
|
95
|
+
}, j))
|
|
96
|
+
}, i)),
|
|
97
|
+
/*#__PURE__*/ jsxs(DragDropProvider, {
|
|
98
|
+
onDragEnd: onDragEnd,
|
|
99
|
+
onDragStart: onDragStart,
|
|
100
|
+
modifiers: [
|
|
101
|
+
RestrictToHorizontalAxis
|
|
102
|
+
],
|
|
103
|
+
sensors: [
|
|
104
|
+
KeyboardSensor.configure({
|
|
105
|
+
keyboardCodes: {
|
|
106
|
+
start: [
|
|
107
|
+
"Space"
|
|
108
|
+
],
|
|
109
|
+
cancel: [
|
|
110
|
+
"Escape"
|
|
111
|
+
],
|
|
112
|
+
end: [
|
|
113
|
+
"Space",
|
|
114
|
+
"Enter"
|
|
115
|
+
],
|
|
116
|
+
left: [
|
|
117
|
+
"ArrowLeft"
|
|
118
|
+
],
|
|
119
|
+
right: [
|
|
120
|
+
"ArrowRight"
|
|
121
|
+
],
|
|
122
|
+
up: [],
|
|
123
|
+
down: []
|
|
124
|
+
}
|
|
125
|
+
}),
|
|
126
|
+
PointerSensor
|
|
127
|
+
],
|
|
128
|
+
children: [
|
|
129
|
+
/*#__PURE__*/ jsxs("div", {
|
|
130
|
+
className: "vuuTableColHeaderRow",
|
|
131
|
+
role: "row",
|
|
132
|
+
"aria-rowindex": headings.length + 1,
|
|
133
|
+
children: [
|
|
134
|
+
showBookends ? /*#__PURE__*/ jsx("div", {
|
|
135
|
+
className: "vuuSelectionDecorator vuuStickyLeft"
|
|
136
|
+
}) : null,
|
|
137
|
+
visibleColumns.map((col, i)=>{
|
|
138
|
+
const allowDragDrop = !!allowDragColumnHeader && !col.pin;
|
|
139
|
+
return isGroupColumn(col) ? /*#__PURE__*/ jsx(GroupHeaderCell, {
|
|
140
|
+
column: col,
|
|
141
|
+
id: `${tableId}-${col.name}`,
|
|
142
|
+
onMoveColumn: onMoveGroupColumn,
|
|
143
|
+
onRemoveColumn: onRemoveGroupColumn,
|
|
144
|
+
onResize: onResizeColumn
|
|
145
|
+
}, col.name) : /*#__PURE__*/ jsx(HeaderCell, {
|
|
146
|
+
allowDragColumnHeader: allowDragDrop,
|
|
147
|
+
allowSelectAll: allowSelectAll,
|
|
148
|
+
allRowsSelected: allRowsSelected,
|
|
149
|
+
column: col,
|
|
150
|
+
index: i,
|
|
151
|
+
id: `${tableId}-${col.name}`,
|
|
152
|
+
onCheckBoxColumnHeaderClick: onCheckBoxColumnHeaderClick,
|
|
153
|
+
onClick: onClick,
|
|
154
|
+
onResize: onResizeColumn,
|
|
155
|
+
showColumnHeaderMenus: showColumnHeaderMenus
|
|
156
|
+
}, `${col.name}-${allowDragDrop}`);
|
|
157
|
+
}),
|
|
158
|
+
showBookends ? /*#__PURE__*/ jsx("div", {
|
|
159
|
+
className: "vuuSelectionDecorator vuuStickyRight"
|
|
160
|
+
}) : null
|
|
161
|
+
]
|
|
162
|
+
}),
|
|
163
|
+
/*#__PURE__*/ jsx(DragOverlay, {
|
|
164
|
+
children: dragColumn ? /*#__PURE__*/ jsx("div", {
|
|
165
|
+
id: dragColumn.id,
|
|
166
|
+
className: "DragColumn",
|
|
167
|
+
children: /*#__PURE__*/ jsx(HeaderCell, {
|
|
168
|
+
column: dragColumn.column,
|
|
169
|
+
className: "vuuDragging",
|
|
170
|
+
id: `${tableId}-${dragColumn.id}-dragging`,
|
|
171
|
+
index: -1
|
|
172
|
+
})
|
|
173
|
+
}) : null
|
|
174
|
+
})
|
|
175
|
+
]
|
|
176
|
+
}),
|
|
177
|
+
customHeaders
|
|
178
|
+
]
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
TableHeader.displayName = "TableHeader";
|
|
182
|
+
export { TableHeader };
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { queryClosest, reorderColumnItems, visibleColumnAtIndex } from "@vuu-ui/vuu-utils";
|
|
2
|
+
import { useCallback, useRef, useState } from "react";
|
|
3
|
+
import { useMeasuredHeight } from "../useMeasuredHeight.mjs";
|
|
4
|
+
import { useForkRef } from "@salt-ds/core";
|
|
5
|
+
const useTableHeader = ({ columns, customHeaderCount, headings, onHeightMeasured, onMoveColumn, onSortColumn })=>{
|
|
6
|
+
const containerRef = useRef(null);
|
|
7
|
+
const scrollingContainerRef = useRef(null);
|
|
8
|
+
const [dragColumn, setDragColumn] = useState(null);
|
|
9
|
+
const handleHeightMeasured = useCallback((height)=>{
|
|
10
|
+
onHeightMeasured(height, customHeaderCount + headings.length + 1);
|
|
11
|
+
}, [
|
|
12
|
+
customHeaderCount,
|
|
13
|
+
headings,
|
|
14
|
+
onHeightMeasured
|
|
15
|
+
]);
|
|
16
|
+
const { measuredRef: rowRef } = useMeasuredHeight({
|
|
17
|
+
onHeightMeasured: handleHeightMeasured
|
|
18
|
+
});
|
|
19
|
+
const setContainerRef = useCallback((el)=>{
|
|
20
|
+
containerRef.current = el;
|
|
21
|
+
if (el) scrollingContainerRef.current = el.closest(".vuuTable-contentContainer");
|
|
22
|
+
else scrollingContainerRef.current = null;
|
|
23
|
+
}, []);
|
|
24
|
+
const handleDragStart = useCallback((evt)=>{
|
|
25
|
+
const element = evt.operation.source;
|
|
26
|
+
const columnName = element.id.split("-").at(-1);
|
|
27
|
+
const column = columns.find((col)=>col.name === columnName);
|
|
28
|
+
if (void 0 === column) throw Error(`[useTableHeader] No column '${columnName}'`);
|
|
29
|
+
setDragColumn({
|
|
30
|
+
column,
|
|
31
|
+
id: element.id
|
|
32
|
+
});
|
|
33
|
+
}, [
|
|
34
|
+
columns
|
|
35
|
+
]);
|
|
36
|
+
const handleDragEnd = useCallback(()=>{
|
|
37
|
+
setTimeout(()=>{
|
|
38
|
+
const listItems = containerRef.current?.querySelectorAll(".vuuTableHeaderCell");
|
|
39
|
+
if (listItems && dragColumn?.column) {
|
|
40
|
+
const orderedColumnNames = Array.from(listItems).map(({ dataset })=>dataset.columnName);
|
|
41
|
+
onMoveColumn(dragColumn?.column.name, reorderColumnItems(columns, orderedColumnNames));
|
|
42
|
+
}
|
|
43
|
+
}, 300);
|
|
44
|
+
}, [
|
|
45
|
+
columns,
|
|
46
|
+
dragColumn,
|
|
47
|
+
onMoveColumn
|
|
48
|
+
]);
|
|
49
|
+
const handleColumnHeaderClick = useCallback((evt)=>{
|
|
50
|
+
const headerCell = queryClosest(evt.target, ".vuuTableHeaderCell");
|
|
51
|
+
const colIdx = parseInt(headerCell?.dataset.index ?? "-1");
|
|
52
|
+
const column = visibleColumnAtIndex(columns, colIdx);
|
|
53
|
+
if (column && false !== column.sortable) {
|
|
54
|
+
const isAdditive = evt.shiftKey;
|
|
55
|
+
onSortColumn(column, isAdditive);
|
|
56
|
+
}
|
|
57
|
+
}, [
|
|
58
|
+
columns,
|
|
59
|
+
onSortColumn
|
|
60
|
+
]);
|
|
61
|
+
return {
|
|
62
|
+
dragColumn,
|
|
63
|
+
onClick: handleColumnHeaderClick,
|
|
64
|
+
onDragEnd: handleDragEnd,
|
|
65
|
+
onDragStart: handleDragStart,
|
|
66
|
+
setContainerRef: useForkRef(setContainerRef, rowRef)
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
export { useTableHeader };
|
package/src/useCell.mjs
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { getColumnStyle } from "@vuu-ui/vuu-utils";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
import { useMemo } from "react";
|
|
4
|
+
const useCell = (column, classBase, isHeader)=>useMemo(()=>{
|
|
5
|
+
const className = clsx(classBase, column.className, {
|
|
6
|
+
vuuPinLeft: "left" === column.pin,
|
|
7
|
+
vuuPinRight: "right" === column.pin,
|
|
8
|
+
vuuEndPin: isHeader && column.pinnedWidth,
|
|
9
|
+
[`${classBase}-editable`]: column.editable,
|
|
10
|
+
[`${classBase}-right`]: "right" === column.align
|
|
11
|
+
});
|
|
12
|
+
const style = getColumnStyle(column);
|
|
13
|
+
return {
|
|
14
|
+
className,
|
|
15
|
+
style
|
|
16
|
+
};
|
|
17
|
+
}, [
|
|
18
|
+
classBase,
|
|
19
|
+
column,
|
|
20
|
+
isHeader
|
|
21
|
+
]);
|
|
22
|
+
export { useCell };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { isCharacterKey } from "@vuu-ui/vuu-utils";
|
|
2
|
+
import { useCallback } from "react";
|
|
3
|
+
import { cellIsTextInput } from "./table-dom-utils.mjs";
|
|
4
|
+
const useCellEditing = ({ navigate })=>{
|
|
5
|
+
const commitHandler = useCallback(()=>{
|
|
6
|
+
navigate();
|
|
7
|
+
}, [
|
|
8
|
+
navigate
|
|
9
|
+
]);
|
|
10
|
+
const editInput = useCallback((evt)=>{
|
|
11
|
+
const cellEl = evt.target;
|
|
12
|
+
const input = cellEl.matches("input") ? cellEl : cellEl.querySelector("input");
|
|
13
|
+
if (input) input.focus();
|
|
14
|
+
}, []);
|
|
15
|
+
const focusInput = useCallback((evt)=>{
|
|
16
|
+
const cellEl = evt.target;
|
|
17
|
+
const input = cellEl.querySelector("input");
|
|
18
|
+
if (input) input.focus();
|
|
19
|
+
}, []);
|
|
20
|
+
const handleKeyDown = useCallback((e)=>{
|
|
21
|
+
const el = e.target;
|
|
22
|
+
if (cellIsTextInput(el)) {
|
|
23
|
+
if (isCharacterKey(e.key)) editInput(e);
|
|
24
|
+
else if ("Enter" === e.key) focusInput(e);
|
|
25
|
+
}
|
|
26
|
+
}, [
|
|
27
|
+
editInput,
|
|
28
|
+
focusInput
|
|
29
|
+
]);
|
|
30
|
+
const handleDoubleClick = useCallback((e)=>{
|
|
31
|
+
const el = e.target;
|
|
32
|
+
if (el.matches("input") || el.querySelector("input")) {
|
|
33
|
+
editInput(e);
|
|
34
|
+
e.stopPropagation();
|
|
35
|
+
}
|
|
36
|
+
}, [
|
|
37
|
+
editInput
|
|
38
|
+
]);
|
|
39
|
+
const handleBlur = useCallback((e)=>{
|
|
40
|
+
const el = e.target;
|
|
41
|
+
el.removeEventListener("vuu-commit", commitHandler, true);
|
|
42
|
+
}, [
|
|
43
|
+
commitHandler
|
|
44
|
+
]);
|
|
45
|
+
const handleFocus = useCallback((e)=>{
|
|
46
|
+
const el = e.target;
|
|
47
|
+
el.addEventListener("vuu-commit", commitHandler, true);
|
|
48
|
+
}, [
|
|
49
|
+
commitHandler
|
|
50
|
+
]);
|
|
51
|
+
return {
|
|
52
|
+
onBlur: handleBlur,
|
|
53
|
+
onDoubleClick: handleDoubleClick,
|
|
54
|
+
onFocus: handleFocus,
|
|
55
|
+
onKeyDown: handleKeyDown
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
export { useCellEditing };
|