Repository navigation
fix(site): close the docs menu with Escape after a click - #62467
Conversation
The docs' delegated keydown handler returned early for every key pressed on the lockup's trigger, so Escape after a click-open (which leaves focus on the trigger) left the menu open. Escape is now handled before the trigger keys, from anywhere on the page, and still closes a flyout submenu first. Search closes on Escape from the document too, so it works after a click moves focus out of the panel.
There was a problem hiding this comment.
The fix works. I built the docs from the base and from this branch and ran the built client in happy-dom, a simulated browser DOM (no browser was available here). On the base, a click-opened docs menu stays open on Escape, and search stays open when Escape is pressed with focus on the page. With this change both close, and focus goes back to the toggle or to the search opener. Keyboard opening still behaves as before. In services/site, bun test and typecheck:tsc pass.
The public API is unchanged; only docs client code is touched.
One small regression: when search is opened over an open menu, Escape now closes both, and focus is lost (see the inline comment).
| // Escape closes the lockup wherever focus is: a click on its trigger may leave focus on the trigger (or, | ||
| // in Safari, on the page), and keyboard opening moves it into the menu, so it can't wait for a keydown | ||
| // that started inside the menu. | ||
| if (key === "Escape" && lockup) { |
There was a problem hiding this comment.
This now also runs while search is open on top of the menu. To reproduce: open the menu with the keyboard, press Ctrl+K, then press Escape in the search field. On the base, Escape closed only search and focus went back to the menu row. Now it closes the menu too, and closeSearch then focuses its opener, which is that menu row, now hidden. Focus ends up nowhere. Skipping this branch while !dialog.hidden (or closing the lockup when search opens) would keep the old behaviour.
What
On a docs page, opening the "Docs ⌄" lockup menu with a click and pressing Escape left it open. Escape now closes any open docs menu or submenu however it was opened, and returns focus to its trigger.
Cause. Docs pages never hydrate, so
src/docs/client.tsdrives the lockup from delegated listeners. Itskeydownlistener began by finding the lockup root and, when the event came from the trigger, handled only the keys that open the menu (ArrowDown,Enter,Space,ArrowUp) and returned for everything else, Escape included:Keyboard opening moves focus into the menu, so Escape arrived from a menu row and reached the branch that closes it. A click leaves focus on the trigger (and in Safari, on the page, since clicking a button does not focus it), so Escape hit that early return.
Fix. Escape is handled before the trigger keys and without requiring the event to start inside the lockup: while a lockup is open it closes an open flyout submenu first (focusing the "Docs" row), otherwise the menu, focusing the trigger.
The same check turned up one more instance: the search dialog listened for Escape on the dialog element, so clicking inside the panel but not in its field moved focus to the page and Escape stopped closing search. It now closes from a document listener. The phone menu sheet, the phone section menu and the Contents bar already closed from document-level listeners, so they are unchanged.
Verification
Headless Playwright against
vite devand against the built docs (bun src/docs/script/build.ts --out …), desktop 1400×900 and phone 420×800:ArrowDown) → Escape closes and focus returns to the triggerArrowRightinto the flyout → Escape closes only the submenu and focuses the "Docs" row, a second Escape closes the menubun typecheck,bun run typecheck:tsc,bun test(192 pass),bun run buildand rootbun run checkall pass. The docs client bundle is 5.7 kB gzipped (budget 6.5 kB).Visual