koel/resources/assets/js/components/ui/context-menu.vue
2022-04-19 21:26:06 +02:00

93 lines
2.1 KiB
Vue

<template>
<nav
class="menu context-menu"
:class="extraClass"
:style="{ top: `${top}px`, left: `${left}px` }"
@contextmenu.prevent
tabindex="0"
v-koel-focus
v-koel-clickaway="close"
@keydown.esc="close"
v-if="shown"
ref="el"
>
<ul>
<slot>Menu items go here.</slot>
</ul>
</nav>
</template>
<script lang="ts" setup>
import { nextTick, ref, toRefs } from 'vue'
const props = defineProps<{ extraClass: string }>()
const { extraClass } = toRefs(props)
const el = ref<HTMLElement>()
const shown = ref(false)
const top = ref(0)
const left = ref(0)
const preventOffScreen = async (element: HTMLElement, isSubmenu = false) => {
const { bottom, right } = element.getBoundingClientRect()
if (bottom > window.innerHeight) {
element.style.top = 'auto'
element.style.bottom = '0'
} else {
element.style.bottom = 'auto'
}
if (right > window.innerWidth) {
element.style.right = isSubmenu ? `${el.value?.getBoundingClientRect().width}px` : '0'
element.style.left = 'auto'
} else {
element.style.right = 'auto'
}
}
const initSubmenus = () => {
Array.from(el.value?.querySelectorAll('.has-sub') as NodeListOf<HTMLElement>).forEach(item => {
const submenu = item.querySelector<HTMLElement>('.submenu')
if (!submenu) {
return
}
item.addEventListener('mouseenter', async () => {
submenu.style.display = 'block'
await nextTick()
await preventOffScreen(submenu, true)
})
item.addEventListener('mouseleave', () => {
submenu.style.top = '0'
submenu.style.bottom = 'auto'
submenu.style.display = 'none'
})
})
}
const open = async (_top = 0, _left = 0) => {
top.value = _top
left.value = _left
shown.value = true
await nextTick()
try {
await preventOffScreen(el.value!)
await initSubmenus()
} catch (e) {
console.error(el.value, e)
// in a non-browser environment (e.g., unit testing), these two functions are broken due to calls to
// getBoundingClientRect() and querySelectorAll
}
}
const close = () => {
shown.value = false
}
defineExpose({ open, close, shown })
</script>