@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.
- package/README.md +15 -4
- package/dist/data-grid/compose-refs.d.ts +19 -0
- package/dist/data-grid/compose-refs.js +41 -0
- package/dist/data-grid/compose-refs.js.map +1 -0
- package/dist/data-grid/data-grid-cell-variants.d.ts +15 -0
- package/dist/data-grid/data-grid-cell-variants.js +1734 -0
- package/dist/data-grid/data-grid-cell-variants.js.map +1 -0
- package/dist/data-grid/data-grid-cell-wrapper.d.ts +9 -0
- package/dist/data-grid/data-grid-cell-wrapper.js +179 -0
- package/dist/data-grid/data-grid-cell-wrapper.js.map +1 -0
- package/dist/data-grid/data-grid-cell.d.ts +8 -0
- package/dist/data-grid/data-grid-cell.js +101 -0
- package/dist/data-grid/data-grid-cell.js.map +1 -0
- package/dist/data-grid/data-grid-column-header.d.ts +12 -0
- package/dist/data-grid/data-grid-column-header.js +205 -0
- package/dist/data-grid/data-grid-column-header.js.map +1 -0
- package/dist/data-grid/data-grid-context-menu.d.ts +12 -0
- package/dist/data-grid/data-grid-context-menu.js +160 -0
- package/dist/data-grid/data-grid-context-menu.js.map +1 -0
- package/dist/data-grid/data-grid-filter-menu.d.ts +12 -0
- package/dist/data-grid/data-grid-filter-menu.js +714 -0
- package/dist/data-grid/data-grid-filter-menu.js.map +1 -0
- package/dist/data-grid/data-grid-keyboard-shortcuts.d.ts +20 -0
- package/dist/data-grid/data-grid-keyboard-shortcuts.js +227 -0
- package/dist/data-grid/data-grid-keyboard-shortcuts.js.map +1 -0
- package/dist/data-grid/data-grid-paste-dialog.d.ts +11 -0
- package/dist/data-grid/data-grid-paste-dialog.js +103 -0
- package/dist/data-grid/data-grid-paste-dialog.js.map +1 -0
- package/dist/data-grid/data-grid-presence.d.ts +14 -0
- package/dist/data-grid/data-grid-presence.js +16 -0
- package/dist/data-grid/data-grid-presence.js.map +1 -0
- package/dist/data-grid/data-grid-row-height-menu.d.ts +15 -0
- package/dist/data-grid/data-grid-row-height-menu.js +56 -0
- package/dist/data-grid/data-grid-row-height-menu.js.map +1 -0
- package/dist/data-grid/data-grid-row.d.ts +28 -0
- package/dist/data-grid/data-grid-row.js +206 -0
- package/dist/data-grid/data-grid-row.js.map +1 -0
- package/dist/data-grid/data-grid-search.d.ts +10 -0
- package/dist/data-grid/data-grid-search.js +183 -0
- package/dist/data-grid/data-grid-search.js.map +1 -0
- package/dist/data-grid/data-grid-select-column.d.ts +12 -0
- package/dist/data-grid/data-grid-select-column.js +177 -0
- package/dist/data-grid/data-grid-select-column.js.map +1 -0
- package/dist/data-grid/data-grid-skeleton.d.ts +13 -0
- package/dist/data-grid/data-grid-skeleton.js +52 -0
- package/dist/data-grid/data-grid-skeleton.js.map +1 -0
- package/dist/data-grid/data-grid-sort-menu.d.ts +12 -0
- package/dist/data-grid/data-grid-sort-menu.js +271 -0
- package/dist/data-grid/data-grid-sort-menu.js.map +1 -0
- package/dist/data-grid/data-grid-view-menu.d.ts +13 -0
- package/dist/data-grid/data-grid-view-menu.js +66 -0
- package/dist/data-grid/data-grid-view-menu.js.map +1 -0
- package/dist/data-grid/data-grid.d.ts +32 -0
- package/dist/data-grid/data-grid.js +235 -0
- package/dist/data-grid/data-grid.js.map +1 -0
- package/dist/data-grid/filters.d.ts +31 -0
- package/dist/data-grid/filters.js +212 -0
- package/dist/data-grid/filters.js.map +1 -0
- package/dist/data-grid/index.d.ts +22 -0
- package/dist/data-grid/index.js +77 -0
- package/dist/data-grid/index.js.map +1 -0
- package/dist/data-grid/messages.d.ts +280 -0
- package/dist/data-grid/messages.js +279 -0
- package/dist/data-grid/messages.js.map +1 -0
- package/dist/data-grid/sortable.d.ts +50 -0
- package/dist/data-grid/sortable.js +361 -0
- package/dist/data-grid/sortable.js.map +1 -0
- package/dist/data-grid/toast.d.ts +10 -0
- package/dist/data-grid/toast.js +21 -0
- package/dist/data-grid/toast.js.map +1 -0
- package/dist/data-grid/types.d.ts +189 -0
- package/dist/data-grid/types.js +1 -0
- package/dist/data-grid/types.js.map +1 -0
- package/dist/data-grid/use-as-ref.d.ts +5 -0
- package/dist/data-grid/use-as-ref.js +13 -0
- package/dist/data-grid/use-as-ref.js.map +1 -0
- package/dist/data-grid/use-badge-overflow.d.ts +24 -0
- package/dist/data-grid/use-badge-overflow.js +123 -0
- package/dist/data-grid/use-badge-overflow.js.map +1 -0
- package/dist/data-grid/use-callback-ref.d.ts +10 -0
- package/dist/data-grid/use-callback-ref.js +12 -0
- package/dist/data-grid/use-callback-ref.js.map +1 -0
- package/dist/data-grid/use-data-grid-undo-redo.d.ts +33 -0
- package/dist/data-grid/use-data-grid-undo-redo.js +345 -0
- package/dist/data-grid/use-data-grid-undo-redo.js.map +1 -0
- package/dist/data-grid/use-data-grid.d.ts +92 -0
- package/dist/data-grid/use-data-grid.js +2692 -0
- package/dist/data-grid/use-data-grid.js.map +1 -0
- package/dist/data-grid/use-debounced-callback.d.ts +6 -0
- package/dist/data-grid/use-debounced-callback.js +19 -0
- package/dist/data-grid/use-debounced-callback.js.map +1 -0
- package/dist/data-grid/use-isomorphic-layout-effect.d.ts +5 -0
- package/dist/data-grid/use-isomorphic-layout-effect.js +6 -0
- package/dist/data-grid/use-isomorphic-layout-effect.js.map +1 -0
- package/dist/data-grid/use-lazy-ref.d.ts +5 -0
- package/dist/data-grid/use-lazy-ref.js +12 -0
- package/dist/data-grid/use-lazy-ref.js.map +1 -0
- package/dist/data-grid/utils.d.ts +73 -0
- package/dist/data-grid/utils.js +408 -0
- package/dist/data-grid/utils.js.map +1 -0
- package/dist/shell/app-launcher.js +16 -16
- package/dist/shell/app-launcher.js.map +1 -1
- package/dist/shell/types.d.ts +13 -0
- package/dist/shell/types.js.map +1 -1
- 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
|