The previous implementation had inverted logic that prevented keyboard shortcuts from working. The check `shortcut.ctrl ? event.ctrlKey : !event.ctrlKey` meant that if ctrl wasn't specified, it required ctrl to NOT be pressed, which broke shortcuts with only meta keys. **Changes:** - Fix modifier matching: only check modifiers that are explicitly required (=== true) - Add check to prevent unwanted modifiers (e.g., if only meta+c, reject ctrl+meta+c) - Improve cross-platform meta/cmd key handling (Mac uses metaKey, Windows/Linux uses ctrlKey) - Allow meta key to permit ctrl on non-Mac platforms (meta is treated as ctrl equivalent) **Now working shortcuts in playground:** - Cmd/Ctrl+C: Copy color to clipboard - Cmd/Ctrl+S: Share color (copy URL) - Cmd/Ctrl+R: Generate random color 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
107 lines
2.8 KiB
TypeScript
107 lines
2.8 KiB
TypeScript
import { useEffect } from 'react';
|
|
|
|
export interface KeyboardShortcut {
|
|
key: string;
|
|
ctrl?: boolean;
|
|
shift?: boolean;
|
|
alt?: boolean;
|
|
meta?: boolean;
|
|
handler: (event: KeyboardEvent) => void;
|
|
description?: string;
|
|
}
|
|
|
|
/**
|
|
* Hook to register keyboard shortcuts
|
|
*
|
|
* @example
|
|
* ```tsx
|
|
* useKeyboard([
|
|
* {
|
|
* key: 'c',
|
|
* meta: true, // Cmd on Mac, Ctrl on Windows
|
|
* handler: () => copyToClipboard(),
|
|
* description: 'Copy color',
|
|
* },
|
|
* {
|
|
* key: 'k',
|
|
* meta: true,
|
|
* handler: () => openCommandPalette(),
|
|
* description: 'Open command palette',
|
|
* },
|
|
* ]);
|
|
* ```
|
|
*/
|
|
export function useKeyboard(shortcuts: KeyboardShortcut[]) {
|
|
useEffect(() => {
|
|
const handleKeyDown = (event: KeyboardEvent) => {
|
|
for (const shortcut of shortcuts) {
|
|
const keyMatches = event.key.toLowerCase() === shortcut.key.toLowerCase();
|
|
|
|
// Check if required modifiers match (only check if explicitly required)
|
|
const ctrlMatches = shortcut.ctrl === true ? event.ctrlKey : true;
|
|
const shiftMatches = shortcut.shift === true ? event.shiftKey : true;
|
|
const altMatches = shortcut.alt === true ? event.altKey : true;
|
|
|
|
// Handle meta/cmd key with cross-platform support
|
|
let metaMatches = true;
|
|
if (shortcut.meta === true) {
|
|
// On Mac: require Cmd key
|
|
// On Windows/Linux: accept Ctrl key as Cmd equivalent
|
|
const isMac = navigator.platform.includes('Mac');
|
|
metaMatches = isMac ? event.metaKey : event.ctrlKey;
|
|
}
|
|
|
|
// Ensure unwanted modifiers are not pressed (unless explicitly required)
|
|
const noExtraCtrl = shortcut.ctrl === true || shortcut.meta === true || !event.ctrlKey;
|
|
const noExtraShift = shortcut.shift === true || !event.shiftKey;
|
|
const noExtraAlt = shortcut.alt === true || !event.altKey;
|
|
const noExtraMeta = shortcut.meta === true || !event.metaKey;
|
|
|
|
if (
|
|
keyMatches &&
|
|
ctrlMatches &&
|
|
shiftMatches &&
|
|
altMatches &&
|
|
metaMatches &&
|
|
noExtraCtrl &&
|
|
noExtraShift &&
|
|
noExtraAlt &&
|
|
noExtraMeta
|
|
) {
|
|
event.preventDefault();
|
|
shortcut.handler(event);
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
window.addEventListener('keydown', handleKeyDown);
|
|
|
|
return () => {
|
|
window.removeEventListener('keydown', handleKeyDown);
|
|
};
|
|
}, [shortcuts]);
|
|
}
|
|
|
|
/**
|
|
* Hook to register a single keyboard shortcut (convenience wrapper)
|
|
*/
|
|
export function useKeyboardShortcut(
|
|
key: string,
|
|
handler: (event: KeyboardEvent) => void,
|
|
modifiers?: {
|
|
ctrl?: boolean;
|
|
shift?: boolean;
|
|
alt?: boolean;
|
|
meta?: boolean;
|
|
}
|
|
) {
|
|
useKeyboard([
|
|
{
|
|
key,
|
|
...modifiers,
|
|
handler,
|
|
},
|
|
]);
|
|
}
|