From 2a2d1b636d951b98c6508880a1292189ddd573dd Mon Sep 17 00:00:00 2001
From: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com>
Date: Sat, 31 Oct 2026 20:28:02 +0800
Subject: [PATCH] fix(workbench): restore focus on a second Alt and keep closed
menus closed
---
src/workbench/MainMenu.interaction.test.tsx | 39 +++++++++++++++++++++
src/workbench/MainMenu.tsx | 15 ++++++--
2 files changed, 51 insertions(+), 3 deletions(-)
diff --git a/src/workbench/MainMenu.interaction.test.tsx b/src/workbench/MainMenu.interaction.test.tsx
index f2b98b5fe..0d003e88b 100644
--- a/src/workbench/MainMenu.interaction.test.tsx
+++ b/src/workbench/MainMenu.interaction.test.tsx
@@ -156,6 +156,45 @@ describe("main menu presentation", () => {
expect(editor).toHaveFocus();
});
+ it("returns focus on a second lone Alt", async () => {
+ setStyle("merged");
+ render(
+ <>
+
+
+ >,
+ );
+ await screen.findByRole("menubar");
+ const editor = screen.getByRole("button", { name: "Editor" });
+ editor.focus();
+ alt();
+ expect(titles().getByRole("menuitem", { name: "File" })).toHaveFocus();
+ alt();
+ expect(editor).toHaveFocus();
+ alt();
+ fireEvent.keyDown(document.activeElement!, { key: "Escape" });
+ expect(editor).toHaveFocus();
+ });
+
+ it("does not reopen a title's menu after the titles collapse and return", async () => {
+ setStyle("merged");
+ render();
+ await screen.findByRole("menubar");
+ fireEvent.click(titles().getByRole("menuitem", { name: "Edit" }));
+ expect(await screen.findByRole("menu", { name: "Edit" })).toBeInTheDocument();
+ act(() => {
+ available = 100;
+ resize();
+ });
+ expect(screen.queryByRole("menu")).toBeNull();
+ act(() => {
+ available = 500;
+ resize();
+ });
+ expect(screen.getByRole("menubar")).toBeInTheDocument();
+ expect(screen.queryByRole("menu")).toBeNull();
+ });
+
it("falls back to a hamburger popup when narrow and restores titles when space returns", async () => {
available = 100;
setStyle("merged");
diff --git a/src/workbench/MainMenu.tsx b/src/workbench/MainMenu.tsx
index 608dd56ed..f9adf94a3 100644
--- a/src/workbench/MainMenu.tsx
+++ b/src/workbench/MainMenu.tsx
@@ -122,7 +122,13 @@ function MainMenuBar({ merged, onExpandedChange }: MainMenuProps & { merged: boo
// Measuring the allocated slot includes all neighbouring toolbar/caption controls.
useLayoutEffect(() => {
if (!merged || !slot.current || !measure.current) return;
- const update = () => setFits(menus.length > 0 && slot.current!.clientWidth >= measure.current!.scrollWidth);
+ const update = () => {
+ const next = menus.length > 0 && slot.current!.clientWidth >= measure.current!.scrollWidth;
+ setFits(next);
+ // Each presentation's menu closes with it, and must not reopen when it returns.
+ if (next) setExpanded(false);
+ else setOpen(null);
+ };
const observer = new ResizeObserver(update);
observer.observe(slot.current);
observer.observe(measure.current);
@@ -161,8 +167,11 @@ function MainMenuBar({ merged, onExpandedChange }: MainMenuProps & { merged: boo
close();
if (persistent) focusTitle(focused);
} else if (persistent) {
- previousFocus.current = document.activeElement as HTMLElement;
- focusTitle(0);
+ if (bar?.contains(document.activeElement)) previousFocus.current?.focus();
+ else {
+ previousFocus.current = document.activeElement as HTMLElement;
+ focusTitle(0);
+ }
} else void expand();
}),
);