@lacspace/hotkeys 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -214,8 +214,9 @@ function useHotkeys(keys, handler, options = {}, deps) {
214
214
  const progressRef = react.useRef(/* @__PURE__ */ new Map());
215
215
  const eventType = options.eventType ?? "keydown";
216
216
  const depList = deps ?? [];
217
+ const target = options.target;
217
218
  react.useEffect(() => {
218
- const el = resolveTarget(optionsRef.current.target);
219
+ const el = resolveTarget(target);
219
220
  if (!el) return;
220
221
  const listener = (rawEvent) => {
221
222
  const event = rawEvent;
@@ -275,7 +276,7 @@ function useHotkeys(keys, handler, options = {}, deps) {
275
276
  return () => {
276
277
  el.removeEventListener(eventType, listener);
277
278
  };
278
- }, [eventType, ...depList]);
279
+ }, [target, eventType, ...depList]);
279
280
  }
280
281
 
281
282
  exports.disableScope = disableScope;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"names":["useSyncExternalStore","useRef","useEffect"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * @lacspace/hotkeys — ergonomic keyboard shortcuts for React.\n *\n * Combos (`mod+k`), key sequences (`g then d`), scopes, and pretty display\n * formatting (`⌘K`). SSR-safe, respects form fields, zero-dependency, fully typed.\n *\n * @packageDocumentation\n */\n\nimport { useEffect, useRef, useSyncExternalStore } from \"react\";\nimport type { RefObject } from \"react\";\n\n/* -------------------------------------------------------------------------- */\n/* Types */\n/* -------------------------------------------------------------------------- */\n\n/** A parsed hotkey combo: the resolved key plus each modifier requirement. */\nexport interface ParsedHotkey {\n /** Normalized key (e.g. `\"k\"`, `\"escape\"`, `\" \"`, `\"arrowup\"`). */\n key: string;\n /** `mod` — Cmd on mac, Ctrl elsewhere. */\n mod: boolean;\n /** Control key. */\n ctrl: boolean;\n /** Alt / Option key. */\n alt: boolean;\n /** Shift key. */\n shift: boolean;\n /** Meta / Cmd / Win key. */\n meta: boolean;\n}\n\n/** The handler invoked when a hotkey (or the final step of a sequence) fires. */\nexport type HotkeyHandler = (event: KeyboardEvent, combo: string) => void;\n\n/** Where to attach the key listener. */\nexport type HotkeyTarget =\n | Window\n | HTMLElement\n | RefObject<HTMLElement | null>;\n\n/** Options for {@link useHotkeys}. */\nexport interface HotkeyOptions {\n /** Master switch. When `false`, nothing fires. @default true */\n enabled?: boolean;\n /** Call `event.preventDefault()` when a hotkey matches. @default true */\n preventDefault?: boolean;\n /**\n * Allow firing while an `input` / `textarea` / `select` / `contentEditable`\n * element is the event source. @default false\n */\n enableOnFormTags?: boolean;\n /** Which key event to listen for. @default \"keydown\" */\n eventType?: \"keydown\" | \"keyup\";\n /** Where to bind the listener. @default window */\n target?: HotkeyTarget;\n /**\n * Scope name(s). The hotkey only fires when at least one is active\n * (see {@link enableScope}). Omit to always fire.\n */\n scopes?: string | string[];\n}\n\n/* -------------------------------------------------------------------------- */\n/* Platform detection */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Detects whether the current platform is a Mac (or iOS device).\n *\n * SSR-safe: always returns `false` when there is no `navigator`.\n *\n * @returns `true` on macOS / iOS, `false` otherwise (and on the server).\n *\n * @example\n * ```ts\n * const symbol = isMac() ? \"⌘\" : \"Ctrl\";\n * ```\n */\nexport function isMac(): boolean {\n if (typeof navigator === \"undefined\") return false;\n const uaData = (navigator as Navigator & {\n userAgentData?: { platform?: string };\n }).userAgentData;\n if (uaData && typeof uaData.platform === \"string\" && uaData.platform) {\n return /mac/i.test(uaData.platform);\n }\n const platform = navigator.platform || \"\";\n if (platform) return /mac|iphone|ipad|ipod/i.test(platform);\n return /mac|iphone|ipad|ipod/i.test(navigator.userAgent || \"\");\n}\n\n/* -------------------------------------------------------------------------- */\n/* Parsing */\n/* -------------------------------------------------------------------------- */\n\nconst MOD_TOKENS: Record<string, keyof Omit<ParsedHotkey, \"key\">> = {\n mod: \"mod\",\n ctrl: \"ctrl\",\n control: \"ctrl\",\n alt: \"alt\",\n option: \"alt\",\n opt: \"alt\",\n shift: \"shift\",\n meta: \"meta\",\n cmd: \"meta\",\n command: \"meta\",\n win: \"meta\",\n super: \"meta\",\n};\n\nconst KEY_ALIASES: Record<string, string> = {\n esc: \"escape\",\n space: \" \",\n spacebar: \" \",\n up: \"arrowup\",\n down: \"arrowdown\",\n left: \"arrowleft\",\n right: \"arrowright\",\n return: \"enter\",\n del: \"delete\",\n ins: \"insert\",\n pgup: \"pageup\",\n pgdn: \"pagedown\",\n};\n\n/** Normalize a raw key token into its canonical `event.key` (lowercased) form. */\nfunction normalizeKey(raw: string): string {\n const k = raw.toLowerCase();\n const aliased = KEY_ALIASES[k];\n if (aliased !== undefined) return aliased;\n return k;\n}\n\n/**\n * Parses a combo string like `\"mod+shift+k\"` into modifier flags and a key.\n *\n * Tokens split on `\"+\"`, case-insensitive. Modifiers: `mod` (Cmd on mac / Ctrl\n * elsewhere), `ctrl`/`control`, `alt`/`option`, `shift`, `meta`/`cmd`/`command`/`win`.\n * The remaining token is the key (`esc`→`escape`, `space`→`\" \"`, arrows→`arrowup`…,\n * single letters lowercased).\n *\n * @param str - The combo string, e.g. `\"mod+k\"` or `\"ctrl+shift+escape\"`.\n * @returns The parsed combo.\n *\n * @example\n * ```ts\n * parseHotkey(\"mod+shift+k\");\n * // { key: \"k\", mod: true, ctrl: false, alt: false, shift: true, meta: false }\n * ```\n */\nexport function parseHotkey(str: string): ParsedHotkey {\n const parsed: ParsedHotkey = {\n key: \"\",\n mod: false,\n ctrl: false,\n alt: false,\n shift: false,\n meta: false,\n };\n const tokens = str.split(\"+\");\n for (const token of tokens) {\n const t = token.trim().toLowerCase();\n if (t === \"\") continue;\n const modKey = MOD_TOKENS[t];\n if (modKey) {\n parsed[modKey] = true;\n } else {\n parsed.key = normalizeKey(token.trim());\n }\n }\n return parsed;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Matching */\n/* -------------------------------------------------------------------------- */\n\n/** Match a keyboard event against an already-parsed combo. */\nfunction matchesParsed(\n event: KeyboardEvent,\n parsed: ParsedHotkey,\n mac: boolean,\n): boolean {\n const wantMeta = parsed.meta || (mac && parsed.mod);\n const wantCtrl = parsed.ctrl || (!mac && parsed.mod);\n if (event.metaKey !== wantMeta) return false;\n if (event.ctrlKey !== wantCtrl) return false;\n if (event.altKey !== parsed.alt) return false;\n if (event.shiftKey !== parsed.shift) return false;\n return event.key.toLowerCase() === parsed.key;\n}\n\n/**\n * Returns `true` when a keyboard event satisfies a combo string.\n *\n * `mod` resolves to `metaKey` on mac and `ctrlKey` elsewhere. Modifiers must\n * match exactly (so `\"ctrl+k\"` does not fire when `Ctrl+Shift+K` is pressed).\n *\n * @param event - The keyboard event.\n * @param combo - The combo string, e.g. `\"mod+k\"`.\n * @returns Whether the event satisfies the combo.\n *\n * @example\n * ```ts\n * window.addEventListener(\"keydown\", (e) => {\n * if (matchesHotkey(e, \"mod+k\")) openPalette();\n * });\n * ```\n */\nexport function matchesHotkey(event: KeyboardEvent, combo: string): boolean {\n return matchesParsed(event, parseHotkey(combo), isMac());\n}\n\n/* -------------------------------------------------------------------------- */\n/* Display formatting */\n/* -------------------------------------------------------------------------- */\n\nfunction formatKeyLabel(key: string, mac: boolean): string {\n if (!key) return \"\";\n switch (key) {\n case \" \":\n return \"Space\";\n case \"escape\":\n return \"Esc\";\n case \"enter\":\n return mac ? \"↵\" : \"Enter\";\n case \"arrowup\":\n return mac ? \"↑\" : \"Up\";\n case \"arrowdown\":\n return mac ? \"↓\" : \"Down\";\n case \"arrowleft\":\n return mac ? \"←\" : \"Left\";\n case \"arrowright\":\n return mac ? \"→\" : \"Right\";\n case \"backspace\":\n return mac ? \"⌫\" : \"Backspace\";\n case \"delete\":\n return mac ? \"⌦\" : \"Del\";\n case \"tab\":\n return mac ? \"⇥\" : \"Tab\";\n default:\n break;\n }\n if (key.length === 1) return key.toUpperCase();\n return key.charAt(0).toUpperCase() + key.slice(1);\n}\n\n/**\n * Formats a combo for display, e.g. mac → `\"⌘⇧K\"`, non-mac → `\"Ctrl+Shift+K\"`.\n *\n * Auto-detects the platform when `opts.mac` is omitted.\n *\n * @param combo - The combo string, e.g. `\"mod+shift+k\"`.\n * @param opts - Optional overrides.\n * @param opts.mac - Force mac (`true`) or non-mac (`false`) rendering.\n * @returns A human-friendly label.\n *\n * @example\n * ```tsx\n * <kbd>{formatHotkey(\"mod+k\")}</kbd> // \"⌘K\" on mac, \"Ctrl+K\" elsewhere\n * formatHotkey(\"ctrl+shift+k\", { mac: false }); // \"Ctrl+Shift+K\"\n * ```\n */\nexport function formatHotkey(combo: string, opts: { mac?: boolean } = {}): string {\n const mac = opts.mac ?? isMac();\n const p = parseHotkey(combo);\n const parts: string[] = [];\n if (mac) {\n if (p.meta || p.mod) parts.push(\"⌘\");\n if (p.ctrl) parts.push(\"⌃\");\n if (p.alt) parts.push(\"⌥\");\n if (p.shift) parts.push(\"⇧\");\n parts.push(formatKeyLabel(p.key, true));\n return parts.join(\"\");\n }\n if (p.ctrl || p.mod) parts.push(\"Ctrl\");\n if (p.alt) parts.push(\"Alt\");\n if (p.shift) parts.push(\"Shift\");\n if (p.meta) parts.push(\"Win\");\n parts.push(formatKeyLabel(p.key, false));\n return parts.join(\"+\");\n}\n\n/* -------------------------------------------------------------------------- */\n/* Scopes (module-level, provider-free) */\n/* -------------------------------------------------------------------------- */\n\nconst activeScopeSet = new Set<string>();\nconst scopeListeners = new Set<() => void>();\nconst EMPTY_SCOPES: readonly string[] = Object.freeze([]);\nlet scopesSnapshot: string[] = [];\n\nfunction refreshScopesSnapshot(): void {\n scopesSnapshot = Array.from(activeScopeSet);\n}\n\nfunction emitScopeChange(): void {\n for (const listener of scopeListeners) listener();\n}\n\n/**\n * Activates a scope. Hotkeys bound to this scope will start firing.\n *\n * @param name - The scope name.\n *\n * @example\n * ```ts\n * enableScope(\"editor\"); // now editor hotkeys are live\n * ```\n */\nexport function enableScope(name: string): void {\n if (!activeScopeSet.has(name)) {\n activeScopeSet.add(name);\n refreshScopesSnapshot();\n emitScopeChange();\n }\n}\n\n/**\n * Deactivates a scope. Hotkeys bound only to this scope stop firing.\n *\n * @param name - The scope name.\n */\nexport function disableScope(name: string): void {\n if (activeScopeSet.delete(name)) {\n refreshScopesSnapshot();\n emitScopeChange();\n }\n}\n\n/**\n * Toggles a scope on or off.\n *\n * @param name - The scope name.\n */\nexport function toggleScope(name: string): void {\n if (activeScopeSet.has(name)) disableScope(name);\n else enableScope(name);\n}\n\n/** Returns whether a scope is currently active. */\nexport function isScopeActive(name: string): boolean {\n return activeScopeSet.has(name);\n}\n\nfunction subscribeScopes(callback: () => void): () => void {\n scopeListeners.add(callback);\n return () => {\n scopeListeners.delete(callback);\n };\n}\n\nfunction getScopesSnapshot(): string[] {\n return scopesSnapshot;\n}\n\nfunction getServerScopesSnapshot(): readonly string[] {\n return EMPTY_SCOPES;\n}\n\n/**\n * Subscribes to the set of active scopes and exposes controls.\n *\n * The component re-renders whenever scopes change (backed by\n * `useSyncExternalStore`, so it is concurrent-safe and SSR-safe).\n *\n * @returns `{ activeScopes, enableScope, disableScope, toggleScope }`.\n *\n * @example\n * ```tsx\n * function ScopeBadge() {\n * const { activeScopes, toggleScope } = useHotkeysScopes();\n * return (\n * <button onClick={() => toggleScope(\"editor\")}>\n * {activeScopes.includes(\"editor\") ? \"Editor on\" : \"Editor off\"}\n * </button>\n * );\n * }\n * ```\n */\nexport function useHotkeysScopes(): {\n activeScopes: readonly string[];\n enableScope: (name: string) => void;\n disableScope: (name: string) => void;\n toggleScope: (name: string) => void;\n} {\n const active = useSyncExternalStore(\n subscribeScopes,\n getScopesSnapshot,\n getServerScopesSnapshot,\n );\n return { activeScopes: active, enableScope, disableScope, toggleScope };\n}\n\n/* -------------------------------------------------------------------------- */\n/* useHotkeys */\n/* -------------------------------------------------------------------------- */\n\nconst SEQUENCE_TIMEOUT = 1000;\n\ninterface HotkeyEntry {\n raw: string;\n steps: ParsedHotkey[];\n isSequence: boolean;\n}\n\ninterface SequenceProgress {\n index: number;\n time: number;\n}\n\nfunction isModifierKey(key: string): boolean {\n return (\n key === \"Control\" ||\n key === \"Shift\" ||\n key === \"Alt\" ||\n key === \"Meta\" ||\n key === \"OS\" ||\n key === \"AltGraph\"\n );\n}\n\nfunction isFromFormField(target: EventTarget | null): boolean {\n if (typeof HTMLElement === \"undefined\") return false;\n if (!(target instanceof HTMLElement)) return false;\n const tag = target.tagName;\n if (tag === \"INPUT\" || tag === \"TEXTAREA\" || tag === \"SELECT\") return true;\n return target.isContentEditable;\n}\n\nfunction resolveTarget(target: HotkeyTarget | undefined): Window | HTMLElement | null {\n if (typeof window === \"undefined\") return null;\n if (!target) return window;\n if (target === window) return window;\n if (typeof HTMLElement !== \"undefined\" && target instanceof HTMLElement) {\n return target;\n }\n return (target as RefObject<HTMLElement | null>).current;\n}\n\nfunction toEntries(keys: string | string[]): HotkeyEntry[] {\n const list = Array.isArray(keys) ? keys : [keys];\n return list.map((raw) => {\n const steps = raw\n .replace(/\\s+then\\s+/gi, \" \")\n .trim()\n .split(/\\s+/)\n .filter(Boolean)\n .map(parseHotkey);\n return { raw, steps, isSequence: steps.length > 1 };\n });\n}\n\n/**\n * Binds keyboard shortcut(s) — combos and/or sequences — for the lifetime of a component.\n *\n * Supports:\n * - **Combos**: `\"mod+k\"`, `\"ctrl+shift+p\"`.\n * - **Multiple combos**: pass an array — any match fires the handler.\n * - **Sequences**: `\"g then d\"` or `\"g d\"` — press keys in order within ~1s.\n * - **Scopes**: only fire while a named scope is active (see {@link enableScope}).\n *\n * The handler is kept in a ref, so `deps` are optional — the latest closure is\n * always used without re-binding the listener.\n *\n * @param keys - A combo/sequence string, or an array of them.\n * @param handler - Called with the event and the matched combo string.\n * @param options - See {@link HotkeyOptions}.\n * @param deps - Optional dependency list (rarely needed thanks to the latest-ref).\n *\n * @example\n * ```tsx\n * // Open a command palette with ⌘K / Ctrl+K\n * useHotkeys(\"mod+k\", (e) => {\n * e.preventDefault();\n * setPaletteOpen(true);\n * });\n *\n * // Navigate with a sequence: press \"g\" then \"d\"\n * useHotkeys(\"g then d\", () => router.push(\"/dashboard\"));\n *\n * // Scoped: only while the \"editor\" scope is active\n * useHotkeys(\"mod+b\", toggleBold, { scopes: \"editor\" });\n * ```\n */\nexport function useHotkeys(\n keys: string | string[],\n handler: HotkeyHandler,\n options: HotkeyOptions = {},\n deps?: unknown[],\n): void {\n const handlerRef = useRef<HotkeyHandler>(handler);\n handlerRef.current = handler;\n\n const optionsRef = useRef<HotkeyOptions>(options);\n optionsRef.current = options;\n\n const entriesRef = useRef<HotkeyEntry[]>([]);\n entriesRef.current = toEntries(keys);\n\n const progressRef = useRef<Map<string, SequenceProgress>>(new Map());\n\n const eventType = options.eventType ?? \"keydown\";\n const depList = deps ?? [];\n\n useEffect(() => {\n const el = resolveTarget(optionsRef.current.target);\n if (!el) return;\n\n const listener = (rawEvent: Event) => {\n const event = rawEvent as KeyboardEvent;\n const o = optionsRef.current;\n\n if (o.enabled === false) return;\n\n const enableOnFormTags = o.enableOnFormTags ?? false;\n if (!enableOnFormTags && isFromFormField(event.target)) return;\n\n const rawScopes = o.scopes;\n const scopeArr =\n rawScopes == null ? [] : Array.isArray(rawScopes) ? rawScopes : [rawScopes];\n if (scopeArr.length > 0 && !scopeArr.some((s) => activeScopeSet.has(s))) {\n return;\n }\n\n const mac = isMac();\n const preventDefault = o.preventDefault ?? true;\n const lone = isModifierKey(event.key);\n const progress = progressRef.current;\n\n for (const entry of entriesRef.current) {\n if (entry.isSequence) {\n // Ignore lone modifier presses so they neither advance nor reset.\n if (lone) continue;\n\n let state = progress.get(entry.raw);\n if (!state) {\n state = { index: 0, time: 0 };\n progress.set(entry.raw, state);\n }\n\n const now = Date.now();\n if (state.index > 0 && now - state.time > SEQUENCE_TIMEOUT) {\n state.index = 0;\n }\n\n const expected = entry.steps[state.index];\n if (expected && matchesParsed(event, expected, mac)) {\n state.index += 1;\n state.time = now;\n if (state.index >= entry.steps.length) {\n state.index = 0;\n if (preventDefault) event.preventDefault();\n handlerRef.current(event, entry.raw);\n }\n } else {\n const first = entry.steps[0];\n if (first && matchesParsed(event, first, mac)) {\n state.index = 1;\n state.time = now;\n } else {\n state.index = 0;\n }\n }\n } else {\n const combo = entry.steps[0];\n if (combo && matchesParsed(event, combo, mac)) {\n if (preventDefault) event.preventDefault();\n handlerRef.current(event, entry.raw);\n }\n }\n }\n };\n\n el.addEventListener(eventType, listener as EventListener);\n return () => {\n el.removeEventListener(eventType, listener as EventListener);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [eventType, ...depList]);\n}\n"]}
1
+ {"version":3,"sources":["../src/index.ts"],"names":["useSyncExternalStore","useRef","useEffect"],"mappings":";;;;;AA+EO,SAAS,KAAA,GAAiB;AAC/B,EAAA,IAAI,OAAO,SAAA,KAAc,WAAA,EAAa,OAAO,KAAA;AAC7C,EAAA,MAAM,SAAU,SAAA,CAEb,aAAA;AACH,EAAA,IAAI,UAAU,OAAO,MAAA,CAAO,QAAA,KAAa,QAAA,IAAY,OAAO,QAAA,EAAU;AACpE,IAAA,OAAO,MAAA,CAAO,IAAA,CAAK,MAAA,CAAO,QAAQ,CAAA;AAAA,EACpC;AACA,EAAA,MAAM,QAAA,GAAW,UAAU,QAAA,IAAY,EAAA;AACvC,EAAA,IAAI,QAAA,EAAU,OAAO,uBAAA,CAAwB,IAAA,CAAK,QAAQ,CAAA;AAC1D,EAAA,OAAO,uBAAA,CAAwB,IAAA,CAAK,SAAA,CAAU,SAAA,IAAa,EAAE,CAAA;AAC/D;AAMA,IAAM,UAAA,GAA8D;AAAA,EAClE,GAAA,EAAK,KAAA;AAAA,EACL,IAAA,EAAM,MAAA;AAAA,EACN,OAAA,EAAS,MAAA;AAAA,EACT,GAAA,EAAK,KAAA;AAAA,EACL,MAAA,EAAQ,KAAA;AAAA,EACR,GAAA,EAAK,KAAA;AAAA,EACL,KAAA,EAAO,OAAA;AAAA,EACP,IAAA,EAAM,MAAA;AAAA,EACN,GAAA,EAAK,MAAA;AAAA,EACL,OAAA,EAAS,MAAA;AAAA,EACT,GAAA,EAAK,MAAA;AAAA,EACL,KAAA,EAAO;AACT,CAAA;AAEA,IAAM,WAAA,GAAsC;AAAA,EAC1C,GAAA,EAAK,QAAA;AAAA,EACL,KAAA,EAAO,GAAA;AAAA,EACP,QAAA,EAAU,GAAA;AAAA,EACV,EAAA,EAAI,SAAA;AAAA,EACJ,IAAA,EAAM,WAAA;AAAA,EACN,IAAA,EAAM,WAAA;AAAA,EACN,KAAA,EAAO,YAAA;AAAA,EACP,MAAA,EAAQ,OAAA;AAAA,EACR,GAAA,EAAK,QAAA;AAAA,EACL,GAAA,EAAK,QAAA;AAAA,EACL,IAAA,EAAM,QAAA;AAAA,EACN,IAAA,EAAM;AACR,CAAA;AAGA,SAAS,aAAa,GAAA,EAAqB;AACzC,EAAA,MAAM,CAAA,GAAI,IAAI,WAAA,EAAY;AAC1B,EAAA,MAAM,OAAA,GAAU,YAAY,CAAC,CAAA;AAC7B,EAAA,IAAI,OAAA,KAAY,QAAW,OAAO,OAAA;AAClC,EAAA,OAAO,CAAA;AACT;AAmBO,SAAS,YAAY,GAAA,EAA2B;AACrD,EAAA,MAAM,MAAA,GAAuB;AAAA,IAC3B,GAAA,EAAK,EAAA;AAAA,IACL,GAAA,EAAK,KAAA;AAAA,IACL,IAAA,EAAM,KAAA;AAAA,IACN,GAAA,EAAK,KAAA;AAAA,IACL,KAAA,EAAO,KAAA;AAAA,IACP,IAAA,EAAM;AAAA,GACR;AACA,EAAA,MAAM,MAAA,GAAS,GAAA,CAAI,KAAA,CAAM,GAAG,CAAA;AAC5B,EAAA,KAAA,MAAW,SAAS,MAAA,EAAQ;AAC1B,IAAA,MAAM,CAAA,GAAI,KAAA,CAAM,IAAA,EAAK,CAAE,WAAA,EAAY;AACnC,IAAA,IAAI,MAAM,EAAA,EAAI;AACd,IAAA,MAAM,MAAA,GAAS,WAAW,CAAC,CAAA;AAC3B,IAAA,IAAI,MAAA,EAAQ;AACV,MAAA,MAAA,CAAO,MAAM,CAAA,GAAI,IAAA;AAAA,IACnB,CAAA,MAAO;AACL,MAAA,MAAA,CAAO,GAAA,GAAM,YAAA,CAAa,KAAA,CAAM,IAAA,EAAM,CAAA;AAAA,IACxC;AAAA,EACF;AACA,EAAA,OAAO,MAAA;AACT;AAOA,SAAS,aAAA,CACP,KAAA,EACA,MAAA,EACA,GAAA,EACS;AACT,EAAA,MAAM,QAAA,GAAW,MAAA,CAAO,IAAA,IAAS,GAAA,IAAO,MAAA,CAAO,GAAA;AAC/C,EAAA,MAAM,QAAA,GAAW,MAAA,CAAO,IAAA,IAAS,CAAC,OAAO,MAAA,CAAO,GAAA;AAChD,EAAA,IAAI,KAAA,CAAM,OAAA,KAAY,QAAA,EAAU,OAAO,KAAA;AACvC,EAAA,IAAI,KAAA,CAAM,OAAA,KAAY,QAAA,EAAU,OAAO,KAAA;AACvC,EAAA,IAAI,KAAA,CAAM,MAAA,KAAW,MAAA,CAAO,GAAA,EAAK,OAAO,KAAA;AACxC,EAAA,IAAI,KAAA,CAAM,QAAA,KAAa,MAAA,CAAO,KAAA,EAAO,OAAO,KAAA;AAC5C,EAAA,OAAO,KAAA,CAAM,GAAA,CAAI,WAAA,EAAY,KAAM,MAAA,CAAO,GAAA;AAC5C;AAmBO,SAAS,aAAA,CAAc,OAAsB,KAAA,EAAwB;AAC1E,EAAA,OAAO,cAAc,KAAA,EAAO,WAAA,CAAY,KAAK,CAAA,EAAG,OAAO,CAAA;AACzD;AAMA,SAAS,cAAA,CAAe,KAAa,GAAA,EAAsB;AACzD,EAAA,IAAI,CAAC,KAAK,OAAO,EAAA;AACjB,EAAA,QAAQ,GAAA;AAAK,IACX,KAAK,GAAA;AACH,MAAA,OAAO,OAAA;AAAA,IACT,KAAK,QAAA;AACH,MAAA,OAAO,KAAA;AAAA,IACT,KAAK,OAAA;AACH,MAAA,OAAO,MAAM,QAAA,GAAM,OAAA;AAAA,IACrB,KAAK,SAAA;AACH,MAAA,OAAO,MAAM,QAAA,GAAM,IAAA;AAAA,IACrB,KAAK,WAAA;AACH,MAAA,OAAO,MAAM,QAAA,GAAM,MAAA;AAAA,IACrB,KAAK,WAAA;AACH,MAAA,OAAO,MAAM,QAAA,GAAM,MAAA;AAAA,IACrB,KAAK,YAAA;AACH,MAAA,OAAO,MAAM,QAAA,GAAM,OAAA;AAAA,IACrB,KAAK,WAAA;AACH,MAAA,OAAO,MAAM,QAAA,GAAM,WAAA;AAAA,IACrB,KAAK,QAAA;AACH,MAAA,OAAO,MAAM,QAAA,GAAM,KAAA;AAAA,IACrB,KAAK,KAAA;AACH,MAAA,OAAO,MAAM,QAAA,GAAM,KAAA;AAEnB;AAEJ,EAAA,IAAI,GAAA,CAAI,MAAA,KAAW,CAAA,EAAG,OAAO,IAAI,WAAA,EAAY;AAC7C,EAAA,OAAO,GAAA,CAAI,OAAO,CAAC,CAAA,CAAE,aAAY,GAAI,GAAA,CAAI,MAAM,CAAC,CAAA;AAClD;AAkBO,SAAS,YAAA,CAAa,KAAA,EAAe,IAAA,GAA0B,EAAC,EAAW;AAChF,EAAA,MAAM,GAAA,GAAM,IAAA,CAAK,GAAA,IAAO,KAAA,EAAM;AAC9B,EAAA,MAAM,CAAA,GAAI,YAAY,KAAK,CAAA;AAC3B,EAAA,MAAM,QAAkB,EAAC;AACzB,EAAA,IAAI,GAAA,EAAK;AACP,IAAA,IAAI,EAAE,IAAA,IAAQ,CAAA,CAAE,GAAA,EAAK,KAAA,CAAM,KAAK,QAAG,CAAA;AACnC,IAAA,IAAI,CAAA,CAAE,IAAA,EAAM,KAAA,CAAM,IAAA,CAAK,QAAG,CAAA;AAC1B,IAAA,IAAI,CAAA,CAAE,GAAA,EAAK,KAAA,CAAM,IAAA,CAAK,QAAG,CAAA;AACzB,IAAA,IAAI,CAAA,CAAE,KAAA,EAAO,KAAA,CAAM,IAAA,CAAK,QAAG,CAAA;AAC3B,IAAA,KAAA,CAAM,IAAA,CAAK,cAAA,CAAe,CAAA,CAAE,GAAA,EAAK,IAAI,CAAC,CAAA;AACtC,IAAA,OAAO,KAAA,CAAM,KAAK,EAAE,CAAA;AAAA,EACtB;AACA,EAAA,IAAI,EAAE,IAAA,IAAQ,CAAA,CAAE,GAAA,EAAK,KAAA,CAAM,KAAK,MAAM,CAAA;AACtC,EAAA,IAAI,CAAA,CAAE,GAAA,EAAK,KAAA,CAAM,IAAA,CAAK,KAAK,CAAA;AAC3B,EAAA,IAAI,CAAA,CAAE,KAAA,EAAO,KAAA,CAAM,IAAA,CAAK,OAAO,CAAA;AAC/B,EAAA,IAAI,CAAA,CAAE,IAAA,EAAM,KAAA,CAAM,IAAA,CAAK,KAAK,CAAA;AAC5B,EAAA,KAAA,CAAM,IAAA,CAAK,cAAA,CAAe,CAAA,CAAE,GAAA,EAAK,KAAK,CAAC,CAAA;AACvC,EAAA,OAAO,KAAA,CAAM,KAAK,GAAG,CAAA;AACvB;AAMA,IAAM,cAAA,uBAAqB,GAAA,EAAY;AACvC,IAAM,cAAA,uBAAqB,GAAA,EAAgB;AAC3C,IAAM,YAAA,GAAkC,MAAA,CAAO,MAAA,CAAO,EAAE,CAAA;AACxD,IAAI,iBAA2B,EAAC;AAEhC,SAAS,qBAAA,GAA8B;AACrC,EAAA,cAAA,GAAiB,KAAA,CAAM,KAAK,cAAc,CAAA;AAC5C;AAEA,SAAS,eAAA,GAAwB;AAC/B,EAAA,KAAA,MAAW,QAAA,IAAY,gBAAgB,QAAA,EAAS;AAClD;AAYO,SAAS,YAAY,IAAA,EAAoB;AAC9C,EAAA,IAAI,CAAC,cAAA,CAAe,GAAA,CAAI,IAAI,CAAA,EAAG;AAC7B,IAAA,cAAA,CAAe,IAAI,IAAI,CAAA;AACvB,IAAA,qBAAA,EAAsB;AACtB,IAAA,eAAA,EAAgB;AAAA,EAClB;AACF;AAOO,SAAS,aAAa,IAAA,EAAoB;AAC/C,EAAA,IAAI,cAAA,CAAe,MAAA,CAAO,IAAI,CAAA,EAAG;AAC/B,IAAA,qBAAA,EAAsB;AACtB,IAAA,eAAA,EAAgB;AAAA,EAClB;AACF;AAOO,SAAS,YAAY,IAAA,EAAoB;AAC9C,EAAA,IAAI,cAAA,CAAe,GAAA,CAAI,IAAI,CAAA,eAAgB,IAAI,CAAA;AAAA,mBAC9B,IAAI,CAAA;AACvB;AAGO,SAAS,cAAc,IAAA,EAAuB;AACnD,EAAA,OAAO,cAAA,CAAe,IAAI,IAAI,CAAA;AAChC;AAEA,SAAS,gBAAgB,QAAA,EAAkC;AACzD,EAAA,cAAA,CAAe,IAAI,QAAQ,CAAA;AAC3B,EAAA,OAAO,MAAM;AACX,IAAA,cAAA,CAAe,OAAO,QAAQ,CAAA;AAAA,EAChC,CAAA;AACF;AAEA,SAAS,iBAAA,GAA8B;AACrC,EAAA,OAAO,cAAA;AACT;AAEA,SAAS,uBAAA,GAA6C;AACpD,EAAA,OAAO,YAAA;AACT;AAsBO,SAAS,gBAAA,GAKd;AACA,EAAA,MAAM,MAAA,GAASA,0BAAA;AAAA,IACb,eAAA;AAAA,IACA,iBAAA;AAAA,IACA;AAAA,GACF;AACA,EAAA,OAAO,EAAE,YAAA,EAAc,MAAA,EAAQ,WAAA,EAAa,cAAc,WAAA,EAAY;AACxE;AAMA,IAAM,gBAAA,GAAmB,GAAA;AAazB,SAAS,cAAc,GAAA,EAAsB;AAC3C,EAAA,OACE,GAAA,KAAQ,SAAA,IACR,GAAA,KAAQ,OAAA,IACR,GAAA,KAAQ,SACR,GAAA,KAAQ,MAAA,IACR,GAAA,KAAQ,IAAA,IACR,GAAA,KAAQ,UAAA;AAEZ;AAEA,SAAS,gBAAgB,MAAA,EAAqC;AAC5D,EAAA,IAAI,OAAO,WAAA,KAAgB,WAAA,EAAa,OAAO,KAAA;AAC/C,EAAA,IAAI,EAAE,MAAA,YAAkB,WAAA,CAAA,EAAc,OAAO,KAAA;AAC7C,EAAA,MAAM,MAAM,MAAA,CAAO,OAAA;AACnB,EAAA,IAAI,QAAQ,OAAA,IAAW,GAAA,KAAQ,UAAA,IAAc,GAAA,KAAQ,UAAU,OAAO,IAAA;AACtE,EAAA,OAAO,MAAA,CAAO,iBAAA;AAChB;AAEA,SAAS,cAAc,MAAA,EAA+D;AACpF,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,EAAa,OAAO,IAAA;AAC1C,EAAA,IAAI,CAAC,QAAQ,OAAO,MAAA;AACpB,EAAA,IAAI,MAAA,KAAW,QAAQ,OAAO,MAAA;AAC9B,EAAA,IAAI,OAAO,WAAA,KAAgB,WAAA,IAAe,MAAA,YAAkB,WAAA,EAAa;AACvE,IAAA,OAAO,MAAA;AAAA,EACT;AACA,EAAA,OAAQ,MAAA,CAAyC,OAAA;AACnD;AAEA,SAAS,UAAU,IAAA,EAAwC;AACzD,EAAA,MAAM,OAAO,KAAA,CAAM,OAAA,CAAQ,IAAI,CAAA,GAAI,IAAA,GAAO,CAAC,IAAI,CAAA;AAC/C,EAAA,OAAO,IAAA,CAAK,GAAA,CAAI,CAAC,GAAA,KAAQ;AACvB,IAAA,MAAM,KAAA,GAAQ,GAAA,CACX,OAAA,CAAQ,cAAA,EAAgB,GAAG,CAAA,CAC3B,IAAA,EAAK,CACL,KAAA,CAAM,KAAK,CAAA,CACX,MAAA,CAAO,OAAO,CAAA,CACd,IAAI,WAAW,CAAA;AAClB,IAAA,OAAO,EAAE,GAAA,EAAK,KAAA,EAAO,UAAA,EAAY,KAAA,CAAM,SAAS,CAAA,EAAE;AAAA,EACpD,CAAC,CAAA;AACH;AAkCO,SAAS,WACd,IAAA,EACA,OAAA,EACA,OAAA,GAAyB,IACzB,IAAA,EACM;AACN,EAAA,MAAM,UAAA,GAAaC,aAAsB,OAAO,CAAA;AAChD,EAAA,UAAA,CAAW,OAAA,GAAU,OAAA;AAErB,EAAA,MAAM,UAAA,GAAaA,aAAsB,OAAO,CAAA;AAChD,EAAA,UAAA,CAAW,OAAA,GAAU,OAAA;AAErB,EAAA,MAAM,UAAA,GAAaA,YAAA,CAAsB,EAAE,CAAA;AAC3C,EAAA,UAAA,CAAW,OAAA,GAAU,UAAU,IAAI,CAAA;AAEnC,EAAA,MAAM,WAAA,GAAcA,YAAA,iBAAsC,IAAI,GAAA,EAAK,CAAA;AAEnE,EAAA,MAAM,SAAA,GAAY,QAAQ,SAAA,IAAa,SAAA;AACvC,EAAA,MAAM,OAAA,GAAU,QAAQ,EAAC;AACzB,EAAA,MAAM,SAAS,OAAA,CAAQ,MAAA;AAEvB,EAAAC,eAAA,CAAU,MAAM;AAKd,IAAA,MAAM,EAAA,GAAK,cAAc,MAAM,CAAA;AAC/B,IAAA,IAAI,CAAC,EAAA,EAAI;AAET,IAAA,MAAM,QAAA,GAAW,CAAC,QAAA,KAAoB;AACpC,MAAA,MAAM,KAAA,GAAQ,QAAA;AACd,MAAA,MAAM,IAAI,UAAA,CAAW,OAAA;AAErB,MAAA,IAAI,CAAA,CAAE,YAAY,KAAA,EAAO;AAEzB,MAAA,MAAM,gBAAA,GAAmB,EAAE,gBAAA,IAAoB,KAAA;AAC/C,MAAA,IAAI,CAAC,gBAAA,IAAoB,eAAA,CAAgB,KAAA,CAAM,MAAM,CAAA,EAAG;AAExD,MAAA,MAAM,YAAY,CAAA,CAAE,MAAA;AACpB,MAAA,MAAM,QAAA,GACJ,SAAA,IAAa,IAAA,GAAO,EAAC,GAAI,KAAA,CAAM,OAAA,CAAQ,SAAS,CAAA,GAAI,SAAA,GAAY,CAAC,SAAS,CAAA;AAC5E,MAAA,IAAI,QAAA,CAAS,MAAA,GAAS,CAAA,IAAK,CAAC,QAAA,CAAS,IAAA,CAAK,CAAC,CAAA,KAAM,cAAA,CAAe,GAAA,CAAI,CAAC,CAAC,CAAA,EAAG;AACvE,QAAA;AAAA,MACF;AAEA,MAAA,MAAM,MAAM,KAAA,EAAM;AAClB,MAAA,MAAM,cAAA,GAAiB,EAAE,cAAA,IAAkB,IAAA;AAC3C,MAAA,MAAM,IAAA,GAAO,aAAA,CAAc,KAAA,CAAM,GAAG,CAAA;AACpC,MAAA,MAAM,WAAW,WAAA,CAAY,OAAA;AAE7B,MAAA,KAAA,MAAW,KAAA,IAAS,WAAW,OAAA,EAAS;AACtC,QAAA,IAAI,MAAM,UAAA,EAAY;AAEpB,UAAA,IAAI,IAAA,EAAM;AAEV,UAAA,IAAI,KAAA,GAAQ,QAAA,CAAS,GAAA,CAAI,KAAA,CAAM,GAAG,CAAA;AAClC,UAAA,IAAI,CAAC,KAAA,EAAO;AACV,YAAA,KAAA,GAAQ,EAAE,KAAA,EAAO,CAAA,EAAG,IAAA,EAAM,CAAA,EAAE;AAC5B,YAAA,QAAA,CAAS,GAAA,CAAI,KAAA,CAAM,GAAA,EAAK,KAAK,CAAA;AAAA,UAC/B;AAEA,UAAA,MAAM,GAAA,GAAM,KAAK,GAAA,EAAI;AACrB,UAAA,IAAI,MAAM,KAAA,GAAQ,CAAA,IAAK,GAAA,GAAM,KAAA,CAAM,OAAO,gBAAA,EAAkB;AAC1D,YAAA,KAAA,CAAM,KAAA,GAAQ,CAAA;AAAA,UAChB;AAEA,UAAA,MAAM,QAAA,GAAW,KAAA,CAAM,KAAA,CAAM,KAAA,CAAM,KAAK,CAAA;AACxC,UAAA,IAAI,QAAA,IAAY,aAAA,CAAc,KAAA,EAAO,QAAA,EAAU,GAAG,CAAA,EAAG;AACnD,YAAA,KAAA,CAAM,KAAA,IAAS,CAAA;AACf,YAAA,KAAA,CAAM,IAAA,GAAO,GAAA;AACb,YAAA,IAAI,KAAA,CAAM,KAAA,IAAS,KAAA,CAAM,KAAA,CAAM,MAAA,EAAQ;AACrC,cAAA,KAAA,CAAM,KAAA,GAAQ,CAAA;AACd,cAAA,IAAI,cAAA,QAAsB,cAAA,EAAe;AACzC,cAAA,UAAA,CAAW,OAAA,CAAQ,KAAA,EAAO,KAAA,CAAM,GAAG,CAAA;AAAA,YACrC;AAAA,UACF,CAAA,MAAO;AACL,YAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,KAAA,CAAM,CAAC,CAAA;AAC3B,YAAA,IAAI,KAAA,IAAS,aAAA,CAAc,KAAA,EAAO,KAAA,EAAO,GAAG,CAAA,EAAG;AAC7C,cAAA,KAAA,CAAM,KAAA,GAAQ,CAAA;AACd,cAAA,KAAA,CAAM,IAAA,GAAO,GAAA;AAAA,YACf,CAAA,MAAO;AACL,cAAA,KAAA,CAAM,KAAA,GAAQ,CAAA;AAAA,YAChB;AAAA,UACF;AAAA,QACF,CAAA,MAAO;AACL,UAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,KAAA,CAAM,CAAC,CAAA;AAC3B,UAAA,IAAI,KAAA,IAAS,aAAA,CAAc,KAAA,EAAO,KAAA,EAAO,GAAG,CAAA,EAAG;AAC7C,YAAA,IAAI,cAAA,QAAsB,cAAA,EAAe;AACzC,YAAA,UAAA,CAAW,OAAA,CAAQ,KAAA,EAAO,KAAA,CAAM,GAAG,CAAA;AAAA,UACrC;AAAA,QACF;AAAA,MACF;AAAA,IACF,CAAA;AAEA,IAAA,EAAA,CAAG,gBAAA,CAAiB,WAAW,QAAyB,CAAA;AACxD,IAAA,OAAO,MAAM;AACX,MAAA,EAAA,CAAG,mBAAA,CAAoB,WAAW,QAAyB,CAAA;AAAA,IAC7D,CAAA;AAAA,EAEF,GAAG,CAAC,MAAA,EAAQ,SAAA,EAAW,GAAG,OAAO,CAAC,CAAA;AACpC","file":"index.cjs","sourcesContent":["/**\n * @lacspace/hotkeys — ergonomic keyboard shortcuts for React.\n *\n * Combos (`mod+k`), key sequences (`g then d`), scopes, and pretty display\n * formatting (`⌘K`). SSR-safe, respects form fields, zero-dependency, fully typed.\n *\n * @packageDocumentation\n */\n\nimport { useEffect, useRef, useSyncExternalStore } from \"react\";\nimport type { RefObject } from \"react\";\n\n/* -------------------------------------------------------------------------- */\n/* Types */\n/* -------------------------------------------------------------------------- */\n\n/** A parsed hotkey combo: the resolved key plus each modifier requirement. */\nexport interface ParsedHotkey {\n /** Normalized key (e.g. `\"k\"`, `\"escape\"`, `\" \"`, `\"arrowup\"`). */\n key: string;\n /** `mod` — Cmd on mac, Ctrl elsewhere. */\n mod: boolean;\n /** Control key. */\n ctrl: boolean;\n /** Alt / Option key. */\n alt: boolean;\n /** Shift key. */\n shift: boolean;\n /** Meta / Cmd / Win key. */\n meta: boolean;\n}\n\n/** The handler invoked when a hotkey (or the final step of a sequence) fires. */\nexport type HotkeyHandler = (event: KeyboardEvent, combo: string) => void;\n\n/** Where to attach the key listener. */\nexport type HotkeyTarget =\n | Window\n | HTMLElement\n | RefObject<HTMLElement | null>;\n\n/** Options for {@link useHotkeys}. */\nexport interface HotkeyOptions {\n /** Master switch. When `false`, nothing fires. @default true */\n enabled?: boolean;\n /** Call `event.preventDefault()` when a hotkey matches. @default true */\n preventDefault?: boolean;\n /**\n * Allow firing while an `input` / `textarea` / `select` / `contentEditable`\n * element is the event source. @default false\n */\n enableOnFormTags?: boolean;\n /** Which key event to listen for. @default \"keydown\" */\n eventType?: \"keydown\" | \"keyup\";\n /** Where to bind the listener. @default window */\n target?: HotkeyTarget;\n /**\n * Scope name(s). The hotkey only fires when at least one is active\n * (see {@link enableScope}). Omit to always fire.\n */\n scopes?: string | string[];\n}\n\n/* -------------------------------------------------------------------------- */\n/* Platform detection */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Detects whether the current platform is a Mac (or iOS device).\n *\n * SSR-safe: always returns `false` when there is no `navigator`.\n *\n * @returns `true` on macOS / iOS, `false` otherwise (and on the server).\n *\n * @example\n * ```ts\n * const symbol = isMac() ? \"⌘\" : \"Ctrl\";\n * ```\n */\nexport function isMac(): boolean {\n if (typeof navigator === \"undefined\") return false;\n const uaData = (navigator as Navigator & {\n userAgentData?: { platform?: string };\n }).userAgentData;\n if (uaData && typeof uaData.platform === \"string\" && uaData.platform) {\n return /mac/i.test(uaData.platform);\n }\n const platform = navigator.platform || \"\";\n if (platform) return /mac|iphone|ipad|ipod/i.test(platform);\n return /mac|iphone|ipad|ipod/i.test(navigator.userAgent || \"\");\n}\n\n/* -------------------------------------------------------------------------- */\n/* Parsing */\n/* -------------------------------------------------------------------------- */\n\nconst MOD_TOKENS: Record<string, keyof Omit<ParsedHotkey, \"key\">> = {\n mod: \"mod\",\n ctrl: \"ctrl\",\n control: \"ctrl\",\n alt: \"alt\",\n option: \"alt\",\n opt: \"alt\",\n shift: \"shift\",\n meta: \"meta\",\n cmd: \"meta\",\n command: \"meta\",\n win: \"meta\",\n super: \"meta\",\n};\n\nconst KEY_ALIASES: Record<string, string> = {\n esc: \"escape\",\n space: \" \",\n spacebar: \" \",\n up: \"arrowup\",\n down: \"arrowdown\",\n left: \"arrowleft\",\n right: \"arrowright\",\n return: \"enter\",\n del: \"delete\",\n ins: \"insert\",\n pgup: \"pageup\",\n pgdn: \"pagedown\",\n};\n\n/** Normalize a raw key token into its canonical `event.key` (lowercased) form. */\nfunction normalizeKey(raw: string): string {\n const k = raw.toLowerCase();\n const aliased = KEY_ALIASES[k];\n if (aliased !== undefined) return aliased;\n return k;\n}\n\n/**\n * Parses a combo string like `\"mod+shift+k\"` into modifier flags and a key.\n *\n * Tokens split on `\"+\"`, case-insensitive. Modifiers: `mod` (Cmd on mac / Ctrl\n * elsewhere), `ctrl`/`control`, `alt`/`option`, `shift`, `meta`/`cmd`/`command`/`win`.\n * The remaining token is the key (`esc`→`escape`, `space`→`\" \"`, arrows→`arrowup`…,\n * single letters lowercased).\n *\n * @param str - The combo string, e.g. `\"mod+k\"` or `\"ctrl+shift+escape\"`.\n * @returns The parsed combo.\n *\n * @example\n * ```ts\n * parseHotkey(\"mod+shift+k\");\n * // { key: \"k\", mod: true, ctrl: false, alt: false, shift: true, meta: false }\n * ```\n */\nexport function parseHotkey(str: string): ParsedHotkey {\n const parsed: ParsedHotkey = {\n key: \"\",\n mod: false,\n ctrl: false,\n alt: false,\n shift: false,\n meta: false,\n };\n const tokens = str.split(\"+\");\n for (const token of tokens) {\n const t = token.trim().toLowerCase();\n if (t === \"\") continue;\n const modKey = MOD_TOKENS[t];\n if (modKey) {\n parsed[modKey] = true;\n } else {\n parsed.key = normalizeKey(token.trim());\n }\n }\n return parsed;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Matching */\n/* -------------------------------------------------------------------------- */\n\n/** Match a keyboard event against an already-parsed combo. */\nfunction matchesParsed(\n event: KeyboardEvent,\n parsed: ParsedHotkey,\n mac: boolean,\n): boolean {\n const wantMeta = parsed.meta || (mac && parsed.mod);\n const wantCtrl = parsed.ctrl || (!mac && parsed.mod);\n if (event.metaKey !== wantMeta) return false;\n if (event.ctrlKey !== wantCtrl) return false;\n if (event.altKey !== parsed.alt) return false;\n if (event.shiftKey !== parsed.shift) return false;\n return event.key.toLowerCase() === parsed.key;\n}\n\n/**\n * Returns `true` when a keyboard event satisfies a combo string.\n *\n * `mod` resolves to `metaKey` on mac and `ctrlKey` elsewhere. Modifiers must\n * match exactly (so `\"ctrl+k\"` does not fire when `Ctrl+Shift+K` is pressed).\n *\n * @param event - The keyboard event.\n * @param combo - The combo string, e.g. `\"mod+k\"`.\n * @returns Whether the event satisfies the combo.\n *\n * @example\n * ```ts\n * window.addEventListener(\"keydown\", (e) => {\n * if (matchesHotkey(e, \"mod+k\")) openPalette();\n * });\n * ```\n */\nexport function matchesHotkey(event: KeyboardEvent, combo: string): boolean {\n return matchesParsed(event, parseHotkey(combo), isMac());\n}\n\n/* -------------------------------------------------------------------------- */\n/* Display formatting */\n/* -------------------------------------------------------------------------- */\n\nfunction formatKeyLabel(key: string, mac: boolean): string {\n if (!key) return \"\";\n switch (key) {\n case \" \":\n return \"Space\";\n case \"escape\":\n return \"Esc\";\n case \"enter\":\n return mac ? \"↵\" : \"Enter\";\n case \"arrowup\":\n return mac ? \"↑\" : \"Up\";\n case \"arrowdown\":\n return mac ? \"↓\" : \"Down\";\n case \"arrowleft\":\n return mac ? \"←\" : \"Left\";\n case \"arrowright\":\n return mac ? \"→\" : \"Right\";\n case \"backspace\":\n return mac ? \"⌫\" : \"Backspace\";\n case \"delete\":\n return mac ? \"⌦\" : \"Del\";\n case \"tab\":\n return mac ? \"⇥\" : \"Tab\";\n default:\n break;\n }\n if (key.length === 1) return key.toUpperCase();\n return key.charAt(0).toUpperCase() + key.slice(1);\n}\n\n/**\n * Formats a combo for display, e.g. mac → `\"⌘⇧K\"`, non-mac → `\"Ctrl+Shift+K\"`.\n *\n * Auto-detects the platform when `opts.mac` is omitted.\n *\n * @param combo - The combo string, e.g. `\"mod+shift+k\"`.\n * @param opts - Optional overrides.\n * @param opts.mac - Force mac (`true`) or non-mac (`false`) rendering.\n * @returns A human-friendly label.\n *\n * @example\n * ```tsx\n * <kbd>{formatHotkey(\"mod+k\")}</kbd> // \"⌘K\" on mac, \"Ctrl+K\" elsewhere\n * formatHotkey(\"ctrl+shift+k\", { mac: false }); // \"Ctrl+Shift+K\"\n * ```\n */\nexport function formatHotkey(combo: string, opts: { mac?: boolean } = {}): string {\n const mac = opts.mac ?? isMac();\n const p = parseHotkey(combo);\n const parts: string[] = [];\n if (mac) {\n if (p.meta || p.mod) parts.push(\"⌘\");\n if (p.ctrl) parts.push(\"⌃\");\n if (p.alt) parts.push(\"⌥\");\n if (p.shift) parts.push(\"⇧\");\n parts.push(formatKeyLabel(p.key, true));\n return parts.join(\"\");\n }\n if (p.ctrl || p.mod) parts.push(\"Ctrl\");\n if (p.alt) parts.push(\"Alt\");\n if (p.shift) parts.push(\"Shift\");\n if (p.meta) parts.push(\"Win\");\n parts.push(formatKeyLabel(p.key, false));\n return parts.join(\"+\");\n}\n\n/* -------------------------------------------------------------------------- */\n/* Scopes (module-level, provider-free) */\n/* -------------------------------------------------------------------------- */\n\nconst activeScopeSet = new Set<string>();\nconst scopeListeners = new Set<() => void>();\nconst EMPTY_SCOPES: readonly string[] = Object.freeze([]);\nlet scopesSnapshot: string[] = [];\n\nfunction refreshScopesSnapshot(): void {\n scopesSnapshot = Array.from(activeScopeSet);\n}\n\nfunction emitScopeChange(): void {\n for (const listener of scopeListeners) listener();\n}\n\n/**\n * Activates a scope. Hotkeys bound to this scope will start firing.\n *\n * @param name - The scope name.\n *\n * @example\n * ```ts\n * enableScope(\"editor\"); // now editor hotkeys are live\n * ```\n */\nexport function enableScope(name: string): void {\n if (!activeScopeSet.has(name)) {\n activeScopeSet.add(name);\n refreshScopesSnapshot();\n emitScopeChange();\n }\n}\n\n/**\n * Deactivates a scope. Hotkeys bound only to this scope stop firing.\n *\n * @param name - The scope name.\n */\nexport function disableScope(name: string): void {\n if (activeScopeSet.delete(name)) {\n refreshScopesSnapshot();\n emitScopeChange();\n }\n}\n\n/**\n * Toggles a scope on or off.\n *\n * @param name - The scope name.\n */\nexport function toggleScope(name: string): void {\n if (activeScopeSet.has(name)) disableScope(name);\n else enableScope(name);\n}\n\n/** Returns whether a scope is currently active. */\nexport function isScopeActive(name: string): boolean {\n return activeScopeSet.has(name);\n}\n\nfunction subscribeScopes(callback: () => void): () => void {\n scopeListeners.add(callback);\n return () => {\n scopeListeners.delete(callback);\n };\n}\n\nfunction getScopesSnapshot(): string[] {\n return scopesSnapshot;\n}\n\nfunction getServerScopesSnapshot(): readonly string[] {\n return EMPTY_SCOPES;\n}\n\n/**\n * Subscribes to the set of active scopes and exposes controls.\n *\n * The component re-renders whenever scopes change (backed by\n * `useSyncExternalStore`, so it is concurrent-safe and SSR-safe).\n *\n * @returns `{ activeScopes, enableScope, disableScope, toggleScope }`.\n *\n * @example\n * ```tsx\n * function ScopeBadge() {\n * const { activeScopes, toggleScope } = useHotkeysScopes();\n * return (\n * <button onClick={() => toggleScope(\"editor\")}>\n * {activeScopes.includes(\"editor\") ? \"Editor on\" : \"Editor off\"}\n * </button>\n * );\n * }\n * ```\n */\nexport function useHotkeysScopes(): {\n activeScopes: readonly string[];\n enableScope: (name: string) => void;\n disableScope: (name: string) => void;\n toggleScope: (name: string) => void;\n} {\n const active = useSyncExternalStore(\n subscribeScopes,\n getScopesSnapshot,\n getServerScopesSnapshot,\n );\n return { activeScopes: active, enableScope, disableScope, toggleScope };\n}\n\n/* -------------------------------------------------------------------------- */\n/* useHotkeys */\n/* -------------------------------------------------------------------------- */\n\nconst SEQUENCE_TIMEOUT = 1000;\n\ninterface HotkeyEntry {\n raw: string;\n steps: ParsedHotkey[];\n isSequence: boolean;\n}\n\ninterface SequenceProgress {\n index: number;\n time: number;\n}\n\nfunction isModifierKey(key: string): boolean {\n return (\n key === \"Control\" ||\n key === \"Shift\" ||\n key === \"Alt\" ||\n key === \"Meta\" ||\n key === \"OS\" ||\n key === \"AltGraph\"\n );\n}\n\nfunction isFromFormField(target: EventTarget | null): boolean {\n if (typeof HTMLElement === \"undefined\") return false;\n if (!(target instanceof HTMLElement)) return false;\n const tag = target.tagName;\n if (tag === \"INPUT\" || tag === \"TEXTAREA\" || tag === \"SELECT\") return true;\n return target.isContentEditable;\n}\n\nfunction resolveTarget(target: HotkeyTarget | undefined): Window | HTMLElement | null {\n if (typeof window === \"undefined\") return null;\n if (!target) return window;\n if (target === window) return window;\n if (typeof HTMLElement !== \"undefined\" && target instanceof HTMLElement) {\n return target;\n }\n return (target as RefObject<HTMLElement | null>).current;\n}\n\nfunction toEntries(keys: string | string[]): HotkeyEntry[] {\n const list = Array.isArray(keys) ? keys : [keys];\n return list.map((raw) => {\n const steps = raw\n .replace(/\\s+then\\s+/gi, \" \")\n .trim()\n .split(/\\s+/)\n .filter(Boolean)\n .map(parseHotkey);\n return { raw, steps, isSequence: steps.length > 1 };\n });\n}\n\n/**\n * Binds keyboard shortcut(s) — combos and/or sequences — for the lifetime of a component.\n *\n * Supports:\n * - **Combos**: `\"mod+k\"`, `\"ctrl+shift+p\"`.\n * - **Multiple combos**: pass an array — any match fires the handler.\n * - **Sequences**: `\"g then d\"` or `\"g d\"` — press keys in order within ~1s.\n * - **Scopes**: only fire while a named scope is active (see {@link enableScope}).\n *\n * The handler is kept in a ref, so `deps` are optional — the latest closure is\n * always used without re-binding the listener.\n *\n * @param keys - A combo/sequence string, or an array of them.\n * @param handler - Called with the event and the matched combo string.\n * @param options - See {@link HotkeyOptions}.\n * @param deps - Optional dependency list (rarely needed thanks to the latest-ref).\n *\n * @example\n * ```tsx\n * // Open a command palette with ⌘K / Ctrl+K\n * useHotkeys(\"mod+k\", (e) => {\n * e.preventDefault();\n * setPaletteOpen(true);\n * });\n *\n * // Navigate with a sequence: press \"g\" then \"d\"\n * useHotkeys(\"g then d\", () => router.push(\"/dashboard\"));\n *\n * // Scoped: only while the \"editor\" scope is active\n * useHotkeys(\"mod+b\", toggleBold, { scopes: \"editor\" });\n * ```\n */\nexport function useHotkeys(\n keys: string | string[],\n handler: HotkeyHandler,\n options: HotkeyOptions = {},\n deps?: unknown[],\n): void {\n const handlerRef = useRef<HotkeyHandler>(handler);\n handlerRef.current = handler;\n\n const optionsRef = useRef<HotkeyOptions>(options);\n optionsRef.current = options;\n\n const entriesRef = useRef<HotkeyEntry[]>([]);\n entriesRef.current = toEntries(keys);\n\n const progressRef = useRef<Map<string, SequenceProgress>>(new Map());\n\n const eventType = options.eventType ?? \"keydown\";\n const depList = deps ?? [];\n const target = options.target;\n\n useEffect(() => {\n // Re-resolve on every target change so a late-mounting / swapped target\n // re-binds. `target` is in the deps below; resolving inside the effect also\n // keeps the common ref-to-element case working (ref.current is set by the\n // time the effect runs after commit).\n const el = resolveTarget(target);\n if (!el) return;\n\n const listener = (rawEvent: Event) => {\n const event = rawEvent as KeyboardEvent;\n const o = optionsRef.current;\n\n if (o.enabled === false) return;\n\n const enableOnFormTags = o.enableOnFormTags ?? false;\n if (!enableOnFormTags && isFromFormField(event.target)) return;\n\n const rawScopes = o.scopes;\n const scopeArr =\n rawScopes == null ? [] : Array.isArray(rawScopes) ? rawScopes : [rawScopes];\n if (scopeArr.length > 0 && !scopeArr.some((s) => activeScopeSet.has(s))) {\n return;\n }\n\n const mac = isMac();\n const preventDefault = o.preventDefault ?? true;\n const lone = isModifierKey(event.key);\n const progress = progressRef.current;\n\n for (const entry of entriesRef.current) {\n if (entry.isSequence) {\n // Ignore lone modifier presses so they neither advance nor reset.\n if (lone) continue;\n\n let state = progress.get(entry.raw);\n if (!state) {\n state = { index: 0, time: 0 };\n progress.set(entry.raw, state);\n }\n\n const now = Date.now();\n if (state.index > 0 && now - state.time > SEQUENCE_TIMEOUT) {\n state.index = 0;\n }\n\n const expected = entry.steps[state.index];\n if (expected && matchesParsed(event, expected, mac)) {\n state.index += 1;\n state.time = now;\n if (state.index >= entry.steps.length) {\n state.index = 0;\n if (preventDefault) event.preventDefault();\n handlerRef.current(event, entry.raw);\n }\n } else {\n const first = entry.steps[0];\n if (first && matchesParsed(event, first, mac)) {\n state.index = 1;\n state.time = now;\n } else {\n state.index = 0;\n }\n }\n } else {\n const combo = entry.steps[0];\n if (combo && matchesParsed(event, combo, mac)) {\n if (preventDefault) event.preventDefault();\n handlerRef.current(event, entry.raw);\n }\n }\n }\n };\n\n el.addEventListener(eventType, listener as EventListener);\n return () => {\n el.removeEventListener(eventType, listener as EventListener);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [target, eventType, ...depList]);\n}\n"]}
package/dist/index.js CHANGED
@@ -212,8 +212,9 @@ function useHotkeys(keys, handler, options = {}, deps) {
212
212
  const progressRef = useRef(/* @__PURE__ */ new Map());
213
213
  const eventType = options.eventType ?? "keydown";
214
214
  const depList = deps ?? [];
215
+ const target = options.target;
215
216
  useEffect(() => {
216
- const el = resolveTarget(optionsRef.current.target);
217
+ const el = resolveTarget(target);
217
218
  if (!el) return;
218
219
  const listener = (rawEvent) => {
219
220
  const event = rawEvent;
@@ -273,7 +274,7 @@ function useHotkeys(keys, handler, options = {}, deps) {
273
274
  return () => {
274
275
  el.removeEventListener(eventType, listener);
275
276
  };
276
- }, [eventType, ...depList]);
277
+ }, [target, eventType, ...depList]);
277
278
  }
