Skip to content

Commit 5a273bf

Browse files
authored
fix(virtual-core): scroll to last index properly (#1105)
1 parent c29062e commit 5a273bf

File tree

3 files changed

+35
-2
lines changed

3 files changed

+35
-2
lines changed

.changeset/wild-chairs-drive.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@tanstack/virtual-core': patch
3+
---
4+
5+
fix(virtual-core): scroll to last index properly

examples/react/dynamic/src/main.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ function RowVirtualizerDynamic() {
7070
width: 400,
7171
overflowY: 'auto',
7272
contain: 'strict',
73+
overflowAnchor: 'none',
7374
}}
7475
>
7576
<div

packages/virtual-core/src/index.ts

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -972,11 +972,30 @@ export class Virtualizer<
972972
)
973973
}
974974

975+
private getMaxScrollOffset = () => {
976+
if (!this.scrollElement) return 0
977+
978+
if ('scrollHeight' in this.scrollElement) {
979+
// Element
980+
return this.options.horizontal
981+
? this.scrollElement.scrollWidth - this.scrollElement.clientWidth
982+
: this.scrollElement.scrollHeight - this.scrollElement.clientHeight
983+
} else {
984+
// Window
985+
const doc = this.scrollElement.document.documentElement
986+
return this.options.horizontal
987+
? doc.scrollWidth - this.scrollElement.innerWidth
988+
: doc.scrollHeight - this.scrollElement.innerHeight
989+
}
990+
}
991+
975992
getOffsetForAlignment = (
976993
toOffset: number,
977994
align: ScrollAlignment,
978995
itemSize = 0,
979996
) => {
997+
if (!this.scrollElement) return 0
998+
980999
const size = this.getSize()
9811000
const scrollOffset = this.getScrollOffset()
9821001

@@ -992,7 +1011,7 @@ export class Virtualizer<
9921011
toOffset -= size
9931012
}
9941013

995-
const maxOffset = this.getTotalSize() + this.options.scrollMargin - size
1014+
const maxOffset = this.getMaxScrollOffset()
9961015

9971016
return Math.max(Math.min(maxOffset, toOffset), 0)
9981017
}
@@ -1014,10 +1033,18 @@ export class Virtualizer<
10141033
} else if (item.start <= scrollOffset + this.options.scrollPaddingStart) {
10151034
align = 'start'
10161035
} else {
1017-
return [scrollOffset, align] as const
1036+
// Item is already visible, return current position with concrete alignment
1037+
// to avoid infinite retry loop if measurements change
1038+
return [scrollOffset, 'start'] as const
10181039
}
10191040
}
10201041

1042+
// For the last item with 'end' alignment, use browser's actual max scroll
1043+
// to account for borders/padding that aren't in our measurements
1044+
if (align === 'end' && index === this.options.count - 1) {
1045+
return [this.getMaxScrollOffset(), align] as const
1046+
}
1047+
10211048
const toOffset =
10221049
align === 'end'
10231050
? item.end + this.options.scrollPaddingEnd

0 commit comments

Comments
 (0)