@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,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,6 @@
1
+ import { useMemo, useRef } from "react";
2
+ const useInitialValue = (value)=>{
3
+ const ref = useRef(value);
4
+ return useMemo(()=>ref.current, []);
5
+ };
6
+ export { useInitialValue };
@@ -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 };