Draw the dragged layer on a card that is not a row

Plan step 36 review of f04e31b3. The card rendered md-state-layer only
on a row, so a card the application marks data-md-dragged without being
a row, as the showcase's "A card being dragged" is, lost the 16% dragged
layer its old inset shadow drew and its header still promises. Every
card now renders the class, since the attribute arrives from script
after render, and card.css keeps the layer off a card that is neither a
row nor dragged. The divider under the header is matched as its child.

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 22:00:49 +02:00
co-authored by Claude Opus 5
parent 7d3abdd506
commit c298b19e75
3 changed files with 32 additions and 19 deletions
+11 -4
View File
@@ -6,10 +6,13 @@
* which never morphs (M3 specifies shape morph for buttons, FABs and list items, never cards —
* docs/audits/m3-alignment/containment.md, C-23).
*
* A row (`data-md-list-row`, `resources/js/list-rows.js`) renders the shared `md-state-layer` and
* `md-focus-ring` classes (foundation/interaction.css) for its tint and its ring; this file adds
* only what a card needs beyond them:
* Every card renders the shared `md-state-layer` class, and a row (`data-md-list-row`,
* `resources/js/list-rows.js`) `md-focus-ring` too (foundation/interaction.css), for its tint and
* its ring; this file adds only what a card needs beyond them:
*
* - *no layer* on a card that is neither a row nor dragged: a card that does not open anything
* answers no pointer, but any card can be carried, and the application sets `data-md-dragged`
* from script long after the view rendered;
* - per-state *elevation*, which the shared class has no opinion on: elevated 1dp rest → 2dp
* hover → 1dp focus/pressed; filled and outlined 0dp rest → 1dp hover → 0dp focus/pressed
* (the cross-check table's Hover/Focus/Pressed rows). Pressed and focus fall back to the rest
@@ -57,6 +60,10 @@
background-color: var(--md-sys-color-surface);
}
[data-md-card]:not([data-md-list-row], [data-md-dragged])::before {
display: none;
}
[data-md-card][data-md-list-row] {
cursor: pointer;
transition-property: box-shadow;
@@ -157,7 +164,7 @@
margin-inline-end: calc(-1 * var(--md-sys-measurement-space100));
}
[data-md-card-header] [data-md-divider] {
[data-md-card-header] > [data-md-divider] {
margin-top: var(--md-sys-measurement-space200);
}
+15 -13
View File
@@ -22,20 +22,21 @@
reaches the opener. Put `tabindex="-1"` on that opener so the card is the one stop, and leave
the card's other actions as they are — they follow it in the tab order.
A row (`data-md-list-row`, `actionable` or not) renders the shared `md-state-layer` and
`md-focus-ring` classes (foundation/interaction.css), which draw the tint and the ring for a
card focused directly; resources/css/components/card.css refines their opacity when the
pointer or the keyboard is on one of the card's *own* buttons instead (which should light only
itself), and adds the `:has([data-md-list-open]:focus-visible)` ring for the non-actionable
row, whose focus never lands on the card itself. Elevation is separate from the tint M3
gives each variant its own per-state box-shadow (elevated 1dp rest 2dp hover 1dp
focus/pressed; filled and outlined 0dp rest 1dp hover 0dp focus/pressed) so it is drawn
directly on `[data-md-card]`, never inside the shared class. Its corner does not move, because
Every card renders the shared `md-state-layer` class and a row (`data-md-list-row`,
`actionable` or not) `md-focus-ring` too (foundation/interaction.css), which draw the tint and
the ring for a card focused directly; resources/css/components/card.css keeps the layer off a
card that is neither a row nor dragged, refines its opacity when the pointer or the keyboard is
on one of the card's *own* buttons instead (which should light only itself), and adds the
`:has([data-md-list-open]:focus-visible)` ring for the non-actionable row, whose focus never
lands on the card itself. Elevation is separate from the tint M3 gives each variant its own
per-state box-shadow (elevated 1dp rest 2dp hover 1dp focus/pressed; filled and outlined
0dp rest 1dp hover 0dp focus/pressed) so it is drawn directly on `[data-md-card]`, never
inside the shared class. Its corner does not move, because
M3 gives a card one shape (docs/audits/m3-alignment/containment.md, C-13, C-23).
`data-md-dragged` is M3's dragged card: the top of a card's elevation scale 8dp elevated,
6dp filled and outlined under the shared class's own 16% dragged tint (it already reads
`data-md-dragged`, foundation/interaction.css). Nothing in the browser tells a card it is
`data-md-dragged` is M3's dragged card, row or not: the top of a card's elevation scale 8dp
elevated, 6dp filled and outlined under the shared class's own 16% dragged tint (it already
reads `data-md-dragged`, foundation/interaction.css). Nothing in the browser tells a card it is
being carried, so the application sets the attribute when its drag starts and takes it off on
drop. M3 requires a single-pointer alternative beside any drag, so keep the same reorder or
delete actions in a menu on the card
@@ -71,7 +72,8 @@
'role' => $role,
'aria-label' => $actionable && filled($title) ? $title : null,
], fn ($value): bool => $value !== null))->class([
'md-state-layer' => $isRow,
// Every card: the application sets `data-md-dragged` from script, after this renders.
'md-state-layer',
'md-focus-ring' => $isRow,
]) }}
>
+6 -2
View File
@@ -46,9 +46,13 @@ it('passes row attributes through and renders the shared interaction classes for
->toContain('data-md-list-row')
->toContain('data-md-list-open')
->toContain('class="md-state-layer md-focus-ring"')
// Any card can be carried, so each draws the shared layer, which card.css keeps off one
// that is neither a row nor dragged.
->and((string) $this->blade('<x-card title="Share">Body</x-card>'))
->not->toContain('md-state-layer')
->not->toContain('md-focus-ring');
->toContain('class="md-state-layer"')
->not->toContain('md-focus-ring')
->and(ComponentStylesheet::read('card')->declarations('[data-md-card]:not([data-md-list-row], [data-md-dragged])::before'))
->toBe(['display' => 'none']);
});
it('makes a directly actionable card the one tab stop, named by its title', function () {