Repository navigation
fix(Item): a list row's touch press release runs on the compositor - #9762
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
AI review status for this pull request.
|
There was a problem hiding this comment.
The compositor release for list rows holds up in Chromium: under a finger the highlight keeps fading through script, and mouse and trackpad rows are unchanged.
Approval waits on an engineering owner's decision that no current spec covers: during a touch press, a caller's own ::before on an interactive Item, ListItem or TreeList row wins over the press layer, so a decorated row's decoration jumps to the corner and the row shows no pressed fill until the release ends. An engineering owner records that decision by commenting /approve-spec <full head SHA> on this pull request (the author may do so as an engineering owner), or adds the outcome to component:List FR11.
Please also fix:
- In WebKit, "a busy first frame after the lift delays the release instead of skipping into it" (
ListRowPress.interaction.browser.spec.ts) fails on this head with opacity 0.03 against more than 0.7, so either start the WebKit release at its first presented frame or skip that case in WebKit and record the measured value. - The description's User impact says tables are built on
Item, but Table renders neitherItemnorListItem, so please drop "tables". RadioListItem.tsxsets the owner style's::beforecontentagain on Item's root, whicharchitecture:public-component-apiINV23 rules out and which still leaves a pressed radio row as a stacking context, so export a ready-made no-layer style fromprivateAttributes/press.stylex.tsand spread that instead.
[Automated review]
| ); | ||
| // At most one frame into the release: its ease-out curve takes the first | ||
| // frame to about 0.74; a start pinned before the block would be past 0.4. | ||
| expect(first).toBeGreaterThan(0.7); |
There was a problem hiding this comment.
In WebKit this fails on this head: the first frame after the 150 ms block shows the layer at opacity 0.033 (3 of 3 runs), so the release skips nearly to rest instead of starting late. Either start the WebKit release at its first presented frame, or skip this case in WebKit and record the measured value, as the video case does.
| '::before': { | ||
| content: 'none', | ||
| }, |
There was a problem hiding this comment.
This sets rowPressLayerStyles.cell's ::before content again on the same element, which architecture:public-component-api INV23 rules out ("a caller does not set those properties again on the same element"). It also leaves cell's isolation, so a pressed radio row still becomes a stacking context. A ready-made no-layer style exported from privateAttributes/press.stylex.ts would keep every condition in the owner.
| * the layer is the row's `::before`, so a caller's decoration there is | ||
| * replaced for that time (decorate with `::after` or a child). |
There was a problem hiding this comment.
Non-blocking: this says a caller's ::before decoration is replaced during a press, but the shipped and tested outcome is the opposite: the caller's properties win (see the changeset). Suggest: "During a press, a caller's own ::before properties win over the layer's."
|
/approve-spec e3a7f4a |
There was a problem hiding this comment.
The compositor release for list rows holds up in Chromium: under a finger the row keeps fading through script, and mouse and trackpad rows are unchanged. The component:List FR11 and DEC-6 change has an engineering owner's approval for this head, so the remaining work is in the code and tests.
Please fix:
- In WebKit, "a busy first frame after the lift still plays the whole release before the press attribute goes" (
ListRowPress.interaction.browser.spec.ts) fails intermittently (2 of 12 runs, first frame at opacity 0.03), so either assert only what holds in WebKit and record the measured frames, as the case before it does, or start the WebKit release at its first painted frame, and update the description, which says this case keeps running there. - The pull request is 2,055 authored lines, over the 2,000-line limit, and
motion/uikit/list.tsonly re-exportstiming.press, so dropping that table entry with its index, README and MEASUREMENTS rows, and holdingcellPressConststotiming.pressdirectly, would bring it under the limit. - Under
architecture:public-component-apiINV23 a caller must not set a property again that the owner stylecellsets on the same element, yetItem'sswipeRootsetsisolationand the radio row setsanimationName(throughpressedAlpha) andbackgroundImage; letcellWithoutLayercarry the radio row's strength release and empty image, and dropisolationfromswipeRoot, whose transform already isolates the row.
The inline comments add two small non-blocking notes.
[Automated review]
| .__frames, | ||
| ); | ||
| const fading = frames.filter(([, , press]) => press === 'fading'); | ||
| expect(fading.length, JSON.stringify(frames)).toBeGreaterThan(3); |
There was a problem hiding this comment.
In WebKit this fails intermittently on this head (2 of 12 runs). In each failure the first frame after the 150 ms block reads opacity 0.033 or 0.034, then rest, so only two or three frames are still fading. That is the curve 150 ms in: WebKit started the release when the block began, as the skip reason on the case above says. Either assert only that the release reaches rest before the attribute goes in WebKit, recording the measured frames, or start the WebKit release at its first painted frame.
|
|
||
| import {timing} from './timing'; | ||
|
|
||
| export const list = { |
There was a problem hiding this comment.
list.highlight only re-exports timing.press.highlightOutMs and highlightOutCurve, and the tests pin cellPressConsts to PRESS_FADE_MS, so a list-specific clock cannot diverge. With its index, README and MEASUREMENTS rows this entry is 68 lines, enough to bring the PR from 2,055 authored lines under 2,000. Suggest dropping it and holding cellPressConsts to timing.press directly.
| // The touch press on a compositor layer (after the overlay, whose | ||
| // own touch paint it turns off): the release keeps fading while | ||
| // script runs. | ||
| isInteractive && rowPressLayerStyles.cell, |
There was a problem hiding this comment.
styles.swipeRoot, composed after this on swipe rows (line 929), sets isolation again over cell's, which INV23 rules out ("a caller does not set those properties again on the same element"). The result matches today, but a condition later added to cell's isolation would skip swipe rows. swipeRoot's translate3d(var(--_item-swipe-travel, 0px), 0, 0) already makes the row a stacking context, so its isolation can go.
| // The row owns the strength the circle's layer reads, and its | ||
| // release (Item's touch layer drops the row's own under a finger: | ||
| // this row has no layer of its own, so it keeps the release). | ||
| !isDisabled && interactionOverlayStyles.pressedAlpha, |
There was a problem hiding this comment.
This sets animationName again over the owner style cell that Item spreads on the same element, and the bare look's backgroundImage: 'none' (line 156) does the same for backgroundImage; INV23 keeps those in the owner. cellWithoutLayer could carry the radio row's strength release (pressRelease on fading, under both pointers) and its empty row image, so this row drops pressedAlpha and its own backgroundImage.
| * content. The layer exists only while a press is on or releasing: at rest | ||
| * a row's own `::before` and its stacking are as they were; during a press | ||
| * the layer is the row's `::before`, so a caller's decoration there is | ||
| * replaced for that time (decorate with `::after` or a child). |
There was a problem hiding this comment.
Non-blocking: this says a caller's ::before decoration is replaced during a press, but FR11 now says it "merges into the press layer" and the changeset says the caller's properties win. Suggest: "During a press, a caller's own ::before properties win over the layer's."
| // to its resting color: from a frame still short of rest, it reached rest | ||
| // inside the blocked window. A release on the main thread would hold its | ||
| // first frame's value until the block ends, 700 ms on, and could not. (The | ||
| // recording's frames are 40 ms apart, so how many land inside the 250 ms |
There was a problem hiding this comment.
Non-blocking: the release is 200 ms (RELEASE_MS), not 250 ms.
|
/approve-spec fc57431 |
There was a problem hiding this comment.
Recommended for approval: under a finger, an interactive ListItem, Item or TreeList row now keeps fading its release while the page runs script, and mouse and trackpad rows are unchanged. The component:List FR11 and DEC-6 change has an engineering owner's approval for this head, and the three issues raised earlier are fixed: the WebKit busy-first-frame case now passes consistently, the change is under 2,000 lines, and cellWithoutLayer carries the radio row's image and release.
Optional comment cleanups:
privateAttributes/press.stylex.tsstill says a caller's::beforeis "replaced" during a press, while FR11 says it merges into the layer and the changeset says the caller's properties win.- The
cellandcellWithoutLayerJSDoc blocks could keep only the reason, in shorter sentences. ListRowPress.interaction.browser.spec.tsmentions "the 250 ms release"; the release is 200 ms.
[Automated review]
| * radio row, whose circle paints the press. The row paints no overlay | ||
| * image, keeps the strength's own release (the circle's layer reads the | ||
| * strength), and under a finger has no `::before` box and is no stacking |
There was a problem hiding this comment.
Non-blocking: this second sentence runs about 30 words on three topics. Shorter, with only the reason: "The row paints no image. It keeps the strength's release, which the circle reads. Under a finger it has no ::before and no stacking context."
|
/approve-spec 6128ad5 |
There was a problem hiding this comment.
Recommended for approval, pending CI: CI has not run on this head yet. Under a finger, an interactive ListItem, Item or TreeList row now keeps fading its release while the app runs script, mouse and trackpad rows are unchanged, and the component:List FR11 and DEC-6 change has an engineering owner's approval for this head.
Optional cleanups:
- Miss: the description's phone check, step 2, freezes the page in a
pointeruplistener, which runs before the release starts, so the row stays pressed through the freeze and fades after it; freezing in the row'sclicklistener shows the fade continuing through the freeze. privateAttributes/press.stylex.tsstill says a caller's::beforeis "replaced" during a press, while FR11 says it merges into the layer and the changeset says the caller's properties win.- The
cellandcellWithoutLayerJSDoc blocks could keep only the reason, in shorter sentences. ListRowPress.interaction.browser.spec.tsmentions "the 250 ms release"; the release is 200 ms.
[Automated review]
There was a problem hiding this comment.
Changes requested: one wording fix. Under a finger, ListItem, Item and TreeList rows now keep fading a release that has started while the page runs script. Mouse and trackpad rows are unchanged, and the component:List FR11 and DEC-6 change has an engineering owner's approval for this head.
Blocking:
- Miss: the changeset and the description say a row's release keeps fading while the page runs script, but script in the tap's own handlers (
pointerup,touchend, or aclickthat runs before the next frame) still holds the row fully pressed until it ends, as your testa busy first frame after the lift delays the release instead of skipping into itasserts, so please say the fix covers a release that has already started, and start the freeze in phone-check step 2 a frame after the lift.
Worth doing before this moves to main:
- Newly live check:
mainnow rejects@playwright/testimports in browser specs, so both new specs should import from@astryxdesign/a11y-spec/playwright, and the WebKit case's ownchromium.launch()contexts needkeepRequestsLocal. privateAttributes/press.stylex.tsstill says a caller's::beforeis "replaced" during a press, andListRowPress.interaction.browser.spec.tsmentions a 250 ms release where the release is 200 ms.
[Automated review]
| '@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.
Miss: this holds for script that starts after the release's first frame, not for script in the tap's own handlers. With 700 ms of script in the row's click or touchend, the row stays fully pressed for the whole block, then fades. The PR's own test a busy first frame after the lift delays the release instead of skipping into it asserts that delay, and its WebKit note says WebKit's first frame after such a block is already near rest. Suggested wording: "Once a list row's touch press release has started, it keeps fading while the page runs script, instead of holding and snapping off when the script ends. Script in the tap's own handlers still runs before the release starts, so the row stays pressed until that script ends, then fades."
| import { | ||
| chromium, | ||
| expect, | ||
| test, | ||
| type Locator, | ||
| type Page, | ||
| } from '@playwright/test'; |
There was a problem hiding this comment.
Newly live check: main now rejects @playwright/test imports in browser specs (no-restricted-imports), so this fails lint once the PR moves to main (the same holds for ListRowPressPaths.interaction.browser.spec.ts:19). Import from @astryxdesign/a11y-spec/playwright, which re-exports chromium and expect, and call keepRequestsLocal on the contexts of the WebKit case's own chromium.launch().
Under a finger, an interactive Item, ListItem or TreeList row paints its press on its own `::before` layer and fades it out as an opacity animation, so a release that has started keeps fading while the page runs script. The release keeps the press model's colour, clock and curve. A fine pointer's row is unchanged. Co-authored-by: Cindy Zhang <cindyxz@meta.com>
6128ad5 to
e2f2ed7
Compare
|
/approve-spec e2f2ed7 |
There was a problem hiding this comment.
Recommended for approval, pending CI: CI has not run on this head yet, because it skips draft pull requests; it runs once the pull request is marked ready for review.
Under a finger, an interactive ListItem, Item or TreeList row now keeps fading a release that has started while the page runs script, and mouse and trackpad rows are unchanged. The component:List FR11 and DEC-6 change has an engineering owner's approval for this head, and the changeset now says that script in the tap's own handlers still holds the row pressed.
Optional cleanups:
- Miss: the header of
privateAttributes/press.stylex.tslists onlycellandcellOnImage, and says only Item and TreeList compose these styles, socellWithoutLayerand RadioListItem are missing. - Miss:
cellPressConsts.cellReleaseMsrepeatspressConsts.releaseMs, the release clock the controller already reads, so reading that constant would remove the copy. - The
cellandcellWithoutLayerJSDoc blocks could keep only the reason, in shorter sentences.
[Automated review]
| * @output `rowPressLayerStyles`: the compositor press layer for list rows | ||
| * (`cell`) and its own-image arms for a row on the `backgroundImage` | ||
| * variant (`cellOnImage`) | ||
| * @position Owner module of the private press attribute for the row layer: | ||
| * the press controller (utils/pressFeedback.ts) writes the attribute, and | ||
| * this file reads it. No package entry exports it. Item and TreeList | ||
| * compose these styles after their overlay variant; | ||
| * utils/interactionOverlay.cell.test.ts tests them. |
There was a problem hiding this comment.
Non-blocking (Miss): @output lists cell and cellOnImage but not cellWithoutLayer, and @position says Item and TreeList compose these styles, while RadioListItem composes cellWithoutLayer. Add both so the header matches the module.
| */ | ||
| export const cellPressConsts = stylex.defineConsts({ | ||
| /** `timing.press.highlightOutMs`, in ms. */ | ||
| cellReleaseMs: 200, |
There was a problem hiding this comment.
Non-blocking (Miss): this repeats pressConsts.releaseMs (utils/interactionOverlay.stylex.ts:86), the release clock the controller reads as PRESS_FADE_MS. press.stylex.ts already imports pressVars from that module, so CELL_RELEASE could read pressConsts.releaseMs and drop this copy. The tests already pin the two equal, so this is cleanup, not a fix.
PR Analysis Report🔎 Preview ·
|
| Component | Size | LoC | Visual · A11y · RTL |
|---|---|---|---|
| PowerSearch | 236.8 kB (+605 B) · 22.2 kB CSS (+377 B) | — | — · — · — |
| Pagination | 87.2 kB (+540 B) · 16.4 kB CSS (+375 B) | — | — · — · — |
| Selector | 78.3 kB (+532 B) · 16.1 kB CSS (+375 B) | — | — · — · — |
181 more components
| Component | Size | LoC | Visual · A11y · RTL |
|---|---|---|---|
| BreadcrumbItem | 68.0 kB (+545 B) · 11.2 kB CSS (+359 B) | — | — · — · — |
| TableCell | 75.8 kB (+484 B) · 16.3 kB CSS (+394 B) | — | — · — · — |
| ContextMenu | 74.6 kB (+495 B) · 15.8 kB CSS (+370 B) | — | — · — · — |
| TableHeaderCell | 75.4 kB (+481 B) · 16.0 kB CSS (+382 B) | — | — · — · — |
| Markdown | 145.2 kB (+475 B) · 18.7 kB CSS (+380 B) | — | — · — · — |
| Table | 84.2 kB (+461 B) · 16.5 kB CSS (+392 B) | — | — · — · — |
| CheckboxListItem | 53.5 kB (+392 B) · 10.4 kB CSS (+384 B) | — | — · — · — |
| ContextMenuItem | 20.5 kB (+403 B) · 5.9 kB CSS (+369 B) | — | — · — · — |
| ListItem | 52.8 kB (+379 B) · 10.4 kB CSS (+384 B) | — | — · — · — |
| TreeList · Storybook | 39.3 kB (+371 B) · 5.3 kB CSS (+387 B) | 1,155 (+3) | — · — · — |
| DropdownMenu | 73.1 kB (+350 B) · 15.9 kB CSS (+385 B) | — | — · — · — |
| MoreMenu | 73.3 kB (+348 B) · 15.9 kB CSS (+385 B) | — | — · — · — |
| SelectorOption | 17.2 kB (+360 B) · 5.8 kB CSS (+371 B) | — | — · — · — |
| DropdownMenuSubMenu | 57.0 kB (+355 B) · 9.2 kB CSS (+375 B) | — | — · — · — |
| DropdownMenuRadioItem | 19.0 kB (+357 B) · 6.3 kB CSS (+372 B) | — | — · — · — |
| RadioListItem | 51.4 kB (+365 B) · 9.4 kB CSS (+362 B) | — | — · — · — |
| DropdownMenuCheckboxItem | 18.9 kB (+354 B) · 6.3 kB CSS (+372 B) | — | — · — · — |
| Item · Storybook | 13.9 kB (+354 B) · 5.2 kB CSS (+371 B) | 1,600 (+1) | — · — · — |
| DropdownMenuItem | 18.2 kB (+349 B) · 5.9 kB CSS (+370 B) | — | — · — · — |
| charts/Chart | 140.6 kB (+541 B) · 494 B CSS (0) | — | — · — · — |
| lab/Comments | 258.9 kB (+512 B) · 2.5 kB CSS (0) | — | — · — · — |
| lab/CommentThread | 253.6 kB (+500 B) · 2.4 kB CSS (0) | — | — · — · — |
| richtext/RichTextEditorToolbar | 225.4 kB (+499 B) · 454 B CSS (0) | — | — · — · — |
| lab/TransferListSelector | 83.1 kB (+469 B) · 1.9 kB CSS (0) | — | — · — · — |
| lab/TransferList | 75.5 kB (+355 B) · 1.7 kB CSS (0) | — | — · — · — |
| lab/Schedule | 60.2 kB (+7 B) · 3.9 kB CSS (0) | — | — · — · — |
| charts/ChartAxis | 6.7 kB (0) · 112 B CSS (0) | — | — · — · — |
| AlertDialog | 83.2 kB (0) · 13.1 kB CSS (0) | — | — · — · — |
| AppShell | 70.5 kB (0) · 12.2 kB CSS (0) | — | — · — · — |
| AspectRatio | 3.8 kB (0) · 3.6 kB CSS (0) | — | — · — · — |
| Avatar | 34.4 kB (0) · 5.4 kB CSS (0) | — | — · — · — |
| AvatarGroup | 37.1 kB (0) · 5.5 kB CSS (0) | — | — · — · — |
| AvatarGroupOverflow | 38.6 kB (0) · 5.5 kB CSS (0) | — | — · — · — |
| AvatarStatusDot | 7.2 kB (0) · 3.6 kB CSS (0) | — | — · — · — |
| Badge | 4.0 kB (0) · 4.0 kB CSS (0) | — | — · — · — |
| Banner | 47.0 kB (0) · 9.2 kB CSS (0) | — | — · — · — |
| BaseTypeahead | 55.1 kB (0) · 9.6 kB CSS (0) | — | — · — · — |
| Blockquote | 3.6 kB (0) · 3.5 kB CSS (0) | — | — · — · — |
| BottomSheetSwitcher | 17.1 kB (0) · 3.4 kB CSS (0) | — | — · — · — |
| Breadcrumbs | 16.0 kB (0) · 3.4 kB CSS (0) | — | — · — · — |
| Button | 45.0 kB (0) · 8.7 kB CSS (0) | — | — · — · — |
| ButtonGroup | 9.0 kB (0) · 3.4 kB CSS (0) | — | — · — · — |
| Calendar | 54.3 kB (0) · 9.3 kB CSS (0) | — | — · — · — |
| Card | 6.5 kB (0) · 6.4 kB CSS (0) | — | — · — · — |
| Carousel | 49.4 kB (0) · 9.5 kB CSS (0) | — | — · — · — |
| Center | 4.2 kB (0) · 4.6 kB CSS (0) | — | — · — · — |
| ChatComposer | 61.2 kB (0) · 10.6 kB CSS (0) | — | — · — · — |
| ChatComposerDrawer | 17.5 kB (0) · 4.7 kB CSS (0) | — | — · — · — |
| ChatComposerInput | 59.2 kB (0) · 10.0 kB CSS (0) | — | — · — · — |
| ChatComposerTokenElement | 59.2 kB (0) · 10.0 kB CSS (0) | — | — · — · — |
| ChatDictationButton | 45.6 kB (0) · 8.8 kB CSS (0) | — | — · — · — |
| ChatLayout | 48.3 kB (0) · 9.3 kB CSS (0) | — | — · — · — |
| ChatLayoutScrollButton | 45.6 kB (0) · 8.9 kB CSS (0) | — | — · — · — |
| ChatMessage | 16.7 kB (0) · 3.7 kB CSS (0) | — | — · — · — |
| ChatMessageBubble | 7.1 kB (0) · 3.7 kB CSS (0) | — | — · — · — |
| ChatMessageList | 22.8 kB (0) · 6.9 kB CSS (0) | — | — · — · — |
| ChatMessageMetadata | 19.8 kB (0) · 4.1 kB CSS (0) | — | — · — · — |
| ChatSendButton | 45.5 kB (0) · 8.7 kB CSS (0) | — | — · — · — |
| ChatSystemMessage | 6.8 kB (0) · 3.8 kB CSS (0) | — | — · — · — |
| ChatTokenizedText | 7.1 kB (0) · 4.0 kB CSS (0) | — | — · — · — |
| ChatToolCalls | 25.9 kB (0) · 8.2 kB CSS (0) | — | — · — · — |
| CheckboxInput | 48.4 kB (0) · 8.7 kB CSS (0) | — | — · — · — |
| CheckboxList | 42.8 kB (0) · 7.5 kB CSS (0) | — | — · — · — |
| Citation | 20.1 kB (0) · 4.4 kB CSS (0) | — | — · — · — |
| ClickableCard | 14.8 kB (0) · 7.0 kB CSS (0) | — | — · — · — |
| Code | 3.6 kB (0) · 3.5 kB CSS (0) | — | — · — · — |
| CodeBlock | 58.9 kB (0) · 10.0 kB CSS (0) | — | — · — · — |
| Collapsible | 23.3 kB (0) · 4.5 kB CSS (0) | — | — · — · — |
| CollapsibleGroup | 6.5 kB (0) · 3.3 kB CSS (0) | — | — · — · — |
| CommandPalette | 51.1 kB (0) · 12.3 kB CSS (0) | — | — · — · — |
| CommandPaletteEmpty | 6.2 kB (0) · 3.5 kB CSS (0) | — | — · — · — |
| CommandPaletteFooter | 17.0 kB (0) · 3.7 kB CSS (0) | — | — · — · — |
| CommandPaletteGroup | 3.5 kB (0) · 3.5 kB CSS (0) | — | — · — · — |
| CommandPaletteInput | 26.9 kB (0) · 7.4 kB CSS (0) | — | — · — · — |
| CommandPaletteItem | 10.3 kB (0) · 3.6 kB CSS (0) | — | — · — · — |
| CommandPaletteList | 16.0 kB (0) · 3.4 kB CSS (0) | — | — · — · — |
| ComplexSelector | 55.6 kB (0) · 10.4 kB CSS (0) | — | — · — · — |
| DateInput | 147.2 kB (0) · 15.5 kB CSS (0) | — | — · — · — |
| DateRangeInput | 71.2 kB (0) · 11.2 kB CSS (0) | — | — · — · — |
| DateTimeInput | 167.9 kB (0) · 17.2 kB CSS (0) | — | — · — · — |
| DialogHeader | 47.9 kB (0) · 10.2 kB CSS (0) | — | — · — · — |
| Drawer | 38.1 kB (0) · 8.2 kB CSS (0) | — | — · — · — |
| DrawerHeader | 48.0 kB (0) · 11.6 kB CSS (0) | — | — · — · — |
| DropdownMenuGroup | 6.2 kB (0) · 3.4 kB CSS (0) | — | — · — · — |
| DropdownMenuRadioGroup | 6.1 kB (0) · 3.4 kB CSS (0) | — | — · — · — |
| FileInput | 53.6 kB (0) · 9.7 kB CSS (0) | — | — · — · — |
| FormLayout | 6.4 kB (0) · 3.5 kB CSS (0) | — | — · — · — |
| Grid | 4.4 kB (0) · 3.8 kB CSS (0) | — | — · — · — |
| GridSpan | 3.5 kB (0) · 3.4 kB CSS (0) | — | — · — · — |
| IconButton | 45.1 kB (0) · 8.7 kB CSS (0) | — | — · — · — |
| InputGroup | 41.5 kB (0) · 7.3 kB CSS (0) | — | — · — · — |
| InputGroupText | 6.4 kB (0) · 3.9 kB CSS (0) | — | — · — · — |
| Kbd | 16.7 kB (0) · 3.6 kB CSS (0) | — | — · — · — |
| LayerProvider | 62.9 kB (0) · 9.4 kB CSS (0) | — | — · — · — |
| LayoutPanel | 7.6 kB (0) · 4.9 kB CSS (0) | — | — · — · — |
| Lightbox | 52.2 kB (0) · 9.1 kB CSS (0) | — | — · — · — |
| Link | 42.6 kB (0) · 6.9 kB CSS (0) | — | — · — · — |
| List | 5.4 kB (0) · 435 B CSS (0) | — | — · — · — |
| MetadataList | 16.7 kB (0) · 3.8 kB CSS (0) | — | — · — · — |
| MetadataListItem | 6.6 kB (0) · 3.5 kB CSS (0) | — | — · — · — |
| MobileNav | 51.8 kB (0) · 9.3 kB CSS (0) | — | — · — · — |
| MobileNavToggle | 45.4 kB (0) · 8.7 kB CSS (0) | — | — · — · — |
| MultiSelector | 73.0 kB (0) · 15.3 kB CSS (0) | — | — · — · — |
| NavHeadingMenu | 14.8 kB (0) · 3.5 kB CSS (0) | — | — · — · — |
| NavIcon | 3.5 kB (0) · 3.4 kB CSS (0) | — | — · — · — |
| NumberInput | 55.0 kB (0) · 10.1 kB CSS (0) | — | — · — · — |
| Outline | 22.2 kB (0) · 4.2 kB CSS (0) | — | — · — · — |
| OverflowList | 8.6 kB (0) · 3.7 kB CSS (0) | — | — · — · — |
| Overlay | 19.8 kB (0) · 4.0 kB CSS (0) | — | — · — · — |
| Popover | 51.8 kB (0) · 12.8 kB CSS (0) | — | — · — · — |
| ProgressBar | 10.8 kB (0) · 6.2 kB CSS (0) | — | — · — · — |
| RadioList | 40.7 kB (0) · 6.4 kB CSS (0) | — | — · — · — |
| ResizeHandle | 19.5 kB (0) · 4.3 kB CSS (0) | — | — · — · — |
| Section | 7.6 kB (0) · 6.3 kB CSS (0) | — | — · — · — |
| SegmentedControl | 81.6 kB (0) · 6.7 kB CSS (0) | — | — · — · — |
| SegmentedControlItem | 10.6 kB (0) · 4.0 kB CSS (0) | — | — · — · — |
| SelectableCard | 15.0 kB (0) · 7.2 kB CSS (0) | — | — · — · — |
| SideNav | 70.9 kB (0) · 10.1 kB CSS (0) | — | — · — · — |
| SideNavCollapseButton | 45.9 kB (0) · 8.8 kB CSS (0) | — | — · — · — |
| SideNavHeading | 58.9 kB (0) · 9.9 kB CSS (0) | — | — · — · — |
| SideNavItem | 54.6 kB (0) · 9.8 kB CSS (0) | — | — · — · — |
| SideNavSection | 6.6 kB (0) · 3.7 kB CSS (0) | — | — · — · — |
| Skeleton | 3.8 kB (0) · 3.8 kB CSS (0) | — | — · — · — |
| Slider | 88.6 kB (0) · 8.3 kB CSS (0) | — | — · — · — |
| StatusDot | 22.5 kB (0) · 5.2 kB CSS (0) | — | — · — · — |
| Step | 28.2 kB (0) · 5.6 kB CSS (0) | — | — · — · — |
| Stepper | 46.7 kB (0) · 8.9 kB CSS (0) | — | — · — · — |
| Switch | 86.9 kB (0) · 9.5 kB CSS (0) | — | — · — · — |
| Tab | 11.6 kB (0) · 4.2 kB CSS (0) | — | — · — · — |
| TableBody | 3.4 kB (0) · 3.3 kB CSS (0) | — | — · — · — |
| TableFooter | 3.4 kB (0) · 3.3 kB CSS (0) | — | — · — · — |
| TableHeader | 3.4 kB (0) · 3.3 kB CSS (0) | — | — · — · — |
| TableRow | 6.5 kB (0) · 4.0 kB CSS (0) | — | — · — · — |
| TableSelectionToolbar | 54.6 kB (0) · 11.8 kB CSS (0) | — | — · — · — |
| TabList | 35.4 kB (0) · 6.4 kB CSS (0) | — | — · — · — |
| TabMenu | 55.6 kB (0) · 9.8 kB CSS (0) | — | — · — · — |
| TextArea | 46.6 kB (0) · 8.8 kB CSS (0) | — | — · — · — |
| TextInput | 52.8 kB (0) · 9.9 kB CSS (0) | — | — · — · — |
| Thumbnail | 49.4 kB (0) · 10.3 kB CSS (0) | — | — · — · — |
| TimeInput | 131.6 kB (0) · 14.5 kB CSS (0) | — | — · — · — |
| Timer | 9.5 kB (0) · 5.9 kB CSS (0) | — | — · — · — |
| Timestamp | 21.3 kB (0) · 8.9 kB CSS (0) | — | — · — · — |
| Toast | 59.0 kB (0) · 9.0 kB CSS (0) | — | — · — · — |
| ToggleButton | 46.0 kB (0) · 8.8 kB CSS (0) | — | — · — · — |
| ToggleButtonGroup | 6.4 kB (0) · 3.4 kB CSS (0) | — | — · — · — |
| Token | 25.5 kB (0) · 4.8 kB CSS (0) | — | — · — · — |
| Tokenizer | 69.5 kB (0) · 17.1 kB CSS (0) | — | — · — · — |
| Toolbar | 34.8 kB (0) · 7.9 kB CSS (0) | — | — · — · — |
| TopNav | 53.4 kB (0) · 9.6 kB CSS (0) | — | — · — · — |
| TopNavHeading | 58.4 kB (0) · 9.7 kB CSS (0) | — | — · — · — |
| TopNavItem | 10.9 kB (0) · 4.0 kB CSS (0) | — | — · — · — |
| TopNavMegaMenu | 54.0 kB (0) · 10.6 kB CSS (0) | — | — · — · — |
| TopNavMegaMenuFeaturedCard | 6.9 kB (0) · 3.6 kB CSS (0) | — | — · — · — |
| TopNavMegaMenuItem | 11.0 kB (0) · 4.2 kB CSS (0) | — | — · — · — |
| TopNavMenu | 58.2 kB (0) · 9.9 kB CSS (0) | — | — · — · — |
| Typeahead | 62.9 kB (0) · 11.2 kB CSS (0) | — | — · — · — |
| TypeaheadItem | 6.3 kB (0) · 3.6 kB CSS (0) | — | — · — · — |
| lab/ChartDotGL | 7.4 kB (0) · 20 B CSS (0) | — | — · — · — |
| lab/ChartDotGLInteractive | 8.3 kB (0) · 20 B CSS (0) | — | — · — · — |
| lab/ChartHeatmapGL | 8.5 kB (0) · 20 B CSS (0) | — | — · — · — |
| lab/ChartStreamGL | 7.5 kB (0) · 20 B CSS (0) | — | — · — · — |
| lab/ChatEmojiPicker | 65.8 kB (0) · 773 B CSS (0) | — | — · — · — |
| lab/ChatReactionBar | 66.3 kB (0) · 1.1 kB CSS (0) | — | — · — · — |
| lab/ChatReasoning | 7.0 kB (0) · 967 B CSS (0) | — | — · — · — |
| lab/ChatTypingIndicator | 24.8 kB (0) · 612 B CSS (0) | — | — · — · — |
| lab/ChatUnreadDivider | 3.6 kB (0) · 348 B CSS (0) | — | — · — · — |
| lab/CircularProgress | 8.5 kB (0) · 1.1 kB CSS (0) | — | — · — · — |
| lab/CodeEditor | 14.0 kB (0) · 743 B CSS (0) | — | — · — · — |
| lab/CommentComposer | 104.5 kB (0) · 1.9 kB CSS (0) | — | — · — · — |
| lab/CommentPin | 46.8 kB (0) · 1.2 kB CSS (0) | — | — · — · — |
| lab/DiffViewer | 71.0 kB (0) · 2.1 kB CSS (0) | — | — · — · — |
| lab/EmojiPicker | 70.4 kB (0) · 722 B CSS (0) | — | — · — · — |
| lab/ListInput | 82.0 kB (0) · 1.6 kB CSS (0) | — | — · — · — |
| lab/LogStream | 8.0 kB (0) · 1.6 kB CSS (0) | — | — · — · — |
| lab/SelectableCardGroup | 50.2 kB (0) · 162 B CSS (0) | — | — · — · — |
| lab/Stat | 4.2 kB (0) · 646 B CSS (0) | — | — · — · — |
| lab/SVGIcon | 7.6 kB (0) · 764 B CSS (0) | — | — · — · — |
| lab/ThreeDScatterGL | 7.6 kB (0) · 20 B CSS (0) | — | — · — · — |
| lab/TourStep | 57.9 kB (0) · 590 B CSS (0) | — | — · — · — |
| richtext/RichTextEditor | 278.2 kB (0) · 2.6 kB CSS (0) | — | — · — · — |
| richtext/RichTextView | 258.4 kB (0) · 2.3 kB CSS (0) | — | — · — · — |
Package size: @astryxdesign/core 6.33 MB packed (+7.4 kB); no new dependencies.
API changes: none in this pull request
Committed API report behind this build: cli 8 rows, theme-butter 1 row, theme-chocolate 1 row, theme-gothic 1 row, theme-matcha 1 row, theme-neutral 1 row, theme-stone 1 row, theme-y2k 1 row; run pnpm api:report.
Wide change (shared styles: packages/core/src/utils/interactionOverlay.stylex.ts, packages/core/src/utils/rowPressLayer.stylex.ts): add ci:run-visual, ci:run-a11y or ci:run-rtl to run them.
Generated by PR Enrichment workflow | View full report
Supersedes #8077 by @vjeux, rebased onto
mainand stacked on #9758 (which supersedes #8890). The change is vjeux's: his commit authorship is kept, and the changeset credits him. This pull request targets #9758's branch so only its own change shows. It moves tomainonce #9758 merges.User impact
This affects people who tap a list row on a phone: a
ListItem, an actionableItem(and the menus and selectors built on one), or aTreeListrow.Rebase onto main (after #9758 merged)
#9758 merged as b206f63, so this branch is now one commit on
main. The range-diff of this pull request's own change, before (on #9758's branch) and after (onmain), is CodeBunny's requested fixes and nothing else:@astryxdesign/a11y-spec/playwright, asmainnow requires. The decoder browser's context callskeepRequestsLocal.privateAttributes/press.stylex.tssays a caller's::before"merges into" the layer while pressed. A spec comment says 200 ms, not 250 ms.No product code changed. The List, Item, TreeList, RadioList and press utility suites pass on the rebased head, apart from two RadioListItem source-build cases that need a full install.
Problem and solution fit
Under a finger, the press now paints on a layer under the row's content (the row's
::before) and fades that layer's opacity. Engines run an opacity animation on the compositor, so it keeps advancing while the main thread is blocked. #9758 makes the press controller end the press when that fade has played, not on a clock from the lift.The layer exists only while a press is on or releasing. At rest, a row's own
::beforeand its stacking are as they were. During a press, a caller's own::beforemerges into the layer, so decoration that should stay through a press belongs on::afteror a child element. A pressed row's divider keeps its resting colour.Changes since #8077
mainchanged under #8077 while it waited:architecture:public-component-apiINV23 and theno-raw-private-attributerule now keep such styles in the attribute's owner module, soutils/rowPressLayer.tsbecomesprivateAttributes/press.stylex.ts. The styles are unchanged. Its clocks stay inutils/rowPressLayer.stylex.ts.mainthe strength's release follows the theme's tempo, capped under Reduce Motion (spec:AST-108FR4). The row layer now plays that same clock, the same way Switch's press layer does, so the layer and the strength land together (FR4a).interactionOverlay.cell.test.tsnow checks that both use one clock, where it used to check the old Reduce Motion constant.cellWithoutLayer, which carries the row's empty image, its strength release and the absence of a::beforebox and stacking context, so RadioListItem sets none of those besidecell(INV23). The radio-row interaction case checks the row is no stacking context.swipeRootalso dropsisolation, because its transform already isolates the row.motion/uikit/list.ts, which only re-exportedtiming.press. It is dropped with its index, README and MEASUREMENTS rows, andcellPressConstsis held totiming.pressdirectly. Record citations left the source comments.Expected behavior and authority
usePressFeedback("fades the release over 200 ms");component:ListFR11;spec:AST-094FR5 and FR13;design:user-statesDR1 and DR5. A mouse row is unchanged (spec:AST-017FR59).Record change,
component:List: the owner decided on 2026-10-25 that on touch, list rows may use::beforeas their press layer, and a pressed row's divider keeps its resting colour. Specs & Decisions gave the record text:::beforeas the press layer, and a pressed row's divider keeps its resting colour. A caller's own::beforeon such a row merges into the press layer only while it is pressed; use::afteror a child for decoration."Smallest restoration
utils/pressFeedback.tswrites, so there is no second press machine.Verification
interactionOverlay.cell.test.ts(17 cases) andinteractionOverlay.exports.test.tsare new. The stylesheet test's compositor-layer block checks the shipped CSS.@babel/preset-typescript, which my local export tree lacks; CI runs them.RadioGroup.a11y.test.tsx's "runs the DOM layer" took 19.5 s here against 14.8 s on fix(core): a touch press's release lasts until its fade has played #9758's head, on the same loaded machine. Its 30 s limit holds on an idle machine. I'm noting it in case CI runs close to that limit.ListRowPress,ListRowPressPaths), Chromium 154:::before, and the storyboard.addEventListener('pointerup', () => requestAnimationFrame(() => requestAnimationFrame(() => { const end = performance.now() + 700; while (performance.now() < end); })), {once: true}). Then press and lift a row: once the release has started, the highlight keeps fading through the freeze. Script that runs in the lift's own handlers, before the next frame, still holds the row pressed until it ends.Scope
Writing rubric results
component:List(FR11's added sentences, DEC-6, the compatibility row, OQ1 removed):::beforeintact, with its reason.