85 lines
2.5 KiB
Vue
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>
|