react-native-reanimated-carousel 2.1.2 → 2.2.4

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 (53) hide show
  1. package/README.md +10 -7
  2. package/README.zh-CN.md +11 -5
  3. package/lib/commonjs/Carousel.js +29 -21
  4. package/lib/commonjs/Carousel.js.map +1 -1
  5. package/lib/commonjs/ScrollViewGesture.js +29 -21
  6. package/lib/commonjs/ScrollViewGesture.js.map +1 -1
  7. package/lib/commonjs/hooks/useAutoPlay.js +27 -13
  8. package/lib/commonjs/hooks/useAutoPlay.js.map +1 -1
  9. package/lib/commonjs/hooks/useCarouselController.js +84 -24
  10. package/lib/commonjs/hooks/useCarouselController.js.map +1 -1
  11. package/lib/commonjs/hooks/useInitProps.js +13 -9
  12. package/lib/commonjs/hooks/useInitProps.js.map +1 -1
  13. package/lib/commonjs/hooks/useOnProgressChange.js +5 -5
  14. package/lib/commonjs/hooks/useOnProgressChange.js.map +1 -1
  15. package/lib/commonjs/layouts/stack.js.map +1 -1
  16. package/lib/module/Carousel.js +26 -21
  17. package/lib/module/Carousel.js.map +1 -1
  18. package/lib/module/ScrollViewGesture.js +27 -21
  19. package/lib/module/ScrollViewGesture.js.map +1 -1
  20. package/lib/module/hooks/useAutoPlay.js +27 -13
  21. package/lib/module/hooks/useAutoPlay.js.map +1 -1
  22. package/lib/module/hooks/useCarouselController.js +82 -24
  23. package/lib/module/hooks/useCarouselController.js.map +1 -1
  24. package/lib/module/hooks/useInitProps.js +13 -9
  25. package/lib/module/hooks/useInitProps.js.map +1 -1
  26. package/lib/module/hooks/useOnProgressChange.js +5 -5
  27. package/lib/module/hooks/useOnProgressChange.js.map +1 -1
  28. package/lib/module/layouts/stack.js.map +1 -1
  29. package/lib/typescript/Carousel.d.ts +3 -4
  30. package/lib/typescript/ScrollViewGesture.d.ts +3 -1
  31. package/lib/typescript/hooks/useAutoPlay.d.ts +1 -1
  32. package/lib/typescript/hooks/useCarouselController.d.ts +5 -3
  33. package/lib/typescript/hooks/useInitProps.d.ts +4 -5
  34. package/lib/typescript/hooks/useOnProgressChange.d.ts +2 -1
  35. package/lib/typescript/layouts/stack.d.ts +1 -1
  36. package/lib/typescript/types.d.ts +22 -4
  37. package/package.json +5 -2
  38. package/src/Carousel.tsx +203 -185
  39. package/src/ScrollViewGesture.tsx +52 -25
  40. package/src/hooks/useAutoPlay.ts +25 -22
  41. package/src/hooks/useCarouselController.tsx +101 -42
  42. package/src/hooks/useInitProps.ts +30 -15
  43. package/src/hooks/useOnProgressChange.ts +7 -6
  44. package/src/layouts/stack.ts +1 -1
  45. package/src/types.ts +23 -4
  46. package/CHANGELOG.md +0 -361
  47. package/lib/commonjs/hooks/useIndexController.js +0 -65
  48. package/lib/commonjs/hooks/useIndexController.js.map +0 -1
  49. package/lib/module/hooks/useIndexController.js +0 -52
  50. package/lib/module/hooks/useIndexController.js.map +0 -1
  51. package/lib/typescript/hooks/useIndexController.d.ts +0 -18
  52. package/src/.DS_Store +0 -0
  53. package/src/hooks/useIndexController.ts +0 -78
package/src/Carousel.tsx CHANGED
@@ -1,10 +1,12 @@
1
- import React, { PropsWithChildren } from 'react';
1
+ import React from 'react';
2
2
  import Animated, { runOnJS, useDerivedValue } from 'react-native-reanimated';
3
+
3
4
  import { useCarouselController } from './hooks/useCarouselController';
4
5
  import { useAutoPlay } from './hooks/useAutoPlay';
5
6
  import { usePropsErrorBoundary } from './hooks/usePropsErrorBoundary';
6
7
  import { ScrollViewGesture } from './ScrollViewGesture';
7
8
  import { useVisibleRanges } from './hooks/useVisibleRanges';
9
+
8
10
  import type { ICarouselInstance, TCarouselProps } from './types';
9
11
  import { StyleSheet, View } from 'react-native';
10
12
  import { DATA_LENGTH } from './constants';
@@ -15,196 +17,212 @@ import { CTX } from './store';
15
17
  import { useCommonVariables } from './hooks/useCommonVariables';
16
18
  import { useOnProgressChange } from './hooks/useOnProgressChange';
17
19
 
18
- function Carousel<T>(
19
- _props: PropsWithChildren<TCarouselProps<T>>,
20
- ref: React.Ref<ICarouselInstance>
21
- ) {
22
- const props = useInitProps(_props);
23
-
24
- const {
25
- data,
26
- loop,
27
- mode,
28
- style,
29
- width,
30
- height,
31
- vertical,
32
- autoPlay,
33
- windowSize,
34
- autoPlayReverse,
35
- autoPlayInterval,
36
- renderItem,
37
- onScrollEnd,
38
- onSnapToItem,
39
- onScrollBegin,
40
- onProgressChange,
41
- customAnimation,
42
- } = props;
43
-
44
- const commonVariables = useCommonVariables(props);
45
- const { size, handlerOffsetX } = commonVariables;
46
-
47
- const offsetX = useDerivedValue(() => {
48
- const totalSize = size * data.length;
49
- const x = handlerOffsetX.value % totalSize;
50
-
51
- if (!loop) {
52
- return handlerOffsetX.value;
53
- }
54
- return isNaN(x) ? 0 : x;
55
- }, [loop, size, data]);
56
-
57
- usePropsErrorBoundary(props);
58
- useOnProgressChange({ size, offsetX, data, onProgressChange });
59
-
60
- const carouselController = useCarouselController({
61
- loop,
62
- size,
63
- handlerOffsetX,
64
- length: data.length,
65
- disable: !data.length,
66
- originalLength: data.length,
67
- onScrollEnd: () => runOnJS(_onScrollEnd)(),
68
- onScrollBegin: () => !!onScrollBegin && runOnJS(onScrollBegin)(),
69
- onChange: (i) => onSnapToItem && runOnJS(onSnapToItem)(i),
70
- duration: autoPlayInterval,
71
- });
72
-
73
- const {
74
- next,
75
- prev,
76
- sharedPreIndex,
77
- sharedIndex,
78
- computedIndex,
79
- getCurrentIndex,
80
- } = carouselController;
81
-
82
- const { run, pause } = useAutoPlay({
83
- autoPlay,
84
- autoPlayInterval,
85
- autoPlayReverse,
86
- carouselController,
87
- });
88
-
89
- const scrollViewGestureOnScrollBegin = React.useCallback(() => {
90
- pause();
91
- onScrollBegin?.();
92
- }, [onScrollBegin, pause]);
93
-
94
- const _onScrollEnd = React.useCallback(() => {
95
- computedIndex();
96
- onScrollEnd?.(sharedPreIndex.current, sharedIndex.current);
97
- }, [sharedPreIndex, sharedIndex, computedIndex, onScrollEnd]);
98
-
99
- const scrollViewGestureOnScrollEnd = React.useCallback(() => {
100
- run();
101
- _onScrollEnd();
102
- }, [_onScrollEnd, run]);
103
-
104
- const goToIndex = React.useCallback(
105
- (i: number, animated?: boolean) => {
106
- carouselController.to(i, animated);
107
- },
108
- [carouselController]
109
- );
110
-
111
- React.useImperativeHandle(
112
- ref,
113
- () => ({
114
- next,
115
- prev,
116
- getCurrentIndex,
117
- goToIndex,
118
- }),
119
- [getCurrentIndex, goToIndex, next, prev]
120
- );
121
-
122
- const visibleRanges = useVisibleRanges({
123
- total: data.length,
124
- viewSize: size,
125
- translation: handlerOffsetX,
126
- windowSize,
127
- });
128
-
129
- const layoutConfig = useLayoutConfig<T>({ ...props, size });
130
-
131
- const renderLayout = React.useCallback(
132
- (item: T, i: number) => {
133
- let realIndex = i;
134
- if (data.length === DATA_LENGTH.SINGLE_ITEM) {
135
- realIndex = i % 1;
136
- }
137
-
138
- if (data.length === DATA_LENGTH.DOUBLE_ITEM) {
139
- realIndex = i % 2;
140
- }
20
+ const Carousel = React.forwardRef<ICarouselInstance, TCarouselProps<any>>(
21
+ (_props, ref) => {
22
+ const props = useInitProps(_props);
141
23
 
142
- return (
143
- <BaseLayout
144
- key={i}
145
- index={i}
146
- handlerOffsetX={offsetX}
147
- visibleRanges={visibleRanges}
148
- animationStyle={customAnimation || layoutConfig}
149
- >
150
- {({ animationValue }) =>
151
- renderItem({
152
- item,
153
- index: realIndex,
154
- animationValue,
155
- })
156
- }
157
- </BaseLayout>
158
- );
159
- },
160
- [
24
+ const {
161
25
  data,
162
- offsetX,
163
- visibleRanges,
26
+ rawData,
27
+ loop,
28
+ mode,
29
+ style,
30
+ width,
31
+ height,
32
+ vertical,
33
+ autoPlay,
34
+ windowSize,
35
+ autoPlayReverse,
36
+ autoPlayInterval,
37
+ scrollAnimationDuration,
164
38
  renderItem,
165
- layoutConfig,
39
+ onScrollEnd,
40
+ onSnapToItem,
41
+ onScrollBegin,
42
+ onProgressChange,
166
43
  customAnimation,
167
- ]
168
- );
169
-
170
- return (
171
- <CTX.Provider value={{ props, common: commonVariables }}>
172
- <View
173
- style={[
174
- styles.container,
175
- { width: width || '100%', height: height || '100%' },
176
- style,
177
- ]}
178
- >
179
- <ScrollViewGesture
180
- size={size}
181
- translation={handlerOffsetX}
182
- onScrollBegin={scrollViewGestureOnScrollBegin}
183
- onScrollEnd={scrollViewGestureOnScrollEnd}
44
+ } = props;
45
+
46
+ const commonVariables = useCommonVariables(props);
47
+ const { size, handlerOffsetX } = commonVariables;
48
+
49
+ const offsetX = useDerivedValue(() => {
50
+ const totalSize = size * data.length;
51
+ const x = handlerOffsetX.value % totalSize;
52
+
53
+ if (!loop) {
54
+ return handlerOffsetX.value;
55
+ }
56
+ return isNaN(x) ? 0 : x;
57
+ }, [loop, size, data]);
58
+
59
+ usePropsErrorBoundary(props);
60
+ useOnProgressChange({ size, offsetX, rawData, onProgressChange });
61
+
62
+ const carouselController = useCarouselController({
63
+ loop,
64
+ size,
65
+ handlerOffsetX,
66
+ length: data.length,
67
+ disable: !data.length,
68
+ originalLength: data.length,
69
+ onScrollEnd: () => runOnJS(_onScrollEnd)(),
70
+ onScrollBegin: () => !!onScrollBegin && runOnJS(onScrollBegin)(),
71
+ onChange: (i) => !!onSnapToItem && runOnJS(onSnapToItem)(i),
72
+ duration: scrollAnimationDuration,
73
+ });
74
+
75
+ const {
76
+ next,
77
+ prev,
78
+ sharedPreIndex,
79
+ sharedIndex,
80
+ computedIndex,
81
+ getCurrentIndex,
82
+ } = carouselController;
83
+
84
+ const { start, pause } = useAutoPlay({
85
+ autoPlay,
86
+ autoPlayInterval,
87
+ autoPlayReverse,
88
+ carouselController,
89
+ });
90
+
91
+ const _onScrollEnd = React.useCallback(() => {
92
+ computedIndex();
93
+ onScrollEnd?.(sharedPreIndex.current, sharedIndex.current);
94
+ }, [sharedPreIndex, sharedIndex, computedIndex, onScrollEnd]);
95
+
96
+ const scrollViewGestureOnScrollBegin = React.useCallback(() => {
97
+ pause();
98
+ onScrollBegin?.();
99
+ }, [onScrollBegin, pause]);
100
+
101
+ const scrollViewGestureOnScrollEnd = React.useCallback(() => {
102
+ start();
103
+ _onScrollEnd();
104
+ }, [_onScrollEnd, start]);
105
+
106
+ const scrollViewGestureOnTouchBegin = React.useCallback(pause, [pause]);
107
+
108
+ const scrollViewGestureOnTouchEnd = React.useCallback(start, [start]);
109
+
110
+ const goToIndex = React.useCallback(
111
+ (i: number, animated?: boolean) => {
112
+ carouselController.to(i, animated);
113
+ },
114
+ [carouselController]
115
+ );
116
+
117
+ React.useImperativeHandle(
118
+ ref,
119
+ () => ({
120
+ next,
121
+ prev,
122
+ getCurrentIndex,
123
+ goToIndex,
124
+ scrollTo: carouselController.scrollTo,
125
+ }),
126
+ [
127
+ getCurrentIndex,
128
+ goToIndex,
129
+ next,
130
+ prev,
131
+ carouselController.scrollTo,
132
+ ]
133
+ );
134
+
135
+ const visibleRanges = useVisibleRanges({
136
+ total: data.length,
137
+ viewSize: size,
138
+ translation: handlerOffsetX,
139
+ windowSize,
140
+ });
141
+
142
+ const layoutConfig = useLayoutConfig({ ...props, size });
143
+
144
+ const renderLayout = React.useCallback(
145
+ (item: any, i: number) => {
146
+ let realIndex = i;
147
+ if (rawData.length === DATA_LENGTH.SINGLE_ITEM) {
148
+ realIndex = i % 1;
149
+ }
150
+
151
+ if (rawData.length === DATA_LENGTH.DOUBLE_ITEM) {
152
+ realIndex = i % 2;
153
+ }
154
+
155
+ return (
156
+ <BaseLayout
157
+ key={i}
158
+ index={i}
159
+ handlerOffsetX={offsetX}
160
+ visibleRanges={visibleRanges}
161
+ animationStyle={customAnimation || layoutConfig}
162
+ >
163
+ {({ animationValue }) =>
164
+ renderItem({
165
+ item,
166
+ index: realIndex,
167
+ animationValue,
168
+ })
169
+ }
170
+ </BaseLayout>
171
+ );
172
+ },
173
+ [
174
+ rawData,
175
+ offsetX,
176
+ visibleRanges,
177
+ renderItem,
178
+ layoutConfig,
179
+ customAnimation,
180
+ ]
181
+ );
182
+
183
+ return (
184
+ <CTX.Provider value={{ props, common: commonVariables }}>
185
+ <View
186
+ style={[
187
+ styles.container,
188
+ { width: width || '100%', height: height || '100%' },
189
+ style,
190
+ ]}
184
191
  >
185
- <Animated.View
186
- key={mode}
187
- style={[
188
- styles.container,
189
- {
190
- width: width || '100%',
191
- height: height || '100%',
192
- },
193
- style,
194
- vertical
195
- ? styles.itemsVertical
196
- : styles.itemsHorizontal,
197
- ]}
192
+ <ScrollViewGesture
193
+ size={size}
194
+ translation={handlerOffsetX}
195
+ onScrollBegin={scrollViewGestureOnScrollBegin}
196
+ onScrollEnd={scrollViewGestureOnScrollEnd}
197
+ onTouchBegin={scrollViewGestureOnTouchBegin}
198
+ onTouchEnd={scrollViewGestureOnTouchEnd}
198
199
  >
199
- {data.map(renderLayout)}
200
- </Animated.View>
201
- </ScrollViewGesture>
202
- </View>
203
- </CTX.Provider>
204
- );
205
- }
206
-
207
- export default React.forwardRef(Carousel) as typeof Carousel;
200
+ <Animated.View
201
+ key={mode}
202
+ style={[
203
+ styles.container,
204
+ {
205
+ width: width || '100%',
206
+ height: height || '100%',
207
+ },
208
+ style,
209
+ vertical
210
+ ? styles.itemsVertical
211
+ : styles.itemsHorizontal,
212
+ ]}
213
+ >
214
+ {data.map(renderLayout)}
215
+ </Animated.View>
216
+ </ScrollViewGesture>
217
+ </View>
218
+ </CTX.Provider>
219
+ );
220
+ }
221
+ );
222
+
223
+ export default Carousel as <T extends any>(
224
+ props: React.PropsWithChildren<TCarouselProps<T>>
225
+ ) => React.ReactElement;
208
226
 
209
227
  const styles = StyleSheet.create({
210
228
  container: {
@@ -25,8 +25,10 @@ type GestureContext = {
25
25
  interface Props {
26
26
  size: number;
27
27
  infinite?: boolean;
28
- onScrollEnd?: () => void;
29
28
  onScrollBegin?: () => void;
29
+ onScrollEnd?: () => void;
30
+ onTouchBegin?: () => void;
31
+ onTouchEnd?: () => void;
30
32
  style?: StyleProp<ViewStyle>;
31
33
  translation: Animated.SharedValue<number>;
32
34
  }
@@ -41,11 +43,18 @@ const IScrollViewGesture: React.FC<Props> = (props) => {
41
43
  enableSnap,
42
44
  panGestureHandlerProps,
43
45
  loop: infinite,
44
- autoPlayInterval,
46
+ scrollAnimationDuration,
45
47
  },
46
48
  } = React.useContext(CTX);
47
49
 
48
- const { translation, onScrollBegin, onScrollEnd, size } = props;
50
+ const {
51
+ translation,
52
+ size,
53
+ onScrollBegin,
54
+ onScrollEnd,
55
+ onTouchBegin,
56
+ onTouchEnd,
57
+ } = props;
49
58
 
50
59
  const maxPage = data.length;
51
60
  const isHorizontal = useDerivedValue(() => !vertical, [vertical]);
@@ -59,7 +68,7 @@ const IScrollViewGesture: React.FC<Props> = (props) => {
59
68
  return withTiming(
60
69
  toValue,
61
70
  {
62
- duration: autoPlayInterval,
71
+ duration: scrollAnimationDuration,
63
72
  easing: Easing.easeOutQuart,
64
73
  },
65
74
  (isFinished) => {
@@ -69,7 +78,7 @@ const IScrollViewGesture: React.FC<Props> = (props) => {
69
78
  }
70
79
  );
71
80
  },
72
- [autoPlayInterval]
81
+ [scrollAnimationDuration]
73
82
  );
74
83
 
75
84
  const endWithSpring = React.useCallback(
@@ -116,22 +125,28 @@ const IScrollViewGesture: React.FC<Props> = (props) => {
116
125
  ]
117
126
  );
118
127
 
119
- const resetBoundary = React.useCallback(() => {
120
- 'worklet';
121
- const onFinish = (isFinished: boolean) => {
128
+ const onFinish = React.useCallback(
129
+ (isFinished: boolean) => {
130
+ 'worklet';
122
131
  if (isFinished) {
123
132
  touching.value = false;
124
133
  onScrollEnd && runOnJS(onScrollEnd)();
125
134
  }
126
- };
127
- const activeDecay = () => {
128
- touching.value = true;
129
- translation.value = withDecay(
130
- { velocity: scrollEndVelocity.value },
131
- onFinish
132
- );
133
- };
135
+ },
136
+ [onScrollEnd, touching]
137
+ );
138
+
139
+ const activeDecay = React.useCallback(() => {
140
+ 'worklet';
141
+ touching.value = true;
142
+ translation.value = withDecay(
143
+ { velocity: scrollEndVelocity.value },
144
+ onFinish
145
+ );
146
+ }, [onFinish, scrollEndVelocity.value, touching, translation]);
134
147
 
148
+ const resetBoundary = React.useCallback(() => {
149
+ 'worklet';
135
150
  if (touching.value) {
136
151
  return;
137
152
  }
@@ -158,15 +173,14 @@ const IScrollViewGesture: React.FC<Props> = (props) => {
158
173
  }
159
174
  }
160
175
  }, [
161
- infinite,
162
- touching,
163
- _withSpring,
176
+ touching.value,
164
177
  translation,
165
- scrollEndTranslation,
166
- scrollEndVelocity,
167
- onScrollEnd,
168
178
  maxPage,
169
179
  size,
180
+ scrollEndTranslation.value,
181
+ infinite,
182
+ activeDecay,
183
+ _withSpring,
170
184
  ]);
171
185
 
172
186
  useAnimatedReaction(
@@ -176,7 +190,7 @@ const IScrollViewGesture: React.FC<Props> = (props) => {
176
190
  resetBoundary();
177
191
  }
178
192
  },
179
- [pagingEnabled]
193
+ [pagingEnabled, resetBoundary]
180
194
  );
181
195
 
182
196
  const panGestureEventHandler = useAnimatedGestureHandler<
@@ -227,7 +241,16 @@ const IScrollViewGesture: React.FC<Props> = (props) => {
227
241
  }
228
242
  },
229
243
  },
230
- [pagingEnabled, isHorizontal.value, infinite, maxPage, size, enableSnap]
244
+ [
245
+ pagingEnabled,
246
+ isHorizontal.value,
247
+ infinite,
248
+ maxPage,
249
+ size,
250
+ enableSnap,
251
+ onScrollBegin,
252
+ onScrollEnd,
253
+ ]
231
254
  );
232
255
 
233
256
  const directionStyle = React.useMemo(() => {
@@ -235,7 +258,11 @@ const IScrollViewGesture: React.FC<Props> = (props) => {
235
258
  }, [vertical]);
236
259
 
237
260
  return (
238
- <Animated.View style={[styles.container, directionStyle, style]}>
261
+ <Animated.View
262
+ style={[styles.container, directionStyle, style]}
263
+ onTouchStart={onTouchBegin}
264
+ onTouchEnd={onTouchEnd}
265
+ >
239
266
  <PanGestureHandler
240
267
  {...panGestureHandlerProps}
241
268
  onGestureEvent={panGestureEventHandler}
@@ -15,44 +15,47 @@ export function useAutoPlay(opts: {
15
15
  } = opts;
16
16
 
17
17
  const timer = React.useRef<NodeJS.Timer>();
18
+ const stopped = React.useRef<boolean>(!autoPlay);
18
19
 
19
- const pause = React.useCallback(() => {
20
- timer.current && clearInterval(timer.current);
21
- }, []);
22
-
23
- const run = React.useCallback(() => {
24
- if (timer.current) {
25
- pause();
20
+ const play = React.useCallback(() => {
21
+ if (stopped.current) {
22
+ return;
26
23
  }
27
24
 
25
+ timer.current = setTimeout(() => {
26
+ autoPlayReverse
27
+ ? carouselController.prev({ onFinished: play })
28
+ : carouselController.next({ onFinished: play });
29
+ }, autoPlayInterval);
30
+ }, [autoPlayReverse, autoPlayInterval, carouselController]);
31
+
32
+ const pause = React.useCallback(() => {
28
33
  if (!autoPlay) {
29
34
  return;
30
35
  }
36
+ timer.current && clearInterval(timer.current);
37
+ stopped.current = true;
38
+ }, [autoPlay]);
31
39
 
32
- timer.current = setInterval(() => {
33
- autoPlayReverse
34
- ? carouselController.prev()
35
- : carouselController.next();
36
- }, autoPlayInterval);
37
- }, [
38
- pause,
39
- autoPlay,
40
- autoPlayReverse,
41
- autoPlayInterval,
42
- carouselController,
43
- ]);
40
+ const start = React.useCallback(() => {
41
+ if (!autoPlay) {
42
+ return;
43
+ }
44
+ stopped.current = false;
45
+ play();
46
+ }, [play, autoPlay]);
44
47
 
45
48
  React.useEffect(() => {
46
49
  if (autoPlay) {
47
- run();
50
+ start();
48
51
  } else {
49
52
  pause();
50
53
  }
51
54
  return pause;
52
- }, [run, pause, autoPlay]);
55
+ }, [pause, start, autoPlay]);
53
56
 
54
57
  return {
55
- run,
56
58
  pause,
59
+ start,
57
60
  };
58
61
  }