fix(use-keyboard-shortcuts): enforce symmetric modifier matching

Modifiers (shift, alt, meta) are now checked in both directions:
when not required, the physical key must NOT be pressed either.

Before: Cmd+K shortcut would fire on Cmd+Shift+K or Cmd+Alt+K.
After: exact modifier combination is enforced.

4 regression tests added.
This commit is contained in:
saravanakumardb1 2026-03-29 12:45:32 -07:00
parent 31cf7c0c6f
commit 6f4957d821
2 changed files with 43 additions and 3 deletions

View File

@ -197,4 +197,44 @@ describe('useKeyboardShortcuts', () => {
expect(handler).toHaveBeenCalledTimes(1);
});
it('does not fire Cmd+K shortcut when Shift is also pressed', () => {
const handler = vi.fn();
const shortcuts: ShortcutDef[] = [{ key: 'k', meta: true, handler }];
renderHook(() => useKeyboardShortcuts(shortcuts));
fireKey('k', { metaKey: true, shiftKey: true });
expect(handler).not.toHaveBeenCalled();
});
it('does not fire Cmd+K shortcut when Alt is also pressed', () => {
const handler = vi.fn();
const shortcuts: ShortcutDef[] = [{ key: 'k', meta: true, handler }];
renderHook(() => useKeyboardShortcuts(shortcuts));
fireKey('k', { metaKey: true, altKey: true });
expect(handler).not.toHaveBeenCalled();
});
it('does not fire bare-key shortcut when meta is pressed', () => {
const handler = vi.fn();
const shortcuts: ShortcutDef[] = [{ key: 'Escape', handler }];
renderHook(() => useKeyboardShortcuts(shortcuts));
fireKey('Escape', { metaKey: true });
expect(handler).not.toHaveBeenCalled();
});
it('does not fire bare-key shortcut when shift is pressed', () => {
const handler = vi.fn();
const shortcuts: ShortcutDef[] = [{ key: '/', handler }];
renderHook(() => useKeyboardShortcuts(shortcuts));
fireKey('/', { shiftKey: true });
expect(handler).not.toHaveBeenCalled();
});
});

View File

@ -35,9 +35,9 @@ export function useKeyboardShortcuts(shortcuts: ShortcutDef[]): void {
const inInput = isInputElement(e.target);
for (const shortcut of shortcuts) {
const metaMatch = shortcut.meta ? e.metaKey || e.ctrlKey : true;
const shiftMatch = shortcut.shift ? e.shiftKey : !e.shiftKey || !shortcut.meta; // only enforce no-shift when meta is set
const altMatch = shortcut.alt ? e.altKey : true;
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();