@amazon-devices/react-navigation__react-native-tab-view 8.0.9000000001 → 8.0.9000000002
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/kepler-compatibility.json +2 -1
- package/lib/module/index.js +0 -7
- package/package.json +1 -1
- package/src/index.tsx +1 -15
- package/lib/module/Pager.android.js +0 -4
- package/lib/module/Pager.ios.js +0 -4
- package/lib/module/Pager.js +0 -4
- package/lib/module/PagerViewAdapter.js +0 -130
- package/lib/module/PanResponderAdapter.js +0 -209
- package/lib/module/PlatformPressable.js +0 -61
- package/lib/module/SceneMap.js +0 -24
- package/lib/module/SceneView.js +0 -72
- package/lib/module/TabBar.js +0 -472
- package/lib/module/TabBarIndicator.js +0 -122
- package/lib/module/TabBarItem.js +0 -217
- package/lib/module/TabBarItemLabel.js +0 -33
- package/lib/module/TabView.js +0 -141
- package/lib/module/types.js +0 -4
- package/lib/module/useAnimatedValue.js +0 -12
- package/src/Pager.android.tsx +0 -1
- package/src/Pager.ios.tsx +0 -1
- package/src/Pager.tsx +0 -1
- package/src/PagerViewAdapter.tsx +0 -185
- package/src/PanResponderAdapter.tsx +0 -344
- package/src/PlatformPressable.tsx +0 -91
- package/src/SceneMap.tsx +0 -30
- package/src/SceneView.tsx +0 -106
- package/src/TabBar.tsx +0 -728
- package/src/TabBarIndicator.tsx +0 -190
- package/src/TabBarItem.tsx +0 -308
- package/src/TabBarItemLabel.tsx +0 -42
- package/src/TabView.tsx +0 -198
- package/src/types.tsx +0 -87
- package/src/useAnimatedValue.tsx +0 -12
package/lib/module/TabBarItem.js
DELETED
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
import * as React from 'react';
|
|
4
|
-
import { Animated, Platform, StyleSheet, View } from 'react-native';
|
|
5
|
-
import useLatestCallback from 'use-latest-callback';
|
|
6
|
-
import { PlatformPressable } from "./PlatformPressable.js";
|
|
7
|
-
import { TabBarItemLabel } from "./TabBarItemLabel.js";
|
|
8
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
9
|
-
const DEFAULT_ACTIVE_COLOR = 'rgba(255, 255, 255, 1)';
|
|
10
|
-
const DEFAULT_INACTIVE_COLOR = 'rgba(255, 255, 255, 0.7)';
|
|
11
|
-
const ICON_SIZE = 24;
|
|
12
|
-
const getActiveOpacity = (position, routesLength, tabIndex) => {
|
|
13
|
-
if (routesLength > 1) {
|
|
14
|
-
const inputRange = Array.from({
|
|
15
|
-
length: routesLength
|
|
16
|
-
}, (_, i) => i);
|
|
17
|
-
return position.interpolate({
|
|
18
|
-
inputRange,
|
|
19
|
-
outputRange: inputRange.map(i => i === tabIndex ? 1 : 0)
|
|
20
|
-
});
|
|
21
|
-
} else {
|
|
22
|
-
return 1;
|
|
23
|
-
}
|
|
24
|
-
};
|
|
25
|
-
const getInactiveOpacity = (position, routesLength, tabIndex) => {
|
|
26
|
-
if (routesLength > 1) {
|
|
27
|
-
const inputRange = Array.from({
|
|
28
|
-
length: routesLength
|
|
29
|
-
}, (_, i) => i);
|
|
30
|
-
return position.interpolate({
|
|
31
|
-
inputRange,
|
|
32
|
-
outputRange: inputRange.map(i => i === tabIndex ? 0 : 1)
|
|
33
|
-
});
|
|
34
|
-
} else {
|
|
35
|
-
return 0;
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
const ANDROID_RIPPLE_DEFAULT = {
|
|
39
|
-
borderless: true
|
|
40
|
-
};
|
|
41
|
-
const TabBarItemInternal = ({
|
|
42
|
-
accessibilityLabel,
|
|
43
|
-
accessible,
|
|
44
|
-
label: customlabel,
|
|
45
|
-
testID,
|
|
46
|
-
onLongPress,
|
|
47
|
-
onPress,
|
|
48
|
-
isFocused,
|
|
49
|
-
position,
|
|
50
|
-
style,
|
|
51
|
-
inactiveColor: inactiveColorCustom,
|
|
52
|
-
activeColor: activeColorCustom,
|
|
53
|
-
labelStyle,
|
|
54
|
-
onLayout,
|
|
55
|
-
index: tabIndex,
|
|
56
|
-
pressColor,
|
|
57
|
-
pressOpacity,
|
|
58
|
-
defaultTabWidth,
|
|
59
|
-
icon: customIcon,
|
|
60
|
-
badge: customBadge,
|
|
61
|
-
href,
|
|
62
|
-
labelText,
|
|
63
|
-
routesLength,
|
|
64
|
-
android_ripple = ANDROID_RIPPLE_DEFAULT,
|
|
65
|
-
labelAllowFontScaling,
|
|
66
|
-
route
|
|
67
|
-
}) => {
|
|
68
|
-
const labelColorFromStyle = StyleSheet.flatten(labelStyle || {}).color;
|
|
69
|
-
const activeColor = activeColorCustom !== undefined ? activeColorCustom : typeof labelColorFromStyle === 'string' ? labelColorFromStyle : DEFAULT_ACTIVE_COLOR;
|
|
70
|
-
const inactiveColor = inactiveColorCustom !== undefined ? inactiveColorCustom : typeof labelColorFromStyle === 'string' ? labelColorFromStyle : DEFAULT_INACTIVE_COLOR;
|
|
71
|
-
const activeOpacity = getActiveOpacity(position, routesLength, tabIndex);
|
|
72
|
-
const inactiveOpacity = getInactiveOpacity(position, routesLength, tabIndex);
|
|
73
|
-
const icon = React.useMemo(() => {
|
|
74
|
-
if (!customIcon) {
|
|
75
|
-
return null;
|
|
76
|
-
}
|
|
77
|
-
const inactiveIcon = customIcon({
|
|
78
|
-
focused: false,
|
|
79
|
-
color: inactiveColor,
|
|
80
|
-
size: ICON_SIZE,
|
|
81
|
-
route
|
|
82
|
-
});
|
|
83
|
-
const activeIcon = customIcon({
|
|
84
|
-
focused: true,
|
|
85
|
-
color: activeColor,
|
|
86
|
-
size: ICON_SIZE,
|
|
87
|
-
route
|
|
88
|
-
});
|
|
89
|
-
return /*#__PURE__*/_jsxs(View, {
|
|
90
|
-
style: styles.icon,
|
|
91
|
-
children: [/*#__PURE__*/_jsx(Animated.View, {
|
|
92
|
-
style: {
|
|
93
|
-
opacity: inactiveOpacity
|
|
94
|
-
},
|
|
95
|
-
children: inactiveIcon
|
|
96
|
-
}), /*#__PURE__*/_jsx(Animated.View, {
|
|
97
|
-
style: [StyleSheet.absoluteFill, {
|
|
98
|
-
opacity: activeOpacity
|
|
99
|
-
}],
|
|
100
|
-
children: activeIcon
|
|
101
|
-
})]
|
|
102
|
-
});
|
|
103
|
-
}, [activeColor, activeOpacity, customIcon, inactiveColor, inactiveOpacity, route]);
|
|
104
|
-
const renderLabel = React.useCallback(focused => customlabel ? customlabel({
|
|
105
|
-
focused,
|
|
106
|
-
color: focused ? activeColor : inactiveColor,
|
|
107
|
-
style: labelStyle,
|
|
108
|
-
labelText,
|
|
109
|
-
allowFontScaling: labelAllowFontScaling,
|
|
110
|
-
route
|
|
111
|
-
}) : /*#__PURE__*/_jsx(TabBarItemLabel, {
|
|
112
|
-
color: focused ? activeColor : inactiveColor,
|
|
113
|
-
icon: icon,
|
|
114
|
-
label: labelText,
|
|
115
|
-
style: labelStyle
|
|
116
|
-
}), [customlabel, activeColor, labelStyle, labelText, labelAllowFontScaling, route, inactiveColor, icon]);
|
|
117
|
-
const tabStyle = StyleSheet.flatten(style);
|
|
118
|
-
const isWidthSet = tabStyle?.width !== undefined;
|
|
119
|
-
const tabContainerStyle = isWidthSet ? null : {
|
|
120
|
-
width: defaultTabWidth
|
|
121
|
-
};
|
|
122
|
-
const ariaLabel = typeof accessibilityLabel !== 'undefined' ? accessibilityLabel : labelText;
|
|
123
|
-
return /*#__PURE__*/_jsx(PlatformPressable, {
|
|
124
|
-
android_ripple: android_ripple,
|
|
125
|
-
testID: testID,
|
|
126
|
-
accessible: accessible,
|
|
127
|
-
role: "tab",
|
|
128
|
-
"aria-label": ariaLabel,
|
|
129
|
-
"aria-selected": isFocused,
|
|
130
|
-
pressColor: pressColor,
|
|
131
|
-
pressOpacity: pressOpacity,
|
|
132
|
-
unstable_pressDelay: 0,
|
|
133
|
-
onLayout: onLayout,
|
|
134
|
-
onPress: onPress,
|
|
135
|
-
onLongPress: onLongPress,
|
|
136
|
-
href: href,
|
|
137
|
-
style: [styles.pressable, tabContainerStyle],
|
|
138
|
-
children: /*#__PURE__*/_jsxs(View, {
|
|
139
|
-
pointerEvents: "none",
|
|
140
|
-
style: [styles.item, tabStyle],
|
|
141
|
-
children: [icon, /*#__PURE__*/_jsxs(View, {
|
|
142
|
-
children: [/*#__PURE__*/_jsx(Animated.View, {
|
|
143
|
-
style: {
|
|
144
|
-
opacity: inactiveOpacity
|
|
145
|
-
},
|
|
146
|
-
children: renderLabel(false)
|
|
147
|
-
}), /*#__PURE__*/_jsx(Animated.View, {
|
|
148
|
-
style: [StyleSheet.absoluteFill, {
|
|
149
|
-
opacity: activeOpacity
|
|
150
|
-
}],
|
|
151
|
-
children: renderLabel(true)
|
|
152
|
-
})]
|
|
153
|
-
}), customBadge != null ? /*#__PURE__*/_jsx(View, {
|
|
154
|
-
style: styles.badge,
|
|
155
|
-
children: customBadge({
|
|
156
|
-
route
|
|
157
|
-
})
|
|
158
|
-
}) : null]
|
|
159
|
-
})
|
|
160
|
-
});
|
|
161
|
-
};
|
|
162
|
-
const MemoizedTabBarItemInternal = /*#__PURE__*/React.memo(TabBarItemInternal);
|
|
163
|
-
export function TabBarItem(props) {
|
|
164
|
-
const {
|
|
165
|
-
onPress,
|
|
166
|
-
onLongPress,
|
|
167
|
-
onLayout,
|
|
168
|
-
navigationState,
|
|
169
|
-
route,
|
|
170
|
-
...rest
|
|
171
|
-
} = props;
|
|
172
|
-
const onPressLatest = useLatestCallback(onPress);
|
|
173
|
-
const onLongPressLatest = useLatestCallback(onLongPress);
|
|
174
|
-
const onLayoutLatest = useLatestCallback(onLayout ? onLayout : () => {});
|
|
175
|
-
const tabIndex = navigationState.routes.indexOf(route);
|
|
176
|
-
return /*#__PURE__*/_jsx(MemoizedTabBarItemInternal, {
|
|
177
|
-
...rest,
|
|
178
|
-
onPress: onPressLatest,
|
|
179
|
-
onLayout: onLayoutLatest,
|
|
180
|
-
onLongPress: onLongPressLatest,
|
|
181
|
-
isFocused: navigationState.index === tabIndex,
|
|
182
|
-
route: route,
|
|
183
|
-
index: tabIndex,
|
|
184
|
-
routesLength: navigationState.routes.length
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
const styles = StyleSheet.create({
|
|
188
|
-
icon: {
|
|
189
|
-
margin: 2
|
|
190
|
-
},
|
|
191
|
-
item: {
|
|
192
|
-
flex: 1,
|
|
193
|
-
alignItems: 'center',
|
|
194
|
-
justifyContent: 'center',
|
|
195
|
-
padding: 10,
|
|
196
|
-
minHeight: 48
|
|
197
|
-
},
|
|
198
|
-
badge: {
|
|
199
|
-
position: 'absolute',
|
|
200
|
-
top: 0,
|
|
201
|
-
end: 0
|
|
202
|
-
},
|
|
203
|
-
pressable: {
|
|
204
|
-
// The label is not pressable on Windows
|
|
205
|
-
// Adding backgroundColor: 'transparent' seems to fix it
|
|
206
|
-
backgroundColor: 'transparent',
|
|
207
|
-
...Platform.select({
|
|
208
|
-
// Roundness for iPad hover effect
|
|
209
|
-
ios: {
|
|
210
|
-
borderRadius: 10,
|
|
211
|
-
borderCurve: 'continuous'
|
|
212
|
-
},
|
|
213
|
-
default: null
|
|
214
|
-
})
|
|
215
|
-
}
|
|
216
|
-
});
|
|
217
|
-
//# sourceMappingURL=TabBarItem.js.map
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
import React from 'react';
|
|
4
|
-
import { Animated, StyleSheet } from 'react-native';
|
|
5
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
6
|
-
export const TabBarItemLabel = /*#__PURE__*/React.memo(({
|
|
7
|
-
color,
|
|
8
|
-
label,
|
|
9
|
-
style,
|
|
10
|
-
icon
|
|
11
|
-
}) => {
|
|
12
|
-
if (!label) {
|
|
13
|
-
return null;
|
|
14
|
-
}
|
|
15
|
-
return /*#__PURE__*/_jsx(Animated.Text, {
|
|
16
|
-
style: [styles.label, icon ? {
|
|
17
|
-
marginTop: 0
|
|
18
|
-
} : null, style, {
|
|
19
|
-
color: color
|
|
20
|
-
}],
|
|
21
|
-
children: label
|
|
22
|
-
});
|
|
23
|
-
});
|
|
24
|
-
TabBarItemLabel.displayName = 'TabBarItemLabel';
|
|
25
|
-
const styles = StyleSheet.create({
|
|
26
|
-
label: {
|
|
27
|
-
margin: 4,
|
|
28
|
-
fontSize: 14,
|
|
29
|
-
fontWeight: '500',
|
|
30
|
-
backgroundColor: 'transparent'
|
|
31
|
-
}
|
|
32
|
-
});
|
|
33
|
-
//# sourceMappingURL=TabBarItemLabel.js.map
|
package/lib/module/TabView.js
DELETED
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
import * as React from 'react';
|
|
4
|
-
import { I18nManager, Platform, StyleSheet, View } from 'react-native';
|
|
5
|
-
import { Pager } from './Pager';
|
|
6
|
-
import { SceneView } from "./SceneView.js";
|
|
7
|
-
import { TabBar } from "./TabBar.js";
|
|
8
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
9
|
-
const renderLazyPlaceholderDefault = () => null;
|
|
10
|
-
export function TabView({
|
|
11
|
-
onIndexChange,
|
|
12
|
-
onTabSelect,
|
|
13
|
-
navigationState,
|
|
14
|
-
renderScene,
|
|
15
|
-
initialLayout,
|
|
16
|
-
keyboardDismissMode = 'auto',
|
|
17
|
-
lazy = false,
|
|
18
|
-
lazyPreloadDistance = 0,
|
|
19
|
-
onSwipeStart,
|
|
20
|
-
onSwipeEnd,
|
|
21
|
-
renderLazyPlaceholder = renderLazyPlaceholderDefault,
|
|
22
|
-
// eslint-disable-next-line @eslint-react/no-unstable-default-props
|
|
23
|
-
renderTabBar = props => /*#__PURE__*/_jsx(TabBar, {
|
|
24
|
-
...props
|
|
25
|
-
}),
|
|
26
|
-
pagerStyle,
|
|
27
|
-
style,
|
|
28
|
-
direction = I18nManager.getConstants().isRTL ? 'rtl' : 'ltr',
|
|
29
|
-
swipeEnabled = true,
|
|
30
|
-
tabBarPosition = 'top',
|
|
31
|
-
animationEnabled = true,
|
|
32
|
-
overScrollMode,
|
|
33
|
-
options: sceneOptions,
|
|
34
|
-
commonOptions
|
|
35
|
-
}) {
|
|
36
|
-
if (Platform.OS !== 'web' && direction !== (I18nManager.getConstants().isRTL ? 'rtl' : 'ltr')) {
|
|
37
|
-
console.warn(`The 'direction' prop is set to '${direction}' but the effective value is '${I18nManager.getConstants().isRTL ? 'rtl' : 'ltr'}'. This is not supported. Make sure to match the 'direction' prop with the writing direction of the app.`);
|
|
38
|
-
}
|
|
39
|
-
const [layout, setLayout] = React.useState({
|
|
40
|
-
width: 0,
|
|
41
|
-
height: 0,
|
|
42
|
-
...initialLayout
|
|
43
|
-
});
|
|
44
|
-
const jumpToIndex = index => {
|
|
45
|
-
if (index !== navigationState.index) {
|
|
46
|
-
onIndexChange(index);
|
|
47
|
-
}
|
|
48
|
-
};
|
|
49
|
-
const handleLayout = e => {
|
|
50
|
-
const {
|
|
51
|
-
height,
|
|
52
|
-
width
|
|
53
|
-
} = e.nativeEvent.layout;
|
|
54
|
-
setLayout(prevLayout => {
|
|
55
|
-
if (prevLayout.width === width && prevLayout.height === height) {
|
|
56
|
-
return prevLayout;
|
|
57
|
-
}
|
|
58
|
-
return {
|
|
59
|
-
height,
|
|
60
|
-
width
|
|
61
|
-
};
|
|
62
|
-
});
|
|
63
|
-
};
|
|
64
|
-
const options = Object.fromEntries(navigationState.routes.map(route => [route.key, {
|
|
65
|
-
...commonOptions,
|
|
66
|
-
...sceneOptions?.[route.key]
|
|
67
|
-
}]));
|
|
68
|
-
return /*#__PURE__*/_jsx(View, {
|
|
69
|
-
onLayout: handleLayout,
|
|
70
|
-
style: [styles.pager, style],
|
|
71
|
-
children: /*#__PURE__*/_jsx(Pager, {
|
|
72
|
-
layout: layout,
|
|
73
|
-
navigationState: navigationState,
|
|
74
|
-
keyboardDismissMode: keyboardDismissMode,
|
|
75
|
-
swipeEnabled: swipeEnabled,
|
|
76
|
-
onSwipeStart: onSwipeStart,
|
|
77
|
-
onSwipeEnd: onSwipeEnd,
|
|
78
|
-
onIndexChange: jumpToIndex,
|
|
79
|
-
onTabSelect: onTabSelect,
|
|
80
|
-
animationEnabled: animationEnabled,
|
|
81
|
-
overScrollMode: overScrollMode,
|
|
82
|
-
style: pagerStyle,
|
|
83
|
-
layoutDirection: direction,
|
|
84
|
-
children: ({
|
|
85
|
-
position,
|
|
86
|
-
render,
|
|
87
|
-
addEnterListener,
|
|
88
|
-
jumpTo
|
|
89
|
-
}) => {
|
|
90
|
-
// All the props here must not change between re-renders
|
|
91
|
-
// This is crucial to optimizing the routes with PureComponent
|
|
92
|
-
const sceneRendererProps = {
|
|
93
|
-
position,
|
|
94
|
-
layout,
|
|
95
|
-
jumpTo
|
|
96
|
-
};
|
|
97
|
-
return /*#__PURE__*/_jsxs(React.Fragment, {
|
|
98
|
-
children: [tabBarPosition === 'top' && renderTabBar({
|
|
99
|
-
...sceneRendererProps,
|
|
100
|
-
options,
|
|
101
|
-
navigationState
|
|
102
|
-
}), render(navigationState.routes.map((route, i) => {
|
|
103
|
-
const {
|
|
104
|
-
sceneStyle
|
|
105
|
-
} = options?.[route.key] ?? {};
|
|
106
|
-
return /*#__PURE__*/_jsx(SceneView, {
|
|
107
|
-
...sceneRendererProps,
|
|
108
|
-
addEnterListener: addEnterListener,
|
|
109
|
-
index: i,
|
|
110
|
-
lazy: typeof lazy === 'function' ? lazy({
|
|
111
|
-
route
|
|
112
|
-
}) : lazy,
|
|
113
|
-
lazyPreloadDistance: lazyPreloadDistance,
|
|
114
|
-
navigationState: navigationState,
|
|
115
|
-
style: sceneStyle,
|
|
116
|
-
children: ({
|
|
117
|
-
loading
|
|
118
|
-
}) => loading ? renderLazyPlaceholder({
|
|
119
|
-
route
|
|
120
|
-
}) : renderScene({
|
|
121
|
-
...sceneRendererProps,
|
|
122
|
-
route
|
|
123
|
-
})
|
|
124
|
-
}, route.key);
|
|
125
|
-
})), tabBarPosition === 'bottom' && renderTabBar({
|
|
126
|
-
...sceneRendererProps,
|
|
127
|
-
options,
|
|
128
|
-
navigationState
|
|
129
|
-
})]
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
})
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
const styles = StyleSheet.create({
|
|
136
|
-
pager: {
|
|
137
|
-
flex: 1,
|
|
138
|
-
overflow: 'hidden'
|
|
139
|
-
}
|
|
140
|
-
});
|
|
141
|
-
//# sourceMappingURL=TabView.js.map
|
package/lib/module/types.js
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
import * as React from 'react';
|
|
4
|
-
import { Animated } from 'react-native';
|
|
5
|
-
export function useAnimatedValue(initialValue) {
|
|
6
|
-
const lazyRef = React.useRef(undefined);
|
|
7
|
-
if (lazyRef.current === undefined) {
|
|
8
|
-
lazyRef.current = new Animated.Value(initialValue);
|
|
9
|
-
}
|
|
10
|
-
return lazyRef.current;
|
|
11
|
-
}
|
|
12
|
-
//# sourceMappingURL=useAnimatedValue.js.map
|
package/src/Pager.android.tsx
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { PagerViewAdapter as Pager } from './PagerViewAdapter';
|
package/src/Pager.ios.tsx
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { PagerViewAdapter as Pager } from './PagerViewAdapter';
|
package/src/Pager.tsx
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { PanResponderAdapter as Pager } from './PanResponderAdapter';
|
package/src/PagerViewAdapter.tsx
DELETED
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
import * as React from 'react';
|
|
2
|
-
import { Animated, Keyboard, Platform, StyleSheet } from 'react-native';
|
|
3
|
-
import ViewPager, {
|
|
4
|
-
type PageScrollStateChangedNativeEvent,
|
|
5
|
-
} from 'react-native-pager-view';
|
|
6
|
-
import useLatestCallback from 'use-latest-callback';
|
|
7
|
-
|
|
8
|
-
import type {
|
|
9
|
-
EventEmitterProps,
|
|
10
|
-
Listener,
|
|
11
|
-
NavigationState,
|
|
12
|
-
PagerProps,
|
|
13
|
-
Route,
|
|
14
|
-
} from './types';
|
|
15
|
-
import { useAnimatedValue } from './useAnimatedValue';
|
|
16
|
-
|
|
17
|
-
const AnimatedViewPager = Animated.createAnimatedComponent(ViewPager);
|
|
18
|
-
|
|
19
|
-
type Props<T extends Route> = PagerProps & {
|
|
20
|
-
onIndexChange: (index: number) => void;
|
|
21
|
-
onTabSelect?: (props: { index: number }) => void;
|
|
22
|
-
navigationState: NavigationState<T>;
|
|
23
|
-
children: (
|
|
24
|
-
props: EventEmitterProps & {
|
|
25
|
-
// Animated value which represents the state of current index
|
|
26
|
-
// It can include fractional digits as it represents the intermediate value
|
|
27
|
-
position: Animated.AnimatedInterpolation<number>;
|
|
28
|
-
// Function to actually render the content of the pager
|
|
29
|
-
// The parent component takes care of rendering
|
|
30
|
-
render: (children: React.ReactNode) => React.ReactNode;
|
|
31
|
-
// Callback to call when switching the tab
|
|
32
|
-
// The tab switch animation is performed even if the index in state is unchanged
|
|
33
|
-
jumpTo: (key: string) => void;
|
|
34
|
-
}
|
|
35
|
-
) => React.ReactElement;
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
const useNativeDriver = Platform.OS !== 'web';
|
|
39
|
-
|
|
40
|
-
export function PagerViewAdapter<T extends Route>({
|
|
41
|
-
keyboardDismissMode = 'auto',
|
|
42
|
-
swipeEnabled = true,
|
|
43
|
-
navigationState,
|
|
44
|
-
onIndexChange,
|
|
45
|
-
onTabSelect,
|
|
46
|
-
onSwipeStart,
|
|
47
|
-
onSwipeEnd,
|
|
48
|
-
children,
|
|
49
|
-
style,
|
|
50
|
-
animationEnabled,
|
|
51
|
-
...rest
|
|
52
|
-
}: Props<T>) {
|
|
53
|
-
const { index } = navigationState;
|
|
54
|
-
|
|
55
|
-
const listenersRef = React.useRef<Listener[]>([]);
|
|
56
|
-
|
|
57
|
-
const pagerRef = React.useRef<ViewPager>(null);
|
|
58
|
-
const indexRef = React.useRef<number>(index);
|
|
59
|
-
const navigationStateRef = React.useRef(navigationState);
|
|
60
|
-
|
|
61
|
-
const position = useAnimatedValue(index);
|
|
62
|
-
const offset = useAnimatedValue(0);
|
|
63
|
-
|
|
64
|
-
React.useEffect(() => {
|
|
65
|
-
navigationStateRef.current = navigationState;
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
const jumpTo = useLatestCallback((key: string) => {
|
|
69
|
-
const index = navigationStateRef.current.routes.findIndex(
|
|
70
|
-
(route: { key: string }) => route.key === key
|
|
71
|
-
);
|
|
72
|
-
|
|
73
|
-
if (animationEnabled) {
|
|
74
|
-
pagerRef.current?.setPage(index);
|
|
75
|
-
} else {
|
|
76
|
-
pagerRef.current?.setPageWithoutAnimation(index);
|
|
77
|
-
position.setValue(index);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
onIndexChange(index);
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
React.useEffect(() => {
|
|
84
|
-
if (keyboardDismissMode === 'auto') {
|
|
85
|
-
Keyboard.dismiss();
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
if (indexRef.current !== index) {
|
|
89
|
-
if (animationEnabled) {
|
|
90
|
-
pagerRef.current?.setPage(index);
|
|
91
|
-
} else {
|
|
92
|
-
pagerRef.current?.setPageWithoutAnimation(index);
|
|
93
|
-
position.setValue(index);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}, [keyboardDismissMode, index, animationEnabled, position]);
|
|
97
|
-
|
|
98
|
-
const onPageScrollStateChanged = (
|
|
99
|
-
state: PageScrollStateChangedNativeEvent
|
|
100
|
-
) => {
|
|
101
|
-
const { pageScrollState } = state.nativeEvent;
|
|
102
|
-
|
|
103
|
-
switch (pageScrollState) {
|
|
104
|
-
case 'idle':
|
|
105
|
-
onSwipeEnd?.();
|
|
106
|
-
return;
|
|
107
|
-
case 'dragging': {
|
|
108
|
-
const subscription = offset.addListener(({ value }) => {
|
|
109
|
-
const next =
|
|
110
|
-
index + (value > 0 ? Math.ceil(value) : Math.floor(value));
|
|
111
|
-
|
|
112
|
-
if (next !== index) {
|
|
113
|
-
listenersRef.current.forEach((listener) => listener(next));
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
offset.removeListener(subscription);
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
onSwipeStart?.();
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
const addEnterListener = useLatestCallback((listener: Listener) => {
|
|
126
|
-
listenersRef.current.push(listener);
|
|
127
|
-
|
|
128
|
-
return () => {
|
|
129
|
-
const index = listenersRef.current.indexOf(listener);
|
|
130
|
-
|
|
131
|
-
if (index > -1) {
|
|
132
|
-
listenersRef.current.splice(index, 1);
|
|
133
|
-
}
|
|
134
|
-
};
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
const memoizedPosition = React.useMemo(
|
|
138
|
-
() => Animated.add(position, offset),
|
|
139
|
-
[offset, position]
|
|
140
|
-
);
|
|
141
|
-
|
|
142
|
-
return children({
|
|
143
|
-
position: memoizedPosition,
|
|
144
|
-
addEnterListener,
|
|
145
|
-
jumpTo,
|
|
146
|
-
render: (children) => (
|
|
147
|
-
<AnimatedViewPager
|
|
148
|
-
{...rest}
|
|
149
|
-
ref={pagerRef}
|
|
150
|
-
style={[styles.container, style]}
|
|
151
|
-
initialPage={index}
|
|
152
|
-
keyboardDismissMode={
|
|
153
|
-
keyboardDismissMode === 'auto' ? 'on-drag' : keyboardDismissMode
|
|
154
|
-
}
|
|
155
|
-
onPageScroll={Animated.event(
|
|
156
|
-
[
|
|
157
|
-
{
|
|
158
|
-
nativeEvent: {
|
|
159
|
-
position: position,
|
|
160
|
-
offset: offset,
|
|
161
|
-
},
|
|
162
|
-
},
|
|
163
|
-
],
|
|
164
|
-
{ useNativeDriver }
|
|
165
|
-
)}
|
|
166
|
-
onPageSelected={(e) => {
|
|
167
|
-
const index = e.nativeEvent.position;
|
|
168
|
-
indexRef.current = index;
|
|
169
|
-
onIndexChange(index);
|
|
170
|
-
onTabSelect?.({ index });
|
|
171
|
-
}}
|
|
172
|
-
onPageScrollStateChanged={onPageScrollStateChanged}
|
|
173
|
-
scrollEnabled={swipeEnabled}
|
|
174
|
-
>
|
|
175
|
-
{children}
|
|
176
|
-
</AnimatedViewPager>
|
|
177
|
-
),
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
const styles = StyleSheet.create({
|
|
182
|
-
container: {
|
|
183
|
-
flex: 1,
|
|
184
|
-
},
|
|
185
|
-
});
|