fix: wrong focus/tab order in dialogs
Massimo Melina committed
Jul 29, 2023 at 15:44 UTC
a7ed62a066642d801dfacaadf8742b7f9acf09d4
1 file changed
+28
shared/dialogs.ts
+28
@@ -28,6 +28,32 @@ export const dialogsDefaults: Partial<DialogOptions> = {
28
padding: true,
29
}
30
31
+// focus trapped on current dialog (MUI already does it)
32
+const focusableSelector = ['input:not([type="hidden"])', 'button', 'select', 'textarea', 'a[href]', '[tabindex]'].map(x =>
33
+ x + ':not([disabled]):not([tabindex="-1"])').join(',')
34
+window.addEventListener('keydown', ev => {
35
+ if (ev.key !== 'Tab') return
36
+ const dialogs = document.querySelectorAll('[role=dialog]')
37
+ const dialog = dialogs[dialogs.length-1]
38
+ if (!dialog) return
39
+ const focusable = dialog.querySelectorAll(focusableSelector)
40
+ const n = focusable.length
41
+ if (!n) return
42
+ const [a, b] = ev.shiftKey ? [n-1, 0] : [0, n-1]
43
+ if (ev.target !== focusable[b] && isDescendant(document.activeElement, dialog)) return // default behavior
44
+ ;(focusable[a] as HTMLElement).focus()
45
+ ev.preventDefault()
46
+})
47
+
48
+function isDescendant(child: Node | null, parent: Node) {
49
+ while (child) {
50
+ if (child === parent)
51
+ return true
52
+ child = child.parentNode
53
+ }
54
+ return false
55
+}
56
+
57
export function Dialogs() {
58
const snap = useSnapshot(dialogs)
59
useEffect(() => {
@@ -57,6 +83,8 @@ function Dialog(d:DialogOptions) {
83
},
84
d.noFrame ? h(d.Content || 'div')
85
: h('div', {
86
+ role: 'dialog',
87
+ 'aria-modal': true,
88
className: 'dialog',
89
style: {
90
...position(),