PronounsPage/components/AutocompleteSelect.vue

124 lines
3.3 KiB
Vue

<template>
<div class="select flex-grow-1">
<input
ref="filter"
v-model="filter"
type="text"
class="form-control"
v-bind="$attrs"
@focus="show"
@blur="hide"
@keydown="filterKeydown"
>
<div v-show="shown" class="list-group shadow">
<a
v-for="(display, option) in visibleOptions()"
:class="['list-group-item', 'list-group-item-action', highlightedOption === option ? 'active' : '']"
href="#"
@click.prevent="select(option)"
>
{{ display }}
</a>
</div>
</div>
</template>
<script>
export default {
inheritAttrs: false,
props: {
modelValue: { required: true, type: String },
options: { required: true },
freeform: { type: Boolean },
},
emits: ['update:modelValue'],
data() {
return {
filter: this.options[this.modelValue] || this.modelValue || '',
shown: false,
highlighted: -1,
};
},
computed: {
highlightedOption() {
return Object.keys(this.visibleOptions())[this.highlighted];
},
},
watch: {
modelValue() {
this.filter = this.options[this.modelValue] || this.modelValue;
this.highlighted = -1;
},
filter() {
if (this.freeform) {
this.$emit('update:modelValue', this.filter);
}
},
},
methods: {
select(option) {
this.$emit('update:modelValue', option);
this.hide();
this.highlighted = -1;
},
show() {
this.shown = true;
},
hide() {
setTimeout(() => {
this.shown = false;
}, 100);
},
filterKeydown(e) {
if (!this.shown) {
this.show();
}
switch (e.key) {
case 'ArrowUp':
this.highlighted--;
e.preventDefault();
e.stopPropagation();
break;
case 'ArrowDown':
this.highlighted++;
e.preventDefault();
e.stopPropagation();
break;
case 'Enter':
if (this.highlightedOption) {
this.select(this.highlightedOption);
e.preventDefault();
e.stopPropagation();
}
break;
default:
break;
}
},
visibleOptions() {
return Object.fromEntries(Object.entries(this.options).filter(([option, display]) => {
return !this.filter ||
option.toLowerCase().includes(this.filter.toLowerCase()) ||
display.toLowerCase().includes(this.filter.toLowerCase());
}));
},
},
};
</script>
<style lang="scss" scoped>
.select {
position: relative;
.list-group {
position: absolute;
top: 100%;
max-height: 300px;
width: min(300px, 100%);
overflow-y: auto;
z-index: 999;
}
}
</style>