react-virtual-renderer 1.1.3 → 1.1.5
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/App.d.ts.map +1 -1
- package/dist/App.js +65 -3
- package/dist/App.js.map +1 -1
- package/dist/components/VirtualGrid.d.ts +30 -0
- package/dist/components/VirtualGrid.d.ts.map +1 -1
- package/dist/components/VirtualGrid.js +147 -117
- package/dist/components/VirtualGrid.js.map +1 -1
- package/dist/components/VirtualList.d.ts +0 -4
- package/dist/components/VirtualList.d.ts.map +1 -1
- package/dist/components/VirtualList.js +76 -86
- package/dist/components/VirtualList.js.map +1 -1
- package/dist/components/VirtualLiveRegion.d.ts +12 -0
- package/dist/components/VirtualLiveRegion.d.ts.map +1 -0
- package/dist/components/VirtualLiveRegion.js +27 -0
- package/dist/components/VirtualLiveRegion.js.map +1 -0
- package/dist/core/FenwickTree.d.ts +47 -0
- package/dist/core/FenwickTree.d.ts.map +1 -0
- package/dist/core/FenwickTree.js +206 -0
- package/dist/core/FenwickTree.js.map +1 -0
- package/dist/core/GridLayoutEngine.d.ts +64 -0
- package/dist/core/GridLayoutEngine.d.ts.map +1 -0
- package/dist/core/GridLayoutEngine.js +153 -0
- package/dist/core/GridLayoutEngine.js.map +1 -0
- package/dist/core/GroupedListEngine.d.ts +21 -0
- package/dist/core/GroupedListEngine.d.ts.map +1 -0
- package/dist/core/GroupedListEngine.js +22 -0
- package/dist/core/GroupedListEngine.js.map +1 -0
- package/dist/core/ListController.d.ts +31 -0
- package/dist/core/ListController.d.ts.map +1 -0
- package/dist/core/ListController.js +71 -0
- package/dist/core/ListController.js.map +1 -0
- package/dist/core/MeasurementSystem.d.ts +1 -1
- package/dist/core/MeasurementSystem.d.ts.map +1 -1
- package/dist/core/MeasurementSystem.js +22 -10
- package/dist/core/MeasurementSystem.js.map +1 -1
- package/dist/core/VirtualizationEngine.d.ts +24 -13
- package/dist/core/VirtualizationEngine.d.ts.map +1 -1
- package/dist/core/VirtualizationEngine.js +50 -87
- package/dist/core/VirtualizationEngine.js.map +1 -1
- package/dist/examples/DynamicHeightLists.d.ts +2 -0
- package/dist/examples/DynamicHeightLists.d.ts.map +1 -0
- package/dist/examples/DynamicHeightLists.js +42 -0
- package/dist/examples/DynamicHeightLists.js.map +1 -0
- package/dist/examples/FixedGrid.d.ts +2 -0
- package/dist/examples/FixedGrid.d.ts.map +1 -0
- package/dist/examples/FixedGrid.js +40 -0
- package/dist/examples/FixedGrid.js.map +1 -0
- package/dist/examples/GroupedContactList.d.ts +2 -0
- package/dist/examples/GroupedContactList.d.ts.map +1 -0
- package/dist/examples/GroupedContactList.js +20 -0
- package/dist/examples/GroupedContactList.js.map +1 -0
- package/dist/examples/InfiniteScrollLists.d.ts +2 -0
- package/dist/examples/InfiniteScrollLists.d.ts.map +1 -0
- package/dist/examples/InfiniteScrollLists.js +93 -0
- package/dist/examples/InfiniteScrollLists.js.map +1 -0
- package/dist/examples/MasonaryGrid.d.ts +2 -0
- package/dist/examples/MasonaryGrid.d.ts.map +1 -0
- package/dist/examples/MasonaryGrid.js +36 -0
- package/dist/examples/MasonaryGrid.js.map +1 -0
- package/dist/examples/PerfHarness.d.ts +2 -0
- package/dist/examples/PerfHarness.d.ts.map +1 -0
- package/dist/examples/PerfHarness.js +131 -0
- package/dist/examples/PerfHarness.js.map +1 -0
- package/dist/hooks/useBidirectionalInfiniteScroll.d.ts +32 -0
- package/dist/hooks/useBidirectionalInfiniteScroll.d.ts.map +1 -0
- package/dist/hooks/useBidirectionalInfiniteScroll.js +89 -0
- package/dist/hooks/useBidirectionalInfiniteScroll.js.map +1 -0
- package/dist/hooks/useInfiniteScroll.d.ts +36 -12
- package/dist/hooks/useInfiniteScroll.d.ts.map +1 -1
- package/dist/hooks/useInfiniteScroll.js +46 -45
- package/dist/hooks/useInfiniteScroll.js.map +1 -1
- package/dist/hooks/useRevoingTabIndex.d.ts +11 -0
- package/dist/hooks/useRevoingTabIndex.d.ts.map +1 -0
- package/dist/hooks/useRevoingTabIndex.js +28 -0
- package/dist/hooks/useRevoingTabIndex.js.map +1 -0
- package/dist/hooks/useVirtualAllyProps.d.ts +23 -0
- package/dist/hooks/useVirtualAllyProps.d.ts.map +1 -0
- package/dist/hooks/useVirtualAllyProps.js +25 -0
- package/dist/hooks/useVirtualAllyProps.js.map +1 -0
- package/dist/hooks/useVirtualList.d.ts +2 -0
- package/dist/hooks/useVirtualList.d.ts.map +1 -1
- package/dist/hooks/useVirtualList.js +12 -4
- package/dist/hooks/useVirtualList.js.map +1 -1
- package/dist/index.cjs.js +1 -1
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.esm.js +1 -1
- package/dist/index.esm.js.map +1 -1
- package/dist/main.js +4 -4
- package/dist/main.js.map +1 -1
- package/dist/types.d.ts +10 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { VirtualList } from "../components/VirtualList";
|
|
4
|
+
// Real cities with approximate populations (2023-era estimates, city
|
|
5
|
+
// proper or metro depending on source - order of magnitude accurate,
|
|
6
|
+
// not intended as an authoritative dataset). 60 real records, tiled
|
|
7
|
+
// below to reach bench scale.
|
|
8
|
+
const REAL_CITIES = [
|
|
9
|
+
["Tokyo", "Japan", 37400000],
|
|
10
|
+
["Delhi", "India", 32900000],
|
|
11
|
+
["Shanghai", "China", 29200000],
|
|
12
|
+
["Dhaka", "Bangladesh", 22500000],
|
|
13
|
+
["São Paulo", "Brazil", 22400000],
|
|
14
|
+
["Cairo", "Egypt", 22200000],
|
|
15
|
+
["Mexico City", "Mexico", 22100000],
|
|
16
|
+
["Beijing", "China", 21900000],
|
|
17
|
+
["Mumbai", "India", 21300000],
|
|
18
|
+
["Osaka", "Japan", 19100000],
|
|
19
|
+
["Chongqing", "China", 18800000],
|
|
20
|
+
["Karachi", "Pakistan", 17000000],
|
|
21
|
+
["Istanbul", "Turkey", 16000000],
|
|
22
|
+
["Kinshasa", "DR Congo", 15600000],
|
|
23
|
+
["Lagos", "Nigeria", 15400000],
|
|
24
|
+
["Buenos Aires", "Argentina", 15400000],
|
|
25
|
+
["Kolkata", "India", 15100000],
|
|
26
|
+
["Manila", "Philippines", 14400000],
|
|
27
|
+
["Tianjin", "China", 14000000],
|
|
28
|
+
["Guangzhou", "China", 13900000],
|
|
29
|
+
["Rio de Janeiro", "Brazil", 13500000],
|
|
30
|
+
["Lahore", "Pakistan", 13400000],
|
|
31
|
+
["Bangalore", "India", 13200000],
|
|
32
|
+
["Shenzhen", "China", 12900000],
|
|
33
|
+
["Moscow", "Russia", 12600000],
|
|
34
|
+
["Chennai", "India", 11500000],
|
|
35
|
+
["Bogotá", "Colombia", 11300000],
|
|
36
|
+
["Paris", "France", 11100000],
|
|
37
|
+
["Jakarta", "Indonesia", 11000000],
|
|
38
|
+
["Lima", "Peru", 10900000],
|
|
39
|
+
["Bangkok", "Thailand", 10700000],
|
|
40
|
+
["Hyderabad", "India", 10500000],
|
|
41
|
+
["Seoul", "South Korea", 9900000],
|
|
42
|
+
["Nagoya", "Japan", 9600000],
|
|
43
|
+
["London", "United Kingdom", 9500000],
|
|
44
|
+
["Chengdu", "China", 9300000],
|
|
45
|
+
["Nanjing", "China", 9300000],
|
|
46
|
+
["Tehran", "Iran", 9100000],
|
|
47
|
+
["Ho Chi Minh City", "Vietnam", 9100000],
|
|
48
|
+
["Luanda", "Angola", 8900000],
|
|
49
|
+
["Wuhan", "China", 8900000],
|
|
50
|
+
["Xi'an", "China", 8700000],
|
|
51
|
+
["Ahmedabad", "India", 8500000],
|
|
52
|
+
["Kuala Lumpur", "Malaysia", 8400000],
|
|
53
|
+
["Hong Kong", "China", 8300000],
|
|
54
|
+
["Dongguan", "China", 8300000],
|
|
55
|
+
["Foshan", "China", 8200000],
|
|
56
|
+
["New York City", "United States", 8100000],
|
|
57
|
+
["Hangzhou", "China", 8000000],
|
|
58
|
+
["Baghdad", "Iraq", 7700000],
|
|
59
|
+
["Shenyang", "China", 7700000],
|
|
60
|
+
["Riyadh", "Saudi Arabia", 7600000],
|
|
61
|
+
["Santiago", "Chile", 6800000],
|
|
62
|
+
["Singapore", "Singapore", 5900000],
|
|
63
|
+
["Toronto", "Canada", 6300000],
|
|
64
|
+
["Berlin", "Germany", 3700000],
|
|
65
|
+
["Nairobi", "Kenya", 4700000],
|
|
66
|
+
["Sydney", "Australia", 5300000],
|
|
67
|
+
["Madrid", "Spain", 6700000],
|
|
68
|
+
["Cape Town", "South Africa", 4600000],
|
|
69
|
+
["Amsterdam", "Netherlands", 1150000],
|
|
70
|
+
];
|
|
71
|
+
const ITEM_COUNT = 100000;
|
|
72
|
+
function buildCityRows(count) {
|
|
73
|
+
return Array.from({ length: count }, (_, i) => {
|
|
74
|
+
const [city, country, basePopulation] = REAL_CITIES[i % REAL_CITIES.length];
|
|
75
|
+
// vary population slightly per tile pass so repeated cities aren't
|
|
76
|
+
// pixel-identical rows - still grounded in the real base figure
|
|
77
|
+
const cycle = Math.floor(i / REAL_CITIES.length);
|
|
78
|
+
const population = Math.max(50000, basePopulation - cycle * 137);
|
|
79
|
+
return { id: i, rank: i + 1, city, country, population };
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
export default function PerfHarnessExample() {
|
|
83
|
+
const containerRef = React.useRef(null);
|
|
84
|
+
const [results, setResults] = React.useState(null);
|
|
85
|
+
const [running, setRunning] = React.useState(false);
|
|
86
|
+
const items = React.useMemo(() => buildCityRows(ITEM_COUNT), []);
|
|
87
|
+
const runBenchmark = React.useCallback(() => {
|
|
88
|
+
const el = containerRef.current;
|
|
89
|
+
if (!el || running)
|
|
90
|
+
return;
|
|
91
|
+
setRunning(true);
|
|
92
|
+
setResults(null);
|
|
93
|
+
const frameTimes = [];
|
|
94
|
+
let lastTime = performance.now();
|
|
95
|
+
let rafId;
|
|
96
|
+
const maxScroll = el.scrollHeight - el.clientHeight;
|
|
97
|
+
const durationMs = 4000;
|
|
98
|
+
const startTime = performance.now();
|
|
99
|
+
function tick(now) {
|
|
100
|
+
frameTimes.push(now - lastTime);
|
|
101
|
+
lastTime = now;
|
|
102
|
+
const elapsed = now - startTime;
|
|
103
|
+
const progress = Math.min(1, elapsed / durationMs);
|
|
104
|
+
el.scrollTop = progress * maxScroll;
|
|
105
|
+
if (progress < 1) {
|
|
106
|
+
rafId = requestAnimationFrame(tick);
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
const sorted = [...frameTimes].sort((a, b) => a - b);
|
|
110
|
+
const avg = frameTimes.reduce((a, b) => a + b, 0) / frameTimes.length;
|
|
111
|
+
const p95 = sorted[Math.floor(sorted.length * 0.95)];
|
|
112
|
+
const max = sorted[sorted.length - 1];
|
|
113
|
+
const droppedFrames = frameTimes.filter((t) => t > 16.7).length;
|
|
114
|
+
setResults({ avg, p95, max, droppedFrames, totalFrames: frameTimes.length });
|
|
115
|
+
setRunning(false);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
rafId = requestAnimationFrame(tick);
|
|
119
|
+
return () => cancelAnimationFrame(rafId);
|
|
120
|
+
}, [running]);
|
|
121
|
+
return (_jsxs("div", { children: [_jsxs("div", { style: { marginBottom: 12, display: "flex", gap: 12, alignItems: "center" }, children: [_jsx("button", { onClick: runBenchmark, disabled: running, children: running ? "Scrolling..." : `Run scroll benchmark (${ITEM_COUNT.toLocaleString()} rows)` }), results && (_jsxs("div", { style: { fontFamily: "monospace", fontSize: 13 }, children: ["avg: ", results.avg.toFixed(2), "ms | p95: ", results.p95.toFixed(2), "ms | max:", " ", results.max.toFixed(2), "ms | dropped frames (>16.7ms):", " ", results.droppedFrames, "/", results.totalFrames] }))] }), _jsx(VirtualList, { ref: containerRef, items: items, height: 600, width: "100%", estimatedItemSize: 40, itemKey: (_, item) => item.id, itemStyle: {
|
|
122
|
+
display: "flex",
|
|
123
|
+
alignItems: "center",
|
|
124
|
+
gap: 16,
|
|
125
|
+
padding: "8px 12px",
|
|
126
|
+
borderBottom: "1px solid #f0f0f0",
|
|
127
|
+
fontFamily: "monospace",
|
|
128
|
+
fontSize: 13,
|
|
129
|
+
}, renderItem: ({ item }) => (_jsxs(_Fragment, { children: [_jsxs("span", { style: { color: "#999", width: 60 }, children: ["#", item.rank] }), _jsx("span", { style: { width: 180, fontWeight: 600 }, children: item.city }), _jsx("span", { style: { width: 160, color: "#666" }, children: item.country }), _jsxs("span", { style: { marginLeft: "auto" }, children: [item.population.toLocaleString(), " people"] })] })) })] }));
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=PerfHarness.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PerfHarness.js","sourceRoot":"","sources":["../../src/examples/PerfHarness.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export interface UseBidirectionalInfiniteScrollParams {
|
|
2
|
+
onLoadOlder?: () => void | Promise<void>;
|
|
3
|
+
onLoadNewer?: () => void | Promise<void>;
|
|
4
|
+
isLoadingOlder?: boolean;
|
|
5
|
+
isLoadingNewer?: boolean;
|
|
6
|
+
root?: React.RefObject<HTMLElement | null>;
|
|
7
|
+
rootMargin?: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Chat/feed-style bidirectional infinite scroll: sentinels at both
|
|
11
|
+
* ends trigger loading older (prepend) or newer (append) content.
|
|
12
|
+
*
|
|
13
|
+
* THE HARD PART: prepending items above the viewport makes native
|
|
14
|
+
* browser scroll-anchoring behave inconsistently (or not fire at all,
|
|
15
|
+
* if content changes async after the anchor point is chosen), causing
|
|
16
|
+
* a visible jump. This hook manually anchors on a specific existing
|
|
17
|
+
* DOM node's offset before the prepend, then restores scrollTop after
|
|
18
|
+
* the DOM updates so that node stays visually fixed.
|
|
19
|
+
*
|
|
20
|
+
* KNOWN LIMITATION: the anchored node can itself get unmounted by
|
|
21
|
+
* virtualization between capture and restore (e.g. a very large,
|
|
22
|
+
* very fast prepend). When that happens the anchor is dropped rather
|
|
23
|
+
* than silently producing a wrong position - noted in restoreAnchor()
|
|
24
|
+
* below. A fully bulletproof version would need to anchor on an
|
|
25
|
+
* offset-based position rather than a DOM node reference; that's a
|
|
26
|
+
* larger change than this hook makes.
|
|
27
|
+
*/
|
|
28
|
+
export declare function useBidirectionalInfiniteScroll({ onLoadOlder, onLoadNewer, isLoadingOlder, isLoadingNewer, root, rootMargin, }: UseBidirectionalInfiniteScrollParams): {
|
|
29
|
+
topSentinelRef: import("react").MutableRefObject<HTMLDivElement | null>;
|
|
30
|
+
bottomSentinelRef: import("react").MutableRefObject<HTMLDivElement | null>;
|
|
31
|
+
};
|
|
32
|
+
//# sourceMappingURL=useBidirectionalInfiniteScroll.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useBidirectionalInfiniteScroll.d.ts","sourceRoot":"","sources":["../../src/hooks/useBidirectionalInfiniteScroll.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,oCAAoC;IACjD,WAAW,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC,WAAW,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAC3C,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,8BAA8B,CAAC,EAC3C,WAAW,EACX,WAAW,EACX,cAAsB,EACtB,cAAsB,EACtB,IAAI,EACJ,UAAoB,GACvB,EAAE,oCAAoC;;;EAmFtC"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// hooks/useBidirectionalInfiniteScroll.ts
|
|
2
|
+
import { useCallback, useEffect, useRef } from 'react';
|
|
3
|
+
/**
|
|
4
|
+
* Chat/feed-style bidirectional infinite scroll: sentinels at both
|
|
5
|
+
* ends trigger loading older (prepend) or newer (append) content.
|
|
6
|
+
*
|
|
7
|
+
* THE HARD PART: prepending items above the viewport makes native
|
|
8
|
+
* browser scroll-anchoring behave inconsistently (or not fire at all,
|
|
9
|
+
* if content changes async after the anchor point is chosen), causing
|
|
10
|
+
* a visible jump. This hook manually anchors on a specific existing
|
|
11
|
+
* DOM node's offset before the prepend, then restores scrollTop after
|
|
12
|
+
* the DOM updates so that node stays visually fixed.
|
|
13
|
+
*
|
|
14
|
+
* KNOWN LIMITATION: the anchored node can itself get unmounted by
|
|
15
|
+
* virtualization between capture and restore (e.g. a very large,
|
|
16
|
+
* very fast prepend). When that happens the anchor is dropped rather
|
|
17
|
+
* than silently producing a wrong position - noted in restoreAnchor()
|
|
18
|
+
* below. A fully bulletproof version would need to anchor on an
|
|
19
|
+
* offset-based position rather than a DOM node reference; that's a
|
|
20
|
+
* larger change than this hook makes.
|
|
21
|
+
*/
|
|
22
|
+
export function useBidirectionalInfiniteScroll({ onLoadOlder, onLoadNewer, isLoadingOlder = false, isLoadingNewer = false, root, rootMargin = '400px', }) {
|
|
23
|
+
const topSentinelRef = useRef(null);
|
|
24
|
+
const bottomSentinelRef = useRef(null);
|
|
25
|
+
const anchorRef = useRef(null);
|
|
26
|
+
const loadingOlderRef = useRef(false);
|
|
27
|
+
const loadingNewerRef = useRef(false);
|
|
28
|
+
const captureAnchor = useCallback(() => {
|
|
29
|
+
const scrollRoot = root?.current;
|
|
30
|
+
if (!scrollRoot)
|
|
31
|
+
return;
|
|
32
|
+
const children = Array.from(scrollRoot.querySelectorAll('[data-virtualize-index]'));
|
|
33
|
+
const rootRect = scrollRoot.getBoundingClientRect();
|
|
34
|
+
for (const child of children) {
|
|
35
|
+
const rect = child.getBoundingClientRect();
|
|
36
|
+
if (rect.top >= rootRect.top) {
|
|
37
|
+
anchorRef.current = { node: child, offsetFromTop: rect.top - rootRect.top };
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}, [root]);
|
|
42
|
+
const restoreAnchor = useCallback(() => {
|
|
43
|
+
const scrollRoot = root?.current;
|
|
44
|
+
const anchor = anchorRef.current;
|
|
45
|
+
if (!scrollRoot || !anchor)
|
|
46
|
+
return;
|
|
47
|
+
if (!scrollRoot.contains(anchor.node)) {
|
|
48
|
+
// anchored node was unmounted by virtualization before we
|
|
49
|
+
// could restore - drop it rather than guess
|
|
50
|
+
anchorRef.current = null;
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const newRect = anchor.node.getBoundingClientRect();
|
|
54
|
+
const rootRect = scrollRoot.getBoundingClientRect();
|
|
55
|
+
const delta = (newRect.top - rootRect.top) - anchor.offsetFromTop;
|
|
56
|
+
scrollRoot.scrollTop += delta;
|
|
57
|
+
anchorRef.current = null;
|
|
58
|
+
}, [root]);
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
if (typeof window === 'undefined' || !('IntersectionObserver' in window))
|
|
61
|
+
return;
|
|
62
|
+
const observers = [];
|
|
63
|
+
if (onLoadOlder && topSentinelRef.current) {
|
|
64
|
+
const obs = new IntersectionObserver((entries) => {
|
|
65
|
+
if (entries[0]?.isIntersecting && !loadingOlderRef.current && !isLoadingOlder) {
|
|
66
|
+
loadingOlderRef.current = true;
|
|
67
|
+
captureAnchor();
|
|
68
|
+
Promise.resolve(onLoadOlder()).finally(() => { loadingOlderRef.current = false; });
|
|
69
|
+
}
|
|
70
|
+
}, { root: root?.current ?? null, rootMargin, threshold: 0 });
|
|
71
|
+
obs.observe(topSentinelRef.current);
|
|
72
|
+
observers.push(obs);
|
|
73
|
+
}
|
|
74
|
+
if (onLoadNewer && bottomSentinelRef.current) {
|
|
75
|
+
const obs = new IntersectionObserver((entries) => {
|
|
76
|
+
if (entries[0]?.isIntersecting && !loadingNewerRef.current && !isLoadingNewer) {
|
|
77
|
+
loadingNewerRef.current = true;
|
|
78
|
+
Promise.resolve(onLoadNewer()).finally(() => { loadingNewerRef.current = false; });
|
|
79
|
+
}
|
|
80
|
+
}, { root: root?.current ?? null, rootMargin, threshold: 0 });
|
|
81
|
+
obs.observe(bottomSentinelRef.current);
|
|
82
|
+
observers.push(obs);
|
|
83
|
+
}
|
|
84
|
+
return () => observers.forEach((o) => o.disconnect());
|
|
85
|
+
}, [onLoadOlder, onLoadNewer, isLoadingOlder, isLoadingNewer, root, rootMargin, captureAnchor]);
|
|
86
|
+
useEffect(() => { restoreAnchor(); });
|
|
87
|
+
return { topSentinelRef, bottomSentinelRef };
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=useBidirectionalInfiniteScroll.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useBidirectionalInfiniteScroll.js","sourceRoot":"","sources":["../../src/hooks/useBidirectionalInfiniteScroll.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,33 +1,57 @@
|
|
|
1
|
+
import { RefObject } from "react";
|
|
1
2
|
export interface UseInfiniteScrollParams {
|
|
2
3
|
/**
|
|
3
|
-
*
|
|
4
|
+
* Called when the sentinel enters the observed root's margin.
|
|
4
5
|
*/
|
|
5
6
|
onLoadMore: () => void | Promise<void>;
|
|
6
7
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
8
|
+
* IntersectionObserver rootMargin - the IO equivalent of the old
|
|
9
|
+
* pixel "threshold" (how far from the edge to fire early).
|
|
10
|
+
* Default: "500px", matching the previous default distance.
|
|
9
11
|
*/
|
|
10
|
-
|
|
12
|
+
rootMargin?: string;
|
|
11
13
|
/**
|
|
12
|
-
* External loading state
|
|
14
|
+
* External loading state - if the caller is already loading (e.g.
|
|
15
|
+
* from a data-fetching hook), a new intersection won't re-trigger.
|
|
13
16
|
*/
|
|
14
17
|
isLoading?: boolean;
|
|
18
|
+
/** Enable / disable the observer entirely. */
|
|
19
|
+
enabled?: boolean;
|
|
15
20
|
/**
|
|
16
|
-
*
|
|
21
|
+
* The scrollable element to use as the IntersectionObserver root.
|
|
22
|
+
* Pass the same ref you gave VirtualList/VirtualGrid so the
|
|
23
|
+
* threshold is evaluated against that container, not the browser
|
|
24
|
+
* viewport. Omit to observe against the viewport.
|
|
17
25
|
*/
|
|
18
|
-
|
|
26
|
+
root?: RefObject<Element | null>;
|
|
19
27
|
}
|
|
20
28
|
export interface UseInfiniteScrollReturn<T extends HTMLElement = HTMLDivElement> {
|
|
21
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Attach this to a small sentinel element rendered after your last
|
|
31
|
+
* item (e.g. `<div ref={sentinelRef} style={{ height: 1 }} />`).
|
|
32
|
+
* It never needs content or visible size.
|
|
33
|
+
*/
|
|
34
|
+
sentinelRef: RefObject<T | null>;
|
|
22
35
|
isAtBottom: boolean;
|
|
23
36
|
isLoading: boolean;
|
|
24
|
-
checkIfAtBottom: () => void;
|
|
25
37
|
}
|
|
26
38
|
/**
|
|
27
39
|
* useInfiniteScroll
|
|
28
40
|
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
41
|
+
* PERFORMANCE: the previous version attached a `scroll` listener and
|
|
42
|
+
* read `scrollTop` / `scrollHeight` / `clientHeight` on every single
|
|
43
|
+
* scroll event. Each of those reads can force a synchronous layout
|
|
44
|
+
* ("layout thrashing") if a style/DOM write happened earlier in the
|
|
45
|
+
* same frame, and the handler ran on the main thread for every pixel
|
|
46
|
+
* scrolled, whether or not the user was anywhere near the bottom.
|
|
47
|
+
*
|
|
48
|
+
* This version observes a 1px sentinel with IntersectionObserver
|
|
49
|
+
* instead. The browser computes intersection off the main thread on
|
|
50
|
+
* its own compositor schedule - there is no per-scroll-event JS cost,
|
|
51
|
+
* no layout reads, and no manual threshold math at all. This is also
|
|
52
|
+
* what react-window/react-virtuoso-adjacent "load more" patterns and
|
|
53
|
+
* most production infinite-scroll implementations converged on for
|
|
54
|
+
* the same reason.
|
|
31
55
|
*/
|
|
32
|
-
export declare function useInfiniteScroll<T extends HTMLElement = HTMLDivElement>({ onLoadMore,
|
|
56
|
+
export declare function useInfiniteScroll<T extends HTMLElement = HTMLDivElement>({ onLoadMore, rootMargin, isLoading, enabled, root, }: UseInfiniteScrollParams): UseInfiniteScrollReturn<T>;
|
|
33
57
|
//# sourceMappingURL=useInfiniteScroll.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useInfiniteScroll.d.ts","sourceRoot":"","sources":["../../src/hooks/useInfiniteScroll.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useInfiniteScroll.d.ts","sourceRoot":"","sources":["../../src/hooks/useInfiniteScroll.ts"],"names":[],"mappings":"AAAA,OAAO,EAIH,SAAS,EACZ,MAAM,OAAO,CAAC;AAEf,MAAM,WAAW,uBAAuB;IACpC;;OAEG;IACH,UAAU,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvC;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB,8CAA8C;IAC9C,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB;;;;;OAKG;IACH,IAAI,CAAC,EAAE,SAAS,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,uBAAuB,CAAC,CAAC,SAAS,WAAW,GAAG,cAAc;IAC3E;;;;OAIG;IACH,WAAW,EAAE,SAAS,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IACjC,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,WAAW,GAAG,cAAc,EAAE,EACtE,UAAU,EACV,UAAoB,EACpB,SAAiB,EACjB,OAAc,EACd,IAAI,GACP,EAAE,uBAAuB,GAAG,uBAAuB,CAAC,CAAC,CAAC,CA4DtD"}
|
|
@@ -1,67 +1,68 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useEffect, useRef, useState, } from "react";
|
|
2
2
|
/**
|
|
3
3
|
* useInfiniteScroll
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* PERFORMANCE: the previous version attached a `scroll` listener and
|
|
6
|
+
* read `scrollTop` / `scrollHeight` / `clientHeight` on every single
|
|
7
|
+
* scroll event. Each of those reads can force a synchronous layout
|
|
8
|
+
* ("layout thrashing") if a style/DOM write happened earlier in the
|
|
9
|
+
* same frame, and the handler ran on the main thread for every pixel
|
|
10
|
+
* scrolled, whether or not the user was anywhere near the bottom.
|
|
11
|
+
*
|
|
12
|
+
* This version observes a 1px sentinel with IntersectionObserver
|
|
13
|
+
* instead. The browser computes intersection off the main thread on
|
|
14
|
+
* its own compositor schedule - there is no per-scroll-event JS cost,
|
|
15
|
+
* no layout reads, and no manual threshold math at all. This is also
|
|
16
|
+
* what react-window/react-virtuoso-adjacent "load more" patterns and
|
|
17
|
+
* most production infinite-scroll implementations converged on for
|
|
18
|
+
* the same reason.
|
|
7
19
|
*/
|
|
8
|
-
export function useInfiniteScroll({ onLoadMore,
|
|
9
|
-
const
|
|
20
|
+
export function useInfiniteScroll({ onLoadMore, rootMargin = "500px", isLoading = false, enabled = true, root, }) {
|
|
21
|
+
const sentinelRef = useRef(null);
|
|
10
22
|
const [isAtBottom, setIsAtBottom] = useState(false);
|
|
11
23
|
const loadingRef = useRef(false);
|
|
12
24
|
const onLoadMoreRef = useRef(onLoadMore);
|
|
13
25
|
useEffect(() => {
|
|
14
26
|
onLoadMoreRef.current = onLoadMore;
|
|
15
27
|
}, [onLoadMore]);
|
|
16
|
-
const checkIfAtBottom = useCallback(() => {
|
|
17
|
-
if (!enabled)
|
|
18
|
-
return;
|
|
19
|
-
const container = containerRef.current;
|
|
20
|
-
if (!container)
|
|
21
|
-
return;
|
|
22
|
-
const { scrollTop, scrollHeight, clientHeight, } = container;
|
|
23
|
-
const distanceFromBottom = scrollHeight -
|
|
24
|
-
scrollTop -
|
|
25
|
-
clientHeight;
|
|
26
|
-
const reachedBottom = distanceFromBottom <= threshold;
|
|
27
|
-
setIsAtBottom(reachedBottom);
|
|
28
|
-
if (reachedBottom &&
|
|
29
|
-
!loadingRef.current &&
|
|
30
|
-
!isLoading) {
|
|
31
|
-
loadingRef.current = true;
|
|
32
|
-
Promise.resolve(onLoadMoreRef.current()).finally(() => {
|
|
33
|
-
loadingRef.current = false;
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
}, [
|
|
37
|
-
threshold,
|
|
38
|
-
enabled,
|
|
39
|
-
isLoading,
|
|
40
|
-
]);
|
|
41
28
|
useEffect(() => {
|
|
42
29
|
if (!enabled)
|
|
43
30
|
return;
|
|
44
|
-
const
|
|
45
|
-
if (!
|
|
31
|
+
const sentinel = sentinelRef.current;
|
|
32
|
+
if (!sentinel)
|
|
46
33
|
return;
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
34
|
+
if (typeof window === "undefined" || !("IntersectionObserver" in window)) {
|
|
35
|
+
// SSR / very old browser fallback: no polling loop is added -
|
|
36
|
+
// the consumer can still call onLoadMore manually (e.g. a
|
|
37
|
+
// "Load more" button) if this environment is a real target.
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const observer = new IntersectionObserver((entries) => {
|
|
41
|
+
const entry = entries[0];
|
|
42
|
+
if (!entry)
|
|
43
|
+
return;
|
|
44
|
+
setIsAtBottom(entry.isIntersecting);
|
|
45
|
+
if (entry.isIntersecting && !loadingRef.current && !isLoading) {
|
|
46
|
+
loadingRef.current = true;
|
|
47
|
+
Promise.resolve(onLoadMoreRef.current()).finally(() => {
|
|
48
|
+
loadingRef.current = false;
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}, {
|
|
52
|
+
root: root?.current ?? null,
|
|
53
|
+
rootMargin,
|
|
54
|
+
threshold: 0,
|
|
55
|
+
});
|
|
56
|
+
observer.observe(sentinel);
|
|
53
57
|
return () => {
|
|
54
|
-
|
|
58
|
+
observer.disconnect();
|
|
55
59
|
};
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
checkIfAtBottom,
|
|
59
|
-
]);
|
|
60
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
61
|
+
}, [enabled, rootMargin, isLoading, root?.current]);
|
|
60
62
|
return {
|
|
61
|
-
|
|
63
|
+
sentinelRef,
|
|
62
64
|
isAtBottom,
|
|
63
65
|
isLoading,
|
|
64
|
-
checkIfAtBottom,
|
|
65
66
|
};
|
|
66
67
|
}
|
|
67
68
|
//# sourceMappingURL=useInfiniteScroll.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useInfiniteScroll.js","sourceRoot":"","sources":["../../src/hooks/useInfiniteScroll.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,
|
|
1
|
+
{"version":3,"file":"useInfiniteScroll.js","sourceRoot":"","sources":["../../src/hooks/useInfiniteScroll.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,SAAS,EACT,MAAM,EACN,QAAQ,GAEX,MAAM,OAAO,CAAC;AA4Cf;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,iBAAiB,CAAyC,EACtE,UAAU,EACV,UAAU,GAAG,OAAO,EACpB,SAAS,GAAG,KAAK,EACjB,OAAO,GAAG,IAAI,EACd,IAAI,GACkB;IACtB,MAAM,WAAW,GAAG,MAAM,CAAW,IAAI,CAAC,CAAC;IAE3C,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAEpD,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IACjC,MAAM,aAAa,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC;IAEzC,SAAS,CAAC,GAAG,EAAE;QACX,aAAa,CAAC,OAAO,GAAG,UAAU,CAAC;IACvC,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC;IAEjB,SAAS,CAAC,GAAG,EAAE;QACX,IAAI,CAAC,OAAO;YAAE,OAAO;QAErB,MAAM,QAAQ,GAAG,WAAW,CAAC,OAAO,CAAC;QACrC,IAAI,CAAC,QAAQ;YAAE,OAAO;QAEtB,IAAI,OAAO,MAAM,KAAK,WAAW,IAAI,CAAC,CAAC,sBAAsB,IAAI,MAAM,CAAC,EAAE,CAAC;YACvE,8DAA8D;YAC9D,0DAA0D;YAC1D,4DAA4D;YAC5D,OAAO;QACX,CAAC;QAED,MAAM,QAAQ,GAAG,IAAI,oBAAoB,CACrC,CAAC,OAAO,EAAE,EAAE;YACR,MAAM,KAAK,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;YACzB,IAAI,CAAC,KAAK;gBAAE,OAAO;YAEnB,aAAa,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;YAEpC,IAAI,KAAK,CAAC,cAAc,IAAI,CAAC,UAAU,CAAC,OAAO,IAAI,CAAC,SAAS,EAAE,CAAC;gBAC5D,UAAU,CAAC,OAAO,GAAG,IAAI,CAAC;gBAE1B,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC,OAAO,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE;oBAClD,UAAU,CAAC,OAAO,GAAG,KAAK,CAAC;gBAC/B,CAAC,CAAC,CAAC;YACP,CAAC;QACL,CAAC,EACD;YACI,IAAI,EAAE,IAAI,EAAE,OAAO,IAAI,IAAI;YAC3B,UAAU;YACV,SAAS,EAAE,CAAC;SACf,CACJ,CAAC;QAEF,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAE3B,OAAO,GAAG,EAAE;YACR,QAAQ,CAAC,UAAU,EAAE,CAAC;QAC1B,CAAC,CAAC;QACF,uDAAuD;IAC3D,CAAC,EAAE,CAAC,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;IAEpD,OAAO;QACH,WAAW;QACX,UAAU;QACV,SAAS;KACZ,CAAC;AACN,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Roving tabindex: only the "active" item is in the tab order
|
|
3
|
+
* (tabIndex 0), everything else is -1. Arrow/Home/End keys move the
|
|
4
|
+
* active index and scroll it into view.
|
|
5
|
+
*/
|
|
6
|
+
export declare function useRovingTabIndex(itemCount: number, scrollToItem: (index: number) => void): {
|
|
7
|
+
activeIndex: number;
|
|
8
|
+
onKeyDown: (e: React.KeyboardEvent) => void;
|
|
9
|
+
setActiveIndex: import("react").Dispatch<import("react").SetStateAction<number>>;
|
|
10
|
+
};
|
|
11
|
+
//# sourceMappingURL=useRevoingTabIndex.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useRevoingTabIndex.d.ts","sourceRoot":"","sources":["../../src/hooks/useRevoingTabIndex.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI;;mBAI9E,KAAK,CAAC,aAAa;;EAgB9B"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// hooks/useRovingTabIndex.ts
|
|
2
|
+
import { useCallback, useState } from 'react';
|
|
3
|
+
/**
|
|
4
|
+
* Roving tabindex: only the "active" item is in the tab order
|
|
5
|
+
* (tabIndex 0), everything else is -1. Arrow/Home/End keys move the
|
|
6
|
+
* active index and scroll it into view.
|
|
7
|
+
*/
|
|
8
|
+
export function useRovingTabIndex(itemCount, scrollToItem) {
|
|
9
|
+
const [activeIndex, setActiveIndex] = useState(0);
|
|
10
|
+
const onKeyDown = useCallback((e) => {
|
|
11
|
+
let next = activeIndex;
|
|
12
|
+
if (e.key === 'ArrowDown')
|
|
13
|
+
next = Math.min(itemCount - 1, activeIndex + 1);
|
|
14
|
+
else if (e.key === 'ArrowUp')
|
|
15
|
+
next = Math.max(0, activeIndex - 1);
|
|
16
|
+
else if (e.key === 'Home')
|
|
17
|
+
next = 0;
|
|
18
|
+
else if (e.key === 'End')
|
|
19
|
+
next = itemCount - 1;
|
|
20
|
+
else
|
|
21
|
+
return;
|
|
22
|
+
e.preventDefault();
|
|
23
|
+
setActiveIndex(next);
|
|
24
|
+
scrollToItem(next);
|
|
25
|
+
}, [activeIndex, itemCount, scrollToItem]);
|
|
26
|
+
return { activeIndex, onKeyDown, setActiveIndex };
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=useRevoingTabIndex.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useRevoingTabIndex.js","sourceRoot":"","sources":["../../src/hooks/useRevoingTabIndex.ts"],"names":[],"mappings":"AAAA,6BAA6B;AAC7B,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAE9C;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,SAAiB,EAAE,YAAqC;IACtF,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;IAElD,MAAM,SAAS,GAAG,WAAW,CACzB,CAAC,CAAsB,EAAE,EAAE;QACvB,IAAI,IAAI,GAAG,WAAW,CAAC;QACvB,IAAI,CAAC,CAAC,GAAG,KAAK,WAAW;YAAE,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,SAAS,GAAG,CAAC,EAAE,WAAW,GAAG,CAAC,CAAC,CAAC;aACtE,IAAI,CAAC,CAAC,GAAG,KAAK,SAAS;YAAE,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,WAAW,GAAG,CAAC,CAAC,CAAC;aAC7D,IAAI,CAAC,CAAC,GAAG,KAAK,MAAM;YAAE,IAAI,GAAG,CAAC,CAAC;aAC/B,IAAI,CAAC,CAAC,GAAG,KAAK,KAAK;YAAE,IAAI,GAAG,SAAS,GAAG,CAAC,CAAC;;YAC1C,OAAO;QAEZ,CAAC,CAAC,cAAc,EAAE,CAAC;QACnB,cAAc,CAAC,IAAI,CAAC,CAAC;QACrB,YAAY,CAAC,IAAI,CAAC,CAAC;IACvB,CAAC,EACD,CAAC,WAAW,EAAE,SAAS,EAAE,YAAY,CAAC,CACzC,CAAC;IAEF,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,cAAc,EAAE,CAAC;AACtD,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface VirtualA11yConfig {
|
|
2
|
+
itemCount: number;
|
|
3
|
+
role?: 'list' | 'grid';
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* ARIA attributes for a virtualized container and its items, so
|
|
7
|
+
* screen readers understand the FULL collection size even though
|
|
8
|
+
* only a window of items exists in the DOM at any time. Without
|
|
9
|
+
* this, a screen reader announces "list, 20 items" (whatever happens
|
|
10
|
+
* to be mounted) instead of the true count.
|
|
11
|
+
*/
|
|
12
|
+
export declare function useVirtualContainerA11y({ itemCount, role }: VirtualA11yConfig): {
|
|
13
|
+
role: "grid" | "list";
|
|
14
|
+
'aria-rowcount': number | undefined;
|
|
15
|
+
'aria-setsize': number | undefined;
|
|
16
|
+
};
|
|
17
|
+
export declare function useVirtualItemA11y(index: number, itemCount: number, role?: 'list' | 'grid'): {
|
|
18
|
+
role: string;
|
|
19
|
+
'aria-posinset': number;
|
|
20
|
+
'aria-setsize': number;
|
|
21
|
+
tabIndex: number;
|
|
22
|
+
};
|
|
23
|
+
//# sourceMappingURL=useVirtualAllyProps.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useVirtualAllyProps.d.ts","sourceRoot":"","sources":["../../src/hooks/useVirtualAllyProps.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,iBAAiB;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC1B;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,EAAE,SAAS,EAAE,IAAa,EAAE,EAAE,iBAAiB;;;;EAStF;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,GAAE,MAAM,GAAG,MAAe;;;;;EAUlG"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// hooks/useVirtualA11yProps.ts
|
|
2
|
+
import { useMemo } from 'react';
|
|
3
|
+
/**
|
|
4
|
+
* ARIA attributes for a virtualized container and its items, so
|
|
5
|
+
* screen readers understand the FULL collection size even though
|
|
6
|
+
* only a window of items exists in the DOM at any time. Without
|
|
7
|
+
* this, a screen reader announces "list, 20 items" (whatever happens
|
|
8
|
+
* to be mounted) instead of the true count.
|
|
9
|
+
*/
|
|
10
|
+
export function useVirtualContainerA11y({ itemCount, role = 'list' }) {
|
|
11
|
+
return useMemo(() => ({
|
|
12
|
+
role,
|
|
13
|
+
'aria-rowcount': role === 'grid' ? itemCount : undefined,
|
|
14
|
+
'aria-setsize': role === 'list' ? itemCount : undefined,
|
|
15
|
+
}), [itemCount, role]);
|
|
16
|
+
}
|
|
17
|
+
export function useVirtualItemA11y(index, itemCount, role = 'list') {
|
|
18
|
+
return useMemo(() => ({
|
|
19
|
+
role: role === 'grid' ? 'row' : 'listitem',
|
|
20
|
+
'aria-posinset': index + 1,
|
|
21
|
+
'aria-setsize': itemCount,
|
|
22
|
+
tabIndex: -1, // managed by roving tabindex, see below
|
|
23
|
+
}), [index, itemCount, role]);
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=useVirtualAllyProps.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useVirtualAllyProps.js","sourceRoot":"","sources":["../../src/hooks/useVirtualAllyProps.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAOhC;;;;;;GAMG;AACH,MAAM,UAAU,uBAAuB,CAAC,EAAE,SAAS,EAAE,IAAI,GAAG,MAAM,EAAqB;IACnF,OAAO,OAAO,CACV,GAAG,EAAE,CAAC,CAAC;QACH,IAAI;QACJ,eAAe,EAAE,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS;QACxD,cAAc,EAAE,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS;KAC1D,CAAC,EACF,CAAC,SAAS,EAAE,IAAI,CAAC,CACpB,CAAC;AACN,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,KAAa,EAAE,SAAiB,EAAE,OAAwB,MAAM;IAC/F,OAAO,OAAO,CACV,GAAG,EAAE,CAAC,CAAC;QACH,IAAI,EAAE,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,UAAU;QAC1C,eAAe,EAAE,KAAK,GAAG,CAAC;QAC1B,cAAc,EAAE,SAAS;QACzB,QAAQ,EAAE,CAAC,CAAC,EAAE,wCAAwC;KACzD,CAAC,EACF,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,CAC3B,CAAC;AACN,CAAC"}
|
|
@@ -97,6 +97,8 @@ export interface UseVirtualListReturn {
|
|
|
97
97
|
* `getItemKey` when provided and falling back to index.
|
|
98
98
|
*/
|
|
99
99
|
getItemKeyForIndex: (index: number) => string | number;
|
|
100
|
+
observeItem: (index: number, element: HTMLElement) => void;
|
|
101
|
+
unobserveItem: (element: HTMLElement) => void;
|
|
100
102
|
}
|
|
101
103
|
/**
|
|
102
104
|
* Virtualized list hook. Supports vertical or horizontal axes,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useVirtualList.d.ts","sourceRoot":"","sources":["../../src/hooks/useVirtualList.ts"],"names":[],"mappings":"AAAA,OAAO,EAMH,aAAa,EACb,gBAAgB,EACnB,MAAM,OAAO,CAAC;AAKf,OAAO,EACH,WAAW,EACX,eAAe,EAClB,MAAM,UAAU,CAAC;AAElB;;;;GAIG;AACH,MAAM,MAAM,UAAU,GAAG,UAAU,GAAG,YAAY,CAAC;AAEnD,MAAM,WAAW,oBAAoB,CAAC,CAAC;IACnC,KAAK,EAAE,CAAC,EAAE,CAAC;IAEX;;;;OAIG;IACH,UAAU,CAAC,EAAE,CACT,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,CAAC,KACN,MAAM,GAAG,MAAM,CAAC;IAErB;;;OAGG;IACH,eAAe,EAAE,MAAM,CAAC;IAExB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,EACP,MAAM,GACN,CAAC,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC,CAAC;IAE9B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,IAAI,CAAC,EAAE,UAAU,CAAC;IAElB;;;;;OAKG;IACH,eAAe,CAAC,EAAE,eAAe,CAAC;IAElC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE3B,QAAQ,CAAC,EAAE,CACP,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,eAAe,KACzB,IAAI,CAAC;IAEV,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IAEzB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IAEzB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB;;;;;OAKG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAClC;AAED,MAAM,WAAW,oBAAoB;IACjC,YAAY,EAAE,WAAW,EAAE,CAAC;IAE5B,WAAW,EAAE,OAAO,CAAC;IAErB,YAAY,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,eAAe,EAAE,eAAe,CAAC;IAEjC,SAAS,EAAE,MAAM,CAAC;IAElB,YAAY,EAAE,CACV,KAAK,EAAE,MAAM,EACb,SAAS,CAAC,EACJ,OAAO,GACP,QAAQ,GACR,KAAK,KACV,IAAI,CAAC;IAEV,cAAc,EAAE,CACZ,MAAM,EAAE,MAAM,KACb,IAAI,CAAC;IAEV,eAAe,EAAE,CACb,MAAM,EAAE,MAAM,KACb,IAAI,CAAC;IAEV,YAAY,EAAE,gBAAgB,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IAEtD,QAAQ,EAAE,gBAAgB,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IAElD,YAAY,EAAE,CACV,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,KACX,aAAa,CAAC;IAEnB;;;;;;;;OAQG;IACH,WAAW,EAAE,CAAC,SAAS,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAE1C;;;OAGG;IACH,kBAAkB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"useVirtualList.d.ts","sourceRoot":"","sources":["../../src/hooks/useVirtualList.ts"],"names":[],"mappings":"AAAA,OAAO,EAMH,aAAa,EACb,gBAAgB,EACnB,MAAM,OAAO,CAAC;AAKf,OAAO,EACH,WAAW,EACX,eAAe,EAClB,MAAM,UAAU,CAAC;AAElB;;;;GAIG;AACH,MAAM,MAAM,UAAU,GAAG,UAAU,GAAG,YAAY,CAAC;AAEnD,MAAM,WAAW,oBAAoB,CAAC,CAAC;IACnC,KAAK,EAAE,CAAC,EAAE,CAAC;IAEX;;;;OAIG;IACH,UAAU,CAAC,EAAE,CACT,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,CAAC,KACN,MAAM,GAAG,MAAM,CAAC;IAErB;;;OAGG;IACH,eAAe,EAAE,MAAM,CAAC;IAExB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,EACP,MAAM,GACN,CAAC,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC,CAAC;IAE9B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,IAAI,CAAC,EAAE,UAAU,CAAC;IAElB;;;;;OAKG;IACH,eAAe,CAAC,EAAE,eAAe,CAAC;IAElC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE3B,QAAQ,CAAC,EAAE,CACP,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,eAAe,KACzB,IAAI,CAAC;IAEV,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IAEzB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IAEzB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB;;;;;OAKG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAClC;AAED,MAAM,WAAW,oBAAoB;IACjC,YAAY,EAAE,WAAW,EAAE,CAAC;IAE5B,WAAW,EAAE,OAAO,CAAC;IAErB,YAAY,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,eAAe,EAAE,eAAe,CAAC;IAEjC,SAAS,EAAE,MAAM,CAAC;IAElB,YAAY,EAAE,CACV,KAAK,EAAE,MAAM,EACb,SAAS,CAAC,EACJ,OAAO,GACP,QAAQ,GACR,KAAK,KACV,IAAI,CAAC;IAEV,cAAc,EAAE,CACZ,MAAM,EAAE,MAAM,KACb,IAAI,CAAC;IAEV,eAAe,EAAE,CACb,MAAM,EAAE,MAAM,KACb,IAAI,CAAC;IAEV,YAAY,EAAE,gBAAgB,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IAEtD,QAAQ,EAAE,gBAAgB,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IAElD,YAAY,EAAE,CACV,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,KACX,aAAa,CAAC;IAEnB;;;;;;;;OAQG;IACH,WAAW,EAAE,CAAC,SAAS,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAE1C;;;OAGG;IACH,kBAAkB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,MAAM,CAAC;IACvD,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;IAC3D,aAAa,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;CACjD;AAID;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,CAAC,GAAG,GAAG,EAClC,MAAM,EAAE,oBAAoB,CAAC,CAAC,CAAC,GAChC,oBAAoB,CAketB"}
|