fix(ui): a removed chip's focus outlives its neighbour; a scroll restore yields to the reader

A chip handed its focus to a Clear all or +N that went in the same removal, and the focus fell to
the body. The restore now stops when the reader scrolls, and finishes the way once the Back-to-top
spacer brings the saved spot within reach.
This commit is contained in:
2026-09-27 21:56:23 +02:00
parent 5ab6778b97
commit 25f2d9d0e4
5 changed files with 152 additions and 19 deletions
+5 -5
View File
@@ -3,7 +3,7 @@ import type { ButtonHTMLAttributes, ReactNode } from "react";
import clsx from "clsx";
import { X } from "lucide-react";
import { FocusSafeButton } from "./FocusSafeButton";
import { focusNeighbour } from "../../lib/useFocusKeeper";
import { handOffFocus } from "../../lib/useFocusKeeper";
import { chipClass, type ChipSize, type ChipVariant } from "./chipStyles";
// The chip family (docs/DESIGN.md "One primitive per button family"): a small rounded-full token that
@@ -61,8 +61,8 @@ export const Chip = forwardRef<HTMLButtonElement, ChipProps>(function Chip(
// went to <body> (round 2, F6 — the active-filter chips of the feed and Plex). Every
// removal chip gets it here, once, instead of each caller remembering.
if (removeLabel && e.currentTarget === document.activeElement)
focusNeighbour(e.currentTarget);
onClick?.(e);
handOffFocus(e.currentTarget, () => onClick?.(e));
else onClick?.(e);
}}
{...rest}
>
@@ -129,8 +129,8 @@ export function DismissibleChip({
// next chip (else the previous one, else what follows the row) first, not to <body> (E28
// review F5). A mouse press on it leaves the focus where it was.
const chip = e.currentTarget.parentElement;
if (chip && e.currentTarget === document.activeElement) focusNeighbour(chip);
onDismiss();
if (chip && e.currentTarget === document.activeElement) handOffFocus(chip, onDismiss);
else onDismiss();
}}
aria-label={dismissLabel}
title={dismissLabel}