Skip to content

Instantly share code, notes, and snippets.

@rabelloo
Last active June 30, 2021 11:26
Show Gist options
  • Save rabelloo/029f64f0365cf7c5e4f8cef97ed5f561 to your computer and use it in GitHub Desktop.
Save rabelloo/029f64f0365cf7c5e4f8cef97ed5f561 to your computer and use it in GitHub Desktop.
import { useEffect } from 'react';
/**
* @example
* useKeyDown('escape', cancel);
*/
export function useKeyDown(keys: string, onKeyDown: Listener): void;
/**
* @example
* useKeyDown({ 'cmd + z': undo, 'cmd + z': redo });
*/
export function useKeyDown(keys: Record<string, Listener>): void;
export function useKeyDown(
keys: string | Record<string, Listener>,
onKeyDown?: Listener
) {
const listeners = typeof keys === 'string' ? { [keys]: onKeyDown } : keys;
const hookKey = Object.keys(listeners).join('|');
useEffect(() => {
const targets = toMap(listeners);
const handler = (ev: KeyboardEvent) => targets.get(keysIn(ev))?.(ev);
document.addEventListener('keydown', handler);
return () => document.removeEventListener('keydown', handler);
}, [hookKey]); // eslint-disable-line react-hooks/exhaustive-deps
}
export const isWindows = navigator.appVersion.includes('Win');
type Listener = (ev: KeyboardEvent) => void;
const toMap = (listeners: Record<string, Listener | undefined>) =>
new Map(
Object.entries(listeners).map(([key, listener]) => [
normalise(key),
listener,
])
);
const normalise = (key: string) =>
key
.toLowerCase()
.replaceAll(' ', '')
.replace(/cmd|command/g, 'meta')
.replace(/opt|option/g, 'alt')
.split('+')
.sort()
.join('+');
const keysIn = (ev: KeyboardEvent) =>
[
ev.altKey && 'alt',
ev.ctrlKey && 'ctrl',
ev.metaKey && 'meta',
ev.shiftKey && 'shift',
ev.key.toLowerCase(),
]
.filter(Boolean)
.sort()
.join('+');
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment