From 1836b8d11202fe74b7bb492388d3c173677307af Mon Sep 17 00:00:00 2001 From: Mark Provo Kluit Date: Fri, 2 Oct 2026 16:11:34 +0200 Subject: [PATCH 1/2] refactor(material): split CSS for carousel variants to separate files --- .../MaterialCarouselItemHolder.module.css | 326 +----------------- ...elItemHolderVariantCenteredHero.module.css | 132 +++++++ ...uselItemHolderVariantFullScreen.module.css | 8 + ...alCarouselItemHolderVariantHero.module.css | 59 ++++ ...selItemHolderVariantMultiBrowse.module.css | 64 ++++ ...selItemHolderVariantUncontained.module.css | 38 ++ 6 files changed, 311 insertions(+), 316 deletions(-) create mode 100644 packages/material/src/components/carousel/styling/variants/MaterialCarouselItemHolderVariantCenteredHero.module.css create mode 100644 packages/material/src/components/carousel/styling/variants/MaterialCarouselItemHolderVariantFullScreen.module.css create mode 100644 packages/material/src/components/carousel/styling/variants/MaterialCarouselItemHolderVariantHero.module.css create mode 100644 packages/material/src/components/carousel/styling/variants/MaterialCarouselItemHolderVariantMultiBrowse.module.css create mode 100644 packages/material/src/components/carousel/styling/variants/MaterialCarouselItemHolderVariantUncontained.module.css diff --git a/packages/material/src/components/carousel/MaterialCarouselItemHolder.module.css b/packages/material/src/components/carousel/MaterialCarouselItemHolder.module.css index 44f65369..c273e778 100644 --- a/packages/material/src/components/carousel/MaterialCarouselItemHolder.module.css +++ b/packages/material/src/components/carousel/MaterialCarouselItemHolder.module.css @@ -1,3 +1,9 @@ +@import './styling/variants/MaterialCarouselItemHolderVariantCenteredHero.module.css'; +@import './styling/variants/MaterialCarouselItemHolderVariantFullScreen.module.css'; +@import './styling/variants/MaterialCarouselItemHolderVariantHero.module.css'; +@import './styling/variants/MaterialCarouselItemHolderVariantMultiBrowse.module.css'; +@import './styling/variants/MaterialCarouselItemHolderVariantUncontained.module.css'; + @layer material.components { :root { --md-comp-carousel-item-small-min-width: 40px; @@ -36,44 +42,6 @@ initial-value: 0px; } - @property --translate { - syntax: ''; - inherits: true; - initial-value: 0px; - } - - @keyframes multi-browse-expand-medium-item { - from { - --max-width: var(--md-comp-carousel-size-small); - margin-inline-end: calc(-1 * (var(--md-comp-carousel-size-large) - var(--md-comp-carousel-size-small))); - } - to { - --max-width: var(--md-comp-carousel-size-medium); - margin-inline-end: calc(-1 * (var(--md-comp-carousel-size-large) - var(--md-comp-carousel-size-medium))); - } - } - - @keyframes multi-browse-expand-large-item { - from { - --max-width: var(--md-comp-carousel-size-medium); - margin-inline-end: calc(-1 * (var(--md-comp-carousel-size-large) - var(--md-comp-carousel-size-medium))); - } - to { - --max-width: var(--md-comp-carousel-size-large); - margin-inline-end: 0; - } - } - - @keyframes multi-browse-expand-last-item { - from { - --max-width: 0; - } - - to { - --max-width: var(--md-comp-carousel-size-large); - } - } - @keyframes collapse-item { from { --max-width: var(--md-comp-carousel-size-large); @@ -88,68 +56,6 @@ } } - @keyframes uncontained-expand-item { - from { - --max-width: var(--md-comp-carousel-size-medium); - } - - to { - --max-width: var(--md-comp-carousel-size-large); - } - } - - @keyframes hero-expand-item { - from { - --max-width: var(--md-comp-carousel-size-small); - } - - to { - --max-width: var(--md-comp-carousel-size-large); - } - } - - @keyframes centered-hero-expand-item { - from { - --max-width: var(--md-comp-carousel-size-small); - } - - to { - --max-width: var(--md-comp-carousel-size-large); - } - } - - @keyframes centered-hero-expand-item-translate { - from { - --translate: calc(-1 * (var(--md-comp-carousel-size-large) - var(--md-comp-carousel-size-small))); - } - - to { - --translate: 0; - } - } - - @keyframes centered-hero-expand-last-item { - from { - --max-width: 0; - } - - to { - --max-width: var(--md-comp-carousel-size-large); - } - } - - @keyframes centered-hero-collapse-item { - from { - --max-width: var(--md-comp-carousel-size-large); - --translate: 0; - } - - to { - --max-width: calc(var(--md-comp-carousel-size-small)); - --translate: calc(var(--md-comp-carousel-size-large) - var(--md-comp-carousel-size-small)); - } - } - .container { --md-comp-carousel-size-small: var(--md-comp-carousel-item-small-max-width); @@ -204,228 +110,16 @@ pointer-events: none; } - /**********************************************************************/ - /* Multi-browse variant */ - /**********************************************************************/ - - sm-carousel[data-variant='multi-browse'] .container { - --container-width: 100cqi; - - /* Animated by @keyframes needed for WebKit */ - /* margin-inline-end: calc(-1 * (var(--min-width) - max(var(--md-comp-carousel-size-small), var(--max-width)))); */ - - @supports (animation-timeline: view()) { - animation: - multi-browse-expand-medium-item linear both, - multi-browse-expand-large-item linear forwards, - collapse-item linear forwards; - animation-timeline: view(x); - animation-range-start: - var(--md-comp-carousel-size-small), - var(--md-comp-carousel-size-large), - exit 0%; - animation-range-end: - var(--md-comp-carousel-size-large), - calc(2 * var(--md-comp-carousel-size-large) + var(--md-comp-carousel-container-gap)), - exit 100%; - - &:last-child { - animation: multi-browse-expand-last-item linear both; - animation-timeline: view(x); - animation-range-start: normal; - animation-range-end: calc(1 * var(--md-comp-carousel-size-large)); - margin-inline-end: 0; - } - } - } - - /**********************************************************************/ - /* Uncontained variant */ - /**********************************************************************/ - - sm-carousel[data-variant='uncontained'] .container { - --container-width: calc(100cqi - var(--md-comp-carousel-size-small)); - - margin-inline-end: calc(-1 * (var(--min-width) - max(var(--md-comp-carousel-size-medium), var(--max-width)))); - - @supports (animation-timeline: view()) { - animation: - uncontained-expand-item linear both, - collapse-item linear forwards; - animation-timeline: view(x); - animation-range-start: - entry 0%, - exit 0%; - animation-range-end: - var(--md-comp-carousel-size-large), - exit 100%; - - &:last-child { - animation: uncontained-expand-item linear both; - animation-timeline: view(x); - animation-range-start: entry 0%; - animation-range-end: var(--md-comp-carousel-size-large); - margin-inline-end: 0; - } - } - } - - /**********************************************************************/ - /* Hero variant */ - /**********************************************************************/ - - sm-carousel[data-variant='hero'] .container { - --container-width: calc(100cqi - var(--md-comp-carousel-size-small) - var(--md-comp-carousel-container-gap)); - - @container (width < 600px) { - --md-comp-carousel-count-large: 1; - } - - margin-inline-end: calc(-1 * (var(--min-width) - max(var(--md-comp-carousel-size-small), var(--max-width)))); - - @supports (animation-timeline: view()) { - animation: - hero-expand-item linear both, - collapse-item linear forwards; - animation-timeline: view(x); - animation-range-start: - var(--md-comp-carousel-size-small), - exit 0%; - animation-range-end: - calc( - var(--md-comp-carousel-size-large) + var(--md-comp-carousel-container-gap) + - var(--md-comp-carousel-size-small) - ), - exit 100%; - - /* Make sure the second last item ends at the leading edge of the scroll container - so that it doesn't continue outside the container. This causes the last two items - to have shapes that are the reverse (small + large) of the first two (large + small) */ - &:nth-last-child(2) { - animation-range-end: - calc( - var(--md-comp-carousel-size-large) + var(--md-comp-carousel-container-gap) + - var(--md-comp-carousel-size-small) - ), - exit calc(100% - var(--md-comp-carousel-size-small)); - } - - &:last-child { - animation: hero-expand-item linear both; - animation-timeline: view(x); - animation-range-start: var(--md-comp-carousel-size-small); - animation-range-end: var(--md-comp-carousel-size-large); - margin-inline-end: 0; - } - } - } - - /**********************************************************************/ - /* Centered-aligned hero variant */ - /**********************************************************************/ - - sm-carousel[data-variant='centered-hero'] .container { - --container-width: calc( - 100cqi - 2 * var(--md-comp-carousel-size-small) - 2 * var(--md-comp-carousel-container-gap) - ); - - @container (width < 600px) { - --md-comp-carousel-count-large: 1; - } - - @supports (animation-timeline: view()) { - animation: - centered-hero-expand-item linear both, - centered-hero-expand-item-translate linear both, - centered-hero-collapse-item linear forwards; - animation-timeline: view(x); - animation-range-start: - entry var(--md-comp-carousel-size-small), - cover - calc( - -1 * - ( - var(--md-comp-carousel-size-large) - var(--md-comp-carousel-size-small) + - var(--md-comp-carousel-container-gap) - ) - ), - exit 0%; - animation-range-end: - cover - calc( - var(--md-comp-carousel-size-large) + var(--md-comp-carousel-container-gap) + - var(--md-comp-carousel-size-small) - ), - entry var(--md-comp-carousel-size-small), - exit calc(var(--md-comp-carousel-size-large) - var(--md-comp-carousel-size-small)); - - &:first-child { - --container-width: calc(100cqi - var(--md-comp-carousel-size-small) - var(--md-comp-carousel-container-gap)); - - animation: - centered-hero-expand-item linear both, - centered-hero-collapse-item linear forwards; - animation-timeline: view(x); - animation-range-start: - entry var(--md-comp-carousel-size-small), - exit 0%; - animation-range-end: - cover - calc( - var(--md-comp-carousel-size-large) + var(--md-comp-carousel-container-gap) + - var(--md-comp-carousel-size-small) - ), - exit calc(var(--md-comp-carousel-size-large) - var(--md-comp-carousel-size-small)); - } - - &:last-child { - --container-width: calc(100cqi - var(--md-comp-carousel-size-small) - var(--md-comp-carousel-container-gap)); - - animation: - centered-hero-expand-item linear both, - centered-hero-expand-item-translate linear both; - animation-timeline: view(x); - animation-range-start: - entry var(--md-comp-carousel-size-small), - cover - calc( - -1 * - ( - var(--md-comp-carousel-size-large) - var(--md-comp-carousel-size-small) + 2 * - var(--md-comp-carousel-container-gap) - ) - ); - animation-range-end: - var(--md-comp-carousel-size-large), - entry var(--md-comp-carousel-size-small); - } - - sm-carousel-item { - translate: var(--translate); - } - scroll-margin-inline-start: calc(var(--md-comp-carousel-container-gap) + var(--md-comp-carousel-size-small)); - } - } - - /**********************************************************************/ - /* Full-screen variant */ - /**********************************************************************/ - - sm-carousel[data-variant='full-screen'] .container { - --md-comp-carousel-count-large: 1; - --container-width: 100cqi; - - min-height: 100dvh; - } - /**********************************************************************/ /* Reduced motion */ /**********************************************************************/ @media (prefers-reduced-motion: reduce) { .container { - /* Disable animation so that items do not expand and collapse when - they enter the scroll port */ + /** + * Disable animation so that items do not expand and collapse when + * they enter the scroll port + */ animation: none !important; } diff --git a/packages/material/src/components/carousel/styling/variants/MaterialCarouselItemHolderVariantCenteredHero.module.css b/packages/material/src/components/carousel/styling/variants/MaterialCarouselItemHolderVariantCenteredHero.module.css new file mode 100644 index 00000000..e6e6eabb --- /dev/null +++ b/packages/material/src/components/carousel/styling/variants/MaterialCarouselItemHolderVariantCenteredHero.module.css @@ -0,0 +1,132 @@ +@layer material.components { + @property --translate { + syntax: ''; + inherits: true; + initial-value: 0px; + } + + @keyframes centered-hero-expand-item { + from { + --max-width: var(--md-comp-carousel-size-small); + } + + to { + --max-width: var(--md-comp-carousel-size-large); + } + } + + @keyframes centered-hero-expand-item-translate { + from { + --translate: calc(-1 * (var(--md-comp-carousel-size-large) - var(--md-comp-carousel-size-small))); + } + + to { + --translate: 0; + } + } + + @keyframes centered-hero-expand-last-item { + from { + --max-width: 0; + } + + to { + --max-width: var(--md-comp-carousel-size-large); + } + } + + @keyframes centered-hero-collapse-item { + from { + --max-width: var(--md-comp-carousel-size-large); + --translate: 0; + } + + to { + --max-width: calc(var(--md-comp-carousel-size-small)); + --translate: calc(var(--md-comp-carousel-size-large) - var(--md-comp-carousel-size-small)); + } + } + + sm-carousel[data-variant='centered-hero'] .container { + --container-width: calc( + 100cqi - 2 * var(--md-comp-carousel-size-small) - 2 * var(--md-comp-carousel-container-gap) + ); + + @container (width < 600px) { + --md-comp-carousel-count-large: 1; + } + + @supports (animation-timeline: view()) { + animation: + centered-hero-expand-item linear both, + centered-hero-expand-item-translate linear both, + centered-hero-collapse-item linear forwards; + animation-timeline: view(x); + animation-range-start: + entry var(--md-comp-carousel-size-small), + cover + calc( + -1 * + ( + var(--md-comp-carousel-size-large) - var(--md-comp-carousel-size-small) + + var(--md-comp-carousel-container-gap) + ) + ), + exit 0%; + animation-range-end: + cover + calc( + var(--md-comp-carousel-size-large) + var(--md-comp-carousel-container-gap) + + var(--md-comp-carousel-size-small) + ), + entry var(--md-comp-carousel-size-small), + exit calc(var(--md-comp-carousel-size-large) - var(--md-comp-carousel-size-small)); + + &:first-child { + --container-width: calc(100cqi - var(--md-comp-carousel-size-small) - var(--md-comp-carousel-container-gap)); + + animation: + centered-hero-expand-item linear both, + centered-hero-collapse-item linear forwards; + animation-timeline: view(x); + animation-range-start: + entry var(--md-comp-carousel-size-small), + exit 0%; + animation-range-end: + cover + calc( + var(--md-comp-carousel-size-large) + var(--md-comp-carousel-container-gap) + + var(--md-comp-carousel-size-small) + ), + exit calc(var(--md-comp-carousel-size-large) - var(--md-comp-carousel-size-small)); + } + + &:last-child { + --container-width: calc(100cqi - var(--md-comp-carousel-size-small) - var(--md-comp-carousel-container-gap)); + + animation: + centered-hero-expand-item linear both, + centered-hero-expand-item-translate linear both; + animation-timeline: view(x); + animation-range-start: + entry var(--md-comp-carousel-size-small), + cover + calc( + -1 * + ( + var(--md-comp-carousel-size-large) - var(--md-comp-carousel-size-small) + 2 * + var(--md-comp-carousel-container-gap) + ) + ); + animation-range-end: + var(--md-comp-carousel-size-large), + entry var(--md-comp-carousel-size-small); + } + + sm-carousel-item { + translate: var(--translate); + } + scroll-margin-inline-start: calc(var(--md-comp-carousel-container-gap) + var(--md-comp-carousel-size-small)); + } + } +} diff --git a/packages/material/src/components/carousel/styling/variants/MaterialCarouselItemHolderVariantFullScreen.module.css b/packages/material/src/components/carousel/styling/variants/MaterialCarouselItemHolderVariantFullScreen.module.css new file mode 100644 index 00000000..bcdc054a --- /dev/null +++ b/packages/material/src/components/carousel/styling/variants/MaterialCarouselItemHolderVariantFullScreen.module.css @@ -0,0 +1,8 @@ +@layer material.components { + sm-carousel[data-variant='full-screen'] .container { + --md-comp-carousel-count-large: 1; + --container-width: 100cqi; + + min-height: 100dvh; + } +} diff --git a/packages/material/src/components/carousel/styling/variants/MaterialCarouselItemHolderVariantHero.module.css b/packages/material/src/components/carousel/styling/variants/MaterialCarouselItemHolderVariantHero.module.css new file mode 100644 index 00000000..2df85f74 --- /dev/null +++ b/packages/material/src/components/carousel/styling/variants/MaterialCarouselItemHolderVariantHero.module.css @@ -0,0 +1,59 @@ +@layer material.components { + @keyframes hero-expand-item { + from { + --max-width: var(--md-comp-carousel-size-small); + } + + to { + --max-width: var(--md-comp-carousel-size-large); + } + } + + sm-carousel[data-variant='hero'] .container { + --container-width: calc(100cqi - var(--md-comp-carousel-size-small) - var(--md-comp-carousel-container-gap)); + + @container (width < 600px) { + --md-comp-carousel-count-large: 1; + } + + margin-inline-end: calc(-1 * (var(--min-width) - max(var(--md-comp-carousel-size-small), var(--max-width)))); + + @supports (animation-timeline: view()) { + animation: + hero-expand-item linear both, + collapse-item linear forwards; + animation-timeline: view(x); + animation-range-start: + var(--md-comp-carousel-size-small), + exit 0%; + animation-range-end: + calc( + var(--md-comp-carousel-size-large) + var(--md-comp-carousel-container-gap) + + var(--md-comp-carousel-size-small) + ), + exit 100%; + + /** + * Make sure the second last item ends at the leading edge of the scroll container + * so that it doesn't continue outside the container. This causes the last two items + * to have shapes that are the reverse (small + large) of the first two (large + small) + */ + &:nth-last-child(2) { + animation-range-end: + calc( + var(--md-comp-carousel-size-large) + var(--md-comp-carousel-container-gap) + + var(--md-comp-carousel-size-small) + ), + exit calc(100% - var(--md-comp-carousel-size-small)); + } + + &:last-child { + animation: hero-expand-item linear both; + animation-timeline: view(x); + animation-range-start: var(--md-comp-carousel-size-small); + animation-range-end: var(--md-comp-carousel-size-large); + margin-inline-end: 0; + } + } + } +} diff --git a/packages/material/src/components/carousel/styling/variants/MaterialCarouselItemHolderVariantMultiBrowse.module.css b/packages/material/src/components/carousel/styling/variants/MaterialCarouselItemHolderVariantMultiBrowse.module.css new file mode 100644 index 00000000..d3f790b4 --- /dev/null +++ b/packages/material/src/components/carousel/styling/variants/MaterialCarouselItemHolderVariantMultiBrowse.module.css @@ -0,0 +1,64 @@ +@layer material.components { + @keyframes multi-browse-expand-medium-item { + from { + --max-width: var(--md-comp-carousel-size-small); + margin-inline-end: calc(-1 * (var(--md-comp-carousel-size-large) - var(--md-comp-carousel-size-small))); + } + to { + --max-width: var(--md-comp-carousel-size-medium); + margin-inline-end: calc(-1 * (var(--md-comp-carousel-size-large) - var(--md-comp-carousel-size-medium))); + } + } + + @keyframes multi-browse-expand-large-item { + from { + --max-width: var(--md-comp-carousel-size-medium); + margin-inline-end: calc(-1 * (var(--md-comp-carousel-size-large) - var(--md-comp-carousel-size-medium))); + } + to { + --max-width: var(--md-comp-carousel-size-large); + margin-inline-end: 0; + } + } + + @keyframes multi-browse-expand-last-item { + from { + --max-width: 0; + } + + to { + --max-width: var(--md-comp-carousel-size-large); + } + } + + sm-carousel[data-variant='multi-browse'] .container { + --container-width: 100cqi; + + /* Animated by @keyframes needed for WebKit */ + /* margin-inline-end: calc(-1 * (var(--min-width) - max(var(--md-comp-carousel-size-small), var(--max-width)))); */ + + @supports (animation-timeline: view()) { + animation: + multi-browse-expand-medium-item linear both, + multi-browse-expand-large-item linear forwards, + collapse-item linear forwards; + animation-timeline: view(x); + animation-range-start: + var(--md-comp-carousel-size-small), + var(--md-comp-carousel-size-large), + exit 0%; + animation-range-end: + var(--md-comp-carousel-size-large), + calc(2 * var(--md-comp-carousel-size-large) + var(--md-comp-carousel-container-gap)), + exit 100%; + + &:last-child { + animation: multi-browse-expand-last-item linear both; + animation-timeline: view(x); + animation-range-start: normal; + animation-range-end: calc(1 * var(--md-comp-carousel-size-large)); + margin-inline-end: 0; + } + } + } +} diff --git a/packages/material/src/components/carousel/styling/variants/MaterialCarouselItemHolderVariantUncontained.module.css b/packages/material/src/components/carousel/styling/variants/MaterialCarouselItemHolderVariantUncontained.module.css new file mode 100644 index 00000000..25cc5a6f --- /dev/null +++ b/packages/material/src/components/carousel/styling/variants/MaterialCarouselItemHolderVariantUncontained.module.css @@ -0,0 +1,38 @@ +@layer material.components { + @keyframes uncontained-expand-item { + from { + --max-width: var(--md-comp-carousel-size-medium); + } + + to { + --max-width: var(--md-comp-carousel-size-large); + } + } + + sm-carousel[data-variant='uncontained'] .container { + --container-width: calc(100cqi - var(--md-comp-carousel-size-small)); + + margin-inline-end: calc(-1 * (var(--min-width) - max(var(--md-comp-carousel-size-medium), var(--max-width)))); + + @supports (animation-timeline: view()) { + animation: + uncontained-expand-item linear both, + collapse-item linear forwards; + animation-timeline: view(x); + animation-range-start: + entry 0%, + exit 0%; + animation-range-end: + var(--md-comp-carousel-size-large), + exit 100%; + + &:last-child { + animation: uncontained-expand-item linear both; + animation-timeline: view(x); + animation-range-start: entry 0%; + animation-range-end: var(--md-comp-carousel-size-large); + margin-inline-end: 0; + } + } + } +} From 0838fae1ed7dde635f2549423994222331014c6c Mon Sep 17 00:00:00 2001 From: Mark Provo Kluit Date: Fri, 2 Oct 2026 16:13:06 +0200 Subject: [PATCH 2/2] fix(material): ignore long-press on carousel items --- .changeset/silly-flies-sort.md | 5 +++++ .../src/components/carousel/MaterialCarouselItem.tsx | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 .changeset/silly-flies-sort.md diff --git a/.changeset/silly-flies-sort.md b/.changeset/silly-flies-sort.md new file mode 100644 index 00000000..e551d256 --- /dev/null +++ b/.changeset/silly-flies-sort.md @@ -0,0 +1,5 @@ +--- +'@solidmaterial/material': patch +--- + +Ignore long-press on carousel items diff --git a/packages/material/src/components/carousel/MaterialCarouselItem.tsx b/packages/material/src/components/carousel/MaterialCarouselItem.tsx index bb058268..a39d33a1 100644 --- a/packages/material/src/components/carousel/MaterialCarouselItem.tsx +++ b/packages/material/src/components/carousel/MaterialCarouselItem.tsx @@ -21,6 +21,10 @@ export const MaterialCarouselItem: FlowComponent = pr // (especially needed in Firefox, WebKit browsers can handle this in CSS) const onDragStart = (event: Event) => event.preventDefault(); + const onContextMenu = (event: PointerEvent) => { + event.preventDefault(); + }; + return ( = pr bool:data-disabled={props.disabled} class={styles['item']} onClick={(event: PointerEvent) => props.onClick(event)} + onContextMenu={onContextMenu} >