Skip to content

Instantly share code, notes, and snippets.

@Smileek
Created March 30, 2025 10:57
Show Gist options
  • Save Smileek/f7c8ee21b42578eea3ec95be55881615 to your computer and use it in GitHub Desktop.
Save Smileek/f7c8ee21b42578eea3ec95be55881615 to your computer and use it in GitHub Desktop.
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