mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-01-13 22:42:25 +08:00
Compare commits
56 Commits
@react-nav
...
@react-nav
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a248c453ba | ||
|
|
e097df880a | ||
|
|
856449b200 | ||
|
|
d94e43c3c8 | ||
|
|
3096de6286 | ||
|
|
1c001424b5 | ||
|
|
0f2368965c | ||
|
|
61f16d3f25 | ||
|
|
853740bfaf | ||
|
|
179b6312fe | ||
|
|
043924ca48 | ||
|
|
813a5903b5 | ||
|
|
3709e652f4 | ||
|
|
5b15c7164f | ||
|
|
e030932497 | ||
|
|
adbfedcd58 | ||
|
|
bc9b044fb3 | ||
|
|
f24d3a3461 | ||
|
|
3df65e2819 | ||
|
|
5c4afc5cb4 | ||
|
|
d5bb357053 | ||
|
|
b1fe73097f | ||
|
|
49f6fed6d3 | ||
|
|
b1a65fc73e | ||
|
|
3ea8eec432 | ||
|
|
00e0f05190 | ||
|
|
193c344ba5 | ||
|
|
358d9e9feb | ||
|
|
6a5d0a035a | ||
|
|
b75744abd5 | ||
|
|
6dbda1a0c2 | ||
|
|
70029d6c13 | ||
|
|
469d0542c7 | ||
|
|
0dcaea3242 | ||
|
|
646cbfb28e | ||
|
|
660cac3557 | ||
|
|
e637250a7e | ||
|
|
82af7bed71 | ||
|
|
cb46d0bca4 | ||
|
|
b3665a325d | ||
|
|
0cc7a12b9c | ||
|
|
90e417248d | ||
|
|
e071a978e6 | ||
|
|
296c836064 | ||
|
|
09f6808d7d | ||
|
|
5bb0f405ce | ||
|
|
2dfa4f3629 | ||
|
|
cf41288760 | ||
|
|
3677818f63 | ||
|
|
162410843c | ||
|
|
028c2887c6 | ||
|
|
7a44cda136 | ||
|
|
a046db536f | ||
|
|
d115787b1c | ||
|
|
80a337024a | ||
|
|
c19da31240 |
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
yarn-*.js binary
|
||||
2
.github/workflows/expo-preview.yml
vendored
2
.github/workflows/expo-preview.yml
vendored
@@ -44,7 +44,7 @@ jobs:
|
||||
|
||||
- name: Get expo link
|
||||
id: expo
|
||||
run: echo "::set-output name=path::@react-navigation/react-react-navigationample?release-channel=pr-${{ github.event.number }}"
|
||||
run: echo "::set-output name=path::@react-navigation/react-navigation-example?release-channel=pr-${{ github.event.number }}"
|
||||
|
||||
- name: Comment on PR
|
||||
uses: unsplash/comment-on-pr@master
|
||||
|
||||
20
.github/workflows/stale.yml
vendored
20
.github/workflows/stale.yml
vendored
@@ -1,20 +0,0 @@
|
||||
name: "Close stale issues and pull requests"
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v1
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
stale-issue-message: 'Hello 👋, this issue has been open for more than 3 months with no activity on it. If the issue is still present in the latest version, please leave a comment within 7 days to keep it open, otherwise it will be closed automatically. If you found a solution on workaround for the issue, please comment here for others to find. If this issue is critical for you, please consider sending a pull request to fix the issue.'
|
||||
stale-pr-message: 'Hello 👋, this pull request has been open for more than 3 months with no activity on it. If you think this is still necessary with the latest version, please comment and ping a maintainer to get this reviewed, otherwise it will be closed automatically in 7 days.'
|
||||
days-before-stale: 90
|
||||
days-before-close: 7
|
||||
stale-issue-label: 'stale'
|
||||
stale-pr-label: 'stale'
|
||||
exempt-issue-label: 'keep open'
|
||||
exempt-pr-label: 'keep open'
|
||||
11
.github/workflows/triage.yml
vendored
11
.github/workflows/triage.yml
vendored
@@ -36,3 +36,14 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
args: comment "Hey! Thanks for opening the issue. The issue tracker is intended for only tracking bug reports. This helps us prioritize fixing bugs in the library. Seems you have a usage question. Please ask the question on [StackOverflow](https://stackoverflow.com/questions/tagged/react-navigation) instead using the `react-navigation` label. You can also chat with other community members on [Reactiflux Discord server](https://www.reactiflux.com/) in the `#react-navigation` channel."
|
||||
|
||||
feature-request:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.label.name == 'feature-request'
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- uses: actions/github@v1.0.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
args: comment "Hey! Thanks for opening the issue. The issue tracker is intended for only tracking bug reports. Seems you have a feature request. Please post the feature request on [Canny](https://react-navigation.canny.io/feature-requests). This lets other users upvote your feature request and helps us prioritize the most requested features."
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,6 +6,7 @@
|
||||
.gradle
|
||||
.project
|
||||
.settings
|
||||
.history
|
||||
|
||||
local.properties
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
module.exports = function(api) {
|
||||
module.exports = function (api) {
|
||||
api.cache(true);
|
||||
return {
|
||||
presets: ['babel-preset-expo'],
|
||||
|
||||
@@ -15,8 +15,8 @@ const modules = ['@expo/vector-icons']
|
||||
// List all packages under `packages/`
|
||||
.readdirSync(packages)
|
||||
// Ignore hidden files such as .DS_Store
|
||||
.filter(p => !p.startsWith('.'))
|
||||
.map(p => {
|
||||
.filter((p) => !p.startsWith('.'))
|
||||
.map((p) => {
|
||||
const pak = JSON.parse(
|
||||
fs.readFileSync(path.join(packages, p, 'package.json'), 'utf8')
|
||||
);
|
||||
@@ -50,9 +50,9 @@ module.exports = {
|
||||
blacklistRE: blacklist(
|
||||
fs
|
||||
.readdirSync(packages)
|
||||
.map(p => path.join(packages, p))
|
||||
.map((p) => path.join(packages, p))
|
||||
.map(
|
||||
it => new RegExp(`^${escape(path.join(it, 'node_modules'))}\\/.*$`)
|
||||
(it) => new RegExp(`^${escape(path.join(it, 'node_modules'))}\\/.*$`)
|
||||
)
|
||||
),
|
||||
|
||||
@@ -65,7 +65,7 @@ module.exports = {
|
||||
},
|
||||
|
||||
server: {
|
||||
enhanceMiddleware: middleware => {
|
||||
enhanceMiddleware: (middleware) => {
|
||||
return (req, res, next) => {
|
||||
// When an asset is imported outside the project root, it has wrong path on Android
|
||||
// This happens for the back button in stack, so we fix the path to correct one
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@expo/vector-icons": "^10.0.0",
|
||||
"@react-native-community/masked-view": "0.1.6",
|
||||
"@react-native-community/masked-view": "0.1.7",
|
||||
"@types/react-native-restart": "^0.0.0",
|
||||
"color": "^3.1.2",
|
||||
"expo": "^36.0.2",
|
||||
@@ -21,22 +21,22 @@
|
||||
"react": "~16.9.0",
|
||||
"react-dom": "~16.9.0",
|
||||
"react-native": "~0.61.5",
|
||||
"react-native-gesture-handler": "^1.5.6",
|
||||
"react-native-gesture-handler": "^1.6.0",
|
||||
"react-native-paper": "^3.6.0",
|
||||
"react-native-reanimated": "^1.7.0",
|
||||
"react-native-restart": "^0.0.13",
|
||||
"react-native-safe-area-context": "^0.7.2",
|
||||
"react-native-screens": "^2.0.0-beta.2",
|
||||
"react-native-restart": "^0.0.14",
|
||||
"react-native-safe-area-context": "^0.7.3",
|
||||
"react-native-screens": "^2.3.0",
|
||||
"react-native-tab-view": "2.13.0",
|
||||
"react-native-unimodules": "^0.7.0",
|
||||
"react-native-web": "^0.11.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@expo/webpack-config": "^0.10.12",
|
||||
"@types/react": "^16.9.19",
|
||||
"@types/react-native": "^0.60.30",
|
||||
"@expo/webpack-config": "^0.11.7",
|
||||
"@types/react": "^16.9.23",
|
||||
"@types/react-native": "^0.61.22",
|
||||
"babel-preset-expo": "^8.0.0",
|
||||
"expo-cli": "^3.11.9",
|
||||
"expo-cli": "^3.13.8",
|
||||
"typescript": "^3.7.5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ export default function BottomTabsScreen() {
|
||||
return (
|
||||
<BottomTabs.Navigator
|
||||
screenOptions={{
|
||||
tabBarButton: props => <TouchableBounce {...props} />,
|
||||
tabBarButton: (props) => <TouchableBounce {...props} />,
|
||||
}}
|
||||
>
|
||||
<BottomTabs.Screen
|
||||
@@ -38,7 +38,7 @@ export default function BottomTabsScreen() {
|
||||
tabBarIcon: getTabBarIcon('file-document-box'),
|
||||
}}
|
||||
>
|
||||
{props => <SimpleStackScreen {...props} headerMode="none" />}
|
||||
{(props) => <SimpleStackScreen {...props} headerMode="none" />}
|
||||
</BottomTabs.Screen>
|
||||
<BottomTabs.Screen
|
||||
name="Chat"
|
||||
|
||||
@@ -15,7 +15,7 @@ export default function BottomTabsScreen() {
|
||||
|
||||
return (
|
||||
<BottomTabs.Navigator>
|
||||
{tabs.map(i => (
|
||||
{tabs.map((i) => (
|
||||
<BottomTabs.Screen
|
||||
key={i}
|
||||
name={`tab-${i}`}
|
||||
@@ -29,12 +29,14 @@ export default function BottomTabsScreen() {
|
||||
{() => (
|
||||
<View style={styles.container}>
|
||||
<Title>Tab {i}</Title>
|
||||
<Button onPress={() => setTabs(tabs => [...tabs, tabs.length])}>
|
||||
<Button onPress={() => setTabs((tabs) => [...tabs, tabs.length])}>
|
||||
Add a tab
|
||||
</Button>
|
||||
<Button
|
||||
onPress={() =>
|
||||
setTabs(tabs => (tabs.length > 1 ? tabs.slice(0, -1) : tabs))
|
||||
setTabs((tabs) =>
|
||||
tabs.length > 1 ? tabs.slice(0, -1) : tabs
|
||||
)
|
||||
}
|
||||
>
|
||||
Remove a tab
|
||||
|
||||
@@ -28,7 +28,7 @@ export default function MaterialBottomTabsScreen() {
|
||||
tabBarColor: '#C9E7F8',
|
||||
}}
|
||||
>
|
||||
{props => <SimpleStackScreen {...props} headerMode="none" />}
|
||||
{(props) => <SimpleStackScreen {...props} headerMode="none" />}
|
||||
</MaterialBottomTabs.Screen>
|
||||
<MaterialBottomTabs.Screen
|
||||
name="Chat"
|
||||
|
||||
@@ -68,10 +68,7 @@ export default function Chat(props: Partial<ScrollViewProps>) {
|
||||
styles.input,
|
||||
{ backgroundColor: colors.card, color: colors.text },
|
||||
]}
|
||||
placeholderTextColor={Color(colors.text)
|
||||
.alpha(0.5)
|
||||
.rgb()
|
||||
.string()}
|
||||
placeholderTextColor={Color(colors.text).alpha(0.5).rgb().string()}
|
||||
placeholder="Write a message"
|
||||
underlineColorAndroid="transparent"
|
||||
/>
|
||||
|
||||
@@ -51,10 +51,7 @@ export default function NewsFeed(props: Props) {
|
||||
<Card style={styles.card}>
|
||||
<TextInput
|
||||
placeholder="What's on your mind?"
|
||||
placeholderTextColor={Color(colors.text)
|
||||
.alpha(0.5)
|
||||
.rgb()
|
||||
.string()}
|
||||
placeholderTextColor={Color(colors.text).alpha(0.5).rgb().string()}
|
||||
style={styles.input}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
@@ -6,8 +6,14 @@ import {
|
||||
Platform,
|
||||
StatusBar,
|
||||
I18nManager,
|
||||
Dimensions,
|
||||
ScaledSize,
|
||||
} from 'react-native';
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
import { enableScreens } from 'react-native-screens';
|
||||
import RNRestart from 'react-native-restart';
|
||||
import { Updates } from 'expo';
|
||||
import { Asset } from 'expo-asset';
|
||||
import { MaterialIcons } from '@expo/vector-icons';
|
||||
import {
|
||||
Provider as PaperProvider,
|
||||
@@ -17,7 +23,6 @@ import {
|
||||
List,
|
||||
Divider,
|
||||
} from 'react-native-paper';
|
||||
import { Asset } from 'expo-asset';
|
||||
import {
|
||||
InitialState,
|
||||
useLinking,
|
||||
@@ -49,10 +54,11 @@ import DynamicTabs from './Screens/DynamicTabs';
|
||||
import AuthFlow from './Screens/AuthFlow';
|
||||
import CompatAPI from './Screens/CompatAPI';
|
||||
import SettingsItem from './Shared/SettingsItem';
|
||||
import { Updates } from 'expo';
|
||||
|
||||
YellowBox.ignoreWarnings(['Require cycle:', 'Warning: Async Storage']);
|
||||
|
||||
enableScreens();
|
||||
|
||||
type RootDrawerParamList = {
|
||||
Root: undefined;
|
||||
Another: undefined;
|
||||
@@ -192,10 +198,24 @@ export default function App() {
|
||||
};
|
||||
}, [theme.colors, theme.dark]);
|
||||
|
||||
const [dimensions, setDimensions] = React.useState(Dimensions.get('window'));
|
||||
|
||||
React.useEffect(() => {
|
||||
const onDimensionsChange = ({ window }: { window: ScaledSize }) => {
|
||||
setDimensions(window);
|
||||
};
|
||||
|
||||
Dimensions.addEventListener('change', onDimensionsChange);
|
||||
|
||||
return () => Dimensions.removeEventListener('change', onDimensionsChange);
|
||||
}, []);
|
||||
|
||||
if (!isReady) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isLargeScreen = dimensions.width > 900;
|
||||
|
||||
return (
|
||||
<PaperProvider theme={paperTheme}>
|
||||
{Platform.OS === 'ios' && (
|
||||
@@ -204,7 +224,7 @@ export default function App() {
|
||||
<NavigationContainer
|
||||
ref={containerRef}
|
||||
initialState={initialState}
|
||||
onStateChange={state =>
|
||||
onStateChange={(state) =>
|
||||
AsyncStorage.setItem(
|
||||
NAVIGATION_PERSISTENCE_KEY,
|
||||
JSON.stringify(state)
|
||||
@@ -212,7 +232,7 @@ export default function App() {
|
||||
}
|
||||
theme={theme}
|
||||
>
|
||||
<Drawer.Navigator>
|
||||
<Drawer.Navigator drawerType={isLargeScreen ? 'permanent' : undefined}>
|
||||
<Drawer.Screen
|
||||
name="Root"
|
||||
options={{
|
||||
@@ -236,13 +256,15 @@ export default function App() {
|
||||
name="Home"
|
||||
options={{
|
||||
title: 'Examples',
|
||||
headerLeft: () => (
|
||||
<Appbar.Action
|
||||
color={theme.colors.text}
|
||||
icon="menu"
|
||||
onPress={() => navigation.toggleDrawer()}
|
||||
/>
|
||||
),
|
||||
headerLeft: isLargeScreen
|
||||
? undefined
|
||||
: () => (
|
||||
<Appbar.Action
|
||||
color={theme.colors.text}
|
||||
icon="menu"
|
||||
onPress={() => navigation.toggleDrawer()}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
>
|
||||
{({
|
||||
@@ -276,12 +298,12 @@ export default function App() {
|
||||
theme.dark ? 'light' : 'dark'
|
||||
);
|
||||
|
||||
setTheme(t => (t.dark ? DefaultTheme : DarkTheme));
|
||||
setTheme((t) => (t.dark ? DefaultTheme : DarkTheme));
|
||||
}}
|
||||
/>
|
||||
<Divider />
|
||||
{(Object.keys(SCREENS) as (keyof typeof SCREENS)[]).map(
|
||||
name => (
|
||||
(name) => (
|
||||
<List.Item
|
||||
key={name}
|
||||
title={SCREENS[name].title}
|
||||
@@ -293,7 +315,7 @@ export default function App() {
|
||||
)}
|
||||
</Stack.Screen>
|
||||
{(Object.keys(SCREENS) as (keyof typeof SCREENS)[]).map(
|
||||
name => (
|
||||
(name) => (
|
||||
<Stack.Screen
|
||||
key={name}
|
||||
name={name}
|
||||
|
||||
@@ -7,7 +7,7 @@ const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
|
||||
const node_modules = path.resolve(__dirname, '..', 'node_modules');
|
||||
const packages = path.resolve(__dirname, '..', 'packages');
|
||||
|
||||
module.exports = async function(env, argv) {
|
||||
module.exports = async function (env, argv) {
|
||||
const config = await createExpoWebpackConfigAsync(env, argv);
|
||||
|
||||
config.context = path.resolve(__dirname, '..');
|
||||
@@ -20,7 +20,7 @@ module.exports = async function(env, argv) {
|
||||
});
|
||||
|
||||
config.resolve.plugins = config.resolve.plugins.filter(
|
||||
p => !(p instanceof ModuleScopePlugin)
|
||||
(p) => !(p instanceof ModuleScopePlugin)
|
||||
);
|
||||
|
||||
Object.assign(config.resolve.alias, {
|
||||
@@ -30,7 +30,7 @@ module.exports = async function(env, argv) {
|
||||
'@expo/vector-icons': path.resolve(node_modules, '@expo/vector-icons'),
|
||||
});
|
||||
|
||||
fs.readdirSync(packages).forEach(name => {
|
||||
fs.readdirSync(packages).forEach((name) => {
|
||||
config.resolve.alias[`@react-navigation/${name}`] = path.resolve(
|
||||
packages,
|
||||
name,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const error = console.error;
|
||||
|
||||
console.error = (...args) =>
|
||||
// Supress error messages regarding error boundary in tests
|
||||
// Suppress error messages regarding error boundary in tests
|
||||
/(Consider adding an error boundary to your tree to customize error handling behavior|React will try to recreate this component tree from scratch using the error boundary you provided|Error boundaries should implement getDerivedStateFromError)/m.test(
|
||||
args[0]
|
||||
)
|
||||
|
||||
13
package.json
13
package.json
@@ -20,29 +20,30 @@
|
||||
"lint": "eslint --ext '.js,.ts,.tsx' .",
|
||||
"typescript": "tsc --noEmit",
|
||||
"test": "jest",
|
||||
"prerelease": "lerna run clean",
|
||||
"release": "lerna publish",
|
||||
"example": "yarn --cwd example"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-class-properties": "^7.8.3",
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.8.3",
|
||||
"@babel/preset-env": "^7.8.4",
|
||||
"@babel/preset-env": "^7.8.7",
|
||||
"@babel/preset-flow": "^7.8.3",
|
||||
"@babel/preset-react": "^7.8.3",
|
||||
"@babel/preset-typescript": "^7.8.3",
|
||||
"@babel/runtime": "^7.8.4",
|
||||
"@babel/runtime": "^7.8.7",
|
||||
"@commitlint/config-conventional": "^8.3.4",
|
||||
"@types/jest": "^25.1.2",
|
||||
"@types/jest": "^25.1.4",
|
||||
"codecov": "^3.6.5",
|
||||
"commitlint": "^8.3.5",
|
||||
"core-js": "^3.6.4",
|
||||
"detox": "^15.1.4",
|
||||
"detox": "^16.0.0",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-config-satya164": "^3.1.5",
|
||||
"husky": "^4.2.1",
|
||||
"husky": "^4.2.3",
|
||||
"jest": "^25.1.0",
|
||||
"lerna": "^3.20.2",
|
||||
"prettier": "^1.19.1",
|
||||
"prettier": "^2.0.1",
|
||||
"typescript": "^3.7.5"
|
||||
},
|
||||
"resolutions": {
|
||||
|
||||
@@ -3,6 +3,72 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [5.2.4](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.2.3...@react-navigation/bottom-tabs@5.2.4) (2020-03-23)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.3](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.2.2...@react-navigation/bottom-tabs@5.2.3) (2020-03-22)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.2](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.2.1...@react-navigation/bottom-tabs@5.2.2) (2020-03-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* don't use react-native-screens on web ([b1a65fc](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/commit/b1a65fc73e8603ae2c06ef101a74df31e80bb9b2)), closes [#7485](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/issues/7485)
|
||||
* initialize height and width to zero if undefined ([3df65e2](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/commit/3df65e28197db3bb8371059146546d57661c5ba3)), closes [#6789](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/issues/6789)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.1](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.2.0...@react-navigation/bottom-tabs@5.2.1) (2020-03-17)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.2.0](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.1.1...@react-navigation/bottom-tabs@5.2.0) (2020-03-16)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add safeAreaInsets to bottom tabs ([82af7be](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/commit/82af7bed7135e42e24693b48cf7f1c6f9f5a6981))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.1](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.1.0...@react-navigation/bottom-tabs@5.1.1) (2020-03-03)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.1.0](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.0.7...@react-navigation/bottom-tabs@5.1.0) (2020-02-26)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add ability add listeners with listeners prop ([1624108](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/commit/162410843c4f175ae107756de1c3af04d1d47aa7)), closes [#6756](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/issues/6756)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.0.7](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.0.6...@react-navigation/bottom-tabs@5.0.7) (2020-02-21)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/bottom-tabs",
|
||||
"description": "Bottom tab navigator following iOS design guidelines",
|
||||
"version": "5.0.7",
|
||||
"version": "5.2.4",
|
||||
"keywords": [
|
||||
"react-native-component",
|
||||
"react-component",
|
||||
@@ -34,22 +34,24 @@
|
||||
"react-native-iphone-x-helper": "^1.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.9.3",
|
||||
"@react-navigation/native": "^5.0.7",
|
||||
"@react-native-community/bob": "^0.10.0",
|
||||
"@react-navigation/native": "^5.1.3",
|
||||
"@types/color": "^3.0.1",
|
||||
"@types/react": "^16.9.19",
|
||||
"@types/react-native": "^0.60.30",
|
||||
"@types/react": "^16.9.23",
|
||||
"@types/react-native": "^0.61.22",
|
||||
"del-cli": "^3.0.0",
|
||||
"react": "~16.9.0",
|
||||
"react-native": "~0.61.5",
|
||||
"react-native-safe-area-context": "^0.7.2",
|
||||
"react-native-safe-area-context": "^0.7.3",
|
||||
"react-native-screens": "^2.3.0",
|
||||
"typescript": "^3.7.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@react-navigation/native": "^5.0.5",
|
||||
"react": "*",
|
||||
"react-native": "*",
|
||||
"react-native-safe-area-context": ">= 0.6.0"
|
||||
"react-native-safe-area-context": ">= 0.6.0",
|
||||
"react-native-screens": ">= 2.0.0-alpha.0 || >= 2.0.0-beta.0 || >= 2.0.0"
|
||||
},
|
||||
"@react-native-community/bob": {
|
||||
"source": "src",
|
||||
|
||||
@@ -48,6 +48,8 @@ function BottomTabNavigator({
|
||||
}
|
||||
|
||||
export default createNavigatorFactory<
|
||||
TabNavigationState,
|
||||
BottomTabNavigationOptions,
|
||||
BottomTabNavigationEventMap,
|
||||
typeof BottomTabNavigator
|
||||
>(BottomTabNavigator);
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
ParamListBase,
|
||||
Descriptor,
|
||||
TabNavigationState,
|
||||
TabActionHelpers,
|
||||
} from '@react-navigation/native';
|
||||
|
||||
export type BottomTabNavigationEventMap = {
|
||||
@@ -40,19 +41,8 @@ export type BottomTabNavigationProp<
|
||||
TabNavigationState,
|
||||
BottomTabNavigationOptions,
|
||||
BottomTabNavigationEventMap
|
||||
> & {
|
||||
/**
|
||||
* Jump to an existing tab.
|
||||
*
|
||||
* @param name Name of the route for the tab.
|
||||
* @param [params] Params object for the route.
|
||||
*/
|
||||
jumpTo<RouteName extends Extract<keyof ParamList, string>>(
|
||||
...args: ParamList[RouteName] extends undefined | any
|
||||
? [RouteName] | [RouteName, ParamList[RouteName]]
|
||||
: [RouteName, ParamList[RouteName]]
|
||||
): void;
|
||||
};
|
||||
> &
|
||||
TabActionHelpers<ParamList>;
|
||||
|
||||
export type BottomTabNavigationOptions = {
|
||||
/**
|
||||
@@ -184,6 +174,16 @@ export type BottomTabBarOptions = {
|
||||
* Whether the label position should adapt to the orientation.
|
||||
*/
|
||||
adaptive?: boolean;
|
||||
/**
|
||||
* Safe area insets for the tab bar. This is used to avoid elements like the navigation bar on Android and bottom safe area on iOS.
|
||||
* By default, the device's safe area insets are automatically detected. You can override the behavior with this option.
|
||||
*/
|
||||
safeAreaInsets?: {
|
||||
top?: number;
|
||||
right?: number;
|
||||
bottom?: number;
|
||||
left?: number;
|
||||
};
|
||||
/**
|
||||
* Style object for the tab bar container.
|
||||
*/
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
CommonActions,
|
||||
useTheme,
|
||||
} from '@react-navigation/native';
|
||||
import { SafeAreaConsumer } from 'react-native-safe-area-context';
|
||||
import { useSafeArea } from 'react-native-safe-area-context';
|
||||
|
||||
import BottomTabItem from './BottomTabItem';
|
||||
import { BottomTabBarProps } from '../types';
|
||||
@@ -43,6 +43,7 @@ export default function BottomTabBar({
|
||||
keyboardHidesTabBar = false,
|
||||
labelPosition,
|
||||
labelStyle,
|
||||
safeAreaInsets,
|
||||
showIcon,
|
||||
showLabel,
|
||||
style,
|
||||
@@ -50,7 +51,12 @@ export default function BottomTabBar({
|
||||
}: Props) {
|
||||
const { colors } = useTheme();
|
||||
|
||||
const [dimensions, setDimensions] = React.useState(Dimensions.get('window'));
|
||||
const [dimensions, setDimensions] = React.useState(() => {
|
||||
const { height = 0, width = 0 } = Dimensions.get('window');
|
||||
|
||||
return { height, width };
|
||||
});
|
||||
|
||||
const [layout, setLayout] = React.useState({
|
||||
height: 0,
|
||||
width: dimensions.width,
|
||||
@@ -115,7 +121,7 @@ export default function BottomTabBar({
|
||||
const handleLayout = (e: LayoutChangeEvent) => {
|
||||
const { height, width } = e.nativeEvent.layout;
|
||||
|
||||
setLayout(layout => {
|
||||
setLayout((layout) => {
|
||||
if (height === layout.height && width === layout.width) {
|
||||
return layout;
|
||||
} else {
|
||||
@@ -158,116 +164,122 @@ export default function BottomTabBar({
|
||||
}
|
||||
};
|
||||
|
||||
const defaultInsets = useSafeArea();
|
||||
|
||||
const insets = {
|
||||
top: safeAreaInsets?.top ?? defaultInsets.top,
|
||||
right: safeAreaInsets?.right ?? defaultInsets.right,
|
||||
bottom: safeAreaInsets?.bottom ?? defaultInsets.bottom,
|
||||
left: safeAreaInsets?.left ?? defaultInsets.left,
|
||||
};
|
||||
|
||||
return (
|
||||
<SafeAreaConsumer>
|
||||
{insets => (
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.tabBar,
|
||||
{
|
||||
backgroundColor: colors.card,
|
||||
borderTopColor: colors.border,
|
||||
},
|
||||
keyboardHidesTabBar
|
||||
? {
|
||||
// When the keyboard is shown, slide down the tab bar
|
||||
transform: [
|
||||
{
|
||||
translateY: visible.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [layout.height, 0],
|
||||
}),
|
||||
},
|
||||
],
|
||||
// Absolutely position the tab bar so that the content is below it
|
||||
// This is needed to avoid gap at bottom when the tab bar is hidden
|
||||
position: keyboardShown ? 'absolute' : null,
|
||||
}
|
||||
: null,
|
||||
{
|
||||
height: DEFAULT_TABBAR_HEIGHT + (insets ? insets.bottom : 0),
|
||||
paddingBottom: insets ? insets.bottom : 0,
|
||||
},
|
||||
style,
|
||||
]}
|
||||
pointerEvents={keyboardHidesTabBar && keyboardShown ? 'none' : 'auto'}
|
||||
>
|
||||
<View style={styles.content} onLayout={handleLayout}>
|
||||
{routes.map((route, index) => {
|
||||
const focused = index === state.index;
|
||||
const { options } = descriptors[route.key];
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.tabBar,
|
||||
{
|
||||
backgroundColor: colors.card,
|
||||
borderTopColor: colors.border,
|
||||
},
|
||||
keyboardHidesTabBar
|
||||
? {
|
||||
// When the keyboard is shown, slide down the tab bar
|
||||
transform: [
|
||||
{
|
||||
translateY: visible.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [layout.height, 0],
|
||||
}),
|
||||
},
|
||||
],
|
||||
// Absolutely position the tab bar so that the content is below it
|
||||
// This is needed to avoid gap at bottom when the tab bar is hidden
|
||||
position: keyboardShown ? 'absolute' : null,
|
||||
}
|
||||
: null,
|
||||
{
|
||||
height: DEFAULT_TABBAR_HEIGHT + insets.bottom,
|
||||
paddingBottom: insets.bottom,
|
||||
paddingHorizontal: Math.max(insets.left, insets.right),
|
||||
},
|
||||
style,
|
||||
]}
|
||||
pointerEvents={keyboardHidesTabBar && keyboardShown ? 'none' : 'auto'}
|
||||
>
|
||||
<View style={styles.content} onLayout={handleLayout}>
|
||||
{routes.map((route, index) => {
|
||||
const focused = index === state.index;
|
||||
const { options } = descriptors[route.key];
|
||||
|
||||
const onPress = () => {
|
||||
const event = navigation.emit({
|
||||
type: 'tabPress',
|
||||
target: route.key,
|
||||
canPreventDefault: true,
|
||||
});
|
||||
const onPress = () => {
|
||||
const event = navigation.emit({
|
||||
type: 'tabPress',
|
||||
target: route.key,
|
||||
canPreventDefault: true,
|
||||
});
|
||||
|
||||
if (!focused && !event.defaultPrevented) {
|
||||
navigation.dispatch({
|
||||
...CommonActions.navigate(route.name),
|
||||
target: state.key,
|
||||
});
|
||||
}
|
||||
};
|
||||
if (!focused && !event.defaultPrevented) {
|
||||
navigation.dispatch({
|
||||
...CommonActions.navigate(route.name),
|
||||
target: state.key,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const onLongPress = () => {
|
||||
navigation.emit({
|
||||
type: 'tabLongPress',
|
||||
target: route.key,
|
||||
});
|
||||
};
|
||||
const onLongPress = () => {
|
||||
navigation.emit({
|
||||
type: 'tabLongPress',
|
||||
target: route.key,
|
||||
});
|
||||
};
|
||||
|
||||
const label =
|
||||
options.tabBarLabel !== undefined
|
||||
? options.tabBarLabel
|
||||
: options.title !== undefined
|
||||
? options.title
|
||||
: route.name;
|
||||
const label =
|
||||
options.tabBarLabel !== undefined
|
||||
? options.tabBarLabel
|
||||
: options.title !== undefined
|
||||
? options.title
|
||||
: route.name;
|
||||
|
||||
const accessibilityLabel =
|
||||
options.tabBarAccessibilityLabel !== undefined
|
||||
? options.tabBarAccessibilityLabel
|
||||
: typeof label === 'string'
|
||||
? `${label}, tab, ${index + 1} of ${routes.length}`
|
||||
: undefined;
|
||||
const accessibilityLabel =
|
||||
options.tabBarAccessibilityLabel !== undefined
|
||||
? options.tabBarAccessibilityLabel
|
||||
: typeof label === 'string'
|
||||
? `${label}, tab, ${index + 1} of ${routes.length}`
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<NavigationContext.Provider
|
||||
key={route.key}
|
||||
value={descriptors[route.key].navigation}
|
||||
>
|
||||
<NavigationRouteContext.Provider value={route}>
|
||||
<BottomTabItem
|
||||
route={route}
|
||||
focused={focused}
|
||||
horizontal={shouldUseHorizontalLabels()}
|
||||
onPress={onPress}
|
||||
onLongPress={onLongPress}
|
||||
accessibilityLabel={accessibilityLabel}
|
||||
testID={options.tabBarTestID}
|
||||
allowFontScaling={allowFontScaling}
|
||||
activeTintColor={activeTintColor}
|
||||
inactiveTintColor={inactiveTintColor}
|
||||
activeBackgroundColor={activeBackgroundColor}
|
||||
inactiveBackgroundColor={inactiveBackgroundColor}
|
||||
button={options.tabBarButton}
|
||||
icon={options.tabBarIcon}
|
||||
label={label}
|
||||
showIcon={showIcon}
|
||||
showLabel={showLabel}
|
||||
labelStyle={labelStyle}
|
||||
style={tabStyle}
|
||||
/>
|
||||
</NavigationRouteContext.Provider>
|
||||
</NavigationContext.Provider>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
</Animated.View>
|
||||
)}
|
||||
</SafeAreaConsumer>
|
||||
return (
|
||||
<NavigationContext.Provider
|
||||
key={route.key}
|
||||
value={descriptors[route.key].navigation}
|
||||
>
|
||||
<NavigationRouteContext.Provider value={route}>
|
||||
<BottomTabItem
|
||||
route={route}
|
||||
focused={focused}
|
||||
horizontal={shouldUseHorizontalLabels()}
|
||||
onPress={onPress}
|
||||
onLongPress={onLongPress}
|
||||
accessibilityLabel={accessibilityLabel}
|
||||
testID={options.tabBarTestID}
|
||||
allowFontScaling={allowFontScaling}
|
||||
activeTintColor={activeTintColor}
|
||||
inactiveTintColor={inactiveTintColor}
|
||||
activeBackgroundColor={activeBackgroundColor}
|
||||
inactiveBackgroundColor={inactiveBackgroundColor}
|
||||
button={options.tabBarButton}
|
||||
icon={options.tabBarIcon}
|
||||
label={label}
|
||||
showIcon={showIcon}
|
||||
showLabel={showLabel}
|
||||
labelStyle={labelStyle}
|
||||
style={tabStyle}
|
||||
/>
|
||||
</NavigationRouteContext.Provider>
|
||||
</NavigationContext.Provider>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
</Animated.View>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -133,9 +133,7 @@ export default function BottomTabBarItem({
|
||||
|
||||
const inactiveTintColor =
|
||||
customInactiveTintColor === undefined
|
||||
? Color(colors.text)
|
||||
.mix(Color(colors.card), 0.5)
|
||||
.hex()
|
||||
? Color(colors.text).mix(Color(colors.card), 0.5).hex()
|
||||
: customInactiveTintColor;
|
||||
|
||||
const renderLabel = ({ focused }: { focused: boolean }) => {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import { Platform, StyleSheet, View } from 'react-native';
|
||||
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
import { Screen, screensEnabled } from 'react-native-screens';
|
||||
|
||||
@@ -10,12 +9,14 @@ type Props = {
|
||||
style?: any;
|
||||
};
|
||||
|
||||
const FAR_FAR_AWAY = 3000; // this should be big enough to move the whole view out of its container
|
||||
const FAR_FAR_AWAY = 30000; // this should be big enough to move the whole view out of its container
|
||||
|
||||
export default class ResourceSavingScene extends React.Component<Props> {
|
||||
render() {
|
||||
if (screensEnabled?.()) {
|
||||
// react-native-screens is buggy on web
|
||||
if (screensEnabled?.() && Platform.OS !== 'web') {
|
||||
const { isVisible, ...rest } = this.props;
|
||||
|
||||
// @ts-ignore
|
||||
return <Screen active={isVisible ? 1 : 0} {...rest} />;
|
||||
}
|
||||
@@ -24,7 +25,13 @@ export default class ResourceSavingScene extends React.Component<Props> {
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[styles.container, style, { opacity: isVisible ? 1 : 0 }]}
|
||||
style={[
|
||||
styles.container,
|
||||
Platform.OS === 'web'
|
||||
? { display: isVisible ? 'flex' : 'none' }
|
||||
: null,
|
||||
style,
|
||||
]}
|
||||
collapsable={false}
|
||||
removeClippedSubviews={
|
||||
// On iOS, set removeClippedSubviews to true only when not focused
|
||||
|
||||
@@ -30,7 +30,7 @@ type Props = {
|
||||
export default function SafeAreaProviderCompat({ children }: Props) {
|
||||
return (
|
||||
<SafeAreaConsumer>
|
||||
{insets => {
|
||||
{(insets) => {
|
||||
if (insets) {
|
||||
// If we already have insets, don't wrap the stack in another safe area provider
|
||||
// This avoids an issue with updates at the cost of potentially incorrect values
|
||||
|
||||
@@ -3,6 +3,65 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [5.1.6](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.5...@react-navigation/compat@5.1.6) (2020-03-23)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.5](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.4...@react-navigation/compat@5.1.5) (2020-03-22)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.4](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.3...@react-navigation/compat@5.1.4) (2020-03-19)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.3](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.2...@react-navigation/compat@5.1.3) (2020-03-17)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.2](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.1...@react-navigation/compat@5.1.2) (2020-03-16)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.1](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.0...@react-navigation/compat@5.1.1) (2020-03-03)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.1.0](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.0.7...@react-navigation/compat@5.1.0) (2020-02-26)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add ability add listeners with listeners prop ([1624108](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/commit/162410843c4f175ae107756de1c3af04d1d47aa7)), closes [#6756](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/issues/6756)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.0.7](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.0.6...@react-navigation/compat@5.0.7) (2020-02-21)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/compat",
|
||||
"description": "Compatibility layer to write navigator definitions in static configuration format",
|
||||
"version": "5.0.7",
|
||||
"version": "5.1.6",
|
||||
"license": "MIT",
|
||||
"repository": "https://github.com/react-navigation/react-navigation/tree/master/packages/compat",
|
||||
"bugs": {
|
||||
@@ -25,9 +25,9 @@
|
||||
"clean": "del lib"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.9.3",
|
||||
"@react-navigation/native": "^5.0.7",
|
||||
"@types/react": "^16.9.19",
|
||||
"@react-native-community/bob": "^0.10.0",
|
||||
"@react-navigation/native": "^5.1.3",
|
||||
"@types/react": "^16.9.23",
|
||||
"react": "~16.9.0",
|
||||
"typescript": "^3.7.5"
|
||||
},
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
TypedNavigator,
|
||||
NavigationProp,
|
||||
RouteProp,
|
||||
EventMapBase,
|
||||
} from '@react-navigation/native';
|
||||
import CompatScreen from './CompatScreen';
|
||||
import ScreenPropsContext from './ScreenPropsContext';
|
||||
@@ -15,7 +16,9 @@ import { CompatScreenType, CompatRouteConfig } from './types';
|
||||
export default function createCompatNavigatorFactory<
|
||||
CreateNavigator extends () => TypedNavigator<
|
||||
ParamListBase,
|
||||
NavigationState,
|
||||
{},
|
||||
EventMapBase,
|
||||
React.ComponentType<any>
|
||||
>
|
||||
>(createNavigator: CreateNavigator) {
|
||||
@@ -66,7 +69,7 @@ export default function createCompatNavigatorFactory<
|
||||
function Navigator({ screenProps }: { screenProps?: unknown }) {
|
||||
const screens = React.useMemo(
|
||||
() =>
|
||||
routeNames.map(name => {
|
||||
routeNames.map((name) => {
|
||||
let getScreenComponent: () => CompatScreenType<NavigationPropType>;
|
||||
|
||||
let initialParams;
|
||||
|
||||
@@ -22,5 +22,7 @@ function SwitchNavigator(props: Props) {
|
||||
}
|
||||
|
||||
export default createCompatNavigatorFactory(
|
||||
createNavigatorFactory<{}, typeof SwitchNavigator>(SwitchNavigator)
|
||||
createNavigatorFactory<TabNavigationState, {}, {}, typeof SwitchNavigator>(
|
||||
SwitchNavigator
|
||||
)
|
||||
);
|
||||
|
||||
@@ -16,7 +16,7 @@ export default function useCompatNavigation<
|
||||
const route = useRoute();
|
||||
|
||||
const isFirstRouteInParent = useNavigationState(
|
||||
state => state.routes[0].key === route.key
|
||||
(state) => state.routes[0].key === route.key
|
||||
);
|
||||
|
||||
const context = React.useRef<Record<string, any>>({});
|
||||
|
||||
@@ -26,8 +26,9 @@ export default function withNavigation<
|
||||
return <Comp ref={onRef} navigation={navigation} {...rest} />;
|
||||
};
|
||||
|
||||
WrappedComponent.displayName = `withNavigation(${Comp.displayName ||
|
||||
Comp.name})`;
|
||||
WrappedComponent.displayName = `withNavigation(${
|
||||
Comp.displayName || Comp.name
|
||||
})`;
|
||||
|
||||
return WrappedComponent;
|
||||
}
|
||||
|
||||
@@ -23,8 +23,9 @@ export default function withNavigationFocus<
|
||||
return <Comp ref={onRef} isFocused={isFocused} {...rest} />;
|
||||
};
|
||||
|
||||
WrappedComponent.displayName = `withNavigationFocus(${Comp.displayName ||
|
||||
Comp.name})`;
|
||||
WrappedComponent.displayName = `withNavigationFocus(${
|
||||
Comp.displayName || Comp.name
|
||||
})`;
|
||||
|
||||
return WrappedComponent;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,73 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [5.3.1](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.3.0...@react-navigation/core@5.3.1) (2020-03-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* don't emit events for screens that don't exist anymore ([1c00142](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/1c001424b595b40f9db9343096c833f75353b099))
|
||||
* only call listeners for focused screen for global events ([3096de6](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/3096de62868a7ed9ed65e529c8ddfa001b9be486))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.3.0](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.2.3...@react-navigation/core@5.3.0) (2020-03-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* return correct value for isFocused after changing screens ([5b15c71](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/5b15c7164f5503f2f0d51006a3f23bd0c58fd9b7)), closes [#7843](https://github.com/react-navigation/react-navigation/tree/master/packages/core/issues/7843)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* support function in listeners prop ([3709e65](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/3709e652f41a16c2c2b05d5dbbe1da2017ba2c3f))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.3](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.2.2...@react-navigation/core@5.2.3) (2020-03-19)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.2](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.2.1...@react-navigation/core@5.2.2) (2020-03-16)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.1](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.2.0...@react-navigation/core@5.2.1) (2020-03-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix links for documentation ([5bb0f40](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/5bb0f405ceb5755d39a0b5b1f2e4ecee0da051bc))
|
||||
* move updating state to useEffect ([2dfa4f3](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/2dfa4f36293a2acb718814f6b2fa79d7c7ddf09c))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.2.0](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.1.6...@react-navigation/core@5.2.0) (2020-02-26)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add ability add listeners with listeners prop ([1624108](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/162410843c4f175ae107756de1c3af04d1d47aa7)), closes [#6756](https://github.com/react-navigation/react-navigation/tree/master/packages/core/issues/6756)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.6](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.1.5...@react-navigation/core@5.1.6) (2020-02-21)
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/core",
|
||||
"description": "Core utilities for building navigators",
|
||||
"version": "5.1.6",
|
||||
"version": "5.3.1",
|
||||
"keywords": [
|
||||
"react",
|
||||
"react-native",
|
||||
@@ -29,23 +29,23 @@
|
||||
"clean": "del lib"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-navigation/routers": "^5.0.2",
|
||||
"@react-navigation/routers": "^5.2.0",
|
||||
"escape-string-regexp": "^2.0.0",
|
||||
"query-string": "^6.10.1",
|
||||
"react-is": "^16.12.0",
|
||||
"query-string": "^6.11.1",
|
||||
"react-is": "^16.13.0",
|
||||
"shortid": "^2.2.15",
|
||||
"use-subscription": "^1.3.0"
|
||||
"use-subscription": "^1.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.9.3",
|
||||
"@types/react": "^16.9.19",
|
||||
"@react-native-community/bob": "^0.10.0",
|
||||
"@types/react": "^16.9.23",
|
||||
"@types/react-is": "^16.7.1",
|
||||
"@types/shortid": "^0.0.29",
|
||||
"@types/use-subscription": "^1.0.0",
|
||||
"del-cli": "^3.0.0",
|
||||
"react": "~16.9.0",
|
||||
"react-native-testing-library": "^1.12.0",
|
||||
"react-test-renderer": "~16.12.0",
|
||||
"react-test-renderer": "~16.9.0",
|
||||
"typescript": "^3.7.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -21,10 +21,10 @@ import useSyncState from './useSyncState';
|
||||
type State = NavigationState | PartialState<NavigationState> | undefined;
|
||||
|
||||
const MISSING_CONTEXT_ERROR =
|
||||
"We couldn't find a navigation context. Have you wrapped your app with 'NavigationContainer'? See https://reactnavigation.org/docs/en/getting-started.html for setup instructions.";
|
||||
"Couldn't find a navigation context. Have you wrapped your app with 'NavigationContainer'? See https://reactnavigation.org/docs/getting-started for setup instructions.";
|
||||
|
||||
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/en/navigating-without-navigation-prop.html#handling-initialization for more details.";
|
||||
"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 const NavigationStateContext = React.createContext<{
|
||||
isDefault?: true;
|
||||
@@ -73,7 +73,7 @@ const getPartialState = (
|
||||
return {
|
||||
...partialState,
|
||||
stale: true,
|
||||
routes: state.routes.map(route => {
|
||||
routes: state.routes.map((route) => {
|
||||
if (route.state === undefined) {
|
||||
return route as Route<string> & {
|
||||
state?: PartialState<NavigationState>;
|
||||
@@ -136,6 +136,7 @@ const BaseNavigationContainer = React.forwardRef(
|
||||
);
|
||||
|
||||
const { trackState, trackAction } = useDevTools({
|
||||
enabled: false,
|
||||
name: '@react-navigation',
|
||||
reset,
|
||||
state,
|
||||
@@ -155,7 +156,7 @@ const BaseNavigationContainer = React.forwardRef(
|
||||
throw new Error(NOT_INITIALIZED_ERROR);
|
||||
}
|
||||
|
||||
listeners[0](navigation => navigation.dispatch(action));
|
||||
listeners[0]((navigation) => navigation.dispatch(action));
|
||||
};
|
||||
|
||||
const canGoBack = () => {
|
||||
@@ -163,7 +164,7 @@ const BaseNavigationContainer = React.forwardRef(
|
||||
return false;
|
||||
}
|
||||
|
||||
const { result, handled } = listeners[0](navigation =>
|
||||
const { result, handled } = listeners[0]((navigation) =>
|
||||
navigation.canGoBack()
|
||||
);
|
||||
|
||||
@@ -238,7 +239,7 @@ const BaseNavigationContainer = React.forwardRef(
|
||||
hasWarnedForSerialization = true;
|
||||
|
||||
console.warn(
|
||||
"We found non-serializable values in the navigation state, which can break usage such as persisting and restoring state. This might happen if you passed non-serializable values such as function, class instances etc. in params. If you need to use components with callbacks in your options, you can use 'navigation.setOptions' instead. See https://reactnavigation.org/docs/en/troubleshooting.html#i-get-the-warning-we-found-non-serializable-values-in-the-navigation-state for more details."
|
||||
"Non-serializable values were found in the navigation state, which can break usage such as persisting and restoring state. This might happen if you passed non-serializable values such as function, class instances etc. in params. If you need to use components with callbacks in your options, you can use 'navigation.setOptions' instead. See https://reactnavigation.org/docs/troubleshooting#i-get-the-warning-non-serializable-values-were-found-in-the-navigation-state for more details."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ type Props = {
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
const MULTIPLE_NAVIGATOR_ERROR = `Another navigator is already registered for this container. You likely have multiple navigators under a single "NavigationContainer" or "Screen". Make sure each navigator is under a separate "Screen" container. See https://reactnavigation.org/docs/en/nesting-navigators.html for a guide on nesting.`;
|
||||
const MULTIPLE_NAVIGATOR_ERROR = `Another navigator is already registered for this container. You likely have multiple navigators under a single "NavigationContainer" or "Screen". Make sure each navigator is under a separate "Screen" container. See https://reactnavigation.org/docs/nesting-navigators for a guide on nesting.`;
|
||||
|
||||
export const SingleNavigatorContext = React.createContext<
|
||||
| {
|
||||
|
||||
@@ -10,10 +10,14 @@ import NavigationContext from './NavigationContext';
|
||||
import NavigationRouteContext from './NavigationRouteContext';
|
||||
import StaticContainer from './StaticContainer';
|
||||
import EnsureSingleNavigator from './EnsureSingleNavigator';
|
||||
import { NavigationProp, RouteConfig } from './types';
|
||||
import { NavigationProp, RouteConfig, EventMapBase } from './types';
|
||||
|
||||
type Props<State extends NavigationState, ScreenOptions extends object> = {
|
||||
screen: RouteConfig<ParamListBase, string, ScreenOptions>;
|
||||
type Props<
|
||||
State extends NavigationState,
|
||||
ScreenOptions extends object,
|
||||
EventMap extends EventMapBase
|
||||
> = {
|
||||
screen: RouteConfig<ParamListBase, string, State, ScreenOptions, EventMap>;
|
||||
navigation: NavigationProp<ParamListBase, string, State, ScreenOptions>;
|
||||
route: Route<string> & {
|
||||
state?: NavigationState | PartialState<NavigationState>;
|
||||
@@ -28,14 +32,15 @@ type Props<State extends NavigationState, ScreenOptions extends object> = {
|
||||
*/
|
||||
export default function SceneView<
|
||||
State extends NavigationState,
|
||||
ScreenOptions extends object
|
||||
ScreenOptions extends object,
|
||||
EventMap extends EventMapBase
|
||||
>({
|
||||
screen,
|
||||
route,
|
||||
navigation,
|
||||
getState,
|
||||
setState,
|
||||
}: Props<State, ScreenOptions>) {
|
||||
}: Props<State, ScreenOptions, EventMap>) {
|
||||
const navigatorKeyRef = React.useRef<string | undefined>();
|
||||
|
||||
const getKey = React.useCallback(() => navigatorKeyRef.current, []);
|
||||
@@ -46,7 +51,7 @@ export default function SceneView<
|
||||
|
||||
const getCurrentState = React.useCallback(() => {
|
||||
const state = getState();
|
||||
const currentRoute = state.routes.find(r => r.key === route.key);
|
||||
const currentRoute = state.routes.find((r) => r.key === route.key);
|
||||
|
||||
return currentRoute ? currentRoute.state : undefined;
|
||||
}, [getState, route.key]);
|
||||
@@ -57,7 +62,7 @@ export default function SceneView<
|
||||
|
||||
setState({
|
||||
...state,
|
||||
routes: state.routes.map(r =>
|
||||
routes: state.routes.map((r) =>
|
||||
r.key === route.key ? { ...r, state: child } : r
|
||||
),
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ParamListBase } from '@react-navigation/routers';
|
||||
import { RouteConfig } from './types';
|
||||
import { ParamListBase, NavigationState } from '@react-navigation/routers';
|
||||
import { RouteConfig, EventMapBase } from './types';
|
||||
|
||||
/**
|
||||
* Empty component used for specifying route configuration.
|
||||
@@ -7,8 +7,10 @@ import { RouteConfig } from './types';
|
||||
export default function Screen<
|
||||
ParamList extends ParamListBase,
|
||||
RouteName extends keyof ParamList,
|
||||
ScreenOptions extends object
|
||||
>(_: RouteConfig<ParamList, RouteName, ScreenOptions>) {
|
||||
State extends NavigationState,
|
||||
ScreenOptions extends object,
|
||||
EventMap extends EventMapBase
|
||||
>(_: RouteConfig<ParamList, RouteName, State, ScreenOptions, EventMap>) {
|
||||
/* istanbul ignore next */
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ it('throws when getState is accessed without a container', () => {
|
||||
const element = <Test />;
|
||||
|
||||
expect(() => render(element).update(element)).toThrowError(
|
||||
"We couldn't find a navigation context. Have you wrapped your app with 'NavigationContainer'?"
|
||||
"Couldn't find a navigation context. Have you wrapped your app with 'NavigationContainer'?"
|
||||
);
|
||||
});
|
||||
|
||||
@@ -47,7 +47,7 @@ it('throws when setState is accessed without a container', () => {
|
||||
const element = <Test />;
|
||||
|
||||
expect(() => render(element).update(element)).toThrowError(
|
||||
"We couldn't find a navigation context. Have you wrapped your app with 'NavigationContainer'?"
|
||||
"Couldn't find a navigation context. Have you wrapped your app with 'NavigationContainer'?"
|
||||
);
|
||||
});
|
||||
|
||||
@@ -122,7 +122,7 @@ it('handle dispatching with ref', () => {
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{state.routes.map(route => descriptors[route.key].render())}
|
||||
{state.routes.map((route) => descriptors[route.key].render())}
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
@@ -220,7 +220,7 @@ it('handle resetting state with ref', () => {
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{state.routes.map(route => descriptors[route.key].render())}
|
||||
{state.routes.map((route) => descriptors[route.key].render())}
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
@@ -371,7 +371,7 @@ it('emits state events when the state changes', () => {
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{state.routes.map(route => descriptors[route.key].render())}
|
||||
{state.routes.map((route) => descriptors[route.key].render())}
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -27,7 +27,7 @@ export default function MockRouter(options: DefaultRouterOptions) {
|
||||
key: String(MockRouterKey.current++),
|
||||
index,
|
||||
routeNames,
|
||||
routes: routeNames.map(name => ({
|
||||
routes: routeNames.map((name) => ({
|
||||
name,
|
||||
key: name,
|
||||
params: routeParamList[name],
|
||||
@@ -43,9 +43,9 @@ export default function MockRouter(options: DefaultRouterOptions) {
|
||||
}
|
||||
|
||||
const routes = state.routes
|
||||
.filter(route => routeNames.includes(route.name))
|
||||
.filter((route) => routeNames.includes(route.name))
|
||||
.map(
|
||||
route =>
|
||||
(route) =>
|
||||
({
|
||||
...route,
|
||||
key: route.key || `${route.name}-${MockRouterKey.current++}`,
|
||||
@@ -73,7 +73,7 @@ export default function MockRouter(options: DefaultRouterOptions) {
|
||||
},
|
||||
|
||||
getStateForRouteNamesChange(state, { routeNames }) {
|
||||
const routes = state.routes.filter(route =>
|
||||
const routes = state.routes.filter((route) =>
|
||||
routeNames.includes(route.name)
|
||||
);
|
||||
|
||||
@@ -86,7 +86,7 @@ export default function MockRouter(options: DefaultRouterOptions) {
|
||||
},
|
||||
|
||||
getStateForRouteFocus(state, key) {
|
||||
const index = state.routes.findIndex(r => r.key === key);
|
||||
const index = state.routes.findIndex((r) => r.key === key);
|
||||
|
||||
if (index === -1 || index === state.index) {
|
||||
return state;
|
||||
@@ -105,7 +105,7 @@ export default function MockRouter(options: DefaultRouterOptions) {
|
||||
|
||||
case 'NAVIGATE': {
|
||||
const index = state.routes.findIndex(
|
||||
route => route.name === action.payload.name
|
||||
(route) => route.name === action.payload.name
|
||||
);
|
||||
|
||||
if (index === -1) {
|
||||
|
||||
@@ -42,7 +42,8 @@ it('converts state to path string with config', () => {
|
||||
Baz: {
|
||||
path: 'baz/:author',
|
||||
parse: {
|
||||
author: (author: string) => author.replace(/^\w/, c => c.toUpperCase()),
|
||||
author: (author: string) =>
|
||||
author.replace(/^\w/, (c) => c.toUpperCase()),
|
||||
id: (id: string) => Number(id.replace(/^x/, '')),
|
||||
valid: Boolean,
|
||||
},
|
||||
@@ -128,7 +129,8 @@ it('handles state with config with nested screens', () => {
|
||||
Baz: {
|
||||
path: 'baz/:author',
|
||||
parse: {
|
||||
author: (author: string) => author.replace(/^\w/, c => c.toUpperCase()),
|
||||
author: (author: string) =>
|
||||
author.replace(/^\w/, (c) => c.toUpperCase()),
|
||||
count: Number,
|
||||
valid: Boolean,
|
||||
},
|
||||
@@ -192,12 +194,14 @@ it('handles state with config with nested screens and unused configs', () => {
|
||||
Baz: {
|
||||
path: 'baz/:author',
|
||||
parse: {
|
||||
author: (author: string) => author.replace(/^\w/, c => c.toUpperCase()),
|
||||
author: (author: string) =>
|
||||
author.replace(/^\w/, (c) => c.toUpperCase()),
|
||||
count: Number,
|
||||
valid: Boolean,
|
||||
},
|
||||
stringify: {
|
||||
author: (author: string) => author.replace(/^\w/, c => c.toLowerCase()),
|
||||
author: (author: string) =>
|
||||
author.replace(/^\w/, (c) => c.toLowerCase()),
|
||||
unknown: (_: unknown) => 'x',
|
||||
},
|
||||
},
|
||||
@@ -255,11 +259,11 @@ it('handles nested object with stringify in it', () => {
|
||||
path: 'bis/:author',
|
||||
stringify: {
|
||||
author: (author: string) =>
|
||||
author.replace(/^\w/, c => c.toLowerCase()),
|
||||
author.replace(/^\w/, (c) => c.toLowerCase()),
|
||||
},
|
||||
parse: {
|
||||
author: (author: string) =>
|
||||
author.replace(/^\w/, c => c.toUpperCase()),
|
||||
author.replace(/^\w/, (c) => c.toUpperCase()),
|
||||
count: Number,
|
||||
valid: Boolean,
|
||||
},
|
||||
|
||||
@@ -42,7 +42,8 @@ it('converts path string to initial state with config', () => {
|
||||
Baz: {
|
||||
path: 'baz/:author',
|
||||
parse: {
|
||||
author: (author: string) => author.replace(/^\w/, c => c.toUpperCase()),
|
||||
author: (author: string) =>
|
||||
author.replace(/^\w/, (c) => c.toUpperCase()),
|
||||
count: Number,
|
||||
valid: Boolean,
|
||||
},
|
||||
@@ -153,7 +154,8 @@ it('converts path string to initial state with config with nested screens', () =
|
||||
Baz: {
|
||||
path: 'baz/:author',
|
||||
parse: {
|
||||
author: (author: string) => author.replace(/^\w/, c => c.toUpperCase()),
|
||||
author: (author: string) =>
|
||||
author.replace(/^\w/, (c) => c.toUpperCase()),
|
||||
count: Number,
|
||||
valid: Boolean,
|
||||
},
|
||||
@@ -217,7 +219,8 @@ it('converts path string to initial state with config with nested screens and un
|
||||
Baz: {
|
||||
path: 'baz/:author',
|
||||
parse: {
|
||||
author: (author: string) => author.replace(/^\w/, c => c.toUpperCase()),
|
||||
author: (author: string) =>
|
||||
author.replace(/^\w/, (c) => c.toUpperCase()),
|
||||
count: Number,
|
||||
valid: Boolean,
|
||||
id: Boolean,
|
||||
@@ -277,11 +280,11 @@ it('handles nested object with unused configs and with parse in it', () => {
|
||||
path: 'bis/:author',
|
||||
stringify: {
|
||||
author: (author: string) =>
|
||||
author.replace(/^\w/, c => c.toLowerCase()),
|
||||
author.replace(/^\w/, (c) => c.toLowerCase()),
|
||||
},
|
||||
parse: {
|
||||
author: (author: string) =>
|
||||
author.replace(/^\w/, c => c.toUpperCase()),
|
||||
author.replace(/^\w/, (c) => c.toUpperCase()),
|
||||
count: Number,
|
||||
valid: Boolean,
|
||||
},
|
||||
@@ -528,11 +531,11 @@ it('handles two initialRouteNames', () => {
|
||||
path: 'bis/:author',
|
||||
stringify: {
|
||||
author: (author: string) =>
|
||||
author.replace(/^\w/, c => c.toLowerCase()),
|
||||
author.replace(/^\w/, (c) => c.toLowerCase()),
|
||||
},
|
||||
parse: {
|
||||
author: (author: string) =>
|
||||
author.replace(/^\w/, c => c.toUpperCase()),
|
||||
author.replace(/^\w/, (c) => c.toUpperCase()),
|
||||
count: Number,
|
||||
valid: Boolean,
|
||||
},
|
||||
@@ -610,11 +613,11 @@ it('accepts initialRouteName without config for it', () => {
|
||||
path: 'bis/:author',
|
||||
stringify: {
|
||||
author: (author: string) =>
|
||||
author.replace(/^\w/, c => c.toLowerCase()),
|
||||
author.replace(/^\w/, (c) => c.toLowerCase()),
|
||||
},
|
||||
parse: {
|
||||
author: (author: string) =>
|
||||
author.replace(/^\w/, c => c.toUpperCase()),
|
||||
author.replace(/^\w/, (c) => c.toUpperCase()),
|
||||
count: Number,
|
||||
valid: Boolean,
|
||||
},
|
||||
|
||||
@@ -372,7 +372,7 @@ it("doesn't update state if action wasn't handled", () => {
|
||||
expect(onStateChange).toBeCalledTimes(0);
|
||||
|
||||
expect(spy.mock.calls[0][0]).toMatch(
|
||||
"The action 'INVALID' with payload 'undefined' was not handled by any navigator."
|
||||
"The action 'INVALID' was not handled by any navigator."
|
||||
);
|
||||
|
||||
spy.mockRestore();
|
||||
@@ -1085,7 +1085,7 @@ it('throws descriptive error for invalid screen component', () => {
|
||||
);
|
||||
|
||||
expect(() => render(element).update(element)).toThrowError(
|
||||
"Got an invalid value for 'component' prop for the screen 'foo'. It must be a a valid React Component."
|
||||
"Got an invalid value for 'component' prop for the screen 'foo'. It must be a valid React Component."
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -119,7 +119,7 @@ it('sets options with screenOptions prop as an object', () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
{state.routes.map(route => {
|
||||
{state.routes.map((route) => {
|
||||
const { render, options } = descriptors[route.key];
|
||||
|
||||
return (
|
||||
@@ -179,7 +179,7 @@ it('sets options with screenOptions prop as a fuction', () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
{state.routes.map(route => {
|
||||
{state.routes.map((route) => {
|
||||
const { render, options } = descriptors[route.key];
|
||||
|
||||
return (
|
||||
@@ -273,7 +273,7 @@ it('sets initial options with setOptions', () => {
|
||||
<BaseNavigationContainer>
|
||||
<TestNavigator>
|
||||
<Screen name="foo" options={{ color: 'blue' }}>
|
||||
{props => <TestScreen {...props} />}
|
||||
{(props) => <TestScreen {...props} />}
|
||||
</Screen>
|
||||
<Screen name="bar" component={jest.fn()} />
|
||||
</TestNavigator>
|
||||
@@ -338,7 +338,7 @@ it('updates options with setOptions', () => {
|
||||
<BaseNavigationContainer>
|
||||
<TestNavigator>
|
||||
<Screen name="foo" options={{ color: 'blue' }}>
|
||||
{props => <TestScreen {...props} />}
|
||||
{(props) => <TestScreen {...props} />}
|
||||
</Screen>
|
||||
<Screen name="bar" component={jest.fn()} />
|
||||
</TestNavigator>
|
||||
|
||||
@@ -15,7 +15,7 @@ it('fires focus and blur events in root navigator', () => {
|
||||
|
||||
React.useImperativeHandle(ref, () => navigation, [navigation]);
|
||||
|
||||
return state.routes.map(route => descriptors[route.key].render());
|
||||
return state.routes.map((route) => descriptors[route.key].render());
|
||||
});
|
||||
|
||||
const firstFocusCallback = jest.fn();
|
||||
@@ -106,7 +106,7 @@ it('fires focus and blur events in nested navigator', () => {
|
||||
|
||||
React.useImperativeHandle(ref, () => navigation, [navigation]);
|
||||
|
||||
return state.routes.map(route => descriptors[route.key].render());
|
||||
return state.routes.map((route) => descriptors[route.key].render());
|
||||
});
|
||||
|
||||
const firstFocusCallback = jest.fn();
|
||||
@@ -362,7 +362,7 @@ it('fires blur event when a route is removed with a delay', async () => {
|
||||
expect(blurCallback).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
it('fires custom events', () => {
|
||||
it('fires custom events added with addListener', () => {
|
||||
const eventName = 'someSuperCoolEvent';
|
||||
|
||||
const TestNavigator = React.forwardRef((props: any, ref: any): any => {
|
||||
@@ -376,7 +376,7 @@ it('fires custom events', () => {
|
||||
state,
|
||||
]);
|
||||
|
||||
return state.routes.map(route => descriptors[route.key].render());
|
||||
return state.routes.map((route) => descriptors[route.key].render());
|
||||
});
|
||||
|
||||
const firstCallback = jest.fn();
|
||||
@@ -409,10 +409,13 @@ it('fires custom events', () => {
|
||||
expect(secondCallback).toBeCalledTimes(0);
|
||||
expect(thirdCallback).toBeCalledTimes(0);
|
||||
|
||||
const target =
|
||||
ref.current.state.routes[ref.current.state.routes.length - 1].key;
|
||||
|
||||
act(() => {
|
||||
ref.current.navigation.emit({
|
||||
type: eventName,
|
||||
target: ref.current.state.routes[ref.current.state.routes.length - 1].key,
|
||||
target,
|
||||
data: 42,
|
||||
});
|
||||
});
|
||||
@@ -422,6 +425,7 @@ it('fires custom events', () => {
|
||||
expect(thirdCallback).toBeCalledTimes(1);
|
||||
expect(thirdCallback.mock.calls[0][0].type).toBe('someSuperCoolEvent');
|
||||
expect(thirdCallback.mock.calls[0][0].data).toBe(42);
|
||||
expect(thirdCallback.mock.calls[0][0].target).toBe(target);
|
||||
expect(thirdCallback.mock.calls[0][0].defaultPrevented).toBe(undefined);
|
||||
expect(thirdCallback.mock.calls[0][0].preventDefault).toBe(undefined);
|
||||
|
||||
@@ -429,11 +433,280 @@ it('fires custom events', () => {
|
||||
ref.current.navigation.emit({ type: eventName });
|
||||
});
|
||||
|
||||
expect(firstCallback.mock.calls[0][0].target).toBe(undefined);
|
||||
expect(secondCallback.mock.calls[0][0].target).toBe(undefined);
|
||||
expect(thirdCallback.mock.calls[1][0].target).toBe(undefined);
|
||||
|
||||
expect(firstCallback).toBeCalledTimes(1);
|
||||
expect(secondCallback).toBeCalledTimes(1);
|
||||
expect(thirdCallback).toBeCalledTimes(2);
|
||||
});
|
||||
|
||||
it("doesn't call same listener multiple times with addListener", () => {
|
||||
const eventName = 'someSuperCoolEvent';
|
||||
|
||||
const TestNavigator = React.forwardRef((props: any, ref: any): any => {
|
||||
const { state, navigation, descriptors } = useNavigationBuilder(
|
||||
MockRouter,
|
||||
props
|
||||
);
|
||||
|
||||
React.useImperativeHandle(ref, () => ({ navigation, state }), [
|
||||
navigation,
|
||||
state,
|
||||
]);
|
||||
|
||||
return state.routes.map((route) => descriptors[route.key].render());
|
||||
});
|
||||
|
||||
const callback = jest.fn();
|
||||
|
||||
const Test = ({ navigation }: any) => {
|
||||
React.useEffect(() => navigation.addListener(eventName, callback), [
|
||||
navigation,
|
||||
]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const ref = React.createRef<any>();
|
||||
|
||||
const element = (
|
||||
<BaseNavigationContainer>
|
||||
<TestNavigator ref={ref}>
|
||||
<Screen name="first" component={Test} />
|
||||
<Screen name="second" component={Test} />
|
||||
<Screen name="third" component={Test} />
|
||||
</TestNavigator>
|
||||
</BaseNavigationContainer>
|
||||
);
|
||||
|
||||
render(element);
|
||||
|
||||
expect(callback).toBeCalledTimes(0);
|
||||
|
||||
act(() => {
|
||||
ref.current.navigation.emit({ type: eventName });
|
||||
});
|
||||
|
||||
expect(callback).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
it('fires custom events added with listeners prop', () => {
|
||||
const eventName = 'someSuperCoolEvent';
|
||||
|
||||
const TestNavigator = React.forwardRef((props: any, ref: any): any => {
|
||||
const { state, navigation } = useNavigationBuilder(MockRouter, props);
|
||||
|
||||
React.useImperativeHandle(ref, () => ({ navigation, state }), [
|
||||
navigation,
|
||||
state,
|
||||
]);
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
const firstCallback = jest.fn();
|
||||
const secondCallback = jest.fn();
|
||||
const thirdCallback = jest.fn();
|
||||
|
||||
const ref = React.createRef<any>();
|
||||
|
||||
const element = (
|
||||
<BaseNavigationContainer>
|
||||
<TestNavigator ref={ref}>
|
||||
<Screen
|
||||
name="first"
|
||||
listeners={{ someSuperCoolEvent: firstCallback }}
|
||||
component={jest.fn()}
|
||||
/>
|
||||
<Screen
|
||||
name="second"
|
||||
listeners={{ someSuperCoolEvent: secondCallback }}
|
||||
component={jest.fn()}
|
||||
/>
|
||||
<Screen
|
||||
name="third"
|
||||
listeners={{ someSuperCoolEvent: thirdCallback }}
|
||||
component={jest.fn()}
|
||||
/>
|
||||
</TestNavigator>
|
||||
</BaseNavigationContainer>
|
||||
);
|
||||
|
||||
render(element);
|
||||
|
||||
expect(firstCallback).toBeCalledTimes(0);
|
||||
expect(secondCallback).toBeCalledTimes(0);
|
||||
expect(thirdCallback).toBeCalledTimes(0);
|
||||
|
||||
const target =
|
||||
ref.current.state.routes[ref.current.state.routes.length - 1].key;
|
||||
|
||||
act(() => {
|
||||
ref.current.navigation.emit({
|
||||
type: eventName,
|
||||
target,
|
||||
data: 42,
|
||||
});
|
||||
});
|
||||
|
||||
expect(firstCallback).toBeCalledTimes(0);
|
||||
expect(secondCallback).toBeCalledTimes(0);
|
||||
expect(thirdCallback).toBeCalledTimes(1);
|
||||
expect(thirdCallback.mock.calls[0][0].type).toBe('someSuperCoolEvent');
|
||||
expect(thirdCallback.mock.calls[0][0].data).toBe(42);
|
||||
expect(thirdCallback.mock.calls[0][0].target).toBe(target);
|
||||
expect(thirdCallback.mock.calls[0][0].defaultPrevented).toBe(undefined);
|
||||
expect(thirdCallback.mock.calls[0][0].preventDefault).toBe(undefined);
|
||||
|
||||
act(() => {
|
||||
ref.current.navigation.emit({ type: eventName });
|
||||
});
|
||||
|
||||
expect(firstCallback.mock.calls[0][0].target).toBe(undefined);
|
||||
|
||||
expect(firstCallback).toBeCalledTimes(1);
|
||||
expect(secondCallback).toBeCalledTimes(0);
|
||||
expect(thirdCallback).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
it("doesn't call same listener multiple times with listeners", () => {
|
||||
const eventName = 'someSuperCoolEvent';
|
||||
|
||||
const TestNavigator = React.forwardRef((props: any, ref: any): any => {
|
||||
const { state, navigation } = useNavigationBuilder(MockRouter, props);
|
||||
|
||||
React.useImperativeHandle(ref, () => ({ navigation, state }), [
|
||||
navigation,
|
||||
state,
|
||||
]);
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
const callback = jest.fn();
|
||||
|
||||
const ref = React.createRef<any>();
|
||||
|
||||
const element = (
|
||||
<BaseNavigationContainer>
|
||||
<TestNavigator ref={ref}>
|
||||
<Screen
|
||||
name="first"
|
||||
listeners={{ someSuperCoolEvent: callback }}
|
||||
component={jest.fn()}
|
||||
/>
|
||||
<Screen
|
||||
name="second"
|
||||
listeners={{ someSuperCoolEvent: callback }}
|
||||
component={jest.fn()}
|
||||
/>
|
||||
<Screen
|
||||
name="third"
|
||||
listeners={{ someSuperCoolEvent: callback }}
|
||||
component={jest.fn()}
|
||||
/>
|
||||
</TestNavigator>
|
||||
</BaseNavigationContainer>
|
||||
);
|
||||
|
||||
render(element);
|
||||
|
||||
expect(callback).toBeCalledTimes(0);
|
||||
|
||||
act(() => {
|
||||
ref.current.navigation.emit({ type: eventName });
|
||||
});
|
||||
|
||||
expect(callback).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
it('fires listeners when callback is provided for listeners prop', () => {
|
||||
const eventName = 'someSuperCoolEvent';
|
||||
|
||||
const TestNavigator = React.forwardRef((props: any, ref: any): any => {
|
||||
const { state, navigation } = useNavigationBuilder(MockRouter, props);
|
||||
|
||||
React.useImperativeHandle(ref, () => ({ navigation, state }), [
|
||||
navigation,
|
||||
state,
|
||||
]);
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
const firstCallback = jest.fn();
|
||||
const secondCallback = jest.fn();
|
||||
const thirdCallback = jest.fn();
|
||||
|
||||
const ref = React.createRef<any>();
|
||||
|
||||
const element = (
|
||||
<BaseNavigationContainer>
|
||||
<TestNavigator ref={ref}>
|
||||
<Screen
|
||||
name="first"
|
||||
listeners={({ route, navigation }) => ({
|
||||
someSuperCoolEvent: (e) => firstCallback(e, route, navigation),
|
||||
})}
|
||||
component={jest.fn()}
|
||||
/>
|
||||
<Screen
|
||||
name="second"
|
||||
listeners={({ route, navigation }) => ({
|
||||
someSuperCoolEvent: (e) => secondCallback(e, route, navigation),
|
||||
})}
|
||||
component={jest.fn()}
|
||||
/>
|
||||
<Screen
|
||||
name="third"
|
||||
listeners={({ route, navigation }) => ({
|
||||
someSuperCoolEvent: (e) => thirdCallback(e, route, navigation),
|
||||
})}
|
||||
component={jest.fn()}
|
||||
/>
|
||||
</TestNavigator>
|
||||
</BaseNavigationContainer>
|
||||
);
|
||||
|
||||
render(element);
|
||||
|
||||
expect(firstCallback).toBeCalledTimes(0);
|
||||
expect(secondCallback).toBeCalledTimes(0);
|
||||
expect(thirdCallback).toBeCalledTimes(0);
|
||||
|
||||
const target =
|
||||
ref.current.state.routes[ref.current.state.routes.length - 1].key;
|
||||
|
||||
act(() => {
|
||||
ref.current.navigation.emit({
|
||||
type: eventName,
|
||||
target,
|
||||
data: 42,
|
||||
});
|
||||
});
|
||||
|
||||
expect(firstCallback).toBeCalledTimes(0);
|
||||
expect(secondCallback).toBeCalledTimes(0);
|
||||
expect(thirdCallback).toBeCalledTimes(1);
|
||||
expect(thirdCallback.mock.calls[0][0].type).toBe('someSuperCoolEvent');
|
||||
expect(thirdCallback.mock.calls[0][0].data).toBe(42);
|
||||
expect(thirdCallback.mock.calls[0][0].target).toBe(target);
|
||||
expect(thirdCallback.mock.calls[0][0].defaultPrevented).toBe(undefined);
|
||||
expect(thirdCallback.mock.calls[0][0].preventDefault).toBe(undefined);
|
||||
|
||||
act(() => {
|
||||
ref.current.navigation.emit({ type: eventName });
|
||||
});
|
||||
|
||||
expect(firstCallback.mock.calls[0][0].target).toBe(undefined);
|
||||
|
||||
expect(firstCallback).toBeCalledTimes(1);
|
||||
expect(secondCallback).toBeCalledTimes(0);
|
||||
expect(thirdCallback).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
it('has option to prevent default', () => {
|
||||
expect.assertions(5);
|
||||
|
||||
@@ -450,7 +723,7 @@ it('has option to prevent default', () => {
|
||||
state,
|
||||
]);
|
||||
|
||||
return state.routes.map(route => descriptors[route.key].render());
|
||||
return state.routes.map((route) => descriptors[route.key].render());
|
||||
});
|
||||
|
||||
const callback = (e: any) => {
|
||||
|
||||
@@ -10,7 +10,7 @@ it('runs focus effect on focus change', () => {
|
||||
const TestNavigator = (props: any): any => {
|
||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||
|
||||
return state.routes.map(route => descriptors[route.key].render());
|
||||
return state.routes.map((route) => descriptors[route.key].render());
|
||||
};
|
||||
|
||||
const focusEffect = jest.fn();
|
||||
@@ -107,7 +107,7 @@ it('runs focus effect when initial state is given', () => {
|
||||
const TestNavigator = (props: any): any => {
|
||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||
|
||||
return state.routes.map(route => descriptors[route.key].render());
|
||||
return state.routes.map((route) => descriptors[route.key].render());
|
||||
};
|
||||
|
||||
const focusEffect = jest.fn();
|
||||
|
||||
@@ -10,7 +10,7 @@ it('renders correct focus state', () => {
|
||||
const TestNavigator = (props: any): any => {
|
||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||
|
||||
return state.routes.map(route => descriptors[route.key].render());
|
||||
return state.routes.map((route) => descriptors[route.key].render());
|
||||
};
|
||||
|
||||
const Test = () => {
|
||||
|
||||
@@ -12,7 +12,7 @@ it('gets navigation prop from context', () => {
|
||||
const TestNavigator = (props: any): any => {
|
||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||
|
||||
return state.routes.map(route => descriptors[route.key].render());
|
||||
return state.routes.map((route) => descriptors[route.key].render());
|
||||
};
|
||||
|
||||
const Test = () => {
|
||||
@@ -38,7 +38,7 @@ it("gets navigation's parent from context", () => {
|
||||
const TestNavigator = (props: any): any => {
|
||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||
|
||||
return state.routes.map(route => descriptors[route.key].render());
|
||||
return state.routes.map((route) => descriptors[route.key].render());
|
||||
};
|
||||
|
||||
const Test = () => {
|
||||
@@ -70,7 +70,7 @@ it("gets navigation's parent's parent from context", () => {
|
||||
const TestNavigator = (props: any): any => {
|
||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||
|
||||
return state.routes.map(route => descriptors[route.key].render());
|
||||
return state.routes.map((route) => descriptors[route.key].render());
|
||||
};
|
||||
|
||||
const Test = () => {
|
||||
@@ -112,7 +112,7 @@ it('throws if called outside a navigation context', () => {
|
||||
const Test = () => {
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
expect(() => useNavigation()).toThrow(
|
||||
"We couldn't find a navigation object. Is your component inside a screen in a navigator?"
|
||||
"Couldn't find a navigation object. Is your component inside a screen in a navigator?"
|
||||
);
|
||||
|
||||
return null;
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import * as React from 'react';
|
||||
import { render } from 'react-native-testing-library';
|
||||
import { render, act } from 'react-native-testing-library';
|
||||
import useEventEmitter from '../useEventEmitter';
|
||||
import useNavigationCache from '../useNavigationCache';
|
||||
import useNavigationBuilder from '../useNavigationBuilder';
|
||||
import BaseNavigationContainer from '../BaseNavigationContainer';
|
||||
import Screen from '../Screen';
|
||||
import MockRouter, { MockRouterKey } from './__fixtures__/MockRouter';
|
||||
|
||||
beforeEach(() => (MockRouterKey.current = 0));
|
||||
@@ -40,7 +43,7 @@ it('preserves reference for navigation objects', () => {
|
||||
});
|
||||
|
||||
if (previous.current) {
|
||||
Object.keys(navigations).forEach(key => {
|
||||
Object.keys(navigations).forEach((key) => {
|
||||
expect(navigations[key]).toBe(previous.current[key]);
|
||||
});
|
||||
}
|
||||
@@ -56,3 +59,136 @@ it('preserves reference for navigation objects', () => {
|
||||
|
||||
root.update(<Test />);
|
||||
});
|
||||
|
||||
it('returns correct value for isFocused', () => {
|
||||
const TestNavigator = (props: any): any => {
|
||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||
|
||||
return state.routes.map((route) => descriptors[route.key].render());
|
||||
};
|
||||
|
||||
let navigation: any;
|
||||
|
||||
const Test = (props: any) => {
|
||||
navigation = props.navigation;
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
render(
|
||||
<BaseNavigationContainer>
|
||||
<TestNavigator>
|
||||
<Screen name="first">{() => null}</Screen>
|
||||
<Screen name="second" component={Test} />
|
||||
<Screen name="third">{() => null}</Screen>
|
||||
</TestNavigator>
|
||||
</BaseNavigationContainer>
|
||||
);
|
||||
|
||||
expect(navigation.isFocused()).toBe(false);
|
||||
|
||||
act(() => navigation.navigate('second'));
|
||||
|
||||
expect(navigation.isFocused()).toBe(true);
|
||||
|
||||
act(() => navigation.navigate('third'));
|
||||
|
||||
expect(navigation.isFocused()).toBe(false);
|
||||
|
||||
act(() => navigation.navigate('second'));
|
||||
|
||||
expect(navigation.isFocused()).toBe(true);
|
||||
});
|
||||
|
||||
it('returns correct value for isFocused after changing screens', () => {
|
||||
const TestRouter = (
|
||||
options: Parameters<typeof MockRouter>[0]
|
||||
): ReturnType<typeof MockRouter> => {
|
||||
const router = MockRouter(options);
|
||||
|
||||
return {
|
||||
...router,
|
||||
|
||||
getStateForRouteNamesChange(state, { routeNames }) {
|
||||
const routes = routeNames.map(
|
||||
(name) =>
|
||||
state.routes.find((r) => r.name === name) || {
|
||||
name,
|
||||
key: name,
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
...state,
|
||||
routeNames,
|
||||
routes,
|
||||
index: routes.length - 1,
|
||||
};
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const TestNavigator = (props: any): any => {
|
||||
const { state, descriptors } = useNavigationBuilder(TestRouter, props);
|
||||
|
||||
return state.routes.map((route) => descriptors[route.key].render());
|
||||
};
|
||||
|
||||
let navigation: any;
|
||||
|
||||
const Test = (props: any) => {
|
||||
navigation = props.navigation;
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const root = render(
|
||||
<BaseNavigationContainer>
|
||||
<TestNavigator>
|
||||
<Screen name="first">{() => null}</Screen>
|
||||
<Screen name="second" component={Test} />
|
||||
<Screen name="third">{() => null}</Screen>
|
||||
</TestNavigator>
|
||||
</BaseNavigationContainer>
|
||||
);
|
||||
|
||||
expect(navigation.isFocused()).toBe(false);
|
||||
|
||||
root.update(
|
||||
<BaseNavigationContainer>
|
||||
<TestNavigator>
|
||||
<Screen name="first">{() => null}</Screen>
|
||||
<Screen name="third">{() => null}</Screen>
|
||||
<Screen name="second" component={Test} />
|
||||
</TestNavigator>
|
||||
</BaseNavigationContainer>
|
||||
);
|
||||
|
||||
expect(navigation.isFocused()).toBe(true);
|
||||
|
||||
root.update(
|
||||
<BaseNavigationContainer>
|
||||
<TestNavigator>
|
||||
<Screen name="first">{() => null}</Screen>
|
||||
<Screen name="third">{() => null}</Screen>
|
||||
<Screen name="fourth">{() => null}</Screen>
|
||||
<Screen name="second" component={Test} />
|
||||
</TestNavigator>
|
||||
</BaseNavigationContainer>
|
||||
);
|
||||
|
||||
expect(navigation.isFocused()).toBe(true);
|
||||
|
||||
root.update(
|
||||
<BaseNavigationContainer>
|
||||
<TestNavigator>
|
||||
<Screen name="first">{() => null}</Screen>
|
||||
<Screen name="third">{() => null}</Screen>
|
||||
<Screen name="second" component={Test} />
|
||||
<Screen name="fourth">{() => null}</Screen>
|
||||
</TestNavigator>
|
||||
</BaseNavigationContainer>
|
||||
);
|
||||
|
||||
expect(navigation.isFocused()).toBe(false);
|
||||
});
|
||||
|
||||
@@ -11,13 +11,13 @@ it('gets the current navigation state', () => {
|
||||
const TestNavigator = (props: any): any => {
|
||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||
|
||||
return state.routes.map(route => descriptors[route.key].render());
|
||||
return state.routes.map((route) => descriptors[route.key].render());
|
||||
};
|
||||
|
||||
const callback = jest.fn();
|
||||
|
||||
const Test = () => {
|
||||
const state = useNavigationState(state => state);
|
||||
const state = useNavigationState((state) => state);
|
||||
|
||||
callback(state);
|
||||
|
||||
@@ -62,13 +62,13 @@ it('gets the current navigation state with selector', () => {
|
||||
const TestNavigator = (props: any): any => {
|
||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||
|
||||
return state.routes.map(route => descriptors[route.key].render());
|
||||
return state.routes.map((route) => descriptors[route.key].render());
|
||||
};
|
||||
|
||||
const callback = jest.fn();
|
||||
|
||||
const Test = () => {
|
||||
const index = useNavigationState(state => state.index);
|
||||
const index = useNavigationState((state) => state.index);
|
||||
|
||||
callback(index);
|
||||
|
||||
@@ -112,7 +112,7 @@ it('gets the correct value if selector changes', () => {
|
||||
const TestNavigator = (props: any): any => {
|
||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||
|
||||
return state.routes.map(route => descriptors[route.key].render());
|
||||
return state.routes.map((route) => descriptors[route.key].render());
|
||||
};
|
||||
|
||||
const callback = jest.fn();
|
||||
@@ -144,12 +144,12 @@ it('gets the correct value if selector changes', () => {
|
||||
);
|
||||
};
|
||||
|
||||
const root = render(<App selector={state => state.index} />);
|
||||
const root = render(<App selector={(state) => state.index} />);
|
||||
|
||||
expect(callback).toBeCalledTimes(1);
|
||||
expect(callback.mock.calls[0][0]).toBe(0);
|
||||
|
||||
root.update(<App selector={state => state.routes[state.index].name} />);
|
||||
root.update(<App selector={(state) => state.routes[state.index].name} />);
|
||||
|
||||
expect(callback).toBeCalledTimes(2);
|
||||
expect(callback.mock.calls[1][0]).toBe('first');
|
||||
|
||||
@@ -137,7 +137,7 @@ it("lets children handle the action if parent didn't", () => {
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{state.routes.map(route => descriptors[route.key].render())}
|
||||
{state.routes.map((route) => descriptors[route.key].render())}
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
@@ -270,7 +270,7 @@ it("action doesn't bubble if target is specified", () => {
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{state.routes.map(route => descriptors[route.key].render())}
|
||||
{state.routes.map((route) => descriptors[route.key].render())}
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
@@ -317,7 +317,7 @@ it('logs error if no navigator handled the action', () => {
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{state.routes.map(route => descriptors[route.key].render())}
|
||||
{state.routes.map((route) => descriptors[route.key].render())}
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
@@ -374,7 +374,7 @@ it('logs error if no navigator handled the action', () => {
|
||||
render(element).update(element);
|
||||
|
||||
expect(spy.mock.calls[0][0]).toMatch(
|
||||
"The action 'UNKNOWN' with payload 'undefined' was not handled by any navigator."
|
||||
"The action 'UNKNOWN' was not handled by any navigator."
|
||||
);
|
||||
|
||||
spy.mockRestore();
|
||||
|
||||
@@ -13,7 +13,7 @@ it('gets route prop from context', () => {
|
||||
const TestNavigator = (props: any): any => {
|
||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||
|
||||
return state.routes.map(route => descriptors[route.key].render());
|
||||
return state.routes.map((route) => descriptors[route.key].render());
|
||||
};
|
||||
|
||||
const Test = () => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as React from 'react';
|
||||
import { ParamListBase } from '@react-navigation/routers';
|
||||
import { ParamListBase, NavigationState } from '@react-navigation/routers';
|
||||
import Screen from './Screen';
|
||||
import { TypedNavigator } from './types';
|
||||
import { TypedNavigator, EventMapBase } from './types';
|
||||
|
||||
/**
|
||||
* Higher order component to create a `Navigator` and `Screen` pair.
|
||||
@@ -11,17 +11,21 @@ import { TypedNavigator } from './types';
|
||||
* @returns Factory method to create a `Navigator` and `Screen` pair.
|
||||
*/
|
||||
export default function createNavigatorFactory<
|
||||
State extends NavigationState,
|
||||
ScreenOptions extends object,
|
||||
EventMap extends EventMapBase,
|
||||
NavigatorComponent extends React.ComponentType<any>
|
||||
>(Navigator: NavigatorComponent) {
|
||||
return function<ParamList extends ParamListBase>(): TypedNavigator<
|
||||
return function <ParamList extends ParamListBase>(): TypedNavigator<
|
||||
ParamList,
|
||||
State,
|
||||
ScreenOptions,
|
||||
EventMap,
|
||||
typeof Navigator
|
||||
> {
|
||||
if (arguments[0] !== undefined) {
|
||||
throw new Error(
|
||||
"Creating a navigator doesn't take an argument. Maybe you are trying to use React Navigation 4 API with React Navigation 5? See https://reactnavigation.org/docs/en/upgrading-from-4.x.html for migration guide."
|
||||
"Creating a navigator doesn't take an argument. Maybe you are trying to use React Navigation 4 API with React Navigation 5? See https://reactnavigation.org/docs/upgrading-from-4.x for migration guide."
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -125,7 +125,7 @@ export default function getPathFromState(
|
||||
if (currentOptions[route.name] !== undefined) {
|
||||
path += pattern
|
||||
.split('/')
|
||||
.map(p => {
|
||||
.map((p) => {
|
||||
const name = p.replace(/^:/, '');
|
||||
|
||||
// If the path has a pattern for a param, put the param in the path
|
||||
|
||||
@@ -64,7 +64,7 @@ export default function getStateFromPath(
|
||||
let initialRoutes: InitialRouteConfig[] = [];
|
||||
// Create a normalized configs array which will be easier to use
|
||||
const configs = ([] as RouteConfig[]).concat(
|
||||
...Object.keys(options).map(key =>
|
||||
...Object.keys(options).map((key) =>
|
||||
createNormalizedConfigs(key, options, [], initialRoutes)
|
||||
)
|
||||
);
|
||||
@@ -91,7 +91,7 @@ export default function getStateFromPath(
|
||||
|
||||
const paramPatterns = config.pattern
|
||||
.split('/')
|
||||
.filter(p => p.startsWith(':'));
|
||||
.filter((p) => p.startsWith(':'));
|
||||
|
||||
if (paramPatterns.length) {
|
||||
params = paramPatterns.reduce<Record<string, any>>((acc, p, i) => {
|
||||
@@ -188,7 +188,7 @@ export default function getStateFromPath(
|
||||
const parseFunction = findParseConfigForRoute(route.name, configs);
|
||||
|
||||
if (parseFunction) {
|
||||
Object.keys(params).forEach(name => {
|
||||
Object.keys(params).forEach((name) => {
|
||||
if (parseFunction[name] && typeof params[name] === 'string') {
|
||||
params[name] = parseFunction[name](params[name] as string);
|
||||
}
|
||||
@@ -233,7 +233,7 @@ function createNormalizedConfigs(
|
||||
connectedRoutes: Object.keys(value.screens),
|
||||
});
|
||||
}
|
||||
Object.keys(value.screens).forEach(nestedConfig => {
|
||||
Object.keys(value.screens).forEach((nestedConfig) => {
|
||||
const result = createNormalizedConfigs(
|
||||
nestedConfig,
|
||||
value.screens as Options,
|
||||
|
||||
@@ -48,6 +48,7 @@ export type EventArg<
|
||||
* Type of the event (e.g. `focus`, `blur`)
|
||||
*/
|
||||
readonly type: EventName;
|
||||
readonly target?: string;
|
||||
} & (CanPreventDefault extends true
|
||||
? {
|
||||
/**
|
||||
@@ -167,18 +168,6 @@ type NavigationHelpersCommon<
|
||||
| { name: RouteName; key?: string; params: ParamList[RouteName] }
|
||||
): void;
|
||||
|
||||
/**
|
||||
* Replace the current route with a new one.
|
||||
*
|
||||
* @param name Route name of the new route.
|
||||
* @param [params] Params object for the new route.
|
||||
*/
|
||||
replace<RouteName extends keyof ParamList>(
|
||||
...args: ParamList[RouteName] extends undefined
|
||||
? [RouteName] | [RouteName, ParamList[RouteName]]
|
||||
: [RouteName, ParamList[RouteName]]
|
||||
): void;
|
||||
|
||||
/**
|
||||
* Reset the navigation state to the provided state.
|
||||
*
|
||||
@@ -357,10 +346,22 @@ export type Descriptor<
|
||||
>;
|
||||
};
|
||||
|
||||
export type ScreenListeners<
|
||||
State extends NavigationState,
|
||||
EventMap extends EventMapBase
|
||||
> = Partial<
|
||||
{
|
||||
[EventName in keyof (EventMap &
|
||||
EventMapCore<State>)]: EventListenerCallback<EventMap, EventName>;
|
||||
}
|
||||
>;
|
||||
|
||||
export type RouteConfig<
|
||||
ParamList extends ParamListBase,
|
||||
RouteName extends keyof ParamList,
|
||||
ScreenOptions extends object
|
||||
State extends NavigationState,
|
||||
ScreenOptions extends object,
|
||||
EventMap extends EventMapBase
|
||||
> = {
|
||||
/**
|
||||
* Route name of this screen.
|
||||
@@ -377,6 +378,16 @@ export type RouteConfig<
|
||||
navigation: any;
|
||||
}) => ScreenOptions);
|
||||
|
||||
/**
|
||||
* Event listeners for this screen.
|
||||
*/
|
||||
listeners?:
|
||||
| ScreenListeners<State, EventMap>
|
||||
| ((props: {
|
||||
route: RouteProp<ParamList, RouteName>;
|
||||
navigation: any;
|
||||
}) => ScreenListeners<State, EventMap>);
|
||||
|
||||
/**
|
||||
* Initial params object for the route.
|
||||
*/
|
||||
@@ -420,7 +431,9 @@ export type NavigationContainerRef =
|
||||
|
||||
export type TypedNavigator<
|
||||
ParamList extends ParamListBase,
|
||||
State extends NavigationState,
|
||||
ScreenOptions extends object,
|
||||
EventMap extends EventMapBase,
|
||||
Navigator extends React.ComponentType<any>
|
||||
> = {
|
||||
/**
|
||||
@@ -451,6 +464,6 @@ export type TypedNavigator<
|
||||
* Component used for specifying route configuration.
|
||||
*/
|
||||
Screen: <RouteName extends keyof ParamList>(
|
||||
_: RouteConfig<ParamList, RouteName, ScreenOptions>
|
||||
_: RouteConfig<ParamList, RouteName, State, ScreenOptions, EventMap>
|
||||
) => null;
|
||||
};
|
||||
|
||||
@@ -13,11 +13,24 @@ import NavigationBuilderContext, {
|
||||
} from './NavigationBuilderContext';
|
||||
import { NavigationEventEmitter } from './useEventEmitter';
|
||||
import useNavigationCache from './useNavigationCache';
|
||||
import { Descriptor, NavigationHelpers, RouteConfig, RouteProp } from './types';
|
||||
import {
|
||||
Descriptor,
|
||||
NavigationHelpers,
|
||||
RouteConfig,
|
||||
RouteProp,
|
||||
EventMapBase,
|
||||
} from './types';
|
||||
|
||||
type Options<State extends NavigationState, ScreenOptions extends object> = {
|
||||
type Options<
|
||||
State extends NavigationState,
|
||||
ScreenOptions extends object,
|
||||
EventMap extends EventMapBase
|
||||
> = {
|
||||
state: State;
|
||||
screens: Record<string, RouteConfig<ParamListBase, string, ScreenOptions>>;
|
||||
screens: Record<
|
||||
string,
|
||||
RouteConfig<ParamListBase, string, State, ScreenOptions, EventMap>
|
||||
>;
|
||||
navigation: NavigationHelpers<ParamListBase>;
|
||||
screenOptions?:
|
||||
| ScreenOptions
|
||||
@@ -49,7 +62,8 @@ type Options<State extends NavigationState, ScreenOptions extends object> = {
|
||||
*/
|
||||
export default function useDescriptors<
|
||||
State extends NavigationState,
|
||||
ScreenOptions extends object
|
||||
ScreenOptions extends object,
|
||||
EventMap extends EventMapBase
|
||||
>({
|
||||
state,
|
||||
screens,
|
||||
@@ -64,7 +78,7 @@ export default function useDescriptors<
|
||||
onRouteFocus,
|
||||
router,
|
||||
emitter,
|
||||
}: Options<State, ScreenOptions>) {
|
||||
}: Options<State, ScreenOptions, EventMap>) {
|
||||
const [options, setOptions] = React.useState<Record<string, object>>({});
|
||||
const { trackAction } = React.useContext(NavigationBuilderContext);
|
||||
|
||||
@@ -133,6 +147,7 @@ export default function useDescriptors<
|
||||
: screen.options({
|
||||
// @ts-ignore
|
||||
route,
|
||||
// @ts-ignore
|
||||
navigation,
|
||||
})),
|
||||
// The options set via `navigation.setOptions`
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
type State = NavigationState | PartialState<NavigationState> | undefined;
|
||||
|
||||
type Options = {
|
||||
enabled: boolean;
|
||||
name: string;
|
||||
reset: (state: NavigationState) => void;
|
||||
state: State;
|
||||
@@ -35,10 +36,11 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
export default function useDevTools({ name, reset, state }: Options) {
|
||||
export default function useDevTools({ name, reset, state, enabled }: Options) {
|
||||
const devToolsRef = React.useRef<DevTools>();
|
||||
|
||||
if (
|
||||
enabled &&
|
||||
process.env.NODE_ENV !== 'production' &&
|
||||
global.__REDUX_DEVTOOLS_EXTENSION__ &&
|
||||
devToolsRef.current === undefined
|
||||
@@ -56,7 +58,7 @@ export default function useDevTools({ name, reset, state }: Options) {
|
||||
|
||||
React.useEffect(
|
||||
() =>
|
||||
devTools?.subscribe(message => {
|
||||
devTools?.subscribe((message) => {
|
||||
if (message.type === 'DISPATCH' && message.state) {
|
||||
reset(JSON.parse(message.state));
|
||||
}
|
||||
|
||||
@@ -5,12 +5,20 @@ export type NavigationEventEmitter = EventEmitter<Record<string, any>> & {
|
||||
create: (target: string) => EventConsumer<Record<string, any>>;
|
||||
};
|
||||
|
||||
type Listeners = ((data: any) => void)[];
|
||||
type Listeners = ((e: any) => void)[];
|
||||
|
||||
/**
|
||||
* Hook to manage the event system used by the navigator to notify screens of various events.
|
||||
*/
|
||||
export default function useEventEmitter(): NavigationEventEmitter {
|
||||
export default function useEventEmitter(
|
||||
listen?: (e: any) => void
|
||||
): NavigationEventEmitter {
|
||||
const listenRef = React.useRef(listen);
|
||||
|
||||
React.useEffect(() => {
|
||||
listenRef.current = listen;
|
||||
});
|
||||
|
||||
const listeners = React.useRef<Record<string, Record<string, Listeners>>>({});
|
||||
|
||||
const create = React.useCallback((target: string) => {
|
||||
@@ -60,7 +68,9 @@ export default function useEventEmitter(): NavigationEventEmitter {
|
||||
const callbacks =
|
||||
target !== undefined
|
||||
? items[target] && items[target].slice()
|
||||
: ([] as Listeners).concat(...Object.keys(items).map(t => items[t]));
|
||||
: ([] as Listeners)
|
||||
.concat(...Object.keys(items).map((t) => items[t]))
|
||||
.filter((cb, i, self) => self.lastIndexOf(cb) === i);
|
||||
|
||||
const event: EventArg<any, any, any> = {
|
||||
get type() {
|
||||
@@ -68,8 +78,18 @@ export default function useEventEmitter(): NavigationEventEmitter {
|
||||
},
|
||||
};
|
||||
|
||||
if (target !== undefined) {
|
||||
Object.defineProperty(event, 'target', {
|
||||
enumerable: true,
|
||||
get() {
|
||||
return target;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (data !== undefined) {
|
||||
Object.defineProperty(event, 'data', {
|
||||
enumerable: true,
|
||||
get() {
|
||||
return data;
|
||||
},
|
||||
@@ -81,11 +101,13 @@ export default function useEventEmitter(): NavigationEventEmitter {
|
||||
|
||||
Object.defineProperties(event, {
|
||||
defaultPrevented: {
|
||||
enumerable: true,
|
||||
get() {
|
||||
return defaultPrevented;
|
||||
},
|
||||
},
|
||||
preventDefault: {
|
||||
enumerable: true,
|
||||
value() {
|
||||
defaultPrevented = true;
|
||||
},
|
||||
@@ -93,7 +115,9 @@ export default function useEventEmitter(): NavigationEventEmitter {
|
||||
});
|
||||
}
|
||||
|
||||
callbacks?.forEach(cb => cb(event));
|
||||
listenRef.current?.(event);
|
||||
|
||||
callbacks?.forEach((cb) => cb(event));
|
||||
|
||||
return event as any;
|
||||
},
|
||||
|
||||
@@ -46,7 +46,7 @@ export default function useFocusEffect(effect: EffectCallback) {
|
||||
' fetchData();\n' +
|
||||
' }, [someId])\n' +
|
||||
'};\n\n' +
|
||||
'See usage guide: https://reactnavigation.org/docs/en/use-focus-effect.html';
|
||||
'See usage guide: https://reactnavigation.org/docs/use-focus-effect';
|
||||
} else {
|
||||
message += ` You returned: '${JSON.stringify(destroy)}'`;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ export default function useNavigation<
|
||||
|
||||
if (navigation === undefined) {
|
||||
throw new Error(
|
||||
"We couldn't find a navigation object. Is your component inside a screen in a navigator?"
|
||||
"Couldn't find a navigation object. Is your component inside a screen in a navigator?"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
RouterFactory,
|
||||
PartialState,
|
||||
NavigationAction,
|
||||
Route,
|
||||
} from '@react-navigation/routers';
|
||||
import { NavigationStateContext } from './BaseNavigationContainer';
|
||||
import NavigationRouteContext from './NavigationRouteContext';
|
||||
@@ -27,6 +28,7 @@ import {
|
||||
DefaultNavigatorOptions,
|
||||
RouteConfig,
|
||||
PrivateValueStore,
|
||||
EventMapBase,
|
||||
} from './types';
|
||||
import useStateGetters from './useStateGetters';
|
||||
import useOnGetState from './useOnGetState';
|
||||
@@ -55,18 +57,28 @@ const isArrayEqual = (a: any[], b: any[]) =>
|
||||
*
|
||||
* @param children React Elements to extract the config from.
|
||||
*/
|
||||
const getRouteConfigsFromChildren = <ScreenOptions extends object>(
|
||||
const getRouteConfigsFromChildren = <
|
||||
State extends NavigationState,
|
||||
ScreenOptions extends object,
|
||||
EventMap extends EventMapBase
|
||||
>(
|
||||
children: React.ReactNode
|
||||
) => {
|
||||
const configs = React.Children.toArray(children).reduce<
|
||||
RouteConfig<ParamListBase, string, ScreenOptions>[]
|
||||
RouteConfig<ParamListBase, string, 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(
|
||||
child.props as RouteConfig<ParamListBase, string, ScreenOptions>
|
||||
child.props as RouteConfig<
|
||||
ParamListBase,
|
||||
string,
|
||||
State,
|
||||
ScreenOptions,
|
||||
EventMap
|
||||
>
|
||||
);
|
||||
return acc;
|
||||
}
|
||||
@@ -75,7 +87,9 @@ const getRouteConfigsFromChildren = <ScreenOptions extends object>(
|
||||
// When we encounter a fragment, we need to dive into its children to extract the configs
|
||||
// This is handy to conditionally define a group of screens
|
||||
acc.push(
|
||||
...getRouteConfigsFromChildren<ScreenOptions>(child.props.children)
|
||||
...getRouteConfigsFromChildren<State, ScreenOptions, EventMap>(
|
||||
child.props.children
|
||||
)
|
||||
);
|
||||
return acc;
|
||||
}
|
||||
@@ -90,7 +104,7 @@ const getRouteConfigsFromChildren = <ScreenOptions extends object>(
|
||||
}, []);
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
configs.forEach(config => {
|
||||
configs.forEach((config) => {
|
||||
const { name, children, component } = config as any;
|
||||
|
||||
if (typeof name !== 'string' || !name) {
|
||||
@@ -116,7 +130,7 @@ const getRouteConfigsFromChildren = <ScreenOptions extends object>(
|
||||
|
||||
if (component !== undefined && !isValidElementType(component)) {
|
||||
throw new Error(
|
||||
`Got an invalid value for 'component' prop for the screen '${name}'. It must be a a valid React Component.`
|
||||
`Got an invalid value for 'component' prop for the screen '${name}'. It must be a valid React Component.`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -177,9 +191,17 @@ export default function useNavigationBuilder<
|
||||
})
|
||||
);
|
||||
|
||||
const routeConfigs = getRouteConfigsFromChildren<ScreenOptions>(children);
|
||||
const routeConfigs = getRouteConfigsFromChildren<
|
||||
State,
|
||||
ScreenOptions,
|
||||
EventMap
|
||||
>(children);
|
||||
|
||||
const screens = routeConfigs.reduce<
|
||||
Record<string, RouteConfig<ParamListBase, string, ScreenOptions>>
|
||||
Record<
|
||||
string,
|
||||
RouteConfig<ParamListBase, string, State, ScreenOptions, EventMap>
|
||||
>
|
||||
>((acc, config) => {
|
||||
if (config.name in acc) {
|
||||
throw new Error(
|
||||
@@ -191,7 +213,7 @@ export default function useNavigationBuilder<
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const routeNames = routeConfigs.map(config => config.name);
|
||||
const routeNames = routeConfigs.map((config) => config.name);
|
||||
const routeParamList = routeNames.reduce<Record<string, object | undefined>>(
|
||||
(acc, curr) => {
|
||||
const { initialParams } = screens[curr];
|
||||
@@ -220,12 +242,12 @@ export default function useNavigationBuilder<
|
||||
}
|
||||
|
||||
const isStateValid = React.useCallback(
|
||||
state => state.type === undefined || state.type === router.type,
|
||||
(state) => state.type === undefined || state.type === router.type,
|
||||
[router.type]
|
||||
);
|
||||
|
||||
const isStateInitialized = React.useCallback(
|
||||
state =>
|
||||
(state) =>
|
||||
state !== undefined && state.stale === false && isStateValid(state),
|
||||
[isStateValid]
|
||||
);
|
||||
@@ -312,12 +334,14 @@ export default function useNavigationBuilder<
|
||||
: state;
|
||||
}
|
||||
|
||||
if (state !== nextState) {
|
||||
// If the state needs to be updated, we'll schedule an update with React
|
||||
// setState in render seems hacky, but that's how React docs implement getDerivedPropsFromState
|
||||
// https://reactjs.org/docs/hooks-faq.html#how-do-i-implement-getderivedstatefromprops
|
||||
setState(nextState);
|
||||
}
|
||||
const shouldUpdate = state !== nextState;
|
||||
|
||||
React.useEffect(() => {
|
||||
if (shouldUpdate) {
|
||||
// If the state needs to be updated, we'll schedule an update with React
|
||||
setState(nextState);
|
||||
}
|
||||
}, [nextState, setState, shouldUpdate]);
|
||||
|
||||
// The up-to-date state will come in next render, but we don't need to wait for it
|
||||
// We can't use the outdated state since the screens have changed, which will cause error due to mismatched config
|
||||
@@ -344,7 +368,50 @@ export default function useNavigationBuilder<
|
||||
: (initializedStateRef.current as State);
|
||||
}, [getCurrentState, isStateInitialized]);
|
||||
|
||||
const emitter = useEventEmitter();
|
||||
const emitter = useEventEmitter((e) => {
|
||||
let routeNames = [];
|
||||
|
||||
let route: Route<string> | undefined;
|
||||
|
||||
if (e.target) {
|
||||
route = state.routes.find((route) => route.key === e.target);
|
||||
|
||||
if (route?.name) {
|
||||
routeNames.push(route.name);
|
||||
}
|
||||
} else {
|
||||
route = state.routes[state.index];
|
||||
routeNames.push(
|
||||
...Object.keys(screens).filter((name) => route?.name === name)
|
||||
);
|
||||
}
|
||||
|
||||
if (route == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const navigation = descriptors[route.key].navigation;
|
||||
|
||||
const listeners = ([] as (((e: any) => void) | undefined)[])
|
||||
.concat(
|
||||
...routeNames.map((name) => {
|
||||
const { listeners } = screens[name];
|
||||
const map =
|
||||
typeof listeners === 'function'
|
||||
? listeners({ route: route as any, navigation })
|
||||
: listeners;
|
||||
|
||||
return map
|
||||
? Object.keys(map)
|
||||
.filter((type) => type === e.type)
|
||||
.map((type) => map?.[type])
|
||||
: undefined;
|
||||
})
|
||||
)
|
||||
.filter((cb, i, self) => cb && self.lastIndexOf(cb) === i);
|
||||
|
||||
listeners.forEach((listener) => listener?.(e));
|
||||
});
|
||||
|
||||
useFocusEvents({ state, emitter });
|
||||
|
||||
@@ -400,7 +467,7 @@ export default function useNavigationBuilder<
|
||||
getStateForRoute,
|
||||
});
|
||||
|
||||
const descriptors = useDescriptors<State, ScreenOptions>({
|
||||
const descriptors = useDescriptors<State, ScreenOptions, EventMap>({
|
||||
state,
|
||||
screens,
|
||||
navigation,
|
||||
|
||||
@@ -63,7 +63,7 @@ export default function useNavigationCache<
|
||||
};
|
||||
|
||||
cache.current = state.routes.reduce<NavigationCache<State, ScreenOptions>>(
|
||||
(acc, route, index) => {
|
||||
(acc, route) => {
|
||||
const previous = cache.current[route.key];
|
||||
|
||||
if (previous) {
|
||||
@@ -103,14 +103,14 @@ export default function useNavigationCache<
|
||||
dangerouslyGetState: getState,
|
||||
dispatch,
|
||||
setOptions: (options: object) =>
|
||||
setOptions(o => ({
|
||||
setOptions((o) => ({
|
||||
...o,
|
||||
[route.key]: { ...o[route.key], ...options },
|
||||
})),
|
||||
isFocused: () => {
|
||||
const state = getState();
|
||||
|
||||
if (index !== state.index) {
|
||||
if (state.routes[state.index].key !== route.key) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -36,18 +36,45 @@ export default function useNavigationHelpers<
|
||||
const parentNavigationHelpers = React.useContext(NavigationContext);
|
||||
|
||||
return React.useMemo(() => {
|
||||
const dispatch = (action: Action | ((state: State) => Action)) => {
|
||||
const payload =
|
||||
typeof action === 'function' ? action(getState()) : action;
|
||||
const dispatch = (op: Action | ((state: State) => Action)) => {
|
||||
const action = typeof op === 'function' ? op(getState()) : op;
|
||||
|
||||
const handled = onAction(payload);
|
||||
const handled = onAction(action);
|
||||
|
||||
if (!handled && process.env.NODE_ENV !== 'production') {
|
||||
console.error(
|
||||
`The action '${payload.type}' with payload '${JSON.stringify(
|
||||
payload.payload
|
||||
)}' was not handled by any navigator. If you are trying to navigate to a screen, check if the screen exists in your navigator. If you're trying to navigate to a screen in a nested navigator, see https://reactnavigation.org/docs/en/nesting-navigators.html#navigating-to-a-screen-in-a-nested-navigator.`
|
||||
);
|
||||
const payload: Record<string, any> | undefined = action.payload;
|
||||
|
||||
let message = `The action '${action.type}'${
|
||||
payload ? ` with payload ${JSON.stringify(action.payload)}` : ''
|
||||
} was not handled by any navigator.`;
|
||||
|
||||
switch (action.type) {
|
||||
case 'NAVIGATE':
|
||||
case 'PUSH':
|
||||
case 'REPLACE':
|
||||
case 'JUMP_TO':
|
||||
if (payload?.name) {
|
||||
message += `\n\nDo you have a screen named '${payload.name}'?\n\nIf you're trying to navigate to a screen in a nested navigator, see https://reactnavigation.org/docs/nesting-navigators#navigating-to-a-screen-in-a-nested-navigator.`;
|
||||
} else {
|
||||
message += `\n\nYou need to pass the name of the screen to navigate to.\n\nSee https://reactnavigation.org/docs/navigation-actions for usage.`;
|
||||
}
|
||||
|
||||
break;
|
||||
case 'GO_BACK':
|
||||
case 'POP':
|
||||
case 'POP_TO_TOP':
|
||||
message += `\n\nIs there any screen to go back to?`;
|
||||
break;
|
||||
case 'OPEN_DRAWER':
|
||||
case 'CLOSE_DRAWER':
|
||||
case 'TOGGLE_DRAWER':
|
||||
message += `\n\nIs your screen inside a Drawer navigator?`;
|
||||
break;
|
||||
}
|
||||
|
||||
message += `\n\nThis is a development-only warning and won't be shown in production.`;
|
||||
|
||||
console.error(message);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ export default function useNavigationState<T>(selector: Selector<T>): T {
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
const unsubscribe = navigation.addListener('state', e => {
|
||||
const unsubscribe = navigation.addListener('state', (e) => {
|
||||
setResult(selectorRef.current(e.data.state));
|
||||
});
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ export default function useOnGetState({
|
||||
const state = getState();
|
||||
return {
|
||||
...state,
|
||||
routes: state.routes.map(route => ({
|
||||
routes: state.routes.map((route) => ({
|
||||
...route,
|
||||
state: getStateForRoute(route.key),
|
||||
})),
|
||||
|
||||
@@ -15,7 +15,7 @@ export default function useRoute<
|
||||
|
||||
if (route === undefined) {
|
||||
throw new Error(
|
||||
"We couldn't find a route object. Is your component inside a screen in a navigator?"
|
||||
"Couldn't find a route object. Is your component inside a screen in a navigator?"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,85 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [5.3.4](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.3.3...@react-navigation/drawer@5.3.4) (2020-03-23)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/drawer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.3.3](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.3.2...@react-navigation/drawer@5.3.3) (2020-03-22)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/drawer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.3.2](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.3.1...@react-navigation/drawer@5.3.2) (2020-03-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* close drawer on pressing Esc on web ([5c4afc5](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/5c4afc5cb40c1206a9d8c40efe3cf947030da48e)), closes [#6745](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/issues/6745)
|
||||
* don't use react-native-screens on web ([b1a65fc](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/b1a65fc73e8603ae2c06ef101a74df31e80bb9b2)), closes [#7485](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/issues/7485)
|
||||
* fix permanent sidebar position ([#7830](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/issues/7830)) ([3ea8eec](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/3ea8eec4324ea82f0ed427f4662e68e1115e60ab))
|
||||
* initialize height and width to zero if undefined ([3df65e2](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/3df65e28197db3bb8371059146546d57661c5ba3)), closes [#6789](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/issues/6789)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.3.1](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.3.0...@react-navigation/drawer@5.3.1) (2020-03-17)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/drawer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.3.0](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.2.0...@react-navigation/drawer@5.3.0) (2020-03-17)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add permanent drawer type ([#7818](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/issues/7818)) ([6a5d0a0](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/6a5d0a035afae60d91aef78401ec8826295746fe))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.2.0](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.1.1...@react-navigation/drawer@5.2.0) (2020-03-16)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* make useIsDrawerOpen workable inside drawer content ([#7746](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/issues/7746)) ([cb46d0b](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/cb46d0bca4e17e847fff46ac94276213ac9697bf))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.1](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.1.0...@react-navigation/drawer@5.1.1) (2020-03-03)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/drawer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.1.0](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.0.7...@react-navigation/drawer@5.1.0) (2020-02-26)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add ability add listeners with listeners prop ([1624108](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/162410843c4f175ae107756de1c3af04d1d47aa7)), closes [#6756](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/issues/6756)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.0.7](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.0.6...@react-navigation/drawer@5.0.7) (2020-02-21)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/drawer
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/drawer",
|
||||
"description": "Drawer navigator component with animated transitions and gesturess",
|
||||
"version": "5.0.7",
|
||||
"version": "5.3.4",
|
||||
"keywords": [
|
||||
"react-native-component",
|
||||
"react-component",
|
||||
@@ -39,17 +39,17 @@
|
||||
"react-native-iphone-x-helper": "^1.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.9.3",
|
||||
"@react-navigation/native": "^5.0.7",
|
||||
"@types/react": "^16.9.19",
|
||||
"@types/react-native": "^0.60.30",
|
||||
"@react-native-community/bob": "^0.10.0",
|
||||
"@react-navigation/native": "^5.1.3",
|
||||
"@types/react": "^16.9.23",
|
||||
"@types/react-native": "^0.61.22",
|
||||
"del-cli": "^3.0.0",
|
||||
"react": "~16.9.0",
|
||||
"react-native": "~0.61.5",
|
||||
"react-native-gesture-handler": "^1.5.6",
|
||||
"react-native-gesture-handler": "^1.6.0",
|
||||
"react-native-reanimated": "^1.7.0",
|
||||
"react-native-safe-area-context": "^0.7.2",
|
||||
"react-native-screens": "^2.0.0-beta.2",
|
||||
"react-native-safe-area-context": "^0.7.3",
|
||||
"react-native-screens": "^2.3.0",
|
||||
"typescript": "^3.7.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
@@ -59,7 +59,7 @@
|
||||
"react-native-gesture-handler": ">= 1.0.0",
|
||||
"react-native-reanimated": ">= 1.0.0",
|
||||
"react-native-safe-area-context": ">= 0.6.0",
|
||||
"react-native-screens": ">= 2.0.0-alpha.0 || >= 2.0.0-beta.0"
|
||||
"react-native-screens": ">= 2.0.0-alpha.0 || >= 2.0.0-beta.0 || >= 2.0.0"
|
||||
},
|
||||
"@react-native-community/bob": {
|
||||
"source": "src",
|
||||
|
||||
@@ -49,6 +49,8 @@ function DrawerNavigator({
|
||||
}
|
||||
|
||||
export default createNavigatorFactory<
|
||||
DrawerNavigationState,
|
||||
DrawerNavigationOptions,
|
||||
DrawerNavigationEventMap,
|
||||
typeof DrawerNavigator
|
||||
>(DrawerNavigator);
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
Descriptor,
|
||||
NavigationHelpers,
|
||||
DrawerNavigationState,
|
||||
DrawerActionHelpers,
|
||||
} from '@react-navigation/native';
|
||||
import { PanGestureHandler } from 'react-native-gesture-handler';
|
||||
|
||||
@@ -26,8 +27,9 @@ export type DrawerNavigationConfig<T = DrawerContentOptions> = {
|
||||
* - `front`: Traditional drawer which covers the screen with a overlay behind it.
|
||||
* - `back`: The drawer is revealed behind the screen on swipe.
|
||||
* - `slide`: Both the screen and the drawer slide on swipe to reveal the drawer.
|
||||
* - `permanent`: A permanent drawer is shown as a sidebar.
|
||||
*/
|
||||
drawerType?: 'front' | 'back' | 'slide';
|
||||
drawerType?: 'front' | 'back' | 'slide' | 'permanent';
|
||||
/**
|
||||
* How far from the edge of the screen the swipe gesture should activate.
|
||||
*/
|
||||
@@ -190,22 +192,8 @@ export type DrawerNavigationProp<
|
||||
DrawerNavigationState,
|
||||
DrawerNavigationOptions,
|
||||
DrawerNavigationEventMap
|
||||
> & {
|
||||
/**
|
||||
* Open the drawer sidebar.
|
||||
*/
|
||||
openDrawer(): void;
|
||||
|
||||
/**
|
||||
* Close the drawer sidebar.
|
||||
*/
|
||||
closeDrawer(): void;
|
||||
|
||||
/**
|
||||
* Open the drawer sidebar if closed, or close if opened.
|
||||
*/
|
||||
toggleDrawer(): void;
|
||||
};
|
||||
> &
|
||||
DrawerActionHelpers<ParamList>;
|
||||
|
||||
export type DrawerDescriptor = Descriptor<
|
||||
ParamListBase,
|
||||
|
||||
5
packages/drawer/src/utils/DrawerOpenContext.tsx
Normal file
5
packages/drawer/src/utils/DrawerOpenContext.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import * as React from 'react';
|
||||
|
||||
const DrawerOpenContext = React.createContext<boolean | null>(null);
|
||||
|
||||
export default DrawerOpenContext;
|
||||
@@ -1,38 +1,17 @@
|
||||
import * as React from 'react';
|
||||
import { useNavigation, ParamListBase } from '@react-navigation/native';
|
||||
import { DrawerNavigationProp } from '../types';
|
||||
import DrawerOpenContext from './DrawerOpenContext';
|
||||
|
||||
/**
|
||||
* Hook to detect if the drawer is open in a parent navigator.
|
||||
*/
|
||||
export default function useIsDrawerOpen() {
|
||||
const navigation = useNavigation();
|
||||
const isDrawerOpen = React.useContext(DrawerOpenContext);
|
||||
|
||||
let drawer = navigation as DrawerNavigationProp<ParamListBase>;
|
||||
|
||||
// The screen might be inside another navigator such as stack nested in drawer
|
||||
// We need to find the closest drawer navigator and add the listener there
|
||||
while (drawer && drawer.dangerouslyGetState().type !== 'drawer') {
|
||||
drawer = drawer.dangerouslyGetParent();
|
||||
if (typeof isDrawerOpen !== 'boolean') {
|
||||
throw new Error(
|
||||
"Couldn't find a drawer. Is your component inside a drawer navigator?"
|
||||
);
|
||||
}
|
||||
|
||||
const [isDrawerOpen, setIsDrawerOpen] = React.useState(() =>
|
||||
drawer
|
||||
? Boolean(
|
||||
drawer.dangerouslyGetState().history.find(it => it.type === 'drawer')
|
||||
)
|
||||
: false
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
const unsubscribe = drawer.addListener('state', e => {
|
||||
setIsDrawerOpen(
|
||||
Boolean(e.data.state.history.find(it => it.type === 'drawer'))
|
||||
);
|
||||
});
|
||||
|
||||
return unsubscribe;
|
||||
}, [drawer, isDrawerOpen]);
|
||||
|
||||
return isDrawerOpen;
|
||||
}
|
||||
|
||||
@@ -68,6 +68,8 @@ const SPRING_CONFIG = {
|
||||
restSpeedThreshold: 0.01,
|
||||
};
|
||||
|
||||
const ANIMATED_ONE = new Animated.Value(1);
|
||||
|
||||
type Binary = 0 | 1;
|
||||
|
||||
type Renderer = (props: { progress: Animated.Node<number> }) => React.ReactNode;
|
||||
@@ -79,7 +81,7 @@ type Props = {
|
||||
onGestureRef?: (ref: PanGestureHandler | null) => void;
|
||||
gestureEnabled: boolean;
|
||||
drawerPosition: 'left' | 'right';
|
||||
drawerType: 'front' | 'back' | 'slide';
|
||||
drawerType: 'front' | 'back' | 'slide' | 'permanent';
|
||||
keyboardDismissMode: 'none' | 'on-drag';
|
||||
swipeEdgeWidth: number;
|
||||
swipeDistanceThreshold?: number;
|
||||
@@ -125,6 +127,12 @@ export default class DrawerView extends React.PureComponent<Props> {
|
||||
statusBarAnimation: 'slide',
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
if (Platform.OS === 'web') {
|
||||
document?.body?.addEventListener?.('keyup', this.handleEscape);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: Props) {
|
||||
const {
|
||||
open,
|
||||
@@ -180,8 +188,22 @@ export default class DrawerView extends React.PureComponent<Props> {
|
||||
componentWillUnmount() {
|
||||
this.toggleStatusBar(false);
|
||||
this.handleEndInteraction();
|
||||
|
||||
if (Platform.OS === 'web') {
|
||||
document?.body?.removeEventListener?.('keyup', this.handleEscape);
|
||||
}
|
||||
}
|
||||
|
||||
private handleEscape = (e: KeyboardEvent) => {
|
||||
const { open, onClose } = this.props;
|
||||
|
||||
if (e.key === 'Escape') {
|
||||
if (open) {
|
||||
onClose();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private handleEndInteraction = () => {
|
||||
if (this.interactionHandle !== undefined) {
|
||||
InteractionManager.clearInteractionHandle(this.interactionHandle);
|
||||
@@ -544,6 +566,7 @@ export default class DrawerView extends React.PureComponent<Props> {
|
||||
gestureHandlerProps,
|
||||
} = this.props;
|
||||
|
||||
const isOpen = drawerType === 'permanent' ? true : open;
|
||||
const isRight = drawerPosition === 'right';
|
||||
|
||||
const contentTranslateX = drawerType === 'front' ? 0 : this.translateX;
|
||||
@@ -569,8 +592,10 @@ export default class DrawerView extends React.PureComponent<Props> {
|
||||
const hitSlop = isRight
|
||||
? // Extend hitSlop to the side of the screen when drawer is closed
|
||||
// This lets the user drag the drawer from the side of the screen
|
||||
{ right: 0, width: open ? undefined : swipeEdgeWidth }
|
||||
: { left: 0, width: open ? undefined : swipeEdgeWidth };
|
||||
{ right: 0, width: isOpen ? undefined : swipeEdgeWidth }
|
||||
: { left: 0, width: isOpen ? undefined : swipeEdgeWidth };
|
||||
|
||||
const progress = drawerType === 'permanent' ? ANIMATED_ONE : this.progress;
|
||||
|
||||
return (
|
||||
<PanGestureHandler
|
||||
@@ -580,62 +605,83 @@ export default class DrawerView extends React.PureComponent<Props> {
|
||||
onGestureEvent={this.handleGestureEvent}
|
||||
onHandlerStateChange={this.handleGestureStateChange}
|
||||
hitSlop={hitSlop}
|
||||
enabled={gestureEnabled}
|
||||
enabled={drawerType !== 'permanent' && gestureEnabled}
|
||||
{...gestureHandlerProps}
|
||||
>
|
||||
<Animated.View
|
||||
onLayout={this.handleContainerLayout}
|
||||
style={styles.main}
|
||||
style={[
|
||||
styles.main,
|
||||
{
|
||||
flexDirection:
|
||||
drawerType === 'permanent' && !isRight ? 'row-reverse' : 'row',
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.content,
|
||||
{
|
||||
drawerType !== 'permanent' && {
|
||||
transform: [{ translateX: contentTranslateX }],
|
||||
},
|
||||
sceneContainerStyle as any,
|
||||
]}
|
||||
>
|
||||
<View
|
||||
accessibilityElementsHidden={open}
|
||||
importantForAccessibility={open ? 'no-hide-descendants' : 'auto'}
|
||||
accessibilityElementsHidden={isOpen}
|
||||
importantForAccessibility={
|
||||
isOpen ? 'no-hide-descendants' : 'auto'
|
||||
}
|
||||
style={styles.content}
|
||||
>
|
||||
{renderSceneContent({ progress: this.progress })}
|
||||
{renderSceneContent({ progress })}
|
||||
</View>
|
||||
<TapGestureHandler
|
||||
enabled={gestureEnabled}
|
||||
onHandlerStateChange={this.handleTapStateChange}
|
||||
>
|
||||
<Overlay progress={this.progress} style={overlayStyle} />
|
||||
</TapGestureHandler>
|
||||
{// Disable overlay if sidebar is permanent
|
||||
drawerType === 'permanent' ? null : (
|
||||
<TapGestureHandler
|
||||
enabled={gestureEnabled}
|
||||
onHandlerStateChange={this.handleTapStateChange}
|
||||
>
|
||||
<Overlay progress={progress} style={overlayStyle} />
|
||||
</TapGestureHandler>
|
||||
)}
|
||||
</Animated.View>
|
||||
<Animated.Code
|
||||
exec={block([
|
||||
onChange(this.manuallyTriggerSpring, [
|
||||
cond(eq(this.manuallyTriggerSpring, TRUE), [
|
||||
set(this.nextIsOpen, FALSE),
|
||||
call([], () => (this.currentOpenValue = false)),
|
||||
{drawerType === 'permanent' ? null : (
|
||||
<Animated.Code
|
||||
exec={block([
|
||||
onChange(this.manuallyTriggerSpring, [
|
||||
cond(eq(this.manuallyTriggerSpring, TRUE), [
|
||||
set(this.nextIsOpen, FALSE),
|
||||
call([], () => (this.currentOpenValue = false)),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
])}
|
||||
/>
|
||||
])}
|
||||
/>
|
||||
)}
|
||||
<Animated.View
|
||||
accessibilityViewIsModal={open}
|
||||
accessibilityViewIsModal={isOpen}
|
||||
removeClippedSubviews={Platform.OS !== 'ios'}
|
||||
onLayout={this.handleDrawerLayout}
|
||||
style={[
|
||||
styles.container,
|
||||
isRight ? { right: offset } : { left: offset },
|
||||
{
|
||||
transform: [{ translateX: drawerTranslateX }],
|
||||
opacity: this.drawerOpacity,
|
||||
zIndex: drawerType === 'back' ? -1 : 0,
|
||||
},
|
||||
drawerType === 'permanent'
|
||||
? // Without this, the `left`/`right` values don't get reset
|
||||
isRight
|
||||
? { right: 0 }
|
||||
: { left: 0 }
|
||||
: [
|
||||
styles.nonPermanent,
|
||||
{
|
||||
transform: [{ translateX: drawerTranslateX }],
|
||||
opacity: this.drawerOpacity,
|
||||
},
|
||||
isRight ? { right: offset } : { left: offset },
|
||||
{ zIndex: drawerType === 'back' ? -1 : 0 },
|
||||
],
|
||||
drawerStyle as any,
|
||||
]}
|
||||
>
|
||||
{renderDrawerContent({ progress: this.progress })}
|
||||
{renderDrawerContent({ progress })}
|
||||
</Animated.View>
|
||||
</Animated.View>
|
||||
</PanGestureHandler>
|
||||
@@ -646,11 +692,13 @@ export default class DrawerView extends React.PureComponent<Props> {
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
backgroundColor: 'white',
|
||||
maxWidth: '100%',
|
||||
},
|
||||
nonPermanent: {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
width: '80%',
|
||||
maxWidth: '100%',
|
||||
},
|
||||
content: {
|
||||
flex: 1,
|
||||
|
||||
@@ -72,14 +72,8 @@ export default function DrawerItem(props: Props) {
|
||||
labelStyle,
|
||||
focused = false,
|
||||
activeTintColor = colors.primary,
|
||||
inactiveTintColor = Color(colors.text)
|
||||
.alpha(0.68)
|
||||
.rgb()
|
||||
.string(),
|
||||
activeBackgroundColor = Color(activeTintColor)
|
||||
.alpha(0.12)
|
||||
.rgb()
|
||||
.string(),
|
||||
inactiveTintColor = Color(colors.text).alpha(0.68).rgb().string(),
|
||||
activeBackgroundColor = Color(activeTintColor).alpha(0.12).rgb().string(),
|
||||
inactiveBackgroundColor = 'transparent',
|
||||
style,
|
||||
onPress,
|
||||
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
DrawerNavigationHelpers,
|
||||
DrawerContentComponentProps,
|
||||
} from '../types';
|
||||
import DrawerOpenContext from '../utils/DrawerOpenContext';
|
||||
import DrawerPositionContext from '../utils/DrawerPositionContext';
|
||||
|
||||
type Props = DrawerNavigationConfig & {
|
||||
@@ -88,15 +89,17 @@ export default function DrawerView({
|
||||
sceneContainerStyle,
|
||||
}: Props) {
|
||||
const [loaded, setLoaded] = React.useState([state.index]);
|
||||
const [drawerWidth, setDrawerWidth] = React.useState(() =>
|
||||
getDefaultDrawerWidth(Dimensions.get('window'))
|
||||
);
|
||||
const [drawerWidth, setDrawerWidth] = React.useState(() => {
|
||||
const { height = 0, width = 0 } = Dimensions.get('window');
|
||||
|
||||
return getDefaultDrawerWidth({ height, width });
|
||||
});
|
||||
|
||||
const drawerGestureRef = React.useRef<PanGestureHandler>(null);
|
||||
|
||||
const { colors } = useTheme();
|
||||
|
||||
const isDrawerOpen = Boolean(state.history.find(it => it.type === 'drawer'));
|
||||
const isDrawerOpen = state.history.some((it) => it.type === 'drawer');
|
||||
|
||||
const handleDrawerOpen = React.useCallback(() => {
|
||||
navigation.dispatch({
|
||||
@@ -203,36 +206,48 @@ export default function DrawerView({
|
||||
<GestureHandlerWrapper style={styles.content}>
|
||||
<SafeAreaProviderCompat>
|
||||
<DrawerGestureContext.Provider value={drawerGestureRef}>
|
||||
<Drawer
|
||||
open={isDrawerOpen}
|
||||
gestureEnabled={gestureEnabled}
|
||||
onOpen={handleDrawerOpen}
|
||||
onClose={handleDrawerClose}
|
||||
onGestureRef={ref => {
|
||||
// @ts-ignore
|
||||
drawerGestureRef.current = ref;
|
||||
}}
|
||||
gestureHandlerProps={gestureHandlerProps}
|
||||
drawerType={drawerType}
|
||||
drawerPosition={drawerPosition}
|
||||
sceneContainerStyle={[
|
||||
{ backgroundColor: colors.background },
|
||||
sceneContainerStyle,
|
||||
]}
|
||||
drawerStyle={[
|
||||
{ width: drawerWidth, backgroundColor: colors.card },
|
||||
drawerStyle,
|
||||
]}
|
||||
overlayStyle={{ backgroundColor: overlayColor }}
|
||||
swipeEdgeWidth={edgeWidth}
|
||||
swipeDistanceThreshold={minSwipeDistance}
|
||||
hideStatusBar={hideStatusBar}
|
||||
statusBarAnimation={statusBarAnimation}
|
||||
renderDrawerContent={renderNavigationView}
|
||||
renderSceneContent={renderContent}
|
||||
keyboardDismissMode={keyboardDismissMode}
|
||||
drawerPostion={drawerPosition}
|
||||
/>
|
||||
<DrawerOpenContext.Provider value={isDrawerOpen}>
|
||||
<Drawer
|
||||
open={isDrawerOpen}
|
||||
gestureEnabled={gestureEnabled}
|
||||
onOpen={handleDrawerOpen}
|
||||
onClose={handleDrawerClose}
|
||||
onGestureRef={(ref) => {
|
||||
// @ts-ignore
|
||||
drawerGestureRef.current = ref;
|
||||
}}
|
||||
gestureHandlerProps={gestureHandlerProps}
|
||||
drawerType={drawerType}
|
||||
drawerPosition={drawerPosition}
|
||||
sceneContainerStyle={[
|
||||
{ backgroundColor: colors.background },
|
||||
sceneContainerStyle,
|
||||
]}
|
||||
drawerStyle={[
|
||||
{ width: drawerWidth, backgroundColor: colors.card },
|
||||
drawerType === 'permanent' &&
|
||||
(drawerPosition === 'left'
|
||||
? {
|
||||
borderRightColor: colors.border,
|
||||
borderRightWidth: StyleSheet.hairlineWidth,
|
||||
}
|
||||
: {
|
||||
borderLeftColor: colors.border,
|
||||
borderLeftWidth: StyleSheet.hairlineWidth,
|
||||
}),
|
||||
drawerStyle,
|
||||
]}
|
||||
overlayStyle={{ backgroundColor: overlayColor }}
|
||||
swipeEdgeWidth={edgeWidth}
|
||||
swipeDistanceThreshold={minSwipeDistance}
|
||||
hideStatusBar={hideStatusBar}
|
||||
statusBarAnimation={statusBarAnimation}
|
||||
renderDrawerContent={renderNavigationView}
|
||||
renderSceneContent={renderContent}
|
||||
keyboardDismissMode={keyboardDismissMode}
|
||||
drawerPostion={drawerPosition}
|
||||
/>
|
||||
</DrawerOpenContext.Provider>
|
||||
</DrawerGestureContext.Provider>
|
||||
</SafeAreaProviderCompat>
|
||||
</GestureHandlerWrapper>
|
||||
|
||||
@@ -9,21 +9,29 @@ type Props = {
|
||||
style?: any;
|
||||
};
|
||||
|
||||
const FAR_FAR_AWAY = 3000; // this should be big enough to move the whole view out of its container
|
||||
const FAR_FAR_AWAY = 30000; // this should be big enough to move the whole view out of its container
|
||||
|
||||
export default class ResourceSavingScene extends React.Component<Props> {
|
||||
render() {
|
||||
if (screensEnabled?.()) {
|
||||
// react-native-screens is buggy on web
|
||||
if (screensEnabled?.() && Platform.OS !== 'web') {
|
||||
const { isVisible, ...rest } = this.props;
|
||||
|
||||
// @ts-ignore
|
||||
return <Screen active={isVisible ? 1 : 0} {...rest} />;
|
||||
}
|
||||
|
||||
const { isVisible, children, style, ...rest } = this.props;
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[styles.container, style]}
|
||||
style={[
|
||||
styles.container,
|
||||
Platform.OS === 'web'
|
||||
? { display: isVisible ? 'flex' : 'none' }
|
||||
: null,
|
||||
style,
|
||||
]}
|
||||
collapsable={false}
|
||||
removeClippedSubviews={
|
||||
// On iOS, set removeClippedSubviews to true only when not focused
|
||||
|
||||
@@ -30,7 +30,7 @@ type Props = {
|
||||
export default function SafeAreaProviderCompat({ children }: Props) {
|
||||
return (
|
||||
<SafeAreaConsumer>
|
||||
{insets => {
|
||||
{(insets) => {
|
||||
if (insets) {
|
||||
// If we already have insets, don't wrap the stack in another safe area provider
|
||||
// This avoids an issue with updates at the cost of potentially incorrect values
|
||||
|
||||
@@ -3,6 +3,65 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [5.1.6](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.1.5...@react-navigation/material-bottom-tabs@5.1.6) (2020-03-23)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.5](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.1.4...@react-navigation/material-bottom-tabs@5.1.5) (2020-03-22)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.4](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.1.3...@react-navigation/material-bottom-tabs@5.1.4) (2020-03-19)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.3](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.1.2...@react-navigation/material-bottom-tabs@5.1.3) (2020-03-17)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.2](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.1.1...@react-navigation/material-bottom-tabs@5.1.2) (2020-03-16)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.1](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.1.0...@react-navigation/material-bottom-tabs@5.1.1) (2020-03-03)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.1.0](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.0.7...@react-navigation/material-bottom-tabs@5.1.0) (2020-02-26)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add ability add listeners with listeners prop ([1624108](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/commit/162410843c4f175ae107756de1c3af04d1d47aa7)), closes [#6756](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/issues/6756)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.0.7](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.0.6...@react-navigation/material-bottom-tabs@5.0.7) (2020-02-21)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/material-bottom-tabs",
|
||||
"description": "Integration for bottom navigation component from react-native-paper",
|
||||
"version": "5.0.7",
|
||||
"version": "5.1.6",
|
||||
"keywords": [
|
||||
"react-native-component",
|
||||
"react-component",
|
||||
@@ -35,10 +35,10 @@
|
||||
"clean": "del lib"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.9.3",
|
||||
"@react-navigation/native": "^5.0.7",
|
||||
"@types/react": "^16.9.19",
|
||||
"@types/react-native": "^0.60.30",
|
||||
"@react-native-community/bob": "^0.10.0",
|
||||
"@react-navigation/native": "^5.1.3",
|
||||
"@types/react": "^16.9.23",
|
||||
"@types/react-native": "^0.61.22",
|
||||
"@types/react-native-vector-icons": "^6.4.5",
|
||||
"del-cli": "^3.0.0",
|
||||
"react": "~16.9.0",
|
||||
|
||||
@@ -49,6 +49,8 @@ function MaterialBottomTabNavigator({
|
||||
}
|
||||
|
||||
export default createNavigatorFactory<
|
||||
TabNavigationState,
|
||||
MaterialBottomTabNavigationOptions,
|
||||
MaterialBottomTabNavigationEventMap,
|
||||
typeof MaterialBottomTabNavigator
|
||||
>(MaterialBottomTabNavigator);
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
NavigationProp,
|
||||
NavigationHelpers,
|
||||
TabNavigationState,
|
||||
TabActionHelpers,
|
||||
} from '@react-navigation/native';
|
||||
|
||||
export type MaterialBottomTabNavigationEventMap = {
|
||||
@@ -28,19 +29,8 @@ export type MaterialBottomTabNavigationProp<
|
||||
TabNavigationState,
|
||||
MaterialBottomTabNavigationOptions,
|
||||
MaterialBottomTabNavigationEventMap
|
||||
> & {
|
||||
/**
|
||||
* Jump to an existing tab.
|
||||
*
|
||||
* @param name Name of the route for the tab.
|
||||
* @param [params] Params object for the route.
|
||||
*/
|
||||
jumpTo<RouteName extends Extract<keyof ParamList, string>>(
|
||||
...args: ParamList[RouteName] extends undefined | any
|
||||
? [RouteName] | [RouteName, ParamList[RouteName]]
|
||||
: [RouteName, ParamList[RouteName]]
|
||||
): void;
|
||||
};
|
||||
> &
|
||||
TabActionHelpers<ParamList>;
|
||||
|
||||
export type MaterialBottomTabNavigationOptions = {
|
||||
/**
|
||||
|
||||
@@ -3,6 +3,65 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [5.1.6](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.1.5...@react-navigation/material-top-tabs@5.1.6) (2020-03-23)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.5](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.1.4...@react-navigation/material-top-tabs@5.1.5) (2020-03-22)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.4](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.1.3...@react-navigation/material-top-tabs@5.1.4) (2020-03-19)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.3](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.1.2...@react-navigation/material-top-tabs@5.1.3) (2020-03-17)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.2](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.1.1...@react-navigation/material-top-tabs@5.1.2) (2020-03-16)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.1](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.1.0...@react-navigation/material-top-tabs@5.1.1) (2020-03-03)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.1.0](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.0.7...@react-navigation/material-top-tabs@5.1.0) (2020-02-26)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add ability add listeners with listeners prop ([1624108](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/commit/162410843c4f175ae107756de1c3af04d1d47aa7)), closes [#6756](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/issues/6756)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.0.7](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.0.6...@react-navigation/material-top-tabs@5.0.7) (2020-02-21)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/material-top-tabs",
|
||||
"description": "Integration for the animated tab view component from react-native-tab-view",
|
||||
"version": "5.0.7",
|
||||
"version": "5.1.6",
|
||||
"keywords": [
|
||||
"react-native-component",
|
||||
"react-component",
|
||||
@@ -38,14 +38,14 @@
|
||||
"color": "^3.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.9.3",
|
||||
"@react-navigation/native": "^5.0.7",
|
||||
"@types/react": "^16.9.19",
|
||||
"@types/react-native": "^0.60.30",
|
||||
"@react-native-community/bob": "^0.10.0",
|
||||
"@react-navigation/native": "^5.1.3",
|
||||
"@types/react": "^16.9.23",
|
||||
"@types/react-native": "^0.61.22",
|
||||
"del-cli": "^3.0.0",
|
||||
"react": "~16.9.0",
|
||||
"react-native": "~0.61.5",
|
||||
"react-native-gesture-handler": "^1.5.6",
|
||||
"react-native-gesture-handler": "^1.6.0",
|
||||
"react-native-reanimated": "^1.7.0",
|
||||
"react-native-tab-view": "^2.13.0",
|
||||
"typescript": "^3.7.5"
|
||||
|
||||
@@ -48,6 +48,8 @@ function MaterialTopTabNavigator({
|
||||
}
|
||||
|
||||
export default createNavigatorFactory<
|
||||
TabNavigationState,
|
||||
MaterialTopTabNavigationOptions,
|
||||
MaterialTopTabNavigationEventMap,
|
||||
typeof MaterialTopTabNavigator
|
||||
>(MaterialTopTabNavigator);
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
Route,
|
||||
NavigationProp,
|
||||
TabNavigationState,
|
||||
TabActionHelpers,
|
||||
} from '@react-navigation/native';
|
||||
|
||||
export type MaterialTopTabNavigationEventMap = {
|
||||
@@ -42,19 +43,8 @@ export type MaterialTopTabNavigationProp<
|
||||
TabNavigationState,
|
||||
MaterialTopTabNavigationOptions,
|
||||
MaterialTopTabNavigationEventMap
|
||||
> & {
|
||||
/**
|
||||
* Jump to an existing tab.
|
||||
*
|
||||
* @param name Name of the route for the tab.
|
||||
* @param [params] Params object for the route.
|
||||
*/
|
||||
jumpTo<RouteName extends Extract<keyof ParamList, string>>(
|
||||
...args: ParamList[RouteName] extends undefined | any
|
||||
? [RouteName] | [RouteName, ParamList[RouteName]]
|
||||
: [RouteName, ParamList[RouteName]]
|
||||
): void;
|
||||
};
|
||||
> &
|
||||
TabActionHelpers<ParamList>;
|
||||
|
||||
export type MaterialTopTabNavigationOptions = {
|
||||
/**
|
||||
|
||||
@@ -14,17 +14,11 @@ export default function TabBarTop(props: MaterialTopTabBarProps) {
|
||||
navigation,
|
||||
descriptors,
|
||||
activeTintColor = colors.text,
|
||||
inactiveTintColor = Color(activeTintColor)
|
||||
.alpha(0.5)
|
||||
.rgb()
|
||||
.string(),
|
||||
inactiveTintColor = Color(activeTintColor).alpha(0.5).rgb().string(),
|
||||
allowFontScaling = true,
|
||||
showIcon = false,
|
||||
showLabel = true,
|
||||
pressColor = Color(activeTintColor)
|
||||
.alpha(0.08)
|
||||
.rgb()
|
||||
.string(),
|
||||
pressColor = Color(activeTintColor).alpha(0.08).rgb().string(),
|
||||
iconStyle,
|
||||
labelStyle,
|
||||
indicatorStyle,
|
||||
|
||||
@@ -47,7 +47,7 @@ export default function MaterialTopTabView({
|
||||
return (
|
||||
<TabView
|
||||
{...rest}
|
||||
onIndexChange={index =>
|
||||
onIndexChange={(index) =>
|
||||
navigation.dispatch({
|
||||
...TabActions.jumpTo(state.routes[index].name),
|
||||
target: state.key,
|
||||
|
||||
@@ -3,6 +3,68 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [5.1.3](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.1.2...@react-navigation/native@5.1.3) (2020-03-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add info about android launchMode in useLinking error ([d94e43c](https://github.com/react-navigation/react-navigation/tree/master/packages/native/commit/d94e43c3c8625b209a5c883b8cb560496d07fda7))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.2](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.1.1...@react-navigation/native@5.1.2) (2020-03-22)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/native
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.1](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.1.0...@react-navigation/native@5.1.1) (2020-03-19)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/native
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.1.0](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.0.10...@react-navigation/native@5.1.0) (2020-03-17)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add permanent drawer type ([#7818](https://github.com/react-navigation/react-navigation/tree/master/packages/native/issues/7818)) ([6a5d0a0](https://github.com/react-navigation/react-navigation/tree/master/packages/native/commit/6a5d0a035afae60d91aef78401ec8826295746fe))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.0.10](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.0.9...@react-navigation/native@5.0.10) (2020-03-16)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/native
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.0.9](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.0.8...@react-navigation/native@5.0.9) (2020-03-03)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/native
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.0.8](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.0.7...@react-navigation/native@5.0.8) (2020-02-26)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/native
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.0.7](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.0.6...@react-navigation/native@5.0.7) (2020-02-21)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/native
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/native",
|
||||
"description": "React Native integration for React Navigation",
|
||||
"version": "5.0.7",
|
||||
"version": "5.1.3",
|
||||
"keywords": [
|
||||
"react-native",
|
||||
"react-navigation",
|
||||
@@ -31,12 +31,12 @@
|
||||
"clean": "del lib"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-navigation/core": "^5.1.6"
|
||||
"@react-navigation/core": "^5.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.9.3",
|
||||
"@types/react": "^16.9.19",
|
||||
"@types/react-native": "^0.60.30",
|
||||
"@react-native-community/bob": "^0.10.0",
|
||||
"@types/react": "^16.9.23",
|
||||
"@types/react-native": "^0.61.22",
|
||||
"del-cli": "^3.0.0",
|
||||
"react": "~16.9.0",
|
||||
"react-native": "~0.61.5",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export default function() {
|
||||
export default function () {
|
||||
throw new Error(
|
||||
"'NavigationNativeContainer' has been renamed to 'NavigationContainer"
|
||||
);
|
||||
|
||||
@@ -7,7 +7,7 @@ const DefaultTheme: Theme = {
|
||||
background: 'rgb(242, 242, 242)',
|
||||
card: 'rgb(255, 255, 255)',
|
||||
text: 'rgb(28, 28, 30)',
|
||||
border: 'rgb(199, 199, 204)',
|
||||
border: 'rgb(224, 224, 224)',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import { Linking } from 'react-native';
|
||||
import { Linking, Platform } from 'react-native';
|
||||
import {
|
||||
getActionFromState,
|
||||
getStateFromPath as getStateFromPathDefault,
|
||||
@@ -20,7 +20,10 @@ export default function useLinking(
|
||||
React.useEffect(() => {
|
||||
if (isUsingLinking) {
|
||||
throw new Error(
|
||||
"Looks like you are using 'useLinking' in multiple components. This is likely an error since deep links should only be handled in one place to avoid conflicts."
|
||||
"Looks like you are using 'useLinking' in multiple components. This is likely an error since deep links should only be handled in one place to avoid conflicts." +
|
||||
(Platform.OS === 'android'
|
||||
? "\n\nIf you're not using it in multiple components, ensure that you have set 'android:launchMode=singleTask' in the '<activity />' section of the 'AndroidManifest.xml' file to avoid launching multiple activities which run multiple instances of the root component."
|
||||
: '')
|
||||
);
|
||||
} else {
|
||||
isUsingLinking = true;
|
||||
|
||||
@@ -40,7 +40,7 @@ export default function useLinking(
|
||||
React.useEffect(() => {
|
||||
if (isUsingLinking) {
|
||||
throw new Error(
|
||||
"Looks like you are using 'useLinking' in multiple components. This is likely an error since URL integration should only be handled in one place to avoid conflicts."
|
||||
"Looks like you are using 'useLinking' in multiple components. This is likely an error since URL integration should only be handled in one place to avoid conflicts. Also ensure that you set your android activity launchMode to single task in your AndroiManifest.xml file."
|
||||
);
|
||||
} else {
|
||||
isUsingLinking = true;
|
||||
|
||||
@@ -6,11 +6,11 @@ type ScrollOptions = { y?: number; animated?: boolean };
|
||||
type ScrollableView =
|
||||
| { scrollToTop(): void }
|
||||
| { scrollTo(options: ScrollOptions): void }
|
||||
| { scrollToOffset(options: ScrollOptions): void }
|
||||
| { scrollToOffset(options: { offset?: number; animated?: boolean }): void }
|
||||
| { scrollResponderScrollTo(options: ScrollOptions): void };
|
||||
|
||||
type ScrollableWrapper =
|
||||
| { getScrollResponder(): ScrollableView }
|
||||
| { getScrollResponder(): React.ReactNode }
|
||||
| { getNode(): ScrollableView }
|
||||
| ScrollableView;
|
||||
|
||||
@@ -79,7 +79,7 @@ export default function useScrollToTop(
|
||||
// Run the operation in the next frame so we're sure all listeners have been run
|
||||
// This is necessary to know if preventDefault() has been called
|
||||
requestAnimationFrame(() => {
|
||||
const scrollable = getScrollableNode(ref);
|
||||
const scrollable = getScrollableNode(ref) as ScrollableWrapper;
|
||||
|
||||
if (isFocused && isFirst && scrollable && !e.defaultPrevented) {
|
||||
if ('scrollToTop' in scrollable) {
|
||||
@@ -87,7 +87,7 @@ export default function useScrollToTop(
|
||||
} else if ('scrollTo' in scrollable) {
|
||||
scrollable.scrollTo({ y: 0, animated: true });
|
||||
} else if ('scrollToOffset' in scrollable) {
|
||||
scrollable.scrollToOffset({ y: 0, animated: true });
|
||||
scrollable.scrollToOffset({ offset: 0, animated: true });
|
||||
} else if ('scrollResponderScrollTo' in scrollable) {
|
||||
scrollable.scrollResponderScrollTo({ y: 0, animated: true });
|
||||
}
|
||||
|
||||
@@ -3,6 +3,52 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [5.2.0](https://github.com/react-navigation/react-navigation/tree/master/packages/routers/compare/@react-navigation/routers@5.1.1...@react-navigation/routers@5.2.0) (2020-03-22)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add keys to routes missing keys during reset ([813a590](https://github.com/react-navigation/react-navigation/tree/master/packages/routers/commit/813a5903b5f44506b9097538ed85229e565b855e))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.1](https://github.com/react-navigation/react-navigation/tree/master/packages/routers/compare/@react-navigation/routers@5.1.0...@react-navigation/routers@5.1.1) (2020-03-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* don't handle action if no routes are present ([660cac3](https://github.com/react-navigation/react-navigation/tree/master/packages/routers/commit/660cac3557bce8978812ce2750e961e7ada92d13))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.1.0](https://github.com/react-navigation/react-navigation/tree/master/packages/routers/compare/@react-navigation/routers@5.0.3...@react-navigation/routers@5.1.0) (2020-03-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix links for documentation ([5bb0f40](https://github.com/react-navigation/react-navigation/tree/master/packages/routers/commit/5bb0f405ceb5755d39a0b5b1f2e4ecee0da051bc))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* make reset bubble up ([09f6808](https://github.com/react-navigation/react-navigation/tree/master/packages/routers/commit/09f6808d7d43c70b2c502151f9f20fad03972886))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.0.3](https://github.com/react-navigation/react-navigation/tree/master/packages/routers/compare/@react-navigation/routers@5.0.2...@react-navigation/routers@5.0.3) (2020-02-26)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/routers
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.0.2](https://github.com/react-navigation/react-navigation/tree/master/packages/routers/compare/@react-navigation/routers@5.0.1...@react-navigation/routers@5.0.2) (2020-02-21)
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/routers",
|
||||
"description": "Routers to help build custom navigators",
|
||||
"version": "5.0.2",
|
||||
"version": "5.2.0",
|
||||
"keywords": [
|
||||
"react",
|
||||
"react-native",
|
||||
@@ -33,7 +33,7 @@
|
||||
"shortid": "^2.2.15"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.9.3",
|
||||
"@react-native-community/bob": "^0.10.0",
|
||||
"del-cli": "^3.0.0",
|
||||
"typescript": "^3.7.5"
|
||||
},
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import shortid from 'shortid';
|
||||
import { CommonNavigationAction, NavigationState, PartialState } from './types';
|
||||
|
||||
/**
|
||||
@@ -12,7 +13,7 @@ const BaseRouter = {
|
||||
switch (action.type) {
|
||||
case 'SET_PARAMS': {
|
||||
const index = action.source
|
||||
? state.routes.findIndex(r => r.key === action.source)
|
||||
? state.routes.findIndex((r) => r.key === action.source)
|
||||
: state.index;
|
||||
|
||||
if (index === -1) {
|
||||
@@ -29,8 +30,40 @@ const BaseRouter = {
|
||||
};
|
||||
}
|
||||
|
||||
case 'RESET':
|
||||
return action.payload as PartialState<State>;
|
||||
case 'RESET': {
|
||||
const nextState = action.payload as State | PartialState<State>;
|
||||
|
||||
if (
|
||||
nextState.routes.length === 0 ||
|
||||
nextState.routes.some(
|
||||
(route: { name: string }) => !state.routeNames.includes(route.name)
|
||||
)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (nextState.stale === false) {
|
||||
if (
|
||||
state.routeNames.length !== nextState.routeNames.length ||
|
||||
nextState.routeNames.some(
|
||||
(name) => !state.routeNames.includes(name)
|
||||
)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
...nextState,
|
||||
routes: nextState.routes.map((route) =>
|
||||
route.key
|
||||
? route
|
||||
: { ...route, key: `${route.name}-${shortid()}` }
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
return nextState;
|
||||
}
|
||||
|
||||
default:
|
||||
return null;
|
||||
|
||||
@@ -41,11 +41,11 @@ export function navigate(...args: any): Action {
|
||||
if (typeof args[0] === 'string') {
|
||||
return { type: 'NAVIGATE', payload: { name: args[0], params: args[1] } };
|
||||
} else {
|
||||
const payload = args[0];
|
||||
const payload = args[0] || {};
|
||||
|
||||
if (!payload.hasOwnProperty('key') && !payload.hasOwnProperty('name')) {
|
||||
throw new Error(
|
||||
'You need to specify name or key when calling navigate with an object as the argument. See https://reactnavigation.org/docs/navigation-actions.html#navigate for usage.'
|
||||
'You need to specify name or key when calling navigate with an object as the argument. See https://reactnavigation.org/docs/navigation-actions#navigate for usage.'
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
import shortid from 'shortid';
|
||||
import { PartialState, CommonNavigationAction, Router } from './types';
|
||||
import {
|
||||
PartialState,
|
||||
CommonNavigationAction,
|
||||
Router,
|
||||
ParamListBase,
|
||||
} from './types';
|
||||
import TabRouter, {
|
||||
TabActions,
|
||||
TabActionType,
|
||||
TabRouterOptions,
|
||||
TabNavigationState,
|
||||
TabActionHelpers,
|
||||
} from './TabRouter';
|
||||
|
||||
export type DrawerActionType =
|
||||
@@ -31,6 +37,25 @@ export type DrawerNavigationState = Omit<
|
||||
history: ({ type: 'route'; key: string } | { type: 'drawer' })[];
|
||||
};
|
||||
|
||||
export type DrawerActionHelpers<
|
||||
ParamList extends ParamListBase
|
||||
> = TabActionHelpers<ParamList> & {
|
||||
/**
|
||||
* Open the drawer sidebar.
|
||||
*/
|
||||
openDrawer(): void;
|
||||
|
||||
/**
|
||||
* Close the drawer sidebar.
|
||||
*/
|
||||
closeDrawer(): void;
|
||||
|
||||
/**
|
||||
* Open the drawer sidebar if closed, or close if opened.
|
||||
*/
|
||||
toggleDrawer(): void;
|
||||
};
|
||||
|
||||
export const DrawerActions = {
|
||||
...TabActions,
|
||||
openDrawer(): DrawerActionType {
|
||||
@@ -46,7 +71,7 @@ export const DrawerActions = {
|
||||
|
||||
const isDrawerOpen = (
|
||||
state: DrawerNavigationState | PartialState<DrawerNavigationState>
|
||||
) => Boolean(state.history?.find(it => it.type === 'drawer'));
|
||||
) => Boolean(state.history?.find((it) => it.type === 'drawer'));
|
||||
|
||||
const openDrawer = (state: DrawerNavigationState): DrawerNavigationState => {
|
||||
if (isDrawerOpen(state)) {
|
||||
@@ -66,7 +91,7 @@ const closeDrawer = (state: DrawerNavigationState): DrawerNavigationState => {
|
||||
|
||||
return {
|
||||
...state,
|
||||
history: state.history.filter(it => it.type !== 'drawer'),
|
||||
history: state.history.filter((it) => it.type !== 'drawer'),
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user