feat(design): titles in click surfaces stay selectable (E28 S3e4b)

A button whose text is a title or a name is now `selectable` (FocusSafeButton,
under Pressable and Button): Chrome selects inside a button once it is
user-select: text (measured), and the click that ends a selection, or a press
dragged past the slop, opens nothing. 23 surfaces carry it (cards, rows, people,
channels, playlists, users, views, messages, log lines, description links); the
Plex caption div gets the same guard. A contract test pins every plain Pressable
with its reason, so a new one has to choose.
This commit is contained in:
2026-09-26 18:46:24 +02:00
parent e1ef56cdbc
commit edf2f22ef9
26 changed files with 309 additions and 8 deletions
@@ -146,3 +146,34 @@ describe("Plex cards in a keyboard-walkable list", () => {
expect(container.querySelector("[data-nav-index]")).toBeNull();
});
});
describe("a Plex card's title stays selectable", () => {
it("opens from its caption, but not on the click that ends selecting the title", () => {
const onOpen = vi.fn();
render(<PlexPosterCard card={movie()} onOpen={onOpen} />);
const title = screen.getByText("M");
const range = document.createRange();
range.selectNodeContents(title);
window.getSelection()!.removeAllRanges();
window.getSelection()!.addRange(range);
fireEvent.click(title);
expect(onOpen).not.toHaveBeenCalled();
window.getSelection()!.removeAllRanges();
fireEvent.click(title);
expect(onOpen).toHaveBeenCalledTimes(1);
});
});
describe("a Plex card's caption and a drag", () => {
it("does not open on a press dragged past the slop", () => {
const onOpen = vi.fn();
render(<PlexPosterCard card={movie()} onOpen={onOpen} />);
const title = screen.getByText("M");
fireEvent.mouseDown(title, { clientX: 10, clientY: 10 });
fireEvent.click(title, { detail: 1, clientX: 60, clientY: 10 });
expect(onOpen).not.toHaveBeenCalled();
fireEvent.mouseDown(title, { clientX: 10, clientY: 10 });
fireEvent.click(title, { detail: 1, clientX: 11, clientY: 10 });
expect(onOpen).toHaveBeenCalledTimes(1);
});
});