@comitor/ui 1.9.0 → 1.11.0

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 (105) hide show
  1. package/README.md +15 -4
  2. package/dist/data-grid/compose-refs.d.ts +19 -0
  3. package/dist/data-grid/compose-refs.js +41 -0
  4. package/dist/data-grid/compose-refs.js.map +1 -0
  5. package/dist/data-grid/data-grid-cell-variants.d.ts +15 -0
  6. package/dist/data-grid/data-grid-cell-variants.js +1734 -0
  7. package/dist/data-grid/data-grid-cell-variants.js.map +1 -0
  8. package/dist/data-grid/data-grid-cell-wrapper.d.ts +9 -0
  9. package/dist/data-grid/data-grid-cell-wrapper.js +179 -0
  10. package/dist/data-grid/data-grid-cell-wrapper.js.map +1 -0
  11. package/dist/data-grid/data-grid-cell.d.ts +8 -0
  12. package/dist/data-grid/data-grid-cell.js +101 -0
  13. package/dist/data-grid/data-grid-cell.js.map +1 -0
  14. package/dist/data-grid/data-grid-column-header.d.ts +12 -0
  15. package/dist/data-grid/data-grid-column-header.js +205 -0
  16. package/dist/data-grid/data-grid-column-header.js.map +1 -0
  17. package/dist/data-grid/data-grid-context-menu.d.ts +12 -0
  18. package/dist/data-grid/data-grid-context-menu.js +160 -0
  19. package/dist/data-grid/data-grid-context-menu.js.map +1 -0
  20. package/dist/data-grid/data-grid-filter-menu.d.ts +12 -0
  21. package/dist/data-grid/data-grid-filter-menu.js +714 -0
  22. package/dist/data-grid/data-grid-filter-menu.js.map +1 -0
  23. package/dist/data-grid/data-grid-keyboard-shortcuts.d.ts +20 -0
  24. package/dist/data-grid/data-grid-keyboard-shortcuts.js +227 -0
  25. package/dist/data-grid/data-grid-keyboard-shortcuts.js.map +1 -0
  26. package/dist/data-grid/data-grid-paste-dialog.d.ts +11 -0
  27. package/dist/data-grid/data-grid-paste-dialog.js +103 -0
  28. package/dist/data-grid/data-grid-paste-dialog.js.map +1 -0
  29. package/dist/data-grid/data-grid-presence.d.ts +14 -0
  30. package/dist/data-grid/data-grid-presence.js +16 -0
  31. package/dist/data-grid/data-grid-presence.js.map +1 -0
  32. package/dist/data-grid/data-grid-row-height-menu.d.ts +15 -0
  33. package/dist/data-grid/data-grid-row-height-menu.js +56 -0
  34. package/dist/data-grid/data-grid-row-height-menu.js.map +1 -0
  35. package/dist/data-grid/data-grid-row.d.ts +28 -0
  36. package/dist/data-grid/data-grid-row.js +206 -0
  37. package/dist/data-grid/data-grid-row.js.map +1 -0
  38. package/dist/data-grid/data-grid-search.d.ts +10 -0
  39. package/dist/data-grid/data-grid-search.js +183 -0
  40. package/dist/data-grid/data-grid-search.js.map +1 -0
  41. package/dist/data-grid/data-grid-select-column.d.ts +12 -0
  42. package/dist/data-grid/data-grid-select-column.js +177 -0
  43. package/dist/data-grid/data-grid-select-column.js.map +1 -0
  44. package/dist/data-grid/data-grid-skeleton.d.ts +13 -0
  45. package/dist/data-grid/data-grid-skeleton.js +52 -0
  46. package/dist/data-grid/data-grid-skeleton.js.map +1 -0
  47. package/dist/data-grid/data-grid-sort-menu.d.ts +12 -0
  48. package/dist/data-grid/data-grid-sort-menu.js +271 -0
  49. package/dist/data-grid/data-grid-sort-menu.js.map +1 -0
  50. package/dist/data-grid/data-grid-view-menu.d.ts +13 -0
  51. package/dist/data-grid/data-grid-view-menu.js +66 -0
  52. package/dist/data-grid/data-grid-view-menu.js.map +1 -0
  53. package/dist/data-grid/data-grid.d.ts +32 -0
  54. package/dist/data-grid/data-grid.js +235 -0
  55. package/dist/data-grid/data-grid.js.map +1 -0
  56. package/dist/data-grid/filters.d.ts +31 -0
  57. package/dist/data-grid/filters.js +212 -0
  58. package/dist/data-grid/filters.js.map +1 -0
  59. package/dist/data-grid/index.d.ts +22 -0
  60. package/dist/data-grid/index.js +77 -0
  61. package/dist/data-grid/index.js.map +1 -0
  62. package/dist/data-grid/messages.d.ts +280 -0
  63. package/dist/data-grid/messages.js +279 -0
  64. package/dist/data-grid/messages.js.map +1 -0
  65. package/dist/data-grid/sortable.d.ts +50 -0
  66. package/dist/data-grid/sortable.js +361 -0
  67. package/dist/data-grid/sortable.js.map +1 -0
  68. package/dist/data-grid/toast.d.ts +10 -0
  69. package/dist/data-grid/toast.js +21 -0
  70. package/dist/data-grid/toast.js.map +1 -0
  71. package/dist/data-grid/types.d.ts +189 -0
  72. package/dist/data-grid/types.js +1 -0
  73. package/dist/data-grid/types.js.map +1 -0
  74. package/dist/data-grid/use-as-ref.d.ts +5 -0
  75. package/dist/data-grid/use-as-ref.js +13 -0
  76. package/dist/data-grid/use-as-ref.js.map +1 -0
  77. package/dist/data-grid/use-badge-overflow.d.ts +24 -0
  78. package/dist/data-grid/use-badge-overflow.js +123 -0
  79. package/dist/data-grid/use-badge-overflow.js.map +1 -0
  80. package/dist/data-grid/use-callback-ref.d.ts +10 -0
  81. package/dist/data-grid/use-callback-ref.js +12 -0
  82. package/dist/data-grid/use-callback-ref.js.map +1 -0
  83. package/dist/data-grid/use-data-grid-undo-redo.d.ts +33 -0
  84. package/dist/data-grid/use-data-grid-undo-redo.js +345 -0
  85. package/dist/data-grid/use-data-grid-undo-redo.js.map +1 -0
  86. package/dist/data-grid/use-data-grid.d.ts +92 -0
  87. package/dist/data-grid/use-data-grid.js +2692 -0
  88. package/dist/data-grid/use-data-grid.js.map +1 -0
  89. package/dist/data-grid/use-debounced-callback.d.ts +6 -0
  90. package/dist/data-grid/use-debounced-callback.js +19 -0
  91. package/dist/data-grid/use-debounced-callback.js.map +1 -0
  92. package/dist/data-grid/use-isomorphic-layout-effect.d.ts +5 -0
  93. package/dist/data-grid/use-isomorphic-layout-effect.js +6 -0
  94. package/dist/data-grid/use-isomorphic-layout-effect.js.map +1 -0
  95. package/dist/data-grid/use-lazy-ref.d.ts +5 -0
  96. package/dist/data-grid/use-lazy-ref.js +12 -0
  97. package/dist/data-grid/use-lazy-ref.js.map +1 -0
  98. package/dist/data-grid/utils.d.ts +73 -0
  99. package/dist/data-grid/utils.js +408 -0
  100. package/dist/data-grid/utils.js.map +1 -0
  101. package/dist/shell/app-launcher.js +16 -16
  102. package/dist/shell/app-launcher.js.map +1 -1
  103. package/dist/shell/types.d.ts +13 -0
  104. package/dist/shell/types.js.map +1 -1
  105. package/package.json +33 -15
