Lexical: Added mobile toolbar support

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.
This commit is contained in:
Dan Brown 2024-12-15 14:03:08 +00:00
parent a71aa241ad
commit 5f07f31c9f
No known key found for this signature in database
GPG Key ID: 46D9F943C24A2EF9
6 changed files with 62 additions and 7 deletions

View 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);
}

View 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);

View 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();
}
});
}

View 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(),

View File

@ -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 {

View File

@ -26,6 +26,7 @@
width: 100%;
border-radius: 8px;
box-shadow: vars.$bs-card;
min-width: 300px;
@include mixins.lightDark(background-color, #FFF, #333)
}