Skip to content

Commit

Permalink
expand hotkey to support scoped hotkeys
Browse files Browse the repository at this point in the history
Co-authored-by: Keith Cirkel <keithamus@users.noreply.github.com>
  • Loading branch information
khiga8 and keithamus committed Sep 23, 2021
1 parent 04b4bcf commit accf2c1
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 7 deletions.
4 changes: 2 additions & 2 deletions examples/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@

<body>

<button onclick="alert('clicked!')" data-hotkey="d">press d to click this button</button><br>
<textarea data-hotkey="t" rows="4" cols="40">press t to focus on this field</textarea><br>
<button onclick="alert('clicked!')" data-hotkey-scope="text-area-1" data-hotkey="Control+d,Meta+d">press meta+d or ctrl+d in text area to click this button</button><br>
<textarea id="text-area-1" data-hotkey="t" rows="4" cols="40">press t to focus on this field</textarea><br>
<label><input data-hotkey="r" type="checkbox">Press r to check/uncheck this checkbox</label><br>
<a href="#ok" data-hotkey="o k">Press <kbd>o k</kbd> click this link</a>

Expand Down
27 changes: 22 additions & 5 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,12 @@ function resetTriePosition() {

function keyDownHandler(event: KeyboardEvent) {
if (event.defaultPrevented) return
if (event.target instanceof Node && isFormField(event.target)) return

if (!(event.target instanceof Node)) return
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 (resetTriePositionTimer != null) {
window.clearTimeout(resetTriePositionTimer)
}
Expand All @@ -31,10 +35,23 @@ function keyDownHandler(event: KeyboardEvent) {

currentTriePosition = newTriePosition
if (newTriePosition instanceof Leaf) {
fireDeterminedAction(newTriePosition.children[newTriePosition.children.length - 1])
event.preventDefault()
let shouldFire = true
const elementToFire = newTriePosition.children[newTriePosition.children.length - 1]
const hotkeyScope = elementToFire.getAttribute('data-hotkey-scope')
if (isFormField(event.target)) {
const target = event.target as HTMLElement
if (target.id !== elementToFire.getAttribute('data-hotkey-scope')) {
shouldFire = false
}
} else if (hotkeyScope) {
shouldFire = false
}

if (shouldFire) {
fireDeterminedAction(elementToFire)
event.preventDefault()
}
resetTriePosition()
return
}
}

Expand Down
73 changes: 73 additions & 0 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,85 @@ describe('hotkey', function () {
})
})

describe('data-hotkey-scope', function () {
it('allows hotkey action from form field', function () {
setHTML(`
<button id="button1" data-hotkey-scope="textfield" data-hotkey="Meta+b">Button 1</button>
<input id="textfield" />`)
document
.getElementById('textfield')
.dispatchEvent(new KeyboardEvent('keydown', {bubbles: true, metaKey: true, cancelable: true, key: 'b'}))
assert.include(elementsActivated, 'button1')
})

it('does nothing if `data-hotkey-scope` is set to non-form field', function () {
setHTML(`
<button id="button1" data-hotkey-scope="button2" data-hotkey="Meta+b">Button 1</button>
<button id="button2" />`)
document
.getElementById('button2')
.dispatchEvent(new KeyboardEvent('keydown', {bubbles: true, metaKey: true, cancelable: true, key: 'b'}))
assert.deepEqual(elementsActivated, [])
})

it('does nothing if `data-hotkey-scope` does not exist', function () {
setHTML(`
<button id="button1" data-hotkey="b">Button 1</button>
<input id="textfield" />`)
document
.getElementById('textfield')
.dispatchEvent(new KeyboardEvent('keydown', {bubbles: true, cancelable: true, key: 'b'}))
assert.deepEqual(elementsActivated, [])
})
})

describe('scoped hotkeys', function () {
it('triggers action if element specified in `data-hotkey-scope` receives hotkey event', function () {
setHTML(`
<button id="button1" data-hotkey-scope="textfield" data-hotkey="Control+c">Button 1</button>
<input id="textfield" />`)
document
.getElementById('textfield')
.dispatchEvent(new KeyboardEvent('keydown', {bubbles: true, cancelable: true, ctrlKey: true, key: 'c'}))
assert.include(elementsActivated, 'button1')
})

it('does not trigger action if document receives hotkey event', function () {
setHTML(`
<button id="button2" data-hotkey-scope="textfield" data-hotkey="Control+b">>Button 2</button>
<input id="textfield" />`)
document.dispatchEvent(new KeyboardEvent('keydown', {bubbles: true, cancelable: true, ctrlKey: true, key: 'b'}))
assert.deepEqual(elementsActivated, [])
})

it('does not trigger action if elements other than one specified in `data-hotkey-scope` receives hotkey event', function () {
setHTML(`
<button id="button3" data-hotkey-scope="textfield1" data-hotkey="Control+c">>Button 3</button>
<input id="textfield1" />
<input id="textfield2" />`)
document
.getElementById('textfield2')
.dispatchEvent(new KeyboardEvent('keydown', {bubbles: true, cancelable: true, ctrlKey: true, key: 'c'}))
assert.deepEqual(elementsActivated, [])
})

it('does nothing if element specified in `data-hotkey-scope` does not exist even if hotkeys are pressed', function () {
setHTML(`
<button id="button1" data-hotkey-scope="bad" data-hotkey="Meta+c">Button 4</button>
`)
document.dispatchEvent(new KeyboardEvent('keydown', {bubbles: true, cancelable: true, metaKey: true, key: 'c'}))
assert.deepEqual(elementsActivated, [])
})
})

describe('eventToHotkeyString', function () {
const tests = [
['Control+J', {ctrlKey: true, shiftKey: true, code: 'KeyJ', key: 'J'}],
['Control+Shift+j', {ctrlKey: true, shiftKey: true, code: 'KeyJ', key: 'j'}],
['Control+j', {ctrlKey: true, code: 'KeyJ', key: 'j'}],
['Meta+Shift+p', {key: 'p', metaKey: true, shiftKey: true, code: 'KeyP'}],
['Meta+Shift+8', {key: '8', metaKey: true, shiftKey: true, code: 'Digit8'}],
['Control+Shift+7', {key: '7', ctrlKey: true, shiftKey: true, code: 'Digit7'}],
['J', {shiftKey: true, code: 'KeyJ', key: 'J'}],
['/', {key: '/', code: ''}],
['1', {key: '1', code: 'Digit1'}],
Expand Down

0 comments on commit accf2c1

Please sign in to comment.