Skip to content

fix(core): let PlacementController clear its own inline styles - #6912

Merged
rubencarvalho merged 5 commits into
mainfrom
ruben/fix-placement-style-cleanup
Nov 11, 2026
Merged

rubencarvalho merged 5 commits into
mainfrom
ruben/fix-placement-style-cleanup

Conversation

@rubencarvalho

@rubencarvalho rubencarvalho commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

Description

PlacementController now removes the inline position styles it writes, with a new clearStyles(), and each overlay decides when to call it. Popover and Menu now clean up after they close, like Tooltip already did. This also fixes closing a popover or menu during its entrance animation, which ended the close too early.

What changed
  • PlacementController.clearStyles() removes only what the controller wrote in its last session: top, left, translate, and --swc-placement-available-* on the floating element, and top, left, and translate on the tip. Other inline styles stay (Tooltip keeps --_swc-tooltip-animation-distance). It does nothing while positioning is active, so a late call can't move a surface that reopened, and it's safe to call more than once.
  • Tooltip: clearPositioningState() calls clearStyles() instead of its own removeProperty calls. Same behavior.
  • Popover and Menu: _stopPositioningWhenClosed() calls clearStyles() after stop(). It already runs after the close transition and bails if the surface reopened. Every open now starts unpositioned, like the first one, and the surface stays hidden until the first compute sets actual-placement.
  • runAfterTransition(): a transitioncancel no longer counts as the end while another transition is still running on the element. Closing during the entrance cancels the entrance transition and starts the exit one, so the after-close work, including swc-after-close, used to run mid-fade. With the new cleanup, that snapped a still-visible surface to the corner.
  • Fixed comments that said stop() resets the inline translate, and updated the stop() JSDoc and placement-controller.mdx. actual-placement handling is unchanged.

Motivation and context

The three overlays had drifted: only Tooltip removed the controller's styles after closing, so a closed popover or menu kept its last position until the next open. Nothing visibly broke, but the list of styles to remove lived outside the controller that writes them. Now the controller owns it, and each component still picks the moment, since their close lifecycles differ.

Related issue(s)

  • fixes SWC-2738

Screenshots (if appropriate)

No visual change: the styles are removed only after the surface is hidden.


Author's checklist

  • I have read the CONTRIBUTING and PULL_REQUESTS documents.
  • I have reviewed at the Accessibility Practices for this feature, see: Aria Practices
  • I have added automated tests to cover my changes.
  • I have included a well-written changeset if my change needs to be published.
  • I have included updated documentation if my change required it.

Reviewer's checklist

  • Includes a Github Issue with appropriate flag or Jira ticket number without a link
  • Includes thoughtfully written changeset if changes suggested include patch, minor, or major features
  • Automated tests cover all use cases and follow best practices for writing
  • Validated on all supported browsers
  • All VRTs are approved before the author can update Golden Hash

Manual review test cases

  • Popover and Menu clear their inline position styles after closing
  • Closing during the entrance fades out in place
  • A rapid reopen keeps its position
  • Tooltip behaves as before
Steps

Popover and Menu clear their inline position styles after closing

  1. Go to the Popover or Menu story.
  2. Open the surface from its trigger, then close it.
  3. Once the fade ends, .swc-Popover and .swc-Popover-tip (or .swc-Menu) in the shadow root have no inline top, left, translate, or --swc-placement-available-*. Reopening fades in at the trigger, with no flash in the corner.

Closing during the entrance fades out in place

  1. Go to the Popover or Menu story.
  2. Open the surface and press Escape right away, while it's still fading in.
  3. It fades out where it was, and swc-after-close fires only after the fade ends.

A rapid reopen keeps its position

  1. Go to the Popover story.
  2. Close the popover and reopen it before the close fade ends.
  3. It stays anchored to the trigger, with no jump.

Tooltip behaves as before

  1. Go to the Tooltip story.
  2. Hover or focus a trigger, then move away.
  3. After the fade, the swc-tooltip host and its .swc-Tooltip-tip have no inline position styles, and the host keeps --_swc-tooltip-animation-distance.

Device review

  • Did it pass in Desktop?
  • Did it pass in (emulated) Mobile?
  • Did it pass in (emulated) iPad?

Accessibility testing checklist

Required: Complete each applicable item and document your testing steps (replace the placeholders with your component-specific instructions).

  • Keyboard (required — document steps below) — What to test for: Focus order is logical; Tab reaches the component and all interactive descendants; Enter/Space activate where appropriate; arrow keys work for tabs, menus, sliders, etc.; no focus traps; Escape dismisses when applicable; focus indicator is visible.

    Steps
    1. Go to the Popover story, Tab to the trigger, and press Enter. Focus moves into the popover. Escape closes it and returns focus to the trigger, and Enter reopens it at the trigger.
    2. Go to the Menu story, Tab to the trigger, and press Enter. Focus lands on the first row, arrow keys move between rows, and Escape closes it and returns focus to the trigger.
    3. Go to the Tooltip story and Tab to a trigger. The tooltip shows at the trigger, and Escape hides it.
  • Screen reader (required — document steps below) — What to test for: Role and name are announced correctly; state changes (e.g. expanded, selected) are announced; labels and relationships are clear; no unnecessary or duplicate announcements.

    Steps
    1. With VoiceOver or NVDA on, open the popover in the Popover story. The trigger's expanded state and the named dialog are announced, the same as before. Close and reopen: same announcements, nothing extra.
    2. Open the menu in the Menu story. The menu and its first row are announced.
    3. Focus a trigger in the Tooltip story. The tooltip text is announced as its description.

