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
This commit is contained in:
co-authored by
Claude Opus 5
parent
0d6b0035ed
commit
56e985c40f
@@ -808,6 +808,7 @@ document.addEventListener('alpine:init', () => {
|
|||||||
strategy: null,
|
strategy: null,
|
||||||
snaps: [],
|
snaps: [],
|
||||||
maxScroll: 0,
|
maxScroll: 0,
|
||||||
|
focalInset: 0,
|
||||||
rtl: false,
|
rtl: false,
|
||||||
vertical: false,
|
vertical: false,
|
||||||
measured: false,
|
measured: false,
|
||||||
@@ -1027,6 +1028,11 @@ document.addEventListener('alpine:init', () => {
|
|||||||
// moved or faded, so every item stays at strategy.itemSize.
|
// moved or faded, so every item stays at strategy.itemSize.
|
||||||
const still = state.reducedMotion.matches
|
const still = state.reducedMotion.matches
|
||||||
|
|
||||||
|
// Content padding shrinks every keyline near the ends (Strategy.kt's
|
||||||
|
// createShiftedKeylineListForContentPadding), so an item at the focal size can still
|
||||||
|
// carry a small inset there: that inset, not zero, is "fully open" for isMasked().
|
||||||
|
state.focalInset = still ? 0 : clamp((size - (firstFocal(keylines)?.size ?? size)) / 2, 0, size / 2)
|
||||||
|
|
||||||
state.items.forEach((item, index) => {
|
state.items.forEach((item, index) => {
|
||||||
const center = index * (size + strategy.itemSpacing) + size / 2 - scroll
|
const center = index * (size + strategy.itemSpacing) + size / 2 - scroll
|
||||||
const before = keylineBefore(keylines, center)
|
const before = keylineBefore(keylines, center)
|
||||||
@@ -1220,7 +1226,7 @@ document.addEventListener('alpine:init', () => {
|
|||||||
return item.left < row.left - 1 || item.right > row.right + 1
|
return item.left < row.left - 1 || item.right > row.right + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
return parseFloat(state.items[index].surface.style.getPropertyValue('--material-carousel-inset')) > 0.5
|
return parseFloat(state.items[index].surface.style.getPropertyValue('--material-carousel-inset')) > state.focalInset + 0.5
|
||||||
},
|
},
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
|
|||||||
@@ -145,9 +145,18 @@ it('snaps a scroll that stops between items onto an item', function () {
|
|||||||
JS));
|
JS));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('brings a partly hidden item into focus when it is pressed', function () {
|
it('brings a partly hidden item into focus when it is pressed, and leaves an open one where it is', function () {
|
||||||
$page = carouselShowcase()
|
$page = carouselShowcase()
|
||||||
->assertScript(onCarousel(0, 'return ! open(4)'));
|
->assertScript(onCarousel(0, 'return open(1) && ! open(4)'));
|
||||||
|
|
||||||
|
// Item 1 rests at the large size, short only of the content padding's share: focusing or
|
||||||
|
// pressing it moves nothing.
|
||||||
|
$page->script(onCarousel(0, 'items[1].focus(); items[1].querySelector(\'[data-material-carousel-content]\').click()'));
|
||||||
|
|
||||||
|
$page->assertScript(onCarousel(0, <<<'JS'
|
||||||
|
await pause(400)
|
||||||
|
return scroller.scrollLeft === 0 && open(1)
|
||||||
|
JS));
|
||||||
|
|
||||||
$page->script(onCarousel(0, 'items[4].querySelector(\'[data-material-carousel-content]\').click()'));
|
$page->script(onCarousel(0, 'items[4].querySelector(\'[data-material-carousel-content]\').click()'));
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user