use-keypress
efferd-/use-keypressFreeHook
Use keypress hook.
Live preview
live · rendered by the registry
Rendered by efferd on their own page — this is the component running, not a screenshot of it.Install it
npx shadcn@latest add http://efferd.com/r/use-keypress.jsonSource
1"use client";23import { useCallback, useEffect, useMemo, useRef } from "react";45const MODS = ["ctrl", "alt", "shift", "meta"] as const;67const isMod = (p: string): boolean => (MODS as readonly string[]).includes(p);89const IGNORE_FOCUS = new Set(["INPUT", "TEXTAREA", "SELECT"]);1011export type UseKeypressOptions = {12 /** One or more chords, e.g. `"meta+k"` or `["ctrl+b", "meta+b"]` — any match fires `callback`. */13 combo: string | string[];14 callback: (event: KeyboardEvent) => void;15 /** When true, calls `preventDefault()` if a chord matches. Default is true. */16 preventDefault?: boolean;17 /** Element to attach to; omit for `window`. */18 target?: HTMLElement | null;19};2021function normalize(combo: string): string {22 const parts = combo23 .toLowerCase()24 .split("+")25 .map((s) => s.trim());26 const M = MODS as readonly string[];27 const mods = parts.filter(isMod).sort((a, b) => M.indexOf(a) - M.indexOf(b));28 const keys = parts.filter((p) => !isMod(p));29 return [...mods, ...keys].join("+");30}3132function matches(e: KeyboardEvent, norm: string): boolean {33 const segs = norm.split("+");34 const wantMods = segs.filter(isMod);35 const wantKey = segs.find((p) => !isMod(p));36 const haveMods = [37 e.ctrlKey && "ctrl",38 e.altKey && "alt",39 e.shiftKey && "shift",40 e.metaKey && "meta",41 ].filter(Boolean) as string[];4243 if (44 wantMods.length !== haveMods.length ||45 !wantMods.every((m) => haveMods.includes(m))46 ) {47 return false;48 }49 return e.key.toLowerCase() === wantKey;50}5152/**53 * Listens for `keydown` shortcut chords. Skips firing while focus is in an input,54 * textarea, select, or `contentEditable` region.55 */56export function useKeypress({57 combo,58 callback,59 preventDefault = true,60 target,61}: UseKeypressOptions) {62 const callbackRef = useRef(callback);63 useEffect(() => {64 callbackRef.current = callback;65 }, [callback]);6667 const normalized = useMemo(68 () => (Array.isArray(combo) ? combo : [combo]).map(normalize),69 [combo]70 );7172 const onKeyDown = useCallback(73 (ev: Event) => {74 const e = ev as KeyboardEvent;75 const el = e.target as HTMLElement | undefined;76 if (IGNORE_FOCUS.has(el?.tagName ?? "") || el?.isContentEditable) {77 return;78 }79 for (const chord of normalized) {80 if (!matches(e, chord)) {81 continue;82 }83 if (preventDefault) {84 e.preventDefault();85 }86 callbackRef.current(e);87 break;88 }89 },90 [normalized, preventDefault]91 );9293 useEffect(() => {94 const el = target ?? window;95// … truncated
Files it writes
More from efferd
All 201 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| use-copy-to-clipboarduse-copy-to-clipboard | efferd | Hooks | Free | no deps | |
| use-media-queryuse-media-query | efferd | Hooks | Free | no deps | |
| use-scrolluse-scroll | efferd | Hooks | Free | no deps |
