Last active
March 4, 2021 18:45
-
-
Save slinkardbrandon/2b3985a03c099f25cd2d83770a3d916a to your computer and use it in GitHub Desktop.
React useKeyBinds hook
This file contains hidden or 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
| export const MyComponent: React.FC = () => { | |
| useKeyBinds({ | |
| "ctrl+z": () => { | |
| console.log('undo'); | |
| }, | |
| "ctrl+y": () => { | |
| console.log('redo'); | |
| }, | |
| }); | |
| return ( | |
| <div> | |
| Some stuff you can edit, undo, and redo with! | |
| </div> | |
| ); | |
| }; |
This file contains hidden or 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
| import React, { useEffect, useMemo } from "react"; | |
| // prettier-ignore | |
| type Alphabet = 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | 'k' | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y'| 'z'; | |
| type Numbers = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"; | |
| type SpecialChars = "enter" | "tab" | "escape" | "delete" | "backspace" | "tab"; | |
| type PrimaryKey = Alphabet | Numbers | SpecialChars; | |
| type ModifierKey = "ctrl" | "alt" | "shift" | "meta"; | |
| type PartialRecord<K extends keyof any, T> = { | |
| [P in K]?: T; | |
| }; | |
| export type KeyBind = PrimaryKey | `${ModifierKey}+${PrimaryKey}`; | |
| export type KeyBindValue = () => void; | |
| export type KeyBinds = PartialRecord<KeyBind, KeyBindValue>; | |
| export type Listener = (ev: KeyboardEvent) => void; | |
| export function useKeyBinds(providedKeyBindMap: KeyBinds) { | |
| const lowerBindMap = useMemo(() => { | |
| return Object.keys(providedKeyBindMap).reduce((prev, curr) => { | |
| prev[curr.toLowerCase()] = providedKeyBindMap[curr]; | |
| return prev; | |
| }, {}); | |
| }, [providedKeyBindMap]); | |
| const executeListenerCallback = React.useCallback( | |
| (ev: KeyboardEvent, callback: () => void) => { | |
| ev.stopPropagation(); | |
| callback(); | |
| }, | |
| [] | |
| ); | |
| useEffect(() => { | |
| const listener: Listener = (ev: KeyboardEvent) => { | |
| if (event.target instanceof HTMLInputElement) { | |
| return; | |
| } | |
| const key = ev.key.toLowerCase(); | |
| const ctrlVariant = `ctrl+${[key]}`; | |
| const shiftVariant = `shift+${[key]}`; | |
| const metaVariant = `meta+${[key]}`; | |
| if (ev.shiftKey && lowerBindMap[shiftVariant]) { | |
| return executeListenerCallback(ev, lowerBindMap[shiftVariant]); | |
| } | |
| if (ev.metaKey && lowerBindMap[metaVariant]) { | |
| return executeListenerCallback(ev, lowerBindMap[metaVariant]); | |
| } | |
| if (ev.ctrlKey && lowerBindMap[ctrlVariant]) { | |
| return executeListenerCallback(ev, lowerBindMap[ctrlVariant]); | |
| } | |
| if (lowerBindMap[key]) { | |
| return executeListenerCallback(ev, lowerBindMap[key]); | |
| } | |
| }; | |
| document.body.addEventListener("keydown", listener); | |
| return () => { | |
| document.body.removeEventListener("keydown", listener); | |
| }; | |
| }, [executeListenerCallback, lowerBindMap]); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment