mirror of
https://github.com/tenrok/vue-select.git
synced 2026-06-22 10:30:34 +03:00
WIP
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:
+46
-7
@@ -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>
|
||||||
|
|||||||
@@ -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');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
Reference in New Issue
Block a user