-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmenu.js
More file actions
111 lines (99 loc) · 2.98 KB
/
menu.js
File metadata and controls
111 lines (99 loc) · 2.98 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
export function menuKeyUX(options) {
return window => {
let inMenu = false
let typingDelayMs = options?.searchDelayMs || 300
let lastTyped = 0
let searchPrefix = ''
function focus(current, next) {
next.tabIndex = 0
next.focus()
current.tabIndex = -1
}
function keyDown(event) {
if (event.target.role !== 'menuitem') {
stop()
return
}
let menu = event.target.closest('[role="menu"]')
if (!menu) return
let items = menu.querySelectorAll('[role="menuitem"]')
let index = Array.from(items).indexOf(event.target)
let nextKey = 'ArrowDown'
let prevKey = 'ArrowUp'
if (menu.getAttribute('aria-orientation') === 'horizontal') {
if (window.document.dir === 'rtl') {
nextKey = 'ArrowLeft'
prevKey = 'ArrowRight'
} else {
nextKey = 'ArrowRight'
prevKey = 'ArrowLeft'
}
}
if (event.key === nextKey) {
event.preventDefault()
focus(event.target, items[index + 1] || items[0])
} else if (event.key === prevKey) {
event.preventDefault()
focus(event.target, items[index - 1] || items[items.length - 1])
} else if (event.key === 'Home') {
event.preventDefault()
focus(event.target, items[0])
} else if (event.key === 'End') {
event.preventDefault()
focus(event.target, items[items.length - 1])
} else if (event.key.length === 1) {
let now = Date.now()
if (now - lastTyped <= typingDelayMs) {
searchPrefix += event.key.toLowerCase()
} else {
searchPrefix = event.key.toLowerCase()
}
lastTyped = now
let found = Array.from(items).find(item => {
return item.textContent
?.trim()
?.toLowerCase()
?.startsWith(searchPrefix)
})
if (found) {
event.preventDefault()
focus(event.target, found)
}
}
}
function stop() {
inMenu = false
window.removeEventListener('keydown', keyDown)
}
function focusIn(event) {
if (event.target.role === 'menuitem') {
let menu = event.target.closest('[role="menu"]')
if (!menu) return
if (!inMenu) {
inMenu = true
window.addEventListener('keydown', keyDown)
}
let items = menu.querySelectorAll('[role="menuitem"]')
for (let item of items) {
if (item !== event.target) {
item.setAttribute('tabindex', -1)
}
}
} else if (inMenu) {
stop()
}
}
function focusOut(event) {
if (!event.relatedTarget || event.relatedTarget === window.document) {
stop()
}
}
window.addEventListener('focusin', focusIn)
window.addEventListener('focusout', focusOut)
return () => {
stop()
window.removeEventListener('focusin', focusIn)
window.removeEventListener('focusout', focusOut)
}
}
}