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.
Files changed (92) hide show
  1. package/dist/App.d.ts.map +1 -1
  2. package/dist/App.js +65 -3
  3. package/dist/App.js.map +1 -1
  4. package/dist/components/VirtualGrid.d.ts +30 -0
  5. package/dist/components/VirtualGrid.d.ts.map +1 -1
  6. package/dist/components/VirtualGrid.js +147 -117
  7. package/dist/components/VirtualGrid.js.map +1 -1
  8. package/dist/components/VirtualList.d.ts +0 -4
  9. package/dist/components/VirtualList.d.ts.map +1 -1
  10. package/dist/components/VirtualList.js +76 -86
  11. package/dist/components/VirtualList.js.map +1 -1
  12. package/dist/components/VirtualLiveRegion.d.ts +12 -0
  13. package/dist/components/VirtualLiveRegion.d.ts.map +1 -0
  14. package/dist/components/VirtualLiveRegion.js +27 -0
  15. package/dist/components/VirtualLiveRegion.js.map +1 -0
  16. package/dist/core/FenwickTree.d.ts +47 -0
  17. package/dist/core/FenwickTree.d.ts.map +1 -0
  18. package/dist/core/FenwickTree.js +206 -0
  19. package/dist/core/FenwickTree.js.map +1 -0
  20. package/dist/core/GridLayoutEngine.d.ts +64 -0
  21. package/dist/core/GridLayoutEngine.d.ts.map +1 -0
  22. package/dist/core/GridLayoutEngine.js +153 -0
  23. package/dist/core/GridLayoutEngine.js.map +1 -0
  24. package/dist/core/GroupedListEngine.d.ts +21 -0
  25. package/dist/core/GroupedListEngine.d.ts.map +1 -0
  26. package/dist/core/GroupedListEngine.js +22 -0
  27. package/dist/core/GroupedListEngine.js.map +1 -0
  28. package/dist/core/ListController.d.ts +31 -0
  29. package/dist/core/ListController.d.ts.map +1 -0
  30. package/dist/core/ListController.js +71 -0
  31. package/dist/core/ListController.js.map +1 -0
  32. package/dist/core/MeasurementSystem.d.ts +1 -1
  33. package/dist/core/MeasurementSystem.d.ts.map +1 -1
  34. package/dist/core/MeasurementSystem.js +22 -10
  35. package/dist/core/MeasurementSystem.js.map +1 -1
  36. package/dist/core/VirtualizationEngine.d.ts +24 -13
  37. package/dist/core/VirtualizationEngine.d.ts.map +1 -1
  38. package/dist/core/VirtualizationEngine.js +50 -87
  39. package/dist/core/VirtualizationEngine.js.map +1 -1
  40. package/dist/examples/DynamicHeightLists.d.ts +2 -0
  41. package/dist/examples/DynamicHeightLists.d.ts.map +1 -0
  42. package/dist/examples/DynamicHeightLists.js +42 -0
  43. package/dist/examples/DynamicHeightLists.js.map +1 -0
  44. package/dist/examples/FixedGrid.d.ts +2 -0
  45. package/dist/examples/FixedGrid.d.ts.map +1 -0
  46. package/dist/examples/FixedGrid.js +40 -0
  47. package/dist/examples/FixedGrid.js.map +1 -0
  48. package/dist/examples/GroupedContactList.d.ts +2 -0
  49. package/dist/examples/GroupedContactList.d.ts.map +1 -0
  50. package/dist/examples/GroupedContactList.js +20 -0
  51. package/dist/examples/GroupedContactList.js.map +1 -0
  52. package/dist/examples/InfiniteScrollLists.d.ts +2 -0
  53. package/dist/examples/InfiniteScrollLists.d.ts.map +1 -0
  54. package/dist/examples/InfiniteScrollLists.js +93 -0
  55. package/dist/examples/InfiniteScrollLists.js.map +1 -0
  56. package/dist/examples/MasonaryGrid.d.ts +2 -0
  57. package/dist/examples/MasonaryGrid.d.ts.map +1 -0
  58. package/dist/examples/MasonaryGrid.js +36 -0
  59. package/dist/examples/MasonaryGrid.js.map +1 -0
  60. package/dist/examples/PerfHarness.d.ts +2 -0
  61. package/dist/examples/PerfHarness.d.ts.map +1 -0
  62. package/dist/examples/PerfHarness.js +131 -0
  63. package/dist/examples/PerfHarness.js.map +1 -0
  64. package/dist/hooks/useBidirectionalInfiniteScroll.d.ts +32 -0
  65. package/dist/hooks/useBidirectionalInfiniteScroll.d.ts.map +1 -0
  66. package/dist/hooks/useBidirectionalInfiniteScroll.js +89 -0
  67. package/dist/hooks/useBidirectionalInfiniteScroll.js.map +1 -0
  68. package/dist/hooks/useInfiniteScroll.d.ts +36 -12
  69. package/dist/hooks/useInfiniteScroll.d.ts.map +1 -1
  70. package/dist/hooks/useInfiniteScroll.js +46 -45
  71. package/dist/hooks/useInfiniteScroll.js.map +1 -1
  72. package/dist/hooks/useRevoingTabIndex.d.ts +11 -0
  73. package/dist/hooks/useRevoingTabIndex.d.ts.map +1 -0
  74. package/dist/hooks/useRevoingTabIndex.js +28 -0
  75. package/dist/hooks/useRevoingTabIndex.js.map +1 -0
  76. package/dist/hooks/useVirtualAllyProps.d.ts +23 -0
  77. package/dist/hooks/useVirtualAllyProps.d.ts.map +1 -0
  78. package/dist/hooks/useVirtualAllyProps.js +25 -0
  79. package/dist/hooks/useVirtualAllyProps.js.map +1 -0
  80. package/dist/hooks/useVirtualList.d.ts +2 -0
  81. package/dist/hooks/useVirtualList.d.ts.map +1 -1
  82. package/dist/hooks/useVirtualList.js +12 -4
  83. package/dist/hooks/useVirtualList.js.map +1 -1
  84. package/dist/index.cjs.js +1 -1
  85. package/dist/index.cjs.js.map +1 -1
  86. package/dist/index.esm.js +1 -1
  87. package/dist/index.esm.js.map +1 -1
  88. package/dist/main.js +4 -4
  89. package/dist/main.js.map +1 -1
  90. package/dist/types.d.ts +10 -0
  91. package/dist/types.d.ts.map +1 -1
  92. 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
