Skip to content

feat(core): gesture host — claims, click suppression and the browser's takeover - #8072

Closed
vjeux wants to merge 113 commits into
meta:mainfrom
vjeux:wip/uikit-pr-g2b
Closed

vjeux wants to merge 113 commits into
meta:mainfrom
vjeux:wip/uikit-pr-g2b

Conversation

@vjeux

@vjeux vjeux commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Depends on #8071 (and its predecessors); review the last commit.

Primary intent

Connect the gesture system to the DOM: one host per document routes touches, pens and mice to the recognizers on the hit path and negotiates with the browser. A claimed touch never scrolls or clicks; an unclaimed one behaves as without the system.

User or maintainer impact

Each touch component negotiates alone, and results drift: ghost clicks after swipes, pinches zooming the page, presses surviving a scroll. The host gives every component one measured negotiation. Known limit: a scroll that begins on a mid-gesture recognizer waits for a busy main thread (measured in iOS Safari; a passive listener loses every diagonal to the scroll).

Problem and solution fit

  • Why does it happen? Each component negotiates alone.
  • Why does that hurt? Lost touches, clicks after gestures.
  • Why does it matter? Safari honors a cancelled touchmove only before it scrolls.

Deltas:

  • GestureHost:
    • touch events for fingers (the cancelable channel), paired with their pointer events (Chromium sends no touchmove within its slop);
    • pointer events for mice and pens; per-target listeners;
    • claims: a non-passive touchmove listener only where a recognizer can claim a moving touch (not taps or press consumers), cancelling while one owns the touch or a pan claims early;
    • pointercancel, a scroll, or an uncancelable move the browser could pan, as the browser taking the touch;
    • click suppression (a cancelled touchend, a one-shot swallower) and replay;
    • delegated consumers by attribute;
    • pointerIdOf(touch): the pointer behind a held finger.
  • touch-action set on attach, restored on detach; the scroll probe on every touch.

Authority

spec:AST-094 FR19 (claims), FR20 (the browser taking a touch) and FR21 (clicks), proposed; pending owner review.

Change and evidence

Measured in Mobile Safari 26.2 (iOS 26.2 simulator, HID touches):

  • touchmove arrives from the first pixel;
  • WebKit commits to a scroll at 10–11 px; cancelling from 8 px keeps the page still, and a flick's first touchmove is cancelable even 20–30 px out;
  • a cancelled touchend stops the click and mouse events;
  • claiming costs no delivery rate (median 17–20 ms apart, as passive).

The browser-proof fixture passed on the simulator: a horizontal pan claimed over a scroller, a vertical drag left to it, no ghost click, a scroll-edge pull, a pinch on its element only, a long press at exactly 500 ms.

Scope

  • This PR has one primary intent.
  • Independently removable deltas were removed or split (press tracking, hooks, proof and docs follow).
  • Public text and artifacts contain no internal Meta context.

Testing

  • pnpm exec vitest run --project ui packages/core/src/gestures packages/core/src/motion: 287 tests passed.
  • tsc --project packages/core/tsconfig.json --noEmit: clean.
  • eslint packages/core/src/gestures: clean.
  • pnpm check:changesets, changeset coverage, pnpm sync:exports:check: clean.
  • Size: 3 files, 2,115 lines added.

Stack (merge in this order): #8052 spec → #8053 constants table → #8058 gesture core → #8059 tap and long press → #8060 pan → #8061 other recognizers → #8054 math → #8055 animator → #8056 animator curves → #8067 useAnimator → #8071 browser negotiation → #8072 gesture host → #8073 press tracking → #8074 browser proof and README.

vjeux added 4 commits October 23, 2026 20:53
Springs, scroll physics, timing curves, navigation values and gesture
thresholds for the touch presentations of core components, each with its
source (Apple documentation, a WWDC session, WebKit's UIKit-matched source,
or a measurement of UIKit on the iOS 26.2 simulator) or labelled a guess
with its measurement task. A test enforces the source rules on every domain
file, in their JSDoc and typed forms, and checks the list of simulator
measurements still owed.
…rena

