react-native-reanimated-carousel 4.0.0-alpha.1 → 4.0.0-alpha.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -3
- package/lib/commonjs/{layouts → components}/BaseLayout.js +5 -21
- package/lib/commonjs/components/BaseLayout.js.map +1 -0
- package/lib/commonjs/components/Carousel.js +28 -48
- package/lib/commonjs/components/Carousel.js.map +1 -1
- package/lib/commonjs/components/ItemRenderer.js +80 -0
- package/lib/commonjs/components/ItemRenderer.js.map +1 -0
- package/lib/commonjs/components/Pagination/Basic/PaginationItem.js +81 -0
- package/lib/commonjs/components/Pagination/Basic/PaginationItem.js.map +1 -0
- package/lib/commonjs/components/Pagination/Basic/index.js +57 -0
- package/lib/commonjs/components/Pagination/Basic/index.js.map +1 -0
- package/lib/commonjs/components/Pagination/index.js +14 -0
- package/lib/commonjs/components/Pagination/index.js.map +1 -0
- package/lib/commonjs/components/ScrollViewGesture.js +51 -33
- package/lib/commonjs/components/ScrollViewGesture.js.map +1 -1
- package/lib/commonjs/components/rnr-demo.test.js +45 -0
- package/lib/commonjs/components/rnr-demo.test.js.map +1 -0
- package/lib/commonjs/hooks/useCarouselController.js +12 -11
- package/lib/commonjs/hooks/useCarouselController.js.map +1 -1
- package/lib/commonjs/hooks/useCommonVariables.js +38 -12
- package/lib/commonjs/hooks/useCommonVariables.js.map +1 -1
- package/lib/commonjs/hooks/useCommonVariables.test.js +38 -0
- package/lib/commonjs/hooks/useCommonVariables.test.js.map +1 -0
- package/lib/commonjs/hooks/useLayoutConfig.js.map +1 -1
- package/lib/commonjs/hooks/useOffsetX.js +9 -6
- package/lib/commonjs/hooks/useOffsetX.js.map +1 -1
- package/lib/commonjs/hooks/useOffsetX.test.js +53 -0
- package/lib/commonjs/hooks/useOffsetX.test.js.map +1 -0
- package/lib/commonjs/hooks/useOnProgressChange.js +4 -1
- package/lib/commonjs/hooks/useOnProgressChange.js.map +1 -1
- package/lib/commonjs/hooks/usePanGestureProxy.js +84 -0
- package/lib/commonjs/hooks/usePanGestureProxy.js.map +1 -0
- package/lib/commonjs/hooks/usePanGestureProxy.test.js +397 -0
- package/lib/commonjs/hooks/usePanGestureProxy.test.js.map +1 -0
- package/lib/commonjs/hooks/useUpdateGestureConfig.js.map +1 -1
- package/lib/commonjs/hooks/useVisibleRanges.js +48 -19
- package/lib/commonjs/hooks/useVisibleRanges.js.map +1 -1
- package/lib/commonjs/hooks/useVisibleRanges.test.js +162 -0
- package/lib/commonjs/hooks/useVisibleRanges.test.js.map +1 -0
- package/lib/commonjs/index.js +8 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/utils/{computeNewIndexWhenDataChanges.js → compute-offset-if-data-changed.js} +3 -3
- package/lib/commonjs/utils/compute-offset-if-data-changed.js.map +1 -0
- package/lib/commonjs/utils/compute-offset-if-data-changed.test.js +30 -0
- package/lib/commonjs/utils/compute-offset-if-data-changed.test.js.map +1 -0
- package/lib/commonjs/utils/compute-offset-if-size-changed.js +18 -0
- package/lib/commonjs/utils/compute-offset-if-size-changed.js.map +1 -0
- package/lib/commonjs/utils/compute-offset-if-size-changed.test.js +72 -0
- package/lib/commonjs/utils/compute-offset-if-size-changed.test.js.map +1 -0
- package/lib/commonjs/utils/handleroffset-direction.js +5 -5
- package/lib/commonjs/utils/handleroffset-direction.js.map +1 -1
- package/lib/commonjs/utils/handleroffset-direction.test.js +46 -0
- package/lib/commonjs/utils/handleroffset-direction.test.js.map +1 -0
- package/lib/commonjs/utils/index.test.js +6 -6
- package/lib/commonjs/utils/index.test.js.map +1 -1
- package/lib/module/{layouts → components}/BaseLayout.js +6 -16
- package/lib/module/components/BaseLayout.js.map +1 -0
- package/lib/module/components/Carousel.js +27 -44
- package/lib/module/components/Carousel.js.map +1 -1
- package/lib/module/components/ItemRenderer.js +62 -0
- package/lib/module/components/ItemRenderer.js.map +1 -0
- package/lib/module/components/Pagination/Basic/PaginationItem.js +63 -0
- package/lib/module/components/Pagination/Basic/PaginationItem.js.map +1 -0
- package/lib/module/components/Pagination/Basic/index.js +42 -0
- package/lib/module/components/Pagination/Basic/index.js.map +1 -0
- package/lib/module/components/Pagination/index.js +5 -0
- package/lib/module/components/Pagination/index.js.map +1 -0
- package/lib/module/components/ScrollViewGesture.js +53 -34
- package/lib/module/components/ScrollViewGesture.js.map +1 -1
- package/lib/module/components/rnr-demo.test.js +33 -0
- package/lib/module/components/rnr-demo.test.js.map +1 -0
- package/lib/module/hooks/useCarouselController.js +12 -11
- package/lib/module/hooks/useCarouselController.js.map +1 -1
- package/lib/module/hooks/useCommonVariables.js +38 -8
- package/lib/module/hooks/useCommonVariables.js.map +1 -1
- package/lib/module/hooks/useCommonVariables.test.js +34 -0
- package/lib/module/hooks/useCommonVariables.test.js.map +1 -0
- package/lib/module/hooks/useLayoutConfig.js.map +1 -1
- package/lib/module/hooks/useOffsetX.js +9 -6
- package/lib/module/hooks/useOffsetX.js.map +1 -1
- package/lib/module/hooks/useOffsetX.test.js +48 -0
- package/lib/module/hooks/useOffsetX.test.js.map +1 -0
- package/lib/module/hooks/useOnProgressChange.js +4 -1
- package/lib/module/hooks/useOnProgressChange.js.map +1 -1
- package/lib/module/hooks/usePanGestureProxy.js +71 -0
- package/lib/module/hooks/usePanGestureProxy.js.map +1 -0
- package/lib/module/hooks/usePanGestureProxy.test.js +383 -0
- package/lib/module/hooks/usePanGestureProxy.test.js.map +1 -0
- package/lib/module/hooks/useUpdateGestureConfig.js.map +1 -1
- package/lib/module/hooks/useVisibleRanges.js +47 -19
- package/lib/module/hooks/useVisibleRanges.js.map +1 -1
- package/lib/module/hooks/useVisibleRanges.test.js +157 -0
- package/lib/module/hooks/useVisibleRanges.test.js.map +1 -0
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/utils/{computeNewIndexWhenDataChanges.js → compute-offset-if-data-changed.js} +2 -2
- package/lib/module/utils/compute-offset-if-data-changed.js.map +1 -0
- package/lib/module/utils/compute-offset-if-data-changed.test.js +27 -0
- package/lib/module/utils/compute-offset-if-data-changed.test.js.map +1 -0
- package/lib/module/utils/compute-offset-if-size-changed.js +11 -0
- package/lib/module/utils/compute-offset-if-size-changed.js.map +1 -0
- package/lib/module/utils/compute-offset-if-size-changed.test.js +69 -0
- package/lib/module/utils/compute-offset-if-size-changed.test.js.map +1 -0
- package/lib/module/utils/handleroffset-direction.js +5 -5
- package/lib/module/utils/handleroffset-direction.js.map +1 -1
- package/lib/module/utils/handleroffset-direction.test.js +41 -0
- package/lib/module/utils/handleroffset-direction.test.js.map +1 -0
- package/lib/module/utils/index.test.js +6 -6
- package/lib/module/utils/index.test.js.map +1 -1
- package/lib/typescript/components/ItemRenderer.d.ts +22 -0
- package/lib/typescript/components/Pagination/Basic/PaginationItem.d.ts +17 -0
- package/lib/typescript/components/Pagination/Basic/index.d.ts +16 -0
- package/lib/typescript/components/Pagination/index.d.ts +3 -0
- package/lib/typescript/components/ScrollViewGesture.d.ts +1 -1
- package/lib/typescript/components/rnr-demo.test.d.ts +1 -0
- package/lib/typescript/hooks/useCarouselController.d.ts +3 -2
- package/lib/typescript/hooks/useCommonVariables.test.d.ts +1 -0
- package/lib/typescript/hooks/useLayoutConfig.d.ts +1 -1
- package/lib/typescript/hooks/useOffsetX.test.d.ts +1 -0
- package/lib/typescript/hooks/usePanGestureProxy.d.ts +9 -0
- package/lib/typescript/hooks/usePanGestureProxy.test.d.ts +1 -0
- package/lib/typescript/hooks/useUpdateGestureConfig.d.ts +3 -2
- package/lib/typescript/hooks/useVisibleRanges.d.ts +8 -4
- package/lib/typescript/hooks/useVisibleRanges.test.d.ts +1 -0
- package/lib/typescript/index.d.ts +2 -0
- package/lib/typescript/types.d.ts +27 -16
- package/lib/typescript/utils/{computeNewIndexWhenDataChanges.d.ts → compute-offset-if-data-changed.d.ts} +1 -1
- package/lib/typescript/utils/compute-offset-if-data-changed.test.d.ts +1 -0
- package/lib/typescript/utils/compute-offset-if-size-changed.d.ts +5 -0
- package/lib/typescript/utils/compute-offset-if-size-changed.test.d.ts +1 -0
- package/lib/typescript/utils/handleroffset-direction.d.ts +2 -1
- package/lib/typescript/utils/handleroffset-direction.test.d.ts +1 -0
- package/package.json +18 -60
- package/src/{layouts → components}/BaseLayout.tsx +7 -35
- package/src/components/Carousel.tsx +33 -71
- package/src/components/ItemRenderer.tsx +105 -0
- package/src/components/Pagination/Basic/PaginationItem.tsx +111 -0
- package/src/components/Pagination/Basic/index.tsx +81 -0
- package/src/components/Pagination/index.tsx +5 -0
- package/src/components/ScrollViewGesture.tsx +74 -49
- package/src/components/rnr-demo.test.tsx +43 -0
- package/src/hooks/useCarouselController.tsx +24 -21
- package/src/hooks/useCommonVariables.test.tsx +41 -0
- package/src/hooks/useCommonVariables.ts +35 -10
- package/src/hooks/useLayoutConfig.ts +1 -1
- package/src/hooks/useOffsetX.test.ts +54 -0
- package/src/hooks/useOffsetX.ts +33 -31
- package/src/hooks/useOnProgressChange.ts +7 -2
- package/src/hooks/usePanGestureProxy.test.tsx +376 -0
- package/src/hooks/usePanGestureProxy.ts +110 -0
- package/src/hooks/useUpdateGestureConfig.ts +4 -2
- package/src/hooks/useVisibleRanges.test.tsx +179 -0
- package/src/hooks/useVisibleRanges.tsx +72 -24
- package/src/index.tsx +3 -0
- package/src/types.ts +28 -17
- package/src/utils/compute-offset-if-data-changed.test.ts +30 -0
- package/src/utils/{computeNewIndexWhenDataChanges.ts → compute-offset-if-data-changed.ts} +1 -1
- package/src/utils/compute-offset-if-size-changed.test.ts +78 -0
- package/src/utils/compute-offset-if-size-changed.ts +11 -0
- package/src/utils/handleroffset-direction.test.ts +52 -0
- package/src/utils/handleroffset-direction.ts +12 -9
- package/src/utils/index.test.ts +6 -6
- package/lib/commonjs/layouts/BaseLayout.js.map +0 -1
- package/lib/commonjs/layouts/ParallaxLayout.js +0 -84
- package/lib/commonjs/layouts/ParallaxLayout.js.map +0 -1
- package/lib/commonjs/utils/computeNewIndexWhenDataChanges.js.map +0 -1
- package/lib/module/layouts/BaseLayout.js.map +0 -1
- package/lib/module/layouts/ParallaxLayout.js +0 -61
- package/lib/module/layouts/ParallaxLayout.js.map +0 -1
- package/lib/module/utils/computeNewIndexWhenDataChanges.js.map +0 -1
- package/lib/typescript/layouts/ParallaxLayout.d.ts +0 -13
- package/src/layouts/ParallaxLayout.tsx +0 -141
- /package/lib/typescript/{layouts → components}/BaseLayout.d.ts +0 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { FC } from "react";
|
|
3
|
+
import type { ViewStyle } from "react-native";
|
|
4
|
+
import type Animated from "react-native-reanimated";
|
|
5
|
+
import { useAnimatedReaction, type AnimatedStyleProp, runOnJS } from "react-native-reanimated";
|
|
6
|
+
|
|
7
|
+
import type { TAnimationStyle } from "./BaseLayout";
|
|
8
|
+
import { BaseLayout } from "./BaseLayout";
|
|
9
|
+
|
|
10
|
+
import type { VisibleRanges } from "../hooks/useVisibleRanges";
|
|
11
|
+
import { useVisibleRanges } from "../hooks/useVisibleRanges";
|
|
12
|
+
import type { CarouselRenderItem } from "../types";
|
|
13
|
+
import { computedRealIndexWithAutoFillData } from "../utils/computed-with-auto-fill-data";
|
|
14
|
+
|
|
15
|
+
interface Props {
|
|
16
|
+
data: any[]
|
|
17
|
+
dataLength: number
|
|
18
|
+
rawDataLength: number
|
|
19
|
+
loop: boolean
|
|
20
|
+
size: number
|
|
21
|
+
windowSize?: number
|
|
22
|
+
autoFillData: boolean
|
|
23
|
+
offsetX: Animated.SharedValue<number>
|
|
24
|
+
handlerOffset: Animated.SharedValue<number>
|
|
25
|
+
layoutConfig: TAnimationStyle
|
|
26
|
+
renderItem: CarouselRenderItem<any>
|
|
27
|
+
customAnimation?: ((value: number) => AnimatedStyleProp<ViewStyle>)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const ItemRenderer: FC<Props> = (props) => {
|
|
31
|
+
const {
|
|
32
|
+
data,
|
|
33
|
+
size,
|
|
34
|
+
windowSize,
|
|
35
|
+
handlerOffset,
|
|
36
|
+
offsetX,
|
|
37
|
+
dataLength,
|
|
38
|
+
rawDataLength,
|
|
39
|
+
loop,
|
|
40
|
+
autoFillData,
|
|
41
|
+
layoutConfig,
|
|
42
|
+
renderItem,
|
|
43
|
+
customAnimation,
|
|
44
|
+
} = props;
|
|
45
|
+
|
|
46
|
+
const visibleRanges = useVisibleRanges({
|
|
47
|
+
total: dataLength,
|
|
48
|
+
viewSize: size,
|
|
49
|
+
translation: handlerOffset,
|
|
50
|
+
windowSize,
|
|
51
|
+
loop,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const [displayedItems, setDisplayedItems] = React.useState<VisibleRanges>(null!);
|
|
55
|
+
|
|
56
|
+
useAnimatedReaction(
|
|
57
|
+
() => visibleRanges.value,
|
|
58
|
+
ranges => runOnJS(setDisplayedItems)(ranges),
|
|
59
|
+
[visibleRanges],
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
if (!displayedItems)
|
|
63
|
+
return null;
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<>
|
|
67
|
+
{
|
|
68
|
+
data.map((item, index) => {
|
|
69
|
+
const realIndex = computedRealIndexWithAutoFillData({
|
|
70
|
+
index,
|
|
71
|
+
dataLength: rawDataLength,
|
|
72
|
+
loop,
|
|
73
|
+
autoFillData,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const { negativeRange, positiveRange } = displayedItems;
|
|
77
|
+
|
|
78
|
+
const shouldRender = (index >= negativeRange[0] && index <= negativeRange[1])
|
|
79
|
+
|| (index >= positiveRange[0] && index <= positiveRange[1]);
|
|
80
|
+
|
|
81
|
+
if (!shouldRender)
|
|
82
|
+
return null;
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<BaseLayout
|
|
86
|
+
key={index}
|
|
87
|
+
index={index}
|
|
88
|
+
handlerOffset={offsetX}
|
|
89
|
+
visibleRanges={visibleRanges}
|
|
90
|
+
animationStyle={customAnimation || layoutConfig}
|
|
91
|
+
>
|
|
92
|
+
{({ animationValue }) =>
|
|
93
|
+
renderItem({
|
|
94
|
+
item,
|
|
95
|
+
index: realIndex,
|
|
96
|
+
animationValue,
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
</BaseLayout>
|
|
100
|
+
);
|
|
101
|
+
})
|
|
102
|
+
}
|
|
103
|
+
</>
|
|
104
|
+
);
|
|
105
|
+
};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { PropsWithChildren } from "react";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import type { ViewStyle } from "react-native";
|
|
4
|
+
import { View } from "react-native";
|
|
5
|
+
import Animated, {
|
|
6
|
+
Extrapolate,
|
|
7
|
+
interpolate,
|
|
8
|
+
useAnimatedStyle,
|
|
9
|
+
} from "react-native-reanimated";
|
|
10
|
+
|
|
11
|
+
export type DotStyle = Omit<ViewStyle, "width" | "height"> & {
|
|
12
|
+
width?: number
|
|
13
|
+
height?: number
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const PaginationItem: React.FC<
|
|
17
|
+
PropsWithChildren<{
|
|
18
|
+
index: number
|
|
19
|
+
count: number
|
|
20
|
+
size?: number
|
|
21
|
+
animValue: Animated.SharedValue<number>
|
|
22
|
+
horizontal?: boolean
|
|
23
|
+
dotStyle?: DotStyle
|
|
24
|
+
activeDotStyle?: DotStyle
|
|
25
|
+
}>
|
|
26
|
+
> = (props) => {
|
|
27
|
+
const {
|
|
28
|
+
animValue,
|
|
29
|
+
dotStyle,
|
|
30
|
+
activeDotStyle,
|
|
31
|
+
index,
|
|
32
|
+
count,
|
|
33
|
+
size,
|
|
34
|
+
horizontal,
|
|
35
|
+
children,
|
|
36
|
+
} = props;
|
|
37
|
+
|
|
38
|
+
const defaultDotSize = 10;
|
|
39
|
+
|
|
40
|
+
const sizes = {
|
|
41
|
+
width: size || dotStyle?.width || defaultDotSize,
|
|
42
|
+
height: size || dotStyle?.height || defaultDotSize,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* TODO: Keep this for future implementation
|
|
47
|
+
* Used to change the size of the active dot with animation
|
|
48
|
+
*/
|
|
49
|
+
// const animatedSize = {
|
|
50
|
+
// width: activeDotStyle?.width,
|
|
51
|
+
// height: activeDotStyle?.height,
|
|
52
|
+
// };
|
|
53
|
+
|
|
54
|
+
const width = sizes.width;
|
|
55
|
+
const height = sizes.height;
|
|
56
|
+
|
|
57
|
+
const animStyle = useAnimatedStyle(() => {
|
|
58
|
+
const size = horizontal ? height : width;
|
|
59
|
+
let inputRange = [index - 1, index, index + 1];
|
|
60
|
+
let outputRange = [-size, 0, size];
|
|
61
|
+
|
|
62
|
+
if (index === 0 && animValue?.value > count - 1) {
|
|
63
|
+
inputRange = [count - 1, count, count + 1];
|
|
64
|
+
outputRange = [-size, 0, size];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
transform: [
|
|
69
|
+
{
|
|
70
|
+
translateX: interpolate(
|
|
71
|
+
animValue?.value,
|
|
72
|
+
inputRange,
|
|
73
|
+
outputRange,
|
|
74
|
+
Extrapolate.CLAMP,
|
|
75
|
+
),
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
};
|
|
79
|
+
}, [animValue, index, count, horizontal]);
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<View
|
|
83
|
+
style={[
|
|
84
|
+
{
|
|
85
|
+
width,
|
|
86
|
+
height,
|
|
87
|
+
overflow: "hidden",
|
|
88
|
+
transform: [
|
|
89
|
+
{
|
|
90
|
+
rotateZ: horizontal ? "90deg" : "0deg",
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
},
|
|
94
|
+
dotStyle,
|
|
95
|
+
]}
|
|
96
|
+
>
|
|
97
|
+
<Animated.View
|
|
98
|
+
style={[
|
|
99
|
+
{
|
|
100
|
+
backgroundColor: "black",
|
|
101
|
+
flex: 1,
|
|
102
|
+
},
|
|
103
|
+
animStyle,
|
|
104
|
+
activeDotStyle,
|
|
105
|
+
]}
|
|
106
|
+
>
|
|
107
|
+
{children}
|
|
108
|
+
</Animated.View>
|
|
109
|
+
</View>
|
|
110
|
+
);
|
|
111
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { StyleProp, ViewStyle } from "react-native";
|
|
3
|
+
import { View } from "react-native";
|
|
4
|
+
import { TouchableWithoutFeedback } from "react-native-gesture-handler";
|
|
5
|
+
import type { SharedValue } from "react-native-reanimated";
|
|
6
|
+
|
|
7
|
+
import type { DotStyle } from "./PaginationItem";
|
|
8
|
+
import { PaginationItem } from "./PaginationItem";
|
|
9
|
+
|
|
10
|
+
export interface BasicProps<T extends {} = {}> {
|
|
11
|
+
progress: SharedValue<number>
|
|
12
|
+
horizontal?: boolean
|
|
13
|
+
data: Array<T>
|
|
14
|
+
renderItem?: (item: T, index: number) => React.ReactNode
|
|
15
|
+
containerStyle?: StyleProp<ViewStyle>
|
|
16
|
+
dotStyle?: DotStyle
|
|
17
|
+
activeDotStyle?: DotStyle
|
|
18
|
+
size?: number
|
|
19
|
+
onPress?: (index: number) => void
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const Basic = <T extends {}>(props: BasicProps<T>) => {
|
|
23
|
+
const {
|
|
24
|
+
activeDotStyle,
|
|
25
|
+
dotStyle,
|
|
26
|
+
progress,
|
|
27
|
+
horizontal = true,
|
|
28
|
+
data,
|
|
29
|
+
size,
|
|
30
|
+
containerStyle,
|
|
31
|
+
renderItem,
|
|
32
|
+
onPress,
|
|
33
|
+
} = props;
|
|
34
|
+
|
|
35
|
+
if (
|
|
36
|
+
typeof size === "string" ||
|
|
37
|
+
typeof dotStyle?.width === "string" ||
|
|
38
|
+
typeof dotStyle?.height === "string"
|
|
39
|
+
)
|
|
40
|
+
throw new Error("size/width/height must be a number");
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<View
|
|
44
|
+
style={[
|
|
45
|
+
{
|
|
46
|
+
justifyContent: "space-between",
|
|
47
|
+
alignSelf: "center",
|
|
48
|
+
},
|
|
49
|
+
horizontal
|
|
50
|
+
? {
|
|
51
|
+
flexDirection: "row",
|
|
52
|
+
}
|
|
53
|
+
: {
|
|
54
|
+
flexDirection: "column",
|
|
55
|
+
},
|
|
56
|
+
containerStyle,
|
|
57
|
+
]}
|
|
58
|
+
>
|
|
59
|
+
{data.map((item, index) => {
|
|
60
|
+
return (
|
|
61
|
+
<TouchableWithoutFeedback
|
|
62
|
+
key={index}
|
|
63
|
+
onPress={() => onPress?.(index)}
|
|
64
|
+
>
|
|
65
|
+
<PaginationItem
|
|
66
|
+
index={index}
|
|
67
|
+
size={size}
|
|
68
|
+
count={data.length}
|
|
69
|
+
dotStyle={dotStyle}
|
|
70
|
+
animValue={progress}
|
|
71
|
+
horizontal={!horizontal}
|
|
72
|
+
activeDotStyle={activeDotStyle}
|
|
73
|
+
>
|
|
74
|
+
{renderItem?.(item, index)}
|
|
75
|
+
</PaginationItem>
|
|
76
|
+
</TouchableWithoutFeedback>
|
|
77
|
+
);
|
|
78
|
+
})}
|
|
79
|
+
</View>
|
|
80
|
+
);
|
|
81
|
+
};
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import type { PropsWithChildren } from "react";
|
|
2
|
-
import React, { useCallback
|
|
2
|
+
import React, { useCallback } from "react";
|
|
3
3
|
import type { StyleProp, ViewStyle } from "react-native";
|
|
4
4
|
import type { GestureStateChangeEvent, PanGestureHandlerEventPayload } from "react-native-gesture-handler";
|
|
5
|
-
import {
|
|
6
|
-
Gesture,
|
|
7
|
-
GestureDetector,
|
|
8
|
-
} from "react-native-gesture-handler";
|
|
5
|
+
import { GestureDetector } from "react-native-gesture-handler";
|
|
9
6
|
import Animated, {
|
|
10
7
|
cancelAnimation,
|
|
11
8
|
measure,
|
|
@@ -18,7 +15,7 @@ import Animated, {
|
|
|
18
15
|
} from "react-native-reanimated";
|
|
19
16
|
|
|
20
17
|
import { Easing } from "../constants";
|
|
21
|
-
import {
|
|
18
|
+
import { usePanGestureProxy } from "../hooks/usePanGestureProxy";
|
|
22
19
|
import { CTX } from "../store";
|
|
23
20
|
import type { WithTimingAnimation } from "../types";
|
|
24
21
|
import { dealWithAnimation } from "../utils/deal-with-animation";
|
|
@@ -28,7 +25,7 @@ interface Props {
|
|
|
28
25
|
infinite?: boolean
|
|
29
26
|
testID?: string
|
|
30
27
|
style?: StyleProp<ViewStyle>
|
|
31
|
-
|
|
28
|
+
onScrollStart?: () => void
|
|
32
29
|
onScrollEnd?: () => void
|
|
33
30
|
onTouchBegin?: () => void
|
|
34
31
|
onTouchEnd?: () => void
|
|
@@ -42,13 +39,15 @@ const IScrollViewGesture: React.FC<PropsWithChildren<Props>> = (props) => {
|
|
|
42
39
|
vertical,
|
|
43
40
|
pagingEnabled,
|
|
44
41
|
snapEnabled,
|
|
45
|
-
loop
|
|
42
|
+
loop,
|
|
46
43
|
scrollAnimationDuration,
|
|
47
44
|
withAnimation,
|
|
48
45
|
enabled,
|
|
49
46
|
dataLength,
|
|
50
47
|
overscrollEnabled,
|
|
51
48
|
maxScrollDistancePerSwipe,
|
|
49
|
+
minScrollDistancePerSwipe,
|
|
50
|
+
fixedDirection,
|
|
52
51
|
},
|
|
53
52
|
} = React.useContext(CTX);
|
|
54
53
|
|
|
@@ -57,7 +56,7 @@ const IScrollViewGesture: React.FC<PropsWithChildren<Props>> = (props) => {
|
|
|
57
56
|
translation,
|
|
58
57
|
testID,
|
|
59
58
|
style = {},
|
|
60
|
-
|
|
59
|
+
onScrollStart,
|
|
61
60
|
onScrollEnd,
|
|
62
61
|
onTouchBegin,
|
|
63
62
|
onTouchEnd,
|
|
@@ -73,12 +72,13 @@ const IScrollViewGesture: React.FC<PropsWithChildren<Props>> = (props) => {
|
|
|
73
72
|
const scrollEndVelocity = useSharedValue(0);
|
|
74
73
|
const containerRef = useAnimatedRef<Animated.View>();
|
|
75
74
|
const maxScrollDistancePerSwipeIsSet = typeof maxScrollDistancePerSwipe === "number";
|
|
75
|
+
const minScrollDistancePerSwipeIsSet = typeof minScrollDistancePerSwipe === "number";
|
|
76
76
|
|
|
77
77
|
// Get the limit of the scroll.
|
|
78
78
|
const getLimit = React.useCallback(() => {
|
|
79
79
|
"worklet";
|
|
80
80
|
|
|
81
|
-
if (!
|
|
81
|
+
if (!loop && !overscrollEnabled) {
|
|
82
82
|
const { width: containerWidth = 0 } = measure(containerRef);
|
|
83
83
|
|
|
84
84
|
// If the item's total width is less than the container's width, then there is no need to scroll.
|
|
@@ -90,7 +90,7 @@ const IScrollViewGesture: React.FC<PropsWithChildren<Props>> = (props) => {
|
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
return dataLength * size;
|
|
93
|
-
}, [
|
|
93
|
+
}, [loop, size, dataLength, overscrollEnabled]);
|
|
94
94
|
|
|
95
95
|
const withSpring = React.useCallback(
|
|
96
96
|
(toValue: number, onFinished?: () => void) => {
|
|
@@ -141,7 +141,7 @@ const IScrollViewGesture: React.FC<PropsWithChildren<Props>> = (props) => {
|
|
|
141
141
|
const computed = offset < 0 ? Math.ceil : Math.floor;
|
|
142
142
|
const page = computed(-translation.value / size);
|
|
143
143
|
|
|
144
|
-
if (
|
|
144
|
+
if (loop) {
|
|
145
145
|
const finalPage = page + offset;
|
|
146
146
|
finalTranslation = withSpring(withProcessTranslation(-finalPage * size), onFinished);
|
|
147
147
|
}
|
|
@@ -161,7 +161,7 @@ const IScrollViewGesture: React.FC<PropsWithChildren<Props>> = (props) => {
|
|
|
161
161
|
translation.value = finalTranslation;
|
|
162
162
|
|
|
163
163
|
function withProcessTranslation(translation: number) {
|
|
164
|
-
if (!
|
|
164
|
+
if (!loop && !overscrollEnabled) {
|
|
165
165
|
const limit = getLimit();
|
|
166
166
|
const sign = Math.sign(translation);
|
|
167
167
|
return sign * Math.max(0, Math.min(limit, Math.abs(translation)));
|
|
@@ -174,7 +174,7 @@ const IScrollViewGesture: React.FC<PropsWithChildren<Props>> = (props) => {
|
|
|
174
174
|
withSpring,
|
|
175
175
|
size,
|
|
176
176
|
maxPage,
|
|
177
|
-
|
|
177
|
+
loop,
|
|
178
178
|
snapEnabled,
|
|
179
179
|
translation,
|
|
180
180
|
pagingEnabled,
|
|
@@ -215,7 +215,7 @@ const IScrollViewGesture: React.FC<PropsWithChildren<Props>> = (props) => {
|
|
|
215
215
|
activeDecay();
|
|
216
216
|
return;
|
|
217
217
|
}
|
|
218
|
-
if (!
|
|
218
|
+
if (!loop) {
|
|
219
219
|
translation.value = withSpring(0);
|
|
220
220
|
return;
|
|
221
221
|
}
|
|
@@ -226,7 +226,7 @@ const IScrollViewGesture: React.FC<PropsWithChildren<Props>> = (props) => {
|
|
|
226
226
|
activeDecay();
|
|
227
227
|
return;
|
|
228
228
|
}
|
|
229
|
-
if (!
|
|
229
|
+
if (!loop)
|
|
230
230
|
translation.value = withSpring(-((maxPage - 1) * size));
|
|
231
231
|
}
|
|
232
232
|
}, [
|
|
@@ -235,7 +235,7 @@ const IScrollViewGesture: React.FC<PropsWithChildren<Props>> = (props) => {
|
|
|
235
235
|
maxPage,
|
|
236
236
|
size,
|
|
237
237
|
scrollEndTranslation.value,
|
|
238
|
-
|
|
238
|
+
loop,
|
|
239
239
|
activeDecay,
|
|
240
240
|
withSpring,
|
|
241
241
|
]);
|
|
@@ -252,7 +252,7 @@ const IScrollViewGesture: React.FC<PropsWithChildren<Props>> = (props) => {
|
|
|
252
252
|
function withProcessTranslation(translation: number) {
|
|
253
253
|
"worklet";
|
|
254
254
|
|
|
255
|
-
if (!
|
|
255
|
+
if (!loop && !overscrollEnabled) {
|
|
256
256
|
const limit = getLimit();
|
|
257
257
|
const sign = Math.sign(translation);
|
|
258
258
|
return sign * Math.max(0, Math.min(limit, Math.abs(translation)));
|
|
@@ -261,15 +261,15 @@ const IScrollViewGesture: React.FC<PropsWithChildren<Props>> = (props) => {
|
|
|
261
261
|
return translation;
|
|
262
262
|
}
|
|
263
263
|
|
|
264
|
-
const
|
|
264
|
+
const onGestureStart = useCallback((_: PanGestureHandlerEventPayload) => {
|
|
265
265
|
"worklet";
|
|
266
266
|
|
|
267
267
|
touching.value = true;
|
|
268
268
|
validStart.value = true;
|
|
269
|
-
|
|
269
|
+
onScrollStart && runOnJS(onScrollStart)();
|
|
270
270
|
|
|
271
271
|
max.value = (maxPage - 1) * size;
|
|
272
|
-
if (!
|
|
272
|
+
if (!loop && !overscrollEnabled)
|
|
273
273
|
max.value = getLimit();
|
|
274
274
|
|
|
275
275
|
panOffset.value = translation.value;
|
|
@@ -277,14 +277,14 @@ const IScrollViewGesture: React.FC<PropsWithChildren<Props>> = (props) => {
|
|
|
277
277
|
max,
|
|
278
278
|
size,
|
|
279
279
|
maxPage,
|
|
280
|
-
|
|
280
|
+
loop,
|
|
281
281
|
touching,
|
|
282
282
|
panOffset,
|
|
283
283
|
validStart,
|
|
284
284
|
translation,
|
|
285
285
|
overscrollEnabled,
|
|
286
286
|
getLimit,
|
|
287
|
-
|
|
287
|
+
onScrollStart,
|
|
288
288
|
]);
|
|
289
289
|
|
|
290
290
|
const onGestureUpdate = useCallback((e: PanGestureHandlerEventPayload) => {
|
|
@@ -296,10 +296,18 @@ const IScrollViewGesture: React.FC<PropsWithChildren<Props>> = (props) => {
|
|
|
296
296
|
}
|
|
297
297
|
touching.value = true;
|
|
298
298
|
const { translationX, translationY } = e;
|
|
299
|
-
|
|
299
|
+
|
|
300
|
+
let panTranslation = isHorizontal.value
|
|
300
301
|
? translationX
|
|
301
302
|
: translationY;
|
|
302
|
-
|
|
303
|
+
|
|
304
|
+
if (fixedDirection === "negative")
|
|
305
|
+
panTranslation = -Math.abs(panTranslation);
|
|
306
|
+
|
|
307
|
+
else if (fixedDirection === "positive")
|
|
308
|
+
panTranslation = +Math.abs(panTranslation);
|
|
309
|
+
|
|
310
|
+
if (!loop) {
|
|
303
311
|
if ((translation.value > 0 || translation.value < -max.value)) {
|
|
304
312
|
const boundary = translation.value > 0 ? 0 : -max.value;
|
|
305
313
|
const fixed = boundary - panOffset.value;
|
|
@@ -315,71 +323,88 @@ const IScrollViewGesture: React.FC<PropsWithChildren<Props>> = (props) => {
|
|
|
315
323
|
isHorizontal,
|
|
316
324
|
max,
|
|
317
325
|
panOffset,
|
|
318
|
-
|
|
326
|
+
loop,
|
|
319
327
|
overscrollEnabled,
|
|
328
|
+
fixedDirection,
|
|
320
329
|
translation,
|
|
321
330
|
validStart,
|
|
322
331
|
touching,
|
|
323
332
|
]);
|
|
324
333
|
|
|
325
|
-
const
|
|
334
|
+
const onGestureEnd = useCallback((e: GestureStateChangeEvent<PanGestureHandlerEventPayload>, _success: boolean) => {
|
|
326
335
|
"worklet";
|
|
327
336
|
|
|
328
337
|
const { velocityX, velocityY, translationX, translationY } = e;
|
|
329
338
|
scrollEndVelocity.value = isHorizontal.value
|
|
330
339
|
? velocityX
|
|
331
340
|
: velocityY;
|
|
332
|
-
|
|
341
|
+
|
|
342
|
+
let panTranslation = isHorizontal.value
|
|
333
343
|
? translationX
|
|
334
344
|
: translationY;
|
|
335
345
|
|
|
346
|
+
if (fixedDirection === "negative")
|
|
347
|
+
panTranslation = -Math.abs(panTranslation);
|
|
348
|
+
|
|
349
|
+
else if (fixedDirection === "positive")
|
|
350
|
+
panTranslation = +Math.abs(panTranslation);
|
|
351
|
+
|
|
352
|
+
scrollEndTranslation.value = panTranslation;
|
|
353
|
+
|
|
336
354
|
const totalTranslation = scrollEndVelocity.value + scrollEndTranslation.value;
|
|
337
355
|
|
|
338
|
-
|
|
356
|
+
/**
|
|
357
|
+
* If the maximum scroll distance is set and the translation `exceeds the maximum scroll distance`,
|
|
358
|
+
* the carousel will keep the view at the current position.
|
|
359
|
+
*/
|
|
360
|
+
if (
|
|
361
|
+
maxScrollDistancePerSwipeIsSet && Math.abs(totalTranslation) > maxScrollDistancePerSwipe
|
|
362
|
+
) {
|
|
339
363
|
const nextPage = Math.round((panOffset.value + maxScrollDistancePerSwipe * Math.sign(totalTranslation)) / size) * size;
|
|
340
364
|
translation.value = withSpring(withProcessTranslation(nextPage), onScrollEnd);
|
|
341
365
|
}
|
|
366
|
+
/**
|
|
367
|
+
* If the minimum scroll distance is set and the translation `didn't exceeds the minimum scroll distance`,
|
|
368
|
+
* the carousel will keep the view at the current position.
|
|
369
|
+
*/
|
|
370
|
+
else if (
|
|
371
|
+
minScrollDistancePerSwipeIsSet && Math.abs(totalTranslation) < minScrollDistancePerSwipe
|
|
372
|
+
) {
|
|
373
|
+
const nextPage = Math.round((panOffset.value + minScrollDistancePerSwipe * Math.sign(totalTranslation)) / size) * size;
|
|
374
|
+
translation.value = withSpring(withProcessTranslation(nextPage), onScrollEnd);
|
|
375
|
+
}
|
|
342
376
|
else {
|
|
343
377
|
endWithSpring(onScrollEnd);
|
|
344
378
|
}
|
|
345
379
|
|
|
346
|
-
if (!
|
|
380
|
+
if (!loop)
|
|
347
381
|
touching.value = false;
|
|
348
382
|
}, [
|
|
349
383
|
size,
|
|
350
|
-
|
|
384
|
+
loop,
|
|
351
385
|
touching,
|
|
352
386
|
panOffset,
|
|
353
387
|
translation,
|
|
354
388
|
isHorizontal,
|
|
355
389
|
scrollEndVelocity,
|
|
356
390
|
scrollEndTranslation,
|
|
391
|
+
fixedDirection,
|
|
357
392
|
maxScrollDistancePerSwipeIsSet,
|
|
358
393
|
maxScrollDistancePerSwipe,
|
|
394
|
+
maxScrollDistancePerSwipeIsSet,
|
|
395
|
+
minScrollDistancePerSwipe,
|
|
359
396
|
endWithSpring,
|
|
360
397
|
withSpring,
|
|
361
398
|
onScrollEnd,
|
|
362
399
|
]);
|
|
363
400
|
|
|
364
|
-
const gesture =
|
|
365
|
-
const gesture = Gesture.Pan()
|
|
366
|
-
.onBegin(onGestureBegin)
|
|
367
|
-
.onUpdate(onGestureUpdate)
|
|
368
|
-
.onEnd(onGestureFinish);
|
|
369
|
-
|
|
370
|
-
if (onConfigurePanGesture)
|
|
371
|
-
onConfigurePanGesture(gesture);
|
|
372
|
-
|
|
373
|
-
return gesture;
|
|
374
|
-
},
|
|
375
|
-
[
|
|
376
|
-
onGestureBegin,
|
|
377
|
-
onGestureUpdate,
|
|
378
|
-
onGestureFinish,
|
|
401
|
+
const gesture = usePanGestureProxy({
|
|
379
402
|
onConfigurePanGesture,
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
403
|
+
onGestureStart,
|
|
404
|
+
onGestureUpdate,
|
|
405
|
+
onGestureEnd,
|
|
406
|
+
options: { enabled },
|
|
407
|
+
});
|
|
383
408
|
|
|
384
409
|
return (
|
|
385
410
|
<GestureDetector gesture={gesture}>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { FC } from "react";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import Animated, { useAnimatedStyle, useDerivedValue } from "react-native-reanimated";
|
|
4
|
+
import renderer from "react-test-renderer";
|
|
5
|
+
|
|
6
|
+
describe("useSharedValue", () => {
|
|
7
|
+
it("retains value on rerender", () => {
|
|
8
|
+
const initialValue = 0;
|
|
9
|
+
const updatedValue = 1;
|
|
10
|
+
|
|
11
|
+
interface Props {
|
|
12
|
+
key: string
|
|
13
|
+
value: number
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const TestComponent: FC<Props> = (props) => {
|
|
17
|
+
const opacity = useDerivedValue(() => props.value, [props.value]);
|
|
18
|
+
const animatedStyle = useAnimatedStyle(() => ({
|
|
19
|
+
opacity: opacity.value,
|
|
20
|
+
}), [opacity]);
|
|
21
|
+
|
|
22
|
+
return <Animated.View style={animatedStyle} />;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// When rendering with initial value
|
|
26
|
+
const wrapper = renderer.create(<TestComponent key="box" value={initialValue} />);
|
|
27
|
+
|
|
28
|
+
expect(
|
|
29
|
+
typeof wrapper.root.children[0] !== "string"
|
|
30
|
+
? wrapper.root.children[0].props.style.animatedStyle.current.value.opacity
|
|
31
|
+
: false,
|
|
32
|
+
).toBe(initialValue);
|
|
33
|
+
|
|
34
|
+
// When rendering with updated value
|
|
35
|
+
wrapper.update(<TestComponent key="box" value={updatedValue} />);
|
|
36
|
+
|
|
37
|
+
expect(
|
|
38
|
+
typeof wrapper.root.children[0] !== "string"
|
|
39
|
+
? wrapper.root.children[0].props.style.animatedStyle.current.value.opacity
|
|
40
|
+
: false,
|
|
41
|
+
).toBe(initialValue);
|
|
42
|
+
});
|
|
43
|
+
});
|