2
0
mirror of https://github.com/tenrok/vue-select.git synced 2026-06-22 10:30:34 +03:00
Updates the root $el to be a focusable button instead of a div. Allows for separating focusing of the component from opening the dropdown
This commit is contained in:
Jeff
2020-02-15 11:27:40 -08:00
parent b7c0d539c4
commit b36267373e
4 changed files with 110 additions and 22 deletions
+46 -7
View File
@@ -1,19 +1,42 @@
<template> <template>
<div id="app"> <!-- <div id="app">-->
<sandbox hide-help v-slot="config"> <!-- <sandbox hide-help v-slot="config">-->
<v-select v-bind="config"/> <!-- <v-select v-bind="config"/>-->
</sandbox> <!-- </sandbox>-->
</div> <!-- </div>-->
<form>
<label for="name">Name</label>
<input type="text" id="name" autofocus>
<label for="email">eMail</label>
<input type="email" id="email">
<label for="emails">eMail</label>
<select id="emails">
<option value="one">one</option>
<option value="two">two</option>
</select>
<label for="country">Country</label>
<v-select :options="countries" id="country" :filterable="false" />
<label for="hello">Hello</label>
<input type="text" id="hello">
</form>
</template> </template>
<script> <script>
import vSelect from '../src/components/Select'; import vSelect from '../src/components/Select';
import Sandbox from '../docs/.vuepress/components/Sandbox'; import Sandbox from '../docs/.vuepress/components/Sandbox';
// import countries from '../docs/.vuepress/data/countryCodes'; import countries from '../docs/.vuepress/data/countryCodes';
// import books from '../docs/.vuepress/data/books'; import books from '../docs/.vuepress/data/books';
export default { export default {
components: {Sandbox, vSelect}, components: {Sandbox, vSelect},
computed: {
countries: () => countries,
books: () => books,
}
}; };
</script> </script>
@@ -36,4 +59,20 @@ export default {
padding-top: 1em; padding-top: 1em;
width: 90%; width: 90%;
} }
form {
width: 500px;
margin: 0 auto;
padding-top: 5rem;
}
label {
width:100%;
display: block;
margin-top:1rem;
}
input {
width: 100%;
}
</style> </style>
+42 -9
View File
@@ -3,8 +3,7 @@
</style> </style>
<template> <template>
<div :dir="dir" class="v-select" :class="stateClasses"> <button :tabindex="tabindex" type="button" @keydown="keypressWhileToggleIsFocused" ref="toggle" @mousedown.prevent="maybeToggleDropdown" class="v-select vs__dropdown-toggle" :dir="dir" :class="stateClasses">
<div ref="toggle" @mousedown.prevent="toggleDropdown" class="vs__dropdown-toggle">
<div class="vs__selected-options" ref="selectedOptions"> <div class="vs__selected-options" ref="selectedOptions">
<slot v-for="option in selectedValue" <slot v-for="option in selectedValue"
@@ -49,8 +48,6 @@
<div class="vs__spinner" v-show="mutableLoading">Loading...</div> <div class="vs__spinner" v-show="mutableLoading">Loading...</div>
</slot> </slot>
</div> </div>
</div>
<transition :name="transition"> <transition :name="transition">
<ul ref="dropdownMenu" v-if="dropdownOpen" class="vs__dropdown-menu" role="listbox" @mousedown.prevent="onMousedown" @mouseup="onMouseUp"> <ul ref="dropdownMenu" v-if="dropdownOpen" class="vs__dropdown-menu" role="listbox" @mousedown.prevent="onMousedown" @mouseup="onMouseUp">
<li <li
@@ -71,7 +68,8 @@
</li> </li>
</ul> </ul>
</transition> </transition>
</div> </button>
</template> </template>
<script type="text/babel"> <script type="text/babel">
@@ -508,6 +506,7 @@
search: '', search: '',
open: false, open: false,
isComposing: false, isComposing: false,
shouldDisplaySearch: true,
pushedTags: [], pushedTags: [],
_value: [] // Internal value managed by Vue Select if no `value` prop is passed _value: [] // Internal value managed by Vue Select if no `value` prop is passed
} }
@@ -666,7 +665,7 @@
* @param {Event} e * @param {Event} e
* @return {void} * @return {void}
*/ */
toggleDropdown ({target}) { maybeToggleDropdown ({target}) {
// don't react to click on deselect/clear buttons, // don't react to click on deselect/clear buttons,
// they dropdown state will be set in their click handlers // they dropdown state will be set in their click handlers
const ignoredButtons = [ const ignoredButtons = [
@@ -678,7 +677,11 @@
return; return;
} }
if (this.open) { this.toggleDropdown(true);
},
toggleDropdown(toggle = true) {
if (this.open || ! toggle) {
this.searchEl.blur(); this.searchEl.blur();
} else if (!this.disabled) { } else if (!this.disabled) {
this.open = true; this.open = true;
@@ -829,7 +832,8 @@
if (this.clearSearchOnBlur) { if (this.clearSearchOnBlur) {
this.search = '' this.search = ''
} }
this.closeSearchOptions() this.closeSearchOptions();
this.$refs.toggle.focus();
return return
} }
// Fixed bug where no-options message could not be closed // Fixed bug where no-options message could not be closed
@@ -907,6 +911,31 @@
if (typeof handlers[e.keyCode] === 'function') { if (typeof handlers[e.keyCode] === 'function') {
return handlers[e.keyCode](e); return handlers[e.keyCode](e);
} }
},
/**
* @param e {KeyboardEvent}
*/
keypressWhileToggleIsFocused(e) {
// if( e.target === this.searchEl ) {
// return;
// }
//
// if( 'Tab' === e.code ) {
// e.preventDefault();
// return
// }
//
// if( ['Space', 'Return'].includes(e.code) ) {
// return this.toggleDropdown();
// }
//
// if( ['ShiftLeft', 'ShiftRight'].includes(e.code)) {
// e.preventDefault();
// return;
// }
//
// return this.toggleDropdown()
} }
}, },
@@ -970,7 +999,7 @@
attributes: { attributes: {
'disabled': this.disabled, 'disabled': this.disabled,
'placeholder': this.searchPlaceholder, 'placeholder': this.searchPlaceholder,
'tabindex': this.tabindex, 'tabindex': '-1',
'readonly': !this.searchable, 'readonly': !this.searchable,
'id': this.inputId, 'id': this.inputId,
'aria-expanded': this.dropdownOpen, 'aria-expanded': this.dropdownOpen,
@@ -1106,6 +1135,10 @@
*/ */
showClearButton() { showClearButton() {
return !this.multiple && this.clearable && !this.open && !this.isValueEmpty return !this.multiple && this.clearable && !this.open && !this.isValueEmpty
},
buttonIsFocused() {
return this.$el === this.$root.$el.querySelector(':focus');
} }
}, },
+1
View File
@@ -18,6 +18,7 @@ $border-radius: $vs-border-radius;
.vs__dropdown-toggle { .vs__dropdown-toggle {
appearance: none; appearance: none;
display: flex; display: flex;
width: 100%;
padding: 0 0 4px 0; padding: 0 0 4px 0;
background: none; background: none;
border: $border-width $border-style $border-color; border: $border-width $border-style $border-color;
+21 -6
View File
@@ -1,10 +1,25 @@
import { selectWithProps } from "../helpers"; import { mountDefault, selectWithProps } from '../helpers';
import OpenIndicator from "../../src/components/OpenIndicator"; import OpenIndicator from "../../src/components/OpenIndicator";
describe("Toggling Dropdown", () => { describe("Toggling Dropdown", () => {
fdescribe('focusing on the button wrapper', () => {
test('when the search is focused buttonIsFocused is false', () => {
const Select = mountDefault();
Select.vm.$refs.search.focus();
expect(Select.vm.buttonIsFocused).toBeFalsy();
})
test('when the button is focused, buttonIsFocused is true', async () => {
const Select = mountDefault();
Select.vm.$refs.toggle.focus();
await Select.vm.$nextTick();
expect(Select.vm.buttonIsFocused).toBeTruthy();
})
});
it("should not open the dropdown when the el is clicked but the component is disabled", () => { it("should not open the dropdown when the el is clicked but the component is disabled", () => {
const Select = selectWithProps({ disabled: true }); const Select = selectWithProps({ disabled: true });
Select.vm.toggleDropdown({ target: Select.vm.$refs.search }); Select.vm.maybeToggleDropdown({ target: Select.vm.$refs.search });
expect(Select.vm.open).toEqual(false); expect(Select.vm.open).toEqual(false);
}); });
@@ -14,7 +29,7 @@ describe("Toggling Dropdown", () => {
options: [{ label: "one" }] options: [{ label: "one" }]
}); });
Select.vm.toggleDropdown({ target: Select.vm.$refs.search }); Select.vm.maybeToggleDropdown({ target: Select.vm.$refs.search });
expect(Select.vm.open).toEqual(true); expect(Select.vm.open).toEqual(true);
}); });
@@ -26,7 +41,7 @@ describe("Toggling Dropdown", () => {
const selectedTag = Select.find(".vs__selected").element; const selectedTag = Select.find(".vs__selected").element;
Select.vm.toggleDropdown({ target: selectedTag }); Select.vm.maybeToggleDropdown({ target: selectedTag });
expect(Select.vm.open).toEqual(true); expect(Select.vm.open).toEqual(true);
}); });
@@ -35,7 +50,7 @@ describe("Toggling Dropdown", () => {
const spy = jest.spyOn(Select.vm.$refs.search, "blur"); const spy = jest.spyOn(Select.vm.$refs.search, "blur");
Select.vm.open = true; Select.vm.open = true;
Select.vm.toggleDropdown({ target: Select.vm.$el }); Select.vm.maybeToggleDropdown({ target: Select.vm.$el });
expect(spy).toHaveBeenCalled(); expect(spy).toHaveBeenCalled();
}); });
@@ -134,7 +149,7 @@ describe("Toggling Dropdown", () => {
noDrop: true, noDrop: true,
}); });
Select.vm.toggleDropdown({ target: Select.vm.$refs.search }); Select.vm.maybeToggleDropdown({ target: Select.vm.$refs.search });
expect(Select.vm.open).toEqual(true); expect(Select.vm.open).toEqual(true);
expect(Select.contains('.vs__dropdown-menu')).toBeFalsy(); expect(Select.contains('.vs__dropdown-menu')).toBeFalsy();
expect(Select.vm.stateClasses['vs--open']).toBeFalsy(); expect(Select.vm.stateClasses['vs--open']).toBeFalsy();