Files
chatwoot/app/javascript/dashboard/composables/useDropdownPosition.js
T
2026-01-16 01:21:51 +05:30

83 lines
2.5 KiB
JavaScript

import { computed, unref, watch } from 'vue';
import { useElementBounding, useWindowSize } from '@vueuse/core';
const FALLBACK_SIZE = 200;
const RTL_DIRECTION = 'rtl';
/**
* Auto-position dropdown based on available space within container
* Returns reactive position object containing class and style
*
* @param {Ref} triggerRef - Trigger element ref
* @param {Ref} dropdownRef - Dropdown element ref
* @param {Ref} enabled - Whether to calculate position
* @param {Object} options - { container: Ref, margin: Number }
*/
export function useDropdownPosition(
triggerRef,
dropdownRef,
enabled,
{ container = null, margin = 16 } = {}
) {
const trigger = useElementBounding(triggerRef);
const dropdown = useElementBounding(dropdownRef);
const bounds = useElementBounding(container);
const { width: winWidth, height: winHeight } = useWindowSize();
const isRTL = computed(
() =>
document.querySelector('#app[dir]')?.getAttribute('dir') === RTL_DIRECTION
);
const position = computed(() => {
// Default fallback if not enabled or refs aren't ready
if (!unref(enabled)) {
return { class: 'top-full mt-2', style: {} };
}
const dropdownHeight = dropdown.height.value || FALLBACK_SIZE;
const dropdownWidth = dropdown.width.value || FALLBACK_SIZE;
/* ---------- Vertical (Tailwind Classes) ---------- */
const spaceBelow = winHeight.value - trigger.bottom.value;
const className =
spaceBelow >= dropdownHeight + margin
? 'top-full mt-2'
: 'bottom-full mb-2';
/* ---------- Horizontal (Inline Styles for precise alignment) ---------- */
const leftBound = container ? bounds.left.value : 0;
const rightBound = container ? bounds.right.value : winWidth.value;
const style = {};
if (isRTL.value) {
const availableLeft = trigger.right.value - leftBound;
const overflow = dropdownWidth - availableLeft;
style.right = overflow > 0 ? `-${overflow}px` : '0px';
} else {
const availableRight = rightBound - trigger.left.value;
const overflow = dropdownWidth - availableRight;
style.left = overflow > 0 ? `-${overflow}px` : '0px';
}
return { class: className, style };
});
const updatePosition = () => {
trigger.update();
dropdown.update();
if (container) bounds.update();
};
// Update position when dropdown opens to ensure RTL state is current
watch(
() => unref(enabled),
isEnabled => {
if (isEnabled) updatePosition();
}
);
return { position, updatePosition };
}