react-native-tab-view 3.2.0 → 3.3.0
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 +4 -0
- package/lib/commonjs/PagerViewAdapter.js +25 -8
- package/lib/commonjs/PagerViewAdapter.js.map +1 -1
- package/lib/commonjs/PanResponderAdapter.js +29 -20
- package/lib/commonjs/PanResponderAdapter.js.map +1 -1
- package/lib/commonjs/SceneMap.js +9 -12
- package/lib/commonjs/SceneMap.js.map +1 -1
- package/lib/commonjs/SceneView.js +54 -101
- package/lib/commonjs/SceneView.js.map +1 -1
- package/lib/commonjs/TabBar.js +327 -327
- package/lib/commonjs/TabBar.js.map +1 -1
- package/lib/commonjs/TabBarIndicator.js +81 -99
- package/lib/commonjs/TabBarIndicator.js.map +1 -1
- package/lib/commonjs/TabBarItem.js +184 -161
- package/lib/commonjs/TabBarItem.js.map +1 -1
- package/lib/commonjs/TabView.js +3 -1
- package/lib/commonjs/TabView.js.map +1 -1
- package/lib/commonjs/types.js.map +1 -1
- package/lib/module/PagerViewAdapter.js +25 -8
- package/lib/module/PagerViewAdapter.js.map +1 -1
- package/lib/module/PanResponderAdapter.js +29 -19
- package/lib/module/PanResponderAdapter.js.map +1 -1
- package/lib/module/SceneMap.js +9 -14
- package/lib/module/SceneMap.js.map +1 -1
- package/lib/module/SceneView.js +53 -98
- package/lib/module/SceneView.js.map +1 -1
- package/lib/module/TabBar.js +323 -323
- package/lib/module/TabBar.js.map +1 -1
- package/lib/module/TabBarIndicator.js +74 -92
- package/lib/module/TabBarIndicator.js.map +1 -1
- package/lib/module/TabBarItem.js +178 -154
- package/lib/module/TabBarItem.js.map +1 -1
- package/lib/module/TabView.js +3 -1
- package/lib/module/TabView.js.map +1 -1
- package/lib/module/types.js.map +1 -1
- package/lib/typescript/PagerViewAdapter.d.ts +1 -1
- package/lib/typescript/PanResponderAdapter.d.ts +1 -1
- package/lib/typescript/SceneMap.d.ts +5 -3
- package/lib/typescript/SceneView.d.ts +1 -18
- package/lib/typescript/TabBar.d.ts +7 -38
- package/lib/typescript/TabBarIndicator.d.ts +2 -10
- package/lib/typescript/TabBarItem.d.ts +3 -5
- package/lib/typescript/TabView.d.ts +1 -1
- package/lib/typescript/types.d.ts +1 -0
- package/package.json +4 -1
- package/src/PagerViewAdapter.tsx +29 -10
- package/src/PanResponderAdapter.tsx +29 -20
- package/src/SceneMap.tsx +11 -7
- package/src/SceneView.tsx +70 -106
- package/src/TabBar.tsx +451 -391
- package/src/TabBarIndicator.tsx +108 -114
- package/src/TabBarItem.tsx +214 -185
- package/src/TabView.tsx +2 -0
- package/src/types.tsx +1 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
/// <reference types="react" />
|
|
2
2
|
import { StyleProp, ViewStyle } from 'react-native';
|
|
3
3
|
import type { Route, SceneRendererProps, NavigationState } from './types';
|
|
4
4
|
export declare type GetTabWidth = (index: number) => number;
|
|
@@ -9,12 +9,4 @@ export declare type Props<T extends Route> = SceneRendererProps & {
|
|
|
9
9
|
getTabWidth: GetTabWidth;
|
|
10
10
|
gap?: number;
|
|
11
11
|
};
|
|
12
|
-
export default
|
|
13
|
-
componentDidMount(): void;
|
|
14
|
-
componentDidUpdate(): void;
|
|
15
|
-
private fadeInIndicator;
|
|
16
|
-
private isIndicatorShown;
|
|
17
|
-
private opacity;
|
|
18
|
-
private getTranslateX;
|
|
19
|
-
render(): JSX.Element;
|
|
20
|
-
}
|
|
12
|
+
export default function TabBarIndicator<T extends Route>({ getTabWidth, layout, navigationState, position, width, gap, style, }: Props<T>): JSX.Element;
|
|
@@ -27,11 +27,9 @@ export declare type Props<T extends Route> = {
|
|
|
27
27
|
onLayout?: (event: LayoutChangeEvent) => void;
|
|
28
28
|
onPress: () => void;
|
|
29
29
|
onLongPress: () => void;
|
|
30
|
+
defaultTabWidth?: number;
|
|
30
31
|
labelStyle?: StyleProp<TextStyle>;
|
|
31
32
|
style: StyleProp<ViewStyle>;
|
|
32
33
|
};
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
private getInactiveOpacity;
|
|
36
|
-
render(): JSX.Element;
|
|
37
|
-
}
|
|
34
|
+
declare function TabBarItem<T extends Route>(props: Props<T>): JSX.Element;
|
|
35
|
+
export default TabBarItem;
|
|
@@ -23,4 +23,4 @@ export declare type Props<T extends Route> = PagerProps & {
|
|
|
23
23
|
pagerStyle?: StyleProp<ViewStyle>;
|
|
24
24
|
style?: StyleProp<ViewStyle>;
|
|
25
25
|
};
|
|
26
|
-
export default function TabView<T extends Route>({ onIndexChange, navigationState, renderScene, initialLayout, keyboardDismissMode, lazy, lazyPreloadDistance, onSwipeStart, onSwipeEnd, renderLazyPlaceholder, renderTabBar, sceneContainerStyle, pagerStyle, style, swipeEnabled, tabBarPosition, }: Props<T>): JSX.Element;
|
|
26
|
+
export default function TabView<T extends Route>({ onIndexChange, navigationState, renderScene, initialLayout, keyboardDismissMode, lazy, lazyPreloadDistance, onSwipeStart, onSwipeEnd, renderLazyPlaceholder, renderTabBar, sceneContainerStyle, pagerStyle, style, swipeEnabled, tabBarPosition, animationEnabled, }: Props<T>): JSX.Element;
|
|
@@ -35,6 +35,7 @@ export declare type EventEmitterProps = {
|
|
|
35
35
|
export declare type PagerProps = Omit<PagerViewProps, 'initialPage' | 'scrollEnabled' | 'onPageScroll' | 'onPageSelected' | 'onPageScrollStateChanged' | 'keyboardDismissMode' | 'children'> & {
|
|
36
36
|
keyboardDismissMode?: 'none' | 'on-drag' | 'auto';
|
|
37
37
|
swipeEnabled?: boolean;
|
|
38
|
+
animationEnabled?: boolean;
|
|
38
39
|
onSwipeStart?: () => void;
|
|
39
40
|
onSwipeEnd?: () => void;
|
|
40
41
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-tab-view",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.0",
|
|
4
4
|
"description": "Tab view component for React Native",
|
|
5
5
|
"main": "lib/commonjs/index.js",
|
|
6
6
|
"react-native": "src/index.tsx",
|
|
@@ -97,5 +97,8 @@
|
|
|
97
97
|
}
|
|
98
98
|
]
|
|
99
99
|
]
|
|
100
|
+
},
|
|
101
|
+
"dependencies": {
|
|
102
|
+
"use-latest-callback": "^0.1.5"
|
|
100
103
|
}
|
|
101
104
|
}
|
package/src/PagerViewAdapter.tsx
CHANGED
|
@@ -41,6 +41,7 @@ export default function PagerViewAdapter<T extends Route>({
|
|
|
41
41
|
onSwipeEnd,
|
|
42
42
|
children,
|
|
43
43
|
style,
|
|
44
|
+
animationEnabled,
|
|
44
45
|
...rest
|
|
45
46
|
}: Props<T>) {
|
|
46
47
|
const { index } = navigationState;
|
|
@@ -58,13 +59,21 @@ export default function PagerViewAdapter<T extends Route>({
|
|
|
58
59
|
navigationStateRef.current = navigationState;
|
|
59
60
|
});
|
|
60
61
|
|
|
61
|
-
const jumpTo = React.useCallback(
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
62
|
+
const jumpTo = React.useCallback(
|
|
63
|
+
(key: string) => {
|
|
64
|
+
const index = navigationStateRef.current.routes.findIndex(
|
|
65
|
+
(route: { key: string }) => route.key === key
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
if (animationEnabled) {
|
|
69
|
+
pagerRef.current?.setPage(index);
|
|
70
|
+
} else {
|
|
71
|
+
pagerRef.current?.setPageWithoutAnimation(index);
|
|
72
|
+
position.setValue(index);
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
[animationEnabled, position]
|
|
76
|
+
);
|
|
68
77
|
|
|
69
78
|
React.useEffect(() => {
|
|
70
79
|
if (keyboardDismissMode === 'auto') {
|
|
@@ -72,9 +81,14 @@ export default function PagerViewAdapter<T extends Route>({
|
|
|
72
81
|
}
|
|
73
82
|
|
|
74
83
|
if (indexRef.current !== index) {
|
|
75
|
-
|
|
84
|
+
if (animationEnabled) {
|
|
85
|
+
pagerRef.current?.setPage(index);
|
|
86
|
+
} else {
|
|
87
|
+
pagerRef.current?.setPageWithoutAnimation(index);
|
|
88
|
+
position.setValue(index);
|
|
89
|
+
}
|
|
76
90
|
}
|
|
77
|
-
}, [keyboardDismissMode, index]);
|
|
91
|
+
}, [keyboardDismissMode, index, animationEnabled, position]);
|
|
78
92
|
|
|
79
93
|
const onPageScrollStateChanged = (
|
|
80
94
|
state: PageScrollStateChangedNativeEvent
|
|
@@ -115,8 +129,13 @@ export default function PagerViewAdapter<T extends Route>({
|
|
|
115
129
|
};
|
|
116
130
|
}, []);
|
|
117
131
|
|
|
132
|
+
const memoizedPosition = React.useMemo(
|
|
133
|
+
() => Animated.add(position, offset),
|
|
134
|
+
[offset, position]
|
|
135
|
+
);
|
|
136
|
+
|
|
118
137
|
return children({
|
|
119
|
-
position:
|
|
138
|
+
position: memoizedPosition,
|
|
120
139
|
addEnterListener,
|
|
121
140
|
jumpTo,
|
|
122
141
|
render: (children) => (
|
|
@@ -58,6 +58,7 @@ export default function PanResponderAdapter<T extends Route>({
|
|
|
58
58
|
onSwipeEnd,
|
|
59
59
|
children,
|
|
60
60
|
style,
|
|
61
|
+
animationEnabled = false,
|
|
61
62
|
}: Props<T>) {
|
|
62
63
|
const { routes, index } = navigationState;
|
|
63
64
|
|
|
@@ -76,27 +77,32 @@ export default function PanResponderAdapter<T extends Route>({
|
|
|
76
77
|
const swipeDistanceThreshold = layout.width / 1.75;
|
|
77
78
|
|
|
78
79
|
const jumpToIndex = React.useCallback(
|
|
79
|
-
(index: number) => {
|
|
80
|
+
(index: number, animate = animationEnabled) => {
|
|
80
81
|
const offset = -index * layoutRef.current.width;
|
|
81
82
|
|
|
82
83
|
const { timing, ...transitionConfig } = DefaultTransitionSpec;
|
|
83
84
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
85
|
+
if (animate) {
|
|
86
|
+
Animated.parallel([
|
|
87
|
+
timing(panX, {
|
|
88
|
+
...transitionConfig,
|
|
89
|
+
toValue: offset,
|
|
90
|
+
useNativeDriver: false,
|
|
91
|
+
}),
|
|
92
|
+
]).start(({ finished }) => {
|
|
93
|
+
if (finished) {
|
|
94
|
+
onIndexChangeRef.current(index);
|
|
95
|
+
pendingIndexRef.current = undefined;
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
pendingIndexRef.current = index;
|
|
99
|
+
} else {
|
|
100
|
+
panX.setValue(offset);
|
|
101
|
+
onIndexChangeRef.current(index);
|
|
102
|
+
pendingIndexRef.current = undefined;
|
|
103
|
+
}
|
|
98
104
|
},
|
|
99
|
-
[panX]
|
|
105
|
+
[animationEnabled, panX]
|
|
100
106
|
);
|
|
101
107
|
|
|
102
108
|
React.useEffect(() => {
|
|
@@ -230,7 +236,7 @@ export default function PanResponderAdapter<T extends Route>({
|
|
|
230
236
|
nextIndex = currentIndex;
|
|
231
237
|
}
|
|
232
238
|
|
|
233
|
-
jumpToIndex(nextIndex);
|
|
239
|
+
jumpToIndex(nextIndex, true);
|
|
234
240
|
};
|
|
235
241
|
|
|
236
242
|
// TODO: use the listeners
|
|
@@ -277,10 +283,13 @@ export default function PanResponderAdapter<T extends Route>({
|
|
|
277
283
|
I18nManager.isRTL ? -1 : 1
|
|
278
284
|
);
|
|
279
285
|
|
|
286
|
+
const position = React.useMemo(
|
|
287
|
+
() => (layout.width ? Animated.divide(panX, -layout.width) : null),
|
|
288
|
+
[layout.width, panX]
|
|
289
|
+
);
|
|
290
|
+
|
|
280
291
|
return children({
|
|
281
|
-
position:
|
|
282
|
-
? Animated.divide(panX, -layout.width)
|
|
283
|
-
: new Animated.Value(index),
|
|
292
|
+
position: position ?? new Animated.Value(index),
|
|
284
293
|
addEnterListener,
|
|
285
294
|
jumpTo,
|
|
286
295
|
render: (children) => (
|
package/src/SceneMap.tsx
CHANGED
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import type { SceneRendererProps } from './types';
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
type SceneProps = {
|
|
5
|
+
route: any;
|
|
6
|
+
} & Omit<SceneRendererProps, 'layout'>;
|
|
7
|
+
|
|
8
|
+
const SceneComponent = React.memo(
|
|
9
|
+
<T extends { component: React.ComponentType<any> } & SceneProps>({
|
|
10
|
+
component,
|
|
11
|
+
...rest
|
|
12
|
+
}: T) => {
|
|
9
13
|
return React.createElement(component, rest);
|
|
10
14
|
}
|
|
11
|
-
|
|
15
|
+
);
|
|
12
16
|
|
|
13
17
|
export default function SceneMap<T extends any>(scenes: {
|
|
14
18
|
[key: string]: React.ComponentType<T>;
|
|
15
19
|
}) {
|
|
16
|
-
return ({ route, jumpTo, position }:
|
|
20
|
+
return ({ route, jumpTo, position }: SceneProps) => (
|
|
17
21
|
<SceneComponent
|
|
18
22
|
key={route.key}
|
|
19
23
|
component={scenes[route.key]}
|
package/src/SceneView.tsx
CHANGED
|
@@ -17,121 +17,85 @@ type Props<T extends Route> = SceneRendererProps &
|
|
|
17
17
|
style?: StyleProp<ViewStyle>;
|
|
18
18
|
};
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
20
|
+
export default function SceneView<T extends Route>({
|
|
21
|
+
children,
|
|
22
|
+
navigationState,
|
|
23
|
+
lazy,
|
|
24
|
+
layout,
|
|
25
|
+
index,
|
|
26
|
+
lazyPreloadDistance,
|
|
27
|
+
addEnterListener,
|
|
28
|
+
style,
|
|
29
|
+
}: Props<T>) {
|
|
30
|
+
const [isLoading, setIsLoading] = React.useState(
|
|
31
|
+
Math.abs(navigationState.index - index) > lazyPreloadDistance
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
if (
|
|
35
|
+
isLoading &&
|
|
36
|
+
Math.abs(navigationState.index - index) <= lazyPreloadDistance
|
|
37
|
+
) {
|
|
38
|
+
// Always render the route when it becomes focused
|
|
39
|
+
setIsLoading(false);
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
React.useEffect(() => {
|
|
43
|
+
const handleEnter = (value: number) => {
|
|
44
|
+
// If we're entering the current route, we need to load it
|
|
45
|
+
if (value === index) {
|
|
46
|
+
setIsLoading((prevState) => {
|
|
47
|
+
if (prevState) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
return prevState;
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
let unsubscribe: (() => void) | undefined;
|
|
56
|
+
let timer: NodeJS.Timeout;
|
|
46
57
|
|
|
47
|
-
|
|
48
|
-
if (this.props.lazy) {
|
|
58
|
+
if (lazy && isLoading) {
|
|
49
59
|
// If lazy mode is enabled, listen to when we enter screens
|
|
50
|
-
|
|
51
|
-
} else if (
|
|
60
|
+
unsubscribe = addEnterListener(handleEnter);
|
|
61
|
+
} else if (isLoading) {
|
|
52
62
|
// If lazy mode is not enabled, render the scene with a delay if not loaded already
|
|
53
63
|
// This improves the initial startup time as the scene is no longer blocking
|
|
54
|
-
|
|
55
|
-
() => this.setState({ loading: false }),
|
|
56
|
-
0
|
|
57
|
-
);
|
|
64
|
+
timer = setTimeout(() => setIsLoading(false), 0);
|
|
58
65
|
}
|
|
59
|
-
}
|
|
60
66
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
67
|
+
return () => {
|
|
68
|
+
unsubscribe?.();
|
|
69
|
+
clearTimeout(timer);
|
|
70
|
+
};
|
|
71
|
+
}, [addEnterListener, index, isLoading, lazy]);
|
|
72
|
+
|
|
73
|
+
const focused = navigationState.index === index;
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<View
|
|
77
|
+
accessibilityElementsHidden={!focused}
|
|
78
|
+
importantForAccessibility={focused ? 'auto' : 'no-hide-descendants'}
|
|
79
|
+
style={[
|
|
80
|
+
styles.route,
|
|
81
|
+
// If we don't have the layout yet, make the focused screen fill the container
|
|
82
|
+
// This avoids delay before we are able to render pages side by side
|
|
83
|
+
layout.width
|
|
84
|
+
? { width: layout.width }
|
|
85
|
+
: focused
|
|
86
|
+
? StyleSheet.absoluteFill
|
|
87
|
+
: null,
|
|
88
|
+
style,
|
|
89
|
+
]}
|
|
90
|
+
>
|
|
91
|
+
{
|
|
92
|
+
// Only render the route only if it's either focused or layout is available
|
|
93
|
+
// When layout is not available, we must not render unfocused routes
|
|
94
|
+
// so that the focused route can fill the screen
|
|
95
|
+
focused || layout.width ? children({ loading: isLoading }) : null
|
|
72
96
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
componentWillUnmount() {
|
|
77
|
-
this.unsubscribe?.();
|
|
78
|
-
|
|
79
|
-
if (this.timerHandler) {
|
|
80
|
-
clearTimeout(this.timerHandler);
|
|
81
|
-
this.timerHandler = undefined;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
private timerHandler: NodeJS.Timeout | undefined;
|
|
86
|
-
|
|
87
|
-
private unsubscribe: (() => void) | null = null;
|
|
88
|
-
|
|
89
|
-
private handleEnter = (value: number) => {
|
|
90
|
-
const { index } = this.props;
|
|
91
|
-
|
|
92
|
-
// If we're entering the current route, we need to load it
|
|
93
|
-
if (value === index) {
|
|
94
|
-
this.setState((prevState) => {
|
|
95
|
-
if (prevState.loading) {
|
|
96
|
-
return { loading: false };
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
return null;
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
render() {
|
|
105
|
-
const { navigationState, index, layout, style } = this.props;
|
|
106
|
-
const { loading } = this.state;
|
|
107
|
-
|
|
108
|
-
const focused = navigationState.index === index;
|
|
109
|
-
|
|
110
|
-
return (
|
|
111
|
-
<View
|
|
112
|
-
accessibilityElementsHidden={!focused}
|
|
113
|
-
importantForAccessibility={focused ? 'auto' : 'no-hide-descendants'}
|
|
114
|
-
style={[
|
|
115
|
-
styles.route,
|
|
116
|
-
// If we don't have the layout yet, make the focused screen fill the container
|
|
117
|
-
// This avoids delay before we are able to render pages side by side
|
|
118
|
-
layout.width
|
|
119
|
-
? { width: layout.width }
|
|
120
|
-
: focused
|
|
121
|
-
? StyleSheet.absoluteFill
|
|
122
|
-
: null,
|
|
123
|
-
style,
|
|
124
|
-
]}
|
|
125
|
-
>
|
|
126
|
-
{
|
|
127
|
-
// Only render the route only if it's either focused or layout is available
|
|
128
|
-
// When layout is not available, we must not render unfocused routes
|
|
129
|
-
// so that the focused route can fill the screen
|
|
130
|
-
focused || layout.width ? this.props.children({ loading }) : null
|
|
131
|
-
}
|
|
132
|
-
</View>
|
|
133
|
-
);
|
|
134
|
-
}
|
|
97
|
+
</View>
|
|
98
|
+
);
|
|
135
99
|
}
|
|
136
100
|
|
|
137
101
|
const styles = StyleSheet.create({
|