fix(nav): Back's focus keeps its place among items of the same name

Restoring by name picked the first item of that name, so Back on the log console (repeated
http.request lines) or two same-titled downloads landed on the wrong one. The saved place wins while
it still holds the name; otherwise the nearest item of that name.
This commit is contained in:
2026-09-19 05:47:16 +02:00
parent 1fa6a0bea8
commit 49fe56b6e7
2 changed files with 45 additions and 3 deletions
+14 -3
View File
@@ -366,6 +366,16 @@ const RESTORE_POLL_MS = 50;
* list N may be another list, and <body> is the honest outcome. Gives up when the time is up, and at
* once when focus is already somewhere (the reader moved on, a dialog took it). The item is focused
* where the scroll restore put it, never scrolled to. Returns a cancel. */
function restoreTarget(list: NavList, pos: NavPosition): HTMLElement | null {
const at = itemAt(list, pos.index);
if (!pos.name || (at && itemName(at) === pos.name)) return at;
const distance = (el: HTMLElement) => Math.abs(indexOf(el) - pos.index);
const named = ownItems(list.el)
.filter((it) => itemName(it) === pos.name)
.sort((a, b) => distance(a) - distance(b));
return named[0] ?? at;
}
export function restoreNavPosition(pos: NavPosition): () => void {
const deadline = Date.now() + RESTORE_MS;
let timer: ReturnType<typeof setTimeout> | undefined;
@@ -381,9 +391,10 @@ export function restoreNavPosition(pos: NavPosition): () => void {
// 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);
// The saved place still holding the saved name wins, and among several items of that name the
// nearest one: names repeat (a log console's `http.request GET /api/feed 200` lines, two
// downloads of one video), and the first of them is rarely the one left behind.
const el = list ? restoreTarget(list, pos) : null;
if (el) return focusItem(el, false);
if (Date.now() < deadline) timer = setTimeout(attempt, RESTORE_POLL_MS);
};