11 Commits
Author SHA1 Message Date
Andreas Reinhold / reiniandClaude Opus 5 cab8d804a5 Reveal a cut-off carousel item under reduced motion
Plan step 36 review of a149970d. Reduced motion writes a zero inset for
every item, so isMasked() never called one partly shown and a press on
an item the row cut off did nothing. It now reads the item's box against
the scrollport there, as it already did for multi-aspect, and the
browser test the rewrite added to document the gap asserts the reveal
instead (plan step 32's owed test).

A focused item's inset ring is 3px in again: Tailwind's
-outline-offset-3 is 3px, not the 12px the rewrite wrote.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
2026-09-14 22:05:40 +02:00
Andreas Reinhold / reiniandClaude Sonnet 5 a149970dd0 Rewrite the carousel without Tailwind
Plan step 36 (containment group): <x-carousel>'s and <x-carousel-item>'s
class lists move into resources/css/components/carousel.css and
carousel-item.css, keyed on data-md-carousel (its value is the layout)
and the parts' data-md-carousel-* hooks (-probe, -scroller, -controls
with "auto"/"always", -previous/-next, -item, -surface, -content,
-label/-label-text). Every selector uses a `>` combinator rather than a
bare descendant one, because a carousel item can itself hold a nested
carousel whose own root would otherwise match its parent's layout
rules too (list.css already solves the same problem for segmented
list rows).

