rx-hotkeys 2.6.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.
package/README.md CHANGED
@@ -1,19 +1,19 @@
1
- # rx-hotkeys: Advanced Keyboard Shortcut Management using rxjs
1
+ # rx-hotkeys: Advanced Keyboard Shortcut Management with RxJS
2
2
 
3
- rx-hotkeys is a powerful and flexible TypeScript library for managing keyboard shortcuts in web applications. It leverages RxJS to handle keyboard events, allowing for the registration of simple key combinations (e.g., `Ctrl+S`) and complex key sequences (e.g., `g` -> `i` for "go to inbox"). It supports contexts for enabling/disabling shortcuts based on application state, and provides a type-safe way to define keys using standard `KeyboardEvent.key` values.
3
+ rx-hotkeys is a powerful and flexible TypeScript library for managing keyboard shortcuts in web applications. It leverages the full power of RxJS to handle keyboard events, allowing for the registration of simple key combinations (e.g., `Ctrl+S`), complex key sequences (e.g., `g` -> `i` for "go to inbox"), and much more. It supports contexts for enabling/disabling shortcuts based on application state, element-scoped listeners, and provides a type-safe API for defining shortcuts.
4
4
 
5
- ## Features
5
+ ## Features
6
6
 
7
- * **Key Combinations:** Define shortcuts that trigger when a specific key and modifier keys (Ctrl, Alt, Shift, Meta) are pressed simultaneously.
8
- * **Key Sequences:** Define shortcuts that trigger when a series of keys are pressed in a specific order.
9
- * **Sequence Timeouts:** Optional timeout between key presses in a sequence to prevent accidental triggers or indefinite waiting.
10
- * **Context Management:** Activate or deactivate groups of shortcuts based on the application's current state (e.g., "editor", "modal", "global").
11
- * **Strict Global Shortcuts:** Option to register global shortcuts that *only* fire when no other context is active, preventing them from triggering unintentionally.
12
- * **Type-Safe Key Definitions:** Uses an exported `Keys` object based on standard `KeyboardEvent.key` values for improved developer experience and fewer errors.
13
- * **RxJS Powered:** Built on RxJS for robust and efficient event handling.
14
- * **Prevent Default:** Option to prevent the default browser action for a triggered shortcut.
15
- * **Debug Mode:** Optional logging for easier development and troubleshooting.
16
- * **Clean API:** Simple and intuitive methods for adding, removing, and managing shortcuts.
7
+ * **Fully Observable API**: Returns an RxJS `Observable` for each shortcut, allowing for powerful stream manipulation like chaining, filtering, debouncing, and merging with other streams.
8
+ * **Flexible Shortcut Definitions**: Define shortcuts using simple, intuitive strings (e.g., `"ctrl+s"` or `"g -> i"`) in addition to the classic object-based configuration.
9
+ * **Element-Scoped Listeners**: Attach shortcuts to specific DOM elements, so they are only active within a certain component or area, not just on the global `document`.
10
+ * **`keyup` Event Support**: Trigger actions on key release (`keyup`) in addition to the default key press (`keydown`).
11
+ * **Key Combinations & Sequences**: Supports both simultaneous key presses (`Ctrl+S`) and ordered key sequences (`g` -> `c`).
12
+ * **Context Management**: Activate or deactivate groups of shortcuts based on the application's current state (e.g., "editor", "modal", "global").
13
+ * **Strict Global Shortcuts**: Option to register global shortcuts that *only* fire when no other context is active.
14
+ * **Type-Safe Key Definitions**: Uses an exported `Keys` object based on standard `KeyboardEvent.key` values for a superior developer experience and fewer errors.
15
+ * **Sequence Timeouts**: Optional timeout between key presses in a sequence to prevent accidental triggers.
16
+ * **Debug Mode**: Optional, detailed console logging for easier development and troubleshooting.
17
17
 
18
18
  ## Installation
19
19
 
@@ -21,13 +21,47 @@ rx-hotkeys is a powerful and flexible TypeScript library for managing keyboard s
21
21
  npm install rxjs rx-hotkeys
