From c356a5ca751c7c22aefe13cf7efef809ff98c015 Mon Sep 17 00:00:00 2001 From: Tofandel Date: Wed, 11 Dec 2024 18:16:37 +0100 Subject: [PATCH 1/2] fix: SSR hydration issue --- src/components/Carousel/Carousel.ts | 17 ++++++----------- src/components/Carousel/Carousel.types.ts | 1 + src/utils/createCloneSlides.ts | 19 ++++++++++--------- .../__snapshots__/carousel.spec.ts.snap | 4 ++-- 4 files changed, 19 insertions(+), 22 deletions(-) diff --git a/src/components/Carousel/Carousel.ts b/src/components/Carousel/Carousel.ts index b91ea8fa..5a7c7ed9 100644 --- a/src/components/Carousel/Carousel.ts +++ b/src/components/Carousel/Carousel.ts @@ -15,8 +15,6 @@ import { ComponentInternalInstance, watchEffect, shallowReactive, - pushScopeId, - getCurrentInstance, } from 'vue' import { ARIA as ARIAComponent } from '@/components/ARIA' @@ -122,6 +120,9 @@ export const Carousel = defineComponent({ const clonedSlidesCount = computed(() => Math.ceil(config.itemsToShow) + 1) function updateBreakpointsConfig(): void { + if (!mounted.value) { + return + } // Determine the width source based on the 'breakpointMode' config const widthSource = (fallbackConfig.value.breakpointMode === 'carousel' @@ -265,12 +266,9 @@ export const Carousel = defineComponent({ }) } - updateBreakpointsConfig() onMounted((): void => { mounted.value = true - if (fallbackConfig.value.breakpointMode === 'carousel') { - updateBreakpointsConfig() - } + updateBreakpointsConfig() initAutoplay() if (root.value) { @@ -565,6 +563,7 @@ export const Carousel = defineComponent({ slidesCount, viewport, slides, + clonedSlidesCount, scrolledIndex, currentSlide: currentSlideIndex, maxSlide: maxSlideIndex, @@ -663,7 +662,7 @@ export const Carousel = defineComponent({ */ const trackTransform: ComputedRef = computed(() => { // Calculate the scrolled index with wrapping offset if applicable - const cloneOffset = config.wrapAround ? clonedSlidesCount.value : 0 + const cloneOffset = config.wrapAround && mounted.value ? clonedSlidesCount.value : 0 // Determine direction multiplier for orientation const directionMultiplier = isReversed.value ? -1 : 1 @@ -702,13 +701,9 @@ export const Carousel = defineComponent({ const addonsElements = slotAddons?.(data) || [] if (config.wrapAround) { - // Ensure scoped CSS tracks properly - const scopeId = output.length > 0 ? output[0].scopeId : null - pushScopeId(scopeId) const toShow = clonedSlidesCount.value const slidesBefore = createCloneSlides({ slides, position: 'before', toShow }) const slidesAfter = createCloneSlides({ slides, position: 'after', toShow }) - pushScopeId(getCurrentInstance()!.vnode.scopeId) output = [...slidesBefore, ...output, ...slidesAfter] } diff --git a/src/components/Carousel/Carousel.types.ts b/src/components/Carousel/Carousel.types.ts index 4d66a024..91824c3a 100644 --- a/src/components/Carousel/Carousel.types.ts +++ b/src/components/Carousel/Carousel.types.ts @@ -19,6 +19,7 @@ export type InjectedCarousel = Reactive<{ viewport: Ref slides: ShallowReactive> slidesCount: ComputedRef + clonedSlidesCount: ComputedRef currentSlide: Ref scrolledIndex: Ref maxSlide: ComputedRef diff --git a/src/utils/createCloneSlides.ts b/src/utils/createCloneSlides.ts index b400b70e..5ef20abb 100644 --- a/src/utils/createCloneSlides.ts +++ b/src/utils/createCloneSlides.ts @@ -1,6 +1,4 @@ -import { cloneVNode, ComponentInternalInstance, h } from 'vue' - -import { Slide } from '@/components/Slide' +import { cloneVNode, ComponentInternalInstance, VNode } from 'vue' type CreateCloneSlidesArgs = { slides: Array @@ -9,11 +7,15 @@ type CreateCloneSlidesArgs = { } export function createCloneSlides({ slides, position, toShow }: CreateCloneSlidesArgs) { - const clones = [] + const clones: VNode[] = [] const isBefore = position === 'before' const start = isBefore ? -toShow : 0 const end = isBefore ? 0 : toShow + if (slides.length <= 0) { + return clones + } + for (let i = start; i < end; i++) { const index = isBefore ? i : slides.length > 0 ? i + slides.length : i + 99999 const props = { @@ -22,11 +24,10 @@ export function createCloneSlides({ slides, position, toShow }: CreateCloneSlide id: undefined, // Make sure we don't duplicate the id which would be invalid html key: `clone-${position}-${i}`, } - clones.push( - slides.length > 0 - ? cloneVNode(slides[(i % slides.length + slides.length) % slides.length].vnode, props) - : h(Slide, props) - ) + const vnode = slides[(i % slides.length + slides.length) % slides.length].vnode + const clone = cloneVNode(vnode, props) + clone.el = null + clones.push(clone) } return clones diff --git a/tests/integration/__snapshots__/carousel.spec.ts.snap b/tests/integration/__snapshots__/carousel.spec.ts.snap index b864b2e8..c465c84c 100644 --- a/tests/integration/__snapshots__/carousel.spec.ts.snap +++ b/tests/integration/__snapshots__/carousel.spec.ts.snap @@ -36,7 +36,7 @@ exports[`SSR Carousel > renders server side properly 1`] = ` " `; -exports[`SSR Carousel > renders server side properly 2`] = `"
"`; +exports[`SSR Carousel > renders server side properly 2`] = `"
"`; exports[`SSR Carousel > renders slotted server side properly 1`] = ` "
@@ -59,7 +59,7 @@ exports[`SSR Carousel > renders slotted server side properly 1`] = `
" `; -exports[`SSR Carousel > renders slotted server side properly 2`] = `"
"`; +exports[`SSR Carousel > renders slotted server side properly 2`] = `"
"`; exports[`Wrap around Carousel.ts > renders wrapAround correctly 1`] = ` "