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.
Files changed (173) hide show
  1. package/README.md +2 -3
  2. package/lib/commonjs/{layouts → components}/BaseLayout.js +5 -21
  3. package/lib/commonjs/components/BaseLayout.js.map +1 -0
  4. package/lib/commonjs/components/Carousel.js +28 -48
  5. package/lib/commonjs/components/Carousel.js.map +1 -1
  6. package/lib/commonjs/components/ItemRenderer.js +80 -0
  7. package/lib/commonjs/components/ItemRenderer.js.map +1 -0
  8. package/lib/commonjs/components/Pagination/Basic/PaginationItem.js +81 -0
  9. package/lib/commonjs/components/Pagination/Basic/PaginationItem.js.map +1 -0
  10. package/lib/commonjs/components/Pagination/Basic/index.js +57 -0
  11. package/lib/commonjs/components/Pagination/Basic/index.js.map +1 -0
  12. package/lib/commonjs/components/Pagination/index.js +14 -0
  13. package/lib/commonjs/components/Pagination/index.js.map +1 -0
  14. package/lib/commonjs/components/ScrollViewGesture.js +51 -33
  15. package/lib/commonjs/components/ScrollViewGesture.js.map +1 -1
  16. package/lib/commonjs/components/rnr-demo.test.js +45 -0
  17. package/lib/commonjs/components/rnr-demo.test.js.map +1 -0
  18. package/lib/commonjs/hooks/useCarouselController.js +12 -11
  19. package/lib/commonjs/hooks/useCarouselController.js.map +1 -1
  20. package/lib/commonjs/hooks/useCommonVariables.js +38 -12
  21. package/lib/commonjs/hooks/useCommonVariables.js.map +1 -1
  22. package/lib/commonjs/hooks/useCommonVariables.test.js +38 -0
  23. package/lib/commonjs/hooks/useCommonVariables.test.js.map +1 -0
  24. package/lib/commonjs/hooks/useLayoutConfig.js.map +1 -1
  25. package/lib/commonjs/hooks/useOffsetX.js +9 -6
  26. package/lib/commonjs/hooks/useOffsetX.js.map +1 -1
  27. package/lib/commonjs/hooks/useOffsetX.test.js +53 -0
  28. package/lib/commonjs/hooks/useOffsetX.test.js.map +1 -0
  29. package/lib/commonjs/hooks/useOnProgressChange.js +4 -1
  30. package/lib/commonjs/hooks/useOnProgressChange.js.map +1 -1
  31. package/lib/commonjs/hooks/usePanGestureProxy.js +84 -0
  32. package/lib/commonjs/hooks/usePanGestureProxy.js.map +1 -0
  33. package/lib/commonjs/hooks/usePanGestureProxy.test.js +397 -0
  34. package/lib/commonjs/hooks/usePanGestureProxy.test.js.map +1 -0
  35. package/lib/commonjs/hooks/useUpdateGestureConfig.js.map +1 -1
  36. package/lib/commonjs/hooks/useVisibleRanges.js +48 -19
  37. package/lib/commonjs/hooks/useVisibleRanges.js.map +1 -1
  38. package/lib/commonjs/hooks/useVisibleRanges.test.js +162 -0
  39. package/lib/commonjs/hooks/useVisibleRanges.test.js.map +1 -0
  40. package/lib/commonjs/index.js +8 -0
  41. package/lib/commonjs/index.js.map +1 -1
  42. package/lib/commonjs/utils/{computeNewIndexWhenDataChanges.js → compute-offset-if-data-changed.js} +3 -3
  43. package/lib/commonjs/utils/compute-offset-if-data-changed.js.map +1 -0
  44. package/lib/commonjs/utils/compute-offset-if-data-changed.test.js +30 -0
  45. package/lib/commonjs/utils/compute-offset-if-data-changed.test.js.map +1 -0
  46. package/lib/commonjs/utils/compute-offset-if-size-changed.js +18 -0
  47. package/lib/commonjs/utils/compute-offset-if-size-changed.js.map +1 -0
  48. package/lib/commonjs/utils/compute-offset-if-size-changed.test.js +72 -0
  49. package/lib/commonjs/utils/compute-offset-if-size-changed.test.js.map +1 -0
  50. package/lib/commonjs/utils/handleroffset-direction.js +5 -5
  51. package/lib/commonjs/utils/handleroffset-direction.js.map +1 -1
  52. package/lib/commonjs/utils/handleroffset-direction.test.js +46 -0
  53. package/lib/commonjs/utils/handleroffset-direction.test.js.map +1 -0
  54. package/lib/commonjs/utils/index.test.js +6 -6
  55. package/lib/commonjs/utils/index.test.js.map +1 -1
  56. package/lib/module/{layouts → components}/BaseLayout.js +6 -16
  57. package/lib/module/components/BaseLayout.js.map +1 -0
  58. package/lib/module/components/Carousel.js +27 -44
  59. package/lib/module/components/Carousel.js.map +1 -1
  60. package/lib/module/components/ItemRenderer.js +62 -0
  61. package/lib/module/components/ItemRenderer.js.map +1 -0
  62. package/lib/module/components/Pagination/Basic/PaginationItem.js +63 -0
  63. package/lib/module/components/Pagination/Basic/PaginationItem.js.map +1 -0
  64. package/lib/module/components/Pagination/Basic/index.js +42 -0
  65. package/lib/module/components/Pagination/Basic/index.js.map +1 -0
  66. package/lib/module/components/Pagination/index.js +5 -0
  67. package/lib/module/components/Pagination/index.js.map +1 -0
  68. package/lib/module/components/ScrollViewGesture.js +53 -34
  69. package/lib/module/components/ScrollViewGesture.js.map +1 -1
  70. package/lib/module/components/rnr-demo.test.js +33 -0
  71. package/lib/module/components/rnr-demo.test.js.map +1 -0
  72. package/lib/module/hooks/useCarouselController.js +12 -11
  73. package/lib/module/hooks/useCarouselController.js.map +1 -1
  74. package/lib/module/hooks/useCommonVariables.js +38 -8
  75. package/lib/module/hooks/useCommonVariables.js.map +1 -1
  76. package/lib/module/hooks/useCommonVariables.test.js +34 -0
  77. package/lib/module/hooks/useCommonVariables.test.js.map +1 -0
  78. package/lib/module/hooks/useLayoutConfig.js.map +1 -1
  79. package/lib/module/hooks/useOffsetX.js +9 -6
  80. package/lib/module/hooks/useOffsetX.js.map +1 -1
  81. package/lib/module/hooks/useOffsetX.test.js +48 -0
  82. package/lib/module/hooks/useOffsetX.test.js.map +1 -0
  83. package/lib/module/hooks/useOnProgressChange.js +4 -1
  84. package/lib/module/hooks/useOnProgressChange.js.map +1 -1
  85. package/lib/module/hooks/usePanGestureProxy.js +71 -0
  86. package/lib/module/hooks/usePanGestureProxy.js.map +1 -0
  87. package/lib/module/hooks/usePanGestureProxy.test.js +383 -0
  88. package/lib/module/hooks/usePanGestureProxy.test.js.map +1 -0
  89. package/lib/module/hooks/useUpdateGestureConfig.js.map +1 -1
  90. package/lib/module/hooks/useVisibleRanges.js +47 -19
  91. package/lib/module/hooks/useVisibleRanges.js.map +1 -1
  92. package/lib/module/hooks/useVisibleRanges.test.js +157 -0
  93. package/lib/module/hooks/useVisibleRanges.test.js.map +1 -0
  94. package/lib/module/index.js +1 -0
  95. package/lib/module/index.js.map +1 -1
  96. package/lib/module/utils/{computeNewIndexWhenDataChanges.js → compute-offset-if-data-changed.js} +2 -2
  97. package/lib/module/utils/compute-offset-if-data-changed.js.map +1 -0
  98. package/lib/module/utils/compute-offset-if-data-changed.test.js +27 -0
  99. package/lib/module/utils/compute-offset-if-data-changed.test.js.map +1 -0
  100. package/lib/module/utils/compute-offset-if-size-changed.js +11 -0
  101. package/lib/module/utils/compute-offset-if-size-changed.js.map +1 -0
  102. package/lib/module/utils/compute-offset-if-size-changed.test.js +69 -0
  103. package/lib/module/utils/compute-offset-if-size-changed.test.js.map +1 -0
  104. package/lib/module/utils/handleroffset-direction.js +5 -5
  105. package/lib/module/utils/handleroffset-direction.js.map +1 -1
  106. package/lib/module/utils/handleroffset-direction.test.js +41 -0
  107. package/lib/module/utils/handleroffset-direction.test.js.map +1 -0
  108. package/lib/module/utils/index.test.js +6 -6
  109. package/lib/module/utils/index.test.js.map +1 -1
  110. package/lib/typescript/components/ItemRenderer.d.ts +22 -0
  111. package/lib/typescript/components/Pagination/Basic/PaginationItem.d.ts +17 -0
  112. package/lib/typescript/components/Pagination/Basic/index.d.ts +16 -0
  113. package/lib/typescript/components/Pagination/index.d.ts +3 -0
  114. package/lib/typescript/components/ScrollViewGesture.d.ts +1 -1
  115. package/lib/typescript/components/rnr-demo.test.d.ts +1 -0
  116. package/lib/typescript/hooks/useCarouselController.d.ts +3 -2
  117. package/lib/typescript/hooks/useCommonVariables.test.d.ts +1 -0
  118. package/lib/typescript/hooks/useLayoutConfig.d.ts +1 -1
  119. package/lib/typescript/hooks/useOffsetX.test.d.ts +1 -0
  120. package/lib/typescript/hooks/usePanGestureProxy.d.ts +9 -0
  121. package/lib/typescript/hooks/usePanGestureProxy.test.d.ts +1 -0
  122. package/lib/typescript/hooks/useUpdateGestureConfig.d.ts +3 -2
  123. package/lib/typescript/hooks/useVisibleRanges.d.ts +8 -4
  124. package/lib/typescript/hooks/useVisibleRanges.test.d.ts +1 -0
  125. package/lib/typescript/index.d.ts +2 -0
  126. package/lib/typescript/types.d.ts +27 -16
  127. package/lib/typescript/utils/{computeNewIndexWhenDataChanges.d.ts → compute-offset-if-data-changed.d.ts} +1 -1
  128. package/lib/typescript/utils/compute-offset-if-data-changed.test.d.ts +1 -0
  129. package/lib/typescript/utils/compute-offset-if-size-changed.d.ts +5 -0
  130. package/lib/typescript/utils/compute-offset-if-size-changed.test.d.ts +1 -0
  131. package/lib/typescript/utils/handleroffset-direction.d.ts +2 -1
  132. package/lib/typescript/utils/handleroffset-direction.test.d.ts +1 -0
  133. package/package.json +18 -60
  134. package/src/{layouts → components}/BaseLayout.tsx +7 -35
  135. package/src/components/Carousel.tsx +33 -71
  136. package/src/components/ItemRenderer.tsx +105 -0
  137. package/src/components/Pagination/Basic/PaginationItem.tsx +111 -0
  138. package/src/components/Pagination/Basic/index.tsx +81 -0
  139. package/src/components/Pagination/index.tsx +5 -0
  140. package/src/components/ScrollViewGesture.tsx +74 -49
  141. package/src/components/rnr-demo.test.tsx +43 -0
  142. package/src/hooks/useCarouselController.tsx +24 -21
  143. package/src/hooks/useCommonVariables.test.tsx +41 -0
  144. package/src/hooks/useCommonVariables.ts +35 -10
  145. package/src/hooks/useLayoutConfig.ts +1 -1
  146. package/src/hooks/useOffsetX.test.ts +54 -0
  147. package/src/hooks/useOffsetX.ts +33 -31
  148. package/src/hooks/useOnProgressChange.ts +7 -2
  149. package/src/hooks/usePanGestureProxy.test.tsx +376 -0
  150. package/src/hooks/usePanGestureProxy.ts +110 -0
  151. package/src/hooks/useUpdateGestureConfig.ts +4 -2
  152. package/src/hooks/useVisibleRanges.test.tsx +179 -0
  153. package/src/hooks/useVisibleRanges.tsx +72 -24
  154. package/src/index.tsx +3 -0
  155. package/src/types.ts +28 -17
  156. package/src/utils/compute-offset-if-data-changed.test.ts +30 -0
  157. package/src/utils/{computeNewIndexWhenDataChanges.ts → compute-offset-if-data-changed.ts} +1 -1
  158. package/src/utils/compute-offset-if-size-changed.test.ts +78 -0
  159. package/src/utils/compute-offset-if-size-changed.ts +11 -0
  160. package/src/utils/handleroffset-direction.test.ts +52 -0
  161. package/src/utils/handleroffset-direction.ts +12 -9
  162. package/src/utils/index.test.ts +6 -6
  163. package/lib/commonjs/layouts/BaseLayout.js.map +0 -1
  164. package/lib/commonjs/layouts/ParallaxLayout.js +0 -84
  165. package/lib/commonjs/layouts/ParallaxLayout.js.map +0 -1
  166. package/lib/commonjs/utils/computeNewIndexWhenDataChanges.js.map +0 -1
  167. package/lib/module/layouts/BaseLayout.js.map +0 -1
  168. package/lib/module/layouts/ParallaxLayout.js +0 -61
  169. package/lib/module/layouts/ParallaxLayout.js.map +0 -1
  170. package/lib/module/utils/computeNewIndexWhenDataChanges.js.map +0 -1
  171. package/lib/typescript/layouts/ParallaxLayout.d.ts +0 -13
  172. package/src/layouts/ParallaxLayout.tsx +0 -141
  173. /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
+ };
@@ -0,0 +1,5 @@
1
+ import { Basic } from "./Basic";
2
+
3
+ export const Pagination = {
4
+ Basic,
5
+ };
@@ -1,11 +1,8 @@
1
1
  import type { PropsWithChildren } from "react";
2
- import React, { useCallback, useMemo } from "react";
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 { useUpdateGestureConfig } from "../hooks/useUpdateGestureConfig";
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
- onScrollBegin?: () => void
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: infinite,
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
- onScrollBegin,
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 (!infinite && !overscrollEnabled) {
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
- }, [infinite, size, dataLength, overscrollEnabled]);
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 (infinite) {
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 (!infinite && !overscrollEnabled) {
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
- infinite,
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 (!infinite) {
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 (!infinite)
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
- infinite,
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 (!infinite && !overscrollEnabled) {
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 onGestureBegin = useCallback(() => {
264
+ const onGestureStart = useCallback((_: PanGestureHandlerEventPayload) => {
265
265
  "worklet";
266
266
 
267
267
  touching.value = true;
268
268
  validStart.value = true;
269
- onScrollBegin && runOnJS(onScrollBegin)();
269
+ onScrollStart && runOnJS(onScrollStart)();
270
270
 
271
271
  max.value = (maxPage - 1) * size;
272
- if (!infinite && !overscrollEnabled)
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
- infinite,
280
+ loop,
281
281
  touching,
282
282
  panOffset,
283
283
  validStart,
284
284
  translation,
285
285
  overscrollEnabled,
286
286
  getLimit,
287
- onScrollBegin,
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
- const panTranslation = isHorizontal.value
299
+
300
+ let panTranslation = isHorizontal.value
300
301
  ? translationX
301
302
  : translationY;
302
- if (!infinite) {
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
- infinite,
326
+ loop,
319
327
  overscrollEnabled,
328
+ fixedDirection,
320
329
  translation,
321
330
  validStart,
322
331
  touching,
323
332
  ]);
324
333
 
325
- const onGestureFinish = useCallback((e: GestureStateChangeEvent<PanGestureHandlerEventPayload>) => {
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
- scrollEndTranslation.value = isHorizontal.value
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
- if (maxScrollDistancePerSwipeIsSet && Math.abs(totalTranslation) > maxScrollDistancePerSwipe) {
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 (!infinite)
380
+ if (!loop)
347
381
  touching.value = false;
348
382
  }, [
349
383
  size,
350
- infinite,
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 = useMemo(() => {
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
- useUpdateGestureConfig(gesture, { enabled });
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
+ });