Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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/gentle-cloths-dance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/virtual-core': patch
---

fix(virtual-core): expand range in masonry layouts to catch items from all lanes
58 changes: 44 additions & 14 deletions packages/virtual-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1036,16 +1036,25 @@ export class Virtualizer<

let end: number
// If there are no measurements, set the end to paddingStart
// If there is only one lane, use the last measurement's end
// Otherwise find the maximum end value among all measurements
if (measurements.length === 0) {
end = this.options.paddingStart
} else if (this.options.lanes === 1) {
end = measurements[measurements.length - 1]?.end ?? 0
} else {
// If lanes is 1, use the last measurement's end, otherwise find the maximum end value among all measurements
end =
this.options.lanes === 1
? (measurements[measurements.length - 1]?.end ?? 0)
: Math.max(
...measurements.slice(-this.options.lanes).map((m) => m.end),
)
const endByLane = Array<number | null>(this.options.lanes).fill(null)
let endIndex = measurements.length - 1
while (endIndex > 0 && endByLane.some((val) => val === null)) {
const item = measurements[endIndex]!
if (endByLane[item.lane] === null) {
endByLane[item.lane] = item.end
}

endIndex--
}

end = Math.max(...endByLane.filter((val): val is number => val !== null))
}

return Math.max(
Expand Down Expand Up @@ -1121,14 +1130,35 @@ function calculateRange({
)
let endIndex = startIndex

while (
endIndex < lastIndex &&
measurements[endIndex]!.end < scrollOffset + outerSize
) {
endIndex++
}
if (lanes === 1) {
while (
endIndex < lastIndex &&
measurements[endIndex]!.end < scrollOffset + outerSize
) {
endIndex++
}
} else if (lanes > 1) {
// Expand forward until we include the visible items from all lanes
// which are closer to the end of the virtualizer window
const endPerLane = Array(lanes).fill(0)
while (
endIndex < lastIndex &&
endPerLane.some((pos) => pos < scrollOffset + outerSize)
) {
const item = measurements[endIndex]!
endPerLane[item.lane] = item.end
endIndex++
}

// Expand backward until we include all lanes' visible items
// closer to the top
const startPerLane = Array(lanes).fill(scrollOffset + outerSize)
while (startIndex > 0 && startPerLane.some((pos) => pos >= scrollOffset)) {
const item = measurements[startIndex]!
startPerLane[item.lane] = item.start
startIndex--
}

if (lanes > 1) {
// Align startIndex to the beginning of its lane
startIndex = Math.max(0, startIndex - (startIndex % lanes))
// Align endIndex to the end of its lane
Expand Down