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
+16
View File
@@ -13,3 +13,19 @@ export function endsTextSelection(el: Element): boolean {
(!!sel.focusNode && el.contains(sel.focusNode))
);
}
// The rule's other half: a press that travelled before its release was a drag (across a row's empty
// padding, say, where nothing selects), not a click, so it opens nothing either.
/** How far, in px, the pointer may move between press and release and still count as a click. */
const CLICK_SLOP = 6;
/** True when a click's pointer moved past the slop since its press at `from`. A keyboard click
* (`detail` 0) carries no pointer, so it never did. */
export function movedPastSlop(
from: { x: number; y: number } | null,
e: { detail: number; clientX: number; clientY: number },
): boolean {
if (!from || e.detail === 0) return false;
return Math.abs(e.clientX - from.x) > CLICK_SLOP || Math.abs(e.clientY - from.y) > CLICK_SLOP;
}