{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"use-keypress","type":"registry:hook","description":"Use keypress hook.","files":[{"path":"hooks/use-keypress.ts","type":"registry:hook","content":"\"use client\";\n\nimport { useCallback, useEffect, useMemo, useRef } from \"react\";\n\nconst MODS = [\"ctrl\", \"alt\", \"shift\", \"meta\"] as const;\n\nconst isMod = (p: string): boolean => (MODS as readonly string[]).includes(p);\n\nconst IGNORE_FOCUS = new Set([\"INPUT\", \"TEXTAREA\", \"SELECT\"]);\n\nexport type UseKeypressOptions = {\n\t/** One or more chords, e.g. `\"meta+k\"` or `[\"ctrl+b\", \"meta+b\"]` — any match fires `callback`. */\n\tcombo: string | string[];\n\tcallback: (event: KeyboardEvent) => void;\n\t/** When true, calls `preventDefault()` if a chord matches. Default is true. */\n\tpreventDefault?: boolean;\n\t/** Element to attach to; omit for `window`. */\n\ttarget?: HTMLElement | null;\n};\n\nfunction normalize(combo: string): string {\n\tconst parts = combo\n\t\t.toLowerCase()\n\t\t.split(\"+\")\n\t\t.map((s) => s.trim());\n\tconst M = MODS as readonly string[];\n\tconst mods = parts.filter(isMod).sort((a, b) => M.indexOf(a) - M.indexOf(b));\n\tconst keys = parts.filter((p) => !isMod(p));\n\treturn [...mods, ...keys].join(\"+\");\n}\n\nfunction matches(e: KeyboardEvent, norm: string): boolean {\n\tconst segs = norm.split(\"+\");\n\tconst wantMods = segs.filter(isMod);\n\tconst wantKey = segs.find((p) => !isMod(p));\n\tconst haveMods = [\n\t\te.ctrlKey && \"ctrl\",\n\t\te.altKey && \"alt\",\n\t\te.shiftKey && \"shift\",\n\t\te.metaKey && \"meta\",\n\t].filter(Boolean) as string[];\n\n\tif (\n\t\twantMods.length !== haveMods.length ||\n\t\t!wantMods.every((m) => haveMods.includes(m))\n\t) {\n\t\treturn false;\n\t}\n\treturn e.key.toLowerCase() === wantKey;\n}\n\n/**\n * Listens for `keydown` shortcut chords. Skips firing while focus is in an input,\n * textarea, select, or `contentEditable` region.\n */\nexport function useKeypress({\n\tcombo,\n\tcallback,\n\tpreventDefault = true,\n\ttarget,\n}: UseKeypressOptions) {\n\tconst callbackRef = useRef(callback);\n\tuseEffect(() => {\n\t\tcallbackRef.current = callback;\n\t}, [callback]);\n\n\tconst normalized = useMemo(\n\t\t() => (Array.isArray(combo) ? combo : [combo]).map(normalize),\n\t\t[combo]\n\t);\n\n\tconst onKeyDown = useCallback(\n\t\t(ev: Event) => {\n\t\t\tconst e = ev as KeyboardEvent;\n\t\t\tconst el = e.target as HTMLElement | undefined;\n\t\t\tif (IGNORE_FOCUS.has(el?.tagName ?? \"\") || el?.isContentEditable) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tfor (const chord of normalized) {\n\t\t\t\tif (!matches(e, chord)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (preventDefault) {\n\t\t\t\t\te.preventDefault();\n\t\t\t\t}\n\t\t\t\tcallbackRef.current(e);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t},\n\t\t[normalized, preventDefault]\n\t);\n\n\tuseEffect(() => {\n\t\tconst el = target ?? window;\n\t\tel.addEventListener(\"keydown\", onKeyDown);\n\t\treturn () => el.removeEventListener(\"keydown\", onKeyDown);\n\t}, [onKeyDown, target]);\n}\n"}]}