useHotkeys.tsx 1.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859
  1. import {useCallback, useEffect, useMemo} from 'react';
  2. import {getKeyCode} from './getKeyCode';
  3. const isKeyPressed = (key: string, evt: KeyboardEvent): boolean => {
  4. const keyCode = getKeyCode(key);
  5. switch (keyCode) {
  6. case getKeyCode('command'):
  7. return evt.metaKey;
  8. case getKeyCode('shift'):
  9. return evt.shiftKey;
  10. case getKeyCode('ctrl'):
  11. return evt.ctrlKey;
  12. case getKeyCode('alt'):
  13. return evt.altKey;
  14. default:
  15. return keyCode === evt.keyCode;
  16. }
  17. };
  18. /**
  19. * Pass in the hotkey combinations under match and the corresponding callback function to be called.
  20. * Separate key names with +. For example, 'command+alt+shift+x'
  21. * Alternate matchings as an array: ['command+alt+backspace', 'ctrl+alt+delete']
  22. *
  23. * Note: you can only use one non-modifier (keys other than shift, ctrl, alt, command) key at a time.
  24. */
  25. export function useHotkeys(
  26. hotkeys: {callback: (e: KeyboardEvent) => void; match: string[] | string}[],
  27. deps: React.DependencyList
  28. ): void {
  29. // eslint-disable-next-line react-hooks/exhaustive-deps
  30. const memoizedHotkeys = useMemo(() => hotkeys, deps);
  31. const onKeyDown = useCallback(
  32. (evt: KeyboardEvent) => {
  33. for (const set of memoizedHotkeys) {
  34. const keysets = Array.isArray(set.match) ? set.match : [set.match];
  35. for (const keyset of keysets) {
  36. const keys = keyset.split('+');
  37. if (keys.every(key => isKeyPressed(key, evt))) {
  38. set.callback(evt);
  39. return;
  40. }
  41. }
  42. }
  43. },
  44. [memoizedHotkeys]
  45. );
  46. useEffect(() => {
  47. document.addEventListener('keydown', onKeyDown);
  48. return () => {
  49. document.removeEventListener('keydown', onKeyDown);
  50. };
  51. }, [onKeyDown]);
  52. }