278
279
 
279
280
  export { disableScope, enableScope, formatHotkey, isMac, isScopeActive, matchesHotkey, parseHotkey, toggleScope, useHotkeys, useHotkeysScopes };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @lacspace/hotkeys — ergonomic keyboard shortcuts for React.\n *\n * Combos (`mod+k`), key sequences (`g then d`), scopes, and pretty display\n * formatting (`⌘K`). SSR-safe, respects form fields, zero-dependency, fully typed.\n *\n * @packageDocumentation\n */\n\nimport { useEffect, useRef, useSyncExternalStore } from \"react\";\nimport type { RefObject } from \"react\";\n\n/* -------------------------------------------------------------------------- */\n/* Types */\n/* -------------------------------------------------------------------------- */\n\n/** A parsed hotkey combo: the resolved key plus each modifier requirement. */\nexport interface ParsedHotkey {\n /** Normalized key (e.g. `\"k\"`, `\"escape\"`, `\" \"`, `\"arrowup\"`). */\n key: string;\n /** `mod` — Cmd on mac, Ctrl elsewhere. */\n mod: boolean;\n /** Control key. */\n ctrl: boolean;\n /** Alt / Option key. */\n alt: boolean;\n /** Shift key. */\n shift: boolean;\n /** Meta / Cmd / Win key. */\n meta: boolean;\n}\n\n/** The handler invoked when a hotkey (or the final step of a sequence) fires. */\nexport type HotkeyHandler = (event: KeyboardEvent, combo: string) => void;\n\n/** Where to attach the key listener. */\nexport type HotkeyTarget =\n | Window\n | HTMLElement\n | RefObject<HTMLElement | null>;\n\n/** Options for {@link useHotkeys}. */\nexport interface HotkeyOptions {\n /** Master switch. When `false`, nothing fires. @default true */\n enabled?: boolean;\n /** Call `event.preventDefault()` when a hotkey matches. @default true */\n preventDefault?: boolean;\n /**\n * Allow firing while an `input` / `textarea` / `select` / `contentEditable`\n * element is the event source. @default false\n */\n enableOnFormTags?: boolean;\n /** Which key event to listen for. @default \"keydown\" */\n eventType?: \"keydown\" | \"keyup\";\n /** Where to bind the listener. @default window */\n target?: HotkeyTarget;\n /**\n * Scope name(s). The hotkey only fires when at least one is active\n * (see {@link enableScope}). Omit to always fire.\n */\n scopes?: string | string[];\n}\n\n/* -------------------------------------------------------------------------- */\n/* Platform detection */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Detects whether the current platform is a Mac (or iOS device).\n *\n * SSR-safe: always returns `false` when there is no `navigator`.\n *\n * @returns `true` on macOS / iOS, `false` otherwise (and on the server).\n *\n * @example\n * ```ts\n * const symbol = isMac() ? \"⌘\" : \"Ctrl\";\n * ```\n */\nexport function isMac(): boolean {\n if (typeof navigator === \"undefined\") return false;\n const uaData = (navigator as Navigator & {\n userAgentData?: { platform?: string };\n }).userAgentData;\n if (uaData && typeof uaData.platform === \"string\" && uaData.platform) {\n return /mac/i.test(uaData.platform);\n }\n const platform = navigator.platform || \"\";\n if (platform) return /mac|iphone|ipad|ipod/i.test(platform);\n return /mac|iphone|ipad|ipod/i.test(navigator.userAgent || \"\");\n}\n\n/* -------------------------------------------------------------------------- */\n/* Parsing */\n/* -------------------------------------------------------------------------- */\n\nconst MOD_TOKENS: Record<string, keyof Omit<ParsedHotkey, \"key\">> = {\n mod: \"mod\",\n ctrl: \"ctrl\",\n control: \"ctrl\",\n alt: \"alt\",\n option: \"alt\",\n opt: \"alt\",\n shift: \"shift\",\n meta: \"meta\",\n cmd: \"meta\",\n command: \"meta\",\n win: \"meta\",\n super: \"meta\",\n};\n\nconst KEY_ALIASES: Record<string, string> = {\n esc: \"escape\",\n space: \" \",\n spacebar: \" \",\n up: \"arrowup\",\n down: \"arrowdown\",\n left: \"arrowleft\",\n right: \"arrowright\",\n return: \"enter\",\n del: \"delete\",\n ins: \"insert\",\n pgup: \"pageup\",\n pgdn: \"pagedown\",\n};\n\n/** Normalize a raw key token into its canonical `event.key` (lowercased) form. */\nfunction normalizeKey(raw: string): string {\n const k = raw.toLowerCase();\n const aliased = KEY_ALIASES[k];\n if (aliased !== undefined) return aliased;\n return k;\n}\n\n/**\n * Parses a combo string like `\"mod+shift+k\"` into modifier flags and a key.\n *\n * Tokens split on `\"+\"`, case-insensitive. Modifiers: `mod` (Cmd on mac / Ctrl\n * elsewhere), `ctrl`/`control`, `alt`/`option`, `shift`, `meta`/`cmd`/`command`/`win`.\n * The remaining token is the key (`esc`→`escape`, `space`→`\" \"`, arrows→`arrowup`…,\n * single letters lowercased).\n *\n * @param str - The combo string, e.g. `\"mod+k\"` or `\"ctrl+shift+escape\"`.\n * @returns The parsed combo.\n *\n * @example\n * ```ts\n * parseHotkey(\"mod+shift+k\");\n * // { key: \"k\", mod: true, ctrl: false, alt: false, shift: true, meta: false }\n * ```\n */\nexport function parseHotkey(str: string): ParsedHotkey {\n const parsed: ParsedHotkey = {\n key: \"\",\n mod: false,\n ctrl: false,\n alt: false,\n shift: false,\n meta: false,\n };\n const tokens = str.split(\"+\");\n for (const token of tokens) {\n const t = token.trim().toLowerCase();\n if (t === \"\") continue;\n const modKey = MOD_TOKENS[t];\n if (modKey) {\n parsed[modKey] = true;\n } else {\n parsed.key = normalizeKey(token.trim());\n }\n }\n return parsed;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Matching */\n/* -------------------------------------------------------------------------- */\n\n/** Match a keyboard event against an already-parsed combo. */\nfunction matchesParsed(\n event: KeyboardEvent,\n parsed: ParsedHotkey,\n mac: boolean,\n): boolean {\n const wantMeta = parsed.meta || (mac && parsed.mod);\n const wantCtrl = parsed.ctrl || (!mac && parsed.mod);\n if (event.metaKey !== wantMeta) return false;\n if (event.ctrlKey !== wantCtrl) return false;\n if (event.altKey !== parsed.alt) return false;\n if (event.shiftKey !== parsed.shift) return false;\n return event.key.toLowerCase() === parsed.key;\n}\n\n/**\n * Returns `true` when a keyboard event satisfies a combo string.\n *\n * `mod` resolves to `metaKey` on mac and `ctrlKey` elsewhere. Modifiers must\n * match exactly (so `\"ctrl+k\"` does not fire when `Ctrl+Shift+K` is pressed).\n *\n * @param event - The keyboard event.\n * @param combo - The combo string, e.g. `\"mod+k\"`.\n * @returns Whether the event satisfies the combo.\n *\n * @example\n * ```ts\n * window.addEventListener(\"keydown\", (e) => {\n * if (matchesHotkey(e, \"mod+k\")) openPalette();\n * });\n * ```\n */\nexport function matchesHotkey(event: KeyboardEvent, combo: string): boolean {\n return matchesParsed(event, parseHotkey(combo), isMac());\n}\n\n/* -------------------------------------------------------------------------- */\n/* Display formatting */\n/* -------------------------------------------------------------------------- */\n\nfunction formatKeyLabel(key: string, mac: boolean): string {\n if (!key) return \"\";\n switch (key) {\n case \" \":\n return \"Space\";\n case \"escape\":\n return \"Esc\";\n case \"enter\":\n return mac ? \"↵\" : \"Enter\";\n case \"arrowup\":\n return mac ? \"↑\" : \"Up\";\n case \"arrowdown\":\n return mac ? \"↓\" : \"Down\";\n case \"arrowleft\":\n return mac ? \"←\" : \"Left\";\n case \"arrowright\":\n return mac ? \"→\" : \"Right\";\n case \"backspace\":\n return mac ? \"⌫\" : \"Backspace\";\n case \"delete\":\n return mac ? \"⌦\" : \"Del\";\n case \"tab\":\n return mac ? \"⇥\" : \"Tab\";\n default:\n break;\n }\n if (key.length === 1) return key.toUpperCase();\n return key.charAt(0).toUpperCase() + key.slice(1);\n}\n\n/**\n * Formats a combo for display, e.g. mac → `\"⌘⇧K\"`, non-mac → `\"Ctrl+Shift+K\"`.\n *\n * Auto-detects the platform when `opts.mac` is omitted.\n *\n * @param combo - The combo string, e.g. `\"mod+shift+k\"`.\n * @param opts - Optional overrides.\n * @param opts.mac - Force mac (`true`) or non-mac (`false`) rendering.\n * @returns A human-friendly label.\n *\n * @example\n * ```tsx\n * <kbd>{formatHotkey(\"mod+k\")}</kbd> // \"⌘K\" on mac, \"Ctrl+K\" elsewhere\n * formatHotkey(\"ctrl+shift+k\", { mac: false }); // \"Ctrl+Shift+K\"\n * ```\n */\nexport function formatHotkey(combo: string, opts: { mac?: boolean } = {}): string {\n const mac = opts.mac ?? isMac();\n const p = parseHotkey(combo);\n const parts: string[] = [];\n if (mac) {\n if (p.meta || p.mod) parts.push(\"⌘\");\n if (p.ctrl) parts.push(\"⌃\");\n if (p.alt) parts.push(\"⌥\");\n if (p.shift) parts.push(\"⇧\");\n parts.push(formatKeyLabel(p.key, true));\n return parts.join(\"\");\n }\n if (p.ctrl || p.mod) parts.push(\"Ctrl\");\n if (p.alt) parts.push(\"Alt\");\n if (p.shift) parts.push(\"Shift\");\n if (p.meta) parts.push(\"Win\");\n parts.push(formatKeyLabel(p.key, false));\n return parts.join(\"+\");\n}\n\n/* -------------------------------------------------------------------------- */\n/* Scopes (module-level, provider-free) */\n/* -------------------------------------------------------------------------- */\n\nconst activeScopeSet = new Set<string>();\nconst scopeListeners = new Set<() => void>();\nconst EMPTY_SCOPES: readonly string[] = Object.freeze([]);\nlet scopesSnapshot: string[] = [];\n\nfunction refreshScopesSnapshot(): void {\n scopesSnapshot = Array.from(activeScopeSet);\n}\n\nfunction emitScopeChange(): void {\n for (const listener of scopeListeners) listener();\n}\n\n/**\n * Activates a scope. Hotkeys bound to this scope will start firing.\n *\n * @param name - The scope name.\n *\n * @example\n * ```ts\n * enableScope(\"editor\"); // now editor hotkeys are live\n * ```\n */\nexport function enableScope(name: string): void {\n if (!activeScopeSet.has(name)) {\n activeScopeSet.add(name);\n refreshScopesSnapshot();\n emitScopeChange();\n }\n}\n\n/**\n * Deactivates a scope. Hotkeys bound only to this scope stop firing.\n *\n * @param name - The scope name.\n */\nexport function disableScope(name: string): void {\n if (activeScopeSet.delete(name)) {\n refreshScopesSnapshot();\n emitScopeChange();\n }\n}\n\n/**\n * Toggles a scope on or off.\n *\n * @param name - The scope name.\n */\nexport function toggleScope(name: string): void {\n if (activeScopeSet.has(name)) disableScope(name);\n else enableScope(name);\n}\n\n/** Returns whether a scope is currently active. */\nexport function isScopeActive(name: string): boolean {\n return activeScopeSet.has(name);\n}\n\nfunction subscribeScopes(callback: () => void): () => void {\n scopeListeners.add(callback);\n return () => {\n scopeListeners.delete(callback);\n };\n}\n\nfunction getScopesSnapshot(): string[] {\n return scopesSnapshot;\n}\n\nfunction getServerScopesSnapshot(): readonly string[] {\n return EMPTY_SCOPES;\n}\n\n/**\n * Subscribes to the set of active scopes and exposes controls.\n *\n * The component re-renders whenever scopes change (backed by\n * `useSyncExternalStore`, so it is concurrent-safe and SSR-safe).\n *\n * @returns `{ activeScopes, enableScope, disableScope, toggleScope }`.\n *\n * @example\n * ```tsx\n * function ScopeBadge() {\n * const { activeScopes, toggleScope } = useHotkeysScopes();\n * return (\n * <button onClick={() => toggleScope(\"editor\")}>\n * {activeScopes.includes(\"editor\") ? \"Editor on\" : \"Editor off\"}\n * </button>\n * );\n * }\n * ```\n */\nexport function useHotkeysScopes(): {\n activeScopes: readonly string[];\n enableScope: (name: string) => void;\n disableScope: (name: string) => void;\n toggleScope: (name: string) => void;\n} {\n const active = useSyncExternalStore(\n subscribeScopes,\n getScopesSnapshot,\n getServerScopesSnapshot,\n );\n return { activeScopes: active, enableScope, disableScope, toggleScope };\n}\n\n/* -------------------------------------------------------------------------- */\n/* useHotkeys */\n/* -------------------------------------------------------------------------- */\n\nconst SEQUENCE_TIMEOUT = 1000;\n\ninterface HotkeyEntry {\n raw: string;\n steps: ParsedHotkey[];\n isSequence: boolean;\n}\n\ninterface SequenceProgress {\n index: number;\n time: number;\n}\n\nfunction isModifierKey(key: string): boolean {\n return (\n key === \"Control\" ||\n key === \"Shift\" ||\n key === \"Alt\" ||\n key === \"Meta\" ||\n key === \"OS\" ||\n key === \"AltGraph\"\n );\n}\n\nfunction isFromFormField(target: EventTarget | null): boolean {\n if (typeof HTMLElement === \"undefined\") return false;\n if (!(target instanceof HTMLElement)) return false;\n const tag = target.tagName;\n if (tag === \"INPUT\" || tag === \"TEXTAREA\" || tag === \"SELECT\") return true;\n return target.isContentEditable;\n}\n\nfunction resolveTarget(target: HotkeyTarget | undefined): Window | HTMLElement | null {\n if (typeof window === \"undefined\") return null;\n if (!target) return window;\n if (target === window) return window;\n if (typeof HTMLElement !== \"undefined\" && target instanceof HTMLElement) {\n return target;\n }\n return (target as RefObject<HTMLElement | null>).current;\n}\n\nfunction toEntries(keys: string | string[]): HotkeyEntry[] {\n const list = Array.isArray(keys) ? keys : [keys];\n return list.map((raw) => {\n const steps = raw\n .replace(/\\s+then\\s+/gi, \" \")\n .trim()\n .split(/\\s+/)\n .filter(Boolean)\n .map(parseHotkey);\n return { raw, steps, isSequence: steps.length > 1 };\n });\n}\n\n/**\n * Binds keyboard shortcut(s) — combos and/or sequences — for the lifetime of a component.\n *\n * Supports:\n * - **Combos**: `\"mod+k\"`, `\"ctrl+shift+p\"`.\n * - **Multiple combos**: pass an array — any match fires the handler.\n * - **Sequences**: `\"g then d\"` or `\"g d\"` — press keys in order within ~1s.\n * - **Scopes**: only fire while a named scope is active (see {@link enableScope}).\n *\n * The handler is kept in a ref, so `deps` are optional — the latest closure is\n * always used without re-binding the listener.\n *\n * @param keys - A combo/sequence string, or an array of them.\n * @param handler - Called with the event and the matched combo string.\n * @param options - See {@link HotkeyOptions}.\n * @param deps - Optional dependency list (rarely needed thanks to the latest-ref).\n *\n * @example\n * ```tsx\n * // Open a command palette with ⌘K / Ctrl+K\n * useHotkeys(\"mod+k\", (e) => {\n * e.preventDefault();\n * setPaletteOpen(true);\n * });\n *\n * // Navigate with a sequence: press \"g\" then \"d\"\n * useHotkeys(\"g then d\", () => router.push(\"/dashboard\"));\n *\n * // Scoped: only while the \"editor\" scope is active\n * useHotkeys(\"mod+b\", toggleBold, { scopes: \"editor\" });\n * ```\n */\nexport function useHotkeys(\n keys: string | string[],\n handler: HotkeyHandler,\n options: HotkeyOptions = {},\n deps?: unknown[],\n): void {\n const handlerRef = useRef<HotkeyHandler>(handler);\n handlerRef.current = handler;\n\n const optionsRef = useRef<HotkeyOptions>(options);\n optionsRef.current = options;\n\n const entriesRef = useRef<HotkeyEntry[]>([]);\n entriesRef.current = toEntries(keys);\n\n const progressRef = useRef<Map<string, SequenceProgress>>(new Map());\n\n const eventType = options.eventType ?? \"keydown\";\n const depList = deps ?? [];\n\n useEffect(() => {\n const el = resolveTarget(optionsRef.current.target);\n if (!el) return;\n\n const listener = (rawEvent: Event) => {\n const event = rawEvent as KeyboardEvent;\n const o = optionsRef.current;\n\n if (o.enabled === false) return;\n\n const enableOnFormTags = o.enableOnFormTags ?? false;\n if (!enableOnFormTags && isFromFormField(event.target)) return;\n\n const rawScopes = o.scopes;\n const scopeArr =\n rawScopes == null ? [] : Array.isArray(rawScopes) ? rawScopes : [rawScopes];\n if (scopeArr.length > 0 && !scopeArr.some((s) => activeScopeSet.has(s))) {\n return;\n }\n\n const mac = isMac();\n const preventDefault = o.preventDefault ?? true;\n const lone = isModifierKey(event.key);\n const progress = progressRef.current;\n\n for (const entry of entriesRef.current) {\n if (entry.isSequence) {\n // Ignore lone modifier presses so they neither advance nor reset.\n if (lone) continue;\n\n let state = progress.get(entry.raw);\n if (!state) {\n state = { index: 0, time: 0 };\n progress.set(entry.raw, state);\n }\n\n const now = Date.now();\n if (state.index > 0 && now - state.time > SEQUENCE_TIMEOUT) {\n state.index = 0;\n }\n\n const expected = entry.steps[state.index];\n if (expected && matchesParsed(event, expected, mac)) {\n state.index += 1;\n state.time = now;\n if (state.index >= entry.steps.length) {\n state.index = 0;\n if (preventDefault) event.preventDefault();\n handlerRef.current(event, entry.raw);\n }\n } else {\n const first = entry.steps[0];\n if (first && matchesParsed(event, first, mac)) {\n state.index = 1;\n state.time = now;\n } else {\n state.index = 0;\n }\n }\n } else {\n const combo = entry.steps[0];\n if (combo && matchesParsed(event, combo, mac)) {\n if (preventDefault) event.preventDefault();\n handlerRef.current(event, entry.raw);\n }\n }\n }\n };\n\n el.addEventListener(eventType, listener as EventListener);\n return () => {\n el.removeEventListener(eventType, listener as EventListener);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [eventType, ...depList]);\n}\n"]}
