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
+32 -1
View File
@@ -3,7 +3,7 @@
// E28 S3b: the chip family (docs/DESIGN.md "One primitive per button family"). One geometry for a
// filter, an applied narrowing, a tag and a suggestion, where five files had five.
import { afterEach, describe, expect, it, vi } from "vitest";
import { act, cleanup, fireEvent, render, screen } from "@testing-library/react";
import { act, cleanup, fireEvent, render, screen, waitFor } from "@testing-library/react";
import { useState } from "react";
import { Chip, DismissibleChip } from "./Chip";
import { chipClass } from "./chipStyles";
@@ -70,6 +70,37 @@ describe("Chip", () => {
expect(document.activeElement).toBe(screen.getByRole("button", { name: "Remove News" }));
});
// Round 3: the neighbour it hands the focus to can go in the same removal — the "Clear all" that
// only shows beside two filters. The focus then goes to the chip before, not to <body>.
it("a removing chip whose neighbour goes with it hands the focus to the chip before", async () => {
function Row() {
const [chips, setChips] = useState(["Music", "News"]);
return (
<div>
{chips.map((c) => (
<Chip
key={c}
variant="token"
removeLabel={`Remove ${c}`}
onClick={() => setChips((all) => all.filter((x) => x !== c))}
>
{c}
</Chip>
))}
{chips.length > 1 && <button onClick={() => setChips([])}>Clear all</button>}
</div>
);
}
render(<Row />);
const news = screen.getByRole("button", { name: "Remove News" });
act(() => news.focus());
fireEvent.click(news, { detail: 0 });
expect(screen.queryByRole("button", { name: "Clear all" })).toBeNull();
await waitFor(() =>
expect(document.activeElement).toBe(screen.getByRole("button", { name: "Remove Music" })),
);
});
it("the measuring mirror lays out exactly like the chip it measures", () => {
render(
<Chip variant="tag" size="xs">