Compare commits

...

57 Commits

Author SHA1 Message Date
Satyajit Sahoo
b85a1c3055 chore: publish
- @react-navigation/bottom-tabs@5.4.1
 - @react-navigation/compat@5.1.17
 - @react-navigation/core@5.5.2
 - @react-navigation/drawer@5.7.1
 - @react-navigation/material-bottom-tabs@5.2.1
 - @react-navigation/material-top-tabs@5.2.1
 - @react-navigation/native@5.2.6
 - @react-navigation/routers@5.4.4
 - @react-navigation/stack@5.3.1
2020-05-08 19:16:47 +02:00
Satyajit Sahoo
18f8188dc8 chore: add source key to package.json 2020-05-08 19:14:29 +02:00
Satyajit Sahoo
47a1229837 fix: fix building typescript definitions. closes #8216 2020-05-08 19:09:13 +02:00
Satyajit Sahoo
00b11e303e chore: publish
- @react-navigation/bottom-tabs@5.4.0
 - @react-navigation/compat@5.1.16
 - @react-navigation/core@5.5.1
 - @react-navigation/drawer@5.7.0
 - @react-navigation/material-bottom-tabs@5.2.0
 - @react-navigation/material-top-tabs@5.2.0
 - @react-navigation/native@5.2.5
 - @react-navigation/routers@5.4.3
 - @react-navigation/stack@5.3.0
