react-native-reanimated-carousel 4.0.0-alpha.1 → 4.0.0-alpha.10
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 +25 -46
- 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/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/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.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 +24 -42
- 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/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/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.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/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 +1 -0
- package/lib/typescript/types.d.ts +13 -4
- 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 +16 -59
- package/src/{layouts → components}/BaseLayout.tsx +7 -35
- package/src/components/Carousel.tsx +24 -58
- package/src/components/ItemRenderer.tsx +105 -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/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 +2 -0
- package/src/types.ts +13 -4
- 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
|
@@ -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
|
+
});
|
|
@@ -21,12 +21,13 @@ interface IOpts {
|
|
|
21
21
|
loop: boolean
|
|
22
22
|
size: number
|
|
23
23
|
dataLength: number
|
|
24
|
-
autoFillData: TCarouselProps["autoFillData"]
|
|
25
24
|
handlerOffset: Animated.SharedValue<number>
|
|
25
|
+
autoFillData: TCarouselProps["autoFillData"]
|
|
26
26
|
withAnimation?: TCarouselProps["withAnimation"]
|
|
27
|
+
fixedDirection?: TCarouselProps["fixedDirection"]
|
|
27
28
|
duration?: number
|
|
28
29
|
defaultIndex?: number
|
|
29
|
-
|
|
30
|
+
onScrollStart?: () => void
|
|
30
31
|
onScrollEnd?: () => void
|
|
31
32
|
}
|
|
32
33
|
|
|
@@ -48,6 +49,7 @@ export function useCarouselController(options: IOpts): ICarouselController {
|
|
|
48
49
|
defaultIndex = 0,
|
|
49
50
|
duration,
|
|
50
51
|
autoFillData,
|
|
52
|
+
fixedDirection,
|
|
51
53
|
} = options;
|
|
52
54
|
|
|
53
55
|
const dataInfo = React.useMemo(
|
|
@@ -136,8 +138,8 @@ export function useCarouselController(options: IOpts): ICarouselController {
|
|
|
136
138
|
options.onScrollEnd?.();
|
|
137
139
|
}, [options]);
|
|
138
140
|
|
|
139
|
-
const
|
|
140
|
-
options.
|
|
141
|
+
const onScrollStart = React.useCallback(() => {
|
|
142
|
+
options.onScrollStart?.();
|
|
141
143
|
}, [options]);
|
|
142
144
|
|
|
143
145
|
const scrollWithTiming = React.useCallback(
|
|
@@ -171,7 +173,7 @@ export function useCarouselController(options: IOpts): ICarouselController {
|
|
|
171
173
|
if (!canSliding() || (!loop && index.value >= dataInfo.length - 1))
|
|
172
174
|
return;
|
|
173
175
|
|
|
174
|
-
|
|
176
|
+
onScrollStart?.();
|
|
175
177
|
|
|
176
178
|
const nextPage = currentFixedPage() + count;
|
|
177
179
|
index.value = nextPage;
|
|
@@ -192,7 +194,7 @@ export function useCarouselController(options: IOpts): ICarouselController {
|
|
|
192
194
|
loop,
|
|
193
195
|
index,
|
|
194
196
|
dataInfo,
|
|
195
|
-
|
|
197
|
+
onScrollStart,
|
|
196
198
|
handlerOffset,
|
|
197
199
|
size,
|
|
198
200
|
scrollWithTiming,
|
|
@@ -205,7 +207,7 @@ export function useCarouselController(options: IOpts): ICarouselController {
|
|
|
205
207
|
const { count = 1, animated = true, onFinished } = opts;
|
|
206
208
|
if (!canSliding() || (!loop && index.value <= 0)) return;
|
|
207
209
|
|
|
208
|
-
|
|
210
|
+
onScrollStart?.();
|
|
209
211
|
|
|
210
212
|
const prevPage = currentFixedPage() - count;
|
|
211
213
|
index.value = prevPage;
|
|
@@ -225,7 +227,7 @@ export function useCarouselController(options: IOpts): ICarouselController {
|
|
|
225
227
|
canSliding,
|
|
226
228
|
loop,
|
|
227
229
|
index,
|
|
228
|
-
|
|
230
|
+
onScrollStart,
|
|
229
231
|
handlerOffset,
|
|
230
232
|
size,
|
|
231
233
|
scrollWithTiming,
|
|
@@ -239,9 +241,9 @@ export function useCarouselController(options: IOpts): ICarouselController {
|
|
|
239
241
|
if (i === index.value) return;
|
|
240
242
|
if (!canSliding()) return;
|
|
241
243
|
|
|
242
|
-
|
|
244
|
+
onScrollStart?.();
|
|
243
245
|
// direction -> 1 | -1
|
|
244
|
-
const direction = handlerOffsetDirection(handlerOffset);
|
|
246
|
+
const direction = handlerOffsetDirection(handlerOffset, fixedDirection);
|
|
245
247
|
|
|
246
248
|
// target offset
|
|
247
249
|
const offset = i * size * direction;
|
|
@@ -252,16 +254,16 @@ export function useCarouselController(options: IOpts): ICarouselController {
|
|
|
252
254
|
|
|
253
255
|
if (loop) {
|
|
254
256
|
isCloseToNextLoop
|
|
255
|
-
|
|
256
|
-
|
|
257
|
+
= Math.abs(handlerOffset.value % totalSize) / totalSize
|
|
258
|
+
>= 0.5;
|
|
257
259
|
}
|
|
258
260
|
|
|
259
261
|
const finalOffset
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
262
|
+
= (Math.floor(Math.abs(handlerOffset.value / totalSize))
|
|
263
|
+
+ (isCloseToNextLoop ? 1 : 0))
|
|
264
|
+
* totalSize
|
|
265
|
+
* direction
|
|
266
|
+
+ offset;
|
|
265
267
|
|
|
266
268
|
if (animated) {
|
|
267
269
|
index.value = i;
|
|
@@ -274,13 +276,14 @@ export function useCarouselController(options: IOpts): ICarouselController {
|
|
|
274
276
|
}
|
|
275
277
|
},
|
|
276
278
|
[
|
|
279
|
+
size,
|
|
280
|
+
loop,
|
|
277
281
|
index,
|
|
278
|
-
|
|
279
|
-
onScrollBegin,
|
|
282
|
+
fixedDirection,
|
|
280
283
|
handlerOffset,
|
|
281
|
-
size,
|
|
282
284
|
dataInfo.length,
|
|
283
|
-
|
|
285
|
+
canSliding,
|
|
286
|
+
onScrollStart,
|
|
284
287
|
scrollWithTiming,
|
|
285
288
|
],
|
|
286
289
|
);
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { renderHook } from "@testing-library/react-hooks";
|
|
2
|
+
|
|
3
|
+
import { useCommonVariables } from "./useCommonVariables";
|
|
4
|
+
|
|
5
|
+
type UseCommonVariablesInput = Parameters<typeof useCommonVariables>[0];
|
|
6
|
+
|
|
7
|
+
const input = {
|
|
8
|
+
vertical: false,
|
|
9
|
+
width: 700,
|
|
10
|
+
height: 350,
|
|
11
|
+
loop: true,
|
|
12
|
+
enabled: true,
|
|
13
|
+
testID: "xxx",
|
|
14
|
+
style: {
|
|
15
|
+
width: "100%",
|
|
16
|
+
},
|
|
17
|
+
autoPlay: false,
|
|
18
|
+
autoPlayInterval: 2000,
|
|
19
|
+
data: [0, 1, 2, 3],
|
|
20
|
+
pagingEnabled: true,
|
|
21
|
+
defaultIndex: 0,
|
|
22
|
+
autoFillData: true,
|
|
23
|
+
dataLength: 4,
|
|
24
|
+
rawData: [0, 1, 2, 3],
|
|
25
|
+
rawDataLength: 4,
|
|
26
|
+
scrollAnimationDuration: 500,
|
|
27
|
+
snapEnabled: true,
|
|
28
|
+
overscrollEnabled: true,
|
|
29
|
+
} as unknown as UseCommonVariablesInput;
|
|
30
|
+
|
|
31
|
+
describe("useCommonVariables", () => {
|
|
32
|
+
it("should return the correct values", async () => {
|
|
33
|
+
const hook = renderHook(() => useCommonVariables(input));
|
|
34
|
+
|
|
35
|
+
expect(hook.result.current.size).toMatchInlineSnapshot("700");
|
|
36
|
+
expect(hook.result.current.validLength).toMatchInlineSnapshot("3");
|
|
37
|
+
expect(hook.result.current.handlerOffset.value).toMatchInlineSnapshot(
|
|
38
|
+
"-0",
|
|
39
|
+
);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import React from "react";
|
|
2
1
|
import type Animated from "react-native-reanimated";
|
|
3
2
|
import { useSharedValue, useAnimatedReaction } from "react-native-reanimated";
|
|
4
3
|
|
|
5
4
|
import type { TInitializeCarouselProps } from "./useInitProps";
|
|
6
5
|
|
|
7
|
-
import {
|
|
6
|
+
import { computeOffsetIfDataChanged } from "../utils/compute-offset-if-data-changed";
|
|
7
|
+
import { computeOffsetIfSizeChanged } from "../utils/compute-offset-if-size-changed";
|
|
8
8
|
import { handlerOffsetDirection } from "../utils/handleroffset-direction";
|
|
9
9
|
|
|
10
10
|
interface ICommonVariables {
|
|
@@ -26,21 +26,20 @@ export function useCommonVariables(
|
|
|
26
26
|
loop,
|
|
27
27
|
} = props;
|
|
28
28
|
const size = vertical ? height : width;
|
|
29
|
-
const validLength = dataLength - 1;
|
|
30
29
|
const defaultHandlerOffsetValue = -Math.abs(defaultIndex * size);
|
|
31
30
|
const _handlerOffset = useSharedValue<number>(defaultHandlerOffsetValue);
|
|
32
31
|
const handlerOffset = defaultScrollOffsetValue ?? _handlerOffset;
|
|
33
32
|
const prevDataLength = useSharedValue(dataLength);
|
|
33
|
+
const prevSize = useSharedValue(size);
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
/**
|
|
36
|
+
* When data changes, we need to compute new index for handlerOffset
|
|
37
|
+
*/
|
|
39
38
|
useAnimatedReaction(() => {
|
|
40
39
|
const previousLength = prevDataLength.value;
|
|
41
40
|
const currentLength = dataLength;
|
|
42
41
|
const isLengthChanged = previousLength !== currentLength;
|
|
43
|
-
const shouldComputed = isLengthChanged && loop;
|
|
42
|
+
const shouldComputed = (isLengthChanged && loop);
|
|
44
43
|
|
|
45
44
|
if (shouldComputed)
|
|
46
45
|
prevDataLength.value = dataLength;
|
|
@@ -55,7 +54,7 @@ export function useCommonVariables(
|
|
|
55
54
|
// direction -> 1 | -1
|
|
56
55
|
const direction = handlerOffsetDirection(handlerOffset);
|
|
57
56
|
|
|
58
|
-
handlerOffset.value =
|
|
57
|
+
handlerOffset.value = computeOffsetIfDataChanged({
|
|
59
58
|
direction,
|
|
60
59
|
previousLength,
|
|
61
60
|
currentLength,
|
|
@@ -65,9 +64,35 @@ export function useCommonVariables(
|
|
|
65
64
|
}
|
|
66
65
|
}, [dataLength, loop]);
|
|
67
66
|
|
|
67
|
+
/**
|
|
68
|
+
* When size changes, we need to compute new index for handlerOffset
|
|
69
|
+
*/
|
|
70
|
+
useAnimatedReaction(() => {
|
|
71
|
+
const previousSize = prevSize.value;
|
|
72
|
+
const isSizeChanged = previousSize !== size;
|
|
73
|
+
const shouldComputed = isSizeChanged;
|
|
74
|
+
|
|
75
|
+
if (shouldComputed)
|
|
76
|
+
prevSize.value = size;
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
shouldComputed,
|
|
80
|
+
previousSize,
|
|
81
|
+
size,
|
|
82
|
+
};
|
|
83
|
+
}, ({ shouldComputed, previousSize, size }) => {
|
|
84
|
+
if (shouldComputed) {
|
|
85
|
+
handlerOffset.value = computeOffsetIfSizeChanged({
|
|
86
|
+
handlerOffset: handlerOffset.value,
|
|
87
|
+
prevSize: previousSize,
|
|
88
|
+
size,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}, [size]);
|
|
92
|
+
|
|
68
93
|
return {
|
|
69
94
|
size,
|
|
70
|
-
validLength,
|
|
95
|
+
validLength: dataLength - 1,
|
|
71
96
|
handlerOffset,
|
|
72
97
|
};
|
|
73
98
|
}
|
|
@@ -2,8 +2,8 @@ import React from "react";
|
|
|
2
2
|
|
|
3
3
|
import type { TInitializeCarouselProps } from "./useInitProps";
|
|
4
4
|
|
|
5
|
+
import type { TAnimationStyle } from "../components/BaseLayout";
|
|
5
6
|
import { Layouts } from "../layouts";
|
|
6
|
-
import type { TAnimationStyle } from "../layouts/BaseLayout";
|
|
7
7
|
|
|
8
8
|
type TLayoutConfigOpts<T> = TInitializeCarouselProps<T> & { size: number };
|
|
9
9
|
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { useSharedValue } from "react-native-reanimated";
|
|
2
|
+
|
|
3
|
+
import { renderHook } from "@testing-library/react-hooks";
|
|
4
|
+
|
|
5
|
+
import type { IOpts } from "./useOffsetX";
|
|
6
|
+
import { useOffsetX } from "./useOffsetX";
|
|
7
|
+
import type { IVisibleRanges } from "./useVisibleRanges";
|
|
8
|
+
|
|
9
|
+
describe("useSharedValue", () => {
|
|
10
|
+
it("should return the correct values", async () => {
|
|
11
|
+
const hook = renderHook(() => {
|
|
12
|
+
const range = useSharedValue({
|
|
13
|
+
negativeRange: [7, 9],
|
|
14
|
+
positiveRange: [0, 3],
|
|
15
|
+
}) as IVisibleRanges;
|
|
16
|
+
const inputs: Array<{
|
|
17
|
+
config: IOpts
|
|
18
|
+
range: IVisibleRanges
|
|
19
|
+
}> = Array.from({ length: 10 }).map((_, index) => ({
|
|
20
|
+
config: {
|
|
21
|
+
dataLength: 10,
|
|
22
|
+
handlerOffset: useSharedValue(-0),
|
|
23
|
+
index,
|
|
24
|
+
loop: false,
|
|
25
|
+
size: 393,
|
|
26
|
+
},
|
|
27
|
+
range,
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
return inputs.map((input) => {
|
|
31
|
+
const { config, range } = input;
|
|
32
|
+
|
|
33
|
+
return useOffsetX(config, range);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const expected = hook.result.current.map(v => v.value).slice();
|
|
38
|
+
|
|
39
|
+
expect(expected).toMatchInlineSnapshot(`
|
|
40
|
+
[
|
|
41
|
+
0,
|
|
42
|
+
393,
|
|
43
|
+
786,
|
|
44
|
+
1179,
|
|
45
|
+
9007199254740991,
|
|
46
|
+
9007199254740991,
|
|
47
|
+
9007199254740991,
|
|
48
|
+
2751,
|
|
49
|
+
3144,
|
|
50
|
+
3537,
|
|
51
|
+
]
|
|
52
|
+
`);
|
|
53
|
+
});
|
|
54
|
+
});
|