Repository navigation
Conversation
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.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
AI review status for this pull request.
|
From CodeBunny's review at 5b7d1eb (spec:AST-094 FR23): with the hidden switch taking a label's activation, a tap on the label of a disabled control still toggled the switch (playing the haptic) and, since the disabled control cannot take focus, left focus on the aria-hidden switch. The switch's click is now cancelled for a disabled or aria-disabled control, and its focus always moves on: to the control when it can take it, else back where it was, else nowhere. The new test fails on the previous head.
… main) into UIControl press tracking
…hing is attached From CodeBunny on meta#8073 (spec:AST-094 FR24), fixed once here: the (pointer: coarse) change listener was added when the host was created, on a fine pointer too, and never removed. It is now added with the first attachment and removed when the last one goes, so nothing touch-related remains after the last instance unmounts. The test fails on the previous head (a listener at creation, none removed).
…t syncs, and the stale headers and changesets match the code - useGestureRecognizer and usePressTracker re-attach when the element behind the ref is replaced and detach when it goes, checked after every render at no render cost (architecture:react-component-runtime INV6); each Effect names the system it syncs (INV3). - A control's profile releases on the deselect clock the shared press styles play, so a painter's `fading` never outlasts the fade it shows; UIButton's 250 ms arrives with a control's own release layer. - PressTracker's and GestureHost's headers describe the cell's delay inside scroll views only, and no delegated consumers or scroll handoff. - One changeset for the press tracking, the one press writer and the hooks. Co-authored-by: Christopher Chedeau <vjeux@users.noreply.github.com>
…ing is attached) into UIControl press tracking The host has no delegated consumers at this layer, so the listener follows the registry alone.
…egan-elsewhere; a recognizer's own scroll is not the browser taking the finger From CodeBunny on meta#8195, fixed once here where the host enters: - spec:AST-094 FR28: a touch with no recognizer or consumer on its path never reached the arena, so a recognizer tracking a finger was never told about a second finger beside it (a menu press could not end on one). GestureArena.untrackedBegan reports such a touch to recognizers that are tracking touches; it creates no record, claim or lock, and its moves and end stay the page's. - spec:AST-094 FR30: a recognizer that scrolls a scroller itself (a menu's edge autoscroll) was cancelled by its own write. The internal GestureHost.ownScroll(scroller) makes onScroll skip that scroller until two frames pass; a real pan in that window still reports itself through pointercancel or touchcancel. Three tests; two fail on the previous head.
…s a dependency useGestureRecognizer.ts: meta#8073's node attachment (a disabled gesture is not attached, a replaced node re-attaches), its host listener sync and the scroll-brake default, plus meta#8074's own changes from c248459: the isEnabled sync keeps the listener quiet (architecture:react-component-runtime INV2) and a change to the press tracker's four build options rebuilds it from the next touch. The tests keep both sides' cases.
There was a problem hiding this comment.
Requesting changes. The browser proof and the README are a useful base, and the disabled-recognizer and fixed-bar issues from the last round are fixed, but this branch's diff against main still carries the four open pull requests beneath it (#8061, #8071, #8072, #8073), and two parts disagree with the current specs. Please land the predecessors first so this pull request is reviewed as its own commits, and:
- Conform the host's uncancelable-
touchmoverule (GestureHost.ts) tospec:AST-094FR20, or have an engineering owner amend FR20 onmainto the narrower rule first. - Rewrite the README passages that no longer match the code: the velocity strategies (
spec:AST-100FR2), the removedinstallPressAttribute/usePressAttribute, the press profiles,axisRatio, the one-edge pan'stouch-actionand the 744 px compact width (spec:AST-012FR9), and fix the fixture header's spec name.
[Automated review]
|
|
||
| - `control` (a button, a switch): the highlight comes on in the same frame | ||
| outside a scroller, the finger is tracked 70 pt beyond the bounds, and | ||
| the release takes UIButton's 0.25 s; |
There was a problem hiding this comment.
Both profiles release on timing.press.highlightOutMs, the 200 ms press fade (PressTracker.ts:120-141), not UIButton's 0.25 s; the header comment there says a control's own 250 ms release is still to come.
| outside a scroller, the finger is tracked 70 pt beyond the bounds, and | ||
| the release takes UIButton's 0.25 s; | ||
| - `cell` (a table or collection row, always in a scroll view in UIKit): the | ||
| highlight always waits the content-touch delay, travel past the pan |
There was a problem hiding this comment.
The cell profile is highlightDelay: 'inScrollers': it highlights at once outside a scroll view and waits the content-touch delay only inside one, as spec:AST-094 FR11 requires ("the 150 ms delay applies only inside scroll views"). "Always waits" no longer describes it.
| ### Rows and cards: delegated press tracking | ||
|
|
||
| A list of a thousand rows cannot pay for a hook and four listeners per row. | ||
| `installPressAttribute(document)` (or the `usePressAttribute()` hook) finds |
There was a problem hiding this comment.
installPressAttribute, usePressAttribute and __tests__/delegatedPress.test.ts (line 400) do not exist at this head: the delegated consumer was removed. This section, and the attribute-pair warning at the end of it, can go or be rewritten around paintPress.
| overshoot. A dragged surface does not jump. `totalTranslation()` is the | ||
| movement since the fingers landed, for a surface that wants it. | ||
| - `axisRatio` (for a pan limited to an axis): the movement along the axis | ||
| must exceed `axisRatio` times the movement across it to begin; movement |
There was a problem hiding this comment.
An axis-limited pan that is still between axisRatio's lines at its 10 px begin fails (PanGestureRecognizer.ts:329-334), as spec:AST-094 FR19 requires a pan that competes with native scrolling to decide within 10 px; it no longer waits.
| | Recognizers on the element | `touch-action` | | ||
| | -------------------------------------------- | ------------------- | | ||
| | tap, long press | `manipulation` | | ||
| | horizontal pan or swipe, horizontal edge pan | `pan-y pinch-zoom` | |
There was a problem hiding this comment.
A screen-edge pan from one edge (the default ['leading']) leaves touch-action as is (ScreenEdgePanGestureRecognizer.ts:63-66, browserNegotiation.test.ts:62), as FR18 asks for a one-way pan; only pans from several horizontal edges get pan-y pinch-zoom.
Main merged meta#8045 and meta#8046. The one conflict is utils/pressFeedback.ts: it takes main's merged meta#8046 text (the finger and button slots, the mouse and key press) and keeps this pull request's own additions, the one writer of the press attribute (writePress, paintPress, readPressPaint) and the header line naming them.
Main's button slot, merged with meta#8046, ends a press's release at once when the next press starts, by clearing the press attribute directly. On a row another painter holds, that clear erased the hold, which this pull request's one writer refuses. A mouse press on a held row, released, then a press elsewhere must leave the row held. Fails until the early end goes through the one writer.
finish() writes none through writePress, as this pull request's controller paints, so a held row stays its owner's when the next press ends a release early, under the finger and the button slots alike.
There was a problem hiding this comment.
Requesting changes: the browser proof and README are a useful base and all 13 Chromium checks pass, but the gesture host they prove no longer matches the current spec:AST-094 on main, and the diff still carries the four open pull requests beneath it.
GestureHost.tslistens only where the primary pointer is coarse, while FR43 andarchitecture:react-component-runtimeINV13 have gestures attach for every pointer, so a mouse, trackpad or desktop pen gets no gesture.- The host writes
touch-actionover the author's value (FR18, DEC-8), lets any scroll under a still finger cancel its tap (FR20, DEC-8), and counts only a finger's first uncancelabletouchmove(FR20), and the screen-edge pan starts at 15 px on elements wider than the edge band (DEC-22). - The README's velocity, compact-size, press-profile and delegated-press passages describe behavior the code,
spec:AST-100andspec:AST-012do not have.
Please conform these, or have an engineering owner amend FR20 or FR43 on main first, and land #8061, #8071, #8072 and #8073 so this diff is its own nine files.
[Automated review]
| } | ||
|
|
||
| private shouldListen(): boolean { | ||
| if (!this.isPrimaryPointerCoarse()) { |
There was a problem hiding this comment.
Current spec:AST-094 FR43 has gestures attach for every pointer, "not only a coarse primary pointer", and architecture:react-component-runtime INV13 says the host attaches "with no touch-only query; under a fine pointer only a component that uses a gesture listens". With this gate a mouse, trackpad or desktop pen never reaches a recognizer (a mouse drag and a hover produce no transition in Chromium).
| element.style.getPropertyValue(property), | ||
| ); | ||
| } | ||
| element.style.setProperty(property, value); |
There was a problem hiding this comment.
FR18 (DEC-8): "The derived value is intersected with the element's own author touch-action and is never wider than it". This writes the derived value over the author's, so touch-action: none plus a horizontal pan computes pan-y pinch-zoom.
| } | ||
| } | ||
| if (taken.length > 0) { | ||
| this.arena.browserTookOver(taken, time); |
There was a problem hiding this comment.
FR20 (DEC-8) counts a native scroll as the browser taking the touch "only once that finger has moved the scroll take distance" (10 px). Here any scroll above an unclaimed touch takes it at any travel, so a 1 px scroll under a still finger fails its tap.
| constructor(options: ScreenEdgePanGestureRecognizerOptions = {}) { | ||
| super({ | ||
| maximumNumberOfTouches: 1, | ||
| hysteresis: UIKIT_SCREEN_EDGE_PAN.hysteresis.value, |
There was a problem hiding this comment.
FR18 and DEC-22 keep the 15 px start only for an element within the 40.2 px band; on a wider element an in-band touch decides at 10 px. This hysteresis applies to every element (a full-width element's edge pan begins at 15 px).
| first), and the velocity is 0.2 times the newest one plus 0.8 times the | ||
| one before. It reproduces every value UIKit reported on six recorded touch | ||
| streams. Like UIKit's, it reacts a frame late to an acceleration, includes | ||
| a hold before the first move, and never decays: a finger at rest sends no |
There was a problem hiding this comment.
VelocityTracker has one filter that releases at 0 after the stillness interval, as spec:AST-100 FR2 and DEC-1 require; the four strategies and the "never decays" reading described here do not exist.
| ### Rows and cards: delegated press tracking | ||
|
|
||
| A list of a thousand rows cannot pay for a hook and four listeners per row. | ||
| `installPressAttribute(document)` (or the `usePressAttribute()` hook) finds |
There was a problem hiding this comment.
installPressAttribute and usePressAttribute do not exist at this head (the delegated consumer was removed), so this section documents a call that does not compile.
Takes meta#8360's head, which merges first in her order and is the reconciled gesture foundation (meta#8076, meta#8073, meta#8446 and meta#8074). Two of its test files, the presented hit-testing fixture and the animator browser spec, name the internal animator createAnimator; this pull request renamed it createMotionAnimator, so they take that name here. Without it, main would stop type-checking when this lands after meta#8360.
meta#8360's change on the internal touch foundation: one click guard shared by the menu press and the gesture host (its click listener stays once the menu press arms it, as its record has it, and lasts only while armed for the gesture host, FR24), the host as the one touch owner (finger scroll, page holds, attachments, the press tracker hook and a held control's kept touch, the long press's single source, presented hit testing on iOS WebKit), and the gesture browser proof in Chromium and WebKit with the README. It carries meta#8074, meta#8270, meta#8293, meta#8315, meta#8320 and meta#8356, and folds in meta#8358 (the long press's tolerance comment and an unused import). Rebased onto section 2 as one commit; the touch-action rule now comes with meta#8071, and meta#8072's claimed-touchend fix, which this line never had, comes back with meta#8309. The animator's busy proof is judged against the harness's compositor-only control (meta#8906), with a main-thread mutant as its negative, and the spring's curve is placed against the control's own clock, so the late frames a loaded runner sends fail nothing. With the host serving every pointer (meta#8071), this change's own pieces follow it: a press tracker takes a mouse press as a press, painting at once and braking no scroll (FR11); a finger's or a pen's hold that does something keeps text unselected under any primary pointer, while a mouse keeps its released behavior (FR41); a claimed pen's stylus touchend is cancelled as a finger's (FR21); and `perEventPointer`, from before FR43, changes nothing. These come from meta#8665, which keeps only the undecided rule for text selection during a mouse or pen drag. The hover recognizer and the host's hover routing land with hover's first user. Co-authored-by: Cindy Zhang <cindyxz@meta.com>
meta#8360's change on the internal touch foundation: one click guard shared by the menu press and the gesture host (its click listener stays once the menu press arms it, as its record has it, and lasts only while armed for the gesture host, FR24), the host as the one touch owner (finger scroll, page holds, attachments, the press tracker hook and a held control's kept touch, the long press's single source, presented hit testing on iOS WebKit), its cases in the gesture browser proof (Chromium and WebKit) and the README. It carries meta#8074, meta#8270, meta#8293, meta#8315, meta#8320 and meta#8356, and folds in meta#8358 (the long press's tolerance comment and an unused import). Rebased onto section 2 as one commit; the touch-action rule now comes with meta#8071, and meta#8072's claimed-touchend fix, which this line never had, comes back with meta#8309. The animator's busy proof is judged against the harness's compositor-only control (meta#8906), with a main-thread mutant as its negative, and the spring's curve is placed against the control's own clock, so the late frames a loaded runner sends fail nothing. With the host serving every pointer (meta#8071), this change's own pieces follow it: a press tracker takes a mouse press as a press, painting at once and braking no scroll (FR11); a finger's or a pen's hold that does something keeps text unselected under any primary pointer, while a mouse keeps its released behavior (FR41); and `perEventPointer`, from before FR43, changes nothing. These come from meta#8665, which keeps only the undecided rule for text selection during a mouse or pen drag. The hover recognizer and the host's hover routing land with hover's first user. Co-authored-by: Cindy Zhang <cindyxz@meta.com>
meta#8360's change on the internal touch foundation: one click guard shared by the menu press and the gesture host (its click listener stays once the menu press arms it, as its record has it, and lasts only while armed for the gesture host, FR24), the host as the one touch owner (finger scroll, page holds, attachments, the press tracker hook and a held control's kept touch, the long press's single source, presented hit testing on iOS WebKit), its cases in the gesture browser proof (Chromium and WebKit) and the README. It carries meta#8074, meta#8270, meta#8293, meta#8315, meta#8320 and meta#8356, and folds in meta#8358 (the long press's tolerance comment and an unused import). Rebased onto main after section 2 as one commit; the touch-action rule came with meta#8071. Where fingers come from their pointer events, a claimed finger's touchend is cancelled from its element (FR21), under a fine primary pointer wherever the browser sends Touch Events and inside an open shadow root too. The animator's busy proof is judged against the harness's compositor-only control (meta#8906), with a main-thread mutant as its negative, and the spring's curve is placed against the control's own clock, so the late frames a loaded runner sends fail nothing. With the host serving every pointer (meta#8071), this change's own pieces follow it: a press tracker takes a mouse press as a press, painting at once and braking no scroll (FR11); a finger's or a pen's hold that does something keeps text unselected under any primary pointer, while a mouse keeps its released behavior (FR41); and `perEventPointer`, from before FR43, changes nothing. These come from meta#8665, which keeps only the undecided rule for text selection during a mouse or pen drag. The hover recognizer and the host's hover routing land with hover's first user. Co-authored-by: Cindy Zhang <cindyxz@meta.com>
meta#8360's change on the internal touch foundation: one click guard shared by the menu press and the gesture host (its click listener stays once the menu press arms it, as its record has it, and lasts only while armed for the gesture host, FR24), the host as the one touch owner (finger scroll, page holds, attachments, the press tracker hook and a held control's kept touch, the long press's single source, presented hit testing on iOS WebKit), its cases in the gesture browser proof (Chromium and WebKit) and the README. It carries meta#8074, meta#8270, meta#8293, meta#8315, meta#8320 and meta#8356, and folds in meta#8358 (the long press's tolerance comment and an unused import). Rebased onto main after section 2 as one commit; the touch-action rule came with meta#8071. Where fingers come from their pointer events, a claimed finger's touchend is cancelled from its element (FR21), under a fine primary pointer wherever the browser sends Touch Events and inside an open shadow root too; at most eight such lifts wait for their touchend, and each goes after 500 ms or with the host (FR24). The animator's busy proof is judged against the harness's compositor-only control (meta#8906), with a main-thread mutant as its negative, and the spring's curve is placed against the control's own clock, so the late frames a loaded runner sends fail nothing. With the host serving every pointer (meta#8071), this change's own pieces follow it: a press tracker takes a mouse press as a press, painting at once and braking no scroll (FR11); a finger's or a pen's hold that does something keeps text unselected under any primary pointer, while a mouse keeps its released behavior (FR41); and `perEventPointer`, from before FR43, changes nothing. These come from meta#8665, which keeps only the undecided rule for text selection during a mouse or pen drag. The hover recognizer and the host's hover routing land with hover's first user. Co-authored-by: Cindy Zhang <cindyxz@meta.com>
meta#8360's change on the internal touch foundation: one click guard shared by the menu press and the gesture host (its click listener stays once the menu press arms it, as its record has it, and lasts only while armed for the gesture host, FR24), the host as the one touch owner (finger scroll, page holds, attachments, the press tracker hook and a held control's kept touch, the long press's single source, presented hit testing on iOS WebKit), its cases in the gesture browser proof (Chromium and WebKit) and the README. It carries meta#8074, meta#8270, meta#8293, meta#8315, meta#8320 and meta#8356, and folds in meta#8358 (the long press's tolerance comment and an unused import). Rebased onto main after section 2 as one commit; the touch-action rule came with meta#8071. Where fingers come from their pointer events, a claimed finger's touchend is cancelled from its element (FR21), under a fine primary pointer wherever the browser sends Touch Events and inside an open shadow root too; at most eight such lifts wait for their touchend, and each goes after 500 ms or with the host (FR24). The animator's busy proof is judged against the harness's compositor-only control (meta#8906), with a main-thread mutant as its negative, and the spring's curve is placed against the control's own clock, so the late frames a loaded runner sends fail nothing. With the host serving every pointer (meta#8071), this change's own pieces follow it: a press tracker takes a mouse press as a press, painting at once and braking no scroll (FR11); a finger's or a pen's hold that does something keeps text unselected under any primary pointer, while a mouse keeps its released behavior (FR41); and `perEventPointer`, from before FR43, changes nothing. These come from meta#8665, which keeps only the undecided rule for text selection during a mouse or pen drag. The hover recognizer and the host's hover routing land with hover's first user. Co-authored-by: Cindy Zhang <cindyxz@meta.com>
meta#8360's change on the internal touch foundation: one click guard shared by the menu press and the gesture host (its click listener stays once the menu press arms it, as its record has it, and lasts only while armed for the gesture host, FR24), the host as the one touch owner (finger scroll, page holds, attachments, the press tracker hook and a held control's kept touch, the long press's single source, presented hit testing on iOS WebKit), its cases in the gesture browser proof (Chromium and WebKit) and the README. It carries meta#8074, meta#8270, meta#8293, meta#8315, meta#8320 and meta#8356, and folds in meta#8358 (the long press's tolerance comment and an unused import). Rebased onto main after section 2 as one commit; the touch-action rule came with meta#8071. Where fingers come from their pointer events, a claimed finger's touchend is cancelled from its element (FR21), under a fine primary pointer wherever the browser sends Touch Events and inside an open shadow root too; at most eight such lifts wait for their touchend, and each goes once taken or after 500 ms, kept across the host's uninstall since the touchend comes in a later task (FR24). The animator's busy proof is judged against the harness's compositor-only control (meta#8906), with a main-thread mutant as its negative, and the spring's curve is placed against the control's own clock, so the late frames a loaded runner sends fail nothing. With the host serving every pointer (meta#8071), this change's own pieces follow it: a press tracker takes a mouse press as a press, painting at once and braking no scroll (FR11); a finger's or a pen's hold that does something keeps text unselected under any primary pointer, while a mouse keeps its released behavior (FR41); and `perEventPointer`, from before FR43, changes nothing. These come from meta#8665, which keeps only the undecided rule for text selection during a mouse or pen drag. The hover recognizer and the host's hover routing land with hover's first user. Co-authored-by: Cindy Zhang <cindyxz@meta.com>
meta#8360's change on the internal touch foundation: one click guard shared by the menu press and the gesture host (its click listener stays once the menu press arms it, as its record has it, and lasts only while armed for the gesture host, FR24), the host as the one touch owner (finger scroll, page holds, attachments, the press tracker hook and a held control's kept touch, the long press's single source, presented hit testing on iOS WebKit), its cases in the gesture browser proof (Chromium and WebKit) and the README. It carries meta#8074, meta#8270, meta#8293, meta#8315, meta#8320 and meta#8356, and folds in meta#8358 (the long press's tolerance comment and an unused import). Rebased onto main after section 2 as one commit; the touch-action rule came with meta#8071. Where fingers come from their pointer events, a claimed finger's touchend is cancelled from its element (FR21), under a fine primary pointer wherever the browser sends Touch Events and inside an open shadow root too; at most eight such lifts wait for their touchend, and each goes once taken or after 500 ms, kept across the host's uninstall, which can run in the microtask after the pointerup, before the touchend that comes later in the same task (FR24). An armed press's page listeners go once the arena has decided its last touch, a tap that waits on a double tap included, and after its last detach the host holds no listener, note, frame or timer (FR24). The animator's busy proof is judged against the harness's compositor-only control (meta#8906), with a main-thread mutant as its negative, and the spring's curve is placed against the control's own clock, so the late frames a loaded runner sends fail nothing. With the host serving every pointer (meta#8071), this change's own pieces follow it: a press tracker takes a mouse press as a press, painting at once and braking no scroll (FR11); a finger's or a pen's hold that does something keeps text unselected under any primary pointer, while a mouse keeps its released behavior (FR41); and `perEventPointer`, from before FR43, changes nothing. These come from meta#8665, which keeps only the undecided rule for text selection during a mouse or pen drag. The hover recognizer and the host's hover routing land with hover's first user. Co-authored-by: Cindy Zhang <cindyxz@meta.com>
Depends on #8073 (and its predecessors); review this PR's own commits (the merges bring main and the stack in).
Primary intent
Prove the gesture system in real browser engines and document it: the same touch scripts run in Chromium and WebKit, and
gestures/README.mdgives component authors the API, UIKit semantics, the browser negotiation and the web differences.User or maintainer impact
jsdom cannot show what a browser does with a cancelled
touchmove, atouch-actionvalue or a native pinch, and migrating components need a document.Problem and solution fit
Negotiation lives in the browser, outside jsdom, so a regression ships as scroll-jank, ghost clicks or lost zoom, which touch users notice first.
Deltas:
__tests__/browser/:README.md: recognizers, the pan's UIKit numbers, the arena, press profiles and paint, what the browser is told (including the early claim), native scrolling, the event clock, velocity, haptics, the touch presentation (feat(core): one touch presentation resolver #8062), known limits, web differences.Authority
spec:AST-094FR9–FR23 (current); FR14: a drag never changes hands midway. The README describes the system in the present tense and cites the constants table for every number.Change and evidence
Both engines check:
touch-actionper element;pan-ycontrol keeping its press sideways;touchmovelisteners only where a recognizer claims a moving touch;Chromium also checks that a drag begun mid-content stays the content's to its top, the sheet never beginning (FR14), and page pinch-zoom, which Linux WebKit cannot drive. The fixture passed in Mobile Safari 26.2 (iOS 26.2 simulator), native scroll and pinch included.
Scope
Testing
The second commit answers two review points on the gesture hooks, with failing-first tests in
useGestureRecognizer.test.tsx:isEnabledoff mid-gesture ends the gesture without calling the listener from the layout Effect (architecture:react-component-runtimeINV2); a handler that callsrecognizer.setEnabled(false)itself still hearscancelled, from that handler;usePressTrackerapplies a changedtrackingExpansion,highlightDelay,cancelTravelorquickTapFlashfrom the next touch, and forwards the caller'sisBraking.tscand strict ESLint clean.pnpm exec playwright test --config packages/core/src/gestures/__tests__/browser/playwright.config.ts --project chromium: 13 passed.WebKit in
mcr.microsoft.com/playwright:v1.61.1-noble, same config,--project webkit: 10 passed, 3 skipped.pnpm exec vitest run --project ui packages/core/src/gestures packages/core/src/motion: 318 tests passed.pnpm check:changesets, changeset coverage: clean.Size: 6 files, 1,321 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.