22
22
  ```
23
23
 
24
+ ## ⚠️ Breaking Changes (v3.0+)
25
+
26
+ Starting with v3.0, the API has been significantly updated for a more powerful and idiomatic RxJS experience. This is a major breaking change.
27
+
28
+ * `addCombination` and `addSequence` no longer accept a `callback` property in their configuration.
29
+ * They now return an **`Observable<KeyboardEvent>`**.
30
+ * You **must** now call `.subscribe()` on the returned Observable to execute your action.
31
+
32
+ **Migration Example:**
33
+
34
+ **Old (v1.x):**
35
+ ```typescript
36
+ // The old way
37
+ keyManager.addCombination({
38
+ id: "save",
39
+ keys: { key: Keys.S, ctrlKey: true },
40
+ callback: () => console.log("File saved!"),
41
+ });
42
+ ```
43
+
44
+ **New (v3.0+):**
45
+ ```typescript
46
+ // The new, observable-based way
47
+ const save$ = keyManager.addCombination({
48
+ id: "save",
49
+ keys: { key: Keys.S, ctrlKey: true }
50
+ });
51
+
52
+ const subscription = save$.subscribe(() => console.log("File saved!"));
53
+
54
+ // Don't forget to unsubscribe when your component is destroyed!
55
+ // The stream will also complete automatically if the shortcut is removed or keyManager.destroy() is called.
56
+ // subscription.unsubscribe();
57
+ ```
24
58
 
25
59
  ## Basic Usage
26
60
 
27
- First, ensure you have the `rx-hotkeys` library and its helper Keys imported:
61
+ First, ensure you have the `Hotkeys` class and its helper `Keys` object imported:
28
62
 
29
63
  ```typescript
30
- import { Hotkeys, Keys, KeyCombinationConfig, KeySequenceConfig } from 'rx-hotkeys';
64
+ import { Hotkeys, Keys } from "rx-hotkeys";
31
65
  ```
32
66
 
33
67
  ### 1. Initialize Hotkeys
@@ -37,54 +71,78 @@ Create an instance of the `Hotkeys` class. You can optionally provide an initial
37
71
  ```typescript
38
72
  const keyManager = new Hotkeys(); // No initial context, debug mode off
39
73
 
40
- // With an initial context and debug mode enabled:
41
- // const keyManager = new Hotkeys('editor', true);
74
+ // Or with an initial context and debug mode enabled:
75
+ // const keyManager = new Hotkeys("editor", true);
42
76
  ```
43
77
 
44
78
  ### 2. Add a Key Combination
45
79
 
46
- Register a shortcut for a key combination, like Ctrl+S.
80
+ Register a shortcut for a key combination, like `Ctrl+S`, by subscribing to the returned Observable.
47
81
 
48
82
  ```typescript
49
- const saveConfig: KeyCombinationConfig = {
83
+ const save$ = keyManager.addCombination({
50
84
  id: "saveFile", // Unique ID for this shortcut
51
- keys: { key: Keys.S, ctrlKey: true }, // Use Keys.S for 's' key
52
- callback: () => {
53
- console.log("Ctrl+S pressed: Save file action triggered!");
54
- },
85
+ keys: { key: Keys.S, ctrlKey: true }, // Use Keys.S for "s" key
55
86
  preventDefault: true, // Prevent browser's default save action
56
87
  description: "Save the current file."
57
- };
88
+ });
58
89
 
59
- keyManager.addCombination(saveConfig);
90
+ const saveSubscription = save$.subscribe((event) => {
91
+ console.log("Ctrl+S pressed: Save file action triggered!", event);
92
+ });
60
93
  ```
61
94
 
62
- ### 3. Add a Key Sequence
95
+ ### 3. Define Shortcuts with Strings (New)
96
+
97
+ You can also use more concise strings to define shortcuts.
98
+
99
+ ```typescript
100
+ // Combination
101
+ const open$ = keyManager.addCombination({ id: "openFile", keys: "ctrl+o" });
102
+ open$.subscribe(() => console.log("Opening file..."));
103
+
104
+ // Sequence
105
+ const command$ = keyManager.addSequence({ id: "showCommandPalette", sequence: "cmd+k" }); // Note: "cmd+k" is a combination, not a sequence. Let's fix this example.
106
+ const command$ = keyManager.addSequence({ id: "goToInbox", sequence: "g -> i" });
107
+ command$.subscribe(() => console.log("Navigating to Inbox..."));
108
+ ```
109
+
110
+ ### 4. Add a Key Sequence
63
111
 
64
112
  Register a shortcut for a sequence of keys, like the Konami code.
65
113
 
66
114
  ```typescript
