Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 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 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | import { match } from 'ts-pattern'; import { useWindowEvent } from '../use-window-event/use-window-event'; export type KbdEventType = 'keydown' | 'keypress' | 'keyup'; const KBD_MOFIFIERS = ['alt', 'ctrl', 'meta', 'shift'] as const; export type KbdModifier = (typeof KBD_MOFIFIERS)[number]; export type KbdKey = | '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'; export const KBD_SEPARATOR = '.'; type KbdSeparator = typeof KBD_SEPARATOR; export type KbdCode = | `${KbdKey}` | `${KbdModifier}${KbdSeparator}${KbdKey}` | `${KbdModifier}${KbdSeparator}${KbdModifier}${KbdSeparator}${KbdKey}` | `${KbdModifier}${KbdSeparator}${KbdModifier}${KbdSeparator}${KbdModifier}${KbdSeparator}${KbdKey}` | `${KbdModifier}${KbdSeparator}${KbdModifier}${KbdSeparator}${KbdModifier}${KbdSeparator}${KbdModifier}${KbdSeparator}${KbdKey}`; const eventMatchesKbdCode = (code: KbdCode, event: KeyboardEvent) => { const parts = code.split(KBD_SEPARATOR) as (KbdKey | KbdModifier)[]; const modifiers = new Set(parts.slice(0, -1)); const key = parts.at(-1)!; return ( event.key.toLowerCase() === key && // Make sure only specified modifiers are pressed (e.g. ctrl.a doesn't match ctrl + shift + a). KBD_MOFIFIERS.every((modifier) => { const required = modifiers.has(modifier); return match(modifier) .with('alt', () => event.altKey === required) .with('ctrl', () => event.ctrlKey === required) .with('meta', () => event.metaKey === required) .with('shift', () => event.shiftKey === required) .exhaustive(); }) ); }; export const useKeyboardEvent = ( eventType: KbdEventType, filter: KbdCode | KbdCode[] | ((event: KeyboardEvent) => boolean), listener: (ev: KeyboardEvent) => void, options?: AddEventListenerOptions | boolean, ) => useWindowEvent( eventType, (event) => { if ( typeof filter === 'function' ? filter(event) : Array.isArray(filter) ? filter.some((code) => eventMatchesKbdCode(code, event)) : eventMatchesKbdCode(filter, event) ) { listener(event); } }, options, ); |