@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,945 +0,0 @@
1
- import React, {
2
- useCallback,
3
- useEffect,
4
- useRef,
5
- useState,
6
- useMemo,
7
- } from "react";
8
- import {
9
- CellInterface,
10
- ScrollCoords,
11
- CellPosition,
12
- GridRef,
13
- SelectionArea,
14
- } from "../Grid";
15
- import { KeyCodes, Direction } from "./../types";
16
- import {
17
- findNextCellWithinBounds,
18
- isEqualCells,
19
- clampIndex,
20
- HiddenType,
21
- autoSizerCanvas,
22
- isArrowKey,
23
- castToString,
24
- } from "../helpers";
25
-
26
- export interface UseEditableOptions {
27
- editorProps?: () => any;
28
- /**
29
- * Inject custom editors based on a cell
30
- */
31
- getEditor?: (cell: CellInterface | null) => React.ElementType;
32
- /**
33
- * Access grid methods
34
- */
35
- gridRef: React.MutableRefObject<GridRef | null>;
36
- /**
37
- * Value getter
38
- */
39
- getValue: (cell: CellInterface) => any;
40
- /**
41
- * Callback when user cancels editing
42
- */
43
- onCancel?: (
44
- e?: React.KeyboardEvent<
45
- HTMLTextAreaElement | HTMLInputElement | HTMLDivElement
46
- >
47
- ) => void;
48
- /**
49
- * Callback when user changes a value in editor
50
- */
51
- onChange?: (value: string, activeCell: CellInterface) => void;
52
- /**
53
- * Callback when user submits a value. Use this to update state
54
- */
55
- onSubmit?: (
56
- value: string,
57
- activeCell: CellInterface,
58
- nextActiveCell?: CellInterface | null
59
- ) => void;
60
- /**
61
- * Callback when user selects an area and presses delete key
62
- */
63
- onDelete?: (activeCell: CellInterface, selections: SelectionArea[]) => void;
64
- /**
65
- * Currently selected cells, injected by useSelection
66
- */
67
- selections: SelectionArea[];
68
- /**
69
- * Active selected cell. This can change, if the user is in formula mode
70
- */
71
- activeCell: CellInterface | null;
72
- /**
73
- * Callback fired before editing. Can be used to prevent editing. Do not use it, Can be removed in next release.
74
- */
75
- canEdit?: (coords: CellInterface) => boolean;
76
- /**
77
- * Number of frozen columns
78
- */
79
- frozenColumns?: number;
80
- /**
81
- * Number of frozen rows
82
- */
83
- frozenRows?: number;
84
- /**
85
- * Hide editor on blur
86
- */
87
- hideOnBlur?: boolean;
88
- /**
89
- * Hidden rows
90
- */
91
- isHiddenRow: HiddenType;
92
- /**
93
- * Hidden columns
94
- */
95
- isHiddenColumn: HiddenType;
96
- /**
97
- * No of columns in the grid
98
- */
99
- columnCount: number;
100
- /**
101
- * No of rows in the grid
102
- */
103
- rowCount: number;
104
- /**
105
- * Top bound of selection
106
- */
107
- selectionTopBound?: number;
108
- /**
109
- * Bottom bound
110
- */
111
- selectionBottomBound?: number;
112
- /**
113
- * Left bound
114
- */
115
- selectionLeftBound?: number;
116
- /**
117
- * Right bound
118
- */
119
- selectionRightBound?: number;
120
- onKeyDown?: (
121
- e: React.KeyboardEvent<
122
- HTMLInputElement | HTMLTextAreaElement | HTMLDivElement
123
- >
124
- ) => void;
125
- /**
126
- * Sync callback before a cell is edited
127
- */
128
- onBeforeEdit?: (coords: CellInterface) => void;
129
- /**
130
- * If true, Once the editor is active, it will be always visible.
131
- * Editor will not scroll with the grid
132
- */
133
- sticky?: boolean;
134
- }
135
-
136
- export interface EditableResults {
137
- /**
138
- * Editor component that can be injected
139
- */
140
- editorComponent: React.ReactNode;
141
- /**
142
- * Double click listener, activates the grid
143
- */
144
- onDoubleClick: (e: React.MouseEvent<HTMLDivElement>) => void;
145
- /**
146
- * OnScroll listener to align the editor
147
- */
148
- onScroll?: (props: ScrollCoords) => void;
149
- /**
150
- * Key down listeners
151
- */
152
- onKeyDown: (e: React.KeyboardEvent<HTMLDivElement>) => void;
153
- /**
154
- * Get next focusable cell based on current activeCell and direction user is moving
155
- */
156
- nextFocusableCell: (
157
- currentCell: CellInterface,
158
- direction: Direction
159
- ) => CellInterface | null;
160
- /**
161
- * Is editing in progress
162
- */
163
- isEditInProgress: boolean;
164
- /**
165
- * Currently editing cell
166
- */
167
- editingCell: CellInterface | null;
168
- /**
169
- * Make a cell editable
170
- */
171
- makeEditable: (cell: CellInterface, value?: string) => void;
172
- /**
173
- * Set editable value imperatively
174
- */
175
- setValue: (
176
- value: string,
177
- activeCell: CellInterface,
178
- previousValue?: string
179
- ) => void;
180
- /**
181
- * Hide editor
182
- */
183
- hideEditor: () => void;
184
- /**
185
- * Show editor
186
- */
187
- showEditor: () => void;
188
- /**
189
- * Bind to mousedown event
190
- */
191
- onMouseDown: (e: React.MouseEvent<HTMLDivElement>) => void;
192
- /**
193
- * Imperatively trigger submit
194
- */
195
- submitEditor: (
196
- value: string,
197
- activeCell: CellInterface,
198
- nextActiveCell?: CellInterface | null
199
- ) => void;
200
- /**
201
- * Cancels an edit
202
- */
203
- cancelEditor: () => void;
204
- }
205
-
206
- export interface EditorProps {
207
- /**
208
- * Currently selected bounds, useful for fomulas
209
- */
210
- selections?: SelectionArea[];
211
- /**
212
- * Initial value of the cell
213
- */
214
- value?: string | number;
215
- /**
216
- * Callback when a value has changed.
217
- */
218
- onChange?: (value: string, activeCell: CellInterface) => void;
219
- /**
220
- * Callback to submit the value back to data store
221
- */
222
- onSubmit?: (
223
- value: string | number,
224
- activeCell: CellInterface,
225
- nextActiveCell?: CellInterface | null
226
- ) => void;
227
- /**
228
- * On Cancel callbacks. Hides the editor
229
- */
230
- onCancel?: (
231
- e?: React.KeyboardEvent<
232
- HTMLTextAreaElement | HTMLInputElement | HTMLDivElement
233
- >
234
- ) => void;
235
- /**
236
- * Cell position, x, y, width and height
237
- */
238
- position: CellPosition;
239
- /**
240
- * Currently active cell, based on selection
241
- */
242
- activeCell: CellInterface;
243
- /**
244
- * Currrently edited cell
245
- */
246
- cell: CellInterface;
247
- /**
248
- * Scroll position of the grid
249
- */
250
- scrollPosition: ScrollCoords;
251
- /**
252
- * Next cell that should receive focus
253
- */
254
- nextFocusableCell?: (
255
- activeCell: CellInterface,
256
- direction?: Direction
257
- ) => CellInterface | null;
258
- /**
259
- * Autofocus the editor when open
260
- */
261
- autoFocus?: boolean;
262
- /**
263
- * On keydown event
264
- */
265
- onKeyDown?: (
266
- e: React.KeyboardEvent<
267
- HTMLInputElement | HTMLTextAreaElement | HTMLDivElement
268
- >
269
- ) => void;
270
- /**
271
- * Max editor width
272
- */
273
- maxWidth?: string | number;
274
- /**
275
- * Max editor height
276
- */
277
- maxHeight?: string | number;
278
- /**
279
- * Indicates if the cell is part of frozen row
280
- */
281
- isFrozenRow?: boolean;
282
- /**
283
- * Indicates if the cell is part of frozen column
284
- */
285
- isFrozenColumn?: boolean;
286
- /**
287
- * Frozen row offset
288
- */
289
- frozenRowOffset?: number;
290
- /**
291
- * Frozen column offset
292
- */
293
- frozenColumnOffset?: number;
294
- }
295
-
296
- /**
297
- * Default cell editor
298
- * @param props
299
- */
300
- const DefaultEditor: React.FC<EditorProps> = (props) => {
301
- const {
302
- onChange,
303
- onSubmit,
304
- onCancel,
305
- position,
306
- cell,
307
- nextFocusableCell,
308
- value = "",
309
- activeCell,
310
- autoFocus = true,
311
- onKeyDown,
312
- selections,
313
- scrollPosition,
314
- maxWidth,
315
- maxHeight,
316
- isFrozenRow,
317
- isFrozenColumn,
318
- frozenRowOffset,
319
- frozenColumnOffset,
320
- ...rest
321
- } = props;
322
- const borderWidth = 2;
323
- const padding = 10; /* 2 + 1 + 1 + 2 + 2 */
324
- const inputRef = useRef<HTMLTextAreaElement | null>(null);
325
- const { x = 0, y = 0, width = 0, height = 0 } = position;
326
- const getWidth = useCallback(
327
- (text: string | number) => {
328
- const textWidth = autoSizerCanvas.measureText(text)?.width || 0;
329
- return Math.max(textWidth + padding, width + borderWidth / 2);
330
- },
331
- [width]
332
- );
333
- useEffect(() => {
334
- setInputWidth(getWidth(value));
335
- }, [value]);
336
- const [inputWidth, setInputWidth] = useState(() => getWidth(value));
337
- useEffect(() => {
338
- if (!inputRef.current) return;
339
- if (autoFocus) inputRef.current.focus();
340
- /* Focus cursor at the end */
341
- inputRef.current.selectionStart = castToString(value)?.length ?? 0;
342
- }, []);
343
- const inputHeight = height;
344
- return (
345
- <div
346
- style={{
347
- top: y - borderWidth / 2,
348
- left: x,
349
- position: "absolute",
350
- width: inputWidth,
351
- height: inputHeight + borderWidth,
352
- padding: borderWidth,
353
- boxShadow: "0 2px 6px 2px rgba(60,64,67,.15)",
354
- border: "2px #1a73e8 solid",
355
- background: "white",
356
- }}
357
- >
358
- <textarea
359
- rows={1}
360
- cols={1}
361
- ref={inputRef}
362
- value={value}
363
- style={{
364
- font: "12px Arial",
365
- lineHeight: 1.2,
366
- width: "100%",
367
- height: "100%",
368
- padding: "0 1px",
369
- margin: 0,
370
- boxSizing: "border-box",
371
- borderWidth: 0,
372
- outline: "none",
373
- resize: "none",
374
- overflow: "hidden",
375
- verticalAlign: "top",
376
- background: "transparent",
377
- }}
378
- onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => {
379
- onChange?.(e.target.value, cell);
380
- }}
381
- onKeyDown={(e: React.KeyboardEvent<HTMLTextAreaElement>) => {
382
- if (!inputRef.current) return;
383
- const isShiftKey = e.nativeEvent.shiftKey;
384
- const value = inputRef.current.value;
385
-
386
- // Enter key
387
- if (e.which === KeyCodes.Enter) {
388
- onSubmit &&
389
- onSubmit(
390
- value,
391
- cell,
392
- nextFocusableCell?.(
393
- cell,
394
- isShiftKey ? Direction.Up : Direction.Down
395
- )
396
- );
397
- }
398
-
399
- if (e.which === KeyCodes.Escape) {
400
- onCancel && onCancel(e);
401
- }
402
-
403
- if (e.which === KeyCodes.Tab) {
404
- e.preventDefault();
405
- onSubmit &&
406
- onSubmit(
407
- value,
408
- cell,
409
- nextFocusableCell?.(
410
- cell,
411
- isShiftKey ? Direction.Left : Direction.Right
412
- )
413
- );
414
- }
415
-
416
- onKeyDown?.(e);
417
- }}
418
- {...rest}
419
- />
420
- </div>
421
- );
422
- };
423
-
424
- export const getDefaultEditor = (cell: CellInterface | null) => DefaultEditor;
425
- const defaultCanEdit = (cell: CellInterface) => true;
426
- const defaultIsHidden = (i: number) => false;
427
-
428
- /**
429
- * Hook to make grid editable
430
- * @param param
431
- */
432
- const useEditable = ({
433
- getEditor = getDefaultEditor,
434
- gridRef,
435
- getValue,
436
- onChange,
437
- onSubmit,
438
- onCancel,
439
- onDelete,
440
- selections = [],
441
- activeCell,
442
- canEdit = defaultCanEdit,
443
- frozenRows = 0,
444
- frozenColumns = 0,
445
- hideOnBlur = true,
446
- isHiddenRow = defaultIsHidden,
447
- isHiddenColumn = defaultIsHidden,
448
- rowCount,
449
- columnCount,
450
- selectionTopBound = 0,
451
- selectionBottomBound = rowCount - 1,
452
- selectionLeftBound = 0,
453
- selectionRightBound = columnCount - 1,
454
- editorProps,
455
- onBeforeEdit,
456
- onKeyDown,
457
- sticky = false,
458
- }: UseEditableOptions): EditableResults => {
459
- const [isEditorShown, setShowEditor] = useState<boolean>(false);
460
- const [value, setValue] = useState<string>("");
461
- const [position, setPosition] = useState<CellPosition>({
462
- x: 0,
463
- y: 0,
464
- width: 0,
465
- height: 0,
466
- });
467
- const currentActiveCellRef = useRef<CellInterface | null>(null);
468
- const initialActiveCell = useRef<CellInterface | null>();
469
- const [scrollPosition, setScrollPosition] = useState<ScrollCoords>({
470
- scrollLeft: 0,
471
- scrollTop: 0,
472
- });
473
- const [autoFocus, setAutoFocus] = useState<boolean>(true);
474
- const isDirtyRef = useRef<boolean>(false);
475
- const currentValueRef = useRef(value);
476
- const initialValueRef = useRef<string>();
477
- const maxEditorDimensionsRef = useRef<{ height: number; width: number }>();
478
- /* To prevent stale closures data */
479
- const getValueRef = useRef(getValue);
480
- const showEditor = useCallback(() => setShowEditor(true), []);
481
- const hideEditor = useCallback(() => {
482
- setShowEditor(false);
483
- currentActiveCellRef.current = null;
484
- }, []);
485
- const focusGrid = useCallback(() => {
486
- requestAnimationFrame(() => gridRef.current && gridRef.current.focus());
487
- }, []);
488
-
489
- /* Keep ref in sync */
490
- useEffect(() => {
491
- currentValueRef.current = value;
492
- });
493
-
494
- /* Keep getvalue ref in sync with upstream prop */
495
- useEffect(() => {
496
- getValueRef.current = getValue;
497
- }, [getValue]);
498
-
499
- /**
500
- * Make a cell editable
501
- * @param coords
502
- * @param initialValue
503
- */
504
- const makeEditable = useCallback(
505
- (
506
- coords: CellInterface,
507
- initialValue?: string,
508
- autoFocus: boolean = true
509
- ) => {
510
- if (!gridRef.current) return;
511
- /* Get actual coords for merged cells */
512
- coords = gridRef.current.getActualCellCoords(coords);
513
-
514
- /* Check if its the same cell */
515
- if (isEqualCells(coords, currentActiveCellRef.current)) {
516
- return;
517
- }
518
-
519
- /* Call on before edit */
520
- if (canEdit(coords)) {
521
- /* Let user modify coords before edit */
522
- onBeforeEdit?.(coords);
523
-
524
- /* Focus */
525
- gridRef.current?.scrollToItem(coords);
526
-
527
- currentActiveCellRef.current = coords;
528
-
529
- /* Get offsets */
530
- const pos = gridRef.current.getCellOffsetFromCoords(coords);
531
- const scrollPosition = gridRef.current.getScrollPosition();
532
- const cellValue = getValueRef.current(coords);
533
- const value = initialValue || cellValue || "";
534
- const cellPosition = sticky
535
- ? // Editor is rendered outside the <Grid /> component
536
- // If the user has scrolled down, and then activate the editor, we will need to adjust the position
537
- // of the sticky editor accordingly
538
- // Subsequent scroll events has no effect, cos of sticky option
539
- getCellPosition(pos, scrollPosition)
540
- : pos;
541
- /**
542
- * Set max editor ref based on grid container
543
- */
544
- const { containerWidth, containerHeight } =
545
- gridRef.current.getDimensions();
546
- maxEditorDimensionsRef.current = {
547
- height: containerHeight - (cellPosition.y ?? 0),
548
- width: containerWidth - (cellPosition.x ?? 0),
549
- };
550
-
551
- /**
552
- * If the user has entered a value in the cell, mark it as dirty
553
- * So that during mousedown, onSubmit gets called
554
- */
555
- isDirtyRef.current = !!initialValue;
556
- initialValueRef.current = initialValue;
557
-
558
- /* Trigger onChange handlers */
559
- setValue(value);
560
- onChange?.(value, coords);
561
- setAutoFocus(autoFocus);
562
- setScrollPosition(scrollPosition);
563
- setPosition(cellPosition);
564
- showEditor();
565
- }
566
- },
567
- [frozenRows, frozenColumns, onBeforeEdit, canEdit, sticky]
568
- );
569
-
570
- /* Frozen flags */
571
- const isFrozenRow =
572
- currentActiveCellRef.current &&
573
- currentActiveCellRef.current?.rowIndex < frozenRows;
574
- const isFrozenColumn =
575
- currentActiveCellRef.current &&
576
- currentActiveCellRef.current?.columnIndex < frozenColumns;
577
-
578
- /**
579
- * Get current cell position based on scroll position
580
- * @param position
581
- * @param scrollPosition
582
- */
583
- const getCellPosition = (
584
- position: CellPosition,
585
- scrollPosition: ScrollCoords
586
- ) => {
587
- if (!currentActiveCellRef.current) return { x: 0, y: 0 };
588
- return {
589
- ...position,
590
- x:
591
- (position.x as number) -
592
- (isFrozenColumn ? 0 : scrollPosition.scrollLeft),
593
- y: (position.y as number) - (isFrozenRow ? 0 : scrollPosition.scrollTop),
594
- };
595
- };
596
-
597
- /* Activate edit mode */
598
- const handleDoubleClick = useCallback(
599
- (e: React.MouseEvent<HTMLElement>) => {
600
- if (!gridRef.current) return;
601
- const coords = gridRef.current.getCellCoordsFromOffset(
602
- e.nativeEvent.clientX,
603
- e.nativeEvent.clientY
604
- );
605
- if (!coords) return;
606
- const { rowIndex, columnIndex } = coords;
607
- makeEditable({ rowIndex, columnIndex });
608
- },
609
- [getValue, frozenRows, frozenColumns]
610
- );
611
-
612
- const isSelectionKey = useCallback((keyCode: number) => {
613
- return (
614
- [
615
- KeyCodes.Right,
616
- KeyCodes.Left,
617
- KeyCodes.Up,
618
- KeyCodes.Down,
619
- KeyCodes.Meta,
620
- KeyCodes.Escape,
621
- KeyCodes.Tab,
622
- KeyCodes.Home,
623
- KeyCodes.End,
624
- KeyCodes.CapsLock,
625
- KeyCodes.PageDown,
626
- KeyCodes.PageUp,
627
- KeyCodes.ScrollLock,
628
- KeyCodes.NumLock,
629
- KeyCodes.Insert,
630
- KeyCodes.Pause,
631
- ].includes(keyCode) ||
632
- // Exclude Function keys
633
- (keyCode >= KeyCodes.F1 && keyCode <= KeyCodes.F12)
634
- );
635
- }, []);
636
-
637
- const handleKeyDown = useCallback(
638
- (e: React.KeyboardEvent<HTMLElement>) => {
639
- const keyCode = e.nativeEvent.keyCode;
640
- if (keyCode === KeyCodes.Tab && !initialActiveCell.current) {
641
- initialActiveCell.current = activeCell;
642
- }
643
- if (isArrowKey(keyCode)) {
644
- initialActiveCell.current = undefined;
645
- }
646
- if (
647
- isSelectionKey(keyCode) ||
648
- e.nativeEvent.ctrlKey ||
649
- (e.nativeEvent.shiftKey &&
650
- (e.nativeEvent.key === "Shift" ||
651
- e.nativeEvent.which === KeyCodes.SPACE)) ||
652
- e.nativeEvent.metaKey ||
653
- e.nativeEvent.which === KeyCodes.ALT
654
- )
655
- return;
656
-
657
- /* If user has not made any selection yet */
658
- if (!activeCell) return;
659
-
660
- const { rowIndex, columnIndex } = activeCell;
661
-
662
- if (keyCode === KeyCodes.Delete || keyCode === KeyCodes.BackSpace) {
663
- // TODO: onbefore delete
664
- onDelete && onDelete(activeCell, selections);
665
- e.preventDefault();
666
- return;
667
- }
668
-
669
- const initialValue =
670
- keyCode === KeyCodes.Enter // Enter key
671
- ? undefined
672
- : e.nativeEvent.key;
673
-
674
- makeEditable({ rowIndex, columnIndex }, initialValue);
675
-
676
- /* Prevent the first keystroke */
677
- e.preventDefault();
678
- },
679
- [getValue, selections, activeCell, onDelete]
680
- );
681
-
682
- /**
683
- * Get next focusable cell
684
- * Respects selection bounds
685
- */
686
-
687
- const nextFocusableCell = useCallback(
688
- (
689
- currentCell: CellInterface,
690
- direction: Direction = Direction.Right
691
- ): CellInterface | null => {
692
- /* Next immediate cell */
693
- const bounds = gridRef.current?.getCellBounds(currentCell);
694
- if (!bounds) return null;
695
- let nextActiveCell;
696
- switch (direction) {
697
- case Direction.Right: {
698
- let columnIndex = clampIndex(
699
- Math.min(bounds.right + 1, selectionRightBound),
700
- isHiddenColumn,
701
- direction
702
- );
703
- nextActiveCell = {
704
- rowIndex: bounds.top,
705
- columnIndex,
706
- };
707
- break;
708
- }
709
- case Direction.Up:
710
- let rowIndex = clampIndex(
711
- Math.max(bounds.top - 1, selectionTopBound),
712
- isHiddenRow,
713
- direction
714
- );
715
- nextActiveCell = {
716
- rowIndex,
717
- columnIndex: bounds.left,
718
- };
719
- break;
720
-
721
- case Direction.Left: {
722
- let columnIndex = clampIndex(
723
- Math.max(bounds.left - 1, selectionLeftBound),
724
- isHiddenColumn,
725
- direction
726
- );
727
- nextActiveCell = {
728
- rowIndex: bounds.top,
729
- columnIndex,
730
- };
731
- break;
732
- }
733
-
734
- default: {
735
- // Down
736
- let rowIndex = clampIndex(
737
- Math.min(
738
- (initialActiveCell.current?.rowIndex ?? bounds.bottom) + 1,
739
- selectionBottomBound
740
- ),
741
- isHiddenRow,
742
- direction
743
- );
744
- nextActiveCell = {
745
- rowIndex,
746
- columnIndex: initialActiveCell.current?.columnIndex ?? bounds.left,
747
- };
748
- break;
749
- }
750
- }
751
- if (direction === Direction.Right && !initialActiveCell.current) {
752
- initialActiveCell.current = currentCell;
753
- }
754
-
755
- if (direction === Direction.Down) {
756
- /* Move to the next row + cell */
757
- initialActiveCell.current = undefined;
758
- }
759
-
760
- /* If user has selected some cells and active cell is within this selection */
761
- if (selections.length && currentCell && gridRef.current) {
762
- const { bounds } = selections[selections.length - 1];
763
- const activeCellBounds = gridRef.current.getCellBounds(currentCell);
764
- const nextCell = findNextCellWithinBounds(
765
- activeCellBounds,
766
- bounds,
767
- direction
768
- );
769
- if (nextCell) nextActiveCell = nextCell;
770
- }
771
- return nextActiveCell;
772
- },
773
- [
774
- selections,
775
- isHiddenRow,
776
- isHiddenColumn,
777
- selectionBottomBound,
778
- selectionTopBound,
779
- ]
780
- );
781
-
782
- /* Save the value */
783
- const handleSubmit = useCallback(
784
- (
785
- value: string,
786
- activeCell: CellInterface,
787
- nextActiveCell?: CellInterface | null
788
- ) => {
789
- /**
790
- * Hide the editor first, so that we can handle onBlur events
791
- * 1. Editor hides -> Submit
792
- * 2. If user clicks outside the grid, onBlur is called, if there is a activeCell, we do another submit
793
- */
794
- hideEditor();
795
-
796
- /* Save the new value */
797
- onSubmit && onSubmit(value, activeCell, nextActiveCell);
798
-
799
- /* Keep the focus */
800
- focusGrid();
801
- },
802
- [onSubmit]
803
- );
804
-
805
- /* When the input is blurred out */
806
- const handleCancel = useCallback(
807
- (e?: React.KeyboardEvent<HTMLTextAreaElement>) => {
808
- hideEditor();
809
- onCancel && onCancel(e);
810
- /* Keep the focus back in the grid */
811
- focusGrid();
812
- },
813
- [onCancel]
814
- );
815
-
816
- const handleMouseDown = useCallback(
817
- (e: React.MouseEvent<HTMLDivElement>) => {
818
- /* Persistent input, hides only during Enter key or during submit or cancel calls */
819
- if (!hideOnBlur) {
820
- return;
821
- }
822
- if (currentActiveCellRef.current) {
823
- if (isDirtyRef.current) {
824
- handleSubmit(currentValueRef.current, currentActiveCellRef.current);
825
- } else {
826
- handleCancel();
827
- }
828
- }
829
- initialActiveCell.current = undefined;
830
- },
831
- [hideOnBlur, handleSubmit, handleCancel]
832
- );
833
-
834
- const handleChange = useCallback(
835
- (newValue: string, activeCell: CellInterface) => {
836
- /**
837
- * Make sure we dont call onChange if initialValue is set
838
- * This is to accomodate for editor that fire onChange during initialvalue
839
- * Eg: Slate <Editor value='' onChange />
840
- */
841
- if (
842
- initialValueRef.current !== void 0 &&
843
- initialValueRef.current === newValue
844
- ) {
845
- initialValueRef.current = void 0;
846
- return;
847
- }
848
- if (!currentActiveCellRef.current) return;
849
- /* Check if the value has changed. Used to conditionally submit if editor is not in focus */
850
- isDirtyRef.current = newValue !== value;
851
- setValue(newValue);
852
- onChange?.(newValue, activeCell);
853
- },
854
- [value]
855
- );
856
-
857
- const handleScroll = useCallback((scrollPos: ScrollCoords) => {
858
- if (!currentActiveCellRef.current) return;
859
- setScrollPosition(scrollPos);
860
- }, []);
861
-
862
- /* Editor */
863
- const editingCell = currentActiveCellRef.current;
864
- const Editor = useMemo(() => {
865
- return editingCell
866
- ? getEditor(editingCell) || getDefaultEditor(editingCell)
867
- : null;
868
- }, [editingCell]);
869
-
870
- const handleBlur = useCallback((e: React.FocusEvent) => {
871
- if (currentActiveCellRef.current) {
872
- /* Keep the focus */
873
- focusGrid();
874
- }
875
- }, []);
876
-
877
- const finalCellPosition = useMemo(() => {
878
- /**
879
- * Since the editor is sticky,
880
- * we dont need to adjust the position,
881
- * as scrollposition wont move the editor
882
- *
883
- * When the editor is first active, in makeEditable,
884
- * we accomodate for the initial scrollPosition
885
- */
886
- if (sticky) {
887
- return position;
888
- }
889
- /**
890
- * If editor is not sticky, keep adjusting
891
- * its position to accomodate for scroll
892
- */
893
- return getCellPosition(position, scrollPosition);
894
- }, [sticky, position, scrollPosition, frozenColumns, frozenRows]);
895
-
896
- /* Get offset of frozen rows and columns */
897
- const frozenRowOffset = gridRef.current?.getRowOffset(frozenRows);
898
- const frozenColumnOffset = gridRef.current?.getColumnOffset(frozenColumns);
899
-
900
- const editorComponent =
901
- isEditorShown && Editor ? (
902
- <Editor
903
- {...editorProps?.()}
904
- /* This is the cell that is currently being edited */
905
- cell={editingCell}
906
- activeCell={activeCell}
907
- autoFocus={autoFocus}
908
- value={value}
909
- selections={selections}
910
- onChange={handleChange}
911
- onSubmit={handleSubmit}
912
- onCancel={handleCancel}
913
- position={finalCellPosition}
914
- scrollPosition={scrollPosition}
915
- nextFocusableCell={nextFocusableCell}
916
- onBlur={handleBlur}
917
- onKeyDown={onKeyDown}
918
- maxWidth={maxEditorDimensionsRef.current?.width}
919
- maxHeight={maxEditorDimensionsRef.current?.height}
920
- isFrozenRow={isFrozenRow}
921
- isFrozenColumn={isFrozenColumn}
922
- frozenRowOffset={frozenRowOffset}
923
- frozenColumnOffset={frozenColumnOffset}
924
- />
925
- ) : null;
926
-
927
- return {
928
- editorComponent,
929
- onDoubleClick: handleDoubleClick,
930
- onKeyDown: handleKeyDown,
931
- nextFocusableCell,
932
- isEditInProgress: !!editingCell,
933
- editingCell,
934
- makeEditable,
935
- setValue: handleChange,
936
- hideEditor,
937
- showEditor,
938
- submitEditor: handleSubmit,
939
- cancelEditor: handleCancel,
940
- onMouseDown: handleMouseDown,
941
- onScroll: handleScroll,
942
- };
943
- };
944
-
945
- export default useEditable;