85 lines
1.7 KiB
Vue
85 lines
1.7 KiB
Vue
<script setup>
|
|
import { provide, ref, watch, onBeforeUnmount } from 'vue';
|
|
|
|
defineOptions({ name: 'HoverCard' });
|
|
|
|
const isOpen = ref(false);
|
|
const position = ref({ x: 0, y: 0 });
|
|
const triggerRef = ref(null);
|
|
const isHoveringTrigger = ref(false);
|
|
const isHoveringContent = ref(false);
|
|
const originalOverflow = ref('');
|
|
|
|
const lockScroll = () => {
|
|
originalOverflow.value = document.body.style.overflow;
|
|
document.body.style.overflow = 'hidden';
|
|
};
|
|
|
|
const unlockScroll = () => {
|
|
document.body.style.overflow = originalOverflow.value;
|
|
};
|
|
|
|
// Watch for changes in isOpen to handle scroll locking
|
|
watch(isOpen, (newValue) => {
|
|
if (newValue) {
|
|
lockScroll();
|
|
} else {
|
|
unlockScroll();
|
|
}
|
|
});
|
|
|
|
const show = () => {
|
|
if (triggerRef.value) {
|
|
const rect = triggerRef.value.getBoundingClientRect();
|
|
position.value = {
|
|
x: rect.left,
|
|
y: rect.bottom + 4,
|
|
};
|
|
isOpen.value = true;
|
|
}
|
|
};
|
|
|
|
const hide = () => {
|
|
// Only hide if neither trigger nor content is being hovered
|
|
if (!isHoveringTrigger.value && !isHoveringContent.value) {
|
|
isOpen.value = false;
|
|
}
|
|
};
|
|
|
|
const setIsHoveringTrigger = (value) => {
|
|
isHoveringTrigger.value = value;
|
|
if (!value && !isHoveringContent.value) {
|
|
hide();
|
|
}
|
|
};
|
|
|
|
const setIsHoveringContent = (value) => {
|
|
isHoveringContent.value = value;
|
|
if (!value && !isHoveringTrigger.value) {
|
|
hide();
|
|
}
|
|
};
|
|
|
|
provide('hover-card', {
|
|
isOpen,
|
|
position,
|
|
triggerRef,
|
|
show,
|
|
hide,
|
|
setIsHoveringContent,
|
|
setIsHoveringTrigger,
|
|
});
|
|
|
|
// Cleanup on component destruction
|
|
onBeforeUnmount(() => {
|
|
if (isOpen.value) {
|
|
unlockScroll();
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<div class="relative inline-block">
|
|
<slot />
|
|
</div>
|
|
</template> |