@remcostoeten/use-shortcut 1.3.0 → 2.0.0

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/CHANGELOG.md CHANGED
@@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [Unreleased]
9
+
10
+ ## [2.0.0] - 2026-03-04
11
+
12
+ ### Changed
13
+
14
+ - Removed non-React public entry points (`createShortcut`, `createShortcutMap`) to focus package API on React-first usage.
15
+
8
16
  ## [1.3.0] - 2026-02-28
9
17
 
10
18
  ### Added
package/README.md CHANGED
@@ -1,331 +1,51 @@
1
1
  # @remcostoeten/use-shortcut
2
2
 
3
- [![npm version](https://img.shields.io/npm/v/@remcostoeten/use-shortcut.svg)](https://www.npmjs.com/package/@remcostoeten/use-shortcut)
4
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
- [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)](https://www.typescriptlang.org/)
3
+ WIP keyboard shortcut library for React with a chainable API.
6
4
 
7
- Chainable keyboard shortcuts for React with **perfect TypeScript intellisense**.
5
+ ## Status
8
6
 
9
- [**View Live Demo & Playground**](https://use-shortcuts.vercel.app)
7
+ - Focus right now: runtime architecture and DX refinement
8
+ - Documentation scope: feature/status overview only (full API docs will be expanded later)
10
9
 
11
- ```tsx
12
- const $ = useShortcut()
10
+ ## Implemented Features
13
11
 
14
- // Chainable, typesafe, platform-aware
15
- $.cmd.shift.key("s").on(() => save())
16
- $.mod.key("k").on(() => search())
17
- $.key("/").except("typing").on(() => focusSearch())
18
- ```
19
-
20
- ## Features
21
-
22
- - **Chainable API** - Fluent, readable shortcut definitions
23
- - **Sequences/chords** - Multi-step bindings like `$.key("g").then("d")`
24
- - **Named scopes** - Activate/deactivate shortcut contexts (`editor`, `navigation`, etc.)
25
- - **Conflict detection** - Detect exact and sequence-prefix overlaps
26
- - **Shortcut maps** - Register many shortcuts at once with `useShortcutMap()`
27
- - **Recording mode** - Capture the next key combo for custom keybind UIs
28
- - **Priorities** - Deterministic ordering for overlapping shortcuts
29
- - **Groups** - Register multiple shortcuts and unbind them together
30
- - **Event filtering** - Global event guard via `eventFilter`
31
- - **Perfect TypeScript** - Intellisense at every step
32
- - **Cross-platform** - `mod` = ⌘ on Mac, Ctrl on Windows/Linux
33
- - **Context-aware** - Skip shortcuts in inputs with `.except()`
34
- - **Zero dependencies** - Only React as peer dependency
35
- - **Tiny** - ~3KB gzipped
12
+ - Chainable shortcut builder: `$.mod.key("k").on(handler)`
13
+ - Modifier support: `ctrl`, `shift`, `alt`, `cmd`, `mod`
14
+ - Sequence support: `$.key("g").then("d")`
15
+ - Scope-aware shortcuts:
16
+ - Register with `.in("editor")`
17
+ - Runtime controls: `setScopes`, `enableScope`, `disableScope`, `getScopes`, `isScopeActive`
18
+ - Exception predicates/presets with `.except(...)`
19
+ - Recording mode: `$.record({ timeoutMs })`
20
+ - Conflict detection (`exact`, `sequence-prefix`)
21
+ - Priority ordering and `stopOnMatch`
22
+ - Global guard/filter support via `eventFilter`
23
+ - React entry point:
24
+ - `useShortcut`
36
25
 
37
- ## Installation
38
-
39
- ### npm/pnpm/bun
40
-
41
- ```bash
42
- npm install @remcostoeten/use-shortcut
43
- pnpm add @remcostoeten/use-shortcut
44
- bun add @remcostoeten/use-shortcut
45
- ```
26
+ ## API Intention (Consumer-Facing)
46
27
 
47
- ### Copy-paste (shadcn-style)
28
+ - `useShortcut(options?)`
29
+ - Main React hook. Use this for the chainable API (`$.mod.key("s").on(...)`).
48
30
 
49
- ```bash
50
- npx @remcostoeten/use-shortcut init
51
- # or
52
- bunx @remcostoeten/use-shortcut init
53
- ```
31
+ Internal helpers follow underscore naming (for example `_createShortcutBuilder`, `_canonicalizeParsed`) and are not re-exported from `src/index.ts`.
54
32
 
55
- This copies the source files directly into your project at `hooks/use-shortcut/`.
33
+ ## Architecture Notes
56
34
 
57
- ### App Architecture Scaffold (React/Next)
35
+ - Core runtime lives in `src/builder.ts`
36
+ - Parsing/formatting are isolated in `src/parser.ts` and `src/formatter.ts`
37
+ - React bindings and map helpers live in `src/hook.ts`
38
+ - Type contracts live in `src/types.ts`
39
+ - CLI scaffold/copy commands live under `cli/`
58
40
 
59
- Generate a scalable, reusable shortcut architecture with typed registry, scopes, provider state/actions, and persistent user bindings:
41
+ ## Development
60
42
 
61
43
  ```bash
62
- # Next.js-oriented scaffold (default)
63
- npx @remcostoeten/use-shortcut scaffold
64
-
65
- # React scaffold
66
- npx @remcostoeten/use-shortcut scaffold --framework react
67
-
68
- # Custom location
69
- npx @remcostoeten/use-shortcut scaffold --target src --dir shortcuts
70
- ```
71
-
72
- Generated structure:
73
-
74
- ```txt
75
- src/shortcuts/
76
- index.ts
77
- provider.tsx
78
- registry.ts
79
- runtime.ts
80
- scopes.ts
81
- storage.ts
82
- types.ts
83
- README.md
84
- ```
85
-
86
- Architecture docs:
87
- - Human guide: [`docs/app-architecture.md`](./docs/app-architecture.md)
88
- - LLM guide: [`docs/app-architecture.llm.md`](./docs/app-architecture.llm.md)
89
-
90
- ## Quick Start
91
-
92
- ```tsx
93
- "use client"
94
-
95
- import { useShortcut } from "@remcostoeten/use-shortcut"
96
-
97
- export function App() {
98
- const $ = useShortcut()
99
-
100
- $.cmd.key("s").on(() => {
101
- console.log("Save!")
102
- })
103
-
104
- $.mod.key("k").on(() => {
105
- console.log("Search!")
106
- })
107
-
108
- return <div>Press ⌘+S or ⌘+K</div>
109
- }
110
- ```
111
-
112
- ## API
113
-
114
- ### Modifiers
115
-
116
- Chain modifiers before calling `.key()`:
117
-
118
- ```tsx
119
- $.ctrl.key("s") // Ctrl+S
120
- $.shift.key("enter") // Shift+Enter
121
- $.alt.key("n") // Alt+N
122
- $.cmd.key("k") // ⌘+K (Mac) or Ctrl+K (Windows)
123
- $.mod.key("k") // Cross-platform: ⌘ on Mac, Ctrl on Windows/Linux
124
-
125
- // Multiple modifiers
126
- $.ctrl.shift.key("p") // Ctrl+Shift+P
127
- $.cmd.shift.alt.key("a") // ⌘+Shift+Alt+A
128
- ```
129
-
130
- ### Keys
131
-
132
- Supports all standard keys:
133
-
134
- ```tsx
135
- // Letters
136
- $.mod.key("s") // a-z
137
-
138
- // Numbers
139
- $.mod.key("1") // 0-9
140
-
141
- // Function keys
142
- $.key("f1") // f1-f12
143
-
144
- // Special keys
145
- $.key("escape") // escape, enter, space, tab
146
- $.key("backspace") // backspace, delete
147
- $.mod.key("up") // up, down, left, right
148
- $.key("home") // home, end, pageup, pagedown
149
-
150
- // Symbols
151
- $.mod.key("slash") // slash, backslash, comma, period
152
- $.mod.key("/") // Also works with actual symbol
153
- ```
154
-
155
- ### Exception Handling
156
-
157
- Skip shortcuts in certain contexts:
158
-
159
- ```tsx
160
- // Built-in presets
161
- $.key("/").except("input").on(handler) // Skip in <input>, <textarea>, <select>
162
- $.key("/").except("editable").on(handler) // Skip in contenteditable
163
- $.key("/").except("typing").on(handler) // Skip in any text input
164
- $.key("escape").except("modal").on(handler) // Skip when modal is open
165
- $.key("enter").except("disabled").on(handler) // Skip on disabled elements
166
-
167
- // Multiple presets
168
- $.key("/").except(["input", "modal"]).on(handler)
169
-
170
- // Custom predicate
171
- $.key("k").except((e) => {
172
- return e.target.classList.contains("no-shortcuts")
173
- }).on(handler)
174
- ```
175
-
176
- ### Handler Options
177
-
178
- ```tsx
179
- $.mod.key("s").on(save, {
180
- preventDefault: true, // Prevent browser default (default: true)
181
- stopPropagation: false, // Stop event bubbling (default: false)
182
- delay: 100, // Delay before firing (ms)
183
- description: "Save doc", // For accessibility
184
- disabled: false, // Temporarily disable
185
- })
186
- ```
187
-
188
- ### Result Object
189
-
190
- `.on()` returns a result object:
191
-
192
- ```tsx
193
- const save = $.mod.key("s").on(handleSave)
194
-
195
- save.display // "⌘S" on Mac, "Ctrl+S" on Windows
196
- save.combo // "cmd+s"
197
- save.isEnabled // true/false
198
- save.enable() // Enable the shortcut
199
- save.disable() // Disable the shortcut
200
- save.unbind() // Remove the shortcut
201
- save.trigger() // Programmatically trigger
202
- ```
203
-
204
- ### Sequences / Chords
205
-
206
- ```tsx
207
- // GitHub-style: press g, then d
208
- $.key("g").then("d").on(() => goToDashboard())
209
-
210
- // Steps can include modifiers too
211
- $.key("g").then("shift+d").on(() => openDebug())
212
- ```
213
-
214
- ### Named Scopes
215
-
216
- ```tsx
217
- const $ = useShortcut({ activeScopes: "navigation" })
218
-
219
- $.in("navigation").key("g").then("d").on(() => goToDashboard())
220
- $.in("editor").mod.key("s").on(() => saveFile())
221
-
222
- $.setScopes("editor") // enable editor scope only
223
- $.enableScope("navigation") // add a second active scope
224
- $.disableScope("editor") // remove one scope
225
- $.getScopes() // ["navigation"]
226
- ```
227
-
228
- ### Shortcut Maps
229
-
230
- ```tsx
231
- import { useShortcutMap } from "@remcostoeten/use-shortcut"
232
-
233
- useShortcutMap({
234
- save: { keys: "mod+s", handler: () => save() },
235
- undo: { keys: "mod+z", handler: () => undo() },
236
- dashboard: { keys: ["g", "d"], handler: () => goToDashboard() },
237
- })
238
- ```
239
-
240
- ### Recording Mode
241
-
242
- ```tsx
243
- const combo = await $.record({ timeoutMs: 5000 })
244
- // e.g. "ctrl+k" or "cmd+k"
245
- ```
246
-
247
- ### Shortcut Groups
248
-
249
- ```tsx
250
- import { createShortcutGroup } from "@remcostoeten/use-shortcut"
251
-
252
- const group = createShortcutGroup()
253
-
254
- group.add($.mod.key("k").on(openPalette))
255
- group.add($.mod.key("p").on(openSearch))
256
-
257
- // Later, cleanup all at once
258
- group.unbindAll()
259
- ```
260
-
261
- ### Hook Options
262
-
263
- ```tsx
264
- const $ = useShortcut({
265
- debug: true, // Log all shortcuts to console
266
- delay: 0, // Global delay for all shortcuts
267
- ignoreInputs: true, // Ignore in form elements (default: true)
268
- disabled: false, // Disable all shortcuts
269
- eventType: "keydown", // or "keyup"
270
- target: window, // Custom event target
271
- activeScopes: "editor", // Active named scopes
272
- sequenceTimeout: 800, // ms to complete sequence
273
- conflictWarnings: true, // Warn on overlaps
274
- onConflict: (conflict) => {
275
- console.warn(conflict)
276
- },
277
- eventFilter: (event) => !event.isComposing,
278
- })
279
- ```
280
-
281
- ### Handler Options (Advanced)
282
-
283
- ```tsx
284
- $.mod.key("k").on(openPalette, {
285
- priority: 10, // higher runs first
286
- stopOnMatch: true, // prevent lower priority handlers from running
287
- })
288
- ```
289
-
290
- ## Vanilla JS (Non-React)
291
-
292
- ```tsx
293
- import { createShortcut } from "@remcostoeten/use-shortcut"
294
-
295
- const $ = createShortcut()
296
-
297
- const save = $.mod.key("s").on(() => {
298
- console.log("Saved!")
299
- })
300
-
301
- // Clean up when done
302
- save.unbind()
303
- ```
304
-
305
- ## Display Formatting
306
-
307
- ```tsx
308
- import { formatShortcut } from "@remcostoeten/use-shortcut"
309
-
310
- formatShortcut("cmd+s") // "⌘S" on Mac, "Ctrl+S" on Windows
311
- formatShortcut("ctrl+shift+p") // "⌃⇧P" on Mac, "Ctrl+Shift+P" on Windows
312
- ```
313
-
314
- ## TypeScript
315
-
316
- Full type definitions with intellisense:
317
-
318
- ```tsx
319
- import type {
320
- ShortcutBuilder,
321
- ShortcutResult,
322
- ShortcutHandler,
323
- HandlerOptions,
324
- ActionKey,
325
- ModifierName,
326
- } from "@remcostoeten/use-shortcut"
44
+ bun run typecheck
45
+ bun run test
46
+ bun run build
327
47
  ```
328
48
 
329
49
  ## License
330
50
 
331
- MIT © Remco Stoeten
51
+ MIT