useKeyboardShortcut Composable
Reactive keyboard shortcut handling with modifier keys and scoped targets
Code
import { ref, onMounted, onUnmounted, type Ref } from 'vue';
type ModifierKey = 'ctrl' | 'shift' | 'alt' | 'meta';
interface ShortcutOptions {
modifiers?: ModifierKey[];
target?: HTMLElement | Window;
preventDefault?: boolean;
stopPropagation?: boolean;
scoped?: boolean; // Only trigger when target is focused
}
export function useKeyboardShortcut(
key: string | string[],
callback: (e: KeyboardEvent) => void,
options: ShortcutOptions = { preventDefault: true, stopPropagation: false, scoped: false }
) {
const isListening = ref(true);
const keys = Array.isArray(key) ? key : [key];
const target = options.target || window;
const isModifierKeyPressed = (e: KeyboardEvent, modifier: ModifierKey) => {
switch (modifier) {
case 'ctrl': return e.ctrlKey;
case 'shift': return e.shiftKey;
case 'alt': return e.altKey;
case 'meta': return e.metaKey;
default: return false;
}
};
const areModifiersPressed = (e: KeyboardEvent) => {
const requiredModifiers = options.modifiers || [];
__TOKEN_47__ (requiredModifiers.length === 0) return true;
// Check all required modifiers are pressed
return requiredModifiers.every(mod => isModifierKeyPressed(e, mod));
};
const handleKeyDown = (e: KeyboardEvent) => {
__TOKEN_51__ (!isListening.value) return;
// Scoped mode: only trigger if target is focused
__TOKEN_53__ (options.scoped && target !== window) {
const focusedElement = document.activeElement;
__TOKEN_55__ (!focusedElement || !target.contains(focusedElement)) return;
}
// Check if key matches
const keyMatches = keys.some(k => {
// Normalize key (case insensitive, handle special keys)
const normalizedKey = k.toLowerCase();
const eventKey = e.key.toLowerCase();
// Handle special cases (e.g. 'enter' vs 'return')
__TOKEN_60__ (normalizedKey === 'enter' && eventKey === 'return') return true;
__TOKEN_62__ (normalizedKey === 'esc' && eventKey === 'escape') return true;
return eventKey === normalizedKey;
});
__TOKEN_65__ (keyMatches && areModifiersPressed(e)) {
__TOKEN_66__ (options.preventDefault) e.preventDefault();
__TOKEN_67__ (options.stopPropagation) e.stopPropagation();
callback(e);
}
};
const enable = () => {
isListening.value = true;
};
const disable = () => {
isListening.value = false;
};
const toggle = () => {
isListening.value = !isListening.value;
};
onMounted(() => {
target.addEventListener('keydown', handleKeyDown);
});
onUnmounted(() => {
target.removeEventListener('keydown', handleKeyDown);
});
return {
isListening,
enable,
disable,
toggle
};
}
// Usage example
// const { disable } = useKeyboardShortcut(
// 's',
// () => saveDocument(),
// { modifiers: ['ctrl'], preventDefault: true }
// );
// // Disable shortcut temporarily
// disable();