From 7e1a8a5ed3c8809f687972aafa6218598c7353ef Mon Sep 17 00:00:00 2001 From: Thomas Cazade Date: Sun, 17 Nov 2024 10:01:20 +0100 Subject: [PATCH] feat(select): add shouldAutofocusOption prop (#134) --- docs/props.md | 8 ++++++++ src/Select.spec.ts | 24 ++++++++++++++++++++++++ src/Select.vue | 11 ++++++++++- 3 files changed, 42 insertions(+), 1 deletion(-) diff --git a/docs/props.md b/docs/props.md index b4a4ad7..910bd84 100644 --- a/docs/props.md +++ b/docs/props.md @@ -122,6 +122,14 @@ Whether the select should allow multiple selections. If `true`, the `v-model` sh Whether the select should display a loading state. When `true`, the select will show a loading spinner or custom loading content provided via the `loading` slot. +## shouldAutofocusOption + +**Type**: `boolean` + +**Default**: `true` + +Whether the first option should be focused when the dropdown is opened. If set to `false`, the first option will not be focused, and the user will have to navigate through the options using the keyboard. + ## closeOnSelect **Type**: `boolean` diff --git a/src/Select.spec.ts b/src/Select.spec.ts index 726ef3c..857592d 100644 --- a/src/Select.spec.ts +++ b/src/Select.spec.ts @@ -298,6 +298,14 @@ describe("single-select option", () => { expect(wrapper.emitted("update:modelValue")).toBeUndefined(); }); + + it("should autofocus the first option when opening the menu, by default", async () => { + const wrapper = mount(VueSelect, { props: { modelValue: null, options } }); + + await openMenu(wrapper); + + expect(wrapper.get(".focused[role='option']").text()).toBe(options[0].label); + }); }); describe("multi-select options", () => { @@ -336,6 +344,14 @@ describe("multi-select options", () => { expect(wrapper.findAll(".menu-option").length).toBe(options.length); expect(wrapper.findAll(".multi-value").length).toBe(0); }); + + it("should autofocus the first option when opening the menu, by default", async () => { + const wrapper = mount(VueSelect, { props: { modelValue: [], isMulti: true, options } }); + + await openMenu(wrapper); + + expect(wrapper.get(".focused[role='option']").text()).toBe(options[0].label); + }); }); describe("clear button", () => { @@ -411,4 +427,12 @@ describe("component props", () => { expect(wrapper.findAll("div[role='option']").length).toBe(options.length); }); + + it("should not autofocus an option when passing the autofocus prop", async () => { + const wrapper = mount(VueSelect, { props: { modelValue: null, options, shouldAutofocusOption: false } }); + + await openMenu(wrapper); + + expect(wrapper.findAll(".focused[role='option']")).toHaveLength(0); + }); }); diff --git a/src/Select.vue b/src/Select.vue index ce6d178..e701d1f 100644 --- a/src/Select.vue +++ b/src/Select.vue @@ -42,6 +42,11 @@ const props = withDefaults( * when fetching the options asynchronously. */ isLoading?: boolean; + /** + * When set to true, focus the first option when the menu is opened. + * When set to false, no option will be focused. + */ + shouldAutofocusOption?: boolean; /** * When set to true, clear the search input when an option is selected. */ @@ -98,6 +103,7 @@ const props = withDefaults( isSearchable: true, isMulti: false, isLoading: false, + shouldAutofocusOption: true, closeOnSelect: true, teleport: undefined, inputId: undefined, @@ -171,7 +177,10 @@ const selectedOptions = computed(() => { const openMenu = (options?: { focusInput?: boolean }) => { menuOpen.value = true; - focusedOption.value = props.options.findIndex((option) => !option.disabled); + + if (props.shouldAutofocusOption) { + focusedOption.value = props.options.findIndex((option) => !option.disabled); + } if (options?.focusInput && input.value) { input.value.focus();