LearnNewsExamplesServices
Frontmatter
id18871
titleArrow-key navigation no longer scrolls a grid horizontally
stateClosed
labels
bugairegressiongrid
assigneesneo-fable
createdAtSep 18, 2026, 2:26 PM
updatedAtSep 18, 2026, 3:30 PM
githubUrlhttps://github.com/neomjs/neo/issues/18871
authorneo-fable
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtSep 18, 2026, 3:30 PM

Arrow-key navigation no longer scrolls a grid horizontally

Closed v13.2.0/archive-v13-2-0-chunk-21 bugairegressiongrid
neo-fable
neo-fable commented on Sep 18, 2026, 2:26 PM

Context

Found while building Tab traversal for grid cell editing (#18861), which scrolls its target into sight through the same primitive the arrow keys use. Measured on dev@f44ddcf81e, fixture test/playwright/component/apps/grid-cell-editing, grid #grid-cell-editing-pooled (40 columns of 150px, 1400px viewport), with no editing involved.

The Problem

Click cell r3c3, press ArrowRight twelve times. The selection moves to c15; the grid does not scroll: .neo-grid-horizontal-scrollbar stays at scrollLeft: 0, the selected column leaves the mounted window, and no cell carries neo-selected any more. The keyboard walks off-screen. ArrowDown scrolls as it should (scrollTop 636 after thirty presses).

The Architectural Reality

grid.Container#scrollByColumns (src/grid/Container.mjs:1340) ends in Neo.main.DomAccess.scrollTo({direction: 'left', id: me.getVdomRoot().id, …}) — it scrolls the container's root node. That was the horizontal scroll owner when #8982 / #8999 fixed this path in February. Since the multi-body architecture (#9612) the owner is the grid.HorizontalScrollbar the container creates (:304); the root no longer scrolls, so the call lands nowhere. grid.View#scrollByRows was not affected.

With id: me.horizontalScrollbar.id, the same twelve presses give scrollLeft: 1000 and r3c15 selected and rendered; thirty ArrowDowns after it keep r33c15 selected and rendered.

Measured once the scroll moved again (2026-09-18), two more defects in the same method: at scrollLeft: 1000 the selected r3c15 sits at x 1282–1432, entirely under the locked-end body (1280–1430); the center body shows 182–1280.

  • The target is read as body.columnPositions.getAt(index + step) with the selection model's index, which counts columns.items. columnPositions holds the center columns only, so every locked-start column shifts the target by one: the method positioned c16 for a selection on c15.
  • It aligns a right-moving target to body.containerWidth, the whole container's width, not to what the center body shows — the container less the two locked regions, which grid.HorizontalScrollbar carries as startWidth / endWidth.

The Fix

scrollByColumns resolves the target column in columns, skips a locked one (always in sight), reads its position by dataField, and scrolls the horizontal scrollbar only when the column lies outside [scrollLeft, scrollLeft + visible width]. The stale visibleColumns bookkeeping goes: grid.Body#afterSetScrollLeft recomputes it on every scroll. One Brain-free e2e spec on the pooled fixture.

Acceptance Criteria

  • Arrowing right past the visible columns scrolls the grid: the selected cell stays rendered and fully inside the center body's visible width; arrowing back left does the same in reverse. Red on dev first.
  • The arm runs in the CI e2e tier; no correctness sleeps.
  • The grid e2e and component tiers stay green.

Out of Scope

grid.Body#updateMountedAndVisibleColumns also counts the columns under the locked-end body as visible (it uses the same containerWidth) — harmless for mounting, noted · Tab traversal (#18861), which inherits this.

Related

#8982 · #8999 (the earlier fix of this path) · #9612 (the multi-body change after which the root stopped scrolling) · #18861.

Live latest-open sweep: latest 20 open issues at 2026-09-18T12:26Z plus all-state searches for scrollByColumns and grid arrow key horizontal scroll — no open equivalent (#8982 and #8999 are closed and describe the pre-multi-body fix). A2A sweep at 12:20Z: no claim on this scope.

Origin Session ID: 323cb989-06a2-4b3f-8d70-912d67796c11

Retrieval Hint: "grid scrollByColumns horizontalScrollbar keyboard navigation ArrowRight does not scroll regression multi-body"