Validation

The new tests failed before each fix and pass after it. The full Storybook suite passes locally (1252 tests), and CI passes, including the Firefox and WebKit jobs.

Commands and results

Run from gen2/packages/swc unless noted. Local tests ran in Chromium.

  • npx cem analyze --config cem.config.js: passed
  • npx vitest --run --project storybook ../core/controllers/placement-controller/test/placement-controller.test.ts: failed before the controller change (host.controller.clearStyles is not a function), 18/18 passed after
  • npx vitest --run --project storybook components/popover/test/popover.test.ts components/menu/test/menu.test.ts components/tooltip/test/tooltip.test.ts: Popover and Menu failed before the fix (surface top is removed: expected '0px' to be ''), all passed after
  • npx vitest --run --project storybook ../core/utils/test/transition.test.ts components/popover/test/popover.test.ts components/menu/test/menu.test.ts -t "Transition Cancel Test|Close During Entrance Keeps Position Test": failed before the runAfterTransition() change (3 of 3 runs), passed after (5 of 5 runs)
  • npx vitest --run --project storybook: 1252 passed
  • yarn type-check:gen2, npx eslint, and npx prettier --check on the changed files (repo root): passed
  • Mutation checks: disabling the active-session guard, skipping the tip, wiping the whole style attribute, skipping the custom properties, or dropping Tooltip's clearStyles() call each fail a test

Cross-model review

gpt-6.1-sol and gemini-4-argon each reviewed the full diff twice. After one fix, both returned MERGE with no findings.

Findings
  • Round 1: gemini-4-argon returned MERGE with no findings. gpt-6.1-sol returned DO NOT MERGE with one MAJOR: closing during the entrance ran the after-close teardown mid-fade (runAfterTransition() settled on the entrance's transitioncancel), so clearStyles() snapped a visible popover or menu to the corner. Fixed in the second commit, with tests that failed first.
  • Round 2 (both commits): both returned MERGE with no findings. gpt-6.1-sol also probed reopen, re-anchor, modal swap, and suppressed discrete-transition events in Chromium and WebKit. It couldn't launch Firefox, but the CI Firefox job passes.
  • Nothing rejected or deferred. The later comment-only commit wasn't re-reviewed.

- popover and menu left the inline top, left, translate, and
--swc-placement-available-* styles on the closed surface, while tooltip
removed them by hand, so the three close paths had drifted apart
- add PlacementController.clearStyles() so the controller owns what it
wrote; each component still decides when to call it, after its own
close transition
- fix comments that claimed stop() resets the inline translate
- closing popover or menu during its entrance cancels the entrance
transition and starts the exit one in its place, and runAfterTransition
treated that cancel as the end of the close
- the after-close work then ran while the surface was still fading, and
with the new style cleanup that snapped a visible surface to the corner
- keep waiting on a transitioncancel while another transition still runs
on the element
@rubencarvalho
rubencarvalho requested a review from a team as a code owner November 9, 2026 12:51
@rubencarvalho rubencarvalho added Status:Ready for review PR ready for review or re-review. gen2 These issues or PRs map to our 2nd generation work to modernizing infrastructure. labels Nov 9, 2026
@github-actions

github-actions Bot commented Nov 9, 2026

Copy link
Copy Markdown
Contributor

📚 Branch Preview Links

🔍 Gen1 Visual Regression Test Results

When a visual regression test fails (or has previously failed while working on this branch), its results can be found in the following URLs:

Deployed to Azure Blob Storage: pr-6912

If the changes are expected, update the current_golden_images_cache hash in the circleci config to accept the new images. Instructions are included in that file.
If the changes are unexpected, you can investigate the cause of the differences and update the code accordingly.

@changeset-bot

changeset-bot Bot commented Nov 9, 2026 •

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: c7e0dbb

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@rubencarvalho rubencarvalho added Status:WIP PR is a work in progress or draft and removed Status:Ready for review PR ready for review or re-review. labels Nov 9, 2026
@rubencarvalho
rubencarvalho marked this pull request as draft November 9, 2026 16:23
@rubencarvalho rubencarvalho added Status:Ready for review PR ready for review or re-review. and removed Status:WIP PR is a work in progress or draft labels Nov 10, 2026
@rubencarvalho
rubencarvalho marked this pull request as ready for review November 10, 2026 11:28
@rubencarvalho rubencarvalho added the run_vrt Triggers the Chromatic VRT run for 2nd-gen label Nov 10, 2026

@miwha-adobe miwha-adobe left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Nice separation between stopping positioning and clearing styles after the fade. Appreciate the safeguards for rapid reopen and the close-during-entrance tests.

@rubencarvalho
rubencarvalho enabled auto-merge (squash) November 11, 2026 10:31
@rubencarvalho
rubencarvalho merged commit 3d0c43d into main Nov 11, 2026
27 of 28 checks passed
@rubencarvalho
rubencarvalho deleted the ruben/fix-placement-style-cleanup branch November 11, 2026 10:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gen2 These issues or PRs map to our 2nd generation work to modernizing infrastructure. run_vrt Triggers the Chromatic VRT run for 2nd-gen Status:Ready for review PR ready for review or re-review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants