Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
92c697f
fix(virtual-core): keep lanes aligned after changing lanes with measu…
piecyk Oct 10, 2026
28a2174
fix(virtual-core): don't replay iOS-deferred size corrections after a…
phcorp Oct 11, 2026
84c1502
ci: Version Packages (#1303)
github-actions[bot] Oct 11, 2026
6732e00
test(virtual-core): pin the iOS deferral and index-scroll orderings m…
phcorp Oct 11, 2026
e7a0a92
fix(virtual-core): leave resizes measured before a scroll command's r…
piecyk Oct 12, 2026
7aa34f6
fix(virtual-core): let a user scroll end a non-smooth scrollToIndex b…
piecyk Oct 12, 2026
46f73fb
ci: Version Packages (#1312)
github-actions[bot] Oct 12, 2026
cb510cb
refactor(virtual-core): describe an in-flight scroll command by one p…
piecyk Oct 12, 2026
3800fb3
fix(virtual-core): treat iOS touchcancel like touchend so interrupted…
mrosberghaus Oct 12, 2026
7c20899
ci: Version Packages (#1315)
github-actions[bot] Oct 12, 2026
f211d3a
refactor(virtual-core): simplify the scroll-correction state (no beha…
piecyk Oct 12, 2026
676a098
ci: Version Packages (#1317)
github-actions[bot] Oct 13, 2026
269e8ed
fix(virtual-core): request deferred ResizeObserver frames from the sc…
NotYoojun Oct 13, 2026
4f9636e
ci: Version Packages (#1322)
github-actions[bot] Oct 13, 2026
4a1a4fd
fix(virtual-core): defer iOS scroll corrections only for touch-driven…
piecyk Oct 13, 2026
3c61b6e
fix(virtual-core): hold a scroll correction the element cannot take (…
piecyk Oct 14, 2026
9c762fe
fix(virtual-core): finish the iOS scroll-correction plan and simplify…
piecyk Oct 15, 2026
77cb05e
refactor(virtual-core): extract the iOS touch gesture into IosGesture…
piecyk Oct 15, 2026
16c786a
test(virtual-core): split index.test.ts into topic files (#1328)
piecyk Oct 15, 2026
d2c7911
fix(virtual-core): keep an instant scroll command through a transient…
piecyk Oct 15, 2026
56b2a63
fix(virtual-core): record the committed max as a clamp candidate (#1330)
piecyk Oct 15, 2026
6789f7a
fix(virtual-core): stop an iOS fling for a command issued during it (…
piecyk Oct 15, 2026
bdaf32b
docs(examples): rebuild the React chat example as a ChatGPT-style app…
piecyk Oct 15, 2026
4296ac2
ci: Version Packages (#1323)
github-actions[bot] Oct 15, 2026
05bb4d3
fix(virtual-core): measure the visible scroll viewport (#1326)
adityareddy-dev Oct 16, 2026
28ff2ca
ci: Version Packages (#1333)
github-actions[bot] Oct 16, 2026
887e971
fix(virtual-core): cancel stale anchor work on explicit scroll (#1335)
prayaggaikwad96 Oct 20, 2026
5d51f1d
fix(virtual-core): hold a clamped compensation retry while a touch ow…
MFA-G Oct 22, 2026
c462c61
ci: Version Packages (#1336)
github-actions[bot] Oct 22, 2026
2343004
fix(virtual-core): account for scrollMargin when deciding the viewpor…
KooYS Oct 26, 2026
88b9773
docs(examples): use spacer rows instead of translateY in the table ex…
dgesteves Oct 26, 2026
91c2931
feat(lit-virtual): add setOptions to update virtualizer options after…
itisrmk Oct 26, 2026
18d0835
ci: Version Packages (#1341)
github-actions[bot] Oct 26, 2026
979c1b4
fix(core): avoid observer loop errors when resize renders change rows…
minwookshin Oct 29, 2026
35ad56d
fix(virtual-core): stop a touch fling for a command issued during it …
Kebechet Oct 29, 2026
6dda0c0
fix(svelte-virtual): preserve onChange across partial option updates …
clean99 Oct 29, 2026
51b06cd
ci: Version Packages (#1349)
github-actions[bot] Oct 29, 2026
9d7ff67
fix(virtual-core): keep a full range when the scroll offset is past t…
btriapitsyn Nov 1, 2026
6f6970b
fix(lit-virtual): reattach scroll observers when the host reconnects …
itisrmk Nov 1, 2026
600204a
fix(svelte-virtual): preserve options when resubscribing (#1356)
clean99 Nov 1, 2026
6936d2f
fix(vue-virtual): detach observers when the scroll element becomes nu…
clean99 Nov 1, 2026
0a368c7
ci: Version Packages (#1358)
github-actions[bot] Nov 1, 2026
acaef5e
fix(virtual-core): compute getOffsetForIndex from the current measure…
Kebechet Nov 3, 2026
bd08fe5
fix(virtual-core): restore programmatic scrolling in RTL elements (#1…
Marve10s Nov 3, 2026
11b48e4
ci: Version Packages (#1364)
github-actions[bot] Nov 3, 2026
54a3f7d
fix(virtual-core): keep only the last rows of a list above the viewpo…
oyobbeb Nov 6, 2026
bd52bbf
chore(fork): merge upstream main 54a3f7d3cc32 (RIG-9206)
rigel-mintaka Nov 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/shared-scroller-offscreen-range.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/virtual-core': patch
---

Render only the last rows of a list that is above the viewport in a shared scroll element. The scroll offset is capped at the end of the scroll element, the list's end plus the content after it, rather than at the list's end, so a list that has the element to itself is capped as before.
84 changes: 84 additions & 0 deletions benchmarks/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,89 @@
# @tanstack/virtual-benchmarks

## 0.0.29

### Patch Changes

- Updated dependencies []:
- @tanstack/react-virtual@3.14.26

## 0.0.28

### Patch Changes

- Updated dependencies []:
- @tanstack/react-virtual@3.14.25

## 0.0.27

### Patch Changes

- Updated dependencies []:
- @tanstack/react-virtual@3.14.24

## 0.0.26

### Patch Changes

- Updated dependencies []:
- @tanstack/react-virtual@3.14.23

## 0.0.25

### Patch Changes

- Updated dependencies []:
- @tanstack/react-virtual@3.14.22

## 0.0.24

### Patch Changes

- Updated dependencies []:
- @tanstack/react-virtual@3.14.21

## 0.0.23

### Patch Changes

- Updated dependencies []:
- @tanstack/react-virtual@3.14.20

## 0.0.22

### Patch Changes

- Updated dependencies []:
- @tanstack/react-virtual@3.14.19

## 0.0.21

### Patch Changes

- Updated dependencies []:
- @tanstack/react-virtual@3.14.18

## 0.0.20

### Patch Changes

- Updated dependencies []:
- @tanstack/react-virtual@3.14.17

## 0.0.19

### Patch Changes

- Updated dependencies []:
- @tanstack/react-virtual@3.14.16

## 0.0.18

### Patch Changes

- Updated dependencies []:
- @tanstack/react-virtual@3.14.15

## 0.0.17

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@tanstack/virtual-benchmarks",
"private": true,
"version": "0.0.17",
"version": "0.0.29",
"type": "module",
"scripts": {
"dev": "vite",
Expand Down
14 changes: 11 additions & 3 deletions docs/api/virtualizer.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ The sync parameter indicates whether scrolling is currently in progress. It is `
overscan?: number
```

The number of items to render above and below the visible area. Increasing this number will increase the amount of time it takes to render the virtualizer, but might decrease the likelihood of seeing slow-rendering blank items at the top and bottom of the virtualizer when scrolling. The default value is `1`.
The number of items to render above and below the visible area. Increasing this number will increase the amount of time it takes to render the virtualizer, but might decrease the likelihood of seeing slow-rendering blank items at the top and bottom of the virtualizer when scrolling. The default value is `1`. When `lanes` is greater than `1`, overscan counts whole rows of `lanes` items, so every lane renders the same number of extra items.

### `horizontal`

Expand Down Expand Up @@ -146,7 +146,7 @@ This function is passed the index of each item and should return a unique key fo
rangeExtractor?: (range: Range) => number[]
```

This function receives visible range indexes and should return array of indexes to render. This is useful if you need to add or remove items from the virtualizer manually regardless of the visible range, eg. rendering sticky items, headers, footers, etc. The default range extractor implementation will return the visible range indexes and is exported as `defaultRangeExtractor`.
This function receives visible range indexes and should return array of indexes to render. This is useful if you need to add or remove items from the virtualizer manually regardless of the visible range, eg. rendering sticky items, headers, footers, etc. The default range extractor implementation will return the visible range indexes and is exported as `defaultRangeExtractor`. When `lanes` is greater than `1`, `range.lanes` holds the lane count and `defaultRangeExtractor` applies `overscan` as whole rows; a custom extractor that computes its own indexes should do the same to keep lanes aligned.

### `scrollToFn`

Expand Down Expand Up @@ -177,6 +177,8 @@ observeElementRect: (

An optional function that if provided is called when the scrollElement changes and should implement the initial measurement and continuous monitoring of the scrollElement's `Rect` (an object with `width` and `height`). It's called with the instance (which also gives you access to the scrollElement via `instance.scrollElement`. Built-in implementations are exported as `observeElementRect` and `observeWindowRect` which are automatically configured for you by your framework adapter's exported functions like `useVirtualizer` or `useWindowVirtualizer`.

The built-in `observeElementRect` measures the visible client area (`clientWidth` and `clientHeight`), excluding borders and scrollbars. Custom implementations should report the same viewport dimensions.

### `observeElementOffset`

```tsx
Expand Down Expand Up @@ -592,14 +594,16 @@ virtualizer.shouldAdjustScrollPositionOnItemSizeChange = (
delta,
instance,
) => {
return item.start < instance.getScrollOffset() + instance.scrollAdjustments
return item.start < (instance.scrollOffset ?? 0)
}
```

The callback receives the resized `item`, the size `delta`, and the `instance`; return `true` to apply the scroll adjustment, `false` to skip it.

On iOS WebKit, scroll-position writes are deferred regardless of this callback while a finger is on screen, during momentum-scroll, and during elastic-overscroll bounce. The cumulative delta is flushed in a single write once the scroll settles, preserving iOS's native momentum physics.

On iOS 27 and later a write no longer stops a fling but is overridden by it within a frame, so a `scrollToIndex`, `scrollToOffset` or `scrollToEnd` issued during a fling is written once the fling ends (about 150ms after its last frame) rather than being lost; a drag before then cancels it, a tap does not.

### `isScrolling`

```tsx
Expand All @@ -623,3 +627,7 @@ scrollOffset: number
```

This option represents the current scroll position along the scrolling axis. It is measured in pixels from the starting point of the scrollable area.

It is a position in the list's layout, and it stays consistent with `getOffsetForIndex`, the argument to `scrollToOffset`, and `initialOffset`: restoring a saved value returns to the same layout position. That is the same item only while the items and sizes before it are unchanged; to restore across changes, pair it with `takeSnapshot()` and `initialMeasurementsCache` as described above, or scroll to the item with `scrollToIndex`.

While a scroll correction is held, because a write to the element is impossible at that moment (a touch gesture or its momentum on iOS, or the element rubber-banding past an edge), `getVirtualItems()` and `getTotalSize()` describe what is rendered, shifted by the held amount so the rows on screen do not move, while `scrollOffset` keeps the layout position. The hold ends with a single write once the element can take it. Prefer `getVirtualItems()` positions over `item.start - scrollOffset` for where a row sits in the viewport, since that difference is off by the held amount for the length of the gesture or bounce.
25 changes: 25 additions & 0 deletions docs/framework/lit/lit-virtual.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,28 @@ private windowVirtualizerController = new WindowVirtualizerController<TItemEleme
```

This class stands of window-based `Virtualizer` instance configured to work with an HTML element as the scrollElement.

## Updating options

The controller reads options once, in its constructor. When a reactive property changes, pass the new values to `setOptions` from `willUpdate` (before render). `VirtualizerController` and `WindowVirtualizerController` both implement it.

`setOptions` merges that partial over the options the controller already holds, including its defaults, and keeps the `onChange` wrapper that re-renders the host. Keys set to `undefined` are ignored, so a partial update does not erase an option from an earlier call.

```ts
@property({ type: Number })
count = 1000

private virtualizerController = new VirtualizerController(this, {
getScrollElement: () => this.scrollElementRef.value,
count: this.count,
estimateSize: () => 35,
})

protected willUpdate(changed: PropertyValues<this>) {
if (changed.has('count')) {
this.virtualizerController.setOptions({ count: this.count })
}
}
```

Call `setOptions` on the controller. `getVirtualizer().setOptions(...)` replaces options wholesale, drops the host `onChange` wrapper, and leaves the controller's stored options stale.
2 changes: 1 addition & 1 deletion examples/angular/chat/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"@angular/platform-browser": "^20.2.0",
"@angular/platform-browser-dynamic": "^20.2.0",
"@angular/router": "^20.2.0",
"@tanstack/angular-virtual": "^6.1.0",
"@tanstack/angular-virtual": "^6.1.12",
"rxjs": "^7.8.2",
"tslib": "^2.8.1",
"zone.js": "0.15.1"
Expand Down
2 changes: 1 addition & 1 deletion examples/angular/dynamic/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"@angular/platform-browser-dynamic": "^20.2.0",
"@angular/router": "^20.2.0",
"@faker-js/faker": "^8.4.1",
"@tanstack/angular-virtual": "^6.1.0",
"@tanstack/angular-virtual": "^6.1.12",
"rxjs": "^7.8.2",
"tslib": "^2.8.1",
"zone.js": "0.15.1"
Expand Down
2 changes: 1 addition & 1 deletion examples/angular/fixed/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"@angular/platform-browser": "^20.2.0",
"@angular/platform-browser-dynamic": "^20.2.0",
"@angular/router": "^20.2.0",
"@tanstack/angular-virtual": "^6.1.0",
"@tanstack/angular-virtual": "^6.1.12",
"rxjs": "^7.8.2",
"tslib": "^2.8.1",
"zone.js": "0.15.1"
Expand Down
2 changes: 1 addition & 1 deletion examples/angular/infinite-scroll/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"@angular/platform-browser-dynamic": "^20.2.0",
"@angular/router": "^20.2.0",
"@tanstack/angular-query-experimental": "5.80.7",
"@tanstack/angular-virtual": "^6.1.0",
"@tanstack/angular-virtual": "^6.1.12",
"rxjs": "^7.8.2",
"tslib": "^2.8.1",
"zone.js": "0.15.1"
Expand Down
2 changes: 1 addition & 1 deletion examples/angular/padding/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"@angular/platform-browser": "^20.2.0",
"@angular/platform-browser-dynamic": "^20.2.0",
"@angular/router": "^20.2.0",
"@tanstack/angular-virtual": "^6.1.0",
"@tanstack/angular-virtual": "^6.1.12",
"rxjs": "^7.8.2",
"tslib": "^2.8.1",
"zone.js": "0.15.1"
Expand Down
2 changes: 1 addition & 1 deletion examples/angular/smooth-scroll/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"@angular/platform-browser": "^20.2.0",
"@angular/platform-browser-dynamic": "^20.2.0",
"@angular/router": "^20.2.0",
"@tanstack/angular-virtual": "^6.1.0",
"@tanstack/angular-virtual": "^6.1.12",
"rxjs": "^7.8.2",
"tslib": "^2.8.1",
"zone.js": "0.15.1"
Expand Down
2 changes: 1 addition & 1 deletion examples/angular/sticky/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"@angular/platform-browser-dynamic": "^20.2.0",
"@angular/router": "^20.2.0",
"@faker-js/faker": "^8.4.1",
"@tanstack/angular-virtual": "^6.1.0",
"@tanstack/angular-virtual": "^6.1.12",
"rxjs": "^7.8.2",
"tslib": "^2.8.1",
"zone.js": "0.15.1"
Expand Down
2 changes: 1 addition & 1 deletion examples/angular/table/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"@angular/router": "^20.2.0",
"@faker-js/faker": "^8.4.1",
"@tanstack/angular-table": "8.21.3",
"@tanstack/angular-virtual": "^6.1.0",
"@tanstack/angular-virtual": "^6.1.12",
"rxjs": "^7.8.2",
"tslib": "^2.8.1",
"zone.js": "0.15.1"
Expand Down
Loading
Loading