Skip to content
Open
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/green-dodos-see.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/virtual-core': patch
---

Preserve start and end padding for empty lists with a nonzero scroll margin.
4 changes: 2 additions & 2 deletions packages/virtual-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2630,11 +2630,11 @@ export class Virtualizer<
const measurements = this.getMeasurements()

let end: number
// If there are no measurements, set the end to paddingStart
// If there are no measurements, include the scroll origin and start padding
// 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
end = this.options.scrollMargin + this.options.paddingStart
} else if (this.options.lanes === 1) {
// Fast path: read last item's end directly from the flat typed array
// when available; avoids a Proxy.get + VirtualItem materialization
Expand Down
51 changes: 50 additions & 1 deletion packages/virtual-core/tests/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { expect, test, vi } from 'vitest'
import { describe, expect, test, vi } from 'vitest'
import {
Virtualizer,
observeElementOffset,
Expand Down Expand Up @@ -34,6 +34,55 @@ test('should return correct total size with one item and multiple lanes', () =>
expect(virtualizer.getTotalSize()).toBe(50)
})

describe.each([0, 50, 200])(
'total size with scrollMargin %i',
(scrollMargin) => {
test.each([
{ paddingStart: 0, paddingEnd: 0 },
{ paddingStart: 40, paddingEnd: 0 },
{ paddingStart: 0, paddingEnd: 60 },
{ paddingStart: 40, paddingEnd: 60 },
])('preserves padding $paddingStart / $paddingEnd', (padding) => {
for (const horizontal of [false, true]) {
for (const lanes of [1, 2]) {
const virtualizer = new Virtualizer({
count: 0,
estimateSize: () => 50,
getScrollElement: () => null,
scrollToFn: vi.fn(),
observeElementRect: vi.fn(),
observeElementOffset: vi.fn(),
scrollMargin,
horizontal,
lanes,
...padding,
})
const totalPadding = padding.paddingStart + padding.paddingEnd

for (const count of [0, 4, 0, 4, 0]) {
virtualizer.setOptions({ ...virtualizer.options, count })
expect(virtualizer.getTotalSize()).toBe(
totalPadding + Math.ceil(count / lanes) * 50,
)
}

virtualizer.setOptions({
...virtualizer.options,
count: 4,
enabled: false,
})
expect(virtualizer.getTotalSize()).toBe(totalPadding)

virtualizer.setOptions({ ...virtualizer.options, enabled: true })
expect(virtualizer.getTotalSize()).toBe(
totalPadding + (4 / lanes) * 50,
)
}
}
})
},
)

test('should correctly recalculate lane assignments when lane count changes', () => {
// Create a mock scroll element
const mockScrollElement = {
Expand Down