vercel-labs / vercel-labs/native
scroll: add horizontal axis and orthogonal nested routing
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
- Create a scroll view whose content is wider than its viewport and configure it for horizontal scrolling.
- Place a vertically scrollable region inside the horizontal region.
- Hover the nested vertical region and perform a horizontal two-finger trackpad gesture.
- Observe that the horizontal region does not move because the canvas event path discards
delta_x. - 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 asElementOptions.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 incanvas_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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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