@fountain-ui/lab 2.0.0-beta.12 → 2.0.0-beta.15

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 (154) hide show
  1. package/build/commonjs/Carousel/Carousel.js +39 -37
  2. package/build/commonjs/Carousel/Carousel.js.map +1 -1
  3. package/build/commonjs/Carousel/CarouselProps.js.map +1 -1
  4. package/build/commonjs/Carousel/animation/createDefaultScrollAnimation.js +2 -2
  5. package/build/commonjs/Carousel/animation/createDefaultScrollAnimation.js.map +1 -1
  6. package/build/commonjs/Carousel/animation/parallaxItemStyleFactory.js +15 -15
  7. package/build/commonjs/Carousel/animation/parallaxItemStyleFactory.js.map +1 -1
  8. package/build/commonjs/Carousel/components/InternalContext.js.map +1 -1
  9. package/build/commonjs/Carousel/components/ItemView.js +6 -4
  10. package/build/commonjs/Carousel/components/ItemView.js.map +1 -1
  11. package/build/commonjs/Carousel/components/RootView.js +21 -3
  12. package/build/commonjs/Carousel/components/RootView.js.map +1 -1
  13. package/build/commonjs/Carousel/components/ScrollViewGesture.js +18 -13
  14. package/build/commonjs/Carousel/components/ScrollViewGesture.js.map +1 -1
  15. package/build/commonjs/Carousel/{hooks → components}/useItemInterpolation.js +6 -4
  16. package/build/commonjs/Carousel/components/useItemInterpolation.js.map +1 -0
  17. package/build/commonjs/Carousel/hooks/index.js +0 -16
  18. package/build/commonjs/Carousel/hooks/index.js.map +1 -1
  19. package/build/commonjs/Carousel/hooks/useAutoplayController.js +4 -1
  20. package/build/commonjs/Carousel/hooks/useAutoplayController.js.map +1 -1
  21. package/build/commonjs/Carousel/hooks/useIndexController.js +15 -46
  22. package/build/commonjs/Carousel/hooks/useIndexController.js.map +1 -1
  23. package/build/commonjs/Carousel/hooks/useItemVisibilityStore.js +12 -12
  24. package/build/commonjs/Carousel/hooks/useItemVisibilityStore.js.map +1 -1
  25. package/build/commonjs/Carousel/hooks/usePagingAnimation.js +121 -69
  26. package/build/commonjs/Carousel/hooks/usePagingAnimation.js.map +1 -1
  27. package/build/commonjs/Carousel/tick.js +16 -0
  28. package/build/commonjs/Carousel/tick.js.map +1 -0
  29. package/build/commonjs/Carousel/types.js.map +1 -1
  30. package/build/commonjs/ViewPager/ChildrenMemoizedPage.js +53 -47
  31. package/build/commonjs/ViewPager/ChildrenMemoizedPage.js.map +1 -1
  32. package/build/commonjs/ViewPager/InternalContext.js +17 -0
  33. package/build/commonjs/ViewPager/InternalContext.js.map +1 -0
  34. package/build/commonjs/ViewPager/ViewPagerNative.js +74 -23
  35. package/build/commonjs/ViewPager/ViewPagerNative.js.map +1 -1
  36. package/build/commonjs/ViewPager/ViewPagerProps.js.map +1 -1
  37. package/build/commonjs/ViewPager/ViewPagerWeb.js +23 -12
  38. package/build/commonjs/ViewPager/ViewPagerWeb.js.map +1 -1
  39. package/build/commonjs/ViewPager/index.js.map +1 -1
  40. package/build/commonjs/ViewPager/types.js +6 -0
  41. package/build/commonjs/ViewPager/types.js.map +1 -0
  42. package/build/commonjs/ViewPager/usePageStore.js +35 -0
  43. package/build/commonjs/ViewPager/usePageStore.js.map +1 -0
  44. package/build/commonjs/ViewPager/utils.js.map +1 -1
  45. package/build/commonjs/ViewabilityTrackerView/measureViewability.js +6 -6
  46. package/build/commonjs/ViewabilityTrackerView/measureViewability.js.map +1 -1
  47. package/build/commonjs/hooks/useUnstableCollapsibleAppBar.js +1 -1
  48. package/build/commonjs/hooks/useUnstableCollapsibleAppBar.js.map +1 -1
  49. package/build/module/Carousel/Carousel.js +39 -39
  50. package/build/module/Carousel/Carousel.js.map +1 -1
  51. package/build/module/Carousel/CarouselProps.js.map +1 -1
  52. package/build/module/Carousel/animation/createDefaultScrollAnimation.js +2 -2
  53. package/build/module/Carousel/animation/createDefaultScrollAnimation.js.map +1 -1
  54. package/build/module/Carousel/animation/parallaxItemStyleFactory.js +15 -15
  55. package/build/module/Carousel/animation/parallaxItemStyleFactory.js.map +1 -1
  56. package/build/module/Carousel/components/InternalContext.js.map +1 -1
  57. package/build/module/Carousel/components/ItemView.js +5 -3
  58. package/build/module/Carousel/components/ItemView.js.map +1 -1
  59. package/build/module/Carousel/components/RootView.js +22 -4
  60. package/build/module/Carousel/components/RootView.js.map +1 -1
  61. package/build/module/Carousel/components/ScrollViewGesture.js +18 -13
  62. package/build/module/Carousel/components/ScrollViewGesture.js.map +1 -1
  63. package/build/module/Carousel/{hooks → components}/useItemInterpolation.js +3 -3
  64. package/build/module/Carousel/components/useItemInterpolation.js.map +1 -0
  65. package/build/module/Carousel/hooks/index.js +0 -2
  66. package/build/module/Carousel/hooks/index.js.map +1 -1
  67. package/build/module/Carousel/hooks/useAutoplayController.js +4 -1
  68. package/build/module/Carousel/hooks/useAutoplayController.js.map +1 -1
  69. package/build/module/Carousel/hooks/useIndexController.js +14 -39
  70. package/build/module/Carousel/hooks/useIndexController.js.map +1 -1
  71. package/build/module/Carousel/hooks/useItemVisibilityStore.js +10 -11
  72. package/build/module/Carousel/hooks/useItemVisibilityStore.js.map +1 -1
  73. package/build/module/Carousel/hooks/usePagingAnimation.js +122 -69
  74. package/build/module/Carousel/hooks/usePagingAnimation.js.map +1 -1
  75. package/build/module/Carousel/tick.js +6 -0
  76. package/build/module/Carousel/tick.js.map +1 -0
  77. package/build/module/Carousel/types.js.map +1 -1
  78. package/build/module/ViewPager/ChildrenMemoizedPage.js +53 -47
  79. package/build/module/ViewPager/ChildrenMemoizedPage.js.map +1 -1
  80. package/build/module/ViewPager/InternalContext.js +7 -0
  81. package/build/module/ViewPager/InternalContext.js.map +1 -0
  82. package/build/module/ViewPager/ViewPagerNative.js +72 -23
  83. package/build/module/ViewPager/ViewPagerNative.js.map +1 -1
  84. package/build/module/ViewPager/ViewPagerProps.js.map +1 -1
  85. package/build/module/ViewPager/ViewPagerWeb.js +21 -13
  86. package/build/module/ViewPager/ViewPagerWeb.js.map +1 -1
  87. package/build/module/ViewPager/index.js.map +1 -1
  88. package/build/module/ViewPager/types.js +2 -0
  89. package/build/module/ViewPager/types.js.map +1 -0
  90. package/build/module/ViewPager/usePageStore.js +25 -0
  91. package/build/module/ViewPager/usePageStore.js.map +1 -0
  92. package/build/module/ViewPager/utils.js.map +1 -1
  93. package/build/module/ViewabilityTrackerView/measureViewability.js +2 -2
  94. package/build/module/ViewabilityTrackerView/measureViewability.js.map +1 -1
  95. package/build/module/hooks/useUnstableCollapsibleAppBar.js +1 -1
  96. package/build/module/hooks/useUnstableCollapsibleAppBar.js.map +1 -1
  97. package/build/typescript/Carousel/CarouselProps.d.ts +4 -3
  98. package/build/typescript/Carousel/animation/parallaxItemStyleFactory.d.ts +5 -5
  99. package/build/typescript/Carousel/components/InternalContext.d.ts +2 -2
  100. package/build/typescript/Carousel/components/ItemView.d.ts +2 -0
  101. package/build/typescript/Carousel/components/RootView.d.ts +4 -4
  102. package/build/typescript/Carousel/components/ScrollViewGesture.d.ts +3 -3
  103. package/build/typescript/Carousel/{hooks → components}/useItemInterpolation.d.ts +0 -0
  104. package/build/typescript/Carousel/hooks/index.d.ts +0 -2
  105. package/build/typescript/Carousel/hooks/useIndexController.d.ts +0 -2
  106. package/build/typescript/Carousel/hooks/useItemVisibilityStore.d.ts +5 -2
  107. package/build/typescript/Carousel/hooks/usePagingAnimation.d.ts +8 -10
  108. package/build/typescript/Carousel/tick.d.ts +2 -0
  109. package/build/typescript/Carousel/types.d.ts +26 -5
  110. package/build/typescript/ViewPager/ChildrenMemoizedPage.d.ts +1 -1
  111. package/build/typescript/ViewPager/InternalContext.d.ts +7 -0
  112. package/build/typescript/ViewPager/ViewPagerNative.d.ts +2 -2
  113. package/build/typescript/ViewPager/ViewPagerProps.d.ts +4 -22
  114. package/build/typescript/ViewPager/ViewPagerWeb.d.ts +2 -2
  115. package/build/typescript/ViewPager/index.d.ts +2 -1
  116. package/build/typescript/ViewPager/types.d.ts +19 -0
  117. package/build/typescript/ViewPager/usePageStore.d.ts +2 -0
  118. package/build/typescript/ViewPager/utils.d.ts +1 -1
  119. package/package.json +3 -3
  120. package/src/Carousel/Carousel.tsx +32 -40
  121. package/src/Carousel/CarouselProps.ts +4 -3
  122. package/src/Carousel/animation/createDefaultScrollAnimation.ts +2 -2
  123. package/src/Carousel/animation/parallaxItemStyleFactory.ts +24 -24
  124. package/src/Carousel/components/InternalContext.ts +2 -2
  125. package/src/Carousel/components/ItemView.tsx +13 -3
  126. package/src/Carousel/components/RootView.tsx +19 -6
  127. package/src/Carousel/components/ScrollViewGesture.tsx +23 -15
  128. package/src/Carousel/{hooks → components}/useItemInterpolation.ts +3 -3
  129. package/src/Carousel/hooks/index.ts +0 -2
  130. package/src/Carousel/hooks/useAutoplayController.ts +4 -1
  131. package/src/Carousel/hooks/useIndexController.tsx +14 -44
  132. package/src/Carousel/hooks/useItemVisibilityStore.ts +17 -13
  133. package/src/Carousel/hooks/usePagingAnimation.ts +161 -83
  134. package/src/Carousel/tick.ts +6 -0
  135. package/src/Carousel/types.ts +34 -5
  136. package/src/ViewPager/ChildrenMemoizedPage.tsx +53 -50
  137. package/src/ViewPager/InternalContext.ts +13 -0
  138. package/src/ViewPager/ViewPagerNative.tsx +91 -44
  139. package/src/ViewPager/ViewPagerProps.ts +4 -27
  140. package/src/ViewPager/ViewPagerWeb.tsx +28 -23
  141. package/src/ViewPager/index.ts +2 -1
  142. package/src/ViewPager/types.ts +24 -0
  143. package/src/ViewPager/usePageStore.ts +30 -0
  144. package/src/ViewPager/utils.tsx +1 -1
  145. package/src/ViewabilityTrackerView/measureViewability.ts +1 -3
  146. package/src/hooks/useUnstableCollapsibleAppBar.ts +1 -1
  147. package/build/commonjs/Carousel/hooks/useDimensionChangeReaction.js +0 -23
  148. package/build/commonjs/Carousel/hooks/useDimensionChangeReaction.js.map +0 -1
  149. package/build/commonjs/Carousel/hooks/useItemInterpolation.js.map +0 -1
  150. package/build/module/Carousel/hooks/useDimensionChangeReaction.js +0 -14
  151. package/build/module/Carousel/hooks/useDimensionChangeReaction.js.map +0 -1
  152. package/build/module/Carousel/hooks/useItemInterpolation.js.map +0 -1
  153. package/build/typescript/Carousel/hooks/useDimensionChangeReaction.d.ts +0 -7
  154. package/src/Carousel/hooks/useDimensionChangeReaction.ts +0 -25
