diff --git a/resources/js/wysiwyg/ui/framework/blocks/button-with-menu.ts b/resources/js/wysiwyg/ui/framework/blocks/button-with-menu.ts index 30dd237f6..2aec7c335 100644 --- a/resources/js/wysiwyg/ui/framework/blocks/button-with-menu.ts +++ b/resources/js/wysiwyg/ui/framework/blocks/button-with-menu.ts @@ -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); } diff --git a/resources/js/wysiwyg/ui/framework/blocks/dropdown-button.ts b/resources/js/wysiwyg/ui/framework/blocks/dropdown-button.ts index cba141f6c..d7f02d573 100644 --- a/resources/js/wysiwyg/ui/framework/blocks/dropdown-button.ts +++ b/resources/js/wysiwyg/ui/framework/blocks/dropdown-button.ts @@ -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); diff --git a/resources/js/wysiwyg/ui/framework/helpers/dropdowns.ts b/resources/js/wysiwyg/ui/framework/helpers/dropdowns.ts index e8cef3c8d..ccced6858 100644 --- a/resources/js/wysiwyg/ui/framework/helpers/dropdowns.ts +++ b/resources/js/wysiwyg/ui/framework/helpers/dropdowns.ts @@ -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 diff --git a/resources/js/wysiwyg/ui/toolbars.ts b/resources/js/wysiwyg/ui/toolbars.ts index 35146e5a4..886e1394b 100644 --- a/resources/js/wysiwyg/ui/toolbars.ts +++ b/resources/js/wysiwyg/ui/toolbars.ts @@ -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(), diff --git a/resources/sass/_editor.scss b/resources/sass/_editor.scss index bdf6ea44c..e48131837 100644 --- a/resources/sass/_editor.scss +++ b/resources/sass/_editor.scss @@ -24,6 +24,14 @@ @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 { diff --git a/resources/sass/_pages.scss b/resources/sass/_pages.scss index 17bcfcfbf..45e58ffc8 100755 --- a/resources/sass/_pages.scss +++ b/resources/sass/_pages.scss @@ -26,6 +26,7 @@ width: 100%; border-radius: 8px; box-shadow: vars.$bs-card; + min-width: 300px; @include mixins.lightDark(background-color, #FFF, #333) }