@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.
- package/LICENSE.md +100 -0
- package/Readme.md +53 -59
- package/dist/cjs/Cell.d.ts +35 -0
- package/dist/cjs/Cell.d.ts.map +1 -0
- package/dist/cjs/Cell.js +29 -0
- package/dist/{CellOverlay.d.ts → cjs/CellOverlay.d.ts} +5 -5
- package/dist/cjs/CellOverlay.d.ts.map +1 -0
- package/dist/cjs/CellOverlay.js +126 -0
- package/dist/cjs/Context.d.ts +8 -0
- package/dist/cjs/Context.d.ts.map +1 -0
- package/dist/cjs/Context.js +13 -0
- package/dist/{FillHandle.d.ts → cjs/FillHandle.d.ts} +2 -1
- package/dist/cjs/FillHandle.d.ts.map +1 -0
- package/dist/cjs/FillHandle.js +26 -0
- package/dist/cjs/Grid.d.ts +558 -0
- package/dist/cjs/Grid.d.ts.map +1 -0
- package/dist/cjs/Grid.js +2446 -0
- package/dist/{GridLine.d.ts → cjs/GridLine.d.ts} +2 -1
- package/dist/cjs/GridLine.d.ts.map +1 -0
- package/dist/cjs/GridLine.js +10 -0
- package/dist/cjs/Image.d.ts +11 -0
- package/dist/cjs/Image.d.ts.map +1 -0
- package/dist/cjs/Image.js +57 -0
- package/dist/cjs/MemoizedGrid.d.ts +49 -0
- package/dist/cjs/MemoizedGrid.d.ts.map +1 -0
- package/dist/cjs/MemoizedGrid.js +105 -0
- package/dist/cjs/Selection.d.ts +5 -0
- package/dist/cjs/Selection.d.ts.map +1 -0
- package/dist/cjs/Selection.js +10 -0
- package/dist/{helpers.d.ts → cjs/helpers.d.ts} +59 -39
- package/dist/cjs/helpers.d.ts.map +1 -0
- package/dist/cjs/helpers.js +1409 -0
- package/dist/{hooks → cjs/hooks}/useCopyPaste.d.ts +3 -2
- package/dist/cjs/hooks/useCopyPaste.d.ts.map +1 -0
- package/dist/cjs/hooks/useCopyPaste.js +230 -0
- package/dist/{hooks → cjs/hooks}/useEditable.d.ts +21 -8
- package/dist/cjs/hooks/useEditable.d.ts.map +1 -0
- package/dist/cjs/hooks/useEditable.js +491 -0
- package/dist/{hooks → cjs/hooks}/useFilter.d.ts +3 -2
- package/dist/cjs/hooks/useFilter.d.ts.map +1 -0
- package/dist/cjs/hooks/useFilter.js +97 -0
- package/dist/{hooks → cjs/hooks}/useImage.d.ts +1 -0
- package/dist/cjs/hooks/useImage.d.ts.map +1 -0
- package/dist/cjs/hooks/useImage.js +80 -0
- package/dist/{hooks → cjs/hooks}/usePagination.d.ts +3 -2
- package/dist/cjs/hooks/usePagination.d.ts.map +1 -0
- package/dist/cjs/hooks/usePagination.js +53 -0
- package/dist/{hooks → cjs/hooks}/useSizer.d.ts +28 -29
- package/dist/cjs/hooks/useSizer.d.ts.map +1 -0
- package/dist/cjs/hooks/useSizer.js +176 -0
- package/dist/{hooks → cjs/hooks}/useTooltip.d.ts +2 -1
- package/dist/cjs/hooks/useTooltip.d.ts.map +1 -0
- package/dist/cjs/hooks/useTooltip.js +82 -0
- package/dist/{hooks → cjs/hooks}/useUndo.d.ts +2 -1
- package/dist/cjs/hooks/useUndo.d.ts.map +1 -0
- package/dist/cjs/hooks/useUndo.js +87 -0
- package/dist/cjs/index.d.ts +19 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +29 -0
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -0
- package/dist/{types.d.ts → cjs/types.d.ts} +8 -3
- package/dist/cjs/types.d.ts.map +1 -0
- package/dist/{types.js → cjs/types.js} +7 -8
- package/dist/cjs/utils.d.ts +9 -0
- package/dist/cjs/utils.d.ts.map +1 -0
- package/dist/cjs/utils.js +58 -0
- package/dist/cjs/vitest.config.d.ts +3 -0
- package/dist/cjs/vitest.config.d.ts.map +1 -0
- package/dist/cjs/vitest.config.js +24 -0
- package/dist/esm/Cell.d.ts +35 -0
- package/dist/esm/Cell.d.ts.map +1 -0
- package/dist/esm/Cell.js +24 -0
- package/dist/esm/CellOverlay.d.ts +21 -0
- package/dist/esm/CellOverlay.d.ts.map +1 -0
- package/dist/esm/CellOverlay.js +120 -0
- package/dist/esm/Context.d.ts +8 -0
- package/dist/esm/Context.d.ts.map +1 -0
- package/dist/esm/Context.js +9 -0
- package/dist/esm/FillHandle.d.ts +8 -0
- package/dist/esm/FillHandle.d.ts.map +1 -0
- package/dist/{FillHandle.js → esm/FillHandle.js} +5 -11
- package/dist/esm/Grid.d.ts +558 -0
- package/dist/esm/Grid.d.ts.map +1 -0
- package/dist/esm/Grid.js +2451 -0
- package/dist/esm/GridLine.d.ts +5 -0
- package/dist/esm/GridLine.d.ts.map +1 -0
- package/dist/esm/GridLine.js +7 -0
- package/dist/esm/Image.d.ts +11 -0
- package/dist/esm/Image.d.ts.map +1 -0
- package/dist/esm/Image.js +53 -0
- package/dist/esm/MemoizedGrid.d.ts +49 -0
- package/dist/esm/MemoizedGrid.d.ts.map +1 -0
- package/dist/esm/MemoizedGrid.js +98 -0
- package/dist/esm/Selection.d.ts +5 -0
- package/dist/esm/Selection.d.ts.map +1 -0
- package/dist/esm/Selection.js +7 -0
- package/dist/esm/helpers.d.ts +281 -0
- package/dist/esm/helpers.d.ts.map +1 -0
- package/dist/esm/helpers.js +1260 -0
- package/dist/esm/hooks/useCopyPaste.d.ts +53 -0
- package/dist/esm/hooks/useCopyPaste.d.ts.map +1 -0
- package/dist/{hooks → esm/hooks}/useCopyPaste.js +40 -46
- package/dist/esm/hooks/useEditable.d.ts +253 -0
- package/dist/esm/hooks/useEditable.d.ts.map +1 -0
- package/dist/esm/hooks/useEditable.js +482 -0
- package/dist/esm/hooks/useFilter.d.ts +69 -0
- package/dist/esm/hooks/useFilter.d.ts.map +1 -0
- package/dist/{hooks → esm/hooks}/useFilter.js +22 -42
- package/dist/esm/hooks/useImage.d.ts +13 -0
- package/dist/esm/hooks/useImage.d.ts.map +1 -0
- package/dist/esm/hooks/useImage.js +76 -0
- package/dist/esm/hooks/usePagination.d.ts +69 -0
- package/dist/esm/hooks/usePagination.d.ts.map +1 -0
- package/dist/esm/hooks/usePagination.js +47 -0
- package/dist/esm/hooks/useSizer.d.ts +114 -0
- package/dist/esm/hooks/useSizer.d.ts.map +1 -0
- package/dist/esm/hooks/useSizer.js +176 -0
- package/dist/esm/hooks/useTooltip.d.ts +43 -0
- package/dist/esm/hooks/useTooltip.d.ts.map +1 -0
- package/dist/esm/hooks/useTooltip.js +80 -0
- package/dist/esm/hooks/useUndo.d.ts +36 -0
- package/dist/esm/hooks/useUndo.d.ts.map +1 -0
- package/dist/{hooks → esm/hooks}/useUndo.js +16 -25
- package/dist/esm/index.d.ts +19 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +17 -0
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -0
- package/dist/esm/types.d.ts +70 -0
- package/dist/esm/types.d.ts.map +1 -0
- package/dist/esm/types.js +68 -0
- package/dist/esm/utils.d.ts +9 -0
- package/dist/esm/utils.d.ts.map +1 -0
- package/dist/esm/utils.js +104 -0
- package/dist/esm/vitest.config.d.ts +3 -0
- package/dist/esm/vitest.config.d.ts.map +1 -0
- package/dist/esm/vitest.config.js +22 -0
- package/package.json +28 -29
- package/dist/Cell.d.ts +0 -23
- package/dist/Cell.js +0 -55
- package/dist/Cell.js.map +0 -1
- package/dist/CellOverlay.js +0 -116
- package/dist/CellOverlay.js.map +0 -1
- package/dist/FillHandle.js.map +0 -1
- package/dist/Grid.d.ts +0 -385
- package/dist/Grid.js +0 -1684
- package/dist/Grid.js.map +0 -1
- package/dist/GridLine.js +0 -13
- package/dist/GridLine.js.map +0 -1
- package/dist/Image.d.ts +0 -8
- package/dist/Image.js +0 -50
- package/dist/Image.js.map +0 -1
- package/dist/Selection.d.ts +0 -4
- package/dist/Selection.js +0 -9
- package/dist/Selection.js.map +0 -1
- package/dist/helpers.js +0 -1033
- package/dist/helpers.js.map +0 -1
- package/dist/hooks/useCopyPaste.js.map +0 -1
- package/dist/hooks/useEditable.js +0 -477
- package/dist/hooks/useEditable.js.map +0 -1
- package/dist/hooks/useFilter.js.map +0 -1
- package/dist/hooks/useImage.js +0 -42
- package/dist/hooks/useImage.js.map +0 -1
- package/dist/hooks/usePagination.js +0 -73
- package/dist/hooks/usePagination.js.map +0 -1
- package/dist/hooks/useSelection.d.ts +0 -175
- package/dist/hooks/useSelection.js +0 -982
- package/dist/hooks/useSelection.js.map +0 -1
- package/dist/hooks/useSizer.js +0 -150
- package/dist/hooks/useSizer.js.map +0 -1
- package/dist/hooks/useTooltip.js +0 -106
- package/dist/hooks/useTooltip.js.map +0 -1
- package/dist/hooks/useTouch.d.ts +0 -21
- package/dist/hooks/useTouch.js +0 -73
- package/dist/hooks/useTouch.js.map +0 -1
- package/dist/hooks/useUndo.js.map +0 -1
- package/dist/index.d.ts +0 -25
- package/dist/index.js +0 -61
- package/dist/index.js.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/utils.d.ts +0 -8
- package/dist/utils.js +0 -153
- package/dist/utils.js.map +0 -1
- package/src/Cell.tsx +0 -100
- package/src/CellOverlay.tsx +0 -191
- package/src/FillHandle.tsx +0 -37
- package/src/Grid.tsx +0 -2677
- package/src/GridLine.tsx +0 -23
- package/src/Image.tsx +0 -43
- package/src/Selection.tsx +0 -9
- package/src/helpers.ts +0 -1455
- package/src/hooks/useCopyPaste.ts +0 -265
- package/src/hooks/useEditable.tsx +0 -945
- package/src/hooks/useFilter.tsx +0 -233
- package/src/hooks/useImage.ts +0 -57
- package/src/hooks/usePagination.tsx +0 -153
- package/src/hooks/useSelection.ts +0 -1440
- package/src/hooks/useSizer.ts +0 -312
- package/src/hooks/useTooltip.tsx +0 -172
- package/src/hooks/useTouch.ts +0 -116
- package/src/hooks/useUndo.ts +0 -134
- package/src/index.tsx +0 -46
- package/src/tests/Grid.spec.tsx +0 -47
- package/src/tests/helpers.test.ts +0 -292
- package/src/tests/usePagination.test.ts +0 -28
- package/src/tests/useSelection.test.ts +0 -27
- package/src/tests/useUndo.test.ts +0 -73
- package/src/types.ts +0 -69
- 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;
|