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,153 @@
1
+ /**
2
+ * Incremental, append-only masonry/grid position engine.
3
+ *
4
+ * PERFORMANCE: the previous VirtualGrid recomputed EVERY item's
5
+ * position from scratch (a full O(n) pass) whenever the items array
6
+ * changed identity - including the ordinary infinite-scroll case of
7
+ * appending 20 new items to an existing 50,000-item masonry board,
8
+ * which turned a single "load more" into an O(n) pass over everything
9
+ * already laid out.
10
+ *
11
+ * This engine keeps running state - per-column heights for masonry, a
12
+ * running row-offset for dynamic-height grid rows - and only computes
13
+ * positions for items it hasn't seen before. Appending k new items
14
+ * costs O(k), never O(n + k). A full reset (columns/width/gap/layout
15
+ * changed) is the only path that pays an O(n) cost, same as any other
16
+ * virtualization library when the layout itself is invalidated.
17
+ *
18
+ * NOTE ON VIRTUAL_GRID'S FAST PATH: this engine is only used for
19
+ * masonry layout, or grid layout with a per-index rowHeight function.
20
+ * Plain grid layout with a fixed numeric rowHeight never touches this
21
+ * file at all - VirtualGrid computes those positions with direct O(1)
22
+ * arithmetic and no array (see VirtualGrid.tsx), which is faster still.
23
+ */
24
+ export class GridLayoutEngine {
25
+ constructor() {
26
+ Object.defineProperty(this, "positions", {
27
+ enumerable: true,
28
+ configurable: true,
29
+ writable: true,
30
+ value: []
31
+ });
32
+ Object.defineProperty(this, "colHeights", {
33
+ enumerable: true,
34
+ configurable: true,
35
+ writable: true,
36
+ value: []
37
+ });
38
+ Object.defineProperty(this, "rowOffsets", {
39
+ enumerable: true,
40
+ configurable: true,
41
+ writable: true,
42
+ value: [0]
43
+ });
44
+ Object.defineProperty(this, "currentRowMax", {
45
+ enumerable: true,
46
+ configurable: true,
47
+ writable: true,
48
+ value: 0
49
+ });
50
+ Object.defineProperty(this, "maxBottom", {
51
+ enumerable: true,
52
+ configurable: true,
53
+ writable: true,
54
+ value: 0
55
+ });
56
+ Object.defineProperty(this, "params", {
57
+ enumerable: true,
58
+ configurable: true,
59
+ writable: true,
60
+ value: null
61
+ });
62
+ }
63
+ sameParams(p) {
64
+ if (!this.params)
65
+ return false;
66
+ return (this.params.cols === p.cols &&
67
+ this.params.colWidth === p.colWidth &&
68
+ this.params.gap === p.gap &&
69
+ this.params.layout === p.layout &&
70
+ this.params.rowHeight === p.rowHeight);
71
+ }
72
+ reset(p) {
73
+ this.positions = [];
74
+ this.colHeights = new Array(p.cols).fill(0);
75
+ this.rowOffsets = [0];
76
+ this.currentRowMax = 0;
77
+ this.maxBottom = 0;
78
+ this.params = p;
79
+ }
80
+ sizeOf(rowHeight, index) {
81
+ return typeof rowHeight === 'function' ? rowHeight(index) : rowHeight;
82
+ }
83
+ /** Masonry: place each new item in whichever column is currently shortest. */
84
+ appendMasonry(fromIndex, itemCount, p) {
85
+ for (let i = fromIndex; i < itemCount; i++) {
86
+ const h = this.sizeOf(p.rowHeight, i);
87
+ let minCol = 0;
88
+ for (let c = 1; c < p.cols; c++) {
89
+ if (this.colHeights[c] < this.colHeights[minCol])
90
+ minCol = c;
91
+ }
92
+ const x = minCol * (p.colWidth + p.gap);
93
+ const y = this.colHeights[minCol];
94
+ this.positions.push({ x, y, width: p.colWidth, height: h });
95
+ this.colHeights[minCol] += h + p.gap;
96
+ if (y + h > this.maxBottom)
97
+ this.maxBottom = y + h;
98
+ }
99
+ }
100
+ /**
101
+ * Grid with per-index rowHeight function: rows advance by the
102
+ * tallest item seen in that row so far. `rowOffsets[r]` is only
103
+ * ever written once (when row r's first item is appended), reading
104
+ * `currentRowMax` accumulated while row r-1 was being built - so
105
+ * this stays correct across multiple incremental append() calls,
106
+ * not just a single full pass.
107
+ */
108
+ appendGrid(fromIndex, itemCount, p) {
109
+ for (let i = fromIndex; i < itemCount; i++) {
110
+ const row = Math.floor(i / p.cols);
111
+ const col = i % p.cols;
112
+ const h = this.sizeOf(p.rowHeight, i);
113
+ while (this.rowOffsets.length <= row) {
114
+ const prevRow = this.rowOffsets.length - 1;
115
+ const gapBefore = prevRow >= 0 ? p.gap : 0;
116
+ this.rowOffsets.push(this.rowOffsets[prevRow] + this.currentRowMax + gapBefore);
117
+ this.currentRowMax = 0;
118
+ }
119
+ const y = this.rowOffsets[row];
120
+ const x = col * (p.colWidth + p.gap);
121
+ this.positions.push({ x, y, width: p.colWidth, height: h });
122
+ this.currentRowMax = Math.max(this.currentRowMax, h);
123
+ if (y + h > this.maxBottom)
124
+ this.maxBottom = y + h;
125
+ }
126
+ }
127
+ /**
128
+ * Ensure positions exist for `itemCount` items under the given
129
+ * params. Existing entries are reused by reference - callers get
130
+ * back the same array instances for indices they've already seen,
131
+ * which keeps this cheap to call on every render.
132
+ */
133
+ sync(itemCount, p) {
134
+ if (!this.sameParams(p) || itemCount < this.positions.length) {
135
+ this.reset(p);
136
+ }
137
+ const from = this.positions.length;
138
+ if (from < itemCount) {
139
+ if (p.layout === 'masonry') {
140
+ this.appendMasonry(from, itemCount, p);
141
+ }
142
+ else {
143
+ this.appendGrid(from, itemCount, p);
144
+ }
145
+ }
146
+ return this.positions;
147
+ }
148
+ /** O(1) - tracked incrementally as items are appended, never rescanned. */
149
+ getTotalHeight() {
150
+ return this.maxBottom;
151
+ }
152
+ }
153
+ //# sourceMappingURL=GridLayoutEngine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GridLayoutEngine.js","sourceRoot":"","sources":["../../src/core/GridLayoutEngine.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ export interface GroupDefinition<T> {
2
+ items: T[];
3
+ getGroupKey: (item: T) => string;
4
+ }
5
+ export interface FlattenedRow<T> {
6
+ type: 'header' | 'item';
7
+ groupKey: string;
8
+ item?: T;
9
+ itemIndex?: number;
10
+ }
11
+ /**
12
+ * Flattens grouped data into one virtualizable array with header rows
13
+ * interspersed - VirtualList treats a header as just another row, no
14
+ * special-casing in the engine. Your existing `stickyIndices` prop is
15
+ * what actually pins headers visually while scrolling.
16
+ */
17
+ export declare function flattenGroups<T>({ items, getGroupKey }: GroupDefinition<T>): {
18
+ rows: FlattenedRow<T>[];
19
+ stickyIndices: number[];
20
+ };
21
+ //# sourceMappingURL=GroupedListEngine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GroupedListEngine.d.ts","sourceRoot":"","sources":["../../src/core/GroupedListEngine.ts"],"names":[],"mappings":"AACA,MAAM,WAAW,eAAe,CAAC,CAAC;IAC9B,KAAK,EAAE,CAAC,EAAE,CAAC;IACX,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,CAAC;CACpC;AAED,MAAM,WAAW,YAAY,CAAC,CAAC;IAC3B,IAAI,EAAE,QAAQ,GAAG,MAAM,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,CAAC,CAAC;IACT,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE,eAAe,CAAC,CAAC,CAAC,GAAG;IAC1E,IAAI,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC;IACxB,aAAa,EAAE,MAAM,EAAE,CAAC;CAC3B,CAgBA"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Flattens grouped data into one virtualizable array with header rows
3
+ * interspersed - VirtualList treats a header as just another row, no
4
+ * special-casing in the engine. Your existing `stickyIndices` prop is
5
+ * what actually pins headers visually while scrolling.
6
+ */
7
+ export function flattenGroups({ items, getGroupKey }) {
8
+ const rows = [];
9
+ const stickyIndices = [];
10
+ let lastKey = null;
11
+ items.forEach((item, itemIndex) => {
12
+ const key = getGroupKey(item);
13
+ if (key !== lastKey) {
14
+ stickyIndices.push(rows.length);
15
+ rows.push({ type: 'header', groupKey: key });
16
+ lastKey = key;
17
+ }
18
+ rows.push({ type: 'item', groupKey: key, item, itemIndex });
19
+ });
20
+ return { rows, stickyIndices };
21
+ }
22
+ //# sourceMappingURL=GroupedListEngine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GroupedListEngine.js","sourceRoot":"","sources":["../../src/core/GroupedListEngine.ts"],"names":[],"mappings":"AAaA;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAI,EAAE,KAAK,EAAE,WAAW,EAAsB;IAIvE,MAAM,IAAI,GAAsB,EAAE,CAAC;IACnC,MAAM,aAAa,GAAa,EAAE,CAAC;IACnC,IAAI,OAAO,GAAkB,IAAI,CAAC;IAElC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,SAAS,EAAE,EAAE;QAC9B,MAAM,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;QAC9B,IAAI,GAAG,KAAK,OAAO,EAAE,CAAC;YAClB,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YAChC,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,CAAC;YAC7C,OAAO,GAAG,GAAG,CAAC;QAClB,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;IAChE,CAAC,CAAC,CAAC;IAEH,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC;AACnC,CAAC"}
@@ -0,0 +1,31 @@
1
+ export interface ListControllerOptions {
2
+ itemCount: number;
3
+ estimatedItemSize: number;
4
+ overscan?: number;
5
+ }
6
+ type Listener = () => void;
7
+ /**
8
+ * Framework-agnostic controller: owns the engine + scroll state,
9
+ * exposes subscribe/getSnapshot. React binds via useSyncExternalStore;
10
+ * Vue could bind via a reactive ref; Svelte via a store - the engine
11
+ * itself never needs to know any of that exists.
12
+ */
13
+ export declare class ListController {
14
+ private engine;
15
+ private scrollOffset;
16
+ private containerSize;
17
+ private itemCount;
18
+ private overscan;
19
+ private listeners;
20
+ constructor({ itemCount, estimatedItemSize, overscan }: ListControllerOptions);
21
+ subscribe: (listener: Listener) => () => boolean;
22
+ private notify;
23
+ setScrollOffset(offset: number): void;
24
+ setContainerSize(size: number): void;
25
+ setItemCount(count: number): void;
26
+ recordItemSize(index: number, size: number): void;
27
+ getSnapshot(): import("..").ItemSize[];
28
+ getTotalSize(): number;
29
+ }
30
+ export {};
31
+ //# sourceMappingURL=ListController.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ListController.d.ts","sourceRoot":"","sources":["../../src/core/ListController.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,qBAAqB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,KAAK,QAAQ,GAAG,MAAM,IAAI,CAAC;AAE3B;;;;;GAKG;AACH,qBAAa,cAAc;IACvB,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,SAAS,CAAuB;gBAE5B,EAAE,SAAS,EAAE,iBAAiB,EAAE,QAAY,EAAE,EAAE,qBAAqB;IAOjF,SAAS,GAAI,UAAU,QAAQ,mBAG7B;IAEF,OAAO,CAAC,MAAM;IAEd,eAAe,CAAC,MAAM,EAAE,MAAM;IAC9B,gBAAgB,CAAC,IAAI,EAAE,MAAM;IAC7B,YAAY,CAAC,KAAK,EAAE,MAAM;IAC1B,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;IAE1C,WAAW;IAIX,YAAY;CACf"}
@@ -0,0 +1,71 @@
1
+ // packages/core/src/ListController.ts
2
+ import { VirtualizationEngine } from './VirtualizationEngine';
3
+ /**
4
+ * Framework-agnostic controller: owns the engine + scroll state,
5
+ * exposes subscribe/getSnapshot. React binds via useSyncExternalStore;
6
+ * Vue could bind via a reactive ref; Svelte via a store - the engine
7
+ * itself never needs to know any of that exists.
8
+ */
9
+ export class ListController {
10
+ constructor({ itemCount, estimatedItemSize, overscan = 5 }) {
11
+ Object.defineProperty(this, "engine", {
12
+ enumerable: true,
13
+ configurable: true,
14
+ writable: true,
15
+ value: void 0
16
+ });
17
+ Object.defineProperty(this, "scrollOffset", {
18
+ enumerable: true,
19
+ configurable: true,
20
+ writable: true,
21
+ value: 0
22
+ });
23
+ Object.defineProperty(this, "containerSize", {
24
+ enumerable: true,
25
+ configurable: true,
26
+ writable: true,
27
+ value: 0
28
+ });
29
+ Object.defineProperty(this, "itemCount", {
30
+ enumerable: true,
31
+ configurable: true,
32
+ writable: true,
33
+ value: void 0
34
+ });
35
+ Object.defineProperty(this, "overscan", {
36
+ enumerable: true,
37
+ configurable: true,
38
+ writable: true,
39
+ value: void 0
40
+ });
41
+ Object.defineProperty(this, "listeners", {
42
+ enumerable: true,
43
+ configurable: true,
44
+ writable: true,
45
+ value: new Set()
46
+ });
47
+ Object.defineProperty(this, "subscribe", {
48
+ enumerable: true,
49
+ configurable: true,
50
+ writable: true,
51
+ value: (listener) => {
52
+ this.listeners.add(listener);
53
+ return () => this.listeners.delete(listener);
54
+ }
55
+ });
56
+ this.engine = new VirtualizationEngine(estimatedItemSize);
57
+ this.itemCount = itemCount;
58
+ this.overscan = overscan;
59
+ this.engine.setItemCount(itemCount);
60
+ }
61
+ notify() { this.listeners.forEach((l) => l()); }
62
+ setScrollOffset(offset) { this.scrollOffset = offset; this.notify(); }
63
+ setContainerSize(size) { this.containerSize = size; this.notify(); }
64
+ setItemCount(count) { this.itemCount = count; this.engine.setItemCount(count); this.notify(); }
65
+ recordItemSize(index, size) { this.engine.recordItemSize(index, size); this.notify(); }
66
+ getSnapshot() {
67
+ return this.engine.getVisibleItems(this.scrollOffset, this.containerSize, this.itemCount, this.overscan);
68
+ }
69
+ getTotalSize() { return this.engine.getTotalSize(); }
70
+ }
71
+ //# sourceMappingURL=ListController.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ListController.js","sourceRoot":"","sources":["../../src/core/ListController.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAU9D;;;;;GAKG;AACH,MAAM,OAAO,cAAc;IAQvB,YAAY,EAAE,SAAS,EAAE,iBAAiB,EAAE,QAAQ,GAAG,CAAC,EAAyB;QAPzE;;;;;WAA6B;QAC7B;;;;mBAAe,CAAC;WAAC;QACjB;;;;mBAAgB,CAAC;WAAC;QAClB;;;;;WAAkB;QAClB;;;;;WAAiB;QACjB;;;;mBAAY,IAAI,GAAG,EAAY;WAAC;QASxC;;;;mBAAY,CAAC,QAAkB,EAAE,EAAE;gBAC/B,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;gBAC7B,OAAO,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YACjD,CAAC;WAAC;QATE,IAAI,CAAC,MAAM,GAAG,IAAI,oBAAoB,CAAC,iBAAiB,CAAC,CAAC;QAC1D,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC;IACxC,CAAC;IAOO,MAAM,KAAK,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAExD,eAAe,CAAC,MAAc,IAAI,IAAI,CAAC,YAAY,GAAG,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAC9E,gBAAgB,CAAC,IAAY,IAAI,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAC5E,YAAY,CAAC,KAAa,IAAI,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IACvG,cAAc,CAAC,KAAa,EAAE,IAAY,IAAI,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAEvG,WAAW;QACP,OAAO,IAAI,CAAC,MAAM,CAAC,eAAe,CAAC,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC7G,CAAC;IAED,YAAY,KAAK,OAAO,IAAI,CAAC,MAAM,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC;CACxD"}
@@ -1,30 +1,41 @@
1
1
  import { ItemSize, VirtualRange } from '../types';
