116 lines
2.7 KiB
Vue
116 lines
2.7 KiB
Vue
<template>
|
|
<q-select
|
|
ref="selectRef"
|
|
v-model="modelValueLocal"
|
|
:options="filteredOptions"
|
|
:option-label="optionLabel"
|
|
:option-value="optionValue"
|
|
:use-input="search"
|
|
input-debounce="200"
|
|
@filter="filterFn"
|
|
@update:model-value="emitValue"
|
|
v-bind="$attrs"
|
|
><template v-slot:append
|
|
><q-btn
|
|
size="xs"
|
|
flat
|
|
dense
|
|
round
|
|
icon="search"
|
|
:color="search ? 'primary' : 'grey'"
|
|
@click="searchBox"
|
|
>
|
|
</q-btn
|
|
></template>
|
|
</q-select>
|
|
</template>
|
|
|
|
<script setup lang="ts" generic="T extends Record<string, string | number | object>">
|
|
import type { PropType } from 'vue';
|
|
import { ref, watch } from 'vue';
|
|
|
|
const search = ref(false);
|
|
const selectRef = ref();
|
|
|
|
defineOptions({ inheritAttrs: false });
|
|
|
|
const props = defineProps({
|
|
modelValue: {
|
|
type: [String, Number, Object] as PropType<string | number | object | null>,
|
|
default: null,
|
|
},
|
|
options: {
|
|
type: Array as PropType<T[]>,
|
|
required: true,
|
|
},
|
|
optionLabel: {
|
|
type: [Function, String] as PropType<((option: T) => string) | string | undefined>,
|
|
required: true,
|
|
},
|
|
optionValue: {
|
|
type: String,
|
|
required: true,
|
|
},
|
|
});
|
|
|
|
const emit = defineEmits<{
|
|
(e: 'update:modelValue', value: string | number | null): void;
|
|
}>();
|
|
|
|
const optionLabel = props.optionLabel;
|
|
const optionValue = props.optionValue;
|
|
|
|
const modelValueLocal = ref<string | number | object | null | undefined>(props.modelValue);
|
|
const filteredOptions = ref<T[]>([...(props.options || [])]);
|
|
|
|
function searchBox() {
|
|
if (search.value) {
|
|
selectRef.value.updateInputValue('');
|
|
}
|
|
search.value = !search.value;
|
|
}
|
|
|
|
watch(
|
|
() => props.modelValue,
|
|
(v) => (modelValueLocal.value = v),
|
|
);
|
|
watch(
|
|
() => props.options,
|
|
(o) => (filteredOptions.value = [...o]),
|
|
);
|
|
|
|
function filterFn(val: string, update: (fn: () => void) => void) {
|
|
update(() => {
|
|
if (!val) {
|
|
filteredOptions.value = [...props.options];
|
|
return;
|
|
}
|
|
const needle = val.toLowerCase();
|
|
filteredOptions.value = props.options.filter((opt) => {
|
|
let field = null;
|
|
if (typeof optionLabel === 'function') {
|
|
field = optionLabel(opt);
|
|
} else {
|
|
field = opt[String(optionLabel)];
|
|
}
|
|
|
|
if (typeof field !== 'string' && typeof field !== 'number') return false;
|
|
return String(field).toLowerCase().includes(needle);
|
|
});
|
|
|
|
// Remove duplicates by optionValue
|
|
const seen = new Set();
|
|
filteredOptions.value = filteredOptions.value.filter((opt) => {
|
|
const value = opt[optionValue];
|
|
if (seen.has(value)) return false;
|
|
seen.add(value);
|
|
return true;
|
|
});
|
|
});
|
|
}
|
|
|
|
function emitValue(val: string | number | null) {
|
|
emit('update:modelValue', val);
|
|
}
|
|
</script>
|