Repository navigation
Conversation
Under a finger, the touch press controller ended a press's release on a clock that starts at the lift. When script delays the first frame of the release animation, the animation ends later, but the clock does not, so the highlight was cut off mid-fade. When the clock runs out, the controller now waits for a release animation that still runs or still waits for its first frame: on the surface, its ::before or ::after, or on a part inside it whose nearest press owner is the surface. With none, the release ends at the clock, as before. Rebased onto main after meta#8360 landed: main's theme tempo already caps the release under Reduce Motion, so only the release-ends-with-its-animation part remains. Co-authored-by: Cindy Zhang <cindyxz@meta.com>
…animation CodeBunny's notes on the first round: pressGesture.ts still described PRESS_FADE_MS as the timer that removes the attribute, and two new comments in pressFeedback.ts had sentences over 25 words.
Under a finger, a pressed ListItem, actionable Item or TreeList row now paints its press on a ::before layer under the row's content and fades that layer's opacity, which the browser runs off the main thread. The row keeps fading while the tapped page runs script, instead of staying pressed and then snapping off. A mouse or trackpad row is unchanged. Rebased onto main and meta#9758 (which supersedes meta#8890): - The row layer's styles read the private press attribute, so they live in its owner module, privateAttributes/press.stylex.ts, as INV23 and the no-raw-private-attribute rule require. - The layer's release clock follows the theme's tempo and its Reduce Motion cap, the same clock as the strength's release on main. Co-authored-by: Cindy Zhang <cindyxz@meta.com>
…aits only inside a scroller UIKit delays a touch only inside a scroll view. Under a finger, a pressable now highlights in the frame the finger lands unless a scroller sits above it, where it still waits the 150 ms content-touch delay. Rows follow the same rule. The controller walks up the flat tree from the touched surface for a box that can scroll on an axis touch-action leaves to the browser, and a fixed box ends the walk. Rebased onto main and meta#9762 (which supersedes meta#8077): - Main's shortened file headers are kept, with the scroller rule added in short sentences. - The controller writes the press attribute only when the paint changes, as the original base did, so a held press is one change. - The busy-main-thread row test reads the row at rest from its last frame: a row outside a scroller now paints in the first frame. Co-authored-by: Cindy Zhang <cindyxz@meta.com>
… and WebKit skips the first-frame case CodeBunny's first round on this pull request: - RadioListItem set the press layer's ::before content again on Item's root. The owner module now exports cellWithoutLayer, which also leaves a pressed radio row out of a stacking context. - WebKit starts the release at the time busy script began, so the first-frame case skips WebKit and records the value it measured.
…obe share one scroll-view test CodeBunny's first round: the controller's walk copied the gesture core's native-scroll probe. isInsideScrollView now lives in utils/scrollView.ts, with parseTouchAction and the root-overflow test, and both the controller and the probe's isInsideScroller call it. The changeset says a page that scrolls keeps the delay.
Specs' text under the owner's 2026-10-25 decision: under the touch presentation an interactive row uses its ::before as the press layer, a pressed row's divider keeps its resting colour, and a caller's own ::before merges into the layer only while the row is pressed. DEC-6 records the decision and answers OQ1, and the compatibility row says the compositor release is implemented.
…le, and the list table entry goes CodeBunny's second round on this pull request: - INV23: cellWithoutLayer now carries the radio row's empty image and its strength release, so RadioListItem no longer sets those beside cell, and swipeRoot drops isolation (its transform already isolates the row). - motion/uikit/list.ts only re-exported timing.press, so it goes with its index, README and MEASUREMENTS rows; cellPressConsts is held to timing.press directly. This brings the change under 2,000 lines. - WebKit: the busy-first-frame end case asserts only the end and the order of frames there, since WebKit's first frame can already be near rest.
Main's meta#8638 renamed the release clock helper fadeMs to an exported pressFadeMs. The release call here now uses pressFadeMs; nothing else in this pull request changes.
Under reduced motion a press never scales a Button, and ClickableCard, SelectableCard and Thumbnail change their hover and selection colors at once on every pointer. Under a coarse primary pointer the three cards' press release fades on the theme's exit duration and easing, capped at the press controller's release clock (component:ClickableCard FR5, component:SelectableCard FR4, component:Thumbnail FR4). The press still shows at once. Under reduced motion that fade takes at most 200 ms, opacity only. A fine primary pointer keeps the timing it had for hover and a mouse press.
Every press release that fades now plays `--duration-exit` and `--ease-exit` instead of 200 ms, scaled by the theme's tempo, on a curve of its own: the shared overlay's touch release, the controller's release clock (which action groups read too), Switch's press layers and the list row layer. A desktop pen press, which the controller fades, moves to the tokens too. Under Reduce Motion a finger's release is no longer than 200 ms, as before. A mouse press on a desktop is unchanged.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
AI review status for this pull request.
|
ClickableCard, SelectableCard and Thumbnail drop their copies of the press release and compose the shared pressedAlpha alone. Under the touch presentation it fades on the exit tokens, and the press controller's clock reads the same token, so the cap goes too. With a fine primary pointer, a finger's release on these cards now fades on the exit tokens, as other touch press releases do. A mouse press keeps the cards' desktop press styling.
94b1c01 to
772101a
Compare
There was a problem hiding this comment.
This pull request needs an engineering owner's spec decision before it can be approved, and it needs changes either way.
Spec decision. As filed against main, the branch rewrites component:Button FR15 (a 200 ms × tempo release) and design:reduced-motion DR2 (colors cross-fade on a touch screen), and it adds requirements main does not state: spec:AST-094 FR5's exit-token sentence, the press-release clauses in component:ClickableCard FR5, component:SelectableCard FR4 and component:Thumbnail FR4, component:List FR11 with DEC-6 (closing OQ1), and design:reduced-motion DEC-4. An engineering owner records the decision by commenting /approve-spec 772101aed922045524391d467b3e1c4d32982b30 on this pull request (the author may do so as an engineering owner), or the records can be approved in their own pull requests first.
Required changes:
- Rebase onto
main, because the branch conflicts with it and the auto-mergedpackages/core/src/utils/pressFeedback.tsdeclaresreleaseAnimationsOfandendAfterReleasetwice, sincemainalready holds #9758's change. .changeset/cards-touch-release-exit-tokens.mdpromises a release "capped at the press's release time" under a coarse pointer, but the head plays the shared release on every pointer with no cap..changeset/cards-touch-release-fine-pointer.mdannounces a fix for a fine-pointer finger release thatmainnever shipped, because the cards there already use the shared release..changeset/list-row-press-compositor.mdsays the row release keeps "the same color, clock and curve as before", while the row now plays--duration-exitand--ease-exit.- Miss:
ButtonPress.interaction.browser.spec.ts,ListRowPress.interaction.browser.spec.tsandListRowPressPaths.interaction.browser.spec.tsimporttestfrom@playwright/test, which the lint config forbids in browser specs, sopnpm lintfails until they import from@astryxdesign/a11y-spec/playwright. - At 4,421 authored lines from six separable increments, the pull request should carry only its top commit (rebase after #8326 and #9890 land) or become a declared stack in which every pull request targets
main. component:ButtonFR15 still lists "the 0.98 press scale also applies under reduced motion" as an implementation gap, though this change fixes it..changeset/press-release-exit-tokens.mdcredits no contributor, sopnpm check:changesetsfails.
The private duration parser and the unused test import noted earlier are fixed.
[Automated review]
| '@astryxdesign/core': patch | ||
| --- | ||
|
|
||
| [feat] Under a coarse primary pointer, the press release of ClickableCard, SelectableCard and Thumbnail now fades on the theme's exit duration and easing, capped at the press's release time. |
There was a problem hiding this comment.
At this head the cards play the shared pressedAlpha release on every pointer, with no cap, so this sentence describes a release that no longer ships. press-release-exit-tokens.md already names the cards; this changeset can go.
| '@astryxdesign/core': patch | ||
| --- | ||
|
|
||
| [fix] With a fine primary pointer, a finger's press release on ClickableCard, SelectableCard and Thumbnail now fades on the theme's exit duration and easing. A mouse press is unchanged. |
There was a problem hiding this comment.
On main the cards already compose pressedAlpha alone, so there is no fine-pointer card release to fix in a released version; press-release-exit-tokens.md covers the change.
| '@astryxdesign/core': patch | ||
| --- | ||
|
|
||
| [fix] A list row's touch press release keeps fading while the page runs script. `ListItem`, `Item` and `TreeList` rows used to stay fully pressed after the finger lifted until the main thread was free, and every frame of the fade repainted the row; the release now runs on the compositor, with the same color, clock and curve as before. Under a finger, while a row is pressed and through its release, the press paints on the row's `::before` and the row is its own stacking context; at rest nothing of it applies, so a row's own `::before` decoration and its children's stacking are as they were. During a press the layer is the row's `::before`, and any `::before` property a caller sets, from `xstyle` (which `Item` composes after the layer) or from a stylesheet, wins over the layer's: a decoration with its own size and background jumps to the row's corner, and the row shows no pressed fill until the release ends. Decoration that should stay through a press belongs on `::after` or a child element. With a mouse or trackpad the row is unchanged: no layer and no new stacking context. |
There was a problem hiding this comment.
The row layer plays --duration-exit and --ease-exit (CELL_RELEASE, RELEASE_EASE in privateAttributes/press.stylex.ts), not the same clock and curve as before. Suggested: "on the theme's exit motion tokens".
| '@astryxdesign/core': patch | ||
| --- | ||
|
|
||
| [fix] A press's release now fades on the theme's exit motion tokens, `--duration-exit` and `--ease-exit`, instead of 200 ms, scaled by the theme's `tempo`, on a curve of its own. This covers every release that fades: a finger, mouse, pen or key press on a phone or tablet, and a pen press on a desktop, on buttons, cards, switches, checkboxes, list and tree rows, tabs, menu items and action groups. With the default tokens the release is 175 ms and holds its colour a little longer before it goes. A theme that changes `--duration-exit` or `--ease-exit` now changes the release too; a theme's `tempo` still does, through `--duration-exit`. Under Reduce Motion a finger's release still fades for no more than 200 ms. A mouse press on a desktop is unchanged. |
There was a problem hiding this comment.
This body credits no contributor, so pnpm check:changesets fails: add an @handle line.
| | FR13 | A standalone Button paints the shadow token for its `elevation`. Inside a ButtonGroup it paints no shadow and reflects `data-elevation="none"`, whatever `elevation` says. | `family:buttons` FR8–FR10; current source and tests | | ||
| | FR14 | The root carries `astryx-button` and reflects the effective `data-variant`, `data-size`, and `data-elevation`. | `family:buttons` FR10; `architecture:component-theming-surface`; tests | | ||
| | FR15 | Hover paints the shared interaction overlay on hover-capable pointers, and a press paints the shared pressed overlay through the press controller: at once under a mouse or a pen (`spec:AST-094` FR11's fine-pointer profile) and on FR11's control profile under a finger, fading out over 200 ms on release at `tempo` 1, scaled by the theme's `tempo` (`spec:AST-108` FR4). Without a reduced-motion preference, a standalone Button also scales to 0.98 on a mouse press, or while the touch press reads on; under reduced motion it MUST NOT scale (`design:reduced-motion` DR1). All of this paint follows the element's exposed disabled state: a Button that is natively disabled or exposes `aria-disabled="true"` (busy-only, disabled with a tooltip, or a caller's own `aria-disabled`) shows no overlay and no press scale, even when it stays operable. Inside a ButtonGroup the press scale does not apply. Button's hover and press transitions are fast, and instant under reduced motion. Implementation gap: the 0.98 press scale also applies under reduced motion; only its transition becomes instant. | Current source and tests; `spec:AST-094` FR11 and DEC-21 for the mouse and pen press | | ||
| | FR15 | Hover paints the shared interaction overlay on hover-capable pointers, and a press paints the shared pressed overlay through the press controller: at once under a mouse or a pen (`spec:AST-094` FR11's fine-pointer profile) and on FR11's control profile under a finger, fading out on release on the theme's exit motion tokens (`--duration-exit`, `--ease-exit`; `spec:AST-094` FR5). Without a reduced-motion preference, a standalone Button also scales to 0.98 on a mouse press, or while the touch press reads on; under reduced motion it MUST NOT scale (`design:reduced-motion` DR1). All of this paint follows the element's exposed disabled state: a Button that is natively disabled or exposes `aria-disabled="true"` (busy-only, disabled with a tooltip, or a caller's own `aria-disabled`) shows no overlay and no press scale, even when it stays operable. Inside a ButtonGroup the press scale does not apply. Button's hover and press transitions are fast, and instant under reduced motion. Implementation gap: the 0.98 press scale also applies under reduced motion; only its transition becomes instant. | Current source and tests; `spec:AST-094` FR11 and DEC-21 for the mouse and pen press | |
There was a problem hiding this comment.
The row still ends with "Implementation gap: the 0.98 press scale also applies under reduced motion", but Button.tsx now adds the reduced-motion scale(1) arms and this record drops the matching Current-state bullet. Delete the sentence.
| * (utils/pressFeedback.ts, as installed by hooks/usePressFeedback.ts) | ||
| */ | ||
|
|
||
| import {expect, test, type Locator, type Page} from '@playwright/test'; |
There was a problem hiding this comment.
eslint.config.js forbids @playwright/test in browser specs (no-restricted-imports); import test and expect from @astryxdesign/a11y-spec/playwright, as Switch.interaction.browser.spec.ts does.
| */ | ||
|
|
||
| import {readFile} from 'node:fs/promises'; | ||
| import { |
There was a problem hiding this comment.
Same lint error as the Button spec: import test and expect from @astryxdesign/a11y-spec/playwright.
| * ListRowPress.interaction.browser.spec.ts | ||
| */ | ||
|
|
||
| import {expect, test, type Locator, type Page} from '@playwright/test'; |
There was a problem hiding this comment.
Same lint error: import test and expect from @astryxdesign/a11y-spec/playwright.
|
/approve-spec 772101a |
Stack: #9890 and #8326 → this pull request. Review only the top commit, "fix(core): the cards' touch press release is the shared one". The commit below it merges #8326 into #9890.
User impact
This affects people who press ClickableCard, SelectableCard or Thumbnail with a finger.
spec:AST-094FR5).--duration-exitand--ease-exit, like other touch press releases, also for a finger with a fine primary pointer. The press controller's clock reads the same token, so there is nothing left to cap.Problem and solution fit
interactionOverlayStyles.pressedAlphaalready releases on--duration-exitand--ease-exit, withmin(--duration-exit, 200ms)under Reduce Motion, andpressFadeMsreads--duration-exit. So the cards composepressedAlphaalone, as they do on main, and no new variant is needed.Expected behavior and authority
spec:AST-094FR5, as fix(core): a press's release fades on the theme's exit motion tokens #9890 records it: "Every press release that fades does so on the theme's exit motion tokens (--duration-exit,--ease-exit), on every device and pointer".component:ClickableCardFR5,component:SelectableCardFR4 andcomponent:ThumbnailFR4, in the spec owner's wording for this change: "A press shows at once. Under the touch presentation, its release fades on the theme's exit motion tokens (--duration-exit,--ease-exit), perspec:AST-094FR5; a mouse press keeps the card's desktop press styling." With fix(core): a press's release fades on the theme's exit motion tokens #9890 the press clock follows the tokens, so fix(core): no press scale or card color fade under reduced motion #8326's cap clause goes.pressedAlphaafter fix(core): a press's release fades on the theme's exit motion tokens #9890 merges, with no edits to the shared press module from two pull requests at once. This pull request is that follow-up.Smallest restoration
ClickableCard.tsx,SelectableCard.tsxandThumbnail.tsx: theSHARED_RELEASE*constants,styles.touchReleaseand its composition are removed, and the import is{interactionOverlayStyles}again. fix(core): no press scale or card color fade under reduced motion #8326's Reduce Motion arms on the overlay, the border and the selected root stay.utils/sharedPressRelease.test.ts(the sync test for the copies) is deleted;hasCardReleaseFadeleaves__tests__/pressState.ts, which is then fix(core): a press's release fades on the theme's exit motion tokens #9890's file;hasReleaseFadeagain, as on main;ReducedMotionPress.a11y.chromium.spec.ts, the fine-pointer case expects a finger's release on the exit tokens.spec:AST-094FR5, and the bullet says a mouse press keeps the card's desktop press styling. Governing rules addspec:AST-094/FR5.[fix]for a finger's release with a fine primary pointer.Rebase
When #8326 and #9890 merge, this branch rebases onto main and keeps only the top commit.
Overlaps with open pull requests
__tests__/pressState.ts. Whichever merges second rebases.check:changesets(its changesetpress-release-exit-tokens.mdhas no contributor line) and two cases in.github/scripts/ci-interaction-webkit.test.mjs(utils/scrollView.tsis not routed to the WebKit proof). This pull request does not edit fix(core): a press's release fades on the theme's exit motion tokens #9890's files; they clear when fix(core): a press's release fades on the theme's exit motion tokens #9890 fixes them.Writing rubric results
Run over the three changed records:
component:ClickableCard,component:SelectableCardandcomponent:Thumbnail.proposes,we decided,previously,a port ofandnowfinds nothing. The changed FR clauses, Compatibility bullets and readiness rows are present tense.#followed by digits,/pull/or/issues/).component:ClickableCardOQ1 is unanswered and keeps its two options. The other two records have none.Evidence
sharedPressRelease.test.ts's check that the copies match the shared module). Browser (ReducedMotionPress.a11y.chromium.spec.tson a Storybook built at the merge commit): the fine-pointer case fails on a finger's release (old clock and curve); the other 5 pass.ReducedMotionPress.a11y.chromium.spec.ts6/6,CardPress.a11y.chromium.spec.ts9/9 and Button's accessibility contract 20/20, on a Storybook built at this head.hasReleaseFadeholds them to the shared release.Touch frames against #8326 and #9890
Local probe, not part of the diff. Real Chromium with phone emulation (402×714, touch, coarse primary pointer), one real tap on each card through touch input. When the press controller writes the release, the probe pauses the release animation and seeks it to each time below (the controller holds the release until that animation finishes, so nothing ends early). At each time it reads the surface's
--astryx-press-alpha, the overlay's computed paint, and a screenshot of the card. The comparison build is the merge of #8326's and #9890's heads. Measured at this pull request's earlier head 94b1c01, against that merge (05ccb64); this head rebuilds the same fold on #9890's amended head, whose change to the press code keeps the same release clock.--duration-exitis 125 ms, under the 200 ms bound.Scope
Testing
test:ui19,778/19,778.test:node16,276 passed and 2 failed: both in.github/scripts/ci-interaction-webkit.test.mjs, becausepackages/core/src/utils/scrollView.tsis bundled by the gesture fixture and the pager bundle but not routed to the WebKit proof. They fail the same way at fix(core): a press's release fades on the theme's exit motion tokens #9890's head, so they come with fix(core): a press's release fades on the theme's exit motion tokens #9890 (see Overlaps).ReducedMotionPress.a11y.chromium.spec.ts6/6,CardPress.a11y.chromium.spec.ts9/9,Button.a11y.chromium.spec.ts20/20.check:knowledgeandreview:prepass.CI=true pnpm lintfails only incheck:changesets, on fix(core): a press's release fades on the theme's exit motion tokens #9890's own changeset (see Overlaps).