]> BookStack Code Mirror - bookstack/blobdiff - resources/js/wysiwyg/ui/framework/helpers/dropdowns.ts
Lexical: Added mobile toolbar support
[bookstack] / resources / js / wysiwyg / ui / framework / helpers / dropdowns.ts
index e8cef3c8d2014e3fdab9d51f64b9c31a6f0a3a83..ccced68586748a1dfdc755e72e2783c190b30c95 100644 (file)
@@ -1,20 +1,48 @@
-
-
-
 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} = options;
+    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);
         }
@@ -25,6 +53,7 @@ export function handleDropdown(options: HandleDropdownParams) {
 
     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();
@@ -44,5 +73,18 @@ export function handleDropdown(options: HandleDropdownParams) {
         toggle.addEventListener('mouseenter', toggleShowing);
     }
 
-    menu.parentElement?.addEventListener('mouseleave', hide);
+    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();
+        }
+    });
 }
\ No newline at end of file