Scenario
I've built custom autocomplete select component where it autocompletes but in addition, if the result is not found we can add new value.(I found it is not possible in vue-select module).
Question
I tried using @blur but the menu gets closed if I click on the item. I've custom div as a list of items for selecting which is getting toggled if the focus is on text field. If I remove blur it works perfect but it doesn't get closed it I click outside of text field. I've copied functions for handling keyup,keydown,keyenter,scrolling,handling pointer for select from vue-select
CODE
<template>
<div class="vselect">
<div class="form-group">
<label>{{label}}</label>
<p class="control has-icon has-icon-right">
<input ref="selected" @keyup="filterSelect($event)" @focus="onFocus" v-model="mutableValue" type="text" class="form-control"
@keydown.up.prevent="onKeyUp" @keydown.down.prevent="onKeyDown" @keydown.enter.prevent="onKeyEnter" @blur="onBlur">
</p>
</div>
<div ref="dropdown" class="my-dropdown" v-show="toggled" v-if="options">
<div v-for="(item,index) in filterList" :class="{'my-dropdwon--item':true,active:index === pointer}" @click="handleItemClick(item)"
@mouseover="pointer = index">{{item}}</div>
</div>
</div>
</template>
<script>
export default {
name: 'VSelect',
props: {
'options': Array,
'label': String,
'value': String,
},
data() {
return {
selected: null,
toggled: false,
filterList: [],
mutableValue: null,
mutableOptions: [],
pointer: 0
}
},
methods: {
filterSelect: function (key) {
if (!this.toggled) this.toggled = !this.toggled;
let oldArr = this.options;
if (this.mutableValue && this.mutableValue.length <= 0)
this.filterList = this.mutableOptions;
else if (key.key.length == 1 || key.key == 'Backspace') {
oldArr = oldArr.filter(item => {
if (item.toLowerCase().includes(this.mutableValue.toLowerCase()))
return true;
})
this.filterList = oldArr;
//console.log('type', this.filterList)
}
// if (key.key == 'Enter')
// this.toggled = !this.toggled;
this.$emit('input', this.mutableValue);
},
handleItemClick: function (item) {
this.mutableValue = item;
this.$emit('input', item);
this.toggled = !this.toggled;
},
onFocus: function () {
this.$refs.dropdown.scrollTop = 0;
this.toggled = !this.toggled;
},
onBlur: function () {
this.handleItemClick(this.mutableValue)
this.$refs.selected.blur();
},
onKeyUp: function () {
if (this.pointer > 0) this.pointer--;
if (this.maybeAdjustScroll) {
this.maybeAdjustScroll()
}
},
onKeyDown: function () {
if (this.pointer < this.options.length && this.filterList.length) this.pointer++;
if (this.pointer == this.options.length) this.pointer = 0;
if (this.maybeAdjustScroll) {
this.maybeAdjustScroll()
}
},
onKeyEnter: function () {
//console.log(this.filterList.length> 0);
if(this.filterList.length > 0)
this.handleItemClick(this.filterList[this.pointer])
this.$refs.selected.blur();
this.$emit('input', this.mutableValue);
this.toggled = false;
},
maybeAdjustScroll() {
let pixelsToPointerTop = this.pixelsToPointerTop()
let pixelsToPointerBottom = this.pixelsToPointerBottom()
//console.log(pixelsToPointerTop,pixelsToPointerBottom);
if (pixelsToPointerTop <= this.viewport().top) {
return this.scrollTo(pixelsToPointerTop)
} else if (pixelsToPointerBottom >= this.viewport().bottom) {
return this.scrollTo(this.viewport().top + this.pointerHeight())
}
},
pixelsToPointerTop() {
let pixelsToPointerTop = 0
if (this.$refs.dropdown && this.$refs.dropdown.children) {
for (let i = 0; i < this.pointer; i++) {
pixelsToPointerTop += this.$refs.dropdown.children[i].offsetHeight
}
}
return pixelsToPointerTop
},
pixelsToPointerBottom() {
return this.pixelsToPointerTop() + this.pointerHeight()
},
pointerHeight() {
let element = this.$refs.dropdown ? this.$refs.dropdown.children[this.pointer] : false
return element ? element.offsetHeight : 0
},
viewport() {
return {
top: this.$refs.dropdown ? this.$refs.dropdown.scrollTop : 0,
bottom: this.$refs.dropdown ? this.$refs.dropdown.offsetHeight + this.$refs.dropdown.scrollTop : 0
}
},
scrollTo(position) {
//console.log(position);
return this.$refs.dropdown ? this.$refs.dropdown.scrollTop = position : null
},
},
mounted() {
this.filterList = this.options;
},
watch: {
value(val) {
this.mutableValue = val
},
options(val) {
this.mutableOptions = val
},
pointer() {
this.maybeAdjustScroll()
}
}
}
</script>
<style scoped>
.vselect {
display: block;
position: relative;
}
.my-dropdown {
width: 100%;
background: #f7f7f7;
margin-top: 0.1rem;
border: 1px solid #ced4da;
border-radius: 3px;
transition: all 0.5s;
position: absolute;
z-index: 1;
max-height: 10rem;
overflow: auto
}
.my-dropdwon--item {
padding: 0.5rem;
width: 100%;
transition: all 0.5s;
}
.active {
cursor: pointer;
background-color: rgb(223, 221, 221);
}
/* .my-dropdwon--item:hover {
cursor: pointer;
background-color: rgb(223, 221, 221);
} */
.form-group {
margin-bottom: 0px;
}
.control.has-icon has-icon-right {
margin-bottom: 0px;
}
.form-group>p {
margin-bottom: 0px;
}
</style>
I have same problem with you before.
The problem is
blur
event will be fired first, it will hide the list of selection. Thenclick
event will not be fired.My solution is replacing
@click
with@mousedown
eventCheck my demo at https://codepen.io/ittus/pen/qYKRPv