67
- const konamiConfig: KeySequenceConfig = {
115
+ const konami$ = keyManager.addSequence({
68
116
  id: "konamiCode",
69
- sequence: [
70
- Keys.ArrowUp, Keys.ArrowUp,
71
- Keys.ArrowDown, Keys.ArrowDown,
72
- Keys.ArrowLeft, Keys.ArrowRight,
73
- Keys.ArrowLeft, Keys.ArrowRight,
74
- Keys.B, Keys.A // 'B' and 'A' from Keys
75
- ],
76
- callback: (event) => { // The last KeyboardEvent of the sequence is passed
77
- console.log("Konami code entered!");
78
- // event.preventDefault(); // Can also be done here if not set in config
79
- },
117
+ sequence: "up -> up -> down -> down -> left -> right -> left -> right -> b -> a",
80
118
  sequenceTimeoutMs: 3000, // User has 3 seconds between each key press
81
119
  description: "Unlock special features."
82
- };
120
+ });
83
121
 
84
- keyManager.addSequence(konamiConfig);
122
+ konami$.subscribe((event) => { // The last KeyboardEvent of the sequence is emitted
123
+ console.log("Konami code entered!");
124
+ });
85
125
  ```
86
126
 
87
- ### 4. Manage Contexts
127
+ ### 5. Advanced Usage: Scopes and `keyup`
128
+
129
+ You can scope a shortcut to a specific element and trigger it on `keyup`.
130
+
131
+ ```typescript
132
+ const myInputField = document.getElementById("my-input");
133
+
134
+ const submit$ = keyManager.addCombination({
135
+ id: "submitOnEnter",
136
+ keys: Keys.Enter,
137
+ target: myInputField, // Only active on this element
138
+ event: "keyup", // Trigger on key release
139
+ preventDefault: true
140
+ });
141
+
142
+ submit$.subscribe(() => console.log("Form submitted on Enter keyup!"));
143
+ ```
144
+
145
+ ### 6. Manage Contexts
88
146
 
89
147
  Control which shortcuts are active by setting the context.
90
148
 
@@ -96,27 +154,9 @@ keyManager.setContext("editor"); // Activates "editor" shortcuts and global shor
96
154
  keyManager.setContext(null);
97
155
  ```
98
156
 
99
- #### Global vs. Strict Global Shortcuts
100
-
101
- Global shortcuts (those without a `context` property) have two behaviors:
102
-
103
- * **Default Global**: By default, a global shortcut will fire in *any* context, unless a more specific shortcut for the same key combination exists for that context.
104
-
105
- ```typescript
106
- // This shortcut for Ctrl+P will fire in the "editor" context, "modal" context, or any other,
107
- // unless a specific "editor" shortcut for Ctrl+P exists.
108
- keyManager.addCombination({ id: 'globalPrint', keys: { key: Keys.P, ctrlKey: true }, callback: myCallback });
109
- ```
110
- * **Strict Global**: By passing `true` as the second argument to `addCombination` or `addSequence`, you can register a "strict" global shortcut. This shortcut will **only** fire when no context is active (`keyManager.getContext()` returns `null`).
111
-
112
- ```typescript
113
- // This help shortcut for "?" will ONLY fire when no other context is active.
114
- keyManager.addCombination({ id: 'strictHelp', keys: Keys.QuestionMark, callback: openHelpModal }, true);
115
- ```
157
+ ### 7. Clean Up
116
158
 
117
- ### 5. Clean Up
118
-
119
- When the Hotkeys instance is no longer needed (e.g., component unmount), call `destroy()` to clean up subscriptions and prevent memory leaks.
159
+ When the Hotkeys instance is no longer needed (e.g., component unmount), call `destroy()` to clean up all internal streams and listeners, preventing memory leaks. This will also `complete` all active shortcut Observables.
120
160
 
