don't run page shortcuts for keys a dialog already handled

Radix dismisses a dialog on Escape from a capture-phase keydown listener and calls `preventDefault()` without stopping propagation, so `useKeyboardListener` still ran the page's Escape shortcut: cancelling the delete dialog in the face library or a classification model also cleared the whole selection. Keys another shortcut hook handled still get through, since their listener order changes with every render.
This commit is contained in:
Josh Hawkins
2026-09-18 06:55:36 -05:00
parent 51eaae4857
commit 27db8f1d74
+18 -1
View File
@@ -7,6 +7,16 @@ export type KeyModifiers = {
shift: boolean;
};
const handledByShortcut = new WeakSet<Event>();
// Radix dismisses a dialog or menu on Escape from a capture-phase listener and
// calls preventDefault() without stopping propagation, so a page shortcut would
// otherwise act on the same press. Keys another shortcut hook handled still get
// through, since their listener order changes with every render.
function handledElsewhere(event: KeyboardEvent): boolean {
return event.defaultPrevented && !handledByShortcut.has(event);
}
export default function useKeyboardListener(
keys: string[],
listener?: (key: string | null, modifiers: KeyModifiers) => boolean,
@@ -27,6 +37,10 @@ export default function useKeyboardListener(
return;
}
if (handledElsewhere(e)) {
return;
}
const modifiers = {
down: true,
repeat: e.repeat,
@@ -63,7 +77,10 @@ export default function useKeyboardListener(
}
} else if (keys.includes(e.key) && listener) {
const preventDefault = listener(e.key, modifiers);
if (preventDefault) e.preventDefault();
if (preventDefault) {
e.preventDefault();
handledByShortcut.add(e);
}
} else if (
listener &&
(e.key === "Shift" || e.key === "Control" || e.key === "Meta")