fix(nav): Back restores the focus after a click on the page's blank area

The page region is <main tabIndex=-1>, so a background click focused it and the
Back restore took that for the reader's choice. A resting place is marked and
one predicate (focusIsUnclaimed) decides "the focus is nowhere" everywhere.
This commit is contained in:
2026-09-27 14:06:16 +02:00
parent 383c85231a
commit 9bedd96774
6 changed files with 45 additions and 11 deletions
+14 -2
View File
@@ -41,6 +41,19 @@ export function isStandInFocus(el: Element | null): boolean {
return el !== null && el === standIn;
}
/** Marks an element that can hold the focus without being a control: the page's scroll region
* (`<main tabIndex={-1}>`, the skip link's target) takes it from a mouse click on the page's blank
* area. Holding it there is the focus RESTING on the page, the same as <body>. */
export const FOCUS_REST_ATTRS = { "data-focus-rest": "" } as const;
/** Whether the focus is nowhere the reader chose — <body>, the keeper's stand-in, or a resting place
* (`FOCUS_REST_ATTRS`) — so a restore may take it. The page region used to count as a choice: a
* click on its background focused it, and Back then left the focus there instead of on the item
* the view was opened from (E28 review H1). */
export function focusIsUnclaimed(el: Element | null = document.activeElement): boolean {
return !el || el === document.body || isStandInFocus(el) || el.hasAttribute("data-focus-rest");
}
/** Where the focus goes once `lost` (removed with the nodes of `record`) has left the page — never
* outside `root`: a slot with nothing focusable left (a page swapped for its loading fallback) must
* not send the focus to the first control of the whole document. */
@@ -80,8 +93,7 @@ export function useFocusKeeper(ref: RefObject<HTMLElement | null>): void {
const lost = held;
if (!lost || lost.isConnected) return;
held = null;
const active = document.activeElement;
if (active && active !== document.body) return; // something already took the focus
if (!focusIsUnclaimed()) return; // something already took the focus
for (const r of records) {
const target = slotFor(r, lost, root);
if (target) {