@fountain-ui/lab 2.0.0-beta.13 → 2.0.0-beta.16
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/build/commonjs/Carousel/Carousel.js +27 -30
- package/build/commonjs/Carousel/Carousel.js.map +1 -1
- package/build/commonjs/Carousel/animation/createDefaultScrollAnimation.js +2 -2
- package/build/commonjs/Carousel/animation/createDefaultScrollAnimation.js.map +1 -1
- package/build/commonjs/Carousel/animation/parallaxItemStyleFactory.js +2 -2
- package/build/commonjs/Carousel/animation/parallaxItemStyleFactory.js.map +1 -1
- package/build/commonjs/Carousel/components/ItemView.js +2 -2
- package/build/commonjs/Carousel/components/ItemView.js.map +1 -1
- package/build/commonjs/Carousel/components/ScrollViewGesture.js +6 -6
- package/build/commonjs/Carousel/components/ScrollViewGesture.js.map +1 -1
- package/build/commonjs/Carousel/{hooks → components}/useItemInterpolation.js +6 -4
- package/build/commonjs/Carousel/components/useItemInterpolation.js.map +1 -0
- package/build/commonjs/Carousel/hooks/index.js +0 -16
- package/build/commonjs/Carousel/hooks/index.js.map +1 -1
- package/build/commonjs/Carousel/hooks/useIndexController.js +23 -45
- package/build/commonjs/Carousel/hooks/useIndexController.js.map +1 -1
- package/build/commonjs/Carousel/hooks/useItemVisibilityStore.js +12 -12
- package/build/commonjs/Carousel/hooks/useItemVisibilityStore.js.map +1 -1
- package/build/commonjs/Carousel/hooks/usePagingAnimation.js +72 -56
- package/build/commonjs/Carousel/hooks/usePagingAnimation.js.map +1 -1
- package/build/commonjs/Carousel/tick.js +16 -0
- package/build/commonjs/Carousel/tick.js.map +1 -0
- package/build/commonjs/Carousel/types.js +1 -0
- package/build/commonjs/Carousel/types.js.map +1 -1
- package/build/commonjs/ViewPager/ChildrenMemoizedPage.js +53 -47
- package/build/commonjs/ViewPager/ChildrenMemoizedPage.js.map +1 -1
- package/build/commonjs/ViewPager/InternalContext.js +17 -0
- package/build/commonjs/ViewPager/InternalContext.js.map +1 -0
- package/build/commonjs/ViewPager/ViewPagerNative.js +40 -17
- package/build/commonjs/ViewPager/ViewPagerNative.js.map +1 -1
- package/build/commonjs/ViewPager/ViewPagerProps.js.map +1 -1
- package/build/commonjs/ViewPager/ViewPagerWeb.js +19 -8
- package/build/commonjs/ViewPager/ViewPagerWeb.js.map +1 -1
- package/build/commonjs/ViewPager/index.js.map +1 -1
- package/build/commonjs/ViewPager/types.js +6 -0
- package/build/commonjs/ViewPager/types.js.map +1 -0
- package/build/commonjs/ViewPager/usePageStore.js +35 -0
- package/build/commonjs/ViewPager/usePageStore.js.map +1 -0
- package/build/commonjs/ViewPager/utils.js.map +1 -1
- package/build/commonjs/ViewabilityTrackerView/measureViewability.js +6 -6
- package/build/commonjs/ViewabilityTrackerView/measureViewability.js.map +1 -1
- package/build/commonjs/hooks/useUnstableCollapsibleAppBar.js +1 -1
- package/build/commonjs/hooks/useUnstableCollapsibleAppBar.js.map +1 -1
- package/build/module/Carousel/Carousel.js +27 -32
- package/build/module/Carousel/Carousel.js.map +1 -1
- package/build/module/Carousel/animation/createDefaultScrollAnimation.js +2 -2
- package/build/module/Carousel/animation/createDefaultScrollAnimation.js.map +1 -1
- package/build/module/Carousel/animation/parallaxItemStyleFactory.js +2 -2
- package/build/module/Carousel/animation/parallaxItemStyleFactory.js.map +1 -1
- package/build/module/Carousel/components/ItemView.js +1 -1
- package/build/module/Carousel/components/ItemView.js.map +1 -1
- package/build/module/Carousel/components/ScrollViewGesture.js +6 -6
- package/build/module/Carousel/components/ScrollViewGesture.js.map +1 -1
- package/build/module/Carousel/{hooks → components}/useItemInterpolation.js +3 -3
- package/build/module/Carousel/components/useItemInterpolation.js.map +1 -0
- package/build/module/Carousel/hooks/index.js +0 -2
- package/build/module/Carousel/hooks/index.js.map +1 -1
- package/build/module/Carousel/hooks/useIndexController.js +23 -39
- package/build/module/Carousel/hooks/useIndexController.js.map +1 -1
- package/build/module/Carousel/hooks/useItemVisibilityStore.js +10 -11
- package/build/module/Carousel/hooks/useItemVisibilityStore.js.map +1 -1
- package/build/module/Carousel/hooks/usePagingAnimation.js +73 -56
- package/build/module/Carousel/hooks/usePagingAnimation.js.map +1 -1
- package/build/module/Carousel/tick.js +6 -0
- package/build/module/Carousel/tick.js.map +1 -0
- package/build/module/Carousel/types.js +1 -0
- package/build/module/Carousel/types.js.map +1 -1
- package/build/module/ViewPager/ChildrenMemoizedPage.js +53 -47
- package/build/module/ViewPager/ChildrenMemoizedPage.js.map +1 -1
- package/build/module/ViewPager/InternalContext.js +7 -0
- package/build/module/ViewPager/InternalContext.js.map +1 -0
- package/build/module/ViewPager/ViewPagerNative.js +38 -17
- package/build/module/ViewPager/ViewPagerNative.js.map +1 -1
- package/build/module/ViewPager/ViewPagerProps.js.map +1 -1
- package/build/module/ViewPager/ViewPagerWeb.js +16 -8
- package/build/module/ViewPager/ViewPagerWeb.js.map +1 -1
- package/build/module/ViewPager/index.js.map +1 -1
- package/build/module/ViewPager/types.js +2 -0
- package/build/module/ViewPager/types.js.map +1 -0
- package/build/module/ViewPager/usePageStore.js +25 -0
- package/build/module/ViewPager/usePageStore.js.map +1 -0
- package/build/module/ViewPager/utils.js.map +1 -1
- package/build/module/ViewabilityTrackerView/measureViewability.js +2 -2
- package/build/module/ViewabilityTrackerView/measureViewability.js.map +1 -1
- package/build/module/hooks/useUnstableCollapsibleAppBar.js +1 -1
- package/build/module/hooks/useUnstableCollapsibleAppBar.js.map +1 -1
- package/build/typescript/Carousel/components/ScrollViewGesture.d.ts +2 -2
- package/build/typescript/Carousel/{hooks → components}/useItemInterpolation.d.ts +0 -0
- package/build/typescript/Carousel/hooks/index.d.ts +0 -2
- package/build/typescript/Carousel/hooks/useIndexController.d.ts +0 -2
- package/build/typescript/Carousel/hooks/useItemVisibilityStore.d.ts +5 -2
- package/build/typescript/Carousel/hooks/usePagingAnimation.d.ts +5 -7
- package/build/typescript/Carousel/tick.d.ts +2 -0
- package/build/typescript/Carousel/types.d.ts +4 -2
- package/build/typescript/ViewPager/ChildrenMemoizedPage.d.ts +1 -1
- package/build/typescript/ViewPager/InternalContext.d.ts +7 -0
- package/build/typescript/ViewPager/ViewPagerNative.d.ts +2 -2
- package/build/typescript/ViewPager/ViewPagerProps.d.ts +4 -22
- package/build/typescript/ViewPager/ViewPagerWeb.d.ts +2 -2
- package/build/typescript/ViewPager/index.d.ts +2 -1
- package/build/typescript/ViewPager/types.d.ts +19 -0
- package/build/typescript/ViewPager/usePageStore.d.ts +2 -0
- package/build/typescript/ViewPager/utils.d.ts +1 -1
- package/package.json +3 -3
- package/src/Carousel/Carousel.tsx +25 -34
- package/src/Carousel/animation/createDefaultScrollAnimation.ts +2 -2
- package/src/Carousel/animation/parallaxItemStyleFactory.ts +1 -1
- package/src/Carousel/components/ItemView.tsx +1 -1
- package/src/Carousel/components/ScrollViewGesture.tsx +8 -7
- package/src/Carousel/{hooks → components}/useItemInterpolation.ts +3 -3
- package/src/Carousel/hooks/index.ts +0 -2
- package/src/Carousel/hooks/useIndexController.tsx +25 -47
- package/src/Carousel/hooks/useItemVisibilityStore.ts +17 -13
- package/src/Carousel/hooks/usePagingAnimation.ts +104 -64
- package/src/Carousel/tick.ts +6 -0
- package/src/Carousel/types.ts +6 -2
- package/src/ViewPager/ChildrenMemoizedPage.tsx +53 -50
- package/src/ViewPager/InternalContext.ts +13 -0
- package/src/ViewPager/ViewPagerNative.tsx +53 -39
- package/src/ViewPager/ViewPagerProps.ts +4 -27
- package/src/ViewPager/ViewPagerWeb.tsx +23 -18
- package/src/ViewPager/index.ts +2 -1
- package/src/ViewPager/types.ts +24 -0
- package/src/ViewPager/usePageStore.ts +30 -0
- package/src/ViewPager/utils.tsx +1 -1
- package/src/ViewabilityTrackerView/measureViewability.ts +1 -3
- package/src/hooks/useUnstableCollapsibleAppBar.ts +1 -1
- package/build/commonjs/Carousel/hooks/useDimensionChangeReaction.js +0 -23
- package/build/commonjs/Carousel/hooks/useDimensionChangeReaction.js.map +0 -1
- package/build/commonjs/Carousel/hooks/useItemInterpolation.js.map +0 -1
- package/build/module/Carousel/hooks/useDimensionChangeReaction.js +0 -14
- package/build/module/Carousel/hooks/useDimensionChangeReaction.js.map +0 -1
- package/build/module/Carousel/hooks/useItemInterpolation.js.map +0 -1
- package/build/typescript/Carousel/hooks/useDimensionChangeReaction.d.ts +0 -7
- package/src/Carousel/hooks/useDimensionChangeReaction.ts +0 -25
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import React, { forwardRef, memo, useImperativeHandle, useMemo, useRef } from 'react';
|
|
1
|
+
import React, { forwardRef, memo, useCallback, useImperativeHandle, useMemo, useRef } from 'react';
|
|
2
2
|
import { Animated } from 'react-native';
|
|
3
3
|
import ViewabilityTrackerView from '../ViewabilityTrackerView';
|
|
4
4
|
import type CarouselProps from './CarouselProps';
|
|
5
5
|
import type { CarouselInstance } from './types';
|
|
6
6
|
import {
|
|
7
7
|
useAutoplayController,
|
|
8
|
-
useDimensionChangeReaction,
|
|
9
8
|
useIndexController,
|
|
10
9
|
useItemVisibilityStore,
|
|
11
10
|
useLoopedData,
|
|
@@ -26,7 +25,7 @@ const Carousel = forwardRef<CarouselInstance, CarouselProps>(function Carousel(p
|
|
|
26
25
|
itemHeight,
|
|
27
26
|
itemWidth,
|
|
28
27
|
loop = false,
|
|
29
|
-
onIndexChange,
|
|
28
|
+
onIndexChange: onIndexChangeProp,
|
|
30
29
|
renderItem,
|
|
31
30
|
scrollEnabled = true,
|
|
32
31
|
style,
|
|
@@ -36,41 +35,41 @@ const Carousel = forwardRef<CarouselInstance, CarouselProps>(function Carousel(p
|
|
|
36
35
|
const data = useLoopedData(originalData, loop);
|
|
37
36
|
|
|
38
37
|
const initialTx = itemWidth * initialIndex;
|
|
39
|
-
const
|
|
40
|
-
const
|
|
38
|
+
const offsetX = useRef(new Animated.Value(initialTx)).current;
|
|
39
|
+
const translateX = useRef(new Animated.Value(0)).current;
|
|
40
|
+
const globalInterpolation = Animated.add(offsetX, translateX);
|
|
41
41
|
|
|
42
|
-
const {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
42
|
+
const [itemVisibilityStore, onIndexChange] = useItemVisibilityStore({
|
|
43
|
+
initialIndex,
|
|
44
|
+
numberOfData: data.length,
|
|
45
|
+
windowSize,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const handleIndexChange = useCallback((newIndex: number) => {
|
|
49
|
+
onIndexChange(newIndex);
|
|
50
|
+
onIndexChangeProp?.(newIndex);
|
|
51
|
+
}, [onIndexChange, onIndexChangeProp]);
|
|
52
|
+
|
|
53
|
+
const indexController = useIndexController({
|
|
49
54
|
initialIndex,
|
|
50
55
|
itemWidth,
|
|
51
56
|
numberOfOriginalData: originalData.length,
|
|
52
|
-
onIndexChange,
|
|
57
|
+
onIndexChange: handleIndexChange,
|
|
53
58
|
});
|
|
54
59
|
|
|
55
|
-
const
|
|
56
|
-
currentIndex,
|
|
57
|
-
numberOfData: data.length,
|
|
58
|
-
windowSize,
|
|
59
|
-
});
|
|
60
|
+
const { getCurrentIndex } = indexController;
|
|
60
61
|
|
|
61
62
|
const {
|
|
62
|
-
|
|
63
|
-
globalInterpolation,
|
|
63
|
+
interruptAnimation,
|
|
64
64
|
startPagingAnimation,
|
|
65
65
|
} = usePagingAnimation({
|
|
66
|
-
controlledTx,
|
|
67
66
|
createScrollAnimation,
|
|
68
|
-
getCurrentIndex,
|
|
69
67
|
itemWidth,
|
|
70
|
-
|
|
68
|
+
indexController,
|
|
71
69
|
loop,
|
|
72
70
|
numberOfData: data.length,
|
|
73
|
-
|
|
71
|
+
offsetX,
|
|
72
|
+
translateX,
|
|
74
73
|
});
|
|
75
74
|
|
|
76
75
|
const autoplayController = useAutoplayController({
|
|
@@ -79,12 +78,6 @@ const Carousel = forwardRef<CarouselInstance, CarouselProps>(function Carousel(p
|
|
|
79
78
|
startPagingAnimation,
|
|
80
79
|
});
|
|
81
80
|
|
|
82
|
-
useDimensionChangeReaction({
|
|
83
|
-
controlledTx,
|
|
84
|
-
currentIndex,
|
|
85
|
-
itemWidth,
|
|
86
|
-
});
|
|
87
|
-
|
|
88
81
|
useImperativeHandle(
|
|
89
82
|
ref,
|
|
90
83
|
() => ({
|
|
@@ -116,8 +109,6 @@ const Carousel = forwardRef<CarouselInstance, CarouselProps>(function Carousel(p
|
|
|
116
109
|
|
|
117
110
|
return (
|
|
118
111
|
<InternalContext.Provider value={contextValue}>
|
|
119
|
-
{monitorElement}
|
|
120
|
-
|
|
121
112
|
<ViewabilityTrackerView
|
|
122
113
|
enabled={autoplay && !disableSmartAutoplay}
|
|
123
114
|
measurementIntervalMillis={Math.max(3000, autoplayInterval)}
|
|
@@ -131,8 +122,8 @@ const Carousel = forwardRef<CarouselInstance, CarouselProps>(function Carousel(p
|
|
|
131
122
|
>
|
|
132
123
|
<ScrollViewGesture
|
|
133
124
|
autoplayController={autoplayController}
|
|
134
|
-
|
|
135
|
-
|
|
125
|
+
interruptAnimation={interruptAnimation}
|
|
126
|
+
translateX={translateX}
|
|
136
127
|
scrollEnabled={scrollEnabled}
|
|
137
128
|
startPagingAnimation={startPagingAnimation}
|
|
138
129
|
>
|
|
@@ -6,8 +6,8 @@ export default function createDefaultScrollAnimation(
|
|
|
6
6
|
): Animated.CompositeAnimation {
|
|
7
7
|
return Animated.timing(animatedValue, {
|
|
8
8
|
toValue: toValue,
|
|
9
|
-
duration:
|
|
10
|
-
easing: Easing.bezier(0.
|
|
9
|
+
duration: 180,
|
|
10
|
+
easing: Easing.bezier(0.2, 0.2, 0.2, 1),
|
|
11
11
|
useNativeDriver: true,
|
|
12
12
|
});
|
|
13
13
|
};
|
|
@@ -25,8 +25,8 @@ export default function parallaxItemStyleFactory(config: ParallaxAnimationConfig
|
|
|
25
25
|
adjacentItemScale,
|
|
26
26
|
scrollingOffset,
|
|
27
27
|
}: Required<ParallaxAnimationConfig> = {
|
|
28
|
-
...config,
|
|
29
28
|
...defaultParallaxAnimationConfig,
|
|
29
|
+
...config,
|
|
30
30
|
};
|
|
31
31
|
|
|
32
32
|
const createItemStyle: CreateItemStyle = (itemInterpolation, itemWidth) => {
|
|
@@ -3,7 +3,7 @@ import React, { useContext, useEffect, useMemo, useState } from 'react';
|
|
|
3
3
|
import type { ViewProps } from 'react-native';
|
|
4
4
|
import { Animated } from 'react-native';
|
|
5
5
|
import { StyleSheet } from '@fountain-ui/core';
|
|
6
|
-
import
|
|
6
|
+
import useItemInterpolation from './useItemInterpolation';
|
|
7
7
|
import InternalContext from './InternalContext';
|
|
8
8
|
|
|
9
9
|
export interface ItemViewProps {
|
|
@@ -8,8 +8,8 @@ import type { AutoplayController, PagingDirection, StartPagingAnimation } from '
|
|
|
8
8
|
export interface ScrollViewGestureProps {
|
|
9
9
|
autoplayController: AutoplayController;
|
|
10
10
|
children: ReactNode;
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
interruptAnimation: () => void;
|
|
12
|
+
translateX: Animated.Value,
|
|
13
13
|
scrollEnabled: boolean;
|
|
14
14
|
startPagingAnimation: StartPagingAnimation;
|
|
15
15
|
}
|
|
@@ -23,6 +23,7 @@ const activeOffsetX: number[] = [-ACTIVE_OFFSET_ABS_X, ACTIVE_OFFSET_ABS_X];
|
|
|
23
23
|
const endAnimationStates: Readonly<GestureHandlerState[]> = [
|
|
24
24
|
GestureHandlerState.CANCELLED,
|
|
25
25
|
GestureHandlerState.END,
|
|
26
|
+
GestureHandlerState.FAILED,
|
|
26
27
|
];
|
|
27
28
|
|
|
28
29
|
function shouldScrollToAdjacent(translationX: number, velocityX: number): boolean {
|
|
@@ -38,8 +39,8 @@ export default function ScrollViewGesture(props: ScrollViewGestureProps) {
|
|
|
38
39
|
const {
|
|
39
40
|
autoplayController,
|
|
40
41
|
children,
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
interruptAnimation,
|
|
43
|
+
translateX,
|
|
43
44
|
scrollEnabled,
|
|
44
45
|
startPagingAnimation,
|
|
45
46
|
} = props;
|
|
@@ -49,11 +50,11 @@ export default function ScrollViewGesture(props: ScrollViewGestureProps) {
|
|
|
49
50
|
const handleGestureBegin = useCallback(() => {
|
|
50
51
|
pauseAutoplay();
|
|
51
52
|
|
|
52
|
-
|
|
53
|
-
}, [
|
|
53
|
+
interruptAnimation();
|
|
54
|
+
}, [interruptAnimation, pauseAutoplay]);
|
|
54
55
|
|
|
55
56
|
const handleGestureEvent = useCallback(Animated.event(
|
|
56
|
-
[{ nativeEvent: { translationX:
|
|
57
|
+
[{ nativeEvent: { translationX: translateX } }],
|
|
57
58
|
{ useNativeDriver: true },
|
|
58
59
|
), []);
|
|
59
60
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useContext, useMemo } from 'react';
|
|
2
2
|
import { Animated } from 'react-native';
|
|
3
|
-
import
|
|
3
|
+
import InternalContext from './InternalContext';
|
|
4
4
|
|
|
5
5
|
const OVERSCROLL_FRICTION_FACTOR = 4;
|
|
6
6
|
|
|
@@ -94,9 +94,9 @@ export default function useItemInterpolation(index: number): Animated.AnimatedIn
|
|
|
94
94
|
? interpolationConfigOnLoop
|
|
95
95
|
: interpolationConfigOnNoLoop;
|
|
96
96
|
|
|
97
|
-
const
|
|
97
|
+
const localOffsetX = globalInterpolation.interpolate(interpolationConfig);
|
|
98
98
|
|
|
99
|
-
return Animated.divide(
|
|
99
|
+
return Animated.divide(localOffsetX, itemWidth);
|
|
100
100
|
}, [
|
|
101
101
|
globalInterpolation,
|
|
102
102
|
interpolationConfigOnLoop,
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
export { default as useAutoplayController } from './useAutoplayController';
|
|
2
|
-
export { default as useDimensionChangeReaction } from './useDimensionChangeReaction';
|
|
3
2
|
export { default as useIndexController } from './useIndexController';
|
|
4
|
-
export { default as useItemInterpolation } from './useItemInterpolation';
|
|
5
3
|
export { default as useLoopedData } from './useLoopedData';
|
|
6
4
|
export { default as usePagingAnimation } from './usePagingAnimation';
|
|
7
5
|
export { default as useItemVisibilityStore } from './useItemVisibilityStore';
|
|
@@ -1,76 +1,54 @@
|
|
|
1
|
-
import React, { useCallback,
|
|
2
|
-
import {
|
|
3
|
-
import type { IndexController } from '../types';
|
|
1
|
+
import React, { useCallback, useRef } from 'react';
|
|
2
|
+
import { mod } from '@fountain-ui/utils';
|
|
3
|
+
import type { AnimationState, IndexController } from '../types';
|
|
4
4
|
|
|
5
5
|
export interface UseIndexControllerParameters {
|
|
6
|
-
controlledTx: Animated.AnimatedValue;
|
|
7
6
|
initialIndex: number;
|
|
8
7
|
itemWidth: number;
|
|
9
8
|
numberOfOriginalData: number;
|
|
10
9
|
onIndexChange?: (newIndex: number) => void;
|
|
11
10
|
}
|
|
12
11
|
|
|
13
|
-
const normalizeIndex = (maybeIndex: number, numberOfData: number): number =>
|
|
14
|
-
Math.abs(Math.floor(maybeIndex)) % numberOfData;
|
|
15
|
-
|
|
16
12
|
export default function useIndexController(params: UseIndexControllerParameters): IndexController {
|
|
17
13
|
const {
|
|
18
|
-
controlledTx,
|
|
19
14
|
initialIndex,
|
|
20
15
|
itemWidth,
|
|
21
16
|
numberOfOriginalData,
|
|
22
17
|
onIndexChange,
|
|
23
18
|
} = params;
|
|
24
19
|
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
const maybeIndex = useMemo(() => {
|
|
29
|
-
const negative = new Animated.Value(-1);
|
|
30
|
-
const reversedTx = Animated.multiply(controlledTx, negative);
|
|
31
|
-
const normalized = Animated.divide(reversedTx, itemWidth);
|
|
32
|
-
return Animated.modulo(normalized, numberOfOriginalData);
|
|
33
|
-
}, [
|
|
34
|
-
controlledTx,
|
|
35
|
-
itemWidth,
|
|
36
|
-
numberOfOriginalData,
|
|
37
|
-
]);
|
|
38
|
-
|
|
39
|
-
useEffect(() => {
|
|
40
|
-
const subscriptionId = maybeIndex.addListener((observedValue) => {
|
|
41
|
-
const newIndex = normalizeIndex(observedValue.value, numberOfOriginalData);
|
|
20
|
+
const currentIndexRef = useRef<number>(initialIndex);
|
|
21
|
+
const indexChangedRef = useRef<boolean>(false);
|
|
42
22
|
|
|
43
|
-
|
|
44
|
-
indexRef.current = newIndex;
|
|
45
|
-
setIndex(newIndex);
|
|
23
|
+
const getCurrentIndex = useCallback(() => currentIndexRef.current, []);
|
|
46
24
|
|
|
47
|
-
|
|
25
|
+
const notifyAnimationState = useCallback((state: AnimationState) => {
|
|
26
|
+
if (state === 'finished' || state === 'interrupted') {
|
|
27
|
+
if (indexChangedRef.current) {
|
|
28
|
+
onIndexChange?.(getCurrentIndex());
|
|
48
29
|
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return () => {
|
|
52
|
-
maybeIndex.removeListener(subscriptionId);
|
|
53
|
-
};
|
|
30
|
+
}
|
|
54
31
|
}, [
|
|
55
|
-
|
|
56
|
-
numberOfOriginalData,
|
|
32
|
+
getCurrentIndex,
|
|
57
33
|
onIndexChange,
|
|
58
34
|
]);
|
|
59
35
|
|
|
60
|
-
const
|
|
36
|
+
const notifyOffsetHasChanged = useCallback((offset: number) => {
|
|
37
|
+
const roundedOffset = Math.round(offset / itemWidth) * itemWidth;
|
|
38
|
+
|
|
39
|
+
// To prevent floating point problem, make sure index is integer type.
|
|
40
|
+
const nextIndex = Math.floor(mod((-roundedOffset / itemWidth), numberOfOriginalData));
|
|
41
|
+
|
|
42
|
+
if (nextIndex !== currentIndexRef.current) {
|
|
43
|
+
currentIndexRef.current = nextIndex;
|
|
44
|
+
indexChangedRef.current = true;
|
|
45
|
+
}
|
|
46
|
+
}, [itemWidth, numberOfOriginalData]);
|
|
61
47
|
|
|
62
48
|
return {
|
|
63
|
-
currentIndex: index,
|
|
64
49
|
getCurrentIndex,
|
|
65
50
|
lastIndex: numberOfOriginalData - 1,
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
collapsable={false}
|
|
69
|
-
style={[
|
|
70
|
-
{ zIndex: maybeIndex },
|
|
71
|
-
{ width: 1, height: 1, position: 'absolute' },
|
|
72
|
-
]}
|
|
73
|
-
/>
|
|
74
|
-
),
|
|
51
|
+
notifyAnimationState,
|
|
52
|
+
notifyOffsetHasChanged,
|
|
75
53
|
};
|
|
76
54
|
};
|
|
@@ -1,12 +1,17 @@
|
|
|
1
|
-
import { useEffect, useRef, useState } from 'react';
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { mod } from '@fountain-ui/utils';
|
|
2
3
|
import type { ItemVisibilityStore, VisibleIndexRanges } from '../types';
|
|
3
4
|
|
|
4
5
|
export interface Parameters {
|
|
5
|
-
|
|
6
|
+
initialIndex: number;
|
|
6
7
|
numberOfData: number;
|
|
7
8
|
windowSize: number;
|
|
8
9
|
}
|
|
9
10
|
|
|
11
|
+
export interface OnIndexChange {
|
|
12
|
+
(newIndex: number): void;
|
|
13
|
+
}
|
|
14
|
+
|
|
10
15
|
class SimpleItemVisibilityStore implements ItemVisibilityStore {
|
|
11
16
|
|
|
12
17
|
private store: VisibleIndexRanges;
|
|
@@ -52,10 +57,6 @@ function normalize(windowSize: number, numberOfData: number): number {
|
|
|
52
57
|
return windowSize;
|
|
53
58
|
}
|
|
54
59
|
|
|
55
|
-
function mod(value: number, modulo: number): number {
|
|
56
|
-
return ((value % modulo) + modulo) % modulo;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
60
|
function makeVisibleIndexRanges(numberOfData: number, windowSize: number, index: number): VisibleIndexRanges {
|
|
60
61
|
const ws = normalize(windowSize, numberOfData);
|
|
61
62
|
|
|
@@ -81,21 +82,24 @@ function makeVisibleIndexRanges(numberOfData: number, windowSize: number, index:
|
|
|
81
82
|
];
|
|
82
83
|
}
|
|
83
84
|
|
|
84
|
-
export default function useItemVisibilityStore(params: Parameters): ItemVisibilityStore {
|
|
85
|
+
export default function useItemVisibilityStore(params: Parameters): [ItemVisibilityStore, OnIndexChange] {
|
|
85
86
|
const {
|
|
86
|
-
|
|
87
|
+
initialIndex,
|
|
87
88
|
numberOfData,
|
|
88
89
|
windowSize,
|
|
89
90
|
} = params;
|
|
90
91
|
|
|
91
|
-
const [initialRange] = useState(() =>
|
|
92
|
+
const [initialRange] = useState(() => {
|
|
93
|
+
return makeVisibleIndexRanges(numberOfData, windowSize, initialIndex);
|
|
94
|
+
});
|
|
95
|
+
|
|
92
96
|
const store = useRef(new SimpleItemVisibilityStore(initialRange)).current;
|
|
93
97
|
|
|
94
|
-
|
|
95
|
-
const newRanges = makeVisibleIndexRanges(numberOfData, windowSize,
|
|
98
|
+
const onIndexChange: OnIndexChange = useCallback((newIndex) => {
|
|
99
|
+
const newRanges = makeVisibleIndexRanges(numberOfData, windowSize, newIndex);
|
|
96
100
|
|
|
97
101
|
store.dispatch(newRanges);
|
|
98
|
-
}, [
|
|
102
|
+
}, [numberOfData, windowSize]);
|
|
99
103
|
|
|
100
104
|
useEffect(() => {
|
|
101
105
|
return () => {
|
|
@@ -103,5 +107,5 @@ export default function useItemVisibilityStore(params: Parameters): ItemVisibili
|
|
|
103
107
|
};
|
|
104
108
|
}, []);
|
|
105
109
|
|
|
106
|
-
return store;
|
|
110
|
+
return [store, onIndexChange];
|
|
107
111
|
};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { useCallback,
|
|
2
|
-
import { Animated
|
|
1
|
+
import { useCallback, useRef } from 'react';
|
|
2
|
+
import { Animated } from 'react-native';
|
|
3
3
|
import type {
|
|
4
4
|
CreateScrollAnimation,
|
|
5
5
|
DirectionalPagingAnimationConfig,
|
|
6
|
-
|
|
6
|
+
IndexController,
|
|
7
7
|
IndexPagingAnimationConfig,
|
|
8
8
|
PagingAnimationConfig,
|
|
9
9
|
PagingAnimationType,
|
|
@@ -12,19 +12,17 @@ import type {
|
|
|
12
12
|
} from '../types';
|
|
13
13
|
|
|
14
14
|
export interface PagingAnimationParameters {
|
|
15
|
-
controlledTx: Animated.Value;
|
|
16
15
|
createScrollAnimation: CreateScrollAnimation;
|
|
17
|
-
getCurrentIndex: GetCurrentIndex;
|
|
18
16
|
itemWidth: number;
|
|
19
|
-
|
|
17
|
+
indexController: IndexController;
|
|
20
18
|
loop: boolean;
|
|
21
19
|
numberOfData: number;
|
|
22
|
-
|
|
20
|
+
offsetX: Animated.Value;
|
|
21
|
+
translateX: Animated.Value;
|
|
23
22
|
}
|
|
24
23
|
|
|
25
24
|
export interface UsePagingAnimation {
|
|
26
|
-
|
|
27
|
-
globalInterpolation: Animated.AnimatedInterpolation;
|
|
25
|
+
interruptAnimation: () => void;
|
|
28
26
|
startPagingAnimation: StartPagingAnimation;
|
|
29
27
|
}
|
|
30
28
|
|
|
@@ -41,77 +39,110 @@ function directionToValue(itemWidth: number) {
|
|
|
41
39
|
};
|
|
42
40
|
}
|
|
43
41
|
|
|
42
|
+
function toValueCompensator(itemWidth: number) {
|
|
43
|
+
return function (toValue: number, currentOffset: number): number {
|
|
44
|
+
const remainder = Math.abs(currentOffset % itemWidth);
|
|
45
|
+
|
|
46
|
+
const halfOfItemWidth = Math.abs(itemWidth / 2);
|
|
47
|
+
const compensateVector = remainder > halfOfItemWidth
|
|
48
|
+
? remainder - itemWidth
|
|
49
|
+
: remainder;
|
|
50
|
+
|
|
51
|
+
const direction = currentOffset > 0 ? -1 : 1;
|
|
52
|
+
|
|
53
|
+
return toValue + (direction * compensateVector);
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
44
57
|
export default function usePagingAnimation(params: PagingAnimationParameters): UsePagingAnimation {
|
|
45
58
|
const {
|
|
46
|
-
controlledTx,
|
|
47
59
|
createScrollAnimation,
|
|
48
|
-
getCurrentIndex,
|
|
49
60
|
itemWidth,
|
|
50
|
-
|
|
61
|
+
indexController,
|
|
51
62
|
loop,
|
|
52
63
|
numberOfData,
|
|
53
|
-
|
|
64
|
+
offsetX,
|
|
65
|
+
translateX,
|
|
54
66
|
} = params;
|
|
55
67
|
|
|
56
|
-
const
|
|
57
|
-
|
|
68
|
+
const {
|
|
69
|
+
getCurrentIndex,
|
|
70
|
+
lastIndex,
|
|
71
|
+
notifyAnimationState,
|
|
72
|
+
notifyOffsetHasChanged,
|
|
73
|
+
} = indexController;
|
|
58
74
|
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
[controlledTx, offsetTx],
|
|
62
|
-
);
|
|
75
|
+
const toValueRef = useRef<number>(0);
|
|
76
|
+
const currentOffsetRef = useRef<number>(0);
|
|
63
77
|
|
|
64
|
-
|
|
65
|
-
const subscriptionId = controlledTx.addListener((value) => {
|
|
66
|
-
const currentTx = value.value;
|
|
78
|
+
const isAnimatingRef = useRef<boolean>(false);
|
|
67
79
|
|
|
68
|
-
|
|
69
|
-
if (currentTx !== 0) {
|
|
70
|
-
const maxWidth = numberOfData * itemWidth;
|
|
80
|
+
const maxWidth = Math.abs(numberOfData * itemWidth);
|
|
71
81
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
82
|
+
const ensureOffsetBoundary: (offset: number) => number = useCallback((offset: number) => {
|
|
83
|
+
if (loop) {
|
|
84
|
+
const isCloseToEnd = Math.abs(offset) >= (maxWidth - itemWidth);
|
|
85
|
+
if (isCloseToEnd) {
|
|
86
|
+
const signOfOffset = offset > 0 ? 1 : -1;
|
|
87
|
+
return offset + (-signOfOffset * maxWidth);
|
|
76
88
|
}
|
|
77
|
-
}
|
|
89
|
+
}
|
|
78
90
|
|
|
79
|
-
return
|
|
80
|
-
|
|
81
|
-
};
|
|
82
|
-
}, [numberOfData, itemWidth]);
|
|
91
|
+
return offset % maxWidth;
|
|
92
|
+
}, [itemWidth, loop, maxWidth]);
|
|
83
93
|
|
|
84
|
-
const
|
|
85
|
-
const
|
|
86
|
-
if (animationRef.current) {
|
|
87
|
-
animationRef.current?.stop();
|
|
88
|
-
animationRef.current = null;
|
|
89
|
-
}
|
|
90
|
-
};
|
|
94
|
+
const requireNewOffset = useCallback((newOffset: number) => {
|
|
95
|
+
const nextOffset = ensureOffsetBoundary(newOffset);
|
|
91
96
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
controlledTx.flattenOffset();
|
|
95
|
-
|
|
96
|
-
// FIXME: react-native-web bug maybe?
|
|
97
|
-
// `AnimatedValue.flattenOffset()` does not trigger any event listener.
|
|
98
|
-
// Accessing value directly via `_value` is dangerous but working on web (`useNativeDriver` always false).
|
|
99
|
-
// So setting same value with `value.setValue(value._value)` will trigger event listener.
|
|
100
|
-
if (Platform.OS === 'web') {
|
|
101
|
-
// @ts-ignore
|
|
102
|
-
controlledTx.setValue(controlledTx._value);
|
|
103
|
-
}
|
|
97
|
+
currentOffsetRef.current = nextOffset;
|
|
98
|
+
offsetX.setValue(nextOffset);
|
|
104
99
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
100
|
+
toValueRef.current = 0;
|
|
101
|
+
translateX.setValue(0);
|
|
102
|
+
}, [
|
|
103
|
+
ensureOffsetBoundary,
|
|
104
|
+
offsetX,
|
|
105
|
+
translateX,
|
|
106
|
+
]);
|
|
107
|
+
|
|
108
|
+
const interruptAnimation = useCallback(() => {
|
|
109
|
+
if (!isAnimatingRef.current) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
translateX.stopAnimation(lastValue => {
|
|
114
|
+
isAnimatingRef.current = false;
|
|
115
|
+
|
|
116
|
+
const prevOffset = currentOffsetRef.current;
|
|
117
|
+
const totalOffset = prevOffset + lastValue;
|
|
118
|
+
|
|
119
|
+
notifyOffsetHasChanged(totalOffset);
|
|
120
|
+
notifyAnimationState('interrupted');
|
|
121
|
+
|
|
122
|
+
requireNewOffset(totalOffset);
|
|
123
|
+
});
|
|
124
|
+
}, [requireNewOffset, translateX]);
|
|
125
|
+
|
|
126
|
+
const finalizeAnimation = useCallback(() => {
|
|
127
|
+
notifyAnimationState('finished');
|
|
108
128
|
|
|
109
|
-
|
|
129
|
+
isAnimatingRef.current = false;
|
|
110
130
|
|
|
111
|
-
|
|
112
|
-
|
|
131
|
+
const prevOffset = currentOffsetRef.current;
|
|
132
|
+
const toValue = toValueRef.current;
|
|
133
|
+
const totalOffset = prevOffset + toValue;
|
|
134
|
+
|
|
135
|
+
requireNewOffset(totalOffset);
|
|
136
|
+
}, [
|
|
137
|
+
notifyAnimationState,
|
|
138
|
+
requireNewOffset,
|
|
139
|
+
]);
|
|
113
140
|
|
|
114
141
|
const startPagingAnimation = useCallback((type: PagingAnimationType, config: PagingAnimationConfig) => {
|
|
142
|
+
if (isAnimatingRef.current) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
115
146
|
const configWithDefaults: PagingAnimationConfig = {
|
|
116
147
|
animated: true,
|
|
117
148
|
...config,
|
|
@@ -120,6 +151,7 @@ export default function usePagingAnimation(params: PagingAnimationParameters): U
|
|
|
120
151
|
const currentIndex = getCurrentIndex();
|
|
121
152
|
|
|
122
153
|
const getValueByDirectionOnAllAdjacentItemsVisible = directionToValue(itemWidth);
|
|
154
|
+
const compensateToValue = toValueCompensator(itemWidth);
|
|
123
155
|
|
|
124
156
|
const getValueByDirectionalPagingOnLoopDisabled = (_config: DirectionalPagingAnimationConfig): number => {
|
|
125
157
|
const { direction, isOriginatedFromGesture } = _config;
|
|
@@ -159,22 +191,29 @@ export default function usePagingAnimation(params: PagingAnimationParameters): U
|
|
|
159
191
|
return distance * direction;
|
|
160
192
|
};
|
|
161
193
|
|
|
162
|
-
const
|
|
194
|
+
const wantedToValue = type === 'directional'
|
|
163
195
|
// @ts-ignore
|
|
164
196
|
? getValueByDirectionalPaging(configWithDefaults)
|
|
165
197
|
// @ts-ignore
|
|
166
198
|
: getValueByIndexPaging(configWithDefaults);
|
|
167
199
|
|
|
200
|
+
const toValue = compensateToValue(wantedToValue, currentOffsetRef.current);
|
|
201
|
+
|
|
168
202
|
toValueRef.current = toValue;
|
|
203
|
+
isAnimatingRef.current = true;
|
|
204
|
+
|
|
205
|
+
notifyOffsetHasChanged(currentOffsetRef.current + toValue);
|
|
169
206
|
|
|
170
207
|
if (configWithDefaults.animated) {
|
|
171
|
-
const animation = createScrollAnimation(
|
|
172
|
-
|
|
208
|
+
const animation = createScrollAnimation(translateX, toValue);
|
|
209
|
+
|
|
173
210
|
animation.start(({ finished }) => {
|
|
174
211
|
if (finished) {
|
|
175
212
|
finalizeAnimation();
|
|
176
213
|
}
|
|
177
214
|
});
|
|
215
|
+
|
|
216
|
+
notifyAnimationState('started');
|
|
178
217
|
} else {
|
|
179
218
|
finalizeAnimation();
|
|
180
219
|
}
|
|
@@ -185,11 +224,12 @@ export default function usePagingAnimation(params: PagingAnimationParameters): U
|
|
|
185
224
|
itemWidth,
|
|
186
225
|
lastIndex,
|
|
187
226
|
loop,
|
|
227
|
+
notifyAnimationState,
|
|
228
|
+
notifyOffsetHasChanged,
|
|
188
229
|
]);
|
|
189
230
|
|
|
190
231
|
return {
|
|
191
|
-
|
|
192
|
-
finalizeAnimation,
|
|
232
|
+
interruptAnimation,
|
|
193
233
|
startPagingAnimation,
|
|
194
234
|
};
|
|
195
235
|
};
|
package/src/Carousel/types.ts
CHANGED
|
@@ -5,6 +5,10 @@ const directions = ['next', 'prev', 'stay'] as const;
|
|
|
5
5
|
|
|
6
6
|
export type PagingDirection = (typeof directions)[number];
|
|
7
7
|
|
|
8
|
+
const animationStates = ['started', 'finished', 'interrupted'] as const;
|
|
9
|
+
|
|
10
|
+
export type AnimationState = (typeof animationStates)[number];
|
|
11
|
+
|
|
8
12
|
export type ItemHeight = number | 'auto';
|
|
9
13
|
|
|
10
14
|
export interface RenderItem<T> {
|
|
@@ -24,10 +28,10 @@ export interface GetCurrentIndex {
|
|
|
24
28
|
}
|
|
25
29
|
|
|
26
30
|
export interface IndexController {
|
|
27
|
-
currentIndex: number;
|
|
28
31
|
getCurrentIndex: GetCurrentIndex;
|
|
29
32
|
lastIndex: number;
|
|
30
|
-
|
|
33
|
+
notifyAnimationState: (state: AnimationState) => void;
|
|
34
|
+
notifyOffsetHasChanged: (offset: number) => void;
|
|
31
35
|
}
|
|
32
36
|
|
|
33
37
|
export type PagingAnimationType = 'directional' | 'index';
|