74 lines
1.4 KiB
Vue
74 lines
1.4 KiB
Vue
<script setup>
|
|
import { provide, ref, watch } from 'vue';
|
|
|
|
defineOptions({ name: 'Popover' });
|
|
|
|
const isOpen = ref(false);
|
|
const position = ref({ x: 0, y: 0 });
|
|
const triggerRef = ref(null);
|
|
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 toggle = () => {
|
|
isOpen.value = !isOpen.value;
|
|
if (isOpen.value && triggerRef.value) {
|
|
const rect = triggerRef.value.getBoundingClientRect();
|
|
position.value = {
|
|
x: rect.left,
|
|
y: rect.bottom + 4,
|
|
};
|
|
}
|
|
};
|
|
|
|
const close = () => {
|
|
isOpen.value = false;
|
|
};
|
|
|
|
provide('popover', {
|
|
isOpen,
|
|
position,
|
|
triggerRef,
|
|
toggle,
|
|
close,
|
|
});
|
|
|
|
onMounted(() => {
|
|
document.addEventListener('click', (event) => {
|
|
const target = event.target;
|
|
if (!triggerRef.value?.contains(target) && !event.target.closest('.popover-content')) {
|
|
close();
|
|
}
|
|
});
|
|
});
|
|
|
|
onBeforeUnmount(() => {
|
|
document.removeEventListener('click', close);
|
|
// Ensure we unlock scroll when component is destroyed
|
|
if (isOpen.value) {
|
|
unlockScroll();
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<div class="relative inline-block">
|
|
<slot />
|
|
</div>
|
|
</template> |