vercel-labs / vercel-labs/native

scroll: add horizontal axis and orthogonal nested routing

Open
#137 1 comment 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Zig
Stars
7.7k
Forks
314
Avg merge
5h
Merged PRs (30d)
13

Description

scroll: add horizontal axis and orthogonal nested routing

Problem

Canvas scroll views currently support only vertical scrolling. The platform input contract already carries both delta_x and delta_y in types.zig, but the canvas event path in canvas_widget_events.zig forwards only the vertical component. The AppKit scroll handler is likewise implemented around a vertical offset in appkit_host.m.

As a result, a native trackpad gesture cannot navigate a horizontal timeline, calendar, or carousel. This becomes especially noticeable when a horizontally scrollable region contains a vertically scrollable child: the vertical child can scroll, but horizontal momentum over that child has no horizontal ancestor to reach.

Minimal reproduction

  1. Create a scroll view whose content is wider than its viewport and configure it for horizontal scrolling.
  2. Place a vertically scrollable region inside the horizontal region.
  3. Hover the nested vertical region and perform a horizontal two-finger trackpad gesture.
  4. Observe that the horizontal region does not move because the canvas event path discards delta_x.
  5. Perform a vertical gesture and observe that the inner vertical region scrolls normally.

Expected behavior

  • A horizontal gesture scrolls the deepest eligible horizontal region.
  • A vertical gesture scrolls the deepest eligible vertical region.
  • For an orthogonally nested pair, each gesture reaches the region matching its axis.
  • Once a gesture chooses an axis, momentum remains locked to that axis so minor diagonal noise does not switch targets mid-gesture.
  • When a same-axis inner region reaches its extent, the existing saturation handoff behavior continues to pass remaining movement to an eligible ancestor.

Proposed scope

Add a single-axis scroll contract:

  • Introduce WidgetScrollAxis { vertical, horizontal }.
  • Expose the axis through Native markup, for example axis="horizontal", and through Zig as ElementOptions.scroll_axis.
  • Retain the existing scalar scroll offset and interpret it along the selected axis.
  • Make scroll layout, content extents, semantics, keyboard behavior, scrollbar placement, and native driver updates axis-aware. The current layout path calculates vertical scroll geometry in widget_layout.zig, while the driver derives a vertical extent in canvas_widget_scroll_drivers.zig; both would select their active dimension from the new axis.
  • Route each gesture to the deepest scroll region whose configured axis matches the gesture's locked dominant axis.
  • Choose that axis from the first meaningful input sample and retain it through the gesture's momentum phase.
  • Preserve the existing same-axis nested boundary handoff covered by the vertical nested-scroll tests in canvas_widget_scroll_tests.zig.

This keeps the change narrow: one scrollable axis per region, one scalar offset, and one axis-aware routing rule. Supporting simultaneous two-dimensional scrolling is not required.

Non-goals

The following application-level features should remain separate from this change:

  • synchronized scroll groups
  • content recycling or reach-boundary callbacks
  • scrollbar visibility controls
  • nested template iteration

Existing issues and pull requests

No existing issue or pull request found against upstream main at eefa369 covers general horizontal canvas scrolling or orthogonal nested routing. PR #90 and PR #83 concern text-field scrolling and do not appear to be the right threads to join.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the scroll input path in src/runtime/canvas_widget_events.zig and the platform contract in src/platform/types.zig, then inspect axis-dependent layout and driver behavior in src/primitives/canvas/widget_layout.zig and src/runtime/canvas_widget_scroll_drivers.zig. Use the nested-scroll cases in src/runtime/canvas_widget_scroll_tests.zig and the AppKit handler in src/platform/macos/appkit_host.m as reference points. Done means horizontal and vertical regions route matching gestures, preserve locked-axis momentum and boundary handoff, and cover the axis-aware behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
zig
Domain
desktop
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.