Repository navigation
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
AI review status for this pull request.
|
d91ce8c to
9795834
Compare
9795834 to
6a3b76c
Compare
There was a problem hiding this comment.
This pull request needs an engineering owner's decision before it can be approved: no current spec governs List's new hasAnimatedChanges and followingContent props, and the draft AST-094 FR8 (a move fades through under Reduce Motion) conflicts with the code keeping the slide (REDUCED_MOTION = 'keep'). An engineering owner can provide or identify the governing List spec, settle FR8 against 'keep', and record the decision by commenting /approve-spec <full head SHA> on this pull request; the author may do so if they are an engineering owner. Independently of that decision, these need changes:
- The diff carries the whole stack (17,607 authored lines); once the predecessor pull requests land it should contain only this pull request's two commits (998 lines).
- With
followingContent="moves", content below the list that layout did not move (for example a footer held bymargin-top: auto) is shifted by the list's height change and slides back (contentBelowinrowMotion.ts). - Row positions are recorded only on
Listrenders, so a layout change between renders (a rotation, a row growing on its own) replays as motion at the next render. - With
hasAnimatedChangesoff, everyListstill subscribes to(pointer: coarse)and reads every row's layout on each render.
[Automated review]
| // Below, not beside: a flex or grid neighbour does not move when the | ||
| // component changes height. | ||
| const top = | ||
| next.getBoundingClientRect().top - presentation(next, 'y', motionNow()); | ||
| if (top < bottom - 1) { | ||
| continue; | ||
| } | ||
| below.push(next); |
There was a problem hiding this comment.
This keeps any in-flow element whose top is below the list, but never checks that layout moved it. When an ancestor absorbs the list's height change (a footer held at the bottom by margin-top: auto, the block after a grid row kept open by a taller column, a fixed-height or overflow: hidden parent), useListUpdateMotion still shifts it by -grew and springs it back, so content that did not move jumps and slides. Recording each candidate's position at every commit, as the rows' are, and springing it by its own measured shift would avoid that.
| const before = placesRef.current; | ||
| placesRef.current = places; | ||
| const height = element.offsetHeight; | ||
| const grew = height - heightRef.current; | ||
| heightRef.current = height; |
There was a problem hiding this comment.
The baseline is only refreshed here, after a List render. A rotation that rewraps rows, a row that grows from its own state, or a late font moves rows without rendering List, and the next unrelated render replays that change as motion for the rows and the content below. Refreshing the places and height when layout changes without a commit (for example from a resize observation held only while hasAnimatedChanges is on) would stop the replay.
| enabled: boolean, | ||
| followingContent: 'moves' | 'stays' = 'moves', | ||
| ): void { | ||
| const animates = useMotionPresentation() === 'uikit' && enabled; |
There was a problem hiding this comment.
List calls this hook for every instance, so with hasAnimatedChanges off each List still subscribes to (pointer: coarse) here and, below, reads every row's offsetParent/offsetTop and its own offsetHeight on every render before the enabled check. Returning before the measurements when the prop is off (clearing placesRef), and reading the pointer inside the effect instead of subscribing, keeps the opt-in free for lists that do not use it.
PR Analysis Report📚 Storybook Preview🧪 Sandbox PreviewModified ComponentsItem (@astryxdesign/core) · View in Storybook
TreeList (@astryxdesign/core) · View in Storybook
Component Size
Sizes are gzip. Import counts the JavaScript the import loads eagerly; CSS counts the package stylesheet rules referenced by the modules it can load from its own package, lazily loaded ones included.
|
| Component | JavaScript | CSS |
|---|---|---|
| core/CheckboxList | +7.7KB |
+22B |
| core/CheckboxListItem | +3.8KB |
+609B |
| core/ContextMenu | +12.0KB |
+626B |
| core/List | +7.8KB |
+40B |
| core/ListItem | +3.8KB |
+609B |
| core/Markdown | +12.0KB |
+613B |
| core/PowerSearch | +11.8KB |
+354B |
| core/Table | +12.0KB |
+607B |
| core/TableCell | +12.0KB |
+609B |
| core/TableHeaderCell | +12.0KB |
+609B |
| core/TreeList | +11.0KB |
+383B |
Bundle Size Summary
Gzip figures measure each package's entry file, not the code an import pulls in.
| Package | Entry (ESM) | Entry gzip before | Entry gzip after | Delta |
|---|---|---|---|---|
| @astryxdesign/core | 4.9KB | 1.2KB | 1.2KB | 0B |
| Package stylesheet | Gzip before | Gzip after | Delta |
|---|---|---|---|
| @astryxdesign/core | 35.1KB | 35.8KB | +647B |
Accessibility Audit
Status: 2 accessibility violation(s) found — 2 serious.
Radio group pattern - 1 issue(s)
- 🟠 serious: Ensure the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds
- Rule:
color-contrast· Affects 3/11 stories · Learn more - WCAG: 1.4.3 (Level AA)
- Rule:
RadioList - 1 issue(s)
- 🟠 serious: Ensure the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds
- Rule:
color-contrast· Affects 3/14 stories · Learn more - WCAG: 1.4.3 (Level AA)
- Rule:
Visual Regression
57 of 504 shot(s) changed.
A change here is a question, not a failure: check whether the after is the
picture you intended. Record that review on the PR. Baseline maintenance is an
explicit dispatch of CI; this report never rewrites the baseline or adds a release gate.
| component | story | theme | mode | pixels |
|---|---|---|---|---|
| Selector | Indicator Space Bottom Sheet Narrow End RTL | neutral | light | 753,933 |
| Selector | Indicator Space Bottom Sheet Wide Start | neutral | light | 740,072 |
| RichTextEditor | Markdown parity: side by side | probe | dark | 558,840 |
| RichTextEditor | Markdown parity: side by side | probe | light | 540,756 |
| Chat | Full AI Chat | probe | light | 230,823 |
| Chat | Full AI Chat | probe | dark | 229,805 |
| AppShell | With Mobile Nav | probe | light | 217,010 |
| AppShell | With Mobile Nav | probe | dark | 216,960 |
| Collapsible | Dividers | probe | dark | 66,445 |
| Collapsible | Dividers | probe | light | 66,445 |
| AppShell | Playground | neutral | dark | 65,198 |
| AppShell | Playground | neutral | light | 63,788 |
| Chat | Default | neutral | dark | 36,304 |
| Chat | Default | neutral | light | 35,767 |
| Chat | Empty State | probe | light | 23,531 |
| Chat | Empty State | probe | dark | 22,622 |
| ChatAutoScroll | Scroll Behavior Comparison | neutral | dark | 15,378 |
| ChatAutoScroll | Scroll Behavior Comparison | neutral | light | 14,107 |
| Chat | With Attachments | probe | light | 13,406 |
| Chat | With Attachments | probe | dark | 11,232 |
and 37 more.
Generated by PR Enrichment workflow | Storybook | Sandbox | View full report
6a3b76c to
5df8024
Compare
There was a problem hiding this comment.
This pull request needs an engineering owner's decision before it can be approved: no current spec governs List's new hasAnimatedChanges and followingContent props, and the draft AST-094 FR8 (a move fades through under Reduce Motion) conflicts with the code keeping the slide (REDUCED_MOTION = 'keep'). An engineering owner can provide or identify the governing List spec, settle FR8 against 'keep', and record the decision by commenting /approve-spec <full head SHA> on this pull request; the author may do so if they are an engineering owner. Independently of that decision, these need changes:
- The diff carries the whole stack (18,305 authored lines); once the predecessors land, or after retargeting and rebasing onto main, it should contain only this pull request's two commits (1,191 lines).
- With
followingContent="moves", content below the list that layout did not move (for example a footer held bymargin-top: auto) is shifted by the list's height change and slides back. - The same transient
translatere-anchorsposition: fixedelements inside that content, so a fixed action bar jumps up the screen for about half a second. - Row positions are recorded only on
Listrenders, so a layout change between renders replays as motion at the next render. - With
hasAnimatedChangesoff, everyListstill subscribes to(pointer: coarse)and reads every row's layout on each render.
[Automated review]
| if ( | ||
| style.position === 'absolute' || | ||
| style.position === 'fixed' || | ||
| style.position === 'sticky' || | ||
| style.display === 'none' | ||
| ) { | ||
| continue; | ||
| } |
There was a problem hiding this comment.
contentBelow checks only the candidate's own position, but the translate it then writes makes the candidate the containing block of any position: fixed element inside it. In a fixture with an action bar fixed 16px above the bottom of a 720px viewport, inside a section after the list, removing a row moved the bar from 660px to 344px from the top, and it stayed re-anchored until releaseMotion cleared the translate about 500 ms later. Skipping candidates that contain fixed-position descendants, or defaulting followingContent to 'stays', would avoid it.
5df8024 to
6ffa28d
Compare
6ffa28d to
84c1f76
Compare
There was a problem hiding this comment.
This pull request needs an engineering owner's decision before it can be approved: no current spec governs List's new hasAnimatedChanges and followingContent props, and the draft AST-094 FR8 (a move fades through under Reduce Motion) conflicts with the code keeping the slide (REDUCED_MOTION = 'keep'). An engineering owner can provide or identify the governing List spec, settle FR8 against 'keep', and record the decision by commenting /approve-spec <full head SHA> on this pull request; the author may do so if they are an engineering owner. Independently of that decision, these need changes:
- The diff carries the whole stack (18,321 authored lines); once the predecessors land, or after retargeting onto the stack's previous branch, it should contain only this pull request's two commits (1,191 lines).
- With
followingContent="moves", content below the list that layout did not move (for example a footer held bymargin-top: auto) is shifted by the list's height change and slides back. - The same transient
translatere-anchorsposition: fixedelements inside that content, so a fixed action bar jumps up the screen until the motion ends. - Row positions are recorded only on
Listrenders, so a layout change between renders replays as motion at the next render. - With
hasAnimatedChangesoff, everyListstill subscribes to(pointer: coarse)and reads every row's layout on each render.
[Automated review]
…and turn it around early in the curve The mid-motion case held every sampled frame's step to a fixed share of the travel, so a frame the browser delayed under load carried two frames' travel into one sample and failed (WebKit, a 55 px step on a 126 px group). Each sample now carries its frame's time, and a step is held to twice the outline spring's peak speed times the time since the last frame, plus a pixel (a degree for the chevron). The page now turns the expand around itself, on the first frame the block moves, so the turn around lands far from either end of the curve under any load; a tap after a poll often landed it near the end, where a restart moved too little to tell. A mutation that restarts the collapse from rest fails it (a 112 px step in one 17 ms frame, against a 40 px bound); the product passes, three runs in each engine.
There was a problem hiding this comment.
This needs an engineering owner's decision before it can be approved: this head carries #8088's rewording of component:TreeList FR8, still proposed on main, so that under a finger the rows after a collapsing group hold until its rows have slid under the parent. An engineering owner records that decision by commenting /approve-spec 2aa3b3b332491c3a07850657b2bfd6e9d064d5e6 on this pull request (the author may do so as an engineering owner), or FR8 is settled through #8088 first. The List change itself follows component:List FR12; after that decision, two changes remain:
- Land the eight predecessor pull requests first, so this diff is the 2,067-line List change rather than 35,919 authored lines.
- Report the base-against-head size of every component the diff changes, or let the existing size line cover it once the predecessors land.
[Automated review]
| | FR5 | `TreeListItemData` MUST admit `className`, `style`, and `xstyle` as one per-item styling seam. Every supplied value MUST compose on the same Item row box; an implementation MUST NOT treat current support for only one mechanism as authority to exclude the other two. | Owner decision; `architecture:public-component-api` INV5–INV6; `spec:AST-002/DEC-1` | Settled intent; implementation may be staged | | ||
| | FR6 | The Item row box is the logical element that paints the row, owns its hover state, and contains Start content, Item label and description, and End content. The styling seam MUST NOT move to the semantic `<li>` or another element without a compatibility decision. | Owner decision and the caller-owned per-row interaction boundary | Settled intent | | ||
| | FR7 | A click on an Item row outside its Item label performs the row's action: an Item with `onClick` runs it; otherwise an Item with children toggles; otherwise an Item whose only action is its link follows that link as Enter does, with modified and middle clicks following the shared enlarged-surface rule in `family:navigation-destinations`. The Chevron and controls in Start content and End content keep their own activation, and a disabled Item does nothing. | Owner decision; `family:navigation-destinations` | Settled intent; implementation may be staged | | ||
| | FR8 | Expanding or collapsing an Item with children MUST play its group's own motion: under a coarse primary pointer its rows slide out from under the parent as one block, clipped to the group's box, and back on collapse, a collapsing group's rows leaving the accessibility tree as the slide starts, while the Chevron turns, by transform only (`spec:AST-094` FR4–FR5); under a fine pointer the group appears and leaves at once. With an `animatedLayout` handle, the group and the rows after it follow `spec:AST-105`'s shared motion under every pointer (FR4, FR5); without one, the group's own slide stays touch-only and the rows after the group take their places at once (`spec:AST-105` FR2): as an expanding group appears and, under a coarse primary pointer, as a collapsing group's rows finish sliding under the parent, until which they hold their places. | UIKit outline list (`UICollectionView`), iOS 26.2, whose rows after a closing group hold until its rows have slid under the parent (0.24–0.37 s measured); `spec:AST-105` FR2 | Proposed; awaiting an engineering owner's approval | |
There was a problem hiding this comment.
This rewords FR8, still "Proposed; awaiting an engineering owner's approval" on main: without a handle, the rows after a collapsing group now hold until its rows have slid under the parent, and the verification row (L279) fails rows that move earlier. An engineering owner records the decision by commenting /approve-spec with this head's full SHA on the pull request (the author may, as an engineering owner), or FR8 is settled through #8088 first.
…ot the timing table From review (an inline comment on meta#8077 about this line): the press controller, which every pressable component loads, imported the whole UIKit timing table for one value, its reduced-motion cap, while architecture:component-style-authoring INV6 ships a component only the rows of a shared table it uses. It now reads the fade's own export, reducedMotionCrossFade.durationMs, as the animator does; the value is the same 200 ms. Red first: a bundle of pressFeedback.ts carried the table's other rows (stateChangeMs, buttonReleaseMs) on the change before this one; it now carries the fade alone.
…th dividers drawn From review on meta#8077: component:List FR11's verification asks for a pressed row between two divided rows, and no case used a list with dividers. A touch case opens the Interactive story with hasDividers, holds the middle row and releases it, and checks the divider above it and its own divider below, by computed border and by the pixels of each divider's strip, while held, halfway through the release fade and after it; each strip must differ from the row's surface just inside it at rest, so a match is a drawn divider. It passes in Chromium and WebKit. A page style that hides a pressed row's divider fails it while the row is held.
…finger From review on meta#8085: component:List FR10's verification asks for every density, and the browser cases opened only balanced and compact lists. A touch case opens the Interactive story at density="spacious" (the density of the bottom-sheet menu rows): its one-line rows are 48 px, already past the 44 px target, so their layers reach 0 px on both sides and no two targets overlap. The story's density comes through a new args option of the spec's openStory helper. It passes in Chromium and WebKit. With the target raised to 56 px, it fails: the rows then grow.
… judges its first frame on that frame's time 'a spring started right before a block plays in full after it' waited a fixed 300 ms of wall clock after the block, then read the screencast. In the full interaction run on a loaded box no moving frame had reached the screencast by then, and the proof failed on an empty read. It now waits until the page shows the box a quarter of the way along and three more frames reach the screencast. Its bound changes form, not strength: the curve's own start (the handle's started) must come after the block, and the first moving frame may be no further along than a curve started a frame before the block ended, at that frame's own time, which is the old 30 px for a frame right after the block and still holds when a loaded machine sends frames sparsely. A curve dated at the call is 107 px in.
…tor's notes drop an undefined law The animator spec's header cited 'law M1' for compositor-only motion and useAnimator.ts cited 'law M4' for an animator that never re-renders, and no document defines either. The spec now cites spec:AST-094 FR5, which keeps touch motion to transform, translate, scale, rotate and opacity; useAnimator's sentence says what it means without the citation.
…e surface releaseAnimationsOf counted only the press owner's own animations (on it, or its ::before or ::after). A component that draws its release on a descendant, such as CheckboxInput's release on its box wrapper's ::after, then lost the end of it: fading went at the 200 ms clock while the 250 ms release still ran, and the frame it went relaid and repainted the box. It now also counts a CSS release animation on a descendant whose nearest press owner (closest [data-astryx-pressable]) is this element, the same nearest-owner rule the layers read by, so a press owner nested inside plays its own press's release and never extends this one. Red first: a unit case whose release runs on a box inside the row found fading gone at the clock on the change before this one; it now holds to the release's end. A second case keeps a nested press owner's release, on it or inside it, out of the row's.
There was a problem hiding this comment.
This needs an engineering owner's decision before it can be approved: this head carries #8088's rewording of component:TreeList FR8, still proposed on main, so that under a finger the rows after a collapsing group hold until its rows have slid under the parent. An engineering owner records that decision by commenting /approve-spec 8134a0f642fbb6979aefb81bf356a21d065714cb on this pull request (the author may do so as an engineering owner), or FR8 is settled through #8088 first. The List change itself follows component:List FR12; after that decision, two changes remain:
- Land the eight predecessor pull requests first, at their current heads (#8360 has moved past the commit this branch carries), so this diff is the 2,067-line List change rather than 36,093 authored lines.
- Report the base-against-head size of every component the diff changes, or let the existing size line cover it once the predecessors land.
[Automated review]
| | FR5 | `TreeListItemData` MUST admit `className`, `style`, and `xstyle` as one per-item styling seam. Every supplied value MUST compose on the same Item row box; an implementation MUST NOT treat current support for only one mechanism as authority to exclude the other two. | Owner decision; `architecture:public-component-api` INV5–INV6; `spec:AST-002/DEC-1` | Settled intent; implementation may be staged | | ||
| | FR6 | The Item row box is the logical element that paints the row, owns its hover state, and contains Start content, Item label and description, and End content. The styling seam MUST NOT move to the semantic `<li>` or another element without a compatibility decision. | Owner decision and the caller-owned per-row interaction boundary | Settled intent | | ||
| | FR7 | A click on an Item row outside its Item label performs the row's action: an Item with `onClick` runs it; otherwise an Item with children toggles; otherwise an Item whose only action is its link follows that link as Enter does, with modified and middle clicks following the shared enlarged-surface rule in `family:navigation-destinations`. The Chevron and controls in Start content and End content keep their own activation, and a disabled Item does nothing. | Owner decision; `family:navigation-destinations` | Settled intent; implementation may be staged | | ||
| | FR8 | Expanding or collapsing an Item with children MUST play its group's own motion: under a coarse primary pointer its rows slide out from under the parent as one block, clipped to the group's box, and back on collapse, a collapsing group's rows leaving the accessibility tree as the slide starts, while the Chevron turns, by transform only (`spec:AST-094` FR4–FR5); under a fine pointer the group appears and leaves at once. With an `animatedLayout` handle, the group and the rows after it follow `spec:AST-105`'s shared motion under every pointer (FR4, FR5); without one, the group's own slide stays touch-only and the rows after the group take their places at once (`spec:AST-105` FR2): as an expanding group appears and, under a coarse primary pointer, as a collapsing group's rows finish sliding under the parent, until which they hold their places. | UIKit outline list (`UICollectionView`), iOS 26.2, whose rows after a closing group hold until its rows have slid under the parent (0.24–0.37 s measured); `spec:AST-105` FR2 | Proposed; awaiting an engineering owner's approval | |
There was a problem hiding this comment.
This rewords FR8, still "Proposed; awaiting an engineering owner's approval" on main: without a handle, the rows after a collapsing group now hold under a coarse primary pointer until its rows have slid under the parent, and the verification row (L279) fails rows that move earlier. An engineering owner approves or rejects this wording by commenting /approve-spec with this head's full SHA on the pull request, or it is settled through #8088 first.
…ide lands when its retire runs first Reported as a WebKit hang of the held-dip proof under an 8-worker run: the proof's wait for the call's finished never settled. Instrumented, the page kept drawing frames (about 60 a second) while the element sat at opacity 0 where it started, with no animation left: the held fade out had been finished from outside, then retired (cancelled) by its timer before the frame that reads the call's start, so the call never started and its landing was never armed. This case orders the retire timer before that frame and fails on this head: the move never lands.
… that cannot come [skip ci] The retire timer cancels a finished animation once the main thread runs past its curve. When a sampler finished a call's animations before the browser started them, a loaded page could run that timer before the frame that reads the call's start: the cancel left only idle animations, the start was never read, and the call's Reduce Motion landing was never armed, so the element stayed hidden where it started and the call never settled. The retire now reads such a call's start from the finished animation before cancelling it, and with nothing left to wait on the call keeps its own clock, so its landing still runs.
… curve, so only rows ship the row layer's constants The shared press overlay (utils/interactionOverlay.stylex.ts) read its release curve from the row layer's constants (cellPressConsts in rowPressLayer.stylex.ts). The overlay reaches nearly every component, and .stylex modules are side effects in core's package.json, so every component then shipped the row layer's constants (the repository's size report: +17-47 B gzip on each component without a press row), against architecture:component-style-authoring INV6. The overlay goes back to its own copy of the curve, main's literal; it and the row layer's copy are both the constants table's timing.press.highlightOutCurve, and a unit case fails if the overlay's drifts. Red first: a source bundle of Card carried the row layer's constants (cellRelease) on the change before this one; it now carries the overlay and none of them.
There was a problem hiding this comment.
This needs an engineering owner's decision before it can be approved: this head carries #8088's rewording of component:TreeList FR8, still proposed on main, so that under a finger the rows after a collapsing group hold until its rows have slid under the parent. An engineering owner records that decision by commenting /approve-spec 0535acf7004d0cbc538e836f2344030f1557d6bc on this pull request (the author may do so as an engineering owner), or FR8 is settled through #8088 first. The List change itself follows component:List FR12, and the latest push's animator and press-release fixes come with tests that fail before them; after that decision, two changes remain:
- Land the eight predecessor pull requests first, so this diff is the 2,067-line List change rather than 36,239 authored lines.
- Report the base-against-head size of every component the diff changes, or let the existing size line cover it once the predecessors land.
[Automated review]
| | FR5 | `TreeListItemData` MUST admit `className`, `style`, and `xstyle` as one per-item styling seam. Every supplied value MUST compose on the same Item row box; an implementation MUST NOT treat current support for only one mechanism as authority to exclude the other two. | Owner decision; `architecture:public-component-api` INV5–INV6; `spec:AST-002/DEC-1` | Settled intent; implementation may be staged | | ||
| | FR6 | The Item row box is the logical element that paints the row, owns its hover state, and contains Start content, Item label and description, and End content. The styling seam MUST NOT move to the semantic `<li>` or another element without a compatibility decision. | Owner decision and the caller-owned per-row interaction boundary | Settled intent | | ||
| | FR7 | A click on an Item row outside its Item label performs the row's action: an Item with `onClick` runs it; otherwise an Item with children toggles; otherwise an Item whose only action is its link follows that link as Enter does, with modified and middle clicks following the shared enlarged-surface rule in `family:navigation-destinations`. The Chevron and controls in Start content and End content keep their own activation, and a disabled Item does nothing. | Owner decision; `family:navigation-destinations` | Settled intent; implementation may be staged | | ||
| | FR8 | Expanding or collapsing an Item with children MUST play its group's own motion: under a coarse primary pointer its rows slide out from under the parent as one block, clipped to the group's box, and back on collapse, a collapsing group's rows leaving the accessibility tree as the slide starts, while the Chevron turns, by transform only (`spec:AST-094` FR4–FR5); under a fine pointer the group appears and leaves at once. With an `animatedLayout` handle, the group and the rows after it follow `spec:AST-105`'s shared motion under every pointer (FR4, FR5); without one, the group's own slide stays touch-only and the rows after the group take their places at once (`spec:AST-105` FR2): as an expanding group appears and, under a coarse primary pointer, as a collapsing group's rows finish sliding under the parent, until which they hold their places. | UIKit outline list (`UICollectionView`), iOS 26.2, whose rows after a closing group hold until its rows have slid under the parent (0.24–0.37 s measured); `spec:AST-105` FR2 | Proposed; awaiting an engineering owner's approval | |
There was a problem hiding this comment.
This rewords FR8, still "Proposed; awaiting an engineering owner's approval" on main: without a handle, the rows after a collapsing group now hold under a coarse primary pointer until its rows have slid under the parent, and the verification row (L279) fails rows that move earlier. It also ties the hold to the slide, while under Reduce Motion those rows fade instead and the code holds the rows after the group until the fade ends. An engineering owner can approve this wording with /approve-spec and this head's full SHA, or FR8 can be settled through #8088 first.
The changeset, the release lookup's comment and its unit case named a checkbox's box as a part that draws its release inside the surface. No component in this change does: the checkbox's release runs on the row around it. The lookup still counts a release on a descendant whose nearest press owner is the surface; the text now says that without the example.
…ease change [skip ci]
… change [skip ci]
…get change [skip ci]
… change [skip ci]
There was a problem hiding this comment.
This needs an engineering owner's decision before it can be approved: this head carries #8088's rewording of component:TreeList FR8, still proposed on main, so that under a finger the rows after a collapsing group hold until its rows have slid under the parent. An engineering owner records that decision by commenting /approve-spec 0c59447f97aade3afceb1291c6ed40f0b2a0cc9a on this pull request (the author may do so as an engineering owner), or FR8 is settled through #8088 first. The List change itself follows component:List FR12, and the latest push's animator and press-overlay fixes come with tests that fail before them; after that decision, two changes remain:
- Land the eight predecessor pull requests first, so this diff is the 2,067-line List change rather than 36,338 authored lines.
- Report the base-against-head size of every component the diff changes, or let the existing size line cover it once the predecessors land.
[Automated review]
Review only this PR's change: vjeux/astryx@0249148...cd9fb69 (depends on #8088).
User need
On iOS, when a table's rows change, the others slide to their new places, so you can see what changed. In Astryx, a List's rows jumped in one frame.
Problem and solution fit
What led to it: this is part of a program to make Astryx's list components behave like iOS tables on phones, and nothing animated the difference between two renders.
component:ListFR12 (current) puts row updates on theanimatedLayouthandle (#8568).With a handle and an
itemKeyon each row, an inserted row enters, and a removed row collapses in place, inert and hidden from assistive technology until it's gone. A row crossing others rides above them on the list's surface. The rows after a change, and the content after the list, glide to their new places. Every part starts on one frame, on the module's shared motion, under every pointer. A row without anitemKeychanges at once, and so does the whole list without a handle. Reduce Motion follows the module's policy. The handle is in List's context, so a row's own layout changes can report to it.A row a swipe carried away exits as itself, with no element added: as a change is measured, a row's own translation moves onto its children, so its box sits at its place in the list while it shows the same thing, and a row that stays gets the translation back after the commit. On a long list, an update reads only the rows near the visible part: the list reports the rows in view and its last row, found by halving, so the module's walk stays near the view.
Size of this pull request's own change (gzip JS/CSS, the compare link's base → head): List +8,011 B JS, CheckboxList +7,850 B JS, ListItem +215 B JS and +80 B CSS; TreeList and Item unchanged. Item, TreeList, RadioList and
useMenuPresschange in the pull requests this one depends on, which report their own sizes.Current specification
component:ListFR12 (current);spec:AST-105FR1–FR5;spec:AST-103DEC-4 (itemKeyis a row's identity).Public delta and caller burden
<List animatedLayout={useAnimatedLayout({containerRef})}>with<ListItem itemKey={id}>.Design and alternatives
Evidence
iOS 26.2 simulator, Mobile Safari, touch input; a real device is the release gate. Recorded at 1eb6bc0; at this head, removal, insert, reorder and swipe were recorded again and match.
With a mouse, from the List story in Chromium (remove, insert, move):
From the automated reviews, each with a test that failed on the previous head where it applied: one update of a 1,000-row list reads 234 row places (4,001 before); a removal near the top makes no pairwise crossing checks (996,076 before); the swiped row exits as itself, at its place; plus the earlier fixes (an inserted row fades in under Reduce Motion, the last row's exit never paints over the content after the list, a caller's inline
overflow-ystays).Scope
Testing
What was run: steps 1–4 and Reduce Motion under a finger on the iOS 26.2 simulator (the clips above), step 5 with a mouse (the recordings above); unit tests (2215, the List, Item, TreeList, motion, utils and hooks suites); the List, TreeList and animated layout browser specs in Chromium (132, 2 skipped) and WebKit (117, 17 skipped).