]> BookStack Code Mirror - bookstack/commitdiff
Lexical: Added mobile toolbar support
authorDan Brown <redacted>
Sun, 15 Dec 2024 14:03:08 +0000 (14:03 +0000)
committerDan Brown <redacted>
Sun, 15 Dec 2024 14:03:08 +0000 (14:03 +0000)
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.

resources/js/wysiwyg/ui/framework/blocks/button-with-menu.ts
resources/js/wysiwyg/ui/framework/blocks/dropdown-button.ts
resources/js/wysiwyg/ui/framework/helpers/dropdowns.ts
resources/js/wysiwyg/ui/toolbars.ts
resources/sass/_editor.scss
resources/sass/_pages.scss

index 30dd237f60f8b470d8ea6a39a08cbcbe31f0330e..2aec7c3352e2b4b07f93e701614c80106fd03cc4 100644 (file)
@@ -16,6 +16,7 @@ export class EditorButtonWithMenu extends EditorContainerUiElement {
             button: {label: 'Menu', icon: caretDownIcon},
             showOnHover: false,
             direction: 'vertical',
+            showAside: false,
         }, menuItems);
         this.addChildren(this.dropdownButton);
     }
index cba141f6c77a0777eb2dc10b1517b63206930a49..d7f02d5732b96e7d167f40249dd2eb8314d5fb9d 100644 (file)
@@ -7,12 +7,14 @@ import {EditorMenuButton} from "./menu-button";
 export type EditorDropdownButtonOptions = {
     showOnHover?: boolean;
     direction?: 'vertical'|'horizontal';
+    showAside?: boolean;
     button: EditorBasicButtonDefinition|EditorButton;
 };
 
 const defaultOptions: EditorDropdownButtonOptions = {
     showOnHover: false,
     direction: 'horizontal',
+    showAside: undefined,
     button: {label: 'Menu'},
 }
 
@@ -65,6 +67,7 @@ export class EditorDropdownButton extends EditorContainerUiElement {
 
         handleDropdown({toggle: button, menu : menu,
             showOnHover: this.options.showOnHover,
+            showAside: typeof this.options.showAside === 'boolean' ? this.options.showAside : (this.options.direction === 'vertical'),
             onOpen : () => {
             this.open = true;
             this.getContext().manager.triggerStateUpdateForElement(this.button);
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
index 35146e5a440aecca04097a3477c01f57e19f3ea4..886e1394b20c447b9bfa0b4432004d45e6442bc4 100644 (file)
@@ -149,8 +149,8 @@ export function getMainEditorFullToolbar(context: EditorUiContext): EditorContai
         new EditorOverflowContainer(4, [
             new EditorButton(link),
 
-            new EditorDropdownButton({button: table, direction: 'vertical'}, [
-                new EditorDropdownButton({button: {label: 'Insert', format: 'long'}, showOnHover: true}, [
+            new EditorDropdownButton({button: table, direction: 'vertical', showAside: false}, [
+                new EditorDropdownButton({button: {label: 'Insert', format: 'long'}, showOnHover: true, showAside: true}, [
                     new EditorTableCreator(),
                 ]),
                 new EditorSeparator(),
index bdf6ea44c3db84a6054e739a941b798a50f7becd..e481318374c0bee4601359ae32509493b6ca8f80 100644 (file)
   @include mixins.lightDark(border-color, #DDD, #000);
 }
 
+@include mixins.smaller-than(vars.$bp-xl) {
+  .editor-toolbar-main {
+    overflow-x: scroll;
+    flex-wrap: nowrap;
+    justify-content: start;
+  }
+}
+
 body.editor-is-fullscreen {
   overflow: hidden;
   .edit-area {
index 17bcfcfbf119abb4b076e7b3a866a12c1b741e97..45e58ffc865030098c45067e01a876778d896a99 100755 (executable)
@@ -26,6 +26,7 @@
   width: 100%;
   border-radius: 8px;
   box-shadow: vars.$bs-card;
+  min-width: 300px;
   @include mixins.lightDark(background-color, #FFF, #333)
 }