@@ -0,0 +1,2692 @@
1
+ "use client";
2
+ import { useDirection } from "@radix-ui/react-direction";
3
+ import {
4
+ getCoreRowModel,
5
+ getFilteredRowModel,
6
+ getSortedRowModel,
7
+ useReactTable
8
+ } from "@tanstack/react-table";
9
+ import { useVirtualizer } from "@tanstack/react-virtual";
10
+ import * as React from "react";
11
+ import { msg } from "./messages.js";
12
+ import { toastError, toastSuccess } from "./toast.js";
13
+ import { useAsRef } from "./use-as-ref.js";
14
+ import { useIsomorphicLayoutEffect } from "./use-isomorphic-layout-effect.js";
15
+ import { useLazyRef } from "./use-lazy-ref.js";
16
+ import {
17
+ formatDate,
18
+ getCellKey,
19
+ getEmptyCellValue,
20
+ getIsFileCellData,
21
+ getIsInPopover,
22
+ getRowHeightValue,
23
+ getScrollDirection,
24
+ matchSelectOption,
25
+ parseCellKey,
26
+ parseTsv,
27
+ readClipboardText,
28
+ scrollCellIntoView,
29
+ writeClipboardText
30
+ } from "./utils.js";
31
+ const DEFAULT_ROW_HEIGHT = "short";
32
+ const OVERSCAN = 6;
33
+ const VIEWPORT_OFFSET = 1;
34
+ const HORIZONTAL_PAGE_SIZE = 5;
35
+ const SCROLL_SYNC_RETRY_COUNT = 16;
36
+ const MIN_COLUMN_SIZE = 60;
37
+ const MAX_COLUMN_SIZE = 800;
38
+ const SEARCH_SHORTCUT_KEY = "f";
39
+ const NON_NAVIGABLE_COLUMN_IDS = /* @__PURE__ */ new Set(["select", "actions"]);
40
+ const AUTO_SCROLL_EDGE_ZONE = 50;
41
+ const AUTO_SCROLL_SPEED_RAMP_ZONE = AUTO_SCROLL_EDGE_ZONE * 3;
42
+ const AUTO_SCROLL_MIN_SPEED = 8;
43
+ const AUTO_SCROLL_MAX_SPEED = 40;
44
+ const AUTO_SCROLL_SELECTION_THROTTLE_MS = 32;
45
+ const DOMAIN_REGEX = /^[\w.-]+\.[a-z]{2,}(\/\S*)?$/i;
46
+ const ISO_DATE_REGEX = /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}.*)?$/;
47
+ const TRUTHY_BOOLEANS = /* @__PURE__ */ new Set(["true", "1", "yes", "checked"]);
48
+ const VALID_BOOLEANS = /* @__PURE__ */ new Set(["true", "false", "1", "0", "yes", "no", "checked", "unchecked"]);
49
+ function useStore(store, selector) {
50
+ const getSnapshot = React.useCallback(() => selector(store.getState()), [store, selector]);
51
+ return React.useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot);
52
+ }
53
+ function useDataGrid({
54
+ data,
55
+ columns,
56
+ rowHeight: rowHeightProp = DEFAULT_ROW_HEIGHT,
57
+ overscan = OVERSCAN,
58
+ dir: dirProp,
59
+ initialState,
60
+ ...props
61
+ }) {
62
+ const dir = useDirection(dirProp);
63
+ const dataGridRef = React.useRef(null);
64
+ const tableRef = React.useRef(null);
65
+ const rowVirtualizerRef = React.useRef(null);
66
+ const headerRef = React.useRef(null);
67
+ const rowMapRef = React.useRef(/* @__PURE__ */ new Map());
68
+ const cellMapRef = React.useRef(/* @__PURE__ */ new Map());
69
+ const footerRef = React.useRef(null);
70
+ const focusGuardRef = React.useRef(false);
71
+ const propsRef = useAsRef({
72
+ ...props,
73
+ data,
74
+ columns,
75
+ initialState
76
+ });
77
+ const listenersRef = useLazyRef(() => /* @__PURE__ */ new Set());
78
+ const stateRef = useLazyRef(() => {
79
+ return {
80
+ sorting: initialState?.sorting ?? [],
81
+ columnFilters: initialState?.columnFilters ?? [],
82
+ rowHeight: rowHeightProp,
83
+ rowSelection: initialState?.rowSelection ?? {},
84
+ selectionState: {
85
+ selectedCells: /* @__PURE__ */ new Set(),
86
+ selectionRange: null,
87
+ isSelecting: false
88
+ },
89
+ focusedCell: null,
90
+ editingCell: null,
91
+ cutCells: /* @__PURE__ */ new Set(),
92
+ contextMenu: {
93
+ open: false,
94
+ x: 0,
95
+ y: 0
96
+ },
97
+ searchQuery: "",
98
+ searchMatches: [],
99
+ matchIndex: -1,
100
+ searchOpen: false,
101
+ lastClickedRowId: null,
102
+ pasteDialog: {
103
+ open: false,
104
+ rowsNeeded: 0,
105
+ clipboardText: ""
106
+ }
107
+ };
108
+ });
109
+ const store = React.useMemo(() => {
110
+ let isBatching = false;
111
+ let pendingNotification = false;
112
+ return {
113
+ subscribe: (callback) => {
114
+ listenersRef.current.add(callback);
115
+ return () => listenersRef.current.delete(callback);
116
+ },
117
+ getState: () => stateRef.current,
118
+ setState: (key, value) => {
119
+ if (Object.is(stateRef.current[key], value)) return;
120
+ stateRef.current[key] = value;
121
+ if (isBatching) {
122
+ pendingNotification = true;
123
+ } else {
124
+ if (!pendingNotification) {
125
+ pendingNotification = true;
126
+ queueMicrotask(() => {
127
+ pendingNotification = false;
128
+ store.notify();
129
+ });
130
+ }
131
+ }
132
+ },
133
+ notify: () => {
134
+ for (const listener of listenersRef.current) {
135
+ listener();
136
+ }
137
+ },
138
+ batch: (fn) => {
139
+ if (isBatching) {
140
+ fn();
141
+ return;
142
+ }
143
+ isBatching = true;
144
+ const wasPending = pendingNotification;
145
+ pendingNotification = false;
146
+ try {
147
+ fn();
148
+ } finally {
149
+ isBatching = false;
150
+ if (pendingNotification || wasPending) {
151
+ pendingNotification = false;
152
+ store.notify();
153
+ }
154
+ }
155
+ }
156
+ };
157
+ }, [listenersRef, stateRef]);
158
+ const focusedCell = useStore(store, (state) => state.focusedCell);
159
+ const editingCell = useStore(store, (state) => state.editingCell);
160
+ const selectionState = useStore(store, (state) => state.selectionState);
161
+ const searchQuery = useStore(store, (state) => state.searchQuery);
162
+ const searchMatches = useStore(store, (state) => state.searchMatches);
163
+ const matchIndex = useStore(store, (state) => state.matchIndex);
164
+ const searchOpen = useStore(store, (state) => state.searchOpen);
165
+ const sorting = useStore(store, (state) => state.sorting);
166
+ const columnFilters = useStore(store, (state) => state.columnFilters);
167
+ const rowSelection = useStore(store, (state) => state.rowSelection);
168
+ const rowHeight = useStore(store, (state) => state.rowHeight);
169
+ const contextMenu = useStore(store, (state) => state.contextMenu);
170
+ const pasteDialog = useStore(store, (state) => state.pasteDialog);
171
+ const rowHeightValue = getRowHeightValue(rowHeight);
172
+ const controlledSorting = props.state?.sorting;
173
+ const controlledColumnFilters = props.state?.columnFilters;
174
+ useIsomorphicLayoutEffect(() => {
175
+ if (!controlledSorting && !controlledColumnFilters) return;
176
+ store.batch(() => {
177
+ if (controlledSorting) store.setState("sorting", controlledSorting);
178
+ if (controlledColumnFilters) {
179
+ store.setState("columnFilters", controlledColumnFilters);
180
+ }
181
+ });
182
+ }, [store, controlledSorting, controlledColumnFilters]);
183
+ const prevCellSelectionMapRef = useLazyRef(() => /* @__PURE__ */ new Map());
184
+ const cellSelectionMap = React.useMemo(() => {
185
+ const selectedCells = selectionState.selectedCells;
186
+ if (selectedCells.size === 0) {
187
+ prevCellSelectionMapRef.current.clear();
188
+ return null;
189
+ }
190
+ const newRowCells = /* @__PURE__ */ new Map();
191
+ for (const cellKey of selectedCells) {
192
+ const { rowIndex } = parseCellKey(cellKey);
193
+ let rowSet = newRowCells.get(rowIndex);
194
+ if (!rowSet) {
195
+ rowSet = /* @__PURE__ */ new Set();
196
+ newRowCells.set(rowIndex, rowSet);
197
+ }
198
+ rowSet.add(cellKey);
199
+ }
200
+ const stableMap = /* @__PURE__ */ new Map();
201
+ for (const [rowIndex, newSet] of newRowCells) {
202
+ const prevSet = prevCellSelectionMapRef.current.get(rowIndex);
203
+ if (prevSet && prevSet.size === newSet.size && [...newSet].every((key) => prevSet.has(key))) {
204
+ stableMap.set(rowIndex, prevSet);
205
+ } else {
206
+ stableMap.set(rowIndex, newSet);
207
+ }
208
+ }
209
+ prevCellSelectionMapRef.current = stableMap;
210
+ return stableMap;
211
+ }, [selectionState.selectedCells, prevCellSelectionMapRef]);
212
+ const visualRowIndexCacheRef = React.useRef(null);
213
+ const getVisualRowIndex = React.useCallback((rowId) => {
214
+ const rows = tableRef.current?.getRowModel().rows;
215
+ if (!rows) return void 0;
216
+ if (visualRowIndexCacheRef.current?.rows !== rows) {
217
+ const map = /* @__PURE__ */ new Map();
218
+ for (const [i, row] of rows.entries()) {
219
+ map.set(row.id, i + 1);
220
+ }
221
+ visualRowIndexCacheRef.current = { rows, map };
222
+ }
223
+ return visualRowIndexCacheRef.current.map.get(rowId);
224
+ }, []);
225
+ const columnIds = React.useMemo(() => {
226
+ return columns.map((c) => {
227
+ if (c.id) return c.id;
228
+ if ("accessorKey" in c) return c.accessorKey;
229
+ return void 0;
230
+ }).filter((id) => Boolean(id));
231
+ }, [columns]);
232
+ const navigableColumnIds = React.useMemo(() => {
233
+ return columnIds.filter((c) => !NON_NAVIGABLE_COLUMN_IDS.has(c));
234
+ }, [columnIds]);
235
+ const onDataUpdate = React.useCallback(
236
+ (updates) => {
237
+ if (propsRef.current.readOnly) return;
238
+ const updateArray = Array.isArray(updates) ? updates : [updates];
239
+ if (updateArray.length === 0) return;
240
+ const currentTable = tableRef.current;
241
+ const currentData = propsRef.current.data;
242
+ const rows = currentTable?.getRowModel().rows;
243
+ const rowUpdatesMap = /* @__PURE__ */ new Map();
244
+ for (const update of updateArray) {
245
+ if (!rows || !currentTable) {
246
+ const existingUpdates = rowUpdatesMap.get(update.rowIndex) ?? [];
247
+ existingUpdates.push({
248
+ columnId: update.columnId,
249
+ value: update.value
250
+ });
251
+ rowUpdatesMap.set(update.rowIndex, existingUpdates);
252
+ } else {
253
+ const row = rows[update.rowIndex];
254
+ if (!row) continue;
255
+ const originalData = row.original;
256
+ const originalRowIndex = currentData.indexOf(originalData);
257
+ const targetIndex = originalRowIndex !== -1 ? originalRowIndex : update.rowIndex;
258
+ const existingUpdates = rowUpdatesMap.get(targetIndex) ?? [];
259
+ existingUpdates.push({
260
+ columnId: update.columnId,
261
+ value: update.value
262
+ });
263
+ rowUpdatesMap.set(targetIndex, existingUpdates);
264
+ }
265
+ }
266
+ const maxUpdateIndex = rowUpdatesMap.size > 0 ? Math.max(...Array.from(rowUpdatesMap.keys())) : -1;
267
+ const dataLength = Math.max(currentData.length, maxUpdateIndex + 1);
268
+ const newData = new Array(dataLength);
269
+ for (let i = 0; i < dataLength; i++) {
270
+ const updates2 = rowUpdatesMap.get(i);
271
+ const existingRow = currentData[i] ?? rows?.[i]?.original;
272
+ if (existingRow == null) continue;
273
+ if (updates2) {
274
+ const updatedRow = { ...existingRow };
275
+ for (const { columnId, value } of updates2) {
276
+ updatedRow[columnId] = value;
277
+ }
278
+ newData[i] = updatedRow;
279
+ } else {
280
+ newData[i] = existingRow;
281
+ }
282
+ }
283
+ propsRef.current.onDataChange?.(newData);
284
+ },
285
+ [propsRef]
286
+ );
287
+ const getIsCellSelected = React.useCallback(
288
+ (rowIndex, columnId) => {
289
+ const currentSelectionState = store.getState().selectionState;
290
+ return currentSelectionState.selectedCells.has(getCellKey(rowIndex, columnId));
291
+ },
292
+ [store]
293
+ );
294
+ const onSelectionClear = React.useCallback(() => {
295
+ store.batch(() => {
296
+ store.setState("selectionState", {
297
+ selectedCells: /* @__PURE__ */ new Set(),
298
+ selectionRange: null,
299
+ isSelecting: false
300
+ });
301
+ store.setState("rowSelection", {});
302
+ });
303
+ }, [store]);
304
+ const selectAll = React.useCallback(() => {
305
+ const allCells = /* @__PURE__ */ new Set();
306
+ const currentTable = tableRef.current;
307
+ const rows = currentTable?.getRowModel().rows ?? [];
308
+ const rowCount = rows.length ?? propsRef.current.data.length;
309
+ for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) {
310
+ for (const columnId of columnIds) {
311
+ allCells.add(getCellKey(rowIndex, columnId));
312
+ }
313
+ }
314
+ const firstColumnId = columnIds[0];
315
+ const lastColumnId = columnIds[columnIds.length - 1];
316
+ store.setState("selectionState", {
317
+ selectedCells: allCells,
318
+ selectionRange: columnIds.length > 0 && rowCount > 0 && firstColumnId && lastColumnId ? {
319
+ start: { rowIndex: 0, columnId: firstColumnId },
320
+ end: { rowIndex: rowCount - 1, columnId: lastColumnId }
321
+ } : null,
322
+ isSelecting: false
323
+ });
324
+ }, [columnIds, propsRef, store]);
325
+ const selectColumn = React.useCallback(
326
+ (columnId) => {
327
+ const currentTable = tableRef.current;
328
+ const rows = currentTable?.getRowModel().rows ?? [];
329
+ const rowCount = rows.length ?? propsRef.current.data.length;
330
+ if (rowCount === 0) return;
331
+ const selectedCells = /* @__PURE__ */ new Set();
332
+ for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) {
333
+ selectedCells.add(getCellKey(rowIndex, columnId));
334
+ }
335
+ store.setState("selectionState", {
336
+ selectedCells,
337
+ selectionRange: {
338
+ start: { rowIndex: 0, columnId },
339
+ end: { rowIndex: rowCount - 1, columnId }
340
+ },
341
+ isSelecting: false
342
+ });
343
+ },
344
+ [propsRef, store]
345
+ );
346
+ const selectRange = React.useCallback(
347
+ (start, end, isSelecting = false) => {
348
+ const startColIndex = columnIds.indexOf(start.columnId);
349
+ const endColIndex = columnIds.indexOf(end.columnId);
350
+ const minRow = Math.min(start.rowIndex, end.rowIndex);
351
+ const maxRow = Math.max(start.rowIndex, end.rowIndex);
352
+ const minCol = Math.min(startColIndex, endColIndex);
353
+ const maxCol = Math.max(startColIndex, endColIndex);
354
+ const selectedCells = /* @__PURE__ */ new Set();
355
+ for (let rowIndex = minRow; rowIndex <= maxRow; rowIndex++) {
356
+ for (let colIndex = minCol; colIndex <= maxCol; colIndex++) {
357
+ const columnId = columnIds[colIndex];
358
+ if (columnId) {
359
+ selectedCells.add(getCellKey(rowIndex, columnId));
360
+ }
361
+ }
362
+ }
363
+ store.setState("selectionState", {
364
+ selectedCells,
365
+ selectionRange: { start, end },
366
+ isSelecting
367
+ });
368
+ },
369
+ [columnIds, store]
370
+ );
371
+ const dragDepsRef = useAsRef({
372
+ selectRange,
373
+ dir,
374
+ rowHeightValue,
375
+ columnIds
376
+ });
377
+ const serializeCellsToTsv = React.useCallback(() => {
378
+ const currentState = store.getState();
379
+ let selectedCellsArray;
380
+ if (!currentState.selectionState.selectedCells.size) {
381
+ if (!currentState.focusedCell) return null;
382
+ const focusedCellKey = getCellKey(currentState.focusedCell.rowIndex, currentState.focusedCell.columnId);
383
+ selectedCellsArray = [focusedCellKey];
384
+ } else {
385
+ selectedCellsArray = Array.from(currentState.selectionState.selectedCells);
386
+ }
387
+ const currentTable = tableRef.current;
388
+ const rows = currentTable?.getRowModel().rows;
389
+ if (!rows) return null;
390
+ const selectedColumnIds = [];
391
+ const seenColumnIds = /* @__PURE__ */ new Set();
392
+ const cellData = /* @__PURE__ */ new Map();
393
+ const rowIndices = /* @__PURE__ */ new Set();
394
+ const rowCellMaps = /* @__PURE__ */ new Map();
395
+ const navigableCells = [];
396
+ for (const cellKey of selectedCellsArray) {
397
+ const { rowIndex, columnId } = parseCellKey(cellKey);
398
+ if (columnId && NON_NAVIGABLE_COLUMN_IDS.has(columnId)) {
399
+ continue;
400
+ }
401
+ navigableCells.push(cellKey);
402
+ if (columnId && !seenColumnIds.has(columnId)) {
403
+ seenColumnIds.add(columnId);
404
+ selectedColumnIds.push(columnId);
405
+ }
406
+ rowIndices.add(rowIndex);
407
+ const row = rows[rowIndex];
408
+ if (row) {
409
+ let cellMap = rowCellMaps.get(rowIndex);
410
+ if (!cellMap) {
411
+ cellMap = new Map(row.getVisibleCells().map((c) => [c.column.id, c]));
412
+ rowCellMaps.set(rowIndex, cellMap);
413
+ }
414
+ const cell = cellMap.get(columnId);
415
+ if (cell) {
416
+ const value = cell.getValue();
417
+ const cellVariant = cell.column.columnDef?.meta?.cell?.variant;
418
+ let serializedValue = "";
419
+ if (cellVariant === "file" || cellVariant === "multi-select") {
420
+ serializedValue = value ? JSON.stringify(value) : "";
421
+ } else if (value instanceof Date) {
422
+ serializedValue = value.toISOString();
423
+ } else {
424
+ serializedValue = String(value ?? "");
425
+ }
426
+ cellData.set(cellKey, serializedValue);
427
+ }
428
+ }
429
+ }
430
+ const colIndices = /* @__PURE__ */ new Set();
431
+ for (const cellKey of navigableCells) {
432
+ const { columnId } = parseCellKey(cellKey);
433
+ const colIndex = selectedColumnIds.indexOf(columnId);
434
+ if (colIndex >= 0) {
435
+ colIndices.add(colIndex);
436
+ }
437
+ }
438
+ const sortedRowIndices = Array.from(rowIndices).sort((a, b) => a - b);
439
+ const sortedColIndices = Array.from(colIndices).sort((a, b) => a - b);
440
+ const sortedColumnIds = sortedColIndices.map((i) => selectedColumnIds[i]);
441
+ const tsvData = sortedRowIndices.map(
442
+ (rowIndex) => sortedColumnIds.map((columnId) => {
443
+ const cellKey = `${rowIndex}:${columnId}`;
444
+ return cellData.get(cellKey) ?? "";
445
+ }).join(" ")
446
+ ).join("\n");
447
+ return { tsvData, selectedCellsArray: navigableCells };
448
+ }, [store]);
449
+ const onCellsCopy = React.useCallback(async () => {
450
+ const result = serializeCellsToTsv();
451
+ if (!result) return;
452
+ const { tsvData, selectedCellsArray } = result;
453
+ try {
454
+ await writeClipboardText(tsvData);
455
+ const currentState = store.getState();
456
+ if (currentState.cutCells.size > 0) {
457
+ store.setState("cutCells", /* @__PURE__ */ new Set());
458
+ }
459
+ toastSuccess(msg.toast.cellsCopied(selectedCellsArray.length));
460
+ } catch {
461
+ toastError(msg.toast.copyFailed);
462
+ }
463
+ }, [store, serializeCellsToTsv]);
464
+ const onCellsCut = React.useCallback(async () => {
465
+ if (propsRef.current.readOnly) return;
466
+ const result = serializeCellsToTsv();
467
+ if (!result) return;
468
+ const { tsvData, selectedCellsArray } = result;
469
+ try {
470
+ await writeClipboardText(tsvData);
471
+ store.setState("cutCells", new Set(selectedCellsArray));
472
+ toastSuccess(msg.toast.cellsCut(selectedCellsArray.length));
473
+ } catch {
474
+ toastError(msg.toast.cutFailed);
475
+ }
476
+ }, [store, propsRef, serializeCellsToTsv]);
477
+ const restoreFocus = React.useCallback((element) => {
478
+ if (element && document.activeElement !== element) {
479
+ requestAnimationFrame(() => {
480
+ element.focus();
481
+ });
482
+ }
483
+ }, []);
484
+ const onCellsPaste = React.useCallback(
485
+ async (expandRows = false, clipboardTextArg) => {
486
+ if (propsRef.current.readOnly) return;
487
+ const currentState = store.getState();
488
+ if (!currentState.focusedCell) return;
489
+ const currentTable = tableRef.current;
490
+ const rows = currentTable?.getRowModel().rows;
491
+ if (!rows) return;
492
+ try {
493
+ let clipboardText = clipboardTextArg ?? currentState.pasteDialog.clipboardText;
494
+ if (!clipboardText) {
495
+ clipboardText = await readClipboardText() ?? "";
496
+ }
497
+ if (!clipboardText) return;
498
+ const rawPastedData = parseTsv(clipboardText, navigableColumnIds.length);
499
+ const selectionCells = currentState.selectionState.selectedCells;
500
+ const isSingleCellClipboard = rawPastedData.length === 1 && (rawPastedData[0]?.length ?? 0) === 1;
501
+ let pastedData = rawPastedData;
502
+ let startRowIndex = currentState.focusedCell.rowIndex;
503
+ let startColIndex = navigableColumnIds.indexOf(currentState.focusedCell.columnId);
504
+ if (isSingleCellClipboard && selectionCells.size > 1) {
505
+ const singleValue = rawPastedData[0]?.[0] ?? "";
506
+ let minRow = Infinity;
507
+ let maxRow = -Infinity;
508
+ let minColIdx = Infinity;
509
+ let maxColIdx = -Infinity;
510
+ for (const cellKey of selectionCells) {
511
+ const { rowIndex, columnId } = parseCellKey(cellKey);
512
+ const colIdx = navigableColumnIds.indexOf(columnId);
513
+ if (colIdx === -1) continue;
514
+ minRow = Math.min(minRow, rowIndex);
515
+ maxRow = Math.max(maxRow, rowIndex);
516
+ minColIdx = Math.min(minColIdx, colIdx);
517
+ maxColIdx = Math.max(maxColIdx, colIdx);
518
+ }
519
+ if (minRow !== Infinity) {
520
+ startRowIndex = minRow;
521
+ startColIndex = minColIdx;
522
+ const numRows = maxRow - minRow + 1;
523
+ const numCols = maxColIdx - minColIdx + 1;
524
+ pastedData = Array.from({ length: numRows }, () => Array.from({ length: numCols }, () => singleValue));
525
+ }
526
+ }
527
+ if (startColIndex === -1) return;
528
+ const rowCount = rows.length ?? propsRef.current.data.length;
529
+ const rowsNeeded = startRowIndex + pastedData.length - rowCount;
530
+ if (rowsNeeded > 0 && !expandRows && propsRef.current.onRowAdd && !currentState.pasteDialog.clipboardText) {
531
+ store.setState("pasteDialog", {
532
+ open: true,
533
+ rowsNeeded,
534
+ clipboardText
535
+ });
536
+ return;
537
+ }
538
+ if (expandRows && rowsNeeded > 0) {
539
+ const expectedRowCount = rowCount + rowsNeeded;
540
+ if (propsRef.current.onRowsAdd) {
541
+ await propsRef.current.onRowsAdd(rowsNeeded);
542
+ } else if (propsRef.current.onRowAdd) {
543
+ for (let i = 0; i < rowsNeeded; i++) {
544
+ await propsRef.current.onRowAdd();
545
+ }
546
+ }
547
+ let attempts = 0;
548
+ const maxAttempts = 50;
549
+ let currentTableRowCount = tableRef.current?.getRowModel().rows.length ?? 0;
550
+ while (currentTableRowCount < expectedRowCount && attempts < maxAttempts) {
551
+ await new Promise((resolve) => setTimeout(resolve, 100));
552
+ currentTableRowCount = tableRef.current?.getRowModel().rows.length ?? 0;
553
+ attempts++;
554
+ }
555
+ }
556
+ const updates = [];
557
+ const tableColumns = currentTable?.getAllColumns() ?? [];
558
+ let cellsUpdated = 0;
559
+ let endRowIndex = startRowIndex;
560
+ let endColIndex = startColIndex;
561
+ const updatedTable = tableRef.current;
562
+ const updatedRows = updatedTable?.getRowModel().rows;
563
+ const currentRowCount = updatedRows?.length ?? 0;
564
+ let cellsSkipped = 0;
565
+ const columnMap = new Map(tableColumns.map((c) => [c.id, c]));
566
+ for (let pasteRowIdx = 0; pasteRowIdx < pastedData.length; pasteRowIdx++) {
567
+ const pasteRow = pastedData[pasteRowIdx];
568
+ if (!pasteRow) continue;
569
+ const targetRowIndex = startRowIndex + pasteRowIdx;
570
+ if (targetRowIndex >= currentRowCount) break;
571
+ for (let pasteColIdx = 0; pasteColIdx < pasteRow.length; pasteColIdx++) {
572
+ const targetColIndex = startColIndex + pasteColIdx;
573
+ if (targetColIndex >= navigableColumnIds.length) break;
574
+ const targetColumnId = navigableColumnIds[targetColIndex];
575
+ if (!targetColumnId) continue;
576
+ const pastedValue = pasteRow[pasteColIdx] ?? "";
577
+ const column = columnMap.get(targetColumnId);
578
+ const cellOpts = column?.columnDef?.meta?.cell;
579
+ const cellVariant = cellOpts?.variant;
580
+ let processedValue = pastedValue;
581
+ let shouldSkip = false;
582
+ switch (cellVariant) {
583
+ case "number": {
584
+ if (!pastedValue) {
585
+ processedValue = null;
586
+ } else {
587
+ const num = Number.parseFloat(pastedValue);
588
+ if (Number.isNaN(num)) shouldSkip = true;
589
+ else processedValue = num;
590
+ }
591
+ break;
592
+ }
593
+ case "checkbox": {
594
+ if (!pastedValue) {
595
+ processedValue = false;
596
+ } else {
597
+ const lower = pastedValue.toLowerCase();
598
+ if (VALID_BOOLEANS.has(lower)) {
599
+ processedValue = TRUTHY_BOOLEANS.has(lower);
600
+ } else {
601
+ shouldSkip = true;
602
+ }
603
+ }
604
+ break;
605
+ }
606
+ case "date": {
607
+ if (!pastedValue) {
608
+ processedValue = null;
609
+ } else {
610
+ const date = new Date(pastedValue);
611
+ if (Number.isNaN(date.getTime())) shouldSkip = true;
612
+ else processedValue = date;
613
+ }
614
+ break;
615
+ }
616
+ case "select": {
617
+ const options = cellOpts?.options ?? [];
618
+ if (!pastedValue) {
619
+ processedValue = null;
620
+ } else {
621
+ const matched = matchSelectOption(pastedValue, options);
622
+ if (matched) processedValue = matched;
623
+ else shouldSkip = true;
624
+ }
625
+ break;
626
+ }
627
+ case "multi-select": {
628
+ const options = cellOpts?.options ?? [];
629
+ let values = [];
630
+ try {
631
+ const parsed = JSON.parse(pastedValue);
632
+ if (Array.isArray(parsed)) {
633
+ values = parsed.filter((v) => typeof v === "string");
634
+ }
635
+ } catch {
636
+ values = pastedValue ? pastedValue.split(",").map((v) => v.trim()) : [];
637
+ }
638
+ const validated = values.map((v) => matchSelectOption(v, options)).filter(Boolean);
639
+ if (values.length > 0 && validated.length === 0) {
640
+ shouldSkip = true;
641
+ } else {
642
+ processedValue = validated;
643
+ }
644
+ break;
645
+ }
646
+ case "file": {
647
+ if (!pastedValue) {
648
+ processedValue = [];
649
+ } else {
650
+ try {
651
+ const parsed = JSON.parse(pastedValue);
652
+ if (!Array.isArray(parsed)) {
653
+ shouldSkip = true;
654
+ } else {
655
+ const validFiles = parsed.filter(getIsFileCellData);
656
+ if (parsed.length > 0 && validFiles.length === 0) {
657
+ shouldSkip = true;
658
+ } else {
659
+ processedValue = validFiles;
660
+ }
661
+ }
662
+ } catch {
663
+ shouldSkip = true;
664
+ }
665
+ }
666
+ break;
667
+ }
668
+ case "url": {
669
+ if (!pastedValue) {
670
+ processedValue = "";
671
+ } else {
672
+ const firstChar = pastedValue[0];
673
+ if (firstChar === "[" || firstChar === "{") {
674
+ shouldSkip = true;
675
+ } else {
676
+ try {
677
+ new URL(pastedValue);
678
+ processedValue = pastedValue;
679
+ } catch {
680
+ if (DOMAIN_REGEX.test(pastedValue)) {
681
+ processedValue = pastedValue;
682
+ } else {
683
+ shouldSkip = true;
684
+ }
685
+ }
686
+ }
687
+ }
688
+ break;
689
+ }
690
+ default: {
691
+ if (!pastedValue) {
692
+ processedValue = "";
693
+ break;
694
+ }
695
+ if (ISO_DATE_REGEX.test(pastedValue)) {
696
+ const date = new Date(pastedValue);
697
+ if (!Number.isNaN(date.getTime())) {
698
+ processedValue = formatDate(date);
699
+ break;
700
+ }
701
+ }
702
+ const firstChar = pastedValue[0];
703
+ if (firstChar === "[" || firstChar === "{" || firstChar === "t" || firstChar === "f") {
704
+ try {
705
+ const parsed = JSON.parse(pastedValue);
706
+ if (Array.isArray(parsed)) {
707
+ if (parsed.length > 0 && parsed.every(getIsFileCellData)) {
708
+ processedValue = parsed.map((f) => f.name).join(", ");
709
+ } else if (parsed.every((v) => typeof v === "string")) {
710
+ processedValue = parsed.join(", ");
711
+ }
712
+ } else if (typeof parsed === "boolean") {
713
+ processedValue = parsed ? msg.booleanText.checked : msg.booleanText.unchecked;
714
+ }
715
+ } catch {
716
+ const lower = pastedValue.toLowerCase();
717
+ if (lower === "true" || lower === "false") {
718
+ processedValue = lower === "true" ? msg.booleanText.checked : msg.booleanText.unchecked;
719
+ }
720
+ }
721
+ }
722
+ }
723
+ }
724
+ if (shouldSkip) {
725
+ cellsSkipped++;
726
+ endRowIndex = Math.max(endRowIndex, targetRowIndex);
727
+ endColIndex = Math.max(endColIndex, targetColIndex);
728
+ continue;
729
+ }
730
+ updates.push({
731
+ rowIndex: targetRowIndex,
732
+ columnId: targetColumnId,
733
+ value: processedValue
734
+ });
735
+ cellsUpdated++;
736
+ endRowIndex = Math.max(endRowIndex, targetRowIndex);
737
+ endColIndex = Math.max(endColIndex, targetColIndex);
738
+ }
739
+ }
740
+ if (updates.length > 0) {
741
+ if (propsRef.current.onPaste) {
742
+ await propsRef.current.onPaste(updates);
743
+ }
744
+ const allUpdates = [...updates];
745
+ if (currentState.cutCells.size > 0) {
746
+ const columnById = new Map(tableColumns.map((c) => [c.id, c]));
747
+ for (const cellKey of currentState.cutCells) {
748
+ const { rowIndex, columnId } = parseCellKey(cellKey);
749
+ const column = columnById.get(columnId);
750
+ const cellVariant = column?.columnDef?.meta?.cell?.variant;
751
+ const emptyValue = getEmptyCellValue(cellVariant);
752
+ allUpdates.push({ rowIndex, columnId, value: emptyValue });
753
+ }
754
+ store.setState("cutCells", /* @__PURE__ */ new Set());
755
+ }
756
+ onDataUpdate(allUpdates);
757
+ if (cellsSkipped > 0) {
758
+ toastSuccess(msg.toast.cellsPastedWithSkipped(cellsUpdated, cellsSkipped));
759
+ } else {
760
+ toastSuccess(msg.toast.cellsPasted(cellsUpdated));
761
+ }
762
+ const endColumnId = navigableColumnIds[endColIndex];
763
+ if (endColumnId) {
764
+ selectRange(
765
+ {
766
+ rowIndex: startRowIndex,
767
+ columnId: currentState.focusedCell.columnId
768
+ },
769
+ { rowIndex: endRowIndex, columnId: endColumnId }
770
+ );
771
+ }
772
+ restoreFocus(dataGridRef.current);
773
+ } else if (cellsSkipped > 0) {
774
+ toastError(msg.toast.pasteSkipped(cellsSkipped));
775
+ }
776
+ if (currentState.pasteDialog.open) {
777
+ store.setState("pasteDialog", {
778
+ open: false,
779
+ rowsNeeded: 0,
780
+ clipboardText: ""
781
+ });
782
+ }
783
+ } catch {
784
+ toastError(msg.toast.pasteFailed);
785
+ }
786
+ },
787
+ [store, navigableColumnIds, propsRef, onDataUpdate, selectRange, restoreFocus]
788
+ );
789
+ const releaseFocusGuard = React.useCallback((immediate = false) => {
790
+ if (immediate) {
791
+ focusGuardRef.current = false;
792
+ return;
793
+ }
794
+ setTimeout(() => {
795
+ focusGuardRef.current = false;
796
+ }, 300);
797
+ }, []);
798
+ const focusCellWrapper = React.useCallback(
799
+ (rowIndex, columnId) => {
800
+ focusGuardRef.current = true;
801
+ requestAnimationFrame(() => {
802
+ const cellKey = getCellKey(rowIndex, columnId);
803
+ const cellWrapperElement = cellMapRef.current.get(cellKey);
804
+ if (!cellWrapperElement) {
805
+ const container = dataGridRef.current;
806
+ if (container) {
807
+ container.focus();
808
+ }
809
+ releaseFocusGuard();
810
+ return;
811
+ }
812
+ cellWrapperElement.focus();
813
+ releaseFocusGuard();
814
+ });
815
+ },
816
+ [releaseFocusGuard]
817
+ );
818
+ const focusCell = React.useCallback(
819
+ (rowIndex, columnId) => {
820
+ store.batch(() => {
821
+ store.setState("focusedCell", { rowIndex, columnId });
822
+ store.setState("editingCell", null);
823
+ });
824
+ const currentState = store.getState();
825
+ if (currentState.searchOpen) return;
826
+ focusCellWrapper(rowIndex, columnId);
827
+ },
828
+ [store, focusCellWrapper]
829
+ );
830
+ const onRowsDelete = React.useCallback(
831
+ async (rowIndices) => {
832
+ if (propsRef.current.readOnly || !propsRef.current.onRowsDelete || rowIndices.length === 0) return;
833
+ const currentTable = tableRef.current;
834
+ const rows = currentTable?.getRowModel().rows;
835
+ if (!rows || rows.length === 0) return;
836
+ const currentState = store.getState();
837
+ const currentFocusedColumn = currentState.focusedCell?.columnId ?? navigableColumnIds[0];
838
+ const minDeletedRowIndex = Math.min(...rowIndices);
839
+ const rowsToDelete = [];
840
+ for (const rowIndex of rowIndices) {
841
+ const row = rows[rowIndex];
842
+ if (row) {
843
+ rowsToDelete.push(row.original);
844
+ }
845
+ }
846
+ await propsRef.current.onRowsDelete(rowsToDelete, rowIndices);
847
+ store.batch(() => {
848
+ store.setState("selectionState", {
849
+ selectedCells: /* @__PURE__ */ new Set(),
850
+ selectionRange: null,
851
+ isSelecting: false
852
+ });
853
+ store.setState("rowSelection", {});
854
+ store.setState("editingCell", null);
855
+ });
856
+ requestAnimationFrame(() => {
857
+ const currentTable2 = tableRef.current;
858
+ const currentRows = currentTable2?.getRowModel().rows ?? [];
859
+ const newRowCount = currentRows.length ?? propsRef.current.data.length;
860
+ if (newRowCount > 0 && currentFocusedColumn) {
861
+ const targetRowIndex = Math.min(minDeletedRowIndex, newRowCount - 1);
862
+ focusCell(targetRowIndex, currentFocusedColumn);
863
+ }
864
+ });
865
+ },
866
+ [propsRef, store, navigableColumnIds, focusCell]
867
+ );
868
+ const navigateCell = React.useCallback(
869
+ (direction) => {
870
+ const currentState = store.getState();
871
+ if (!currentState.focusedCell) return;
872
+ const { rowIndex, columnId } = currentState.focusedCell;
873
+ const currentColIndex = navigableColumnIds.indexOf(columnId);
874
+ const rowVirtualizer2 = rowVirtualizerRef.current;
875
+ const currentTable = tableRef.current;
876
+ const rows = currentTable?.getRowModel().rows ?? [];
877
+ const rowCount = rows.length ?? propsRef.current.data.length;
878
+ let newRowIndex = rowIndex;
879
+ let newColumnId = columnId;
880
+ const isRtl = dir === "rtl";
881
+ switch (direction) {
882
+ case "up":
883
+ newRowIndex = Math.max(0, rowIndex - 1);
884
+ break;
885
+ case "down":
886
+ newRowIndex = Math.min(rowCount - 1, rowIndex + 1);
887
+ break;
888
+ case "left":
889
+ if (isRtl) {
890
+ if (currentColIndex < navigableColumnIds.length - 1) {
891
+ const nextColumnId = navigableColumnIds[currentColIndex + 1];
892
+ if (nextColumnId) newColumnId = nextColumnId;
893
+ }
894
+ } else {
895
+ if (currentColIndex > 0) {
896
+ const prevColumnId = navigableColumnIds[currentColIndex - 1];
897
+ if (prevColumnId) newColumnId = prevColumnId;
898
+ }
899
+ }
900
+ break;
901
+ case "right":
902
+ if (isRtl) {
903
+ if (currentColIndex > 0) {
904
+ const prevColumnId = navigableColumnIds[currentColIndex - 1];
905
+ if (prevColumnId) newColumnId = prevColumnId;
906
+ }
907
+ } else {
908
+ if (currentColIndex < navigableColumnIds.length - 1) {
909
+ const nextColumnId = navigableColumnIds[currentColIndex + 1];
910
+ if (nextColumnId) newColumnId = nextColumnId;
911
+ }
912
+ }
913
+ break;
914
+ case "home":
915
+ if (navigableColumnIds.length > 0) {
916
+ newColumnId = navigableColumnIds[0] ?? columnId;
917
+ }
918
+ break;
919
+ case "end":
920
+ if (navigableColumnIds.length > 0) {
921
+ newColumnId = navigableColumnIds[navigableColumnIds.length - 1] ?? columnId;
922
+ }
923
+ break;
924
+ case "ctrl+home":
925
+ newRowIndex = 0;
926
+ if (navigableColumnIds.length > 0) {
927
+ newColumnId = navigableColumnIds[0] ?? columnId;
928
+ }
929
+ break;
930
+ case "ctrl+end":
931
+ newRowIndex = Math.max(0, rowCount - 1);
932
+ if (navigableColumnIds.length > 0) {
933
+ newColumnId = navigableColumnIds[navigableColumnIds.length - 1] ?? columnId;
934
+ }
935
+ break;
936
+ case "ctrl+up":
937
+ newRowIndex = 0;
938
+ break;
939
+ case "ctrl+down":
940
+ newRowIndex = Math.max(0, rowCount - 1);
941
+ break;
942
+ case "pageup":
943
+ if (rowVirtualizer2) {
944
+ const visibleRange = rowVirtualizer2.getVirtualItems();
945
+ const pageSize = visibleRange.length ?? 10;
946
+ newRowIndex = Math.max(0, rowIndex - pageSize);
947
+ } else {
948
+ newRowIndex = Math.max(0, rowIndex - 10);
949
+ }
950
+ break;
951
+ case "pagedown":
952
+ if (rowVirtualizer2) {
953
+ const visibleRange = rowVirtualizer2.getVirtualItems();
954
+ const pageSize = visibleRange.length ?? 10;
955
+ newRowIndex = Math.min(rowCount - 1, rowIndex + pageSize);
956
+ } else {
957
+ newRowIndex = Math.min(rowCount - 1, rowIndex + 10);
958
+ }
959
+ break;
960
+ case "pageleft":
961
+ if (currentColIndex > 0) {
962
+ const targetIndex = Math.max(0, currentColIndex - HORIZONTAL_PAGE_SIZE);
963
+ const targetColumnId = navigableColumnIds[targetIndex];
964
+ if (targetColumnId) newColumnId = targetColumnId;
965
+ }
966
+ break;
967
+ case "pageright":
968
+ if (currentColIndex < navigableColumnIds.length - 1) {
969
+ const targetIndex = Math.min(navigableColumnIds.length - 1, currentColIndex + HORIZONTAL_PAGE_SIZE);
970
+ const targetColumnId = navigableColumnIds[targetIndex];
971
+ if (targetColumnId) newColumnId = targetColumnId;
972
+ }
973
+ break;
974
+ }
975
+ if (newRowIndex !== rowIndex || newColumnId !== columnId) {
976
+ focusCell(newRowIndex, newColumnId);
977
+ const container = dataGridRef.current;
978
+ if (!container) return;
979
+ const targetRow = rowMapRef.current.get(newRowIndex);
980
+ const cellKey = getCellKey(newRowIndex, newColumnId);
981
+ const targetCell = cellMapRef.current.get(cellKey);
982
+ if (!targetRow) {
983
+ if (rowVirtualizer2) {
984
+ const align = direction === "up" || direction === "pageup" || direction === "ctrl+up" || direction === "ctrl+home" ? "start" : direction === "down" || direction === "pagedown" || direction === "ctrl+down" || direction === "ctrl+end" ? "end" : "center";
985
+ rowVirtualizer2.scrollToIndex(newRowIndex, { align });
986
+ if (newColumnId !== columnId) {
987
+ requestAnimationFrame(() => {
988
+ const cellKeyRetry = getCellKey(newRowIndex, newColumnId);
989
+ const targetCellRetry = cellMapRef.current.get(cellKeyRetry);
990
+ if (targetCellRetry) {
991
+ const scrollDirection = getScrollDirection(direction);
992
+ scrollCellIntoView({
993
+ container,
994
+ targetCell: targetCellRetry,
995
+ tableRef,
996
+ viewportOffset: VIEWPORT_OFFSET,
997
+ direction: scrollDirection,
998
+ isRtl: dir === "rtl"
999
+ });
1000
+ }
1001
+ });
1002
+ }
1003
+ } else {
1004
+ const rowHeightValue2 = getRowHeightValue(rowHeight);
1005
+ const estimatedScrollTop = newRowIndex * rowHeightValue2;
1006
+ container.scrollTop = estimatedScrollTop;
1007
+ }
1008
+ return;
1009
+ }
1010
+ if (newRowIndex !== rowIndex && targetRow) {
1011
+ requestAnimationFrame(() => {
1012
+ const containerRect = container.getBoundingClientRect();
1013
+ const headerHeight = headerRef.current?.getBoundingClientRect().height ?? 0;
1014
+ const footerHeight = footerRef.current?.getBoundingClientRect().height ?? 0;
1015
+ const viewportTop = containerRect.top + headerHeight + VIEWPORT_OFFSET;
1016
+ const viewportBottom = containerRect.bottom - footerHeight - VIEWPORT_OFFSET;
1017
+ const rowRect = targetRow.getBoundingClientRect();
1018
+ const isFullyVisible = rowRect.top >= viewportTop && rowRect.bottom <= viewportBottom;
1019
+ if (!isFullyVisible) {
1020
+ const isVerticalNavigation = direction === "up" || direction === "down" || direction === "pageup" || direction === "pagedown" || direction === "ctrl+up" || direction === "ctrl+down" || direction === "ctrl+home" || direction === "ctrl+end";
1021
+ if (isVerticalNavigation) {
1022
+ if (direction === "down" || direction === "pagedown" || direction === "ctrl+down" || direction === "ctrl+end") {
1023
+ container.scrollTop += rowRect.bottom - viewportBottom;
1024
+ } else {
1025
+ container.scrollTop -= viewportTop - rowRect.top;
1026
+ }
1027
+ }
1028
+ }
1029
+ });
1030
+ }
1031
+ if (newColumnId !== columnId && targetCell) {
1032
+ requestAnimationFrame(() => {
1033
+ const scrollDirection = getScrollDirection(direction);
1034
+ scrollCellIntoView({
1035
+ container,
1036
+ targetCell,
1037
+ tableRef,
1038
+ viewportOffset: VIEWPORT_OFFSET,
1039
+ direction: scrollDirection,
1040
+ isRtl: dir === "rtl"
1041
+ });
1042
+ });
1043
+ }
1044
+ }
1045
+ },
1046
+ [dir, store, navigableColumnIds, focusCell, propsRef, rowHeight]
1047
+ );
1048
+ const onCellEditingStart = React.useCallback(
1049
+ (rowIndex, columnId) => {
1050
+ if (propsRef.current.readOnly) return;
1051
+ store.batch(() => {
1052
+ store.setState("focusedCell", { rowIndex, columnId });
1053
+ store.setState("editingCell", { rowIndex, columnId });
1054
+ });
1055
+ },
1056
+ [store, propsRef]
1057
+ );
1058
+ const onCellEditingStop = React.useCallback(
1059
+ (opts) => {
1060
+ const currentState = store.getState();
1061
+ const currentEditing = currentState.editingCell;
1062
+ store.setState("editingCell", null);
1063
+ if (opts?.moveToNextRow && currentEditing) {
1064
+ const { rowIndex, columnId } = currentEditing;
1065
+ const currentTable = tableRef.current;
1066
+ const rows = currentTable?.getRowModel().rows ?? [];
1067
+ const rowCount = rows.length ?? propsRef.current.data.length;
1068
+ const nextRowIndex = rowIndex + 1;
1069
+ if (nextRowIndex < rowCount) {
1070
+ requestAnimationFrame(() => {
1071
+ focusCell(nextRowIndex, columnId);
1072
+ });
1073
+ }
1074
+ } else if (opts?.direction && currentEditing) {
1075
+ const { rowIndex, columnId } = currentEditing;
1076
+ focusCell(rowIndex, columnId);
1077
+ requestAnimationFrame(() => {
1078
+ navigateCell(opts.direction ?? "right");
1079
+ });
1080
+ } else if (currentEditing) {
1081
+ const { rowIndex, columnId } = currentEditing;
1082
+ focusCellWrapper(rowIndex, columnId);
1083
+ }
1084
+ },
1085
+ [store, propsRef, focusCell, navigateCell, focusCellWrapper]
1086
+ );
1087
+ const onSearchOpenChange = React.useCallback(
1088
+ (open) => {
1089
+ if (open) {
1090
+ store.setState("searchOpen", true);
1091
+ return;
1092
+ }
1093
+ const currentState = store.getState();
1094
+ const currentMatch = currentState.matchIndex >= 0 && currentState.searchMatches[currentState.matchIndex];
1095
+ store.batch(() => {
1096
+ store.setState("searchOpen", false);
1097
+ store.setState("searchQuery", "");
1098
+ store.setState("searchMatches", []);
1099
+ store.setState("matchIndex", -1);
1100
+ if (currentMatch) {
1101
+ store.setState("focusedCell", {
1102
+ rowIndex: currentMatch.rowIndex,
1103
+ columnId: currentMatch.columnId
1104
+ });
1105
+ }
1106
+ });
1107
+ if (dataGridRef.current && document.activeElement !== dataGridRef.current) {
1108
+ dataGridRef.current.focus();
1109
+ }
1110
+ },
1111
+ [store]
1112
+ );
1113
+ const onSearch = React.useCallback(
1114
+ (query) => {
1115
+ if (!query.trim()) {
1116
+ store.batch(() => {
1117
+ store.setState("searchMatches", []);
1118
+ store.setState("matchIndex", -1);
1119
+ });
1120
+ return;
1121
+ }
1122
+ const matches = [];
1123
+ const currentTable = tableRef.current;
1124
+ const rows = currentTable?.getRowModel().rows ?? [];
1125
+ const lowerQuery = query.toLowerCase();
1126
+ for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) {
1127
+ const row = rows[rowIndex];
1128
+ if (!row) continue;
1129
+ const cellById = new Map(row.getVisibleCells().map((c) => [c.column.id, c]));
1130
+ for (const columnId of columnIds) {
1131
+ const cell = cellById.get(columnId);
1132
+ if (!cell) continue;
1133
+ const value = cell.getValue();
1134
+ const stringValue = String(value ?? "").toLowerCase();
1135
+ if (stringValue.includes(lowerQuery)) {
1136
+ matches.push({ rowIndex, columnId });
1137
+ }
1138
+ }
1139
+ }
1140
+ store.batch(() => {
1141
+ store.setState("searchMatches", matches);
1142
+ store.setState("matchIndex", matches.length > 0 ? 0 : -1);
1143
+ });
1144
+ if (matches.length > 0 && matches[0]) {
1145
+ const firstMatch = matches[0];
1146
+ rowVirtualizerRef.current?.scrollToIndex(firstMatch.rowIndex, {
1147
+ align: "center"
1148
+ });
1149
+ }
1150
+ },
1151
+ [columnIds, store]
1152
+ );
1153
+ const onSearchQueryChange = React.useCallback((query) => store.setState("searchQuery", query), [store]);
1154
+ const onNavigateToPrevMatch = React.useCallback(() => {
1155
+ const currentState = store.getState();
1156
+ if (currentState.searchMatches.length === 0) return;
1157
+ const prevIndex = currentState.matchIndex - 1 < 0 ? currentState.searchMatches.length - 1 : currentState.matchIndex - 1;
1158
+ const match = currentState.searchMatches[prevIndex];
1159
+ if (match) {
1160
+ rowVirtualizerRef.current?.scrollToIndex(match.rowIndex, {
1161
+ align: "center"
1162
+ });
1163
+ requestAnimationFrame(() => {
1164
+ store.setState("matchIndex", prevIndex);
1165
+ requestAnimationFrame(() => {
1166
+ focusCell(match.rowIndex, match.columnId);
1167
+ });
1168
+ });
1169
+ }
1170
+ }, [store, focusCell]);
1171
+ const onNavigateToNextMatch = React.useCallback(() => {
1172
+ const currentState = store.getState();
1173
+ if (currentState.searchMatches.length === 0) return;
1174
+ const nextIndex = (currentState.matchIndex + 1) % currentState.searchMatches.length;
1175
+ const match = currentState.searchMatches[nextIndex];
1176
+ if (match) {
1177
+ rowVirtualizerRef.current?.scrollToIndex(match.rowIndex, {
1178
+ align: "center"
1179
+ });
1180
+ requestAnimationFrame(() => {
1181
+ store.setState("matchIndex", nextIndex);
1182
+ requestAnimationFrame(() => {
1183
+ focusCell(match.rowIndex, match.columnId);
1184
+ });
1185
+ });
1186
+ }
1187
+ }, [store, focusCell]);
1188
+ const searchMatchSet = React.useMemo(() => {
1189
+ return new Set(searchMatches.map((m) => getCellKey(m.rowIndex, m.columnId)));
1190
+ }, [searchMatches]);
1191
+ const getIsSearchMatch = React.useCallback(
1192
+ (rowIndex, columnId) => {
1193
+ return searchMatchSet.has(getCellKey(rowIndex, columnId));
1194
+ },
1195
+ [searchMatchSet]
1196
+ );
1197
+ const getIsActiveSearchMatch = React.useCallback(
1198
+ (rowIndex, columnId) => {
1199
+ const currentState = store.getState();
1200
+ if (currentState.matchIndex < 0) return false;
1201
+ const currentMatch = currentState.searchMatches[currentState.matchIndex];
1202
+ return currentMatch?.rowIndex === rowIndex && currentMatch?.columnId === columnId;
1203
+ },
1204
+ [store]
1205
+ );
1206
+ const searchMatchesByRow = React.useMemo(() => {
1207
+ if (searchMatches.length === 0) return null;
1208
+ const rowMap = /* @__PURE__ */ new Map();
1209
+ for (const match of searchMatches) {
1210
+ let columnSet = rowMap.get(match.rowIndex);
1211
+ if (!columnSet) {
1212
+ columnSet = /* @__PURE__ */ new Set();
1213
+ rowMap.set(match.rowIndex, columnSet);
1214
+ }
1215
+ columnSet.add(match.columnId);
1216
+ }
1217
+ return rowMap;
1218
+ }, [searchMatches]);
1219
+ const activeSearchMatch = React.useMemo(() => {
1220
+ if (matchIndex < 0 || searchMatches.length === 0) return null;
1221
+ return searchMatches[matchIndex] ?? null;
1222
+ }, [searchMatches, matchIndex]);
1223
+ const blurCell = React.useCallback(() => {
1224
+ const currentState = store.getState();
1225
+ if (currentState.editingCell && document.activeElement instanceof HTMLElement) {
1226
+ document.activeElement.blur();
1227
+ }
1228
+ store.batch(() => {
1229
+ store.setState("focusedCell", null);
1230
+ store.setState("editingCell", null);
1231
+ });
1232
+ }, [store]);
1233
+ const scrollToCell = React.useCallback(
1234
+ (rowIndex, columnId) => {
1235
+ requestAnimationFrame(() => {
1236
+ const container = dataGridRef.current;
1237
+ const cellKey = getCellKey(rowIndex, columnId);
1238
+ const targetCell = cellMapRef.current.get(cellKey);
1239
+ if (container && targetCell) {
1240
+ scrollCellIntoView({
1241
+ container,
1242
+ targetCell,
1243
+ tableRef,
1244
+ viewportOffset: VIEWPORT_OFFSET,
1245
+ isRtl: dir === "rtl"
1246
+ });
1247
+ }
1248
+ });
1249
+ },
1250
+ [dir]
1251
+ );
1252
+ const onCellClick = React.useCallback(
1253
+ (rowIndex, columnId, event) => {
1254
+ if (event?.button === 2) return;
1255
+ const currentState = store.getState();
1256
+ const currentFocused = currentState.focusedCell;
1257
+ if (event) {
1258
+ if (event.ctrlKey || event.metaKey) {
1259
+ event.preventDefault();
1260
+ const cellKey = getCellKey(rowIndex, columnId);
1261
+ const newSelectedCells = new Set(currentState.selectionState.selectedCells);
1262
+ if (newSelectedCells.has(cellKey)) {
1263
+ newSelectedCells.delete(cellKey);
1264
+ } else {
1265
+ newSelectedCells.add(cellKey);
1266
+ }
1267
+ store.setState("selectionState", {
1268
+ selectedCells: newSelectedCells,
1269
+ selectionRange: null,
1270
+ isSelecting: false
1271
+ });
1272
+ focusCell(rowIndex, columnId);
1273
+ scrollToCell(rowIndex, columnId);
1274
+ return;
1275
+ }
1276
+ if (event.shiftKey && currentState.focusedCell) {
1277
+ event.preventDefault();
1278
+ selectRange(currentState.focusedCell, { rowIndex, columnId });
1279
+ scrollToCell(rowIndex, columnId);
1280
+ return;
1281
+ }
1282
+ }
1283
+ const hasSelectedCells = currentState.selectionState.selectedCells.size > 0;
1284
+ const hasSelectedRows = Object.keys(currentState.rowSelection).length > 0;
1285
+ if (hasSelectedCells && !currentState.selectionState.isSelecting) {
1286
+ const cellKey = getCellKey(rowIndex, columnId);
1287
+ const isClickingSelectedCell = currentState.selectionState.selectedCells.has(cellKey);
1288
+ if (!isClickingSelectedCell) {
1289
+ onSelectionClear();
1290
+ } else {
1291
+ focusCell(rowIndex, columnId);
1292
+ scrollToCell(rowIndex, columnId);
1293
+ return;
1294
+ }
1295
+ } else if (hasSelectedRows && columnId !== "select") {
1296
+ onSelectionClear();
1297
+ }
1298
+ if (currentFocused?.rowIndex === rowIndex && currentFocused?.columnId === columnId) {
1299
+ onCellEditingStart(rowIndex, columnId);
1300
+ } else {
1301
+ focusCell(rowIndex, columnId);
1302
+ scrollToCell(rowIndex, columnId);
1303
+ }
1304
+ },
1305
+ [store, focusCell, scrollToCell, onCellEditingStart, selectRange, onSelectionClear]
1306
+ );
1307
+ const onCellDoubleClick = React.useCallback(
1308
+ (rowIndex, columnId, event) => {
1309
+ if (event?.defaultPrevented) return;
1310
+ onCellEditingStart(rowIndex, columnId);
1311
+ },
1312
+ [onCellEditingStart]
1313
+ );
1314
+ const onCellMouseDown = React.useCallback(
1315
+ (rowIndex, columnId, event) => {
1316
+ if (event.button === 2) {
1317
+ return;
1318
+ }
1319
+ event.preventDefault();
1320
+ if (!event.ctrlKey && !event.metaKey && !event.shiftKey) {
1321
+ const cellKey = getCellKey(rowIndex, columnId);
1322
+ store.batch(() => {
1323
+ store.setState("selectionState", {
1324
+ selectedCells: propsRef.current.enableSingleCellSelection ? /* @__PURE__ */ new Set([cellKey]) : /* @__PURE__ */ new Set(),
1325
+ selectionRange: {
1326
+ start: { rowIndex, columnId },
1327
+ end: { rowIndex, columnId }
1328
+ },
1329
+ isSelecting: true
1330
+ });
1331
+ store.setState("rowSelection", {});
1332
+ });
1333
+ }
1334
+ },
1335
+ [store, propsRef]
1336
+ );
1337
+ const onCellMouseEnter = React.useCallback(
1338
+ (rowIndex, columnId) => {
1339
+ const currentState = store.getState();
1340
+ if (currentState.selectionState.isSelecting && currentState.selectionState.selectionRange) {
1341
+ const start = currentState.selectionState.selectionRange.start;
1342
+ const end = { rowIndex, columnId };
1343
+ if (currentState.focusedCell?.rowIndex !== start.rowIndex || currentState.focusedCell?.columnId !== start.columnId) {
1344
+ focusCell(start.rowIndex, start.columnId);
1345
+ }
1346
+ selectRange(start, end, true);
1347
+ }
1348
+ },
1349
+ [store, selectRange, focusCell]
1350
+ );
1351
+ const onCellMouseUp = React.useCallback(() => {
1352
+ const currentState = store.getState();
1353
+ store.setState("selectionState", {
1354
+ ...currentState.selectionState,
1355
+ isSelecting: false
1356
+ });
1357
+ }, [store]);
1358
+ const onCellContextMenu = React.useCallback(
1359
+ (rowIndex, columnId, event) => {
1360
+ event.preventDefault();
1361
+ event.stopPropagation();
1362
+ const currentState = store.getState();
1363
+ const cellKey = getCellKey(rowIndex, columnId);
1364
+ const isTargetCellSelected = currentState.selectionState.selectedCells.has(cellKey);
1365
+ if (!isTargetCellSelected) {
1366
+ store.batch(() => {
1367
+ store.setState("selectionState", {
1368
+ selectedCells: /* @__PURE__ */ new Set([cellKey]),
1369
+ selectionRange: {
1370
+ start: { rowIndex, columnId },
1371
+ end: { rowIndex, columnId }
1372
+ },
1373
+ isSelecting: false
1374
+ });
1375
+ store.setState("focusedCell", { rowIndex, columnId });
1376
+ });
1377
+ }
1378
+ store.setState("contextMenu", {
1379
+ open: true,
1380
+ x: event.clientX,
1381
+ y: event.clientY
1382
+ });
1383
+ },
1384
+ [store]
1385
+ );
1386
+ const onContextMenuOpenChange = React.useCallback(
1387
+ (open) => {
1388
+ if (!open) {
1389
+ const currentMenu = store.getState().contextMenu;
1390
+ store.setState("contextMenu", {
1391
+ open: false,
1392
+ x: currentMenu.x,
1393
+ y: currentMenu.y
1394
+ });
1395
+ }
1396
+ },
1397
+ [store]
1398
+ );
1399
+ const onSortingChange = React.useCallback(
1400
+ (updater) => {
1401
+ const currentState = store.getState();
1402
+ const newSorting = typeof updater === "function" ? updater(currentState.sorting) : updater;
1403
+ store.setState("sorting", newSorting);
1404
+ propsRef.current.onSortingChange?.(newSorting);
1405
+ },
1406
+ [store, propsRef]
1407
+ );
1408
+ const onColumnFiltersChange = React.useCallback(
1409
+ (updater) => {
1410
+ const currentState = store.getState();
1411
+ const newColumnFilters = typeof updater === "function" ? updater(currentState.columnFilters) : updater;
1412
+ store.setState("columnFilters", newColumnFilters);
1413
+ propsRef.current.onColumnFiltersChange?.(newColumnFilters);
1414
+ },
1415
+ [store, propsRef]
1416
+ );
1417
+ const onRowSelectionChange = React.useCallback(
1418
+ (updater) => {
1419
+ const currentState = store.getState();
1420
+ const newRowSelection = typeof updater === "function" ? updater(currentState.rowSelection) : updater;
1421
+ const selectedRows = Object.keys(newRowSelection).filter((key) => newRowSelection[key]);
1422
+ const selectedCells = /* @__PURE__ */ new Set();
1423
+ const rows = tableRef.current?.getRowModel().rows ?? [];
1424
+ for (const rowId of selectedRows) {
1425
+ const rowIndex = rows.findIndex((r) => r.id === rowId);
1426
+ if (rowIndex === -1) continue;
1427
+ for (const columnId of columnIds) {
1428
+ selectedCells.add(getCellKey(rowIndex, columnId));
1429
+ }
1430
+ }
1431
+ store.batch(() => {
1432
+ store.setState("rowSelection", newRowSelection);
1433
+ store.setState("selectionState", {
1434
+ selectedCells,
1435
+ selectionRange: null,
1436
+ isSelecting: false
1437
+ });
1438
+ store.setState("focusedCell", null);
1439
+ store.setState("editingCell", null);
1440
+ });
1441
+ propsRef.current.onRowSelectionChange?.(updater);
1442
+ },
1443
+ [store, columnIds, propsRef]
1444
+ );
1445
+ const onRowSelect = React.useCallback(
1446
+ (rowId, selected, shiftKey) => {
1447
+ const currentState = store.getState();
1448
+ const rows = tableRef.current?.getRowModel().rows ?? [];
1449
+ const currentRowIndex = rows.findIndex((r) => r.id === rowId);
1450
+ const currentRow = currentRowIndex >= 0 ? rows[currentRowIndex] : null;
1451
+ if (!currentRow) return;
1452
+ if (shiftKey && currentState.lastClickedRowId !== null) {
1453
+ const lastClickedRowIndex = rows.findIndex((r) => r.id === currentState.lastClickedRowId);
1454
+ if (lastClickedRowIndex >= 0) {
1455
+ const startIndex = Math.min(lastClickedRowIndex, currentRowIndex);
1456
+ const endIndex = Math.max(lastClickedRowIndex, currentRowIndex);
1457
+ const newRowSelection = {
1458
+ ...currentState.rowSelection
1459
+ };
1460
+ for (let i = startIndex; i <= endIndex; i++) {
1461
+ const row = rows[i];
1462
+ if (row) {
1463
+ newRowSelection[row.id] = selected;
1464
+ }
1465
+ }
1466
+ onRowSelectionChange(newRowSelection);
1467
+ } else {
1468
+ onRowSelectionChange({
1469
+ ...currentState.rowSelection,
1470
+ [currentRow.id]: selected
1471
+ });
1472
+ }
1473
+ } else {
1474
+ onRowSelectionChange({
1475
+ ...currentState.rowSelection,
1476
+ [currentRow.id]: selected
1477
+ });
1478
+ }
1479
+ store.setState("lastClickedRowId", rowId);
1480
+ },
1481
+ [store, onRowSelectionChange]
1482
+ );
1483
+ const onRowHeightChange = React.useCallback(
1484
+ (updater) => {
1485
+ const currentState = store.getState();
1486
+ const newRowHeight = typeof updater === "function" ? updater(currentState.rowHeight) : updater;
1487
+ store.setState("rowHeight", newRowHeight);
1488
+ propsRef.current.onRowHeightChange?.(newRowHeight);
1489
+ },
1490
+ [store, propsRef]
1491
+ );
1492
+ const onColumnClick = React.useCallback(
1493
+ (columnId) => {
1494
+ if (!propsRef.current.enableColumnSelection) {
1495
+ onSelectionClear();
1496
+ return;
1497
+ }
1498
+ selectColumn(columnId);
1499
+ },
1500
+ [propsRef, selectColumn, onSelectionClear]
1501
+ );
1502
+ const onPasteDialogOpenChange = React.useCallback(
1503
+ (open) => {
1504
+ if (!open) {
1505
+ store.setState("pasteDialog", {
1506
+ open: false,
1507
+ rowsNeeded: 0,
1508
+ clipboardText: ""
1509
+ });
1510
+ }
1511
+ },
1512
+ [store]
1513
+ );
1514
+ const defaultColumn = React.useMemo(
1515
+ () => ({
1516
+ // Cell is rendered directly in DataGridRow to bypass flexRender's
1517
+ // unstable cell.getContext() (https://github.com/TanStack/table/issues/4794)
1518
+ minSize: MIN_COLUMN_SIZE,
1519
+ maxSize: MAX_COLUMN_SIZE
1520
+ }),
1521
+ []
1522
+ );
1523
+ const hasRowAdd = Boolean(props.onRowAdd);
1524
+ const hasRowsDelete = Boolean(props.onRowsDelete);
1525
+ const hasFilesUpload = Boolean(props.onFilesUpload);
1526
+ const hasFilesDelete = Boolean(props.onFilesDelete);
1527
+ const onFilesUpload = React.useCallback(
1528
+ (params) => {
1529
+ const upload = propsRef.current.onFilesUpload;
1530
+ return upload ? upload(params) : Promise.resolve([]);
1531
+ },
1532
+ [propsRef]
1533
+ );
1534
+ const onFilesDelete = React.useCallback(
1535
+ async (params) => {
1536
+ await propsRef.current.onFilesDelete?.(params);
1537
+ },
1538
+ [propsRef]
1539
+ );
1540
+ const tableMeta = React.useMemo(() => {
1541
+ return {
1542
+ ...propsRef.current.meta,
1543
+ dataGridRef,
1544
+ cellMapRef,
1545
+ get focusedCell() {
1546
+ return store.getState().focusedCell;
1547
+ },
1548
+ get editingCell() {
1549
+ return store.getState().editingCell;
1550
+ },
1551
+ get selectionState() {
1552
+ return store.getState().selectionState;
1553
+ },
1554
+ get searchOpen() {
1555
+ return store.getState().searchOpen;
1556
+ },
1557
+ get contextMenu() {
1558
+ return store.getState().contextMenu;
1559
+ },
1560
+ get pasteDialog() {
1561
+ return store.getState().pasteDialog;
1562
+ },
1563
+ get rowHeight() {
1564
+ return store.getState().rowHeight;
1565
+ },
1566
+ get readOnly() {
1567
+ return propsRef.current.readOnly;
1568
+ },
1569
+ getIsCellSelected,
1570
+ getIsSearchMatch,
1571
+ getIsActiveSearchMatch,
1572
+ getVisualRowIndex,
1573
+ scrollToCell: (rowIndex, columnId, align = "auto") => {
1574
+ const container = dataGridRef.current;
1575
+ if (!container) return;
1576
+ rowVirtualizerRef.current?.scrollToIndex(rowIndex, { align });
1577
+ const scrollRowIntoView = (retries = 1) => {
1578
+ requestAnimationFrame(() => {
1579
+ const targetRow = rowMapRef.current.get(rowIndex);
1580
+ if (!targetRow) {
1581
+ if (retries > 0) scrollRowIntoView(retries - 1);
1582
+ return;
1583
+ }
1584
+ const headerBottom = headerRef.current?.getBoundingClientRect().bottom ?? container.getBoundingClientRect().top;
1585
+ const viewportTop = headerBottom + VIEWPORT_OFFSET;
1586
+ const rowRect = targetRow.getBoundingClientRect();
1587
+ if (rowRect.top < viewportTop) {
1588
+ container.scrollTop -= viewportTop - rowRect.top;
1589
+ }
1590
+ const cellKey = getCellKey(rowIndex, columnId);
1591
+ const targetCell = cellMapRef.current.get(cellKey);
1592
+ if (targetCell) {
1593
+ scrollCellIntoView({
1594
+ container,
1595
+ targetCell,
1596
+ tableRef,
1597
+ viewportOffset: VIEWPORT_OFFSET,
1598
+ isRtl: dir === "rtl"
1599
+ });
1600
+ }
1601
+ });
1602
+ };
1603
+ scrollRowIntoView();
1604
+ },
1605
+ onRowHeightChange,
1606
+ onRowSelect,
1607
+ onDataUpdate,
1608
+ onRowsDelete: hasRowsDelete ? onRowsDelete : void 0,
1609
+ onColumnClick,
1610
+ onCellClick,
1611
+ onCellDoubleClick,
1612
+ onCellMouseDown,
1613
+ onCellMouseEnter,
1614
+ onCellMouseUp,
1615
+ onCellContextMenu,
1616
+ onCellEditingStart,
1617
+ onCellEditingStop,
1618
+ onCellsCopy,
1619
+ onCellsCut,
1620
+ onCellsPaste,
1621
+ onSelectionClear,
1622
+ onFilesUpload: hasFilesUpload ? onFilesUpload : void 0,
1623
+ onFilesDelete: hasFilesDelete ? onFilesDelete : void 0,
1624
+ onContextMenuOpenChange,
1625
+ onPasteDialogOpenChange
1626
+ };
1627
+ }, [
1628
+ propsRef,
1629
+ store,
1630
+ dir,
1631
+ getIsCellSelected,
1632
+ getIsSearchMatch,
1633
+ getIsActiveSearchMatch,
1634
+ getVisualRowIndex,
1635
+ onRowHeightChange,
1636
+ onRowSelect,
1637
+ onDataUpdate,
1638
+ onRowsDelete,
1639
+ onColumnClick,
1640
+ onCellClick,
1641
+ onCellDoubleClick,
1642
+ onCellMouseDown,
1643
+ onCellMouseEnter,
1644
+ onCellMouseUp,
1645
+ onCellContextMenu,
1646
+ onCellEditingStart,
1647
+ onCellEditingStop,
1648
+ onCellsCopy,
1649
+ onCellsCut,
1650
+ onCellsPaste,
1651
+ onSelectionClear,
1652
+ onContextMenuOpenChange,
1653
+ onPasteDialogOpenChange,
1654
+ hasRowsDelete,
1655
+ hasFilesUpload,
1656
+ hasFilesDelete,
1657
+ onFilesUpload,
1658
+ onFilesDelete
1659
+ ]);
1660
+ const getMemoizedCoreRowModel = React.useMemo(() => getCoreRowModel(), []);
1661
+ const getMemoizedFilteredRowModel = React.useMemo(() => getFilteredRowModel(), []);
1662
+ const getMemoizedSortedRowModel = React.useMemo(() => getSortedRowModel(), []);
1663
+ const tableState = React.useMemo(
1664
+ () => ({
1665
+ ...propsRef.current.state,
1666
+ sorting,
1667
+ columnFilters,
1668
+ rowSelection
1669
+ }),
1670
+ [propsRef, sorting, columnFilters, rowSelection]
1671
+ );
1672
+ const tableOptions = React.useMemo(() => {
1673
+ return {
1674
+ ...propsRef.current,
1675
+ data,
1676
+ columns,
1677
+ defaultColumn,
1678
+ initialState: propsRef.current.initialState,
1679
+ state: tableState,
1680
+ onRowSelectionChange,
1681
+ onSortingChange,
1682
+ onColumnFiltersChange,
1683
+ columnResizeMode: "onChange",
1684
+ columnResizeDirection: dir,
1685
+ getCoreRowModel: getMemoizedCoreRowModel,
1686
+ getFilteredRowModel: getMemoizedFilteredRowModel,
1687
+ getSortedRowModel: getMemoizedSortedRowModel,
1688
+ meta: tableMeta
1689
+ };
1690
+ }, [
1691
+ propsRef,
1692
+ data,
1693
+ columns,
1694
+ defaultColumn,
1695
+ tableState,
1696
+ dir,
1697
+ onRowSelectionChange,
1698
+ onSortingChange,
1699
+ onColumnFiltersChange,
1700
+ getMemoizedCoreRowModel,
1701
+ getMemoizedFilteredRowModel,
1702
+ getMemoizedSortedRowModel,
1703
+ tableMeta
1704
+ ]);
1705
+ const table = useReactTable(tableOptions);
1706
+ if (!tableRef.current) {
1707
+ tableRef.current = table;
1708
+ }
1709
+ const columnSizeVars = React.useMemo(() => {
1710
+ const headers = table.getFlatHeaders();
1711
+ const colSizes = {};
1712
+ for (const header of headers) {
1713
+ colSizes[`--header-${header.id}-size`] = header.getSize();
1714
+ colSizes[`--col-${header.column.id}-size`] = header.column.getSize();
1715
+ }
1716
+ return colSizes;
1717
+ }, [table.getState().columnSizingInfo, table.getState().columnSizing]);
1718
+ const isFirefox = React.useSyncExternalStore(
1719
+ React.useCallback(() => () => {
1720
+ }, []),
1721
+ React.useCallback(() => {
1722
+ if (typeof window === "undefined" || typeof navigator === "undefined") {
1723
+ return false;
1724
+ }
1725
+ return navigator.userAgent.indexOf("Firefox") !== -1;
1726
+ }, []),
1727
+ React.useCallback(() => false, [])
1728
+ );
1729
+ const adjustLayout = React.useMemo(() => {
1730
+ const columnPinning = table.getState().columnPinning;
1731
+ return isFirefox && ((columnPinning.left?.length ?? 0) > 0 || (columnPinning.right?.length ?? 0) > 0);
1732
+ }, [isFirefox, table.getState().columnPinning]);
1733
+ const rowVirtualizer = useVirtualizer({
1734
+ count: table.getRowModel().rows.length,
1735
+ getScrollElement: () => dataGridRef.current,
1736
+ estimateSize: () => rowHeightValue,
1737
+ overscan,
1738
+ measureElement: !isFirefox ? (element) => element?.getBoundingClientRect().height : void 0,
1739
+ scrollPaddingStart: (headerRef.current?.getBoundingClientRect().bottom ?? 0) - (dataGridRef.current?.getBoundingClientRect().top ?? 0) + VIEWPORT_OFFSET,
1740
+ // Add extra row buffer to absorb virtual position drift after render measurements
1741
+ scrollPaddingEnd: (dataGridRef.current?.getBoundingClientRect().bottom ?? 0) - (footerRef.current?.getBoundingClientRect().top ?? dataGridRef.current?.getBoundingClientRect().bottom ?? 0) + rowHeightValue + VIEWPORT_OFFSET
1742
+ });
1743
+ if (!rowVirtualizerRef.current) {
1744
+ rowVirtualizerRef.current = rowVirtualizer;
1745
+ }
1746
+ const onScrollToRow = React.useCallback(
1747
+ async (opts) => {
1748
+ const rowIndex = opts?.rowIndex ?? 0;
1749
+ const columnId = opts?.columnId;
1750
+ focusGuardRef.current = true;
1751
+ const navigableIds = propsRef.current.columns.map((c) => {
1752
+ if (c.id) return c.id;
1753
+ if ("accessorKey" in c) return c.accessorKey;
1754
+ return void 0;
1755
+ }).filter((id) => Boolean(id)).filter((c) => !NON_NAVIGABLE_COLUMN_IDS.has(c));
1756
+ const targetColumnId = columnId ?? navigableIds[0];
1757
+ if (!targetColumnId) {
1758
+ releaseFocusGuard(true);
1759
+ return;
1760
+ }
1761
+ async function onScrollAndFocus(retryCount) {
1762
+ if (!targetColumnId) return;
1763
+ const currentRowCount = propsRef.current.data.length;
1764
+ if (rowIndex >= currentRowCount && retryCount > 0) {
1765
+ await new Promise((resolve) => setTimeout(resolve, 50));
1766
+ await onScrollAndFocus(retryCount - 1);
1767
+ return;
1768
+ }
1769
+ const safeRowIndex = Math.min(rowIndex, Math.max(0, currentRowCount - 1));
1770
+ const isBottomHalf = safeRowIndex > currentRowCount / 2;
1771
+ rowVirtualizer.scrollToIndex(safeRowIndex, {
1772
+ align: isBottomHalf ? "end" : "start"
1773
+ });
1774
+ await new Promise((resolve) => requestAnimationFrame(resolve));
1775
+ const container = dataGridRef.current;
1776
+ const targetRow = rowMapRef.current.get(safeRowIndex);
1777
+ if (container && targetRow) {
1778
+ const containerRect = container.getBoundingClientRect();
1779
+ const headerHeight = headerRef.current?.getBoundingClientRect().height ?? 0;
1780
+ const footerHeight = footerRef.current?.getBoundingClientRect().height ?? 0;
1781
+ const viewportTop = containerRect.top + headerHeight + VIEWPORT_OFFSET;
1782
+ const viewportBottom = containerRect.bottom - footerHeight - VIEWPORT_OFFSET;
1783
+ const rowRect = targetRow.getBoundingClientRect();
1784
+ const isFullyVisible = rowRect.top >= viewportTop && rowRect.bottom <= viewportBottom;
1785
+ if (!isFullyVisible) {
1786
+ if (rowRect.top < viewportTop) {
1787
+ container.scrollTop -= viewportTop - rowRect.top;
1788
+ } else if (rowRect.bottom > viewportBottom) {
1789
+ container.scrollTop += rowRect.bottom - viewportBottom;
1790
+ }
1791
+ }
1792
+ }
1793
+ store.batch(() => {
1794
+ store.setState("focusedCell", {
1795
+ rowIndex: safeRowIndex,
1796
+ columnId: targetColumnId
1797
+ });
1798
+ store.setState("editingCell", null);
1799
+ });
1800
+ const cellKey = getCellKey(safeRowIndex, targetColumnId);
1801
+ const cellElement = cellMapRef.current.get(cellKey);
1802
+ if (cellElement) {
1803
+ cellElement.focus();
1804
+ releaseFocusGuard();
1805
+ } else if (retryCount > 0) {
1806
+ await new Promise((resolve) => requestAnimationFrame(resolve));
1807
+ await onScrollAndFocus(retryCount - 1);
1808
+ } else {
1809
+ dataGridRef.current?.focus();
1810
+ releaseFocusGuard();
1811
+ }
1812
+ }
1813
+ await onScrollAndFocus(SCROLL_SYNC_RETRY_COUNT);
1814
+ },
1815
+ [rowVirtualizer, propsRef, store, releaseFocusGuard]
1816
+ );
1817
+ const onRowAdd = React.useCallback(
1818
+ async (event) => {
1819
+ if (propsRef.current.readOnly || !propsRef.current.onRowAdd) return;
1820
+ const initialRowCount = propsRef.current.data.length;
1821
+ let result;
1822
+ try {
1823
+ result = await propsRef.current.onRowAdd(event);
1824
+ } catch {
1825
+ return;
1826
+ }
1827
+ if (result === null || event?.defaultPrevented) return;
1828
+ onSelectionClear();
1829
+ const targetRowIndex = result.rowIndex ?? initialRowCount;
1830
+ const targetColumnId = result.columnId;
1831
+ onScrollToRow({
1832
+ rowIndex: targetRowIndex,
1833
+ columnId: targetColumnId
1834
+ });
1835
+ },
1836
+ [propsRef, onScrollToRow, onSelectionClear]
1837
+ );
1838
+ const onDataGridKeyDown = React.useCallback(
1839
+ (event) => {
1840
+ const currentState = store.getState();
1841
+ const { key, ctrlKey, metaKey, shiftKey, altKey } = event;
1842
+ const isCtrlPressed = ctrlKey || metaKey;
1843
+ if (propsRef.current.enableSearch && isCtrlPressed && !shiftKey && key === SEARCH_SHORTCUT_KEY) {
1844
+ event.preventDefault();
1845
+ onSearchOpenChange(true);
1846
+ return;
1847
+ }
1848
+ if (propsRef.current.enableSearch && currentState.searchOpen && !currentState.editingCell) {
1849
+ if (key === "Enter") {
1850
+ event.preventDefault();
1851
+ if (shiftKey) {
1852
+ onNavigateToPrevMatch();
1853
+ } else {
1854
+ onNavigateToNextMatch();
1855
+ }
1856
+ return;
1857
+ }
1858
+ if (key === "Escape") {
1859
+ event.preventDefault();
1860
+ onSearchOpenChange(false);
1861
+ return;
1862
+ }
1863
+ return;
1864
+ }
1865
+ if (currentState.editingCell) return;
1866
+ if (isCtrlPressed && (key === "Backspace" || key === "Delete") && !propsRef.current.readOnly && propsRef.current.onRowsDelete) {
1867
+ const rowIndices = /* @__PURE__ */ new Set();
1868
+ const selectedRowIds = Object.keys(currentState.rowSelection);
1869
+ if (selectedRowIds.length > 0) {
1870
+ const currentTable = tableRef.current;
1871
+ const rows = currentTable?.getRowModel().rows ?? [];
1872
+ for (const row of rows) {
1873
+ if (currentState.rowSelection[row.id]) {
1874
+ rowIndices.add(row.index);
1875
+ }
1876
+ }
1877
+ } else if (currentState.selectionState.selectedCells.size > 0) {
1878
+ for (const cellKey of currentState.selectionState.selectedCells) {
1879
+ const { rowIndex } = parseCellKey(cellKey);
1880
+ rowIndices.add(rowIndex);
1881
+ }
1882
+ } else if (currentState.focusedCell) {
1883
+ rowIndices.add(currentState.focusedCell.rowIndex);
1884
+ }
1885
+ if (rowIndices.size > 0) {
1886
+ event.preventDefault();
1887
+ onRowsDelete(Array.from(rowIndices));
1888
+ }
1889
+ return;
1890
+ }
1891
+ if (!currentState.focusedCell) return;
1892
+ let direction = null;
1893
+ if (isCtrlPressed && !shiftKey && key === "a") {
1894
+ event.preventDefault();
1895
+ selectAll();
1896
+ return;
1897
+ }
1898
+ if (isCtrlPressed && !shiftKey && key === "c") {
1899
+ event.preventDefault();
1900
+ onCellsCopy();
1901
+ return;
1902
+ }
1903
+ if (isCtrlPressed && !shiftKey && key === "x" && !propsRef.current.readOnly) {
1904
+ event.preventDefault();
1905
+ onCellsCut();
1906
+ return;
1907
+ }
1908
+ if (propsRef.current.enablePaste && isCtrlPressed && !shiftKey && key === "v" && !propsRef.current.readOnly) {
1909
+ return;
1910
+ }
1911
+ if ((key === "Delete" || key === "Backspace") && !isCtrlPressed && !propsRef.current.readOnly) {
1912
+ const cellsToClear = currentState.selectionState.selectedCells.size > 0 ? Array.from(currentState.selectionState.selectedCells) : currentState.focusedCell ? [getCellKey(currentState.focusedCell.rowIndex, currentState.focusedCell.columnId)] : [];
1913
+ if (cellsToClear.length > 0) {
1914
+ event.preventDefault();
1915
+ const updates = [];
1916
+ const currentTable = tableRef.current;
1917
+ const tableColumns = currentTable?.getAllColumns() ?? [];
1918
+ const columnById = new Map(tableColumns.map((c) => [c.id, c]));
1919
+ for (const cellKey of cellsToClear) {
1920
+ const { rowIndex, columnId } = parseCellKey(cellKey);
1921
+ const column = columnById.get(columnId);
1922
+ const cellVariant = column?.columnDef?.meta?.cell?.variant;
1923
+ const emptyValue = getEmptyCellValue(cellVariant);
1924
+ updates.push({ rowIndex, columnId, value: emptyValue });
1925
+ }
1926
+ onDataUpdate(updates);
1927
+ if (currentState.selectionState.selectedCells.size > 0) {
1928
+ onSelectionClear();
1929
+ }
1930
+ if (currentState.cutCells.size > 0) {
1931
+ store.setState("cutCells", /* @__PURE__ */ new Set());
1932
+ }
1933
+ }
1934
+ return;
1935
+ }
1936
+ if (key === "Enter" && shiftKey && !propsRef.current.readOnly && propsRef.current.onRowAdd) {
1937
+ event.preventDefault();
1938
+ const initialRowCount = propsRef.current.data.length;
1939
+ const currentColumnId = currentState.focusedCell.columnId;
1940
+ Promise.resolve(propsRef.current.onRowAdd()).then(async (result) => {
1941
+ if (result === null) return;
1942
+ onSelectionClear();
1943
+ const targetRowIndex = result.rowIndex ?? initialRowCount;
1944
+ const targetColumnId = result.columnId ?? currentColumnId;
1945
+ onScrollToRow({
1946
+ rowIndex: targetRowIndex,
1947
+ columnId: targetColumnId
1948
+ });
1949
+ }).catch(() => {
1950
+ });
1951
+ return;
1952
+ }
1953
+ switch (key) {
1954
+ case "ArrowUp":
1955
+ if (altKey && !isCtrlPressed && !shiftKey) {
1956
+ direction = "pageup";
1957
+ } else if (isCtrlPressed && shiftKey) {
1958
+ const selectionEdge = currentState.selectionState.selectionRange?.end || currentState.focusedCell;
1959
+ const currentColIndex = navigableColumnIds.indexOf(selectionEdge.columnId);
1960
+ const selectionStart = currentState.selectionState.selectionRange?.start || currentState.focusedCell;
1961
+ selectRange(selectionStart, {
1962
+ rowIndex: 0,
1963
+ columnId: navigableColumnIds[currentColIndex] ?? selectionEdge.columnId
1964
+ });
1965
+ const rowVirtualizer2 = rowVirtualizerRef.current;
1966
+ if (rowVirtualizer2) {
1967
+ rowVirtualizer2.scrollToIndex(0, { align: "start" });
1968
+ }
1969
+ restoreFocus(dataGridRef.current);
1970
+ event.preventDefault();
1971
+ return;
1972
+ } else if (isCtrlPressed && !shiftKey) {
1973
+ direction = "ctrl+up";
1974
+ } else {
1975
+ direction = "up";
1976
+ }
1977
+ break;
1978
+ case "ArrowDown":
1979
+ if (altKey && !isCtrlPressed && !shiftKey) {
1980
+ direction = "pagedown";
1981
+ } else if (isCtrlPressed && shiftKey) {
1982
+ const rowCount = tableRef.current?.getRowModel().rows.length || propsRef.current.data.length;
1983
+ const selectionEdge = currentState.selectionState.selectionRange?.end || currentState.focusedCell;
1984
+ const currentColIndex = navigableColumnIds.indexOf(selectionEdge.columnId);
1985
+ const selectionStart = currentState.selectionState.selectionRange?.start || currentState.focusedCell;
1986
+ selectRange(selectionStart, {
1987
+ rowIndex: Math.max(0, rowCount - 1),
1988
+ columnId: navigableColumnIds[currentColIndex] ?? selectionEdge.columnId
1989
+ });
1990
+ const rowVirtualizer2 = rowVirtualizerRef.current;
1991
+ if (rowVirtualizer2) {
1992
+ rowVirtualizer2.scrollToIndex(Math.max(0, rowCount - 1), {
1993
+ align: "end"
1994
+ });
1995
+ }
1996
+ restoreFocus(dataGridRef.current);
1997
+ event.preventDefault();
1998
+ return;
1999
+ } else if (isCtrlPressed && !shiftKey) {
2000
+ direction = "ctrl+down";
2001
+ } else {
2002
+ direction = "down";
2003
+ }
2004
+ break;
2005
+ case "ArrowLeft":
2006
+ if (isCtrlPressed && shiftKey) {
2007
+ const selectionEdge = currentState.selectionState.selectionRange?.end || currentState.focusedCell;
2008
+ const selectionStart = currentState.selectionState.selectionRange?.start || currentState.focusedCell;
2009
+ const targetColumnId = dir === "rtl" ? navigableColumnIds[navigableColumnIds.length - 1] : navigableColumnIds[0];
2010
+ if (targetColumnId) {
2011
+ selectRange(selectionStart, {
2012
+ rowIndex: selectionEdge.rowIndex,
2013
+ columnId: targetColumnId
2014
+ });
2015
+ const container = dataGridRef.current;
2016
+ const cellKey = getCellKey(selectionEdge.rowIndex, targetColumnId);
2017
+ const targetCell = cellMapRef.current.get(cellKey);
2018
+ if (container && targetCell) {
2019
+ scrollCellIntoView({
2020
+ container,
2021
+ targetCell,
2022
+ tableRef,
2023
+ viewportOffset: VIEWPORT_OFFSET,
2024
+ direction: "home",
2025
+ isRtl: dir === "rtl"
2026
+ });
2027
+ }
2028
+ restoreFocus(container);
2029
+ }
2030
+ event.preventDefault();
2031
+ return;
2032
+ } else if (isCtrlPressed && !shiftKey) {
2033
+ direction = "home";
2034
+ } else {
2035
+ direction = "left";
2036
+ }
2037
+ break;
2038
+ case "ArrowRight":
2039
+ if (isCtrlPressed && shiftKey) {
2040
+ const selectionEdge = currentState.selectionState.selectionRange?.end || currentState.focusedCell;
2041
+ const selectionStart = currentState.selectionState.selectionRange?.start || currentState.focusedCell;
2042
+ const targetColumnId = dir === "rtl" ? navigableColumnIds[0] : navigableColumnIds[navigableColumnIds.length - 1];
2043
+ if (targetColumnId) {
2044
+ selectRange(selectionStart, {
2045
+ rowIndex: selectionEdge.rowIndex,
2046
+ columnId: targetColumnId
2047
+ });
2048
+ const container = dataGridRef.current;
2049
+ const cellKey = getCellKey(selectionEdge.rowIndex, targetColumnId);
2050
+ const targetCell = cellMapRef.current.get(cellKey);
2051
+ if (container && targetCell) {
2052
+ scrollCellIntoView({
2053
+ container,
2054
+ targetCell,
2055
+ tableRef,
2056
+ viewportOffset: VIEWPORT_OFFSET,
2057
+ direction: "end",
2058
+ isRtl: dir === "rtl"
2059
+ });
2060
+ }
2061
+ restoreFocus(container);
2062
+ }
2063
+ event.preventDefault();
2064
+ return;
2065
+ } else if (isCtrlPressed && !shiftKey) {
2066
+ direction = "end";
2067
+ } else {
2068
+ direction = "right";
2069
+ }
2070
+ break;
2071
+ case "Home":
2072
+ direction = isCtrlPressed ? "ctrl+home" : "home";
2073
+ break;
2074
+ case "End":
2075
+ direction = isCtrlPressed ? "ctrl+end" : "end";
2076
+ break;
2077
+ case "PageUp":
2078
+ direction = altKey ? "pageleft" : "pageup";
2079
+ break;
2080
+ case "PageDown":
2081
+ direction = altKey ? "pageright" : "pagedown";
2082
+ break;
2083
+ case "Escape":
2084
+ event.preventDefault();
2085
+ if (currentState.selectionState.selectedCells.size > 0 || Object.keys(currentState.rowSelection).length > 0) {
2086
+ onSelectionClear();
2087
+ } else {
2088
+ blurCell();
2089
+ }
2090
+ return;
2091
+ case "Tab":
2092
+ event.preventDefault();
2093
+ if (dir === "rtl") {
2094
+ direction = event.shiftKey ? "right" : "left";
2095
+ } else {
2096
+ direction = event.shiftKey ? "left" : "right";
2097
+ }
2098
+ break;
2099
+ }
2100
+ if (direction) {
2101
+ event.preventDefault();
2102
+ if (shiftKey && key !== "Tab" && currentState.focusedCell) {
2103
+ const selectionEdge = currentState.selectionState.selectionRange?.end || currentState.focusedCell;
2104
+ const currentColIndex = navigableColumnIds.indexOf(selectionEdge.columnId);
2105
+ let newRowIndex = selectionEdge.rowIndex;
2106
+ let newColumnId = selectionEdge.columnId;
2107
+ const isRtl = dir === "rtl";
2108
+ const rowCount = tableRef.current?.getRowModel().rows.length || propsRef.current.data.length;
2109
+ switch (direction) {
2110
+ case "up":
2111
+ newRowIndex = Math.max(0, selectionEdge.rowIndex - 1);
2112
+ break;
2113
+ case "down":
2114
+ newRowIndex = Math.min(rowCount - 1, selectionEdge.rowIndex + 1);
2115
+ break;
2116
+ case "left":
2117
+ if (isRtl) {
2118
+ if (currentColIndex < navigableColumnIds.length - 1) {
2119
+ const nextColumnId = navigableColumnIds[currentColIndex + 1];
2120
+ if (nextColumnId) newColumnId = nextColumnId;
2121
+ }
2122
+ } else {
2123
+ if (currentColIndex > 0) {
2124
+ const prevColumnId = navigableColumnIds[currentColIndex - 1];
2125
+ if (prevColumnId) newColumnId = prevColumnId;
2126
+ }
2127
+ }
2128
+ break;
2129
+ case "right":
2130
+ if (isRtl) {
2131
+ if (currentColIndex > 0) {
2132
+ const prevColumnId = navigableColumnIds[currentColIndex - 1];
2133
+ if (prevColumnId) newColumnId = prevColumnId;
2134
+ }
2135
+ } else {
2136
+ if (currentColIndex < navigableColumnIds.length - 1) {
2137
+ const nextColumnId = navigableColumnIds[currentColIndex + 1];
2138
+ if (nextColumnId) newColumnId = nextColumnId;
2139
+ }
2140
+ }
2141
+ break;
2142
+ case "home":
2143
+ if (navigableColumnIds.length > 0) {
2144
+ newColumnId = navigableColumnIds[0] ?? newColumnId;
2145
+ }
2146
+ break;
2147
+ case "end":
2148
+ if (navigableColumnIds.length > 0) {
2149
+ newColumnId = navigableColumnIds[navigableColumnIds.length - 1] ?? newColumnId;
2150
+ }
2151
+ break;
2152
+ }
2153
+ const selectionStart = currentState.selectionState.selectionRange?.start || currentState.focusedCell;
2154
+ selectRange(selectionStart, {
2155
+ rowIndex: newRowIndex,
2156
+ columnId: newColumnId
2157
+ });
2158
+ const container = dataGridRef.current;
2159
+ const targetRow = rowMapRef.current.get(newRowIndex);
2160
+ const cellKey = getCellKey(newRowIndex, newColumnId);
2161
+ const targetCell = cellMapRef.current.get(cellKey);
2162
+ if (newRowIndex !== selectionEdge.rowIndex && (direction === "up" || direction === "down")) {
2163
+ if (container && targetRow) {
2164
+ const containerRect = container.getBoundingClientRect();
2165
+ const headerHeight = headerRef.current?.getBoundingClientRect().height ?? 0;
2166
+ const footerHeight = footerRef.current?.getBoundingClientRect().height ?? 0;
2167
+ const viewportTop = containerRect.top + headerHeight + VIEWPORT_OFFSET;
2168
+ const viewportBottom = containerRect.bottom - footerHeight - VIEWPORT_OFFSET;
2169
+ const rowRect = targetRow.getBoundingClientRect();
2170
+ const isFullyVisible = rowRect.top >= viewportTop && rowRect.bottom <= viewportBottom;
2171
+ if (!isFullyVisible) {
2172
+ const scrollNeeded = direction === "down" ? rowRect.bottom - viewportBottom : viewportTop - rowRect.top;
2173
+ if (direction === "down") {
2174
+ container.scrollTop += scrollNeeded;
2175
+ } else {
2176
+ container.scrollTop -= scrollNeeded;
2177
+ }
2178
+ restoreFocus(container);
2179
+ }
2180
+ } else {
2181
+ const rowVirtualizer2 = rowVirtualizerRef.current;
2182
+ if (rowVirtualizer2) {
2183
+ const align = direction === "up" ? "start" : "end";
2184
+ rowVirtualizer2.scrollToIndex(newRowIndex, { align });
2185
+ restoreFocus(container);
2186
+ }
2187
+ }
2188
+ }
2189
+ if (newColumnId !== selectionEdge.columnId && (direction === "left" || direction === "right" || direction === "home" || direction === "end")) {
2190
+ if (container && targetCell) {
2191
+ scrollCellIntoView({
2192
+ container,
2193
+ targetCell,
2194
+ tableRef,
2195
+ viewportOffset: VIEWPORT_OFFSET,
2196
+ direction,
2197
+ isRtl
2198
+ });
2199
+ }
2200
+ }
2201
+ } else {
2202
+ if (currentState.selectionState.selectedCells.size > 0) {
2203
+ onSelectionClear();
2204
+ }
2205
+ navigateCell(direction);
2206
+ }
2207
+ }
2208
+ },
2209
+ [
2210
+ dir,
2211
+ store,
2212
+ propsRef,
2213
+ blurCell,
2214
+ navigateCell,
2215
+ selectAll,
2216
+ onCellsCopy,
2217
+ onCellsCut,
2218
+ onDataUpdate,
2219
+ onSelectionClear,
2220
+ navigableColumnIds,
2221
+ selectRange,
2222
+ onSearchOpenChange,
2223
+ onNavigateToNextMatch,
2224
+ onNavigateToPrevMatch,
2225
+ onRowsDelete,
2226
+ restoreFocus,
2227
+ onScrollToRow
2228
+ ]
2229
+ );
2230
+ const searchState = React.useMemo(() => {
2231
+ if (!propsRef.current.enableSearch) return void 0;
2232
+ return {
2233
+ searchMatches,
2234
+ matchIndex,
2235
+ searchOpen,
2236
+ onSearchOpenChange,
2237
+ searchQuery,
2238
+ onSearchQueryChange,
2239
+ onSearch,
2240
+ onNavigateToNextMatch,
2241
+ onNavigateToPrevMatch
2242
+ };
2243
+ }, [
2244
+ propsRef,
2245
+ searchMatches,
2246
+ matchIndex,
2247
+ searchOpen,
2248
+ onSearchOpenChange,
2249
+ searchQuery,
2250
+ onSearchQueryChange,
2251
+ onSearch,
2252
+ onNavigateToNextMatch,
2253
+ onNavigateToPrevMatch
2254
+ ]);
2255
+ React.useEffect(() => {
2256
+ const dataGridElement = dataGridRef.current;
2257
+ if (!dataGridElement) return;
2258
+ dataGridElement.addEventListener("keydown", onDataGridKeyDown);
2259
+ return () => {
2260
+ dataGridElement.removeEventListener("keydown", onDataGridKeyDown);
2261
+ };
2262
+ }, [onDataGridKeyDown]);
2263
+ React.useEffect(() => {
2264
+ const dataGridElement = dataGridRef.current;
2265
+ if (!dataGridElement) return;
2266
+ function onPaste(event) {
2267
+ if (!propsRef.current.enablePaste || propsRef.current.readOnly) return;
2268
+ const currentState = store.getState();
2269
+ if (currentState.editingCell || !currentState.focusedCell) return;
2270
+ const clipboardText = event.clipboardData?.getData("text/plain") ?? "";
2271
+ if (!clipboardText) return;
2272
+ event.preventDefault();
2273
+ onCellsPaste(false, clipboardText);
2274
+ }
2275
+ dataGridElement.addEventListener("paste", onPaste);
2276
+ return () => {
2277
+ dataGridElement.removeEventListener("paste", onPaste);
2278
+ };
2279
+ }, [propsRef, store, onCellsPaste]);
2280
+ React.useEffect(() => {
2281
+ function onGlobalKeyDown(event) {
2282
+ const dataGridElement = dataGridRef.current;
2283
+ if (!dataGridElement) return;
2284
+ const target = event.target;
2285
+ if (!(target instanceof HTMLElement)) return;
2286
+ const { key, ctrlKey, metaKey, shiftKey } = event;
2287
+ const isCommandPressed = ctrlKey || metaKey;
2288
+ if (propsRef.current.enableSearch && isCommandPressed && !shiftKey && key === SEARCH_SHORTCUT_KEY) {
2289
+ const isInInput = target.tagName === "INPUT" || target.tagName === "TEXTAREA";
2290
+ const isInDataGrid2 = dataGridElement.contains(target);
2291
+ const isInSearchInput = target.closest('[role="search"]') !== null;
2292
+ if (isInDataGrid2 || isInSearchInput || !isInInput) {
2293
+ event.preventDefault();
2294
+ event.stopPropagation();
2295
+ const nextSearchOpen = !store.getState().searchOpen;
2296
+ onSearchOpenChange(nextSearchOpen);
2297
+ if (nextSearchOpen && !isInDataGrid2 && !isInSearchInput) {
2298
+ requestAnimationFrame(() => {
2299
+ dataGridElement.focus();
2300
+ });
2301
+ }
2302
+ return;
2303
+ }
2304
+ }
2305
+ const isInDataGrid = dataGridElement.contains(target);
2306
+ if (!isInDataGrid) return;
2307
+ if (key === "Escape") {
2308
+ const currentState = store.getState();
2309
+ const hasSelections = currentState.selectionState.selectedCells.size > 0 || Object.keys(currentState.rowSelection).length > 0;
2310
+ if (hasSelections) {
2311
+ event.preventDefault();
2312
+ event.stopPropagation();
2313
+ onSelectionClear();
2314
+ }
2315
+ }
2316
+ }
2317
+ window.addEventListener("keydown", onGlobalKeyDown, true);
2318
+ return () => {
2319
+ window.removeEventListener("keydown", onGlobalKeyDown, true);
2320
+ };
2321
+ }, [propsRef, onSearchOpenChange, store, onSelectionClear]);
2322
+ React.useEffect(() => {
2323
+ const currentState = store.getState();
2324
+ const autoFocus = propsRef.current.autoFocus;
2325
+ if (autoFocus && data.length > 0 && columns.length > 0 && !currentState.focusedCell) {
2326
+ if (navigableColumnIds.length > 0) {
2327
+ const rafId = requestAnimationFrame(() => {
2328
+ if (typeof autoFocus === "object") {
2329
+ const { rowIndex, columnId } = autoFocus;
2330
+ if (columnId) {
2331
+ focusCell(rowIndex ?? 0, columnId);
2332
+ }
2333
+ return;
2334
+ }
2335
+ const firstColumnId = navigableColumnIds[0];
2336
+ if (firstColumnId) {
2337
+ focusCell(0, firstColumnId);
2338
+ }
2339
+ });
2340
+ return () => cancelAnimationFrame(rafId);
2341
+ }
2342
+ }
2343
+ }, [store, propsRef, data, columns, navigableColumnIds, focusCell]);
2344
+ React.useEffect(() => {
2345
+ const container = dataGridRef.current;
2346
+ if (!container) return;
2347
+ function onFocusOut(event) {
2348
+ if (focusGuardRef.current) return;
2349
+ const currentContainer = dataGridRef.current;
2350
+ if (!currentContainer) return;
2351
+ const currentState = store.getState();
2352
+ if (!currentState.focusedCell || currentState.editingCell) return;
2353
+ const relatedTarget = event.relatedTarget;
2354
+ const isFocusMovingOutsideGrid = !relatedTarget || !currentContainer.contains(relatedTarget);
2355
+ const isFocusMovingToPopover = getIsInPopover(relatedTarget);
2356
+ if (isFocusMovingOutsideGrid && !isFocusMovingToPopover) {
2357
+ const { rowIndex, columnId } = currentState.focusedCell;
2358
+ const cellKey = getCellKey(rowIndex, columnId);
2359
+ const cellElement = cellMapRef.current.get(cellKey);
2360
+ requestAnimationFrame(() => {
2361
+ if (focusGuardRef.current) return;
2362
+ if (cellElement && document.body.contains(cellElement)) {
2363
+ cellElement.focus();
2364
+ } else {
2365
+ currentContainer.focus();
2366
+ }
2367
+ });
2368
+ }
2369
+ }
2370
+ container.addEventListener("focusout", onFocusOut);
2371
+ return () => {
2372
+ container.removeEventListener("focusout", onFocusOut);
2373
+ };
2374
+ }, [store]);
2375
+ React.useEffect(() => {
2376
+ function onOutsideClick(event) {
2377
+ if (event.button === 2) {
2378
+ return;
2379
+ }
2380
+ if (dataGridRef.current && !dataGridRef.current.contains(event.target)) {
2381
+ const elements = document.elementsFromPoint(event.clientX, event.clientY);
2382
+ const isInsidePopover = elements.some((element) => getIsInPopover(element));
2383
+ if (!isInsidePopover) {
2384
+ blurCell();
2385
+ const currentState = store.getState();
2386
+ if (currentState.selectionState.selectedCells.size > 0 || Object.keys(currentState.rowSelection).length > 0) {
2387
+ onSelectionClear();
2388
+ }
2389
+ }
2390
+ }
2391
+ }
2392
+ document.addEventListener("mousedown", onOutsideClick);
2393
+ return () => {
2394
+ document.removeEventListener("mousedown", onOutsideClick);
2395
+ };
2396
+ }, [store, blurCell, onSelectionClear]);
2397
+ React.useEffect(() => {
2398
+ function onSelectStart(event) {
2399
+ event.preventDefault();
2400
+ }
2401
+ function onContextMenu(event) {
2402
+ event.preventDefault();
2403
+ }
2404
+ function onCleanup() {
2405
+ document.removeEventListener("selectstart", onSelectStart);
2406
+ document.removeEventListener("contextmenu", onContextMenu);
2407
+ document.body.style.userSelect = "";
2408
+ }
2409
+ const onUnsubscribe = store.subscribe(() => {
2410
+ const currentState = store.getState();
2411
+ if (currentState.selectionState.isSelecting) {
2412
+ document.addEventListener("selectstart", onSelectStart);
2413
+ document.addEventListener("contextmenu", onContextMenu);
2414
+ document.body.style.userSelect = "none";
2415
+ } else {
2416
+ onCleanup();
2417
+ }
2418
+ });
2419
+ return () => {
2420
+ onCleanup();
2421
+ onUnsubscribe();
2422
+ };
2423
+ }, [store]);
2424
+ React.useEffect(() => {
2425
+ let rafId = null;
2426
+ let mouseX = 0;
2427
+ let mouseY = 0;
2428
+ let mouseReady = false;
2429
+ let active = false;
2430
+ let lastSelectionTime = 0;
2431
+ let resizeObserver = null;
2432
+ let cachedRect = null;
2433
+ let cachedHdrH = 0;
2434
+ let cachedFtrH = 0;
2435
+ let cachedLpw = 0;
2436
+ let cachedRpw = 0;
2437
+ function getAutoScrollSpeed(dist) {
2438
+ const t = Math.min(dist / AUTO_SCROLL_SPEED_RAMP_ZONE, 1);
2439
+ return Math.round(AUTO_SCROLL_MIN_SPEED + (AUTO_SCROLL_MAX_SPEED - AUTO_SCROLL_MIN_SPEED) * t);
2440
+ }
2441
+ function cacheLayout(container) {
2442
+ cachedRect = container.getBoundingClientRect();
2443
+ cachedHdrH = headerRef.current?.getBoundingClientRect().height ?? 0;
2444
+ cachedFtrH = footerRef.current?.getBoundingClientRect().height ?? 0;
2445
+ const tbl = tableRef.current;
2446
+ if (tbl) {
2447
+ cachedLpw = tbl.getLeftVisibleLeafColumns().reduce((s, c) => s + c.getSize(), 0);
2448
+ cachedRpw = tbl.getRightVisibleLeafColumns().reduce((s, c) => s + c.getSize(), 0);
2449
+ }
2450
+ }
2451
+ function tick() {
2452
+ if (!active) return;
2453
+ const container = dataGridRef.current;
2454
+ const tbl = tableRef.current;
2455
+ if (!container || !tbl) {
2456
+ onAutoScrollStop();
2457
+ return;
2458
+ }
2459
+ if (!mouseReady || !cachedRect) {
2460
+ rafId = requestAnimationFrame(tick);
2461
+ return;
2462
+ }
2463
+ const rect = cachedRect;
2464
+ const { dir: dir2 } = dragDepsRef.current;
2465
+ const hasNegativeScroll = container.scrollLeft < 0;
2466
+ const isActuallyRtl = dir2 === "rtl" || hasNegativeScroll;
2467
+ const dataTop = rect.top + cachedHdrH;
2468
+ const dataBottom = rect.bottom - cachedFtrH;
2469
+ const scrollAreaLeft = isActuallyRtl ? rect.left + cachedRpw : rect.left + cachedLpw;
2470
+ const scrollAreaRight = isActuallyRtl ? rect.right - cachedLpw : rect.right - cachedRpw;
2471
+ let dy = 0;
2472
+ let dx = 0;
2473
+ if (mouseY < dataTop) dy = -getAutoScrollSpeed(dataTop - mouseY);
2474
+ else if (mouseY > dataBottom) dy = getAutoScrollSpeed(mouseY - dataBottom);
2475
+ if (mouseX < scrollAreaLeft) dx = -getAutoScrollSpeed(scrollAreaLeft - mouseX);
2476
+ else if (mouseX > scrollAreaRight) dx = getAutoScrollSpeed(mouseX - scrollAreaRight);
2477
+ if (dx === 0 && dy === 0) {
2478
+ rafId = requestAnimationFrame(tick);
2479
+ return;
2480
+ }
2481
+ container.scrollTop += dy;
2482
+ container.scrollLeft += dx;
2483
+ const now = performance.now();
2484
+ if (now - lastSelectionTime < AUTO_SCROLL_SELECTION_THROTTLE_MS) {
2485
+ rafId = requestAnimationFrame(tick);
2486
+ return;
2487
+ }
2488
+ const { rowHeightValue: rh, columnIds: columnIds2 } = dragDepsRef.current;
2489
+ if (columnIds2.length === 0) {
2490
+ rafId = requestAnimationFrame(tick);
2491
+ return;
2492
+ }
2493
+ const totalRows = tbl.getRowModel().rows.length;
2494
+ const clampedY = Math.max(dataTop, Math.min(mouseY, dataBottom));
2495
+ const absY = container.scrollTop + (clampedY - dataTop);
2496
+ const rowIndex = Math.max(0, Math.min(Math.floor(absY / rh), totalRows - 1));
2497
+ const st = store.getState();
2498
+ const range = st.selectionState.selectionRange;
2499
+ let columnId;
2500
+ if (dx !== 0) {
2501
+ const clampedX = Math.max(rect.left, Math.min(mouseX, rect.right));
2502
+ const relX = clampedX - rect.left;
2503
+ const leftZoneWidth = isActuallyRtl ? cachedRpw : cachedLpw;
2504
+ const rightZoneWidth = isActuallyRtl ? cachedLpw : cachedRpw;
2505
+ if (relX < leftZoneWidth) {
2506
+ const columns2 = isActuallyRtl ? tbl.getRightVisibleLeafColumns() : tbl.getLeftVisibleLeafColumns();
2507
+ columnId = columns2[0]?.id ?? columnIds2[0] ?? "";
2508
+ let cx = 0;
2509
+ for (const col of columns2) {
2510
+ if (relX < cx + col.getSize()) {
2511
+ columnId = col.id;
2512
+ break;
2513
+ }
2514
+ cx += col.getSize();
2515
+ }
2516
+ } else if (relX > rect.width - rightZoneWidth) {
2517
+ const columns2 = isActuallyRtl ? tbl.getLeftVisibleLeafColumns() : tbl.getRightVisibleLeafColumns();
2518
+ columnId = columns2[0]?.id ?? columnIds2[columnIds2.length - 1] ?? "";
2519
+ let cx = rect.width - rightZoneWidth;
2520
+ for (const col of columns2) {
2521
+ if (relX < cx + col.getSize()) {
2522
+ columnId = col.id;
2523
+ break;
2524
+ }
2525
+ cx += col.getSize();
2526
+ }
2527
+ } else {
2528
+ const center = tbl.getCenterVisibleLeafColumns();
2529
+ const centerZoneWidth = rect.width - leftZoneWidth - rightZoneWidth;
2530
+ const distFromVisualLeft = relX - leftZoneWidth;
2531
+ let absX;
2532
+ if (isActuallyRtl) {
2533
+ const scrollFromRight = hasNegativeScroll ? -container.scrollLeft : container.scrollWidth - container.clientWidth - container.scrollLeft;
2534
+ absX = scrollFromRight + (centerZoneWidth - distFromVisualLeft);
2535
+ } else {
2536
+ absX = container.scrollLeft + distFromVisualLeft;
2537
+ }
2538
+ columnId = center[center.length - 1]?.id ?? columnIds2[columnIds2.length - 1] ?? "";
2539
+ let cw = 0;
2540
+ for (const col of center) {
2541
+ cw += col.getSize();
2542
+ if (absX < cw) {
2543
+ columnId = col.id;
2544
+ break;
2545
+ }
2546
+ }
2547
+ }
2548
+ }
2549
+ if (!columnId) {
2550
+ columnId = range?.end.columnId ?? columnIds2[0] ?? "";
2551
+ }
2552
+ if (range && (rowIndex !== range.end.rowIndex || columnId !== range.end.columnId)) {
2553
+ dragDepsRef.current.selectRange(range.start, { rowIndex, columnId }, true);
2554
+ lastSelectionTime = now;
2555
+ }
2556
+ rafId = requestAnimationFrame(tick);
2557
+ }
2558
+ function onMove(event) {
2559
+ mouseX = event.clientX;
2560
+ mouseY = event.clientY;
2561
+ mouseReady = true;
2562
+ }
2563
+ function onUp() {
2564
+ onAutoScrollStop();
2565
+ const st = store.getState();
2566
+ if (st.selectionState.isSelecting) {
2567
+ store.setState("selectionState", {
2568
+ ...st.selectionState,
2569
+ isSelecting: false
2570
+ });
2571
+ }
2572
+ }
2573
+ function onAutoScrollStart() {
2574
+ if (active) return;
2575
+ const container = dataGridRef.current;
2576
+ if (!container) return;
2577
+ active = true;
2578
+ mouseReady = false;
2579
+ cachedRect = null;
2580
+ lastSelectionTime = 0;
2581
+ document.addEventListener("mousemove", onMove);
2582
+ document.addEventListener("mouseup", onUp);
2583
+ resizeObserver = new ResizeObserver(() => {
2584
+ const currentContainer = dataGridRef.current;
2585
+ if (currentContainer) cacheLayout(currentContainer);
2586
+ });
2587
+ resizeObserver.observe(container);
2588
+ rafId = requestAnimationFrame(() => {
2589
+ const currentContainer = dataGridRef.current;
2590
+ if (currentContainer) cacheLayout(currentContainer);
2591
+ rafId = requestAnimationFrame(tick);
2592
+ });
2593
+ }
2594
+ function onAutoScrollStop() {
2595
+ if (!active) return;
2596
+ active = false;
2597
+ cachedRect = null;
2598
+ document.removeEventListener("mousemove", onMove);
2599
+ document.removeEventListener("mouseup", onUp);
2600
+ resizeObserver?.disconnect();
2601
+ resizeObserver = null;
2602
+ if (rafId !== null) {
2603
+ cancelAnimationFrame(rafId);
2604
+ rafId = null;
2605
+ }
2606
+ }
2607
+ if (store.getState().selectionState.isSelecting) onAutoScrollStart();
2608
+ const onUnsubscribe = store.subscribe(() => {
2609
+ const st = store.getState();
2610
+ if (st.selectionState.isSelecting && !active) onAutoScrollStart();
2611
+ else if (!st.selectionState.isSelecting && active) onAutoScrollStop();
2612
+ });
2613
+ return () => {
2614
+ onAutoScrollStop();
2615
+ onUnsubscribe();
2616
+ };
2617
+ }, [store, dragDepsRef]);
2618
+ useIsomorphicLayoutEffect(() => {
2619
+ const rafId = requestAnimationFrame(() => {
2620
+ rowVirtualizer.measure();
2621
+ });
2622
+ return () => cancelAnimationFrame(rafId);
2623
+ }, [
2624
+ rowHeight,
2625
+ table.getState().columnFilters,
2626
+ table.getState().columnOrder,
2627
+ table.getState().columnPinning,
2628
+ table.getState().columnSizing,
2629
+ table.getState().columnVisibility,
2630
+ table.getState().expanded,
2631
+ table.getState().globalFilter,
2632
+ table.getState().grouping,
2633
+ table.getState().rowSelection,
2634
+ table.getState().sorting
2635
+ ]);
2636
+ const virtualTotalSize = rowVirtualizer.getTotalSize();
2637
+ const virtualItems = rowVirtualizer.getVirtualItems();
2638
+ const measureElement = rowVirtualizer.measureElement;
2639
+ return React.useMemo(
2640
+ () => ({
2641
+ dataGridRef,
2642
+ headerRef,
2643
+ rowMapRef,
2644
+ footerRef,
2645
+ dir,
2646
+ table,
2647
+ tableMeta,
2648
+ virtualTotalSize,
2649
+ virtualItems,
2650
+ measureElement,
2651
+ columns,
2652
+ columnSizeVars,
2653
+ searchState,
2654
+ searchMatchesByRow,
2655
+ activeSearchMatch,
2656
+ cellSelectionMap,
2657
+ focusedCell,
2658
+ editingCell,
2659
+ rowHeight,
2660
+ contextMenu,
2661
+ pasteDialog,
2662
+ onRowAdd: hasRowAdd ? onRowAdd : void 0,
2663
+ adjustLayout
2664
+ }),
2665
+ [
2666
+ hasRowAdd,
2667
+ dir,
2668
+ table,
2669
+ tableMeta,
2670
+ virtualTotalSize,
2671
+ virtualItems,
2672
+ measureElement,
2673
+ columns,
2674
+ columnSizeVars,
2675
+ searchState,
2676
+ searchMatchesByRow,
2677
+ activeSearchMatch,
2678
+ cellSelectionMap,
2679
+ focusedCell,
2680
+ editingCell,
2681
+ rowHeight,
2682
+ contextMenu,
2683
+ pasteDialog,
2684
+ onRowAdd,
2685
+ adjustLayout
2686
+ ]
2687
+ );
2688
+ }
2689
+ export {
2690
+ useDataGrid
2691
+ };
2692
+ //# sourceMappingURL=use-data-grid.js.map