@rowsncolumns/grid 9.0.4 → 13.2.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (208) hide show
  1. package/LICENSE.md +100 -0
  2. package/Readme.md +53 -59
  3. package/dist/cjs/Cell.d.ts +35 -0
  4. package/dist/cjs/Cell.d.ts.map +1 -0
  5. package/dist/cjs/Cell.js +29 -0
  6. package/dist/{CellOverlay.d.ts → cjs/CellOverlay.d.ts} +5 -5
  7. package/dist/cjs/CellOverlay.d.ts.map +1 -0
  8. package/dist/cjs/CellOverlay.js +126 -0
  9. package/dist/cjs/Context.d.ts +8 -0
  10. package/dist/cjs/Context.d.ts.map +1 -0
  11. package/dist/cjs/Context.js +13 -0
  12. package/dist/{FillHandle.d.ts → cjs/FillHandle.d.ts} +2 -1
  13. package/dist/cjs/FillHandle.d.ts.map +1 -0
  14. package/dist/cjs/FillHandle.js +26 -0
  15. package/dist/cjs/Grid.d.ts +558 -0
  16. package/dist/cjs/Grid.d.ts.map +1 -0
  17. package/dist/cjs/Grid.js +2446 -0
  18. package/dist/{GridLine.d.ts → cjs/GridLine.d.ts} +2 -1
  19. package/dist/cjs/GridLine.d.ts.map +1 -0
  20. package/dist/cjs/GridLine.js +10 -0
  21. package/dist/cjs/Image.d.ts +11 -0
  22. package/dist/cjs/Image.d.ts.map +1 -0
  23. package/dist/cjs/Image.js +57 -0
  24. package/dist/cjs/MemoizedGrid.d.ts +49 -0
  25. package/dist/cjs/MemoizedGrid.d.ts.map +1 -0
  26. package/dist/cjs/MemoizedGrid.js +105 -0
  27. package/dist/cjs/Selection.d.ts +5 -0
  28. package/dist/cjs/Selection.d.ts.map +1 -0
  29. package/dist/cjs/Selection.js +10 -0
  30. package/dist/{helpers.d.ts → cjs/helpers.d.ts} +59 -39
  31. package/dist/cjs/helpers.d.ts.map +1 -0
  32. package/dist/cjs/helpers.js +1409 -0
  33. package/dist/{hooks → cjs/hooks}/useCopyPaste.d.ts +3 -2
  34. package/dist/cjs/hooks/useCopyPaste.d.ts.map +1 -0
  35. package/dist/cjs/hooks/useCopyPaste.js +230 -0
  36. package/dist/{hooks → cjs/hooks}/useEditable.d.ts +21 -8
  37. package/dist/cjs/hooks/useEditable.d.ts.map +1 -0
  38. package/dist/cjs/hooks/useEditable.js +491 -0
  39. package/dist/{hooks → cjs/hooks}/useFilter.d.ts +3 -2
  40. package/dist/cjs/hooks/useFilter.d.ts.map +1 -0
  41. package/dist/cjs/hooks/useFilter.js +97 -0
  42. package/dist/{hooks → cjs/hooks}/useImage.d.ts +1 -0
  43. package/dist/cjs/hooks/useImage.d.ts.map +1 -0
  44. package/dist/cjs/hooks/useImage.js +80 -0
  45. package/dist/{hooks → cjs/hooks}/usePagination.d.ts +3 -2
  46. package/dist/cjs/hooks/usePagination.d.ts.map +1 -0
  47. package/dist/cjs/hooks/usePagination.js +53 -0
  48. package/dist/{hooks → cjs/hooks}/useSizer.d.ts +28 -29
  49. package/dist/cjs/hooks/useSizer.d.ts.map +1 -0
  50. package/dist/cjs/hooks/useSizer.js +176 -0
  51. package/dist/{hooks → cjs/hooks}/useTooltip.d.ts +2 -1
  52. package/dist/cjs/hooks/useTooltip.d.ts.map +1 -0
  53. package/dist/cjs/hooks/useTooltip.js +82 -0
  54. package/dist/{hooks → cjs/hooks}/useUndo.d.ts +2 -1
  55. package/dist/cjs/hooks/useUndo.d.ts.map +1 -0
  56. package/dist/cjs/hooks/useUndo.js +87 -0
  57. package/dist/cjs/index.d.ts +19 -0
  58. package/dist/cjs/index.d.ts.map +1 -0
  59. package/dist/cjs/index.js +29 -0
  60. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -0
  61. package/dist/{types.d.ts → cjs/types.d.ts} +8 -3
  62. package/dist/cjs/types.d.ts.map +1 -0
  63. package/dist/{types.js → cjs/types.js} +7 -8
  64. package/dist/cjs/utils.d.ts +9 -0
  65. package/dist/cjs/utils.d.ts.map +1 -0
  66. package/dist/cjs/utils.js +58 -0
  67. package/dist/cjs/vitest.config.d.ts +3 -0
  68. package/dist/cjs/vitest.config.d.ts.map +1 -0
  69. package/dist/cjs/vitest.config.js +24 -0
  70. package/dist/esm/Cell.d.ts +35 -0
  71. package/dist/esm/Cell.d.ts.map +1 -0
  72. package/dist/esm/Cell.js +24 -0
  73. package/dist/esm/CellOverlay.d.ts +21 -0
  74. package/dist/esm/CellOverlay.d.ts.map +1 -0
  75. package/dist/esm/CellOverlay.js +120 -0
  76. package/dist/esm/Context.d.ts +8 -0
  77. package/dist/esm/Context.d.ts.map +1 -0
  78. package/dist/esm/Context.js +9 -0
  79. package/dist/esm/FillHandle.d.ts +8 -0
  80. package/dist/esm/FillHandle.d.ts.map +1 -0
  81. package/dist/{FillHandle.js → esm/FillHandle.js} +5 -11
  82. package/dist/esm/Grid.d.ts +558 -0
  83. package/dist/esm/Grid.d.ts.map +1 -0
  84. package/dist/esm/Grid.js +2451 -0
  85. package/dist/esm/GridLine.d.ts +5 -0
  86. package/dist/esm/GridLine.d.ts.map +1 -0
  87. package/dist/esm/GridLine.js +7 -0
  88. package/dist/esm/Image.d.ts +11 -0
  89. package/dist/esm/Image.d.ts.map +1 -0
  90. package/dist/esm/Image.js +53 -0
  91. package/dist/esm/MemoizedGrid.d.ts +49 -0
  92. package/dist/esm/MemoizedGrid.d.ts.map +1 -0
  93. package/dist/esm/MemoizedGrid.js +98 -0
  94. package/dist/esm/Selection.d.ts +5 -0
  95. package/dist/esm/Selection.d.ts.map +1 -0
  96. package/dist/esm/Selection.js +7 -0
  97. package/dist/esm/helpers.d.ts +281 -0
  98. package/dist/esm/helpers.d.ts.map +1 -0
  99. package/dist/esm/helpers.js +1260 -0
  100. package/dist/esm/hooks/useCopyPaste.d.ts +53 -0
  101. package/dist/esm/hooks/useCopyPaste.d.ts.map +1 -0
  102. package/dist/{hooks → esm/hooks}/useCopyPaste.js +40 -46
  103. package/dist/esm/hooks/useEditable.d.ts +253 -0
  104. package/dist/esm/hooks/useEditable.d.ts.map +1 -0
  105. package/dist/esm/hooks/useEditable.js +482 -0
  106. package/dist/esm/hooks/useFilter.d.ts +69 -0
  107. package/dist/esm/hooks/useFilter.d.ts.map +1 -0
  108. package/dist/{hooks → esm/hooks}/useFilter.js +22 -42
  109. package/dist/esm/hooks/useImage.d.ts +13 -0
  110. package/dist/esm/hooks/useImage.d.ts.map +1 -0
  111. package/dist/esm/hooks/useImage.js +76 -0
  112. package/dist/esm/hooks/usePagination.d.ts +69 -0
  113. package/dist/esm/hooks/usePagination.d.ts.map +1 -0
  114. package/dist/esm/hooks/usePagination.js +47 -0
  115. package/dist/esm/hooks/useSizer.d.ts +114 -0
  116. package/dist/esm/hooks/useSizer.d.ts.map +1 -0
  117. package/dist/esm/hooks/useSizer.js +176 -0
  118. package/dist/esm/hooks/useTooltip.d.ts +43 -0
  119. package/dist/esm/hooks/useTooltip.d.ts.map +1 -0
  120. package/dist/esm/hooks/useTooltip.js +80 -0
  121. package/dist/esm/hooks/useUndo.d.ts +36 -0
  122. package/dist/esm/hooks/useUndo.d.ts.map +1 -0
  123. package/dist/{hooks → esm/hooks}/useUndo.js +16 -25
  124. package/dist/esm/index.d.ts +19 -0
  125. package/dist/esm/index.d.ts.map +1 -0
  126. package/dist/esm/index.js +17 -0
  127. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -0
  128. package/dist/esm/types.d.ts +70 -0
  129. package/dist/esm/types.d.ts.map +1 -0
  130. package/dist/esm/types.js +68 -0
  131. package/dist/esm/utils.d.ts +9 -0
  132. package/dist/esm/utils.d.ts.map +1 -0
  133. package/dist/esm/utils.js +104 -0
  134. package/dist/esm/vitest.config.d.ts +3 -0
  135. package/dist/esm/vitest.config.d.ts.map +1 -0
  136. package/dist/esm/vitest.config.js +22 -0
  137. package/package.json +28 -29
  138. package/dist/Cell.d.ts +0 -23
  139. package/dist/Cell.js +0 -55
  140. package/dist/Cell.js.map +0 -1
  141. package/dist/CellOverlay.js +0 -116
  142. package/dist/CellOverlay.js.map +0 -1
  143. package/dist/FillHandle.js.map +0 -1
  144. package/dist/Grid.d.ts +0 -385
  145. package/dist/Grid.js +0 -1684
  146. package/dist/Grid.js.map +0 -1
  147. package/dist/GridLine.js +0 -13
  148. package/dist/GridLine.js.map +0 -1
  149. package/dist/Image.d.ts +0 -8
  150. package/dist/Image.js +0 -50
  151. package/dist/Image.js.map +0 -1
  152. package/dist/Selection.d.ts +0 -4
  153. package/dist/Selection.js +0 -9
  154. package/dist/Selection.js.map +0 -1
  155. package/dist/helpers.js +0 -1033
  156. package/dist/helpers.js.map +0 -1
  157. package/dist/hooks/useCopyPaste.js.map +0 -1
  158. package/dist/hooks/useEditable.js +0 -477
  159. package/dist/hooks/useEditable.js.map +0 -1
  160. package/dist/hooks/useFilter.js.map +0 -1
  161. package/dist/hooks/useImage.js +0 -42
  162. package/dist/hooks/useImage.js.map +0 -1
  163. package/dist/hooks/usePagination.js +0 -73
  164. package/dist/hooks/usePagination.js.map +0 -1
  165. package/dist/hooks/useSelection.d.ts +0 -175
  166. package/dist/hooks/useSelection.js +0 -982
  167. package/dist/hooks/useSelection.js.map +0 -1
  168. package/dist/hooks/useSizer.js +0 -150
  169. package/dist/hooks/useSizer.js.map +0 -1
  170. package/dist/hooks/useTooltip.js +0 -106
  171. package/dist/hooks/useTooltip.js.map +0 -1
  172. package/dist/hooks/useTouch.d.ts +0 -21
  173. package/dist/hooks/useTouch.js +0 -73
  174. package/dist/hooks/useTouch.js.map +0 -1
  175. package/dist/hooks/useUndo.js.map +0 -1
  176. package/dist/index.d.ts +0 -25
  177. package/dist/index.js +0 -61
  178. package/dist/index.js.map +0 -1
  179. package/dist/types.js.map +0 -1
  180. package/dist/utils.d.ts +0 -8
  181. package/dist/utils.js +0 -153
  182. package/dist/utils.js.map +0 -1
  183. package/src/Cell.tsx +0 -100
  184. package/src/CellOverlay.tsx +0 -191
  185. package/src/FillHandle.tsx +0 -37
  186. package/src/Grid.tsx +0 -2677
  187. package/src/GridLine.tsx +0 -23
  188. package/src/Image.tsx +0 -43
  189. package/src/Selection.tsx +0 -9
  190. package/src/helpers.ts +0 -1455
  191. package/src/hooks/useCopyPaste.ts +0 -265
  192. package/src/hooks/useEditable.tsx +0 -945
  193. package/src/hooks/useFilter.tsx +0 -233
  194. package/src/hooks/useImage.ts +0 -57
  195. package/src/hooks/usePagination.tsx +0 -153
  196. package/src/hooks/useSelection.ts +0 -1440
  197. package/src/hooks/useSizer.ts +0 -312
  198. package/src/hooks/useTooltip.tsx +0 -172
  199. package/src/hooks/useTouch.ts +0 -116
  200. package/src/hooks/useUndo.ts +0 -134
  201. package/src/index.tsx +0 -46
  202. package/src/tests/Grid.spec.tsx +0 -47
  203. package/src/tests/helpers.test.ts +0 -292
  204. package/src/tests/usePagination.test.ts +0 -28
  205. package/src/tests/useSelection.test.ts +0 -27
  206. package/src/tests/useUndo.test.ts +0 -73
  207. package/src/types.ts +0 -69
  208. package/src/utils.tsx +0 -287
