mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-21 06:18:45 +02:00
184 lines
4.2 KiB
Vue
184 lines
4.2 KiB
Vue
<template>
|
|
<v-text-field
|
|
ref="inputRef"
|
|
v-model="input"
|
|
variant="outlined"
|
|
v-bind="$attrs"
|
|
:error-messages="errors"
|
|
:error="errors.length > 0"
|
|
density="compact"
|
|
autocomplete="new-password"
|
|
@keydown="onKeyDown"
|
|
/>
|
|
<div v-if="showMenu" class="menu" :style="`width: ${width}px;`">
|
|
<v-list class="list">
|
|
<v-list-item
|
|
v-for="(domain, index) in filteredDomains"
|
|
:key="index"
|
|
:class="selectionIndex === index ? 'bg-primary-lighten-1' : ''"
|
|
@click="selectDomain(domain)"
|
|
@mouseenter="selectionIndex = index"
|
|
>
|
|
<v-list-item-title>{{ domain.name }}</v-list-item-title>
|
|
</v-list-item>
|
|
</v-list>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, computed, onMounted, onUnmounted, onUpdated, nextTick } from 'vue'
|
|
import { useDomainsStore } from '@/stores'
|
|
import constants from '@/constants.json'
|
|
|
|
const domainsStore = useDomainsStore()
|
|
const props = defineProps({
|
|
modelValue: { type: String, default: '' },
|
|
allowAdd: { type: Boolean, default: false },
|
|
role: { type: String, default: constants.USER },
|
|
errorMsg: { type: Array, default: () => [] },
|
|
})
|
|
const emit = defineEmits(['domain-selected', 'update:model-value'])
|
|
|
|
const input = computed({
|
|
get() {
|
|
return props.modelValue
|
|
},
|
|
set(value) {
|
|
errors.value = []
|
|
if (value.indexOf('@') !== -1) {
|
|
domainSearch.value = value.split('@')[1]
|
|
if (props.allowAdd) {
|
|
showMenu.value = filteredDomains.value.length > 0
|
|
} else {
|
|
showMenu.value = true
|
|
}
|
|
} else {
|
|
showMenu.value = false
|
|
}
|
|
emit('update:model-value', value)
|
|
},
|
|
})
|
|
|
|
const filteredDomains = computed(() => {
|
|
return Object.values(domainsStore.domains).filter((domain) =>
|
|
domain.name.startsWith(domainSearch.value)
|
|
)
|
|
})
|
|
|
|
const errors = computed({
|
|
get() {
|
|
return [...localErrors.value, ...props.errorMsg]
|
|
},
|
|
set(value) {
|
|
localErrors.value = value
|
|
},
|
|
})
|
|
|
|
const domainSearch = ref('')
|
|
const selectionIndex = ref(0)
|
|
const showMenu = ref(false)
|
|
const width = ref(500)
|
|
const inputRef = ref()
|
|
const localErrors = ref([])
|
|
|
|
function onKeyDown(e) {
|
|
const keyCode = e.keyCode
|
|
if (keyCode === 40 || keyCode === 34) {
|
|
// on arrow down or page down
|
|
if (!showMenu.value) {
|
|
domainSearch.value = ''
|
|
showMenu.value = true
|
|
} else {
|
|
increaseSelectionIndex()
|
|
}
|
|
e.preventDefault()
|
|
} else if (keyCode === 38 || keyCode === 33) {
|
|
// on arrow up or page up
|
|
if (!showMenu.value) {
|
|
domainSearch.value = ''
|
|
showMenu.value = true
|
|
} else {
|
|
decreaseSelectionIndex()
|
|
}
|
|
e.preventDefault()
|
|
} else if (keyCode === 13 || keyCode === 9) {
|
|
// on enter or tab
|
|
if (filteredDomains.value.length > 0) {
|
|
selectDomain(filteredDomains.value[selectionIndex.value])
|
|
} else if (props.allowAdd) {
|
|
selectDomain()
|
|
}
|
|
e.preventDefault()
|
|
} else if (keyCode === 27) {
|
|
// on escape
|
|
if (filteredDomains.value.length > 0) {
|
|
showMenu.value = false
|
|
e.preventDefault()
|
|
}
|
|
}
|
|
}
|
|
|
|
function increaseSelectionIndex() {
|
|
if (selectionIndex.value >= filteredDomains.value.length - 1) {
|
|
selectionIndex.value = 0
|
|
} else {
|
|
selectionIndex.value += 1
|
|
}
|
|
}
|
|
|
|
function decreaseSelectionIndex() {
|
|
if (selectionIndex.value <= 0) {
|
|
selectionIndex.value = filteredDomains.value.length - 1
|
|
} else {
|
|
selectionIndex.value -= 1
|
|
}
|
|
}
|
|
|
|
function selectDomain(domain) {
|
|
if (domain !== undefined) {
|
|
input.value = input.value.split('@')[0] + '@' + domain.name
|
|
}
|
|
showMenu.value = false
|
|
emit('domain-selected')
|
|
}
|
|
|
|
function updateMinWidthProperty() {
|
|
width.value = inputRef.value.$el.clientWidth
|
|
}
|
|
|
|
onMounted(() => {
|
|
domainsStore.getDomains()
|
|
window.addEventListener('resize', updateMinWidthProperty)
|
|
})
|
|
onUnmounted(() => {
|
|
window.removeEventListener('resize', updateMinWidthProperty)
|
|
})
|
|
onUpdated(() => {
|
|
nextTick(() => {
|
|
updateMinWidthProperty()
|
|
})
|
|
})
|
|
</script>
|
|
|
|
<style scoped>
|
|
.menu {
|
|
display: inline-block;
|
|
position: fixed;
|
|
margin-top: -20px;
|
|
padding: 5px 0;
|
|
box-shadow: 0 1px 5px 1px rgba(0, 0, 0, 0.3);
|
|
overflow-y: auto;
|
|
overflow-x: hidden;
|
|
contain: content;
|
|
z-index: 99;
|
|
}
|
|
|
|
.list {
|
|
width: 100%;
|
|
height: 100%;
|
|
font-size: 1.1em;
|
|
overflow-x: hidden;
|
|
padding: 0;
|
|
}
|
|
</style>
|