mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-01-13 09:30:30 +08:00
Compare commits
42 Commits
@react-nav
...
@react-nav
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bd9f0ad5f6 | ||
|
|
c326c106f9 | ||
|
|
52451d1109 | ||
|
|
0945689b70 | ||
|
|
37b9454f3e | ||
|
|
fb7ac960c8 | ||
|
|
e8515f9cd9 | ||
|
|
5eee804e7f | ||
|
|
45dbe5c40e | ||
|
|
d26bcc057e | ||
|
|
836ca4482e | ||
|
|
fdd549a536 | ||
|
|
128bbbe62a | ||
|
|
a186b445b4 | ||
|
|
ac11a3bded | ||
|
|
55d635f53e | ||
|
|
95600500a4 | ||
|
|
6cf124a190 | ||
|
|
bfd0d94985 | ||
|
|
748e92f120 | ||
|
|
7f3b27a9ec | ||
|
|
f51086edea | ||
|
|
7196889bf1 | ||
|
|
7dc2f5832e | ||
|
|
8ec6c1a603 | ||
|
|
960f0a5281 | ||
|
|
da91cec941 | ||
|
|
38e17aae93 | ||
|
|
0f60b4617f | ||
|
|
f01bb4834b | ||
|
|
261a33a0d0 | ||
|
|
d6cac6713a | ||
|
|
8ee0dda155 | ||
|
|
8585f97226 | ||
|
|
23ab350492 | ||
|
|
80ff5a9c54 | ||
|
|
90ebfc40b3 | ||
|
|
091b2a2038 | ||
|
|
01f86d2ac6 | ||
|
|
c49dab31b2 | ||
|
|
16e7ac131f | ||
|
|
9e3650831c |
@@ -87,6 +87,9 @@ jobs:
|
||||
- run:
|
||||
name: Build packages in the monorepo
|
||||
command: yarn lerna run prepare
|
||||
- run:
|
||||
name: Verify built type definitions are correct
|
||||
command: yarn typescript
|
||||
- run:
|
||||
name: Verify paths for types
|
||||
command: node scripts/check-types-path.js
|
||||
|
||||
1
.github/workflows/expo.yml
vendored
1
.github/workflows/expo.yml
vendored
@@ -3,6 +3,7 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"expo": {
|
||||
"name": "React Navigation",
|
||||
"owner": "react-navigation",
|
||||
"slug": "NavigationPlayground",
|
||||
"slug": "react-navigation-example",
|
||||
"description": "Demonstrates the functionality and various capabilities of React Navigation.",
|
||||
"privacy": "public",
|
||||
"version": "1.0.0",
|
||||
|
||||
@@ -14,13 +14,14 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@expo/vector-icons": "^10.0.0",
|
||||
"@react-native-async-storage/async-storage": "^1.13.1",
|
||||
"@react-native-community/masked-view": "0.1.10",
|
||||
"color": "^3.1.2",
|
||||
"color": "^3.1.3",
|
||||
"expo": "^39.0.0",
|
||||
"expo-asset": "~8.2.0",
|
||||
"expo-blur": "~8.2.0",
|
||||
"expo-linking": "^1.0.4",
|
||||
"expo-updates": "~0.3.3",
|
||||
"expo-updates": "~0.3.5",
|
||||
"koa": "^2.13.0",
|
||||
"react": "~16.13.1",
|
||||
"react-dom": "~16.13.1",
|
||||
@@ -31,30 +32,30 @@
|
||||
"react-native-reanimated": "~1.13.0",
|
||||
"react-native-safe-area-context": "3.1.4",
|
||||
"react-native-screens": "~2.10.1",
|
||||
"react-native-tab-view": "^2.15.1",
|
||||
"react-native-tab-view": "^2.15.2",
|
||||
"react-native-vector-icons": "^7.0.0",
|
||||
"react-native-web": "^0.13.13"
|
||||
"react-native-web": "^0.13.16"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/node": "^7.10.1",
|
||||
"@expo/webpack-config": "^0.12.21",
|
||||
"@types/cheerio": "^0.22.21",
|
||||
"@babel/node": "^7.12.1",
|
||||
"@expo/webpack-config": "^0.12.40",
|
||||
"@types/cheerio": "^0.22.22",
|
||||
"@types/jest-dev-server": "^4.2.0",
|
||||
"@types/koa": "^2.11.4",
|
||||
"@types/koa": "^2.11.6",
|
||||
"@types/node-fetch": "^2.5.7",
|
||||
"@types/react": "~16.9.35",
|
||||
"@types/react": "~16.9.53",
|
||||
"@types/react-dom": "^16.9.8",
|
||||
"@types/react-native": "~0.63.2",
|
||||
"@types/react-native": "~0.63.30",
|
||||
"babel-loader": "^8.1.0",
|
||||
"babel-plugin-module-resolver": "^4.0.0",
|
||||
"babel-preset-expo": "^8.3.0",
|
||||
"cheerio": "^1.0.0-rc.3",
|
||||
"expo-cli": "^3.27.7",
|
||||
"jest": "^26.4.2",
|
||||
"expo-cli": "^3.28.2",
|
||||
"jest": "^26.6.1",
|
||||
"jest-dev-server": "^4.4.0",
|
||||
"mock-require-assets": "^0.0.1",
|
||||
"node-fetch": "^2.6.1",
|
||||
"nodemon": "^2.0.4",
|
||||
"nodemon": "^2.0.6",
|
||||
"playwright": "^0.14.0",
|
||||
"serve": "^11.3.0",
|
||||
"typescript": "^4.0.3"
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
import { AsyncStorage } from 'react-native';
|
||||
|
||||
export default AsyncStorage;
|
||||
@@ -1,14 +0,0 @@
|
||||
export default {
|
||||
getItem(key: string) {
|
||||
return Promise.resolve(localStorage.getItem(key));
|
||||
},
|
||||
setItem(key: string, value: string) {
|
||||
return Promise.resolve(localStorage.setItem(key, value));
|
||||
},
|
||||
removeItem(key: string) {
|
||||
return Promise.resolve(localStorage.removeItem(key));
|
||||
},
|
||||
clear() {
|
||||
return Promise.resolve(localStorage.clear());
|
||||
},
|
||||
};
|
||||
@@ -31,9 +31,11 @@ const AuthContext = React.createContext<{
|
||||
});
|
||||
|
||||
const SplashScreen = () => {
|
||||
const { colors } = useTheme();
|
||||
|
||||
return (
|
||||
<View style={styles.content}>
|
||||
<ActivityIndicator />
|
||||
<ActivityIndicator color={colors.primary} />
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -25,18 +25,9 @@ const LinkButton = ({
|
||||
to,
|
||||
...rest
|
||||
}: React.ComponentProps<typeof Button> & { to: string }) => {
|
||||
const { onPress, ...props } = useLinkProps({ to });
|
||||
const props = useLinkProps({ to });
|
||||
|
||||
return (
|
||||
<Button
|
||||
{...props}
|
||||
{...rest}
|
||||
{...Platform.select({
|
||||
web: { onClick: onPress } as any,
|
||||
default: { onPress },
|
||||
})}
|
||||
/>
|
||||
);
|
||||
return <Button {...props} {...rest} />;
|
||||
};
|
||||
|
||||
const ArticleScreen = ({
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import * as React from 'react';
|
||||
import {
|
||||
ScrollView,
|
||||
YellowBox,
|
||||
Platform,
|
||||
StatusBar,
|
||||
I18nManager,
|
||||
Dimensions,
|
||||
ScaledSize,
|
||||
Linking,
|
||||
LogBox,
|
||||
} from 'react-native';
|
||||
import { enableScreens } from 'react-native-screens';
|
||||
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
|
||||
@@ -38,9 +38,9 @@ import {
|
||||
HeaderStyleInterpolators,
|
||||
} from '@react-navigation/stack';
|
||||
import { useReduxDevToolsExtension } from '@react-navigation/devtools';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
|
||||
import { restartApp } from './Restart';
|
||||
import AsyncStorage from './AsyncStorage';
|
||||
import LinkingPrefixes from './LinkingPrefixes';
|
||||
import SettingsItem from './Shared/SettingsItem';
|
||||
import SimpleStack from './Screens/SimpleStack';
|
||||
@@ -58,7 +58,9 @@ import PreventRemove from './Screens/PreventRemove';
|
||||
import CompatAPI from './Screens/CompatAPI';
|
||||
import LinkComponent from './Screens/LinkComponent';
|
||||
|
||||
YellowBox.ignoreWarnings(['Require cycle:', 'Warning: Async Storage']);
|
||||
if (Platform.OS !== 'web') {
|
||||
LogBox.ignoreLogs(['Require cycle:']);
|
||||
}
|
||||
|
||||
enableScreens();
|
||||
|
||||
|
||||
10
package.json
10
package.json
@@ -26,14 +26,14 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/config-conventional": "^11.0.0",
|
||||
"@types/jest": "^26.0.14",
|
||||
"babel-jest": "^26.3.0",
|
||||
"codecov": "^3.7.2",
|
||||
"@types/jest": "^26.0.15",
|
||||
"babel-jest": "^26.6.1",
|
||||
"codecov": "^3.8.0",
|
||||
"commitlint": "^11.0.0",
|
||||
"eslint": "^7.9.0",
|
||||
"eslint": "^7.12.0",
|
||||
"eslint-config-satya164": "^3.1.8",
|
||||
"husky": "^4.3.0",
|
||||
"jest": "^26.4.2",
|
||||
"jest": "^26.6.1",
|
||||
"lerna": "^3.22.1",
|
||||
"metro-react-native-babel-preset": "^0.63.0",
|
||||
"prettier": "^2.1.2",
|
||||
|
||||
@@ -3,6 +3,55 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [5.10.3](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@5.10.2...@react-navigation/bottom-tabs@5.10.3) (2020-11-03)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.10.2](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@5.10.1...@react-navigation/bottom-tabs@5.10.2) (2020-10-30)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.10.1](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@5.10.0...@react-navigation/bottom-tabs@5.10.1) (2020-10-28)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.10.0](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@5.9.2...@react-navigation/bottom-tabs@5.10.0) (2020-10-24)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add optional screens per navigator ([#8805](https://github.com/react-navigation/react-navigation/issues/8805)) ([7196889](https://github.com/react-navigation/react-navigation/commit/7196889bf1218eb6a736d9475e33a909c2248c3b))
|
||||
* add sceneContainerStyle prop to bottom-tabs navigator ([#8947](https://github.com/react-navigation/react-navigation/issues/8947)) ([f01bb48](https://github.com/react-navigation/react-navigation/commit/f01bb4834b01e13ab9a6b220328349f77ca49428))
|
||||
* improve types for navigation state ([#8980](https://github.com/react-navigation/react-navigation/issues/8980)) ([7dc2f58](https://github.com/react-navigation/react-navigation/commit/7dc2f5832e371473f3263c01ab39824eb9e2057d))
|
||||
* update helper types to have navigator specific methods ([f51086e](https://github.com/react-navigation/react-navigation/commit/f51086edea42f2382dac8c6914aac8574132114b))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.9.2](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@5.9.1...@react-navigation/bottom-tabs@5.9.2) (2020-10-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* use route keys instead of index for lazy load ([c49dab3](https://github.com/react-navigation/react-navigation/commit/c49dab31b2c63a1735f0ed0a1936ecf7bbcd8b13))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.9.1](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@5.9.0...@react-navigation/bottom-tabs@5.9.1) (2020-09-28)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/bottom-tabs",
|
||||
"description": "Bottom tab navigator following iOS design guidelines",
|
||||
"version": "5.9.1",
|
||||
"version": "5.10.3",
|
||||
"keywords": [
|
||||
"react-native-component",
|
||||
"react-component",
|
||||
@@ -36,16 +36,16 @@
|
||||
"clean": "del lib"
|
||||
},
|
||||
"dependencies": {
|
||||
"color": "^3.1.2",
|
||||
"react-native-iphone-x-helper": "^1.2.1"
|
||||
"color": "^3.1.3",
|
||||
"react-native-iphone-x-helper": "^1.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.16.2",
|
||||
"@react-navigation/native": "^5.7.5",
|
||||
"@testing-library/react-native": "^7.0.2",
|
||||
"@react-navigation/native": "^5.8.3",
|
||||
"@testing-library/react-native": "^7.1.0",
|
||||
"@types/color": "^3.0.1",
|
||||
"@types/react": "^16.9.49",
|
||||
"@types/react-native": "^0.63.20",
|
||||
"@types/react": "^16.9.53",
|
||||
"@types/react-native": "^0.63.30",
|
||||
"del-cli": "^3.0.1",
|
||||
"react": "~16.13.1",
|
||||
"react-native": "~0.63.2",
|
||||
|
||||
@@ -6,6 +6,8 @@ import {
|
||||
TabRouter,
|
||||
TabRouterOptions,
|
||||
TabNavigationState,
|
||||
TabActionHelpers,
|
||||
ParamListBase,
|
||||
} from '@react-navigation/native';
|
||||
import BottomTabView from '../views/BottomTabView';
|
||||
import type {
|
||||
@@ -23,11 +25,13 @@ function BottomTabNavigator({
|
||||
backBehavior,
|
||||
children,
|
||||
screenOptions,
|
||||
sceneContainerStyle,
|
||||
...rest
|
||||
}: Props) {
|
||||
const { state, descriptors, navigation } = useNavigationBuilder<
|
||||
TabNavigationState,
|
||||
TabNavigationState<ParamListBase>,
|
||||
TabRouterOptions,
|
||||
TabActionHelpers<ParamListBase>,
|
||||
BottomTabNavigationOptions,
|
||||
BottomTabNavigationEventMap
|
||||
>(TabRouter, {
|
||||
@@ -43,12 +47,13 @@ function BottomTabNavigator({
|
||||
state={state}
|
||||
navigation={navigation}
|
||||
descriptors={descriptors}
|
||||
sceneContainerStyle={sceneContainerStyle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default createNavigatorFactory<
|
||||
TabNavigationState,
|
||||
TabNavigationState<ParamListBase>,
|
||||
BottomTabNavigationOptions,
|
||||
BottomTabNavigationEventMap,
|
||||
typeof BottomTabNavigator
|
||||
|
||||
@@ -33,7 +33,8 @@ export type LabelPosition = 'beside-icon' | 'below-icon';
|
||||
export type BottomTabNavigationHelpers = NavigationHelpers<
|
||||
ParamListBase,
|
||||
BottomTabNavigationEventMap
|
||||
>;
|
||||
> &
|
||||
TabActionHelpers<ParamListBase>;
|
||||
|
||||
export type BottomTabNavigationProp<
|
||||
ParamList extends ParamListBase,
|
||||
@@ -41,7 +42,7 @@ export type BottomTabNavigationProp<
|
||||
> = NavigationProp<
|
||||
ParamList,
|
||||
RouteName,
|
||||
TabNavigationState,
|
||||
TabNavigationState<ParamList>,
|
||||
BottomTabNavigationOptions,
|
||||
BottomTabNavigationEventMap
|
||||
> &
|
||||
@@ -148,7 +149,7 @@ export type BottomTabNavigationOptions = {
|
||||
export type BottomTabDescriptor = Descriptor<
|
||||
ParamListBase,
|
||||
string,
|
||||
TabNavigationState,
|
||||
TabNavigationState<ParamListBase>,
|
||||
BottomTabNavigationOptions
|
||||
>;
|
||||
|
||||
@@ -170,6 +171,16 @@ export type BottomTabNavigationConfig<T = BottomTabBarOptions> = {
|
||||
* Options for the tab bar which will be passed as props to the tab bar component.
|
||||
*/
|
||||
tabBarOptions?: T;
|
||||
/**
|
||||
* Whether inactive screens should be detached from the view hierarchy to save memory.
|
||||
* Make sure to call `enableScreens` from `react-native-screens` to make it work.
|
||||
* Defaults to `true`.
|
||||
*/
|
||||
detachInactiveScreens?: boolean;
|
||||
/**
|
||||
* Style object for the component wrapping the screen content.
|
||||
*/
|
||||
sceneContainerStyle?: StyleProp<ViewStyle>;
|
||||
};
|
||||
|
||||
export type BottomTabBarOptions = {
|
||||
@@ -240,7 +251,7 @@ export type BottomTabBarOptions = {
|
||||
};
|
||||
|
||||
export type BottomTabBarProps<T = BottomTabBarOptions> = T & {
|
||||
state: TabNavigationState;
|
||||
state: TabNavigationState<ParamListBase>;
|
||||
descriptors: BottomTabDescriptorMap;
|
||||
navigation: NavigationHelpers<ParamListBase, BottomTabNavigationEventMap>;
|
||||
};
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import * as React from 'react';
|
||||
import { View, StyleSheet } from 'react-native';
|
||||
import { View, StyleSheet, StyleProp, ViewStyle } from 'react-native';
|
||||
|
||||
import {
|
||||
NavigationHelpersContext,
|
||||
ParamListBase,
|
||||
TabNavigationState,
|
||||
useTheme,
|
||||
} from '@react-navigation/native';
|
||||
@@ -19,21 +20,23 @@ import type {
|
||||
} from '../types';
|
||||
|
||||
type Props = BottomTabNavigationConfig & {
|
||||
state: TabNavigationState;
|
||||
state: TabNavigationState<ParamListBase>;
|
||||
navigation: BottomTabNavigationHelpers;
|
||||
descriptors: BottomTabDescriptorMap;
|
||||
};
|
||||
|
||||
type State = {
|
||||
loaded: number[];
|
||||
loaded: string[];
|
||||
};
|
||||
|
||||
function SceneContent({
|
||||
isFocused,
|
||||
children,
|
||||
style,
|
||||
}: {
|
||||
isFocused: boolean;
|
||||
children: React.ReactNode;
|
||||
style?: StyleProp<ViewStyle>;
|
||||
}) {
|
||||
const { colors } = useTheme();
|
||||
|
||||
@@ -41,7 +44,7 @@ function SceneContent({
|
||||
<View
|
||||
accessibilityElementsHidden={!isFocused}
|
||||
importantForAccessibility={isFocused ? 'auto' : 'no-hide-descendants'}
|
||||
style={[styles.content, { backgroundColor: colors.background }]}
|
||||
style={[styles.content, { backgroundColor: colors.background }, style]}
|
||||
>
|
||||
{children}
|
||||
</View>
|
||||
@@ -54,18 +57,18 @@ export default class BottomTabView extends React.Component<Props, State> {
|
||||
};
|
||||
|
||||
static getDerivedStateFromProps(nextProps: Props, prevState: State) {
|
||||
const { index } = nextProps.state;
|
||||
const focusedRouteKey = nextProps.state.routes[nextProps.state.index].key;
|
||||
|
||||
return {
|
||||
// Set the current tab to be loaded if it was not loaded before
|
||||
loaded: prevState.loaded.includes(index)
|
||||
loaded: prevState.loaded.includes(focusedRouteKey)
|
||||
? prevState.loaded
|
||||
: [...prevState.loaded, index],
|
||||
: [...prevState.loaded, focusedRouteKey],
|
||||
};
|
||||
}
|
||||
|
||||
state = {
|
||||
loaded: [this.props.state.index],
|
||||
state: State = {
|
||||
loaded: [this.props.state.routes[this.props.state.index].key],
|
||||
};
|
||||
|
||||
private renderTabBar = () => {
|
||||
@@ -85,7 +88,14 @@ export default class BottomTabView extends React.Component<Props, State> {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { state, descriptors, navigation, lazy } = this.props;
|
||||
const {
|
||||
state,
|
||||
descriptors,
|
||||
navigation,
|
||||
lazy,
|
||||
detachInactiveScreens = true,
|
||||
sceneContainerStyle,
|
||||
} = this.props;
|
||||
const { routes } = state;
|
||||
const { loaded } = this.state;
|
||||
|
||||
@@ -93,7 +103,11 @@ export default class BottomTabView extends React.Component<Props, State> {
|
||||
<NavigationHelpersContext.Provider value={navigation}>
|
||||
<SafeAreaProviderCompat>
|
||||
<View style={styles.container}>
|
||||
<ScreenContainer style={styles.pages}>
|
||||
<ScreenContainer
|
||||
// @ts-ignore
|
||||
enabled={detachInactiveScreens}
|
||||
style={styles.pages}
|
||||
>
|
||||
{routes.map((route, index) => {
|
||||
const descriptor = descriptors[route.key];
|
||||
const { unmountOnBlur } = descriptor.options;
|
||||
@@ -103,7 +117,7 @@ export default class BottomTabView extends React.Component<Props, State> {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (lazy && !loaded.includes(index) && !isFocused) {
|
||||
if (lazy && !loaded.includes(route.key) && !isFocused) {
|
||||
// Don't render a screen if we've never navigated to it
|
||||
return null;
|
||||
}
|
||||
@@ -113,8 +127,12 @@ export default class BottomTabView extends React.Component<Props, State> {
|
||||
key={route.key}
|
||||
style={StyleSheet.absoluteFill}
|
||||
isVisible={isFocused}
|
||||
enabled={detachInactiveScreens}
|
||||
>
|
||||
<SceneContent isFocused={isFocused}>
|
||||
<SceneContent
|
||||
isFocused={isFocused}
|
||||
style={sceneContainerStyle}
|
||||
>
|
||||
{descriptor.render()}
|
||||
</SceneContent>
|
||||
</ResourceSavingScene>
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
import * as React from 'react';
|
||||
import { Platform, StyleSheet, View } from 'react-native';
|
||||
import { Screen, screensEnabled } from 'react-native-screens';
|
||||
import {
|
||||
Screen,
|
||||
screensEnabled,
|
||||
// @ts-ignore
|
||||
shouldUseActivityState,
|
||||
} from 'react-native-screens';
|
||||
|
||||
type Props = {
|
||||
isVisible: boolean;
|
||||
children: React.ReactNode;
|
||||
enabled: boolean;
|
||||
style?: any;
|
||||
};
|
||||
|
||||
@@ -16,8 +22,17 @@ export default class ResourceSavingScene extends React.Component<Props> {
|
||||
if (screensEnabled?.() && Platform.OS !== 'web') {
|
||||
const { isVisible, ...rest } = this.props;
|
||||
|
||||
// @ts-expect-error: stackPresentation is incorrectly marked as required
|
||||
return <Screen active={isVisible ? 1 : 0} {...rest} />;
|
||||
if (shouldUseActivityState) {
|
||||
return (
|
||||
// @ts-expect-error: there was an `active` prop and no `activityState` in older version and stackPresentation was required
|
||||
<Screen activityState={isVisible ? 2 : 0} {...rest} />
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
// @ts-expect-error: there was an `active` prop and no `activityState` in older version and stackPresentation was required
|
||||
<Screen active={isVisible ? 1 : 0} {...rest} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const { isVisible, children, style, ...rest } = this.props;
|
||||
|
||||
@@ -3,6 +3,50 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [5.3.3](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.3.2...@react-navigation/compat@5.3.3) (2020-11-03)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.3.2](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.3.1...@react-navigation/compat@5.3.2) (2020-10-30)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.3.1](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.3.0...@react-navigation/compat@5.3.1) (2020-10-28)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.3.0](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.2.8...@react-navigation/compat@5.3.0) (2020-10-24)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* improve types for navigation state ([#8980](https://github.com/react-navigation/react-navigation/issues/8980)) ([7dc2f58](https://github.com/react-navigation/react-navigation/commit/7dc2f5832e371473f3263c01ab39824eb9e2057d))
|
||||
* update helper types to have navigator specific methods ([f51086e](https://github.com/react-navigation/react-navigation/commit/f51086edea42f2382dac8c6914aac8574132114b))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.8](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.2.7...@react-navigation/compat@5.2.8) (2020-10-07)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.7](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.2.6...@react-navigation/compat@5.2.7) (2020-09-28)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/compat",
|
||||
"description": "Compatibility layer to write navigator definitions in static configuration format",
|
||||
"version": "5.2.7",
|
||||
"version": "5.3.3",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -32,8 +32,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.16.2",
|
||||
"@react-navigation/native": "^5.7.5",
|
||||
"@types/react": "^16.9.49",
|
||||
"@react-navigation/native": "^5.8.3",
|
||||
"@types/react": "^16.9.53",
|
||||
"react": "~16.13.1",
|
||||
"typescript": "^4.0.3"
|
||||
},
|
||||
|
||||
@@ -19,7 +19,11 @@ type EventName =
|
||||
export default function createCompatNavigationProp<
|
||||
NavigationPropType extends NavigationProp<ParamListBase>,
|
||||
ParamList extends ParamListBase = NavigationPropType extends NavigationProp<
|
||||
infer P
|
||||
infer P,
|
||||
any,
|
||||
any,
|
||||
any,
|
||||
any
|
||||
>
|
||||
? P
|
||||
: ParamListBase
|
||||
|
||||
@@ -32,7 +32,11 @@ export default function createCompatNavigatorFactory<
|
||||
const createCompatNavigator = <
|
||||
NavigationPropType extends NavigationProp<any, any, any, any, any>,
|
||||
ParamList extends ParamListBase = NavigationPropType extends NavigationProp<
|
||||
infer P
|
||||
infer P,
|
||||
any,
|
||||
any,
|
||||
any,
|
||||
any
|
||||
>
|
||||
? P
|
||||
: ParamListBase,
|
||||
|
||||
@@ -5,6 +5,8 @@ import {
|
||||
TabRouter,
|
||||
TabRouterOptions,
|
||||
TabNavigationState,
|
||||
TabActionHelpers,
|
||||
ParamListBase,
|
||||
} from '@react-navigation/native';
|
||||
import createCompatNavigatorFactory from './createCompatNavigatorFactory';
|
||||
|
||||
@@ -12,17 +14,21 @@ type Props = DefaultNavigatorOptions<{}> & TabRouterOptions;
|
||||
|
||||
function SwitchNavigator(props: Props) {
|
||||
const { state, descriptors } = useNavigationBuilder<
|
||||
TabNavigationState,
|
||||
TabNavigationState<ParamListBase>,
|
||||
TabRouterOptions,
|
||||
{},
|
||||
{}
|
||||
{},
|
||||
TabActionHelpers<ParamListBase>
|
||||
>(TabRouter, props);
|
||||
|
||||
return descriptors[state.routes[state.index].key].render();
|
||||
}
|
||||
|
||||
export default createCompatNavigatorFactory(
|
||||
createNavigatorFactory<TabNavigationState, {}, {}, typeof SwitchNavigator>(
|
||||
SwitchNavigator
|
||||
)
|
||||
createNavigatorFactory<
|
||||
TabNavigationState<ParamListBase>,
|
||||
{},
|
||||
{},
|
||||
typeof SwitchNavigator
|
||||
>(SwitchNavigator)
|
||||
);
|
||||
|
||||
@@ -8,7 +8,11 @@ import type * as helpers from './helpers';
|
||||
export type CompatNavigationProp<
|
||||
NavigationPropType extends NavigationProp<ParamListBase>,
|
||||
ParamList extends ParamListBase = NavigationPropType extends NavigationProp<
|
||||
infer P
|
||||
infer P,
|
||||
any,
|
||||
any,
|
||||
any,
|
||||
any
|
||||
>
|
||||
? P
|
||||
: ParamListBase,
|
||||
@@ -67,7 +71,11 @@ export type CompatScreenType<
|
||||
export type CompatRouteConfig<
|
||||
NavigationPropType extends NavigationProp<ParamListBase>,
|
||||
ParamList extends ParamListBase = NavigationPropType extends NavigationProp<
|
||||
infer P
|
||||
infer P,
|
||||
any,
|
||||
any,
|
||||
any,
|
||||
any
|
||||
>
|
||||
? P
|
||||
: ParamListBase
|
||||
|
||||
@@ -3,6 +3,67 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [5.13.3](https://github.com/react-navigation/react-navigation/compare/@react-navigation/core@5.13.2...@react-navigation/core@5.13.3) (2020-11-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* handle navigating to same screen again for nested screens ([0945689](https://github.com/react-navigation/react-navigation/commit/0945689b70d71a4b5d766c61d57009761c460bf6))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.13.2](https://github.com/react-navigation/react-navigation/compare/@react-navigation/core@5.13.1...@react-navigation/core@5.13.2) (2020-10-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix params from for the root screen when creating action ([e8515f9](https://github.com/react-navigation/react-navigation/commit/e8515f9cd94a912c107a407dea3d953c4172393f)), closes [#9006](https://github.com/react-navigation/react-navigation/issues/9006)
|
||||
* trim routes if an index is specified in state ([fb7ac96](https://github.com/react-navigation/react-navigation/commit/fb7ac960c8e1ffca200ecb12696ce5531a139e50))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.13.1](https://github.com/react-navigation/react-navigation/compare/@react-navigation/core@5.13.0...@react-navigation/core@5.13.1) (2020-10-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* improve types for route prop in screenOptions ([d26bcc0](https://github.com/react-navigation/react-navigation/commit/d26bcc057ef31f8950f909adf83e263171a42d74))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.13.0](https://github.com/react-navigation/react-navigation/compare/@react-navigation/core@5.12.5...@react-navigation/core@5.13.0) (2020-10-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix imports from query-string. closes [#8971](https://github.com/react-navigation/react-navigation/issues/8971) ([#8976](https://github.com/react-navigation/react-navigation/issues/8976)) ([261a33a](https://github.com/react-navigation/react-navigation/commit/261a33a0d03150c87b06f01aeace4926b1c03eb6))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add an unhandled action listener ([#8895](https://github.com/react-navigation/react-navigation/issues/8895)) ([80ff5a9](https://github.com/react-navigation/react-navigation/commit/80ff5a9c543a44fa2fd7ba7fda0598f1b0d52a64))
|
||||
* allow deep linking to reset state ([#8973](https://github.com/react-navigation/react-navigation/issues/8973)) ([7f3b27a](https://github.com/react-navigation/react-navigation/commit/7f3b27a9ec8edd9604ac19774baa1f60963ccdc9)), closes [#8952](https://github.com/react-navigation/react-navigation/issues/8952)
|
||||
* improve types for navigation state ([#8980](https://github.com/react-navigation/react-navigation/issues/8980)) ([7dc2f58](https://github.com/react-navigation/react-navigation/commit/7dc2f5832e371473f3263c01ab39824eb9e2057d))
|
||||
* update helper types to have navigator specific methods ([f51086e](https://github.com/react-navigation/react-navigation/commit/f51086edea42f2382dac8c6914aac8574132114b))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.12.5](https://github.com/react-navigation/react-navigation/compare/@react-navigation/core@5.12.4...@react-navigation/core@5.12.5) (2020-10-07)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.12.4](https://github.com/react-navigation/react-navigation/compare/@react-navigation/core@5.12.3...@react-navigation/core@5.12.4) (2020-09-22)
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/core",
|
||||
"description": "Core utilities for building navigators",
|
||||
"version": "5.12.4",
|
||||
"version": "5.13.3",
|
||||
"keywords": [
|
||||
"react",
|
||||
"react-native",
|
||||
@@ -35,17 +35,17 @@
|
||||
"clean": "del lib"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-navigation/routers": "^5.4.12",
|
||||
"@react-navigation/routers": "^5.5.1",
|
||||
"escape-string-regexp": "^4.0.0",
|
||||
"nanoid": "^3.1.12",
|
||||
"query-string": "^6.13.1",
|
||||
"nanoid": "^3.1.15",
|
||||
"query-string": "^6.13.6",
|
||||
"react-is": "^16.13.0",
|
||||
"use-subscription": "^1.4.0"
|
||||
"use-subscription": "^1.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.16.2",
|
||||
"@testing-library/react-native": "^7.0.2",
|
||||
"@types/react": "^16.9.49",
|
||||
"@testing-library/react-native": "^7.1.0",
|
||||
"@types/react": "^16.9.53",
|
||||
"@types/react-is": "^16.7.1",
|
||||
"@types/use-subscription": "^1.0.0",
|
||||
"del-cli": "^3.0.1",
|
||||
|
||||
@@ -94,6 +94,7 @@ const BaseNavigationContainer = React.forwardRef(
|
||||
{
|
||||
initialState,
|
||||
onStateChange,
|
||||
onUnhandledAction,
|
||||
independent,
|
||||
children,
|
||||
}: NavigationContainerProps,
|
||||
@@ -342,51 +343,56 @@ const BaseNavigationContainer = React.forwardRef(
|
||||
isFirstMountRef.current = false;
|
||||
}, [getRootState, emitter, state]);
|
||||
|
||||
const onUnhandledAction = React.useCallback((action: NavigationAction) => {
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
return;
|
||||
}
|
||||
const defaultOnUnhandledAction = React.useCallback(
|
||||
(action: NavigationAction) => {
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
return;
|
||||
}
|
||||
|
||||
const payload: Record<string, any> | undefined = action.payload;
|
||||
const payload: Record<string, any> | undefined = action.payload;
|
||||
|
||||
let message = `The action '${action.type}'${
|
||||
payload ? ` with payload ${JSON.stringify(action.payload)}` : ''
|
||||
} was not handled by any navigator.`;
|
||||
let message = `The action '${action.type}'${
|
||||
payload ? ` with payload ${JSON.stringify(action.payload)}` : ''
|
||||
} was not handled by any navigator.`;
|
||||
|
||||
switch (action.type) {
|
||||
case 'NAVIGATE':
|
||||
case 'PUSH':
|
||||
case 'REPLACE':
|
||||
case 'JUMP_TO':
|
||||
if (payload?.name) {
|
||||
message += `\n\nDo you have a screen named '${payload.name}'?\n\nIf you're trying to navigate to a screen in a nested navigator, see https://reactnavigation.org/docs/nesting-navigators#navigating-to-a-screen-in-a-nested-navigator.`;
|
||||
} else {
|
||||
message += `\n\nYou need to pass the name of the screen to navigate to.\n\nSee https://reactnavigation.org/docs/navigation-actions for usage.`;
|
||||
}
|
||||
switch (action.type) {
|
||||
case 'NAVIGATE':
|
||||
case 'PUSH':
|
||||
case 'REPLACE':
|
||||
case 'JUMP_TO':
|
||||
if (payload?.name) {
|
||||
message += `\n\nDo you have a screen named '${payload.name}'?\n\nIf you're trying to navigate to a screen in a nested navigator, see https://reactnavigation.org/docs/nesting-navigators#navigating-to-a-screen-in-a-nested-navigator.`;
|
||||
} else {
|
||||
message += `\n\nYou need to pass the name of the screen to navigate to.\n\nSee https://reactnavigation.org/docs/navigation-actions for usage.`;
|
||||
}
|
||||
|
||||
break;
|
||||
case 'GO_BACK':
|
||||
case 'POP':
|
||||
case 'POP_TO_TOP':
|
||||
message += `\n\nIs there any screen to go back to?`;
|
||||
break;
|
||||
case 'OPEN_DRAWER':
|
||||
case 'CLOSE_DRAWER':
|
||||
case 'TOGGLE_DRAWER':
|
||||
message += `\n\nIs your screen inside a Drawer navigator?`;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 'GO_BACK':
|
||||
case 'POP':
|
||||
case 'POP_TO_TOP':
|
||||
message += `\n\nIs there any screen to go back to?`;
|
||||
break;
|
||||
case 'OPEN_DRAWER':
|
||||
case 'CLOSE_DRAWER':
|
||||
case 'TOGGLE_DRAWER':
|
||||
message += `\n\nIs your screen inside a Drawer navigator?`;
|
||||
break;
|
||||
}
|
||||
|
||||
message += `\n\nThis is a development-only warning and won't be shown in production.`;
|
||||
message += `\n\nThis is a development-only warning and won't be shown in production.`;
|
||||
|
||||
console.error(message);
|
||||
}, []);
|
||||
console.error(message);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<ScheduleUpdateContext.Provider value={scheduleContext}>
|
||||
<NavigationBuilderContext.Provider value={builderContext}>
|
||||
<NavigationStateContext.Provider value={context}>
|
||||
<UnhandledActionContext.Provider value={onUnhandledAction}>
|
||||
<UnhandledActionContext.Provider
|
||||
value={onUnhandledAction ?? defaultOnUnhandledAction}
|
||||
>
|
||||
<EnsureSingleNavigator>{children}</EnsureSingleNavigator>
|
||||
</UnhandledActionContext.Provider>
|
||||
</NavigationStateContext.Provider>
|
||||
|
||||
@@ -721,3 +721,39 @@ it("throws if the ref hasn't finished initializing", () => {
|
||||
|
||||
render(element);
|
||||
});
|
||||
|
||||
it('invokes the unhandled action listener with the unhandled action', () => {
|
||||
const ref = React.createRef<NavigationContainerRef>();
|
||||
const fn = jest.fn();
|
||||
|
||||
const TestNavigator = (props: any) => {
|
||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{state.routes.map((route) => descriptors[route.key].render())}
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const TestScreen = () => <></>;
|
||||
|
||||
render(
|
||||
<BaseNavigationContainer ref={ref} onUnhandledAction={fn}>
|
||||
<TestNavigator>
|
||||
<Screen name="foo" component={TestScreen} />
|
||||
<Screen name="bar" component={TestScreen} />
|
||||
</TestNavigator>
|
||||
</BaseNavigationContainer>
|
||||
);
|
||||
|
||||
act(() => ref.current!.navigate('bar'));
|
||||
act(() => ref.current!.navigate('baz'));
|
||||
|
||||
expect(fn).toHaveBeenCalledWith({
|
||||
payload: {
|
||||
name: 'baz',
|
||||
},
|
||||
type: 'NAVIGATE',
|
||||
});
|
||||
});
|
||||
|
||||
@@ -132,6 +132,17 @@ export default function MockRouter(options: DefaultRouterOptions) {
|
||||
};
|
||||
}
|
||||
|
||||
case 'GO_BACK': {
|
||||
if (state.index === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
index: state.index - 1,
|
||||
};
|
||||
}
|
||||
|
||||
default:
|
||||
return BaseRouter.getStateForAction(state, action);
|
||||
}
|
||||
|
||||
@@ -43,32 +43,435 @@ it('gets navigate action from state', () => {
|
||||
},
|
||||
type: 'NAVIGATE',
|
||||
});
|
||||
});
|
||||
|
||||
expect(
|
||||
getActionFromState({
|
||||
it('gets navigate action from state for top-level screen', () => {
|
||||
const state = {
|
||||
routes: [
|
||||
{
|
||||
name: 'foo',
|
||||
params: { answer: 42 },
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(getActionFromState(state)).toEqual({
|
||||
payload: {
|
||||
name: 'foo',
|
||||
params: { answer: 42 },
|
||||
},
|
||||
type: 'NAVIGATE',
|
||||
});
|
||||
});
|
||||
|
||||
it('gets navigate action from state for top-level screen with 2 screens', () => {
|
||||
const state = {
|
||||
routes: [
|
||||
{
|
||||
name: 'foo',
|
||||
params: { answer: 42 },
|
||||
},
|
||||
{
|
||||
name: 'bar',
|
||||
params: { author: 'jane' },
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(getActionFromState(state)).toEqual({
|
||||
payload: {
|
||||
routes: [
|
||||
{
|
||||
name: 'foo',
|
||||
params: { answer: 42 },
|
||||
},
|
||||
{
|
||||
name: 'bar',
|
||||
params: { author: 'jane' },
|
||||
},
|
||||
],
|
||||
},
|
||||
type: 'RESET',
|
||||
});
|
||||
});
|
||||
|
||||
it('gets navigate action from state for top-level screen with 2 screens with config', () => {
|
||||
const state = {
|
||||
routes: [
|
||||
{
|
||||
name: 'foo',
|
||||
params: { answer: 42 },
|
||||
},
|
||||
{
|
||||
name: 'bar',
|
||||
params: { author: 'jane' },
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const config = {
|
||||
initialRouteName: 'foo',
|
||||
screens: {
|
||||
bar: 'bar',
|
||||
},
|
||||
};
|
||||
|
||||
expect(getActionFromState(state, config)).toEqual({
|
||||
payload: {
|
||||
name: 'bar',
|
||||
params: { author: 'jane' },
|
||||
},
|
||||
type: 'NAVIGATE',
|
||||
});
|
||||
});
|
||||
|
||||
it('gets navigate action from state for top-level screen with more than 2 screens with config', () => {
|
||||
const state = {
|
||||
routes: [
|
||||
{
|
||||
name: 'foo',
|
||||
params: { answer: 42 },
|
||||
},
|
||||
{
|
||||
name: 'bar',
|
||||
params: { author: 'jane' },
|
||||
},
|
||||
{ name: 'baz' },
|
||||
],
|
||||
};
|
||||
|
||||
const config = {
|
||||
initialRouteName: 'foo',
|
||||
screens: {
|
||||
bar: 'bar',
|
||||
},
|
||||
};
|
||||
|
||||
expect(getActionFromState(state, config)).toEqual({
|
||||
payload: {
|
||||
routes: [
|
||||
{
|
||||
name: 'foo',
|
||||
params: { answer: 42 },
|
||||
},
|
||||
{
|
||||
name: 'bar',
|
||||
params: { author: 'jane' },
|
||||
},
|
||||
{ name: 'baz' },
|
||||
],
|
||||
},
|
||||
type: 'RESET',
|
||||
});
|
||||
});
|
||||
|
||||
it('gets navigate action from state for top-level screen with more than 2 screens with config with lower index', () => {
|
||||
const state = {
|
||||
index: 1,
|
||||
routes: [
|
||||
{
|
||||
name: 'foo',
|
||||
params: { answer: 42 },
|
||||
},
|
||||
{
|
||||
name: 'bar',
|
||||
params: { author: 'jane' },
|
||||
},
|
||||
{ name: 'baz' },
|
||||
],
|
||||
};
|
||||
|
||||
const config = {
|
||||
initialRouteName: 'foo',
|
||||
screens: {
|
||||
bar: 'bar',
|
||||
},
|
||||
};
|
||||
|
||||
expect(getActionFromState(state, config)).toEqual({
|
||||
payload: {
|
||||
name: 'bar',
|
||||
params: { author: 'jane' },
|
||||
},
|
||||
type: 'NAVIGATE',
|
||||
});
|
||||
});
|
||||
|
||||
it('gets navigate action from state with 2 screens', () => {
|
||||
const state = {
|
||||
routes: [
|
||||
{
|
||||
name: 'foo',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'bar',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'qux',
|
||||
params: { author: 'jane' },
|
||||
},
|
||||
{ name: 'quz' },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(getActionFromState(state)).toEqual({
|
||||
payload: {
|
||||
name: 'foo',
|
||||
params: {
|
||||
screen: 'bar',
|
||||
initial: true,
|
||||
params: {
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'bar',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'qux',
|
||||
params: { author: 'jane' },
|
||||
},
|
||||
{ name: 'quz' },
|
||||
],
|
||||
name: 'qux',
|
||||
params: {
|
||||
author: 'jane',
|
||||
},
|
||||
},
|
||||
{ name: 'quz' },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
).toEqual({
|
||||
},
|
||||
},
|
||||
type: 'NAVIGATE',
|
||||
});
|
||||
});
|
||||
|
||||
it('gets navigate action from state with 2 screens with lower index', () => {
|
||||
const state = {
|
||||
routes: [
|
||||
{
|
||||
name: 'foo',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'bar',
|
||||
state: {
|
||||
index: 0,
|
||||
routes: [
|
||||
{
|
||||
name: 'qux',
|
||||
params: { author: 'jane' },
|
||||
},
|
||||
{ name: 'quz' },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(getActionFromState(state)).toEqual({
|
||||
payload: {
|
||||
name: 'foo',
|
||||
params: {
|
||||
screen: 'bar',
|
||||
initial: true,
|
||||
params: {
|
||||
screen: 'qux',
|
||||
initial: true,
|
||||
params: {
|
||||
author: 'jane',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
type: 'NAVIGATE',
|
||||
});
|
||||
});
|
||||
|
||||
it('gets navigate action from state with more than 2 screens', () => {
|
||||
const state = {
|
||||
routes: [
|
||||
{
|
||||
name: 'foo',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'bar',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'qux',
|
||||
params: { author: 'jane' },
|
||||
},
|
||||
{ name: 'quz' },
|
||||
{ name: 'qua' },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(getActionFromState(state)).toEqual({
|
||||
payload: {
|
||||
name: 'foo',
|
||||
params: {
|
||||
screen: 'bar',
|
||||
initial: true,
|
||||
params: {
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'qux',
|
||||
params: {
|
||||
author: 'jane',
|
||||
},
|
||||
},
|
||||
{ name: 'quz' },
|
||||
{ name: 'qua' },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
type: 'NAVIGATE',
|
||||
});
|
||||
});
|
||||
|
||||
it('gets navigate action from state with config', () => {
|
||||
const state = {
|
||||
routes: [
|
||||
{
|
||||
name: 'foo',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'bar',
|
||||
params: { answer: 42 },
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'qux',
|
||||
params: { author: 'jane' },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const config = {
|
||||
screens: {
|
||||
foo: {
|
||||
initialRouteName: 'bar',
|
||||
screens: {
|
||||
bar: {
|
||||
initialRouteName: 'qux',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(getActionFromState(state, config)).toEqual({
|
||||
payload: {
|
||||
name: 'foo',
|
||||
params: {
|
||||
params: {
|
||||
answer: 42,
|
||||
params: {
|
||||
author: 'jane',
|
||||
},
|
||||
screen: 'qux',
|
||||
initial: true,
|
||||
},
|
||||
screen: 'bar',
|
||||
initial: true,
|
||||
},
|
||||
},
|
||||
type: 'NAVIGATE',
|
||||
});
|
||||
});
|
||||
|
||||
it('gets navigate action from state for top-level screen with config', () => {
|
||||
const state = {
|
||||
routes: [
|
||||
{
|
||||
name: 'foo',
|
||||
params: { answer: 42 },
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const config = {
|
||||
screens: {
|
||||
initialRouteName: 'bar',
|
||||
foo: {
|
||||
path: 'some-path/:answer',
|
||||
parse: {
|
||||
answer: Number,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(getActionFromState(state, config)).toEqual({
|
||||
payload: {
|
||||
name: 'foo',
|
||||
params: { answer: 42 },
|
||||
},
|
||||
type: 'NAVIGATE',
|
||||
});
|
||||
});
|
||||
|
||||
it('gets navigate action from state with 2 screens including initial route and with config', () => {
|
||||
const state = {
|
||||
routes: [
|
||||
{
|
||||
name: 'foo',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'bar',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'qux',
|
||||
params: { author: 'jane' },
|
||||
},
|
||||
{ name: 'quz' },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const config = {
|
||||
screens: {
|
||||
foo: {
|
||||
initialRouteName: 'bar',
|
||||
screens: {
|
||||
bar: {
|
||||
initialRouteName: 'qux',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(getActionFromState(state, config)).toEqual({
|
||||
payload: {
|
||||
name: 'foo',
|
||||
params: {
|
||||
@@ -84,6 +487,262 @@ it('gets navigate action from state', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('gets navigate action from state with 2 screens without initial route and with config', () => {
|
||||
const state = {
|
||||
routes: [
|
||||
{
|
||||
name: 'foo',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'bar',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'qux',
|
||||
params: { author: 'jane' },
|
||||
},
|
||||
{ name: 'quz' },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const config = {
|
||||
screens: {
|
||||
foo: {
|
||||
initialRouteName: 'bar',
|
||||
screens: {
|
||||
bar: {
|
||||
initialRouteName: 'quz',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(getActionFromState(state, config)).toEqual({
|
||||
payload: {
|
||||
name: 'foo',
|
||||
params: {
|
||||
initial: true,
|
||||
screen: 'bar',
|
||||
params: {
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'qux',
|
||||
params: {
|
||||
author: 'jane',
|
||||
},
|
||||
},
|
||||
{ name: 'quz' },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
type: 'NAVIGATE',
|
||||
});
|
||||
});
|
||||
|
||||
it('gets navigate action from state with 2 screens including route with key and with config', () => {
|
||||
const state = {
|
||||
routes: [
|
||||
{
|
||||
name: 'foo',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'bar',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
key: 'test',
|
||||
name: 'qux',
|
||||
params: { author: 'jane' },
|
||||
},
|
||||
{ name: 'quz' },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const config = {
|
||||
screens: {
|
||||
foo: {
|
||||
initialRouteName: 'bar',
|
||||
screens: {
|
||||
bar: {
|
||||
initialRouteName: 'qux',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(getActionFromState(state, config)).toEqual({
|
||||
payload: {
|
||||
name: 'foo',
|
||||
params: {
|
||||
initial: true,
|
||||
screen: 'bar',
|
||||
params: {
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
key: 'test',
|
||||
name: 'qux',
|
||||
params: {
|
||||
author: 'jane',
|
||||
},
|
||||
},
|
||||
{ name: 'quz' },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
type: 'NAVIGATE',
|
||||
});
|
||||
});
|
||||
|
||||
it('gets navigate action from state with more than 2 screens and with config', () => {
|
||||
const state = {
|
||||
routes: [
|
||||
{
|
||||
name: 'foo',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'bar',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'qux',
|
||||
params: { author: 'jane' },
|
||||
},
|
||||
{ name: 'quz' },
|
||||
{ name: 'qua' },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const config = {
|
||||
screens: {
|
||||
foo: {
|
||||
initialRouteName: 'bar',
|
||||
screens: {
|
||||
bar: {
|
||||
initialRouteName: 'qux',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(getActionFromState(state, config)).toEqual({
|
||||
payload: {
|
||||
name: 'foo',
|
||||
params: {
|
||||
initial: true,
|
||||
screen: 'bar',
|
||||
params: {
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'qux',
|
||||
params: {
|
||||
author: 'jane',
|
||||
},
|
||||
},
|
||||
{ name: 'quz' },
|
||||
{ name: 'qua' },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
type: 'NAVIGATE',
|
||||
});
|
||||
});
|
||||
|
||||
it('gets navigate action from state with more than 2 screens with lower index', () => {
|
||||
const state = {
|
||||
routes: [
|
||||
{
|
||||
name: 'foo',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'bar',
|
||||
state: {
|
||||
index: 1,
|
||||
routes: [
|
||||
{ name: 'quu' },
|
||||
{
|
||||
name: 'qux',
|
||||
params: { author: 'jane' },
|
||||
},
|
||||
{ name: 'quz' },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const config = {
|
||||
screens: {
|
||||
foo: {
|
||||
initialRouteName: 'bar',
|
||||
screens: {
|
||||
bar: {
|
||||
initialRouteName: 'quu',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(getActionFromState(state, config)).toEqual({
|
||||
payload: {
|
||||
name: 'foo',
|
||||
params: {
|
||||
screen: 'bar',
|
||||
initial: true,
|
||||
params: {
|
||||
screen: 'qux',
|
||||
initial: false,
|
||||
params: {
|
||||
author: 'jane',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
type: 'NAVIGATE',
|
||||
});
|
||||
});
|
||||
|
||||
it("doesn't return action if no routes are provided'", () => {
|
||||
expect(getActionFromState({ routes: [] })).toBe(undefined);
|
||||
});
|
||||
|
||||
it('gets reset action from state', () => {
|
||||
const state = {
|
||||
routes: [
|
||||
|
||||
@@ -735,6 +735,20 @@ it('navigates to nested child in a navigator', () => {
|
||||
expect(element).toMatchInlineSnapshot(
|
||||
`"[bar-a, {\\"lol\\":\\"why\\",\\"whoa\\":\\"test\\"}]"`
|
||||
);
|
||||
|
||||
act(() => navigation.current?.navigate('bar', { screen: 'bar-b' }));
|
||||
|
||||
act(() => navigation.current?.goBack());
|
||||
|
||||
expect(element).toMatchInlineSnapshot(
|
||||
`"[bar-a, {\\"lol\\":\\"why\\",\\"whoa\\":\\"test\\"}]"`
|
||||
);
|
||||
|
||||
act(() => navigation.current?.navigate('bar', { screen: 'bar-b' }));
|
||||
|
||||
expect(element).toMatchInlineSnapshot(
|
||||
`"[bar-b, {\\"some\\":\\"stuff\\",\\"test\\":42}]"`
|
||||
);
|
||||
});
|
||||
|
||||
it('navigates to nested child in a navigator with initial: false', () => {
|
||||
@@ -1093,6 +1107,194 @@ it('navigates to nested child in a navigator with initial: false', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('resets state of a nested child in a navigator', () => {
|
||||
const TestNavigator = (props: any): any => {
|
||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||
|
||||
return descriptors[state.routes[state.index].key].render();
|
||||
};
|
||||
|
||||
const TestComponent = ({ route }: any): any =>
|
||||
`[${route.name}, ${JSON.stringify(route.params)}]`;
|
||||
|
||||
const onStateChange = jest.fn();
|
||||
|
||||
const navigation = React.createRef<NavigationContainerRef>();
|
||||
|
||||
const first = render(
|
||||
<BaseNavigationContainer ref={navigation} onStateChange={onStateChange}>
|
||||
<TestNavigator>
|
||||
<Screen name="foo">
|
||||
{() => (
|
||||
<TestNavigator>
|
||||
<Screen name="foo-a" component={TestComponent} />
|
||||
<Screen name="foo-b" component={TestComponent} />
|
||||
</TestNavigator>
|
||||
)}
|
||||
</Screen>
|
||||
<Screen name="bar">
|
||||
{() => (
|
||||
<TestNavigator initialRouteName="bar-a">
|
||||
<Screen name="bar-a" component={TestComponent} />
|
||||
<Screen
|
||||
name="bar-b"
|
||||
component={TestComponent}
|
||||
initialParams={{ some: 'stuff' }}
|
||||
/>
|
||||
</TestNavigator>
|
||||
)}
|
||||
</Screen>
|
||||
</TestNavigator>
|
||||
</BaseNavigationContainer>
|
||||
);
|
||||
|
||||
expect(first).toMatchInlineSnapshot(`"[foo-a, undefined]"`);
|
||||
|
||||
expect(navigation.current?.getRootState()).toEqual({
|
||||
index: 0,
|
||||
key: '0',
|
||||
routeNames: ['foo', 'bar'],
|
||||
routes: [
|
||||
{
|
||||
key: 'foo',
|
||||
name: 'foo',
|
||||
state: {
|
||||
index: 0,
|
||||
key: '1',
|
||||
routeNames: ['foo-a', 'foo-b'],
|
||||
routes: [
|
||||
{
|
||||
key: 'foo-a',
|
||||
name: 'foo-a',
|
||||
},
|
||||
{
|
||||
key: 'foo-b',
|
||||
name: 'foo-b',
|
||||
},
|
||||
],
|
||||
stale: false,
|
||||
type: 'test',
|
||||
},
|
||||
},
|
||||
{ key: 'bar', name: 'bar' },
|
||||
],
|
||||
stale: false,
|
||||
type: 'test',
|
||||
});
|
||||
|
||||
act(() =>
|
||||
navigation.current?.navigate('bar', {
|
||||
state: {
|
||||
routes: [{ name: 'bar-a' }, { name: 'bar-b' }],
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
expect(first).toMatchInlineSnapshot(`"[bar-a, undefined]"`);
|
||||
|
||||
expect(navigation.current?.getRootState()).toEqual({
|
||||
index: 1,
|
||||
key: '0',
|
||||
routeNames: ['foo', 'bar'],
|
||||
routes: [
|
||||
{ key: 'foo', name: 'foo' },
|
||||
{
|
||||
key: 'bar',
|
||||
name: 'bar',
|
||||
params: {
|
||||
state: {
|
||||
routes: [{ name: 'bar-a' }, { name: 'bar-b' }],
|
||||
},
|
||||
},
|
||||
state: {
|
||||
index: 0,
|
||||
key: '4',
|
||||
routeNames: ['bar-a', 'bar-b'],
|
||||
routes: [
|
||||
{
|
||||
key: 'bar-a-2',
|
||||
name: 'bar-a',
|
||||
},
|
||||
{
|
||||
key: 'bar-b-3',
|
||||
name: 'bar-b',
|
||||
params: { some: 'stuff' },
|
||||
},
|
||||
],
|
||||
stale: false,
|
||||
type: 'test',
|
||||
},
|
||||
},
|
||||
],
|
||||
stale: false,
|
||||
type: 'test',
|
||||
});
|
||||
|
||||
act(() =>
|
||||
navigation.current?.navigate('bar', {
|
||||
state: {
|
||||
index: 2,
|
||||
routes: [
|
||||
{ key: '37', name: 'bar-b' },
|
||||
{ name: 'bar-b' },
|
||||
{ name: 'bar-a', params: { test: 18 } },
|
||||
],
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
expect(first).toMatchInlineSnapshot(`"[bar-a, {\\"test\\":18}]"`);
|
||||
|
||||
expect(navigation.current?.getRootState()).toEqual({
|
||||
index: 1,
|
||||
key: '0',
|
||||
routeNames: ['foo', 'bar'],
|
||||
routes: [
|
||||
{ key: 'foo', name: 'foo' },
|
||||
{
|
||||
key: 'bar',
|
||||
name: 'bar',
|
||||
params: {
|
||||
state: {
|
||||
index: 2,
|
||||
routes: [
|
||||
{ key: '37', name: 'bar-b' },
|
||||
{ name: 'bar-b' },
|
||||
{ name: 'bar-a', params: { test: 18 } },
|
||||
],
|
||||
},
|
||||
},
|
||||
state: {
|
||||
index: 2,
|
||||
key: '7',
|
||||
routeNames: ['bar-a', 'bar-b'],
|
||||
routes: [
|
||||
{
|
||||
key: '37',
|
||||
name: 'bar-b',
|
||||
params: { some: 'stuff' },
|
||||
},
|
||||
{
|
||||
key: 'bar-b-5',
|
||||
name: 'bar-b',
|
||||
params: { some: 'stuff' },
|
||||
},
|
||||
{
|
||||
key: 'bar-a-6',
|
||||
name: 'bar-a',
|
||||
params: { test: 18 },
|
||||
},
|
||||
],
|
||||
stale: false,
|
||||
type: 'test',
|
||||
},
|
||||
},
|
||||
],
|
||||
stale: false,
|
||||
type: 'test',
|
||||
});
|
||||
});
|
||||
|
||||
it('gives access to internal state', () => {
|
||||
const TestNavigator = (props: any): any => {
|
||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||
|
||||
@@ -22,6 +22,7 @@ it('sets options with options prop as an object', () => {
|
||||
const { state, descriptors } = useNavigationBuilder<
|
||||
NavigationState,
|
||||
any,
|
||||
{},
|
||||
{ title?: string },
|
||||
any
|
||||
>(MockRouter, props);
|
||||
@@ -67,6 +68,7 @@ it('sets options with options prop as a fuction', () => {
|
||||
const { state, descriptors } = useNavigationBuilder<
|
||||
NavigationState,
|
||||
any,
|
||||
{},
|
||||
{ title?: string },
|
||||
any
|
||||
>(MockRouter, props);
|
||||
@@ -113,6 +115,7 @@ it('sets options with screenOptions prop as an object', () => {
|
||||
const { state, descriptors } = useNavigationBuilder<
|
||||
NavigationState,
|
||||
any,
|
||||
{},
|
||||
{ title?: string },
|
||||
any
|
||||
>(MockRouter, props);
|
||||
@@ -173,6 +176,7 @@ it('sets options with screenOptions prop as a fuction', () => {
|
||||
const { state, descriptors } = useNavigationBuilder<
|
||||
NavigationState,
|
||||
any,
|
||||
{},
|
||||
{ title?: string },
|
||||
any
|
||||
>(MockRouter, props);
|
||||
@@ -245,6 +249,7 @@ it('sets initial options with setOptions', () => {
|
||||
const { state, descriptors } = useNavigationBuilder<
|
||||
NavigationState,
|
||||
any,
|
||||
{},
|
||||
{
|
||||
title?: string;
|
||||
color?: string;
|
||||
@@ -302,6 +307,7 @@ it('updates options with setOptions', () => {
|
||||
NavigationState,
|
||||
any,
|
||||
any,
|
||||
any,
|
||||
any
|
||||
>(MockRouter, props);
|
||||
const { render, options } = descriptors[state.routes[state.index].key];
|
||||
@@ -378,6 +384,7 @@ it("returns correct value for canGoBack when it's not overridden", () => {
|
||||
const { state, descriptors } = useNavigationBuilder<
|
||||
NavigationState,
|
||||
any,
|
||||
{},
|
||||
{ title?: string },
|
||||
any
|
||||
>(MockRouter, props);
|
||||
@@ -441,6 +448,7 @@ it(`returns false for canGoBack when current router doesn't handle GO_BACK`, ()
|
||||
NavigationState,
|
||||
any,
|
||||
any,
|
||||
any,
|
||||
any
|
||||
>(TestRouter, props);
|
||||
|
||||
@@ -491,6 +499,7 @@ it('returns true for canGoBack when current router handles GO_BACK', () => {
|
||||
const { state, descriptors } = useNavigationBuilder<
|
||||
NavigationState,
|
||||
any,
|
||||
{},
|
||||
{ title?: string },
|
||||
any
|
||||
>(ParentRouter, props);
|
||||
@@ -501,6 +510,7 @@ it('returns true for canGoBack when current router handles GO_BACK', () => {
|
||||
const { state, descriptors } = useNavigationBuilder<
|
||||
NavigationState,
|
||||
any,
|
||||
{},
|
||||
{ title?: string },
|
||||
any
|
||||
>(MockRouter, props);
|
||||
@@ -558,6 +568,7 @@ it('returns true for canGoBack when parent router handles GO_BACK', () => {
|
||||
const { state, descriptors } = useNavigationBuilder<
|
||||
NavigationState,
|
||||
any,
|
||||
{},
|
||||
{ title?: string },
|
||||
any
|
||||
>(OverrodeRouter, props);
|
||||
@@ -568,6 +579,7 @@ it('returns true for canGoBack when parent router handles GO_BACK', () => {
|
||||
const { state, descriptors } = useNavigationBuilder<
|
||||
NavigationState,
|
||||
any,
|
||||
{},
|
||||
{ title?: string },
|
||||
any
|
||||
>(MockRouter, props);
|
||||
|
||||
@@ -1,43 +1,94 @@
|
||||
import type { PartialState, NavigationState } from '@react-navigation/routers';
|
||||
import type {
|
||||
Route,
|
||||
PartialRoute,
|
||||
NavigationState,
|
||||
PartialState,
|
||||
CommonActions,
|
||||
} from '@react-navigation/routers';
|
||||
import type { PathConfig, PathConfigMap, NestedNavigateParams } from './types';
|
||||
|
||||
type NavigateParams = {
|
||||
screen?: string;
|
||||
params?: NavigateParams;
|
||||
initial?: boolean;
|
||||
type ConfigItem = {
|
||||
initialRouteName?: string;
|
||||
screens?: Record<string, ConfigItem>;
|
||||
};
|
||||
|
||||
type NavigateAction = {
|
||||
type Options = { initialRouteName?: string; screens: PathConfigMap };
|
||||
|
||||
type NavigateAction<State extends NavigationState> = {
|
||||
type: 'NAVIGATE';
|
||||
payload: { name: string; params: NavigateParams };
|
||||
payload: {
|
||||
name: string;
|
||||
params?: NestedNavigateParams<State>;
|
||||
};
|
||||
};
|
||||
|
||||
export default function getActionFromState(
|
||||
state: PartialState<NavigationState>
|
||||
): NavigateAction | undefined {
|
||||
if (state.routes.length === 0) {
|
||||
state: PartialState<NavigationState>,
|
||||
options?: Options
|
||||
): NavigateAction<NavigationState> | CommonActions.Action | undefined {
|
||||
// Create a normalized configs object which will be easier to use
|
||||
const normalizedConfig = options ? createNormalizedConfigItem(options) : {};
|
||||
|
||||
const routes =
|
||||
state.index != null ? state.routes.slice(0, state.index + 1) : state.routes;
|
||||
|
||||
if (routes.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Try to construct payload for a `NAVIGATE` action from the state
|
||||
// This lets us preserve the navigation state and not lose it
|
||||
let route = state.routes[state.routes.length - 1];
|
||||
if (
|
||||
!(
|
||||
routes.length === 1 ||
|
||||
(routes.length === 2 &&
|
||||
routes[0].name === normalizedConfig?.initialRouteName)
|
||||
)
|
||||
) {
|
||||
return {
|
||||
type: 'RESET',
|
||||
payload: state,
|
||||
};
|
||||
}
|
||||
|
||||
let payload: { name: string; params: NavigateParams } = {
|
||||
name: route.name,
|
||||
params: { ...route.params },
|
||||
};
|
||||
const route = state.routes[state.index ?? state.routes.length - 1];
|
||||
|
||||
let current = route.state;
|
||||
let params = payload.params;
|
||||
let current: PartialState<NavigationState> | undefined = route?.state;
|
||||
let config: ConfigItem | undefined = normalizedConfig?.screens?.[route?.name];
|
||||
let params: NestedNavigateParams<NavigationState> = { ...route.params };
|
||||
|
||||
let payload = route ? { name: route.name, params } : undefined;
|
||||
|
||||
while (current) {
|
||||
if (current.routes.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
route = current.routes[current.routes.length - 1];
|
||||
params.initial = current.routes.length === 1;
|
||||
params.screen = route.name;
|
||||
const routes =
|
||||
current.index != null
|
||||
? current.routes.slice(0, current.index + 1)
|
||||
: current.routes;
|
||||
|
||||
const route: Route<string> | PartialRoute<Route<string>> =
|
||||
routes[routes.length - 1];
|
||||
|
||||
if (routes.length === 1) {
|
||||
params.initial = true;
|
||||
params.screen = route.name;
|
||||
params.state = undefined; // Explicitly set to override existing value when merging params
|
||||
} else if (
|
||||
routes.length === 2 &&
|
||||
routes[0].key === undefined &&
|
||||
routes[0].name === config?.initialRouteName
|
||||
) {
|
||||
params.initial = false;
|
||||
params.screen = route.name;
|
||||
params.state = undefined;
|
||||
} else {
|
||||
params.initial = undefined;
|
||||
params.screen = undefined;
|
||||
params.params = undefined;
|
||||
params.state = current;
|
||||
break;
|
||||
}
|
||||
|
||||
if (route.state) {
|
||||
params.params = { ...route.params };
|
||||
@@ -47,10 +98,34 @@ export default function getActionFromState(
|
||||
}
|
||||
|
||||
current = route.state;
|
||||
config = config?.screens?.[route.name];
|
||||
}
|
||||
|
||||
if (!payload) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to construct payload for a `NAVIGATE` action from the state
|
||||
// This lets us preserve the navigation state and not lose it
|
||||
return {
|
||||
type: 'NAVIGATE',
|
||||
payload,
|
||||
};
|
||||
}
|
||||
|
||||
const createNormalizedConfigItem = (config: PathConfig | string) =>
|
||||
typeof config === 'object' && config != null
|
||||
? {
|
||||
initialRouteName: config.initialRouteName,
|
||||
screens:
|
||||
config.screens != null
|
||||
? createNormalizedConfigs(config.screens)
|
||||
: undefined,
|
||||
}
|
||||
: {};
|
||||
|
||||
const createNormalizedConfigs = (options: PathConfigMap) =>
|
||||
Object.entries(options).reduce<Record<string, ConfigItem>>((acc, [k, v]) => {
|
||||
acc[k] = createNormalizedConfigItem(v);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import queryString from 'query-string';
|
||||
import * as queryString from 'query-string';
|
||||
import type {
|
||||
NavigationState,
|
||||
PartialState,
|
||||
@@ -35,7 +35,7 @@ const getActiveRoute = (state: State): { name: string; params?: object } => {
|
||||
/**
|
||||
* Utility to serialize a navigation state object to a path string.
|
||||
*
|
||||
* Example:
|
||||
* @example
|
||||
* ```js
|
||||
* getPathFromState(
|
||||
* {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import escape from 'escape-string-regexp';
|
||||
import queryString from 'query-string';
|
||||
import * as queryString from 'query-string';
|
||||
import type {
|
||||
NavigationState,
|
||||
PartialState,
|
||||
@@ -37,7 +37,7 @@ type ResultState = PartialState<NavigationState> & {
|
||||
* Utility to parse a path string to initial state object accepted by the container.
|
||||
* This is useful for deep linking when we need to handle the incoming URL.
|
||||
*
|
||||
* Example:
|
||||
* @example
|
||||
* ```js
|
||||
* getStateFromPath(
|
||||
* '/chat/jane/42',
|
||||
|
||||
@@ -10,8 +10,9 @@ import type {
|
||||
} from '@react-navigation/routers';
|
||||
|
||||
export type DefaultNavigatorOptions<
|
||||
ScreenOptions extends {}
|
||||
> = DefaultRouterOptions & {
|
||||
ScreenOptions extends {},
|
||||
ParamList extends ParamListBase = ParamListBase
|
||||
> = DefaultRouterOptions<Extract<keyof ParamList, string>> & {
|
||||
/**
|
||||
* Children React Elements to extract the route configuration from.
|
||||
* Only `Screen` components are supported as children.
|
||||
@@ -23,7 +24,7 @@ export type DefaultNavigatorOptions<
|
||||
screenOptions?:
|
||||
| ScreenOptions
|
||||
| ((props: {
|
||||
route: RouteProp<ParamListBase, string>;
|
||||
route: RouteProp<ParamList, keyof ParamList>;
|
||||
navigation: any;
|
||||
}) => ScreenOptions);
|
||||
};
|
||||
@@ -237,6 +238,10 @@ export type NavigationContainerProps = {
|
||||
* Callback which is called with the latest navigation state when it changes.
|
||||
*/
|
||||
onStateChange?: (state: NavigationState | undefined) => void;
|
||||
/**
|
||||
* Callback which is called when an action is not handled.
|
||||
*/
|
||||
onUnhandledAction?: (action: NavigationAction) => void;
|
||||
/**
|
||||
* Whether this navigation container should be independent of parent containers.
|
||||
* If this is not set to `true`, this container cannot be nested inside another container.
|
||||
@@ -252,7 +257,7 @@ export type NavigationContainerProps = {
|
||||
export type NavigationProp<
|
||||
ParamList extends ParamListBase,
|
||||
RouteName extends keyof ParamList = string,
|
||||
State extends NavigationState = NavigationState,
|
||||
State extends NavigationState = NavigationState<ParamList>,
|
||||
ScreenOptions extends {} = {},
|
||||
EventMap extends EventMapBase = {}
|
||||
> = NavigationHelpersCommon<ParamList, State> & {
|
||||
@@ -277,20 +282,7 @@ export type NavigationProp<
|
||||
export type RouteProp<
|
||||
ParamList extends ParamListBase,
|
||||
RouteName extends keyof ParamList
|
||||
> = Omit<Route<Extract<RouteName, string>>, 'params'> &
|
||||
(undefined extends ParamList[RouteName]
|
||||
? Readonly<{
|
||||
/**
|
||||
* Params for this route
|
||||
*/
|
||||
params?: Readonly<ParamList[RouteName]>;
|
||||
}>
|
||||
: Readonly<{
|
||||
/**
|
||||
* Params for this route
|
||||
*/
|
||||
params: Readonly<ParamList[RouteName]>;
|
||||
}>);
|
||||
> = Route<Extract<RouteName, string>, ParamList[RouteName]>;
|
||||
|
||||
export type CompositeNavigationProp<
|
||||
A extends NavigationProp<ParamListBase, string, any, any>,
|
||||
@@ -498,14 +490,11 @@ export type TypedNavigator<
|
||||
* Navigator component which manages the child screens.
|
||||
*/
|
||||
Navigator: React.ComponentType<
|
||||
Omit<React.ComponentProps<Navigator>, keyof DefaultNavigatorOptions<any>> &
|
||||
Omit<DefaultNavigatorOptions<ScreenOptions>, 'initialRouteName'> & {
|
||||
/**
|
||||
* Name of the route to focus by on initial render.
|
||||
* If not specified, usually the first route is used.
|
||||
*/
|
||||
initialRouteName?: keyof ParamList;
|
||||
}
|
||||
Omit<
|
||||
React.ComponentProps<Navigator>,
|
||||
keyof DefaultNavigatorOptions<any, any>
|
||||
> &
|
||||
DefaultNavigatorOptions<ScreenOptions, ParamList>
|
||||
>;
|
||||
/**
|
||||
* Component used for specifying route configuration.
|
||||
@@ -515,6 +504,20 @@ export type TypedNavigator<
|
||||
) => null;
|
||||
};
|
||||
|
||||
export type NestedNavigateParams<State extends NavigationState> =
|
||||
| {
|
||||
screen?: string;
|
||||
params?: object;
|
||||
initial?: boolean;
|
||||
state?: never;
|
||||
}
|
||||
| {
|
||||
screen?: never;
|
||||
params?: never;
|
||||
initial?: never;
|
||||
state?: PartialState<State> | State;
|
||||
};
|
||||
|
||||
export type PathConfig = {
|
||||
path?: string;
|
||||
exact?: boolean;
|
||||
|
||||
@@ -23,30 +23,27 @@ import useFocusEvents from './useFocusEvents';
|
||||
import useOnRouteFocus from './useOnRouteFocus';
|
||||
import useChildListeners from './useChildListeners';
|
||||
import useFocusedListenersChildrenAdapter from './useFocusedListenersChildrenAdapter';
|
||||
import useKeyedChildListeners from './useKeyedChildListeners';
|
||||
import useOnGetState from './useOnGetState';
|
||||
import useScheduleUpdate from './useScheduleUpdate';
|
||||
import useCurrentRender from './useCurrentRender';
|
||||
import isArrayEqual from './isArrayEqual';
|
||||
import {
|
||||
DefaultNavigatorOptions,
|
||||
RouteConfig,
|
||||
PrivateValueStore,
|
||||
EventMapBase,
|
||||
EventMapCore,
|
||||
NestedNavigateParams,
|
||||
} from './types';
|
||||
import useKeyedChildListeners from './useKeyedChildListeners';
|
||||
import useOnGetState from './useOnGetState';
|
||||
import useScheduleUpdate from './useScheduleUpdate';
|
||||
import useCurrentRender from './useCurrentRender';
|
||||
import isArrayEqual from './isArrayEqual';
|
||||
|
||||
// This is to make TypeScript compiler happy
|
||||
// eslint-disable-next-line babel/no-unused-expressions
|
||||
PrivateValueStore;
|
||||
|
||||
type NavigatorRoute = {
|
||||
type NavigatorRoute<State extends NavigationState> = {
|
||||
key: string;
|
||||
params?: {
|
||||
screen?: string;
|
||||
params?: object;
|
||||
initial?: boolean;
|
||||
};
|
||||
params?: NestedNavigateParams<State>;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -182,6 +179,7 @@ const getRouteConfigsFromChildren = <
|
||||
export default function useNavigationBuilder<
|
||||
State extends NavigationState,
|
||||
RouterOptions extends DefaultRouterOptions,
|
||||
ActionHelpers extends Record<string, () => void>,
|
||||
ScreenOptions extends {},
|
||||
EventMap extends Record<string, any>
|
||||
>(
|
||||
@@ -191,20 +189,15 @@ export default function useNavigationBuilder<
|
||||
const navigatorKey = useRegisterNavigator();
|
||||
|
||||
const route = React.useContext(NavigationRouteContext) as
|
||||
| NavigatorRoute
|
||||
| NavigatorRoute<State>
|
||||
| undefined;
|
||||
|
||||
const previousNestedParamsRef = React.useRef(route?.params);
|
||||
|
||||
React.useEffect(() => {
|
||||
previousNestedParamsRef.current = route?.params;
|
||||
}, [route]);
|
||||
|
||||
const { children, ...rest } = options;
|
||||
const { current: router } = React.useRef<Router<State, any>>(
|
||||
createRouter({
|
||||
...((rest as unknown) as RouterOptions),
|
||||
...(route?.params &&
|
||||
route.params.state == null &&
|
||||
route.params.initial !== false &&
|
||||
typeof route.params.screen === 'string'
|
||||
? { initialRouteName: route.params.screen }
|
||||
@@ -239,7 +232,9 @@ export default function useNavigationBuilder<
|
||||
(acc, curr) => {
|
||||
const { initialParams } = screens[curr];
|
||||
const initialParamsFromParams =
|
||||
route?.params?.initial !== false && route?.params?.screen === curr
|
||||
route?.params?.state == null &&
|
||||
route?.params?.initial !== false &&
|
||||
route?.params?.screen === curr
|
||||
? route.params.params
|
||||
: undefined;
|
||||
|
||||
@@ -287,7 +282,10 @@ export default function useNavigationBuilder<
|
||||
// We also need to re-initialize it if the state passed from parent was changed (maybe due to reset)
|
||||
// Otherwise assume that the state was provided as initial state
|
||||
// So we need to rehydrate it to make it usable
|
||||
if (currentState === undefined || !isStateValid(currentState)) {
|
||||
if (
|
||||
(currentState === undefined || !isStateValid(currentState)) &&
|
||||
route?.params?.state == null
|
||||
) {
|
||||
return [
|
||||
router.getInitialState({
|
||||
routeNames,
|
||||
@@ -297,10 +295,13 @@ export default function useNavigationBuilder<
|
||||
];
|
||||
} else {
|
||||
return [
|
||||
router.getRehydratedState(currentState as PartialState<State>, {
|
||||
routeNames,
|
||||
routeParamList,
|
||||
}),
|
||||
router.getRehydratedState(
|
||||
route?.params?.state ?? (currentState as PartialState<State>),
|
||||
{
|
||||
routeNames,
|
||||
routeParamList,
|
||||
}
|
||||
),
|
||||
false,
|
||||
];
|
||||
}
|
||||
@@ -331,21 +332,54 @@ export default function useNavigationBuilder<
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
typeof route?.params?.screen === 'string' &&
|
||||
(route.params !== previousNestedParamsRef.current ||
|
||||
(route.params.initial === false && isFirstStateInitialization))
|
||||
) {
|
||||
// If the route was updated with new name and/or params, we should navigate there
|
||||
const previousNestedParamsRef = React.useRef(route?.params);
|
||||
|
||||
React.useEffect(() => {
|
||||
previousNestedParamsRef.current = route?.params;
|
||||
}, [route?.params]);
|
||||
|
||||
if (route?.params) {
|
||||
const previousParams = previousNestedParamsRef.current;
|
||||
|
||||
let action: CommonActions.Action | undefined;
|
||||
|
||||
if (
|
||||
typeof route.params.state === 'object' &&
|
||||
route.params.state != null &&
|
||||
route.params.state !== previousParams?.state
|
||||
) {
|
||||
// If the route was updated with new state, we should reset to it
|
||||
action = CommonActions.reset(route.params.state);
|
||||
} else if (
|
||||
typeof route.params.screen === 'string' &&
|
||||
((route.params.initial === false && isFirstStateInitialization) ||
|
||||
route.params !== previousParams)
|
||||
) {
|
||||
// FIXME: Since params are merged, `route.params.params` might contain params from an older route
|
||||
// So we need to make sure to reuse it only if:
|
||||
// - The screen is the same, so navigation happened with same params
|
||||
// - Params have actually changed
|
||||
// - It's the first navigation during initialization
|
||||
const params = (
|
||||
route.params.screen === nextState.routes[nextState.index].name
|
||||
? route.params.screen === previousParams?.screen
|
||||
: route.params.params !== previousParams?.params ||
|
||||
(route.params.initial === false && isFirstStateInitialization)
|
||||
)
|
||||
? route.params.params
|
||||
: undefined;
|
||||
|
||||
// If the route was updated with new screen name and/or params, we should navigate there
|
||||
action = CommonActions.navigate(route.params.screen, params);
|
||||
}
|
||||
|
||||
// The update should be limited to current navigator only, so we call the router manually
|
||||
const updatedState = router.getStateForAction(
|
||||
nextState,
|
||||
CommonActions.navigate(route.params.screen, route.params.params),
|
||||
{
|
||||
routeNames,
|
||||
routeParamList,
|
||||
}
|
||||
);
|
||||
const updatedState = action
|
||||
? router.getStateForAction(nextState, action, {
|
||||
routeNames,
|
||||
routeParamList,
|
||||
})
|
||||
: null;
|
||||
|
||||
nextState =
|
||||
updatedState !== null
|
||||
@@ -484,7 +518,12 @@ export default function useNavigationBuilder<
|
||||
setState,
|
||||
});
|
||||
|
||||
const navigation = useNavigationHelpers<State, NavigationAction, EventMap>({
|
||||
const navigation = useNavigationHelpers<
|
||||
State,
|
||||
ActionHelpers,
|
||||
NavigationAction,
|
||||
EventMap
|
||||
>({
|
||||
onAction,
|
||||
getState,
|
||||
emitter,
|
||||
|
||||
@@ -31,6 +31,7 @@ type Options<State extends NavigationState, Action extends NavigationAction> = {
|
||||
*/
|
||||
export default function useNavigationHelpers<
|
||||
State extends NavigationState,
|
||||
ActionHelpers extends Record<string, () => void>,
|
||||
Action extends NavigationAction,
|
||||
EventMap extends Record<string, any>
|
||||
>({ onAction, getState, emitter, router }: Options<State, Action>) {
|
||||
@@ -85,7 +86,8 @@ export default function useNavigationHelpers<
|
||||
dangerouslyGetParent: () => parentNavigationHelpers as any,
|
||||
dangerouslyGetState: getState,
|
||||
} as NavigationHelpers<ParamListBase, EventMap> &
|
||||
(NavigationProp<ParamListBase, string, any, any, any> | undefined);
|
||||
(NavigationProp<ParamListBase, string, any, any, any> | undefined) &
|
||||
ActionHelpers;
|
||||
}, [
|
||||
emitter.emit,
|
||||
getState,
|
||||
|
||||
@@ -3,6 +3,46 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [5.1.11](https://github.com/react-navigation/react-navigation/compare/@react-navigation/devtools@5.1.10...@react-navigation/devtools@5.1.11) (2020-11-03)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/devtools
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.10](https://github.com/react-navigation/react-navigation/compare/@react-navigation/devtools@5.1.9...@react-navigation/devtools@5.1.10) (2020-10-30)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/devtools
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.9](https://github.com/react-navigation/react-navigation/compare/@react-navigation/devtools@5.1.8...@react-navigation/devtools@5.1.9) (2020-10-28)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/devtools
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.8](https://github.com/react-navigation/react-navigation/compare/@react-navigation/devtools@5.1.7...@react-navigation/devtools@5.1.8) (2020-10-24)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/devtools
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.7](https://github.com/react-navigation/react-navigation/compare/@react-navigation/devtools@5.1.6...@react-navigation/devtools@5.1.7) (2020-10-07)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/devtools
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.6](https://github.com/react-navigation/react-navigation/compare/@react-navigation/devtools@5.1.5...@react-navigation/devtools@5.1.6) (2020-09-22)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/devtools
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/devtools",
|
||||
"description": "Developer tools for React Navigation",
|
||||
"version": "5.1.6",
|
||||
"version": "5.1.11",
|
||||
"keywords": [
|
||||
"react",
|
||||
"react-native",
|
||||
@@ -36,14 +36,14 @@
|
||||
"clean": "del lib"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-navigation/core": "^5.12.4",
|
||||
"deep-equal": "^2.0.3"
|
||||
"@react-navigation/core": "^5.13.3",
|
||||
"deep-equal": "^2.0.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.16.2",
|
||||
"@testing-library/react-native": "^7.0.2",
|
||||
"@testing-library/react-native": "^7.1.0",
|
||||
"@types/deep-equal": "^1.0.1",
|
||||
"@types/react": "^16.9.49",
|
||||
"@types/react": "^16.9.53",
|
||||
"del-cli": "^3.0.1",
|
||||
"react": "~16.13.1",
|
||||
"typescript": "^4.0.3"
|
||||
|
||||
@@ -3,6 +3,54 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [5.10.3](https://github.com/react-navigation/react-navigation/compare/@react-navigation/drawer@5.10.2...@react-navigation/drawer@5.10.3) (2020-11-03)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/drawer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.10.2](https://github.com/react-navigation/react-navigation/compare/@react-navigation/drawer@5.10.1...@react-navigation/drawer@5.10.2) (2020-10-30)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/drawer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.10.1](https://github.com/react-navigation/react-navigation/compare/@react-navigation/drawer@5.10.0...@react-navigation/drawer@5.10.1) (2020-10-28)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/drawer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.10.0](https://github.com/react-navigation/react-navigation/compare/@react-navigation/drawer@5.9.3...@react-navigation/drawer@5.10.0) (2020-10-24)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add optional screens per navigator ([#8805](https://github.com/react-navigation/react-navigation/issues/8805)) ([7196889](https://github.com/react-navigation/react-navigation/commit/7196889bf1218eb6a736d9475e33a909c2248c3b))
|
||||
* improve types for navigation state ([#8980](https://github.com/react-navigation/react-navigation/issues/8980)) ([7dc2f58](https://github.com/react-navigation/react-navigation/commit/7dc2f5832e371473f3263c01ab39824eb9e2057d))
|
||||
* update helper types to have navigator specific methods ([f51086e](https://github.com/react-navigation/react-navigation/commit/f51086edea42f2382dac8c6914aac8574132114b))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.9.3](https://github.com/react-navigation/react-navigation/compare/@react-navigation/drawer@5.9.2...@react-navigation/drawer@5.9.3) (2020-10-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* use route keys instead of index for lazy load ([c49dab3](https://github.com/react-navigation/react-navigation/commit/c49dab31b2c63a1735f0ed0a1936ecf7bbcd8b13))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.9.2](https://github.com/react-navigation/react-navigation/compare/@react-navigation/drawer@5.9.1...@react-navigation/drawer@5.9.2) (2020-09-28)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/drawer
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/drawer",
|
||||
"description": "Drawer navigator component with animated transitions and gesturess",
|
||||
"version": "5.9.2",
|
||||
"version": "5.10.3",
|
||||
"keywords": [
|
||||
"react-native-component",
|
||||
"react-component",
|
||||
@@ -41,15 +41,15 @@
|
||||
"clean": "del lib"
|
||||
},
|
||||
"dependencies": {
|
||||
"color": "^3.1.2",
|
||||
"react-native-iphone-x-helper": "^1.2.1"
|
||||
"color": "^3.1.3",
|
||||
"react-native-iphone-x-helper": "^1.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.16.2",
|
||||
"@react-navigation/native": "^5.7.5",
|
||||
"@testing-library/react-native": "^7.0.2",
|
||||
"@types/react": "^16.9.49",
|
||||
"@types/react-native": "^0.63.20",
|
||||
"@react-navigation/native": "^5.8.3",
|
||||
"@testing-library/react-native": "^7.1.0",
|
||||
"@types/react": "^16.9.53",
|
||||
"@types/react-native": "^0.63.30",
|
||||
"del-cli": "^3.0.1",
|
||||
"react": "~16.13.1",
|
||||
"react-native": "~0.63.2",
|
||||
|
||||
@@ -6,6 +6,8 @@ import {
|
||||
DrawerNavigationState,
|
||||
DrawerRouterOptions,
|
||||
DrawerRouter,
|
||||
DrawerActionHelpers,
|
||||
ParamListBase,
|
||||
} from '@react-navigation/native';
|
||||
|
||||
import DrawerView from '../views/DrawerView';
|
||||
@@ -28,8 +30,9 @@ function DrawerNavigator({
|
||||
...rest
|
||||
}: Props) {
|
||||
const { state, descriptors, navigation } = useNavigationBuilder<
|
||||
DrawerNavigationState,
|
||||
DrawerNavigationState<ParamListBase>,
|
||||
DrawerRouterOptions,
|
||||
DrawerActionHelpers<ParamListBase>,
|
||||
DrawerNavigationOptions,
|
||||
DrawerNavigationEventMap
|
||||
>(DrawerRouter, {
|
||||
@@ -51,7 +54,7 @@ function DrawerNavigator({
|
||||
}
|
||||
|
||||
export default createNavigatorFactory<
|
||||
DrawerNavigationState,
|
||||
DrawerNavigationState<ParamListBase>,
|
||||
DrawerNavigationOptions,
|
||||
DrawerNavigationEventMap,
|
||||
typeof DrawerNavigator
|
||||
|
||||
@@ -86,6 +86,12 @@ export type DrawerNavigationConfig<T = DrawerContentOptions> = {
|
||||
* You can pass a custom background color for a drawer or a custom width here.
|
||||
*/
|
||||
drawerStyle?: StyleProp<ViewStyle>;
|
||||
/**
|
||||
* Whether inactive screens should be detached from the view hierarchy to save memory.
|
||||
* Make sure to call `enableScreens` from `react-native-screens` to make it work.
|
||||
* Defaults to `true`.
|
||||
*/
|
||||
detachInactiveScreens?: boolean;
|
||||
};
|
||||
|
||||
export type DrawerNavigationOptions = {
|
||||
@@ -136,7 +142,7 @@ export type DrawerNavigationOptions = {
|
||||
};
|
||||
|
||||
export type DrawerContentComponentProps<T = DrawerContentOptions> = T & {
|
||||
state: DrawerNavigationState;
|
||||
state: DrawerNavigationState<ParamListBase>;
|
||||
navigation: DrawerNavigationHelpers;
|
||||
descriptors: DrawerDescriptorMap;
|
||||
/**
|
||||
@@ -195,7 +201,8 @@ export type DrawerNavigationEventMap = {
|
||||
export type DrawerNavigationHelpers = NavigationHelpers<
|
||||
ParamListBase,
|
||||
DrawerNavigationEventMap
|
||||
>;
|
||||
> &
|
||||
DrawerActionHelpers<ParamListBase>;
|
||||
|
||||
export type DrawerNavigationProp<
|
||||
ParamList extends ParamListBase,
|
||||
@@ -203,7 +210,7 @@ export type DrawerNavigationProp<
|
||||
> = NavigationProp<
|
||||
ParamList,
|
||||
RouteName,
|
||||
DrawerNavigationState,
|
||||
DrawerNavigationState<ParamList>,
|
||||
DrawerNavigationOptions,
|
||||
DrawerNavigationEventMap
|
||||
> &
|
||||
@@ -220,7 +227,7 @@ export type DrawerScreenProps<
|
||||
export type DrawerDescriptor = Descriptor<
|
||||
ParamListBase,
|
||||
string,
|
||||
DrawerNavigationState,
|
||||
DrawerNavigationState<ParamListBase>,
|
||||
DrawerNavigationOptions
|
||||
>;
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
CommonActions,
|
||||
DrawerActions,
|
||||
DrawerNavigationState,
|
||||
ParamListBase,
|
||||
useLinkBuilder,
|
||||
} from '@react-navigation/native';
|
||||
import DrawerItem from './DrawerItem';
|
||||
@@ -13,7 +14,7 @@ import type {
|
||||
} from '../types';
|
||||
|
||||
type Props = Omit<DrawerContentOptions, 'contentContainerStyle' | 'style'> & {
|
||||
state: DrawerNavigationState;
|
||||
state: DrawerNavigationState<ParamListBase>;
|
||||
navigation: DrawerNavigationHelpers;
|
||||
descriptors: DrawerDescriptorMap;
|
||||
};
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
DrawerNavigationState,
|
||||
DrawerActions,
|
||||
useTheme,
|
||||
ParamListBase,
|
||||
} from '@react-navigation/native';
|
||||
|
||||
import { GestureHandlerRootView } from './GestureHandler';
|
||||
@@ -31,7 +32,7 @@ import type {
|
||||
} from '../types';
|
||||
|
||||
type Props = DrawerNavigationConfig & {
|
||||
state: DrawerNavigationState;
|
||||
state: DrawerNavigationState<ParamListBase>;
|
||||
navigation: DrawerNavigationHelpers;
|
||||
descriptors: DrawerDescriptorMap;
|
||||
};
|
||||
@@ -82,8 +83,9 @@ export default function DrawerView({
|
||||
gestureHandlerProps,
|
||||
minSwipeDistance,
|
||||
sceneContainerStyle,
|
||||
detachInactiveScreens = true,
|
||||
}: Props) {
|
||||
const [loaded, setLoaded] = React.useState([state.index]);
|
||||
const [loaded, setLoaded] = React.useState([state.routes[state.index].key]);
|
||||
const dimensions = useWindowDimensions();
|
||||
|
||||
const { colors } = useTheme();
|
||||
@@ -129,8 +131,10 @@ export default function DrawerView({
|
||||
return () => subscription?.remove();
|
||||
}, [handleDrawerClose, isDrawerOpen, navigation, state.key]);
|
||||
|
||||
if (!loaded.includes(state.index)) {
|
||||
setLoaded([...loaded, state.index]);
|
||||
const focusedRouteKey = state.routes[state.index].key;
|
||||
|
||||
if (!loaded.includes(focusedRouteKey)) {
|
||||
setLoaded([...loaded, focusedRouteKey]);
|
||||
}
|
||||
|
||||
const renderNavigationView = ({ progress }: any) => {
|
||||
@@ -149,7 +153,8 @@ export default function DrawerView({
|
||||
|
||||
const renderContent = () => {
|
||||
return (
|
||||
<ScreenContainer style={styles.content}>
|
||||
// @ts-ignore
|
||||
<ScreenContainer enabled={detachInactiveScreens} style={styles.content}>
|
||||
{state.routes.map((route, index) => {
|
||||
const descriptor = descriptors[route.key];
|
||||
const { unmountOnBlur } = descriptor.options;
|
||||
@@ -159,7 +164,7 @@ export default function DrawerView({
|
||||
return null;
|
||||
}
|
||||
|
||||
if (lazy && !loaded.includes(index) && !isFocused) {
|
||||
if (lazy && !loaded.includes(route.key) && !isFocused) {
|
||||
// Don't render a screen if we've never navigated to it
|
||||
return null;
|
||||
}
|
||||
@@ -169,6 +174,7 @@ export default function DrawerView({
|
||||
key={route.key}
|
||||
style={[StyleSheet.absoluteFill, { opacity: isFocused ? 1 : 0 }]}
|
||||
isVisible={isFocused}
|
||||
enabled={detachInactiveScreens}
|
||||
>
|
||||
{descriptor.render()}
|
||||
</ResourceSavingScene>
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
import * as React from 'react';
|
||||
import { Platform, StyleSheet, View } from 'react-native';
|
||||
import { Screen, screensEnabled } from 'react-native-screens';
|
||||
import {
|
||||
Screen,
|
||||
screensEnabled,
|
||||
// @ts-ignore
|
||||
shouldUseActivityState,
|
||||
} from 'react-native-screens';
|
||||
|
||||
type Props = {
|
||||
isVisible: boolean;
|
||||
children: React.ReactNode;
|
||||
enabled: boolean;
|
||||
style?: any;
|
||||
};
|
||||
|
||||
@@ -16,8 +22,17 @@ export default class ResourceSavingScene extends React.Component<Props> {
|
||||
if (screensEnabled?.() && Platform.OS !== 'web') {
|
||||
const { isVisible, ...rest } = this.props;
|
||||
|
||||
// @ts-expect-error: stackPresentation is incorrectly marked as required
|
||||
return <Screen active={isVisible ? 1 : 0} {...rest} />;
|
||||
if (shouldUseActivityState) {
|
||||
return (
|
||||
// @ts-expect-error: there was an `active` prop and no `activityState` in older version and stackPresentation was required
|
||||
<Screen activityState={isVisible ? 2 : 0} {...rest} />
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
// @ts-expect-error: there was an `active` prop and no `activityState` in older version and stackPresentation was required
|
||||
<Screen active={isVisible ? 1 : 0} {...rest} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const { isVisible, children, style, ...rest } = this.props;
|
||||
|
||||
@@ -3,6 +3,51 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [5.3.3](https://github.com/react-navigation/react-navigation/compare/@react-navigation/material-bottom-tabs@5.3.2...@react-navigation/material-bottom-tabs@5.3.3) (2020-11-03)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.3.2](https://github.com/react-navigation/react-navigation/compare/@react-navigation/material-bottom-tabs@5.3.1...@react-navigation/material-bottom-tabs@5.3.2) (2020-10-30)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.3.1](https://github.com/react-navigation/react-navigation/compare/@react-navigation/material-bottom-tabs@5.3.0...@react-navigation/material-bottom-tabs@5.3.1) (2020-10-28)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.3.0](https://github.com/react-navigation/react-navigation/compare/@react-navigation/material-bottom-tabs@5.2.19...@react-navigation/material-bottom-tabs@5.3.0) (2020-10-24)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* improve types for navigation state ([#8980](https://github.com/react-navigation/react-navigation/issues/8980)) ([7dc2f58](https://github.com/react-navigation/react-navigation/commit/7dc2f5832e371473f3263c01ab39824eb9e2057d))
|
||||
* make react-native-vector-icons optional ([#8936](https://github.com/react-navigation/react-navigation/issues/8936)) ([90ebfc4](https://github.com/react-navigation/react-navigation/commit/90ebfc40b387b209031e6275aaa0be95192f7d04)), closes [/github.com/callstack/react-native-paper/blob/4b26429c49053eaa4c3e0fae208639e01093fa87/src/components/MaterialCommunityIcon.tsx#L14](https://github.com//github.com/callstack/react-native-paper/blob/4b26429c49053eaa4c3e0fae208639e01093fa87/src/components/MaterialCommunityIcon.tsx/issues/L14) [#8821](https://github.com/react-navigation/react-navigation/issues/8821)
|
||||
* update helper types to have navigator specific methods ([f51086e](https://github.com/react-navigation/react-navigation/commit/f51086edea42f2382dac8c6914aac8574132114b))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.19](https://github.com/react-navigation/react-navigation/compare/@react-navigation/material-bottom-tabs@5.2.18...@react-navigation/material-bottom-tabs@5.2.19) (2020-10-07)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.18](https://github.com/react-navigation/react-navigation/compare/@react-navigation/material-bottom-tabs@5.2.17...@react-navigation/material-bottom-tabs@5.2.18) (2020-09-28)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/material-bottom-tabs",
|
||||
"description": "Integration for bottom navigation component from react-native-paper",
|
||||
"version": "5.2.18",
|
||||
"version": "5.3.3",
|
||||
"keywords": [
|
||||
"react-native-component",
|
||||
"react-component",
|
||||
@@ -42,10 +42,10 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.16.2",
|
||||
"@react-navigation/native": "^5.7.5",
|
||||
"@testing-library/react-native": "^7.0.2",
|
||||
"@types/react": "^16.9.49",
|
||||
"@types/react-native": "^0.63.20",
|
||||
"@react-navigation/native": "^5.8.3",
|
||||
"@testing-library/react-native": "^7.1.0",
|
||||
"@types/react": "^16.9.53",
|
||||
"@types/react-native": "^0.63.30",
|
||||
"@types/react-native-vector-icons": "^6.4.6",
|
||||
"del-cli": "^3.0.1",
|
||||
"react": "~16.13.1",
|
||||
|
||||
@@ -6,6 +6,8 @@ import {
|
||||
TabRouter,
|
||||
TabRouterOptions,
|
||||
TabNavigationState,
|
||||
TabActionHelpers,
|
||||
ParamListBase,
|
||||
} from '@react-navigation/native';
|
||||
|
||||
import MaterialBottomTabView from '../views/MaterialBottomTabView';
|
||||
@@ -27,8 +29,9 @@ function MaterialBottomTabNavigator({
|
||||
...rest
|
||||
}: Props) {
|
||||
const { state, descriptors, navigation } = useNavigationBuilder<
|
||||
TabNavigationState,
|
||||
TabNavigationState<ParamListBase>,
|
||||
TabRouterOptions,
|
||||
TabActionHelpers<ParamListBase>,
|
||||
MaterialBottomTabNavigationOptions,
|
||||
MaterialBottomTabNavigationEventMap
|
||||
>(TabRouter, {
|
||||
@@ -49,7 +52,7 @@ function MaterialBottomTabNavigator({
|
||||
}
|
||||
|
||||
export default createNavigatorFactory<
|
||||
TabNavigationState,
|
||||
TabNavigationState<ParamListBase>,
|
||||
MaterialBottomTabNavigationOptions,
|
||||
MaterialBottomTabNavigationEventMap,
|
||||
typeof MaterialBottomTabNavigator
|
||||
|
||||
@@ -19,7 +19,8 @@ export type MaterialBottomTabNavigationEventMap = {
|
||||
export type MaterialBottomTabNavigationHelpers = NavigationHelpers<
|
||||
ParamListBase,
|
||||
MaterialBottomTabNavigationEventMap
|
||||
>;
|
||||
> &
|
||||
TabActionHelpers<ParamListBase>;
|
||||
|
||||
export type MaterialBottomTabNavigationProp<
|
||||
ParamList extends ParamListBase,
|
||||
@@ -27,7 +28,7 @@ export type MaterialBottomTabNavigationProp<
|
||||
> = NavigationProp<
|
||||
ParamList,
|
||||
RouteName,
|
||||
TabNavigationState,
|
||||
TabNavigationState<ParamList>,
|
||||
MaterialBottomTabNavigationOptions,
|
||||
MaterialBottomTabNavigationEventMap
|
||||
> &
|
||||
@@ -84,7 +85,7 @@ export type MaterialBottomTabNavigationOptions = {
|
||||
export type MaterialBottomTabDescriptor = Descriptor<
|
||||
ParamListBase,
|
||||
string,
|
||||
TabNavigationState,
|
||||
TabNavigationState<ParamListBase>,
|
||||
MaterialBottomTabNavigationOptions
|
||||
>;
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import { StyleSheet, Platform } from 'react-native';
|
||||
import { BottomNavigation, DefaultTheme, DarkTheme } from 'react-native-paper';
|
||||
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
import {
|
||||
NavigationHelpersContext,
|
||||
Route,
|
||||
@@ -10,6 +9,7 @@ import {
|
||||
useTheme,
|
||||
useLinkBuilder,
|
||||
Link,
|
||||
ParamListBase,
|
||||
} from '@react-navigation/native';
|
||||
|
||||
import type {
|
||||
@@ -19,13 +19,55 @@ import type {
|
||||
} from '../types';
|
||||
|
||||
type Props = MaterialBottomTabNavigationConfig & {
|
||||
state: TabNavigationState;
|
||||
state: TabNavigationState<ParamListBase>;
|
||||
navigation: MaterialBottomTabNavigationHelpers;
|
||||
descriptors: MaterialBottomTabDescriptorMap;
|
||||
};
|
||||
|
||||
type Scene = { route: { key: string } };
|
||||
|
||||
// Optionally require vector-icons referenced from react-native-paper:
|
||||
// https://github.com/callstack/react-native-paper/blob/4b26429c49053eaa4c3e0fae208639e01093fa87/src/components/MaterialCommunityIcon.tsx#L14
|
||||
let MaterialCommunityIcons: any;
|
||||
|
||||
try {
|
||||
// Optionally require vector-icons
|
||||
MaterialCommunityIcons = require('react-native-vector-icons/MaterialCommunityIcons')
|
||||
.default;
|
||||
} catch (e) {
|
||||
// @ts-expect-error
|
||||
if (global.__expo?.Icon?.MaterialCommunityIcons) {
|
||||
// Snack doesn't properly bundle vector icons from sub-path
|
||||
// Use icons from the __expo global if available
|
||||
// @ts-expect-error
|
||||
MaterialCommunityIcons = global.__expo.Icon.MaterialCommunityIcons;
|
||||
} else {
|
||||
let isErrorLogged = false;
|
||||
|
||||
// Fallback component for icons
|
||||
MaterialCommunityIcons = () => {
|
||||
if (!isErrorLogged) {
|
||||
if (
|
||||
!/(Cannot find module|Module not found|Cannot resolve module)/.test(
|
||||
e.message
|
||||
)
|
||||
) {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
console.warn(
|
||||
`Tried to use the icon '${name}' in a component from '@react-navigation/material-bottom-tabs', but 'react-native-vector-icons' could not be loaded.`,
|
||||
`To remove this warning, try installing 'react-native-vector-icons' or use another method.`
|
||||
);
|
||||
|
||||
isErrorLogged = true;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function MaterialBottomTabViewInner({
|
||||
state,
|
||||
navigation,
|
||||
|
||||
@@ -3,6 +3,50 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [5.3.3](https://github.com/react-navigation/react-navigation/compare/@react-navigation/material-top-tabs@5.3.2...@react-navigation/material-top-tabs@5.3.3) (2020-11-03)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.3.2](https://github.com/react-navigation/react-navigation/compare/@react-navigation/material-top-tabs@5.3.1...@react-navigation/material-top-tabs@5.3.2) (2020-10-30)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.3.1](https://github.com/react-navigation/react-navigation/compare/@react-navigation/material-top-tabs@5.3.0...@react-navigation/material-top-tabs@5.3.1) (2020-10-28)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.3.0](https://github.com/react-navigation/react-navigation/compare/@react-navigation/material-top-tabs@5.2.19...@react-navigation/material-top-tabs@5.3.0) (2020-10-24)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* improve types for navigation state ([#8980](https://github.com/react-navigation/react-navigation/issues/8980)) ([7dc2f58](https://github.com/react-navigation/react-navigation/commit/7dc2f5832e371473f3263c01ab39824eb9e2057d))
|
||||
* update helper types to have navigator specific methods ([f51086e](https://github.com/react-navigation/react-navigation/commit/f51086edea42f2382dac8c6914aac8574132114b))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.19](https://github.com/react-navigation/react-navigation/compare/@react-navigation/material-top-tabs@5.2.18...@react-navigation/material-top-tabs@5.2.19) (2020-10-07)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.18](https://github.com/react-navigation/react-navigation/compare/@react-navigation/material-top-tabs@5.2.17...@react-navigation/material-top-tabs@5.2.18) (2020-09-28)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/material-top-tabs",
|
||||
"description": "Integration for the animated tab view component from react-native-tab-view",
|
||||
"version": "5.2.18",
|
||||
"version": "5.3.3",
|
||||
"keywords": [
|
||||
"react-native-component",
|
||||
"react-component",
|
||||
@@ -41,20 +41,20 @@
|
||||
"clean": "del lib"
|
||||
},
|
||||
"dependencies": {
|
||||
"color": "^3.1.2"
|
||||
"color": "^3.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.16.2",
|
||||
"@react-navigation/native": "^5.7.5",
|
||||
"@testing-library/react-native": "^7.0.2",
|
||||
"@types/react": "^16.9.49",
|
||||
"@types/react-native": "^0.63.20",
|
||||
"@react-navigation/native": "^5.8.3",
|
||||
"@testing-library/react-native": "^7.1.0",
|
||||
"@types/react": "^16.9.53",
|
||||
"@types/react-native": "^0.63.30",
|
||||
"del-cli": "^3.0.1",
|
||||
"react": "~16.13.1",
|
||||
"react-native": "~0.63.2",
|
||||
"react-native-gesture-handler": "~1.7.0",
|
||||
"react-native-reanimated": "~1.13.0",
|
||||
"react-native-tab-view": "^2.15.1",
|
||||
"react-native-tab-view": "^2.15.2",
|
||||
"typescript": "^4.0.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -6,6 +6,8 @@ import {
|
||||
TabRouter,
|
||||
TabRouterOptions,
|
||||
TabNavigationState,
|
||||
TabActionHelpers,
|
||||
ParamListBase,
|
||||
} from '@react-navigation/native';
|
||||
import MaterialTopTabView from '../views/MaterialTopTabView';
|
||||
import type {
|
||||
@@ -26,8 +28,9 @@ function MaterialTopTabNavigator({
|
||||
...rest
|
||||
}: Props) {
|
||||
const { state, descriptors, navigation } = useNavigationBuilder<
|
||||
TabNavigationState,
|
||||
TabNavigationState<ParamListBase>,
|
||||
TabRouterOptions,
|
||||
TabActionHelpers<ParamListBase>,
|
||||
MaterialTopTabNavigationOptions,
|
||||
MaterialTopTabNavigationEventMap
|
||||
>(TabRouter, {
|
||||
@@ -48,7 +51,7 @@ function MaterialTopTabNavigator({
|
||||
}
|
||||
|
||||
export default createNavigatorFactory<
|
||||
TabNavigationState,
|
||||
TabNavigationState<ParamListBase>,
|
||||
MaterialTopTabNavigationOptions,
|
||||
MaterialTopTabNavigationEventMap,
|
||||
typeof MaterialTopTabNavigator
|
||||
|
||||
@@ -37,7 +37,8 @@ export type MaterialTopTabNavigationEventMap = {
|
||||
export type MaterialTopTabNavigationHelpers = NavigationHelpers<
|
||||
ParamListBase,
|
||||
MaterialTopTabNavigationEventMap
|
||||
>;
|
||||
> &
|
||||
TabActionHelpers<ParamListBase>;
|
||||
|
||||
export type MaterialTopTabNavigationProp<
|
||||
ParamList extends ParamListBase,
|
||||
@@ -45,7 +46,7 @@ export type MaterialTopTabNavigationProp<
|
||||
> = NavigationProp<
|
||||
ParamList,
|
||||
RouteName,
|
||||
TabNavigationState,
|
||||
TabNavigationState<ParamList>,
|
||||
MaterialTopTabNavigationOptions,
|
||||
MaterialTopTabNavigationEventMap
|
||||
> &
|
||||
@@ -94,7 +95,7 @@ export type MaterialTopTabNavigationOptions = {
|
||||
export type MaterialTopTabDescriptor = Descriptor<
|
||||
ParamListBase,
|
||||
string,
|
||||
TabNavigationState,
|
||||
TabNavigationState<ParamListBase>,
|
||||
MaterialTopTabNavigationOptions
|
||||
>;
|
||||
|
||||
@@ -192,7 +193,7 @@ export type MaterialTopTabBarOptions = Partial<
|
||||
|
||||
export type MaterialTopTabBarProps = MaterialTopTabBarOptions &
|
||||
SceneRendererProps & {
|
||||
state: TabNavigationState;
|
||||
state: TabNavigationState<ParamListBase>;
|
||||
navigation: NavigationHelpers<
|
||||
ParamListBase,
|
||||
MaterialTopTabNavigationEventMap
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
NavigationHelpersContext,
|
||||
TabNavigationState,
|
||||
TabActions,
|
||||
ParamListBase,
|
||||
useTheme,
|
||||
} from '@react-navigation/native';
|
||||
|
||||
@@ -16,7 +17,7 @@ import type {
|
||||
} from '../types';
|
||||
|
||||
type Props = MaterialTopTabNavigationConfig & {
|
||||
state: TabNavigationState;
|
||||
state: TabNavigationState<ParamListBase>;
|
||||
navigation: MaterialTopTabNavigationHelpers;
|
||||
descriptors: MaterialTopTabDescriptorMap;
|
||||
tabBarPosition?: 'top' | 'bottom';
|
||||
|
||||
@@ -3,6 +3,57 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [5.8.3](https://github.com/react-navigation/react-navigation/compare/@react-navigation/native@5.8.2...@react-navigation/native@5.8.3) (2020-11-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* make sure that invalid linking config doesn't work if app is open ([52451d1](https://github.com/react-navigation/react-navigation/commit/52451d11094b8551e3c6950b3e005d68225c7da9))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.8.2](https://github.com/react-navigation/react-navigation/compare/@react-navigation/native@5.8.1...@react-navigation/native@5.8.2) (2020-10-30)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/native
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.8.1](https://github.com/react-navigation/react-navigation/compare/@react-navigation/native@5.8.0...@react-navigation/native@5.8.1) (2020-10-28)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/native
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.8.0](https://github.com/react-navigation/react-navigation/compare/@react-navigation/native@5.7.6...@react-navigation/native@5.8.0) (2020-10-24)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add `getInitialURL` and `subscribe` options to linking config ([748e92f](https://github.com/react-navigation/react-navigation/commit/748e92f120b9ff73c6b1e14515f60c76701081db))
|
||||
* allow deep linking to reset state ([#8973](https://github.com/react-navigation/react-navigation/issues/8973)) ([7f3b27a](https://github.com/react-navigation/react-navigation/commit/7f3b27a9ec8edd9604ac19774baa1f60963ccdc9)), closes [#8952](https://github.com/react-navigation/react-navigation/issues/8952)
|
||||
* support wildcard string prefixes ([#8942](https://github.com/react-navigation/react-navigation/issues/8942)) ([23ab350](https://github.com/react-navigation/react-navigation/commit/23ab3504921b7e741a48d66c6a953905206df4b7)), closes [#8941](https://github.com/react-navigation/react-navigation/issues/8941)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.7.6](https://github.com/react-navigation/react-navigation/compare/@react-navigation/native@5.7.5...@react-navigation/native@5.7.6) (2020-10-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add missing check for initial state on web ([9e36508](https://github.com/react-navigation/react-navigation/commit/9e3650831c22b47130d2b388390f7eb7910fe91d))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.7.5](https://github.com/react-navigation/react-navigation/compare/@react-navigation/native@5.7.4...@react-navigation/native@5.7.5) (2020-09-28)
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/native",
|
||||
"description": "React Native integration for React Navigation",
|
||||
"version": "5.7.5",
|
||||
"version": "5.8.3",
|
||||
"keywords": [
|
||||
"react-native",
|
||||
"react-navigation",
|
||||
@@ -37,15 +37,16 @@
|
||||
"clean": "del lib"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-navigation/core": "^5.12.4",
|
||||
"nanoid": "^3.1.12"
|
||||
"@react-navigation/core": "^5.13.3",
|
||||
"escape-string-regexp": "^4.0.0",
|
||||
"nanoid": "^3.1.15"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.16.2",
|
||||
"@testing-library/react-native": "^7.0.2",
|
||||
"@types/react": "^16.9.49",
|
||||
"@testing-library/react-native": "^7.1.0",
|
||||
"@types/react": "^16.9.53",
|
||||
"@types/react-dom": "^16.9.8",
|
||||
"@types/react-native": "^0.63.20",
|
||||
"@types/react-native": "^0.63.30",
|
||||
"del-cli": "^3.0.1",
|
||||
"react": "~16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
|
||||
@@ -27,12 +27,23 @@ export type LinkingOptions = {
|
||||
* The prefixes are stripped from the URL before parsing them.
|
||||
* Usually they are the `scheme` + `host` (e.g. `myapp://chat?user=jane`)
|
||||
* Only applicable on Android and iOS.
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* {
|
||||
* prefixes: [
|
||||
* "myapp://", // App-specific scheme
|
||||
* "https://example.com", // Prefix for universal links
|
||||
* "https://*.example.com" // Prefix which matches any subdomain
|
||||
* ]
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
prefixes: string[];
|
||||
/**
|
||||
* Config to fine-tune how to parse the path.
|
||||
*
|
||||
* Example:
|
||||
* @example
|
||||
* ```js
|
||||
* {
|
||||
* Chat: {
|
||||
@@ -43,13 +54,47 @@ export type LinkingOptions = {
|
||||
* ```
|
||||
*/
|
||||
config?: { initialRouteName?: string; screens: PathConfigMap };
|
||||
/**
|
||||
* Custom function to get the initial URL used for linking.
|
||||
* Uses `Linking.getInitialURL()` by default.
|
||||
* Not supported on Web.
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* {
|
||||
* getInitialURL () => Linking.getInitialURL(),
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
getInitialURL?: () => Promise<string | null | undefined>;
|
||||
/**
|
||||
* Custom function to get subscribe to URL updates.
|
||||
* Uses `Linking.addEventListener('url', callback)` by default.
|
||||
* Not supported on Web.
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* {
|
||||
* subscribe: (listener) => {
|
||||
* const onReceiveURL = ({ url }) => listener(url);
|
||||
*
|
||||
* Linking.addEventListener('url', onReceiveURL);
|
||||
*
|
||||
* return () => Linking.removeEventListener('url', onReceiveURL);
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
subscribe?: (
|
||||
listener: (url: string) => void
|
||||
) => undefined | void | (() => void);
|
||||
/**
|
||||
* Custom function to parse the URL to a valid navigation state (advanced).
|
||||
* Only applicable on Web.
|
||||
*/
|
||||
getStateFromPath?: typeof getStateFromPathDefault;
|
||||
/**
|
||||
* Custom function to convert the state object to a valid URL (advanced).
|
||||
* Only applicable on Web.
|
||||
*/
|
||||
getPathFromState?: typeof getPathFromStateDefault;
|
||||
};
|
||||
|
||||
@@ -22,7 +22,7 @@ export default function useLinkProps({ to, action }: Props) {
|
||||
const linkTo = useLinkTo();
|
||||
|
||||
const onPress = (
|
||||
e: React.MouseEvent<HTMLAnchorElement, MouseEvent> | GestureResponderEvent
|
||||
e?: React.MouseEvent<HTMLAnchorElement, MouseEvent> | GestureResponderEvent
|
||||
) => {
|
||||
let shouldHandle = false;
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ export default function useLinkTo() {
|
||||
root = current;
|
||||
}
|
||||
|
||||
const action = getActionFromState(state);
|
||||
const action = getActionFromState(state, options?.config);
|
||||
|
||||
if (action !== undefined) {
|
||||
root.dispatch(action);
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
NavigationContainerRef,
|
||||
} from '@react-navigation/core';
|
||||
import type { LinkingOptions } from './types';
|
||||
import escapeStringRegexp from 'escape-string-regexp';
|
||||
|
||||
let isUsingLinking = false;
|
||||
|
||||
@@ -15,6 +16,22 @@ export default function useLinking(
|
||||
enabled = true,
|
||||
prefixes,
|
||||
config,
|
||||
getInitialURL = () =>
|
||||
Promise.race([
|
||||
Linking.getInitialURL(),
|
||||
new Promise<undefined>((resolve) =>
|
||||
// Timeout in 150ms if `getInitialState` doesn't resolve
|
||||
// Workaround for https://github.com/facebook/react-native/issues/25675
|
||||
setTimeout(resolve, 150)
|
||||
),
|
||||
]),
|
||||
subscribe = (listener) => {
|
||||
const callback = ({ url }: { url: string }) => listener(url);
|
||||
|
||||
Linking.addEventListener('url', callback);
|
||||
|
||||
return () => Linking.removeEventListener('url', callback);
|
||||
},
|
||||
getStateFromPath = getStateFromPathDefault,
|
||||
}: LinkingOptions
|
||||
) {
|
||||
@@ -47,19 +64,29 @@ export default function useLinking(
|
||||
const enabledRef = React.useRef(enabled);
|
||||
const prefixesRef = React.useRef(prefixes);
|
||||
const configRef = React.useRef(config);
|
||||
const getInitialURLRef = React.useRef(getInitialURL);
|
||||
const getStateFromPathRef = React.useRef(getStateFromPath);
|
||||
|
||||
React.useEffect(() => {
|
||||
enabledRef.current = enabled;
|
||||
prefixesRef.current = prefixes;
|
||||
configRef.current = config;
|
||||
getInitialURLRef.current = getInitialURL;
|
||||
getStateFromPathRef.current = getStateFromPath;
|
||||
}, [config, enabled, getStateFromPath, prefixes]);
|
||||
}, [config, enabled, prefixes, getInitialURL, getStateFromPath]);
|
||||
|
||||
const extractPathFromURL = React.useCallback((url: string) => {
|
||||
for (const prefix of prefixesRef.current) {
|
||||
if (url.startsWith(prefix)) {
|
||||
return url.replace(prefix, '');
|
||||
const protocol = prefix.match(/^[^:]+:\/\//)?.[0] ?? '';
|
||||
const host = prefix.replace(protocol, '');
|
||||
const prefixRegex = new RegExp(
|
||||
`^${escapeStringRegexp(protocol)}${host
|
||||
.split('.')
|
||||
.map((it) => (it === '*' ? '[^/]+' : escapeStringRegexp(it)))
|
||||
.join('\\.')}`
|
||||
);
|
||||
if (prefixRegex.test(url)) {
|
||||
return url.replace(prefixRegex, '');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,15 +98,7 @@ export default function useLinking(
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const url = await (Promise.race([
|
||||
Linking.getInitialURL(),
|
||||
new Promise((resolve) =>
|
||||
// Timeout in 150ms if `getInitialState` doesn't resolve
|
||||
// Workaround for https://github.com/facebook/react-native/issues/25675
|
||||
setTimeout(resolve, 150)
|
||||
),
|
||||
]) as Promise<string | null | undefined>);
|
||||
|
||||
const url = await getInitialURLRef.current();
|
||||
const path = url ? extractPathFromURL(url) : null;
|
||||
|
||||
if (path) {
|
||||
@@ -90,7 +109,7 @@ export default function useLinking(
|
||||
}, [extractPathFromURL]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const listener = ({ url }: { url: string }) => {
|
||||
const listener = (url: string) => {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
@@ -102,7 +121,20 @@ export default function useLinking(
|
||||
const state = getStateFromPathRef.current(path, configRef.current);
|
||||
|
||||
if (state) {
|
||||
const action = getActionFromState(state);
|
||||
// Make sure that the routes in the state exist in the root navigator
|
||||
// Otherwise there's an error in the linking configuration
|
||||
const rootState = navigation.getRootState();
|
||||
|
||||
if (
|
||||
state.routes.some((r) => !rootState?.routeNames.includes(r.name))
|
||||
) {
|
||||
console.warn(
|
||||
"The navigation state parsed from the URL contains routes not present in the root navigator. This usually means that the linking configuration doesn't match the navigation structure. See https://reactnavigation.org/docs/configuring-links for more details on how to specify a linking configuration."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const action = getActionFromState(state, configRef.current);
|
||||
|
||||
if (action !== undefined) {
|
||||
navigation.dispatch(action);
|
||||
@@ -113,10 +145,8 @@ export default function useLinking(
|
||||
}
|
||||
};
|
||||
|
||||
Linking.addEventListener('url', listener);
|
||||
|
||||
return () => Linking.removeEventListener('url', listener);
|
||||
}, [enabled, extractPathFromURL, ref]);
|
||||
return subscribe(listener);
|
||||
}, [enabled, ref, subscribe, extractPathFromURL]);
|
||||
|
||||
return {
|
||||
getInitialState,
|
||||
|
||||
@@ -399,11 +399,26 @@ export default function useLinking(
|
||||
|
||||
// We should only dispatch an action when going forward
|
||||
// Otherwise the action will likely add items to history, which would mess things up
|
||||
if (state && index > previousIndex) {
|
||||
const action = getActionFromState(state);
|
||||
if (state) {
|
||||
// Make sure that the routes in the state exist in the root navigator
|
||||
// Otherwise there's an error in the linking configuration
|
||||
const rootState = navigation.getRootState();
|
||||
|
||||
if (action !== undefined) {
|
||||
navigation.dispatch(action);
|
||||
if (state.routes.some((r) => !rootState?.routeNames.includes(r.name))) {
|
||||
console.warn(
|
||||
"The navigation state parsed from the URL contains routes not present in the root navigator. This usually means that the linking configuration doesn't match the navigation structure. See https://reactnavigation.org/docs/configuring-links for more details on how to specify a linking configuration."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (index > previousIndex) {
|
||||
const action = getActionFromState(state, configRef.current);
|
||||
|
||||
if (action !== undefined) {
|
||||
navigation.dispatch(action);
|
||||
} else {
|
||||
navigation.resetRoot(state);
|
||||
}
|
||||
} else {
|
||||
navigation.resetRoot(state);
|
||||
}
|
||||
@@ -423,13 +438,16 @@ export default function useLinking(
|
||||
// We need to record the current metadata on the first render if they aren't set
|
||||
// This will allow the initial state to be in the history entry
|
||||
const state = ref.current.getRootState();
|
||||
const path = getPathFromStateRef.current(state, configRef.current);
|
||||
|
||||
if (previousStateRef.current === undefined) {
|
||||
previousStateRef.current = state;
|
||||
if (state) {
|
||||
const path = getPathFromStateRef.current(state, configRef.current);
|
||||
|
||||
if (previousStateRef.current === undefined) {
|
||||
previousStateRef.current = state;
|
||||
}
|
||||
|
||||
history.replace({ path, state });
|
||||
}
|
||||
|
||||
history.replace({ path, state });
|
||||
}
|
||||
|
||||
const onStateChange = async () => {
|
||||
|
||||
@@ -3,6 +3,34 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [5.5.1](https://github.com/react-navigation/react-navigation/compare/@react-navigation/routers@5.5.0...@react-navigation/routers@5.5.1) (2020-10-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* improve types for route prop in screenOptions ([d26bcc0](https://github.com/react-navigation/react-navigation/commit/d26bcc057ef31f8950f909adf83e263171a42d74))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.5.0](https://github.com/react-navigation/react-navigation/compare/@react-navigation/routers@5.4.12...@react-navigation/routers@5.5.0) (2020-10-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* handle pushing a route with duplicate key ([091b2a2](https://github.com/react-navigation/react-navigation/commit/091b2a2038af1097be57a1bb451623d64a1efc92))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* allow deep linking to reset state ([#8973](https://github.com/react-navigation/react-navigation/issues/8973)) ([7f3b27a](https://github.com/react-navigation/react-navigation/commit/7f3b27a9ec8edd9604ac19774baa1f60963ccdc9)), closes [#8952](https://github.com/react-navigation/react-navigation/issues/8952)
|
||||
* improve types for navigation state ([#8980](https://github.com/react-navigation/react-navigation/issues/8980)) ([7dc2f58](https://github.com/react-navigation/react-navigation/commit/7dc2f5832e371473f3263c01ab39824eb9e2057d))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.4.12](https://github.com/react-navigation/react-navigation/compare/@react-navigation/routers@5.4.11...@react-navigation/routers@5.4.12) (2020-09-22)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/routers
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/routers",
|
||||
"description": "Routers to help build custom navigators",
|
||||
"version": "5.4.12",
|
||||
"version": "5.5.1",
|
||||
"keywords": [
|
||||
"react",
|
||||
"react-native",
|
||||
@@ -36,7 +36,7 @@
|
||||
"clean": "del lib"
|
||||
},
|
||||
"dependencies": {
|
||||
"nanoid": "^3.1.12"
|
||||
"nanoid": "^3.1.15"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.16.2",
|
||||
|
||||
@@ -25,8 +25,8 @@ export type DrawerRouterOptions = TabRouterOptions & {
|
||||
openByDefault?: boolean;
|
||||
};
|
||||
|
||||
export type DrawerNavigationState = Omit<
|
||||
TabNavigationState,
|
||||
export type DrawerNavigationState<ParamList extends ParamListBase> = Omit<
|
||||
TabNavigationState<ParamList>,
|
||||
'type' | 'history'
|
||||
> & {
|
||||
/**
|
||||
@@ -72,10 +72,14 @@ export const DrawerActions = {
|
||||
};
|
||||
|
||||
const isDrawerOpen = (
|
||||
state: DrawerNavigationState | PartialState<DrawerNavigationState>
|
||||
state:
|
||||
| DrawerNavigationState<ParamListBase>
|
||||
| PartialState<DrawerNavigationState<ParamListBase>>
|
||||
) => Boolean(state.history?.find((it) => it.type === 'drawer'));
|
||||
|
||||
const openDrawer = (state: DrawerNavigationState): DrawerNavigationState => {
|
||||
const openDrawer = (
|
||||
state: DrawerNavigationState<ParamListBase>
|
||||
): DrawerNavigationState<ParamListBase> => {
|
||||
if (isDrawerOpen(state)) {
|
||||
return state;
|
||||
}
|
||||
@@ -86,7 +90,9 @@ const openDrawer = (state: DrawerNavigationState): DrawerNavigationState => {
|
||||
};
|
||||
};
|
||||
|
||||
const closeDrawer = (state: DrawerNavigationState): DrawerNavigationState => {
|
||||
const closeDrawer = (
|
||||
state: DrawerNavigationState<ParamListBase>
|
||||
): DrawerNavigationState<ParamListBase> => {
|
||||
if (!isDrawerOpen(state)) {
|
||||
return state;
|
||||
}
|
||||
@@ -101,11 +107,11 @@ export default function DrawerRouter({
|
||||
openByDefault,
|
||||
...rest
|
||||
}: DrawerRouterOptions): Router<
|
||||
DrawerNavigationState,
|
||||
DrawerNavigationState<ParamListBase>,
|
||||
DrawerActionType | CommonNavigationAction
|
||||
> {
|
||||
const router = (TabRouter(rest) as unknown) as Router<
|
||||
DrawerNavigationState,
|
||||
DrawerNavigationState<ParamListBase>,
|
||||
TabActionType | CommonNavigationAction
|
||||
>;
|
||||
|
||||
|
||||
@@ -36,7 +36,9 @@ export type StackActionType =
|
||||
|
||||
export type StackRouterOptions = DefaultRouterOptions;
|
||||
|
||||
export type StackNavigationState = NavigationState & {
|
||||
export type StackNavigationState<
|
||||
ParamList extends ParamListBase
|
||||
> = NavigationState<ParamList> & {
|
||||
/**
|
||||
* Type of the router, in this case, it's stack.
|
||||
*/
|
||||
@@ -96,7 +98,7 @@ export const StackActions = {
|
||||
|
||||
export default function StackRouter(options: StackRouterOptions) {
|
||||
const router: Router<
|
||||
StackNavigationState,
|
||||
StackNavigationState<ParamListBase>,
|
||||
CommonNavigationAction | StackActionType
|
||||
> = {
|
||||
...BaseRouter,
|
||||
@@ -256,10 +258,35 @@ export default function StackRouter(options: StackRouterOptions) {
|
||||
|
||||
case 'PUSH':
|
||||
if (state.routeNames.includes(action.payload.name)) {
|
||||
return {
|
||||
...state,
|
||||
index: state.index + 1,
|
||||
routes: [
|
||||
const route =
|
||||
action.payload.name && action.payload.key
|
||||
? state.routes.find(
|
||||
(route) =>
|
||||
route.name === action.payload.name &&
|
||||
route.key === action.payload.key
|
||||
)
|
||||
: undefined;
|
||||
|
||||
let routes: Route<string>[];
|
||||
|
||||
if (route) {
|
||||
routes = state.routes.filter((r) => r.key !== route.key);
|
||||
routes.push(
|
||||
action.payload.params
|
||||
? {
|
||||
...route,
|
||||
params:
|
||||
action.payload.params !== undefined
|
||||
? {
|
||||
...route.params,
|
||||
...action.payload.params,
|
||||
}
|
||||
: route.params,
|
||||
}
|
||||
: route
|
||||
);
|
||||
} else {
|
||||
routes = [
|
||||
...state.routes,
|
||||
{
|
||||
key:
|
||||
@@ -275,7 +302,13 @@ export default function StackRouter(options: StackRouterOptions) {
|
||||
}
|
||||
: action.payload.params,
|
||||
},
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
index: routes.length - 1,
|
||||
routes,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,10 @@ export type TabRouterOptions = DefaultRouterOptions & {
|
||||
backBehavior?: BackBehavior;
|
||||
};
|
||||
|
||||
export type TabNavigationState = Omit<NavigationState, 'history'> & {
|
||||
export type TabNavigationState<ParamList extends ParamListBase> = Omit<
|
||||
NavigationState<ParamList>,
|
||||
'history'
|
||||
> & {
|
||||
/**
|
||||
* Type of the router, in this case, it's tab.
|
||||
*/
|
||||
@@ -93,7 +96,7 @@ const getRouteHistory = (
|
||||
};
|
||||
|
||||
const changeIndex = (
|
||||
state: TabNavigationState,
|
||||
state: TabNavigationState<ParamListBase>,
|
||||
index: number,
|
||||
backBehavior: BackBehavior,
|
||||
initialRouteName: string | undefined
|
||||
@@ -127,7 +130,7 @@ export default function TabRouter({
|
||||
backBehavior = 'history',
|
||||
}: TabRouterOptions) {
|
||||
const router: Router<
|
||||
TabNavigationState,
|
||||
TabNavigationState<ParamListBase>,
|
||||
TabActionType | CommonNavigationAction
|
||||
> = {
|
||||
...BaseRouter,
|
||||
@@ -172,9 +175,9 @@ export default function TabRouter({
|
||||
}
|
||||
|
||||
const routes = routeNames.map((name) => {
|
||||
const route = (state as PartialState<TabNavigationState>).routes.find(
|
||||
(r) => r.name === name
|
||||
);
|
||||
const route = (state as PartialState<
|
||||
TabNavigationState<ParamListBase>
|
||||
>).routes.find((r) => r.name === name);
|
||||
|
||||
return {
|
||||
...route,
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
DrawerRouter,
|
||||
DrawerActions,
|
||||
DrawerNavigationState,
|
||||
ParamListBase,
|
||||
} from '..';
|
||||
|
||||
jest.mock('nanoid/non-secure', () => ({ nanoid: () => 'test' }));
|
||||
@@ -199,7 +200,7 @@ it('gets rehydrated state from partial state', () => {
|
||||
it("doesn't rehydrate state if it's not stale", () => {
|
||||
const router = DrawerRouter({});
|
||||
|
||||
const state: DrawerNavigationState = {
|
||||
const state: DrawerNavigationState<ParamListBase> = {
|
||||
index: 0,
|
||||
key: 'drawer-test',
|
||||
routeNames: ['bar', 'baz', 'qux'],
|
||||
@@ -340,7 +341,7 @@ it('handles open drawer action', () => {
|
||||
history: [{ type: 'route', key: 'bar' }, { type: 'drawer' }],
|
||||
});
|
||||
|
||||
const state: DrawerNavigationState = {
|
||||
const state: DrawerNavigationState<ParamListBase> = {
|
||||
stale: false as const,
|
||||
type: 'drawer' as const,
|
||||
key: 'root',
|
||||
@@ -395,7 +396,7 @@ it('handles close drawer action', () => {
|
||||
history: [{ type: 'route', key: 'bar' }],
|
||||
});
|
||||
|
||||
const state: DrawerNavigationState = {
|
||||
const state: DrawerNavigationState<ParamListBase> = {
|
||||
stale: false as const,
|
||||
type: 'drawer' as const,
|
||||
key: 'root',
|
||||
@@ -487,7 +488,7 @@ it('handles toggle drawer action', () => {
|
||||
it('updates history on focus change', () => {
|
||||
const router = DrawerRouter({ backBehavior: 'history' });
|
||||
|
||||
const state: DrawerNavigationState = {
|
||||
const state: DrawerNavigationState<ParamListBase> = {
|
||||
index: 0,
|
||||
key: 'drawer-test',
|
||||
routeNames: ['baz', 'bar'],
|
||||
|
||||
@@ -848,7 +848,7 @@ it('handles push action', () => {
|
||||
stale: false,
|
||||
type: 'stack',
|
||||
key: 'root',
|
||||
index: 3,
|
||||
index: 1,
|
||||
routeNames: ['baz', 'bar', 'qux'],
|
||||
routes: [
|
||||
{ key: 'bar', name: 'bar' },
|
||||
@@ -873,7 +873,7 @@ it('handles push action', () => {
|
||||
stale: false,
|
||||
type: 'stack',
|
||||
key: 'root',
|
||||
index: 3,
|
||||
index: 1,
|
||||
routeNames: ['baz', 'bar', 'qux'],
|
||||
routes: [
|
||||
{ key: 'bar', name: 'bar' },
|
||||
@@ -895,6 +895,73 @@ it('handles push action', () => {
|
||||
options
|
||||
)
|
||||
).toBe(null);
|
||||
|
||||
expect(
|
||||
router.getStateForAction(
|
||||
{
|
||||
stale: false,
|
||||
type: 'stack',
|
||||
key: 'root',
|
||||
index: 2,
|
||||
routeNames: ['baz', 'bar', 'qux'],
|
||||
routes: [
|
||||
{ key: 'bar-3', name: 'bar' },
|
||||
{ key: 'bar-4', name: 'bar', params: { foo: 21 } },
|
||||
{ key: 'baz-5', name: 'baz' },
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'PUSH',
|
||||
payload: { name: 'bar', key: 'bar-4', params: { bar: 29 } },
|
||||
},
|
||||
options
|
||||
)
|
||||
).toEqual({
|
||||
stale: false,
|
||||
type: 'stack',
|
||||
key: 'root',
|
||||
index: 2,
|
||||
routeNames: ['baz', 'bar', 'qux'],
|
||||
routes: [
|
||||
{ key: 'bar-3', name: 'bar' },
|
||||
{ key: 'baz-5', name: 'baz' },
|
||||
{ key: 'bar-4', name: 'bar', params: { foo: 21, bar: 29 } },
|
||||
],
|
||||
});
|
||||
|
||||
expect(
|
||||
router.getStateForAction(
|
||||
{
|
||||
stale: false,
|
||||
type: 'stack',
|
||||
key: 'root',
|
||||
index: 2,
|
||||
routeNames: ['baz', 'bar', 'qux'],
|
||||
routes: [
|
||||
{ key: 'bar-3', name: 'bar' },
|
||||
{ key: 'bar-4', name: 'bar' },
|
||||
{ key: 'baz-5', name: 'baz' },
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'PUSH',
|
||||
payload: { name: 'bar', key: 'bar-6', params: { bar: 29 } },
|
||||
},
|
||||
options
|
||||
)
|
||||
).toEqual({
|
||||
stale: false,
|
||||
type: 'stack',
|
||||
key: 'root',
|
||||
index: 3,
|
||||
routeNames: ['baz', 'bar', 'qux'],
|
||||
routes: [
|
||||
{ key: 'bar-3', name: 'bar' },
|
||||
{ key: 'bar-4', name: 'bar' },
|
||||
{ key: 'baz-5', name: 'baz' },
|
||||
{ key: 'bar-6', name: 'bar', params: { bar: 29 } },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('changes index on focus change', () => {
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import { CommonActions, TabRouter, TabActions, TabNavigationState } from '..';
|
||||
import {
|
||||
CommonActions,
|
||||
TabRouter,
|
||||
TabActions,
|
||||
TabNavigationState,
|
||||
ParamListBase,
|
||||
} from '..';
|
||||
|
||||
jest.mock('nanoid/non-secure', () => ({ nanoid: () => 'test' }));
|
||||
|
||||
@@ -217,7 +223,7 @@ it('gets rehydrated state from partial state', () => {
|
||||
it("doesn't rehydrate state if it's not stale", () => {
|
||||
const router = TabRouter({});
|
||||
|
||||
const state: TabNavigationState = {
|
||||
const state: TabNavigationState<ParamListBase> = {
|
||||
index: 0,
|
||||
key: 'tab-test',
|
||||
routeNames: ['bar', 'baz', 'qux'],
|
||||
@@ -699,7 +705,7 @@ it('handles back action with backBehavior: history', () => {
|
||||
state,
|
||||
TabActions.jumpTo('qux'),
|
||||
options
|
||||
) as TabNavigationState;
|
||||
) as TabNavigationState<ParamListBase>;
|
||||
|
||||
expect(
|
||||
router.getStateForAction(state, CommonActions.goBack(), options)
|
||||
@@ -721,7 +727,7 @@ it('handles back action with backBehavior: history', () => {
|
||||
state,
|
||||
TabActions.jumpTo('baz'),
|
||||
options
|
||||
) as TabNavigationState;
|
||||
) as TabNavigationState<ParamListBase>;
|
||||
|
||||
expect(
|
||||
router.getStateForAction(state, CommonActions.goBack(), options)
|
||||
@@ -746,7 +752,7 @@ it('handles back action with backBehavior: history', () => {
|
||||
state,
|
||||
TabActions.jumpTo('bar'),
|
||||
options
|
||||
) as TabNavigationState;
|
||||
) as TabNavigationState<ParamListBase>;
|
||||
|
||||
expect(
|
||||
router.getStateForAction(state, CommonActions.goBack(), options)
|
||||
@@ -785,7 +791,7 @@ it('handles back action with backBehavior: order', () => {
|
||||
state,
|
||||
TabActions.jumpTo('qux'),
|
||||
options
|
||||
) as TabNavigationState;
|
||||
) as TabNavigationState<ParamListBase>;
|
||||
|
||||
expect(
|
||||
router.getStateForAction(state, CommonActions.goBack(), options)
|
||||
@@ -810,7 +816,7 @@ it('handles back action with backBehavior: order', () => {
|
||||
state,
|
||||
TabActions.jumpTo('baz'),
|
||||
options
|
||||
) as TabNavigationState;
|
||||
) as TabNavigationState<ParamListBase>;
|
||||
|
||||
expect(
|
||||
router.getStateForAction(state, CommonActions.goBack(), options)
|
||||
@@ -832,7 +838,7 @@ it('handles back action with backBehavior: order', () => {
|
||||
state,
|
||||
TabActions.jumpTo('bar'),
|
||||
options
|
||||
) as TabNavigationState;
|
||||
) as TabNavigationState<ParamListBase>;
|
||||
|
||||
expect(
|
||||
router.getStateForAction(state, CommonActions.goBack(), options)
|
||||
@@ -856,7 +862,7 @@ it('handles back action with backBehavior: initialRoute', () => {
|
||||
state,
|
||||
TabActions.jumpTo('qux'),
|
||||
options
|
||||
) as TabNavigationState;
|
||||
) as TabNavigationState<ParamListBase>;
|
||||
|
||||
expect(
|
||||
router.getStateForAction(state, CommonActions.goBack(), options)
|
||||
@@ -878,7 +884,7 @@ it('handles back action with backBehavior: initialRoute', () => {
|
||||
state,
|
||||
TabActions.jumpTo('baz'),
|
||||
options
|
||||
) as TabNavigationState;
|
||||
) as TabNavigationState<ParamListBase>;
|
||||
|
||||
expect(
|
||||
router.getStateForAction(state, CommonActions.goBack(), options)
|
||||
@@ -900,7 +906,7 @@ it('handles back action with backBehavior: initialRoute', () => {
|
||||
state,
|
||||
TabActions.jumpTo('bar'),
|
||||
options
|
||||
) as TabNavigationState;
|
||||
) as TabNavigationState<ParamListBase>;
|
||||
|
||||
expect(
|
||||
router.getStateForAction(state, CommonActions.goBack(), options)
|
||||
@@ -928,7 +934,7 @@ it('handles back action with backBehavior: initialRoute and initialRouteName', (
|
||||
state,
|
||||
TabActions.jumpTo('qux'),
|
||||
options
|
||||
) as TabNavigationState;
|
||||
) as TabNavigationState<ParamListBase>;
|
||||
|
||||
expect(
|
||||
router.getStateForAction(state, CommonActions.goBack(), options)
|
||||
@@ -950,7 +956,7 @@ it('handles back action with backBehavior: initialRoute and initialRouteName', (
|
||||
state,
|
||||
TabActions.jumpTo('bar'),
|
||||
options
|
||||
) as TabNavigationState;
|
||||
) as TabNavigationState<ParamListBase>;
|
||||
|
||||
expect(
|
||||
router.getStateForAction(state, CommonActions.goBack(), options)
|
||||
@@ -972,7 +978,7 @@ it('handles back action with backBehavior: initialRoute and initialRouteName', (
|
||||
state,
|
||||
TabActions.jumpTo('baz'),
|
||||
options
|
||||
) as TabNavigationState;
|
||||
) as TabNavigationState<ParamListBase>;
|
||||
|
||||
expect(
|
||||
router.getStateForAction(state, CommonActions.goBack(), options)
|
||||
@@ -992,7 +998,7 @@ it('handles back action with backBehavior: none', () => {
|
||||
state,
|
||||
TabActions.jumpTo('baz'),
|
||||
options
|
||||
) as TabNavigationState;
|
||||
) as TabNavigationState<ParamListBase>;
|
||||
|
||||
expect(
|
||||
router.getStateForAction(state, CommonActions.goBack(), options)
|
||||
@@ -1006,7 +1012,7 @@ it('updates route key history on navigate and jump to', () => {
|
||||
routeParamList: {},
|
||||
};
|
||||
|
||||
let state: TabNavigationState = {
|
||||
let state: TabNavigationState<ParamListBase> = {
|
||||
index: 1,
|
||||
key: 'tab-test',
|
||||
routeNames: ['bar', 'baz', 'qux'],
|
||||
@@ -1024,7 +1030,7 @@ it('updates route key history on navigate and jump to', () => {
|
||||
state,
|
||||
TabActions.jumpTo('qux'),
|
||||
options
|
||||
) as TabNavigationState;
|
||||
) as TabNavigationState<ParamListBase>;
|
||||
|
||||
expect(state.history).toEqual([
|
||||
{ type: 'route', key: 'baz-0' },
|
||||
@@ -1035,7 +1041,7 @@ it('updates route key history on navigate and jump to', () => {
|
||||
state,
|
||||
CommonActions.navigate('bar'),
|
||||
options
|
||||
) as TabNavigationState;
|
||||
) as TabNavigationState<ParamListBase>;
|
||||
|
||||
expect(state.history).toEqual([
|
||||
{ type: 'route', key: 'baz-0' },
|
||||
@@ -1047,7 +1053,7 @@ it('updates route key history on navigate and jump to', () => {
|
||||
state,
|
||||
TabActions.jumpTo('baz'),
|
||||
options
|
||||
) as TabNavigationState;
|
||||
) as TabNavigationState<ParamListBase>;
|
||||
|
||||
expect(state.history).toEqual([
|
||||
{ type: 'route', key: 'qux-0' },
|
||||
@@ -1059,7 +1065,7 @@ it('updates route key history on navigate and jump to', () => {
|
||||
state,
|
||||
CommonActions.goBack(),
|
||||
options
|
||||
) as TabNavigationState;
|
||||
) as TabNavigationState<ParamListBase>;
|
||||
|
||||
expect(state.history).toEqual([
|
||||
{ type: 'route', key: 'qux-0' },
|
||||
@@ -1070,7 +1076,7 @@ it('updates route key history on navigate and jump to', () => {
|
||||
state,
|
||||
CommonActions.goBack(),
|
||||
options
|
||||
) as TabNavigationState;
|
||||
) as TabNavigationState<ParamListBase>;
|
||||
|
||||
expect(state.history).toEqual([{ type: 'route', key: 'qux-0' }]);
|
||||
});
|
||||
|
||||
@@ -2,7 +2,16 @@ import type * as CommonActions from './CommonActions';
|
||||
|
||||
export type CommonNavigationAction = CommonActions.Action;
|
||||
|
||||
export type NavigationState = Readonly<{
|
||||
type NavigationRoute<
|
||||
ParamList extends ParamListBase,
|
||||
RouteName extends keyof ParamList
|
||||
> = Route<Extract<RouteName, string>, ParamList[RouteName]> & {
|
||||
state?: NavigationState | PartialState<NavigationState>;
|
||||
};
|
||||
|
||||
export type NavigationState<
|
||||
ParamList extends ParamListBase = ParamListBase
|
||||
> = Readonly<{
|
||||
/**
|
||||
* Unique key for the navigation state.
|
||||
*/
|
||||
@@ -14,7 +23,7 @@ export type NavigationState = Readonly<{
|
||||
/**
|
||||
* List of valid route names as defined in the screen components.
|
||||
*/
|
||||
routeNames: string[];
|
||||
routeNames: Extract<keyof ParamList, string>[];
|
||||
/**
|
||||
* Alternative entries for history.
|
||||
*/
|
||||
@@ -22,9 +31,7 @@ export type NavigationState = Readonly<{
|
||||
/**
|
||||
* List of rendered routes.
|
||||
*/
|
||||
routes: (Route<string> & {
|
||||
state?: NavigationState | PartialState<NavigationState>;
|
||||
})[];
|
||||
routes: NavigationRoute<ParamList, keyof ParamList>[];
|
||||
/**
|
||||
* Custom type for the state, whether it's for tab, stack, drawer etc.
|
||||
* During rehydration, the state will be discarded if type doesn't match with router type.
|
||||
@@ -43,19 +50,24 @@ export type InitialState = Readonly<
|
||||
}
|
||||
>;
|
||||
|
||||
export type PartialRoute<R extends Route<string>> = Omit<R, 'key'> & {
|
||||
key?: string;
|
||||
state?: PartialState<NavigationState>;
|
||||
};
|
||||
|
||||
export type PartialState<State extends NavigationState> = Partial<
|
||||
Omit<State, 'stale' | 'type' | 'key' | 'routes' | 'routeNames'>
|
||||
> &
|
||||
Readonly<{
|
||||
stale?: true;
|
||||
type?: string;
|
||||
routes: (Omit<Route<string>, 'key'> & {
|
||||
key?: string;
|
||||
state?: InitialState;
|
||||
})[];
|
||||
routes: PartialRoute<Route<string>>[];
|
||||
}>;
|
||||
|
||||
export type Route<RouteName extends string> = Readonly<{
|
||||
export type Route<
|
||||
RouteName extends string,
|
||||
Params extends object | undefined = object | undefined
|
||||
> = Readonly<{
|
||||
/**
|
||||
* Unique key for the route.
|
||||
*/
|
||||
@@ -64,11 +76,20 @@ export type Route<RouteName extends string> = Readonly<{
|
||||
* User-provided name for the route.
|
||||
*/
|
||||
name: RouteName;
|
||||
/**
|
||||
* Params for the route.
|
||||
*/
|
||||
params?: object;
|
||||
}>;
|
||||
}> &
|
||||
(undefined extends Params
|
||||
? Readonly<{
|
||||
/**
|
||||
* Params for this route
|
||||
*/
|
||||
params?: Readonly<Params>;
|
||||
}>
|
||||
: Readonly<{
|
||||
/**
|
||||
* Params for this route
|
||||
*/
|
||||
params: Readonly<Params>;
|
||||
}>);
|
||||
|
||||
export type ParamListBase = Record<string, object | undefined>;
|
||||
|
||||
@@ -95,12 +116,12 @@ export type ActionCreators<Action extends NavigationAction> = {
|
||||
[key: string]: (...args: any) => Action;
|
||||
};
|
||||
|
||||
export type DefaultRouterOptions = {
|
||||
export type DefaultRouterOptions<RouteName extends string = string> = {
|
||||
/**
|
||||
* Name of the route to focus by on initial render.
|
||||
* If not specified, usually the first route is used.
|
||||
*/
|
||||
initialRouteName?: string;
|
||||
initialRouteName?: RouteName;
|
||||
};
|
||||
|
||||
export type RouterFactory<
|
||||
|
||||
@@ -3,6 +3,65 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [5.12.0](https://github.com/react-navigation/react-navigation/compare/@react-navigation/stack@5.11.1...@react-navigation/stack@5.12.0) (2020-11-03)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add a headerBackAccessibilityLabel option in stack ([c326c10](https://github.com/react-navigation/react-navigation/commit/c326c106f9a2492ff45bdc8da9bfbc404e48786a)), closes [#9016](https://github.com/react-navigation/react-navigation/issues/9016)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.11.1](https://github.com/react-navigation/react-navigation/compare/@react-navigation/stack@5.11.0...@react-navigation/stack@5.11.1) (2020-10-30)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/stack
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.11.0](https://github.com/react-navigation/react-navigation/compare/@react-navigation/stack@5.10.0...@react-navigation/stack@5.11.0) (2020-10-28)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* enable react-native-screens in Stack by default on iOS ([45dbe5c](https://github.com/react-navigation/react-navigation/commit/45dbe5c40ebc66c62593b3fad35cff3048b888a4))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.10.0](https://github.com/react-navigation/react-navigation/compare/@react-navigation/stack@5.9.3...@react-navigation/stack@5.10.0) (2020-10-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add missing check for parent header when calculating height ([da91cec](https://github.com/react-navigation/react-navigation/commit/da91cec941e7dec352ba1910901904d769c9f3a3))
|
||||
* don't set statusbar height on nested header by default ([38e17aa](https://github.com/react-navigation/react-navigation/commit/38e17aae939974b47fe5c77d8670b9a4544250f2))
|
||||
* fix header buttons not pressable when headerTransparent=true & headerMode=float ([#8804](https://github.com/react-navigation/react-navigation/issues/8804)) ([d6cac67](https://github.com/react-navigation/react-navigation/commit/d6cac6713a51e4de320fc1c7ece72a2b92241574)), closes [#8731](https://github.com/react-navigation/react-navigation/issues/8731)
|
||||
* set needsOffscreenAlphaCompositing and update default android animation ([8ee0dda](https://github.com/react-navigation/react-navigation/commit/8ee0dda155b4cde43be1e58170e44823b54e7d4f)), closes [#8696](https://github.com/react-navigation/react-navigation/issues/8696)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add optional screens per navigator ([#8805](https://github.com/react-navigation/react-navigation/issues/8805)) ([7196889](https://github.com/react-navigation/react-navigation/commit/7196889bf1218eb6a736d9475e33a909c2248c3b))
|
||||
* improve types for navigation state ([#8980](https://github.com/react-navigation/react-navigation/issues/8980)) ([7dc2f58](https://github.com/react-navigation/react-navigation/commit/7dc2f5832e371473f3263c01ab39824eb9e2057d))
|
||||
* update helper types to have navigator specific methods ([f51086e](https://github.com/react-navigation/react-navigation/commit/f51086edea42f2382dac8c6914aac8574132114b))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.9.3](https://github.com/react-navigation/react-navigation/compare/@react-navigation/stack@5.9.2...@react-navigation/stack@5.9.3) (2020-10-07)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/stack
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.9.2](https://github.com/react-navigation/react-navigation/compare/@react-navigation/stack@5.9.1...@react-navigation/stack@5.9.2) (2020-09-28)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/stack
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/stack",
|
||||
"description": "Stack navigator component for iOS and Android with animated transitions and gestures",
|
||||
"version": "5.9.2",
|
||||
"version": "5.12.0",
|
||||
"keywords": [
|
||||
"react-native-component",
|
||||
"react-component",
|
||||
@@ -40,17 +40,17 @@
|
||||
"clean": "del lib"
|
||||
},
|
||||
"dependencies": {
|
||||
"color": "^3.1.2",
|
||||
"react-native-iphone-x-helper": "^1.2.1"
|
||||
"color": "^3.1.3",
|
||||
"react-native-iphone-x-helper": "^1.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.16.2",
|
||||
"@react-native-community/masked-view": "^0.1.10",
|
||||
"@react-navigation/native": "^5.7.5",
|
||||
"@testing-library/react-native": "^7.0.2",
|
||||
"@react-navigation/native": "^5.8.3",
|
||||
"@testing-library/react-native": "^7.1.0",
|
||||
"@types/color": "^3.0.1",
|
||||
"@types/react": "^16.9.49",
|
||||
"@types/react-native": "^0.63.20",
|
||||
"@types/react": "^16.9.53",
|
||||
"@types/react-native": "^0.63.30",
|
||||
"del-cli": "^3.0.1",
|
||||
"react": "~16.13.1",
|
||||
"react-native": "~0.63.2",
|
||||
|
||||
@@ -289,8 +289,8 @@ export function forScaleFromCenterAndroid({
|
||||
);
|
||||
|
||||
const opacity = progress.interpolate({
|
||||
inputRange: [0, 0.8, 1, 1.2, 2],
|
||||
outputRange: [0, 0.5, 1, 0.33, 0],
|
||||
inputRange: [0, 0.75, 0.875, 1, 1.0825, 1.2075, 2],
|
||||
outputRange: [0, 0, 1, 1, 1, 1, 0],
|
||||
});
|
||||
|
||||
const scale = conditional(
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
import type { TransitionPreset } from '../types';
|
||||
|
||||
const ANDROID_VERSION_PIE = 28;
|
||||
const ANDROID_VERSION_10 = 29;
|
||||
|
||||
/**
|
||||
* Standard iOS navigation transition.
|
||||
@@ -102,10 +103,13 @@ export const ScaleFromCenterAndroid: TransitionPreset = {
|
||||
*/
|
||||
export const DefaultTransition = Platform.select({
|
||||
ios: SlideFromRightIOS,
|
||||
default:
|
||||
Platform.OS === 'android' && Platform.Version >= ANDROID_VERSION_PIE
|
||||
android:
|
||||
Platform.Version >= ANDROID_VERSION_10
|
||||
? ScaleFromCenterAndroid
|
||||
: Platform.Version >= ANDROID_VERSION_PIE
|
||||
? RevealFromBottomAndroid
|
||||
: FadeFromBottomAndroid,
|
||||
default: ScaleFromCenterAndroid,
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,6 +9,8 @@ import {
|
||||
StackRouterOptions,
|
||||
StackNavigationState,
|
||||
StackActions,
|
||||
ParamListBase,
|
||||
StackActionHelpers,
|
||||
} from '@react-navigation/native';
|
||||
import StackView from '../views/Stack/StackView';
|
||||
import type {
|
||||
@@ -36,8 +38,9 @@ function StackNavigator({
|
||||
};
|
||||
|
||||
const { state, descriptors, navigation } = useNavigationBuilder<
|
||||
StackNavigationState,
|
||||
StackNavigationState<ParamListBase>,
|
||||
StackRouterOptions,
|
||||
StackActionHelpers<ParamListBase>,
|
||||
StackNavigationOptions,
|
||||
StackNavigationEventMap
|
||||
>(StackRouter, {
|
||||
@@ -91,7 +94,7 @@ function StackNavigator({
|
||||
}
|
||||
|
||||
export default createNavigatorFactory<
|
||||
StackNavigationState,
|
||||
StackNavigationState<ParamListBase>,
|
||||
StackNavigationOptions,
|
||||
StackNavigationEventMap,
|
||||
typeof StackNavigator
|
||||
|
||||
@@ -44,7 +44,8 @@ export type StackNavigationEventMap = {
|
||||
export type StackNavigationHelpers = NavigationHelpers<
|
||||
ParamListBase,
|
||||
StackNavigationEventMap
|
||||
>;
|
||||
> &
|
||||
StackActionHelpers<ParamListBase>;
|
||||
|
||||
export type StackNavigationProp<
|
||||
ParamList extends ParamListBase,
|
||||
@@ -52,7 +53,7 @@ export type StackNavigationProp<
|
||||
> = NavigationProp<
|
||||
ParamList,
|
||||
RouteName,
|
||||
StackNavigationState,
|
||||
StackNavigationState<ParamList>,
|
||||
StackNavigationOptions,
|
||||
StackNavigationEventMap
|
||||
> &
|
||||
@@ -144,6 +145,10 @@ export type StackHeaderOptions = {
|
||||
* Whether back button title font should scale to respect Text Size accessibility settings. Defaults to `false`.
|
||||
*/
|
||||
headerBackAllowFontScaling?: boolean;
|
||||
/**
|
||||
* Accessibility label for the header back button.
|
||||
*/
|
||||
headerBackAccessibilityLabel?: string;
|
||||
/**
|
||||
* Title string used by the back button on iOS. Defaults to the previous scene's `headerTitle`.
|
||||
* Use `headerBackTitleVisible: false` to hide it.
|
||||
@@ -250,7 +255,7 @@ export type StackHeaderProps = {
|
||||
export type StackDescriptor = Descriptor<
|
||||
ParamListBase,
|
||||
string,
|
||||
StackNavigationState,
|
||||
StackNavigationState<ParamListBase>,
|
||||
StackNavigationOptions
|
||||
>;
|
||||
|
||||
@@ -320,11 +325,11 @@ export type StackNavigationOptions = StackHeaderOptions &
|
||||
*/
|
||||
gestureResponseDistance?: {
|
||||
/**
|
||||
* Distance for horizontal direction. Defaults to 25.
|
||||
* Distance for vertical direction. Defaults to 135.
|
||||
*/
|
||||
vertical?: number;
|
||||
/**
|
||||
* Distance for vertical direction. Defaults to 135.
|
||||
* Distance for horizontal direction. Defaults to 25.
|
||||
*/
|
||||
horizontal?: number;
|
||||
};
|
||||
@@ -344,6 +349,13 @@ export type StackNavigationOptions = StackHeaderOptions &
|
||||
bottom?: number;
|
||||
left?: number;
|
||||
};
|
||||
/**
|
||||
* Whether to detach the previous screen from the view hierarchy to save memory.
|
||||
* Set it to `false` if you need the previous screen to be seen through the active screen.
|
||||
* Only applicable if `detachInactiveScreens` isn't set to `false`.
|
||||
* Defaults to `false` for the last screen when mode='modal', otherwise `true`.
|
||||
*/
|
||||
detachPreviousScreen?: boolean;
|
||||
};
|
||||
|
||||
export type StackNavigationConfig = {
|
||||
@@ -354,6 +366,12 @@ export type StackNavigationConfig = {
|
||||
* Defaults to `true`.
|
||||
*/
|
||||
keyboardHandlingEnabled?: boolean;
|
||||
/**
|
||||
* Whether inactive screens should be detached from the view hierarchy to save memory.
|
||||
* Make sure to call `enableScreens` from `react-native-screens` to make it work.
|
||||
* Defaults to `true`.
|
||||
*/
|
||||
detachInactiveScreens?: boolean;
|
||||
};
|
||||
|
||||
export type StackHeaderLeftButtonProps = {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import { View, StyleSheet, StyleProp, ViewStyle } from 'react-native';
|
||||
import { Animated, View, StyleSheet, StyleProp, ViewStyle } from 'react-native';
|
||||
import {
|
||||
NavigationContext,
|
||||
NavigationRouteContext,
|
||||
@@ -60,7 +60,7 @@ export default function HeaderContainer({
|
||||
const parentPreviousScene = React.useContext(PreviousSceneContext);
|
||||
|
||||
return (
|
||||
<View pointerEvents="box-none" style={style}>
|
||||
<Animated.View pointerEvents="box-none" style={style}>
|
||||
{scenes.slice(-3).map((scene, i, self) => {
|
||||
if ((mode === 'screen' && i !== self.length - 1) || !scene) {
|
||||
return null;
|
||||
@@ -130,11 +130,14 @@ export default function HeaderContainer({
|
||||
<View
|
||||
onLayout={
|
||||
onContentHeightChange
|
||||
? (e) =>
|
||||
? (e) => {
|
||||
const { height } = e.nativeEvent.layout;
|
||||
|
||||
onContentHeightChange({
|
||||
route: scene.route,
|
||||
height: e.nativeEvent.layout.height,
|
||||
})
|
||||
height,
|
||||
});
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
pointerEvents={isFocused ? 'box-none' : 'none'}
|
||||
@@ -156,7 +159,7 @@ export default function HeaderContainer({
|
||||
</NavigationContext.Provider>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
</Animated.View>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import type { EdgeInsets } from 'react-native-safe-area-context';
|
||||
import type { Route } from '@react-navigation/native';
|
||||
import HeaderBackButton from './HeaderBackButton';
|
||||
import HeaderBackground from './HeaderBackground';
|
||||
import HeaderShownContext from '../../utils/HeaderShownContext';
|
||||
import memoize from '../../utils/memoize';
|
||||
import type {
|
||||
Layout,
|
||||
@@ -32,11 +33,6 @@ type Props = StackHeaderOptions & {
|
||||
styleInterpolator: StackHeaderStyleInterpolator;
|
||||
};
|
||||
|
||||
type State = {
|
||||
titleLayout?: Layout;
|
||||
leftLabelLayout?: Layout;
|
||||
};
|
||||
|
||||
const warnIfHeaderStylesDefined = (styles: Record<string, any>) => {
|
||||
Object.keys(styles).forEach((styleProp) => {
|
||||
const value = styles[styleProp];
|
||||
@@ -76,30 +72,35 @@ export const getDefaultHeaderHeight = (
|
||||
return headerHeight + statusBarHeight;
|
||||
};
|
||||
|
||||
export default class HeaderSegment extends React.Component<Props, State> {
|
||||
state: State = {};
|
||||
export default function HeaderSegment(props: Props) {
|
||||
const [leftLabelLayout, setLeftLabelLayout] = React.useState<
|
||||
Layout | undefined
|
||||
>(undefined);
|
||||
|
||||
private handleTitleLayout = (e: LayoutChangeEvent) => {
|
||||
const [titleLayout, setTitleLayout] = React.useState<Layout | undefined>(
|
||||
undefined
|
||||
);
|
||||
|
||||
const isParentHeaderShown = React.useContext(HeaderShownContext);
|
||||
|
||||
const handleTitleLayout = (e: LayoutChangeEvent) => {
|
||||
const { height, width } = e.nativeEvent.layout;
|
||||
|
||||
this.setState(({ titleLayout }) => {
|
||||
setTitleLayout((titleLayout) => {
|
||||
if (
|
||||
titleLayout &&
|
||||
height === titleLayout.height &&
|
||||
width === titleLayout.width
|
||||
) {
|
||||
return null;
|
||||
return titleLayout;
|
||||
}
|
||||
|
||||
return {
|
||||
titleLayout: { height, width },
|
||||
};
|
||||
return { height, width };
|
||||
});
|
||||
};
|
||||
|
||||
private handleLeftLabelLayout = (e: LayoutChangeEvent) => {
|
||||
const handleLeftLabelLayout = (e: LayoutChangeEvent) => {
|
||||
const { height, width } = e.nativeEvent.layout;
|
||||
const { leftLabelLayout } = this.state;
|
||||
|
||||
if (
|
||||
leftLabelLayout &&
|
||||
@@ -109,10 +110,10 @@ export default class HeaderSegment extends React.Component<Props, State> {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({ leftLabelLayout: { height, width } });
|
||||
setLeftLabelLayout({ height, width });
|
||||
};
|
||||
|
||||
private getInterpolatedStyle = memoize(
|
||||
const getInterpolatedStyle = memoize(
|
||||
(
|
||||
styleInterpolator: StackHeaderStyleInterpolator,
|
||||
layout: Layout,
|
||||
@@ -137,258 +138,253 @@ export default class HeaderSegment extends React.Component<Props, State> {
|
||||
})
|
||||
);
|
||||
|
||||
render() {
|
||||
const {
|
||||
scene,
|
||||
layout,
|
||||
insets,
|
||||
title: currentTitle,
|
||||
leftLabel: previousTitle,
|
||||
onGoBack,
|
||||
headerTitle,
|
||||
headerTitleAlign = Platform.select({
|
||||
ios: 'center',
|
||||
default: 'left',
|
||||
}),
|
||||
headerLeft: left = onGoBack
|
||||
? (props: StackHeaderLeftButtonProps) => <HeaderBackButton {...props} />
|
||||
: undefined,
|
||||
headerTransparent,
|
||||
headerTintColor,
|
||||
headerBackground,
|
||||
headerRight: right,
|
||||
headerBackImage: backImage,
|
||||
headerBackTitle: leftLabel,
|
||||
headerBackTitleVisible,
|
||||
headerTruncatedBackTitle: truncatedLabel,
|
||||
headerPressColorAndroid: pressColorAndroid,
|
||||
headerBackAllowFontScaling: backAllowFontScaling,
|
||||
headerTitleAllowFontScaling: titleAllowFontScaling,
|
||||
headerTitleStyle: customTitleStyle,
|
||||
headerBackTitleStyle: customLeftLabelStyle,
|
||||
headerLeftContainerStyle: leftContainerStyle,
|
||||
headerRightContainerStyle: rightContainerStyle,
|
||||
headerTitleContainerStyle: titleContainerStyle,
|
||||
headerStyle: customHeaderStyle,
|
||||
headerStatusBarHeight = insets.top,
|
||||
styleInterpolator,
|
||||
} = this.props;
|
||||
const {
|
||||
scene,
|
||||
layout,
|
||||
insets,
|
||||
title: currentTitle,
|
||||
leftLabel: previousTitle,
|
||||
onGoBack,
|
||||
headerTitle,
|
||||
headerTitleAlign = Platform.select({
|
||||
ios: 'center',
|
||||
default: 'left',
|
||||
}),
|
||||
headerLeft: left = onGoBack
|
||||
? (props: StackHeaderLeftButtonProps) => <HeaderBackButton {...props} />
|
||||
: undefined,
|
||||
headerTransparent,
|
||||
headerTintColor,
|
||||
headerBackground,
|
||||
headerRight: right,
|
||||
headerBackImage: backImage,
|
||||
headerBackTitle: leftLabel,
|
||||
headerBackTitleVisible,
|
||||
headerTruncatedBackTitle: truncatedLabel,
|
||||
headerPressColorAndroid: pressColorAndroid,
|
||||
headerBackAccessibilityLabel: backAccessibilityLabel,
|
||||
headerBackAllowFontScaling: backAllowFontScaling,
|
||||
headerTitleAllowFontScaling: titleAllowFontScaling,
|
||||
headerTitleStyle: customTitleStyle,
|
||||
headerBackTitleStyle: customLeftLabelStyle,
|
||||
headerLeftContainerStyle: leftContainerStyle,
|
||||
headerRightContainerStyle: rightContainerStyle,
|
||||
headerTitleContainerStyle: titleContainerStyle,
|
||||
headerStyle: customHeaderStyle,
|
||||
headerStatusBarHeight = isParentHeaderShown ? 0 : insets.top,
|
||||
styleInterpolator,
|
||||
} = props;
|
||||
|
||||
const { leftLabelLayout, titleLayout } = this.state;
|
||||
const defaultHeight = getDefaultHeaderHeight(layout, headerStatusBarHeight);
|
||||
|
||||
const defaultHeight = getDefaultHeaderHeight(layout, headerStatusBarHeight);
|
||||
const {
|
||||
height = defaultHeight,
|
||||
minHeight,
|
||||
maxHeight,
|
||||
backgroundColor,
|
||||
borderBottomColor,
|
||||
borderBottomEndRadius,
|
||||
borderBottomLeftRadius,
|
||||
borderBottomRightRadius,
|
||||
borderBottomStartRadius,
|
||||
borderBottomWidth,
|
||||
borderColor,
|
||||
borderEndColor,
|
||||
borderEndWidth,
|
||||
borderLeftColor,
|
||||
borderLeftWidth,
|
||||
borderRadius,
|
||||
borderRightColor,
|
||||
borderRightWidth,
|
||||
borderStartColor,
|
||||
borderStartWidth,
|
||||
borderStyle,
|
||||
borderTopColor,
|
||||
borderTopEndRadius,
|
||||
borderTopLeftRadius,
|
||||
borderTopRightRadius,
|
||||
borderTopStartRadius,
|
||||
borderTopWidth,
|
||||
borderWidth,
|
||||
// @ts-expect-error: web support for shadow
|
||||
boxShadow,
|
||||
elevation,
|
||||
shadowColor,
|
||||
shadowOffset,
|
||||
shadowOpacity,
|
||||
shadowRadius,
|
||||
opacity,
|
||||
transform,
|
||||
...unsafeStyles
|
||||
} = StyleSheet.flatten(customHeaderStyle || {}) as ViewStyle;
|
||||
|
||||
const {
|
||||
height = defaultHeight,
|
||||
minHeight,
|
||||
maxHeight,
|
||||
backgroundColor,
|
||||
borderBottomColor,
|
||||
borderBottomEndRadius,
|
||||
borderBottomLeftRadius,
|
||||
borderBottomRightRadius,
|
||||
borderBottomStartRadius,
|
||||
borderBottomWidth,
|
||||
borderColor,
|
||||
borderEndColor,
|
||||
borderEndWidth,
|
||||
borderLeftColor,
|
||||
borderLeftWidth,
|
||||
borderRadius,
|
||||
borderRightColor,
|
||||
borderRightWidth,
|
||||
borderStartColor,
|
||||
borderStartWidth,
|
||||
borderStyle,
|
||||
borderTopColor,
|
||||
borderTopEndRadius,
|
||||
borderTopLeftRadius,
|
||||
borderTopRightRadius,
|
||||
borderTopStartRadius,
|
||||
borderTopWidth,
|
||||
borderWidth,
|
||||
// @ts-expect-error: web support for shadow
|
||||
boxShadow,
|
||||
elevation,
|
||||
shadowColor,
|
||||
shadowOffset,
|
||||
shadowOpacity,
|
||||
shadowRadius,
|
||||
opacity,
|
||||
transform,
|
||||
...unsafeStyles
|
||||
} = StyleSheet.flatten(customHeaderStyle || {}) as ViewStyle;
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
warnIfHeaderStylesDefined(unsafeStyles);
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
warnIfHeaderStylesDefined(unsafeStyles);
|
||||
const safeStyles: ViewStyle = {
|
||||
backgroundColor,
|
||||
borderBottomColor,
|
||||
borderBottomEndRadius,
|
||||
borderBottomLeftRadius,
|
||||
borderBottomRightRadius,
|
||||
borderBottomStartRadius,
|
||||
borderBottomWidth,
|
||||
borderColor,
|
||||
borderEndColor,
|
||||
borderEndWidth,
|
||||
borderLeftColor,
|
||||
borderLeftWidth,
|
||||
borderRadius,
|
||||
borderRightColor,
|
||||
borderRightWidth,
|
||||
borderStartColor,
|
||||
borderStartWidth,
|
||||
borderStyle,
|
||||
borderTopColor,
|
||||
borderTopEndRadius,
|
||||
borderTopLeftRadius,
|
||||
borderTopRightRadius,
|
||||
borderTopStartRadius,
|
||||
borderTopWidth,
|
||||
borderWidth,
|
||||
// @ts-expect-error: boxShadow is only for Web
|
||||
boxShadow,
|
||||
elevation,
|
||||
shadowColor,
|
||||
shadowOffset,
|
||||
shadowOpacity,
|
||||
shadowRadius,
|
||||
opacity,
|
||||
transform,
|
||||
};
|
||||
|
||||
// Setting a property to undefined triggers default style
|
||||
// So we need to filter them out
|
||||
// Users can use `null` instead
|
||||
for (const styleProp in safeStyles) {
|
||||
// @ts-expect-error: typescript wrongly complains that styleProp cannot be used to index safeStyles
|
||||
if (safeStyles[styleProp] === undefined) {
|
||||
// @ts-expect-error
|
||||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
||||
delete safeStyles[styleProp];
|
||||
}
|
||||
}
|
||||
|
||||
const safeStyles: ViewStyle = {
|
||||
backgroundColor,
|
||||
borderBottomColor,
|
||||
borderBottomEndRadius,
|
||||
borderBottomLeftRadius,
|
||||
borderBottomRightRadius,
|
||||
borderBottomStartRadius,
|
||||
borderBottomWidth,
|
||||
borderColor,
|
||||
borderEndColor,
|
||||
borderEndWidth,
|
||||
borderLeftColor,
|
||||
borderLeftWidth,
|
||||
borderRadius,
|
||||
borderRightColor,
|
||||
borderRightWidth,
|
||||
borderStartColor,
|
||||
borderStartWidth,
|
||||
borderStyle,
|
||||
borderTopColor,
|
||||
borderTopEndRadius,
|
||||
borderTopLeftRadius,
|
||||
borderTopRightRadius,
|
||||
borderTopStartRadius,
|
||||
borderTopWidth,
|
||||
borderWidth,
|
||||
// @ts-expect-error: boxShadow is only for Web
|
||||
boxShadow,
|
||||
elevation,
|
||||
shadowColor,
|
||||
shadowOffset,
|
||||
shadowOpacity,
|
||||
shadowRadius,
|
||||
opacity,
|
||||
transform,
|
||||
};
|
||||
const {
|
||||
titleStyle,
|
||||
leftButtonStyle,
|
||||
leftLabelStyle,
|
||||
rightButtonStyle,
|
||||
backgroundStyle,
|
||||
} = getInterpolatedStyle(
|
||||
styleInterpolator,
|
||||
layout,
|
||||
scene.progress.current,
|
||||
scene.progress.next,
|
||||
titleLayout,
|
||||
previousTitle ? leftLabelLayout : undefined,
|
||||
typeof height === 'number' ? height : defaultHeight
|
||||
);
|
||||
|
||||
// Setting a property to undefined triggers default style
|
||||
// So we need to filter them out
|
||||
// Users can use `null` instead
|
||||
for (const styleProp in safeStyles) {
|
||||
// @ts-expect-error: typescript wrongly complains that styleProp cannot be used to index safeStyles
|
||||
if (safeStyles[styleProp] === undefined) {
|
||||
// @ts-expect-error
|
||||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
||||
delete safeStyles[styleProp];
|
||||
}
|
||||
}
|
||||
const leftButton = left
|
||||
? left({
|
||||
backImage,
|
||||
pressColorAndroid,
|
||||
accessibilityLabel: backAccessibilityLabel,
|
||||
allowFontScaling: backAllowFontScaling,
|
||||
onPress: onGoBack,
|
||||
labelVisible: headerBackTitleVisible,
|
||||
label: leftLabel !== undefined ? leftLabel : previousTitle,
|
||||
truncatedLabel,
|
||||
labelStyle: [leftLabelStyle, customLeftLabelStyle],
|
||||
onLabelLayout: handleLeftLabelLayout,
|
||||
screenLayout: layout,
|
||||
titleLayout,
|
||||
tintColor: headerTintColor,
|
||||
canGoBack: Boolean(onGoBack),
|
||||
})
|
||||
: null;
|
||||
|
||||
const {
|
||||
titleStyle,
|
||||
leftButtonStyle,
|
||||
leftLabelStyle,
|
||||
rightButtonStyle,
|
||||
backgroundStyle,
|
||||
} = this.getInterpolatedStyle(
|
||||
styleInterpolator,
|
||||
layout,
|
||||
scene.progress.current,
|
||||
scene.progress.next,
|
||||
titleLayout,
|
||||
previousTitle ? leftLabelLayout : undefined,
|
||||
typeof height === 'number' ? height : defaultHeight
|
||||
);
|
||||
const rightButton = right ? right({ tintColor: headerTintColor }) : null;
|
||||
|
||||
const leftButton = left
|
||||
? left({
|
||||
backImage,
|
||||
pressColorAndroid,
|
||||
allowFontScaling: backAllowFontScaling,
|
||||
onPress: onGoBack,
|
||||
labelVisible: headerBackTitleVisible,
|
||||
label: leftLabel !== undefined ? leftLabel : previousTitle,
|
||||
truncatedLabel,
|
||||
labelStyle: [leftLabelStyle, customLeftLabelStyle],
|
||||
onLabelLayout: this.handleLeftLabelLayout,
|
||||
screenLayout: layout,
|
||||
titleLayout,
|
||||
tintColor: headerTintColor,
|
||||
canGoBack: Boolean(onGoBack),
|
||||
})
|
||||
: null;
|
||||
|
||||
const rightButton = right ? right({ tintColor: headerTintColor }) : null;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Animated.View
|
||||
pointerEvents="box-none"
|
||||
style={[StyleSheet.absoluteFill, { zIndex: 0 }, backgroundStyle]}
|
||||
>
|
||||
{headerBackground ? (
|
||||
headerBackground({ style: safeStyles })
|
||||
) : headerTransparent ? null : (
|
||||
<HeaderBackground style={safeStyles} />
|
||||
)}
|
||||
</Animated.View>
|
||||
<Animated.View
|
||||
pointerEvents="box-none"
|
||||
style={[{ height, minHeight, maxHeight, opacity, transform }]}
|
||||
>
|
||||
<View
|
||||
pointerEvents="none"
|
||||
style={{ height: headerStatusBarHeight }}
|
||||
/>
|
||||
<View pointerEvents="box-none" style={styles.content}>
|
||||
{leftButton ? (
|
||||
<Animated.View
|
||||
pointerEvents="box-none"
|
||||
style={[
|
||||
styles.left,
|
||||
{ left: insets.left },
|
||||
leftButtonStyle,
|
||||
leftContainerStyle,
|
||||
]}
|
||||
>
|
||||
{leftButton}
|
||||
</Animated.View>
|
||||
) : null}
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Animated.View
|
||||
pointerEvents="box-none"
|
||||
style={[StyleSheet.absoluteFill, { zIndex: 0 }, backgroundStyle]}
|
||||
>
|
||||
{headerBackground ? (
|
||||
headerBackground({ style: safeStyles })
|
||||
) : headerTransparent ? null : (
|
||||
<HeaderBackground style={safeStyles} />
|
||||
)}
|
||||
</Animated.View>
|
||||
<Animated.View
|
||||
pointerEvents="box-none"
|
||||
style={[{ height, minHeight, maxHeight, opacity, transform }]}
|
||||
>
|
||||
<View pointerEvents="none" style={{ height: headerStatusBarHeight }} />
|
||||
<View pointerEvents="box-none" style={styles.content}>
|
||||
{leftButton ? (
|
||||
<Animated.View
|
||||
pointerEvents="box-none"
|
||||
style={[
|
||||
headerTitleAlign === 'left'
|
||||
? {
|
||||
position: 'absolute',
|
||||
left: (leftButton ? 72 : 16) + insets.left,
|
||||
right: (rightButton ? 72 : 16) + insets.right,
|
||||
}
|
||||
: {
|
||||
marginHorizontal:
|
||||
(leftButton ? 32 : 16) +
|
||||
(leftButton && headerBackTitleVisible !== false
|
||||
? 40
|
||||
: 0) +
|
||||
Math.max(insets.left, insets.right),
|
||||
},
|
||||
titleStyle,
|
||||
titleContainerStyle,
|
||||
styles.left,
|
||||
{ left: insets.left },
|
||||
leftButtonStyle,
|
||||
leftContainerStyle,
|
||||
]}
|
||||
>
|
||||
{headerTitle({
|
||||
children: currentTitle,
|
||||
onLayout: this.handleTitleLayout,
|
||||
allowFontScaling: titleAllowFontScaling,
|
||||
tintColor: headerTintColor,
|
||||
style: customTitleStyle,
|
||||
})}
|
||||
{leftButton}
|
||||
</Animated.View>
|
||||
{rightButton ? (
|
||||
<Animated.View
|
||||
pointerEvents="box-none"
|
||||
style={[
|
||||
styles.right,
|
||||
{ right: insets.right },
|
||||
rightButtonStyle,
|
||||
rightContainerStyle,
|
||||
]}
|
||||
>
|
||||
{rightButton}
|
||||
</Animated.View>
|
||||
) : null}
|
||||
</View>
|
||||
</Animated.View>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
) : null}
|
||||
<Animated.View
|
||||
pointerEvents="box-none"
|
||||
style={[
|
||||
headerTitleAlign === 'left'
|
||||
? {
|
||||
position: 'absolute',
|
||||
left: (leftButton ? 72 : 16) + insets.left,
|
||||
right: (rightButton ? 72 : 16) + insets.right,
|
||||
}
|
||||
: {
|
||||
marginHorizontal:
|
||||
(leftButton ? 32 : 16) +
|
||||
(leftButton && headerBackTitleVisible !== false
|
||||
? 40
|
||||
: 0) +
|
||||
Math.max(insets.left, insets.right),
|
||||
},
|
||||
titleStyle,
|
||||
titleContainerStyle,
|
||||
]}
|
||||
>
|
||||
{headerTitle({
|
||||
children: currentTitle,
|
||||
onLayout: handleTitleLayout,
|
||||
allowFontScaling: titleAllowFontScaling,
|
||||
tintColor: headerTintColor,
|
||||
style: customTitleStyle,
|
||||
})}
|
||||
</Animated.View>
|
||||
{rightButton ? (
|
||||
<Animated.View
|
||||
pointerEvents="box-none"
|
||||
style={[
|
||||
styles.right,
|
||||
{ right: insets.right },
|
||||
rightButtonStyle,
|
||||
rightContainerStyle,
|
||||
]}
|
||||
>
|
||||
{rightButton}
|
||||
</Animated.View>
|
||||
) : null}
|
||||
</View>
|
||||
</Animated.View>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
|
||||
@@ -34,6 +34,9 @@ class WebScreen extends React.Component<
|
||||
|
||||
const AnimatedWebScreen = Animated.createAnimatedComponent(WebScreen);
|
||||
|
||||
// @ts-ignore
|
||||
export const shouldUseActivityState = Screens?.shouldUseActivityState;
|
||||
|
||||
export const MaybeScreenContainer = ({
|
||||
enabled,
|
||||
...rest
|
||||
@@ -41,8 +44,11 @@ export const MaybeScreenContainer = ({
|
||||
enabled: boolean;
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
if (enabled && Platform.OS !== 'web' && Screens && Screens.screensEnabled()) {
|
||||
return <Screens.ScreenContainer {...rest} />;
|
||||
if (enabled && Platform.OS !== 'web' && Screens?.screensEnabled()) {
|
||||
return (
|
||||
// @ts-ignore
|
||||
<Screens.ScreenContainer enabled={enabled} {...rest} />
|
||||
);
|
||||
}
|
||||
|
||||
return <View {...rest} />;
|
||||
@@ -54,16 +60,25 @@ export const MaybeScreen = ({
|
||||
...rest
|
||||
}: ViewProps & {
|
||||
enabled: boolean;
|
||||
active: 0 | 1 | Animated.AnimatedInterpolation;
|
||||
active: 0 | 1 | 2 | Animated.AnimatedInterpolation;
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
if (enabled && Platform.OS === 'web') {
|
||||
return <AnimatedWebScreen active={active} {...rest} />;
|
||||
}
|
||||
|
||||
if (enabled && Screens && Screens.screensEnabled()) {
|
||||
// @ts-expect-error: stackPresentation is incorrectly marked as required
|
||||
return <Screens.Screen active={active} {...rest} />;
|
||||
if (enabled && Screens?.screensEnabled()) {
|
||||
if (shouldUseActivityState) {
|
||||
return (
|
||||
// @ts-expect-error: there was an `active` prop and no `activityState` in older version and stackPresentation was required
|
||||
<Screens.Screen enabled={enabled} activityState={active} {...rest} />
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
// @ts-expect-error: there was an `active` prop and no `activityState` in older version and stackPresentation was required
|
||||
<Screens.Screen enabled={enabled} active={active} {...rest} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return <View {...rest} />;
|
||||
|
||||
@@ -79,6 +79,15 @@ const GESTURE_RESPONSE_DISTANCE_VERTICAL = 135;
|
||||
|
||||
const useNativeDriver = Platform.OS !== 'web';
|
||||
|
||||
const hasOpacityStyle = (style: any) => {
|
||||
if (style) {
|
||||
const flattenedStyle = StyleSheet.flatten(style);
|
||||
return flattenedStyle.opacity != null;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
export default class Card extends React.Component<Props> {
|
||||
static defaultProps = {
|
||||
overlayEnabled: Platform.OS !== 'ios',
|
||||
@@ -533,6 +542,7 @@ export default class Card extends React.Component<Props> {
|
||||
</View>
|
||||
) : null}
|
||||
<Animated.View
|
||||
needsOffscreenAlphaCompositing={hasOpacityStyle(containerStyle)}
|
||||
style={[styles.container, containerStyle, customContainerStyle]}
|
||||
pointerEvents="box-none"
|
||||
>
|
||||
|
||||
@@ -66,6 +66,7 @@ type Props = TransitionPreset & {
|
||||
route: Route<string>;
|
||||
height: number;
|
||||
}) => void;
|
||||
isParentHeaderShown: boolean;
|
||||
};
|
||||
|
||||
const EPSILON = 0.1;
|
||||
@@ -93,6 +94,7 @@ function CardContainer({
|
||||
hasAbsoluteHeader,
|
||||
headerHeight,
|
||||
onHeaderHeightChange,
|
||||
isParentHeaderShown,
|
||||
index,
|
||||
layout,
|
||||
onCloseRoute,
|
||||
@@ -181,7 +183,6 @@ function CardContainer({
|
||||
};
|
||||
}, [pointerEvents, scene.progress.next]);
|
||||
|
||||
const isParentHeaderShown = React.useContext(HeaderShownContext);
|
||||
const isCurrentHeaderShown = headerMode !== 'none' && headerShown !== false;
|
||||
const previousScene = getPreviousScene({ route: scene.route });
|
||||
|
||||
|
||||
@@ -4,12 +4,19 @@ import {
|
||||
StyleSheet,
|
||||
LayoutChangeEvent,
|
||||
Dimensions,
|
||||
Platform,
|
||||
} from 'react-native';
|
||||
import type { EdgeInsets } from 'react-native-safe-area-context';
|
||||
import type { Route, StackNavigationState } from '@react-navigation/native';
|
||||
import type {
|
||||
ParamListBase,
|
||||
Route,
|
||||
StackNavigationState,
|
||||
} from '@react-navigation/native';
|
||||
|
||||
import { MaybeScreenContainer, MaybeScreen } from '../Screens';
|
||||
import {
|
||||
MaybeScreenContainer,
|
||||
MaybeScreen,
|
||||
shouldUseActivityState,
|
||||
} from '../Screens';
|
||||
import { getDefaultHeaderHeight } from '../Header/HeaderSegment';
|
||||
import type { Props as HeaderContainerProps } from '../Header/HeaderContainer';
|
||||
import CardContainer from './CardContainer';
|
||||
@@ -19,7 +26,6 @@ import {
|
||||
} from '../../TransitionConfigs/TransitionPresets';
|
||||
import { forNoAnimation as forNoAnimationHeader } from '../../TransitionConfigs/HeaderStyleInterpolators';
|
||||
import { forNoAnimation as forNoAnimationCard } from '../../TransitionConfigs/CardStyleInterpolators';
|
||||
import HeaderShownContext from '../../utils/HeaderShownContext';
|
||||
import getDistanceForDirection from '../../utils/getDistanceForDirection';
|
||||
import type {
|
||||
Layout,
|
||||
@@ -38,7 +44,7 @@ type GestureValues = {
|
||||
type Props = {
|
||||
mode: StackCardMode;
|
||||
insets: EdgeInsets;
|
||||
state: StackNavigationState;
|
||||
state: StackNavigationState<ParamListBase>;
|
||||
descriptors: StackDescriptorMap;
|
||||
routes: Route<string>[];
|
||||
openingRouteKeys: string[];
|
||||
@@ -52,6 +58,7 @@ type Props = {
|
||||
renderHeader: (props: HeaderContainerProps) => React.ReactNode;
|
||||
renderScene: (props: { route: Route<string> }) => React.ReactNode;
|
||||
headerMode: StackHeaderMode;
|
||||
isParentHeaderShown: boolean;
|
||||
onTransitionStart: (
|
||||
props: { route: Route<string> },
|
||||
closing: boolean
|
||||
@@ -63,6 +70,7 @@ type Props = {
|
||||
onGestureStart?: (props: { route: Route<string> }) => void;
|
||||
onGestureEnd?: (props: { route: Route<string> }) => void;
|
||||
onGestureCancel?: (props: { route: Route<string> }) => void;
|
||||
detachInactiveScreens?: boolean;
|
||||
};
|
||||
|
||||
type State = {
|
||||
@@ -76,11 +84,16 @@ type State = {
|
||||
|
||||
const EPSILON = 0.01;
|
||||
|
||||
const STATE_INACTIVE = 0;
|
||||
const STATE_TRANSITIONING_OR_BELOW_TOP = 1;
|
||||
const STATE_ON_TOP = 2;
|
||||
|
||||
const FALLBACK_DESCRIPTOR = Object.freeze({ options: {} });
|
||||
|
||||
const getHeaderHeights = (
|
||||
routes: Route<string>[],
|
||||
insets: EdgeInsets,
|
||||
isParentHeaderShown: boolean,
|
||||
descriptors: StackDescriptorMap,
|
||||
layout: Layout,
|
||||
previous: Record<string, number>
|
||||
@@ -97,7 +110,9 @@ const getHeaderHeights = (
|
||||
...options.safeAreaInsets,
|
||||
};
|
||||
|
||||
const { headerStatusBarHeight = safeAreaInsets.top } = options;
|
||||
const {
|
||||
headerStatusBarHeight = isParentHeaderShown ? 0 : safeAreaInsets.top,
|
||||
} = options;
|
||||
|
||||
acc[curr.key] =
|
||||
typeof height === 'number'
|
||||
@@ -260,6 +275,7 @@ export default class CardStack extends React.Component<Props, State> {
|
||||
headerHeights: getHeaderHeights(
|
||||
props.routes,
|
||||
props.insets,
|
||||
props.isParentHeaderShown,
|
||||
state.descriptors,
|
||||
state.layout,
|
||||
state.headerHeights
|
||||
@@ -302,6 +318,7 @@ export default class CardStack extends React.Component<Props, State> {
|
||||
headerHeights: getHeaderHeights(
|
||||
props.routes,
|
||||
props.insets,
|
||||
props.isParentHeaderShown,
|
||||
state.descriptors,
|
||||
layout,
|
||||
state.headerHeights
|
||||
@@ -370,6 +387,7 @@ export default class CardStack extends React.Component<Props, State> {
|
||||
renderHeader,
|
||||
renderScene,
|
||||
headerMode,
|
||||
isParentHeaderShown,
|
||||
onTransitionStart,
|
||||
onTransitionEnd,
|
||||
onPageChangeStart,
|
||||
@@ -378,6 +396,8 @@ export default class CardStack extends React.Component<Props, State> {
|
||||
onGestureStart,
|
||||
onGestureEnd,
|
||||
onGestureCancel,
|
||||
// Enable on new versions of screens or for non modals on older versions
|
||||
detachInactiveScreens = shouldUseActivityState || mode !== 'modal',
|
||||
} = this.props;
|
||||
|
||||
const { scenes, layout, gestures, headerHeights } = this.state;
|
||||
@@ -385,6 +405,7 @@ export default class CardStack extends React.Component<Props, State> {
|
||||
const focusedRoute = state.routes[state.index];
|
||||
const focusedDescriptor = descriptors[focusedRoute.key];
|
||||
const focusedOptions = focusedDescriptor ? focusedDescriptor.options : {};
|
||||
const focusedHeaderHeight = headerHeights[focusedRoute.key];
|
||||
|
||||
let defaultTransitionPreset =
|
||||
mode === 'modal' ? ModalTransition : DefaultTransition;
|
||||
@@ -403,212 +424,250 @@ export default class CardStack extends React.Component<Props, State> {
|
||||
left = insets.left,
|
||||
} = focusedOptions.safeAreaInsets || {};
|
||||
|
||||
// Screens is buggy on iOS and web, so we only enable it on Android
|
||||
// For modals, usually we want the screen underneath to be visible, so also disable it there
|
||||
const isScreensEnabled = Platform.OS !== 'ios' && mode !== 'modal';
|
||||
let activeScreensLimit = 1;
|
||||
|
||||
for (let i = scenes.length - 1; i >= 0; i--) {
|
||||
const {
|
||||
// By default, we don't want to detach the previous screen of the active one for modals
|
||||
detachPreviousScreen = mode === 'modal'
|
||||
? i !== scenes.length - 1
|
||||
: true,
|
||||
} = scenes[i].descriptor.options;
|
||||
|
||||
if (detachPreviousScreen === false) {
|
||||
activeScreensLimit++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const isFloatHeaderAbsolute =
|
||||
headerMode === 'float'
|
||||
? this.state.scenes.slice(-2).some((scene) => {
|
||||
const { descriptor } = scene;
|
||||
const options = descriptor ? descriptor.options : {};
|
||||
const {
|
||||
headerTransparent,
|
||||
headerShown = isParentHeaderShown === false,
|
||||
} = options;
|
||||
|
||||
if (headerTransparent || headerShown === false) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
})
|
||||
: false;
|
||||
|
||||
const floatingHeader =
|
||||
headerMode === 'float' ? (
|
||||
<React.Fragment key="header">
|
||||
{renderHeader({
|
||||
mode: 'float',
|
||||
layout,
|
||||
insets: { top, right, bottom, left },
|
||||
scenes,
|
||||
getPreviousScene: this.getPreviousScene,
|
||||
getFocusedRoute: this.getFocusedRoute,
|
||||
onContentHeightChange: this.handleHeaderLayout,
|
||||
gestureDirection:
|
||||
focusedOptions.gestureDirection !== undefined
|
||||
? focusedOptions.gestureDirection
|
||||
: defaultTransitionPreset.gestureDirection,
|
||||
styleInterpolator:
|
||||
focusedOptions.headerStyleInterpolator !== undefined
|
||||
? focusedOptions.headerStyleInterpolator
|
||||
: defaultTransitionPreset.headerStyleInterpolator,
|
||||
style: [
|
||||
styles.floating,
|
||||
isFloatHeaderAbsolute && [
|
||||
// Without this, the header buttons won't be touchable on Android when headerTransparent: true
|
||||
{ height: focusedHeaderHeight },
|
||||
styles.absolute,
|
||||
],
|
||||
],
|
||||
})}
|
||||
</React.Fragment>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<HeaderShownContext.Consumer>
|
||||
{(isParentHeaderShown) => {
|
||||
const isFloatHeaderAbsolute =
|
||||
headerMode === 'float'
|
||||
? this.state.scenes.slice(-2).some((scene) => {
|
||||
const { descriptor } = scene;
|
||||
const options = descriptor ? descriptor.options : {};
|
||||
const {
|
||||
headerTransparent,
|
||||
headerShown = isParentHeaderShown === false,
|
||||
} = options;
|
||||
<React.Fragment>
|
||||
{isFloatHeaderAbsolute ? null : floatingHeader}
|
||||
<MaybeScreenContainer
|
||||
enabled={detachInactiveScreens}
|
||||
style={styles.container}
|
||||
onLayout={this.handleLayout}
|
||||
>
|
||||
{routes.map((route, index, self) => {
|
||||
const focused = focusedRoute.key === route.key;
|
||||
const gesture = gestures[route.key];
|
||||
const scene = scenes[index];
|
||||
|
||||
if (headerTransparent || headerShown === false) {
|
||||
return true;
|
||||
}
|
||||
// For the screens that shouldn't be active, the value is 0
|
||||
// For those that should be active, but are not the top screen, the value is 1
|
||||
// For those on top of the stack and with interaction enabled, the value is 2
|
||||
// For the old implementation, it stays the same it was
|
||||
let isScreenActive: Animated.AnimatedInterpolation | 2 | 1 | 0 = 1;
|
||||
|
||||
return false;
|
||||
})
|
||||
: false;
|
||||
if (shouldUseActivityState) {
|
||||
if (index < self.length - activeScreensLimit - 1) {
|
||||
// screen should be inactive because it is too deep in the stack
|
||||
isScreenActive = STATE_INACTIVE;
|
||||
} else {
|
||||
const sceneForActivity = scenes[self.length - 1];
|
||||
const outputValue =
|
||||
index === self.length - 1
|
||||
? STATE_ON_TOP // the screen is on top after the transition
|
||||
: index >= self.length - activeScreensLimit
|
||||
? STATE_TRANSITIONING_OR_BELOW_TOP // the screen should stay active after the transition, it is not on top but is in activeLimit
|
||||
: STATE_INACTIVE; // the screen should be active only during the transition, it is at the edge of activeLimit
|
||||
isScreenActive = sceneForActivity
|
||||
? sceneForActivity.progress.current.interpolate({
|
||||
inputRange: [0, 1 - EPSILON, 1],
|
||||
outputRange: [1, 1, outputValue],
|
||||
extrapolate: 'clamp',
|
||||
})
|
||||
: STATE_TRANSITIONING_OR_BELOW_TOP;
|
||||
}
|
||||
} else {
|
||||
isScreenActive = scene.progress.next
|
||||
? scene.progress.next.interpolate({
|
||||
inputRange: [0, 1 - EPSILON, 1],
|
||||
outputRange: [1, 1, 0],
|
||||
extrapolate: 'clamp',
|
||||
})
|
||||
: 1;
|
||||
}
|
||||
|
||||
const floatingHeader =
|
||||
headerMode === 'float' ? (
|
||||
<React.Fragment key="header">
|
||||
{renderHeader({
|
||||
mode: 'float',
|
||||
layout,
|
||||
insets: { top, right, bottom, left },
|
||||
scenes,
|
||||
getPreviousScene: this.getPreviousScene,
|
||||
getFocusedRoute: this.getFocusedRoute,
|
||||
onContentHeightChange: this.handleHeaderLayout,
|
||||
gestureDirection:
|
||||
focusedOptions.gestureDirection !== undefined
|
||||
? focusedOptions.gestureDirection
|
||||
: defaultTransitionPreset.gestureDirection,
|
||||
styleInterpolator:
|
||||
focusedOptions.headerStyleInterpolator !== undefined
|
||||
? focusedOptions.headerStyleInterpolator
|
||||
: defaultTransitionPreset.headerStyleInterpolator,
|
||||
style: [
|
||||
styles.floating,
|
||||
isFloatHeaderAbsolute && styles.absolute,
|
||||
],
|
||||
})}
|
||||
</React.Fragment>
|
||||
) : null;
|
||||
const {
|
||||
safeAreaInsets,
|
||||
headerShown = isParentHeaderShown === false,
|
||||
headerTransparent,
|
||||
cardShadowEnabled,
|
||||
cardOverlayEnabled,
|
||||
cardOverlay,
|
||||
cardStyle,
|
||||
animationEnabled,
|
||||
gestureResponseDistance,
|
||||
gestureVelocityImpact,
|
||||
gestureDirection = defaultTransitionPreset.gestureDirection,
|
||||
transitionSpec = defaultTransitionPreset.transitionSpec,
|
||||
cardStyleInterpolator = animationEnabled === false
|
||||
? forNoAnimationCard
|
||||
: defaultTransitionPreset.cardStyleInterpolator,
|
||||
headerStyleInterpolator = defaultTransitionPreset.headerStyleInterpolator,
|
||||
} = scene.descriptor
|
||||
? scene.descriptor.options
|
||||
: ({} as StackNavigationOptions);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{isFloatHeaderAbsolute ? null : floatingHeader}
|
||||
<MaybeScreenContainer
|
||||
enabled={isScreensEnabled}
|
||||
style={styles.container}
|
||||
onLayout={this.handleLayout}
|
||||
let transitionConfig = {
|
||||
gestureDirection,
|
||||
transitionSpec,
|
||||
cardStyleInterpolator,
|
||||
headerStyleInterpolator,
|
||||
};
|
||||
|
||||
// When a screen is not the last, it should use next screen's transition config
|
||||
// Many transitions also animate the previous screen, so using 2 different transitions doesn't look right
|
||||
// For example combining a slide and a modal transition would look wrong otherwise
|
||||
// With this approach, combining different transition styles in the same navigator mostly looks right
|
||||
// This will still be broken when 2 transitions have different idle state (e.g. modal presentation),
|
||||
// but majority of the transitions look alright
|
||||
if (index !== self.length - 1) {
|
||||
const nextScene = scenes[index + 1];
|
||||
|
||||
if (nextScene) {
|
||||
const {
|
||||
animationEnabled,
|
||||
gestureDirection = defaultTransitionPreset.gestureDirection,
|
||||
transitionSpec = defaultTransitionPreset.transitionSpec,
|
||||
cardStyleInterpolator = animationEnabled === false
|
||||
? forNoAnimationCard
|
||||
: defaultTransitionPreset.cardStyleInterpolator,
|
||||
headerStyleInterpolator = defaultTransitionPreset.headerStyleInterpolator,
|
||||
} = nextScene.descriptor
|
||||
? nextScene.descriptor.options
|
||||
: ({} as StackNavigationOptions);
|
||||
|
||||
transitionConfig = {
|
||||
gestureDirection,
|
||||
transitionSpec,
|
||||
cardStyleInterpolator,
|
||||
headerStyleInterpolator,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
top: safeAreaInsetTop = insets.top,
|
||||
right: safeAreaInsetRight = insets.right,
|
||||
bottom: safeAreaInsetBottom = insets.bottom,
|
||||
left: safeAreaInsetLeft = insets.left,
|
||||
} = safeAreaInsets || {};
|
||||
|
||||
const headerHeight =
|
||||
headerMode !== 'none' && headerShown !== false
|
||||
? headerHeights[route.key]
|
||||
: 0;
|
||||
|
||||
return (
|
||||
<MaybeScreen
|
||||
key={route.key}
|
||||
style={StyleSheet.absoluteFill}
|
||||
enabled={detachInactiveScreens}
|
||||
active={isScreenActive}
|
||||
pointerEvents="box-none"
|
||||
>
|
||||
{routes.map((route, index, self) => {
|
||||
const focused = focusedRoute.key === route.key;
|
||||
const gesture = gestures[route.key];
|
||||
const scene = scenes[index];
|
||||
|
||||
const isScreenActive = scene.progress.next
|
||||
? scene.progress.next.interpolate({
|
||||
inputRange: [0, 1 - EPSILON, 1],
|
||||
outputRange: [1, 1, 0],
|
||||
extrapolate: 'clamp',
|
||||
})
|
||||
: 1;
|
||||
|
||||
const {
|
||||
safeAreaInsets,
|
||||
headerShown = isParentHeaderShown === false,
|
||||
headerTransparent,
|
||||
cardShadowEnabled,
|
||||
cardOverlayEnabled,
|
||||
cardOverlay,
|
||||
cardStyle,
|
||||
animationEnabled,
|
||||
gestureResponseDistance,
|
||||
gestureVelocityImpact,
|
||||
gestureDirection = defaultTransitionPreset.gestureDirection,
|
||||
transitionSpec = defaultTransitionPreset.transitionSpec,
|
||||
cardStyleInterpolator = animationEnabled === false
|
||||
? forNoAnimationCard
|
||||
: defaultTransitionPreset.cardStyleInterpolator,
|
||||
headerStyleInterpolator = defaultTransitionPreset.headerStyleInterpolator,
|
||||
} = scene.descriptor
|
||||
? scene.descriptor.options
|
||||
: ({} as StackNavigationOptions);
|
||||
|
||||
let transitionConfig = {
|
||||
gestureDirection,
|
||||
transitionSpec,
|
||||
cardStyleInterpolator,
|
||||
headerStyleInterpolator,
|
||||
};
|
||||
|
||||
// When a screen is not the last, it should use next screen's transition config
|
||||
// Many transitions also animate the previous screen, so using 2 different transitions doesn't look right
|
||||
// For example combining a slide and a modal transition would look wrong otherwise
|
||||
// With this approach, combining different transition styles in the same navigator mostly looks right
|
||||
// This will still be broken when 2 transitions have different idle state (e.g. modal presentation),
|
||||
// but majority of the transitions look alright
|
||||
if (index !== self.length - 1) {
|
||||
const nextScene = scenes[index + 1];
|
||||
|
||||
if (nextScene) {
|
||||
const {
|
||||
animationEnabled,
|
||||
gestureDirection = defaultTransitionPreset.gestureDirection,
|
||||
transitionSpec = defaultTransitionPreset.transitionSpec,
|
||||
cardStyleInterpolator = animationEnabled === false
|
||||
? forNoAnimationCard
|
||||
: defaultTransitionPreset.cardStyleInterpolator,
|
||||
headerStyleInterpolator = defaultTransitionPreset.headerStyleInterpolator,
|
||||
} = nextScene.descriptor
|
||||
? nextScene.descriptor.options
|
||||
: ({} as StackNavigationOptions);
|
||||
|
||||
transitionConfig = {
|
||||
gestureDirection,
|
||||
transitionSpec,
|
||||
cardStyleInterpolator,
|
||||
headerStyleInterpolator,
|
||||
};
|
||||
}
|
||||
<CardContainer
|
||||
index={index}
|
||||
active={index === self.length - 1}
|
||||
focused={focused}
|
||||
closing={closingRouteKeys.includes(route.key)}
|
||||
layout={layout}
|
||||
gesture={gesture}
|
||||
scene={scene}
|
||||
safeAreaInsetTop={safeAreaInsetTop}
|
||||
safeAreaInsetRight={safeAreaInsetRight}
|
||||
safeAreaInsetBottom={safeAreaInsetBottom}
|
||||
safeAreaInsetLeft={safeAreaInsetLeft}
|
||||
cardOverlay={cardOverlay}
|
||||
cardOverlayEnabled={cardOverlayEnabled}
|
||||
cardShadowEnabled={cardShadowEnabled}
|
||||
cardStyle={cardStyle}
|
||||
onPageChangeStart={onPageChangeStart}
|
||||
onPageChangeConfirm={onPageChangeConfirm}
|
||||
onPageChangeCancel={onPageChangeCancel}
|
||||
onGestureStart={onGestureStart}
|
||||
onGestureCancel={onGestureCancel}
|
||||
onGestureEnd={onGestureEnd}
|
||||
gestureResponseDistance={gestureResponseDistance}
|
||||
headerHeight={headerHeight}
|
||||
isParentHeaderShown={isParentHeaderShown}
|
||||
onHeaderHeightChange={this.handleHeaderLayout}
|
||||
getPreviousScene={this.getPreviousScene}
|
||||
getFocusedRoute={this.getFocusedRoute}
|
||||
mode={mode}
|
||||
headerMode={headerMode}
|
||||
headerShown={headerShown}
|
||||
hasAbsoluteHeader={
|
||||
isFloatHeaderAbsolute && !headerTransparent
|
||||
}
|
||||
|
||||
const {
|
||||
top: safeAreaInsetTop = insets.top,
|
||||
right: safeAreaInsetRight = insets.right,
|
||||
bottom: safeAreaInsetBottom = insets.bottom,
|
||||
left: safeAreaInsetLeft = insets.left,
|
||||
} = safeAreaInsets || {};
|
||||
|
||||
const headerHeight =
|
||||
headerMode !== 'none' && headerShown !== false
|
||||
? headerHeights[route.key]
|
||||
: 0;
|
||||
|
||||
return (
|
||||
<MaybeScreen
|
||||
key={route.key}
|
||||
style={StyleSheet.absoluteFill}
|
||||
enabled={isScreensEnabled}
|
||||
active={isScreenActive}
|
||||
pointerEvents="box-none"
|
||||
>
|
||||
<CardContainer
|
||||
index={index}
|
||||
active={index === self.length - 1}
|
||||
focused={focused}
|
||||
closing={closingRouteKeys.includes(route.key)}
|
||||
layout={layout}
|
||||
gesture={gesture}
|
||||
scene={scene}
|
||||
safeAreaInsetTop={safeAreaInsetTop}
|
||||
safeAreaInsetRight={safeAreaInsetRight}
|
||||
safeAreaInsetBottom={safeAreaInsetBottom}
|
||||
safeAreaInsetLeft={safeAreaInsetLeft}
|
||||
cardOverlay={cardOverlay}
|
||||
cardOverlayEnabled={cardOverlayEnabled}
|
||||
cardShadowEnabled={cardShadowEnabled}
|
||||
cardStyle={cardStyle}
|
||||
onPageChangeStart={onPageChangeStart}
|
||||
onPageChangeConfirm={onPageChangeConfirm}
|
||||
onPageChangeCancel={onPageChangeCancel}
|
||||
onGestureStart={onGestureStart}
|
||||
onGestureCancel={onGestureCancel}
|
||||
onGestureEnd={onGestureEnd}
|
||||
gestureResponseDistance={gestureResponseDistance}
|
||||
headerHeight={headerHeight}
|
||||
onHeaderHeightChange={this.handleHeaderLayout}
|
||||
getPreviousScene={this.getPreviousScene}
|
||||
getFocusedRoute={this.getFocusedRoute}
|
||||
mode={mode}
|
||||
headerMode={headerMode}
|
||||
headerShown={headerShown}
|
||||
hasAbsoluteHeader={
|
||||
isFloatHeaderAbsolute && !headerTransparent
|
||||
}
|
||||
renderHeader={renderHeader}
|
||||
renderScene={renderScene}
|
||||
onOpenRoute={onOpenRoute}
|
||||
onCloseRoute={onCloseRoute}
|
||||
onTransitionStart={onTransitionStart}
|
||||
onTransitionEnd={onTransitionEnd}
|
||||
gestureEnabled={
|
||||
index !== 0 && getGesturesEnabled({ route })
|
||||
}
|
||||
gestureVelocityImpact={gestureVelocityImpact}
|
||||
{...transitionConfig}
|
||||
/>
|
||||
</MaybeScreen>
|
||||
);
|
||||
})}
|
||||
</MaybeScreenContainer>
|
||||
{isFloatHeaderAbsolute ? floatingHeader : null}
|
||||
</React.Fragment>
|
||||
);
|
||||
}}
|
||||
</HeaderShownContext.Consumer>
|
||||
renderHeader={renderHeader}
|
||||
renderScene={renderScene}
|
||||
onOpenRoute={onOpenRoute}
|
||||
onCloseRoute={onCloseRoute}
|
||||
onTransitionStart={onTransitionStart}
|
||||
onTransitionEnd={onTransitionEnd}
|
||||
gestureEnabled={index !== 0 && getGesturesEnabled({ route })}
|
||||
gestureVelocityImpact={gestureVelocityImpact}
|
||||
{...transitionConfig}
|
||||
/>
|
||||
</MaybeScreen>
|
||||
);
|
||||
})}
|
||||
</MaybeScreenContainer>
|
||||
{isFloatHeaderAbsolute ? floatingHeader : null}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
StackActions,
|
||||
StackNavigationState,
|
||||
Route,
|
||||
ParamListBase,
|
||||
} from '@react-navigation/native';
|
||||
|
||||
import { GestureHandlerRootView } from '../GestureHandler';
|
||||
@@ -20,9 +21,10 @@ import type {
|
||||
StackNavigationConfig,
|
||||
StackDescriptorMap,
|
||||
} from '../../types';
|
||||
import HeaderShownContext from '../../utils/HeaderShownContext';
|
||||
|
||||
type Props = StackNavigationConfig & {
|
||||
state: StackNavigationState;
|
||||
state: StackNavigationState<ParamListBase>;
|
||||
navigation: StackNavigationHelpers;
|
||||
descriptors: StackDescriptorMap;
|
||||
};
|
||||
@@ -455,29 +457,34 @@ export default class StackView extends React.Component<Props, State> {
|
||||
{(insets) => (
|
||||
<KeyboardManager enabled={keyboardHandlingEnabled !== false}>
|
||||
{(props) => (
|
||||
<CardStack
|
||||
mode={mode}
|
||||
insets={insets as EdgeInsets}
|
||||
getPreviousRoute={this.getPreviousRoute}
|
||||
getGesturesEnabled={this.getGesturesEnabled}
|
||||
routes={routes}
|
||||
openingRouteKeys={openingRouteKeys}
|
||||
closingRouteKeys={closingRouteKeys}
|
||||
onOpenRoute={this.handleOpenRoute}
|
||||
onCloseRoute={this.handleCloseRoute}
|
||||
onTransitionStart={this.handleTransitionStart}
|
||||
onTransitionEnd={this.handleTransitionEnd}
|
||||
renderHeader={this.renderHeader}
|
||||
renderScene={this.renderScene}
|
||||
headerMode={headerMode}
|
||||
state={state}
|
||||
descriptors={descriptors}
|
||||
onGestureStart={this.handleGestureStart}
|
||||
onGestureEnd={this.handleGestureEnd}
|
||||
onGestureCancel={this.handleGestureCancel}
|
||||
{...rest}
|
||||
{...props}
|
||||
/>
|
||||
<HeaderShownContext.Consumer>
|
||||
{(isParentHeaderShown) => (
|
||||
<CardStack
|
||||
mode={mode}
|
||||
insets={insets as EdgeInsets}
|
||||
isParentHeaderShown={isParentHeaderShown}
|
||||
getPreviousRoute={this.getPreviousRoute}
|
||||
getGesturesEnabled={this.getGesturesEnabled}
|
||||
routes={routes}
|
||||
openingRouteKeys={openingRouteKeys}
|
||||
closingRouteKeys={closingRouteKeys}
|
||||
onOpenRoute={this.handleOpenRoute}
|
||||
onCloseRoute={this.handleCloseRoute}
|
||||
onTransitionStart={this.handleTransitionStart}
|
||||
onTransitionEnd={this.handleTransitionEnd}
|
||||
renderHeader={this.renderHeader}
|
||||
renderScene={this.renderScene}
|
||||
headerMode={headerMode}
|
||||
state={state}
|
||||
descriptors={descriptors}
|
||||
onGestureStart={this.handleGestureStart}
|
||||
onGestureEnd={this.handleGestureEnd}
|
||||
onGestureCancel={this.handleGestureCancel}
|
||||
{...rest}
|
||||
{...props}
|
||||
/>
|
||||
)}
|
||||
</HeaderShownContext.Consumer>
|
||||
)}
|
||||
</KeyboardManager>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user