Files
modoboa/frontend/src/components/tools/EmailField.vue
T

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>