Give a card per-state elevation and M3's two keyboard models

Plan step 19, containment.md C-13, C-23 and the directly actionable card
of C-13/C-18. A card had one elevation and never changed it, and its
hover replaced the elevated card's shadow with the state layer; it now
names its variant in `data-card` and moves a step on hover (elevated
1 to 2, filled and outlined 0 to 1) with the layer over it. The 12 to 16
corner morph on hover is gone: M3 gives a card one shape and lists shape
morph for buttons, FABs and list items only. New `actionable` prop for
M3's directly actionable card — the card is the one tab stop, with
`role="button"` (or `link`), its `title` as the name, and Enter or Space
reaching the opener from resources/js/list-rows.js.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
This commit is contained in:
Andreas Reinhold / reini
2026-09-14 06:07:14 +02:00
co-authored by Claude Opus 5
parent 26a9230a48
commit 675cfc806a
7 changed files with 131 additions and 18 deletions
+26
View File
@@ -11,6 +11,11 @@
* Not a stretched link (`::after { inset: 0 }`): Safari makes no containing block of a <tr>, so in
* a table every overlay would cover the whole table; and not one <button> around the row, which
* could hold no other buttons. The listeners sit on `document` and are added once.
*
* A row that is also `data-list-actionable` is M3's **directly actionable card** (`<x-card
* actionable>`): the card is the one tab stop, so Enter and Space on the card reach the opener,
* and the card's other actions follow it in the tab order with their own keys. The opener itself
* carries `tabindex="-1"`, which the card's caller writes.
*/
/** Anything that answers a click itself — the row's own controls, and the opener. */
@@ -99,6 +104,27 @@ document.addEventListener('click', (event) => {
opener.click()
})
// A directly actionable card is a control, so it answers Enter and Space the way a
// button does — by reaching the opener, which owns the href or the wire:click.
document.addEventListener('keydown', (event) => {
if (event.key !== 'Enter' && event.key !== ' ') {
return
}
if (event.defaultPrevented || !(event.target instanceof Element) || !event.target.matches('[data-list-actionable]')) {
return
}
const opener = event.target.querySelector('[data-list-open]')
if (!opener) {
return
}
event.preventDefault()
opener.click()
})
// The middle button is not a `click`, and on a row that goes somewhere it means
// what it means on a link.
document.addEventListener('auxclick', (event) => {