learning_ai_common_plat/packages/use-keyboard-shortcuts/src/use-keyboard-shortcuts.ts
Saravanakumar D 07c0d304bc fix(use-keyboard-shortcuts): guard against undefined e.key and shortcut.key
Some keyboard events (dead keys, modifier-only presses) have e.key
as undefined. Similarly, malformed shortcut definitions may lack a key.
Added early-return guards to prevent TypeError.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-30 01:02:15 -07:00

62 lines
1.9 KiB
TypeScript

import { useEffect } from 'react';
export interface ShortcutDef {
/** Key to match (e.g. 'k', 'n', 'Escape', '/') */
key: string;
/** Require Cmd (Mac) or Ctrl (Windows/Linux) — default: false */
meta?: boolean;
/** Require Shift — default: false */
shift?: boolean;
/** Require Alt/Option — default: false */
alt?: boolean;
/** Handler to invoke when shortcut fires */
handler: () => void;
/** Allow firing when focus is in input/textarea — default: false */
allowInInput?: boolean;
/** Human-readable description for help overlay / accessibility */
description?: string;
}
function isInputElement(el: EventTarget | null): boolean {
if (!el || !(el instanceof HTMLElement)) {
return false;
}
const tag = el.tagName;
return tag === 'INPUT' || tag === 'TEXTAREA' || el.isContentEditable;
}
export function useKeyboardShortcuts(shortcuts: ShortcutDef[]): void {
useEffect(() => {
if (shortcuts.length === 0) {
return;
}
function handleKeyDown(e: KeyboardEvent) {
if (!e.key) return;
const inInput = isInputElement(e.target);
for (const shortcut of shortcuts) {
if (!shortcut.key) continue;
const metaMatch = shortcut.meta ? e.metaKey || e.ctrlKey : !(e.metaKey || e.ctrlKey);
const shiftMatch = shortcut.shift ? e.shiftKey : !e.shiftKey;
const altMatch = shortcut.alt ? e.altKey : !e.altKey;
// Normalize key comparison (case-insensitive for letters)
const keyMatch = e.key.toLowerCase() === shortcut.key.toLowerCase();
if (keyMatch && metaMatch && shiftMatch && altMatch) {
if (inInput && !shortcut.allowInInput) {
continue;
}
e.preventDefault();
shortcut.handler();
return;
}
}
}
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, [shortcuts]);
}