Compare commits

...

20 Commits

Author SHA1 Message Date
Satyajit Sahoo
1e05895b24 chore: publish
- @react-navigation/bottom-tabs@6.0.0-next.9
 - @react-navigation/core@6.0.0-next.6
 - @react-navigation/devtools@6.0.0-next.6
 - @react-navigation/drawer@6.0.0-next.8
 - @react-navigation/elements@1.0.0-next.8
 - @react-navigation/material-bottom-tabs@6.0.0-next.6
 - @react-navigation/material-top-tabs@6.0.0-next.7
 - @react-navigation/native@6.0.0-next.6
 - @react-navigation/stack@6.0.0-next.13
2021-05-09 07:14:01 +02:00
Satyajit Sahoo
929c3e3a3b fix: fix type annotations for useNavigation (again) 2021-05-09 07:12:55 +02:00
Satyajit Sahoo
b5d539a11b chore: publish
- @react-navigation/bottom-tabs@6.0.0-next.8
 - @react-navigation/core@6.0.0-next.5
 - @react-navigation/devtools@6.0.0-next.5
 - @react-navigation/drawer@6.0.0-next.7
 - @react-navigation/elements@1.0.0-next.7
 - @react-navigation/material-bottom-tabs@6.0.0-next.5
 - @react-navigation/material-top-tabs@6.0.0-next.6
 - @react-navigation/native@6.0.0-next.5
 - @react-navigation/stack@6.0.0-next.12
2021-05-09 07:04:29 +02:00
Satyajit Sahoo
7da45e1e89 fix: fix type annotations for useNavigation 2021-05-09 07:03:38 +02:00
Satyajit Sahoo
47134d7052 chore: publish
- @react-navigation/bottom-tabs@6.0.0-next.7
 - @react-navigation/core@6.0.0-next.4
 - @react-navigation/devtools@6.0.0-next.4
 - @react-navigation/drawer@6.0.0-next.6
 - @react-navigation/elements@1.0.0-next.6
 - @react-navigation/material-bottom-tabs@6.0.0-next.4
 - @react-navigation/material-top-tabs@6.0.0-next.5
 - @react-navigation/native@6.0.0-next.4
 - @react-navigation/stack@6.0.0-next.11
