fix(ui): a dialog whose own video goes fullscreen no longer crashes the app

The portal host followed the fullscreen element into it. When that element is the dialog's own
content (the library player's <video>, by its f key or its own button), the move is into the host's
own descendant, which the DOM refuses: HierarchyRequestError in a layout effect, and the crash
screen replaced the app. The host now stays where it is then — the content already shows.
This commit is contained in:
2026-10-07 23:16:25 +02:00
parent 61d3a1a189
commit d25a132495
2 changed files with 25 additions and 1 deletions
@@ -85,3 +85,24 @@ describe("a dialog with an autoFocus child", () => {
stage.remove(); stage.remove();
}); });
}); });
describe("a dialog whose own content goes fullscreen", () => {
it("stays where it is and keeps that content", () => {
// The library player is a <video> inside a dialog, and its fullscreen (the `f` key, or the
// video's own button) makes that video the fullscreen element. Moving the dialog's host INTO
// its own descendant is not a move the DOM allows: it threw, and the whole app went to the
// crash screen.
render(
<Modal initialFocus="close" title="Player" onClose={() => {}}>
<video aria-label="film" />
</Modal>,
);
const video = screen.getByLabelText("film");
const host = video.closest("body > div");
setFullscreen(video);
expect(video.isConnected).toBe(true);
expect(video.closest("body > div")).toBe(host);
});
});
+4 -1
View File
@@ -95,7 +95,10 @@ export function useFullscreenPortalHost(): HTMLElement | null {
}, [host]); }, [host]);
useLayoutEffect(() => { useLayoutEffect(() => {
if (!host) return; if (!host) return;
const parent = fsRoot ?? document.body; // When the fullscreen element is the overlay's OWN content (a <video> inside a dialog, put in
// fullscreen by its key or its own button), it already shows; moving the host into its own
// descendant is a move the DOM refuses (HierarchyRequestError, the whole app crashed).
const parent = fsRoot && !host.contains(fsRoot) ? fsRoot : document.body;
if (host.parentNode === parent) return; if (host.parentNode === parent) return;
const focused = host.contains(document.activeElement) const focused = host.contains(document.activeElement)
? (document.activeElement as HTMLElement) ? (document.activeElement as HTMLElement)