- * Callback triggered when user reaches threshold
4
+ * Called when the sentinel enters the observed root's margin.
4
5
  */
5
6
  onLoadMore: () => void | Promise<void>;
6
7
  /**
7
- * Distance from bottom before triggering load
8
- * Default: 500px
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
- threshold?: number;
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
- * Enable / Disable infinite scroll
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
- enabled?: boolean;
26
+ root?: RefObject<Element | null>;
19
27
  }
20
28
  export interface UseInfiniteScrollReturn<T extends HTMLElement = HTMLDivElement> {
21
- containerRef: React.RefObject<T | null>;
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
- * Automatically calls onLoadMore when the user
30
- * scrolls near the bottom of a container.
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, threshold, isLoading, enabled, }: UseInfiniteScrollParams): UseInfiniteScrollReturn<T>;
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":"AAOA,MAAM,WAAW,uBAAuB;IACpC;;OAEG;IACH,UAAU,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvC;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB;;OAEG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,uBAAuB,CACpC,CAAC,SAAS,WAAW,GAAG,cAAc;IAEtC,YAAY,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IACxC,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,eAAe,EAAE,MAAM,IAAI,CAAC;CAC/B;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC7B,CAAC,SAAS,WAAW,GAAG,cAAc,EACxC,EACE,UAAU,EACV,SAAe,EACf,SAAiB,EACjB,OAAc,GACjB,EAAE,uBAAuB,GAAG,uBAAuB,CAAC,CAAC,CAAC,CA6FtD"}
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 { useCallback, useEffect, useRef, useState, } from "react";
1
+ import { useEffect, useRef, useState, } from "react";
2
2
  /**
3
3
  * useInfiniteScroll
4
4
  *
5
- * Automatically calls onLoadMore when the user
6
- * scrolls near the bottom of a container.
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, threshold = 500, isLoading = false, enabled = true, }) {
9
- const containerRef = useRef(null);
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 container = containerRef.current;
45
- if (!container)
31
+ const sentinel = sentinelRef.current;
32
+ if (!sentinel)
46
33
  return;
47
- const handleScroll = () => {
48
- checkIfAtBottom();
49
- };
50
- container.addEventListener("scroll", handleScroll, { passive: true });
51
- // Initial check
52
- checkIfAtBottom();
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
- container.removeEventListener("scroll", handleScroll);
58
+ observer.disconnect();
55
59
  };
56
- }, [
57
- enabled,
58
- checkIfAtBottom,
59
- ]);
60
+ // eslint-disable-next-line react-hooks/exhaustive-deps
61
+ }, [enabled, rootMargin, isLoading, root?.current]);
60
62
  return {
61
- containerRef,
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,WAAW,EACX,SAAS,EACT,MAAM,EACN,QAAQ,GACX,MAAM,OAAO,CAAC;AAkCf;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAE/B,EACE,UAAU,EACV,SAAS,GAAG,GAAG,EACf,SAAS,GAAG,KAAK,EACjB,OAAO,GAAG,IAAI,GACQ;IACtB,MAAM,YAAY,GAAG,MAAM,CAAW,IAAI,CAAC,CAAC;IAE5C,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAC7B,QAAQ,CAAC,KAAK,CAAC,CAAC;IAEpB,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAEjC,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,MAAM,eAAe,GAAG,WAAW,CAAC,GAAG,EAAE;QACrC,IAAI,CAAC,OAAO;YAAE,OAAO;QAErB,MAAM,SAAS,GAAG,YAAY,CAAC,OAAO,CAAC;QAEvC,IAAI,CAAC,SAAS;YAAE,OAAO;QAEvB,MAAM,EACF,SAAS,EACT,YAAY,EACZ,YAAY,GACf,GAAG,SAAS,CAAC;QAEd,MAAM,kBAAkB,GACpB,YAAY;YACZ,SAAS;YACT,YAAY,CAAC;QAEjB,MAAM,aAAa,GACf,kBAAkB,IAAI,SAAS,CAAC;QAEpC,aAAa,CAAC,aAAa,CAAC,CAAC;QAE7B,IACI,aAAa;YACb,CAAC,UAAU,CAAC,OAAO;YACnB,CAAC,SAAS,EACZ,CAAC;YACC,UAAU,CAAC,OAAO,GAAG,IAAI,CAAC;YAE1B,OAAO,CAAC,OAAO,CACX,aAAa,CAAC,OAAO,EAAE,CAC1B,CAAC,OAAO,CAAC,GAAG,EAAE;gBACX,UAAU,CAAC,OAAO,GAAG,KAAK,CAAC;YAC/B,CAAC,CAAC,CAAC;QACP,CAAC;IACL,CAAC,EAAE;QACC,SAAS;QACT,OAAO;QACP,SAAS;KACZ,CAAC,CAAC;IAEH,SAAS,CAAC,GAAG,EAAE;QACX,IAAI,CAAC,OAAO;YAAE,OAAO;QAErB,MAAM,SAAS,GAAG,YAAY,CAAC,OAAO,CAAC;QAEvC,IAAI,CAAC,SAAS;YAAE,OAAO;QAEvB,MAAM,YAAY,GAAG,GAAG,EAAE;YACtB,eAAe,EAAE,CAAC;QACtB,CAAC,CAAC;QAEF,SAAS,CAAC,gBAAgB,CACtB,QAAQ,EACR,YAAY,EACZ,EAAE,OAAO,EAAE,IAAI,EAAE,CACpB,CAAC;QAEF,gBAAgB;QAChB,eAAe,EAAE,CAAC;QAElB,OAAO,GAAG,EAAE;YACR,SAAS,CAAC,mBAAmB,CACzB,QAAQ,EACR,YAAY,CACf,CAAC;QACN,CAAC,CAAC;IACN,CAAC,EAAE;QACC,OAAO;QACP,eAAe;KAClB,CAAC,CAAC;IAEH,OAAO;QACH,YAAY;QACZ,UAAU;QACV,SAAS;QACT,eAAe;KAClB,CAAC;AACN,CAAC"}
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;CAC1D;AAID;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,CAAC,GAAG,GAAG,EAClC,MAAM,EAAE,oBAAoB,CAAC,CAAC,CAAC,GAChC,oBAAoB,CAwdtB"}
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"}