Skip to content

fix(virtual-core): account for scrollMargin when deciding the viewport is at the end - #1340

Merged
piecyk merged 1 commit into
TanStack:mainfrom
KooYS:fix/scroll-margin-end-check
Oct 26, 2026
Merged

piecyk merged 1 commit into
TanStack:mainfrom
KooYS:fix/scroll-margin-end-check

Conversation

@KooYS

@KooYS KooYS commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

🎯 Changes

Fixes #1339.

getVirtualDistanceFromEnd subtracts two values measured from different origins:

this._getLayoutTotalSize() - this.getSize() - this.getScrollOffset()

_getLayoutTotalSize() is the sizer, which is end - scrollMargin + paddingEnd and therefore starts scrollMargin into the scroll content. getScrollOffset() is measured from the top of that content. The result reads the viewport scrollMargin px closer to the end than it is, so resizeItem's wasAtEnd fires for a reader who is still inside the margin and an item growing below the fold pulls them down.

getDistanceFromEnd() and isAtEnd() are computed from the element's own max scroll and are correct, so today they report "not at the end" for the same viewport the internal check treats as pinned.

The change lifts the total into the element's space before subtracting. scrollMargin defaults to 0, so a list that does not set it is unaffected. Reading the intent the other way round gives the same expression: if the distance is meant to be from the end of the list rather than of the element, the scroll offset is the value that needs the margin taken off it.

With scrollMargin: 300 and the default scrollEndThreshold of 1, growing the last row:

distance from end isAtEnd() before after
0 true adjusted adjusted
1 true adjusted adjusted
300 false adjusted not adjusted
900 false not adjusted not adjusted

Tests. createChatVirtualizer takes a scrollMargin, which also moves the mock element's scrollHeight, and two cases sit next to the existing end-anchor tests: a reader 100px above the end with a 150px margin is left alone, which fails on main, and an end-pinned viewport with the same margin still gets its 70px adjustment, which passes before and after.

✅ Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested this code locally with pnpm run test:pr.

For virtual-core: test:lib 440 passing and 1 skipped, test:types, test:eslint, build. At the root: test:sherif, test:docs, test:knip. test:build did not run on this machine, it fails the same way on a clean main here because publint shells out to the system pnpm. I did not run the Playwright e2e.

🚀 Release Impact

  • This change affects published code, and I have generated a changeset.
  • This change is docs/CI/dev-only (no release).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Corrected end-of-viewport detection to account for scroll margin, preventing unintended scrolling for readers who are near—but not at—the end.
    • Preserved end-pinned scrolling behavior, including resize adjustments.

…t is at the end

`getVirtualDistanceFromEnd` subtracted a scroll offset measured from the top
of the scroll content from a total that starts `scrollMargin` into it, so the
end check read the viewport `scrollMargin` px closer to the end than it is.
With `anchorTo: 'end'` that pulled a reader sitting inside the margin back
down when an item grew, while `getDistanceFromEnd()` and `isAtEnd()`, which
use the element's own max scroll, correctly reported them as away from the end.

Lift the total into the element's space before subtracting. `scrollMargin`
defaults to 0, so lists that do not set it are unaffected, and an end-pinned
viewport still gets pinned.

Co-Authored-By: Claude Opus 5 <[email protected]>
@changeset-bot

changeset-bot Bot commented Oct 25, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 5d760dc

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 9 packages
Name Type
@tanstack/virtual-core Patch
@tanstack/angular-virtual Patch
@tanstack/lit-virtual Patch
@tanstack/marko-virtual Patch
@tanstack/react-virtual Patch
@tanstack/solid-virtual Patch
@tanstack/svelte-virtual Patch
@tanstack/vue-virtual Patch
@tanstack/virtual-benchmarks Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitai Bot commented Oct 25, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: fc3757bd-d5bf-427c-8833-5c82f9f5e0dd
Commits

Reviewing files that changed from the base of the PR and between c462c61 and 5d760dc.

Files selected for processing: 3
  • .changeset/scroll-margin-end-check.md
  • packages/virtual-core/src/index.ts
  • packages/virtual-core/tests/anchor-end.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 7 remain after this review.

Walkthrough

The virtual distance from the end now includes scrollMargin. Regression tests cover streaming item growth when the viewport is away from or pinned to the end. A patch changeset records the update.

Changes: Scroll margin end-distance calculation

Layer / File(s) Summary
End-distance calculation and regression coverage
packages/virtual-core/src/index.ts, packages/virtual-core/tests/anchor-end.test.ts, .changeset/scroll-margin-end-check.md
getVirtualDistanceFromEnd adds scrollMargin before calculating the distance and still clamps the result to zero. Tests cover streaming growth when a reader is 100px from the end and when the viewport is pinned to the end. The changeset records a patch release for @tanstack/virtual-core.

Priority: Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix · Severity of issue fixed: Medium

Suggested reviewers: piecyk

Merge Risk: Minimal · up to 5d760

The change is ready to merge; the intended nonzero-scroll-margin behavior is covered without identified regressions.

Pre-merge checks: Passed 5
Check name Status Explanation
Title check Passed The title follows the conventional commit format and names the primary change: getVirtualDistanceFromEnd now accounts for scrollMargin when deciding whether the viewport is at the end. It matches the …
Description check Passed The description covers the change and its motivation, linked issue #1339, a worked example table, the test changes, and the validation run. It fills in the template's Changes, Checklist, and Release I…
Linked Issues check Passed Issue #1339 is directly linked and requires scrollMargin-aware end detection while preserving end pinning and the scrollMargin: 0 behavior. getVirtualDistanceFromEnd now adds `this.options.scrol…
Out of Scope Changes check Passed The changed source implements the correction requested by issue #1339. The added tests verify the affected nonzero-margin behavior and preserve end pinning. The changeset documents the published packa…
Docstring Coverage Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 2 files. (1 skipped: 1 …
Full details: Title check

Explanation

The title follows the conventional commit format and names the primary change: getVirtualDistanceFromEnd now accounts for scrollMargin when deciding whether the viewport is at the end. It matches the code change in packages/virtual-core/src/index.ts and the added tests, so it is specific and accurate.

Full details: Description check

Explanation

The description covers the change and its motivation, linked issue #1339, a worked example table, the test changes, and the validation run. It fills in the template's Changes, Checklist, and Release Impact sections. The Release Impact section checks the changeset box, and the raw summary confirms a changeset was added. The description does not follow the template's emoji headings exactly, but all required information is present.

Full details: Linked Issues check

Explanation

Issue #1339 is directly linked and requires scrollMargin-aware end detection while preserving end pinning and the scrollMargin: 0 behavior. getVirtualDistanceFromEnd now adds this.options.scrollMargin before subtracting the viewport size and scroll offset. The result remains clamped to zero. The change therefore aligns the internal distance with the element-space distance used by the public end checks. The added tests cover a reader 100px from the end with a 150px margin and an end-pinned viewport with the same margin. Existing zero-margin behavior remains covered by the existing tests.

Full details: Out of Scope Changes check

Explanation

The changed source implements the correction requested by issue #1339. The added tests verify the affected nonzero-margin behavior and preserve end pinning. The changeset documents the published package fix. No unrelated product behavior or unrelated files appear in the whole-PR diff.

Full details: Docstring Coverage

Explanation

Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 2 files. (1 skipped: 1 unsupported.)

Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Warning

Some tools did not complete. Review the errors below.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

packages/virtual-core/tests/anchor-end.test.ts

Parsing error: "parserOptions.project" has been provided for @typescript-eslint/parser.
The file was not found in any of the provided project(s): packages/virtual-core/tests/anchor-end.test.ts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@piecyk piecyk left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @KooYS, nice catch and a really clear write-up. The math checks out, the total is in list space and the offset is in element space, so adding scrollMargin back makes it line up with getDistanceFromEnd(). I also ran the new test on main and it fails there, so it covers the bug. LGTM 🚀

@nx-cloud

nx-cloud Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit 5d760dc

Command Status Duration Result
nx affected --targets=test:sherif,test:knip,tes... ✅ Succeeded 4m 21s View ↗
nx run-many --target=build --exclude=examples/** ✅ Succeeded 22s View ↗

☁️ Nx Cloud last updated this comment at 2026-10-26 11:38:10 UTC

@pkg-pr-new

pkg-pr-new Bot commented Oct 26, 2026

Copy link
Copy Markdown
More templates

@tanstack/angular-virtual

npm i https://pkg.pr.new/@tanstack/angular-virtual@1340

@tanstack/lit-virtual

npm i https://pkg.pr.new/@tanstack/lit-virtual@1340

@tanstack/marko-virtual

npm i https://pkg.pr.new/@tanstack/marko-virtual@1340

@tanstack/react-virtual

npm i https://pkg.pr.new/@tanstack/react-virtual@1340

@tanstack/solid-virtual

npm i https://pkg.pr.new/@tanstack/solid-virtual@1340

@tanstack/svelte-virtual

npm i https://pkg.pr.new/@tanstack/svelte-virtual@1340

@tanstack/virtual-core

npm i https://pkg.pr.new/@tanstack/virtual-core@1340

@tanstack/vue-virtual

npm i https://pkg.pr.new/@tanstack/vue-virtual@1340

commit: 5d760dc

@piecyk
piecyk merged commit 2343004 into TanStack:main Oct 26, 2026
10 checks passed
@github-actions github-actions Bot mentioned this pull request Oct 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

scrollMargin is ignored when deciding the viewport is at the end

2 participants