Merge pull request #20681 from calixteman/bug2016212

Correctly handle tab/page down when on a menu (bug 2016212)
This commit is contained in:
calixteman 2026-02-18 15:39:53 +01:00 committed by GitHub
commit 6b1b94e7d4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 90 additions and 2 deletions

View File

@ -355,4 +355,73 @@ describe("PDF Thumbnail View", () => {
);
});
});
describe("Menu keyboard navigation with multi-character keys (bug 2016212)", () => {
let pages;
beforeEach(async () => {
pages = await loadAndWait(
"page_with_number_and_link.pdf",
"#viewsManagerSelectorButton",
null,
null,
{ enableSplitMerge: true }
);
});
afterEach(async () => {
await closePages(pages);
});
it("must navigate menus with ArrowDown and Tab keys", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
await page.click("#viewsManagerToggleButton");
await waitForThumbnailVisible(page, 1);
// Focus the views manager selector button
await page.waitForSelector("#viewsManagerSelectorButton", {
visible: true,
});
await page.focus("#viewsManagerSelectorButton");
// Open menu with Enter key
await page.keyboard.press("Enter");
// Wait for menu to be expanded
await waitForMenu(page, "#viewsManagerSelectorButton");
// Check that focus moved to the first menu button (pages)
await page.waitForSelector("#thumbnailsViewMenu:focus", {
visible: true,
});
// Press ArrowDown to navigate to second item
await page.keyboard.press("ArrowDown");
// Should now be on outlines button
await page.waitForSelector("#outlinesViewMenu:focus", {
visible: true,
});
// Press Tab to move to the manage button (should close views menu)
await page.keyboard.press("Tab");
// Wait for views manager menu to be collapsed
await waitForMenu(page, "#viewsManagerSelectorButton", false);
// Focus should be on manage button
await page.waitForSelector("#viewsManagerStatusActionButton:focus", {
visible: true,
});
// Open manage menu with Space key
await page.keyboard.press(" ");
// Wait for manage menu to be expanded
await waitForMenu(page, "#viewsManagerStatusActionButton");
})
);
});
});
});

View File

@ -28,6 +28,8 @@ class Menu {
#lastIndex = -1;
#onFocusOutBound = this.#onFocusOut.bind(this);
/**
* Create a menu for the given button.
* @param {HTMLElement} menuContainer
@ -97,7 +99,18 @@ class Menu {
},
{ signal }
);
window.addEventListener("blur", this.#closeMenu.bind(this), { signal });
const closeMenu = this.#closeMenu.bind(this);
window.addEventListener("blur", closeMenu, { signal });
menu.addEventListener("focusout", this.#onFocusOutBound, { signal });
}
#onFocusOut({ relatedTarget }) {
if (
!this.#triggeringButton.contains(relatedTarget) &&
!this.#menu.contains(relatedTarget)
) {
this.#closeMenu();
}
}
/**
@ -112,6 +125,7 @@ class Menu {
this.#openMenu();
});
this.#triggeringButton.addEventListener("focusout", this.#onFocusOutBound);
const { signal } = this.#menuAC;
@ -148,7 +162,12 @@ class Menu {
stopEvent(e);
break;
default:
const char = e.key.toLocaleLowerCase();
const { key } = e;
if (!/^\p{L}$/u.test(key)) {
// It isn't a single letter, so ignore it.
break;
}
const char = key.toLocaleLowerCase();
this.#goToNextItem(e.target, true, item =>
item.textContent.trim().toLowerCase().startsWith(char)
);