Files
arne-drums/src/ui/Widgets/Dropdown/Dropdown.vue
Daniel Ledda adbc1b8dd3 update
2024-06-02 17:37:58 +02:00

85 lines
2.5 KiB
Vue

<template>
<div
class="trigger"
ref="trigger"
@mouseenter="onMouseEnter"
@touchstart="onMouseEnter"
@mouseleave="onMouseLeave">
<slot />
</div>
<Teleport to="#dropdowns">
<div
ref="dropdown"
class="dropdown"
:class="{ visible }"
@mouseleave="visible = false">
<slot name="content" />
</div>
</Teleport>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
const trigger = ref<HTMLDivElement>();
const dropdown = ref<HTMLDivElement>();
const visible = ref(false);
const top = ref('0px');
const left = ref('0px');
const effectiveTop = computed(() => visible.value ? top.value : 0);
const effectiveLeft = computed(() => visible.value ? left.value : 0);
function onMouseEnter(e: MouseEvent | TouchEvent) {
visible.value = true;
if (trigger.value && dropdown.value) {
const rectTrigger = trigger.value.getBoundingClientRect();
const rectDropdown = dropdown.value.getBoundingClientRect();
console.log(rectDropdown);
top.value = Math.min(window.innerHeight - rectDropdown.height, rectTrigger.top) + 'px';
left.value = rectTrigger.width + rectTrigger.left + 'px';
}
window.addEventListener('touchstart', onWindowClick);
window.addEventListener('click', onWindowClick);
}
function outside(e: MouseEvent, el: HTMLElement) {
const rect = el.getBoundingClientRect();
return e.clientX < rect.x
|| e.clientX > rect.x + rect.width
|| e.clientY < rect.y
|| e.clientY > rect.y + rect.height;
}
function onWindowClick(e: MouseEvent | TouchEvent) {
if (visible.value && e instanceof MouseEvent && dropdown.value && !outside(e, dropdown.value)) {
visible.value = false;
window.removeEventListener('touchstart', onWindowClick);
window.removeEventListener('click', onWindowClick);
}
}
function onMouseLeave(e: MouseEvent) {
if (trigger.value && outside(e, trigger.value) && dropdown.value && outside(e, dropdown.value)) {
visible.value = false;
}
}
</script>
<style scoped lang="scss">
.dropdown {
position: absolute;
top: v-bind(effectiveTop);
left: v-bind(effectiveLeft);
visibility: hidden;
&.visible {
visibility: visible;
}
&:hover {
visibility: visible;
}
}
</style>