react-virtual-renderer 1.1.3 → 1.1.4

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 (84) 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 +66 -47
  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/VirtualizationEngine.d.ts +24 -13
  33. package/dist/core/VirtualizationEngine.d.ts.map +1 -1
  34. package/dist/core/VirtualizationEngine.js +50 -87
  35. package/dist/core/VirtualizationEngine.js.map +1 -1
  36. package/dist/examples/DynamicHeightLists.d.ts +2 -0
  37. package/dist/examples/DynamicHeightLists.d.ts.map +1 -0
  38. package/dist/examples/DynamicHeightLists.js +42 -0
  39. package/dist/examples/DynamicHeightLists.js.map +1 -0
  40. package/dist/examples/FixedGrid.d.ts +2 -0
  41. package/dist/examples/FixedGrid.d.ts.map +1 -0
  42. package/dist/examples/FixedGrid.js +40 -0
  43. package/dist/examples/FixedGrid.js.map +1 -0
  44. package/dist/examples/GroupedContactList.d.ts +2 -0
  45. package/dist/examples/GroupedContactList.d.ts.map +1 -0
  46. package/dist/examples/GroupedContactList.js +20 -0
  47. package/dist/examples/GroupedContactList.js.map +1 -0
  48. package/dist/examples/InfiniteScrollLists.d.ts +2 -0
  49. package/dist/examples/InfiniteScrollLists.d.ts.map +1 -0
  50. package/dist/examples/InfiniteScrollLists.js +93 -0
  51. package/dist/examples/InfiniteScrollLists.js.map +1 -0
  52. package/dist/examples/MasonaryGrid.d.ts +2 -0
  53. package/dist/examples/MasonaryGrid.d.ts.map +1 -0
  54. package/dist/examples/MasonaryGrid.js +36 -0
  55. package/dist/examples/MasonaryGrid.js.map +1 -0
  56. package/dist/examples/PerfHarness.d.ts +2 -0
  57. package/dist/examples/PerfHarness.d.ts.map +1 -0
  58. package/dist/examples/PerfHarness.js +131 -0
  59. package/dist/examples/PerfHarness.js.map +1 -0
  60. package/dist/hooks/useBidirectionalInfiniteScroll.d.ts +32 -0
  61. package/dist/hooks/useBidirectionalInfiniteScroll.d.ts.map +1 -0
  62. package/dist/hooks/useBidirectionalInfiniteScroll.js +89 -0
  63. package/dist/hooks/useBidirectionalInfiniteScroll.js.map +1 -0
  64. package/dist/hooks/useInfiniteScroll.d.ts +36 -12
  65. package/dist/hooks/useInfiniteScroll.d.ts.map +1 -1
  66. package/dist/hooks/useInfiniteScroll.js +46 -45
  67. package/dist/hooks/useInfiniteScroll.js.map +1 -1
  68. package/dist/hooks/useRevoingTabIndex.d.ts +11 -0
  69. package/dist/hooks/useRevoingTabIndex.d.ts.map +1 -0
  70. package/dist/hooks/useRevoingTabIndex.js +28 -0
  71. package/dist/hooks/useRevoingTabIndex.js.map +1 -0
  72. package/dist/hooks/useVirtualAllyProps.d.ts +23 -0
  73. package/dist/hooks/useVirtualAllyProps.d.ts.map +1 -0
  74. package/dist/hooks/useVirtualAllyProps.js +25 -0
  75. package/dist/hooks/useVirtualAllyProps.js.map +1 -0
  76. package/dist/index.cjs.js +1 -1
  77. package/dist/index.cjs.js.map +1 -1
  78. package/dist/index.esm.js +1 -1
  79. package/dist/index.esm.js.map +1 -1
  80. package/dist/main.js +4 -4
  81. package/dist/main.js.map +1 -1
  82. package/dist/types.d.ts +10 -0
  83. package/dist/types.d.ts.map +1 -1
  84. package/package.json +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DynamicHeightLists.js","sourceRoot":"","sources":["../../src/examples/DynamicHeightLists.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export default function FixedGridExample(): import("react/jsx-runtime").JSX.Element;
