rx-hotkeys 1.0.0 → 2.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 +7 -7
- package/dist/hotkeys.d.ts +1 -1
- package/dist/hotkeys.js +24 -24
- package/dist/hotkeys.test.js +4 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +7 -2
package/README.md
CHANGED
|
@@ -26,18 +26,18 @@ npm install rxjs rx-hotkeys
|
|
|
26
26
|
First, ensure you have the `rx-hotkeys` library and its helper Keys imported:
|
|
27
27
|
|
|
28
28
|
```typescript
|
|
29
|
-
import {
|
|
29
|
+
import { Hotkeys, Keys, KeyCombinationConfig, KeySequenceConfig } from 'rx-hotkeys';
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
1. Initialize
|
|
32
|
+
1. Initialize Hotkeys
|
|
33
33
|
|
|
34
34
|
Create an instance of the `Hotkeys` class. You can optionally provide an initial context and enable debug mode.
|
|
35
35
|
|
|
36
36
|
```typescript
|
|
37
|
-
const keyManager = new
|
|
37
|
+
const keyManager = new Hotkeys(); // No initial context, debug mode off
|
|
38
38
|
|
|
39
39
|
// With an initial context and debug mode enabled:
|
|
40
|
-
// const keyManager = new
|
|
40
|
+
// const keyManager = new Hotkeys('editor', true);
|
|
41
41
|
```
|
|
42
42
|
|
|
43
43
|
2. Add a Key Combination
|
|
@@ -93,7 +93,7 @@ keyManager.setContext(null);
|
|
|
93
93
|
|
|
94
94
|
5. Clean Up
|
|
95
95
|
|
|
96
|
-
When the
|
|
96
|
+
When the Hotkeys instance is no longer needed (e.g., component unmount), call `destroy()` to clean up subscriptions and prevent memory leaks.
|
|
97
97
|
|
|
98
98
|
```typescript
|
|
99
99
|
// In a component lifecycle cleanup method or similar:
|
|
@@ -108,11 +108,11 @@ keyManager.destroy();
|
|
|
108
108
|
* `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`.
|
|
109
109
|
* `StandardKey`: A TypeScript type representing any valid key string from the Keys object.
|
|
110
110
|
|
|
111
|
-
### `
|
|
111
|
+
### `Hotkeys` Class
|
|
112
112
|
|
|
113
113
|
`constructor(initialContext?: string | null, debugMode?: boolean)`
|
|
114
114
|
|
|
115
|
-
Creates a new
|
|
115
|
+
Creates a new Hotkeys instance.
|
|
116
116
|
|
|
117
117
|
`addCombination(config: KeyCombinationConfig): string | undefined`
|
|
118
118
|
|
package/dist/hotkeys.d.ts
CHANGED
|
@@ -48,7 +48,7 @@ export interface KeySequenceConfig extends ShortcutConfigBase {
|
|
|
48
48
|
* Allows registration of single key combinations (e.g., Ctrl+S) and key sequences (e.g., g -> i).
|
|
49
49
|
* Supports contexts to enable/disable shortcuts based on application state.
|
|
50
50
|
*/
|
|
51
|
-
export declare class
|
|
51
|
+
export declare class Hotkeys {
|
|
52
52
|
private static readonly KEYDOWN_EVENT;
|
|
53
53
|
private static readonly LOG_PREFIX;
|
|
54
54
|
private keydown$;
|
package/dist/hotkeys.js
CHANGED
|
@@ -20,7 +20,7 @@ function compareKey(eventKey, configuredKey) {
|
|
|
20
20
|
* Allows registration of single key combinations (e.g., Ctrl+S) and key sequences (e.g., g -> i).
|
|
21
21
|
* Supports contexts to enable/disable shortcuts based on application state.
|
|
22
22
|
*/
|
|
23
|
-
export class
|
|
23
|
+
export class Hotkeys {
|
|
24
24
|
static KEYDOWN_EVENT = "keydown";
|
|
25
25
|
static LOG_PREFIX = "Hotkeys:";
|
|
26
26
|
keydown$;
|
|
@@ -36,13 +36,13 @@ export class HotKeys {
|
|
|
36
36
|
constructor(initialContext = null, debugMode = false) {
|
|
37
37
|
this.debugMode = debugMode;
|
|
38
38
|
if (typeof document === "undefined" || typeof performance === "undefined") {
|
|
39
|
-
throw new Error(`${
|
|
39
|
+
throw new Error(`${Hotkeys.LOG_PREFIX} Hotkeys can only be used in a browser environment with global 'document' and 'performance' objects.`);
|
|
40
40
|
}
|
|
41
|
-
this.keydown$ = fromEvent(document,
|
|
41
|
+
this.keydown$ = fromEvent(document, Hotkeys.KEYDOWN_EVENT);
|
|
42
42
|
this.activeContext$ = new BehaviorSubject(initialContext);
|
|
43
43
|
this.activeShortcuts = new Map();
|
|
44
44
|
if (this.debugMode) {
|
|
45
|
-
console.log(`${
|
|
45
|
+
console.log(`${Hotkeys.LOG_PREFIX} Library initialized. Initial context: "${initialContext}". Debug mode: ${debugMode}.`);
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
/**
|
|
@@ -54,7 +54,7 @@ export class HotKeys {
|
|
|
54
54
|
*/
|
|
55
55
|
setContext(contextName) {
|
|
56
56
|
if (this.debugMode) {
|
|
57
|
-
console.log(`${
|
|
57
|
+
console.log(`${Hotkeys.LOG_PREFIX} Context changed to "${contextName}"`);
|
|
58
58
|
}
|
|
59
59
|
this.activeContext$.next(contextName);
|
|
60
60
|
}
|
|
@@ -73,7 +73,7 @@ export class HotKeys {
|
|
|
73
73
|
setDebugMode(enable) {
|
|
74
74
|
this.debugMode = enable;
|
|
75
75
|
if (this.debugMode) {
|
|
76
|
-
console.log(`${
|
|
76
|
+
console.log(`${Hotkeys.LOG_PREFIX} Debug mode ${enable ? 'enabled' : 'disabled'}.`);
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
79
|
/**
|
|
@@ -90,12 +90,12 @@ export class HotKeys {
|
|
|
90
90
|
_registerShortcut(config, subscription, type, detailsForLog) {
|
|
91
91
|
const existingShortcut = this.activeShortcuts.get(config.id);
|
|
92
92
|
if (existingShortcut) {
|
|
93
|
-
console.warn(`${
|
|
93
|
+
console.warn(`${Hotkeys.LOG_PREFIX} Shortcut with ID "${config.id}" already exists. It will be overwritten.`);
|
|
94
94
|
existingShortcut.subscription.unsubscribe();
|
|
95
95
|
}
|
|
96
96
|
this.activeShortcuts.set(config.id, { id: config.id, config, subscription });
|
|
97
97
|
if (this.debugMode) {
|
|
98
|
-
console.log(`${
|
|
98
|
+
console.log(`${Hotkeys.LOG_PREFIX} ${type} shortcut "${config.id}" added. ${detailsForLog}, Context: ${config.context ?? "any"}`);
|
|
99
99
|
}
|
|
100
100
|
return config.id;
|
|
101
101
|
}
|
|
@@ -121,7 +121,7 @@ export class HotKeys {
|
|
|
121
121
|
addCombination(config) {
|
|
122
122
|
const { keys, callback, context, preventDefault = false, id } = config;
|
|
123
123
|
if (!keys || !keys.key || typeof keys.key !== 'string' || keys.key.trim() === '') {
|
|
124
|
-
console.warn(`${
|
|
124
|
+
console.warn(`${Hotkeys.LOG_PREFIX} Invalid 'keys.key' for combination shortcut "${id}". Key must be a non-empty value from Keys. Shortcut not added.`);
|
|
125
125
|
return undefined;
|
|
126
126
|
}
|
|
127
127
|
const configuredMainKey = keys.key;
|
|
@@ -131,12 +131,12 @@ export class HotKeys {
|
|
|
131
131
|
(keys.metaKey === undefined || event.metaKey === keys.metaKey)), filter(event => compareKey(event.key, configuredMainKey)), tap(event => {
|
|
132
132
|
if (this.debugMode) {
|
|
133
133
|
const preventAction = preventDefault ? ", preventing default" : "";
|
|
134
|
-
console.log(`${
|
|
134
|
+
console.log(`${Hotkeys.LOG_PREFIX} Combination "${id}" triggered${preventAction}.`);
|
|
135
135
|
}
|
|
136
136
|
if (preventDefault)
|
|
137
137
|
event.preventDefault();
|
|
138
138
|
}), catchError(err => {
|
|
139
|
-
console.error(`${
|
|
139
|
+
console.error(`${Hotkeys.LOG_PREFIX} Error in combination stream for shortcut "${id}":`, err);
|
|
140
140
|
return EMPTY;
|
|
141
141
|
}));
|
|
142
142
|
const subscription = shortcut$.subscribe(event => {
|
|
@@ -144,7 +144,7 @@ export class HotKeys {
|
|
|
144
144
|
callback(event);
|
|
145
145
|
}
|
|
146
146
|
catch (e) {
|
|
147
|
-
console.error(`${
|
|
147
|
+
console.error(`${Hotkeys.LOG_PREFIX} Error in user callback for combination shortcut "${id}":`, e);
|
|
148
148
|
}
|
|
149
149
|
});
|
|
150
150
|
const keyDetails = `key: "${keys.key}"` +
|
|
@@ -177,11 +177,11 @@ export class HotKeys {
|
|
|
177
177
|
addSequence(config) {
|
|
178
178
|
const { sequence, callback, context, preventDefault = false, id, sequenceTimeoutMs } = config;
|
|
179
179
|
if (!Array.isArray(sequence) || sequence.length === 0) {
|
|
180
|
-
console.warn(`${
|
|
180
|
+
console.warn(`${Hotkeys.LOG_PREFIX} Sequence for shortcut "${id}" is empty or invalid. Shortcut not added.`);
|
|
181
181
|
return undefined;
|
|
182
182
|
}
|
|
183
183
|
if (sequence.some(key => typeof key !== 'string' || key.trim() === '')) {
|
|
184
|
-
console.warn(`${
|
|
184
|
+
console.warn(`${Hotkeys.LOG_PREFIX} Invalid key in sequence for shortcut "${id}". All keys must be non-empty strings from Keys. Shortcut not added.`);
|
|
185
185
|
return undefined;
|
|
186
186
|
}
|
|
187
187
|
const configuredSequence = sequence;
|
|
@@ -198,7 +198,7 @@ export class HotKeys {
|
|
|
198
198
|
}
|
|
199
199
|
if (matchedEvents.length > 0 && (currentTime - lastEventTime > sequenceTimeoutMs)) {
|
|
200
200
|
if (this.debugMode) {
|
|
201
|
-
console.log(`${
|
|
201
|
+
console.log(`${Hotkeys.LOG_PREFIX} Sequence "${id}" (timeout: ${sequenceTimeoutMs}ms) attempt timed out. Matched: ${matchedEvents.map(e => e.key).join(',')}. Resetting.`);
|
|
202
202
|
}
|
|
203
203
|
matchedEvents = [];
|
|
204
204
|
}
|
|
@@ -213,7 +213,7 @@ export class HotKeys {
|
|
|
213
213
|
const newMatchedEvents = [...matchedEvents, event];
|
|
214
214
|
if (newMatchedEvents.length === sequenceLength) {
|
|
215
215
|
if (this.debugMode)
|
|
216
|
-
console.log(`${
|
|
216
|
+
console.log(`${Hotkeys.LOG_PREFIX} Sequence "${id}" (timeout: ${sequenceTimeoutMs}ms) matched.`);
|
|
217
217
|
return { matchedEvents: newMatchedEvents, lastEventTime: currentTime, emitState: 'emit' };
|
|
218
218
|
}
|
|
219
219
|
else {
|
|
@@ -222,7 +222,7 @@ export class HotKeys {
|
|
|
222
222
|
}
|
|
223
223
|
else {
|
|
224
224
|
if (matchedEvents.length > 0 && this.debugMode) {
|
|
225
|
-
console.log(`${
|
|
225
|
+
console.log(`${Hotkeys.LOG_PREFIX} Sequence "${id}" (timeout: ${sequenceTimeoutMs}ms) broken by key "${event.key}". Matched: ${matchedEvents.map(e => e.key).join(',')}. Resetting.`);
|
|
226
226
|
}
|
|
227
227
|
if (sequenceLength > 0 && compareKey(event.key, configuredSequence[0])) {
|
|
228
228
|
return { matchedEvents: [event], lastEventTime: currentTime, emitState: 'in-progress' };
|
|
@@ -244,13 +244,13 @@ export class HotKeys {
|
|
|
244
244
|
if (this.debugMode) {
|
|
245
245
|
const timeoutInfo = (sequenceTimeoutMs && sequenceTimeoutMs > 0) ? ` (with timeout logic)` : ` (no timeout logic)`;
|
|
246
246
|
const preventAction = preventDefault ? ", preventing default for last event" : "";
|
|
247
|
-
console.log(`${
|
|
247
|
+
console.log(`${Hotkeys.LOG_PREFIX} Sequence "${id}" triggered${timeoutInfo}${preventAction}.`);
|
|
248
248
|
}
|
|
249
249
|
if (preventDefault && events.length > 0) {
|
|
250
250
|
events[events.length - 1].preventDefault();
|
|
251
251
|
}
|
|
252
252
|
}), catchError(err => {
|
|
253
|
-
console.error(`${
|
|
253
|
+
console.error(`${Hotkeys.LOG_PREFIX} Error in sequence stream for shortcut "${id}":`, err);
|
|
254
254
|
return EMPTY;
|
|
255
255
|
}));
|
|
256
256
|
const subscription = finalShortcut$.subscribe((events) => {
|
|
@@ -259,7 +259,7 @@ export class HotKeys {
|
|
|
259
259
|
callback(events[events.length - 1]);
|
|
260
260
|
}
|
|
261
261
|
catch (e) {
|
|
262
|
-
console.error(`${
|
|
262
|
+
console.error(`${Hotkeys.LOG_PREFIX} Error in user callback for sequence shortcut "${id}":`, e);
|
|
263
263
|
}
|
|
264
264
|
});
|
|
265
265
|
const logDetails = `Sequence: ${sequence.join(" -> ")}${sequenceTimeoutMs && sequenceTimeoutMs > 0 ? ` (timeout: ${sequenceTimeoutMs}ms)` : ''}`;
|
|
@@ -278,10 +278,10 @@ export class HotKeys {
|
|
|
278
278
|
shortcut.subscription.unsubscribe();
|
|
279
279
|
this.activeShortcuts.delete(id);
|
|
280
280
|
if (this.debugMode)
|
|
281
|
-
console.log(`${
|
|
281
|
+
console.log(`${Hotkeys.LOG_PREFIX} Shortcut "${id}" removed.`);
|
|
282
282
|
return true;
|
|
283
283
|
}
|
|
284
|
-
console.warn(`${
|
|
284
|
+
console.warn(`${Hotkeys.LOG_PREFIX} Shortcut with ID "${id}" not found for removal.`);
|
|
285
285
|
return false;
|
|
286
286
|
}
|
|
287
287
|
/**
|
|
@@ -311,11 +311,11 @@ export class HotKeys {
|
|
|
311
311
|
*/
|
|
312
312
|
destroy() {
|
|
313
313
|
if (this.debugMode)
|
|
314
|
-
console.log(`${
|
|
314
|
+
console.log(`${Hotkeys.LOG_PREFIX} Destroying library instance and unsubscribing all shortcuts.`);
|
|
315
315
|
this.activeShortcuts.forEach(shortcut => shortcut.subscription.unsubscribe());
|
|
316
316
|
this.activeShortcuts.clear();
|
|
317
317
|
this.activeContext$.complete(); // Complete the BehaviorSubject to release its resources
|
|
318
318
|
if (this.debugMode)
|
|
319
|
-
console.log(`${
|
|
319
|
+
console.log(`${Hotkeys.LOG_PREFIX} Library destroyed.`);
|
|
320
320
|
}
|
|
321
321
|
}
|
package/dist/hotkeys.test.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, it, before, beforeEach, afterEach, mock } from "node:test";
|
|
2
2
|
import assert from "node:assert";
|
|
3
3
|
// Importing main library components
|
|
4
|
-
import {
|
|
4
|
+
import { Hotkeys } from "./hotkeys.js";
|
|
5
5
|
// Importing Keys and StandardKey from the separate keys.js file
|
|
6
6
|
import { Keys } from "./keys.js";
|
|
7
7
|
import { fromEvent, BehaviorSubject } from "rxjs";
|
|
@@ -49,7 +49,7 @@ describe("Hotkeys Library (Node.js Test Runner)", () => {
|
|
|
49
49
|
let consoleErrorMock;
|
|
50
50
|
let performanceNowMock; // To mock global.performance.now specifically for sequence tests
|
|
51
51
|
beforeEach(() => {
|
|
52
|
-
keyManager = new
|
|
52
|
+
keyManager = new Hotkeys(null, false);
|
|
53
53
|
mockCallback = createMockFn();
|
|
54
54
|
consoleWarnMock = mock.method(console, "warn");
|
|
55
55
|
consoleErrorMock = mock.method(console, "error");
|
|
@@ -72,13 +72,13 @@ describe("Hotkeys Library (Node.js Test Runner)", () => {
|
|
|
72
72
|
});
|
|
73
73
|
describe("Initialization and Basic Context", () => {
|
|
74
74
|
it("should initialize without errors", () => {
|
|
75
|
-
assert(keyManager instanceof
|
|
75
|
+
assert(keyManager instanceof Hotkeys);
|
|
76
76
|
});
|
|
77
77
|
it("should initialize with a null context by default", () => {
|
|
78
78
|
assert.strictEqual(keyManager.getContext(), null);
|
|
79
79
|
});
|
|
80
80
|
it("should initialize with a given initial context", () => {
|
|
81
|
-
const manager = new
|
|
81
|
+
const manager = new Hotkeys("editor");
|
|
82
82
|
assert.strictEqual(manager.getContext(), "editor");
|
|
83
83
|
manager.destroy();
|
|
84
84
|
});
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { type StandardKey, Keys, } from "./keys.js";
|
|
2
|
-
export { type KeyCombinationConfig, type KeySequenceConfig,
|
|
2
|
+
export { type KeyCombinationConfig, type KeySequenceConfig, Hotkeys, } from "./hotkeys.js";
|
|
3
3
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { Keys, } from "./keys.js";
|
|
2
|
-
export {
|
|
2
|
+
export { Hotkeys, } from "./hotkeys.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rx-hotkeys",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"build": "tsc",
|
|
@@ -14,7 +14,12 @@
|
|
|
14
14
|
"types": "./dist/index.d.ts"
|
|
15
15
|
}
|
|
16
16
|
},
|
|
17
|
-
"keywords": [
|
|
17
|
+
"keywords": [
|
|
18
|
+
"rxjs",
|
|
19
|
+
"hotkeys",
|
|
20
|
+
"hotkey",
|
|
21
|
+
"key"
|
|
22
|
+
],
|
|
18
23
|
"author": "Colin Cheng <zbinlin@outlook.com>",
|
|
19
24
|
"repository": {
|
|
20
25
|
"type": "git",
|