From d2438a4410c274bff4db9cae980fda052bc598e6 Mon Sep 17 00:00:00 2001 From: Ferit Date: Fri, 16 Oct 2026 10:12:00 +0300 Subject: [PATCH 1/2] fix: escape field IDs in hotkey scopes --- src/index.ts | 2 +- test/test.js | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index fede7e6..9e579fb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -25,7 +25,7 @@ function keyDownHandler(event: KeyboardEvent) { if (isFormField(event.target)) { const target = event.target as HTMLElement if (!target.id) return - if (!target.ownerDocument.querySelector(`[data-hotkey-scope="${target.id}"]`)) return + if (!target.ownerDocument.querySelector(`[data-hotkey-scope="${CSS.escape(target.id)}"]`)) return } // If the user presses a hotkey that doesn't exist in the Trie, diff --git a/test/test.js b/test/test.js index d26d497..bd3fa7c 100644 --- a/test/test.js +++ b/test/test.js @@ -173,6 +173,19 @@ describe('hotkey', function () { }) describe('data-hotkey-scope', function () { + it.each(['field"one', 'field\\one'])('matches a scope whose ID contains CSS escapes: %s', function (id) { + setHTML(` + + `) + const field = document.getElementById('textfield') + field.id = id + document.getElementById('button1').setAttribute('data-hotkey-scope', id) + + field.dispatchEvent(new KeyboardEvent('keydown', {bubbles: true, key: 'b', metaKey: true})) + + expect(elementsActivated).toEqual(['button1']) + }) + it('allows hotkey action from form field', function () { setHTML(` From a8152d24c7d13c7143fa82853a3a462fbcf420b1 Mon Sep 17 00:00:00 2001 From: Ferit Date: Thu, 5 Nov 2026 15:56:03 +0200 Subject: [PATCH 2/2] Handle hotkey scopes without CSS.escape --- src/index.ts | 8 +++++++- test/test.js | 15 +++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 9e579fb..9068ba9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -25,7 +25,13 @@ function keyDownHandler(event: KeyboardEvent) { if (isFormField(event.target)) { const target = event.target as HTMLElement if (!target.id) return - if (!target.ownerDocument.querySelector(`[data-hotkey-scope="${CSS.escape(target.id)}"]`)) return + const hasScope = + typeof CSS !== 'undefined' && CSS.escape + ? target.ownerDocument.querySelector(`[data-hotkey-scope="${CSS.escape(target.id)}"]`) + : Array.from(target.ownerDocument.querySelectorAll('[data-hotkey-scope]')).some( + element => element.getAttribute('data-hotkey-scope') === target.id + ) + if (!hasScope) return } // If the user presses a hotkey that doesn't exist in the Trie, diff --git a/test/test.js b/test/test.js index bd3fa7c..7478321 100644 --- a/test/test.js +++ b/test/test.js @@ -173,6 +173,21 @@ describe('hotkey', function () { }) describe('data-hotkey-scope', function () { + it('matches scoped form fields without CSS.escape', function () { + setHTML('') + const field = document.getElementById('textfield') + field.id = 'field"one' + document.getElementById('button1').setAttribute('data-hotkey-scope', field.id) + const original = CSS.escape + try { + CSS.escape = undefined + field.dispatchEvent(new KeyboardEvent('keydown', {bubbles: true, key: 'b', metaKey: true})) + expect(elementsActivated).toEqual(['button1']) + } finally { + CSS.escape = original + } + }) + it.each(['field"one', 'field\\one'])('matches a scope whose ID contains CSS escapes: %s', function (id) { setHTML(`