Skip to content

Commit ce37930

Browse files
committed
🎨 The menu no longer extends beyond the window
fix #15400
1 parent b37f65b commit ce37930

File tree

3 files changed

+74
-53
lines changed

3 files changed

+74
-53
lines changed

app/src/assets/scss/component/_menu.scss

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,6 @@
169169
}
170170

171171
&__items {
172-
max-height: 80vh;
173172
overflow: auto;
174173
padding: 0 8px;
175174
}
@@ -367,7 +366,6 @@
367366
&__submenu {
368367
overflow: auto;
369368
display: none;
370-
max-height: 80vh;
371369
border: 1px solid var(--b3-theme-surface-lighter);
372370
border-radius: var(--b3-border-radius-b);
373371
background-color: var(--b3-menu-background);

app/src/menus/Menu.ts

Lines changed: 42 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -60,23 +60,46 @@ export class Menu {
6060
});
6161
}
6262

63-
public showSubMenu(subMenuElement: HTMLElement) {
63+
public showSubMenu(subMenuElement: HTMLElement | null) {
64+
if (!subMenuElement) {
65+
return;
66+
}
67+
const itemsMenuElement = subMenuElement.lastElementChild as HTMLElement;
68+
if (itemsMenuElement) {
69+
itemsMenuElement.style.maxHeight = "";
70+
}
6471
const itemRect = subMenuElement.parentElement.getBoundingClientRect();
65-
subMenuElement.style.top = (itemRect.top - 8) + "px";
66-
subMenuElement.style.left = (itemRect.right + 8) + "px";
67-
subMenuElement.style.bottom = "auto";
68-
const rect = subMenuElement.getBoundingClientRect();
69-
if (rect.right > window.innerWidth) {
70-
if (itemRect.left - 8 > rect.width) {
71-
subMenuElement.style.left = (itemRect.left - 8 - rect.width) + "px";
72+
const subMenuRect = subMenuElement.getBoundingClientRect();
73+
74+
// 垂直方向位置调整
75+
// 减 9px 是为了尽量对齐菜单选项(b3-menu__submenu 的默认 padding-top 加上子菜单首个 b3-menu__item 的默认 margin-top)
76+
// 减 1px 是为了避免在特定情况下渲染出不应存在的滚动条而做的兼容处理
77+
const top = Math.min(itemRect.top - 9, window.innerHeight - subMenuRect.height - 1);
78+
subMenuElement.style.top = Math.max(Constants.SIZE_TOOLBAR_HEIGHT, top) + "px";
79+
80+
// 水平方向位置调整
81+
if (subMenuRect.right <= window.innerWidth) {
82+
// 8px 是 b3-menu__items 的默认 padding-right
83+
subMenuElement.style.left = (itemRect.right + 8) + "px";
84+
} else {
85+
if (itemRect.left - 8 > subMenuRect.width) {
86+
subMenuElement.style.left = (itemRect.left - 8 - subMenuRect.width) + "px";
7287
} else {
73-
subMenuElement.style.left = (window.innerWidth - rect.width) + "px";
88+
subMenuElement.style.left = (window.innerWidth - subMenuRect.width) + "px";
7489
}
7590
}
76-
if (rect.bottom > window.innerHeight) {
77-
subMenuElement.style.top = "auto";
78-
subMenuElement.style.bottom = "8px";
91+
92+
this.updateMaxHeight(subMenuElement, itemsMenuElement);
93+
}
94+
95+
private updateMaxHeight(menuElement: HTMLElement, itemsMenuElement: HTMLElement) {
96+
if (!menuElement || !itemsMenuElement) {
97+
return;
7998
}
99+
const menuRect = menuElement.getBoundingClientRect();
100+
const itemsMenuRect = itemsMenuElement.getBoundingClientRect();
101+
const availableHeight = (window.innerHeight - menuRect.top) - (menuRect.height - itemsMenuRect.height);
102+
itemsMenuElement.style.maxHeight = Math.max(availableHeight, 0) + "px";
80103
}
81104

82105
private preventDefault(event: KeyboardEvent) {
@@ -146,24 +169,19 @@ export class Menu {
146169
this.element.style.zIndex = (++window.siyuan.zIndex).toString();
147170
this.element.classList.remove("fn__none");
148171
setPosition(this.element, options.x - (options.isLeft ? this.element.clientWidth : 0), options.y, options.h, options.w);
149-
const menuTop = parseInt(this.element.style.top) || options.y;
150-
const availableHeight = window.innerHeight - menuTop - Constants.SIZE_TOOLBAR_HEIGHT;
151-
(this.element.lastElementChild as HTMLElement).style.maxHeight = Math.max(availableHeight, 0) + "px";
172+
this.updateMaxHeight(this.element, this.element.lastElementChild as HTMLElement);
152173
}
153174

154175
public resetPosition() {
155176
if (this.element.classList.contains("fn__none")) {
156177
return;
157178
}
158-
159-
setPosition(this.element, parseInt(this.element.style.left) || 0, parseInt(this.element.style.top) || 0, 0, 0);
160-
const menuTop = parseInt(this.element.style.top) || 0;
161-
const availableHeight = window.innerHeight - menuTop - Constants.SIZE_TOOLBAR_HEIGHT;
162-
(this.element.lastElementChild as HTMLElement).style.maxHeight = Math.max(availableHeight, 0) + "px";
163-
164-
const showSubMenus = this.element.querySelectorAll(".b3-menu__item--show .b3-menu__submenu");
165-
showSubMenus.forEach((subMenuElement) => {
166-
this.showSubMenu(subMenuElement as HTMLElement);
179+
setPosition(this.element, parseFloat(this.element.style.left), parseFloat(this.element.style.top), 0, 0); // 如果不存在 left 或 top,则得到 NaN
180+
this.updateMaxHeight(this.element, this.element.lastElementChild as HTMLElement);
181+
const subMenuElements = this.element.querySelectorAll(".b3-menu__item--show .b3-menu__submenu") as NodeListOf<HTMLElement>;
182+
subMenuElements.forEach((subMenuElement) => {
183+
// 可能有多层子菜单,都要重新定位
184+
this.showSubMenu(subMenuElement);
167185
});
168186
}
169187

app/src/util/setPosition.ts

Lines changed: 32 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,39 @@
11
import {Constants} from "../constants";
22

3-
export const setPosition = (element: HTMLElement, x: number, y: number, targetHeight = 0, targetLeft = 0) => {
4-
element.style.top = y + "px";
5-
element.style.left = x + "px";
3+
export const setPosition = (element: HTMLElement, left: number, top: number, targetHeight = 0, targetLeft = 0) => {
4+
const isTopValid = !isNaN(top); // 存在 top 时调整垂直方向位置
5+
const isLeftValid = !isNaN(left); // 存在 left 时调整水平方向位置
6+
if (isTopValid) {
7+
element.style.top = top + "px";
8+
}
9+
if (isLeftValid) {
10+
element.style.left = left + "px";
11+
}
612
const rect = element.getBoundingClientRect();
7-
8-
// 垂直方向调整
9-
if (rect.bottom > window.innerHeight || rect.top < Constants.SIZE_TOOLBAR_HEIGHT) {
10-
const bottomSpace = window.innerHeight - y;
11-
const topSpace = y - Constants.SIZE_TOOLBAR_HEIGHT;
12-
13-
if (bottomSpace >= rect.height) {
14-
// 如果下方空间足够,直接使用原位置
15-
element.style.top = y + "px";
16-
} else if (topSpace >= rect.height) {
17-
// 如果上方空间足够,向上调整
18-
element.style.top = (y - rect.height - targetHeight) + "px";
19-
} else {
20-
// 如果上下空间都不够,优先展现在下部
21-
const maxTop = Math.max(Constants.SIZE_TOOLBAR_HEIGHT, window.innerHeight - rect.height);
22-
element.style.top = maxTop + "px";
13+
14+
if (isTopValid) {
15+
if (rect.top < Constants.SIZE_TOOLBAR_HEIGHT) {
16+
// 如果元素接触顶栏,向下移
17+
element.style.top = Constants.SIZE_TOOLBAR_HEIGHT + "px";
18+
} else if (rect.bottom > window.innerHeight) {
19+
// 如果元素底部超出窗口(下方空间不够),向上移
20+
if (top - Constants.SIZE_TOOLBAR_HEIGHT >= rect.height) {
21+
// 如果上方空间足够,向上移
22+
element.style.top = (top - rect.height - targetHeight) + "px";
23+
} else {
24+
// 如果上下空间都不够,向上移,但尽量靠底部
25+
element.style.top = Math.max(Constants.SIZE_TOOLBAR_HEIGHT, window.innerHeight - rect.height) + "px";
26+
}
2327
}
2428
}
25-
26-
// 水平方向调整
27-
if (rect.right > window.innerWidth) {
28-
// 展现在左侧
29-
element.style.left = `${window.innerWidth - rect.width - targetLeft}px`;
30-
} else if (rect.left < 0) {
31-
// 依旧展现在左侧,只是位置右移
32-
element.style.left = "0";
29+
30+
if (isLeftValid) {
31+
if (rect.right > window.innerWidth) {
32+
// 展现在左侧
33+
element.style.left = window.innerWidth - rect.width - targetLeft + "px";
34+
} else if (rect.left < 0) {
35+
// 依旧展现在左侧,只是位置右移
36+
element.style.left = "0";
37+
}
3338
}
3439
};

0 commit comments

Comments
 (0)