Say what the browser gives a popover trigger, and pin the part all engines can prove #94

Merged
lilleman merged 1 commits from popover-expanded into main 2026-08-26 15:37:06 +02:00
2 changed files with 6 additions and 2 deletions
Showing only changes of commit 0ad4c6b09c - Show all commits
+5 -2
View File
@@ -254,8 +254,11 @@ Revisit only if the stated reason stops holding.
open-state style and the old-browser fallback both read; the partial **requires a caller-named open-state style and the old-browser fallback both read; the partial **requires a caller-named
`id`** and fails loud without one, since that is the `popovertarget` idref (never generate one — `id`** and fails loud without one, since that is the `popovertarget` idref (never generate one —
nondeterministic HTML forecloses the caching decision); and **neither `aria-expanded` nor nondeterministic HTML forecloses the caching decision); and **neither `aria-expanded` nor
`aria-haspopup` is written**, because a zero-JS invoker cannot keep the first truthful and the `aria-haspopup` is written**: every engine maintains the first itself on a declarative
second would promise `role="menu"` semantics these panels don't implement. `<details>` stays where `popovertarget` invoker, so a hand-written one replaces a live state with a static lie, and the
second would promise `role="menu"` semantics these panels don't implement. **That guarantee is the
declarative attribute's alone** — open a panel from script and no engine applies it, so
"enhancing" one of these triggers is what would cost it its accessibility. `<details>` stays where
it means disclosure rather than popup: the nav tree. `shell.ejs` hand-rolls the same block for the it means disclosure rather than popup: the nav tree. `shell.ejs` hand-rolls the same block for the
profile menu (its trigger composes escaped user values and its one item is a CSRF POST form) — keep profile menu (its trigger composes escaped user values and its one item is a CSRF POST form) — keep
the two in step. the two in step.
+1
View File
@@ -94,6 +94,7 @@ test("a popover menu sits on its trigger and closes on an outside click or Esc
await expect(panel).toBeVisible(); await expect(panel).toBeVisible();
await page.keyboard.press("Escape"); await page.keyboard.press("Escape");
await expect(panel).toBeHidden(); await expect(panel).toBeHidden();
await expect(trigger).toBeFocused(); // the browser returns focus, so no trigger needs a tabindex
}); });
test("mobile layout hides the sidebar off-canvas behind the hamburger", async ({ page }) => { test("mobile layout hides the sidebar off-canvas behind the hamburger", async ({ page }) => {