@alaarab/ogrid-react 2.1.2 → 2.1.4

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 (72) hide show
  1. package/dist/esm/index.js +7233 -26
  2. package/package.json +7 -4
  3. package/dist/esm/components/BaseColumnHeaderMenu.js +0 -78
  4. package/dist/esm/components/BaseDropIndicator.js +0 -4
  5. package/dist/esm/components/BaseEmptyState.js +0 -4
  6. package/dist/esm/components/BaseInlineCellEditor.js +0 -167
  7. package/dist/esm/components/BaseLoadingOverlay.js +0 -4
  8. package/dist/esm/components/CellErrorBoundary.js +0 -43
  9. package/dist/esm/components/ColumnChooserProps.js +0 -6
  10. package/dist/esm/components/ColumnHeaderFilterContent.js +0 -33
  11. package/dist/esm/components/ColumnHeaderFilterRenderers.js +0 -67
  12. package/dist/esm/components/EmptyState.js +0 -19
  13. package/dist/esm/components/GridContextMenu.js +0 -35
  14. package/dist/esm/components/MarchingAntsOverlay.js +0 -90
  15. package/dist/esm/components/OGridLayout.js +0 -136
  16. package/dist/esm/components/PaginationControlsProps.js +0 -6
  17. package/dist/esm/components/SideBar.js +0 -123
  18. package/dist/esm/components/StatusBar.js +0 -6
  19. package/dist/esm/components/createOGrid.js +0 -19
  20. package/dist/esm/constants/domHelpers.js +0 -16
  21. package/dist/esm/hooks/index.js +0 -43
  22. package/dist/esm/hooks/useActiveCell.js +0 -75
  23. package/dist/esm/hooks/useCellEditing.js +0 -15
  24. package/dist/esm/hooks/useCellSelection.js +0 -389
  25. package/dist/esm/hooks/useClipboard.js +0 -106
  26. package/dist/esm/hooks/useColumnChooserState.js +0 -74
  27. package/dist/esm/hooks/useColumnHeaderFilterState.js +0 -191
  28. package/dist/esm/hooks/useColumnHeaderMenuState.js +0 -106
  29. package/dist/esm/hooks/useColumnMeta.js +0 -61
  30. package/dist/esm/hooks/useColumnPinning.js +0 -67
  31. package/dist/esm/hooks/useColumnReorder.js +0 -143
  32. package/dist/esm/hooks/useColumnResize.js +0 -127
  33. package/dist/esm/hooks/useContextMenu.js +0 -21
  34. package/dist/esm/hooks/useDataGridContextMenu.js +0 -24
  35. package/dist/esm/hooks/useDataGridEditing.js +0 -56
  36. package/dist/esm/hooks/useDataGridInteraction.js +0 -109
  37. package/dist/esm/hooks/useDataGridLayout.js +0 -172
  38. package/dist/esm/hooks/useDataGridState.js +0 -169
  39. package/dist/esm/hooks/useDataGridTableOrchestration.js +0 -199
  40. package/dist/esm/hooks/useDateFilterState.js +0 -34
  41. package/dist/esm/hooks/useDebounce.js +0 -35
  42. package/dist/esm/hooks/useFillHandle.js +0 -200
  43. package/dist/esm/hooks/useFilterOptions.js +0 -55
  44. package/dist/esm/hooks/useInlineCellEditorState.js +0 -38
  45. package/dist/esm/hooks/useKeyboardNavigation.js +0 -261
  46. package/dist/esm/hooks/useLatestRef.js +0 -11
  47. package/dist/esm/hooks/useListVirtualizer.js +0 -29
  48. package/dist/esm/hooks/useMultiSelectFilterState.js +0 -59
  49. package/dist/esm/hooks/useOGrid.js +0 -355
  50. package/dist/esm/hooks/useOGridDataFetching.js +0 -74
  51. package/dist/esm/hooks/useOGridFilters.js +0 -59
  52. package/dist/esm/hooks/useOGridPagination.js +0 -24
  53. package/dist/esm/hooks/useOGridSorting.js +0 -24
  54. package/dist/esm/hooks/usePaginationControls.js +0 -16
  55. package/dist/esm/hooks/usePeopleFilterState.js +0 -73
  56. package/dist/esm/hooks/useRichSelectState.js +0 -60
  57. package/dist/esm/hooks/useRowSelection.js +0 -69
  58. package/dist/esm/hooks/useSelectState.js +0 -62
  59. package/dist/esm/hooks/useShallowEqualMemo.js +0 -14
  60. package/dist/esm/hooks/useSideBarState.js +0 -39
  61. package/dist/esm/hooks/useTableLayout.js +0 -69
  62. package/dist/esm/hooks/useTextFilterState.js +0 -25
  63. package/dist/esm/hooks/useUndoRedo.js +0 -84
  64. package/dist/esm/hooks/useVirtualScroll.js +0 -69
  65. package/dist/esm/storybook/index.js +0 -1
  66. package/dist/esm/storybook/mockData.js +0 -73
  67. package/dist/esm/types/columnTypes.js +0 -1
  68. package/dist/esm/types/dataGridTypes.js +0 -1
  69. package/dist/esm/types/index.js +0 -1
  70. package/dist/esm/utils/dataGridViewModel.js +0 -54
  71. package/dist/esm/utils/gridRowComparator.js +0 -2
  72. package/dist/esm/utils/index.js +0 -5
