Skip to content

fix(react-headless-components-preview): update non-modal dialog implementation to use popover API#36244

Open
dmytrokirpa wants to merge 4 commits into
masterfrom
fix/switch-headless-non-modal-dialog-to-popover
Open

fix(react-headless-components-preview): update non-modal dialog implementation to use popover API#36244
dmytrokirpa wants to merge 4 commits into
masterfrom
fix/switch-headless-non-modal-dialog-to-popover

Conversation

@dmytrokirpa
Copy link
Copy Markdown
Contributor

@dmytrokirpa dmytrokirpa commented May 25, 2026

This pull request updates the implementation of non-modal dialogs in @fluentui/react-headless-components-preview to use the native popover API, improving accessibility and alignment with browser standards. It also introduces new event handling for dialog state changes, updates the test suite to cover nested dialogs, and refines both code comments and styles for clarity and correctness.

Non-modal Dialog Implementation Improvements

  • Refactored non-modal dialogs to use the native showPopover() API (with popover="manual") instead of show(), enabling them to enter the browser top layer and improving stacking and accessibility. Fallback to show() is used if the popover API is unavailable. (DialogSurface.tsx, DialogSurface.types.ts, useDialogSurface.ts, renderDialogSurface.tsx) [1] [2] [3] [4] [5] [6] [7] [8] [9]

Event Handling and API Changes

  • Added a new surfaceToggle event type to DialogOpenChangeData to handle open/close events triggered by the native popover toggle, ensuring React state stays in sync with the DOM. (dialogContext.ts, dialog.api.md, useDialog.ts) [1] [2] [3]

Testing Enhancements

  • Updated Cypress tests to use dialog[data-open] for non-modal dialogs and added a comprehensive test for accessible nested modal and non-modal dialogs, verifying ARIA attributes and correct rendering. (Dialog.cy.tsx) [1] [2] [3]

Documentation and Code Comments

  • Improved and clarified documentation and inline comments to reflect the new popover-based implementation and its benefits over the previous approach. (DialogSurface.tsx, DialogSurface.types.ts, useDialogSurface.ts, renderDialogSurface.tsx) [1] [2] [3] [4] [5]

Styling Adjustments

  • Updated CSS to scope the dialog backdrop style to modal dialogs only, preventing unintended styling of non-modal dialogs. (dialog.module.css)

@dmytrokirpa dmytrokirpa self-assigned this May 25, 2026
@github-actions
Copy link
Copy Markdown

Pull request demo site: URL

@dmytrokirpa dmytrokirpa force-pushed the fix/switch-headless-non-modal-dialog-to-popover branch from 71e32c9 to 32b9621 Compare May 25, 2026 17:08
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 25, 2026

📊 Bundle size report

Package & Exports Baseline (minified/GZIP) PR Change
react-headless-components-preview
react-headless-components-preview: entire library
160.569 kB
45.74 kB
157.166 kB
44.568 kB
-3.403 kB
-1.172 kB

🤖 This report was generated against aa727a4aac5088916fe7d22e11355ba4938951e2

@dmytrokirpa dmytrokirpa marked this pull request as ready for review May 25, 2026 19:27
@dmytrokirpa dmytrokirpa requested a review from a team as a code owner May 25, 2026 19:27
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR updates the headless preview Dialog non-modal surface implementation to use the native Popover API (top-layer) instead of relying on dialog.show() + portalling, adds a new open-change event type for native surface toggles, and extends Cypress coverage to include nested modal + non-modal dialogs.

Changes:

  • Switch non-modal DialogSurface open/close behavior to popover="manual" + showPopover()/hidePopover() (with a show() fallback) and add a surfaceToggle open-change event for native toggle synchronization.
  • Remove the non-modal Portal rendering path and adjust story styling to scope ::backdrop styles to modal dialogs only.
  • Update Cypress tests/selectors for non-modal open state and add an accessibility-focused nested dialog test.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
packages/react-components/react-headless-components-preview/stories/src/Dialog/dialog.module.css Scope backdrop styling to modal dialogs via :modal::backdrop.
packages/react-components/react-headless-components-preview/library/src/components/Dialog/useDialog.ts Allow default-prevent handling for both React synthetic events and native events.
packages/react-components/react-headless-components-preview/library/src/components/Dialog/DialogSurface/useDialogSurface.ts Implement popover-based non-modal open/close + toggle event syncing + focus restoration.
packages/react-components/react-headless-components-preview/library/src/components/Dialog/DialogSurface/renderDialogSurface.tsx Render DialogSurface inline (remove non-modal Portal path).
packages/react-components/react-headless-components-preview/library/src/components/Dialog/DialogSurface/DialogSurface.types.ts Update docs/comments to reflect popover-based non-modal behavior.
packages/react-components/react-headless-components-preview/library/src/components/Dialog/DialogSurface/DialogSurface.tsx Update docs/comments to mention showPopover() usage.
packages/react-components/react-headless-components-preview/library/src/components/Dialog/dialogContext.ts Add surfaceToggle to DialogOpenChangeData.
packages/react-components/react-headless-components-preview/library/src/components/Dialog/Dialog.cy.tsx Update selectors and add nested modal + non-modal accessibility test coverage.
packages/react-components/react-headless-components-preview/library/etc/dialog.api.md Update extracted API to include surfaceToggle event type.
change/@fluentui-react-headless-components-preview-6def4baa-5ccb-46a3-92a8-da4416b5111b.json Beachball change file for the patch release.

Comment on lines 64 to +68
if (!open) {
// Close while the element is still connected so the browser runs its native
// close-the-dialog steps (including focus restoration). If `unmountOnClose` is
// true, schedule the actual DOM removal now that close has run.
if (dialog.open) {
if (modalType === 'non-modal') {
if (typeof dialog.hidePopover === 'function') {
const isPopoverOpen = SUPPORTS_POPOVER_OPEN_SELECTOR && dialog.matches(':popover-open');
if (isPopoverOpen) {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

fixed in 122c0c0

);

return state.modalType === 'non-modal' ? <Portal>{content}</Portal> : content;
return content;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

the whole goal of this PR is to not use Portal, so we'll rely on Popover

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants