@alaarab/ogrid-react 2.1.3 → 2.1.5

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 -371
  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,199 +0,0 @@
1
- import { useCallback, useRef, useMemo } from 'react';
2
- import { useDataGridState } from './useDataGridState';
3
- import { useColumnResize } from './useColumnResize';
4
- import { useColumnReorder } from './useColumnReorder';
5
- import { useVirtualScroll } from './useVirtualScroll';
6
- import { useLatestRef } from './useLatestRef';
7
- import { buildHeaderRows } from '../utils';
8
- // ---------------------------------------------------------------------------
9
- // Hook
10
- // ---------------------------------------------------------------------------
11
- /**
12
- * Shared orchestration hook for DataGridTable.
13
- *
14
- * Encapsulates all state management and computation that is identical across
15
- * Radix, Fluent, and Material DataGridTable implementations. Each UI package
16
- * calls this hook, then renders its own framework-specific JSX using the
17
- * returned values.
18
- */
19
- export function useDataGridTableOrchestration(params) {
20
- const { props } = params;
21
- // ── Refs ────────────────────────────────────────────────────────────────
22
- const wrapperRef = useRef(null);
23
- const tableContainerRef = useRef(null);
24
- const lastMouseShiftRef = useRef(false);
25
- // ── Core state ──────────────────────────────────────────────────────────
26
- const state = useDataGridState({ props, wrapperRef });
27
- const { layout, rowSelection: rowSel, editing, interaction, contextMenu: ctxMenu, viewModels, pinning } = state;
28
- const { visibleCols: visibleColsTyped, totalColCount, hasCheckboxCol, hasRowNumbersCol, colOffset, containerWidth, minTableWidth, desiredTableWidth, columnSizingOverrides, setColumnSizingOverrides, measuredColumnWidths, } = layout;
29
- const visibleCols = visibleColsTyped;
30
- const { selectedRowIds, updateSelection, handleRowCheckboxChange, handleSelectAll, allSelected, someSelected } = rowSel;
31
- const { editingCell, setEditingCell, pendingEditorValue, setPendingEditorValue, commitCellEdit, cancelPopoverEdit, popoverAnchorEl, setPopoverAnchorEl } = editing;
32
- const { setActiveCell, handleCellMouseDown, selectionRange, hasCellSelection, handleGridKeyDown, handleFillHandleMouseDown, handleCopy, handleCut, handlePaste, cutRange, copyRange, canUndo, canRedo, onUndo, onRedo, isDragging } = interaction;
33
- const { menuPosition, handleCellContextMenu, closeContextMenu } = ctxMenu;
34
- const { headerFilterInput, cellDescriptorInput, statusBarConfig, showEmptyInGrid, onCellError } = viewModels;
35
- const { headerMenu } = pinning;
36
- const handlePasteVoid = useCallback(() => { void handlePaste(); }, [handlePaste]);
37
- // ── Props destructuring ─────────────────────────────────────────────────
38
- const { items, columns, getRowId, emptyState, layoutMode = 'fill', rowSelection = 'none', suppressHorizontalScroll, stickyHeader = true, isLoading = false, loadingMessage = 'Loading\u2026', 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, visibleColumns, columnOrder, onColumnOrderChange, columnReorder, virtualScroll, rowHeight, density = 'normal', pinnedColumns, currentPage = 1, pageSize: propPageSize = 25, } = props;
39
- // ── Derived values ──────────────────────────────────────────────────────
40
- const rowNumberOffset = hasRowNumbersCol ? (currentPage - 1) * propPageSize : 0;
41
- const headerRows = useMemo(() => buildHeaderRows(columns, visibleColumns), [columns, visibleColumns]);
42
- const allowOverflowX = !suppressHorizontalScroll && containerWidth > 0 && (minTableWidth > containerWidth || desiredTableWidth > containerWidth);
43
- const fitToContent = layoutMode === 'content';
44
- // ── Column resize ──────────────────────────────────────────────────────
45
- const { handleResizeStart, getColumnWidth } = useColumnResize({
46
- columnSizingOverrides,
47
- setColumnSizingOverrides,
48
- });
49
- // ── Column reorder ─────────────────────────────────────────────────────
50
- const { isDragging: isReorderDragging, dropIndicatorX, handleHeaderMouseDown } = useColumnReorder({
51
- columns: visibleCols,
52
- columnOrder,
53
- onColumnOrderChange,
54
- enabled: columnReorder === true,
55
- pinnedColumns,
56
- wrapperRef,
57
- });
58
- // ── Virtual scroll ─────────────────────────────────────────────────────
59
- const virtualScrollEnabled = virtualScroll?.enabled === true;
60
- const virtualRowHeight = virtualScroll?.rowHeight ?? 36;
61
- const { visibleRange } = useVirtualScroll({
62
- totalRows: items.length,
63
- rowHeight: virtualRowHeight,
64
- enabled: virtualScrollEnabled,
65
- overscan: virtualScroll?.overscan,
66
- containerRef: wrapperRef,
67
- });
68
- // ── Memoized callback groups ───────────────────────────────────────────
69
- const editCallbacks = useMemo(() => ({ commitCellEdit, setEditingCell, setPendingEditorValue, cancelPopoverEdit }), [commitCellEdit, setEditingCell, setPendingEditorValue, cancelPopoverEdit]);
70
- const interactionHandlers = useMemo(() => ({ handleCellMouseDown, setActiveCell, setEditingCell, handleCellContextMenu }), [handleCellMouseDown, setActiveCell, setEditingCell, handleCellContextMenu]);
71
- // ── Stable refs for volatile state ─────────────────────────────────────
72
- const cellDescriptorInputRef = useLatestRef(cellDescriptorInput);
73
- const pendingEditorValueRef = useLatestRef(pendingEditorValue);
74
- const popoverAnchorElRef = useLatestRef(popoverAnchorEl);
75
- const selectedRowIdsRef = useLatestRef(selectedRowIds);
76
- // ── Stable row-click handler ───────────────────────────────────────────
77
- const handleSingleRowClick = useCallback((e) => {
78
- if (rowSelection !== 'single')
79
- return;
80
- const rowId = e.currentTarget.dataset.rowId;
81
- if (!rowId)
82
- return;
83
- const ids = selectedRowIdsRef.current;
84
- updateSelection(ids.has(rowId) ? new Set() : new Set([rowId]));
85
- }, [rowSelection, updateSelection, selectedRowIdsRef]);
86
- // ── Return ─────────────────────────────────────────────────────────────
87
- return {
88
- // Refs
89
- wrapperRef,
90
- tableContainerRef,
91
- lastMouseShiftRef,
92
- // State sub-objects
93
- layout,
94
- rowSel,
95
- editing,
96
- interaction,
97
- ctxMenu,
98
- viewModels,
99
- pinning,
100
- // Column resize
101
- handleResizeStart,
102
- getColumnWidth,
103
- // Column reorder
104
- isReorderDragging,
105
- dropIndicatorX,
106
- handleHeaderMouseDown,
107
- // Virtual scroll
108
- virtualScrollEnabled,
109
- virtualRowHeight,
110
- visibleRange,
111
- // Derived from props
112
- items,
113
- columns,
114
- getRowId,
115
- emptyState,
116
- layoutMode,
117
- rowSelection,
118
- suppressHorizontalScroll,
119
- stickyHeader,
120
- isLoading,
121
- loadingMessage,
122
- ariaLabel,
123
- ariaLabelledBy,
124
- visibleColumns,
125
- columnOrder,
126
- columnReorder,
127
- density,
128
- rowHeight,
129
- pinnedColumns,
130
- currentPage,
131
- propPageSize,
132
- // Computed values
133
- rowNumberOffset,
134
- headerRows,
135
- allowOverflowX,
136
- fitToContent,
137
- // Memoized callback groups
138
- editCallbacks,
139
- interactionHandlers,
140
- // Stable refs for volatile state
141
- cellDescriptorInputRef,
142
- pendingEditorValueRef,
143
- popoverAnchorElRef,
144
- selectedRowIdsRef,
145
- // Convenience handlers
146
- handleSingleRowClick,
147
- handlePasteVoid,
148
- // Layout-derived references
149
- visibleCols,
150
- totalColCount,
151
- hasCheckboxCol,
152
- hasRowNumbersCol,
153
- colOffset,
154
- containerWidth,
155
- minTableWidth,
156
- desiredTableWidth,
157
- columnSizingOverrides,
158
- setColumnSizingOverrides,
159
- measuredColumnWidths,
160
- // Row selection shortcuts
161
- selectedRowIds,
162
- updateSelection,
163
- handleRowCheckboxChange,
164
- handleSelectAll,
165
- allSelected,
166
- someSelected,
167
- // Editing shortcuts
168
- editingCell,
169
- setPopoverAnchorEl,
170
- cancelPopoverEdit,
171
- // Interaction shortcuts
172
- setActiveCell,
173
- selectionRange,
174
- hasCellSelection,
175
- handleGridKeyDown,
176
- handleFillHandleMouseDown,
177
- handleCopy,
178
- handleCut,
179
- cutRange,
180
- copyRange,
181
- canUndo,
182
- canRedo,
183
- onUndo,
184
- onRedo,
185
- isDragging,
186
- // Context menu shortcuts
187
- menuPosition,
188
- handleCellContextMenu,
189
- closeContextMenu,
190
- // ViewModel shortcuts
191
- headerFilterInput,
192
- cellDescriptorInput,
193
- statusBarConfig,
194
- showEmptyInGrid,
195
- onCellError,
196
- // Pinning shortcuts
197
- headerMenu,
198
- };
199
- }
@@ -1,34 +0,0 @@
1
- /**
2
- * Date filter state sub-hook for column header filters.
3
- * Manages temporary date from/to values and apply/clear handlers.
4
- */
5
- import { useState, useCallback, useEffect } from 'react';
6
- export function useDateFilterState(params) {
7
- const { dateValue, onDateChange, isFilterOpen } = params;
8
- const [tempDateFrom, setTempDateFrom] = useState(dateValue?.from ?? '');
9
- const [tempDateTo, setTempDateTo] = useState(dateValue?.to ?? '');
10
- // Sync temp state when popover opens
11
- useEffect(() => {
12
- if (isFilterOpen) {
13
- setTempDateFrom(dateValue?.from ?? '');
14
- setTempDateTo(dateValue?.to ?? '');
15
- }
16
- }, [isFilterOpen, dateValue]);
17
- const handleDateApply = useCallback(() => {
18
- const from = tempDateFrom || undefined;
19
- const to = tempDateTo || undefined;
20
- onDateChange?.(from || to ? { from, to } : undefined);
21
- }, [onDateChange, tempDateFrom, tempDateTo]);
22
- const handleDateClear = useCallback(() => {
23
- setTempDateFrom('');
24
- setTempDateTo('');
25
- }, []);
26
- return {
27
- tempDateFrom,
28
- setTempDateFrom,
29
- tempDateTo,
30
- setTempDateTo,
31
- handleDateApply,
32
- handleDateClear,
33
- };
34
- }
@@ -1,35 +0,0 @@
1
- import { useState, useEffect, useCallback, useRef } from 'react';
2
- /**
3
- * Returns a debounced value that updates after the specified delay when the source value changes.
4
- */
5
- export function useDebounce(value, delayMs) {
6
- const [debouncedValue, setDebouncedValue] = useState(value);
7
- useEffect(() => {
8
- const id = setTimeout(() => {
9
- setDebouncedValue(value);
10
- }, delayMs);
11
- return () => clearTimeout(id);
12
- }, [value, delayMs]);
13
- return debouncedValue;
14
- }
15
- /**
16
- * Returns a stable callback that invokes the given function after the specified delay.
17
- * Each new call resets the timer.
18
- */
19
- export function useDebouncedCallback(fn, delayMs) {
20
- const timeoutRef = useRef(undefined);
21
- const fnRef = useRef(fn);
22
- fnRef.current = fn;
23
- const debounced = useCallback(((...args) => {
24
- if (timeoutRef.current)
25
- clearTimeout(timeoutRef.current);
26
- timeoutRef.current = setTimeout(() => {
27
- fnRef.current(...args);
28
- }, delayMs);
29
- }), [delayMs]);
30
- useEffect(() => () => {
31
- if (timeoutRef.current)
32
- clearTimeout(timeoutRef.current);
33
- }, []);
34
- return debounced;
35
- }
@@ -1,200 +0,0 @@
1
- import { useState, useCallback, useRef, useEffect } from 'react';
2
- import { normalizeSelectionRange } from '../types';
3
- import { applyFillValues } from '../utils';
4
- import { useLatestRef } from './useLatestRef';
5
- /** DOM attribute name for fill-drag range highlighting (same as cell selection drag). */
6
- const DRAG_ATTR = 'data-drag-range';
7
- /**
8
- * Manages Excel-style fill handle drag-to-fill for cell ranges.
9
- * @param params - Items, columns, selection range, editability, and value change callback.
10
- * @returns Fill drag state, setter, and mousedown handler for the fill handle.
11
- */
12
- export function useFillHandle(params) {
13
- const { items, visibleCols, editable, onCellValueChanged, selectionRange, setSelectionRange, setActiveCell, colOffset, wrapperRef, beginBatch, endBatch, } = params;
14
- const [fillDrag, setFillDrag] = useState(null);
15
- const fillDragEndRef = useRef({ endRow: 0, endCol: 0 });
16
- const rafRef = useRef(0);
17
- const liveFillRangeRef = useRef(null);
18
- const colOffsetRef = useLatestRef(colOffset);
19
- useEffect(() => {
20
- if (!fillDrag || editable === false || !onCellValueChanged || !wrapperRef.current)
21
- return;
22
- fillDragEndRef.current = { endRow: fillDrag.startRow, endCol: fillDrag.startCol };
23
- liveFillRangeRef.current = null;
24
- /** Set of currently drag-marked HTMLElements — avoids O(n) full DOM scan on clear. */
25
- const markedCells = new Set();
26
- /** Cell lookup index built on drag start — O(1) lookups per frame. */
27
- let cellIndex = null;
28
- const buildCellIndex = () => {
29
- const wrapper = wrapperRef.current;
30
- if (!wrapper)
31
- return;
32
- cellIndex = new Map();
33
- const cells = wrapper.querySelectorAll('[data-row-index][data-col-index]');
34
- for (let i = 0; i < cells.length; i++) {
35
- const el = cells[i];
36
- const r = el.getAttribute('data-row-index') ?? '';
37
- const c = el.getAttribute('data-col-index') ?? '';
38
- cellIndex.set(`${r},${c}`, el);
39
- }
40
- };
41
- // Build the index once at fill drag start
42
- buildCellIndex();
43
- const applyDragAttrs = (range) => {
44
- const wrapper = wrapperRef.current;
45
- if (!wrapper)
46
- return;
47
- const minR = Math.min(range.startRow, range.endRow);
48
- const maxR = Math.max(range.startRow, range.endRow);
49
- const minC = Math.min(range.startCol, range.endCol);
50
- const maxC = Math.max(range.startCol, range.endCol);
51
- const colOff = colOffsetRef.current;
52
- // Un-mark cells no longer in range
53
- for (const el of markedCells) {
54
- const r = parseInt(el.getAttribute('data-row-index') ?? '', 10);
55
- const c = parseInt(el.getAttribute('data-col-index') ?? '', 10) - colOff;
56
- if (!(r >= minR && r <= maxR && c >= minC && c <= maxC)) {
57
- el.removeAttribute(DRAG_ATTR);
58
- markedCells.delete(el);
59
- }
60
- }
61
- // Look up only cells in the new range — O(range size) via Map lookup
62
- for (let r = minR; r <= maxR; r++) {
63
- for (let c = minC; c <= maxC; c++) {
64
- const key = `${r},${c + colOff}`;
65
- let el = cellIndex?.get(key);
66
- // Handle virtual scroll recycling — if element is stale, rebuild index once
67
- if (el && !el.isConnected) {
68
- buildCellIndex();
69
- el = cellIndex?.get(key);
70
- }
71
- if (el) {
72
- if (!el.hasAttribute(DRAG_ATTR))
73
- el.setAttribute(DRAG_ATTR, '');
74
- markedCells.add(el);
75
- }
76
- }
77
- }
78
- };
79
- const clearDragAttrs = () => {
80
- for (const el of markedCells) {
81
- el.removeAttribute(DRAG_ATTR);
82
- }
83
- markedCells.clear();
84
- cellIndex = null;
85
- };
86
- let lastFillMousePos = null;
87
- const resolveRange = (cx, cy) => {
88
- const target = document.elementFromPoint(cx, cy);
89
- const cell = target?.closest?.('[data-row-index][data-col-index]');
90
- if (!cell || !wrapperRef.current?.contains(cell))
91
- return null;
92
- const r = parseInt(cell.getAttribute('data-row-index') ?? '', 10);
93
- const c = parseInt(cell.getAttribute('data-col-index') ?? '', 10);
94
- if (Number.isNaN(r) || Number.isNaN(c) || c < colOffsetRef.current)
95
- return null;
96
- const dataCol = c - colOffsetRef.current;
97
- return normalizeSelectionRange({
98
- startRow: fillDrag.startRow,
99
- startCol: fillDrag.startCol,
100
- endRow: r,
101
- endCol: dataCol,
102
- });
103
- };
104
- const onMove = (e) => {
105
- lastFillMousePos = { cx: e.clientX, cy: e.clientY };
106
- if (rafRef.current)
107
- cancelAnimationFrame(rafRef.current);
108
- rafRef.current = requestAnimationFrame(() => {
109
- rafRef.current = 0;
110
- if (!lastFillMousePos)
111
- return;
112
- const newRange = resolveRange(lastFillMousePos.cx, lastFillMousePos.cy);
113
- if (!newRange)
114
- return;
115
- // Skip if unchanged
116
- const prev = liveFillRangeRef.current;
117
- if (prev &&
118
- prev.startRow === newRange.startRow &&
119
- prev.startCol === newRange.startCol &&
120
- prev.endRow === newRange.endRow &&
121
- prev.endCol === newRange.endCol) {
122
- return;
123
- }
124
- liveFillRangeRef.current = newRange;
125
- fillDragEndRef.current = { endRow: newRange.endRow, endCol: newRange.endCol };
126
- applyDragAttrs(newRange);
127
- });
128
- };
129
- const onUp = () => {
130
- if (rafRef.current) {
131
- cancelAnimationFrame(rafRef.current);
132
- rafRef.current = 0;
133
- }
134
- // Flush: resolve final position if RAF hasn't executed yet
135
- if (lastFillMousePos) {
136
- const flushed = resolveRange(lastFillMousePos.cx, lastFillMousePos.cy);
137
- if (flushed) {
138
- liveFillRangeRef.current = flushed;
139
- fillDragEndRef.current = { endRow: flushed.endRow, endCol: flushed.endCol };
140
- }
141
- }
142
- clearDragAttrs();
143
- const end = fillDragEndRef.current;
144
- const norm = normalizeSelectionRange({
145
- startRow: fillDrag.startRow,
146
- startCol: fillDrag.startCol,
147
- endRow: end.endRow,
148
- endCol: end.endCol,
149
- });
150
- // Commit range to React state
151
- setSelectionRange(norm);
152
- setActiveCell({ rowIndex: end.endRow, columnIndex: end.endCol + colOffsetRef.current });
153
- // Apply fill values
154
- const fillEvents = applyFillValues(norm, fillDrag.startRow, fillDrag.startCol, items, visibleCols);
155
- if (fillEvents.length > 0) {
156
- beginBatch?.();
157
- for (const evt of fillEvents)
158
- onCellValueChanged(evt);
159
- endBatch?.();
160
- }
161
- setFillDrag(null);
162
- liveFillRangeRef.current = null;
163
- };
164
- window.addEventListener('mousemove', onMove, true);
165
- window.addEventListener('mouseup', onUp, true);
166
- return () => {
167
- window.removeEventListener('mousemove', onMove, true);
168
- window.removeEventListener('mouseup', onUp, true);
169
- if (rafRef.current)
170
- cancelAnimationFrame(rafRef.current);
171
- };
172
- }, [
173
- fillDrag,
174
- editable,
175
- items,
176
- visibleCols,
177
- setSelectionRange,
178
- setActiveCell,
179
- onCellValueChanged,
180
- beginBatch,
181
- endBatch,
182
- colOffsetRef,
183
- wrapperRef,
184
- ]);
185
- // Ref mirror — keeps handleFillHandleMouseDown stable across selection changes
186
- const selectionRangeRef = useRef(selectionRange);
187
- selectionRangeRef.current = selectionRange;
188
- const handleFillHandleMouseDown = useCallback((e) => {
189
- e.preventDefault();
190
- e.stopPropagation();
191
- const range = selectionRangeRef.current;
192
- if (!range)
193
- return;
194
- setFillDrag({
195
- startRow: range.startRow,
196
- startCol: range.startCol,
197
- });
198
- }, []);
199
- return { fillDrag, setFillDrag, handleFillHandleMouseDown };
200
- }
@@ -1,55 +0,0 @@
1
- import { useState, useEffect, useCallback, useRef } from 'react';
2
- /** Shallow-compare two string arrays by value. */
3
- function fieldsEqual(a, b) {
4
- if (a.length !== b.length)
5
- return false;
6
- for (let i = 0; i < a.length; i++) {
7
- if (a[i] !== b[i])
8
- return false;
9
- }
10
- return true;
11
- }
12
- /**
13
- * Load filter options for the given fields from a data source.
14
- *
15
- * Accepts `IDataSource<T>` or a plain `{ fetchFilterOptions }` object.
16
- */
17
- export function useFilterOptions(dataSource, fields) {
18
- // Stabilize the fields array so inline literals (e.g. ['a','b']) don't
19
- // cause infinite re-render loops via useCallback/useEffect deps.
20
- const fieldsRef = useRef(fields);
21
- if (!fieldsEqual(fieldsRef.current, fields)) {
22
- fieldsRef.current = fields;
23
- }
24
- const stableFields = fieldsRef.current;
25
- const [filterOptions, setFilterOptions] = useState({});
26
- const [loadingOptions, setLoadingOptions] = useState({});
27
- const load = useCallback(async () => {
28
- const fetcher = 'fetchFilterOptions' in dataSource && typeof dataSource.fetchFilterOptions === 'function'
29
- ? dataSource.fetchFilterOptions.bind(dataSource)
30
- : undefined;
31
- if (!fetcher) {
32
- setFilterOptions({});
33
- setLoadingOptions({});
34
- return;
35
- }
36
- const loading = {};
37
- stableFields.forEach((f) => { loading[f] = true; });
38
- setLoadingOptions(loading);
39
- const results = {};
40
- await Promise.all(stableFields.map(async (field) => {
41
- try {
42
- results[field] = await fetcher(field);
43
- }
44
- catch {
45
- results[field] = [];
46
- }
47
- }));
48
- setFilterOptions(results);
49
- setLoadingOptions({});
50
- }, [dataSource, stableFields]);
51
- useEffect(() => {
52
- load().catch(() => { });
53
- }, [load]);
54
- return { filterOptions, loadingOptions };
55
- }
@@ -1,38 +0,0 @@
1
- /**
2
- * Headless inline cell editor state for Fluent, Material, and Radix InlineCellEditor.
3
- * UI packages use this hook and render only the framework input (Input, TextField, select, Checkbox).
4
- */
5
- import { useState, useCallback } from 'react';
6
- /**
7
- * Returns localValue/setLocalValue (for text), handleKeyDown (Escape cancel, Enter commit for text),
8
- * handleBlur (commit on blur for text), commit(value), cancel(). UI renders only the input.
9
- */
10
- export function useInlineCellEditorState(params) {
11
- const { value, editorType, onCommit, onCancel } = params;
12
- const [localValue, setLocalValue] = useState(value !== null && value !== undefined ? String(value) : '');
13
- const handleKeyDown = useCallback((e) => {
14
- if (e.key === 'Escape') {
15
- e.preventDefault();
16
- e.stopPropagation(); // Don't let the grid handler clear selection on Escape
17
- onCancel();
18
- }
19
- if (e.key === 'Enter' && editorType === 'text') {
20
- e.preventDefault();
21
- e.stopPropagation(); // Don't let the grid handler re-open an editor
22
- onCommit(localValue);
23
- }
24
- }, [onCancel, onCommit, localValue, editorType]);
25
- const handleBlur = useCallback(() => {
26
- if (editorType === 'text') {
27
- onCommit(localValue);
28
- }
29
- }, [editorType, localValue, onCommit]);
30
- return {
31
- localValue,
32
- setLocalValue,
33
- handleKeyDown,
34
- handleBlur,
35
- commit: onCommit,
36
- cancel: onCancel,
37
- };
38
- }