diff --git a/src/components/Carousel/Carousel.css b/src/components/Carousel/Carousel.css index 7d1d87d..5735751 100644 --- a/src/components/Carousel/Carousel.css +++ b/src/components/Carousel/Carousel.css @@ -49,3 +49,15 @@ flex-direction: column-reverse; } } + +.carousel.is-vertical { + .carousel__slide--clone:first-child { + margin-block-start: var(--vc-trk-cloned-offset); + } +} + +.carousel:not(.is-vertical) { + .carousel__slide--clone:first-child { + margin-inline-start: var(--vc-trk-cloned-offset); + } +} diff --git a/src/components/Carousel/Carousel.ts b/src/components/Carousel/Carousel.ts index 4c2afd1..e176f87 100644 --- a/src/components/Carousel/Carousel.ts +++ b/src/components/Carousel/Carousel.ts @@ -402,7 +402,13 @@ export const Carousel = defineComponent({ effectiveSlideSize: effectiveSlideSize.value, }) - activeSlideIndex.value = currentSlideIndex.value + draggedSlides + activeSlideIndex.value = config.wrapAround + ? currentSlideIndex.value + draggedSlides + : getNumberInRange({ + val: currentSlideIndex.value + draggedSlides, + max: maxSlideIndex.value, + min: minSlideIndex.value, + }) // Emit a drag event for further customization if needed emit('drag', { deltaX: dragged.x, deltaY: dragged.y }) @@ -473,14 +479,22 @@ export const Carousel = defineComponent({ } let targetIndex = slideIndex + let mappedIndex = slideIndex + + prevSlideIndex.value = currentSlideIndex.value - // Calculate shortest path in wrap-around mode if (!config.wrapAround) { targetIndex = getNumberInRange({ val: targetIndex, max: maxSlideIndex.value, min: minSlideIndex.value, }) + } else { + mappedIndex = mapNumberToRange({ + val: targetIndex, + max: maxSlideIndex.value, + min: 0, + }) } emit('slide-start', { @@ -491,29 +505,20 @@ export const Carousel = defineComponent({ }) stopAutoplay() - prevSlideIndex.value = currentSlideIndex.value - - const mappedNumber = config.wrapAround - ? mapNumberToRange({ - val: targetIndex, - max: maxSlideIndex.value, - min: 0, - }) - : targetIndex - isSliding.value = true - currentSlideIndex.value = targetIndex - if (mappedNumber !== targetIndex) { + currentSlideIndex.value = targetIndex + if (mappedIndex !== targetIndex) { modelWatcher.pause() } - emit('update:modelValue', mappedNumber) + emit('update:modelValue', mappedIndex) - transitionTimer = setTimeout((): void => { + const transitionCallback = (): void => { if (config.wrapAround) { - if (mappedNumber !== targetIndex) { + if (mappedIndex !== targetIndex) { modelWatcher.resume() - currentSlideIndex.value = mappedNumber + + currentSlideIndex.value = mappedIndex emit('loop', { currentSlideIndex: currentSlideIndex.value, slidingToIndex: slideIndex, @@ -529,7 +534,9 @@ export const Carousel = defineComponent({ isSliding.value = false resetAutoplay() - }, config.transition) + } + + transitionTimer = setTimeout(transitionCallback, config.transition) } function next(skipTransition = false): void { @@ -660,17 +667,19 @@ export const Carousel = defineComponent({ return { before: 0, after: 0 } } - const slidesToClone = Math.ceil(config.itemsToShow) + const slidesToClone = Math.ceil(config.itemsToShow + (config.itemsToScroll - 1)) + const before = slidesToClone - activeSlideIndex.value + const after = slidesToClone - (slidesCount.value - (activeSlideIndex.value + 1)) return { - before: Math.max(slidesToClone, slidesToClone - activeSlideIndex.value), - after: Math.max( - slidesToClone, - slidesToClone - (slidesCount.value - (activeSlideIndex.value + 1)) - ), + before: Math.max(0, before), + after: Math.max(0, after), } }) + const clonedSlidesOffset = computed( + () => clonedSlidesCount.value.before * effectiveSlideSize.value * -1 + ) const trackTransform: ComputedRef = computed(() => { const directionMultiplier = isReversed.value ? 1 : -1 const translateAxis = isVertical.value ? 'Y' : 'X' @@ -679,13 +688,10 @@ export const Carousel = defineComponent({ const scrolledOffset = scrolledIndex.value * effectiveSlideSize.value * directionMultiplier - const clonedSlidesOffset = - clonedSlidesCount.value.before * effectiveSlideSize.value * -1 - // Include user drag interaction offset const dragOffset = isVertical.value ? dragged.y : dragged.x - const totalOffset = scrolledOffset + clonedSlidesOffset + dragOffset + const totalOffset = scrolledOffset + dragOffset return `translate${translateAxis}(${totalOffset}px)` }) @@ -695,6 +701,7 @@ export const Carousel = defineComponent({ 'transition-duration': isSliding.value ? `${config.transition}ms` : undefined, gap: config.gap > 0 ? `${config.gap}px` : undefined, '--vc-trk-height': trackHeight.value, + '--vc-trk-cloned-offset': `${clonedSlidesOffset.value}px`, })) return () => { diff --git a/src/utils/getScrolledIndex.ts b/src/utils/getScrolledIndex.ts index d005866..aca1b35 100644 --- a/src/utils/getScrolledIndex.ts +++ b/src/utils/getScrolledIndex.ts @@ -1,7 +1,6 @@ import { CarouselConfig } from '@/shared' import { getNumberInRange } from './getNumberInRange' -import { mapNumberToRange } from './mapNumberToRange' type GetScrolledIndexArgs = { config: Pick @@ -12,11 +11,16 @@ type GetScrolledIndexArgs = { export const calculateOffset = (snapAlign: string, itemsToShow: number): number => { switch (snapAlign) { default: - case 'start': return 0 - case 'center': return (itemsToShow - 1) / 2 - case 'center-odd': return (itemsToShow - 1) / 2 - case 'center-even': return (itemsToShow - 2) / 2 - case 'end': return itemsToShow - 1 + case 'start': + return 0 + case 'center': + return (itemsToShow - 1) / 2 + case 'center-odd': + return (itemsToShow - 1) / 2 + case 'center-even': + return (itemsToShow - 2) / 2 + case 'end': + return itemsToShow - 1 } } @@ -31,17 +35,12 @@ export function getScrolledIndex({ const offset = calculateOffset(snapAlign, itemsToShow) // Compute the index with or without wrapAround - if (!wrapAround) { - return getNumberInRange({ - val: currentSlide - offset, - max: slidesCount - itemsToShow, - min: 0, - }) - } else { - return mapNumberToRange({ - val: currentSlide - offset, - max: slidesCount + itemsToShow, - min: 0 - itemsToShow, - }) + if (wrapAround) { + return currentSlide - offset } + return getNumberInRange({ + val: currentSlide - offset, + max: slidesCount - itemsToShow, + min: 0, + }) } diff --git a/tests/integration/__snapshots__/carousel.spec.ts.snap b/tests/integration/__snapshots__/carousel.spec.ts.snap index 7a37214..6c7d322 100644 --- a/tests/integration/__snapshots__/carousel.spec.ts.snap +++ b/tests/integration/__snapshots__/carousel.spec.ts.snap @@ -4,16 +4,13 @@ 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`] = ` "
" `; -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`] = ` "