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
@@ -2,6 +2,7 @@ import React from 'react';
2
2
  import type Animated from 'react-native-reanimated';
3
3
  import { Easing } from '../constants';
4
4
  import { runOnJS, useSharedValue, withTiming } from 'react-native-reanimated';
5
+ import type { TCarouselActionOptions } from '../types';
5
6
 
6
7
  interface IOpts {
7
8
  loop: boolean;
@@ -22,14 +23,15 @@ export interface ICarouselController {
22
23
  index: Animated.SharedValue<number>;
23
24
  sharedIndex: React.MutableRefObject<number>;
24
25
  sharedPreIndex: React.MutableRefObject<number>;
25
- prev: () => void;
26
- next: () => void;
26
+ prev: (opts?: TCarouselActionOptions) => void;
27
+ next: (opts?: TCarouselActionOptions) => void;
27
28
  computedIndex: () => void;
28
29
  getCurrentIndex: () => number;
29
30
  to: (index: number, animated?: boolean) => void;
31
+ scrollTo: (opts?: TCarouselActionOptions) => void;
30
32
  }
31
33
 
32
- export function useCarouselController(opts: IOpts): ICarouselController {
34
+ export function useCarouselController(options: IOpts): ICarouselController {
33
35
  const {
34
36
  size,
35
37
  loop,
@@ -39,13 +41,26 @@ export function useCarouselController(opts: IOpts): ICarouselController {
39
41
  length,
40
42
  onChange,
41
43
  duration,
42
- } = opts;
44
+ } = options;
43
45
 
44
46
  const index = useSharedValue<number>(0);
45
47
  // The Index displayed to the user
46
48
  const sharedIndex = React.useRef<number>(0);
47
49
  const sharedPreIndex = React.useRef<number>(0);
48
50
 
51
+ const currentFixedPage = React.useCallback(() => {
52
+ if (loop) {
53
+ return -Math.round(handlerOffsetX.value / size);
54
+ }
55
+
56
+ const fixed = (handlerOffsetX.value / size) % length;
57
+ return Math.round(
58
+ handlerOffsetX.value <= 0
59
+ ? Math.abs(fixed)
60
+ : Math.abs(fixed > 0 ? length - fixed : 0)
61
+ );
62
+ }, [handlerOffsetX, length, size, loop]);
63
+
49
64
  const convertToSharedIndex = React.useCallback(
50
65
  (i: number) => {
51
66
  if (loop) {
@@ -92,22 +107,22 @@ export function useCarouselController(opts: IOpts): ICarouselController {
92
107
  }, [disable]);
93
108
 
94
109
  const onScrollEnd = React.useCallback(() => {
95
- opts.onScrollEnd?.();
96
- }, [opts]);
110
+ options.onScrollEnd?.();
111
+ }, [options]);
97
112
 
98
113
  const onScrollBegin = React.useCallback(() => {
99
- opts.onScrollBegin?.();
100
- }, [opts]);
114
+ options.onScrollBegin?.();
115
+ }, [options]);
101
116
 
102
117
  const scrollWithTiming = React.useCallback(
103
- (toValue: number, callback?: () => void) => {
118
+ (toValue: number, onFinished?: () => void) => {
104
119
  return withTiming(
105
120
  toValue,
106
121
  { duration, easing: Easing.easeOutQuart },
107
122
  (isFinished: boolean) => {
108
- callback?.();
109
123
  if (isFinished) {
110
124
  runOnJS(onScrollEnd)();
125
+ onFinished && runOnJS(onFinished)();
111
126
  }
112
127
  }
113
128
  );
@@ -115,42 +130,70 @@ export function useCarouselController(opts: IOpts): ICarouselController {
115
130
  [onScrollEnd, duration]
116
131
  );
117
132
 
118
- const next = React.useCallback(() => {
119
- if (!canSliding() || (!loop && index.value === length - 1)) return;
133
+ const next = React.useCallback(
134
+ (opts: TCarouselActionOptions = {}) => {
135
+ const { count = 1, animated = true, onFinished } = opts;
136
+ if (!canSliding() || (!loop && index.value >= length - 1)) return;
120
137
 
121
- onScrollBegin?.();
138
+ onScrollBegin?.();
122
139
 
123
- const currentPage = Math.round(handlerOffsetX.value / size);
140
+ const nextPage = currentFixedPage() + count;
141
+ index.value = nextPage;
124
142
 
125
- handlerOffsetX.value = scrollWithTiming((currentPage - 1) * size);
126
- }, [
127
- canSliding,
128
- loop,
129
- index.value,
130
- length,
131
- onScrollBegin,
132
- handlerOffsetX,
133
- size,
134
- scrollWithTiming,
135
- ]);
143
+ if (animated) {
144
+ handlerOffsetX.value = scrollWithTiming(
145
+ -nextPage * size,
146
+ onFinished
147
+ );
148
+ } else {
149
+ handlerOffsetX.value = -nextPage * size;
150
+ onFinished?.();
151
+ }
152
+ },
153
+ [
154
+ canSliding,
155
+ loop,
156
+ index,
157
+ length,
158
+ onScrollBegin,
159
+ handlerOffsetX,
160
+ size,
161
+ scrollWithTiming,
162
+ currentFixedPage,
163
+ ]
164
+ );
136
165
 
137
- const prev = React.useCallback(() => {
138
- if (!canSliding() || (!loop && index.value === 0)) return;
166
+ const prev = React.useCallback(
167
+ (opts: TCarouselActionOptions = {}) => {
168
+ const { count = 1, animated = true, onFinished } = opts;
169
+ if (!canSliding() || (!loop && index.value <= 0)) return;
139
170
 
140
- onScrollBegin?.();
171
+ onScrollBegin?.();
141
172
 
142
- const currentPage = Math.round(handlerOffsetX.value / size);
173
+ const prevPage = currentFixedPage() - count;
174
+ index.value = prevPage;
143
175
 
144
- handlerOffsetX.value = scrollWithTiming((currentPage + 1) * size);
145
- }, [
146
- canSliding,
147
- loop,
148
- index.value,
149
- onScrollBegin,
150
- handlerOffsetX,
151
- size,
152
- scrollWithTiming,
153
- ]);
176
+ if (animated) {
177
+ handlerOffsetX.value = scrollWithTiming(
178
+ -prevPage * size,
179
+ onFinished
180
+ );
181
+ } else {
182
+ handlerOffsetX.value = -prevPage * size;
183
+ onFinished?.();
184
+ }
185
+ },
186
+ [
187
+ canSliding,
188
+ loop,
189
+ index,
190
+ onScrollBegin,
191
+ handlerOffsetX,
192
+ size,
193
+ scrollWithTiming,
194
+ currentFixedPage,
195
+ ]
196
+ );
154
197
 
155
198
  const to = React.useCallback(
156
199
  (idx: number, animated: boolean = false) => {
@@ -162,9 +205,8 @@ export function useCarouselController(opts: IOpts): ICarouselController {
162
205
  const offset = handlerOffsetX.value + (index.value - idx) * size;
163
206
 
164
207
  if (animated) {
165
- handlerOffsetX.value = scrollWithTiming(offset, () => {
166
- index.value = idx;
167
- });
208
+ index.value = idx;
209
+ handlerOffsetX.value = scrollWithTiming(offset);
168
210
  } else {
169
211
  handlerOffsetX.value = offset;
170
212
  index.value = idx;
@@ -182,10 +224,27 @@ export function useCarouselController(opts: IOpts): ICarouselController {
182
224
  ]
183
225
  );
184
226
 
227
+ const scrollTo = React.useCallback(
228
+ (opts: TCarouselActionOptions = {}) => {
229
+ const { count, animated = false, onFinished } = opts;
230
+ if (!count) {
231
+ return;
232
+ }
233
+ const n = Math.round(count);
234
+ if (n < 0) {
235
+ prev({ count: Math.abs(n), animated, onFinished });
236
+ } else {
237
+ next({ count: n, animated, onFinished });
238
+ }
239
+ },
240
+ [prev, next]
241
+ );
242
+
185
243
  return {
186
244
  next,
187
245
  prev,
188
246
  to,
247
+ scrollTo,
189
248
  index,
190
249
  length,
191
250
  sharedIndex,
@@ -2,21 +2,33 @@ import React from 'react';
2
2
  import { DATA_LENGTH } from '../constants';
3
3
  import type { TCarouselProps } from '../types';
4
4
 
5
- export type TInitializeCarouselProps<T> = TCarouselProps<T> & {
6
- defaultIndex: Required<TCarouselProps>['defaultIndex'];
7
- loop: Required<TCarouselProps>['loop'];
8
- width: Required<TCarouselProps>['width'];
9
- height: Required<TCarouselProps>['height'];
10
- };
5
+ type TGetRequiredProps<P extends keyof TCarouselProps> = Record<
6
+ P,
7
+ Required<TCarouselProps>[P]
8
+ >;
9
+
10
+ export type TInitializeCarouselProps<T> = TCarouselProps<T> &
11
+ TGetRequiredProps<
12
+ | 'defaultIndex'
13
+ | 'loop'
14
+ | 'width'
15
+ | 'height'
16
+ | 'scrollAnimationDuration'
17
+ | 'autoPlayInterval'
18
+ > & {
19
+ // Raw data that has not been processed
20
+ rawData: T[];
21
+ };
11
22
 
12
23
  export function useInitProps<T>(
13
24
  props: TCarouselProps<T>
14
25
  ): TInitializeCarouselProps<T> {
15
26
  const {
16
27
  defaultIndex = 0,
17
- data: _data = [],
28
+ data: rawData = [],
18
29
  loop = true,
19
- autoPlayInterval = 1000,
30
+ autoPlayInterval: _autoPlayInterval = 1000,
31
+ scrollAnimationDuration = 500,
20
32
  style = {},
21
33
  panGestureHandlerProps = {},
22
34
  pagingEnabled = true,
@@ -27,20 +39,21 @@ export function useInitProps<T>(
27
39
 
28
40
  const width = Math.round(_width || 0);
29
41
  const height = Math.round(_height || 0);
42
+ const autoPlayInterval = Math.max(_autoPlayInterval, 0);
30
43
 
31
44
  const data = React.useMemo<T[]>(() => {
32
- if (!loop) return _data;
45
+ if (!loop) return rawData;
33
46
 
34
- if (_data.length === DATA_LENGTH.SINGLE_ITEM) {
35
- return [_data[0], _data[0], _data[0]];
47
+ if (rawData.length === DATA_LENGTH.SINGLE_ITEM) {
48
+ return [rawData[0], rawData[0], rawData[0]];
36
49
  }
37
50
 
38
- if (_data.length === DATA_LENGTH.DOUBLE_ITEM) {
39
- return [_data[0], _data[1], _data[0], _data[1]];
51
+ if (rawData.length === DATA_LENGTH.DOUBLE_ITEM) {
52
+ return [rawData[0], rawData[1], rawData[0], rawData[1]];
40
53
  }
41
54
 
42
- return _data;
43
- }, [_data, loop]);
55
+ return rawData;
56
+ }, [rawData, loop]);
44
57
 
45
58
  if (props.mode === 'vertical-stack' || props.mode === 'horizontal-stack') {
46
59
  if (!props.modeConfig) {
@@ -53,8 +66,10 @@ export function useInitProps<T>(
53
66
  ...props,
54
67
  defaultIndex,
55
68
  data,
69
+ rawData,
56
70
  loop,
57
71
  autoPlayInterval,
72
+ scrollAnimationDuration,
58
73
  style,
59
74
  panGestureHandlerProps,
60
75
  pagingEnabled,
@@ -9,31 +9,32 @@ export function useOnProgressChange(
9
9
  opts: {
10
10
  size: number;
11
11
  offsetX: Animated.SharedValue<number>;
12
- } & Pick<TCarouselProps, 'data' | 'onProgressChange'>
12
+ rawData: TCarouselProps['data'];
13
+ } & Pick<TCarouselProps, 'onProgressChange'>
13
14
  ) {
14
- const { offsetX, data, size, onProgressChange } = opts;
15
+ const { offsetX, rawData, size, onProgressChange } = opts;
15
16
  useAnimatedReaction(
16
17
  () => offsetX.value,
17
18
  (_value) => {
18
19
  let value = _value;
19
20
 
20
- if (data.length === DATA_LENGTH.SINGLE_ITEM) {
21
+ if (rawData.length === DATA_LENGTH.SINGLE_ITEM) {
21
22
  value = value % size;
22
23
  }
23
24
 
24
- if (data.length === DATA_LENGTH.DOUBLE_ITEM) {
25
+ if (rawData.length === DATA_LENGTH.DOUBLE_ITEM) {
25
26
  value = value % (size * 2);
26
27
  }
27
28
 
28
29
  let absoluteProgress = Math.abs(value / size);
29
30
 
30
31
  if (value > 0) {
31
- absoluteProgress = data.length - absoluteProgress;
32
+ absoluteProgress = rawData.length - absoluteProgress;
32
33
  }
33
34
 
34
35
  !!onProgressChange &&
35
36
  runOnJS(onProgressChange)(value, absoluteProgress);
36
37
  },
37
- [onProgressChange, data]
38
+ [onProgressChange, rawData]
38
39
  );
39
40
  }
@@ -1,7 +1,7 @@
1
1
  import { useMemo } from 'react';
2
2
  import { Dimensions, TransformsStyle, ViewStyle } from 'react-native';
3
3
  import { Extrapolate, interpolate } from 'react-native-reanimated';
4
- import type { ComputedDirectionTypes, CustomConfig } from 'src/types';
4
+ import type { ComputedDirectionTypes, CustomConfig } from '../types';
5
5
 
6
6
  const screen = Dimensions.get('window');
7
7
 
package/src/types.ts CHANGED
@@ -71,6 +71,11 @@ export type TCarouselProps<T = any> = {
71
71
  * @description playback interval
72
72
  */
73
73
  autoPlayInterval?: number;
74
+ /**
75
+ * Time a scroll animation takes to finish
76
+ * @default 500 (ms)
77
+ */
78
+ scrollAnimationDuration?: number;
74
79
  /**
75
80
  * Carousel container style
76
81
  */
@@ -138,21 +143,29 @@ export type TCarouselProps<T = any> = {
138
143
 
139
144
  export interface ICarouselInstance {
140
145
  /**
141
- * Play the last one
146
+ * Scroll to previous item, it takes one optional argument (count),
147
+ * which allows you to specify how many items to cross
142
148
  */
143
- prev: () => void;
149
+ prev: (opts?: TCarouselActionOptions) => void;
144
150
  /**
145
- * Play the next one
151
+ * Scroll to next item, it takes one optional argument (count),
152
+ * which allows you to specify how many items to cross
146
153
  */
147
- next: () => void;
154
+ next: (opts?: TCarouselActionOptions) => void;
148
155
  /**
149
156
  * Get current item index
150
157
  */
151
158
  getCurrentIndex: () => number;
152
159
  /**
153
160
  * Go to index
161
+ * @deprecated use scrollTo instead
154
162
  */
155
163
  goToIndex: (index: number, animated?: boolean) => void;
164
+ /**
165
+ * Use value to scroll to a position where relative to the current position,
166
+ * scrollTo(-2) is equivalent to prev(2), scrollTo(2) is equivalent to next(2)
167
+ */
168
+ scrollTo: (opts?: TCarouselActionOptions) => void;
156
169
  }
157
170
 
158
171
  export interface CarouselRenderItemInfo<ItemT> {
@@ -164,3 +177,9 @@ export interface CarouselRenderItemInfo<ItemT> {
164
177
  export type CarouselRenderItem<ItemT> = (
165
178
  info: CarouselRenderItemInfo<ItemT>
166
179
  ) => React.ReactElement;
180
+
181
+ export interface TCarouselActionOptions {
182
+ count?: number;
183
+ animated?: boolean;
184
+ onFinished?: () => void;
185
+ }