mirror of
https://github.com/BookStackApp/BookStack.git
synced 2025-01-10 11:07:36 +00:00
5f07f31c9f
Adds dynamic and fixed (out of DOM order) positioning with location adjustment depending on space. Also adds smarter hiding to prevent disappearing when mouse leaves but within the same space as the toggle.
90 lines
No EOL
3 KiB
TypeScript
90 lines
No EOL
3 KiB
TypeScript
interface HandleDropdownParams {
|
|
toggle: HTMLElement;
|
|
menu: HTMLElement;
|
|
showOnHover?: boolean,
|
|
onOpen?: Function | undefined;
|
|
onClose?: Function | undefined;
|
|
showAside?: boolean;
|
|
}
|
|
|
|
function positionMenu(menu: HTMLElement, toggle: HTMLElement, showAside: boolean) {
|
|
const toggleRect = toggle.getBoundingClientRect();
|
|
const menuBounds = menu.getBoundingClientRect();
|
|
|
|
menu.style.position = 'fixed';
|
|
|
|
if (showAside) {
|
|
let targetLeft = toggleRect.right;
|
|
const isRightOOB = toggleRect.right + menuBounds.width > window.innerWidth;
|
|
if (isRightOOB) {
|
|
targetLeft = Math.max(toggleRect.left - menuBounds.width, 0);
|
|
}
|
|
|
|
menu.style.top = toggleRect.top + 'px';
|
|
menu.style.left = targetLeft + 'px';
|
|
} else {
|
|
const isRightOOB = toggleRect.left + menuBounds.width > window.innerWidth;
|
|
let targetLeft = toggleRect.left;
|
|
if (isRightOOB) {
|
|
targetLeft = Math.max(toggleRect.right - menuBounds.width, 0);
|
|
}
|
|
|
|
menu.style.top = toggleRect.bottom + 'px';
|
|
menu.style.left = targetLeft + 'px';
|
|
}
|
|
}
|
|
|
|
export function handleDropdown(options: HandleDropdownParams) {
|
|
const {menu, toggle, onClose, onOpen, showOnHover, showAside} = options;
|
|
let clickListener: Function|null = null;
|
|
|
|
const hide = () => {
|
|
menu.hidden = true;
|
|
menu.style.removeProperty('position');
|
|
menu.style.removeProperty('left');
|
|
menu.style.removeProperty('top');
|
|
if (clickListener) {
|
|
window.removeEventListener('click', clickListener as EventListener);
|
|
}
|
|
if (onClose) {
|
|
onClose();
|
|
}
|
|
};
|
|
|
|
const show = () => {
|
|
menu.hidden = false
|
|
positionMenu(menu, toggle, Boolean(showAside));
|
|
clickListener = (event: MouseEvent) => {
|
|
if (!toggle.contains(event.target as HTMLElement) && !menu.contains(event.target as HTMLElement)) {
|
|
hide();
|
|
}
|
|
}
|
|
window.addEventListener('click', clickListener as EventListener);
|
|
if (onOpen) {
|
|
onOpen();
|
|
}
|
|
};
|
|
|
|
const toggleShowing = (event: MouseEvent) => {
|
|
menu.hasAttribute('hidden') ? show() : hide();
|
|
};
|
|
toggle.addEventListener('click', toggleShowing);
|
|
if (showOnHover) {
|
|
toggle.addEventListener('mouseenter', toggleShowing);
|
|
}
|
|
|
|
menu.parentElement?.addEventListener('mouseleave', (event: MouseEvent) => {
|
|
|
|
// Prevent mouseleave hiding if withing the same bounds of the toggle.
|
|
// Avoids hiding in the event the mouse is interrupted by a high z-index
|
|
// item like a browser scrollbar.
|
|
const toggleBounds = toggle.getBoundingClientRect();
|
|
const withinX = event.clientX <= toggleBounds.right && event.clientX >= toggleBounds.left;
|
|
const withinY = event.clientY <= toggleBounds.bottom && event.clientY >= toggleBounds.top;
|
|
const withinToggle = withinX && withinY;
|
|
|
|
if (!withinToggle) {
|
|
hide();
|
|
}
|
|
});
|
|
} |