Skip to content

fix(BottomSheet): a replaced sheet moves down to a shorter entering sheet again - #8920

Merged
imdreamrunner merged 2 commits into
mainfrom
fix/bottom-sheet-replace-alignment
Oct 30, 2026
Merged

imdreamrunner merged 2 commits into
mainfrom
fix/bottom-sheet-replace-alignment

Conversation

@imdreamrunner

Copy link
Copy Markdown
Contributor

User impact

A flow written the way the BottomSheetSwitcher deprecation recommends — each step its own BottomSheet, one state value naming the open step — runs the replace handoff between steps. On every switch to a shorter step, the taller sheet being replaced stayed at its own height while the new sheet rose in front of it, and then the band of it showing above the new sheet faded out. The sheet's height changed by a fade, not by the sheet adjusting. This hit every replace to a shorter sheet, modal and hasScrim={false}, on touch and mouse alike. BottomSheetSwitcher flows were not affected.

Problem and solution fit

  • Why the defect occurs. alignmentOffsetForPanels (in bottomSheetStack.ts) read where the entering sheet would rest from enteringPanel.parentElement. The handoff was written when a standalone panel sat inside a positioner wrapper. feat(Popover): presentation — a BottomSheet on a phone when asked, as iOS presents a popover #8229 made the open dialog the panel's positioner and removed that wrapper. feat(core): BottomSheet stacks by open order, replaces on a same-update switch, and BottomSheetSwitcher is deprecated #7648 merged after it, so the parent became the dialog, which spans the viewport. Its top is 0, so the offset was always 0 and the retained sheet never moved. Switcher steps keep their positioner and their own measurement, which is why the switcher still aligns.
  • Why it harms the task. The handoff exists so that a step flow reads as one sheet whose height follows its content. With no alignment, the old sheet's top band hangs above the new step for about half a second and then disappears.
  • Why that matters. Standalone steps are the documented replacement for the deprecated switcher, and the builder docs promise the same handoff. A migrated flow now looks broken where the switcher flow looked right.

The fix measures the entering panel itself: its box, less the translation its entrance is still applying. That is where it comes to rest, whatever element hosts it.

Expected behavior and authority

No record changes: the records already describe the expected behavior.

Smallest restoration

One measurement function in bottomSheetStack.ts changes. There is no API change, no style or token change, no record change, and BottomSheetSwitcher code is untouched.

Evidence

  • Reproduction before the change. The deployed Storybook built from main 90da4a7, Core/BottomSheetReplace "Hug content", sampled per frame in Chromium:
    • Phone (390×844, touch), step 1 → 2: the retained panel's translateY stays 0. The visible sheet top holds at 434 px for about 500 ms, then jumps to 590 px as the retained band fades, with no intermediate position.
    • Step 3 → 2 does the same (534 → 590 px).
    • Desktop (1280×800) and the "No scrim" and "Narrow viewport" mirrors show the same thing.
  • Result after the change. On the same switch, the retained panel moves down 156 px (104 px on desktop) while the entering sheet rises. The visible top glides from 434 to 590 px through 13 intermediate positions in about 225 ms, and the retained sheet then fades beneath the entering one. Per frame, each standalone replace story now matches its BottomSheetSwitcher counterpart within 0.3 px on all 32 measured switches (4 story pairs × 4 switches × phone and desktop).
  • Representative unchanged paths.
    • The BottomSheetSwitcher stories match v0.7.4 within 0.5 px per frame (5 stories × 4 switches × phone and desktop), before and after this change. The three switcher stories added after v0.7.4 (stacked over a sheet, stacked under a sheet, typing over an advancing flow) are identical on main and with this change.
    • Stacking push and pop (Orders drill-in) is unchanged: a sheet opened over another still rises on top while the one beneath recedes.
    • The Lab MobilePowerSearch touch flow is unchanged. Its steps stack, and all are the same tall height.
    • Under reduced motion no transform runs, and the timing is the same as before: a coarse pointer cross-fades within 200 ms, and a fine pointer changes at once.
  • Regression test. BottomSheetReplace.test.tsx now lays each step out as a browser does: the dialog spans the viewport and the panel sits in it. Before, the tests gave the dialog a box it never has. Five cases fail on main and pass here:
    • alignment on advance;
    • alignment when the alignment finishes first;
    • alignment on going back;
    • an entering panel still 240 px below where it rests (removing the translation subtraction fails this case);
    • reduced motion with a measured alignment, where the retained sheet does not move.

