@inklu/keys 2.2.0 → 3.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.
@@ -11,12 +11,8 @@ type Letter = "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" | "K" |
11
11
  type Digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9";
12
12
  type FunctionKey = `F${Exclude<Digit, "0">}` | `F1${Digit}` | `F2${"0" | "1" | "2" | "3" | "4"}`;
13
13
  type Punctuation = "/" | "[" | "]" | "\\" | "=" | "-" | "," | "." | ";" | "`" | "'" | "+" | "?" | "!" | "@" | "#" | "$" | "%" | "^" | "&" | "*" | "(" | ")" | "_" | "{" | "}" | "|" | ":" | '"' | "<" | ">" | "~";
14
- /**
15
- * The key of a chord: the character or named key it produces (`"K"`,
16
- * `"Escape"`), or a physical key by its `KeyboardEvent.code` in brackets
17
- * (`"[KeyK]"`), which stays put whatever the keyboard layout.
18
- */
19
- type Key = Letter | Digit | FunctionKey | Punctuation | (typeof NAMED_KEYS)[number] | `[${string}]`;
14
+ /** The key of a chord: the character or named key it produces (`"K"`, `"Escape"`). */
15
+ type Key = Letter | Digit | FunctionKey | Punctuation | (typeof NAMED_KEYS)[number];
20
16
  type Held<Modifier extends string> = "" | `${Modifier}+`;
21
17
  type Modifiers = `${Held<"Control">}${Held<"Alt">}${Held<"Shift">}${Held<"Meta">}` | `Mod+${Held<"Alt">}${Held<"Shift">}`;
22
18
  /**
@@ -53,13 +49,6 @@ type SerializedBinding = string | Array<string>;
53
49
  type Json = string | number | boolean | null | ReadonlyArray<Json> | {
54
50
  readonly [key: string]: Json;
55
51
  };
56
- /** Display options for formatting hotkeys. */
57
- interface DisplayOptions {
58
- /** Platform to write for. Defaults to the one detected. */
59
- platform?: Platform;
60
- /** Symbols such as `⌘` and `↩` rather than names such as `Cmd`. Defaults to `true`. */
61
- useSymbols?: boolean;
62
- }
63
52
  /** Trigger source for a command. */
64
53
  type CommandSource = "keyboard" | "api" | "dom";
65
54
  /** Context passed to a command's handler. */