2020-05-08 16:34:03 +02:00
Satyajit Sahoo
f384706741 feat: use links in bottom navigation tabs 2020-05-08 16:11:24 +02:00
Satyajit Sahoo
d1a6f3e30e chore: upgrade depenendecies 2020-05-08 16:06:28 +02:00
Satyajit Sahoo
fd6636a8cd chore: update circleci config 2020-05-08 03:19:47 +02:00
Satyajit Sahoo
eb24fea8b9 chore: upgrade depenendecies 2020-05-07 21:08:55 +02:00
Linus Unnebäck
85ae378d8c fix: return a promise-like from getInitialState (#8210) 2020-05-07 20:56:55 +02:00
Satyajit Sahoo
bea14aa26f feat: add generic type aliases for screen props
closes #7971
2020-05-06 19:00:04 +02:00
Satyajit Sahoo
4d1e102f8c fix: include safe are insets in title's margins 2020-05-06 16:49:02 +02:00
Satyajit Sahoo
f07cd13561 fix: add proper margins to the header title 2020-05-06 16:14:40 +02:00
Satyajit Sahoo
f6d06768d3 fix: avoid cleaning up state when a new navigator is mounted. fixes #8195 2020-05-06 15:49:59 +02:00
Satyajit Sahoo
3381d680d7 chore: publish
- @react-navigation/bottom-tabs@5.3.4
 - @react-navigation/compat@5.1.15
 - @react-navigation/core@5.5.0
 - @react-navigation/drawer@5.6.4
 - @react-navigation/material-bottom-tabs@5.1.15
 - @react-navigation/material-top-tabs@5.1.15
 - @react-navigation/native@5.2.4
 - @react-navigation/stack@5.2.19
2020-05-05 20:07:13 +02:00
Wojciech Lewicki
fcd1cc64c1 feat: add support for optional params to linking (#8196) 2020-05-05 17:18:34 +02:00
Wojciech Lewicki
3999fc2836 feat: support params anywhere in path segement (#8184) 2020-05-04 15:07:27 +02:00
Satyajit Sahoo
9fd2635756 fix: return undefined for buildLink if linking is not enabled 2020-05-04 06:35:22 +02:00
Satyajit Sahoo
6bec620a3f chore: publish
- @react-navigation/bottom-tabs@5.3.3
 - @react-navigation/compat@5.1.14
 - @react-navigation/drawer@5.6.3
 - @react-navigation/material-bottom-tabs@5.1.14
 - @react-navigation/material-top-tabs@5.1.14
 - @react-navigation/native@5.2.3
 - @react-navigation/stack@5.2.18
2020-05-01 17:31:59 +02:00
Satyajit Sahoo
c7b8e2e966 fix: default linking enabled to true 2020-05-01 17:28:41 +02:00
Satyajit Sahoo
719e1a7b46 chore: publish
- @react-navigation/bottom-tabs@5.3.2
 - @react-navigation/compat@5.1.13
 - @react-navigation/drawer@5.6.2
 - @react-navigation/material-bottom-tabs@5.1.13
 - @react-navigation/material-top-tabs@5.1.13
 - @react-navigation/native@5.2.2
 - @react-navigation/stack@5.2.17
2020-05-01 16:51:12 +02:00
Satyajit Sahoo
10eca8b92e fix: don't throw when using 'useLinking'. fixes #8171 2020-05-01 16:49:06 +02:00
Satyajit Sahoo
b66e3436a7 chore: publish
- @react-navigation/bottom-tabs@5.3.1
 - @react-navigation/compat@5.1.12
 - @react-navigation/drawer@5.6.1
 - @react-navigation/material-bottom-tabs@5.1.12
 - @react-navigation/material-top-tabs@5.1.12
 - @react-navigation/native@5.2.1
 - @react-navigation/stack@5.2.16
2020-05-01 00:28:55 +02:00
Satyajit Sahoo
1c075ffb16 fix: render fallback only if linking is enabled. closes #8161 2020-05-01 00:27:42 +02:00
Satyajit Sahoo
1ee3038a4d chore: publish
- @react-navigation/bottom-tabs@5.3.0
 - @react-navigation/compat@5.1.11
 - @react-navigation/core@5.4.0
 - @react-navigation/drawer@5.6.0
 - @react-navigation/material-bottom-tabs@5.1.11
 - @react-navigation/material-top-tabs@5.1.11
 - @react-navigation/native@5.2.0
 - @react-navigation/routers@5.4.2
 - @react-navigation/stack@5.2.15
2020-04-30 23:01:46 +02:00
Evan Bacon
961b519fb1 chore: create _redirects for netlify deploy (#8160) 2020-04-30 23:01:21 +02:00
Satyajit Sahoo
0a19e94b23 fix: make sure the address bar hides when scrolling on web
This commit adds a check to detect if the screen content fills the available body, and if yes, then it adjusts the styles so that scrolling triggers a scroll on the body which hides the address bar in browser.

Tested on Safari in iOS and Chrome on Android.

This behaviour can be overriden by the user by specifying `cardStyle: { flex: 1 }`, which will keep both the header and the address bar always visible.
2020-04-30 21:53:17 +02:00
Evan Bacon
1e73fed6de chore: fix scrolling in web examples (#8020) 2020-04-30 13:17:55 +02:00
Satyajit Sahoo
3193a30da6 refactor: add missing methods to container navigation prop 2020-04-29 19:14:24 +02:00
Satyajit Sahoo
499c50cd43 refactor: make history type-checked 2020-04-29 19:13:14 +02:00
ainar
420f6926e1 fix: fix backBehavior with initialRoute (#8110) 2020-04-29 13:37:15 +02:00
Satyajit Sahoo
70be3f6d86 fix: fix closing drawer on web with tap on overlay 2020-04-29 13:05:30 +02:00
WoLewicki
bd35b4fc20 fix: parsing url 2020-04-29 12:52:30 +02:00
Satyajit Sahoo
c511bc0b2b refactor: stub gesture handler on web
Gesture handler doesn't work great on Web and causes issues such as disabling text selection even when not enabled. So we stub it out. It also reduces bundle size on web.
2020-04-29 12:49:46 +02:00
Satyajit Sahoo
b4834ce703 chore: replace AsyncStorage with localStorage on web 2020-04-29 02:16:11 +02:00
Satyajit Sahoo
ae5442ebe8 fix: return onPress instead of onClick for useLinkProps 2020-04-28 23:05:16 +02:00
Satyajit Sahoo
6dd52d35cf refactor: simplify resolving the thenable 2020-04-28 16:14:58 +02:00
Satyajit Sahoo
d6fa279d93 fix: add catch to thenable returned by getInitialState 2020-04-28 15:35:06 +02:00
Satyajit Sahoo
c3fa83efe0 fix: handle empty paths when parsing 2020-04-28 15:12:43 +02:00
Satyajit Sahoo
f2291d110f feat: add a useLinkProps hook 2020-04-27 17:45:20 +02:00
Satyajit Sahoo
942d2be2c7 feat: add action prop to Link 2020-04-27 17:45:20 +02:00
Satyajit Sahoo
b747e527a4 refactor: remove onLink prop for now 2020-04-27 17:45:20 +02:00
Satyajit Sahoo
38020de80b refactor: simplify API for useLinkBuilder 2020-04-27 17:45:20 +02:00
Satyajit Sahoo
67404f4999 test: configure playwright for e2e tests 2020-04-27 17:45:20 +02:00
Satyajit Sahoo
2792f438fe feat: add useLinkBuilder hook to build links
We need to be able to create links from a navigate action to have accessible links in the built-in components such as drawer and tabs.
2020-04-27 17:45:20 +02:00
satyajit.happy
2573b5beaa feat: add Link component as useLinkTo hook for navigating to links
The `Link` component can be used to navigate to URLs. On web, it'll use an `a` tag for proper accessibility. On React Native, it'll use a `Text`.

Example:

```js
<Link to="/feed/hot">Go to 🔥</Link>
```

Sometimes we might want more complex styling and more control over the behaviour, or navigate to a URL programmatically. The `useLinkTo` hook can be used for that.

Example:

```js
function LinkButton({ to, ...rest }) {
  const linkTo = useLinkTo();

  return (
    <Button
      {...rest}
      href={to}
      onPress={(e) => {
        e.preventDefault();
        linkTo(to);
      }}
    />
  );
}
```
2020-04-27 17:45:20 +02:00
Satyajit Sahoo
2697355ab2 chore: publish
- @react-navigation/bottom-tabs@5.2.8
 - @react-navigation/compat@5.1.10
 - @react-navigation/core@5.3.5
 - @react-navigation/drawer@5.5.1
 - @react-navigation/material-bottom-tabs@5.1.10
 - @react-navigation/material-top-tabs@5.1.10
 - @react-navigation/native@5.1.7
 - @react-navigation/routers@5.4.1
 - @react-navigation/stack@5.2.14
2020-04-27 02:57:03 +02:00
Satyajit Sahoo
a695cf9c05 fix: don't add back the route being replaced 2020-04-27 02:41:46 +02:00
Satyajit Sahoo
c9c825bee6 fix: add config to enable redux devtools integration 2020-04-25 21:46:57 +02:00
Satyajit Sahoo
b172b51f17 fix: fix behaviour of openByDefault in drawer when focus changes 2020-04-23 20:00:47 +02:00
Satyajit Sahoo
9c05af50b4 test: add more tests for TabRouter and history 2020-04-23 18:11:30 +02:00
Satyajit Sahoo
24febf6ea9 fix: spread parent params to children in compat navigator
fixes #6785
2020-04-23 14:10:26 +02:00
Satyajit Sahoo
8cbb201f1a fix: fix typo in navigationOptions 2020-04-23 13:51:40 +02:00
Satyajit Sahoo
2467ce4ff7 chore: publish
- @react-navigation/stack@5.2.13
2020-04-22 17:57:16 +02:00
Satyajit Sahoo
5683bebfd6 chore: publish
- @react-navigation/stack@5.2.12
2020-04-22 16:26:11 +02:00
Satyajit Sahoo
78485cea69 fix: animate card to existing closing state on gesture end
fixes #7938
2020-04-22 15:16:39 +02:00
Satyajit Sahoo
1613915669 chore: mark screens and masked view as optional in stack
Needs e54819c4de to work.
2020-04-22 14:02:21 +02:00
Satyajit Sahoo
335a04edc1 chore: add action to check package versions 2020-04-20 14:35:07 +02:00
113 changed files with 6252 additions and 1680 deletions

View File

@@ -1,58 +1,96 @@
version: 2
version: 2.1
defaults: &defaults
docker:
- image: circleci/node:10
working_directory: ~/project
executors:
default:
docker:
- image: circleci/node:10
working_directory: ~/project
environment:
YARN_CACHE_FOLDER: "~/.cache/yarn"
commands:
attach_project:
steps:
- attach_workspace:
at: ~/project
jobs:
install-dependencies:
<<: *defaults
executor: default
steps:
- checkout
- attach_workspace:
at: ~/project
- attach_project
- restore_cache:
keys:
- v1-dependencies-{{ checksum "yarn.lock" }}
- v1-dependencies-
- run: yarn install --frozen-lockfile
- v2-dependencies-{{ checksum "yarn.lock" }}
- v2-dependencies-
- run:
name: Install project dependencies
command: yarn install --frozen-lockfile
- save_cache:
key: v1-dependencies-{{ checksum "yarn.lock" }}
paths: node_modules
key: v2-dependencies-{{ checksum "yarn.lock" }}
paths: ~/.cache/yarn
- persist_to_workspace:
root: .
paths: .
lint-and-typecheck:
<<: *defaults
steps:
- attach_workspace:
at: ~/project
- run: |
yarn lint
yarn typescript
executor: default
steps:
- attach_project
- run:
name: Lint files
command: yarn lint
- run:
name: Typecheck files
command: yarn typescript
unit-tests:
<<: *defaults
steps:
- attach_workspace:
at: ~/project
- run: |
yarn test --coverage
cat ./coverage/lcov.info | ./node_modules/.bin/codecov
- store_artifacts:
path: coverage
destination: coverage
executor: default
steps:
- attach_project
- run:
name: Run unit tests
command: yarn test --coverage
- run:
name: Upload test coverage
command: cat ./coverage/lcov.info | ./node_modules/.bin/codecov
- store_artifacts:
path: coverage
destination: coverage
integration-tests:
executor: default
steps:
- attach_project
- run:
name: Install Headless Chrome dependencies
command: |
sudo apt-get install -yq \
gconf-service libasound2 libatk1.0-0 libatk-bridge2.0-0 libc6 libcairo2 libcups2 libdbus-1-3 \
libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 \
libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 \
libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 ca-certificates \
fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget
- run:
name: Build example for web
command: yarn example expo build:web --no-pwa
- run:
name: Run integration tests
command: yarn example test --maxWorkers=2
build-packages:
<<: *defaults
steps:
- attach_workspace:
at: ~/project
- run: |
yarn lerna run prepare
node scripts/check-types-path.js
executor: default
steps:
- attach_project
- run:
name: Build packages in the monorepo
command: yarn lerna run prepare
- run:
name: Verify paths for types
command: node scripts/check-types-path.js
workflows:
version: 2
build-and-test:
jobs:
- install-dependencies
@@ -62,6 +100,9 @@ workflows:
- unit-tests:
requires:
- install-dependencies
- integration-tests:
requires:
- install-dependencies
- build-packages:
requires:
- install-dependencies

27
.github/workflows/versions.yml vendored Normal file
View File

@@ -0,0 +1,27 @@
name: Check versions
on:
issues:
types: [opened]
jobs:
check-versions:
runs-on: ubuntu-latest
steps:
- uses: react-navigation/check-versions-action@master
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
packages: |
@react-navigation/bottom-tabs
@react-navigation/compat
@react-navigation/core
@react-navigation/drawer
@react-navigation/material-bottom-tabs
@react-navigation/material-top-tabs
@react-navigation/native
@react-navigation/routers
@react-navigation/stack
react-navigation-animated-switch
react-navigation-drawer
react-navigation-material-bottom-tabs
react-navigation-stack
react-navigation-tabs

View File

@@ -1,10 +0,0 @@
{
"settings": {
"import/core-modules": [
"detox",
"detox/runners/jest/adapter",
"detox/runners/jest/specReporter"
]
},
"env": { "jest": true, "jasmine": true }
}

View File

@@ -0,0 +1,44 @@
import { page } from '../config/setup-playwright';
beforeEach(async () => {
await page.click('[data-testid=LinkComponent]');
});
it('loads the article page', async () => {
expect(await page.evaluate(() => location.pathname + location.search)).toBe(
'/link-component/Article?author=Gandalf'
);
expect(
((await page.accessibility.snapshot()) as any)?.children?.find(
(it: any) => it.role === 'heading'
)?.name
).toBe('Article by Gandalf');
});
it('goes to the album page and goes back', async () => {
await page.click('[href="/link-component/Album"]');
expect(await page.evaluate(() => location.pathname + location.search)).toBe(
'/link-component/Album'
);
expect(
((await page.accessibility.snapshot()) as any)?.children?.find(
(it: any) => it.role === 'heading'
)?.name
).toBe('Album');
await page.click('[aria-label="Article by Gandalf, back"]');
await page.waitForNavigation();
expect(await page.evaluate(() => location.pathname + location.search)).toBe(
'/link-component/Article?author=Gandalf'
);
expect(
((await page.accessibility.snapshot()) as any)?.children?.find(
(it: any) => it.role === 'heading'
)?.name
).toBe('Article by Gandalf');
});

View File

@@ -1,9 +0,0 @@
import { by, element, expect, device } from 'detox';
beforeEach(async () => {
await device.reloadReactNative();
});
it('has dark theme toggle', async () => {
await expect(element(by.text('Dark theme'))).toBeVisible();
});

View File

@@ -0,0 +1,13 @@
import { page } from '../config/setup-playwright';
it('loads the example app', async () => {
const snapshot = await page.accessibility.snapshot();
// @ts-ignore
expect(snapshot?.children?.find((it) => it.role === 'heading')?.name).toBe(
'Examples'
);
const title = await page.$eval('[role=heading]', (el) => el.textContent);
expect(title).toBe('Examples');
});

View File

@@ -1,6 +0,0 @@
{
"setupFilesAfterEnv": ["./init.js"],
"testEnvironment": "node",
"reporters": ["detox/runners/jest/streamlineReporter"],
"verbose": true
}

View File

@@ -0,0 +1,24 @@
/* eslint-env jest */
import { chromium, Browser, BrowserContext, Page } from 'playwright';
let browser: Browser;
let context: BrowserContext;
let page: Page;
beforeAll(async () => {
browser = await chromium.launch();
});
afterAll(async () => {
await browser.close();
});
beforeEach(async () => {
context = await browser.newContext();
page = await context.newPage();
await page.goto('http://localhost:3579');
});
export { browser, context, page };

View File

@@ -0,0 +1,8 @@
import { setup } from 'jest-dev-server';
export default async function () {
await setup({
command: 'yarn serve -l 3579 web-build',
port: 3579,
});
}

View File

@@ -0,0 +1,5 @@
import { teardown } from 'jest-dev-server';
export default async function () {
await teardown();
}

View File

@@ -1,28 +0,0 @@
/* eslint-disable import/no-commonjs */
const detox = require('detox');
const config = require('../../package.json').detox;
const adapter = require('detox/runners/jest/adapter');
const specReporter = require('detox/runners/jest/specReporter');
// Set the default timeout
jest.setTimeout(120000);
jasmine.getEnv().addReporter(adapter);
// This takes care of generating status logs on a per-spec basis. By default, jest only reports at file-level.
// This is strictly optional.
jasmine.getEnv().addReporter(specReporter);
beforeAll(async () => {
await detox.init(config);
}, 300000);
beforeEach(async () => {
await adapter.beforeEach();
});
afterAll(async () => {
await adapter.afterAll();
await detox.cleanup();
});

6
example/jest.config.js Normal file
View File

@@ -0,0 +1,6 @@
module.exports = {
testRegex: '/__integration_tests__/.*\\.(test|spec)\\.(js|tsx?)$',
globalSetup: './e2e/config/setup-server.tsx',
globalTeardown: './e2e/config/teardown-server.tsx',
setupFilesAfterEnv: ['./e2e/config/setup-playwright.tsx'],
};

View File

@@ -8,34 +8,40 @@
"web": "expo start:web",
"native": "react-native start",
"android": "react-native run-android",
"ios": "react-native run-ios"
"ios": "react-native run-ios",
"test": "jest"
},
"dependencies": {
"@expo/vector-icons": "^10.0.0",
"@react-native-community/masked-view": "^0.1.7",
"@expo/vector-icons": "^10.2.0",
"@react-native-community/masked-view": "^0.1.10",
"color": "^3.1.2",
"expo": "^37.0.0",
"expo": "^37.0.8",
"expo-asset": "~8.1.3",
"expo-blur": "~8.1.0",
"react": "~16.9.0",
"react-dom": "~16.9.0",
"react-native": "~0.61.5",
"react-native-gesture-handler": "^1.6.0",
"react-native-paper": "^3.7.0",
"react-native-reanimated": "^1.7.0",
"react-native-restart": "^0.0.14",
"react-native-paper": "^3.10.1",
"react-native-reanimated": "^1.8.0",
"react-native-restart": "^0.0.15",
"react-native-safe-area-context": "^0.7.3",
"react-native-screens": "^2.3.0",
"react-native-screens": "^2.7.0",
"react-native-tab-view": "2.14.0",
"react-native-unimodules": "~0.8.1",
"react-native-unimodules": "~0.9.1",
"react-native-web": "^0.11.7"
},
"devDependencies": {
"@expo/webpack-config": "^0.11.19",
"@types/react": "^16.9.23",
"@types/react-native": "^0.60.22",
"@types/jest-dev-server": "^4.2.0",
"@types/react": "^16.9.34",
"@types/react-native": "^0.62.7",
"babel-preset-expo": "^8.1.0",
"expo-cli": "^3.17.18",
"expo-cli": "^3.20.1",
"jest": "^26.0.1",
"jest-dev-server": "^4.4.0",
"playwright": "^0.14.0",
"serve": "^11.3.0",
"typescript": "^3.8.3"
}
}

View File

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

View File

@@ -0,0 +1,14 @@
export default {
getItem(key: string) {
return Promise.resolve(localStorage.getItem(key));
},
setItem(key: string, value: string) {
return Promise.resolve(localStorage.setItem(key, value));
},
removeItem(key: string) {
return Promise.resolve(localStorage.removeItem(key));
},
clear() {
return Promise.resolve(localStorage.clear());
},
};

View File

@@ -1,4 +1,5 @@
import * as React from 'react';
import { Platform } from 'react-native';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import TouchableBounce from '../Shared/TouchableBounce';
@@ -28,7 +29,10 @@ export default function BottomTabsScreen() {
return (
<BottomTabs.Navigator
screenOptions={{
tabBarButton: (props) => <TouchableBounce {...props} />,
tabBarButton:
Platform.OS === 'web'
? undefined
: (props) => <TouchableBounce {...props} />,
}}
>
<BottomTabs.Screen

View File

@@ -1,5 +1,5 @@
import * as React from 'react';
import { View, StyleSheet } from 'react-native';
import { View, ScrollView, StyleSheet, Platform } from 'react-native';
import { Button } from 'react-native-paper';
import {
createCompatNavigatorFactory,
@@ -11,25 +11,32 @@ import {
} from '@react-navigation/stack';
import Article from '../Shared/Article';
import Albums from '../Shared/Albums';
import NewsFeed from '../Shared/NewsFeed';
type CompatStackParams = {
Article: { author: string };
Album: undefined;
Albums: undefined;
Nested: { author: string };
};
const ArticleScreen: CompatScreenType<StackNavigationProp<
CompatStackParams,
'Article'
type NestedStackParams = {
Feed: undefined;
Article: { author: string };
};
const scrollEnabled = Platform.select({ web: true, default: false });
const AlbumsScreen: CompatScreenType<StackNavigationProp<
CompatStackParams
>> = ({ navigation }) => {
return (
<React.Fragment>
<ScrollView>
<View style={styles.buttons}>
<Button
mode="contained"
onPress={() => navigation.push('Album')}
onPress={() => navigation.push('Nested', { author: 'Babel fish' })}
style={styles.button}
>
Push album
Push nested
</Button>
<Button
mode="outlined"
@@ -39,24 +46,20 @@ const ArticleScreen: CompatScreenType<StackNavigationProp<
Go back
</Button>
</View>
<Article author={{ name: navigation.getParam('author') }} />
</React.Fragment>
<Albums scrollEnabled={scrollEnabled} />
</ScrollView>
);
};
ArticleScreen.navigationOptions = ({ navigation }) => ({
title: `Article by ${navigation.getParam('author')}`,
});
const AlbumsScreen: CompatScreenType<StackNavigationProp<
CompatStackParams
>> = ({ navigation }) => {
const FeedScreen: CompatScreenType<StackNavigationProp<NestedStackParams>> = ({
navigation,
}) => {
return (
<React.Fragment>
<ScrollView>
<View style={styles.buttons}>
<Button
mode="contained"
onPress={() => navigation.push('Article', { author: 'Babel fish' })}
onPress={() => navigation.push('Article')}
style={styles.button}
>
Push article
@@ -69,22 +72,69 @@ const AlbumsScreen: CompatScreenType<StackNavigationProp<
Go back
</Button>
</View>
<Albums />
</React.Fragment>
<NewsFeed scrollEnabled={scrollEnabled} />
</ScrollView>
);
};
const CompatStack = createCompatNavigatorFactory(createStackNavigator)<
const ArticleScreen: CompatScreenType<StackNavigationProp<
NestedStackParams,
'Article'
>> = ({ navigation }) => {
navigation.dangerouslyGetParent();
return (
<ScrollView>
<View style={styles.buttons}>
<Button
mode="contained"
onPress={() => navigation.push('Albums')}
style={styles.button}
>
Push albums
</Button>
<Button
mode="outlined"
onPress={() => navigation.goBack()}
style={styles.button}
>
Go back
</Button>
</View>
<Article
author={{ name: navigation.getParam('author') }}
scrollEnabled={scrollEnabled}
/>
</ScrollView>
);
};
ArticleScreen.navigationOptions = ({ navigation }) => ({
title: `Article by ${navigation.getParam('author')}`,
});
const createCompatStackNavigator = createCompatNavigatorFactory(
createStackNavigator
);
const CompatStack = createCompatStackNavigator<
StackNavigationProp<CompatStackParams>
>(
{
Article: {
screen: ArticleScreen,
Albums: AlbumsScreen,
Nested: {
screen: createCompatStackNavigator<
StackNavigationProp<NestedStackParams>
>(
{
Feed: FeedScreen,
Article: ArticleScreen,
},
{ navigationOptions: { headerShown: false } }
),
params: {
author: 'Gandalf',
},
},
Album: AlbumsScreen,
},
{
mode: 'modal',

View File

@@ -0,0 +1,162 @@
import * as React from 'react';
import { View, StyleSheet, ScrollView, Platform } from 'react-native';
import { Button } from 'react-native-paper';
import {
Link,
StackActions,
RouteProp,
ParamListBase,
useLinkProps,
} from '@react-navigation/native';
import {
createStackNavigator,
StackNavigationProp,
} from '@react-navigation/stack';
import Article from '../Shared/Article';
import Albums from '../Shared/Albums';
type SimpleStackParams = {
Article: { author: string };
Album: undefined;
};
type SimpleStackNavigation = StackNavigationProp<SimpleStackParams>;
const scrollEnabled = Platform.select({ web: true, default: false });
const LinkButton = ({
to,
...rest
}: React.ComponentProps<typeof Button> & { to: string }) => {
const { onPress, ...props } = useLinkProps({ to });
return (
<Button
{...props}
{...rest}
{...Platform.select({
web: { onClick: onPress } as any,
default: { onPress },
})}
/>
);
};
const ArticleScreen = ({
navigation,
route,
}: {
navigation: SimpleStackNavigation;
route: RouteProp<SimpleStackParams, 'Article'>;
}) => {
return (
<ScrollView>
<View style={styles.buttons}>
<Link
to="/link-component/Album"
style={[styles.button, { padding: 8 }]}
>
Go to /link-component/Album
</Link>
<Link
to="/link-component/Album"
action={StackActions.replace('Album')}
style={[styles.button, { padding: 8 }]}
>
Replace with /link-component/Album
</Link>
<LinkButton
to="/link-component/Album"
mode="contained"
style={styles.button}
>
Go to /link-component/Album
</LinkButton>
<Button
mode="outlined"
onPress={() => navigation.goBack()}
style={styles.button}
>
Go back
</Button>
</View>
<Article
author={{ name: route.params.author }}
scrollEnabled={scrollEnabled}
/>
</ScrollView>
);
};
const AlbumsScreen = ({
navigation,
}: {
navigation: SimpleStackNavigation;
}) => {
return (
<ScrollView>
<View style={styles.buttons}>
<Link
to="/link-component/Article?author=Babel"
style={[styles.button, { padding: 8 }]}
>
Go to /link-component/Article
</Link>
<LinkButton
to="/link-component/Article?author=Babel"
mode="contained"
style={styles.button}
>
Go to /link-component/Article
</LinkButton>
<Button
mode="outlined"
onPress={() => navigation.goBack()}
style={styles.button}
>
Go back
</Button>
</View>
<Albums scrollEnabled={scrollEnabled} />
</ScrollView>
);
};
const SimpleStack = createStackNavigator<SimpleStackParams>();
type Props = Partial<React.ComponentProps<typeof SimpleStack.Navigator>> & {
navigation: StackNavigationProp<ParamListBase>;
};
export default function SimpleStackScreen({ navigation, ...rest }: Props) {
navigation.setOptions({
headerShown: false,
});
return (
<SimpleStack.Navigator {...rest}>
<SimpleStack.Screen
name="Article"
component={ArticleScreen}
options={({ route }) => ({
title: `Article by ${route.params.author}`,
})}
initialParams={{ author: 'Gandalf' }}
/>
<SimpleStack.Screen
name="Album"
component={AlbumsScreen}
options={{ title: 'Album' }}
/>
</SimpleStack.Navigator>
);
}
const styles = StyleSheet.create({
buttons: {
padding: 8,
},
button: {
margin: 8,
},
});

View File

@@ -1,5 +1,5 @@
import * as React from 'react';
import { View, StyleSheet, ScrollView } from 'react-native';
import { View, StyleSheet, ScrollView, Platform } from 'react-native';
import { Button } from 'react-native-paper';
import { RouteProp, ParamListBase } from '@react-navigation/native';
import {
@@ -17,6 +17,8 @@ type ModalStackParams = {
type ModalStackNavigation = StackNavigationProp<ModalStackParams>;
const scrollEnabled = Platform.select({ web: true, default: false });
const ArticleScreen = ({
navigation,
route,
@@ -42,7 +44,10 @@ const ArticleScreen = ({
Go back
</Button>
</View>
<Article author={{ name: route.params.author }} scrollEnabled={false} />
<Article
author={{ name: route.params.author }}
scrollEnabled={scrollEnabled}
/>
</ScrollView>
);
};
@@ -66,7 +71,7 @@ const AlbumsScreen = ({ navigation }: { navigation: ModalStackNavigation }) => {
Go back
</Button>
</View>
<Albums scrollEnabled={false} />
<Albums scrollEnabled={scrollEnabled} />
</ScrollView>
);
};

View File

@@ -1,5 +1,5 @@
import * as React from 'react';
import { View, StyleSheet, ScrollView } from 'react-native';
import { View, Platform, StyleSheet, ScrollView } from 'react-native';
import { Button } from 'react-native-paper';
import { RouteProp, ParamListBase } from '@react-navigation/native';
import {
@@ -18,6 +18,8 @@ type SimpleStackParams = {
type SimpleStackNavigation = StackNavigationProp<SimpleStackParams>;
const scrollEnabled = Platform.select({ web: true, default: false });
const ArticleScreen = ({
navigation,
route,
@@ -43,7 +45,10 @@ const ArticleScreen = ({
Pop screen
</Button>
</View>
<Article author={{ name: route.params.author }} scrollEnabled={false} />
<Article
author={{ name: route.params.author }}
scrollEnabled={scrollEnabled}
/>
</ScrollView>
);
};
@@ -71,7 +76,7 @@ const NewsFeedScreen = ({
Go back
</Button>
</View>
<NewsFeed scrollEnabled={false} />
<NewsFeed scrollEnabled={scrollEnabled} />
</ScrollView>
);
};
@@ -99,7 +104,7 @@ const AlbumsScreen = ({
Pop by 2
</Button>
</View>
<Albums scrollEnabled={false} />
<Albums scrollEnabled={scrollEnabled} />
</ScrollView>
);
};

View File

@@ -20,6 +20,8 @@ type SimpleStackParams = {
type SimpleStackNavigation = StackNavigationProp<SimpleStackParams>;
const scrollEnabled = Platform.select({ web: true, default: false });
const ArticleScreen = ({
navigation,
route,
@@ -45,7 +47,10 @@ const ArticleScreen = ({
Go back
</Button>
</View>
<Article author={{ name: route.params.author }} scrollEnabled={false} />
<Article
author={{ name: route.params.author }}
scrollEnabled={scrollEnabled}
/>
</ScrollView>
);
};
@@ -75,7 +80,7 @@ const AlbumsScreen = ({
Go back
</Button>
</View>
<Albums scrollEnabled={false} />
<Albums scrollEnabled={scrollEnabled} />
</ScrollView>
);
};

View File

@@ -1,5 +1,5 @@
import * as React from 'react';
import { View, StyleSheet, ScrollView } from 'react-native';
import { View, StyleSheet, ScrollView, Platform } from 'react-native';
import { Button, Paragraph } from 'react-native-paper';
import { RouteProp, ParamListBase, useTheme } from '@react-navigation/native';
import {
@@ -15,6 +15,8 @@ type SimpleStackParams = {
type SimpleStackNavigation = StackNavigationProp<SimpleStackParams>;
const scrollEnabled = Platform.select({ web: true, default: false });
const ArticleScreen = ({
navigation,
route,
@@ -40,7 +42,10 @@ const ArticleScreen = ({
Go back
</Button>
</View>
<Article author={{ name: route.params.author }} scrollEnabled={false} />
<Article
author={{ name: route.params.author }}
scrollEnabled={scrollEnabled}
/>
</ScrollView>
);
};

View File

@@ -1,7 +1,6 @@
import * as React from 'react';
import {
ScrollView,
AsyncStorage,
YellowBox,
Platform,
StatusBar,
@@ -22,11 +21,10 @@ import {
Appbar,
List,
Divider,
Text,
} from 'react-native-paper';
import {
InitialState,
useLinking,
NavigationContainerRef,
NavigationContainer,
DefaultTheme,
DarkTheme,
@@ -42,6 +40,7 @@ import {
HeaderStyleInterpolators,
} from '@react-navigation/stack';
import AsyncStorage from './AsyncStorage';
import LinkingPrefixes from './LinkingPrefixes';
import SettingsItem from './Shared/SettingsItem';
import SimpleStack from './Screens/SimpleStack';
@@ -55,11 +54,15 @@ import DynamicTabs from './Screens/DynamicTabs';
import AuthFlow from './Screens/AuthFlow';
import CompatAPI from './Screens/CompatAPI';
import MasterDetail from './Screens/MasterDetail';
import LinkComponent from './Screens/LinkComponent';
YellowBox.ignoreWarnings(['Require cycle:', 'Warning: Async Storage']);
enableScreens();
// @ts-ignore
global.REACT_NAVIGATION_REDUX_DEVTOOLS_EXTENSION_INTEGRATION_ENABLED = true;
type RootDrawerParamList = {
Root: undefined;
Another: undefined;
@@ -110,6 +113,10 @@ const SCREENS = {
title: 'Compat Layer',
component: CompatAPI,
},
LinkComponent: {
title: '<Link />',
component: LinkComponent,
},
};
const Drawer = createDrawerNavigator<RootDrawerParamList>();
@@ -121,36 +128,6 @@ const THEME_PERSISTENCE_KEY = 'THEME_TYPE';
Asset.loadAsync(StackAssets);
export default function App() {
const containerRef = React.useRef<NavigationContainerRef>(null);
// To test deep linking on, run the following in the Terminal:
// Android: adb shell am start -a android.intent.action.VIEW -d "exp://127.0.0.1:19000/--/simple-stack"
// iOS: xcrun simctl openurl booted exp://127.0.0.1:19000/--/simple-stack
// Android (bare): adb shell am start -a android.intent.action.VIEW -d "rne://127.0.0.1:19000/--/simple-stack"
// iOS (bare): xcrun simctl openurl booted rne://127.0.0.1:19000/--/simple-stack
// The first segment of the link is the the scheme + host (returned by `Linking.makeUrl`)
const { getInitialState } = useLinking(containerRef, {
prefixes: LinkingPrefixes,
config: {
Root: {
path: '',
initialRouteName: 'Home',
screens: Object.keys(SCREENS).reduce<{ [key: string]: string }>(
(acc, name) => {
// Convert screen names such as SimpleStack to kebab case (simple-stack)
acc[name] = name
.replace(/([A-Z]+)/g, '-$1')
.replace(/^-/, '')
.toLowerCase();
return acc;
},
{ Home: '' }
),
},
},
});
const [theme, setTheme] = React.useState(DefaultTheme);
const [isReady, setIsReady] = React.useState(false);
@@ -161,12 +138,13 @@ export default function App() {
React.useEffect(() => {
const restoreState = async () => {
try {
let state = await getInitialState();
let state;
if (Platform.OS !== 'web' && state === undefined) {
const savedState = await AsyncStorage.getItem(
NAVIGATION_PERSISTENCE_KEY
);
state = savedState ? JSON.parse(savedState) : undefined;
}
@@ -187,7 +165,7 @@ export default function App() {
};
restoreState();
}, [getInitialState]);
}, []);
const paperTheme = React.useMemo(() => {
const t = theme.dark ? PaperDarkTheme : PaperLightTheme;
@@ -227,7 +205,6 @@ export default function App() {
<StatusBar barStyle={theme.dark ? 'light-content' : 'dark-content'} />
)}
<NavigationContainer
ref={containerRef}
initialState={initialState}
onStateChange={(state) =>
AsyncStorage.setItem(
@@ -236,6 +213,34 @@ export default function App() {
)
}
theme={theme}
linking={{
// To test deep linking on, run the following in the Terminal:
// Android: adb shell am start -a android.intent.action.VIEW -d "exp://127.0.0.1:19000/--/simple-stack"
// iOS: xcrun simctl openurl booted exp://127.0.0.1:19000/--/simple-stack
// Android (bare): adb shell am start -a android.intent.action.VIEW -d "rne://127.0.0.1:19000/--/simple-stack"
// iOS (bare): xcrun simctl openurl booted rne://127.0.0.1:19000/--/simple-stack
// The first segment of the link is the the scheme + host (returned by `Linking.makeUrl`)
prefixes: LinkingPrefixes,
config: {
Root: {
path: '',
initialRouteName: 'Home',
screens: Object.keys(SCREENS).reduce<{ [key: string]: string }>(
(acc, name) => {
// Convert screen names such as SimpleStack to kebab case (simple-stack)
acc[name] = name
.replace(/([A-Z]+)/g, '-$1')
.replace(/^-/, '')
.toLowerCase();
return acc;
},
{ Home: '' }
),
},
},
}}
fallback={<Text>Loading</Text>}
>
<Drawer.Navigator drawerType={isLargeScreen ? 'permanent' : undefined}>
<Drawer.Screen
@@ -311,6 +316,7 @@ export default function App() {
(name) => (
<List.Item
key={name}
testID={name}
title={SCREENS[name].title}
onPress={() => navigation.navigate(name)}
/>

1
example/web/_redirects Normal file
View File

@@ -0,0 +1 @@
/* /index.html 200

View File

@@ -24,19 +24,21 @@ module.exports = async function (env, argv) {
);
Object.assign(config.resolve.alias, {
react: path.resolve(node_modules, 'react'),
'react': path.resolve(node_modules, 'react'),
'react-native': path.resolve(node_modules, 'react-native-web'),
'react-native-web': path.resolve(node_modules, 'react-native-web'),
'@expo/vector-icons': path.resolve(node_modules, '@expo/vector-icons'),
});
fs.readdirSync(packages).forEach((name) => {
config.resolve.alias[`@react-navigation/${name}`] = path.resolve(
packages,
name,
'src'
);
});
fs.readdirSync(packages)
.filter((name) => !name.startsWith('.'))
.forEach((name) => {
config.resolve.alias[`@react-navigation/${name}`] = path.resolve(
packages,
name,
require(`../packages/${name}/package.json`).source
);
});
return config;
};

View File

@@ -27,23 +27,23 @@
"devDependencies": {
"@babel/plugin-proposal-class-properties": "^7.8.3",
"@babel/plugin-proposal-optional-chaining": "^7.9.0",
"@babel/preset-env": "^7.9.0",
"@babel/preset-env": "^7.9.6",
"@babel/preset-flow": "^7.9.0",
"@babel/preset-react": "^7.9.4",
"@babel/preset-typescript": "^7.9.0",
"@babel/runtime": "^7.9.2",
"@babel/runtime": "^7.9.6",
"@commitlint/config-conventional": "^8.3.4",
"@types/jest": "^25.2.1",
"babel-jest": "^25.2.6",
"babel-jest": "^26.0.1",
"codecov": "^3.6.5",
"commitlint": "^8.3.5",
"core-js": "^3.6.4",
"core-js": "^3.6.5",
"eslint": "^6.8.0",
"eslint-config-satya164": "^3.1.6",
"husky": "^4.2.3",
"jest": "^25.2.7",
"eslint-config-satya164": "^3.1.7",
"husky": "^4.2.5",
"jest": "^26.0.1",
"lerna": "^3.20.2",
"prettier": "^2.0.4",
"prettier": "^2.0.5",
"typescript": "^3.8.3"
},
"resolutions": {

View File

@@ -3,6 +3,80 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [5.4.1](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.4.0...@react-navigation/bottom-tabs@5.4.1) (2020-05-08)
### Bug Fixes
* fix building typescript definitions. closes [#8216](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/issues/8216) ([47a1229](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/commit/47a12298378747edd2d22e54dc1c8677f98c49b4))
# [5.4.0](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.3.4...@react-navigation/bottom-tabs@5.4.0) (2020-05-08)
### Features
* add generic type aliases for screen props ([bea14aa](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/commit/bea14aa26fd5cbfebc7973733c5cf1f44fd323aa)), closes [#7971](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/issues/7971)
## [5.3.4](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.3.3...@react-navigation/bottom-tabs@5.3.4) (2020-05-05)
**Note:** Version bump only for package @react-navigation/bottom-tabs
## [5.3.3](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.3.2...@react-navigation/bottom-tabs@5.3.3) (2020-05-01)
**Note:** Version bump only for package @react-navigation/bottom-tabs
## [5.3.2](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.3.1...@react-navigation/bottom-tabs@5.3.2) (2020-05-01)
**Note:** Version bump only for package @react-navigation/bottom-tabs
## [5.3.1](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.3.0...@react-navigation/bottom-tabs@5.3.1) (2020-04-30)
**Note:** Version bump only for package @react-navigation/bottom-tabs
# [5.3.0](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.2.8...@react-navigation/bottom-tabs@5.3.0) (2020-04-30)
### Features
* add `useLinkBuilder` hook to build links ([2792f43](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/commit/2792f438fe45428fe193e3708fee7ad61966cbf4))
* add action prop to Link ([942d2be](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/commit/942d2be2c72720469475ce12ec8df23825994dbf))
## [5.2.8](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.2.7...@react-navigation/bottom-tabs@5.2.8) (2020-04-27)
**Note:** Version bump only for package @react-navigation/bottom-tabs
## [5.2.7](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.2.6...@react-navigation/bottom-tabs@5.2.7) (2020-04-17)
**Note:** Version bump only for package @react-navigation/bottom-tabs

View File

@@ -1,7 +1,7 @@
{
"name": "@react-navigation/bottom-tabs",
"description": "Bottom tab navigator following iOS design guidelines",
"version": "5.2.7",
"version": "5.4.1",
"keywords": [
"react-native-component",
"react-component",
@@ -15,6 +15,7 @@
"repository": "https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs",
"main": "lib/commonjs/index.js",
"react-native": "src/index.tsx",
"source": "src/index.tsx",
"module": "lib/module/index.js",
"types": "lib/typescript/src/index.d.ts",
"files": [
@@ -34,16 +35,16 @@
"react-native-iphone-x-helper": "^1.2.1"
},
"devDependencies": {
"@react-native-community/bob": "^0.10.0",
"@react-navigation/native": "^5.1.6",
"@react-native-community/bob": "^0.13.1",
"@react-navigation/native": "^5.2.6",
"@types/color": "^3.0.1",
"@types/react": "^16.9.23",
"@types/react-native": "^0.61.22",
"@types/react": "^16.9.34",
"@types/react-native": "^0.62.7",
"del-cli": "^3.0.0",
"react": "~16.9.0",
"react-native": "~0.61.5",
"react-native-safe-area-context": "^0.7.3",
"react-native-screens": "^2.3.0",
"react-native-screens": "^2.7.0",
"typescript": "^3.8.3"
},
"peerDependencies": {

View File

@@ -15,6 +15,7 @@ export { default as BottomTabBar } from './views/BottomTabBar';
export type {
BottomTabNavigationOptions,
BottomTabNavigationProp,
BottomTabScreenProps,
BottomTabBarProps,
BottomTabBarOptions,
} from './types';

View File

@@ -4,6 +4,7 @@ import {
StyleProp,
TextStyle,
ViewStyle,
GestureResponderEvent,
} from 'react-native';
import {
NavigationHelpers,
@@ -12,6 +13,7 @@ import {
Descriptor,
TabNavigationState,
TabActionHelpers,
RouteProp,
} from '@react-navigation/native';
export type BottomTabNavigationEventMap = {
@@ -44,6 +46,14 @@ export type BottomTabNavigationProp<
> &
TabActionHelpers<ParamList>;
export type BottomTabScreenProps<
ParamList extends ParamListBase,
RouteName extends keyof ParamList = string
> = {
navigation: BottomTabNavigationProp<ParamList, RouteName>;
route: RouteProp<ParamList, RouteName>;
};
export type BottomTabNavigationOptions = {
/**
* Title text for the screen.
@@ -196,6 +206,13 @@ export type BottomTabBarProps = BottomTabBarOptions & {
navigation: NavigationHelpers<ParamListBase, BottomTabNavigationEventMap>;
};
export type BottomTabBarButtonProps = TouchableWithoutFeedbackProps & {
export type BottomTabBarButtonProps = Omit<
TouchableWithoutFeedbackProps,
'onPress'
> & {
to?: string;
children: React.ReactNode;
onPress?: (
e: React.MouseEvent<HTMLAnchorElement, MouseEvent> | GestureResponderEvent
) => void;
};

View File

@@ -14,6 +14,7 @@ import {
NavigationRouteContext,
CommonActions,
useTheme,
useLinkBuilder,
} from '@react-navigation/native';
import { useSafeArea } from 'react-native-safe-area-context';
@@ -50,6 +51,7 @@ export default function BottomTabBar({
tabStyle,
}: Props) {
const { colors } = useTheme();
const buildLink = useLinkBuilder();
const [dimensions, setDimensions] = React.useState(() => {
const { height = 0, width = 0 } = Dimensions.get('window');
@@ -260,6 +262,7 @@ export default function BottomTabBar({
onPress={onPress}
onLongPress={onLongPress}
accessibilityLabel={accessibilityLabel}
to={buildLink(route.name, route.params)}
testID={options.tabBarTestID}
allowFontScaling={allowFontScaling}
activeTintColor={activeTintColor}

View File

@@ -4,11 +4,13 @@ import {
TouchableWithoutFeedback,
Animated,
StyleSheet,
Platform,
StyleProp,
ViewStyle,
TextStyle,
GestureResponderEvent,
} from 'react-native';
import { Route, useTheme } from '@react-navigation/native';
import { Link, Route, useTheme } from '@react-navigation/native';
import Color from 'color';
import TabBarIcon from './TabBarIcon';
@@ -37,6 +39,10 @@ type Props = {
size: number;
color: string;
}) => React.ReactNode;
/**
* URL to use for the link to the tab.
*/
to?: string;
/**
* The button for the tab. Uses a `TouchableWithoutFeedback` by default.
*/
@@ -50,13 +56,16 @@ type Props = {
*/
testID?: string;
/**
* Function to execute on press.
* Function to execute on press in React Native.
* On the web, this will use onClick.
*/
onPress: () => void;
onPress: (
e: React.MouseEvent<HTMLElement, MouseEvent> | GestureResponderEvent
) => void;
/**
* Function to execute on long press.
*/
onLongPress: () => void;
onLongPress: (e: GestureResponderEvent) => void;
/**
* Whether the label should be aligned with the icon horizontally.
*/
@@ -104,11 +113,48 @@ export default function BottomTabBarItem({
route,
label,
icon,
button = ({ children, style, ...rest }: BottomTabBarButtonProps) => (
<TouchableWithoutFeedback {...rest}>
<View style={style}>{children}</View>
</TouchableWithoutFeedback>
),
to,
button = ({
children,
style,
onPress,
to,
accessibilityRole,
...rest
}: BottomTabBarButtonProps) => {
if (Platform.OS === 'web' && to) {
// React Native Web doesn't forward `onClick` if we use `TouchableWithoutFeedback`.
// We need to use `onClick` to be able to prevent default browser handling of links.
return (
<Link
{...rest}
to={to}
style={[styles.button, style]}
onPress={(e: any) => {
if (
!(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) && // ignore clicks with modifier keys
(e.button == null || e.button === 0) // ignore everything but left clicks
) {
e.preventDefault();
onPress?.(e);
}
}}
>
{children}
</Link>
);
} else {
return (
<TouchableWithoutFeedback
{...rest}
accessibilityRole={accessibilityRole}
onPress={onPress}
>
<View style={style}>{children}</View>
</TouchableWithoutFeedback>
);
}
},
accessibilityLabel,
testID,
onPress,
@@ -196,6 +242,7 @@ export default function BottomTabBarItem({
: inactiveBackgroundColor;
return button({
to,
onPress,
onLongPress,
testID,
@@ -248,4 +295,7 @@ const styles = StyleSheet.create({
fontSize: 12,
marginLeft: 20,
},
button: {
display: 'flex',
},
});

View File

@@ -1,7 +1,11 @@
import * as React from 'react';
import { View, StyleSheet } from 'react-native';
import { TabNavigationState, useTheme } from '@react-navigation/native';
import {
NavigationHelpersContext,
TabNavigationState,
useTheme,
} from '@react-navigation/native';
// eslint-disable-next-line import/no-unresolved
import { ScreenContainer } from 'react-native-screens';
@@ -91,44 +95,46 @@ export default class BottomTabView extends React.Component<Props, State> {
};
render() {
const { state, descriptors, lazy } = this.props;
const { state, descriptors, navigation, lazy } = this.props;
const { routes } = state;
const { loaded } = this.state;
return (
<SafeAreaProviderCompat>
<View style={styles.container}>
<ScreenContainer style={styles.pages}>
{routes.map((route, index) => {
const descriptor = descriptors[route.key];
const { unmountOnBlur } = descriptor.options;
const isFocused = state.index === index;
<NavigationHelpersContext.Provider value={navigation}>
<SafeAreaProviderCompat>
<View style={styles.container}>
<ScreenContainer style={styles.pages}>
{routes.map((route, index) => {
const descriptor = descriptors[route.key];
const { unmountOnBlur } = descriptor.options;
const isFocused = state.index === index;
if (unmountOnBlur && !isFocused) {
return null;
}
if (unmountOnBlur && !isFocused) {
return null;
}
if (lazy && !loaded.includes(index) && !isFocused) {
// Don't render a screen if we've never navigated to it
return null;
}
if (lazy && !loaded.includes(index) && !isFocused) {
// Don't render a screen if we've never navigated to it
return null;
}
return (
<ResourceSavingScene
key={route.key}
style={StyleSheet.absoluteFill}
isVisible={isFocused}
>
<SceneContent isFocused={isFocused}>
{descriptor.render()}
</SceneContent>
</ResourceSavingScene>
);
})}
</ScreenContainer>
{this.renderTabBar()}
</View>
</SafeAreaProviderCompat>
return (
<ResourceSavingScene
key={route.key}
style={StyleSheet.absoluteFill}
isVisible={isFocused}
>
<SceneContent isFocused={isFocused}>
{descriptor.render()}
</SceneContent>
</ResourceSavingScene>
);
})}
</ScreenContainer>
{this.renderTabBar()}
</View>
</SafeAreaProviderCompat>
</NavigationHelpersContext.Provider>
);
}
}

View File

@@ -3,6 +3,77 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [5.1.17](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.16...@react-navigation/compat@5.1.17) (2020-05-08)
### Bug Fixes
* fix building typescript definitions. closes [#8216](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/issues/8216) ([47a1229](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/commit/47a12298378747edd2d22e54dc1c8677f98c49b4))
## [5.1.16](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.15...@react-navigation/compat@5.1.16) (2020-05-08)
**Note:** Version bump only for package @react-navigation/compat
## [5.1.15](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.14...@react-navigation/compat@5.1.15) (2020-05-05)
**Note:** Version bump only for package @react-navigation/compat
## [5.1.14](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.13...@react-navigation/compat@5.1.14) (2020-05-01)
**Note:** Version bump only for package @react-navigation/compat
## [5.1.13](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.12...@react-navigation/compat@5.1.13) (2020-05-01)
**Note:** Version bump only for package @react-navigation/compat
## [5.1.12](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.11...@react-navigation/compat@5.1.12) (2020-04-30)
**Note:** Version bump only for package @react-navigation/compat
## [5.1.11](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.10...@react-navigation/compat@5.1.11) (2020-04-30)
**Note:** Version bump only for package @react-navigation/compat
## [5.1.10](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.9...@react-navigation/compat@5.1.10) (2020-04-27)
### Bug Fixes
* fix typo in navigationOptions ([8cbb201](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/commit/8cbb201f1a7fb90e45a078df6bc42ce4771cc6a6))
* spread parent params to children in compat navigator ([24febf6](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/commit/24febf6ea99be2e5f22005fdd2a82136d647255c)), closes [#6785](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/issues/6785)
## [5.1.9](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.8...@react-navigation/compat@5.1.9) (2020-04-17)
**Note:** Version bump only for package @react-navigation/compat

View File

@@ -1,7 +1,7 @@
{
"name": "@react-navigation/compat",
"description": "Compatibility layer to write navigator definitions in static configuration format",
"version": "5.1.9",
"version": "5.1.17",
"license": "MIT",
"repository": "https://github.com/react-navigation/react-navigation/tree/master/packages/compat",
"bugs": {
@@ -10,6 +10,7 @@
"homepage": "https://reactnavigation.org/docs/compatibility.html",
"main": "lib/commonjs/index.js",
"react-native": "src/index.tsx",
"source": "src/index.tsx",
"module": "lib/module/index.js",
"types": "lib/typescript/src/index.d.ts",
"files": [
@@ -25,9 +26,9 @@
"clean": "del lib"
},
"devDependencies": {
"@react-native-community/bob": "^0.10.0",
"@react-navigation/native": "^5.1.6",
"@types/react": "^16.9.23",
"@react-native-community/bob": "^0.13.1",
"@react-navigation/native": "^5.2.6",
"@types/react": "^16.9.34",
"react": "~16.9.0",
"typescript": "^3.8.3"
},

View File

@@ -7,6 +7,7 @@ import {
NavigationProp,
RouteProp,
EventMapBase,
NavigationRouteContext,
} from '@react-navigation/native';
import CompatScreen from './CompatScreen';
import ScreenPropsContext from './ScreenPropsContext';
@@ -67,6 +68,9 @@ export default function createCompatNavigatorFactory<
const routeNames = order !== undefined ? order : Object.keys(routeConfig);
function Navigator({ screenProps }: { screenProps?: unknown }) {
const parentRouteParams = React.useContext(NavigationRouteContext)
?.params;
const screens = React.useMemo(
() =>
routeNames.map((name) => {
@@ -135,7 +139,7 @@ export default function createCompatNavigatorFactory<
<Pair.Screen
key={name}
name={name}
initialParams={initialParams}
initialParams={{ ...parentRouteParams, ...initialParams }}
options={screenOptions}
>
{({ navigation, route }) => (
@@ -148,7 +152,7 @@ export default function createCompatNavigatorFactory<
</Pair.Screen>
);
}),
[screenProps]
[parentRouteParams, screenProps]
);
return (
@@ -163,7 +167,7 @@ export default function createCompatNavigatorFactory<
);
}
Navigator.navigationOtions = parentNavigationOptions;
Navigator.navigationOptions = parentNavigationOptions;
return Navigator;
};

View File

@@ -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.5.2](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.5.1...@react-navigation/core@5.5.2) (2020-05-08)
### Bug Fixes
* fix building typescript definitions. closes [#8216](https://github.com/react-navigation/react-navigation/tree/master/packages/core/issues/8216) ([47a1229](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/47a12298378747edd2d22e54dc1c8677f98c49b4))
## [5.5.1](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.5.0...@react-navigation/core@5.5.1) (2020-05-08)
### Bug Fixes
* avoid cleaning up state when a new navigator is mounted. fixes [#8195](https://github.com/react-navigation/react-navigation/tree/master/packages/core/issues/8195) ([f6d0676](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/f6d06768d3c36d1f5beaffcb660f3c259209f2e7))
# [5.5.0](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.4.0...@react-navigation/core@5.5.0) (2020-05-05)
### Features
* add support for optional params to linking ([#8196](https://github.com/react-navigation/react-navigation/tree/master/packages/core/issues/8196)) ([fcd1cc6](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/fcd1cc64c151e4941f3f544a54b5048d853821f6))
* support params anywhere in path segement ([#8184](https://github.com/react-navigation/react-navigation/tree/master/packages/core/issues/8184)) ([3999fc2](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/3999fc28365c3a06a17d963c7be7fb7e897f99e0))
# [5.4.0](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.3.5...@react-navigation/core@5.4.0) (2020-04-30)
### Bug Fixes
* handle empty paths when parsing ([c3fa83e](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/c3fa83efe0d73db76365f8be3d6a8ca1d1289b71))
* parsing url ([bd35b4f](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/bd35b4fc202c3868fb75c3675b62de67557089e1))
### Features
* add `useLinkBuilder` hook to build links ([2792f43](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/2792f438fe45428fe193e3708fee7ad61966cbf4))
## [5.3.5](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.3.4...@react-navigation/core@5.3.5) (2020-04-27)
### Bug Fixes
* add config to enable redux devtools integration ([c9c825b](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/c9c825bee61426635a28ee149eeeff3d628171cd))
## [5.3.4](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.3.3...@react-navigation/core@5.3.4) (2020-04-17)

View File

@@ -1,7 +1,7 @@
{
"name": "@react-navigation/core",
"description": "Core utilities for building navigators",
"version": "5.3.4",
"version": "5.5.2",
"keywords": [
"react",
"react-native",
@@ -15,6 +15,7 @@
"homepage": "https://reactnavigation.org",
"main": "lib/commonjs/index.js",
"react-native": "src/index.tsx",
"source": "src/index.tsx",
"module": "lib/module/index.js",
"types": "lib/typescript/src/index.d.ts",
"files": [
@@ -29,21 +30,21 @@
"clean": "del lib"
},
"dependencies": {
"@react-navigation/routers": "^5.4.0",
"escape-string-regexp": "^2.0.0",
"nanoid": "^3.0.2",
"query-string": "^6.12.0",
"@react-navigation/routers": "^5.4.4",
"escape-string-regexp": "^4.0.0",
"nanoid": "^3.1.5",
"query-string": "^6.12.1",
"react-is": "^16.13.0",
"use-subscription": "^1.4.0"
},
"devDependencies": {
"@react-native-community/bob": "^0.10.0",
"@types/react": "^16.9.23",
"@react-native-community/bob": "^0.13.1",
"@types/react": "^16.9.34",
"@types/react-is": "^16.7.1",
"@types/use-subscription": "^1.0.0",
"del-cli": "^3.0.0",
"react": "~16.9.0",
"react-native-testing-library": "^1.12.0",
"react-native-testing-library": "^1.13.2",
"react-test-renderer": "~16.13.1",
"typescript": "^3.8.3"
},

View File

@@ -21,6 +21,9 @@ import { NavigationContainerRef, NavigationContainerProps } from './types';
type State = NavigationState | PartialState<NavigationState> | undefined;
const DEVTOOLS_CONFIG_KEY =
'REACT_NAVIGATION_REDUX_DEVTOOLS_EXTENSION_INTEGRATION_ENABLED';
const MISSING_CONTEXT_ERROR =
"Couldn't find a navigation context. Have you wrapped your app with 'NavigationContainer'? See https://reactnavigation.org/docs/getting-started for setup instructions.";
@@ -143,7 +146,9 @@ const BaseNavigationContainer = React.forwardRef(
);
const { trackState, trackAction } = useDevTools({
enabled: false,
enabled:
// @ts-ignore
DEVTOOLS_CONFIG_KEY in global ? global[DEVTOOLS_CONFIG_KEY] : false,
name: '@react-navigation',
reset,
state,
@@ -214,6 +219,8 @@ const BaseNavigationContainer = React.forwardRef(
dispatch,
canGoBack,
getRootState,
dangerouslyGetState: () => state,
dangerouslyGetParent: () => undefined,
}));
const builderContext = React.useMemo(

View File

@@ -0,0 +1,13 @@
import * as React from 'react';
import { ParamListBase } from '@react-navigation/routers';
import { NavigationHelpers } from './types';
/**
* Context which holds the navigation helpers of the parent navigator.
* Navigators should use this context in their view component.
*/
const NavigationHelpersContext = React.createContext<
NavigationHelpers<ParamListBase> | undefined
>(undefined);
export default NavigationHelpersContext;

View File

@@ -426,6 +426,49 @@ it('ignores empty string paths', () => {
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
});
it('keeps query params if path is empty', () => {
const path = '/?foo=42';
const config = {
Foo: {
screens: {
Foe: 'foe',
Bar: {
screens: {
Qux: {
path: '',
parse: { foo: Number },
},
Baz: 'baz',
},
},
},
},
};
const state = {
routes: [
{
name: 'Foo',
state: {
routes: [
{
name: 'Bar',
state: {
routes: [{ name: 'Qux', params: { foo: 42 } }],
},
},
],
},
},
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState(getStateFromPath(path, config), config)).toEqual(
path
);
});
it('cuts nested configs too', () => {
const path = '/baz';
const config = {
@@ -495,6 +538,8 @@ it('handles empty path at the end', () => {
});
it('returns "/" for empty path', () => {
const path = '/';
const config = {
Foo: {
path: '',
@@ -519,7 +564,8 @@ it('returns "/" for empty path', () => {
],
};
expect(getPathFromState(state, config)).toBe('/');
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
});
it('parses no path specified', () => {

File diff suppressed because it is too large Load Diff

View File

@@ -379,6 +379,8 @@ it("doesn't update state if action wasn't handled", () => {
});
it('cleans up state when the navigator unmounts', () => {
jest.useFakeTimers();
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
@@ -426,6 +428,8 @@ it('cleans up state when the navigator unmounts', () => {
<BaseNavigationContainer onStateChange={onStateChange} children={null} />
);
act(() => jest.runAllTimers());
expect(onStateChange).toBeCalledTimes(2);
expect(onStateChange).lastCalledWith(undefined);
});

View File

@@ -115,70 +115,68 @@ export default function getPathFromState(
pattern = nestedRouteNames.substring(1);
}
// we don't add empty path strings to path
if (pattern !== '') {
const config =
currentOptions[route.name] !== undefined
? (currentOptions[route.name] as { stringify?: StringifyConfig })
.stringify
: undefined;
const params = route.params
? // Stringify all of the param values before we use them
Object.entries(route.params).reduce<{
[key: string]: string;
}>((acc, [key, value]) => {
acc[key] = config?.[key] ? config[key](value) : String(value);
return acc;
}, {})
const config =
currentOptions[route.name] !== undefined
? (currentOptions[route.name] as { stringify?: StringifyConfig })
.stringify
: undefined;
if (currentOptions[route.name] !== undefined) {
path += pattern
.split('/')
.map((p) => {
const name = p.replace(/^:/, '');
const params = route.params
? // Stringify all of the param values before we use them
Object.entries(route.params).reduce<{
[key: string]: string;
}>((acc, [key, value]) => {
acc[key] = config?.[key] ? config[key](value) : String(value);
return acc;
}, {})
: undefined;
// If the path has a pattern for a param, put the param in the path
if (params && name in params && p.startsWith(':')) {
const value = params[name];
// Remove the used value from the params object since we'll use the rest for query string
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete params[name];
return encodeURIComponent(value);
}
if (currentOptions[route.name] !== undefined) {
path += pattern
.split('/')
.map((p) => {
const name = p.replace(/^:/, '').replace(/\?$/, '');
return encodeURIComponent(p);
})
.join('/');
} else {
path += encodeURIComponent(route.name);
}
if (route.state) {
path += '/';
} else if (params) {
for (let param in params) {
if (params[param] === 'undefined') {
// If the path has a pattern for a param, put the param in the path
if (params && name in params && p.startsWith(':')) {
const value = params[name];
// Remove the used value from the params object since we'll use the rest for query string
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete params[param];
delete params[name];
return encodeURIComponent(value);
} else if (p.endsWith('?')) {
// optional params without value assigned in route.params should be ignored
return '';
}
}
const query = queryString.stringify(params);
return encodeURIComponent(p);
})
.join('/');
} else {
path += encodeURIComponent(route.name);
}
if (query) {
path += `?${query}`;
if (route.state) {
path += '/';
} else if (params) {
for (let param in params) {
if (params[param] === 'undefined') {
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete params[param];
}
}
const query = queryString.stringify(params);
if (query) {
path += `?${query}`;
}
}
current = route.state;
}
path =
path !== '/' && path.slice(path.length - 1) === '/'
? path.slice(0, -1)
: path;
// Remove multiple as well as trailing slashes
path = path.replace(/\/+/g, '/');
path = path.length > 1 ? path.replace(/\/$/, '') : path;
return path;
}

View File

@@ -20,7 +20,8 @@ type Options = {
};
type RouteConfig = {
match: RegExp;
screen: string;
match: RegExp | null;
pattern: string;
routeNames: string[];
parse: ParseConfig | undefined;
@@ -58,10 +59,8 @@ export default function getStateFromPath(
path: string,
options: Options = {}
): ResultState | undefined {
if (path === '') {
return undefined;
}
let initialRoutes: InitialRouteConfig[] = [];
// Create a normalized configs array which will be easier to use
const configs = ([] as RouteConfig[]).concat(
...Object.keys(options).map((key) =>
@@ -69,13 +68,45 @@ export default function getStateFromPath(
)
);
let result: PartialState<NavigationState> | undefined;
let current: PartialState<NavigationState> | undefined;
// sort configs so the most exhaustive is always first to be chosen
configs.sort(
(config1, config2) =>
config2.pattern.split('/').length - config1.pattern.split('/').length
);
let remaining = path
.replace(/[/]+/, '/') // Replace multiple slash (//) with single ones
.replace(/\/+/g, '/') // Replace multiple slash (//) with single ones
.replace(/^\//, '') // Remove extra leading slash
.replace(/\?.*/, ''); // Remove query params which we will handle later
.replace(/\?.*$/, ''); // Remove query params which we will handle later
// Make sure there is a trailing slash
remaining = remaining.endsWith('/') ? remaining : `${remaining}/`;
if (remaining === '/') {
// We need to add special handling of empty path so navigation to empty path also works
// When handling empty path, we should only look at the root level config
const match = configs.find(
(config) =>
config.pattern === '' &&
config.routeNames.every(
// make sure that none of the parent configs have a non-empty path defined
(name) => !configs.find((c) => c.screen === name)?.pattern
)
);
if (match) {
return createNestedStateObject(
match.routeNames,
initialRoutes,
parseQueryParams(path, match.parse)
);
}
return undefined;
}
let result: PartialState<NavigationState> | undefined;
let current: PartialState<NavigationState> | undefined;
while (remaining) {
let routeNames: string[] | undefined;
@@ -83,6 +114,10 @@ export default function getStateFromPath(
// Go through all configs, and see if the next path segment matches our regex
for (const config of configs) {
if (!config.match) {
continue;
}
const match = remaining.match(config.match);
// If our regex matches, we need to extract params from the path
@@ -95,20 +130,21 @@ export default function getStateFromPath(
if (paramPatterns.length) {
params = paramPatterns.reduce<Record<string, any>>((acc, p, i) => {
const key = p.replace(/^:/, '');
const value = match[i + 1]; // The param segments start from index 1 in the regex match result
const key = p.replace(/^:/, '').replace(/\?$/, '');
const value = match[(i + 1) * 2].replace(/\//, ''); // The param segments appear every second item starting from 2 in the regex match result
acc[key] =
config.parse && config.parse[key]
? config.parse[key](value)
: value;
if (value) {
acc[key] =
config.parse && config.parse[key]
? config.parse[key](value)
: value;
}
return acc;
}, {});
}
// Remove the matched segment from the remaining path
remaining = remaining.replace(match[0], '');
remaining = remaining.replace(match[1], '');
break;
}
@@ -123,34 +159,7 @@ export default function getStateFromPath(
remaining = segments.join('/');
}
let state: InitialState;
let routeName = routeNames.shift() as string;
let initialRoute = findInitialRoute(routeName, initialRoutes);
state = createNestedState(
initialRoute,
routeName,
routeNames.length === 0,
params
);
if (routeNames.length > 0) {
let nestedState = state;
while ((routeName = routeNames.shift() as string)) {
initialRoute = findInitialRoute(routeName, initialRoutes);
nestedState.routes[nestedState.index || 0].state = createNestedState(
initialRoute,
routeName,
routeNames.length === 0,
params
);
if (routeNames.length > 0) {
nestedState = nestedState.routes[nestedState.index || 0]
.state as InitialState;
}
}
}
const state = createNestedStateObject(routeNames, initialRoutes, params);
if (current) {
// The state should be nested inside the deepest route we parsed before
@@ -172,29 +181,13 @@ export default function getStateFromPath(
return undefined;
}
const query = path.split('?')[1];
if (query) {
while (current?.routes[current.index || 0].state) {
// The query params apply to the deepest route
current = current.routes[current.index || 0].state;
}
const route = (current as PartialState<NavigationState>).routes[
current?.index || 0
];
const params = queryString.parse(query);
const parseFunction = findParseConfigForRoute(route.name, configs);
if (parseFunction) {
Object.keys(params).forEach((name) => {
if (parseFunction[name] && typeof params[name] === 'string') {
params[name] = parseFunction[name](params[name] as string);
}
});
}
const route = findFocusedRoute(current);
const params = parseQueryParams(
path,
findParseConfigForRoute(route.name, configs)
);
if (params) {
route.params = { ...route.params, ...params };
}
@@ -215,16 +208,15 @@ function createNormalizedConfigs(
if (typeof value === 'string') {
// If a string is specified as the value of the key(e.g. Foo: '/path'), use it as the pattern
if (value !== '') {
configs.push(createConfigItem(routeNames, value));
}
configs.push(createConfigItem(key, routeNames, value));
} else if (typeof value === 'object') {
// if an object is specified as the value (e.g. Foo: { ... }),
// it can have `path` property and
// it could have `screens` prop which has nested configs
if (value.path && value.path !== '') {
configs.push(createConfigItem(routeNames, value.path, value.parse));
if (typeof value.path === 'string') {
configs.push(createConfigItem(key, routeNames, value.path, value.parse));
}
if (value.screens) {
// property `initialRouteName` without `screens` has no purpose
if (value.initialRouteName) {
@@ -251,15 +243,28 @@ function createNormalizedConfigs(
}
function createConfigItem(
screen: string,
routeNames: string[],
pattern: string,
parse?: ParseConfig
): RouteConfig {
const match = new RegExp(
'^' + escape(pattern).replace(/:[a-z0-9]+/gi, '([^/]+)') + '/?'
);
const match = pattern
? new RegExp(
`^(${pattern
.split('/')
.map((it) => {
if (it.startsWith(':')) {
return `(([^/]+\\/)${it.endsWith('?') ? '?' : ''})`;
}
return `${escape(it)}\\/`;
})
.join('')})`
)
: null;
return {
screen,
match,
pattern,
// The routeNames array is mutated, so copy it to keep the current state
@@ -295,9 +300,9 @@ function findInitialRoute(
return undefined;
}
// returns nested state object with values depending on whether
// returns state object with values depending on whether
// it is the end of state and if there is initialRoute for this level
function createNestedState(
function createStateObject(
initialRoute: string | undefined,
routeName: string,
isEmpty: boolean,
@@ -331,3 +336,73 @@ function createNestedState(
}
}
}
function createNestedStateObject(
routeNames: string[],
initialRoutes: InitialRouteConfig[],
params: object | undefined
) {
let state: InitialState;
let routeName = routeNames.shift() as string;
let initialRoute = findInitialRoute(routeName, initialRoutes);
state = createStateObject(
initialRoute,
routeName,
routeNames.length === 0,
params
);
if (routeNames.length > 0) {
let nestedState = state;
while ((routeName = routeNames.shift() as string)) {
initialRoute = findInitialRoute(routeName, initialRoutes);
nestedState.routes[nestedState.index || 0].state = createStateObject(
initialRoute,
routeName,
routeNames.length === 0,
params
);
if (routeNames.length > 0) {
nestedState = nestedState.routes[nestedState.index || 0]
.state as InitialState;
}
}
}
return state;
}
function findFocusedRoute(state: InitialState) {
let current: InitialState | undefined = state;
while (current?.routes[current.index || 0].state) {
// The query params apply to the deepest route
current = current.routes[current.index || 0].state;
}
const route = (current as PartialState<NavigationState>).routes[
current?.index || 0
];
return route;
}
function parseQueryParams(
path: string,
parseConfig?: Record<string, (value: string) => any>
) {
const query = path.split('?')[1];
const params = queryString.parse(query);
if (parseConfig) {
Object.keys(params).forEach((name) => {
if (parseConfig[name] && typeof params[name] === 'string') {
params[name] = parseConfig[name](params[name] as string);
}
});
}
return Object.keys(params).length ? params : undefined;
}

View File

@@ -3,6 +3,7 @@ export * from '@react-navigation/routers';
export { default as BaseNavigationContainer } from './BaseNavigationContainer';
export { default as createNavigatorFactory } from './createNavigatorFactory';
export { default as NavigationHelpersContext } from './NavigationHelpersContext';
export { default as NavigationContext } from './NavigationContext';
export { default as NavigationRouteContext } from './NavigationRouteContext';

View File

@@ -193,6 +193,20 @@ type NavigationHelpersCommon<
* Note that this method doesn't re-render screen when the result changes. So don't use it in `render`.
*/
canGoBack(): boolean;
/**
* Returns the parent navigator, if any. Reason why the function is called
* dangerouslyGetParent is to warn developers against overusing it to eg. get parent
* of parent and other hard-to-follow patterns.
*/
dangerouslyGetParent<T = NavigationProp<ParamListBase> | undefined>(): T;
/**
* Returns the navigator's state. Reason why the function is called
* dangerouslyGetState is to discourage developers to use internal navigation's state.
* Note that this method doesn't re-render screen when the result changes. So don't use it in `render`.
*/
dangerouslyGetState(): State;
} & PrivateValueStore<ParamList, keyof ParamList, {}>;
export type NavigationHelpers<
@@ -254,20 +268,6 @@ export type NavigationProp<
* @param options Options object for the route.
*/
setOptions(options: Partial<ScreenOptions>): void;
/**
* Returns the parent navigator, if any. Reason why the function is called
* dangerouslyGetParent is to warn developers against overusing it to eg. get parent
* of parent and other hard-to-follow patterns.
*/
dangerouslyGetParent<T = NavigationProp<ParamListBase> | undefined>(): T;
/**
* Returns the navigator's state. Reason why the function is called
* dangerouslyGetState is to discourage developers to use internal navigation's state.
* Note that this method doesn't re-render screen when the result changes. So don't use it in `render`.
*/
dangerouslyGetState(): State;
} & EventConsumer<EventMap & EventMapCore<State>> &
PrivateValueStore<ParamList, RouteName, EventMap>;

View File

@@ -9,6 +9,7 @@ import useNavigation from './useNavigation';
*/
export default function useIsFocused(): boolean {
const navigation = useNavigation();
// eslint-disable-next-line react-hooks/exhaustive-deps
const getCurrentValue = React.useCallback(navigation.isFocused, [navigation]);
const subscribe = React.useCallback(
(callback: (value: boolean) => void) => {

View File

@@ -362,9 +362,14 @@ export default function useNavigationBuilder<
return () => {
// We need to clean up state for this navigator on unmount
if (getCurrentState() !== undefined && getKey() === navigatorKey) {
setState(undefined);
}
// We do it in a timeout because we need to detect if another navigator mounted in the meantime
// For example, if another navigator has started rendering, we should skip cleanup
// Otherwise, our cleanup step will cleanup state for the other navigator and re-initialize it
setTimeout(() => {
if (getCurrentState() !== undefined && getKey() === navigatorKey) {
setState(undefined);
}
}, 0);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

View File

@@ -7,7 +7,6 @@ import {
Router,
} from '@react-navigation/routers';
import { NavigationEventEmitter } from './useEventEmitter';
import NavigationContext from './NavigationContext';
import { NavigationHelpers, NavigationProp } from './types';
@@ -49,12 +48,10 @@ export default function useNavigationCache<
// Cache object which holds navigation objects for each screen
// We use `React.useMemo` instead of `React.useRef` coz we want to invalidate it when deps change
// In reality, these deps will rarely change, if ever
const parentNavigation = React.useContext(NavigationContext);
const cache = React.useMemo(
() => ({ current: {} as NavigationCache<State, ScreenOptions> }),
// eslint-disable-next-line react-hooks/exhaustive-deps
[getState, navigation, setOptions, router, emitter, parentNavigation]
[getState, navigation, setOptions, router, emitter]
);
const actions = {
@@ -99,8 +96,6 @@ export default function useNavigationCache<
...rest,
...helpers,
...emitter.create(route.key),
dangerouslyGetParent: () => parentNavigation as any,
dangerouslyGetState: getState,
dispatch,
setOptions: (options: object) =>
setOptions((o) => ({

View File

@@ -112,6 +112,8 @@ export default function useNavigationHelpers<
false
);
},
dangerouslyGetParent: () => parentNavigationHelpers as any,
dangerouslyGetState: getState,
} as NavigationHelpers<ParamListBase, EventMap> &
(NavigationProp<ParamListBase, string, any, any, any> | undefined);
}, [router, getState, parentNavigationHelpers, emitter.emit, onAction]);

View File

@@ -3,6 +3,86 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [5.7.1](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.7.0...@react-navigation/drawer@5.7.1) (2020-05-08)
### Bug Fixes
* fix building typescript definitions. closes [#8216](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/issues/8216) ([47a1229](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/47a12298378747edd2d22e54dc1c8677f98c49b4))
# [5.7.0](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.6.4...@react-navigation/drawer@5.7.0) (2020-05-08)
### Features
* add generic type aliases for screen props ([bea14aa](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/bea14aa26fd5cbfebc7973733c5cf1f44fd323aa)), closes [#7971](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/issues/7971)
## [5.6.4](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.6.3...@react-navigation/drawer@5.6.4) (2020-05-05)
**Note:** Version bump only for package @react-navigation/drawer
## [5.6.3](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.6.2...@react-navigation/drawer@5.6.3) (2020-05-01)
**Note:** Version bump only for package @react-navigation/drawer
## [5.6.2](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.6.1...@react-navigation/drawer@5.6.2) (2020-05-01)
**Note:** Version bump only for package @react-navigation/drawer
## [5.6.1](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.6.0...@react-navigation/drawer@5.6.1) (2020-04-30)
**Note:** Version bump only for package @react-navigation/drawer
# [5.6.0](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.5.1...@react-navigation/drawer@5.6.0) (2020-04-30)
### Bug Fixes
* fix closing drawer on web with tap on overlay ([70be3f6](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/70be3f6d863c56211e2f90bdf743bd8526338248))
* make sure the address bar hides when scrolling on web ([0a19e94](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/0a19e94b23a4d2b5f22d1d9deb0544f586f475ee))
### Features
* add `useLinkBuilder` hook to build links ([2792f43](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/2792f438fe45428fe193e3708fee7ad61966cbf4))
* add action prop to Link ([942d2be](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/942d2be2c72720469475ce12ec8df23825994dbf))
## [5.5.1](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.5.0...@react-navigation/drawer@5.5.1) (2020-04-27)
**Note:** Version bump only for package @react-navigation/drawer
# [5.5.0](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.4.1...@react-navigation/drawer@5.5.0) (2020-04-17)

View File

@@ -1,7 +1,7 @@
{
"name": "@react-navigation/drawer",
"description": "Drawer navigator component with animated transitions and gesturess",
"version": "5.5.0",
"version": "5.7.1",
"keywords": [
"react-native-component",
"react-component",
@@ -20,6 +20,7 @@
"homepage": "https://reactnavigation.org/docs/drawer-navigator.html",
"main": "lib/commonjs/index.js",
"react-native": "src/index.tsx",
"source": "src/index.tsx",
"module": "lib/module/index.js",
"types": "lib/typescript/src/index.d.ts",
"files": [
@@ -39,17 +40,17 @@
"react-native-iphone-x-helper": "^1.2.1"
},
"devDependencies": {
"@react-native-community/bob": "^0.10.0",
"@react-navigation/native": "^5.1.6",
"@types/react": "^16.9.23",
"@types/react-native": "^0.61.22",
"@react-native-community/bob": "^0.13.1",
"@react-navigation/native": "^5.2.6",
"@types/react": "^16.9.34",
"@types/react-native": "^0.62.7",
"del-cli": "^3.0.0",
"react": "~16.9.0",
"react-native": "~0.61.5",
"react-native-gesture-handler": "^1.6.0",
"react-native-reanimated": "^1.7.0",
"react-native-reanimated": "^1.8.0",
"react-native-safe-area-context": "^0.7.3",
"react-native-screens": "^2.3.0",
"react-native-screens": "^2.7.0",
"typescript": "^3.8.3"
},
"peerDependencies": {

View File

@@ -25,6 +25,7 @@ export { default as useIsDrawerOpen } from './utils/useIsDrawerOpen';
export type {
DrawerNavigationOptions,
DrawerNavigationProp,
DrawerScreenProps,
DrawerContentOptions,
DrawerContentComponentProps,
} from './types';

View File

@@ -8,8 +8,9 @@ import {
NavigationHelpers,
DrawerNavigationState,
DrawerActionHelpers,
RouteProp,
} from '@react-navigation/native';
import { PanGestureHandler } from 'react-native-gesture-handler';
import type { PanGestureHandlerProperties } from 'react-native-gesture-handler';
export type Scene = {
route: Route<string>;
@@ -32,6 +33,7 @@ export type DrawerNavigationConfig<T = DrawerContentOptions> = {
drawerType?: 'front' | 'back' | 'slide' | 'permanent';
/**
* How far from the edge of the screen the swipe gesture should activate.
* Not supported on Web.
*/
edgeWidth?: number;
/**
@@ -58,8 +60,9 @@ export type DrawerNavigationConfig<T = DrawerContentOptions> = {
statusBarAnimation?: 'slide' | 'none' | 'fade';
/**
* Props to pass to the underlying pan gesture handler.
* Not supported on Web.
*/
gestureHandlerProps?: React.ComponentProps<typeof PanGestureHandler>;
gestureHandlerProps?: PanGestureHandlerProperties;
/**
* Whether the screens should render the first time they are accessed. Defaults to `true`.
* Set it to `false` if you want to render all screens on initial render.
@@ -113,13 +116,15 @@ export type DrawerNavigationOptions = {
* Whether you can use gestures to open or close the drawer.
* Setting this to `false` disables swipe gestures as well as tap on overlay to close.
* See `swipeEnabled` to disable only the swipe gesture.
* Defaults to `true`
* Defaults to `true`.
* Not supported on Web.
*/
gestureEnabled?: boolean;
/**
* Whether you can use swipe gestures to open or close the drawer.
* Defaults to `true`
* Defaults to `true`.
* Not supported on Web.
*/
swipeEnabled?: boolean;
@@ -204,6 +209,14 @@ export type DrawerNavigationProp<
> &
DrawerActionHelpers<ParamList>;
export type DrawerScreenProps<
ParamList extends ParamListBase,
RouteName extends keyof ParamList = string
> = {
navigation: DrawerNavigationProp<ParamList, RouteName>;
route: RouteProp<ParamList, RouteName>;
};
export type DrawerDescriptor = Descriptor<
ParamListBase,
string,

View File

@@ -10,14 +10,14 @@ import {
StyleProp,
View,
InteractionManager,
TouchableWithoutFeedback,
} from 'react-native';
import Animated from 'react-native-reanimated';
import {
PanGestureHandler,
TapGestureHandler,
State as GestureState,
TapGestureHandlerStateChangeEvent,
} from 'react-native-gesture-handler';
import Animated from 'react-native-reanimated';
GestureState,
} from './GestureHandler';
import Overlay from './Overlay';
const {
@@ -79,7 +79,6 @@ type Props = {
open: boolean;
onOpen: () => void;
onClose: () => void;
onGestureRef?: (ref: PanGestureHandler | null) => void;
gestureEnabled: boolean;
swipeEnabled: boolean;
drawerPosition: 'left' | 'right';
@@ -511,28 +510,17 @@ export default class DrawerView extends React.Component<Props> {
},
]);
private handleTapStateChange =
Platform.OS === 'web'
? // FIXME: Drawer doesn't close on Web with the same code that we use for native
({ nativeEvent }: TapGestureHandlerStateChangeEvent) => {
if (
nativeEvent.state === GestureState.END &&
nativeEvent.oldState === GestureState.ACTIVE
) {
this.toggleDrawer(false);
}
}
: event([
{
nativeEvent: {
oldState: (s: Animated.Value<number>) =>
cond(
eq(s, GestureState.ACTIVE),
set(this.manuallyTriggerSpring, TRUE)
),
},
},
]);
private handleTapStateChange = event([
{
nativeEvent: {
oldState: (s: Animated.Value<number>) =>
cond(
eq(s, GestureState.ACTIVE),
set(this.manuallyTriggerSpring, TRUE)
),
},
},
]);
private handleContainerLayout = (e: LayoutChangeEvent) =>
this.containerWidth.setValue(e.nativeEvent.layout.width);
@@ -579,7 +567,6 @@ export default class DrawerView extends React.Component<Props> {
sceneContainerStyle,
drawerStyle,
overlayStyle,
onGestureRef,
renderDrawerContent,
renderSceneContent,
gestureHandlerProps,
@@ -624,7 +611,6 @@ export default class DrawerView extends React.Component<Props> {
return (
<PanGestureHandler
ref={onGestureRef}
activeOffsetX={[-SWIPE_DISTANCE_MINIMUM, SWIPE_DISTANCE_MINIMUM]}
failOffsetY={[-SWIPE_DISTANCE_MINIMUM, SWIPE_DISTANCE_MINIMUM]}
onGestureEvent={this.handleGestureEvent}
@@ -663,12 +649,20 @@ export default class DrawerView extends React.Component<Props> {
</View>
{
// Disable overlay if sidebar is permanent
drawerType === 'permanent' ? null : (
drawerType === 'permanent' ? null : Platform.OS === 'web' ? (
<TouchableWithoutFeedback
onPress={
gestureEnabled ? () => this.toggleDrawer(false) : undefined
}
>
<Overlay progress={progress} style={overlayStyle as any} />
</TouchableWithoutFeedback>
) : (
<TapGestureHandler
enabled={gestureEnabled}
onHandlerStateChange={this.handleTapStateChange}
>
<Overlay progress={progress} style={overlayStyle} />
<Overlay progress={progress} style={overlayStyle as any} />
</TapGestureHandler>
)
}
@@ -737,6 +731,11 @@ const styles = StyleSheet.create({
},
main: {
flex: 1,
overflow: 'hidden',
...Platform.select({
// FIXME: We need to hide `overflowX` on Web so the translated content doesn't show offscreen.
// But adding `overflowX: 'hidden'` prevents content from collapsing the URL bar.
web: null,
default: { overflow: 'hidden' },
}),
},
});

View File

@@ -6,8 +6,10 @@ import {
StyleProp,
ViewStyle,
TextStyle,
Platform,
TouchableWithoutFeedbackProps,
} from 'react-native';
import { useTheme } from '@react-navigation/native';
import { Link, useTheme } from '@react-navigation/native';
import Color from 'color';
import TouchableItem from './TouchableItem';
@@ -26,6 +28,10 @@ type Props = {
size: number;
color: string;
}) => React.ReactNode;
/**
* URL to use for the link to the tab.
*/
to?: string;
/**
* Whether to highlight the drawer item as active.
*/
@@ -60,6 +66,54 @@ type Props = {
style?: StyleProp<ViewStyle>;
};
const Touchable = ({
children,
style,
onPress,
to,
accessibilityRole,
delayPressIn,
...rest
}: TouchableWithoutFeedbackProps & {
to?: string;
children: React.ReactNode;
onPress?: () => void;
}) => {
if (Platform.OS === 'web' && to) {
// React Native Web doesn't forward `onClick` if we use `TouchableWithoutFeedback`.
// We need to use `onClick` to be able to prevent default browser handling of links.
return (
<Link
{...rest}
to={to}
style={[styles.button, style]}
onPress={(e: any) => {
if (
!(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) && // ignore clicks with modifier keys
(e.button == null || e.button === 0) // ignore everything but left clicks
) {
e.preventDefault();
onPress?.(e);
}
}}
>
{children}
</Link>
);
} else {
return (
<TouchableItem
{...rest}
accessibilityRole={accessibilityRole}
delayPressIn={delayPressIn}
onPress={onPress}
>
<View style={style}>{children}</View>
</TouchableItem>
);
}
};
/**
* A component used to show an action item with an icon and a label in a navigation drawer.
*/
@@ -70,6 +124,7 @@ export default function DrawerItem(props: Props) {
icon,
label,
labelStyle,
to,
focused = false,
activeTintColor = colors.primary,
inactiveTintColor = Color(colors.text).alpha(0.68).rgb().string(),
@@ -94,7 +149,7 @@ export default function DrawerItem(props: Props) {
{...rest}
style={[styles.container, { borderRadius, backgroundColor }, style]}
>
<TouchableItem
<Touchable
delayPressIn={0}
onPress={onPress}
style={[styles.wrapper, { borderRadius }]}
@@ -102,6 +157,7 @@ export default function DrawerItem(props: Props) {
accessibilityComponentType="button"
accessibilityRole="button"
accessibilityStates={focused ? ['selected'] : []}
to={to}
>
<React.Fragment>
{iconNode}
@@ -129,7 +185,7 @@ export default function DrawerItem(props: Props) {
)}
</View>
</React.Fragment>
</TouchableItem>
</Touchable>
</View>
);
}
@@ -148,4 +204,7 @@ const styles = StyleSheet.create({
label: {
marginRight: 32,
},
button: {
display: 'flex',
},
});

View File

@@ -3,6 +3,7 @@ import {
CommonActions,
DrawerActions,
DrawerNavigationState,
useLinkBuilder,
} from '@react-navigation/native';
import DrawerItem from './DrawerItem';
import {
@@ -31,6 +32,8 @@ export default function DrawerItemList({
itemStyle,
labelStyle,
}: Props) {
const buildLink = useLinkBuilder();
return (state.routes.map((route, i) => {
const focused = i === state.index;
const { title, drawerLabel, drawerIcon } = descriptors[route.key].options;
@@ -53,6 +56,7 @@ export default function DrawerItemList({
inactiveBackgroundColor={inactiveBackgroundColor}
labelStyle={labelStyle}
style={itemStyle}
to={buildLink(route.name, route.params)}
onPress={() => {
navigation.dispatch({
...(focused

View File

@@ -12,16 +12,13 @@ import {
// eslint-disable-next-line import/no-unresolved
import { ScreenContainer } from 'react-native-screens';
import {
PanGestureHandler,
GestureHandlerRootView,
} from 'react-native-gesture-handler';
import {
NavigationHelpersContext,
DrawerNavigationState,
DrawerActions,
useTheme,
} from '@react-navigation/native';
import DrawerGestureContext from '../utils/DrawerGestureContext';
import { GestureHandlerRootView } from './GestureHandler';
import SafeAreaProviderCompat from './SafeAreaProviderCompat';
import ResourceSavingScene from './ResourceSavingScene';
import DrawerContent from './DrawerContent';
@@ -93,8 +90,6 @@ export default function DrawerView({
Dimensions.get('window')
);
const drawerGestureRef = React.useRef<PanGestureHandler>(null);
const { colors } = useTheme();
const isDrawerOpen = state.history.some((it) => it.type === 'drawer');
@@ -201,9 +196,9 @@ export default function DrawerView({
const { gestureEnabled, swipeEnabled } = descriptors[activeKey].options;
return (
<GestureHandlerWrapper style={styles.content}>
<SafeAreaProviderCompat>
<DrawerGestureContext.Provider value={drawerGestureRef}>
<NavigationHelpersContext.Provider value={navigation}>
<GestureHandlerWrapper style={styles.content}>
<SafeAreaProviderCompat>
<DrawerOpenContext.Provider value={isDrawerOpen}>
<Drawer
open={isDrawerOpen}
@@ -211,10 +206,6 @@ export default function DrawerView({
swipeEnabled={swipeEnabled}
onOpen={handleDrawerOpen}
onClose={handleDrawerClose}
onGestureRef={(ref) => {
// @ts-ignore
drawerGestureRef.current = ref;
}}
gestureHandlerProps={gestureHandlerProps}
drawerType={drawerType}
drawerPosition={drawerPosition}
@@ -251,9 +242,9 @@ export default function DrawerView({
dimensions={dimensions}
/>
</DrawerOpenContext.Provider>
</DrawerGestureContext.Provider>
</SafeAreaProviderCompat>
</GestureHandlerWrapper>
</SafeAreaProviderCompat>
</GestureHandlerWrapper>
</NavigationHelpersContext.Provider>
);
}

View File

@@ -0,0 +1,23 @@
import * as React from 'react';
import {
PanGestureHandler as PanGestureHandlerNative,
PanGestureHandlerProperties,
} from 'react-native-gesture-handler';
import DrawerGestureContext from '../utils/DrawerGestureContext';
export function PanGestureHandler(props: PanGestureHandlerProperties) {
const gestureRef = React.useRef<PanGestureHandlerNative>(null);
return (
<DrawerGestureContext.Provider value={gestureRef}>
<PanGestureHandlerNative {...props} />
</DrawerGestureContext.Provider>
);
}
export {
GestureHandlerRootView,
TapGestureHandler,
State as GestureState,
PanGestureHandlerGestureEvent,
} from 'react-native-gesture-handler';

View File

@@ -0,0 +1,31 @@
import * as React from 'react';
import { View } from 'react-native';
import type {
PanGestureHandlerProperties,
TapGestureHandlerProperties,
} from 'react-native-gesture-handler';
const Dummy: any = ({ children }: { children: React.ReactNode }) => (
<>{children}</>
);
export const PanGestureHandler = Dummy as React.ComponentType<
PanGestureHandlerProperties
>;
export const TapGestureHandler = Dummy as React.ComponentType<
TapGestureHandlerProperties
>;
export const GestureHandlerRootView = View;
export const GestureState = {
UNDETERMINED: 0,
FAILED: 1,
BEGAN: 2,
CANCELLED: 3,
ACTIVE: 4,
END: 5,
};
export type { PanGestureHandlerGestureEvent } from 'react-native-gesture-handler';

View File

@@ -29,7 +29,7 @@ export default class ResourceSavingScene extends React.Component<Props> {
styles.container,
Platform.OS === 'web'
? { display: isVisible ? 'flex' : 'none' }
: null,
: { overflow: 'hidden' },
style,
]}
collapsable={false}
@@ -52,7 +52,6 @@ export default class ResourceSavingScene extends React.Component<Props> {
const styles = StyleSheet.create({
container: {
flex: 1,
overflow: 'hidden',
},
attached: {
flex: 1,

View File

@@ -3,6 +3,77 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [5.2.1](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.2.0...@react-navigation/material-bottom-tabs@5.2.1) (2020-05-08)
### Bug Fixes
* fix building typescript definitions. closes [#8216](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/issues/8216) ([47a1229](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/commit/47a12298378747edd2d22e54dc1c8677f98c49b4))
# [5.2.0](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.1.15...@react-navigation/material-bottom-tabs@5.2.0) (2020-05-08)
### Features
* add generic type aliases for screen props ([bea14aa](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/commit/bea14aa26fd5cbfebc7973733c5cf1f44fd323aa)), closes [#7971](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/issues/7971)
* use links in bottom navigation tabs ([f384706](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/commit/f384706741f7e2422c284b65da10425f7af680c0))
## [5.1.15](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.1.14...@react-navigation/material-bottom-tabs@5.1.15) (2020-05-05)
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
## [5.1.14](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.1.13...@react-navigation/material-bottom-tabs@5.1.14) (2020-05-01)
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
## [5.1.13](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.1.12...@react-navigation/material-bottom-tabs@5.1.13) (2020-05-01)
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
## [5.1.12](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.1.11...@react-navigation/material-bottom-tabs@5.1.12) (2020-04-30)
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
## [5.1.11](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.1.10...@react-navigation/material-bottom-tabs@5.1.11) (2020-04-30)
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
## [5.1.10](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.1.9...@react-navigation/material-bottom-tabs@5.1.10) (2020-04-27)
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
## [5.1.9](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.1.8...@react-navigation/material-bottom-tabs@5.1.9) (2020-04-17)
**Note:** Version bump only for package @react-navigation/material-bottom-tabs

View File

@@ -1,7 +1,7 @@
{
"name": "@react-navigation/material-bottom-tabs",
"description": "Integration for bottom navigation component from react-native-paper",
"version": "5.1.9",
"version": "5.2.1",
"keywords": [
"react-native-component",
"react-component",
@@ -20,6 +20,7 @@
"homepage": "https://reactnavigation.org/docs/material-bottom-tab-navigator.html",
"main": "lib/commonjs/index.js",
"react-native": "src/index.tsx",
"source": "src/index.tsx",
"module": "lib/module/index.js",
"types": "lib/typescript/src/index.d.ts",
"files": [
@@ -35,15 +36,15 @@
"clean": "del lib"
},
"devDependencies": {
"@react-native-community/bob": "^0.10.0",
"@react-navigation/native": "^5.1.6",
"@types/react": "^16.9.23",
"@types/react-native": "^0.61.22",
"@react-native-community/bob": "^0.13.1",
"@react-navigation/native": "^5.2.6",
"@types/react": "^16.9.34",
"@types/react-native": "^0.62.7",
"@types/react-native-vector-icons": "^6.4.5",
"del-cli": "^3.0.0",
"react": "~16.9.0",
"react-native": "~0.61.5",
"react-native-paper": "^3.7.0",
"react-native-paper": "^3.10.1",
"react-native-vector-icons": "^6.6.0",
"typescript": "^3.8.3"
},

View File

@@ -14,4 +14,5 @@ export { default as MaterialBottomTabView } from './views/MaterialBottomTabView'
export type {
MaterialBottomTabNavigationOptions,
MaterialBottomTabNavigationProp,
MaterialBottomTabScreenProps,
} from './types';

View File

@@ -6,6 +6,7 @@ import {
NavigationHelpers,
TabNavigationState,
TabActionHelpers,
RouteProp,
} from '@react-navigation/native';
export type MaterialBottomTabNavigationEventMap = {
@@ -32,6 +33,14 @@ export type MaterialBottomTabNavigationProp<
> &
TabActionHelpers<ParamList>;
export type MaterialBottomTabScreenProps<
ParamList extends ParamListBase,
RouteName extends keyof ParamList = string
> = {
navigation: MaterialBottomTabNavigationProp<ParamList, RouteName>;
route: RouteProp<ParamList, RouteName>;
};
export type MaterialBottomTabNavigationOptions = {
/**
* Title text for the screen.

View File

@@ -1,12 +1,15 @@
import * as React from 'react';
import { StyleSheet } from 'react-native';
import { StyleSheet, Platform } from 'react-native';
import { BottomNavigation, DefaultTheme, DarkTheme } from 'react-native-paper';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import {
NavigationHelpersContext,
Route,
TabNavigationState,
TabActions,
useTheme,
useLinkBuilder,
Link,
} from '@react-navigation/native';
import {
@@ -23,13 +26,14 @@ type Props = MaterialBottomTabNavigationConfig & {
type Scene = { route: { key: string } };
export default function MaterialBottomTabView({
function MaterialBottomTabViewInner({
state,
navigation,
descriptors,
...rest
}: Props) {
const { dark, colors } = useTheme();
const buildLink = useLinkBuilder();
const theme = React.useMemo(() => {
const t = dark ? DarkTheme : DefaultTheme;
@@ -56,6 +60,37 @@ export default function MaterialBottomTabView({
})
}
renderScene={({ route }) => descriptors[route.key].render()}
renderTouchable={
Platform.OS === 'web'
? ({
onPress,
route,
accessibilityRole: _0,
borderless: _1,
centered: _2,
rippleColor: _3,
...rest
}) => {
return (
<Link
{...rest}
// @ts-ignore
to={buildLink(route.name, route.params)}
accessibilityRole="link"
onPress={(e: any) => {
if (
!(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) && // ignore clicks with modifier keys
(e.button == null || e.button === 0) // ignore everything but left clicks
) {
e.preventDefault();
onPress?.(e);
}
}}
/>
);
}
: undefined
}
renderIcon={({ route, focused, color }) => {
const { options } = descriptors[route.key];
@@ -66,8 +101,6 @@ export default function MaterialBottomTabView({
color={color}
size={24}
style={styles.icon}
importantForAccessibility="no-hide-descendants"
accessibilityElementsHidden
/>
);
}
@@ -108,6 +141,14 @@ export default function MaterialBottomTabView({
);
}
export default function MaterialBottomTabView(props: Props) {
return (
<NavigationHelpersContext.Provider value={props.navigation}>
<MaterialBottomTabViewInner {...props} />
</NavigationHelpersContext.Provider>
);
}
const styles = StyleSheet.create({
icon: {
backgroundColor: 'transparent',

View File

@@ -3,6 +3,76 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [5.2.1](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.2.0...@react-navigation/material-top-tabs@5.2.1) (2020-05-08)
### Bug Fixes
* fix building typescript definitions. closes [#8216](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/issues/8216) ([47a1229](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/commit/47a12298378747edd2d22e54dc1c8677f98c49b4))
# [5.2.0](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.1.15...@react-navigation/material-top-tabs@5.2.0) (2020-05-08)
### Features
* add generic type aliases for screen props ([bea14aa](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/commit/bea14aa26fd5cbfebc7973733c5cf1f44fd323aa)), closes [#7971](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/issues/7971)
## [5.1.15](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.1.14...@react-navigation/material-top-tabs@5.1.15) (2020-05-05)
**Note:** Version bump only for package @react-navigation/material-top-tabs
## [5.1.14](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.1.13...@react-navigation/material-top-tabs@5.1.14) (2020-05-01)
**Note:** Version bump only for package @react-navigation/material-top-tabs
## [5.1.13](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.1.12...@react-navigation/material-top-tabs@5.1.13) (2020-05-01)
**Note:** Version bump only for package @react-navigation/material-top-tabs
## [5.1.12](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.1.11...@react-navigation/material-top-tabs@5.1.12) (2020-04-30)
**Note:** Version bump only for package @react-navigation/material-top-tabs
## [5.1.11](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.1.10...@react-navigation/material-top-tabs@5.1.11) (2020-04-30)
**Note:** Version bump only for package @react-navigation/material-top-tabs
## [5.1.10](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.1.9...@react-navigation/material-top-tabs@5.1.10) (2020-04-27)
**Note:** Version bump only for package @react-navigation/material-top-tabs
## [5.1.9](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.1.8...@react-navigation/material-top-tabs@5.1.9) (2020-04-17)
**Note:** Version bump only for package @react-navigation/material-top-tabs

View File

@@ -1,7 +1,7 @@
{
"name": "@react-navigation/material-top-tabs",
"description": "Integration for the animated tab view component from react-native-tab-view",
"version": "5.1.9",
"version": "5.2.1",
"keywords": [
"react-native-component",
"react-component",
@@ -20,6 +20,7 @@
"homepage": "https://reactnavigation.org/docs/material-top-tab-navigator.html",
"main": "lib/commonjs/index.js",
"react-native": "src/index.tsx",
"source": "src/index.tsx",
"module": "lib/module/index.js",
"types": "lib/typescript/src/index.d.ts",
"files": [
@@ -38,15 +39,15 @@
"color": "^3.1.2"
},
"devDependencies": {
"@react-native-community/bob": "^0.10.0",
"@react-navigation/native": "^5.1.6",
"@types/react": "^16.9.23",
"@types/react-native": "^0.61.22",
"@react-native-community/bob": "^0.13.1",
"@react-navigation/native": "^5.2.6",
"@types/react": "^16.9.34",
"@types/react-native": "^0.62.7",
"del-cli": "^3.0.0",
"react": "~16.9.0",
"react-native": "~0.61.5",
"react-native-gesture-handler": "^1.6.0",
"react-native-reanimated": "^1.7.0",
"react-native-reanimated": "^1.8.0",
"react-native-tab-view": "^2.14.0",
"typescript": "^3.8.3"
},

View File

@@ -15,6 +15,7 @@ export { default as MaterialTopTabBar } from './views/MaterialTopTabBar';
export type {
MaterialTopTabNavigationOptions,
MaterialTopTabNavigationProp,
MaterialTopTabScreenProps,
MaterialTopTabBarProps,
MaterialTopTabBarOptions,
} from './types';

View File

@@ -8,6 +8,7 @@ import {
NavigationProp,
TabNavigationState,
TabActionHelpers,
RouteProp,
} from '@react-navigation/native';
export type MaterialTopTabNavigationEventMap = {
@@ -46,6 +47,14 @@ export type MaterialTopTabNavigationProp<
> &
TabActionHelpers<ParamList>;
export type MaterialTopTabScreenProps<
ParamList extends ParamListBase,
RouteName extends keyof ParamList = string
> = {
navigation: MaterialTopTabNavigationProp<ParamList, RouteName>;
route: RouteProp<ParamList, RouteName>;
};
export type MaterialTopTabNavigationOptions = {
/**
* Title text for the screen.

View File

@@ -1,6 +1,7 @@
import * as React from 'react';
import { TabView, SceneRendererProps } from 'react-native-tab-view';
import {
NavigationHelpersContext,
TabNavigationState,
TabActions,
useTheme,
@@ -45,25 +46,27 @@ export default function MaterialTopTabView({
};
return (
<TabView
{...rest}
onIndexChange={(index) =>
navigation.dispatch({
...TabActions.jumpTo(state.routes[index].name),
target: state.key,
})
}
renderScene={({ route }) => descriptors[route.key].render()}
navigationState={state}
renderTabBar={renderTabBar}
renderPager={pager}
renderLazyPlaceholder={lazyPlaceholder}
onSwipeStart={() => navigation.emit({ type: 'swipeStart' })}
onSwipeEnd={() => navigation.emit({ type: 'swipeEnd' })}
sceneContainerStyle={[
{ backgroundColor: colors.background },
sceneContainerStyle,
]}
/>
<NavigationHelpersContext.Provider value={navigation}>
<TabView
{...rest}
onIndexChange={(index) =>
navigation.dispatch({
...TabActions.jumpTo(state.routes[index].name),
target: state.key,
})
}
renderScene={({ route }) => descriptors[route.key].render()}
navigationState={state}
renderTabBar={renderTabBar}
renderPager={pager}
renderLazyPlaceholder={lazyPlaceholder}
onSwipeStart={() => navigation.emit({ type: 'swipeStart' })}
onSwipeEnd={() => navigation.emit({ type: 'swipeEnd' })}
sceneContainerStyle={[
{ backgroundColor: colors.background },
sceneContainerStyle,
]}
/>
</NavigationHelpersContext.Provider>
);
}

View File

@@ -3,6 +3,100 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [5.2.6](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.2.5...@react-navigation/native@5.2.6) (2020-05-08)
### Bug Fixes
* fix building typescript definitions. closes [#8216](https://github.com/react-navigation/react-navigation/tree/master/packages/native/issues/8216) ([47a1229](https://github.com/react-navigation/react-navigation/tree/master/packages/native/commit/47a12298378747edd2d22e54dc1c8677f98c49b4))
## [5.2.5](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.2.4...@react-navigation/native@5.2.5) (2020-05-08)
### Bug Fixes
* return a promise-like from getInitialState ([#8210](https://github.com/react-navigation/react-navigation/tree/master/packages/native/issues/8210)) ([85ae378](https://github.com/react-navigation/react-navigation/tree/master/packages/native/commit/85ae378d8cb1073895b281e13ebccee881d4c062))
## [5.2.4](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.2.3...@react-navigation/native@5.2.4) (2020-05-05)
### Bug Fixes
* return undefined for buildLink if linking is not enabled ([9fd2635](https://github.com/react-navigation/react-navigation/tree/master/packages/native/commit/9fd2635756362c8da79656b4d9b101bebaaf7003))
## [5.2.3](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.2.2...@react-navigation/native@5.2.3) (2020-05-01)
### Bug Fixes
* default linking enabled to true ([c7b8e2e](https://github.com/react-navigation/react-navigation/tree/master/packages/native/commit/c7b8e2e9666733143eef156b27f3e4995c36b856))
## [5.2.2](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.2.1...@react-navigation/native@5.2.2) (2020-05-01)
### Bug Fixes
* don't throw when using 'useLinking'. fixes [#8171](https://github.com/react-navigation/react-navigation/tree/master/packages/native/issues/8171) ([10eca8b](https://github.com/react-navigation/react-navigation/tree/master/packages/native/commit/10eca8b92edbce6dbef8abaf189e4b59a29b3748))
## [5.2.1](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.2.0...@react-navigation/native@5.2.1) (2020-04-30)
### Bug Fixes
* render fallback only if linking is enabled. closes [#8161](https://github.com/react-navigation/react-navigation/tree/master/packages/native/issues/8161) ([1c075ff](https://github.com/react-navigation/react-navigation/tree/master/packages/native/commit/1c075ffb169d233ed0515efea264a5a69b4de52e))
# [5.2.0](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.1.7...@react-navigation/native@5.2.0) (2020-04-30)
### Bug Fixes
* add catch to thenable returned by getInitialState ([d6fa279](https://github.com/react-navigation/react-navigation/tree/master/packages/native/commit/d6fa279d9371c7a6403d10d209a2a64147891c63))
* return onPress instead of onClick for useLinkProps ([ae5442e](https://github.com/react-navigation/react-navigation/tree/master/packages/native/commit/ae5442ebe812b91fa1f12164f27d1aeed918ab0e))
### Features
* add `useLinkBuilder` hook to build links ([2792f43](https://github.com/react-navigation/react-navigation/tree/master/packages/native/commit/2792f438fe45428fe193e3708fee7ad61966cbf4))
* add a useLinkProps hook ([f2291d1](https://github.com/react-navigation/react-navigation/tree/master/packages/native/commit/f2291d110faa2aa8e10c9133c1c0c28d54af7917))
* add action prop to Link ([942d2be](https://github.com/react-navigation/react-navigation/tree/master/packages/native/commit/942d2be2c72720469475ce12ec8df23825994dbf))
* add Link component as useLinkTo hook for navigating to links ([2573b5b](https://github.com/react-navigation/react-navigation/tree/master/packages/native/commit/2573b5beaac1240434e52f3f57bb29da2f541c88))
## [5.1.7](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.1.6...@react-navigation/native@5.1.7) (2020-04-27)
**Note:** Version bump only for package @react-navigation/native
## [5.1.6](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.1.5...@react-navigation/native@5.1.6) (2020-04-17)

View File

@@ -1,7 +1,7 @@
{
"name": "@react-navigation/native",
"description": "React Native integration for React Navigation",
"version": "5.1.6",
"version": "5.2.6",
"keywords": [
"react-native",
"react-navigation",
@@ -16,6 +16,7 @@
"homepage": "https://reactnavigation.org",
"main": "lib/commonjs/index.js",
"react-native": "src/index.tsx",
"source": "src/index.tsx",
"module": "lib/module/index.js",
"types": "lib/typescript/src/index.d.ts",
"files": [
@@ -31,16 +32,16 @@
"clean": "del lib"
},
"dependencies": {
"@react-navigation/core": "^5.3.4"
"@react-navigation/core": "^5.5.2"
},
"devDependencies": {
"@react-native-community/bob": "^0.10.0",
"@types/react": "^16.9.23",
"@types/react-native": "^0.61.22",
"@react-native-community/bob": "^0.13.1",
"@types/react": "^16.9.34",
"@types/react-native": "^0.62.7",
"del-cli": "^3.0.0",
"react": "~16.9.0",
"react-native": "~0.61.5",
"react-native-testing-library": "^1.12.0",
"react-native-testing-library": "^1.13.2",
"typescript": "^3.8.3"
},
"peerDependencies": {

View File

@@ -0,0 +1,42 @@
import * as React from 'react';
import { Text, TextProps, GestureResponderEvent, Platform } from 'react-native';
import { NavigationAction } from '@react-navigation/core';
import useLinkProps from './useLinkProps';
type Props = {
to: string;
action?: NavigationAction;
target?: string;
} & (TextProps & { children: React.ReactNode });
/**
* Component to render link to another screen using a path.
* Uses an anchor tag on the web.
*
* @param props.to Absolute path to screen (e.g. `/feeds/hot`).
* @param props.action Optional action to use for in-page navigation. By default, the path is parsed to an action based on linking config.
* @param props.children Child elements to render the content.
*/
export default function Link({ to, action, ...rest }: Props) {
const props = useLinkProps({ to, action });
const onPress = (
e: React.MouseEvent<HTMLAnchorElement, MouseEvent> | GestureResponderEvent
) => {
if ('onPress' in rest) {
// @ts-ignore
rest.onPress?.(e);
}
props.onPress(e);
};
return React.createElement(Text, {
...props,
...rest,
...Platform.select({
web: { onClick: onPress } as any,
default: { onPress },
}),
});
}

View File

@@ -0,0 +1,8 @@
import * as React from 'react';
import { LinkingOptions } from './types';
const LinkingContext = React.createContext<{
options: LinkingOptions | undefined;
}>({ options: undefined });
export default LinkingContext;

View File

@@ -6,38 +6,70 @@ import {
} from '@react-navigation/core';
import ThemeProvider from './theming/ThemeProvider';
import DefaultTheme from './theming/DefaultTheme';
import LinkingContext from './LinkingContext';
import useThenable from './useThenable';
import useLinking from './useLinking';
import useBackButton from './useBackButton';
import { Theme } from './types';
import { Theme, LinkingOptions } from './types';
type Props = NavigationContainerProps & {
theme?: Theme;
linking?: LinkingOptions;
fallback?: React.ReactNode;
};
/**
* Container component which holds the navigation state
* designed for mobile apps.
* Container component which holds the navigation state designed for React Native apps.
* This should be rendered at the root wrapping the whole app.
*
* @param props.initialState Initial state object for the navigation tree.
* @param props.initialState Initial state object for the navigation tree. When deep link handling is enabled, this will be ignored if there's an incoming link.
* @param props.onStateChange Callback which is called with the latest navigation state when it changes.
* @param props.theme Theme object for the navigators.
* @param props.linking Options for deep linking. Deep link handling is enabled when this prop is provided, unless `linking.enabled` is `false`.
* @param props.fallback Fallback component to render until we have finished getting initial state when linking is enabled. Defaults to `null`.
* @param props.children Child elements to render the content.
* @param props.ref Ref object which refers to the navigation object containing helper methods.
*/
const NavigationContainer = React.forwardRef(function NavigationContainer(
{ theme = DefaultTheme, ...rest }: Props,
{ theme = DefaultTheme, linking, fallback = null, ...rest }: Props,
ref?: React.Ref<NavigationContainerRef | null>
) {
const isLinkingEnabled = linking ? linking.enabled !== false : false;
const refContainer = React.useRef<NavigationContainerRef>(null);
useBackButton(refContainer);
const { getInitialState } = useLinking(refContainer, {
enabled: isLinkingEnabled,
prefixes: [],
...linking,
});
const [isReady, initialState = rest.initialState] = useThenable(
getInitialState
);
React.useImperativeHandle(ref, () => refContainer.current);
const linkingContext = React.useMemo(() => ({ options: linking }), [linking]);
if (isLinkingEnabled && !isReady) {
// This is temporary until we have Suspense for data-fetching
// Then the fallback will be handled by a parent `Suspense` component
return fallback as React.ReactElement;
}
return (
<ThemeProvider value={theme}>
<BaseNavigationContainer {...rest} ref={refContainer} />
</ThemeProvider>
<LinkingContext.Provider value={linkingContext}>
<ThemeProvider value={theme}>
<BaseNavigationContainer
{...rest}
initialState={initialState}
ref={refContainer}
/>
</ThemeProvider>
</LinkingContext.Provider>
);
});

View File

@@ -1,5 +0,0 @@
export default function () {
throw new Error(
"'NavigationNativeContainer' has been renamed to 'NavigationContainer"
);
}

View File

@@ -17,7 +17,7 @@ it('throws if multiple instances of useLinking are used', () => {
let element;
expect(() => (element = render(<Sample />))).toThrowError(
"Looks like you are using 'useLinking' in multiple components."
'Looks like you have configured linking in multiple places.'
);
// @ts-ignore
@@ -41,9 +41,7 @@ it('throws if multiple instances of useLinking are used', () => {
<B />
</>
))
).toThrowError(
"Looks like you are using 'useLinking' in multiple components."
);
).toThrowError('Looks like you have configured linking in multiple places.');
// @ts-ignore
element?.unmount();
@@ -57,5 +55,19 @@ it('throws if multiple instances of useLinking are used', () => {
render(wrapper2).unmount();
expect(() => render(wrapper2)).not.toThrow();
expect(() => (element = render(wrapper2))).not.toThrow();
// @ts-ignore
element?.unmount();
function Sample3() {
useLinking(ref, options);
useLinking(ref, { ...options, enabled: false });
return null;
}
expect(() => (element = render(<Sample3 />))).not.toThrowError();
// @ts-ignore
element?.unmount();
});

View File

@@ -1,13 +1,17 @@
export * from '@react-navigation/core';
export { default as NavigationContainer } from './NavigationContainer';
export { default as NavigationNativeContainer } from './NavigationNativeContainer';
export { default as useBackButton } from './useBackButton';
export { default as useLinking } from './useLinking';
export { default as useScrollToTop } from './useScrollToTop';
export { default as DefaultTheme } from './theming/DefaultTheme';
export { default as DarkTheme } from './theming/DarkTheme';
export { default as ThemeProvider } from './theming/ThemeProvider';
export { default as useTheme } from './theming/useTheme';
export { default as Link } from './Link';
export { default as useLinking } from './useLinking';
export { default as useLinkTo } from './useLinkTo';
export { default as useLinkProps } from './useLinkProps';
export { default as useLinkBuilder } from './useLinkBuilder';

View File

@@ -15,6 +15,11 @@ export type Theme = {
};
export type LinkingOptions = {
/**
* Whether deep link handling should be enabled.
* Defaults to true.
*/
enabled?: boolean;
/**
* The prefixes are stripped from the URL before parsing them.
* Usually they are the `scheme` + `host` (e.g. `myapp://chat?user=jane`)

View File

@@ -0,0 +1,81 @@
import * as React from 'react';
import {
NavigationHelpers,
NavigationHelpersContext,
NavigationProp,
ParamListBase,
getPathFromState,
} from '@react-navigation/core';
import LinkingContext from './LinkingContext';
type NavigationObject =
| NavigationHelpers<ParamListBase>
| NavigationProp<ParamListBase>;
type MinimalState = {
index: number;
routes: { name: string; params?: object; state?: MinimalState }[];
};
const getRootStateForNavigate = (
navigation: NavigationObject,
state: MinimalState
): MinimalState => {
const parent = navigation.dangerouslyGetParent();
if (parent) {
const parentState = parent.dangerouslyGetState();
return getRootStateForNavigate(parent, {
index: 0,
routes: [
{
...parentState.routes[parentState.index],
state: state,
},
],
});
}
return state;
};
/**
* Build destination link for a navigate action.
* Useful for showing anchor tags on the web for buttons that perform navigation.
*/
export default function useLinkBuilder() {
const navigation = React.useContext(NavigationHelpersContext);
const linking = React.useContext(LinkingContext);
const buildLink = React.useCallback(
(name: string, params?: object) => {
const { options } = linking;
if (options?.enabled === false) {
return undefined;
}
const state = navigation
? getRootStateForNavigate(navigation, {
index: 0,
routes: [{ name, params }],
})
: // If we couldn't find a navigation object in context, we're at root
// So we'll construct a basic state object to use
{
index: 0,
routes: [{ name, params }],
};
const path = options?.getPathFromState
? options.getPathFromState(state, options?.config)
: getPathFromState(state, options?.config);
return path;
},
[linking, navigation]
);
return buildLink;
}

View File

@@ -0,0 +1,70 @@
import * as React from 'react';
import { Platform, GestureResponderEvent } from 'react-native';
import {
NavigationAction,
NavigationHelpersContext,
} from '@react-navigation/core';
import useLinkTo from './useLinkTo';
type Props = {
to: string;
action?: NavigationAction;
};
/**
* Hook to get props for an anchor tag so it can work with in page navigation.
*
* @param props.to Absolute path to screen (e.g. `/feeds/hot`).
* @param props.action Optional action to use for in-page navigation. By default, the path is parsed to an action based on linking config.
*/
export default function useLinkProps({ to, action }: Props) {
const navigation = React.useContext(NavigationHelpersContext);
const linkTo = useLinkTo();
const onPress = (
e: React.MouseEvent<HTMLAnchorElement, MouseEvent> | GestureResponderEvent
) => {
let shouldHandle = false;
if (Platform.OS !== 'web' || !e) {
shouldHandle = e ? !e.defaultPrevented : true;
} else if (
!e.defaultPrevented && // onPress prevented default
// @ts-ignore
!(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) && // ignore clicks with modifier keys
// @ts-ignore
(e.button == null || e.button === 0) && // ignore everything but left clicks
// @ts-ignore
[undefined, null, '', 'self'].includes(e.currentTarget?.target) // let browser handle "target=_blank" etc.
) {
e.preventDefault();
shouldHandle = true;
}
if (shouldHandle) {
if (action) {
if (navigation) {
navigation.dispatch(action);
} else {
throw new Error("Couldn't find a navigation object.");
}
} else {
if (typeof to !== 'string') {
throw new Error(
`To 'to' option is invalid (found '${String(
to
)}'. It must be a valid string for navigation.`
);
}
linkTo(to);
}
}
};
return {
href: to,
accessibilityRole: 'link' as const,
onPress,
};
}

View File

@@ -0,0 +1,55 @@
import * as React from 'react';
import {
getStateFromPath,
getActionFromState,
NavigationContext,
} from '@react-navigation/core';
import LinkingContext from './LinkingContext';
export default function useLinkTo() {
const navigation = React.useContext(NavigationContext);
const linking = React.useContext(LinkingContext);
const linkTo = React.useCallback(
(path: string) => {
if (!path.startsWith('/')) {
throw new Error(`The path must start with '/' (${path}).`);
}
if (navigation === undefined) {
throw new Error(
"Couldn't find a navigation object. Is your component inside a screen in a navigator?"
);
}
const { options } = linking;
const state = options?.getStateFromPath
? options.getStateFromPath(path, options.config)
: getStateFromPath(path, options?.config);
if (state) {
let root = navigation;
let current;
// Traverse up to get the root navigation
while ((current = root.dangerouslyGetParent())) {
root = current;
}
const action = getActionFromState(state);
if (action !== undefined) {
root.dispatch(action);
} else {
root.reset(state);
}
} else {
throw new Error('Failed to parse the path to a navigation state.');
}
},
[linking, navigation]
);
return linkTo;
}

View File

@@ -12,21 +12,28 @@ let isUsingLinking = false;
export default function useLinking(
ref: React.RefObject<NavigationContainerRef>,
{
enabled = true,
prefixes,
config,
getStateFromPath = getStateFromPathDefault,
}: LinkingOptions
) {
React.useEffect(() => {
if (isUsingLinking) {
if (enabled !== false && 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." +
(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."
: '')
[
'Looks like you have configured linking in multiple places. This is likely an error since deep links should only be handled in one place to avoid conflicts. Make sure that:',
"- You are not using both 'linking' prop and 'useLinking'",
"- You don't have 'useLinking' in multiple components",
Platform.OS === 'android'
? "- You have set 'android:launchMode=singleTask' in the '<activity />' section of the 'AndroidManifest.xml' file to avoid launching multiple instances"
: '',
]
.join('\n')
.trim()
);
} else {
isUsingLinking = true;
isUsingLinking = enabled !== false;
}
return () => {
@@ -37,15 +44,17 @@ export default function useLinking(
// We store these options in ref to avoid re-creating getInitialState and re-subscribing listeners
// This lets user avoid wrapping the items in `React.useCallback` or `React.useMemo`
// Not re-creating `getInitialState` is important coz it makes it easier for the user to use in an effect
const enabledRef = React.useRef(enabled);
const prefixesRef = React.useRef(prefixes);
const configRef = React.useRef(config);
const getStateFromPathRef = React.useRef(getStateFromPath);
React.useEffect(() => {
enabledRef.current = enabled;
prefixesRef.current = prefixes;
configRef.current = config;
getStateFromPathRef.current = getStateFromPath;
}, [config, getStateFromPath, prefixes]);
}, [config, enabled, getStateFromPath, prefixes]);
const extractPathFromURL = React.useCallback((url: string) => {
for (const prefix of prefixesRef.current) {
@@ -58,7 +67,19 @@ export default function useLinking(
}, []);
const getInitialState = React.useCallback(async () => {
const url = await Linking.getInitialURL();
if (!enabledRef.current) {
return undefined;
}
const url = await (Promise.race([
Linking.getInitialURL(),
new Promise((resolve) =>
// Timeout in 150ms if `getInitialState` doesn't resolve
// Workaround for https://github.com/facebook/react-native/issues/25675
setTimeout(resolve, 150)
),
]) as Promise<string | null | undefined>);
const path = url ? extractPathFromURL(url) : null;
if (path) {
@@ -70,6 +91,10 @@ export default function useLinking(
React.useEffect(() => {
const listener = ({ url }: { url: string }) => {
if (!enabled) {
return;
}
const path = extractPathFromURL(url);
const navigation = ref.current;
@@ -91,7 +116,7 @@ export default function useLinking(
Linking.addEventListener('url', listener);
return () => Linking.removeEventListener('url', listener);
}, [extractPathFromURL, ref]);
}, [enabled, extractPathFromURL, ref]);
return {
getInitialState,

View File

@@ -8,6 +8,17 @@ import {
} from '@react-navigation/core';
import { LinkingOptions } from './types';
type ResultState = ReturnType<typeof getStateFromPathDefault>;
type HistoryState = { index: number };
declare const history: {
state?: HistoryState;
go(delta: number): void;
pushState(state: HistoryState, title: string, url: string): void;
replaceState(state: HistoryState, title: string, url: string): void;
};
const getStateLength = (state: NavigationState) => {
let length = 0;
@@ -32,18 +43,25 @@ let isUsingLinking = false;
export default function useLinking(
ref: React.RefObject<NavigationContainerRef>,
{
enabled = true,
config,
getStateFromPath = getStateFromPathDefault,
getPathFromState = getPathFromStateDefault,
}: LinkingOptions
) {
React.useEffect(() => {
if (isUsingLinking) {
if (enabled !== false && 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. Also ensure that you set your android activity launchMode to single task in your AndroiManifest.xml file."
[
'Looks like you have configured linking in multiple places. This is likely an error since URL integration should only be handled in one place to avoid conflicts. Make sure that:',
"- You are not using both 'linking' prop and 'useLinking'",
"- You don't have 'useLinking' in multiple components",
]
.join('\n')
.trim()
);
} else {
isUsingLinking = true;
isUsingLinking = enabled !== false;
}
return () => {
@@ -54,25 +72,40 @@ export default function useLinking(
// We store these options in ref to avoid re-creating getInitialState and re-subscribing listeners
// This lets user avoid wrapping the items in `React.useCallback` or `React.useMemo`
// Not re-creating `getInitialState` is important coz it makes it easier for the user to use in an effect
const enabledRef = React.useRef(enabled);
const configRef = React.useRef(config);
const getStateFromPathRef = React.useRef(getStateFromPath);
const getPathFromStateRef = React.useRef(getPathFromState);
React.useEffect(() => {
enabledRef.current = enabled;
configRef.current = config;
getStateFromPathRef.current = getStateFromPath;
getPathFromStateRef.current = getPathFromState;
}, [config, getPathFromState, getStateFromPath]);
}, [config, enabled, getPathFromState, getStateFromPath]);
// Make it an async function to keep consistent with the native impl
const getInitialState = React.useCallback(async () => {
const path = location.pathname + location.search;
const getInitialState = React.useCallback(() => {
let value: ResultState | undefined;
if (path) {
return getStateFromPathRef.current(path, configRef.current);
} else {
return undefined;
if (enabledRef.current) {
const path = location.pathname + location.search;
if (path) {
value = getStateFromPathRef.current(path, configRef.current);
}
}
// Make it a thenable to keep consistent with the native impl
const thenable = {
then(onfulfilled?: (state: ResultState | undefined) => void) {
return Promise.resolve(onfulfilled ? onfulfilled(value) : value);
},
catch() {
return thenable;
},
};
return thenable as PromiseLike<ResultState | undefined>;
}, []);
const previousStateLengthRef = React.useRef<number | undefined>(undefined);
@@ -92,10 +125,10 @@ export default function useLinking(
const numberOfIndicesAhead = React.useRef(0);
React.useEffect(() => {
window.addEventListener('popstate', () => {
const onPopState = () => {
const navigation = ref.current;
if (!navigation) {
if (!navigation || !enabled) {
return;
}
@@ -169,10 +202,18 @@ export default function useLinking(
}
}
}
});
}, [ref]);
};
window.addEventListener('popstate', onPopState);
return () => window.removeEventListener('popstate', onPopState);
}, [enabled, ref]);
React.useEffect(() => {
if (!enabled) {
return;
}
if (ref.current && previousStateLengthRef.current === undefined) {
previousStateLengthRef.current = getStateLength(
ref.current.getRootState()

View File

@@ -0,0 +1,45 @@
import * as React from 'react';
export default function useThenable<T>(create: () => PromiseLike<T>) {
const [promise] = React.useState(create);
// Check if our thenable is synchronous
let resolved = false;
let value: T | undefined;
promise.then((result) => {
resolved = true;
value = result;
});
const [state, setState] = React.useState<[boolean, T | undefined]>([
resolved,
value,
]);
React.useEffect(() => {
let cancelled = false;
const resolve = async () => {
let result;
try {
result = await promise;
} finally {
if (!cancelled) {
setState([true, result]);
}
}
};
if (!resolved) {
resolve();
}
return () => {
cancelled = true;
};
}, [promise, resolved]);
return state;
}

View File

@@ -3,6 +3,47 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [5.4.4](https://github.com/react-navigation/react-navigation/tree/master/packages/routers/compare/@react-navigation/routers@5.4.3...@react-navigation/routers@5.4.4) (2020-05-08)
### Bug Fixes
* fix building typescript definitions. closes [#8216](https://github.com/react-navigation/react-navigation/tree/master/packages/routers/issues/8216) ([47a1229](https://github.com/react-navigation/react-navigation/tree/master/packages/routers/commit/47a12298378747edd2d22e54dc1c8677f98c49b4))
## [5.4.3](https://github.com/react-navigation/react-navigation/tree/master/packages/routers/compare/@react-navigation/routers@5.4.2...@react-navigation/routers@5.4.3) (2020-05-08)
**Note:** Version bump only for package @react-navigation/routers
## [5.4.2](https://github.com/react-navigation/react-navigation/tree/master/packages/routers/compare/@react-navigation/routers@5.4.1...@react-navigation/routers@5.4.2) (2020-04-30)
### Bug Fixes
* fix backBehavior with initialRoute ([#8110](https://github.com/react-navigation/react-navigation/tree/master/packages/routers/issues/8110)) ([420f692](https://github.com/react-navigation/react-navigation/tree/master/packages/routers/commit/420f6926e111d32c2388c44ff0bee2b8ea238a57))
## [5.4.1](https://github.com/react-navigation/react-navigation/tree/master/packages/routers/compare/@react-navigation/routers@5.4.0...@react-navigation/routers@5.4.1) (2020-04-27)
### Bug Fixes
* fix behaviour of openByDefault in drawer when focus changes ([b172b51](https://github.com/react-navigation/react-navigation/tree/master/packages/routers/commit/b172b51f175a9f8044cb2a8e9d74a86480d8f11e))
# [5.4.0](https://github.com/react-navigation/react-navigation/tree/master/packages/routers/compare/@react-navigation/routers@5.3.0...@react-navigation/routers@5.4.0) (2020-04-17)

View File

@@ -1,7 +1,7 @@
{
"name": "@react-navigation/routers",
"description": "Routers to help build custom navigators",
"version": "5.4.0",
"version": "5.4.4",
"keywords": [
"react",
"react-native",
@@ -15,6 +15,7 @@
"homepage": "https://reactnavigation.org/docs/custom-routers.html",
"main": "lib/commonjs/index.js",
"react-native": "src/index.tsx",
"source": "src/index.tsx",
"module": "lib/module/index.js",
"types": "lib/typescript/src/index.d.ts",
"files": [
@@ -30,10 +31,10 @@
"clean": "del lib"
},
"dependencies": {
"nanoid": "^3.0.2"
"nanoid": "^3.1.5"
},
"devDependencies": {
"@react-native-community/bob": "^0.10.0",
"@react-native-community/bob": "^0.13.1",
"del-cli": "^3.0.0",
"typescript": "^3.8.3"
},

View File

@@ -153,6 +153,10 @@ export default function DrawerRouter({
getStateForRouteFocus(state, key) {
const result = router.getStateForRouteFocus(state, key);
if (openByDefault) {
return openDrawer(result);
}
return closeDrawer(result);
},

View File

@@ -59,21 +59,31 @@ export const TabActions = {
const getRouteHistory = (
routes: Route<string>[],
index: number,
backBehavior: BackBehavior
backBehavior: BackBehavior,
initialRouteName: string | undefined
) => {
const history = [{ type: TYPE_ROUTE, key: routes[index].key }];
let initialRouteIndex;
switch (backBehavior) {
case 'initialRoute':
if (index !== 0) {
history.unshift({ type: TYPE_ROUTE, key: routes[0].key });
}
break;
case 'order':
for (let i = index; i > 0; i--) {
history.unshift({ type: TYPE_ROUTE, key: routes[i - 1].key });
}
break;
case 'initialRoute':
initialRouteIndex = routes.findIndex(
(route) => route.name === initialRouteName
);
initialRouteIndex = initialRouteIndex === -1 ? 0 : initialRouteIndex;
if (initialRouteIndex !== index) {
history.unshift({
type: TYPE_ROUTE,
key: routes[initialRouteIndex].key,
});
}
break;
case 'history':
// The history will fill up on navigation
break;
@@ -85,7 +95,8 @@ const getRouteHistory = (
const changeIndex = (
state: TabNavigationState,
index: number,
backBehavior: BackBehavior
backBehavior: BackBehavior,
initialRouteName: string | undefined
) => {
let history;
@@ -96,7 +107,12 @@ const changeIndex = (
.filter((it) => (it.type === 'route' ? it.key !== currentKey : false))
.concat({ type: TYPE_ROUTE, key: currentKey });
} else {
history = getRouteHistory(state.routes, index, backBehavior);
history = getRouteHistory(
state.routes,
index,
backBehavior,
initialRouteName
);
}
return {
@@ -130,7 +146,12 @@ export default function TabRouter({
params: routeParamList[name],
}));
const history = getRouteHistory(routes, index, backBehavior);
const history = getRouteHistory(
routes,
index,
backBehavior,
initialRouteName
);
return {
stale: false,
@@ -189,7 +210,12 @@ export default function TabRouter({
);
if (!history?.length) {
history = getRouteHistory(routes, index, backBehavior);
history = getRouteHistory(
routes,
index,
backBehavior,
initialRouteName
);
}
return {
@@ -223,7 +249,12 @@ export default function TabRouter({
);
if (!history.length) {
history = getRouteHistory(routes, index, backBehavior);
history = getRouteHistory(
routes,
index,
backBehavior,
initialRouteName
);
}
return {
@@ -242,7 +273,7 @@ export default function TabRouter({
return state;
}
return changeIndex(state, index, backBehavior);
return changeIndex(state, index, backBehavior, initialRouteName);
},
getStateForAction(state, action) {
@@ -284,7 +315,8 @@ export default function TabRouter({
: state.routes,
},
index,
backBehavior
backBehavior,
initialRouteName
);
}

View File

@@ -214,6 +214,161 @@ it("doesn't rehydrate state if it's not stale", () => {
).toBe(state);
});
it('restores correct history on rehydrating with backBehavior: order', () => {
const router = TabRouter({ backBehavior: 'order' });
const options = {
routeNames: ['foo', 'bar', 'baz', 'qux'],
routeParamList: {},
};
expect(
router.getRehydratedState(
{
index: 2,
routes: [
{ key: 'foo-0', name: 'foo' },
{ key: 'bar-0', name: 'bar' },
{ key: 'baz-0', name: 'baz' },
{ key: 'qux-0', name: 'qux' },
],
},
options
)
).toEqual({
key: 'tab-test',
index: 2,
routeNames: ['foo', 'bar', 'baz', 'qux'],
routes: [
{ key: 'foo-0', name: 'foo' },
{ key: 'bar-0', name: 'bar' },
{ key: 'baz-0', name: 'baz' },
{ key: 'qux-0', name: 'qux' },
],
history: [
{ key: 'foo-0', type: 'route' },
{ key: 'bar-0', type: 'route' },
{ key: 'baz-0', type: 'route' },
],
stale: false,
type: 'tab',
});
});
it('restores correct history on rehydrating with backBehavior: history', () => {
const router = TabRouter({ backBehavior: 'history' });
const options = {
routeNames: ['foo', 'bar', 'baz', 'qux'],
routeParamList: {},
};
expect(
router.getRehydratedState(
{
index: 2,
routes: [
{ key: 'foo-0', name: 'foo' },
{ key: 'bar-0', name: 'bar' },
{ key: 'baz-0', name: 'baz' },
{ key: 'qux-0', name: 'qux' },
],
},
options
)
).toEqual({
key: 'tab-test',
index: 2,
routeNames: ['foo', 'bar', 'baz', 'qux'],
routes: [
{ key: 'foo-0', name: 'foo' },
{ key: 'bar-0', name: 'bar' },
{ key: 'baz-0', name: 'baz' },
{ key: 'qux-0', name: 'qux' },
],
history: [{ key: 'baz-0', type: 'route' }],
stale: false,
type: 'tab',
});
});
it('restores correct history on rehydrating with backBehavior: initialRoute', () => {
const router = TabRouter({ backBehavior: 'initialRoute' });
const options = {
routeNames: ['foo', 'bar', 'baz', 'qux'],
routeParamList: {},
};
expect(
router.getRehydratedState(
{
index: 2,
routes: [
{ key: 'foo-0', name: 'foo' },
{ key: 'bar-0', name: 'bar' },
{ key: 'baz-0', name: 'baz' },
{ key: 'qux-0', name: 'qux' },
],
},
options
)
).toEqual({
key: 'tab-test',
index: 2,
routeNames: ['foo', 'bar', 'baz', 'qux'],
routes: [
{ key: 'foo-0', name: 'foo' },
{ key: 'bar-0', name: 'bar' },
{ key: 'baz-0', name: 'baz' },
{ key: 'qux-0', name: 'qux' },
],
history: [
{ key: 'foo-0', type: 'route' },
{ key: 'baz-0', type: 'route' },
],
stale: false,
type: 'tab',
});
});
it('restores correct history on rehydrating with backBehavior: none', () => {
const router = TabRouter({ backBehavior: 'none' });
const options = {
routeNames: ['foo', 'bar', 'baz', 'qux'],
routeParamList: {},
};
expect(
router.getRehydratedState(
{
index: 2,
routes: [
{ key: 'foo-0', name: 'foo' },
{ key: 'bar-0', name: 'bar' },
{ key: 'baz-0', name: 'baz' },
{ key: 'qux-0', name: 'qux' },
],
},
options
)
).toEqual({
key: 'tab-test',
index: 2,
routeNames: ['foo', 'bar', 'baz', 'qux'],
routes: [
{ key: 'foo-0', name: 'foo' },
{ key: 'bar-0', name: 'bar' },
{ key: 'baz-0', name: 'baz' },
{ key: 'qux-0', name: 'qux' },
],
history: [{ key: 'baz-0', type: 'route' }],
stale: false,
type: 'tab',
});
});
it('gets state on route names change', () => {
const router = TabRouter({});
@@ -254,6 +409,38 @@ it('gets state on route names change', () => {
stale: false,
type: 'tab',
});
expect(
router.getStateForRouteNamesChange(
{
index: 0,
key: 'tab-test',
routeNames: ['bar', 'baz'],
routes: [
{ key: 'bar-test', name: 'bar' },
{ key: 'baz-test', name: 'baz', params: { answer: 42 } },
],
history: [{ type: 'route', key: 'bar-test' }],
stale: false,
type: 'tab',
},
{
routeNames: ['foo', 'fiz'],
routeParamList: {},
}
)
).toEqual({
index: 0,
key: 'tab-test',
routeNames: ['foo', 'fiz'],
routes: [
{ key: 'foo-test', name: 'foo' },
{ key: 'fiz-test', name: 'fiz' },
],
history: [{ type: 'route', key: 'foo-test' }],
stale: false,
type: 'tab',
});
});
it('preserves focused route on route names change', () => {
@@ -695,6 +882,78 @@ it('handles back action with backBehavior: initialRoute', () => {
).toEqual(null);
});
it('handles back action with backBehavior: initialRoute and initialRouteName', () => {
const router = TabRouter({
backBehavior: 'initialRoute',
initialRouteName: 'baz',
});
const options = {
routeNames: ['bar', 'baz', 'qux'],
routeParamList: {},
};
let state = router.getInitialState(options);
expect(
router.getStateForAction(state, CommonActions.goBack(), options)
).toEqual(null);
state = router.getStateForAction(
state,
TabActions.jumpTo('qux'),
options
) as TabNavigationState;
expect(
router.getStateForAction(state, CommonActions.goBack(), options)
).toEqual({
stale: false,
type: 'tab',
key: 'tab-test',
index: 1,
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'bar-test', name: 'bar' },
{ key: 'baz-test', name: 'baz' },
{ key: 'qux-test', name: 'qux' },
],
history: [{ type: 'route', key: 'baz-test' }],
});
state = router.getStateForAction(
state,
TabActions.jumpTo('bar'),
options
) as TabNavigationState;
expect(
router.getStateForAction(state, CommonActions.goBack(), options)
).toEqual({
stale: false,
type: 'tab',
key: 'tab-test',
index: 1,
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'bar-test', name: 'bar' },
{ key: 'baz-test', name: 'baz' },
{ key: 'qux-test', name: 'qux' },
],
history: [{ type: 'route', key: 'baz-test' }],
});
state = router.getStateForAction(
state,
TabActions.jumpTo('baz'),
options
) as TabNavigationState;
expect(
router.getStateForAction(state, CommonActions.goBack(), options)
).toEqual(null);
});
it('handles back action with backBehavior: none', () => {
const router = TabRouter({ backBehavior: 'none' });
const options = {

View File

@@ -3,6 +3,99 @@
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/stack/compare/@react-navigation/stack@5.3.0...@react-navigation/stack@5.3.1) (2020-05-08)
### Bug Fixes
* fix building typescript definitions. closes [#8216](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/issues/8216) ([47a1229](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/commit/47a12298378747edd2d22e54dc1c8677f98c49b4))
# [5.3.0](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.2.19...@react-navigation/stack@5.3.0) (2020-05-08)
### Bug Fixes
* add proper margins to the header title ([f07cd13](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/commit/f07cd135619d635e8841aa0df0b6e687636e7408))
* include safe are insets in title's margins ([4d1e102](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/commit/4d1e102f8c3ffab116d0195fbab3086f6345a077))
### Features
* add generic type aliases for screen props ([bea14aa](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/commit/bea14aa26fd5cbfebc7973733c5cf1f44fd323aa)), closes [#7971](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/issues/7971)
## [5.2.19](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.2.18...@react-navigation/stack@5.2.19) (2020-05-05)
**Note:** Version bump only for package @react-navigation/stack
## [5.2.18](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.2.17...@react-navigation/stack@5.2.18) (2020-05-01)
**Note:** Version bump only for package @react-navigation/stack
## [5.2.17](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.2.16...@react-navigation/stack@5.2.17) (2020-05-01)
**Note:** Version bump only for package @react-navigation/stack
## [5.2.16](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.2.15...@react-navigation/stack@5.2.16) (2020-04-30)
**Note:** Version bump only for package @react-navigation/stack
## [5.2.15](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.2.14...@react-navigation/stack@5.2.15) (2020-04-30)
### Bug Fixes
* make sure the address bar hides when scrolling on web ([0a19e94](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/commit/0a19e94b23a4d2b5f22d1d9deb0544f586f475ee))
## [5.2.14](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.2.13...@react-navigation/stack@5.2.14) (2020-04-27)
### Bug Fixes
* don't add back the route being replaced ([a695cf9](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/commit/a695cf9c058521ccb4a83eb206dc0da7ce100032))
## [5.2.12](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.2.11...@react-navigation/stack@5.2.12) (2020-04-22)
### Bug Fixes
* animate card to existing closing state on gesture end ([78485ce](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/commit/78485cea6939b9ffec76e0c4b410bc426ed93402)), closes [#7938](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/issues/7938)
## [5.2.11](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.2.10...@react-navigation/stack@5.2.11) (2020-04-17)

View File

@@ -1,7 +1,7 @@
{
"name": "@react-navigation/stack",
"description": "Stack navigator component for iOS and Android with animated transitions and gestures",
"version": "5.2.11",
"version": "5.3.1",
"keywords": [
"react-native-component",
"react-component",
@@ -19,6 +19,7 @@
"homepage": "https://reactnavigation.org/docs/stack-navigator.html",
"main": "lib/commonjs/index.js",
"react-native": "src/index.tsx",
"source": "src/index.tsx",
"module": "lib/module/index.js",
"types": "lib/typescript/src/index.d.ts",
"files": [
@@ -38,18 +39,18 @@
"react-native-iphone-x-helper": "^1.2.1"
},
"devDependencies": {
"@react-native-community/bob": "^0.10.0",
"@react-native-community/masked-view": "^0.1.7",
"@react-navigation/native": "^5.1.6",
"@react-native-community/bob": "^0.13.1",
"@react-native-community/masked-view": "^0.1.10",
"@react-navigation/native": "^5.2.6",
"@types/color": "^3.0.1",
"@types/react": "^16.9.23",
"@types/react-native": "^0.61.22",
"@types/react": "^16.9.34",
"@types/react-native": "^0.62.7",
"del-cli": "^3.0.0",
"react": "~16.9.0",
"react-native": "~0.61.5",
"react-native-gesture-handler": "^1.6.0",
"react-native-safe-area-context": "^0.7.3",
"react-native-screens": "^2.3.0",
"react-native-screens": "^2.7.0",
"typescript": "^3.8.3"
},
"peerDependencies": {

View File

@@ -51,6 +51,7 @@ export { default as useGestureHandlerRef } from './utils/useGestureHandlerRef';
export type {
StackNavigationOptions,
StackNavigationProp,
StackScreenProps,
StackHeaderProps,
StackHeaderLeftButtonProps,
StackHeaderTitleProps,

View File

@@ -15,6 +15,7 @@ import {
NavigationHelpers,
StackNavigationState,
StackActionHelpers,
RouteProp,
} from '@react-navigation/native';
export type StackNavigationEventMap = {
@@ -45,6 +46,14 @@ export type StackNavigationProp<
> &
StackActionHelpers<ParamList>;
export type StackScreenProps<
ParamList extends ParamListBase,
RouteName extends keyof ParamList = string
> = {
navigation: StackNavigationProp<ParamList, RouteName>;
route: RouteProp<ParamList, RouteName>;
};
export type Layout = { width: number; height: number };
export type GestureDirection =
@@ -276,7 +285,8 @@ export type StackNavigationOptions = StackHeaderOptions &
cardStyle?: StyleProp<ViewStyle>;
/**
* Whether transition animation should be enabled the screen.
* If you set it to `false`, the screen won't animate when pushing or popping. Defaults to `true`.
* If you set it to `false`, the screen won't animate when pushing or popping.
* Defaults to `true` on Android and iOS, `false` on Web.
*/
animationEnabled?: boolean;
/**
@@ -286,10 +296,12 @@ export type StackNavigationOptions = StackHeaderOptions &
animationTypeForReplace?: 'push' | 'pop';
/**
* Whether you can use gestures to dismiss this screen. Defaults to `true` on iOS, `false` on Android.
* Not supported on Web.
*/
gestureEnabled?: boolean;
/**
* Object to override the distance of touch start from the edge of the screen to recognize gestures.
* Not supported on Web.
*/
gestureResponseDistance?: {
/**
@@ -302,8 +314,8 @@ export type StackNavigationOptions = StackHeaderOptions &
horizontal?: number;
};
/**
* Number which determines the relevance of velocity for the gesture.
* Defaults to 0.3.
* Number which determines the relevance of velocity for the gesture. Defaults to 0.3.
* Not supported on Web.
*/
gestureVelocityImpact?: number;
/**

View File

@@ -1,6 +1,5 @@
import * as React from 'react';
import { PanGestureHandler } from 'react-native-gesture-handler';
export default React.createContext<React.Ref<PanGestureHandler> | undefined>(
undefined
);
export default React.createContext<React.Ref<
import('react-native-gesture-handler').PanGestureHandler
> | null>(null);

View File

@@ -0,0 +1,22 @@
import * as React from 'react';
import {
PanGestureHandler as PanGestureHandlerNative,
PanGestureHandlerProperties,
} from 'react-native-gesture-handler';
import GestureHandlerRefContext from '../utils/GestureHandlerRefContext';
export function PanGestureHandler(props: PanGestureHandlerProperties) {
const gestureRef = React.useRef<PanGestureHandlerNative>(null);
return (
<GestureHandlerRefContext.Provider value={gestureRef}>
<PanGestureHandlerNative {...props} />
</GestureHandlerRefContext.Provider>
);
}
export {
GestureHandlerRootView,
State as GestureState,
PanGestureHandlerGestureEvent,
} from 'react-native-gesture-handler';

View File

@@ -0,0 +1,24 @@
import * as React from 'react';
import { View } from 'react-native';
import type { PanGestureHandlerProperties } from 'react-native-gesture-handler';
const Dummy: any = ({ children }: { children: React.ReactNode }) => (
<>{children}</>
);
export const PanGestureHandler = Dummy as React.ComponentType<
PanGestureHandlerProperties
>;
export const GestureHandlerRootView = View;
export const GestureState = {
UNDETERMINED: 0,
FAILED: 1,
BEGAN: 2,
CANCELLED: 3,
ACTIVE: 4,
END: 5,
};
export type { PanGestureHandlerGestureEvent } from 'react-native-gesture-handler';

View File

@@ -41,6 +41,7 @@ export default React.memo(function Header(props: StackHeaderProps) {
: previous.route.name;
}
// eslint-disable-next-line react-hooks/exhaustive-deps
const goBack = React.useCallback(
debounce(() => {
if (navigation.isFocused() && navigation.canGoBack()) {

Some files were not shown because too many files have changed in this diff Show More