Compare commits

...

58 Commits

Author SHA1 Message Date
Satyajit Sahoo
ced2a24aa6 chore: publish
- @react-navigation/bottom-tabs@5.5.0
 - @react-navigation/compat@5.1.24
 - @react-navigation/core@5.8.2
 - @react-navigation/drawer@5.8.0
 - @react-navigation/material-bottom-tabs@5.2.8
 - @react-navigation/material-top-tabs@5.2.8
 - @react-navigation/native@5.4.3
 - @react-navigation/routers@5.4.7
 - @react-navigation/stack@5.4.0
2020-05-23 18:36:57 +02:00
Satyajit Sahoo
ebf1345b39 refactor: simplify bottom tab bar 2020-05-23 18:34:12 +02:00
Satyajit Sahoo
df3544d9b4 Revert "fix: allow HeaderBackground's subViews to be touchable" (#8316) 2020-05-23 18:22:35 +02:00
Ashoat Tevosyan
c1e46f8e33 feat: animate changes to tabBarVisible in BottomTabBar (#8286)
## Motivation

Some designs call for custom keyboard inputs, or other bottom-aligned views meant overlap over the keyboard. Right now the best option on Android for this case is to set `tabBarVisible`. However changes to `tabBarVisible` doesn't get animated currently, which makes the custom-keyboard-open experience a bit more jarring than the native-keyboard-open one.

## Approach

I basically cribbed the `Animated.Value` we were using for `keyboardHidesTabBar` and made it depend on both. Note that the offset height depends on which of the two uses cases we're dealing with, which is explained in the code.

## Test plan

I played around with the `BottomTabs` example, setting certain screens to `tabBarVisible: true` and making sure it animated.
2020-05-23 18:16:30 +02:00
Bright Lee
021a9111d7 fix: allow HeaderBackground's subViews to be touchable (#8314)
When you're using the following options on `Stack`, the touch event goes to the behind of the Header and we can't really solve this problem in given `headerBackground` component level.
```
 options={{
          headerTransparent: true,
          headerBackground: 
```

### Problem
![May-23-2020 09-31-01](https://user-images.githubusercontent.com/916690/82726502-ebc11e80-9ce4-11ea-81d0-cc6a18bd70a7.gif)
2020-05-23 18:14:59 +02:00
Satyajit Sahoo
d3ace96981 chore: add linking prefix for expo to slience the warning 2020-05-23 17:37:45 +02:00
Satyajit Sahoo
edbc6b1e84 chore: tweak metro config 2020-05-23 17:33:34 +02:00
Satyajit Sahoo
c52d19bec8 chore: add example to hide and show bottom tab bar 2020-05-21 15:31:34 +02:00
Satyajit Sahoo
6dd45fcff9 fix: don't ignore previous header heights on layout update 2020-05-21 12:54:12 +02:00
Janic Duplessis
d62fbfe255 feat: update react-native-safe-area-context to 1.0.0 (#8182)
I made sure 1.0 is backwards compatible with react-navigation, which means using rn-safe-area-context@1+ with older versions of react-navigation will still work.
2020-05-21 11:25:36 +02:00
Evan Bacon
b14094619f chore: ignore __tests__ in prod builds (#8307)
The tests are being bundled and shipped in prod, this adds a bit of unneeded weight to npm installs. Now they won't be included.

```
@react-navigation/core
- before: 274 files - pkg: 211.0 kB - unpkg: 1 MB
- after: 238 files - pkg: 192.1 kB - unpkg: 827.3 kB
```
2020-05-21 11:15:12 +02:00
Ashoat Tevosyan
4c4d864af2 refactor: memoize initializedState in useNavigationBuilder (#8281) 2020-05-21 11:13:06 +02:00
Michał Osadnik
e1969f4e17 refactor: extract NavigationStateContext (#8304) 2020-05-21 10:41:34 +02:00
Satyajit Sahoo
175c07a28c chore: publish
- @react-navigation/example@5.1.0
 - @react-navigation/bottom-tabs@5.4.7
 - @react-navigation/compat@5.1.23
 - @react-navigation/core@5.8.1
 - @react-navigation/drawer@5.7.7
 - @react-navigation/material-bottom-tabs@5.2.7
 - @react-navigation/material-top-tabs@5.2.7
 - @react-navigation/native@5.4.2
 - @react-navigation/routers@5.4.6
 - @react-navigation/stack@5.3.9
2020-05-20 13:27:29 +02:00
osdnk
2980627cbf chore: publish
- @react-navigation/bottom-tabs@5.4.6
 - @react-navigation/compat@5.1.22
 - @react-navigation/core@5.8.0
 - @react-navigation/drawer@5.7.6
 - @react-navigation/material-bottom-tabs@5.2.6
 - @react-navigation/material-top-tabs@5.2.6
 - @react-navigation/native@5.4.1
 - @react-navigation/routers@5.4.5
 - @react-navigation/stack@5.3.8
2020-05-20 10:29:05 +02:00
Michał Osadnik
d024ec6d74 feat: add getCurrentOptions (#8277) 2020-05-19 20:53:08 +02:00
Satyajit Sahoo
4d1b85c751 chore: limit height of material top tabs example 2020-05-19 19:27:05 +02:00
Satyajit Sahoo
4a818fdfb3 chore: tweak master-detail example 2020-05-19 18:56:51 +02:00
Satyajit Sahoo
0194de1061 chore: upgrade bob 2020-05-19 14:25:20 +02:00
Michał Osadnik
7b25c8eb2e feat: add getCurrentRoute (#8254) 2020-05-18 01:55:09 +02:00
Satyajit Sahoo
9304a8a16c chore: publish
- @react-navigation/bottom-tabs@5.4.5
 - @react-navigation/compat@5.1.21
 - @react-navigation/core@5.7.0
 - @react-navigation/drawer@5.7.5
 - @react-navigation/material-bottom-tabs@5.2.5
 - @react-navigation/material-top-tabs@5.2.5
 - @react-navigation/native@5.4.0
 - @react-navigation/stack@5.3.7
2020-05-17 01:20:24 +02:00
Satyajit Sahoo
51b40879bd fix: center icons in material tab bar. fixes #8248 2020-05-17 01:06:29 +02:00
Satyajit Sahoo
51f4d11fdf fix: don't use Object.fromEntries 2020-05-17 00:56:19 +02:00
Satyajit Sahoo
c2aa4bb2eb test: fix integration tests 2020-05-16 21:18:42 +02:00
Satyajit Sahoo
199a892a6d chore: fix the links in the example 2020-05-16 01:58:05 +02:00
Satyajit Sahoo
60cb3c9ba7 feat: add a PathConfig type 2020-05-15 18:57:28 +02:00
Satyajit Sahoo
6ccceaea8b chore: tweak linking config in the example 2020-05-15 18:48:03 +02:00
Satyajit Sahoo
d14f38b80a fix: fix types for linking options 2020-05-15 18:37:58 +02:00
Satyajit Sahoo
c481748f00 chore: publish
- @react-navigation/stack@5.3.6
2020-05-15 17:39:47 +02:00
Satyajit Sahoo
d45dbe97dc fix: reduce header title margin. fixes #8267 2020-05-15 17:39:33 +02:00
Janic Duplessis
7623945f6e chore: fix repo url in readme (#8235) 2020-05-14 19:10:08 +02:00
Satyajit Sahoo
1dddaff45c chore: publish
- @react-navigation/bottom-tabs@5.4.4
 - @react-navigation/compat@5.1.20
 - @react-navigation/core@5.6.1
 - @react-navigation/drawer@5.7.4
 - @react-navigation/material-bottom-tabs@5.2.4
 - @react-navigation/material-top-tabs@5.2.4
 - @react-navigation/native@5.3.2
 - @react-navigation/stack@5.3.5
2020-05-14 13:22:54 +02:00
Satyajit Sahoo
21b397f0d6 fix: don't use flat since it's not supported in node 2020-05-14 13:22:14 +02:00
Satyajit Sahoo
2ff0531695 chore: publish
- @react-navigation/bottom-tabs@5.4.3
 - @react-navigation/compat@5.1.19
 - @react-navigation/core@5.6.0
 - @react-navigation/drawer@5.7.3
 - @react-navigation/material-bottom-tabs@5.2.3
 - @react-navigation/material-top-tabs@5.2.3
 - @react-navigation/native@5.3.1
 - @react-navigation/stack@5.3.4
2020-05-14 12:45:50 +02:00
Satyajit Sahoo
0149e85a95 fix: ignore state updates when we're not mounted
closes #8226
2020-05-14 12:43:44 +02:00
Satyajit Sahoo
3c47716826 fix: ignore extra slashes in the pattern 2020-05-14 04:40:44 +02:00
Satyajit Sahoo
acc9646426 feat: merge path patterns for nested screens (#8253)
Currently, when we define path patterns in the linking config, they ignore the parent screen's path when parsing, for example, say we have this config:

{
  Home: {
    path: 'home',
    screens: {
      Profile: 'u/:id',
    },
  },
}
If we parse the URL /home, we'll get this state:

{
  routes: [{ name: 'Home' }],
}
If we parse the URL /home/u/cal, we'll get this state:

{
  routes: [
    {
      name: 'Home',
      state: {
        routes: [
          {
            name: 'Home',
            state: {
              routes: [
                {
                  name: 'Profile',
                  params: { id: 'cal' },
                },
              ],
            },
          },
        ],
      },
    },
  ],
}
Note how we have 2 Home screens nested inside each other. This is not something people usually expect and it seems to trip up a lot of people. Something like following will be more intuitive:

{
  routes: [
    {
      name: 'Home',
      state: {
        routes: [
          {
            name: 'Profile',
            params: { id: 'cal' },
          },
        ],
      },
    },
  ],
}
Essentially, we're treating patterns as relative to their parent rather than matching the exact segments. This PR changes the behavior of parsing links to treat configs like so. I hope it'll be easier to understand for people.

There is also a new option, exact, which brings back the olde behavior of matching the exact pattern:

{
  Home: {
    path: 'home',
    screens: {
      Profile: {
        path: 'u/:id',
        exact: true,
      },
    },
  },
}
Which will be useful if home is not in the URL, e.g. /u/cal.

This change only affects situations where both parent and child screen configuration have a pattern defined. If the parent didn't have a pattern defined, nothing changes from previous behavior:

{
  Home: {
    screens: {
      Profile: {
        path: 'u/:id',
      },
    },
  },
}
2020-05-14 00:39:32 +02:00
Satyajit Sahoo
6dce0780ed chore: publish
- @react-navigation/stack@5.3.3
2020-05-11 17:31:35 +02:00
Hein Rutjes
dd7cff2016 fix: fix ios transitionspec settle time (#8028)
# Why

When using the stack navigator on iOS, it takes a (too) long time before the  `didFocus` and stack `onTransitionEnd` lifecycle events are triggered. The visual animation is typically completed well within 500 msec, but it takes around 1000 msec before the previously mentioned events are emitted. This causes problems with for instance `react-navigation-shared-element`, which relies on these events to fire in a timely manner(https://github.com/IjzerenHein/react-navigation-shared-element/issues/24)

# How

This PR updates the resting threshold so that the underlying spring settles faster. No visual differences or differences in smoothness were witnessed during testing.

## Before

Time to settle `didFocus`: **941**
Time to settle `stack.onTransitionEnd`: **924**

```
15:59:55.743 [ListViewStack]startTransition, closing: false, nestingDepth: 1
15:59:55.744 [ListViewStack]willFocus, scene: "DetailScreen", depth: 1, closing: false
15:59:55.745 Transition start: "ListScreen" -> "DetailScreen"
15:59:56.667 [ListViewStack]endTransition, closing: false, nestingDepth: 1
15:59:56.668 Transition end: "DetailScreen"
15:59:56.685 [ListViewStack]didFocus, scene: "DetailScreen", depth: 1
```

## After

Time to settle `didFocus`: **529**
Time to settle `stack.onTransitionEnd`: **512**

```
15:55:00.686 [ListViewStack]startTransition, closing: false, nestingDepth: 1
15:55:00.687 [ListViewStack]willFocus, scene: "DetailScreen", depth: 1, closing: false
15:55:00.687 Transition start: "ListScreen" -> "DetailScreen"
15:55:01.198 [ListViewStack]endTransition, closing: false, nestingDepth: 1
15:55:01.199 Transition end: "DetailScreen"
15:55:01.216 [ListViewStack]didFocus, scene: "DetailScreen", depth: 1
2020-05-11 17:03:12 +02:00
Satyajit Sahoo
740c6b6706 chore: publish
- @react-navigation/bottom-tabs@5.4.2
 - @react-navigation/compat@5.1.18
 - @react-navigation/drawer@5.7.2
 - @react-navigation/material-bottom-tabs@5.2.2
 - @react-navigation/material-top-tabs@5.2.2
 - @react-navigation/native@5.3.0
 - @react-navigation/stack@5.3.2
2020-05-10 08:34:40 +02:00
Satyajit Sahoo
039017bc2a feat: initialState should take priority over deep link 2020-05-10 08:28:48 +02:00
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
76 changed files with 5312 additions and 1709 deletions

View File

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

41
example/CHANGELOG.md Normal file
View File

@@ -0,0 +1,41 @@
# Change Log
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [5.1.0](https://github.com/react-navigation/react-navigation/compare/@react-navigation/example@5.0.0-alpha.23...@react-navigation/example@5.1.0) (2020-05-20)
### Bug Fixes
* add config to enable redux devtools integration ([c9c825b](https://github.com/react-navigation/react-navigation/commit/c9c825bee61426635a28ee149eeeff3d628171cd))
* clamp interpolated styles ([67798af](https://github.com/react-navigation/react-navigation/commit/67798af869dcbbf323629fc7e7cc9062d1e12c29))
* disable screens when mode is modal on older expo versions ([94d7b28](https://github.com/react-navigation/react-navigation/commit/94d7b28c0b2ce0d56c99b224610f305be6451626))
* dispatch pop early when screen is closed with gesture ([#336](https://github.com/react-navigation/react-navigation/issues/336)) ([3d937d1](https://github.com/react-navigation/react-navigation/commit/3d937d1e6571cd613e830d64f7b2e7426076d371)), closes [#267](https://github.com/react-navigation/react-navigation/issues/267)
* provide initial values for safe area to prevent blank screen ([#238](https://github.com/react-navigation/react-navigation/issues/238)) ([77b7570](https://github.com/react-navigation/react-navigation/commit/77b757091c0451e20bca01138629669c7da544a8))
* render fallback only if linking is enabled. closes [#8161](https://github.com/react-navigation/react-navigation/issues/8161) ([1c075ff](https://github.com/react-navigation/react-navigation/commit/1c075ffb169d233ed0515efea264a5a69b4de52e))
* return onPress instead of onClick for useLinkProps ([ae5442e](https://github.com/react-navigation/react-navigation/commit/ae5442ebe812b91fa1f12164f27d1aeed918ab0e))
* rtl in native app example ([50b366e](https://github.com/react-navigation/react-navigation/commit/50b366e7341f201d29a44f20b7771b3a832b0045))
* screens integration on Android ([#294](https://github.com/react-navigation/react-navigation/issues/294)) ([9bfb295](https://github.com/react-navigation/react-navigation/commit/9bfb29562020c61b4d5c9bee278bcb1c7bdb8b67))
* spread parent params to children in compat navigator ([24febf6](https://github.com/react-navigation/react-navigation/commit/24febf6ea99be2e5f22005fdd2a82136d647255c)), closes [#6785](https://github.com/react-navigation/react-navigation/issues/6785)
* update screens for native stack ([5411816](https://github.com/react-navigation/react-navigation/commit/54118161885738a6d20b062c7e6679f3bace8424))
* wrap navigators in gesture handler root ([41a5e1a](https://github.com/react-navigation/react-navigation/commit/41a5e1a385aa5180abc3992a4c67077c37b998b9))
### Features
* add `animationTypeForReplace` option ([#297](https://github.com/react-navigation/react-navigation/issues/297)) ([6262f72](https://github.com/react-navigation/react-navigation/commit/6262f7298bff843571fb4b1a677d3beabe29833e))
* add `screens` prop for nested configs ([#308](https://github.com/react-navigation/react-navigation/issues/308)) ([b931ae6](https://github.com/react-navigation/react-navigation/commit/b931ae62dfb2c5253c94ea5ace73e9070ec17c4a))
* add `useLinkBuilder` hook to build links ([2792f43](https://github.com/react-navigation/react-navigation/commit/2792f438fe45428fe193e3708fee7ad61966cbf4))
* add a useLinkProps hook ([f2291d1](https://github.com/react-navigation/react-navigation/commit/f2291d110faa2aa8e10c9133c1c0c28d54af7917))
* add action prop to Link ([942d2be](https://github.com/react-navigation/react-navigation/commit/942d2be2c72720469475ce12ec8df23825994dbf))
* add custom theme support ([#211](https://github.com/react-navigation/react-navigation/issues/211)) ([00fc616](https://github.com/react-navigation/react-navigation/commit/00fc616de0572bade8aa85052cdc8290360b1d7f))
* add deeplinking to native example ([#309](https://github.com/react-navigation/react-navigation/issues/309)) ([e55e866](https://github.com/react-navigation/react-navigation/commit/e55e866af2f2163ee89bc527997cda13ffeb2abe))
* add headerStatusBarHeight option to stack ([b201fd2](https://github.com/react-navigation/react-navigation/commit/b201fd20716a2f03cb9373c72281f5d396a9356d))
* add Link component as useLinkTo hook for navigating to links ([2573b5b](https://github.com/react-navigation/react-navigation/commit/2573b5beaac1240434e52f3f57bb29da2f541c88))
* add openByDefault option to drawer ([36689e2](https://github.com/react-navigation/react-navigation/commit/36689e24c21b474692bb7ecd0b901c8afbbe9a20))
* add permanent drawer type ([#7818](https://github.com/react-navigation/react-navigation/issues/7818)) ([6a5d0a0](https://github.com/react-navigation/react-navigation/commit/6a5d0a035afae60d91aef78401ec8826295746fe))
* add preventDefault functionality in material bottom tabs ([3dede31](https://github.com/react-navigation/react-navigation/commit/3dede316ccab3b2403a475f60ce20b5c4e4cc068))
* emit appear and dismiss events for native stack ([f1df4a0](https://github.com/react-navigation/react-navigation/commit/f1df4a080877b3642e748a41a5ffc2da8c449a8c))
* initialState should take priority over deep link ([039017b](https://github.com/react-navigation/react-navigation/commit/039017bc2af69120d2d10e8f2c8a62919c37eb65))
* integrate with history API on web ([5a3f835](https://github.com/react-navigation/react-navigation/commit/5a3f8356b05bff7ed20893a5db6804612da3e568))

View File

@@ -7,12 +7,6 @@
"slug": "react-navigation-example", "slug": "react-navigation-example",
"description": "Demo app to showcase various functionality of React Navigation", "description": "Demo app to showcase various functionality of React Navigation",
"privacy": "public", "privacy": "public",
"sdkVersion": "37.0.0",
"platforms": [
"ios",
"android",
"web"
],
"version": "1.0.0", "version": "1.0.0",
"icon": "./assets/icon.png", "icon": "./assets/icon.png",
"splash": { "splash": {
@@ -20,15 +14,16 @@
"resizeMode": "contain", "resizeMode": "contain",
"backgroundColor": "#ffffff" "backgroundColor": "#ffffff"
}, },
"updates": { "sdkVersion": "37.0.0",
"fallbackToCacheTimeout": 0 "platforms": ["ios", "android", "web"],
},
"assetBundlePatterns": [
"**/*"
],
"ios": { "ios": {
"supportsTablet": true "supportsTablet": true
}, },
"updates": {
"fallbackToCacheTimeout": 0
},
"assetBundlePatterns": ["**/*"],
"scheme": "rne",
"entryPoint": "App.tsx" "entryPoint": "App.tsx"
} }
} }

View File

@@ -6,7 +6,7 @@ beforeEach(async () => {
it('loads the article page', async () => { it('loads the article page', async () => {
expect(await page.evaluate(() => location.pathname + location.search)).toBe( expect(await page.evaluate(() => location.pathname + location.search)).toBe(
'/link-component/Article?author=Gandalf' '/link-component/article/gandalf'
); );
expect( expect(
((await page.accessibility.snapshot()) as any)?.children?.find( ((await page.accessibility.snapshot()) as any)?.children?.find(
@@ -16,24 +16,24 @@ it('loads the article page', async () => {
}); });
it('goes to the album page and goes back', async () => { it('goes to the album page and goes back', async () => {
await page.click('[href="/link-component/Album"]'); await page.click('[href="/link-component/music"]');
expect(await page.evaluate(() => location.pathname + location.search)).toBe( expect(await page.evaluate(() => location.pathname + location.search)).toBe(
'/link-component/Album' '/link-component/music'
); );
expect( expect(
((await page.accessibility.snapshot()) as any)?.children?.find( ((await page.accessibility.snapshot()) as any)?.children?.find(
(it: any) => it.role === 'heading' (it: any) => it.role === 'heading'
)?.name )?.name
).toBe('Album'); ).toBe('Albums');
await page.click('[aria-label="Article by Gandalf, back"]'); await page.click('[aria-label="Article by Gandalf, back"]');
await page.waitForNavigation(); await page.waitForNavigation();
expect(await page.evaluate(() => location.pathname + location.search)).toBe( expect(await page.evaluate(() => location.pathname + location.search)).toBe(
'/link-component/Article?author=Gandalf' '/link-component/article/gandalf'
); );
expect( expect(

View File

@@ -1,35 +1,38 @@
PODS: PODS:
- boost-for-react-native (1.63.0) - boost-for-react-native (1.63.0)
- DoubleConversion (1.1.6) - DoubleConversion (1.1.6)
- EXAppLoaderProvider (8.0.0) - EXBlur (8.1.0):
- EXBlur (8.0.0):
- UMCore - UMCore
- EXConstants (8.0.0): - EXConstants (9.0.0):
- UMConstantsInterface - UMConstantsInterface
- UMCore - UMCore
- EXErrorRecovery (1.0.0): - EXErrorRecovery (1.1.0):
- UMCore - UMCore
- EXFileSystem (8.0.0): - EXFileSystem (8.1.0):
- UMCore - UMCore
- UMFileSystemInterface - UMFileSystemInterface
- EXFont (8.0.0): - EXFont (8.1.0):
- UMCore - UMCore
- UMFontInterface - UMFontInterface
- EXKeepAwake (8.0.0): - EXImageLoader (1.0.1):
- React-Core
- UMCore - UMCore
- EXLinearGradient (8.0.0): - UMImageLoaderInterface
- EXKeepAwake (8.1.0):
- UMCore - UMCore
- EXLocation (8.0.0): - EXLinearGradient (8.1.0):
- UMCore
- EXLocation (8.1.0):
- UMCore - UMCore
- UMPermissionsInterface - UMPermissionsInterface
- UMTaskManagerInterface - UMTaskManagerInterface
- EXPermissions (8.0.0): - EXPermissions (8.1.0):
- UMCore - UMCore
- UMPermissionsInterface - UMPermissionsInterface
- EXSQLite (8.0.0): - EXSQLite (8.1.0):
- UMCore - UMCore
- UMFileSystemInterface - UMFileSystemInterface
- EXWebBrowser (8.0.0): - EXWebBrowser (8.2.1):
- UMCore - UMCore
- FBLazyVector (0.61.5) - FBLazyVector (0.61.5)
- FBReactNativeSpec (0.61.5): - FBReactNativeSpec (0.61.5):
@@ -50,8 +53,6 @@ PODS:
- glog - glog
- glog (0.3.5) - glog (0.3.5)
- RCTRequired (0.61.5) - RCTRequired (0.61.5)
- RCTRestart (0.0.13):
- React
- RCTTypeSafety (0.61.5): - RCTTypeSafety (0.61.5):
- FBLazyVector (= 0.61.5) - FBLazyVector (= 0.61.5)
- Folly (= 2018.10.22.00) - Folly (= 2018.10.22.00)
@@ -214,7 +215,9 @@ PODS:
- React-cxxreact (= 0.61.5) - React-cxxreact (= 0.61.5)
- React-jsi (= 0.61.5) - React-jsi (= 0.61.5)
- React-jsinspector (0.61.5) - React-jsinspector (0.61.5)
- react-native-safe-area-context (0.6.2): - react-native-restart (0.0.15):
- React
- react-native-safe-area-context (1.0.0):
- React - React
- React-RCTActionSheet (0.61.5): - React-RCTActionSheet (0.61.5):
- React-Core/RCTActionSheetHeaders (= 0.61.5) - React-Core/RCTActionSheetHeaders (= 0.61.5)
@@ -251,39 +254,41 @@ PODS:
- React-cxxreact (= 0.61.5) - React-cxxreact (= 0.61.5)
- React-jsi (= 0.61.5) - React-jsi (= 0.61.5)
- ReactCommon/jscallinvoker (= 0.61.5) - ReactCommon/jscallinvoker (= 0.61.5)
- RNCMaskedView (0.1.5): - RNCMaskedView (0.1.10):
- React - React
- RNGestureHandler (1.5.5): - RNGestureHandler (1.6.1):
- React - React
- RNReanimated (1.4.0): - RNReanimated (1.8.0):
- React - React
- RNScreens (2.0.0-alpha.33): - RNScreens (2.7.0):
- React - React
- UMBarCodeScannerInterface (5.0.0) - UMAppLoader (1.0.2)
- UMCameraInterface (5.0.0) - UMBarCodeScannerInterface (5.1.0)
- UMConstantsInterface (5.0.0) - UMCameraInterface (5.1.0)
- UMCore (5.0.0) - UMConstantsInterface (5.1.0)
- UMFaceDetectorInterface (5.0.0) - UMCore (5.1.2)
- UMFileSystemInterface (5.0.0) - UMFaceDetectorInterface (5.1.0)
- UMFontInterface (5.0.0) - UMFileSystemInterface (5.1.0)
- UMImageLoaderInterface (5.0.0) - UMFontInterface (5.1.0)
- UMPermissionsInterface (5.0.0) - UMImageLoaderInterface (5.1.0)
- UMReactNativeAdapter (5.0.0): - UMPermissionsInterface (5.1.0):
- UMCore
- UMReactNativeAdapter (5.2.0):
- React-Core - React-Core
- UMCore - UMCore
- UMFontInterface - UMFontInterface
- UMSensorsInterface (5.0.0) - UMSensorsInterface (5.1.0)
- UMTaskManagerInterface (5.0.0) - UMTaskManagerInterface (5.1.0)
- Yoga (1.14.0) - Yoga (1.14.0)
DEPENDENCIES: DEPENDENCIES:
- DoubleConversion (from `../../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`) - DoubleConversion (from `../../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`)
- EXAppLoaderProvider (from `../../node_modules/expo-app-loader-provider/ios`)
- EXBlur (from `../../node_modules/expo-blur/ios`) - EXBlur (from `../../node_modules/expo-blur/ios`)
- EXConstants (from `../../node_modules/expo-constants/ios`) - EXConstants (from `../../node_modules/expo-constants/ios`)
- EXErrorRecovery (from `../../node_modules/expo-error-recovery/ios`) - EXErrorRecovery (from `../../node_modules/expo-error-recovery/ios`)
- EXFileSystem (from `../../node_modules/expo-file-system/ios`) - EXFileSystem (from `../../node_modules/expo-file-system/ios`)
- EXFont (from `../../node_modules/expo-font/ios`) - EXFont (from `../../node_modules/expo-font/ios`)
- EXImageLoader (from `../../node_modules/expo-image-loader/ios`)
- EXKeepAwake (from `../../node_modules/expo-keep-awake/ios`) - EXKeepAwake (from `../../node_modules/expo-keep-awake/ios`)
- EXLinearGradient (from `../../node_modules/expo-linear-gradient/ios`) - EXLinearGradient (from `../../node_modules/expo-linear-gradient/ios`)
- EXLocation (from `../../node_modules/expo-location/ios`) - EXLocation (from `../../node_modules/expo-location/ios`)
@@ -295,7 +300,6 @@ DEPENDENCIES:
- Folly (from `../../node_modules/react-native/third-party-podspecs/Folly.podspec`) - Folly (from `../../node_modules/react-native/third-party-podspecs/Folly.podspec`)
- glog (from `../../node_modules/react-native/third-party-podspecs/glog.podspec`) - glog (from `../../node_modules/react-native/third-party-podspecs/glog.podspec`)
- RCTRequired (from `../../node_modules/react-native/Libraries/RCTRequired`) - RCTRequired (from `../../node_modules/react-native/Libraries/RCTRequired`)
- RCTRestart (from `../../node_modules/react-native-restart/ios`)
- RCTTypeSafety (from `../../node_modules/react-native/Libraries/TypeSafety`) - RCTTypeSafety (from `../../node_modules/react-native/Libraries/TypeSafety`)
- React (from `../../node_modules/react-native/`) - React (from `../../node_modules/react-native/`)
- React-Core (from `../../node_modules/react-native/`) - React-Core (from `../../node_modules/react-native/`)
@@ -306,6 +310,7 @@ DEPENDENCIES:
- React-jsi (from `../../node_modules/react-native/ReactCommon/jsi`) - React-jsi (from `../../node_modules/react-native/ReactCommon/jsi`)
- React-jsiexecutor (from `../../node_modules/react-native/ReactCommon/jsiexecutor`) - React-jsiexecutor (from `../../node_modules/react-native/ReactCommon/jsiexecutor`)
- React-jsinspector (from `../../node_modules/react-native/ReactCommon/jsinspector`) - React-jsinspector (from `../../node_modules/react-native/ReactCommon/jsinspector`)
- react-native-restart (from `../../node_modules/react-native-restart`)
- react-native-safe-area-context (from `../../node_modules/react-native-safe-area-context`) - react-native-safe-area-context (from `../../node_modules/react-native-safe-area-context`)
- React-RCTActionSheet (from `../../node_modules/react-native/Libraries/ActionSheetIOS`) - React-RCTActionSheet (from `../../node_modules/react-native/Libraries/ActionSheetIOS`)
- React-RCTAnimation (from `../../node_modules/react-native/Libraries/NativeAnimation`) - React-RCTAnimation (from `../../node_modules/react-native/Libraries/NativeAnimation`)
@@ -322,10 +327,11 @@ DEPENDENCIES:
- RNGestureHandler (from `../../node_modules/react-native-gesture-handler`) - RNGestureHandler (from `../../node_modules/react-native-gesture-handler`)
- RNReanimated (from `../../node_modules/react-native-reanimated`) - RNReanimated (from `../../node_modules/react-native-reanimated`)
- RNScreens (from `../../node_modules/react-native-screens`) - RNScreens (from `../../node_modules/react-native-screens`)
- UMAppLoader (from `../../node_modules/unimodules-app-loader/ios`)
- UMBarCodeScannerInterface (from `../../node_modules/unimodules-barcode-scanner-interface/ios`) - UMBarCodeScannerInterface (from `../../node_modules/unimodules-barcode-scanner-interface/ios`)
- UMCameraInterface (from `../../node_modules/unimodules-camera-interface/ios`) - UMCameraInterface (from `../../node_modules/unimodules-camera-interface/ios`)
- UMConstantsInterface (from `../../node_modules/unimodules-constants-interface/ios`) - UMConstantsInterface (from `../../node_modules/unimodules-constants-interface/ios`)
- "UMCore (from `../../node_modules/@unimodules/core/ios`)" - "UMCore (from `../../node_modules/react-native-unimodules/node_modules/@unimodules/core/ios`)"
- UMFaceDetectorInterface (from `../../node_modules/unimodules-face-detector-interface/ios`) - UMFaceDetectorInterface (from `../../node_modules/unimodules-face-detector-interface/ios`)
- UMFileSystemInterface (from `../../node_modules/unimodules-file-system-interface/ios`) - UMFileSystemInterface (from `../../node_modules/unimodules-file-system-interface/ios`)
- UMFontInterface (from `../../node_modules/unimodules-font-interface/ios`) - UMFontInterface (from `../../node_modules/unimodules-font-interface/ios`)
@@ -343,42 +349,30 @@ SPEC REPOS:
EXTERNAL SOURCES: EXTERNAL SOURCES:
DoubleConversion: DoubleConversion:
:podspec: "../../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec" :podspec: "../../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec"
EXAppLoaderProvider:
:path: !ruby/object:Pathname
path: "../../node_modules/expo-app-loader-provider/ios"
EXBlur: EXBlur:
:path: !ruby/object:Pathname :path: "../../node_modules/expo-blur/ios"
path: "../../node_modules/expo-blur/ios"
EXConstants: EXConstants:
:path: !ruby/object:Pathname :path: "../../node_modules/expo-constants/ios"
path: "../../node_modules/expo-constants/ios"
EXErrorRecovery: EXErrorRecovery:
:path: !ruby/object:Pathname :path: "../../node_modules/expo-error-recovery/ios"
path: "../../node_modules/expo-error-recovery/ios"
EXFileSystem: EXFileSystem:
:path: !ruby/object:Pathname :path: "../../node_modules/expo-file-system/ios"
path: "../../node_modules/expo-file-system/ios"
EXFont: EXFont:
:path: !ruby/object:Pathname :path: "../../node_modules/expo-font/ios"
path: "../../node_modules/expo-font/ios" EXImageLoader:
:path: "../../node_modules/expo-image-loader/ios"
EXKeepAwake: EXKeepAwake:
:path: !ruby/object:Pathname :path: "../../node_modules/expo-keep-awake/ios"
path: "../../node_modules/expo-keep-awake/ios"
EXLinearGradient: EXLinearGradient:
:path: !ruby/object:Pathname :path: "../../node_modules/expo-linear-gradient/ios"
path: "../../node_modules/expo-linear-gradient/ios"
EXLocation: EXLocation:
:path: !ruby/object:Pathname :path: "../../node_modules/expo-location/ios"
path: "../../node_modules/expo-location/ios"
EXPermissions: EXPermissions:
:path: !ruby/object:Pathname :path: "../../node_modules/expo-permissions/ios"
path: "../../node_modules/expo-permissions/ios"
EXSQLite: EXSQLite:
:path: !ruby/object:Pathname :path: "../../node_modules/expo-sqlite/ios"
path: "../../node_modules/expo-sqlite/ios"
EXWebBrowser: EXWebBrowser:
:path: !ruby/object:Pathname :path: "../../node_modules/expo-web-browser/ios"
path: "../../node_modules/expo-web-browser/ios"
FBLazyVector: FBLazyVector:
:path: "../../node_modules/react-native/Libraries/FBLazyVector" :path: "../../node_modules/react-native/Libraries/FBLazyVector"
FBReactNativeSpec: FBReactNativeSpec:
@@ -389,8 +383,6 @@ EXTERNAL SOURCES:
:podspec: "../../node_modules/react-native/third-party-podspecs/glog.podspec" :podspec: "../../node_modules/react-native/third-party-podspecs/glog.podspec"
RCTRequired: RCTRequired:
:path: "../../node_modules/react-native/Libraries/RCTRequired" :path: "../../node_modules/react-native/Libraries/RCTRequired"
RCTRestart:
:path: "../../node_modules/react-native-restart/ios"
RCTTypeSafety: RCTTypeSafety:
:path: "../../node_modules/react-native/Libraries/TypeSafety" :path: "../../node_modules/react-native/Libraries/TypeSafety"
React: React:
@@ -407,6 +399,8 @@ EXTERNAL SOURCES:
:path: "../../node_modules/react-native/ReactCommon/jsiexecutor" :path: "../../node_modules/react-native/ReactCommon/jsiexecutor"
React-jsinspector: React-jsinspector:
:path: "../../node_modules/react-native/ReactCommon/jsinspector" :path: "../../node_modules/react-native/ReactCommon/jsinspector"
react-native-restart:
:path: "../../node_modules/react-native-restart"
react-native-safe-area-context: react-native-safe-area-context:
:path: "../../node_modules/react-native-safe-area-context" :path: "../../node_modules/react-native-safe-area-context"
React-RCTActionSheet: React-RCTActionSheet:
@@ -437,66 +431,55 @@ EXTERNAL SOURCES:
:path: "../../node_modules/react-native-reanimated" :path: "../../node_modules/react-native-reanimated"
RNScreens: RNScreens:
:path: "../../node_modules/react-native-screens" :path: "../../node_modules/react-native-screens"
UMAppLoader:
:path: "../../node_modules/unimodules-app-loader/ios"
UMBarCodeScannerInterface: UMBarCodeScannerInterface:
:path: !ruby/object:Pathname :path: "../../node_modules/unimodules-barcode-scanner-interface/ios"
path: "../../node_modules/unimodules-barcode-scanner-interface/ios"
UMCameraInterface: UMCameraInterface:
:path: !ruby/object:Pathname :path: "../../node_modules/unimodules-camera-interface/ios"
path: "../../node_modules/unimodules-camera-interface/ios"
UMConstantsInterface: UMConstantsInterface:
:path: !ruby/object:Pathname :path: "../../node_modules/unimodules-constants-interface/ios"
path: "../../node_modules/unimodules-constants-interface/ios"
UMCore: UMCore:
:path: !ruby/object:Pathname :path: "../../node_modules/react-native-unimodules/node_modules/@unimodules/core/ios"
path: "../../node_modules/@unimodules/core/ios"
UMFaceDetectorInterface: UMFaceDetectorInterface:
:path: !ruby/object:Pathname :path: "../../node_modules/unimodules-face-detector-interface/ios"
path: "../../node_modules/unimodules-face-detector-interface/ios"
UMFileSystemInterface: UMFileSystemInterface:
:path: !ruby/object:Pathname :path: "../../node_modules/unimodules-file-system-interface/ios"
path: "../../node_modules/unimodules-file-system-interface/ios"
UMFontInterface: UMFontInterface:
:path: !ruby/object:Pathname :path: "../../node_modules/unimodules-font-interface/ios"
path: "../../node_modules/unimodules-font-interface/ios"
UMImageLoaderInterface: UMImageLoaderInterface:
:path: !ruby/object:Pathname :path: "../../node_modules/unimodules-image-loader-interface/ios"
path: "../../node_modules/unimodules-image-loader-interface/ios"
UMPermissionsInterface: UMPermissionsInterface:
:path: !ruby/object:Pathname :path: "../../node_modules/unimodules-permissions-interface/ios"
path: "../../node_modules/unimodules-permissions-interface/ios"
UMReactNativeAdapter: UMReactNativeAdapter:
:path: !ruby/object:Pathname :path: "../../node_modules/@unimodules/react-native-adapter/ios"
path: "../../node_modules/@unimodules/react-native-adapter/ios"
UMSensorsInterface: UMSensorsInterface:
:path: !ruby/object:Pathname :path: "../../node_modules/unimodules-sensors-interface/ios"
path: "../../node_modules/unimodules-sensors-interface/ios"
UMTaskManagerInterface: UMTaskManagerInterface:
:path: !ruby/object:Pathname :path: "../../node_modules/unimodules-task-manager-interface/ios"
path: "../../node_modules/unimodules-task-manager-interface/ios"
Yoga: Yoga:
:path: "../../node_modules/react-native/ReactCommon/yoga" :path: "../../node_modules/react-native/ReactCommon/yoga"
SPEC CHECKSUMS: SPEC CHECKSUMS:
boost-for-react-native: 39c7adb57c4e60d6c5479dd8623128eb5b3f0f2c boost-for-react-native: 39c7adb57c4e60d6c5479dd8623128eb5b3f0f2c
DoubleConversion: 5805e889d232975c086db112ece9ed034df7a0b2 DoubleConversion: 5805e889d232975c086db112ece9ed034df7a0b2
EXAppLoaderProvider: ebdb6bc2632c1ccadbe49f5e4104d8d690969c49 EXBlur: aa14d84bff6e9c2232fbcaf54ad809eee1cc41dc
EXBlur: d1604f66f89a9414f5ee65dfb23874437c1bb147 EXConstants: 5304709b1bea70a4828f48ba4c7fc3ec3b2d9b17
EXConstants: 4051b16c17ef3defa03c541d42811dc92b249146 EXErrorRecovery: 8f4c21ab2f51bf75defe4536f841a37de59b0661
EXErrorRecovery: d36db99ec6a3808f313f01b0890eb443796dd1c2 EXFileSystem: cf4232ba7c62dc49b78c2d36005f97b6fddf0b01
EXFileSystem: 6e0d9bb6cc4ea404dbb8f583c1a8a2dcdf4b83b6 EXFont: 8326ecf966be559f7ced7c8e221a32fc4d9ed8b0
EXFont: 6187b5ab46ee578d5f8e7f2ea092752e78772235 EXImageLoader: 5ad6896fa1ef2ee814b551873cbf7a7baccc694a
EXKeepAwake: 66e9f80b6d129633725a0e42f8d285c229876811 EXKeepAwake: d045bc2cf1ad5a04f0323cc7c894b95b414042e0
EXLinearGradient: 75f302f9d6484267a3f6d3252df2e7a5f00e716a EXLinearGradient: 97d8095d1e4ad96f7893e010e564796ed8aeea42
EXLocation: 3c75d012ca92eed94d4338778d79c49d1252393a EXLocation: bbd487fd96a18a3ad9725389bbb94c4a5f78edf3
EXPermissions: 9bc08859a675d291e89be9a0870155c27c16ac35 EXPermissions: 24b97f734ce9172d245a5be38ad9ccfcb6135964
EXSQLite: 220226a354912b100dfe913f5fe6f31762c8927e EXSQLite: 877ad6c8eb169353a2f94d5ad26510ffadd46a1f
EXWebBrowser: db32607359fb7b55b7b7b91df32dd3d8355bb3b7 EXWebBrowser: 5902f99ac5ac551e5c82ff46f13a337b323aa9ea
FBLazyVector: aaeaf388755e4f29cd74acbc9e3b8da6d807c37f FBLazyVector: aaeaf388755e4f29cd74acbc9e3b8da6d807c37f
FBReactNativeSpec: 118d0d177724c2d67f08a59136eb29ef5943ec75 FBReactNativeSpec: 118d0d177724c2d67f08a59136eb29ef5943ec75
Folly: 30e7936e1c45c08d884aa59369ed951a8e68cf51 Folly: 30e7936e1c45c08d884aa59369ed951a8e68cf51
glog: 1f3da668190260b06b429bb211bfbee5cd790c28 glog: 1f3da668190260b06b429bb211bfbee5cd790c28
RCTRequired: b153add4da6e7dbc44aebf93f3cf4fcae392ddf1 RCTRequired: b153add4da6e7dbc44aebf93f3cf4fcae392ddf1
RCTRestart: dd19aab87fc1118e05b6b5b91b959105647f56b4
RCTTypeSafety: 9aa1b91d7f9310fc6eadc3cf95126ffe818af320 RCTTypeSafety: 9aa1b91d7f9310fc6eadc3cf95126ffe818af320
React: b6a59ef847b2b40bb6e0180a97d0ca716969ac78 React: b6a59ef847b2b40bb6e0180a97d0ca716969ac78
React-Core: 688b451f7d616cc1134ac95295b593d1b5158a04 React-Core: 688b451f7d616cc1134ac95295b593d1b5158a04
@@ -505,7 +488,8 @@ SPEC CHECKSUMS:
React-jsi: cb2cd74d7ccf4cffb071a46833613edc79cdf8f7 React-jsi: cb2cd74d7ccf4cffb071a46833613edc79cdf8f7
React-jsiexecutor: d5525f9ed5f782fdbacb64b9b01a43a9323d2386 React-jsiexecutor: d5525f9ed5f782fdbacb64b9b01a43a9323d2386
React-jsinspector: fa0ecc501688c3c4c34f28834a76302233e29dc0 React-jsinspector: fa0ecc501688c3c4c34f28834a76302233e29dc0
react-native-safe-area-context: 25260c5d0b9c53fd7aa88e569e2edae72af1f6a3 react-native-restart: fff228304625f55de2ebd4de43938110f4c888ed
react-native-safe-area-context: a346c75f2288147527365ce27b59ca6d38c27805
React-RCTActionSheet: 600b4d10e3aea0913b5a92256d2719c0cdd26d76 React-RCTActionSheet: 600b4d10e3aea0913b5a92256d2719c0cdd26d76
React-RCTAnimation: 791a87558389c80908ed06cc5dfc5e7920dfa360 React-RCTAnimation: 791a87558389c80908ed06cc5dfc5e7920dfa360
React-RCTBlob: d89293cc0236d9cb0933d85e430b0bbe81ad1d72 React-RCTBlob: d89293cc0236d9cb0933d85e430b0bbe81ad1d72
@@ -516,24 +500,25 @@ SPEC CHECKSUMS:
React-RCTText: 9ccc88273e9a3aacff5094d2175a605efa854dbe React-RCTText: 9ccc88273e9a3aacff5094d2175a605efa854dbe
React-RCTVibration: a49a1f42bf8f5acf1c3e297097517c6b3af377ad React-RCTVibration: a49a1f42bf8f5acf1c3e297097517c6b3af377ad
ReactCommon: 198c7c8d3591f975e5431bec1b0b3b581aa1c5dd ReactCommon: 198c7c8d3591f975e5431bec1b0b3b581aa1c5dd
RNCMaskedView: dd13f9f7b146a9ad82f9b7eb6c9b5548fcf6e990 RNCMaskedView: 5a8ec07677aa885546a0d98da336457e2bea557f
RNGestureHandler: d2270608171c868581b840cfc692f2962c05cd17 RNGestureHandler: 8f09cd560f8d533eb36da5a6c5a843af9f056b38
RNReanimated: b2ab0b693dddd2339bd2f300e770f6302d2e960c RNReanimated: 955cf4068714003d2f1a6e2bae3fb1118f359aff
RNScreens: 1c7fd499b915c77c21e8e6c327890c5af9b4cf7e RNScreens: cf198f915f8a2bf163de94ca9f5bfc8d326c3706
UMBarCodeScannerInterface: 3802c8574ef119c150701d679ab386e2266d6a54 UMAppLoader: ee77a072f9e15128f777ccd6d2d00f52ab4387e6
UMCameraInterface: 985d301f688ed392f815728f0dd906ca34b7ccb1 UMBarCodeScannerInterface: 9dc692b87e5f20fe277fa57aa47f45d418c3cc6c
UMConstantsInterface: bda5f8bd3403ad99e663eb3c4da685d063c5653c UMCameraInterface: 625878bbf2ba188a8548675e1d1d2e438a653e6d
UMCore: 7ab08669a8bb2e61f557c1fe9784521cb5aa28e3 UMConstantsInterface: 64060cf86587bcd90b1dbd804cceb6d377a308c1
UMFaceDetectorInterface: ce14e8e597f6a52aa66e4ab956cb5bff4fa8acf8 UMCore: eb200e882eadafcd31ead290770835fd648c0945
UMFileSystemInterface: 2ed004c9620f43f0b36b33c42ce668500850d6a4 UMFaceDetectorInterface: d6677d6ddc9ab95a0ca857aa7f8ba76656cc770f
UMFontInterface: 24fbc0a02ade6c60ad3ee3e2b5d597c8dcfc3208 UMFileSystemInterface: c70ea7147198b9807080f3597f26236be49b0165
UMImageLoaderInterface: 3976a14c588341228881ff75970fbabf122efca4 UMFontInterface: d9d3b27af698c5389ae9e20b99ef56a083f491fb
UMPermissionsInterface: 2abf9f7f4aa7110e27beaf634a7deda2d50ff3d7 UMImageLoaderInterface: 14dd2c46c67167491effc9e91250e9510f12709e
UMReactNativeAdapter: 230406e3335a8dbd4c9c0e654488a1cf3b44552f UMPermissionsInterface: 5e83a9167c177e4a0f0a3539345983cc749efb3e
UMSensorsInterface: d708a892ef1500bdd9fc3ff03f7836c66d1634d3 UMReactNativeAdapter: 126da3486c1a1f11945b649d557d6c2ebb9407b2
UMTaskManagerInterface: a98e37a576a5220bf43b8faf33cfdc129d2f441d UMSensorsInterface: 48941f70175e2975af1a9386c6d6cb16d8126805
UMTaskManagerInterface: cb890c79c63885504ddc0efd7a7d01481760aca2
Yoga: f2a7cd4280bfe2cca5a7aed98ba0eb3d1310f18b Yoga: f2a7cd4280bfe2cca5a7aed98ba0eb3d1310f18b
PODFILE CHECKSUM: c48a21ff513d3eadafa50f8797207ef2be75e234 PODFILE CHECKSUM: c48a21ff513d3eadafa50f8797207ef2be75e234
COCOAPODS: 1.8.4 COCOAPODS: 1.9.1

View File

@@ -8,26 +8,25 @@ const blacklist = require('metro-config/src/defaults/blacklist');
const root = path.resolve(__dirname, '..'); const root = path.resolve(__dirname, '..');
const packages = path.resolve(root, 'packages'); const packages = path.resolve(root, 'packages');
const workspaces = fs
// List all packages under `packages/`
.readdirSync(packages)
// Ignore hidden files such as .DS_Store
.filter((p) => !p.startsWith('.'))
.map((p) => path.join(packages, p));
// Get the list of dependencies for all packages in the monorepo // Get the list of dependencies for all packages in the monorepo
const modules = ['@expo/vector-icons'] const modules = ['@expo/vector-icons']
.concat( .concat(
...fs ...workspaces.map((it) => {
// List all packages under `packages/` const pak = JSON.parse(
.readdirSync(packages) fs.readFileSync(path.join(it, 'package.json'), 'utf8')
// Ignore hidden files such as .DS_Store );
.filter((p) => !p.startsWith('.'))
.map((p) => {
const pak = JSON.parse(
fs.readFileSync(path.join(packages, p, 'package.json'), 'utf8')
);
// We need to collect list of deps that this package imports // We need to make sure that only one version is loaded for peerDependencies
// Collecting both dependencies are peerDependencies should do it // So we blacklist them at the root, and alias them to the versions in example's node_modules
return Object.keys({ return pak.peerDependencies ? Object.keys(pak.peerDependencies) : [];
...pak.dependencies, })
...pak.peerDependencies,
});
})
) )
.sort() .sort()
.filter( .filter(
@@ -45,15 +44,16 @@ module.exports = {
watchFolders: [root], watchFolders: [root],
resolver: { resolver: {
// We need to blacklist `node_modules` of all our packages // We need to blacklist the peerDependencies we've collected in packages' node_modules
// This will avoid Metro throwing duplicate module errors
blacklistRE: blacklist( blacklistRE: blacklist(
fs [].concat(
.readdirSync(packages) ...workspaces.map((it) =>
.map((p) => path.join(packages, p)) modules.map(
.map( (m) =>
(it) => new RegExp(`^${escape(path.join(it, 'node_modules'))}\\/.*$`) new RegExp(`^${escape(path.join(it, 'node_modules', m))}\\/.*$`)
)
) )
)
), ),
// When we import a package from the monorepo, metro won't be able to find their deps // When we import a package from the monorepo, metro won't be able to find their deps

View File

@@ -1,7 +1,7 @@
{ {
"name": "@react-navigation/example", "name": "@react-navigation/example",
"description": "Demo app to showcase various functionality of React Navigation", "description": "Demo app to showcase various functionality of React Navigation",
"version": "5.0.0", "version": "5.1.0",
"private": true, "private": true,
"scripts": { "scripts": {
"start": "expo start", "start": "expo start",
@@ -12,33 +12,33 @@
"test": "jest" "test": "jest"
}, },
"dependencies": { "dependencies": {
"@expo/vector-icons": "^10.0.0", "@expo/vector-icons": "^10.2.0",
"@react-native-community/masked-view": "^0.1.7", "@react-native-community/masked-view": "^0.1.10",
"color": "^3.1.2", "color": "^3.1.2",
"expo": "^37.0.0", "expo": "^37.0.8",
"expo-asset": "~8.1.3", "expo-asset": "~8.1.3",
"expo-blur": "~8.1.0", "expo-blur": "~8.1.0",
"react": "~16.9.0", "react": "~16.9.0",
"react-dom": "~16.9.0", "react-dom": "~16.9.0",
"react-native": "~0.61.5", "react-native": "~0.61.5",
"react-native-gesture-handler": "^1.6.0", "react-native-gesture-handler": "^1.6.0",
"react-native-paper": "^3.7.0", "react-native-paper": "^3.10.1",
"react-native-reanimated": "^1.7.0", "react-native-reanimated": "^1.8.0",
"react-native-restart": "^0.0.14", "react-native-restart": "^0.0.15",
"react-native-safe-area-context": "^0.7.3", "react-native-safe-area-context": "^1.0.0",
"react-native-screens": "^2.3.0", "react-native-screens": "^2.7.0",
"react-native-tab-view": "2.14.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" "react-native-web": "^0.11.7"
}, },
"devDependencies": { "devDependencies": {
"@expo/webpack-config": "^0.11.19", "@expo/webpack-config": "^0.11.19",
"@types/jest-dev-server": "^4.2.0", "@types/jest-dev-server": "^4.2.0",
"@types/react": "^16.9.23", "@types/react": "^16.9.34",
"@types/react-native": "^0.60.22", "@types/react-native": "^0.62.7",
"babel-preset-expo": "^8.1.0", "babel-preset-expo": "^8.1.0",
"expo-cli": "^3.17.18", "expo-cli": "^3.20.1",
"jest": "^25.2.7", "jest": "^26.0.1",
"jest-dev-server": "^4.4.0", "jest-dev-server": "^4.4.0",
"playwright": "^0.14.0", "playwright": "^0.14.0",
"serve": "^11.3.0", "serve": "^11.3.0",

View File

@@ -1,7 +1,11 @@
import * as React from 'react'; import * as React from 'react';
import { Platform } from 'react-native'; import { View, ScrollView, StyleSheet, Platform } from 'react-native';
import { Button } from 'react-native-paper';
import { MaterialCommunityIcons } from '@expo/vector-icons'; import { MaterialCommunityIcons } from '@expo/vector-icons';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; import {
createBottomTabNavigator,
BottomTabNavigationProp,
} from '@react-navigation/bottom-tabs';
import TouchableBounce from '../Shared/TouchableBounce'; import TouchableBounce from '../Shared/TouchableBounce';
import Albums from '../Shared/Albums'; import Albums from '../Shared/Albums';
import Contacts from '../Shared/Contacts'; import Contacts from '../Shared/Contacts';
@@ -23,6 +27,36 @@ type BottomTabParams = {
Chat: undefined; Chat: undefined;
}; };
const scrollEnabled = Platform.select({ web: true, default: false });
const AlbumsScreen = ({
navigation,
}: {
navigation: BottomTabNavigationProp<BottomTabParams>;
}) => {
return (
<ScrollView>
<View style={styles.buttons}>
<Button
mode="outlined"
onPress={() => navigation.setOptions({ tabBarVisible: false })}
style={styles.button}
>
Hide tab bar
</Button>
<Button
mode="outlined"
onPress={() => navigation.setOptions({ tabBarVisible: true })}
style={styles.button}
>
Show tab bar
</Button>
</View>
<Albums scrollEnabled={scrollEnabled} />
</ScrollView>
);
};
const BottomTabs = createBottomTabNavigator<BottomTabParams>(); const BottomTabs = createBottomTabNavigator<BottomTabParams>();
export default function BottomTabsScreen() { export default function BottomTabsScreen() {
@@ -62,7 +96,7 @@ export default function BottomTabsScreen() {
/> />
<BottomTabs.Screen <BottomTabs.Screen
name="Albums" name="Albums"
component={Albums} component={AlbumsScreen}
options={{ options={{
title: 'Albums', title: 'Albums',
tabBarIcon: getTabBarIcon('image-album'), tabBarIcon: getTabBarIcon('image-album'),
@@ -71,3 +105,13 @@ export default function BottomTabsScreen() {
</BottomTabs.Navigator> </BottomTabs.Navigator>
); );
} }
const styles = StyleSheet.create({
buttons: {
flexDirection: 'row',
padding: 8,
},
button: {
margin: 8,
},
});

View File

@@ -17,7 +17,7 @@ import Albums from '../Shared/Albums';
type SimpleStackParams = { type SimpleStackParams = {
Article: { author: string }; Article: { author: string };
Album: undefined; Albums: undefined;
}; };
type SimpleStackNavigation = StackNavigationProp<SimpleStackParams>; type SimpleStackNavigation = StackNavigationProp<SimpleStackParams>;
@@ -53,24 +53,24 @@ const ArticleScreen = ({
<ScrollView> <ScrollView>
<View style={styles.buttons}> <View style={styles.buttons}>
<Link <Link
to="/link-component/Album" to="/link-component/music"
style={[styles.button, { padding: 8 }]} style={[styles.button, { padding: 8 }]}
> >
Go to /link-component/Album Go to /link-component/music
</Link> </Link>
<Link <Link
to="/link-component/Album" to="/link-component/music"
action={StackActions.replace('Album')} action={StackActions.replace('Albums')}
style={[styles.button, { padding: 8 }]} style={[styles.button, { padding: 8 }]}
> >
Replace with /link-component/Album Replace with /link-component/music
</Link> </Link>
<LinkButton <LinkButton
to="/link-component/Album" to="/link-component/music"
mode="contained" mode="contained"
style={styles.button} style={styles.button}
> >
Go to /link-component/Album Go to /link-component/music
</LinkButton> </LinkButton>
<Button <Button
mode="outlined" mode="outlined"
@@ -97,17 +97,17 @@ const AlbumsScreen = ({
<ScrollView> <ScrollView>
<View style={styles.buttons}> <View style={styles.buttons}>
<Link <Link
to="/link-component/Article?author=Babel" to="/link-component/article/babel"
style={[styles.button, { padding: 8 }]} style={[styles.button, { padding: 8 }]}
> >
Go to /link-component/Article Go to /link-component/article
</Link> </Link>
<LinkButton <LinkButton
to="/link-component/Article?author=Babel" to="/link-component/article/babel"
mode="contained" mode="contained"
style={styles.button} style={styles.button}
> >
Go to /link-component/Article Go to /link-component/article
</LinkButton> </LinkButton>
<Button <Button
mode="outlined" mode="outlined"
@@ -144,9 +144,9 @@ export default function SimpleStackScreen({ navigation, ...rest }: Props) {
initialParams={{ author: 'Gandalf' }} initialParams={{ author: 'Gandalf' }}
/> />
<SimpleStack.Screen <SimpleStack.Screen
name="Album" name="Albums"
component={AlbumsScreen} component={AlbumsScreen}
options={{ title: 'Album' }} options={{ title: 'Albums' }}
/> />
</SimpleStack.Navigator> </SimpleStack.Navigator>
); );

View File

@@ -1,12 +1,18 @@
import * as React from 'react'; import * as React from 'react';
import { Dimensions, ScaledSize } from 'react-native'; import { Dimensions, ScaledSize } from 'react-native';
import { Appbar } from 'react-native-paper'; import { Appbar } from 'react-native-paper';
import { ParamListBase } from '@react-navigation/native'; import {
useTheme,
useNavigation,
ParamListBase,
} from '@react-navigation/native';
import { StackNavigationProp } from '@react-navigation/stack'; import { StackNavigationProp } from '@react-navigation/stack';
import { import {
createDrawerNavigator, createDrawerNavigator,
DrawerNavigationProp, DrawerNavigationProp,
DrawerContent, DrawerContent,
DrawerContentComponentProps,
DrawerContentOptions,
} from '@react-navigation/drawer'; } from '@react-navigation/drawer';
import Article from '../Shared/Article'; import Article from '../Shared/Article';
import Albums from '../Shared/Albums'; import Albums from '../Shared/Albums';
@@ -15,7 +21,7 @@ import NewsFeed from '../Shared/NewsFeed';
type DrawerParams = { type DrawerParams = {
Article: undefined; Article: undefined;
NewsFeed: undefined; NewsFeed: undefined;
Album: undefined; Albums: undefined;
}; };
type DrawerNavigation = DrawerNavigationProp<DrawerParams>; type DrawerNavigation = DrawerNavigationProp<DrawerParams>;
@@ -43,10 +49,11 @@ const Header = ({
onGoBack: () => void; onGoBack: () => void;
title: string; title: string;
}) => { }) => {
const { colors } = useTheme();
const isLargeScreen = useIsLargeScreen(); const isLargeScreen = useIsLargeScreen();
return ( return (
<Appbar.Header> <Appbar.Header style={{ backgroundColor: colors.card, elevation: 1 }}>
{isLargeScreen ? null : <Appbar.BackAction onPress={onGoBack} />} {isLargeScreen ? null : <Appbar.BackAction onPress={onGoBack} />}
<Appbar.Content title={title} /> <Appbar.Content title={title} />
</Appbar.Header> </Appbar.Header>
@@ -80,6 +87,23 @@ const AlbumsScreen = ({ navigation }: { navigation: DrawerNavigation }) => {
); );
}; };
const CustomDrawerContent = (
props: DrawerContentComponentProps<DrawerContentOptions>
) => {
const { colors } = useTheme();
const navigation = useNavigation();
return (
<>
<Appbar.Header style={{ backgroundColor: colors.card, elevation: 1 }}>
<Appbar.Action icon="close" onPress={() => navigation.goBack()} />
<Appbar.Content title="Pages" />
</Appbar.Header>
<DrawerContent {...props} />
</>
);
};
const Drawer = createDrawerNavigator<DrawerParams>(); const Drawer = createDrawerNavigator<DrawerParams>();
type Props = Partial<React.ComponentProps<typeof Drawer.Navigator>> & { type Props = Partial<React.ComponentProps<typeof Drawer.Navigator>> & {
@@ -100,15 +124,7 @@ export default function DrawerScreen({ navigation, ...rest }: Props) {
drawerType={isLargeScreen ? 'permanent' : 'back'} drawerType={isLargeScreen ? 'permanent' : 'back'}
drawerStyle={isLargeScreen ? null : { width: '100%' }} drawerStyle={isLargeScreen ? null : { width: '100%' }}
overlayColor="transparent" overlayColor="transparent"
drawerContent={(props) => ( drawerContent={(props) => <CustomDrawerContent {...props} />}
<>
<Appbar.Header>
<Appbar.Action icon="close" onPress={() => navigation.goBack()} />
<Appbar.Content title="Pages" />
</Appbar.Header>
<DrawerContent {...props} />
</>
)}
{...rest} {...rest}
> >
<Drawer.Screen name="Article" component={ArticleScreen} /> <Drawer.Screen name="Article" component={ArticleScreen} />
@@ -118,9 +134,9 @@ export default function DrawerScreen({ navigation, ...rest }: Props) {
options={{ title: 'Feed' }} options={{ title: 'Feed' }}
/> />
<Drawer.Screen <Drawer.Screen
name="Album" name="Albums"
component={AlbumsScreen} component={AlbumsScreen}
options={{ title: 'Album' }} options={{ title: 'Albums' }}
/> />
</Drawer.Navigator> </Drawer.Navigator>
); );

View File

@@ -1,4 +1,6 @@
import * as React from 'react'; import * as React from 'react';
import { ParamListBase } from '@react-navigation/native';
import { StackNavigationProp } from '@react-navigation/stack';
import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs'; import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs';
import Albums from '../Shared/Albums'; import Albums from '../Shared/Albums';
import Contacts from '../Shared/Contacts'; import Contacts from '../Shared/Contacts';
@@ -12,7 +14,15 @@ type MaterialTopTabParams = {
const MaterialTopTabs = createMaterialTopTabNavigator<MaterialTopTabParams>(); const MaterialTopTabs = createMaterialTopTabNavigator<MaterialTopTabParams>();
export default function MaterialTopTabsScreen() { type Props = {
navigation: StackNavigationProp<ParamListBase>;
};
export default function MaterialTopTabsScreen({ navigation }: Props) {
navigation.setOptions({
cardStyle: { flex: 1 },
});
return ( return (
<MaterialTopTabs.Navigator> <MaterialTopTabs.Navigator>
<MaterialTopTabs.Screen <MaterialTopTabs.Screen

View File

@@ -12,7 +12,7 @@ import Albums from '../Shared/Albums';
type ModalStackParams = { type ModalStackParams = {
Article: { author: string }; Article: { author: string };
Album: undefined; Albums: undefined;
}; };
type ModalStackNavigation = StackNavigationProp<ModalStackParams>; type ModalStackNavigation = StackNavigationProp<ModalStackParams>;
@@ -31,7 +31,7 @@ const ArticleScreen = ({
<View style={styles.buttons}> <View style={styles.buttons}>
<Button <Button
mode="contained" mode="contained"
onPress={() => navigation.push('Album')} onPress={() => navigation.push('Albums')}
style={styles.button} style={styles.button}
> >
Push album Push album
@@ -112,9 +112,9 @@ export default function SimpleStackScreen({ navigation, options }: Props) {
initialParams={{ author: 'Gandalf' }} initialParams={{ author: 'Gandalf' }}
/> />
<ModalPresentationStack.Screen <ModalPresentationStack.Screen
name="Album" name="Albums"
component={AlbumsScreen} component={AlbumsScreen}
options={{ title: 'Album' }} options={{ title: 'Albums' }}
/> />
</ModalPresentationStack.Navigator> </ModalPresentationStack.Navigator>
); );

View File

@@ -13,7 +13,7 @@ import NewsFeed from '../Shared/NewsFeed';
type SimpleStackParams = { type SimpleStackParams = {
Article: { author: string }; Article: { author: string };
NewsFeed: undefined; NewsFeed: undefined;
Album: undefined; Albums: undefined;
}; };
type SimpleStackNavigation = StackNavigationProp<SimpleStackParams>; type SimpleStackNavigation = StackNavigationProp<SimpleStackParams>;
@@ -63,7 +63,7 @@ const NewsFeedScreen = ({
<View style={styles.buttons}> <View style={styles.buttons}>
<Button <Button
mode="contained" mode="contained"
onPress={() => navigation.navigate('Album')} onPress={() => navigation.navigate('Albums')}
style={styles.button} style={styles.button}
> >
Navigate to album Navigate to album
@@ -136,9 +136,9 @@ export default function SimpleStackScreen({ navigation, ...rest }: Props) {
options={{ title: 'Feed' }} options={{ title: 'Feed' }}
/> />
<SimpleStack.Screen <SimpleStack.Screen
name="Album" name="Albums"
component={AlbumsScreen} component={AlbumsScreen}
options={{ title: 'Album' }} options={{ title: 'Albums' }}
/> />
</SimpleStack.Navigator> </SimpleStack.Navigator>
); );

View File

@@ -15,7 +15,7 @@ import Albums from '../Shared/Albums';
type SimpleStackParams = { type SimpleStackParams = {
Article: { author: string }; Article: { author: string };
Album: undefined; Albums: undefined;
}; };
type SimpleStackNavigation = StackNavigationProp<SimpleStackParams>; type SimpleStackNavigation = StackNavigationProp<SimpleStackParams>;
@@ -34,7 +34,7 @@ const ArticleScreen = ({
<View style={styles.buttons}> <View style={styles.buttons}>
<Button <Button
mode="contained" mode="contained"
onPress={() => navigation.push('Album')} onPress={() => navigation.push('Albums')}
style={styles.button} style={styles.button}
> >
Push album Push album
@@ -131,10 +131,10 @@ export default function SimpleStackScreen({ navigation, ...rest }: Props) {
initialParams={{ author: 'Gandalf' }} initialParams={{ author: 'Gandalf' }}
/> />
<SimpleStack.Screen <SimpleStack.Screen
name="Album" name="Albums"
component={AlbumsScreen} component={AlbumsScreen}
options={{ options={{
title: 'Album', title: 'Albums',
headerBackTitle: 'Back', headerBackTitle: 'Back',
headerTransparent: true, headerTransparent: true,
headerBackground: () => ( headerBackground: () => (

View File

@@ -7,6 +7,7 @@ import {
I18nManager, I18nManager,
Dimensions, Dimensions,
ScaledSize, ScaledSize,
Linking,
} from 'react-native'; } from 'react-native';
// eslint-disable-next-line import/no-unresolved // eslint-disable-next-line import/no-unresolved
import { enableScreens } from 'react-native-screens'; import { enableScreens } from 'react-native-screens';
@@ -138,18 +139,18 @@ export default function App() {
React.useEffect(() => { React.useEffect(() => {
const restoreState = async () => { const restoreState = async () => {
try { try {
let state; const initialUrl = await Linking.getInitialURL();
if (Platform.OS !== 'web' && state === undefined) { if (Platform.OS !== 'web' || initialUrl === null) {
const savedState = await AsyncStorage.getItem( const savedState = await AsyncStorage.getItem(
NAVIGATION_PERSISTENCE_KEY NAVIGATION_PERSISTENCE_KEY
); );
state = savedState ? JSON.parse(savedState) : undefined; const state = savedState ? JSON.parse(savedState) : undefined;
}
if (state !== undefined) { if (state !== undefined) {
setInitialState(state); setInitialState(state);
}
} }
} finally { } finally {
try { try {
@@ -238,6 +239,22 @@ export default function App() {
{ Home: '' } { Home: '' }
), ),
}, },
Article: {
path: 'article/:author?',
parse: {
author: (author) =>
author.charAt(0).toUpperCase() +
author.slice(1).replace(/-/g, ' '),
},
stringify: {
author: (author: string) =>
author.toLowerCase().replace(/\s/g, '-'),
},
},
Albums: 'music',
Chat: 'chat',
Contacts: 'people',
NewsFeed: 'feed',
}, },
}} }}
fallback={<Text>Loading</Text>} fallback={<Text>Loading</Text>}

View File

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

View File

@@ -13,7 +13,7 @@
"license": "MIT", "license": "MIT",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/satya164/react-navigation.git" "url": "git+https://github.com/react-navigation/react-navigation.git"
}, },
"author": "Satyajit Sahoo <satyajit.happy@gmail.com> (https://github.com/satya164/), Michał Osadnik <micosa97@gmail.com> (https://github.com/osdnk/)", "author": "Satyajit Sahoo <satyajit.happy@gmail.com> (https://github.com/satya164/), Michał Osadnik <micosa97@gmail.com> (https://github.com/osdnk/)",
"scripts": { "scripts": {
@@ -27,23 +27,23 @@
"devDependencies": { "devDependencies": {
"@babel/plugin-proposal-class-properties": "^7.8.3", "@babel/plugin-proposal-class-properties": "^7.8.3",
"@babel/plugin-proposal-optional-chaining": "^7.9.0", "@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-flow": "^7.9.0",
"@babel/preset-react": "^7.9.4", "@babel/preset-react": "^7.9.4",
"@babel/preset-typescript": "^7.9.0", "@babel/preset-typescript": "^7.9.0",
"@babel/runtime": "^7.9.2", "@babel/runtime": "^7.9.6",
"@commitlint/config-conventional": "^8.3.4", "@commitlint/config-conventional": "^8.3.4",
"@types/jest": "^25.2.1", "@types/jest": "^25.2.1",
"babel-jest": "^25.2.6", "babel-jest": "^26.0.1",
"codecov": "^3.6.5", "codecov": "^3.6.5",
"commitlint": "^8.3.5", "commitlint": "^8.3.5",
"core-js": "^3.6.4", "core-js": "^3.6.5",
"eslint": "^6.8.0", "eslint": "^6.8.0",
"eslint-config-satya164": "^3.1.6", "eslint-config-satya164": "^3.1.7",
"husky": "^4.2.3", "husky": "^4.2.5",
"jest": "^25.2.7", "jest": "^26.0.1",
"lerna": "^3.20.2", "lerna": "^3.20.2",
"prettier": "^2.0.4", "prettier": "^2.0.5",
"typescript": "^3.8.3" "typescript": "^3.8.3"
}, },
"resolutions": { "resolutions": {

View File

@@ -3,6 +3,96 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [5.5.0](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.4.7...@react-navigation/bottom-tabs@5.5.0) (2020-05-23)
### Features
* animate changes to tabBarVisible in BottomTabBar ([#8286](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/issues/8286)) ([c1e46f8](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/commit/c1e46f8e331e0054995aa476455af204d02d4170))
* update react-native-safe-area-context to 1.0.0 ([#8182](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/issues/8182)) ([d62fbfe](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/commit/d62fbfe255140f16b182e8b54b276a7c96f2aec6))
## [5.4.7](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.4.6...@react-navigation/bottom-tabs@5.4.7) (2020-05-20)
**Note:** Version bump only for package @react-navigation/bottom-tabs
## [5.4.6](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.4.5...@react-navigation/bottom-tabs@5.4.6) (2020-05-20)
**Note:** Version bump only for package @react-navigation/bottom-tabs
## [5.4.5](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.4.4...@react-navigation/bottom-tabs@5.4.5) (2020-05-16)
**Note:** Version bump only for package @react-navigation/bottom-tabs
## [5.4.4](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.4.3...@react-navigation/bottom-tabs@5.4.4) (2020-05-14)
**Note:** Version bump only for package @react-navigation/bottom-tabs
## [5.4.3](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.4.2...@react-navigation/bottom-tabs@5.4.3) (2020-05-14)
**Note:** Version bump only for package @react-navigation/bottom-tabs
## [5.4.2](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.4.1...@react-navigation/bottom-tabs@5.4.2) (2020-05-10)
**Note:** Version bump only for package @react-navigation/bottom-tabs
## [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) ## [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 **Note:** Version bump only for package @react-navigation/bottom-tabs

View File

@@ -1,7 +1,7 @@
{ {
"name": "@react-navigation/bottom-tabs", "name": "@react-navigation/bottom-tabs",
"description": "Bottom tab navigator following iOS design guidelines", "description": "Bottom tab navigator following iOS design guidelines",
"version": "5.3.3", "version": "5.5.0",
"keywords": [ "keywords": [
"react-native-component", "react-native-component",
"react-component", "react-component",
@@ -15,11 +15,13 @@
"repository": "https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs", "repository": "https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs",
"main": "lib/commonjs/index.js", "main": "lib/commonjs/index.js",
"react-native": "src/index.tsx", "react-native": "src/index.tsx",
"source": "src/index.tsx",
"module": "lib/module/index.js", "module": "lib/module/index.js",
"types": "lib/typescript/src/index.d.ts", "types": "lib/typescript/src/index.d.ts",
"files": [ "files": [
"src", "src",
"lib" "lib",
"!**/__tests__"
], ],
"sideEffects": false, "sideEffects": false,
"publishConfig": { "publishConfig": {
@@ -34,16 +36,16 @@
"react-native-iphone-x-helper": "^1.2.1" "react-native-iphone-x-helper": "^1.2.1"
}, },
"devDependencies": { "devDependencies": {
"@react-native-community/bob": "^0.10.0", "@react-native-community/bob": "^0.14.3",
"@react-navigation/native": "^5.2.3", "@react-navigation/native": "^5.4.3",
"@types/color": "^3.0.1", "@types/color": "^3.0.1",
"@types/react": "^16.9.23", "@types/react": "^16.9.34",
"@types/react-native": "^0.61.22", "@types/react-native": "^0.62.7",
"del-cli": "^3.0.0", "del-cli": "^3.0.0",
"react": "~16.9.0", "react": "~16.9.0",
"react-native": "~0.61.5", "react-native": "~0.61.5",
"react-native-safe-area-context": "^0.7.3", "react-native-safe-area-context": "^1.0.0",
"react-native-screens": "^2.3.0", "react-native-screens": "^2.7.0",
"typescript": "^3.8.3" "typescript": "^3.8.3"
}, },
"peerDependencies": { "peerDependencies": {

View File

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

View File

@@ -13,6 +13,7 @@ import {
Descriptor, Descriptor,
TabNavigationState, TabNavigationState,
TabActionHelpers, TabActionHelpers,
RouteProp,
} from '@react-navigation/native'; } from '@react-navigation/native';
export type BottomTabNavigationEventMap = { export type BottomTabNavigationEventMap = {
@@ -45,6 +46,14 @@ export type BottomTabNavigationProp<
> & > &
TabActionHelpers<ParamList>; TabActionHelpers<ParamList>;
export type BottomTabScreenProps<
ParamList extends ParamListBase,
RouteName extends keyof ParamList = string
> = {
navigation: BottomTabNavigationProp<ParamList, RouteName>;
route: RouteProp<ParamList, RouteName>;
};
export type BottomTabNavigationOptions = { export type BottomTabNavigationOptions = {
/** /**
* Title text for the screen. * Title text for the screen.

View File

@@ -53,52 +53,60 @@ export default function BottomTabBar({
const { colors } = useTheme(); const { colors } = useTheme();
const buildLink = useLinkBuilder(); const buildLink = useLinkBuilder();
const [dimensions, setDimensions] = React.useState(() => { const focusedRoute = state.routes[state.index];
const { height = 0, width = 0 } = Dimensions.get('window'); const focusedDescriptor = descriptors[focusedRoute.key];
const focusedOptions = focusedDescriptor.options;
return { height, width }; const [isKeyboardShown, setIsKeyboardShown] = React.useState(false);
});
const [layout, setLayout] = React.useState({ const shouldShowTabBar =
height: 0, focusedOptions.tabBarVisible !== false &&
width: dimensions.width, !(keyboardHidesTabBar && isKeyboardShown);
});
const [keyboardShown, setKeyboardShown] = React.useState(false);
const [visible] = React.useState(() => new Animated.Value(1)); const [isTabBarHidden, setIsTabBarHidden] = React.useState(!shouldShowTabBar);
const { routes } = state; const [visible] = React.useState(
() => new Animated.Value(shouldShowTabBar ? 1 : 0)
);
React.useEffect(() => { React.useEffect(() => {
if (keyboardShown) { if (shouldShowTabBar) {
Animated.timing(visible, {
toValue: 0,
duration: 200,
useNativeDriver,
}).start();
}
}, [keyboardShown, visible]);
React.useEffect(() => {
const handleOrientationChange = ({ window }: { window: ScaledSize }) => {
setDimensions(window);
};
const handleKeyboardShow = () => setKeyboardShown(true);
const handleKeyboardHide = () =>
Animated.timing(visible, { Animated.timing(visible, {
toValue: 1, toValue: 1,
duration: 250, duration: 250,
useNativeDriver, useNativeDriver,
}).start(({ finished }) => { }).start(({ finished }) => {
if (finished) { if (finished) {
setKeyboardShown(false); setIsTabBarHidden(false);
} }
}); });
} else {
setIsTabBarHidden(true);
Animated.timing(visible, {
toValue: 0,
duration: 200,
useNativeDriver,
}).start();
}
}, [shouldShowTabBar, visible]);
const [dimensions, setDimensions] = React.useState(() => {
const { height = 0, width = 0 } = Dimensions.get('window');
return { height, width };
});
React.useEffect(() => {
const handleOrientationChange = ({ window }: { window: ScaledSize }) => {
setDimensions(window);
};
Dimensions.addEventListener('change', handleOrientationChange); Dimensions.addEventListener('change', handleOrientationChange);
const handleKeyboardShow = () => setIsKeyboardShown(true);
const handleKeyboardHide = () => setIsKeyboardShown(false);
if (Platform.OS === 'ios') { if (Platform.OS === 'ios') {
Keyboard.addListener('keyboardWillShow', handleKeyboardShow); Keyboard.addListener('keyboardWillShow', handleKeyboardShow);
Keyboard.addListener('keyboardWillHide', handleKeyboardHide); Keyboard.addListener('keyboardWillHide', handleKeyboardHide);
@@ -118,7 +126,12 @@ export default function BottomTabBar({
Keyboard.removeListener('keyboardDidHide', handleKeyboardHide); Keyboard.removeListener('keyboardDidHide', handleKeyboardHide);
} }
}; };
}, [visible]); }, []);
const [layout, setLayout] = React.useState({
height: 0,
width: dimensions.width,
});
const handleLayout = (e: LayoutChangeEvent) => { const handleLayout = (e: LayoutChangeEvent) => {
const { height, width } = e.nativeEvent.layout; const { height, width } = e.nativeEvent.layout;
@@ -135,6 +148,7 @@ export default function BottomTabBar({
}); });
}; };
const { routes } = state;
const shouldUseHorizontalLabels = () => { const shouldUseHorizontalLabels = () => {
if (labelPosition) { if (labelPosition) {
return labelPosition === 'beside-icon'; return labelPosition === 'beside-icon';
@@ -183,22 +197,19 @@ export default function BottomTabBar({
backgroundColor: colors.card, backgroundColor: colors.card,
borderTopColor: colors.border, borderTopColor: colors.border,
}, },
keyboardHidesTabBar {
? { transform: [
// When the keyboard is shown, slide down the tab bar {
transform: [ translateY: visible.interpolate({
{ inputRange: [0, 1],
translateY: visible.interpolate({ outputRange: [layout.height + insets.bottom, 0],
inputRange: [0, 1], }),
outputRange: [layout.height, 0], },
}), ],
}, // Absolutely position the tab bar so that the content is below it
], // This is needed to avoid gap at bottom when the tab bar is hidden
// Absolutely position the tab bar so that the content is below it position: isTabBarHidden ? 'absolute' : null,
// This is needed to avoid gap at bottom when the tab bar is hidden },
position: keyboardShown ? 'absolute' : null,
}
: null,
{ {
height: DEFAULT_TABBAR_HEIGHT + insets.bottom, height: DEFAULT_TABBAR_HEIGHT + insets.bottom,
paddingBottom: insets.bottom, paddingBottom: insets.bottom,
@@ -206,7 +217,7 @@ export default function BottomTabBar({
}, },
style, style,
]} ]}
pointerEvents={keyboardHidesTabBar && keyboardShown ? 'none' : 'auto'} pointerEvents={isTabBarHidden ? 'none' : 'auto'}
> >
<View style={styles.content} onLayout={handleLayout}> <View style={styles.content} onLayout={handleLayout}>
{routes.map((route, index) => { {routes.map((route, index) => {

View File

@@ -75,17 +75,8 @@ export default class BottomTabView extends React.Component<Props, State> {
tabBarOptions, tabBarOptions,
state, state,
navigation, navigation,
descriptors,
} = this.props; } = this.props;
const { descriptors } = this.props;
const route = state.routes[state.index];
const descriptor = descriptors[route.key];
const options = descriptor.options;
if (options.tabBarVisible === false) {
return null;
}
return tabBar({ return tabBar({
...tabBarOptions, ...tabBarOptions,
state: state, state: state,

View File

@@ -3,6 +3,89 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [5.1.24](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.23...@react-navigation/compat@5.1.24) (2020-05-23)
**Note:** Version bump only for package @react-navigation/compat
## [5.1.23](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.22...@react-navigation/compat@5.1.23) (2020-05-20)
**Note:** Version bump only for package @react-navigation/compat
## [5.1.22](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.21...@react-navigation/compat@5.1.22) (2020-05-20)
**Note:** Version bump only for package @react-navigation/compat
## [5.1.21](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.20...@react-navigation/compat@5.1.21) (2020-05-16)
**Note:** Version bump only for package @react-navigation/compat
## [5.1.20](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.19...@react-navigation/compat@5.1.20) (2020-05-14)
**Note:** Version bump only for package @react-navigation/compat
## [5.1.19](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.18...@react-navigation/compat@5.1.19) (2020-05-14)
**Note:** Version bump only for package @react-navigation/compat
## [5.1.18](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.17...@react-navigation/compat@5.1.18) (2020-05-10)
**Note:** Version bump only for package @react-navigation/compat
## [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) ## [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 **Note:** Version bump only for package @react-navigation/compat

View File

@@ -1,7 +1,7 @@
{ {
"name": "@react-navigation/compat", "name": "@react-navigation/compat",
"description": "Compatibility layer to write navigator definitions in static configuration format", "description": "Compatibility layer to write navigator definitions in static configuration format",
"version": "5.1.14", "version": "5.1.24",
"license": "MIT", "license": "MIT",
"repository": "https://github.com/react-navigation/react-navigation/tree/master/packages/compat", "repository": "https://github.com/react-navigation/react-navigation/tree/master/packages/compat",
"bugs": { "bugs": {
@@ -10,11 +10,13 @@
"homepage": "https://reactnavigation.org/docs/compatibility.html", "homepage": "https://reactnavigation.org/docs/compatibility.html",
"main": "lib/commonjs/index.js", "main": "lib/commonjs/index.js",
"react-native": "src/index.tsx", "react-native": "src/index.tsx",
"source": "src/index.tsx",
"module": "lib/module/index.js", "module": "lib/module/index.js",
"types": "lib/typescript/src/index.d.ts", "types": "lib/typescript/src/index.d.ts",
"files": [ "files": [
"src", "src",
"lib" "lib",
"!**/__tests__"
], ],
"sideEffects": false, "sideEffects": false,
"publishConfig": { "publishConfig": {
@@ -25,9 +27,9 @@
"clean": "del lib" "clean": "del lib"
}, },
"devDependencies": { "devDependencies": {
"@react-native-community/bob": "^0.10.0", "@react-native-community/bob": "^0.14.3",
"@react-navigation/native": "^5.2.3", "@react-navigation/native": "^5.4.3",
"@types/react": "^16.9.23", "@types/react": "^16.9.34",
"react": "~16.9.0", "react": "~16.9.0",
"typescript": "^3.8.3" "typescript": "^3.8.3"
}, },

View File

@@ -3,6 +3,112 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [5.8.2](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.8.1...@react-navigation/core@5.8.2) (2020-05-23)
**Note:** Version bump only for package @react-navigation/core
## [5.8.1](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.8.0...@react-navigation/core@5.8.1) (2020-05-20)
**Note:** Version bump only for package @react-navigation/core
# [5.8.0](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.7.0...@react-navigation/core@5.8.0) (2020-05-20)
### Features
* add getCurrentOptions ([#8277](https://github.com/react-navigation/react-navigation/tree/master/packages/core/issues/8277)) ([d024ec6](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/d024ec6d74dffe481ce6fde732c729e20c1668f4))
* add getCurrentRoute ([#8254](https://github.com/react-navigation/react-navigation/tree/master/packages/core/issues/8254)) ([7b25c8e](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/7b25c8eb2e6f96128fd86b92615346ce55bedeca))
# [5.7.0](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.6.1...@react-navigation/core@5.7.0) (2020-05-16)
### Bug Fixes
* don't use Object.fromEntries ([51f4d11](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/51f4d11fdf4bd2bb06f8cd4094f051816590e62c))
### Features
* add a PathConfig type ([60cb3c9](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/60cb3c9ba76d7ef166c9fe8b55f23728975b5b6e))
## [5.6.1](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.6.0...@react-navigation/core@5.6.1) (2020-05-14)
### Bug Fixes
* don't use flat since it's not supported in node ([21b397f](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/21b397f0d6b96ec4875d3172f47533130bb08009))
# [5.6.0](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.5.2...@react-navigation/core@5.6.0) (2020-05-14)
### Bug Fixes
* ignore extra slashes in the pattern ([3c47716](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/3c47716826d0dfa69dfa6112141c116723372ea1))
* ignore state updates when we're not mounted ([0149e85](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/0149e85a95b90c6a9d487fa753ddbf5d01c03e3d)), closes [#8226](https://github.com/react-navigation/react-navigation/tree/master/packages/core/issues/8226)
### Features
* merge path patterns for nested screens ([#8253](https://github.com/react-navigation/react-navigation/tree/master/packages/core/issues/8253)) ([acc9646](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/acc9646426fee53558d686dfbe5fd0e35361d8c0))
## [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) # [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)

View File

@@ -1,7 +1,7 @@
{ {
"name": "@react-navigation/core", "name": "@react-navigation/core",
"description": "Core utilities for building navigators", "description": "Core utilities for building navigators",
"version": "5.4.0", "version": "5.8.2",
"keywords": [ "keywords": [
"react", "react",
"react-native", "react-native",
@@ -15,11 +15,13 @@
"homepage": "https://reactnavigation.org", "homepage": "https://reactnavigation.org",
"main": "lib/commonjs/index.js", "main": "lib/commonjs/index.js",
"react-native": "src/index.tsx", "react-native": "src/index.tsx",
"source": "src/index.tsx",
"module": "lib/module/index.js", "module": "lib/module/index.js",
"types": "lib/typescript/src/index.d.ts", "types": "lib/typescript/src/index.d.ts",
"files": [ "files": [
"src", "src",
"lib" "lib",
"!**/__tests__"
], ],
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
@@ -29,21 +31,21 @@
"clean": "del lib" "clean": "del lib"
}, },
"dependencies": { "dependencies": {
"@react-navigation/routers": "^5.4.2", "@react-navigation/routers": "^5.4.7",
"escape-string-regexp": "^2.0.0", "escape-string-regexp": "^4.0.0",
"nanoid": "^3.0.2", "nanoid": "^3.1.5",
"query-string": "^6.12.0", "query-string": "^6.12.1",
"react-is": "^16.13.0", "react-is": "^16.13.0",
"use-subscription": "^1.4.0" "use-subscription": "^1.4.0"
}, },
"devDependencies": { "devDependencies": {
"@react-native-community/bob": "^0.10.0", "@react-native-community/bob": "^0.14.3",
"@types/react": "^16.9.23", "@types/react": "^16.9.34",
"@types/react-is": "^16.7.1", "@types/react-is": "^16.7.1",
"@types/use-subscription": "^1.0.0", "@types/use-subscription": "^1.0.0",
"del-cli": "^3.0.0", "del-cli": "^3.0.0",
"react": "~16.9.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", "react-test-renderer": "~16.13.1",
"typescript": "^3.8.3" "typescript": "^3.8.3"
}, },

View File

@@ -13,49 +13,22 @@ import { ScheduleUpdateContext } from './useScheduleUpdate';
import useFocusedListeners from './useFocusedListeners'; import useFocusedListeners from './useFocusedListeners';
import useDevTools from './useDevTools'; import useDevTools from './useDevTools';
import useStateGetters from './useStateGetters'; import useStateGetters from './useStateGetters';
import useOptionsGetters from './useOptionsGetters';
import useEventEmitter from './useEventEmitter'; import useEventEmitter from './useEventEmitter';
import useSyncState from './useSyncState'; import useSyncState from './useSyncState';
import isSerializable from './isSerializable'; import isSerializable from './isSerializable';
import { NavigationContainerRef, NavigationContainerProps } from './types'; import { NavigationContainerRef, NavigationContainerProps } from './types';
import NavigationStateContext from './NavigationStateContext';
type State = NavigationState | PartialState<NavigationState> | undefined; type State = NavigationState | PartialState<NavigationState> | undefined;
const DEVTOOLS_CONFIG_KEY = const DEVTOOLS_CONFIG_KEY =
'REACT_NAVIGATION_REDUX_DEVTOOLS_EXTENSION_INTEGRATION_ENABLED'; '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.";
const NOT_INITIALIZED_ERROR = const NOT_INITIALIZED_ERROR =
"The 'navigation' object hasn't been initialized yet. This might happen if you don't have a navigator mounted, or if the navigator hasn't finished mounting. See https://reactnavigation.org/docs/navigating-without-navigation-prop#handling-initialization for more details."; "The 'navigation' object hasn't been initialized yet. This might happen if you don't have a navigator mounted, or if the navigator hasn't finished mounting. See https://reactnavigation.org/docs/navigating-without-navigation-prop#handling-initialization for more details.";
export const NavigationStateContext = React.createContext<{
isDefault?: true;
state?: NavigationState | PartialState<NavigationState>;
getKey: () => string | undefined;
setKey: (key: string) => void;
getState: () => NavigationState | PartialState<NavigationState> | undefined;
setState: (
state: NavigationState | PartialState<NavigationState> | undefined
) => void;
}>({
isDefault: true,
get getKey(): any {
throw new Error(MISSING_CONTEXT_ERROR);
},
get setKey(): any {
throw new Error(MISSING_CONTEXT_ERROR);
},
get getState(): any {
throw new Error(MISSING_CONTEXT_ERROR);
},
get setState(): any {
throw new Error(MISSING_CONTEXT_ERROR);
},
});
let hasWarnedForSerialization = false; let hasWarnedForSerialization = false;
/** /**
@@ -199,8 +172,21 @@ const BaseNavigationContainer = React.forwardRef(
return getStateForRoute('root'); return getStateForRoute('root');
}, [getStateForRoute]); }, [getStateForRoute]);
const getCurrentRoute = React.useCallback(() => {
let state = getRootState();
if (state === undefined) {
return undefined;
}
while (state.routes[state.index].state !== undefined) {
state = state.routes[state.index].state as NavigationState;
}
return state.routes[state.index];
}, [getRootState]);
const emitter = useEventEmitter(); const emitter = useEventEmitter();
const { addOptionsGetter, getCurrentOptions } = useOptionsGetters({});
React.useImperativeHandle(ref, () => ({ React.useImperativeHandle(ref, () => ({
...(Object.keys(CommonActions) as (keyof typeof CommonActions)[]).reduce< ...(Object.keys(CommonActions) as (keyof typeof CommonActions)[]).reduce<
any any
@@ -221,6 +207,8 @@ const BaseNavigationContainer = React.forwardRef(
getRootState, getRootState,
dangerouslyGetState: () => state, dangerouslyGetState: () => state,
dangerouslyGetParent: () => undefined, dangerouslyGetParent: () => undefined,
getCurrentRoute,
getCurrentOptions,
})); }));
const builderContext = React.useMemo( const builderContext = React.useMemo(
@@ -244,8 +232,9 @@ const BaseNavigationContainer = React.forwardRef(
setState, setState,
getKey, getKey,
setKey, setKey,
addOptionsGetter,
}), }),
[getKey, getState, setKey, setState, state] [getKey, getState, setKey, setState, state, addOptionsGetter]
); );
React.useEffect(() => { React.useEffect(() => {

View File

@@ -0,0 +1,35 @@
import * as React from 'react';
import { NavigationState, PartialState } from '@react-navigation/routers';
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.";
export default React.createContext<{
isDefault?: true;
state?: NavigationState | PartialState<NavigationState>;
getKey: () => string | undefined;
setKey: (key: string) => void;
getState: () => NavigationState | PartialState<NavigationState> | undefined;
setState: (
state: NavigationState | PartialState<NavigationState> | undefined
) => void;
addOptionsGetter?: (
key: string,
getter: () => object | undefined | null
) => void;
}>({
isDefault: true,
get getKey(): any {
throw new Error(MISSING_CONTEXT_ERROR);
},
get setKey(): any {
throw new Error(MISSING_CONTEXT_ERROR);
},
get getState(): any {
throw new Error(MISSING_CONTEXT_ERROR);
},
get setState(): any {
throw new Error(MISSING_CONTEXT_ERROR);
},
});

View File

@@ -5,12 +5,13 @@ import {
NavigationState, NavigationState,
PartialState, PartialState,
} from '@react-navigation/routers'; } from '@react-navigation/routers';
import { NavigationStateContext } from './BaseNavigationContainer'; import NavigationStateContext from './NavigationStateContext';
import NavigationContext from './NavigationContext'; import NavigationContext from './NavigationContext';
import NavigationRouteContext from './NavigationRouteContext'; import NavigationRouteContext from './NavigationRouteContext';
import StaticContainer from './StaticContainer'; import StaticContainer from './StaticContainer';
import EnsureSingleNavigator from './EnsureSingleNavigator'; import EnsureSingleNavigator from './EnsureSingleNavigator';
import { NavigationProp, RouteConfig, EventMapBase } from './types'; import { NavigationProp, RouteConfig, EventMapBase } from './types';
import useOptionsGetters from './useOptionsGetters';
type Props< type Props<
State extends NavigationState, State extends NavigationState,
@@ -24,6 +25,7 @@ type Props<
}; };
getState: () => State; getState: () => State;
setState: (state: State) => void; setState: (state: State) => void;
options: object;
}; };
/** /**
@@ -40,11 +42,24 @@ export default function SceneView<
navigation, navigation,
getState, getState,
setState, setState,
options,
}: Props<State, ScreenOptions, EventMap>) { }: Props<State, ScreenOptions, EventMap>) {
const navigatorKeyRef = React.useRef<string | undefined>(); const navigatorKeyRef = React.useRef<string | undefined>();
const getKey = React.useCallback(() => navigatorKeyRef.current, []); const getKey = React.useCallback(() => navigatorKeyRef.current, []);
const optionsRef = React.useRef<object | undefined>(options);
React.useEffect(() => {
optionsRef.current = options;
}, [options]);
const getOptions = React.useCallback(() => optionsRef.current, []);
const { addOptionsGetter } = useOptionsGetters({
key: route.key,
getOptions,
});
const setKey = React.useCallback((key: string) => { const setKey = React.useCallback((key: string) => {
navigatorKeyRef.current = key; navigatorKeyRef.current = key;
}, []); }, []);
@@ -77,8 +92,16 @@ export default function SceneView<
setState: setCurrentState, setState: setCurrentState,
getKey, getKey,
setKey, setKey,
addOptionsGetter,
}), }),
[getCurrentState, getKey, route.state, setCurrentState, setKey] [
getCurrentState,
getKey,
route.state,
setCurrentState,
setKey,
addOptionsGetter,
]
); );
return ( return (

View File

@@ -5,9 +5,8 @@ import {
NavigationState, NavigationState,
Router, Router,
} from '@react-navigation/routers'; } from '@react-navigation/routers';
import BaseNavigationContainer, { import BaseNavigationContainer from '../BaseNavigationContainer';
NavigationStateContext, import NavigationStateContext from '../NavigationStateContext';
} from '../BaseNavigationContainer';
import MockRouter, { MockActions } from './__fixtures__/MockRouter'; import MockRouter, { MockActions } from './__fixtures__/MockRouter';
import useNavigationBuilder from '../useNavigationBuilder'; import useNavigationBuilder from '../useNavigationBuilder';
import Screen from '../Screen'; import Screen from '../Screen';

View File

@@ -117,7 +117,8 @@ it("doesn't add query param for empty params", () => {
}); });
it('handles state with config with nested screens', () => { it('handles state with config with nested screens', () => {
const path = '/foe/bar/sweet/apple/baz/jane?answer=42&count=10&valid=true'; const path =
'/foo/foe/bar/sweet/apple/baz/jane?answer=42&count=10&valid=true';
const config = { const config = {
Foo: { Foo: {
path: 'foo', path: 'foo',
@@ -182,8 +183,77 @@ it('handles state with config with nested screens', () => {
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path); expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
}); });
it('handles state with config with nested screens and exact', () => {
const path = '/foe/bar/sweet/apple/baz/jane?answer=42&count=10&valid=true';
const config = {
Foo: {
path: 'foo',
screens: {
Foe: {
path: 'foe',
exact: true,
},
},
},
Bar: 'bar/:type/:fruit',
Baz: {
path: 'baz/:author',
parse: {
author: (author: string) =>
author.replace(/^\w/, (c) => c.toUpperCase()),
count: Number,
valid: Boolean,
},
stringify: {
author: (author: string) => author.toLowerCase(),
id: (id: number) => `x${id}`,
unknown: (_: unknown) => 'x',
},
},
};
const state = {
routes: [
{
name: 'Foo',
state: {
routes: [
{
name: 'Foe',
state: {
routes: [
{
name: 'Bar',
params: { fruit: 'apple', type: 'sweet' },
state: {
routes: [
{
name: 'Baz',
params: {
author: 'Jane',
count: '10',
answer: '42',
valid: true,
},
},
],
},
},
],
},
},
],
},
},
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
});
it('handles state with config with nested screens and unused configs', () => { it('handles state with config with nested screens and unused configs', () => {
const path = '/foe/baz/jane?answer=42&count=10&valid=true'; const path = '/foo/foe/baz/jane?answer=42&count=10&valid=true';
const config = { const config = {
Foo: { Foo: {
path: 'foo', path: 'foo',
@@ -239,6 +309,66 @@ it('handles state with config with nested screens and unused configs', () => {
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path); expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
}); });
it('handles state with config with nested screens and unused configs with exact', () => {
const path = '/foe/baz/jane?answer=42&count=10&valid=true';
const config = {
Foo: {
path: 'foo',
screens: {
Foe: {
path: 'foe',
exact: true,
},
},
},
Baz: {
path: 'baz/:author',
parse: {
author: (author: string) =>
author.replace(/^\w/, (c) => c.toUpperCase()),
count: Number,
valid: Boolean,
},
stringify: {
author: (author: string) =>
author.replace(/^\w/, (c) => c.toLowerCase()),
unknown: (_: unknown) => 'x',
},
},
};
const state = {
routes: [
{
name: 'Foo',
state: {
routes: [
{
name: 'Foe',
state: {
routes: [
{
name: 'Baz',
params: {
author: 'Jane',
count: 10,
answer: '42',
valid: true,
},
},
],
},
},
],
},
},
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
});
it('handles nested object with stringify in it', () => { it('handles nested object with stringify in it', () => {
const path = '/bar/sweet/apple/foo/bis/jane?answer=42&count=10&valid=true'; const path = '/bar/sweet/apple/foo/bis/jane?answer=42&count=10&valid=true';
const config = { const config = {
@@ -252,7 +382,6 @@ it('handles nested object with stringify in it', () => {
}, },
Bar: 'bar/:type/:fruit', Bar: 'bar/:type/:fruit',
Baz: { Baz: {
path: 'baz',
screens: { screens: {
Bos: 'bos', Bos: 'bos',
Bis: { Bis: {
@@ -312,8 +441,82 @@ it('handles nested object with stringify in it', () => {
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path); expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
}); });
it('handles nested object with stringify in it with exact', () => {
const path = '/bar/sweet/apple/foo/bis/jane?answer=42&count=10&valid=true';
const config = {
Foo: {
path: 'foo',
screens: {
Foe: {
path: 'foe',
},
},
},
Bar: 'bar/:type/:fruit',
Baz: {
path: 'baz',
screens: {
Bos: 'bos',
Bis: {
path: 'bis/:author',
exact: true,
stringify: {
author: (author: string) =>
author.replace(/^\w/, (c) => c.toLowerCase()),
},
parse: {
author: (author: string) =>
author.replace(/^\w/, (c) => c.toUpperCase()),
count: Number,
valid: Boolean,
},
},
},
},
};
const state = {
routes: [
{
name: 'Bar',
params: { fruit: 'apple', type: 'sweet' },
state: {
routes: [
{
name: 'Foo',
state: {
routes: [
{
name: 'Baz',
state: {
routes: [
{
name: 'Bis',
params: {
author: 'Jane',
count: 10,
answer: '42',
valid: true,
},
},
],
},
},
],
},
},
],
},
},
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
});
it('handles nested object for second route depth', () => { it('handles nested object for second route depth', () => {
const path = '/baz'; const path = '/foo/bar/baz';
const config = { const config = {
Foo: { Foo: {
path: 'foo', path: 'foo',
@@ -351,7 +554,95 @@ it('handles nested object for second route depth', () => {
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path); expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
}); });
it('handles nested object for second route depth and and path and stringify in roots', () => { it('handles nested object for second route depth with exact', () => {
const path = '/baz';
const config = {
Foo: {
path: 'foo',
screens: {
Foe: 'foe',
Bar: {
path: 'bar',
screens: {
Baz: {
path: 'baz',
exact: true,
},
},
},
},
},
};
const state = {
routes: [
{
name: 'Foo',
state: {
routes: [
{
name: 'Bar',
state: {
routes: [{ name: 'Baz' }],
},
},
],
},
},
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
});
it('handles nested object for second route depth and path and stringify in roots', () => {
const path = '/foo/dathomir/bar/42/baz';
const config = {
Foo: {
path: 'foo/:planet',
stringify: {
id: (id: number) => `planet=${id}`,
},
screens: {
Foe: 'foe',
Bar: {
path: 'bar/:id',
parse: {
id: Number,
},
screens: {
Baz: 'baz',
},
},
},
},
};
const state = {
routes: [
{
name: 'Foo',
params: { planet: 'dathomir' },
state: {
routes: [
{
name: 'Bar',
state: {
routes: [{ name: 'Baz', params: { id: 42 } }],
},
},
],
},
},
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
});
it('handles nested object for second route depth and path and stringify in roots with exact', () => {
const path = '/baz'; const path = '/baz';
const config = { const config = {
Foo: { Foo: {
@@ -370,7 +661,10 @@ it('handles nested object for second route depth and and path and stringify in r
id: Number, id: Number,
}, },
screens: { screens: {
Baz: 'baz', Baz: {
path: 'baz',
exact: true,
},
}, },
}, },
}, },
@@ -470,7 +764,7 @@ it('keeps query params if path is empty', () => {
}); });
it('cuts nested configs too', () => { it('cuts nested configs too', () => {
const path = '/baz'; const path = '/foo/baz';
const config = { const config = {
Foo: { Foo: {
path: 'foo', path: 'foo',
@@ -478,7 +772,48 @@ it('cuts nested configs too', () => {
Bar: '', Bar: '',
}, },
}, },
Baz: { path: 'baz' }, Baz: {
path: 'baz',
},
};
const state = {
routes: [
{
name: 'Foo',
state: {
routes: [
{
name: 'Bar',
state: {
routes: [{ name: 'Baz' }],
},
},
],
},
},
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
});
it('cuts nested configs too with exact', () => {
const path = '/baz';
const config = {
Foo: {
path: 'foo',
screens: {
Bar: {
path: '',
exact: true,
},
},
},
Baz: {
path: 'baz',
},
}; };
const state = { const state = {
@@ -504,7 +839,7 @@ it('cuts nested configs too', () => {
}); });
it('handles empty path at the end', () => { it('handles empty path at the end', () => {
const path = '/bar'; const path = '/foo/bar';
const config = { const config = {
Foo: { Foo: {
path: 'foo', path: 'foo',
@@ -641,7 +976,6 @@ it('strips undefined query params', () => {
}, },
Bar: 'bar/:type/:fruit', Bar: 'bar/:type/:fruit',
Baz: { Baz: {
path: 'baz',
screens: { screens: {
Bos: 'bos', Bos: 'bos',
Bis: { Bis: {
@@ -681,7 +1015,79 @@ it('strips undefined query params', () => {
params: { params: {
author: 'Jane', author: 'Jane',
count: 10, count: 10,
answer: undefined, valid: true,
},
},
],
},
},
],
},
},
],
},
},
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
});
it('strips undefined query params with exact', () => {
const path = '/bar/sweet/apple/foo/bis/jane?count=10&valid=true';
const config = {
Foo: {
path: 'foo',
screens: {
Foe: {
path: 'foe',
},
},
},
Bar: 'bar/:type/:fruit',
Baz: {
path: 'baz',
screens: {
Bos: 'bos',
Bis: {
path: 'bis/:author',
exact: true,
stringify: {
author: (author: string) =>
author.replace(/^\w/, (c) => c.toLowerCase()),
},
parse: {
author: (author: string) =>
author.replace(/^\w/, (c) => c.toUpperCase()),
count: Number,
valid: Boolean,
},
},
},
},
};
const state = {
routes: [
{
name: 'Bar',
params: { fruit: 'apple', type: 'sweet' },
state: {
routes: [
{
name: 'Foo',
state: {
routes: [
{
name: 'Baz',
state: {
routes: [
{
name: 'Bis',
params: {
author: 'Jane',
count: 10,
valid: true, valid: true,
}, },
}, },
@@ -714,7 +1120,6 @@ it('handles stripping all query params', () => {
}, },
Bar: 'bar/:type/:fruit', Bar: 'bar/:type/:fruit',
Baz: { Baz: {
path: 'baz',
screens: { screens: {
Bos: 'bos', Bos: 'bos',
Bis: { Bis: {
@@ -753,9 +1158,6 @@ it('handles stripping all query params', () => {
name: 'Bis', name: 'Bis',
params: { params: {
author: 'Jane', author: 'Jane',
count: undefined,
answer: undefined,
valid: undefined,
}, },
}, },
], ],
@@ -773,3 +1175,93 @@ it('handles stripping all query params', () => {
expect(getPathFromState(state, config)).toBe(path); expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path); expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
}); });
it('handles stripping all query params with exact', () => {
const path = '/bar/sweet/apple/foo/bis/jane';
const config = {
Foo: {
path: 'foo',
screens: {
Foe: {
path: 'foe',
},
},
},
Bar: 'bar/:type/:fruit',
Baz: {
path: 'baz',
screens: {
Bos: 'bos',
Bis: {
path: 'bis/:author',
exact: true,
stringify: {
author: (author: string) =>
author.replace(/^\w/, (c) => c.toLowerCase()),
},
parse: {
author: (author: string) =>
author.replace(/^\w/, (c) => c.toUpperCase()),
count: Number,
valid: Boolean,
},
},
},
},
};
const state = {
routes: [
{
name: 'Bar',
params: { fruit: 'apple', type: 'sweet' },
state: {
routes: [
{
name: 'Foo',
state: {
routes: [
{
name: 'Baz',
state: {
routes: [
{
name: 'Bis',
params: {
author: 'Jane',
},
},
],
},
},
],
},
},
],
},
},
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
});
it('replaces undefined query params', () => {
const path = '/bar/undefined/apple';
const config = {
Bar: 'bar/:type/:fruit',
};
const state = {
routes: [
{
name: 'Bar',
params: { fruit: 'apple' },
},
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
});

View File

@@ -147,7 +147,10 @@ it('converts path string to initial state with config with nested screens', () =
Foo: { Foo: {
path: 'foo', path: 'foo',
screens: { screens: {
Foe: 'foe', Foe: {
path: 'foe',
exact: true,
},
}, },
}, },
Bar: 'bar/:type/:fruit', Bar: 'bar/:type/:fruit',
@@ -213,7 +216,10 @@ it('converts path string to initial state with config with nested screens and un
Foo: { Foo: {
path: 'foo', path: 'foo',
screens: { screens: {
Foe: 'foe', Foe: {
path: 'foe',
exact: true,
},
}, },
}, },
Baz: { Baz: {
@@ -268,16 +274,23 @@ it('handles nested object with unused configs and with parse in it', () => {
Foo: { Foo: {
path: 'foo', path: 'foo',
screens: { screens: {
Foe: 'foe', Foe: {
path: 'foe',
exact: true,
},
}, },
}, },
Bar: 'bar/:type/:fruit', Bar: 'bar/:type/:fruit',
Baz: { Baz: {
path: 'baz', path: 'baz',
screens: { screens: {
Bos: 'bos', Bos: {
path: 'bos',
exact: true,
},
Bis: { Bis: {
path: 'bis/:author', path: 'bis/:author',
exact: true,
stringify: { stringify: {
author: (author: string) => author: (author: string) =>
author.replace(/^\w/, (c) => c.toLowerCase()), author.replace(/^\w/, (c) => c.toLowerCase()),
@@ -348,11 +361,18 @@ it('handles parse in nested object for second route depth', () => {
Foo: { Foo: {
path: 'foo', path: 'foo',
screens: { screens: {
Foe: 'foe', Foe: {
path: 'foe',
exact: true,
},
Bar: { Bar: {
path: 'bar', path: 'bar',
exact: true,
screens: { screens: {
Baz: 'baz', Baz: {
path: 'baz',
exact: true,
},
}, },
}, },
}, },
@@ -519,16 +539,23 @@ it('handles two initialRouteNames', () => {
Foo: { Foo: {
path: 'foo', path: 'foo',
screens: { screens: {
Foe: 'foe', Foe: {
path: 'foe',
exact: true,
},
}, },
}, },
Bar: 'bar/:type/:fruit', Bar: 'bar/:type/:fruit',
Baz: { Baz: {
initialRouteName: 'Bos', initialRouteName: 'Bos',
screens: { screens: {
Bos: 'bos', Bos: {
path: 'bos',
exact: true,
},
Bis: { Bis: {
path: 'bis/:author', path: 'bis/:author',
exact: true,
stringify: { stringify: {
author: (author: string) => author: (author: string) =>
author.replace(/^\w/, (c) => c.toLowerCase()), author.replace(/^\w/, (c) => c.toLowerCase()),
@@ -601,16 +628,23 @@ it('accepts initialRouteName without config for it', () => {
Foo: { Foo: {
path: 'foo', path: 'foo',
screens: { screens: {
Foe: 'foe', Foe: {
path: 'foe',
exact: true,
},
}, },
}, },
Bar: 'bar/:type/:fruit', Bar: 'bar/:type/:fruit',
Baz: { Baz: {
initialRouteName: 'Bas', initialRouteName: 'Bas',
screens: { screens: {
Bos: 'bos', Bos: {
path: 'bos',
exact: true,
},
Bis: { Bis: {
path: 'bis/:author', path: 'bis/:author',
exact: true,
stringify: { stringify: {
author: (author: string) => author: (author: string) =>
author.replace(/^\w/, (c) => c.toLowerCase()), author.replace(/^\w/, (c) => c.toLowerCase()),
@@ -984,3 +1018,868 @@ it('handles not taking path with too many segments', () => {
state state
); );
}); });
it('handles differently ordered params v1', () => {
const path = '/foos/5/res/20';
const config = {
Foe: {
path: '/',
initialRouteName: 'Foo',
screens: {
Foo: 'foo',
Bis: {
path: 'foos/:id',
parse: {
id: Number,
},
},
Bas: {
path: 'foos/:id/res/:pwd',
parse: {
id: Number,
pwd: Number,
},
},
},
},
};
const state = {
routes: [
{
name: 'Foe',
state: {
index: 1,
routes: [
{
name: 'Foo',
},
{
name: 'Bas',
params: { id: 5, pwd: 20 },
},
],
},
},
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
});
it('handles differently ordered params v2', () => {
const path = '/5/20/foos/res';
const config = {
Foe: {
path: '/',
initialRouteName: 'Foo',
screens: {
Foo: 'foo',
Bis: {
path: 'foos/:id',
parse: {
id: Number,
},
},
Bas: {
path: ':id/:pwd/foos/res',
parse: {
id: Number,
pwd: Number,
},
},
},
},
};
const state = {
routes: [
{
name: 'Foe',
state: {
index: 1,
routes: [
{
name: 'Foo',
},
{
name: 'Bas',
params: { id: 5, pwd: 20 },
},
],
},
},
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
});
it('handles differently ordered params v3', () => {
const path = '/foos/5/20/res';
const config = {
Foe: {
path: '/',
initialRouteName: 'Foo',
screens: {
Foo: 'foo',
Bis: {
path: 'foos/:id',
parse: {
id: Number,
},
},
Bas: {
path: 'foos/:id/:pwd/res',
parse: {
id: Number,
pwd: Number,
},
},
},
},
};
const state = {
routes: [
{
name: 'Foe',
state: {
index: 1,
routes: [
{
name: 'Foo',
},
{
name: 'Bas',
params: { id: 5, pwd: 20 },
},
],
},
},
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
});
it('handles differently ordered params v4', () => {
const path = '5/foos/res/20';
const config = {
Foe: {
path: '/',
initialRouteName: 'Foo',
screens: {
Foo: 'foo',
Bis: {
path: 'foos/:id',
parse: {
id: Number,
},
},
Bas: {
path: ':id/foos/res/:pwd',
parse: {
id: Number,
pwd: Number,
},
},
},
},
};
const state = {
routes: [
{
name: 'Foe',
state: {
index: 1,
routes: [
{
name: 'Foo',
},
{
name: 'Bas',
params: { id: 5, pwd: 20 },
},
],
},
},
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
});
it('handles simple optional params', () => {
const path = '/foos/5';
const config = {
Foe: {
path: '/',
initialRouteName: 'Foo',
screens: {
Foo: 'foo',
Bis: {
path: 'foo/:id',
parse: {
id: Number,
},
},
Bas: {
path: 'foos/:id/:nip?',
parse: {
id: Number,
nip: Number,
},
},
},
},
};
const state = {
routes: [
{
name: 'Foe',
state: {
index: 1,
routes: [
{
name: 'Foo',
},
{
name: 'Bas',
params: { id: 5 },
},
],
},
},
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
});
it('handle 2 optional params at the end v1', () => {
const path = '/foos/5';
const config = {
Foe: {
path: '/',
initialRouteName: 'Foo',
screens: {
Foo: 'foo',
Bis: {
path: 'foo/:id',
parse: {
id: Number,
},
},
Bas: {
path: 'foos/:id/:nip?/:pwd?',
parse: {
id: Number,
nip: Number,
},
},
},
},
};
const state = {
routes: [
{
name: 'Foe',
state: {
index: 1,
routes: [
{
name: 'Foo',
},
{
name: 'Bas',
params: { id: 5 },
},
],
},
},
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
});
it('handle 2 optional params at the end v2', () => {
const path = '/foos/5/10';
const config = {
Foe: {
path: '/',
initialRouteName: 'Foo',
screens: {
Foo: 'foo',
Bis: {
path: 'foo/:id',
parse: {
id: Number,
},
},
Bas: {
path: 'foos/:id/:nip?/:pwd?',
parse: {
id: Number,
nip: Number,
},
},
},
},
};
const state = {
routes: [
{
name: 'Foe',
state: {
index: 1,
routes: [
{
name: 'Foo',
},
{
name: 'Bas',
params: { id: 5, nip: 10 },
},
],
},
},
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
});
it('handle 2 optional params at the end v3', () => {
const path = '/foos/5/10/15';
const config = {
Foe: {
path: '/',
initialRouteName: 'Foo',
screens: {
Foo: 'foo',
Bis: {
path: 'foo/:id',
parse: {
id: Number,
},
},
Bas: {
path: 'foos/:id/:nip?/:pwd?',
parse: {
id: Number,
nip: Number,
pwd: Number,
},
},
},
},
};
const state = {
routes: [
{
name: 'Foe',
state: {
index: 1,
routes: [
{
name: 'Foo',
},
{
name: 'Bas',
params: { id: 5, nip: 10, pwd: 15 },
},
],
},
},
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
});
it('handle optional params in the middle v1', () => {
const path = '/foos/5/10';
const config = {
Foe: {
path: '/',
initialRouteName: 'Foo',
screens: {
Foo: 'foo',
Bis: {
path: 'foo/:id',
parse: {
id: Number,
},
},
Bas: {
path: 'foos/:id/:nip?/:pwd',
parse: {
id: Number,
nip: Number,
pwd: Number,
},
},
},
},
};
const state = {
routes: [
{
name: 'Foe',
state: {
index: 1,
routes: [
{
name: 'Foo',
},
{
name: 'Bas',
params: { id: 5, pwd: 10 },
},
],
},
},
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
});
it('handle optional params in the middle v2', () => {
const path = '/foos/5/10/15';
const config = {
Foe: {
path: '/',
initialRouteName: 'Foo',
screens: {
Foo: 'foo',
Bis: {
path: 'foo/:id',
parse: {
id: Number,
},
},
Bas: {
path: 'foos/:id/:nip?/:pwd',
parse: {
id: Number,
nip: Number,
pwd: Number,
},
},
},
},
};
const state = {
routes: [
{
name: 'Foe',
state: {
index: 1,
routes: [
{
name: 'Foo',
},
{
name: 'Bas',
params: { id: 5, nip: 10, pwd: 15 },
},
],
},
},
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
});
it('handle optional params in the middle v3', () => {
const path = '/foos/5/10/15';
const config = {
Foe: {
path: '/',
initialRouteName: 'Foo',
screens: {
Foo: 'foo',
Bis: {
path: 'foo/:id',
parse: {
id: Number,
},
},
Bas: {
path: 'foos/:id/:nip?/:pwd/:smh',
parse: {
id: Number,
nip: Number,
pwd: Number,
smh: Number,
},
},
},
},
};
const state = {
routes: [
{
name: 'Foe',
state: {
index: 1,
routes: [
{
name: 'Foo',
},
{
name: 'Bas',
params: { id: 5, pwd: 10, smh: 15 },
},
],
},
},
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
});
it('handle optional params in the middle v4', () => {
const path = '/foos/5/10';
const config = {
Foe: {
path: '/',
initialRouteName: 'Foo',
screens: {
Foo: 'foo',
Bis: {
path: 'foo/:id',
parse: {
id: Number,
},
},
Bas: {
path: 'foos/:nip?/:pwd/:smh?/:id',
parse: {
id: Number,
nip: Number,
pwd: Number,
smh: Number,
},
},
},
},
};
const state = {
routes: [
{
name: 'Foe',
state: {
index: 1,
routes: [
{
name: 'Foo',
},
{
name: 'Bas',
params: { pwd: 5, id: 10 },
},
],
},
},
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
});
it('handle optional params in the middle v5', () => {
const path = '/foos/5/10/15';
const config = {
Foe: {
path: '/',
initialRouteName: 'Foo',
screens: {
Foo: 'foo',
Bis: {
path: 'foo/:id',
parse: {
id: Number,
},
},
Bas: {
path: 'foos/:nip?/:pwd/:smh?/:id',
parse: {
id: Number,
nip: Number,
pwd: Number,
smh: Number,
},
},
},
},
};
const state = {
routes: [
{
name: 'Foe',
state: {
index: 1,
routes: [
{
name: 'Foo',
},
{
name: 'Bas',
params: { nip: 5, pwd: 10, id: 15 },
},
],
},
},
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
});
it('handle optional params in the beginning v1', () => {
const path = '5/10/foos/15';
const config = {
Foe: {
path: '/',
initialRouteName: 'Foo',
screens: {
Foo: 'foo',
Bis: {
path: 'foo/:id',
parse: {
id: Number,
},
},
Bas: {
path: ':nip?/:pwd/foos/:smh?/:id',
parse: {
id: Number,
nip: Number,
pwd: Number,
smh: Number,
},
},
},
},
};
const state = {
routes: [
{
name: 'Foe',
state: {
index: 1,
routes: [
{
name: 'Foo',
},
{
name: 'Bas',
params: { nip: 5, pwd: 10, id: 15 },
},
],
},
},
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
});
it('handle optional params in the beginning v2', () => {
const path = '5/10/foos/15';
const config = {
Foe: {
path: '/',
initialRouteName: 'Foo',
screens: {
Foo: 'foo',
Bis: {
path: 'foo/:id',
parse: {
id: Number,
},
},
Bas: {
path: ':nip?/:smh?/:pwd/foos/:id',
parse: {
id: Number,
nip: Number,
pwd: Number,
smh: Number,
},
},
},
},
};
const state = {
routes: [
{
name: 'Foe',
state: {
index: 1,
routes: [
{
name: 'Foo',
},
{
name: 'Bas',
params: { nip: 5, pwd: 10, id: 15 },
},
],
},
},
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
});
it('merges parent patterns if needed', () => {
const path = 'foo/42/baz/babel';
const config = {
Foo: {
path: 'foo/:bar',
parse: {
bar: Number,
},
screens: {
Baz: 'baz/:qux',
},
},
};
const state = {
routes: [
{
name: 'Foo',
params: { bar: 42 },
state: {
routes: [
{
name: 'Baz',
params: { qux: 'babel' },
},
],
},
},
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
});
it('ignores extra slashes in the pattern', () => {
const path = '/bar/42';
const config = {
Foo: {
screens: {
Bar: {
path: '/bar//:id/',
},
},
},
};
const state = {
routes: [
{
name: 'Foo',
state: {
routes: [
{
name: 'Bar',
params: { id: '42' },
},
],
},
},
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
});

View File

@@ -379,6 +379,8 @@ it("doesn't update state if action wasn't handled", () => {
}); });
it('cleans up state when the navigator unmounts', () => { it('cleans up state when the navigator unmounts', () => {
jest.useFakeTimers();
const TestNavigator = (props: any) => { const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props); const { state, descriptors } = useNavigationBuilder(MockRouter, props);
@@ -426,6 +428,8 @@ it('cleans up state when the navigator unmounts', () => {
<BaseNavigationContainer onStateChange={onStateChange} children={null} /> <BaseNavigationContainer onStateChange={onStateChange} children={null} />
); );
act(() => jest.runAllTimers());
expect(onStateChange).toBeCalledTimes(2); expect(onStateChange).toBeCalledTimes(2);
expect(onStateChange).lastCalledWith(undefined); expect(onStateChange).lastCalledWith(undefined);
}); });
@@ -1486,3 +1490,128 @@ it("doesn't throw if children is null", () => {
expect(() => render(element).update(element)).not.toThrowError(); expect(() => render(element).update(element)).not.toThrowError();
}); });
it('returns currently focused route with getCurrentRoute', () => {
const TestNavigator = (props: any): any => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return descriptors[state.routes[state.index].key].render();
};
const TestScreen = () => null;
const navigation = React.createRef<NavigationContainerRef>();
const container = (
<BaseNavigationContainer ref={navigation}>
<TestNavigator>
<Screen name="bar" options={{ a: 'b' }}>
{() => (
<TestNavigator initialRouteName="bar-a">
<Screen
name="bar-a"
component={TestScreen}
options={{ sample: 'data' }}
/>
</TestNavigator>
)}
</Screen>
<Screen name="xux" component={TestScreen} />
</TestNavigator>
</BaseNavigationContainer>
);
render(container).update(container);
expect(navigation.current?.getCurrentRoute()).toEqual({
key: 'bar-a',
name: 'bar-a',
});
});
it("returns currently focused route's options with getCurrentOptions", () => {
const TestNavigator = (props: any): any => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return descriptors[state.routes[state.index].key].render();
};
const TestScreen = () => null;
const navigation = React.createRef<NavigationContainerRef>();
const container = (
<BaseNavigationContainer ref={navigation}>
<TestNavigator>
<Screen name="bar" options={{ a: 'b' }}>
{() => (
<TestNavigator
initialRouteName="bar-a"
screenOptions={() => ({ sample2: 'data' })}
>
<Screen
name="bar-a"
component={TestScreen}
options={{ sample: 'data' }}
/>
</TestNavigator>
)}
</Screen>
<Screen name="xux" component={TestScreen} />
</TestNavigator>
</BaseNavigationContainer>
);
render(container).update(container);
expect(navigation.current?.getCurrentOptions()).toEqual({
sample: 'data',
sample2: 'data',
});
});
it('does not throw if while getting current options with no options defined', () => {
const TestNavigator = (props: any): any => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return descriptors[state.routes[state.index].key].render();
};
const TestScreen = () => null;
const navigation = React.createRef<NavigationContainerRef>();
const container = (
<BaseNavigationContainer ref={navigation}>
<TestNavigator>
<Screen name="bar" options={{ a: 'b' }}>
{() => (
<TestNavigator initialRouteName="bar-a">
<Screen
name="bar-b"
component={TestScreen}
options={{ wrongKey: true }}
/>
<Screen name="bar-a" component={TestScreen} />
</TestNavigator>
)}
</Screen>
</TestNavigator>
</BaseNavigationContainer>
);
render(container).update(container);
expect(navigation.current?.getCurrentOptions()).toEqual({});
});
it('does not throw if while getting current options with empty container', () => {
const navigation = React.createRef<NavigationContainerRef>();
// @ts-ignore
const container = <BaseNavigationContainer ref={navigation} />;
render(container).update(container);
expect(navigation.current?.getCurrentOptions()).toEqual(undefined);
});

View File

@@ -4,19 +4,18 @@ import {
PartialState, PartialState,
Route, Route,
} from '@react-navigation/routers'; } from '@react-navigation/routers';
import { PathConfig } from './types';
type State = NavigationState | Omit<PartialState<NavigationState>, 'stale'>; type State = NavigationState | Omit<PartialState<NavigationState>, 'stale'>;
type StringifyConfig = Record<string, (value: any) => string>; type StringifyConfig = Record<string, (value: any) => string>;
type Options = { type OptionsItem = PathConfig[string];
[routeName: string]:
| string type ConfigItem = {
| { pattern?: string;
path?: string; stringify?: StringifyConfig;
stringify?: StringifyConfig; screens?: Record<string, ConfigItem>;
screens?: Options;
};
}; };
/** /**
@@ -48,101 +47,104 @@ type Options = {
*/ */
export default function getPathFromState( export default function getPathFromState(
state?: State, state?: State,
options: Options = {} options: PathConfig = {}
): string { ): string {
if (state === undefined) { if (state === undefined) {
throw Error('NavigationState not passed'); throw Error('NavigationState not passed');
} }
let path = '/';
// Create a normalized configs array which will be easier to use
const configs = createNormalizedConfigs(options);
let path = '/';
let current: State | undefined = state; let current: State | undefined = state;
const allParams: Record<string, any> = {};
while (current) { while (current) {
let index = typeof current.index === 'number' ? current.index : 0; let index = typeof current.index === 'number' ? current.index : 0;
let route = current.routes[index] as Route<string> & { let route = current.routes[index] as Route<string> & {
state?: State; state?: State;
}; };
let currentOptions = options;
let pattern = route.name;
// we keep all the route names that appeared during going deeper in config in case the pattern is resolved to undefined
let nestedRouteNames = '';
while (route.name in currentOptions) { let pattern: string | undefined;
if (typeof currentOptions[route.name] === 'string') {
pattern = currentOptions[route.name] as string; let currentParams: Record<string, any> = { ...route.params };
break; let currentOptions = configs;
} else if (typeof currentOptions[route.name] === 'object') {
// if there is no `screens` property, we return pattern // Keep all the route names that appeared during going deeper in config in case the pattern is resolved to undefined
if ( let nestedRouteNames = [];
!(currentOptions[route.name] as {
screens: Options; let hasNext = true;
}).screens
) { while (route.name in currentOptions && hasNext) {
pattern = (currentOptions[route.name] as { path: string }).path; pattern = currentOptions[route.name].pattern;
nestedRouteNames = `${nestedRouteNames}/${route.name}`;
break; nestedRouteNames.push(route.name);
if (route.params) {
const stringify = currentOptions[route.name]?.stringify;
currentParams = fromEntries(
Object.entries(route.params).map(([key, value]) => [
key,
stringify?.[key] ? stringify[key](value) : String(value),
])
);
if (pattern) {
Object.assign(allParams, currentParams);
}
}
// If there is no `screens` property or no nested state, we return pattern
if (!currentOptions[route.name].screens || route.state === undefined) {
hasNext = false;
} else {
index =
typeof route.state.index === 'number'
? route.state.index
: route.state.routes.length - 1;
const nextRoute = route.state.routes[index];
const nestedConfig = currentOptions[route.name].screens;
// if there is config for next route name, we go deeper
if (nestedConfig && nextRoute.name in nestedConfig) {
route = nextRoute as Route<string> & { state?: State };
currentOptions = nestedConfig;
} else { } else {
// if it is the end of state, we return pattern // If not, there is no sense in going deeper in config
if (route.state === undefined) { hasNext = false;
pattern = (currentOptions[route.name] as { path: string }).path;
nestedRouteNames = `${nestedRouteNames}/${route.name}`;
break;
} else {
index =
typeof route.state.index === 'number' ? route.state.index : 0;
const nextRoute = route.state.routes[index];
const deeperConfig = (currentOptions[route.name] as {
screens: Options;
}).screens;
// if there is config for next route name, we go deeper
if (nextRoute.name in deeperConfig) {
nestedRouteNames = `${nestedRouteNames}/${route.name}`;
route = nextRoute as Route<string> & { state?: State };
currentOptions = deeperConfig;
} else {
// if not, there is no sense in going deeper in config
pattern = (currentOptions[route.name] as { path: string }).path;
nestedRouteNames = `${nestedRouteNames}/${route.name}`;
break;
}
}
} }
} }
} }
if (pattern === undefined) { if (pattern === undefined) {
// cut the first `/` pattern = nestedRouteNames.join('/');
pattern = nestedRouteNames.substring(1);
} }
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;
}, {})
: undefined;
if (currentOptions[route.name] !== undefined) { if (currentOptions[route.name] !== undefined) {
path += pattern path += pattern
.split('/') .split('/')
.map((p) => { .map((p) => {
const name = p.replace(/^:/, ''); const name = p.replace(/^:/, '').replace(/\?$/, '');
// If the path has a pattern for a param, put the param in the path // If the path has a pattern for a param, put the param in the path
if (params && name in params && p.startsWith(':')) { if (p.startsWith(':')) {
const value = params[name]; const value = allParams[name];
// Remove the used value from the params object since we'll use the rest for query string // 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 if (currentParams) {
delete params[name]; // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete currentParams[name];
}
if (value === undefined && p.endsWith('?')) {
// Optional params without value assigned in route.params should be ignored
return '';
}
return encodeURIComponent(value); return encodeURIComponent(value);
} }
@@ -155,14 +157,15 @@ export default function getPathFromState(
if (route.state) { if (route.state) {
path += '/'; path += '/';
} else if (params) { } else if (currentParams) {
for (let param in params) { for (let param in currentParams) {
if (params[param] === 'undefined') { if (currentParams[param] === 'undefined') {
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete params[param]; delete currentParams[param];
} }
} }
const query = queryString.stringify(params);
const query = queryString.stringify(currentParams);
if (query) { if (query) {
path += `?${query}`; path += `?${query}`;
@@ -173,8 +176,63 @@ export default function getPathFromState(
} }
// Remove multiple as well as trailing slashes // Remove multiple as well as trailing slashes
path = path.replace(/\/+/, '/'); path = path.replace(/\/+/g, '/');
path = path.length > 1 ? path.replace(/\/$/, '') : path; path = path.length > 1 ? path.replace(/\/$/, '') : path;
return path; return path;
} }
// Object.fromEntries is not available in older iOS versions
const fromEntries = <K extends string, V>(entries: (readonly [K, V])[]) =>
entries.reduce((acc, [k, v]) => {
acc[k] = v;
return acc;
}, {} as Record<K, V>);
const joinPaths = (...paths: string[]): string =>
([] as string[])
.concat(...paths.map((p) => p.split('/')))
.filter(Boolean)
.join('/');
const createConfigItem = (
config: OptionsItem | string,
parentPattern?: string
): ConfigItem => {
if (typeof config === 'string') {
// If a string is specified as the value of the key(e.g. Foo: '/path'), use it as the pattern
const pattern = parentPattern ? joinPaths(parentPattern, config) : config;
return { pattern };
}
// If an object is specified as the value (e.g. Foo: { ... }),
// It can have `path` property and `screens` prop which has nested configs
const pattern =
config.exact !== true && parentPattern && config.path
? joinPaths(parentPattern, config.path)
: config.path;
const screens = config.screens
? createNormalizedConfigs(config.screens, pattern)
: undefined;
return {
// Normalize pattern to remove any leading, trailing slashes, duplicate slashes etc.
pattern: pattern?.split('/').filter(Boolean).join('/'),
stringify: config.stringify,
screens,
};
};
const createNormalizedConfigs = (
options: PathConfig,
pattern?: string
): Record<string, ConfigItem> =>
fromEntries(
Object.entries(options).map(([name, c]) => {
const result = createConfigItem(c, pattern);
return [name, result];
})
);

View File

@@ -1,29 +1,21 @@
import escape from 'escape-string-regexp';
import queryString from 'query-string'; import queryString from 'query-string';
import { import {
NavigationState, NavigationState,
PartialState, PartialState,
InitialState, InitialState,
} from '@react-navigation/routers'; } from '@react-navigation/routers';
import { PathConfig } from './types';
type ParseConfig = Record<string, (value: string) => any>; type ParseConfig = Record<string, (value: string) => any>;
type Options = {
[routeName: string]:
| string
| {
path?: string;
parse?: ParseConfig;
screens?: Options;
initialRouteName?: string;
};
};
type RouteConfig = { type RouteConfig = {
screen: string; screen: string;
match: string | null; regex?: RegExp;
path: string;
pattern: string; pattern: string;
routeNames: string[]; routeNames: string[];
parse: ParseConfig | undefined; parse?: ParseConfig;
}; };
type InitialRouteConfig = { type InitialRouteConfig = {
@@ -56,45 +48,53 @@ type ResultState = PartialState<NavigationState> & {
*/ */
export default function getStateFromPath( export default function getStateFromPath(
path: string, path: string,
options: Options = {} options: PathConfig = {}
): ResultState | undefined { ): ResultState | undefined {
let initialRoutes: InitialRouteConfig[] = []; let initialRoutes: InitialRouteConfig[] = [];
// Create a normalized configs array which will be easier to use // Create a normalized configs array which will be easier to use
const configs = ([] as RouteConfig[]).concat( const configs = ([] as RouteConfig[])
...Object.keys(options).map((key) => .concat(
createNormalizedConfigs(key, options, [], initialRoutes) ...Object.keys(options).map((key) =>
createNormalizedConfigs(key, options, [], initialRoutes)
)
) )
); .sort(
(a, b) =>
// sort configs so the most exhaustive is always first to be chosen // Sort configs so the most exhaustive is always first to be chosen
configs.sort( b.pattern.split('/').length - a.pattern.split('/').length
(config1, config2) => );
config2.pattern.split('/').length - config1.pattern.split('/').length
);
let remaining = path 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 extra leading slash
.replace(/\?.*/, ''); // Remove query params which we will handle later .replace(/\?.*$/, ''); // Remove query params which we will handle later
if (remaining === '') { // 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 // 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 // When handling empty path, we should only look at the root level config
const match = configs.find( const match = configs.find(
(config) => (config) =>
config.pattern === '' && config.path === '' &&
config.routeNames.every( config.routeNames.every(
// make sure that none of the parent configs have a non-empty path defined // Make sure that none of the parent configs have a non-empty path defined
(name) => !configs.find((c) => c.screen === name)?.pattern (name) => !configs.find((c) => c.screen === name)?.path
) )
); );
if (match) { if (match) {
return createNestedStateObject( return createNestedStateObject(
match.routeNames, match.routeNames.map((name, i, self) => {
initialRoutes, if (i === self.length - 1) {
parseQueryParams(path, match.parse) return { name, params: parseQueryParams(path, match.parse) };
}
return { name };
}),
initialRoutes
); );
} }
@@ -106,35 +106,18 @@ export default function getStateFromPath(
while (remaining) { while (remaining) {
let routeNames: string[] | undefined; let routeNames: string[] | undefined;
let params: Record<string, any> | undefined; let allParams: Record<string, any> | undefined;
// Go through all configs, and see if the next path segment matches our regex // Go through all configs, and see if the next path segment matches our regex
for (const config of configs) { for (const config of configs) {
if (!config.match) { if (!config.regex) {
continue; continue;
} }
let didMatch = true; const match = remaining.match(config.regex);
const matchParts = config.match.split('/');
const remainingParts = remaining.split('/');
// we check if remaining path has enough segments to be handled with this pattern // If our regex matches, we need to extract params from the path
if (config.pattern.split('/').length > remainingParts.length) { if (match) {
continue;
}
// we keep info about the index of segment on which the params start
let paramsIndex = 0;
// the beginning of the remaining path should be the same as the part of config before params
for (paramsIndex; paramsIndex < matchParts.length; paramsIndex++) {
if (matchParts[paramsIndex] !== remainingParts[paramsIndex]) {
didMatch = false;
break;
}
}
// If the first part of the path matches, we need to extract params from the path
if (didMatch) {
routeNames = [...config.routeNames]; routeNames = [...config.routeNames];
const paramPatterns = config.pattern const paramPatterns = config.pattern
@@ -142,28 +125,16 @@ export default function getStateFromPath(
.filter((p) => p.startsWith(':')); .filter((p) => p.startsWith(':'));
if (paramPatterns.length) { if (paramPatterns.length) {
params = paramPatterns.reduce<Record<string, any>>((acc, p, i) => { allParams = paramPatterns.reduce<Record<string, any>>((acc, p, i) => {
const key = p.replace(/^:/, ''); const value = match![(i + 1) * 2].replace(/\//, ''); // The param segments appear every second item starting from 2 in the regex match result
const value = remainingParts[i + paramsIndex]; // The param segments start from the end of matched part
acc[key] = acc[p] = value;
config.parse && config.parse[key]
? config.parse[key](value)
: value;
return acc; return acc;
}, {}); }, {});
} }
// if pattern and remaining path have same amount of segments, there should be nothing left remaining = remaining.replace(match[1], '');
if (config.pattern.split('/').length === remainingParts.length) {
remaining = '';
} else {
// For each segment of the pattern, remove one segment from remaining path
let i = config.pattern.split('/').length;
while (i--) {
remaining = remaining.substr(remaining.indexOf('/') + 1);
}
}
break; break;
} }
@@ -178,7 +149,46 @@ export default function getStateFromPath(
remaining = segments.join('/'); remaining = segments.join('/');
} }
const state = createNestedStateObject(routeNames, initialRoutes, params); const state = createNestedStateObject(
routeNames.map((name) => {
const config = configs.find((c) => c.screen === name);
let params: object | undefined;
if (allParams && config?.path) {
const pattern = config.path;
if (pattern) {
const paramPatterns = pattern
.split('/')
.filter((p) => p.startsWith(':'));
if (paramPatterns.length) {
params = paramPatterns.reduce<Record<string, any>>((acc, p) => {
const key = p.replace(/^:/, '').replace(/\?$/, '');
const value = allParams![p];
if (value) {
acc[key] =
config.parse && config.parse[key]
? config.parse[key](value)
: value;
}
return acc;
}, {});
}
}
}
if (params && Object.keys(params).length) {
return { name, params };
}
return { name };
}),
initialRoutes
);
if (current) { if (current) {
// The state should be nested inside the deepest route we parsed before // The state should be nested inside the deepest route we parsed before
@@ -213,44 +223,65 @@ export default function getStateFromPath(
return result; return result;
} }
function createNormalizedConfigs( const joinPaths = (...paths: string[]): string =>
key: string, ([] as string[])
routeConfig: Options, .concat(...paths.map((p) => p.split('/')))
.filter(Boolean)
.join('/');
const createNormalizedConfigs = (
screen: string,
routeConfig: PathConfig,
routeNames: string[] = [], routeNames: string[] = [],
initials: InitialRouteConfig[] initials: InitialRouteConfig[],
): RouteConfig[] { parentPattern?: string
): RouteConfig[] => {
const configs: RouteConfig[] = []; const configs: RouteConfig[] = [];
routeNames.push(key); routeNames.push(screen);
const value = routeConfig[key]; const config = routeConfig[screen];
if (typeof value === 'string') { if (typeof config === 'string') {
// If a string is specified as the value of the key(e.g. Foo: '/path'), use it as the pattern // If a string is specified as the value of the key(e.g. Foo: '/path'), use it as the pattern
configs.push(createConfigItem(key, routeNames, value)); const pattern = parentPattern ? joinPaths(parentPattern, config) : config;
} else if (typeof value === 'object') {
configs.push(createConfigItem(screen, routeNames, pattern, config));
} else if (typeof config === 'object') {
let pattern: string | undefined;
// if an object is specified as the value (e.g. Foo: { ... }), // if an object is specified as the value (e.g. Foo: { ... }),
// it can have `path` property and // it can have `path` property and
// it could have `screens` prop which has nested configs // it could have `screens` prop which has nested configs
if (typeof value.path === 'string') { if (typeof config.path === 'string') {
configs.push(createConfigItem(key, routeNames, value.path, value.parse)); pattern =
config.exact !== true && parentPattern
? joinPaths(parentPattern, config.path)
: config.path;
configs.push(
createConfigItem(screen, routeNames, pattern, config.path, config.parse)
);
} }
if (value.screens) { if (config.screens) {
// property `initialRouteName` without `screens` has no purpose // property `initialRouteName` without `screens` has no purpose
if (value.initialRouteName) { if (config.initialRouteName) {
initials.push({ initials.push({
initialRouteName: value.initialRouteName, initialRouteName: config.initialRouteName,
connectedRoutes: Object.keys(value.screens), connectedRoutes: Object.keys(config.screens),
}); });
} }
Object.keys(value.screens).forEach((nestedConfig) => {
Object.keys(config.screens).forEach((nestedConfig) => {
const result = createNormalizedConfigs( const result = createNormalizedConfigs(
nestedConfig, nestedConfig,
value.screens as Options, config.screens as PathConfig,
routeNames, routeNames,
initials initials,
pattern
); );
configs.push(...result); configs.push(...result);
}); });
} }
@@ -259,44 +290,62 @@ function createNormalizedConfigs(
routeNames.pop(); routeNames.pop();
return configs; return configs;
} };
function createConfigItem( const createConfigItem = (
screen: string, screen: string,
routeNames: string[], routeNames: string[],
pattern: string, pattern: string,
path: string,
parse?: ParseConfig parse?: ParseConfig
): RouteConfig { ): RouteConfig => {
// part being matched ends on the first param // Normalize pattern to remove any leading, trailing slashes, duplicate slashes etc.
const match = pattern !== '' ? pattern.split('/:')[0] : null; pattern = pattern.split('/').filter(Boolean).join('/');
const regex = pattern
? new RegExp(
`^(${pattern
.split('/')
.map((it) => {
if (it.startsWith(':')) {
return `(([^/]+\\/)${it.endsWith('?') ? '?' : ''})`;
}
return `${escape(it)}\\/`;
})
.join('')})`
)
: undefined;
return { return {
screen, screen,
match, regex,
pattern, pattern,
path,
// The routeNames array is mutated, so copy it to keep the current state // The routeNames array is mutated, so copy it to keep the current state
routeNames: [...routeNames], routeNames: [...routeNames],
parse, parse,
}; };
} };
function findParseConfigForRoute( const findParseConfigForRoute = (
routeName: string, routeName: string,
flatConfig: RouteConfig[] flatConfig: RouteConfig[]
): ParseConfig | undefined { ): ParseConfig | undefined => {
for (const config of flatConfig) { for (const config of flatConfig) {
if (routeName === config.routeNames[config.routeNames.length - 1]) { if (routeName === config.routeNames[config.routeNames.length - 1]) {
return config.parse; return config.parse;
} }
} }
return undefined;
}
// tries to find an initial route connected with the one passed return undefined;
function findInitialRoute( };
// Try to find an initial route connected with the one passed
const findInitialRoute = (
routeName: string, routeName: string,
initialRoutes: InitialRouteConfig[] initialRoutes: InitialRouteConfig[]
): string | undefined { ): string | undefined => {
for (const config of initialRoutes) { for (const config of initialRoutes) {
if (config.connectedRoutes.includes(routeName)) { if (config.connectedRoutes.includes(routeName)) {
return config.initialRouteName === routeName return config.initialRouteName === routeName
@@ -305,28 +354,25 @@ function findInitialRoute(
} }
} }
return undefined; return undefined;
} };
// returns 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 // it is the end of state and if there is initialRoute for this level
function createStateObject( const createStateObject = (
initialRoute: string | undefined, initialRoute: string | undefined,
routeName: string, routeName: string,
isEmpty: boolean, params: Record<string, any> | undefined,
params?: Record<string, any> | undefined isEmpty: boolean
): InitialState { ): InitialState => {
if (isEmpty) { if (isEmpty) {
if (initialRoute) { if (initialRoute) {
return { return {
index: 1, index: 1,
routes: [ routes: [{ name: initialRoute }, { name: routeName as string, params }],
{ name: initialRoute },
{ name: routeName as string, ...(params && { params }) },
],
}; };
} else { } else {
return { return {
routes: [{ name: routeName as string, ...(params && { params }) }], routes: [{ name: routeName as string, params }],
}; };
} }
} else { } else {
@@ -335,53 +381,59 @@ function createStateObject(
index: 1, index: 1,
routes: [ routes: [
{ name: initialRoute }, { name: initialRoute },
{ name: routeName as string, state: { routes: [] } }, { name: routeName as string, params, state: { routes: [] } },
], ],
}; };
} else { } else {
return { routes: [{ name: routeName as string, state: { routes: [] } }] }; return {
routes: [{ name: routeName as string, params, state: { routes: [] } }],
};
} }
} }
} };
function createNestedStateObject( const createNestedStateObject = (
routeNames: string[], routes: { name: string; params?: object }[],
initialRoutes: InitialRouteConfig[], initialRoutes: InitialRouteConfig[]
params: object | undefined ) => {
) {
let state: InitialState; let state: InitialState;
let routeName = routeNames.shift() as string; let route = routes.shift() as { name: string; params?: object };
let initialRoute = findInitialRoute(routeName, initialRoutes); let initialRoute = findInitialRoute(route.name, initialRoutes);
state = createStateObject( state = createStateObject(
initialRoute, initialRoute,
routeName, route.name,
routeNames.length === 0, route.params,
params routes.length === 0
); );
if (routeNames.length > 0) { if (routes.length > 0) {
let nestedState = state; let nestedState = state;
while ((routeName = routeNames.shift() as string)) { while ((route = routes.shift() as { name: string; params?: object })) {
initialRoute = findInitialRoute(routeName, initialRoutes); initialRoute = findInitialRoute(route.name, initialRoutes);
nestedState.routes[nestedState.index || 0].state = createStateObject(
const nestedStateIndex =
nestedState.index || nestedState.routes.length - 1;
nestedState.routes[nestedStateIndex].state = createStateObject(
initialRoute, initialRoute,
routeName, route.name,
routeNames.length === 0, route.params,
params routes.length === 0
); );
if (routeNames.length > 0) {
nestedState = nestedState.routes[nestedState.index || 0] if (routes.length > 0) {
nestedState = nestedState.routes[nestedStateIndex]
.state as InitialState; .state as InitialState;
} }
} }
} }
return state; return state;
} };
function findFocusedRoute(state: InitialState) { const findFocusedRoute = (state: InitialState) => {
let current: InitialState | undefined = state; let current: InitialState | undefined = state;
while (current?.routes[current.index || 0].state) { while (current?.routes[current.index || 0].state) {
@@ -394,12 +446,12 @@ function findFocusedRoute(state: InitialState) {
]; ];
return route; return route;
} };
function parseQueryParams( const parseQueryParams = (
path: string, path: string,
parseConfig?: Record<string, (value: string) => any> parseConfig?: Record<string, (value: string) => any>
) { ) => {
const query = path.split('?')[1]; const query = path.split('?')[1];
const params = queryString.parse(query); const params = queryString.parse(query);
@@ -412,4 +464,4 @@ function parseQueryParams(
} }
return Object.keys(params).length ? params : undefined; return Object.keys(params).length ? params : undefined;
} };

View File

@@ -422,6 +422,14 @@ export type NavigationContainerRef = NavigationHelpers<ParamListBase> &
* Get the rehydrated navigation state of the navigation tree. * Get the rehydrated navigation state of the navigation tree.
*/ */
getRootState(): NavigationState; getRootState(): NavigationState;
/**
* Get the currently focused navigation route.
*/
getCurrentRoute(): Route<string> | undefined;
/**
* Get the currently focused route's options.
*/
getCurrentOptions(): object | undefined;
}; };
export type TypedNavigator< export type TypedNavigator<
@@ -462,3 +470,16 @@ export type TypedNavigator<
_: RouteConfig<ParamList, RouteName, State, ScreenOptions, EventMap> _: RouteConfig<ParamList, RouteName, State, ScreenOptions, EventMap>
) => null; ) => null;
}; };
export type PathConfig = {
[routeName: string]:
| string
| {
path?: string;
exact?: boolean;
parse?: Record<string, (value: string) => any>;
stringify?: Record<string, (value: any) => string>;
screens?: PathConfig;
initialRouteName?: string;
};
};

View File

@@ -117,6 +117,28 @@ export default function useDescriptors<
const screen = screens[route.name]; const screen = screens[route.name];
const navigation = navigations[route.key]; const navigation = navigations[route.key];
const routeOptions = {
// The default `screenOptions` passed to the navigator
...(typeof screenOptions === 'object' || screenOptions == null
? screenOptions
: screenOptions({
// @ts-ignore
route,
navigation,
})),
// The `options` prop passed to `Screen` elements
...(typeof screen.options === 'object' || screen.options == null
? screen.options
: screen.options({
// @ts-ignore
route,
// @ts-ignore
navigation,
})),
// The options set via `navigation.setOptions`
...options[route.key],
};
acc[route.key] = { acc[route.key] = {
navigation, navigation,
render() { render() {
@@ -128,31 +150,12 @@ export default function useDescriptors<
screen={screen} screen={screen}
getState={getState} getState={getState}
setState={setState} setState={setState}
options={routeOptions}
/> />
</NavigationBuilderContext.Provider> </NavigationBuilderContext.Provider>
); );
}, },
options: { options: routeOptions,
// The default `screenOptions` passed to the navigator
...(typeof screenOptions === 'object' || screenOptions == null
? screenOptions
: screenOptions({
// @ts-ignore
route,
navigation,
})),
// The `options` prop passed to `Screen` elements
...(typeof screen.options === 'object' || screen.options == null
? screen.options
: screen.options({
// @ts-ignore
route,
// @ts-ignore
navigation,
})),
// The options set via `navigation.setOptions`
...options[route.key],
},
}; };
return acc; return acc;

View File

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

View File

@@ -11,7 +11,7 @@ import {
NavigationAction, NavigationAction,
Route, Route,
} from '@react-navigation/routers'; } from '@react-navigation/routers';
import { NavigationStateContext } from './BaseNavigationContainer'; import NavigationStateContext from './NavigationStateContext';
import NavigationRouteContext from './NavigationRouteContext'; import NavigationRouteContext from './NavigationRouteContext';
import Screen from './Screen'; import Screen from './Screen';
import useEventEmitter from './useEventEmitter'; import useEventEmitter from './useEventEmitter';
@@ -264,41 +264,36 @@ export default function useNavigationBuilder<
getKey, getKey,
} = React.useContext(NavigationStateContext); } = React.useContext(NavigationStateContext);
const previousStateRef = React.useRef< const [initializedState, isFirstStateInitialization] = React.useMemo(() => {
NavigationState | PartialState<NavigationState> | undefined
>();
const initializedStateRef = React.useRef<State>();
let isFirstStateInitialization = false;
if (
initializedStateRef.current === undefined ||
currentState !== previousStateRef.current
) {
// If the current state isn't initialized on first render, we initialize it // If the current state isn't initialized on first render, we initialize it
// We also need to re-initialize it if the state passed from parent was changed (maybe due to reset) // We also need to re-initialize it if the state passed from parent was changed (maybe due to reset)
// Otherwise assume that the state was provided as initial state // Otherwise assume that the state was provided as initial state
// So we need to rehydrate it to make it usable // So we need to rehydrate it to make it usable
if (currentState === undefined || !isStateValid(currentState)) { if (currentState === undefined || !isStateValid(currentState)) {
isFirstStateInitialization = true; return [
initializedStateRef.current = router.getInitialState({ router.getInitialState({
routeNames,
routeParamList,
});
} else {
initializedStateRef.current = router.getRehydratedState(
currentState as PartialState<State>,
{
routeNames, routeNames,
routeParamList, routeParamList,
} }),
); true,
];
} else {
return [
router.getRehydratedState(currentState as PartialState<State>, {
routeNames,
routeParamList,
}),
false,
];
} }
} // We explicitly don't include routeNames/routeParamList in the dep list
// below. We want to avoid forcing a new state to be calculated in cases
React.useEffect(() => { // where routeConfigs change without affecting routeNames/routeParamList.
previousStateRef.current = currentState; // Instead, we handle changes to these in the nextState code below. Note
}, [currentState]); // that some changes to routeConfigs are explicitly ignored, such as changes
// to initialParams
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [currentState, router, isStateValid]);
let state = let state =
// If the state isn't initialized, or stale, use the state we initialized instead // If the state isn't initialized, or stale, use the state we initialized instead
@@ -306,7 +301,7 @@ export default function useNavigationBuilder<
// So it'll be `undefined` or stale untill the first navigation event happens // So it'll be `undefined` or stale untill the first navigation event happens
isStateInitialized(currentState) isStateInitialized(currentState)
? (currentState as State) ? (currentState as State)
: (initializedStateRef.current as State); : (initializedState as State);
let nextState: State = state; let nextState: State = state;
@@ -362,13 +357,24 @@ export default function useNavigationBuilder<
return () => { return () => {
// We need to clean up state for this navigator on unmount // We need to clean up state for this navigator on unmount
if (getCurrentState() !== undefined && getKey() === navigatorKey) { // We do it in a timeout because we need to detect if another navigator mounted in the meantime
setState(undefined); // 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 // eslint-disable-next-line react-hooks/exhaustive-deps
}, []); }, []);
// We initialize this ref here to avoid a new getState getting initialized
// whenever initializedState changes. We want getState to have access to the
// latest initializedState, but don't need it to change when that happens
const initializedStateRef = React.useRef<State>();
initializedStateRef.current = initializedState;
const getState = React.useCallback((): State => { const getState = React.useCallback((): State => {
const currentState = getCurrentState(); const currentState = getCurrentState();

View File

@@ -0,0 +1,70 @@
import * as React from 'react';
import NavigationStateContext from './NavigationStateContext';
import { NavigationState } from '@react-navigation/routers';
export default function useOptionsGetters({
key,
getOptions,
getState,
}: {
key?: string;
getOptions?: () => object | undefined;
getState?: () => NavigationState;
}) {
const optionsGettersFromChild = React.useRef<
Record<string, (() => object | undefined | null) | undefined>
>({});
const { addOptionsGetter: parentAddOptionsGetter } = React.useContext(
NavigationStateContext
);
const getOptionsFromListener = React.useCallback(() => {
for (let key in optionsGettersFromChild.current) {
if (optionsGettersFromChild.current.hasOwnProperty(key)) {
const result = optionsGettersFromChild.current[key]?.();
// null means unfocused route
if (result !== null) {
return result;
}
}
}
return null;
}, []);
const getCurrentOptions = React.useCallback(() => {
if (getState) {
const state = getState();
if (state.routes[state.index].key !== key) {
// null means unfocused route
return null;
}
}
const optionsFromListener = getOptionsFromListener();
if (optionsFromListener !== null) {
return optionsFromListener;
}
return getOptions?.() ?? undefined;
}, [getState, getOptionsFromListener, getOptions, key]);
React.useEffect(() => {
return parentAddOptionsGetter?.(key!, getCurrentOptions);
}, [getCurrentOptions, parentAddOptionsGetter, key]);
const addOptionsGetter = React.useCallback(
(key: string, getter: () => object | undefined | null) => {
optionsGettersFromChild.current[key] = getter;
return () => {
optionsGettersFromChild.current[key] = undefined;
};
},
[]
);
return {
addOptionsGetter,
getCurrentOptions,
};
}

View File

@@ -8,6 +8,15 @@ const UNINTIALIZED_STATE = {};
export default function useSyncState<T>(initialState?: (() => T) | T) { export default function useSyncState<T>(initialState?: (() => T) | T) {
const stateRef = React.useRef<T>(UNINTIALIZED_STATE as any); const stateRef = React.useRef<T>(UNINTIALIZED_STATE as any);
const isSchedulingRef = React.useRef(false); const isSchedulingRef = React.useRef(false);
const isMountedRef = React.useRef(true);
React.useEffect(() => {
isMountedRef.current = true;
return () => {
isMountedRef.current = false;
};
}, []);
if (stateRef.current === UNINTIALIZED_STATE) { if (stateRef.current === UNINTIALIZED_STATE) {
stateRef.current = stateRef.current =
@@ -20,7 +29,7 @@ export default function useSyncState<T>(initialState?: (() => T) | T) {
const getState = React.useCallback(() => stateRef.current, []); const getState = React.useCallback(() => stateRef.current, []);
const setState = React.useCallback((state: T) => { const setState = React.useCallback((state: T) => {
if (state === stateRef.current) { if (state === stateRef.current || !isMountedRef.current) {
return; return;
} }
@@ -42,6 +51,10 @@ export default function useSyncState<T>(initialState?: (() => T) | T) {
}, []); }, []);
const flushUpdates = React.useCallback(() => { const flushUpdates = React.useCallback(() => {
if (!isMountedRef.current) {
return;
}
// Make sure that the tracking state is up-to-date. // Make sure that the tracking state is up-to-date.
// We call it unconditionally, but React should skip the update if state is unchanged. // We call it unconditionally, but React should skip the update if state is unchanged.
setTrackingState(stateRef.current); setTrackingState(stateRef.current);

View File

@@ -3,6 +3,95 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [5.8.0](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.7.7...@react-navigation/drawer@5.8.0) (2020-05-23)
### Features
* update react-native-safe-area-context to 1.0.0 ([#8182](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/issues/8182)) ([d62fbfe](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/d62fbfe255140f16b182e8b54b276a7c96f2aec6))
## [5.7.7](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.7.6...@react-navigation/drawer@5.7.7) (2020-05-20)
**Note:** Version bump only for package @react-navigation/drawer
## [5.7.6](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.7.5...@react-navigation/drawer@5.7.6) (2020-05-20)
**Note:** Version bump only for package @react-navigation/drawer
## [5.7.5](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.7.4...@react-navigation/drawer@5.7.5) (2020-05-16)
**Note:** Version bump only for package @react-navigation/drawer
## [5.7.4](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.7.3...@react-navigation/drawer@5.7.4) (2020-05-14)
**Note:** Version bump only for package @react-navigation/drawer
## [5.7.3](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.7.2...@react-navigation/drawer@5.7.3) (2020-05-14)
**Note:** Version bump only for package @react-navigation/drawer
## [5.7.2](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.7.1...@react-navigation/drawer@5.7.2) (2020-05-10)
**Note:** Version bump only for package @react-navigation/drawer
## [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) ## [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 **Note:** Version bump only for package @react-navigation/drawer

View File

@@ -1,7 +1,7 @@
{ {
"name": "@react-navigation/drawer", "name": "@react-navigation/drawer",
"description": "Drawer navigator component with animated transitions and gesturess", "description": "Drawer navigator component with animated transitions and gesturess",
"version": "5.6.3", "version": "5.8.0",
"keywords": [ "keywords": [
"react-native-component", "react-native-component",
"react-component", "react-component",
@@ -20,11 +20,13 @@
"homepage": "https://reactnavigation.org/docs/drawer-navigator.html", "homepage": "https://reactnavigation.org/docs/drawer-navigator.html",
"main": "lib/commonjs/index.js", "main": "lib/commonjs/index.js",
"react-native": "src/index.tsx", "react-native": "src/index.tsx",
"source": "src/index.tsx",
"module": "lib/module/index.js", "module": "lib/module/index.js",
"types": "lib/typescript/src/index.d.ts", "types": "lib/typescript/src/index.d.ts",
"files": [ "files": [
"src", "src",
"lib" "lib",
"!**/__tests__"
], ],
"sideEffects": false, "sideEffects": false,
"publishConfig": { "publishConfig": {
@@ -39,17 +41,17 @@
"react-native-iphone-x-helper": "^1.2.1" "react-native-iphone-x-helper": "^1.2.1"
}, },
"devDependencies": { "devDependencies": {
"@react-native-community/bob": "^0.10.0", "@react-native-community/bob": "^0.14.3",
"@react-navigation/native": "^5.2.3", "@react-navigation/native": "^5.4.3",
"@types/react": "^16.9.23", "@types/react": "^16.9.34",
"@types/react-native": "^0.61.22", "@types/react-native": "^0.62.7",
"del-cli": "^3.0.0", "del-cli": "^3.0.0",
"react": "~16.9.0", "react": "~16.9.0",
"react-native": "~0.61.5", "react-native": "~0.61.5",
"react-native-gesture-handler": "^1.6.0", "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-safe-area-context": "^1.0.0",
"react-native-screens": "^2.3.0", "react-native-screens": "^2.7.0",
"typescript": "^3.8.3" "typescript": "^3.8.3"
}, },
"peerDependencies": { "peerDependencies": {

View File

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

View File

@@ -8,6 +8,7 @@ import {
NavigationHelpers, NavigationHelpers,
DrawerNavigationState, DrawerNavigationState,
DrawerActionHelpers, DrawerActionHelpers,
RouteProp,
} from '@react-navigation/native'; } from '@react-navigation/native';
import type { PanGestureHandlerProperties } from 'react-native-gesture-handler'; import type { PanGestureHandlerProperties } from 'react-native-gesture-handler';
@@ -208,6 +209,14 @@ export type DrawerNavigationProp<
> & > &
DrawerActionHelpers<ParamList>; 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< export type DrawerDescriptor = Descriptor<
ParamListBase, ParamListBase,
string, string,

View File

@@ -655,14 +655,14 @@ export default class DrawerView extends React.Component<Props> {
gestureEnabled ? () => this.toggleDrawer(false) : undefined gestureEnabled ? () => this.toggleDrawer(false) : undefined
} }
> >
<Overlay progress={progress} style={overlayStyle} /> <Overlay progress={progress} style={overlayStyle as any} />
</TouchableWithoutFeedback> </TouchableWithoutFeedback>
) : ( ) : (
<TapGestureHandler <TapGestureHandler
enabled={gestureEnabled} enabled={gestureEnabled}
onHandlerStateChange={this.handleTapStateChange} onHandlerStateChange={this.handleTapStateChange}
> >
<Overlay progress={progress} style={overlayStyle} /> <Overlay progress={progress} style={overlayStyle as any} />
</TapGestureHandler> </TapGestureHandler>
) )
} }

View File

@@ -3,6 +3,96 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [5.2.8](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.2.7...@react-navigation/material-bottom-tabs@5.2.8) (2020-05-23)
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
## [5.2.7](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.2.6...@react-navigation/material-bottom-tabs@5.2.7) (2020-05-20)
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
## [5.2.6](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.2.5...@react-navigation/material-bottom-tabs@5.2.6) (2020-05-20)
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
## [5.2.5](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.2.4...@react-navigation/material-bottom-tabs@5.2.5) (2020-05-16)
### Bug Fixes
* center icons in material tab bar. fixes [#8248](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/issues/8248) ([51b4087](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/commit/51b40879bdb9cea5462a2291955513a88eb87340))
## [5.2.4](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.2.3...@react-navigation/material-bottom-tabs@5.2.4) (2020-05-14)
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
## [5.2.3](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.2.2...@react-navigation/material-bottom-tabs@5.2.3) (2020-05-14)
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
## [5.2.2](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.2.1...@react-navigation/material-bottom-tabs@5.2.2) (2020-05-10)
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
## [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) ## [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 **Note:** Version bump only for package @react-navigation/material-bottom-tabs

View File

@@ -1,7 +1,7 @@
{ {
"name": "@react-navigation/material-bottom-tabs", "name": "@react-navigation/material-bottom-tabs",
"description": "Integration for bottom navigation component from react-native-paper", "description": "Integration for bottom navigation component from react-native-paper",
"version": "5.1.14", "version": "5.2.8",
"keywords": [ "keywords": [
"react-native-component", "react-native-component",
"react-component", "react-component",
@@ -20,11 +20,13 @@
"homepage": "https://reactnavigation.org/docs/material-bottom-tab-navigator.html", "homepage": "https://reactnavigation.org/docs/material-bottom-tab-navigator.html",
"main": "lib/commonjs/index.js", "main": "lib/commonjs/index.js",
"react-native": "src/index.tsx", "react-native": "src/index.tsx",
"source": "src/index.tsx",
"module": "lib/module/index.js", "module": "lib/module/index.js",
"types": "lib/typescript/src/index.d.ts", "types": "lib/typescript/src/index.d.ts",
"files": [ "files": [
"src", "src",
"lib" "lib",
"!**/__tests__"
], ],
"sideEffects": false, "sideEffects": false,
"publishConfig": { "publishConfig": {
@@ -35,15 +37,15 @@
"clean": "del lib" "clean": "del lib"
}, },
"devDependencies": { "devDependencies": {
"@react-native-community/bob": "^0.10.0", "@react-native-community/bob": "^0.14.3",
"@react-navigation/native": "^5.2.3", "@react-navigation/native": "^5.4.3",
"@types/react": "^16.9.23", "@types/react": "^16.9.34",
"@types/react-native": "^0.61.22", "@types/react-native": "^0.62.7",
"@types/react-native-vector-icons": "^6.4.5", "@types/react-native-vector-icons": "^6.4.5",
"del-cli": "^3.0.0", "del-cli": "^3.0.0",
"react": "~16.9.0", "react": "~16.9.0",
"react-native": "~0.61.5", "react-native": "~0.61.5",
"react-native-paper": "^3.7.0", "react-native-paper": "^3.10.1",
"react-native-vector-icons": "^6.6.0", "react-native-vector-icons": "^6.6.0",
"typescript": "^3.8.3" "typescript": "^3.8.3"
}, },

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
import * as React from 'react'; 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 { BottomNavigation, DefaultTheme, DarkTheme } from 'react-native-paper';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import { import {
@@ -8,6 +8,8 @@ import {
TabNavigationState, TabNavigationState,
TabActions, TabActions,
useTheme, useTheme,
useLinkBuilder,
Link,
} from '@react-navigation/native'; } from '@react-navigation/native';
import { import {
@@ -24,13 +26,14 @@ type Props = MaterialBottomTabNavigationConfig & {
type Scene = { route: { key: string } }; type Scene = { route: { key: string } };
export default function MaterialBottomTabView({ function MaterialBottomTabViewInner({
state, state,
navigation, navigation,
descriptors, descriptors,
...rest ...rest
}: Props) { }: Props) {
const { dark, colors } = useTheme(); const { dark, colors } = useTheme();
const buildLink = useLinkBuilder();
const theme = React.useMemo(() => { const theme = React.useMemo(() => {
const t = dark ? DarkTheme : DefaultTheme; const t = dark ? DarkTheme : DefaultTheme;
@@ -46,67 +49,104 @@ export default function MaterialBottomTabView({
}, [colors, dark]); }, [colors, dark]);
return ( return (
<NavigationHelpersContext.Provider value={navigation}> <BottomNavigation
<BottomNavigation {...rest}
{...rest} theme={theme}
theme={theme} navigationState={state}
navigationState={state} onIndexChange={(index: number) =>
onIndexChange={(index: number) => navigation.dispatch({
navigation.dispatch({ ...TabActions.jumpTo(state.routes[index].name),
...TabActions.jumpTo(state.routes[index].name), target: state.key,
target: state.key, })
}) }
renderScene={({ route }) => descriptors[route.key].render()}
renderTouchable={
Platform.OS === 'web'
? ({
onPress,
route,
accessibilityRole: _0,
borderless: _1,
centered: _2,
rippleColor: _3,
style,
...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);
}
}}
style={[styles.touchable, style]}
/>
);
}
: undefined
}
renderIcon={({ route, focused, color }) => {
const { options } = descriptors[route.key];
if (typeof options.tabBarIcon === 'string') {
return (
<MaterialCommunityIcons
name={options.tabBarIcon}
color={color}
size={24}
style={styles.icon}
/>
);
} }
renderScene={({ route }) => descriptors[route.key].render()}
renderIcon={({ route, focused, color }) => {
const { options } = descriptors[route.key];
if (typeof options.tabBarIcon === 'string') { if (typeof options.tabBarIcon === 'function') {
return ( return options.tabBarIcon({ focused, color });
<MaterialCommunityIcons
name={options.tabBarIcon}
color={color}
size={24}
style={styles.icon}
importantForAccessibility="no-hide-descendants"
accessibilityElementsHidden
/>
);
}
if (typeof options.tabBarIcon === 'function') {
return options.tabBarIcon({ focused, color });
}
return null;
}}
getLabelText={({ route }: Scene) => {
const { options } = descriptors[route.key];
return options.tabBarLabel !== undefined
? options.tabBarLabel
: options.title !== undefined
? options.title
: (route as Route<string>).name;
}}
getColor={({ route }) => descriptors[route.key].options.tabBarColor}
getBadge={({ route }) => descriptors[route.key].options.tabBarBadge}
getAccessibilityLabel={({ route }) =>
descriptors[route.key].options.tabBarAccessibilityLabel
} }
getTestID={({ route }) => descriptors[route.key].options.tabBarTestID}
onTabPress={({ route, preventDefault }) => {
const event = navigation.emit({
type: 'tabPress',
target: route.key,
canPreventDefault: true,
});
if (event.defaultPrevented) { return null;
preventDefault(); }}
} getLabelText={({ route }: Scene) => {
}} const { options } = descriptors[route.key];
/>
return options.tabBarLabel !== undefined
? options.tabBarLabel
: options.title !== undefined
? options.title
: (route as Route<string>).name;
}}
getColor={({ route }) => descriptors[route.key].options.tabBarColor}
getBadge={({ route }) => descriptors[route.key].options.tabBarBadge}
getAccessibilityLabel={({ route }) =>
descriptors[route.key].options.tabBarAccessibilityLabel
}
getTestID={({ route }) => descriptors[route.key].options.tabBarTestID}
onTabPress={({ route, preventDefault }) => {
const event = navigation.emit({
type: 'tabPress',
target: route.key,
canPreventDefault: true,
});
if (event.defaultPrevented) {
preventDefault();
}
}}
/>
);
}
export default function MaterialBottomTabView(props: Props) {
return (
<NavigationHelpersContext.Provider value={props.navigation}>
<MaterialBottomTabViewInner {...props} />
</NavigationHelpersContext.Provider> </NavigationHelpersContext.Provider>
); );
} }
@@ -115,4 +155,8 @@ const styles = StyleSheet.create({
icon: { icon: {
backgroundColor: 'transparent', backgroundColor: 'transparent',
}, },
touchable: {
display: 'flex',
justifyContent: 'center',
},
}); });

View File

@@ -3,6 +3,92 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [5.2.8](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.2.7...@react-navigation/material-top-tabs@5.2.8) (2020-05-23)
**Note:** Version bump only for package @react-navigation/material-top-tabs
## [5.2.7](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.2.6...@react-navigation/material-top-tabs@5.2.7) (2020-05-20)
**Note:** Version bump only for package @react-navigation/material-top-tabs
## [5.2.6](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.2.5...@react-navigation/material-top-tabs@5.2.6) (2020-05-20)
**Note:** Version bump only for package @react-navigation/material-top-tabs
## [5.2.5](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.2.4...@react-navigation/material-top-tabs@5.2.5) (2020-05-16)
**Note:** Version bump only for package @react-navigation/material-top-tabs
## [5.2.4](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.2.3...@react-navigation/material-top-tabs@5.2.4) (2020-05-14)
**Note:** Version bump only for package @react-navigation/material-top-tabs
## [5.2.3](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.2.2...@react-navigation/material-top-tabs@5.2.3) (2020-05-14)
**Note:** Version bump only for package @react-navigation/material-top-tabs
## [5.2.2](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.2.1...@react-navigation/material-top-tabs@5.2.2) (2020-05-10)
**Note:** Version bump only for package @react-navigation/material-top-tabs
## [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) ## [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 **Note:** Version bump only for package @react-navigation/material-top-tabs

View File

@@ -1,7 +1,7 @@
{ {
"name": "@react-navigation/material-top-tabs", "name": "@react-navigation/material-top-tabs",
"description": "Integration for the animated tab view component from react-native-tab-view", "description": "Integration for the animated tab view component from react-native-tab-view",
"version": "5.1.14", "version": "5.2.8",
"keywords": [ "keywords": [
"react-native-component", "react-native-component",
"react-component", "react-component",
@@ -20,11 +20,13 @@
"homepage": "https://reactnavigation.org/docs/material-top-tab-navigator.html", "homepage": "https://reactnavigation.org/docs/material-top-tab-navigator.html",
"main": "lib/commonjs/index.js", "main": "lib/commonjs/index.js",
"react-native": "src/index.tsx", "react-native": "src/index.tsx",
"source": "src/index.tsx",
"module": "lib/module/index.js", "module": "lib/module/index.js",
"types": "lib/typescript/src/index.d.ts", "types": "lib/typescript/src/index.d.ts",
"files": [ "files": [
"src", "src",
"lib" "lib",
"!**/__tests__"
], ],
"sideEffects": false, "sideEffects": false,
"publishConfig": { "publishConfig": {
@@ -38,15 +40,15 @@
"color": "^3.1.2" "color": "^3.1.2"
}, },
"devDependencies": { "devDependencies": {
"@react-native-community/bob": "^0.10.0", "@react-native-community/bob": "^0.14.3",
"@react-navigation/native": "^5.2.3", "@react-navigation/native": "^5.4.3",
"@types/react": "^16.9.23", "@types/react": "^16.9.34",
"@types/react-native": "^0.61.22", "@types/react-native": "^0.62.7",
"del-cli": "^3.0.0", "del-cli": "^3.0.0",
"react": "~16.9.0", "react": "~16.9.0",
"react-native": "~0.61.5", "react-native": "~0.61.5",
"react-native-gesture-handler": "^1.6.0", "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", "react-native-tab-view": "^2.14.0",
"typescript": "^3.8.3" "typescript": "^3.8.3"
}, },

View File

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

View File

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

View File

@@ -3,6 +3,106 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [5.4.3](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.4.2...@react-navigation/native@5.4.3) (2020-05-23)
**Note:** Version bump only for package @react-navigation/native
## [5.4.2](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.4.1...@react-navigation/native@5.4.2) (2020-05-20)
**Note:** Version bump only for package @react-navigation/native
## [5.4.1](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.4.0...@react-navigation/native@5.4.1) (2020-05-20)
**Note:** Version bump only for package @react-navigation/native
# [5.4.0](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.3.2...@react-navigation/native@5.4.0) (2020-05-16)
### Bug Fixes
* fix types for linking options ([d14f38b](https://github.com/react-navigation/react-navigation/tree/master/packages/native/commit/d14f38b80ad569d5828c1919cea426c659173924))
### Features
* add a PathConfig type ([60cb3c9](https://github.com/react-navigation/react-navigation/tree/master/packages/native/commit/60cb3c9ba76d7ef166c9fe8b55f23728975b5b6e))
## [5.3.2](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.3.1...@react-navigation/native@5.3.2) (2020-05-14)
**Note:** Version bump only for package @react-navigation/native
## [5.3.1](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.3.0...@react-navigation/native@5.3.1) (2020-05-14)
**Note:** Version bump only for package @react-navigation/native
# [5.3.0](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.2.6...@react-navigation/native@5.3.0) (2020-05-10)
### Features
* initialState should take priority over deep link ([039017b](https://github.com/react-navigation/react-navigation/tree/master/packages/native/commit/039017bc2af69120d2d10e8f2c8a62919c37eb65))
## [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) ## [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)

View File

@@ -1,7 +1,7 @@
{ {
"name": "@react-navigation/native", "name": "@react-navigation/native",
"description": "React Native integration for React Navigation", "description": "React Native integration for React Navigation",
"version": "5.2.3", "version": "5.4.3",
"keywords": [ "keywords": [
"react-native", "react-native",
"react-navigation", "react-navigation",
@@ -16,11 +16,13 @@
"homepage": "https://reactnavigation.org", "homepage": "https://reactnavigation.org",
"main": "lib/commonjs/index.js", "main": "lib/commonjs/index.js",
"react-native": "src/index.tsx", "react-native": "src/index.tsx",
"source": "src/index.tsx",
"module": "lib/module/index.js", "module": "lib/module/index.js",
"types": "lib/typescript/src/index.d.ts", "types": "lib/typescript/src/index.d.ts",
"files": [ "files": [
"src", "src",
"lib" "lib",
"!**/__tests__"
], ],
"sideEffects": false, "sideEffects": false,
"publishConfig": { "publishConfig": {
@@ -31,16 +33,16 @@
"clean": "del lib" "clean": "del lib"
}, },
"dependencies": { "dependencies": {
"@react-navigation/core": "^5.4.0" "@react-navigation/core": "^5.8.2"
}, },
"devDependencies": { "devDependencies": {
"@react-native-community/bob": "^0.10.0", "@react-native-community/bob": "^0.14.3",
"@types/react": "^16.9.23", "@types/react": "^16.9.34",
"@types/react-native": "^0.61.22", "@types/react-native": "^0.62.7",
"del-cli": "^3.0.0", "del-cli": "^3.0.0",
"react": "~16.9.0", "react": "~16.9.0",
"react-native": "~0.61.5", "react-native": "~0.61.5",
"react-native-testing-library": "^1.12.0", "react-native-testing-library": "^1.13.2",
"typescript": "^3.8.3" "typescript": "^3.8.3"
}, },
"peerDependencies": { "peerDependencies": {

View File

@@ -22,7 +22,7 @@ type Props = NavigationContainerProps & {
* Container component which holds the navigation state designed for React Native apps. * Container component which holds the navigation state designed for React Native apps.
* This should be rendered at the root wrapping the whole app. * This should be rendered at the root wrapping the whole app.
* *
* @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.initialState Initial state object for the navigation tree. When deep link handling is enabled, this will override deep links when specified. Make sure that you don't specify an `initialState` when there's a deep link (`Linking.getInitialURL()`).
* @param props.onStateChange Callback which is called with the latest navigation state when it changes. * @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.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.linking Options for deep linking. Deep link handling is enabled when this prop is provided, unless `linking.enabled` is `false`.
@@ -46,15 +46,13 @@ const NavigationContainer = React.forwardRef(function NavigationContainer(
...linking, ...linking,
}); });
const [isReady, initialState = rest.initialState] = useThenable( const [isReady, initialState] = useThenable(getInitialState);
getInitialState
);
React.useImperativeHandle(ref, () => refContainer.current); React.useImperativeHandle(ref, () => refContainer.current);
const linkingContext = React.useMemo(() => ({ options: linking }), [linking]); const linkingContext = React.useMemo(() => ({ options: linking }), [linking]);
if (isLinkingEnabled && !isReady) { if (rest.initialState == null && isLinkingEnabled && !isReady) {
// This is temporary until we have Suspense for data-fetching // This is temporary until we have Suspense for data-fetching
// Then the fallback will be handled by a parent `Suspense` component // Then the fallback will be handled by a parent `Suspense` component
return fallback as React.ReactElement; return fallback as React.ReactElement;
@@ -65,7 +63,9 @@ const NavigationContainer = React.forwardRef(function NavigationContainer(
<ThemeProvider value={theme}> <ThemeProvider value={theme}>
<BaseNavigationContainer <BaseNavigationContainer
{...rest} {...rest}
initialState={initialState} initialState={
rest.initialState == null ? initialState : rest.initialState
}
ref={refContainer} ref={refContainer}
/> />
</ThemeProvider> </ThemeProvider>

View File

@@ -1,6 +1,7 @@
import { import {
getStateFromPath as getStateFromPathDefault, getStateFromPath as getStateFromPathDefault,
getPathFromState as getPathFromStateDefault, getPathFromState as getPathFromStateDefault,
PathConfig,
} from '@react-navigation/core'; } from '@react-navigation/core';
export type Theme = { export type Theme = {
@@ -39,7 +40,7 @@ export type LinkingOptions = {
* } * }
* ``` * ```
*/ */
config?: Parameters<typeof getStateFromPathDefault>[1]; config?: PathConfig;
/** /**
* Custom function to parse the URL to a valid navigation state (advanced). * Custom function to parse the URL to a valid navigation state (advanced).
* Only applicable on Web. * Only applicable on Web.

View File

@@ -52,14 +52,18 @@ export default function useLinkBuilder() {
(name: string, params?: object) => { (name: string, params?: object) => {
const { options } = linking; const { options } = linking;
// If we couldn't find a navigation object in context, we're at root if (options?.enabled === false) {
// So we'll construct a basic state object to use return undefined;
}
const state = navigation const state = navigation
? getRootStateForNavigate(navigation, { ? getRootStateForNavigate(navigation, {
index: 0, index: 0,
routes: [{ name, params }], 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, index: 0,
routes: [{ name, params }], routes: [{ name, params }],
}; };

View File

@@ -49,6 +49,14 @@ export default function useLinkProps({ to, action }: Props) {
throw new Error("Couldn't find a navigation object."); throw new Error("Couldn't find a navigation object.");
} }
} else { } 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); linkTo(to);
} }
} }

View File

@@ -95,18 +95,17 @@ export default function useLinking(
} }
} }
const then = (callback: (state: ResultState | undefined) => void) =>
Promise.resolve(callback(value));
// Make it a thenable to keep consistent with the native impl // Make it a thenable to keep consistent with the native impl
const thenable = { const thenable = {
then, then(onfulfilled?: (state: ResultState | undefined) => void) {
return Promise.resolve(onfulfilled ? onfulfilled(value) : value);
},
catch() { catch() {
return thenable; return thenable;
}, },
}; };
return thenable; return thenable as PromiseLike<ResultState | undefined>;
}, []); }, []);
const previousStateLengthRef = React.useRef<number | undefined>(undefined); const previousStateLengthRef = React.useRef<number | undefined>(undefined);

View File

@@ -1,8 +1,6 @@
import * as React from 'react'; import * as React from 'react';
type Thenable<T> = { then(cb: (result: T) => void): void }; export default function useThenable<T>(create: () => PromiseLike<T>) {
export default function useThenable<T>(create: () => Thenable<T>) {
const [promise] = React.useState(create); const [promise] = React.useState(create);
// Check if our thenable is synchronous // Check if our thenable is synchronous

View File

@@ -3,6 +3,49 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [5.4.7](https://github.com/react-navigation/react-navigation/tree/master/packages/routers/compare/@react-navigation/routers@5.4.6...@react-navigation/routers@5.4.7) (2020-05-23)
**Note:** Version bump only for package @react-navigation/routers
## [5.4.6](https://github.com/react-navigation/react-navigation/tree/master/packages/routers/compare/@react-navigation/routers@5.4.5...@react-navigation/routers@5.4.6) (2020-05-20)
**Note:** Version bump only for package @react-navigation/routers
## [5.4.5](https://github.com/react-navigation/react-navigation/tree/master/packages/routers/compare/@react-navigation/routers@5.4.4...@react-navigation/routers@5.4.5) (2020-05-20)
**Note:** Version bump only for package @react-navigation/routers
## [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) ## [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)

View File

@@ -1,7 +1,7 @@
{ {
"name": "@react-navigation/routers", "name": "@react-navigation/routers",
"description": "Routers to help build custom navigators", "description": "Routers to help build custom navigators",
"version": "5.4.2", "version": "5.4.7",
"keywords": [ "keywords": [
"react", "react",
"react-native", "react-native",
@@ -15,11 +15,13 @@
"homepage": "https://reactnavigation.org/docs/custom-routers.html", "homepage": "https://reactnavigation.org/docs/custom-routers.html",
"main": "lib/commonjs/index.js", "main": "lib/commonjs/index.js",
"react-native": "src/index.tsx", "react-native": "src/index.tsx",
"source": "src/index.tsx",
"module": "lib/module/index.js", "module": "lib/module/index.js",
"types": "lib/typescript/src/index.d.ts", "types": "lib/typescript/src/index.d.ts",
"files": [ "files": [
"src", "src",
"lib" "lib",
"!**/__tests__"
], ],
"sideEffects": false, "sideEffects": false,
"publishConfig": { "publishConfig": {
@@ -30,10 +32,10 @@
"clean": "del lib" "clean": "del lib"
}, },
"dependencies": { "dependencies": {
"nanoid": "^3.0.2" "nanoid": "^3.1.5"
}, },
"devDependencies": { "devDependencies": {
"@react-native-community/bob": "^0.10.0", "@react-native-community/bob": "^0.14.3",
"del-cli": "^3.0.0", "del-cli": "^3.0.0",
"typescript": "^3.8.3" "typescript": "^3.8.3"
}, },

View File

@@ -3,6 +3,129 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [5.4.0](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.3.9...@react-navigation/stack@5.4.0) (2020-05-23)
### Bug Fixes
* allow HeaderBackground's subViews to be touchable ([#8314](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/issues/8314)) ([021a911](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/commit/021a9111d76b9b0d940c8829f7caebeb292fec2a))
* don't ignore previous header heights on layout update ([6dd45fc](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/commit/6dd45fcff98a0c222d120d6f76a37130de45b92f))
### Features
* update react-native-safe-area-context to 1.0.0 ([#8182](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/issues/8182)) ([d62fbfe](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/commit/d62fbfe255140f16b182e8b54b276a7c96f2aec6))
## [5.3.9](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.3.8...@react-navigation/stack@5.3.9) (2020-05-20)
**Note:** Version bump only for package @react-navigation/stack
## [5.3.8](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.3.7...@react-navigation/stack@5.3.8) (2020-05-20)
**Note:** Version bump only for package @react-navigation/stack
## [5.3.7](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.3.6...@react-navigation/stack@5.3.7) (2020-05-16)
**Note:** Version bump only for package @react-navigation/stack
## [5.3.6](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.3.5...@react-navigation/stack@5.3.6) (2020-05-15)
### Bug Fixes
* reduce header title margin. fixes [#8267](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/issues/8267) ([d45dbe9](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/commit/d45dbe97dc6625c6a8e80b5e658ab5a95045e5e8))
## [5.3.5](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.3.4...@react-navigation/stack@5.3.5) (2020-05-14)
**Note:** Version bump only for package @react-navigation/stack
## [5.3.4](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.3.3...@react-navigation/stack@5.3.4) (2020-05-14)
**Note:** Version bump only for package @react-navigation/stack
## [5.3.3](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.3.2...@react-navigation/stack@5.3.3) (2020-05-11)
### Bug Fixes
* fix ios transitionspec settle time ([#8028](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/issues/8028)) ([dd7cff2](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/commit/dd7cff201608365a80f1d50a006df3e0d18e94a1))
## [5.3.2](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.3.1...@react-navigation/stack@5.3.2) (2020-05-10)
**Note:** Version bump only for package @react-navigation/stack
## [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) ## [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 **Note:** Version bump only for package @react-navigation/stack

View File

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

View File

@@ -11,8 +11,8 @@ export const TransitionIOSSpec: TransitionSpec = {
damping: 500, damping: 500,
mass: 3, mass: 3,
overshootClamping: true, overshootClamping: true,
restDisplacementThreshold: 0.01, restDisplacementThreshold: 10,
restSpeedThreshold: 0.01, restSpeedThreshold: 10,
}, },
}; };

View File

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

View File

@@ -15,6 +15,7 @@ import {
NavigationHelpers, NavigationHelpers,
StackNavigationState, StackNavigationState,
StackActionHelpers, StackActionHelpers,
RouteProp,
} from '@react-navigation/native'; } from '@react-navigation/native';
export type StackNavigationEventMap = { export type StackNavigationEventMap = {
@@ -45,6 +46,14 @@ export type StackNavigationProp<
> & > &
StackActionHelpers<ParamList>; 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 Layout = { width: number; height: number };
export type GestureDirection = export type GestureDirection =

View File

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

View File

@@ -307,6 +307,8 @@ export default class HeaderSegment extends React.Component<Props, State> {
}) })
: null; : null;
const rightButton = right ? right({ tintColor: headerTintColor }) : null;
return ( return (
<React.Fragment> <React.Fragment>
<Animated.View <Animated.View
@@ -345,8 +347,19 @@ export default class HeaderSegment extends React.Component<Props, State> {
pointerEvents="box-none" pointerEvents="box-none"
style={[ style={[
headerTitleAlign === 'left' headerTitleAlign === 'left'
? { position: 'absolute', left: leftButton ? 72 : 16 } ? {
: { marginHorizontal: 18 }, position: 'absolute',
left: (leftButton ? 72 : 16) + insets.left,
right: (rightButton ? 72 : 16) + insets.right,
}
: {
marginHorizontal:
(leftButton ? 32 : 16) +
(leftButton && headerBackTitleVisible !== false
? 40
: 0) +
Math.max(insets.left, insets.right),
},
titleStyle, titleStyle,
titleContainerStyle, titleContainerStyle,
]} ]}
@@ -359,7 +372,7 @@ export default class HeaderSegment extends React.Component<Props, State> {
style: customTitleStyle, style: customTitleStyle,
})} })}
</Animated.View> </Animated.View>
{right ? ( {rightButton ? (
<Animated.View <Animated.View
pointerEvents="box-none" pointerEvents="box-none"
style={[ style={[
@@ -369,7 +382,7 @@ export default class HeaderSegment extends React.Component<Props, State> {
rightContainerStyle, rightContainerStyle,
]} ]}
> >
{right({ tintColor: headerTintColor })} {rightButton}
</Animated.View> </Animated.View>
) : null} ) : null}
</View> </View>

View File

@@ -2,7 +2,7 @@ import * as React from 'react';
import { Animated, StyleSheet, Platform } from 'react-native'; import { Animated, StyleSheet, Platform } from 'react-native';
import { useTheme } from '@react-navigation/native'; import { useTheme } from '@react-navigation/native';
type Props = React.ComponentProps<typeof Animated.Text> & { type Props = Omit<React.ComponentProps<typeof Animated.Text>, 'key'> & {
tintColor?: string; tintColor?: string;
children?: string; children?: string;
}; };

View File

@@ -299,7 +299,7 @@ export default class CardStack extends React.Component<Props, State> {
props.insets, props.insets,
state.descriptors, state.descriptors,
layout, layout,
{} state.headerHeights
), ),
}; };
}); });

2464
yarn.lock

File diff suppressed because it is too large Load Diff