Skip to content

Conversation

@Rajdeepc
Copy link
Contributor

@Rajdeepc Rajdeepc commented Jan 5, 2026

Description

Root cause

When we migrated from .showModal() to the Popover API for performance optimization, modal overlays were configured to use popover="auto". The browser's Popover API only allows one popover="auto" element to be visible at a time—opening a second one automatically closes the first (light dismiss behavior).
Additionally, a recent PR (#5907) added click-blocking for modal overlays to replicate showModal() behavior. This click-blocking was preventing light dismiss from working when combined with popover="manual".

Changes

  1. Overlay.ts
  • Changed modal overlays from popover="auto" to popover="manual"
  • Allows multiple modal overlays to be visible simultaneously
  • Restores the stacking behavior that existed with showModal()
  • Updated needsModalBackdrop to only apply to page overlays
  • Modal overlays rely on handlePointerup in OverlayStack for light dismiss
  • Page overlays retain the backdrop since they don't have light dismiss
  1. OverlayStack.ts
  • Modified handlePointerdown to only block clicks for page overlays
  • Allows click events to flow through for modal overlays
  • Ensures pointerdownPath is cached so handlePointerup can handle light dismiss
  • Modified handleClick to only block clicks for page overlays
  • Same rationale as above
  • Added explicit Escape key handling for modal type
  • Since popover="manual" doesn't have automatic Escape key dismissal, we now handle it explicitly
  • Ensures only the topmost modal closes when Escape is pressed

Motivation and context

Related issue(s)

  • fixes [Issue Number]

Screenshots (if appropriate)


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

  • Descriptive Test Statement

    1. Go here
    2. Do this action
    3. Expect this result
  • Descriptive Test Statement

    1. Go here
    2. Do this action
    3. Expect this result

Device review

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

@changeset-bot
Copy link

changeset-bot bot commented Jan 5, 2026

⚠️ No Changeset found

Latest commit: 63775dd

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.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

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

@github-actions
Copy link
Contributor

github-actions bot commented Jan 5, 2026

📚 Branch Preview Links

🔍 First Generation 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-5951

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.

// The overlay's transition guarantee uses 3 animation frames before
// calling finish() which triggers returnFocus(). We need to wait longer
// than that before re-enabling hover to prevent it from opening.
requestAnimationFrame(() => {
Copy link
Contributor

Choose a reason for hiding this comment

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

This looks a bit wonky and I assume it might bring further timing issues down the road when used in quite complex applications.

Copy link
Contributor Author

@Rajdeepc Rajdeepc Jan 6, 2026

Choose a reason for hiding this comment

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

@spdev3000 Thanks for the feedback. I’ve refactored this to a cleaner approach using a single setTimeout. The 100ms delay reliably covers the close transition and focus return, while remaining short enough to avoid any perceived sluggishness.

This also smooths out edge cases like rapid close/open interactions. Please take another look and let me know if you see any remaining concerns or edge cases we should address.

@spdev3000
Copy link
Contributor

looks good for me, one question:
I am wondering, if clicking outside a modal overlay (with popover="manual") also dismisses it within your approach (if "dismissable" is set for example to the dialog component).
And also the other way - if we don't have a dismissable attritube set to dialog - is the user supposed to dismiss the dialog and "Escaping" is being prevented?

@Rajdeepc
Copy link
Contributor Author

Rajdeepc commented Jan 8, 2026

looks good for me, one question: I am wondering, if clicking outside a modal overlay (with popover="manual") also dismisses it within your approach (if "dismissable" is set for example to the dialog component). And also the other way - if we don't have a dismissable attritube set to dialog - is the user supposed to dismiss the dialog and "Escaping" is being prevented?

To your first question this is intentional - modal overlays should trap interaction until explicitly dismissed. This matches the Spectrum design pattern where modals require explicit user action to close. If you need click-outside-to-close behavior, you should use type="auto" instead of type="modal" (for non-blocking overlays) or implement click-outside logic in your dialog component and programmatically close the overlay.
To your second question, If non-dismissable dialogs should also block Escape, we could add a way for dialog content to communicate its dismissable state to the overlay or check if the overlay content has a dismissable attribute before closing.

@spdev3000
Copy link
Contributor

To #1
SWC currently supports dismissing a modal dialog via click outside a DialogWrapper, see: https://opensource.adobe.com/spectrum-web-components/components/dialog-wrapper/#dismissable
So this should still work.

re #2: Yes we have use cases, where no "cancel" button is rendered inside a dialog and Escape should not dismiss or cancel the dialog, so we'd need such a behavior.

@Rajdeepc
Copy link
Contributor Author

Rajdeepc commented Jan 12, 2026

To #1 SWC currently supports dismissing a modal dialog via click outside a DialogWrapper, see: https://opensource.adobe.com/spectrum-web-components/components/dialog-wrapper/#dismissable So this should still work.

re #2: Yes we have use cases, where no "cancel" button is rendered inside a dialog and Escape should not dismiss or cancel the dialog, so we'd need such a behavior.

@spdev3000 you can use the overlay type="page"
https://stackblitz.com/edit/vitejs-vite-mnzgm64e?file=src%2Fmy-element.ts

@Rajdeepc Rajdeepc marked this pull request as ready for review January 12, 2026 14:38
@Rajdeepc Rajdeepc requested a review from a team as a code owner January 12, 2026 14:38
@Rajdeepc Rajdeepc self-assigned this Jan 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants