@remcostoeten/use-shortcut 2.1.0 → 2.3.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/README.md +120 -40
- package/dist/constants.d.ts +36 -0
- package/dist/constants.js +1 -0
- package/dist/constants.mjs +1 -0
- package/dist/formatter.d.ts +30 -0
- package/dist/formatter.js +1 -0
- package/dist/formatter.mjs +1 -0
- package/dist/index.d.ts +5 -468
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/dist/parser.d.ts +47 -0
- package/dist/parser.js +1 -0
- package/dist/parser.mjs +1 -0
- package/dist/react.d.ts +79 -0
- package/dist/react.js +1 -0
- package/dist/react.mjs +1 -0
- package/dist/types-yQWKtHDh.d.ts +320 -0
- package/package.json +46 -5
- package/dist/cli/index.mjs +0 -469
package/dist/cli/index.mjs
DELETED
|
@@ -1,469 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import{existsSync as h,mkdirSync as f,readFileSync as A,writeFileSync as x}from"fs";import{join as l,dirname as B}from"path";import{fileURLToPath as I}from"url";function m(t){return{"scopes.ts":`/** App-defined scope catalog used by the scaffolded provider. */
|
|
3
|
-
export const shortcutScopes = ["global", "navigation", "editor", "modal"] as const
|
|
4
|
-
|
|
5
|
-
export type ShortcutScope = (typeof shortcutScopes)[number]
|
|
6
|
-
|
|
7
|
-
/** Default active scopes at provider boot. */
|
|
8
|
-
export const defaultActiveScopes: ShortcutScope[] = ["global", "navigation"]
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Normalizes a scope input into an array form.
|
|
12
|
-
*
|
|
13
|
-
* @param scopes - One or many scope names
|
|
14
|
-
* @returns Array of scope names
|
|
15
|
-
*/
|
|
16
|
-
export function normalizeScopes(scopes: ShortcutScope | ShortcutScope[]): ShortcutScope[] {
|
|
17
|
-
return Array.isArray(scopes) ? scopes : [scopes]
|
|
18
|
-
}
|
|
19
|
-
`,"registry.ts":`import type { HandlerOptions } from "@remcostoeten/use-shortcut"
|
|
20
|
-
import type { ShortcutScope } from "./scopes"
|
|
21
|
-
|
|
22
|
-
export type ShortcutDefinition = {
|
|
23
|
-
description: string
|
|
24
|
-
defaultKeys: string | string[]
|
|
25
|
-
scopes: ShortcutScope[]
|
|
26
|
-
options?: Omit<HandlerOptions, "scopes">
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export const shortcutRegistry = {
|
|
30
|
-
openCommandPalette: {
|
|
31
|
-
description: "Open global command palette",
|
|
32
|
-
defaultKeys: "mod+k",
|
|
33
|
-
scopes: ["global", "navigation"],
|
|
34
|
-
},
|
|
35
|
-
saveDocument: {
|
|
36
|
-
description: "Save the active editor document",
|
|
37
|
-
defaultKeys: "mod+s",
|
|
38
|
-
scopes: ["editor"],
|
|
39
|
-
},
|
|
40
|
-
goDashboard: {
|
|
41
|
-
description: "Navigate to dashboard (vim style sequence)",
|
|
42
|
-
defaultKeys: ["g", "d"],
|
|
43
|
-
scopes: ["navigation"],
|
|
44
|
-
},
|
|
45
|
-
closeOverlay: {
|
|
46
|
-
description: "Close active overlay",
|
|
47
|
-
defaultKeys: "escape",
|
|
48
|
-
scopes: ["global", "modal"],
|
|
49
|
-
},
|
|
50
|
-
} as const satisfies Record<string, ShortcutDefinition>
|
|
51
|
-
|
|
52
|
-
export type ShortcutActionId = keyof typeof shortcutRegistry
|
|
53
|
-
export type ShortcutBindings = Record<ShortcutActionId, string | string[]>
|
|
54
|
-
`,"types.ts":`import type { ShortcutActionId, ShortcutBindings } from "./registry"
|
|
55
|
-
import type { ShortcutScope } from "./scopes"
|
|
56
|
-
|
|
57
|
-
export type ShortcutHandlers = Record<ShortcutActionId, (event: KeyboardEvent) => void>
|
|
58
|
-
|
|
59
|
-
export type ShortcutState = {
|
|
60
|
-
activeScopes: ShortcutScope[]
|
|
61
|
-
bindings: ShortcutBindings
|
|
62
|
-
enabled: boolean
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export type ShortcutActions = {
|
|
66
|
-
setScopes: (scopes: ShortcutScope | ShortcutScope[]) => void
|
|
67
|
-
enableScope: (scope: ShortcutScope) => void
|
|
68
|
-
disableScope: (scope: ShortcutScope) => void
|
|
69
|
-
setBinding: (actionId: ShortcutActionId, keys: string | string[]) => void
|
|
70
|
-
resetBinding: (actionId: ShortcutActionId) => void
|
|
71
|
-
resetBindings: () => void
|
|
72
|
-
setEnabled: (enabled: boolean) => void
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export type ShortcutMeta = {
|
|
76
|
-
hasBindingOverrides: boolean
|
|
77
|
-
availableActions: ShortcutActionId[]
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export type ShortcutContextValue = {
|
|
81
|
-
state: ShortcutState
|
|
82
|
-
actions: ShortcutActions
|
|
83
|
-
meta: ShortcutMeta
|
|
84
|
-
}
|
|
85
|
-
`,"runtime.ts":`import type { HandlerOptions, ShortcutMap } from "@remcostoeten/use-shortcut"
|
|
86
|
-
import { shortcutRegistry, type ShortcutActionId, type ShortcutBindings } from "./registry"
|
|
87
|
-
import type { ShortcutHandlers } from "./types"
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Returns a fresh binding object seeded from \`shortcutRegistry\` defaults.
|
|
91
|
-
*
|
|
92
|
-
* @returns Default shortcut bindings for every registered action
|
|
93
|
-
*/
|
|
94
|
-
export function createDefaultShortcutBindings(): ShortcutBindings {
|
|
95
|
-
const bindings = {} as ShortcutBindings
|
|
96
|
-
|
|
97
|
-
for (const actionId of Object.keys(shortcutRegistry) as ShortcutActionId[]) {
|
|
98
|
-
bindings[actionId] = shortcutRegistry[actionId].defaultKeys
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
return bindings
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Builds a \`ShortcutMap\` by combining current bindings with action handlers.
|
|
106
|
-
* Actions without handlers are skipped.
|
|
107
|
-
*
|
|
108
|
-
* @param bindings - Current binding state (defaults + user overrides)
|
|
109
|
-
* @param handlers - Runtime action handlers from the consuming app
|
|
110
|
-
* @returns Runtime shortcut map consumable by \`registerShortcutMap\`
|
|
111
|
-
*/
|
|
112
|
-
export function buildShortcutMap(bindings: ShortcutBindings, handlers: ShortcutHandlers): ShortcutMap {
|
|
113
|
-
const map: ShortcutMap = {}
|
|
114
|
-
|
|
115
|
-
for (const actionId of Object.keys(shortcutRegistry) as ShortcutActionId[]) {
|
|
116
|
-
const definition = shortcutRegistry[actionId]
|
|
117
|
-
const handler = handlers[actionId]
|
|
118
|
-
|
|
119
|
-
if (!handler) {
|
|
120
|
-
continue
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const definitionOptions: Partial<HandlerOptions> = ("options" in definition && definition.options)
|
|
124
|
-
? definition.options
|
|
125
|
-
: {}
|
|
126
|
-
|
|
127
|
-
map[actionId] = {
|
|
128
|
-
keys: bindings[actionId],
|
|
129
|
-
handler,
|
|
130
|
-
options: {
|
|
131
|
-
...definitionOptions,
|
|
132
|
-
scopes: definition.scopes,
|
|
133
|
-
description: definition.description,
|
|
134
|
-
},
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
return map
|
|
139
|
-
}
|
|
140
|
-
`,"storage.ts":`import type { ShortcutActionId, ShortcutBindings } from "./registry"
|
|
141
|
-
|
|
142
|
-
/** Default localStorage key used by the scaffolded shortcut provider. */
|
|
143
|
-
export const DEFAULT_SHORTCUT_STORAGE_KEY = "app-shortcut-bindings"
|
|
144
|
-
|
|
145
|
-
function _isBindingValue(value: unknown): value is string | string[] {
|
|
146
|
-
if (typeof value === "string") return true
|
|
147
|
-
return Array.isArray(value) && value.every((entry) => typeof entry === "string")
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Loads persisted shortcut binding overrides from localStorage.
|
|
152
|
-
*
|
|
153
|
-
* @param storageKey - Storage key namespace
|
|
154
|
-
* @returns Partial binding overrides keyed by action id
|
|
155
|
-
*/
|
|
156
|
-
export function loadShortcutBindings(storageKey: string): Partial<ShortcutBindings> {
|
|
157
|
-
if (typeof window === "undefined") return {}
|
|
158
|
-
|
|
159
|
-
try {
|
|
160
|
-
const raw = window.localStorage.getItem(storageKey)
|
|
161
|
-
if (!raw) return {}
|
|
162
|
-
|
|
163
|
-
const parsed = JSON.parse(raw) as unknown
|
|
164
|
-
if (!parsed || typeof parsed !== "object") return {}
|
|
165
|
-
|
|
166
|
-
const result: Partial<ShortcutBindings> = {}
|
|
167
|
-
|
|
168
|
-
for (const [actionId, value] of Object.entries(parsed as Record<string, unknown>)) {
|
|
169
|
-
if (!_isBindingValue(value)) continue
|
|
170
|
-
result[actionId as ShortcutActionId] = value
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
return result
|
|
174
|
-
} catch {
|
|
175
|
-
return {}
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Persists all shortcut bindings to localStorage.
|
|
181
|
-
*
|
|
182
|
-
* @param storageKey - Storage key namespace
|
|
183
|
-
* @param bindings - Full current binding set
|
|
184
|
-
*/
|
|
185
|
-
export function saveShortcutBindings(storageKey: string, bindings: ShortcutBindings): void {
|
|
186
|
-
if (typeof window === "undefined") return
|
|
187
|
-
|
|
188
|
-
try {
|
|
189
|
-
window.localStorage.setItem(storageKey, JSON.stringify(bindings))
|
|
190
|
-
} catch {
|
|
191
|
-
// Ignore quota/security errors.
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/**
|
|
196
|
-
* Removes persisted shortcut bindings from localStorage.
|
|
197
|
-
*
|
|
198
|
-
* @param storageKey - Storage key namespace
|
|
199
|
-
*/
|
|
200
|
-
export function clearShortcutBindings(storageKey: string): void {
|
|
201
|
-
if (typeof window === "undefined") return
|
|
202
|
-
|
|
203
|
-
try {
|
|
204
|
-
window.localStorage.removeItem(storageKey)
|
|
205
|
-
} catch {
|
|
206
|
-
// Ignore quota/security errors.
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
`,"provider.tsx":`"use client"
|
|
210
|
-
|
|
211
|
-
import {
|
|
212
|
-
createContext,
|
|
213
|
-
useCallback,
|
|
214
|
-
useContext,
|
|
215
|
-
useEffect,
|
|
216
|
-
useMemo,
|
|
217
|
-
useState,
|
|
218
|
-
type ReactNode,
|
|
219
|
-
} from "react"
|
|
220
|
-
import { registerShortcutMap, useShortcut, type UseShortcutOptions } from "@remcostoeten/use-shortcut"
|
|
221
|
-
import { shortcutRegistry, type ShortcutActionId, type ShortcutBindings } from "./registry"
|
|
222
|
-
import { buildShortcutMap, createDefaultShortcutBindings } from "./runtime"
|
|
223
|
-
import { defaultActiveScopes, normalizeScopes, type ShortcutScope } from "./scopes"
|
|
224
|
-
import {
|
|
225
|
-
DEFAULT_SHORTCUT_STORAGE_KEY,
|
|
226
|
-
clearShortcutBindings,
|
|
227
|
-
loadShortcutBindings,
|
|
228
|
-
saveShortcutBindings,
|
|
229
|
-
} from "./storage"
|
|
230
|
-
import type { ShortcutContextValue, ShortcutHandlers } from "./types"
|
|
231
|
-
|
|
232
|
-
const _ShortcutContext = createContext<ShortcutContextValue | null>(null)
|
|
233
|
-
|
|
234
|
-
function _mergeBindings(defaultBindings: ShortcutBindings, persisted: Partial<ShortcutBindings>): ShortcutBindings {
|
|
235
|
-
const merged = { ...defaultBindings }
|
|
236
|
-
|
|
237
|
-
for (const actionId of Object.keys(defaultBindings) as ShortcutActionId[]) {
|
|
238
|
-
const value = persisted[actionId]
|
|
239
|
-
if (typeof value === "string" || Array.isArray(value)) {
|
|
240
|
-
merged[actionId] = value
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
return merged
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
function _isSameBinding(a: string | string[], b: string | string[]): boolean {
|
|
248
|
-
const left = Array.isArray(a) ? a.join("|") : a
|
|
249
|
-
const right = Array.isArray(b) ? b.join("|") : b
|
|
250
|
-
return left === right
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
function _hasBindingOverrides(bindings: ShortcutBindings): boolean {
|
|
254
|
-
const defaults = createDefaultShortcutBindings()
|
|
255
|
-
|
|
256
|
-
for (const actionId of Object.keys(defaults) as ShortcutActionId[]) {
|
|
257
|
-
if (!_isSameBinding(bindings[actionId], defaults[actionId])) {
|
|
258
|
-
return true
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
return false
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
/** Props for the scaffolded \`ShortcutProvider\`. */
|
|
266
|
-
export type ShortcutProviderProps = {
|
|
267
|
-
children: ReactNode
|
|
268
|
-
handlers: ShortcutHandlers
|
|
269
|
-
initialScopes?: ShortcutScope[]
|
|
270
|
-
initialEnabled?: boolean
|
|
271
|
-
persistBindings?: boolean
|
|
272
|
-
storageKey?: string
|
|
273
|
-
shortcutOptions?: Omit<UseShortcutOptions, "activeScopes" | "disabled">
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
/**
|
|
277
|
-
* App-level provider that binds action handlers, scope state, and optional binding persistence.
|
|
278
|
-
*/
|
|
279
|
-
export function ShortcutProvider({
|
|
280
|
-
children,
|
|
281
|
-
handlers,
|
|
282
|
-
initialScopes = defaultActiveScopes,
|
|
283
|
-
initialEnabled = true,
|
|
284
|
-
persistBindings = true,
|
|
285
|
-
storageKey = DEFAULT_SHORTCUT_STORAGE_KEY,
|
|
286
|
-
shortcutOptions,
|
|
287
|
-
}: ShortcutProviderProps) {
|
|
288
|
-
const [activeScopes, setActiveScopes] = useState<ShortcutScope[]>(initialScopes)
|
|
289
|
-
const [enabled, setEnabled] = useState(initialEnabled)
|
|
290
|
-
const [bindings, setBindings] = useState<ShortcutBindings>(() => createDefaultShortcutBindings())
|
|
291
|
-
|
|
292
|
-
useEffect(() => {
|
|
293
|
-
setEnabled(initialEnabled)
|
|
294
|
-
}, [initialEnabled])
|
|
295
|
-
|
|
296
|
-
useEffect(() => {
|
|
297
|
-
if (!persistBindings) return
|
|
298
|
-
|
|
299
|
-
const persisted = loadShortcutBindings(storageKey)
|
|
300
|
-
setBindings((current) => _mergeBindings(current, persisted))
|
|
301
|
-
}, [persistBindings, storageKey])
|
|
302
|
-
|
|
303
|
-
useEffect(() => {
|
|
304
|
-
if (!persistBindings) return
|
|
305
|
-
saveShortcutBindings(storageKey, bindings)
|
|
306
|
-
}, [bindings, persistBindings, storageKey])
|
|
307
|
-
|
|
308
|
-
const shortcutMap = useMemo(() => buildShortcutMap(bindings, handlers), [bindings, handlers])
|
|
309
|
-
|
|
310
|
-
const $ = useShortcut({
|
|
311
|
-
...shortcutOptions,
|
|
312
|
-
activeScopes,
|
|
313
|
-
disabled: !enabled,
|
|
314
|
-
})
|
|
315
|
-
|
|
316
|
-
useEffect(() => {
|
|
317
|
-
const registrations = registerShortcutMap($, shortcutMap)
|
|
318
|
-
|
|
319
|
-
return () => {
|
|
320
|
-
for (const result of Object.values(registrations)) {
|
|
321
|
-
result.unbind()
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
}, [$, shortcutMap])
|
|
325
|
-
|
|
326
|
-
const setScopes = useCallback((scopes: ShortcutScope | ShortcutScope[]) => {
|
|
327
|
-
setActiveScopes(normalizeScopes(scopes))
|
|
328
|
-
}, [])
|
|
329
|
-
|
|
330
|
-
const enableScope = useCallback((scope: ShortcutScope) => {
|
|
331
|
-
setActiveScopes((current) => {
|
|
332
|
-
if (current.includes(scope)) return current
|
|
333
|
-
return [...current, scope]
|
|
334
|
-
})
|
|
335
|
-
}, [])
|
|
336
|
-
|
|
337
|
-
const disableScope = useCallback((scope: ShortcutScope) => {
|
|
338
|
-
setActiveScopes((current) => current.filter((item) => item !== scope))
|
|
339
|
-
}, [])
|
|
340
|
-
|
|
341
|
-
const setBinding = useCallback((actionId: ShortcutActionId, keys: string | string[]) => {
|
|
342
|
-
setBindings((current) => ({
|
|
343
|
-
...current,
|
|
344
|
-
[actionId]: keys,
|
|
345
|
-
}))
|
|
346
|
-
}, [])
|
|
347
|
-
|
|
348
|
-
const resetBinding = useCallback((actionId: ShortcutActionId) => {
|
|
349
|
-
setBindings((current) => ({
|
|
350
|
-
...current,
|
|
351
|
-
[actionId]: shortcutRegistry[actionId].defaultKeys,
|
|
352
|
-
}))
|
|
353
|
-
}, [])
|
|
354
|
-
|
|
355
|
-
const resetBindings = useCallback(() => {
|
|
356
|
-
setBindings(createDefaultShortcutBindings())
|
|
357
|
-
if (persistBindings) clearShortcutBindings(storageKey)
|
|
358
|
-
}, [persistBindings, storageKey])
|
|
359
|
-
|
|
360
|
-
const contextValue = useMemo<ShortcutContextValue>(
|
|
361
|
-
() => ({
|
|
362
|
-
state: {
|
|
363
|
-
activeScopes,
|
|
364
|
-
bindings,
|
|
365
|
-
enabled,
|
|
366
|
-
},
|
|
367
|
-
actions: {
|
|
368
|
-
setScopes,
|
|
369
|
-
enableScope,
|
|
370
|
-
disableScope,
|
|
371
|
-
setBinding,
|
|
372
|
-
resetBinding,
|
|
373
|
-
resetBindings,
|
|
374
|
-
setEnabled,
|
|
375
|
-
},
|
|
376
|
-
meta: {
|
|
377
|
-
hasBindingOverrides: _hasBindingOverrides(bindings),
|
|
378
|
-
availableActions: Object.keys(shortcutRegistry) as ShortcutActionId[],
|
|
379
|
-
},
|
|
380
|
-
}),
|
|
381
|
-
[
|
|
382
|
-
activeScopes,
|
|
383
|
-
bindings,
|
|
384
|
-
enabled,
|
|
385
|
-
setScopes,
|
|
386
|
-
enableScope,
|
|
387
|
-
disableScope,
|
|
388
|
-
setBinding,
|
|
389
|
-
resetBinding,
|
|
390
|
-
resetBindings,
|
|
391
|
-
],
|
|
392
|
-
)
|
|
393
|
-
|
|
394
|
-
return <_ShortcutContext.Provider value={contextValue}>{children}</_ShortcutContext.Provider>
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
/**
|
|
398
|
-
* Reads the shortcut manager context exposed by \`ShortcutProvider\`.
|
|
399
|
-
*/
|
|
400
|
-
export function useShortcutManager(): ShortcutContextValue {
|
|
401
|
-
const context = useContext(_ShortcutContext)
|
|
402
|
-
|
|
403
|
-
if (!context) {
|
|
404
|
-
throw new Error("useShortcutManager must be used within <ShortcutProvider>")
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
return context
|
|
408
|
-
}
|
|
409
|
-
`,"index.ts":`export { ShortcutProvider, useShortcutManager, type ShortcutProviderProps } from "./provider"
|
|
410
|
-
export { shortcutRegistry, type ShortcutActionId, type ShortcutBindings } from "./registry"
|
|
411
|
-
export { defaultActiveScopes, shortcutScopes, type ShortcutScope } from "./scopes"
|
|
412
|
-
export type {
|
|
413
|
-
ShortcutContextValue,
|
|
414
|
-
ShortcutActions,
|
|
415
|
-
ShortcutHandlers,
|
|
416
|
-
ShortcutMeta,
|
|
417
|
-
ShortcutState,
|
|
418
|
-
} from "./types"
|
|
419
|
-
`,"README.md":`# Shortcut Architecture Scaffold
|
|
420
|
-
|
|
421
|
-
This folder was generated by \`use-shortcut scaffold\`.
|
|
422
|
-
|
|
423
|
-
It follows a scalable architecture with a strict split between:
|
|
424
|
-
|
|
425
|
-
- **Registry (data)**: \`registry.ts\` is the single source of truth for action ids, default keys, scope ownership, and metadata.
|
|
426
|
-
- **Runtime assembly**: \`runtime.ts\` converts registry + current bindings + handlers into a runtime map.
|
|
427
|
-
- **State and actions provider**: \`provider.tsx\` owns scope state, binding overrides, and persistence.
|
|
428
|
-
- **Storage adapter**: \`storage.ts\` isolates persistence so you can swap \`localStorage\` for API/DB.
|
|
429
|
-
- **Typed contract**: \`types.ts\` exposes \`state/actions/meta\` for UI and feature modules.
|
|
430
|
-
|
|
431
|
-
## How To Extend
|
|
432
|
-
|
|
433
|
-
1. Add a new action in \`registry.ts\`.
|
|
434
|
-
2. Implement its handler in your app and pass it via \`handlers\` to \`<ShortcutProvider>\`.
|
|
435
|
-
3. Optionally expose a user-configurable key in your settings UI through \`useShortcutManager().actions.setBinding\`.
|
|
436
|
-
4. Activate scopes from feature boundaries (for example editor route enters \`editor\` scope).
|
|
437
|
-
|
|
438
|
-
## Wiring A React App
|
|
439
|
-
|
|
440
|
-
Scaffold the shortcut files into a React app shell:
|
|
441
|
-
|
|
442
|
-
\`\`\`bash
|
|
443
|
-
npx @remcostoeten/use-shortcut scaffold --framework react --target apps/shortcut-playground/src
|
|
444
|
-
\`\`\`
|
|
445
|
-
|
|
446
|
-
Then wire those generated files into your app root by wrapping your app in \`<ShortcutProvider handlers={...} />\`.
|
|
447
|
-
|
|
448
|
-
${t==="next"?"## Next.js Integration\n\n1. Create a client provider wrapper at `app/shortcut-provider.tsx` and render `<ShortcutProvider />` there.\n2. Render that provider inside `app/layout.tsx` around your app shell.\n3. Keep page/server components pure; shortcut handlers stay in client components.\n":"## React Integration\n\n1. Wrap your app root (for example in `main.tsx`) with `<ShortcutProvider />`.\n2. Keep handlers in a top-level client component and pass them to the provider via `handlers`.\n3. Use `useShortcutManager()` inside feature components to toggle scopes and bindings.\n"}
|
|
449
|
-
## Rules For Scale
|
|
450
|
-
|
|
451
|
-
- Keep handlers side-effect focused and feature-owned; keep the registry declarative.
|
|
452
|
-
- Use scopes instead of conditionals in handlers.
|
|
453
|
-
- Persist only key bindings, not executable handlers.
|
|
454
|
-
- Treat \`registry.ts\` as your architectural contract.
|
|
455
|
-
`}}var k=I(import.meta.url),O=B(k),e={reset:"\x1B[0m",green:"\x1B[32m",cyan:"\x1B[36m",yellow:"\x1B[33m",dim:"\x1B[2m",red:"\x1B[31m"};function r(t,o=e.reset){console.log(`${o}${t}${e.reset}`)}function _(){return l(O,"..","src")}function R(t){return l(process.cwd(),t,"use-shortcut")}function C(t,o){return l(process.cwd(),t,o)}function p(t,o,a){let s=t.indexOf(o);if(s===-1)return a;let n=t[s+1];return!n||n.startsWith("--")?a:n}function g(t,o){return t.includes(o)}var P=["index.ts","hook.ts","builder.ts","types.ts","parser.ts","constants.ts","formatter.ts","runtime/types.ts","runtime/binding.ts","runtime/conflicts.ts","runtime/debug.ts","runtime/guards.ts","runtime/keys.ts","runtime/listener.ts","runtime/recording.ts"];function E(t="hooks",o=!1){let a=_(),s=R(t);if(r(`
|
|
456
|
-
use-shortcut CLI
|
|
457
|
-
`,e.cyan),h(s)&&!o){r(`Directory already exists: ${s}`,e.yellow),r(`Use --force to overwrite existing files
|
|
458
|
-
`,e.dim);return}f(s,{recursive:!0});let n=0;for(let i of P){let c=l(a,i),d=l(s,i);if(f(B(d),{recursive:!0}),!h(c)){r(`Source file not found: ${i}`,e.yellow);continue}let u=A(c,"utf-8");x(d,u),n+=1,r(` wrote ${i}`,e.green)}r(`
|
|
459
|
-
Copied ${n} files to:`,e.green),r(` ${s}
|
|
460
|
-
`,e.dim),r("Usage:",e.cyan),r(` import { useShortcut } from "@/${t}/use-shortcut"`,e.dim),r(" const $ = useShortcut()",e.dim),r(` $.mod.key("k").on(() => console.log("Search"))
|
|
461
|
-
`,e.dim)}function y(t,o="src",a="shortcuts",s=!1){let n=C(o,a),i=m(t);r(`
|
|
462
|
-
use-shortcut CLI
|
|
463
|
-
`,e.cyan),r(`Scaffolding ${t} architecture in ${n}
|
|
464
|
-
`,e.dim),f(n,{recursive:!0});let c=0,d=0;for(let[u,w]of Object.entries(i)){let S=l(n,u);if(h(S)&&!s){d+=1,r(` skipped ${u} (already exists)`,e.yellow);continue}x(S,w),c+=1,r(` wrote ${u}`,e.green)}r("",e.reset),r(`Architecture scaffold complete: ${c} written, ${d} skipped.`,e.green),r(`Location: ${n}
|
|
465
|
-
`,e.dim),r("Next steps:",e.cyan),r(` 1. Open ${l(o,a,"registry.ts")} and define your action catalog`,e.dim),r(" 2. Wire app handlers into <ShortcutProvider handlers={...} />",e.dim),r(" 3. Toggle scopes from feature boundaries via useShortcutManager()",e.dim),r(` 4. Optionally expose setBinding/resetBinding in your settings UI
|
|
466
|
-
`,e.dim)}function b(){r(`
|
|
467
|
-
use-shortcut CLI
|
|
468
|
-
`,e.cyan),r("Commands:",e.yellow),r(" init [--target hooks] [--force]",e.dim),r(" Copy source files into your project (shadcn-style).",e.dim),r("",e.dim),r(" scaffold [--framework next|react] [--target src] [--dir shortcuts] [--force]",e.dim),r(" Generate a scalable app shortcut architecture.",e.dim),r("",e.dim),r(" init --architecture",e.dim),r(` Alias for scaffold with defaults.
|
|
469
|
-
`,e.dim)}function v(t){if(t==="next"||t==="react")return t;r(`Invalid framework: ${t}. Expected "next" or "react".`,e.red),process.exit(1)}function K(){let t=process.argv.slice(2),o=t[0];if(!o||o==="--help"||o==="-h"||o==="help"){b();return}if(o==="init"){if(g(t,"--architecture")||g(t,"--app")||g(t,"--scaffold")){let i=v(p(t,"--framework","next")),c=p(t,"--target","src"),d=p(t,"--dir","shortcuts"),u=g(t,"--force");y(i,c,d,u);return}let s=p(t,"--target","hooks"),n=g(t,"--force");E(s,n);return}if(o==="scaffold"||o==="architecture"){let s=v(p(t,"--framework","next")),n=p(t,"--target","src"),i=p(t,"--dir","shortcuts"),c=g(t,"--force");y(s,n,i,c);return}b(),process.exit(1)}K();
|