@@ -1,1440 +0,0 @@
1
- import React, {
2
- useState,
3
- useCallback,
4
- useRef,
5
- useEffect,
6
- useMemo,
7
- useReducer,
8
- } from "react";
9
- import { SelectionArea, CellInterface, GridRef, AreaProps } from "./../Grid";
10
- import {
11
- findNextCellWithinBounds,
12
- Align,
13
- getBoundedCells,
14
- cellIdentifier,
15
- isEqualCells,
16
- clampIndex,
17
- HiddenType,
18
- findNextCellInDataRegion,
19
- selectionSpansCells,
20
- newSelectionFromDrag,
21
- clampCellCoords,
22
- cellRangeToBounds,
23
- } from "./../helpers";
24
- import {
25
- KeyCodes,
26
- Direction,
27
- MouseButtonCodes,
28
- SelectionPolicy,
29
- } from "./../types";
30
-
31
- export interface UseSelectionOptions {
32
- /**
33
- * Access grid functions
34
- */
35
- gridRef: React.MutableRefObject<GridRef | null>;
36
- /**
37
- * Initial selections
38
- */
39
- initialSelections?: SelectionArea[];
40
- /**
41
- * Option to set 0,0 as initially selected cell
42
- */
43
- initialActiveCell?: CellInterface | null;
44
- /**
45
- * No of columns in the grid
46
- */
47
- columnCount?: number;
48
- /**
49
- * No of rows in the grid
50
- */
51
- rowCount?: number;
52
- /**
53
- * Allow deselect a selected area
54
- */
55
- allowDeselectSelection?: boolean;
56
- /**
57
- * onFill
58
- */
59
- onFill?: (
60
- activeCell: CellInterface,
61
- selection: SelectionArea | null,
62
- selections: SelectionArea[]
63
- ) => void;
64
- /**
65
- * Hidden rows
66
- */
67
- isHiddenRow?: HiddenType;
68
- /**
69
- * Hidden columns
70
- */
71
- isHiddenColumn?: HiddenType;
72
- /**
73
- * Always scroll to active cell
74
- */
75
- alwaysScrollToActiveCell?: boolean;
76
- /**
77
- * Top bound of selection
78
- */
79
- selectionTopBound?: number;
80
- /**
81
- * Bottom bound
82
- */
83
- selectionBottomBound?: number;
84
- /**
85
- * Left bound
86
- */
87
- selectionLeftBound?: number;
88
- /**
89
- * Right bound
90
- */
91
- selectionRightBound?: number;
92
- /**
93
- * merged cells
94
- */
95
- mergedCells?: AreaProps[];
96
- /**
97
- * Mousedown
98
- */
99
- mouseDownInterceptor?: (
100
- e: React.MouseEvent<HTMLDivElement>,
101
- coords: CellInterface,
102
- start: React.MutableRefObject<CellInterface | null>,
103
- end: React.MutableRefObject<CellInterface | null>
104
- ) => boolean | undefined;
105
- mouseMoveInterceptor?: (
106
- e: globalThis.MouseEvent,
107
- coords: CellInterface,
108
- start: React.MutableRefObject<CellInterface | null>,
109
- end: React.MutableRefObject<CellInterface | null>
110
- ) => boolean | undefined;
111
- canSelectionSpanMergedCells?: (
112
- start: CellInterface,
113
- end: CellInterface
114
- ) => boolean;
115
- /**
116
- * Selection policy
117
- */
118
- selectionPolicy?: SelectionPolicy;
119
- /**
120
- * Value getter
121
- */
122
- getValue: (cell: CellInterface) => string | number | undefined;
123
- /**
124
- * New selection mode
125
- */
126
- newSelectionMode?: NewSelectionMode;
127
- /**
128
- * When selection is moved
129
- */
130
- onSelectionMove?: (from: SelectionArea, to: SelectionArea) => void;
131
- }
132
-
133
- export type NewSelectionMode = "clear" | "modify" | "append";
134
-
135
- export interface SelectionResults {
136
- /**
137
- * Active selected cell
138
- */
139
- activeCell: CellInterface | null;
140
- /**
141
- * Use this to invoke a new selection. All old selection will be cleared
142
- */
143
- newSelection: (coords: CellInterface) => void;
144
- /**
145
- * Use this to update selections without clearning old selection.
146
- */
147
- setSelections: (
148
- selections:
149
- | SelectionArea[]
150
- | ((selections: SelectionArea[]) => SelectionArea[])
151
- ) => void;
152
- /**
153
- * Modify selectio
154
- */
155
- modifySelection: (coords: CellInterface) => void;
156
- /**
157
- * Set the currently active cell
158
- */
159
- setActiveCell: (coords: CellInterface | null, shouldScroll?: boolean) => void;
160
- /**
161
- * Only saves internal state
162
- */
163
- setActiveCellState: (coords: CellInterface | null) => void;
164
- /**
165
- * Array of all selection bounds
166
- */
167
- selections: SelectionArea[];
168
- /**
169
- * Handler for mousedown, use to set activeCell
170
- */
171
- onMouseDown: (e: React.MouseEvent<HTMLDivElement>) => void;
172
- /**
173
- * Used to move selections based on pressed key
174
- */
175
- onKeyDown: (e: React.KeyboardEvent<HTMLElement>) => void;
176
- /**
177
- * Mousedown event on fillhandle
178
- */
179
- fillHandleProps?: Record<string, (e: any) => void>;
180
- /**
181
- *
182
- * Fill selections
183
- */
184
- fillSelection: SelectionArea | undefined;
185
- /**
186
- * Clears the last selection
187
- */
188
- clearLastSelection: () => void;
189
- /**
190
- * Select all cells
191
- */
192
- selectAll: () => void;
193
- /**
194
- * Add a new selection
195
- */
196
- appendSelection: (start: CellInterface, end: CellInterface) => void;
197
- /**
198
- * Clear all current selections
199
- */
200
- clearSelections: () => void;
201
- /**
202
- * On Selection mousedown
203
- */
204
- onSelectionMouseDown?: (
205
- e: React.MouseEvent<HTMLDivElement>,
206
- cell: CellInterface | undefined,
207
- selection: SelectionArea | undefined,
208
- index: number | undefined,
209
- shouldClamp?: boolean
210
- ) => void;
211
- /**
212
- * Boolean to indicate if a selection is being dragged
213
- */
214
- isDragging?: boolean;
215
- /**
216
- * Currently dragged selection
217
- */
218
- draggedSelection?: SelectionArea;
219
- /**
220
- * The selection that user selected before beginning
221
- * the drag
222
- */
223
- initialDraggedSelection?: SelectionArea;
224
- }
225
-
226
- const EMPTY_SELECTION: SelectionArea[] = [];
227
- const defaultIsHidden = (i: number) => false;
228
- const defaultSelectionSpan = () => true;
229
- /**
230
- * Hook to enable selection in datagrid
231
- * @param initialSelection
232
- */
233
- const useSelection = ({
234
- gridRef,
235
- initialActiveCell = null,
236
- initialSelections = EMPTY_SELECTION,
237
- columnCount = 0,
238
- rowCount = 0,
239
- selectionPolicy = "multiple",
240
- newSelectionMode = "clear",
241
- allowDeselectSelection = true,
242
- onFill,
243
- isHiddenRow = defaultIsHidden,
244
- isHiddenColumn = defaultIsHidden,
245
- alwaysScrollToActiveCell = true,
246
- selectionTopBound = 0,
247
- selectionBottomBound = rowCount - 1,
248
- selectionLeftBound = 0,
249
- selectionRightBound = columnCount - 1,
250
- mouseDownInterceptor,
251
- mouseMoveInterceptor,
252
- mergedCells = [],
253
- canSelectionSpanMergedCells = defaultSelectionSpan,
254
- getValue,
255
- onSelectionMove,
256
- }: UseSelectionOptions): SelectionResults => {
257
- const [activeCell, setActiveCell] = useState<CellInterface | null>(
258
- initialActiveCell
259
- );
260
- const [selections, setSelections] =
261
- useState<SelectionArea[]>(initialSelections);
262
- const [fillSelection, setFillSelection] = useState<SelectionArea>();
263
- const selectionStart = useRef<CellInterface | null>(null);
264
- const selectionEnd = useRef<CellInterface | null>(null);
265
- const isSelecting = useRef<boolean>(false);
266
- const isFilling = useRef<boolean>(false);
267
- const firstActiveCell = useRef<CellInterface | null>(null);
268
- const fillSelectionRef = useRef<SelectionArea>();
269
-
270
- /* Drag drop selection */
271
- const [_, forceRender] = useReducer((s) => s + 1, 0);
272
- const isDragging = useRef<boolean>(false);
273
- const hasUserMovedSelection = useRef<boolean>(false);
274
- const initialDraggedSelection = useRef<SelectionArea>();
275
- const initialDraggedCell = useRef<CellInterface>();
276
- const draggedSelection = useRef<SelectionArea>();
277
- const draggedSelectionIndex = useRef<number>();
278
- /**
279
- * Need to store in ref because on mousemove and mouseup event that are
280
- * registered in document
281
- */
282
- const activeCellRef = useRef(activeCell);
283
- const activeSelectionsRef = useRef(selections);
284
- const mergedCellsRef = useRef(mergedCells);
285
-
286
- useEffect(() => {
287
- activeCellRef.current = activeCell;
288
- }, [activeCell]);
289
- useEffect(() => {
290
- activeSelectionsRef.current = selections;
291
- }, [selections]);
292
- useEffect(() => {
293
- mergedCellsRef.current = mergedCells;
294
- }, [mergedCells]);
295
- useEffect(() => {
296
- fillSelectionRef.current = fillSelection;
297
- }, [fillSelection]);
298
-
299
- /* Check if cell is out of bounds */
300
- const isCellOutOfBounds = useCallback(
301
- (cell: CellInterface) => {
302
- return (
303
- cell.rowIndex < selectionTopBound ||
304
- cell.columnIndex < selectionLeftBound
305
- );
306
- },
307
- [selectionTopBound, selectionLeftBound]
308
- );
309
-
310
- /* New selection */
311
- const newSelection = (start: CellInterface, end: CellInterface = start) => {
312
- /* Validate bounds */
313
- if (isCellOutOfBounds(start)) {
314
- return;
315
- }
316
- selectionStart.current = start;
317
- selectionEnd.current = end;
318
- const bounds = selectionFromStartEnd(start, end);
319
- if (!bounds) return;
320
- const coords = { rowIndex: bounds.top, columnIndex: bounds.left };
321
- /* Keep track of first cell that was selected by user */
322
- setActiveCell(coords);
323
- if (newSelectionMode === "clear") {
324
- firstActiveCell.current = coords;
325
- clearSelections();
326
- } else if (newSelectionMode === "modify") {
327
- modifySelection(coords);
328
- } else {
329
- appendSelection(coords);
330
- }
331
- };
332
-
333
- /**
334
- * selection object from start, end
335
- * @param start
336
- * @param end
337
- */
338
- const selectionFromStartEnd = (start: CellInterface, end: CellInterface) => {
339
- if (!gridRef?.current) return null;
340
- const spanMerges = canSelectionSpanMergedCells?.(start, end);
341
- return cellRangeToBounds(
342
- start,
343
- end,
344
- spanMerges,
345
- gridRef.current.getCellBounds
346
- );
347
- };
348
-
349
- /* Modify current selection */
350
- const modifySelection = (coords: CellInterface, setInProgress?: boolean) => {
351
- if (selectionPolicy === "single") {
352
- return;
353
- }
354
- if (!selectionStart.current) return;
355
- if (isCellOutOfBounds(coords)) {
356
- return;
357
- }
358
- selectionEnd.current = coords;
359
- const bounds = selectionFromStartEnd(selectionStart.current, coords);
360
- if (!bounds) return;
361
-
362
- /**
363
- * 1. Multiple selections on mousedown/mousemove
364
- * 2. Move the activeCell to newly selection. Done by appendSelection
365
- */
366
- setSelections((prevSelection) => {
367
- const len = prevSelection.length;
368
- if (!len) {
369
- return [{ bounds, inProgress: setInProgress ? true : false }];
370
- }
371
- return prevSelection.map((sel, i) => {
372
- if (len - 1 === i) {
373
- return {
374
- ...sel,
375
- bounds,
376
- inProgress: setInProgress ? true : false,
377
- };
378
- }
379
- return sel;
380
- });
381
- });
382
- };
383
-
384
- /* Adds a new selection, CMD key */
385
- const appendSelection = (
386
- start: CellInterface,
387
- end: CellInterface = start
388
- ) => {
389
- if (selectionPolicy !== "multiple") {
390
- return;
391
- }
392
- if (!start) return;
393
- /* Validate bounds */
394
- if (isCellOutOfBounds(start)) {
395
- return;
396
- }
397
- selectionStart.current = start;
398
- selectionEnd.current = end;
399
- const bounds = selectionFromStartEnd(start, end);
400
- if (!bounds) return;
401
- setActiveCell({ rowIndex: bounds.top, columnIndex: bounds.left });
402
- setSelections((prev) => [...prev, { bounds }]);
403
- };
404
-
405
- const removeSelectionByIndex = useCallback(
406
- (index: number): SelectionArea[] => {
407
- const newSelection = selections.filter((_, idx) => idx !== index);
408
- setSelections(newSelection);
409
- return newSelection;
410
- },
411
- [selections]
412
- );
413
-
414
- const clearSelections = () => {
415
- setSelections(EMPTY_SELECTION);
416
- };
417
-
418
- const getPossibleActiveCellFromSelections = (
419
- selections: SelectionArea[]
420
- ): CellInterface | null => {
421
- if (!selections.length) return null;
422
- const { bounds } = selections[selections.length - 1];
423
- return {
424
- rowIndex: bounds.top,
425
- columnIndex: bounds.left,
426
- };
427
- };
428
-
429
- const cellIndexInSelection = (
430
- cell: CellInterface,
431
- selections: SelectionArea[]
432
- ) => {
433
- const id = cellIdentifier(
434
- Math.max(selectionTopBound, cell.rowIndex),
435
- Math.max(selectionLeftBound, cell.columnIndex)
436
- );
437
- return selections.findIndex((sel) => {
438
- const boundedCells = getBoundedCells(sel.bounds);
439
- return boundedCells.has(id);
440
- });
441
- };
442
-
443
- const cellEqualsSelection = (
444
- cell: CellInterface | null,
445
- selections: SelectionArea[]
446
- ): boolean => {
447
- if (cell === null) return false;
448
- return selections.some((sel) => {
449
- return (
450
- sel.bounds.left === cell.columnIndex &&
451
- sel.bounds.top === cell.rowIndex &&
452
- sel.bounds.right === cell.columnIndex &&
453
- sel.bounds.bottom === cell.rowIndex
454
- );
455
- });
456
- };
457
-
458
- const boundsSubsetOfSelection = (bounds: AreaProps, selection: AreaProps) => {
459
- return (
460
- bounds.top >= selection.top &&
461
- bounds.bottom <= selection.bottom &&
462
- bounds.left >= selection.left &&
463
- bounds.right <= selection.right
464
- );
465
- };
466
-
467
- /**
468
- * Triggers a new selection start
469
- */
470
- const handleMouseDown = useCallback(
471
- (e: React.MouseEvent<HTMLDivElement>) => {
472
- /* Exit early if grid is not initialized */
473
- if (!gridRef || !gridRef.current) return;
474
- const coords = gridRef.current.getCellCoordsFromOffset(
475
- e.nativeEvent.clientX,
476
- e.nativeEvent.clientY
477
- );
478
- if (!coords) return;
479
- /* Check if its context menu click */
480
- const isContextMenuClick = e.nativeEvent.which === MouseButtonCodes.right;
481
- if (isContextMenuClick) {
482
- const cellIndex = cellIndexInSelection(coords, selections);
483
- if (cellIndex !== -1) return;
484
- }
485
- const isShiftKey = e.nativeEvent.shiftKey;
486
- const isMetaKey = e.nativeEvent.ctrlKey || e.nativeEvent.metaKey;
487
- const allowMultiple = isMetaKey;
488
- const allowDeselect = allowDeselectSelection;
489
- const hasSelections = selections.length > 0;
490
- const isDeselecting = isMetaKey && allowDeselect;
491
-
492
- if (!isContextMenuClick && selectionPolicy !== "single") {
493
- document.addEventListener("mouseup", handleMouseUp);
494
- document.addEventListener("mousemove", handleMouseMove);
495
- }
496
-
497
- /* Activate selection mode */
498
- isSelecting.current = true;
499
-
500
- if (
501
- mouseDownInterceptor?.(e, coords, selectionStart, selectionEnd) ===
502
- false
503
- ) {
504
- return;
505
- }
506
-
507
- /* Shift key */
508
- if (isShiftKey) {
509
- modifySelection(coords);
510
- return;
511
- }
512
-
513
- /* Is the current cell same as active cell */
514
- const isSameAsActiveCell = isEqualCells(coords, activeCell);
515
-
516
- /* Command or Control key */
517
- if (activeCell && allowMultiple) {
518
- /**
519
- * User is adding activeCell to selection
520
- *
521
- * 1. User is selecting and not de-selecting
522
- * 2. User has not made any selection
523
- * 3. Trying to add active cell to selection
524
- */
525
- if (isSameAsActiveCell && (!isDeselecting || !hasSelections)) {
526
- return;
527
- }
528
-
529
- /**
530
- * User is manually trying to select multiple selections,
531
- * So add the current active cell to the list
532
- */
533
- if (isMetaKey && !hasSelections) {
534
- appendSelection(activeCell);
535
- }
536
-
537
- /**
538
- * Check if this cell has already been selected (only for manual deselect)
539
- * Remove it from selection
540
- *
541
- * Future enhancements -> Split selection, so that 1 cell can be removed from range
542
- */
543
- if (isMetaKey && allowDeselect) {
544
- const cellIndex = cellIndexInSelection(coords, selections);
545
- if (cellIndex !== -1) {
546
- const newSelection = removeSelectionByIndex(cellIndex);
547
- const nextActiveCell =
548
- getPossibleActiveCellFromSelections(newSelection);
549
- if (nextActiveCell !== null) {
550
- setActiveCell(nextActiveCell);
551
- }
552
- if (
553
- newSelection.length === 1 &&
554
- cellEqualsSelection(nextActiveCell, newSelection)
555
- ) {
556
- /* Since we only have 1 cell, lets clear the selections and only keep activeCell */
557
- clearSelections();
558
- }
559
- return;
560
- }
561
- }
562
-
563
- /**
564
- * TODO
565
- * 1. Ability to remove selection
566
- * 2. Ability to remove from selection area
567
- * 3. Ability to switch activeCell if its part of removed selection
568
- */
569
- appendSelection(coords);
570
- return;
571
- }
572
-
573
- /**
574
- * Scroll to the selected cell
575
- */
576
- if (alwaysScrollToActiveCell) {
577
- gridRef.current?.scrollToItem(coords);
578
- }
579
-
580
- /**
581
- * If user is selecting the same same,
582
- * let not trigger another state change
583
- */
584
- if (isSameAsActiveCell) return;
585
-
586
- /* Trigger new selection */
587
- newSelection(coords);
588
- },
589
- [
590
- activeCell,
591
- selections,
592
- selectionPolicy,
593
- allowDeselectSelection,
594
- alwaysScrollToActiveCell,
595
- rowCount,
596
- columnCount,
597
- newSelectionMode,
598
- ]
599
- );
600
-
601
- /**
602
- * Mousemove handler
603
- */
604
- const handleMouseMove = useCallback((e: globalThis.MouseEvent) => {
605
- /* Exit if user is not in selection mode */
606
- if (!isSelecting.current || !gridRef?.current) return;
607
-
608
- const coords = gridRef.current.getCellCoordsFromOffset(
609
- e.clientX,
610
- e.clientY
611
- );
612
-
613
- if (!coords) return;
614
-
615
- if (
616
- mouseMoveInterceptor?.(e, coords, selectionStart, selectionEnd) === false
617
- ) {
618
- return;
619
- }
620
-
621
- if (isEqualCells(firstActiveCell.current, coords)) {
622
- return clearSelections();
623
- }
624
-
625
- modifySelection(coords, true);
626
-
627
- gridRef.current?.scrollToItem(coords);
628
- }, []);
629
- /**
630
- * Mouse up handler
631
- */
632
- const handleMouseUp = useCallback(() => {
633
- /* Reset selection mode */
634
- isSelecting.current = false;
635
-
636
- /* Remove listener */
637
- document.removeEventListener("mousemove", handleMouseMove);
638
- document.removeEventListener("mouseup", handleMouseUp);
639
-
640
- /* Update last selection */
641
- setSelections((prevSelection) => {
642
- const len = prevSelection.length;
643
- if (!len) return EMPTY_SELECTION;
644
- return prevSelection.map((sel, i) => {
645
- if (len - 1 === i) {
646
- return {
647
- ...sel,
648
- inProgress: false,
649
- };
650
- }
651
- return sel;
652
- });
653
- });
654
- }, []);
655
-
656
- /**
657
- * Navigate selection using keyboard
658
- * @param direction
659
- * @param modify
660
- */
661
- const keyNavigate = useCallback(
662
- (direction: Direction, modify?: boolean, metaKeyPressed?: boolean) => {
663
- if (
664
- !selectionStart.current ||
665
- !selectionEnd.current ||
666
- !gridRef?.current ||
667
- !activeCell
668
- )
669
- return;
670
- const currentCell = modify ? selectionEnd.current : activeCell;
671
- var { rowIndex, columnIndex } = currentCell;
672
- const isMergedCell = gridRef?.current.isMergedCell({
673
- rowIndex,
674
- columnIndex,
675
- });
676
-
677
- const currentBounds = gridRef.current.getCellBounds({
678
- rowIndex,
679
- columnIndex,
680
- });
681
-
682
- switch (direction) {
683
- case Direction.Up:
684
- if (isMergedCell) rowIndex = currentBounds.top;
685
- rowIndex = clampIndex(
686
- Math.max(rowIndex - 1, selectionTopBound),
687
- isHiddenRow,
688
- direction
689
- );
690
- // Shift + Ctrl/Commmand
691
- // TODO: Scroll to last contentful cell
692
- if (metaKeyPressed) {
693
- const startCell = {
694
- ...currentCell,
695
- // Expand from the starting cell
696
- columnIndex: selectionStart.current.columnIndex,
697
- };
698
- rowIndex = findNextCellInDataRegion(
699
- startCell,
700
- getValue,
701
- isHiddenRow,
702
- direction,
703
- selectionTopBound
704
- );
705
- }
706
- break;
707
-
708
- case Direction.Down:
709
- if (isMergedCell) rowIndex = currentBounds.bottom;
710
- rowIndex = clampIndex(
711
- Math.min(rowIndex + 1, selectionBottomBound),
712
- isHiddenRow,
713
- direction
714
- );
715
- // Shift + Ctrl/Commmand
716
- if (metaKeyPressed) {
717
- const startCell = {
718
- ...currentCell,
719
- // Expand from the starting cell
720
- columnIndex: selectionStart.current.columnIndex,
721
- };
722
- rowIndex = findNextCellInDataRegion(
723
- startCell,
724
- getValue,
725
- isHiddenRow,
726
- direction,
727
- selectionBottomBound
728
- );
729
- }
730
- break;
731
-
732
- case Direction.Left:
733
- if (isMergedCell) columnIndex = currentBounds.left;
734
- columnIndex = clampIndex(
735
- Math.max(columnIndex - 1, selectionLeftBound),
736
- isHiddenColumn,
737
- direction
738
- );
739
- // Shift + Ctrl/Commmand
740
- if (metaKeyPressed) {
741
- const startCell = {
742
- ...currentCell,
743
- // Expand from the starting cell
744
- rowIndex: selectionStart.current.rowIndex,
745
- };
746
- columnIndex = findNextCellInDataRegion(
747
- startCell,
748
- getValue,
749
- isHiddenColumn,
750
- direction,
751
- selectionLeftBound
752
- );
753
- }
754
- break;
755
-
756
- case Direction.Right:
757
- if (isMergedCell) columnIndex = currentBounds.right;
758
- columnIndex = clampIndex(
759
- Math.min(columnIndex + 1, selectionRightBound),
760
- isHiddenColumn,
761
- direction
762
- );
763
- // Shift + Ctrl/Commmand
764
- if (metaKeyPressed) {
765
- const startCell = {
766
- ...currentCell,
767
- // Expand from the starting cell
768
- rowIndex: selectionStart.current.rowIndex,
769
- };
770
- columnIndex = findNextCellInDataRegion(
771
- startCell,
772
- getValue,
773
- isHiddenColumn,
774
- direction,
775
- selectionRightBound
776
- );
777
- }
778
- break;
779
- }
780
-
781
- const newBounds = gridRef.current.getCellBounds({
782
- rowIndex,
783
- columnIndex,
784
- });
785
- const coords = { rowIndex: newBounds.top, columnIndex: newBounds.left };
786
- const scrollToCell = modify
787
- ? selectionEnd.current.rowIndex === coords.rowIndex
788
- ? // Scroll to a column
789
- { columnIndex: coords.columnIndex }
790
- : // Scroll to row
791
- { rowIndex: coords.rowIndex }
792
- : // Scroll to cell
793
- { rowIndex, columnIndex };
794
-
795
- const isUserNavigatingToActiveCell = isEqualCells(
796
- firstActiveCell.current,
797
- coords
798
- );
799
-
800
- if (modify && !isUserNavigatingToActiveCell) {
801
- modifySelection(coords);
802
- } else {
803
- newSelection(coords);
804
- }
805
-
806
- /* Keep the item in view */
807
- gridRef.current.scrollToItem(scrollToCell);
808
- },
809
- [
810
- activeCell,
811
- isHiddenRow,
812
- isHiddenColumn,
813
- selectionLeftBound,
814
- selectionTopBound,
815
- selectionPolicy,
816
- newSelectionMode,
817
- ]
818
- );
819
-
820
- // ⌘A or ⌘+Shift+Space
821
- const selectAll = () => {
822
- selectionStart.current = {
823
- rowIndex: selectionTopBound,
824
- columnIndex: selectionLeftBound,
825
- };
826
- modifySelection({
827
- rowIndex: selectionBottomBound,
828
- columnIndex: selectionRightBound,
829
- });
830
- };
831
-
832
- // Ctrl+Space
833
- const selectColumn = useCallback(() => {
834
- if (!selectionEnd.current || !selectionStart.current) return;
835
- selectionStart.current = {
836
- rowIndex: selectionTopBound,
837
- columnIndex: selectionStart.current.columnIndex,
838
- };
839
- modifySelection({
840
- rowIndex: rowCount - 1,
841
- columnIndex: selectionEnd.current.columnIndex,
842
- });
843
- }, [selectionTopBound]);
844
-
845
- // Shift+Space
846
- const selectRow = useCallback(() => {
847
- if (!selectionEnd.current || !selectionStart.current) return;
848
- selectionStart.current = {
849
- rowIndex: selectionStart.current.rowIndex,
850
- columnIndex: selectionLeftBound,
851
- };
852
- modifySelection({
853
- rowIndex: selectionEnd.current.rowIndex,
854
- columnIndex: selectionRightBound,
855
- });
856
- }, [selectionLeftBound]);
857
-
858
- // Home
859
- const selectFirstCellInRow = () => {
860
- if (!selectionStart.current || !gridRef?.current) return;
861
- const cell = {
862
- rowIndex: selectionStart.current.rowIndex,
863
- columnIndex: selectionLeftBound,
864
- };
865
- newSelection(cell);
866
-
867
- gridRef?.current.scrollToItem(cell);
868
- };
869
- // End
870
- const selectLastCellInRow = () => {
871
- if (!selectionStart.current || !gridRef?.current) return;
872
- const cell = {
873
- rowIndex: selectionStart.current.rowIndex,
874
- columnIndex: selectionRightBound,
875
- };
876
- newSelection(cell);
877
- gridRef?.current.scrollToItem(cell);
878
- };
879
-
880
- // ⌘+Home
881
- const selectFirstCellInColumn = () => {
882
- if (!selectionStart.current || !gridRef?.current) return;
883
- const cell = {
884
- rowIndex: selectionTopBound,
885
- columnIndex: selectionStart.current.columnIndex,
886
- };
887
- newSelection(cell);
888
-
889
- gridRef?.current.scrollToItem(cell);
890
- };
891
- // ⌘+End
892
- const selectLastCellInColumn = () => {
893
- if (!selectionStart.current || !gridRef?.current) return;
894
- const cell = {
895
- rowIndex: rowCount - 1,
896
- columnIndex: selectionStart.current.columnIndex,
897
- };
898
- newSelection(cell);
899
- gridRef?.current.scrollToItem(cell);
900
- };
901
-
902
- // ⌘+Backspace
903
- const scrollToActiveCell = () => {
904
- if (!activeCell || !gridRef?.current) return;
905
- gridRef?.current.scrollToItem(activeCell, Align.smart);
906
- };
907
-
908
- // Page down
909
- const pageDown = () => {
910
- if (!activeCell || !gridRef?.current) return;
911
- const { visibleRowStartIndex, visibleRowStopIndex } =
912
- gridRef.current.getViewPort();
913
- const pageSize = visibleRowStopIndex - visibleRowStartIndex;
914
- const rowIndex = Math.min(activeCell.rowIndex + pageSize, rowCount - 1);
915
- const newActiveCell = {
916
- rowIndex,
917
- columnIndex: activeCell.columnIndex,
918
- };
919
- handleSetActiveCell(newActiveCell, false);
920
- /* Scroll to the new row */
921
- gridRef?.current.scrollToItem({ rowIndex }, Align.end);
922
- };
923
-
924
- // Page up
925
- const pageUp = () => {
926
- if (!activeCell || !gridRef?.current) return;
927
- const { visibleRowStartIndex, visibleRowStopIndex } =
928
- gridRef.current.getViewPort();
929
- const pageSize = visibleRowStopIndex - visibleRowStartIndex;
930
- const rowIndex = Math.max(
931
- activeCell.rowIndex - pageSize,
932
- selectionTopBound
933
- );
934
- const newActiveCell = {
935
- rowIndex,
936
- columnIndex: activeCell.columnIndex,
937
- };
938
- handleSetActiveCell(newActiveCell, false);
939
- /* Scroll to the new row */
940
- gridRef?.current.scrollToItem({ rowIndex }, Align.end);
941
- };
942
-
943
- // Page right
944
- const pageRight = () => {
945
- if (!activeCell || !gridRef?.current) return;
946
- const { visibleColumnStartIndex, visibleColumnStopIndex } =
947
- gridRef.current.getViewPort();
948
- const pageSize = visibleColumnStopIndex - visibleColumnStartIndex;
949
- const columnIndex = Math.min(
950
- activeCell.columnIndex + pageSize,
951
- selectionRightBound
952
- );
953
- const newActiveCell = {
954
- columnIndex,
955
- rowIndex: activeCell.rowIndex,
956
- };
957
- handleSetActiveCell(newActiveCell, false);
958
- /* Scroll to the new row */
959
- gridRef?.current.scrollToItem({ columnIndex }, Align.end);
960
- };
961
-
962
- // Page left
963
- const pageLeft = () => {
964
- if (!activeCell || !gridRef?.current) return;
965
- const { visibleColumnStartIndex, visibleColumnStopIndex } =
966
- gridRef.current.getViewPort();
967
- const pageSize = visibleColumnStopIndex - visibleColumnStartIndex;
968
- const columnIndex = Math.max(
969
- activeCell.columnIndex - pageSize,
970
- selectionLeftBound
971
- );
972
- const newActiveCell = {
973
- columnIndex,
974
- rowIndex: activeCell.rowIndex,
975
- };
976
- handleSetActiveCell(newActiveCell, false);
977
- /* Scroll to the new row */
978
- gridRef?.current.scrollToItem({ columnIndex }, Align.end);
979
- };
980
-
981
- const handleKeyDown = useCallback(
982
- (e: React.KeyboardEvent) => {
983
- if (!gridRef?.current) return;
984
- const isShiftKey = e.nativeEvent.shiftKey;
985
- const isAltKey = e.nativeEvent.altKey;
986
- const isMetaKey = e.nativeEvent.ctrlKey || e.nativeEvent.metaKey;
987
- switch (e.nativeEvent.which) {
988
- case KeyCodes.Right:
989
- keyNavigate(Direction.Right, isShiftKey, isMetaKey);
990
- e.preventDefault();
991
- break;
992
-
993
- case KeyCodes.Left:
994
- keyNavigate(Direction.Left, isShiftKey, isMetaKey);
995
- e.preventDefault();
996
- break;
997
-
998
- // Up
999
- case KeyCodes.Up:
1000
- keyNavigate(Direction.Up, isShiftKey, isMetaKey);
1001
- e.preventDefault();
1002
- break;
1003
-
1004
- case KeyCodes.Down:
1005
- keyNavigate(Direction.Down, isShiftKey, isMetaKey);
1006
- e.preventDefault();
1007
- break;
1008
-
1009
- case KeyCodes.A:
1010
- if (isMetaKey) {
1011
- selectAll();
1012
- }
1013
- e.preventDefault();
1014
- break;
1015
-
1016
- case KeyCodes.Home:
1017
- if (isMetaKey) {
1018
- selectFirstCellInColumn();
1019
- } else {
1020
- selectFirstCellInRow();
1021
- }
1022
- break;
1023
-
1024
- case KeyCodes.End:
1025
- if (isMetaKey) {
1026
- selectLastCellInColumn();
1027
- } else {
1028
- selectLastCellInRow();
1029
- }
1030
- break;
1031
-
1032
- case KeyCodes.BackSpace:
1033
- if (isMetaKey) scrollToActiveCell();
1034
- break;
1035
-
1036
- case KeyCodes.SPACE:
1037
- if (isMetaKey && isShiftKey) {
1038
- selectAll();
1039
- } else if (isMetaKey) {
1040
- selectColumn();
1041
- } else if (isShiftKey) {
1042
- selectRow();
1043
- }
1044
- break;
1045
-
1046
- case KeyCodes.Tab:
1047
- /* Cycle through the selections if selections.length > 0 */
1048
- if (selections.length && activeCell && gridRef) {
1049
- const { bounds } = selections[selections.length - 1];
1050
- const activeCellBounds = gridRef.current.getCellBounds(activeCell);
1051
- const direction = isShiftKey ? Direction.Left : Direction.Right;
1052
- const nextCell = findNextCellWithinBounds(
1053
- activeCellBounds,
1054
- bounds,
1055
- direction
1056
- );
1057
- if (nextCell) {
1058
- setActiveCell(nextCell);
1059
- if (gridRef.current) gridRef.current.scrollToItem(nextCell);
1060
- }
1061
- } else {
1062
- if (isShiftKey) {
1063
- keyNavigate(Direction.Left);
1064
- } else {
1065
- keyNavigate(Direction.Right);
1066
- }
1067
- }
1068
- e.preventDefault();
1069
- break;
1070
-
1071
- case KeyCodes.PageDown:
1072
- if (isAltKey) {
1073
- pageRight();
1074
- } else pageDown();
1075
- break;
1076
-
1077
- case KeyCodes.PageUp:
1078
- if (isAltKey) {
1079
- pageLeft();
1080
- } else pageUp();
1081
- break;
1082
- }
1083
- },
1084
- [
1085
- rowCount,
1086
- columnCount,
1087
- activeCell,
1088
- selections,
1089
- selectionPolicy,
1090
- newSelectionMode,
1091
- ]
1092
- );
1093
-
1094
- /**
1095
- * User modified active cell deliberately
1096
- */
1097
- const handleSetActiveCell = useCallback(
1098
- (coords: CellInterface | null, shouldScroll = true) => {
1099
- selectionStart.current = coords;
1100
- firstActiveCell.current = coords;
1101
- selectionEnd.current = coords;
1102
- setActiveCell(coords);
1103
- /* Scroll to the cell */
1104
- if (shouldScroll && coords && gridRef?.current) {
1105
- gridRef.current.scrollToItem(coords);
1106
- }
1107
- },
1108
- []
1109
- );
1110
-
1111
- /**
1112
- * TODO
1113
- * 1. Fill does not extend to merged cells
1114
- */
1115
- const handleFillHandleMouseMove = useCallback((e: globalThis.MouseEvent) => {
1116
- /* Exit if user is not in selection mode */
1117
- if (!isFilling.current || !gridRef?.current || !activeCellRef.current)
1118
- return;
1119
-
1120
- const coords = gridRef.current.getCellCoordsFromOffset(
1121
- e.clientX,
1122
- e.clientY,
1123
- false
1124
- );
1125
- if (!coords) return;
1126
- const selections = activeSelectionsRef.current;
1127
- let bounds = selectionFromStartEnd(activeCellRef.current, coords);
1128
- const hasSelections = selections.length > 0;
1129
- const activeCellBounds = hasSelections
1130
- ? selections[selections.length - 1].bounds
1131
- : gridRef.current.getCellBounds(activeCellRef.current);
1132
- if (!bounds) return;
1133
-
1134
- const direction =
1135
- bounds.top < activeCellBounds.top
1136
- ? Direction.Up
1137
- : bounds.bottom > activeCellBounds.bottom
1138
- ? Direction.Down
1139
- : bounds.right > activeCellBounds.right
1140
- ? Direction.Right
1141
- : Direction.Left;
1142
-
1143
- if (direction === Direction.Right) {
1144
- bounds = {
1145
- ...activeCellBounds,
1146
- right: Math.min(selectionRightBound, bounds.right),
1147
- };
1148
- }
1149
-
1150
- if (direction === Direction.Up) {
1151
- bounds = {
1152
- ...activeCellBounds,
1153
- top: Math.max(selectionTopBound, bounds.top),
1154
- };
1155
- }
1156
-
1157
- if (direction === Direction.Left) {
1158
- bounds = {
1159
- ...activeCellBounds,
1160
- left: Math.max(selectionLeftBound, bounds.left),
1161
- };
1162
- }
1163
-
1164
- if (direction === Direction.Down) {
1165
- bounds = {
1166
- ...activeCellBounds,
1167
- bottom: Math.min(selectionBottomBound, bounds.bottom),
1168
- };
1169
- }
1170
-
1171
- /**
1172
- * If user moves back to the same selection, clear
1173
- */
1174
- if (
1175
- hasSelections &&
1176
- boundsSubsetOfSelection(bounds, selections[0].bounds)
1177
- ) {
1178
- setFillSelection(undefined);
1179
- return;
1180
- }
1181
-
1182
- setFillSelection({ bounds });
1183
-
1184
- gridRef.current.scrollToItem(coords);
1185
- }, []);
1186
-
1187
- /**
1188
- * When user releases mouse on the fill handle
1189
- */
1190
- const handleFillHandleMouseUp = useCallback(
1191
- (e: globalThis.MouseEvent) => {
1192
- isFilling.current = false;
1193
-
1194
- /* Remove listener */
1195
- document.removeEventListener("mousemove", handleFillHandleMouseMove);
1196
- document.removeEventListener("mouseup", handleFillHandleMouseUp);
1197
-
1198
- /* Exit early */
1199
- if (!gridRef || !activeCellRef.current) return;
1200
-
1201
- /* Update last selection */
1202
- let fillSelection = fillSelectionRef.current;
1203
-
1204
- /* Update last selection */
1205
- setFillSelection(undefined);
1206
-
1207
- /* Use ref, as we are binding to document */
1208
- const selections = activeSelectionsRef.current;
1209
- const activeCell = activeCellRef.current;
1210
- if (!activeCell || !fillSelection) return;
1211
-
1212
- const newBounds = (fillSelection as SelectionArea)?.bounds;
1213
- if (!newBounds) return;
1214
-
1215
- /* Focus on the grid */
1216
- gridRef.current?.focus();
1217
-
1218
- /* Callback */
1219
- onFill && onFill(activeCell, fillSelection, selections);
1220
-
1221
- /* Modify last selection */
1222
- setSelections((prevSelection) => {
1223
- const len = prevSelection.length;
1224
- if (!len) {
1225
- return [{ bounds: newBounds }];
1226
- }
1227
- return prevSelection.map((sel, i) => {
1228
- if (len - 1 === i) {
1229
- return {
1230
- ...sel,
1231
- bounds: newBounds,
1232
- };
1233
- }
1234
- return sel;
1235
- });
1236
- });
1237
- },
1238
- [onFill]
1239
- );
1240
-
1241
- const handleFillHandleMouseDown = useCallback(
1242
- (e: React.MouseEvent<HTMLDivElement>) => {
1243
- e.stopPropagation();
1244
- isFilling.current = true;
1245
- document.addEventListener("mousemove", handleFillHandleMouseMove);
1246
- document.addEventListener("mouseup", handleFillHandleMouseUp);
1247
- },
1248
- [handleFillHandleMouseMove, handleFillHandleMouseUp]
1249
- );
1250
-
1251
- /**
1252
- * Remove the last selection from state
1253
- */
1254
- const handleClearLastSelection = useCallback(() => {
1255
- setSelections((prev) => prev.slice(0, -1));
1256
- }, []);
1257
-
1258
- const fillHandleProps = useMemo(() => {
1259
- return {
1260
- onMouseDown: handleFillHandleMouseDown,
1261
- };
1262
- }, [handleFillHandleMouseDown]);
1263
-
1264
- /**
1265
- * When user mouse downs on selection
1266
- */
1267
- const handleSelectionMouseDown = useCallback(
1268
- (
1269
- e: React.MouseEvent<HTMLDivElement>,
1270
- activeCell: CellInterface | undefined,
1271
- selection: SelectionArea | undefined,
1272
- index: number | undefined,
1273
- shouldClamp: boolean = true
1274
- ) => {
1275
- if (!gridRef.current) {
1276
- return;
1277
- }
1278
- let coords = gridRef.current.getCellCoordsFromOffset(
1279
- e.nativeEvent.clientX,
1280
- e.nativeEvent.clientY,
1281
- false // Todo
1282
- );
1283
- if (!coords) {
1284
- return;
1285
- }
1286
- /* Initial cell that is selected by user */
1287
- initialDraggedCell.current = coords;
1288
-
1289
- /* Make sure the coords do not extend selection bounds */
1290
- if (shouldClamp) {
1291
- coords = clampCellCoords(coords, activeCell, selection);
1292
- }
1293
-
1294
- /* Set selection */
1295
- if (activeCell) {
1296
- initialDraggedSelection.current = draggedSelection.current = {
1297
- bounds: gridRef.current.getCellBounds(activeCell),
1298
- };
1299
- }
1300
- if (selection) {
1301
- initialDraggedSelection.current = draggedSelection.current = selection;
1302
- draggedSelectionIndex.current = index;
1303
- }
1304
-
1305
- /* Set dragging flag */
1306
- isDragging.current = true;
1307
-
1308
- /* Listen to mousemove and mousedown events */
1309
- document.addEventListener("mouseup", handleSelectionMouseUp);
1310
- document.addEventListener("mousemove", handleSelectionMouseMove);
1311
-
1312
- /* Force a re-render */
1313
- forceRender();
1314
- },
1315
- [selectionTopBound, selectionLeftBound, rowCount, columnCount, selections]
1316
- );
1317
-
1318
- /**
1319
- * Ond drag move
1320
- */
1321
- const handleSelectionMouseMove = useCallback((e: globalThis.MouseEvent) => {
1322
- if (!gridRef?.current) return;
1323
- const coords = gridRef.current.getCellCoordsFromOffset(
1324
- e.clientX,
1325
- e.clientY
1326
- );
1327
- if (!coords) {
1328
- return;
1329
- }
1330
- if (!initialDraggedSelection.current || !initialDraggedCell.current) {
1331
- return;
1332
- }
1333
- /**
1334
- * Skip if user is moving the selection
1335
- * to the same starting position
1336
- */
1337
- if (
1338
- !hasUserMovedSelection.current &&
1339
- isEqualCells(initialDraggedCell.current, coords)
1340
- ) {
1341
- return;
1342
- }
1343
- let sel = newSelectionFromDrag(
1344
- initialDraggedSelection.current,
1345
- initialDraggedCell.current,
1346
- coords,
1347
- selectionTopBound,
1348
- selectionLeftBound,
1349
- rowCount,
1350
- columnCount
1351
- );
1352
- // Not required
1353
- // sel = { bounds : extendAreaToMergedCells(sel.bounds, mergedCellsRef.current) }
1354
- draggedSelection.current = sel;
1355
-
1356
- // User has now moved the selection,
1357
- hasUserMovedSelection.current = true;
1358
-
1359
- /* Scroll to the cell */
1360
- gridRef.current?.scrollToItem(coords);
1361
-
1362
- /* Re-render */
1363
- forceRender();
1364
- }, []);
1365
-
1366
- /**
1367
- * When drag/drop is complete
1368
- */
1369
- const handleSelectionMouseUp = useCallback(() => {
1370
- const sel = draggedSelection.current;
1371
- if (sel) {
1372
- /* Select the first cell in the selection area */
1373
- const coords = { rowIndex: sel.bounds.top, columnIndex: sel.bounds.left };
1374
- setActiveCell(coords);
1375
- /* Update selection start */
1376
- selectionStart.current = coords;
1377
-
1378
- if (selectionSpansCells(sel?.bounds)) {
1379
- setSelections((prev) => {
1380
- return prev.map((cur, index) => {
1381
- if (index === draggedSelectionIndex.current) {
1382
- return sel;
1383
- }
1384
- return cur;
1385
- });
1386
- });
1387
-
1388
- /* Update selection end */
1389
- selectionEnd.current = {
1390
- rowIndex: sel.bounds.bottom,
1391
- columnIndex: sel.bounds.right,
1392
- };
1393
- }
1394
- }
1395
-
1396
- if (initialDraggedSelection.current && sel) {
1397
- onSelectionMove?.(initialDraggedSelection.current, sel);
1398
- }
1399
-
1400
- /* Focus on the grid */
1401
- gridRef.current?.focus();
1402
-
1403
- /* Reset all references */
1404
- isDragging.current = false;
1405
- draggedSelection.current = void 0;
1406
- draggedSelectionIndex.current = void 0;
1407
- hasUserMovedSelection.current = false;
1408
-
1409
- /* Remove event handlers */
1410
- document.removeEventListener("mouseup", handleSelectionMouseUp);
1411
- document.removeEventListener("mousemove", handleSelectionMouseMove);
1412
-
1413
- /* Force render */
1414
- forceRender();
1415
- }, []);
1416
-
1417
- return {
1418
- activeCell,
1419
- selections,
1420
- isDragging: isDragging.current,
1421
- draggedSelection: draggedSelection.current,
1422
- initialDraggedSelection: initialDraggedSelection.current,
1423
- onSelectionMouseDown: handleSelectionMouseDown,
1424
- onMouseDown: handleMouseDown,
1425
- onKeyDown: handleKeyDown,
1426
- newSelection,
1427
- setSelections,
1428
- setActiveCell: handleSetActiveCell,
1429
- setActiveCellState: setActiveCell,
1430
- fillHandleProps,
1431
- fillSelection,
1432
- clearLastSelection: handleClearLastSelection,
1433
- modifySelection,
1434
- selectAll,
1435
- appendSelection,
1436
- clearSelections,
1437
- };
1438
- };
1439
-
1440
- export default useSelection;