rx-hotkeys 1.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.
@@ -0,0 +1,360 @@
1
+ import { describe, it, before, beforeEach, afterEach, mock } from "node:test";
2
+ import assert from "node:assert";
3
+ // Importing main library components
4
+ import { HotKeys } from "./hotkeys.js";
5
+ // Importing Keys and StandardKey from the separate keys.js file
6
+ import { Keys } from "./keys.js";
7
+ import { fromEvent, BehaviorSubject } from "rxjs";
8
+ import { createMockFn, dispatchKeyEvent } from "./testutils.js";
9
+ import { JSDOM } from "jsdom";
10
+ // --- JSDOM and RxJS setup for Node.js tests ---
11
+ let dom;
12
+ let window;
13
+ let document;
14
+ let originalPerformanceNow;
15
+ before(() => {
16
+ dom = new JSDOM("<!DOCTYPE html><html><body></body></html>", {
17
+ url: "http://localhost",
18
+ });
19
+ window = dom.window;
20
+ document = window.document;
21
+ // @ts-ignore
22
+ global.document = document;
23
+ // @ts-ignore
24
+ global.KeyboardEvent = window.KeyboardEvent;
25
+ // @ts-ignore
26
+ global.BehaviorSubject = BehaviorSubject;
27
+ // @ts-ignore
28
+ global.fromEvent = fromEvent;
29
+ // @ts-ignore
30
+ if (typeof global.performance === 'undefined') {
31
+ // @ts-ignore
32
+ global.performance = {};
33
+ }
34
+ // @ts-ignore
35
+ originalPerformanceNow = global.performance.now;
36
+ // @ts-ignore
37
+ if (typeof global.performance.now !== 'function') {
38
+ // @ts-ignore
39
+ global.performance.now = (() => {
40
+ const start = Date.now();
41
+ return () => Date.now() - start;
42
+ })();
43
+ }
44
+ });
45
+ describe("Hotkeys Library (Node.js Test Runner)", () => {
46
+ let keyManager;
47
+ let mockCallback;
48
+ let consoleWarnMock;
49
+ let consoleErrorMock;
50
+ let performanceNowMock; // To mock global.performance.now specifically for sequence tests
51
+ beforeEach(() => {
52
+ keyManager = new HotKeys(null, false);
53
+ mockCallback = createMockFn();
54
+ consoleWarnMock = mock.method(console, "warn");
55
+ consoleErrorMock = mock.method(console, "error");
56
+ });
57
+ afterEach(() => {
58
+ if (keyManager) {
59
+ keyManager.destroy();
60
+ }
61
+ mockCallback.mockClear();
62
+ mock.reset();
63
+ if (consoleWarnMock && consoleWarnMock.mock)
64
+ consoleWarnMock.mock.restore();
65
+ if (consoleErrorMock && consoleErrorMock.mock)
66
+ consoleErrorMock.mock.restore();
67
+ // @ts-ignore
68
+ if (global.performance && global.performance.now !== originalPerformanceNow) {
69
+ // @ts-ignore
70
+ global.performance.now = originalPerformanceNow;
71
+ }
72
+ });
73
+ describe("Initialization and Basic Context", () => {
74
+ it("should initialize without errors", () => {
75
+ assert(keyManager instanceof HotKeys);
76
+ });
77
+ it("should initialize with a null context by default", () => {
78
+ assert.strictEqual(keyManager.getContext(), null);
79
+ });
80
+ it("should initialize with a given initial context", () => {
81
+ const manager = new HotKeys("editor");
82
+ assert.strictEqual(manager.getContext(), "editor");
83
+ manager.destroy();
84
+ });
85
+ it("should set and get context", () => {
86
+ keyManager.setContext("modal");
87
+ assert.strictEqual(keyManager.getContext(), "modal");
88
+ keyManager.setContext(null);
89
+ assert.strictEqual(keyManager.getContext(), null);
90
+ });
91
+ it("should toggle debug mode and log appropriately", () => {
92
+ const consoleLogMock = mock.method(console, "log");
93
+ keyManager.setDebugMode(true);
94
+ keyManager.setContext("debug_test");
95
+ assert.ok(consoleLogMock.mock.calls.some(call => call.arguments[0].includes('Context changed to "debug_test"')));
96
+ consoleLogMock.mock.resetCalls();
97
+ keyManager.setDebugMode(false);
98
+ keyManager.setContext("no_debug_test");
99
+ assert.ok(!consoleLogMock.mock.calls.some(call => call.arguments[0].includes('Context changed to "no_debug_test"')));
100
+ consoleLogMock.mock.restore();
101
+ });
102
+ });
103
+ describe("addCombination", () => {
104
+ it("should trigger callback for a simple key combination (e.g., 'A')", () => {
105
+ const config = { id: "simpleA", keys: { key: Keys.A }, callback: mockCallback };
106
+ const result = keyManager.addCombination(config);
107
+ assert.strictEqual(result, "simpleA");
108
+ dispatchKeyEvent("a");
109
+ assert.strictEqual(mockCallback.calledCount, 1, "Callback for 'a' not called");
110
+ mockCallback.mockClear();
111
+ dispatchKeyEvent("A");
112
+ assert.strictEqual(mockCallback.calledCount, 1, "Callback for 'A' not called");
113
+ });
114
+ it("should return undefined and warn if keys.key is null or undefined (runtime check)", () => {
115
+ // This test checks runtime robustness if `any` is used to bypass StandardKey
116
+ const config = { id: "nullKey", keys: { key: null }, callback: mockCallback };
117
+ const result = keyManager.addCombination(config);
118
+ assert.strictEqual(result, undefined, "Should return undefined for null key");
119
+ assert.strictEqual(consoleWarnMock.mock.calls.length, 1);
120
+ assert.ok(consoleWarnMock.mock.calls[0].arguments[0].includes('Invalid \'keys.key\' for combination shortcut "nullKey"'));
121
+ });
122
+ it("should pass the KeyboardEvent to the callback", () => {
123
+ const config = { id: "eventPass", keys: { key: Keys.E }, callback: mockCallback };
124
+ keyManager.addCombination(config);
125
+ const event = dispatchKeyEvent("e");
126
+ assert.strictEqual(mockCallback.calledCount, 1);
127
+ assert.deepStrictEqual(mockCallback.lastArgs, [event]);
128
+ });
129
+ it("should trigger callback for a combination with Ctrl key", () => {
130
+ const config = { id: "ctrlS", keys: { key: Keys.S, ctrlKey: true }, callback: mockCallback };
131
+ keyManager.addCombination(config);
132
+ dispatchKeyEvent("s", { ctrlKey: true });
133
+ assert.strictEqual(mockCallback.calledCount, 1);
134
+ });
135
+ it("should NOT trigger callback if specified modifier key (ctrlKey: false) is false and event has it true", () => {
136
+ const config = { id: "noCtrlA", keys: { key: Keys.A, ctrlKey: false }, callback: mockCallback };
137
+ keyManager.addCombination(config);
138
+ dispatchKeyEvent("a", { ctrlKey: true });
139
+ assert.strictEqual(mockCallback.calledCount, 0);
140
+ dispatchKeyEvent("a", { ctrlKey: false });
141
+ assert.strictEqual(mockCallback.calledCount, 1);
142
+ });
143
+ it("should trigger for special keys like Escape", () => {
144
+ const config = { id: "escapeKey", keys: { key: Keys.Escape }, callback: mockCallback };
145
+ keyManager.addCombination(config);
146
+ dispatchKeyEvent("Escape"); // Event key matches Keys.Escape
147
+ assert.strictEqual(mockCallback.calledCount, 1);
148
+ });
149
+ it("should handle preventDefault correctly", () => {
150
+ const config = { id: "preventA", keys: { key: Keys.A }, callback: mockCallback, preventDefault: true };
151
+ keyManager.addCombination(config);
152
+ const event = dispatchKeyEvent("a");
153
+ assert.strictEqual(mockCallback.calledCount, 1);
154
+ assert.strictEqual(event.defaultPrevented, true);
155
+ });
156
+ it("should overwrite an existing combination with the same ID and warn", () => {
157
+ const firstCallback = createMockFn();
158
+ const secondCallback = createMockFn();
159
+ keyManager.addCombination({ id: "combo1", keys: { key: Keys.K }, callback: firstCallback });
160
+ consoleWarnMock.mock.resetCalls();
161
+ keyManager.addCombination({ id: "combo1", keys: { key: Keys.K, ctrlKey: true }, callback: secondCallback });
162
+ assert.strictEqual(consoleWarnMock.mock.calls.length, 1);
163
+ assert.ok(consoleWarnMock.mock.calls[0].arguments[0].includes(`Shortcut with ID "combo1" already exists`));
164
+ dispatchKeyEvent("k");
165
+ assert.strictEqual(firstCallback.calledCount, 0);
166
+ dispatchKeyEvent("k", { ctrlKey: true });
167
+ assert.strictEqual(secondCallback.calledCount, 1);
168
+ });
169
+ it("should log an error via console.error if callback throws, and not affect other shortcuts", () => {
170
+ const errorCallback = () => { throw new Error("Test callback error"); };
171
+ const workingCallback = createMockFn();
172
+ keyManager.addCombination({ id: "errorCombo", keys: { key: Keys.E }, callback: errorCallback });
173
+ keyManager.addCombination({ id: "workingCombo", keys: { key: Keys.W }, callback: workingCallback });
174
+ dispatchKeyEvent("e");
175
+ assert.strictEqual(consoleErrorMock.mock.calls.length, 1);
176
+ assert.ok(consoleErrorMock.mock.calls[0].arguments[0].includes('Error in user callback for combination shortcut "errorCombo"'));
177
+ dispatchKeyEvent("w");
178
+ assert.strictEqual(workingCallback.calledCount, 1);
179
+ });
180
+ });
181
+ describe("addSequence", () => {
182
+ it("should trigger callback for a simple key sequence", () => {
183
+ const config = { id: "seqGI", sequence: [Keys.G, Keys.I], callback: mockCallback };
184
+ const result = keyManager.addSequence(config);
185
+ assert.strictEqual(result, "seqGI");
186
+ dispatchKeyEvent("g"); // Dispatch 'g' (lowercase)
187
+ dispatchKeyEvent("i"); // Dispatch 'i' (lowercase)
188
+ assert.strictEqual(mockCallback.calledCount, 1);
189
+ });
190
+ it("should trigger callback for Konami code using Keys", () => {
191
+ const konamiSequence = [
192
+ Keys.ArrowUp, Keys.ArrowUp, Keys.ArrowDown, Keys.ArrowDown,
193
+ Keys.ArrowLeft, Keys.ArrowRight, Keys.ArrowLeft, Keys.ArrowRight,
194
+ Keys.B, Keys.A // Using 'B' and 'A' from Keys
195
+ ];
196
+ const config = { id: "konami", sequence: konamiSequence, callback: mockCallback };
197
+ keyManager.addSequence(config);
198
+ // Dispatch events using the string values that browser events would produce
199
+ ["ArrowUp", "ArrowUp", "ArrowDown", "ArrowDown", "ArrowLeft", "ArrowRight", "ArrowLeft", "ArrowRight", "b", "a"].forEach(key => dispatchKeyEvent(key));
200
+ assert.strictEqual(mockCallback.calledCount, 1, "Konami sequence callback not triggered");
201
+ });
202
+ it("should return undefined and warn if sequence is empty", () => {
203
+ const config = { id: "emptySeq", sequence: [], callback: mockCallback };
204
+ const result = keyManager.addSequence(config);
205
+ assert.strictEqual(result, undefined);
206
+ assert.strictEqual(consoleWarnMock.mock.calls.length, 1);
207
+ assert.ok(consoleWarnMock.mock.calls[0].arguments[0].includes(`Sequence for shortcut "emptySeq" is empty`));
208
+ });
209
+ it("should return undefined and warn if sequence contains an invalid key (runtime check with 'as any')", () => {
210
+ // This test checks runtime robustness if `any` is used to bypass StandardKey[]
211
+ const config = { id: "invalidKeyInSeq", sequence: [Keys.A, "", Keys.C], callback: mockCallback };
212
+ const result = keyManager.addSequence(config);
213
+ assert.strictEqual(result, undefined, "addSequence should return undefined for sequence with empty string");
214
+ assert.strictEqual(consoleWarnMock.mock.calls.length, 1, "console.warn was not called for invalid key in sequence");
215
+ assert.ok(consoleWarnMock.mock.calls[0].arguments[0].includes(`Invalid key in sequence for shortcut "invalidKeyInSeq"`));
216
+ });
217
+ it("should pass the last KeyboardEvent of the sequence to the callback", () => {
218
+ const config = { id: "seqEventPass", sequence: [Keys.X, Keys.Y], callback: mockCallback };
219
+ keyManager.addSequence(config);
220
+ dispatchKeyEvent("x");
221
+ const lastEvent = dispatchKeyEvent("y");
222
+ assert.strictEqual(mockCallback.calledCount, 1);
223
+ assert.deepStrictEqual(mockCallback.lastArgs, [lastEvent]);
224
+ });
225
+ it("should prevent default for the last key event in the sequence when preventDefault is true", () => {
226
+ const config = { id: "seqPrevent", sequence: [Keys.M, Keys.N], callback: mockCallback, preventDefault: true };
227
+ keyManager.addSequence(config);
228
+ dispatchKeyEvent("m");
229
+ const eventN = dispatchKeyEvent("n");
230
+ assert.strictEqual(mockCallback.calledCount, 1);
231
+ assert.strictEqual(eventN.defaultPrevented, true);
232
+ });
233
+ it("should log an error via console.error if sequence callback throws", () => {
234
+ const errorCallback = () => { throw new Error("Test sequence callback error"); };
235
+ keyManager.addSequence({ id: "errorSeq", sequence: [Keys.E, Keys.S], callback: errorCallback });
236
+ dispatchKeyEvent("e");
237
+ dispatchKeyEvent("s");
238
+ assert.strictEqual(consoleErrorMock.mock.calls.length, 1);
239
+ assert.ok(consoleErrorMock.mock.calls[0].arguments[0].includes('Error in user callback for sequence shortcut "errorSeq"'));
240
+ });
241
+ describe("Sequence Contextual Triggering", () => {
242
+ let editorSequenceConfig;
243
+ beforeEach(() => {
244
+ editorSequenceConfig = {
245
+ id: "sequenceInEditor",
246
+ sequence: [Keys.C, Keys.O, Keys.D, Keys.E],
247
+ callback: mockCallback,
248
+ context: "editor",
249
+ };
250
+ });
251
+ it("should trigger sequence in matching context", () => {
252
+ keyManager.addSequence(editorSequenceConfig);
253
+ keyManager.setContext("editor");
254
+ [Keys.C, Keys.O, Keys.D, Keys.E].forEach(k => dispatchKeyEvent(k));
255
+ assert.strictEqual(mockCallback.calledCount, 1);
256
+ });
257
+ });
258
+ describe("Sequence Timeouts", () => {
259
+ const TIMEOUT_MS = 100;
260
+ let originalPerformanceNowForSuite;
261
+ beforeEach(() => {
262
+ mock.timers.enable({ apis: ["Date", "setTimeout", "setInterval"], now: 0 });
263
+ // @ts-ignore
264
+ if (!originalPerformanceNowForSuite && global.performance && global.performance.now) {
265
+ // @ts-ignore
266
+ originalPerformanceNowForSuite = global.performance.now;
267
+ }
268
+ // @ts-ignore
269
+ performanceNowMock = mock.method(global.performance, "now", () => Date.now());
270
+ });
271
+ afterEach(() => {
272
+ if (performanceNowMock && performanceNowMock.mock) {
273
+ performanceNowMock.mock.restore();
274
+ }
275
+ else if (originalPerformanceNowForSuite) {
276
+ // @ts-ignore
277
+ global.performance.now = originalPerformanceNowForSuite;
278
+ }
279
+ mock.timers.reset();
280
+ });
281
+ it("should trigger sequence if keys are pressed within specified timeout", () => {
282
+ const config = { id: "seqTimeoutOk", sequence: [Keys.T, Keys.O, Keys.K], callback: mockCallback, sequenceTimeoutMs: TIMEOUT_MS };
283
+ keyManager.addSequence(config);
284
+ dispatchKeyEvent(Keys.T);
285
+ mock.timers.tick(TIMEOUT_MS / 2);
286
+ dispatchKeyEvent(Keys.O);
287
+ mock.timers.tick(TIMEOUT_MS / 2);
288
+ const lastEvent = dispatchKeyEvent(Keys.K);
289
+ assert.strictEqual(mockCallback.calledCount, 1);
290
+ assert.deepStrictEqual(mockCallback.lastArgs, [lastEvent]);
291
+ });
292
+ it("should NOT trigger sequence if a key press is delayed beyond timeout", () => {
293
+ const config = { id: "seqTimeoutFail", sequence: [Keys.D, Keys.E, Keys.L], callback: mockCallback, sequenceTimeoutMs: TIMEOUT_MS };
294
+ keyManager.addSequence(config);
295
+ dispatchKeyEvent(Keys.D);
296
+ mock.timers.tick(TIMEOUT_MS / 2);
297
+ dispatchKeyEvent(Keys.E);
298
+ mock.timers.tick(TIMEOUT_MS + 1);
299
+ dispatchKeyEvent(Keys.L);
300
+ assert.strictEqual(mockCallback.calledCount, 0);
301
+ });
302
+ });
303
+ });
304
+ describe("remove", () => {
305
+ it("should remove a combination shortcut", () => {
306
+ keyManager.addCombination({ id: "remA", keys: { key: Keys.A }, callback: mockCallback });
307
+ assert.strictEqual(keyManager.remove("remA"), true);
308
+ dispatchKeyEvent(Keys.A);
309
+ assert.strictEqual(mockCallback.calledCount, 0);
310
+ });
311
+ it("should remove a sequence shortcut", () => {
312
+ keyManager.addSequence({ id: "remSeq", sequence: [Keys.A, Keys.B], callback: mockCallback });
313
+ assert.strictEqual(keyManager.remove("remSeq"), true);
314
+ dispatchKeyEvent(Keys.A);
315
+ dispatchKeyEvent(Keys.B);
316
+ assert.strictEqual(mockCallback.calledCount, 0);
317
+ });
318
+ });
319
+ describe("getActiveShortcuts", () => {
320
+ it("should return active combination and sequence shortcuts", () => {
321
+ keyManager.addCombination({ id: "combo1", keys: { key: Keys.A }, callback: createMockFn(), description: "Test A" });
322
+ keyManager.addSequence({ id: "seq1", sequence: [Keys.B, Keys.C], callback: createMockFn(), context: "modal", description: "Test BC" });
323
+ const active = keyManager.getActiveShortcuts();
324
+ assert.strictEqual(active.length, 2);
325
+ const combo = active.find(s => s.id === "combo1");
326
+ assert.ok(combo);
327
+ assert.strictEqual(combo.type, "combination");
328
+ const seq = active.find(s => s.id === "seq1");
329
+ assert.ok(seq);
330
+ assert.strictEqual(seq.type, "sequence");
331
+ });
332
+ });
333
+ describe("hasShortcut", () => {
334
+ it("should return true for an existing combination shortcut", () => {
335
+ keyManager.addCombination({ id: "existsCombo", keys: { key: Keys.E }, callback: mockCallback });
336
+ assert.strictEqual(keyManager.hasShortcut("existsCombo"), true);
337
+ });
338
+ it("should return false for a shortcut that failed to add (e.g. invalid key object)", () => {
339
+ // This test now relies on the runtime check for !keys.key, as TS would catch `key: null` directly.
340
+ const config = { id: "invalidKeyCombo", keys: { key: null }, callback: mockCallback };
341
+ keyManager.addCombination(config);
342
+ assert.strictEqual(keyManager.hasShortcut("invalidKeyCombo"), false);
343
+ });
344
+ });
345
+ describe("destroy", () => {
346
+ it("should clear active shortcuts and prevent further triggers", () => {
347
+ keyManager.addCombination({ id: "destroyTestCombo", keys: { key: Keys.D }, callback: mockCallback });
348
+ keyManager.addSequence({ id: "destroyTestSeq", sequence: [Keys.X, Keys.Y], callback: mockCallback });
349
+ // @ts-ignore
350
+ assert.strictEqual(keyManager['activeShortcuts'].size, 2);
351
+ keyManager.destroy();
352
+ // @ts-ignore
353
+ assert.strictEqual(keyManager['activeShortcuts'].size, 0);
354
+ dispatchKeyEvent(Keys.D);
355
+ dispatchKeyEvent(Keys.X);
356
+ dispatchKeyEvent(Keys.Y);
357
+ assert.strictEqual(mockCallback.calledCount, 0);
358
+ });
359
+ });
360
+ });
@@ -0,0 +1,3 @@
1
+ export { type StandardKey, Keys, } from "./keys.js";
2
+ export { type KeyCombinationConfig, type KeySequenceConfig, HotKeys, } from "./hotkeys.js";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,KAAK,WAAW,EAChB,IAAI,GACP,MAAM,WAAW,CAAC;AACnB,OAAO,EACH,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,EACtB,OAAO,GACV,MAAM,cAAc,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { Keys, } from "./keys.js";
2
+ export { HotKeys, } from "./hotkeys.js";
package/dist/keys.d.ts ADDED
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Provides a set of common, standard string values for `KeyboardEvent.key`.
3
+ * Using these values can help avoid typos and ensure consistency.
4
+ * These are based on the MDN documentation:
5
+ * https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values
6
+ *
7
+ * All shortcut configurations should use values from this object.
8
+ */
9
+ export declare const Keys: {
10
+ readonly Unidentified: "Unidentified";
11
+ readonly Alt: "Alt";
12
+ readonly AltGraph: "AltGraph";
13
+ readonly CapsLock: "CapsLock";
14
+ readonly Control: "Control";
15
+ readonly Fn: "Fn";
16
+ readonly FnLock: "FnLock";
17
+ readonly Hyper: "Hyper";
18
+ readonly Meta: "Meta";
19
+ readonly NumLock: "NumLock";
20
+ readonly ScrollLock: "ScrollLock";
21
+ readonly Shift: "Shift";
22
+ readonly Super: "Super";
23
+ readonly Symbol: "Symbol";
24
+ readonly SymbolLock: "SymbolLock";
25
+ readonly Enter: "Enter";
26
+ readonly Tab: "Tab";
27
+ readonly Space: " ";
28
+ readonly ArrowDown: "ArrowDown";
29
+ readonly ArrowLeft: "ArrowLeft";
30
+ readonly ArrowRight: "ArrowRight";
31
+ readonly ArrowUp: "ArrowUp";
32
+ readonly End: "End";
33
+ readonly Home: "Home";
34
+ readonly PageDown: "PageDown";
35
+ readonly PageUp: "PageUp";
36
+ readonly Backspace: "Backspace";
37
+ readonly Clear: "Clear";
38
+ readonly Copy: "Copy";
39
+ readonly CrSel: "CrSel";
40
+ readonly Cut: "Cut";
41
+ readonly Delete: "Delete";
42
+ readonly EraseEof: "EraseEof";
43
+ readonly ExSel: "ExSel";
44
+ readonly Insert: "Insert";
45
+ readonly Paste: "Paste";
46
+ readonly Redo: "Redo";
47
+ readonly Undo: "Undo";
48
+ readonly Accept: "Accept";
49
+ readonly Again: "Again";
50
+ readonly Attn: "Attn";
51
+ readonly Cancel: "Cancel";
52
+ readonly ContextMenu: "ContextMenu";
53
+ readonly Escape: "Escape";
54
+ readonly Execute: "Execute";
55
+ readonly Find: "Find";
56
+ readonly Finish: "Finish";
57
+ readonly Help: "Help";
58
+ readonly Pause: "Pause";
59
+ readonly Play: "Play";
60
+ readonly Props: "Props";
61
+ readonly Select: "Select";
62
+ readonly ZoomIn: "ZoomIn";
63
+ readonly ZoomOut: "ZoomOut";
64
+ readonly BrightnessDown: "BrightnessDown";
65
+ readonly BrightnessUp: "BrightnessUp";
66
+ readonly Eject: "Eject";
67
+ readonly LogOff: "LogOff";
68
+ readonly Power: "Power";
69
+ readonly PowerOff: "PowerOff";
70
+ readonly PrintScreen: "PrintScreen";
71
+ readonly Hibernate: "Hibernate";
72
+ readonly Standby: "Standby";
73
+ readonly WakeUp: "WakeUp";
74
+ readonly F1: "F1";
75
+ readonly F2: "F2";
76
+ readonly F3: "F3";
77
+ readonly F4: "F4";
78
+ readonly F5: "F5";
79
+ readonly F6: "F6";
80
+ readonly F7: "F7";
81
+ readonly F8: "F8";
82
+ readonly F9: "F9";
83
+ readonly F10: "F10";
84
+ readonly F11: "F11";
85
+ readonly F12: "F12";
86
+ readonly F13: "F13";
87
+ readonly F14: "F14";
88
+ readonly F15: "F15";
89
+ readonly F16: "F16";
90
+ readonly F17: "F17";
91
+ readonly F18: "F18";
92
+ readonly F19: "F19";
93
+ readonly F20: "F20";
94
+ readonly AppSwitch: "AppSwitch";
95
+ readonly Call: "Call";
96
+ readonly Camera: "Camera";
97
+ readonly EndCall: "EndCall";
98
+ readonly GoBack: "GoBack";
99
+ readonly GoHome: "GoHome";
100
+ readonly HeadsetHook: "HeadsetHook";
101
+ readonly MediaPlayPause: "MediaPlayPause";
102
+ readonly MediaStop: "MediaStop";
103
+ readonly MediaTrackNext: "MediaTrackNext";
104
+ readonly MediaTrackPrevious: "MediaTrackPrevious";
105
+ readonly AudioVolumeDown: "AudioVolumeDown";
106
+ readonly AudioVolumeUp: "AudioVolumeUp";
107
+ readonly AudioVolumeMute: "AudioVolumeMute";
108
+ readonly Decimal: ".";
109
+ readonly KeypadMultiply: "*";
110
+ readonly KeypadAdd: "+";
111
+ readonly KeypadSubtract: "-";
112
+ readonly KeypadDivide: "/";
113
+ readonly A: "A";
114
+ readonly B: "B";
115
+ readonly C: "C";
116
+ readonly D: "D";
117
+ readonly E: "E";
118
+ readonly F: "F";
119
+ readonly G: "G";
120
+ readonly H: "H";
121
+ readonly I: "I";
122
+ readonly J: "J";
123
+ readonly K: "K";
124
+ readonly L: "L";
125
+ readonly M: "M";
126
+ readonly N: "N";
127
+ readonly O: "O";
128
+ readonly P: "P";
129
+ readonly Q: "Q";
130
+ readonly R: "R";
131
+ readonly S: "S";
132
+ readonly T: "T";
133
+ readonly U: "U";
134
+ readonly V: "V";
135
+ readonly W: "W";
136
+ readonly X: "X";
137
+ readonly Y: "Y";
138
+ readonly Z: "Z";
139
+ readonly Digit0: "0";
140
+ readonly Digit1: "1";
141
+ readonly Digit2: "2";
142
+ readonly Digit3: "3";
143
+ readonly Digit4: "4";
144
+ readonly Digit5: "5";
145
+ readonly Digit6: "6";
146
+ readonly Digit7: "7";
147
+ readonly Digit8: "8";
148
+ readonly Digit9: "9";
149
+ };
150
+ /**
151
+ * Represents the set of allowed string literal values for keys, derived from the KeyValues object.
152
+ * This ensures type safety when configuring shortcuts.
153
+ */
154
+ export type StandardKey = typeof Keys[keyof typeof Keys];
155
+ //# sourceMappingURL=keys.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,eAAO,MAAM,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyHP,CAAC;AAEX;;;GAGG;AACH,MAAM,MAAM,WAAW,GAAG,OAAO,IAAI,CAAC,MAAM,OAAO,IAAI,CAAC,CAAC"}
package/dist/keys.js ADDED
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Provides a set of common, standard string values for `KeyboardEvent.key`.
3
+ * Using these values can help avoid typos and ensure consistency.
4
+ * These are based on the MDN documentation:
5
+ * https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values
6
+ *
7
+ * All shortcut configurations should use values from this object.
8
+ */
9
+ export const Keys = {
10
+ // Special Values
11
+ Unidentified: "Unidentified",
12
+ // Modifier Keys
13
+ Alt: "Alt",
14
+ AltGraph: "AltGraph",
15
+ CapsLock: "CapsLock",
16
+ Control: "Control",
17
+ Fn: "Fn",
18
+ FnLock: "FnLock",
19
+ Hyper: "Hyper",
20
+ Meta: "Meta", // Command key on Mac, Windows key on Windows
21
+ NumLock: "NumLock",
22
+ ScrollLock: "ScrollLock",
23
+ Shift: "Shift",
24
+ Super: "Super",
25
+ Symbol: "Symbol",
26
+ SymbolLock: "SymbolLock",
27
+ // Whitespace Keys
28
+ Enter: "Enter",
29
+ Tab: "Tab",
30
+ Space: " ", // Standard value for Space Bar
31
+ // Navigation Keys
32
+ ArrowDown: "ArrowDown",
33
+ ArrowLeft: "ArrowLeft",
34
+ ArrowRight: "ArrowRight",
35
+ ArrowUp: "ArrowUp",
36
+ End: "End",
37
+ Home: "Home",
38
+ PageDown: "PageDown",
39
+ PageUp: "PageUp",
40
+ // Editing Keys
41
+ Backspace: "Backspace",
42
+ Clear: "Clear",
43
+ Copy: "Copy",
44
+ CrSel: "CrSel", // Cursor Select
45
+ Cut: "Cut",
46
+ Delete: "Delete",
47
+ EraseEof: "EraseEof", // Erase to End of Field
48
+ ExSel: "ExSel", // Extend Selection
49
+ Insert: "Insert",
50
+ Paste: "Paste",
51
+ Redo: "Redo",
52
+ Undo: "Undo",
53
+ // UI Keys
54
+ Accept: "Accept",
55
+ Again: "Again",
56
+ Attn: "Attn", // Attention
57
+ Cancel: "Cancel",
58
+ ContextMenu: "ContextMenu", // Application key
59
+ Escape: "Escape",
60
+ Execute: "Execute",
61
+ Find: "Find",
62
+ Finish: "Finish",
63
+ Help: "Help",
64
+ Pause: "Pause",
65
+ Play: "Play",
66
+ Props: "Props", // Properties
67
+ Select: "Select",
68
+ ZoomIn: "ZoomIn",
69
+ ZoomOut: "ZoomOut",
70
+ // Device Keys
71
+ BrightnessDown: "BrightnessDown",
72
+ BrightnessUp: "BrightnessUp",
73
+ Eject: "Eject",
74
+ LogOff: "LogOff",
75
+ Power: "Power",
76
+ PowerOff: "PowerOff",
77
+ PrintScreen: "PrintScreen",
78
+ Hibernate: "Hibernate",
79
+ Standby: "Standby", // Suspend or Sleep
80
+ WakeUp: "WakeUp",
81
+ // Function Keys
82
+ F1: "F1", F2: "F2", F3: "F3", F4: "F4",
83
+ F5: "F5", F6: "F6", F7: "F7", F8: "F8",
84
+ F9: "F9", F10: "F10", F11: "F11", F12: "F12",
85
+ F13: "F13", F14: "F14", F15: "F15", F16: "F16",
86
+ F17: "F17", F18: "F18", F19: "F19", F20: "F20",
87
+ // Phone Keys (selection)
88
+ AppSwitch: "AppSwitch",
89
+ Call: "Call",
90
+ Camera: "Camera",
91
+ EndCall: "EndCall",
92
+ GoBack: "GoBack",
93
+ GoHome: "GoHome",
94
+ HeadsetHook: "HeadsetHook",
95
+ // Multimedia Keys (selection)
96
+ MediaPlayPause: "MediaPlayPause",
97
+ MediaStop: "MediaStop",
98
+ MediaTrackNext: "MediaTrackNext",
99
+ MediaTrackPrevious: "MediaTrackPrevious",
100
+ AudioVolumeDown: "AudioVolumeDown",
101
+ AudioVolumeUp: "AudioVolumeUp",
102
+ AudioVolumeMute: "AudioVolumeMute",
103
+ // Numeric Keypad (special characters, numbers 0-9 are via KeyValues.DigitN)
104
+ Decimal: ".", // This is the character for the decimal point
105
+ KeypadMultiply: "*",
106
+ KeypadAdd: "+",
107
+ KeypadSubtract: "-",
108
+ KeypadDivide: "/",
109
+ // Character Keys (Uppercase A-Z for configuration via KeyValues)
110
+ // The library handles case-insensitivity for these when matching browser events.
111
+ A: "A", B: "B", C: "C", D: "D", E: "E", F: "F", G: "G", H: "H", I: "I",
112
+ J: "J", K: "K", L: "L", M: "M", N: "N", O: "O", P: "P", Q: "Q", R: "R",
113
+ S: "S", T: "T", U: "U", V: "V", W: "W", X: "X", Y: "Y", Z: "Z",
114
+ // Digit Keys (0-9 for configuration via KeyValues)
115
+ Digit0: "0", Digit1: "1", Digit2: "2", Digit3: "3", Digit4: "4",
116
+ Digit5: "5", Digit6: "6", Digit7: "7", Digit8: "8", Digit9: "9",
117
+ };
@@ -0,0 +1,14 @@
1
+ export declare function createMockFn(): {
2
+ (...args: any[]): void;
3
+ calledCount: number;
4
+ calls: any[];
5
+ lastArgs: any[];
6
+ mockClear(): void;
7
+ };
8
+ /**
9
+ * Dispatches a KeyboardEvent to the document (JSDOM).
10
+ * @param key - The key value, e.g., "a", "Escape", "ArrowUp"
11
+ * @param modifiers - Optional modifier keys
12
+ */
13
+ export declare function dispatchKeyEvent(key: string, modifiers?: Partial<KeyboardEventInit>): KeyboardEvent;
14
+ //# sourceMappingURL=testutils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testutils.d.ts","sourceRoot":"","sources":["../src/testutils.ts"],"names":[],"mappings":"AAAA,wBAAgB,YAAY;cACH,GAAG,EAAE;;;;;EAc7B;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC5B,GAAG,EAAE,MAAM,EACX,SAAS,GAAE,OAAO,CAAC,iBAAiB,CAAM,GAC3C,aAAa,CASf"}