react-virtual-renderer 1.1.2 → 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 (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 +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/MeasurementSystem.d.ts +54 -6
  33. package/dist/core/MeasurementSystem.d.ts.map +1 -1
  34. package/dist/core/MeasurementSystem.js +113 -9
  35. package/dist/core/MeasurementSystem.js.map +1 -1
  36. package/dist/core/VirtualizationEngine.d.ts +28 -5
  37. package/dist/core/VirtualizationEngine.d.ts.map +1 -1
  38. package/dist/core/VirtualizationEngine.js +58 -52
  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 +37 -14
  69. package/dist/hooks/useInfiniteScroll.d.ts.map +1 -1
  70. package/dist/hooks/useInfiniteScroll.js +48 -38
  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 +82 -4
  81. package/dist/hooks/useVirtualList.d.ts.map +1 -1
  82. package/dist/hooks/useVirtualList.js +220 -92
  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,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,19 +1,54 @@
1
1
  export declare class MeasurementSystem {
2
2
  private resizeObserver;
3
3
  private measurements;
4
+ /**
5
+ * NEW (additive): separate cache for width measurements, used
6
+ * only when an element is observed in 'horizontal' axis mode.
7
+ * Existing height-only consumers never populate or read this.
8
+ */
9
+ private widthMeasurements;
4
10
  private observedElements;
11
+ /**
12
+ * NEW (additive): tracks which axis each observed element
13
+ * should report on. Defaults to 'height' to preserve existing
14
+ * behavior for every caller that doesn't pass an axis.
15
+ */
16
+ private observedAxis;
5
17
  private pendingUpdates;
18
+ /**
19
+ * NEW (additive): pending width updates, flushed alongside
20
+ * height updates on the same RAF batch.
21
+ */
22
+ private pendingWidthUpdates;
6
23
  private rafId;
7
24
  private onMeasure;
25
+ /**
26
+ * NEW (additive): optional width callback. If not provided,
27
+ * width measurements are still tracked internally (available
28
+ * via getWidthMeasurement) but no callback fires - existing
29
+ * callers that only pass onMeasure are unaffected.
30
+ */
31
+ private onMeasureWidth;
8
32
  constructor();
9
33
  /**
10
- * Start measuring container
34
+ * Start measuring container.
35
+ *
36
+ * `onMeasureWidth` is optional and additive: existing callers
37
+ * that only pass `onMeasure` keep working exactly as before
38
+ * (height-only). Pass `onMeasureWidth` to also receive width
39
+ * updates for elements observed in 'width' axis mode.
11
40
  */
12
- startMeasuring(container: HTMLElement, onMeasure: (index: number, size: number) => void): void;
41
+ startMeasuring(container: HTMLElement, onMeasure: (index: number, size: number) => void, onMeasureWidth?: (index: number, size: number) => void): void;
13
42
  /**
14
- * Observe item
43
+ * Observe item.
44
+ *
45
+ * `axis` is optional and additive, defaulting to 'height' so
46
+ * every existing call site (which never passes a third arg)
47
+ * behaves exactly as before. Pass axis='width' for elements in
48
+ * a horizontal-scrolling list so contentRect.width is measured
49
+ * instead of contentRect.height.
15
50
  */
16
- observeItem(index: number, element: HTMLElement): void;
51
+ observeItem(index: number, element: HTMLElement, axis?: 'height' | 'width'): void;
17
52
  /**
18
53
  * Unobserve item
19
54
  */
@@ -27,15 +62,28 @@ export declare class MeasurementSystem {
27
62
  */
28
63
  private flushPendingUpdates;
29
64
  /**
30
- * Fallback measurement
65
+ * Fallback measurement.
66
+ *
67
+ * `axis` is optional and additive, defaulting to 'height' to
68
+ * preserve existing behavior for all current call sites.
31
69
  */
32
- syncMeasure(index: number, element: HTMLElement): void;
70
+ syncMeasure(index: number, element: HTMLElement, axis?: 'height' | 'width'): void;
33
71
  /**
34
72
  * Preserve measurements
35
73
  */
36
74
  stopMeasuring(): void;
37
75
  getMeasurement(index: number): number | undefined;
76
+ /**
77
+ * NEW (additive): mirrors getMeasurement() but for the width
78
+ * cache. Returns undefined for any index never observed in
79
+ * 'width' axis mode.
80
+ */
81
+ getWidthMeasurement(index: number): number | undefined;
38
82
  getAllMeasurements(): Map<number, number>;
83
+ /**
84
+ * NEW (additive): mirrors getAllMeasurements() for widths.
85
+ */
86
+ getAllWidthMeasurements(): Map<number, number>;
39
87
  clearPending(): void;
40
88
  /**
41
89
  * Explicit cache clear
@@ -1 +1 @@
1
- {"version":3,"file":"MeasurementSystem.d.ts","sourceRoot":"","sources":["../../src/core/MeasurementSystem.ts"],"names":[],"mappings":"AAAA,qBAAa,iBAAiB;IAC1B,OAAO,CAAC,cAAc,CAA+B;IAErD,OAAO,CAAC,YAAY,CAA6B;IAEjD,OAAO,CAAC,gBAAgB,CACe;IAEvC,OAAO,CAAC,cAAc,CACQ;IAE9B,OAAO,CAAC,KAAK,CAAuB;IAEpC,OAAO,CAAC,SAAS,CAEC;;IAclB;;OAEG;IACH,cAAc,CACV,SAAS,EAAE,WAAW,EACtB,SAAS,EAAE,CACP,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,KACX,IAAI,GACV,IAAI;IA4BP;;OAEG;IACH,WAAW,CACP,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,WAAW,GACrB,IAAI;IAmBP;;OAEG;IACH,aAAa,CACT,OAAO,EAAE,WAAW,GACrB,IAAI;IAUP;;OAEG;IACH,OAAO,CAAC,YAAY,CAwClB;IAEF;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAsB3B;;OAEG;IACH,WAAW,CACP,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,WAAW,GACrB,IAAI;IA+BP;;OAEG;IACH,aAAa,IAAI,IAAI;IAYrB,cAAc,CACV,KAAK,EAAE,MAAM,GACd,MAAM,GAAG,SAAS;IAMrB,kBAAkB,IAAI,GAAG,CACrB,MAAM,EACN,MAAM,CACT;IAMD,YAAY,IAAI,IAAI;IAIpB;;OAEG;IACH,iBAAiB,IAAI,IAAI;IAIzB,OAAO,IAAI,IAAI;CAWlB"}
1
+ {"version":3,"file":"MeasurementSystem.d.ts","sourceRoot":"","sources":["../../src/core/MeasurementSystem.ts"],"names":[],"mappings":"AAAA,qBAAa,iBAAiB;IAC1B,OAAO,CAAC,cAAc,CAA+B;IAErD,OAAO,CAAC,YAAY,CAA6B;IAEjD;;;;OAIG;IACH,OAAO,CAAC,iBAAiB,CAA6B;IAEtD,OAAO,CAAC,gBAAgB,CACe;IAEvC;;;;OAIG;IACH,OAAO,CAAC,YAAY,CAC+B;IAEnD,OAAO,CAAC,cAAc,CACQ;IAE9B;;;OAGG;IACH,OAAO,CAAC,mBAAmB,CACG;IAE9B,OAAO,CAAC,KAAK,CAAuB;IAEpC,OAAO,CAAC,SAAS,CAEC;IAElB;;;;;OAKG;IACH,OAAO,CAAC,cAAc,CAEJ;;IAclB;;;;;;;OAOG;IACH,cAAc,CACV,SAAS,EAAE,WAAW,EACtB,SAAS,EAAE,CACP,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,KACX,IAAI,EACT,cAAc,CAAC,EAAE,CACb,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,KACX,IAAI,GACV,IAAI;IA6BP;;;;;;;;OAQG;IACH,WAAW,CACP,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,WAAW,EACpB,IAAI,GAAE,QAAQ,GAAG,OAAkB,GACpC,IAAI;IAsBP;;OAEG;IACH,aAAa,CACT,OAAO,EAAE,WAAW,GACrB,IAAI;IAYP;;OAEG;IACH,OAAO,CAAC,YAAY,CAkElB;IAEF;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAiC3B;;;;;OAKG;IACH,WAAW,CACP,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,WAAW,EACpB,IAAI,GAAE,QAAQ,GAAG,OAAkB,GACpC,IAAI;IAwCP;;OAEG;IACH,aAAa,IAAI,IAAI;IAarB,cAAc,CACV,KAAK,EAAE,MAAM,GACd,MAAM,GAAG,SAAS;IAMrB;;;;OAIG;IACH,mBAAmB,CACf,KAAK,EAAE,MAAM,GACd,MAAM,GAAG,SAAS;IAIrB,kBAAkB,IAAI,GAAG,CACrB,MAAM,EACN,MAAM,CACT;IAMD;;OAEG;IACH,uBAAuB,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC;IAI9C,YAAY,IAAI,IAAI;IAKpB;;OAEG;IACH,iBAAiB,IAAI,IAAI;IAKzB,OAAO,IAAI,IAAI;CAgBlB"}
@@ -12,18 +12,50 @@ export class MeasurementSystem {
12
12
  writable: true,
13
13
  value: new Map()
14
14
  });
15
+ /**
16
+ * NEW (additive): separate cache for width measurements, used
17
+ * only when an element is observed in 'horizontal' axis mode.
18
+ * Existing height-only consumers never populate or read this.
19
+ */
20
+ Object.defineProperty(this, "widthMeasurements", {
21
+ enumerable: true,
22
+ configurable: true,
23
+ writable: true,
24
+ value: new Map()
25
+ });
15
26
  Object.defineProperty(this, "observedElements", {
16
27
  enumerable: true,
17
28
  configurable: true,
18
29
  writable: true,
19
30
  value: new WeakMap()
20
31
  });
32
+ /**
33
+ * NEW (additive): tracks which axis each observed element
34
+ * should report on. Defaults to 'height' to preserve existing
35
+ * behavior for every caller that doesn't pass an axis.
36
+ */
37
+ Object.defineProperty(this, "observedAxis", {
38
+ enumerable: true,
39
+ configurable: true,
40
+ writable: true,
41
+ value: new WeakMap()
42
+ });
21
43
  Object.defineProperty(this, "pendingUpdates", {
22
44
  enumerable: true,
23
45
  configurable: true,
24
46
  writable: true,
25
47
  value: new Map()
26
48
  });
49
+ /**
50
+ * NEW (additive): pending width updates, flushed alongside
51
+ * height updates on the same RAF batch.
52
+ */
53
+ Object.defineProperty(this, "pendingWidthUpdates", {
54
+ enumerable: true,
55
+ configurable: true,
56
+ writable: true,
57
+ value: new Map()
58
+ });
27
59
  Object.defineProperty(this, "rafId", {
28
60
  enumerable: true,
29
61
  configurable: true,
@@ -36,6 +68,18 @@ export class MeasurementSystem {
36
68
  writable: true,
37
69
  value: null
38
70
  });
71
+ /**
72
+ * NEW (additive): optional width callback. If not provided,
73
+ * width measurements are still tracked internally (available
74
+ * via getWidthMeasurement) but no callback fires - existing
75
+ * callers that only pass onMeasure are unaffected.
76
+ */
77
+ Object.defineProperty(this, "onMeasureWidth", {
78
+ enumerable: true,
79
+ configurable: true,
80
+ writable: true,
81
+ value: null
82
+ });
39
83
  /**
40
84
  * ResizeObserver callback
41
85
  */
@@ -50,6 +94,16 @@ export class MeasurementSystem {
50
94
  if (index === undefined) {
51
95
  continue;
52
96
  }
97
+ const axis = this.observedAxis.get(element) ?? 'height';
98
+ if (axis === 'width') {
99
+ const widthSize = Math.round(entry.contentRect.width);
100
+ if (this.widthMeasurements.get(index) !==
101
+ widthSize) {
102
+ this.widthMeasurements.set(index, widthSize);
103
+ this.pendingWidthUpdates.set(index, widthSize);
104
+ }
105
+ continue;
106
+ }
53
107
  const size = Math.round(entry.contentRect.height);
54
108
  if (this.measurements.get(index) === size) {
55
109
  continue;
@@ -67,10 +121,16 @@ export class MeasurementSystem {
67
121
  }
68
122
  }
69
123
  /**
70
- * Start measuring container
124
+ * Start measuring container.
125
+ *
126
+ * `onMeasureWidth` is optional and additive: existing callers
127
+ * that only pass `onMeasure` keep working exactly as before
128
+ * (height-only). Pass `onMeasureWidth` to also receive width
129
+ * updates for elements observed in 'width' axis mode.
71
130
  */
72
- startMeasuring(container, onMeasure) {
131
+ startMeasuring(container, onMeasure, onMeasureWidth) {
73
132
  this.onMeasure = onMeasure;
133
+ this.onMeasureWidth = onMeasureWidth ?? null;
74
134
  const items = container.querySelectorAll('[data-virtualize-index]');
75
135
  items.forEach((item) => {
76
136
  const indexAttr = item.dataset.virtualizeIndex;
@@ -82,14 +142,21 @@ export class MeasurementSystem {
82
142
  });
83
143
  }
84
144
  /**
85
- * Observe item
145
+ * Observe item.
146
+ *
147
+ * `axis` is optional and additive, defaulting to 'height' so
148
+ * every existing call site (which never passes a third arg)
149
+ * behaves exactly as before. Pass axis='width' for elements in
150
+ * a horizontal-scrolling list so contentRect.width is measured
151
+ * instead of contentRect.height.
86
152
  */
87
- observeItem(index, element) {
153
+ observeItem(index, element, axis = 'height') {
88
154
  element.dataset.virtualizeIndex =
89
155
  String(index);
90
156
  this.observedElements.set(element, index);
157
+ this.observedAxis.set(element, axis);
91
158
  this.resizeObserver?.observe(element);
92
- this.syncMeasure(index, element);
159
+ this.syncMeasure(index, element, axis);
93
160
  }
94
161
  /**
95
162
  * Unobserve item
@@ -97,6 +164,7 @@ export class MeasurementSystem {
97
164
  unobserveItem(element) {
98
165
  this.resizeObserver?.unobserve(element);
99
166
  this.observedElements.delete(element);
167
+ this.observedAxis.delete(element);
100
168
  }
101
169
  /**
102
170
  * Batch updates using RAF
@@ -111,19 +179,34 @@ export class MeasurementSystem {
111
179
  this.onMeasure?.(index, size);
112
180
  });
113
181
  this.pendingUpdates.clear();
182
+ this.pendingWidthUpdates.forEach((size, index) => {
183
+ this.onMeasureWidth?.(index, size);
184
+ });
185
+ this.pendingWidthUpdates.clear();
114
186
  this.rafId = null;
115
187
  });
116
188
  }
117
189
  /**
118
- * Fallback measurement
190
+ * Fallback measurement.
191
+ *
192
+ * `axis` is optional and additive, defaulting to 'height' to
193
+ * preserve existing behavior for all current call sites.
119
194
  */
120
- syncMeasure(index, element) {
121
- const size = element.getBoundingClientRect()
122
- .height;
195
+ syncMeasure(index, element, axis = 'height') {
196
+ const rect = element.getBoundingClientRect();
197
+ const size = axis === 'width' ? rect.width : rect.height;
123
198
  if (size <= 0) {
124
199
  return;
125
200
  }
126
201
  const rounded = Math.round(size);
202
+ if (axis === 'width') {
203
+ if (this.widthMeasurements.get(index) === rounded) {
204
+ return;
205
+ }
206
+ this.widthMeasurements.set(index, rounded);
207
+ this.onMeasureWidth?.(index, rounded);
208
+ return;
209
+ }
127
210
  if (this.measurements.get(index) === rounded) {
128
211
  return;
129
212
  }
@@ -136,6 +219,7 @@ export class MeasurementSystem {
136
219
  stopMeasuring() {
137
220
  this.resizeObserver?.disconnect();
138
221
  this.pendingUpdates.clear();
222
+ this.pendingWidthUpdates.clear();
139
223
  if (this.rafId !== null) {
140
224
  cancelAnimationFrame(this.rafId);
141
225
  }
@@ -143,25 +227,45 @@ export class MeasurementSystem {
143
227
  getMeasurement(index) {
144
228
  return this.measurements.get(index);
145
229
  }
230
+ /**
231
+ * NEW (additive): mirrors getMeasurement() but for the width
232
+ * cache. Returns undefined for any index never observed in
233
+ * 'width' axis mode.
234
+ */
235
+ getWidthMeasurement(index) {
236
+ return this.widthMeasurements.get(index);
237
+ }
146
238
  getAllMeasurements() {
147
239
  return new Map(this.measurements);
148
240
  }
241
+ /**
242
+ * NEW (additive): mirrors getAllMeasurements() for widths.
243
+ */
244
+ getAllWidthMeasurements() {
245
+ return new Map(this.widthMeasurements);
246
+ }
149
247
  clearPending() {
150
248
  this.pendingUpdates.clear();
249
+ this.pendingWidthUpdates.clear();
151
250
  }
152
251
  /**
153
252
  * Explicit cache clear
154
253
  */
155
254
  clearMeasurements() {
156
255
  this.measurements.clear();
256
+ this.widthMeasurements.clear();
157
257
  }
158
258
  destroy() {
159
259
  this.stopMeasuring();
160
260
  this.measurements.clear();
161
261
  this.pendingUpdates.clear();
262
+ this.widthMeasurements.clear();
263
+ this.pendingWidthUpdates.clear();
162
264
  this.observedElements =
163
265
  new WeakMap();
266
+ this.observedAxis = new WeakMap();
164
267
  this.onMeasure = null;
268
+ this.onMeasureWidth = null;
165
269
  }
166
270
  }
167
271
  //# sourceMappingURL=MeasurementSystem.js.map