Source: components/autocomplete.vue

<template>
    <q-select ref="select" class="q-ma-none q-pa-none" :options="filteredOptions" v-model="selectedItem"
        :option-label="optionLabel" :option-value="optionValue" :clearable="clearable" :emit-value="emitValue"
        :map-options="mapOptions" :dense="dense" :options-dense="optionsDense" :outlined="outlined" input-debounce="0"
        @focus="focus" :filled="filled" :label="label" :bg-color="bgColor" options-html display-value-html
        @popup-show="startEditing" :disable="disable">
        <template v-slot:before-options v-if="searchable">
            <q-icon name="search" />
            <input ref="inputFilter" v-model="filter" style="outline: none; border:0" />
        </template>
    </q-select>
</template>
<script>
/**
 * Autocomplete component for selecting options from a dropdown list.
 *
 * @component Autocomplete
 *
 */
export default {
    name: "Autocomplete",
    props: {
        disable: { type: Boolean, default: false },
        searchable: { type: Boolean, default: true },
        clearable: { type: Boolean, default: true },
        emitValue: { type: Boolean, default: false },
        mapOptions: { type: Boolean, default: false },
        optionsDense: { type: Boolean, default: true },
        outlined: { type: Boolean, default: true },
        options: { type: Array, default: () => [] },
        optionLabel: { type: String, default: "label" },
        optionValue: { type: String, default: "value" },
        filled: { type: Boolean, default: true },
        dense: { type: Boolean, default: true },
        label: { type: String, default: undefined },
        bgColor: { type: String, default: "white" },
        value: { type: [String, Number, Array, Object, Boolean], default: null }
    },
    data() {
        return {
            selectedItem: this.value,
            filter: '',
        }
    },
    computed: {
        /**
         * Returns the filtered options based on the input value.
         * @returns {Array} The filtered options.
         */
        filteredOptions() {
            let f = this.options.filter(option =>
                option[this.optionLabel].toLowerCase().includes(this.filter.toLowerCase())
            );
            if (f.length == 0) {
                return this.options;
            } else {
                return f;
            }
        },
    },
    methods: {
        /**
         * Starts the editing process.
         */
        async startEditing() {
            if (this.searchable) {
                this.$nextTick(() => {
                    this.filter = "";
                    this.$refs.inputFilter.focus();
                });
            }
        },
        async focus() {
            this.$refs.select.focus();
        },
    },
}
</script>
<style scoped>
input:focus {
    outline: none;
}
</style>