@rowsncolumns/grid 9.0.3 → 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 -980
- 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 -1437
- 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
|
@@ -0,0 +1,1260 @@
|
|
|
1
|
+
// Utilities extracted from https://github.com/bvaughn/react-window
|
|
2
|
+
import { useEffect, useLayoutEffect, useMemo, useRef } from "react";
|
|
3
|
+
import { unstable_IdlePriority, unstable_scheduleCallback } from "scheduler";
|
|
4
|
+
import { Direction } from "./types";
|
|
5
|
+
export var Align;
|
|
6
|
+
(function (Align) {
|
|
7
|
+
Align["start"] = "start";
|
|
8
|
+
Align["end"] = "end";
|
|
9
|
+
Align["center"] = "center";
|
|
10
|
+
Align["auto"] = "auto";
|
|
11
|
+
Align["smart"] = "smart";
|
|
12
|
+
})(Align || (Align = {}));
|
|
13
|
+
export var ItemType;
|
|
14
|
+
(function (ItemType) {
|
|
15
|
+
ItemType["row"] = "row";
|
|
16
|
+
ItemType["column"] = "column";
|
|
17
|
+
})(ItemType || (ItemType = {}));
|
|
18
|
+
export const getRowStartIndexForOffset = ({ rowHeight, columnWidth, rowCount, columnCount, instanceProps, offset, scale, }) => {
|
|
19
|
+
return findNearestItem({
|
|
20
|
+
itemType: ItemType.row,
|
|
21
|
+
rowHeight,
|
|
22
|
+
columnWidth,
|
|
23
|
+
rowCount,
|
|
24
|
+
columnCount,
|
|
25
|
+
instanceProps,
|
|
26
|
+
offset,
|
|
27
|
+
scale,
|
|
28
|
+
});
|
|
29
|
+
};
|
|
30
|
+
export const getRowStopIndexForStartIndex = ({ startIndex, rowCount, rowHeight, columnWidth, scrollTop, containerHeight, instanceProps, scale, visibleRowRanges, }) => {
|
|
31
|
+
const itemMetadata = getItemMetadata({
|
|
32
|
+
itemType: ItemType.row,
|
|
33
|
+
rowHeight,
|
|
34
|
+
columnWidth,
|
|
35
|
+
index: startIndex,
|
|
36
|
+
instanceProps,
|
|
37
|
+
scale,
|
|
38
|
+
});
|
|
39
|
+
const maxOffset = scrollTop + containerHeight;
|
|
40
|
+
let offset = itemMetadata.offset + itemMetadata.size;
|
|
41
|
+
let stopIndex = startIndex;
|
|
42
|
+
// If visibleRowRanges provided, use it to skip hidden rows efficiently
|
|
43
|
+
if (visibleRowRanges && visibleRowRanges.length > 0) {
|
|
44
|
+
// Find which range we're currently in
|
|
45
|
+
let currentRangeIdx = 0;
|
|
46
|
+
for (let i = 0; i < visibleRowRanges.length; i++) {
|
|
47
|
+
if (visibleRowRanges[i].end >= stopIndex) {
|
|
48
|
+
currentRangeIdx = i;
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
while (stopIndex < rowCount - 1 && offset < maxOffset) {
|
|
53
|
+
const currentRange = visibleRowRanges[currentRangeIdx];
|
|
54
|
+
if (currentRange && stopIndex < currentRange.end) {
|
|
55
|
+
// Still within current visible range, move to next row
|
|
56
|
+
stopIndex++;
|
|
57
|
+
offset += getItemMetadata({
|
|
58
|
+
itemType: ItemType.row,
|
|
59
|
+
rowHeight,
|
|
60
|
+
columnWidth,
|
|
61
|
+
index: stopIndex,
|
|
62
|
+
instanceProps,
|
|
63
|
+
scale,
|
|
64
|
+
visibleRowRanges,
|
|
65
|
+
}).size;
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
// At end of current range, jump to next visible range
|
|
69
|
+
currentRangeIdx++;
|
|
70
|
+
if (currentRangeIdx >= visibleRowRanges.length) {
|
|
71
|
+
// No more visible ranges
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
const nextRange = visibleRowRanges[currentRangeIdx];
|
|
75
|
+
stopIndex = nextRange.start;
|
|
76
|
+
offset = getItemMetadata({
|
|
77
|
+
itemType: ItemType.row,
|
|
78
|
+
rowHeight,
|
|
79
|
+
columnWidth,
|
|
80
|
+
index: stopIndex,
|
|
81
|
+
instanceProps,
|
|
82
|
+
scale,
|
|
83
|
+
visibleRowRanges,
|
|
84
|
+
}).offset;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
// Fallback to original behavior
|
|
90
|
+
while (stopIndex < rowCount - 1 && offset < maxOffset) {
|
|
91
|
+
stopIndex++;
|
|
92
|
+
offset += getItemMetadata({
|
|
93
|
+
itemType: ItemType.row,
|
|
94
|
+
rowHeight,
|
|
95
|
+
columnWidth,
|
|
96
|
+
index: stopIndex,
|
|
97
|
+
instanceProps,
|
|
98
|
+
scale,
|
|
99
|
+
}).size;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return stopIndex;
|
|
103
|
+
};
|
|
104
|
+
export const getColumnStartIndexForOffset = ({ rowHeight, columnWidth, rowCount, columnCount, instanceProps, offset, scale, }) => {
|
|
105
|
+
return findNearestItem({
|
|
106
|
+
itemType: ItemType.column,
|
|
107
|
+
rowHeight,
|
|
108
|
+
columnWidth,
|
|
109
|
+
rowCount,
|
|
110
|
+
columnCount,
|
|
111
|
+
instanceProps,
|
|
112
|
+
offset,
|
|
113
|
+
scale,
|
|
114
|
+
});
|
|
115
|
+
};
|
|
116
|
+
export const getColumnStopIndexForStartIndex = ({ startIndex, rowHeight, columnWidth, instanceProps, containerWidth, scrollLeft, columnCount, scale, }) => {
|
|
117
|
+
const itemMetadata = getItemMetadata({
|
|
118
|
+
itemType: ItemType.column,
|
|
119
|
+
index: startIndex,
|
|
120
|
+
rowHeight,
|
|
121
|
+
columnWidth,
|
|
122
|
+
instanceProps,
|
|
123
|
+
scale,
|
|
124
|
+
});
|
|
125
|
+
const maxOffset = scrollLeft + containerWidth;
|
|
126
|
+
let offset = itemMetadata.offset + itemMetadata.size;
|
|
127
|
+
let stopIndex = startIndex;
|
|
128
|
+
while (stopIndex < columnCount - 1 && offset < maxOffset) {
|
|
129
|
+
stopIndex++;
|
|
130
|
+
offset += getItemMetadata({
|
|
131
|
+
itemType: ItemType.column,
|
|
132
|
+
rowHeight,
|
|
133
|
+
columnWidth,
|
|
134
|
+
index: stopIndex,
|
|
135
|
+
instanceProps,
|
|
136
|
+
scale,
|
|
137
|
+
}).size;
|
|
138
|
+
}
|
|
139
|
+
return stopIndex;
|
|
140
|
+
};
|
|
141
|
+
export const getBoundedCells = (area) => {
|
|
142
|
+
const cells = new Set();
|
|
143
|
+
if (!area)
|
|
144
|
+
return cells;
|
|
145
|
+
const { startRowIndex: top, startColumnIndex: left, endColumnIndex: right, endRowIndex: bottom, } = area;
|
|
146
|
+
for (let i = top; i <= bottom; i++) {
|
|
147
|
+
for (let j = left; j <= right; j++) {
|
|
148
|
+
cells.add(cellIdentifier(i, j));
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return cells;
|
|
152
|
+
};
|
|
153
|
+
export const itemKey = ({ rowIndex, columnIndex }) => `${rowIndex}:${columnIndex}`;
|
|
154
|
+
export const getRowOffset = ({ index, rowHeight, columnWidth, instanceProps, scale, readonly, visibleRowRanges, }) => {
|
|
155
|
+
return getItemMetadata({
|
|
156
|
+
itemType: ItemType.row,
|
|
157
|
+
index,
|
|
158
|
+
rowHeight,
|
|
159
|
+
columnWidth,
|
|
160
|
+
instanceProps,
|
|
161
|
+
scale,
|
|
162
|
+
readonly,
|
|
163
|
+
visibleRowRanges,
|
|
164
|
+
}).offset;
|
|
165
|
+
};
|
|
166
|
+
export const getColumnOffset = ({ index, rowHeight, columnWidth, instanceProps, scale, readonly, }) => {
|
|
167
|
+
return getItemMetadata({
|
|
168
|
+
itemType: ItemType.column,
|
|
169
|
+
index,
|
|
170
|
+
rowHeight,
|
|
171
|
+
columnWidth,
|
|
172
|
+
instanceProps,
|
|
173
|
+
scale,
|
|
174
|
+
readonly,
|
|
175
|
+
}).offset;
|
|
176
|
+
};
|
|
177
|
+
export const getRowHeight = (index, instanceProps) => {
|
|
178
|
+
return instanceProps.rowMetadataMap.get(index)?.size ?? 0;
|
|
179
|
+
};
|
|
180
|
+
export const getColumnWidth = (index, instanceProps) => {
|
|
181
|
+
return instanceProps.columnMetadataMap.get(index)?.size ?? 0;
|
|
182
|
+
};
|
|
183
|
+
export const getItemMetadata = ({ itemType, index, rowHeight, columnWidth, instanceProps, scale = 1, readonly, visibleRowRanges, }) => {
|
|
184
|
+
let itemMetadataMap, itemSize, lastMeasuredIndex, recalcIndices;
|
|
185
|
+
if (itemType === "column") {
|
|
186
|
+
itemMetadataMap = instanceProps.columnMetadataMap;
|
|
187
|
+
itemSize = columnWidth;
|
|
188
|
+
lastMeasuredIndex = instanceProps.lastMeasuredColumnIndex;
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
itemMetadataMap = instanceProps.rowMetadataMap;
|
|
192
|
+
itemSize = rowHeight;
|
|
193
|
+
lastMeasuredIndex = instanceProps.lastMeasuredRowIndex;
|
|
194
|
+
}
|
|
195
|
+
if (index > lastMeasuredIndex) {
|
|
196
|
+
let offset = 0;
|
|
197
|
+
if (lastMeasuredIndex >= 0) {
|
|
198
|
+
const itemMetadata = itemMetadataMap.get(lastMeasuredIndex);
|
|
199
|
+
if (itemMetadata) {
|
|
200
|
+
offset = itemMetadata.offset + itemMetadata.size;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
// If visibleRowRanges provided for rows, skip expensive itemSize() calls for hidden rows
|
|
204
|
+
// We still store metadata for all rows (required by binary search), but hidden rows get size=0
|
|
205
|
+
if (itemType === "row" && visibleRowRanges && visibleRowRanges.length > 0) {
|
|
206
|
+
// Build a quick lookup for which ranges we're in
|
|
207
|
+
let rangeIdx = 0;
|
|
208
|
+
for (let i = lastMeasuredIndex + 1; i <= index; i++) {
|
|
209
|
+
// Advance to the appropriate range
|
|
210
|
+
while (rangeIdx < visibleRowRanges.length && visibleRowRanges[rangeIdx].end < i) {
|
|
211
|
+
rangeIdx++;
|
|
212
|
+
}
|
|
213
|
+
// Check if i is in the current range (visible) or in a gap (hidden)
|
|
214
|
+
const currentRange = visibleRowRanges[rangeIdx];
|
|
215
|
+
const isVisible = currentRange && i >= currentRange.start && i <= currentRange.end;
|
|
216
|
+
// Only call itemSize for visible rows - hidden rows have size 0
|
|
217
|
+
const size = isVisible ? itemSize(i) * scale : 0;
|
|
218
|
+
itemMetadataMap.set(i, { offset, size });
|
|
219
|
+
offset += size;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
// Original behavior for columns or when no visibleRowRanges
|
|
224
|
+
for (let i = lastMeasuredIndex + 1; i <= index; i++) {
|
|
225
|
+
const size = itemSize(i) * scale;
|
|
226
|
+
itemMetadataMap.set(i, {
|
|
227
|
+
offset,
|
|
228
|
+
size,
|
|
229
|
+
});
|
|
230
|
+
offset += size;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
if (!readonly) {
|
|
234
|
+
if (itemType === "column") {
|
|
235
|
+
instanceProps.lastMeasuredColumnIndex = index;
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
instanceProps.lastMeasuredRowIndex = index;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return itemMetadataMap.get(index);
|
|
243
|
+
};
|
|
244
|
+
const findNearestItem = ({ itemType, rowHeight, columnWidth, rowCount, columnCount, instanceProps, offset, scale, }) => {
|
|
245
|
+
let itemMetadataMap, lastMeasuredIndex;
|
|
246
|
+
if (itemType === "column") {
|
|
247
|
+
itemMetadataMap = instanceProps.columnMetadataMap;
|
|
248
|
+
lastMeasuredIndex = instanceProps.lastMeasuredColumnIndex;
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
itemMetadataMap = instanceProps.rowMetadataMap;
|
|
252
|
+
lastMeasuredIndex = instanceProps.lastMeasuredRowIndex;
|
|
253
|
+
}
|
|
254
|
+
const lastMeasuredItemOffset = lastMeasuredIndex > 0
|
|
255
|
+
? itemMetadataMap.get(lastMeasuredIndex)?.offset ?? 0
|
|
256
|
+
: 0;
|
|
257
|
+
if (lastMeasuredItemOffset >= offset) {
|
|
258
|
+
// If we've already measured items within this range just use a binary search as it's faster.
|
|
259
|
+
return findNearestItemBinarySearch({
|
|
260
|
+
itemType,
|
|
261
|
+
rowHeight,
|
|
262
|
+
columnWidth,
|
|
263
|
+
instanceProps,
|
|
264
|
+
high: lastMeasuredIndex,
|
|
265
|
+
low: 0,
|
|
266
|
+
offset,
|
|
267
|
+
scale,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
// If we haven't yet measured this high, fallback to an exponential search with an inner binary search.
|
|
272
|
+
// The exponential search avoids pre-computing sizes for the full set of items as a binary search would.
|
|
273
|
+
// The overall complexity for this approach is O(log n).
|
|
274
|
+
return findNearestItemExponentialSearch({
|
|
275
|
+
itemType,
|
|
276
|
+
rowHeight,
|
|
277
|
+
rowCount,
|
|
278
|
+
columnCount,
|
|
279
|
+
columnWidth,
|
|
280
|
+
instanceProps,
|
|
281
|
+
index: Math.max(0, lastMeasuredIndex),
|
|
282
|
+
offset,
|
|
283
|
+
scale,
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
const findNearestItemBinarySearch = ({ itemType, rowHeight, columnWidth, instanceProps, high, low, offset, scale, }) => {
|
|
288
|
+
while (low <= high) {
|
|
289
|
+
const middle = low + Math.floor((high - low) / 2);
|
|
290
|
+
const currentOffset = getItemMetadata({
|
|
291
|
+
itemType,
|
|
292
|
+
rowHeight,
|
|
293
|
+
columnWidth,
|
|
294
|
+
index: middle,
|
|
295
|
+
instanceProps,
|
|
296
|
+
scale,
|
|
297
|
+
}).offset;
|
|
298
|
+
if (currentOffset === offset) {
|
|
299
|
+
return middle;
|
|
300
|
+
}
|
|
301
|
+
else if (currentOffset < offset) {
|
|
302
|
+
low = middle + 1;
|
|
303
|
+
}
|
|
304
|
+
else if (currentOffset > offset) {
|
|
305
|
+
high = middle - 1;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
if (low > 0) {
|
|
309
|
+
return low - 1;
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
return 0;
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
const findNearestItemExponentialSearch = ({ itemType, rowHeight, columnWidth, rowCount, columnCount, instanceProps, index, offset, scale, }) => {
|
|
316
|
+
const itemCount = itemType === "column" ? columnCount : rowCount;
|
|
317
|
+
let interval = 1;
|
|
318
|
+
while (index < itemCount &&
|
|
319
|
+
getItemMetadata({
|
|
320
|
+
itemType,
|
|
321
|
+
rowHeight,
|
|
322
|
+
columnWidth,
|
|
323
|
+
index,
|
|
324
|
+
instanceProps,
|
|
325
|
+
scale,
|
|
326
|
+
}).offset < offset) {
|
|
327
|
+
index += interval;
|
|
328
|
+
interval *= 2;
|
|
329
|
+
}
|
|
330
|
+
return findNearestItemBinarySearch({
|
|
331
|
+
itemType,
|
|
332
|
+
rowHeight,
|
|
333
|
+
columnWidth,
|
|
334
|
+
instanceProps,
|
|
335
|
+
high: Math.min(index, itemCount - 1),
|
|
336
|
+
low: Math.floor(index / 2),
|
|
337
|
+
offset,
|
|
338
|
+
scale,
|
|
339
|
+
});
|
|
340
|
+
};
|
|
341
|
+
export const getEstimatedTotalHeight = (rowCount, instanceProps) => {
|
|
342
|
+
const { estimatedRowHeight } = instanceProps;
|
|
343
|
+
let totalSizeOfMeasuredRows = 0;
|
|
344
|
+
let { lastMeasuredRowIndex } = instanceProps;
|
|
345
|
+
const { rowMetadataMap } = instanceProps;
|
|
346
|
+
// Edge case check for when the number of items decreases while a scroll is in progress.
|
|
347
|
+
// https://github.com/bvaughn/react-window/pull/138
|
|
348
|
+
if (lastMeasuredRowIndex >= rowCount) {
|
|
349
|
+
lastMeasuredRowIndex = rowCount - 1;
|
|
350
|
+
}
|
|
351
|
+
if (lastMeasuredRowIndex >= 0) {
|
|
352
|
+
const itemMetadata = rowMetadataMap.get(lastMeasuredRowIndex);
|
|
353
|
+
if (itemMetadata) {
|
|
354
|
+
totalSizeOfMeasuredRows = itemMetadata.offset + itemMetadata.size;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
const numUnmeasuredItems = rowCount - lastMeasuredRowIndex - 1;
|
|
358
|
+
const totalSizeOfUnmeasuredItems = numUnmeasuredItems * estimatedRowHeight;
|
|
359
|
+
return totalSizeOfMeasuredRows + totalSizeOfUnmeasuredItems;
|
|
360
|
+
};
|
|
361
|
+
export const getEstimatedTotalWidth = (columnCount, instanceProps) => {
|
|
362
|
+
const { estimatedColumnWidth } = instanceProps;
|
|
363
|
+
let totalSizeOfMeasuredRows = 0;
|
|
364
|
+
let { lastMeasuredColumnIndex } = instanceProps;
|
|
365
|
+
const { columnMetadataMap } = instanceProps;
|
|
366
|
+
// Edge case check for when the number of items decreases while a scroll is in progress.
|
|
367
|
+
// https://github.com/bvaughn/react-window/pull/138
|
|
368
|
+
if (lastMeasuredColumnIndex >= columnCount) {
|
|
369
|
+
lastMeasuredColumnIndex = columnCount - 1;
|
|
370
|
+
}
|
|
371
|
+
if (lastMeasuredColumnIndex >= 0) {
|
|
372
|
+
const itemMetadata = columnMetadataMap.get(lastMeasuredColumnIndex);
|
|
373
|
+
if (itemMetadata) {
|
|
374
|
+
totalSizeOfMeasuredRows = itemMetadata.offset + itemMetadata.size;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
const numUnmeasuredItems = columnCount - lastMeasuredColumnIndex - 1;
|
|
378
|
+
const totalSizeOfUnmeasuredItems = numUnmeasuredItems * estimatedColumnWidth;
|
|
379
|
+
return totalSizeOfMeasuredRows + totalSizeOfUnmeasuredItems;
|
|
380
|
+
};
|
|
381
|
+
/* Create a stringified cell identifier */
|
|
382
|
+
export const cellIdentifier = (rowIndex, columnIndex) => `${rowIndex},${columnIndex}`;
|
|
383
|
+
/**
|
|
384
|
+
* @desc Throttle fn
|
|
385
|
+
* @param func function
|
|
386
|
+
* @param limit Delay in milliseconds
|
|
387
|
+
*/
|
|
388
|
+
export function throttle(func, limit) {
|
|
389
|
+
let lastEventTimestamp = null;
|
|
390
|
+
const callable = (...args) => {
|
|
391
|
+
const now = Date.now();
|
|
392
|
+
if (!lastEventTimestamp || now - lastEventTimestamp >= limit) {
|
|
393
|
+
lastEventTimestamp = now;
|
|
394
|
+
func(...args);
|
|
395
|
+
}
|
|
396
|
+
};
|
|
397
|
+
return callable;
|
|
398
|
+
}
|
|
399
|
+
export function debounce(cb, wait = 300) {
|
|
400
|
+
let h = 0;
|
|
401
|
+
const callable = (...args) => {
|
|
402
|
+
clearTimeout(h);
|
|
403
|
+
h = window.setTimeout(() => cb(...args), wait);
|
|
404
|
+
};
|
|
405
|
+
return callable;
|
|
406
|
+
}
|
|
407
|
+
export function rafThrottle(callback) {
|
|
408
|
+
let active = false; // a simple flag
|
|
409
|
+
let evt; // to keep track of the last event
|
|
410
|
+
const handler = function () {
|
|
411
|
+
// fired only when screen has refreshed
|
|
412
|
+
active = false; // release our flag
|
|
413
|
+
callback(evt);
|
|
414
|
+
};
|
|
415
|
+
return function handleEvent(e) {
|
|
416
|
+
// the actual event handler
|
|
417
|
+
evt = e; // save our event at each call
|
|
418
|
+
evt && evt.persist && evt.persist();
|
|
419
|
+
if (!active) {
|
|
420
|
+
// only if we weren't already doing it
|
|
421
|
+
active = true; // raise the flag
|
|
422
|
+
requestAnimationFrame(handler); // wait for next screen refresh
|
|
423
|
+
}
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
export const scheduleIdlePriorityCallback = (callback) => unstable_scheduleCallback(unstable_IdlePriority, callback);
|
|
427
|
+
export function ricThrottle(callback) {
|
|
428
|
+
let active = false; // a simple flag
|
|
429
|
+
let evt; // to keep track of the last event
|
|
430
|
+
const handler = function () {
|
|
431
|
+
// fired only when screen has refreshed
|
|
432
|
+
active = false; // release our flag
|
|
433
|
+
callback(evt);
|
|
434
|
+
};
|
|
435
|
+
return function handleEvent(e) {
|
|
436
|
+
// the actual event handler
|
|
437
|
+
evt = e; // save our event at each call
|
|
438
|
+
evt && evt.persist && evt.persist();
|
|
439
|
+
if (!active) {
|
|
440
|
+
// only if we weren't already doing it
|
|
441
|
+
active = true; // raise the flag
|
|
442
|
+
scheduleIdlePriorityCallback(handler); // wait for next screen refresh
|
|
443
|
+
}
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
export const getOffsetForIndexAndAlignment = ({ itemType, containerHeight, containerWidth, rowHeight, columnWidth, columnCount, rowCount, index, align = Align.smart, scrollOffset, instanceProps, scrollbarSize, frozenOffset = 0, scale, estimatedTotalHeight, estimatedTotalWidth, }) => {
|
|
447
|
+
const size = itemType === "column" ? containerWidth : containerHeight;
|
|
448
|
+
const itemMetadata = getItemMetadata({
|
|
449
|
+
itemType,
|
|
450
|
+
rowHeight,
|
|
451
|
+
columnWidth,
|
|
452
|
+
index,
|
|
453
|
+
instanceProps,
|
|
454
|
+
scale,
|
|
455
|
+
});
|
|
456
|
+
// Get estimated total size after ItemMetadata is computed,
|
|
457
|
+
// To ensure it reflects actual measurements instead of just estimates.
|
|
458
|
+
const estimatedTotalSize = itemType === "column" ? estimatedTotalWidth : estimatedTotalHeight;
|
|
459
|
+
const maxOffset = Math.max(0, Math.min(estimatedTotalSize - size + scrollbarSize, itemMetadata.offset - frozenOffset));
|
|
460
|
+
const minOffset = Math.max(0, itemMetadata.offset - size + scrollbarSize + itemMetadata.size);
|
|
461
|
+
if (align === Align.smart) {
|
|
462
|
+
if (scrollOffset >= minOffset - size && scrollOffset <= maxOffset + size) {
|
|
463
|
+
align = Align.auto;
|
|
464
|
+
}
|
|
465
|
+
else {
|
|
466
|
+
align = Align.center;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
switch (align) {
|
|
470
|
+
case Align.start:
|
|
471
|
+
return maxOffset;
|
|
472
|
+
case Align.end:
|
|
473
|
+
return minOffset;
|
|
474
|
+
case Align.center:
|
|
475
|
+
return Math.round(minOffset + (maxOffset - minOffset) / 2);
|
|
476
|
+
case Align.auto:
|
|
477
|
+
default:
|
|
478
|
+
if (scrollOffset >= minOffset && scrollOffset <= maxOffset) {
|
|
479
|
+
return scrollOffset;
|
|
480
|
+
}
|
|
481
|
+
else if (minOffset > maxOffset) {
|
|
482
|
+
// Because we only take into account the scrollbar size when calculating minOffset
|
|
483
|
+
// this value can be larger than maxOffset when at the end of the list
|
|
484
|
+
return minOffset;
|
|
485
|
+
}
|
|
486
|
+
else if (scrollOffset < minOffset) {
|
|
487
|
+
return minOffset;
|
|
488
|
+
}
|
|
489
|
+
else {
|
|
490
|
+
return maxOffset;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
};
|
|
494
|
+
export const getOffsetForColumnAndAlignment = (props) => {
|
|
495
|
+
return getOffsetForIndexAndAlignment({
|
|
496
|
+
itemType: ItemType.column,
|
|
497
|
+
...props,
|
|
498
|
+
});
|
|
499
|
+
};
|
|
500
|
+
export const getOffsetForRowAndAlignment = (props) => {
|
|
501
|
+
return getOffsetForIndexAndAlignment({
|
|
502
|
+
itemType: ItemType.row,
|
|
503
|
+
...props,
|
|
504
|
+
});
|
|
505
|
+
};
|
|
506
|
+
// Animation frame based implementation of setTimeout.
|
|
507
|
+
// Inspired by Joe Lambert, https://gist.github.com/joelambert/1002116#file-requesttimeout-js
|
|
508
|
+
const hasNativePerformanceNow = typeof performance === "object" && typeof performance.now === "function";
|
|
509
|
+
const now = hasNativePerformanceNow
|
|
510
|
+
? () => performance.now()
|
|
511
|
+
: () => Date.now();
|
|
512
|
+
export function cancelTimeout(timeoutID) {
|
|
513
|
+
cancelAnimationFrame(timeoutID.id);
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* Create a throttler based on RAF
|
|
517
|
+
* @param callback
|
|
518
|
+
* @param delay
|
|
519
|
+
*/
|
|
520
|
+
export function requestTimeout(callback, delay) {
|
|
521
|
+
const start = now();
|
|
522
|
+
function tick() {
|
|
523
|
+
if (now() - start >= delay) {
|
|
524
|
+
callback.call(null);
|
|
525
|
+
}
|
|
526
|
+
else {
|
|
527
|
+
timeoutID.id = requestAnimationFrame(tick);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
const timeoutID = {
|
|
531
|
+
id: requestAnimationFrame(tick),
|
|
532
|
+
};
|
|
533
|
+
return timeoutID;
|
|
534
|
+
}
|
|
535
|
+
export const selectionFromActiveCell = (activeCell) => {
|
|
536
|
+
if (!activeCell)
|
|
537
|
+
return [];
|
|
538
|
+
return [
|
|
539
|
+
{
|
|
540
|
+
range: {
|
|
541
|
+
startRowIndex: activeCell.rowIndex,
|
|
542
|
+
startColumnIndex: activeCell.columnIndex,
|
|
543
|
+
endRowIndex: activeCell.rowIndex,
|
|
544
|
+
endColumnIndex: activeCell.columnIndex,
|
|
545
|
+
},
|
|
546
|
+
},
|
|
547
|
+
];
|
|
548
|
+
};
|
|
549
|
+
/**
|
|
550
|
+
* Check if a selection are spans multiple cells
|
|
551
|
+
* @param sel
|
|
552
|
+
*/
|
|
553
|
+
export const selectionSpansCells = (sel) => {
|
|
554
|
+
if (!sel)
|
|
555
|
+
return false;
|
|
556
|
+
return (sel.endRowIndex !== sel.startRowIndex ||
|
|
557
|
+
sel.startColumnIndex !== sel.endColumnIndex);
|
|
558
|
+
};
|
|
559
|
+
/**
|
|
560
|
+
* When user tries to drag a selection
|
|
561
|
+
* @param initialSelection
|
|
562
|
+
* @param from
|
|
563
|
+
* @param to
|
|
564
|
+
*/
|
|
565
|
+
export const newSelectionFromDrag = (initialSelection, from, to, topBound = 0, leftBound = 0, rowCount, columnCount) => {
|
|
566
|
+
const currentBounds = initialSelection.range;
|
|
567
|
+
const startRowIndex = Math.max(topBound, Math.min(rowCount, to.rowIndex + currentBounds.startRowIndex - from.rowIndex));
|
|
568
|
+
const startColumnIndex = Math.max(leftBound, Math.min(columnCount, to.columnIndex + currentBounds.startColumnIndex - from.columnIndex));
|
|
569
|
+
return {
|
|
570
|
+
range: {
|
|
571
|
+
startRowIndex,
|
|
572
|
+
startColumnIndex,
|
|
573
|
+
endRowIndex: startRowIndex +
|
|
574
|
+
(currentBounds.endRowIndex - currentBounds.startRowIndex),
|
|
575
|
+
endColumnIndex: startColumnIndex +
|
|
576
|
+
(currentBounds.endColumnIndex - currentBounds.startColumnIndex),
|
|
577
|
+
},
|
|
578
|
+
};
|
|
579
|
+
};
|
|
580
|
+
/**
|
|
581
|
+
* Clamp cell coordinates to be inside activeCell and selection
|
|
582
|
+
* @param coords
|
|
583
|
+
* @param activeCell
|
|
584
|
+
* @param selection
|
|
585
|
+
*/
|
|
586
|
+
export const clampCellCoords = (coords, activeCell, selection) => {
|
|
587
|
+
if (activeCell) {
|
|
588
|
+
coords.rowIndex = Math.max(activeCell.rowIndex, coords.rowIndex);
|
|
589
|
+
coords.columnIndex = Math.min(activeCell.columnIndex, coords.columnIndex);
|
|
590
|
+
}
|
|
591
|
+
if (selection) {
|
|
592
|
+
coords.rowIndex = Math.min(selection.range.endRowIndex, Math.max(selection.range.startRowIndex, coords.rowIndex));
|
|
593
|
+
coords.columnIndex = Math.min(selection.range.endColumnIndex, Math.max(selection.range.startColumnIndex, coords.columnIndex));
|
|
594
|
+
}
|
|
595
|
+
return coords;
|
|
596
|
+
};
|
|
597
|
+
/**
|
|
598
|
+
* Converts a number to alphabet
|
|
599
|
+
* @param i
|
|
600
|
+
*/
|
|
601
|
+
export const numberToAlphabet = (i) => {
|
|
602
|
+
return ((i >= 26 ? numberToAlphabet(((i / 26) >> 0) - 1) : "") +
|
|
603
|
+
"abcdefghijklmnopqrstuvwxyz"[i % 26 >> 0]).toUpperCase();
|
|
604
|
+
};
|
|
605
|
+
/**
|
|
606
|
+
* Convert selections to html and csv data
|
|
607
|
+
* @param rows
|
|
608
|
+
*/
|
|
609
|
+
export const prepareClipboardData = (rows) => {
|
|
610
|
+
const html = ["<table>"];
|
|
611
|
+
const csv = [];
|
|
612
|
+
const sanitizeCell = (value) => {
|
|
613
|
+
if (isNull(value))
|
|
614
|
+
return "";
|
|
615
|
+
return value;
|
|
616
|
+
};
|
|
617
|
+
rows.forEach((row) => {
|
|
618
|
+
html.push("<tr>");
|
|
619
|
+
const csvRow = [];
|
|
620
|
+
row.forEach((cell) => {
|
|
621
|
+
html.push(`<td>${sanitizeCell(cell)}</td>`);
|
|
622
|
+
csvRow.push(`${castToString(cell)?.replace(/"/g, '""')}`);
|
|
623
|
+
});
|
|
624
|
+
csv.push(csvRow.join(","));
|
|
625
|
+
html.push("</tr>");
|
|
626
|
+
});
|
|
627
|
+
html.push("</table>");
|
|
628
|
+
return [html.join(""), csv.join("\n")];
|
|
629
|
+
};
|
|
630
|
+
/**
|
|
631
|
+
* Cycles active cell within selecton bounds
|
|
632
|
+
* @param activeCellBounds
|
|
633
|
+
* @param selectionBounds
|
|
634
|
+
* @param direction
|
|
635
|
+
*/
|
|
636
|
+
export const findNextCellWithinBounds = (activeCellBounds, selectionBounds, direction = Direction.Right) => {
|
|
637
|
+
const intersects = areaIntersects(activeCellBounds, selectionBounds);
|
|
638
|
+
if (!intersects)
|
|
639
|
+
return null;
|
|
640
|
+
let rowIndex, columnIndex;
|
|
641
|
+
let nextActiveCell = null;
|
|
642
|
+
if (direction === Direction.Right) {
|
|
643
|
+
rowIndex = activeCellBounds.startRowIndex;
|
|
644
|
+
columnIndex = activeCellBounds.startColumnIndex + 1;
|
|
645
|
+
if (columnIndex > selectionBounds.endColumnIndex) {
|
|
646
|
+
rowIndex = rowIndex + 1;
|
|
647
|
+
columnIndex = selectionBounds.startColumnIndex;
|
|
648
|
+
if (rowIndex > selectionBounds.endRowIndex) {
|
|
649
|
+
rowIndex = selectionBounds.startRowIndex;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
nextActiveCell = { rowIndex, columnIndex };
|
|
653
|
+
}
|
|
654
|
+
if (direction === Direction.Left) {
|
|
655
|
+
rowIndex = activeCellBounds.endRowIndex;
|
|
656
|
+
columnIndex = activeCellBounds.startColumnIndex - 1;
|
|
657
|
+
if (columnIndex < selectionBounds.startColumnIndex) {
|
|
658
|
+
rowIndex = rowIndex - 1;
|
|
659
|
+
columnIndex = selectionBounds.endColumnIndex;
|
|
660
|
+
if (rowIndex < selectionBounds.startRowIndex) {
|
|
661
|
+
rowIndex = selectionBounds.endRowIndex;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
nextActiveCell = { rowIndex, columnIndex };
|
|
665
|
+
}
|
|
666
|
+
if (direction === Direction.Down) {
|
|
667
|
+
rowIndex = activeCellBounds.endRowIndex + 1;
|
|
668
|
+
columnIndex = activeCellBounds.startColumnIndex;
|
|
669
|
+
if (rowIndex > selectionBounds.endRowIndex) {
|
|
670
|
+
columnIndex = activeCellBounds.startColumnIndex + 1;
|
|
671
|
+
rowIndex = selectionBounds.startRowIndex;
|
|
672
|
+
if (columnIndex > selectionBounds.endColumnIndex) {
|
|
673
|
+
columnIndex = selectionBounds.startColumnIndex;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
nextActiveCell = { rowIndex, columnIndex };
|
|
677
|
+
}
|
|
678
|
+
if (direction === Direction.Up) {
|
|
679
|
+
rowIndex = activeCellBounds.startRowIndex - 1;
|
|
680
|
+
columnIndex = activeCellBounds.startColumnIndex;
|
|
681
|
+
if (rowIndex < selectionBounds.startRowIndex) {
|
|
682
|
+
columnIndex = activeCellBounds.startColumnIndex - 1;
|
|
683
|
+
rowIndex = selectionBounds.endRowIndex;
|
|
684
|
+
if (columnIndex < selectionBounds.startColumnIndex) {
|
|
685
|
+
columnIndex = selectionBounds.endColumnIndex;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
nextActiveCell = { rowIndex, columnIndex };
|
|
689
|
+
}
|
|
690
|
+
return nextActiveCell;
|
|
691
|
+
};
|
|
692
|
+
/**
|
|
693
|
+
* Check if 2 areas overlap
|
|
694
|
+
* @param area1
|
|
695
|
+
* @param area2
|
|
696
|
+
*/
|
|
697
|
+
export const areaIntersects = (area1, area2) => {
|
|
698
|
+
if (!area2) {
|
|
699
|
+
return false;
|
|
700
|
+
}
|
|
701
|
+
if (area1.startColumnIndex > area2.endColumnIndex ||
|
|
702
|
+
area2.startColumnIndex > area1.endColumnIndex) {
|
|
703
|
+
return false;
|
|
704
|
+
}
|
|
705
|
+
if (area1.startRowIndex > area2.endRowIndex ||
|
|
706
|
+
area2.startRowIndex > area1.endRowIndex) {
|
|
707
|
+
return false;
|
|
708
|
+
}
|
|
709
|
+
return true;
|
|
710
|
+
};
|
|
711
|
+
/**
|
|
712
|
+
* Check if area is inside another area
|
|
713
|
+
* @param needle
|
|
714
|
+
* @param haystack
|
|
715
|
+
*/
|
|
716
|
+
export const areaInsideArea = (needle, haystack) => {
|
|
717
|
+
return (needle.startRowIndex >= haystack.startRowIndex &&
|
|
718
|
+
needle.endRowIndex <= haystack.endRowIndex &&
|
|
719
|
+
needle.startColumnIndex >= haystack.startColumnIndex &&
|
|
720
|
+
needle.endColumnIndex <= haystack.endColumnIndex);
|
|
721
|
+
};
|
|
722
|
+
/**
|
|
723
|
+
* Check if two areas are equal
|
|
724
|
+
* @param area1
|
|
725
|
+
* @param area2
|
|
726
|
+
*/
|
|
727
|
+
export const isAreasEqual = (area1, area2) => {
|
|
728
|
+
if (area1 === void 0 || area2 === void 0) {
|
|
729
|
+
return false;
|
|
730
|
+
}
|
|
731
|
+
return (area1.endRowIndex === area2.endRowIndex &&
|
|
732
|
+
area1.startRowIndex === area2.startRowIndex &&
|
|
733
|
+
area1.startColumnIndex === area2.startColumnIndex &&
|
|
734
|
+
area1.endColumnIndex === area2.endColumnIndex);
|
|
735
|
+
};
|
|
736
|
+
/**
|
|
737
|
+
* Get maximum bound of an area, caters to merged cells
|
|
738
|
+
* @param area
|
|
739
|
+
* @param boundGetter
|
|
740
|
+
*/
|
|
741
|
+
export const extendAreaToMergedCells = (_area, mergedCells) => {
|
|
742
|
+
const area = { ..._area };
|
|
743
|
+
for (const bounds of mergedCells) {
|
|
744
|
+
if (areaIntersects(area, bounds)) {
|
|
745
|
+
area.startRowIndex = Math.min(area.startRowIndex, bounds.startRowIndex);
|
|
746
|
+
area.endRowIndex = Math.max(area.endRowIndex, bounds.endRowIndex);
|
|
747
|
+
area.endColumnIndex = Math.max(area.endColumnIndex, bounds.endColumnIndex);
|
|
748
|
+
area.startColumnIndex = Math.min(area.startColumnIndex, bounds.startColumnIndex);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
return area;
|
|
752
|
+
};
|
|
753
|
+
/**
|
|
754
|
+
* Convert 2 cells to bounds
|
|
755
|
+
* @param start
|
|
756
|
+
* @param end
|
|
757
|
+
* @returns
|
|
758
|
+
*
|
|
759
|
+
* 2 loops O(n)
|
|
760
|
+
*/
|
|
761
|
+
export const cellRangeToBounds = (start, end, spanMerges = true, getCellBounds, selectionLeftbound = 1, selectionTopBound = 1) => {
|
|
762
|
+
let top = Math.min(Math.max(selectionTopBound, start.rowIndex), Math.max(selectionTopBound, end.rowIndex));
|
|
763
|
+
let bottom = Math.max(start.rowIndex, end.rowIndex);
|
|
764
|
+
let left = Math.min(Math.max(selectionLeftbound, start.columnIndex), Math.max(selectionLeftbound, end.columnIndex));
|
|
765
|
+
let right = Math.max(start.columnIndex, end.columnIndex);
|
|
766
|
+
/**
|
|
767
|
+
* The idea is that
|
|
768
|
+
* We do 2 loops >
|
|
769
|
+
* Left to Right and then top to bottom
|
|
770
|
+
* => Find top cell and bottom cell and check
|
|
771
|
+
* if there are any merged cells at the edge
|
|
772
|
+
* Then keep extending our top and bottom bounds accordingly
|
|
773
|
+
*
|
|
774
|
+
* Same goes for Top to bottom
|
|
775
|
+
* => Find left most and right most cells
|
|
776
|
+
*/
|
|
777
|
+
if (spanMerges) {
|
|
778
|
+
for (let columnIndex = left; columnIndex <= right; columnIndex++) {
|
|
779
|
+
const topCell = getCellBounds({ rowIndex: top, columnIndex });
|
|
780
|
+
const bottomCell = getCellBounds({ rowIndex: bottom, columnIndex });
|
|
781
|
+
bottom = Math.max(topCell.endRowIndex, bottomCell.endRowIndex, bottom);
|
|
782
|
+
top = Math.min(topCell.startRowIndex, bottomCell.startRowIndex, top);
|
|
783
|
+
}
|
|
784
|
+
for (let rowIndex = top; rowIndex <= bottom; rowIndex++) {
|
|
785
|
+
const topCell = getCellBounds({ rowIndex, columnIndex: left });
|
|
786
|
+
const bottomCell = getCellBounds({ rowIndex, columnIndex: right });
|
|
787
|
+
right = Math.max(topCell.endColumnIndex, bottomCell.endColumnIndex, right);
|
|
788
|
+
left = Math.min(topCell.startColumnIndex, bottomCell.startColumnIndex, left);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
return {
|
|
792
|
+
startRowIndex: top,
|
|
793
|
+
startColumnIndex: left,
|
|
794
|
+
endColumnIndex: right,
|
|
795
|
+
endRowIndex: bottom,
|
|
796
|
+
};
|
|
797
|
+
};
|
|
798
|
+
/**
|
|
799
|
+
* Create a range out of a cell
|
|
800
|
+
* @param param0
|
|
801
|
+
* @returns
|
|
802
|
+
*/
|
|
803
|
+
export const cellToArea = ({ rowIndex, columnIndex, }) => {
|
|
804
|
+
return {
|
|
805
|
+
startRowIndex: rowIndex,
|
|
806
|
+
startColumnIndex: columnIndex,
|
|
807
|
+
endColumnIndex: columnIndex,
|
|
808
|
+
endRowIndex: rowIndex,
|
|
809
|
+
};
|
|
810
|
+
};
|
|
811
|
+
/**
|
|
812
|
+
* Check if its being rendered in Browser or SSR
|
|
813
|
+
*/
|
|
814
|
+
export const canUseDOM = typeof window !== "undefined" &&
|
|
815
|
+
typeof document !== "undefined" &&
|
|
816
|
+
typeof window.document !== "undefined";
|
|
817
|
+
/**
|
|
818
|
+
* Make a font-family value safe for CSS font shorthand (canvas
|
|
819
|
+
* `ctx.font`). Family names that aren't a single safe identifier
|
|
820
|
+
* (leading digit, spaces with odd characters, slashes, …) must be
|
|
821
|
+
* quoted or the shorthand is invalid; generic families (sans-serif,
|
|
822
|
+
* serif, monospace, …) must stay unquoted to keep their generic
|
|
823
|
+
* meaning. Accepts a single family or a comma-separated stack.
|
|
824
|
+
*/
|
|
825
|
+
export const toCssFontFamily = (family) => family
|
|
826
|
+
.split(",")
|
|
827
|
+
.map((part) => {
|
|
828
|
+
const trimmed = part.trim().replace(/^["']|["']$/g, "");
|
|
829
|
+
if (!trimmed)
|
|
830
|
+
return "";
|
|
831
|
+
return /^[a-zA-Z][a-zA-Z0-9-]*$/.test(trimmed)
|
|
832
|
+
? trimmed
|
|
833
|
+
: `"${trimmed.replace(/"/g, "")}"`;
|
|
834
|
+
})
|
|
835
|
+
.filter(Boolean)
|
|
836
|
+
.join(", ");
|
|
837
|
+
export const AutoSizerCanvas = (defaults = {}) => {
|
|
838
|
+
const { fontFamily = "Arial", fontSize = 12, fontWeight = "", fontStyle = "", lineHeight = 16, scale = 1, } = defaults;
|
|
839
|
+
const o = {
|
|
840
|
+
fontFamily,
|
|
841
|
+
fontSize,
|
|
842
|
+
fontWeight,
|
|
843
|
+
fontStyle,
|
|
844
|
+
lineHeight,
|
|
845
|
+
scale,
|
|
846
|
+
};
|
|
847
|
+
const canvas = canUseDOM && document.createElement("canvas");
|
|
848
|
+
const context = canvas ? canvas.getContext("2d") : null;
|
|
849
|
+
const SegmenterCtor = typeof Intl !== "undefined" && "Segmenter" in Intl
|
|
850
|
+
? Intl.Segmenter
|
|
851
|
+
: undefined;
|
|
852
|
+
const wordSegmenter = SegmenterCtor
|
|
853
|
+
? new SegmenterCtor(undefined, { granularity: "word" })
|
|
854
|
+
: undefined;
|
|
855
|
+
const graphemeSegmenter = SegmenterCtor
|
|
856
|
+
? new SegmenterCtor(undefined, { granularity: "grapheme" })
|
|
857
|
+
: undefined;
|
|
858
|
+
const setFont = (options = {}) => {
|
|
859
|
+
for (const key in options) {
|
|
860
|
+
o[key] = options[key] ?? o[key];
|
|
861
|
+
}
|
|
862
|
+
if (context) {
|
|
863
|
+
// Canvas `ctx.font` takes CSS font shorthand — an unquoted family
|
|
864
|
+
// that needs quoting (leading digit, slash, …) makes the whole
|
|
865
|
+
// assignment silently no-op, so the sizer keeps measuring with the
|
|
866
|
+
// PREVIOUS font. Quote non-identifier families (generic families
|
|
867
|
+
// must stay unquoted) and always end on a sans-serif fallback so
|
|
868
|
+
// painter and sizer agree on unknown fonts.
|
|
869
|
+
const family = o.fontFamily
|
|
870
|
+
? `${toCssFontFamily(String(o.fontFamily))}, sans-serif`
|
|
871
|
+
: "sans-serif";
|
|
872
|
+
context.font = `${o.fontStyle} ${o.fontWeight} ${o.fontSize * o.scale}px ${family}`;
|
|
873
|
+
}
|
|
874
|
+
};
|
|
875
|
+
const getWidthOfLongestText = (text, shouldWrap, width) => {
|
|
876
|
+
if (text == null) {
|
|
877
|
+
return { width: 0, height: 0 };
|
|
878
|
+
}
|
|
879
|
+
return getMultilineTextDimensions(text, shouldWrap, width);
|
|
880
|
+
};
|
|
881
|
+
// Get width and height of multi-line text
|
|
882
|
+
const getMultilineTextDimensions = (text, shouldWrap, width) => {
|
|
883
|
+
const lines = text.split("\n");
|
|
884
|
+
let textWidth = 0;
|
|
885
|
+
// Keep this in-sync with the default line-height used by <Cell /> (1.15)
|
|
886
|
+
const lineHeightRatio = 1.2;
|
|
887
|
+
const lineHeightPx = o.fontSize * lineHeightRatio * o.scale;
|
|
888
|
+
let currentHeightPx = 0;
|
|
889
|
+
const maxWidth = width ?? 0;
|
|
890
|
+
const wrapAtWord = shouldWrap;
|
|
891
|
+
const SPACE = " ";
|
|
892
|
+
const DASH = "-";
|
|
893
|
+
const measureWidth = (value) => context?.measureText(value).width ?? 0;
|
|
894
|
+
const pushLine = (content, clampToWidth) => {
|
|
895
|
+
const normalized = content.replace(/\s+$/g, "");
|
|
896
|
+
const measured = normalized.length ? measureWidth(normalized) : 0;
|
|
897
|
+
const finalWidth = clampToWidth && maxWidth > 0 ? Math.min(measured, maxWidth) : measured;
|
|
898
|
+
textWidth = Math.max(textWidth, finalWidth);
|
|
899
|
+
currentHeightPx += lineHeightPx;
|
|
900
|
+
};
|
|
901
|
+
const getWordSegments = (value) => {
|
|
902
|
+
if (wordSegmenter) {
|
|
903
|
+
return Array.from(wordSegmenter.segment(value), (segment) => segment.segment);
|
|
904
|
+
}
|
|
905
|
+
return value.match(/\S+|\s+/g) ?? [value];
|
|
906
|
+
};
|
|
907
|
+
const getGraphemeSegments = (value) => {
|
|
908
|
+
if (graphemeSegmenter) {
|
|
909
|
+
return Array.from(graphemeSegmenter.segment(value), (segment) => segment.segment);
|
|
910
|
+
}
|
|
911
|
+
return Array.from(value);
|
|
912
|
+
};
|
|
913
|
+
const splitSegment = (segment) => {
|
|
914
|
+
if (maxWidth <= 0) {
|
|
915
|
+
return [segment];
|
|
916
|
+
}
|
|
917
|
+
const graphemes = getGraphemeSegments(segment);
|
|
918
|
+
const pieces = [];
|
|
919
|
+
let buffer = "";
|
|
920
|
+
for (const grapheme of graphemes) {
|
|
921
|
+
const tentative = buffer + grapheme;
|
|
922
|
+
const tentativeWidth = measureWidth(tentative);
|
|
923
|
+
if (tentativeWidth <= maxWidth || !buffer) {
|
|
924
|
+
buffer = tentative;
|
|
925
|
+
}
|
|
926
|
+
else {
|
|
927
|
+
pieces.push(buffer);
|
|
928
|
+
buffer = grapheme;
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
if (buffer) {
|
|
932
|
+
pieces.push(buffer);
|
|
933
|
+
}
|
|
934
|
+
return pieces;
|
|
935
|
+
};
|
|
936
|
+
const wrapLine = (line) => {
|
|
937
|
+
if (!shouldWrap || maxWidth <= 0) {
|
|
938
|
+
pushLine(line, false);
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
const segments = getWordSegments(line);
|
|
942
|
+
let currentLine = "";
|
|
943
|
+
for (const segment of segments) {
|
|
944
|
+
const tentative = currentLine + segment;
|
|
945
|
+
const tentativeWidth = measureWidth(tentative);
|
|
946
|
+
if (tentativeWidth <= maxWidth || tentative.trim().length === 0) {
|
|
947
|
+
currentLine = tentative;
|
|
948
|
+
continue;
|
|
949
|
+
}
|
|
950
|
+
if (currentLine.trim().length) {
|
|
951
|
+
pushLine(currentLine, true);
|
|
952
|
+
currentLine = segment.trimStart();
|
|
953
|
+
if (!currentLine.length) {
|
|
954
|
+
continue;
|
|
955
|
+
}
|
|
956
|
+
if (measureWidth(currentLine) > maxWidth) {
|
|
957
|
+
const pieces = splitSegment(currentLine);
|
|
958
|
+
for (let i = 0; i < pieces.length - 1; i++) {
|
|
959
|
+
pushLine(pieces[i], true);
|
|
960
|
+
}
|
|
961
|
+
currentLine = pieces[pieces.length - 1] ?? "";
|
|
962
|
+
}
|
|
963
|
+
continue;
|
|
964
|
+
}
|
|
965
|
+
const trimmedSegment = segment.trim();
|
|
966
|
+
const pieces = splitSegment(trimmedSegment);
|
|
967
|
+
for (let i = 0; i < pieces.length; i++) {
|
|
968
|
+
const piece = pieces[i];
|
|
969
|
+
if (i === pieces.length - 1) {
|
|
970
|
+
currentLine = piece;
|
|
971
|
+
}
|
|
972
|
+
else {
|
|
973
|
+
pushLine(piece, true);
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
const trimmedLine = currentLine.trim();
|
|
978
|
+
if (trimmedLine.length) {
|
|
979
|
+
pushLine(trimmedLine, true);
|
|
980
|
+
}
|
|
981
|
+
else if (currentLine.length) {
|
|
982
|
+
currentHeightPx += lineHeightPx;
|
|
983
|
+
}
|
|
984
|
+
};
|
|
985
|
+
for (const line of lines) {
|
|
986
|
+
wrapLine(line);
|
|
987
|
+
}
|
|
988
|
+
return {
|
|
989
|
+
width: textWidth,
|
|
990
|
+
height: currentHeightPx,
|
|
991
|
+
};
|
|
992
|
+
};
|
|
993
|
+
const measureText = (text, shouldWrap, boundedWidth) => getWidthOfLongestText(castToString(text), shouldWrap, boundedWidth);
|
|
994
|
+
const reset = () => setFont(defaults);
|
|
995
|
+
/* Set font in constructor */
|
|
996
|
+
setFont(o);
|
|
997
|
+
return {
|
|
998
|
+
context,
|
|
999
|
+
measureText,
|
|
1000
|
+
setFont,
|
|
1001
|
+
reset,
|
|
1002
|
+
};
|
|
1003
|
+
};
|
|
1004
|
+
/* Export a singleton */
|
|
1005
|
+
export const autoSizerCanvas = AutoSizerCanvas();
|
|
1006
|
+
/* Check if a value is null */
|
|
1007
|
+
export const isNull = (value) => value === void 0 || value === null || value === "";
|
|
1008
|
+
export const isEqualCells = (a, b) => {
|
|
1009
|
+
if (isNull(a) ||
|
|
1010
|
+
isNull(b) ||
|
|
1011
|
+
a === null ||
|
|
1012
|
+
b === null ||
|
|
1013
|
+
a === void 0 ||
|
|
1014
|
+
b === void 0)
|
|
1015
|
+
return false;
|
|
1016
|
+
return a.rowIndex === b.rowIndex && a.columnIndex === b.columnIndex;
|
|
1017
|
+
};
|
|
1018
|
+
/**
|
|
1019
|
+
* Simple utility function to check if cell is within bounds
|
|
1020
|
+
* @param cell
|
|
1021
|
+
* @param bounds
|
|
1022
|
+
*/
|
|
1023
|
+
export const isCellWithinBounds = (cell, bounds) => {
|
|
1024
|
+
if (!bounds) {
|
|
1025
|
+
return false;
|
|
1026
|
+
}
|
|
1027
|
+
if (cell.rowIndex < bounds.startRowIndex ||
|
|
1028
|
+
cell.rowIndex > bounds.endRowIndex) {
|
|
1029
|
+
return false;
|
|
1030
|
+
}
|
|
1031
|
+
if (cell.columnIndex < bounds.startColumnIndex ||
|
|
1032
|
+
cell.columnIndex > bounds.endColumnIndex) {
|
|
1033
|
+
return false;
|
|
1034
|
+
}
|
|
1035
|
+
return true;
|
|
1036
|
+
};
|
|
1037
|
+
export const clampIndex = (index, isHidden, direction) => {
|
|
1038
|
+
switch (direction) {
|
|
1039
|
+
case Direction.Right:
|
|
1040
|
+
case Direction.Down: {
|
|
1041
|
+
let hidden = isHidden?.(index);
|
|
1042
|
+
while (hidden === true) {
|
|
1043
|
+
hidden = isHidden?.(++index);
|
|
1044
|
+
}
|
|
1045
|
+
break;
|
|
1046
|
+
}
|
|
1047
|
+
case Direction.Left:
|
|
1048
|
+
case Direction.Up: {
|
|
1049
|
+
let hidden = isHidden?.(index);
|
|
1050
|
+
while (hidden === true) {
|
|
1051
|
+
hidden = isHidden?.(--index);
|
|
1052
|
+
}
|
|
1053
|
+
break;
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
return Math.max(0, index);
|
|
1057
|
+
};
|
|
1058
|
+
/**
|
|
1059
|
+
* Find a cell with content if the current cell is out of the current dataregion
|
|
1060
|
+
* [
|
|
1061
|
+
* 1, 2, 3,
|
|
1062
|
+
* x, x, x
|
|
1063
|
+
* 7, 8, 9
|
|
1064
|
+
* ]
|
|
1065
|
+
* activeCel = 2
|
|
1066
|
+
* direction = Down
|
|
1067
|
+
* New Cell = 8
|
|
1068
|
+
*
|
|
1069
|
+
* @param activeCell
|
|
1070
|
+
* @param getValue
|
|
1071
|
+
* @param isHidden
|
|
1072
|
+
* @param direction
|
|
1073
|
+
* @param limit
|
|
1074
|
+
*/
|
|
1075
|
+
export const findNextContentfulCell = (sheetId, activeCell, getValue, isHidden, direction, limit) => {
|
|
1076
|
+
let { rowIndex, columnIndex } = activeCell;
|
|
1077
|
+
switch (direction) {
|
|
1078
|
+
case Direction.Down: {
|
|
1079
|
+
rowIndex = clampIndex(Math.min(rowIndex + 1, limit), isHidden, direction);
|
|
1080
|
+
while (isNull(getValue(sheetId, rowIndex, columnIndex)) &&
|
|
1081
|
+
rowIndex < limit) {
|
|
1082
|
+
rowIndex = clampIndex(Math.min(rowIndex + 1, limit), isHidden, direction);
|
|
1083
|
+
}
|
|
1084
|
+
return { rowIndex: rowIndex, columnIndex };
|
|
1085
|
+
}
|
|
1086
|
+
case Direction.Up: {
|
|
1087
|
+
rowIndex = clampIndex(Math.max(rowIndex - 1, limit), isHidden, direction);
|
|
1088
|
+
while (isNull(getValue(sheetId, rowIndex, columnIndex)) &&
|
|
1089
|
+
rowIndex > limit) {
|
|
1090
|
+
rowIndex = clampIndex(Math.max(rowIndex - 1, limit), isHidden, direction);
|
|
1091
|
+
}
|
|
1092
|
+
return { rowIndex, columnIndex };
|
|
1093
|
+
}
|
|
1094
|
+
case Direction.Right: {
|
|
1095
|
+
columnIndex = clampIndex(Math.min(columnIndex + 1, limit), isHidden, direction);
|
|
1096
|
+
while (isNull(getValue(sheetId, rowIndex, columnIndex)) &&
|
|
1097
|
+
columnIndex < limit) {
|
|
1098
|
+
columnIndex = clampIndex(Math.min(columnIndex + 1, limit), isHidden, direction);
|
|
1099
|
+
}
|
|
1100
|
+
return { rowIndex, columnIndex };
|
|
1101
|
+
}
|
|
1102
|
+
case Direction.Left: {
|
|
1103
|
+
columnIndex = clampIndex(Math.max(columnIndex - 1, limit), isHidden, direction);
|
|
1104
|
+
while (isNull(getValue(sheetId, rowIndex, columnIndex)) &&
|
|
1105
|
+
columnIndex > limit) {
|
|
1106
|
+
columnIndex = clampIndex(Math.max(columnIndex - 1, limit), isHidden, direction);
|
|
1107
|
+
}
|
|
1108
|
+
return { rowIndex, columnIndex };
|
|
1109
|
+
}
|
|
1110
|
+
default:
|
|
1111
|
+
return activeCell;
|
|
1112
|
+
}
|
|
1113
|
+
};
|
|
1114
|
+
/**
|
|
1115
|
+
* Find the next cell
|
|
1116
|
+
* @param activeCell
|
|
1117
|
+
* @param getValue
|
|
1118
|
+
* @param isHidden
|
|
1119
|
+
* @param direction
|
|
1120
|
+
* @param limit
|
|
1121
|
+
*/
|
|
1122
|
+
export const findLastContentfulCell = (sheetId, activeCell, getValue, isHidden, direction, limit) => {
|
|
1123
|
+
let { rowIndex, columnIndex } = activeCell;
|
|
1124
|
+
switch (direction) {
|
|
1125
|
+
case Direction.Down: {
|
|
1126
|
+
while (!isNull(getValue(sheetId, clampIndex(Math.min(rowIndex + 1, limit), isHidden, direction), columnIndex)) &&
|
|
1127
|
+
rowIndex < limit) {
|
|
1128
|
+
clampIndex(Math.min(++rowIndex, limit), isHidden, direction);
|
|
1129
|
+
}
|
|
1130
|
+
return {
|
|
1131
|
+
columnIndex,
|
|
1132
|
+
rowIndex,
|
|
1133
|
+
};
|
|
1134
|
+
}
|
|
1135
|
+
case Direction.Up: {
|
|
1136
|
+
while (!isNull(getValue(sheetId, clampIndex(Math.max(rowIndex - 1, limit), isHidden, direction), columnIndex)) &&
|
|
1137
|
+
rowIndex > limit) {
|
|
1138
|
+
rowIndex = clampIndex(Math.max(--rowIndex, limit), isHidden, direction);
|
|
1139
|
+
}
|
|
1140
|
+
return {
|
|
1141
|
+
columnIndex,
|
|
1142
|
+
rowIndex,
|
|
1143
|
+
};
|
|
1144
|
+
}
|
|
1145
|
+
case Direction.Right: {
|
|
1146
|
+
while (!isNull(getValue(sheetId, rowIndex, clampIndex(Math.min(columnIndex + 1, limit), isHidden, direction))) &&
|
|
1147
|
+
columnIndex < limit) {
|
|
1148
|
+
columnIndex = clampIndex(Math.min(++columnIndex, limit), isHidden, direction);
|
|
1149
|
+
}
|
|
1150
|
+
return {
|
|
1151
|
+
rowIndex,
|
|
1152
|
+
columnIndex,
|
|
1153
|
+
};
|
|
1154
|
+
}
|
|
1155
|
+
case Direction.Left: {
|
|
1156
|
+
while (!isNull(getValue(sheetId, rowIndex, clampIndex(Math.max(columnIndex - 1, limit), isHidden, direction))) &&
|
|
1157
|
+
columnIndex > limit) {
|
|
1158
|
+
columnIndex = clampIndex(Math.max(--columnIndex, limit), isHidden, direction);
|
|
1159
|
+
}
|
|
1160
|
+
return {
|
|
1161
|
+
rowIndex,
|
|
1162
|
+
columnIndex,
|
|
1163
|
+
};
|
|
1164
|
+
}
|
|
1165
|
+
default:
|
|
1166
|
+
return activeCell;
|
|
1167
|
+
}
|
|
1168
|
+
};
|
|
1169
|
+
/**
|
|
1170
|
+
* Ex
|
|
1171
|
+
*/
|
|
1172
|
+
export const findNextCellInDataRegion = (sheetId, activeCell, getValue, isHidden, direction, limit) => {
|
|
1173
|
+
const { rowIndex, columnIndex } = activeCell;
|
|
1174
|
+
const isCurrentCellEmpty = isNull(getValue(sheetId, rowIndex, columnIndex));
|
|
1175
|
+
const didWeReachTheEdge = (cur, next) => {
|
|
1176
|
+
return (cur && next) || (cur && !next) || (!cur && next);
|
|
1177
|
+
};
|
|
1178
|
+
switch (direction) {
|
|
1179
|
+
case Direction.Down: {
|
|
1180
|
+
const nextIndex = clampIndex(Math.min(rowIndex + 1, limit), isHidden, direction);
|
|
1181
|
+
const nextCellValue = getValue(sheetId, nextIndex, columnIndex);
|
|
1182
|
+
const isNextCellEmpty = isNull(nextCellValue);
|
|
1183
|
+
const isEdge = didWeReachTheEdge(isCurrentCellEmpty, isNextCellEmpty);
|
|
1184
|
+
const nextCell = isEdge
|
|
1185
|
+
? findNextContentfulCell(sheetId, activeCell, getValue, isHidden, direction, limit)
|
|
1186
|
+
: findLastContentfulCell(sheetId, activeCell, getValue, isHidden, direction, limit);
|
|
1187
|
+
return nextCell?.rowIndex;
|
|
1188
|
+
}
|
|
1189
|
+
case Direction.Up: {
|
|
1190
|
+
const nextIndex = clampIndex(Math.max(rowIndex - 1, limit), isHidden, direction);
|
|
1191
|
+
const nextCellValue = getValue(sheetId, nextIndex, columnIndex);
|
|
1192
|
+
const isNextCellEmpty = isNull(nextCellValue);
|
|
1193
|
+
const isEdge = didWeReachTheEdge(isCurrentCellEmpty, isNextCellEmpty);
|
|
1194
|
+
const nextCell = isEdge
|
|
1195
|
+
? findNextContentfulCell(sheetId, activeCell, getValue, isHidden, direction, limit)
|
|
1196
|
+
: findLastContentfulCell(sheetId, activeCell, getValue, isHidden, direction, limit);
|
|
1197
|
+
return nextCell?.rowIndex;
|
|
1198
|
+
}
|
|
1199
|
+
case Direction.Right: {
|
|
1200
|
+
const nextIndex = clampIndex(Math.min(columnIndex + 1, limit), isHidden, direction);
|
|
1201
|
+
const nextCellValue = getValue(sheetId, rowIndex, nextIndex);
|
|
1202
|
+
const isNextCellEmpty = isNull(nextCellValue);
|
|
1203
|
+
const isEdge = didWeReachTheEdge(isCurrentCellEmpty, isNextCellEmpty);
|
|
1204
|
+
const nextCell = isEdge
|
|
1205
|
+
? findNextContentfulCell(sheetId, activeCell, getValue, isHidden, direction, limit)
|
|
1206
|
+
: findLastContentfulCell(sheetId, activeCell, getValue, isHidden, direction, limit);
|
|
1207
|
+
return nextCell?.columnIndex;
|
|
1208
|
+
}
|
|
1209
|
+
case Direction.Left: {
|
|
1210
|
+
const nextIndex = clampIndex(Math.max(columnIndex - 1, limit), isHidden, direction);
|
|
1211
|
+
const nextCellValue = getValue(sheetId, rowIndex, nextIndex);
|
|
1212
|
+
const isNextCellEmpty = isNull(nextCellValue);
|
|
1213
|
+
const isEdge = didWeReachTheEdge(isCurrentCellEmpty, isNextCellEmpty);
|
|
1214
|
+
const nextCell = isEdge
|
|
1215
|
+
? findNextContentfulCell(sheetId, activeCell, getValue, isHidden, direction, limit)
|
|
1216
|
+
: findLastContentfulCell(sheetId, activeCell, getValue, isHidden, direction, limit);
|
|
1217
|
+
return nextCell?.columnIndex;
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
};
|
|
1221
|
+
/**
|
|
1222
|
+
* Converts a value to string
|
|
1223
|
+
* @param value
|
|
1224
|
+
*/
|
|
1225
|
+
export const castToString = (value) => {
|
|
1226
|
+
if (value === null || value === void 0)
|
|
1227
|
+
return void 0;
|
|
1228
|
+
return typeof value !== "string" ? "" + value : value;
|
|
1229
|
+
};
|
|
1230
|
+
export const isArrowKey = (key) => {
|
|
1231
|
+
return (["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].indexOf(key) !== -1);
|
|
1232
|
+
};
|
|
1233
|
+
export const getScrollIncrement = (delta, minDim = 8) => Math.max(1, Math.round(Math.abs(delta) / minDim));
|
|
1234
|
+
export const useIsomorphicLayoutEffect = canUseDOM
|
|
1235
|
+
? useLayoutEffect
|
|
1236
|
+
: useEffect;
|
|
1237
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1238
|
+
export const useCallbackRef = (callback) => {
|
|
1239
|
+
const callbackRef = useRef(callback);
|
|
1240
|
+
useIsomorphicLayoutEffect(() => {
|
|
1241
|
+
callbackRef.current = callback;
|
|
1242
|
+
});
|
|
1243
|
+
// https://github.com/facebook/react/issues/19240
|
|
1244
|
+
return useMemo(() => ((...args) => callbackRef.current?.(...args)), []);
|
|
1245
|
+
};
|
|
1246
|
+
export const getDevicePixelRatio = () => canUseDOM && typeof window !== "undefined" ? window.devicePixelRatio : 1;
|
|
1247
|
+
export const getPreviousCellInDirection = (cell, direction = Direction.Down) => {
|
|
1248
|
+
if (direction === Direction.Down) {
|
|
1249
|
+
return {
|
|
1250
|
+
...cell,
|
|
1251
|
+
rowIndex: cell.rowIndex - 1,
|
|
1252
|
+
};
|
|
1253
|
+
}
|
|
1254
|
+
if (direction === Direction.Right) {
|
|
1255
|
+
return {
|
|
1256
|
+
...cell,
|
|
1257
|
+
columnIndex: cell.columnIndex - 1,
|
|
1258
|
+
};
|
|
1259
|
+
}
|
|
1260
|
+
};
|