From edf1f560a38d9a2f349ff3f2c369c77ffe3a8ca4 Mon Sep 17 00:00:00 2001 From: rameel Date: Fri, 3 Apr 2026 01:57:31 +0500 Subject: [PATCH 1/4] Fix typo in stopPropagation --- src/plugins/hotkey/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/hotkey/index.js b/src/plugins/hotkey/index.js index 7da40e6..4be88f9 100644 --- a/src/plugins/hotkey/index.js +++ b/src/plugins/hotkey/index.js @@ -26,7 +26,7 @@ function plugin({ directive }) { hotkey, e => { has_modifier(modifiers, "prevent") && e.preventDefault(); - has_modifier(modifiers, "stop") && e.stopPropogation(); + has_modifier(modifiers, "stop") && e.stopPropagation(); e.hotkey = hotkey; listener(e); From 6893c99a85e42c34708643af8a01e176a2c3eac9 Mon Sep 17 00:00:00 2001 From: rameel Date: Fri, 3 Apr 2026 03:09:28 +0500 Subject: [PATCH 2/4] Add tests --- tests/playwright/assets/page.html | 1 + tests/playwright/x-hotkey.spec.js | 183 ++++++++++++++++++++++++++++++ 2 files changed, 184 insertions(+) create mode 100644 tests/playwright/x-hotkey.spec.js diff --git a/tests/playwright/assets/page.html b/tests/playwright/assets/page.html index bca7431..d3f1f2d 100644 --- a/tests/playwright/assets/page.html +++ b/tests/playwright/assets/page.html @@ -15,6 +15,7 @@ + diff --git a/tests/playwright/x-hotkey.spec.js b/tests/playwright/x-hotkey.spec.js new file mode 100644 index 0000000..8468915 --- /dev/null +++ b/tests/playwright/x-hotkey.spec.js @@ -0,0 +1,183 @@ +import { expect, test } from "@playwright/test"; +import { set_html } from "./assets/utils"; + +test("x-hotkey triggers expression on matching key", async ({ page }) => { + await set_html(page, ` +
+
+ +
`); + + await page.keyboard.press("Control+k"); + + await expect(page.locator("span")).toHaveText("1"); +}); + +test("x-hotkey does not trigger on non-matching key", async ({ page }) => { + await set_html(page, ` +
+
+ +
`); + + await page.keyboard.press("Control+j"); + await page.keyboard.press("Control+Shift+k"); + await page.keyboard.press("k"); + + await expect(page.locator("span")).toHaveText("0"); +}); + +test("x-hotkey exposes $event in expression", async ({ page }) => { + await set_html(page, ` +
+
+ +
`); + + await page.keyboard.press("Control+k"); + + await expect(page.locator("span")).toHaveText("keydown"); +}); + +test("x-hotkey sets $event.hotkey to the matched hotkey string", async ({ page }) => { + await set_html(page, ` +
+
+ +
`); + + await page.keyboard.press("Control+k"); + + await expect(page.locator("span")).toHaveText("ctrl+k"); +}); + +test("x-hotkey .prevent modifier calls preventDefault()", async ({ page }) => { + await set_html(page, ` +
+
+ +
`); + + await page.keyboard.press("Control+k"); + + await expect(page.locator("span")).toHaveText("true"); +}); + +test("x-hotkey .stop modifier stops event propagation", async ({ page }) => { + await set_html(page, ` +
+
+ +
+ + +
`); + + await page.locator("#btn").focus(); + await page.keyboard.press("Control+k"); + + await expect(page.locator("#inner")).toHaveText("1"); + await expect(page.locator("#outer")).toHaveText("0"); +}); + +test("x-hotkey without .stop modifier does not stop propagation", async ({ page }) => { + await set_html(page, ` +
+
+ +
+ + +
`); + + await page.locator("#btn").focus(); + await page.keyboard.press("Control+k"); + + await expect(page.locator("#inner")).toHaveText("1"); + await expect(page.locator("#outer")).toHaveText("1"); +}); + +test("x-hotkey .window modifier listens on window", async ({ page }) => { + await set_html(page, ` +
+ +
+ +
`); + + await page.locator("#other").focus(); + await page.keyboard.press("Control+k"); + + await expect(page.locator("span")).toHaveText("1"); +}); + +test("x-hotkey .document modifier listens on document", async ({ page }) => { + await set_html(page, ` +
+ +
+ +
`); + + await page.locator("#other").focus(); + await page.keyboard.press("Control+k"); + + await expect(page.locator("span")).toHaveText("1"); +}); + +test("x-hotkey:keyup listens on keyup event", async ({ page }) => { + await set_html(page, ` +
+
+ +
`); + + await page.keyboard.press("Control+k"); + + await expect(page.locator("span")).toHaveText("keyup"); +}); + +test("x-hotkey supports multiple hotkeys via comma-separated values", async ({ page }) => { + await set_html(page, ` +
+
+ +
`); + + await page.keyboard.press("Control+j"); + await page.keyboard.press("Control+k"); + + await expect(page.locator("span")).toHaveText("2"); +}); + +test("x-hotkey with no expression does not throw", async ({ page }) => { + const errors = []; + page.on("pageerror", e => errors.push(e)); + + await set_html(page, ` +
+
+
`); + + await page.keyboard.press("Control+k"); + + expect(errors).toHaveLength(0); +}); + +test("x-hotkey cleans up listener when element is removed", async ({ page }) => { + await set_html(page, ` +
+ + + +
`); + + await page.keyboard.press("Control+k"); + await expect(page.locator("span")).toHaveText("1"); + + await page.locator("button").click(); + await page.keyboard.press("Control+k"); + await expect(page.locator("span")).toHaveText("1"); +}); From 08fb96f33cfc28b578c090bcedd9a26a688cddba Mon Sep 17 00:00:00 2001 From: rameel Date: Fri, 3 Apr 2026 03:27:36 +0500 Subject: [PATCH 3/4] Ensure element is unmounted before asserting hotkey cleanup --- tests/playwright/x-hotkey.spec.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/playwright/x-hotkey.spec.js b/tests/playwright/x-hotkey.spec.js index 8468915..cead222 100644 --- a/tests/playwright/x-hotkey.spec.js +++ b/tests/playwright/x-hotkey.spec.js @@ -168,7 +168,7 @@ test("x-hotkey cleans up listener when element is removed", async ({ page }) => await set_html(page, `
@@ -177,7 +177,12 @@ test("x-hotkey cleans up listener when element is removed", async ({ page }) => await page.keyboard.press("Control+k"); await expect(page.locator("span")).toHaveText("1"); + const hotkey_el = page.locator("#id_1"); + await expect(hotkey_el).toBeAttached(); + await page.locator("button").click(); + await hotkey_el.waitFor({ state: "detached" }); + await page.keyboard.press("Control+k"); await expect(page.locator("span")).toHaveText("1"); }); From ce38ae468f3a27848aac14f8b797b56e5c53993a Mon Sep 17 00:00:00 2001 From: rameel Date: Sat, 4 Apr 2026 02:38:23 +0500 Subject: [PATCH 4/4] Add test for x-hotkey .once modifier --- tests/playwright/x-hotkey.spec.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/playwright/x-hotkey.spec.js b/tests/playwright/x-hotkey.spec.js index cead222..3a87fe7 100644 --- a/tests/playwright/x-hotkey.spec.js +++ b/tests/playwright/x-hotkey.spec.js @@ -186,3 +186,20 @@ test("x-hotkey cleans up listener when element is removed", async ({ page }) => await page.keyboard.press("Control+k"); await expect(page.locator("span")).toHaveText("1"); }); + +test("x-hotkey .once modifier fires only once", async ({ page }) => { + await set_html(page, ` +
+
+ +
`); + + await page.keyboard.press("Control+k"); + await expect(page.locator("span")).toHaveText("1"); + + await page.keyboard.press("Control+k"); + await expect(page.locator("span")).toHaveText("1"); + + await page.keyboard.press("Control+k"); + await expect(page.locator("span")).toHaveText("1"); +});