@remcostoeten/use-shortcut 1.3.0 → 2.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/CHANGELOG.md +17 -0
- package/README.md +65 -307
- package/dist/cli/index.mjs +88 -216
- package/dist/index.d.mts +84 -35
- package/dist/index.d.ts +84 -35
- package/dist/index.js +404 -264
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +405 -261
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -2
- package/src/__tests__/features.test.ts +0 -133
- package/src/builder.ts +0 -618
- package/src/constants.ts +0 -114
- package/src/formatter.ts +0 -82
- package/src/hook.ts +0 -213
- package/src/index.ts +0 -59
- package/src/parser.ts +0 -119
- package/src/types.ts +0 -270
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,23 @@ 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
|
+
## [2.0.1] - 2026-03-11
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- Re-exported `useShortcutMap`, `registerShortcutMap`, `createShortcutGroup`, and `useShortcutGroup` from the public package entrypoint.
|
|
13
|
+
- Re-exported shortcut map and shortcut group types from the public package entrypoint.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- Updated the README to document the public shortcut map API.
|
|
18
|
+
|
|
19
|
+
## [2.0.0] - 2026-03-04
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- Removed non-React public entry points (`createShortcut`, `createShortcutMap`) to focus package API on React-first usage.
|
|
24
|
+
|
|
8
25
|
## [1.3.0] - 2026-02-28
|
|
9
26
|
|
|
10
27
|
### Added
|
package/README.md
CHANGED
|
@@ -1,331 +1,89 @@
|
|
|
1
1
|
# @remcostoeten/use-shortcut
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
[](https://opensource.org/licenses/MIT)
|
|
5
|
-
[](https://www.typescriptlang.org/)
|
|
3
|
+
WIP keyboard shortcut library for React with a chainable API.
|
|
6
4
|
|
|
7
|
-
|
|
5
|
+
## Status
|
|
8
6
|
|
|
9
|
-
|
|
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
|
-
|
|
12
|
-
const $ = useShortcut()
|
|
13
|
-
|
|
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
|
|
36
|
-
|
|
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
|
-
```
|
|
46
|
-
|
|
47
|
-
### Copy-paste (shadcn-style)
|
|
48
|
-
|
|
49
|
-
```bash
|
|
50
|
-
npx @remcostoeten/use-shortcut init
|
|
51
|
-
# or
|
|
52
|
-
bunx @remcostoeten/use-shortcut init
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
This copies the source files directly into your project at `hooks/use-shortcut/`.
|
|
56
|
-
|
|
57
|
-
### App Architecture Scaffold (React/Next)
|
|
58
|
-
|
|
59
|
-
Generate a scalable, reusable shortcut architecture with typed registry, scopes, provider state/actions, and persistent user bindings:
|
|
60
|
-
|
|
61
|
-
```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:
|
|
10
|
+
## Implemented Features
|
|
158
11
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
|
12
|
+
- Chainable shortcut builder: `$.mod.key("k").on(handler)`
|
|
13
|
+
- Bulk shortcut maps: `useShortcutMap()` and `registerShortcutMap()`
|
|
14
|
+
- Modifier support: `ctrl`, `shift`, `alt`, `cmd`, `mod`
|
|
15
|
+
- Sequence support: `$.key("g").then("d")`
|
|
16
|
+
- Scope-aware shortcuts:
|
|
17
|
+
- Register with `.in("editor")`
|
|
18
|
+
- Runtime controls: `setScopes`, `enableScope`, `disableScope`, `getScopes`, `isScopeActive`
|
|
19
|
+
- Exception predicates/presets with `.except(...)`
|
|
20
|
+
- Recording mode: `$.record({ timeoutMs })`
|
|
21
|
+
- Conflict detection (`exact`, `sequence-prefix`)
|
|
22
|
+
- Priority ordering and `stopOnMatch`
|
|
23
|
+
- Global guard/filter support via `eventFilter`
|
|
24
|
+
- React entry point:
|
|
25
|
+
- `useShortcut`
|
|
26
|
+
- `useShortcutMap`
|
|
27
|
+
- `useShortcutGroup`
|
|
189
28
|
|
|
190
|
-
|
|
29
|
+
## API Intention (Consumer-Facing)
|
|
191
30
|
|
|
192
|
-
|
|
193
|
-
|
|
31
|
+
- `useShortcut(options?)`
|
|
32
|
+
- Main React hook. Use this for the chainable API (`$.mod.key("s").on(...)`).
|
|
33
|
+
- `useShortcutMap(shortcutMap, options?)`
|
|
34
|
+
- React-safe bulk registration for render paths where a declarative object is cleaner than multiple `.on()` calls.
|
|
35
|
+
- `registerShortcutMap(builder, shortcutMap)`
|
|
36
|
+
- Imperative bulk registration helper when you already have a `useShortcut()` builder.
|
|
194
37
|
|
|
195
|
-
|
|
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
|
-
```
|
|
38
|
+
Internal helpers follow underscore naming (for example `_createShortcutBuilder`, `_canonicalizeParsed`) and are not re-exported from `src/index.ts`.
|
|
203
39
|
|
|
204
|
-
|
|
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
|
|
40
|
+
## Shortcut Map Example
|
|
229
41
|
|
|
230
42
|
```tsx
|
|
231
43
|
import { useShortcutMap } from "@remcostoeten/use-shortcut"
|
|
232
44
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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()
|
|
45
|
+
function App() {
|
|
46
|
+
useShortcutMap(
|
|
47
|
+
{
|
|
48
|
+
openPalette: {
|
|
49
|
+
keys: "mod+k",
|
|
50
|
+
handler: () => openPalette(),
|
|
51
|
+
options: { preventDefault: true },
|
|
52
|
+
},
|
|
53
|
+
closePalette: {
|
|
54
|
+
keys: "escape",
|
|
55
|
+
handler: () => closePalette(),
|
|
56
|
+
},
|
|
57
|
+
toggleSidebar: {
|
|
58
|
+
keys: "g then s",
|
|
59
|
+
handler: () => toggleSidebar(),
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
{ ignoreInputs: false },
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
return <div>Shortcuts ready</div>
|
|
66
|
+
}
|
|
303
67
|
```
|
|
304
68
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
```tsx
|
|
308
|
-
import { formatShortcut } from "@remcostoeten/use-shortcut"
|
|
69
|
+
If you already have a builder from `useShortcut()`, you can bulk register with `registerShortcutMap($, shortcutMap)` and unbind the returned handles on cleanup.
|
|
309
70
|
|
|
310
|
-
|
|
311
|
-
formatShortcut("ctrl+shift+p") // "⌃⇧P" on Mac, "Ctrl+Shift+P" on Windows
|
|
312
|
-
```
|
|
71
|
+
## Architecture Notes
|
|
313
72
|
|
|
314
|
-
|
|
73
|
+
- Core runtime lives in `src/builder.ts`
|
|
74
|
+
- Parsing/formatting are isolated in `src/parser.ts` and `src/formatter.ts`
|
|
75
|
+
- React bindings and map helpers live in `src/hook.ts`
|
|
76
|
+
- Type contracts live in `src/types.ts`
|
|
77
|
+
- CLI scaffold/copy commands live under `cli/`
|
|
315
78
|
|
|
316
|
-
|
|
79
|
+
## Development
|
|
317
80
|
|
|
318
|
-
```
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
ShortcutHandler,
|
|
323
|
-
HandlerOptions,
|
|
324
|
-
ActionKey,
|
|
325
|
-
ModifierName,
|
|
326
|
-
} from "@remcostoeten/use-shortcut"
|
|
81
|
+
```bash
|
|
82
|
+
bun run typecheck
|
|
83
|
+
bun run test
|
|
84
|
+
bun run build
|
|
327
85
|
```
|
|
328
86
|
|
|
329
87
|
## License
|
|
330
88
|
|
|
331
|
-
MIT
|
|
89
|
+
MIT
|