Behaviour is unchanged: resources/js/carousel.js (the keyline maths,
C-05's reduced-motion fix, C-11's vertical full-screen layout, C-12's
padding, C-18's item-as-tab-stop, the multi-aspect layout) is touched
only where it reads or writes the renamed hooks and dataset
properties; every inline custom property it writes
(--material-carousel-*) is untouched. The item renders the shared
md-focus-ring class (foundation/interaction.css) instead of a
hand-rolled ring, refined to an inset offset since an outward one
would draw under the neighbouring item. The previous/next buttons
mirror whole in RTL from carousel.css rather than through
<x-icon mirror-rtl>, which <x-button icon> has no prop to reach (a
component outside this batch); the technique matches how the
Tailwind-era markup already mirrored the whole button. The overlay
label's literal white ink over the scrim (C-25) is kept, with the
same reasoning as before.

Hooks renamed data-material-carousel(-item/-surface/-content/-label)
-> data-md-carousel(-item/-surface/-content/-label), data-padding(-end)
-> data-md-padding(-end), data-centered -> data-md-centered, updated in
the same commit: resources/js/carousel.js, tests/Feature/Components/
CarouselTest.php (rewritten on data-md-* and ComponentStylesheet) and
tests/Browser/CarouselTest.php. Browser tests owed by
docs/plans/material-3-browser-tests.md, added but not run: the
multi-aspect carousel's previous/next, arrow keys, Home and End
(scoped by data-md-carousel="multi-aspect" rather than a position in
the showcase, so reordering its examples cannot silently mis-target
the wrong carousel); a reduced-motion click on an item cut off only by
the row's own edge, which documents rather than fixes a real gap —
isMasked()'s inset check is always false once C-05 zeroes every
item's inset, so the click-to-reveal affordance does not fire there
(found by the Chromium baseline, step 32; fixing it is outside a hook
rename).

Imported from the Containment block of components.css.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
2026-09-14 21:44:38 +02:00
Andreas Reinhold / reiniandClaude Opus 5 56e985c40f Leave a carousel item at the padded focal size where it is
Plan step 32, found by the Browser suite. Content padding (16dp by default
since containment.md C-12) shrinks every keyline near the ends, so large
items resting at the start or the end carry a small inset. Bring-into-view
took any inset over half a pixel for "not fully open", so focusing or
pressing a large item at rest scrolled the row to it. The focal keyline's
own inset at the current scroll is the threshold now. The press test also
checks that focusing and pressing an open item moves nothing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
2026-09-14 13:36:26 +02:00
Andreas Reinhold / reiniandClaude Opus 5 0d6b0035ed Bring a pressed carousel item into focus, though it is a tab stop
Plan step 32, found by the Browser suite. Since containment.md C-18 put
the tab stop on each item, the item itself matches the selector for
interactive controls, so every press on an item read as a press on a
control inside it and never scrolled a partly hidden item into focus. A
mouse press still worked through the focus it gives the item; an activation
that clicks without focusing, as a test or assistive technology does, did
nothing. Only a control other than the item counts now.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
2026-09-14 13:36:12 +02:00
Andreas Reinhold / reiniandClaude Opus 5 bb9473b0b2 Add M3's uncontained multi-aspect-ratio carousel
Plan step 23, containment.md § Missing ("Carousel: the uncontained
multi-aspect-ratio layout"). `<x-carousel layout="multi-aspect">` is the
layout M3 added in November 2025: each `<x-carousel-item aspect="…">`
keeps its own ratio at the row's fixed height, held inside M3's 9:16 to
16:9 range (a square by default), with 16dp leading padding, 8dp gaps,
the extra-large corner and uncontained (default) scrolling.

The keyline machinery does not fit it: an Arrangement counts items of
one size each, and every snap position and mask follows from that size.
So the layout is a plain flex row, unmasked, and carousel.js only
measures each item's resting position off the DOM, which keeps the
previous/next buttons, the arrow keys, Home/End and bring-into-view
working. The header and SKILL.md say so.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
2026-09-14 07:14:29 +02:00
Andreas Reinhold / reiniandClaude Opus 5 5e58dd5449 Scroll the full-screen carousel down the page, as M3's does
Plan step 19, containment.md C-11. The full-screen layout was another
horizontal row with a 28px corner and a mask, where M3's "shows one
edge-to-edge large item at a time and scrolls vertically". It is now a
vertical scroll-snap column: items fill the row with no corner and no
mask, 16dp apart, no end padding, the previous/next buttons point up and
down and the arrow keys are Up and Down. The row is capped at the 840px
medium window, which with the portrait rule in the header comment is as
far as CSS can hold M3's "compact and medium, portrait only". The ported
FullScreenCarouselStrategy keylines go with it: nothing measures them
any more.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
2026-09-14 06:17:31 +02:00
Andreas Reinhold / reiniandClaude Opus 5 9875ba156e Put the carousel's tab stop on its items, as M3 asks
Plan step 19, containment.md C-18. The row was the focusable `region`
and the items were not focusable at all, which is the thing M3's
accessibility page draws a Don't for: "use Tab to place initial focus on
the first carousel item", "avoid focusing on the carousel container".
Each item is now `tabindex="0"` with the focus ring drawn inside it, the
row is out of the tab order, and from a focused item the arrows move one
item (moving focus with them), Home and End go to the ends, and Space or
Enter opens one that is not fully in view.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
2026-09-14 06:14:34 +02:00
Andreas Reinhold / reiniandClaude Opus 5 ad37862a9f Give the carousel M3's end padding and a role for its label
Plan step 19, containment.md C-12 and C-25. `padding` defaulted to 0
where M3's specs table gives every layout 16dp of leading and trailing
padding — leading only for `uncontained`, none for the edge-to-edge
`full-screen` — so the default is 16 and each layout now says which
ends it applies to (`data-padding`, `data-padding-end`). The row gains
the 8dp above and below that goes with it, without shortening the
items. The overlay label is `inverse-on-surface` instead of a literal
`white`, which no scheme or contrast profile follows.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
2026-09-14 06:12:24 +02:00
Andreas Reinhold / reiniandClaude Opus 5 f895b553b6 Leave carousel items unmasked under reduced motion
Plan step 12, containment.md C-05. Only the parallax half of M3's rule
was honoured: the mask was still written every frame, so items kept
growing and shrinking between the keylines. Under reduced motion the
inset, the shift and the label fade are now all zero, so every item
stays at the strategy's large size and the keylines only decide where
the row snaps. `--material-carousel-pin` went with it: with no mask
there is nothing to pin the content to.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
2026-09-14 05:59:15 +02:00
Andreas Reinhold / reiniandClaude Opus 5 7ebcb0565f Send a carousel scroll on when it comes to rest short of its item
tests / lint (push) Has been cancelled
tests / feature (8.4) (push) Has been cancelled
tests / feature (8.5) (push) Has been cancelled
tests / browser (chrome, chromium) (push) Has been cancelled
tests / browser (firefox, firefox) (push) Has been cancelled
tests / browser (safari, webkit) (push) Has been cancelled
CI's WebKit twice left the carousel on its first item after Next. A
scroll started by the buttons or keys that comes to rest anywhere but
its item is now sent there once more; a scroll the person starts
themselves cancels that.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
2026-09-13 09:40:40 +02:00
Andreas Reinhold / reiniandClaude Opus 5 ad724662ca Add the M3 Expressive carousel
tests / browser (chrome, chromium) (push) Successful in 2m24s
tests / browser (firefox, firefox) (push) Successful in 2m48s
tests / browser (safari, webkit) (push) Successful in 3m42s
tests / lint (push) Successful in 1m0s
tests / feature (8.4) (push) Successful in 1m7s
tests / feature (8.5) (push) Successful in 1m2s
Multi-browse, hero, uncontained and full-screen layouts on a native
scroll-snap row, with Compose's keyline maths masking each item as it
scrolls. Completes Phase 5.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
2026-09-13 07:40:30 +02:00