Merge pull request #4338 from element-hq/toger5/compound-portal-root

[ElementCallComponent] Use compound-web's PortalRoot in EC.
This commit is contained in:
Timo
2026-10-12 12:59:28 +02:00
committed by GitHub
14 changed files with 182 additions and 45 deletions
+29
View File
@@ -11,6 +11,7 @@ import { type ReactNode, useState } from "react";
import userEvent from "@testing-library/user-event";
import { Modal } from "./Modal";
import { RootElementProvider } from "./RootElementContext";
const originalMatchMedia = window.matchMedia;
afterEach(() => {
@@ -70,3 +71,31 @@ test("the modal renders as a drawer in mobile viewports", () => {
);
expect(queryByRole("dialog")).toMatchSnapshot();
});
test("the modal can be closed by clicking the backdrop, in another window too", async () => {
// A dialog moved into another window, as a host does for a Document
// Picture-in-Picture window: its nodes are not `Node`s of this window
const frame = document.createElement("iframe");
document.body.appendChild(frame);
const otherBody = frame.contentDocument!.body;
function ModalFn(): ReactNode {
const [isOpen, setOpen] = useState(true);
return (
<RootElementProvider value={otherBody}>
<Modal title="My modal" open={isOpen} onDismiss={() => setOpen(false)}>
<p>This is the content.</p>
</Modal>
</RootElementProvider>
);
}
render(<ModalFn />);
const dialog = otherBody.querySelector('[role="dialog"]');
expect(dialog).not.toBeNull();
const backdrop = dialog!.previousElementSibling as HTMLElement;
const user = userEvent.setup({ document: frame.contentDocument! });
await user.click(backdrop);
expect(otherBody.querySelector('[role="dialog"]')).toBeNull();
frame.remove();
});
+6
View File
@@ -160,6 +160,12 @@ export const Modal: FC<Props> = ({
<DialogPortal container={rootElement}>
<DialogOverlay
className={classNames(overlayStyles.bg, overlayStyles.animate)}
// Radix dismisses on a pointer down outside the dialog, but only
// for event targets that are a `Node` of the window it was loaded
// in. A host can move the dialog into another window (a Document
// Picture-in-Picture window), whose nodes fail that check, so the
// backdrop closes the dialog itself.
onClick={() => onDismiss?.()}
/>
<DialogContent
asChild
+2 -2
View File
@@ -15,7 +15,7 @@ exports[`AppBar > renders 1`] = `
tabindex="0"
>
<div
class="_indicator-icon_147l5_17"
class="_indicator-icon_j097f_17"
style="--cpd-icon-button-size: 100%;"
>
<svg
@@ -58,7 +58,7 @@ exports[`AppBar > renders with title and subtitle 1`] = `
tabindex="0"
>
<div
class="_indicator-icon_147l5_17"
class="_indicator-icon_j097f_17"
style="--cpd-icon-button-size: 100%;"
>
<svg
@@ -345,6 +345,7 @@ export const MediaMuteAndSwitchButton: FC<MediaMuteAndSwitchButtonProps> = ({
// Keeps the items the menu's own children for assistive tech.
role="none"
className={styles.deviceList}
data-testid="device-list"
style={
{
"--device-list-max-height":
@@ -181,7 +181,7 @@ exports[`InCallView > rendering > renders 1`] = `
tabindex="0"
>
<div
class="_indicator-icon_147l5_17"
class="_indicator-icon_j097f_17"
style="--cpd-icon-button-size: 100%;"
>
<svg
@@ -15,7 +15,7 @@ exports[`LobbyView > renders with AppBar android 1`] = `
tabindex="0"
>
<div
class="_indicator-icon_147l5_17"
class="_indicator-icon_j097f_17"
style="--cpd-icon-button-size: 100%;"
>
<svg
@@ -111,7 +111,7 @@ exports[`LobbyView > renders with AppBar android 1`] = `
tabindex="0"
>
<div
class="_indicator-icon_147l5_17"
class="_indicator-icon_j097f_17"
style="--cpd-icon-button-size: 100%;"
>
<svg
@@ -247,7 +247,7 @@ exports[`LobbyView > renders with AppBar ios 1`] = `
tabindex="0"
>
<div
class="_indicator-icon_147l5_17"
class="_indicator-icon_j097f_17"
style="--cpd-icon-button-size: 100%;"
>
<svg
@@ -343,7 +343,7 @@ exports[`LobbyView > renders with AppBar ios 1`] = `
tabindex="0"
>
<div
class="_indicator-icon_147l5_17"
class="_indicator-icon_j097f_17"
style="--cpd-icon-button-size: 100%;"
>
<svg
@@ -621,7 +621,7 @@ exports[`LobbyView > renders with header and participant count 1`] = `
tabindex="0"
>
<div
class="_indicator-icon_147l5_17"
class="_indicator-icon_j097f_17"
style="--cpd-icon-button-size: 100%;"
>
<svg