Skip to content

fix(Virtual, VirtualGrid): animate grid rows past row 3; scroll auto rows of displaySize items - #86

Merged
chiefcll merged 1 commit into
mainfrom
fix/virtual-grid-animation-display-size
Nov 11, 2026
Merged

chiefcll merged 1 commit into
mainfrom
fix/virtual-grid-animation-display-size

Conversation

@chiefcll

Copy link
Copy Markdown
Contributor

Two bugs reported from the field.

VirtualGrid: the vertical transition stops working after the third row

When the focused row changes, applyShift records where the rows were so that it can animate them from there. It took that reading inside the microtask. The post-mutation flex pass had already run by then, so it read the new layout and the compensation was zero. As soon as the window starts sliding, every row change jumped a row instead of animating. With the default buffer of 2 that happens at row index 3, hence "after the third row".

Fix: read this.y + active.y in onSelectedChanged, before setSlice changes the window. Virtual.tsx already does this with shiftPrevChildPos.

Virtual: with items.length === displaySize, the last tile is not scrolled into view

With scroll="auto" (the default) and no wrap, the row scrolls one slot for any longer list, and the last item ends at slot displaySize - 2. A list of exactly displaySize items took the "everything fits" path instead. The row never scrolled, and the last item ended one slot further right, out of view.

The suggested fix was to change total <= displaySize to total < displaySize. I measured the focused item's position at each press in every scroll mode, with and without wrap, for 3–6 items with displaySize 4. That change fixes auto, but it breaks:

  • scroll="always": the last item can no longer be reached (focus stops one short).
  • every wrap mode: the row is one slot off and the same item is mounted twice.

Fix: only auto without wrap at exactly displaySize items takes the windowed path now. After this change, that case is the only one whose positions changed. Going back left, it follows the same path as longer lists.

Tests

  • contract-virtual.test.tsx: auto with 4 items and displaySize 4, Right ×4 then Left ×3, checking the focused item's screen x at each step.
  • contract-virtual-grid.test.tsx: with animations on (frames driven the same way as the existing B14 test), Down to row 6 and Up back to row 0. Each press must start a y transition, no mounted cell may move before it starts, and the grid must end at the right y. Without the fix it fails with ArrowDown to row 3: no y transition.

Both tests fail without their fix. npm test (757 passed), npm run tsc and npm run lint pass. I have not checked either fix in an app on the WebGL renderer; the tests run on the DOM renderer in jsdom.

Not fixed (pre-existing, for a reviewer to know)

  • scroll="always" already skips an item when the count is between displaySize and displaySize + bufferSize. For example, with 5 items and displaySize 4 the focus goes v2 → v4. The selected math assumes the window start was never clamped.
  • wrap with exactly displaySize items still doesn't scroll. Changing that means deciding how wrap fills a window that has more slots than there are items.

🤖 Generated with Claude Code

…rows of displaySize items

VirtualGrid: the row change read the rows' old position in applyShift,
after the post-mutation flex pass had already laid out the new window.
Once the window slid (row 3 on with the default buffer of 2) the rows
jumped a row and the y transition did not run. The position is now read
in onSelectedChanged, before the window changes.

Virtual: with scroll="auto" (no wrap) and exactly displaySize items, the
row never scrolled and the last item ended one slot past where it does
for any other list length, out of view. That case now goes through the
windowed path. Wrap and the other scroll modes keep the all-mounted path:
switching them too broke "always" (last item unreachable) and wrap (items
mounted twice, row off by a slot).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chiefcll
chiefcll merged commit 04aca97 into main Nov 11, 2026
1 check passed
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.

1 participant