@rowsncolumns/grid 9.0.4 → 13.2.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (208) hide show
  1. package/LICENSE.md +100 -0
  2. package/Readme.md +53 -59
  3. package/dist/cjs/Cell.d.ts +35 -0
  4. package/dist/cjs/Cell.d.ts.map +1 -0
  5. package/dist/cjs/Cell.js +29 -0
  6. package/dist/{CellOverlay.d.ts → cjs/CellOverlay.d.ts} +5 -5
  7. package/dist/cjs/CellOverlay.d.ts.map +1 -0
  8. package/dist/cjs/CellOverlay.js +126 -0
  9. package/dist/cjs/Context.d.ts +8 -0
  10. package/dist/cjs/Context.d.ts.map +1 -0
  11. package/dist/cjs/Context.js +13 -0
  12. package/dist/{FillHandle.d.ts → cjs/FillHandle.d.ts} +2 -1
  13. package/dist/cjs/FillHandle.d.ts.map +1 -0
  14. package/dist/cjs/FillHandle.js +26 -0
  15. package/dist/cjs/Grid.d.ts +558 -0
  16. package/dist/cjs/Grid.d.ts.map +1 -0
  17. package/dist/cjs/Grid.js +2446 -0
  18. package/dist/{GridLine.d.ts → cjs/GridLine.d.ts} +2 -1
  19. package/dist/cjs/GridLine.d.ts.map +1 -0
  20. package/dist/cjs/GridLine.js +10 -0
  21. package/dist/cjs/Image.d.ts +11 -0
  22. package/dist/cjs/Image.d.ts.map +1 -0
  23. package/dist/cjs/Image.js +57 -0
  24. package/dist/cjs/MemoizedGrid.d.ts +49 -0
  25. package/dist/cjs/MemoizedGrid.d.ts.map +1 -0
  26. package/dist/cjs/MemoizedGrid.js +105 -0
  27. package/dist/cjs/Selection.d.ts +5 -0
  28. package/dist/cjs/Selection.d.ts.map +1 -0
  29. package/dist/cjs/Selection.js +10 -0
  30. package/dist/{helpers.d.ts → cjs/helpers.d.ts} +59 -39
  31. package/dist/cjs/helpers.d.ts.map +1 -0
  32. package/dist/cjs/helpers.js +1409 -0
  33. package/dist/{hooks → cjs/hooks}/useCopyPaste.d.ts +3 -2
  34. package/dist/cjs/hooks/useCopyPaste.d.ts.map +1 -0
  35. package/dist/cjs/hooks/useCopyPaste.js +230 -0
  36. package/dist/{hooks → cjs/hooks}/useEditable.d.ts +21 -8
  37. package/dist/cjs/hooks/useEditable.d.ts.map +1 -0
  38. package/dist/cjs/hooks/useEditable.js +491 -0
  39. package/dist/{hooks → cjs/hooks}/useFilter.d.ts +3 -2
  40. package/dist/cjs/hooks/useFilter.d.ts.map +1 -0
  41. package/dist/cjs/hooks/useFilter.js +97 -0
  42. package/dist/{hooks → cjs/hooks}/useImage.d.ts +1 -0
  43. package/dist/cjs/hooks/useImage.d.ts.map +1 -0
  44. package/dist/cjs/hooks/useImage.js +80 -0
  45. package/dist/{hooks → cjs/hooks}/usePagination.d.ts +3 -2
  46. package/dist/cjs/hooks/usePagination.d.ts.map +1 -0
  47. package/dist/cjs/hooks/usePagination.js +53 -0
  48. package/dist/{hooks → cjs/hooks}/useSizer.d.ts +28 -29
  49. package/dist/cjs/hooks/useSizer.d.ts.map +1 -0
  50. package/dist/cjs/hooks/useSizer.js +176 -0
  51. package/dist/{hooks → cjs/hooks}/useTooltip.d.ts +2 -1
  52. package/dist/cjs/hooks/useTooltip.d.ts.map +1 -0
  53. package/dist/cjs/hooks/useTooltip.js +82 -0
  54. package/dist/{hooks → cjs/hooks}/useUndo.d.ts +2 -1
  55. package/dist/cjs/hooks/useUndo.d.ts.map +1 -0
  56. package/dist/cjs/hooks/useUndo.js +87 -0
  57. package/dist/cjs/index.d.ts +19 -0
  58. package/dist/cjs/index.d.ts.map +1 -0
  59. package/dist/cjs/index.js +29 -0
  60. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -0
  61. package/dist/{types.d.ts → cjs/types.d.ts} +8 -3
  62. package/dist/cjs/types.d.ts.map +1 -0
  63. package/dist/{types.js → cjs/types.js} +7 -8
  64. package/dist/cjs/utils.d.ts +9 -0
  65. package/dist/cjs/utils.d.ts.map +1 -0
  66. package/dist/cjs/utils.js +58 -0
  67. package/dist/cjs/vitest.config.d.ts +3 -0
  68. package/dist/cjs/vitest.config.d.ts.map +1 -0
  69. package/dist/cjs/vitest.config.js +24 -0
  70. package/dist/esm/Cell.d.ts +35 -0
  71. package/dist/esm/Cell.d.ts.map +1 -0
  72. package/dist/esm/Cell.js +24 -0
  73. package/dist/esm/CellOverlay.d.ts +21 -0
  74. package/dist/esm/CellOverlay.d.ts.map +1 -0
  75. package/dist/esm/CellOverlay.js +120 -0
  76. package/dist/esm/Context.d.ts +8 -0
  77. package/dist/esm/Context.d.ts.map +1 -0
  78. package/dist/esm/Context.js +9 -0
  79. package/dist/esm/FillHandle.d.ts +8 -0
  80. package/dist/esm/FillHandle.d.ts.map +1 -0
  81. package/dist/{FillHandle.js → esm/FillHandle.js} +5 -11
  82. package/dist/esm/Grid.d.ts +558 -0
  83. package/dist/esm/Grid.d.ts.map +1 -0
  84. package/dist/esm/Grid.js +2451 -0
  85. package/dist/esm/GridLine.d.ts +5 -0
  86. package/dist/esm/GridLine.d.ts.map +1 -0
  87. package/dist/esm/GridLine.js +7 -0
  88. package/dist/esm/Image.d.ts +11 -0
  89. package/dist/esm/Image.d.ts.map +1 -0
  90. package/dist/esm/Image.js +53 -0
  91. package/dist/esm/MemoizedGrid.d.ts +49 -0
  92. package/dist/esm/MemoizedGrid.d.ts.map +1 -0
  93. package/dist/esm/MemoizedGrid.js +98 -0
  94. package/dist/esm/Selection.d.ts +5 -0
  95. package/dist/esm/Selection.d.ts.map +1 -0
  96. package/dist/esm/Selection.js +7 -0
  97. package/dist/esm/helpers.d.ts +281 -0
  98. package/dist/esm/helpers.d.ts.map +1 -0
  99. package/dist/esm/helpers.js +1260 -0
  100. package/dist/esm/hooks/useCopyPaste.d.ts +53 -0
  101. package/dist/esm/hooks/useCopyPaste.d.ts.map +1 -0
  102. package/dist/{hooks → esm/hooks}/useCopyPaste.js +40 -46
  103. package/dist/esm/hooks/useEditable.d.ts +253 -0
  104. package/dist/esm/hooks/useEditable.d.ts.map +1 -0
  105. package/dist/esm/hooks/useEditable.js +482 -0
  106. package/dist/esm/hooks/useFilter.d.ts +69 -0
  107. package/dist/esm/hooks/useFilter.d.ts.map +1 -0
  108. package/dist/{hooks → esm/hooks}/useFilter.js +22 -42
  109. package/dist/esm/hooks/useImage.d.ts +13 -0
  110. package/dist/esm/hooks/useImage.d.ts.map +1 -0
  111. package/dist/esm/hooks/useImage.js +76 -0
  112. package/dist/esm/hooks/usePagination.d.ts +69 -0
  113. package/dist/esm/hooks/usePagination.d.ts.map +1 -0
  114. package/dist/esm/hooks/usePagination.js +47 -0
  115. package/dist/esm/hooks/useSizer.d.ts +114 -0
  116. package/dist/esm/hooks/useSizer.d.ts.map +1 -0
  117. package/dist/esm/hooks/useSizer.js +176 -0
  118. package/dist/esm/hooks/useTooltip.d.ts +43 -0
  119. package/dist/esm/hooks/useTooltip.d.ts.map +1 -0
  120. package/dist/esm/hooks/useTooltip.js +80 -0
  121. package/dist/esm/hooks/useUndo.d.ts +36 -0
  122. package/dist/esm/hooks/useUndo.d.ts.map +1 -0
  123. package/dist/{hooks → esm/hooks}/useUndo.js +16 -25
  124. package/dist/esm/index.d.ts +19 -0
  125. package/dist/esm/index.d.ts.map +1 -0
  126. package/dist/esm/index.js +17 -0
  127. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -0
  128. package/dist/esm/types.d.ts +70 -0
  129. package/dist/esm/types.d.ts.map +1 -0
  130. package/dist/esm/types.js +68 -0
  131. package/dist/esm/utils.d.ts +9 -0
  132. package/dist/esm/utils.d.ts.map +1 -0
  133. package/dist/esm/utils.js +104 -0
  134. package/dist/esm/vitest.config.d.ts +3 -0
  135. package/dist/esm/vitest.config.d.ts.map +1 -0
  136. package/dist/esm/vitest.config.js +22 -0
  137. package/package.json +28 -29
  138. package/dist/Cell.d.ts +0 -23
  139. package/dist/Cell.js +0 -55
  140. package/dist/Cell.js.map +0 -1
  141. package/dist/CellOverlay.js +0 -116
  142. package/dist/CellOverlay.js.map +0 -1
  143. package/dist/FillHandle.js.map +0 -1
  144. package/dist/Grid.d.ts +0 -385
  145. package/dist/Grid.js +0 -1684
  146. package/dist/Grid.js.map +0 -1
  147. package/dist/GridLine.js +0 -13
  148. package/dist/GridLine.js.map +0 -1
  149. package/dist/Image.d.ts +0 -8
  150. package/dist/Image.js +0 -50
  151. package/dist/Image.js.map +0 -1
  152. package/dist/Selection.d.ts +0 -4
  153. package/dist/Selection.js +0 -9
  154. package/dist/Selection.js.map +0 -1
  155. package/dist/helpers.js +0 -1033
  156. package/dist/helpers.js.map +0 -1
  157. package/dist/hooks/useCopyPaste.js.map +0 -1
  158. package/dist/hooks/useEditable.js +0 -477
  159. package/dist/hooks/useEditable.js.map +0 -1
  160. package/dist/hooks/useFilter.js.map +0 -1
  161. package/dist/hooks/useImage.js +0 -42
  162. package/dist/hooks/useImage.js.map +0 -1
  163. package/dist/hooks/usePagination.js +0 -73
  164. package/dist/hooks/usePagination.js.map +0 -1
  165. package/dist/hooks/useSelection.d.ts +0 -175
  166. package/dist/hooks/useSelection.js +0 -982
  167. package/dist/hooks/useSelection.js.map +0 -1
  168. package/dist/hooks/useSizer.js +0 -150
  169. package/dist/hooks/useSizer.js.map +0 -1
  170. package/dist/hooks/useTooltip.js +0 -106
  171. package/dist/hooks/useTooltip.js.map +0 -1
  172. package/dist/hooks/useTouch.d.ts +0 -21
  173. package/dist/hooks/useTouch.js +0 -73
  174. package/dist/hooks/useTouch.js.map +0 -1
  175. package/dist/hooks/useUndo.js.map +0 -1
  176. package/dist/index.d.ts +0 -25
  177. package/dist/index.js +0 -61
  178. package/dist/index.js.map +0 -1
  179. package/dist/types.js.map +0 -1
  180. package/dist/utils.d.ts +0 -8
  181. package/dist/utils.js +0 -153
  182. package/dist/utils.js.map +0 -1
  183. package/src/Cell.tsx +0 -100
  184. package/src/CellOverlay.tsx +0 -191
  185. package/src/FillHandle.tsx +0 -37
  186. package/src/Grid.tsx +0 -2677
  187. package/src/GridLine.tsx +0 -23
  188. package/src/Image.tsx +0 -43
  189. package/src/Selection.tsx +0 -9
  190. package/src/helpers.ts +0 -1455
  191. package/src/hooks/useCopyPaste.ts +0 -265
  192. package/src/hooks/useEditable.tsx +0 -945
  193. package/src/hooks/useFilter.tsx +0 -233
  194. package/src/hooks/useImage.ts +0 -57
  195. package/src/hooks/usePagination.tsx +0 -153
  196. package/src/hooks/useSelection.ts +0 -1440
  197. package/src/hooks/useSizer.ts +0 -312
  198. package/src/hooks/useTooltip.tsx +0 -172
  199. package/src/hooks/useTouch.ts +0 -116
  200. package/src/hooks/useUndo.ts +0 -134
  201. package/src/index.tsx +0 -46
  202. package/src/tests/Grid.spec.tsx +0 -47
  203. package/src/tests/helpers.test.ts +0 -292
  204. package/src/tests/usePagination.test.ts +0 -28
  205. package/src/tests/useSelection.test.ts +0 -27
  206. package/src/tests/useUndo.test.ts +0 -73
  207. package/src/types.ts +0 -69
  208. package/src/utils.tsx +0 -287
@@ -0,0 +1,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
+ };