@rowsncolumns/grid 9.0.4 → 13.2.32

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
package/src/Grid.tsx DELETED
@@ -1,2677 +0,0 @@
1
- import React, {
2
- useRef,
3
- useCallback,
4
- useState,
5
- useMemo,
6
- forwardRef,
7
- useImperativeHandle,
8
- useReducer,
9
- memo,
10
- useEffect,
11
- Key,
12
- UIEvent,
13
- } from "react";
14
- import { Stage, Layer, Group, Line } from "react-konva";
15
- import {
16
- getRowStartIndexForOffset,
17
- getRowStopIndexForStartIndex,
18
- getColumnStartIndexForOffset,
19
- getColumnStopIndexForStartIndex,
20
- itemKey,
21
- getRowOffset as getRowOffsetHelper,
22
- getColumnOffset as getColumnOffsetHelper,
23
- getColumnWidth as getColumnWidthHelper,
24
- getRowHeight as getRowHeightHelper,
25
- getEstimatedTotalHeight,
26
- getEstimatedTotalWidth,
27
- getBoundedCells,
28
- cellIdentifier,
29
- throttle,
30
- getOffsetForColumnAndAlignment,
31
- getOffsetForRowAndAlignment,
32
- requestTimeout,
33
- cancelTimeout,
34
- TimeoutID,
35
- Align,
36
- clampIndex,
37
- canUseDOM,
38
- } from "./helpers";
39
- import type { ShapeConfig } from "konva/lib/Shape";
40
- import { CellRenderer as defaultItemRenderer } from "./Cell";
41
- import { CellRenderer as defaultOverlayRenderer } from "./CellOverlay";
42
- import Selection from "./Selection";
43
- import FillHandle from "./FillHandle";
44
- import GridLine from "./GridLine";
45
- import { createHTMLBox } from "./utils";
46
- import invariant from "tiny-invariant";
47
- import type { StageConfig } from "konva/lib/Stage";
48
- import { Direction } from "./types";
49
- import Konva from "konva";
50
-
51
- export interface GridProps
52
- extends Omit<React.HTMLAttributes<HTMLDivElement>, "onScroll" | "children"> {
53
- /**
54
- * Width of the grid
55
- */
56
- width?: number;
57
- /**
58
- * Height of the grid
59
- */
60
- height?: number;
61
- /**
62
- * No of columns in the grid
63
- */
64
- columnCount: number;
65
- /**
66
- * No of rows in the grid
67
- */
68
- rowCount: number;
69
- /**
70
- * Should return height of a row at an index
71
- */
72
- rowHeight?: ItemSizer;
73
- /**
74
- * Should return width of a column at an index
75
- */
76
- columnWidth?: ItemSizer;
77
- /**
78
- * Size of the scrollbar. Default is 13
79
- */
80
- scrollbarSize?: number;
81
- /**
82
- * Helps in lazy grid width calculation
83
- */
84
- estimatedColumnWidth?: number;
85
- /**
86
- * Helps in lazy grid height calculation
87
- */
88
- estimatedRowHeight?: number;
89
- /**
90
- * Called when user scrolls the grid
91
- */
92
- onScroll?: ({ scrollLeft, scrollTop }: ScrollCoords) => void;
93
- /**
94
- * Called immediately on scroll
95
- */
96
- onImmediateScroll?: ({ scrollLeft, scrollTop }: ScrollCoords) => void;
97
- /**
98
- * Show scrollbars on the left and right of the grid
99
- */
100
- showScrollbar?: boolean;
101
- /**
102
- * Currently active cell
103
- */
104
- activeCell?: CellInterface | null;
105
- /**
106
- * Background of selection
107
- */
108
- selectionBackgroundColor?: string;
109
- /**
110
- * Border color of selected area
111
- */
112
- selectionBorderColor?: string;
113
- /**
114
- * Stroke width of the selection
115
- */
116
- selectionStrokeWidth?: number;
117
- /**
118
- * Active Cell Stroke width
119
- */
120
- activeCellStrokeWidth?: number;
121
- /**
122
- * Array of selected cell areas
123
- */
124
- selections?: SelectionArea[];
125
- /**
126
- * Fill selection
127
- */
128
- fillSelection?: SelectionArea | null;
129
- /**
130
- * Array of merged cells
131
- */
132
- mergedCells?: AreaProps[];
133
- /**
134
- * Number of frozen rows
135
- */
136
- frozenRows?: number;
137
- /**
138
- * Number of frozen columns
139
- */
140
- frozenColumns?: number;
141
- /**
142
- * Snap to row and column when scrolling
143
- */
144
- snap?: boolean;
145
- /**
146
- * Show shadow as you scroll for frozen rows and columns
147
- */
148
- showFrozenShadow?: boolean;
149
- /**
150
- * Shadow settings
151
- */
152
- shadowSettings?: ShapeConfig;
153
- /**
154
- * Scroll throttle wait timeout
155
- */
156
- scrollThrottleTimeout?: number;
157
- /**
158
- * Cell styles for border
159
- */
160
- borderStyles?: StylingProps;
161
- /**
162
- * Extend certains to coords
163
- */
164
- cellAreas?: CellRangeArea[];
165
- /**
166
- * Cell renderer. Must be a Konva Component eg: Group, Rect etc
167
- */
168
- itemRenderer?: (props: RendererProps) => React.ReactNode;
169
- /**
170
- * Render custom overlays like stroke on top of cell
171
- */
172
- overlayRenderer?: (props: RendererProps) => React.ReactNode;
173
- /**
174
- * Allow users to customize selected cells design
175
- */
176
- selectionRenderer?: (props: SelectionProps) => React.ReactNode;
177
- /**
178
- * Bind to fill handle
179
- */
180
- fillHandleProps?: Record<string, (e: any) => void>;
181
- /**
182
- * Fired when scroll viewport changes
183
- */
184
- onViewChange?: (view: ViewPortProps) => void;
185
- /**
186
- * Called right before a row is being rendered.
187
- * Will be called for frozen cells and merged cells
188
- */
189
- onBeforeRenderRow?: (rowIndex: number) => void;
190
- /**
191
- * Custom grid overlays
192
- */
193
- children?: (props: ScrollCoords) => React.ReactNode;
194
- /**
195
- * Allows users to Wrap stage children in Top level Context
196
- */
197
- wrapper?: (children: React.ReactNode) => React.ReactNode;
198
- /**
199
- * Props that can be injected to Konva stage
200
- */
201
- stageProps?: Omit<StageConfig, "container">;
202
- /**
203
- * Show fillhandle
204
- */
205
- showFillHandle?: boolean;
206
- /**
207
- * Overscan row and columns
208
- */
209
- overscanCount?: number;
210
- /**
211
- * Border color of fill handle
212
- */
213
- fillhandleBorderColor?: string;
214
- /**
215
- * Show grid lines.
216
- * Useful for spreadsheets
217
- */
218
- showGridLines?: boolean;
219
- /**
220
- * Customize grid line color
221
- */
222
- gridLineColor?: string;
223
- /**
224
- * Width of the grid line
225
- */
226
- gridLineWidth?: number;
227
- /**
228
- * Gridline component
229
- */
230
- gridLineRenderer?: (props: ShapeConfig) => React.ReactNode;
231
- /**
232
- * Shadow stroke color
233
- */
234
- shadowStroke?: string;
235
- /**
236
- * Draw overlay for each cell.
237
- * Can be used to apply stroke or draw on top of a cell
238
- */
239
- enableCellOverlay?: boolean;
240
- /**
241
- * Check if its hidden row
242
- */
243
- isHiddenRow?: (rowIndex: number) => boolean;
244
- /**
245
- * Check if its a hidden column. Skip rendering hidden
246
- */
247
- isHiddenColumn?: (columnIndex: number) => boolean;
248
- /**
249
- * Is Hidden cell
250
- */
251
- isHiddenCell?: (rowIndex: number, columnIndex: number) => boolean;
252
- /**
253
- * Scale
254
- */
255
- scale?: number;
256
- /**
257
- * Enable draging active cell and selections
258
- */
259
- enableSelectionDrag?: boolean;
260
- /**
261
- * Is user currently dragging a selection
262
- */
263
- isDraggingSelection?: boolean;
264
- }
265
-
266
- export interface CellRangeArea extends CellInterface {
267
- toColumnIndex: number;
268
- }
269
-
270
- export type RefAttribute = {
271
- ref?: React.Ref<GridRef>;
272
- };
273
-
274
- export type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
275
- export interface SelectionProps
276
- extends AreaMeta,
277
- ShapeConfig,
278
- Omit<React.HTMLAttributes<HTMLDivElement>, "draggable"> {
279
- fillHandleProps?: Record<string, (e: any) => void>;
280
- type: "fill" | "activeCell" | "selection" | "border";
281
- isDragging?: boolean;
282
- inProgress?: boolean;
283
- activeCell?: CellInterface;
284
- selection?: SelectionArea;
285
- key: number;
286
- draggable?: boolean;
287
- bounds?: AreaProps;
288
- borderCoverWidth?: number;
289
- }
290
-
291
- export type ScrollCoords = {
292
- scrollTop: number;
293
- scrollLeft: number;
294
- };
295
-
296
- export type OptionalScrollCoords = {
297
- scrollTop?: number;
298
- scrollLeft?: number;
299
- };
300
-
301
- export interface ScrollState extends ScrollCoords {
302
- isScrolling: boolean;
303
- verticalScrollDirection: Direction;
304
- horizontalScrollDirection: Direction;
305
- }
306
-
307
- export type RenderComponent = React.FC<RendererProps>;
308
- export interface CellPosition extends Pick<ShapeConfig, "x" | "y"> {
309
- width?: ShapeConfig["width"];
310
- height?: ShapeConfig["height"];
311
- }
312
- export interface RendererProps
313
- extends CellInterface,
314
- CellPosition,
315
- Omit<ShapeConfig, "scale"> {
316
- key: Key;
317
- isMergedCell?: boolean;
318
- isOverlay?: boolean;
319
- }
320
-
321
- export type ItemSizer = (index: number) => number;
322
-
323
- export interface SelectionArea extends AreaStyle {
324
- bounds: AreaProps;
325
- inProgress?: boolean;
326
- /**
327
- * When user drags the fill handle
328
- */
329
- isFilling?: boolean;
330
- }
331
- export interface AreaProps {
332
- top: number;
333
- bottom: number;
334
- left: number;
335
- right: number;
336
- }
337
-
338
- export interface CellInterface {
339
- rowIndex: number;
340
- columnIndex: number;
341
- }
342
-
343
- export interface OptionalCellInterface {
344
- rowIndex?: number;
345
- columnIndex?: number;
346
- }
347
-
348
- export interface ViewPortProps {
349
- rowStartIndex: number;
350
- rowStopIndex: number;
351
- columnStartIndex: number;
352
- columnStopIndex: number;
353
- visibleRowStartIndex: number;
354
- visibleRowStopIndex: number;
355
- visibleColumnStartIndex: number;
356
- visibleColumnStopIndex: number;
357
- }
358
-
359
- export interface InstanceInterface {
360
- columnMetadataMap: CellMetaDataMap;
361
- rowMetadataMap: CellMetaDataMap;
362
- lastMeasuredColumnIndex: number;
363
- lastMeasuredRowIndex: number;
364
- estimatedRowHeight: number;
365
- estimatedColumnWidth: number;
366
- recalcColumnIndices: number[];
367
- recalcRowIndices: number[];
368
- }
369
-
370
- export type CellMetaDataMap = Record<number, CellMetaData>;
371
- export type CellMetaData = {
372
- offset: number;
373
- size: number;
374
- };
375
-
376
- export interface SnapRowProps {
377
- deltaY: number;
378
- }
379
-
380
- export interface SnapColumnProps {
381
- deltaX: number;
382
- }
383
-
384
- export interface PosXY {
385
- x?: number;
386
- y?: number;
387
- }
388
-
389
- export interface PosXYRequired {
390
- x: number;
391
- y: number;
392
- }
393
-
394
- export type GridRef = {
395
- scrollTo: (scrollPosition: ScrollCoords) => void;
396
- scrollBy: (pos: PosXY) => void;
397
- stage: Konva.Stage | null;
398
- container: HTMLDivElement | null;
399
- resetAfterIndices: (
400
- coords: OptionalCellInterface,
401
- shouldForceUpdate?: boolean
402
- ) => void;
403
- getScrollPosition: () => ScrollCoords;
404
- isMergedCell: (coords: CellInterface) => boolean;
405
- getCellBounds: (coords: CellInterface, spanMerges?: boolean) => AreaProps;
406
- getCellCoordsFromOffset: (
407
- x: number,
408
- y: number,
409
- includeFrozen?: boolean
410
- ) => CellInterface | null;
411
- getCellOffsetFromCoords: (coords: CellInterface) => CellPosition;
412
- getActualCellCoords: (coords: CellInterface) => CellInterface;
413
- scrollToItem: (coords: OptionalCellInterface, align?: Align) => void;
414
- focus: () => void;
415
- resizeColumns: (indices: number[]) => void;
416
- resizeRows: (indices: number[]) => void;
417
- getViewPort: () => ViewPortProps;
418
- getRelativePositionFromOffset: (x: number, y: number) => PosXYRequired | null;
419
- scrollToTop: () => void;
420
- scrollToBottom: () => void;
421
- getDimensions: () => {
422
- containerWidth: number;
423
- containerHeight: number;
424
- estimatedTotalWidth: number;
425
- estimatedTotalHeight: number;
426
- };
427
- getRowOffset: (index: number) => number;
428
- getColumnOffset: (index: number) => number;
429
- };
430
-
431
- export type MergedCellMap = Map<string, AreaProps>;
432
-
433
- export type StylingProps = AreaStyle[];
434
-
435
- export interface AreaStyle extends AreaMeta {
436
- bounds: AreaProps;
437
- style?: Style;
438
- strokeStyle?: "dashed" | "solid" | "dotted";
439
- }
440
- export interface AreaMeta {
441
- title?: string;
442
- [key: string]: any;
443
- }
444
-
445
- export interface Style {
446
- stroke?: string;
447
- strokeLeftColor?: string;
448
- strokeTopColor?: string;
449
- strokeRightColor?: string;
450
- strokeBottomColor?: string;
451
- strokeWidth?: number;
452
- strokeTopWidth?: number;
453
- strokeRightWidth?: number;
454
- strokeBottomWidth?: number;
455
- strokeLeftWidth?: number;
456
- strokeStyle?: string;
457
- }
458
-
459
- interface ScrollSnapRef {
460
- visibleRowStartIndex: number;
461
- rowCount: number;
462
- frozenRows: number;
463
- visibleColumnStartIndex: number;
464
- columnCount: number;
465
- frozenColumns: number;
466
- isHiddenRow?: (rowIndex: number) => boolean;
467
- isHiddenColumn?: (columnIndex: number) => boolean;
468
- }
469
-
470
- const DEFAULT_ESTIMATED_ITEM_SIZE = 50;
471
- const defaultShadowSettings: ShapeConfig = {
472
- strokeWidth: 1,
473
- };
474
- const defaultRowHeight = () => 20;
475
- const defaultColumnWidth = () => 60;
476
- const defaultSelectionRenderer = (props: SelectionProps) => {
477
- return <Selection {...props} />;
478
- };
479
- const defaultGridLineRenderer = (props: ShapeConfig) => {
480
- return <GridLine {...props} />;
481
- };
482
- export const RESET_SCROLL_EVENTS_DEBOUNCE_INTERVAL = 150;
483
- /* Placeholder for empty array -> Prevents re-render */
484
- const EMPTY_ARRAY: any = [];
485
-
486
- /**
487
- * Grid component using React Konva
488
- * @param props
489
- *
490
- * TODO: Fix bug with snapping, since onWheel is a global handler, rowCount, columnCount becomes state
491
- */
492
- const Grid: React.FC<GridProps & RefAttribute> = memo(
493
- forwardRef<GridRef, GridProps>((props, forwardedRef) => {
494
- const {
495
- width: containerWidth = 800,
496
- height: containerHeight = 600,
497
- estimatedColumnWidth,
498
- estimatedRowHeight,
499
- rowHeight = defaultRowHeight,
500
- columnWidth = defaultColumnWidth,
501
- rowCount = 0,
502
- columnCount = 0,
503
- scrollbarSize = 13,
504
- onScroll,
505
- onImmediateScroll,
506
- showScrollbar = true,
507
- selectionBackgroundColor = "rgb(14, 101, 235, 0.1)",
508
- selectionBorderColor = "#1a73e8",
509
- selectionStrokeWidth = 1,
510
- activeCellStrokeWidth = 2,
511
- activeCell,
512
- selections = EMPTY_ARRAY as SelectionArea[],
513
- frozenRows = 0,
514
- frozenColumns = 0,
515
- itemRenderer = defaultItemRenderer,
516
- enableCellOverlay = false,
517
- overlayRenderer = defaultOverlayRenderer,
518
- mergedCells = EMPTY_ARRAY as AreaProps[],
519
- snap = false,
520
- scrollThrottleTimeout = 80,
521
- onViewChange,
522
- selectionRenderer = defaultSelectionRenderer,
523
- onBeforeRenderRow,
524
- showFrozenShadow = false,
525
- shadowSettings = defaultShadowSettings,
526
- borderStyles = EMPTY_ARRAY as StylingProps,
527
- children,
528
- stageProps,
529
- wrapper = (children: React.ReactNode): React.ReactNode => children,
530
- cellAreas = EMPTY_ARRAY,
531
- showFillHandle = false,
532
- fillSelection,
533
- overscanCount = 1,
534
- fillHandleProps,
535
- fillhandleBorderColor = "white",
536
- showGridLines = false,
537
- gridLineColor = "#E3E2E2",
538
- gridLineWidth = 1,
539
- gridLineRenderer = defaultGridLineRenderer,
540
- isHiddenRow,
541
- isHiddenColumn,
542
- isHiddenCell,
543
- scale = 1,
544
- enableSelectionDrag = false,
545
- isDraggingSelection = false,
546
- ...rest
547
- } = props;
548
-
549
- invariant(
550
- !(children && typeof children !== "function"),
551
- "Children should be a function"
552
- );
553
-
554
- /* Expose some methods in ref */
555
- useImperativeHandle(forwardedRef, () => {
556
- return {
557
- scrollTo,
558
- scrollBy,
559
- scrollToItem,
560
- stage: stageRef.current,
561
- container: containerRef.current,
562
- resetAfterIndices,
563
- getScrollPosition,
564
- isMergedCell,
565
- getCellBounds,
566
- getCellCoordsFromOffset,
567
- getCellOffsetFromCoords,
568
- getActualCellCoords,
569
- focus: focusContainer,
570
- resizeColumns,
571
- resizeRows,
572
- getViewPort,
573
- getRelativePositionFromOffset,
574
- scrollToTop,
575
- scrollToBottom,
576
- getDimensions,
577
- getRowOffset,
578
- getColumnOffset,
579
- };
580
- });
581
-
582
- const instanceProps = useRef<InstanceInterface>({
583
- columnMetadataMap: {},
584
- rowMetadataMap: {},
585
- lastMeasuredColumnIndex: -1,
586
- lastMeasuredRowIndex: -1,
587
- estimatedColumnWidth: estimatedColumnWidth || DEFAULT_ESTIMATED_ITEM_SIZE,
588
- estimatedRowHeight: estimatedRowHeight || DEFAULT_ESTIMATED_ITEM_SIZE,
589
- recalcColumnIndices: [],
590
- recalcRowIndices: [],
591
- });
592
- const stageRef = useRef<Konva.Stage>(null);
593
- const containerRef = useRef<HTMLDivElement>(null);
594
- const scrollContainerRef = useRef<HTMLDivElement>(null);
595
- const verticalScrollRef = useRef<HTMLDivElement>(null);
596
- const wheelingRef = useRef<number | null>(null);
597
- const horizontalScrollRef = useRef<HTMLDivElement>(null);
598
- const [_, forceRender] = useReducer((s) => s + 1, 0);
599
- const [scrollState, setScrollState] = useState<ScrollState>({
600
- scrollTop: 0,
601
- scrollLeft: 0,
602
- isScrolling: false,
603
- verticalScrollDirection: Direction.Down,
604
- horizontalScrollDirection: Direction.Right,
605
- });
606
- const scrollSnapRefs = useRef<ScrollSnapRef | null>(null);
607
- const {
608
- scrollTop,
609
- scrollLeft,
610
- isScrolling,
611
- verticalScrollDirection,
612
- horizontalScrollDirection,
613
- } = scrollState;
614
- const isMounted = useRef(false);
615
-
616
- /* Focus container */
617
- const focusContainer = useCallback(() => {
618
- if (canUseDOM && document.activeElement !== containerRef.current) {
619
- return containerRef.current?.focus();
620
- }
621
- }, []);
622
-
623
- /**
624
- * Get top offset of rowIndex
625
- */
626
- const getRowOffset = useCallback(
627
- (index: number) => {
628
- return getRowOffsetHelper({
629
- index,
630
- rowHeight,
631
- columnWidth,
632
- instanceProps: instanceProps.current,
633
- scale,
634
- });
635
- },
636
- [rowHeight, columnWidth, instanceProps.current, scale]
637
- );
638
-
639
- /**
640
- * Get lefft offset of columnIndex
641
- */
642
- const getColumnOffset = useCallback(
643
- (index: number) => {
644
- return getColumnOffsetHelper({
645
- index,
646
- rowHeight,
647
- columnWidth,
648
- instanceProps: instanceProps.current,
649
- scale,
650
- });
651
- },
652
- [rowHeight, columnWidth, instanceProps.current, scale]
653
- );
654
-
655
- /**
656
- * Get height of rowIndex
657
- */
658
- const getRowHeight = useCallback(
659
- (index: number) => {
660
- return getRowHeightHelper(index, instanceProps.current);
661
- },
662
- [instanceProps.current]
663
- );
664
-
665
- /**
666
- * Get height of columNiondex
667
- */
668
- const getColumnWidth = useCallback(
669
- (index: number) => {
670
- return getColumnWidthHelper(index, instanceProps.current);
671
- },
672
- [instanceProps.current]
673
- );
674
-
675
- /**
676
- * onScroll callback
677
- */
678
- useEffect(() => {
679
- if (!isMounted.current) return;
680
- onScroll?.({ scrollTop, scrollLeft });
681
- }, [scrollTop, scrollLeft]);
682
-
683
- /**
684
- * Handle mouse wheeel
685
- */
686
- useEffect(() => {
687
- scrollContainerRef.current?.addEventListener("wheel", handleWheel, {
688
- passive: false,
689
- });
690
- isMounted.current = true;
691
- return () => {
692
- scrollContainerRef.current?.removeEventListener("wheel", handleWheel);
693
- };
694
- }, []);
695
-
696
- /**
697
- * Imperatively get the current scroll position
698
- */
699
- const getScrollPosition = useCallback(() => {
700
- return {
701
- scrollTop,
702
- scrollLeft,
703
- };
704
- }, [scrollTop, scrollLeft]);
705
-
706
- /* Redraw grid imperatively */
707
- const resetAfterIndices = useCallback(
708
- (
709
- { columnIndex, rowIndex }: OptionalCellInterface,
710
- shouldForceUpdate: boolean = true
711
- ) => {
712
- if (typeof columnIndex === "number") {
713
- instanceProps.current.recalcColumnIndices = [];
714
- instanceProps.current.lastMeasuredColumnIndex = Math.min(
715
- instanceProps.current.lastMeasuredColumnIndex,
716
- columnIndex - 1
717
- );
718
- }
719
- if (typeof rowIndex === "number") {
720
- instanceProps.current.recalcRowIndices = [];
721
- instanceProps.current.lastMeasuredRowIndex = Math.min(
722
- instanceProps.current.lastMeasuredRowIndex,
723
- rowIndex - 1
724
- );
725
- }
726
- if (shouldForceUpdate) forceRender();
727
- },
728
- []
729
- );
730
-
731
- /**
732
- * Create a map of merged cells
733
- * [rowIndex, columnindex] => [parentRowIndex, parentColumnIndex]
734
- */
735
- const mergedCellMap = useMemo((): MergedCellMap => {
736
- const mergedCellMap = new Map();
737
- for (let i = 0; i < mergedCells.length; i++) {
738
- const bounds = mergedCells[i];
739
- for (const cell of getBoundedCells(bounds)) {
740
- mergedCellMap.set(cell, bounds);
741
- }
742
- }
743
- return mergedCellMap;
744
- }, [mergedCells]);
745
-
746
- /* Check if a cell is part of a merged cell */
747
- const isMergedCell = useCallback(
748
- ({ rowIndex, columnIndex }: CellInterface) => {
749
- return mergedCellMap.has(cellIdentifier(rowIndex, columnIndex));
750
- },
751
- [mergedCells]
752
- );
753
-
754
- /* Get top, left bounds of a cell */
755
- const getCellBounds = useCallback(
756
- (
757
- { rowIndex, columnIndex }: CellInterface,
758
- spanMerges = true
759
- ): AreaProps => {
760
- if (spanMerges) {
761
- const isMerged = isMergedCell({ rowIndex, columnIndex });
762
- if (isMerged)
763
- return mergedCellMap.get(
764
- cellIdentifier(rowIndex, columnIndex)
765
- ) as AreaProps;
766
- }
767
- return {
768
- top: rowIndex,
769
- left: columnIndex,
770
- right: columnIndex,
771
- bottom: rowIndex,
772
- } as AreaProps;
773
- },
774
- [mergedCellMap]
775
- );
776
-
777
- /* Get top, left bounds of a cell */
778
- const getActualCellCoords = useCallback(
779
- ({ rowIndex, columnIndex }: CellInterface): CellInterface => {
780
- const isMerged = isMergedCell({ rowIndex, columnIndex });
781
- if (isMerged) {
782
- const cell = mergedCellMap.get(
783
- cellIdentifier(rowIndex, columnIndex)
784
- ) as AreaProps;
785
- return {
786
- rowIndex: cell?.top,
787
- columnIndex: cell?.left,
788
- };
789
- }
790
- return {
791
- rowIndex,
792
- columnIndex,
793
- };
794
- },
795
- [mergedCellMap]
796
- );
797
-
798
- const frozenColumnWidth = getColumnOffset(frozenColumns);
799
- const frozenRowHeight = getRowOffset(frozenRows);
800
- const [
801
- rowStartIndex,
802
- rowStopIndex,
803
- visibleRowStartIndex,
804
- visibleRowStopIndex,
805
- ] = useMemo(() => {
806
- const startIndex = getRowStartIndexForOffset({
807
- rowHeight,
808
- columnWidth,
809
- rowCount,
810
- columnCount,
811
- instanceProps: instanceProps.current,
812
- offset: scrollTop + frozenRowHeight,
813
- scale,
814
- });
815
- const stopIndex = getRowStopIndexForStartIndex({
816
- startIndex,
817
- rowCount,
818
- rowHeight,
819
- columnWidth,
820
- scrollTop,
821
- containerHeight,
822
- instanceProps: instanceProps.current,
823
- scale,
824
- });
825
-
826
- // Overscan by one item in each direction so that tab/focus works.
827
- // If there isn't at least one extra item, tab loops back around.
828
- const overscanBackward =
829
- !isScrolling || verticalScrollDirection === Direction.Up
830
- ? Math.max(1, overscanCount)
831
- : 1;
832
- const overscanForward =
833
- !isScrolling || verticalScrollDirection === Direction.Down
834
- ? Math.max(1, overscanCount)
835
- : 1;
836
-
837
- return [
838
- Math.max(0, startIndex - overscanBackward),
839
- Math.max(0, Math.min(rowCount - 1, stopIndex + overscanForward)),
840
- startIndex,
841
- stopIndex,
842
- ];
843
- }, [
844
- rowHeight,
845
- columnWidth,
846
- rowCount,
847
- columnCount,
848
- scale,
849
- scrollTop,
850
- containerHeight,
851
- frozenRows,
852
- overscanCount,
853
- frozenRowHeight,
854
- ]);
855
-
856
- const [
857
- columnStartIndex,
858
- columnStopIndex,
859
- visibleColumnStartIndex,
860
- visibleColumnStopIndex,
861
- ] = useMemo(() => {
862
- const startIndex = getColumnStartIndexForOffset({
863
- rowHeight,
864
- columnWidth,
865
- rowCount,
866
- columnCount,
867
- instanceProps: instanceProps.current,
868
- offset: scrollLeft + frozenColumnWidth,
869
- scale,
870
- });
871
-
872
- const stopIndex = getColumnStopIndexForStartIndex({
873
- startIndex,
874
- columnCount,
875
- rowHeight,
876
- columnWidth,
877
- scrollLeft,
878
- containerWidth,
879
- instanceProps: instanceProps.current,
880
- scale,
881
- });
882
-
883
- // Overscan by one item in each direction so that tab/focus works.
884
- // If there isn't at least one extra item, tab loops back around.
885
- const overscanBackward =
886
- !isScrolling || horizontalScrollDirection === Direction.Left
887
- ? Math.max(1, overscanCount)
888
- : 1;
889
- const overscanForward =
890
- !isScrolling || horizontalScrollDirection === Direction.Right
891
- ? Math.max(1, overscanCount)
892
- : 1;
893
-
894
- return [
895
- Math.max(0, startIndex - overscanBackward),
896
- Math.max(0, Math.min(columnCount - 1, stopIndex + overscanForward)),
897
- startIndex,
898
- stopIndex,
899
- ];
900
- }, [
901
- rowHeight,
902
- columnWidth,
903
- rowCount,
904
- columnCount,
905
- scale,
906
- frozenColumns,
907
- containerWidth,
908
- scrollLeft,
909
- frozenColumnWidth,
910
- ]);
911
-
912
- const estimatedTotalHeight = getEstimatedTotalHeight(
913
- rowCount,
914
- instanceProps.current
915
- );
916
- const estimatedTotalWidth = getEstimatedTotalWidth(
917
- columnCount,
918
- instanceProps.current
919
- );
920
-
921
- /* Method to get dimensions of the grid */
922
- const getDimensions = useCallback(() => {
923
- return {
924
- containerWidth,
925
- containerHeight,
926
- estimatedTotalWidth,
927
- estimatedTotalHeight,
928
- };
929
- }, [
930
- containerWidth,
931
- containerHeight,
932
- estimatedTotalWidth,
933
- estimatedTotalHeight,
934
- ]);
935
-
936
- /**
937
- * Update snap properties if its active
938
- * We need this because we are binding `onwheel` event to document
939
- * So props go stale
940
- */
941
- useEffect(() => {
942
- if (snap) {
943
- scrollSnapRefs.current = {
944
- visibleRowStartIndex,
945
- rowCount,
946
- frozenRows,
947
- visibleColumnStartIndex,
948
- columnCount,
949
- frozenColumns,
950
- isHiddenRow,
951
- isHiddenColumn,
952
- };
953
- }
954
- }, [
955
- snap,
956
- visibleRowStartIndex,
957
- rowCount,
958
- frozenRows,
959
- visibleColumnStartIndex,
960
- columnCount,
961
- frozenColumns,
962
- isHiddenRow,
963
- isHiddenColumn,
964
- ]);
965
-
966
- /**
967
- * Snaps vertical scrollbar to the next/prev visible row
968
- */
969
- const snapToRowFn = useCallback(({ deltaY }: SnapRowProps) => {
970
- if (!verticalScrollRef.current || !scrollSnapRefs.current) return;
971
- if (deltaY !== 0) {
972
- const direction = deltaY < 0 ? Direction.Up : Direction.Down;
973
- const { visibleRowStartIndex, rowCount, isHiddenRow } =
974
- scrollSnapRefs.current;
975
- let nextRowIndex =
976
- direction === Direction.Up
977
- ? // User is scrolling up
978
- Math.max(0, visibleRowStartIndex - 1)
979
- : Math.min(visibleRowStartIndex, rowCount - 1);
980
- /* Ignore hidden row */
981
- nextRowIndex = clampIndex(nextRowIndex, isHiddenRow, direction);
982
- const rowHeight = getRowHeight(nextRowIndex);
983
- verticalScrollRef.current.scrollTop +=
984
- (direction === Direction.Up ? -1 : 1) * rowHeight;
985
- }
986
- }, []);
987
-
988
- /**
989
- * Snaps horizontal scrollbar to the next/prev visible column
990
- */
991
- const snapToColumnFn = useCallback(({ deltaX }: SnapColumnProps) => {
992
- if (!horizontalScrollRef.current || !scrollSnapRefs.current) return;
993
- if (deltaX !== 0) {
994
- const { visibleColumnStartIndex, columnCount, isHiddenColumn } =
995
- scrollSnapRefs.current;
996
- const direction = deltaX < 0 ? Direction.Left : Direction.Right;
997
- let nextColumnIndex =
998
- direction === Direction.Left
999
- ? Math.max(0, visibleColumnStartIndex - 1)
1000
- : Math.min(visibleColumnStartIndex, columnCount - 1);
1001
- /* Ignore hidden column */
1002
- nextColumnIndex = clampIndex(
1003
- nextColumnIndex,
1004
- isHiddenColumn,
1005
- direction
1006
- );
1007
- const columnWidth = getColumnWidth(nextColumnIndex);
1008
- horizontalScrollRef.current.scrollLeft +=
1009
- (direction === Direction.Left ? -1 : 1) * columnWidth;
1010
- }
1011
- }, []);
1012
- const snapToRowThrottler = useRef<({ deltaY }: SnapRowProps) => void>();
1013
- const snapToColumnThrottler =
1014
- useRef<({ deltaX }: SnapColumnProps) => void>();
1015
-
1016
- /**
1017
- * Register snap throttlers
1018
- */
1019
- useEffect(() => {
1020
- if (snap) {
1021
- snapToRowThrottler.current = throttle(
1022
- snapToRowFn,
1023
- scrollThrottleTimeout
1024
- );
1025
- snapToColumnThrottler.current = throttle(
1026
- snapToColumnFn,
1027
- scrollThrottleTimeout
1028
- );
1029
- }
1030
- return () => {
1031
- snapToRowThrottler.current = undefined;
1032
- snapToColumnThrottler.current = undefined;
1033
- };
1034
- }, [snap]);
1035
-
1036
- /* Find frozen column boundary */
1037
- const isWithinFrozenColumnBoundary = useCallback(
1038
- (x: number) => {
1039
- return frozenColumns > 0 && x < frozenColumnWidth;
1040
- },
1041
- [frozenColumns, frozenColumnWidth]
1042
- );
1043
-
1044
- /* Find frozen row boundary */
1045
- const isWithinFrozenRowBoundary = useCallback(
1046
- (y: number) => {
1047
- return frozenRows > 0 && y < frozenRowHeight;
1048
- },
1049
- [frozenRows, frozenRowHeight]
1050
- );
1051
-
1052
- /**
1053
- * Get relative mouse position
1054
- */
1055
- const getRelativePositionFromOffset = useCallback(
1056
- (left: number, top: number): PosXYRequired | null => {
1057
- invariant(
1058
- typeof left === "number" && typeof top === "number",
1059
- "Top and left should be a number"
1060
- );
1061
- if (!stageRef.current) return null;
1062
- const stage = stageRef.current.getStage();
1063
- const rect = containerRef.current?.getBoundingClientRect();
1064
- if (rect) {
1065
- left = left - rect.x;
1066
- top = top - rect.y;
1067
- }
1068
- const { x, y } = stage
1069
- .getAbsoluteTransform()
1070
- .copy()
1071
- .invert()
1072
- .point({ x: left, y: top });
1073
-
1074
- return { x, y };
1075
- },
1076
- []
1077
- );
1078
-
1079
- /**
1080
- * Get cell cordinates from current mouse x/y positions
1081
- */
1082
- const getCellCoordsFromOffset = useCallback(
1083
- (
1084
- left: number,
1085
- top: number,
1086
- includeFrozen: boolean = true
1087
- ): CellInterface | null => {
1088
- const pos = getRelativePositionFromOffset(left, top);
1089
- if (!pos) return null;
1090
- const { x, y } = pos;
1091
- const rowOffset =
1092
- includeFrozen && isWithinFrozenRowBoundary(y) ? y : y + scrollTop;
1093
- const columnOffset =
1094
- includeFrozen && isWithinFrozenColumnBoundary(x) ? x : x + scrollLeft;
1095
- if (
1096
- rowOffset > estimatedTotalHeight ||
1097
- columnOffset > estimatedTotalWidth
1098
- ) {
1099
- return null;
1100
- }
1101
- const rowIndex = getRowStartIndexForOffset({
1102
- rowHeight,
1103
- columnWidth,
1104
- rowCount,
1105
- columnCount,
1106
- instanceProps: instanceProps.current,
1107
- offset: rowOffset,
1108
- scale,
1109
- });
1110
- const columnIndex = getColumnStartIndexForOffset({
1111
- rowHeight,
1112
- columnWidth,
1113
- rowCount,
1114
- columnCount,
1115
- instanceProps: instanceProps.current,
1116
- offset: columnOffset,
1117
- scale,
1118
- });
1119
- /* To be compatible with merged cells */
1120
- const bounds = getCellBounds({ rowIndex, columnIndex });
1121
-
1122
- return { rowIndex: bounds.top, columnIndex: bounds.left };
1123
- },
1124
- [
1125
- scrollLeft,
1126
- rowHeight,
1127
- columnWidth,
1128
- scrollTop,
1129
- rowCount,
1130
- estimatedTotalHeight,
1131
- estimatedTotalWidth,
1132
- columnCount,
1133
- mergedCellMap,
1134
- ]
1135
- );
1136
-
1137
- /**
1138
- * Get cell offset position from rowIndex, columnIndex
1139
- */
1140
- const getCellOffsetFromCoords = useCallback(
1141
- (cell: CellInterface): CellPosition => {
1142
- const {
1143
- top: rowIndex,
1144
- left: columnIndex,
1145
- right,
1146
- bottom,
1147
- } = getCellBounds(cell);
1148
- const x = getColumnOffset(columnIndex);
1149
- const y = getRowOffset(rowIndex);
1150
- const width = getColumnOffset(right + 1) - x;
1151
- const height = getRowOffset(bottom + 1) - y;
1152
-
1153
- return {
1154
- x,
1155
- y,
1156
- width,
1157
- height,
1158
- };
1159
- },
1160
- [mergedCellMap]
1161
- );
1162
-
1163
- /**
1164
- * Resize one or more columns
1165
- */
1166
-
1167
- const resizeColumns = useCallback((indices: number[]) => {
1168
- const leftMost = Math.min(...indices);
1169
- instanceProps.current.recalcColumnIndices = indices;
1170
- resetAfterIndices({ columnIndex: leftMost }, false);
1171
- forceRender();
1172
- }, []);
1173
-
1174
- /**
1175
- * Resize one or more rows
1176
- */
1177
- const resizeRows = useCallback((indices: number[]) => {
1178
- const topMost = Math.min(...indices);
1179
- instanceProps.current.recalcRowIndices = indices;
1180
- resetAfterIndices({ rowIndex: topMost }, false);
1181
- forceRender();
1182
- }, []);
1183
-
1184
- /* Always if the viewport changes */
1185
- useEffect(() => {
1186
- if (instanceProps.current.recalcColumnIndices.length) {
1187
- instanceProps.current.recalcColumnIndices.length = 0;
1188
- }
1189
- if (instanceProps.current.recalcRowIndices.length) {
1190
- instanceProps.current.recalcRowIndices.length = 0;
1191
- }
1192
- }, [rowStopIndex, columnStopIndex, scale]);
1193
-
1194
- /* Get current view port of the grid */
1195
- const getViewPort = useCallback((): ViewPortProps => {
1196
- return {
1197
- rowStartIndex,
1198
- rowStopIndex,
1199
- columnStartIndex,
1200
- columnStopIndex,
1201
- visibleRowStartIndex,
1202
- visibleRowStopIndex,
1203
- visibleColumnStartIndex,
1204
- visibleColumnStopIndex,
1205
- };
1206
- }, [
1207
- rowStartIndex,
1208
- rowStopIndex,
1209
- columnStartIndex,
1210
- columnStopIndex,
1211
- visibleRowStartIndex,
1212
- visibleRowStopIndex,
1213
- visibleColumnStartIndex,
1214
- visibleColumnStopIndex,
1215
- ]);
1216
-
1217
- /**
1218
- * When the grid is scrolling,
1219
- * 1. Stage does not listen to any mouse events
1220
- * 2. Div container does not listen to pointer events
1221
- */
1222
- const resetIsScrollingTimeoutID = useRef<TimeoutID | null>(null);
1223
- const resetIsScrollingDebounced = useCallback(() => {
1224
- if (resetIsScrollingTimeoutID.current !== null) {
1225
- cancelTimeout(resetIsScrollingTimeoutID.current);
1226
- }
1227
- resetIsScrollingTimeoutID.current = requestTimeout(
1228
- resetIsScrolling,
1229
- RESET_SCROLL_EVENTS_DEBOUNCE_INTERVAL
1230
- );
1231
- }, []);
1232
- /* Reset isScrolling */
1233
- const resetIsScrolling = useCallback(() => {
1234
- resetIsScrollingTimeoutID.current = null;
1235
-
1236
- setScrollState((prev) => {
1237
- return {
1238
- ...prev,
1239
- isScrolling: false,
1240
- };
1241
- });
1242
- }, []);
1243
-
1244
- /* Handle vertical scroll */
1245
- const handleScroll = useCallback(
1246
- (e: UIEvent<Element>) => {
1247
- const { scrollTop } = e.currentTarget;
1248
-
1249
- setScrollState((prev) => ({
1250
- ...prev,
1251
- isScrolling: true,
1252
- verticalScrollDirection:
1253
- prev.scrollTop > scrollTop ? Direction.Up : Direction.Down,
1254
- scrollTop,
1255
- }));
1256
-
1257
- /* Scroll callbacks */
1258
- onImmediateScroll?.({ scrollTop, scrollLeft });
1259
-
1260
- /* Reset isScrolling if required */
1261
- resetIsScrollingDebounced();
1262
- },
1263
- [scrollLeft]
1264
- );
1265
-
1266
- /* Handle horizontal scroll */
1267
- const handleScrollLeft = useCallback(
1268
- (e: UIEvent<Element>) => {
1269
- const { scrollLeft } = e.currentTarget;
1270
- setScrollState((prev) => ({
1271
- ...prev,
1272
- isScrolling: true,
1273
- horizontalScrollDirection:
1274
- prev.scrollLeft > scrollLeft ? Direction.Left : Direction.Right,
1275
- scrollLeft,
1276
- }));
1277
-
1278
- /* Scroll callbacks */
1279
- onImmediateScroll?.({ scrollLeft, scrollTop });
1280
-
1281
- /* Reset isScrolling if required */
1282
- resetIsScrollingDebounced();
1283
- },
1284
- [scrollTop]
1285
- );
1286
-
1287
- /* Scroll based on left, top position */
1288
- const scrollTo = useCallback(
1289
- ({ scrollTop, scrollLeft }: OptionalScrollCoords) => {
1290
- /* If scrollbar is visible, lets update it which triggers a state change */
1291
- if (showScrollbar) {
1292
- if (horizontalScrollRef.current && scrollLeft !== void 0)
1293
- horizontalScrollRef.current.scrollLeft = scrollLeft;
1294
- if (verticalScrollRef.current && scrollTop !== void 0)
1295
- verticalScrollRef.current.scrollTop = scrollTop;
1296
- } else {
1297
- setScrollState((prev) => {
1298
- return {
1299
- ...prev,
1300
- scrollLeft: scrollLeft == void 0 ? prev.scrollLeft : scrollLeft,
1301
- scrollTop: scrollTop == void 0 ? prev.scrollTop : scrollTop,
1302
- };
1303
- });
1304
- }
1305
- },
1306
- [showScrollbar]
1307
- );
1308
-
1309
- /* Scroll grid to top */
1310
- const scrollToTop = useCallback(() => {
1311
- scrollTo({ scrollTop: 0, scrollLeft: 0 });
1312
- }, []);
1313
-
1314
- /* Scroll grid to bottom */
1315
- const scrollToBottom = useCallback(() => {
1316
- scrollTo({ scrollTop: estimatedTotalHeight - containerHeight });
1317
- }, [estimatedTotalHeight, containerHeight]);
1318
-
1319
- /**
1320
- * Scrollby utility
1321
- */
1322
- const scrollBy = useCallback(({ x, y }: PosXY) => {
1323
- if (showScrollbar) {
1324
- if (horizontalScrollRef.current && x !== void 0)
1325
- horizontalScrollRef.current.scrollLeft += x;
1326
- if (verticalScrollRef.current && y !== void 0)
1327
- verticalScrollRef.current.scrollTop += y;
1328
- } else {
1329
- setScrollState((prev) => {
1330
- return {
1331
- ...prev,
1332
- scrollLeft: x == void 0 ? prev.scrollLeft : prev.scrollLeft + x,
1333
- scrollTop: y == void 0 ? prev.scrollTop : prev.scrollTop + y,
1334
- };
1335
- });
1336
- }
1337
- }, []);
1338
-
1339
- /**
1340
- * Scrolls to cell
1341
- * Respects frozen rows and columns
1342
- */
1343
- const scrollToItem = useCallback(
1344
- (
1345
- { rowIndex, columnIndex }: OptionalCellInterface,
1346
- align: Align = Align.smart
1347
- ) => {
1348
- const isFrozenRow = rowIndex !== void 0 && rowIndex < frozenRows;
1349
- const isFrozenColumn =
1350
- columnIndex !== void 0 && columnIndex < frozenColumns;
1351
- const frozenColumnOffset = getColumnOffset(frozenColumns);
1352
- /* Making sure getColumnWidth works */
1353
- const x =
1354
- columnIndex !== void 0 ? getColumnOffset(columnIndex) : void 0;
1355
- /* Making sure getRowHeight works */
1356
- const y = rowIndex !== void 0 ? getRowOffset(rowIndex) : void 0;
1357
- const width = columnIndex !== void 0 ? getColumnWidth(columnIndex) : 0;
1358
- const height = rowIndex !== void 0 ? getRowHeight(rowIndex) : 0;
1359
- const columnAlign = width > containerWidth ? Align.start : align;
1360
- const rowAlign = height > containerHeight ? Align.start : align;
1361
- const newScrollLeft =
1362
- columnIndex !== void 0 && !isFrozenColumn
1363
- ? getOffsetForColumnAndAlignment({
1364
- index: columnIndex,
1365
- containerHeight,
1366
- containerWidth,
1367
- columnCount,
1368
- columnWidth,
1369
- rowCount,
1370
- rowHeight,
1371
- scrollOffset: scrollLeft,
1372
- instanceProps: instanceProps.current,
1373
- scrollbarSize,
1374
- frozenOffset: frozenColumnOffset,
1375
- align: columnAlign,
1376
- scale,
1377
- estimatedTotalWidth,
1378
- estimatedTotalHeight,
1379
- })
1380
- : void 0;
1381
-
1382
- const frozenRowOffset = getRowOffset(frozenRows);
1383
- const newScrollTop =
1384
- rowIndex !== void 0 && !isFrozenRow
1385
- ? getOffsetForRowAndAlignment({
1386
- index: rowIndex,
1387
- containerHeight,
1388
- containerWidth,
1389
- columnCount,
1390
- columnWidth,
1391
- rowCount,
1392
- rowHeight,
1393
- scrollOffset: scrollTop,
1394
- instanceProps: instanceProps.current,
1395
- scrollbarSize,
1396
- frozenOffset: frozenRowOffset,
1397
- align: rowAlign,
1398
- scale,
1399
- estimatedTotalWidth,
1400
- estimatedTotalHeight,
1401
- })
1402
- : void 0;
1403
-
1404
- const coords = {
1405
- scrollLeft: newScrollLeft,
1406
- scrollTop: newScrollTop,
1407
- };
1408
- const isOutsideViewport =
1409
- (rowIndex !== void 0 &&
1410
- rowIndex > rowStopIndex + (rowStopIndex - rowStartIndex)) ||
1411
- (columnIndex !== void 0 &&
1412
- columnIndex >
1413
- columnStopIndex + (columnStopIndex - columnStartIndex));
1414
-
1415
- /* Scroll in the next frame, Useful when user wants to jump from 1st column to last */
1416
- if (isOutsideViewport) {
1417
- window.requestAnimationFrame(() => {
1418
- scrollTo(coords);
1419
- });
1420
- } else scrollTo(coords);
1421
- },
1422
- [
1423
- containerHeight,
1424
- containerWidth,
1425
- estimatedTotalWidth,
1426
- estimatedTotalHeight,
1427
- rowCount,
1428
- columnCount,
1429
- scrollbarSize,
1430
- scrollLeft,
1431
- scrollTop,
1432
- frozenRows,
1433
- frozenColumns,
1434
- ]
1435
- );
1436
-
1437
- /**
1438
- * Fired when user tries to scroll the canvas
1439
- */
1440
- const handleWheel = useCallback((event: WheelEvent) => {
1441
- /* If user presses shift key, scroll horizontally */
1442
- const isScrollingHorizontally = event.shiftKey;
1443
-
1444
- /* Prevent browser back in Mac */
1445
- event.preventDefault();
1446
- const { deltaX, deltaY, deltaMode } = event;
1447
- /* Scroll natively */
1448
- if (wheelingRef.current) return;
1449
-
1450
- let dx = isScrollingHorizontally ? deltaY : deltaX;
1451
- let dy = deltaY;
1452
-
1453
- /* Scroll only in one direction */
1454
- const isHorizontal =
1455
- isScrollingHorizontally || Math.abs(dx) > Math.abs(dy);
1456
-
1457
- /* If snaps are active */
1458
- if (snap) {
1459
- if (isHorizontal) {
1460
- snapToColumnThrottler.current?.({
1461
- deltaX,
1462
- });
1463
- } else {
1464
- snapToRowThrottler.current?.({
1465
- deltaY,
1466
- });
1467
- }
1468
- return;
1469
- }
1470
-
1471
- if (deltaMode === 1) {
1472
- dy = dy * scrollbarSize;
1473
- }
1474
- if (!horizontalScrollRef.current || !verticalScrollRef.current) return;
1475
- const currentScroll = isHorizontal
1476
- ? horizontalScrollRef.current?.scrollLeft
1477
- : verticalScrollRef.current?.scrollTop;
1478
- wheelingRef.current = window.requestAnimationFrame(() => {
1479
- wheelingRef.current = null;
1480
- if (isHorizontal) {
1481
- if (horizontalScrollRef.current)
1482
- horizontalScrollRef.current.scrollLeft = currentScroll + dx;
1483
- } else {
1484
- if (verticalScrollRef.current)
1485
- verticalScrollRef.current.scrollTop = currentScroll + dy;
1486
- }
1487
- });
1488
- }, []);
1489
-
1490
- /* Callback when visible rows or columns have changed */
1491
- useEffect(() => {
1492
- onViewChange?.({
1493
- rowStartIndex,
1494
- rowStopIndex,
1495
- columnStartIndex,
1496
- columnStopIndex,
1497
- visibleRowStartIndex,
1498
- visibleRowStopIndex,
1499
- visibleColumnStartIndex,
1500
- visibleColumnStopIndex,
1501
- });
1502
- }, [
1503
- rowStartIndex,
1504
- rowStopIndex,
1505
- columnStartIndex,
1506
- columnStopIndex,
1507
- visibleRowStartIndex,
1508
- visibleRowStopIndex,
1509
- visibleColumnStartIndex,
1510
- visibleColumnStopIndex,
1511
- ]);
1512
-
1513
- /* Draw gridlines */
1514
- const gridLines = [];
1515
- const gridLinesFrozenRow = [];
1516
- const gridLinesFrozenColumn = [];
1517
- const gridLinesFrozenIntersection = [];
1518
- if (showGridLines) {
1519
- // Horizontal
1520
- for (let rowIndex = rowStartIndex; rowIndex <= rowStopIndex; rowIndex++) {
1521
- /* Ignore frozen rows */
1522
- if (rowIndex < frozenRows || isHiddenRow?.(rowIndex)) continue;
1523
- const x1 = getColumnOffset(frozenColumns);
1524
- const x2 = getColumnOffset(Math.min(columnStopIndex + 1, columnCount));
1525
- const y1 = getRowOffset(Math.min(rowIndex + 1, rowCount));
1526
- const y2 = y1;
1527
- gridLines.push(
1528
- gridLineRenderer({
1529
- points: [x1, y1, x2, y2],
1530
- stroke: gridLineColor,
1531
- strokeWidth: gridLineWidth,
1532
- offsetY: -0.5,
1533
- key: itemKey({ rowIndex: rowIndex, columnIndex: y1 }),
1534
- })
1535
- );
1536
- gridLinesFrozenColumn.push(
1537
- gridLineRenderer({
1538
- points: [0, y1, x2, y2],
1539
- stroke: gridLineColor,
1540
- strokeWidth: gridLineWidth,
1541
- offsetY: -0.5,
1542
- key: itemKey({ rowIndex: rowIndex, columnIndex: y1 }),
1543
- })
1544
- );
1545
- }
1546
- // Vertical
1547
- for (
1548
- let columnIndex = columnStartIndex;
1549
- columnIndex <= columnStopIndex;
1550
- columnIndex++
1551
- ) {
1552
- const x1 = getColumnOffset(Math.min(columnIndex + 1, columnCount));
1553
- const x2 = x1;
1554
- const y1 = getRowOffset(frozenRows);
1555
- const y2 = getRowOffset(Math.min(rowStopIndex + 1, rowCount));
1556
- gridLines.push(
1557
- gridLineRenderer({
1558
- points: [x1, y1, x2, y2],
1559
- stroke: gridLineColor,
1560
- strokeWidth: gridLineWidth,
1561
- offsetX: -0.5,
1562
- key: itemKey({ rowIndex: x1, columnIndex: columnIndex }),
1563
- })
1564
- );
1565
- gridLinesFrozenRow.push(
1566
- gridLineRenderer({
1567
- points: [x1, 0, x2, y2],
1568
- stroke: gridLineColor,
1569
- strokeWidth: gridLineWidth,
1570
- offsetX: -0.5,
1571
- key: itemKey({ rowIndex: x1, columnIndex: columnIndex }),
1572
- })
1573
- );
1574
- }
1575
- for (
1576
- let rowIndex = 0;
1577
- rowIndex < Math.min(columnStopIndex, frozenRows);
1578
- rowIndex++
1579
- ) {
1580
- if (isHiddenRow?.(rowIndex)) continue;
1581
- const x1 = 0;
1582
- const x2 = getColumnOffset(Math.min(columnStopIndex + 1, columnCount));
1583
- const y1 = getRowOffset(Math.min(rowIndex + 1, rowCount));
1584
- const y2 = y1;
1585
- gridLinesFrozenRow.push(
1586
- gridLineRenderer({
1587
- points: [x1, y1, x2, y2],
1588
- stroke: gridLineColor,
1589
- strokeWidth: gridLineWidth,
1590
- offsetY: -0.5,
1591
- key: itemKey({ rowIndex: rowIndex, columnIndex: y1 }),
1592
- })
1593
- );
1594
- gridLinesFrozenIntersection.push(
1595
- gridLineRenderer({
1596
- points: [x1, y1, x2, y2],
1597
- stroke: gridLineColor,
1598
- strokeWidth: gridLineWidth,
1599
- offsetY: -0.5,
1600
- key: itemKey({ rowIndex: rowIndex, columnIndex: y1 }),
1601
- })
1602
- );
1603
- }
1604
-
1605
- for (
1606
- let columnIndex = 0;
1607
- columnIndex < Math.min(columnStopIndex, frozenColumns);
1608
- columnIndex++
1609
- ) {
1610
- const x1 = getColumnOffset(Math.min(columnIndex + 1, columnCount));
1611
- const x2 = x1;
1612
- const y1 = 0;
1613
- const y2 = getRowOffset(Math.min(rowStopIndex + 1, rowCount));
1614
- gridLinesFrozenColumn.push(
1615
- gridLineRenderer({
1616
- points: [x1, y1, x2, y2],
1617
- stroke: gridLineColor,
1618
- strokeWidth: gridLineWidth,
1619
- offsetX: -0.5,
1620
- key: itemKey({ rowIndex: x1, columnIndex: columnIndex }),
1621
- })
1622
- );
1623
- gridLinesFrozenIntersection.push(
1624
- gridLineRenderer({
1625
- points: [x1, y1, x2, y2],
1626
- stroke: gridLineColor,
1627
- strokeWidth: gridLineWidth,
1628
- offsetX: -0.5,
1629
- key: itemKey({ rowIndex: x1, columnIndex: columnIndex }),
1630
- })
1631
- );
1632
- }
1633
- }
1634
-
1635
- const mergedCellRenderMap = new Set();
1636
- /* Draw all cells */
1637
- const cells: React.ReactNode[] = [];
1638
- /**
1639
- * Lets users draw cells on top of existing canvas
1640
- */
1641
- const cellOverlays: React.ReactNode[] = [];
1642
-
1643
- if (columnCount > 0 && rowCount) {
1644
- for (let rowIndex = rowStartIndex; rowIndex <= rowStopIndex; rowIndex++) {
1645
- /* Skip frozen rows */
1646
- if (rowIndex < frozenRows || isHiddenRow?.(rowIndex)) {
1647
- continue;
1648
- }
1649
- /**
1650
- * Do any pre-processing of the row before being renderered.
1651
- * Useful for `react-table` to call `prepareRow(row)`
1652
- */
1653
- onBeforeRenderRow?.(rowIndex);
1654
-
1655
- for (
1656
- let columnIndex = columnStartIndex;
1657
- columnIndex <= columnStopIndex;
1658
- columnIndex++
1659
- ) {
1660
- /**
1661
- * Skip frozen columns
1662
- * Skip merged cells that are out of bounds
1663
- */
1664
- if (columnIndex < frozenColumns) {
1665
- continue;
1666
- }
1667
-
1668
- const isMerged = isMergedCell({ rowIndex, columnIndex });
1669
- const bounds = getCellBounds({ rowIndex, columnIndex });
1670
- const actualRowIndex = isMerged ? bounds.top : rowIndex;
1671
- const actualColumnIndex = isMerged ? bounds.left : columnIndex;
1672
- const actualBottom = Math.max(rowIndex, bounds.bottom);
1673
- const actualRight = Math.max(columnIndex, bounds.right);
1674
- if (!isMerged && isHiddenCell?.(actualRowIndex, actualColumnIndex)) {
1675
- continue;
1676
- }
1677
- if (isMerged) {
1678
- const cellId = cellIdentifier(bounds.top, bounds.left);
1679
- if (mergedCellRenderMap.has(cellId)) {
1680
- continue;
1681
- }
1682
- mergedCellRenderMap.add(cellId);
1683
- }
1684
-
1685
- const y = getRowOffset(actualRowIndex);
1686
- const height =
1687
- getRowOffset(actualBottom) - y + getRowHeight(actualBottom);
1688
-
1689
- const x = getColumnOffset(actualColumnIndex);
1690
-
1691
- const width =
1692
- getColumnOffset(actualRight) - x + getColumnWidth(actualRight);
1693
-
1694
- cells.push(
1695
- itemRenderer({
1696
- x,
1697
- y,
1698
- width,
1699
- height,
1700
- rowIndex: actualRowIndex,
1701
- columnIndex: actualColumnIndex,
1702
- isMergedCell: isMerged,
1703
- key: itemKey({
1704
- rowIndex: actualRowIndex,
1705
- columnIndex: actualColumnIndex,
1706
- }),
1707
- })
1708
- );
1709
-
1710
- if (enableCellOverlay) {
1711
- cellOverlays.push(
1712
- overlayRenderer({
1713
- x,
1714
- y,
1715
- width,
1716
- height,
1717
- rowIndex: actualRowIndex,
1718
- columnIndex: actualColumnIndex,
1719
- isMergedCell: isMerged,
1720
- key: itemKey({
1721
- rowIndex: actualRowIndex,
1722
- columnIndex: actualColumnIndex,
1723
- }),
1724
- })
1725
- );
1726
- }
1727
- }
1728
- }
1729
- }
1730
-
1731
- /**
1732
- * Extend certain cells.
1733
- * Mimics google sheets functionality where
1734
- * oevrflowed cell content can cover adjacent cells
1735
- */
1736
- const ranges = [];
1737
- for (const { rowIndex, columnIndex, toColumnIndex } of cellAreas) {
1738
- /* Skip merged cells, Merged cell cannot be extended */
1739
- if (
1740
- rowIndex < frozenRows ||
1741
- columnIndex < frozenColumns ||
1742
- isMergedCell({ rowIndex, columnIndex }) ||
1743
- isHiddenCell?.(rowIndex, columnIndex)
1744
- ) {
1745
- continue;
1746
- }
1747
- const x = getColumnOffset(columnIndex);
1748
- const y = getRowOffset(rowIndex);
1749
- const height = getRowHeight(rowIndex);
1750
- const { x: offsetX = 0 } = getCellOffsetFromCoords({
1751
- rowIndex,
1752
- columnIndex: toColumnIndex + 1,
1753
- });
1754
- ranges.push(
1755
- itemRenderer({
1756
- x,
1757
- y,
1758
- width: offsetX - x,
1759
- height,
1760
- rowIndex,
1761
- columnIndex,
1762
- key: `range:${itemKey({ rowIndex, columnIndex })}`,
1763
- })
1764
- );
1765
- }
1766
-
1767
- /* Draw frozen rows */
1768
- const frozenRowCells = [];
1769
- const frozenRowCellOverlays: React.ReactNode[] = [];
1770
- for (
1771
- let rowIndex = 0;
1772
- rowIndex < Math.min(rowStopIndex, frozenRows);
1773
- rowIndex++
1774
- ) {
1775
- if (isHiddenRow?.(rowIndex)) continue;
1776
- /**
1777
- * Do any pre-processing of the row before being renderered.
1778
- * Useful for `react-table` to call `prepareRow(row)`
1779
- */
1780
- onBeforeRenderRow?.(rowIndex);
1781
-
1782
- for (
1783
- let columnIndex = columnStartIndex;
1784
- columnIndex <= columnStopIndex;
1785
- columnIndex++
1786
- ) {
1787
- /* Skip merged cells columns */
1788
- if (columnIndex < frozenColumns) {
1789
- continue;
1790
- }
1791
-
1792
- const isMerged = isMergedCell({ rowIndex, columnIndex });
1793
- const bounds = getCellBounds({ rowIndex, columnIndex });
1794
- const actualRowIndex = isMerged ? bounds.top : rowIndex;
1795
- const actualColumnIndex = isMerged ? bounds.left : columnIndex;
1796
- const actualBottom = Math.max(rowIndex, bounds.bottom);
1797
- const actualRight = Math.max(columnIndex, bounds.right);
1798
- if (!isMerged && isHiddenCell?.(actualRowIndex, actualColumnIndex)) {
1799
- continue;
1800
- }
1801
- if (isMerged) {
1802
- const cellId = cellIdentifier(bounds.top, bounds.left);
1803
- if (mergedCellRenderMap.has(cellId)) {
1804
- continue;
1805
- }
1806
- mergedCellRenderMap.add(cellId);
1807
- }
1808
-
1809
- const y = getRowOffset(actualRowIndex);
1810
- const height =
1811
- getRowOffset(actualBottom) - y + getRowHeight(actualBottom);
1812
-
1813
- const x = getColumnOffset(actualColumnIndex);
1814
-
1815
- const width =
1816
- getColumnOffset(actualRight) - x + getColumnWidth(actualRight);
1817
-
1818
- frozenRowCells.push(
1819
- itemRenderer({
1820
- x,
1821
- y,
1822
- width,
1823
- height,
1824
- rowIndex: actualRowIndex,
1825
- columnIndex: actualColumnIndex,
1826
- isMergedCell: isMerged,
1827
- key: itemKey({
1828
- rowIndex: actualRowIndex,
1829
- columnIndex: actualColumnIndex,
1830
- }),
1831
- })
1832
- );
1833
-
1834
- if (enableCellOverlay) {
1835
- frozenRowCellOverlays.push(
1836
- overlayRenderer({
1837
- x,
1838
- y,
1839
- width,
1840
- height,
1841
- rowIndex: actualRowIndex,
1842
- columnIndex: actualColumnIndex,
1843
- isMergedCell: isMerged,
1844
- key: itemKey({
1845
- rowIndex: actualRowIndex,
1846
- columnIndex: actualColumnIndex,
1847
- }),
1848
- })
1849
- );
1850
- }
1851
- }
1852
- }
1853
-
1854
- /* Draw frozen columns */
1855
- const frozenColumnCells = [];
1856
- const frozenColumnCellOverlays = [];
1857
- for (let rowIndex = rowStartIndex; rowIndex <= rowStopIndex; rowIndex++) {
1858
- if (rowIndex < frozenRows || isHiddenRow?.(rowIndex)) {
1859
- continue;
1860
- }
1861
- /**
1862
- * Do any pre-processing of the row before being renderered.
1863
- * Useful for `react-table` to call `prepareRow(row)`
1864
- */
1865
- onBeforeRenderRow?.(rowIndex);
1866
-
1867
- for (
1868
- let columnIndex = 0;
1869
- columnIndex < Math.min(columnStopIndex, frozenColumns);
1870
- columnIndex++
1871
- ) {
1872
- const isMerged = isMergedCell({ rowIndex, columnIndex });
1873
- const bounds = getCellBounds({ rowIndex, columnIndex });
1874
- const actualRowIndex = isMerged ? bounds.top : rowIndex;
1875
- const actualColumnIndex = isMerged ? bounds.left : columnIndex;
1876
- const actualBottom = Math.max(rowIndex, bounds.bottom);
1877
- const actualRight = Math.max(columnIndex, bounds.right);
1878
- if (!isMerged && isHiddenCell?.(actualRowIndex, actualColumnIndex)) {
1879
- continue;
1880
- }
1881
- if (isMerged) {
1882
- const cellId = cellIdentifier(bounds.top, bounds.left);
1883
- if (mergedCellRenderMap.has(cellId)) {
1884
- continue;
1885
- }
1886
- mergedCellRenderMap.add(cellId);
1887
- }
1888
-
1889
- const y = getRowOffset(actualRowIndex);
1890
- const height =
1891
- getRowOffset(actualBottom) - y + getRowHeight(actualBottom);
1892
-
1893
- const x = getColumnOffset(actualColumnIndex);
1894
-
1895
- const width =
1896
- getColumnOffset(actualRight) - x + getColumnWidth(actualRight);
1897
-
1898
- frozenColumnCells.push(
1899
- itemRenderer({
1900
- x,
1901
- y,
1902
- width,
1903
- height,
1904
- rowIndex: actualRowIndex,
1905
- columnIndex: actualColumnIndex,
1906
- isMergedCell: isMerged,
1907
- key: itemKey({
1908
- rowIndex: actualRowIndex,
1909
- columnIndex: actualColumnIndex,
1910
- }),
1911
- })
1912
- );
1913
-
1914
- if (enableCellOverlay) {
1915
- frozenColumnCellOverlays.push(
1916
- overlayRenderer({
1917
- x,
1918
- y,
1919
- width,
1920
- height,
1921
- rowIndex: actualRowIndex,
1922
- columnIndex: actualColumnIndex,
1923
- isMergedCell: isMerged,
1924
- key: itemKey({
1925
- rowIndex: actualRowIndex,
1926
- columnIndex: actualColumnIndex,
1927
- }),
1928
- })
1929
- );
1930
- }
1931
- }
1932
- }
1933
-
1934
- /**
1935
- * Frozen column shadow
1936
- */
1937
- const frozenColumnShadow = useMemo(() => {
1938
- const frozenColumnLineX = getColumnOffset(frozenColumns);
1939
- const frozenColumnLineY = getRowOffset(
1940
- Math.min(rowStopIndex + 1, rowCount)
1941
- );
1942
- return (
1943
- <Line
1944
- points={[frozenColumnLineX, 0, frozenColumnLineX, frozenColumnLineY]}
1945
- offsetX={-0.5}
1946
- strokeWidth={1}
1947
- shadowForStrokeEnabled={false}
1948
- strokeScaleEnabled={false}
1949
- hitStrokeWidth={0}
1950
- listening={false}
1951
- perfectDrawEnabled={false}
1952
- {...shadowSettings}
1953
- />
1954
- );
1955
- }, [
1956
- shadowSettings,
1957
- frozenColumns,
1958
- rowStopIndex,
1959
- rowCount,
1960
- instanceProps.current.lastMeasuredColumnIndex,
1961
- instanceProps.current.recalcColumnIndices,
1962
- ]);
1963
-
1964
- /**
1965
- * Frozen row shadow
1966
- */
1967
- const frozenRowShadow = useMemo(() => {
1968
- const frozenRowLineY = getRowOffset(frozenRows);
1969
- const frozenRowLineX = getColumnOffset(
1970
- Math.min(columnStopIndex + 1, columnCount)
1971
- );
1972
- return (
1973
- <Line
1974
- points={[0, frozenRowLineY, frozenRowLineX, frozenRowLineY]}
1975
- offsetY={-0.5}
1976
- strokeWidth={1}
1977
- shadowForStrokeEnabled={false}
1978
- strokeScaleEnabled={false}
1979
- hitStrokeWidth={0}
1980
- listening={false}
1981
- perfectDrawEnabled={false}
1982
- {...shadowSettings}
1983
- />
1984
- );
1985
- }, [
1986
- shadowSettings,
1987
- frozenRows,
1988
- columnStopIndex,
1989
- columnCount,
1990
- instanceProps.current.lastMeasuredRowIndex,
1991
- instanceProps.current.recalcRowIndices,
1992
- ]);
1993
-
1994
- /* Draw frozen intersection cells */
1995
- const frozenIntersectionCells = [];
1996
- const frozenIntersectionCellOverlays = [];
1997
- for (
1998
- let rowIndex = 0;
1999
- rowIndex < Math.min(rowStopIndex, frozenRows);
2000
- rowIndex++
2001
- ) {
2002
- if (isHiddenRow?.(rowIndex)) continue;
2003
- for (
2004
- let columnIndex = 0;
2005
- columnIndex < Math.min(columnStopIndex, frozenColumns);
2006
- columnIndex++
2007
- ) {
2008
- const isMerged = isMergedCell({ rowIndex, columnIndex });
2009
- const bounds = getCellBounds({ rowIndex, columnIndex });
2010
- const actualRowIndex = isMerged ? bounds.top : rowIndex;
2011
- const actualColumnIndex = isMerged ? bounds.left : columnIndex;
2012
- const actualBottom = Math.max(rowIndex, bounds.bottom);
2013
- const actualRight = Math.max(columnIndex, bounds.right);
2014
- if (!isMerged && isHiddenCell?.(actualRowIndex, actualColumnIndex)) {
2015
- continue;
2016
- }
2017
- if (isMerged) {
2018
- const cellId = cellIdentifier(bounds.top, bounds.left);
2019
- if (mergedCellRenderMap.has(cellId)) {
2020
- continue;
2021
- }
2022
- mergedCellRenderMap.add(cellId);
2023
- }
2024
-
2025
- const y = getRowOffset(actualRowIndex);
2026
- const height =
2027
- getRowOffset(actualBottom) - y + getRowHeight(actualBottom);
2028
-
2029
- const x = getColumnOffset(actualColumnIndex);
2030
-
2031
- const width =
2032
- getColumnOffset(actualRight) - x + getColumnWidth(actualRight);
2033
-
2034
- frozenIntersectionCells.push(
2035
- itemRenderer({
2036
- x,
2037
- y,
2038
- width,
2039
- height,
2040
- rowIndex: actualRowIndex,
2041
- columnIndex: actualColumnIndex,
2042
- isMergedCell: isMerged,
2043
- key: itemKey({
2044
- rowIndex: actualRowIndex,
2045
- columnIndex: actualColumnIndex,
2046
- }),
2047
- })
2048
- );
2049
-
2050
- if (enableCellOverlay) {
2051
- frozenIntersectionCellOverlays.push(
2052
- overlayRenderer({
2053
- x,
2054
- y,
2055
- width,
2056
- height,
2057
- rowIndex: actualRowIndex,
2058
- columnIndex: actualColumnIndex,
2059
- isMergedCell: isMerged,
2060
- key: itemKey({
2061
- rowIndex: actualRowIndex,
2062
- columnIndex: actualColumnIndex,
2063
- }),
2064
- })
2065
- );
2066
- }
2067
- }
2068
- }
2069
-
2070
- /**
2071
- * Renders active cell
2072
- */
2073
- let fillHandleDimension = {};
2074
- let activeCellSelection = null;
2075
- let activeCellSelectionFrozenColumn = null;
2076
- let activeCellSelectionFrozenRow = null;
2077
- let activeCellSelectionFrozenIntersection = null;
2078
- if (activeCell) {
2079
- const bounds = getCellBounds(activeCell);
2080
- const { top, left, right, bottom } = bounds;
2081
- const actualBottom = Math.min(rowStopIndex, bottom);
2082
- const actualRight = Math.min(columnStopIndex, right);
2083
- const isInFrozenColumn = left < frozenColumns;
2084
- const isInFrozenRow = top < frozenRows;
2085
- const isInFrozenIntersection = isInFrozenRow && isInFrozenColumn;
2086
- const y = getRowOffset(top);
2087
- const height =
2088
- getRowOffset(actualBottom) - y + getRowHeight(actualBottom);
2089
-
2090
- const x = getColumnOffset(left);
2091
-
2092
- const width =
2093
- getColumnOffset(actualRight) - x + getColumnWidth(actualRight);
2094
-
2095
- const cell = selectionRenderer({
2096
- stroke: selectionBorderColor,
2097
- strokeWidth: activeCellStrokeWidth,
2098
- fill: "transparent",
2099
- x: x,
2100
- y: y,
2101
- width: width,
2102
- height: height,
2103
- type: "activeCell",
2104
- key: 0,
2105
- activeCell,
2106
- isDragging: isDraggingSelection,
2107
- /* Active cell is draggable only there are no other selections */
2108
- draggable: enableSelectionDrag && !selections.length,
2109
- });
2110
-
2111
- if (isInFrozenIntersection) {
2112
- activeCellSelectionFrozenIntersection = cell;
2113
- } else if (isInFrozenRow) {
2114
- activeCellSelectionFrozenRow = cell;
2115
- } else if (isInFrozenColumn) {
2116
- activeCellSelectionFrozenColumn = cell;
2117
- } else {
2118
- activeCellSelection = cell;
2119
- }
2120
-
2121
- fillHandleDimension = {
2122
- x: x + width,
2123
- y: y + height,
2124
- };
2125
- }
2126
-
2127
- /**
2128
- * Convert selections to area
2129
- * Removed useMemo as changes to lastMeasureRowIndex, lastMeasuredColumnIndex,
2130
- * does not trigger useMemo
2131
- * Dependencies : [selections, rowStopIndex, columnStopIndex, instanceProps]
2132
- */
2133
-
2134
- let isSelectionInProgress = false;
2135
- const selectionAreas = [];
2136
- const selectionAreasFrozenColumns = [];
2137
- const selectionAreasFrozenRows = [];
2138
- const selectionAreasIntersection = [];
2139
- for (let i = 0; i < selections.length; i++) {
2140
- const selection = selections[i];
2141
- const { bounds, inProgress, style } = selection;
2142
- const { top, left, right, bottom } = bounds;
2143
- const selectionBounds = { x: 0, y: 0, width: 0, height: 0 };
2144
- const actualBottom = Math.min(rowStopIndex, bottom);
2145
- const actualRight = Math.min(columnStopIndex, right);
2146
- const isLeftBoundFrozen = left < frozenColumns;
2147
- const isTopBoundFrozen = top < frozenRows;
2148
- const isIntersectionFrozen = top < frozenRows && left < frozenColumns;
2149
- const isLast = i === selections.length - 1;
2150
- const styles = {
2151
- stroke: inProgress ? selectionBackgroundColor : selectionBorderColor,
2152
- fill: selectionBackgroundColor,
2153
- strokeWidth: isDraggingSelection ? 0 : 1,
2154
- isDragging: isDraggingSelection,
2155
- draggable: inProgress ? false : enableSelectionDrag,
2156
- ...style,
2157
- };
2158
- /**
2159
- * If selection is in progress,
2160
- * use this variable to hide fill handle
2161
- */
2162
- if (inProgress) {
2163
- isSelectionInProgress = true;
2164
- }
2165
- selectionBounds.y = getRowOffset(top);
2166
- selectionBounds.height =
2167
- getRowOffset(actualBottom) -
2168
- selectionBounds.y +
2169
- getRowHeight(actualBottom);
2170
-
2171
- selectionBounds.x = getColumnOffset(left);
2172
-
2173
- selectionBounds.width =
2174
- getColumnOffset(actualRight) -
2175
- selectionBounds.x +
2176
- getColumnWidth(actualRight);
2177
-
2178
- if (isLeftBoundFrozen) {
2179
- const frozenColumnSelectionWidth =
2180
- getColumnOffset(Math.min(right + 1, frozenColumns)) -
2181
- getColumnOffset(left);
2182
- selectionAreasFrozenColumns.push(
2183
- selectionRenderer({
2184
- ...styles,
2185
- type: "selection",
2186
- key: i,
2187
- x: selectionBounds.x,
2188
- y: selectionBounds.y,
2189
- width: frozenColumnSelectionWidth,
2190
- height: selectionBounds.height,
2191
- strokeRightWidth:
2192
- frozenColumnSelectionWidth === selectionBounds.width &&
2193
- !isDraggingSelection
2194
- ? selectionStrokeWidth
2195
- : 0,
2196
- selection,
2197
- inProgress,
2198
- })
2199
- );
2200
- }
2201
-
2202
- if (isTopBoundFrozen) {
2203
- const frozenRowSelectionHeight =
2204
- getRowOffset(Math.min(bottom + 1, frozenRows)) - getRowOffset(top);
2205
- selectionAreasFrozenRows.push(
2206
- selectionRenderer({
2207
- ...styles,
2208
- type: "selection",
2209
- key: i,
2210
- x: selectionBounds.x,
2211
- y: selectionBounds.y,
2212
- width: selectionBounds.width,
2213
- height: frozenRowSelectionHeight,
2214
- strokeBottomWidth:
2215
- frozenRowSelectionHeight === selectionBounds.height &&
2216
- !isDraggingSelection
2217
- ? selectionStrokeWidth
2218
- : 0,
2219
- selection,
2220
- inProgress,
2221
- })
2222
- );
2223
- }
2224
-
2225
- if (isIntersectionFrozen) {
2226
- const frozenIntersectionSelectionHeight =
2227
- getRowOffset(Math.min(bottom + 1, frozenRows)) - getRowOffset(top);
2228
-
2229
- const frozenIntersectionSelectionWidth =
2230
- getColumnOffset(Math.min(right + 1, frozenColumns)) -
2231
- getColumnOffset(left);
2232
-
2233
- selectionAreasIntersection.push(
2234
- selectionRenderer({
2235
- ...styles,
2236
- type: "selection",
2237
- key: i,
2238
- x: selectionBounds.x,
2239
- y: selectionBounds.y,
2240
- width: frozenIntersectionSelectionWidth,
2241
- height: frozenIntersectionSelectionHeight,
2242
- strokeBottomWidth:
2243
- frozenIntersectionSelectionHeight === selectionBounds.height &&
2244
- !isDraggingSelection
2245
- ? selectionStrokeWidth
2246
- : 0,
2247
- strokeRightWidth:
2248
- frozenIntersectionSelectionWidth === selectionBounds.width &&
2249
- !isDraggingSelection
2250
- ? selectionStrokeWidth
2251
- : 0,
2252
- selection,
2253
- inProgress,
2254
- })
2255
- );
2256
- }
2257
- selectionAreas.push(
2258
- selectionRenderer({
2259
- ...styles,
2260
- type: "selection",
2261
- key: i,
2262
- x: selectionBounds.x,
2263
- y: selectionBounds.y,
2264
- width: selectionBounds.width,
2265
- height: selectionBounds.height,
2266
- selection,
2267
- inProgress,
2268
- })
2269
- );
2270
-
2271
- if (isLast) {
2272
- fillHandleDimension = {
2273
- x: selectionBounds.x + selectionBounds.width,
2274
- y: selectionBounds.y + selectionBounds.height,
2275
- };
2276
- }
2277
- }
2278
-
2279
- /**
2280
- * Fillselection
2281
- */
2282
- let fillSelections = null;
2283
- if (fillSelection) {
2284
- const { bounds } = fillSelection;
2285
- const { top, left, right, bottom } = bounds;
2286
- const actualBottom = Math.min(rowStopIndex, bottom);
2287
- const actualRight = Math.min(columnStopIndex, right);
2288
- const x = getColumnOffset(left);
2289
- const y = getRowOffset(top);
2290
- const height =
2291
- getRowOffset(actualBottom) - y + getRowHeight(actualBottom);
2292
- const width =
2293
- getColumnOffset(actualRight) - x + getColumnWidth(actualRight);
2294
-
2295
- fillSelections = selectionRenderer({
2296
- type: "fill",
2297
- x,
2298
- y,
2299
- width,
2300
- height,
2301
- key: -1,
2302
- stroke: "gray",
2303
- strokeStyle: "dashed",
2304
- });
2305
- }
2306
-
2307
- const borderStyleCells = [];
2308
- const borderStyleCellsFrozenColumns = [];
2309
- const borderStyleCellsFrozenRows = [];
2310
- const borderStyleCellsIntersection = [];
2311
- for (let i = 0; i < borderStyles.length; i++) {
2312
- const { bounds, style, title, ...rest } = borderStyles[i];
2313
- const { top, right, bottom, left } = bounds;
2314
- const isLeftBoundFrozen = left < frozenColumns;
2315
- const isTopBoundFrozen = top < frozenRows;
2316
- const isIntersectionFrozen = top < frozenRows && left < frozenColumns;
2317
- const x = getColumnOffset(left);
2318
- const y = getRowOffset(top);
2319
- const width = getColumnOffset(Math.min(columnCount, right + 1)) - x;
2320
- const height = getRowOffset(Math.min(rowCount, bottom + 1)) - y;
2321
-
2322
- borderStyleCells.push(
2323
- createHTMLBox({
2324
- ...rest,
2325
- ...style,
2326
- x,
2327
- y,
2328
- key: i,
2329
- width,
2330
- height,
2331
- type: "border",
2332
- })
2333
- );
2334
-
2335
- if (isLeftBoundFrozen) {
2336
- const frozenColumnSelectionWidth =
2337
- getColumnOffset(Math.min(right + 1, frozenColumns)) -
2338
- getColumnOffset(left);
2339
- borderStyleCellsFrozenColumns.push(
2340
- createHTMLBox({
2341
- ...rest,
2342
- ...style,
2343
- type: "border",
2344
- x,
2345
- y,
2346
- key: i,
2347
- width: frozenColumnSelectionWidth,
2348
- height,
2349
- strokeRightWidth:
2350
- frozenColumnSelectionWidth === width
2351
- ? style?.strokeRightWidth || style?.strokeWidth
2352
- : 0,
2353
- })
2354
- );
2355
- }
2356
-
2357
- if (isTopBoundFrozen) {
2358
- const frozenRowSelectionHeight =
2359
- getRowOffset(Math.min(bottom + 1, frozenRows)) - getRowOffset(top);
2360
-
2361
- borderStyleCellsFrozenRows.push(
2362
- createHTMLBox({
2363
- ...rest,
2364
- ...style,
2365
- type: "border",
2366
- x,
2367
- y,
2368
- key: i,
2369
- width,
2370
- height: frozenRowSelectionHeight,
2371
- strokeBottomWidth:
2372
- frozenRowSelectionHeight === height
2373
- ? style?.strokeBottomWidth || style?.strokeWidth
2374
- : 0,
2375
- })
2376
- );
2377
- }
2378
-
2379
- if (isIntersectionFrozen) {
2380
- const frozenIntersectionSelectionHeight =
2381
- getRowOffset(Math.min(bottom + 1, frozenRows)) - getRowOffset(top);
2382
-
2383
- const frozenIntersectionSelectionWidth =
2384
- getColumnOffset(Math.min(right + 1, frozenColumns)) -
2385
- getColumnOffset(left);
2386
-
2387
- borderStyleCellsIntersection.push(
2388
- createHTMLBox({
2389
- ...rest,
2390
- ...style,
2391
- type: "border",
2392
- x,
2393
- y,
2394
- key: i,
2395
- width: frozenIntersectionSelectionWidth,
2396
- height: frozenIntersectionSelectionHeight,
2397
- strokeBottomWidth:
2398
- frozenIntersectionSelectionHeight === height
2399
- ? selectionStrokeWidth
2400
- : 0,
2401
- strokeRightWidth:
2402
- frozenIntersectionSelectionWidth === width
2403
- ? selectionStrokeWidth
2404
- : 0,
2405
- })
2406
- );
2407
- }
2408
- }
2409
-
2410
- /* Spacing for frozen row/column clip */
2411
- const frozenSpacing = 1;
2412
- /**
2413
- * Prevents drawing hit region when scrolling
2414
- */
2415
- const listenToEvents = !isScrolling;
2416
- /* Frozen row shadow */
2417
- const frozenRowShadowComponent =
2418
- showFrozenShadow && frozenRows !== 0 ? frozenRowShadow : null;
2419
- /* Frozen column shadow */
2420
- const frozenColumnShadowComponent =
2421
- showFrozenShadow && frozenColumns !== 0 ? frozenColumnShadow : null;
2422
- const stageChildren = (
2423
- <>
2424
- <Layer>
2425
- <Group
2426
- clipX={frozenColumnWidth}
2427
- clipY={frozenRowHeight}
2428
- clipWidth={containerWidth - frozenColumnWidth}
2429
- clipHeight={containerHeight - frozenRowHeight}
2430
- >
2431
- <Group offsetY={scrollTop} offsetX={scrollLeft}>
2432
- {gridLines}
2433
- {cells}
2434
- {cellOverlays}
2435
- {ranges}
2436
- </Group>
2437
- </Group>
2438
-
2439
- <Group
2440
- clipX={frozenColumnWidth}
2441
- clipY={0}
2442
- clipWidth={containerWidth - frozenColumnWidth}
2443
- clipHeight={frozenRowHeight + frozenSpacing}
2444
- >
2445
- <Group offsetY={0} offsetX={scrollLeft}>
2446
- {gridLinesFrozenRow}
2447
- {frozenRowCells}
2448
- {frozenRowShadowComponent}
2449
- {frozenRowCellOverlays}
2450
- </Group>
2451
- </Group>
2452
- <Group
2453
- clipX={0}
2454
- clipY={frozenRowHeight}
2455
- clipWidth={frozenColumnWidth + frozenSpacing}
2456
- clipHeight={containerHeight - frozenRowHeight}
2457
- >
2458
- <Group offsetY={scrollTop} offsetX={0}>
2459
- {gridLinesFrozenColumn}
2460
- {frozenColumnCells}
2461
- {frozenColumnShadowComponent}
2462
- {frozenColumnCellOverlays}
2463
- </Group>
2464
- </Group>
2465
- <Group
2466
- offsetY={0}
2467
- offsetX={0}
2468
- clipX={0}
2469
- clipY={0}
2470
- clipWidth={frozenColumnWidth + frozenSpacing}
2471
- clipHeight={frozenRowHeight + frozenSpacing}
2472
- >
2473
- {gridLinesFrozenIntersection}
2474
- {frozenIntersectionCells}
2475
- {frozenRowShadowComponent}
2476
- {frozenColumnShadowComponent}
2477
- {frozenIntersectionCellOverlays}
2478
- </Group>
2479
- </Layer>
2480
- {children && typeof children === "function"
2481
- ? children({
2482
- scrollLeft,
2483
- scrollTop,
2484
- })
2485
- : null}
2486
- </>
2487
- );
2488
- const fillHandleWidth = 8;
2489
- const fillhandleComponent =
2490
- showFillHandle && !isSelectionInProgress ? (
2491
- <FillHandle
2492
- {...fillHandleDimension}
2493
- stroke={selectionBorderColor}
2494
- size={fillHandleWidth}
2495
- borderColor={fillhandleBorderColor}
2496
- {...fillHandleProps}
2497
- />
2498
- ) : null;
2499
- const selectionChildren = (
2500
- <div
2501
- style={{
2502
- pointerEvents: "none",
2503
- }}
2504
- >
2505
- <div
2506
- style={{
2507
- position: "absolute",
2508
- left: frozenColumnWidth,
2509
- top: frozenRowHeight,
2510
- right: 0,
2511
- bottom: 0,
2512
- overflow: "hidden",
2513
- }}
2514
- >
2515
- <div
2516
- style={{
2517
- transform: `translate(-${scrollLeft + frozenColumnWidth}px, -${
2518
- scrollTop + frozenRowHeight
2519
- }px)`,
2520
- }}
2521
- >
2522
- {borderStyleCells}
2523
- {fillSelections}
2524
- {selectionAreas}
2525
- {activeCellSelection}
2526
- {fillhandleComponent}
2527
- </div>
2528
- </div>
2529
- {frozenColumns ? (
2530
- <div
2531
- style={{
2532
- position: "absolute",
2533
- width: frozenColumnWidth + fillHandleWidth,
2534
- top: frozenRowHeight,
2535
- left: 0,
2536
- bottom: 0,
2537
- overflow: "hidden",
2538
- }}
2539
- >
2540
- <div
2541
- style={{
2542
- transform: `translate(0, -${scrollTop + frozenRowHeight}px)`,
2543
- }}
2544
- >
2545
- {borderStyleCellsFrozenColumns}
2546
- {selectionAreasFrozenColumns}
2547
- {activeCellSelectionFrozenColumn}
2548
- {fillhandleComponent}
2549
- </div>
2550
- </div>
2551
- ) : null}
2552
- {frozenRows ? (
2553
- <div
2554
- style={{
2555
- position: "absolute",
2556
- height: frozenRowHeight + fillHandleWidth,
2557
- left: frozenColumnWidth,
2558
- right: 0,
2559
- top: 0,
2560
- overflow: "hidden",
2561
- }}
2562
- >
2563
- <div
2564
- style={{
2565
- transform: `translate(-${scrollLeft + frozenColumnWidth}px, 0)`,
2566
- }}
2567
- >
2568
- {borderStyleCellsFrozenRows}
2569
- {selectionAreasFrozenRows}
2570
- {activeCellSelectionFrozenRow}
2571
- {fillhandleComponent}
2572
- </div>
2573
- </div>
2574
- ) : null}
2575
- {frozenRows && frozenColumns ? (
2576
- <div
2577
- style={{
2578
- position: "absolute",
2579
- height: frozenRowHeight + fillHandleWidth,
2580
- width: frozenColumnWidth + fillHandleWidth,
2581
- left: 0,
2582
- top: 0,
2583
- overflow: "hidden",
2584
- pointerEvents: "none",
2585
- }}
2586
- >
2587
- {borderStyleCellsIntersection}
2588
- {selectionAreasIntersection}
2589
- {activeCellSelectionFrozenIntersection}
2590
- {fillhandleComponent}
2591
- </div>
2592
- ) : null}
2593
- </div>
2594
- );
2595
- return (
2596
- <div
2597
- style={{
2598
- position: "relative",
2599
- width: containerWidth,
2600
- userSelect: "none",
2601
- }}
2602
- className="rowsncolumns-grid"
2603
- ref={scrollContainerRef}
2604
- >
2605
- <div
2606
- className="rowsncolumns-grid-container"
2607
- tabIndex={0}
2608
- ref={containerRef}
2609
- {...rest}
2610
- >
2611
- <Stage
2612
- width={containerWidth}
2613
- height={containerHeight}
2614
- ref={stageRef}
2615
- listening={listenToEvents}
2616
- {...stageProps}
2617
- >
2618
- {wrapper(stageChildren)}
2619
- </Stage>
2620
- </div>
2621
- {selectionChildren}
2622
- {showScrollbar ? (
2623
- <>
2624
- <div
2625
- className="rowsncolumns-grid-scrollbar rowsncolumns-grid-scrollbar-y"
2626
- tabIndex={-1}
2627
- style={{
2628
- height: containerHeight,
2629
- overflow: "scroll",
2630
- position: "absolute",
2631
- right: 0,
2632
- top: 0,
2633
- width: scrollbarSize,
2634
- willChange: "transform",
2635
- }}
2636
- onScroll={handleScroll}
2637
- ref={verticalScrollRef}
2638
- >
2639
- <div
2640
- style={{
2641
- position: "absolute",
2642
- height: estimatedTotalHeight,
2643
- width: 1,
2644
- }}
2645
- />
2646
- </div>
2647
- <div
2648
- className="rowsncolumns-grid-scrollbar rowsncolumns-grid-scrollbar-x"
2649
- tabIndex={-1}
2650
- style={{
2651
- overflow: "scroll",
2652
- position: "absolute",
2653
- bottom: 0,
2654
- left: 0,
2655
- width: containerWidth,
2656
- height: scrollbarSize,
2657
- willChange: "transform",
2658
- }}
2659
- onScroll={handleScrollLeft}
2660
- ref={horizontalScrollRef}
2661
- >
2662
- <div
2663
- style={{
2664
- position: "absolute",
2665
- width: estimatedTotalWidth,
2666
- height: 1,
2667
- }}
2668
- />
2669
- </div>
2670
- </>
2671
- ) : null}
2672
- </div>
2673
- );
2674
- })
2675
- );
2676
-
2677
- export default Grid;