@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
@@ -0,0 +1,2451 @@
1
+ import React, { forwardRef, memo, useCallback, useEffect, useImperativeHandle, useMemo, useReducer, useRef, useState, } from "react";
2
+ import { Line } from "react-konva";
3
+ import invariant from "tiny-invariant";
4
+ import { CellRenderer as defaultItemRenderer } from "./Cell";
5
+ import { CellRenderer as defaultOverlayRenderer } from "./CellOverlay";
6
+ import { GridContext } from "./Context";
7
+ import DefaultFillHandle from "./FillHandle";
8
+ import GridLine from "./GridLine";
9
+ import { ricThrottle } from "./helpers";
10
+ import { getScrollIncrement, useCallbackRef } from "./helpers";
11
+ import { Align, cancelTimeout, canUseDOM, cellIdentifier, clampIndex, getBoundedCells, getColumnOffset as getColumnOffsetHelper, getColumnStartIndexForOffset, getColumnStopIndexForStartIndex, getColumnWidth as getColumnWidthHelper, getEstimatedTotalHeight, getEstimatedTotalWidth, getOffsetForColumnAndAlignment, getOffsetForRowAndAlignment, getRowHeight as getRowHeightHelper, getRowOffset as getRowOffsetHelper, getRowStartIndexForOffset, getRowStopIndexForStartIndex, itemKey, requestTimeout, useIsomorphicLayoutEffect, } from "./helpers";
12
+ import { MemoizedGrid } from "./MemoizedGrid";
13
+ import Selection from "./Selection";
14
+ import { Direction } from "./types";
15
+ const DEFAULT_ESTIMATED_ITEM_SIZE = 50;
16
+ const defaultShadowSettings = {
17
+ strokeWidth: 1,
18
+ };
19
+ const DEFAULT_ROW_HEIGHT = 21;
20
+ const DEFAULT_COLUMN_WIDTH = 100;
21
+ const defaultRowHeight = () => DEFAULT_COLUMN_WIDTH;
22
+ const defaultColumnWidth = () => 60;
23
+ const defaultSelectionRenderer = (props) => {
24
+ return React.createElement(Selection, { ...props });
25
+ };
26
+ const defaultGridLineRenderer = (props) => {
27
+ return React.createElement(GridLine, { ...props });
28
+ };
29
+ export const RESET_SCROLL_EVENTS_DEBOUNCE_INTERVAL = 150;
30
+ /* Placeholder for empty array -> Prevents re-render */
31
+ const EMPTY_ARRAY = [];
32
+ /* Max scroll height */
33
+ const MAX_SCROLLBAR_HEIGHT = 16777000;
34
+ /**
35
+ * Grid component using React Konva
36
+ * @param props
37
+ *
38
+ * TODO: Fix bug with snapping, since onWheel is a global handler, rowCount, columnCount becomes state
39
+ */
40
+ const Grid = memo(forwardRef((props, forwardedRef) => {
41
+ const { width: containerWidth = 800, height: containerHeight = 600, estimatedColumnWidth, estimatedRowHeight, rowHeight = defaultRowHeight, columnWidth = defaultColumnWidth, rowCount = 0, columnCount = 0, scrollbarSize = 15, scrollToCellOffset = 0, extendScrollSize = false, autoFocus, onScroll, onImmediateScroll, showScrollbar = true, selectionBackgroundColor = "rgb(14, 101, 235, 0.1)", selectionBorderColor = "#1a73e8", selectionStrokeWidth = 1, activeCellStrokeWidth = 2, activeCell, selections = EMPTY_ARRAY, frozenRows = 0, frozenColumns = 0, itemRenderer = defaultItemRenderer, headerRenderer = defaultItemRenderer, enableCellOverlay = false, enableCellBackground = true, overlayRenderer = defaultOverlayRenderer, backgroundRenderer, mergedCells = EMPTY_ARRAY, snap = false, scrollThrottleTimeout = 80, onViewPortChange, onDimensionsChange, selectionRenderer = defaultSelectionRenderer, onBeforeRenderRow, onAfterScrollToCell, showFrozenRowShadow = false, showFrozenColumnShadow = false, shadowSettings = defaultShadowSettings, borderStyles = EMPTY_ARRAY, children, gridChildren, stageProps, wrapper, showFillHandle = false, fillSelection, overscanCount = 1, fillHandleSize = 8, onFillHandleMouseDown, onFillHandleDoubleClick, fillhandleBorderColor = "white", fillHandleBackgroundColor = selectionBorderColor, showGridLines = false, gridLineColor = "#E3E2E2", gridLineWidth = 1, gridLineRenderer = defaultGridLineRenderer, isHiddenRow, visibleRowRanges, isHiddenColumn, isHiddenCell, isHiddenOverlay, isHiddenBackground, scale = 1, enableSelectionDrag = false, isDraggingSelection = false, isTouchDevice, FillHandle = DefaultFillHandle, showHeaders, frozenShadowThickness = 0, preventBrowserDefaultScroll = false, footerHeight = 0, footerComponent, getOverflowBuffer, getFrozenColumnOverflowCells, ...rest } = props;
42
+ const instanceProps = useRef({
43
+ columnMetadataMap: new Map(),
44
+ rowMetadataMap: new Map(),
45
+ lastMeasuredColumnIndex: -1,
46
+ lastMeasuredRowIndex: -1,
47
+ estimatedColumnWidth: estimatedColumnWidth || DEFAULT_ESTIMATED_ITEM_SIZE,
48
+ estimatedRowHeight: estimatedRowHeight || DEFAULT_ESTIMATED_ITEM_SIZE,
49
+ });
50
+ const stageRef = useRef(null);
51
+ const rowHeaderStageRef = useRef(null);
52
+ const columnHeaderStageRef = useRef(null);
53
+ const containerRef = useRef(null);
54
+ const scrollContainerRef = useRef(null);
55
+ const verticalScrollRef = useRef(null);
56
+ const wheelingRef = useRef(null);
57
+ const horizontalScrollRef = useRef(null);
58
+ const isEndOfVerticalScrollRef = useRef(false);
59
+ const isStartOffVerticalScrollRef = useRef(false);
60
+ const [_, forceRender] = useReducer((s) => s + 1, 0);
61
+ // Cell caching
62
+ const cellCacheRef = useRef(new Map());
63
+ const cellOverlayCacheRef = useRef(new Map());
64
+ const cellBackgroundCacheRef = useRef(new Map());
65
+ const [scrollState, setScrollState] = useState({
66
+ scrollTop: 0,
67
+ scrollLeft: 0,
68
+ isScrolling: false,
69
+ verticalScrollDirection: Direction.Down,
70
+ horizontalScrollDirection: Direction.Right,
71
+ });
72
+ const scrollSnapRefs = useRef(null);
73
+ const { scrollTop, scrollLeft, isScrolling } = scrollState;
74
+ const isMounted = useRef(false);
75
+ /* Focus container */
76
+ const focusContainer = useCallback(() => {
77
+ if (canUseDOM && document.activeElement !== containerRef.current) {
78
+ return containerRef.current?.focus();
79
+ }
80
+ }, []);
81
+ /**
82
+ * Get top offset of rowIndex
83
+ */
84
+ const getRowOffset = useCallback((index, readonly) => {
85
+ if (index < 0) {
86
+ return 0;
87
+ }
88
+ return getRowOffsetHelper({
89
+ index,
90
+ rowHeight,
91
+ columnWidth,
92
+ instanceProps: instanceProps.current,
93
+ scale,
94
+ readonly,
95
+ visibleRowRanges,
96
+ });
97
+ }, [rowHeight, columnWidth, scale, visibleRowRanges]);
98
+ /**
99
+ * Get lefft offset of columnIndex
100
+ */
101
+ const getColumnOffset = useCallback((index, readonly) => {
102
+ if (index < 0) {
103
+ return 0;
104
+ }
105
+ return getColumnOffsetHelper({
106
+ index,
107
+ rowHeight,
108
+ columnWidth,
109
+ instanceProps: instanceProps.current,
110
+ scale,
111
+ readonly,
112
+ });
113
+ }, [rowHeight, columnWidth, scale]);
114
+ /**
115
+ * Get height of rowIndex
116
+ */
117
+ const getRowHeight = useCallback((index) => {
118
+ return getRowHeightHelper(index, instanceProps.current);
119
+ }, []);
120
+ /**
121
+ * Get height of columNiondex
122
+ */
123
+ const getColumnWidth = useCallback((index) => {
124
+ return getColumnWidthHelper(index, instanceProps.current);
125
+ }, []);
126
+ /**
127
+ * onScroll callback
128
+ */
129
+ useEffect(() => {
130
+ if (!isMounted.current)
131
+ return;
132
+ onScroll?.({ scrollTop, scrollLeft });
133
+ }, [scrollTop, scrollLeft, onScroll]);
134
+ /**
135
+ * Autofocus container
136
+ * Imperative focus, the `div` does not support autoFocus
137
+ * property
138
+ */
139
+ useEffect(() => {
140
+ if (autoFocus) {
141
+ focusContainer();
142
+ }
143
+ }, [autoFocus, focusContainer]);
144
+ /**
145
+ * Imperatively get the current scroll position
146
+ */
147
+ const getScrollPosition = useCallback(() => {
148
+ return {
149
+ scrollTop,
150
+ scrollLeft,
151
+ };
152
+ }, [scrollTop, scrollLeft]);
153
+ /**
154
+ * Redraw grid
155
+ */
156
+ const redrawGrid = useCallback(() => {
157
+ // Sync props
158
+ forceRender();
159
+ // Redraw camvas
160
+ stageRef.current?.batchDraw();
161
+ }, []);
162
+ /**
163
+ * Create a map of merged cells
164
+ * [rowIndex, columnindex] => [parentRowIndex, parentColumnIndex]
165
+ */
166
+ const mergedCellMap = useMemo(() => {
167
+ const mergedCellMap = new Map();
168
+ if (mergedCells) {
169
+ for (let i = 0; i < mergedCells.length; i++) {
170
+ const bounds = mergedCells[i];
171
+ for (const cell of getBoundedCells(bounds)) {
172
+ mergedCellMap.set(cell, bounds);
173
+ }
174
+ }
175
+ }
176
+ return mergedCellMap;
177
+ }, [mergedCells]);
178
+ /* Check if a cell is part of a merged cell */
179
+ const isMergedCell = useCallback(({ rowIndex, columnIndex }) => {
180
+ return mergedCellMap.has(cellIdentifier(rowIndex, columnIndex));
181
+ }, [mergedCellMap]);
182
+ /* Get top, left bounds of a cell */
183
+ const getCellBounds = useCallback(({ rowIndex, columnIndex }, spanMerges = true) => {
184
+ if (spanMerges) {
185
+ const isMerged = isMergedCell({ rowIndex, columnIndex });
186
+ if (isMerged)
187
+ return mergedCellMap.get(cellIdentifier(rowIndex, columnIndex));
188
+ }
189
+ return {
190
+ startRowIndex: rowIndex,
191
+ startColumnIndex: columnIndex,
192
+ endColumnIndex: columnIndex,
193
+ endRowIndex: rowIndex,
194
+ };
195
+ }, [mergedCellMap, isMergedCell]);
196
+ /* Get top, left bounds of a cell */
197
+ const getActualCellCoords = useCallback(({ rowIndex, columnIndex }) => {
198
+ const isMerged = isMergedCell({ rowIndex, columnIndex });
199
+ if (isMerged) {
200
+ const cell = mergedCellMap.get(cellIdentifier(rowIndex, columnIndex));
201
+ return {
202
+ rowIndex: cell?.startRowIndex,
203
+ columnIndex: cell?.startColumnIndex,
204
+ };
205
+ }
206
+ return {
207
+ rowIndex,
208
+ columnIndex,
209
+ };
210
+ }, [mergedCellMap, isMergedCell]);
211
+ /* Spacing for frozen row/column clip */
212
+ const frozenSpacing = 1;
213
+ const frozenColumnWidth = useMemo(() => getColumnOffset(frozenColumns), [frozenColumns, getColumnOffset]);
214
+ const frozenRowHeight = useMemo(() => getRowOffset(frozenRows), [frozenRows, getRowOffset]);
215
+ // Header columns dimensions: Only for Spreadsheet
216
+ const headerRowHeight = useMemo(() => getRowOffset(1) + frozenSpacing, [getRowOffset, frozenSpacing]);
217
+ const headerColumnWidth = useMemo(() => getColumnOffset(1) + frozenSpacing, [getColumnOffset, frozenSpacing]);
218
+ const [rowStartIndex, rowStopIndex, visibleRowStartIndex, visibleRowStopIndex,] = useMemo(() => {
219
+ const startIndex = getRowStartIndexForOffset({
220
+ rowHeight,
221
+ columnWidth,
222
+ rowCount,
223
+ columnCount,
224
+ instanceProps: instanceProps.current,
225
+ offset: scrollTop + frozenRowHeight,
226
+ scale,
227
+ });
228
+ const stopIndex = getRowStopIndexForStartIndex({
229
+ startIndex,
230
+ rowCount,
231
+ rowHeight,
232
+ columnWidth,
233
+ scrollTop,
234
+ containerHeight,
235
+ instanceProps: instanceProps.current,
236
+ scale,
237
+ visibleRowRanges,
238
+ });
239
+ return [
240
+ Math.max(0, startIndex),
241
+ Math.max(0, Math.min(rowCount - 1, stopIndex)),
242
+ startIndex,
243
+ stopIndex,
244
+ ];
245
+ }, [
246
+ rowHeight,
247
+ columnWidth,
248
+ rowCount,
249
+ columnCount,
250
+ scale,
251
+ scrollTop,
252
+ containerHeight,
253
+ frozenRowHeight,
254
+ visibleRowRanges,
255
+ ]);
256
+ const [columnStartIndex, columnStopIndex, visibleColumnStartIndex, visibleColumnStopIndex,] = useMemo(() => {
257
+ const startIndex = getColumnStartIndexForOffset({
258
+ rowHeight,
259
+ columnWidth,
260
+ rowCount,
261
+ columnCount,
262
+ instanceProps: instanceProps.current,
263
+ offset: Math.max(0, scrollLeft +
264
+ frozenColumnWidth -
265
+ (getOverflowBuffer?.(scrollLeft) ?? 0)),
266
+ scale,
267
+ });
268
+ const stopIndex = getColumnStopIndexForStartIndex({
269
+ startIndex,
270
+ columnCount,
271
+ rowHeight,
272
+ columnWidth,
273
+ scrollLeft,
274
+ containerWidth,
275
+ instanceProps: instanceProps.current,
276
+ scale,
277
+ });
278
+ // Text overflow can be made visible with a higher
279
+ // overscanCount
280
+ return [
281
+ Math.max(0, startIndex - overscanCount),
282
+ Math.max(0, Math.min(columnCount - 1, stopIndex)),
283
+ startIndex,
284
+ stopIndex,
285
+ ];
286
+ }, [
287
+ rowHeight,
288
+ columnWidth,
289
+ rowCount,
290
+ columnCount,
291
+ scale,
292
+ containerWidth,
293
+ scrollLeft,
294
+ overscanCount,
295
+ frozenColumnWidth,
296
+ getOverflowBuffer,
297
+ ]);
298
+ /**
299
+ * Get visible row indices within the viewport range.
300
+ * Uses visibleRowRanges for O(r) iteration when available,
301
+ * otherwise falls back to checking isHiddenRow for each row.
302
+ */
303
+ const getVisibleRowIndices = useCallback((startIndex, stopIndex, skipFrozen = true) => {
304
+ const indices = [];
305
+ if (visibleRowRanges) {
306
+ // Use pre-computed ranges for efficient iteration
307
+ for (const range of visibleRowRanges) {
308
+ // Skip ranges entirely before our viewport
309
+ if (range.end < startIndex)
310
+ continue;
311
+ // Stop if we've passed the viewport
312
+ if (range.start > stopIndex)
313
+ break;
314
+ const effectiveStart = Math.max(range.start, startIndex);
315
+ const effectiveEnd = Math.min(range.end, stopIndex);
316
+ for (let rowIndex = effectiveStart; rowIndex <= effectiveEnd; rowIndex++) {
317
+ if (skipFrozen && rowIndex < frozenRows)
318
+ continue;
319
+ indices.push(rowIndex);
320
+ }
321
+ }
322
+ }
323
+ else {
324
+ // Fallback to checking each row
325
+ for (let rowIndex = startIndex; rowIndex <= stopIndex; rowIndex++) {
326
+ if (skipFrozen && rowIndex < frozenRows)
327
+ continue;
328
+ if (isHiddenRow?.(rowIndex))
329
+ continue;
330
+ indices.push(rowIndex);
331
+ }
332
+ }
333
+ return indices;
334
+ }, [visibleRowRanges, isHiddenRow, frozenRows]);
335
+ const estimatedTotalHeight = useMemo(() => getEstimatedTotalHeight(rowCount, instanceProps.current), [rowCount, instanceProps.current.lastMeasuredRowIndex, rowHeight]);
336
+ const estimatedTotalWidth = useMemo(() => getEstimatedTotalWidth(columnCount, instanceProps.current), [columnCount, instanceProps.current.lastMeasuredColumnIndex, columnWidth]);
337
+ /* Method to get dimensions of the grid */
338
+ const getDimensions = useCallback(() => {
339
+ return {
340
+ containerWidth,
341
+ containerHeight,
342
+ estimatedTotalWidth,
343
+ estimatedTotalHeight,
344
+ };
345
+ }, [
346
+ containerWidth,
347
+ containerHeight,
348
+ estimatedTotalWidth,
349
+ estimatedTotalHeight,
350
+ ]);
351
+ /**
352
+ * Update snap properties if its active
353
+ * We need this because we are binding `onwheel` event to document
354
+ * So props go stale
355
+ */
356
+ useIsomorphicLayoutEffect(() => {
357
+ if (snap) {
358
+ scrollSnapRefs.current = {
359
+ visibleRowStartIndex,
360
+ rowCount,
361
+ visibleColumnStartIndex,
362
+ columnCount,
363
+ };
364
+ }
365
+ }, [
366
+ snap,
367
+ visibleRowStartIndex,
368
+ rowCount,
369
+ visibleColumnStartIndex,
370
+ columnCount,
371
+ ]);
372
+ /**
373
+ * Snaps vertical scrollbar to the next/prev visible row
374
+ */
375
+ const snapToRowFn = useCallback(({ deltaY }) => {
376
+ if (!verticalScrollRef.current || !scrollSnapRefs.current)
377
+ return;
378
+ if (deltaY !== 0) {
379
+ const direction = deltaY < 0 ? Direction.Up : Direction.Down;
380
+ const index = getScrollIncrement(deltaY, DEFAULT_ROW_HEIGHT);
381
+ const { visibleRowStartIndex, rowCount } = scrollSnapRefs.current;
382
+ const nextVisibleRow = clampIndex(visibleRowStartIndex + (direction === Direction.Down ? 1 : -1), isHiddenRow, direction);
383
+ let nextIndex = direction === Direction.Up
384
+ ? // User is scrolling up
385
+ Math.max(0, nextVisibleRow - index)
386
+ : Math.min(nextVisibleRow + index, rowCount - 1);
387
+ /* Ignore hidden row */
388
+ nextIndex = clampIndex(nextIndex, isHiddenRow, direction);
389
+ const rowOffset = getRowOffset(nextIndex) - frozenRowHeight;
390
+ verticalScrollRef.current.scrollTo({
391
+ top: rowOffset,
392
+ behavior: "auto",
393
+ });
394
+ }
395
+ }, [getRowOffset, isHiddenRow, frozenRowHeight]);
396
+ /**
397
+ * Snaps horizontal scrollbar to the next/prev visible column
398
+ */
399
+ const snapToColumnFn = useCallback(({ deltaX }) => {
400
+ if (!horizontalScrollRef.current || !scrollSnapRefs.current)
401
+ return;
402
+ if (deltaX !== 0) {
403
+ const index = getScrollIncrement(deltaX, DEFAULT_COLUMN_WIDTH); // Min column width
404
+ const direction = deltaX < 0 ? Direction.Left : Direction.Right;
405
+ const { visibleColumnStartIndex, columnCount } = scrollSnapRefs.current;
406
+ const nextVisibleColumn = clampIndex(visibleColumnStartIndex + (direction === Direction.Right ? 1 : -1), isHiddenColumn, direction);
407
+ let nextIndex = direction === Direction.Left
408
+ ? Math.max(0, nextVisibleColumn - index)
409
+ : Math.min(nextVisibleColumn + index, columnCount - 1);
410
+ /* Ignore hidden column */
411
+ nextIndex = clampIndex(nextIndex, isHiddenColumn, direction);
412
+ const columnOffset = getColumnOffset(nextIndex) - frozenColumnWidth;
413
+ horizontalScrollRef.current.scrollTo({
414
+ left: columnOffset,
415
+ behavior: "auto",
416
+ });
417
+ }
418
+ }, [getColumnOffset, isHiddenColumn, frozenColumnWidth]);
419
+ const snapToRowThrottler = useRef(undefined);
420
+ const snapToColumnThrottler = useRef(undefined);
421
+ /**
422
+ * Register snap throttlers
423
+ */
424
+ useIsomorphicLayoutEffect(() => {
425
+ if (snap) {
426
+ snapToRowThrottler.current = ricThrottle(snapToRowFn);
427
+ snapToColumnThrottler.current = ricThrottle(snapToColumnFn);
428
+ }
429
+ return () => {
430
+ snapToRowThrottler.current = undefined;
431
+ snapToColumnThrottler.current = undefined;
432
+ };
433
+ }, [snap, scrollThrottleTimeout, snapToColumnFn, snapToRowFn]);
434
+ /* Find frozen column boundary */
435
+ const isWithinFrozenColumnBoundary = useCallback((x) => {
436
+ return frozenColumns > 0 && x < frozenColumnWidth;
437
+ }, [frozenColumns, frozenColumnWidth]);
438
+ /* Find frozen row boundary */
439
+ const isWithinFrozenRowBoundary = useCallback((y) => {
440
+ return frozenRows > 0 && y < frozenRowHeight;
441
+ }, [frozenRows, frozenRowHeight]);
442
+ /**
443
+ * Get relative mouse position
444
+ */
445
+ const getRelativePositionFromOffset = useCallback((left, top) => {
446
+ invariant(typeof left === "number" && typeof top === "number", "Top and left should be a number");
447
+ if (!stageRef.current) {
448
+ return undefined;
449
+ }
450
+ const stage = stageRef.current.getStage();
451
+ const rect = containerRef.current?.getBoundingClientRect();
452
+ if (rect) {
453
+ left = left - rect.x;
454
+ top = top - rect.y;
455
+ }
456
+ const { x, y } = stage
457
+ .getAbsoluteTransform()
458
+ .copy()
459
+ .invert()
460
+ .point({ x: left, y: top });
461
+ return { x, y };
462
+ }, []);
463
+ /**
464
+ * Get position absolute to document
465
+ * from relative grid coordinates
466
+ */
467
+ const getAbsolutePositionFromOffset = useCallback((x, y) => {
468
+ const rect = containerRef.current?.getBoundingClientRect();
469
+ if (rect) {
470
+ x = x + rect.x;
471
+ y = y + rect.y;
472
+ }
473
+ return {
474
+ x,
475
+ y,
476
+ };
477
+ }, []);
478
+ /**
479
+ * Get cell cordinates from current mouse x/y positions
480
+ */
481
+ const getCellCoordsFromOffset = useCallback((left, top, includeFrozen = true) => {
482
+ const pos = getRelativePositionFromOffset(left, top);
483
+ if (!pos)
484
+ return undefined;
485
+ const { x, y } = pos;
486
+ const rowOffset = includeFrozen && isWithinFrozenRowBoundary(y) ? y : y + scrollTop;
487
+ const columnOffset = includeFrozen && isWithinFrozenColumnBoundary(x) ? x : x + scrollLeft;
488
+ if (rowOffset > estimatedTotalHeight ||
489
+ columnOffset > estimatedTotalWidth) {
490
+ return undefined;
491
+ }
492
+ const rowIndex = getRowStartIndexForOffset({
493
+ rowHeight,
494
+ columnWidth,
495
+ rowCount,
496
+ columnCount,
497
+ instanceProps: instanceProps.current,
498
+ offset: rowOffset,
499
+ scale,
500
+ });
501
+ const columnIndex = getColumnStartIndexForOffset({
502
+ rowHeight,
503
+ columnWidth,
504
+ rowCount,
505
+ columnCount,
506
+ instanceProps: instanceProps.current,
507
+ offset: columnOffset,
508
+ scale,
509
+ });
510
+ /* To be compatible with merged cells */
511
+ const bounds = getCellBounds({ rowIndex, columnIndex });
512
+ return {
513
+ rowIndex: bounds.startRowIndex,
514
+ columnIndex: bounds.startColumnIndex,
515
+ };
516
+ }, [
517
+ scale,
518
+ scrollLeft,
519
+ rowHeight,
520
+ columnWidth,
521
+ scrollTop,
522
+ rowCount,
523
+ estimatedTotalHeight,
524
+ estimatedTotalWidth,
525
+ columnCount,
526
+ isWithinFrozenRowBoundary,
527
+ isWithinFrozenColumnBoundary,
528
+ getRelativePositionFromOffset,
529
+ getCellBounds,
530
+ ]);
531
+ /**
532
+ * Get cell offset position from rowIndex, columnIndex
533
+ *
534
+ */
535
+ const getCellOffsetFromCoords = useCallback((cell, readonly) => {
536
+ const { startRowIndex, startColumnIndex, endColumnIndex, endRowIndex } = getCellBounds(cell);
537
+ const x = getColumnOffset(startColumnIndex, readonly);
538
+ const y = getRowOffset(startRowIndex, readonly);
539
+ const width = getColumnOffset(endColumnIndex + 1, readonly) - x;
540
+ const height = getRowOffset(endRowIndex + 1, readonly) - y;
541
+ return {
542
+ x,
543
+ y,
544
+ width,
545
+ height,
546
+ };
547
+ }, [getColumnOffset, getRowOffset, getCellBounds]);
548
+ /* Get current view port of the grid */
549
+ const getViewPort = useCallbackRef(useCallback(() => {
550
+ return {
551
+ rowStartIndex,
552
+ rowStopIndex,
553
+ columnStartIndex,
554
+ columnStopIndex,
555
+ visibleRowStartIndex,
556
+ visibleRowStopIndex,
557
+ visibleColumnStartIndex,
558
+ visibleColumnStopIndex,
559
+ };
560
+ }, [
561
+ rowStartIndex,
562
+ rowStopIndex,
563
+ columnStartIndex,
564
+ columnStopIndex,
565
+ visibleRowStartIndex,
566
+ visibleRowStopIndex,
567
+ visibleColumnStartIndex,
568
+ visibleColumnStopIndex,
569
+ ]));
570
+ // Scrollbars
571
+ const scrollbarXContainterWidth = containerWidth - frozenColumnWidth;
572
+ const scrollbarXWidth = estimatedTotalWidth -
573
+ frozenColumnWidth +
574
+ (extendScrollSize ? scrollbarSize : 0);
575
+ const scrollbarYContainterHeight = containerHeight - frozenRowHeight;
576
+ const scrollbarYHeight = estimatedTotalHeight -
577
+ frozenRowHeight +
578
+ (extendScrollSize ? scrollbarSize : 0) +
579
+ (footerHeight > 0 ? footerHeight - scrollbarSize : 0);
580
+ const showScrollbarX = showScrollbar && scrollbarXWidth > scrollbarXContainterWidth;
581
+ const showScrollbarY = showScrollbar && scrollbarYHeight > scrollbarYContainterHeight;
582
+ const needsScaling = scrollbarYHeight > MAX_SCROLLBAR_HEIGHT;
583
+ const effectiveScrollbarYHeight = Math.min(MAX_SCROLLBAR_HEIGHT, scrollbarYHeight);
584
+ /**
585
+ * Clears the cell cache
586
+ * when scrolling is done or when user changes sheet
587
+ */
588
+ const clearCache = useCallback(() => {
589
+ cellCacheRef.current.clear();
590
+ cellOverlayCacheRef.current.clear();
591
+ cellBackgroundCacheRef.current.clear();
592
+ }, []);
593
+ /**
594
+ * Conditionally get and set cell cache and return cell
595
+ */
596
+ const getCellCache = useCallback((cache, cellId, nodeGetter, isScrolling, isMerged = false) => {
597
+ const cell = isScrolling && !isMerged && cache.has(cellId)
598
+ ? cache.get(cellId)
599
+ : nodeGetter();
600
+ // Get it from cache
601
+ if (isScrolling && !cache.has(cellId)) {
602
+ cache.set(cellId, cell);
603
+ }
604
+ return cell;
605
+ }, []);
606
+ /* Redraw grid imperatively */
607
+ const resetAfterIndices = useCallback(({ columnIndex, rowIndex }, shouldForceUpdate = true) => {
608
+ if (typeof columnIndex === "number") {
609
+ instanceProps.current.lastMeasuredColumnIndex = Math.min(instanceProps.current.lastMeasuredColumnIndex, columnIndex - 1);
610
+ }
611
+ if (typeof rowIndex === "number") {
612
+ instanceProps.current.lastMeasuredRowIndex = Math.min(instanceProps.current.lastMeasuredRowIndex, rowIndex - 1);
613
+ }
614
+ clearCache();
615
+ if (shouldForceUpdate)
616
+ forceRender();
617
+ }, [clearCache]);
618
+ /**
619
+ * Resize one or more columns
620
+ * Update columnMetadataMap
621
+ * lastMeasuredRowIndex or lastMeasuredColumnIndex
622
+ *
623
+ * This fixes unexpected rendering issue where
624
+ * 1. User resizes a column - And informs the grid that from column 2 needs to be repainted and updates the state
625
+ * 2. In the render, function, use uses getCellOffsetFromCoords, which changes the lastMeasured indexes
626
+ * 3. 2) Will happen before state has been updated and it wont queue column from 1) to be resized
627
+ */
628
+ const resizeColumn = useCallback((index, dimension) => {
629
+ instanceProps.current.columnMetadataMap.get(index).size = dimension;
630
+ resetAfterIndices({ columnIndex: index }, false);
631
+ }, [resetAfterIndices]);
632
+ /**
633
+ * Resize one or more rows
634
+ */
635
+ const resizeRow = useCallback((index, dimension) => {
636
+ instanceProps.current.rowMetadataMap.get(index).size = dimension;
637
+ resetAfterIndices({ rowIndex: index + 1 }, false);
638
+ }, [resetAfterIndices]);
639
+ /* Expose some methods in ref */
640
+ useImperativeHandle(forwardedRef, () => {
641
+ return {
642
+ scrollTo,
643
+ scrollBy,
644
+ scrollToCell,
645
+ stage: stageRef.current,
646
+ rowHeaderStage: rowHeaderStageRef.current,
647
+ columnHeaderStage: columnHeaderStageRef.current,
648
+ container: containerRef.current,
649
+ resetAfterIndices,
650
+ getScrollPosition,
651
+ isMergedCell,
652
+ getCellBounds,
653
+ getCellCoordsFromOffset,
654
+ getCellOffsetFromCoords,
655
+ getActualCellCoords,
656
+ focus: focusContainer,
657
+ resizeColumn,
658
+ resizeRow,
659
+ getViewPort,
660
+ getRelativePositionFromOffset,
661
+ getAbsolutePositionFromOffset,
662
+ scrollToTop,
663
+ scrollToBottom,
664
+ getDimensions,
665
+ getRowOffset,
666
+ getColumnOffset,
667
+ redrawGrid,
668
+ clearCache,
669
+ };
670
+ });
671
+ /**
672
+ * When the grid is scrolling,
673
+ * 1. Stage does not listen to any mouse events
674
+ * 2. Div container does not listen to pointer events
675
+ */
676
+ /* Reset isScrolling */
677
+ const resetIsScrolling = useCallback(() => {
678
+ resetIsScrollingTimeoutID.current = null;
679
+ // Reset cell cache
680
+ clearCache();
681
+ setScrollState((prev) => {
682
+ // Only if required
683
+ if (prev.isScrolling === false) {
684
+ return prev;
685
+ }
686
+ return {
687
+ ...prev,
688
+ isScrolling: false,
689
+ };
690
+ });
691
+ }, [clearCache]);
692
+ const resetIsScrollingTimeoutID = useRef(null);
693
+ const resetIsScrollingDebounced = useCallback(() => {
694
+ if (resetIsScrollingTimeoutID.current !== null) {
695
+ cancelTimeout(resetIsScrollingTimeoutID.current);
696
+ }
697
+ resetIsScrollingTimeoutID.current = requestTimeout(resetIsScrolling, RESET_SCROLL_EVENTS_DEBOUNCE_INTERVAL);
698
+ }, [resetIsScrolling]);
699
+ /* Handle vertical scroll */
700
+ const handleScroll = useCallback((e) => {
701
+ const { scrollTop } = e.currentTarget;
702
+ // Calculate what percentage of the scrollable area has been scrolled
703
+ // Apply scaling if needed
704
+ const scaledScrollTop = needsScaling
705
+ ? (scrollTop /
706
+ (effectiveScrollbarYHeight - scrollbarYContainterHeight)) *
707
+ (scrollbarYHeight - scrollbarYContainterHeight)
708
+ : scrollTop;
709
+ setScrollState((prev) => {
710
+ // Only setstate if required
711
+ if (prev.scrollTop === scaledScrollTop) {
712
+ return prev;
713
+ }
714
+ return {
715
+ ...prev,
716
+ isScrolling: true,
717
+ verticalScrollDirection: prev.scrollTop > scaledScrollTop ? Direction.Up : Direction.Down,
718
+ scrollTop: scaledScrollTop,
719
+ };
720
+ });
721
+ /* Scroll callbacks */
722
+ onImmediateScroll?.({ scrollTop, scrollLeft });
723
+ /* Reset isScrolling if required */
724
+ resetIsScrollingDebounced();
725
+ }, [
726
+ scrollLeft,
727
+ needsScaling,
728
+ effectiveScrollbarYHeight,
729
+ scrollbarYHeight,
730
+ scrollbarYContainterHeight,
731
+ onImmediateScroll,
732
+ resetIsScrollingDebounced,
733
+ ]);
734
+ /* Handle horizontal scroll */
735
+ const handleScrollLeft = useCallback((e) => {
736
+ const { scrollLeft } = e.currentTarget;
737
+ setScrollState((prev) => {
738
+ // Only setstate if required
739
+ if (prev.scrollLeft === scrollLeft) {
740
+ return prev;
741
+ }
742
+ return {
743
+ ...prev,
744
+ isScrolling: true,
745
+ horizontalScrollDirection: prev.scrollLeft > scrollLeft ? Direction.Left : Direction.Right,
746
+ scrollLeft,
747
+ };
748
+ });
749
+ /* Scroll callbacks */
750
+ onImmediateScroll?.({ scrollLeft, scrollTop });
751
+ /* Reset isScrolling if required */
752
+ resetIsScrollingDebounced();
753
+ }, [scrollTop, onImmediateScroll, resetIsScrollingDebounced]);
754
+ /* Scroll based on left, top position */
755
+ const scrollTo = useCallback(({ scrollTop, scrollLeft },
756
+ // On touch devices, moving the scrollbars causes
757
+ // scroll flicker
758
+ // But when a sheet is changed, we need to immediately
759
+ // update the scroll state
760
+ // immediate is only set, when user switches to a new sheet
761
+ // Use with care
762
+ immediate) => {
763
+ // Only update state in non-touch
764
+ // devices, otherwise it causes flickering
765
+ // because of scrollbar state updates
766
+ if (!isTouchDevice || immediate) {
767
+ setScrollState((prev) => {
768
+ return {
769
+ ...prev,
770
+ scrollLeft: scrollLeft == void 0 ? prev.scrollLeft : scrollLeft,
771
+ scrollTop: scrollTop == void 0 ? prev.scrollTop : scrollTop,
772
+ };
773
+ });
774
+ }
775
+ if (showScrollbar) {
776
+ /* If scrollbar is visible, lets update it which triggers a state change */
777
+ if (scrollLeft !== void 0) {
778
+ horizontalScrollRef.current?.scrollTo({
779
+ left: scrollLeft,
780
+ behavior: "auto",
781
+ });
782
+ }
783
+ if (scrollTop !== void 0) {
784
+ // Apply scaling if needed
785
+ const scaledScrollTop = needsScaling
786
+ ? (scrollTop / (scrollbarYHeight - scrollbarYContainterHeight)) *
787
+ (effectiveScrollbarYHeight - scrollbarYContainterHeight)
788
+ : scrollTop;
789
+ verticalScrollRef.current?.scrollTo({
790
+ top: scaledScrollTop,
791
+ behavior: "auto",
792
+ });
793
+ }
794
+ }
795
+ }, [
796
+ showScrollbar,
797
+ needsScaling,
798
+ effectiveScrollbarYHeight,
799
+ scrollbarYHeight,
800
+ scrollbarYContainterHeight,
801
+ isTouchDevice,
802
+ ]);
803
+ /* Scroll grid to top */
804
+ const scrollToTop = useCallback(() => {
805
+ scrollTo({ scrollTop: 0, scrollLeft: 0 });
806
+ }, [scrollTo]);
807
+ /* Scroll grid to bottom */
808
+ const scrollToBottom = useCallback(() => {
809
+ scrollTo({
810
+ scrollTop: Math.floor(estimatedTotalHeight - containerHeight + footerHeight),
811
+ });
812
+ }, [estimatedTotalHeight, containerHeight, footerHeight, scrollTo]);
813
+ /**
814
+ * Scrollby utility
815
+ */
816
+ const scrollBy = useCallback(({ x, y }) => {
817
+ setScrollState((prev) => {
818
+ return {
819
+ ...prev,
820
+ scrollLeft: x == void 0 ? prev.scrollLeft : prev.scrollLeft + x,
821
+ scrollTop: y == void 0 ? prev.scrollTop : prev.scrollTop + y,
822
+ };
823
+ });
824
+ if (showScrollbar) {
825
+ if (horizontalScrollRef.current && x !== void 0)
826
+ horizontalScrollRef.current.scrollBy({ left: x, behavior: "auto" });
827
+ if (verticalScrollRef.current && y !== void 0)
828
+ verticalScrollRef.current.scrollBy({ top: y, behavior: "auto" });
829
+ }
830
+ }, [showScrollbar]);
831
+ /**
832
+ * Scrolls to cell
833
+ * Respects frozen rows and columns
834
+ */
835
+ const scrollToCell = useCallback(({ rowIndex, columnIndex }, align = Align.auto) => {
836
+ const isFrozenRow = rowIndex !== void 0 && rowIndex < frozenRows;
837
+ const isFrozenColumn = columnIndex !== void 0 && columnIndex < frozenColumns;
838
+ const frozenColumnOffset = getColumnOffset(frozenColumns);
839
+ /* Making sure getColumnWidth works */
840
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
841
+ const x = columnIndex !== void 0 ? getColumnOffset(columnIndex) : void 0;
842
+ /* Making sure getRowHeight works */
843
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
844
+ const y = rowIndex !== void 0 ? getRowOffset(rowIndex) : void 0;
845
+ const width = columnIndex !== void 0 ? getColumnWidth(columnIndex) : 0;
846
+ const height = rowIndex !== void 0 ? getRowHeight(rowIndex) : 0;
847
+ const columnAlign = width > containerWidth ? Align.start : align;
848
+ const rowAlign = height > containerHeight ? Align.start : align;
849
+ const newScrollLeft = columnIndex !== void 0 && !isFrozenColumn
850
+ ? Math.floor(getOffsetForColumnAndAlignment({
851
+ index: columnIndex,
852
+ containerHeight,
853
+ containerWidth,
854
+ columnCount,
855
+ columnWidth,
856
+ rowCount,
857
+ rowHeight,
858
+ scrollOffset: scrollLeft,
859
+ instanceProps: instanceProps.current,
860
+ scrollbarSize: scrollbarSize +
861
+ (columnIndex === columnCount - 1 ? 0 : scrollToCellOffset),
862
+ frozenOffset: frozenColumnOffset,
863
+ align: columnAlign,
864
+ scale,
865
+ estimatedTotalWidth,
866
+ estimatedTotalHeight,
867
+ }))
868
+ : void 0;
869
+ const frozenRowOffset = getRowOffset(frozenRows);
870
+ let newScrollTop = rowIndex !== void 0 && !isFrozenRow
871
+ ? Math.floor(getOffsetForRowAndAlignment({
872
+ index: rowIndex,
873
+ containerHeight: containerHeight,
874
+ containerWidth,
875
+ columnCount,
876
+ columnWidth,
877
+ rowCount,
878
+ rowHeight,
879
+ scrollOffset: scrollTop,
880
+ instanceProps: instanceProps.current,
881
+ scrollbarSize: scrollbarSize +
882
+ (rowIndex === rowCount - 1 ? 0 : scrollToCellOffset),
883
+ frozenOffset: frozenRowOffset,
884
+ align: rowAlign,
885
+ scale,
886
+ estimatedTotalWidth,
887
+ estimatedTotalHeight: estimatedTotalHeight + footerHeight,
888
+ }))
889
+ : void 0;
890
+ // We already reached bottom of the last cell
891
+ // If scrolling to last row and already at bottom, reveal the footer
892
+ // if (
893
+ // scrollTop > 0 &&
894
+ // newScrollTop !== void 0 &&
895
+ // newScrollTop === scrollTop &&
896
+ // rowIndex === rowCount - 1 &&
897
+ // footerHeight > 0
898
+ // ) {
899
+ // const maxContentScroll = estimatedTotalHeight - containerHeight;
900
+ // const maxScrollWithFooter = maxContentScroll + footerHeight;
901
+ // // Only reveal footer if we haven't scrolled past the content yet
902
+ // if (scrollTop < maxScrollWithFooter) {
903
+ // newScrollTop = scrollTop + footerHeight;
904
+ // }
905
+ // }
906
+ const isOutsideViewport = (rowIndex !== void 0 &&
907
+ rowIndex > rowStopIndex + (rowStopIndex - rowStartIndex)) ||
908
+ (columnIndex !== void 0 &&
909
+ columnIndex >
910
+ columnStopIndex + (columnStopIndex - columnStartIndex));
911
+ if (isOutsideViewport) {
912
+ requestAnimationFrame(() => {
913
+ scrollTo({
914
+ scrollLeft: newScrollLeft,
915
+ scrollTop: newScrollTop,
916
+ });
917
+ });
918
+ }
919
+ else {
920
+ scrollTo({
921
+ scrollLeft: newScrollLeft,
922
+ scrollTop: newScrollTop,
923
+ });
924
+ }
925
+ // After scroll to cell callback
926
+ onAfterScrollToCell?.(newScrollLeft, newScrollTop);
927
+ }, [
928
+ containerHeight,
929
+ columnStartIndex,
930
+ columnStopIndex,
931
+ rowStartIndex,
932
+ rowStopIndex,
933
+ containerWidth,
934
+ estimatedTotalWidth,
935
+ estimatedTotalHeight,
936
+ rowCount,
937
+ columnCount,
938
+ scrollbarSize,
939
+ scrollToCellOffset,
940
+ scrollLeft,
941
+ scrollTop,
942
+ frozenRows,
943
+ frozenColumns,
944
+ columnWidth,
945
+ getColumnOffset,
946
+ getColumnWidth,
947
+ getRowHeight,
948
+ getRowOffset,
949
+ rowHeight,
950
+ scale,
951
+ scrollTo,
952
+ onAfterScrollToCell,
953
+ footerHeight,
954
+ ]);
955
+ /**
956
+ * React to changes in rowCount or columnCount
957
+ */
958
+ if (visibleRowStopIndex > rowCount ||
959
+ visibleColumnStopIndex > columnCount) {
960
+ scrollToTop();
961
+ }
962
+ // Check if we have reached end of vertical scroll
963
+ isEndOfVerticalScrollRef.current =
964
+ estimatedTotalHeight ===
965
+ scrollState.scrollTop + containerHeight - scrollbarSize;
966
+ isStartOffVerticalScrollRef.current = scrollState.scrollTop === 0;
967
+ /**
968
+ * Fired when user tries to scroll the canvas
969
+ */
970
+ const handleWheel = useCallback((event) => {
971
+ /* If user presses shift key, scroll horizontally */
972
+ const isCtrlKey = event.ctrlKey;
973
+ const isMetaKey = event.metaKey;
974
+ // Ctrl and meta key are used for zooming
975
+ if (isCtrlKey || isMetaKey) {
976
+ // Disable wheel event on ctrl key
977
+ event.preventDefault();
978
+ return;
979
+ }
980
+ const isScrollingHorizontally = event.shiftKey;
981
+ const { deltaX, deltaY, deltaMode } = event;
982
+ const isVerticalEndOfScroll = !isScrollingHorizontally &&
983
+ ((isEndOfVerticalScrollRef.current && deltaY > 0) ||
984
+ (isStartOffVerticalScrollRef.current && deltaY < 0));
985
+ const isUserSwipingLeft = Math.abs(deltaX) > 0;
986
+ const shouldPreventDefault = preventBrowserDefaultScroll
987
+ ? true
988
+ : !isVerticalEndOfScroll || isUserSwipingLeft;
989
+ // There is a bug currently which enables swipe to back and forward
990
+ // when isStartOffVerticalScrollRef and isEndOfVerticalScrollRef is true
991
+ // Need more user research to understand what is the expectation here
992
+ // Should swipe back and forward be disabled completely in the grid?
993
+ // Or should it be enabled if user is at the top/bottom/left/right
994
+ // i.e. end of the grid ?
995
+ // deltaX === 0
996
+ /* Prevent browser back in Mac */
997
+ // Mac, swipe to back or forward will not work with
998
+ // this grid. But that can be achieved by checking
999
+ // StartOfHorizontalScroll and EndOfHorizontalScroll
1000
+ // Todo: when users requests this
1001
+ if (shouldPreventDefault) {
1002
+ event.preventDefault();
1003
+ }
1004
+ /* Scroll natively */
1005
+ if (wheelingRef.current) {
1006
+ return;
1007
+ }
1008
+ let dx = isScrollingHorizontally
1009
+ ? deltaY === 0
1010
+ ? deltaX
1011
+ : deltaY
1012
+ : deltaX;
1013
+ let dy = deltaY;
1014
+ /* If snaps are active */
1015
+ if (snap) {
1016
+ /* Scroll only in one direction */
1017
+ const isHorizontal = isScrollingHorizontally || Math.abs(dx) > Math.abs(dy);
1018
+ if (isHorizontal) {
1019
+ snapToColumnThrottler.current?.({
1020
+ deltaX,
1021
+ });
1022
+ }
1023
+ else {
1024
+ snapToRowThrottler.current?.({
1025
+ deltaY,
1026
+ });
1027
+ }
1028
+ return;
1029
+ }
1030
+ if (deltaMode === event.DOM_DELTA_LINE) {
1031
+ if (isScrollingHorizontally) {
1032
+ dx *= DEFAULT_COLUMN_WIDTH;
1033
+ }
1034
+ else {
1035
+ dy *= DEFAULT_ROW_HEIGHT;
1036
+ }
1037
+ }
1038
+ if (!horizontalScrollRef.current && !verticalScrollRef.current) {
1039
+ return;
1040
+ }
1041
+ wheelingRef.current = window.requestAnimationFrame(() => {
1042
+ wheelingRef.current = null;
1043
+ if (isScrollingHorizontally) {
1044
+ horizontalScrollRef.current?.scrollBy({
1045
+ left: dx,
1046
+ behavior: "auto",
1047
+ });
1048
+ }
1049
+ else {
1050
+ horizontalScrollRef.current?.scrollBy({
1051
+ left: dx,
1052
+ behavior: "auto",
1053
+ });
1054
+ verticalScrollRef.current?.scrollBy({
1055
+ top: dy,
1056
+ behavior: "auto",
1057
+ });
1058
+ }
1059
+ });
1060
+ }, [snap, preventBrowserDefaultScroll]);
1061
+ /**
1062
+ * Handle mouse wheeel
1063
+ */
1064
+ useEffect(() => {
1065
+ const scrollContainer = scrollContainerRef.current;
1066
+ scrollContainer?.addEventListener("wheel", handleWheel, {
1067
+ passive: false,
1068
+ });
1069
+ isMounted.current = true;
1070
+ return () => {
1071
+ scrollContainer?.removeEventListener("wheel", handleWheel);
1072
+ };
1073
+ }, [handleWheel]);
1074
+ /* Callback when visible rows or columns have changed */
1075
+ useEffect(() => {
1076
+ onViewPortChange?.({
1077
+ rowStartIndex,
1078
+ rowStopIndex,
1079
+ columnStartIndex,
1080
+ columnStopIndex,
1081
+ visibleRowStartIndex,
1082
+ visibleRowStopIndex,
1083
+ visibleColumnStartIndex,
1084
+ visibleColumnStopIndex,
1085
+ });
1086
+ }, [
1087
+ onViewPortChange,
1088
+ rowStartIndex,
1089
+ rowStopIndex,
1090
+ columnStartIndex,
1091
+ columnStopIndex,
1092
+ visibleRowStartIndex,
1093
+ visibleRowStopIndex,
1094
+ visibleColumnStartIndex,
1095
+ visibleColumnStopIndex,
1096
+ ]);
1097
+ // Callback when grid dimensions change
1098
+ useEffect(() => {
1099
+ if (!isScrolling) {
1100
+ onDimensionsChange?.({
1101
+ containerWidth: containerWidth - frozenColumnWidth,
1102
+ containerHeight: containerHeight - frozenRowHeight,
1103
+ estimatedTotalWidth: estimatedTotalWidth -
1104
+ frozenColumnWidth +
1105
+ (extendScrollSize ? scrollbarSize : 0),
1106
+ estimatedTotalHeight: estimatedTotalHeight -
1107
+ frozenRowHeight +
1108
+ (extendScrollSize ? scrollbarSize : 0),
1109
+ });
1110
+ }
1111
+ }, [
1112
+ containerWidth,
1113
+ containerHeight,
1114
+ estimatedTotalWidth,
1115
+ estimatedTotalHeight,
1116
+ isScrolling,
1117
+ frozenRowHeight,
1118
+ frozenColumnWidth,
1119
+ extendScrollSize,
1120
+ scrollbarSize,
1121
+ onDimensionsChange,
1122
+ ]);
1123
+ /* Draw gridlines */
1124
+ const [gridLines, gridLinesFrozenRow, gridLinesFrozenColumn, gridLinesFrozenIntersection,] = useMemo(() => {
1125
+ const gridLines = [];
1126
+ const gridLinesFrozenRow = [];
1127
+ const gridLinesFrozenColumn = [];
1128
+ const gridLinesFrozenIntersection = [];
1129
+ if (showGridLines) {
1130
+ // Horizontal - use getVisibleRowIndices for efficient iteration
1131
+ const visibleRows = getVisibleRowIndices(rowStartIndex, rowStopIndex);
1132
+ for (const rowIndex of visibleRows) {
1133
+ const x1 = getColumnOffset(frozenColumns);
1134
+ const x2 = getColumnOffset(Math.min(columnStopIndex + 1, columnCount));
1135
+ const y1 = getRowOffset(Math.min(rowIndex + 1, rowCount));
1136
+ const y2 = y1;
1137
+ gridLines.push(gridLineRenderer({
1138
+ points: [x1, y1, x2, y2],
1139
+ stroke: gridLineColor,
1140
+ strokeWidth: gridLineWidth,
1141
+ offsetY: -0.5,
1142
+ key: itemKey({ rowIndex: rowIndex, columnIndex: y1 }),
1143
+ }));
1144
+ if (frozenColumns > 1) {
1145
+ gridLinesFrozenColumn.push(gridLineRenderer({
1146
+ points: [0, y1, x2, y2],
1147
+ stroke: gridLineColor,
1148
+ strokeWidth: gridLineWidth,
1149
+ offsetY: -0.5,
1150
+ key: itemKey({ rowIndex: rowIndex, columnIndex: y1 }),
1151
+ }));
1152
+ }
1153
+ }
1154
+ // Vertical
1155
+ for (let columnIndex = columnStartIndex; columnIndex <= columnStopIndex; columnIndex++) {
1156
+ /* Ignore frozen rows */
1157
+ if (columnIndex < frozenColumns || isHiddenColumn?.(columnIndex))
1158
+ continue;
1159
+ const x1 = getColumnOffset(Math.min(columnIndex + 1, columnCount));
1160
+ const x2 = x1;
1161
+ const y1 = getRowOffset(frozenRows);
1162
+ const y2 = getRowOffset(Math.min(rowStopIndex + 1, rowCount));
1163
+ gridLines.push(gridLineRenderer({
1164
+ points: [x1, y1, x2, y2],
1165
+ stroke: gridLineColor,
1166
+ strokeWidth: gridLineWidth,
1167
+ offsetX: -0.5,
1168
+ key: itemKey({ rowIndex: x1, columnIndex: columnIndex }),
1169
+ }));
1170
+ if (frozenRows > 1) {
1171
+ gridLinesFrozenRow.push(gridLineRenderer({
1172
+ points: [x1, 0, x2, y2],
1173
+ stroke: gridLineColor,
1174
+ strokeWidth: gridLineWidth,
1175
+ offsetX: -0.5,
1176
+ key: itemKey({ rowIndex: x1, columnIndex: columnIndex }),
1177
+ }));
1178
+ }
1179
+ }
1180
+ for (let rowIndex = 0; rowIndex < Math.min(rowStopIndex, frozenRows); rowIndex++) {
1181
+ if (isHiddenRow?.(rowIndex)) {
1182
+ continue;
1183
+ }
1184
+ const x1 = getColumnOffset(1);
1185
+ const x2 = getColumnOffset(Math.min(columnStopIndex + 1, columnCount));
1186
+ const y1 = getRowOffset(Math.min(rowIndex + 1, rowCount));
1187
+ const y2 = y1;
1188
+ gridLinesFrozenRow.push(gridLineRenderer({
1189
+ points: [x1, y1, x2, y2],
1190
+ stroke: gridLineColor,
1191
+ strokeWidth: gridLineWidth,
1192
+ offsetY: -0.5,
1193
+ key: itemKey({ rowIndex: rowIndex, columnIndex: y1 }),
1194
+ }));
1195
+ gridLinesFrozenIntersection.push(gridLineRenderer({
1196
+ points: [x1, y1, x2, y2],
1197
+ stroke: gridLineColor,
1198
+ strokeWidth: gridLineWidth,
1199
+ offsetY: -0.5,
1200
+ key: itemKey({ rowIndex: rowIndex, columnIndex: y1 }),
1201
+ }));
1202
+ }
1203
+ for (let columnIndex = 0; columnIndex < Math.min(columnStopIndex, frozenColumns); columnIndex++) {
1204
+ if (isHiddenColumn?.(columnIndex))
1205
+ continue;
1206
+ const x1 = getColumnOffset(Math.min(columnIndex + 1, columnCount));
1207
+ const x2 = x1;
1208
+ const y1 = getRowOffset(1);
1209
+ const y2 = getRowOffset(Math.min(rowStopIndex + 1, rowCount));
1210
+ gridLinesFrozenColumn.push(gridLineRenderer({
1211
+ points: [x1, y1, x2, y2],
1212
+ stroke: gridLineColor,
1213
+ strokeWidth: gridLineWidth,
1214
+ offsetX: -0.5,
1215
+ key: itemKey({ rowIndex: x1, columnIndex: columnIndex }),
1216
+ }));
1217
+ gridLinesFrozenIntersection.push(gridLineRenderer({
1218
+ points: [x1, y1, x2, y2],
1219
+ stroke: gridLineColor,
1220
+ strokeWidth: gridLineWidth,
1221
+ offsetX: -0.5,
1222
+ key: itemKey({ rowIndex: x1, columnIndex: columnIndex }),
1223
+ }));
1224
+ }
1225
+ }
1226
+ return [
1227
+ gridLines,
1228
+ gridLinesFrozenRow,
1229
+ gridLinesFrozenColumn,
1230
+ gridLinesFrozenIntersection,
1231
+ ];
1232
+ }, [
1233
+ rowStartIndex,
1234
+ rowStopIndex,
1235
+ columnStartIndex,
1236
+ columnStopIndex,
1237
+ getRowOffset,
1238
+ getColumnOffset,
1239
+ gridLineColor,
1240
+ gridLineWidth,
1241
+ rowCount,
1242
+ columnCount,
1243
+ frozenRows,
1244
+ frozenColumns,
1245
+ showGridLines,
1246
+ getVisibleRowIndices,
1247
+ isHiddenRow,
1248
+ isHiddenColumn,
1249
+ gridLineRenderer,
1250
+ ]);
1251
+ // A data update arrives as a NEW renderer identity (hosts recreate their getters per data
1252
+ // version — e.g. a windowed engine loading cells asynchronously mid-scroll). The scroll cell
1253
+ // cache keys only on cell id, so an entry computed against the OLD data keeps painting beside
1254
+ // freshly rendered neighbours until the scroll-end clear — e.g. a long string that overflowed
1255
+ // into a then-blank neighbour drawn OVER the neighbour's just-loaded text. Invalidate the
1256
+ // cache the moment any renderer changes; a `useMemo` so it runs during render, BEFORE both
1257
+ // cell useMemos below consume the cache. (Clearing on mount is a harmless no-op.)
1258
+ useMemo(clearCache, [
1259
+ clearCache,
1260
+ itemRenderer,
1261
+ overlayRenderer,
1262
+ backgroundRenderer,
1263
+ ]);
1264
+ const mergedCellRenderMap = new Set();
1265
+ const mergedCellFrozenRowMap = new Set();
1266
+ // Get frozen column overflow cells - computed AFTER frozen cells useMemo
1267
+ // so it has data from current render (itemRenderer just populated overflowMapRef)
1268
+ const frozenColumnOverflowCells = getFrozenColumnOverflowCells?.(frozenColumnWidth, frozenColumns);
1269
+ // Frozen cells useMemo runs FIRST so itemRenderer populates overflowMapRef
1270
+ const [frozenRowCellBackgrounds, frozenColumnCellBackgrounds, frozenRowCells, frozenColumnCells, frozenRowCellOverlays, frozenColumnCellOverlays,] = useMemo(() => {
1271
+ /* Draw frozen rows */
1272
+ const frozenRowCells = [];
1273
+ const frozenRowCellOverlays = [];
1274
+ const frozenRowCellBackgrounds = [];
1275
+ for (let rowIndex = 1; rowIndex < Math.min(rowStopIndex, frozenRows); rowIndex++) {
1276
+ if (isHiddenRow?.(rowIndex))
1277
+ continue;
1278
+ /**
1279
+ * Do any pre-processing of the row before being renderered.
1280
+ * Useful for `react-table` to call `prepareRow(row)`
1281
+ */
1282
+ onBeforeRenderRow?.(rowIndex);
1283
+ for (let columnIndex = columnStartIndex; columnIndex <= columnStopIndex; columnIndex++) {
1284
+ /* Skip merged cells columns */
1285
+ if (columnIndex < frozenColumns &&
1286
+ !frozenColumnOverflowCells?.has(cellIdentifier(rowIndex, columnIndex))) {
1287
+ continue;
1288
+ }
1289
+ const isMerged = isMergedCell({ rowIndex, columnIndex });
1290
+ const bounds = getCellBounds({ rowIndex, columnIndex });
1291
+ const actualRowIndex = isMerged ? bounds.startRowIndex : rowIndex;
1292
+ const actualColumnIndex = isMerged
1293
+ ? bounds.startColumnIndex
1294
+ : columnIndex;
1295
+ const actualBottom = Math.max(rowIndex, bounds.endRowIndex);
1296
+ const actualRight = Math.max(columnIndex, bounds.endColumnIndex);
1297
+ if (isHiddenColumn?.(actualColumnIndex)) {
1298
+ continue;
1299
+ }
1300
+ if (!isMerged && isHiddenCell?.(actualRowIndex, actualColumnIndex)) {
1301
+ continue;
1302
+ }
1303
+ if (isMerged) {
1304
+ const cellId = cellIdentifier(bounds.startRowIndex, bounds.startColumnIndex);
1305
+ if (mergedCellFrozenRowMap.has(cellId)) {
1306
+ continue;
1307
+ }
1308
+ mergedCellFrozenRowMap.add(cellId);
1309
+ }
1310
+ const y = getRowOffset(actualRowIndex);
1311
+ const height = getRowOffset(actualBottom) - y + getRowHeight(actualBottom);
1312
+ const x = getColumnOffset(actualColumnIndex);
1313
+ const width = getColumnOffset(actualRight) - x + getColumnWidth(actualRight);
1314
+ const cellId = cellIdentifier(actualRowIndex, actualColumnIndex);
1315
+ const frozenRowCell = getCellCache(cellCacheRef.current, cellId, () => itemRenderer({
1316
+ x,
1317
+ y,
1318
+ width,
1319
+ height,
1320
+ rowIndex: actualRowIndex,
1321
+ columnIndex: actualColumnIndex,
1322
+ isMergedCell: isMerged,
1323
+ key: itemKey({
1324
+ rowIndex: actualRowIndex,
1325
+ columnIndex: actualColumnIndex,
1326
+ }),
1327
+ }), isScrolling, isMerged);
1328
+ frozenRowCells.push(frozenRowCell);
1329
+ if (enableCellOverlay &&
1330
+ !isHiddenOverlay?.(actualRowIndex, actualColumnIndex)) {
1331
+ const frozenRowCellOverlay = getCellCache(cellOverlayCacheRef.current, cellId, () => overlayRenderer({
1332
+ x,
1333
+ y,
1334
+ width,
1335
+ height,
1336
+ rowIndex: actualRowIndex,
1337
+ columnIndex: actualColumnIndex,
1338
+ isMergedCell: isMerged,
1339
+ key: itemKey({
1340
+ rowIndex: actualRowIndex,
1341
+ columnIndex: actualColumnIndex,
1342
+ }),
1343
+ }), isScrolling);
1344
+ frozenRowCellOverlays.push(frozenRowCellOverlay);
1345
+ }
1346
+ if (enableCellBackground &&
1347
+ !isHiddenBackground?.(actualRowIndex, actualColumnIndex)) {
1348
+ const frozenRowCellBackground = getCellCache(cellBackgroundCacheRef.current, cellId, () => backgroundRenderer?.({
1349
+ x,
1350
+ y,
1351
+ width,
1352
+ height,
1353
+ rowIndex: actualRowIndex,
1354
+ columnIndex: actualColumnIndex,
1355
+ isMergedCell: isMerged,
1356
+ key: itemKey({
1357
+ rowIndex: actualRowIndex,
1358
+ columnIndex: actualColumnIndex,
1359
+ }),
1360
+ }), isScrolling);
1361
+ frozenRowCellBackgrounds.push(frozenRowCellBackground);
1362
+ }
1363
+ }
1364
+ }
1365
+ /* Draw frozen columns */
1366
+ const mergedCellFrozenColumnMap = new Set();
1367
+ const frozenColumnCells = [];
1368
+ const frozenColumnCellOverlays = [];
1369
+ const frozenColumnCellBackgrounds = [];
1370
+ // Use getVisibleRowIndices for efficient iteration over visible rows
1371
+ const visibleRowsForFrozenCols = getVisibleRowIndices(rowStartIndex, rowStopIndex);
1372
+ for (const rowIndex of visibleRowsForFrozenCols) {
1373
+ for (let columnIndex = 1; columnIndex < Math.min(columnStopIndex, frozenColumns); columnIndex++) {
1374
+ const isMerged = isMergedCell({ rowIndex, columnIndex });
1375
+ const bounds = getCellBounds({ rowIndex, columnIndex });
1376
+ const actualRowIndex = isMerged ? bounds.startRowIndex : rowIndex;
1377
+ const actualColumnIndex = isMerged
1378
+ ? bounds.startColumnIndex
1379
+ : columnIndex;
1380
+ const actualBottom = Math.max(rowIndex, bounds.endRowIndex);
1381
+ const actualRight = Math.max(columnIndex, bounds.endColumnIndex);
1382
+ if (isHiddenColumn?.(actualColumnIndex)) {
1383
+ continue;
1384
+ }
1385
+ if (!isMerged && isHiddenCell?.(actualRowIndex, actualColumnIndex)) {
1386
+ continue;
1387
+ }
1388
+ if (isMerged) {
1389
+ const cellId = cellIdentifier(bounds.startRowIndex, bounds.startColumnIndex);
1390
+ if (mergedCellFrozenColumnMap.has(cellId)) {
1391
+ continue;
1392
+ }
1393
+ mergedCellFrozenColumnMap.add(cellId);
1394
+ }
1395
+ const y = getRowOffset(actualRowIndex);
1396
+ const height = getRowOffset(actualBottom) - y + getRowHeight(actualBottom);
1397
+ const x = getColumnOffset(actualColumnIndex);
1398
+ const width = getColumnOffset(actualRight) - x + getColumnWidth(actualRight);
1399
+ const cellId = cellIdentifier(actualRowIndex, actualColumnIndex);
1400
+ const frozenColumnCell = getCellCache(cellCacheRef.current, cellId, () => itemRenderer({
1401
+ x,
1402
+ y,
1403
+ width,
1404
+ height,
1405
+ rowIndex: actualRowIndex,
1406
+ columnIndex: actualColumnIndex,
1407
+ isMergedCell: isMerged,
1408
+ key: itemKey({
1409
+ rowIndex: actualRowIndex,
1410
+ columnIndex: actualColumnIndex,
1411
+ }),
1412
+ }), isScrolling, isMerged);
1413
+ frozenColumnCells.push(frozenColumnCell);
1414
+ if (enableCellOverlay &&
1415
+ !isHiddenOverlay?.(actualRowIndex, actualColumnIndex)) {
1416
+ const frozenColumnCellOverlay = getCellCache(cellOverlayCacheRef.current, cellId, () => overlayRenderer({
1417
+ x,
1418
+ y,
1419
+ width,
1420
+ height,
1421
+ rowIndex: actualRowIndex,
1422
+ columnIndex: actualColumnIndex,
1423
+ isMergedCell: isMerged,
1424
+ key: itemKey({
1425
+ rowIndex: actualRowIndex,
1426
+ columnIndex: actualColumnIndex,
1427
+ }),
1428
+ }), isScrolling);
1429
+ frozenColumnCellOverlays.push(frozenColumnCellOverlay);
1430
+ }
1431
+ if (enableCellBackground &&
1432
+ !isHiddenBackground?.(actualRowIndex, actualColumnIndex)) {
1433
+ const frozenColumnCellBackground = getCellCache(cellBackgroundCacheRef.current, cellId, () => backgroundRenderer?.({
1434
+ x,
1435
+ y,
1436
+ width,
1437
+ height,
1438
+ rowIndex: actualRowIndex,
1439
+ columnIndex: actualColumnIndex,
1440
+ isMergedCell: isMerged,
1441
+ key: itemKey({
1442
+ rowIndex: actualRowIndex,
1443
+ columnIndex: actualColumnIndex,
1444
+ }),
1445
+ }), isScrolling);
1446
+ frozenColumnCellBackgrounds.push(frozenColumnCellBackground);
1447
+ }
1448
+ }
1449
+ }
1450
+ return [
1451
+ frozenRowCellBackgrounds,
1452
+ frozenColumnCellBackgrounds,
1453
+ frozenRowCells,
1454
+ frozenColumnCells,
1455
+ frozenRowCellOverlays,
1456
+ frozenColumnCellOverlays,
1457
+ ];
1458
+ }, [
1459
+ rowStartIndex,
1460
+ rowStopIndex,
1461
+ columnStartIndex,
1462
+ columnStopIndex,
1463
+ isMergedCell,
1464
+ getCellBounds,
1465
+ getRowOffset,
1466
+ getRowHeight,
1467
+ getColumnOffset,
1468
+ getColumnWidth,
1469
+ isHiddenCell,
1470
+ isHiddenRow,
1471
+ isHiddenColumn,
1472
+ getVisibleRowIndices,
1473
+ itemRenderer,
1474
+ frozenRows,
1475
+ frozenColumns,
1476
+ enableCellOverlay,
1477
+ onBeforeRenderRow,
1478
+ isHiddenOverlay,
1479
+ overlayRenderer,
1480
+ isHiddenBackground,
1481
+ backgroundRenderer,
1482
+ enableCellBackground,
1483
+ getCellCache,
1484
+ isScrolling,
1485
+ frozenColumnOverflowCells,
1486
+ ]);
1487
+ const [cellBackgrounds, cells, cellOverlays] = useMemo(() => {
1488
+ /* Draw all cells */
1489
+ const cells = [];
1490
+ /**
1491
+ * Lets users draw cells on top of existing canvas
1492
+ */
1493
+ const cellOverlays = [];
1494
+ // Lets users draw layers of cells
1495
+ // 1. Background, 2. Text 3. Border
1496
+ const cellBackgrounds = [];
1497
+ if (columnCount > 0 && rowCount) {
1498
+ // Use getVisibleRowIndices for efficient iteration over visible rows
1499
+ const visibleRows = getVisibleRowIndices(rowStartIndex, rowStopIndex);
1500
+ for (const rowIndex of visibleRows) {
1501
+ for (let columnIndex = columnStartIndex; columnIndex <= columnStopIndex; columnIndex++) {
1502
+ /**
1503
+ * Skip frozen columns unless they overflow into the body area
1504
+ * Skip merged cells that are out of bounds
1505
+ */
1506
+ if (columnIndex < frozenColumns &&
1507
+ !frozenColumnOverflowCells?.has(cellIdentifier(rowIndex, columnIndex))) {
1508
+ continue;
1509
+ }
1510
+ const isMerged = isMergedCell({ rowIndex, columnIndex });
1511
+ const bounds = getCellBounds({ rowIndex, columnIndex });
1512
+ const actualRowIndex = isMerged ? bounds.startRowIndex : rowIndex;
1513
+ const actualColumnIndex = isMerged
1514
+ ? bounds.startColumnIndex
1515
+ : columnIndex;
1516
+ const actualBottom = Math.max(rowIndex, bounds.endRowIndex);
1517
+ const actualRight = Math.max(columnIndex, bounds.endColumnIndex);
1518
+ const cellId = cellIdentifier(bounds.startRowIndex, bounds.startColumnIndex);
1519
+ if (isHiddenColumn?.(actualColumnIndex)) {
1520
+ continue;
1521
+ }
1522
+ if (!isMerged &&
1523
+ isHiddenCell?.(actualRowIndex, actualColumnIndex)) {
1524
+ continue;
1525
+ }
1526
+ if (isMerged) {
1527
+ if (mergedCellRenderMap.has(cellId)) {
1528
+ continue;
1529
+ }
1530
+ mergedCellRenderMap.add(cellId);
1531
+ }
1532
+ const y = getRowOffset(actualRowIndex);
1533
+ const height = getRowOffset(actualBottom) - y + getRowHeight(actualBottom);
1534
+ const x = getColumnOffset(actualColumnIndex);
1535
+ const width = getColumnOffset(actualRight) - x + getColumnWidth(actualRight);
1536
+ const cell = getCellCache(cellCacheRef.current, cellId, () => itemRenderer({
1537
+ x,
1538
+ y,
1539
+ width,
1540
+ height,
1541
+ rowIndex: actualRowIndex,
1542
+ columnIndex: actualColumnIndex,
1543
+ isMergedCell: isMerged,
1544
+ key: itemKey({
1545
+ rowIndex: actualRowIndex,
1546
+ columnIndex: actualColumnIndex,
1547
+ }),
1548
+ }), isScrolling, isMerged);
1549
+ cells.push(cell);
1550
+ if (enableCellOverlay &&
1551
+ !isHiddenOverlay?.(actualRowIndex, actualColumnIndex)) {
1552
+ const cellOverlay = getCellCache(cellOverlayCacheRef.current, cellId, () => overlayRenderer({
1553
+ x,
1554
+ y,
1555
+ width,
1556
+ height,
1557
+ rowIndex: actualRowIndex,
1558
+ columnIndex: actualColumnIndex,
1559
+ isMergedCell: isMerged,
1560
+ key: itemKey({
1561
+ rowIndex: actualRowIndex,
1562
+ columnIndex: actualColumnIndex,
1563
+ }),
1564
+ }), isScrolling);
1565
+ cellOverlays.push(cellOverlay);
1566
+ }
1567
+ if (enableCellBackground &&
1568
+ !isHiddenBackground?.(actualRowIndex, actualColumnIndex)) {
1569
+ const cellBackground = getCellCache(cellBackgroundCacheRef.current, cellId, () => backgroundRenderer?.({
1570
+ x,
1571
+ y,
1572
+ width,
1573
+ height,
1574
+ rowIndex: actualRowIndex,
1575
+ columnIndex: actualColumnIndex,
1576
+ isMergedCell: isMerged,
1577
+ key: itemKey({
1578
+ rowIndex: actualRowIndex,
1579
+ columnIndex: actualColumnIndex,
1580
+ }),
1581
+ }), isScrolling);
1582
+ cellBackgrounds.push(cellBackground);
1583
+ }
1584
+ }
1585
+ }
1586
+ }
1587
+ return [cellBackgrounds, cells, cellOverlays];
1588
+ }, [
1589
+ rowStartIndex,
1590
+ rowStopIndex,
1591
+ columnStartIndex,
1592
+ columnStopIndex,
1593
+ isScrolling,
1594
+ isMergedCell,
1595
+ getCellBounds,
1596
+ getRowOffset,
1597
+ getRowHeight,
1598
+ getColumnOffset,
1599
+ getColumnWidth,
1600
+ isHiddenOverlay,
1601
+ itemRenderer,
1602
+ rowCount,
1603
+ columnCount,
1604
+ frozenColumns,
1605
+ getVisibleRowIndices,
1606
+ isHiddenColumn,
1607
+ isHiddenCell,
1608
+ enableCellOverlay,
1609
+ overlayRenderer,
1610
+ backgroundRenderer,
1611
+ enableCellBackground,
1612
+ isHiddenBackground,
1613
+ getCellCache,
1614
+ frozenColumnOverflowCells,
1615
+ ]);
1616
+ /**
1617
+ * Get x offset of a cell
1618
+ * takes into account frozen columns
1619
+ * and scoll
1620
+ */
1621
+ const getXOffset = useCallback((columnIndex) => {
1622
+ const isInFrozenColumn = columnIndex < frozenColumns;
1623
+ return (getColumnOffset(columnIndex) - (isInFrozenColumn ? 0 : scrollLeft));
1624
+ }, [getColumnOffset, scrollLeft, frozenColumns]);
1625
+ /**
1626
+ * Get y offset of a cell
1627
+ * takes into account frozen columns
1628
+ * and scoll
1629
+ */
1630
+ const getYOffset = useCallback((rowIndex) => {
1631
+ const isInFrozenRow = rowIndex < frozenRows;
1632
+ return getRowOffset(rowIndex) - (isInFrozenRow ? 0 : scrollTop);
1633
+ }, [getRowOffset, scrollTop, frozenRows]);
1634
+ // Frozen header cells
1635
+ const [frozenRowHeaderCells, frozenColumnHeaderCells] = useMemo(() => {
1636
+ /* Draw frozen rows */
1637
+ const frozenRowHeaderCells = [];
1638
+ const frozenColumnHeaderCells = [];
1639
+ for (let rowIndex = 0; rowIndex < 1; rowIndex++) {
1640
+ for (let columnIndex = columnStartIndex; columnIndex <= columnStopIndex; columnIndex++) {
1641
+ const y = getRowOffset(rowIndex);
1642
+ const height = getRowOffset(rowIndex) - y + getRowHeight(rowIndex);
1643
+ const x = getColumnOffset(columnIndex);
1644
+ const width = getColumnOffset(columnIndex) - x + getColumnWidth(columnIndex);
1645
+ frozenColumnHeaderCells.push(headerRenderer({
1646
+ x,
1647
+ y,
1648
+ width,
1649
+ height,
1650
+ rowIndex,
1651
+ columnIndex,
1652
+ key: itemKey({
1653
+ rowIndex,
1654
+ columnIndex,
1655
+ }),
1656
+ }));
1657
+ }
1658
+ }
1659
+ /* Draw frozen columns (row headers) */
1660
+ // Use getVisibleRowIndices for efficient iteration
1661
+ const visibleRowsForHeaders = getVisibleRowIndices(rowStartIndex, rowStopIndex, false);
1662
+ for (const rowIndex of visibleRowsForHeaders) {
1663
+ for (let columnIndex = 0; columnIndex < 1; columnIndex++) {
1664
+ const y = getRowOffset(rowIndex);
1665
+ const height = getRowOffset(rowIndex) - y + getRowHeight(rowIndex);
1666
+ const x = getColumnOffset(columnIndex);
1667
+ const width = getColumnOffset(columnIndex) - x + getColumnWidth(columnIndex);
1668
+ frozenRowHeaderCells.push(headerRenderer({
1669
+ x,
1670
+ y,
1671
+ width,
1672
+ height,
1673
+ rowIndex,
1674
+ columnIndex,
1675
+ key: itemKey({
1676
+ rowIndex,
1677
+ columnIndex,
1678
+ }),
1679
+ }));
1680
+ }
1681
+ }
1682
+ return [frozenRowHeaderCells, frozenColumnHeaderCells];
1683
+ }, [
1684
+ rowStartIndex,
1685
+ rowStopIndex,
1686
+ columnStartIndex,
1687
+ columnStopIndex,
1688
+ getRowOffset,
1689
+ getRowHeight,
1690
+ getColumnOffset,
1691
+ getColumnWidth,
1692
+ headerRenderer,
1693
+ getVisibleRowIndices,
1694
+ ]);
1695
+ /**
1696
+ * Frozen column shadow
1697
+ */
1698
+ const frozenColumnShadow = useMemo(() => {
1699
+ const frozenColumnLineX = getColumnOffset(frozenColumns);
1700
+ const frozenColumnLineY = getRowOffset(Math.min(rowStopIndex + 1, rowCount));
1701
+ return (React.createElement(Line, { points: [frozenColumnLineX, 0, frozenColumnLineX, frozenColumnLineY], offsetX: -0.5, strokeWidth: 1, shadowForStrokeEnabled: false, strokeScaleEnabled: false, hitStrokeWidth: 0, listening: false, perfectDrawEnabled: false, ...shadowSettings }));
1702
+ }, [
1703
+ shadowSettings,
1704
+ frozenColumns,
1705
+ rowStopIndex,
1706
+ rowCount,
1707
+ getRowOffset,
1708
+ getColumnOffset,
1709
+ ]);
1710
+ /**
1711
+ * Frozen row shadow
1712
+ */
1713
+ const frozenRowShadow = useMemo(() => {
1714
+ const frozenRowLineY = getRowOffset(frozenRows);
1715
+ const frozenRowLineX = getColumnOffset(Math.min(columnStopIndex + 1, columnCount));
1716
+ return (React.createElement(Line, { points: [0, frozenRowLineY, frozenRowLineX, frozenRowLineY], offsetY: -0.5, strokeWidth: 1, shadowForStrokeEnabled: false, strokeScaleEnabled: false, hitStrokeWidth: 0, listening: false, perfectDrawEnabled: false, ...shadowSettings }));
1717
+ }, [
1718
+ shadowSettings,
1719
+ frozenRows,
1720
+ columnStopIndex,
1721
+ columnCount,
1722
+ getRowOffset,
1723
+ getColumnOffset,
1724
+ ]);
1725
+ /* Draw frozen intersection cells */
1726
+ const [frozenIntersectionCellBackgrounds, frozenIntersectionCells, frozenIntersectionCellOverlays,] = useMemo(() => {
1727
+ const frozenIntersectionCellBackgrounds = [];
1728
+ const frozenIntersectionCells = [];
1729
+ const frozenIntersectionCellOverlays = [];
1730
+ for (let rowIndex = 1; rowIndex < Math.min(rowStopIndex, frozenRows); rowIndex++) {
1731
+ if (isHiddenRow?.(rowIndex))
1732
+ continue;
1733
+ for (let columnIndex = 1; columnIndex < Math.min(columnStopIndex, frozenColumns); columnIndex++) {
1734
+ const isMerged = isMergedCell({ rowIndex, columnIndex });
1735
+ const bounds = getCellBounds({ rowIndex, columnIndex });
1736
+ const actualRowIndex = isMerged ? bounds.startRowIndex : rowIndex;
1737
+ const actualColumnIndex = isMerged
1738
+ ? bounds.startColumnIndex
1739
+ : columnIndex;
1740
+ const actualBottom = Math.max(rowIndex, bounds.endRowIndex);
1741
+ const actualRight = Math.max(columnIndex, bounds.endColumnIndex);
1742
+ if (isHiddenColumn?.(actualColumnIndex)) {
1743
+ continue;
1744
+ }
1745
+ if (!isMerged && isHiddenCell?.(actualRowIndex, actualColumnIndex)) {
1746
+ continue;
1747
+ }
1748
+ if (isMerged) {
1749
+ const cellId = cellIdentifier(bounds.startRowIndex, bounds.startColumnIndex);
1750
+ if (mergedCellRenderMap.has(cellId)) {
1751
+ continue;
1752
+ }
1753
+ mergedCellRenderMap.add(cellId);
1754
+ }
1755
+ const y = getRowOffset(actualRowIndex);
1756
+ const height = getRowOffset(actualBottom) - y + getRowHeight(actualBottom);
1757
+ const x = getColumnOffset(actualColumnIndex);
1758
+ const width = getColumnOffset(actualRight) - x + getColumnWidth(actualRight);
1759
+ frozenIntersectionCells.push(itemRenderer({
1760
+ x,
1761
+ y,
1762
+ width,
1763
+ height,
1764
+ rowIndex: actualRowIndex,
1765
+ columnIndex: actualColumnIndex,
1766
+ isMergedCell: isMerged,
1767
+ key: itemKey({
1768
+ rowIndex: actualRowIndex,
1769
+ columnIndex: actualColumnIndex,
1770
+ }),
1771
+ }));
1772
+ if (enableCellOverlay &&
1773
+ !isHiddenOverlay?.(actualRowIndex, actualColumnIndex)) {
1774
+ frozenIntersectionCellOverlays.push(overlayRenderer({
1775
+ x,
1776
+ y,
1777
+ width,
1778
+ height,
1779
+ rowIndex: actualRowIndex,
1780
+ columnIndex: actualColumnIndex,
1781
+ isMergedCell: isMerged,
1782
+ key: itemKey({
1783
+ rowIndex: actualRowIndex,
1784
+ columnIndex: actualColumnIndex,
1785
+ }),
1786
+ }));
1787
+ }
1788
+ if (enableCellBackground &&
1789
+ !isHiddenBackground?.(actualRowIndex, actualColumnIndex)) {
1790
+ frozenIntersectionCellBackgrounds.push(backgroundRenderer?.({
1791
+ x,
1792
+ y,
1793
+ width,
1794
+ height,
1795
+ rowIndex: actualRowIndex,
1796
+ columnIndex: actualColumnIndex,
1797
+ isMergedCell: isMerged,
1798
+ key: itemKey({
1799
+ rowIndex: actualRowIndex,
1800
+ columnIndex: actualColumnIndex,
1801
+ }),
1802
+ }));
1803
+ }
1804
+ }
1805
+ }
1806
+ return [
1807
+ frozenIntersectionCellBackgrounds,
1808
+ frozenIntersectionCells,
1809
+ frozenIntersectionCellOverlays,
1810
+ ];
1811
+ }, [
1812
+ rowStopIndex,
1813
+ columnStopIndex,
1814
+ isMergedCell,
1815
+ getCellBounds,
1816
+ getRowOffset,
1817
+ getRowHeight,
1818
+ getColumnOffset,
1819
+ getColumnWidth,
1820
+ itemRenderer,
1821
+ frozenRows,
1822
+ frozenColumns,
1823
+ enableCellOverlay,
1824
+ isHiddenRow,
1825
+ isHiddenColumn,
1826
+ isHiddenCell,
1827
+ isHiddenOverlay,
1828
+ isHiddenBackground,
1829
+ enableCellBackground,
1830
+ backgroundRenderer,
1831
+ overlayRenderer,
1832
+ ]);
1833
+ /* Draw frozen intersection cells */
1834
+ const [frozenIntersectionRowHeaderCells, frozenIntersectionColumnHeaderCells,] = useMemo(() => {
1835
+ const frozenIntersectionColumnHeaderCells = [];
1836
+ const frozenIntersectionRowHeaderCells = [];
1837
+ for (let rowIndex = 0; rowIndex < 1; rowIndex++) {
1838
+ if (isHiddenRow?.(rowIndex))
1839
+ continue;
1840
+ for (let columnIndex = 0; columnIndex < Math.min(columnStopIndex, frozenColumns); columnIndex++) {
1841
+ if (isHiddenColumn?.(columnIndex)) {
1842
+ continue;
1843
+ }
1844
+ const y = getRowOffset(rowIndex);
1845
+ const height = getRowOffset(rowIndex) - y + getRowHeight(rowIndex);
1846
+ const x = getColumnOffset(columnIndex);
1847
+ const width = getColumnOffset(columnIndex) - x + getColumnWidth(columnIndex);
1848
+ frozenIntersectionColumnHeaderCells.push(headerRenderer({
1849
+ x,
1850
+ y,
1851
+ width,
1852
+ height,
1853
+ rowIndex,
1854
+ columnIndex,
1855
+ key: itemKey({
1856
+ rowIndex,
1857
+ columnIndex,
1858
+ }),
1859
+ }));
1860
+ }
1861
+ }
1862
+ for (let rowIndex = 0; rowIndex < Math.min(rowStopIndex, frozenRows); rowIndex++) {
1863
+ if (isHiddenRow?.(rowIndex))
1864
+ continue;
1865
+ for (let columnIndex = 0; columnIndex < 1; columnIndex++) {
1866
+ if (isHiddenColumn?.(columnIndex)) {
1867
+ continue;
1868
+ }
1869
+ const y = getRowOffset(rowIndex);
1870
+ const height = getRowOffset(rowIndex) - y + getRowHeight(rowIndex);
1871
+ const x = getColumnOffset(columnIndex);
1872
+ const width = getColumnOffset(columnIndex) - x + getColumnWidth(columnIndex);
1873
+ frozenIntersectionRowHeaderCells.push(headerRenderer({
1874
+ x,
1875
+ y,
1876
+ width,
1877
+ height,
1878
+ rowIndex,
1879
+ columnIndex,
1880
+ key: itemKey({
1881
+ rowIndex,
1882
+ columnIndex,
1883
+ }),
1884
+ }));
1885
+ }
1886
+ }
1887
+ return [
1888
+ frozenIntersectionRowHeaderCells,
1889
+ frozenIntersectionColumnHeaderCells,
1890
+ ];
1891
+ }, [
1892
+ rowStopIndex,
1893
+ columnStopIndex,
1894
+ frozenColumns,
1895
+ frozenRows,
1896
+ isHiddenColumn,
1897
+ isHiddenRow,
1898
+ getRowOffset,
1899
+ getRowHeight,
1900
+ getColumnOffset,
1901
+ getColumnWidth,
1902
+ headerRenderer,
1903
+ ]);
1904
+ /**
1905
+ * Renders active cell
1906
+ */
1907
+ const [fillHandleDimension, activeCellSelection, activeCellSelectionFrozenColumn, activeCellSelectionFrozenRow, activeCellSelectionFrozenIntersection,] = useMemo(() => {
1908
+ const fillHandleDimension = {};
1909
+ let activeCellSelection = null;
1910
+ let activeCellSelectionFrozenColumn = null;
1911
+ let activeCellSelectionFrozenRow = null;
1912
+ let activeCellSelectionFrozenIntersection = null;
1913
+ if (activeCell) {
1914
+ const bounds = getCellBounds(activeCell);
1915
+ const { startRowIndex: top, startColumnIndex: left, endColumnIndex: right, endRowIndex: bottom, } = bounds;
1916
+ const actualBottom = Math.min(rowStopIndex, bottom);
1917
+ const actualRight = Math.min(columnStopIndex, right);
1918
+ const isInFrozenColumn = left < frozenColumns;
1919
+ const isInFrozenRow = top < frozenRows;
1920
+ const isInFrozenIntersection = isInFrozenRow && isInFrozenColumn;
1921
+ const isInBody = actualBottom >= frozenRows || actualRight >= frozenColumns;
1922
+ const isMerged = isMergedCell(activeCell);
1923
+ const y = getYOffset(top);
1924
+ const height = getYOffset(actualBottom) - y + getRowHeight(actualBottom);
1925
+ const x = getXOffset(left);
1926
+ const width = getXOffset(actualRight) - x + getColumnWidth(actualRight);
1927
+ const cell = selectionRenderer({
1928
+ stroke: selectionBorderColor,
1929
+ strokeWidth: activeCellStrokeWidth,
1930
+ fill: "transparent",
1931
+ x: x,
1932
+ y: y,
1933
+ width: width,
1934
+ height: height,
1935
+ type: "activeCell",
1936
+ index: 0,
1937
+ activeCell,
1938
+ isDragging: isDraggingSelection,
1939
+ /* Active cell is draggable only there are no other selections */
1940
+ draggable: enableSelectionDrag && selections.length === 0,
1941
+ });
1942
+ // Active cell is hidden when xuser is dragging selections
1943
+ if (!(isDraggingSelection && selections.length > 0)) {
1944
+ // Merged cell extends to main body
1945
+ if (isInFrozenIntersection) {
1946
+ activeCellSelectionFrozenIntersection = cell;
1947
+ // Set fill handle position
1948
+ fillHandleDimension.position = "fi";
1949
+ }
1950
+ else if (isInFrozenRow) {
1951
+ activeCellSelectionFrozenRow = cell;
1952
+ // Set fill handle position
1953
+ fillHandleDimension.position = "fr";
1954
+ }
1955
+ else if (isInFrozenColumn) {
1956
+ activeCellSelectionFrozenColumn = cell;
1957
+ // Set fill handle position
1958
+ fillHandleDimension.position = "fc";
1959
+ }
1960
+ else {
1961
+ activeCellSelection = cell;
1962
+ }
1963
+ if (isMerged && isInBody) {
1964
+ activeCellSelection = cell;
1965
+ }
1966
+ }
1967
+ fillHandleDimension.x = x + width;
1968
+ fillHandleDimension.y = y + height;
1969
+ }
1970
+ return [
1971
+ fillHandleDimension,
1972
+ activeCellSelection,
1973
+ activeCellSelectionFrozenColumn,
1974
+ activeCellSelectionFrozenRow,
1975
+ activeCellSelectionFrozenIntersection,
1976
+ ];
1977
+ }, [
1978
+ getXOffset,
1979
+ getYOffset,
1980
+ activeCell,
1981
+ frozenRows,
1982
+ frozenColumns,
1983
+ activeCellStrokeWidth,
1984
+ columnStopIndex,
1985
+ enableSelectionDrag,
1986
+ isDraggingSelection,
1987
+ rowStopIndex,
1988
+ selectionBorderColor,
1989
+ selections.length,
1990
+ getColumnWidth,
1991
+ getRowHeight,
1992
+ getCellBounds,
1993
+ isMergedCell,
1994
+ selectionRenderer,
1995
+ ]);
1996
+ /**
1997
+ * Convert selections to area
1998
+ * Removed useMemo as changes to lastMeasureRowIndex, lastMeasuredColumnIndex,
1999
+ * does not trigger useMemo
2000
+ * Dependencies : [selections, rowStopIndex, columnStopIndex, instanceProps]
2001
+ */
2002
+ let isSelectionInProgress = false;
2003
+ const [selectionAreas, selectionAreasFrozenColumns, selectionAreasFrozenRows, selectionAreasIntersection,] = useMemo(() => {
2004
+ const selectionAreas = [];
2005
+ const selectionAreasFrozenColumns = [];
2006
+ const selectionAreasFrozenRows = [];
2007
+ const selectionAreasIntersection = [];
2008
+ for (let i = 0; i < selections.length; i++) {
2009
+ const selection = selections[i];
2010
+ const { range, inProgress, style } = selection;
2011
+ const { startRowIndex: top, startColumnIndex: left, endColumnIndex: right, endRowIndex: bottom, } = range;
2012
+ // Detect full row/column selections - don't expand to merged cells
2013
+ // to prevent selecting adjacent rows/columns due to merged cells at edges
2014
+ const isFullRowSelection = right >= columnCount - 1;
2015
+ const isFullColumnSelection = bottom >= rowCount - 1;
2016
+ const spanMerges = !(isFullRowSelection || isFullColumnSelection);
2017
+ const bounds = getCellBounds({ rowIndex: top, columnIndex: left }, spanMerges);
2018
+ const selectionBounds = { x: 0, y: 0, width: 0, height: 0 };
2019
+ const actualBottom = Math.max(bounds.endRowIndex, Math.min(rowStopIndex, bottom));
2020
+ const actualRight = Math.max(bounds.endColumnIndex, Math.min(columnStopIndex, right));
2021
+ const actualLeft = Math.min(bounds.startColumnIndex, left);
2022
+ const isLeftBoundFrozen = left < frozenColumns;
2023
+ const isTopBoundFrozen = top < frozenRows;
2024
+ const isRightBoundFrozen = right < frozenColumns;
2025
+ const isBottomBoundFrozen = bottom < frozenRows;
2026
+ const isIntersectionFrozen = top < frozenRows && left < frozenColumns;
2027
+ const isLast = i === selections.length - 1;
2028
+ const styles = {
2029
+ stroke: inProgress ? selectionBackgroundColor : selectionBorderColor,
2030
+ fill: selectionBackgroundColor,
2031
+ strokeWidth: selectionStrokeWidth,
2032
+ isDragging: isDraggingSelection,
2033
+ draggable: inProgress ? false : enableSelectionDrag,
2034
+ ...style,
2035
+ };
2036
+ /**
2037
+ * If selection is in progress,
2038
+ * use this variable to hide fill handle
2039
+ */
2040
+ if (inProgress) {
2041
+ isSelectionInProgress = true;
2042
+ }
2043
+ selectionBounds.y = Math.max(0, getYOffset(top));
2044
+ selectionBounds.height = Math.max(0, getYOffset(actualBottom) -
2045
+ selectionBounds.y +
2046
+ getRowHeight(actualBottom));
2047
+ selectionBounds.x = Math.max(0, getXOffset(actualLeft));
2048
+ selectionBounds.width =
2049
+ getXOffset(actualRight) -
2050
+ selectionBounds.x +
2051
+ getColumnWidth(actualRight);
2052
+ if (isLeftBoundFrozen) {
2053
+ const frozenColumnSelectionWidth = getColumnOffset(Math.min(right + 1, frozenColumns)) -
2054
+ getColumnOffset(left);
2055
+ selectionAreasFrozenColumns.push(selectionRenderer({
2056
+ ...styles,
2057
+ type: "selection",
2058
+ index: i,
2059
+ x: selectionBounds.x,
2060
+ y: selectionBounds.y,
2061
+ width: frozenColumnSelectionWidth,
2062
+ height: selectionBounds.height,
2063
+ // To counter selections that span frozen + floating cells
2064
+ strokeRightWidth: frozenColumnSelectionWidth === selectionBounds.width
2065
+ ? undefined // Take it from strokeWidth
2066
+ : 0,
2067
+ selection,
2068
+ inProgress,
2069
+ }));
2070
+ }
2071
+ if (isTopBoundFrozen) {
2072
+ const frozenRowSelectionHeight = getRowOffset(Math.min(bottom + 1, frozenRows)) - getRowOffset(top);
2073
+ selectionAreasFrozenRows.push(selectionRenderer({
2074
+ ...styles,
2075
+ type: "selection",
2076
+ index: i,
2077
+ x: selectionBounds.x,
2078
+ y: selectionBounds.y,
2079
+ width: selectionBounds.width,
2080
+ height: frozenRowSelectionHeight,
2081
+ strokeBottomWidth: frozenRowSelectionHeight === selectionBounds.height
2082
+ ? undefined // Take it from strokeWidth
2083
+ : 0,
2084
+ selection,
2085
+ inProgress,
2086
+ }));
2087
+ }
2088
+ if (isIntersectionFrozen) {
2089
+ const frozenIntersectionSelectionHeight = getRowOffset(Math.min(bottom + 1, frozenRows)) - getRowOffset(top);
2090
+ const frozenIntersectionSelectionWidth = getColumnOffset(Math.min(right + 1, frozenColumns)) -
2091
+ getColumnOffset(left);
2092
+ selectionAreasIntersection.push(selectionRenderer({
2093
+ ...styles,
2094
+ type: "selection",
2095
+ index: i,
2096
+ x: selectionBounds.x,
2097
+ y: selectionBounds.y,
2098
+ width: frozenIntersectionSelectionWidth,
2099
+ height: frozenIntersectionSelectionHeight,
2100
+ strokeBottomWidth: frozenIntersectionSelectionHeight === selectionBounds.height &&
2101
+ !isDraggingSelection
2102
+ ? undefined // Take it from strokeWidth
2103
+ : 0,
2104
+ strokeRightWidth: frozenIntersectionSelectionWidth === selectionBounds.width &&
2105
+ !isDraggingSelection
2106
+ ? undefined // Take it from strokeWidth
2107
+ : 0,
2108
+ selection,
2109
+ inProgress,
2110
+ }));
2111
+ }
2112
+ selectionAreas.push(selectionRenderer({
2113
+ ...styles,
2114
+ type: "selection",
2115
+ index: i,
2116
+ x: selectionBounds.x,
2117
+ y: selectionBounds.y,
2118
+ width: selectionBounds.width,
2119
+ height: selectionBounds.height,
2120
+ selection,
2121
+ inProgress,
2122
+ }));
2123
+ if (isLast) {
2124
+ fillHandleDimension.x = selectionBounds.x + selectionBounds.width;
2125
+ fillHandleDimension.y = selectionBounds.y + selectionBounds.height;
2126
+ // Set fill handle position
2127
+ fillHandleDimension.position = isIntersectionFrozen
2128
+ ? "fi"
2129
+ : isRightBoundFrozen
2130
+ ? "fc"
2131
+ : isBottomBoundFrozen
2132
+ ? "fr"
2133
+ : undefined;
2134
+ }
2135
+ }
2136
+ return [
2137
+ selectionAreas,
2138
+ selectionAreasFrozenColumns,
2139
+ selectionAreasFrozenRows,
2140
+ selectionAreasIntersection,
2141
+ ];
2142
+ }, [
2143
+ getXOffset,
2144
+ getYOffset,
2145
+ selections,
2146
+ columnStopIndex,
2147
+ rowStopIndex,
2148
+ frozenRows,
2149
+ frozenColumns,
2150
+ isDraggingSelection,
2151
+ selectionRenderer,
2152
+ getColumnOffset,
2153
+ getColumnWidth,
2154
+ getRowOffset,
2155
+ getRowHeight,
2156
+ getCellBounds,
2157
+ ]);
2158
+ /**
2159
+ * Fillselection
2160
+ */
2161
+ let fillSelections = null;
2162
+ if (fillSelection) {
2163
+ const { range } = fillSelection;
2164
+ const { startRowIndex: top, startColumnIndex: left, endColumnIndex: right, endRowIndex: bottom, } = range;
2165
+ const actualBottom = Math.min(rowStopIndex, bottom);
2166
+ const actualRight = Math.min(columnStopIndex, right);
2167
+ const x = getXOffset(left);
2168
+ const y = getYOffset(top);
2169
+ const height = getYOffset(actualBottom) - y + getRowHeight(actualBottom);
2170
+ const width = getXOffset(actualRight) - x + getColumnWidth(actualRight);
2171
+ fillSelections = selectionRenderer({
2172
+ type: "fill",
2173
+ x,
2174
+ y,
2175
+ width,
2176
+ height,
2177
+ index: -1,
2178
+ stroke: "gray",
2179
+ strokeStyle: "dashed",
2180
+ strokeWidth: 1,
2181
+ });
2182
+ }
2183
+ const [borderStyleCells, borderStyleCellsFrozenColumns, borderStyleCellsFrozenRows, borderStyleCellsIntersection,] = useMemo(() => {
2184
+ const borderStyleCells = [];
2185
+ const borderStyleCellsFrozenColumns = [];
2186
+ const borderStyleCellsFrozenRows = [];
2187
+ const borderStyleCellsIntersection = [];
2188
+ for (let i = 0; i < borderStyles.length; i++) {
2189
+ const { range, style, type = "border", index: key, title, ...rest } = borderStyles[i];
2190
+ const { startRowIndex: top, startColumnIndex: left, endColumnIndex: right, endRowIndex: bottom, } = range;
2191
+ // Ignore out of bounds
2192
+ if (top > rowStopIndex || bottom < rowStartIndex) {
2193
+ // continue;
2194
+ }
2195
+ const isLeftBoundFrozen = left < frozenColumns;
2196
+ const isTopBoundFrozen = top < frozenRows;
2197
+ const isIntersectionFrozen = top < frozenRows && left < frozenColumns;
2198
+ /**
2199
+ * A border style renders a copy into every pane its range touches,
2200
+ * each clipped by that pane's overflow container. The title must
2201
+ * appear exactly once — otherwise the copies clipped at a frozen
2202
+ * boundary show a duplicate, half-cut title that slides with the
2203
+ * scroll. It goes to the single pane that fully contains the range
2204
+ * (intersection → frozen columns → frozen rows → body).
2205
+ */
2206
+ const isFullyFrozenColumns = right < frozenColumns;
2207
+ const isFullyFrozenRows = bottom < frozenRows;
2208
+ const x = getXOffset(left);
2209
+ const y = getYOffset(top);
2210
+ const width = Math.max(0, getXOffset(Math.min(columnCount, right + 1)) - x);
2211
+ const height = Math.max(0, getYOffset(bottom) - y + getRowHeight(bottom));
2212
+ const startsOnFirstBodyRow = frozenRows > 0 && top === frozenRows;
2213
+ borderStyleCells.push(selectionRenderer({
2214
+ ...rest,
2215
+ ...style,
2216
+ title: isFullyFrozenColumns || isFullyFrozenRows ? undefined : title,
2217
+ flipTitleToBottom: startsOnFirstBodyRow,
2218
+ range,
2219
+ x,
2220
+ y,
2221
+ index: key ?? i,
2222
+ width,
2223
+ height,
2224
+ type,
2225
+ }));
2226
+ if (isLeftBoundFrozen) {
2227
+ const frozenColumnSelectionWidth = getColumnOffset(Math.min(right + 1, frozenColumns)) -
2228
+ getColumnOffset(left);
2229
+ borderStyleCellsFrozenColumns.push(selectionRenderer({
2230
+ ...rest,
2231
+ ...style,
2232
+ title: isFullyFrozenColumns && !isFullyFrozenRows ? title : undefined,
2233
+ keepTitleWithinBounds: true,
2234
+ flipTitleToBottom: startsOnFirstBodyRow,
2235
+ range,
2236
+ type,
2237
+ x,
2238
+ y,
2239
+ index: key ?? i,
2240
+ width: frozenColumnSelectionWidth,
2241
+ height,
2242
+ strokeRightWidth: frozenColumnSelectionWidth === width
2243
+ ? style?.strokeRightWidth || style?.strokeWidth
2244
+ : 0,
2245
+ }));
2246
+ }
2247
+ if (isTopBoundFrozen) {
2248
+ const frozenRowSelectionHeight = getRowOffset(Math.min(bottom + 1, frozenRows)) - getRowOffset(top);
2249
+ borderStyleCellsFrozenRows.push(selectionRenderer({
2250
+ ...rest,
2251
+ ...style,
2252
+ title: isFullyFrozenRows && !isFullyFrozenColumns ? title : undefined,
2253
+ keepTitleWithinVerticalBounds: true,
2254
+ range,
2255
+ type,
2256
+ x,
2257
+ y,
2258
+ index: key ?? i,
2259
+ width,
2260
+ height: frozenRowSelectionHeight,
2261
+ strokeBottomWidth: frozenRowSelectionHeight === height
2262
+ ? style?.strokeBottomWidth || style?.strokeWidth
2263
+ : 0,
2264
+ }));
2265
+ }
2266
+ if (isIntersectionFrozen) {
2267
+ const frozenIntersectionSelectionHeight = getRowOffset(Math.min(bottom + 1, frozenRows)) - getRowOffset(top);
2268
+ const frozenIntersectionSelectionWidth = getColumnOffset(Math.min(right + 1, frozenColumns)) -
2269
+ getColumnOffset(left);
2270
+ borderStyleCellsIntersection.push(selectionRenderer({
2271
+ ...rest,
2272
+ ...style,
2273
+ title: isFullyFrozenColumns && isFullyFrozenRows ? title : undefined,
2274
+ keepTitleWithinBounds: true,
2275
+ keepTitleWithinVerticalBounds: true,
2276
+ range,
2277
+ type,
2278
+ x,
2279
+ y,
2280
+ index: key ?? i,
2281
+ width: frozenIntersectionSelectionWidth,
2282
+ height: frozenIntersectionSelectionHeight,
2283
+ strokeBottomWidth: frozenIntersectionSelectionHeight === height
2284
+ ? selectionStrokeWidth
2285
+ : 0,
2286
+ strokeRightWidth: frozenIntersectionSelectionWidth === width
2287
+ ? selectionStrokeWidth
2288
+ : 0,
2289
+ }));
2290
+ }
2291
+ }
2292
+ return [
2293
+ borderStyleCells,
2294
+ borderStyleCellsFrozenColumns,
2295
+ borderStyleCellsFrozenRows,
2296
+ borderStyleCellsIntersection,
2297
+ ];
2298
+ }, [
2299
+ getXOffset,
2300
+ getYOffset,
2301
+ borderStyles,
2302
+ columnCount,
2303
+ selectionStrokeWidth,
2304
+ frozenRows,
2305
+ frozenColumns,
2306
+ selectionRenderer,
2307
+ getColumnOffset,
2308
+ getRowOffset,
2309
+ getRowHeight,
2310
+ rowStartIndex,
2311
+ rowStopIndex,
2312
+ ]);
2313
+ /**
2314
+ * Prevents drawing hit region when scrolling
2315
+ */
2316
+ const listenToEvents = !isScrolling;
2317
+ /* Frozen row shadow */
2318
+ const frozenRowShadowComponent = showFrozenRowShadow && frozenRows !== 1 ? frozenRowShadow : null;
2319
+ /* Frozen column shadow */
2320
+ const frozenColumnShadowComponent = showFrozenColumnShadow && frozenColumns !== 1 ? frozenColumnShadow : null;
2321
+ const fillHandleWidth = fillHandleSize;
2322
+ const fillhandleComponent = showFillHandle && !isSelectionInProgress ? (React.createElement(FillHandle, { ...fillHandleDimension, stroke: fillHandleBackgroundColor, size: fillHandleWidth, borderColor: fillhandleBorderColor, onMouseDown: onFillHandleMouseDown, onDoubleClick: onFillHandleDoubleClick })) : null;
2323
+ const selectionChildren = (React.createElement("div", { className: "pointer-events-none" },
2324
+ React.createElement("div", { className: "absolute right-0 bottom-0 overflow-hidden", style: {
2325
+ left: frozenColumnWidth,
2326
+ top: frozenRowHeight,
2327
+ } },
2328
+ React.createElement("div", { style: {
2329
+ transform: `translate(-${frozenColumnWidth}px, -${frozenRowHeight}px)`,
2330
+ } },
2331
+ activeCellSelection,
2332
+ selectionAreas,
2333
+ borderStyleCells,
2334
+ fillhandleComponent,
2335
+ fillSelections)),
2336
+ frozenColumns ? (React.createElement("div", { className: "absolute left-0 bottom-0 overflow-hidden", style: {
2337
+ top: frozenRowHeight,
2338
+ width: frozenColumnWidth + fillHandleWidth,
2339
+ } },
2340
+ React.createElement("div", { style: {
2341
+ transform: `translate(0, -${frozenRowHeight}px)`,
2342
+ } },
2343
+ activeCellSelectionFrozenColumn,
2344
+ selectionAreasFrozenColumns,
2345
+ borderStyleCellsFrozenColumns,
2346
+ fillHandleDimension.position === "fc"
2347
+ ? fillhandleComponent
2348
+ : null,
2349
+ fillSelections))) : null,
2350
+ frozenRows ? (React.createElement("div", { className: "absolute top-0 right-0 overflow-hidden", style: {
2351
+ left: frozenColumnWidth,
2352
+ height: frozenRowHeight + fillHandleWidth,
2353
+ } },
2354
+ React.createElement("div", { style: {
2355
+ transform: `translate(-${frozenColumnWidth}px, 0)`,
2356
+ } },
2357
+ activeCellSelectionFrozenRow,
2358
+ selectionAreasFrozenRows,
2359
+ borderStyleCellsFrozenRows,
2360
+ fillHandleDimension.position === "fr"
2361
+ ? fillhandleComponent
2362
+ : null,
2363
+ fillSelections))) : null,
2364
+ frozenRows && frozenColumns ? (React.createElement("div", { className: "absolute left-0 top-0 overflow-hidden pointer-events-none", style: {
2365
+ height: frozenRowHeight + fillHandleWidth,
2366
+ width: frozenColumnWidth + fillHandleWidth,
2367
+ } },
2368
+ activeCellSelectionFrozenIntersection,
2369
+ selectionAreasIntersection,
2370
+ borderStyleCellsIntersection,
2371
+ fillHandleDimension.position === "fi" ? fillhandleComponent : null,
2372
+ fillSelections)) : null));
2373
+ /**
2374
+ * Context value of grid children
2375
+ */
2376
+ const contextValue = useMemo(() => ({
2377
+ getCellOffsetFromCoords,
2378
+ }), [getCellOffsetFromCoords]);
2379
+ // Print style
2380
+ const containerStyle = {
2381
+ width: containerWidth,
2382
+ "--rnc-print-top-margin": `-${headerRowHeight}px`,
2383
+ "--rnc-print-left-margin": `-${headerColumnWidth}px`,
2384
+ };
2385
+ // Footer final height - gradually reveal footer as user scrolls to bottom
2386
+ const contentMaxScrollTop = estimatedTotalHeight - containerHeight;
2387
+ const scrollPastBottom = Math.max(0, scrollTop - contentMaxScrollTop);
2388
+ const footerRevealAmount = Math.min(scrollPastBottom, footerHeight);
2389
+ return (React.createElement("div", { style: containerStyle, className: "rowsncolumns-grid print:w-full print:h-full print:block relative ", ref: scrollContainerRef },
2390
+ React.createElement("div", { className: "rowsncolumns-grid-container outline-none touch-none select-none relative", tabIndex: 0, ref: containerRef, ...rest },
2391
+ React.createElement(MemoizedGrid, { cells: cells, cellOverlays: cellOverlays, cellBackgrounds: cellBackgrounds, width: containerWidth, height: containerHeight, ref: stageRef, gridLines: gridLines, frozenRowCells: frozenRowCells, frozenColumnCells: frozenColumnCells, frozenColumnWidth: frozenColumnWidth, frozenRowHeight: frozenRowHeight, scrollTop: scrollTop, scrollLeft: scrollLeft, frozenSpacing: frozenSpacing, frozenIntersectionCells: frozenIntersectionCells, gridLinesFrozenColumn: gridLinesFrozenColumn, gridLinesFrozenRow: gridLinesFrozenRow, gridLinesFrozenIntersection: gridLinesFrozenIntersection, frozenColumnShadowComponent: frozenColumnShadowComponent, frozenRowShadowComponent: frozenRowShadowComponent, frozenIntersectionCellOverlays: frozenIntersectionCellOverlays, frozenIntersectionCellBackgrounds: frozenIntersectionCellBackgrounds, frozenRowCellOverlays: frozenRowCellOverlays, frozenRowCellBackgrounds: frozenRowCellBackgrounds, frozenColumnCellBackgrounds: frozenColumnCellBackgrounds, frozenColumnCellOverlays: frozenColumnCellOverlays, frozenRowHeaderCells: frozenRowHeaderCells, frozenColumnHeaderCells: frozenColumnHeaderCells, frozenIntersectionRowHeaderCells: frozenIntersectionRowHeaderCells, frozenIntersectionColumnHeaderCells: frozenIntersectionColumnHeaderCells, headerRowHeight: headerRowHeight, headerColumnWidth: headerColumnWidth, showHeaders: showHeaders, listening: listenToEvents, rowHeaderStageRef: rowHeaderStageRef, columnHeaderStageRef: columnHeaderStageRef })),
2392
+ selectionChildren,
2393
+ React.createElement(GridContext.Provider, { value: contextValue },
2394
+ gridChildren && typeof gridChildren === "function"
2395
+ ? gridChildren({
2396
+ scrollLeft,
2397
+ scrollTop,
2398
+ frozenRowHeight,
2399
+ frozenColumnWidth,
2400
+ containerHeight,
2401
+ containerWidth,
2402
+ })
2403
+ : null,
2404
+ children),
2405
+ footerComponent ? (React.createElement("div", { className: "absolute inset-0 overflow-hidden pointer-events-none" },
2406
+ React.createElement("div", { style: {
2407
+ height: footerHeight,
2408
+ bottom: 0,
2409
+ transform: `translateY(${footerHeight - footerRevealAmount}px)`,
2410
+ willChange: "transform",
2411
+ }, className: "rnc-bottom-footer absolute left-0 right-0 overflow-hidden pointer-events-auto flex flex-col" }, React.isValidElement(footerComponent)
2412
+ ? footerComponent
2413
+ : React.createElement(footerComponent)))) : null,
2414
+ showScrollbarX ? (React.createElement(React.Fragment, null,
2415
+ React.createElement("div", { className: "rnc-scrollbar-shim bg-rnc-scrollbar-background print:hidden border-t border-l border-b border-t-rnc-scrollbar-border border-l-rnc-border border-b-rnc-scrollbar-border border-solid left-0 right-0 bottom-0 absolute", style: {
2416
+ height: scrollbarSize,
2417
+ } }),
2418
+ frozenColumnWidth === 0 ? null : (React.createElement("div", { className: "rnc-frozen-col-sep ml-px border-l-0 border-b-0 border-t-0 border-r border-solid border-r-rnc-border absolute bottom-0 left-0", style: {
2419
+ height: scrollbarSize,
2420
+ width: frozenColumnWidth,
2421
+ } })),
2422
+ React.createElement("div", { role: "scrollbar", "aria-orientation": "horizontal", className: "rnc-scrollbar print:hidden rnc-scrollbar-x absolute bottom-0 will-change-transform overflow-scroll", tabIndex: -1, style: {
2423
+ left: frozenColumnWidth,
2424
+ width: scrollbarXContainterWidth,
2425
+ height: scrollbarSize,
2426
+ }, onScroll: handleScrollLeft, ref: horizontalScrollRef },
2427
+ React.createElement("div", { style: {
2428
+ position: "absolute",
2429
+ width: scrollbarXWidth,
2430
+ height: 1,
2431
+ } })))) : null,
2432
+ showScrollbarY ? (React.createElement(React.Fragment, null,
2433
+ React.createElement("div", { className: "rnc-scrollbar-shim bg-rnc-scrollbar-background print:hidden border-t border-l border-r border-t-rnc-border border-l-rnc-scrollbar-border border-r-rnc-scrollbar-border border-solid absolute bottom-0 right-0 top-0", style: {
2434
+ width: scrollbarSize,
2435
+ } }),
2436
+ frozenRowHeight === 0 ? null : (React.createElement("div", { className: "rnc-frozen-row-sep mt-px border-l-0 border-r-0 border-t-0 border-b border-b-rnc-border border-solid absolute top-0 right-0", style: {
2437
+ width: scrollbarSize,
2438
+ height: frozenRowHeight,
2439
+ } })),
2440
+ React.createElement("div", { role: "scrollbar", "aria-orientation": "vertical", className: "rnc-scrollbar print:hidden rnc-scrollbar-y overflow-scroll absolute right-0 will-change-transform", tabIndex: -1, style: {
2441
+ height: scrollbarYContainterHeight,
2442
+ top: frozenRowHeight,
2443
+ width: scrollbarSize,
2444
+ }, onScroll: handleScroll, ref: verticalScrollRef },
2445
+ React.createElement("div", { className: "absolute", style: {
2446
+ height: effectiveScrollbarYHeight,
2447
+ width: 1,
2448
+ } })))) : null));
2449
+ }));
2450
+ Grid.displayName = "Grid";
2451
+ export default Grid;