From 6dfa435e3467d0f938753fc062eaa3c16dd64831 Mon Sep 17 00:00:00 2001 From: Ferit Date: Thu, 15 Oct 2026 18:21:16 +0300 Subject: [PATCH] Keep keyboard navigation within the current menu --- src/index.ts | 4 +++- test/test.js | 23 +++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 43d32fc..434476b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -147,7 +147,9 @@ function focusFirstItem(details: Element) { } function sibling(details: Element, next: boolean): HTMLElement | null { - const options = Array.from(details.querySelectorAll('[role^="menuitem"]:not([hidden]):not([disabled])')) + const options = Array.from( + details.querySelectorAll('[role^="menuitem"]:not([hidden]):not([disabled])') + ).filter(option => option.closest('details') === details) const selected = document.activeElement const index = selected instanceof HTMLElement ? options.indexOf(selected) : -1 const found = next ? options[index + 1] : options[index - 1] diff --git a/test/test.js b/test/test.js index 3d2d339..754fdf9 100644 --- a/test/test.js +++ b/test/test.js @@ -114,6 +114,29 @@ describe('details-menu element', function () { assert.equal(first, document.activeElement, 'arrow up focuses first item') }) + it('skips items belonging to a closed nested menu when navigating', function () { + const details = document.querySelector('details') + details.open = true + const [first, second] = details.querySelectorAll('[role="menuitem"]') + const nested = document.createElement('details') + const summary = document.createElement('summary') + summary.textContent = 'More' + const menu = document.createElement('details-menu') + const item = document.createElement('button') + item.setAttribute('role', 'menuitem') + item.textContent = 'Nested' + menu.append(item) + nested.append(summary, menu) + first.after(nested) + first.focus() + + first.dispatchEvent(new KeyboardEvent('keydown', {key: 'ArrowDown', bubbles: true})) + assert.equal(document.activeElement, second) + + second.dispatchEvent(new KeyboardEvent('keydown', {key: 'ArrowUp', bubbles: true})) + assert.equal(document.activeElement, first) + }) + it('closes and focuses summary on escape', function () { const details = document.querySelector('details') const summary = details.querySelector('summary')