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
+13 -6
View File
@@ -85,6 +85,16 @@ export function escapeHandled(e: KeyboardEvent): boolean {
return handledEscapes.has(e);
}
/** THE close-on-Escape test for layer `id`: an Escape no layer above has taken yet, while `id` is on
* top — and it claims the event for `id`. Both halves are needed: a list inside a dialog closes with a
* synchronous flush that pops its layer before the dialog's `window` listener runs, so topmost alone
* read "yes" and one Escape closed the list AND the dialog (E28, the share dialog's user picker). */
export function takeEscape(e: KeyboardEvent, id: number): boolean {
if (e.key !== "Escape" || escapeHandled(e) || !isTopmost(id)) return false;
markEscapeHandled(e);
return true;
}
/** Register an open layer for as long as `active` is true, and get live predicates to read inside your
* own key handler. Pass `onEscape` to also install a topmost-only Escape listener (the common case —
* modals, popovers). Omit it for a layer that owns a bigger key handler and only needs `anyAbove()`
@@ -110,12 +120,9 @@ export function useLayer(
let onKey: ((e: KeyboardEvent) => void) | undefined;
if (escRef.current) {
onKey = (e: KeyboardEvent) => {
if (e.key === "Escape" && isTopmost(id)) {
// Stamp BEFORE closing so a player's `window` listener (fires after this `document` one)
// sees the Escape as consumed even though closing us pops this layer off the stack.
markEscapeHandled(e);
escRef.current?.();
}
// takeEscape stamps BEFORE closing, so a `window` listener below (fires after this `document`
// one) sees the Escape as consumed even though closing us pops this layer off the stack.
if (takeEscape(e, id)) escRef.current?.();
};
document.addEventListener("keydown", onKey);
}