Skip to content

Commit e94ccfa

Browse files
authored
Merge pull request #8 from rameel/fix-once-option
Fix 'once' option being consumed by non-matching key events
2 parents 79adb99 + 4f46fb0 commit e94ccfa

2 files changed

Lines changed: 73 additions & 3 deletions

File tree

src/hotkey.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,17 +78,27 @@ export function registerHotkey(
7878
target = document.querySelector(target) ?? error(`No element found for selector '${target}'`);
7979
}
8080

81-
return listen(target, eventName, function (this: EventTarget, e: KeyboardEvent) {
82-
if (!(options as HotkeyEventListenerOptions)?.trusted || e.isTrusted) {
81+
const require_trusted = (options as HotkeyEventListenerOptions)?.trusted;
82+
const once = (options as HotkeyEventListenerOptions)?.once;
83+
84+
const native_options = options && typeof options === "object"
85+
? { capture: options.capture, passive: options.passive }
86+
: options;
87+
88+
const unregister = listen(target, eventName, function (this: EventTarget, e: KeyboardEvent) {
89+
if (!require_trusted || e.isTrusted) {
8390
if (!(e.target as HTMLElement)?.closest("[data-hotkey-ignore]")) {
8491
if (info.code === e.code.toUpperCase()) {
8592
if (control_keys.every(n => info[n as keyof Hotkey] === e[n as keyof KeyboardEvent])) {
93+
once && unregister();
8694
handler.call(this, e);
8795
}
8896
}
8997
}
9098
}
91-
} as EventListener, options);
99+
} as EventListener, native_options);
100+
101+
return unregister;
92102
}
93103

94104
function describe(hotkey: string): Hotkey {

tests/hotkey.spec.js

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,3 +339,63 @@ test("should not trigger on untrusted events when 'trusted' option is true", asy
339339
const triggered = await page.evaluate(() => window.hotkeyTriggered);
340340
expect(triggered).toBe(false);
341341
});
342+
343+
test("should not be consumed by non-matching keys when 'once' option is set", async ({ page }) => {
344+
await page.evaluate(() => {
345+
window.hotkeyCount = 0;
346+
const el = document.getElementById("text");
347+
348+
window.registerHotkey(el, "Ctrl + K", () => {
349+
window.hotkeyCount++;
350+
}, "keydown", { once: true });
351+
});
352+
353+
await page.locator("#text").focus();
354+
355+
await page.keyboard.press("A");
356+
await page.keyboard.press("B");
357+
358+
await page.keyboard.press("Control+k");
359+
360+
const count = await page.evaluate(() => window.hotkeyCount);
361+
expect(count).toBe(1);
362+
});
363+
364+
test("should trigger only once with 'once' option", async ({ page }) => {
365+
await page.evaluate(() => {
366+
window.hotkeyCount = 0;
367+
const el = document.getElementById("text");
368+
369+
window.registerHotkey(el, "Ctrl + K", () => {
370+
window.hotkeyCount++;
371+
}, "keydown", { once: true });
372+
});
373+
374+
await page.locator("#text").focus();
375+
376+
// Press the hotkey twice
377+
await page.keyboard.press("Control+k");
378+
await page.keyboard.press("Control+k");
379+
380+
const count = await page.evaluate(() => window.hotkeyCount);
381+
expect(count).toBe(1);
382+
});
383+
384+
test("should trigger multiple times without 'once' option", async ({ page }) => {
385+
await page.evaluate(() => {
386+
window.hotkeyCount = 0;
387+
const el = document.getElementById("text");
388+
389+
window.registerHotkey(el, "Ctrl + K", () => {
390+
window.hotkeyCount++;
391+
});
392+
});
393+
394+
await page.locator("#text").focus();
395+
396+
await page.keyboard.press("Control+k");
397+
await page.keyboard.press("Control+k");
398+
399+
const count = await page.evaluate(() => window.hotkeyCount);
400+
expect(count).toBe(2);
401+
});

0 commit comments

Comments
 (0)