From 4d4288d37fa9ee9a20c5346ede0bebaa92c4c4db Mon Sep 17 00:00:00 2001 From: Petr Konecny Date: Sat, 10 Oct 2026 19:31:35 +0200 Subject: [PATCH] fix: interpolate without mirroring the ends when infinite scroll is off interpolateInsideCarousel always treated the first and last slides as clones, so with infinite scroll disabled and three or more slides, the first two slides also took their current value near the end of the carousel. useInterpolateInsideCarousel now passes whether infinite scroll is on, and the helper only mirrors the ends when it is. It still defaults to on for direct callers. Co-Authored-By: Claude Opus 5.5 --- src/hooks/useInterpolateInsideCarousel.ts | 12 ++++++--- .../interpolateInsideCarousel.test.ts | 26 +++++++++++++++++++ src/utils/interpolateInsideCarousel.ts | 4 +++ 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/src/hooks/useInterpolateInsideCarousel.ts b/src/hooks/useInterpolateInsideCarousel.ts index 5fe154b..b77f77d 100644 --- a/src/hooks/useInterpolateInsideCarousel.ts +++ b/src/hooks/useInterpolateInsideCarousel.ts @@ -8,9 +8,15 @@ export const useInterpolateInsideCarousel = (values: { valueAfter: number offset?: number }) => { - const { scrollValue } = useCarouselContext() + const { scrollValue, disableInfiniteScroll } = useCarouselContext() const { index, total } = useAutoCarouselSlideIndex() return useDerivedValue(() => { - return interpolateInsideCarousel(scrollValue.value, index, total, values) - }, [scrollValue, values]) + return interpolateInsideCarousel( + scrollValue.value, + index, + total, + values, + !disableInfiniteScroll, + ) + }, [scrollValue, values, disableInfiniteScroll]) } diff --git a/src/utils/__tests__/interpolateInsideCarousel.test.ts b/src/utils/__tests__/interpolateInsideCarousel.test.ts index 101c3c2..4209f40 100644 --- a/src/utils/__tests__/interpolateInsideCarousel.test.ts +++ b/src/utils/__tests__/interpolateInsideCarousel.test.ts @@ -300,4 +300,30 @@ describe('interpolateInsideCarousel', () => { }) expect(result).toBe(1) }) + + describe('without infinite scroll', () => { + // Three real slides and no clones, so the ends don't mirror each other. + const values = { valueBefore: 0, thisValue: 1, valueAfter: 0 } + + it('keeps the first slide at the side value while the second is current', () => { + expect(interpolateInsideCarousel(1, 0, 3, values, false)).toBe(0) + }) + + it('keeps the second slide at the side value while the last is current', () => { + expect(interpolateInsideCarousel(2, 1, 3, values, false)).toBe(0) + }) + + it('keeps the last slide at the side value while the second is current', () => { + expect(interpolateInsideCarousel(1, 2, 3, values, false)).toBe(0) + }) + + it('gives the current slide its own value', () => { + expect(interpolateInsideCarousel(0, 0, 3, values, false)).toBe(1) + expect(interpolateInsideCarousel(2, 2, 3, values, false)).toBe(1) + }) + + it('interpolates halfway between slides', () => { + expect(interpolateInsideCarousel(0.5, 0, 3, values, false)).toBe(0.5) + }) + }) }) diff --git a/src/utils/interpolateInsideCarousel.ts b/src/utils/interpolateInsideCarousel.ts index 51333d7..d64d09b 100644 --- a/src/utils/interpolateInsideCarousel.ts +++ b/src/utils/interpolateInsideCarousel.ts @@ -10,6 +10,9 @@ export const interpolateInsideCarousel = ( valueAfter: number offset?: number }, + // With infinite scroll the first and last slides are clones, so a slide also counts as current + // while its clone is. + isInfinite = true, ) => { 'worklet' const { valueBefore, thisValue, valueAfter, offset = 0 } = values @@ -27,6 +30,7 @@ export const interpolateInsideCarousel = ( } const getAdjustedIndex = (slideIndex: number) => { + if (!isInfinite) return slideIndex if (slideIndex === 0) return Math.max(totalLength - 2, 0) if (slideIndex === totalLength - 1) return 1 if (slideIndex === 1) return totalLength - 1