2
+ /**
3
+ * Core offset/range math shared by VirtualList and VirtualGrid.
4
+ *
5
+ * PERFORMANCE: previously this held a plain prefix-sum array and a
6
+ * `dirty` flag, rebuilding the WHOLE array (O(n)) the next time
7
+ * anything was read after any single item's size changed. Every read
8
+ * (getItemOffset, getTotalSize, the binary search for visible range)
9
+ * paid that O(n) cost whenever a measurement had landed since the last
10
+ * read - which, for a dynamic-height list, is essentially every scroll
11
+ * frame.
12
+ *
13
+ * This version is backed by a FenwickTree (see FenwickTree.ts). There
14
+ * is no dirty flag and no rebuild: every operation below is O(log n),
15
+ * full stop, regardless of how many items have been re-measured.
16
+ */
2
17
  export declare class VirtualizationEngine {
3
- private itemSizeMap;
18
+ private tree;
4
19
  private estimatedItemSize;
5
20
  private itemCount;
6
- private prefix;
7
- private dirty;
8
21
  constructor(estimatedItemSize?: number);
9
22
  setItemCount(count: number): void;
10
23
  recordItemSize(index: number, size: number): void;
11
24
  /**
12
- * NEW (additive): Invalidate cached measurements from `fromIndex`
13
- * onward (inclusive), or all measurements if `fromIndex` is
14
- * omitted. Affected indices fall back to `estimatedItemSize`
15
- * until they are re-measured and recordItemSize() is called
16
- * again for them.
25
+ * Invalidate cached measurements from `fromIndex` onward
26
+ * (inclusive), or everything if omitted. Affected indices fall
27
+ * back to `estimatedItemSize` until re-measured.
17
28
  *
18
- * Does not remove items below fromIndex, and does not touch
19
- * itemCount. Safe to call at any time; it only marks the
20
- * prefix-sum cache dirty so the next read rebuilds it.
29
+ * Each reset index is a single O(log n) point update - so
30
+ * invalidating a suffix of k items is O(k log n), not a full
31
+ * array rebuild. A full invalidate (no fromIndex) is the one
32
+ * intentionally O(n) path in this class, reserved for "the whole
33
+ * dataset changed" situations, not the scroll hot path.
21
34
  */
22
35
  invalidateFrom(fromIndex?: number): void;
23
- private rebuildPrefix;
24
36
  getItemSize(index: number): number;
25
37
  getItemOffset(index: number): number;
26
38
  getTotalSize(): number;
27
- private binarySearch;
28
39
  getVisibleRange(scrollOffset: number, containerSize: number, itemCount: number, overscan?: number): VirtualRange;
29
40
  getVisibleItems(scrollOffset: number, containerSize: number, itemCount: number, overscan: number): ItemSize[];
30
41
  getScrollOffsetForIndex(index: number, containerSize: number, alignment?: 'start' | 'center' | 'end'): number;
@@ -1 +1 @@
1
- {"version":3,"file":"VirtualizationEngine.d.ts","sourceRoot":"","sources":["../../src/core/VirtualizationEngine.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,QAAQ,EACR,YAAY,EAEf,MAAM,UAAU,CAAC;AAElB,qBAAa,oBAAoB;IAC7B,OAAO,CAAC,WAAW,CAAwB;IAC3C,OAAO,CAAC,iBAAiB,CAAS;IAClC,OAAO,CAAC,SAAS,CAAK;IAGtB,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,KAAK,CAAQ;gBAET,iBAAiB,SAAK;IAIlC,YAAY,CAAC,KAAK,EAAE,MAAM;IAqB1B,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;IAO1C;;;;;;;;;;OAUG;IACH,cAAc,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI;IAqBxC,OAAO,CAAC,aAAa;IAYrB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAIlC,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAKpC,YAAY,IAAI,MAAM;IAKtB,OAAO,CAAC,YAAY;IAkBpB,eAAe,CACX,YAAY,EAAE,MAAM,EACpB,aAAa,EAAE,MAAM,EACrB,SAAS,EAAE,MAAM,EACjB,QAAQ,SAAI,GACb,YAAY;IAcf,eAAe,CACX,YAAY,EAAE,MAAM,EACpB,aAAa,EAAE,MAAM,EACrB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,GACjB,QAAQ,EAAE;IAqBb,uBAAuB,CAAC,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,SAAS,GAAE,OAAO,GAAG,QAAQ,GAAG,KAAe;CAchH"}
1
+ {"version":3,"file":"VirtualizationEngine.d.ts","sourceRoot":"","sources":["../../src/core/VirtualizationEngine.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,QAAQ,EACR,YAAY,EACf,MAAM,UAAU,CAAC;AAGlB;;;;;;;;;;;;;;GAcG;AACH,qBAAa,oBAAoB;IAC7B,OAAO,CAAC,IAAI,CAAc;IAC1B,OAAO,CAAC,iBAAiB,CAAS;IAClC,OAAO,CAAC,SAAS,CAAK;gBAEV,iBAAiB,SAAK;IAKlC,YAAY,CAAC,KAAK,EAAE,MAAM;IAM1B,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;IAK1C;;;;;;;;;;OAUG;IACH,cAAc,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI;IAYxC,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAKlC,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAIpC,YAAY,IAAI,MAAM;IAItB,eAAe,CACX,YAAY,EAAE,MAAM,EACpB,aAAa,EAAE,MAAM,EACrB,SAAS,EAAE,MAAM,EACjB,QAAQ,SAAI,GACb,YAAY;IAuBf,eAAe,CACX,YAAY,EAAE,MAAM,EACpB,aAAa,EAAE,MAAM,EACrB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,GACjB,QAAQ,EAAE;IAqBb,uBAAuB,CACnB,KAAK,EAAE,MAAM,EACb,aAAa,EAAE,MAAM,EACrB,SAAS,GAAE,OAAO,GAAG,QAAQ,GAAG,KAAe;CAetD"}
@@ -1,10 +1,26 @@
1
+ import { FenwickTree } from './FenwickTree';
2
+ /**
3
+ * Core offset/range math shared by VirtualList and VirtualGrid.
4
+ *
5
+ * PERFORMANCE: previously this held a plain prefix-sum array and a
6
+ * `dirty` flag, rebuilding the WHOLE array (O(n)) the next time
7
+ * anything was read after any single item's size changed. Every read
8
+ * (getItemOffset, getTotalSize, the binary search for visible range)
9
+ * paid that O(n) cost whenever a measurement had landed since the last
10
+ * read - which, for a dynamic-height list, is essentially every scroll
11
+ * frame.
12
+ *
13
+ * This version is backed by a FenwickTree (see FenwickTree.ts). There
14
+ * is no dirty flag and no rebuild: every operation below is O(log n),
15
+ * full stop, regardless of how many items have been re-measured.
16
+ */
1
17
  export class VirtualizationEngine {
2
18
  constructor(estimatedItemSize = 35) {
3
- Object.defineProperty(this, "itemSizeMap", {
19
+ Object.defineProperty(this, "tree", {
4
20
  enumerable: true,
5
21
  configurable: true,
6
22
  writable: true,
7
- value: {}
23
+ value: void 0
8
24
  });
9
25
  Object.defineProperty(this, "estimatedItemSize", {
10
26
  enumerable: true,
@@ -18,117 +34,64 @@ export class VirtualizationEngine {
18
34
  writable: true,
19
35
  value: 0
20
36
  });
21
- // prefix sum cache
22
- Object.defineProperty(this, "prefix", {
23
- enumerable: true,
24
- configurable: true,
25
- writable: true,
26
- value: [0]
27
- });
28
- Object.defineProperty(this, "dirty", {
29
- enumerable: true,
30
- configurable: true,
31
- writable: true,
32
- value: true
33
- });
34
37
  this.estimatedItemSize = estimatedItemSize;
38
+ this.tree = new FenwickTree(0);
35
39
  }
36
40
  setItemCount(count) {
37
- /**
38
- * FIX: previously this unconditionally set `dirty = true`
39
- * on every call, even when the count hadn't changed. Since
40
- * getVisibleRange() calls setItemCount() on every single
41
- * invocation (i.e. every scroll frame), that forced a full
42
- * O(n) prefix-sum rebuild every frame regardless of whether
43
- * anything actually changed.
44
- *
45
- * Now we only mark dirty when the count actually differs.
46
- * Measurement updates still mark dirty via recordItemSize(),
47
- * so cache invalidation on real changes is unaffected.
48
- */
49
- if (this.itemCount === count) {
41
+ if (this.itemCount === count)
50
42
  return;
51
- }
43
+ this.tree.resize(count, this.estimatedItemSize);
52
44
  this.itemCount = count;
53
- this.dirty = true;
54
45
  }
55
46
  recordItemSize(index, size) {
56
- if (this.itemSizeMap[index] === size)
47
+ if (index < 0 || index >= this.itemCount)
57
48
  return;
58
- this.itemSizeMap[index] = size;
59
- this.dirty = true;
49
+ this.tree.update(index, size);
60
50
  }
61
51
  /**
62
- * NEW (additive): Invalidate cached measurements from `fromIndex`
63
- * onward (inclusive), or all measurements if `fromIndex` is
64
- * omitted. Affected indices fall back to `estimatedItemSize`
65
- * until they are re-measured and recordItemSize() is called
66
- * again for them.
52
+ * Invalidate cached measurements from `fromIndex` onward
53
+ * (inclusive), or everything if omitted. Affected indices fall
54
+ * back to `estimatedItemSize` until re-measured.
67
55
  *
68
- * Does not remove items below fromIndex, and does not touch
69
- * itemCount. Safe to call at any time; it only marks the
70
- * prefix-sum cache dirty so the next read rebuilds it.
56
+ * Each reset index is a single O(log n) point update - so
57
+ * invalidating a suffix of k items is O(k log n), not a full
58
+ * array rebuild. A full invalidate (no fromIndex) is the one
59
+ * intentionally O(n) path in this class, reserved for "the whole
60
+ * dataset changed" situations, not the scroll hot path.
71
61
  */
72
62
  invalidateFrom(fromIndex) {
73
63
  if (fromIndex === undefined) {
74
- this.itemSizeMap = {};
75
- this.dirty = true;
64
+ this.tree.resetAll(this.estimatedItemSize);
76
65
  return;
77
66
  }
78
- if (fromIndex < 0) {
79
- fromIndex = 0;
80
- }
81
- for (const key of Object.keys(this.itemSizeMap)) {
82
- const index = Number(key);
83
- if (index >= fromIndex) {
84
- delete this.itemSizeMap[index];
85
- }
86
- }
87
- this.dirty = true;
88
- }
89
- rebuildPrefix() {
90
- let sum = 0;
91
- this.prefix = [0];
92
- for (let i = 0; i < this.itemCount; i++) {
93
- sum += this.itemSizeMap[i] ?? this.estimatedItemSize;
94
- this.prefix[i + 1] = sum;
67
+ const start = Math.max(0, fromIndex);
68
+ for (let i = start; i < this.itemCount; i++) {
69
+ this.tree.update(i, this.estimatedItemSize);
95
70
  }
96
- this.dirty = false;
97
71
  }
98
72
  getItemSize(index) {
99
- return this.itemSizeMap[index] ?? this.estimatedItemSize;
73
+ if (index < 0 || index >= this.itemCount)
74
+ return this.estimatedItemSize;
75
+ return this.tree.getValue(index);
100
76
  }
101
77
  getItemOffset(index) {
102
- if (this.dirty)
103
- this.rebuildPrefix();
104
- return this.prefix[index] ?? 0;
78
+ return this.tree.prefixSum(Math.max(0, index));
105
79
  }
106
80
  getTotalSize() {
107
- if (this.dirty)
108
- this.rebuildPrefix();
109
- return this.prefix[this.itemCount] || 0;
110
- }
111
- binarySearch(offset) {
112
- if (this.dirty)
113
- this.rebuildPrefix();
114
- let low = 0;
115
- let high = this.itemCount - 1;
116
- while (low <= high) {
117
- const mid = (low + high) >> 1;
118
- const value = this.prefix[mid];
119
- if (value === offset)
120
- return mid;
121
- if (value < offset)
122
- low = mid + 1;
123
- else
124
- high = mid - 1;
125
- }
126
- return Math.max(0, low - 1);
81
+ return this.tree.total();
127
82
  }
128
83
  getVisibleRange(scrollOffset, containerSize, itemCount, overscan = 5) {
129
84
  this.setItemCount(itemCount);
130
- const start = this.binarySearch(scrollOffset);
131
- const end = this.binarySearch(scrollOffset + containerSize);
85
+ if (itemCount === 0) {
86
+ return {
87
+ startIndex: 0,
88
+ endIndex: -1,
89
+ overscanStartIndex: 0,
90
+ overscanEndIndex: -1,
91
+ };
92
+ }
93
+ const start = this.tree.findIndexAtOffset(scrollOffset);
94
+ const end = this.tree.findIndexAtOffset(scrollOffset + containerSize);
132
95
  return {
133
96
  startIndex: Math.max(0, start - overscan),
134
97
  endIndex: Math.min(itemCount - 1, end + overscan),
@@ -1 +1 @@
1
- {"version":3,"file":"VirtualizationEngine.js","sourceRoot":"","sources":["../../src/core/VirtualizationEngine.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"VirtualizationEngine.js","sourceRoot":"","sources":["../../src/core/VirtualizationEngine.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export default function DynamicHeightListExample(): import("react/jsx-runtime").JSX.Element;
2
+ //# sourceMappingURL=DynamicHeightLists.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DynamicHeightLists.d.ts","sourceRoot":"","sources":["../../src/examples/DynamicHeightLists.tsx"],"names":[],"mappings":"AAqDA,MAAM,CAAC,OAAO,UAAU,wBAAwB,4CAyD/C"}
@@ -0,0 +1,42 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React from "react";
3
+ import { VirtualList } from "../components/VirtualList";
4
+ async function fetchRealComments(signal) {
5
+ const res = await fetch("https://jsonplaceholder.typicode.com/comments", { signal });
6
+ if (!res.ok)
7
+ throw new Error(`Failed to fetch comments: ${res.status}`);
8
+ return res.json();
9
+ }
10
+ function tileToTargetSize(source, targetCount) {
11
+ return Array.from({ length: targetCount }, (_, i) => {
12
+ const src = source[i % source.length];
13
+ return { id: i, author: src.email, body: src.body };
14
+ });
15
+ }
16
+ const TARGET_COUNT = 50000;
17
+ export default function DynamicHeightListExample() {
18
+ const [comments, setComments] = React.useState(null);
19
+ const [error, setError] = React.useState(null);
20
+ const listRef = React.useRef(null);
21
+ React.useEffect(() => {
22
+ const controller = new AbortController();
23
+ fetchRealComments(controller.signal)
24
+ .then((raw) => setComments(tileToTargetSize(raw, TARGET_COUNT)))
25
+ .catch((err) => {
26
+ if (!controller.signal.aborted) {
27
+ setError(err instanceof Error ? err.message : "Failed to load comments");
28
+ }
29
+ });
30
+ return () => controller.abort();
31
+ }, []);
32
+ if (error) {
33
+ return _jsx("div", { style: { padding: 16, color: "#b91c1c" }, children: error });
34
+ }
35
+ if (!comments) {
36
+ return _jsx("div", { style: { padding: 16, color: "#888" }, children: "Loading real comment data\u2026" });
37
+ }
38
+ return (_jsxs("div", { children: [_jsx("div", { style: { marginBottom: 8 }, children: _jsx("button", { onClick: () => {
39
+ listRef.current?.scrollTo({ top: 500000, behavior: "smooth" });
40
+ }, children: "Jump deep into the list (tests offset math on unmeasured items)" }) }), _jsx(VirtualList, { ref: listRef, items: comments, height: 600, width: "100%", estimatedItemSize: 90, overscan: 6, itemKey: (_, item) => item.id, itemStyle: { borderBottom: "1px solid #eee", padding: "8px 12px" }, renderItem: ({ item }) => (_jsxs("div", { children: [_jsx("strong", { children: item.author }), _jsx("p", { style: { margin: "4px 0 0" }, children: item.body })] })) })] }));
41
+ }
42
+ //# sourceMappingURL=DynamicHeightLists.js.map