{"_id":"@dskyle77/shortcut-manager","_rev":"2-4ba184a533073c3d178f0507a5982345","name":"@dskyle77/shortcut-manager","dist-tags":{"latest":"0.1.1"},"versions":{"0.1.0":{"name":"@dskyle77/shortcut-manager","version":"0.1.0","keywords":["keyboard-shortcuts","hotkeys","react","nextjs","accessibility","context-aware"],"license":"MIT","_id":"@dskyle77/shortcut-manager@0.1.0","maintainers":[{"name":"dskyle77","email":"dskyle77@gmail.com"}],"homepage":"https://github.com/dskyle77/smart-shortcut-manager#readme","bugs":{"url":"https://github.com/dskyle77/smart-shortcut-manager/issues"},"dist":{"shasum":"9e4cd18c4922eb1ef9a02c3c22e3aea3646d65dd","tarball":"https://registry.npmjs.org/@dskyle77/shortcut-manager/-/shortcut-manager-0.1.0.tgz","fileCount":5,"integrity":"sha512-gWBUlpmuabK1DRe0D/RPkRJStxnlWis5quD0YMMvVy7TgciDX8+2R4GvbbXp4T8y8oJKIosyPQr6eURRZoJvwQ==","signatures":[{"sig":"MEQCIDZQqcrdfU0t3ah7ex0b3Hk2P4sTT85dpQ96OLPnBpa8AiB5Ap+Xk/DXr3d9r96pFNjoujY3DMZHlLMVjhRwCh9Nvg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":20640},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"0429d627e40a98b3db8fddd097884723839f5ab3","scripts":{"dev":"tsup src/index.ts --format esm --dts --watch","lint":"eslint src","test":"vitest run","build":"tsup src/index.ts --format esm --dts --clean","typecheck":"tsc --noEmit","test:watch":"vitest","prepublishOnly":"npm run build && npm run test"},"_npmUser":{"name":"dskyle77","email":"dskyle77@gmail.com"},"repository":{"url":"git+https://github.com/dskyle77/smart-shortcut-manager.git","type":"git"},"_npmVersion":"11.15.0","description":"A context-aware keyboard shortcut manager for React and Next.js apps.","directories":{},"sideEffects":false,"_nodeVersion":"24.4.1","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.2.0","jsdom":"^25.0.0","react":"^18.3.0","vitest":"^2.0.0","react-dom":"^18.3.0","typescript":"^5.5.0","@types/react":"^18.3.0","@vitejs/plugin-react":"^4.3.0","@testing-library/react":"^16.0.0"},"peerDependencies":{"react":">=17.0.0"},"_npmOperationalInternal":{"tmp":"tmp/shortcut-manager_0.1.0_1785419003411_0.2898860550985929","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@dskyle77/shortcut-manager","version":"0.1.1","description":"A context-aware keyboard shortcut manager for React and Next.js apps.","type":"module","main":"./dist/index.js","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"repository":{"type":"git","url":"git+https://github.com/dskyle77/smart-shortcut-manager.git"},"homepage":"https://github.com/dskyle77/smart-shortcut-manager#readme","sideEffects":false,"scripts":{"build":"tsup src/index.ts --format esm --dts --clean","dev":"tsup src/index.ts --format esm --dts --watch","test":"vitest run","test:watch":"vitest","typecheck":"tsc --noEmit","lint":"eslint src","prepublishOnly":"npm run build && npm run test"},"keywords":["keyboard-shortcuts","hotkeys","react","nextjs","accessibility","context-aware"],"license":"MIT","peerDependencies":{"react":">=17.0.0"},"devDependencies":{"@testing-library/react":"^16.0.0","@types/react":"^18.3.0","@vitejs/plugin-react":"^4.3.0","jsdom":"^25.0.0","react":"^18.3.0","react-dom":"^18.3.0","tsup":"^8.2.0","typescript":"^5.5.0","vitest":"^2.0.0"},"engines":{"node":">=18"},"gitHead":"a6333d46019fc560b0f81df7bfded86b5836de34","_id":"@dskyle77/shortcut-manager@0.1.1","bugs":{"url":"https://github.com/dskyle77/smart-shortcut-manager/issues"},"_nodeVersion":"24.4.1","_npmVersion":"11.15.0","dist":{"integrity":"sha512-16lBVn4xhBk26EHYzqlP3R7rWZ2s8WW+GJOjaoLhasQ7+HowM3BdJ14G3Yl1jJN9yNk0dhZ/fUnPmkcIty/wtA==","shasum":"87effe1411bad5b80ee40accd38181c460287795","tarball":"https://registry.npmjs.org/@dskyle77/shortcut-manager/-/shortcut-manager-0.1.1.tgz","fileCount":5,"unpackedSize":20640,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCF8ucBrLy2xhkYaGYr1Oz/VeEHufzgJS/Llun0/fE3KQIhAMkZaBcRKlegpLeb4TKx07XpamxZ+58IgcDBjWKK/Oj7"}]},"_npmUser":{"name":"dskyle77","email":"dskyle77@gmail.com"},"directories":{},"maintainers":[{"name":"dskyle77","email":"dskyle77@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/shortcut-manager_0.1.1_1785484758843_0.15568808994419303"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-30T13:43:22.188Z","modified":"2026-07-31T07:59:19.122Z","0.1.0":"2026-07-30T13:43:23.553Z","0.1.1":"2026-07-31T07:59:18.989Z"},"bugs":{"url":"https://github.com/dskyle77/smart-shortcut-manager/issues"},"license":"MIT","homepage":"https://github.com/dskyle77/smart-shortcut-manager#readme","keywords":["keyboard-shortcuts","hotkeys","react","nextjs","accessibility","context-aware"],"repository":{"type":"git","url":"git+https://github.com/dskyle77/smart-shortcut-manager.git"},"description":"A context-aware keyboard shortcut manager for React and Next.js apps.","maintainers":[{"name":"dskyle77","email":"dskyle77@gmail.com"}],"readme":"# smart-shortcut-manager\n\nA context-aware keyboard shortcut manager for React and Next.js apps.\n\nRegister global shortcuts, then let modals, panels, or focused editors\n**shadow** them with their own scoped shortcuts — no manual bookkeeping,\nno conflicts, SSR-safe.\n\n```tsx\nuseShortcut(\"Ctrl+Shift+D\", duplicate);\n```\n\n## Install\n\n```bash\nnpm install @dskyle77/shortcut-manager\n```\n\n## Quick start\n\nWrap your app once, near the root layout:\n\n```tsx\n// app/layout.tsx (Next.js) or your app root\nimport { ShortcutProvider } from \"@dskyle77/shortcut-manager\";\n\nexport default function RootLayout({ children }: { children: React.ReactNode }) {\n  return (\n    <html>\n      <body>\n        <ShortcutProvider>{children}</ShortcutProvider>\n      </body>\n    </html>\n  );\n}\n```\n\nThen register shortcuts from any component:\n\n```tsx\nimport { useShortcut } from \"@dskyle77/shortcut-manager\";\n\nfunction Editor({ duplicate }: { duplicate: () => void }) {\n  useShortcut(\"Ctrl+Shift+D\", duplicate);\n  return <div>...</div>;\n}\n```\n\n## Context-aware scopes\n\nThe \"smart\" part: shortcuts can belong to a **scope**, and only the\nmost-recently-activated scope's shortcuts take priority. This means a\nmodal can safely reuse a key combo (like `Escape` or `Ctrl+D`) that's\nalready bound globally, without you having to manually disable the\nglobal one.\n\n```tsx\nimport { useShortcut, useShortcutScope } from \"@dskyle77/shortcut-manager\";\n\nfunction ConfirmModal({ open, onClose, onConfirm }: {\n  open: boolean;\n  onClose: () => void;\n  onConfirm: () => void;\n}) {\n  // Activates the \"modal\" scope only while `open` is true.\n  useShortcutScope(\"modal\", open);\n\n  // Scope alone gates when these fire — no ternary needed. The handlers\n  // are registered as soon as the component mounts, but stay silent\n  // until \"modal\" is the active scope.\n  useShortcut(\"Escape\", onClose, { scope: \"modal\" });\n  useShortcut(\"Enter\", onConfirm, { scope: \"modal\" });\n\n  if (!open) return null;\n  return <div role=\"dialog\">...</div>;\n}\n```\n\n> **Note:** you don't need `open ? \"Escape\" : null` here — registering a\n> scoped shortcut isn't the same as it firing. It's added to the registry\n> on mount, but only *fires* while `\"modal\"` is on top of the scope stack.\n> Save the `combo ? ... : null` pattern for gating a shortcut for a reason\n> **unrelated to scope** — see below.\n\n### Conditionally disabling a shortcut (unrelated to scope)\n\nThe `combo ? \"X\" : null` pattern is for when a shortcut needs to turn off\nfor a reason that has nothing to do with which scope is active — e.g. the\ncomponent stays mounted and its scope stays active, but the action itself\nis temporarily invalid:\n\n```tsx\nfunction ConfirmModal({ open, isSaving, onConfirm }: {\n  open: boolean;\n  isSaving: boolean;\n  onConfirm: () => void;\n}) {\n  useShortcutScope(\"modal\", open);\n\n  // Still scoped to \"modal\" — but also disabled mid-save, independent\n  // of whether the modal scope is active.\n  useShortcut(isSaving ? null : \"Enter\", onConfirm, { scope: \"modal\" });\n\n  if (!open) return null;\n  return <div role=\"dialog\">{isSaving ? \"Saving…\" : \"Press Enter to confirm\"}</div>;\n}\n```\n\nHere `isSaving` can flip while the modal stays open and the scope stays\nactive the whole time — so scope can't express this, but the ternary can.\n\n## API\n\n### `<ShortcutProvider>`\n\nProvides the shortcut registry via context. Render once near your app root.\nSafe to render on the server — it doesn't touch `window`/`document` until a\nshortcut is actually registered on the client.\n\n### `useShortcut(combo, handler, options?)`\n\nRegisters a shortcut for the lifetime of the calling component.\n\n- `combo` — a string like `\"Ctrl+Shift+D\"`, `\"Cmd+K\"`, `\"Escape\"`. Pass `null`\n  or `undefined` to conditionally disable the shortcut without unmounting.\n- `handler` — `(event: KeyboardEvent) => void`. Can change every render;\n  no need to memoize it.\n- `options`:\n  - `scope?: string` — see [Context-aware scopes](#context-aware-scopes).\n  - `preventDefault?: boolean`\n  - `stopPropagation?: boolean`\n  - `allowInFormFields?: boolean` — by default, shortcuts don't fire while\n    typing in an `<input>`, `<textarea>`, `<select>`, or contenteditable.\n  - `priority?: number` — for multiple shortcuts on the same combo *within\n    the same scope*, higher priority wins. Default `0`.\n\n### `useShortcutScope(scope, active?)`\n\nActivates a named scope while `active` is `true` (default `true`, so it\nactivates for the component's mounted lifetime if you omit it).\n\n### `useShortcutManager()`\n\nReturns the underlying `ShortcutManager` instance — handy for building a\n\"keyboard shortcuts\" help panel via `manager.list()`.\n\n### `ShortcutManager` (framework-agnostic core)\n\nEverything above is a thin React wrapper around this class, which you can\nuse directly in non-React code:\n\n```ts\nimport { ShortcutManager } from \"@dskyle77/shortcut-manager\";\n\nconst manager = new ShortcutManager();\nconst unregister = manager.register(\"Ctrl+K\", () => openCommandPalette());\n\nmanager.pushScope(\"modal\");\nmanager.register(\"Escape\", closeModal, { scope: \"modal\" });\nmanager.popScope(\"modal\");\n```\n\n## Supported key syntax\n\n- Modifiers: `Ctrl`/`Control`, `Cmd`/`Command`/`Meta`/`Win`, `Alt`/`Option`, `Shift`\n  (order doesn't matter — `\"Shift+Ctrl+K\"` and `\"Ctrl+Shift+K\"` are identical)\n- Named keys: `Escape`/`Esc`, `Enter`/`Return`, `Delete`/`Del`, `Space`,\n  arrow keys (`ArrowUp`/`Up`, etc.), and any other `KeyboardEvent.key` value\n- Combine with `+`, e.g. `\"Ctrl+Shift+ArrowUp\"`\n\n## License\n\nMIT\n","readmeFilename":"README.md"}