Scope

  • One defect is restored.
  • Separable contradictory or unsettled tagalongs were removed or split.
  • No new public API, visual representation, default, or interaction model is hidden under the bug-fix label.
  • Public text and artifacts contain no internal Meta context.

Testing

  • pnpm exec vitest run packages/core/src/BottomSheet packages/core/src/Layer packages/lab/src/MobilePowerSearch packages/lab/src/MobileTokenizer: 23 files, 818 tests passed.
  • After pnpm build, all passed:
    • @astryxdesign/core: tsc --project tsconfig.json --noEmit and typecheck:docs;
    • @astryxdesign/lab: typecheck:docs;
    • @astryxdesign/storybook: typecheck;
    • internal/a11y-spec: typecheck;
    • @astryxdesign/cli: typecheck:strict and typecheck:template-docs.
  • ASTRYX_STRICT_LINT=1 eslint --max-warnings=0 on the changed files, plus Prettier.
  • pnpm check:repo (includes check:knowledge and check:changesets), and git diff --check.
  • Real browser: Chromium, phone profile (390×844, touch, DPR 2) and desktop (1280×800, mouse), each with and without prefers-reduced-motion: reduce. Each panel's position, transform and opacity, and the visible sheet top, were sampled every animation frame across each switch. Builds compared: the main build, a local Storybook build of v0.7.4, and a local Storybook build of this change. Stories:
    • every BottomSheetSwitcher story with a step switch;
    • the BottomSheetReplace mirrors;
    • BottomSheetStacking "Orders drill-in";
    • Lab MobilePowerSearch "Touch flow" (Filters → Add filter → a field's value step).

@vercel

vercel Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
astryx Ready Ready Preview Oct 30, 2026 3:43pm UTC

Request Review

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Oct 30, 2026
@astracat-bot

astracat-bot Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

AI review status for this pull request.

Review status Updated
🔄 In progress (for maintainers only) Oct 30, 2026, 3:52 PM UTC

…heet again

In the replace handoff between standalone sheets, a taller retained sheet
is meant to move down to the entering sheet's resting top edge while the
entering sheet rises, and fade only after both motions finish
(component:BottomSheet FR10). The stack measured that resting edge from
the entering panel's parent element. A standalone panel sits directly in
its dialog, which spans the viewport, so the parent's top was always 0,
the offset always 0, and the retained sheet stayed at its own height and
faded out above the entering sheet.

The resting top is now read from the entering panel itself: its box less
the translation its entrance is still applying, whatever element hosts it.
BottomSheetSwitcher steps keep their own measurement and are unchanged.
Under reduced motion the retained sheet still does not move.

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

Looks good: measuring the entering panel itself restores the component:BottomSheet FR10 alignment for standalone replace handoffs, and the new replace cases fail without the fix and pass with it. In Chromium and WebKit the standalone Hug content flow now matches the BottomSheetSwitcher handoff frame for frame, while reduced motion still keeps the replaced sheet in place. The CI run for this commit was cancelled before it finished, so it needs a fresh run before merging; nothing else is needed.

[Automated review]

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

Looks good: measuring the entering panel itself restores the component:BottomSheet FR10 alignment for standalone replace handoffs, and the five replace cases you named fail without the fix and pass with it. In Chromium the standalone Hug content handoff now matches the BottomSheetSwitcher handoff frame for frame (and at landing in WebKit), while reduced motion still keeps the replaced sheet in place with unchanged timing. No CI workflow has reported on this commit yet, so let the required checks run before merging.

[Automated review]

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

Looks good: measuring the entering panel itself restores the component:BottomSheet FR10 alignment for standalone replace handoffs, and the five replace cases you named fail without the fix and pass with it. In Chromium the standalone Hug content handoff now matches the BottomSheetSwitcher handoff frame for frame (and at landing in WebKit), while reduced motion still keeps the replaced sheet in place with unchanged timing. The CI run for this commit was cancelled before its jobs finished, so the required checks need a fresh run before merging.

[Automated review]

@github-actions

github-actions Bot commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

PR Analysis Report

📚 Storybook Preview

View Storybook for this PR

🧪 Sandbox Preview

View Sandbox for this PR

Component Size

Component Import before Import after Delta CSS before CSS after Delta
core/BottomSheet 34.2KB 34.3KB +68B 7.9KB 7.9KB 0B
core/BottomSheetSwitcher 14.8KB 14.8KB 0B 3.3KB 3.3KB 0B
core/ContextMenu 75.8KB 75.8KB 0B 14.8KB 14.8KB 0B
core/DateInput 101.4KB 101.4KB +54B 14.9KB 14.9KB 0B
core/DateTimeInput 111.6KB 111.7KB +45B 15.4KB 15.4KB 0B
core/DropdownMenu 73.9KB 73.9KB 0B 15.0KB 15.0KB 0B
core/Markdown 146.0KB 146.0KB 0B 17.8KB 17.8KB 0B
core/MoreMenu 74.2KB 74.2KB 0B 15.0KB 15.0KB 0B
core/MultiSelector 74.2KB 74.2KB -1B 14.8KB 14.8KB 0B
core/Pagination 86.4KB 86.4KB 0B 15.5KB 15.5KB 0B
core/Popover 50.9KB 50.9KB -1B 12.2KB 12.2KB 0B
core/PowerSearch 168.0KB 168.0KB +42B 20.1KB 20.1KB 0B
core/Selector 78.0KB 78.0KB +1B 15.2KB 15.2KB 0B
core/Table 85.4KB 85.4KB 0B 15.6KB 15.6KB 0B
core/TableCell 77.0KB 77.0KB -1B 15.3KB 15.3KB 0B
core/TableHeaderCell 76.6KB 76.6KB -1B 15.0KB 15.0KB 0B
core/TimeInput 88.6KB 88.6KB +65B 13.9KB 13.9KB 0B
core/Tokenizer 69.8KB 69.8KB +1B 16.7KB 16.7KB 0B
lab/ChatEmojiPicker N/A 66.7KB N/A N/A 773B N/A
lab/ChatReactionBar N/A 67.3KB N/A N/A 1.1KB N/A
lab/CommentComposer N/A 95.7KB N/A N/A 1.9KB N/A
lab/CommentThread N/A 217.7KB N/A N/A 2.4KB N/A
lab/Comments N/A 222.2KB N/A N/A 2.5KB N/A
lab/MobilePowerSearch N/A 154.6KB N/A N/A 1.2KB N/A
lab/MobileTokenizer N/A 94.1KB N/A N/A 880B N/A
lab/TourStep N/A 56.3KB N/A N/A 590B N/A
richtext/RichTextEditorToolbar N/A 214.0KB N/A N/A 454B N/A

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.

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.4KB 35.4KB 0B

Accessibility Audit

Status: The accessibility audit did not run on this pull request. It runs after merges to main and before each release; pull requests that touch components run it with the ci:heavy-lanes label.


Generated by PR Enrichment workflow | Storybook | Sandbox | View full report

@imdreamrunner
imdreamrunner merged commit 8e1e440 into main Oct 30, 2026
98 of 104 checks passed

This branch was successfully deployed

1 active deployment
Preview — 649334c6 Deployed Oct 30, 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.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant