Draw M3's dragged state on a card

Plan step 23, containment.md § Missing ("Cards: the dragged state").
`data-dragged` on `<x-card>` takes the top of M3's card elevation scale
— Level4 (8dp) elevated, Level3 (6dp) filled and outlined — under the
16% dragged state layer from tokens/state.css. The row's hover and press
rules now exclude a dragged card, so the card a pointer is holding stays
at its dragged level. The application owns the attribute: nothing in the
browser tells a card it is being carried.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
This commit is contained in:
Andreas Reinhold / reini
2026-09-14 06:34:35 +02:00
co-authored by Claude Fable 5.1
parent 927e439064
commit b1f64771f3
5 changed files with 60 additions and 4 deletions
+26 -4
View File
@@ -86,23 +86,45 @@
}
@media (hover: hover) {
[data-card][data-list-row]:hover:not(:has(:is(a, button, input, select, textarea, label, summary):not([data-list-open]):hover)) {
[data-card][data-list-row]:not([data-dragged]):hover:not(:has(:is(a, button, input, select, textarea, label, summary):not([data-list-open]):hover)) {
box-shadow: var(--md-sys-elevation-1), inset 0 0 0 100vmax color-mix(in srgb, var(--md-sys-color-on-surface) 8%, transparent);
}
[data-card='elevated'][data-list-row]:hover:not(:has(:is(a, button, input, select, textarea, label, summary):not([data-list-open]):hover)) {
[data-card='elevated'][data-list-row]:not([data-dragged]):hover:not(:has(:is(a, button, input, select, textarea, label, summary):not([data-list-open]):hover)) {
box-shadow: var(--md-sys-elevation-2), inset 0 0 0 100vmax color-mix(in srgb, var(--md-sys-color-on-surface) 8%, transparent);
}
}
[data-card][data-list-row]:active:not(:has(:is(a, button, input, select, textarea, label, summary):not([data-list-open]):active)) {
[data-card][data-list-row]:not([data-dragged]):active:not(:has(:is(a, button, input, select, textarea, label, summary):not([data-list-open]):active)) {
box-shadow: inset 0 0 0 100vmax color-mix(in srgb, var(--md-sys-color-on-surface) 10%, transparent);
}
[data-card='elevated'][data-list-row]:active:not(:has(:is(a, button, input, select, textarea, label, summary):not([data-list-open]):active)) {
[data-card='elevated'][data-list-row]:not([data-dragged]):active:not(:has(:is(a, button, input, select, textarea, label, summary):not([data-list-open]):active)) {
box-shadow: var(--md-sys-elevation-1), inset 0 0 0 100vmax color-mix(in srgb, var(--md-sys-color-on-surface) 10%, transparent);
}
/*
* M3's dragged card (`<x-card data-dragged>`): the highest elevation a card ever takes, under the
* 16% dragged state layer. The token files' per-state elevation — elevated Level4 (8dp), filled
* and outlined Level3 (6dp) — and md.sys.state.dragged-state-layer-opacity from tokens/state.css
* (docs/reference/m3/components-actions-communication-containment.md § Cards → Specs). Written as
* one box-shadow with the row's other states, and named in the hover and press rules above so the
* card a pointer is holding stays at its dragged level rather than falling back to a press.
*
* The application owns the attribute: it sets `data-dragged` when its drag starts and takes it off
* on drop, since nothing in the browser tells a card it is being carried. M3 asks for a
* single-pointer alternative beside any drag, so give the card a menu with the same actions.
*/
[data-card][data-dragged] {
box-shadow: var(--md-sys-elevation-3),
inset 0 0 0 100vmax color-mix(in srgb, var(--md-sys-color-on-surface) calc(var(--md-sys-state-dragged-state-layer-opacity) * 100%), transparent);
}
[data-card='elevated'][data-dragged] {
box-shadow: var(--md-sys-elevation-4),
inset 0 0 0 100vmax color-mix(in srgb, var(--md-sys-color-on-surface) calc(var(--md-sys-state-dragged-state-layer-opacity) * 100%), transparent);
}
/* Focus is on the card itself when the card is the tab stop (`<x-card actionable>`). */
[data-card][data-list-row]:is(:focus-visible, :has([data-list-open]:focus-visible)) {
outline: 3px solid var(--md-sys-color-secondary);