2021-05-09 06:45:00 +02:00
Satyajit Sahoo
a369ba3645 fix: make sure disabling react-native-screens works 2021-05-09 06:43:48 +02:00
Satyajit Sahoo
4294318210 refactor: use react-native-screens for web as well 2021-05-09 06:43:09 +02:00
Wojciech Lewicki
8da4c58065 fix: enable screens only on supported platforms (#9494) 2021-05-09 06:42:56 +02:00
Satyajit Sahoo
7809bc0650 chore: upgrade expo to stable version 2021-05-09 06:12:08 +02:00
Satyajit Sahoo
8f2b95ca97 chore: drop support for older versions of react-native-screens 2021-05-09 06:03:38 +02:00
Jesse Katsumata
6866ad2cda chore: update @types/react-native and remove ts-ignore (#9518) 2021-05-09 05:45:52 +02:00
Satyajit Sahoo
459fd27050 fix: animate pressable opacity 2021-05-09 05:41:27 +02:00
Satyajit Sahoo
14786594c0 feat: support navigate-like object in Link 2021-05-09 04:56:45 +02:00
Satyajit Sahoo
b28bfddc17 feat: add ability to specify root param list 2021-05-09 04:33:56 +02:00
Satyajit Sahoo
1a6aebefcb feat: add a new component to group multiple screens with common options 2021-05-09 01:14:24 +02:00
Satyajit Sahoo
33b2dbb85c chore: bump node versions on CI 2021-05-09 00:26:16 +02:00
Satyajit Sahoo
4bb0b43f1a feat: automatically set headerMode if it's modal presentation style 2021-05-08 23:20:12 +02:00
Satyajit Sahoo
260da9b103 docs: update comments for various options 2021-05-08 21:57:14 +02:00
Satyajit Sahoo
9ac709ea1e refactor: drop mode prop in favor of animationPresentation option
BREAKING CHANGE: This drops the mode prop on the navigator in favor of a per-screen option animationPresentation
2021-05-08 07:04:25 +02:00
Satyajit Sahoo
60fa3b9be9 feat: support mixing regular and modal presentation in same stack 2021-05-08 07:04:25 +02:00
70 changed files with 2027 additions and 1085 deletions

View File

@@ -3,7 +3,7 @@ version: 2.1
executors:
default:
docker:
- image: circleci/node:10
- image: circleci/node:14
working_directory: ~/project
environment:
YARN_CACHE_FOLDER: "~/.cache/yarn"
@@ -36,10 +36,12 @@ jobs:
command: yarn install --frozen-lockfile
- save_cache:
key: yarn-packages-v1-{{ .Branch }}-{{ checksum "yarn.lock" }}
paths: ~/.cache/yarn
paths:
- ~/.cache/yarn
- persist_to_workspace:
root: .
paths: .
paths:
- .
lint-and-typecheck:
executor: default

View File

@@ -13,7 +13,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v1
with:
node-version: 10.x
node-version: 14.x
- name: Setup Expo
uses: expo/expo-github-action@v5

View File

@@ -16,7 +16,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v1
with:
node-version: 10.x
node-version: 14.x
- name: Setup Expo
uses: expo/expo-github-action@v5

1
.gitignore vendored
View File

@@ -26,6 +26,7 @@ build
npm-debug.*
*.tsbuildinfo
*.log
*.jks
*.p8

View File

@@ -15,13 +15,13 @@
"dependencies": {
"@expo/vector-icons": "^12.0.0",
"@react-native-async-storage/async-storage": "^1.13.0",
"@react-native-masked-view/masked-view": "0.2.3",
"@react-native-masked-view/masked-view": "~0.2.4",
"color": "^3.1.3",
"expo": "^41.0.0-beta.2",
"expo": "^41.0.1",
"expo-asset": "~8.3.1",
"expo-blur": "~9.0.3",
"expo-linking": "~2.2.1",
"expo-updates": "~0.5.3",
"expo-linking": "~2.2.3",
"expo-updates": "~0.5.4",
"koa": "^2.13.0",
"react": "16.13.1",
"react-dom": "16.13.1",
@@ -51,7 +51,7 @@
"babel-plugin-module-resolver": "^4.0.0",
"babel-preset-expo": "8.3.0",
"cheerio": "^1.0.0-rc.3",
"expo-cli": "^4.3.4",
"expo-cli": "^4.4.4",
"jest": "^26.6.3",
"jest-dev-server": "^4.4.0",
"mock-require-assets": "^0.0.1",

View File

@@ -1,5 +1,4 @@
import * as React from 'react';
import { Platform } from 'react-native';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import {
getFocusedRouteNameFromRoute,
@@ -9,7 +8,6 @@ import {
import type { StackScreenProps } from '@react-navigation/stack';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { HeaderBackButton } from '@react-navigation/elements';
import TouchableBounce from '../Shared/TouchableBounce';
import Albums from '../Shared/Albums';
import Contacts from '../Shared/Contacts';
import Chat from '../Shared/Chat';
@@ -51,10 +49,6 @@ export default function BottomTabsScreen({
headerLeft: (props) => (
<HeaderBackButton {...props} onPress={navigation.goBack} />
),
tabBarButton:
Platform.OS === 'web'
? undefined
: (props) => <TouchableBounce {...props} />,
}}
>
<BottomTabs.Screen

View File

@@ -24,7 +24,8 @@ const scrollEnabled = Platform.select({ web: true, default: false });
const LinkButton = ({
to,
...rest
}: React.ComponentProps<typeof Button> & { to: string }) => {
}: React.ComponentProps<typeof Button> &
Parameters<typeof useLinkProps>[0]) => {
const props = useLinkProps({ to });
return <Button {...props} {...rest} />;
@@ -57,6 +58,13 @@ const ArticleScreen = ({
>
Go to /link-component/music
</LinkButton>
<LinkButton
to={{ screen: 'Home' }}
mode="contained"
style={styles.button}
>
Go to /
</LinkButton>
<Button
mode="outlined"
onPress={() => navigation.goBack()}

View File

@@ -117,24 +117,29 @@ export default function SimpleStackScreen({
return (
<SimpleStack.Navigator
screenOptions={{
...TransitionPresets.SlideFromRightIOS,
headerMode: 'float',
headerStyleInterpolator: HeaderStyleInterpolators.forUIKit,
}}
>
<SimpleStack.Screen
name="Article"
component={ArticleScreen}
options={({ route }) => ({
title: `Article by ${route.params?.author ?? 'Unknown'}`,
})}
initialParams={{ author: 'Gandalf' }}
/>
<SimpleStack.Screen
name="NewsFeed"
component={NewsFeedScreen}
options={{ title: 'Feed' }}
/>
<SimpleStack.Group
screenOptions={{
...TransitionPresets.SlideFromRightIOS,
headerMode: 'float',
}}
>
<SimpleStack.Screen
name="Article"
component={ArticleScreen}
options={({ route }) => ({
title: `Article by ${route.params?.author ?? 'Unknown'}`,
})}
initialParams={{ author: 'Gandalf' }}
/>
<SimpleStack.Screen
name="NewsFeed"
component={NewsFeedScreen}
options={{ title: 'Feed' }}
/>
</SimpleStack.Group>
<SimpleStack.Screen
name="Albums"
component={AlbumsScreen}

View File

@@ -0,0 +1,137 @@
import * as React from 'react';
import { View, StyleSheet, ScrollView, Platform } from 'react-native';
import { Button } from 'react-native-paper';
import type { ParamListBase } from '@react-navigation/native';
import {
createStackNavigator,
StackScreenProps,
TransitionPresets,
} from '@react-navigation/stack';
import Article from '../Shared/Article';
import Albums from '../Shared/Albums';
type MixedStackParams = {
Article: { author: string };
Albums: undefined;
};
const scrollEnabled = Platform.select({ web: true, default: false });
const ArticleScreen = ({
navigation,
route,
}: StackScreenProps<MixedStackParams, 'Article'>) => {
return (
<ScrollView>
<View style={styles.buttons}>
<View>
<Button
mode="contained"
onPress={() => navigation.push('Article', { author: 'Dalek' })}
style={styles.button}
>
Push article
</Button>
<Button
mode="outlined"
onPress={() => navigation.goBack()}
style={styles.button}
>
Go back
</Button>
</View>
<View>
<Button
mode="contained"
onPress={() => navigation.push('Albums')}
style={styles.button}
>
Push album
</Button>
</View>
</View>
<Article
author={{ name: route.params.author }}
scrollEnabled={scrollEnabled}
/>
</ScrollView>
);
};
const AlbumsScreen = ({ navigation }: StackScreenProps<MixedStackParams>) => {
return (
<ScrollView>
<View style={styles.buttons}>
<View>
<Button
mode="contained"
onPress={() => navigation.push('Albums')}
style={styles.button}
>
Push album
</Button>
<Button
mode="outlined"
onPress={() => navigation.goBack()}
style={styles.button}
>
Go back
</Button>
</View>
<View>
<Button
mode="contained"
onPress={() => navigation.push('Article', { author: 'The Doctor' })}
style={styles.button}
>
Push article
</Button>
</View>
</View>
<Albums scrollEnabled={scrollEnabled} />
</ScrollView>
);
};
const MixedStack = createStackNavigator<MixedStackParams>();
type Props = StackScreenProps<ParamListBase>;
export default function MixedStackScreen({ navigation }: Props) {
React.useLayoutEffect(() => {
navigation.setOptions({
headerShown: false,
});
}, [navigation]);
return (
<MixedStack.Navigator>
<MixedStack.Screen
name="Article"
component={ArticleScreen}
options={({ route }) => ({
title: `Article by ${route.params.author}`,
})}
initialParams={{ author: 'Gandalf' }}
/>
<MixedStack.Screen
name="Albums"
component={AlbumsScreen}
options={{
title: 'Albums',
...TransitionPresets.ModalPresentationIOS,
}}
/>
</MixedStack.Navigator>
);
}
const styles = StyleSheet.create({
buttons: {
flexDirection: 'row',
padding: 8,
},
button: {
margin: 8,
},
});

View File

@@ -82,7 +82,7 @@ export default function ModalStackScreen({ navigation }: Props) {
}, [navigation]);
return (
<ModalStack.Navigator mode="modal">
<ModalStack.Navigator screenOptions={{ animationPresentation: 'modal' }}>
<ModalStack.Screen
name="Article"
component={ArticleScreen}

View File

@@ -8,7 +8,7 @@ import {
} from '@react-navigation/stack';
import Article from '../Shared/Article';
type SimpleStackParams = {
type TransparentStackParams = {
Article: { author: string };
Dialog: undefined;
};
@@ -18,7 +18,7 @@ const scrollEnabled = Platform.select({ web: true, default: false });
const ArticleScreen = ({
navigation,
route,
}: StackScreenProps<SimpleStackParams, 'Article'>) => {
}: StackScreenProps<TransparentStackParams, 'Article'>) => {
return (
<ScrollView>
<View style={styles.buttons}>
@@ -45,7 +45,9 @@ const ArticleScreen = ({
);
};
const DialogScreen = ({ navigation }: StackScreenProps<SimpleStackParams>) => {
const DialogScreen = ({
navigation,
}: StackScreenProps<TransparentStackParams>) => {
const { colors } = useTheme();
return (
@@ -70,12 +72,11 @@ const DialogScreen = ({ navigation }: StackScreenProps<SimpleStackParams>) => {
);
};
const SimpleStack = createStackNavigator<SimpleStackParams>();
const TransparentStack = createStackNavigator<TransparentStackParams>();
type Props = Partial<React.ComponentProps<typeof SimpleStack.Navigator>> &
StackScreenProps<ParamListBase>;
type Props = StackScreenProps<ParamListBase>;
export default function SimpleStackScreen({ navigation, ...rest }: Props) {
export default function TransparentStackScreen({ navigation }: Props) {
React.useLayoutEffect(() => {
navigation.setOptions({
headerShown: false,
@@ -83,13 +84,15 @@ export default function SimpleStackScreen({ navigation, ...rest }: Props) {
}, [navigation]);
return (
<SimpleStack.Navigator mode="modal" {...rest}>
<SimpleStack.Screen
<TransparentStack.Navigator
screenOptions={{ animationPresentation: 'modal' }}
>
<TransparentStack.Screen
name="Article"
component={ArticleScreen}
initialParams={{ author: 'Gandalf' }}
/>
<SimpleStack.Screen
<TransparentStack.Screen
name="Dialog"
component={DialogScreen}
options={{
@@ -122,7 +125,7 @@ export default function SimpleStackScreen({ navigation, ...rest }: Props) {
}),
}}
/>
</SimpleStack.Navigator>
</TransparentStack.Navigator>
);
}

View File

@@ -1,4 +0,0 @@
// @ts-expect-error: there are no type definitions for deep imports
import TouchableBounce from 'react-native/Libraries/Components/Touchable/TouchableBounce';
export default TouchableBounce;

View File

@@ -1,3 +0,0 @@
import { TouchableOpacity } from 'react-native';
export default TouchableOpacity;

View File

@@ -29,6 +29,7 @@ import {
DarkTheme,
PathConfigMap,
useNavigationContainerRef,
NavigatorScreenParams,
} from '@react-navigation/native';
import { createDrawerNavigator } from '@react-navigation/drawer';
import {
@@ -42,6 +43,7 @@ import { restartApp } from './Restart';
import SettingsItem from './Shared/SettingsItem';
import SimpleStack from './Screens/SimpleStack';
import ModalStack from './Screens/ModalStack';
import MixedStack from './Screens/MixedStack';
import MixedHeaderMode from './Screens/MixedHeaderMode';
import StackTransparent from './Screens/StackTransparent';
import StackHeaderCustomization from './Screens/StackHeaderCustomization';
@@ -61,6 +63,13 @@ if (Platform.OS !== 'web') {
enableScreens();
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace ReactNavigation {
interface RootParamList extends RootStackParamList {}
}
}
type RootDrawerParamList = {
Examples: undefined;
};
@@ -71,6 +80,10 @@ const SCREENS = {
title: 'Modal Stack',
component: ModalStack,
},
MixedStack: {
title: 'Regular + Modal Stack',
component: MixedStack,
},
MixedHeaderMode: {
title: 'Float + Screen Header Stack',
component: MixedHeaderMode,
@@ -115,7 +128,7 @@ const SCREENS = {
};
type RootStackParamList = {
Home: undefined;
Home: NavigatorScreenParams<RootDrawerParamList>;
NotFound: undefined;
} & {
[P in keyof typeof SCREENS]: undefined;
@@ -228,7 +241,9 @@ export default function App() {
prefixes: [createURL('/')],
config: {
initialRouteName: 'Home',
screens: Object.keys(SCREENS).reduce<PathConfigMap>(
screens: Object.keys(SCREENS).reduce<
PathConfigMap<RootStackParamList>
>(
(acc, name) => {
// Convert screen names such as SimpleStack to kebab case (simple-stack)
const path = name
@@ -236,13 +251,15 @@ export default function App() {
.replace(/^-/, '')
.toLowerCase();
// @ts-expect-error: these types aren't accurate
// But we aren't too concerned for now
acc[name] = {
path,
screens: {
Article: {
path: 'article/:author?',
parse: {
author: (author) =>
author: (author: string) =>
author.charAt(0).toUpperCase() +
author.slice(1).replace(/-/g, ' '),
},

View File

@@ -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.
# [6.0.0-next.9](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@6.0.0-next.8...@react-navigation/bottom-tabs@6.0.0-next.9) (2021-05-09)
**Note:** Version bump only for package @react-navigation/bottom-tabs
# [6.0.0-next.8](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@6.0.0-next.7...@react-navigation/bottom-tabs@6.0.0-next.8) (2021-05-09)
**Note:** Version bump only for package @react-navigation/bottom-tabs
# [6.0.0-next.7](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@6.0.0-next.6...@react-navigation/bottom-tabs@6.0.0-next.7) (2021-05-09)
### Bug Fixes
* enable screens only on supported platforms ([#9494](https://github.com/react-navigation/react-navigation/issues/9494)) ([8da4c58](https://github.com/react-navigation/react-navigation/commit/8da4c58065607d44e9dc1ad8943e09537598dcd7))
* make sure disabling react-native-screens works ([a369ba3](https://github.com/react-navigation/react-navigation/commit/a369ba36451ddc2bb5b247e61b725bce1e3fb5e5))
# [6.0.0-next.6](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@6.0.0-next.5...@react-navigation/bottom-tabs@6.0.0-next.6) (2021-05-01)
**Note:** Version bump only for package @react-navigation/bottom-tabs

View File

@@ -1,7 +1,7 @@
{
"name": "@react-navigation/bottom-tabs",
"description": "Bottom tab navigator following iOS design guidelines",
"version": "6.0.0-next.6",
"version": "6.0.0-next.9",
"keywords": [
"react-native-component",
"react-component",
@@ -36,16 +36,16 @@
"clean": "del lib"
},
"dependencies": {
"@react-navigation/elements": "^1.0.0-next.5",
"@react-navigation/elements": "^1.0.0-next.8",
"color": "^3.1.3",
"warn-once": "^0.0.1"
},
"devDependencies": {
"@react-navigation/native": "^6.0.0-next.3",
"@react-navigation/native": "^6.0.0-next.6",
"@testing-library/react-native": "^7.2.0",
"@types/color": "^3.0.1",
"@types/react": "^16.9.53",
"@types/react-native": "~0.63.51",
"@types/react-native": "~0.64.4",
"del-cli": "^3.0.1",
"react": "~16.13.1",
"react-native": "~0.63.4",

View File

@@ -1,6 +1,5 @@
import * as React from 'react';
import { StyleSheet } from 'react-native';
import { ScreenContainer } from 'react-native-screens';
import { StyleSheet, Platform } from 'react-native';
import { SafeAreaInsetsContext } from 'react-native-safe-area-context';
import {
NavigationHelpersContext,
@@ -13,8 +12,7 @@ import {
SafeAreaProviderCompat,
getHeaderTitle,
} from '@react-navigation/elements';
import ScreenFallback from './ScreenFallback';
import { MaybeScreenContainer, MaybeScreen } from './ScreenFallback';
import BottomTabBar, { getTabBarHeight } from './BottomTabBar';
import BottomTabBarHeightCallbackContext from '../utils/BottomTabBarHeightCallbackContext';
import BottomTabBarHeightContext from '../utils/BottomTabBarHeightContext';
@@ -40,7 +38,9 @@ export default function BottomTabView(props: Props) {
navigation,
descriptors,
safeAreaInsets,
detachInactiveScreens = true,
detachInactiveScreens = Platform.OS === 'web' ||
Platform.OS === 'android' ||
Platform.OS === 'ios',
sceneContainerStyle,
} = props;
@@ -91,8 +91,7 @@ export default function BottomTabView(props: Props) {
return (
<NavigationHelpersContext.Provider value={navigation}>
<SafeAreaProviderCompat>
<ScreenContainer
// @ts-ignore
<MaybeScreenContainer
enabled={detachInactiveScreens}
style={styles.container}
>
@@ -121,7 +120,7 @@ export default function BottomTabView(props: Props) {
} = descriptor.options;
return (
<ScreenFallback
<MaybeScreen
key={route.key}
style={StyleSheet.absoluteFill}
visible={isFocused}
@@ -147,10 +146,10 @@ export default function BottomTabView(props: Props) {
{descriptor.render()}
</Screen>
</BottomTabBarHeightContext.Provider>
</ScreenFallback>
</MaybeScreen>
);
})}
</ScreenContainer>
</MaybeScreenContainer>
<BottomTabBarHeightCallbackContext.Provider value={setTabBarHeight}>
{renderTabBar()}
</BottomTabBarHeightCallbackContext.Provider>

View File

@@ -1,11 +1,5 @@
import * as React from 'react';
import { Platform, StyleProp, ViewStyle } from 'react-native';
import {
Screen,
screensEnabled,
// @ts-ignore
shouldUseActivityState,
} from 'react-native-screens';
import { StyleProp, View, ViewProps, ViewStyle } from 'react-native';
import { ResourceSavingView } from '@react-navigation/elements';
type Props = {
@@ -15,22 +9,35 @@ type Props = {
style?: StyleProp<ViewStyle>;
};
export default function ScreenFallback({ visible, children, ...rest }: Props) {
// react-native-screens is buggy on web
if (screensEnabled?.() && Platform.OS !== 'web') {
if (shouldUseActivityState) {
return (
<Screen activityState={visible ? 2 : 0} {...rest}>
{children}
</Screen>
);
} else {
return (
<Screen active={visible ? 1 : 0} {...rest}>
{children}
</Screen>
);
}
let Screens: typeof import('react-native-screens') | undefined;
try {
Screens = require('react-native-screens');
} catch (e) {
// Ignore
}
export const MaybeScreenContainer = ({
enabled,
...rest
}: ViewProps & {
enabled: boolean;
children: React.ReactNode;
}) => {
if (Screens?.screensEnabled?.()) {
return <Screens.ScreenContainer enabled={enabled} {...rest} />;
}
return <View {...rest} />;
};
export function MaybeScreen({ visible, children, ...rest }: Props) {
if (Screens?.screensEnabled?.()) {
return (
<Screens.Screen activityState={visible ? 2 : 0} {...rest}>
{children}
</Screens.Screen>
);
}
return (

View File

@@ -3,6 +3,40 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [6.0.0-next.6](https://github.com/react-navigation/react-navigation/compare/@react-navigation/core@6.0.0-next.5...@react-navigation/core@6.0.0-next.6) (2021-05-09)
### Bug Fixes
* fix type annotations for useNavigation (again) ([929c3e3](https://github.com/react-navigation/react-navigation/commit/929c3e3a3b3eb32d197ef1f887dc4cbdce48eaff))
# [6.0.0-next.5](https://github.com/react-navigation/react-navigation/compare/@react-navigation/core@6.0.0-next.4...@react-navigation/core@6.0.0-next.5) (2021-05-09)
### Bug Fixes
* fix type annotations for useNavigation ([7da45e1](https://github.com/react-navigation/react-navigation/commit/7da45e1e8951ff46e09db4ebc2c88085c67ab8e9))
# [6.0.0-next.4](https://github.com/react-navigation/react-navigation/compare/@react-navigation/core@6.0.0-next.3...@react-navigation/core@6.0.0-next.4) (2021-05-09)
### Features
* add a new component to group multiple screens with common options ([1a6aebe](https://github.com/react-navigation/react-navigation/commit/1a6aebefcb77ea708687475c55742407d69808ce))
* add ability to specify root param list ([b28bfdd](https://github.com/react-navigation/react-navigation/commit/b28bfddc17cbf3996fac04a34b2a7085ecf88be5))
# [6.0.0-next.3](https://github.com/react-navigation/react-navigation/compare/@react-navigation/core@6.0.0-next.2...@react-navigation/core@6.0.0-next.3) (2021-05-01)

View File

@@ -1,7 +1,7 @@
{
"name": "@react-navigation/core",
"description": "Core utilities for building navigators",
"version": "6.0.0-next.3",
"version": "6.0.0-next.6",
"keywords": [
"react",
"react-native",

View File

@@ -0,0 +1,13 @@
import type { ParamListBase } from '@react-navigation/routers';
import type { RouteGroupConfig } from './types';
/**
* Empty component used for grouping screen configs.
*/
export default function Group<
ParamList extends ParamListBase,
ScreenOptions extends {}
>(_: RouteGroupConfig<ParamList, ScreenOptions>) {
/* istanbul ignore next */
return null;
}

View File

@@ -9,14 +9,10 @@ import NavigationStateContext from './NavigationStateContext';
import StaticContainer from './StaticContainer';
import EnsureSingleNavigator from './EnsureSingleNavigator';
import useOptionsGetters from './useOptionsGetters';
import type { NavigationProp, RouteConfig, EventMapBase } from './types';
import type { NavigationProp, RouteConfigComponent } from './types';
type Props<
State extends NavigationState,
ScreenOptions extends {},
EventMap extends EventMapBase
> = {
screen: RouteConfig<ParamListBase, string, State, ScreenOptions, EventMap>;
type Props<State extends NavigationState, ScreenOptions extends {}> = {
screen: RouteConfigComponent<ParamListBase, string> & { name: string };
navigation: NavigationProp<ParamListBase, string, State, ScreenOptions>;
route: Route<string>;
routeState: NavigationState | PartialState<NavigationState> | undefined;
@@ -31,8 +27,7 @@ type Props<
*/
export default function SceneView<
State extends NavigationState,
ScreenOptions extends {},
EventMap extends EventMapBase
ScreenOptions extends {}
>({
screen,
route,
@@ -41,7 +36,7 @@ export default function SceneView<
getState,
setState,
options,
}: Props<State, ScreenOptions, EventMap>) {
}: Props<State, ScreenOptions>) {
const navigatorKeyRef = React.useRef<string | undefined>();
const getKey = React.useCallback(() => navigatorKeyRef.current, []);

View File

@@ -33,8 +33,10 @@ it('converts state to path string', () => {
const path = '/foo/bar/baz%20qux?author=jane&valid=true';
expect(getPathFromState(state)).toBe(path);
expect(getPathFromState(getStateFromPath(path) as State)).toBe(path);
expect(getPathFromState<object>(state)).toBe(path);
expect(
getPathFromState<object>(getStateFromPath<object>(path) as State)
).toBe(path);
});
it('converts state to path string with config', () => {
@@ -97,9 +99,12 @@ it('converts state to path string with config', () => {
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState<object>(state, config)).toBe(path);
expect(
getPathFromState(getStateFromPath(path, config) as State, config)
getPathFromState<object>(
getStateFromPath<object>(path, config) as State,
config
)
).toBe(path);
});
@@ -116,8 +121,10 @@ it('handles route without param', () => {
],
};
expect(getPathFromState(state)).toBe(path);
expect(getPathFromState(getStateFromPath(path) as State)).toBe(path);
expect(getPathFromState<object>(state)).toBe(path);
expect(
getPathFromState<object>(getStateFromPath<object>(path) as State)
).toBe(path);
});
it("doesn't add query param for empty params", () => {
@@ -131,8 +138,10 @@ it("doesn't add query param for empty params", () => {
],
};
expect(getPathFromState(state)).toBe(path);
expect(getPathFromState(getStateFromPath(path) as State)).toBe(path);
expect(getPathFromState<object>(state)).toBe(path);
expect(
getPathFromState<object>(getStateFromPath<object>(path) as State)
).toBe(path);
});
it('handles state with config with nested screens', () => {
@@ -208,9 +217,12 @@ it('handles state with config with nested screens', () => {
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState<object>(state, config)).toBe(path);
expect(
getPathFromState(getStateFromPath(path, config) as State, config)
getPathFromState<object>(
getStateFromPath<object>(path, config) as State,
config
)
).toBe(path);
});
@@ -287,9 +299,12 @@ it('handles state with config with nested screens and exact', () => {
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState<object>(state, config)).toBe(path);
expect(
getPathFromState(getStateFromPath(path, config) as State, config)
getPathFromState<object>(
getStateFromPath<object>(path, config) as State,
config
)
).toBe(path);
});
@@ -352,9 +367,12 @@ it('handles state with config with nested screens and unused configs', () => {
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState<object>(state, config)).toBe(path);
expect(
getPathFromState(getStateFromPath(path, config) as State, config)
getPathFromState<object>(
getStateFromPath<object>(path, config) as State,
config
)
).toBe(path);
});
@@ -418,9 +436,12 @@ it('handles state with config with nested screens and unused configs with exact'
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState<object>(state, config)).toBe(path);
expect(
getPathFromState(getStateFromPath(path, config) as State, config)
getPathFromState<object>(
getStateFromPath<object>(path, config) as State,
config
)
).toBe(path);
});
@@ -498,9 +519,12 @@ it('handles nested object with stringify in it', () => {
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState<object>(state, config)).toBe(path);
expect(
getPathFromState(getStateFromPath(path, config) as State, config)
getPathFromState<object>(
getStateFromPath<object>(path, config) as State,
config
)
).toBe(path);
});
@@ -580,9 +604,12 @@ it('handles nested object with stringify in it with exact', () => {
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState<object>(state, config)).toBe(path);
expect(
getPathFromState(getStateFromPath(path, config) as State, config)
getPathFromState<object>(
getStateFromPath<object>(path, config) as State,
config
)
).toBe(path);
});
@@ -623,9 +650,12 @@ it('handles nested object for second route depth', () => {
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState<object>(state, config)).toBe(path);
expect(
getPathFromState(getStateFromPath(path, config) as State, config)
getPathFromState<object>(
getStateFromPath<object>(path, config) as State,
config
)
).toBe(path);
});
@@ -669,9 +699,12 @@ it('handles nested object for second route depth with exact', () => {
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState<object>(state, config)).toBe(path);
expect(
getPathFromState(getStateFromPath(path, config) as State, config)
getPathFromState<object>(
getStateFromPath<object>(path, config) as State,
config
)
).toBe(path);
});
@@ -719,9 +752,12 @@ it('handles nested object for second route depth and path and stringify in roots
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState<object>(state, config)).toBe(path);
expect(
getPathFromState(getStateFromPath(path, config) as State, config)
getPathFromState<object>(
getStateFromPath<object>(path, config) as State,
config
)
).toBe(path);
});
@@ -774,9 +810,12 @@ it('handles nested object for second route depth and path and stringify in roots
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState<object>(state, config)).toBe(path);
expect(
getPathFromState(getStateFromPath(path, config) as State, config)
getPathFromState<object>(
getStateFromPath<object>(path, config) as State,
config
)
).toBe(path);
});
@@ -805,9 +844,12 @@ it('ignores empty string paths', () => {
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState<object>(state, config)).toBe(path);
expect(
getPathFromState(getStateFromPath(path, config) as State, config)
getPathFromState<object>(
getStateFromPath<object>(path, config) as State,
config
)
).toBe(path);
});
@@ -850,9 +892,12 @@ it('keeps query params if path is empty', () => {
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState<object>(state, config)).toBe(path);
expect(
getPathFromState(getStateFromPath(path, config) as State, config)
getPathFromState<object>(
getStateFromPath<object>(path, config) as State,
config
)
).toEqual(path);
});
@@ -894,9 +939,12 @@ it('cuts nested configs too', () => {
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState<object>(state, config)).toBe(path);
expect(
getPathFromState(getStateFromPath(path, config) as State, config)
getPathFromState<object>(
getStateFromPath<object>(path, config) as State,
config
)
).toBe(path);
});
@@ -939,9 +987,12 @@ it('cuts nested configs too with exact', () => {
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState<object>(state, config)).toBe(path);
expect(
getPathFromState(getStateFromPath(path, config) as State, config)
getPathFromState<object>(
getStateFromPath<object>(path, config) as State,
config
)
).toBe(path);
});
@@ -977,9 +1028,12 @@ it('handles empty path at the end', () => {
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState<object>(state, config)).toBe(path);
expect(
getPathFromState(getStateFromPath(path, config) as State, config)
getPathFromState<object>(
getStateFromPath<object>(path, config) as State,
config
)
).toBe(path);
});
@@ -1012,9 +1066,12 @@ it('returns "/" for empty path', () => {
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState<object>(state, config)).toBe(path);
expect(
getPathFromState(getStateFromPath(path, config) as State, config)
getPathFromState<object>(
getStateFromPath<object>(path, config) as State,
config
)
).toBe(path);
});
@@ -1042,9 +1099,12 @@ it('parses no path specified', () => {
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState<object>(state, config)).toBe(path);
expect(
getPathFromState(getStateFromPath(path, config) as State, config)
getPathFromState<object>(
getStateFromPath<object>(path, config) as State,
config
)
).toBe(path);
});
@@ -1121,9 +1181,12 @@ it('strips undefined query params', () => {
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState<object>(state, config)).toBe(path);
expect(
getPathFromState(getStateFromPath(path, config) as State, config)
getPathFromState<object>(
getStateFromPath<object>(path, config) as State,
config
)
).toBe(path);
});
@@ -1201,9 +1264,12 @@ it('strips undefined query params with exact', () => {
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState<object>(state, config)).toBe(path);
expect(
getPathFromState(getStateFromPath(path, config) as State, config)
getPathFromState<object>(
getStateFromPath<object>(path, config) as State,
config
)
).toBe(path);
});
@@ -1278,9 +1344,12 @@ it('handles stripping all query params', () => {
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState<object>(state, config)).toBe(path);
expect(
getPathFromState(getStateFromPath(path, config) as State, config)
getPathFromState<object>(
getStateFromPath<object>(path, config) as State,
config
)
).toBe(path);
});
@@ -1357,9 +1426,12 @@ it('handles stripping all query params with exact', () => {
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState<object>(state, config)).toBe(path);
expect(
getPathFromState(getStateFromPath(path, config) as State, config)
getPathFromState<object>(
getStateFromPath<object>(path, config) as State,
config
)
).toBe(path);
});
@@ -1380,9 +1452,12 @@ it('replaces undefined query params', () => {
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState<object>(state, config)).toBe(path);
expect(
getPathFromState(getStateFromPath(path, config) as State, config)
getPathFromState<object>(
getStateFromPath<object>(path, config) as State,
config
)
).toBe(path);
});
@@ -1405,9 +1480,12 @@ it('matches wildcard patterns at root', () => {
routes: [{ name: '404' }],
};
expect(getPathFromState(state, config)).toBe('/404');
expect(getPathFromState<object>(state, config)).toBe('/404');
expect(
getPathFromState(getStateFromPath(path, config) as State, config)
getPathFromState<object>(
getStateFromPath<object>(path, config) as State,
config
)
).toBe('/404');
});
@@ -1447,9 +1525,12 @@ it('matches wildcard patterns at nested level', () => {
],
};
expect(getPathFromState(state, config)).toBe('/bar/42/404');
expect(getPathFromState<object>(state, config)).toBe('/bar/42/404');
expect(
getPathFromState(getStateFromPath(path, config) as State, config)
getPathFromState<object>(
getStateFromPath<object>(path, config) as State,
config
)
).toBe('/bar/42/404');
});
@@ -1492,9 +1573,12 @@ it('matches wildcard patterns at nested level with exact', () => {
],
};
expect(getPathFromState(state, config)).toBe('/404');
expect(getPathFromState<object>(state, config)).toBe('/404');
expect(
getPathFromState(getStateFromPath(path, config) as State, config)
getPathFromState<object>(
getStateFromPath<object>(path, config) as State,
config
)
).toBe('/404');
});
@@ -1535,9 +1619,12 @@ it('tries to match wildcard patterns at the end', () => {
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState<object>(state, config)).toBe(path);
expect(
getPathFromState(getStateFromPath(path, config) as State, config)
getPathFromState<object>(
getStateFromPath<object>(path, config) as State,
config
)
).toBe(path);
});
@@ -1570,8 +1657,11 @@ it('uses nearest parent wildcard match for unmatched paths', () => {
],
};
expect(getPathFromState(state, config)).toBe('/404');
expect(getPathFromState<object>(state, config)).toBe('/404');
expect(
getPathFromState(getStateFromPath(path, config) as State, config)
getPathFromState<object>(
getStateFromPath<object>(path, config) as State,
config
)
).toBe('/404');
});

View File

@@ -12,7 +12,7 @@ const changePath = <T extends InitialState>(state: T, path: string): T =>
});
it('returns undefined for invalid path', () => {
expect(getStateFromPath('//')).toBe(undefined);
expect(getStateFromPath<object>('//')).toBe(undefined);
});
it('converts path string to initial state', () => {
@@ -41,8 +41,8 @@ it('converts path string to initial state', () => {
],
};
expect(getStateFromPath(path)).toEqual(state);
expect(getStateFromPath(getPathFromState(state))).toEqual(
expect(getStateFromPath<object>(path)).toEqual(state);
expect(getStateFromPath<object>(getPathFromState<object>(state))).toEqual(
changePath(state, '/foo/bar/baz%20qux?author=jane%20%26%20co&valid=true')
);
});
@@ -106,16 +106,16 @@ it('converts path string to initial state with config', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(state);
});
it('handles leading slash when converting', () => {
const path = '/foo/bar/?count=42';
expect(getStateFromPath(path)).toEqual({
expect(getStateFromPath<object>(path)).toEqual({
routes: [
{
name: 'foo',
@@ -136,7 +136,7 @@ it('handles leading slash when converting', () => {
it('handles ending slash when converting', () => {
const path = 'foo/bar/?count=42';
expect(getStateFromPath(path)).toEqual({
expect(getStateFromPath<object>(path)).toEqual({
routes: [
{
name: 'foo',
@@ -167,8 +167,8 @@ it('handles route without param', () => {
],
};
expect(getStateFromPath(path)).toEqual(state);
expect(getStateFromPath(getPathFromState(state))).toEqual(
expect(getStateFromPath<object>(path)).toEqual(state);
expect(getStateFromPath<object>(getPathFromState<object>(state))).toEqual(
changePath(state, '/foo/bar')
);
});
@@ -245,10 +245,10 @@ it('converts path string to initial state with config with nested screens', () =
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(state);
});
it('converts path string to initial state with config with nested screens and unused parse functions', () => {
@@ -308,10 +308,10 @@ it('converts path string to initial state with config with nested screens and un
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
changePath(state, '/foe/baz/Jane?count=10&answer=42&valid=true')
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(changePath(state, '/foe/baz/Jane?count=10&answer=42&valid=true'));
});
it('handles nested object with unused configs and with parse in it', () => {
@@ -400,10 +400,10 @@ it('handles nested object with unused configs and with parse in it', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(state);
});
it('handles parse in nested object for second route depth', () => {
@@ -450,10 +450,10 @@ it('handles parse in nested object for second route depth', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(state);
});
it('handles parse in nested object for second route depth and and path and parse in roots', () => {
@@ -501,10 +501,10 @@ it('handles parse in nested object for second route depth and and path and parse
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(state);
});
it('handles initialRouteName at top level', () => {
@@ -545,10 +545,10 @@ it('handles initialRouteName at top level', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(state);
});
it('handles initialRouteName inside a screen', () => {
@@ -591,10 +591,10 @@ it('handles initialRouteName inside a screen', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(state);
});
it('handles initialRouteName included in path', () => {
@@ -633,10 +633,10 @@ it('handles initialRouteName included in path', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(state);
});
it('handles two initialRouteNames', () => {
@@ -728,10 +728,10 @@ it('handles two initialRouteNames', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(state);
});
it('accepts initialRouteName without config for it', () => {
@@ -823,10 +823,10 @@ it('accepts initialRouteName without config for it', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(state);
});
it('returns undefined if path is empty and no matching screen is present', () => {
@@ -847,7 +847,7 @@ it('returns undefined if path is empty and no matching screen is present', () =>
const path = '';
expect(getStateFromPath(path, config)).toEqual(undefined);
expect(getStateFromPath<object>(path, config)).toEqual(undefined);
});
it('returns matching screen if path is empty', () => {
@@ -886,10 +886,10 @@ it('returns matching screen if path is empty', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
changePath(state, '/')
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(changePath(state, '/'));
});
it('returns matching screen with params if path is empty', () => {
@@ -931,10 +931,10 @@ it('returns matching screen with params if path is empty', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
changePath(state, '/?foo=42')
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(changePath(state, '/?foo=42'));
});
it("doesn't match nested screen if path is empty", () => {
@@ -959,7 +959,7 @@ it("doesn't match nested screen if path is empty", () => {
const path = '';
expect(getStateFromPath(path, config)).toEqual(undefined);
expect(getStateFromPath<object>(path, config)).toEqual(undefined);
});
it('chooses more exhaustive pattern', () => {
@@ -1004,10 +1004,10 @@ it('chooses more exhaustive pattern', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(state);
});
it('handles same paths beginnings', () => {
@@ -1048,10 +1048,10 @@ it('handles same paths beginnings', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(state);
});
it('handles same paths beginnings with params', () => {
@@ -1096,10 +1096,10 @@ it('handles same paths beginnings with params', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(state);
});
it('handles not taking path with too many segments', () => {
@@ -1151,10 +1151,10 @@ it('handles not taking path with too many segments', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(state);
});
it('handles differently ordered params v1', () => {
@@ -1206,10 +1206,10 @@ it('handles differently ordered params v1', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(state);
});
it('handles differently ordered params v2', () => {
@@ -1261,10 +1261,10 @@ it('handles differently ordered params v2', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(state);
});
it('handles differently ordered params v3', () => {
@@ -1316,10 +1316,10 @@ it('handles differently ordered params v3', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(state);
});
it('handles differently ordered params v4', () => {
@@ -1371,10 +1371,10 @@ it('handles differently ordered params v4', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
changePath(state, '/5/foos/res/20')
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(changePath(state, '/5/foos/res/20'));
});
it('handles simple optional params', () => {
@@ -1426,10 +1426,10 @@ it('handles simple optional params', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(state);
});
it('handle 2 optional params at the end v1', () => {
@@ -1481,10 +1481,10 @@ it('handle 2 optional params at the end v1', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(state);
});
it('handle 2 optional params at the end v2', () => {
@@ -1536,10 +1536,10 @@ it('handle 2 optional params at the end v2', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(state);
});
it('handle 2 optional params at the end v3', () => {
@@ -1592,10 +1592,10 @@ it('handle 2 optional params at the end v3', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(state);
});
it('handle optional params in the middle v1', () => {
@@ -1648,10 +1648,10 @@ it('handle optional params in the middle v1', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(state);
});
it('handle optional params in the middle v2', () => {
@@ -1704,10 +1704,10 @@ it('handle optional params in the middle v2', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(state);
});
it('handle optional params in the middle v3', () => {
@@ -1761,10 +1761,10 @@ it('handle optional params in the middle v3', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(state);
});
it('handle optional params in the middle v4', () => {
@@ -1818,10 +1818,10 @@ it('handle optional params in the middle v4', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(state);
});
it('handle optional params in the middle v5', () => {
@@ -1875,10 +1875,10 @@ it('handle optional params in the middle v5', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(state);
});
it('handle optional params in the beginning v1', () => {
@@ -1932,10 +1932,10 @@ it('handle optional params in the beginning v1', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
changePath(state, '/5/10/foos/15')
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(changePath(state, '/5/10/foos/15'));
});
it('handle optional params in the beginning v2', () => {
@@ -1989,10 +1989,10 @@ it('handle optional params in the beginning v2', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
changePath(state, '/5/10/foos/15')
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(changePath(state, '/5/10/foos/15'));
});
it('merges parent patterns if needed', () => {
@@ -2030,10 +2030,10 @@ it('merges parent patterns if needed', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
changePath(state, '/foo/42/baz/babel')
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(changePath(state, '/foo/42/baz/babel'));
});
it('ignores extra slashes in the pattern', () => {
@@ -2067,10 +2067,10 @@ it('ignores extra slashes in the pattern', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(state);
});
it('matches wildcard patterns at root', () => {
@@ -2092,10 +2092,10 @@ it('matches wildcard patterns at root', () => {
routes: [{ name: '404', path }],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
changePath(state, '/404')
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(changePath(state, '/404'));
});
it('matches wildcard patterns at nested level', () => {
@@ -2134,10 +2134,10 @@ it('matches wildcard patterns at nested level', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
changePath(state, '/bar/42/404')
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(changePath(state, '/bar/42/404'));
});
it('matches wildcard patterns at nested level with exact', () => {
@@ -2179,10 +2179,10 @@ it('matches wildcard patterns at nested level with exact', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
changePath(state, '/404')
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(changePath(state, '/404'));
});
it('tries to match wildcard patterns at the end', () => {
@@ -2222,10 +2222,10 @@ it('tries to match wildcard patterns at the end', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(state);
});
it('uses nearest parent wildcard match for unmatched paths', () => {
@@ -2257,17 +2257,17 @@ it('uses nearest parent wildcard match for unmatched paths', () => {
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
changePath(state, '/404')
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(changePath(state, '/404'));
});
it('throws if two screens map to the same pattern', () => {
const path = '/bar/42/baz/test';
expect(() =>
getStateFromPath(path, {
getStateFromPath<object>(path, {
screens: {
Foo: {
screens: {
@@ -2287,7 +2287,7 @@ it('throws if two screens map to the same pattern', () => {
);
expect(() =>
getStateFromPath(path, {
getStateFromPath<object>(path, {
screens: {
Foo: {
screens: {
@@ -2354,10 +2354,10 @@ it('correctly applies initialRouteName for config with similar route names', ()
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(state);
});
it('correctly applies initialRouteName for config with similar route names v2', () => {
@@ -2414,8 +2414,8 @@ it('correctly applies initialRouteName for config with similar route names v2',
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
expect(getStateFromPath<object>(path, config)).toEqual(state);
expect(
getStateFromPath<object>(getPathFromState<object>(state, config), config)
).toEqual(state);
});

View File

@@ -1,6 +1,7 @@
import * as React from 'react';
import { render, act } from '@testing-library/react-native';
import type { NavigationState, ParamListBase } from '@react-navigation/routers';
import Group from '../Group';
import Screen from '../Screen';
import BaseNavigationContainer from '../BaseNavigationContainer';
import useNavigationBuilder from '../useNavigationBuilder';
@@ -248,6 +249,53 @@ it('initializes state for nested screens in React.Fragment', () => {
});
});
it('initializes state for nested screens in Group', () => {
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return descriptors[state.routes[state.index].key].render();
};
const TestScreen = (props: any) => {
React.useEffect(() => {
props.navigation.dispatch({ type: 'UPDATE' });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return null;
};
const onStateChange = jest.fn();
const element = (
<BaseNavigationContainer onStateChange={onStateChange}>
<TestNavigator>
<Screen name="foo" component={TestScreen} />
<Group>
<Screen name="bar" component={jest.fn()} />
<Screen name="baz" component={jest.fn()} />
</Group>
</TestNavigator>
</BaseNavigationContainer>
);
render(element).update(element);
expect(onStateChange).toBeCalledTimes(1);
expect(onStateChange).toBeCalledWith({
stale: false,
type: 'test',
index: 0,
key: '0',
routeNames: ['foo', 'bar', 'baz'],
routes: [
{ key: 'foo', name: 'foo' },
{ key: 'bar', name: 'bar' },
{ key: 'baz', name: 'baz' },
],
});
});
it('initializes state for nested navigator on navigation', () => {
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
@@ -1450,7 +1498,7 @@ it('throws when Screen is not the direct children', () => {
);
expect(() => render(element).update(element)).toThrowError(
"A navigator can only contain 'Screen' components as its direct children (found 'Bar')"
"A navigator can only contain 'Screen', 'Group' or 'React.Fragment' as its direct children (found 'Bar')"
);
});
@@ -1475,7 +1523,7 @@ it('throws when undefined component is a direct children', () => {
spy.mockRestore();
expect(() => render(element).update(element)).toThrowError(
"A navigator can only contain 'Screen' components as its direct children (found 'undefined' for the screen 'foo')"
"A navigator can only contain 'Screen', 'Group' or 'React.Fragment' as its direct children (found 'undefined' for the screen 'foo')"
);
});
@@ -1495,7 +1543,7 @@ it('throws when a tag is a direct children', () => {
);
expect(() => render(element).update(element)).toThrowError(
"A navigator can only contain 'Screen' components as its direct children (found 'screen' for the screen 'foo')"
"A navigator can only contain 'Screen', 'Group' or 'React.Fragment' as its direct children (found 'screen' for the screen 'foo')"
);
});
@@ -1515,7 +1563,7 @@ it('throws when a React Element is not the direct children', () => {
);
expect(() => render(element).update(element)).toThrowError(
"A navigator can only contain 'Screen' components as its direct children (found 'Hello world')"
"A navigator can only contain 'Screen', 'Group' or 'React.Fragment' as its direct children (found 'Hello world')"
);
});
@@ -1838,19 +1886,16 @@ it("returns focused screen's options with getCurrentOptions when focused screen
<TestNavigator>
<Screen name="bar" options={{ a: 'b' }}>
{() => (
<TestNavigator
initialRouteName="bar-a"
screenOptions={() => ({ sample2: 'data' })}
>
<TestNavigator initialRouteName="bar-a">
<Screen
name="bar-a"
component={TestScreen}
options={{ sample: 'data' }}
options={{ sample: '1' }}
/>
<Screen
name="bar-b"
component={TestScreen}
options={{ sample3: 'data' }}
options={{ sample2: '2' }}
/>
</TestNavigator>
)}
@@ -1863,15 +1908,122 @@ it("returns focused screen's options with getCurrentOptions when focused screen
render(container).update(container);
expect(navigation.getCurrentOptions()).toEqual({
sample: 'data',
sample2: 'data',
sample: '1',
});
act(() => navigation.navigate('bar-b'));
expect(navigation.getCurrentOptions()).toEqual({
sample2: 'data',
sample3: 'data',
sample2: '2',
});
});
it("returns focused screen's options with getCurrentOptions when focused screen is rendered when using screenOptions", () => {
const TestNavigator = (props: any): any => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return descriptors[state.routes[state.index].key].render();
};
const TestScreen = () => null;
const navigation = createNavigationContainerRef<ParamListBase>();
const container = (
<BaseNavigationContainer ref={navigation}>
<TestNavigator>
<Screen name="bar" options={{ a: 'b' }}>
{() => (
<TestNavigator
initialRouteName="bar-a"
screenOptions={() => ({ sample2: '2' })}
>
<Screen
name="bar-a"
component={TestScreen}
options={{ sample: '1' }}
/>
<Screen
name="bar-b"
component={TestScreen}
options={{ sample3: '3' }}
/>
</TestNavigator>
)}
</Screen>
<Screen name="xux" component={TestScreen} />
</TestNavigator>
</BaseNavigationContainer>
);
render(container).update(container);
expect(navigation.getCurrentOptions()).toEqual({
sample: '1',
sample2: '2',
});
act(() => navigation.navigate('bar-b'));
expect(navigation.getCurrentOptions()).toEqual({
sample2: '2',
sample3: '3',
});
});
it("returns focused screen's options with getCurrentOptions when focused screen is rendered when using Group", () => {
const TestNavigator = (props: any): any => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return descriptors[state.routes[state.index].key].render();
};
const TestScreen = () => null;
const navigation = createNavigationContainerRef<ParamListBase>();
const container = (
<BaseNavigationContainer ref={navigation}>
<TestNavigator>
<Screen name="bar" options={{ a: 'b' }}>
{() => (
<TestNavigator
initialRouteName="bar-a"
screenOptions={() => ({ sample2: '2' })}
>
<Screen
name="bar-a"
component={TestScreen}
options={{ sample: '1' }}
/>
<Group screenOptions={{ sample4: '4' }}>
<Screen
name="bar-b"
component={TestScreen}
options={{ sample3: '3' }}
/>
</Group>
</TestNavigator>
)}
</Screen>
<Screen name="xux" component={TestScreen} />
</TestNavigator>
</BaseNavigationContainer>
);
render(container).update(container);
expect(navigation.getCurrentOptions()).toEqual({
sample: '1',
sample2: '2',
});
act(() => navigation.navigate('bar-b'));
expect(navigation.getCurrentOptions()).toEqual({
sample2: '2',
sample3: '3',
sample4: '4',
});
});
@@ -1891,19 +2043,16 @@ it("returns focused screen's options with getCurrentOptions when all screens are
<TestNavigator>
<Screen name="bar" options={{ a: 'b' }}>
{() => (
<TestNavigator
initialRouteName="bar-a"
screenOptions={() => ({ sample2: 'data' })}
>
<TestNavigator initialRouteName="bar-a">
<Screen
name="bar-a"
component={TestScreen}
options={{ sample: 'data' }}
options={{ sample: '1' }}
/>
<Screen
name="bar-b"
component={TestScreen}
options={{ sample3: 'data' }}
options={{ sample2: '2' }}
/>
</TestNavigator>
)}
@@ -1916,15 +2065,122 @@ it("returns focused screen's options with getCurrentOptions when all screens are
render(container).update(container);
expect(navigation.getCurrentOptions()).toEqual({
sample: 'data',
sample2: 'data',
sample: '1',
});
act(() => navigation.navigate('bar-b'));
expect(navigation.getCurrentOptions()).toEqual({
sample2: 'data',
sample3: 'data',
sample2: '2',
});
});
it("returns focused screen's options with getCurrentOptions when all screens are rendered with screenOptions", () => {
const TestNavigator = (props: any): any => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return <>{state.routes.map((route) => descriptors[route.key].render())}</>;
};
const TestScreen = () => null;
const navigation = createNavigationContainerRef<ParamListBase>();
const container = (
<BaseNavigationContainer ref={navigation}>
<TestNavigator>
<Screen name="bar" options={{ a: 'b' }}>
{() => (
<TestNavigator
initialRouteName="bar-a"
screenOptions={() => ({ sample2: '2' })}
>
<Screen
name="bar-a"
component={TestScreen}
options={{ sample: '1' }}
/>
<Screen
name="bar-b"
component={TestScreen}
options={{ sample3: '3' }}
/>
</TestNavigator>
)}
</Screen>
<Screen name="xux" component={TestScreen} />
</TestNavigator>
</BaseNavigationContainer>
);
render(container).update(container);
expect(navigation.getCurrentOptions()).toEqual({
sample: '1',
sample2: '2',
});
act(() => navigation.navigate('bar-b'));
expect(navigation.getCurrentOptions()).toEqual({
sample2: '2',
sample3: '3',
});
});
it("returns focused screen's options with getCurrentOptions when all screens are rendered with Group", () => {
const TestNavigator = (props: any): any => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return <>{state.routes.map((route) => descriptors[route.key].render())}</>;
};
const TestScreen = () => null;
const navigation = createNavigationContainerRef<ParamListBase>();
const container = (
<BaseNavigationContainer ref={navigation}>
<TestNavigator>
<Screen name="bar" options={{ a: 'b' }}>
{() => (
<TestNavigator
initialRouteName="bar-a"
screenOptions={() => ({ sample2: '2' })}
>
<Screen
name="bar-a"
component={TestScreen}
options={{ sample: '1' }}
/>
<Group screenOptions={{ sample4: '4' }}>
<Screen
name="bar-b"
component={TestScreen}
options={{ sample3: '3' }}
/>
</Group>
</TestNavigator>
)}
</Screen>
<Screen name="xux" component={TestScreen} />
</TestNavigator>
</BaseNavigationContainer>
);
render(container).update(container);
expect(navigation.getCurrentOptions()).toEqual({
sample: '1',
sample2: '2',
});
act(() => navigation.navigate('bar-b'));
expect(navigation.getCurrentOptions()).toEqual({
sample2: '2',
sample3: '3',
sample4: '4',
});
});

View File

@@ -1,11 +1,11 @@
import { CommonActions, ParamListBase } from '@react-navigation/routers';
import { CommonActions } from '@react-navigation/routers';
import type { NavigationContainerRefWithCurrent } from './types';
export const NOT_INITIALIZED_ERROR =
"The 'navigation' object hasn't been initialized yet. This might happen if you don't have a navigator mounted, or if the navigator hasn't finished mounting. See https://reactnavigation.org/docs/navigating-without-navigation-prop#handling-initialization for more details.";
export default function createNavigationContainerRef<
ParamList extends ParamListBase
ParamList extends {} = ReactNavigation.RootParamList
>(): NavigationContainerRefWithCurrent<ParamList> {
const methods = [
...Object.keys(CommonActions),

View File

@@ -1,5 +1,6 @@
import type * as React from 'react';
import type { ParamListBase, NavigationState } from '@react-navigation/routers';
import Group from './Group';
import Screen from './Screen';
import type { TypedNavigator, EventMapBase } from './types';
@@ -31,6 +32,7 @@ export default function createNavigatorFactory<
return {
Navigator,
Group,
Screen,
};
};

View File

@@ -13,7 +13,10 @@ type ConfigItem = {
screens?: Record<string, ConfigItem>;
};
type Options = { initialRouteName?: string; screens: PathConfigMap };
type Options = {
initialRouteName?: string;
screens: PathConfigMap<object>;
};
type NavigateAction<State extends NavigationState> = {
type: 'NAVIGATE';
@@ -29,7 +32,9 @@ export default function getActionFromState(
options?: Options
): NavigateAction<NavigationState> | CommonActions.Action | undefined {
// Create a normalized configs object which will be easier to use
const normalizedConfig = options ? createNormalizedConfigItem(options) : {};
const normalizedConfig = options
? createNormalizedConfigItem(options as PathConfig<object> | string)
: {};
const routes =
state.index != null ? state.routes.slice(0, state.index + 1) : state.routes;
@@ -130,7 +135,7 @@ export default function getActionFromState(
};
}
const createNormalizedConfigItem = (config: PathConfig | string) =>
const createNormalizedConfigItem = (config: PathConfig<object> | string) =>
typeof config === 'object' && config != null
? {
initialRouteName: config.initialRouteName,
@@ -141,7 +146,7 @@ const createNormalizedConfigItem = (config: PathConfig | string) =>
}
: {};
const createNormalizedConfigs = (options: PathConfigMap) =>
const createNormalizedConfigs = (options: PathConfigMap<object>) =>
Object.entries(options).reduce<Record<string, ConfigItem>>((acc, [k, v]) => {
acc[k] = createNormalizedConfigItem(v);
return acc;

View File

@@ -7,7 +7,10 @@ import type {
import fromEntries from './fromEntries';
import type { PathConfig, PathConfigMap } from './types';
type Options = { initialRouteName?: string; screens: PathConfigMap };
type Options<ParamList> = {
initialRouteName?: string;
screens: PathConfigMap<ParamList>;
};
type State = NavigationState | Omit<PartialState<NavigationState>, 'stale'>;
@@ -61,9 +64,9 @@ const getActiveRoute = (state: State): { name: string; params?: object } => {
* @param options Extra options to fine-tune how to serialize the path.
* @returns Path representing the state, e.g. /foo/bar?count=42.
*/
export default function getPathFromState(
export default function getPathFromState<ParamList extends {}>(
state: State,
options?: Options
options?: Options<ParamList>
): string {
if (state == null) {
throw Error(
@@ -238,7 +241,7 @@ const joinPaths = (...paths: string[]): string =>
.join('/');
const createConfigItem = (
config: PathConfig | string,
config: PathConfig<object> | string,
parentPattern?: string
): ConfigItem => {
if (typeof config === 'string') {
@@ -276,7 +279,7 @@ const createConfigItem = (
};
const createNormalizedConfigs = (
options: PathConfigMap,
options: PathConfigMap<object>,
pattern?: string
): Record<string, ConfigItem> =>
fromEntries(

View File

@@ -8,9 +8,9 @@ import type {
import findFocusedRoute from './findFocusedRoute';
import type { PathConfigMap } from './types';
type Options = {
type Options<ParamList extends {}> = {
initialRouteName?: string;
screens: PathConfigMap;
screens: PathConfigMap<ParamList>;
};
type ParseConfig = Record<string, (value: string) => any>;
@@ -60,9 +60,9 @@ type ParsedRoute = {
* @param path Path string to parse and convert, e.g. /foo/bar?count=42.
* @param options Extra options to fine-tune how to parse the path.
*/
export default function getStateFromPath(
export default function getStateFromPath<ParamList extends {}>(
path: string,
options?: Options
options?: Options<ParamList>
): ResultState | undefined {
let initialRoutes: InitialRouteConfig[] = [];
@@ -106,7 +106,7 @@ export default function getStateFromPath(
...Object.keys(screens).map((key) =>
createNormalizedConfigs(
key,
screens as PathConfigMap,
screens as PathConfigMap<object>,
[],
initialRoutes,
[]
@@ -307,7 +307,7 @@ const matchAgainstConfigs = (remaining: string, configs: RouteConfig[]) => {
const createNormalizedConfigs = (
screen: string,
routeConfig: PathConfigMap,
routeConfig: PathConfigMap<object>,
routeNames: string[] = [],
initials: InitialRouteConfig[],
parentScreens: string[],
@@ -319,6 +319,7 @@ const createNormalizedConfigs = (
parentScreens.push(screen);
// @ts-expect-error: we can't strongly typecheck this for now
const config = routeConfig[screen];
if (typeof config === 'string') {
@@ -345,7 +346,13 @@ const createNormalizedConfigs = (
: config.path || '';
configs.push(
createConfigItem(screen, routeNames, pattern, config.path, config.parse)
createConfigItem(
screen,
routeNames,
pattern!,
config.path,
config.parse
)
);
}
@@ -361,7 +368,7 @@ const createNormalizedConfigs = (
Object.keys(config.screens).forEach((nestedConfig) => {
const result = createNormalizedConfigs(
nestedConfig,
config.screens as PathConfigMap,
config.screens as PathConfigMap<object>,
routeNames,
initials,
[...parentScreens],

View File

@@ -9,6 +9,14 @@ import type {
ParamListBase,
} from '@react-navigation/routers';
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace ReactNavigation {
// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface RootParamList {}
}
}
type Keyof<T extends {}> = Extract<keyof T, string>;
export type DefaultNavigatorOptions<
@@ -17,7 +25,7 @@ export type DefaultNavigatorOptions<
> = DefaultRouterOptions<Keyof<ParamList>> & {
/**
* Children React Elements to extract the route configuration from.
* Only `Screen` components are supported as children.
* Only `Screen`, `Group` and `React.Fragment` are supported as children.
*/
children: React.ReactNode;
/**
@@ -265,7 +273,7 @@ export type NavigationContainerProps = {
};
export type NavigationProp<
ParamList extends ParamListBase,
ParamList extends {},
RouteName extends keyof ParamList = Keyof<ParamList>,
State extends NavigationState = NavigationState<ParamList>,
ScreenOptions extends {} = {},
@@ -376,6 +384,38 @@ export type ScreenListeners<
}
>;
export type RouteConfigComponent<
ParamList extends ParamListBase,
RouteName extends keyof ParamList
> =
| {
/**
* React component to render for this screen.
*/
component: React.ComponentType<any>;
getComponent?: never;
children?: never;
}
| {
/**
* Lazily get a React component to render for this screen.
*/
getComponent: () => React.ComponentType<any>;
component?: never;
children?: never;
}
| {
/**
* Render callback to render content of this screen.
*/
children: (props: {
route: RouteProp<ParamList, RouteName>;
navigation: any;
}) => React.ReactNode;
component?: never;
getComponent?: never;
};
export type RouteConfig<
ParamList extends ParamListBase,
RouteName extends keyof ParamList,
@@ -420,35 +460,27 @@ export type RouteConfig<
* Initial params object for the route.
*/
initialParams?: Partial<ParamList[RouteName]>;
} & (
| {
/**
* React component to render for this screen.
*/
component: React.ComponentType<any>;
getComponent?: never;
children?: never;
}
| {
/**
* Lazily get a React component to render for this screen.
*/
getComponent: () => React.ComponentType<any>;
component?: never;
children?: never;
}
| {
/**
* Render callback to render content of this screen.
*/
children: (props: {
route: RouteProp<ParamList, RouteName>;
} & RouteConfigComponent<ParamList, RouteName>;
export type RouteGroupConfig<
ParamList extends ParamListBase,
ScreenOptions extends {}
> = {
/**
* Navigator options for this screen.
*/
screenOptions?:
| ScreenOptions
| ((props: {
route: RouteProp<ParamList, keyof ParamList>;
navigation: any;
}) => React.ReactNode;
component?: never;
getComponent?: never;
}
);
}) => ScreenOptions);
/**
* Children React Elements to extract the route configuration from.
* Only `Screen`, `Group` and `React.Fragment` are supported as children.
*/
children: React.ReactNode;
};
export type NavigationContainerEventMap = {
/**
@@ -486,7 +518,7 @@ export type NavigationContainerEventMap = {
};
export type NavigationContainerRef<
ParamList extends ParamListBase
ParamList extends {}
> = NavigationHelpers<ParamList> &
EventConsumer<NavigationContainerEventMap> & {
/**
@@ -514,7 +546,7 @@ export type NavigationContainerRef<
};
export type NavigationContainerRefWithCurrent<
ParamList extends ParamListBase
ParamList extends {}
> = NavigationContainerRef<ParamList> & {
current: NavigationContainerRef<ParamList> | null;
};
@@ -536,6 +568,10 @@ export type TypedNavigator<
> &
DefaultNavigatorOptions<ScreenOptions, ParamList>
>;
/**
* Component used for grouping multiple route configuration.
*/
Group: React.ComponentType<RouteGroupConfig<ParamList, ScreenOptions>>;
/**
* Component used for specifying route configuration.
*/
@@ -573,15 +609,20 @@ export type NavigatorScreenParams<
};
}[keyof ParamList];
export type PathConfig = {
export type PathConfig<ParamList extends {}> = {
path?: string;
exact?: boolean;
parse?: Record<string, (value: string) => any>;
stringify?: Record<string, (value: any) => string>;
screens?: PathConfigMap;
initialRouteName?: string;
screens?: PathConfigMap<ParamList>;
initialRouteName?: keyof ParamList;
};
export type PathConfigMap = {
[routeName: string]: string | PathConfig;
export type PathConfigMap<ParamList extends {}> = {
[RouteName in keyof ParamList]?: ParamList[RouteName] extends NavigatorScreenParams<
infer T,
any
>
? string | PathConfig<T>
: string | Omit<PathConfig<{}>, 'screens' | 'initialRouteName'>;
};

View File

@@ -24,6 +24,22 @@ import type {
NavigationProp,
} from './types';
export type ScreenConfigWithParent<
State extends NavigationState,
ScreenOptions extends {},
EventMap extends EventMapBase
> = [
(ScreenOptionsOrCallback<ScreenOptions> | undefined)[] | undefined,
RouteConfig<ParamListBase, string, State, ScreenOptions, EventMap>
];
type ScreenOptionsOrCallback<ScreenOptions extends {}> =
| ScreenOptions
| ((props: {
route: RouteProp<ParamListBase, string>;
navigation: any;
}) => ScreenOptions);
type Options<
State extends NavigationState,
ScreenOptions extends {},
@@ -32,15 +48,10 @@ type Options<
state: State;
screens: Record<
string,
RouteConfig<ParamListBase, string, State, ScreenOptions, EventMap>
ScreenConfigWithParent<State, ScreenOptions, EventMap>
>;
navigation: NavigationHelpers<ParamListBase>;
screenOptions?:
| ScreenOptions
| ((props: {
route: RouteProp<ParamListBase>;
navigation: any;
}) => ScreenOptions);
screenOptions?: ScreenOptionsOrCallback<ScreenOptions>;
defaultScreenOptions?:
| ScreenOptions
| ((props: {
@@ -137,29 +148,31 @@ export default function useDescriptors<
>
>
>((acc, route, i) => {
const screen = screens[route.name];
const config = screens[route.name];
const screen = config[1];
const navigation = navigations[route.key];
const customOptions = {
const optionsList = [
// The default `screenOptions` passed to the navigator
...(typeof screenOptions === 'object' || screenOptions == null
? screenOptions
: // @ts-expect-error: this is a function, but typescript doesn't think so
screenOptions({
route,
navigation,
})),
// The `options` prop passed to `Screen` elements
...(typeof screen.options === 'object' || screen.options == null
? screen.options
: // @ts-expect-error: this is a function, but typescript doesn't think so
screen.options({
route,
navigation,
})),
screenOptions,
// The `screenOptions` props passed to `Group` elements
...((config[0]
? config[0].filter(Boolean)
: []) as ScreenOptionsOrCallback<ScreenOptions>[]),
// The `options` prop passed to `Screen` elements,
screen.options,
// The options set via `navigation.setOptions`
...options[route.key],
};
options[route.key],
];
const customOptions = optionsList.reduce<ScreenOptions>(
(acc, curr) =>
Object.assign(
acc,
typeof curr !== 'function' ? curr : curr({ route, navigation })
),
{} as ScreenOptions
);
const mergedOptions = {
...(typeof defaultScreenOptions === 'function'

View File

@@ -1,5 +1,4 @@
import * as React from 'react';
import type { ParamListBase } from '@react-navigation/routers';
import NavigationContext from './NavigationContext';
import type { NavigationProp } from './types';
@@ -9,7 +8,7 @@ import type { NavigationProp } from './types';
* @returns Navigation prop of the parent screen.
*/
export default function useNavigation<
T extends NavigationProp<ParamListBase>
T = NavigationProp<ReactNavigation.RootParamList>
>(): T {
const navigation = React.useContext(NavigationContext);
@@ -19,5 +18,6 @@ export default function useNavigation<
);
}
return navigation as T;
// FIXME: Figure out a better way to do this
return (navigation as unknown) as T;
}

View File

@@ -14,10 +14,11 @@ import {
} from '@react-navigation/routers';
import NavigationStateContext from './NavigationStateContext';
import NavigationRouteContext from './NavigationRouteContext';
import Group from './Group';
import Screen from './Screen';
import useEventEmitter from './useEventEmitter';
import useRegisterNavigator from './useRegisterNavigator';
import useDescriptors from './useDescriptors';
import useDescriptors, { ScreenConfigWithParent } from './useDescriptors';
import useNavigationHelpers from './useNavigationHelpers';
import useOnAction from './useOnAction';
import useFocusEvents from './useFocusEvents';
@@ -57,33 +58,40 @@ const getRouteConfigsFromChildren = <
ScreenOptions extends {},
EventMap extends EventMapBase
>(
children: React.ReactNode
children: React.ReactNode,
options?: ScreenConfigWithParent<State, ScreenOptions, EventMap>[0]
) => {
const configs = React.Children.toArray(children).reduce<
RouteConfig<ParamListBase, string, State, ScreenOptions, EventMap>[]
ScreenConfigWithParent<State, ScreenOptions, EventMap>[]
>((acc, child) => {
if (React.isValidElement(child)) {
if (child.type === Screen) {
// We can only extract the config from `Screen` elements
// If something else was rendered, it's probably a bug
acc.push(
acc.push([
options,
child.props as RouteConfig<
ParamListBase,
string,
State,
ScreenOptions,
EventMap
>
);
>,
]);
return acc;
}
if (child.type === React.Fragment) {
// When we encounter a fragment, we need to dive into its children to extract the configs
if (child.type === React.Fragment || child.type === Group) {
// When we encounter a fragment or group, we need to dive into its children to extract the configs
// This is handy to conditionally define a group of screens
acc.push(
...getRouteConfigsFromChildren<State, ScreenOptions, EventMap>(
child.props.children
child.props.children,
child.type !== Group
? options
: options != null
? [...options, child.props.screenOptions]
: [child.props.screenOptions]
)
);
return acc;
@@ -91,7 +99,7 @@ const getRouteConfigsFromChildren = <
}
throw new Error(
`A navigator can only contain 'Screen' components as its direct children (found ${
`A navigator can only contain 'Screen', 'Group' or 'React.Fragment' as its direct children (found ${
React.isValidElement(child)
? `'${
typeof child.type === 'string' ? child.type : child.type?.name
@@ -107,7 +115,7 @@ const getRouteConfigsFromChildren = <
if (process.env.NODE_ENV !== 'production') {
configs.forEach((config) => {
const { name, children, component, getComponent } = config;
const { name, children, component, getComponent } = config[1];
if (typeof name !== 'string' || !name) {
throw new Error(
@@ -220,25 +228,22 @@ export default function useNavigationBuilder<
>(children);
const screens = routeConfigs.reduce<
Record<
string,
RouteConfig<ParamListBase, string, State, ScreenOptions, EventMap>
>
Record<string, ScreenConfigWithParent<State, ScreenOptions, EventMap>>
>((acc, config) => {
if (config.name in acc) {
if (config[1].name in acc) {
throw new Error(
`A navigator cannot contain multiple 'Screen' components with the same name (found duplicate screen named '${config.name}')`
`A navigator cannot contain multiple 'Screen' components with the same name (found duplicate screen named '${config[1].name}')`
);
}
acc[config.name] = config;
acc[config[1].name] = config;
return acc;
}, {});
const routeNames = routeConfigs.map((config) => config.name);
const routeNames = routeConfigs.map((config) => config[1].name);
const routeParamList = routeNames.reduce<Record<string, object | undefined>>(
(acc, curr) => {
const { initialParams } = screens[curr];
const { initialParams } = screens[curr][1];
const initialParamsFromParams =
route?.params?.state == null &&
route?.params?.initial !== false &&
@@ -263,7 +268,7 @@ export default function useNavigationBuilder<
>(
(acc, curr) =>
Object.assign(acc, {
[curr]: screens[curr].getId,
[curr]: screens[curr][1].getId,
}),
{}
);
@@ -481,7 +486,7 @@ export default function useNavigationBuilder<
const listeners = ([] as (((e: any) => void) | undefined)[])
.concat(
...routeNames.map((name) => {
const { listeners } = screens[name];
const { listeners } = screens[name][1];
const map =
typeof listeners === 'function'
? listeners({ route: route as any, navigation })

View File

@@ -3,6 +3,30 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [6.0.0-next.6](https://github.com/react-navigation/react-navigation/compare/@react-navigation/devtools@6.0.0-next.5...@react-navigation/devtools@6.0.0-next.6) (2021-05-09)
**Note:** Version bump only for package @react-navigation/devtools
# [6.0.0-next.5](https://github.com/react-navigation/react-navigation/compare/@react-navigation/devtools@6.0.0-next.4...@react-navigation/devtools@6.0.0-next.5) (2021-05-09)
**Note:** Version bump only for package @react-navigation/devtools
# [6.0.0-next.4](https://github.com/react-navigation/react-navigation/compare/@react-navigation/devtools@6.0.0-next.3...@react-navigation/devtools@6.0.0-next.4) (2021-05-09)
**Note:** Version bump only for package @react-navigation/devtools
# [6.0.0-next.3](https://github.com/react-navigation/react-navigation/compare/@react-navigation/devtools@6.0.0-next.2...@react-navigation/devtools@6.0.0-next.3) (2021-05-01)

View File

@@ -1,7 +1,7 @@
{
"name": "@react-navigation/devtools",
"description": "Developer tools for React Navigation",
"version": "6.0.0-next.3",
"version": "6.0.0-next.6",
"keywords": [
"react",
"react-native",
@@ -36,7 +36,7 @@
"clean": "del lib"
},
"dependencies": {
"@react-navigation/core": "^6.0.0-next.3",
"@react-navigation/core": "^6.0.0-next.6",
"deep-equal": "^2.0.5"
},
"devDependencies": {

View File

@@ -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.
# [6.0.0-next.8](https://github.com/react-navigation/react-navigation/compare/@react-navigation/drawer@6.0.0-next.7...@react-navigation/drawer@6.0.0-next.8) (2021-05-09)
**Note:** Version bump only for package @react-navigation/drawer
# [6.0.0-next.7](https://github.com/react-navigation/react-navigation/compare/@react-navigation/drawer@6.0.0-next.6...@react-navigation/drawer@6.0.0-next.7) (2021-05-09)
**Note:** Version bump only for package @react-navigation/drawer
# [6.0.0-next.6](https://github.com/react-navigation/react-navigation/compare/@react-navigation/drawer@6.0.0-next.5...@react-navigation/drawer@6.0.0-next.6) (2021-05-09)
### Bug Fixes
* enable screens only on supported platforms ([#9494](https://github.com/react-navigation/react-navigation/issues/9494)) ([8da4c58](https://github.com/react-navigation/react-navigation/commit/8da4c58065607d44e9dc1ad8943e09537598dcd7))
* make sure disabling react-native-screens works ([a369ba3](https://github.com/react-navigation/react-navigation/commit/a369ba36451ddc2bb5b247e61b725bce1e3fb5e5))
# [6.0.0-next.5](https://github.com/react-navigation/react-navigation/compare/@react-navigation/drawer@6.0.0-next.4...@react-navigation/drawer@6.0.0-next.5) (2021-05-01)
**Note:** Version bump only for package @react-navigation/drawer

View File

@@ -1,7 +1,7 @@
{
"name": "@react-navigation/drawer",
"description": "Drawer navigator component with animated transitions and gesturess",
"version": "6.0.0-next.5",
"version": "6.0.0-next.8",
"keywords": [
"react-native-component",
"react-component",
@@ -41,15 +41,15 @@
"clean": "del lib"
},
"dependencies": {
"@react-navigation/elements": "^1.0.0-next.5",
"@react-navigation/elements": "^1.0.0-next.8",
"color": "^3.1.3",
"warn-once": "^0.0.1"
},
"devDependencies": {
"@react-navigation/native": "^6.0.0-next.3",
"@react-navigation/native": "^6.0.0-next.6",
"@testing-library/react-native": "^7.2.0",
"@types/react": "^16.9.53",
"@types/react-native": "~0.63.51",
"@types/react-native": "~0.64.4",
"del-cli": "^3.0.1",
"react": "~16.13.1",
"react-native": "~0.63.4",

View File

@@ -6,7 +6,6 @@ import {
Platform,
BackHandler,
} from 'react-native';
import { ScreenContainer } from 'react-native-screens';
import { useSafeAreaFrame } from 'react-native-safe-area-context';
import Animated from 'react-native-reanimated';
import {
@@ -22,9 +21,8 @@ import {
SafeAreaProviderCompat,
getHeaderTitle,
} from '@react-navigation/elements';
import { MaybeScreenContainer, MaybeScreen } from './ScreenFallback';
import { GestureHandlerRootView } from './GestureHandler';
import ScreenFallback from './ScreenFallback';
import DrawerToggleButton from './DrawerToggleButton';
import DrawerContent from './DrawerContent';
import DrawerStatusContext from '../utils/DrawerStatusContext';
@@ -76,7 +74,9 @@ function DrawerViewBase({
drawerContent = (props: DrawerContentComponentProps) => (
<DrawerContent {...props} />
),
detachInactiveScreens = true,
detachInactiveScreens = Platform.OS === 'web' ||
Platform.OS === 'android' ||
Platform.OS === 'ios',
// Running in chrome debugger
// @ts-expect-error
useLegacyImplementation = !global.nativeCallSyncHook ||
@@ -189,8 +189,10 @@ function DrawerViewBase({
const renderSceneContent = () => {
return (
// @ts-ignore
<ScreenContainer enabled={detachInactiveScreens} style={styles.content}>
<MaybeScreenContainer
enabled={detachInactiveScreens}
style={styles.content}
>
{state.routes.map((route, index) => {
const descriptor = descriptors[route.key];
const { lazy = true, unmountOnBlur } = descriptor.options;
@@ -221,7 +223,7 @@ function DrawerViewBase({
} = descriptor.options;
return (
<ScreenFallback
<MaybeScreen
key={route.key}
style={[StyleSheet.absoluteFill, { opacity: isFocused ? 1 : 0 }]}
visible={isFocused}
@@ -243,10 +245,10 @@ function DrawerViewBase({
>
{descriptor.render()}
</Screen>
</ScreenFallback>
</MaybeScreen>
);
})}
</ScreenContainer>
</MaybeScreenContainer>
);
};

View File

@@ -1,11 +1,5 @@
import * as React from 'react';
import { Platform, StyleProp, ViewStyle } from 'react-native';
import {
Screen,
screensEnabled,
// @ts-ignore
shouldUseActivityState,
} from 'react-native-screens';
import { StyleProp, View, ViewProps, ViewStyle } from 'react-native';
import { ResourceSavingView } from '@react-navigation/elements';
type Props = {
@@ -15,22 +9,35 @@ type Props = {
style?: StyleProp<ViewStyle>;
};
export default function ScreenFallback({ visible, children, ...rest }: Props) {
// react-native-screens is buggy on web
if (screensEnabled?.() && Platform.OS !== 'web') {
if (shouldUseActivityState) {
return (
<Screen activityState={visible ? 2 : 0} {...rest}>
{children}
</Screen>
);
} else {
return (
<Screen active={visible ? 1 : 0} {...rest}>
{children}
</Screen>
);
}
let Screens: typeof import('react-native-screens') | undefined;
try {
Screens = require('react-native-screens');
} catch (e) {
// Ignore
}
export const MaybeScreenContainer = ({
enabled,
...rest
}: ViewProps & {
enabled: boolean;
children: React.ReactNode;
}) => {
if (Screens?.screensEnabled?.()) {
return <Screens.ScreenContainer enabled={enabled} {...rest} />;
}
return <View {...rest} />;
};
export function MaybeScreen({ visible, children, ...rest }: Props) {
if (Screens?.screensEnabled?.()) {
return (
<Screens.Screen activityState={visible ? 2 : 0} {...rest}>
{children}
</Screens.Screen>
);
}
return (

View File

@@ -3,6 +3,33 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.0.0-next.8](https://github.com/react-navigation/react-navigation/compare/@react-navigation/elements@1.0.0-next.7...@react-navigation/elements@1.0.0-next.8) (2021-05-09)
**Note:** Version bump only for package @react-navigation/elements
# [1.0.0-next.7](https://github.com/react-navigation/react-navigation/compare/@react-navigation/elements@1.0.0-next.6...@react-navigation/elements@1.0.0-next.7) (2021-05-09)
**Note:** Version bump only for package @react-navigation/elements
# [1.0.0-next.6](https://github.com/react-navigation/react-navigation/compare/@react-navigation/elements@1.0.0-next.5...@react-navigation/elements@1.0.0-next.6) (2021-05-09)
### Bug Fixes
* animate pressable opacity ([459fd27](https://github.com/react-navigation/react-navigation/commit/459fd270503075343b71ad446efdc2517eedcf21))
# [1.0.0-next.5](https://github.com/react-navigation/react-navigation/compare/@react-navigation/elements@1.0.0-next.4...@react-navigation/elements@1.0.0-next.5) (2021-05-01)
**Note:** Version bump only for package @react-navigation/elements

View File

@@ -1,7 +1,7 @@
{
"name": "@react-navigation/elements",
"description": "UI Components for React Navigation",
"version": "1.0.0-next.5",
"version": "1.0.0-next.8",
"keywords": [
"react-native",
"react-navigation",
@@ -38,10 +38,10 @@
},
"devDependencies": {
"@react-native-masked-view/masked-view": "^0.2.3",
"@react-navigation/native": "^6.0.0-next.3",
"@react-navigation/native": "^6.0.0-next.6",
"@testing-library/react-native": "^7.2.0",
"@types/react": "^16.9.53",
"@types/react-native": "~0.63.51",
"@types/react-native": "~0.64.4",
"del-cli": "^3.0.1",
"react": "~16.13.1",
"react-native": "~0.63.4",

View File

@@ -1,13 +1,25 @@
import * as React from 'react';
import { Platform, Pressable, PressableProps } from 'react-native';
import {
Animated,
Easing,
GestureResponderEvent,
Platform,
Pressable,
PressableProps,
StyleProp,
ViewStyle,
} from 'react-native';
import { useTheme } from '@react-navigation/native';
export type Props = PressableProps & {
export type Props = Omit<PressableProps, 'style'> & {
pressColor?: string;
pressOpacity?: number;
style?: Animated.WithAnimatedValue<StyleProp<ViewStyle>>;
children: React.ReactNode;
};
const AnimatedPressable = Animated.createAnimatedComponent(Pressable);
const ANDROID_VERSION_LOLLIPOP = 21;
const ANDROID_SUPPORTS_RIPPLE =
Platform.OS === 'android' && Platform.Version >= ANDROID_VERSION_LOLLIPOP;
@@ -16,16 +28,44 @@ const ANDROID_SUPPORTS_RIPPLE =
* PlatformPressable provides an abstraction on top of Pressable to handle platform differences.
*/
export default function PlatformPressable({
onPressIn,
onPressOut,
android_ripple,
pressColor,
pressOpacity,
pressOpacity = 0.3,
style,
...rest
}: Props) {
const { dark } = useTheme();
const [opacity] = React.useState(() => new Animated.Value(1));
const animateTo = (toValue: number, duration: number) => {
if (ANDROID_SUPPORTS_RIPPLE) {
return;
}
Animated.timing(opacity, {
toValue,
duration,
easing: Easing.inOut(Easing.quad),
useNativeDriver: true,
}).start();
};
const handlePressIn = (e: GestureResponderEvent) => {
animateTo(pressOpacity, 150);
onPressIn?.(e);
};
const handlePressOut = (e: GestureResponderEvent) => {
animateTo(1, 200);
onPressOut?.(e);
};
return (
<Pressable
<AnimatedPressable
onPressIn={handlePressIn}
onPressOut={handlePressOut}
android_ripple={
ANDROID_SUPPORTS_RIPPLE
? {
@@ -39,10 +79,7 @@ export default function PlatformPressable({
}
: undefined
}
style={({ pressed }) => [
{ opacity: pressed && !ANDROID_SUPPORTS_RIPPLE ? pressOpacity : 1 },
typeof style === 'function' ? style({ pressed }) : style,
]}
style={[{ opacity: !ANDROID_SUPPORTS_RIPPLE ? opacity : 1 }, style]}
{...rest}
/>
);

View File

@@ -3,6 +3,30 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [6.0.0-next.6](https://github.com/react-navigation/react-navigation/compare/@react-navigation/material-bottom-tabs@6.0.0-next.5...@react-navigation/material-bottom-tabs@6.0.0-next.6) (2021-05-09)
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
# [6.0.0-next.5](https://github.com/react-navigation/react-navigation/compare/@react-navigation/material-bottom-tabs@6.0.0-next.4...@react-navigation/material-bottom-tabs@6.0.0-next.5) (2021-05-09)
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
# [6.0.0-next.4](https://github.com/react-navigation/react-navigation/compare/@react-navigation/material-bottom-tabs@6.0.0-next.3...@react-navigation/material-bottom-tabs@6.0.0-next.4) (2021-05-09)
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
# [6.0.0-next.3](https://github.com/react-navigation/react-navigation/compare/@react-navigation/material-bottom-tabs@6.0.0-next.2...@react-navigation/material-bottom-tabs@6.0.0-next.3) (2021-05-01)
**Note:** Version bump only for package @react-navigation/material-bottom-tabs

View File

@@ -1,7 +1,7 @@
{
"name": "@react-navigation/material-bottom-tabs",
"description": "Integration for bottom navigation component from react-native-paper",
"version": "6.0.0-next.3",
"version": "6.0.0-next.6",
"keywords": [
"react-native-component",
"react-component",
@@ -41,10 +41,10 @@
"clean": "del lib"
},
"devDependencies": {
"@react-navigation/native": "^6.0.0-next.3",
"@react-navigation/native": "^6.0.0-next.6",
"@testing-library/react-native": "^7.2.0",
"@types/react": "^16.9.53",
"@types/react-native": "~0.63.51",
"@types/react-native": "~0.64.4",
"@types/react-native-vector-icons": "^6.4.6",
"del-cli": "^3.0.1",
"react": "~16.13.1",

View File

@@ -3,6 +3,30 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [6.0.0-next.7](https://github.com/react-navigation/react-navigation/compare/@react-navigation/material-top-tabs@6.0.0-next.6...@react-navigation/material-top-tabs@6.0.0-next.7) (2021-05-09)
**Note:** Version bump only for package @react-navigation/material-top-tabs
# [6.0.0-next.6](https://github.com/react-navigation/react-navigation/compare/@react-navigation/material-top-tabs@6.0.0-next.5...@react-navigation/material-top-tabs@6.0.0-next.6) (2021-05-09)
**Note:** Version bump only for package @react-navigation/material-top-tabs
# [6.0.0-next.5](https://github.com/react-navigation/react-navigation/compare/@react-navigation/material-top-tabs@6.0.0-next.4...@react-navigation/material-top-tabs@6.0.0-next.5) (2021-05-09)
**Note:** Version bump only for package @react-navigation/material-top-tabs
# [6.0.0-next.4](https://github.com/react-navigation/react-navigation/compare/@react-navigation/material-top-tabs@6.0.0-next.3...@react-navigation/material-top-tabs@6.0.0-next.4) (2021-05-01)
**Note:** Version bump only for package @react-navigation/material-top-tabs

View File

@@ -1,7 +1,7 @@
{
"name": "@react-navigation/material-top-tabs",
"description": "Integration for the animated tab view component from react-native-tab-view",
"version": "6.0.0-next.4",
"version": "6.0.0-next.7",
"keywords": [
"react-native-component",
"react-component",
@@ -45,10 +45,10 @@
"warn-once": "^0.0.1"
},
"devDependencies": {
"@react-navigation/native": "^6.0.0-next.3",
"@react-navigation/native": "^6.0.0-next.6",
"@testing-library/react-native": "^7.2.0",
"@types/react": "^16.9.53",
"@types/react-native": "~0.63.51",
"@types/react-native": "~0.64.4",
"del-cli": "^3.0.1",
"react": "~16.13.1",
"react-native": "~0.63.4",

View File

@@ -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.
# [6.0.0-next.6](https://github.com/react-navigation/react-navigation/compare/@react-navigation/native@6.0.0-next.5...@react-navigation/native@6.0.0-next.6) (2021-05-09)
**Note:** Version bump only for package @react-navigation/native
# [6.0.0-next.5](https://github.com/react-navigation/react-navigation/compare/@react-navigation/native@6.0.0-next.4...@react-navigation/native@6.0.0-next.5) (2021-05-09)
**Note:** Version bump only for package @react-navigation/native
# [6.0.0-next.4](https://github.com/react-navigation/react-navigation/compare/@react-navigation/native@6.0.0-next.3...@react-navigation/native@6.0.0-next.4) (2021-05-09)
### Features
* add ability to specify root param list ([b28bfdd](https://github.com/react-navigation/react-navigation/commit/b28bfddc17cbf3996fac04a34b2a7085ecf88be5))
* support navigate-like object in Link ([1478659](https://github.com/react-navigation/react-navigation/commit/14786594c004d8176570f1a4ab013b57b3180665))
# [6.0.0-next.3](https://github.com/react-navigation/react-navigation/compare/@react-navigation/native@6.0.0-next.2...@react-navigation/native@6.0.0-next.3) (2021-05-01)

View File

@@ -1,7 +1,7 @@
{
"name": "@react-navigation/native",
"description": "React Native integration for React Navigation",
"version": "6.0.0-next.3",
"version": "6.0.0-next.6",
"keywords": [
"react-native",
"react-navigation",
@@ -37,7 +37,7 @@
"clean": "del lib"
},
"dependencies": {
"@react-navigation/core": "^6.0.0-next.3",
"@react-navigation/core": "^6.0.0-next.6",
"escape-string-regexp": "^4.0.0",
"nanoid": "^3.1.22"
},
@@ -45,7 +45,7 @@
"@testing-library/react-native": "^7.2.0",
"@types/react": "^16.9.53",
"@types/react-dom": "^16.9.8",
"@types/react-native": "~0.63.51",
"@types/react-native": "~0.64.4",
"del-cli": "^3.0.1",
"react": "~16.13.1",
"react-dom": "^16.13.1",

View File

@@ -2,9 +2,10 @@ import * as React from 'react';
import { Text, TextProps, GestureResponderEvent, Platform } from 'react-native';
import type { NavigationAction } from '@react-navigation/core';
import useLinkProps from './useLinkProps';
import type { To } from './useLinkTo';
type Props = {
to: string;
to: To;
action?: NavigationAction;
target?: string;
onPress?: (

View File

@@ -1,8 +1,9 @@
import * as React from 'react';
import type { ParamListBase } from '@react-navigation/core';
import type { LinkingOptions } from './types';
const LinkingContext = React.createContext<{
options: LinkingOptions | undefined;
options: LinkingOptions<ParamListBase> | undefined;
}>({ options: undefined });
LinkingContext.displayName = 'LinkingContext';

View File

@@ -14,9 +14,9 @@ import useDocumentTitle from './useDocumentTitle';
import useBackButton from './useBackButton';
import type { Theme, LinkingOptions, DocumentTitleOptions } from './types';
type Props = NavigationContainerProps & {
type Props<ParamList extends {}> = NavigationContainerProps & {
theme?: Theme;
linking?: LinkingOptions;
linking?: LinkingOptions<ParamList>;
fallback?: React.ReactNode;
documentTitle?: DocumentTitleOptions;
onReady?: () => void;
@@ -36,7 +36,7 @@ type Props = NavigationContainerProps & {
* @param props.children Child elements to render the content.
* @param props.ref Ref object which refers to the navigation object containing helper methods.
*/
const NavigationContainer = React.forwardRef(function NavigationContainer(
function NavigationContainerInner(
{
theme = DefaultTheme,
linking,
@@ -44,7 +44,7 @@ const NavigationContainer = React.forwardRef(function NavigationContainer(
documentTitle,
onReady,
...rest
}: Props,
}: Props<ParamListBase>,
ref?: React.Ref<NavigationContainerRef<ParamListBase> | null>
) {
const isLinkingEnabled = linking ? linking.enabled !== false : false;
@@ -101,6 +101,14 @@ const NavigationContainer = React.forwardRef(function NavigationContainer(
</ThemeProvider>
</LinkingContext.Provider>
);
});
}
const NavigationContainer = React.forwardRef(NavigationContainerInner) as <
RootParamList extends {} = ReactNavigation.RootParamList
>(
props: Props<RootParamList> & {
ref?: React.Ref<NavigationContainerRef<RootParamList>>;
}
) => React.ReactElement;
export default NavigationContainer;

View File

@@ -5,6 +5,7 @@ import {
StackRouter,
TabRouter,
NavigationHelpersContext,
NavigatorScreenParams,
} from '@react-navigation/core';
import { renderToString } from 'react-dom/server';
import NavigationContainer from '../NavigationContainer';
@@ -36,24 +37,37 @@ it('renders correct state with location', () => {
);
});
const Stack = createStackNavigator();
type StackAParamList = {
Home: NavigatorScreenParams<StackBParamList>;
Chat: undefined;
};
type StackBParamList = {
Profile: undefined;
Settings: undefined;
Feed: undefined;
Updates: undefined;
};
const StackA = createStackNavigator<StackAParamList>();
const StackB = createStackNavigator<StackBParamList>();
const TestScreen = ({ route }: any): any =>
`${route.name} ${JSON.stringify(route.params)}`;
const NestedStack = () => {
return (
<Stack.Navigator initialRouteName="Feed">
<Stack.Screen name="Profile" component={TestScreen} />
<Stack.Screen name="Settings" component={TestScreen} />
<Stack.Screen name="Feed" component={TestScreen} />
<Stack.Screen name="Updates" component={TestScreen} />
</Stack.Navigator>
<StackB.Navigator initialRouteName="Feed">
<StackB.Screen name="Profile" component={TestScreen} />
<StackB.Screen name="Settings" component={TestScreen} />
<StackB.Screen name="Feed" component={TestScreen} />
<StackB.Screen name="Updates" component={TestScreen} />
</StackB.Navigator>
);
};
const element = (
<NavigationContainer
<NavigationContainer<StackAParamList>
linking={{
prefixes: [],
config: {
@@ -73,10 +87,10 @@ it('renders correct state with location', () => {
},
}}
>
<Stack.Navigator>
<Stack.Screen name="Home" component={NestedStack} />
<Stack.Screen name="Chat" component={TestScreen} />
</Stack.Navigator>
<StackA.Navigator>
<StackA.Screen name="Home" component={NestedStack} />
<StackA.Screen name="Chat" component={TestScreen} />
</StackA.Navigator>
</NavigationContainer>
);

View File

@@ -17,7 +17,7 @@ export type Theme = {
};
};
export type LinkingOptions = {
export type LinkingOptions<ParamList extends {}> = {
/**
* Whether deep link handling should be enabled.
* Defaults to true.
@@ -53,7 +53,10 @@ export type LinkingOptions = {
* }
* ```
*/
config?: { initialRouteName?: string; screens: PathConfigMap };
config?: {
initialRouteName?: keyof ParamList;
screens: PathConfigMap<ParamList>;
};
/**
* Custom function to get the initial URL used for linking.
* Uses `Linking.getInitialURL()` by default.

View File

@@ -4,10 +4,10 @@ import {
NavigationAction,
NavigationHelpersContext,
} from '@react-navigation/core';
import useLinkTo from './useLinkTo';
import useLinkTo, { To } from './useLinkTo';
type Props = {
to: string;
to: To;
action?: NavigationAction;
};
@@ -49,14 +49,6 @@ export default function useLinkProps({ to, action }: Props) {
throw new Error("Couldn't find a navigation object.");
}
} else {
if (typeof to !== 'string') {
throw new Error(
`To 'to' option is invalid (found '${String(
to
)}'. It must be a valid string for navigation.`
);
}
linkTo(to);
}
}

View File

@@ -6,37 +6,57 @@ import {
} from '@react-navigation/core';
import LinkingContext from './LinkingContext';
export type To<
ParamList extends ReactNavigation.RootParamList = ReactNavigation.RootParamList,
RouteName extends keyof ParamList = keyof ParamList
> =
| string
| (undefined extends ParamList[RouteName]
? {
screen: RouteName;
params?: ParamList[RouteName];
}
: {
screen: RouteName;
params: ParamList[RouteName];
});
export default function useLinkTo() {
const navigation = React.useContext(NavigationContext);
const linking = React.useContext(LinkingContext);
const linkTo = React.useCallback(
(path: string) => {
if (!path.startsWith('/')) {
throw new Error(`The path must start with '/' (${path}).`);
}
(to: To) => {
if (navigation === undefined) {
throw new Error(
"Couldn't find a navigation object. Is your component inside a screen in a navigator?"
);
}
let root = navigation;
let current;
// Traverse up to get the root navigation
while ((current = root.getParent())) {
root = current;
}
if (typeof to !== 'string') {
root.navigate(to.screen, to.params);
return;
}
if (!to.startsWith('/')) {
throw new Error(`The path must start with '/' (${to}).`);
}
const { options } = linking;
const state = options?.getStateFromPath
? options.getStateFromPath(path, options.config)
: getStateFromPath(path, options?.config);
? options.getStateFromPath(to, options.config)
: getStateFromPath(to, options?.config);
if (state) {
let root = navigation;
let current;
// Traverse up to get the root navigation
while ((current = root.getParent())) {
root = current;
}
const action = getActionFromState(state, options?.config);
if (action !== undefined) {

View File

@@ -45,7 +45,7 @@ export default function useLinking(
};
},
getStateFromPath = getStateFromPathDefault,
}: LinkingOptions
}: LinkingOptions<ParamListBase>
) {
React.useEffect(() => {
if (enabled !== false && isUsingLinking) {

View File

@@ -295,7 +295,7 @@ export default function useLinking(
config,
getStateFromPath = getStateFromPathDefault,
getPathFromState = getPathFromStateDefault,
}: LinkingOptions
}: LinkingOptions<ParamListBase>
) {
React.useEffect(() => {
if (enabled !== false && isUsingLinking) {

View File

@@ -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.
# [6.0.0-next.13](https://github.com/react-navigation/react-navigation/compare/@react-navigation/stack@6.0.0-next.12...@react-navigation/stack@6.0.0-next.13) (2021-05-09)
**Note:** Version bump only for package @react-navigation/stack
# [6.0.0-next.12](https://github.com/react-navigation/react-navigation/compare/@react-navigation/stack@6.0.0-next.11...@react-navigation/stack@6.0.0-next.12) (2021-05-09)
**Note:** Version bump only for package @react-navigation/stack
# [6.0.0-next.11](https://github.com/react-navigation/react-navigation/compare/@react-navigation/stack@6.0.0-next.10...@react-navigation/stack@6.0.0-next.11) (2021-05-09)
### Bug Fixes
* enable screens only on supported platforms ([#9494](https://github.com/react-navigation/react-navigation/issues/9494)) ([8da4c58](https://github.com/react-navigation/react-navigation/commit/8da4c58065607d44e9dc1ad8943e09537598dcd7))
* make sure disabling react-native-screens works ([a369ba3](https://github.com/react-navigation/react-navigation/commit/a369ba36451ddc2bb5b247e61b725bce1e3fb5e5))
### Code Refactoring
* drop mode prop in favor of animationPresentation option ([9ac709e](https://github.com/react-navigation/react-navigation/commit/9ac709ea1e5a63c3a48abfa334ff6a6925095a72))
### Features
* automatically set headerMode if it's modal presentation style ([4bb0b43](https://github.com/react-navigation/react-navigation/commit/4bb0b43f1a0f27c96843415de6eaa37edebfb561))
* support mixing regular and modal presentation in same stack ([60fa3b9](https://github.com/react-navigation/react-navigation/commit/60fa3b9be976a73a5b04b632b4b37672674c956b))
### BREAKING CHANGES
* This drops the mode prop on the navigator in favor of a per-screen option animationPresentation
# [6.0.0-next.10](https://github.com/react-navigation/react-navigation/compare/@react-navigation/stack@6.0.0-next.9...@react-navigation/stack@6.0.0-next.10) (2021-05-01)
**Note:** Version bump only for package @react-navigation/stack

View File

@@ -1,7 +1,7 @@
{
"name": "@react-navigation/stack",
"description": "Stack navigator component for iOS and Android with animated transitions and gestures",
"version": "6.0.0-next.10",
"version": "6.0.0-next.13",
"keywords": [
"react-native-component",
"react-component",
@@ -40,17 +40,17 @@
"clean": "del lib"
},
"dependencies": {
"@react-navigation/elements": "^1.0.0-next.5",
"@react-navigation/elements": "^1.0.0-next.8",
"color": "^3.1.3",
"react-native-iphone-x-helper": "^1.3.0",
"warn-once": "^0.0.1"
},
"devDependencies": {
"@react-navigation/native": "^6.0.0-next.3",
"@react-navigation/native": "^6.0.0-next.6",
"@testing-library/react-native": "^7.2.0",
"@types/color": "^3.0.1",
"@types/react": "^16.9.53",
"@types/react-native": "~0.63.51",
"@types/react-native": "~0.64.4",
"del-cli": "^3.0.1",
"react": "~16.13.1",
"react-native": "~0.63.4",

View File

@@ -118,13 +118,15 @@ export function forModalPresentationIOS({
: 0
);
const isFirst = index === 0;
const translateY = multiply(
progress.interpolate({
inputRange: [0, 1, 2],
outputRange: [
screen.height,
index === 0 ? 0 : topOffset,
(index === 0 ? statusBarHeight : 0) - topOffset * aspectRatio,
isFirst ? 0 : topOffset,
(isFirst ? statusBarHeight : 0) - topOffset * aspectRatio,
],
}),
inverted
@@ -148,7 +150,7 @@ export function forModalPresentationIOS({
const borderRadius = isLandscape
? 0
: index === 0
: isFirst
? progress.interpolate({
inputRange: [0, 1, 1.0001, 2],
outputRange: [0, 0, isIphoneX() ? 38 : 0, 10],
@@ -164,8 +166,8 @@ export function forModalPresentationIOS({
// But different border radius for corners improves animation perf
borderBottomLeftRadius: isIphoneX() ? borderRadius : 0,
borderBottomRightRadius: isIphoneX() ? borderRadius : 0,
marginTop: index === 0 ? 0 : statusBarHeight,
marginBottom: index === 0 ? 0 : topOffset,
marginTop: isFirst ? 0 : statusBarHeight,
marginBottom: isFirst ? 0 : topOffset,
transform: [{ translateY }, { scale }],
},
overlayStyle: { opacity: overlayOpacity },

View File

@@ -1,5 +1,4 @@
import * as React from 'react';
import { Platform } from 'react-native';
import {
useNavigationBuilder,
createNavigatorFactory,
@@ -54,21 +53,9 @@ function StackNavigator({
initialRouteName,
children,
screenOptions,
defaultScreenOptions: ({ options }) => ({
defaultScreenOptions: () => ({
headerShown: headerMode ? headerMode !== 'none' : true,
headerMode:
headerMode && headerMode !== 'none'
? headerMode
: rest.mode !== 'modal' &&
Platform.OS === 'ios' &&
options.header === undefined
? 'float'
: 'screen',
gestureEnabled: Platform.OS === 'ios',
animationEnabled:
Platform.OS !== 'web' &&
Platform.OS !== 'windows' &&
Platform.OS !== 'macos',
headerMode: headerMode && headerMode !== 'none' ? headerMode : undefined,
}),
});

View File

@@ -72,11 +72,21 @@ export type GestureDirection =
| 'vertical'
| 'vertical-inverted';
type SceneOptionsDefaults = TransitionPreset & {
animationEnabled: boolean;
gestureEnabled: boolean;
cardOverlayEnabled: boolean;
headerMode: StackHeaderMode;
};
export type Scene = {
/**
* Descriptor object for the screen.
*/
descriptor: StackDescriptor;
descriptor: Omit<StackDescriptor, 'options'> & {
options: Omit<StackDescriptor['options'], keyof SceneOptionsDefaults> &
SceneOptionsDefaults;
};
/**
* Animated nodes representing the progress of the animation.
*/
@@ -102,7 +112,7 @@ export type SceneProgress = {
export type StackHeaderMode = 'float' | 'screen';
export type StackCardMode = 'card' | 'modal';
export type StackPresentationMode = 'card' | 'modal';
export type StackHeaderOptions = HeaderOptions & {
/**
@@ -228,8 +238,12 @@ export type StackNavigationOptions = StackHeaderOptions &
*
* You can also specify `{ backgroundColor: 'transparent' }` to make the previous screen visible underneath.
* This is useful to implement things like modal dialogs.
* If you use [`react-native-screens`](https://github.com/kmagiera/react-native-screens), you should also specify `mode: 'modal'`
* in the stack view config when using a transparent background so previous screens aren't detached.
*
* You should also specify `detachPreviousScreen: false` in options when using a transparent background
* so that the previous screen isn't detached and stays below the current screen.
*
* You might also need to change the animation of the screen using `cardStyleInterpolator`
* so that the previous screen isn't transformed or invisible.
*/
cardStyle?: StyleProp<ViewStyle>;
/**
@@ -238,6 +252,16 @@ export type StackNavigationOptions = StackHeaderOptions &
* Defaults to `true` on Android and iOS, `false` on Web.
*/
animationEnabled?: boolean;
/**
* Whether this screen should be presented as a modal or a regular card.
*
* If you haven't customized the animations separately, the animation will change based on the value:
* - 'modal' - modal animation on iOS and Android. It'll also default `headerMode` to `screen`.
* - 'card' - horizontal slide animation on iOS, OS-default animation on Android.
*
* Defaults to 'card'.
*/
animationPresentation?: 'card' | 'modal';
/**
* The type of animation to use when this screen replaces another screen. Defaults to `push`.
* When `pop` is used, the `pop` animation is applied to the screen being replaced.
@@ -262,13 +286,12 @@ export type StackNavigationOptions = StackHeaderOptions &
* 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`.
* Defaults to `false` for the last screen for modals, otherwise `true`.
*/
detachPreviousScreen?: boolean;
};
export type StackNavigationConfig = {
mode?: StackCardMode;
/**
* If `false`, the keyboard will NOT automatically dismiss when navigating to a new screen.
* Defaults to `true`.
@@ -319,7 +342,7 @@ export type StackCardInterpolationProps = {
progress: Animated.AnimatedInterpolation;
};
/**
* The index of the card in the stack.
* The index of the card with this interpolation in the stack.
*/
index: number;
/**

View File

@@ -18,13 +18,13 @@ import {
import type {
Layout,
Scene,
StackHeaderStyleInterpolator,
StackNavigationProp,
StackHeaderProps,
StackHeaderMode,
} from '../../types';
export type Props = {
mode: 'float' | 'screen';
mode: StackHeaderMode;
layout: Layout;
scenes: (Scene | undefined)[];
getPreviousScene: (props: { route: Route<string> }) => Scene | undefined;
@@ -33,7 +33,6 @@ export type Props = {
route: Route<string>;
height: number;
}) => void;
styleInterpolator: StackHeaderStyleInterpolator;
style?: Animated.WithAnimatedValue<StyleProp<ViewStyle>>;
};
@@ -44,7 +43,6 @@ export default function HeaderContainer({
getPreviousScene,
getFocusedRoute,
onContentHeightChange,
styleInterpolator,
style,
}: Props) {
const focusedRoute = getFocusedRoute();
@@ -57,8 +55,13 @@ export default function HeaderContainer({
return null;
}
const { header, headerMode, headerShown = true, headerTransparent } =
scene.descriptor.options || {};
const {
header,
headerMode,
headerShown = true,
headerTransparent,
headerStyleInterpolator,
} = scene.descriptor.options;
if (headerMode !== mode || !headerShown) {
return null;
@@ -120,7 +123,7 @@ export default function HeaderContainer({
: nextGestureDirection === 'horizontal-inverted'
? forSlideRight
: forSlideLeft
: styleInterpolator
: headerStyleInterpolator
: forNoAnimation,
};

View File

@@ -1,5 +1,5 @@
import * as React from 'react';
import { Animated, View, Platform, ViewProps } from 'react-native';
import { Animated, View, ViewProps } from 'react-native';
let Screens: typeof import('react-native-screens') | undefined;
@@ -9,31 +9,6 @@ try {
// Ignore
}
export const shouldUseActivityState = Screens?.shouldUseActivityState;
// So we use our custom implementation to handle a11y better
class WebScreen extends React.Component<
ViewProps & {
active: number;
children: React.ReactNode;
}
> {
render() {
const { active, style, ...rest } = this.props;
return (
<View
// @ts-expect-error: hidden exists on web, but not in React Native
hidden={!active}
style={[style, { display: active ? 'flex' : 'none' }]}
{...rest}
/>
);
}
}
const AnimatedWebScreen = Animated.createAnimatedComponent(WebScreen);
export const MaybeScreenContainer = ({
enabled,
...rest
@@ -41,11 +16,8 @@ export const MaybeScreenContainer = ({
enabled: boolean;
children: React.ReactNode;
}) => {
if (enabled && Platform.OS !== 'web' && Screens?.screensEnabled()) {
return (
// @ts-ignore
<Screens.ScreenContainer enabled={enabled} {...rest} />
);
if (Screens != null) {
return <Screens.ScreenContainer enabled={enabled} {...rest} />;
}
return <View {...rest} />;
@@ -60,18 +32,10 @@ export const MaybeScreen = ({
active: 0 | 1 | Animated.AnimatedInterpolation;
children: React.ReactNode;
}) => {
if (enabled && Platform.OS === 'web') {
return <AnimatedWebScreen active={active} {...rest} />;
}
if (enabled && Screens?.screensEnabled()) {
if (shouldUseActivityState) {
return (
<Screens.Screen enabled={enabled} activityState={active} {...rest} />
);
} else {
return <Screens.Screen enabled={enabled} active={active} {...rest} />;
}
if (Screens != null) {
return (
<Screens.Screen enabled={enabled} activityState={active} {...rest} />
);
}
return <View {...rest} />;

View File

@@ -29,10 +29,12 @@ import type {
StackCardStyleInterpolator,
GestureDirection,
Layout,
StackCardInterpolationProps,
} from '../../types';
type Props = ViewProps & {
index: number;
// index: number;
interpolationIndex: number;
closing: boolean;
next?: Animated.AnimatedInterpolation;
current: Animated.AnimatedInterpolation;
@@ -344,38 +346,14 @@ export default class Card extends React.Component<Props> {
private getInterpolatedStyle = memoize(
(
styleInterpolator: StackCardStyleInterpolator,
index: number,
current: Animated.AnimatedInterpolation,
next: Animated.AnimatedInterpolation | undefined,
layout: Layout,
insetTop: number,
insetRight: number,
insetBottom: number,
insetLeft: number
) =>
styleInterpolator({
index,
current: { progress: current },
next: next && { progress: next },
closing: this.isClosing,
swiping: this.isSwiping,
inverted: this.inverted,
layouts: {
screen: layout,
},
insets: {
top: insetTop,
right: insetRight,
bottom: insetBottom,
left: insetLeft,
},
})
animation: StackCardInterpolationProps
) => styleInterpolator(animation)
);
// Keep track of the animation context when deps changes.
private getCardAnimationContext = memoize(
private getCardAnimation = memoize(
(
index: number,
interpolationIndex: number,
current: Animated.AnimatedInterpolation,
next: Animated.AnimatedInterpolation | undefined,
layout: Layout,
@@ -384,7 +362,7 @@ export default class Card extends React.Component<Props> {
insetBottom: number,
insetLeft: number
) => ({
index,
index: interpolationIndex,
current: { progress: current },
next: next && { progress: next },
closing: this.isClosing,
@@ -450,7 +428,8 @@ export default class Card extends React.Component<Props> {
render() {
const {
styleInterpolator,
index,
// index,
interpolationIndex,
current,
gesture,
next,
@@ -469,9 +448,8 @@ export default class Card extends React.Component<Props> {
...rest
} = this.props;
const interpolatedStyle = this.getInterpolatedStyle(
styleInterpolator,
index,
const interpolationProps = this.getCardAnimation(
interpolationIndex,
current,
next,
layout,
@@ -481,15 +459,9 @@ export default class Card extends React.Component<Props> {
insets.left
);
const animationContext = this.getCardAnimationContext(
index,
current,
next,
layout,
insets.top,
insets.right,
insets.bottom,
insets.left
const interpolatedStyle = this.getInterpolatedStyle(
styleInterpolator,
interpolationProps
);
const {
@@ -521,13 +493,13 @@ export default class Card extends React.Component<Props> {
: false;
return (
<CardAnimationContext.Provider value={animationContext}>
<CardAnimationContext.Provider value={interpolationProps}>
{
// StatusBar messes with translucent status bar on Android
// So we should only enable it on iOS
Platform.OS === 'ios' &&
overlayEnabled &&
index === 0 &&
interpolationIndex === 0 &&
next &&
styleInterpolator === forModalPresentationIOS ? (
<ModalStatusBarManager

View File

@@ -1,5 +1,5 @@
import * as React from 'react';
import { Animated, View, StyleSheet, StyleProp, ViewStyle } from 'react-native';
import { Animated, View, StyleSheet } from 'react-native';
import { Route, useTheme } from '@react-navigation/native';
import {
HeaderShownContext,
@@ -11,16 +11,10 @@ import type { Props as HeaderContainerProps } from '../Header/HeaderContainer';
import Card from './Card';
import { forModalPresentationIOS } from '../../TransitionConfigs/CardStyleInterpolators';
import ModalPresentationContext from '../../utils/ModalPresentationContext';
import type {
Layout,
StackHeaderMode,
StackCardMode,
TransitionPreset,
Scene,
} from '../../types';
import type { Layout, Scene } from '../../types';
type Props = TransitionPreset & {
index: number;
type Props = {
interpolationIndex: number;
active: boolean;
focused: boolean;
closing: boolean;
@@ -32,12 +26,6 @@ type Props = TransitionPreset & {
safeAreaInsetRight: number;
safeAreaInsetBottom: number;
safeAreaInsetLeft: number;
cardOverlay?: (props: {
style: Animated.WithAnimatedValue<StyleProp<ViewStyle>>;
}) => React.ReactNode;
cardOverlayEnabled: boolean;
cardShadowEnabled?: boolean;
cardStyle?: StyleProp<ViewStyle>;
getPreviousScene: (props: { route: Route<string> }) => Scene | undefined;
getFocusedRoute: () => Route<string>;
renderHeader: (props: HeaderContainerProps) => React.ReactNode;
@@ -55,12 +43,6 @@ type Props = TransitionPreset & {
onGestureStart?: (props: { route: Route<string> }) => void;
onGestureEnd?: (props: { route: Route<string> }) => void;
onGestureCancel?: (props: { route: Route<string> }) => void;
gestureEnabled?: boolean;
gestureResponseDistance?: number;
gestureVelocityImpact?: number;
mode: StackCardMode;
headerMode: StackHeaderMode;
headerShown: boolean;
hasAbsoluteFloatHeader: boolean;
headerHeight: number;
onHeaderHeightChange: (props: {
@@ -74,30 +56,17 @@ const EPSILON = 0.1;
function CardContainer({
active,
cardOverlay,
cardOverlayEnabled,
cardShadowEnabled,
cardStyle,
cardStyleInterpolator,
closing,
gesture,
focused,
gestureDirection,
gestureEnabled,
gestureResponseDistance,
gestureVelocityImpact,
getPreviousScene,
getFocusedRoute,
mode,
headerDarkContent,
headerMode,
headerShown,
headerStyleInterpolator,
hasAbsoluteFloatHeader,
headerHeight,
onHeaderHeightChange,
isParentHeaderShown,
index,
interpolationIndex,
layout,
onCloseRoute,
onOpenRoute,
@@ -116,7 +85,6 @@ function CardContainer({
safeAreaInsetRight,
safeAreaInsetTop,
scene,
transitionSpec,
}: Props) {
const parentHeaderHeight = React.useContext(HeaderHeightContext);
@@ -188,7 +156,6 @@ function CardContainer({
);
React.useEffect(() => {
// @ts-expect-error: AnimatedInterpolation optionally has addListener, but the type defs don't think so
const listener = scene.progress.next?.addListener?.(
({ value }: { value: number }) => {
setPointerEvents(value <= EPSILON ? 'box-none' : 'none');
@@ -197,12 +164,27 @@ function CardContainer({
return () => {
if (listener) {
// @ts-expect-error: AnimatedInterpolation optionally has removedListener, but the type defs don't think so
scene.progress.next?.removeListener?.(listener);
}
};
}, [pointerEvents, scene.progress.next]);
const {
animationPresentation,
cardOverlay,
cardOverlayEnabled,
cardShadowEnabled,
cardStyle,
cardStyleInterpolator,
gestureDirection,
gestureEnabled,
gestureResponseDistance,
gestureVelocityImpact,
headerMode,
headerShown,
transitionSpec,
} = scene.descriptor.options;
const isModalPresentation = cardStyleInterpolator === forModalPresentationIOS;
const previousScene = getPreviousScene({ route: scene.descriptor.route });
@@ -221,7 +203,7 @@ function CardContainer({
return (
<Card
index={index}
interpolationIndex={interpolationIndex}
gestureDirection={gestureDirection}
layout={layout}
insets={insets}
@@ -246,7 +228,9 @@ function CardContainer({
accessibilityElementsHidden={!focused}
importantForAccessibility={focused ? 'auto' : 'no-hide-descendants'}
pointerEvents={active ? 'box-none' : pointerEvents}
pageOverflowEnabled={headerMode !== 'float' && mode === 'card'}
pageOverflowEnabled={
headerMode !== 'float' && animationPresentation !== 'modal'
}
headerDarkContent={headerDarkContent}
containerStyle={
hasAbsoluteFloatHeader && headerMode !== 'screen'
@@ -278,14 +262,15 @@ function CardContainer({
</HeaderBackContext.Provider>
</View>
{headerMode !== 'float' ? (
<ModalPresentationContext.Provider value={isModalPresentation}>
<ModalPresentationContext.Provider
value={isModalPresentation && interpolationIndex !== 0}
>
{renderHeader({
mode: 'screen',
layout,
scenes: [previousScene, scene],
getPreviousScene,
getFocusedRoute,
styleInterpolator: headerStyleInterpolator,
onContentHeightChange: onHeaderHeightChange,
})}
</ModalPresentationContext.Provider>

View File

@@ -18,26 +18,25 @@ import {
Background,
} from '@react-navigation/elements';
import {
MaybeScreenContainer,
MaybeScreen,
shouldUseActivityState,
} from '../Screens';
import { MaybeScreenContainer, MaybeScreen } from '../Screens';
import type { Props as HeaderContainerProps } from '../Header/HeaderContainer';
import CardContainer from './CardContainer';
import {
DefaultTransition,
ModalTransition,
} from '../../TransitionConfigs/TransitionPresets';
import { forNoAnimation as forNoAnimationCard } from '../../TransitionConfigs/CardStyleInterpolators';
import {
forModalPresentationIOS,
forNoAnimation as forNoAnimationCard,
} from '../../TransitionConfigs/CardStyleInterpolators';
import getDistanceForDirection from '../../utils/getDistanceForDirection';
import type {
Layout,
StackCardMode,
StackDescriptorMap,
StackNavigationOptions,
StackDescriptor,
Scene,
StackDescriptor,
StackDescriptorMap,
StackHeaderMode,
StackNavigationOptions,
} from '../../types';
type GestureValues = {
@@ -45,7 +44,6 @@ type GestureValues = {
};
type Props = {
mode: StackCardMode;
insets: EdgeInsets;
state: StackNavigationState<ParamListBase>;
descriptors: StackDescriptorMap;
@@ -57,7 +55,6 @@ type Props = {
getPreviousRoute: (props: {
route: Route<string>;
}) => Route<string> | undefined;
getGesturesEnabled: (props: { route: Route<string> }) => boolean;
renderHeader: (props: HeaderContainerProps) => React.ReactNode;
renderScene: (props: { route: Route<string> }) => React.ReactNode;
isParentHeaderShown: boolean;
@@ -121,27 +118,25 @@ const getHeaderHeights = (
};
const getDistanceFromOptions = (
mode: StackCardMode,
layout: Layout,
descriptor?: StackDescriptor
) => {
const {
gestureDirection = mode === 'modal'
animationPresentation,
gestureDirection = animationPresentation === 'modal'
? ModalTransition.gestureDirection
: DefaultTransition.gestureDirection,
} = descriptor?.options || {};
} = (descriptor?.options || {}) as StackNavigationOptions;
return getDistanceForDirection(layout, gestureDirection);
};
const getProgressFromGesture = (
mode: StackCardMode,
gesture: Animated.Value,
layout: Layout,
descriptor?: StackDescriptor
) => {
const distance = getDistanceFromOptions(
mode,
{
// Make sure that we have a non-zero distance, otherwise there will be incorrect progress
// This causes blank screen on web if it was previously inside container with display: none
@@ -165,7 +160,10 @@ const getProgressFromGesture = (
};
export default class CardStack extends React.Component<Props, State> {
static getDerivedStateFromProps(props: Props, state: State) {
static getDerivedStateFromProps(
props: Props,
state: State
): Partial<State> | null {
if (
props.routes === state.routes &&
props.descriptors === state.descriptors
@@ -182,7 +180,7 @@ export default class CardStack extends React.Component<Props, State> {
new Animated.Value(
props.openingRouteKeys.includes(curr.key) &&
animationEnabled !== false
? getDistanceFromOptions(props.mode, state.layout, descriptor)
? getDistanceFromOptions(state.layout, descriptor)
: 0
);
@@ -216,19 +214,97 @@ export default class CardStack extends React.Component<Props, State> {
props.descriptors[previousRoute?.key] ||
state.descriptors[previousRoute?.key];
const { options } = descriptor;
let defaultTransitionPreset =
options.animationPresentation === 'modal'
? ModalTransition
: DefaultTransition;
const {
animationEnabled = Platform.OS !== 'web' &&
Platform.OS !== 'windows' &&
Platform.OS !== 'macos',
gestureEnabled = Platform.OS === 'ios' &&
animationEnabled &&
index !== 0,
gestureDirection = defaultTransitionPreset.gestureDirection,
transitionSpec = defaultTransitionPreset.transitionSpec,
cardStyleInterpolator = animationEnabled === false
? forNoAnimationCard
: defaultTransitionPreset.cardStyleInterpolator,
headerStyleInterpolator = defaultTransitionPreset.headerStyleInterpolator,
cardOverlayEnabled = Platform.OS !== 'ios' ||
cardStyleInterpolator === forModalPresentationIOS,
} = options;
let transitionConfig = {
gestureDirection,
transitionSpec,
cardStyleInterpolator,
headerStyleInterpolator,
cardOverlayEnabled,
};
// 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) {
if (nextDescriptor) {
const {
animationEnabled,
gestureDirection = defaultTransitionPreset.gestureDirection,
transitionSpec = defaultTransitionPreset.transitionSpec,
cardStyleInterpolator = animationEnabled === false
? forNoAnimationCard
: defaultTransitionPreset.cardStyleInterpolator,
headerStyleInterpolator = defaultTransitionPreset.headerStyleInterpolator,
cardOverlayEnabled = descriptor.options.cardOverlayEnabled ??
cardStyleInterpolator === forModalPresentationIOS,
} = nextDescriptor.options;
transitionConfig = {
gestureDirection,
transitionSpec,
cardStyleInterpolator,
headerStyleInterpolator,
cardOverlayEnabled,
};
}
}
const headerMode: StackHeaderMode =
options.headerMode ??
(options.animationPresentation !== 'modal' &&
transitionConfig.cardStyleInterpolator !== forModalPresentationIOS &&
Platform.OS === 'ios' &&
options.header === undefined
? 'float'
: 'screen');
const scene = {
route,
descriptor,
descriptor: {
...descriptor,
options: {
...options,
...transitionConfig,
animationEnabled,
gestureEnabled,
headerMode,
},
},
progress: {
current: getProgressFromGesture(
props.mode,
currentGesture,
state.layout,
descriptor
),
next: nextGesture
? getProgressFromGesture(
props.mode,
nextGesture,
state.layout,
nextDescriptor
@@ -236,7 +312,6 @@ export default class CardStack extends React.Component<Props, State> {
: undefined,
previous: previousGesture
? getProgressFromGesture(
props.mode,
previousGesture,
state.layout,
previousDescriptor
@@ -369,15 +444,12 @@ export default class CardStack extends React.Component<Props, State> {
render() {
const {
mode,
insets,
descriptors,
state,
routes,
closingRouteKeys,
onOpenRoute,
onCloseRoute,
getGesturesEnabled,
renderHeader,
renderScene,
isParentHeaderShown,
@@ -389,47 +461,19 @@ export default class CardStack extends React.Component<Props, State> {
onGestureStart,
onGestureEnd,
onGestureCancel,
// Enable on new versions of `react-native-screens`
// On older versions of `react-native-screens`, there's an issue with screens not being responsive to user interaction.
detachInactiveScreens = Platform.OS === 'web'
? true
: shouldUseActivityState ?? false,
detachInactiveScreens = Platform.OS === 'web' ||
Platform.OS === 'android' ||
Platform.OS === 'ios',
} = this.props;
const { scenes, layout, gestures, headerHeights } = this.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;
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 = this.state.scenes.slice(-2).some((scene) => {
const options = scene.descriptor.options ?? {};
const {
headerMode = 'screen',
headerTransparent,
headerShown = true,
} = options;
const { headerMode, headerTransparent, headerShown = true } = options;
if (
headerTransparent ||
@@ -442,6 +486,25 @@ export default class CardStack extends React.Component<Props, State> {
return false;
});
let activeScreensLimit = 1;
for (let i = scenes.length - 1; i >= 0; i--) {
const { options } = scenes[i].descriptor;
const {
// By default, we don't want to detach the previous screen of the active one for modals
detachPreviousScreen = options.animationPresentation === 'modal' ||
options.cardStyleInterpolator === forModalPresentationIOS
? i !== scenes.length - 1
: true,
} = options;
if (detachPreviousScreen === false) {
activeScreensLimit++;
} else {
break;
}
}
const floatingHeader = (
<React.Fragment key="header">
{renderHeader({
@@ -451,10 +514,6 @@ export default class CardStack extends React.Component<Props, State> {
getPreviousScene: this.getPreviousScene,
getFocusedRoute: this.getFocusedRoute,
onContentHeightChange: this.handleHeaderLayout,
styleInterpolator:
focusedOptions.headerStyleInterpolator !== undefined
? focusedOptions.headerStyleInterpolator
: defaultTransitionPreset.headerStyleInterpolator,
style: [
styles.floating,
isFloatHeaderAbsolute && [
@@ -486,96 +545,33 @@ export default class CardStack extends React.Component<Props, State> {
// For the old implementation, it stays the same it was
let isScreenActive: Animated.AnimatedInterpolation | 2 | 1 | 0 = 1;
if (shouldUseActivityState || Platform.OS === 'web') {
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;
}
if (index < self.length - activeScreensLimit - 1) {
// screen should be inactive because it is too deep in the stack
isScreenActive = STATE_INACTIVE;
} else {
isScreenActive = scene.progress.next
? scene.progress.next.interpolate({
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, 0],
outputRange: [1, 1, outputValue],
extrapolate: 'clamp',
})
: 1;
: STATE_TRANSITIONING_OR_BELOW_TOP;
}
const {
cardStyleInterpolator,
headerShown = true,
headerMode = 'screen',
headerTransparent,
headerStyle,
headerTintColor,
cardShadowEnabled,
cardOverlayEnabled = Platform.OS !== 'ios' || mode === 'modal',
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,
};
}
}
} = scene.descriptor.options;
const safeAreaInsetTop = insets.top;
const safeAreaInsetRight = insets.right;
@@ -598,6 +594,20 @@ export default class CardStack extends React.Component<Props, State> {
}
}
// Start from current card and count backwards the number of cards with same interpolation
let interpolationIndex = 0;
for (let i = index - 1; i >= 0; i--) {
const cardStyleInterpolatorCurrent =
scenes[i]?.descriptor.options.cardStyleInterpolator;
if (cardStyleInterpolatorCurrent !== cardStyleInterpolator) {
break;
}
interpolationIndex++;
}
return (
<MaybeScreen
key={route.key}
@@ -607,7 +617,7 @@ export default class CardStack extends React.Component<Props, State> {
pointerEvents="box-none"
>
<CardContainer
index={index}
interpolationIndex={interpolationIndex}
active={index === self.length - 1}
focused={focused}
closing={closingRouteKeys.includes(route.key)}
@@ -618,25 +628,17 @@ export default class CardStack extends React.Component<Props, State> {
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}
headerDarkContent={headerDarkContent}
hasAbsoluteFloatHeader={
isFloatHeaderAbsolute && !headerTransparent
@@ -647,9 +649,6 @@ export default class CardStack extends React.Component<Props, State> {
onCloseRoute={onCloseRoute}
onTransitionStart={onTransitionStart}
onTransitionEnd={onTransitionEnd}
gestureEnabled={index !== 0 && getGesturesEnabled({ route })}
gestureVelocityImpact={gestureVelocityImpact}
{...transitionConfig}
/>
</MaybeScreen>
);

View File

@@ -288,24 +288,6 @@ export default class StackView extends React.Component<Props, State> {
descriptors: {},
};
private getGesturesEnabled = ({ route }: { route: Route<string> }) => {
const descriptor = this.state.descriptors[route.key];
if (descriptor) {
const { gestureEnabled, animationEnabled } = descriptor.options;
if (animationEnabled === false) {
// When animation is disabled, also disable gestures
// The gesture to dismiss a route will look weird when not animated
return false;
}
return gestureEnabled !== false;
}
return false;
};
private getPreviousRoute = ({ route }: { route: Route<string> }) => {
const { closingRouteKeys, replacingRouteKeys } = this.state;
const routes = this.state.routes.filter(
@@ -438,7 +420,6 @@ export default class StackView extends React.Component<Props, State> {
state,
navigation,
keyboardHandlingEnabled,
mode = 'card',
// eslint-disable-next-line @typescript-eslint/no-unused-vars
descriptors: _,
...rest
@@ -462,11 +443,9 @@ export default class StackView extends React.Component<Props, State> {
<HeaderShownContext.Consumer>
{(isParentHeaderShown) => (
<CardStack
mode={mode}
insets={insets as EdgeInsets}
isParentHeaderShown={isParentHeaderShown}
getPreviousRoute={this.getPreviousRoute}
getGesturesEnabled={this.getGesturesEnabled}
routes={routes}
openingRouteKeys={openingRouteKeys}
closingRouteKeys={closingRouteKeys}

418
yarn.lock
View File

@@ -1397,12 +1397,32 @@
xcode "^3.0.1"
xml2js "^0.4.23"
"@expo/config-plugins@1.0.29":
version "1.0.29"
resolved "https://registry.yarnpkg.com/@expo/config-plugins/-/config-plugins-1.0.29.tgz#fc36a7a90a08e1a67ed2c19241f4309b08e88a25"
integrity sha512-dKtb4dTRiyWmCr/HyMFqt1tHIFpuuvIPnmvakOrg012sY7be81W9WOXjsr6WP7uLT2BfSLFhj1A9EAUzpl6sJw==
dependencies:
"@expo/config-types" "^40.0.0-beta.2"
"@expo/configure-splash-screen" "0.3.4"
"@expo/image-utils" "0.3.13"
"@expo/json-file" "8.2.29"
"@expo/plist" "0.0.12"
find-up "~5.0.0"
fs-extra "9.0.0"
getenv "^1.0.0"
glob "7.1.6"
resolve-from "^5.0.0"
slash "^3.0.0"
slugify "^1.3.4"
xcode "^3.0.1"
xml2js "^0.4.23"
"@expo/config-types@^40.0.0-beta.2":
version "40.0.0-beta.2"
resolved "https://registry.yarnpkg.com/@expo/config-types/-/config-types-40.0.0-beta.2.tgz#4fea4ef5654d02218b02b0b3772529a9ce5b0471"
integrity sha512-t9pHCQMXOP4nwd7LGXuHkLlFy0JdfknRSCAeVF4Kw2/y+5OBbR9hW9ZVnetpBf0kORrekgiI7K/qDaa3hh5+Qg==
"@expo/config@3.3.34", "@expo/config@^3.3.18":
"@expo/config@3.3.34":
version "3.3.34"
resolved "https://registry.yarnpkg.com/@expo/config/-/config-3.3.34.tgz#ab8cab4c04a406cc92ed483f7a7f071e8e0f3e98"
integrity sha512-Yekmn9sIm70vGUwugXlL/jpTQufTJXV7IrYWvFKd4B8ZwdMBFK08NY2XBwvl+jJOVdhmLe+yHc44bCmrEPb6vA==
@@ -1422,6 +1442,26 @@
semver "7.3.2"
slugify "^1.3.4"
"@expo/config@3.3.39", "@expo/config@^3.3.35":
version "3.3.39"
resolved "https://registry.yarnpkg.com/@expo/config/-/config-3.3.39.tgz#c5b02544aa6f1c4efb208f34fdee062d42e092e8"
integrity sha512-xT+OeMSeKrX+OtOs15Plvr8YIsfUfyT+ofpSxzMuAHQ4ivbhR5bIW7XcJTwg4ie3GUTatraoDUYz84TLoirKaA==
dependencies:
"@babel/core" "7.9.0"
"@babel/plugin-proposal-class-properties" "~7.12.13"
"@babel/preset-env" "~7.12.13"
"@babel/preset-typescript" "~7.12.13"
"@expo/config-plugins" "1.0.29"
"@expo/config-types" "^40.0.0-beta.2"
"@expo/json-file" "8.2.29"
fs-extra "9.0.0"
getenv "^1.0.0"
glob "7.1.6"
require-from-string "^2.0.2"
resolve-from "^5.0.0"
semver "7.3.2"
slugify "^1.3.4"
"@expo/configure-splash-screen@0.3.4":
version "0.3.4"
resolved "https://registry.yarnpkg.com/@expo/configure-splash-screen/-/configure-splash-screen-0.3.4.tgz#b91d8f08fd96272bd3d7aaa9b51d6189b932c7cc"
@@ -1438,24 +1478,24 @@
xcode "^3.0.0"
xml-js "^1.6.11"
"@expo/dev-server@0.1.60":
version "0.1.60"
resolved "https://registry.yarnpkg.com/@expo/dev-server/-/dev-server-0.1.60.tgz#eb88f5c52e2617611a41325089a3fb516e3d813d"
integrity sha512-tm+l8enWZ//2nst6s91V+29KwXlA69bFhG6b50lTXfY++7Q0OaOsDoGl3cQaWHKo2K3MQ/7C8riIGf7q6T5wjg==
"@expo/dev-server@0.1.65":
version "0.1.65"
resolved "https://registry.yarnpkg.com/@expo/dev-server/-/dev-server-0.1.65.tgz#e20ff69786c9f0a39156a920a2ffdc328686a193"
integrity sha512-3N2omGlKQrvcU2T/LmtWf+YoQc6yGwFHlikdy1Zh+Z1DBI0rnXPb4YSie3a7k9iBhZlXbCWp2AmzxRRUiQKoMg==
dependencies:
"@expo/bunyan" "4.0.0"
"@expo/metro-config" "0.1.60"
"@expo/metro-config" "0.1.65"
"@react-native-community/cli-server-api" "4.9.0"
body-parser "1.19.0"
resolve-from "^5.0.0"
serialize-error "6.0.0"
"@expo/dev-tools@0.13.89":
version "0.13.89"
resolved "https://registry.yarnpkg.com/@expo/dev-tools/-/dev-tools-0.13.89.tgz#743d8d496a94abb6f55c4c01e4dc1fbf3f91aa25"
integrity sha512-8qlcd46x5CJU2VR+IL3MoHY0eScLe9Io2ibzvuFK+7kNP3gQle/LU+biAKtWIfUVlp4+xzEaAiDc202RTtHhqA==
"@expo/dev-tools@0.13.95":
version "0.13.95"
resolved "https://registry.yarnpkg.com/@expo/dev-tools/-/dev-tools-0.13.95.tgz#62f3655cf4fc12c59ed969399a79bc6417521290"
integrity sha512-g75GOKTfhaZsPSQw7uHHhObwmkZHb9DPtg5/ptDfwzt7FEkvU10gLvepin6dfwOnzTZ1M/HMKMExxa68FCav7Q==
dependencies:
"@expo/config" "3.3.34"
"@expo/config" "3.3.39"
base64url "3.0.1"
express "4.16.4"
freeport-async "2.0.0"
@@ -1501,6 +1541,23 @@
semver "7.3.2"
tempy "0.3.0"
"@expo/image-utils@0.3.13":
version "0.3.13"
resolved "https://registry.yarnpkg.com/@expo/image-utils/-/image-utils-0.3.13.tgz#cba070a61ce89e6c113b8e6afd5655cb83da6377"
integrity sha512-BpKoFVJBjG9H5AU040Skrm3R2uDGpWXBU/4TivB5H10cyJphoJKp3GNJVPHYLOVc70OtAxjWDIhLMW6xsWfrgw==
dependencies:
"@expo/spawn-async" "1.5.0"
chalk "^4.0.0"
fs-extra "9.0.0"
getenv "^1.0.0"
jimp "0.12.1"
mime "^2.4.4"
node-fetch "^2.6.0"
parse-png "^2.1.0"
resolve-from "^5.0.0"
semver "7.3.2"
tempy "0.3.0"
"@expo/json-file@8.2.28":
version "8.2.28"
resolved "https://registry.yarnpkg.com/@expo/json-file/-/json-file-8.2.28.tgz#9218bb70ad12d8be03c6376990b01fbeb4a15b72"
@@ -1511,7 +1568,27 @@
json5 "^1.0.1"
write-file-atomic "^2.3.0"
"@expo/metro-config@0.1.60", "@expo/metro-config@^0.1.59":
"@expo/json-file@8.2.29":
version "8.2.29"
resolved "https://registry.yarnpkg.com/@expo/json-file/-/json-file-8.2.29.tgz#5e66c4c1dc531a583fe654d99fe417246d91741d"
integrity sha512-9C8XwpJiJN9fyClnnNDSTh034zJU9hon6MCUqbBa4dZYQN7OZ00KFZEpbtjy+FndE7YD+KagDxRD6O1HS5vU0g==
dependencies:
"@babel/code-frame" "~7.10.4"
fs-extra "9.0.0"
json5 "^1.0.1"
write-file-atomic "^2.3.0"
"@expo/metro-config@0.1.65", "@expo/metro-config@^0.1.63":
version "0.1.65"
resolved "https://registry.yarnpkg.com/@expo/metro-config/-/metro-config-0.1.65.tgz#95bd4f265def2edca6d229e3860d933425045d83"
integrity sha512-iREEn0RXueAUAuiu0Z4ECxed2UhEkI0KZD2avRggEmCBdDS6uo4TgfFo/2pmMLfXv56nS0wRmtB6o3SqUcgh3g==
dependencies:
"@expo/config" "3.3.39"
chalk "^4.1.0"
getenv "^1.0.0"
metro-react-native-babel-transformer "^0.59.0"
"@expo/metro-config@^0.1.59":
version "0.1.60"
resolved "https://registry.yarnpkg.com/@expo/metro-config/-/metro-config-0.1.60.tgz#43b2bf4d5e00a02a700f756f7ae6bbd9e755e124"
integrity sha512-aA/UMZ5ga1QfF9pN1rYvwTg7ez/Ptz6vSFWyOdYM00X+egKlQFDwe8UuMKiUZUHaLFJ3XbixMLTIvDVdnVxk7w==
@@ -1521,20 +1598,20 @@
getenv "^1.0.0"
metro-react-native-babel-transformer "^0.59.0"
"@expo/osascript@2.0.25":
version "2.0.25"
resolved "https://registry.yarnpkg.com/@expo/osascript/-/osascript-2.0.25.tgz#bd88b08ba4c32c09a349c66745ec60d34f0f55f9"
integrity sha512-rB+RLHCp72q0OBWmisoBswfTpyzc91OJMs3UQVWJP9mXVNJhemONt7PKjE+FinBm33uH1HCC6U7JPGigpVsJBg==
"@expo/osascript@2.0.27":
version "2.0.27"
resolved "https://registry.yarnpkg.com/@expo/osascript/-/osascript-2.0.27.tgz#738fa409e4bff1288c0cf29bb6606fbf130ff0b1"
integrity sha512-q2fh4EuegZkr48v8lvZSQdCRhHbtoBJ3CUpmipVjAXJ+jMJGwwCcZvGpE57N5ODxCTKi5RP0NiEfFAfEnpGnQw==
dependencies:
"@expo/spawn-async" "^1.5.0"
exec-async "^2.2.0"
"@expo/package-manager@0.0.39":
version "0.0.39"
resolved "https://registry.yarnpkg.com/@expo/package-manager/-/package-manager-0.0.39.tgz#6cf7458af198184e5c4dbcf73546f581bc328ae4"
integrity sha512-2KSx1jjdDmWsnkZfWZL4TwveeIyAcORQna5VOZdd+sBalK8taMtPQEdchwAGD4mOFsFFPmpbQHwboRfXoSlbOQ==
"@expo/package-manager@0.0.42":
version "0.0.42"
resolved "https://registry.yarnpkg.com/@expo/package-manager/-/package-manager-0.0.42.tgz#6b33854ee72c3936010c6527b6aafc731d8f12c0"
integrity sha512-8237/Gpz1NvcWv+Ja2ytMaVxgVPiiiai8wjvyGSvD0GWm3vUm66qi9lAN9m3EtfL0DvcwLIz0GYzleM8mOSxXw==
dependencies:
"@expo/json-file" "8.2.28"
"@expo/json-file" "8.2.29"
"@expo/spawn-async" "^1.5.0"
ansi-regex "^5.0.0"
chalk "^4.0.0"
@@ -1558,10 +1635,10 @@
resolved "https://registry.yarnpkg.com/@expo/results/-/results-1.0.0.tgz#fd4b22f936ceafce23b04799f54b87fe2a9e18d1"
integrity sha512-qECzzXX5oJot3m2Gu9pfRDz50USdBieQVwYAzeAtQRUTD3PVeTK1tlRUoDcrK8PSruDLuVYdKkLebX4w/o55VA==
"@expo/schemer@1.3.27":
version "1.3.27"
resolved "https://registry.yarnpkg.com/@expo/schemer/-/schemer-1.3.27.tgz#55d7e7a28bfbd4eca26f59306e305cc5fc8b33e0"
integrity sha512-cuCvSo6qErgK7OOM8CoCtCsVifq+WX1wUCeu+fmSyhnZcqnz45ZTK20Ghk5bT3OrTNQipbTiCjn9RCDrkEMMkg==
"@expo/schemer@1.3.28":
version "1.3.28"
resolved "https://registry.yarnpkg.com/@expo/schemer/-/schemer-1.3.28.tgz#3ad544f3c1b61bea9dda0ec5d195002b70d8a3ca"
integrity sha512-9wmnhlD1X1ro8FTFzM/J3nSxaFpI9X+bcaimP3hKkc3flIR8cGjQcLmE+MOEgE2LET0ScxRBtM3hteernFI6Ww==
dependencies:
ajv "^5.2.2"
json-schema-traverse "0.3.1"
@@ -1593,7 +1670,47 @@
lodash.pick "^4.4.0"
lodash.template "^4.5.0"
"@expo/webpack-config@0.12.64", "@expo/webpack-config@~0.12.63":
"@expo/webpack-config@0.12.69":
version "0.12.69"
resolved "https://registry.yarnpkg.com/@expo/webpack-config/-/webpack-config-0.12.69.tgz#e71da0c2a2db5fce0d940311a558d01cd01cd034"
integrity sha512-S/gJvzcsd0M6nHsjsVftQgazFwpmcOfAh2ryOTd6QTn5OL9tFsNXfWfP7gad+a+hJLKz43hEDAlJKljNgy9KDw==
dependencies:
"@babel/core" "7.9.0"
"@babel/runtime" "7.9.0"
"@expo/config" "3.3.39"
"@pmmmwh/react-refresh-webpack-plugin" "^0.3.3"
babel-loader "8.1.0"
chalk "^4.0.0"
clean-webpack-plugin "^3.0.0"
copy-webpack-plugin "~6.0.3"
css-loader "~3.6.0"
expo-pwa "0.0.75"
file-loader "~6.0.0"
find-yarn-workspace-root "~2.0.0"
getenv "^1.0.0"
html-loader "~1.1.0"
html-webpack-plugin "~4.3.0"
is-wsl "^2.0.0"
mini-css-extract-plugin "^0.5.0"
node-html-parser "^1.2.12"
optimize-css-assets-webpack-plugin "^5.0.3"
pnp-webpack-plugin "^1.5.0"
postcss-safe-parser "^4.0.2"
progress "^2.0.3"
react-dev-utils "~11.0.1"
react-refresh "^0.8.2"
semver "~7.3.2"
style-loader "~1.2.1"
terser-webpack-plugin "^3.0.6"
url-loader "~4.1.0"
webpack "4.43.0"
webpack-deep-scope-plugin "1.6.0"
webpack-manifest-plugin "~2.2.0"
webpackbar "^4.0.0"
workbox-webpack-plugin "^3.6.3"
worker-loader "^2.0.0"
"@expo/webpack-config@~0.12.63":
version "0.12.64"
resolved "https://registry.yarnpkg.com/@expo/webpack-config/-/webpack-config-0.12.64.tgz#0b44fa37416e765bb9ffe65399d0a40c0d3387f0"
integrity sha512-STR9ctDYG36JUIjgcGClvsi3ZU55lc2jtIxf04+0TN8IVYOCoYTF3P971zA1dd3En/q//CpgtKX/UAoimdlL9A==
@@ -1633,10 +1750,10 @@
workbox-webpack-plugin "^3.6.3"
worker-loader "^2.0.0"
"@expo/xcpretty@^1.0.4":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@expo/xcpretty/-/xcpretty-1.1.1.tgz#2b5b1679f256449e13f25cebfc0dcf008b687b57"
integrity sha512-YospUT3y7tPheNCW0SgzbDM8KzipuWukW/qxJRX1MnlgVb1wnnsvtBrY0z/ndRCBMlZFVUu+FkNfI0qDKo8YiA==
"@expo/xcpretty@~2.0.1":
version "2.0.1"
resolved "https://registry.yarnpkg.com/@expo/xcpretty/-/xcpretty-2.0.1.tgz#2c912166ca50a88f710cfe3b6b441144f5df14a2"
integrity sha512-fyQbzvZpLiKpx68QDzeLlL1AtFhhEW35dqxIqb4QQ6e3iofu57NdWBQTmIAQzDOPcNNXUR9SFncu3M4iyWwQ7Q==
dependencies:
"@babel/code-frame" "7.10.4"
chalk "^4.1.0"
@@ -3256,11 +3373,16 @@
sudo-prompt "^9.0.0"
wcwidth "^1.0.1"
"@react-native-masked-view/masked-view@0.2.3", "@react-native-masked-view/masked-view@^0.2.3":
"@react-native-masked-view/masked-view@^0.2.3":
version "0.2.3"
resolved "https://registry.yarnpkg.com/@react-native-masked-view/masked-view/-/masked-view-0.2.3.tgz#5f7a39be4787c89d5b35ac80de2ea5d0a0bb64d7"
integrity sha512-t8VcdaFbyXscNs26h/NCsPqVjawWHhIMzGBR9oCIx6kbDa7JW3Q67lNLGkzDudSVnw3Qv09JZrkLXXOGjOABpg==
"@react-native-masked-view/masked-view@~0.2.4":
version "0.2.4"
resolved "https://registry.yarnpkg.com/@react-native-masked-view/masked-view/-/masked-view-0.2.4.tgz#305a548abd47dee494a38f90016432d3a6e4164c"
integrity sha512-2Y9OXWHRutYmdyW+bNMaFTW8uTBpaaK20xdIFoVtqahEOO9++B+ut3CAWKPZcdRtb9ikG0LUKChGqMeocg0PGA==
"@segment/loosely-validate-event@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@segment/loosely-validate-event/-/loosely-validate-event-2.0.0.tgz#87dfc979e5b4e7b82c5f1d8b722dfd5d77644681"
@@ -3677,13 +3799,20 @@
dependencies:
"@types/react" "*"
"@types/react-native@~0.63.2", "@types/react-native@~0.63.51":
"@types/react-native@~0.63.2":
version "0.63.52"
resolved "https://registry.yarnpkg.com/@types/react-native/-/react-native-0.63.52.tgz#449beb4a413ec0f2c172cbf676a95f5b0952adf4"
integrity sha512-sBXvvtJaIUSXQLDh9NZitx1KHkKUdBLZy34lFKJaIXtpHIh5OEbBXeyUTFBtFwjk/RD0tneAtUqsdhheZRzAzw==
dependencies:
"@types/react" "*"
"@types/react-native@~0.64.4":
version "0.64.4"
resolved "https://registry.yarnpkg.com/@types/react-native/-/react-native-0.64.4.tgz#9f11bef7dd5520801884829c73b19d75aa42e73c"
integrity sha512-VqnlmadGkD5usREvnuyVpWDS1W8f6cCz6MP5fZdgONsaZ9/Ijfb9Iq9MZ5O3bnW1OyJixDX9HtSp3COsFSLD8Q==
dependencies:
"@types/react" "*"
"@types/react@*":
version "17.0.3"
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.3.tgz#ba6e215368501ac3826951eef2904574c262cc79"
@@ -3755,11 +3884,6 @@
resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.7.tgz#545158342f949e8fd3bfd813224971ecddc3fac4"
integrity sha512-0VBprVqfgFD7Ehb2vd8Lh9TG3jP98gvr8rgehQqzztZNI7o8zS8Ad4jyZneKELphpuE212D8J70LnSNQSyO6bQ==
"@types/text-table@^0.2.1":
version "0.2.1"
resolved "https://registry.yarnpkg.com/@types/text-table/-/text-table-0.2.1.tgz#39c4d4a058a82f677392dfd09976e83d9b4c9264"
integrity sha512-dchbFCWfVgUSWEvhOkXGS7zjm+K7jCUvGrQkAHPk2Fmslfofp4HQTH2pqnQ3Pw5GPYv0zWa2AQjKtsfZThuemQ==
"@types/uglify-js@*":
version "3.13.0"
resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.13.0.tgz#1cad8df1fb0b143c5aba08de5712ea9d1ff71124"
@@ -3896,13 +4020,12 @@
dependencies:
compare-versions "^3.4.0"
"@unimodules/react-native-adapter@~6.2.1":
version "6.2.1"
resolved "https://registry.yarnpkg.com/@unimodules/react-native-adapter/-/react-native-adapter-6.2.1.tgz#e944bca491aee79bdef68b5d70f9534b79e7a785"
integrity sha512-dVg0FnaH04VvLr6hRPQJuFoiTQhR1RgShjtm1w/vndAGK4CJ1oerLSBL+p887UFmNTk/NSMLkcbdS1ZIRH06Rg==
"@unimodules/react-native-adapter@~6.2.2":
version "6.2.2"
resolved "https://registry.yarnpkg.com/@unimodules/react-native-adapter/-/react-native-adapter-6.2.2.tgz#0b76af76589ec5ff2c52f274e40ed609e91c5e4c"
integrity sha512-hBXL+IX3u+4TcAHu9lIItdycA7pYWZn3Tt7s5TTna9QKHjyrwo0zVss27LkpJ40tXRHyh/GJ8VzN2CD+0M5I2A==
dependencies:
invariant "^2.2.4"
lodash "^4.5.0"
"@webassemblyjs/ast@1.9.0":
version "1.9.0"
@@ -5508,14 +5631,6 @@ callsites@^3.0.0:
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
camel-case@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-3.0.0.tgz#ca3c3688a4e9cf3a4cda777dc4dcbc713249cf73"
integrity sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=
dependencies:
no-case "^2.2.0"
upper-case "^1.1.1"
camel-case@^4.1.1:
version "4.1.2"
resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-4.1.2.tgz#9728072a954f805228225a6deea6b38461e1bd5a"
@@ -7996,10 +8111,10 @@ expect@^26.6.2:
jest-message-util "^26.6.2"
jest-regex-util "^26.0.0"
expo-application@~3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/expo-application/-/expo-application-3.1.0.tgz#7c04b31a202ac24d60a00982276cbe8b2fb87e34"
integrity sha512-s/lLjkuBf9TnfSANPgOJe+T2xB+Nztl+vyjyBhoeLgxo4E7YQRHqKFofwHnQJ308suOAB5vt0MI87EgFgnSR/Q==
expo-application@~3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/expo-application/-/expo-application-3.1.2.tgz#7c93e8108a7f2a0be0afe37151f36c98e0111e7c"
integrity sha512-JZcKUpGmqzQ1zLxRxUditGzPqnPCXY6JT/Pbq4nyV4VPzjDd8wihVPuud+cuv8gHgdj8QLvKs/lcJJqN94EX5Q==
expo-asset@~8.3.1:
version "8.3.1"
@@ -8017,33 +8132,33 @@ expo-blur@~9.0.3:
resolved "https://registry.yarnpkg.com/expo-blur/-/expo-blur-9.0.3.tgz#5095b5bd04d000f73b351d0e4736767b86b49bbd"
integrity sha512-7jMqzo53eg5t9Uc1Tg+yGu9lKL9the4CWAQO0kC6YMhrsDiB13mwOUesC+IScHDTSUKNMGcHI8kN+OmvEWfPig==
expo-cli@^4.3.4:
version "4.3.4"
resolved "https://registry.yarnpkg.com/expo-cli/-/expo-cli-4.3.4.tgz#54af9c9f6cb91096a73583f189a2f01fb3c74127"
integrity sha512-feD7OAnzf7PKZgi4VpEpkuI8TYdplPaQVILoS+6ejMLvjjgDOTPh9Tn47xdox35Q3S1VPWS6qivNvOXjhUNDDQ==
expo-cli@^4.4.4:
version "4.4.4"
resolved "https://registry.yarnpkg.com/expo-cli/-/expo-cli-4.4.4.tgz#6ddc3ebba6c7cf82dd620df328dc8f3fba8f73d6"
integrity sha512-bQN+XxZYoJnSY3fCNSBAZ1ChZEhHOmv5KhwNmPa/Tc2/IPRFCzdopNGf+Uf5FrpjDex/UqewjVrAkb4Ak4/1Hg==
dependencies:
"@expo/apple-utils" "0.0.0-alpha.17"
"@expo/bunyan" "4.0.0"
"@expo/config" "3.3.34"
"@expo/config-plugins" "1.0.24"
"@expo/dev-tools" "0.13.89"
"@expo/json-file" "8.2.28"
"@expo/osascript" "2.0.25"
"@expo/package-manager" "0.0.39"
"@expo/config" "3.3.39"
"@expo/config-plugins" "1.0.29"
"@expo/dev-tools" "0.13.95"
"@expo/json-file" "8.2.29"
"@expo/osascript" "2.0.27"
"@expo/package-manager" "0.0.42"
"@expo/plist" "0.0.12"
"@expo/results" "^1.0.0"
"@expo/simple-spinner" "1.0.2"
"@expo/spawn-async" "1.5.0"
"@expo/xcpretty" "^1.0.4"
"@expo/xcpretty" "~2.0.1"
"@hapi/joi" "^17.1.1"
babel-runtime "6.26.0"
base32.js "0.1.0"
boxen "4.1.0"
bplist-parser "0.2.0"
chalk "^4.0.0"
cli-table3 "^0.6.0"
command-exists "^1.2.8"
commander "2.17.1"
concat-stream "1.6.2"
dateformat "3.0.3"
env-editor "^0.4.1"
envinfo "7.5.0"
@@ -8072,6 +8187,7 @@ expo-cli@^4.3.4:
react-dev-utils "~11.0.1"
read-last-lines "1.6.0"
semver "7.3.2"
slugify "^1.3.4"
strip-ansi "^6.0.0"
tar "^6.0.5"
tempy "^0.7.1"
@@ -8081,15 +8197,14 @@ expo-cli@^4.3.4:
url-join "4.0.0"
uuid "^8.0.0"
wrap-ansi "^7.0.0"
xcode "^3.0.1"
xdl "59.0.29"
xdl "59.0.35"
expo-constants@~10.1.1:
version "10.1.1"
resolved "https://registry.yarnpkg.com/expo-constants/-/expo-constants-10.1.1.tgz#1cdf8c23c63bc3923da199d608037ae845361dd9"
integrity sha512-sj8NgtJ97hh8HempI1z9Eo2nYFPV2PdjmmflvZLUBKbxqU4k+9+FiE6aTKNb23LsdbadZu2D/hzXT0pDub/GdQ==
expo-constants@~10.1.3:
version "10.1.3"
resolved "https://registry.yarnpkg.com/expo-constants/-/expo-constants-10.1.3.tgz#dfbe30362d27d6f500318eb528621424034b72d5"
integrity sha512-Eq/xeshnhSoe4ok89d5lrHvI9jq3bMe1FhJUbiHVGcGmW8mGCotwbQBIfDkkMrAKnSOwQq/Qfyg0XBxnG2XFjw==
dependencies:
"@expo/config" "^3.3.18"
"@expo/config" "^3.3.35"
uuid "^3.3.2"
expo-error-recovery@~2.1.0:
@@ -8097,10 +8212,10 @@ expo-error-recovery@~2.1.0:
resolved "https://registry.yarnpkg.com/expo-error-recovery/-/expo-error-recovery-2.1.0.tgz#7baf6cabc53162cdd2b36edb20b8aa6d1cdc1107"
integrity sha512-N5g2QKtdNntUNGQVnB/tG1jHdtJP1+kLMWDS+7ZKRcKfulm3JX/M3l460fsEtqg84n/latxPkBT0yfKw2DSq+Q==
expo-file-system@~11.0.0:
version "11.0.0"
resolved "https://registry.yarnpkg.com/expo-file-system/-/expo-file-system-11.0.0.tgz#3a6d2f8a9ad4033fae2cabe1a2133372a90a4f20"
integrity sha512-SkJ3HwPLYoLFUR4w7KMvyhxRdCZcDsW/jeoUffGSh52wHs3+1OTKDMta4nQImeiWGZvXsOJ1KyJ+WubRxAgtMg==
expo-file-system@~11.0.2:
version "11.0.2"
resolved "https://registry.yarnpkg.com/expo-file-system/-/expo-file-system-11.0.2.tgz#c3f9b9c6ba25456a0d32c7a9bb38e55310d471bd"
integrity sha512-nodNvUVa+US4N4xnj5BFw8W9ZF/qCHJVC2t45cHWrBiwkVVxz45wjE7uSHUmkMWyWT7a/7AJuL3XJfYp7h90IQ==
dependencies:
"@expo/config-plugins" "^1.0.18"
uuid "^3.4.0"
@@ -8112,17 +8227,17 @@ expo-font@~9.1.0:
dependencies:
fontfaceobserver "^2.1.0"
expo-keep-awake@~9.1.1:
version "9.1.1"
resolved "https://registry.yarnpkg.com/expo-keep-awake/-/expo-keep-awake-9.1.1.tgz#d56d520ae62bc22b9178afacaafea73a1adc89cf"
integrity sha512-2NLUN/be9gjsvk0SF0ArA5omzUUIiVc/cMLfHdb/YnGGzBmNNGYOg9x/4iMzzASuKblttzT3iaf0uqgBqCxM6Q==
expo-keep-awake@~9.1.2:
version "9.1.2"
resolved "https://registry.yarnpkg.com/expo-keep-awake/-/expo-keep-awake-9.1.2.tgz#b3e52c7bef0ade975ae88637a2bf980f6b573368"
integrity sha512-CCuEOQUNLYtMA0rt0sQ9u5LlIMH7lDJG7dImoorfKMsP95yHXy8dl3oCdtaz2zbsPgggVYeom9gE+gQu+Ki4rQ==
expo-linking@~2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/expo-linking/-/expo-linking-2.2.1.tgz#9365ea80be10016ac8b88ec49784c413b921beac"
integrity sha512-/6kjAJId/v4b4qgkH+VT1e2GuBK5KonUO++FdxaK6UC4X0mdZIsWZfFCTqEARWKISedaLMT5v1+DQQRkiVj6cw==
expo-linking@~2.2.3:
version "2.2.3"
resolved "https://registry.yarnpkg.com/expo-linking/-/expo-linking-2.2.3.tgz#925584a0ed7a617065fe92a39ba4509f70972664"
integrity sha512-1NqL8sY4EzwRPVnBxbvNKBCfsG/tNZm72tVwzjoLxhO0XA6VTtZcb72Jaku4IlqEyH0lejxV2YAU0rbLv1pAVA==
dependencies:
expo-constants "~10.1.1"
expo-constants "~10.1.3"
invariant "^2.2.4"
qs "^6.5.0"
url-parse "^1.4.4"
@@ -8138,15 +8253,26 @@ expo-pwa@0.0.70:
commander "2.20.0"
update-check "1.5.3"
expo-pwa@0.0.75:
version "0.0.75"
resolved "https://registry.yarnpkg.com/expo-pwa/-/expo-pwa-0.0.75.tgz#9511d9226aea2715007ab898ab28b74555b3ee28"
integrity sha512-EULfm5yg+xxluhj/qYZokUuFZKoarX8DTHFXNzW8+TlsL38n89PBR7okT5na7YpFEdAaOPTBNQEZZfN5O5tPHQ==
dependencies:
"@expo/config" "3.3.39"
"@expo/image-utils" "0.3.13"
chalk "^4.0.0"
commander "2.20.0"
update-check "1.5.3"
expo-structured-headers@~1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/expo-structured-headers/-/expo-structured-headers-1.0.1.tgz#2f3117fc9480e01aa6705493d3213060c2dcf683"
integrity sha512-U4frE8rT5x1LqZv5Ru/9CtzY8gh0wp1MfwTu5hetPydnwxXfWchuXvKgWRjEHglFU2X/29kYFY/J1UkPvCN8xg==
expo-updates@~0.5.3:
version "0.5.3"
resolved "https://registry.yarnpkg.com/expo-updates/-/expo-updates-0.5.3.tgz#b8806260f84fb0bd4e2b276f797dc5fab536824d"
integrity sha512-BmTvD6qtSEArg4GUpFbGOfRddn2pL4waR1JhDkBl6Af0RdzkzzLDpxdPyr9cuRMg2iWhxpbHag0TaRNlBeuhiA==
expo-updates@~0.5.4:
version "0.5.4"
resolved "https://registry.yarnpkg.com/expo-updates/-/expo-updates-0.5.4.tgz#ee1b395fee701fb230e7ae28729f4d7055dd3514"
integrity sha512-2lwvcBqZutUT4TwqPgTpcBuomjTHt781k1DjGKpf1lS9X/E4w1dgklESXfSASaTS84YuXfK/i6/7LjTWucCv/A==
dependencies:
"@expo/config-plugins" "^1.0.18"
"@expo/metro-config" "^0.1.59"
@@ -8155,25 +8281,25 @@ expo-updates@~0.5.3:
resolve-from "^5.0.0"
uuid "^3.4.0"
expo@^41.0.0-beta.2:
version "41.0.0-beta.2"
resolved "https://registry.yarnpkg.com/expo/-/expo-41.0.0-beta.2.tgz#444702ade489b69bf6f396b9aa7eb255e76e81b1"
integrity sha512-TIr1VmV79SnpcUsWoiDPYtj6scFEuQwmZTkxkbf8fXO5nKsEXz8KMJfp2Lbo+n2Lv27LZ4wP0ZETQqHGWG9jRA==
expo@^41.0.1:
version "41.0.1"
resolved "https://registry.yarnpkg.com/expo/-/expo-41.0.1.tgz#2689003212dcc948d010f86dadf055721a6314b4"
integrity sha512-Lk4Xdst+OfsLgBNeu89hxk0qFrZnHwwXFmBbJkYLlZkdC3tvNJ8jgGHsKg6jYpsnal/z0uVc+uk2ev91qXQykg==
dependencies:
"@babel/runtime" "^7.1.2"
"@expo/metro-config" "^0.1.59"
"@expo/metro-config" "^0.1.63"
"@expo/vector-icons" "^12.0.4"
"@unimodules/core" "~7.1.0"
"@unimodules/react-native-adapter" "~6.2.1"
"@unimodules/react-native-adapter" "~6.2.2"
babel-preset-expo "~8.3.0"
cross-spawn "^6.0.5"
expo-application "~3.1.0"
expo-application "~3.1.2"
expo-asset "~8.3.1"
expo-constants "~10.1.1"
expo-constants "~10.1.3"
expo-error-recovery "~2.1.0"
expo-file-system "~11.0.0"
expo-file-system "~11.0.2"
expo-font "~9.1.0"
expo-keep-awake "~9.1.1"
expo-keep-awake "~9.1.2"
fbemitter "^2.1.1"
invariant "^2.2.2"
md5-file "^3.2.3"
@@ -9921,11 +10047,6 @@ imurmurhash@^0.1.4:
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
integrity sha1-khi5srkoojixPcT7a21XbyMUU+o=
indent-string@3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-3.2.0.tgz#4a5fd6d27cc332f37e5419a504dbb837105c9289"
integrity sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=
indent-string@4.0.0, indent-string@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251"
@@ -11862,7 +11983,7 @@ lodash.uniq@^4.5.0:
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
"lodash@>=3.5 <5", lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.3.0, lodash@^4.5.0, lodash@^4.7.0:
"lodash@>=3.5 <5", lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.3.0, lodash@^4.7.0:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@@ -11903,11 +12024,6 @@ loud-rejection@^1.0.0:
currently-unhandled "^0.4.1"
signal-exit "^3.0.0"
lower-case@^1.1.1:
version "1.1.4"
resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac"
integrity sha1-miyr0bno4K6ZOkv31YdcOcQujqw=
lower-case@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28"
@@ -12864,7 +12980,7 @@ natural-compare@^1.4.0:
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=
ncp@2.0.0, ncp@~2.0.0:
ncp@~2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ncp/-/ncp-2.0.0.tgz#195a21d6c46e361d2fb1281ba38b91e9df7bdbb3"
integrity sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=
@@ -12898,13 +13014,6 @@ nice-try@^1.0.4:
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
no-case@^2.2.0:
version "2.3.2"
resolved "https://registry.yarnpkg.com/no-case/-/no-case-2.3.2.tgz#60b813396be39b3f1288a4c1ed5d1e7d28b464ac"
integrity sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==
dependencies:
lower-case "^1.1.1"
no-case@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d"
@@ -13909,14 +14018,6 @@ parseurl@^1.3.2, parseurl@~1.3.2, parseurl@~1.3.3:
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
pascal-case@2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/pascal-case/-/pascal-case-2.0.1.tgz#2d578d3455f660da65eca18ef95b4e0de912761e"
integrity sha1-LVeNNFX2YNpl7KGO+VtODekSdh4=
dependencies:
camel-case "^3.0.0"
upper-case-first "^1.1.0"
pascal-case@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/pascal-case/-/pascal-case-3.1.2.tgz#b48e0ef2b98e205e7c1dae747d0b1508237660eb"
@@ -15165,9 +15266,9 @@ react-native-vector-icons@^8.1.0:
yargs "^16.1.1"
react-native-web@~0.15.0:
version "0.15.5"
resolved "https://registry.yarnpkg.com/react-native-web/-/react-native-web-0.15.5.tgz#6ad8d4553df080c9a9c30b63cffc7b41d0e076ec"
integrity sha512-SSXQfVJ8Zm5p633JM5QgkZ1BhZHqJEkRaSUa1BUzgzpMcqWX+XUFM0ZFwsRO5O9yd0wgi4yH89Da4qdoaCjoDw==
version "0.15.7"
resolved "https://registry.yarnpkg.com/react-native-web/-/react-native-web-0.15.7.tgz#5b07d98032a36af3cb558357c7a1470e879fc422"
integrity sha512-mrbQayl1luIO4Gfyw6KLdlWc30JcJOQgRn84iOj6dLJYVRwcE6W5U2Af68hQYFJsGgVb9sdlYC0ppfFM7ywqXQ==
dependencies:
array-find-index "^1.0.2"
create-react-class "^15.7.0"
@@ -15612,11 +15713,6 @@ repeating@^2.0.0:
dependencies:
is-finite "^1.0.0"
replace-string@1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/replace-string/-/replace-string-1.1.0.tgz#87062117f823fe5800c306bacb2cfa359b935fea"
integrity sha1-hwYhF/gj/lgAwwa6yyz6NZuTX+o=
request-promise-core@1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.4.tgz#3eedd4223208d419867b78ce815167d10593a22f"
@@ -16367,7 +16463,7 @@ slugid@1.1.0:
dependencies:
uuid "^2.0.1"
slugify@^1.3.4, slugify@^1.3.6:
slugify@^1.3.4:
version "1.5.0"
resolved "https://registry.yarnpkg.com/slugify/-/slugify-1.5.0.tgz#5f3c8e2a84105b54eb51486db1b468a599b3c9b8"
integrity sha512-Q2UPZ2udzquy1ElHfOLILMBMqBEXkiD3wE75qtBvV+FsDdZZjUqPZ44vqLTejAVq+wLLHacOMcENnP8+ZbzmIA==
@@ -17632,11 +17728,16 @@ typedarray@^0.0.6:
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
typescript@^4.2.3, typescript@~4.2.3:
typescript@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.3.tgz#39062d8019912d43726298f09493d598048c1ce3"
integrity sha512-qOcYwxaByStAWrBf4x0fibwZvMRG+r4cQoTjbPtUlrWjBHbmCAww1i448U0GJ+3cNNEtebDteo/cHOR3xJ4wEw==
typescript@~4.2.3:
version "4.2.4"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.4.tgz#8610b59747de028fda898a8aef0e103f156d0961"
integrity sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg==
ua-parser-js@^0.7.18:
version "0.7.27"
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.27.tgz#b54f8ce9eb6c7abf3584edeaf9a3d8b3bd92edba"
@@ -17856,18 +17957,6 @@ update-notifier@^4.1.0:
semver-diff "^3.1.1"
xdg-basedir "^4.0.0"
upper-case-first@^1.1.0:
version "1.1.2"
resolved "https://registry.yarnpkg.com/upper-case-first/-/upper-case-first-1.1.2.tgz#5d79bedcff14419518fd2edb0a0507c9b6859115"
integrity sha1-XXm+3P8UQZUY/S7bCgUHybaFkRU=
dependencies:
upper-case "^1.1.1"
upper-case@^1.1.1:
version "1.1.3"
resolved "https://registry.yarnpkg.com/upper-case/-/upper-case-1.1.3.tgz#f6b4501c2ec4cdd26ba78be7222961de77621598"
integrity sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=
uri-js@^4.2.2:
version "4.4.1"
resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e"
@@ -18737,24 +18826,24 @@ xdg-basedir@^4.0.0:
resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13"
integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==
xdl@59.0.29:
version "59.0.29"
resolved "https://registry.yarnpkg.com/xdl/-/xdl-59.0.29.tgz#ae2fa01dbf195d3e2d4f91cf6ad61277c4323fa7"
integrity sha512-f593BX+PUoAy/Sz+OG6GFGhobKKWbi5nzW9Pph9RKZK3ovpiKc+dVX8zNY+bJxWCkgPCv7c11smb/8mOPsWAQA==
xdl@59.0.35:
version "59.0.35"
resolved "https://registry.yarnpkg.com/xdl/-/xdl-59.0.35.tgz#70f807e1c2930260563ca0169bca9f80abb6ef84"
integrity sha512-nXVjmVKLA/Z+jxhXQm9ZFwaLPehbg0xxT0tW9IbeDVK/QqXbrX2UV/qHdTw9Qa95rxHqaccjX2qbPPgRQqa/Rg==
dependencies:
"@expo/bunyan" "4.0.0"
"@expo/config" "3.3.34"
"@expo/dev-server" "0.1.60"
"@expo/config" "3.3.39"
"@expo/config-plugins" "1.0.29"
"@expo/dev-server" "0.1.65"
"@expo/devcert" "^1.0.0"
"@expo/json-file" "8.2.28"
"@expo/osascript" "2.0.25"
"@expo/package-manager" "0.0.39"
"@expo/json-file" "8.2.29"
"@expo/osascript" "2.0.27"
"@expo/package-manager" "0.0.42"
"@expo/plist" "0.0.12"
"@expo/schemer" "1.3.27"
"@expo/schemer" "1.3.28"
"@expo/spawn-async" "1.5.0"
"@expo/webpack-config" "0.12.64"
"@expo/webpack-config" "0.12.69"
"@hapi/joi" "^17.1.1"
"@types/text-table" "^0.2.1"
analytics-node "3.5.0"
axios "0.21.1"
boxen "4.1.0"
@@ -18768,7 +18857,6 @@ xdl@59.0.29:
getenv "^1.0.0"
glob "7.1.6"
hasbin "1.2.3"
indent-string "3.2.0"
internal-ip "4.3.0"
is-reachable "^4.0.0"
is-root "^2.1.0"
@@ -18778,25 +18866,22 @@ xdl@59.0.29:
md5hex "1.0.0"
minimatch "3.0.4"
mv "2.1.1"
ncp "2.0.0"
node-forge "0.10.0"
p-map "3.0.0"
p-retry "4.1.0"
p-timeout "3.1.0"
package-json "6.4.0"
pascal-case "2.0.1"
pretty-bytes "^5.3.0"
probe-image-size "~6.0.0"
progress "2.0.3"
prompts "^2.3.2"
raven "2.6.3"
react-dev-utils "~11.0.1"
replace-string "1.1.0"
requireg "^0.2.2"
resolve-from "^5.0.0"
semver "7.3.2"
serialize-error "6.0.0"
slugid "1.1.0"
slugify "^1.3.6"
source-map-support "0.4.18"
split "1.0.1"
strip-ansi "^6.0.0"
@@ -18808,6 +18893,7 @@ xdl@59.0.29:
uuid "3.3.2"
webpack "4.43.0"
webpack-dev-server "3.11.0"
wrap-ansi "^7.0.0"
xhr@^2.0.1:
version "2.6.0"