@inklu/keys 2.0.1 → 2.1.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/LICENSE +1 -1
- package/README.md +77 -51
- package/dist/attributes.d.mts +11 -2
- package/dist/attributes.mjs +7 -6
- package/dist/{index-BTgscYdI.d.mts → index-3d9f54ln.d.mts} +50 -10
- package/dist/index.d.mts +3 -3
- package/dist/index.mjs +2 -3
- package/dist/react.d.mts +22 -6
- package/dist/react.mjs +130 -97
- package/dist/src-B76YvObL.mjs +1687 -0
- package/dist/{attributes-DotLr2MW.d.mts → types-QEXldPnL.d.mts} +163 -191
- package/package.json +5 -5
- package/dist/src-DgYjA1oS.mjs +0 -1697
|
@@ -1,10 +1,38 @@
|
|
|
1
|
-
|
|
1
|
+
//#region src/chord.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* One chord: its spelling, reading it from a string or a keypress, comparing
|
|
4
|
+
* the two, and writing it back. Everything else in the package handles chords
|
|
5
|
+
* through here, so there is one grammar and one idea of which key was pressed.
|
|
6
|
+
*/
|
|
7
|
+
/** Supported platforms for hotkey resolution and display formatting. */
|
|
8
|
+
type Platform = "mac" | "windows" | "linux";
|
|
9
|
+
/**
|
|
10
|
+
* Named keys with a place in the `Key` type, as `KeyboardEvent.key` spells
|
|
11
|
+
* them. Any other name still works as a key; `validateChord` only remarks on it.
|
|
12
|
+
*/
|
|
13
|
+
declare const NAMED_KEYS: readonly ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Home", "End", "PageUp", "PageDown", "Enter", "Escape", "Space", "Tab", "Backspace", "Delete", "Insert", "CapsLock", "ContextMenu", "PrintScreen", "ScrollLock", "Pause", "NumLock", "Help", "Clear", "MediaPlayPause", "MediaStop", "MediaTrackNext", "MediaTrackPrevious", "AudioVolumeUp", "AudioVolumeDown", "AudioVolumeMute", "BrowserBack", "BrowserForward", "BrowserRefresh", "BrowserSearch", "BrowserHome"];
|
|
14
|
+
type Letter = "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" | "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R" | "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z";
|
|
15
|
+
type Digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9";
|
|
16
|
+
type FunctionKey = `F${Exclude<Digit, "0">}` | `F1${Digit}` | `F2${"0" | "1" | "2" | "3" | "4"}`;
|
|
17
|
+
type Punctuation = "/" | "[" | "]" | "\\" | "=" | "-" | "," | "." | ";" | "`" | "'" | "+" | "?" | "!" | "@" | "#" | "$" | "%" | "^" | "&" | "*" | "(" | ")" | "_" | "{" | "}" | "|" | ":" | '"' | "<" | ">" | "~";
|
|
18
|
+
/**
|
|
19
|
+
* The key of a chord: the character or named key it produces (`"K"`,
|
|
20
|
+
* `"Escape"`), or a physical key by its `KeyboardEvent.code` in brackets
|
|
21
|
+
* (`"[KeyK]"`), which stays put whatever the keyboard layout.
|
|
22
|
+
*/
|
|
23
|
+
type Key = Letter | Digit | FunctionKey | Punctuation | (typeof NAMED_KEYS)[number] | `[${string}]`;
|
|
24
|
+
type Held<Modifier extends string> = "" | `${Modifier}+`;
|
|
25
|
+
type Modifiers = `${Held<"Control">}${Held<"Alt">}${Held<"Shift">}${Held<"Meta">}` | `Mod+${Held<"Alt">}${Held<"Shift">}`;
|
|
26
|
+
/**
|
|
27
|
+
* A single hotkey chord, e.g. `"Mod+K"`: modifiers in the order Control, Alt,
|
|
28
|
+
* Shift, Meta, then one key. `Mod` is Command on macOS and Control elsewhere.
|
|
29
|
+
*/
|
|
30
|
+
type Chord = `${Modifiers}${Key}`;
|
|
31
|
+
//#endregion
|
|
2
32
|
//#region src/types.d.ts
|
|
3
|
-
/** A single hotkey chord, e.g. `"Mod+K"`. */
|
|
4
|
-
type Chord = Hotkey$1;
|
|
5
33
|
/** A sequence of chords pressed in order, e.g. `G` then `I`. */
|
|
6
34
|
interface SequenceBinding {
|
|
7
|
-
readonly sequence:
|
|
35
|
+
readonly sequence: ReadonlyArray<Chord>;
|
|
8
36
|
}
|
|
9
37
|
/** A hotkey chord or sequence that triggers a command. */
|
|
10
38
|
type Binding = Chord | SequenceBinding;
|
|
@@ -16,9 +44,16 @@ type SerializedBinding = string | Array<string>;
|
|
|
16
44
|
type Json = string | number | boolean | null | ReadonlyArray<Json> | {
|
|
17
45
|
readonly [key: string]: Json;
|
|
18
46
|
};
|
|
47
|
+
/** Display options for formatting hotkeys. */
|
|
48
|
+
interface DisplayOptions {
|
|
49
|
+
/** Platform to write for. Defaults to the one detected. */
|
|
50
|
+
platform?: Platform;
|
|
51
|
+
/** Symbols such as `⌘` and `↵` rather than names such as `Cmd`. Defaults to `true`. */
|
|
52
|
+
useSymbols?: boolean;
|
|
53
|
+
}
|
|
19
54
|
/** Trigger source for a command. */
|
|
20
55
|
type CommandSource = "keyboard" | "api" | "dom";
|
|
21
|
-
/** Context passed to a command's
|
|
56
|
+
/** Context passed to a command's handler. */
|
|
22
57
|
interface CommandContext<Id extends string = string> {
|
|
23
58
|
/** ID of the command being run. */
|
|
24
59
|
readonly id: Id;
|
|
@@ -34,15 +69,16 @@ interface CommandContext<Id extends string = string> {
|
|
|
34
69
|
* @example
|
|
35
70
|
* ```ts
|
|
36
71
|
* run: ({ commands }) => {
|
|
37
|
-
* commands.setEnabled("doc.publish", false);
|
|
38
72
|
* commands.run("doc.save");
|
|
39
73
|
* }
|
|
40
74
|
* ```
|
|
41
75
|
*/
|
|
42
76
|
readonly commands: Commands<Id>;
|
|
43
77
|
}
|
|
78
|
+
/** What a command does when it runs. */
|
|
79
|
+
type CommandHandler<Id extends string = string> = (context: CommandContext<Id>) => void | Promise<void>;
|
|
44
80
|
/**
|
|
45
|
-
*
|
|
81
|
+
* A command: what it is called, which keys reach it, and where it is live.
|
|
46
82
|
*
|
|
47
83
|
* `Id` is the registry's id union. `createCommands` fills it in, so
|
|
48
84
|
* `commands.run(...)` inside a handler is checked against the registry's own
|
|
@@ -67,10 +103,6 @@ interface CommandDefinition<Id extends string = string> {
|
|
|
67
103
|
keys?: BindingInput;
|
|
68
104
|
/** Scope required for this command to fire. Omit for global, or `"*"` to bypass exclusive scopes. */
|
|
69
105
|
scope?: string;
|
|
70
|
-
/** Guard function evaluated before running. */
|
|
71
|
-
when?: () => boolean;
|
|
72
|
-
/** Whether the command is enabled. Defaults to `true`. */
|
|
73
|
-
enabled?: boolean;
|
|
74
106
|
/**
|
|
75
107
|
* Prevent default event behavior when run. Defaults to `true`. For a
|
|
76
108
|
* sequence this also covers each step as it matches, so a first step such
|
|
@@ -78,20 +110,23 @@ interface CommandDefinition<Id extends string = string> {
|
|
|
78
110
|
*/
|
|
79
111
|
preventDefault?: boolean;
|
|
80
112
|
/**
|
|
81
|
-
* Whether to ignore key events from text inputs, textareas and
|
|
82
|
-
* contenteditable elements. When omitted
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
113
|
+
* Whether to ignore key events from text inputs, textareas, selects and
|
|
114
|
+
* contenteditable elements. When omitted it is decided per binding: chords
|
|
115
|
+
* holding Control or Meta (so `Mod+…`) and bare `Escape` still fire inside
|
|
116
|
+
* inputs, while everything else — bare keys, Shift/Alt chords, and
|
|
117
|
+
* sequences starting with one — is ignored there, so typing is never
|
|
118
|
+
* hijacked.
|
|
87
119
|
*/
|
|
88
120
|
ignoreInputs?: boolean;
|
|
89
|
-
/** Require key release before firing again (prevents auto-repeat). Defaults to `false`. */
|
|
90
|
-
requireReset?: boolean;
|
|
91
121
|
/** Timeout in milliseconds between sequence steps. Defaults to `1000`. */
|
|
92
122
|
sequenceTimeout?: number;
|
|
93
|
-
/**
|
|
94
|
-
|
|
123
|
+
/**
|
|
124
|
+
* What the command does, when that needs nothing a component owns. Leave
|
|
125
|
+
* it out and attach the behaviour where the state lives, with
|
|
126
|
+
* `commands.handle()` or `useCommandHandler`: a command with no handler
|
|
127
|
+
* cannot run and leaves its key alone.
|
|
128
|
+
*/
|
|
129
|
+
run?: CommandHandler<Id>;
|
|
95
130
|
}
|
|
96
131
|
/** Resolved command snapshot including effective bindings, display tokens, and reachability. */
|
|
97
132
|
interface Command<Id extends string = string> {
|
|
@@ -122,15 +157,11 @@ interface Command<Id extends string = string> {
|
|
|
122
157
|
* text or `aria-description`, and the only accessible form of a sequence.
|
|
123
158
|
*/
|
|
124
159
|
readonly spoken: string;
|
|
125
|
-
/** Whether
|
|
126
|
-
readonly enabled: boolean;
|
|
127
|
-
/** Whether the command is enabled and in an active scope. */
|
|
160
|
+
/** Whether its keys work right now: it has a handler and its scope is active. */
|
|
128
161
|
readonly reachable: boolean;
|
|
129
162
|
/** Whether the command has a user keymap override. */
|
|
130
163
|
readonly overridden: boolean;
|
|
131
164
|
}
|
|
132
|
-
/** Supported platforms for hotkey resolution and display formatting. */
|
|
133
|
-
type Platform = "mac" | "windows" | "linux";
|
|
134
165
|
/** Storage interface for persisting keymap overrides. */
|
|
135
166
|
interface KeymapStorage {
|
|
136
167
|
getItem: (key: string) => string | null;
|
|
@@ -144,83 +175,36 @@ interface KeymapPersistence {
|
|
|
144
175
|
/** Getter returning the storage implementation lazily. */
|
|
145
176
|
storage: () => KeymapStorage | null;
|
|
146
177
|
}
|
|
147
|
-
/**
|
|
148
|
-
* How two bindings collide: `"exact"` when they are the same keys, `"prefix"`
|
|
149
|
-
* when one is the start of the other — chord `G` against `G then I` — so the
|
|
150
|
-
* shorter one fires on the way to the longer one.
|
|
151
|
-
*/
|
|
152
|
-
type ConflictKind = "exact" | "prefix";
|
|
153
|
-
/** Details of a conflict between commands whose bindings collide in overlapping scopes. */
|
|
154
|
-
interface Conflict<Id extends string = string> {
|
|
155
|
-
/** ID of the conflicting command. */
|
|
156
|
-
readonly id: Id;
|
|
157
|
-
/** The conflicting command's binding. */
|
|
158
|
-
readonly binding: Binding;
|
|
159
|
-
/** Whether the bindings are identical or one is a prefix of the other. */
|
|
160
|
-
readonly kind: ConflictKind;
|
|
161
|
-
/** Formatted display representation of the binding. */
|
|
162
|
-
readonly display: string;
|
|
163
|
-
/** Overlapping scope where conflict occurs, if any. */
|
|
164
|
-
readonly scope?: string;
|
|
165
|
-
}
|
|
166
|
-
/** Options for activating a scope. */
|
|
167
|
-
interface ScopeOptions {
|
|
168
|
-
/** Shadow commands in lower scopes, including global commands. */
|
|
169
|
-
exclusive?: boolean;
|
|
170
|
-
/**
|
|
171
|
-
* Also shadow `"*"` commands, which otherwise survive every barrier.
|
|
172
|
-
* Implies `exclusive`. Meant for surfaces that must own the whole keyboard,
|
|
173
|
-
* such as recording a new shortcut.
|
|
174
|
-
*/
|
|
175
|
-
strict?: boolean;
|
|
176
|
-
}
|
|
177
|
-
//#endregion
|
|
178
|
-
//#region src/display.d.ts
|
|
179
|
-
/** Display options for formatting hotkeys. */
|
|
180
|
-
type DisplayOptions = Omit<FormatDisplayOptions, "parts">;
|
|
181
|
-
/** Formats a binding for display on the target platform (e.g. `⌘K` or `Ctrl+K`). */
|
|
182
|
-
declare function formatBinding(binding: Binding, options?: DisplayOptions): string;
|
|
183
|
-
/** Splits a binding into display tokens for rendering `<kbd>` elements. */
|
|
184
|
-
declare function bindingTokens(binding: Binding, options?: DisplayOptions): Array<string>;
|
|
185
|
-
/**
|
|
186
|
-
* Phrases a binding for a screen reader: `"Control+K"` for a chord,
|
|
187
|
-
* `"G then I"` for a sequence. `aria-keyshortcuts` cannot express a sequence
|
|
188
|
-
* (a space there means "or"), so this is how a sequence gets announced — put it
|
|
189
|
-
* in visually hidden text or `aria-description`.
|
|
190
|
-
*/
|
|
191
|
-
declare function spokenBinding(binding: Binding, platform?: Platform): string;
|
|
192
|
-
/** Generates an `aria-keyshortcuts` attribute string for a binding. */
|
|
193
|
-
declare function ariaKeyShortcuts(binding: Binding, platform?: Platform): string;
|
|
194
|
-
//#endregion
|
|
195
|
-
//#region src/keymap.d.ts
|
|
196
178
|
/** One command's stored override: an array replaces the default, `{ add }` extends it. */
|
|
197
179
|
type SerializedOverride = Array<SerializedBinding> | {
|
|
198
180
|
add: Array<SerializedBinding>;
|
|
199
181
|
};
|
|
200
|
-
/**
|
|
201
|
-
|
|
202
|
-
|
|
182
|
+
/**
|
|
183
|
+
* Persisted user overrides, keyed by command id. `version` lets a release
|
|
184
|
+
* skip a format it does not know instead of misreading it.
|
|
185
|
+
*/
|
|
186
|
+
type SerializedKeymap = {
|
|
203
187
|
version: 1;
|
|
204
188
|
overrides: Record<string, SerializedOverride>;
|
|
205
|
-
}
|
|
189
|
+
};
|
|
206
190
|
/** Keymap interface for querying, modifying, and persisting keybindings. */
|
|
207
191
|
interface CommandKeymap<Id extends string = string> {
|
|
208
192
|
/** Active bindings in effect: the default, with any user override applied. */
|
|
209
|
-
get(id: Id
|
|
193
|
+
get(id: Id): ReadonlyArray<Binding>;
|
|
210
194
|
/**
|
|
211
195
|
* Replaces a command's bindings. Passing `null` unbinds it without
|
|
212
196
|
* restoring the default; passing exactly the default forgets the override,
|
|
213
197
|
* so the command follows the default again if the app changes it.
|
|
214
198
|
*/
|
|
215
|
-
set(id: Id
|
|
199
|
+
set(id: Id, input: BindingInput | null): void;
|
|
216
200
|
/**
|
|
217
201
|
* Appends an additional binding. On a command the user has not replaced,
|
|
218
202
|
* it is stored on top of the default rather than as a copy of it, so a
|
|
219
203
|
* default the app changes later still applies alongside the added keys.
|
|
220
204
|
*/
|
|
221
|
-
add(id: Id
|
|
205
|
+
add(id: Id, input: BindingInput): void;
|
|
222
206
|
/** Resets user overrides back to defaults for a specific command or all commands. */
|
|
223
|
-
reset(id?: Id
|
|
207
|
+
reset(id?: Id): void;
|
|
224
208
|
/** Exports user overrides as JSON-serializable data. */
|
|
225
209
|
export(): SerializedKeymap;
|
|
226
210
|
/**
|
|
@@ -229,8 +213,68 @@ interface CommandKeymap<Id extends string = string> {
|
|
|
229
213
|
*/
|
|
230
214
|
import(data: Json): void;
|
|
231
215
|
}
|
|
232
|
-
|
|
233
|
-
|
|
216
|
+
/**
|
|
217
|
+
* How two bindings collide: `"exact"` when they are the same keys, `"prefix"`
|
|
218
|
+
* when one is the start of the other — chord `G` against `G then I` — so the
|
|
219
|
+
* shorter one fires on the way to the longer one.
|
|
220
|
+
*/
|
|
221
|
+
type ConflictKind = "exact" | "prefix";
|
|
222
|
+
/** Details of a conflict between commands whose bindings collide in overlapping scopes. */
|
|
223
|
+
interface Conflict<Id extends string = string> {
|
|
224
|
+
/** ID of the conflicting command. */
|
|
225
|
+
readonly id: Id;
|
|
226
|
+
/** The conflicting command's binding. */
|
|
227
|
+
readonly binding: Binding;
|
|
228
|
+
/** Whether the bindings are identical or one is a prefix of the other. */
|
|
229
|
+
readonly kind: ConflictKind;
|
|
230
|
+
/** Formatted display representation of the binding. */
|
|
231
|
+
readonly display: string;
|
|
232
|
+
/** Overlapping scope where conflict occurs, if any. */
|
|
233
|
+
readonly scope?: string;
|
|
234
|
+
}
|
|
235
|
+
/** Options for conflict detection. */
|
|
236
|
+
interface ConflictOptions<Id extends string = string> {
|
|
237
|
+
/** Command ID to ignore during conflict checking. */
|
|
238
|
+
ignore?: Id;
|
|
239
|
+
/** Scope to evaluate conflicts in. Defaults to global. */
|
|
240
|
+
scope?: string;
|
|
241
|
+
}
|
|
242
|
+
/** Options for activating a scope. */
|
|
243
|
+
interface ScopeOptions {
|
|
244
|
+
/** Silence the keys of commands in lower scopes, including global commands. */
|
|
245
|
+
exclusive?: boolean;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Receives each keypress while the keyboard is captured: the event, and the
|
|
249
|
+
* chord it expresses (`null` for a modifier on its own). It decides what the
|
|
250
|
+
* key means, including whether to `preventDefault()` it.
|
|
251
|
+
*/
|
|
252
|
+
type KeyCapture = (event: KeyboardEvent, chord: Chord | null) => void;
|
|
253
|
+
/** Options passed when manually running a command. */
|
|
254
|
+
interface RunOptions {
|
|
255
|
+
/** Trigger source. Defaults to `"api"`. */
|
|
256
|
+
source?: CommandSource;
|
|
257
|
+
/** Originating DOM event. */
|
|
258
|
+
event?: Event;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Why a command cannot run: its id is not registered, nothing has attached a
|
|
262
|
+
* handler to it, or its scope is not open.
|
|
263
|
+
*/
|
|
264
|
+
type RunBlockReason = "unknown" | "unhandled" | "out-of-scope";
|
|
265
|
+
/** Represents a partially completed key sequence. */
|
|
266
|
+
interface PendingSequence<Id extends string = string> {
|
|
267
|
+
/** Command ID. */
|
|
268
|
+
readonly id: Id;
|
|
269
|
+
/** Sequence binding being typed. */
|
|
270
|
+
readonly binding: SequenceBinding;
|
|
271
|
+
/** Number of steps matched so far. */
|
|
272
|
+
readonly matched: number;
|
|
273
|
+
/** Total steps in the sequence. */
|
|
274
|
+
readonly total: number;
|
|
275
|
+
/** Formatted remaining steps. */
|
|
276
|
+
readonly remaining: ReadonlyArray<string>;
|
|
277
|
+
}
|
|
234
278
|
/** Configuration options for a commands registry. */
|
|
235
279
|
interface CommandsOptions<Id extends string = string> {
|
|
236
280
|
/**
|
|
@@ -284,90 +328,58 @@ interface CommandsOptions<Id extends string = string> {
|
|
|
284
328
|
*/
|
|
285
329
|
onRun?: (context: CommandContext<Id>) => void;
|
|
286
330
|
/**
|
|
287
|
-
* Error handler called when a
|
|
288
|
-
*
|
|
331
|
+
* Error handler called when a handler or `onRun` throws. Defaults to
|
|
332
|
+
* `console.error`.
|
|
289
333
|
*/
|
|
290
334
|
onError?: (error: Error, context: CommandContext<Id>) => void;
|
|
291
335
|
}
|
|
292
|
-
/**
|
|
293
|
-
interface RunOptions {
|
|
294
|
-
/** Trigger source. Defaults to `"api"`. */
|
|
295
|
-
source?: CommandSource;
|
|
296
|
-
/** Originating DOM event. */
|
|
297
|
-
event?: Event;
|
|
298
|
-
}
|
|
299
|
-
/** Options for conflict detection. */
|
|
300
|
-
interface ConflictOptions<Id extends string = string> {
|
|
301
|
-
/** Command ID to ignore during conflict checking. */
|
|
302
|
-
ignore?: Id;
|
|
303
|
-
/** Scope to evaluate conflicts in. Defaults to global. */
|
|
304
|
-
scope?: string;
|
|
305
|
-
}
|
|
306
|
-
/** Reason why a command cannot run. */
|
|
307
|
-
type RunBlockReason = "unknown" | "disabled" | "out-of-scope" | "guarded";
|
|
308
|
-
/** Represents a partially completed key sequence. */
|
|
309
|
-
interface PendingSequence<Id extends string = string> {
|
|
310
|
-
/** Command ID. */
|
|
311
|
-
readonly id: Id;
|
|
312
|
-
/** Sequence binding being typed. */
|
|
313
|
-
readonly binding: SequenceBinding;
|
|
314
|
-
/** Number of steps matched so far. */
|
|
315
|
-
readonly matched: number;
|
|
316
|
-
/** Total steps in the sequence. */
|
|
317
|
-
readonly total: number;
|
|
318
|
-
/** Formatted remaining steps. */
|
|
319
|
-
readonly remaining: ReadonlyArray<string>;
|
|
320
|
-
}
|
|
321
|
-
/** Handle for a dynamically registered runtime command. */
|
|
322
|
-
interface CommandHandle {
|
|
323
|
-
/** Runs the command. Returns `true` if executed. */
|
|
324
|
-
run: (options?: RunOptions) => boolean;
|
|
325
|
-
/** Disposes and unregisters the command. */
|
|
326
|
-
dispose: () => void;
|
|
327
|
-
}
|
|
328
|
-
/** Public interface for the commands registry. */
|
|
336
|
+
/** The commands registry. */
|
|
329
337
|
interface Commands<Id extends string = string> {
|
|
330
338
|
/** Keymap holding default and user bindings. */
|
|
331
339
|
readonly keymap: CommandKeymap<Id>;
|
|
332
340
|
/** Retrieves a command's snapshot, or `undefined` if unregistered. */
|
|
333
|
-
get(id: Id
|
|
334
|
-
/** Returns all
|
|
341
|
+
get(id: Id): Command<Id> | undefined;
|
|
342
|
+
/** Returns all commands in definition order. */
|
|
335
343
|
list(): ReadonlyArray<Command<Id>>;
|
|
336
344
|
/**
|
|
337
|
-
* The
|
|
338
|
-
*
|
|
339
|
-
*
|
|
340
|
-
*
|
|
341
|
-
*
|
|
342
|
-
* subtree that hydrates after the client state moved on still matches the
|
|
343
|
-
* server's HTML.
|
|
345
|
+
* The list as the server rendered it: default bindings in the
|
|
346
|
+
* platform-neutral form, no scope, user override or attached handler
|
|
347
|
+
* applied. Referentially stable. Hand it to a framework as the server
|
|
348
|
+
* snapshot (`useSyncExternalStore`'s third argument), so a subtree that
|
|
349
|
+
* hydrates after the client state moved on still matches the server's HTML.
|
|
344
350
|
*/
|
|
345
351
|
serverList(): ReadonlyArray<Command<Id>>;
|
|
346
|
-
/**
|
|
352
|
+
/**
|
|
353
|
+
* Runs a command by ID. Returns `true` if executed. A direct call is not
|
|
354
|
+
* a keypress, so it passes `exclusive` barriers: a palette can run what it
|
|
355
|
+
* lists while its own scope silences the keyboard.
|
|
356
|
+
*/
|
|
347
357
|
run(id: Id, options?: RunOptions): boolean;
|
|
348
|
-
/** Checks whether
|
|
349
|
-
canRun(id: Id
|
|
350
|
-
/** Returns why
|
|
351
|
-
whyNotRun(id: Id
|
|
352
|
-
/**
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
+
/** Checks whether `run` would execute the command right now. */
|
|
359
|
+
canRun(id: Id): boolean;
|
|
360
|
+
/** Returns why `run` would refuse the command, or `null` if it would run. */
|
|
361
|
+
whyNotRun(id: Id): RunBlockReason | null;
|
|
362
|
+
/**
|
|
363
|
+
* Attaches what a command does until the returned function is called.
|
|
364
|
+
* The most recently attached handler runs; disposing it hands the command
|
|
365
|
+
* back to the one before, or to the definition's own `run`.
|
|
366
|
+
*/
|
|
367
|
+
handle(id: Id, run: CommandHandler<Id>): () => void;
|
|
358
368
|
/** Activates a scope until the returned disposal function is called. */
|
|
359
369
|
pushScope(name: string, options?: ScopeOptions): () => void;
|
|
370
|
+
/**
|
|
371
|
+
* Takes the whole keyboard until the returned function is called: every
|
|
372
|
+
* keypress goes to `onKey` first and no command's keys are live, `"*"`
|
|
373
|
+
* ones included. This is what recording a new shortcut is built on.
|
|
374
|
+
*/
|
|
375
|
+
capture(onKey: KeyCapture): () => void;
|
|
360
376
|
/**
|
|
361
377
|
* Finds commands in overlapping scopes whose bindings collide with the given
|
|
362
378
|
* one — the same keys (`kind: "exact"`), or one a prefix of the other, like
|
|
363
379
|
* chord `G` against `G then I` (`kind: "prefix"`).
|
|
364
380
|
*/
|
|
365
381
|
conflicts(binding: BindingInput, options?: ConflictOptions<Id>): Array<Conflict<Id>>;
|
|
366
|
-
/**
|
|
367
|
-
display(id: Id | (string & {})): string;
|
|
368
|
-
/** Splits a command's primary binding into `<kbd>` tokens. */
|
|
369
|
-
tokens(id: Id | (string & {})): Array<string>;
|
|
370
|
-
/** Returns currently active partially-typed sequences. */
|
|
382
|
+
/** Returns the partially-typed sequences. Tracked only while something is subscribed. */
|
|
371
383
|
pending(): ReadonlyArray<PendingSequence<Id>>;
|
|
372
384
|
/** Subscribes to sequence progress changes. */
|
|
373
385
|
subscribePending(listener: () => void): () => void;
|
|
@@ -379,48 +391,8 @@ interface Commands<Id extends string = string> {
|
|
|
379
391
|
readonly singleKeyShortcuts: boolean;
|
|
380
392
|
/** Turns single-key shortcuts on or off for every command (WCAG 2.1.4). */
|
|
381
393
|
setSingleKeyShortcuts(enabled: boolean): void;
|
|
382
|
-
/** Tears down listeners and clears all
|
|
394
|
+
/** Tears down listeners and clears all commands. */
|
|
383
395
|
destroy(): void;
|
|
384
396
|
}
|
|
385
|
-
/**
|
|
386
|
-
* Creates a command registry.
|
|
387
|
-
*
|
|
388
|
-
* @example
|
|
389
|
-
* ```ts
|
|
390
|
-
* export const commands = createCommands({
|
|
391
|
-
* "palette.open": {
|
|
392
|
-
* title: "Open command palette",
|
|
393
|
-
* keys: "Mod+K",
|
|
394
|
-
* run: () => setPaletteOpen(true),
|
|
395
|
-
* },
|
|
396
|
-
* "nav.inbox": {
|
|
397
|
-
* title: "Go to inbox",
|
|
398
|
-
* keys: seq("G", "I"),
|
|
399
|
-
* run: () => navigate("/inbox"),
|
|
400
|
-
* },
|
|
401
|
-
* });
|
|
402
|
-
* ```
|
|
403
|
-
*/
|
|
404
|
-
declare function createCommands<Id extends string>(definitions: { [K in Id]: CommandDefinition<Id>; }, options?: CommandsOptions<NoInfer<Id>>): Commands<Id>;
|
|
405
|
-
/**
|
|
406
|
-
* Extracts command ID union from a Commands registry type.
|
|
407
|
-
*
|
|
408
|
-
* @example
|
|
409
|
-
* ```ts
|
|
410
|
-
* export const commands = createCommands({ … });
|
|
411
|
-
* export type AppCommandId = CommandId<typeof commands>;
|
|
412
|
-
* ```
|
|
413
|
-
*/
|
|
414
|
-
type CommandId<C> = C extends Commands<infer Id> ? Id : never;
|
|
415
|
-
//#endregion
|
|
416
|
-
//#region src/attributes.d.ts
|
|
417
|
-
interface ObserveOptions {
|
|
418
|
-
/** Root document or element to delegate from. Defaults to `document`. */
|
|
419
|
-
root?: Document | Element;
|
|
420
|
-
}
|
|
421
|
-
/**
|
|
422
|
-
* Wires a commands registry to DOM data attributes. Returns a cleanup function.
|
|
423
|
-
*/
|
|
424
|
-
declare function observe<Id extends string>(registry: Commands<Id>, options?: ObserveOptions): () => void;
|
|
425
397
|
//#endregion
|
|
426
|
-
export {
|
|
398
|
+
export { SequenceBinding as C, Chord as D, SerializedOverride as E, Key as O, ScopeOptions as S, SerializedKeymap as T, KeymapPersistence as _, CommandDefinition as a, RunBlockReason as b, CommandSource as c, Conflict as d, ConflictKind as f, KeyCapture as g, Json as h, CommandContext as i, Platform as k, Commands as l, DisplayOptions as m, BindingInput as n, CommandHandler as o, ConflictOptions as p, Command as r, CommandKeymap as s, Binding as t, CommandsOptions as u, KeymapStorage as v, SerializedBinding as w, RunOptions as x, PendingSequence as y };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inklu/keys",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "A command system for the web — typed commands, portable keybindings, scopes, sequences, conflict detection, rebinding, and platform-aware display.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"accessibility",
|
|
@@ -41,12 +41,11 @@
|
|
|
41
41
|
"publishConfig": {
|
|
42
42
|
"access": "public"
|
|
43
43
|
},
|
|
44
|
-
"dependencies": {
|
|
45
|
-
"@tanstack/hotkeys": "^0.10.0"
|
|
46
|
-
},
|
|
47
44
|
"devDependencies": {
|
|
48
45
|
"@inklu/tsconfig": "1.0.0",
|
|
46
|
+
"@playwright/test": "^1.63.0",
|
|
49
47
|
"@testing-library/react": "^16.3.3",
|
|
48
|
+
"@types/node": "^26.6.2",
|
|
50
49
|
"@types/react": "^19.3.0",
|
|
51
50
|
"@types/react-dom": "^19.3.0",
|
|
52
51
|
"jsdom": "^30.1.1",
|
|
@@ -67,6 +66,7 @@
|
|
|
67
66
|
"scripts": {
|
|
68
67
|
"build": "vp pack",
|
|
69
68
|
"dev": "vp pack --watch",
|
|
70
|
-
"test": "vp test"
|
|
69
|
+
"test": "vp test",
|
|
70
|
+
"test:e2e": "playwright test"
|
|
71
71
|
}
|
|
72
72
|
}
|