diff --git a/.changeset/config.json b/.changeset/config.json
index 8bc7e8a..24b29b5 100644
--- a/.changeset/config.json
+++ b/.changeset/config.json
@@ -1,11 +1,13 @@
{
- "$schema": "https://unpkg.com/@changesets/config@3.1.1/schema.json",
+ "$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json",
+ "changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [],
- "linked": [],
+ "linked": [
+ ["@keybindy/core", "@keybindy/react"]
+ ],
"access": "public",
"baseBranch": "main",
- "changelog": false,
"updateInternalDependencies": "patch",
"ignore": ["@tests/*"]
}
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..1786004
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,42 @@
+name: CI
+
+on:
+ push:
+ branches: [main]
+ pull_request:
+ branches: [main]
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ validate:
+ name: Test & Build
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Install pnpm
+ uses: pnpm/action-setup@v4
+ with:
+ version: 10.16.1
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: 20
+ cache: 'pnpm'
+
+ - name: Install dependencies
+ run: pnpm install --frozen-lockfile
+
+ - name: Build Packages
+ run: pnpm build
+
+ - name: Run Tests
+ run: pnpm test
+
+ - name: Run Lint
+ run: pnpm lint
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..c088622
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,57 @@
+name: Release
+
+on:
+ push:
+ branches:
+ - main
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: false
+
+jobs:
+ release:
+ name: Release & Publish
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+ pull-requests: write
+ id-token: write
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Install pnpm
+ uses: pnpm/action-setup@v4
+ with:
+ version: 10.16.1
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: 20
+ cache: 'pnpm'
+ registry-url: 'https://registry.npmjs.org'
+
+ - name: Install dependencies
+ run: pnpm install --frozen-lockfile
+
+ - name: Run Tests & Build
+ run: |
+ pnpm test
+ pnpm build
+
+ - name: Create Release PR or Publish to NPM
+ id: changesets
+ uses: changesets/action@v1
+ with:
+ publish: pnpm release
+ version: pnpm version
+ title: 'chore(release): Version Packages'
+ commit: 'chore(release): Version Packages'
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
+ NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
diff --git a/README.md b/README.md
index 007e7ee..bcb6cda 100644
--- a/README.md
+++ b/README.md
@@ -1,101 +1,102 @@
# Keybindy
-**A lightweight, fast, and framework-agnostic TypeScript library for managing keyboard shortcuts in modern web applications.**
+
+ The lightweight, framework-agnostic keyboard shortcut engine for modern web apps.
+ Fast, tiny, type-safe, and zero dependencies.
+
-Keybindy provides a simple yet powerful API to register, manage, and scope keyboard shortcuts with a tiny footprint and zero dependencies. It is designed to be flexible, tree-shakeable, and easy to integrate into any project, from vanilla JavaScript to React.
-
-[](https://opensource.org/licenses/MIT)
+
+
+
+
+
---
-## About Keybindy
-
-Keyboard shortcuts are essential for a productive user experience, but managing them across different components, contexts, and frameworks can be complex. Keybindy solves this by providing a robust, centralized, and framework-agnostic solution.
+## π― About Keybindy
-- **Tiny & Dependency-Free**: Approximately 2KB gzipped, with no external dependencies.
-- **Framework-Agnostic**: Works with Vanilla JS, React, Vue, Svelte, and any other framework.
-- **Simple yet Powerful API**: Clean and intuitive methods to register, scope, and manage shortcuts.
-- **Type-Safe**: Written entirely in TypeScript for a great developer experience.
-- **SSR-Safe**: Designed to work flawlessly in server-side rendering environments like Next.js.
+Handling keyboard shortcuts in modern web applications shouldn't require messy event listeners, broken modal state, or bulky dependencies.
-## Packages
+**Keybindy** is a centralized, high-performance keyboard shortcut engine (~2KB gzipped) designed to handle everything from simple hotkeys to complex Figma/Vim-style layered tools with ease:
-This repository is a monorepo managed by [pnpm](https://pnpm.io/). It contains the following packages:
+- **Simultaneous, Hold, & Sequential Chains**: Support for chords (`Ctrl+Shift+P`), holdable push-to-talk keys, and Vim sequences (`G` then `D`).
+- **Modal Isolation & Cascading Tools**: Strict scope isolation (`default` mode) or layered priority overrides (`cascade` mode).
+- **Smart Input Detection**: Automatically ignores keystrokes when typing in inputs, textareas, and contenteditables (with per-shortcut overrides).
+- **Lifecycle Safety in React**: Stable ref architecture that completely eliminates hook "blinking" and stale closures.
+- **Zero Dependencies & 100% Type-Safe**: Written in TypeScript with rich autocomplete.
-| Package | Version | Description | Docs |
-| ------------------------------------- | --------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | ---------------------------------- |
-| [`@keybindy/core`](./packages/core) | [](https://www.npmjs.com/package/@keybindy/core) | The core, framework-agnostic shortcut management library. | [Docs](./packages/core/README.md) |
-| [`@keybindy/react`](./packages/react) | [](https://www.npmjs.com/package/@keybindy/react) | React components and hooks for seamless integration with React applications. | [Docs](./packages/react/README.md) |
+---
-## Installation
+## π¦ Packages
-Install the package you need using your preferred package manager:
+| Package | Version | Description | Docs |
+| :--- | :--- | :--- | :--- |
+| [`@keybindy/core`](./packages/core) | [](https://www.npmjs.com/package/@keybindy/core) | Core framework-agnostic shortcut engine. | [Core Docs](./packages/core/README.md) |
+| [`@keybindy/react`](./packages/react) | [](https://www.npmjs.com/package/@keybindy/react) | Modern React hooks & components (`useShortcut`, `useShortcuts`, ` `). | [React Docs](./packages/react/README.md) |
-```bash
-# For the core library
-pnpm add @keybindy/core
-
-# For the React integration
-pnpm add @keybindy/react
-```
+---
-## Getting Started
+## π Quick Look
-Here is a quick example of how to use `@keybindy/core` in a vanilla JavaScript project.
+### Vanilla JS / Framework-Agnostic (`@keybindy/core`)
```ts
import ShortcutManager from '@keybindy/core';
-// 1. Initialize the manager
-const manager = new ShortcutManager();
+const shortcuts = new ShortcutManager({ ignoreInputs: true });
-// 2. Define a handler
-const saveDocument = () => {
- console.log('Document saved!');
-};
+// Register shortcut
+shortcuts.register(['Ctrl', 'S'], () => {
+ saveDocument();
+}, { preventDefault: true });
-// 3. Register the shortcut
-manager.register(['Ctrl', 'S'], saveDocument, {
- // Optional: prevent the browser's default save action
- preventDefault: true,
-});
+// Sequential Vim-style shortcut (G then D)
+shortcuts.register(['G', 'D'], () => {
+ navigateToDashboard();
+}, { sequential: true });
```
-For framework-specific usage, please see the `README.md` file in the corresponding package directory.
+### React (`@keybindy/react`)
-## Development and Contributing
+```tsx
+import { useShortcut, useShortcuts } from '@keybindy/react';
-This project is a monorepo, and we welcome contributions! To get started with local development, follow these steps:
+function Canvas() {
+ const [scale, setScale] = useState(1);
-1. **Clone the repository:**
+ // β‘οΈ Always accesses latest state with ZERO re-registration flickering
+ useShortcut(['Ctrl', '+'], () => setScale(s => s + 0.1), {
+ preventDefault: true,
+ });
- ```bash
- git clone https://github.com/keybindyjs/keybindy.git
- cd keybindy
- ```
-
-2. **Install dependencies:**
+ useShortcuts([
+ { keys: ['Space'], handler: (e, state) => setPanning(state === 'down'), options: { hold: true } },
+ { keys: ['Esc'], handler: deselectAll, options: { enableInInput: true } },
+ ]);
+}
+```
- This project uses `pnpm` for workspace management. Install it if you haven't already (`npm install -g pnpm`), then run:
+---
- ```bash
- pnpm install
- ```
+## π Local Development & Testing
-3. **Run tests:**
+```bash
+# 1. Clone repo
+git clone https://github.com/keybindyjs/keybindy.git
+cd keybindy
- You can run tests for all packages or for a specific one.
+# 2. Install dependencies
+pnpm install
- ```bash
- # Run all tests
- pnpm test
+# 3. Run all unit tests
+pnpm test
- # Run tests for a specific package (e.g., @keybindy/react)
- pnpm --filter @keybindy/react test
- ```
+# 4. Build packages
+pnpm build
+```
-We encourage you to open an issue or a pull request if you have ideas for improvement or have found a bug.
+---
-## License
+## π License
-This project is licensed under the MIT License. See the [LICENSE](./LICENSE.md) file for details.
+MIT Β© [Keybindy Contributors](https://github.com/keybindyjs/keybindy)
diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md
index 10358fe..32f904c 100644
--- a/packages/core/CHANGELOG.md
+++ b/packages/core/CHANGELOG.md
@@ -1,5 +1,23 @@
# @keybindy/core
+## [2.0.0] - 2026-08-30
+
+### Major Features & Improvements
+
+- **Cascading Scope Mode & Priority Weights**:
+ - Added `scopeMode: 'default' | 'cascade'`. In `'default'` mode, only the topmost active scope is enabled (ideal for strict modal isolation). In `'cascade'` mode, all active scopes work together.
+ - Added dynamic numeric priority weighting (`setScopePriority(scope, priority)`, `removeScopePriority(scope)`). Common key collisions are resolved by priority weight (e.g. `100 > 10 > 0`) or stack order, while non-colliding parent shortcuts continue executing.
+- **Guard & Interceptor Middlewares**:
+ - Added `beforeEach((shortcut, event) => boolean | void, options?)` guard hook. Returning `false` safely cancels shortcut execution.
+ - Added `afterEach((shortcut, event) => void, options?)` interceptor hook for analytics, telemetry, and side-effects.
+- **Smart Input Target Handling**:
+ - Added automatic ignoring of keystrokes when typing inside ` `, `