@@ -84,17 +73,6 @@ type CommandHandler<Id extends string = string> = (context: CommandContext<Id>)
84
73
  interface CommandDefinition<Id extends string = string> {
85
74
  /** Human-readable title. */
86
75
  title: string;
87
- /** Longer explanation, such as a palette's secondary line or a tooltip. */
88
- description?: string;
89
- /** Heading to list the command under in a palette or shortcut sheet, e.g. `"Navigate"`. */
90
- group?: string;
91
- /** Extra search terms `matchCommands` checks besides the title, e.g. `["find", "search"]`. */
92
- keywords?: ReadonlyArray<string>;
93
- /**
94
- * Leave the command out of `matchCommands`. It still runs and still appears
95
- * in `list()`. Defaults to `false`.
96
- */
97
- hidden?: boolean;
98
76
  /**
99
77
  * Default key binding(s). Write `{ mac, other }` where the platforms'
100
78
  * conventions differ, as they do for redo and for back and forward.
@@ -103,26 +81,11 @@ interface CommandDefinition<Id extends string = string> {
103
81
  /** Scope required for this command to fire. Omit for global, or `"*"` to bypass exclusive scopes. */
104
82
  scope?: string;
105
83
  /**
106
- * Prevent default event behavior when run, and for each step of a sequence as
107
- * it matches. Defaults to `true`.
108
- */
109
- preventDefault?: boolean;
110
- /**
111
- * Whether to ignore key events from text inputs, selects, contenteditable
112
- * elements and widgets with a text-entry ARIA role. When omitted, chords
113
- * holding Control or Meta and bare `Escape` still fire there, except the
114
- * field's own: select all, copy, cut, paste, undo, redo, and moving or
115
- * deleting by word or line.
84
+ * Whether a text field keeps the keys: inputs, selects, contenteditable
85
+ * elements and widgets with a text-entry ARIA role. Defaults to `true`,
86
+ * except for `Escape`. Set `false` on a command that should fire there too.
116
87
  */
117
88
  ignoreInputs?: boolean;
118
- /** Timeout in milliseconds between sequence steps. Defaults to `1000`. */
119
- sequenceTimeout?: number;
120
- /**
121
- * Whether holding the keys down runs the command again on each auto-repeat.
122
- * Defaults to `true`. With `false` it runs once per press and still claims
123
- * its key while held.
124
- */
125
- repeat?: boolean;
126
89
  /**
127
90
  * What the command does, when that needs nothing a component owns. Otherwise
128
91
  * attach it with `commands.handle()` or `useCommandHandler`. A command with
@@ -134,12 +97,6 @@ interface CommandDefinition<Id extends string = string> {
134
97
  interface Command<Id extends string = string> {
135
98
  readonly id: Id;
136
99
  readonly title: string;
137
- readonly description?: string;
138
- readonly group?: string;
139
- /** Extra search terms; empty when the definition gave none. */
140
- readonly keywords: ReadonlyArray<string>;
141
- /** Whether `matchCommands` leaves the command out. */
142
- readonly hidden: boolean;
143
100
  readonly scope?: string;
144
101
  /** Active bindings after user overrides. */
145
102
  readonly bindings: ReadonlyArray<Binding>;
@@ -149,37 +106,21 @@ interface Command<Id extends string = string> {
149
106
  readonly tokens: ReadonlyArray<ReadonlyArray<string>>;
150
107
  /** Value for the `aria-keyshortcuts` attribute. Chords only: it cannot express a sequence. */
151
108
  readonly aria: string;
152
- /**
153
- * Every binding phrased for a screen reader, joined by `" or "`: `"Command K"`,
154
- * `"G then I"`. The only accessible form of a sequence.
155
- */
156
- readonly spoken: string;
157
109
  /** Whether its keys work right now: it has a handler and its scope is active. */
158
110
  readonly reachable: boolean;
159
111
  /** Whether the command has a user keymap override. */
160
112
  readonly overridden: boolean;
161
113
  }
162
- /** A user's change to one command: `"replace"` the default (`[]` unbinds), or `"add"` to it. */
163
- interface Override {
164
- readonly kind: "replace" | "add";
165
- readonly bindings: ReadonlyArray<Binding>;
166
- }
167
114
  /** Where user overrides are kept between visits. `webStorage()` makes one. */
168
115
  interface KeymapStore {
169
- /** The stored overrides: empty when there are none, `null` when they cannot be read. */
170
- read(): Map<string, Override> | null;
171
- write(overrides: ReadonlyMap<string, Override>): void;
172
- /** Calls `onChange` when another tab writes. Returns a function that stops. */
173
- watch(onChange: () => void): () => void;
116
+ /** The stored overrides by command id: empty when there are none, `null` when they cannot be read. */
117
+ read(): Map<string, ReadonlyArray<Binding>> | null;
118
+ write(overrides: ReadonlyMap<string, ReadonlyArray<Binding>>): void;
174
119
  }
175
- /** One command's stored override: an array replaces the default, `{ add }` extends it. */
176
- type SerializedOverride = Array<SerializedBinding> | {
177
- add: Array<SerializedBinding>;
178
- };
179
120
  /** User overrides as JSON-safe data, keyed by command id. What `exportKeymap()` returns. */
180
121
  type SerializedKeymap = {
181
122
  version: 1;
182
- overrides: Record<string, SerializedOverride>;
123
+ overrides: Record<string, Array<SerializedBinding>>;
183
124
  };
184
125
  /** A registry's keymap: the keys in effect, and the user's changes to them. */
185
126
  interface CommandKeymap<Id extends string = string> {
@@ -187,19 +128,14 @@ interface CommandKeymap<Id extends string = string> {
187
128
  get(id: Id): ReadonlyArray<Binding>;
188
129
  /** Replaces a command's bindings. `null` unbinds it; exactly the default forgets the override. */
189
130
  set(id: Id, input: BindingInput | null): void;
190
- /** Appends a binding, stored on top of the default so a default that changes still applies. */
191
- add(id: Id, input: BindingInput): void;
192
131
  /** Resets user overrides back to defaults for a specific command or all commands. */
193
132
  reset(id?: Id): void;
194
133
  }
195
134
  /**
196
- * One thing in a binding's way: a command in an overlapping scope with the
197
- * same keys (`"exact"`) or with one binding starting the other (`"prefix"`),
198
- * or the browser or operating system keeping the keys (`"reserved"`, which
199
- * has no `id`).
135
+ * A command in an overlapping scope whose keys stand in a binding's way: the
136
+ * same keys (`"exact"`), or one binding starting the other (`"prefix"`).
200
137
  */
201
- type Conflict<Id extends string = string> = {
202
- /** Whether the bindings are identical or one is a prefix of the other. */
138
+ interface Conflict<Id extends string = string> {
203
139
  readonly kind: "exact" | "prefix";
204
140
  /** ID of the conflicting command. */
205
141
  readonly id: Id;
@@ -209,13 +145,7 @@ type Conflict<Id extends string = string> = {
209
145
  readonly display: string;
210
146
  /** The conflicting command's scope, if it has one. */
211
147
  readonly scope?: string;
212
- } | {
213
- readonly kind: "reserved";
214
- /** The binding that was asked about. */
215
- readonly binding: Binding;
216
- /** Formatted display representation of that binding. */
217
- readonly display: string;
218
- };
148
+ }
219
149
  /** Options for conflict detection. */
220
150
  interface ConflictOptions<Id extends string = string> {
221
151
  /** Command ID to ignore during conflict checking. */
@@ -245,19 +175,6 @@ interface RunOptions {
245
175
  * handler to it, or its scope is not open.
246
176
  */
247
177
  type RunBlockReason = "unknown" | "unhandled" | "out-of-scope";
248
- /** Represents a partially completed key sequence. */
249
- interface PendingSequence<Id extends string = string> {
250
- /** Command ID. */
251
- readonly id: Id;
252
- /** Sequence binding being typed. */
253
- readonly binding: SequenceBinding;
254
- /** Number of steps matched so far. */
255
- readonly matched: number;
256
- /** Total steps in the sequence. */
257
- readonly total: number;
258
- /** Formatted remaining steps. */
259
- readonly remaining: ReadonlyArray<string>;
260
- }
261
178
  /** Configuration options for a commands registry. */
262
179
  interface CommandsOptions<Id extends string = string> {
263
180
  /**
@@ -275,8 +192,6 @@ interface CommandsOptions<Id extends string = string> {
275
192
  bindings?: Partial<Record<Id, BindingInput | PlatformBindings | null>>;
276
193
  /** Platform for modifier resolution and display formatting. Defaults to auto-detection. */
277
194
  platform?: Platform;
278
- /** Target element for key events. Defaults to `document`. */
279
- target?: HTMLElement | Document | Window | null;
280
195
  /**
281
196
  * Where to keep user keymap overrides between visits, such as
282
197
  * `webStorage("my-app.keys")`. They load on the first `mount()`, so the
@@ -289,15 +204,8 @@ interface CommandsOptions<Id extends string = string> {
289
204
  * `true`. WCAG 2.1.4 requires a way to turn them off.
290
205
  */
291
206
  singleKeyShortcuts?: boolean;
292
- /** Display options for rendering shortcuts. */
293
- display?: DisplayOptions;
294
- /** Called whenever a command runs. */
207
+ /** Called before any command runs, for logging or analytics. */
295
208
  onRun?: (context: CommandContext<Id>) => void;
296
- /**
297
- * Error handler called when a handler or `onRun` throws. Defaults to
298
- * `console.error`.
299
- */
300
- onError?: (error: Error, context: CommandContext<Id>) => void;
301
209
  }
302
210
  /** The commands registry. */
303
211
  interface Commands<Id extends string = string> {
@@ -317,8 +225,6 @@ interface Commands<Id extends string = string> {
317
225
  * barriers, which silence keys, not commands.
318
226
  */
319
227
  run(id: Id, options?: RunOptions): boolean;
320
- /** Checks whether `run` would execute the command right now. */
321
- canRun(id: Id): boolean;
322
228
  /** Returns why `run` would refuse the command, or `null` if it would run. */
323
229
  whyNotRun(id: Id): RunBlockReason | null;
324
230
  /**
@@ -333,15 +239,8 @@ interface Commands<Id extends string = string> {
333
239
  * keypress goes to `onKey` and no command runs.
334
240
  */
335
241
  capture(onKey: KeyCapture): () => void;
336
- /**
337
- * Finds what stands in a binding's way: the platform reserving it (listed
338
- * first), then commands in overlapping scopes with colliding keys.
339
- */
242
+ /** Finds the commands in overlapping scopes whose keys collide with a binding. */
340
243
  conflicts(binding: BindingInput, options?: ConflictOptions<Id>): Array<Conflict<Id>>;
341
- /** Returns the partially-typed sequences. Tracked only while something is subscribed. */
342
- pending(): ReadonlyArray<PendingSequence<Id>>;
343
- /** Subscribes to sequence progress changes. */
344
- subscribePending(listener: () => void): () => void;
345
244
  /** Starts listening for key events. Returns an unmount function. */
346
245
  mount(): () => void;
347
246
  /** Subscribes to registry changes. Returns an unsubscribe function. */
@@ -354,4 +253,4 @@ interface Commands<Id extends string = string> {
354
253
  destroy(): void;
355
254
  }
356
255
  //#endregion
357
- export { SequenceBinding as C, Chord as D, SerializedOverride as E, Key as O, ScopeOptions as S, SerializedKeymap as T, Override as _, CommandDefinition as a, RunBlockReason as b, CommandSource as c, Conflict as d, ConflictOptions as f, KeymapStore as g, KeyCapture as h, CommandContext as i, Platform as k, Commands as l, Json as m, BindingInput as n, CommandHandler as o, DisplayOptions as p, Command as r, CommandKeymap as s, Binding as t, CommandsOptions as u, PendingSequence as v, SerializedBinding as w, RunOptions as x, PlatformBindings as y };
256
+ export { Chord as C, SerializedKeymap as S, Platform as T, RunBlockReason as _, CommandDefinition as a, SequenceBinding as b, CommandSource as c, Conflict as d, ConflictOptions as f, PlatformBindings as g, KeymapStore as h, CommandContext as i, Commands as l, KeyCapture as m, BindingInput as n, CommandHandler as o, Json as p, Command as r, CommandKeymap as s, Binding as t, CommandsOptions as u, RunOptions as v, Key as w, SerializedBinding as x, ScopeOptions as y };
package/package.json CHANGED
@@ -1,10 +1,9 @@
1
1
  {
2
2
  "name": "@inklu/keys",
3
- "version": "2.2.0",
4
- "description": "A command system for the web — typed commands, portable keybindings, scopes, sequences, conflict detection, rebinding, and platform-aware display.",
3
+ "version": "3.0.0",
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",
7
- "command-palette",
8
7
  "commands",
9
8
  "hotkeys",
10
9
  "keybindings",