revert: the Modal Escape guard for a fullscreen own-content video

Chrome does not hand the page the Escape it spends on leaving fullscreen (checked with a real
keypress: the first Esc leaves fullscreen, the second closes the player). The guard defended a case
that does not happen, under a comment saying it does. This reverts 121ebeeb.
This commit is contained in:
2026-10-07 23:59:32 +02:00
parent 121ebeebc1
commit d1704a5519
2 changed files with 2 additions and 42 deletions
@@ -106,39 +106,3 @@ describe("a dialog whose own content goes fullscreen", () => {
expect(video.closest("body > div")).toBe(host);
});
});
describe("Escape while the dialog's own content is fullscreen", () => {
const escape = () =>
act(() => {
window.dispatchEvent(new KeyboardEvent("keydown", { key: "Escape", bubbles: true }));
});
it("only leaves fullscreen (the browser's part) — the dialog stays open", () => {
// The browser spends that Escape on leaving fullscreen and still hands it to the page, with the
// video still the fullscreen element: the dialog closed with it, and the viewer lost the player.
const onClose = vi.fn();
render(
<Modal initialFocus="close" title="Player" onClose={onClose}>
<video aria-label="film" />
</Modal>,
);
setFullscreen(screen.getByLabelText("film"));
escape();
expect(onClose).not.toHaveBeenCalled();
});
it("still closes a dialog opened over something else that is fullscreen", () => {
const stage = document.createElement("div");
document.body.appendChild(stage);
setFullscreen(stage);
const onClose = vi.fn();
render(
<Modal initialFocus="close" title="Download" onClose={onClose}>
<p>form</p>
</Modal>,
);
escape();
expect(onClose).toHaveBeenCalledTimes(1);
stage.remove();
});
});
+2 -6
View File
@@ -13,7 +13,7 @@ import { useTranslation } from "react-i18next";
import { X } from "lucide-react";
import { useBackToClose } from "../../lib/history";
import { anyBlockingLayerAbove, pushLayer, removeLayer, takeEscape } from "../../lib/layerStack";
import { fullscreenElement, useFullscreenPortalHost } from "../../lib/fullscreen";
import { useFullscreenPortalHost } from "../../lib/fullscreen";
import { firstFieldIn, focusablesIn, trapTab, useDialogChrome } from "./dialogFocus";
import { IconButton } from "./IconButton";
import Toaster from "./Toaster";
@@ -151,11 +151,7 @@ export default function Modal({
const onKey = (e: KeyboardEvent) => {
if (takeEscape(e, id)) {
e.stopPropagation();
// The browser spends an Escape on leaving fullscreen and still hands it to the page, the
// fullscreen element still set. When that element is the dialog's own content (the library
// player's <video>), the Escape was the browser's: closing too lost the viewer the player.
const fs = fullscreenElement();
if (!(fs && rootRef.current?.contains(fs))) close();
close();
return;
}
// Focus trap: keep Tab / Shift+Tab cycling inside the dialog instead of reaching the inert