2
+ //# sourceMappingURL=FixedGrid.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FixedGrid.d.ts","sourceRoot":"","sources":["../../src/examples/FixedGrid.tsx"],"names":[],"mappings":"AAoDA,MAAM,CAAC,OAAO,UAAU,gBAAgB,4CAwDvC"}
@@ -0,0 +1,40 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React from "react";
3
+ import { VirtualGrid } from "../components/VirtualGrid";
4
+ async function fetchRealProducts(signal) {
5
+ const res = await fetch("https://dummyjson.com/products?limit=194", { signal });
6
+ if (!res.ok)
7
+ throw new Error(`Failed to fetch products: ${res.status}`);
8
+ const data = await res.json();
9
+ return data.products;
10
+ }
11
+ function tileToTargetSize(source, targetCount) {
12
+ return Array.from({ length: targetCount }, (_, i) => {
13
+ const src = source[i % source.length];
14
+ return { id: i, name: src.title, price: src.price, thumbnail: src.thumbnail };
15
+ });
16
+ }
17
+ const TARGET_COUNT = 200000;
18
+ export default function FixedGridExample() {
19
+ const [products, setProducts] = React.useState(null);
20
+ const [error, setError] = React.useState(null);
21
+ React.useEffect(() => {
22
+ const controller = new AbortController();
23
+ fetchRealProducts(controller.signal)
24
+ .then((raw) => setProducts(tileToTargetSize(raw, TARGET_COUNT)))
25
+ .catch((err) => {
26
+ if (!controller.signal.aborted) {
27
+ setError(err instanceof Error ? err.message : "Failed to load products");
28
+ }
29
+ });
30
+ return () => controller.abort();
31
+ }, []);
32
+ if (error) {
33
+ return _jsx("div", { style: { padding: 16, color: "#b91c1c" }, children: error });
34
+ }
35
+ if (!products) {
36
+ return _jsx("div", { style: { padding: 16, color: "#888" }, children: "Loading real product data\u2026" });
37
+ }
38
+ return (_jsx(VirtualGrid, { items: products, layout: "grid", columnCount: "auto", rowHeight: 220, gap: 16, height: 700, width: "100%", overscan: 8, itemKey: (_, item) => item.id, itemStyle: { borderRadius: 8, overflow: "hidden", border: "1px solid #eee" }, renderItem: ({ item }) => (_jsxs("div", { style: { display: "flex", flexDirection: "column", height: "100%" }, children: [_jsx("img", { src: item.thumbnail, alt: item.name, loading: "lazy", style: { flex: 1, width: "100%", objectFit: "cover" } }), _jsxs("div", { style: { padding: 8 }, children: [_jsx("div", { style: { fontWeight: 600, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }, children: item.name }), _jsxs("div", { style: { color: "#555" }, children: ["$", item.price.toFixed(2)] })] })] })) }));
39
+ }
40
+ //# sourceMappingURL=FixedGrid.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FixedGrid.js","sourceRoot":"","sources":["../../src/examples/FixedGrid.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export default function GroupedContactListExample(): import("react/jsx-runtime").JSX.Element;
2
+ //# sourceMappingURL=GroupedContactList.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GroupedContactList.d.ts","sourceRoot":"","sources":["../../src/examples/GroupedContactList.tsx"],"names":[],"mappings":"AAcA,MAAM,CAAC,OAAO,UAAU,yBAAyB,4CAkChD"}
@@ -0,0 +1,20 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ // examples/GroupedContactList.tsx
3
+ import React from 'react';
4
+ import { VirtualList } from '../components/VirtualList';
5
+ import { flattenGroups } from '../core/GroupedListEngine';
6
+ const FIRST_NAMES = [
7
+ 'Amara', 'Beatrix', 'Cyrus', 'Dahlia', 'Elias', 'Farah', 'Gideon', 'Harlow',
8
+ 'Ines', 'Jasper', 'Kira', 'Lior', 'Marisol', 'Noor', 'Otis', 'Priya',
9
+ 'Quinn', 'Rosalind', 'Soren', 'Talia', 'Uma', 'Vance', 'Willa', 'Xiomara',
10
+ 'Yusuf', 'Zara',
11
+ ];
12
+ export default function GroupedContactListExample() {
13
+ const contacts = React.useMemo(() => Array.from({ length: 5000 }, (_, i) => {
14
+ const first = FIRST_NAMES[i % FIRST_NAMES.length];
15
+ return { id: i, name: `${first} ${String(i).padStart(4, '0')}` };
16
+ }).sort((a, b) => a.name.localeCompare(b.name)), []);
17
+ const { rows, stickyIndices } = React.useMemo(() => flattenGroups({ items: contacts, getGroupKey: (c) => c.name[0].toUpperCase() }), [contacts]);
18
+ return (_jsx(VirtualList, { items: rows, height: 600, width: "100%", estimatedItemSize: 44, stickyIndices: stickyIndices, itemKey: (_, row) => (row.type === 'header' ? `h-${row.groupKey}` : row.item.id), renderItem: ({ item: row }) => row.type === 'header' ? (_jsx("div", { style: { background: '#f8f9fa', fontWeight: 700, padding: '6px 12px' }, children: row.groupKey })) : (_jsx("div", { style: { padding: '10px 12px' }, children: row.item.name })) }));
19
+ }
20
+ //# sourceMappingURL=GroupedContactList.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GroupedContactList.js","sourceRoot":"","sources":["../../src/examples/GroupedContactList.tsx"],"names":[],"mappings":";AAAA,kCAAkC;AAClC,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAI1D,MAAM,WAAW,GAAG;IAChB,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ;IAC3E,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO;IACpE,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS;IACzE,OAAO,EAAE,MAAM;CAClB,CAAC;AAEF,MAAM,CAAC,OAAO,UAAU,yBAAyB;IAC7C,MAAM,QAAQ,GAAc,KAAK,CAAC,OAAO,CACrC,GAAG,EAAE,CACD,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QAClC,MAAM,KAAK,GAAG,WAAW,CAAC,CAAC,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC;QAClD,OAAO,EAAE,EAAE,EAAE,CAAC,EAAE,IAAI,EAAE,GAAG,KAAK,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC;IACrE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,EACnD,EAAE,CACL,CAAC;IAEF,MAAM,EAAE,IAAI,EAAE,aAAa,EAAE,GAAG,KAAK,CAAC,OAAO,CACzC,GAAG,EAAE,CAAC,aAAa,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC,EACrF,CAAC,QAAQ,CAAC,CACb,CAAC;IAEF,OAAO,CACH,KAAC,WAAW,IACR,KAAK,EAAE,IAAI,EACX,MAAM,EAAE,GAAG,EACX,KAAK,EAAC,MAAM,EACZ,iBAAiB,EAAE,EAAE,EACrB,aAAa,EAAE,aAAa,EAC5B,OAAO,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,IAAK,CAAC,EAAE,CAAC,EACjF,UAAU,EAAE,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE,EAAE,CAC1B,GAAG,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,CACpB,cAAK,KAAK,EAAE,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,GAAG,EAAE,OAAO,EAAE,UAAU,EAAE,YACtE,GAAG,CAAC,QAAQ,GACX,CACT,CAAC,CAAC,CAAC,CACA,cAAK,KAAK,EAAE,EAAE,OAAO,EAAE,WAAW,EAAE,YAAG,GAAG,CAAC,IAAK,CAAC,IAAI,GAAO,CAC/D,GAEP,CACL,CAAC;AACN,CAAC"}
@@ -0,0 +1,2 @@
1
+ export default function InfiniteScrollExample(): import("react/jsx-runtime").JSX.Element;
2
+ //# sourceMappingURL=InfiniteScrollLists.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InfiniteScrollLists.d.ts","sourceRoot":"","sources":["../../src/examples/InfiniteScrollLists.tsx"],"names":[],"mappings":"AA6EA,MAAM,CAAC,OAAO,UAAU,qBAAqB,4CA4H5C"}
@@ -0,0 +1,93 @@
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
+ import { useInfiniteScroll } from "../hooks/useInfiniteScroll";
5
+ const PAGE_SIZE = 20;
6
+ async function fetchPostsPage(skip, signal) {
7
+ const res = await fetch(`https://dummyjson.com/posts?limit=${PAGE_SIZE}&skip=${skip}`, { signal });
8
+ if (!res.ok)
9
+ throw new Error(`Failed to fetch posts: ${res.status}`);
10
+ return res.json();
11
+ }
12
+ function normalizePost(raw) {
13
+ const reactions = typeof raw.reactions === "number"
14
+ ? { likes: raw.reactions, dislikes: 0 }
15
+ : raw.reactions;
16
+ return {
17
+ id: raw.id,
18
+ title: raw.title,
19
+ body: raw.body,
20
+ tags: raw.tags,
21
+ likes: reactions.likes,
22
+ dislikes: reactions.dislikes,
23
+ views: raw.views,
24
+ };
25
+ }
26
+ export default function InfiniteScrollExample() {
27
+ const [items, setItems] = React.useState([]);
28
+ const [total, setTotal] = React.useState(null);
29
+ const [isLoading, setIsLoading] = React.useState(false);
30
+ const [error, setError] = React.useState(null);
31
+ const containerRef = React.useRef(null);
32
+ const abortRef = React.useRef(null);
33
+ const hasMore = total === null || items.length < total;
34
+ const loadMore = React.useCallback(async () => {
35
+ if (total !== null && items.length >= total)
36
+ return;
37
+ const controller = new AbortController();
38
+ abortRef.current = controller;
39
+ setIsLoading(true);
40
+ setError(null);
41
+ try {
42
+ const data = await fetchPostsPage(items.length, controller.signal);
43
+ setItems((prev) => [...prev, ...data.posts.map(normalizePost)]);
44
+ setTotal(data.total);
45
+ }
46
+ catch (err) {
47
+ if (!controller.signal.aborted) {
48
+ setError(err instanceof Error ? err.message : "Failed to load more posts");
49
+ }
50
+ }
51
+ finally {
52
+ if (!controller.signal.aborted)
53
+ setIsLoading(false);
54
+ }
55
+ }, [items.length, total]);
56
+ React.useEffect(() => {
57
+ loadMore();
58
+ // eslint-disable-next-line react-hooks/exhaustive-deps
59
+ }, []);
60
+ React.useEffect(() => {
61
+ return () => {
62
+ abortRef.current?.abort();
63
+ };
64
+ }, []);
65
+ const { sentinelRef } = useInfiniteScroll({
66
+ onLoadMore: loadMore,
67
+ isLoading,
68
+ enabled: hasMore,
69
+ root: containerRef,
70
+ rootMargin: "400px",
71
+ });
72
+ return (_jsxs("div", { children: [error && (_jsxs("div", { style: { padding: 8, color: "#b91c1c", fontSize: 13 }, children: [error, " \u2014", " ", _jsx("button", { onClick: () => loadMore(), style: { textDecoration: "underline" }, children: "retry" })] })), _jsx(VirtualList, { ref: containerRef, items: items, height: 600, width: "100%", estimatedItemSize: 140, itemKey: (_, item) => item.id, itemStyle: {
73
+ display: "flex",
74
+ flexDirection: "column",
75
+ gap: 6,
76
+ padding: "14px 16px",
77
+ borderBottom: "1px solid #eee",
78
+ }, renderItem: ({ item }) => (_jsxs(_Fragment, { children: [_jsx("div", { style: { fontWeight: 600, fontSize: 15 }, children: item.title }), _jsx("p", { style: { margin: 0, color: "#555", lineHeight: 1.5 }, children: item.body }), _jsx("div", { style: { display: "flex", gap: 6, flexWrap: "wrap", marginTop: 4 }, children: item.tags.map((tag) => (_jsxs("span", { style: {
79
+ fontSize: 12,
80
+ padding: "2px 8px",
81
+ borderRadius: 999,
82
+ background: "#f1f3f5",
83
+ color: "#495057",
84
+ }, children: ["#", tag] }, tag))) }), _jsxs("div", { style: { display: "flex", gap: 16, fontSize: 12, color: "#888", marginTop: 4 }, children: [_jsxs("span", { children: ["\uD83D\uDC4D ", item.likes] }), _jsxs("span", { children: ["\uD83D\uDC4E ", item.dislikes] }), _jsxs("span", { children: ["\uD83D\uDC41 ", item.views.toLocaleString(), " views"] })] })] })),
85
+ // sentinel is now ALWAYS mounted, independent of virtualization -
86
+ // this is the actual fix, not the renderItem branch above
87
+ footer: hasMore ? (_jsx("div", { ref: sentinelRef, style: { height: 1, width: "100%" } })) : null }), _jsx("div", { style: { textAlign: "center", padding: 12, color: "#888", fontSize: 13 }, children: isLoading
88
+ ? "Loading more…"
89
+ : hasMore
90
+ ? `Loaded ${items.length} of ${total ?? "…"} posts`
91
+ : `That's everything (${items.length} posts)` })] }));
92
+ }
93
+ //# sourceMappingURL=InfiniteScrollLists.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InfiniteScrollLists.js","sourceRoot":"","sources":["../../src/examples/InfiniteScrollLists.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export default function MasonryGridExample(): import("react/jsx-runtime").JSX.Element;
2
+ //# sourceMappingURL=MasonaryGrid.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MasonaryGrid.d.ts","sourceRoot":"","sources":["../../src/examples/MasonaryGrid.tsx"],"names":[],"mappings":"AAoDA,MAAM,CAAC,OAAO,UAAU,kBAAkB,4CAiDzC"}
@@ -0,0 +1,36 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ import React from "react";
3
+ import { VirtualGrid } from "../components/VirtualGrid";
4
+ /**
5
+ * Plain data generator - NOT a component or hook, so it must not call
6
+ * useState/useEffect/etc. (that was the bug in the previous version:
7
+ * a bare `[images] = useState(...)` inside a regular function violates
8
+ * the Rules of Hooks and silently breaks - hooks only run inside a
9
+ * component body or another hook).
10
+ */
11
+ function makePhotos(startId, count) {
12
+ return Array.from({ length: count }, (_, i) => {
13
+ const id = startId + i;
14
+ // deterministic pseudo-random height per id, stable across re-renders -
15
+ // matches picsum's own {width}/{height} path so the requested image
16
+ // dimensions always equal the space we've reserved for it
17
+ const height = 220 + (id % 6) * 60;
18
+ return {
19
+ id,
20
+ url: `https://picsum.photos/500/${height}?random=${id}`,
21
+ title: `Beautiful Image ${id + 1}`,
22
+ height,
23
+ };
24
+ });
25
+ }
26
+ export default function MasonryGridExample() {
27
+ const [photos, setPhotos] = React.useState(() => makePhotos(0, 200));
28
+ const rowHeightForIndex = React.useCallback((index) => photos[index]?.height ?? 220, [photos]);
29
+ return (_jsxs("div", { children: [_jsx("div", { style: { marginBottom: 8, display: "flex", gap: 8, alignItems: "center" }, children: _jsxs("button", { onClick: () => setPhotos((prev) => [...prev, ...makePhotos(prev.length, 20)]), children: ["Load 20 more (", photos.length, " total) - watch this stay fast even at 50k+"] }) }), _jsx(VirtualGrid, { items: photos, layout: "masonry", columnCount: 5, rowHeight: rowHeightForIndex, gap: 12, height: 700, width: "100%", overscan: 6, itemKey: (_, item) => item.id, itemStyle: { borderRadius: 8, overflow: "hidden", background: "#eee" }, renderItem: ({ item }) => (_jsx("img", { src: item.url, alt: item.title, loading: "lazy", width: 500, height: item.height, style: {
30
+ width: "100%",
31
+ height: "100%",
32
+ objectFit: "cover",
33
+ display: "block",
34
+ } })) })] }));
35
+ }
36
+ //# sourceMappingURL=MasonaryGrid.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MasonaryGrid.js","sourceRoot":"","sources":["../../src/examples/MasonaryGrid.tsx"],"names":[],"mappings":";AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AA4BxD;;;;;;GAMG;AACH,SAAS,UAAU,CAAC,OAAe,EAAE,KAAa;IAC9C,OAAO,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QAC1C,MAAM,EAAE,GAAG,OAAO,GAAG,CAAC,CAAC;QACvB,wEAAwE;QACxE,oEAAoE;QACpE,0DAA0D;QAC1D,MAAM,MAAM,GAAG,GAAG,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC;QACnC,OAAO;YACH,EAAE;YACF,GAAG,EAAE,6BAA6B,MAAM,WAAW,EAAE,EAAE;YACvD,KAAK,EAAE,mBAAmB,EAAE,GAAG,CAAC,EAAE;YAClC,MAAM;SACT,CAAC;IACN,CAAC,CAAC,CAAC;AACP,CAAC;AAED,MAAM,CAAC,OAAO,UAAU,kBAAkB;IACtC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAgB,GAAG,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;IAEpF,MAAM,iBAAiB,GAAG,KAAK,CAAC,WAAW,CACvC,CAAC,KAAa,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,MAAM,IAAI,GAAG,EAC/C,CAAC,MAAM,CAAC,CACX,CAAC;IAEF,OAAO,CACH,0BACI,cAAK,KAAK,EAAE,EAAE,YAAY,EAAE,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,YAC1E,kBACI,OAAO,EAAE,GAAG,EAAE,CACV,SAAS,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,GAAG,UAAU,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,+BAGnD,MAAM,CAAC,MAAM,mDACvB,GACP,EAEN,KAAC,WAAW,IACR,KAAK,EAAE,MAAM,EACb,MAAM,EAAC,SAAS,EAChB,WAAW,EAAE,CAAC,EACd,SAAS,EAAE,iBAAiB,EAC5B,GAAG,EAAE,EAAE,EACP,MAAM,EAAE,GAAG,EACX,KAAK,EAAC,MAAM,EACZ,QAAQ,EAAE,CAAC,EACX,OAAO,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,EAC7B,SAAS,EAAE,EAAE,YAAY,EAAE,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,EAAE,EACtE,UAAU,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CACtB,cACI,GAAG,EAAE,IAAI,CAAC,GAAG,EACb,GAAG,EAAE,IAAI,CAAC,KAAK,EACf,OAAO,EAAC,MAAM,EACd,KAAK,EAAE,GAAG,EACV,MAAM,EAAE,IAAI,CAAC,MAAM,EACnB,KAAK,EAAE;wBACH,KAAK,EAAE,MAAM;wBACb,MAAM,EAAE,MAAM;wBACd,SAAS,EAAE,OAAO;wBAClB,OAAO,EAAE,OAAO;qBACnB,GACH,CACL,GACH,IACA,CACT,CAAC;AACN,CAAC"}
@@ -0,0 +1,2 @@
1
+ export default function PerfHarnessExample(): import("react/jsx-runtime").JSX.Element;
2
+ //# sourceMappingURL=PerfHarness.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PerfHarness.d.ts","sourceRoot":"","sources":["../../src/examples/PerfHarness.tsx"],"names":[],"mappings":"AAkHA,MAAM,CAAC,OAAO,UAAU,kBAAkB,4CAmGzC"}
@@ -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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"}