Skip to content

Commit 7612120

Browse files
committed
feat: keyboard accessibility improvements
1 parent 2f97f14 commit 7612120

File tree

1 file changed

+34
-9
lines changed

1 file changed

+34
-9
lines changed

src/Select.vue

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script setup lang="ts">
2-
import { computed, onBeforeUnmount, onMounted, ref } from "vue";
2+
import { computed, onBeforeUnmount, onMounted, ref, watch } from "vue";
33
44
import type { Option } from "./types";
55
import ChevronDownIcon from "./icons/ChevronDownIcon.vue";
@@ -150,12 +150,6 @@ const closeMenu = () => {
150150
search.value = "";
151151
};
152152
153-
const focusInput = () => {
154-
if (input.value) {
155-
input.value.focus();
156-
}
157-
};
158-
159153
const setOption = (value: string) => {
160154
if (props.isMulti) {
161155
selected.value = [...selected.value, value];
@@ -214,6 +208,12 @@ const handleNavigation = (e: KeyboardEvent) => {
214208
setOption(filteredOptions.value[focusedOption.value].value);
215209
}
216210
211+
// When pressing space with menu open but no search, select the focused option.
212+
if (e.code === "Space" && search.value.length === 0) {
213+
e.preventDefault();
214+
setOption(filteredOptions.value[focusedOption.value].value);
215+
}
216+
217217
if (e.key === "Escape") {
218218
e.preventDefault();
219219
menuOpen.value = false;
@@ -222,6 +222,20 @@ const handleNavigation = (e: KeyboardEvent) => {
222222
}
223223
};
224224
225+
/**
226+
* When pressing space inside the input, open the menu only if the search is
227+
* empty. Otherwise, the user is typing and we should skip this action.
228+
*
229+
* @param e KeyboardEvent
230+
*/
231+
const handleInputSpace = (e: KeyboardEvent) => {
232+
if (!menuOpen.value && search.value.length === 0) {
233+
e.preventDefault();
234+
e.stopImmediatePropagation();
235+
openMenu();
236+
}
237+
};
238+
225239
const handleClickOutside = (event: MouseEvent) => {
226240
if (container.value && !container.value.contains(event.target as Node)) {
227241
menuOpen.value = false;
@@ -244,6 +258,16 @@ const calculateMenuPosition = () => {
244258
return { top: "0px", left: "0px" };
245259
};
246260
261+
// When focusing the input and typing, open the menu automatically.
262+
watch(
263+
() => search.value,
264+
() => {
265+
if (search.value && !menuOpen.value) {
266+
openMenu();
267+
}
268+
},
269+
);
270+
247271
onMounted(() => {
248272
document.addEventListener("click", handleClickOutside);
249273
document.addEventListener("keydown", handleNavigation);
@@ -276,7 +300,7 @@ onBeforeUnmount(() => {
276300
<div
277301
v-if="!props.isMulti && selectedOptions[0]"
278302
class="single-value"
279-
@click="focusInput"
303+
@click="input?.focus()"
280304
>
281305
<slot name="value" :option="selectedOptions[0]">
282306
{{ getOptionLabel(selectedOptions[0]) }}
@@ -309,8 +333,9 @@ onBeforeUnmount(() => {
309333
tabindex="0"
310334
:disabled="isDisabled"
311335
:placeholder="selectedOptions.length === 0 ? placeholder : ''"
312-
@focus="openMenu({ focusInput: false })"
336+
@mousedown="openMenu()"
313337
@keydown.tab="closeMenu"
338+
@keydown.space="handleInputSpace"
314339
>
315340
</div>
316341

0 commit comments

Comments
 (0)