Repository navigation
Conversation
…uard At this layer the host has no click listener of its own: the click guard (utils/clickGuard.ts) hears every click through one listener per window. cixzhang's meta#8072 action-group press, ported onto the input stream below, still disarmed the host's swallower and subscribed the host's onClick, so the merged host failed to compile. The press now disarms the click guard when it begins and subscribes no click: what it does is unchanged. The click-count case becomes: a group press adds no click listener of its own, before or after the host installs mid-press. GestureHost.test.ts resets the click guard after each case, so a guard one case armed no longer keeps its pointerdown subscription into the next (it hid the coarse host's pointerdown listener from a later case).
… that finger until it lifts (spec:AST-094 FR14) A recognition that claimed its touches and was then cancelled gave them back to the browser unless another recognition owned them, so a finger resting there could pinch-zoom the page. A gesture that cancels itself on a finger's move, a long press dragged past its tolerance after it began, gave back a finger that was dragging: the host stopped cancelling its moves and the page scrolled the rest of the drag, a hand change FR14 forbids and UIKit never makes (its scroll view never takes a touch another recognizer had). A cancel during a finger's move now keeps that finger claimed until it lifts; a resting finger is given back as before, and a waiting recognition that fails still gives its claim back. The new arena case fails on the previous code: the dragging finger reads unclaimed right after the cancel.
…ide the block and judges it against the settled mark
The busy-window proof's precondition, that the change was presented before
the block began, was too strict. Both engines' compositors can present a
change the page committed before the block a few milliseconds into it: six
probes each found it 5 ms into the block in WebKit (frames -12 rest, +5
checked, +16 checked) and 1 ms in in Chromium in three of six. With about
half the attempts rejected, three in a row failed about one run in eight
("the change was never presented before the block").
The precondition is now that the change showed by the block's end, and the
proof judges the frames on screen during the block from the change on: the
last frame before the block if it already shows the change, plus every frame
inside the block from the change. Each judged frame must also be the settled
checked colour, read after the block, so a mark frozen mid-blend during the
block fails, as "shows the checked mark, whole" says.
Evidence (local stand-in copies, not shipped): a late frame (the block
starting 300 ms after the tap) passes; a change that shows only after the
block takes the retry path and reports "the change was never presented
during the block"; a 1 s linear transition on the mark, which WebKit freezes
at 229 during the block, passed the earlier assertions and fails the settled
check (123). The proof passes 10/10 in Chromium and in WebKit.
… scroller under it is at the edge spec:AST-094 FR19 and DEC-18 (proposed in meta#8628): a scrollEdge pan may cancel its touch's moves from the first one, only while every scroller under the finger sits at its edge and the finger moves away from it, and only the moves the browser would rubber-band; it still begins at FR17's 10 px. The first case reads the first 2 px move cancelled at the edge and fails on this head; the others keep what must not change: room to scroll leaves every move to the browser, a first move toward the content too, a pan without scrollEdge cancels nothing before it begins, and a touch the browser took for a scroll stays the browser's when the list reaches its edge.
… cancelled at the edge spec:AST-094 FR19 and DEC-18 (proposed in meta#8628). The sheet's scroll-edge pan at the top of its content: every touchmove the page sees is cancelled, the first one included. WebKit sends that first move at 2 px, before the sheet's 10 px begin, and it reaches the page uncancelled on this head; Chromium sends none inside its own 15 px slop, so there it proves only that nothing scrolls.
…e away from an edge spec:AST-094 FR19 and DEC-18 (proposed in meta#8628). GestureRecognizer gains keepsTouch(touchId), false by default and reached by the host through recognizerInternals: whether it holds that finger before it begins. A scrollEdge pan does while it is possible, the touch is a finger the browser has not taken, its travel so far goes the pan's own way, and no scroller under the finger can scroll for it (the probe its begin already reads), so only moves the browser would rubber-band are held, never a real scroll. The host's blocking touchmove check cancels a move a participant holds, as it does a claimed one; the arena already holds that move when the check runs, since the host feeds it from its window-capture subscriber first. The pan still begins at 10 px; a mouse or a pen is never held. The README says so.
…straight, (7, 7) and (8, 8), and the screen-edge band The FR18–FR22 verification row's real-touch states, in the gesture browser spec on a bare page: - a finger moved 9 px straight or (7, 7) over a scroll view keeps its tap and scrolls nothing; one moved 10 px or (8, 8) is a tap or a scroll, never both; a tap never cancels a touchmove; - a screen-edge pan on a strip as wide as its band, landing 40 px from the edge, has not claimed at 14 px of travel and claims at 15; - a touch landing 41 px from the edge never begins the edge pan, and the horizontal scroll it starts is not blocked. The bare fixture exposes the tap and screen-edge pan recognizers.
…edge-zone strip decides at UIKit's 15 px The host's arena environment had no viewSize, so under the real host a screen-edge pan never saw its edge zone while a touch was down and decided at 10 px, while its attach-time touch-action (read through the default environment) had already claimed the axis (spec:AST-094 FR18). Co-authored-by: Christopher Chedeau <vjeux@users.noreply.github.com>
…fter layout rounding Lane 0's real-host browser spec found a strip authored at the 40.2 px edge region lays out at 40.203 px (layout snaps to 1/64 px), so the strict comparison read it as off the zone and the pan decided at 10 px instead of UIKit's 15 (spec:AST-094 FR18). The zone takes half a pixel of tolerance: the rounded strip is the zone, a view a pixel wider still is not. Failing-first test at 40.203 px: it claims pan-x and decides at 15. Co-authored-by: Christopher Chedeau <vjeuxx@gmail.com>
…now that a strip within half a pixel is the edge zone (meta#8061)
…, 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.
…n: pan-y only on a strip within its band, left as is on a wider view
…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.
… are all passive spec:AST-094 FR20: a watcher registers only passive listeners, but a watcher-only page still got the host's cancelable document touchend and its contextmenu and window click listeners. The document touchend is now passive until an enabled claimer (a recognizer that claims touches, or a consumer) is attached, and the contextmenu and click listeners are on only then, swapped as attachments change. The new test (a lone watcher leaves no cancelable document or window listener; a claiming pan brings the three) fails on the previous head.
…r watchers) into the host line The host keeps this PR's isRtlElement and its fingers held before a recognizer begins (a kept press, a scroll-edge pan), beside meta#8061's view size and meta#8309's pointer-events finger lookup. meta#8589's claimer check counts only participants active on this pointer (isActive, isConsumerActive), and a group press's click listener now stays while a claimer keeps it installed. The browser spec keeps the fine-pointer describe beside the FR18-FR22 real-touch states.
…inter reaches its recognizers) as a dependency
Main's meta#8521 is its head 25b468c squashed, which this pull request already carries: every file it touches is byte-identical to 25b468c. In the two files that conflicted (the motion README and animator.ts), the resolution is this side's version, main's merged meta#8521 with this pull request's later work on top. Nothing of main is dropped.
…ny primary pointer (spec:AST-094 FR41) locksSelectionFor drops its coarse-pointer gate, which merged FR41 no longer has: a finger and a pen take the lock wherever their hold does something, a touchscreen laptop's included; a mouse never does, keeping its released behavior (spec:AST-017 FR59). No pointer query decides it, so the function takes the pointer kind only. The every-pointer changeset says so.
…t without touching that view Its disarm timer outlived the view it armed on: in a full run a test file ended inside the 400 ms window, the environment tore its window down, and the timer's disarm called removeEventListener on what was left, an unhandled TypeError during a later file's test. Fails until disarm checks its view is still there.
…e selection hold at once (spec:AST-094 FR41) The hold a claimed mouse or pen drag puts on the document is released a task after the press ends, so the press's own mouseup and click, which follow a lift in the same task, still meet it. No mouseup or click follows a pointercancel, a blur or a detach, yet the hold lingered that task too: a check right after the cancel, or a test that ends there, met a page that still refused selection. Fails until a cancel releases at once.
…ing it; a cancel lifts the drag's selection hold at once (spec:AST-094 FR41) The click guard keeps the window it listens on and, at disarm, removes nothing when that window is gone (the document has another view, or its listener methods went with a torn-down environment): its listeners went with it. The disarm timer that outlived a test file no longer throws. The document-wide selection hold of a claimed mouse or pen drag ends a task after a lift, so the press's own mouseup and click still meet it, and at once after a pointercancel, a blur or a detach, which nothing follows: the host passes isCancel from its cancel paths. Also drops an unused import from the host and has a test assert the translation it kept in a variable.
There was a problem hiding this comment.
The circle's mouse and pen press now matches spec:AST-094 FR11's fine-pointer profile and DEC-21 on main. Approval still waits on an engineering owner for two other changes at this head: the rewrite of component:Button FR15 to a 250 ms coarse-pointer release painted on ::before, and the public indicatorPressScope press hand-off, which component:CheckboxInput AV1 does not allow today. An engineering owner can record the Button decision by commenting /approve-spec <full head SHA> on this pull request (you may do this yourself as an engineering owner), and can provide or identify the record that lets an indicator own its press. Separately, please:
- Rebase once #8255, #8682, #8665 and #8118 land, so this diff holds the three circle files instead of 42,216 authored lines.
- Remove CheckboxInput's
--_indicator-press-fillhand-down and its docs (CheckboxInput.tsx:90-96,CheckboxInput.doc.mjs:162-168,indicator.markers.stylex.ts:25-30). They describe an:activemouse press that the circle no longer reads. - Drop "A mouse on a desktop keeps
:active." from.changeset/press-control-profile.md, and stack on #8696 and #8695 so Core's marks and the jsdom selection-mark tests line up with the circle.
[Automated review]
| '--_indicator-press-fill': { | ||
| default: 'transparent', | ||
| ':active': { | ||
| default: colorVars['--color-overlay-pressed'], | ||
| '@media (pointer: coarse)': 'transparent', | ||
| }, | ||
| }, |
There was a problem hiding this comment.
After this pull request no indicator reads --_indicator-press-fill: the circle, the only indicator that paints its own press, takes --_press-paint-image for every pointer. spec:AST-094 FR11 and DEC-21 also replace :active for a mouse press. Removing this property, its two entries in CheckboxInput.doc.mjs and its line in derivedVarRegistry.test.ts keeps a theme author from building a second, :active press model from the docs.
| * The indicator applies this marker together with | ||
| * `data-astryx-indicator-press="self"` and paints the press itself from what | ||
| * the owner hands down as inherited private properties (CheckboxInput: | ||
| * `--_indicator-press-fill` for a mouse press, `--_press-paint-image` for a | ||
| * touch press, `--_indicator-hover` for hover). The owner finds it with | ||
| * `stylex.when.descendant()` and leaves its own layer off. |
There was a problem hiding this comment.
This tells an indicator author that a mouse press arrives as --_indicator-press-fill and only a touch press as --_press-paint-image. With this pull request's circle, the image is the press for every pointer, as FR11's fine-pointer profile says. Worth describing --_press-paint-image alone here.
The unhandled TypeError in full runs came from a click guard a test left armed: its 400 ms disarm timer fired after the environment tore that file's window down, in a later file. A test that ends with the guard armed now checks the next test starts with it disarmed. Fails until the shared test setup disarms every guard as each test ends.
…est ends; the guard has no special case for a removed window The cause of the unhandled TypeError was test isolation, not the guard: a test that ended inside the guard's 400 ms window left its timer to fire after the environment tore that file's window down. The shared setup now disarms every guard in afterEach, so no timer outlives its test. The check 2d365a7 added to disarm, which skipped unsubscribing once the window looked gone, only covered for that leak (a browser keeps a closed frame's listener methods), so it goes, with its case.
… paint Button.spec.md FR15: main's text (meta#8613: the 200 ms release at tempo 1, scaled by the theme's tempo, spec:AST-108 FR4) with this pull request's own wording kept; the measured 250 ms release is likewise stated at tempo 1.
…busy first frame outran pressFeedback.ts removes data-astryx-press="fading" on a timer of the release's duration, started at the write. When the main thread is busy right after the lift, the release animation starts only at the first frame after the block, so the timer removes the release partway through it: on a list row after a 150 ms block, 0.736 -> 0.558 -> gone about 52 ms in. These fail on that code: - pressFeedback.test.ts: with a release animation the stylesheet started late (or still waiting for its first frame) when the clock runs out, the controller's release and another painter's (paintPress) stay fading until the animation finishes. Guards: a cancelled animation ends it, no animation ends it on the clock (reduced motion's instant release, a surface without a press layer), a descendant's, an endless or an earlier animation is not waited for, and a new press keeps its paint when the old release ends later. - ButtonPressRelease.interaction.browser.spec.ts, both engines: after 150 ms of script at the release, the button's layer keeps the release until its strength reaches 0.
…y first frame started it endAfterRelease replaces the clock that removed data-astryx-press="fading" at the release's duration after the write, for the controller's release and for every other painter's (paintPress). The clock now only looks: at the release's duration, a release animation the write started on the surface (one of its own CSS animations, on it or its ::before or ::after, finite, started at or after the write or still waiting for its first frame) is waited for until it finishes or is cancelled, so a busy main thread that starts the animation late ends it late too, at strength 0. With no such animation (reduced motion's instant release, a surface without a press layer, an engine without Web Animations) the release ends on the clock, as before. Any new write, or a new press, cancels the wait. A slot's pending clock is now a cancel function, so the release's wait and every timer clear the same way.
…the press-paint base
…nto the fine-pointer press paint
…ts animation does) into the circle's FR43 change
There was a problem hiding this comment.
The circle's mouse and pen press now matches spec:AST-094 FR11 and DEC-21, but approval waits on an engineering owner for two other changes at this head: the rewrite of component:Button FR15 to a 250 ms coarse-pointer release painted on ::before, and the new public indicatorPressScope press hand-off, which no current record governs. An engineering owner can record the Button decision by commenting /approve-spec <full head SHA> on this pull request (the author may do this as an engineering owner), and can provide or identify the record that lets a theme indicator own its press. Separately, please:
- Rebase once #8255, #8682, #8665 and #8118 land, so this diff holds the three circle files instead of 42,619 authored lines.
- Remove
--_indicator-press-filland correct the docs that call it an indicator's mouse press (CheckboxInput.tsx,CheckboxInput.doc.mjs,indicator.markers.stylex.ts), and drop "A mouse on a desktop keeps:active." from.changeset/press-control-profile.md. - In the merged gesture stack, build
PressTrackerfrom acreate…factory and stop re-exporting the internal animator ascreateAnimator, asarchitecture:react-component-runtimeINV13 onmainnow requires.
[Automated review]
Depends on #8255 and #8682 (and their predecessors). Review only this PR's change: vjeux/astryx@bdc151c...c36a3c6
User need
spec:AST-094FR43 gives a fine pointer a finger's press model: a press paints at once onpointerdown, through the press controller and a finger's release fade, in place of:active. Lab's CircleCheckIndicator still painted a mouse press with its own:activefill and read its press image only under a coarse pointer, so with a mouse it showed a different press from the checkbox and radio marks beside it.Problem and solution fit
:activefill (--_indicator-press-fill) goes: the image is the circle's only press, so a held pen never draws two.Current specification
spec:AST-094FR43, FR11's fine-pointer profile, DEC-21.Public delta and caller burden
None in a published package: Lab is private, so there is no changeset. With a mouse or a pen, the circle's press appears at
pointerdownand fades on release, as under a finger; a right click paints nothing.analyze-pr.js).Design and alternatives
Keeping the
:activefill for a mouse would leave the circle with two press models, unlike the core marks after FR43.Evidence
Chromium, a fine primary pointer, a real Storybook build: a mouse press is marked by the controller at
pointerdown, the circle shows the round press image, and no fill sits beside it (fails on this PR's base). A unit case pins the image on every pointer and no:activefill (fails on the base).Scope
Testing
CI=true pnpm lint0 errors;pnpm buildthen rootpnpm typecheckclean; CircleCheckIndicator Chromium 16 passed with SelectionMarks, WebKit 9/9; registry-contract (preview) over 1,036 items passes; the Lab-import codemod moves nothing; units 16,561 underCI=true, with no unhandled error: the three StyleX jsdom cases below remain (test: the test runtime keeps every StyleX rule under a multi-condition prelude #8695), and the load timeouts that test(Item): wait for the swipe layer's module, not a one-second window #8352, test(Selector, MultiSelector): wait for the on-demand sheet module before the first open #8642 and test(DropdownMenu): the SSR test loads the menu and its sheet at collection #8685 fix pass alone; a mouse click still activates every story's control.CI=true pnpm lint0 errors;pnpm buildthen rootpnpm typecheckclean; CircleCheckIndicator Chromium 16 passed with SelectionMarks, WebKit 9/9; registry-contract (preview) over 1,036 items passes; the Lab-import codemod moves nothing; units 16,469 underCI=true: the three StyleX jsdom cases below remain (test: the test runtime keeps every StyleX rule under a multi-condition prelude #8695), and the load timeouts that test(Item): wait for the swipe layer's module, not a one-second window #8352, test(Selector, MultiSelector): wait for the on-demand sheet module before the first open #8642 and test(DropdownMenu): the SSR test loads the menu and its sheet at collection #8685 fix pass alone; a mouse click still activates every story's control.CI=true pnpm lint0 errors;pnpm buildthen rootpnpm typecheckclean; CircleCheckIndicator Chromium 16 passed with SelectionMarks, WebKit 9/9; registry-contract (preview): generate-data, thenverify-full-catalogover 1,036 items, passes; the Lab-import codemod moves nothing; a mouse click still activates every story's control.CI=true. Three jsdom cases that read a selection mark'stransition-durationunder(prefers-reduced-motion: reduce) and (not (pointer: coarse))getundefined: StyleX dev-runtime multi-group media key collision (jsdom only), fixed by test: the test runtime keeps every StyleX rule under a multi-condition prelude #8695; production CSS keeps both rules. The others are load timeouts that test(Item): wait for the swipe layer's module, not a one-second window #8352 and test(Selector, MultiSelector): wait for the on-demand sheet module before the first open #8642 fix.cache.test.mjs's npm-lock cases fail on main on this machine too.