From ce37930cc924eb5a0f632b8e4620cbc25080e059 Mon Sep 17 00:00:00 2001 From: Jeffrey Chen <78434827+TCOTC@users.noreply.github.com> Date: Wed, 8 Oct 2025 03:33:21 +0800 Subject: [PATCH] :art: The menu no longer extends beyond the window fix https://github.com/siyuan-note/siyuan/issues/15400 --- app/src/assets/scss/component/_menu.scss | 2 - app/src/menus/Menu.ts | 66 +++++++++++++++--------- app/src/util/setPosition.ts | 59 +++++++++++---------- 3 files changed, 74 insertions(+), 53 deletions(-) diff --git a/app/src/assets/scss/component/_menu.scss b/app/src/assets/scss/component/_menu.scss index 60de8f4a0..d471b559a 100644 --- a/app/src/assets/scss/component/_menu.scss +++ b/app/src/assets/scss/component/_menu.scss @@ -169,7 +169,6 @@ } &__items { - max-height: 80vh; overflow: auto; padding: 0 8px; } @@ -367,7 +366,6 @@ &__submenu { overflow: auto; display: none; - max-height: 80vh; border: 1px solid var(--b3-theme-surface-lighter); border-radius: var(--b3-border-radius-b); background-color: var(--b3-menu-background); diff --git a/app/src/menus/Menu.ts b/app/src/menus/Menu.ts index 3f6ce4b06..c5bed79af 100644 --- a/app/src/menus/Menu.ts +++ b/app/src/menus/Menu.ts @@ -60,23 +60,46 @@ export class Menu { }); } - public showSubMenu(subMenuElement: HTMLElement) { + public showSubMenu(subMenuElement: HTMLElement | null) { + if (!subMenuElement) { + return; + } + const itemsMenuElement = subMenuElement.lastElementChild as HTMLElement; + if (itemsMenuElement) { + itemsMenuElement.style.maxHeight = ""; + } const itemRect = subMenuElement.parentElement.getBoundingClientRect(); - subMenuElement.style.top = (itemRect.top - 8) + "px"; - subMenuElement.style.left = (itemRect.right + 8) + "px"; - subMenuElement.style.bottom = "auto"; - const rect = subMenuElement.getBoundingClientRect(); - if (rect.right > window.innerWidth) { - if (itemRect.left - 8 > rect.width) { - subMenuElement.style.left = (itemRect.left - 8 - rect.width) + "px"; + const subMenuRect = subMenuElement.getBoundingClientRect(); + + // 垂直方向位置调整 + // 减 9px 是为了尽量对齐菜单选项(b3-menu__submenu 的默认 padding-top 加上子菜单首个 b3-menu__item 的默认 margin-top) + // 减 1px 是为了避免在特定情况下渲染出不应存在的滚动条而做的兼容处理 + const top = Math.min(itemRect.top - 9, window.innerHeight - subMenuRect.height - 1); + subMenuElement.style.top = Math.max(Constants.SIZE_TOOLBAR_HEIGHT, top) + "px"; + + // 水平方向位置调整 + if (subMenuRect.right <= window.innerWidth) { + // 8px 是 b3-menu__items 的默认 padding-right + subMenuElement.style.left = (itemRect.right + 8) + "px"; + } else { + if (itemRect.left - 8 > subMenuRect.width) { + subMenuElement.style.left = (itemRect.left - 8 - subMenuRect.width) + "px"; } else { - subMenuElement.style.left = (window.innerWidth - rect.width) + "px"; + subMenuElement.style.left = (window.innerWidth - subMenuRect.width) + "px"; } } - if (rect.bottom > window.innerHeight) { - subMenuElement.style.top = "auto"; - subMenuElement.style.bottom = "8px"; + + this.updateMaxHeight(subMenuElement, itemsMenuElement); + } + + private updateMaxHeight(menuElement: HTMLElement, itemsMenuElement: HTMLElement) { + if (!menuElement || !itemsMenuElement) { + return; } + const menuRect = menuElement.getBoundingClientRect(); + const itemsMenuRect = itemsMenuElement.getBoundingClientRect(); + const availableHeight = (window.innerHeight - menuRect.top) - (menuRect.height - itemsMenuRect.height); + itemsMenuElement.style.maxHeight = Math.max(availableHeight, 0) + "px"; } private preventDefault(event: KeyboardEvent) { @@ -146,24 +169,19 @@ export class Menu { this.element.style.zIndex = (++window.siyuan.zIndex).toString(); this.element.classList.remove("fn__none"); setPosition(this.element, options.x - (options.isLeft ? this.element.clientWidth : 0), options.y, options.h, options.w); - const menuTop = parseInt(this.element.style.top) || options.y; - const availableHeight = window.innerHeight - menuTop - Constants.SIZE_TOOLBAR_HEIGHT; - (this.element.lastElementChild as HTMLElement).style.maxHeight = Math.max(availableHeight, 0) + "px"; + this.updateMaxHeight(this.element, this.element.lastElementChild as HTMLElement); } public resetPosition() { if (this.element.classList.contains("fn__none")) { return; } - - setPosition(this.element, parseInt(this.element.style.left) || 0, parseInt(this.element.style.top) || 0, 0, 0); - const menuTop = parseInt(this.element.style.top) || 0; - const availableHeight = window.innerHeight - menuTop - Constants.SIZE_TOOLBAR_HEIGHT; - (this.element.lastElementChild as HTMLElement).style.maxHeight = Math.max(availableHeight, 0) + "px"; - - const showSubMenus = this.element.querySelectorAll(".b3-menu__item--show .b3-menu__submenu"); - showSubMenus.forEach((subMenuElement) => { - this.showSubMenu(subMenuElement as HTMLElement); + setPosition(this.element, parseFloat(this.element.style.left), parseFloat(this.element.style.top), 0, 0); // 如果不存在 left 或 top,则得到 NaN + this.updateMaxHeight(this.element, this.element.lastElementChild as HTMLElement); + const subMenuElements = this.element.querySelectorAll(".b3-menu__item--show .b3-menu__submenu") as NodeListOf; + subMenuElements.forEach((subMenuElement) => { + // 可能有多层子菜单,都要重新定位 + this.showSubMenu(subMenuElement); }); } diff --git a/app/src/util/setPosition.ts b/app/src/util/setPosition.ts index 9c584dec6..8854439d8 100644 --- a/app/src/util/setPosition.ts +++ b/app/src/util/setPosition.ts @@ -1,34 +1,39 @@ import {Constants} from "../constants"; -export const setPosition = (element: HTMLElement, x: number, y: number, targetHeight = 0, targetLeft = 0) => { - element.style.top = y + "px"; - element.style.left = x + "px"; +export const setPosition = (element: HTMLElement, left: number, top: number, targetHeight = 0, targetLeft = 0) => { + const isTopValid = !isNaN(top); // 存在 top 时调整垂直方向位置 + const isLeftValid = !isNaN(left); // 存在 left 时调整水平方向位置 + if (isTopValid) { + element.style.top = top + "px"; + } + if (isLeftValid) { + element.style.left = left + "px"; + } const rect = element.getBoundingClientRect(); - - // 垂直方向调整 - if (rect.bottom > window.innerHeight || rect.top < Constants.SIZE_TOOLBAR_HEIGHT) { - const bottomSpace = window.innerHeight - y; - const topSpace = y - Constants.SIZE_TOOLBAR_HEIGHT; - - if (bottomSpace >= rect.height) { - // 如果下方空间足够,直接使用原位置 - element.style.top = y + "px"; - } else if (topSpace >= rect.height) { - // 如果上方空间足够,向上调整 - element.style.top = (y - rect.height - targetHeight) + "px"; - } else { - // 如果上下空间都不够,优先展现在下部 - const maxTop = Math.max(Constants.SIZE_TOOLBAR_HEIGHT, window.innerHeight - rect.height); - element.style.top = maxTop + "px"; + + if (isTopValid) { + if (rect.top < Constants.SIZE_TOOLBAR_HEIGHT) { + // 如果元素接触顶栏,向下移 + element.style.top = Constants.SIZE_TOOLBAR_HEIGHT + "px"; + } else if (rect.bottom > window.innerHeight) { + // 如果元素底部超出窗口(下方空间不够),向上移 + if (top - Constants.SIZE_TOOLBAR_HEIGHT >= rect.height) { + // 如果上方空间足够,向上移 + element.style.top = (top - rect.height - targetHeight) + "px"; + } else { + // 如果上下空间都不够,向上移,但尽量靠底部 + element.style.top = Math.max(Constants.SIZE_TOOLBAR_HEIGHT, window.innerHeight - rect.height) + "px"; + } } } - - // 水平方向调整 - if (rect.right > window.innerWidth) { - // 展现在左侧 - element.style.left = `${window.innerWidth - rect.width - targetLeft}px`; - } else if (rect.left < 0) { - // 依旧展现在左侧,只是位置右移 - element.style.left = "0"; + + if (isLeftValid) { + if (rect.right > window.innerWidth) { + // 展现在左侧 + element.style.left = window.innerWidth - rect.width - targetLeft + "px"; + } else if (rect.left < 0) { + // 依旧展现在左侧,只是位置右移 + element.style.left = "0"; + } } };