1
+ {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @lacspace/hotkeys — ergonomic keyboard shortcuts for React.\n *\n * Combos (`mod+k`), key sequences (`g then d`), scopes, and pretty display\n * formatting (`⌘K`). SSR-safe, respects form fields, zero-dependency, fully typed.\n *\n * @packageDocumentation\n */\n\nimport { useEffect, useRef, useSyncExternalStore } from \"react\";\nimport type { RefObject } from \"react\";\n\n/* -------------------------------------------------------------------------- */\n/* Types */\n/* -------------------------------------------------------------------------- */\n\n/** A parsed hotkey combo: the resolved key plus each modifier requirement. */\nexport interface ParsedHotkey {\n /** Normalized key (e.g. `\"k\"`, `\"escape\"`, `\" \"`, `\"arrowup\"`). */\n key: string;\n /** `mod` — Cmd on mac, Ctrl elsewhere. */\n mod: boolean;\n /** Control key. */\n ctrl: boolean;\n /** Alt / Option key. */\n alt: boolean;\n /** Shift key. */\n shift: boolean;\n /** Meta / Cmd / Win key. */\n meta: boolean;\n}\n\n/** The handler invoked when a hotkey (or the final step of a sequence) fires. */\nexport type HotkeyHandler = (event: KeyboardEvent, combo: string) => void;\n\n/** Where to attach the key listener. */\nexport type HotkeyTarget =\n | Window\n | HTMLElement\n | RefObject<HTMLElement | null>;\n\n/** Options for {@link useHotkeys}. */\nexport interface HotkeyOptions {\n /** Master switch. When `false`, nothing fires. @default true */\n enabled?: boolean;\n /** Call `event.preventDefault()` when a hotkey matches. @default true */\n preventDefault?: boolean;\n /**\n * Allow firing while an `input` / `textarea` / `select` / `contentEditable`\n * element is the event source. @default false\n */\n enableOnFormTags?: boolean;\n /** Which key event to listen for. @default \"keydown\" */\n eventType?: \"keydown\" | \"keyup\";\n /** Where to bind the listener. @default window */\n target?: HotkeyTarget;\n /**\n * Scope name(s). The hotkey only fires when at least one is active\n * (see {@link enableScope}). Omit to always fire.\n */\n scopes?: string | string[];\n}\n\n/* -------------------------------------------------------------------------- */\n/* Platform detection */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Detects whether the current platform is a Mac (or iOS device).\n *\n * SSR-safe: always returns `false` when there is no `navigator`.\n *\n * @returns `true` on macOS / iOS, `false` otherwise (and on the server).\n *\n * @example\n * ```ts\n * const symbol = isMac() ? \"⌘\" : \"Ctrl\";\n * ```\n */\nexport function isMac(): boolean {\n if (typeof navigator === \"undefined\") return false;\n const uaData = (navigator as Navigator & {\n userAgentData?: { platform?: string };\n }).userAgentData;\n if (uaData && typeof uaData.platform === \"string\" && uaData.platform) {\n return /mac/i.test(uaData.platform);\n }\n const platform = navigator.platform || \"\";\n if (platform) return /mac|iphone|ipad|ipod/i.test(platform);\n return /mac|iphone|ipad|ipod/i.test(navigator.userAgent || \"\");\n}\n\n/* -------------------------------------------------------------------------- */\n/* Parsing */\n/* -------------------------------------------------------------------------- */\n\nconst MOD_TOKENS: Record<string, keyof Omit<ParsedHotkey, \"key\">> = {\n mod: \"mod\",\n ctrl: \"ctrl\",\n control: \"ctrl\",\n alt: \"alt\",\n option: \"alt\",\n opt: \"alt\",\n shift: \"shift\",\n meta: \"meta\",\n cmd: \"meta\",\n command: \"meta\",\n win: \"meta\",\n super: \"meta\",\n};\n\nconst KEY_ALIASES: Record<string, string> = {\n esc: \"escape\",\n space: \" \",\n spacebar: \" \",\n up: \"arrowup\",\n down: \"arrowdown\",\n left: \"arrowleft\",\n right: \"arrowright\",\n return: \"enter\",\n del: \"delete\",\n ins: \"insert\",\n pgup: \"pageup\",\n pgdn: \"pagedown\",\n};\n\n/** Normalize a raw key token into its canonical `event.key` (lowercased) form. */\nfunction normalizeKey(raw: string): string {\n const k = raw.toLowerCase();\n const aliased = KEY_ALIASES[k];\n if (aliased !== undefined) return aliased;\n return k;\n}\n\n/**\n * Parses a combo string like `\"mod+shift+k\"` into modifier flags and a key.\n *\n * Tokens split on `\"+\"`, case-insensitive. Modifiers: `mod` (Cmd on mac / Ctrl\n * elsewhere), `ctrl`/`control`, `alt`/`option`, `shift`, `meta`/`cmd`/`command`/`win`.\n * The remaining token is the key (`esc`→`escape`, `space`→`\" \"`, arrows→`arrowup`…,\n * single letters lowercased).\n *\n * @param str - The combo string, e.g. `\"mod+k\"` or `\"ctrl+shift+escape\"`.\n * @returns The parsed combo.\n *\n * @example\n * ```ts\n * parseHotkey(\"mod+shift+k\");\n * // { key: \"k\", mod: true, ctrl: false, alt: false, shift: true, meta: false }\n * ```\n */\nexport function parseHotkey(str: string): ParsedHotkey {\n const parsed: ParsedHotkey = {\n key: \"\",\n mod: false,\n ctrl: false,\n alt: false,\n shift: false,\n meta: false,\n };\n const tokens = str.split(\"+\");\n for (const token of tokens) {\n const t = token.trim().toLowerCase();\n if (t === \"\") continue;\n const modKey = MOD_TOKENS[t];\n if (modKey) {\n parsed[modKey] = true;\n } else {\n parsed.key = normalizeKey(token.trim());\n }\n }\n return parsed;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Matching */\n/* -------------------------------------------------------------------------- */\n\n/** Match a keyboard event against an already-parsed combo. */\nfunction matchesParsed(\n event: KeyboardEvent,\n parsed: ParsedHotkey,\n mac: boolean,\n): boolean {\n const wantMeta = parsed.meta || (mac && parsed.mod);\n const wantCtrl = parsed.ctrl || (!mac && parsed.mod);\n if (event.metaKey !== wantMeta) return false;\n if (event.ctrlKey !== wantCtrl) return false;\n if (event.altKey !== parsed.alt) return false;\n if (event.shiftKey !== parsed.shift) return false;\n return event.key.toLowerCase() === parsed.key;\n}\n\n/**\n * Returns `true` when a keyboard event satisfies a combo string.\n *\n * `mod` resolves to `metaKey` on mac and `ctrlKey` elsewhere. Modifiers must\n * match exactly (so `\"ctrl+k\"` does not fire when `Ctrl+Shift+K` is pressed).\n *\n * @param event - The keyboard event.\n * @param combo - The combo string, e.g. `\"mod+k\"`.\n * @returns Whether the event satisfies the combo.\n *\n * @example\n * ```ts\n * window.addEventListener(\"keydown\", (e) => {\n * if (matchesHotkey(e, \"mod+k\")) openPalette();\n * });\n * ```\n */\nexport function matchesHotkey(event: KeyboardEvent, combo: string): boolean {\n return matchesParsed(event, parseHotkey(combo), isMac());\n}\n\n/* -------------------------------------------------------------------------- */\n/* Display formatting */\n/* -------------------------------------------------------------------------- */\n\nfunction formatKeyLabel(key: string, mac: boolean): string {\n if (!key) return \"\";\n switch (key) {\n case \" \":\n return \"Space\";\n case \"escape\":\n return \"Esc\";\n case \"enter\":\n return mac ? \"↵\" : \"Enter\";\n case \"arrowup\":\n return mac ? \"↑\" : \"Up\";\n case \"arrowdown\":\n return mac ? \"↓\" : \"Down\";\n case \"arrowleft\":\n return mac ? \"←\" : \"Left\";\n case \"arrowright\":\n return mac ? \"→\" : \"Right\";\n case \"backspace\":\n return mac ? \"⌫\" : \"Backspace\";\n case \"delete\":\n return mac ? \"⌦\" : \"Del\";\n case \"tab\":\n return mac ? \"⇥\" : \"Tab\";\n default:\n break;\n }\n if (key.length === 1) return key.toUpperCase();\n return key.charAt(0).toUpperCase() + key.slice(1);\n}\n\n/**\n * Formats a combo for display, e.g. mac → `\"⌘⇧K\"`, non-mac → `\"Ctrl+Shift+K\"`.\n *\n * Auto-detects the platform when `opts.mac` is omitted.\n *\n * @param combo - The combo string, e.g. `\"mod+shift+k\"`.\n * @param opts - Optional overrides.\n * @param opts.mac - Force mac (`true`) or non-mac (`false`) rendering.\n * @returns A human-friendly label.\n *\n * @example\n * ```tsx\n * <kbd>{formatHotkey(\"mod+k\")}</kbd> // \"⌘K\" on mac, \"Ctrl+K\" elsewhere\n * formatHotkey(\"ctrl+shift+k\", { mac: false }); // \"Ctrl+Shift+K\"\n * ```\n */\nexport function formatHotkey(combo: string, opts: { mac?: boolean } = {}): string {\n const mac = opts.mac ?? isMac();\n const p = parseHotkey(combo);\n const parts: string[] = [];\n if (mac) {\n if (p.meta || p.mod) parts.push(\"⌘\");\n if (p.ctrl) parts.push(\"⌃\");\n if (p.alt) parts.push(\"⌥\");\n if (p.shift) parts.push(\"⇧\");\n parts.push(formatKeyLabel(p.key, true));\n return parts.join(\"\");\n }\n if (p.ctrl || p.mod) parts.push(\"Ctrl\");\n if (p.alt) parts.push(\"Alt\");\n if (p.shift) parts.push(\"Shift\");\n if (p.meta) parts.push(\"Win\");\n parts.push(formatKeyLabel(p.key, false));\n return parts.join(\"+\");\n}\n\n/* -------------------------------------------------------------------------- */\n/* Scopes (module-level, provider-free) */\n/* -------------------------------------------------------------------------- */\n\nconst activeScopeSet = new Set<string>();\nconst scopeListeners = new Set<() => void>();\nconst EMPTY_SCOPES: readonly string[] = Object.freeze([]);\nlet scopesSnapshot: string[] = [];\n\nfunction refreshScopesSnapshot(): void {\n scopesSnapshot = Array.from(activeScopeSet);\n}\n\nfunction emitScopeChange(): void {\n for (const listener of scopeListeners) listener();\n}\n\n/**\n * Activates a scope. Hotkeys bound to this scope will start firing.\n *\n * @param name - The scope name.\n *\n * @example\n * ```ts\n * enableScope(\"editor\"); // now editor hotkeys are live\n * ```\n */\nexport function enableScope(name: string): void {\n if (!activeScopeSet.has(name)) {\n activeScopeSet.add(name);\n refreshScopesSnapshot();\n emitScopeChange();\n }\n}\n\n/**\n * Deactivates a scope. Hotkeys bound only to this scope stop firing.\n *\n * @param name - The scope name.\n */\nexport function disableScope(name: string): void {\n if (activeScopeSet.delete(name)) {\n refreshScopesSnapshot();\n emitScopeChange();\n }\n}\n\n/**\n * Toggles a scope on or off.\n *\n * @param name - The scope name.\n */\nexport function toggleScope(name: string): void {\n if (activeScopeSet.has(name)) disableScope(name);\n else enableScope(name);\n}\n\n/** Returns whether a scope is currently active. */\nexport function isScopeActive(name: string): boolean {\n return activeScopeSet.has(name);\n}\n\nfunction subscribeScopes(callback: () => void): () => void {\n scopeListeners.add(callback);\n return () => {\n scopeListeners.delete(callback);\n };\n}\n\nfunction getScopesSnapshot(): string[] {\n return scopesSnapshot;\n}\n\nfunction getServerScopesSnapshot(): readonly string[] {\n return EMPTY_SCOPES;\n}\n\n/**\n * Subscribes to the set of active scopes and exposes controls.\n *\n * The component re-renders whenever scopes change (backed by\n * `useSyncExternalStore`, so it is concurrent-safe and SSR-safe).\n *\n * @returns `{ activeScopes, enableScope, disableScope, toggleScope }`.\n *\n * @example\n * ```tsx\n * function ScopeBadge() {\n * const { activeScopes, toggleScope } = useHotkeysScopes();\n * return (\n * <button onClick={() => toggleScope(\"editor\")}>\n * {activeScopes.includes(\"editor\") ? \"Editor on\" : \"Editor off\"}\n * </button>\n * );\n * }\n * ```\n */\nexport function useHotkeysScopes(): {\n activeScopes: readonly string[];\n enableScope: (name: string) => void;\n disableScope: (name: string) => void;\n toggleScope: (name: string) => void;\n} {\n const active = useSyncExternalStore(\n subscribeScopes,\n getScopesSnapshot,\n getServerScopesSnapshot,\n );\n return { activeScopes: active, enableScope, disableScope, toggleScope };\n}\n\n/* -------------------------------------------------------------------------- */\n/* useHotkeys */\n/* -------------------------------------------------------------------------- */\n\nconst SEQUENCE_TIMEOUT = 1000;\n\ninterface HotkeyEntry {\n raw: string;\n steps: ParsedHotkey[];\n isSequence: boolean;\n}\n\ninterface SequenceProgress {\n index: number;\n time: number;\n}\n\nfunction isModifierKey(key: string): boolean {\n return (\n key === \"Control\" ||\n key === \"Shift\" ||\n key === \"Alt\" ||\n key === \"Meta\" ||\n key === \"OS\" ||\n key === \"AltGraph\"\n );\n}\n\nfunction isFromFormField(target: EventTarget | null): boolean {\n if (typeof HTMLElement === \"undefined\") return false;\n if (!(target instanceof HTMLElement)) return false;\n const tag = target.tagName;\n if (tag === \"INPUT\" || tag === \"TEXTAREA\" || tag === \"SELECT\") return true;\n return target.isContentEditable;\n}\n\nfunction resolveTarget(target: HotkeyTarget | undefined): Window | HTMLElement | null {\n if (typeof window === \"undefined\") return null;\n if (!target) return window;\n if (target === window) return window;\n if (typeof HTMLElement !== \"undefined\" && target instanceof HTMLElement) {\n return target;\n }\n return (target as RefObject<HTMLElement | null>).current;\n}\n\nfunction toEntries(keys: string | string[]): HotkeyEntry[] {\n const list = Array.isArray(keys) ? keys : [keys];\n return list.map((raw) => {\n const steps = raw\n .replace(/\\s+then\\s+/gi, \" \")\n .trim()\n .split(/\\s+/)\n .filter(Boolean)\n .map(parseHotkey);\n return { raw, steps, isSequence: steps.length > 1 };\n });\n}\n\n/**\n * Binds keyboard shortcut(s) — combos and/or sequences — for the lifetime of a component.\n *\n * Supports:\n * - **Combos**: `\"mod+k\"`, `\"ctrl+shift+p\"`.\n * - **Multiple combos**: pass an array — any match fires the handler.\n * - **Sequences**: `\"g then d\"` or `\"g d\"` — press keys in order within ~1s.\n * - **Scopes**: only fire while a named scope is active (see {@link enableScope}).\n *\n * The handler is kept in a ref, so `deps` are optional — the latest closure is\n * always used without re-binding the listener.\n *\n * @param keys - A combo/sequence string, or an array of them.\n * @param handler - Called with the event and the matched combo string.\n * @param options - See {@link HotkeyOptions}.\n * @param deps - Optional dependency list (rarely needed thanks to the latest-ref).\n *\n * @example\n * ```tsx\n * // Open a command palette with ⌘K / Ctrl+K\n * useHotkeys(\"mod+k\", (e) => {\n * e.preventDefault();\n * setPaletteOpen(true);\n * });\n *\n * // Navigate with a sequence: press \"g\" then \"d\"\n * useHotkeys(\"g then d\", () => router.push(\"/dashboard\"));\n *\n * // Scoped: only while the \"editor\" scope is active\n * useHotkeys(\"mod+b\", toggleBold, { scopes: \"editor\" });\n * ```\n */\nexport function useHotkeys(\n keys: string | string[],\n handler: HotkeyHandler,\n options: HotkeyOptions = {},\n deps?: unknown[],\n): void {\n const handlerRef = useRef<HotkeyHandler>(handler);\n handlerRef.current = handler;\n\n const optionsRef = useRef<HotkeyOptions>(options);\n optionsRef.current = options;\n\n const entriesRef = useRef<HotkeyEntry[]>([]);\n entriesRef.current = toEntries(keys);\n\n const progressRef = useRef<Map<string, SequenceProgress>>(new Map());\n\n const eventType = options.eventType ?? \"keydown\";\n const depList = deps ?? [];\n const target = options.target;\n\n useEffect(() => {\n // Re-resolve on every target change so a late-mounting / swapped target\n // re-binds. `target` is in the deps below; resolving inside the effect also\n // keeps the common ref-to-element case working (ref.current is set by the\n // time the effect runs after commit).\n const el = resolveTarget(target);\n if (!el) return;\n\n const listener = (rawEvent: Event) => {\n const event = rawEvent as KeyboardEvent;\n const o = optionsRef.current;\n\n if (o.enabled === false) return;\n\n const enableOnFormTags = o.enableOnFormTags ?? false;\n if (!enableOnFormTags && isFromFormField(event.target)) return;\n\n const rawScopes = o.scopes;\n const scopeArr =\n rawScopes == null ? [] : Array.isArray(rawScopes) ? rawScopes : [rawScopes];\n if (scopeArr.length > 0 && !scopeArr.some((s) => activeScopeSet.has(s))) {\n return;\n }\n\n const mac = isMac();\n const preventDefault = o.preventDefault ?? true;\n const lone = isModifierKey(event.key);\n const progress = progressRef.current;\n\n for (const entry of entriesRef.current) {\n if (entry.isSequence) {\n // Ignore lone modifier presses so they neither advance nor reset.\n if (lone) continue;\n\n let state = progress.get(entry.raw);\n if (!state) {\n state = { index: 0, time: 0 };\n progress.set(entry.raw, state);\n }\n\n const now = Date.now();\n if (state.index > 0 && now - state.time > SEQUENCE_TIMEOUT) {\n state.index = 0;\n }\n\n const expected = entry.steps[state.index];\n if (expected && matchesParsed(event, expected, mac)) {\n state.index += 1;\n state.time = now;\n if (state.index >= entry.steps.length) {\n state.index = 0;\n if (preventDefault) event.preventDefault();\n handlerRef.current(event, entry.raw);\n }\n } else {\n const first = entry.steps[0];\n if (first && matchesParsed(event, first, mac)) {\n state.index = 1;\n state.time = now;\n } else {\n state.index = 0;\n }\n }\n } else {\n const combo = entry.steps[0];\n if (combo && matchesParsed(event, combo, mac)) {\n if (preventDefault) event.preventDefault();\n handlerRef.current(event, entry.raw);\n }\n }\n }\n };\n\n el.addEventListener(eventType, listener as EventListener);\n return () => {\n el.removeEventListener(eventType, listener as EventListener);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [target, eventType, ...depList]);\n}\n"]}
package/package.json CHANGED
@@ -1,23 +1,66 @@
1
1
  {
2
2
  "name": "@lacspace/hotkeys",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Ergonomic keyboard shortcuts for React — combos (mod+k), key sequences (g then d), scopes, and pretty display formatting (⌘K). SSR-safe, respects form fields, zero-dependency, fully typed.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
7
7
  "module": "./dist/index.js",
8
8
  "types": "./dist/index.d.ts",
9
- "exports": { ".": { "import": { "types": "./dist/index.d.ts", "default": "./dist/index.js" }, "require": { "types": "./dist/index.d.cts", "default": "./dist/index.cjs" } } },
10
- "files": ["dist"],
9
+ "exports": {
10
+ ".": {
11
+ "import": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ },
15
+ "require": {
16
+ "types": "./dist/index.d.cts",
17
+ "default": "./dist/index.cjs"
18
+ }
19
+ }
20
+ },
21
+ "files": [
22
+ "dist"
23
+ ],
11
24
  "sideEffects": false,
