fix(web): ignore title shortcuts while a dialog or menu is open

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
jakeandClaude Opus 5.5 committed 2026-10-06 12:23:04 -04:00
1 parent f36820321c
commit 4eadce286b
3 files changed
+81 -11

No files matched your search

@@ -2,10 +2,16 @@ import { useHotkey } from "@tanstack/react-hotkeys";
import { useAtomValue } from "jotai";
import { commandPaletteOpenAtom } from "@/lib/atoms/command-palette";
import { isOverlayOpen } from "@/lib/overlays";
import { useTitleContext, useTitleUserInfo } from "./title-context";
import { useTitleActions } from "./use-title-actions";
const whenNoOverlay = (fn: () => void) => () => {
if (isOverlayOpen()) return;
fn();
};
export function TitleKeyboardShortcuts() {
const { titleType } = useTitleContext();
const { userStatus } = useTitleUserInfo();
@@ -16,12 +22,16 @@ export function TitleKeyboardShortcuts() {
const enabled = !commandPaletteOpen;
// W: toggle watchlist (add if not in library, remove if in library)
useHotkey("W", () => handleStatusChange(userStatus ? null : "watchlist"), {
enabled,
});
useHotkey(
"W",
whenNoOverlay(() => handleStatusChange(userStatus ? null : "watchlist")),
{
enabled,
},
);
useHotkey(
"M",
() => {
whenNoOverlay(() => {
if (titleType === "movie") {
if (userStatus === "completed") {
handleUnwatchMovie();
@@ -29,16 +39,40 @@ export function TitleKeyboardShortcuts() {
handleWatchMovie();
}
}
},
}),
{ enabled },
);
useHotkey(
"Escape",
whenNoOverlay(() => window.history.back()),
{ enabled },
);
useHotkey("Escape", () => window.history.back(), { enabled });
useHotkey("1", () => handleRating(1), { enabled });
useHotkey("2", () => handleRating(2), { enabled });
useHotkey("3", () => handleRating(3), { enabled });
useHotkey("4", () => handleRating(4), { enabled });
useHotkey("5", () => handleRating(5), { enabled });
useHotkey(
"1",
whenNoOverlay(() => handleRating(1)),
{ enabled },
);
useHotkey(
"2",
whenNoOverlay(() => handleRating(2)),
{ enabled },
);
useHotkey(
"3",
whenNoOverlay(() => handleRating(3)),
{ enabled },
);
useHotkey(
"4",
whenNoOverlay(() => handleRating(4)),
{ enabled },
);
useHotkey(
"5",
whenNoOverlay(() => handleRating(5)),
{ enabled },
);
return null;
}
+30
View File
@@ -0,0 +1,30 @@
import { afterEach, describe, expect, test } from "vitest";
import { isOverlayOpen } from "./overlays";
describe("isOverlayOpen", () => {
const added: HTMLElement[] = [];
function add(role: string) {
const el = document.createElement("div");
el.setAttribute("role", role);
document.body.appendChild(el);
added.push(el);
return el;
}
afterEach(() => {
for (const el of added.splice(0)) el.remove();
});
test("false when nothing is open", () => {
expect(isOverlayOpen()).toBe(false);
});
test.each(["dialog", "alertdialog", "menu"])("true with role=%s", (role) => {
const el = add(role);
expect(isOverlayOpen()).toBe(true);
el.remove();
expect(isOverlayOpen()).toBe(false);
});
});
+6
View File
@@ -0,0 +1,6 @@
const OVERLAY_SELECTOR = '[role="dialog"], [role="alertdialog"], [role="menu"]';
/** True while any dialog, alert dialog, popover or menu is open on the page. */
export function isOverlayOpen(root: ParentNode = document): boolean {
return root.querySelector(OVERLAY_SELECTOR) !== null;
}