diff --git a/.changeset/modern-carpets-sleep.md b/.changeset/modern-carpets-sleep.md new file mode 100644 index 0000000..ca69889 --- /dev/null +++ b/.changeset/modern-carpets-sleep.md @@ -0,0 +1,5 @@ +--- +"react-native-reanimated-carousel": patch +--- + +This PR updates the customAnimation function signature to include an index parameter, allowing users to apply custom animations based on the item’s index. \ No newline at end of file diff --git a/src/components/Carousel.test.tsx b/src/components/Carousel.test.tsx index 2199ade..e6df8a7 100644 --- a/src/components/Carousel.test.tsx +++ b/src/components/Carousel.test.tsx @@ -2,7 +2,7 @@ import type { FC } from "react"; import React from "react"; import type { PanGesture } from "react-native-gesture-handler"; import { Gesture, State } from "react-native-gesture-handler"; -import Animated, { useDerivedValue, useSharedValue } from "react-native-reanimated"; +import Animated, { interpolate, useDerivedValue, useSharedValue } from "react-native-reanimated"; import type { ReactTestInstance } from "react-test-renderer"; import { act, render, waitFor } from "@testing-library/react-native"; @@ -301,7 +301,11 @@ describe("Test the real swipe behavior of Carousel to ensure it's working as exp fireGestureHandler(getByGestureTestId(gestureTestId), [ { state: State.BEGAN, translationX: 0, velocityX: -5 }, - { state: State.ACTIVE, translationX: -slideWidth * 0.15, velocityX: -5 }, + { + state: State.ACTIVE, + translationX: -slideWidth * 0.15, + velocityX: -5, + }, { state: State.END, translationX: -slideWidth * 0.25, velocityX: -5 }, ]); @@ -314,8 +318,16 @@ describe("Test the real swipe behavior of Carousel to ensure it's working as exp fireGestureHandler(getByGestureTestId(gestureTestId), [ { state: State.BEGAN, translationX: 0, velocityX: -1000 }, - { state: State.ACTIVE, translationX: -slideWidth * 0.15, velocityX: -1000 }, - { state: State.END, translationX: -slideWidth * 0.25, velocityX: -1000 }, + { + state: State.ACTIVE, + translationX: -slideWidth * 0.15, + velocityX: -1000, + }, + { + state: State.END, + translationX: -slideWidth * 0.25, + velocityX: -1000, + }, ]); await waitFor(() => expect(progress.current).toBe(1)); @@ -441,6 +453,45 @@ describe("Test the real swipe behavior of Carousel to ensure it's working as exp } }); + it("`customAnimation` prop: should apply the custom animation", async () => { + const progress = { current: 0 }; + const indexes: Record = {}; + const Wrapper = createCarousel(progress); + const { getByTestId } = render( + { + "worklet"; + + indexes[index] = index; + + const zIndex = interpolate(value, [-1, 0, 1], [10, 20, 30]); + const translateX = interpolate(value, [-2, 0, 1], [-slideWidth, 0, slideWidth]); + + return { + transform: [{ translateX }], + zIndex, + }; + }} + /> + ); + + await verifyInitialRender(getByTestId); + + swipeToLeftOnce(); + await waitFor(() => { + expect(progress.current).toBe(1); + + expect(indexes).toMatchInlineSnapshot(` + { + "0": 0, + "1": 1, + "2": 2, + "3": 3, + } + `); + }); + }); + it("`overscrollEnabled` prop: should respect overscrollEnabled=false and prevent scrolling beyond bounds", async () => { const containerWidth = slideWidth; const containerHeight = containerWidth / 2; diff --git a/src/components/ItemLayout.tsx b/src/components/ItemLayout.tsx index fb43268..67ccbcc 100644 --- a/src/components/ItemLayout.tsx +++ b/src/components/ItemLayout.tsx @@ -3,13 +3,14 @@ import type { ViewStyle } from "react-native"; import type { SharedValue } from "react-native-reanimated"; import Animated, { useAnimatedStyle, useDerivedValue } from "react-native-reanimated"; +import { TCarouselProps } from "src/types"; import type { IOpts } from "../hooks/useOffsetX"; import { useOffsetX } from "../hooks/useOffsetX"; import type { IVisibleRanges } from "../hooks/useVisibleRanges"; import type { ILayoutConfig } from "../layouts/stack"; import { useGlobalState } from "../store"; -export type TAnimationStyle = (value: number) => ViewStyle; +export type TAnimationStyle = NonNullable; export const ItemLayout: React.FC<{ index: number; @@ -56,8 +57,8 @@ export const ItemLayout: React.FC<{ const x = useOffsetX(offsetXConfig, visibleRanges); const animationValue = useDerivedValue(() => x.value / size, [x, size]); const animatedStyle = useAnimatedStyle( - () => animationStyle(x.value / size), - [animationStyle] + () => animationStyle(x.value / size, index), + [animationStyle, index] ); // TODO: For dynamic dimension in the future diff --git a/src/components/ItemRenderer.tsx b/src/components/ItemRenderer.tsx index 901e132..deee0c1 100644 --- a/src/components/ItemRenderer.tsx +++ b/src/components/ItemRenderer.tsx @@ -24,7 +24,7 @@ interface Props { handlerOffset: SharedValue; layoutConfig: TAnimationStyle; renderItem: CarouselRenderItem; - customAnimation?: (value: number) => ViewStyle; + customAnimation?: (value: number, index: number) => ViewStyle; } export const ItemRenderer: FC = (props) => { diff --git a/src/types.ts b/src/types.ts index 24358a1..c3e938c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -187,8 +187,9 @@ export type TCarouselProps = { /** * Custom animations. * Must use `worklet`, Details: https://docs.swmansion.com/react-native-reanimated/docs/2.2.0/worklets/ + * @test_coverage ✅ tested in Carousel.test.tsx > should apply the custom animation */ - customAnimation?: (value: number) => ViewStyle; + customAnimation?: (value: number, index: number) => ViewStyle; /** * Render carousel item. * @test_coverage ✅ tested in Carousel.test.tsx > should render items correctly