An internal gesture system for touch presentations, not exported from the
package. GestureRecognizer carries UIKit's states and allowed transitions,
discrete and continuous recognizers, require-to-fail, simultaneous
recognition on request, shouldBegin and shouldReceiveTouch,
cancelsTouchesInView and the touch delays. GestureArena settles each touch
sequence without the DOM: participants, resolution order, claims, delivery
to the view, click dispositions (allow, suppress, defer and replay) and
timers as deadlines on the event clock. Its rules are tested with scripted
recognizers, independent of any real one.
…sholds

Tap recognizes at the lift within 45 pt of movement, taps 0.35 s and 45 pt
apart, and a 1.5 s hold; long press begins at exactly touch-down + 0.5 s on
the event clock within 10 pt, even when the main thread was blocked, and a
zero duration begins on the touch itself. The arena tests with these real
recognizers cover tap against double tap, delegate requirements, deferred
clicks and cancellation.
@vercel

vercel Bot commented Oct 24, 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 Oct 27, 2026 8:53am UTC

Request Review

@github-actions github-actions Bot added the needs:design-review Affects visuals — Design should review label Oct 24, 2026
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Oct 24, 2026
@astracat-bot

astracat-bot Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

AI review status for this pull request.

Review status Updated
❓ Needs maintainer input (for maintainers only) Oct 30, 2026, 7:53 AM UTC

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This pull request is on hold for a spec decision: the gesture host counts only a finger's first uncancelable touchmove, and only where the browser could pan that way, as the browser taking the touch (GestureHost.ts lines 791-826), while the current spec:AST-094 FR20 says any uncancelable touchmove counts. An engineering owner needs to decide whether FR20 is amended on main to the host's rule or the host counts every uncancelable move, and records that decision by commenting /approve-spec <full head SHA> on this pull request; the author may do so as an engineering owner. Independently of that decision, these changes are still needed:

  • Keep an ownScroll exemption alive until the newest call's frame has passed (lines 1461-1472): an earlier call's timer clears it, so a menu autoscrolling under a finger loses its press (AST-094 FR30).
  • Import only UIKIT_PRESS and UIKIT_CLICK, and stop importing recognizer classes just for instanceof, so components ship only the gesture-table rows they read (architecture:component-style-authoring INV6).
  • Gate the finger's target listener on isEnabled (line 742) and claimsWhileMoving on claimsTouches (lines 160-165), and re-sync the document listeners after a detach that leaves only disabled recognizers (lines 516-523), as AST-094 FR19, FR20 and FR24 require.
  • Land #8061 and #8071 first so this diff carries only the host, split a separable piece out of its 2,680 lines or show why it has to land as one, and add the real-touch browser tests that AST-094 names for FR18–FR22.

[Automated review]

