@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,112 @@
|
|
|
1
|
+
import { useCallback } from "react";
|
|
2
|
+
import { dataCellQuery, getTableCell, headerCellQuery } from "./table-dom-utils.mjs";
|
|
3
|
+
import { isArrowKey, queryClosest } from "@vuu-ui/vuu-utils";
|
|
4
|
+
const getCellPosition = (el)=>{
|
|
5
|
+
const top = parseInt(el.parentElement?.style.top ?? "-1");
|
|
6
|
+
return {
|
|
7
|
+
top
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
const isDifferentCellPosition = (currentPos, newPos)=>{
|
|
11
|
+
if (void 0 === currentPos) return true;
|
|
12
|
+
return currentPos[0] !== newPos[0] || currentPos[1] !== newPos[1];
|
|
13
|
+
};
|
|
14
|
+
const useCellFocus = ({ cellFocusStateRef, containerRef, disableFocus = false, requestScroll })=>{
|
|
15
|
+
const focusCellPlaceholderRef = useCallback((el)=>{
|
|
16
|
+
cellFocusStateRef.current.placeholderEl = el;
|
|
17
|
+
}, [
|
|
18
|
+
cellFocusStateRef
|
|
19
|
+
]);
|
|
20
|
+
const focusCell = useCallback((cellPos)=>{
|
|
21
|
+
if (containerRef.current) {
|
|
22
|
+
const { current: state } = cellFocusStateRef;
|
|
23
|
+
if (isDifferentCellPosition(state.cellPos, cellPos)) {
|
|
24
|
+
const activeCell = getTableCell(containerRef, cellPos);
|
|
25
|
+
if (activeCell) {
|
|
26
|
+
if (activeCell !== state.el) {
|
|
27
|
+
state.el?.removeAttribute("tabindex");
|
|
28
|
+
activeCell.setAttribute("tabindex", "0");
|
|
29
|
+
state.el = activeCell;
|
|
30
|
+
state.pos = getCellPosition(activeCell);
|
|
31
|
+
state.outsideViewport = false;
|
|
32
|
+
if (state.placeholderEl) state.placeholderEl.style.top = `${state.pos.top}px`;
|
|
33
|
+
}
|
|
34
|
+
state.cellPos = cellPos;
|
|
35
|
+
requestScroll?.({
|
|
36
|
+
type: "scroll-row",
|
|
37
|
+
rowIndex: cellPos[0]
|
|
38
|
+
});
|
|
39
|
+
activeCell.focus({
|
|
40
|
+
preventScroll: true
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}, [
|
|
46
|
+
cellFocusStateRef,
|
|
47
|
+
containerRef,
|
|
48
|
+
requestScroll
|
|
49
|
+
]);
|
|
50
|
+
const setTableBodyRef = useCallback((el)=>{
|
|
51
|
+
if (el) {
|
|
52
|
+
const { current: state } = cellFocusStateRef;
|
|
53
|
+
const table = queryClosest(el, ".vuuTable");
|
|
54
|
+
if (table) {
|
|
55
|
+
if (null === state.el && !disableFocus) {
|
|
56
|
+
const headerCell = table.querySelector(headerCellQuery(1));
|
|
57
|
+
if (headerCell) {
|
|
58
|
+
headerCell.setAttribute("tabindex", "0");
|
|
59
|
+
state.cellPos = [
|
|
60
|
+
1,
|
|
61
|
+
1
|
|
62
|
+
];
|
|
63
|
+
state.el = headerCell;
|
|
64
|
+
state.pos = {
|
|
65
|
+
top: -20
|
|
66
|
+
};
|
|
67
|
+
if (state.placeholderEl) state.placeholderEl.style.top = "-20px";
|
|
68
|
+
} else {
|
|
69
|
+
const cell = table.querySelector(dataCellQuery(0, 0));
|
|
70
|
+
if (cell) {
|
|
71
|
+
cell.setAttribute("tabindex", "0");
|
|
72
|
+
state.cellPos = [
|
|
73
|
+
1,
|
|
74
|
+
1
|
|
75
|
+
];
|
|
76
|
+
state.el = cell;
|
|
77
|
+
state.pos = {
|
|
78
|
+
top: 0
|
|
79
|
+
};
|
|
80
|
+
if (state.placeholderEl) state.placeholderEl.style.top = "0px";
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}, [
|
|
87
|
+
cellFocusStateRef,
|
|
88
|
+
disableFocus
|
|
89
|
+
]);
|
|
90
|
+
const focusCellPlaceholderKeyDown = useCallback((evt)=>{
|
|
91
|
+
const { outsideViewport, pos } = cellFocusStateRef.current;
|
|
92
|
+
if (pos && isArrowKey(evt.key)) if ("above" === outsideViewport) requestScroll?.({
|
|
93
|
+
type: "scroll-top",
|
|
94
|
+
scrollPos: pos.top
|
|
95
|
+
});
|
|
96
|
+
else if ("below" === outsideViewport) requestScroll?.({
|
|
97
|
+
type: "scroll-bottom",
|
|
98
|
+
scrollPos: pos.top
|
|
99
|
+
});
|
|
100
|
+
else throw Error("cellFocusPlaceholder should not have focus if inside viewport");
|
|
101
|
+
}, [
|
|
102
|
+
cellFocusStateRef,
|
|
103
|
+
requestScroll
|
|
104
|
+
]);
|
|
105
|
+
return {
|
|
106
|
+
focusCell,
|
|
107
|
+
focusCellPlaceholderKeyDown,
|
|
108
|
+
focusCellPlaceholderRef,
|
|
109
|
+
setTableBodyRef
|
|
110
|
+
};
|
|
111
|
+
};
|
|
112
|
+
export { useCellFocus };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { dispatchMouseEvent, useStateRef } from "@vuu-ui/vuu-utils";
|
|
2
|
+
import { useCallback, useRef } from "react";
|
|
3
|
+
const isRowSelectionKey = (key)=>"Enter" === key || " " === key;
|
|
4
|
+
const useControlledTableNavigation = (initialValue, rowCount)=>{
|
|
5
|
+
const tableRef = useRef(null);
|
|
6
|
+
const [highlightedIndexRef, setHighlightedIndex] = useStateRef(initialValue);
|
|
7
|
+
const handleKeyDown = useCallback((e)=>{
|
|
8
|
+
if ("ArrowDown" === e.key) setHighlightedIndex((index = -1)=>Math.min(rowCount - 1, index + 1));
|
|
9
|
+
else if ("ArrowUp" === e.key) setHighlightedIndex((index = -1)=>Math.max(0, index - 1));
|
|
10
|
+
else if (isRowSelectionKey(e.key)) {
|
|
11
|
+
const { current: rowIdx } = highlightedIndexRef;
|
|
12
|
+
if ("number" == typeof rowIdx) {
|
|
13
|
+
const rowEl = tableRef.current?.querySelector(`[aria-rowindex="${rowIdx + 1}"]`);
|
|
14
|
+
if (rowEl) dispatchMouseEvent(rowEl, "click");
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}, [
|
|
18
|
+
highlightedIndexRef,
|
|
19
|
+
rowCount,
|
|
20
|
+
setHighlightedIndex
|
|
21
|
+
]);
|
|
22
|
+
const handleHighlight = useCallback((idx)=>{
|
|
23
|
+
setHighlightedIndex(idx);
|
|
24
|
+
}, [
|
|
25
|
+
setHighlightedIndex
|
|
26
|
+
]);
|
|
27
|
+
return {
|
|
28
|
+
highlightedIndexRef,
|
|
29
|
+
onHighlight: handleHighlight,
|
|
30
|
+
onKeyDown: handleKeyDown,
|
|
31
|
+
tableRef
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
export { isRowSelectionKey, useControlledTableNavigation };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { queryClosest } from "@vuu-ui/vuu-utils";
|
|
2
|
+
import { useCallback } from "react";
|
|
3
|
+
const useEditableCell = ()=>useCallback((evt)=>{
|
|
4
|
+
if ("Enter" === evt.key) {
|
|
5
|
+
const el = evt.target;
|
|
6
|
+
const inputElement = el.querySelector("input");
|
|
7
|
+
if (inputElement !== document.activeElement) inputElement?.focus();
|
|
8
|
+
} else if ("Escape" === evt.key) {
|
|
9
|
+
const input = queryClosest(evt.target, "input");
|
|
10
|
+
const cell = queryClosest(input, ".vuuTableCell");
|
|
11
|
+
if (cell) {
|
|
12
|
+
cell.tabIndex = 0;
|
|
13
|
+
cell.focus();
|
|
14
|
+
evt.stopPropagation();
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}, []);
|
|
18
|
+
export { useEditableCell };
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { queryClosest } from "@vuu-ui/vuu-utils";
|
|
2
|
+
import { useControlled } from "@salt-ds/core";
|
|
3
|
+
import { useCallback, useEffect, useRef } from "react";
|
|
4
|
+
import { cellDropdownShowing, closestRowIndex, getAriaCellPos, getFocusedCell, getLevelUp, getNextCellPos, getNextEditableCellPos, getTreeNodeOperation } from "./table-dom-utils.mjs";
|
|
5
|
+
const rowNavigationKeys = new Set([
|
|
6
|
+
"Home",
|
|
7
|
+
"End",
|
|
8
|
+
"PageUp",
|
|
9
|
+
"PageDown",
|
|
10
|
+
"ArrowDown",
|
|
11
|
+
"ArrowUp"
|
|
12
|
+
]);
|
|
13
|
+
const CellLocator = ".vuuTableCell,.vuuTableHeaderCell,.vuuTableGroupHeaderCell";
|
|
14
|
+
const CellControlLocator = ".vuuColumnMenu,.vuuColumnHeaderPill";
|
|
15
|
+
const cellNavigationKeys = new Set(rowNavigationKeys);
|
|
16
|
+
cellNavigationKeys.add("ArrowLeft");
|
|
17
|
+
cellNavigationKeys.add("ArrowRight");
|
|
18
|
+
const isNavigationKey = (key, navigationStyle)=>{
|
|
19
|
+
switch(navigationStyle){
|
|
20
|
+
case "cell":
|
|
21
|
+
case "tree":
|
|
22
|
+
return cellNavigationKeys.has(key);
|
|
23
|
+
case "row":
|
|
24
|
+
return rowNavigationKeys.has(key);
|
|
25
|
+
default:
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
const focusControlWithinCell = (e, el)=>{
|
|
30
|
+
if (e.shiftKey && e.key.match(/Arrow(Left|Right)/)) {
|
|
31
|
+
if (el?.classList.contains("vuuTableHeaderCell")) {
|
|
32
|
+
const menuButton = el?.querySelector(".vuuColumnMenu");
|
|
33
|
+
if (menuButton) {
|
|
34
|
+
menuButton.focus();
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
} else if (el?.classList.contains("vuuTableGroupHeaderCell")) {
|
|
38
|
+
const headerPill = el?.querySelector(".vuuColumnHeaderPill");
|
|
39
|
+
if (headerPill) {
|
|
40
|
+
headerPill.focus();
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
} else if (el?.classList.contains("vuuColumnHeaderPill")) {
|
|
44
|
+
const nextPill = el.parentElement?.nextElementSibling?.firstChild;
|
|
45
|
+
if (nextPill?.classList.contains("vuuColumnHeaderPill")) {
|
|
46
|
+
nextPill.focus();
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
{
|
|
50
|
+
const removeButton = queryClosest(el, ".vuuTableGroupHeaderCell", true).querySelector(".vuuTableGroupHeaderCell-removeAll");
|
|
51
|
+
if (removeButton) {
|
|
52
|
+
removeButton.focus();
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return false;
|
|
59
|
+
};
|
|
60
|
+
const PageKeys = [
|
|
61
|
+
"Home",
|
|
62
|
+
"End",
|
|
63
|
+
"PageUp",
|
|
64
|
+
"PageDown"
|
|
65
|
+
];
|
|
66
|
+
const isPagingKey = (key)=>PageKeys.includes(key);
|
|
67
|
+
const useKeyboardNavigation = ({ cellFocusStateRef, columnCount = 0, containerRef, defaultHighlightedIndex, disableHighlightOnFocus, editSessionInProgress, focusCell, headerCount, highlightedIndex: highlightedIndexProp, navigationStyle, requestScroll, onHighlight, onToggleGroup, rowCount = 0, viewportRowCount })=>{
|
|
68
|
+
const highlightedIndexRef = useRef(void 0);
|
|
69
|
+
const [highlightedIndex, setHighlightedIdx] = useControlled({
|
|
70
|
+
controlled: highlightedIndexProp,
|
|
71
|
+
default: defaultHighlightedIndex,
|
|
72
|
+
name: "UseKeyboardNavigation"
|
|
73
|
+
});
|
|
74
|
+
highlightedIndexRef.current = highlightedIndex;
|
|
75
|
+
const maxRowIndex = rowCount + headerCount;
|
|
76
|
+
const setHighlightedIndex = useCallback((idx)=>{
|
|
77
|
+
onHighlight?.(idx);
|
|
78
|
+
setHighlightedIdx(idx);
|
|
79
|
+
highlightedIndexRef.current = idx;
|
|
80
|
+
}, [
|
|
81
|
+
onHighlight,
|
|
82
|
+
setHighlightedIdx
|
|
83
|
+
]);
|
|
84
|
+
const setActiveCell = useCallback((rowIdx, colIdx)=>{
|
|
85
|
+
const pos = [
|
|
86
|
+
rowIdx,
|
|
87
|
+
colIdx
|
|
88
|
+
];
|
|
89
|
+
if ("row" === navigationStyle) setHighlightedIdx(rowIdx);
|
|
90
|
+
else focusCell(pos);
|
|
91
|
+
}, [
|
|
92
|
+
focusCell,
|
|
93
|
+
navigationStyle,
|
|
94
|
+
setHighlightedIdx
|
|
95
|
+
]);
|
|
96
|
+
const nextPageItemIdx = useCallback((key, [rowIdx, colIdx])=>new Promise((resolve)=>{
|
|
97
|
+
let newRowIdx = rowIdx;
|
|
98
|
+
switch(key){
|
|
99
|
+
case "PageDown":
|
|
100
|
+
newRowIdx = Math.min(rowCount - 1, rowIdx + viewportRowCount);
|
|
101
|
+
if (newRowIdx !== rowIdx) requestScroll?.({
|
|
102
|
+
type: "scroll-page",
|
|
103
|
+
direction: "down"
|
|
104
|
+
});
|
|
105
|
+
break;
|
|
106
|
+
case "PageUp":
|
|
107
|
+
newRowIdx = Math.max(0, rowIdx - viewportRowCount);
|
|
108
|
+
if (newRowIdx !== rowIdx) requestScroll?.({
|
|
109
|
+
type: "scroll-page",
|
|
110
|
+
direction: "up"
|
|
111
|
+
});
|
|
112
|
+
break;
|
|
113
|
+
case "Home":
|
|
114
|
+
newRowIdx = headerCount + 1;
|
|
115
|
+
if (newRowIdx !== rowIdx) requestScroll?.({
|
|
116
|
+
type: "scroll-end",
|
|
117
|
+
direction: "home"
|
|
118
|
+
});
|
|
119
|
+
break;
|
|
120
|
+
case "End":
|
|
121
|
+
newRowIdx = rowCount + headerCount;
|
|
122
|
+
if (newRowIdx !== rowIdx) requestScroll?.({
|
|
123
|
+
type: "scroll-end",
|
|
124
|
+
direction: "end"
|
|
125
|
+
});
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
setTimeout(()=>{
|
|
129
|
+
resolve([
|
|
130
|
+
newRowIdx,
|
|
131
|
+
colIdx
|
|
132
|
+
]);
|
|
133
|
+
}, 35);
|
|
134
|
+
}), [
|
|
135
|
+
headerCount,
|
|
136
|
+
requestScroll,
|
|
137
|
+
rowCount,
|
|
138
|
+
viewportRowCount
|
|
139
|
+
]);
|
|
140
|
+
const handleFocus = useCallback(()=>{
|
|
141
|
+
if (true !== disableHighlightOnFocus) {
|
|
142
|
+
if (containerRef.current?.contains(document.activeElement)) {
|
|
143
|
+
const focusedCell = getFocusedCell(document.activeElement);
|
|
144
|
+
if (focusedCell) {
|
|
145
|
+
cellFocusStateRef.current.cell = focusedCell;
|
|
146
|
+
if ("row" === navigationStyle) setHighlightedIdx(cellFocusStateRef.current.cellPos?.[0]);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}, [
|
|
151
|
+
disableHighlightOnFocus,
|
|
152
|
+
containerRef,
|
|
153
|
+
cellFocusStateRef,
|
|
154
|
+
navigationStyle,
|
|
155
|
+
setHighlightedIdx
|
|
156
|
+
]);
|
|
157
|
+
const navigateChildItems = useCallback(async (navigationStyle = "cell", key, shiftKey = false)=>{
|
|
158
|
+
const { cellPos } = cellFocusStateRef.current;
|
|
159
|
+
if (void 0 === cellPos) throw Error("navigateChildItems called before cellPos is set");
|
|
160
|
+
const [rowIdx, colIdx] = cellPos;
|
|
161
|
+
let nextRowIdx = -1, nextColIdx = -1;
|
|
162
|
+
if (isPagingKey(key)) [nextRowIdx, nextColIdx] = await nextPageItemIdx(key, cellPos);
|
|
163
|
+
else {
|
|
164
|
+
const treeNodeOperation = getTreeNodeOperation(containerRef, navigationStyle, cellPos, key, shiftKey);
|
|
165
|
+
if ("expand" === treeNodeOperation || "collapse" === treeNodeOperation) onToggleGroup(treeNodeOperation, rowIdx - headerCount - 1);
|
|
166
|
+
else if ("level-up" === treeNodeOperation) [nextRowIdx, nextColIdx] = getLevelUp(containerRef, cellPos);
|
|
167
|
+
else if (editSessionInProgress) [nextRowIdx, nextColIdx] = getNextEditableCellPos(containerRef, key, cellPos, columnCount, maxRowIndex);
|
|
168
|
+
else [nextRowIdx, nextColIdx] = getNextCellPos(key, cellPos, columnCount, maxRowIndex);
|
|
169
|
+
}
|
|
170
|
+
if (nextRowIdx !== rowIdx || nextColIdx !== colIdx) {
|
|
171
|
+
setActiveCell(nextRowIdx, nextColIdx);
|
|
172
|
+
setHighlightedIndex(nextRowIdx);
|
|
173
|
+
}
|
|
174
|
+
}, [
|
|
175
|
+
cellFocusStateRef,
|
|
176
|
+
nextPageItemIdx,
|
|
177
|
+
containerRef,
|
|
178
|
+
editSessionInProgress,
|
|
179
|
+
onToggleGroup,
|
|
180
|
+
headerCount,
|
|
181
|
+
columnCount,
|
|
182
|
+
maxRowIndex,
|
|
183
|
+
setActiveCell,
|
|
184
|
+
setHighlightedIndex
|
|
185
|
+
]);
|
|
186
|
+
const scrollRowIntoViewIfNecessary = useCallback((rowIndex)=>{
|
|
187
|
+
requestScroll?.({
|
|
188
|
+
type: "scroll-row",
|
|
189
|
+
rowIndex
|
|
190
|
+
});
|
|
191
|
+
}, [
|
|
192
|
+
requestScroll
|
|
193
|
+
]);
|
|
194
|
+
const moveHighlightedRow = useCallback(async (key)=>{
|
|
195
|
+
const { current: highlighted } = highlightedIndexRef;
|
|
196
|
+
const [nextRowIdx] = isPagingKey(key) ? await nextPageItemIdx(key, [
|
|
197
|
+
highlighted ?? -1,
|
|
198
|
+
0
|
|
199
|
+
]) : getNextCellPos(key, [
|
|
200
|
+
highlighted ?? -1,
|
|
201
|
+
0
|
|
202
|
+
], columnCount, maxRowIndex);
|
|
203
|
+
if (nextRowIdx !== highlighted) {
|
|
204
|
+
setHighlightedIndex(nextRowIdx);
|
|
205
|
+
scrollRowIntoViewIfNecessary(nextRowIdx);
|
|
206
|
+
}
|
|
207
|
+
}, [
|
|
208
|
+
columnCount,
|
|
209
|
+
maxRowIndex,
|
|
210
|
+
nextPageItemIdx,
|
|
211
|
+
scrollRowIntoViewIfNecessary,
|
|
212
|
+
setHighlightedIndex
|
|
213
|
+
]);
|
|
214
|
+
useEffect(()=>{
|
|
215
|
+
if (void 0 !== highlightedIndexProp && -1 !== highlightedIndexProp) requestAnimationFrame(()=>{
|
|
216
|
+
scrollRowIntoViewIfNecessary(highlightedIndexProp);
|
|
217
|
+
});
|
|
218
|
+
}, [
|
|
219
|
+
highlightedIndexProp,
|
|
220
|
+
scrollRowIntoViewIfNecessary
|
|
221
|
+
]);
|
|
222
|
+
const handleKeyDown = useCallback((e)=>{
|
|
223
|
+
const cell = queryClosest(e.target, `${CellLocator},${CellControlLocator}`);
|
|
224
|
+
if (cellDropdownShowing(cell)) return;
|
|
225
|
+
if (rowCount > 0 && isNavigationKey(e.key, navigationStyle)) {
|
|
226
|
+
e.preventDefault();
|
|
227
|
+
e.stopPropagation();
|
|
228
|
+
if ("row" === navigationStyle) moveHighlightedRow(e.key);
|
|
229
|
+
else if ("none" !== navigationStyle) {
|
|
230
|
+
if (!focusControlWithinCell(e, cell)) navigateChildItems(navigationStyle, e.key, e.shiftKey);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}, [
|
|
234
|
+
rowCount,
|
|
235
|
+
navigationStyle,
|
|
236
|
+
moveHighlightedRow,
|
|
237
|
+
navigateChildItems
|
|
238
|
+
]);
|
|
239
|
+
const handleClick = useCallback((evt)=>{
|
|
240
|
+
const target = queryClosest(evt.target, CellLocator);
|
|
241
|
+
const focusedCell = getFocusedCell(target);
|
|
242
|
+
if (focusedCell) {
|
|
243
|
+
const [rowIdx, colIdx] = getAriaCellPos(focusedCell);
|
|
244
|
+
setActiveCell(rowIdx, colIdx);
|
|
245
|
+
}
|
|
246
|
+
}, [
|
|
247
|
+
setActiveCell
|
|
248
|
+
]);
|
|
249
|
+
const handleMouseLeave = useCallback(()=>{
|
|
250
|
+
setHighlightedIndex(-1);
|
|
251
|
+
}, [
|
|
252
|
+
setHighlightedIndex
|
|
253
|
+
]);
|
|
254
|
+
const handleMouseMove = useCallback((evt)=>{
|
|
255
|
+
const rowIdx = closestRowIndex(evt.target);
|
|
256
|
+
if (-1 !== rowIdx && rowIdx !== highlightedIndexRef.current) setHighlightedIndex(rowIdx);
|
|
257
|
+
}, [
|
|
258
|
+
setHighlightedIndex
|
|
259
|
+
]);
|
|
260
|
+
const navigateCell = useCallback(()=>{
|
|
261
|
+
navigateChildItems("cell", "ArrowDown");
|
|
262
|
+
}, [
|
|
263
|
+
navigateChildItems
|
|
264
|
+
]);
|
|
265
|
+
return {
|
|
266
|
+
highlightedIndexRef,
|
|
267
|
+
navigateCell,
|
|
268
|
+
onClick: handleClick,
|
|
269
|
+
onFocus: handleFocus,
|
|
270
|
+
onKeyDown: handleKeyDown,
|
|
271
|
+
onMouseLeave: "row" === navigationStyle ? handleMouseLeave : void 0,
|
|
272
|
+
onMouseMove: "row" === navigationStyle ? handleMouseMove : void 0
|
|
273
|
+
};
|
|
274
|
+
};
|
|
275
|
+
export { isNavigationKey, isPagingKey, useKeyboardNavigation };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { isValidNumber } from "@vuu-ui/vuu-utils";
|
|
2
|
+
import { useCallback, useMemo, useRef, useState } from "react";
|
|
3
|
+
const useMeasuredHeight = ({ onHeightMeasured, height: heightProp = 0 })=>{
|
|
4
|
+
const [measuredHeight, setMeasuredHeight] = useState(heightProp);
|
|
5
|
+
const lastMeasuredHeight = useRef(-1);
|
|
6
|
+
useMemo(()=>{
|
|
7
|
+
if (0 !== heightProp) setMeasuredHeight(heightProp);
|
|
8
|
+
}, [
|
|
9
|
+
heightProp
|
|
10
|
+
]);
|
|
11
|
+
const resizeObserver = useMemo(()=>new ResizeObserver((entries)=>{
|
|
12
|
+
for (const entry of entries){
|
|
13
|
+
const [{ blockSize: measuredSize }] = entry.borderBoxSize;
|
|
14
|
+
const newHeight = Math.round(measuredSize);
|
|
15
|
+
if (lastMeasuredHeight.current !== newHeight) {
|
|
16
|
+
if (isValidNumber(newHeight)) {
|
|
17
|
+
lastMeasuredHeight.current = newHeight;
|
|
18
|
+
setMeasuredHeight(newHeight);
|
|
19
|
+
onHeightMeasured?.(newHeight);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}), [
|
|
24
|
+
onHeightMeasured
|
|
25
|
+
]);
|
|
26
|
+
const measuredRef = useCallback((el)=>{
|
|
27
|
+
if (el) {
|
|
28
|
+
if (0 === heightProp) {
|
|
29
|
+
const { height } = el.getBoundingClientRect();
|
|
30
|
+
resizeObserver.observe(el);
|
|
31
|
+
const measuredHeight = Math.round(height);
|
|
32
|
+
setMeasuredHeight(measuredHeight);
|
|
33
|
+
}
|
|
34
|
+
} else resizeObserver.disconnect();
|
|
35
|
+
}, [
|
|
36
|
+
resizeObserver,
|
|
37
|
+
heightProp
|
|
38
|
+
]);
|
|
39
|
+
return {
|
|
40
|
+
measuredHeight,
|
|
41
|
+
measuredRef
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
export { useMeasuredHeight };
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef } from "react";
|
|
2
|
+
const WidthHeight = [
|
|
3
|
+
"height",
|
|
4
|
+
"width"
|
|
5
|
+
];
|
|
6
|
+
const WidthOnly = [
|
|
7
|
+
"width"
|
|
8
|
+
];
|
|
9
|
+
const observedMap = new Map();
|
|
10
|
+
const getTargetSize = (element, size, dimension)=>{
|
|
11
|
+
switch(dimension){
|
|
12
|
+
case "height":
|
|
13
|
+
return size.height;
|
|
14
|
+
case "clientHeight":
|
|
15
|
+
return element.clientHeight;
|
|
16
|
+
case "clientWidth":
|
|
17
|
+
return element.clientWidth;
|
|
18
|
+
case "contentHeight":
|
|
19
|
+
return size.contentHeight;
|
|
20
|
+
case "contentWidth":
|
|
21
|
+
return size.contentWidth;
|
|
22
|
+
case "scrollHeight":
|
|
23
|
+
return Math.ceil(element.scrollHeight);
|
|
24
|
+
case "scrollWidth":
|
|
25
|
+
return Math.ceil(element.scrollWidth);
|
|
26
|
+
case "width":
|
|
27
|
+
return size.width;
|
|
28
|
+
default:
|
|
29
|
+
return 0;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
const resizeObserver = new ResizeObserver((entries)=>{
|
|
33
|
+
for (const entry of entries){
|
|
34
|
+
const { target, borderBoxSize, contentBoxSize } = entry;
|
|
35
|
+
const observedTarget = observedMap.get(target);
|
|
36
|
+
if (observedTarget) {
|
|
37
|
+
const [{ blockSize: height, inlineSize: width }] = borderBoxSize;
|
|
38
|
+
const [{ blockSize: contentHeight, inlineSize: contentWidth }] = contentBoxSize;
|
|
39
|
+
const { onResize, measurements } = observedTarget;
|
|
40
|
+
let sizeChanged = false;
|
|
41
|
+
for (const [dimension, size] of Object.entries(measurements)){
|
|
42
|
+
const newSize = getTargetSize(target, {
|
|
43
|
+
height,
|
|
44
|
+
width,
|
|
45
|
+
contentHeight,
|
|
46
|
+
contentWidth
|
|
47
|
+
}, dimension);
|
|
48
|
+
if (newSize !== size) {
|
|
49
|
+
sizeChanged = true;
|
|
50
|
+
measurements[dimension] = newSize;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (sizeChanged) onResize && onResize(measurements);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
function useResizeObserver(ref, dimensions, onResize, reportInitialSize = false) {
|
|
58
|
+
const dimensionsRef = useRef(dimensions);
|
|
59
|
+
const measure = useCallback((target)=>{
|
|
60
|
+
const { width, height } = target.getBoundingClientRect();
|
|
61
|
+
const { clientWidth: contentWidth, clientHeight: contentHeight } = target;
|
|
62
|
+
return dimensionsRef.current.reduce((map, dim)=>{
|
|
63
|
+
map[dim] = getTargetSize(target, {
|
|
64
|
+
width,
|
|
65
|
+
height,
|
|
66
|
+
contentHeight,
|
|
67
|
+
contentWidth
|
|
68
|
+
}, dim);
|
|
69
|
+
return map;
|
|
70
|
+
}, {});
|
|
71
|
+
}, []);
|
|
72
|
+
useEffect(()=>{
|
|
73
|
+
const target = ref.current;
|
|
74
|
+
async function registerObserver() {
|
|
75
|
+
observedMap.set(target, {
|
|
76
|
+
measurements: {}
|
|
77
|
+
});
|
|
78
|
+
const observedTarget = observedMap.get(target);
|
|
79
|
+
if (observedTarget) {
|
|
80
|
+
const measurements = measure(target);
|
|
81
|
+
observedTarget.measurements = measurements;
|
|
82
|
+
resizeObserver.observe(target);
|
|
83
|
+
if (reportInitialSize) onResize(measurements);
|
|
84
|
+
} else console.log("%cuseResizeObserver an target expected to be under observation wa snot found. This warrants investigation", "font-weight:bold; color:red;");
|
|
85
|
+
}
|
|
86
|
+
if (target) {
|
|
87
|
+
if (observedMap.has(target)) throw Error("useResizeObserver attemping to observe same element twice");
|
|
88
|
+
registerObserver();
|
|
89
|
+
}
|
|
90
|
+
return ()=>{
|
|
91
|
+
if (target && observedMap.has(target)) {
|
|
92
|
+
resizeObserver.unobserve(target);
|
|
93
|
+
observedMap.delete(target);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
}, [
|
|
97
|
+
measure,
|
|
98
|
+
ref
|
|
99
|
+
]);
|
|
100
|
+
useEffect(()=>{
|
|
101
|
+
const target = ref.current;
|
|
102
|
+
const record = observedMap.get(target);
|
|
103
|
+
if (record) {
|
|
104
|
+
if (dimensionsRef.current !== dimensions) {
|
|
105
|
+
dimensionsRef.current = dimensions;
|
|
106
|
+
const measurements = measure(target);
|
|
107
|
+
record.measurements = measurements;
|
|
108
|
+
}
|
|
109
|
+
record.onResize = onResize;
|
|
110
|
+
}
|
|
111
|
+
}, [
|
|
112
|
+
dimensions,
|
|
113
|
+
measure,
|
|
114
|
+
ref,
|
|
115
|
+
onResize
|
|
116
|
+
]);
|
|
117
|
+
}
|
|
118
|
+
export { WidthHeight, WidthOnly, useResizeObserver };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { getRowClassNameGenerator } from "@vuu-ui/vuu-utils";
|
|
2
|
+
import { useMemo } from "react";
|
|
3
|
+
const createClassNameGenerator = (ids)=>{
|
|
4
|
+
const functions = [];
|
|
5
|
+
ids?.forEach((id)=>{
|
|
6
|
+
const fn = getRowClassNameGenerator(id);
|
|
7
|
+
if (fn) functions.push(fn.fn);
|
|
8
|
+
});
|
|
9
|
+
return (dataRow)=>{
|
|
10
|
+
const classNames = [];
|
|
11
|
+
functions?.forEach((fn)=>{
|
|
12
|
+
const className = fn(dataRow);
|
|
13
|
+
if (className) classNames.push(className);
|
|
14
|
+
});
|
|
15
|
+
return classNames.join(" ");
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
const useRowClassNameGenerators = ({ rowClassNameGenerators })=>useMemo(()=>createClassNameGenerator(rowClassNameGenerators), [
|
|
19
|
+
rowClassNameGenerators
|
|
20
|
+
]);
|
|
21
|
+
export { useRowClassNameGenerators };
|