@dynamic-scroll/core 1.2.1
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/dist/index.d.mts +346 -0
- package/dist/index.d.ts +346 -0
- package/dist/index.js +893 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +880 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +64 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,893 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
var reactDom = require('react-dom');
|
|
5
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
6
|
+
|
|
7
|
+
// src/DynamicScroll.tsx
|
|
8
|
+
function useHeightMap({
|
|
9
|
+
items,
|
|
10
|
+
estimatedItemSize
|
|
11
|
+
}) {
|
|
12
|
+
const heightMapRef = react.useRef(/* @__PURE__ */ new Map());
|
|
13
|
+
const pendingIdsRef = react.useRef(/* @__PURE__ */ new Set());
|
|
14
|
+
const batchRafRef = react.useRef(null);
|
|
15
|
+
const [version, setVersion] = react.useState(0);
|
|
16
|
+
const unmeasuredIds = [];
|
|
17
|
+
const currentIds = /* @__PURE__ */ new Set();
|
|
18
|
+
if (estimatedItemSize === void 0) {
|
|
19
|
+
for (const item of items) {
|
|
20
|
+
currentIds.add(item.id);
|
|
21
|
+
if (!heightMapRef.current.has(item.id)) {
|
|
22
|
+
unmeasuredIds.push(item.id);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
for (const id of pendingIdsRef.current) {
|
|
26
|
+
if (!currentIds.has(id)) {
|
|
27
|
+
pendingIdsRef.current.delete(id);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
for (const id of unmeasuredIds) {
|
|
31
|
+
pendingIdsRef.current.add(id);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const isAllMeasured = estimatedItemSize !== void 0 || items.length > 0 && unmeasuredIds.length === 0;
|
|
35
|
+
const onItemMeasured = react.useCallback((id, height) => {
|
|
36
|
+
heightMapRef.current.set(id, height);
|
|
37
|
+
pendingIdsRef.current.delete(id);
|
|
38
|
+
if (pendingIdsRef.current.size === 0) {
|
|
39
|
+
setVersion((v) => v + 1);
|
|
40
|
+
}
|
|
41
|
+
}, []);
|
|
42
|
+
const onHeightChange = react.useCallback((id, height) => {
|
|
43
|
+
const cur = heightMapRef.current.get(id);
|
|
44
|
+
if (cur === height) return;
|
|
45
|
+
heightMapRef.current.set(id, height);
|
|
46
|
+
if (batchRafRef.current === null) {
|
|
47
|
+
batchRafRef.current = requestAnimationFrame(() => {
|
|
48
|
+
batchRafRef.current = null;
|
|
49
|
+
setVersion((v) => v + 1);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}, []);
|
|
53
|
+
return {
|
|
54
|
+
heightMapRef,
|
|
55
|
+
isAllMeasured,
|
|
56
|
+
unmeasuredIds,
|
|
57
|
+
onItemMeasured,
|
|
58
|
+
onHeightChange,
|
|
59
|
+
version
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function usePositions({
|
|
63
|
+
items,
|
|
64
|
+
heightMapRef,
|
|
65
|
+
version,
|
|
66
|
+
estimatedItemSize
|
|
67
|
+
}) {
|
|
68
|
+
const childPositions = react.useMemo(() => {
|
|
69
|
+
const positions = [0];
|
|
70
|
+
for (let i = 0; i < items.length; i++) {
|
|
71
|
+
const height = heightMapRef.current.get(items[i].id) ?? estimatedItemSize ?? 0;
|
|
72
|
+
positions.push(positions[i] + height);
|
|
73
|
+
}
|
|
74
|
+
return positions;
|
|
75
|
+
}, [items, version, estimatedItemSize]);
|
|
76
|
+
const totalHeight = childPositions.length > 1 ? childPositions[childPositions.length - 1] : 0;
|
|
77
|
+
return { childPositions, totalHeight };
|
|
78
|
+
}
|
|
79
|
+
function useGroupPositions(items, groupBy, heightMapRef, version) {
|
|
80
|
+
return react.useMemo(() => {
|
|
81
|
+
if (!groupBy || items.length === 0) return null;
|
|
82
|
+
const heightByGroup = /* @__PURE__ */ new Map();
|
|
83
|
+
const groupKeyByIndex = [];
|
|
84
|
+
const groupOrder = [];
|
|
85
|
+
for (let i = 0; i < items.length; i++) {
|
|
86
|
+
const key = groupBy(items[i]);
|
|
87
|
+
groupKeyByIndex.push(key);
|
|
88
|
+
const itemHeight = heightMapRef.current.get(items[i].id) ?? 0;
|
|
89
|
+
const current = heightByGroup.get(key) ?? 0;
|
|
90
|
+
heightByGroup.set(key, current + itemHeight);
|
|
91
|
+
if (!groupOrder.includes(key)) {
|
|
92
|
+
groupOrder.push(key);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const cumulativeHeightByGroup = /* @__PURE__ */ new Map();
|
|
96
|
+
let cumulative = 0;
|
|
97
|
+
for (let i = groupOrder.length - 1; i >= 0; i--) {
|
|
98
|
+
cumulativeHeightByGroup.set(groupOrder[i], cumulative);
|
|
99
|
+
cumulative += heightByGroup.get(groupOrder[i]) ?? 0;
|
|
100
|
+
}
|
|
101
|
+
return { heightByGroup, cumulativeHeightByGroup, groupKeyByIndex };
|
|
102
|
+
}, [items, groupBy, version]);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// src/utils/binarySearch.ts
|
|
106
|
+
function generateStartNodeIndex(scrollTop, nodePositions, itemCount) {
|
|
107
|
+
let startRange = 0;
|
|
108
|
+
let endRange = itemCount - 1;
|
|
109
|
+
if (endRange < 0) return 0;
|
|
110
|
+
if (scrollTop <= 0) return 0;
|
|
111
|
+
while (endRange !== startRange) {
|
|
112
|
+
const middle = Math.floor((endRange - startRange) / 2 + startRange);
|
|
113
|
+
const nodeCenter = (nodePositions[middle] + nodePositions[middle + 1]) / 2;
|
|
114
|
+
if (nodeCenter <= scrollTop && nodePositions[middle + 1] > scrollTop) {
|
|
115
|
+
return middle;
|
|
116
|
+
}
|
|
117
|
+
if (middle === startRange) {
|
|
118
|
+
return endRange;
|
|
119
|
+
}
|
|
120
|
+
if (nodeCenter <= scrollTop) {
|
|
121
|
+
startRange = middle;
|
|
122
|
+
} else {
|
|
123
|
+
endRange = middle;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return startRange;
|
|
127
|
+
}
|
|
128
|
+
function generateEndNodeIndex(nodePositions, startNodeIndex, itemCount, viewportHeight) {
|
|
129
|
+
if (itemCount === 0) return 0;
|
|
130
|
+
const basePosition = nodePositions[startNodeIndex + 1] ?? nodePositions[startNodeIndex];
|
|
131
|
+
const targetPosition = basePosition + viewportHeight;
|
|
132
|
+
let low = startNodeIndex;
|
|
133
|
+
let high = itemCount - 1;
|
|
134
|
+
if (nodePositions[high] <= targetPosition) {
|
|
135
|
+
return high;
|
|
136
|
+
}
|
|
137
|
+
while (low < high) {
|
|
138
|
+
const mid = Math.floor((low + high) / 2);
|
|
139
|
+
if (nodePositions[mid] <= targetPosition) {
|
|
140
|
+
low = mid + 1;
|
|
141
|
+
} else {
|
|
142
|
+
high = mid;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return low;
|
|
146
|
+
}
|
|
147
|
+
function generateCenteredStartNodeIndex(nodePositions, targetRowHeight, targetRowIndex, viewportHeight) {
|
|
148
|
+
if (!nodePositions || nodePositions.length === 0) return 0;
|
|
149
|
+
const totalHeight = nodePositions[targetRowIndex] + targetRowHeight;
|
|
150
|
+
if (totalHeight <= viewportHeight) return 0;
|
|
151
|
+
const viewHalf = Math.floor(viewportHeight / 2);
|
|
152
|
+
let start = 0;
|
|
153
|
+
let end = targetRowIndex;
|
|
154
|
+
let answer = 0;
|
|
155
|
+
let closestDistance = Infinity;
|
|
156
|
+
while (start <= end) {
|
|
157
|
+
const middle = Math.floor((end - start) / 2 + start);
|
|
158
|
+
const middleHeight = nodePositions[targetRowIndex] - nodePositions[middle] + targetRowHeight;
|
|
159
|
+
const distance = Math.abs(middleHeight - viewHalf);
|
|
160
|
+
if (distance < closestDistance) {
|
|
161
|
+
closestDistance = distance;
|
|
162
|
+
answer = middle;
|
|
163
|
+
}
|
|
164
|
+
if (viewHalf < middleHeight) {
|
|
165
|
+
start = middle + 1;
|
|
166
|
+
} else {
|
|
167
|
+
end = middle - 1;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return answer;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// src/hooks/useScrollState.ts
|
|
174
|
+
function useScrollState({
|
|
175
|
+
scrollTop,
|
|
176
|
+
itemCount,
|
|
177
|
+
overscanCount = 8,
|
|
178
|
+
viewportHeight,
|
|
179
|
+
childPositions
|
|
180
|
+
}) {
|
|
181
|
+
const firstVisibleNode = react.useMemo(
|
|
182
|
+
() => generateStartNodeIndex(scrollTop, childPositions, itemCount),
|
|
183
|
+
[scrollTop, childPositions, itemCount]
|
|
184
|
+
);
|
|
185
|
+
const lastVisibleNode = react.useMemo(
|
|
186
|
+
() => generateEndNodeIndex(
|
|
187
|
+
childPositions,
|
|
188
|
+
firstVisibleNode,
|
|
189
|
+
itemCount,
|
|
190
|
+
viewportHeight
|
|
191
|
+
),
|
|
192
|
+
[childPositions, firstVisibleNode, itemCount, viewportHeight]
|
|
193
|
+
);
|
|
194
|
+
const startNode = Math.max(0, firstVisibleNode - overscanCount);
|
|
195
|
+
const endNode = Math.min(itemCount - 1, lastVisibleNode + overscanCount);
|
|
196
|
+
const visibleNodeCount = Math.max(0, endNode - startNode + 1);
|
|
197
|
+
return {
|
|
198
|
+
firstVisibleNode,
|
|
199
|
+
lastVisibleNode,
|
|
200
|
+
startNode,
|
|
201
|
+
endNode,
|
|
202
|
+
visibleNodeCount
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
function MeasureInner({
|
|
206
|
+
children,
|
|
207
|
+
itemId,
|
|
208
|
+
position,
|
|
209
|
+
onHeightChange,
|
|
210
|
+
knownHeight
|
|
211
|
+
}) {
|
|
212
|
+
const ref = react.useRef(null);
|
|
213
|
+
const prevHeightRef = react.useRef(knownHeight ?? 0);
|
|
214
|
+
const [heightLocked, setHeightLocked] = react.useState(!!knownHeight);
|
|
215
|
+
react.useEffect(() => {
|
|
216
|
+
const node = ref.current;
|
|
217
|
+
if (!node) return;
|
|
218
|
+
const measure = () => {
|
|
219
|
+
const newHeight = Math.ceil(node.offsetHeight);
|
|
220
|
+
if (newHeight === 0 || prevHeightRef.current === newHeight) return;
|
|
221
|
+
const oldHeight = prevHeightRef.current;
|
|
222
|
+
prevHeightRef.current = newHeight;
|
|
223
|
+
console.log(`[Measure] ${itemId} height changed: ${oldHeight} \u2192 ${newHeight} (locked: ${heightLocked}, knownHeight: ${knownHeight})`);
|
|
224
|
+
onHeightChange(itemId, newHeight);
|
|
225
|
+
};
|
|
226
|
+
const resizeObserver = new ResizeObserver(measure);
|
|
227
|
+
resizeObserver.observe(node);
|
|
228
|
+
return () => {
|
|
229
|
+
resizeObserver.disconnect();
|
|
230
|
+
};
|
|
231
|
+
}, [itemId, onHeightChange]);
|
|
232
|
+
react.useEffect(() => {
|
|
233
|
+
if (!heightLocked) return;
|
|
234
|
+
const node = ref.current;
|
|
235
|
+
if (!node) return;
|
|
236
|
+
const mutationObserver = new MutationObserver(() => {
|
|
237
|
+
console.log(`[Measure] ${itemId} mutation detected \u2192 unlocking height (was ${knownHeight}px)`);
|
|
238
|
+
setHeightLocked(false);
|
|
239
|
+
});
|
|
240
|
+
mutationObserver.observe(node, {
|
|
241
|
+
childList: true,
|
|
242
|
+
subtree: false
|
|
243
|
+
});
|
|
244
|
+
return () => {
|
|
245
|
+
mutationObserver.disconnect();
|
|
246
|
+
};
|
|
247
|
+
}, [heightLocked]);
|
|
248
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
249
|
+
"div",
|
|
250
|
+
{
|
|
251
|
+
"data-dynamic-scroll-item": itemId,
|
|
252
|
+
style: {
|
|
253
|
+
position: "absolute",
|
|
254
|
+
top: position,
|
|
255
|
+
left: 0,
|
|
256
|
+
width: "100%",
|
|
257
|
+
...heightLocked && knownHeight ? { height: knownHeight, overflow: "hidden" } : {}
|
|
258
|
+
},
|
|
259
|
+
ref,
|
|
260
|
+
children
|
|
261
|
+
}
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
var Measure = react.memo(MeasureInner);
|
|
265
|
+
var DEFAULT_OVERSCAN = 8;
|
|
266
|
+
function VirtualScrollInner({
|
|
267
|
+
items,
|
|
268
|
+
renderItem,
|
|
269
|
+
childPositions,
|
|
270
|
+
totalHeight,
|
|
271
|
+
heightMapRef,
|
|
272
|
+
onHeightChange,
|
|
273
|
+
overscanCount = DEFAULT_OVERSCAN,
|
|
274
|
+
onStartReached,
|
|
275
|
+
onEndReached,
|
|
276
|
+
threshold = 0,
|
|
277
|
+
onAtBottomChange,
|
|
278
|
+
syncScrollUpdates = false,
|
|
279
|
+
className,
|
|
280
|
+
style,
|
|
281
|
+
isMeasuring = false,
|
|
282
|
+
loadingComponent,
|
|
283
|
+
bottomLoadingComponent,
|
|
284
|
+
initialScrollPosition = "bottom",
|
|
285
|
+
groupInfo,
|
|
286
|
+
renderGroupHeader
|
|
287
|
+
}, ref) {
|
|
288
|
+
const containerRef = react.useRef(null);
|
|
289
|
+
const [scrollTop, setScrollTop] = react.useState(0);
|
|
290
|
+
const [viewportHeight, setViewportHeight] = react.useState(0);
|
|
291
|
+
const animationFrameRef = react.useRef(null);
|
|
292
|
+
const isAtBottomRef = react.useRef(true);
|
|
293
|
+
const mountedRef = react.useRef(false);
|
|
294
|
+
const programmaticScrollRef = react.useRef(false);
|
|
295
|
+
const backwardLoadingRef = react.useRef(false);
|
|
296
|
+
const prevScrollHeightRef = react.useRef(0);
|
|
297
|
+
const forwardLoadingRef = react.useRef(false);
|
|
298
|
+
react.useLayoutEffect(() => {
|
|
299
|
+
const el = containerRef.current;
|
|
300
|
+
if (!el) return;
|
|
301
|
+
setViewportHeight(el.clientHeight);
|
|
302
|
+
}, []);
|
|
303
|
+
react.useLayoutEffect(() => {
|
|
304
|
+
const el = containerRef.current;
|
|
305
|
+
if (!el || mountedRef.current || totalHeight <= 0) return;
|
|
306
|
+
mountedRef.current = true;
|
|
307
|
+
if (initialScrollPosition === "top") {
|
|
308
|
+
el.scrollTop = 0;
|
|
309
|
+
setScrollTop(0);
|
|
310
|
+
isAtBottomRef.current = false;
|
|
311
|
+
onAtBottomChange?.(false);
|
|
312
|
+
} else if (initialScrollPosition === "bottom") {
|
|
313
|
+
el.scrollTop = el.scrollHeight;
|
|
314
|
+
setScrollTop(el.scrollTop);
|
|
315
|
+
isAtBottomRef.current = true;
|
|
316
|
+
onAtBottomChange?.(true);
|
|
317
|
+
} else {
|
|
318
|
+
const { index, align = "center" } = initialScrollPosition;
|
|
319
|
+
if (index >= 0 && index < items.length) {
|
|
320
|
+
const itemTop = childPositions[index];
|
|
321
|
+
const itemHeight = heightMapRef.current.get(items[index].id) ?? 0;
|
|
322
|
+
let target;
|
|
323
|
+
if (align === "start") {
|
|
324
|
+
target = itemTop;
|
|
325
|
+
} else if (align === "end") {
|
|
326
|
+
target = itemTop + itemHeight - el.clientHeight;
|
|
327
|
+
} else {
|
|
328
|
+
target = itemTop + itemHeight / 2 - el.clientHeight / 2;
|
|
329
|
+
}
|
|
330
|
+
el.scrollTop = Math.max(0, target);
|
|
331
|
+
setScrollTop(el.scrollTop);
|
|
332
|
+
isAtBottomRef.current = false;
|
|
333
|
+
onAtBottomChange?.(false);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}, [totalHeight, onAtBottomChange, initialScrollPosition, items, childPositions, heightMapRef]);
|
|
337
|
+
const { startNode, endNode } = useScrollState({
|
|
338
|
+
scrollTop,
|
|
339
|
+
itemCount: items.length,
|
|
340
|
+
overscanCount,
|
|
341
|
+
viewportHeight,
|
|
342
|
+
childPositions
|
|
343
|
+
});
|
|
344
|
+
const onScroll = react.useCallback(
|
|
345
|
+
(e) => {
|
|
346
|
+
if (animationFrameRef.current !== null) {
|
|
347
|
+
cancelAnimationFrame(animationFrameRef.current);
|
|
348
|
+
}
|
|
349
|
+
animationFrameRef.current = requestAnimationFrame(() => {
|
|
350
|
+
animationFrameRef.current = null;
|
|
351
|
+
const target = e.target;
|
|
352
|
+
const newScrollTop = target.scrollTop;
|
|
353
|
+
if (syncScrollUpdates) {
|
|
354
|
+
reactDom.flushSync(() => setScrollTop(newScrollTop));
|
|
355
|
+
} else {
|
|
356
|
+
setScrollTop(newScrollTop);
|
|
357
|
+
}
|
|
358
|
+
if (programmaticScrollRef.current) return;
|
|
359
|
+
const atBottom = target.scrollHeight - newScrollTop - target.clientHeight <= 1;
|
|
360
|
+
if (isAtBottomRef.current !== atBottom) {
|
|
361
|
+
isAtBottomRef.current = atBottom;
|
|
362
|
+
onAtBottomChange?.(atBottom);
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
},
|
|
366
|
+
[syncScrollUpdates, onAtBottomChange]
|
|
367
|
+
);
|
|
368
|
+
react.useEffect(() => {
|
|
369
|
+
const el = containerRef.current;
|
|
370
|
+
if (!el) return;
|
|
371
|
+
el.addEventListener("scroll", onScroll);
|
|
372
|
+
return () => el.removeEventListener("scroll", onScroll);
|
|
373
|
+
}, [onScroll]);
|
|
374
|
+
const scrollToBottom = react.useCallback(
|
|
375
|
+
(behavior = "auto") => {
|
|
376
|
+
const el = containerRef.current;
|
|
377
|
+
if (!el || items.length === 0) return;
|
|
378
|
+
el.scrollTo({ top: el.scrollHeight, left: 0, behavior });
|
|
379
|
+
isAtBottomRef.current = true;
|
|
380
|
+
onAtBottomChange?.(true);
|
|
381
|
+
},
|
|
382
|
+
[items.length, onAtBottomChange]
|
|
383
|
+
);
|
|
384
|
+
const scrollToItem = react.useCallback(
|
|
385
|
+
(index, align = "center") => {
|
|
386
|
+
const el = containerRef.current;
|
|
387
|
+
if (!el || index < 0 || index >= items.length) {
|
|
388
|
+
console.warn("[scrollToItem] SKIP", { index, itemsLength: items.length, hasEl: !!el });
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
const itemTop = childPositions[index];
|
|
392
|
+
const itemHeight = heightMapRef.current.get(items[index].id) ?? 0;
|
|
393
|
+
let target;
|
|
394
|
+
if (align === "start") {
|
|
395
|
+
target = itemTop;
|
|
396
|
+
} else if (align === "end") {
|
|
397
|
+
target = itemTop + itemHeight - el.clientHeight;
|
|
398
|
+
} else {
|
|
399
|
+
target = itemTop + itemHeight / 2 - el.clientHeight / 2;
|
|
400
|
+
}
|
|
401
|
+
const maxScrollTop = el.scrollHeight - el.clientHeight;
|
|
402
|
+
const finalTarget = Math.max(0, Math.min(target, maxScrollTop));
|
|
403
|
+
console.log("[scrollToItem]", {
|
|
404
|
+
index,
|
|
405
|
+
align,
|
|
406
|
+
itemId: items[index].id,
|
|
407
|
+
itemTop,
|
|
408
|
+
itemHeight,
|
|
409
|
+
target,
|
|
410
|
+
finalTarget,
|
|
411
|
+
viewportHeight: el.clientHeight,
|
|
412
|
+
scrollHeight: el.scrollHeight,
|
|
413
|
+
totalHeight,
|
|
414
|
+
maxScrollTop
|
|
415
|
+
});
|
|
416
|
+
programmaticScrollRef.current = true;
|
|
417
|
+
isAtBottomRef.current = false;
|
|
418
|
+
el.scrollTo({ top: finalTarget, left: 0, behavior: "auto" });
|
|
419
|
+
requestAnimationFrame(() => {
|
|
420
|
+
programmaticScrollRef.current = false;
|
|
421
|
+
});
|
|
422
|
+
},
|
|
423
|
+
[childPositions, items, heightMapRef]
|
|
424
|
+
);
|
|
425
|
+
react.useImperativeHandle(ref, () => ({
|
|
426
|
+
scrollToItem,
|
|
427
|
+
scrollToBottom,
|
|
428
|
+
scrollToOffset: (offset, behavior = "auto") => {
|
|
429
|
+
containerRef.current?.scrollTo({ top: offset, left: 0, behavior });
|
|
430
|
+
},
|
|
431
|
+
getScrollOffset: () => containerRef.current?.scrollTop ?? 0
|
|
432
|
+
}));
|
|
433
|
+
react.useLayoutEffect(() => {
|
|
434
|
+
if (!mountedRef.current) return;
|
|
435
|
+
const el = containerRef.current;
|
|
436
|
+
if (!el) return;
|
|
437
|
+
if (prevScrollHeightRef.current > 0 && !isMeasuring) {
|
|
438
|
+
const diff = el.scrollHeight - prevScrollHeightRef.current;
|
|
439
|
+
console.log("[totalHeight] backward adjust", { diff, prevScrollHeight: prevScrollHeightRef.current, newScrollHeight: el.scrollHeight });
|
|
440
|
+
if (diff > 0) {
|
|
441
|
+
el.scrollTop += diff;
|
|
442
|
+
setScrollTop(el.scrollTop);
|
|
443
|
+
}
|
|
444
|
+
prevScrollHeightRef.current = 0;
|
|
445
|
+
backwardLoadingRef.current = false;
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
if (forwardLoadingRef.current) {
|
|
449
|
+
if (!isMeasuring) {
|
|
450
|
+
forwardLoadingRef.current = false;
|
|
451
|
+
}
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
if (isAtBottomRef.current && !isMeasuring) {
|
|
455
|
+
console.log("[totalHeight] stick-to-bottom", { scrollHeight: el.scrollHeight });
|
|
456
|
+
el.scrollTop = el.scrollHeight;
|
|
457
|
+
}
|
|
458
|
+
}, [totalHeight, isMeasuring]);
|
|
459
|
+
react.useLayoutEffect(() => {
|
|
460
|
+
const el = containerRef.current;
|
|
461
|
+
if (!el || !mountedRef.current || programmaticScrollRef.current) return;
|
|
462
|
+
const actualScrollTop = el.scrollTop;
|
|
463
|
+
if (actualScrollTop <= threshold && onStartReached && !backwardLoadingRef.current && prevScrollHeightRef.current === 0) {
|
|
464
|
+
console.log("[reach] START reached", { actualScrollTop, threshold, scrollHeight: el.scrollHeight, hasCallback: !!onStartReached });
|
|
465
|
+
backwardLoadingRef.current = true;
|
|
466
|
+
prevScrollHeightRef.current = el.scrollHeight;
|
|
467
|
+
Promise.resolve(onStartReached()).catch(() => {
|
|
468
|
+
prevScrollHeightRef.current = 0;
|
|
469
|
+
backwardLoadingRef.current = false;
|
|
470
|
+
});
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
const distFromBottom = el.scrollHeight - actualScrollTop - el.clientHeight;
|
|
474
|
+
if (distFromBottom <= threshold && onEndReached && !forwardLoadingRef.current) {
|
|
475
|
+
console.log("[reach] END reached", { distFromBottom, threshold, actualScrollTop, scrollHeight: el.scrollHeight, clientHeight: el.clientHeight });
|
|
476
|
+
forwardLoadingRef.current = true;
|
|
477
|
+
Promise.resolve(onEndReached()).catch(() => {
|
|
478
|
+
forwardLoadingRef.current = false;
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
}, [scrollTop, threshold, onStartReached, onEndReached]);
|
|
482
|
+
const visibleChildren = react.useMemo(() => {
|
|
483
|
+
if (viewportHeight === 0) return null;
|
|
484
|
+
const result = [];
|
|
485
|
+
for (let i = startNode; i <= endNode && i < items.length; i++) {
|
|
486
|
+
const item = items[i];
|
|
487
|
+
result.push(
|
|
488
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
489
|
+
Measure,
|
|
490
|
+
{
|
|
491
|
+
itemId: item.id,
|
|
492
|
+
position: childPositions[i],
|
|
493
|
+
onHeightChange,
|
|
494
|
+
knownHeight: heightMapRef.current.get(item.id),
|
|
495
|
+
children: renderItem(item, i)
|
|
496
|
+
},
|
|
497
|
+
item.id
|
|
498
|
+
)
|
|
499
|
+
);
|
|
500
|
+
}
|
|
501
|
+
return result;
|
|
502
|
+
}, [startNode, endNode, items, childPositions, renderItem, onHeightChange, viewportHeight]);
|
|
503
|
+
const groupWrappers = react.useMemo(() => {
|
|
504
|
+
if (!groupInfo || !renderGroupHeader || viewportHeight === 0) return null;
|
|
505
|
+
const renderedGroups = /* @__PURE__ */ new Set();
|
|
506
|
+
const result = [];
|
|
507
|
+
for (let i = startNode; i <= endNode && i < items.length; i++) {
|
|
508
|
+
const groupKey = groupInfo.groupKeyByIndex[i];
|
|
509
|
+
if (!groupKey || renderedGroups.has(groupKey)) continue;
|
|
510
|
+
renderedGroups.add(groupKey);
|
|
511
|
+
const groupTop = groupInfo.groupStartPositions.get(groupKey) ?? 0;
|
|
512
|
+
const groupHeight = groupInfo.heightByGroup.get(groupKey) ?? 0;
|
|
513
|
+
result.push(
|
|
514
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
515
|
+
"div",
|
|
516
|
+
{
|
|
517
|
+
style: {
|
|
518
|
+
position: "absolute",
|
|
519
|
+
top: groupTop,
|
|
520
|
+
left: 0,
|
|
521
|
+
width: "100%",
|
|
522
|
+
height: groupHeight,
|
|
523
|
+
pointerEvents: "none",
|
|
524
|
+
zIndex: 1
|
|
525
|
+
},
|
|
526
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
527
|
+
"div",
|
|
528
|
+
{
|
|
529
|
+
style: {
|
|
530
|
+
position: "sticky",
|
|
531
|
+
top: 0,
|
|
532
|
+
pointerEvents: "auto"
|
|
533
|
+
},
|
|
534
|
+
children: renderGroupHeader(groupKey)
|
|
535
|
+
}
|
|
536
|
+
)
|
|
537
|
+
},
|
|
538
|
+
`__group_${groupKey}`
|
|
539
|
+
)
|
|
540
|
+
);
|
|
541
|
+
}
|
|
542
|
+
return result;
|
|
543
|
+
}, [groupInfo, renderGroupHeader, startNode, endNode, items, viewportHeight]);
|
|
544
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
545
|
+
"div",
|
|
546
|
+
{
|
|
547
|
+
ref: containerRef,
|
|
548
|
+
className,
|
|
549
|
+
style: { overflow: "auto", position: "relative", ...style },
|
|
550
|
+
children: [
|
|
551
|
+
(backwardLoadingRef.current || isMeasuring) && loadingComponent,
|
|
552
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "relative", width: "100%", height: totalHeight }, children: [
|
|
553
|
+
groupWrappers,
|
|
554
|
+
visibleChildren
|
|
555
|
+
] }),
|
|
556
|
+
forwardLoadingRef.current && bottomLoadingComponent
|
|
557
|
+
]
|
|
558
|
+
}
|
|
559
|
+
);
|
|
560
|
+
}
|
|
561
|
+
var VirtualScroll = react.forwardRef(VirtualScrollInner);
|
|
562
|
+
var IMAGE_LOAD_TIMEOUT = 5e3;
|
|
563
|
+
function InitialMeasure({
|
|
564
|
+
children,
|
|
565
|
+
itemId,
|
|
566
|
+
onMeasured
|
|
567
|
+
}) {
|
|
568
|
+
const ref = react.useRef(null);
|
|
569
|
+
const reportedRef = react.useRef(false);
|
|
570
|
+
react.useEffect(() => {
|
|
571
|
+
const node = ref.current;
|
|
572
|
+
if (!node || reportedRef.current) return;
|
|
573
|
+
const report = (reason) => {
|
|
574
|
+
if (reportedRef.current) return;
|
|
575
|
+
reportedRef.current = true;
|
|
576
|
+
const height = Math.ceil(node.offsetHeight);
|
|
577
|
+
const images2 = node.querySelectorAll("img");
|
|
578
|
+
const imgInfo = Array.from(images2).map((img) => ({
|
|
579
|
+
complete: img.complete,
|
|
580
|
+
naturalHeight: img.naturalHeight,
|
|
581
|
+
offsetHeight: img.offsetHeight,
|
|
582
|
+
src: img.src.slice(-40)
|
|
583
|
+
}));
|
|
584
|
+
console.log(`[InitialMeasure] ${itemId} \u2192 ${height}px (${reason})`, imgInfo.length > 0 ? imgInfo : "no images");
|
|
585
|
+
onMeasured(itemId, Math.max(height, 1));
|
|
586
|
+
};
|
|
587
|
+
const images = node.querySelectorAll("img");
|
|
588
|
+
const pending = [];
|
|
589
|
+
images.forEach((img) => {
|
|
590
|
+
if (!img.complete) pending.push(img);
|
|
591
|
+
});
|
|
592
|
+
if (pending.length === 0) {
|
|
593
|
+
queueMicrotask(() => report(images.length > 0 ? "images already complete" : "no images"));
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
console.log(`[InitialMeasure] ${itemId} waiting for ${pending.length} image(s)`);
|
|
597
|
+
let remaining = pending.length;
|
|
598
|
+
const onSettled = () => {
|
|
599
|
+
remaining--;
|
|
600
|
+
if (remaining <= 0) report("image load/error");
|
|
601
|
+
};
|
|
602
|
+
pending.forEach((img) => {
|
|
603
|
+
img.addEventListener("load", onSettled, { once: true });
|
|
604
|
+
img.addEventListener("error", onSettled, { once: true });
|
|
605
|
+
});
|
|
606
|
+
const timeout = setTimeout(() => report("timeout"), IMAGE_LOAD_TIMEOUT);
|
|
607
|
+
return () => {
|
|
608
|
+
clearTimeout(timeout);
|
|
609
|
+
pending.forEach((img) => {
|
|
610
|
+
img.removeEventListener("load", onSettled);
|
|
611
|
+
img.removeEventListener("error", onSettled);
|
|
612
|
+
});
|
|
613
|
+
};
|
|
614
|
+
}, [itemId, onMeasured]);
|
|
615
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
616
|
+
"div",
|
|
617
|
+
{
|
|
618
|
+
ref,
|
|
619
|
+
"data-dynamic-scroll-measure": itemId,
|
|
620
|
+
style: {
|
|
621
|
+
position: "absolute",
|
|
622
|
+
top: 0,
|
|
623
|
+
left: 0,
|
|
624
|
+
width: "100%",
|
|
625
|
+
visibility: "hidden",
|
|
626
|
+
pointerEvents: "none"
|
|
627
|
+
},
|
|
628
|
+
children
|
|
629
|
+
}
|
|
630
|
+
);
|
|
631
|
+
}
|
|
632
|
+
function DynamicScrollInner({
|
|
633
|
+
items,
|
|
634
|
+
renderItem,
|
|
635
|
+
overscanCount,
|
|
636
|
+
estimatedItemSize,
|
|
637
|
+
onStartReached,
|
|
638
|
+
onEndReached,
|
|
639
|
+
threshold,
|
|
640
|
+
onAtBottomChange,
|
|
641
|
+
syncScrollUpdates,
|
|
642
|
+
className,
|
|
643
|
+
style,
|
|
644
|
+
groupBy,
|
|
645
|
+
renderGroupHeader,
|
|
646
|
+
renderGroupSeparator,
|
|
647
|
+
loadingComponent,
|
|
648
|
+
bottomLoadingComponent,
|
|
649
|
+
initialScrollPosition = "bottom",
|
|
650
|
+
onMeasurementComplete,
|
|
651
|
+
initialLoadingComponent
|
|
652
|
+
}, ref) {
|
|
653
|
+
const itemsWithSeparators = react.useMemo(() => {
|
|
654
|
+
if (!groupBy || !renderGroupSeparator) return items;
|
|
655
|
+
const result = [];
|
|
656
|
+
let prevGroup = null;
|
|
657
|
+
for (let i = 0; i < items.length; i++) {
|
|
658
|
+
const currentGroup = groupBy(items[i]);
|
|
659
|
+
if (currentGroup !== prevGroup) {
|
|
660
|
+
result.push({
|
|
661
|
+
id: `__separator_${currentGroup}`,
|
|
662
|
+
__isSeparator: true,
|
|
663
|
+
__groupKey: currentGroup
|
|
664
|
+
});
|
|
665
|
+
prevGroup = currentGroup;
|
|
666
|
+
}
|
|
667
|
+
result.push(items[i]);
|
|
668
|
+
}
|
|
669
|
+
return result;
|
|
670
|
+
}, [items, groupBy, renderGroupSeparator]);
|
|
671
|
+
const wrappedRenderItem = react.useMemo(() => {
|
|
672
|
+
if (!groupBy || !renderGroupSeparator) return renderItem;
|
|
673
|
+
return (item, _index) => {
|
|
674
|
+
if ("__isSeparator" in item && item.__isSeparator) {
|
|
675
|
+
return renderGroupSeparator(item.__groupKey);
|
|
676
|
+
}
|
|
677
|
+
return renderItem(item, _index);
|
|
678
|
+
};
|
|
679
|
+
}, [groupBy, renderGroupSeparator, renderItem]);
|
|
680
|
+
const allItems = itemsWithSeparators;
|
|
681
|
+
const {
|
|
682
|
+
heightMapRef,
|
|
683
|
+
isAllMeasured,
|
|
684
|
+
unmeasuredIds,
|
|
685
|
+
onItemMeasured,
|
|
686
|
+
onHeightChange,
|
|
687
|
+
version
|
|
688
|
+
} = useHeightMap({ items: allItems, estimatedItemSize });
|
|
689
|
+
const hasEverMeasuredRef = react.useRef(false);
|
|
690
|
+
if (isAllMeasured && !hasEverMeasuredRef.current) {
|
|
691
|
+
hasEverMeasuredRef.current = true;
|
|
692
|
+
console.log("[DynamicScroll] initial measurement complete", { itemCount: allItems.length, unmeasuredCount: unmeasuredIds.length });
|
|
693
|
+
}
|
|
694
|
+
const isMeasuring = !isAllMeasured && hasEverMeasuredRef.current;
|
|
695
|
+
const innerRef = react.useRef(null);
|
|
696
|
+
const pendingScrollRef = react.useRef(null);
|
|
697
|
+
const isMeasuringRef = react.useRef(isMeasuring);
|
|
698
|
+
isMeasuringRef.current = isMeasuring;
|
|
699
|
+
const wasMeasuringRef = react.useRef(false);
|
|
700
|
+
react.useEffect(() => {
|
|
701
|
+
if (isMeasuring) {
|
|
702
|
+
wasMeasuringRef.current = true;
|
|
703
|
+
console.log("[DynamicScroll] measuring started", { unmeasuredCount: unmeasuredIds.length, totalItems: allItems.length });
|
|
704
|
+
} else if (wasMeasuringRef.current) {
|
|
705
|
+
wasMeasuringRef.current = false;
|
|
706
|
+
console.log("[DynamicScroll] measuring complete", { hasPendingScroll: !!pendingScrollRef.current });
|
|
707
|
+
if (pendingScrollRef.current) {
|
|
708
|
+
pendingScrollRef.current();
|
|
709
|
+
pendingScrollRef.current = null;
|
|
710
|
+
}
|
|
711
|
+
onMeasurementComplete?.();
|
|
712
|
+
}
|
|
713
|
+
}, [isMeasuring, onMeasurementComplete, unmeasuredIds.length, allItems.length]);
|
|
714
|
+
const toInternalIndex = react.useCallback(
|
|
715
|
+
(externalIndex) => {
|
|
716
|
+
if (!groupBy || !renderGroupSeparator) return externalIndex;
|
|
717
|
+
let separatorCount = 0;
|
|
718
|
+
let prevGroup = null;
|
|
719
|
+
for (let i = 0; i <= externalIndex && i < items.length; i++) {
|
|
720
|
+
const group = groupBy(items[i]);
|
|
721
|
+
if (group !== prevGroup) {
|
|
722
|
+
separatorCount++;
|
|
723
|
+
prevGroup = group;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
return externalIndex + separatorCount;
|
|
727
|
+
},
|
|
728
|
+
[items, groupBy, renderGroupSeparator]
|
|
729
|
+
);
|
|
730
|
+
react.useImperativeHandle(ref, () => ({
|
|
731
|
+
scrollToItem: (index, align) => {
|
|
732
|
+
const internalIdx = toInternalIndex(index);
|
|
733
|
+
console.log("[DynamicScroll.scrollToItem]", { externalIndex: index, internalIndex: internalIdx, align, isMeasuring: isMeasuringRef.current });
|
|
734
|
+
const action = () => innerRef.current?.scrollToItem(internalIdx, align);
|
|
735
|
+
isMeasuringRef.current ? pendingScrollRef.current = action : action();
|
|
736
|
+
},
|
|
737
|
+
scrollToBottom: (behavior) => {
|
|
738
|
+
console.log("[DynamicScroll.scrollToBottom]", { behavior, isMeasuring: isMeasuringRef.current, queued: isMeasuringRef.current });
|
|
739
|
+
const action = () => innerRef.current?.scrollToBottom(behavior);
|
|
740
|
+
isMeasuringRef.current ? pendingScrollRef.current = action : action();
|
|
741
|
+
},
|
|
742
|
+
scrollToOffset: (offset, behavior) => {
|
|
743
|
+
const action = () => innerRef.current?.scrollToOffset(offset, behavior);
|
|
744
|
+
isMeasuringRef.current ? pendingScrollRef.current = action : action();
|
|
745
|
+
},
|
|
746
|
+
getScrollOffset: () => innerRef.current?.getScrollOffset() ?? 0
|
|
747
|
+
}));
|
|
748
|
+
const stableItemsRef = react.useRef(allItems);
|
|
749
|
+
if (!isMeasuring) {
|
|
750
|
+
stableItemsRef.current = allItems;
|
|
751
|
+
}
|
|
752
|
+
const stableItems = isMeasuring ? stableItemsRef.current : allItems;
|
|
753
|
+
const { childPositions, totalHeight } = usePositions({
|
|
754
|
+
items: stableItems,
|
|
755
|
+
heightMapRef,
|
|
756
|
+
version,
|
|
757
|
+
estimatedItemSize
|
|
758
|
+
});
|
|
759
|
+
const groupByWithSeparator = react.useMemo(() => {
|
|
760
|
+
if (!groupBy) return void 0;
|
|
761
|
+
return (item) => {
|
|
762
|
+
if ("__isSeparator" in item && item.__isSeparator) {
|
|
763
|
+
return item.__groupKey;
|
|
764
|
+
}
|
|
765
|
+
return groupBy(item);
|
|
766
|
+
};
|
|
767
|
+
}, [groupBy]);
|
|
768
|
+
const groupInfo = useGroupPositions(stableItems, groupByWithSeparator, heightMapRef, version);
|
|
769
|
+
const virtualScrollGroupInfo = react.useMemo(() => {
|
|
770
|
+
if (!groupInfo || childPositions.length === 0) return null;
|
|
771
|
+
const groupStartPositions = /* @__PURE__ */ new Map();
|
|
772
|
+
let prevGroup = null;
|
|
773
|
+
for (let i = 0; i < groupInfo.groupKeyByIndex.length; i++) {
|
|
774
|
+
const group = groupInfo.groupKeyByIndex[i];
|
|
775
|
+
if (group !== prevGroup) {
|
|
776
|
+
groupStartPositions.set(group, childPositions[i] ?? 0);
|
|
777
|
+
prevGroup = group;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
return {
|
|
781
|
+
heightByGroup: groupInfo.heightByGroup,
|
|
782
|
+
groupKeyByIndex: groupInfo.groupKeyByIndex,
|
|
783
|
+
groupStartPositions
|
|
784
|
+
};
|
|
785
|
+
}, [groupInfo, childPositions]);
|
|
786
|
+
if (!hasEverMeasuredRef.current) {
|
|
787
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
788
|
+
"div",
|
|
789
|
+
{
|
|
790
|
+
className,
|
|
791
|
+
style: {
|
|
792
|
+
overflow: "hidden",
|
|
793
|
+
position: "relative",
|
|
794
|
+
...style
|
|
795
|
+
},
|
|
796
|
+
children: [
|
|
797
|
+
initialLoadingComponent,
|
|
798
|
+
unmeasuredIds.map((id) => {
|
|
799
|
+
const index = allItems.findIndex((item) => item.id === id);
|
|
800
|
+
if (index === -1) return null;
|
|
801
|
+
return /* @__PURE__ */ jsxRuntime.jsx(InitialMeasure, { itemId: id, onMeasured: onItemMeasured, children: wrappedRenderItem(allItems[index], index) }, id);
|
|
802
|
+
})
|
|
803
|
+
]
|
|
804
|
+
}
|
|
805
|
+
);
|
|
806
|
+
}
|
|
807
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
808
|
+
isMeasuring && /* @__PURE__ */ jsxRuntime.jsx(
|
|
809
|
+
"div",
|
|
810
|
+
{
|
|
811
|
+
style: {
|
|
812
|
+
position: "absolute",
|
|
813
|
+
top: 0,
|
|
814
|
+
left: 0,
|
|
815
|
+
width: "100%",
|
|
816
|
+
overflow: "hidden",
|
|
817
|
+
height: 0,
|
|
818
|
+
visibility: "hidden",
|
|
819
|
+
pointerEvents: "none"
|
|
820
|
+
},
|
|
821
|
+
children: unmeasuredIds.map((id) => {
|
|
822
|
+
const index = allItems.findIndex((item) => item.id === id);
|
|
823
|
+
if (index === -1) return null;
|
|
824
|
+
return /* @__PURE__ */ jsxRuntime.jsx(InitialMeasure, { itemId: id, onMeasured: onItemMeasured, children: wrappedRenderItem(allItems[index], index) }, id);
|
|
825
|
+
})
|
|
826
|
+
}
|
|
827
|
+
),
|
|
828
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
829
|
+
VirtualScroll,
|
|
830
|
+
{
|
|
831
|
+
ref: innerRef,
|
|
832
|
+
items: stableItems,
|
|
833
|
+
renderItem: wrappedRenderItem,
|
|
834
|
+
childPositions,
|
|
835
|
+
totalHeight,
|
|
836
|
+
heightMapRef,
|
|
837
|
+
onHeightChange,
|
|
838
|
+
overscanCount,
|
|
839
|
+
onStartReached: isMeasuring ? void 0 : onStartReached,
|
|
840
|
+
onEndReached: isMeasuring ? void 0 : onEndReached,
|
|
841
|
+
threshold,
|
|
842
|
+
onAtBottomChange,
|
|
843
|
+
syncScrollUpdates,
|
|
844
|
+
className,
|
|
845
|
+
style,
|
|
846
|
+
isMeasuring,
|
|
847
|
+
loadingComponent,
|
|
848
|
+
bottomLoadingComponent,
|
|
849
|
+
initialScrollPosition: typeof initialScrollPosition === "object" && "index" in initialScrollPosition ? { ...initialScrollPosition, index: toInternalIndex(initialScrollPosition.index) } : initialScrollPosition,
|
|
850
|
+
groupInfo: virtualScrollGroupInfo,
|
|
851
|
+
renderGroupHeader
|
|
852
|
+
}
|
|
853
|
+
)
|
|
854
|
+
] });
|
|
855
|
+
}
|
|
856
|
+
var DynamicScroll = react.forwardRef(DynamicScrollInner);
|
|
857
|
+
function StickyGroupHeader({
|
|
858
|
+
groupKey,
|
|
859
|
+
renderGroupHeader,
|
|
860
|
+
totalHeight,
|
|
861
|
+
cumulativeHeight,
|
|
862
|
+
topOffset = 0
|
|
863
|
+
}) {
|
|
864
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
865
|
+
"div",
|
|
866
|
+
{
|
|
867
|
+
"data-dynamic-scroll-group-header": groupKey,
|
|
868
|
+
style: {
|
|
869
|
+
position: "sticky",
|
|
870
|
+
top: topOffset,
|
|
871
|
+
zIndex: 1,
|
|
872
|
+
height: totalHeight - cumulativeHeight,
|
|
873
|
+
pointerEvents: "none"
|
|
874
|
+
},
|
|
875
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: { pointerEvents: "auto" }, children: renderGroupHeader(groupKey) })
|
|
876
|
+
}
|
|
877
|
+
);
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
exports.DynamicScroll = DynamicScroll;
|
|
881
|
+
exports.InitialMeasure = InitialMeasure;
|
|
882
|
+
exports.Measure = Measure;
|
|
883
|
+
exports.StickyGroupHeader = StickyGroupHeader;
|
|
884
|
+
exports.VirtualScroll = VirtualScroll;
|
|
885
|
+
exports.generateCenteredStartNodeIndex = generateCenteredStartNodeIndex;
|
|
886
|
+
exports.generateEndNodeIndex = generateEndNodeIndex;
|
|
887
|
+
exports.generateStartNodeIndex = generateStartNodeIndex;
|
|
888
|
+
exports.useGroupPositions = useGroupPositions;
|
|
889
|
+
exports.useHeightMap = useHeightMap;
|
|
890
|
+
exports.usePositions = usePositions;
|
|
891
|
+
exports.useScrollState = useScrollState;
|
|
892
|
+
//# sourceMappingURL=index.js.map
|
|
893
|
+
//# sourceMappingURL=index.js.map
|