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