fix(design): the saved-view chip on one line, one Escape per layer, the /watch action row (E28 S3d)

- SelectTrigger lays its toolbar value out as a row; the saved-view chip passes its glyph and
  modified dot through the icon/trailing slots (an <svg> is display:block and broke the chip in two).
- One Escape in a dialog's open list closed the list AND the dialog: the list's close flushes
  synchronously and pops its layer before the dialog's window listener runs. takeEscape() is the one
  close-on-Escape test (topmost AND not yet taken); Modal, the Welcome lightbox and useLayer use it,
  and keymap.dispatch ignores a taken Escape.
- /watch: the Download sits on the toolbar rung, level with the Repeat chip.
This commit is contained in:
2026-09-25 01:40:45 +02:00
parent 1833f594cb
commit b4a6569d0d
12 changed files with 144 additions and 26 deletions
+11
View File
@@ -11,6 +11,7 @@ import {
isTypingTarget,
matches,
} from "./keymap";
import { markEscapeHandled } from "./layerStack";
/** A keydown as it arrives from a real focus target (jsdom only fills `e.target` on dispatch). */
function press(init: KeyboardEventInit, on?: HTMLElement): KeyboardEvent {
@@ -143,6 +144,16 @@ describe("dispatch", () => {
input.remove();
});
// E28: an Escape a menu above already took is spent — a player or page acting on it too is the
// double close the layer stack exists to prevent.
it("ignores an Escape a layer already took", () => {
const calls: string[] = [];
const e = press({ key: "Escape" });
markEscapeHandled(e);
expect(dispatch(e, "plexPlayer", plexHandlers(calls))).toBe(false);
expect(calls).toEqual([]);
});
// A `handledLocally` row is documentation + a shared chord, matched on the focused element by its
// own component (Shift+F10 belongs to whichever row has focus). `dispatch` must not claim it, and
// the mapped handler type must not demand one — this test is the runtime half of that.