react-native-tab-view 5.0.0-alpha.0 → 5.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/lib/module/DefaultAdapter.android.js +4 -0
- package/lib/module/DefaultAdapter.android.js.map +1 -0
- package/lib/module/DefaultAdapter.ios.js +4 -0
- package/lib/module/DefaultAdapter.ios.js.map +1 -0
- package/lib/module/DefaultAdapter.js +5 -0
- package/lib/module/DefaultAdapter.js.map +1 -0
- package/lib/module/PagerViewAdapter.native.js +29 -13
- package/lib/module/PagerViewAdapter.native.js.map +1 -1
- package/lib/module/PanResponderAdapter.js +3 -0
- package/lib/module/PanResponderAdapter.js.map +1 -1
- package/lib/module/PlatformPressable.js +1 -1
- package/lib/module/PlatformPressable.js.map +1 -1
- package/lib/module/SceneMap.js +12 -6
- package/lib/module/SceneMap.js.map +1 -1
- package/lib/module/SceneView.js +25 -1
- package/lib/module/SceneView.js.map +1 -1
- package/lib/module/ScrollViewAdapter.js +49 -18
- package/lib/module/ScrollViewAdapter.js.map +1 -1
- package/lib/module/TabBar.js +279 -169
- package/lib/module/TabBar.js.map +1 -1
- package/lib/module/TabBarIndicator.js +314 -90
- package/lib/module/TabBarIndicator.js.map +1 -1
- package/lib/module/TabBarItem.js +94 -44
- package/lib/module/TabBarItem.js.map +1 -1
- package/lib/module/TabBarItemLabel.js +3 -2
- package/lib/module/TabBarItemLabel.js.map +1 -1
- package/lib/module/TabView.js +2 -2
- package/lib/module/TabView.js.map +1 -1
- package/lib/module/constants.js +10 -0
- package/lib/module/constants.js.map +1 -0
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/useLayoutWidths.js +46 -0
- package/lib/module/useLayoutWidths.js.map +1 -0
- package/lib/typescript/src/DefaultAdapter.android.d.ts +2 -0
- package/lib/typescript/src/DefaultAdapter.android.d.ts.map +1 -0
- package/lib/typescript/src/DefaultAdapter.d.ts +6 -0
- package/lib/typescript/src/DefaultAdapter.d.ts.map +1 -0
- package/lib/typescript/src/DefaultAdapter.ios.d.ts +2 -0
- package/lib/typescript/src/DefaultAdapter.ios.d.ts.map +1 -0
- package/lib/typescript/src/PagerViewAdapter.d.ts +1 -1
- package/lib/typescript/src/PagerViewAdapter.d.ts.map +1 -1
- package/lib/typescript/src/PagerViewAdapter.native.d.ts +2 -2
- package/lib/typescript/src/PagerViewAdapter.native.d.ts.map +1 -1
- package/lib/typescript/src/PanResponderAdapter.d.ts +1 -1
- package/lib/typescript/src/PanResponderAdapter.d.ts.map +1 -1
- package/lib/typescript/src/PlatformPressable.d.ts +5 -5
- package/lib/typescript/src/PlatformPressable.d.ts.map +1 -1
- package/lib/typescript/src/SceneMap.d.ts +2 -2
- package/lib/typescript/src/SceneMap.d.ts.map +1 -1
- package/lib/typescript/src/SceneView.d.ts +2 -2
- package/lib/typescript/src/SceneView.d.ts.map +1 -1
- package/lib/typescript/src/ScrollViewAdapter.d.ts +2 -3
- package/lib/typescript/src/ScrollViewAdapter.d.ts.map +1 -1
- package/lib/typescript/src/TabBar.d.ts +26 -25
- package/lib/typescript/src/TabBar.d.ts.map +1 -1
- package/lib/typescript/src/TabBarIndicator.d.ts +5 -7
- package/lib/typescript/src/TabBarIndicator.d.ts.map +1 -1
- package/lib/typescript/src/TabBarItem.d.ts +17 -10
- package/lib/typescript/src/TabBarItem.d.ts.map +1 -1
- package/lib/typescript/src/TabBarItemLabel.d.ts +4 -3
- package/lib/typescript/src/TabBarItemLabel.d.ts.map +1 -1
- package/lib/typescript/src/TabView.d.ts +111 -17
- package/lib/typescript/src/TabView.d.ts.map +1 -1
- package/lib/typescript/src/constants.d.ts +8 -0
- package/lib/typescript/src/constants.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +13 -12
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +31 -31
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/lib/typescript/src/useLayoutWidths.d.ts +2 -0
- package/lib/typescript/src/useLayoutWidths.d.ts.map +1 -0
- package/lib/typescript/src/useMeasureLayout.d.ts +1 -1
- package/lib/typescript/src/useMeasureLayout.d.ts.map +1 -1
- package/package.json +13 -12
- package/src/DefaultAdapter.android.tsx +1 -0
- package/src/DefaultAdapter.ios.tsx +1 -0
- package/src/DefaultAdapter.tsx +13 -0
- package/src/PagerViewAdapter.native.tsx +36 -18
- package/src/PanResponderAdapter.tsx +4 -0
- package/src/PlatformPressable.tsx +11 -7
- package/src/SceneMap.tsx +17 -9
- package/src/SceneView.tsx +41 -2
- package/src/ScrollViewAdapter.tsx +88 -22
- package/src/TabBar.tsx +440 -235
- package/src/TabBarIndicator.tsx +389 -124
- package/src/TabBarItem.tsx +107 -46
- package/src/TabBarItemLabel.tsx +5 -4
- package/src/TabView.tsx +118 -20
- package/src/constants.tsx +8 -0
- package/src/index.tsx +5 -1
- package/src/types.tsx +40 -36
- package/src/useLayoutWidths.tsx +51 -0
- package/lib/module/Pager.android.js +0 -4
- package/lib/module/Pager.android.js.map +0 -1
- package/lib/module/Pager.ios.js +0 -4
- package/lib/module/Pager.ios.js.map +0 -1
- package/lib/module/Pager.js +0 -4
- package/lib/module/Pager.js.map +0 -1
- package/lib/typescript/src/Pager.android.d.ts +0 -2
- package/lib/typescript/src/Pager.android.d.ts.map +0 -1
- package/lib/typescript/src/Pager.d.ts +0 -2
- package/lib/typescript/src/Pager.d.ts.map +0 -1
- package/lib/typescript/src/Pager.ios.d.ts +0 -2
- package/lib/typescript/src/Pager.ios.d.ts.map +0 -1
- package/src/Pager.android.tsx +0 -1
- package/src/Pager.ios.tsx +0 -1
- package/src/Pager.tsx +0 -1
|
@@ -36,9 +36,19 @@ export function PagerViewAdapter({
|
|
|
36
36
|
children,
|
|
37
37
|
style,
|
|
38
38
|
animationEnabled,
|
|
39
|
+
layoutDirection = 'ltr',
|
|
39
40
|
...rest
|
|
40
41
|
}: PagerViewAdapterProps) {
|
|
41
|
-
const { index } = navigationState;
|
|
42
|
+
const { index, routes } = navigationState;
|
|
43
|
+
|
|
44
|
+
const isRTL = layoutDirection === 'rtl';
|
|
45
|
+
|
|
46
|
+
// iOS reports RTL pager offsets from the opposite end
|
|
47
|
+
// So we need to invert them to get the logical route index
|
|
48
|
+
const shouldInvertOffset = Platform.OS === 'ios' && isRTL;
|
|
49
|
+
const initialPosition = shouldInvertOffset
|
|
50
|
+
? routes.length - 1 - index
|
|
51
|
+
: index;
|
|
42
52
|
|
|
43
53
|
const listeners = React.useRef<Set<Listener>>(new Set()).current;
|
|
44
54
|
|
|
@@ -46,25 +56,33 @@ export function PagerViewAdapter({
|
|
|
46
56
|
const indexRef = React.useRef<number>(index);
|
|
47
57
|
const navigationStateRef = React.useRef(navigationState);
|
|
48
58
|
|
|
49
|
-
const position = useAnimatedValue(
|
|
59
|
+
const position = useAnimatedValue(initialPosition);
|
|
50
60
|
const offset = useAnimatedValue(0);
|
|
51
61
|
|
|
52
62
|
React.useEffect(() => {
|
|
53
63
|
navigationStateRef.current = navigationState;
|
|
54
64
|
});
|
|
55
65
|
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
66
|
+
const setReportedOffset = useLatestCallback((index: number) => {
|
|
67
|
+
position.setValue(shouldInvertOffset ? routes.length - 1 - index : index);
|
|
68
|
+
offset.setValue(0);
|
|
69
|
+
});
|
|
60
70
|
|
|
71
|
+
const setPage = useLatestCallback((index: number) => {
|
|
61
72
|
if (animationEnabled) {
|
|
62
73
|
pagerRef.current?.setPage(index);
|
|
63
74
|
} else {
|
|
64
75
|
pagerRef.current?.setPageWithoutAnimation(index);
|
|
65
|
-
|
|
76
|
+
setReportedOffset(index);
|
|
66
77
|
}
|
|
78
|
+
});
|
|
67
79
|
|
|
80
|
+
const jumpTo = useLatestCallback((key: string) => {
|
|
81
|
+
const index = navigationStateRef.current.routes.findIndex(
|
|
82
|
+
(route: { key: string }) => route.key === key
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
setPage(index);
|
|
68
86
|
onIndexChange(index);
|
|
69
87
|
});
|
|
70
88
|
|
|
@@ -74,14 +92,9 @@ export function PagerViewAdapter({
|
|
|
74
92
|
}
|
|
75
93
|
|
|
76
94
|
if (indexRef.current !== index) {
|
|
77
|
-
|
|
78
|
-
pagerRef.current?.setPage(index);
|
|
79
|
-
} else {
|
|
80
|
-
pagerRef.current?.setPageWithoutAnimation(index);
|
|
81
|
-
position.setValue(index);
|
|
82
|
-
}
|
|
95
|
+
setPage(index);
|
|
83
96
|
}
|
|
84
|
-
}, [keyboardDismissMode, index,
|
|
97
|
+
}, [keyboardDismissMode, index, setPage]);
|
|
85
98
|
|
|
86
99
|
const onPageScrollStateChanged = (
|
|
87
100
|
state: PageScrollStateChangedNativeEvent
|
|
@@ -123,10 +136,14 @@ export function PagerViewAdapter({
|
|
|
123
136
|
};
|
|
124
137
|
});
|
|
125
138
|
|
|
126
|
-
const memoizedPosition = React.useMemo(
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
139
|
+
const memoizedPosition = React.useMemo(() => {
|
|
140
|
+
const value = Animated.add(position, offset);
|
|
141
|
+
|
|
142
|
+
// Convert the native RTL offset back to the logical route index
|
|
143
|
+
return shouldInvertOffset
|
|
144
|
+
? Animated.subtract(routes.length - 1, value)
|
|
145
|
+
: value;
|
|
146
|
+
}, [offset, position, routes.length, shouldInvertOffset]);
|
|
130
147
|
|
|
131
148
|
return children({
|
|
132
149
|
position: memoizedPosition,
|
|
@@ -138,6 +155,7 @@ export function PagerViewAdapter({
|
|
|
138
155
|
ref={pagerRef}
|
|
139
156
|
style={[styles.container, style]}
|
|
140
157
|
initialPage={index}
|
|
158
|
+
layoutDirection={layoutDirection}
|
|
141
159
|
keyboardDismissMode={
|
|
142
160
|
keyboardDismissMode === 'auto' ? 'on-drag' : keyboardDismissMode
|
|
143
161
|
}
|
|
@@ -293,6 +293,10 @@ export function PanResponderAdapter({
|
|
|
293
293
|
const route = routes[i];
|
|
294
294
|
const focused = i === index;
|
|
295
295
|
|
|
296
|
+
if (route == null) {
|
|
297
|
+
throw new Error(`Couldn't find a route at index ${i}.`);
|
|
298
|
+
}
|
|
299
|
+
|
|
296
300
|
if (!layout.width && !focused) {
|
|
297
301
|
return null;
|
|
298
302
|
}
|
|
@@ -8,12 +8,16 @@ import {
|
|
|
8
8
|
} from 'react-native';
|
|
9
9
|
|
|
10
10
|
export type Props = Omit<PressableProps, 'onPress'> & {
|
|
11
|
-
href?: string;
|
|
12
|
-
pressColor?: ColorValue;
|
|
13
|
-
pressOpacity?: number;
|
|
14
|
-
onPress?:
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
href?: string | undefined;
|
|
12
|
+
pressColor?: ColorValue | undefined;
|
|
13
|
+
pressOpacity?: number | undefined;
|
|
14
|
+
onPress?:
|
|
15
|
+
| ((
|
|
16
|
+
e:
|
|
17
|
+
| React.MouseEvent<HTMLAnchorElement, MouseEvent>
|
|
18
|
+
| GestureResponderEvent
|
|
19
|
+
) => void)
|
|
20
|
+
| undefined;
|
|
17
21
|
children: React.ReactNode;
|
|
18
22
|
};
|
|
19
23
|
|
|
@@ -27,7 +31,7 @@ const ANDROID_SUPPORTS_RIPPLE =
|
|
|
27
31
|
export function PlatformPressable({
|
|
28
32
|
disabled,
|
|
29
33
|
android_ripple,
|
|
30
|
-
pressColor = 'rgba(0, 0, 0, .
|
|
34
|
+
pressColor = 'rgba(0, 0, 0, .10)',
|
|
31
35
|
pressOpacity,
|
|
32
36
|
style,
|
|
33
37
|
onPress,
|
package/src/SceneMap.tsx
CHANGED
|
@@ -18,13 +18,21 @@ const SceneComponent = React.memo(
|
|
|
18
18
|
SceneComponent.displayName = 'SceneComponent';
|
|
19
19
|
|
|
20
20
|
export function SceneMap<T>(scenes: { [key: string]: React.ComponentType<T> }) {
|
|
21
|
-
return ({ route, jumpTo, position }: SceneProps) =>
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
route
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
21
|
+
return ({ route, jumpTo, position }: SceneProps) => {
|
|
22
|
+
const component = scenes[route.key];
|
|
23
|
+
|
|
24
|
+
if (component == null) {
|
|
25
|
+
throw new Error(`Couldn't find a scene for route '${route.key}'.`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<SceneComponent
|
|
30
|
+
key={route.key}
|
|
31
|
+
component={component}
|
|
32
|
+
route={route}
|
|
33
|
+
jumpTo={jumpTo}
|
|
34
|
+
position={position}
|
|
35
|
+
/>
|
|
36
|
+
);
|
|
37
|
+
};
|
|
30
38
|
}
|
package/src/SceneView.tsx
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
Platform,
|
|
4
|
+
type StyleProp,
|
|
5
|
+
StyleSheet,
|
|
6
|
+
View,
|
|
7
|
+
type ViewStyle,
|
|
8
|
+
} from 'react-native';
|
|
3
9
|
|
|
4
10
|
import type {
|
|
5
11
|
EventEmitterProps,
|
|
@@ -69,8 +75,41 @@ export function SceneView<T extends Route>({
|
|
|
69
75
|
};
|
|
70
76
|
}, [subscribe, index, isLoading, lazy]);
|
|
71
77
|
|
|
78
|
+
const ref = React.useRef<View>(null);
|
|
79
|
+
|
|
80
|
+
React.useLayoutEffect(() => {
|
|
81
|
+
let unsubscribe: (() => void) | undefined;
|
|
82
|
+
|
|
83
|
+
const element = ref.current;
|
|
84
|
+
|
|
85
|
+
if (
|
|
86
|
+
Platform.OS === 'web' &&
|
|
87
|
+
typeof HTMLElement !== 'undefined' &&
|
|
88
|
+
element &&
|
|
89
|
+
element instanceof HTMLElement
|
|
90
|
+
) {
|
|
91
|
+
if (isFocused) {
|
|
92
|
+
element.removeAttribute('inert');
|
|
93
|
+
} else {
|
|
94
|
+
element.setAttribute('inert', '');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Remove inert as soon as the view is in viewport
|
|
98
|
+
// This will allow quick interaction after swiping
|
|
99
|
+
unsubscribe = subscribe((event) => {
|
|
100
|
+
if (event.type === 'enter' && event.index === index) {
|
|
101
|
+
element.removeAttribute('inert');
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return () => {
|
|
107
|
+
unsubscribe?.();
|
|
108
|
+
};
|
|
109
|
+
}, [index, isFocused, subscribe]);
|
|
110
|
+
|
|
72
111
|
return (
|
|
73
|
-
<View aria-hidden={!isFocused} style={[styles.route, style]}>
|
|
112
|
+
<View ref={ref} aria-hidden={!isFocused} style={[styles.route, style]}>
|
|
74
113
|
{children({ loading: isLoading })}
|
|
75
114
|
</View>
|
|
76
115
|
);
|
|
@@ -36,7 +36,7 @@ export function ScrollViewAdapter({
|
|
|
36
36
|
children,
|
|
37
37
|
style,
|
|
38
38
|
animationEnabled = true,
|
|
39
|
-
layoutDirection
|
|
39
|
+
layoutDirection = 'ltr',
|
|
40
40
|
decelerationRate = 'fast',
|
|
41
41
|
bounces = false,
|
|
42
42
|
overScrollMode = 'never',
|
|
@@ -45,6 +45,16 @@ export function ScrollViewAdapter({
|
|
|
45
45
|
}: ScrollViewAdapterProps) {
|
|
46
46
|
const { index, routes } = navigationState;
|
|
47
47
|
|
|
48
|
+
const isRTL = layoutDirection === 'rtl';
|
|
49
|
+
|
|
50
|
+
// Android reports RTL scroll offsets from the opposite end (0 at the max scroll offset)
|
|
51
|
+
// So we need to invert them to get the logical route index
|
|
52
|
+
const shouldInvertOffset = Platform.OS === 'android' && isRTL;
|
|
53
|
+
|
|
54
|
+
// Web uses negative scroll offsets in RTL
|
|
55
|
+
// So we need to negate them to get the logical route index
|
|
56
|
+
const shouldNegateOffset = Platform.OS === 'web' && isRTL;
|
|
57
|
+
|
|
48
58
|
const listeners = React.useRef<Set<Listener>>(new Set()).current;
|
|
49
59
|
|
|
50
60
|
const scrollViewRef = React.useRef<ScrollView>(null);
|
|
@@ -52,13 +62,48 @@ export function ScrollViewAdapter({
|
|
|
52
62
|
|
|
53
63
|
const isInitialRef = React.useRef(true);
|
|
54
64
|
|
|
65
|
+
const getScrollOffsetForIndex = React.useCallback(
|
|
66
|
+
(index: number, width: number) =>
|
|
67
|
+
(shouldNegateOffset ? -index : index) * width,
|
|
68
|
+
[shouldNegateOffset]
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const getReportedOffsetForIndex = React.useCallback(
|
|
72
|
+
(index: number, width: number) =>
|
|
73
|
+
shouldInvertOffset
|
|
74
|
+
? (routes.length - 1 - index) * width
|
|
75
|
+
: getScrollOffsetForIndex(index, width),
|
|
76
|
+
[getScrollOffsetForIndex, routes.length, shouldInvertOffset]
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
const getIndexFromOffset = React.useCallback(
|
|
80
|
+
(x: number, width: number) => {
|
|
81
|
+
const offset = clamp(
|
|
82
|
+
x / width,
|
|
83
|
+
shouldNegateOffset ? -(routes.length - 1) : 0,
|
|
84
|
+
routes.length - 1
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
if (shouldInvertOffset) {
|
|
88
|
+
return routes.length - 1 - offset;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (shouldNegateOffset) {
|
|
92
|
+
return -offset;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return offset;
|
|
96
|
+
},
|
|
97
|
+
[routes.length, shouldInvertOffset, shouldNegateOffset]
|
|
98
|
+
);
|
|
99
|
+
|
|
55
100
|
const [layout, onLayout] = useMeasureLayout(containerRef, ({ width }) => {
|
|
56
101
|
if (isInitialRef.current) {
|
|
57
|
-
const x = index
|
|
102
|
+
const x = getScrollOffsetForIndex(index, width);
|
|
58
103
|
|
|
59
104
|
setContentOffset({ x, y: 0 });
|
|
60
105
|
|
|
61
|
-
offsetX.setValue(
|
|
106
|
+
offsetX.setValue(getReportedOffsetForIndex(index, width));
|
|
62
107
|
} else if (indexRef.current !== index) {
|
|
63
108
|
scrollToIndex(index);
|
|
64
109
|
}
|
|
@@ -67,14 +112,14 @@ export function ScrollViewAdapter({
|
|
|
67
112
|
});
|
|
68
113
|
|
|
69
114
|
const [contentOffset, setContentOffset] = React.useState(() => ({
|
|
70
|
-
x: index
|
|
115
|
+
x: getScrollOffsetForIndex(index, layout.width),
|
|
71
116
|
y: 0,
|
|
72
117
|
}));
|
|
73
118
|
|
|
74
119
|
React.useEffect(() => {
|
|
75
|
-
// FIXME: contentOffset is not supported on Android
|
|
120
|
+
// FIXME: contentOffset is not supported on Android and Web
|
|
76
121
|
// So we manually scroll after state update
|
|
77
|
-
if (Platform.OS === 'android') {
|
|
122
|
+
if (Platform.OS === 'android' || Platform.OS === 'web') {
|
|
78
123
|
requestAnimationFrame(() => {
|
|
79
124
|
scrollViewRef.current?.scrollTo({
|
|
80
125
|
x: contentOffset.x,
|
|
@@ -82,13 +127,13 @@ export function ScrollViewAdapter({
|
|
|
82
127
|
});
|
|
83
128
|
});
|
|
84
129
|
}
|
|
85
|
-
}, [
|
|
130
|
+
}, [contentOffset.x]);
|
|
86
131
|
|
|
87
132
|
const [offsetX] = React.useState(() => new Animated.Value(contentOffset.x));
|
|
88
133
|
|
|
89
134
|
const scrollToIndex = useLatestCallback((index: number) => {
|
|
90
135
|
scrollViewRef.current?.scrollTo({
|
|
91
|
-
x: index
|
|
136
|
+
x: getScrollOffsetForIndex(index, layout.width),
|
|
92
137
|
animated: animationEnabled,
|
|
93
138
|
});
|
|
94
139
|
});
|
|
@@ -119,25 +164,43 @@ export function ScrollViewAdapter({
|
|
|
119
164
|
};
|
|
120
165
|
});
|
|
121
166
|
|
|
122
|
-
const position = React.useMemo(
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
167
|
+
const position = React.useMemo(() => {
|
|
168
|
+
if (!layout.width) {
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const value = Animated.divide(offsetX, layout.width);
|
|
173
|
+
|
|
174
|
+
// Convert platform scroll offsets back to logical route index
|
|
175
|
+
return shouldInvertOffset
|
|
176
|
+
? Animated.subtract(routes.length - 1, value)
|
|
177
|
+
: shouldNegateOffset
|
|
178
|
+
? Animated.multiply(value, -1)
|
|
179
|
+
: value;
|
|
180
|
+
}, [
|
|
181
|
+
layout.width,
|
|
182
|
+
offsetX,
|
|
183
|
+
routes.length,
|
|
184
|
+
shouldInvertOffset,
|
|
185
|
+
shouldNegateOffset,
|
|
186
|
+
]);
|
|
126
187
|
|
|
127
188
|
const indexRef = React.useRef(index);
|
|
128
|
-
const timerRef = React.useRef<
|
|
189
|
+
const timerRef = React.useRef<ReturnType<typeof setTimeout> | undefined>(
|
|
190
|
+
undefined
|
|
191
|
+
);
|
|
129
192
|
|
|
130
193
|
const onScrollEnd = (x: number) => {
|
|
131
|
-
const
|
|
194
|
+
const nextIndex = getIndexFromOffset(x, layout.width);
|
|
132
195
|
|
|
133
|
-
if (
|
|
134
|
-
indexRef.current =
|
|
196
|
+
if (nextIndex % 1 === 0) {
|
|
197
|
+
indexRef.current = nextIndex;
|
|
135
198
|
|
|
136
|
-
if (
|
|
137
|
-
onIndexChange(
|
|
199
|
+
if (nextIndex !== index) {
|
|
200
|
+
onIndexChange(nextIndex);
|
|
138
201
|
}
|
|
139
202
|
|
|
140
|
-
onTabSelect?.({ index:
|
|
203
|
+
onTabSelect?.({ index: nextIndex });
|
|
141
204
|
}
|
|
142
205
|
};
|
|
143
206
|
|
|
@@ -150,11 +213,10 @@ export function ScrollViewAdapter({
|
|
|
150
213
|
},
|
|
151
214
|
],
|
|
152
215
|
{
|
|
153
|
-
useNativeDriver:
|
|
216
|
+
useNativeDriver: Platform.OS !== 'web',
|
|
154
217
|
listener: (event: ScrollEvent) => {
|
|
155
218
|
const { x } = event.nativeEvent.contentOffset;
|
|
156
|
-
|
|
157
|
-
const value = clamp(x / layout.width, 0, routes.length - 1);
|
|
219
|
+
const value = getIndexFromOffset(x, layout.width);
|
|
158
220
|
|
|
159
221
|
// The offset will overlap the current and the adjacent page
|
|
160
222
|
// So we need to get the index of the adjacent page
|
|
@@ -228,6 +290,10 @@ export function ScrollViewAdapter({
|
|
|
228
290
|
const route = routes[i];
|
|
229
291
|
const focused = i === index;
|
|
230
292
|
|
|
293
|
+
if (route == null) {
|
|
294
|
+
throw new Error(`Couldn't find a route at index ${i}.`);
|
|
295
|
+
}
|
|
296
|
+
|
|
231
297
|
if (!layout.width && !focused) {
|
|
232
298
|
return null;
|
|
233
299
|
}
|