-
Notifications
You must be signed in to change notification settings - Fork 333
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat(keyboard): create keyboard hooks * feat(usekeyboard): redesign event handler to match keyboard events from browser \ * test(usekeyboard): add testcase * docs(usekeyboard): create new hooks document
- Loading branch information
Showing
12 changed files
with
570 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
import React from 'react' | ||
import { mount } from 'enzyme' | ||
import { useKeyboard, KeyMod, KeyCode } from 'components' | ||
import { renderHook, act } from '@testing-library/react-hooks' | ||
import { KeyboardResult } from '../use-keyboard' | ||
|
||
describe('UseKeyboard', () => { | ||
it('should work correctly', () => { | ||
let code = null | ||
const handler = jest.fn().mockImplementation(e => { | ||
code = e.keyCode | ||
}) | ||
renderHook(() => useKeyboard(handler, KeyCode.KEY_H)) | ||
document.dispatchEvent(new KeyboardEvent('keydown', { keyCode: KeyCode.KEY_H })) | ||
expect(handler).toBeCalledTimes(1) | ||
expect(code).toEqual(KeyCode.KEY_H) | ||
}) | ||
|
||
it('should not trigger handler', () => { | ||
const handler = jest.fn().mockImplementation(() => {}) | ||
renderHook(() => useKeyboard(handler, [KeyCode.KEY_0])) | ||
const event = new KeyboardEvent('keydown', { keyCode: KeyCode.KEY_1 }) | ||
document.dispatchEvent(event) | ||
expect(handler).not.toBeCalled() | ||
}) | ||
|
||
it('should trigger with command key', () => { | ||
const handler = jest.fn().mockImplementation(() => {}) | ||
renderHook(() => useKeyboard(handler, [KeyCode.KEY_A, KeyMod.Shift])) | ||
const event = new KeyboardEvent('keydown', { keyCode: KeyCode.KEY_A }) | ||
document.dispatchEvent(event) | ||
expect(handler).not.toBeCalled() | ||
const event2 = new KeyboardEvent('keydown', { | ||
keyCode: KeyCode.KEY_A, | ||
shiftKey: true, | ||
}) | ||
document.dispatchEvent(event2) | ||
expect(handler).toBeCalledTimes(1) | ||
}) | ||
|
||
it('should ignore command when code does not exist', () => { | ||
const handler = jest.fn().mockImplementation(() => {}) | ||
renderHook(() => useKeyboard(handler, [KeyCode.KEY_A, 12345])) | ||
const event = new KeyboardEvent('keydown', { keyCode: KeyCode.KEY_A }) | ||
document.dispatchEvent(event) | ||
expect(handler).toBeCalled() | ||
}) | ||
|
||
it('should work with each command', () => { | ||
const handler = jest.fn().mockImplementation(() => {}) | ||
renderHook(() => | ||
useKeyboard(handler, [KeyCode.KEY_A, KeyMod.Alt, KeyMod.CtrlCmd, KeyMod.WinCtrl]), | ||
) | ||
document.dispatchEvent( | ||
new KeyboardEvent('keydown', { | ||
keyCode: KeyCode.KEY_A, | ||
}), | ||
) | ||
expect(handler).not.toBeCalled() | ||
document.dispatchEvent( | ||
new KeyboardEvent('keydown', { | ||
keyCode: KeyCode.KEY_A, | ||
altKey: true, | ||
}), | ||
) | ||
expect(handler).not.toBeCalled() | ||
document.dispatchEvent( | ||
new KeyboardEvent('keydown', { | ||
keyCode: KeyCode.KEY_A, | ||
altKey: true, | ||
ctrlKey: true, | ||
}), | ||
) | ||
expect(handler).not.toBeCalled() | ||
document.dispatchEvent( | ||
new KeyboardEvent('keydown', { | ||
keyCode: KeyCode.KEY_A, | ||
altKey: true, | ||
ctrlKey: true, | ||
metaKey: true, | ||
}), | ||
) | ||
expect(handler).toBeCalledTimes(1) | ||
}) | ||
|
||
it('should ignore global events', () => { | ||
const handler = jest.fn().mockImplementation(() => {}) | ||
renderHook(() => useKeyboard(handler, [KeyCode.KEY_A], { disableGlobalEvent: true })) | ||
const event = new KeyboardEvent('keydown', { keyCode: KeyCode.KEY_A }) | ||
document.dispatchEvent(event) | ||
expect(handler).not.toBeCalled() | ||
}) | ||
|
||
it('should respond to different event types', () => { | ||
const handler = jest.fn().mockImplementation(() => {}) | ||
renderHook(() => useKeyboard(handler, [KeyCode.KEY_A], { event: 'keyup' })) | ||
document.dispatchEvent(new KeyboardEvent('keydown', { keyCode: KeyCode.KEY_A })) | ||
expect(handler).not.toBeCalled() | ||
|
||
document.dispatchEvent(new KeyboardEvent('keypress', { keyCode: KeyCode.KEY_A })) | ||
expect(handler).not.toBeCalled() | ||
|
||
document.dispatchEvent(new KeyboardEvent('keyup', { keyCode: KeyCode.KEY_A })) | ||
expect(handler).toBeCalled() | ||
}) | ||
|
||
it('should pass the keyboard events', () => { | ||
const handler = jest.fn().mockImplementation(() => {}) | ||
const nativeHandler = jest.fn().mockImplementation(() => {}) | ||
const { result } = renderHook<void, KeyboardResult>(() => | ||
useKeyboard(handler, KeyCode.Escape), | ||
) | ||
const wrapper = mount( | ||
<div onKeyDown={nativeHandler}> | ||
<span id="inner" {...result.current.bindings} /> | ||
</div>, | ||
) | ||
const inner = wrapper.find('#inner').at(0) | ||
act(() => { | ||
inner.simulate('keyup', { | ||
keyCode: KeyCode.Escape, | ||
}) | ||
}) | ||
expect(handler).not.toBeCalled() | ||
expect(nativeHandler).not.toBeCalled() | ||
act(() => { | ||
inner.simulate('keydown', { | ||
keyCode: KeyCode.Escape, | ||
}) | ||
}) | ||
expect(handler).toBeCalled() | ||
expect(nativeHandler).toBeCalled() | ||
}) | ||
|
||
it('should prevent default events', () => { | ||
const handler = jest.fn().mockImplementation(() => {}) | ||
const nativeHandler = jest.fn().mockImplementation(() => {}) | ||
const { result } = renderHook<void, KeyboardResult>(() => | ||
useKeyboard(handler, KeyCode.Escape, { | ||
disableGlobalEvent: true, | ||
stopPropagation: true, | ||
}), | ||
) | ||
const wrapper = mount( | ||
<div onKeyDown={nativeHandler}> | ||
<span id="inner" {...result.current.bindings} /> | ||
</div>, | ||
) | ||
const inner = wrapper.find('#inner').at(0) | ||
act(() => { | ||
inner.simulate('keydown', { | ||
keyCode: KeyCode.Escape, | ||
}) | ||
}) | ||
expect(handler).toBeCalled() | ||
expect(nativeHandler).not.toBeCalled() | ||
}) | ||
|
||
it('should trigger capture event', () => { | ||
const handler = jest.fn().mockImplementation(() => {}) | ||
const { result } = renderHook<void, KeyboardResult>(() => | ||
useKeyboard(handler, KeyCode.Escape, { capture: true, disableGlobalEvent: true }), | ||
) | ||
const wrapper = mount( | ||
<div onKeyDownCapture={result.current.bindings.onKeyDownCapture}> | ||
<span id="inner" /> | ||
</div>, | ||
) | ||
const inner = wrapper.find('#inner').at(0) | ||
act(() => { | ||
inner.simulate('keydown', { | ||
keyCode: KeyCode.Escape, | ||
}) | ||
}) | ||
expect(handler).toBeCalled() | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
/** | ||
* KeyBinding Codes | ||
* The content of this file is based on the design of the open source project "microsoft/vscode", | ||
* Copyright (c) Microsoft Corporation. All rights reserved. | ||
* | ||
* We inherit the KeyMod values from "microsoft/vscode", | ||
* but use the Browser's KeyboardEvent event implementation, and all values are used only as identification. | ||
*/ | ||
|
||
export enum KeyCode { | ||
Unknown = 0, | ||
Backspace = 8, | ||
Tab = 9, | ||
Enter = 13, | ||
Shift = 16, | ||
Ctrl = 17, | ||
Alt = 18, | ||
PauseBreak = 19, | ||
CapsLock = 20, | ||
Escape = 27, | ||
Space = 32, | ||
PageUp = 33, | ||
PageDown = 34, | ||
End = 35, | ||
Home = 36, | ||
LeftArrow = 37, | ||
UpArrow = 38, | ||
RightArrow = 39, | ||
DownArrow = 40, | ||
Insert = 45, | ||
Delete = 46, | ||
KEY_0 = 48, | ||
KEY_1 = 49, | ||
KEY_2 = 50, | ||
KEY_3 = 51, | ||
KEY_4 = 52, | ||
KEY_5 = 53, | ||
KEY_6 = 54, | ||
KEY_7 = 55, | ||
KEY_8 = 56, | ||
KEY_9 = 57, | ||
KEY_A = 65, | ||
KEY_B = 66, | ||
KEY_C = 67, | ||
KEY_D = 68, | ||
KEY_E = 69, | ||
KEY_F = 70, | ||
KEY_G = 71, | ||
KEY_H = 72, | ||
KEY_I = 73, | ||
KEY_J = 74, | ||
KEY_K = 75, | ||
KEY_L = 76, | ||
KEY_M = 77, | ||
KEY_N = 78, | ||
KEY_O = 79, | ||
KEY_P = 80, | ||
KEY_Q = 81, | ||
KEY_R = 82, | ||
KEY_S = 83, | ||
KEY_T = 84, | ||
KEY_U = 85, | ||
KEY_V = 86, | ||
KEY_W = 87, | ||
KEY_X = 88, | ||
KEY_Y = 89, | ||
KEY_Z = 90, | ||
Meta = 91, | ||
F1 = 112, | ||
F2 = 113, | ||
F3 = 114, | ||
F4 = 115, | ||
F5 = 116, | ||
F6 = 117, | ||
F7 = 118, | ||
F8 = 119, | ||
F9 = 120, | ||
F10 = 121, | ||
F11 = 122, | ||
F12 = 123, | ||
NumLock = 144, | ||
ScrollLock = 145, | ||
Equal = 187, | ||
Minus = 189, | ||
Backquote = 192, | ||
Backslash = 220, | ||
} | ||
|
||
export enum KeyMod { | ||
CtrlCmd = (1 << 11) >>> 0, | ||
Shift = (1 << 10) >>> 0, | ||
Alt = (1 << 9) >>> 0, | ||
WinCtrl = (1 << 8) >>> 0, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { isMac } from '../utils/collections' | ||
import { KeyMod } from './codes' | ||
|
||
/* istanbul ignore next */ | ||
export const getCtrlKeysByPlatform = (): Record<string, 'metaKey' | 'ctrlKey'> => { | ||
return { | ||
CtrlCmd: isMac() ? 'metaKey' : 'ctrlKey', | ||
WinCtrl: isMac() ? 'ctrlKey' : 'metaKey', | ||
} | ||
} | ||
|
||
export const getActiveModMap = ( | ||
bindings: number[], | ||
): Record<keyof typeof KeyMod, boolean> => { | ||
const modBindings = bindings.filter((item: number) => !!KeyMod[item]) | ||
const activeModMap: Record<keyof typeof KeyMod, boolean> = { | ||
CtrlCmd: false, | ||
Shift: false, | ||
Alt: false, | ||
WinCtrl: false, | ||
} | ||
modBindings.forEach(code => { | ||
const modKey = KeyMod[code] as keyof typeof KeyMod | ||
activeModMap[modKey] = true | ||
}) | ||
return activeModMap | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import useKeyboard from './use-keyboard' | ||
import { KeyMod, KeyCode } from './codes' | ||
|
||
export default useKeyboard | ||
export { KeyMod, KeyCode } |
Oops, something went wrong.