121
161
  ```typescript
122
162
  // In a component lifecycle cleanup method or similar:
@@ -128,8 +168,8 @@ keyManager.destroy();
128
168
 
129
169
  ### `Keys` Object & `StandardKey` Type
130
170
 
131
- * `Keys`: An exported constant object containing standard KeyboardEvent.key string values (e.g., Keys.Enter, Keys.ArrowUp, Keys.A). It's highly recommended to use these when defining key in `KeyCombinationConfig` or keys in the sequence array of `KeySequenceConfig`.
132
- * `StandardKey`: A TypeScript type representing any valid key string from the Keys object.
171
+ * `Keys`: An exported constant object containing standard `KeyboardEvent.key` string values (e.g., `Keys.Enter`, `Keys.ArrowUp`, `Keys.A`). It's highly recommended to use these for type safety and to avoid typos.
172
+ * `StandardKey`: A TypeScript type representing any valid key string from the `Keys` object.
133
173
 
134
174
  ### `Hotkeys` Class
135
175
 
@@ -137,39 +177,35 @@ keyManager.destroy();
137
177
 
138
178
  Creates a new Hotkeys instance.
139
179
 
140
- `addCombination(config: KeyCombinationConfig): string | undefined`
180
+ `addCombination(config: KeyCombinationConfig): Observable<KeyboardEvent>`
141
181
 
142
182
  Registers a key combination shortcut.
183
+ * `config`: The `KeyCombinationConfig` object.
184
+ * Returns an `Observable<KeyboardEvent>` that emits when the shortcut is triggered.
143
185
 
144
- * `config`: The KeyCombinationConfig object.
145
- * Returns the shortcut ID if successful, undefined otherwise.
146
-
147
- `addSequence(config: KeySequenceConfig): string | undefined`
186
+ `addSequence(config: KeySequenceConfig): Observable<KeyboardEvent>`
148
187
 
149
188
  Registers a key sequence shortcut.
150
-
151
- * `config`: The KeySequenceConfig object.
152
- * Returns the shortcut ID if successful, undefined otherwise.
189
+ * `config`: The `KeySequenceConfig` object.
190
+ * Returns an `Observable<KeyboardEvent>` that emits the final `KeyboardEvent` when the sequence is completed.
153
191
 
154
192
  `setContext(contextName: string | null): boolean`
155
193
 
156
- Sets the active context. Only shortcuts matching this context or global shortcuts (no context) will trigger.
194
+ Sets the active context. Only shortcuts matching this context or global shortcuts will trigger.
157
195
 
158
196
  `getContext(): string | null`
159
197
 
160
- Returns the current active context name, or null.
198
+ Returns the current active context name, or `null`.
161
199
 
162
200
  `remove(id: string): boolean`
163
201
 
164
- Removes a registered shortcut by its ID.
165
-
166
- * Returns true if found and removed, false otherwise.
202
+ Removes a registered shortcut by its ID. This will cause the corresponding Observable to complete.
203
+ * Returns `true` if found and removed, `false` otherwise.
167
204
 
168
205
  `hasShortcut(id: string): boolean`
169
206
 
170
207
  Checks if a shortcut with the given ID is registered.
171
-
172
- * Returns true if it exists, false otherwise.
208
+ * Returns `true` if it exists, `false` otherwise.
173
209
 
174
210
  `getActiveShortcuts(): { id: string; description?: string; context?: string | null; type: "combination" | "sequence" }[]`
175
211
 
@@ -185,32 +221,36 @@ Cleans up all subscriptions and resources. Essential to call to prevent memory l
185
221
 
186
222
  ### Configuration Interfaces
187
223
 
188
- `KeyCombinationConfig`
224
+ #### `KeyCombinationConfig`
189
225
 
190
226
  * `id: string` (required): Unique identifier for the shortcut.
191
- * `keys: { key: StandardKey; ctrlKey?: boolean; altKey?: boolean; shiftKey?: boolean; metaKey?: boolean; } | StandardKey | Array<{ key: StandardKey; ctrlKey?: boolean; altKey?: boolean; shiftKey?: boolean; metaKey?: boolean; } | StandardKey>` (required): Defines the main key (from Keys) and optional modifier keys.
192
- * `callback: (event: KeyboardEvent) => void` (required): Function to execute when the shortcut is triggered. The triggering `KeyboardEvent` is passed as an argument.
227
+ * `keys: string | KeyCombinationTrigger | KeyCombinationTrigger[]` (required): Defines the key(s). Can be a string (`"ctrl+s"`), a shorthand `StandardKey` (`Keys.Escape`), an object (`{ key: Keys.S, ctrlKey: true }`), or an array of these.
193
228
  * `context?: string | null`: Specifies the context in which this shortcut is active. If `null` or `undefined`, it's a global shortcut.
194
- * `preventDefault?: boolean`: If true, `event.preventDefault()` will be called when the shortcut triggers. Defaults to `false`.
229
+ * `preventDefault?: boolean`: If `true`, `event.preventDefault()` will be called when the shortcut triggers. Defaults to `false`.
195
230
  * `description?: string`: An optional description for the shortcut (e.g., for help menus).
196
231
  * `strict?: boolean` (optional): If `true` and the shortcut has no `context`, it will only fire when no other context is active. Defaults to `false`.
232
+ * `target?: HTMLElement` (optional): The DOM element to attach the listener to. Defaults to `document`.
233
+ * `event?: "keydown" | "keyup"` (optional): The keyboard event to listen for. Defaults to `"keydown"`.
234
+ * `callback?: (event: KeyboardEvent) => void` (**@deprecated**): This property is deprecated. Subscribe to the `Observable` returned by `addCombination` instead.
197
235
 
198
- `KeySequenceConfig`
236
+ #### `KeySequenceConfig`
199
237
 
200
238
  * `id: string` (required): Unique identifier.
201
- * `sequence: StandardKey[]` (required): An array of `StandardKey` values (from `Keys`) representing the key sequence.
202
- * `callback: (event: KeyboardEvent) => void` (required): Function to execute. The last `KeyboardEvent` of the sequence is passed.
239
+ * `sequence: string | StandardKey[]` (required): An array of `StandardKey` values or a string representation (e.g., `"g -> i"`).
203
240
  * `context?: string | null`: Context for activation.
204
- * `preventDefault?: boolean`: If true, `event.preventDefault()` is called for the last event in the sequence. Defaults to `false`.
241
+ * `preventDefault?: boolean`: If `true`, `event.preventDefault()` is called for the last event in the sequence. Defaults to `false`.
205
242
  * `description?: string`: Optional description.
206
- * `sequenceTimeoutMs?: number`: Optional. Maximum time (in milliseconds) allowed between consecutive key presses in the sequence. If exceeded, the sequence resets. If `0` or `undefined`, no inter-key timeout is applied (uses simpler buffer-based matching).
207
- * `strict?: boolean` (optional): If `true` and the shortcut has no `context`, it will only fire when no other context is active. Defaults to `false`.
243
+ * `sequenceTimeoutMs?: number`: Optional. Maximum time (in milliseconds) allowed between consecutive key presses in the sequence.
244
+ * `strict?: boolean` (optional): If `true` and the shortcut has no `context`, it will only fire when no other context is active.
245
+ * `target?: HTMLElement` (optional): The DOM element to attach the listener to. Defaults to `document`.
246
+ * `event?: "keydown" | "keyup"` (optional): The keyboard event to listen for. Defaults to `"keydown"`.
247
+ * `callback?: (event: KeyboardEvent) => void` (**@deprecated**): This property is deprecated. Subscribe to the `Observable` returned by `addSequence` instead.
208
248
 
249
+ ## Key Matching & Normalization
209
250
 
210
- ## Key Matching Logic
211
-
212
- * **Single Character Keys** (e.g., `Keys.A`, `Keys.Digit7`): When you configure a shortcut with a single character key from `Keys`, the library matches it case-insensitively against the `event.key` from the browser. For example, if you configure `Keys.A`, it will trigger for both "a" and "A" key presses (assuming Shift isn't a required modifier).
213
- * **Special Keys** (e.g., `Keys.Enter`, `Keys.ArrowUp`, `Keys.Escape`): These are multi-character `event.key` values. The library matches these case-sensitively against the `event.key`. Using the `Keys` object ensures you provide the correct, standard case-sensitive string.
251
+ * **Case Insensitivity**: The library automatically handles case for you. `keys: "a"` will match both "a" and "A" presses. `keys: "escape"` will match an event where `event.key` is `"Escape"`.
252
+ * **Aliases**: Common aliases are supported in string definitions, such as `cmd` for `Meta`, `option` for `Alt`, and `esc` for `Escape`.
253
+ * **Special Keys**: For full type-safety, it is recommended to use the exported `Keys` object (e.g., `Keys.Enter`, `Keys.ArrowUp`).
214
254
 
215
255
  ## Contributing
216
256
 
@@ -218,12 +258,10 @@ Contributions are welcome! Please feel free to submit issues, fork the repositor
218
258
 
219
259
  ## Development Setup
220
260
 
221
- 1. Clone the repository.
222
- 2. Install dependencies: `npm install`.
223
- 3. Run tests: `npm test`.
261
+ 1. Clone the repository.
262
+ 2. Install dependencies: `npm install`.
263
+ 3. Run tests: `npm test`.
224
264
 
225
- # License
265
+ ## License
226
266
 
227
267
  This project is licensed under the MIT License.
228
-
229
- Powered by AI
package/dist/hotkeys.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Subscription, Observable } from "rxjs";
1
+ import { Observable, Subject } from "rxjs";
2
2
  import { type StandardKey } from "./keys.js";
3
3
  export declare enum ShortcutTypes {
4
4
  Combination = "combination",
@@ -6,7 +6,10 @@ export declare enum ShortcutTypes {
6
6
  }
7
7
  interface ShortcutConfigBase {
8
8
  id: string;
9
- callback: (event: KeyboardEvent) => void;
9
+ /**
10
+ * @deprecated The callback property is deprecated. `addCombination` and `addSequence` now return an Observable. Please subscribe to it instead.
11
+ */
12
+ callback?: (event: KeyboardEvent) => void;
10
13
  context?: string | null;
11
14
  preventDefault?: boolean;
12
15
  description?: string;
@@ -19,6 +22,20 @@ interface ShortcutConfigBase {
19
22
  * @default false
20
23
  */
21
24
  strict?: boolean;
25
+ /**
26
+ * The DOM element to which the event listener for this shortcut will be attached.
27
+ * If not provided, the listener will be attached to the `document`.
28
+ * Useful for creating shortcuts that are only active within a specific component or area.
29
+ * @default document
30
+ */
31
+ target?: HTMLElement;
32
+ /**
33
+ * The type of keyboard event to listen for.
34
+ * Use "keydown" for actions that should happen immediately upon pressing a key.
35
+ * Use "keyup" for actions that should happen upon releasing a key.
36
+ * @default "keydown"
37
+ */
38
+ event?: "keydown" | "keyup";
22
39
  }
23
40
  /**
24
41
  * Defines a single key trigger, which can be a StandardKey (for simple presses like "Escape")
@@ -44,32 +61,34 @@ type KeyCombinationTrigger = {
44
61
  export interface KeyCombinationConfig extends ShortcutConfigBase {
45
62
  /**
46
63
  * Defines the key or key combination(s) that trigger the shortcut.
47
- * Can be a single trigger or an array of triggers.
48
- * Each trigger can be an object specifying the main `key` (from `StandardKey`) and optional
64
+ * Can be a single trigger, an array of triggers, or a string representation.
65
+ *
66
+ * **Object/Array:** Each trigger can be an object specifying the main `key` (from `StandardKey`) and optional
49
67
  * modifiers (`ctrlKey`, `altKey`, `shiftKey`, `metaKey`).
50
68
  * Example: `{ key: Keys.S, ctrlKey: true }` for Ctrl+S.
51
69
  *
52
- * Alternatively, for a simple key press without any modifiers, a trigger can be
53
- * a `StandardKey` directly.
54
- * Example: `Keys.Escape` for the Escape key. When using this shorthand,
55
- * it implies that no modifier keys (Ctrl, Alt, Shift, Meta) should be active.
70
+ * **Shorthand:** For a simple key press without modifiers, a trigger can be a `StandardKey` directly.
71
+ * Example: `Keys.Escape` for the Escape key.
72
+ *
73
+ * **String:** A human-readable string like `"ctrl+s"` or `"shift+alt+k"`. Modifiers are joined by `+`.
74
+ * Example: `"meta+k"`, `"ctrl+shift+?"`
56
75
  *
57
76
  * To define multiple triggers for the same action:
58
77
  * Example: `keys: [Keys.Enter, { key: Keys.Space, ctrlKey: true }]`
59
78
  */
60
- keys: KeyCombinationTrigger | KeyCombinationTrigger[];
79
+ keys: KeyCombinationTrigger | KeyCombinationTrigger[] | string;
61
80
  }
62
81
  export interface KeySequenceConfig extends ShortcutConfigBase {
63
82
  /**
64
- * An array of keys that form the sequence.
65
- * Each key in the sequence MUST be a value from the exported `Keys` object
66
- * (e.g., `Keys.ArrowUp`, `Keys.G`, `Keys.Digit1`).
67
- * The library handles case-insensitivity for single character keys automatically
68
- * when comparing with the actual browser event's `event.key`.
69
- * Refer to: https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values
70
- * Example: [Keys.Control, Keys.Alt, Keys.Delete] or [Keys.G, Keys.I]
83
+ * An array of keys or a string defining the sequence.
84
+ *
85
+ * **Array:** Each key in the sequence MUST be a value from `Keys`.
86
+ * Example: `[Keys.G, Keys.I]`
87
+ *
88
+ * **String:** A string where keys are separated by `->`.
89
+ * Example: `"g -> i"`, `"up -> up -> down -> down"`
71
90
  */
72
- sequence: StandardKey[];
91
+ sequence: StandardKey[] | string;
73
92
  /**
74
93
  * Optional: Timeout in milliseconds between consecutive key presses in the sequence.
75
94
  * If the time between two keys in the sequence exceeds this value, the sequence attempt is reset.
@@ -81,7 +100,7 @@ type ShortcutConfig = KeyCombinationConfig | KeySequenceConfig;
81
100
  export interface ActiveShortcut {
82
101
  id: string;
83
102
  config: ShortcutConfig;
84
- subscription: Subscription;
103
+ terminator$: Subject<void>;
85
104
  }
86
105
  /**
87
106
  * Manages keyboard shortcuts for web applications.
@@ -90,8 +109,10 @@ export interface ActiveShortcut {
90
109
  */
91
110
  export declare class Hotkeys {
92
111
  private static readonly KEYDOWN_EVENT;
112
+ private static readonly KEYUP_EVENT;
93
113
  private static readonly LOG_PREFIX;
94
- private keydown$;
114
+ private keydownStreams;
115
+ private keyupStreams;
95
116
  private activeContext$;
96
117
  private activeShortcuts;
97
118
  private debugMode;
@@ -102,6 +123,13 @@ export declare class Hotkeys {
102
123
  * @throws Error if not in a browser environment (i.e., `document` or `performance` is undefined).
103
124
  */
104
125
  constructor(initialContext?: string | null, debugMode?: boolean);
126
+ /**
127
+ * Gets or creates a shared event stream for a given event type and target.
128
+ * @param eventType The type of event ("keydown" or "keyup").
129
+ * @param target The DOM element to attach the listener to.
130
+ * @returns A shared Observable for the specified event.
131
+ */
132
+ private _getEventStream;
105
133
  /**
106
134
  * Sets the active context for shortcuts.
107
135
  * Only shortcuts matching this context (or shortcuts with no specific context defined)
@@ -172,57 +200,80 @@ export declare class Hotkeys {
172
200
  * @returns An object containing configuredMainKey and modifier states, or null if parsing fails.
173
201
  */
174
202
  private _parseKeyTrigger;
203
+ private _parseCombinationString;
204
+ private _parseSequenceString;
175
205
  /**
176
- * Registers a key combination shortcut (e.g., Ctrl+S, Shift+Enter, or a single key like Escape).
177
- * The callback is triggered when the specified key and modifier keys (if any) are pressed.
206
+ * Registers a key combination shortcut (e.g., Ctrl+S, Shift+Enter, or a single key like Escape)
207
+ * and returns an Observable that emits the `KeyboardEvent` when the combination is triggered.
178
208
  * @param config - Configuration object for the key combination.
179
209
  * See {@link KeyCombinationConfig} for details.
180
- * The `key` property (or the direct `StandardKey` if using shorthand) must be a value from the `Keys` object.
181
- * @returns The ID of the registered shortcut if successful, or `undefined` if the configuration is invalid.
182
- * A warning is logged to the console if the configuration is invalid or if a shortcut with the same ID is overwritten.
210
+ * @returns An `Observable<KeyboardEvent>` that you can subscribe to. The stream will be automatically
211
+ * completed if the shortcut is removed via `remove(id)` or `destroy()`, or if it's overwritten.
212
+ * If the configuration is invalid, an empty Observable is returned and a warning is logged.
183
213
  * @example
184
214
  * ```typescript