if (inputs.length === 0) {
return;
}
if (!event.cancelable) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This branch counts only a finger's first uncancelable touchmove, only where the browser could pan along its main axis, and never a claimed touch's (with the scroll path skipping claimed touches too, line 1497), while current spec:AST-094 FR20 says an uncancelable touchmove MUST count as the browser taking the touch. The Chromium reason in the comment is real, so this needs an engineering owner's decision: amend FR20 on main to this rule, or count every uncancelable move here.

}
view.requestAnimationFrame(() => {
view.requestAnimationFrame(() => {
this.ownedScrolls.delete(scroller);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This delete is unconditional, so an earlier ownScroll call's timer removes the exemption a newer call has just renewed. With an autoscroll loop that re-registers its requestAnimationFrame before it scrolls, the scroll from the third frame's write arrives after the first call's delete, and onScroll hands the held finger to the browser (a probe loses the press on frame 4), against AST-094 FR30. Keeping a per-scroller generation or frame deadline and deleting only when no newer call came since fixes it; a test with consecutive per-frame calls would pin it.

* SYNC: When modified, update GestureHost.test.ts and README.md.
*/

import {UIKIT_GESTURES} from '../motion/uikit/gestures';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

UIKIT_GESTURES bundles all eleven groups (its own comment says so, citing architecture:component-style-authoring INV6), and the host reads three values from two of them, so every component that attaches a recognizer will ship the swipe, pinch, rotation and edge rows. Importing UIKIT_PRESS and UIKIT_CLICK removes those; the tap and long-press rows still come in through the TapGestureRecognizer import here and LongPressGestureRecognizer in touchAction.ts, both used only for instanceof.

this.watchTarget(
touch.identifier,
target,
recognizers.some(claimsWhileMoving),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This flag counts disabled recognizers, while refresh gates the element listener on r.isEnabled: a finger under a disabled pan gets a passive: false target listener although nothing on its path can claim it (AST-094 FR19, FR24). recognizers.some(r => r.isEnabled && claimsWhileMoving(r)) matches refresh.

*/
function claimsWhileMoving(recognizer: GestureRecognizer): boolean {
return (
recognizer.acceptsTouchInput &&

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

A recognizer whose claimsTouches is false never claims, but this still makes its element and target listeners blocking, where AST-094 FR20 says a watcher "registers only passive listeners" (DEC-7). Adding recognizer.claimsTouches && here covers the first watcher a component adds.

Comment on lines +517 to +523
if (
registration.recognizers.length > 0 ||
registration.hovers.length > 0 ||
registration.consumer != null
) {
return;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This early return skips syncListeners() whenever a disabled recognizer, hover or consumer stays on the element, so detaching the last enabled pan beside a disabled long press leaves every document and window listener registered (AST-094 FR24: "while a touch presentation is disabled, no touch-only listener, observer or timer is active"). Re-syncing before the return fixes it.

vjeux added a commit to vjeux/astryx that referenced this pull request Oct 26, 2026
… recognizer's own scroll, from meta#8072) into the experimental touch layer
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 26, 2026
Every recognizer and press tracker attaches through the host, so what it
imports ships with every touch component (architecture:component-style-
authoring INV6). The esbuild bundle of the host alone must carry the press
and click groups it reads and never the whole table. It fails on this head:
the host imports UIKIT_GESTURES, the union of every group (the same test as
the offer on meta#8072).
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 26, 2026
The host read its values off UIKIT_GESTURES, the union of every group, so
any component attaching a recognizer or press tracker bundled all eleven
groups. It imports UIKIT_PRESS and UIKIT_CLICK (meta#8468's per-group exports),
as the offer on meta#8072 does at that layer, and the bundle test passes.
cixzhang added a commit to vjeux/astryx that referenced this pull request Oct 26, 2026
…slop first

meta#8309 lets a scroll under a finger that has not travelled the press slop
leave the touch where it is (the page's own scroll, not the finger's pan).
The FR30 case from meta#8072 used a still finger, so after the merge its
unannounced scroll no longer dropped the touch for the right reason. The
finger now travels 30 px (a tolerant long press keeps tracking it) before
the recognizer's own write and the unannounced scroll that follows, so the
case checks ownScroll alone.
@vjeux

vjeux commented Oct 26, 2026

Copy link
Copy Markdown
Contributor Author

Why this note: the host items in this review, and the two host items in #8073's review, are already fixed on the gesture line built above this stack (#8309), each with a test that failed first. You can take any of them down to this branch as you like. One link per item:

The FR20 spec question at the top of this review stays an owner decision; nothing here assumes its answer.

…ith one passive listener and press-time document listeners

family:action-groups FR5 (and the AST-094 FR19/FR24 exception proposed with
it): the behavior applies to every pointer, mouse and pen included, so a
group must hear a press where the primary pointer is fine, where the host
otherwise registers nothing. attach() takes { actionGroup: true }: under a
fine pointer the group element gets one passive pointerdown listener; a
press on it adds document pointermove, pointerup and pointercancel
listeners (and window blur, plus the click listener for the click the lift
owes), and they go at the release. Only the group's own recognizers and
consumer take that press. Under a coarse pointer the host's usual listeners
serve the group, as every other recognizer.

Tests (failing first): idle under a fine pointer, only the group's one
passive pointerdown and nothing for a recognizer that is not a group; a
press adds the document listeners and the release removes them; a
recognizer that is not a group on the press path takes no part; detach
removes the group listener, and a coarse pointer keeps the usual
listeners.
cixzhang added a commit to vjeux/astryx that referenced this pull request Oct 27, 2026
cixzhang added a commit to vjeux/astryx that referenced this pull request Oct 27, 2026
cixzhang added a commit to vjeux/astryx that referenced this pull request Oct 27, 2026
…tion) as a dependency

The action group's press filter reads acceptsTouchInput through internals(),
as the rest of the gesture host does now that the getter is protected.
cixzhang added a commit to vjeux/astryx that referenced this pull request Oct 27, 2026
…nce, so a still finger's long press begins

A browser with Touch Events sends them beside the pointer events for one
finger. With inputMode 'pointer-events' the host still read both, so the
finger was two touches and a long press (one touch required) never
began; a moving finger's pan still worked, which hid it. The host now
registers no Touch Event listener in that mode: the finger comes from its
pointer events alone.

Test (failing first): under 'pointer-events', a touch pointerdown plus its
touchstart is one touch, and a long press held past its duration begins
and ends.
cixzhang added a commit to vjeux/astryx that referenced this pull request Oct 27, 2026
cixzhang added a commit to vjeux/astryx that referenced this pull request Oct 27, 2026

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This pull request is on hold for two spec decisions: the gesture host counts only a finger's first uncancelable touchmove, and never a claimed touch's, as the browser taking it (GestureHost.ts lines 844-879), while the current spec:AST-094 FR20 counts every uncancelable move; and the new action-group path runs recognizers under a fine primary pointer on the strength of family:action-groups FR5 and an FR19/FR24 exception that exist only in the open spec pull request #8622. An engineering owner needs to decide whether FR20 is amended on main or the host follows it, and whether #8622's record and exception become current or the action-group path is dropped, and records that decision by commenting /approve-spec <full head SHA> on this pull request; the author may do so as an engineering owner. Independently of those decisions, these changes are needed:

  • Count a native scroll as the browser taking a finger only after its 10 px of travel (AST-094 FR20 and DEC-8, lines 1661-1695).
  • Intersect the derived touch-action with the author's instead of writing over it (FR18 and DEC-8, lines 524-555).
  • Fix the ownScroll expiry, the UIKIT_GESTURES import, the three listener gates and the pointer-events claim (FR30, INV6, FR19, FR20, FR24).
  • Land #8061 and #8071 first, split or justify the host's 3,053 lines, and add the real-touch browser tests AST-094 names for FR18–FR22.

[Automated review]

) {
continue;
}
if (touch.target instanceof Node && scroller.contains(touch.target)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This takes any unclaimed finger under a scrolling scroller, whatever its travel. Current spec:AST-094 FR20 counts a native scroll "only once that finger has moved the scroll take distance (FR2), so sub-pixel movement under a still finger does not cancel a tap" (DEC-8), so a still finger's press should survive a carousel advancing or scroll anchoring under it. Keep each finger's travel from where it went down and skip fingers under 10 px; the test at GestureHost.test.ts lines 802-811 asserts the takeover with no travel.

element.style.getPropertyValue(property),
);
}
element.style.setProperty(property, value);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This writes the derived touch-action over the element's own value without reading it. Current spec:AST-094 FR18 (DEC-8) requires the derived value to be "intersected with the element's own author touch-action and … never wider than it", and the author's value wins when it leaves out pinch-zoom: here an author's none becomes manipulation under a tap, and an author's pan-x pan-y becomes pan-y pinch-zoom under a horizontal pan.

// Pointer Events only: a finger comes from its pointer events alone. A
// browser that has Touch Events still sends them for the same finger, and
// reading both would track it twice (a long press needs one touch).
if (this.inputMode === 'auto') {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

With inputMode: 'pointer-events' no Touch Event listener is registered, so touchIds stays empty and cancelIfClaimed never cancels: a claimed pan's touchmoves and touchend go through, and the browser can scroll under it (spec:AST-094 FR19, FR21). Map a finger's touches to its pointer-fed arena id for the claim in this mode, or drop the option until a caller needs it.

if (
this.isInstalled ||
this.groupPressPointer != null ||
!isPointerKind(event.pointerType) ||

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Under a fine primary pointer this also accepts touch, so a finger on an action group is tracked by the group's recognizers, while current spec:AST-094 FR19 says that where the primary pointer is fine "no recognizer attaches and no touch listener is registered". The family:action-groups FR5 and FR19/FR24 exception this path relies on exist only in the open spec pull request #8622, so this path waits on an engineering owner's decision.

vjeux added a commit to vjeux/astryx that referenced this pull request Oct 27, 2026
…t stream

Ports cixzhang's meta#8072 action-group press (d2c3c78) onto this pull
request's shared input stream, with no change to what it does. Its
per-press listeners were registered straight on the document and the
window with capture constants this layer no longer has, so the merged host
failed to compile, and its window click listener would have doubled the
input stream's click subscriber (two onClick calls per click once the host
installs, and a listener its guard never removed).

The press now subscribes its pointer's moves and lift and the window's own
blur through subscribeInput for as long as it is held. It subscribes to
click only while the host is not installed, drops that subscription after
the lift's task, and an installing host takes it over, so a click is heard
once. Her two host tests read the press's listeners on the window, where
the stream registers them; a new case counts onClick per click before and
after the host installs mid-press, and fails against each of three mutants
(no takeover, no drop, no subscription).
…, and under inputMode 'pointer-events' a claimed finger's touchmoves are cancelled

- spec:AST-094 FR20 and DEC-8: a native scroll above an unclaimed finger
  counts as the browser taking it only once that finger has travelled the
  take distance (10 px) from where it landed. A scroll under a still finger
  (scroll anchoring, a carousel advancing, a transcript pinned to its end)
  is the page's own, and the touch and its click stay. (The same rule
  meta#8309 carried, moved down to the host that introduces it.)
- spec:AST-094 FR19 and FR21 under inputMode 'pointer-events': a finger
  comes from its pointer events, so its touches carried no id of ours and a
  claim never cancelled them. The blocking touchmove listener now matches a
  touch to the nearest live pointer-fed finger on its target, so a claimed
  pan's moves are cancelled and the browser cannot scroll under it.

Tests (failing first): 9 px and (7, 7) are not taken, 10 px and (8, 8) are,
a still finger is not; the FR30 own-scroll case moves its finger first; under
'pointer-events' a pan's touchmoves pass before its claim and are cancelled
after it.
…es its recognizers, and the group keeps answering afterwards

Under a fine pointer the host is not installed, so usesTouchEvents keeps
its initial true, and a group press's own touch pointermove and pointerup
went down the touch-pairing path and returned: the finger's moves and lift
never reached the group, its contact stayed in the arena, and the group
answered no press after it, a mouse's included. The group press now feeds
its own events through the pointer path for every pointer type.

Test (failing first): under a fine pointer a finger tap on a group
recognizes, and a mouse tap afterwards does too.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This pull request is on hold for two spec decisions: the gesture host counts only a finger's first uncancelable touchmove, and never a claimed touch's, as the browser taking it (GestureHost.ts lines 844-879), while the current spec:AST-094 FR20 counts every uncancelable move; and the action-group path (lines 1328-1484) runs recognizers under a fine primary pointer on the strength of family:action-groups FR5 and an FR19/FR24 exception that exist only in the open spec pull request #8622. An engineering owner needs to decide whether FR20 is amended on main or the host follows it, and whether #8622's record and exception become current or the action-group path is dropped, and records that decision by commenting /approve-spec <full head SHA> on this pull request; the author may do so as an engineering owner. The newest commits bring the scroll take distance in line with FR20, and independently of the spec decisions these changes are still needed:

  • Intersect the derived touch-action with the author's instead of writing over it (FR18 and DEC-8, lines 524-555).
  • Keep the newest ownScroll call's exemption (FR30), import only UIKIT_PRESS and UIKIT_CLICK (INV6), gate the three listeners on isEnabled and claimsTouches (FR19, FR20, FR24), and cancel a claimed finger's touchend under inputMode: 'pointer-events' (FR21).
  • Land #8071 first, split or justify the host's 3,244 lines, and add the real-touch browser tests AST-094 names for FR18–FR22.

[Automated review]

// Pointer Events only: a finger comes from its pointer events alone. A
// browser that has Touch Events still sends them for the same finger, and
// reading both would track it twice (a long press needs one touch).
if (this.inputMode === 'auto') {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Under inputMode: 'pointer-events' this branch registers no Touch Event listener and watchTarget never runs, so although a claimed finger's touchmoves are now cancelled through pointerFingerAt, nothing cancels its touchend: in a probe a claimed pan's lift goes through uncancelled, against spec:AST-094 FR21 (“its touchend MUST be cancelled, and no click or compatibility mouse event reaches the page”); the swallower armed at the pointerup stops only the click. Cancel the claimed finger's touchend from the element listener in this mode, with a test beside the new touchmove one, or drop the option until a caller needs it.

this.feedMoves(inputs);
};

/**

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This doc comment describes cancelIfClaimed, but pointerFingerAt and its own comment now sit between them, so it heads the wrong method and cancelIfClaimed (line 921) has none; move it down to that method.

@cixzhang

Copy link
Copy Markdown
Contributor

Posted by Cindy's agent on her behalf.

Cindy's decision on gestures on the web: gestures attach for every pointer, not only touch.

  • Stays off: mouse swipe actions (they'd take over text selection and native drag), pen pinch, edge pan on web inputs, and the 44 px touch target, which stays touch-only and never overlaps a neighbor.
  • Web adaptations: Pan takes wheel and trackpad scroll; Pinch takes ctrl+wheel. A shared "context request" input covers long-press, right-click and Shift+F10. The press tracker gets a fine-pointer profile with no delay, and reorder starts on a handle drag without a hold.
  • The gesture host drops its touch-only query, and pointer queries move into the environment queries. Desktop listeners attach only where a component uses a gesture, and no new code ships for that.

This supersedes the earlier exclusion of mouse press paint. The spec:AST-094 amendment follows in its own spec PR, and the gesture-host PRs conform to it.

…uchend is cancelled too

CodeBunny on 5c59af1: a claimed pan's touchmoves were cancelled, but its
lift went through (spec:AST-094 FR21). Where a finger comes from its
pointer events, the host notes where a claimed finger's pointerup landed,
and the element's blocking touchend listener (registered only in this mode,
where the primary pointer is coarse and a recognizer takes touches)
cancels the touchend at that place on that target. The cancelIfClaimed doc
comment moves back above its method.

Test (failing first): a claimed pan's touchend is cancelled; an unclaimed
tap's is not.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This pull request is on hold for two engineering-owner decisions: the gesture host counts only a finger's first uncancelable touchmove, and never a claimed touch's, as the browser taking it (GestureHost.ts lines 874-909), while the current spec:AST-094 FR20 counts every one; and the action-group path (lines 1383-1539) runs recognizers under a fine primary pointer, which current FR19 and FR24 rule out until the every-pointer amendment (#8646) or family:action-groups (#8622) lands. An engineering owner records each decision by amending the record on main or by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner. Independently of those decisions, these changes are needed:

  • Intersect the derived touch-action with the element's author value instead of writing over it (FR18, DEC-8).
  • Give a one-edge screen-edge pan its pan-x claim on an element within the edge band and a 10 px start on a wider element (FR17-FR19, DEC-13, DEC-22).
  • Keep the newest ownScroll call's exemption (FR30), re-sync listeners on every detach (FR24), gate the target listener on isEnabled (FR19), and import only UIKIT_PRESS and UIKIT_CLICK (architecture:component-style-authoring INV6).
  • Land #8071 and #8061 first, split or justify the host's 3,367 lines, and add the real-touch browser tests AST-094 names for FR18-FR22.

[Automated review]

Comment on lines +63 to +66
override get nativeTouchBehavior(): NativeTouchBehavior {
if (this.edges.length === 1) {
return ALL_NATIVE_TOUCH;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

For one edge this leaves the browser every touch behavior, and the constructor (lines 47-52) always starts the pan at the table's 15 px. Current spec:AST-094 FR18 has a screen-edge pan claim pan-x in the 40.2 px band on an element within the band and start there at 15 px, and FR17 and FR19 (DEC-13, DEC-22) have it start at 10 px on an element wider than the band. Deriving both from whether the attached element lies within the band, and updating ScreenEdgePanGestureRecognizer.test.ts:61-69, browserNegotiation.test.ts:68-73 and arenaPinchSwipeEdge.test.ts:50-75, would bring it in line.

@vjeux

vjeux commented Oct 30, 2026

Copy link
Copy Markdown
Contributor Author

Folded into #8071 under the section plan: #8071 now holds this change together with its own, on main after #8061, as one commit that credits this PR's authors. Closing; the branch stays.

This branch was successfully deployed

1 active deployment
Preview — 2b567a3f Deployed Oct 27, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Meta Open Source bot. needs:design-review Affects visuals — Design should review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants