fix(design): the keyboard ring of a flush row stays visible (E28)

A list item's ring sat 2 px outside it. On a table row it ran under the
sticky head and past the table's sides; on a Plex playlist show header, flush
in its clipping card, it was cut away and the focus was invisible. A table row
(by its tag), a log line and the Plex show header and episode rows now ring
inside themselves, the show header with its card's corners.

The sticky header's fade-below was drawn at rest, over the first row's top
16 px, hiding the ring's top edge: it now fades in with the first 1rem of
scroll (a scroll-driven animation; no fade where unsupported).
This commit is contained in:
2026-09-26 20:01:04 +02:00
parent 47c12f9405
commit 3e7c79fa69
7 changed files with 116 additions and 6 deletions
@@ -28,3 +28,24 @@ describe("the fade edge", () => {
expect(copies).toEqual([]);
});
});
// A sticky header's fade shows only once content has slid under it. Drawn at rest it lay over the
// first row's top 16 px, dimming it and hiding the top edge of the row's keyboard ring (E28, seen
// live). jsdom runs no scroll timeline, so what is pinned is the recipe: resting opacity 0, and the
// scroll-driven fade-in over the first 1rem, only where the engine has scroll timelines.
describe("the sticky header's fade", () => {
const css = readFileSync(new URL("../index.css", import.meta.url), "utf-8");
const at = css.indexOf("@utility fade-below {");
const body = css.slice(at, css.indexOf("\n}\n", at));
it("rests invisible and fades in with the first 1rem of scroll", () => {
expect(at, "the utility").toBeGreaterThan(0);
expect(body).toMatch(/opacity:\s*0;/);
expect(body).toMatch(/@supports \(animation-timeline: scroll\(\)\)/);
expect(body).toMatch(/animation-timeline:\s*scroll\(nearest block\)/);
expect(body).toMatch(/animation-range:\s*0 1rem/);
expect(css).toMatch(
/@keyframes fade-edge-in \{\s*from \{\s*opacity: 0;\s*\}\s*to \{\s*opacity: 1;/,
);
});
});