Created
March 30, 2025 10:57
-
-
Save Smileek/f7c8ee21b42578eea3ec95be55881615 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { ComputedRef, Ref, ref, watch } from 'vue'; | |
export const useDropdownAttributes = () => { | |
const dropdownWidth = ref(''); | |
const dropdownTop = ref(''); | |
const dropdownBottom = ref(''); | |
const dropdownLeft = ref(''); | |
const isDirectedUpwards = ref(false); | |
const togglerRect = ref<DOMRect>(); | |
const dropdownRect = ref<DOMRect>(); | |
const PADDING_ALLOWANCE = 8; | |
const getFirstScrollableParent = (element: HTMLElement | null): HTMLElement => { | |
const parentElement = element?.parentElement; | |
if (!parentElement) return document.body; | |
const overflowY = window.getComputedStyle(parentElement).overflowY; | |
if (overflowY === 'scroll' || overflowY === 'auto') return parentElement; | |
return getFirstScrollableParent(parentElement); | |
}; | |
const autodetectPosition = ( | |
isDropdownDisplayed: Ref<boolean>, | |
togglerElement: HTMLElement | null = null, | |
dropdownElement: HTMLElement | null = null, | |
dropdownContent: Ref<unknown> | ComputedRef<unknown> = ref([]), | |
isUpwardPreferred = false, | |
) => { | |
if (!togglerElement || !dropdownElement) return; | |
const updateDropdownAttributes = () => { | |
togglerRect.value = togglerElement.getBoundingClientRect(); | |
dropdownRect.value = dropdownElement.getBoundingClientRect(); | |
dropdownWidth.value = `${togglerRect.value.width}px`; | |
dropdownBottom.value = `${window.innerHeight - togglerRect.value.top}px`; | |
dropdownTop.value = `${ | |
window.innerHeight - togglerRect.value.bottom - dropdownRect.value.height | |
}px`; | |
dropdownLeft.value = `${togglerRect.value.left}px`; | |
}; | |
const handleResize = () => { | |
requestAnimationFrame(updateDropdownAttributes); | |
}; | |
const handleScroll = () => { | |
requestAnimationFrame(updateDropdownAttributes); | |
}; | |
watch( | |
[isDropdownDisplayed, dropdownContent], | |
([newVal, _]) => { | |
const scrollableParent = getFirstScrollableParent(togglerElement); | |
if (!newVal) { | |
window.removeEventListener('resize', handleResize); | |
window.removeEventListener('scroll', handleScroll); | |
scrollableParent.removeEventListener('resize', handleResize); | |
scrollableParent.removeEventListener('scroll', handleScroll); | |
return; | |
} | |
requestAnimationFrame(() => { | |
const distanceFromBottom = | |
window.innerHeight - togglerElement.getBoundingClientRect().bottom; | |
const distanceFromTop = togglerElement.getBoundingClientRect().top; | |
const dropdownHeight = dropdownElement.offsetHeight; | |
isDirectedUpwards.value = isUpwardPreferred | |
? distanceFromTop > dropdownHeight + PADDING_ALLOWANCE | |
: distanceFromBottom < dropdownHeight + PADDING_ALLOWANCE && | |
distanceFromTop > dropdownHeight + PADDING_ALLOWANCE; | |
updateDropdownAttributes(); | |
window.addEventListener('resize', handleResize); | |
window.addEventListener('scroll', handleScroll); | |
scrollableParent.addEventListener('resize', handleResize); | |
scrollableParent.addEventListener('scroll', handleScroll); | |
}); | |
}, | |
{ deep: true }, | |
); | |
}; | |
return { | |
autodetectPosition, | |
dropdownTop, | |
dropdownBottom, | |
dropdownLeft, | |
dropdownWidth, | |
isDirectedUpwards, | |
togglerRect, | |
dropdownRect, | |
}; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment