diff --git a/src/Carousel.tsx b/src/Carousel.tsx index 12344553..956bb24c 100644 --- a/src/Carousel.tsx +++ b/src/Carousel.tsx @@ -9,13 +9,13 @@ import { useVisibleRanges } from './hooks/useVisibleRanges'; import type { ICarouselInstance, TCarouselProps } from './types'; import { StyleSheet, View } from 'react-native'; -import { DATA_LENGTH } from './constants'; import { BaseLayout } from './layouts/BaseLayout'; import { useLayoutConfig } from './hooks/useLayoutConfig'; import { useInitProps } from './hooks/useInitProps'; import { CTX } from './store'; import { useCommonVariables } from './hooks/useCommonVariables'; import { useOnProgressChange } from './hooks/useOnProgressChange'; +import { computedRealIndexWithAutoFillData } from './utils/computedWithAutoFillData'; const Carousel = React.forwardRef>( (_props, ref) => { @@ -25,6 +25,7 @@ const Carousel = React.forwardRef>( data, rawData, loop, + autoFillData, mode, style, width, @@ -60,7 +61,14 @@ const Carousel = React.forwardRef>( }, [loop, size, dataLength]); usePropsErrorBoundary(props); - useOnProgressChange({ size, offsetX, rawData, onProgressChange }); + useOnProgressChange({ + autoFillData, + loop, + size, + offsetX, + rawData, + onProgressChange, + }); const carouselController = useCarouselController({ loop, @@ -148,14 +156,12 @@ const Carousel = React.forwardRef>( const renderLayout = React.useCallback( (item: any, i: number) => { - let realIndex = i; - if (rawData.length === DATA_LENGTH.SINGLE_ITEM) { - realIndex = i % 1; - } - - if (rawData.length === DATA_LENGTH.DOUBLE_ITEM) { - realIndex = i % 2; - } + const realIndex = computedRealIndexWithAutoFillData({ + index: i, + dataLength: rawData.length, + loop, + autoFillData, + }); return ( >( ); }, [ + loop, rawData, offsetX, visibleRanges, + autoFillData, renderItem, layoutConfig, customAnimation, diff --git a/src/hooks/useInitProps.ts b/src/hooks/useInitProps.ts index e27a7322..bc54d222 100644 --- a/src/hooks/useInitProps.ts +++ b/src/hooks/useInitProps.ts @@ -1,5 +1,5 @@ +import { computedFillDataWithAutoFillData } from '@/utils/computedWithAutoFillData'; import React from 'react'; -import { DATA_LENGTH } from '../constants'; import type { TCarouselProps } from '../types'; type TGetRequiredProps

= Record< @@ -15,6 +15,7 @@ export type TInitializeCarouselProps = TCarouselProps & | 'height' | 'scrollAnimationDuration' | 'autoPlayInterval' + | 'autoFillData' > & { // Raw data that has not been processed rawData: T[]; @@ -43,19 +44,16 @@ export function useInitProps( const height = Math.round(_height || 0); const autoPlayInterval = Math.max(_autoPlayInterval, 0); - const data = React.useMemo(() => { - if (!loop || !autoFillData) return rawData; - - if (rawData.length === DATA_LENGTH.SINGLE_ITEM) { - return [rawData[0], rawData[0], rawData[0]]; - } - - if (rawData.length === DATA_LENGTH.DOUBLE_ITEM) { - return [rawData[0], rawData[1], rawData[0], rawData[1]]; - } - - return rawData; - }, [rawData, loop, autoFillData]); + const data = React.useMemo( + () => + computedFillDataWithAutoFillData({ + loop, + autoFillData, + data: rawData, + dataLength: rawData.length, + }), + [rawData, loop, autoFillData] + ); if (props.mode === 'vertical-stack' || props.mode === 'horizontal-stack') { if (!props.modeConfig) { @@ -67,6 +65,7 @@ export function useInitProps( return { ...props, defaultIndex, + autoFillData, data, rawData, loop, diff --git a/src/hooks/useOnProgressChange.ts b/src/hooks/useOnProgressChange.ts index 1c066cc2..88f9e1a4 100644 --- a/src/hooks/useOnProgressChange.ts +++ b/src/hooks/useOnProgressChange.ts @@ -2,29 +2,39 @@ import Animated, { runOnJS, useAnimatedReaction, } from 'react-native-reanimated'; -import { DATA_LENGTH } from '../constants'; +import { computedOffsetXValueWithAutoFillData } from '@/utils/computedWithAutoFillData'; import type { TCarouselProps } from '../types'; export function useOnProgressChange( opts: { size: number; + autoFillData: boolean; + loop: boolean; offsetX: Animated.SharedValue; rawData: TCarouselProps['data']; } & Pick ) { - const { offsetX, rawData, size, onProgressChange } = opts; + const { autoFillData, loop, offsetX, rawData, size, onProgressChange } = + opts; + const rawDataLength = rawData.length; + useAnimatedReaction( () => offsetX.value, (_value) => { - let value = _value; - - if (rawDataLength === DATA_LENGTH.SINGLE_ITEM) { - value = value % size; - } + let value = computedOffsetXValueWithAutoFillData({ + value: _value, + rawDataLength, + size, + autoFillData, + loop, + }); - if (rawDataLength === DATA_LENGTH.DOUBLE_ITEM) { - value = value % (size * 2); + if (!loop) { + value = Math.max( + -((rawDataLength - 1) * size), + Math.min(value, 0) + ); } let absoluteProgress = Math.abs(value / size); @@ -36,6 +46,6 @@ export function useOnProgressChange( !!onProgressChange && runOnJS(onProgressChange)(value, absoluteProgress); }, - [onProgressChange, rawDataLength] + [loop, autoFillData, rawDataLength, onProgressChange] ); } diff --git a/src/utils/computedWithAutoFillData.ts b/src/utils/computedWithAutoFillData.ts new file mode 100644 index 00000000..49c9f15f --- /dev/null +++ b/src/utils/computedWithAutoFillData.ts @@ -0,0 +1,76 @@ +import { DATA_LENGTH } from 'src/constants'; + +const { SINGLE_ITEM, DOUBLE_ITEM } = DATA_LENGTH; + +function isAutoFillData(params: { autoFillData: boolean; loop: boolean }) { + 'worklet'; + return !!params.loop && !!params.autoFillData; +} + +type BaseParams = { + autoFillData: boolean; + loop: boolean; +} & T; + +export function computedOffsetXValueWithAutoFillData( + params: BaseParams<{ + rawDataLength: number; + value: number; + size: number; + }> +) { + 'worklet'; + + const { rawDataLength, value, size, loop, autoFillData } = params; + + if (isAutoFillData({ loop, autoFillData })) { + switch (rawDataLength) { + case SINGLE_ITEM: + return value % size; + case DOUBLE_ITEM: + return value % (size * 2); + } + } + + return value; +} + +export function computedRealIndexWithAutoFillData( + params: BaseParams<{ + index: number; + dataLength: number; + }> +) { + const { index, dataLength, loop, autoFillData } = params; + + if (isAutoFillData({ loop, autoFillData })) { + switch (dataLength) { + case SINGLE_ITEM: + return index % 1; + case DOUBLE_ITEM: + return index % 2; + } + } + + return index; +} + +export function computedFillDataWithAutoFillData( + params: BaseParams<{ + data: T[]; + dataLength: number; + }> +): T[] { + const { data, loop, autoFillData, dataLength } = params; + + if (isAutoFillData({ loop, autoFillData })) { + switch (dataLength) { + case SINGLE_ITEM: + return [data[0], data[0], data[0]]; + case DOUBLE_ITEM: + return [data[0], data[1], data[0], data[1]]; + } + } + + return data; +}