185
215
  * import { Keys } from "./keys";
186
216
  * // For Ctrl+S
187
- * keyManager.addCombination({
217
+ * const save$ = keyManager.addCombination({
188
218
  * id: "saveFile",
189
219
  * keys: { key: Keys.S, ctrlKey: true },
190
- * callback: () => console.log("File saved!"),
191
220
  * context: "editor"
192
221
  * });
222
+ * save$.subscribe(event => console.log("File saved!", event));
223
+ *
224
+ * // For Ctrl+S using a string
225
+ * const save$ = keyManager.addCombination({ id: "saveFile", keys: "ctrl+s" });
226
+ * save$.subscribe(event => console.log("File saved!", event));
227
+ *
193
228
  * // For just the Escape key, or Ctrl+Space
194
- * keyManager.addCombination({
229
+ * const close$ = keyManager.addCombination({
195
230
  * id: "closeModal",
196
231
  * keys: [Keys.Escape, {key: Keys.Space, ctrlKey: true}],
197
- * callback: () => console.log("Modal closed!")
198
232
  * });
233
+ * close$.subscribe(() => console.log("Modal closed!"));
234
+ *
235
+ * // For the Escape key on a specific element
236
+ * const myModal = document.getElementById("my-modal");
237
+ * const close$ = keyManager.addCombination({ id: "closeModal", keys: Keys.Escape, target: myModal });
238
+ * close$.subscribe(() => console.log("Modal closed!"));
199
239
  * ```
200
240
  */
201
- addCombination(config: KeyCombinationConfig): string | undefined;
241
+ addCombination(config: KeyCombinationConfig): Observable<KeyboardEvent>;
202
242
  /**
203
- * Registers a key sequence shortcut (e.g., g -> i, or ArrowUp -> ArrowUp -> ArrowDown).
204
- * The callback is triggered when the specified keys are pressed in order.
243
+ * Registers a key sequence shortcut (e.g., g -> i, or ArrowUp -> ArrowUp -> ArrowDown)
244
+ * and returns an Observable that emits the final `KeyboardEvent` of the sequence when it's completed.
205
245
  * An optional timeout can be specified for the time allowed between key presses in the sequence.
206
246
  * @param config - Configuration object for the key sequence.
207
247
  * See {@link KeySequenceConfig} for details.
208
248
  * Each key in the `sequence` array must be a value from the `Keys` object.
209
- * @returns The ID of the registered shortcut if successful, or `undefined` if the configuration is invalid (e.g., empty sequence or invalid keys).
210
- * A warning is logged to the console if the configuration is invalid or if a shortcut with the same ID is overwritten.
249
+ * Or using string for `sequence`.
250
+ * @returns An `Observable<KeyboardEvent>` that you can subscribe to. The stream will be automatically
251
+ * completed if the shortcut is removed via `remove(id)` or `destroy()`, or if it's overwritten.
252
+ * If the configuration is invalid, an empty Observable is returned and a warning is logged.
211
253
  * @example
212
254
  * ```typescript
213
255
  * import { Keys } from "./keys";
214
- * keyManager.addSequence({
256
+ * const konami$ = keyManager.addSequence({
215
257
  * id: "konamiCode",
216
258
  * sequence: [Keys.ArrowUp, Keys.ArrowUp, Keys.ArrowDown, Keys.ArrowDown, Keys.A, Keys.B],
217
- * callback: () => console.log("Konami!"),
218
259
  * sequenceTimeoutMs: 2000 // 2 seconds between keys
219
260
  * });
261
+ * konami$.subscribe(event => console.log("Konami!", event));
262
+ * ```
263
+ * ```typescript
264
+ * // Using a string for the sequence
265
+ * const konami$ = keyManager.addSequence({
266
+ * id: "konamiCode",
267
+ * sequence: "up -> up -> down -> down -> a -> b",
268
+ * sequenceTimeoutMs: 2000
269
+ * });
270
+ * konami$.subscribe(event => console.log("Konami!", event));
220
271
  * ```
221
272
  */
222
- addSequence(config: KeySequenceConfig): string | undefined;
273
+ addSequence(config: KeySequenceConfig): Observable<KeyboardEvent>;
223
274
  /**
224
275
  * Removes a registered shortcut by its ID.
225
- * This will unsubscribe from the underlying keyboard event stream for that shortcut.
276
+ * This will complete the corresponding Observable stream for any subscribers.
226
277
  * @param id - The unique ID of the shortcut to remove.
227
278
  * @returns True if the shortcut was found and removed, false otherwise.
228
279
  * A warning is logged to the console if no shortcut with the given ID is found.
@@ -1 +1 @@
1
- {"version":3,"file":"hotkeys.d.ts","sourceRoot":"","sources":["../src/hotkeys.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"hotkeys.d.ts","sourceRoot":"","sources":["../src/hotkeys.ts"],"names":[],"mappings":"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"}