@@ -1,127 +0,0 @@
1
- import { useCallback, useEffect, useRef } from 'react';
2
- import { useLatestRef } from './useLatestRef';
3
- /**
4
- * Manages column resize drag interactions with RAF-throttled state updates.
5
- * @param params - Sizing overrides, setter, min/default widths, and resize callback.
6
- * @returns Resize start handler and column width getter.
7
- */
8
- export function useColumnResize({ columnSizingOverrides, setColumnSizingOverrides, minWidth = 80, defaultWidth = 120, onColumnResized, }) {
9
- const rafRef = useRef(0);
10
- const onColumnResizedRef = useRef(onColumnResized);
11
- onColumnResizedRef.current = onColumnResized;
12
- const columnSizingOverridesRef = useLatestRef(columnSizingOverrides);
13
- // Track active drag listeners so we can clean up on unmount
14
- const cleanupRef = useRef(null);
15
- useEffect(() => {
16
- return () => {
17
- if (cleanupRef.current) {
18
- cleanupRef.current();
19
- cleanupRef.current = null;
20
- }
21
- };
22
- }, []);
23
- const handleResizeStart = useCallback((e, col) => {
24
- e.preventDefault();
25
- e.stopPropagation();
26
- // Clean up any in-progress drag before starting a new one
27
- if (cleanupRef.current) {
28
- cleanupRef.current();
29
- cleanupRef.current = null;
30
- }
31
- const startX = e.clientX;
32
- const columnId = col.columnId;
33
- // Measure the actual rendered width from the DOM. With table-layout: auto,
34
- // the browser may have auto-sized the column wider than the config values.
35
- // The resize handle is a direct child of <th>, so parentElement is the header cell.
36
- // Use closest('th') instead of parentElement to handle frameworks (e.g. Fluent UI)
37
- // that wrap header cell children in an internal <button> element.
38
- const thEl = e.currentTarget.closest('th');
39
- const startWidth = thEl
40
- ? thEl.getBoundingClientRect().width
41
- : columnSizingOverridesRef.current[columnId]?.widthPx
42
- ?? col.idealWidth
43
- ?? col.defaultWidth
44
- ?? defaultWidth;
45
- let latestWidth = startWidth;
46
- // Lock all column widths to their current DOM widths on first resize.
47
- // With table-layout:auto, resizing one column causes the browser to compress others.
48
- // Snapshotting all widths prevents this — only the dragged column changes.
49
- const thead = thEl?.closest('thead');
50
- if (thead) {
51
- const allThs = thead.querySelectorAll('th[data-column-id]');
52
- if (allThs.length > 0) {
53
- setColumnSizingOverrides((prev) => {
54
- const next = { ...prev };
55
- allThs.forEach((th) => {
56
- const colId = th.dataset.columnId;
57
- if (colId && !next[colId]) {
58
- next[colId] = { widthPx: th.getBoundingClientRect().width };
59
- }
60
- });
61
- next[columnId] = { widthPx: startWidth };
62
- return next;
63
- });
64
- }
65
- }
66
- // Lock cursor and prevent text selection during drag
67
- const prevCursor = document.body.style.cursor;
68
- const prevUserSelect = document.body.style.userSelect;
69
- document.body.style.cursor = 'col-resize';
70
- document.body.style.userSelect = 'none';
71
- const flushWidth = () => {
72
- setColumnSizingOverrides((prev) => ({
73
- ...prev,
74
- [columnId]: { widthPx: latestWidth },
75
- }));
76
- };
77
- const onMove = (moveEvent) => {
78
- const deltaX = moveEvent.clientX - startX;
79
- latestWidth = Math.max(minWidth, startWidth + deltaX);
80
- if (!rafRef.current) {
81
- rafRef.current = requestAnimationFrame(() => {
82
- rafRef.current = 0;
83
- flushWidth();
84
- });
85
- }
86
- };
87
- const cleanup = () => {
88
- document.removeEventListener('mousemove', onMove);
89
- document.removeEventListener('mouseup', onUp);
90
- cleanupRef.current = null;
91
- // Restore cursor and user-select
92
- document.body.style.cursor = prevCursor;
93
- document.body.style.userSelect = prevUserSelect;
94
- // Cancel pending RAF and flush final width synchronously
95
- if (rafRef.current) {
96
- cancelAnimationFrame(rafRef.current);
97
- rafRef.current = 0;
98
- }
99
- };
100
- const onUp = () => {
101
- cleanup();
102
- flushWidth();
103
- // Remove any rogue :focus-visible outlines that appeared during the drag.
104
- // Re-focus the grid wrapper so keyboard navigation still works.
105
- const wrapper = thEl?.closest('[tabindex]');
106
- if (wrapper) {
107
- wrapper.focus({ preventScroll: true });
108
- }
109
- else if (document.activeElement instanceof HTMLElement) {
110
- document.activeElement.blur();
111
- }
112
- if (onColumnResizedRef.current) {
113
- onColumnResizedRef.current(columnId, latestWidth);
114
- }
115
- };
116
- document.addEventListener('mousemove', onMove);
117
- document.addEventListener('mouseup', onUp);
118
- cleanupRef.current = cleanup;
119
- }, [defaultWidth, minWidth, setColumnSizingOverrides, columnSizingOverridesRef]);
120
- const getColumnWidth = useCallback((col) => {
121
- return columnSizingOverrides[col.columnId]?.widthPx
122
- ?? col.idealWidth
123
- ?? col.defaultWidth
124
- ?? defaultWidth;
125
- }, [columnSizingOverrides, defaultWidth]);
126
- return { handleResizeStart, getColumnWidth };
127
- }
@@ -1,21 +0,0 @@
1
- import { useState, useCallback } from 'react';
2
- /**
3
- * Manages context menu position state for right-click menus.
4
- * @returns Menu position, setter, right-click handler, and close handler.
5
- */
6
- export function useContextMenu() {
7
- const [contextMenuPosition, setContextMenuPosition] = useState(null);
8
- const handleCellContextMenu = useCallback((e) => {
9
- e.preventDefault?.();
10
- setContextMenuPosition({ x: e.clientX, y: e.clientY });
11
- }, []);
12
- const closeContextMenu = useCallback(() => {
13
- setContextMenuPosition(null);
14
- }, []);
15
- return {
16
- contextMenuPosition,
17
- setContextMenuPosition,
18
- handleCellContextMenu,
19
- closeContextMenu,
20
- };
21
- }
@@ -1,24 +0,0 @@
1
- import { useMemo } from 'react';
2
- import { useContextMenu } from './useContextMenu';
3
- // Stable no-op handlers used when cellSelection is disabled
4
- const NOOP = () => { };
5
- const NOOP_CTX = (_e) => { };
6
- /**
7
- * Manages context menu position and handlers.
8
- * Extracted from useDataGridState for modularity.
9
- */
10
- export function useDataGridContextMenu(params) {
11
- const { cellSelection } = params;
12
- const { contextMenuPosition, setContextMenuPosition, handleCellContextMenu, closeContextMenu } = useContextMenu();
13
- const contextMenuState = useMemo(() => ({
14
- menuPosition: cellSelection ? contextMenuPosition : null,
15
- setMenuPosition: cellSelection ? setContextMenuPosition : NOOP,
16
- handleCellContextMenu: cellSelection ? handleCellContextMenu : NOOP_CTX,
17
- closeContextMenu: cellSelection ? closeContextMenu : NOOP,
18
- }), [cellSelection, contextMenuPosition, setContextMenuPosition, handleCellContextMenu, closeContextMenu]);
19
- return {
20
- contextMenu: contextMenuState,
21
- contextMenuPosition,
22
- setContextMenuPosition,
23
- };
24
- }
@@ -1,56 +0,0 @@
1
- import { useMemo, useCallback, useState } from 'react';
2
- import { parseValue } from '../utils';
3
- import { useLatestRef } from './useLatestRef';
4
- /**
5
- * Manages cell editing commit/cancel logic and popover editor state.
6
- * Extracted from useDataGridState for modularity.
7
- *
8
- * The editingCell/setEditingCell/pendingEditorValue/setPendingEditorValue are
9
- * passed in from useCellEditing() (called at the orchestrator level) to avoid
10
- * circular dependencies with useDataGridInteraction.
11
- */
12
- export function useDataGridEditing(params) {
13
- const { editingCell, setEditingCell, pendingEditorValue, setPendingEditorValue, onCellValueChanged, setActiveCell, } = params;
14
- const [popoverAnchorEl, setPopoverAnchorEl] = useState(null);
15
- const visibleColsRef = useLatestRef(params.visibleCols);
16
- const itemsLengthRef = useLatestRef(params.itemsLength);
17
- const commitCellEdit = useCallback((item, columnId, oldValue, newValue, rowIndex, globalColIndex) => {
18
- // Validate via valueParser before committing
19
- const col = visibleColsRef.current.find((c) => c.columnId === columnId);
20
- if (col) {
21
- const result = parseValue(newValue, oldValue, item, col);
22
- if (!result.valid) {
23
- // Reject -- cancel the edit
24
- setEditingCell(null);
25
- setPopoverAnchorEl(null);
26
- setPendingEditorValue(undefined);
27
- return;
28
- }
29
- newValue = result.value;
30
- }
31
- onCellValueChanged?.({
32
- item,
33
- columnId,
34
- oldValue,
35
- newValue,
36
- rowIndex,
37
- });
38
- setEditingCell(null);
39
- setPopoverAnchorEl(null);
40
- setPendingEditorValue(undefined);
41
- // Advance to next row for inline editors
42
- if (rowIndex < itemsLengthRef.current - 1) {
43
- setActiveCell({ rowIndex: rowIndex + 1, columnIndex: globalColIndex });
44
- }
45
- }, [onCellValueChanged, setEditingCell, setPendingEditorValue, setActiveCell, visibleColsRef, itemsLengthRef]);
46
- const cancelPopoverEdit = useCallback(() => {
47
- setEditingCell(null);
48
- setPopoverAnchorEl(null);
49
- setPendingEditorValue(undefined);
50
- }, [setEditingCell, setPendingEditorValue]);
51
- const editingState = useMemo(() => ({
52
- editingCell, setEditingCell, pendingEditorValue, setPendingEditorValue,
53
- commitCellEdit, cancelPopoverEdit, popoverAnchorEl, setPopoverAnchorEl,
54
- }), [editingCell, setEditingCell, pendingEditorValue, setPendingEditorValue, commitCellEdit, cancelPopoverEdit, popoverAnchorEl, setPopoverAnchorEl]);
55
- return { editing: editingState };
56
- }
@@ -1,109 +0,0 @@
1
- import { useMemo, useCallback } from 'react';
2
- import { useCellSelection } from './useCellSelection';
3
- import { useClipboard } from './useClipboard';
4
- import { useKeyboardNavigation } from './useKeyboardNavigation';
5
- import { useFillHandle } from './useFillHandle';
6
- import { useUndoRedo } from './useUndoRedo';
7
- // Stable no-op handlers used when cellSelection is disabled (module-scope = no re-renders)
8
- const NOOP = () => { };
9
- const NOOP_ASYNC = async () => { };
10
- const NOOP_MOUSE = (_e, _r, _c) => { };
11
- const NOOP_KEY = (_e) => { };
12
- /**
13
- * Manages cell selection, keyboard navigation, clipboard, fill handle, and undo/redo.
14
- * Extracted from useDataGridState for modularity.
15
- *
16
- * activeCell/setActiveCell and editingCell/setEditingCell are passed in from the
17
- * orchestrator level to avoid circular dependencies with useDataGridEditing.
18
- */
19
- export function useDataGridInteraction(params) {
20
- const { items, visibleCols, colOffset, hasCheckboxCol, visibleColumnCount, getRowId, editable, onCellValueChangedProp, cellSelection, rowSelection, selectedRowIds, editingCell, setEditingCell, activeCell, setActiveCell, handleRowCheckboxChange, setContextMenuPosition, wrapperRef, } = params;
21
- // Wrap onCellValueChanged with undo/redo tracking
22
- const undoRedo = useUndoRedo({ onCellValueChanged: onCellValueChangedProp });
23
- const onCellValueChanged = undoRedo.onCellValueChanged;
24
- const { selectionRange, setSelectionRange, handleCellMouseDown: handleCellMouseDownBase, handleSelectAllCells, isDragging, } = useCellSelection({
25
- colOffset,
26
- rowCount: items.length,
27
- visibleColCount: visibleCols.length,
28
- setActiveCell,
29
- wrapperRef,
30
- });
31
- const { handleCopy, handleCut, handlePaste, cutRange, copyRange, clearClipboardRanges } = useClipboard({
32
- items,
33
- visibleCols,
34
- colOffset,
35
- selectionRange,
36
- activeCell,
37
- editable,
38
- onCellValueChanged,
39
- beginBatch: undoRedo.beginBatch,
40
- endBatch: undoRedo.endBatch,
41
- });
42
- const handleCellMouseDown = useCallback((e, rowIndex, globalColIndex) => {
43
- if (e.button !== 0)
44
- return;
45
- wrapperRef.current?.focus({ preventScroll: true });
46
- clearClipboardRanges();
47
- handleCellMouseDownBase(e, rowIndex, globalColIndex);
48
- }, [handleCellMouseDownBase, clearClipboardRanges, wrapperRef]);
49
- const { handleGridKeyDown } = useKeyboardNavigation({
50
- data: { items, visibleCols, colOffset, hasCheckboxCol, visibleColumnCount, getRowId },
51
- state: { activeCell, selectionRange, editingCell, selectedRowIds },
52
- handlers: { setActiveCell, setSelectionRange, setEditingCell, handleRowCheckboxChange, handleCopy, handleCut, handlePaste, setContextMenu: setContextMenuPosition, onUndo: undoRedo.undo, onRedo: undoRedo.redo, clearClipboardRanges },
53
- features: { editable, onCellValueChanged, rowSelection: rowSelection ?? 'none', wrapperRef },
54
- });
55
- const { handleFillHandleMouseDown } = useFillHandle({
56
- items,
57
- visibleCols,
58
- editable,
59
- onCellValueChanged,
60
- selectionRange,
61
- setSelectionRange,
62
- setActiveCell,
63
- colOffset,
64
- wrapperRef,
65
- beginBatch: undoRedo.beginBatch,
66
- endBatch: undoRedo.endBatch,
67
- });
68
- const hasCellSelection = selectionRange != null || activeCell != null;
69
- const interactionState = useMemo(() => ({
70
- activeCell: cellSelection ? activeCell : null,
71
- setActiveCell: cellSelection ? setActiveCell : NOOP,
72
- selectionRange: cellSelection ? selectionRange : null,
73
- setSelectionRange: cellSelection ? setSelectionRange : NOOP,
74
- handleCellMouseDown: cellSelection ? handleCellMouseDown : NOOP_MOUSE,
75
- handleSelectAllCells: cellSelection ? handleSelectAllCells : NOOP,
76
- hasCellSelection: cellSelection ? hasCellSelection : false,
77
- handleGridKeyDown: cellSelection ? handleGridKeyDown : NOOP_KEY,
78
- handleFillHandleMouseDown: cellSelection ? handleFillHandleMouseDown : NOOP,
79
- handleCopy: cellSelection ? handleCopy : NOOP,
80
- handleCut: cellSelection ? handleCut : NOOP,
81
- handlePaste: cellSelection ? handlePaste : NOOP_ASYNC,
82
- cutRange: cellSelection ? cutRange : null,
83
- copyRange: cellSelection ? copyRange : null,
84
- clearClipboardRanges: cellSelection ? clearClipboardRanges : NOOP,
85
- canUndo: undoRedo.canUndo,
86
- canRedo: undoRedo.canRedo,
87
- onUndo: undoRedo.undo,
88
- onRedo: undoRedo.redo,
89
- isDragging: cellSelection ? isDragging : false,
90
- }), [
91
- cellSelection, activeCell, setActiveCell, selectionRange, setSelectionRange,
92
- handleCellMouseDown, handleSelectAllCells, hasCellSelection, handleGridKeyDown,
93
- handleFillHandleMouseDown, handleCopy, handleCut, handlePaste, cutRange, copyRange,
94
- clearClipboardRanges, undoRedo.canUndo, undoRedo.canRedo, undoRedo.undo, undoRedo.redo,
95
- isDragging,
96
- ]);
97
- return {
98
- interaction: interactionState,
99
- selectionRange,
100
- setSelectionRange,
101
- cutRange,
102
- copyRange,
103
- clearClipboardRanges,
104
- isDragging,
105
- onCellValueChanged,
106
- canUndo: undoRedo.canUndo,
107
- canRedo: undoRedo.canRedo,
108
- };
109
- }
@@ -1,172 +0,0 @@
1
- import { useMemo, useState, useLayoutEffect, useCallback } from 'react';
2
- import { flattenColumns } from '../utils';
3
- import { CHECKBOX_COLUMN_WIDTH, DEFAULT_MIN_COLUMN_WIDTH } from '@alaarab/ogrid-core';
4
- import { useTableLayout } from './useTableLayout';
5
- import { useColumnPinning } from './useColumnPinning';
6
- import { useColumnHeaderMenuState } from './useColumnHeaderMenuState';
7
- import { useLatestRef } from './useLatestRef';
8
- /**
9
- * Manages column layout, visibility, sizing, pinning, and header menu state.
10
- * Extracted from useDataGridState for modularity.
11
- */
12
- export function useDataGridLayout(params) {
13
- const { columns, items, getRowId, visibleColumns, columnOrder, rowSelection = 'none', showRowNumbers, initialColumnWidths, onColumnResized, onAutosizeColumn, pinnedColumns, onColumnPinned, sortBy, sortDirection, onColumnSort, wrapperRef, } = params;
14
- // Cast is safe: input columns are React.IColumnDef instances; flattenColumns only extracts leaves.
15
- const flatColumnsRaw = useMemo(() => flattenColumns(columns), [columns]);
16
- // Apply runtime pin overrides (from applyColumnState or programmatic changes)
17
- const flatColumns = useMemo(() => {
18
- if (!pinnedColumns || Object.keys(pinnedColumns).length === 0)
19
- return flatColumnsRaw;
20
- return flatColumnsRaw.map((col) => {
21
- const override = pinnedColumns[col.columnId];
22
- if (override && col.pinned !== override) {
23
- return { ...col, pinned: override };
24
- }
25
- return col;
26
- });
27
- }, [flatColumnsRaw, pinnedColumns]);
28
- const visibleCols = useMemo(() => {
29
- const filtered = visibleColumns
30
- ? flatColumns.filter((c) => visibleColumns.has(c.columnId))
31
- : flatColumns;
32
- if (!columnOrder?.length)
33
- return filtered;
34
- const orderMap = new Map();
35
- for (let i = 0; i < columnOrder.length; i++) {
36
- orderMap.set(columnOrder[i], i);
37
- }
38
- return [...filtered].sort((a, b) => {
39
- const ia = orderMap.get(a.columnId) ?? -1;
40
- const ib = orderMap.get(b.columnId) ?? -1;
41
- if (ia === -1 && ib === -1)
42
- return 0;
43
- if (ia === -1)
44
- return 1;
45
- if (ib === -1)
46
- return -1;
47
- return ia - ib;
48
- });
49
- }, [flatColumns, visibleColumns, columnOrder]);
50
- const visibleColumnCount = visibleCols.length;
51
- const hasCheckboxCol = rowSelection === 'multiple';
52
- const hasRowNumbersCol = !!showRowNumbers;
53
- const specialColsCount = (hasCheckboxCol ? 1 : 0) + (hasRowNumbersCol ? 1 : 0);
54
- const totalColCount = visibleColumnCount + specialColsCount;
55
- const colOffset = specialColsCount;
56
- const rowIndexByRowId = useMemo(() => {
57
- const m = new Map();
58
- items.forEach((item, idx) => m.set(getRowId(item), idx));
59
- return m;
60
- }, [items, getRowId]);
61
- const { containerWidth, minTableWidth, desiredTableWidth, columnSizingOverrides, setColumnSizingOverrides, } = useTableLayout({
62
- wrapperRef,
63
- visibleCols,
64
- flatColumns,
65
- hasCheckboxCol,
66
- initialColumnWidths,
67
- onColumnResized,
68
- });
69
- const pinningResult = useColumnPinning({
70
- columns: flatColumns,
71
- pinnedColumns,
72
- onColumnPinned,
73
- });
74
- // Measure actual column widths from the DOM for accurate pinning offsets.
75
- const [measuredColumnWidths, setMeasuredColumnWidths] = useState({});
76
- useLayoutEffect(() => {
77
- const wrapper = wrapperRef.current;
78
- if (!wrapper)
79
- return;
80
- const headerCells = wrapper.querySelectorAll('th[data-column-id]');
81
- if (headerCells.length === 0)
82
- return;
83
- const measured = {};
84
- headerCells.forEach((cell) => {
85
- const colId = cell.getAttribute('data-column-id');
86
- if (colId)
87
- measured[colId] = cell.offsetWidth;
88
- });
89
- setMeasuredColumnWidths((prev) => {
90
- for (const key in measured) {
91
- if (prev[key] !== measured[key])
92
- return measured;
93
- }
94
- if (Object.keys(prev).length !== Object.keys(measured).length)
95
- return measured;
96
- return prev;
97
- });
98
- }, [visibleCols, containerWidth, columnSizingOverrides, wrapperRef]);
99
- // Build column width map for pinning offset computation
100
- const columnWidthMap = useMemo(() => {
101
- const map = {};
102
- for (const col of visibleCols) {
103
- const override = columnSizingOverrides[col.columnId];
104
- map[col.columnId] = override
105
- ? override.widthPx
106
- : (measuredColumnWidths[col.columnId] ?? col.idealWidth ?? col.defaultWidth ?? col.minWidth ?? DEFAULT_MIN_COLUMN_WIDTH);
107
- }
108
- return map;
109
- }, [visibleCols, columnSizingOverrides, measuredColumnWidths]);
110
- const leftOffsets = useMemo(() => pinningResult.computeLeftOffsets(visibleCols, columnWidthMap, DEFAULT_MIN_COLUMN_WIDTH, hasCheckboxCol, CHECKBOX_COLUMN_WIDTH), [pinningResult, visibleCols, columnWidthMap, hasCheckboxCol]);
111
- const rightOffsets = useMemo(() => pinningResult.computeRightOffsets(visibleCols, columnWidthMap, DEFAULT_MIN_COLUMN_WIDTH), [pinningResult, visibleCols, columnWidthMap]);
112
- // Stabilize onColumnSort via ref
113
- const onColumnSortRef = useLatestRef(onColumnSort);
114
- const stableOnColumnSort = useCallback((columnKey, direction) => onColumnSortRef.current?.(columnKey, direction), [onColumnSortRef]);
115
- // Autosize callback
116
- const handleAutosizeColumn = useCallback((columnId, width) => {
117
- setColumnSizingOverrides((prev) => ({ ...prev, [columnId]: { widthPx: width } }));
118
- (onAutosizeColumn ?? onColumnResized)?.(columnId, width);
119
- }, [setColumnSizingOverrides, onAutosizeColumn, onColumnResized]);
120
- const headerMenuResult = useColumnHeaderMenuState({
121
- pinnedColumns: pinningResult.pinnedColumns,
122
- onPinColumn: pinningResult.pinColumn,
123
- onUnpinColumn: pinningResult.unpinColumn,
124
- sortBy,
125
- sortDirection: sortDirection ?? 'asc',
126
- onColumnSort: stableOnColumnSort,
127
- onColumnResized,
128
- onAutosizeColumn: handleAutosizeColumn,
129
- columns: flatColumns,
130
- });
131
- // Memoize layout sub-object
132
- const layoutState = useMemo(() => ({
133
- flatColumns, visibleCols, visibleColumnCount, totalColCount, colOffset,
134
- hasCheckboxCol, hasRowNumbersCol, rowIndexByRowId, containerWidth, minTableWidth,
135
- desiredTableWidth, columnSizingOverrides, setColumnSizingOverrides, onColumnResized,
136
- measuredColumnWidths,
137
- }), [
138
- flatColumns, visibleCols, visibleColumnCount, totalColCount, colOffset,
139
- hasCheckboxCol, hasRowNumbersCol, rowIndexByRowId, containerWidth, minTableWidth,
140
- desiredTableWidth, columnSizingOverrides, setColumnSizingOverrides, onColumnResized,
141
- measuredColumnWidths,
142
- ]);
143
- // Memoize pinning sub-object
144
- const pinningState = useMemo(() => ({
145
- pinnedColumns: pinningResult.pinnedColumns,
146
- pinColumn: pinningResult.pinColumn,
147
- unpinColumn: pinningResult.unpinColumn,
148
- isPinned: pinningResult.isPinned,
149
- leftOffsets,
150
- rightOffsets,
151
- headerMenu: headerMenuResult,
152
- }), [
153
- pinningResult.pinnedColumns, pinningResult.pinColumn, pinningResult.unpinColumn,
154
- pinningResult.isPinned, leftOffsets, rightOffsets,
155
- headerMenuResult,
156
- ]);
157
- return {
158
- layout: layoutState,
159
- pinning: pinningState,
160
- flatColumns,
161
- visibleCols,
162
- visibleColumnCount,
163
- totalColCount,
164
- colOffset,
165
- hasCheckboxCol,
166
- hasRowNumbersCol,
167
- columnSizingOverrides,
168
- setColumnSizingOverrides,
169
- handleAutosizeColumn,
170
- stableOnColumnSort,
171
- };
172
- }
@@ -1,169 +0,0 @@
1
- import { useMemo, useCallback } from 'react';
2
- import { getDataGridStatusBarConfig, computeAggregations } from '../utils';
3
- import { useRowSelection } from './useRowSelection';
4
- import { useCellEditing } from './useCellEditing';
5
- import { useActiveCell } from './useActiveCell';
6
- import { useLatestRef } from './useLatestRef';
7
- import { useDataGridLayout } from './useDataGridLayout';
8
- import { useDataGridEditing } from './useDataGridEditing';
9
- import { useDataGridInteraction } from './useDataGridInteraction';
10
- import { useDataGridContextMenu } from './useDataGridContextMenu';
11
- /**
12
- * Single orchestration hook for DataGridTable. Takes grid props and wrapper ref,
13
- * returns all derived state and handlers so Fluent/Material/Radix can be thin view layers.
14
- *
15
- * Internally delegates to focused sub-hooks:
16
- * - useDataGridLayout -- column layout, sizing, pinning, header menu
17
- * - useDataGridEditing -- cell editing commit/cancel, popover editor
18
- * - useDataGridInteraction -- cell selection, keyboard nav, clipboard, fill handle, undo/redo
19
- * - useDataGridContextMenu -- context menu state
20
- */
21
- export function useDataGridState(params) {
22
- const { props, wrapperRef } = params;
23
- const { items, columns, getRowId, visibleColumns, columnOrder, rowSelection = 'none', selectedRows: controlledSelectedRows, onSelectionChange, showRowNumbers, statusBar, emptyState, editable, cellSelection: cellSelectionProp, onCellValueChanged: onCellValueChangedProp, initialColumnWidths, onColumnResized, onAutosizeColumn, pinnedColumns, onColumnPinned, onCellError, } = props;
24
- const cellSelection = cellSelectionProp !== false;
25
- // --- Shared state hooks (called at orchestrator level to break circular deps) ---
26
- const { editingCell, setEditingCell, pendingEditorValue, setPendingEditorValue, } = useCellEditing();
27
- const { activeCell, setActiveCell } = useActiveCell(wrapperRef, editingCell);
28
- // --- 1. Layout, pinning, header menu ---
29
- const layoutResult = useDataGridLayout({
30
- columns,
31
- items,
32
- getRowId,
33
- visibleColumns,
34
- columnOrder,
35
- rowSelection,
36
- showRowNumbers,
37
- initialColumnWidths,
38
- onColumnResized,
39
- onAutosizeColumn,
40
- pinnedColumns,
41
- onColumnPinned,
42
- sortBy: props.sortBy,
43
- sortDirection: props.sortDirection,
44
- onColumnSort: props.onColumnSort,
45
- wrapperRef,
46
- });
47
- const { visibleCols, visibleColumnCount, colOffset, hasCheckboxCol, } = layoutResult;
48
- // --- 2. Row selection ---
49
- const rowSelectionResult = useRowSelection({
50
- items,
51
- getRowId,
52
- rowSelection,
53
- controlledSelectedRows,
54
- onSelectionChange,
55
- });
56
- const { selectedRowIds, updateSelection, handleRowCheckboxChange, handleSelectAll, allSelected, someSelected, } = rowSelectionResult;
57
- // --- 3. Context menu ---
58
- const contextMenuResult = useDataGridContextMenu({ cellSelection });
59
- const { setContextMenuPosition } = contextMenuResult;
60
- // --- 4. Interaction (selection, keyboard, clipboard, fill handle, undo/redo) ---
61
- const interactionResult = useDataGridInteraction({
62
- items,
63
- visibleCols,
64
- colOffset,
65
- hasCheckboxCol,
66
- visibleColumnCount,
67
- getRowId,
68
- editable,
69
- onCellValueChangedProp,
70
- cellSelection,
71
- rowSelection,
72
- selectedRowIds,
73
- editingCell,
74
- setEditingCell,
75
- activeCell,
76
- setActiveCell,
77
- handleRowCheckboxChange,
78
- setContextMenuPosition,
79
- wrapperRef,
80
- });
81
- const { selectionRange, cutRange, copyRange, isDragging, onCellValueChanged, } = interactionResult;
82
- // --- 5. Editing (commit/cancel logic) ---
83
- const editingResult = useDataGridEditing({
84
- editingCell,
85
- setEditingCell,
86
- pendingEditorValue,
87
- setPendingEditorValue,
88
- visibleCols,
89
- itemsLength: items.length,
90
- onCellValueChanged,
91
- setActiveCell,
92
- });
93
- // --- 6. View models ---
94
- const { sortBy, sortDirection, filters, onFilterChange, filterOptions, loadingFilterOptions, peopleSearch, } = props;
95
- const onFilterChangeRef = useLatestRef(onFilterChange);
96
- const peopleSearchRef = useLatestRef(peopleSearch);
97
- const stableOnFilterChange = useCallback((...args) => onFilterChangeRef.current?.(...args), [onFilterChangeRef]);
98
- const stablePeopleSearch = useCallback((...args) => peopleSearchRef.current?.(...args) ?? Promise.resolve([]), [peopleSearchRef]);
99
- const headerFilterInput = useMemo(() => ({
100
- sortBy,
101
- sortDirection,
102
- onColumnSort: layoutResult.stableOnColumnSort,
103
- filters,
104
- onFilterChange: stableOnFilterChange,
105
- filterOptions,
106
- loadingFilterOptions,
107
- peopleSearch: peopleSearch ? stablePeopleSearch : undefined,
108
- }), [
109
- sortBy,
110
- sortDirection,
111
- layoutResult.stableOnColumnSort,
112
- filters,
113
- stableOnFilterChange,
114
- filterOptions,
115
- loadingFilterOptions,
116
- peopleSearch, stablePeopleSearch,
117
- ]);
118
- const cellDescriptorInput = useMemo(() => ({
119
- editingCell,
120
- activeCell: cellSelection ? activeCell : null,
121
- selectionRange: cellSelection ? selectionRange : null,
122
- cutRange: cellSelection ? cutRange : null,
123
- copyRange: cellSelection ? copyRange : null,
124
- colOffset,
125
- itemsLength: items.length,
126
- getRowId,
127
- editable,
128
- onCellValueChanged,
129
- isDragging: cellSelection ? isDragging : false,
130
- }), [
131
- editingCell,
132
- activeCell,
133
- selectionRange,
134
- cutRange,
135
- copyRange,
136
- colOffset,
137
- items.length,
138
- getRowId,
139
- editable,
140
- onCellValueChanged,
141
- cellSelection,
142
- isDragging,
143
- ]);
144
- const aggregation = useMemo(() => computeAggregations(items, visibleCols, cellSelection ? selectionRange : null), [items, visibleCols, selectionRange, cellSelection]);
145
- const statusBarConfig = useMemo(() => {
146
- const base = getDataGridStatusBarConfig(statusBar, items.length, selectedRowIds.size);
147
- if (!base)
148
- return null;
149
- return { ...base, aggregation: aggregation ?? undefined };
150
- }, [statusBar, items.length, selectedRowIds.size, aggregation]);
151
- const showEmptyInGrid = items.length === 0 && !!emptyState && !props.isLoading;
152
- // --- Memoize remaining sub-objects ---
153
- const rowSelectionState = useMemo(() => ({
154
- selectedRowIds, updateSelection, handleRowCheckboxChange,
155
- handleSelectAll, allSelected, someSelected,
156
- }), [selectedRowIds, updateSelection, handleRowCheckboxChange, handleSelectAll, allSelected, someSelected]);
157
- const viewModelsState = useMemo(() => ({
158
- headerFilterInput, cellDescriptorInput, statusBarConfig, showEmptyInGrid, onCellError,
159
- }), [headerFilterInput, cellDescriptorInput, statusBarConfig, showEmptyInGrid, onCellError]);
160
- return {
161
- layout: layoutResult.layout,
162
- rowSelection: rowSelectionState,
163
- editing: editingResult.editing,
164
- interaction: interactionResult.interaction,
165
- contextMenu: contextMenuResult.contextMenu,
166
- viewModels: viewModelsState,
167
- pinning: layoutResult.pinning,
168
- };
169
- }