Skip to content

Commit e106b54

Browse files
committed
refactor: update Carousel component to use visibleRange for slide visibility and add unit tests for calculateAverage utility
1 parent 14be947 commit e106b54

File tree

5 files changed

+175
-119
lines changed

5 files changed

+175
-119
lines changed

src/components/Carousel/Carousel.ts

Lines changed: 143 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -358,19 +358,19 @@ export const Carousel = defineComponent({
358358
case 'ArrowUp':
359359
if (isVertical.value === event.key.endsWith('Up')) {
360360
if (isReversed.value) {
361-
nav.next(true)
361+
next(true)
362362
} else {
363-
nav.prev(true)
363+
prev(true)
364364
}
365365
}
366366
break
367367
case 'ArrowRight':
368368
case 'ArrowDown':
369369
if (isVertical.value === event.key.endsWith('Down')) {
370370
if (isReversed.value) {
371-
nav.prev(true)
371+
prev(true)
372372
} else {
373-
nav.next(true)
373+
next(true)
374374
}
375375
}
376376
break
@@ -469,46 +469,6 @@ export const Carousel = defineComponent({
469469
document.removeEventListener(endEvent, handleDragEnd)
470470
}
471471

472-
const minVisibleSlideIndex = computed(() => {
473-
let output = 0
474-
const snapAlignOffset = getSnapAlignOffset({
475-
itemsToShow: +config.itemsToShow,
476-
align: config.snapAlign,
477-
})
478-
479-
if (config.wrapAround) {
480-
output = currentSlideIndex.value - snapAlignOffset
481-
} else {
482-
output = getNumberInRange({
483-
val: currentSlideIndex.value - snapAlignOffset,
484-
max: slidesCount.value - +config.itemsToShow,
485-
min: 0,
486-
})
487-
}
488-
489-
return Math.floor(output)
490-
})
491-
492-
const maxVisibleSlideIndex = computed(() => {
493-
let output = +config.itemsToShow - 1
494-
495-
const snapAlignOffset = getSnapAlignOffset({
496-
itemsToShow: +config.itemsToShow,
497-
align: config.snapAlign,
498-
})
499-
500-
if (config.wrapAround) {
501-
output += currentSlideIndex.value - snapAlignOffset
502-
} else {
503-
output += getNumberInRange({
504-
val: currentSlideIndex.value - snapAlignOffset,
505-
max: slidesCount.value - +config.itemsToShow,
506-
min: 0,
507-
})
508-
}
509-
510-
return Math.ceil(output)
511-
})
512472
/**
513473
* Autoplay
514474
*/
@@ -615,40 +575,6 @@ export const Carousel = defineComponent({
615575
slideTo(currentSlideIndex.value - config.itemsToScroll, skipTransition)
616576
}
617577

618-
const nav: CarouselNav = { slideTo, next, prev }
619-
620-
const provided: InjectedCarousel = reactive({
621-
config,
622-
slidesCount,
623-
viewport,
624-
slides,
625-
currentSlide: currentSlideIndex,
626-
activeSlide: activeSlideIndex,
627-
maxVisibleSlide: maxVisibleSlideIndex,
628-
minVisibleSlide: minVisibleSlideIndex,
629-
maxSlide: maxSlideIndex,
630-
minSlide: minSlideIndex,
631-
slideSize,
632-
isVertical,
633-
normalizedDir,
634-
nav,
635-
isSliding,
636-
slideRegistry,
637-
})
638-
639-
provide(injectCarousel, provided)
640-
/** @deprecated provides */
641-
provide('config', config)
642-
provide('slidesCount', slidesCount)
643-
provide('currentSlide', currentSlideIndex)
644-
provide('maxSlide', maxSlideIndex)
645-
provide('minSlide', minSlideIndex)
646-
provide('slideSize', slideSize)
647-
provide('isVertical', isVertical)
648-
provide('normalizeDir', normalizedDir)
649-
provide('nav', nav)
650-
provide('isSliding', isSliding)
651-
652578
function restartCarousel(): void {
653579
updateBreakpointsConfig()
654580
updateSlidesData()
@@ -682,28 +608,6 @@ export const Carousel = defineComponent({
682608
// Init carousel
683609
emit('before-init')
684610

685-
const data = reactive<CarouselData>({
686-
config,
687-
slidesCount,
688-
slideSize,
689-
currentSlide: currentSlideIndex,
690-
maxSlide: maxSlideIndex,
691-
minSlide: minSlideIndex,
692-
middleSlide: middleSlideIndex,
693-
})
694-
695-
expose<CarouselExposed>({
696-
updateBreakpointsConfig,
697-
updateSlidesData,
698-
updateSlideSize,
699-
restartCarousel,
700-
slideTo,
701-
next,
702-
prev,
703-
nav,
704-
data,
705-
})
706-
707611
const clonedSlidesCount = computed(() => {
708612
if (!config.wrapAround) {
709613
return { before: 0, after: 0 }
@@ -724,6 +628,9 @@ export const Carousel = defineComponent({
724628
})
725629

726630
const clonedSlidesOffset = computed(() => {
631+
if (!clonedSlidesCount.value.before) {
632+
return 0
633+
}
727634
if (isAuto.value) {
728635
return (
729636
slidesRect.value
@@ -735,18 +642,26 @@ export const Carousel = defineComponent({
735642
return clonedSlidesCount.value.before * effectiveSlideSize.value * -1
736643
})
737644

738-
const scrolledOffset = computed(() => {
739-
let output = 0
740-
645+
const snapAlignOffset = computed(() => {
741646
if (isAuto.value) {
742647
const slideIndex =
743648
((currentSlideIndex.value % slides.length) + slides.length) % slides.length
744-
const snapAlignOffset = getSnapAlignOffset({
649+
return getSnapAlignOffset({
745650
slideSize: slidesRect.value[slideIndex]?.[dimension.value],
746651
viewportSize: viewportRect.value[dimension.value],
747652
align: config.snapAlign,
748653
})
654+
}
749655

656+
return getSnapAlignOffset({
657+
align: config.snapAlign,
658+
itemsToShow: +config.itemsToShow,
659+
})
660+
})
661+
const scrolledOffset = computed(() => {
662+
let output = 0
663+
664+
if (isAuto.value) {
750665
if (currentSlideIndex.value < 0) {
751666
output =
752667
slidesRect.value
@@ -757,7 +672,7 @@ export const Carousel = defineComponent({
757672
.slice(0, currentSlideIndex.value)
758673
.reduce((acc, slide) => acc + slide[dimension.value] + config.gap, 0)
759674
}
760-
output -= snapAlignOffset
675+
output -= snapAlignOffset.value
761676

762677
// remove whitespace
763678
if (!config.wrapAround) {
@@ -776,11 +691,7 @@ export const Carousel = defineComponent({
776691
})
777692
}
778693
} else {
779-
const offsetSlides = getSnapAlignOffset({
780-
align: config.snapAlign,
781-
itemsToShow: +config.itemsToShow,
782-
})
783-
let scrolledSlides = currentSlideIndex.value - offsetSlides
694+
let scrolledSlides = currentSlideIndex.value - snapAlignOffset.value
784695

785696
// remove whitespace
786697
if (!config.wrapAround) {
@@ -796,6 +707,84 @@ export const Carousel = defineComponent({
796707
return output * (isReversed.value ? 1 : -1)
797708
})
798709

710+
const visibleRange = computed(() => {
711+
if (!isAuto.value) {
712+
const base = currentSlideIndex.value - snapAlignOffset.value
713+
if (config.wrapAround) {
714+
return {
715+
min: Math.floor(base),
716+
max: Math.ceil(base + Number(config.itemsToShow) - 1),
717+
}
718+
}
719+
return {
720+
min: Math.floor(
721+
getNumberInRange({
722+
val: base,
723+
max: slidesCount.value - Number(config.itemsToShow),
724+
min: 0,
725+
})
726+
),
727+
max: Math.ceil(
728+
getNumberInRange({
729+
val: base + Number(config.itemsToShow) - 1,
730+
max: slidesCount.value - 1,
731+
min: 0,
732+
})
733+
),
734+
}
735+
}
736+
737+
// Auto width mode
738+
let minIndex = 0
739+
{
740+
let accumulatedSize = 0
741+
let index = 0 - clonedSlidesCount.value.before
742+
const offset = Math.abs(scrolledOffset.value + clonedSlidesOffset.value)
743+
744+
while (accumulatedSize <= offset) {
745+
const normalizedIndex =
746+
((index % slides.length) + slides.length) % slides.length
747+
accumulatedSize +=
748+
slidesRect.value[normalizedIndex]?.[dimension.value] + config.gap
749+
index++
750+
}
751+
minIndex = index - 1
752+
}
753+
754+
let maxIndex = 0
755+
{
756+
let index = minIndex
757+
let accumulatedSize = 0
758+
if (index < 0) {
759+
accumulatedSize =
760+
slidesRect.value
761+
.slice(0, index)
762+
.reduce((acc, slide) => acc + slide[dimension.value] + config.gap, 0) -
763+
Math.abs(scrolledOffset.value + clonedSlidesOffset.value)
764+
} else {
765+
accumulatedSize =
766+
slidesRect.value
767+
.slice(0, index)
768+
.reduce((acc, slide) => acc + slide[dimension.value] + config.gap, 0) -
769+
Math.abs(scrolledOffset.value)
770+
}
771+
772+
while (accumulatedSize < viewportRect.value[dimension.value]) {
773+
const normalizedIndex =
774+
((index % slides.length) + slides.length) % slides.length
775+
accumulatedSize +=
776+
slidesRect.value[normalizedIndex]?.[dimension.value] + config.gap
777+
index++
778+
}
779+
maxIndex = index - 1
780+
}
781+
782+
return {
783+
min: Math.floor(minIndex),
784+
max: Math.ceil(maxIndex),
785+
}
786+
})
787+
799788
const trackTransform: ComputedRef<string | undefined> = computed(() => {
800789
if (config.slideEffect === 'fade') {
801790
return undefined
@@ -820,6 +809,50 @@ export const Carousel = defineComponent({
820809
'--vc-cloned-offset': toCssValue(clonedSlidesOffset.value),
821810
}))
822811

812+
const nav: CarouselNav = { slideTo, next, prev }
813+
814+
const provided: InjectedCarousel = reactive({
815+
config,
816+
slidesCount,
817+
viewport,
818+
slides,
819+
currentSlide: currentSlideIndex,
820+
activeSlide: activeSlideIndex,
821+
maxSlide: maxSlideIndex,
822+
minSlide: minSlideIndex,
823+
visibleRange,
824+
slideSize,
825+
isVertical,
826+
normalizedDir,
827+
nav,
828+
isSliding,
829+
slideRegistry,
830+
})
831+
832+
provide(injectCarousel, provided)
833+
834+
const data = reactive<CarouselData>({
835+
config,
836+
slidesCount,
837+
slideSize,
838+
currentSlide: currentSlideIndex,
839+
maxSlide: maxSlideIndex,
840+
minSlide: minSlideIndex,
841+
middleSlide: middleSlideIndex,
842+
})
843+
844+
expose<CarouselExposed>({
845+
updateBreakpointsConfig,
846+
updateSlidesData,
847+
updateSlideSize,
848+
restartCarousel,
849+
slideTo,
850+
next,
851+
prev,
852+
nav,
853+
data,
854+
})
855+
823856
return () => {
824857
const slotSlides = slots.default || slots.slides
825858
const outputSlides = slotSlides?.(data) || []

src/components/Carousel/Carousel.types.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,7 @@ export type InjectedCarousel = Reactive<{
2121
slidesCount: ComputedRef<number>
2222
activeSlide: Ref<number>
2323
currentSlide: Ref<number>
24-
maxVisibleSlide: ComputedRef<number>
25-
minVisibleSlide: ComputedRef<number>
24+
visibleRange: ComputedRef<{ min: number; max: number }>
2625
slideSize: Ref<number>
2726
isVertical: ComputedRef<boolean>
2827
normalizedDir: ComputedRef<NormalizedDir>

src/components/Slide/Slide.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,12 +76,11 @@ export const Slide = defineComponent({
7676
const isNext: ComputedRef<boolean> = computed(
7777
() => currentIndex.value === carousel.activeSlide + 1
7878
)
79-
const isVisible: ComputedRef<boolean> = computed(() => {
80-
return (
81-
currentIndex.value >= carousel.minVisibleSlide &&
82-
currentIndex.value <= carousel.maxVisibleSlide
83-
)
84-
})
79+
const isVisible: ComputedRef<boolean> = computed(
80+
() =>
81+
currentIndex.value >= carousel.visibleRange.min &&
82+
currentIndex.value <= carousel.visibleRange.max
83+
)
8584

8685
const slideStyle = computed(() => {
8786
if (carousel.config.itemsToShow === 'auto') {

src/utils/calculateAverage.spec.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { describe, expect, test } from 'vitest'
2+
3+
import { calculateAverage } from './calculateAverage'
4+
5+
describe('calculateAverage', () => {
6+
test('returns 0 for empty array', () => {
7+
expect(calculateAverage([])).toBe(0)
8+
})
9+
10+
test('calculates average for single value', () => {
11+
expect(calculateAverage([5])).toBe(5)
12+
})
13+
14+
test('calculates average for multiple values', () => {
15+
expect(calculateAverage([1, 2, 3, 4, 5])).toBe(3)
16+
})
17+
18+
test('handles decimal numbers', () => {
19+
expect(calculateAverage([1.5, 2.5, 3.5])).toBe(2.5)
20+
})
21+
22+
test('handles negative numbers', () => {
23+
expect(calculateAverage([-1, 0, 1])).toBe(0)
24+
})
25+
})

0 commit comments

Comments
 (0)