feat(ui): a control can dock under the toasts and ride every toast host

The toast stack already has a host in the page and in every overlay (dialogs,
both players, the fullscreen player), the topmost one shows it, and inside an
overlay it sits above the player's control band. lib/toastDock lets one control
use exactly that placement; it renders outside the live region, so a ticking
control is not read out every second.
This commit is contained in:
2026-09-19 22:08:10 +02:00
parent a7d7456279
commit 2d9595b0e5
3 changed files with 101 additions and 53 deletions
@@ -83,7 +83,7 @@ describe("Toaster — handing the toasts to another host", () => {
render(<Toaster />);
act(() => void notify({ level: "info", message: "saved" }));
const overlay = render(<Toaster overlay />);
const region = overlay.container.firstElementChild!;
const region = overlay.container.querySelector("[aria-live]")!;
expect(region.textContent).toContain("saved"); // it took the toast over…
expect(region.getAttribute("aria-live")).toBe("off"); // …while still off
await act(async () => void (await new Promise((r) => setTimeout(r, 0))));
@@ -170,7 +170,7 @@ describe("Toaster — handing the toasts to another host", () => {
// docked bar, above them at z-overlay, and took their clicks for as long as a toast was up.
render(<Toaster overlay />);
act(() => void notify({ level: "error", message: "boom" }));
const host = hosts()[0]!;
const host = hosts()[0]!.closest("[data-toast-region]")!;
expect(host.className).toContain("bottom-24");
expect(host.className).toContain("pointer-events-none");
expect(screen.getByText("boom").closest(".toast-card")!.className).toContain(