12
- "scripts": { "build": "tsup", "prepublishOnly": "npm run build" },
13
- "keywords": ["react","hotkeys","keyboard-shortcuts","usehotkeys","keybindings","shortcuts","cmd-k","accessibility","ssr","typescript","zero-dependency"],
25
+ "scripts": {
26
+ "build": "tsup",
27
+ "prepublishOnly": "npm run build"
28
+ },
29
+ "keywords": [
30
+ "react",
31
+ "hotkeys",
32
+ "keyboard-shortcuts",
33
+ "usehotkeys",
34
+ "keybindings",
35
+ "shortcuts",
36
+ "cmd-k",
37
+ "accessibility",
38
+ "ssr",
39
+ "typescript",
40
+ "zero-dependency"
41
+ ],
14
42
  "author": "Lacspace <contact@lacspace.com>",
15
43
  "license": "SEE LICENSE IN LICENSE",
16
44
  "homepage": "https://lacspace.com/packages",
17
- "repository": { "type": "git", "url": "git+https://github.com/lacspace/npm-packages.git", "directory": "hotkeys" },
18
- "bugs": { "url": "https://github.com/lacspace/npm-packages/issues" },
19
- "engines": { "node": ">=18" },
20
- "peerDependencies": { "react": ">=18" },
21
- "devDependencies": { "@types/react": "^18.3.12", "react": "^18.3.1" },
22
- "publishConfig": { "access": "public" }
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "git+https://github.com/lacspace/npm-packages.git",
48
+ "directory": "hotkeys"
49
+ },
50
+ "bugs": {
51
+ "url": "https://github.com/lacspace/npm-packages/issues"
52
+ },
53
+ "engines": {
54
+ "node": ">=18"
55
+ },
56
+ "peerDependencies": {
57
+ "react": ">=18"
58
+ },
59
+ "devDependencies": {
60
+ "@types/react": "^18.3.12",
61
+ "react": "^18.3.1"
62
+ },
63
+ "publishConfig": {
64
+ "access": "public"
65
+ }
23
66
  }