Skip to content

Commit 6b472fa

Browse files
committed
Add useKeyPress hook
1 parent 761cb50 commit 6b472fa

File tree

2 files changed

+110
-0
lines changed

2 files changed

+110
-0
lines changed
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { mount } from 'enzyme';
2+
import { useRef } from 'preact/hooks';
3+
import { act } from 'preact/test-utils';
4+
5+
import { useKeyPress } from '../use-key-press';
6+
7+
describe('useKeyPress', () => {
8+
let handler;
9+
const defaultKeys = ['d', 'u'];
10+
11+
const createEvent = (name, props) => {
12+
const event = new Event(name);
13+
Object.assign(event, props);
14+
return event;
15+
};
16+
17+
// Create a fake component to mount in tests that uses the hook
18+
function FakeComponent({ keys = defaultKeys, enabled = true }) {
19+
const myRef = useRef();
20+
useKeyPress(keys, handler, { enabled });
21+
return (
22+
<div ref={myRef}>
23+
<button>Hi</button>
24+
</div>
25+
);
26+
}
27+
28+
function createComponent(props) {
29+
return mount(<FakeComponent {...props} />);
30+
}
31+
32+
beforeEach(() => {
33+
handler = sinon.stub();
34+
});
35+
36+
defaultKeys.forEach(key => {
37+
it(`should invoke callback when registered key ${key} is pressed`, () => {
38+
const event = createEvent('keydown', { key });
39+
const wrapper = createComponent();
40+
41+
act(() => {
42+
document.body.dispatchEvent(event);
43+
});
44+
wrapper.update();
45+
46+
assert.calledOnce(handler);
47+
48+
// Update the component to change it and re-execute the hook
49+
wrapper.setProps({ enabled: false });
50+
51+
act(() => {
52+
document.body.dispatchEvent(event);
53+
});
54+
55+
// Cleanup of hook should have removed eventListeners, so the callback
56+
// is not called again
57+
assert.calledOnce(handler);
58+
});
59+
});
60+
61+
it('should not invoke callback if a non-registered key is pressed', () => {
62+
const event = createEvent('keydown', { key: 'q' });
63+
const wrapper = createComponent();
64+
65+
act(() => {
66+
document.body.dispatchEvent(event);
67+
});
68+
wrapper.update();
69+
70+
assert.notCalled(handler);
71+
});
72+
});

src/hooks/use-key-press.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { useEffect } from 'preact/hooks';
2+
3+
import { ListenerCollection } from '../util/listener-collection';
4+
5+
type UseKeyPressOptions = {
6+
/** Enable listening for key events? Can be set to false to disable */
7+
enabled?: boolean;
8+
};
9+
10+
/**
11+
* Listen on HTMLElement `target` for key press events for the designated `keys`
12+
* and invoke a callback. Do not listen if not `enabled`.
13+
*
14+
* @param keys - Array of keys (e.g. 'Escape', 'd') to listen for
15+
*/
16+
export function useKeyPress(
17+
keys: string[],
18+
callback: (e: KeyboardEvent) => void,
19+
{ enabled = true }: UseKeyPressOptions = {}
20+
) {
21+
useEffect(() => {
22+
if (!enabled) {
23+
return () => {};
24+
}
25+
const target = document.body;
26+
const listeners = new ListenerCollection();
27+
28+
listeners.add(target, 'keydown', event => {
29+
if (keys.includes(event.key)) {
30+
callback(event);
31+
}
32+
});
33+
34+
return () => {
35+
listeners.removeAll();
36+
};
37+
}, [enabled, callback, keys]);
38+
}

0 commit comments

Comments
 (0)