Repository navigation
Conversation
…an the edge band decides at 10 px again, DEC-22) into the experimental touch exports
…t reads its end before the arena's reset) into facebook#8336
The case walked ContextMenu, DropdownMenu, useLongPress and useMenuPress's imports and failed on the UIKit gestures table: it kept that table out of the menus' bundles, the lazy design the no-lazy-load ruling and direct imports retire. The long-press numbers keep one source, still pinned by the table, recognizer and hooks case and by the hooks reaching longPress.ts.
…xposes no ontouchstart is taken once Chromium fixes ontouchstart when the page is created, so turning touch emulation on later sends Touch Events without it: the host reads Pointer Events for the finger and must not read its Touch Events too. A one-finger long press fails here, the finger tracked twice.
…from their pointer events install() reads a finger from its pointer events when the window has no ontouchstart, but the four touch handlers still took the Touch Events a browser sends anyway (Chromium after touch emulation turns on), tracking the finger twice, so a one-finger long press never recognized. Each touch handler now returns while usesTouchEvents is false, after noting the fingers down, as install()'s comment promised.
…pointer events) into facebook#8320
…ter events) into meta#8293
…ter events) into meta#8356
…ter events) into meta#8358
…n before the click guard internal/motion.ts (meta#8562) re-exports UIKIT_CLICK from the barrel, so a tree carrying both fails typecheck and the Storybook build since this pull request dropped it. The barrel keeps it, with the click guard's window, and the table of every gesture group still leaves it out. Fails until the barrel exports it.
…'s window This pull request dropped UIKIT_CLICK when the click guard took the stray-click window over, but importers written before it read the barrel's UIKIT_CLICK (internal/motion.ts in meta#8562 re-exports it), so a tree carrying both failed typecheck and the Storybook build. UIKIT_CLICK stays in the barrel, deprecated, with the guard's 400 ms (CLICK_GUARD_WINDOW_MS), and UIKIT_GESTURES still leaves it out.
… the selection hold change's base
…election hold change's base
…ter proofs
spec:AST-094 FR43 has gestures attach for every pointer, so each FR43 proof needs a pen and a wheel as real engine input, not only a finger. webInputDriver(page) adds wheel({x, y, dx, dy, ctrl}) and pen(points): in Chromium through CDP Input.dispatchMouseEvent, with a wheel step's deltas scaled back so the page receives exactly its CSS px (Chromium divides them by the device scale factor times the visual viewport's scale: a raw 120 px step reached a phone page as 40), and a pen stroke with pointerType pen, pressure, tilt and scripted timestamps; in WebKit through page.mouse.wheel in a desktop context. WebKit's phone emulation has no wheel and Playwright has no pen for WebKit: the driver's wheels and pens flags say so, with WHEEL_SKIP and PEN_SKIP. The input log also records wheel steps and pointer pressure.
… attached, in this host's terms meta#8360's action-group lifetime case pins that an element stays a group while any attachment asks and stops with the last. Here every element that uses a gesture is served as a group under a fine pointer, so the same invariant reads as the element's one passive pointerdown: with two gestures attached, detaching either keeps it, and detaching the last removes it. It passes on this head.
meta#8360 carries this pull request's predecessors and the review fixes of the foundation it holds; this pull request's own diff is now its six commits (a second finger fails a one-finger pan wherever fingers come from, and the check reruns once that finger is delivered; a press's selection lock gets what it landed on; a pinch's second finger on another pan claims neither; the wider-band edge pan's real-touch proof; text). Two conflicts: - GestureHost.ts: the selection lock's target takes meta#8360's presented target, which is what the press landed on unless an element under it is moving, so it keeps this pull request's fix and meta#8293's correction. - .changeset/gesture-press-tracking.md: meta#8360's text, which names both writers of the press attribute.
ListItem declares --_hit-border-bottom (the divider's width, 0 on the last row) for the touch hit layer, and List.doc.mjs did not list it, so derivedVarRegistry.test.ts failed: "List has undocumented CSS vars in source: --_hit-border-bottom". It is now in List.doc.mjs theming.vars[] as a private var, and in VARS_WITHOUT_DERIVED_MAPPING with its reason: it is the ::after layer's geometry, which follows --border-width, not a property a theme author sets (as with --_thumbnail-hit-inset and --_input-clear-hit-inset). Red first: "List: all source vars are in doc file" failed on the change before this one; it passes now (122 of 122).
…esture host's subscriptions The gesture host subscribes no click: the one cancelling click subscriber is the click guard's, added at a window's first arm and kept. The release note, the stream's header and the gestures README listed click with the host's touchend and contextmenu; they now name the guard's own.
… lift and the browser's menu, keeps a kept press's finger, and goes when the page hides From the review of this pull request, three cases fail on its head: - an armed press installed the page listeners without the claiming ones, so a claimed finger's touchend stayed passive and the browser's menu opened during a claimed long press (spec:AST-094 FR21); - a kept press got its blocking touchmove during an armed press, but its moves were cancelled only where the primary pointer is coarse, so a held stepper on a touchscreen laptop lost its finger (FR11, DEC-21); - a page hidden mid-press cancelled the press but kept the page's listeners until the next lift anywhere (FR24).
…lift and the browser's menu, keeps a kept press's finger, and goes when the page hides - A finger's or a pen's armed press now brings the claiming listeners (the cancelable touchend and the contextmenu canceller) with the page listeners, as wherever the host installs, so a claimed lift clicks nothing and a claimed hold opens no browser menu (spec:AST-094 FR21). A mouse's press needs neither and adds neither. - A kept press keeps its finger under any pointer: its target blocks and its moves are cancelled once its press is delivered, not only where the primary pointer is coarse (FR11, DEC-21). - A page hidden mid-press releases the armed press as a blur does, so the page's listeners go with the cancelled press (FR24).
…'s selection An action group's press is a long press with no minimum duration, so it claims at the press's pointerdown, before any movement. The drag selection hold then cleared the page's selection, so a plain click on a group's button left its Copy or Cut with nothing to act on. The case fails on this head: the selection is gone after the pointerdown.
…age's selection The drag selection hold cleared the page's selection when any mouse or pen contact was claimed, but an action group's press claims at its pointerdown, before it has moved or selected anything, so a plain click on a group's button left its Copy or Cut with nothing to act on. A claim before any movement now keeps the page's selection: the hold still refuses a new one, and puts the page's back over a selection that appears while it runs. A claim after movement clears what its drag selected, as before (spec:AST-094 FR41). The case before the new one now waits for its hold to end a task after the lift, so no hold outlives that test.
…t every pointer does - GestureHost's header, the gestures README and coarsePointer.ts no longer describe the coarse-only host: a finger or a pen takes the touch-down lock under any primary pointer, a kept press's finger is kept during a fine pointer's press too, and recognizers attach for every pointer while the page listeners last a press (spec:AST-094 FR19, FR24, FR41, FR43). - The fine-pointer selection case asserted the replaced rule and passed only because it read iOS WebKit's inline lock without iOS WebKit: it now asserts the lock a fine pointer's finger and pen take, and that a mouse takes none. - FR41's sentence and its verification row say that a press claimed at its own pointerdown, before it moves, keeps the page's selection, while a drag that moved before its claim loses what it selected.
… name their measured causes The hold proofs that need a tap's click skip in Playwright's WebKit with a reason that blamed this story. Measured against a plain button: that WebKit synthesizes no click after a scripted touch on any element, while its own tap path clicks; these proofs script the touch, so the reason now says that. The steppers cancel a touch's pointerdown, not only its mousedown. Canceling the mousedown alone was tried: iOS Safari, Chromium and WebKit's tap path then all step on the click, but Chrome on Android focuses a held stepper at its long press (focusin about 500 ms in, measured on the emulator), which would blur an editing field. So the cancel stays, and the comments name why; WebKit's Linux build, outside spec:AST-013's support, sends no click after a tap there.
…erdown name their measured causes)
…cause Its reason blamed this story. Measured: WebKit's Linux build clicks a plain button after a tap, but sends no click once the touch's pointerdown is canceled, unlike iOS Safari and Chrome. The steppers cancel it because Chrome on Android otherwise focuses a held stepper at its long press (measured on the emulator), so the proof runs in Chromium and on the iOS simulator; that engine is outside spec:AST-013's support. The comment that documents open() moves back above it.
…ion, in the present tense The sentence said a drag that moved before its claim loses the selection it started, but the hold clears the page's selection then, whatever made it; and an unclaimed mouse drag kept "today's" behavior, a phrase that ages. It now says a claim after movement clears the page's selection, a press claimed at its own pointerdown keeps it, and an unclaimed mouse drag keeps its released behavior.
This pull request now sits on meta#8665, so a stack carrying it and meta#8665's other dependants (meta#8676) needs no second GestureHost resolution. Two hunks in GestureHost.ts's pointerdown, both this pull request's: - the second-finger check where fingers come from pointer events stays, followed by meta#8665's rule that a right click, a pen's barrel button or a macOS ctrl-click starts no press; - the check that runs again once the new finger is delivered stays, followed by meta#8665's selection lock call, which no longer takes the window.
…eta#8666 08a2a28) into the web-input drivers helper The harness files are 8d35612's verified union, byte for byte, plus meta#8535's change to the backward self-test, which this line carries; the two helpers hold the same content apart from that change. This line's own harness additions leave it, as no open pull request builds on them: the earlier busy-window control and pooled proof (now meta#8906), openStory waiting for Storybook to finish a render, the touch driver's synthesizesClicks and SCRIPTED_CLICK_SKIP, and the wheel-at-DPR-3 self-test.
…events, as a browser sends them The gesture host now reads a finger from its pointer events once it has seen the pointerdown (meta#8336, through meta#8360). This suite's touch helper sent a move or a lift as a touch event alone, which no browser does, so the two slide tests lost the finger and the touched half's paint stayed on. The helper now sends pointermove or pointerup first, as a browser does; the browser proofs, which use real input, pass unchanged.
…as the repeat arms it Under a loaded machine one WebKit run of "a slide onto the other half carries the paint and the repeat" failed with its third step 68 ms off the schedule. The schedule was timed from touch-down, but the hold starts from the press's delivery and the repeat arms each beat from the moment its step ran (stepperTracking.ts). So a slow touch dispatch, or one late beat, moved every later step, and the proof charged it to each of them. Instrumented over repeated runs at load 83 to 105, WebKit's first step sat up to 38 ms after touch-down plus 500 ms, while every later interval stayed within 17 ms of its beat. Each step is now checked against its own beat: the first 500 ms after the press's first paint, which is written when the hold is armed, and each later one its interval after the step before. The leave-and-return proof's resumed beat is timed from the second step the same way. The slack per step is unchanged.
… armed-press touchmove (spec:AST-094 FR24) Where the primary pointer is fine, the gesture host now registers a kept press's blocking touchmove only once a press is armed, since that press's pointerdown comes before any touch it brings (meta#8665). The proof expected it on the idle column. It now expects the passive pointerdown alone while idle, and the blocking touchmove on the column once a press is held.
There was a problem hiding this comment.
This review is on hold for a spec decision: the every-pointer stepper press matches spec:AST-094 FR43, but this diff also rewrites component:NumberInput FR4 (the hold-repeat, with a pause-on-leave rule the code does not follow) and FR5 (the coarse-pointer capsule and focus rule) and adds claimed-drag text-selection sentences to spec:AST-094 FR41, none of them approved for this head. An engineering owner needs to approve or amend those requirements and record it 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 need changes:
- The diff against main carries the whole stack (40,009 authored lines); land #8218, #8665 and #8666 first, or retarget this PR onto them, so it shows its own seven-file change.
- A mouse holding + that drifts below the stacked steppers keeps stepping as − and commits the lower value, because a held press tracks 70 px past the column and takes the nearer half (
packages/core/src/NumberInput/NumberInputTouch.tsx, around line 286). - The coarse-pointer capsule widens the steppers to 94 px with 47 × 28-36 px halves and no hit area, which
spec:AST-094FR42 rules out for the default look.
[Automated review]
| event === 'touchDragInside' || | ||
| event === 'touchDragEnter' || | ||
| event === 'touchUpInside'; | ||
| const half: StepperHalf | null = isInside ? halfUnder(touch) : null; |
There was a problem hiding this comment.
Inside the press tracker's 70 px tracking area this takes the half whose centre is nearer, so in the stacked column a point below the column is on −. In Chromium on "With Number Steppers", a mouse holding + reached 4, then drifted 29 px below the column: the − half lit, the value counted down and the release committed 1. Head FR4 says moving off the stepper pauses the repeat. Stepping only while the pointer is over a half (and pausing outside both), or keeping the last half outside the column, would fix it; a test that holds + and moves 29 px below the column would catch it.
Depends on #8218, #8665 and #8666 (the pen driver). Review only this PR's change: vjeux/astryx@2d1f7b4...d217b02
User need
What led to it: in Astryx's touch-presentation work (
spec:AST-094), the owner decided that gestures work for every pointer, not only a finger (FR43, merged in #8646). The steppers from #8205 and #8218 still set up their press handling only on phones: a mouse took a second code path, and on a touchscreen laptop a finger got the browser's defaults, so quick taps could zoom the page and a long press selected text.Design and alternatives
Tradeoffs: a held mouse keeps stepping within 70 px of the stepper, as a finger does, instead of pausing as soon as it leaves; a click more than 400 ms after a release counts as a new press (#8218 allowed 1000 ms).
Current specification
spec:AST-094FR43, FR41, FR19, FR24, FR18 and FR11 with DEC-21;component:NumberInputunchanged. Public-API gaps (spec:AST-107FR7): press timing, the selection lock, the host's clock, UIKit's tables.Public delta and caller burden
No API change.
Testing
Open Storybook's NumberInput "With number steppers" story:
Phones behave as on #8218. Unit tests cover each pointer, and a Chromium test presses with a real pen; the new ones fail on the previous code. Two node checks fail through this stack's bases, not this change: a bundle check that #8671 replaces, and a List row check that #8085 fixes.
Evidence
Recorded on the iOS 26.2 simulator and the Android emulator.
Size (the PR size report's measure, gzip): NumberInput 78.1 → 77.7 KB, CSS unchanged.
Scope