-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Expand file tree
/
Copy pathuseKeyDownHandlers.js
More file actions
72 lines (65 loc) · 2.42 KB
/
useKeyDownHandlers.js
File metadata and controls
72 lines (65 loc) · 2.42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import { mapKeys } from 'lodash';
import PropTypes from 'prop-types';
import { useCallback, useEffect, useRef } from 'react';
/**
* Attaches keydown handlers to the global document.
*
* Handles Mac/PC switching of Ctrl to Cmd.
*
* @param {Record<string, (e: KeyboardEvent) => void>} keyHandlers - an object
* which maps from the key to its event handler. The object keys are a combination
* of the key and prefixes `ctrl-` `shift-` (ie. 'ctrl-f', 'ctrl-shift-f')
* and the values are the function to call when that specific key is pressed.
*/
export default function useKeyDownHandlers(keyHandlers) {
/**
* Instead of memoizing the handlers, use a ref and call the current
* handler at the time of the event.
*/
const handlers = useRef(keyHandlers);
useEffect(() => {
handlers.current = mapKeys(keyHandlers, (value, key) => key?.toLowerCase());
}, [keyHandlers]);
/**
* Will call all matching handlers, starting with the most specific: 'ctrl-shift-f' => 'ctrl-f' => 'f'.
* Can use e.stopPropagation() to prevent subsequent handlers.
* @type {(function(KeyboardEvent): void)}
*/
const handleEvent = useCallback((e) => {
if (!e.key) return;
const isMac = navigator.userAgent.toLowerCase().indexOf('mac') !== -1;
const isCtrl = isMac ? e.metaKey : e.ctrlKey;
if (isCtrl && e.shiftKey && e.altKey && e.code === 'KeyN') {
// specifically for creating a new folder
handlers.current[`ctrl-alt-shift-n`]?.(e);
} else if (isCtrl && e.altKey && e.code === 'KeyN') {
// specifically for creating a new file
handlers.current[`ctrl-alt-n`]?.(e);
} else if (e.shiftKey && isCtrl) {
handlers.current[
`ctrl-shift-${
/^\d+$/.test(e.code.at(-1)) ? e.code.at(-1) : e.key.toLowerCase()
}`
]?.(e);
} else if (isCtrl) {
handlers.current[`ctrl-${e.key.toLowerCase()}`]?.(e);
}
handlers.current[e.key?.toLowerCase()]?.(e);
}, []);
useEffect(() => {
document.addEventListener('keydown', handleEvent);
return () => document.removeEventListener('keydown', handleEvent);
}, [handleEvent]);
}
/**
* Component version can be used in class components where hooks can't be used.
*
* @param {Record<string, (e: KeyboardEvent) => void>} handlers
*/
export const DocumentKeyDown = ({ handlers }) => {
useKeyDownHandlers(handlers);
return null;
};
DocumentKeyDown.propTypes = {
handlers: PropTypes.objectOf(PropTypes.func)
};