diff --git a/.changeset/green-dodos-see.md b/.changeset/green-dodos-see.md new file mode 100644 index 00000000..22e7ab10 --- /dev/null +++ b/.changeset/green-dodos-see.md @@ -0,0 +1,5 @@ +--- +'@tanstack/virtual-core': patch +--- + +Preserve start and end padding for empty lists with a nonzero scroll margin. diff --git a/packages/virtual-core/src/index.ts b/packages/virtual-core/src/index.ts index bd6e7cb2..0a8227e9 100644 --- a/packages/virtual-core/src/index.ts +++ b/packages/virtual-core/src/index.ts @@ -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 diff --git a/packages/virtual-core/tests/index.test.ts b/packages/virtual-core/tests/index.test.ts index 7e609fd2..ebed6971 100644 --- a/packages/virtual-core/tests/index.test.ts +++ b/packages/virtual-core/tests/index.test.ts @@ -1,4 +1,4 @@ -import { expect, test, vi } from 'vitest' +import { describe, expect, test, vi } from 'vitest' import { Virtualizer, observeElementOffset, @@ -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 = {