Skip to content

Commit 69f152c

Browse files
committed
fix(a11y): keep focus after selecting or clearing an option
1 parent 6c806a0 commit 69f152c

File tree

2 files changed

+111
-5
lines changed

2 files changed

+111
-5
lines changed

src/Select.spec.ts

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1197,3 +1197,113 @@ describe("exposed component methods and refs", () => {
11971197
expect(headerIndex).toBeLessThan(firstOptionIndex);
11981198
});
11991199
});
1200+
1201+
describe("accessibility focus management", () => {
1202+
it("should keep focus on input after selecting an option via click in single-select", async () => {
1203+
const wrapper = mount(VueSelect, { props: { modelValue: null, options }, attachTo: document.body });
1204+
1205+
const input = wrapper.get("input");
1206+
await input.trigger("focus");
1207+
await openMenu(wrapper);
1208+
await wrapper.get("div[role='option']").trigger("click");
1209+
1210+
expect(document.activeElement).toBe(input.element);
1211+
1212+
wrapper.unmount();
1213+
});
1214+
1215+
it("should keep focus on input after selecting an option via keyboard in single-select", async () => {
1216+
const wrapper = mount(VueSelect, { props: { modelValue: null, options }, attachTo: document.body });
1217+
1218+
const input = wrapper.get("input");
1219+
await input.trigger("focus");
1220+
await openMenu(wrapper);
1221+
await dispatchEvent(wrapper, new KeyboardEvent("keydown", { key: "Enter" }));
1222+
1223+
expect(document.activeElement).toBe(input.element);
1224+
1225+
wrapper.unmount();
1226+
});
1227+
1228+
it("should keep focus on input after selecting an option via click in multi-select", async () => {
1229+
const wrapper = mount(VueSelect, { props: { modelValue: [], isMulti: true, options }, attachTo: document.body });
1230+
1231+
const input = wrapper.get("input");
1232+
await input.trigger("focus");
1233+
await openMenu(wrapper);
1234+
await wrapper.get("div[role='option']").trigger("click");
1235+
1236+
expect(document.activeElement).toBe(input.element);
1237+
1238+
wrapper.unmount();
1239+
});
1240+
1241+
it("should keep focus on input after selecting an option via keyboard in multi-select", async () => {
1242+
const wrapper = mount(VueSelect, { props: { modelValue: [], isMulti: true, options }, attachTo: document.body });
1243+
1244+
const input = wrapper.get("input");
1245+
await input.trigger("focus");
1246+
await openMenu(wrapper);
1247+
await dispatchEvent(wrapper, new KeyboardEvent("keydown", { key: "Enter" }));
1248+
1249+
expect(document.activeElement).toBe(input.element);
1250+
1251+
wrapper.unmount();
1252+
});
1253+
1254+
it("should keep focus on input after clearing via clear button in single-select", async () => {
1255+
const wrapper = mount(VueSelect, { props: { modelValue: options[0]?.value, options, isClearable: true }, attachTo: document.body });
1256+
1257+
const input = wrapper.get("input");
1258+
await input.trigger("focus");
1259+
await wrapper.get(".clear-button").trigger("click");
1260+
1261+
expect(document.activeElement).toBe(input.element);
1262+
1263+
wrapper.unmount();
1264+
});
1265+
1266+
it("should keep focus on input after clearing via clear button in multi-select", async () => {
1267+
const wrapper = mount(VueSelect, { props: { modelValue: [options[0]?.value], isMulti: true, options, isClearable: true }, attachTo: document.body });
1268+
1269+
const input = wrapper.get("input");
1270+
await input.trigger("focus");
1271+
await wrapper.get(".clear-button").trigger("click");
1272+
1273+
expect(document.activeElement).toBe(input.element);
1274+
1275+
wrapper.unmount();
1276+
});
1277+
1278+
it("should keep focus on input after clearing via exposed clear method", async () => {
1279+
const wrapper = mount(VueSelect, { props: { modelValue: options[0]?.value, options }, attachTo: document.body });
1280+
1281+
const input = wrapper.get("input");
1282+
await input.trigger("focus");
1283+
1284+
wrapper.vm.clear();
1285+
await wrapper.vm.$nextTick();
1286+
1287+
expect(document.activeElement).toBe(input.element);
1288+
1289+
wrapper.unmount();
1290+
});
1291+
1292+
it("should keep focus on input after selecting with Space key", async () => {
1293+
const wrapper = mount(VueSelect, { props: { modelValue: null, options }, attachTo: document.body });
1294+
1295+
const input = wrapper.get("input");
1296+
await input.trigger("focus");
1297+
await openMenu(wrapper);
1298+
1299+
const event = new KeyboardEvent("keydown", {});
1300+
Object.defineProperty(event, "code", { value: "Space" });
1301+
Object.defineProperty(event, "key", { value: " " });
1302+
document.dispatchEvent(event);
1303+
await wrapper.vm.$nextTick();
1304+
1305+
expect(document.activeElement).toBe(input.element);
1306+
1307+
wrapper.unmount();
1308+
});
1309+
});

src/Select.vue

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -223,10 +223,6 @@ const setOption = (option: GenericOption) => {
223223
if (props.closeOnSelect) {
224224
closeMenu();
225225
}
226-
227-
if (inputRef.value) {
228-
inputRef.value.blur();
229-
}
230226
};
231227
232228
const removeOption = (option: GenericOption) => {
@@ -267,7 +263,7 @@ const clear = () => {
267263
}
268264
269265
if (inputRef.value) {
270-
inputRef.value.blur();
266+
inputRef.value.focus();
271267
}
272268
};
273269

0 commit comments

Comments
 (0)