Skip to content

fix(gestures): a pan on its own scroller takes a trackpad's scroll, as a finger's pan (spec:AST-094 FR43) - #10212

Merged
cixzhang merged 1 commit into
meta:mainfrom
vjeux:uikit/c23-gesture-host-own-scroller
Nov 6, 2026
Merged

cixzhang merged 1 commit into
meta:mainfrom
vjeux:uikit/c23-gesture-host-own-scroller

Conversation

@vjeux

@vjeux vjeux commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

Stack: #10212 → #8153

User impact

On a desktop, a trackpad swipe does not move a pan whose element is also its own scroller, though a finger's pan there does: the host hands the wheel to that scroller, so the row scrolls natively. Carousel's snap paging (#8153, stacked on this) is such a row, and spec:AST-094 FR43 has a pager follow a trackpad and a finger alike.

Problem and solution fit

  • Why: a wheel pan gives the wheel to any scroller from the target up to the pan's element, that element included.
  • Why: for a finger, the derived touch-action already keeps that axis from the browser, so the two pointers disagree on the same element.
  • Fix: on the axis that the derived touch-action keeps, the pan's own element is the pan's for a wheel too. A scroller inside it still takes the wheel first, and a mouse wheel's notches stay native.
  • The mechanism this needs: manageStyle: false applies to its own recognizer. A pager whose viewport is its track attaches its catch to the same element with manageStyle: false, which before dropped the pan's touch-action for the whole element.

Expected behavior and authority

  • spec:AST-094 FR43: a pager follows a trackpad and a finger alike, and a pan takes a trackpad's two-finger scroll where its record allows it, never a mouse wheel's notches.
  • No FR text changes; the gesture module's README and the allowedScrollTypes JSDoc state the own-scroller rule.

Smallest restoration

  • A wheel pan on its own scroller takes that scroller's trackpad scroll on the axis its derived touch-action keeps.
  • manageStyle: false leaves the element's styles, and the hold's lock (FR41), to the caller for that recognizer only.
  • An element with one recognizer behaves as before. Internal only: no public API change, nothing lazy-loaded.

Evidence

  • Desktop (GR-DESKTOP), the new browser case on a pan's own scroller with a scroller inside it:
    • trackpad (WebKit): before, the row scrolled natively and the pan never began; after, the pan begins and the row's scroll offset holds;
    • mouse wheel (Chromium's driver wheels read as a mouse's): the row scrolls natively, before and after;
    • the scroller inside the pager takes the wheel first, before and after.
  • Host cases: the trackpad case and the per-recognizer styling (in both attach orders) fail with main's GestureHost.ts and pass here.

Scope

  • One defect is restored.
  • Separable contradictory or unsettled tagalongs were removed or split.
  • No new public API, visual representation, default, or interaction model is hidden under the bug-fix label.
  • Public text and artifacts contain no internal Meta context.

Testing

  • Red first, with main's GestureHost.ts: the host cases fail (the trackpad case, and the per-recognizer styling in both attach orders); the browser case fails in WebKit at the trackpad step, where the pan never began and the row scrolled natively. Its Chromium mouse-wheel step passes before and after.
  • At 581f5e9: host and wheel cases, 334 passed; the browser case, Chromium ×3 and WebKit ×3, 3 passed each; core tsc, 0 errors; strict eslint on the changed files passes; check:changesets passes; pnpm review:pre, 215 authored lines, no findings.
  • At 076b10a, the same GestureHost.ts: jsdom over gestures, hooks and Carousel, 1,853 passed; CI=true pnpm lint passes; Storybook builds; the gestures browser spec, Chromium on a pinned pair of 2 cores 69 passed, 4 skipped, and WebKit in the CI job's profile (podman --cpus=4, CI=1, LP_NUM_THREADS=4, 1 worker) 61 passed, 12 skipped.
  • Size: Core's GestureHost module bundled with the size report's esbuild options (minified, production): 72,022 B → 72,461 B (+439 B); gzip 21,180 B → 21,324 B (+144 B). Nothing lazy-loaded; the API report is unchanged.

@vercel

vercel Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
astryx Ready Ready Preview Nov 6, 2026 4:18am UTC

Request Review

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Nov 6, 2026
@astracat-bot

astracat-bot Bot commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

AI review status for this pull request.

Review status Updated
🟢 Reviewed (for maintainers only) Nov 6, 2026, 4:31 AM UTC

@github-actions

github-actions Bot commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

PR Analysis Report

🔎 Preview · 581f5e9

Storybook · Sandbox · built from this PR's head

Modified components

Component Size LoC
DateInput 165.9 kB (+171 B) · 15.6 kB CSS (0) —
lab/CommentThread 273.4 kB (+165 B) · 2.4 kB CSS (0) —
Switch 94.0 kB (+148 B) · 9.5 kB CSS (0) —
7 more components
Component Size LoC
SegmentedControl 88.3 kB (+147 B) · 6.8 kB CSS (0) —
Slider 96.4 kB (+142 B) · 8.3 kB CSS (0) —
AlertDialog 94.2 kB (+132 B) · 13.2 kB CSS (0) —
PowerSearch 262.6 kB (+131 B) · 22.7 kB CSS (0) —
TimeInput 142.7 kB (+128 B) · 14.6 kB CSS (0) —
lab/Comments 279.1 kB (+122 B) · 2.5 kB CSS (0) —
DateTimeInput 188.4 kB (+113 B) · 17.3 kB CSS (0) —

Package size: @astryxdesign/core 6.61 MB packed (+1.2 kB); no new dependencies.

API changes: none in this pull request

Committed API report behind this build: cli 19 rows, core 23 rows; run pnpm api:report.

Not run on this PR: add ci:run-visual, ci:run-a11y or ci:run-rtl to run them.


Generated by PR Enrichment workflow | View full report

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Changes requested: the gesture-host fix looks right and both new tests fail on main and pass here, but the module docs still describe the old wheel rule.

  • packages/core/src/gestures/README.md (the "Wheel and trackpad" paragraph) and the allowedScrollTypes JSDoc in GestureRecognizer.ts and PanGestureRecognizer.ts still say a pan takes a trackpad scroll only after every scroller under the pointer, so please add the new exception (a pan's own scroller is skipped on the axis its touch-action keeps, and an inner scroller still goes first), as the SYNC line in GestureHost.ts asks.

Optional: a host test that attaches the long press before the pan would cover the dropped touch-action the description names; on main the current test fails only at its final detach check. CI has not run on this head yet.

[Automated review]

node != null;
node = parentAcrossShadow(node)
) {
if (node === boundary && keepsBoundary) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This skips the pan's own scroller when its touch-action keeps the axis, but README.md's "Wheel and trackpad" paragraph and the allowedScrollTypes JSDoc in GestureRecognizer.ts and PanGestureRecognizer.ts still say a pan moves only after every scroller under the pointer. Please state the exception there, and that an inner scroller still goes first; the SYNC line at the top of this file names the README.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in 581f5e9: the README's "Wheel and trackpad" paragraph and the allowedScrollTypes JSDoc in GestureRecognizer.ts and PanGestureRecognizer.ts state the own-scroller exception, and that an inner scroller still goes first.

detachers.push(coarsePrimaryPointer());
const {row} = mount('<div id="row">A row a pan and a catch share</div>');
const lock = vi.fn(() => () => {});
const detachPan = host.attach(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Non-blocking: on main this case fails only at the final hasAttribute check (the mark left after both detach). The dropped touch-action the description names happens when the unmanaged recognizer attaches first, or when the pan is enabled after it; a case that attaches the long press before the pan would cover it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in 581f5e9: the case runs in both attach orders; with the hold attached first, main drops the pan's touch-action at the first check.

…s a finger's pan (spec:AST-094 FR43)

On the axis that a pan's derived touch-action keeps from the browser, the
pan's own element is the pan's for a trackpad's scroll too, as it is for a
finger. A scroller inside it still takes the wheel first, and a mouse
wheel's notches stay native.

The mechanism this needs: manageStyle: false applies to its own
recognizer, so a pager's catch on the same element no longer drops the
pan's touch-action. The gesture README and the allowedScrollTypes JSDoc
state the own-scroller rule.
@vjeux
vjeux force-pushed the uikit/c23-gesture-host-own-scroller branch from 076b10a to 581f5e9 Compare November 6, 2026 04:10
@vjeux vjeux changed the title fix(gestures): manageStyle is per recognizer, and a pan takes its own scroller's trackpad scroll fix(gestures): a pan on its own scroller takes a trackpad's scroll, as a finger's pan (spec:AST-094 FR43) Nov 6, 2026
@cixzhang

cixzhang commented Nov 6, 2026

Copy link
Copy Markdown
Contributor

/approve-spec 581f5e9

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approved pending CI: CI has not run on this head yet, because the pull request is a draft from a fork; mark it ready for review, and a maintainer adds the ci:run label so CI runs.

The own-scroller trackpad rule and the per-recognizer manageStyle: false follow AST-094 FR43, FR18 and FR41, the module docs now state the exception, and the new wheel, host and WebKit trackpad cases fail on main and pass here. One optional test-cleanup note is inline.

[Automated review]

{manageStyle: false},
);
const detachEarly = holdFirst ? attachHold() : null;
const detachPan = host.attach(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Non-blocking: these attachments sit outside detachers, so a failed assertion above the explicit detaches leaves the row attached. With main's GestureHost.ts, the two cases fail and seven unrelated listener-count cases fail with them. Pushing detachPan and detachHold onto detachers, as the neighbouring cases do, keeps a future failure to this case.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agreed. It is test-only, so it rides this pull request's next push rather than moving the stamped head.

@vjeux
vjeux marked this pull request as ready for review November 6, 2026 04:37
@cixzhang cixzhang added the ci:run A maintainer lets this outside contributor's pull request run CI (AST-030 FR17) label Nov 6, 2026
@cixzhang
cixzhang marked this pull request as draft November 6, 2026 04:54
@cixzhang
cixzhang marked this pull request as ready for review November 6, 2026 04:54
@cixzhang
cixzhang merged commit af2dcb6 into meta:main Nov 6, 2026
171 of 176 checks passed
cixzhang added a commit to vjeux/astryx that referenced this pull request Nov 6, 2026
@vjeux
vjeux deleted the uikit/c23-gesture-host-own-scroller branch November 6, 2026 05:32
cixzhang added a commit that referenced this pull request Nov 6, 2026
…ative snap's stops (#8153)

* fix(gestures): a pan on its own scroller takes a trackpad's scroll, as a finger's pan (spec:AST-094 FR43)

On the axis that a pan's derived touch-action keeps from the browser, the
pan's own element is the pan's for a trackpad's scroll too, as it is for a
finger. A scroller inside it still takes the wheel first, and a mouse
wheel's notches stay native.

The mechanism this needs: manageStyle: false applies to its own
recognizer, so a pager's catch on the same element no longer drops the
pan's touch-action. The gesture README and the allowedScrollTypes JSDoc
state the own-scroller rule.

* feat(core): Carousel's hasSnap takes useSnapPaging(), iOS paging on native snap's stops

hasSnap keeps native CSS snap. hasSnap={useSnapPaging()} pages the row
under a finger and on a trackpad swipe, for every pointer: a flick moves
one view, or one item with {pages: 'item'}, landing on an item's start
edge. A mouse wheel and a mouse drag keep native scrolling. Only a
carousel given the hook's value bundles the pager, and the hook has no
non-React equivalent. The hook has its doc page and a runnable example
block, and Carousel's record no longer lists snap paging as unbuilt.

* Merge main into the Carousel snap paging branch (#10212 landed)

* fix(SnapPaging): a held row's moves stop where the row ends

While a flick holds the row, its children's translate widens the row's
overflow, so its live scroll width runs past its last item. The press,
Shift+wheel and scrollTo moves now clamp to the travel the row was taken
over with, so no space shows past the last item. Carousel's record states
that it also asks the value to move a row it holds.

* Merge main into the Carousel snap paging branch

Co-authored-by: Cindy Zhang <cindyxz@meta.com>

This branch was successfully deployed

1 active deployment
Preview — 581f5e99 Deployed Nov 6, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci:run A maintainer lets this outside contributor's pull request run CI (AST-030 FR17) CLA Signed This label is managed by the Meta Open Source bot.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants