64 lines
2.0 KiB
JavaScript
64 lines
2.0 KiB
JavaScript
import { computed } from 'vue';
|
|
import { useElementBounding, useWindowSize } from '@vueuse/core';
|
|
|
|
/**
|
|
* Auto-position dropdown based on available space
|
|
* @param {Ref} triggerRef - Trigger element ref
|
|
* @param {Ref} dropdownRef - Dropdown element ref
|
|
* @param {Ref} enabled - Whether to calculate position
|
|
* @returns {Object} { positionClasses, updatePosition }
|
|
*/
|
|
export function useDropdownPosition(triggerRef, dropdownRef, enabled) {
|
|
const SAFE_MARGIN = 16;
|
|
const RTL = 'rtl';
|
|
|
|
const triggerBounds = useElementBounding(triggerRef);
|
|
const dropdownBounds = useElementBounding(dropdownRef);
|
|
const { height: windowHeight, width: windowWidth } = useWindowSize();
|
|
|
|
const positionClasses = computed(() => {
|
|
// Don't calculate if not enabled
|
|
if (!enabled?.value) {
|
|
return 'top-full mt-2 ltr:left-0 rtl:right-0';
|
|
}
|
|
|
|
if (!triggerRef.value || !dropdownRef.value) {
|
|
return 'top-full mt-2 ltr:left-0 rtl:right-0';
|
|
}
|
|
|
|
const spaceBelow = windowHeight.value - triggerBounds.bottom.value;
|
|
const dropdownHeight = dropdownBounds.height.value || 200;
|
|
const dropdownWidth = dropdownBounds.width.value || 200;
|
|
|
|
// Check if RTL by checking app div with dir attribute
|
|
const appDiv = document.querySelector('#app[dir]');
|
|
const isRTL = appDiv?.getAttribute('dir') === RTL;
|
|
|
|
// Use appropriate edge based on text direction
|
|
const triggerEdge = isRTL
|
|
? triggerBounds.right.value
|
|
: triggerBounds.left.value;
|
|
const wouldOverflow = isRTL
|
|
? triggerEdge - dropdownWidth - SAFE_MARGIN < 0
|
|
: triggerEdge + dropdownWidth + SAFE_MARGIN > windowWidth.value;
|
|
|
|
const vertical =
|
|
spaceBelow >= dropdownHeight + SAFE_MARGIN
|
|
? 'top-full mt-2'
|
|
: 'bottom-full mb-2';
|
|
|
|
const horizontal = wouldOverflow
|
|
? 'ltr:right-0 rtl:left-0'
|
|
: 'ltr:left-0 rtl:right-0';
|
|
|
|
return `${vertical} ${horizontal}`;
|
|
});
|
|
|
|
const updatePosition = () => {
|
|
triggerBounds.update();
|
|
dropdownBounds.update();
|
|
};
|
|
|
|
return { positionClasses, updatePosition };
|
|
}
|