Skip to content

Commit 07bc39f

Browse files
committed
fix: kind of fix modal tabbing order (and tabbing in general)
1 parent e645cd9 commit 07bc39f

File tree

3 files changed

+56
-88
lines changed

3 files changed

+56
-88
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apps/backend/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "web"
3-
version = "3.1.14"
3+
version = "3.1.15"
44
edition = "2024"
55

66
[dependencies]

apps/frontend/src/components/elements/Modal.vue

Lines changed: 54 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -21,62 +21,69 @@
2121
leave-to-class="opacity-0 scale-95 translate-y-4"
2222
>
2323
<div v-if="isOpen" class="z-100 fixed inset-0 transform overflow-y-auto">
24-
<div
25-
class="flex min-h-full items-center justify-center p-4"
26-
@click="handleBackdropClick"
27-
>
28-
<div v-if="$slots.modal">
29-
<slot name="modal" />
30-
</div>
24+
<dialog style="all: unset">
3125
<div
32-
v-else
33-
ref="modalRef"
34-
class="relative w-full max-w-lg transform divide-y divide-neutral-700 rounded-3xl border border-neutral-700 bg-neutral-950"
35-
role="dialog"
36-
:aria-labelledby="titleId"
37-
:aria-describedby="descriptionId"
38-
aria-modal="true"
39-
@click.stop
26+
class="flex min-h-full items-center justify-center p-4"
27+
@click="handleBackdropClick"
4028
>
29+
<div v-if="$slots.modal">
30+
<slot name="modal" />
31+
</div>
4132
<div
42-
v-if="title || $slots.header"
43-
ref="titleRef"
44-
class="flex items-center justify-between p-4"
33+
v-else
34+
ref="modalRef"
35+
class="relative w-full max-w-lg transform divide-y divide-neutral-700 rounded-3xl border border-neutral-700 bg-neutral-950"
36+
role="dialog"
37+
:aria-labelledby="titleId"
38+
:aria-describedby="descriptionId"
39+
aria-modal="true"
40+
@click.stop
4541
>
46-
<div v-if="$slots.header">
47-
<slot name="header" />
48-
</div>
49-
<h2 v-else-if="title" :id="titleId" class="text-xl font-semibold">
50-
{{ title }}
51-
</h2>
52-
53-
<button
54-
v-if="closable"
55-
type="button"
56-
class="hover:text-brand-50 cursor-pointer rounded-full border border-neutral-700 bg-neutral-900 p-1 transition-colors hover:bg-neutral-800"
57-
@click="closeModal"
42+
<div
43+
v-if="title || $slots.header"
44+
ref="titleRef"
45+
class="flex items-center justify-between p-4"
5846
>
59-
<span class="sr-only">Close modal</span>
60-
<Icon name="pixelarticons:close" :size="24" mode="svg" />
61-
</button>
62-
</div>
47+
<div v-if="$slots.header">
48+
<slot name="header" />
49+
</div>
50+
<h2
51+
v-else-if="title"
52+
:id="titleId"
53+
class="text-xl font-semibold"
54+
>
55+
{{ title }}
56+
</h2>
57+
58+
<button
59+
v-if="closable"
60+
autofocus
61+
type="button"
62+
class="hover:text-brand-50 cursor-pointer rounded-full border border-neutral-700 bg-neutral-900 p-1 transition-colors hover:bg-neutral-800"
63+
@click="closeModal"
64+
>
65+
<span class="sr-only">Close modal</span>
66+
<Icon name="pixelarticons:close" :size="24" mode="svg" />
67+
</button>
68+
</div>
6369

64-
<div
65-
class="scrollbar-none overflow-y-scroll p-4"
66-
:style="`max-height: calc(100vh - ${combinedHeight}px - 48px)`"
67-
>
68-
<slot />
69-
</div>
70+
<div
71+
class="scrollbar-none overflow-y-scroll p-4"
72+
:style="`max-height: calc(100vh - ${combinedHeight}px - 48px)`"
73+
>
74+
<slot />
75+
</div>
7076

71-
<div
72-
v-if="$slots.footer"
73-
ref="footerRef"
74-
class="flex flex-col items-center justify-end gap-2 p-4 md:flex-row"
75-
>
76-
<slot name="footer" />
77+
<div
78+
v-if="$slots.footer"
79+
ref="footerRef"
80+
class="flex flex-col items-center justify-end gap-2 p-4 md:flex-row"
81+
>
82+
<slot name="footer" />
83+
</div>
7784
</div>
7885
</div>
79-
</div>
86+
</dialog>
8087
</div>
8188
</Transition>
8289
</Teleport>
@@ -129,57 +136,18 @@ const handleKeydown = (event: KeyboardEvent) => {
129136
}
130137
}
131138
132-
const focusableElements = computed(() => {
133-
if (!modalRef.value) return []
134-
135-
const selector =
136-
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
137-
return Array.from(modalRef.value.querySelectorAll(selector)) as HTMLElement[]
138-
})
139-
140-
const firstFocusableElement = computed(() => focusableElements.value[0])
141-
const lastFocusableElement = computed(
142-
() => focusableElements.value[focusableElements.value.length - 1]
143-
)
144-
145-
const handleTabKey = (event: KeyboardEvent) => {
146-
if (event.key !== 'Tab') return
147-
148-
if (focusableElements.value.length === 0) {
149-
event.preventDefault()
150-
return
151-
}
152-
153-
if (event.shiftKey) {
154-
if (document.activeElement === firstFocusableElement.value) {
155-
event.preventDefault()
156-
lastFocusableElement.value?.focus()
157-
}
158-
} else {
159-
if (document.activeElement === lastFocusableElement.value) {
160-
event.preventDefault()
161-
firstFocusableElement.value?.focus()
162-
}
163-
}
164-
}
165-
166139
onMounted(() => {
167140
document.addEventListener('keydown', handleKeydown)
168-
document.addEventListener('keydown', handleTabKey)
169141
})
170142
171143
onUnmounted(() => {
172144
document.removeEventListener('keydown', handleKeydown)
173-
document.removeEventListener('keydown', handleTabKey)
174145
})
175146
176147
watch(
177148
() => props.isOpen,
178149
(isOpen) => {
179150
if (isOpen) {
180-
nextTick(() => {
181-
firstFocusableElement.value?.focus() || modalRef.value?.focus()
182-
})
183151
document.body.style.overflow = 'hidden'
184152
} else {
185153
document.body.style.overflow = ''

0 commit comments

Comments
 (0)