Skip to content

feat(core): a checkbox and a radio option press the same way for every pointer (spec:AST-094 FR43) - #8696

Draft
vjeux wants to merge 1896 commits into
meta:mainfrom
vjeux:uikit/c06-fr43a
Draft

vjeux wants to merge 1896 commits into
meta:mainfrom
vjeux:uikit/c06-fr43a

Conversation

@vjeux

@vjeux vjeux commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Depends on #8233 and #8682 (and their predecessors). Review only this PR's change: vjeux/astryx@b809fd6...d96069b

User need

spec:AST-094 FR43 gives a fine pointer a finger's press model. With a mouse, CheckboxInput, CheckboxList and RadioList still pressed through :active: a CheckboxList row pressed on its label left its box blank, a pen on a fine-pointer page pressed the box instead of the row, and a RadioList row kept its own :active paint.

Problem and solution fit

Current specification

  • spec:AST-094 FR43, FR11's fine-pointer profile, DEC-21; FR24.

Public delta and caller burden

No API change. With a mouse or a pen, a checkbox and a radio option paint their press at pointerdown and fade on release, as under a finger; a CheckboxList row pressed anywhere paints the row and its box. A right click and a read-only row paint nothing. Touch targets stay coarse-only. The stylesheet loses the pointer-query wrappers and :active arms.

  • Size: base → head per component in this pull request's CI Component Size report (analyze-pr.js).

Design and alternatives

Keeping :active for a mouse would leave two press models. Tests that pinned a coarse-only press are conformed, including scripts/build-css.test.mjs's desktop-rule case (from #8118's line), narrowed to the Button's host layer: a selection mark's layer is its only press for every pointer.

Evidence

Chromium, a fine primary pointer, a real Storybook build: a mouse marks the press at pointerdown and the layer paints the pressed token, then releases on the host's clock (checkbox, CheckboxList row, RadioList row; each fails on the previous head). A held pen paints the same way. With a mouse or a pen, a CheckboxList row presses from its label and its box.

Scope

  • One capability is added.
  • Unrelated fixes, cleanup, and policy changes were removed or split.
  • Consumer docs, focused tests, and migration evidence ship with the feature.
  • Public text and artifacts contain no internal Meta context.

Testing

cixzhang and others added 30 commits October 26, 2026 19:51
…8072 9800eb5, the FR18-FR22 browser states) as a dependency
…eta#8061 9adbe2d, meta#8072 9800eb5) into the gesture watcher

The README's touch-action table takes meta#8061's edge-pan rows beside this
PR's watcher row.
…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.
…'s passive-only watcher listeners) into the arena fixes

The host-owner changeset keeps this PR's own listeners for the scroll
lock and finger-scroll watch, and adds meta#8336's scroll-edge hold.
…inter reaches its recognizers) as a dependency
… under a fine pointer) as a dependency
…tion group's finger under a fine pointer) into the gesture watcher
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.
…ta#8521) into meta#8270

# Conflicts:
#	packages/core/src/gestures/__tests__/GestureHost.test.ts
…g) into meta#8293

# Conflicts:
#	packages/core/src/motion/animator.ts
The note's reword joined its next sentence onto one long line; it now
wraps like the rest of the file. No code change.
cixzhang's meta#8589 keeps the host's touchend passive, and its contextmenu
and click listeners off, until a claimer is attached
(syncClaimingListeners). Ported onto this pull request's input stream with
no change to what it does: install() subscribes the touchend passive, and
syncClaimingListeners swaps it for a cancelling subscription and adds the
contextmenu and click subscriptions while a claimer is attached. A group
press brings its own click subscription only while the host has none, and
the host's takes it over. Her watcher test reads those listeners on the
window, where the input stream registers them; her new group test (a
finger, then a mouse, under a fine pointer) sits before the detach test.
The test file and the bare fixture import both her
ScreenEdgePanGestureRecognizer and this layer's selection lock from utils.
Here the click guard (utils/clickGuard.ts) owns clicks, so cixzhang's
meta#8589 syncClaimingListeners, ported at meta#8356, switches only the touchend's
cancelability and the contextmenu subscription; the group press subscribes
no click of its own. Her meta#8072 5c59af1 (a finger on an action group
under a fine pointer reaches its recognizers) arms the click guard where it
armed the swallower. Her watcher test reads the touchend and the
contextmenu on the window, and no click listener: the guard's comes only
once a gesture acts.
… the lift, under real touch input