@@ -1,22 +1,29 @@
1
- import { useCallback, useEffect, useMemo, useRef } from 'react';
2
- import { Animated, Platform } from 'react-native';
3
- import type { CreateScrollAnimation, GetCurrentIndex, PagingDirection, StartPagingAnimation } from '../types';
4
-
5
- export interface PagingAnimationConfig {
6
- controlledTx: Animated.Value;
1
+ import { useCallback, useRef } from 'react';
2
+ import { Animated } from 'react-native';
3
+ import type {
4
+ CreateScrollAnimation,
5
+ DirectionalPagingAnimationConfig,
6
+ IndexController,
7
+ IndexPagingAnimationConfig,
8
+ PagingAnimationConfig,
9
+ PagingAnimationType,
10
+ PagingDirection,
11
+ StartPagingAnimation,
12
+ } from '../types';
13
+
14
+ export interface PagingAnimationParameters {
7
15
  createScrollAnimation: CreateScrollAnimation;
8
- getCurrentIndex: GetCurrentIndex;
9
16
  itemWidth: number;
10
- lastIndex: number;
17
+ indexController: IndexController;
11
18
  loop: boolean;
12
19
  numberOfData: number;
13
- offsetTx: Animated.Value;
20
+ offsetX: Animated.Value;
21
+ translateX: Animated.Value;
14
22
  }
15
23
 
16
24
  export interface UsePagingAnimation {
17
- finalizeAnimation: () => void;
18
- globalInterpolation: Animated.AnimatedInterpolation;
19
- startAnimation: StartPagingAnimation;
25
+ interruptAnimation: () => void;
26
+ startPagingAnimation: StartPagingAnimation;
20
27
  }
21
28
 
22
29
  function directionToValue(itemWidth: number) {
@@ -32,105 +39,176 @@ function directionToValue(itemWidth: number) {
32
39
  };
33
40
  }
34
41
 
35
- export default function usePagingAnimation(config: PagingAnimationConfig): UsePagingAnimation {
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
+
57
+ export default function usePagingAnimation(params: PagingAnimationParameters): UsePagingAnimation {
36
58
  const {
37
- controlledTx,
38
59
  createScrollAnimation,
39
- getCurrentIndex,
40
60
  itemWidth,
41
- lastIndex,
61
+ indexController,
42
62
  loop,
43
63
  numberOfData,
44
- offsetTx,
45
- } = config;
64
+ offsetX,
65
+ translateX,
66
+ } = params;
46
67
 
47
- const animationRef = useRef<Animated.CompositeAnimation | null>(null);
48
- const toValueRef = useRef<number>(0);
68
+ const {
69
+ getCurrentIndex,
70
+ lastIndex,
71
+ notifyOffsetHasChanged,
72
+ } = indexController;
49
73
 
50
- const globalInterpolation = useMemo(
51
- () => Animated.add(controlledTx, offsetTx),
52
- [controlledTx, offsetTx],
53
- );
74
+ const toValueRef = useRef<number>(0);
75
+ const currentOffsetRef = useRef<number>(0);
54
76
 
55
- useEffect(() => {
56
- const subscriptionId = controlledTx.addListener((value) => {
57
- const currentTx = value.value;
77
+ const isAnimatingRef = useRef<boolean>(false);
58
78
 
59
- // Prevent infinite loop
60
- if (currentTx !== 0) {
61
- const maxWidth = numberOfData * itemWidth;
79
+ const maxWidth = Math.abs(numberOfData * itemWidth);
62
80
 
63
- if (Math.abs(Math.round(currentTx)) === Math.round(maxWidth)) {
64
- // reset position
65
- controlledTx.setValue(0);
66
- }
81
+ const ensureOffsetBoundary: (offset: number) => number = useCallback((offset: number) => {
82
+ if (loop) {
83
+ const isCloseToEnd = Math.abs(offset) >= (maxWidth - itemWidth);
84
+ if (isCloseToEnd) {
85
+ const signOfOffset = offset > 0 ? 1 : -1;
86
+ return offset + (-signOfOffset * maxWidth);
67
87
  }
68
- });
88
+ }
69
89
 
70
- return () => {
71
- controlledTx.removeListener(subscriptionId);
72
- };
73
- }, [numberOfData, itemWidth]);
90
+ return offset % maxWidth;
91
+ }, [itemWidth, loop, maxWidth]);
74
92
 
75
- const finalizeAnimation = useCallback(() => {
76
- const stopUnfinishedSnapAnimation = () => {
77
- if (animationRef.current) {
78
- animationRef.current?.stop();
79
- animationRef.current = null;
80
- }
81
- };
93
+ const requireNewOffset = useCallback((newOffset: number) => {
94
+ const nextOffset = ensureOffsetBoundary(newOffset);
82
95
 
83
- const resetBoundary = () => {
84
- controlledTx.setOffset(toValueRef.current);
85
- controlledTx.flattenOffset();
86
-
87
- // FIXME: react-native-web bug maybe?
88
- // `AnimatedValue.flattenOffset()` does not trigger any event listener.
89
- // Accessing value directly via `_value` is dangerous but working on web (`useNativeDriver` always false).
90
- // So setting same value with `value.setValue(value._value)` will trigger event listener.
91
- if (Platform.OS === 'web') {
92
- // @ts-ignore
93
- controlledTx.setValue(controlledTx._value);
94
- }
96
+ currentOffsetRef.current = nextOffset;
97
+ offsetX.setValue(nextOffset);
95
98
 
96
- offsetTx.setValue(0);
97
- toValueRef.current = 0;
98
- };
99
+ toValueRef.current = 0;
100
+ translateX.setValue(0);
101
+ }, [
102
+ ensureOffsetBoundary,
103
+ offsetX,
104
+ translateX,
105
+ ]);
106
+
107
+ const interruptAnimation = useCallback(() => {
108
+ if (toValueRef.current === 0) {
109
+ // Performance optimization
110
+ return;
111
+ }
112
+
113
+ translateX.stopAnimation(lastValue => {
114
+ isAnimatingRef.current = false;
99
115
 
100
- stopUnfinishedSnapAnimation();
116
+ const prevOffset = currentOffsetRef.current;
117
+ const totalOffset = prevOffset + lastValue;
101
118
 
102
- resetBoundary();
103
- }, [controlledTx]);
119
+ notifyOffsetHasChanged(totalOffset);
104
120
 
105
- const startAnimation = useCallback((direction: PagingDirection, isGesture: boolean = false) => {
106
- const getToValueByDirection = directionToValue(itemWidth);
121
+ requireNewOffset(totalOffset);
122
+ });
123
+ }, [requireNewOffset, translateX]);
124
+
125
+ const finalizeAnimation = useCallback(() => {
126
+ isAnimatingRef.current = false;
127
+
128
+ const prevOffset = currentOffsetRef.current;
129
+ const toValue = toValueRef.current;
130
+ const totalOffset = prevOffset + toValue;
131
+
132
+ requireNewOffset(totalOffset);
133
+ }, [requireNewOffset]);
134
+
135
+ const startPagingAnimation = useCallback((type: PagingAnimationType, config: PagingAnimationConfig) => {
136
+ if (isAnimatingRef.current) {
137
+ return;
138
+ }
139
+
140
+ const configWithDefaults: PagingAnimationConfig = {
141
+ animated: true,
142
+ ...config,
143
+ };
107
144
 
108
145
  const currentIndex = getCurrentIndex();
109
146
 
110
- const computeToValueOnNoLoop = (): number => {
147
+ const getValueByDirectionOnAllAdjacentItemsVisible = directionToValue(itemWidth);
148
+ const compensateToValue = toValueCompensator(itemWidth);
149
+
150
+ const getValueByDirectionalPagingOnLoopDisabled = (_config: DirectionalPagingAnimationConfig): number => {
151
+ const { direction, isOriginatedFromGesture } = _config;
152
+
111
153
  if (currentIndex === 0 && direction === 'prev') {
112
- return isGesture
113
- ? getToValueByDirection('stay')
154
+ return isOriginatedFromGesture
155
+ ? getValueByDirectionOnAllAdjacentItemsVisible('stay')
114
156
  : -lastIndex * itemWidth; // last position
115
157
  } else if (currentIndex === lastIndex && direction === 'next') {
116
- return isGesture
117
- ? getToValueByDirection('stay')
158
+ return isOriginatedFromGesture
159
+ ? getValueByDirectionOnAllAdjacentItemsVisible('stay')
118
160
  : lastIndex * itemWidth; // first position
119
161
  }
120
- return getToValueByDirection(direction);
162
+ return getValueByDirectionOnAllAdjacentItemsVisible(direction);
121
163
  };
122
164
 
123
- const toValue = loop ? getToValueByDirection(direction) : computeToValueOnNoLoop();
124
- const animation = createScrollAnimation(offsetTx, toValue);
165
+ const getValueByDirectionalPaging = (_config: DirectionalPagingAnimationConfig): number => {
166
+ const _configWithDefaults: DirectionalPagingAnimationConfig = {
167
+ isOriginatedFromGesture: false,
168
+ ..._config,
169
+ };
125
170
 
126
- animationRef.current = animation;
127
- toValueRef.current = toValue;
171
+ return loop
172
+ ? getValueByDirectionOnAllAdjacentItemsVisible(_configWithDefaults.direction)
173
+ : getValueByDirectionalPagingOnLoopDisabled(_configWithDefaults);
174
+ };
128
175
 
129
- animation.start(({ finished }) => {
130
- if (finished) {
131
- finalizeAnimation();
176
+ const getValueByIndexPaging = ({ index }: IndexPagingAnimationConfig): number => {
177
+ if (index < 0 || index > lastIndex || index === currentIndex) {
178
+ // no animation if index is invalid or equals to current index
179
+ return 0;
132
180
  }
133
- });
181
+
182
+ const distance = Math.abs(currentIndex - index) * itemWidth;
183
+ const direction = index > currentIndex ? -1 : 1;
184
+
185
+ return distance * direction;
186
+ };
187
+
188
+ const wantedToValue = type === 'directional'
189
+ // @ts-ignore
190
+ ? getValueByDirectionalPaging(configWithDefaults)
191
+ // @ts-ignore
192
+ : getValueByIndexPaging(configWithDefaults);
193
+
194
+ const toValue = compensateToValue(wantedToValue, currentOffsetRef.current);
195
+
196
+ toValueRef.current = toValue;
197
+ isAnimatingRef.current = true;
198
+
199
+ if (configWithDefaults.animated) {
200
+ const animation = createScrollAnimation(translateX, toValue);
201
+
202
+ animation.start(({ finished }) => {
203
+ if (finished) {
204
+ finalizeAnimation();
205
+ }
206
+ });
207
+ } else {
208
+ finalizeAnimation();
209
+ }
210
+
211
+ notifyOffsetHasChanged(currentOffsetRef.current + toValue);
134
212
  }, [
135
213
  createScrollAnimation,
136
214
  getCurrentIndex,
@@ -138,11 +216,11 @@ export default function usePagingAnimation(config: PagingAnimationConfig): UsePa
138
216
  itemWidth,
139
217
  lastIndex,
140
218
  loop,
219
+ notifyOffsetHasChanged,
141
220
  ]);
142
221
 
143
222
  return {
144
- globalInterpolation,
145
- finalizeAnimation,
146
- startAnimation,
223
+ interruptAnimation,
224
+ startPagingAnimation,
147
225
  };
148
226
  };
@@ -0,0 +1,6 @@
1
+ import { logger } from '@fountain-ui/utils';
2
+
3
+ export default logger('Carousel', {
4
+ enabled: __DEV__,
5
+ format: 'diff',
6
+ });
@@ -5,6 +5,8 @@ const directions = ['next', 'prev', 'stay'] as const;
5
5
 
6
6
  export type PagingDirection = (typeof directions)[number];
7
7
 
8
+ export type ItemHeight = number | 'auto';
9
+
8
10
  export interface RenderItem<T> {
9
11
  (info: { item: T, index: number, interpolation: Animated.AnimatedInterpolation }): ReactElement | null;
10
12
  }
@@ -22,14 +24,30 @@ export interface GetCurrentIndex {
22
24
  }
23
25
 
24
26
  export interface IndexController {
25
- currentIndex: number;
26
27
  getCurrentIndex: GetCurrentIndex;
27
28
  lastIndex: number;
28
- monitorElement: ReactElement;
29
+ notifyOffsetHasChanged: (offset: number) => void;
29
30
  }
30
31
 
32
+ export type PagingAnimationType = 'directional' | 'index';
33
+
34
+ export interface BasePagingAnimationConfig {
35
+ animated?: boolean;
36
+ }
37
+
38
+ export interface DirectionalPagingAnimationConfig extends BasePagingAnimationConfig {
39
+ direction: PagingDirection;
40
+ isOriginatedFromGesture?: boolean;
41
+ }
42
+
43
+ export interface IndexPagingAnimationConfig extends BasePagingAnimationConfig {
44
+ index: number;
45
+ }
46
+
47
+ export type PagingAnimationConfig = DirectionalPagingAnimationConfig | IndexPagingAnimationConfig;
48
+
31
49
  export interface StartPagingAnimation {
32
- (direction: PagingDirection, isGesture?: boolean): void;
50
+ (type: PagingAnimationType, config: PagingAnimationConfig): void;
33
51
  }
34
52
 
35
53
  export type VisibleIndexRanges = Array<[number, number]>;
@@ -49,7 +67,17 @@ export interface AutoplayController {
49
67
  resume: () => void;
50
68
  }
51
69
 
70
+ export interface ScrollToOption {
71
+ index: number;
72
+ animated?: boolean;
73
+ }
74
+
52
75
  export interface CarouselInstance {
76
+ /**
77
+ * Get current visible item index.
78
+ */
79
+ getCurrentIndex: GetCurrentIndex;
80
+
53
81
  /**
54
82
  * Scroll to next visible item.
55
83
  */
@@ -61,7 +89,8 @@ export interface CarouselInstance {
61
89
  prev: () => void;
62
90
 
63
91
  /**
64
- * Get current visible item index.
92
+ * Scroll to desired indexed item.
93
+ * Invalid index is ignored.
65
94
  */
66
- getCurrentIndex: GetCurrentIndex;
95
+ scrollTo: (option: ScrollToOption) => void;
67
96
  }
@@ -1,92 +1,95 @@
1
- import React, { memo, useMemo, useState } from 'react';
1
+ import React, { memo, useCallback, useContext, useEffect, useMemo, useReducer, useRef, useState } from 'react';
2
2
  import { Platform, View } from 'react-native';
3
- import { runOnJS, useAnimatedReaction } from 'react-native-reanimated';
4
3
  import { StyleSheet } from '@fountain-ui/core';
5
- import type { PageProps } from './ViewPagerProps';
4
+ import type { PageProps } from './types';
6
5
  import PageStateContext from './PageStateContext';
6
+ import InternalContext from './InternalContext';
7
7
 
8
8
  const styles = StyleSheet.create({
9
9
  fill: { width: '100%', height: '100%' },
10
10
  none: { display: 'none' },
11
11
  });
12
12
 
13
- interface InternalPageState {
13
+ interface InternalPageDescription {
14
14
  isActive: boolean;
15
- isLoaded: boolean;
15
+ becomeNeighbor: boolean;
16
+ }
17
+
18
+ interface InternalPageState {
19
+ active: boolean;
20
+ loaded: boolean;
16
21
  }
17
22
 
18
23
  function Page(props: PageProps) {
19
24
  const {
20
25
  index,
26
+ initialPage,
21
27
  children,
22
28
  loading,
23
29
  offscreenPageRerenderLimit,
24
- sharedIndex,
25
30
  } = props;
26
31
 
27
- const assumeInitialPageState = (): InternalPageState => {
28
- const activeIndex = sharedIndex.value;
32
+ const { pageStore } = useContext(InternalContext);
29
33
 
30
- const isActive = index === activeIndex;
34
+ const computePageDescription: (page: number) => InternalPageDescription = useCallback((page: number) => {
35
+ const activeIndex = page;
31
36
 
32
- if (loading === 'eager') {
33
- return { isActive, isLoaded: true };
34
- }
37
+ const isActive = index === activeIndex;
35
38
 
36
- const isLoaded = index >= activeIndex - offscreenPageRerenderLimit
39
+ const shouldRerender = index >= activeIndex - offscreenPageRerenderLimit
37
40
  && index <= activeIndex + offscreenPageRerenderLimit;
38
41
 
39
- return { isActive, isLoaded };
40
- };
42
+ const becomeNeighbor = shouldRerender && !isActive;
43
+
44
+ return { isActive, becomeNeighbor };
45
+ }, [index]);
41
46
 
42
- const [pageState, setPageState] = useState<InternalPageState>(assumeInitialPageState);
47
+ const [initialState] = useState<InternalPageState>(() => {
48
+ const { isActive, becomeNeighbor } = computePageDescription(initialPage);
43
49
 
44
- const content = pageState.isLoaded ? children : null;
50
+ if (loading === 'eager') {
51
+ return { active: isActive, loaded: true };
52
+ }
45
53
 
46
- const onActiveStateChange = (isActive: boolean) => {
47
- setPageState(prevState => ({
48
- ...prevState,
49
- isActive,
50
- isLoaded: isActive || prevState.isLoaded,
51
- }));
52
- };
54
+ return { active: isActive, loaded: isActive || becomeNeighbor };
55
+ });
53
56
 
54
- const onBecomeNeighbor = () => {
55
- setPageState(prevState => ({
56
- ...prevState,
57
- isLoaded: true,
58
- }));
59
- };
57
+ // `Bailing out of a state update` is not working as expected.
58
+ const pageStateRef = useRef<InternalPageState>(initialState);
59
+ const [, forceRender] = useReducer((s) => s + 1, 0);
60
60
 
61
- useAnimatedReaction(
62
- () => {
63
- const activeIndex = sharedIndex.value;
61
+ const { active, loaded } = pageStateRef.current;
64
62
 
65
- const isActive = index === activeIndex;
63
+ const content = loaded ? children : null;
66
64
 
67
- const shouldRerender = index >= activeIndex - offscreenPageRerenderLimit
68
- && index <= activeIndex + offscreenPageRerenderLimit;
65
+ useEffect(() => {
66
+ return pageStore.subscribe(newPage => {
67
+ const { isActive, becomeNeighbor } = computePageDescription(newPage);
69
68
 
70
- const becomeNeighbor = shouldRerender && !isActive;
69
+ const currentState = pageStateRef.current;
71
70
 
72
- return { isActive, becomeNeighbor };
73
- },
74
- (nextState, prevState) => {
75
- if (nextState.isActive !== prevState?.isActive) {
76
- runOnJS(onActiveStateChange)(nextState.isActive);
77
- } else if (nextState.becomeNeighbor) {
78
- runOnJS(onBecomeNeighbor)();
71
+ const newState: InternalPageState = {
72
+ active: isActive,
73
+ loaded: isActive || becomeNeighbor ? true : currentState.loaded,
74
+ };
75
+
76
+ if (
77
+ currentState.active !== newState.active
78
+ || currentState.loaded !== newState.loaded
79
+ ) {
80
+ pageStateRef.current = newState;
81
+
82
+ forceRender();
79
83
  }
80
- },
81
- [index],
82
- );
84
+ });
85
+ }, [pageStore, computePageDescription]);
83
86
 
84
87
  const contextValue = useMemo(() => ({
85
- isActive: pageState.isActive,
86
- }), [pageState.isActive]);
88
+ isActive: active,
89
+ }), [active]);
87
90
 
88
91
  const style = Platform.OS === 'web'
89
- ? (pageState.isActive ? StyleSheet.absoluteFill : styles.none)
92
+ ? (active ? StyleSheet.absoluteFill : styles.none)
90
93
  : styles.fill;
91
94
 
92
95
  return (
@@ -0,0 +1,13 @@
1
+ import { createContext } from 'react';
2
+ import type { MonoStore } from '@fountain-ui/core';
3
+ import { MockStore } from '@fountain-ui/core';
4
+
5
+ export interface InternalContextValue {
6
+ pageStore: MonoStore<number>;
7
+ }
8
+
9
+ const InternalContext = createContext<InternalContextValue>({
10
+ pageStore: new MockStore(),
11
+ });
12
+
13
+ export default InternalContext;