mirror of
https://github.com/jakejarvis/sofa.git
synced 2026-10-15 23:55:43 -04:00
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:
3 files changed
+80
-10
No files matched your search
@@ -2,10 +2,16 @@ import { useHotkey } from "@tanstack/react-hotkeys";
|
|||||||
import { useAtomValue } from "jotai";
|
import { useAtomValue } from "jotai";
|
||||||
|
|
||||||
import { commandPaletteOpenAtom } from "@/lib/atoms/command-palette";
|
import { commandPaletteOpenAtom } from "@/lib/atoms/command-palette";
|
||||||
|
import { isOverlayOpen } from "@/lib/overlays";
|
||||||
|
|
||||||
import { useTitleContext, useTitleUserInfo } from "./title-context";
|
import { useTitleContext, useTitleUserInfo } from "./title-context";
|
||||||
import { useTitleActions } from "./use-title-actions";
|
import { useTitleActions } from "./use-title-actions";
|
||||||
|
|
||||||
|
const whenNoOverlay = (fn: () => void) => () => {
|
||||||
|
if (isOverlayOpen()) return;
|
||||||
|
fn();
|
||||||
|
};
|
||||||
|
|
||||||
export function TitleKeyboardShortcuts() {
|
export function TitleKeyboardShortcuts() {
|
||||||
const { titleType } = useTitleContext();
|
const { titleType } = useTitleContext();
|
||||||
const { userStatus } = useTitleUserInfo();
|
const { userStatus } = useTitleUserInfo();
|
||||||
@@ -16,12 +22,16 @@ export function TitleKeyboardShortcuts() {
|
|||||||
const enabled = !commandPaletteOpen;
|
const enabled = !commandPaletteOpen;
|
||||||
|
|
||||||
// W: toggle watchlist (add if not in library, remove if in library)
|
// W: toggle watchlist (add if not in library, remove if in library)
|
||||||
useHotkey("W", () => handleStatusChange(userStatus ? null : "watchlist"), {
|
useHotkey(
|
||||||
|
"W",
|
||||||
|
whenNoOverlay(() => handleStatusChange(userStatus ? null : "watchlist")),
|
||||||
|
{
|
||||||
enabled,
|
enabled,
|
||||||
});
|
},
|
||||||
|
);
|
||||||
useHotkey(
|
useHotkey(
|
||||||
"M",
|
"M",
|
||||||
() => {
|
whenNoOverlay(() => {
|
||||||
if (titleType === "movie") {
|
if (titleType === "movie") {
|
||||||
if (userStatus === "completed") {
|
if (userStatus === "completed") {
|
||||||
handleUnwatchMovie();
|
handleUnwatchMovie();
|
||||||
@@ -29,16 +39,40 @@ export function TitleKeyboardShortcuts() {
|
|||||||
handleWatchMovie();
|
handleWatchMovie();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}),
|
||||||
|
{ enabled },
|
||||||
|
);
|
||||||
|
useHotkey(
|
||||||
|
"Escape",
|
||||||
|
whenNoOverlay(() => window.history.back()),
|
||||||
{ enabled },
|
{ enabled },
|
||||||
);
|
);
|
||||||
useHotkey("Escape", () => window.history.back(), { enabled });
|
|
||||||
|
|
||||||
useHotkey("1", () => handleRating(1), { enabled });
|
useHotkey(
|
||||||
useHotkey("2", () => handleRating(2), { enabled });
|
"1",
|
||||||
useHotkey("3", () => handleRating(3), { enabled });
|
whenNoOverlay(() => handleRating(1)),
|
||||||
useHotkey("4", () => handleRating(4), { enabled });
|
{ enabled },
|
||||||
useHotkey("5", () => handleRating(5), { 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;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
Reference in new issue
Block a user