fix(home): Back keeps the question, and the focus finds its card by name
Opening a card left Home, and Back came back to "Doesn't matter" with a different hero, whose card then dropped out of the Continue rail and moved the keyboard onto its neighbour. The budget, reroll seed and seen list live on the history entry now, and a returning list focuses the item by its name before its place.
This commit is contained in:
+31
-12
@@ -164,6 +164,18 @@ function slotOfRemoved(delta: 1 | -1): { list: NavList; index: number } | null {
|
||||
: null;
|
||||
}
|
||||
|
||||
/** What an item is called: its own name when it gives one (`navItemAttrs`), else the open element's
|
||||
* label or text — a title link's text is the title (deep-review E11 round 3). */
|
||||
function itemName(el: HTMLElement): string {
|
||||
const open = el.querySelector("[data-nav-open]");
|
||||
return (
|
||||
el.getAttribute("data-nav-name") ||
|
||||
open?.getAttribute("aria-label") ||
|
||||
open?.textContent ||
|
||||
""
|
||||
).trim();
|
||||
}
|
||||
|
||||
function focusItem(el: HTMLElement, reveal = true): void {
|
||||
if (roved !== el) {
|
||||
if (named) roved?.removeAttribute("role");
|
||||
@@ -179,15 +191,7 @@ function focusItem(el: HTMLElement, reveal = true): void {
|
||||
// A card root is a plain <div>: focused, it has no role and no name, and a screen reader read it
|
||||
// as an unlabelled run of text. While it holds the focus it is a group named after what Enter
|
||||
// opens (deep-review E11 round 2). A row (<tr>) keeps its own role.
|
||||
// The item's own name when it gives one (`navItemAttrs`), else the open element's label or text:
|
||||
// a title link's text is the title (deep-review E11 round 3).
|
||||
const open = el.querySelector("[data-nav-open]");
|
||||
const name = (
|
||||
el.getAttribute("data-nav-name") ||
|
||||
open?.getAttribute("aria-label") ||
|
||||
open?.textContent ||
|
||||
""
|
||||
).trim();
|
||||
const name = itemName(el);
|
||||
if (
|
||||
el.tagName === "DIV" &&
|
||||
name &&
|
||||
@@ -321,12 +325,14 @@ function onKey(e: KeyboardEvent): void {
|
||||
// on screen again. An overlay (the YouTube player) never unmounts the list; it restores focus itself.
|
||||
|
||||
/** Where focus sits in the page's lists: the list's place in reading order, how many lists the page
|
||||
* had on screen, the list's accessible name when it has one (a rail's title), and the item's index. */
|
||||
* had on screen, the list's accessible name when it has one (a rail's title), the item's index, and
|
||||
* the item's own name. */
|
||||
export interface NavPosition {
|
||||
readonly list: number;
|
||||
readonly lists: number;
|
||||
readonly label?: string | null;
|
||||
readonly index: number;
|
||||
readonly name?: string | null;
|
||||
}
|
||||
|
||||
const labelOf = (list: NavList): string | null => list.el.getAttribute("aria-label");
|
||||
@@ -336,7 +342,15 @@ export function navPosition(): NavPosition | null {
|
||||
if (!cur) return null;
|
||||
const all = ordered();
|
||||
const list = all.indexOf(cur.list);
|
||||
return list < 0 ? null : { list, lists: all.length, label: labelOf(cur.list), index: cur.index };
|
||||
return list < 0
|
||||
? null
|
||||
: {
|
||||
list,
|
||||
lists: all.length,
|
||||
label: labelOf(cur.list),
|
||||
index: cur.index,
|
||||
name: itemName(cur.item) || null,
|
||||
};
|
||||
}
|
||||
|
||||
// How long a returning page gets to put its lists back (a lazy chunk, cached data), and how often we
|
||||
@@ -364,7 +378,12 @@ export function restoreNavPosition(pos: NavPosition): () => void {
|
||||
: all.length === pos.lists
|
||||
? all[pos.list]
|
||||
: undefined;
|
||||
const el = list ? itemAt(list, pos.index) : null;
|
||||
// The item by its NAME first, its place second: a rail's contents can shift while the page was
|
||||
// away — Home's Continue rail leaves out whatever the hero shows, so a different hero moved every
|
||||
// card after it one place and the focus landed on the neighbour (prod UAT 2026-09-19).
|
||||
const named =
|
||||
list && pos.name ? ownItems(list.el).find((it) => itemName(it) === pos.name) : null;
|
||||
const el = named ?? (list ? itemAt(list, pos.index) : null);
|
||||
if (el) return focusItem(el, false);
|
||||
if (Date.now() < deadline) timer = setTimeout(attempt, RESTORE_POLL_MS);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user