new Vue({
el: '.single-select',
data: {
hover: false,
dropdownShow: false,
input: '',
selection: {},
pointer: -1,
filteredResults: [],
diff: 0
},
computed: {
visibleResults() {
return this.dropdownShow && window.veg.length > 0
},
sortedResults() {
return window.veg.sort((a, b) => this.compare(a, b))
}
},
methods: {
compare(a, b) {
if (a.text < b.text) {
return -1
}
if (a.text > b.text) {
return 1
}
return 0
},
select(index) {
if(index >= 0) {
this.error = false
this.selection = this.sortedResults[index]
this.input = this.selection.text
this.$emit('input', this.selection)
this.closeDropdown()
}
else {
this.error = true
this.closeDropdown()
this.hover = false
}
},
showResults() {
this.dropdownShow = true
this.error = false
},
closeDropdown() {
this.dropdownShow = false
this.hover = false
},
toggleDropdown() {
this.dropdownShow = !this.dropdownShow
this.hover = !this.hover
},
setPointerIndex(index) {
this.pointer = index
},
movePointerDown() {
if (!this.sortedResults) {
return
}
if (this.pointer >= this.sortedResults.length - 1) {
return
}
if (!this.visibleResults) {
return
}
this.pointer++
if(this.pointer > 5) {
this.$refs.dropdown.scrollTop += 40
}
},
movePointerUp() {
if (this.pointer > 0 && this.visibleResults) {
this.pointer--
if(this.pointer <= 5) {
this.$refs.dropdown.scrollTop -= 40
}
}
},
setPointer(event) {
if (event.key != "ArrowDown" && event.key != "ArrowUp" && event.key != "Enter" && event.key != "Escape" && event.key != "Tab") {
let filteredResults = this.sortedResults.filter(result => result.text.toUpperCase().startsWith(event.key.toUpperCase()))
let filteredZeroIndex = this.sortedResults.indexOf(filteredResults[0])
if (filteredResults.length > 0) {
if (this.pointer == -1 || this.sortedResults[this.pointer] == filteredResults[filteredResults.length -1] || !filteredResults.includes(this.sortedResults[this.pointer])) {
this.pointer = filteredZeroIndex
}
else if (this.sortedResults[this.pointer] == filteredResults[this.pointer - filteredZeroIndex] && this.pointer > -1) {
this.pointer++
}
this.$refs.dropdown.scrollTop = this.$refs.options[this.pointer].offsetTop
}
else {
return
}
}
}
},
})
body {
padding: 2rem;
}
.single-select {
max-width: 480px;
}
.single-select-results {
max-height: 144px;
}
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body>
<div class="single-select">
<p class="text-xs leading-5 font-medium">
Select Option
</p>
<div class="mt-1 relative">
<div @mouseover="hover = true" @mouseleave="hover = false" @mousedown="toggleDropdown" @keydown="setPointer($event)" @keydown.enter="select(pointer)" @keyup.tab.stop="closeDropdown" @keyup.esc.stop="closeDropdown" @keyup.down="movePointerDown" @keyup.up="movePointerUp" class="rnup-relative">
<input type="text" placeholder="Please select" v-model="input" readonly class="cursor-pointer select-none text-base w-full border rounded p-1">
</div>
<div class="absolute z-10 w-full">
<div ref="dropdown" v-if="visibleResults" class="single-select-results overflow-y-auto mt-sm">
<div v-for="(result, index) in sortedResults" ref="options" @mouseover="setPointerIndex(index)" @click="select(index)" @keydown.enter="select(index)" @keyup.enter="select(index)" @keyup.tab.stop="closeDropdown" @keyup.esc.stop="closeDropdown" @keyup.down="movePointerDown" @keyup.up="movePointerUp">
<p :class="{ 'bg-slate-200' : index === pointer }">
{{ result.text }}
</p>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script>
window.veg = [
{
text: 'Carrots',
},
{
text: 'Peas',
},
{
text: 'Sweetcorn',
},
{
text: 'Runner Beans',
},
{
text: 'Broccoli',
},
{
text: 'Cauliflower',
},
{
text: 'Cabbage',
},
{
text: 'Spinach',
},
{
text: 'Cake'
},
{
text: 'Spirulina'
}
]
</script>
</body>
</html>