mirror of
https://gitlab.com/PronounsPage/PronounsPage.git
synced 2025-08-05 03:57:03 -04:00
121 lines
3.0 KiB
Vue
121 lines
3.0 KiB
Vue
<script setup lang="ts">
|
|
defineOptions({
|
|
inheritAttrs: false,
|
|
});
|
|
|
|
const props = defineProps<{
|
|
options: Record<string, string>;
|
|
freeform?: boolean;
|
|
}>();
|
|
|
|
const modelValue = defineModel<string | null>({ required: true });
|
|
|
|
const filter = ref((modelValue.value !== null ? props.options[modelValue.value] : null) ?? modelValue.value);
|
|
const shown = ref(false);
|
|
const highlighted = ref(-1);
|
|
|
|
const visibleOptions = computed((): Record<string, string> => {
|
|
return Object.fromEntries(Object.entries(props.options).filter(([option, display]) => {
|
|
return !filter.value ||
|
|
option.toLowerCase().includes(filter.value.toLowerCase()) ||
|
|
display.toLowerCase().includes(filter.value.toLowerCase());
|
|
}));
|
|
});
|
|
|
|
const highlightedOption = computed(() => {
|
|
return Object.keys(visibleOptions.value)[highlighted.value];
|
|
});
|
|
|
|
watch(modelValue, () => {
|
|
filter.value = (modelValue.value !== null ? props.options[modelValue.value] : null) ?? modelValue.value;
|
|
highlighted.value = -1;
|
|
});
|
|
|
|
watch(filter, () => {
|
|
if (props.freeform) {
|
|
modelValue.value = filter.value;
|
|
}
|
|
});
|
|
|
|
const select = (option: string) => {
|
|
modelValue.value = option;
|
|
hide();
|
|
highlighted.value = -1;
|
|
};
|
|
const show = () => {
|
|
shown.value = true;
|
|
};
|
|
const hide = () => {
|
|
setTimeout(() => {
|
|
shown.value = false;
|
|
}, 100);
|
|
};
|
|
const filterKeydown = (event: KeyboardEvent) => {
|
|
if (!shown.value) {
|
|
show();
|
|
}
|
|
|
|
switch (event.key) {
|
|
case 'ArrowUp':
|
|
highlighted.value--;
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
break;
|
|
case 'ArrowDown':
|
|
highlighted.value++;
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
break;
|
|
case 'Enter':
|
|
if (highlightedOption.value) {
|
|
select(highlightedOption.value);
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<template>
|
|
<div class="select flex-grow-1">
|
|
<input
|
|
v-model="filter"
|
|
type="text"
|
|
class="form-control"
|
|
autocomplete="off"
|
|
v-bind="$attrs"
|
|
@focus="show"
|
|
@blur="hide"
|
|
@keydown="filterKeydown"
|
|
>
|
|
<div v-show="shown" class="list-group shadow">
|
|
<button
|
|
v-for="(display, option) in visibleOptions"
|
|
:key="option"
|
|
type="button"
|
|
:class="['list-group-item', 'list-group-item-action', highlightedOption === option ? 'active' : '']"
|
|
@click.prevent="select(option)"
|
|
>
|
|
{{ display }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<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>
|