spec:AST-094 FR20's "follows the scroll to the lift" had only a jsdom
test with a synthetic takeover; the FR18-FR22 verification row asks for
real touch input. In Chromium with injected touches, a vertical drag on a
list a watcher pan is attached to scrolls the list natively, no touchmove
is cancelled, and the watcher goes from began to ended at the lift.
… at the edge stays the page's

spec:AST-094 FR10: two fingers before a claim are the page's pinch. The
scroll-edge hold (DEC-18) counted any finger, so with two fingers down it
cancelled the pinch's moves before any drag was claimed. It now holds only
while a single finger is down. The new two-finger test fails on the
previous head.

Co-authored-by: Christopher Chedeau <vjeuxx@gmail.com>
vjeux added 2 commits October 27, 2026 10:55
…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.

@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.

Approval waits on one spec decision: the branch rewords the current component:Button FR15 (a 250 ms coarse-pointer release painted on ::before, 200 ms for a Space press and for fine pointers, and a Reduce Motion split), so an engineering owner needs to approve that rewording by commenting /approve-spec <full head SHA> on this pull request, or the record should keep main's FR15 text; the author may do so as an engineering owner. The selection controls' own mouse and pen press is now covered by spec:AST-094 FR11, FR43 and DEC-21 on main. Whatever the FR15 decision, these still need changes:

  • Rebase onto main once the predecessor pull requests land, so this pull request carries its own 15 files (996 lines) instead of 269 files and 42,403 authored lines.
  • With Reduce Motion and a mouse, the checkbox and radio release still fades for 200 ms (CheckboxInput.tsx, RadioListItem.tsx); design:reduced-motion DR3 asks for an immediate change, which controlPressLayer.ts and rowPressLayer.ts already make with their (prefers-reduced-motion: reduce) and (not (pointer: coarse)) arm.
  • A standalone checkbox's layer declares a 250 ms release while the press controller clears a fine pointer's press after about 200 ms, so align the two clocks and assert how long the press stays fading.

[Automated review]

vjeux added 21 commits October 27, 2026 13:05
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.
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 meta#8118's own
additions kept; the touch release's measured 250 ms is likewise stated
at tempo 1.
… 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.
…t) under the selection controls' FR43 change
…nder the selection controls' FR43 change
…release ends when its animation does) into the selection controls' FR43 change

@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.

Approval waits on one spec decision: the branch rewords the current component:Button FR15 (a 250 ms coarse-pointer release painted on ::before, 200 ms for a Space press and for fine pointers, and a Reduce Motion split) and adds a verification row for it, so an engineering owner needs to approve that record by commenting /approve-spec <full head SHA> on this pull request, or the record should keep main's FR15 text; the author may do so as an engineering owner. The selection controls' own mouse and pen press is covered by spec:AST-094 FR11, FR43 and DEC-21 on main. Whatever the FR15 decision, these still need changes:

  • Rebase onto main once the predecessor pull requests land, so this pull request carries its own 15 files (996 lines) instead of 271 files and 42,815 authored lines.
  • With Reduce Motion and a mouse, the checkbox and radio release still fades for 200 ms (CheckboxInput.tsx, RadioListItem.tsx); design:reduced-motion DR3 asks for an immediate change, which the shared press overlay already makes with its (prefers-reduced-motion: reduce) and (not (pointer: coarse)) arm.
  • A standalone checkbox's layer declares a 250 ms release while the press controller clears a fine pointer's press after about 200 ms, so the fade snaps off at about 15%; align the two clocks and assert how long the press stays fading.

[Automated review]

This branch had an error being deployed

1 failed deployment
Preview — d96069b6 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 needs:spec-owner-review Current knowledge records await owner approval

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants