diff --git a/jump.js b/jump.js
index c878cc0..75778fc 100644
--- a/jump.js
+++ b/jump.js
@@ -1,6 +1,7 @@
export function jumpKeyUX() {
return window => {
let jumps = []
+ let lastFocusedElement = null
function focus(next) {
let current = window.document.activeElement
@@ -8,27 +9,44 @@ export function jumpKeyUX() {
jumps.push(new WeakRef(current))
}
next.focus({ focusVisible: true })
+ lastFocusedElement = null
}
- function back() {
- let ref = jumps.pop()
- if (!ref) {
- window.document.activeElement.blur()
- return
+ function jumpBack() {
+ let ref = jumps.pop();
+ if (ref) {
+ let el = ref.deref();
+
+ if (el && el.isConnected) {
+ el.focus();
+ return true;
+ }
+ return jumpBack();
}
- let el = ref.deref()
- if (el && el.isConnected) {
- el.focus()
- } else {
- back()
+ return false;
+ }
+
+ function blur() {
+ let activeElement = document.activeElement;
+ if (activeElement && activeElement !== document.body) {
+ lastFocusedElement = activeElement;
}
+ activeElement.blur();
}
-
+
+ function jumpBackOrBlur() {
+ let successfullyJumped = jumpBack();
+ if (!successfullyJumped) {
+ blur();
+ }
+ }
+
let tries = 0
let finding
function jump(from) {
clearInterval(finding)
+ tries = 0
let ariaControls = from.getAttribute('aria-controls')
finding = setInterval(() => {
if (tries++ > 50) {
@@ -53,14 +71,21 @@ export function jumpKeyUX() {
}, 50)
}
+ function restoreFocus(event) {
+ event.preventDefault()
+ lastFocusedElement.focus({ focusVisible: true })
+ lastFocusedElement = null
+ }
+
function keyDown(event) {
- if (event.target.getAttribute('aria-controls')) {
- if (event.key === 'Enter') {
- jump(event.target)
- }
+ if (event.target.getAttribute('aria-controls') && event.key === 'Enter') {
+ jump(event.target)
}
- if (event.key === 'Escape') {
- back()
+
+ if (event.key === 'Tab' && lastFocusedElement) {
+ restoreFocus(event)
+ } else if (event.key === 'Escape') {
+ jumpBackOrBlur()
}
}
diff --git a/package.json b/package.json
index 25516b4..a7c2f22 100644
--- a/package.json
+++ b/package.json
@@ -85,7 +85,7 @@
"import": {
"./index.js": "{ startKeyUX, hotkeyKeyUX, pressKeyUX, focusGroupKeyUX, jumpKeyUX, hiddenKeyUX, likelyWithKeyboard, getHotKeyHint, hotkeyOverrides, hotkeyMacCompat }"
},
- "limit": "2162 B"
+ "limit": "2208 B"
}
],
"clean-publish": {
diff --git a/test/jump.test.ts b/test/jump.test.ts
index fa2f595..f82703e 100644
--- a/test/jump.test.ts
+++ b/test/jump.test.ts
@@ -6,8 +6,35 @@ import { setTimeout } from 'node:timers/promises'
import { jumpKeyUX, startKeyUX } from '../index.js'
import { keyboardClick, mouseClick, press } from './utils.js'
+test('jumps to next next elemnt, blur and restore focus on the last element', async () => {
+ let { window } = new JSDOM()
+ global.document = window.document
+
+ startKeyUX(window, [jumpKeyUX()])
+ window.document.body.innerHTML =
+ '' +
+ ''
+ let step1 = window.document.querySelector('#step1')!
+ let step2 = window.document.querySelector('#step1')!
+
+ step1.focus()
+
+ press(window, 'Tab')
+ equal(window.document.activeElement, step1)
+ await setTimeout(50)
+ equal(window.document.activeElement, step2)
+
+ press(window, 'Escape')
+ equal(window.document.activeElement, window.document.body)
+
+ press(window, 'Tab')
+ equal(window.document.activeElement, step2)
+})
+
test('jumps to next area by click and back by escape', async () => {
- let window = new JSDOM().window
+ let { window } = new JSDOM()
+ global.document = window.document
+
startKeyUX(window, [jumpKeyUX()])
window.document.body.innerHTML =
'' +
@@ -26,6 +53,7 @@ test('jumps to next area by click and back by escape', async () => {
step1.focus()
keyboardClick(window, step1)
+
equal(window.document.activeElement, step1)
await setTimeout(50)
equal(window.document.activeElement, step2button)
@@ -71,6 +99,7 @@ test('stops event tracking', async () => {
keyboardClick(window, step1)
await setTimeout(50)
equal(window.document.activeElement, window.document.body)
+
})
test('ignores mouse click', async () => {