Compare commits

...

234 Commits

Author SHA1 Message Date
Satyajit Sahoo
b5d539a11b chore: publish
- @react-navigation/bottom-tabs@6.0.0-next.8
 - @react-navigation/core@6.0.0-next.5
 - @react-navigation/devtools@6.0.0-next.5
 - @react-navigation/drawer@6.0.0-next.7
 - @react-navigation/elements@1.0.0-next.7
 - @react-navigation/material-bottom-tabs@6.0.0-next.5
 - @react-navigation/material-top-tabs@6.0.0-next.6
 - @react-navigation/native@6.0.0-next.5
 - @react-navigation/stack@6.0.0-next.12
2021-05-09 07:04:29 +02:00
Satyajit Sahoo
7da45e1e89 fix: fix type annotations for useNavigation 2021-05-09 07:03:38 +02:00
Satyajit Sahoo
47134d7052 chore: publish
- @react-navigation/bottom-tabs@6.0.0-next.7
 - @react-navigation/core@6.0.0-next.4
 - @react-navigation/devtools@6.0.0-next.4
 - @react-navigation/drawer@6.0.0-next.6
 - @react-navigation/elements@1.0.0-next.6
 - @react-navigation/material-bottom-tabs@6.0.0-next.4
 - @react-navigation/material-top-tabs@6.0.0-next.5
 - @react-navigation/native@6.0.0-next.4
 - @react-navigation/stack@6.0.0-next.11
2021-05-09 06:45:00 +02:00
Satyajit Sahoo
a369ba3645 fix: make sure disabling react-native-screens works 2021-05-09 06:43:48 +02:00
Satyajit Sahoo
4294318210 refactor: use react-native-screens for web as well 2021-05-09 06:43:09 +02:00
Wojciech Lewicki
8da4c58065 fix: enable screens only on supported platforms (#9494) 2021-05-09 06:42:56 +02:00
Satyajit Sahoo
7809bc0650 chore: upgrade expo to stable version 2021-05-09 06:12:08 +02:00
Satyajit Sahoo
8f2b95ca97 chore: drop support for older versions of react-native-screens 2021-05-09 06:03:38 +02:00
Jesse Katsumata
6866ad2cda chore: update @types/react-native and remove ts-ignore (#9518) 2021-05-09 05:45:52 +02:00
Satyajit Sahoo
459fd27050 fix: animate pressable opacity 2021-05-09 05:41:27 +02:00
Satyajit Sahoo
14786594c0 feat: support navigate-like object in Link 2021-05-09 04:56:45 +02:00
Satyajit Sahoo
b28bfddc17 feat: add ability to specify root param list 2021-05-09 04:33:56 +02:00
Satyajit Sahoo
1a6aebefcb feat: add a new component to group multiple screens with common options 2021-05-09 01:14:24 +02:00
Satyajit Sahoo
33b2dbb85c chore: bump node versions on CI 2021-05-09 00:26:16 +02:00
Satyajit Sahoo
4bb0b43f1a feat: automatically set headerMode if it's modal presentation style 2021-05-08 23:20:12 +02:00
Satyajit Sahoo
260da9b103 docs: update comments for various options 2021-05-08 21:57:14 +02:00
Satyajit Sahoo
9ac709ea1e refactor: drop mode prop in favor of animationPresentation option
BREAKING CHANGE: This drops the mode prop on the navigator in favor of a per-screen option animationPresentation
2021-05-08 07:04:25 +02:00
Satyajit Sahoo
60fa3b9be9 feat: support mixing regular and modal presentation in same stack 2021-05-08 07:04:25 +02:00
Satyajit Sahoo
cf6a9e614d chore: publish
- @react-navigation/bottom-tabs@6.0.0-next.6
 - @react-navigation/core@6.0.0-next.3
 - @react-navigation/devtools@6.0.0-next.3
 - @react-navigation/drawer@6.0.0-next.5
 - @react-navigation/elements@1.0.0-next.5
 - @react-navigation/material-bottom-tabs@6.0.0-next.3
 - @react-navigation/material-top-tabs@6.0.0-next.4
 - @react-navigation/native@6.0.0-next.3
 - @react-navigation/stack@6.0.0-next.10
2021-05-01 23:53:18 +02:00
Satyajit Sahoo
0ecd112ec9 feat: add helper and hook for container ref 2021-05-01 23:51:10 +02:00
Satyajit Sahoo
def7c03d7d feat: add a CompositeScreenProps type 2021-05-01 21:58:33 +02:00
Satyajit Sahoo
83242a7bef refactor: tweak the types 2021-05-01 21:32:10 +02:00
Satyajit Sahoo
f48303f036 chore: add an action to label sponsors 2021-04-24 17:37:03 +02:00
Satyajit Sahoo
dc779b8d82 chore: fix string interpolation in first pr action 2021-04-19 15:21:54 +02:00
Satyajit Sahoo
d7401b0200 chore: use pull_request_target for first pr action 2021-04-19 00:42:43 +02:00
Satyajit Sahoo
372d5921b8 chore: fix typo 2021-04-17 15:07:14 +02:00
Satyajit Sahoo
f940153d02 chore: add stale bit to close issues without repro 2021-04-16 22:16:06 +02:00
Satyajit Sahoo
aef35c5046 chore: publish
- @react-navigation/bottom-tabs@6.0.0-next.5
2021-04-16 12:40:53 +02:00
Satyajit Sahoo
338ed6ff07 fix: update tab bar height correctly. fixes #9296 2021-04-16 12:39:17 +02:00
Satyajit Sahoo
47e371609d chore: use locally installed expo-cli 2021-04-14 22:34:20 +02:00
Satyajit Sahoo
ef42fa2d36 chore: add 'needs repro' label automatically 2021-04-14 22:05:43 +02:00
Satyajit Sahoo
cc5d195f9a chore: publish
- @react-navigation/material-top-tabs@6.0.0-next.3
2021-04-08 07:41:46 +02:00
Satyajit Sahoo
7e10bcd089 refactor: move lazyPlaceholder to options 2021-04-08 07:39:36 +02:00
Satyajit Sahoo
c8dd70a033 chore: publish
- @react-navigation/bottom-tabs@6.0.0-next.4
 - @react-navigation/core@6.0.0-next.2
 - @react-navigation/devtools@6.0.0-next.2
 - @react-navigation/drawer@6.0.0-next.4
 - @react-navigation/elements@1.0.0-next.4
 - @react-navigation/material-bottom-tabs@6.0.0-next.2
 - @react-navigation/material-top-tabs@6.0.0-next.2
 - @react-navigation/native@6.0.0-next.2
 - @react-navigation/routers@6.0.0-next.2
 - @react-navigation/stack@6.0.0-next.9
2021-04-08 06:19:12 +02:00
Satyajit Sahoo
277fec481b chore: fix dist-tag for publishing 2021-04-08 06:14:41 +02:00
Satyajit Sahoo
3241190b19 fix: fix drawer overlay on web 2021-04-08 05:46:18 +02:00
Satyajit Sahoo
ef7370b215 chore: configure reanimated 2 2021-04-04 06:55:37 +02:00
Satyajit Sahoo
1149e718c1 refactor: add reanimated 2 implementation for drawer 2021-04-04 06:43:12 +02:00
Satyajit Sahoo
d85a4fd8ed chore: upgrade expo to SDK 41 2021-04-04 06:41:26 +02:00
Satyajit Sahoo
b89396888f fix: don't handle back button with permanent drawer 2021-04-04 01:09:05 +02:00
WoLewicki
c38906a7a0 fix: properly resolve initialRouteNames 2021-04-04 01:08:57 +02:00
Janic Duplessis
d87857e5d9 fix: remove calls to removed Keyboard.removeListener in useIsKeyboardShown (#9457) 2021-04-04 01:06:55 +02:00
Satyajit Sahoo
5ae0badc44 fix: only handle back button in drawer when focused 2021-04-02 05:21:32 +02:00
Jemmy Phan
84020a0b27 feat: improve useNavigationState typing (#9464) 2021-03-29 05:42:19 +02:00
Satyajit Sahoo
5473982859 chore: publish
- @react-navigation/bottom-tabs@6.0.0-next.3
 - @react-navigation/drawer@6.0.0-next.3
 - @react-navigation/elements@1.0.0-next.3
 - @react-navigation/stack@6.0.0-next.8
2021-03-22 19:29:22 +01:00
Satyajit Sahoo
de805a3ebf fix: use tab role on Android for accessibility 2021-03-22 19:21:47 +01:00
Satyajit Sahoo
cbaabc1288 feat: add a Background component 2021-03-22 19:03:09 +01:00
Satyajit Sahoo
dd48fe9b15 chore: publish
- @react-navigation/stack@6.0.0-next.7
2021-03-22 02:16:16 +01:00
Satyajit Sahoo
48851c9ebd refactor: make gestureResponseDistance a number
BREAKING CHANGE: now we need to pass a number instead of an object
2021-03-22 02:15:28 +01:00
Satyajit Sahoo
197c916a23 chore: publish
- @react-navigation/stack@6.0.0-next.6
2021-03-14 16:44:53 +01:00
Satyajit Sahoo
31caaf3071 refactor: set headerMode to screen by default when custom header is specified 2021-03-14 16:43:51 +01:00
Satyajit Sahoo
5bcce9926a chore: publish
- @react-navigation/stack@6.0.0-next.5
2021-03-14 16:28:11 +01:00
Satyajit Sahoo
aacc1b525d refactor: move headerMode to options
BREAKING CHANGE: headerMode is now moved to options instead of props
2021-03-14 16:27:53 +01:00
Satyajit Sahoo
3ad2bcbaf8 fix: consider header colors when managing statusbar 2021-03-13 17:38:46 +01:00
Satyajit Sahoo
faee245d2e fix: consider header colors when managing statusbar 2021-03-13 17:37:43 +01:00
Satyajit Sahoo
e1ab06d3d7 chore: publish
- @react-navigation/stack@6.0.0-next.4
2021-03-12 23:09:39 +01:00
Satyajit Sahoo
a204edd012 fix: add special statusbar handling to modal presentation 2021-03-12 23:08:13 +01:00
Satyajit Sahoo
78b00bd814 chore: update expo-cli 2021-03-12 03:30:37 +01:00
Satyajit Sahoo
923722cbb0 chore: publish
- @react-navigation/bottom-tabs@6.0.0-next.2
 - @react-navigation/drawer@6.0.0-next.2
 - @react-navigation/elements@1.0.0-next.2
 - @react-navigation/stack@6.0.0-next.3
2021-03-12 03:04:24 +01:00
Satyajit Sahoo
2c1adc9043 refactor: rename ResourceSavingScene to ResourceSavingView 2021-03-12 03:02:07 +01:00
Satyajit Sahoo
40439ccb42 fix: use theme in PlatformPressable 2021-03-12 02:54:02 +01:00
Satyajit Sahoo
24b3f739da feat: return nearest parent header height for useHeaderHeight 2021-03-12 01:59:40 +01:00
Satyajit Sahoo
2c8401d5cb feat: export drawer button 2021-03-12 01:43:57 +01:00
Satyajit Sahoo
6cc463f20d refactor: drop safeAreaInsets option
Custom insets can be provided by wrapping the navigator in a SafeAreaProvider, so we don't need an option.
2021-03-12 01:15:01 +01:00
Satyajit Sahoo
e6c6cc8331 chore: publish
- @react-navigation/stack@6.0.0-next.2
2021-03-11 13:14:07 +01:00
Adam Trzciński
8a6511c491 fix: respect headerStatusBarHeight option in Stack (#9405)
Co-authored-by: Satyajit Sahoo <satyajit.happy@gmail.com>
2021-03-11 13:12:54 +01:00
Satyajit Sahoo
bfd6eb79c3 chore: publish
- @react-navigation/bottom-tabs@6.0.0-next.1
 - @react-navigation/core@6.0.0-next.1
 - @react-navigation/devtools@6.0.0-next.1
 - @react-navigation/drawer@6.0.0-next.1
 - @react-navigation/elements@1.0.0-next.1
 - @react-navigation/material-bottom-tabs@6.0.0-next.1
 - @react-navigation/material-top-tabs@6.0.0-next.1
 - @react-navigation/native@6.0.0-next.1
 - @react-navigation/routers@6.0.0-next.1
 - @react-navigation/stack@6.0.0-next.1
2021-03-10 05:13:55 +01:00
Satyajit Sahoo
ebcd077626 docs: update links in READMEs 2021-03-10 05:12:24 +01:00
Satyajit Sahoo
5d0c7ea6d4 chore: allow main branch for publishing 2021-03-10 05:09:21 +01:00
Satyajit Sahoo
038eb87c42 fix: remove use of deprecated currentlyFocusedField 2021-03-10 05:06:26 +01:00
Satyajit Sahoo
72f90b50d2 fix: fix peer dep versions 2021-03-09 20:10:12 +01:00
Satyajit Sahoo
0fce8eb2a1 chore: publish
- @react-navigation/bottom-tabs@6.0.0-next.0
 - @react-navigation/core@6.0.0-next.0
 - @react-navigation/devtools@6.0.0-next.0
 - @react-navigation/drawer@6.0.0-next.0
 - @react-navigation/elements@1.0.0-next.0
 - @react-navigation/material-bottom-tabs@6.0.0-next.0
 - @react-navigation/material-top-tabs@6.0.0-next.0
 - @react-navigation/native@6.0.0-next.0
 - @react-navigation/routers@6.0.0-next.0
 - @react-navigation/stack@6.0.0-next.0
2021-03-09 17:51:56 +01:00
Satyajit Sahoo
040581af2d chore: change version of elements to make lerna happy 2021-03-09 17:43:32 +01:00
Satyajit Sahoo
8e0ae5fc79 chore: add deprecation warnings 2021-03-09 17:27:40 +01:00
Satyajit Sahoo
22610014b3 feat: upgrade to latest react-native-tab-view using ViewPager 2021-03-09 04:00:45 +01:00
Satyajit Sahoo
70ddcce76f chore: add a tsconfig to workaround expo-cli adding its own 2021-03-07 18:14:11 +01:00
Satyajit Sahoo
cfd1c9b6ef feat: move lazy to options for material-top-tabs
BREAKING CHANGE: The lazy prop now can be configured per screen instead of for the whole navigator. To keep previous behavior, you can specify it in screenOptions
2021-03-07 00:34:05 +01:00
Satyajit Sahoo
9fce3a433c chore: upgrade dependencies 2021-03-07 00:28:13 +01:00
Satyajit Sahoo
e127c84ae9 refactor: move tabBarOptions to options for material top tabs 2021-03-06 22:49:42 +01:00
Satyajit Sahoo
2bc4882692 fix: show a missing icon symbol instead of empty area in bottom tab bar 2021-03-06 19:00:38 +01:00
Satyajit Sahoo
36a9b4f866 fix: check duplicate names only for immediate nested screens 2021-03-05 18:46:54 +01:00
Satyajit Sahoo
13d85530ae fix: show redbox instead of crash if navigation isn't initialized 2021-03-05 13:44:17 +01:00
Satyajit Sahoo
86e64fdcd8 feat: associate path with the route it opens when deep linking (#9384)
This commit adds a new optional property on the `route` object called `path`.
This property will be added if the screen was opened from a deep link.

Having this property helps with few things:

- Preserve the URL when the path was unmatched, e.g. 404 routes
- Expose the path to the user so they could handle it manually if needed, e.g. open in a webview
- Avoid changing URL if state to path doesn't match current path, e.g. if orders of params change

Fixes #9102
2021-03-05 12:20:35 +01:00
Satyajit Sahoo
585ae7d7f6 chore: upgrade expo-cli 2021-03-05 02:40:08 +01:00
Satyajit Sahoo
7ac59e6109 chore: fix crash due to asyncstorage on web 2021-03-05 02:38:01 +01:00
Satyajit Sahoo
7c92cd9ca9 chore: upgrade playwright 2021-03-05 02:26:22 +01:00
Satyajit Sahoo
c725ac3976 chore: fix dependencies of elements package 2021-03-04 22:55:34 +01:00
Satyajit Sahoo
f49b0d3a3b refactor: migrate to Pressable 2021-02-22 00:47:04 +01:00
Satyajit Sahoo
205f297d07 chore: fix warnings in tests 2021-02-22 00:15:54 +01:00
Satyajit Sahoo
0fc718b49d chore: upgrade @types/react-native 2021-02-21 23:39:54 +01:00
Satyajit Sahoo
26351254fc chore: fix warnings from module-resolver 2021-02-21 23:32:18 +01:00
Satyajit Sahoo
d6466b7a4b feat: allow returning null or undefined to skip actions with dispatch 2021-02-21 21:27:26 +01:00
Satyajit Sahoo
02a031e46e feat: warn on duplicate screen names across navigators 2021-02-21 21:19:52 +01:00
Satyajit Sahoo
61c6bb01b9 fix: address breaking change in react-native for Linking 2021-02-21 15:42:02 +01:00
Satyajit Sahoo
61e653d7c4 fix: fix getId being called for incorrect routes. closes #9343 2021-02-21 15:38:53 +01:00
Satyajit Sahoo
ca1c1622be chore: add missing elements dependency for navigators 2021-02-13 01:13:17 +01:00
Satyajit Sahoo
9a0774ec35 chore: fix icon name for tab examples 2021-02-11 18:59:50 +01:00
Satyajit Sahoo
55e6f3dd33 refactor: move Header to elements package 2021-02-11 18:56:40 +01:00
Satyajit Sahoo
86a2b09a43 refactor: refactor bottom tab view to a function component 2021-02-05 16:10:15 +01:00
Satyajit Sahoo
509ca49b64 refactor: move more header stuff to elements package 2021-02-04 15:07:22 +01:00
Satyajit Sahoo
07ba7a9687 feat: initial implementation of @react-navigation/elements 2021-01-29 20:11:23 +01:00
Satyajit Sahoo
c345ef1d0b fix: fix transparent modal on web 2021-01-29 17:45:56 +01:00
Satyajit Sahoo
865d8b3e51 fix: fix drawer screen content not being interactable on Android 2021-01-29 17:45:43 +01:00
Satyajit Sahoo
af53dd6548 fix: fix pointerEvents in ResourceSavingScene
fixes #9241, fixes #9242
2021-01-29 17:45:31 +01:00
Petra Daneva
52dbe4bd66 feat: add pressColor and pressOpacity props to drawerItem (#8834)
Co-authored-by: Petra Daneva <p.daneva@dineout.bg>
Co-authored-by: Satyajit Sahoo <satyajit.happy@gmail.com>
2021-01-29 17:45:00 +01:00
sharifhh
3367ddf9df fix: fix StackRouter incorrectly handling invalid route if key is present 2021-01-29 17:35:32 +01:00
Satyajit Sahoo
b735de153c fix: fix drawer and bottom tabs not being visible on web. closes #9225 2021-01-29 17:35:09 +01:00
Satyajit Sahoo
fd034fea35 fix: normalize prefix when parsing. fixes #9081 2021-01-22 13:41:28 +01:00
Satyajit Sahoo
cda6397b89 refactor: don't use a boolean for drawer status
BREAKING CHANGE: Drawer status is now a union ('open', 'closed') instead of a boolean. This will let us implement more types of status in future.

Following this the following exports have been renamed as well:
- getIsDrawerOpenFromState -> getDrawerStatusFromState
- useIsDrawerOpen -> useDrawerStatus
2021-01-15 12:49:20 +01:00
Satyajit Sahoo
79a85a431c refactor: don't use absolute position for header
BREAKING CHANGE: We now use flexbox for header elements which could break some existing style code which relied on absolute positioning.
2021-01-15 12:06:59 +01:00
Satyajit Sahoo
22a8afac74 feat: don't hardcode header back test ID
BREAKING CHANGE: Now the back button test ID can be customized using headerBackTestID option
2021-01-15 11:35:29 +01:00
Satyajit Sahoo
4954d6aae3 fix: enable detachInactiveScreens by default on web for better a11y 2021-01-14 14:53:35 +01:00
Satyajit Sahoo
6c5e196459 chore: wrap example list in SafeAreaView 2021-01-14 14:42:33 +01:00
Satyajit Sahoo
e86ce7bcb7 chore: update Reactiflux instructions 2021-01-14 12:45:53 +01:00
youngjuning
6c3cccf877 feat: export TransitionPreset for custom TransitionPresets (#9173) 2021-01-14 12:39:15 +01:00
Dulmandakh
db6bdb0430 chore: set displayName for LinkingContext (#9202)
Co-authored-by: Satyajit Sahoo <satyajit.happy@gmail.com>
2021-01-14 12:30:46 +01:00
Dulmandakh
c9d31bafdb chore: set displayName for ThemeContext (#9201)
Co-authored-by: Satyajit Sahoo <satyajit.happy@gmail.com>
2021-01-14 12:30:36 +01:00
Satyajit Sahoo
52dd4e7ac9 fix: support sync getInitialURL in native useLinking 2021-01-14 03:34:41 +01:00
Satyajit Sahoo
15b8bb3458 feat: add a way to specify an unique ID for screens
With this, the user will be able to specify a `getId` function for their screens which returns an unique ID to use for the screen:

```js
<Stack.Screen
  name="Profile"
  component={ProfileScreen}
  getId={({ params }) => params.userId}
/>
```

This is an alternative to the `key` option in `navigate` with several advantages:

- Often users specify a key that's dependent on data already in params, such as `userId`. So it's much easier to specify it one place rather than at every call site.
- Users won't need to deal with generating a unique key for routes manually.
- This will work with other actions such as `push`, and not just navigate.
- With this, it'll be possible to have multiple instances of the screen even if you use `navigate`, which may be desirable in many cases (such as profile screens).
2021-01-14 03:33:44 +01:00
Satyajit Sahoo
802db004ae chore: upgrade dependencies 2021-01-08 13:59:24 +01:00
Satyajit Sahoo
b376e9c5ed fix: fix typo for default prop in drawer 2020-12-10 01:49:27 +01:00
Satyajit Sahoo
03ba1f2930 fix: fix default screen options not being respected 2020-12-04 00:34:16 +01:00
Satyajit Sahoo
69d333f6c2 fix: fix initial metrics on server 2020-12-03 02:19:54 +01:00
Satyajit Sahoo
068a9a456c feat: move lazy to options for bottom-tabs and drawer
BREAKING CHANGE: The lazy prop now can be configured per screen instead of for the whole navigator. To keep previous behavior, you can specify it in screenOptions
2020-12-03 02:05:13 +01:00
Satyajit Sahoo
65d8b487f8 chore: upgrade to typescript 4.1 2020-12-03 01:56:28 +01:00
Satyajit Sahoo
12b893d7ca fix: drop usage of Dimensions in favor of metrics from safe-area-context 2020-12-03 01:40:00 +01:00
Satyajit Sahoo
73aad00f19 chore: fix listing packages in metro config 2020-12-03 01:00:28 +01:00
Satyajit Sahoo
f137a84a80 chore: tweak comment for getParent 2020-12-02 21:15:29 +01:00
Satyajit Sahoo
8bdc6c6b9b fix: default to backBehavior: firstRoute for TabRouter
BREAKING CHANGE: Returning to first route after pressing back seems more common in apps. This commit changes the default for tab and drawer navigators to follow this common practice. To preserve previous behavior, you can pass backBehavior=history to tab and drawer navigators.
2020-12-02 21:06:32 +01:00
Satyajit Sahoo
7c1cd261bf feat: add a new backBehavior: firstRoute for TabRouter 2020-12-02 20:35:09 +01:00
Satyajit Sahoo
c361795d97 fix: print an error when passing a second argument to useFocusEffect 2020-12-02 20:27:34 +01:00
Satyajit Sahoo
5a9a1edae7 feat: stop exporting useLinking hook
BREAKING CHANGE: we have added linking prop for the same use case which is easier to use. so no need to export useLinking anymore.
2020-11-29 17:59:17 +01:00
Satyajit Sahoo
33b474ef64 chore: fix typo in workflow 2020-11-29 17:56:21 +01:00
Satyajit Sahoo
ca50b172f9 chore: bump github scripts 2020-11-29 17:56:13 +01:00
Satyajit Sahoo
d72a7bdea2 chore: add a label for first pull request 2020-11-29 17:56:01 +01:00
Satyajit Sahoo
a93e1d85f0 chore: only count repro from user's github repos 2020-11-29 17:55:51 +01:00
Satyajit Sahoo
14ac256af3 fix: force dismiss keyboard if there was no gesture
closes #9078
2020-11-21 05:28:25 +01:00
Satyajit Sahoo
b82a9126bb fix: fix incorrect state change events in independent nested container
fixes #9080
2020-11-20 18:10:35 +01:00
Satyajit Sahoo
b0416957bc chore: only match repo links for GitHub in action 2020-11-20 17:20:18 +01:00
Satyajit Sahoo
15e5678037 refactor: move drawerContentOptions to options
BREAKING CHANGE:  This commit moves options from `drawerContentOptions` to regular `options` in order to reduce confusion between the two, as well as to make it more flexible to configure the drawer on a per screen basis.
2020-11-19 03:57:48 +01:00
Satyajit Sahoo
852f2f0f19 chore: remove compat package 2020-11-17 20:36:06 +01:00
Satyajit Sahoo
c85f2ff47a feat: add an option to specify default options for the navigator 2020-11-17 20:25:49 +01:00
Satyajit Sahoo
6f326cf0c5 feat: add a slide animation for modals on Android 2020-11-16 18:09:58 +01:00
Satyajit Sahoo
422dfc55dd chore: fix typo in github workflow 2020-11-16 15:36:30 +01:00
Satyajit Sahoo
2f66e5e773 chore: add action to check for repro 2020-11-16 15:36:20 +01:00
Satyajit Sahoo
f7ff1adee7 refactor: move tabBarOptions to options for bottom tabs
BREAKING CHANGE: This commit moves options from `tabBarOptions` to regular `options` in order to reduce confusion between the two, as well as to make it more flexible to configure the tab bar based on a per screen basis.
2020-11-13 21:23:18 +01:00
Satyajit Sahoo
ddf27bf41a refactor: don't use deprecated APIs from react-native-safe-area-context
BREAKING CHANGE: We now require newer versions of safe area context library.
2020-11-13 08:06:14 +01:00
Satyajit Sahoo
a97a43aa1d refactor: drop support for tabBarVisible option
BREAKING CHANGE: We need to add support for specifying style for tab bar in options to support the use cases which need this.
2020-11-13 07:23:59 +01:00
Satyajit Sahoo
376db8be5e chore: remove peer dep on masked view to make it optional 2020-11-13 07:12:33 +01:00
Satyajit Sahoo
227f133536 fix: drop dangerously prefix from getState and getParent
BREAKING CHANGE
2020-11-13 04:48:14 +01:00
Jesse Katsumata
8773dcb80f chore: replace community maskedview (#9058)
replaced `@react-native-community/masked-view` with `@react-native-masked-view/masked-view` due to namespace change.
2020-11-12 06:21:11 +01:00
Satyajit Sahoo
3ec1a862b2 refactor: default to sliding drawer on iOS 2020-11-12 04:00:11 +01:00
Satyajit Sahoo
5648e1a7b3 refactor: drop drawerOpen and drawerClose events
BREAKING CHANGE: drawer's status can be queried through the isDrawerOpen hook. no need for the events
2020-11-12 03:58:52 +01:00
Satyajit Sahoo
21a11543bf fix: add missing helper types in descriptors 2020-11-12 03:54:41 +01:00
Satyajit Sahoo
4cad132c2c refactor: simplify props for stack and drawer headers
BREAKING CHANGE: Previously, the stack header accepted scene and previous scene which contained things such as descriptor, navigation prop, progress etc. The commit simplifies them to pass `route`, `navigation`, `options` and `progress` directly to the header. Similaryly, the `previous` argument now contains `options`, `route` and `progress`.
2020-11-12 02:47:51 +01:00
Satyajit Sahoo
d5c091cbcf refactor: add route prop to the descriptor 2020-11-12 02:46:25 +01:00
Satyajit Sahoo
8a63f193bf feat: use modal presentation style for modals on iOS by default 2020-11-12 01:39:42 +01:00
Satyajit Sahoo
0c55803b32 fix: drop support for headerMode='none'
BREAKING CHANGE: The header can be hidden with `headerShown: false` already. Using both `headerMode='none'` and `headerShown` together causes confusion. So it's time to drop the unnecessary `headerMode` prop.
2020-11-12 00:58:25 +01:00
Satyajit Sahoo
0e13e8d23c fix: drop support for legacy linking config
BREAKING CHANGE: This commit drops support for legacy linking config which allowed screens to be specified without the screens property in the config.
2020-11-11 23:33:18 +01:00
Satyajit Sahoo
366d0181dc fix: don't merge params on navigation
BREAKING CHANGE: Previous versions of React Navigation merged params on navigation which caused confusion. This commit changes params not to be merged.

The old behaviour can still be achieved by passing `merge: true` explicitly:

```js
CommonActions.navigate({
  name: 'bar',
  params: { fruit: 'orange' },
  merge: true,
})
```
`initialParams` specified for the screen are always merged.
2020-11-11 23:16:03 +01:00
Satyajit Sahoo
ebab518352 fix: remove the state property from route prop
BREAKING CHANGE: any code which relies on `route.state` will break.

Previous versions printed a warning on accessing `route.state`. This commit removes the property entirely. Accessing this property isn't safe since child navigator state isn't gurranteed to be in sync with parent navigator state and cause subtle bugs in apps.
2020-11-11 22:20:47 +01:00
Satyajit Sahoo
b70ba66f24 chore: prepare for 6.x alpha 2020-11-11 22:07:33 +01:00
Satyajit Sahoo
a2337648bf chore: publish
- @react-navigation/bottom-tabs@5.11.1
 - @react-navigation/compat@5.3.9
 - @react-navigation/core@5.14.3
 - @react-navigation/devtools@5.1.17
 - @react-navigation/drawer@5.11.2
 - @react-navigation/material-bottom-tabs@5.3.9
 - @react-navigation/material-top-tabs@5.3.9
 - @react-navigation/native@5.8.9
 - @react-navigation/stack@5.12.6
2020-11-10 20:41:26 +01:00
Satyajit Sahoo
8f764d8b08 fix: improve the error message for incorrect screen configuration 2020-11-10 20:29:59 +01:00
Satyajit Sahoo
f8e998b10c refactor: simplify getStateFromPath 2020-11-10 19:44:00 +01:00
Satyajit Sahoo
da35085f1e fix: make sure inactive screen don't increase scroll area on web 2020-11-10 18:21:36 +01:00
Satyajit Sahoo
1f5fb5481a chore: publish
- @react-navigation/drawer@5.11.1
2020-11-09 20:40:11 +01:00
Satyajit Sahoo
18bbd177d9 fix: provide correct context to drawe header 2020-11-09 20:37:26 +01:00
Satyajit Sahoo
151055cf5a chore: publish
- @react-navigation/bottom-tabs@5.11.0
 - @react-navigation/compat@5.3.8
 - @react-navigation/core@5.14.2
 - @react-navigation/devtools@5.1.16
 - @react-navigation/drawer@5.11.0
 - @react-navigation/material-bottom-tabs@5.3.8
 - @react-navigation/material-top-tabs@5.3.8
 - @react-navigation/native@5.8.8
 - @react-navigation/routers@5.6.2
 - @react-navigation/stack@5.12.5
2020-11-09 20:17:39 +01:00
Satyajit Sahoo
52172453df fix: try fixing drawer blink on Android 2020-11-09 20:05:27 +01:00
Satyajit Sahoo
7bc385e4f3 chore: show header in drawer by default 2020-11-09 19:36:36 +01:00
Satyajit Sahoo
6ac4d40140 feat: add a tabBarBadgeStyle option to customize the badge 2020-11-09 19:28:49 +01:00
Satyajit Sahoo
dbe961ba5b feat: add option to show a header in drawer navigator screens
This commit adds new `header` and `headerShown` options in drawer navigator to be able to show a header, along with a bunch of header related options similar to stack navigator.

Historically, we have suggested to nest a stack navigator inside drawer navigator to render a header. While it works, it's not efficient to nest an entire navigator just for a header, considering it adds a lot of additional overhead from the code to handle animations, gestures etc. which won't ever be run in this case. It also increases the view hierarchy which has negative impacts on performance on Android, and could cause content not to render on older iOS devices.

Another issue with the approach is that since drawer navigator is at the root in this setup, it's possible to open drawer from every screen in the stack navigator, which usually isn't the expected behaviour. It's necessary to write additional code to disable the gesture to open drawer in all screens but first.

In addition, users also need to add a custom drawer icon to the header manually to be able to toggle the drawer

If drawer navigator could render its own header we'd avoid all these shortcomings as well as make the code simpler.

For now, I have implemented a new `Header` component in drawer since it's way simpler than stack navigator header. Though we may consider creating a shared UI package and add such components there which all our navigators could use.

The `Header` includes a button to toggle the drawer by default, and supports customization options such as showing custom left/right/title components. For this commit, I have kept `headerShown` to `false` by default coz I wasn't sure if it'd be a breaking change to start showing headers in drawers. Probably we can toggle it to `true` by default in next major.
2020-11-09 18:52:24 +01:00
Satyajit Sahoo
05d4e4d3be refactor: minor tweak 2020-11-09 02:02:43 +01:00
Satyajit Sahoo
48b2e77730 fix: throw if the same pattern resolves to multiple screens 2020-11-09 01:56:30 +01:00
Satyajit Sahoo
e08c91ff0a feat: add a hook to get bottom tab bar height
Usage:

```js
import { useBottomTabBarHeight } from '@react-navigation/stack';

// ...

const headerHeight = useBottomTabBarHeight();
```

closes #8037, closes #8536
2020-11-08 20:08:50 +01:00
Satyajit Sahoo
5bd682f0bf feat: add a getIsDrawerOpenFromState utility to drawer 2020-11-08 17:51:13 +01:00
Satyajit Sahoo
50a161dc3d chore: publish
- @react-navigation/bottom-tabs@5.10.7
 - @react-navigation/compat@5.3.7
 - @react-navigation/core@5.14.1
 - @react-navigation/devtools@5.1.15
 - @react-navigation/drawer@5.10.7
 - @react-navigation/material-bottom-tabs@5.3.7
 - @react-navigation/material-top-tabs@5.3.7
 - @react-navigation/native@5.8.7
 - @react-navigation/routers@5.6.1
 - @react-navigation/stack@5.12.4
2020-11-08 09:06:37 +01:00
Satyajit Sahoo
360b0e9958 fix: tweak error message when navigator has non-screen children 2020-11-07 16:43:45 +01:00
Satyajit Sahoo
e50c8aa942 refactor: use a regular action for 'resetRoot'
Previously, 'resetRoot' directly performed a 'setState' on the container instead of dispatching an action. This meant that hooks such as listener for 'preventRemove' won't be notified by it. This commit changes it to dispatch a regular 'RESET' action which will behave the same as other actions.
2020-11-07 15:55:48 +01:00
Satyajit Sahoo
8f0efc8db5 fix: don't hide child header automatically in stack 2020-11-07 14:39:23 +01:00
Satyajit Sahoo
7de6677e72 chore: fix statusbar height in modal example 2020-11-07 00:34:25 +01:00
Satyajit Sahoo
1dad338b7a chore: publish
- @react-navigation/bottom-tabs@5.10.6
 - @react-navigation/compat@5.3.6
 - @react-navigation/core@5.14.0
 - @react-navigation/devtools@5.1.14
 - @react-navigation/drawer@5.10.6
 - @react-navigation/material-bottom-tabs@5.3.6
 - @react-navigation/material-top-tabs@5.3.6
 - @react-navigation/native@5.8.6
 - @react-navigation/routers@5.6.0
 - @react-navigation/stack@5.12.3
2020-11-04 22:37:22 +01:00
Satyajit Sahoo
ce7d20e336 fix: disable react-native-screens on iOS for older versions 2020-11-04 22:36:43 +01:00
Satyajit Sahoo
e3e58c2d89 feat: add a NavigatorScreenParams type. closes #6931 2020-11-04 22:36:43 +01:00
Satyajit Sahoo
cb2e744dce fix: always respect key in the route object when generating action 2020-11-04 22:36:43 +01:00
Satyajit Sahoo
9beca3a802 feat: add a merge option to navigate to control merging params 2020-11-04 22:36:43 +01:00
Satyajit Sahoo
ec7b02af2c feat: add warning on accessing the state object on route prop 2020-11-04 22:36:43 +01:00
Satyajit Sahoo
4c2379cec1 fix: ignore any errors from deep linking 2020-11-04 22:36:43 +01:00
marhaupe
1169ed0946 fix: android textinput gets blurred after navigating back
When navigating from ScreenA to ScreenB and then back to ScreenA,
react-navigation unconditionally calls `Keyboard.dismiss()`.
If the user is fast enough and taps on a `TextInput` after coming
back from ScreenB, the keyboard opens, just to be dismissed again.
This would also happen if some logic automatically focuses the
`TextInput` in ScreenA. This behaviour can be seen observed in
https://snack.expo.io/lTDZhVNhV.
2020-11-04 22:35:57 +01:00
Satyajit Sahoo
bf464a8378 chore: tweak workflows for triage 2020-11-04 14:23:46 +01:00
Satyajit Sahoo
a495506e20 chore: publish
- @react-navigation/bottom-tabs@5.10.5
 - @react-navigation/compat@5.3.5
 - @react-navigation/core@5.13.5
 - @react-navigation/devtools@5.1.13
 - @react-navigation/drawer@5.10.5
 - @react-navigation/material-bottom-tabs@5.3.5
 - @react-navigation/material-top-tabs@5.3.5
 - @react-navigation/native@5.8.5
 - @react-navigation/stack@5.12.2
2020-11-04 13:24:15 +01:00
Satyajit Sahoo
b20f2d1f7c fix: use useDebugValue in more places 2020-11-04 13:21:36 +01:00
Satyajit Sahoo
66f3a4a0bb fix: don't use use-subscription to avoid peer dep related errors
The `use-subscription` package has a peer dep on latest React. This is problematic when using npm due to it's peer dependency algorithm which installs multiple versions of React when using an older version of React (Native).

This means that we'll need to use an ancient version of `use-subscription` to support older React versions with npm and make sure to never update it, or test with every version.

It's much lower maintenance to incporporate the same update in render logic that `use-subscription` has and not deal with dependencies. So this commit removes the `use-subscription` dependency.

See https://github.com/react-navigation/react-navigation/issues/9021#issuecomment-721679760 for more context.
2020-11-04 13:06:49 +01:00
Satyajit Sahoo
84cc0d758a chore: publish
- @react-navigation/bottom-tabs@5.10.4
 - @react-navigation/compat@5.3.4
 - @react-navigation/core@5.13.4
 - @react-navigation/devtools@5.1.12
 - @react-navigation/drawer@5.10.4
 - @react-navigation/material-bottom-tabs@5.3.4
 - @react-navigation/material-top-tabs@5.3.4
 - @react-navigation/native@5.8.4
 - @react-navigation/stack@5.12.1
2020-11-03 07:04:08 +01:00
Satyajit Sahoo
ebc7f9ea75 fix: fix nested navigation not working the first time 2020-11-03 06:59:42 +01:00
Satyajit Sahoo
bd9f0ad5f6 chore: publish
- @react-navigation/bottom-tabs@5.10.3
 - @react-navigation/compat@5.3.3
 - @react-navigation/core@5.13.3
 - @react-navigation/devtools@5.1.11
 - @react-navigation/drawer@5.10.3
 - @react-navigation/material-bottom-tabs@5.3.3
 - @react-navigation/material-top-tabs@5.3.3
 - @react-navigation/native@5.8.3
 - @react-navigation/stack@5.12.0
2020-11-03 06:31:58 +01:00
Satyajit Sahoo
c326c106f9 feat: add a headerBackAccessibilityLabel option in stack
closes #9016
2020-11-03 06:22:51 +01:00
Satyajit Sahoo
52451d1109 fix: make sure that invalid linking config doesn't work if app is open 2020-11-03 06:15:44 +01:00
Satyajit Sahoo
0945689b70 fix: handle navigating to same screen again for nested screens 2020-11-03 05:51:52 +01:00
Satyajit Sahoo
37b9454f3e chore: publish
- @react-navigation/bottom-tabs@5.10.2
 - @react-navigation/compat@5.3.2
 - @react-navigation/core@5.13.2
 - @react-navigation/devtools@5.1.10
 - @react-navigation/drawer@5.10.2
 - @react-navigation/material-bottom-tabs@5.3.2
 - @react-navigation/material-top-tabs@5.3.2
 - @react-navigation/native@5.8.2
 - @react-navigation/stack@5.11.1
2020-10-30 13:42:48 +01:00
Satyajit Sahoo
fb7ac960c8 fix: trim routes if an index is specified in state 2020-10-30 13:41:28 +01:00
Satyajit Sahoo
e8515f9cd9 fix: fix params from for the root screen when creating action
closes #9006
2020-10-30 13:26:52 +01:00
Satyajit Sahoo
5eee804e7f chore: publish
- @react-navigation/bottom-tabs@5.10.1
 - @react-navigation/compat@5.3.1
 - @react-navigation/core@5.13.1
 - @react-navigation/devtools@5.1.9
 - @react-navigation/drawer@5.10.1
 - @react-navigation/material-bottom-tabs@5.3.1
 - @react-navigation/material-top-tabs@5.3.1
 - @react-navigation/native@5.8.1
 - @react-navigation/routers@5.5.1
 - @react-navigation/stack@5.11.0
2020-10-28 22:21:16 +01:00
Satyajit Sahoo
45dbe5c40e feat: enable react-native-screens in Stack by default on iOS 2020-10-28 22:15:37 +01:00
Satyajit Sahoo
d26bcc057e fix: improve types for route prop in screenOptions 2020-10-28 22:06:52 +01:00
Satyajit Sahoo
836ca4482e chore: fix loading indicator not visible in auth example 2020-10-27 01:37:53 +01:00
Satyajit Sahoo
fdd549a536 chore: migrate example to community async-storage 2020-10-27 00:44:30 +01:00
Satyajit Sahoo
128bbbe62a chore: add ability to manually run expo publish workflow 2020-10-25 02:05:18 +02:00
Satyajit Sahoo
a186b445b4 chore: fix slug for example app 2020-10-25 01:58:38 +02:00
Satyajit Sahoo
ac11a3bded chore: publish
- @react-navigation/bottom-tabs@5.10.0
 - @react-navigation/compat@5.3.0
 - @react-navigation/core@5.13.0
 - @react-navigation/devtools@5.1.8
 - @react-navigation/drawer@5.10.0
 - @react-navigation/material-bottom-tabs@5.3.0
 - @react-navigation/material-top-tabs@5.3.0
 - @react-navigation/native@5.8.0
 - @react-navigation/routers@5.5.0
 - @react-navigation/stack@5.10.0
2020-10-25 01:38:02 +02:00
Satyajit Sahoo
55d635f53e chore: fix custom link button example on web 2020-10-25 01:32:36 +02:00
Satyajit Sahoo
95600500a4 chore: upgrade depenendecies 2020-10-25 01:28:19 +02:00
Satyajit Sahoo
6cf124a190 docs: improve jsdoc for linking 2020-10-24 23:51:59 +02:00
Satyajit Sahoo
bfd0d94985 docs: fix incorrect comment 2020-10-24 23:35:50 +02:00
Satyajit Sahoo
748e92f120 feat: add getInitialURL and subscribe options to linking config
For apps with push notifications linking to screens inside the app, currently we need to handle them separately (e.g. [instructions for firebase](https://rnfirebase.io/messaging/notifications#handling-interaction), [instructions for expo notifications](https://docs.expo.io/push-notifications/receiving-notifications/)). But if we add a link in the notification to use for deep linking, we can instead reuse the same deep linking logic instead.

This commit adds the `getInitialURL` and `subscribe` options which internally used `Linking` API to allow more advanced implementations by combining it with other sources such as push notifications.

Example usage with Firebase notifications could look like this:

```js
const linking = {
  prefixes: ['myapp://', 'https://myapp.com'],
  async getInitialURL() {
    // Check if app was opened from a deep link
    const url = await Linking.getInitialURL();

    if (url != null) {
      return url;
    }

    // Check if there is an initial firebase notification
    const message = await messaging().getInitialNotification();

    // Get the `url` property from the notification which corresponds to a screen
    // This property needs to be set on the notification payload when sending it
    return message?.notification.url;
  },
  subscribe(listener) {
    const onReceiveURL = ({ url }: { url: string }) => listener(url);

    // Listen to incoming links from deep linking
    Linking.addEventListener('url', onReceiveURL);

    // Listen to firebase push notifications
    const unsubscribeNotification = messaging().onNotificationOpenedApp(
      (message) => {
        const url = message.notification.url;

        if (url) {
          // If the notification has a `url` property, use it for linking
          listener(url);
        }
      }
    );

    return () => {
      // Clean up the event listeners
      Linking.removeEventListener('url', onReceiveURL);
      unsubscribeNotification();
    };
  },
  config,
};
```
2020-10-24 23:32:51 +02:00
Satyajit Sahoo
7f3b27a9ec feat: allow deep linking to reset state (#8973)
Currently when we receive a deep link after the app is rendered, it always results in a `navigate` action. While it's ok with the default configuration, it may result in incorrect behaviour when a custom `getStateForPath` function is provided and it returns a routes array different than the initial route and new route pair.

The commit changes 2 things:

1. Add ability to reset state via params of `navigate` by specifying a `state` property instead of `screen`
2. Update `getStateForAction` to return an action for reset when necessary according to the deep linking configuration

Closes #8952
2020-10-24 15:27:06 +02:00
Satyajit Sahoo
f51086edea feat: update helper types to have navigator specific methods 2020-10-23 18:12:36 +02:00
Wojciech Lewicki
7196889bf1 feat: add optional screens per navigator (#8805)
Changes done here will work properly with https://github.com/software-mansion/react-native-screens/pull/624 merged and released. The documentation of `screensEnabled` and `activeLimit` props should also be added. It also enabled `Screens` in iOS stack-navigator by default.

New things:
- `detachInactiveScreens` - prop for navigators with `react-native-screens` integration that can be set by user. It controls if the `react-native-screens` are used by the navigator.
- `detachPreviousScreen` - option that tells to keep the previous screen active. It can be set by user, defaults to `true` for normal mode and `false` for the last screen for mode = “modal”.

Co-authored-by: Satyajit Sahoo <satyajit.happy@gmail.com>
2020-10-23 17:59:26 +02:00
Satyajit Sahoo
7dc2f5832e feat: improve types for navigation state (#8980)
The commit improves the navigation state object to have more specific types.
e.g. The `routeNames` array will now have proper type instead of `string[]`
2020-10-23 17:06:31 +02:00
Satyajit Sahoo
8ec6c1a603 chore: remove test code from example app 2020-10-23 16:51:44 +02:00
Satyajit Sahoo
960f0a5281 refactor: make sure height set on header container is focused header height 2020-10-23 03:32:40 +02:00
Satyajit Sahoo
da91cec941 fix: add missing check for parent header when calculating height 2020-10-23 02:37:36 +02:00
Satyajit Sahoo
38e17aae93 fix: don't set statusbar height on nested header by default 2020-10-23 02:27:50 +02:00
Satyajit Sahoo
0f60b4617f refactor: refactor HeaderSegment to function component 2020-10-23 02:17:04 +02:00
Drew Miller
f01bb4834b feat: add sceneContainerStyle prop to bottom-tabs navigator (#8947)
This feature adds the sceneContainerStyle prop to the bottom-tabs navigator, to allow setting styles on the container's view. It's already implemented in the material-top-tabs and drawer navigators, I've simply ported it into the bottom-tabs navigator.

It also fixes this issue:

https://github.com/react-navigation/react-navigation/issues/8076

Co-authored-by: Satyajit Sahoo <satyajit.happy@gmail.com>
2020-10-23 02:16:09 +02:00
Satyajit Sahoo
261a33a0d0 fix: fix imports from query-string. closes #8971 (#8976) 2020-10-23 02:15:47 +02:00
Listi
d6cac6713a fix: fix header buttons not pressable when headerTransparent=true & headerMode=float (#8804)
Motivation
--
Previously when using `headerMode="float"` with headerTransparent set to true, we cant press header buttons in Android. This PR fixes this. (resolves #8731 )
Been doing some debugging and found out that this is caused by `HeaderContainer` being set as `absolute`. Initially it didn't have width & height in Android when it's set to default, that's why we can't access the children.
So, the solution in this PR is to define the height by using headerHeight. But, since we can't access headerHeight from header, Im using local state for keeping up with the headerHeight. Or should I move the HeaderHeight provider out of StackContainer? I'm not sure, since I think it was intended to be kept inside the StackContainer

Test Plan
--
With this config, now the header button can be clicked. Tested in both platform
```typescript
  <Stack.Navigator
    headerMode="float"
    screenOptions={{
      headerTransparent: true
    }}
  >
     <Stack.Screen  
      name="Home Screen"
      component={Home}
      />
      <Stack.Screen  
      name="Details Screen"
      component={Details}
      />
  </Stack.Navigator>
```

Android:
-
![Kapture 2020-09-30 at 19 01 21](https://user-images.githubusercontent.com/24470609/94682575-5b0fe480-034f-11eb-880a-318643d4eb00.gif)

iOS:
--
<img width="300" src="https://user-images.githubusercontent.com/24470609/94682743-9a3e3580-034f-11eb-8e90-2d31748bde5c.gif" />
2020-10-23 01:52:34 +02:00
Satyajit Sahoo
8ee0dda155 fix: set needsOffscreenAlphaCompositing and update default android animation
closes #8696
2020-10-23 01:38:14 +02:00
David Pett
8585f97226 docs: fix comments for gestureResponseDistance (#8954)
gestureResponseDistance vertical and horizontal documentations were swapped
2020-10-21 13:12:54 +02:00
Hossein Mohammadi
23ab350492 feat: support wildcard string prefixes (#8942)
Prefixes should be more flexible for situations like wild card subdomain. On android and IOS we can define wild cards by * but react-navigation does not work, In this PR I added support for RegExp Prefixes.

For Example
```js
{
  prefixes: [
    /^[^.s]+.example.com/g
 ],
}
```
I tested this work well.

Closes #8941 

Co-authored-by: Satyajit Sahoo <satyajit.happy@gmail.com>
2020-10-20 12:01:49 +02:00
ahmadj-levelbenefits
80ff5a9c54 feat: add an unhandled action listener (#8895)
Often developers miss these console messages: this allows missed routes to be emitted to whatever event logger users prefer.
2020-10-09 16:49:20 +02:00
Kaushil Ruparelia
90ebfc40b3 feat: make react-native-vector-icons optional (#8936)
Referenced from 4b26429c49/src/components/MaterialCommunityIcon.tsx (L14)

https://callstack.github.io/react-native-paper/getting-started.html
> To get smaller bundle size by excluding modules you don't use, you can use our optional babel plugin. The plugin automatically rewrites the import statements so that only the modules you use are imported instead of the whole library. Add react-native-paper/babel to the plugins section in your babel.config.js for production environment. It should look like this:
> ```
> module.exports = {
>   presets: ['module:metro-react-native-babel-preset'],
>   env: {
>     production: {
>       plugins: ['react-native-paper/babel'],
>     },
>   },
> };
> ```
> If you created your project using Expo, it'll look something like this:
> ```
> module.exports = function(api) {
>   api.cache(true);
>   return {
>     presets: ['babel-preset-expo'],
>     env: {
>       production: {
>         plugins: ['react-native-paper/babel'],
>       },
>     },
>   };
> };
> ```

Closes #8821
2020-10-09 13:41:37 +02:00
Satyajit Sahoo
091b2a2038 fix: handle pushing a route with duplicate key
Currently, stack router adds a duplicate route when pushing a new route with a key that already exists. This is a buggy behaviour since keys need to be unique in the stack.

This commit fixes the behaviour to bring the existing route with the same key to focus (and merge new params if any) instead of adding a duplicate route.
2020-10-09 00:28:45 +02:00
290 changed files with 18054 additions and 14129 deletions

View File

@@ -3,11 +3,17 @@ version: 2.1
executors:
default:
docker:
- image: circleci/node:10
- image: circleci/node:14
working_directory: ~/project
environment:
YARN_CACHE_FOLDER: "~/.cache/yarn"
playwright:
docker:
- image: mcr.microsoft.com/playwright:bionic
environment:
NODE_ENV: development
commands:
attach_project:
steps:
@@ -30,10 +36,12 @@ jobs:
command: yarn install --frozen-lockfile
- save_cache:
key: yarn-packages-v1-{{ .Branch }}-{{ checksum "yarn.lock" }}
paths: ~/.cache/yarn
paths:
- ~/.cache/yarn
- persist_to_workspace:
root: .
paths: .
paths:
- .
lint-and-typecheck:
executor: default
@@ -61,18 +69,9 @@ jobs:
destination: coverage
integration-tests:
executor: default
executor: playwright
steps:
- attach_project
- run:
name: Install Headless Chrome dependencies
command: |
sudo apt-get install -yq \
gconf-service libasound2 libatk1.0-0 libatk-bridge2.0-0 libc6 libcairo2 libcups2 libdbus-1-3 \
libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 \
libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 \
libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 ca-certificates \
fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget
- run:
name: Build example for web
command: yarn example expo build:web --no-pwa
@@ -87,6 +86,9 @@ jobs:
- run:
name: Build packages in the monorepo
command: yarn lerna run prepare
- run:
name: Verify built type definitions are correct
command: yarn typescript
- run:
name: Verify paths for types
command: node scripts/check-types-path.js

View File

@@ -8,12 +8,12 @@
"@react-navigation/core",
"@react-navigation/native",
"@react-navigation/routers",
"@react-navigation/compat",
"@react-navigation/stack",
"@react-navigation/drawer",
"@react-navigation/bottom-tabs",
"@react-navigation/material-top-tabs",
"@react-navigation/material-bottom-tabs",
"@react-navigation/elements",
"@react-navigation/devtools"
]
},

View File

@@ -14,7 +14,7 @@ contact_links:
about: Ask and answer questions using the react-navigation label.
- name: Reactiflux
url: https://www.reactiflux.com/
about: Chat with other community members in the react-navigation channel.
about: Chat with other community members in the help-react-native channel.
- name: Write an RFC
url: https://github.com/react-navigation/rfcs
about: Write a RFC if you have ideas for how to implement a feature request.

76
.github/workflows/check-repro.yml vendored Normal file
View File

@@ -0,0 +1,76 @@
name: Check for repro
on:
issues:
types: [opened, edited]
issue_comment:
types: [created, edited]
jobs:
check-repro:
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v3
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const user = context.payload.sender.login;
const body = context.payload.comment
? context.payload.comment.body
: context.payload.issue.body;
const regex = new RegExp(
`https?:\\/\\/((github\\.com\\/${user}\\/[^/]+\\/?[\\s\\n]+)|(snack\\.expo\\.io\\/.+))`,
'gm'
);
if (regex.test(body)) {
await github.issues.addLabels({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels: ['repro provided'],
});
try {
await github.issues.removeLabel({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
name: 'needs repro',
});
} catch (error) {
if (!/Label does not exist/.test(error.message)) {
throw error;
}
}
} else {
if (context.eventName !== 'issues') {
return;
}
const body = "Hey! Thanks for opening the issue. The issue doesn't seem to contain a link to a repro (a [snack.expo.io](https://snack.expo.io) link or link to a GitHub repo under your username).\n\nCan you provide a [minimal repro](https://stackoverflow.com/help/minimal-reproducible-example) which demonstrates the issue? A repro will help us debug the issue faster. Please try to keep the repro as small as possible and make sure that we can run it without additional setup.";
const comments = await github.issues.listComments({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
});
if (comments.data.some(comment => comment.body === body)) {
return;
}
await github.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body,
});
await github.issues.addLabels({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels: ['needs repro'],
});
}

View File

@@ -13,14 +13,12 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v1
with:
node-version: 10.x
node-version: 14.x
- name: Setup Expo
uses: expo/expo-github-action@v5
with:
expo-version: 3.x
expo-username: ${{ secrets.EXPO_CLI_USERNAME }}
expo-password: ${{ secrets.EXPO_CLI_PASSWORD }}
expo-token: ${{ secrets.EXPO_TOKEN }}
expo-cache: true
- name: Restore yarn cache
@@ -36,7 +34,7 @@ jobs:
- name: Publish Expo app
working-directory: ./example
run: expo publish --release-channel=pr-${{ github.event.number }}
run: yarn expo publish --release-channel=pr-${{ github.event.number }}
env:
EXPO_USE_DEV_SERVER: true
@@ -45,7 +43,7 @@ jobs:
run: echo "::set-output name=path::@react-navigation/react-navigation-example?release-channel=pr-${{ github.event.number }}"
- name: Comment on PR
uses: actions/github-script@v2
uses: actions/github-script@v3
with:
github-token: ${{secrets.GITHUB_TOKEN}}
script: |

View File

@@ -3,6 +3,7 @@ on:
push:
branches:
- main
workflow_dispatch:
jobs:
publish:
@@ -15,14 +16,12 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v1
with:
node-version: 10.x
node-version: 14.x
- name: Setup Expo
uses: expo/expo-github-action@v5
with:
expo-version: 3.x
expo-username: ${{ secrets.EXPO_CLI_USERNAME }}
expo-password: ${{ secrets.EXPO_CLI_PASSWORD }}
expo-token: ${{ secrets.EXPO_TOKEN }}
expo-cache: true
- name: Restore yarn cache
@@ -38,4 +37,4 @@ jobs:
- name: Publish Expo app
working-directory: ./example
run: expo publish
run: yarn expo publish

View File

@@ -0,0 +1,45 @@
name: First pull request
on: pull_request_target
jobs:
welcome:
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v3
with:
github-token: ${{secrets.GITHUB_TOKEN}}
script: |
// Get a list of all issues created by the PR opener
// See: https://octokit.github.io/rest.js/#pagination
const creator = context.payload.sender.login;
const options = github.issues.listForRepo.endpoint.merge({
...context.issue,
creator,
state: 'all'
});
const issues = await github.paginate(options);
for (const issue of issues) {
if (issue.number === context.issue.number) {
continue;
}
if (issue.pull_request) {
return; // Creator is already a contributor.
}
}
await github.issues.addLabels({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels: ['first pull request'],
});
await github.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `Hey ${creator}! Thanks for opening your first pull request in this repo. If you haven't already, make sure to read our [contribution guidelines](https://github.com/react-navigation/react-navigation/blob/main/CONTRIBUTING.md).`
});

15
.github/workflows/sponsor.yml vendored Normal file
View File

@@ -0,0 +1,15 @@
name: Label sponsors
on:
pull_request:
types: [opened]
issues:
types: [opened]
jobs:
build:
name: is-sponsor-label
runs-on: ubuntu-latest
steps:
- uses: JasonEtco/is-sponsor-label-action@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

21
.github/workflows/stale.yml vendored Normal file
View File

@@ -0,0 +1,21 @@
name: Close stale issues and PRs
on:
schedule:
- cron: '30 1 * * *'
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v3
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 30
days-before-close: 7
any-of-labels: 'needs more info,needs repro,needs response'
exempt-issue-labels: 'repro provided,keep open'
exempt-pr-labels: 'keep open'
stale-issue-label: 'stale'
stale-pr-label: 'stale'
stale-issue-message: 'Hello 👋, this issue has been open for more than a month without a repro or any activity. If the issue is still present in the latest version, please provide a repro or leave a comment within 7 days to keep it open, otherwise it will be closed automatically. If you found a solution or workaround for the issue, please comment here for others to find. If this issue is critical for you, please consider sending a pull request to fix it.'
stale-pr-message: 'Hello 👋, this pull request has been open for more than a month with no activity on it. If you think this is still necessary with the latest version, please comment and ping a maintainer to get this reviewed, otherwise it will be closed automatically in 7 days.'

View File

@@ -8,7 +8,7 @@ jobs:
runs-on: ubuntu-latest
if: github.event.label.name == 'needs more info'
steps:
- uses: actions/github-script@v2
- uses: actions/github-script@v3
with:
github-token: ${{secrets.GITHUB_TOKEN}}
script: |
@@ -16,14 +16,14 @@ jobs:
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: "Hey! Thanks for opening the issue. Can you provide more information about the issue? Please fill the issue template when opening the issue without deleting any section. We need all the information we can to be able to help.\n\nMake sure to at least provide - Current behaviour, Expected behaviour, A way to [reproduce the issue with minimal code](https://stackoverflow.com/help/minimal-reproducible-example) (link to [snack.expo.io](https://snack.expo.io)) or a repo on GitHub, and the information about your environment (such as the platform of the device, exact versions of all the packages mentioned in the template etc.)."
body: "Hey! Thanks for opening the issue. Can you provide more information about the issue? Please fill the issue template when opening the issue without deleting any section. We need all the information we can to be able to help.\n\nMake sure to at least provide - Current behaviour, Expected behaviour, A way to [reproduce the issue with minimal code](https://stackoverflow.com/help/minimal-reproducible-example) (link to [snack.expo.io](https://snack.expo.io)) or a repo on GitHub, and the information about your environment (such as the platform of the device, exact versions of all the packages mentioned in the template etc.). In addition, if you can provide a video or GIF demonstrating the issue, it will also be very helpful."
})
needs-repro:
runs-on: ubuntu-latest
if: github.event.label.name == 'needs repro'
steps:
- uses: actions/github-script@v2
- uses: actions/github-script@v3
with:
github-token: ${{secrets.GITHUB_TOKEN}}
script: |
@@ -31,14 +31,14 @@ jobs:
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: "Hey! Thanks for opening the issue. Can you provide a [minimal repro](https://stackoverflow.com/help/minimal-reproducible-example) which demonstrates the issue? Posting a snippet of your code in the issue is useful, but it's not usually straightforward to run. A repro will help us debug the issue faster. Please try to keep the repro as small as possible.\n\nThe easiest way to provide a repro is on [snack.expo.io](https://snack.expo.io). If it's not possible to repro it on [snack.expo.io](https://snack.expo.io), then please provide the repro in a GitHub repository."
body: "Hey! Thanks for opening the issue. Can you provide a [minimal repro](https://stackoverflow.com/help/minimal-reproducible-example) which demonstrates the issue? Posting a snippet of your code in the issue is useful, but it's not usually straightforward to run. A repro will help us debug the issue faster. Please try to keep the repro as small as possible and make sure that we can run it without additional setup.\n\nThe easiest way to provide a repro is on [snack.expo.io](https://snack.expo.io). If it's not possible to repro it on [snack.expo.io](https://snack.expo.io), then please provide the repro in a GitHub repository."
})
question:
runs-on: ubuntu-latest
if: github.event.label.name == 'question'
steps:
- uses: actions/github-script@v2
- uses: actions/github-script@v3
with:
github-token: ${{secrets.GITHUB_TOKEN}}
script: |
@@ -46,14 +46,14 @@ jobs:
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: "Hey! Thanks for opening the issue. The issue tracker is intended for only tracking bug reports. This helps us prioritize fixing bugs in the library. Seems you have a usage question or an issue unrelated to this library. Please ask the question on [StackOverflow](https://stackoverflow.com/questions/tagged/react-navigation) instead using the `react-navigation` label. You can also chat with other community members on [Reactiflux Discord server](https://www.reactiflux.com/) in the `#react-navigation` channel.\n\nIf you believe that this is actually a bug in the library, please open a new issue and fill the issue template with relevant information."
body: "Hey! Thanks for opening the issue. The issue tracker is intended for only tracking bug reports. This helps us prioritize fixing bugs in the library. Seems you have a usage question or an issue unrelated to this library. Please ask the question on [StackOverflow](https://stackoverflow.com/questions/tagged/react-navigation) instead using the `react-navigation` label. You can also chat with other community members on [Reactiflux Discord server](https://www.reactiflux.com/) in the `#help-react-native` channel.\n\nIf you believe that this is actually a bug in the library, please open a new issue and fill the issue template with relevant information."
})
feature-request:
runs-on: ubuntu-latest
if: github.event.label.name == 'feature-request'
steps:
- uses: actions/github-script@v2
- uses: actions/github-script@v3
with:
github-token: ${{secrets.GITHUB_TOKEN}}
script: |
@@ -68,7 +68,7 @@ jobs:
runs-on: ubuntu-latest
if: github.event.label.name == 'library:react-native-screens'
steps:
- uses: actions/github-script@v2
- uses: actions/github-script@v3
with:
github-token: ${{secrets.GITHUB_TOKEN}}
script: |
@@ -76,14 +76,14 @@ jobs:
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: "Hey! Thanks for opening the issue. The issue tracker is intended for only tracking bug reports in React Navigation. Seems you have an issue related to `native-stack` navigator or `react-native-screens` library. Please post your issue in [this repo](https://github.com/software-mansion/react-native-screens) so that it's notified to the maintainers of that library."
body: "Hey! Thanks for opening the issue. Seems that this issue issue related to `react-native-screens` library which is a dependency of React Navigation. Can you also post your issue in [this repo](https://github.com/software-mansion/react-native-screens) so that it's notified to the maintainers of that library? This will help us fix the issue faster since it's upto the maintainers of that library to investigate it."
})
react-native-reanimated:
runs-on: ubuntu-latest
if: github.event.label.name == 'library:react-native-reanimated'
steps:
- uses: actions/github-script@v2
- uses: actions/github-script@v3
with:
github-token: ${{secrets.GITHUB_TOKEN}}
script: |
@@ -91,14 +91,14 @@ jobs:
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: "Hey! Thanks for opening the issue. The issue tracker is intended for only tracking bug reports in React Navigation. Seems you have an issue related to `react-native-reanimated` library. Please post your issue in [this repo](https://github.com/software-mansion/react-native-reanimated) so that it's notified to the maintainers of that library."
body: "Hey! Thanks for opening the issue. Seems that this issue issue related to `react-native-reanimated` library which is a dependency of React Navigation. Can you also post your issue in [this repo](https://github.com/software-mansion/react-native-reanimated) so that it's notified to the maintainers of that library? This will help us fix the issue faster since it's upto the maintainers of that library to investigate it."
})
react-native-gesture-handler:
runs-on: ubuntu-latest
if: github.event.label.name == 'library:react-native-gesture-handler'
steps:
- uses: actions/github-script@v2
- uses: actions/github-script@v3
with:
github-token: ${{secrets.GITHUB_TOKEN}}
script: |
@@ -106,14 +106,14 @@ jobs:
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: "Hey! Thanks for opening the issue. The issue tracker is intended for only tracking bug reports in React Navigation. Seems you have an issue related to `react-native-gesture-handler` library. Please post your issue in [this repo](https://github.com/software-mansion/react-native-gesture-handler) so that it's notified to the maintainers of that library."
body: "Hey! Thanks for opening the issue. Seems that this issue issue related to `react-native-gesture-handler` library which is a dependency of React Navigation. Can you also post your issue in [this repo](https://github.com/software-mansion/react-native-gesture-handler) so that it's notified to the maintainers of that library? This will help us fix the issue faster since it's upto the maintainers of that library to investigate it."
})
react-native-safe-area-context:
runs-on: ubuntu-latest
if: github.event.label.name == 'library:react-native-safe-area-context'
steps:
- uses: actions/github-script@v2
- uses: actions/github-script@v3
with:
github-token: ${{secrets.GITHUB_TOKEN}}
script: |
@@ -121,5 +121,5 @@ jobs:
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: "Hey! Thanks for opening the issue. The issue tracker is intended for only tracking bug reports in React Navigation. Seems you have an issue related to `react-native-safe-area-context` library. Please post your issue in [this repo](https://github.com/th3rdwave/react-native-safe-area-context) so that it's notified to the maintainers of that library."
body: "Hey! Thanks for opening the issue. Seems that this issue issue related to `react-native-safe-area-context` library which is a dependency of React Navigation. Can you also post your issue in [this repo](https://github.com/th3rdwave/react-native-safe-area-context) so that it's notified to the maintainers of that library? This will help us fix the issue faster since it's upto the maintainers of that library to investigate it."
})

3
.gitignore vendored
View File

@@ -26,6 +26,7 @@ build
npm-debug.*
*.tsbuildinfo
*.log
*.jks
*.p8
@@ -33,3 +34,5 @@ npm-debug.*
*.key
*.mobileprovision
*.orig.*
*.iml

View File

@@ -1,10 +1,10 @@
import 'react-native-gesture-handler';
import { registerRootComponent } from 'expo';
import { Asset } from 'expo-asset';
import { Assets as StackAssets } from '@react-navigation/stack';
import { Assets } from '@react-navigation/elements';
import App from './src/index';
Asset.loadAsync(StackAssets);
Asset.loadAsync(Assets);
registerRootComponent(App);

View File

@@ -4,7 +4,7 @@
"expo": {
"name": "React Navigation",
"owner": "react-navigation",
"slug": "NavigationPlayground",
"slug": "react-navigation-example",
"description": "Demonstrates the functionality and various capabilities of React Navigation.",
"privacy": "public",
"version": "1.0.0",

View File

@@ -2,5 +2,6 @@ module.exports = function (api) {
api.cache(true);
return {
presets: ['babel-preset-expo'],
plugins: ['react-native-reanimated/plugin'],
};
};

View File

@@ -8,6 +8,7 @@ it('loads the article page', async () => {
expect(await page.evaluate(() => location.pathname + location.search)).toBe(
'/link-component/article/gandalf'
);
expect(
((await page.accessibility.snapshot()) as any)?.children?.find(
(it: any) => it.role === 'heading'

View File

@@ -21,4 +21,8 @@ beforeEach(async () => {
await page.goto('http://localhost:3579');
});
afterEach(async () => {
await context.close();
});
export { browser, context, page };

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@@ -8,12 +8,15 @@ const blacklist = require('metro-config/src/defaults/blacklist');
const root = path.resolve(__dirname, '..');
const packages = path.resolve(root, 'packages');
// List all packages under `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));
.map((p) => path.join(packages, p))
.filter(
(p) =>
fs.statSync(p).isDirectory() &&
fs.existsSync(path.join(p, 'package.json'))
);
// Get the list of dependencies for all packages in the monorepo
const modules = ['@expo/vector-icons']
@@ -68,27 +71,13 @@ module.exports = {
enhanceMiddleware: (middleware) => {
return (req, res, next) => {
// When an asset is imported outside the project root, it has wrong path on Android
// This happens for the back button in stack, so we fix the path to correct one
const assets = '/packages/stack/src/views/assets';
if (req.url.startsWith(assets)) {
req.url = req.url.replace(
assets,
'/assets/../packages/stack/src/views/assets'
);
// So we fix the path to correct one
if (/\/packages\/.+\.png\?.+$/.test(req.url)) {
req.url = `/assets/../${req.url}`;
}
return middleware(req, res, next);
};
},
},
transformer: {
getTransformOptions: () => ({
transform: {
experimentalImportSupport: false,
inlineRequires: true,
},
}),
},
};

View File

@@ -13,50 +13,52 @@
"test": "jest"
},
"dependencies": {
"@expo/vector-icons": "^10.0.0",
"@react-native-community/masked-view": "0.1.10",
"color": "^3.1.2",
"expo": "^39.0.0",
"expo-asset": "~8.2.0",
"expo-blur": "~8.2.0",
"expo-linking": "^1.0.4",
"expo-updates": "~0.3.5",
"@expo/vector-icons": "^12.0.0",
"@react-native-async-storage/async-storage": "^1.13.0",
"@react-native-masked-view/masked-view": "~0.2.4",
"color": "^3.1.3",
"expo": "^41.0.1",
"expo-asset": "~8.3.1",
"expo-blur": "~9.0.3",
"expo-linking": "~2.2.3",
"expo-updates": "~0.5.4",
"koa": "^2.13.0",
"react": "~16.13.1",
"react-dom": "~16.13.1",
"react-native": "~0.63.2",
"react": "16.13.1",
"react-dom": "16.13.1",
"react-native": "https://github.com/expo/react-native/archive/sdk-41.0.0.tar.gz",
"react-native-appearance": "~0.3.3",
"react-native-gesture-handler": "~1.7.0",
"react-native-paper": "^4.2.0",
"react-native-reanimated": "~1.13.0",
"react-native-safe-area-context": "3.1.4",
"react-native-screens": "~2.10.1",
"react-native-tab-view": "^2.15.2",
"react-native-vector-icons": "^7.0.0",
"react-native-web": "^0.13.16"
"react-native-gesture-handler": "~1.10.2",
"react-native-pager-view": "~5.0.12",
"react-native-paper": "^4.7.2",
"react-native-reanimated": "~2.1.0",
"react-native-safe-area-context": "~3.2.0",
"react-native-screens": "~3.0.0",
"react-native-tab-view": "^3.0.1",
"react-native-vector-icons": "^8.1.0",
"react-native-web": "~0.15.0"
},
"devDependencies": {
"@babel/node": "^7.10.1",
"@expo/webpack-config": "^0.12.38",
"@types/cheerio": "^0.22.22",
"@babel/node": "^7.13.13",
"@expo/webpack-config": "~0.12.63",
"@types/cheerio": "^0.22.28",
"@types/jest-dev-server": "^4.2.0",
"@types/koa": "^2.11.4",
"@types/node-fetch": "^2.5.7",
"@types/react": "~16.9.51",
"@types/react-dom": "^16.9.8",
"@types/react-native": "~0.63.25",
"babel-loader": "^8.1.0",
"@types/koa": "^2.13.1",
"@types/node-fetch": "^2.5.9",
"@types/react": "~16.9.35",
"@types/react-dom": "~16.9.8",
"@types/react-native": "~0.63.2",
"babel-loader": "^8.2.2",
"babel-plugin-module-resolver": "^4.0.0",
"babel-preset-expo": "^8.3.0",
"babel-preset-expo": "8.3.0",
"cheerio": "^1.0.0-rc.3",
"expo-cli": "^3.27.14",
"jest": "^26.5.2",
"expo-cli": "^4.4.4",
"jest": "^26.6.3",
"jest-dev-server": "^4.4.0",
"mock-require-assets": "^0.0.1",
"node-fetch": "^2.6.1",
"nodemon": "^2.0.4",
"playwright": "^0.14.0",
"nodemon": "^2.0.6",
"playwright": "^1.10.0",
"serve": "^11.3.0",
"typescript": "^4.0.3"
"typescript": "~4.2.3"
}
}

View File

@@ -24,6 +24,7 @@ module.exports = {
'module-resolver',
{
root: ['..'],
extensions: ['.tsx', '.ts', '.js', '.json'],
alias: {
'react-native': 'react-native-web',
...alias,

View File

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

View File

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

View File

@@ -1,2 +0,0 @@
import * as Linking from 'expo-linking';
export default [Linking.makeUrl('/')];

View File

@@ -1 +0,0 @@
export default ['rne://127.0.0.1:19000/--/'];

View File

@@ -4,9 +4,9 @@ import { Title, Button } from 'react-native-paper';
import { useTheme, ParamListBase } from '@react-navigation/native';
import {
createStackNavigator,
HeaderBackButton,
StackScreenProps,
} from '@react-navigation/stack';
import { HeaderBackButton } from '@react-navigation/elements';
type AuthStackParams = {
Splash: undefined;
@@ -31,9 +31,11 @@ const AuthContext = React.createContext<{
});
const SplashScreen = () => {
const { colors } = useTheme();
return (
<View style={styles.content}>
<ActivityIndicator />
<ActivityIndicator color={colors.primary} />
</View>
);
};

View File

@@ -1,21 +1,17 @@
import * as React from 'react';
import { View, ScrollView, StyleSheet, Platform } from 'react-native';
import { Button } from 'react-native-paper';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import {
getFocusedRouteNameFromRoute,
ParamListBase,
NavigatorScreenParams,
} from '@react-navigation/native';
import type { StackScreenProps } from '@react-navigation/stack';
import {
createBottomTabNavigator,
BottomTabScreenProps,
} from '@react-navigation/bottom-tabs';
import TouchableBounce from '../Shared/TouchableBounce';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { HeaderBackButton } from '@react-navigation/elements';
import Albums from '../Shared/Albums';
import Contacts from '../Shared/Contacts';
import Chat from '../Shared/Chat';
import SimpleStackScreen from './SimpleStack';
import SimpleStackScreen, { SimpleStackParams } from './SimpleStack';
const getTabBarIcon = (name: string) => ({
color,
@@ -26,38 +22,10 @@ const getTabBarIcon = (name: string) => ({
}) => <MaterialCommunityIcons name={name} color={color} size={size} />;
type BottomTabParams = {
Article: undefined;
Albums: undefined;
Contacts: undefined;
Chat: undefined;
};
const scrollEnabled = Platform.select({ web: true, default: false });
const AlbumsScreen = ({
navigation,
}: BottomTabScreenProps<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>
);
TabStack: NavigatorScreenParams<SimpleStackParams>;
TabAlbums: undefined;
TabContacts: undefined;
TabChat: undefined;
};
const BottomTabs = createBottomTabNavigator<BottomTabParams>();
@@ -70,6 +38,7 @@ export default function BottomTabsScreen({
React.useLayoutEffect(() => {
navigation.setOptions({
headerShown: false,
title: routeName,
});
}, [navigation, routeName]);
@@ -77,22 +46,21 @@ export default function BottomTabsScreen({
return (
<BottomTabs.Navigator
screenOptions={{
tabBarButton:
Platform.OS === 'web'
? undefined
: (props) => <TouchableBounce {...props} />,
headerLeft: (props) => (
<HeaderBackButton {...props} onPress={navigation.goBack} />
),
}}
>
<BottomTabs.Screen
name="Article"
name="TabStack"
component={SimpleStackScreen}
options={{
title: 'Article',
tabBarIcon: getTabBarIcon('file-document-box'),
tabBarIcon: getTabBarIcon('file-document'),
}}
/>
<BottomTabs.Screen
name="Chat"
name="TabChat"
component={Chat}
options={{
tabBarLabel: 'Chat',
@@ -101,7 +69,7 @@ export default function BottomTabsScreen({
}}
/>
<BottomTabs.Screen
name="Contacts"
name="TabContacts"
component={Contacts}
options={{
title: 'Contacts',
@@ -109,8 +77,8 @@ export default function BottomTabsScreen({
}}
/>
<BottomTabs.Screen
name="Albums"
component={AlbumsScreen}
name="TabAlbums"
component={Albums}
options={{
title: 'Albums',
tabBarIcon: getTabBarIcon('image-album'),
@@ -119,13 +87,3 @@ export default function BottomTabsScreen({
</BottomTabs.Navigator>
);
}
const styles = StyleSheet.create({
buttons: {
flexDirection: 'row',
padding: 8,
},
button: {
margin: 8,
},
});

View File

@@ -1,164 +0,0 @@
import * as React from 'react';
import { View, ScrollView, StyleSheet, Platform } from 'react-native';
import { Button } from 'react-native-paper';
import {
createCompatNavigatorFactory,
CompatScreenType,
} from '@react-navigation/compat';
import {
createStackNavigator,
StackNavigationProp,
StackScreenProps,
} from '@react-navigation/stack';
import Article from '../Shared/Article';
import Albums from '../Shared/Albums';
import NewsFeed from '../Shared/NewsFeed';
type CompatStackParams = {
Albums: undefined;
Nested: { author: string };
};
type NestedStackParams = {
Feed: undefined;
Article: { author: string };
};
const scrollEnabled = Platform.select({ web: true, default: false });
const AlbumsScreen: CompatScreenType<StackNavigationProp<
CompatStackParams
>> = ({ navigation }) => {
return (
<ScrollView>
<View style={styles.buttons}>
<Button
mode="contained"
onPress={() => navigation.push('Nested', { author: 'Babel fish' })}
style={styles.button}
>
Push nested
</Button>
<Button
mode="outlined"
onPress={() => navigation.goBack()}
style={styles.button}
>
Go back
</Button>
</View>
<Albums scrollEnabled={scrollEnabled} />
</ScrollView>
);
};
const FeedScreen: CompatScreenType<StackNavigationProp<NestedStackParams>> = ({
navigation,
}) => {
return (
<ScrollView>
<View style={styles.buttons}>
<Button
mode="contained"
onPress={() => navigation.push('Article')}
style={styles.button}
>
Push article
</Button>
<Button
mode="outlined"
onPress={() => navigation.goBack()}
style={styles.button}
>
Go back
</Button>
</View>
<NewsFeed scrollEnabled={scrollEnabled} />
</ScrollView>
);
};
const ArticleScreen: CompatScreenType<StackNavigationProp<
NestedStackParams,
'Article'
>> = ({ navigation }) => {
return (
<ScrollView>
<View style={styles.buttons}>
<Button
mode="contained"
onPress={() => navigation.push('Albums')}
style={styles.button}
>
Push albums
</Button>
<Button
mode="outlined"
onPress={() => navigation.goBack()}
style={styles.button}
>
Go back
</Button>
</View>
<Article
author={{ name: navigation.getParam('author') }}
scrollEnabled={scrollEnabled}
/>
</ScrollView>
);
};
ArticleScreen.navigationOptions = ({ navigation }) => ({
title: `Article by ${navigation.getParam('author')}`,
});
const createCompatStackNavigator = createCompatNavigatorFactory(
createStackNavigator
);
const CompatStack = createCompatStackNavigator<
StackNavigationProp<CompatStackParams>
>(
{
Albums: AlbumsScreen,
Nested: {
screen: createCompatStackNavigator<
StackNavigationProp<NestedStackParams>
>(
{
Feed: { getScreen: () => FeedScreen },
Article: { getScreen: () => ArticleScreen },
},
{ navigationOptions: { headerShown: false } }
),
params: {
author: 'Gandalf',
},
},
},
{
mode: 'modal',
}
);
export default function CompatStackScreen({
navigation,
}: StackScreenProps<{}>) {
React.useLayoutEffect(() => {
navigation.setOptions({
headerShown: false,
});
}, [navigation]);
return <CompatStack />;
}
const styles = StyleSheet.create({
buttons: {
flexDirection: 'row',
padding: 8,
},
button: {
margin: 8,
},
});

View File

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

View File

@@ -11,7 +11,6 @@ import {
DrawerScreenProps,
DrawerContent,
DrawerContentComponentProps,
DrawerContentOptions,
} from '@react-navigation/drawer';
import type { StackScreenProps } from '@react-navigation/stack';
import Article from '../Shared/Article';
@@ -91,9 +90,7 @@ const AlbumsScreen = ({
);
};
const CustomDrawerContent = (
props: DrawerContentComponentProps<DrawerContentOptions>
) => {
const CustomDrawerContent = (props: DrawerContentComponentProps) => {
const { colors } = useTheme();
const navigation = useNavigation();
@@ -126,10 +123,14 @@ export default function DrawerScreen({ navigation, ...rest }: Props) {
return (
<Drawer.Navigator
openByDefault
drawerType={isLargeScreen ? 'permanent' : 'back'}
drawerStyle={isLargeScreen ? null : { width: '100%' }}
overlayColor="transparent"
drawerContent={(props) => <CustomDrawerContent {...props} />}
screenOptions={{
headerShown: false,
drawerType: isLargeScreen ? 'permanent' : 'back',
drawerStyle: isLargeScreen ? null : { width: '100%' },
drawerContentContainerStyle: { paddingTop: 4 },
overlayColor: 'transparent',
}}
{...rest}
>
<Drawer.Screen name="Article" component={ArticleScreen} />

View File

@@ -1,36 +1,41 @@
import * as React from 'react';
import { StyleSheet } from 'react-native';
import type { NavigatorScreenParams } from '@react-navigation/native';
import { createMaterialBottomTabNavigator } from '@react-navigation/material-bottom-tabs';
import Albums from '../Shared/Albums';
import Contacts from '../Shared/Contacts';
import Chat from '../Shared/Chat';
import SimpleStackScreen from './SimpleStack';
import SimpleStackScreen, { SimpleStackParams } from './SimpleStack';
type MaterialBottomTabParams = {
Article: undefined;
Albums: undefined;
Contacts: undefined;
Chat: undefined;
TabStack: NavigatorScreenParams<SimpleStackParams>;
TabAlbums: undefined;
TabContacts: undefined;
TabChat: undefined;
};
const MaterialBottomTabs = createMaterialBottomTabNavigator<
MaterialBottomTabParams
>();
const MaterialBottomTabs = createMaterialBottomTabNavigator<MaterialBottomTabParams>();
export default function MaterialBottomTabsScreen() {
return (
<MaterialBottomTabs.Navigator barStyle={styles.tabBar}>
<MaterialBottomTabs.Screen
name="Article"
component={SimpleStackScreen}
name="TabStack"
options={{
tabBarLabel: 'Article',
tabBarIcon: 'file-document-box',
tabBarIcon: 'file-document',
tabBarColor: '#C9E7F8',
}}
/>
>
{(props) => (
<SimpleStackScreen
{...props}
screenOptions={{ headerShown: false }}
/>
)}
</MaterialBottomTabs.Screen>
<MaterialBottomTabs.Screen
name="Chat"
name="TabChat"
component={Chat}
options={{
tabBarLabel: 'Chat',
@@ -40,7 +45,7 @@ export default function MaterialBottomTabsScreen() {
}}
/>
<MaterialBottomTabs.Screen
name="Contacts"
name="TabContacts"
component={Contacts}
options={{
tabBarLabel: 'Contacts',
@@ -49,7 +54,7 @@ export default function MaterialBottomTabsScreen() {
}}
/>
<MaterialBottomTabs.Screen
name="Albums"
name="TabAlbums"
component={Albums}
options={{
tabBarLabel: 'Albums',

View File

@@ -0,0 +1,164 @@
import * as React from 'react';
import { View, Platform, StyleSheet, ScrollView } from 'react-native';
import { Button } from 'react-native-paper';
import type { ParamListBase } from '@react-navigation/native';
import {
createStackNavigator,
StackScreenProps,
TransitionPresets,
HeaderStyleInterpolators,
} from '@react-navigation/stack';
import Article from '../Shared/Article';
import Albums from '../Shared/Albums';
import NewsFeed from '../Shared/NewsFeed';
export type SimpleStackParams = {
Article: { author: string } | undefined;
NewsFeed: { date: number };
Albums: undefined;
};
const scrollEnabled = Platform.select({ web: true, default: false });
const ArticleScreen = ({
navigation,
route,
}: StackScreenProps<SimpleStackParams, 'Article'>) => {
return (
<ScrollView>
<View style={styles.buttons}>
<Button
mode="contained"
onPress={() => navigation.push('NewsFeed', { date: Date.now() })}
style={styles.button}
>
Push feed
</Button>
<Button
mode="outlined"
onPress={() => navigation.pop()}
style={styles.button}
>
Pop screen
</Button>
</View>
<Article
author={{ name: route.params?.author ?? 'Unknown' }}
scrollEnabled={scrollEnabled}
/>
</ScrollView>
);
};
const NewsFeedScreen = ({
route,
navigation,
}: StackScreenProps<SimpleStackParams, 'NewsFeed'>) => {
return (
<ScrollView>
<View style={styles.buttons}>
<Button
mode="contained"
onPress={() => navigation.push('Albums')}
style={styles.button}
>
Navigate to album
</Button>
<Button
mode="outlined"
onPress={() => navigation.pop()}
style={styles.button}
>
Pop screen
</Button>
</View>
<NewsFeed scrollEnabled={scrollEnabled} date={route.params.date} />
</ScrollView>
);
};
const AlbumsScreen = ({
navigation,
}: StackScreenProps<SimpleStackParams, 'Albums'>) => {
return (
<ScrollView>
<View style={styles.buttons}>
<Button
mode="contained"
onPress={() => navigation.push('Article', { author: 'Babel fish' })}
style={styles.button}
>
Push article
</Button>
<Button
mode="outlined"
onPress={() => navigation.pop()}
style={styles.button}
>
Pop screen
</Button>
</View>
<Albums scrollEnabled={scrollEnabled} />
</ScrollView>
);
};
const SimpleStack = createStackNavigator<SimpleStackParams>();
export default function SimpleStackScreen({
navigation,
}: StackScreenProps<ParamListBase>) {
React.useLayoutEffect(() => {
navigation.setOptions({
headerShown: false,
});
}, [navigation]);
return (
<SimpleStack.Navigator
screenOptions={{
headerStyleInterpolator: HeaderStyleInterpolators.forUIKit,
}}
>
<SimpleStack.Group
screenOptions={{
...TransitionPresets.SlideFromRightIOS,
headerMode: 'float',
}}
>
<SimpleStack.Screen
name="Article"
component={ArticleScreen}
options={({ route }) => ({
title: `Article by ${route.params?.author ?? 'Unknown'}`,
})}
initialParams={{ author: 'Gandalf' }}
/>
<SimpleStack.Screen
name="NewsFeed"
component={NewsFeedScreen}
options={{ title: 'Feed' }}
/>
</SimpleStack.Group>
<SimpleStack.Screen
name="Albums"
component={AlbumsScreen}
options={{
...TransitionPresets.ModalSlideFromBottomIOS,
headerMode: 'screen',
title: 'Albums',
}}
/>
</SimpleStack.Navigator>
);
}
const styles = StyleSheet.create({
buttons: {
flexDirection: 'row',
padding: 8,
},
button: {
margin: 8,
},
});

View File

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

View File

@@ -5,8 +5,6 @@ import type { ParamListBase } from '@react-navigation/native';
import {
createStackNavigator,
StackScreenProps,
StackNavigationOptions,
TransitionPresets,
} from '@react-navigation/stack';
import Article from '../Shared/Article';
import Albums from '../Shared/Albums';
@@ -72,13 +70,11 @@ const AlbumsScreen = ({ navigation }: StackScreenProps<ModalStackParams>) => {
);
};
const ModalPresentationStack = createStackNavigator<ModalStackParams>();
const ModalStack = createStackNavigator<ModalStackParams>();
type Props = StackScreenProps<ParamListBase> & {
options?: StackNavigationOptions;
};
type Props = StackScreenProps<ParamListBase>;
export default function SimpleStackScreen({ navigation, options }: Props) {
export default function ModalStackScreen({ navigation }: Props) {
React.useLayoutEffect(() => {
navigation.setOptions({
headerShown: false,
@@ -86,20 +82,8 @@ export default function SimpleStackScreen({ navigation, options }: Props) {
}, [navigation]);
return (
<ModalPresentationStack.Navigator
mode="modal"
screenOptions={({ route, navigation }) => ({
...TransitionPresets.ModalPresentationIOS,
cardOverlayEnabled: true,
gestureEnabled: true,
headerStatusBarHeight:
navigation.dangerouslyGetState().routes.indexOf(route) > 0
? 0
: undefined,
})}
{...options}
>
<ModalPresentationStack.Screen
<ModalStack.Navigator screenOptions={{ animationPresentation: 'modal' }}>
<ModalStack.Screen
name="Article"
component={ArticleScreen}
options={({ route }) => ({
@@ -107,12 +91,12 @@ export default function SimpleStackScreen({ navigation, options }: Props) {
})}
initialParams={{ author: 'Gandalf' }}
/>
<ModalPresentationStack.Screen
<ModalStack.Screen
name="Albums"
component={AlbumsScreen}
options={{ title: 'Albums' }}
/>
</ModalPresentationStack.Navigator>
</ModalStack.Navigator>
);
}

View File

@@ -4,11 +4,12 @@ import { Button } from 'react-native-paper';
import type { StackScreenProps } from '@react-navigation/stack';
const NotFoundScreen = ({
route,
navigation,
}: StackScreenProps<{ Home: undefined }>) => {
return (
<View style={styles.container}>
<Text style={styles.title}>404 Not Found</Text>
<Text style={styles.title}>404 Not Found ({route.path})</Text>
<Button
mode="contained"
onPress={() => navigation.navigate('Home')}

View File

@@ -77,18 +77,28 @@ const InputScreen = ({
e.preventDefault();
Alert.alert(
'Discard changes?',
'You have unsaved changes. Are you sure to discard them and leave the screen?',
[
{ text: "Don't leave", style: 'cancel', onPress: () => {} },
{
text: 'Discard',
style: 'destructive',
onPress: () => navigation.dispatch(action),
},
]
);
if (Platform.OS === 'web') {
const discard = confirm(
'You have unsaved changes. Discard them and leave the screen?'
);
if (discard) {
navigation.dispatch(action);
}
} else {
Alert.alert(
'Discard changes?',
'You have unsaved changes. Discard them and leave the screen?',
[
{ text: "Don't leave", style: 'cancel', onPress: () => {} },
{
text: 'Discard',
style: 'destructive',
onPress: () => navigation.dispatch(action),
},
]
);
}
}),
[hasUnsavedChanges, navigation]
);

View File

@@ -4,13 +4,14 @@ import { Button } from 'react-native-paper';
import type { ParamListBase } from '@react-navigation/native';
import {
createStackNavigator,
StackNavigationOptions,
StackScreenProps,
} from '@react-navigation/stack';
import Article from '../Shared/Article';
import Albums from '../Shared/Albums';
import NewsFeed from '../Shared/NewsFeed';
type SimpleStackParams = {
export type SimpleStackParams = {
Article: { author: string } | undefined;
NewsFeed: { date: number };
Albums: undefined;
@@ -105,7 +106,10 @@ const SimpleStack = createStackNavigator<SimpleStackParams>();
export default function SimpleStackScreen({
navigation,
}: StackScreenProps<ParamListBase>) {
screenOptions,
}: StackScreenProps<ParamListBase> & {
screenOptions?: StackNavigationOptions;
}) {
React.useLayoutEffect(() => {
navigation.setOptions({
headerShown: false,
@@ -113,7 +117,7 @@ export default function SimpleStackScreen({
}, [navigation]);
return (
<SimpleStack.Navigator>
<SimpleStack.Navigator screenOptions={screenOptions}>
<SimpleStack.Screen
name="Article"
component={ArticleScreen}

View File

@@ -13,11 +13,10 @@ import { useTheme, ParamListBase } from '@react-navigation/native';
import {
createStackNavigator,
StackScreenProps,
HeaderBackground,
useHeaderHeight,
Header,
StackHeaderProps,
} from '@react-navigation/stack';
import { HeaderBackground, useHeaderHeight } from '@react-navigation/elements';
import BlurView from '../Shared/BlurView';
import Article from '../Shared/Article';
import Albums from '../Shared/Albums';
@@ -87,11 +86,10 @@ const AlbumsScreen = ({ navigation }: StackScreenProps<SimpleStackParams>) => {
const SimpleStack = createStackNavigator<SimpleStackParams>();
type Props = Partial<React.ComponentProps<typeof SimpleStack.Navigator>> &
StackScreenProps<ParamListBase>;
type Props = StackScreenProps<ParamListBase>;
function CustomHeader(props: StackHeaderProps) {
const { current, next } = props.scene.progress;
const { current, next } = props.progress;
const progress = Animated.add(current, next || 0);
const opacity = progress.interpolate({
@@ -109,7 +107,7 @@ function CustomHeader(props: StackHeaderProps) {
);
}
export default function SimpleStackScreen({ navigation, ...rest }: Props) {
export default function HeaderCustomizationScreen({ navigation }: Props) {
React.useLayoutEffect(() => {
navigation.setOptions({
headerShown: false,
@@ -117,19 +115,20 @@ export default function SimpleStackScreen({ navigation, ...rest }: Props) {
}, [navigation]);
const { colors, dark } = useTheme();
const [headerTitleCentered, setHeaderTitleCentered] = React.useState(true);
return (
<SimpleStack.Navigator {...rest}>
<SimpleStack.Navigator screenOptions={{ headerMode: 'float' }}>
<SimpleStack.Screen
name="Article"
component={ArticleScreen}
options={({ route }) => ({
title: `Article by ${route.params?.author}`,
header: CustomHeader,
header: (props) => <CustomHeader {...props} />,
headerTintColor: '#fff',
headerStyle: { backgroundColor: '#ff005d' },
headerBackTitleVisible: false,
headerTitleAlign: 'center',
headerTitleAlign: headerTitleCentered ? 'center' : 'left',
headerBackImage: ({ tintColor }) => (
<MaterialCommunityIcons
name="arrow-left-circle-outline"
@@ -142,12 +141,13 @@ export default function SimpleStackScreen({ navigation, ...rest }: Props) {
<Appbar.Action
color={tintColor}
icon="dots-horizontal-circle-outline"
onPress={() =>
onPress={() => {
setHeaderTitleCentered((centered) => !centered);
Alert.alert(
'Never gonna give you up!',
'Never gonna let you down! Never gonna run around and desert you!'
)
}
);
}}
/>
),
})}

View File

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

View File

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

View File

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

View File

@@ -1,50 +1,50 @@
import * as React from 'react';
import {
ScrollView,
YellowBox,
Platform,
StatusBar,
I18nManager,
Dimensions,
ScaledSize,
Linking,
LogBox,
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { enableScreens } from 'react-native-screens';
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
import {
Provider as PaperProvider,
DefaultTheme as PaperLightTheme,
DarkTheme as PaperDarkTheme,
Appbar,
List,
Divider,
Text,
} from 'react-native-paper';
import { createURL } from 'expo-linking';
import AsyncStorage from '@react-native-async-storage/async-storage';
import {
InitialState,
NavigationContainer,
DefaultTheme,
DarkTheme,
PathConfigMap,
NavigationContainerRef,
useNavigationContainerRef,
NavigatorScreenParams,
} from '@react-navigation/native';
import {
createDrawerNavigator,
DrawerScreenProps,
} from '@react-navigation/drawer';
import { createDrawerNavigator } from '@react-navigation/drawer';
import {
createStackNavigator,
StackScreenProps,
HeaderStyleInterpolators,
StackNavigationProp,
} from '@react-navigation/stack';
import { useReduxDevToolsExtension } from '@react-navigation/devtools';
import { restartApp } from './Restart';
import AsyncStorage from './AsyncStorage';
import LinkingPrefixes from './LinkingPrefixes';
import SettingsItem from './Shared/SettingsItem';
import SimpleStack from './Screens/SimpleStack';
import ModalPresentationStack from './Screens/ModalPresentationStack';
import ModalStack from './Screens/ModalStack';
import MixedStack from './Screens/MixedStack';
import MixedHeaderMode from './Screens/MixedHeaderMode';
import StackTransparent from './Screens/StackTransparent';
import StackHeaderCustomization from './Screens/StackHeaderCustomization';
import BottomTabs from './Screens/BottomTabs';
@@ -55,23 +55,38 @@ import DynamicTabs from './Screens/DynamicTabs';
import MasterDetail from './Screens/MasterDetail';
import AuthFlow from './Screens/AuthFlow';
import PreventRemove from './Screens/PreventRemove';
import CompatAPI from './Screens/CompatAPI';
import LinkComponent from './Screens/LinkComponent';
YellowBox.ignoreWarnings(['Require cycle:', 'Warning: Async Storage']);
if (Platform.OS !== 'web') {
LogBox.ignoreLogs(['Require cycle:']);
}
enableScreens();
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace ReactNavigation {
interface RootParamList extends RootStackParamList {}
}
}
type RootDrawerParamList = {
Root: undefined;
Another: undefined;
Examples: undefined;
};
const SCREENS = {
SimpleStack: { title: 'Simple Stack', component: SimpleStack },
ModalPresentationStack: {
title: 'Modal Presentation Stack',
component: ModalPresentationStack,
ModalStack: {
title: 'Modal Stack',
component: ModalStack,
},
MixedStack: {
title: 'Regular + Modal Stack',
component: MixedStack,
},
MixedHeaderMode: {
title: 'Float + Screen Header Stack',
component: MixedHeaderMode,
},
StackTransparent: {
title: 'Transparent Stack',
@@ -106,10 +121,6 @@ const SCREENS = {
title: 'Prevent removing screen',
component: PreventRemove,
},
CompatAPI: {
title: 'Compat Layer',
component: CompatAPI,
},
LinkComponent: {
title: '<Link />',
component: LinkComponent,
@@ -117,7 +128,7 @@ const SCREENS = {
};
type RootStackParamList = {
Home: undefined;
Home: NavigatorScreenParams<RootDrawerParamList>;
NotFound: undefined;
} & {
[P in keyof typeof SCREENS]: undefined;
@@ -143,7 +154,7 @@ export default function App() {
const initialUrl = await Linking.getInitialURL();
if (Platform.OS !== 'web' || initialUrl === null) {
const savedState = await AsyncStorage.getItem(
const savedState = await AsyncStorage?.getItem(
NAVIGATION_PERSISTENCE_KEY
);
@@ -155,7 +166,7 @@ export default function App() {
}
} finally {
try {
const themeName = await AsyncStorage.getItem(THEME_PERSISTENCE_KEY);
const themeName = await AsyncStorage?.getItem(THEME_PERSISTENCE_KEY);
setTheme(themeName === 'dark' ? DarkTheme : DefaultTheme);
} catch (e) {
@@ -195,7 +206,7 @@ export default function App() {
return () => Dimensions.removeEventListener('change', onDimensionsChange);
}, []);
const navigationRef = React.useRef<NavigationContainerRef>(null);
const navigationRef = useNavigationContainerRef<RootStackParamList>();
useReduxDevToolsExtension(navigationRef);
@@ -214,7 +225,7 @@ export default function App() {
ref={navigationRef}
initialState={initialState}
onStateChange={(state) =>
AsyncStorage.setItem(
AsyncStorage?.setItem(
NAVIGATION_PERSISTENCE_KEY,
JSON.stringify(state)
)
@@ -227,52 +238,55 @@ export default function App() {
// Android (bare): adb shell am start -a android.intent.action.VIEW -d "rne://127.0.0.1:19000/--/simple-stack"
// iOS (bare): xcrun simctl openurl booted rne://127.0.0.1:19000/--/simple-stack
// The first segment of the link is the the scheme + host (returned by `Linking.makeUrl`)
prefixes: LinkingPrefixes,
prefixes: [createURL('/')],
config: {
screens: {
Root: {
path: '',
initialRouteName: 'Home',
screens: Object.keys(SCREENS).reduce<PathConfigMap>(
(acc, name) => {
// Convert screen names such as SimpleStack to kebab case (simple-stack)
const path = name
.replace(/([A-Z]+)/g, '-$1')
.replace(/^-/, '')
.toLowerCase();
initialRouteName: 'Home',
screens: Object.keys(SCREENS).reduce<
PathConfigMap<RootStackParamList>
>(
(acc, name) => {
// Convert screen names such as SimpleStack to kebab case (simple-stack)
const path = name
.replace(/([A-Z]+)/g, '-$1')
.replace(/^-/, '')
.toLowerCase();
acc[name] = {
path,
screens: {
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',
Dialog: 'dialog',
// @ts-expect-error: these types aren't accurate
// But we aren't too concerned for now
acc[name] = {
path,
screens: {
Article: {
path: 'article/:author?',
parse: {
author: (author: string) =>
author.charAt(0).toUpperCase() +
author.slice(1).replace(/-/g, ' '),
},
};
return acc;
stringify: {
author: (author: string) =>
author.toLowerCase().replace(/\s/g, '-'),
},
},
Albums: 'music',
Chat: 'chat',
Contacts: 'people',
NewsFeed: 'feed',
Dialog: 'dialog',
},
{
Home: '',
NotFound: '*',
}
),
};
return acc;
},
},
{
Home: {
screens: {
Examples: '',
},
},
NotFound: '*',
}
),
},
}}
fallback={<Text>Loading</Text>}
@@ -281,95 +295,96 @@ export default function App() {
`${options?.title ?? route?.name} - React Navigation Example`,
}}
>
<Drawer.Navigator drawerType={isLargeScreen ? 'permanent' : undefined}>
<Drawer.Screen
name="Root"
<Stack.Navigator
screenOptions={{
headerStyleInterpolator: HeaderStyleInterpolators.forUIKit,
}}
>
<Stack.Screen
name="Home"
options={{
title: 'Examples',
drawerIcon: ({ size, color }) => (
<MaterialIcons size={size} color={color} name="folder" />
),
headerShown: false,
}}
>
{({ navigation }: DrawerScreenProps<RootDrawerParamList>) => (
<Stack.Navigator
{() => (
<Drawer.Navigator
screenOptions={{
headerStyleInterpolator: HeaderStyleInterpolators.forUIKit,
drawerType: isLargeScreen ? 'permanent' : undefined,
}}
>
<Stack.Screen
name="Home"
<Drawer.Screen
name="Examples"
options={{
title: 'Examples',
headerLeft: isLargeScreen
? undefined
: () => (
<Appbar.Action
color={theme.colors.text}
icon="menu"
onPress={() => navigation.toggleDrawer()}
/>
),
drawerIcon: ({ size, color }) => (
<MaterialIcons size={size} color={color} name="folder" />
),
}}
>
{({ navigation }: StackScreenProps<RootStackParamList>) => (
{({
navigation,
}: {
navigation: StackNavigationProp<RootStackParamList>;
}) => (
<ScrollView
style={{ backgroundColor: theme.colors.background }}
>
<SettingsItem
label="Right to left"
value={I18nManager.isRTL}
onValueChange={() => {
I18nManager.forceRTL(!I18nManager.isRTL);
restartApp();
}}
/>
<Divider />
<SettingsItem
label="Dark theme"
value={theme.dark}
onValueChange={() => {
AsyncStorage.setItem(
THEME_PERSISTENCE_KEY,
theme.dark ? 'light' : 'dark'
);
<SafeAreaView edges={['right', 'bottom', 'left']}>
<SettingsItem
label="Right to left"
value={I18nManager.isRTL}
onValueChange={() => {
I18nManager.forceRTL(!I18nManager.isRTL);
restartApp();
}}
/>
<Divider />
<SettingsItem
label="Dark theme"
value={theme.dark}
onValueChange={() => {
AsyncStorage?.setItem(
THEME_PERSISTENCE_KEY,
theme.dark ? 'light' : 'dark'
);
setTheme((t) => (t.dark ? DefaultTheme : DarkTheme));
}}
/>
<Divider />
{(Object.keys(SCREENS) as (keyof typeof SCREENS)[]).map(
(name) => (
<List.Item
key={name}
testID={name}
title={SCREENS[name].title}
onPress={() => navigation.navigate(name)}
/>
)
)}
setTheme((t) =>
t.dark ? DefaultTheme : DarkTheme
);
}}
/>
<Divider />
{(Object.keys(SCREENS) as (keyof typeof SCREENS)[]).map(
(name) => (
<List.Item
key={name}
testID={name}
title={SCREENS[name].title}
onPress={() => navigation.navigate(name)}
/>
)
)}
</SafeAreaView>
</ScrollView>
)}
</Stack.Screen>
<Stack.Screen
name="NotFound"
component={NotFound}
options={{ title: 'Oops!' }}
/>
{(Object.keys(SCREENS) as (keyof typeof SCREENS)[]).map(
(name) => (
<Stack.Screen
key={name}
name={name}
getComponent={() => SCREENS[name].component}
options={{ title: SCREENS[name].title }}
/>
)
)}
</Stack.Navigator>
</Drawer.Screen>
</Drawer.Navigator>
)}
</Drawer.Screen>
</Drawer.Navigator>
</Stack.Screen>
<Stack.Screen
name="NotFound"
component={NotFound}
options={{ title: 'Oops!' }}
/>
{(Object.keys(SCREENS) as (keyof typeof SCREENS)[]).map((name) => (
<Stack.Screen
key={name}
name={name}
getComponent={() => SCREENS[name].component}
options={{ title: SCREENS[name].title }}
/>
))}
</Stack.Navigator>
</NavigationContainer>
</PaperProvider>
);

18
example/tsconfig.json Normal file
View File

@@ -0,0 +1,18 @@
{
"extends": "../tsconfig",
"references": [
{ "path": "../packages/bottom-tabs" },
{ "path": "../packages/core" },
{ "path": "../packages/devtools" },
{ "path": "../packages/drawer" },
{ "path": "../packages/elements" },
{ "path": "../packages/material-bottom-tabs" },
{ "path": "../packages/material-top-tabs" },
{ "path": "../packages/native" },
{ "path": "../packages/routers" },
{ "path": "../packages/stack" },
],
"compilerOptions": {
// Avoid expo-cli auto-generating a tsconfig
}
}

View File

@@ -11,6 +11,7 @@
"allowBranch": "main",
"conventionalCommits": true,
"createRelease": "github",
"distTag": "next",
"message": "chore: publish",
"ignoreChanges": [
"**/__fixtures__/**",

View File

@@ -8,7 +8,7 @@
]
},
"publishConfig": {
"registry": "https://registry.npmjs.org/"
"access": "public"
},
"license": "MIT",
"repository": {
@@ -25,19 +25,19 @@
"example": "yarn --cwd example"
},
"devDependencies": {
"@commitlint/config-conventional": "^11.0.0",
"@types/jest": "^26.0.14",
"babel-jest": "^26.5.2",
"codecov": "^3.8.0",
"commitlint": "^11.0.0",
"eslint": "^7.10.0",
"eslint-config-satya164": "^3.1.8",
"husky": "^4.3.0",
"jest": "^26.5.2",
"lerna": "^3.22.1",
"metro-react-native-babel-preset": "^0.63.0",
"prettier": "^2.1.2",
"typescript": "^4.0.3"
"@commitlint/config-conventional": "^12.1.1",
"@types/jest": "^26.0.22",
"babel-jest": "^26.6.3",
"codecov": "^3.8.1",
"commitlint": "^12.1.1",
"eslint": "^7.23.0",
"eslint-config-satya164": "^3.1.10",
"husky": "^4.3.6",
"jest": "^26.6.3",
"lerna": "^4.0.0",
"metro-react-native-babel-preset": "^0.65.2",
"prettier": "^2.2.1",
"typescript": "^4.2.3"
},
"resolutions": {
"react": "~16.13.1",

View File

@@ -3,6 +3,223 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [6.0.0-next.8](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@6.0.0-next.7...@react-navigation/bottom-tabs@6.0.0-next.8) (2021-05-09)
**Note:** Version bump only for package @react-navigation/bottom-tabs
# [6.0.0-next.7](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@6.0.0-next.6...@react-navigation/bottom-tabs@6.0.0-next.7) (2021-05-09)
### Bug Fixes
* enable screens only on supported platforms ([#9494](https://github.com/react-navigation/react-navigation/issues/9494)) ([8da4c58](https://github.com/react-navigation/react-navigation/commit/8da4c58065607d44e9dc1ad8943e09537598dcd7))
* make sure disabling react-native-screens works ([a369ba3](https://github.com/react-navigation/react-navigation/commit/a369ba36451ddc2bb5b247e61b725bce1e3fb5e5))
# [6.0.0-next.6](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@6.0.0-next.5...@react-navigation/bottom-tabs@6.0.0-next.6) (2021-05-01)
**Note:** Version bump only for package @react-navigation/bottom-tabs
# [6.0.0-next.5](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@6.0.0-next.4...@react-navigation/bottom-tabs@6.0.0-next.5) (2021-04-16)
### Bug Fixes
* update tab bar height correctly. fixes [#9296](https://github.com/react-navigation/react-navigation/issues/9296) ([338ed6f](https://github.com/react-navigation/react-navigation/commit/338ed6ff07bd2d6efa1abdb369612ea72f540502))
# [6.0.0-next.4](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@6.0.0-next.3...@react-navigation/bottom-tabs@6.0.0-next.4) (2021-04-08)
### Bug Fixes
* remove calls to removed Keyboard.removeListener in useIsKeyboardShown ([#9457](https://github.com/react-navigation/react-navigation/issues/9457)) ([d87857e](https://github.com/react-navigation/react-navigation/commit/d87857e5d93c19ebee2fd84eb4910e36001ec2a3))
# [6.0.0-next.3](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@6.0.0-next.2...@react-navigation/bottom-tabs@6.0.0-next.3) (2021-03-22)
### Bug Fixes
* use tab role on Android for accessibility ([de805a3](https://github.com/react-navigation/react-navigation/commit/de805a3ebf35db81cb7b7bcbf5cfd4a03e69c567))
### Features
* add a Background component ([cbaabc1](https://github.com/react-navigation/react-navigation/commit/cbaabc1288e780698e499a00b9ca06ab9746a0da))
# [6.0.0-next.2](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@6.0.0-next.1...@react-navigation/bottom-tabs@6.0.0-next.2) (2021-03-12)
**Note:** Version bump only for package @react-navigation/bottom-tabs
# [6.0.0-next.1](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@6.0.0...@react-navigation/bottom-tabs@6.0.0-next.1) (2021-03-10)
### Bug Fixes
* fix peer dep versions ([72f90b5](https://github.com/react-navigation/react-navigation/commit/72f90b50d27eda1315bb750beca8a36f26dafe17))
# [6.0.0-next.0](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@5.11.1...@react-navigation/bottom-tabs@6.0.0-next.0) (2021-03-09)
### Bug Fixes
* add missing helper types in descriptors ([21a1154](https://github.com/react-navigation/react-navigation/commit/21a11543bf41c4559c2570d5accc0bbb3b67eb8d))
* drop usage of Dimensions in favor of metrics from safe-area-context ([12b893d](https://github.com/react-navigation/react-navigation/commit/12b893d7ca8cdb726b973972797658ac9c7d17d7))
* enable detachInactiveScreens by default on web for better a11y ([4954d6a](https://github.com/react-navigation/react-navigation/commit/4954d6aae3cdbb5855d44ff17d80d16b81fb224e))
* fix drawer and bottom tabs not being visible on web. closes [#9225](https://github.com/react-navigation/react-navigation/issues/9225) ([b735de1](https://github.com/react-navigation/react-navigation/commit/b735de153ca650240625dba6d8b5c8d16b913bac))
* fix drawer screen content not being interactable on Android ([865d8b3](https://github.com/react-navigation/react-navigation/commit/865d8b3e51e117a01243966c160b7cd147d236ac))
* fix initial metrics on server ([69d333f](https://github.com/react-navigation/react-navigation/commit/69d333f6c23e0c37eaf4d3f8b413e8f96d6827f8))
* fix pointerEvents in ResourceSavingScene ([af53dd6](https://github.com/react-navigation/react-navigation/commit/af53dd6548630124f831446e0eee468da5d9bf5e)), closes [#9241](https://github.com/react-navigation/react-navigation/issues/9241) [#9242](https://github.com/react-navigation/react-navigation/issues/9242)
* show a missing icon symbol instead of empty area in bottom tab bar ([2bc4882](https://github.com/react-navigation/react-navigation/commit/2bc4882692be9f02d781639892e1f98b891811c4))
### Code Refactoring
* don't use deprecated APIs from react-native-safe-area-context ([ddf27bf](https://github.com/react-navigation/react-navigation/commit/ddf27bf41a2efc5d1573aad0f8fe6c27a98c32b3))
* drop support for tabBarVisible option ([a97a43a](https://github.com/react-navigation/react-navigation/commit/a97a43aa1d2a615074ade93f1addebcee1dbfb65))
* move `tabBarOptions` to `options` for bottom tabs ([f7ff1ad](https://github.com/react-navigation/react-navigation/commit/f7ff1adee7654b2d624dee4ae8844be217f23026))
### Features
* initial implementation of @react-navigation/elements ([07ba7a9](https://github.com/react-navigation/react-navigation/commit/07ba7a96870efdb8acf99eb82ba0b1d3eac90bab))
* move lazy to options for bottom-tabs and drawer ([068a9a4](https://github.com/react-navigation/react-navigation/commit/068a9a456c31a08104097f2a8434c66c30a5be99))
### BREAKING CHANGES
* The lazy prop now can be configured per screen instead of for the whole navigator. To keep previous behavior, you can specify it in screenOptions
* This commit moves options from `tabBarOptions` to regular `options` in order to reduce confusion between the two, as well as to make it more flexible to configure the tab bar based on a per screen basis.
* We now require newer versions of safe area context library.
* We need to add support for specifying style for tab bar in options to support the use cases which need this.
## [5.11.1](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@5.11.0...@react-navigation/bottom-tabs@5.11.1) (2020-11-10)
**Note:** Version bump only for package @react-navigation/bottom-tabs
# [5.11.0](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@5.10.7...@react-navigation/bottom-tabs@5.11.0) (2020-11-09)
### Features
* add a hook to get bottom tab bar height ([e08c91f](https://github.com/react-navigation/react-navigation/commit/e08c91ff0a3df13dc6e6096a3e95f60722e6946b)), closes [#8037](https://github.com/react-navigation/react-navigation/issues/8037) [#8536](https://github.com/react-navigation/react-navigation/issues/8536)
* add a tabBarBadgeStyle option to customize the badge ([6ac4d40](https://github.com/react-navigation/react-navigation/commit/6ac4d40140189a29d857c4d1203bced6929f7baf))
## [5.10.7](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@5.10.6...@react-navigation/bottom-tabs@5.10.7) (2020-11-08)
**Note:** Version bump only for package @react-navigation/bottom-tabs
## [5.10.6](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@5.10.5...@react-navigation/bottom-tabs@5.10.6) (2020-11-04)
### Bug Fixes
* disable react-native-screens on iOS for older versions ([ce7d20e](https://github.com/react-navigation/react-navigation/commit/ce7d20e3366415b07a537e01ee0b17ce7e72cad6))
## [5.10.5](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@5.10.4...@react-navigation/bottom-tabs@5.10.5) (2020-11-04)
**Note:** Version bump only for package @react-navigation/bottom-tabs
## [5.10.4](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@5.10.3...@react-navigation/bottom-tabs@5.10.4) (2020-11-03)
**Note:** Version bump only for package @react-navigation/bottom-tabs
## [5.10.3](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@5.10.2...@react-navigation/bottom-tabs@5.10.3) (2020-11-03)
**Note:** Version bump only for package @react-navigation/bottom-tabs
## [5.10.2](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@5.10.1...@react-navigation/bottom-tabs@5.10.2) (2020-10-30)
**Note:** Version bump only for package @react-navigation/bottom-tabs
## [5.10.1](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@5.10.0...@react-navigation/bottom-tabs@5.10.1) (2020-10-28)
**Note:** Version bump only for package @react-navigation/bottom-tabs
# [5.10.0](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@5.9.2...@react-navigation/bottom-tabs@5.10.0) (2020-10-24)
### Features
* add optional screens per navigator ([#8805](https://github.com/react-navigation/react-navigation/issues/8805)) ([7196889](https://github.com/react-navigation/react-navigation/commit/7196889bf1218eb6a736d9475e33a909c2248c3b))
* add sceneContainerStyle prop to bottom-tabs navigator ([#8947](https://github.com/react-navigation/react-navigation/issues/8947)) ([f01bb48](https://github.com/react-navigation/react-navigation/commit/f01bb4834b01e13ab9a6b220328349f77ca49428))
* improve types for navigation state ([#8980](https://github.com/react-navigation/react-navigation/issues/8980)) ([7dc2f58](https://github.com/react-navigation/react-navigation/commit/7dc2f5832e371473f3263c01ab39824eb9e2057d))
* update helper types to have navigator specific methods ([f51086e](https://github.com/react-navigation/react-navigation/commit/f51086edea42f2382dac8c6914aac8574132114b))
## [5.9.2](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@5.9.1...@react-navigation/bottom-tabs@5.9.2) (2020-10-07)

View File

@@ -2,4 +2,4 @@
Bottom tab navigator for React Navigation following iOS design guidelines.
Installation instructions and documentation can be found on the [React Navigation website](https://reactnavigation.org/docs/bottom-tab-navigator/).
Installation instructions and documentation can be found on the [React Navigation website](https://reactnavigation.org/docs/6.x/bottom-tab-navigator/).

View File

@@ -1,7 +1,7 @@
{
"name": "@react-navigation/bottom-tabs",
"description": "Bottom tab navigator following iOS design guidelines",
"version": "5.9.2",
"version": "6.0.0-next.8",
"keywords": [
"react-native-component",
"react-component",
@@ -36,31 +36,32 @@
"clean": "del lib"
},
"dependencies": {
"color": "^3.1.2",
"react-native-iphone-x-helper": "^1.2.1"
"@react-navigation/elements": "^1.0.0-next.7",
"color": "^3.1.3",
"warn-once": "^0.0.1"
},
"devDependencies": {
"@react-native-community/bob": "^0.16.2",
"@react-navigation/native": "^5.7.6",
"@testing-library/react-native": "^7.0.2",
"@react-navigation/native": "^6.0.0-next.5",
"@testing-library/react-native": "^7.2.0",
"@types/color": "^3.0.1",
"@types/react": "^16.9.51",
"@types/react-native": "^0.63.25",
"@types/react": "^16.9.53",
"@types/react-native": "~0.64.4",
"del-cli": "^3.0.1",
"react": "~16.13.1",
"react-native": "~0.63.2",
"react-native-safe-area-context": "3.1.4",
"react-native-screens": "~2.10.1",
"typescript": "^4.0.3"
"react-native": "~0.63.4",
"react-native-builder-bob": "^0.18.1",
"react-native-safe-area-context": "~3.2.0",
"react-native-screens": "~3.0.0",
"typescript": "^4.2.3"
},
"peerDependencies": {
"@react-navigation/native": "^5.0.5",
"@react-navigation/native": "^6.0.0",
"react": "*",
"react-native": "*",
"react-native-safe-area-context": ">= 0.6.0",
"react-native-screens": ">= 2.0.0-alpha.0 || >= 2.0.0-beta.0 || >= 2.0.0"
"react-native-safe-area-context": ">= 3.0.0",
"react-native-screens": ">= 3.0.0"
},
"@react-native-community/bob": {
"react-native-builder-bob": {
"source": "src",
"output": "lib",
"targets": [

View File

@@ -9,6 +9,13 @@ export { default as createBottomTabNavigator } from './navigators/createBottomTa
export { default as BottomTabView } from './views/BottomTabView';
export { default as BottomTabBar } from './views/BottomTabBar';
/**
* Utilities
*/
export { default as BottomTabBarHeightContext } from './utils/BottomTabBarHeightContext';
export { default as useBottomTabBarHeight } from './utils/useBottomTabBarHeight';
/**
* Types
*/
@@ -17,6 +24,5 @@ export type {
BottomTabNavigationProp,
BottomTabScreenProps,
BottomTabBarProps,
BottomTabBarOptions,
BottomTabBarButtonProps,
} from './types';

View File

@@ -1,4 +1,5 @@
import * as React from 'react';
import warnOnce from 'warn-once';
import {
useNavigationBuilder,
createNavigatorFactory,
@@ -6,6 +7,8 @@ import {
TabRouter,
TabRouterOptions,
TabNavigationState,
TabActionHelpers,
ParamListBase,
} from '@react-navigation/native';
import BottomTabView from '../views/BottomTabView';
import type {
@@ -23,11 +26,54 @@ function BottomTabNavigator({
backBehavior,
children,
screenOptions,
sceneContainerStyle,
// @ts-expect-error: lazy is deprecated
lazy,
// @ts-expect-error: tabBarOptions is deprecated
tabBarOptions,
...rest
}: Props) {
let defaultScreenOptions: BottomTabNavigationOptions = {};
if (tabBarOptions) {
Object.assign(defaultScreenOptions, {
tabBarHideOnKeyboard: tabBarOptions.keyboardHidesTabBar,
tabBarActiveTintColor: tabBarOptions.activeTintColor,
tabBarInactiveTintColor: tabBarOptions.inactiveTintColor,
tabBarActiveBackgroundColor: tabBarOptions.activeBackgroundColor,
tabBarInactiveBackgroundColor: tabBarOptions.inactiveBackgroundColor,
tabBarAllowFontScaling: tabBarOptions.allowFontScaling,
tabBarShowLabel: tabBarOptions.showLabel,
tabBarLabelStyle: tabBarOptions.labelStyle,
tabBarIconStyle: tabBarOptions.iconStyle,
tabBarItemStyle: tabBarOptions.tabStyle,
tabBarLabelPosition: tabBarOptions.labelPosition,
tabBarAdaptive: tabBarOptions.adaptive,
});
warnOnce(
tabBarOptions,
`Bottom Tab Navigator: 'tabBarOptions' is deprecated. Migrate the options to 'screenOptions' instead.\n\nPlace the following in 'screenOptions' in your code to keep current behavior:\n\n${JSON.stringify(
defaultScreenOptions,
null,
2
)}\n\nSee https://reactnavigation.org/docs/6.x/bottom-tab-navigator#options for more details.`
);
}
if (typeof lazy === 'boolean') {
defaultScreenOptions.lazy = lazy;
warnOnce(
true,
`Bottom Tab Navigator: 'lazy' in props is deprecated. Move it to 'screenOptions' instead.`
);
}
const { state, descriptors, navigation } = useNavigationBuilder<
TabNavigationState,
TabNavigationState<ParamListBase>,
TabRouterOptions,
TabActionHelpers<ParamListBase>,
BottomTabNavigationOptions,
BottomTabNavigationEventMap
>(TabRouter, {
@@ -35,6 +81,7 @@ function BottomTabNavigator({
backBehavior,
children,
screenOptions,
defaultScreenOptions,
});
return (
@@ -43,12 +90,13 @@ function BottomTabNavigator({
state={state}
navigation={navigation}
descriptors={descriptors}
sceneContainerStyle={sceneContainerStyle}
/>
);
}
export default createNavigatorFactory<
TabNavigationState,
TabNavigationState<ParamListBase>,
BottomTabNavigationOptions,
BottomTabNavigationEventMap,
typeof BottomTabNavigator

View File

@@ -16,6 +16,10 @@ import type {
TabActionHelpers,
RouteProp,
} from '@react-navigation/native';
import type { EdgeInsets } from 'react-native-safe-area-context';
import type { HeaderOptions } from '@react-navigation/elements';
export type Layout = { width: number; height: number };
export type BottomTabNavigationEventMap = {
/**
@@ -33,15 +37,16 @@ export type LabelPosition = 'beside-icon' | 'below-icon';
export type BottomTabNavigationHelpers = NavigationHelpers<
ParamListBase,
BottomTabNavigationEventMap
>;
> &
TabActionHelpers<ParamListBase>;
export type BottomTabNavigationProp<
ParamList extends ParamListBase,
RouteName extends keyof ParamList = string
RouteName extends keyof ParamList = keyof ParamList
> = NavigationProp<
ParamList,
RouteName,
TabNavigationState,
TabNavigationState<ParamList>,
BottomTabNavigationOptions,
BottomTabNavigationEventMap
> &
@@ -49,7 +54,7 @@ export type BottomTabNavigationProp<
export type BottomTabScreenProps<
ParamList extends ParamListBase,
RouteName extends keyof ParamList = string
RouteName extends keyof ParamList = keyof ParamList
> = {
navigation: BottomTabNavigationProp<ParamList, RouteName>;
route: RouteProp<ParamList, RouteName>;
@@ -75,16 +80,34 @@ export type TabBarVisibilityAnimationConfig =
| TimingKeyboardAnimationConfig
| SpringKeyboardAnimationConfig;
export type BottomTabNavigationOptions = {
export type BottomTabNavigationOptions = HeaderOptions & {
/**
* Title text for the screen.
*/
title?: string;
/**
* Whether this screens should render the first time it's accessed. Defaults to `true`.
* Set it to `false` if you want to render the screen on initial render.
*/
lazy?: boolean;
/**
* Function that given returns a React Element to display as a header.
*/
header?: (props: BottomTabHeaderProps) => React.ReactNode;
/**
* Whether to show the header. Setting this to `false` hides the header.
* Defaults to `true`.
*/
headerShown?: boolean;
/**
* Title string of a tab displayed in the tab bar
* or a function that given { focused: boolean, color: string, position: 'below-icon' | 'beside-icon' } returns a React.Node to display in tab bar.
* When undefined, scene title is used. To hide, see tabBarOptions.showLabel in the previous section.
*
* When undefined, scene title is used. Use `tabBarShowLabel` to hide the label.
*/
tabBarLabel?:
| string
@@ -108,6 +131,12 @@ export type BottomTabNavigationOptions = {
*/
tabBarBadge?: number | string;
/**
* Custom style for the tab bar badge.
* You can specify a background color or text color here.
*/
tabBarBadgeStyle?: StyleProp<TextStyle>;
/**
* Accessibility label for the tab button. This is read by the screen reader when the user taps the tab.
* It's recommended to set this if you don't have a label for the tab.
@@ -119,11 +148,6 @@ export type BottomTabNavigationOptions = {
*/
tabBarTestID?: string;
/**
* Boolean indicating whether the tab bar is visible when this screen is active.
*/
tabBarVisible?: boolean;
/**
* Animation config for showing and hiding the tab bar.
*/
@@ -138,6 +162,73 @@ export type BottomTabNavigationOptions = {
*/
tabBarButton?: (props: BottomTabBarButtonProps) => React.ReactNode;
/**
* Color for the icon and label in the active tab.
*/
tabBarActiveTintColor?: string;
/**
* Color for the icon and label in the inactive tabs.
*/
tabBarInactiveTintColor?: string;
/**
* Background color for the active tab.
*/
tabBarActiveBackgroundColor?: string;
/**
* background color for the inactive tabs.
*/
tabBarInactiveBackgroundColor?: string;
/**
* Whether label font should scale to respect Text Size accessibility settings.
*/
tabBarAllowFontScaling?: boolean;
/**
* Whether the tab label should be visible. Defaults to `true`.
*/
tabBarShowLabel?: boolean;
/**
* Style object for the tab label.
*/
tabBarLabelStyle?: StyleProp<TextStyle>;
/**
* Style object for the tab icon.
*/
tabBarIconStyle?: StyleProp<TextStyle>;
/**
* Style object for the tab item container.
*/
tabBarItemStyle?: StyleProp<ViewStyle>;
/**
* Whether the label is rendered below the icon or beside the icon.
* By default, the position is chosen automatically based on device width.
* In `below-icon` orientation (typical for iPhones), the label is rendered below and in `beside-icon` orientation, it's rendered beside (typical for iPad).
*/
tabBarLabelPosition?: LabelPosition;
/**
* Whether the label position should adapt to the orientation.
*/
tabBarAdaptive?: boolean;
/**
* Whether the tab bar gets hidden when the keyboard is shown. Defaults to `false`.
*/
tabBarHideOnKeyboard?: boolean;
/**
* Style object for the tab bar container.
*/
tabBarStyle?: Animated.WithAnimatedValue<StyleProp<ViewStyle>>;
/**
* Whether this screen should be unmounted when navigating away from it.
* Defaults to `false`.
@@ -146,83 +237,18 @@ export type BottomTabNavigationOptions = {
};
export type BottomTabDescriptor = Descriptor<
ParamListBase,
string,
TabNavigationState,
BottomTabNavigationOptions
BottomTabNavigationOptions,
BottomTabNavigationProp<ParamListBase>,
RouteProp<ParamListBase>
>;
export type BottomTabDescriptorMap = {
[key: string]: BottomTabDescriptor;
};
export type BottomTabDescriptorMap = Record<string, BottomTabDescriptor>;
export type BottomTabNavigationConfig<T = BottomTabBarOptions> = {
/**
* Whether the screens should render the first time they are accessed. Defaults to `true`.
* Set it to `false` if you want to render all screens on initial render.
*/
lazy?: boolean;
export type BottomTabNavigationConfig = {
/**
* Function that returns a React element to display as the tab bar.
*/
tabBar?: (props: BottomTabBarProps<T>) => React.ReactNode;
/**
* Options for the tab bar which will be passed as props to the tab bar component.
*/
tabBarOptions?: T;
};
export type BottomTabBarOptions = {
/**
* Whether the tab bar gets hidden when the keyboard is shown. Defaults to `false`.
*/
keyboardHidesTabBar?: boolean;
/**
* Color for the icon and label in the active tab.
*/
activeTintColor?: string;
/**
* Color for the icon and label in the inactive tabs.
*/
inactiveTintColor?: string;
/**
* Background color for the active tab.
*/
activeBackgroundColor?: string;
/**
* background color for the inactive tabs.
*/
inactiveBackgroundColor?: string;
/**
* Whether label font should scale to respect Text Size accessibility settings.
*/
allowFontScaling?: boolean;
/**
* Whether the tab label should be visible. Defaults to `true`.
*/
showLabel?: boolean;
/**
* Style object for the tab label.
*/
labelStyle?: StyleProp<TextStyle>;
/**
* Style object for the tab icon.
*/
iconStyle?: StyleProp<TextStyle>;
/**
* Style object for the tab container.
*/
tabStyle?: StyleProp<ViewStyle>;
/**
* Whether the label is rendered below the icon or beside the icon.
* By default, the position is chosen automatically based on device width.
* In `below-icon` orientation (typical for iPhones), the label is rendered below and in `beside-icon` orientation, it's rendered beside (typical for iPad).
*/
labelPosition?: LabelPosition;
/**
* Whether the label position should adapt to the orientation.
*/
adaptive?: boolean;
tabBar?: (props: BottomTabBarProps) => React.ReactNode;
/**
* Safe area insets for the tab bar. This is used to avoid elements like the navigation bar on Android and bottom safe area on iOS.
* By default, the device's safe area insets are automatically detected. You can override the behavior with this option.
@@ -234,15 +260,41 @@ export type BottomTabBarOptions = {
left?: number;
};
/**
* Style object for the tab bar container.
* Whether inactive screens should be detached from the view hierarchy to save memory.
* Make sure to call `enableScreens` from `react-native-screens` to make it work.
* Defaults to `true` on Android.
*/
style?: Animated.WithAnimatedValue<StyleProp<ViewStyle>>;
detachInactiveScreens?: boolean;
/**
* Style object for the component wrapping the screen content.
*/
sceneContainerStyle?: StyleProp<ViewStyle>;
};
export type BottomTabBarProps<T = BottomTabBarOptions> = T & {
state: TabNavigationState;
export type BottomTabHeaderProps = {
/**
* Layout of the screen.
*/
layout: Layout;
/**
* Options for the current screen.
*/
options: BottomTabNavigationOptions;
/**
* Route object for the current screen.
*/
route: RouteProp<ParamListBase>;
/**
* Navigation prop for the header.
*/
navigation: BottomTabNavigationProp<ParamListBase>;
};
export type BottomTabBarProps = {
state: TabNavigationState<ParamListBase>;
descriptors: BottomTabDescriptorMap;
navigation: NavigationHelpers<ParamListBase, BottomTabNavigationEventMap>;
insets: EdgeInsets;
};
export type BottomTabBarButtonProps = Omit<

View File

@@ -0,0 +1,5 @@
import * as React from 'react';
export default React.createContext<((height: number) => void) | undefined>(
undefined
);

View File

@@ -0,0 +1,14 @@
import * as React from 'react';
import BottomTabBarHeightContext from './BottomTabBarHeightContext';
export default function useFloatingBottomTabBarHeight() {
const height = React.useContext(BottomTabBarHeightContext);
if (height === undefined) {
throw new Error(
"Couldn't find the bottom tab bar height. Are you inside a screen in Bottom Tab Navigator?"
);
}
return height;
}

View File

@@ -1,5 +1,5 @@
import * as React from 'react';
import { Keyboard, Platform } from 'react-native';
import { Keyboard, Platform, EmitterSubscription } from 'react-native';
export default function useIsKeyboardShown() {
const [isKeyboardShown, setIsKeyboardShown] = React.useState(false);
@@ -8,22 +8,22 @@ export default function useIsKeyboardShown() {
const handleKeyboardShow = () => setIsKeyboardShown(true);
const handleKeyboardHide = () => setIsKeyboardShown(false);
let subscriptions: EmitterSubscription[];
if (Platform.OS === 'ios') {
Keyboard.addListener('keyboardWillShow', handleKeyboardShow);
Keyboard.addListener('keyboardWillHide', handleKeyboardHide);
subscriptions = [
Keyboard.addListener('keyboardWillShow', handleKeyboardShow),
Keyboard.addListener('keyboardWillHide', handleKeyboardHide),
];
} else {
Keyboard.addListener('keyboardDidShow', handleKeyboardShow);
Keyboard.addListener('keyboardDidHide', handleKeyboardHide);
subscriptions = [
Keyboard.addListener('keyboardDidShow', handleKeyboardShow),
Keyboard.addListener('keyboardDidHide', handleKeyboardHide),
];
}
return () => {
if (Platform.OS === 'ios') {
Keyboard.removeListener('keyboardWillShow', handleKeyboardShow);
Keyboard.removeListener('keyboardWillHide', handleKeyboardHide);
} else {
Keyboard.removeListener('keyboardDidShow', handleKeyboardShow);
Keyboard.removeListener('keyboardDidHide', handleKeyboardHide);
}
subscriptions.forEach((s) => s.remove());
};
}, []);

View File

@@ -1,37 +0,0 @@
import * as React from 'react';
import { ScaledSize, Dimensions } from 'react-native';
// This is similar to the new useWindowDimensions hook in react-native
// However, we have a custom implementation to support older RN versions
export default function useWindowDimensions() {
const [dimensions, setDimensions] = React.useState(() => {
// `height` and `width` maybe undefined during SSR, so we initialize them
const { height = 0, width = 0 } = Dimensions.get('window');
return { height, width };
});
React.useEffect(() => {
const onChange = ({ window }: { window: ScaledSize }) => {
const { width, height } = window;
setDimensions((d) => {
if (width === d.width && height === d.height) {
return d;
}
return { width, height };
});
};
// We might have missed an update before the listener was added
// So make sure to update the dimensions
onChange({ window: Dimensions.get('window') });
Dimensions.addEventListener('change', onChange);
return () => Dimensions.removeEventListener('change', onChange);
}, []);
return dimensions;
}

View File

@@ -5,24 +5,28 @@ import {
StyleSheet,
Platform,
LayoutChangeEvent,
StyleProp,
ViewStyle,
} from 'react-native';
import {
NavigationContext,
NavigationRouteContext,
TabNavigationState,
ParamListBase,
CommonActions,
useTheme,
useLinkBuilder,
} from '@react-navigation/native';
import { useSafeArea } from 'react-native-safe-area-context';
import { MissingIcon } from '@react-navigation/elements';
import { EdgeInsets, useSafeAreaFrame } from 'react-native-safe-area-context';
import BottomTabItem from './BottomTabItem';
import useWindowDimensions from '../utils/useWindowDimensions';
import BottomTabBarHeightCallbackContext from '../utils/BottomTabBarHeightCallbackContext';
import useIsKeyboardShown from '../utils/useIsKeyboardShown';
import type { BottomTabBarProps } from '../types';
import type { BottomTabBarProps, BottomTabDescriptorMap } from '../types';
type Props = BottomTabBarProps & {
activeTintColor?: string;
inactiveTintColor?: string;
style?: Animated.WithAnimatedValue<StyleProp<ViewStyle>>;
};
const DEFAULT_TABBAR_HEIGHT = 49;
@@ -31,24 +35,102 @@ const DEFAULT_MAX_TAB_ITEM_WIDTH = 125;
const useNativeDriver = Platform.OS !== 'web';
type Options = {
state: TabNavigationState<ParamListBase>;
descriptors: BottomTabDescriptorMap;
layout: { height: number; width: number };
dimensions: { height: number; width: number };
};
const shouldUseHorizontalLabels = ({
state,
descriptors,
layout,
dimensions,
}: Options) => {
const { tabBarLabelPosition, tabBarAdaptive = true } = descriptors[
state.routes[state.index].key
].options;
if (tabBarLabelPosition) {
return tabBarLabelPosition === 'beside-icon';
}
if (!tabBarAdaptive) {
return false;
}
if (layout.width >= 768) {
// Screen size matches a tablet
const maxTabWidth = state.routes.reduce((acc, route) => {
const { tabBarItemStyle } = descriptors[route.key].options;
const flattenedStyle = StyleSheet.flatten(tabBarItemStyle);
if (flattenedStyle) {
if (typeof flattenedStyle.width === 'number') {
return acc + flattenedStyle.width;
} else if (typeof flattenedStyle.maxWidth === 'number') {
return acc + flattenedStyle.maxWidth;
}
}
return acc + DEFAULT_MAX_TAB_ITEM_WIDTH;
}, 0);
return maxTabWidth <= layout.width;
} else {
return dimensions.width > dimensions.height;
}
};
const getPaddingBottom = (insets: EdgeInsets) =>
Math.max(insets.bottom - Platform.select({ ios: 4, default: 0 }), 0);
export const getTabBarHeight = ({
state,
descriptors,
dimensions,
insets,
style,
...rest
}: Options & {
insets: EdgeInsets;
style: Animated.WithAnimatedValue<StyleProp<ViewStyle>> | undefined;
}) => {
// @ts-ignore
const customHeight = StyleSheet.flatten(style)?.height;
if (typeof customHeight === 'number') {
return customHeight;
}
const isLandscape = dimensions.width > dimensions.height;
const horizontalLabels = shouldUseHorizontalLabels({
state,
descriptors,
dimensions,
...rest,
});
const paddingBottom = getPaddingBottom(insets);
if (
Platform.OS === 'ios' &&
!Platform.isPad &&
isLandscape &&
horizontalLabels
) {
return COMPACT_TABBAR_HEIGHT + paddingBottom;
}
return DEFAULT_TABBAR_HEIGHT + paddingBottom;
};
export default function BottomTabBar({
state,
navigation,
descriptors,
activeBackgroundColor,
activeTintColor,
adaptive = true,
allowFontScaling,
inactiveBackgroundColor,
inactiveTintColor,
keyboardHidesTabBar = false,
labelPosition,
labelStyle,
iconStyle,
safeAreaInsets,
showLabel,
insets,
style,
tabStyle,
}: Props) {
const { colors } = useTheme();
const buildLink = useLinkBuilder();
@@ -57,20 +139,26 @@ export default function BottomTabBar({
const focusedDescriptor = descriptors[focusedRoute.key];
const focusedOptions = focusedDescriptor.options;
const dimensions = useWindowDimensions();
const {
tabBarShowLabel,
tabBarHideOnKeyboard = false,
tabBarVisibilityAnimationConfig,
tabBarStyle,
} = focusedOptions;
const dimensions = useSafeAreaFrame();
const isKeyboardShown = useIsKeyboardShown();
const shouldShowTabBar =
focusedOptions.tabBarVisible !== false &&
!(keyboardHidesTabBar && isKeyboardShown);
const onHeightChange = React.useContext(BottomTabBarHeightCallbackContext);
const shouldShowTabBar = !(tabBarHideOnKeyboard && isKeyboardShown);
const visibilityAnimationConfigRef = React.useRef(
focusedOptions.tabBarVisibilityAnimationConfig
tabBarVisibilityAnimationConfig
);
React.useEffect(() => {
visibilityAnimationConfigRef.current =
focusedOptions.tabBarVisibilityAnimationConfig;
visibilityAnimationConfigRef.current = tabBarVisibilityAnimationConfig;
});
const [isTabBarHidden, setIsTabBarHidden] = React.useState(!shouldShowTabBar);
@@ -120,11 +208,19 @@ export default function BottomTabBar({
width: dimensions.width,
});
const isLandscape = () => dimensions.width > dimensions.height;
const handleLayout = (e: LayoutChangeEvent) => {
const { height, width } = e.nativeEvent.layout;
const topBorderWidth =
// @ts-ignore
StyleSheet.flatten([styles.tabBar, tabBarStyle])?.borderTopWidth;
onHeightChange?.(
height +
paddingBottom +
(typeof topBorderWidth === 'number' ? topBorderWidth : 0)
);
setLayout((layout) => {
if (height === layout.height && width === layout.width) {
return layout;
@@ -138,60 +234,23 @@ export default function BottomTabBar({
};
const { routes } = state;
const shouldUseHorizontalLabels = () => {
if (labelPosition) {
return labelPosition === 'beside-icon';
}
if (!adaptive) {
return false;
}
const paddingBottom = getPaddingBottom(insets);
const tabBarHeight = getTabBarHeight({
state,
descriptors,
insets,
dimensions,
layout,
style: [tabBarStyle, style],
});
if (layout.width >= 768) {
// Screen size matches a tablet
let maxTabItemWidth = DEFAULT_MAX_TAB_ITEM_WIDTH;
const flattenedStyle = StyleSheet.flatten(tabStyle);
if (flattenedStyle) {
if (typeof flattenedStyle.width === 'number') {
maxTabItemWidth = flattenedStyle.width;
} else if (typeof flattenedStyle.maxWidth === 'number') {
maxTabItemWidth = flattenedStyle.maxWidth;
}
}
return routes.length * maxTabItemWidth <= layout.width;
} else {
return isLandscape();
}
};
const defaultInsets = useSafeArea();
const insets = {
top: safeAreaInsets?.top ?? defaultInsets.top,
right: safeAreaInsets?.right ?? defaultInsets.right,
bottom: safeAreaInsets?.bottom ?? defaultInsets.bottom,
left: safeAreaInsets?.left ?? defaultInsets.left,
};
const paddingBottom = Math.max(
insets.bottom - Platform.select({ ios: 4, default: 0 }),
0
);
const getDefaultTabBarHeight = () => {
if (
Platform.OS === 'ios' &&
!Platform.isPad &&
isLandscape() &&
shouldUseHorizontalLabels()
) {
return COMPACT_TABBAR_HEIGHT;
}
return DEFAULT_TABBAR_HEIGHT;
};
const hasHorizontalLabels = shouldUseHorizontalLabels({
state,
descriptors,
dimensions,
layout,
});
return (
<Animated.View
@@ -218,15 +277,16 @@ export default function BottomTabBar({
position: isTabBarHidden ? 'absolute' : (null as any),
},
{
height: getDefaultTabBarHeight() + paddingBottom,
height: tabBarHeight,
paddingBottom,
paddingHorizontal: Math.max(insets.left, insets.right),
},
style,
tabBarStyle,
]}
pointerEvents={isTabBarHidden ? 'none' : 'auto'}
onLayout={handleLayout}
>
<View style={styles.content} onLayout={handleLayout}>
<View accessibilityRole="tablist" style={styles.content}>
{routes.map((route, index) => {
const focused = index === state.index;
const { options } = descriptors[route.key];
@@ -263,7 +323,7 @@ export default function BottomTabBar({
const accessibilityLabel =
options.tabBarAccessibilityLabel !== undefined
? options.tabBarAccessibilityLabel
: typeof label === 'string'
: typeof label === 'string' && Platform.OS === 'ios'
? `${label}, tab, ${index + 1} of ${routes.length}`
: undefined;
@@ -276,25 +336,33 @@ export default function BottomTabBar({
<BottomTabItem
route={route}
focused={focused}
horizontal={shouldUseHorizontalLabels()}
horizontal={hasHorizontalLabels}
onPress={onPress}
onLongPress={onLongPress}
accessibilityLabel={accessibilityLabel}
to={buildLink(route.name, route.params)}
testID={options.tabBarTestID}
allowFontScaling={allowFontScaling}
activeTintColor={activeTintColor}
inactiveTintColor={inactiveTintColor}
activeBackgroundColor={activeBackgroundColor}
inactiveBackgroundColor={inactiveBackgroundColor}
allowFontScaling={options.tabBarAllowFontScaling}
activeTintColor={options.tabBarActiveTintColor}
inactiveTintColor={options.tabBarInactiveTintColor}
activeBackgroundColor={options.tabBarActiveBackgroundColor}
inactiveBackgroundColor={
options.tabBarInactiveBackgroundColor
}
button={options.tabBarButton}
icon={options.tabBarIcon}
icon={
options.tabBarIcon ??
(({ color, size }) => (
<MissingIcon color={color} size={size} />
))
}
badge={options.tabBarBadge}
badgeStyle={options.tabBarBadgeStyle}
label={label}
showLabel={showLabel}
labelStyle={labelStyle}
iconStyle={iconStyle}
style={tabStyle}
showLabel={tabBarShowLabel}
labelStyle={options.tabBarLabelStyle}
iconStyle={options.tabBarIconStyle}
style={options.tabBarItemStyle}
/>
</NavigationRouteContext.Provider>
</NavigationContext.Provider>

View File

@@ -1,8 +1,7 @@
import React from 'react';
import {
View,
Text,
TouchableWithoutFeedback,
Pressable,
StyleSheet,
Platform,
StyleProp,
@@ -38,7 +37,7 @@ type Props = {
/**
* Icon to display for the tab.
*/
icon?: (props: {
icon: (props: {
focused: boolean;
size: number;
color: string;
@@ -47,6 +46,10 @@ type Props = {
* Text to show in a badge on the tab icon.
*/
badge?: number | string;
/**
* Custom style for the badge.
*/
badgeStyle?: StyleProp<TextStyle>;
/**
* URL to use for the link to the tab.
*/
@@ -122,6 +125,7 @@ export default function BottomTabBarItem({
label,
icon,
badge,
badgeStyle,
to,
button = ({
children,
@@ -154,13 +158,14 @@ export default function BottomTabBarItem({
);
} else {
return (
<TouchableWithoutFeedback
<Pressable
{...rest}
accessibilityRole={accessibilityRole}
onPress={onPress}
style={style}
>
<View style={style}>{children}</View>
</TouchableWithoutFeedback>
{children}
</Pressable>
);
}
},
@@ -235,6 +240,7 @@ export default function BottomTabBarItem({
route={route}
horizontal={horizontal}
badge={badge}
badgeStyle={badgeStyle}
activeOpacity={activeOpacity}
inactiveOpacity={inactiveOpacity}
activeTintColor={activeTintColor}
@@ -257,7 +263,8 @@ export default function BottomTabBarItem({
onLongPress,
testID,
accessibilityLabel,
accessibilityRole: 'button',
// FIXME: accessibilityRole: 'tab' doesn't seem to work as expected on iOS
accessibilityRole: Platform.select({ ios: 'button', default: 'tab' }),
accessibilityState: { selected: focused },
// @ts-expect-error: keep for compatibility with older React Native versions
accessibilityStates: focused ? ['selected'] : [],

View File

@@ -1,132 +1,161 @@
import * as React from 'react';
import { View, StyleSheet } from 'react-native';
import { StyleSheet, Platform } from 'react-native';
import { SafeAreaInsetsContext } from 'react-native-safe-area-context';
import {
NavigationHelpersContext,
ParamListBase,
TabNavigationState,
useTheme,
} from '@react-navigation/native';
import { ScreenContainer } from 'react-native-screens';
import SafeAreaProviderCompat from './SafeAreaProviderCompat';
import ResourceSavingScene from './ResourceSavingScene';
import BottomTabBar from './BottomTabBar';
import {
Header,
Screen,
SafeAreaProviderCompat,
getHeaderTitle,
} from '@react-navigation/elements';
import { MaybeScreenContainer, MaybeScreen } from './ScreenFallback';
import BottomTabBar, { getTabBarHeight } from './BottomTabBar';
import BottomTabBarHeightCallbackContext from '../utils/BottomTabBarHeightCallbackContext';
import BottomTabBarHeightContext from '../utils/BottomTabBarHeightContext';
import type {
BottomTabNavigationConfig,
BottomTabDescriptorMap,
BottomTabNavigationHelpers,
BottomTabBarProps,
BottomTabHeaderProps,
BottomTabNavigationProp,
} from '../types';
type Props = BottomTabNavigationConfig & {
state: TabNavigationState;
state: TabNavigationState<ParamListBase>;
navigation: BottomTabNavigationHelpers;
descriptors: BottomTabDescriptorMap;
};
type State = {
loaded: string[];
};
export default function BottomTabView(props: Props) {
const {
tabBar = (props: BottomTabBarProps) => <BottomTabBar {...props} />,
state,
navigation,
descriptors,
safeAreaInsets,
detachInactiveScreens = Platform.OS === 'web' ||
Platform.OS === 'android' ||
Platform.OS === 'ios',
sceneContainerStyle,
} = props;
function SceneContent({
isFocused,
children,
}: {
isFocused: boolean;
children: React.ReactNode;
}) {
const { colors } = useTheme();
const focusedRouteKey = state.routes[state.index].key;
const [loaded, setLoaded] = React.useState([focusedRouteKey]);
if (!loaded.includes(focusedRouteKey)) {
setLoaded([...loaded, focusedRouteKey]);
}
const dimensions = SafeAreaProviderCompat.initialMetrics.frame;
const [tabBarHeight, setTabBarHeight] = React.useState(() =>
getTabBarHeight({
state,
descriptors,
dimensions,
layout: { width: dimensions.width, height: 0 },
insets: {
...SafeAreaProviderCompat.initialMetrics.insets,
...props.safeAreaInsets,
},
style: descriptors[state.routes[state.index].key].options.tabBarStyle,
})
);
const renderTabBar = () => {
return (
<SafeAreaInsetsContext.Consumer>
{(insets) =>
tabBar({
state: state,
descriptors: descriptors,
navigation: navigation,
insets: {
top: safeAreaInsets?.top ?? insets?.top ?? 0,
right: safeAreaInsets?.right ?? insets?.right ?? 0,
bottom: safeAreaInsets?.bottom ?? insets?.bottom ?? 0,
left: safeAreaInsets?.left ?? insets?.left ?? 0,
},
})
}
</SafeAreaInsetsContext.Consumer>
);
};
const { routes } = state;
return (
<View
accessibilityElementsHidden={!isFocused}
importantForAccessibility={isFocused ? 'auto' : 'no-hide-descendants'}
style={[styles.content, { backgroundColor: colors.background }]}
>
{children}
</View>
);
}
<NavigationHelpersContext.Provider value={navigation}>
<SafeAreaProviderCompat>
<MaybeScreenContainer
enabled={detachInactiveScreens}
style={styles.container}
>
{routes.map((route, index) => {
const descriptor = descriptors[route.key];
const { lazy = true, unmountOnBlur } = descriptor.options;
const isFocused = state.index === index;
export default class BottomTabView extends React.Component<Props, State> {
static defaultProps = {
lazy: true,
};
if (unmountOnBlur && !isFocused) {
return null;
}
static getDerivedStateFromProps(nextProps: Props, prevState: State) {
const focusedRouteKey = nextProps.state.routes[nextProps.state.index].key;
if (lazy && !loaded.includes(route.key) && !isFocused) {
// Don't render a lazy screen if we've never navigated to it
return null;
}
return {
// Set the current tab to be loaded if it was not loaded before
loaded: prevState.loaded.includes(focusedRouteKey)
? prevState.loaded
: [...prevState.loaded, focusedRouteKey],
};
}
const {
header = ({ layout, options }: BottomTabHeaderProps) => (
<Header
{...options}
layout={layout}
title={getHeaderTitle(options, route.name)}
/>
),
} = descriptor.options;
state: State = {
loaded: [this.props.state.routes[this.props.state.index].key],
};
private renderTabBar = () => {
const {
tabBar = (props: BottomTabBarProps) => <BottomTabBar {...props} />,
tabBarOptions,
state,
navigation,
descriptors,
} = this.props;
return tabBar({
...tabBarOptions,
state: state,
descriptors: descriptors,
navigation: navigation,
});
};
render() {
const { state, descriptors, navigation, lazy } = this.props;
const { routes } = state;
const { loaded } = this.state;
return (
<NavigationHelpersContext.Provider value={navigation}>
<SafeAreaProviderCompat>
<View style={styles.container}>
<ScreenContainer style={styles.pages}>
{routes.map((route, index) => {
const descriptor = descriptors[route.key];
const { unmountOnBlur } = descriptor.options;
const isFocused = state.index === index;
if (unmountOnBlur && !isFocused) {
return null;
}
if (lazy && !loaded.includes(route.key) && !isFocused) {
// Don't render a screen if we've never navigated to it
return null;
}
return (
<ResourceSavingScene
key={route.key}
style={StyleSheet.absoluteFill}
isVisible={isFocused}
return (
<MaybeScreen
key={route.key}
style={StyleSheet.absoluteFill}
visible={isFocused}
enabled={detachInactiveScreens}
>
<BottomTabBarHeightContext.Provider value={tabBarHeight}>
<Screen
focused={isFocused}
route={descriptor.route}
navigation={descriptor.navigation}
headerShown={descriptor.options.headerShown}
headerStatusBarHeight={
descriptor.options.headerStatusBarHeight
}
header={header({
layout: dimensions,
route: descriptor.route,
navigation: descriptor.navigation as BottomTabNavigationProp<ParamListBase>,
options: descriptor.options,
})}
style={sceneContainerStyle}
>
<SceneContent isFocused={isFocused}>
{descriptor.render()}
</SceneContent>
</ResourceSavingScene>
);
})}
</ScreenContainer>
{this.renderTabBar()}
</View>
</SafeAreaProviderCompat>
</NavigationHelpersContext.Provider>
);
}
{descriptor.render()}
</Screen>
</BottomTabBarHeightContext.Provider>
</MaybeScreen>
);
})}
</MaybeScreenContainer>
<BottomTabBarHeightCallbackContext.Provider value={setTabBarHeight}>
{renderTabBar()}
</BottomTabBarHeightCallbackContext.Provider>
</SafeAreaProviderCompat>
</NavigationHelpersContext.Provider>
);
}
const styles = StyleSheet.create({
@@ -134,10 +163,4 @@ const styles = StyleSheet.create({
flex: 1,
overflow: 'hidden',
},
pages: {
flex: 1,
},
content: {
flex: 1,
},
});

View File

@@ -1,63 +0,0 @@
import * as React from 'react';
import { Platform, StyleSheet, View } from 'react-native';
import { Screen, screensEnabled } from 'react-native-screens';
type Props = {
isVisible: boolean;
children: React.ReactNode;
style?: any;
};
const FAR_FAR_AWAY = 30000; // this should be big enough to move the whole view out of its container
export default class ResourceSavingScene extends React.Component<Props> {
render() {
// react-native-screens is buggy on web
if (screensEnabled?.() && Platform.OS !== 'web') {
const { isVisible, ...rest } = this.props;
// @ts-expect-error: stackPresentation is incorrectly marked as required
return <Screen active={isVisible ? 1 : 0} {...rest} />;
}
const { isVisible, children, style, ...rest } = this.props;
return (
<View
style={[
styles.container,
Platform.OS === 'web'
? { display: isVisible ? 'flex' : 'none' }
: null,
style,
]}
collapsable={false}
removeClippedSubviews={
// On iOS, set removeClippedSubviews to true only when not focused
// This is an workaround for a bug where the clipped view never re-appears
Platform.OS === 'ios' ? !isVisible : true
}
pointerEvents={isVisible ? 'auto' : 'none'}
{...rest}
>
<View style={isVisible ? styles.attached : styles.detached}>
{children}
</View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
overflow: 'hidden',
},
attached: {
flex: 1,
},
detached: {
flex: 1,
top: FAR_FAR_AWAY,
},
});

View File

@@ -1,49 +0,0 @@
import * as React from 'react';
import {
SafeAreaProvider,
SafeAreaConsumer,
initialWindowSafeAreaInsets,
} from 'react-native-safe-area-context';
import {
getStatusBarHeight,
getBottomSpace,
} from 'react-native-iphone-x-helper';
// The provider component for safe area initializes asynchornously
// Until the insets are available, there'll be blank screen
// To avoid the blank screen, we specify some initial values
const initialSafeAreaInsets = {
// Approximate values which are good enough for most cases
top: getStatusBarHeight(true),
bottom: getBottomSpace(),
right: 0,
left: 0,
// If we are on a newer version of the library, we can get the correct window insets
// The component might not be filling the window, but this is good enough for most cases
...initialWindowSafeAreaInsets,
};
type Props = {
children: React.ReactNode;
};
export default function SafeAreaProviderCompat({ children }: Props) {
return (
<SafeAreaConsumer>
{(insets) => {
if (insets) {
// If we already have insets, don't wrap the stack in another safe area provider
// This avoids an issue with updates at the cost of potentially incorrect values
// https://github.com/react-navigation/react-navigation/issues/174
return children;
}
return (
<SafeAreaProvider initialSafeAreaInsets={initialSafeAreaInsets}>
{children}
</SafeAreaProvider>
);
}}
</SafeAreaConsumer>
);
}

View File

@@ -0,0 +1,48 @@
import * as React from 'react';
import { StyleProp, View, ViewProps, ViewStyle } from 'react-native';
import { ResourceSavingView } from '@react-navigation/elements';
type Props = {
visible: boolean;
children: React.ReactNode;
enabled: boolean;
style?: StyleProp<ViewStyle>;
};
let Screens: typeof import('react-native-screens') | undefined;
try {
Screens = require('react-native-screens');
} catch (e) {
// Ignore
}
export const MaybeScreenContainer = ({
enabled,
...rest
}: ViewProps & {
enabled: boolean;
children: React.ReactNode;
}) => {
if (Screens?.screensEnabled?.()) {
return <Screens.ScreenContainer enabled={enabled} {...rest} />;
}
return <View {...rest} />;
};
export function MaybeScreen({ visible, children, ...rest }: Props) {
if (Screens?.screensEnabled?.()) {
return (
<Screens.Screen activityState={visible ? 2 : 0} {...rest}>
{children}
</Screens.Screen>
);
}
return (
<ResourceSavingView visible={visible} {...rest}>
{children}
</ResourceSavingView>
);
}

View File

@@ -1,5 +1,11 @@
import React from 'react';
import { View, StyleSheet, StyleProp, ViewStyle } from 'react-native';
import {
View,
StyleSheet,
StyleProp,
TextStyle,
ViewStyle,
} from 'react-native';
import type { Route } from '@react-navigation/native';
import Badge from './Badge';
@@ -7,6 +13,7 @@ type Props = {
route: Route<string>;
horizontal: boolean;
badge?: string | number;
badgeStyle?: StyleProp<TextStyle>;
activeOpacity: number;
inactiveOpacity: number;
activeTintColor: string;
@@ -22,6 +29,7 @@ type Props = {
export default function TabBarIcon({
horizontal,
badge,
badgeStyle,
activeOpacity,
inactiveOpacity,
activeTintColor,
@@ -56,6 +64,7 @@ export default function TabBarIcon({
style={[
styles.badge,
horizontal ? styles.badgeHorizontal : styles.badgeVertical,
badgeStyle,
]}
size={(size * 3) / 4}
>

View File

@@ -3,7 +3,8 @@
"references": [
{ "path": "../core" },
{ "path": "../routers" },
{ "path": "../native" }
{ "path": "../native" },
{ "path": "../elements" }
],
"compilerOptions": {
"outDir": "./lib/typescript"

View File

@@ -1,694 +0,0 @@
# Change Log
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [5.2.8](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.2.7...@react-navigation/compat@5.2.8) (2020-10-07)
**Note:** Version bump only for package @react-navigation/compat
## [5.2.7](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.2.6...@react-navigation/compat@5.2.7) (2020-09-28)
**Note:** Version bump only for package @react-navigation/compat
## [5.2.6](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.2.5...@react-navigation/compat@5.2.6) (2020-09-22)
**Note:** Version bump only for package @react-navigation/compat
## [5.2.5](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.2.4...@react-navigation/compat@5.2.5) (2020-08-04)
**Note:** Version bump only for package @react-navigation/compat
## [5.2.4](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.2.3...@react-navigation/compat@5.2.4) (2020-07-28)
**Note:** Version bump only for package @react-navigation/compat
## [5.2.3](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.2.1...@react-navigation/compat@5.2.3) (2020-07-22)
### Bug Fixes
* fix false warning due to change in Object.assign in metro preset ([5e358b3](https://github.com/react-navigation/react-navigation/commit/5e358b3aadac7bb186521872d515fff2e571a940)), closes [#8584](https://github.com/react-navigation/react-navigation/issues/8584)
## [5.2.2](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.2.1...@react-navigation/compat@5.2.2) (2020-07-22)
### Bug Fixes
* fix false warning due to change in Object.assign in metro preset ([240a706](https://github.com/react-navigation/react-navigation/commit/240a706a56220b63d603a52407a738c2872349dd)), closes [#8584](https://github.com/react-navigation/react-navigation/issues/8584)
## [5.2.1](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.2.0...@react-navigation/compat@5.2.1) (2020-07-19)
**Note:** Version bump only for package @react-navigation/compat
# [5.2.0](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.1.28...@react-navigation/compat@5.2.0) (2020-07-10)
### Features
* add a getComponent prop to lazily specify components ([f418029](https://github.com/react-navigation/react-navigation/commit/f4180295bf22e32c65f6a7ab7089523cb2de58fb))
## [5.1.28](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.1.27...@react-navigation/compat@5.1.28) (2020-06-25)
**Note:** Version bump only for package @react-navigation/compat
## [5.1.27](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.1.26...@react-navigation/compat@5.1.27) (2020-06-24)
### Bug Fixes
* more improvements to types ([d244488](https://github.com/react-navigation/react-navigation/commit/d2444887be227bbbdcfcb13a7f26a8ebb344043e))
## [5.1.26](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.1.25...@react-navigation/compat@5.1.26) (2020-06-06)
**Note:** Version bump only for package @react-navigation/compat
## [5.1.25](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.1.24...@react-navigation/compat@5.1.25) (2020-05-27)
**Note:** Version bump only for package @react-navigation/compat
## [5.1.24](https://github.com/react-navigation/react-navigation/tree/main/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/main/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/main/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/main/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/main/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/main/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/main/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/main/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/main/packages/compat/issues/8216) ([47a1229](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/commit/47a12298378747edd2d22e54dc1c8677f98c49b4))
## [5.1.16](https://github.com/react-navigation/react-navigation/tree/main/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/main/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/main/packages/compat/compare/@react-navigation/compat@5.1.13...@react-navigation/compat@5.1.14) (2020-05-01)
**Note:** Version bump only for package @react-navigation/compat
## [5.1.13](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.1.12...@react-navigation/compat@5.1.13) (2020-05-01)
**Note:** Version bump only for package @react-navigation/compat
## [5.1.12](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.1.11...@react-navigation/compat@5.1.12) (2020-04-30)
**Note:** Version bump only for package @react-navigation/compat
## [5.1.11](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.1.10...@react-navigation/compat@5.1.11) (2020-04-30)
**Note:** Version bump only for package @react-navigation/compat
## [5.1.10](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.1.9...@react-navigation/compat@5.1.10) (2020-04-27)
### Bug Fixes
* fix typo in navigationOptions ([8cbb201](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/commit/8cbb201f1a7fb90e45a078df6bc42ce4771cc6a6))
* spread parent params to children in compat navigator ([24febf6](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/commit/24febf6ea99be2e5f22005fdd2a82136d647255c)), closes [#6785](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/issues/6785)
## [5.1.9](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.1.8...@react-navigation/compat@5.1.9) (2020-04-17)
**Note:** Version bump only for package @react-navigation/compat
## [5.1.8](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.1.7...@react-navigation/compat@5.1.8) (2020-04-08)
### Bug Fixes
* use 1 as default in compatibility pop action ([4408117](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/commit/44081172d440c713ad3543a2d5e1e18ebc8f72a4))
## [5.1.7](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.1.6...@react-navigation/compat@5.1.7) (2020-03-30)
**Note:** Version bump only for package @react-navigation/compat
## [5.1.6](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.1.5...@react-navigation/compat@5.1.6) (2020-03-23)
**Note:** Version bump only for package @react-navigation/compat
## [5.1.5](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.1.4...@react-navigation/compat@5.1.5) (2020-03-22)
**Note:** Version bump only for package @react-navigation/compat
## [5.1.4](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.1.3...@react-navigation/compat@5.1.4) (2020-03-19)
**Note:** Version bump only for package @react-navigation/compat
## [5.1.3](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.1.2...@react-navigation/compat@5.1.3) (2020-03-17)
**Note:** Version bump only for package @react-navigation/compat
## [5.1.2](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.1.1...@react-navigation/compat@5.1.2) (2020-03-16)
**Note:** Version bump only for package @react-navigation/compat
## [5.1.1](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.1.0...@react-navigation/compat@5.1.1) (2020-03-03)
**Note:** Version bump only for package @react-navigation/compat
# [5.1.0](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.0.7...@react-navigation/compat@5.1.0) (2020-02-26)
### Features
* add ability add listeners with listeners prop ([1624108](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/commit/162410843c4f175ae107756de1c3af04d1d47aa7)), closes [#6756](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/issues/6756)
## [5.0.7](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.0.6...@react-navigation/compat@5.0.7) (2020-02-21)
**Note:** Version bump only for package @react-navigation/compat
## [5.0.6](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.0.5...@react-navigation/compat@5.0.6) (2020-02-19)
### Bug Fixes
* add NavigationEvents ([d69b0db](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/commit/d69b0db60455b8789276822ba73f5349db8842d7)), closes [/github.com/react-navigation/react-navigation/issues/6821#issuecomment-588268512](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/issues/issuecomment-588268512)
## [5.0.5](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.0.4...@react-navigation/compat@5.0.5) (2020-02-14)
**Note:** Version bump only for package @react-navigation/compat
## [5.0.4](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.0.3...@react-navigation/compat@5.0.4) (2020-02-14)
**Note:** Version bump only for package @react-navigation/compat
## [5.0.3](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.0.2...@react-navigation/compat@5.0.3) (2020-02-12)
**Note:** Version bump only for package @react-navigation/compat
## [5.0.2](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.0.1...@react-navigation/compat@5.0.2) (2020-02-11)
**Note:** Version bump only for package @react-navigation/compat
## [5.0.1](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.0.0-alpha.34...@react-navigation/compat@5.0.1) (2020-02-10)
**Note:** Version bump only for package @react-navigation/compat
# [5.0.0-alpha.34](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.0.0-alpha.33...@react-navigation/compat@5.0.0-alpha.34) (2020-02-04)
**Note:** Version bump only for package @react-navigation/compat
# [5.0.0-alpha.33](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.0.0-alpha.32...@react-navigation/compat@5.0.0-alpha.33) (2020-02-04)
**Note:** Version bump only for package @react-navigation/compat
# [5.0.0-alpha.32](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.0.0-alpha.31...@react-navigation/compat@5.0.0-alpha.32) (2020-02-03)
**Note:** Version bump only for package @react-navigation/compat
# [5.0.0-alpha.31](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.0.0-alpha.28...@react-navigation/compat@5.0.0-alpha.31) (2020-02-02)
### Bug Fixes
* add licenses ([0c159db](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/commit/0c159db4c9bc85e83b5cfe6819ab2562669a4d8f))
* throw when assigning or accessing the router property in compat ([944fa35](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/commit/944fa35ed4778ebc7fa7cd50092719cbd5bf3caf))
# [5.0.0-alpha.29](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.0.0-alpha.28...@react-navigation/compat@5.0.0-alpha.29) (2020-02-02)
### Bug Fixes
* add licenses ([0c159db](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/commit/0c159db4c9bc85e83b5cfe6819ab2562669a4d8f))
* throw when assigning or accessing the router property in compat ([944fa35](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/commit/944fa35ed4778ebc7fa7cd50092719cbd5bf3caf))
# [5.0.0-alpha.28](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.0.0-alpha.27...@react-navigation/compat@5.0.0-alpha.28) (2020-01-24)
**Note:** Version bump only for package @react-navigation/compat
# [5.0.0-alpha.27](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.0.0-alpha.26...@react-navigation/compat@5.0.0-alpha.27) (2020-01-23)
### Bug Fixes
* ensure re-render on isFirstRouteInParent change in compat layer ([14ae373](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/commit/14ae3738cf46088e082bd1c60b9dcc6dacacd1bf))
* improvements to the compat layer ([2a76dc4](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/commit/2a76dc4d3c4cc0365a3afcff6ac321145efed026))
# [5.0.0-alpha.26](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.0.0-alpha.25...@react-navigation/compat@5.0.0-alpha.26) (2020-01-14)
**Note:** Version bump only for package @react-navigation/compat
# [5.0.0-alpha.25](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.0.0-alpha.24...@react-navigation/compat@5.0.0-alpha.25) (2020-01-13)
### Bug Fixes
* make sure paths aren't aliased when building definitions ([65a5dac](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/commit/65a5dac2bf887f4ba081ab15bd4c9870bb15697f)), closes [#265](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/issues/265)
# [5.0.0-alpha.24](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.0.0-alpha.23...@react-navigation/compat@5.0.0-alpha.24) (2020-01-13)
**Note:** Version bump only for package @react-navigation/compat
# [5.0.0-alpha.23](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.0.0-alpha.21...@react-navigation/compat@5.0.0-alpha.23) (2020-01-09)
**Note:** Version bump only for package @react-navigation/compat
# [5.0.0-alpha.22](https://github.com/react-navigation/react-navigation/tree/main/packages/compat/compare/@react-navigation/compat@5.0.0-alpha.21...@react-navigation/compat@5.0.0-alpha.22) (2020-01-09)
**Note:** Version bump only for package @react-navigation/compat
# [5.0.0-alpha.21](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.20...@react-navigation/compat@5.0.0-alpha.21) (2020-01-05)
**Note:** Version bump only for package @react-navigation/compat
# [5.0.0-alpha.20](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.19...@react-navigation/compat@5.0.0-alpha.20) (2020-01-01)
**Note:** Version bump only for package @react-navigation/compat
# [5.0.0-alpha.19](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.18...@react-navigation/compat@5.0.0-alpha.19) (2019-12-19)
**Note:** Version bump only for package @react-navigation/compat
# [5.0.0-alpha.18](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.17...@react-navigation/compat@5.0.0-alpha.18) (2019-12-16)
**Note:** Version bump only for package @react-navigation/compat
# [5.0.0-alpha.17](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.16...@react-navigation/compat@5.0.0-alpha.17) (2019-12-11)
**Note:** Version bump only for package @react-navigation/compat
# [5.0.0-alpha.16](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.15...@react-navigation/compat@5.0.0-alpha.16) (2019-12-10)
**Note:** Version bump only for package @react-navigation/compat
# [5.0.0-alpha.15](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.14...@react-navigation/compat@5.0.0-alpha.15) (2019-11-17)
**Note:** Version bump only for package @react-navigation/compat
# [5.0.0-alpha.14](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.13...@react-navigation/compat@5.0.0-alpha.14) (2019-11-10)
**Note:** Version bump only for package @react-navigation/compat
# [5.0.0-alpha.13](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.12...@react-navigation/compat@5.0.0-alpha.13) (2019-11-08)
**Note:** Version bump only for package @react-navigation/compat
# [5.0.0-alpha.12](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.11...@react-navigation/compat@5.0.0-alpha.12) (2019-11-04)
**Note:** Version bump only for package @react-navigation/compat
# [5.0.0-alpha.11](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.10...@react-navigation/compat@5.0.0-alpha.11) (2019-10-30)
### Bug Fixes
* drop isFirstRouteInParent method ([#145](https://github.com/react-navigation/navigation-ex/issues/145)) ([3a77107](https://github.com/react-navigation/navigation-ex/commit/3a77107))
# [5.0.0-alpha.10](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.9...@react-navigation/compat@5.0.0-alpha.10) (2019-10-29)
**Note:** Version bump only for package @react-navigation/compat
# [5.0.0-alpha.9](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.8...@react-navigation/compat@5.0.0-alpha.9) (2019-10-22)
**Note:** Version bump only for package @react-navigation/compat
# [5.0.0-alpha.8](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.7...@react-navigation/compat@5.0.0-alpha.8) (2019-10-15)
### Features
* initial version of native stack ([#102](https://github.com/react-navigation/navigation-ex/issues/102)) ([ba3f718](https://github.com/react-navigation/navigation-ex/commit/ba3f718))
# [5.0.0-alpha.7](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.6...@react-navigation/compat@5.0.0-alpha.7) (2019-10-06)
**Note:** Version bump only for package @react-navigation/compat
# [5.0.0-alpha.6](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.5...@react-navigation/compat@5.0.0-alpha.6) (2019-10-03)
**Note:** Version bump only for package @react-navigation/compat
# [5.0.0-alpha.5](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.4...@react-navigation/compat@5.0.0-alpha.5) (2019-10-03)
**Note:** Version bump only for package @react-navigation/compat
# [5.0.0-alpha.4](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.3...@react-navigation/compat@5.0.0-alpha.4) (2019-09-27)
**Note:** Version bump only for package @react-navigation/compat
# [5.0.0-alpha.3](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.2...@react-navigation/compat@5.0.0-alpha.3) (2019-09-23)
### Bug Fixes
* throw when wrapping a compat navigatofor compat ([8920da6](https://github.com/react-navigation/navigation-ex/commit/8920da6))
# [5.0.0-alpha.2](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.1...@react-navigation/compat@5.0.0-alpha.2) (2019-09-17)
### Bug Fixes
* provide navigation prop in header ([30e510d](https://github.com/react-navigation/navigation-ex/commit/30e510d))
### Features
* add createSwitchNavigator ([ff50282](https://github.com/react-navigation/navigation-ex/commit/ff50282))
* add withNavigation and withNavigationFocus ([114a5dc](https://github.com/react-navigation/navigation-ex/commit/114a5dc))
# 5.0.0-alpha.1 (2019-09-16)
### Bug Fixes
* fix dispatching compat actions ([88a560a](https://github.com/react-navigation/navigation-ex/commit/88a560a))
* support legacy goBack method ([d83d3de](https://github.com/react-navigation/navigation-ex/commit/d83d3de))
### Features
* compatibility layer ([e0f28a4](https://github.com/react-navigation/navigation-ex/commit/e0f28a4))

View File

@@ -1,5 +0,0 @@
# `@react-navigation/compat`
Compatibility layer to write navigator definitions in static configuration format.
Installation instructions and documentation can be found on the [React Navigation website](https://reactnavigation.org/docs/compatibility/).

View File

@@ -1,17 +0,0 @@
import * as React from 'react';
import ScreenPropsContext from './ScreenPropsContext';
import useCompatNavigation from './useCompatNavigation';
type Props = {
getComponent: () => React.ComponentType<any>;
};
function CompatScreen({ getComponent }: Props) {
const navigation = useCompatNavigation();
const screenProps = React.useContext(ScreenPropsContext);
const ScreenComponent = getComponent();
return <ScreenComponent navigation={navigation} screenProps={screenProps} />;
}
export default React.memo(CompatScreen);

View File

@@ -1,13 +0,0 @@
import { DrawerActions, DrawerActionType } from '@react-navigation/native';
export function openDrawer(): DrawerActionType {
return DrawerActions.openDrawer();
}
export function closeDrawer(): DrawerActionType {
return DrawerActions.closeDrawer();
}
export function toggleDrawer(): DrawerActionType {
return DrawerActions.toggleDrawer();
}

View File

@@ -1,48 +0,0 @@
import { CommonActions, NavigationState } from '@react-navigation/native';
export function navigate({
routeName,
params,
key,
action,
}: {
routeName: string;
params?: object;
key?: string;
action?: never;
}): CommonActions.Action {
if (action !== undefined) {
throw new Error(
'Sub-actions are not supported for `navigate`. Remove the `action` key from the options.'
);
}
return CommonActions.navigate({
name: routeName,
key: key,
params: params,
});
}
export function back(options?: { key?: null | string }) {
return options?.key != null
? (state: NavigationState) => ({
...CommonActions.goBack(),
source: options.key,
target: state.key,
})
: CommonActions.goBack();
}
export function setParams({
params,
key,
}: {
params: object;
key?: string;
}): CommonActions.Action {
return {
...CommonActions.setParams(params),
...(key !== undefined ? { source: key } : null),
};
}

View File

@@ -1,45 +0,0 @@
import * as React from 'react';
import { useNavigation } from '@react-navigation/native';
type Props = {
onWillFocus?: () => void;
onDidFocus?: () => void;
onWillBlur?: () => void;
onDidBlur?: () => void;
};
export default function NavigationEvents(props: Props) {
const navigation = useNavigation();
const propsRef = React.useRef(props);
React.useEffect(() => {
propsRef.current = props;
});
React.useEffect(() => {
const unsubFocus = navigation.addListener('focus', () => {
propsRef.current.onWillFocus?.();
});
const unsubBlur = navigation.addListener('blur', () => {
propsRef.current.onWillBlur?.();
});
// @ts-expect-error: transitionEnd may not exist on this navigator
const unsubTransitionEnd = navigation.addListener('transitionEnd', () => {
if (navigation.isFocused()) {
propsRef.current.onDidFocus?.();
} else {
propsRef.current.onDidBlur?.();
}
});
return () => {
unsubFocus();
unsubBlur();
unsubTransitionEnd();
};
}, [navigation]);
return null;
}

View File

@@ -1,3 +0,0 @@
import * as React from 'react';
export default React.createContext<unknown>(undefined);

View File

@@ -1,71 +0,0 @@
import {
CommonActions,
StackActions,
StackActionType,
} from '@react-navigation/native';
export function reset(): CommonActions.Action {
throw new Error(
'The legacy `reset` action is not supported. Use the new reset API by accessing the original navigation object at `navigation.original`.'
);
}
export function replace({
routeName,
params,
key,
newKey,
action,
}: {
routeName: string;
params?: object;
key?: string;
newKey?: string;
action?: never;
}): StackActionType {
if (action !== undefined) {
throw new Error(
'Sub-actions are not supported for `replace`. Remove the `action` key from the options.'
);
}
return {
type: 'REPLACE',
payload: {
name: routeName,
key: newKey,
params,
},
...(key !== undefined ? { source: key } : null),
};
}
export function push({
routeName,
params,
action,
}: {
routeName: string;
params?: object;
action?: never;
}): StackActionType {
if (action !== undefined) {
throw new Error(
'Sub-actions are not supported for `push`. Remove the `action` key from the options.'
);
}
return StackActions.push(routeName, params);
}
export function pop({ n = 1 }: { n: number }): StackActionType {
return StackActions.pop(n);
}
export function popToTop(): StackActionType {
return StackActions.popToTop();
}
export function dismiss(): CommonActions.Action {
throw new Error('The legacy `dismiss` action is not supported.');
}

View File

@@ -1,18 +0,0 @@
import { TabActions, TabActionType } from '@react-navigation/native';
export function jumpTo({
routeName,
key,
}: {
routeName: string;
key?: string;
}): TabActionType {
if (key === undefined) {
return TabActions.jumpTo(routeName);
} else {
return {
...TabActions.jumpTo(routeName),
target: key,
};
}
}

View File

@@ -1,218 +0,0 @@
import type {
NavigationState,
PartialState,
ParamListBase,
NavigationProp,
RouteProp,
} from '@react-navigation/native';
import * as helpers from './helpers';
import type { CompatNavigationProp } from './types';
type EventName =
| 'action'
| 'willFocus'
| 'willBlur'
| 'didFocus'
| 'didBlur'
| 'refocus';
export default function createCompatNavigationProp<
NavigationPropType extends NavigationProp<ParamListBase>,
ParamList extends ParamListBase = NavigationPropType extends NavigationProp<
infer P
>
? P
: ParamListBase
>(
navigation: NavigationPropType,
state:
| (RouteProp<ParamList, keyof ParamList> & {
state?: NavigationState | PartialState<NavigationState>;
})
| NavigationState
| PartialState<NavigationState>,
context: Record<string, any>,
isFirstRouteInParent?: boolean
): CompatNavigationProp<NavigationPropType> {
context.parent = context.parent || {};
context.subscriptions = context.subscriptions || {
didFocus: new Map<() => void, () => void>(),
didBlur: new Map<() => void, () => void>(),
refocus: new Map<() => void, () => void>(),
};
return {
...navigation,
...Object.entries(helpers).reduce<{
[key: string]: (...args: any[]) => void;
}>((acc, [name, method]: [string, Function]) => {
if (name in navigation) {
acc[name] = (...args: any[]) => navigation.dispatch(method(...args));
}
return acc;
}, {}),
original: navigation,
addListener(type: EventName, callback: () => void) {
let unsubscribe: () => void;
switch (type) {
case 'willFocus':
unsubscribe = navigation.addListener('focus', callback);
break;
case 'willBlur':
unsubscribe = navigation.addListener('blur', callback);
break;
case 'didFocus': {
const listener = () => {
if (navigation.isFocused()) {
callback();
}
};
// @ts-expect-error: this event may not exist in this navigator
unsubscribe = navigation.addListener('transitionEnd', listener);
context.subscriptions.didFocus.set(callback, unsubscribe);
break;
}
case 'didBlur': {
const listener = () => {
if (!navigation.isFocused()) {
callback();
}
};
// @ts-expect-error: this event may not exist in this navigator
unsubscribe = navigation.addListener('transitionEnd', listener);
context.subscriptions.didBlur.set(callback, unsubscribe);
break;
}
case 'refocus': {
const listener = () => {
if (navigation.isFocused()) {
callback();
}
};
// @ts-expect-error: this event may not exist in this navigator
unsubscribe = navigation.addListener('tabPress', listener);
context.subscriptions.refocus.set(callback, unsubscribe);
break;
}
case 'action':
throw new Error("Listening to 'action' events is not supported.");
default:
unsubscribe = navigation.addListener(type, callback);
}
const subscription = () => unsubscribe();
subscription.remove = unsubscribe;
return subscription;
},
removeListener(type: EventName, callback: () => void) {
context.subscriptions = context.subscriptions || {};
switch (type) {
case 'willFocus':
navigation.removeListener('focus', callback);
break;
case 'willBlur':
navigation.removeListener('blur', callback);
break;
case 'didFocus': {
const unsubscribe = context.subscriptions.didFocus.get(callback);
unsubscribe?.();
break;
}
case 'didBlur': {
const unsubscribe = context.subscriptions.didBlur.get(callback);
unsubscribe?.();
break;
}
case 'refocus': {
const unsubscribe = context.subscriptions.refocus.get(callback);
unsubscribe?.();
break;
}
case 'action':
throw new Error("Listening to 'action' events is not supported.");
default:
navigation.removeListener(type, callback);
}
},
state: {
// @ts-expect-error: these properties may actually exist
key: state.key,
// @ts-expect-error
routeName: state.name,
// @ts-expect-error
params: state.params ?? {},
get index() {
// @ts-expect-error
if (state.index !== undefined) {
// @ts-expect-error
return state.index;
}
console.warn(
"Looks like you are using 'navigation.state.index' in your code. Accessing child navigation state for a route is not safe and won't work correctly. You should refactor it not to access the 'index' property in the child navigation state."
);
// @ts-expect-error
return state.state?.index;
},
get routes() {
// @ts-expect-error
if (state.routes !== undefined) {
// @ts-expect-error
return state.routes;
}
console.warn(
"Looks like you are using 'navigation.state.routes' in your code. Accessing child navigation state for a route is not safe and won't work correctly. You should refactor it not to access the 'routes' property in the child navigation state."
);
// @ts-expect-error
return state.state?.routes;
},
},
getParam<T extends keyof ParamList>(
paramName: T,
defaultValue: ParamList[T]
): ParamList[T] {
// @ts-expect-error
const params = state.params;
if (params && paramName in params) {
return params[paramName];
}
return defaultValue;
},
isFirstRouteInParent(): boolean {
if (typeof isFirstRouteInParent === 'boolean') {
return isFirstRouteInParent;
}
const { routes } = navigation.dangerouslyGetState();
// @ts-expect-error
return routes[0].key === state.key;
},
dangerouslyGetParent() {
const parent = navigation.dangerouslyGetParent();
if (parent) {
return createCompatNavigationProp(
parent,
navigation.dangerouslyGetState(),
context.parent
);
}
return undefined;
},
} as any;
}

View File

@@ -1,190 +0,0 @@
import * as React from 'react';
import {
NavigationState,
PartialState,
ParamListBase,
TypedNavigator,
NavigationProp,
RouteProp,
NavigationRouteContext,
} from '@react-navigation/native';
import CompatScreen from './CompatScreen';
import ScreenPropsContext from './ScreenPropsContext';
import createCompatNavigationProp from './createCompatNavigationProp';
import type { CompatScreenType, CompatRouteConfig } from './types';
export default function createCompatNavigatorFactory<
CreateNavigator extends () => TypedNavigator<
ParamListBase,
NavigationState,
{},
any,
React.ComponentType<any>
>
>(createNavigator: CreateNavigator) {
// @ts-expect-error: isCompat may or may not exist
if (createNavigator.isCompat) {
throw new Error(
`The navigator is already in compat mode. You don't need to wrap it in 'createCompatNavigatorFactory'.`
);
}
const createCompatNavigator = <
NavigationPropType extends NavigationProp<any, any, any, any, any>,
ParamList extends ParamListBase = NavigationPropType extends NavigationProp<
infer P
>
? P
: ParamListBase,
ScreenOptions extends {} = NavigationPropType extends NavigationProp<
any,
any,
any,
infer O
>
? O
: {},
NavigationConfig extends {} = React.ComponentProps<
ReturnType<CreateNavigator>['Navigator']
>
>(
routeConfig: CompatRouteConfig<NavigationPropType>,
navigationConfig: Partial<Omit<NavigationConfig, 'screenOptions'>> & {
order?: Extract<keyof ParamList, string>[];
defaultNavigationOptions?: ScreenOptions;
navigationOptions?: Record<string, any>;
} = {}
) => {
const Pair = createNavigator();
const {
order,
defaultNavigationOptions,
navigationOptions: parentNavigationOptions,
...restConfig
} = navigationConfig;
const routeNames = order !== undefined ? order : Object.keys(routeConfig);
function Navigator({ screenProps }: { screenProps?: unknown }) {
const parentRouteParams = React.useContext(NavigationRouteContext)
?.params;
const screens = React.useMemo(
() =>
routeNames.map((name) => {
let getScreenComponent: () => CompatScreenType<NavigationPropType>;
let initialParams;
const routeConfigItem = routeConfig[name];
if ('getScreen' in routeConfigItem) {
getScreenComponent = routeConfigItem.getScreen;
initialParams = routeConfigItem.params;
} else if ('screen' in routeConfigItem) {
getScreenComponent = () => routeConfigItem.screen;
initialParams = routeConfigItem.params;
} else {
getScreenComponent = () => routeConfigItem;
}
const screenOptions = ({
navigation,
route,
}: {
navigation: NavigationPropType;
route: RouteProp<ParamList, keyof ParamList> & {
state?: NavigationState | PartialState<NavigationState>;
};
}) => {
// @ts-expect-error: navigationOptions may exists on the component, but TS is dumb
const routeNavigationOptions = routeConfigItem.navigationOptions;
const screenNavigationOptions = getScreenComponent()
.navigationOptions;
if (
routeNavigationOptions == null &&
screenNavigationOptions == null
) {
return undefined;
}
const options =
typeof routeNavigationOptions === 'function' ||
typeof screenNavigationOptions === 'function'
? {
navigation: createCompatNavigationProp<
NavigationPropType,
ParamList
>(navigation, route, {}),
navigationOptions: defaultNavigationOptions || {},
screenProps,
}
: {};
return {
...(typeof routeNavigationOptions === 'function'
? routeNavigationOptions(options)
: routeNavigationOptions),
...(typeof screenNavigationOptions === 'function'
? (screenNavigationOptions as (o: any) => ScreenOptions)(
options
)
: screenNavigationOptions),
} as ScreenOptions;
};
return (
<Pair.Screen
key={name}
name={name}
initialParams={{ ...parentRouteParams, ...initialParams }}
options={screenOptions}
>
{() => <CompatScreen getComponent={getScreenComponent} />}
</Pair.Screen>
);
}),
[parentRouteParams, screenProps]
);
return (
<ScreenPropsContext.Provider value={screenProps}>
<Pair.Navigator
{...(restConfig as NavigationConfig)}
screenOptions={defaultNavigationOptions}
>
{screens}
</Pair.Navigator>
</ScreenPropsContext.Provider>
);
}
Navigator.navigationOptions = parentNavigationOptions;
return Navigator;
};
Object.defineProperties(createCompatNavigator, {
isCompat: {
get() {
return true;
},
},
router: {
get() {
throw new Error(
"It's no longer possible to access the router with the 'router' property."
);
},
set() {
throw new Error(
"It's no longer possible to override the router by assigning the 'router' property."
);
},
},
});
return createCompatNavigator;
}

View File

@@ -1,28 +0,0 @@
import {
useNavigationBuilder,
createNavigatorFactory,
DefaultNavigatorOptions,
TabRouter,
TabRouterOptions,
TabNavigationState,
} from '@react-navigation/native';
import createCompatNavigatorFactory from './createCompatNavigatorFactory';
type Props = DefaultNavigatorOptions<{}> & TabRouterOptions;
function SwitchNavigator(props: Props) {
const { state, descriptors } = useNavigationBuilder<
TabNavigationState,
TabRouterOptions,
{},
{}
>(TabRouter, props);
return descriptors[state.routes[state.index].key].render();
}
export default createCompatNavigatorFactory(
createNavigatorFactory<TabNavigationState, {}, {}, typeof SwitchNavigator>(
SwitchNavigator
)
);

View File

@@ -1,88 +0,0 @@
import * as NavigationActions from './NavigationActions';
import * as StackActions from './StackActions';
import * as SwitchActions from './SwitchActions';
import * as DrawerActions from './DrawerActions';
type NavigateActionPayload = Parameters<typeof NavigationActions.navigate>['0'];
type NavigateActionType = ReturnType<typeof NavigationActions.navigate>;
export function navigate(
routeName: string,
params?: object,
action?: never
): NavigateActionType;
// eslint-disable-next-line no-redeclare
export function navigate(options: NavigateActionPayload): NavigateActionType;
// eslint-disable-next-line no-redeclare
export function navigate(
options: string | NavigateActionPayload,
params?: object,
action?: never
): NavigateActionType {
if (typeof options === 'string') {
return NavigationActions.navigate({
routeName: options,
params,
action,
});
}
return NavigationActions.navigate(options);
}
export function goBack(fromKey?: null | string) {
return NavigationActions.back({ key: fromKey });
}
export function setParams(params: object) {
return NavigationActions.setParams({ params });
}
export function reset() {
return StackActions.reset();
}
export function replace(routeName: string, params?: object, action?: never) {
return StackActions.replace({
routeName,
params,
action,
});
}
export function push(routeName: string, params?: object, action?: never) {
return StackActions.push({
routeName,
params,
action,
});
}
export function pop(n: number = 1) {
return StackActions.pop(typeof n === 'number' ? { n } : n);
}
export function popToTop() {
return StackActions.popToTop();
}
export function dismiss() {
return StackActions.dismiss();
}
export function jumpTo(routeName: string) {
return SwitchActions.jumpTo({ routeName });
}
export function openDrawer() {
return DrawerActions.openDrawer();
}
export function closeDrawer() {
return DrawerActions.closeDrawer();
}
export function toggleDrawer() {
return DrawerActions.toggleDrawer();
}

View File

@@ -1,19 +0,0 @@
import * as NavigationActions from './NavigationActions';
import * as StackActions from './StackActions';
import * as DrawerActions from './DrawerActions';
import * as SwitchActions from './SwitchActions';
export { NavigationActions, StackActions, DrawerActions, SwitchActions };
export { default as createCompatNavigatorFactory } from './createCompatNavigatorFactory';
export { default as createCompatNavigationProp } from './createCompatNavigationProp';
export { default as createSwitchNavigator } from './createSwitchNavigator';
export { default as withNavigation } from './withNavigation';
export { default as withNavigationFocus } from './withNavigationFocus';
export { default as NavigationEvents } from './NavigationEvents';
export * from './types';

View File

@@ -1,84 +0,0 @@
import type {
ParamListBase,
NavigationProp,
Route,
} from '@react-navigation/native';
import type * as helpers from './helpers';
export type CompatNavigationProp<
NavigationPropType extends NavigationProp<ParamListBase>,
ParamList extends ParamListBase = NavigationPropType extends NavigationProp<
infer P
>
? P
: ParamListBase,
RouteName extends Extract<keyof ParamList, string> = Extract<
NavigationPropType extends NavigationProp<any, infer R> ? R : string,
string
>
> = Omit<NavigationPropType, keyof typeof helpers> &
{
[method in Extract<keyof NavigationPropType, keyof typeof helpers>]: (
...args: Parameters<typeof helpers[method]>
) => void;
} & {
state: Route<RouteName> & {
routeName: RouteName;
};
getParam<T extends keyof ParamList[RouteName]>(
paramName: T,
defaultValue?: ParamList[RouteName][T]
): ParamList[RouteName][T];
isFirstRouteInParent(): boolean;
dangerouslyGetParent<
T = NavigationProp<ParamListBase> | undefined
>(): T extends NavigationProp<ParamListBase>
? CompatNavigationProp<T>
: undefined;
};
export type CompatNavigationOptions<
NavigationPropType extends NavigationProp<ParamListBase>,
ScreenOptions extends {} = NavigationPropType extends NavigationProp<
any,
any,
any,
infer O
>
? O
: {}
> =
| ((options: {
navigation: CompatNavigationProp<NavigationPropType>;
navigationOptions: Partial<ScreenOptions>;
screenProps: unknown;
}) => ScreenOptions)
| ScreenOptions;
export type CompatScreenType<
NavigationPropType extends NavigationProp<ParamListBase>
> = React.ComponentType<{
navigation: CompatNavigationProp<NavigationPropType>;
screenProps: unknown;
}> & {
navigationOptions?: CompatNavigationOptions<NavigationPropType>;
};
export type CompatRouteConfig<
NavigationPropType extends NavigationProp<ParamListBase>,
ParamList extends ParamListBase = NavigationPropType extends NavigationProp<
infer P
>
? P
: ParamListBase
> = {
[RouteName in keyof ParamList]:
| React.ComponentType<any>
| ((
| { screen: React.ComponentType<any> }
| { getScreen(): React.ComponentType<any> }
) & {
navigationOptions?: CompatNavigationOptions<NavigationPropType>;
params?: ParamList[RouteName];
});
};

View File

@@ -1,34 +0,0 @@
import * as React from 'react';
import {
useNavigation,
useRoute,
NavigationProp,
ParamListBase,
useNavigationState,
} from '@react-navigation/native';
import createCompatNavigationProp from './createCompatNavigationProp';
import type { CompatNavigationProp } from './types';
export default function useCompatNavigation<
T extends NavigationProp<ParamListBase>
>() {
const navigation = useNavigation();
const route = useRoute();
const isFirstRouteInParent = useNavigationState(
(state) => state.routes[0].key === route.key
);
const context = React.useRef<Record<string, any>>({});
return React.useMemo(
() =>
createCompatNavigationProp(
navigation,
route as any,
context.current,
isFirstRouteInParent
) as CompatNavigationProp<T>,
[isFirstRouteInParent, navigation, route]
);
}

View File

@@ -1,34 +0,0 @@
import * as React from 'react';
import type { NavigationProp, ParamListBase } from '@react-navigation/native';
import useCompatNavigation from './useCompatNavigation';
import type { CompatNavigationProp } from './types';
type InjectedProps<T extends NavigationProp<ParamListBase>> = {
navigation: CompatNavigationProp<T>;
};
export default function withNavigation<
T extends NavigationProp<ParamListBase>,
P extends InjectedProps<T>,
C extends React.ComponentType<P>
>(Comp: C) {
const WrappedComponent = ({
onRef,
...rest
}: Exclude<P, InjectedProps<T>> & {
onRef?: C extends React.ComponentClass<any>
? React.Ref<InstanceType<C>>
: never;
}): React.ReactElement => {
const navigation = useCompatNavigation<T>();
// @ts-expect-error: type checking HOC is hard
return <Comp ref={onRef} navigation={navigation} {...rest} />;
};
WrappedComponent.displayName = `withNavigation(${
Comp.displayName || Comp.name
})`;
return WrappedComponent;
}

View File

@@ -1,31 +0,0 @@
import * as React from 'react';
import { useIsFocused } from '@react-navigation/native';
type InjectedProps = {
isFocused: boolean;
};
export default function withNavigationFocus<
P extends InjectedProps,
C extends React.ComponentType<P>
>(Comp: C) {
const WrappedComponent = ({
onRef,
...rest
}: Exclude<P, InjectedProps> & {
onRef?: C extends React.ComponentClass<any>
? React.Ref<InstanceType<C>>
: never;
}): React.ReactElement => {
const isFocused = useIsFocused();
// @ts-expect-error: type checking HOC is hard
return <Comp ref={onRef} isFocused={isFocused} {...rest} />;
};
WrappedComponent.displayName = `withNavigationFocus(${
Comp.displayName || Comp.name
})`;
return WrappedComponent;
}

View File

@@ -3,6 +3,240 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [6.0.0-next.5](https://github.com/react-navigation/react-navigation/compare/@react-navigation/core@6.0.0-next.4...@react-navigation/core@6.0.0-next.5) (2021-05-09)
### Bug Fixes
* fix type annotations for useNavigation ([7da45e1](https://github.com/react-navigation/react-navigation/commit/7da45e1e8951ff46e09db4ebc2c88085c67ab8e9))
# [6.0.0-next.4](https://github.com/react-navigation/react-navigation/compare/@react-navigation/core@6.0.0-next.3...@react-navigation/core@6.0.0-next.4) (2021-05-09)
### Features
* add a new component to group multiple screens with common options ([1a6aebe](https://github.com/react-navigation/react-navigation/commit/1a6aebefcb77ea708687475c55742407d69808ce))
* add ability to specify root param list ([b28bfdd](https://github.com/react-navigation/react-navigation/commit/b28bfddc17cbf3996fac04a34b2a7085ecf88be5))
# [6.0.0-next.3](https://github.com/react-navigation/react-navigation/compare/@react-navigation/core@6.0.0-next.2...@react-navigation/core@6.0.0-next.3) (2021-05-01)
### Features
* add a CompositeScreenProps type ([def7c03](https://github.com/react-navigation/react-navigation/commit/def7c03d7d7b42cf322f4e277f8f76858717654e))
* add helper and hook for container ref ([0ecd112](https://github.com/react-navigation/react-navigation/commit/0ecd112ec9786a26261ada3d33ef44dc1ec84da0))
# [6.0.0-next.2](https://github.com/react-navigation/react-navigation/compare/@react-navigation/core@6.0.0-next.1...@react-navigation/core@6.0.0-next.2) (2021-04-08)
### Bug Fixes
* properly resolve initialRouteNames ([c38906a](https://github.com/react-navigation/react-navigation/commit/c38906a7a09b997f37ce56734ea823c75ea744db))
### Features
* improve useNavigationState typing ([#9464](https://github.com/react-navigation/react-navigation/issues/9464)) ([84020a0](https://github.com/react-navigation/react-navigation/commit/84020a0b27ebae50d3037438a51d95eb31b02424))
# [6.0.0-next.1](https://github.com/react-navigation/react-navigation/compare/@react-navigation/core@6.0.0...@react-navigation/core@6.0.0-next.1) (2021-03-10)
**Note:** Version bump only for package @react-navigation/core
# [6.0.0-next.0](https://github.com/react-navigation/react-navigation/compare/@react-navigation/core@5.14.3...@react-navigation/core@6.0.0-next.0) (2021-03-09)
### Bug Fixes
* add missing helper types in descriptors ([21a1154](https://github.com/react-navigation/react-navigation/commit/21a11543bf41c4559c2570d5accc0bbb3b67eb8d))
* check duplicate names only for immediate nested screens ([36a9b4f](https://github.com/react-navigation/react-navigation/commit/36a9b4f866c49d6f7350405c54b86bd77e374eb5))
* don't merge params on navigation ([366d018](https://github.com/react-navigation/react-navigation/commit/366d0181dc02597b8d11e343f37fc77bee164c70))
* drop dangerously prefix from getState and getParent ([227f133](https://github.com/react-navigation/react-navigation/commit/227f133536af85dc5ff85eeb269b76ed80cd3f05))
* drop support for legacy linking config ([0e13e8d](https://github.com/react-navigation/react-navigation/commit/0e13e8d23cc2ea74f3b0fce9334ee5c8be2484f4))
* fix default screen options not being respected ([03ba1f2](https://github.com/react-navigation/react-navigation/commit/03ba1f2930cb731266e6bc3044c67fb267837ed1))
* fix incorrect state change events in independent nested container ([b82a912](https://github.com/react-navigation/react-navigation/commit/b82a9126bb91a84e21473723ce40da0cce732a39)), closes [#9080](https://github.com/react-navigation/react-navigation/issues/9080)
* print an error when passing a second argument to useFocusEffect ([c361795](https://github.com/react-navigation/react-navigation/commit/c361795d97eb20150913ddc3fbf139e095d25830))
* remove the state property from route prop ([ebab518](https://github.com/react-navigation/react-navigation/commit/ebab5183522f5ae03f50f88289c0e7acc208dc02))
* show redbox instead of crash if navigation isn't initialized ([13d8553](https://github.com/react-navigation/react-navigation/commit/13d85530ae684eb956d3c4df25919572caf0ec1a))
### Features
* add a way to specify an unique ID for screens ([15b8bb3](https://github.com/react-navigation/react-navigation/commit/15b8bb34584db3cb166f6aafd45f0b95f14fde62))
* add an option to specify default options for the navigator ([c85f2ff](https://github.com/react-navigation/react-navigation/commit/c85f2ff47a2b3d403a3cbe993b46d04914358ba5))
* allow returning null or undefined to skip actions with dispatch ([d6466b7](https://github.com/react-navigation/react-navigation/commit/d6466b7a4b5d3087981dbd50a5f5f56a6092edb3))
* associate path with the route it opens when deep linking ([#9384](https://github.com/react-navigation/react-navigation/issues/9384)) ([86e64fd](https://github.com/react-navigation/react-navigation/commit/86e64fdcd81a57cf3f3bdab4c9035b52984e7009)), closes [#9102](https://github.com/react-navigation/react-navigation/issues/9102)
* warn on duplicate screen names across navigators ([02a031e](https://github.com/react-navigation/react-navigation/commit/02a031e46eac432fc3e26c5de30c7bdf0a81ce49))
### BREAKING CHANGES
* This commit drops support for legacy linking config which allowed screens to be specified without the screens property in the config.
* Previous versions of React Navigation merged params on navigation which caused confusion. This commit changes params not to be merged.
The old behaviour can still be achieved by passing `merge: true` explicitly:
```js
CommonActions.navigate({
name: 'bar',
params: { fruit: 'orange' },
merge: true,
})
```
`initialParams` specified for the screen are always merged.
* any code which relies on `route.state` will break.
Previous versions printed a warning on accessing `route.state`. This commit removes the property entirely. Accessing this property isn't safe since child navigator state isn't gurranteed to be in sync with parent navigator state and cause subtle bugs in apps.
## [5.14.3](https://github.com/react-navigation/react-navigation/compare/@react-navigation/core@5.14.2...@react-navigation/core@5.14.3) (2020-11-10)
### Bug Fixes
* improve the error message for incorrect screen configuration ([8f764d8](https://github.com/react-navigation/react-navigation/commit/8f764d8b0809604716d5d92ea33cc1beee02e804))
## [5.14.2](https://github.com/react-navigation/react-navigation/compare/@react-navigation/core@5.14.1...@react-navigation/core@5.14.2) (2020-11-09)
### Bug Fixes
* throw if the same pattern resolves to multiple screens ([48b2e77](https://github.com/react-navigation/react-navigation/commit/48b2e777307908e8b3fcb49d8555b610dc0e38f2))
## [5.14.1](https://github.com/react-navigation/react-navigation/compare/@react-navigation/core@5.14.0...@react-navigation/core@5.14.1) (2020-11-08)
### Bug Fixes
* tweak error message when navigator has non-screen children ([360b0e9](https://github.com/react-navigation/react-navigation/commit/360b0e995835990c55b75898757ebdd120d52446))
# [5.14.0](https://github.com/react-navigation/react-navigation/compare/@react-navigation/core@5.13.5...@react-navigation/core@5.14.0) (2020-11-04)
### Bug Fixes
* always respect key in the route object when generating action ([cb2e744](https://github.com/react-navigation/react-navigation/commit/cb2e744dcebf7f71ddaa5462d393a6dbfd971fcd))
### Features
* add a NavigatorScreenParams type. closes [#6931](https://github.com/react-navigation/react-navigation/issues/6931) ([e3e58c2](https://github.com/react-navigation/react-navigation/commit/e3e58c2d890e7fab75d78371e349aea55a402fcd))
* add warning on accessing the state object on route prop ([ec7b02a](https://github.com/react-navigation/react-navigation/commit/ec7b02af2ca835122b9000799e2366d7009da6e3))
## [5.13.5](https://github.com/react-navigation/react-navigation/compare/@react-navigation/core@5.13.4...@react-navigation/core@5.13.5) (2020-11-04)
### Bug Fixes
* don't use use-subscription to avoid peer dep related errors ([66f3a4a](https://github.com/react-navigation/react-navigation/commit/66f3a4a0bb39475434668bc94fb1750dbe618ee0)), closes [/github.com/react-navigation/react-navigation/issues/9021#issuecomment-721679760](https://github.com//github.com/react-navigation/react-navigation/issues/9021/issues/issuecomment-721679760)
* use useDebugValue in more places ([b20f2d1](https://github.com/react-navigation/react-navigation/commit/b20f2d1f7ccb82db70df9cddf5746557912daa99))
## [5.13.4](https://github.com/react-navigation/react-navigation/compare/@react-navigation/core@5.13.3...@react-navigation/core@5.13.4) (2020-11-03)
### Bug Fixes
* fix nested navigation not working the first time ([ebc7f9e](https://github.com/react-navigation/react-navigation/commit/ebc7f9ea75bbf6e3b6303027cfa023d7c97342ff))
## [5.13.3](https://github.com/react-navigation/react-navigation/compare/@react-navigation/core@5.13.2...@react-navigation/core@5.13.3) (2020-11-03)
### Bug Fixes
* handle navigating to same screen again for nested screens ([0945689](https://github.com/react-navigation/react-navigation/commit/0945689b70d71a4b5d766c61d57009761c460bf6))
## [5.13.2](https://github.com/react-navigation/react-navigation/compare/@react-navigation/core@5.13.1...@react-navigation/core@5.13.2) (2020-10-30)
### Bug Fixes
* fix params from for the root screen when creating action ([e8515f9](https://github.com/react-navigation/react-navigation/commit/e8515f9cd94a912c107a407dea3d953c4172393f)), closes [#9006](https://github.com/react-navigation/react-navigation/issues/9006)
* trim routes if an index is specified in state ([fb7ac96](https://github.com/react-navigation/react-navigation/commit/fb7ac960c8e1ffca200ecb12696ce5531a139e50))
## [5.13.1](https://github.com/react-navigation/react-navigation/compare/@react-navigation/core@5.13.0...@react-navigation/core@5.13.1) (2020-10-28)
### Bug Fixes
* improve types for route prop in screenOptions ([d26bcc0](https://github.com/react-navigation/react-navigation/commit/d26bcc057ef31f8950f909adf83e263171a42d74))
# [5.13.0](https://github.com/react-navigation/react-navigation/compare/@react-navigation/core@5.12.5...@react-navigation/core@5.13.0) (2020-10-24)
### Bug Fixes
* fix imports from query-string. closes [#8971](https://github.com/react-navigation/react-navigation/issues/8971) ([#8976](https://github.com/react-navigation/react-navigation/issues/8976)) ([261a33a](https://github.com/react-navigation/react-navigation/commit/261a33a0d03150c87b06f01aeace4926b1c03eb6))
### Features
* add an unhandled action listener ([#8895](https://github.com/react-navigation/react-navigation/issues/8895)) ([80ff5a9](https://github.com/react-navigation/react-navigation/commit/80ff5a9c543a44fa2fd7ba7fda0598f1b0d52a64))
* allow deep linking to reset state ([#8973](https://github.com/react-navigation/react-navigation/issues/8973)) ([7f3b27a](https://github.com/react-navigation/react-navigation/commit/7f3b27a9ec8edd9604ac19774baa1f60963ccdc9)), closes [#8952](https://github.com/react-navigation/react-navigation/issues/8952)
* improve types for navigation state ([#8980](https://github.com/react-navigation/react-navigation/issues/8980)) ([7dc2f58](https://github.com/react-navigation/react-navigation/commit/7dc2f5832e371473f3263c01ab39824eb9e2057d))
* update helper types to have navigator specific methods ([f51086e](https://github.com/react-navigation/react-navigation/commit/f51086edea42f2382dac8c6914aac8574132114b))
## [5.12.5](https://github.com/react-navigation/react-navigation/compare/@react-navigation/core@5.12.4...@react-navigation/core@5.12.5) (2020-10-07)
**Note:** Version bump only for package @react-navigation/core

View File

@@ -1,7 +1,7 @@
{
"name": "@react-navigation/core",
"description": "Core utilities for building navigators",
"version": "5.12.5",
"version": "6.0.0-next.5",
"keywords": [
"react",
"react-native",
@@ -35,28 +35,27 @@
"clean": "del lib"
},
"dependencies": {
"@react-navigation/routers": "^5.4.12",
"@react-navigation/routers": "^6.0.0-next.2",
"escape-string-regexp": "^4.0.0",
"nanoid": "^3.1.12",
"query-string": "^6.13.5",
"react-is": "^16.13.0",
"use-subscription": "^1.4.0"
"nanoid": "^3.1.22",
"query-string": "^7.0.0",
"react-is": "^16.13.0"
},
"devDependencies": {
"@react-native-community/bob": "^0.16.2",
"@testing-library/react-native": "^7.0.2",
"@types/react": "^16.9.51",
"@testing-library/react-native": "^7.2.0",
"@types/react": "^16.9.53",
"@types/react-is": "^16.7.1",
"@types/use-subscription": "^1.0.0",
"del-cli": "^3.0.1",
"immer": "^9.0.1",
"react": "~16.13.1",
"react-native-builder-bob": "^0.18.1",
"react-test-renderer": "~16.13.1",
"typescript": "^4.0.3"
"typescript": "^4.2.3"
},
"peerDependencies": {
"react": "*"
},
"@react-native-community/bob": {
"react-native-builder-bob": {
"source": "src",
"output": "lib",
"targets": [

View File

@@ -6,11 +6,14 @@ import {
InitialState,
PartialState,
NavigationAction,
ParamListBase,
} from '@react-navigation/routers';
import EnsureSingleNavigator from './EnsureSingleNavigator';
import UnhandledActionContext from './UnhandledActionContext';
import NavigationBuilderContext from './NavigationBuilderContext';
import NavigationStateContext from './NavigationStateContext';
import UnhandledActionContext from './UnhandledActionContext';
import NavigationRouteContext from './NavigationRouteContext';
import NavigationContext from './NavigationContext';
import { ScheduleUpdateContext } from './useScheduleUpdate';
import useChildListeners from './useChildListeners';
import useKeyedChildListeners from './useKeyedChildListeners';
@@ -18,6 +21,9 @@ import useOptionsGetters from './useOptionsGetters';
import useEventEmitter from './useEventEmitter';
import useSyncState from './useSyncState';
import checkSerializable from './checkSerializable';
import checkDuplicateRouteNames from './checkDuplicateRouteNames';
import findFocusedRoute from './findFocusedRoute';
import { NOT_INITIALIZED_ERROR } from './createNavigationContainerRef';
import type {
NavigationContainerEventMap,
NavigationContainerRef,
@@ -26,10 +32,8 @@ import type {
type State = NavigationState | PartialState<NavigationState> | undefined;
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.";
const serializableWarnings: string[] = [];
const duplicateNameWarnings: string[] = [];
try {
/**
@@ -94,16 +98,17 @@ const BaseNavigationContainer = React.forwardRef(
{
initialState,
onStateChange,
onUnhandledAction,
independent,
children,
}: NavigationContainerProps,
ref?: React.Ref<NavigationContainerRef>
ref?: React.Ref<NavigationContainerRef<ParamListBase>>
) {
const parent = React.useContext(NavigationStateContext);
if (!parent.isDefault && !independent) {
throw new Error(
"Looks like you have nested a 'NavigationContainer' inside another. Normally you need only one container at the root of the app, so this was probably an error. If this was intentional, pass 'independent={true}' explicitely. Note that this will make the child navigators disconnected from the parent and you won't be able to navigate between them."
"Looks like you have nested a 'NavigationContainer' inside another. Normally you need only one container at the root of the app, so this was probably an error. If this was intentional, pass 'independent={true}' explicitly. Note that this will make the child navigators disconnected from the parent and you won't be able to navigate between them."
);
}
@@ -135,10 +140,10 @@ const BaseNavigationContainer = React.forwardRef(
action: NavigationAction | ((state: NavigationState) => NavigationAction)
) => {
if (listeners.focus[0] == null) {
throw new Error(NOT_INITIALIZED_ERROR);
console.error(NOT_INITIALIZED_ERROR);
} else {
listeners.focus[0]((navigation) => navigation.dispatch(action));
}
listeners.focus[0]((navigation) => navigation.dispatch(action));
};
const canGoBack = () => {
@@ -159,9 +164,20 @@ const BaseNavigationContainer = React.forwardRef(
const resetRoot = React.useCallback(
(state?: PartialState<NavigationState> | NavigationState) => {
setState(state);
const target = state?.key ?? keyedListeners.getState.root?.().key;
if (target == null) {
console.error(NOT_INITIALIZED_ERROR);
} else {
listeners.focus[0]((navigation) =>
navigation.dispatch({
...CommonActions.reset(state),
target,
})
);
}
},
[setState]
[keyedListeners.getState, listeners.focus]
);
const getRootState = React.useCallback(() => {
@@ -169,14 +185,15 @@ const BaseNavigationContainer = React.forwardRef(
}, [keyedListeners.getState]);
const getCurrentRoute = React.useCallback(() => {
let state = getRootState();
if (state === undefined) {
const state = getRootState();
if (state == null) {
return undefined;
}
while (state.routes[state.index].state !== undefined) {
state = state.routes[state.index].state as NavigationState;
}
return state.routes[state.index];
const route = findFocusedRoute(state);
return route as Route<string> | undefined;
}, [getRootState]);
const emitter = useEventEmitter<NavigationContainerEventMap>();
@@ -184,16 +201,10 @@ const BaseNavigationContainer = React.forwardRef(
const { addOptionsGetter, getCurrentOptions } = useOptionsGetters({});
React.useImperativeHandle(ref, () => ({
...(Object.keys(CommonActions) as (keyof typeof CommonActions)[]).reduce<
any
>((acc, name) => {
...Object.keys(CommonActions).reduce<any>((acc, name) => {
acc[name] = (...args: any[]) =>
dispatch(
CommonActions[name](
// @ts-expect-error: we can't know the type statically
...args
)
);
// @ts-expect-error: this is ok
dispatch(CommonActions[name](...args));
return acc;
}, {}),
...emitter.create('root'),
@@ -201,10 +212,11 @@ const BaseNavigationContainer = React.forwardRef(
dispatch,
canGoBack,
getRootState,
dangerouslyGetState: () => state,
dangerouslyGetParent: () => undefined,
getState: () => state,
getParent: () => undefined,
getCurrentRoute,
getCurrentOptions,
isReady: () => listeners.focus[0] != null,
}));
const onDispatchAction = React.useCallback(
@@ -280,15 +292,17 @@ const BaseNavigationContainer = React.forwardRef(
});
React.useEffect(() => {
if (process.env.NODE_ENV !== 'production') {
if (state !== undefined) {
const result = checkSerializable(state);
const hydratedState = getRootState();
if (!result.serializable) {
const { location, reason } = result;
if (process.env.NODE_ENV !== 'production') {
if (hydratedState !== undefined) {
const serializableResult = checkSerializable(hydratedState);
if (!serializableResult.serializable) {
const { location, reason } = serializableResult;
let path = '';
let pointer: Record<any, any> = state;
let pointer: Record<any, any> = hydratedState;
let params = false;
for (let i = 0; i < location.length; i++) {
@@ -330,69 +344,102 @@ const BaseNavigationContainer = React.forwardRef(
console.warn(message);
}
}
const duplicateRouteNamesResult = checkDuplicateRouteNames(
hydratedState
);
if (duplicateRouteNamesResult.length) {
const message = `Found screens with the same name nested inside one another. Check:\n${duplicateRouteNamesResult.map(
(locations) => `\n${locations.join(', ')}`
)}\n\nThis can cause confusing behavior during navigation. Consider using unique names for each screen instead.`;
if (!duplicateNameWarnings.includes(message)) {
duplicateNameWarnings.push(message);
console.warn(message);
}
}
}
}
emitter.emit({ type: 'state', data: { state } });
if (!isFirstMountRef.current && onStateChangeRef.current) {
onStateChangeRef.current(getRootState());
onStateChangeRef.current(hydratedState);
}
isFirstMountRef.current = false;
}, [getRootState, emitter, state]);
const onUnhandledAction = React.useCallback((action: NavigationAction) => {
if (process.env.NODE_ENV === 'production') {
return;
}
const defaultOnUnhandledAction = React.useCallback(
(action: NavigationAction) => {
if (process.env.NODE_ENV === 'production') {
return;
}
const payload: Record<string, any> | undefined = action.payload;
const payload: Record<string, any> | undefined = action.payload;
let message = `The action '${action.type}'${
payload ? ` with payload ${JSON.stringify(action.payload)}` : ''
} was not handled by any navigator.`;
let message = `The action '${action.type}'${
payload ? ` with payload ${JSON.stringify(action.payload)}` : ''
} was not handled by any navigator.`;
switch (action.type) {
case 'NAVIGATE':
case 'PUSH':
case 'REPLACE':
case 'JUMP_TO':
if (payload?.name) {
message += `\n\nDo you have a screen named '${payload.name}'?\n\nIf you're trying to navigate to a screen in a nested navigator, see https://reactnavigation.org/docs/nesting-navigators#navigating-to-a-screen-in-a-nested-navigator.`;
} else {
message += `\n\nYou need to pass the name of the screen to navigate to.\n\nSee https://reactnavigation.org/docs/navigation-actions for usage.`;
}
switch (action.type) {
case 'NAVIGATE':
case 'PUSH':
case 'REPLACE':
case 'JUMP_TO':
if (payload?.name) {
message += `\n\nDo you have a screen named '${payload.name}'?\n\nIf you're trying to navigate to a screen in a nested navigator, see https://reactnavigation.org/docs/nesting-navigators#navigating-to-a-screen-in-a-nested-navigator.`;
} else {
message += `\n\nYou need to pass the name of the screen to navigate to.\n\nSee https://reactnavigation.org/docs/navigation-actions for usage.`;
}
break;
case 'GO_BACK':
case 'POP':
case 'POP_TO_TOP':
message += `\n\nIs there any screen to go back to?`;
break;
case 'OPEN_DRAWER':
case 'CLOSE_DRAWER':
case 'TOGGLE_DRAWER':
message += `\n\nIs your screen inside a Drawer navigator?`;
break;
}
break;
case 'GO_BACK':
case 'POP':
case 'POP_TO_TOP':
message += `\n\nIs there any screen to go back to?`;
break;
case 'OPEN_DRAWER':
case 'CLOSE_DRAWER':
case 'TOGGLE_DRAWER':
message += `\n\nIs your screen inside a Drawer navigator?`;
break;
}
message += `\n\nThis is a development-only warning and won't be shown in production.`;
message += `\n\nThis is a development-only warning and won't be shown in production.`;
console.error(message);
}, []);
console.error(message);
},
[]
);
return (
let element = (
<ScheduleUpdateContext.Provider value={scheduleContext}>
<NavigationBuilderContext.Provider value={builderContext}>
<NavigationStateContext.Provider value={context}>
<UnhandledActionContext.Provider value={onUnhandledAction}>
<UnhandledActionContext.Provider
value={onUnhandledAction ?? defaultOnUnhandledAction}
>
<EnsureSingleNavigator>{children}</EnsureSingleNavigator>
</UnhandledActionContext.Provider>
</NavigationStateContext.Provider>
</NavigationBuilderContext.Provider>
</ScheduleUpdateContext.Provider>
);
if (independent) {
// We need to clear any existing contexts for nested independent container to work correctly
element = (
<NavigationRouteContext.Provider value={undefined}>
<NavigationContext.Provider value={undefined}>
{element}
</NavigationContext.Provider>
</NavigationRouteContext.Provider>
);
}
return element;
}
);

View File

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

View File

@@ -6,7 +6,7 @@ import type { NavigationProp } from './types';
* Context which holds the navigation prop for a screen.
*/
const NavigationContext = React.createContext<
NavigationProp<ParamListBase, string, any, any> | undefined
NavigationProp<ParamListBase> | undefined
>(undefined);
export default NavigationContext;

View File

@@ -9,18 +9,13 @@ import NavigationStateContext from './NavigationStateContext';
import StaticContainer from './StaticContainer';
import EnsureSingleNavigator from './EnsureSingleNavigator';
import useOptionsGetters from './useOptionsGetters';
import type { NavigationProp, RouteConfig, EventMapBase } from './types';
import type { NavigationProp, RouteConfigComponent } from './types';
type Props<
State extends NavigationState,
ScreenOptions extends {},
EventMap extends EventMapBase
> = {
screen: RouteConfig<ParamListBase, string, State, ScreenOptions, EventMap>;
type Props<State extends NavigationState, ScreenOptions extends {}> = {
screen: RouteConfigComponent<ParamListBase, string> & { name: string };
navigation: NavigationProp<ParamListBase, string, State, ScreenOptions>;
route: Route<string> & {
state?: NavigationState | PartialState<NavigationState>;
};
route: Route<string>;
routeState: NavigationState | PartialState<NavigationState> | undefined;
getState: () => State;
setState: (state: State) => void;
options: object;
@@ -32,16 +27,16 @@ type Props<
*/
export default function SceneView<
State extends NavigationState,
ScreenOptions extends {},
EventMap extends EventMapBase
ScreenOptions extends {}
>({
screen,
route,
navigation,
routeState,
getState,
setState,
options,
}: Props<State, ScreenOptions, EventMap>) {
}: Props<State, ScreenOptions>) {
const navigatorKeyRef = React.useRef<string | undefined>();
const getKey = React.useCallback(() => navigatorKeyRef.current, []);
@@ -86,7 +81,7 @@ export default function SceneView<
const context = React.useMemo(
() => ({
state: route.state,
state: routeState,
getState: getCurrentState,
setState: setCurrentState,
getKey,
@@ -95,7 +90,7 @@ export default function SceneView<
addOptionsGetter,
}),
[
route.state,
routeState,
getCurrentState,
setCurrentState,
getKey,

View File

@@ -3,16 +3,17 @@ import { act, render } from '@testing-library/react-native';
import {
DefaultRouterOptions,
NavigationState,
ParamListBase,
Router,
StackRouter,
TabRouter,
} from '@react-navigation/routers';
import BaseNavigationContainer from '../BaseNavigationContainer';
import NavigationStateContext from '../NavigationStateContext';
import createNavigationContainerRef from '../createNavigationContainerRef';
import MockRouter, { MockActions } from './__fixtures__/MockRouter';
import useNavigationBuilder from '../useNavigationBuilder';
import Screen from '../Screen';
import type { NavigationContainerRef } from '../types';
it('throws when getState is accessed without a container', () => {
expect.assertions(1);
@@ -128,7 +129,7 @@ it('handle dispatching with ref', () => {
);
};
const ref = React.createRef<NavigationContainerRef>();
const ref = createNavigationContainerRef<ParamListBase>();
const onStateChange = jest.fn();
@@ -141,10 +142,10 @@ it('handle dispatching with ref', () => {
state: {
index: 0,
key: '4',
routeNames: ['qux', 'lex'],
routeNames: ['qux2', 'lex2'],
routes: [
{ key: 'qux', name: 'qux' },
{ key: 'lex', name: 'lex' },
{ key: 'qux2', name: 'qux2' },
{ key: 'lex2', name: 'lex2' },
],
},
},
@@ -163,8 +164,8 @@ it('handle dispatching with ref', () => {
<Screen name="foo2">
{() => (
<ChildNavigator>
<Screen name="qux">{() => null}</Screen>
<Screen name="lex">{() => null}</Screen>
<Screen name="qux1">{() => null}</Screen>
<Screen name="lex1">{() => null}</Screen>
</ChildNavigator>
)}
</Screen>
@@ -172,8 +173,8 @@ it('handle dispatching with ref', () => {
<Screen name="baz">
{() => (
<ChildNavigator>
<Screen name="qux">{() => null}</Screen>
<Screen name="lex">{() => null}</Screen>
<Screen name="qux2">{() => null}</Screen>
<Screen name="lex2">{() => null}</Screen>
</ChildNavigator>
)}
</Screen>
@@ -203,10 +204,10 @@ it('handle dispatching with ref', () => {
type: 'test',
index: 0,
key: '1',
routeNames: ['qux', 'lex'],
routeNames: ['qux2', 'lex2'],
routes: [
{ key: 'lex', name: 'lex' },
{ key: 'qux', name: 'qux' },
{ key: 'lex2', name: 'lex2' },
{ key: 'qux2', name: 'qux2' },
],
},
},
@@ -226,7 +227,7 @@ it('handle resetting state with ref', () => {
);
};
const ref = React.createRef<NavigationContainerRef>();
const ref = createNavigationContainerRef<ParamListBase>();
const onStateChange = jest.fn();
@@ -237,8 +238,8 @@ it('handle resetting state with ref', () => {
<Screen name="foo2">
{() => (
<TestNavigator>
<Screen name="qux">{() => null}</Screen>
<Screen name="lex">{() => null}</Screen>
<Screen name="qux1">{() => null}</Screen>
<Screen name="lex1">{() => null}</Screen>
</TestNavigator>
)}
</Screen>
@@ -246,8 +247,8 @@ it('handle resetting state with ref', () => {
<Screen name="baz">
{() => (
<TestNavigator>
<Screen name="qux">{() => null}</Screen>
<Screen name="lex">{() => null}</Screen>
<Screen name="qux2">{() => null}</Screen>
<Screen name="lex2">{() => null}</Screen>
</TestNavigator>
)}
</Screen>
@@ -266,10 +267,10 @@ it('handle resetting state with ref', () => {
state: {
index: 0,
key: '4',
routeNames: ['qux', 'lex'],
routeNames: ['qux2', 'lex2'],
routes: [
{ key: 'qux', name: 'qux' },
{ key: 'lex', name: 'lex' },
{ key: 'qux2', name: 'qux2' },
{ key: 'lex2', name: 'lex2' },
],
},
},
@@ -293,10 +294,10 @@ it('handle resetting state with ref', () => {
state: {
index: 0,
key: '6',
routeNames: ['qux', 'lex'],
routeNames: ['qux2', 'lex2'],
routes: [
{ key: 'qux', name: 'qux' },
{ key: 'lex', name: 'lex' },
{ key: 'qux2', name: 'qux2' },
{ key: 'lex2', name: 'lex2' },
],
stale: false,
type: 'test',
@@ -316,7 +317,7 @@ it('handles getRootState', () => {
return descriptors[state.routes[state.index].key].render();
};
const ref = React.createRef<NavigationContainerRef>();
const ref = createNavigationContainerRef<ParamListBase>();
const element = (
<BaseNavigationContainer ref={ref}>
@@ -378,7 +379,7 @@ it('emits state events when the state changes', () => {
);
};
const ref = React.createRef<NavigationContainerRef>();
const ref = createNavigationContainerRef<ParamListBase>();
const element = (
<BaseNavigationContainer ref={ref}>
@@ -448,7 +449,7 @@ it('emits state events when new navigator mounts', () => {
);
};
const ref = React.createRef<NavigationContainerRef>();
const ref = createNavigationContainerRef<ParamListBase>();
const NestedNavigator = () => {
const [isRendered, setIsRendered] = React.useState(false);
@@ -537,7 +538,7 @@ it('emits option events when options change with tab router', () => {
);
};
const ref = React.createRef<NavigationContainerRef>();
const ref = createNavigationContainerRef<ParamListBase>();
const element = (
<BaseNavigationContainer ref={ref}>
@@ -611,7 +612,7 @@ it('emits option events when options change with stack router', () => {
);
};
const ref = React.createRef<NavigationContainerRef>();
const ref = createNavigationContainerRef<ParamListBase>();
const element = (
<BaseNavigationContainer ref={ref}>
@@ -677,23 +678,27 @@ it('emits option events when options change with stack router', () => {
it('throws if there is no navigator rendered', () => {
expect.assertions(1);
const ref = React.createRef<NavigationContainerRef>();
const ref = createNavigationContainerRef<ParamListBase>();
const element = <BaseNavigationContainer ref={ref} children={null} />;
render(element);
act(() => {
expect(() => ref.current?.dispatch({ type: 'WHATEVER' })).toThrow(
"The 'navigation' object hasn't been initialized yet."
);
});
const spy = jest.spyOn(console, 'error').mockImplementation();
ref.current?.dispatch({ type: 'WHATEVER' });
expect(spy.mock.calls[0][0]).toMatch(
"The 'navigation' object hasn't been initialized yet."
);
spy.mockRestore();
});
it("throws if the ref hasn't finished initializing", () => {
expect.assertions(1);
const ref = React.createRef<NavigationContainerRef>();
const ref = createNavigationContainerRef<ParamListBase>();
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
@@ -703,9 +708,15 @@ it("throws if the ref hasn't finished initializing", () => {
const TestScreen = () => {
React.useEffect(() => {
expect(() => ref.current?.dispatch({ type: 'WHATEVER' })).toThrow(
const spy = jest.spyOn(console, 'error').mockImplementation();
ref.current?.dispatch({ type: 'WHATEVER' });
expect(spy.mock.calls[0][0]).toMatch(
"The 'navigation' object hasn't been initialized yet."
);
spy.mockRestore();
}, []);
return null;
@@ -721,3 +732,185 @@ it("throws if the ref hasn't finished initializing", () => {
render(element);
});
it('invokes the unhandled action listener with the unhandled action', () => {
const ref = createNavigationContainerRef<ParamListBase>();
const fn = jest.fn();
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return (
<React.Fragment>
{state.routes.map((route) => descriptors[route.key].render())}
</React.Fragment>
);
};
const TestScreen = () => <></>;
render(
<BaseNavigationContainer ref={ref} onUnhandledAction={fn}>
<TestNavigator>
<Screen name="foo" component={TestScreen} />
<Screen name="bar" component={TestScreen} />
</TestNavigator>
</BaseNavigationContainer>
);
act(() => ref.current!.navigate('bar'));
act(() => ref.current!.navigate('baz'));
expect(fn).toHaveBeenCalledWith({
payload: {
name: 'baz',
},
type: 'NAVIGATE',
});
});
it('works with state change events in independent nested container', () => {
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return (
<React.Fragment>
{state.routes.map((route) => descriptors[route.key].render())}
</React.Fragment>
);
};
const ref = createNavigationContainerRef<ParamListBase>();
const onStateChange = jest.fn();
render(
<BaseNavigationContainer>
<TestNavigator>
<Screen name="foo">
{() => (
<BaseNavigationContainer
independent
ref={ref}
onStateChange={onStateChange}
>
<TestNavigator>
<Screen name="qux">{() => null}</Screen>
<Screen name="lex">{() => null}</Screen>
</TestNavigator>
</BaseNavigationContainer>
)}
</Screen>
<Screen name="bar">{() => null}</Screen>
</TestNavigator>
</BaseNavigationContainer>
);
act(() => ref.current?.navigate('lex'));
expect(onStateChange).toBeCalledWith({
index: 1,
key: '15',
routeNames: ['qux', 'lex'],
routes: [
{ key: 'qux', name: 'qux' },
{ key: 'lex', name: 'lex' },
],
stale: false,
type: 'test',
});
expect(ref.current?.getRootState()).toEqual({
index: 1,
key: '15',
routeNames: ['qux', 'lex'],
routes: [
{ key: 'qux', name: 'qux' },
{ key: 'lex', name: 'lex' },
],
stale: false,
type: 'test',
});
});
it('warns for duplicate route names nested inside each other', () => {
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return (
<React.Fragment>
{descriptors[state.routes[state.index].key].render()}
</React.Fragment>
);
};
const TestScreen = () => <></>;
const spy = jest.spyOn(console, 'warn').mockImplementation();
render(
<BaseNavigationContainer>
<TestNavigator>
<Screen name="foo">
{() => (
<TestNavigator>
<Screen name="foo" component={TestScreen} />
<Screen name="baz" component={TestScreen} />
</TestNavigator>
)}
</Screen>
<Screen name="bar" component={TestScreen} />
</TestNavigator>
</BaseNavigationContainer>
);
expect(spy.mock.calls[0][0]).toMatch(
'Found screens with the same name nested inside one another.'
);
render(
<BaseNavigationContainer>
<TestNavigator>
<Screen name="qux">
{() => (
<TestNavigator>
<Screen name="foo">
{() => (
<TestNavigator>
<Screen name="foo" component={TestScreen} />
<Screen name="baz" component={TestScreen} />
</TestNavigator>
)}
</Screen>
<Screen name="bar" component={TestScreen} />
</TestNavigator>
)}
</Screen>
</TestNavigator>
</BaseNavigationContainer>
);
expect(spy.mock.calls[1][0]).toMatch(
'Found screens with the same name nested inside one another.'
);
render(
<BaseNavigationContainer>
<TestNavigator initialRouteName="bar">
<Screen name="foo" component={TestScreen} />
<Screen name="bar">
{() => (
<TestNavigator>
<Screen name="foo" component={TestScreen} />
<Screen name="baz" component={TestScreen} />
</TestNavigator>
)}
</Screen>
</TestNavigator>
</BaseNavigationContainer>
);
expect(spy).toHaveBeenCalledTimes(2);
spy.mockRestore();
});

View File

@@ -132,6 +132,17 @@ export default function MockRouter(options: DefaultRouterOptions) {
};
}
case 'GO_BACK': {
if (state.index === 0) {
return null;
}
return {
...state,
index: state.index - 1,
};
}
default:
return BaseRouter.getStateForAction(state, action);
}

View File

@@ -15,6 +15,7 @@ it('gets navigate action from state', () => {
{
name: 'qux',
params: { author: 'jane' },
path: '/foo/bar',
},
],
},
@@ -35,6 +36,7 @@ it('gets navigate action from state', () => {
author: 'jane',
},
screen: 'qux',
path: '/foo/bar',
initial: true,
},
screen: 'bar',
@@ -43,11 +45,61 @@ it('gets navigate action from state', () => {
},
type: 'NAVIGATE',
});
});
expect(
getActionFromState({
it('gets navigate action from state for top-level screen', () => {
const state = {
routes: [
{
name: 'foo',
params: { answer: 42 },
path: '/foo/bar',
},
],
};
expect(getActionFromState(state)).toEqual({
payload: {
name: 'foo',
params: { answer: 42 },
path: '/foo/bar',
},
type: 'NAVIGATE',
});
});
it('gets reset action from state with 1 route with key at root', () => {
const state = {
routes: [
{
name: 'foo',
key: 'test',
state: {
routes: [
{
name: 'bar',
state: {
routes: [
{
key: 'test',
name: 'qux',
params: { author: 'jane' },
path: '/foo/bar',
},
],
},
},
],
},
},
],
};
expect(getActionFromState(state)).toEqual({
payload: {
routes: [
{
key: 'test',
name: 'foo',
state: {
routes: [
@@ -56,10 +108,11 @@ it('gets navigate action from state', () => {
state: {
routes: [
{
key: 'test',
name: 'qux',
params: { author: 'jane' },
path: '/foo/bar',
},
{ name: 'quz' },
],
},
},
@@ -67,8 +120,473 @@ it('gets navigate action from state', () => {
},
},
],
})
).toEqual({
},
type: 'RESET',
});
});
it('gets reset action from state for top-level screen with 2 screens', () => {
const state = {
routes: [
{
name: 'foo',
params: { answer: 42 },
},
{
name: 'bar',
params: { author: 'jane' },
path: '/foo/bar',
},
],
};
expect(getActionFromState(state)).toEqual({
payload: {
routes: [
{
name: 'foo',
params: { answer: 42 },
},
{
name: 'bar',
params: { author: 'jane' },
path: '/foo/bar',
},
],
},
type: 'RESET',
});
});
it('gets reset action from state for top-level screen with more than 2 screens with config', () => {
const state = {
routes: [
{
name: 'foo',
params: { answer: 42 },
},
{
name: 'bar',
params: { author: 'jane' },
},
{ name: 'baz' },
],
};
const config = {
initialRouteName: 'foo',
screens: {
bar: 'bar',
},
};
expect(getActionFromState(state, config)).toEqual({
payload: {
routes: [
{
name: 'foo',
params: { answer: 42 },
},
{
name: 'bar',
params: { author: 'jane' },
},
{ name: 'baz' },
],
},
type: 'RESET',
});
});
it('gets reset action from state for top-level screen with 2 screens with config', () => {
const state = {
routes: [
{
name: 'foo',
params: { answer: 42 },
},
{
name: 'bar',
key: 'test',
params: { author: 'jane' },
path: '/foo/bar',
},
],
};
const config = {
initialRouteName: 'foo',
screens: {
bar: 'bar',
},
};
expect(getActionFromState(state, config)).toEqual({
payload: {
routes: [
{
name: 'foo',
params: { answer: 42 },
},
{
name: 'bar',
key: 'test',
params: { author: 'jane' },
path: '/foo/bar',
},
],
},
type: 'RESET',
});
});
it('gets navigate action from state for top-level screen with 2 screens with config', () => {
const state = {
routes: [
{
name: 'foo',
params: { answer: 42 },
},
{
name: 'bar',
params: { author: 'jane' },
path: '/foo/bar',
},
],
};
const config = {
initialRouteName: 'foo',
screens: {
bar: 'bar',
},
};
expect(getActionFromState(state, config)).toEqual({
payload: {
name: 'bar',
params: { author: 'jane' },
path: '/foo/bar',
},
type: 'NAVIGATE',
});
});
it('gets navigate action from state for top-level screen with more than 2 screens with config with lower index', () => {
const state = {
index: 1,
routes: [
{
name: 'foo',
params: { answer: 42 },
},
{
name: 'bar',
params: { author: 'jane' },
path: '/foo/bar',
},
{ name: 'baz' },
],
};
const config = {
initialRouteName: 'foo',
screens: {
bar: 'bar',
},
};
expect(getActionFromState(state, config)).toEqual({
payload: {
name: 'bar',
params: { author: 'jane' },
path: '/foo/bar',
},
type: 'NAVIGATE',
});
});
it('gets navigate action from state with 2 screens', () => {
const state = {
routes: [
{
name: 'foo',
state: {
routes: [
{
name: 'bar',
state: {
routes: [
{
name: 'qux',
params: { author: 'jane' },
},
{ name: 'quz', path: '/foo/bar' },
],
},
},
],
},
},
],
};
expect(getActionFromState(state)).toEqual({
payload: {
name: 'foo',
params: {
screen: 'bar',
initial: true,
params: {
state: {
routes: [
{
name: 'qux',
params: {
author: 'jane',
},
},
{ name: 'quz', path: '/foo/bar' },
],
},
},
},
},
type: 'NAVIGATE',
});
});
it('gets navigate action from state with 2 screens with lower index', () => {
const state = {
routes: [
{
name: 'foo',
state: {
routes: [
{
name: 'bar',
state: {
index: 0,
routes: [
{
name: 'qux',
params: { author: 'jane' },
path: '/foo/bar',
},
{ name: 'quz' },
],
},
},
],
},
},
],
};
expect(getActionFromState(state)).toEqual({
payload: {
name: 'foo',
params: {
screen: 'bar',
initial: true,
params: {
screen: 'qux',
initial: true,
params: {
author: 'jane',
},
path: '/foo/bar',
},
},
},
type: 'NAVIGATE',
});
});
it('gets navigate action from state with more than 2 screens', () => {
const state = {
routes: [
{
name: 'foo',
state: {
routes: [
{
name: 'bar',
state: {
routes: [
{
name: 'qux',
params: { author: 'jane' },
},
{ name: 'quz' },
{ name: 'qua' },
],
},
},
],
},
},
],
};
expect(getActionFromState(state)).toEqual({
payload: {
name: 'foo',
params: {
screen: 'bar',
initial: true,
params: {
state: {
routes: [
{
name: 'qux',
params: {
author: 'jane',
},
},
{ name: 'quz' },
{ name: 'qua' },
],
},
},
},
},
type: 'NAVIGATE',
});
});
it('gets navigate action from state with config', () => {
const state = {
routes: [
{
name: 'foo',
state: {
routes: [
{
name: 'bar',
params: { answer: 42 },
state: {
routes: [
{
name: 'qux',
params: { author: 'jane' },
path: '/foo/bar',
},
],
},
},
],
},
},
],
};
const config = {
screens: {
foo: {
initialRouteName: 'bar',
screens: {
bar: {
initialRouteName: 'qux',
},
},
},
},
};
expect(getActionFromState(state, config)).toEqual({
payload: {
name: 'foo',
params: {
params: {
answer: 42,
params: {
author: 'jane',
},
screen: 'qux',
path: '/foo/bar',
initial: true,
},
screen: 'bar',
initial: true,
},
},
type: 'NAVIGATE',
});
});
it('gets navigate action from state for top-level screen with config', () => {
const state = {
routes: [
{
name: 'foo',
params: { answer: 42 },
path: '/foo/bar',
},
],
};
const config = {
screens: {
initialRouteName: 'bar',
foo: {
path: 'some-path/:answer',
parse: {
answer: Number,
},
},
},
};
expect(getActionFromState(state, config)).toEqual({
payload: {
name: 'foo',
params: { answer: 42 },
path: '/foo/bar',
},
type: 'NAVIGATE',
});
});
it('gets navigate action from state with 2 screens including initial route and with config', () => {
const state = {
routes: [
{
name: 'foo',
state: {
routes: [
{
name: 'bar',
state: {
routes: [
{
name: 'qux',
params: { author: 'jane' },
},
{ name: 'quz', path: '/foo/bar' },
],
},
},
],
},
},
],
};
const config = {
screens: {
foo: {
initialRouteName: 'bar',
screens: {
bar: {
initialRouteName: 'qux',
},
},
},
},
};
expect(getActionFromState(state, config)).toEqual({
payload: {
name: 'foo',
params: {
@@ -77,6 +595,7 @@ it('gets navigate action from state', () => {
params: {
screen: 'quz',
initial: false,
path: '/foo/bar',
},
},
},
@@ -84,7 +603,334 @@ it('gets navigate action from state', () => {
});
});
it('gets reset action from state', () => {
it('gets navigate action from state with 2 screens without initial route and with config', () => {
const state = {
routes: [
{
name: 'foo',
state: {
routes: [
{
name: 'bar',
state: {
routes: [
{
name: 'qux',
params: { author: 'jane' },
},
{ name: 'quz', path: '/foo/bar' },
],
},
},
],
},
},
],
};
const config = {
screens: {
foo: {
initialRouteName: 'bar',
screens: {
bar: {
initialRouteName: 'quz',
},
},
},
},
};
expect(getActionFromState(state, config)).toEqual({
payload: {
name: 'foo',
params: {
initial: true,
screen: 'bar',
params: {
state: {
routes: [
{
name: 'qux',
params: {
author: 'jane',
},
},
{ name: 'quz', path: '/foo/bar' },
],
},
},
},
},
type: 'NAVIGATE',
});
});
it('gets navigate action from state with 2 screens including route with key on initial route and with config', () => {
const state = {
routes: [
{
name: 'foo',
state: {
routes: [
{
name: 'bar',
state: {
routes: [
{
key: 'test',
name: 'qux',
params: { author: 'jane' },
},
{ name: 'quz' },
],
},
},
],
},
},
],
};
const config = {
screens: {
foo: {
initialRouteName: 'bar',
screens: {
bar: {
initialRouteName: 'qux',
},
},
},
},
};
expect(getActionFromState(state, config)).toEqual({
payload: {
name: 'foo',
params: {
initial: true,
screen: 'bar',
params: {
state: {
routes: [
{
key: 'test',
name: 'qux',
params: {
author: 'jane',
},
},
{ name: 'quz' },
],
},
},
},
},
type: 'NAVIGATE',
});
});
it('gets navigate action from state with 2 screens including route with key on 2nd route and with config', () => {
const state = {
routes: [
{
name: 'foo',
state: {
routes: [
{
name: 'bar',
state: {
routes: [
{
name: 'qux',
params: { author: 'jane' },
},
{
key: 'test',
name: 'quz',
},
],
},
},
],
},
},
],
};
const config = {
screens: {
foo: {
initialRouteName: 'bar',
screens: {
bar: {
initialRouteName: 'qux',
},
},
},
},
};
expect(getActionFromState(state, config)).toEqual({
payload: {
name: 'foo',
params: {
initial: true,
screen: 'bar',
params: {
state: {
routes: [
{
name: 'qux',
params: {
author: 'jane',
},
},
{
key: 'test',
name: 'quz',
},
],
},
},
},
},
type: 'NAVIGATE',
});
});
it('gets navigate action from state with more than 2 screens and with config', () => {
const state = {
routes: [
{
name: 'foo',
state: {
routes: [
{
name: 'bar',
state: {
routes: [
{
name: 'qux',
params: { author: 'jane' },
},
{ name: 'quz' },
{ name: 'qua' },
],
},
},
],
},
},
],
};
const config = {
screens: {
foo: {
initialRouteName: 'bar',
screens: {
bar: {
initialRouteName: 'qux',
},
},
},
},
};
expect(getActionFromState(state, config)).toEqual({
payload: {
name: 'foo',
params: {
initial: true,
screen: 'bar',
params: {
state: {
routes: [
{
name: 'qux',
params: {
author: 'jane',
},
},
{ name: 'quz' },
{ name: 'qua' },
],
},
},
},
},
type: 'NAVIGATE',
});
});
it('gets navigate action from state with more than 2 screens with lower index', () => {
const state = {
routes: [
{
name: 'foo',
state: {
routes: [
{
name: 'bar',
state: {
index: 1,
routes: [
{ name: 'quu' },
{
name: 'qux',
params: { author: 'jane' },
path: '/foo/bar',
},
{ name: 'quz' },
],
},
},
],
},
},
],
};
const config = {
screens: {
foo: {
initialRouteName: 'bar',
screens: {
bar: {
initialRouteName: 'quu',
},
},
},
},
};
expect(getActionFromState(state, config)).toEqual({
payload: {
name: 'foo',
params: {
screen: 'bar',
initial: true,
params: {
screen: 'qux',
initial: false,
path: '/foo/bar',
params: {
author: 'jane',
},
},
},
},
type: 'NAVIGATE',
});
});
it("doesn't return action if no routes are provided'", () => {
expect(getActionFromState({ routes: [] })).toBe(undefined);
});
it('gets undefined action from state', () => {
const state = {
routes: [
{

View File

@@ -1,4 +1,5 @@
import getFocusedRouteNameFromRoute from '../getFocusedRouteNameFromRoute';
import { CHILD_STATE } from '../useRouteCache';
it('gets undefined if there is no nested state', () => {
expect(getFocusedRouteNameFromRoute({ name: 'Home' })).toBe(undefined);
@@ -8,6 +9,7 @@ it('gets focused route name from nested state', () => {
expect(
getFocusedRouteNameFromRoute({
name: 'Home',
// @ts-expect-error: this isn't in the type defs
state: {
routes: [{ name: 'Article' }],
},
@@ -17,6 +19,7 @@ it('gets focused route name from nested state', () => {
expect(
getFocusedRouteNameFromRoute({
name: 'Home',
// @ts-expect-error: this isn't in the type defs
state: {
index: 1,
routes: [{ name: 'Article' }, { name: 'Chat' }, { name: 'Album' }],
@@ -27,6 +30,7 @@ it('gets focused route name from nested state', () => {
expect(
getFocusedRouteNameFromRoute({
name: 'Home',
// @ts-expect-error: this isn't in the type defs
state: {
routes: [{ name: 'Article' }, { name: 'Chat' }],
},
@@ -36,6 +40,7 @@ it('gets focused route name from nested state', () => {
expect(
getFocusedRouteNameFromRoute({
name: 'Home',
// @ts-expect-error: this isn't in the type defs
state: {
type: 'tab',
routes: [{ name: 'Article' }, { name: 'Chat' }],
@@ -44,6 +49,50 @@ it('gets focused route name from nested state', () => {
).toBe('Article');
});
it('gets focused route name from nested state with symbol', () => {
expect(
getFocusedRouteNameFromRoute({
name: 'Home',
// @ts-expect-error: this isn't in the type defs
[CHILD_STATE]: {
routes: [{ name: 'Article' }],
},
})
).toBe('Article');
expect(
getFocusedRouteNameFromRoute({
name: 'Home',
// @ts-expect-error: this isn't in the type defs
[CHILD_STATE]: {
index: 1,
routes: [{ name: 'Article' }, { name: 'Chat' }, { name: 'Album' }],
},
})
).toBe('Chat');
expect(
getFocusedRouteNameFromRoute({
name: 'Home',
// @ts-expect-error: this isn't in the type defs
[CHILD_STATE]: {
routes: [{ name: 'Article' }, { name: 'Chat' }],
},
})
).toBe('Chat');
expect(
getFocusedRouteNameFromRoute({
name: 'Home',
// @ts-expect-error: this isn't in the type defs
[CHILD_STATE]: {
type: 'tab',
routes: [{ name: 'Article' }, { name: 'Chat' }],
},
})
).toBe('Article');
});
it('gets nested screen in params if present', () => {
expect(
getFocusedRouteNameFromRoute({

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,12 +1,13 @@
import * as React from 'react';
import { render, act } from '@testing-library/react-native';
import type { NavigationState } from '@react-navigation/routers';
import type { NavigationState, ParamListBase } from '@react-navigation/routers';
import Group from '../Group';
import Screen from '../Screen';
import BaseNavigationContainer from '../BaseNavigationContainer';
import useNavigationBuilder from '../useNavigationBuilder';
import createNavigationContainerRef from '../createNavigationContainerRef';
import useNavigation from '../useNavigation';
import MockRouter, { MockRouterKey } from './__fixtures__/MockRouter';
import type { NavigationContainerRef } from '../types';
beforeEach(() => (MockRouterKey.current = 0));
@@ -248,6 +249,53 @@ it('initializes state for nested screens in React.Fragment', () => {
});
});
it('initializes state for nested screens in Group', () => {
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return descriptors[state.routes[state.index].key].render();
};
const TestScreen = (props: any) => {
React.useEffect(() => {
props.navigation.dispatch({ type: 'UPDATE' });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return null;
};
const onStateChange = jest.fn();
const element = (
<BaseNavigationContainer onStateChange={onStateChange}>
<TestNavigator>
<Screen name="foo" component={TestScreen} />
<Group>
<Screen name="bar" component={jest.fn()} />
<Screen name="baz" component={jest.fn()} />
</Group>
</TestNavigator>
</BaseNavigationContainer>
);
render(element).update(element);
expect(onStateChange).toBeCalledTimes(1);
expect(onStateChange).toBeCalledWith({
stale: false,
type: 'test',
index: 0,
key: '0',
routeNames: ['foo', 'bar', 'baz'],
routes: [
{ key: 'foo', name: 'foo' },
{ key: 'bar', name: 'bar' },
{ key: 'baz', name: 'baz' },
],
});
});
it('initializes state for nested navigator on navigation', () => {
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
@@ -550,7 +598,7 @@ it('updates route params with setParams applied to parent', () => {
let setParams: (params: object) => void = () => undefined;
const FooScreen = (props: any) => {
const parent = props.navigation.dangerouslyGetParent();
const parent = props.navigation.getParent();
if (parent) {
setParams = parent.setParams;
}
@@ -679,7 +727,7 @@ it('navigates to nested child in a navigator', () => {
const onStateChange = jest.fn();
const navigation = React.createRef<NavigationContainerRef>();
const navigation = createNavigationContainerRef<ParamListBase>();
const element = render(
<BaseNavigationContainer ref={navigation} onStateChange={onStateChange}>
@@ -715,7 +763,7 @@ it('navigates to nested child in a navigator', () => {
expect(element).toMatchInlineSnapshot(`"[foo-a, undefined]"`);
act(() =>
navigation.current?.navigate('bar', {
navigation.navigate('bar', {
screen: 'bar-b',
params: { test: 42 },
})
@@ -726,7 +774,7 @@ it('navigates to nested child in a navigator', () => {
);
act(() =>
navigation.current?.navigate('bar', {
navigation.navigate('bar', {
screen: 'bar-a',
params: { whoa: 'test' },
})
@@ -735,6 +783,20 @@ it('navigates to nested child in a navigator', () => {
expect(element).toMatchInlineSnapshot(
`"[bar-a, {\\"lol\\":\\"why\\",\\"whoa\\":\\"test\\"}]"`
);
act(() => navigation.navigate('bar', { screen: 'bar-b' }));
act(() => navigation.goBack());
expect(element).toMatchInlineSnapshot(
`"[bar-a, {\\"lol\\":\\"why\\",\\"whoa\\":\\"test\\"}]"`
);
act(() => navigation.navigate('bar', { screen: 'bar-b' }));
expect(element).toMatchInlineSnapshot(
`"[bar-b, {\\"some\\":\\"stuff\\",\\"test\\":42,\\"whoa\\":\\"test\\"}]"`
);
});
it('navigates to nested child in a navigator with initial: false', () => {
@@ -785,7 +847,7 @@ it('navigates to nested child in a navigator with initial: false', () => {
const onStateChange = jest.fn();
const navigation = React.createRef<NavigationContainerRef>();
const navigation = createNavigationContainerRef<ParamListBase>();
const first = render(
<BaseNavigationContainer ref={navigation} onStateChange={onStateChange}>
@@ -819,7 +881,7 @@ it('navigates to nested child in a navigator with initial: false', () => {
);
expect(first).toMatchInlineSnapshot(`"[foo-a, undefined]"`);
expect(navigation.current?.getRootState()).toEqual({
expect(navigation.getRootState()).toEqual({
index: 0,
key: '0',
routeNames: ['foo', 'bar'],
@@ -852,7 +914,7 @@ it('navigates to nested child in a navigator with initial: false', () => {
});
act(() =>
navigation.current?.navigate('bar', {
navigation.navigate('bar', {
screen: 'bar-b',
params: { test: 42 },
})
@@ -862,7 +924,7 @@ it('navigates to nested child in a navigator with initial: false', () => {
`"[bar-b, {\\"some\\":\\"stuff\\",\\"test\\":42}]"`
);
expect(navigation.current?.getRootState()).toEqual({
expect(navigation.getRootState()).toEqual({
index: 2,
key: '0',
routeNames: ['foo', 'bar'],
@@ -930,7 +992,7 @@ it('navigates to nested child in a navigator with initial: false', () => {
);
expect(second).toMatchInlineSnapshot(`"[foo-a, undefined]"`);
expect(navigation.current?.getRootState()).toEqual({
expect(navigation.getRootState()).toEqual({
index: 0,
key: '4',
routeNames: ['foo', 'bar'],
@@ -957,7 +1019,7 @@ it('navigates to nested child in a navigator with initial: false', () => {
});
act(() =>
navigation.current?.navigate('bar', {
navigation.navigate('bar', {
screen: 'bar-b',
params: { test: 42 },
initial: false,
@@ -966,7 +1028,7 @@ it('navigates to nested child in a navigator with initial: false', () => {
expect(second).toMatchInlineSnapshot(`"[bar-b, {\\"test\\":42}]"`);
expect(navigation.current?.getRootState()).toEqual({
expect(navigation.getRootState()).toEqual({
index: 2,
key: '4',
routeNames: ['foo', 'bar'],
@@ -1057,7 +1119,7 @@ it('navigates to nested child in a navigator with initial: false', () => {
expect(third).toMatchInlineSnapshot(`"[bar-b, {\\"some\\":\\"stuff\\"}]"`);
expect(navigation.current?.getRootState()).toEqual({
expect(navigation.getRootState()).toEqual({
index: 1,
key: '11',
routeNames: ['foo', 'bar'],
@@ -1093,6 +1155,194 @@ it('navigates to nested child in a navigator with initial: false', () => {
});
});
it('resets state of a nested child in a navigator', () => {
const TestNavigator = (props: any): any => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return descriptors[state.routes[state.index].key].render();
};
const TestComponent = ({ route }: any): any =>
`[${route.name}, ${JSON.stringify(route.params)}]`;
const onStateChange = jest.fn();
const navigation = createNavigationContainerRef<ParamListBase>();
const first = render(
<BaseNavigationContainer ref={navigation} onStateChange={onStateChange}>
<TestNavigator>
<Screen name="foo">
{() => (
<TestNavigator>
<Screen name="foo-a" component={TestComponent} />
<Screen name="foo-b" component={TestComponent} />
</TestNavigator>
)}
</Screen>
<Screen name="bar">
{() => (
<TestNavigator initialRouteName="bar-a">
<Screen name="bar-a" component={TestComponent} />
<Screen
name="bar-b"
component={TestComponent}
initialParams={{ some: 'stuff' }}
/>
</TestNavigator>
)}
</Screen>
</TestNavigator>
</BaseNavigationContainer>
);
expect(first).toMatchInlineSnapshot(`"[foo-a, undefined]"`);
expect(navigation.getRootState()).toEqual({
index: 0,
key: '0',
routeNames: ['foo', 'bar'],
routes: [
{
key: 'foo',
name: 'foo',
state: {
index: 0,
key: '1',
routeNames: ['foo-a', 'foo-b'],
routes: [
{
key: 'foo-a',
name: 'foo-a',
},
{
key: 'foo-b',
name: 'foo-b',
},
],
stale: false,
type: 'test',
},
},
{ key: 'bar', name: 'bar' },
],
stale: false,
type: 'test',
});
act(() =>
navigation.navigate('bar', {
state: {
routes: [{ name: 'bar-a' }, { name: 'bar-b' }],
},
})
);
expect(first).toMatchInlineSnapshot(`"[bar-a, undefined]"`);
expect(navigation.getRootState()).toEqual({
index: 1,
key: '0',
routeNames: ['foo', 'bar'],
routes: [
{ key: 'foo', name: 'foo' },
{
key: 'bar',
name: 'bar',
params: {
state: {
routes: [{ name: 'bar-a' }, { name: 'bar-b' }],
},
},
state: {
index: 0,
key: '4',
routeNames: ['bar-a', 'bar-b'],
routes: [
{
key: 'bar-a-2',
name: 'bar-a',
},
{
key: 'bar-b-3',
name: 'bar-b',
params: { some: 'stuff' },
},
],
stale: false,
type: 'test',
},
},
],
stale: false,
type: 'test',
});
act(() =>
navigation.navigate('bar', {
state: {
index: 2,
routes: [
{ key: '37', name: 'bar-b' },
{ name: 'bar-b' },
{ name: 'bar-a', params: { test: 18 } },
],
},
})
);
expect(first).toMatchInlineSnapshot(`"[bar-a, {\\"test\\":18}]"`);
expect(navigation.getRootState()).toEqual({
index: 1,
key: '0',
routeNames: ['foo', 'bar'],
routes: [
{ key: 'foo', name: 'foo' },
{
key: 'bar',
name: 'bar',
params: {
state: {
index: 2,
routes: [
{ key: '37', name: 'bar-b' },
{ name: 'bar-b' },
{ name: 'bar-a', params: { test: 18 } },
],
},
},
state: {
index: 2,
key: '7',
routeNames: ['bar-a', 'bar-b'],
routes: [
{
key: '37',
name: 'bar-b',
params: { some: 'stuff' },
},
{
key: 'bar-b-5',
name: 'bar-b',
params: { some: 'stuff' },
},
{
key: 'bar-a-6',
name: 'bar-a',
params: { test: 18 },
},
],
stale: false,
type: 'test',
},
},
],
stale: false,
type: 'test',
});
});
it('gives access to internal state', () => {
const TestNavigator = (props: any): any => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
@@ -1104,7 +1354,7 @@ it('gives access to internal state', () => {
const Test = () => {
const navigation = useNavigation();
state = navigation.dangerouslyGetState();
state = navigation.getState();
return null;
};
@@ -1134,7 +1384,7 @@ it('preserves order of screens in state with non-numeric names', () => {
return null;
};
const navigation = React.createRef<NavigationContainerRef>();
const navigation = createNavigationContainerRef<ParamListBase>();
const root = (
<BaseNavigationContainer ref={navigation}>
@@ -1148,11 +1398,7 @@ it('preserves order of screens in state with non-numeric names', () => {
render(root);
expect(navigation.current?.getRootState().routeNames).toEqual([
'foo',
'bar',
'baz',
]);
expect(navigation.getRootState().routeNames).toEqual(['foo', 'bar', 'baz']);
});
it('preserves order of screens in state with numeric names', () => {
@@ -1161,7 +1407,7 @@ it('preserves order of screens in state with numeric names', () => {
return null;
};
const navigation = React.createRef<NavigationContainerRef>();
const navigation = createNavigationContainerRef<ParamListBase>();
const root = (
<BaseNavigationContainer ref={navigation}>
@@ -1175,11 +1421,7 @@ it('preserves order of screens in state with numeric names', () => {
render(root);
expect(navigation.current?.getRootState().routeNames).toEqual([
'4',
'7',
'1',
]);
expect(navigation.getRootState().routeNames).toEqual(['4', '7', '1']);
});
it("throws if navigator doesn't have any screens", () => {
@@ -1256,7 +1498,52 @@ it('throws when Screen is not the direct children', () => {
);
expect(() => render(element).update(element)).toThrowError(
"A navigator can only contain 'Screen' components as its direct children (found 'Bar')"
"A navigator can only contain 'Screen', 'Group' or 'React.Fragment' as its direct children (found 'Bar')"
);
});
it('throws when undefined component is a direct children', () => {
const TestNavigator = (props: any) => {
useNavigationBuilder(MockRouter, props);
return null;
};
const Undefined = undefined;
const spy = jest.spyOn(console, 'error').mockImplementation();
const element = (
<BaseNavigationContainer>
<TestNavigator>
{/* @ts-ignore */}
<Undefined name="foo" component={jest.fn()} />
</TestNavigator>
</BaseNavigationContainer>
);
spy.mockRestore();
expect(() => render(element).update(element)).toThrowError(
"A navigator can only contain 'Screen', 'Group' or 'React.Fragment' as its direct children (found 'undefined' for the screen 'foo')"
);
});
it('throws when a tag is a direct children', () => {
const TestNavigator = (props: any) => {
useNavigationBuilder(MockRouter, props);
return null;
};
const element = (
<BaseNavigationContainer>
<TestNavigator>
{/* @ts-ignore */}
<screen name="foo" component={jest.fn()} />
</TestNavigator>
</BaseNavigationContainer>
);
expect(() => render(element).update(element)).toThrowError(
"A navigator can only contain 'Screen', 'Group' or 'React.Fragment' as its direct children (found 'screen' for the screen 'foo')"
);
});
@@ -1276,7 +1563,7 @@ it('throws when a React Element is not the direct children', () => {
);
expect(() => render(element).update(element)).toThrowError(
"A navigator can only contain 'Screen' components as its direct children (found 'Hello world')"
"A navigator can only contain 'Screen', 'Group' or 'React.Fragment' as its direct children (found 'Hello world')"
);
});
@@ -1554,7 +1841,7 @@ it('returns currently focused route with getCurrentRoute', () => {
const TestScreen = () => null;
const navigation = React.createRef<NavigationContainerRef>();
const navigation = createNavigationContainerRef<ParamListBase>();
const container = (
<BaseNavigationContainer ref={navigation}>
@@ -1577,7 +1864,7 @@ it('returns currently focused route with getCurrentRoute', () => {
render(container).update(container);
expect(navigation.current?.getCurrentRoute()).toEqual({
expect(navigation.getCurrentRoute()).toEqual({
key: 'bar-a',
name: 'bar-a',
});
@@ -1592,26 +1879,23 @@ it("returns focused screen's options with getCurrentOptions when focused screen
const TestScreen = () => null;
const navigation = React.createRef<NavigationContainerRef>();
const navigation = createNavigationContainerRef<ParamListBase>();
const container = (
<BaseNavigationContainer ref={navigation}>
<TestNavigator>
<Screen name="bar" options={{ a: 'b' }}>
{() => (
<TestNavigator
initialRouteName="bar-a"
screenOptions={() => ({ sample2: 'data' })}
>
<TestNavigator initialRouteName="bar-a">
<Screen
name="bar-a"
component={TestScreen}
options={{ sample: 'data' }}
options={{ sample: '1' }}
/>
<Screen
name="bar-b"
component={TestScreen}
options={{ sample3: 'data' }}
options={{ sample2: '2' }}
/>
</TestNavigator>
)}
@@ -1623,16 +1907,123 @@ it("returns focused screen's options with getCurrentOptions when focused screen
render(container).update(container);
expect(navigation.current?.getCurrentOptions()).toEqual({
sample: 'data',
sample2: 'data',
expect(navigation.getCurrentOptions()).toEqual({
sample: '1',
});
act(() => navigation.current?.navigate('bar-b'));
act(() => navigation.navigate('bar-b'));
expect(navigation.current?.getCurrentOptions()).toEqual({
sample2: 'data',
sample3: 'data',
expect(navigation.getCurrentOptions()).toEqual({
sample2: '2',
});
});
it("returns focused screen's options with getCurrentOptions when focused screen is rendered when using screenOptions", () => {
const TestNavigator = (props: any): any => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return descriptors[state.routes[state.index].key].render();
};
const TestScreen = () => null;
const navigation = createNavigationContainerRef<ParamListBase>();
const container = (
<BaseNavigationContainer ref={navigation}>
<TestNavigator>
<Screen name="bar" options={{ a: 'b' }}>
{() => (
<TestNavigator
initialRouteName="bar-a"
screenOptions={() => ({ sample2: '2' })}
>
<Screen
name="bar-a"
component={TestScreen}
options={{ sample: '1' }}
/>
<Screen
name="bar-b"
component={TestScreen}
options={{ sample3: '3' }}
/>
</TestNavigator>
)}
</Screen>
<Screen name="xux" component={TestScreen} />
</TestNavigator>
</BaseNavigationContainer>
);
render(container).update(container);
expect(navigation.getCurrentOptions()).toEqual({
sample: '1',
sample2: '2',
});
act(() => navigation.navigate('bar-b'));
expect(navigation.getCurrentOptions()).toEqual({
sample2: '2',
sample3: '3',
});
});
it("returns focused screen's options with getCurrentOptions when focused screen is rendered when using Group", () => {
const TestNavigator = (props: any): any => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return descriptors[state.routes[state.index].key].render();
};
const TestScreen = () => null;
const navigation = createNavigationContainerRef<ParamListBase>();
const container = (
<BaseNavigationContainer ref={navigation}>
<TestNavigator>
<Screen name="bar" options={{ a: 'b' }}>
{() => (
<TestNavigator
initialRouteName="bar-a"
screenOptions={() => ({ sample2: '2' })}
>
<Screen
name="bar-a"
component={TestScreen}
options={{ sample: '1' }}
/>
<Group screenOptions={{ sample4: '4' }}>
<Screen
name="bar-b"
component={TestScreen}
options={{ sample3: '3' }}
/>
</Group>
</TestNavigator>
)}
</Screen>
<Screen name="xux" component={TestScreen} />
</TestNavigator>
</BaseNavigationContainer>
);
render(container).update(container);
expect(navigation.getCurrentOptions()).toEqual({
sample: '1',
sample2: '2',
});
act(() => navigation.navigate('bar-b'));
expect(navigation.getCurrentOptions()).toEqual({
sample2: '2',
sample3: '3',
sample4: '4',
});
});
@@ -1645,26 +2036,23 @@ it("returns focused screen's options with getCurrentOptions when all screens are
const TestScreen = () => null;
const navigation = React.createRef<NavigationContainerRef>();
const navigation = createNavigationContainerRef<ParamListBase>();
const container = (
<BaseNavigationContainer ref={navigation}>
<TestNavigator>
<Screen name="bar" options={{ a: 'b' }}>
{() => (
<TestNavigator
initialRouteName="bar-a"
screenOptions={() => ({ sample2: 'data' })}
>
<TestNavigator initialRouteName="bar-a">
<Screen
name="bar-a"
component={TestScreen}
options={{ sample: 'data' }}
options={{ sample: '1' }}
/>
<Screen
name="bar-b"
component={TestScreen}
options={{ sample3: 'data' }}
options={{ sample2: '2' }}
/>
</TestNavigator>
)}
@@ -1676,16 +2064,123 @@ it("returns focused screen's options with getCurrentOptions when all screens are
render(container).update(container);
expect(navigation.current?.getCurrentOptions()).toEqual({
sample: 'data',
sample2: 'data',
expect(navigation.getCurrentOptions()).toEqual({
sample: '1',
});
act(() => navigation.current?.navigate('bar-b'));
act(() => navigation.navigate('bar-b'));
expect(navigation.current?.getCurrentOptions()).toEqual({
sample2: 'data',
sample3: 'data',
expect(navigation.getCurrentOptions()).toEqual({
sample2: '2',
});
});
it("returns focused screen's options with getCurrentOptions when all screens are rendered with screenOptions", () => {
const TestNavigator = (props: any): any => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return <>{state.routes.map((route) => descriptors[route.key].render())}</>;
};
const TestScreen = () => null;
const navigation = createNavigationContainerRef<ParamListBase>();
const container = (
<BaseNavigationContainer ref={navigation}>
<TestNavigator>
<Screen name="bar" options={{ a: 'b' }}>
{() => (
<TestNavigator
initialRouteName="bar-a"
screenOptions={() => ({ sample2: '2' })}
>
<Screen
name="bar-a"
component={TestScreen}
options={{ sample: '1' }}
/>
<Screen
name="bar-b"
component={TestScreen}
options={{ sample3: '3' }}
/>
</TestNavigator>
)}
</Screen>
<Screen name="xux" component={TestScreen} />
</TestNavigator>
</BaseNavigationContainer>
);
render(container).update(container);
expect(navigation.getCurrentOptions()).toEqual({
sample: '1',
sample2: '2',
});
act(() => navigation.navigate('bar-b'));
expect(navigation.getCurrentOptions()).toEqual({
sample2: '2',
sample3: '3',
});
});
it("returns focused screen's options with getCurrentOptions when all screens are rendered with Group", () => {
const TestNavigator = (props: any): any => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return <>{state.routes.map((route) => descriptors[route.key].render())}</>;
};
const TestScreen = () => null;
const navigation = createNavigationContainerRef<ParamListBase>();
const container = (
<BaseNavigationContainer ref={navigation}>
<TestNavigator>
<Screen name="bar" options={{ a: 'b' }}>
{() => (
<TestNavigator
initialRouteName="bar-a"
screenOptions={() => ({ sample2: '2' })}
>
<Screen
name="bar-a"
component={TestScreen}
options={{ sample: '1' }}
/>
<Group screenOptions={{ sample4: '4' }}>
<Screen
name="bar-b"
component={TestScreen}
options={{ sample3: '3' }}
/>
</Group>
</TestNavigator>
)}
</Screen>
<Screen name="xux" component={TestScreen} />
</TestNavigator>
</BaseNavigationContainer>
);
render(container).update(container);
expect(navigation.getCurrentOptions()).toEqual({
sample: '1',
sample2: '2',
});
act(() => navigation.navigate('bar-b'));
expect(navigation.getCurrentOptions()).toEqual({
sample2: '2',
sample3: '3',
sample4: '4',
});
});
@@ -1698,7 +2193,7 @@ it('does not throw if while getting current options with no options defined', ()
const TestScreen = () => null;
const navigation = React.createRef<NavigationContainerRef>();
const navigation = createNavigationContainerRef<ParamListBase>();
const container = (
<BaseNavigationContainer ref={navigation}>
@@ -1721,11 +2216,11 @@ it('does not throw if while getting current options with no options defined', ()
render(container).update(container);
expect(navigation.current?.getCurrentOptions()).toEqual({});
expect(navigation.getCurrentOptions()).toEqual({});
});
it('does not throw if while getting current options with empty container', () => {
const navigation = React.createRef<NavigationContainerRef>();
const navigation = createNavigationContainerRef<ParamListBase>();
const container = (
<BaseNavigationContainer ref={navigation} children={null} />
@@ -1733,5 +2228,5 @@ it('does not throw if while getting current options with empty container', () =>
render(container).update(container);
expect(navigation.current?.getCurrentOptions()).toEqual(undefined);
expect(navigation.getCurrentOptions()).toEqual(undefined);
});

View File

@@ -22,6 +22,7 @@ it('sets options with options prop as an object', () => {
const { state, descriptors } = useNavigationBuilder<
NavigationState,
any,
{},
{ title?: string },
any
>(MockRouter, props);
@@ -67,6 +68,7 @@ it('sets options with options prop as a fuction', () => {
const { state, descriptors } = useNavigationBuilder<
NavigationState,
any,
{},
{ title?: string },
any
>(MockRouter, props);
@@ -113,6 +115,7 @@ it('sets options with screenOptions prop as an object', () => {
const { state, descriptors } = useNavigationBuilder<
NavigationState,
any,
{},
{ title?: string },
any
>(MockRouter, props);
@@ -173,6 +176,7 @@ it('sets options with screenOptions prop as a fuction', () => {
const { state, descriptors } = useNavigationBuilder<
NavigationState,
any,
{},
{ title?: string },
any
>(MockRouter, props);
@@ -245,6 +249,7 @@ it('sets initial options with setOptions', () => {
const { state, descriptors } = useNavigationBuilder<
NavigationState,
any,
{},
{
title?: string;
color?: string;
@@ -302,6 +307,7 @@ it('updates options with setOptions', () => {
NavigationState,
any,
any,
any,
any
>(MockRouter, props);
const { render, options } = descriptors[state.routes[state.index].key];
@@ -378,6 +384,7 @@ it("returns correct value for canGoBack when it's not overridden", () => {
const { state, descriptors } = useNavigationBuilder<
NavigationState,
any,
{},
{ title?: string },
any
>(MockRouter, props);
@@ -441,6 +448,7 @@ it(`returns false for canGoBack when current router doesn't handle GO_BACK`, ()
NavigationState,
any,
any,
any,
any
>(TestRouter, props);
@@ -491,6 +499,7 @@ it('returns true for canGoBack when current router handles GO_BACK', () => {
const { state, descriptors } = useNavigationBuilder<
NavigationState,
any,
{},
{ title?: string },
any
>(ParentRouter, props);
@@ -501,6 +510,7 @@ it('returns true for canGoBack when current router handles GO_BACK', () => {
const { state, descriptors } = useNavigationBuilder<
NavigationState,
any,
{},
{ title?: string },
any
>(MockRouter, props);
@@ -558,6 +568,7 @@ it('returns true for canGoBack when parent router handles GO_BACK', () => {
const { state, descriptors } = useNavigationBuilder<
NavigationState,
any,
{},
{ title?: string },
any
>(OverrodeRouter, props);
@@ -568,6 +579,7 @@ it('returns true for canGoBack when parent router handles GO_BACK', () => {
const { state, descriptors } = useNavigationBuilder<
NavigationState,
any,
{},
{ title?: string },
any
>(MockRouter, props);
@@ -588,14 +600,14 @@ it('returns true for canGoBack when parent router handles GO_BACK', () => {
const root = (
<BaseNavigationContainer>
<TestNavigator>
<Screen name="baz">
<Screen name="foo">
{() => (
<TestNavigator>
<Screen name="qux" component={TestScreen} />
<Screen name="bar" component={TestScreen} />
</TestNavigator>
)}
</Screen>
<Screen name="qux">
<Screen name="baz">
{() => (
<OverrodeNavigator>
<Screen name="qux">{() => null}</Screen>

View File

@@ -239,3 +239,135 @@ it('runs cleanup when component is unmounted', () => {
expect(focusEffect).toBeCalledTimes(1);
expect(focusEffectCleanup).toBeCalledTimes(1);
});
it('prints error when a dependency array is passed', () => {
const TestNavigator = (props: any): any => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return descriptors[state.routes[state.index].key].render();
};
const Test = () => {
// @ts-ignore
useFocusEffect(() => {}, []);
return null;
};
const App = () => (
<BaseNavigationContainer>
<TestNavigator>
<Screen name="test" component={Test} />
</TestNavigator>
</BaseNavigationContainer>
);
const spy = jest.spyOn(console, 'error').mockImplementation();
render(<App />);
expect(spy.mock.calls[0][0]).toMatch(
"You passed a second argument to 'useFocusEffect', but it only accepts one argument."
);
spy.mockRestore();
});
it('prints error when the effect returns a value', () => {
const TestNavigator = (props: any): any => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return descriptors[state.routes[state.index].key].render();
};
const Test = () => {
// @ts-ignore
useFocusEffect(() => 42);
return null;
};
const App = () => (
<BaseNavigationContainer>
<TestNavigator>
<Screen name="test" component={Test} />
</TestNavigator>
</BaseNavigationContainer>
);
const spy = jest.spyOn(console, 'error').mockImplementation();
render(<App />);
expect(spy.mock.calls[0][0]).toMatch(
"An effect function must not return anything besides a function, which is used for clean-up. You returned '42'."
);
spy.mockRestore();
});
it('prints error when the effect returns null', () => {
const TestNavigator = (props: any): any => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return descriptors[state.routes[state.index].key].render();
};
const Test = () => {
// @ts-ignore
useFocusEffect(() => null);
return null;
};
const App = () => (
<BaseNavigationContainer>
<TestNavigator>
<Screen name="test" component={Test} />
</TestNavigator>
</BaseNavigationContainer>
);
const spy = jest.spyOn(console, 'error').mockImplementation();
render(<App />);
expect(spy.mock.calls[0][0]).toMatch(
"An effect function must not return anything besides a function, which is used for clean-up. You returned 'null'. If your effect does not require clean-up, return 'undefined' (or nothing)."
);
spy.mockRestore();
});
it('prints error when the effect is an async function', () => {
const TestNavigator = (props: any): any => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return descriptors[state.routes[state.index].key].render();
};
const Test = () => {
// @ts-ignore
useFocusEffect(async () => {});
return null;
};
const App = () => (
<BaseNavigationContainer>
<TestNavigator>
<Screen name="test" component={Test} />
</TestNavigator>
</BaseNavigationContainer>
);
const spy = jest.spyOn(console, 'error').mockImplementation();
render(<App />);
expect(spy.mock.calls[0][0]).toMatch(
"An effect function must not return anything besides a function, which is used for clean-up.\n\nIt looks like you wrote 'useFocusEffect(async () => ...)' or returned a Promise."
);
spy.mockRestore();
});

View File

@@ -44,7 +44,7 @@ it("gets navigation's parent from context", () => {
const Test = () => {
const navigation = useNavigation();
expect(navigation.dangerouslyGetParent()).toBeDefined();
expect(navigation.getParent()).toBeDefined();
return null;
};
@@ -75,7 +75,7 @@ it("gets navigation's parent's parent from context", () => {
const Test = () => {
const navigation = useNavigation();
const parent = navigation.dangerouslyGetParent();
const parent = navigation.getParent();
expect(parent).toBeDefined();
if (parent !== undefined) {
@@ -91,7 +91,7 @@ it("gets navigation's parent's parent from context", () => {
<Screen name="foo">
{() => (
<TestNavigator>
<Screen name="foo">
<Screen name="bar">
{() => (
<TestNavigator>
<Screen name="quo" component={Test} />

View File

@@ -5,15 +5,16 @@ import {
DefaultRouterOptions,
NavigationState,
StackRouter,
ParamListBase,
} from '@react-navigation/routers';
import useNavigationBuilder from '../useNavigationBuilder';
import BaseNavigationContainer from '../BaseNavigationContainer';
import Screen from '../Screen';
import createNavigationContainerRef from '../createNavigationContainerRef';
import MockRouter, {
MockActions,
MockRouterKey,
} from './__fixtures__/MockRouter';
import type { NavigationContainerRef } from '../types';
jest.mock('nanoid/non-secure', () => {
const m = { nanoid: () => String(++m.__key), __key: 0 };
@@ -571,7 +572,7 @@ it("prevents removing a screen with 'beforeRemove' event", () => {
const onStateChange = jest.fn();
const ref = React.createRef<NavigationContainerRef>();
const ref = createNavigationContainerRef<ParamListBase>();
const element = (
<BaseNavigationContainer ref={ref} onStateChange={onStateChange}>
@@ -706,7 +707,7 @@ it("prevents removing a child screen with 'beforeRemove' event", () => {
const onStateChange = jest.fn();
const ref = React.createRef<NavigationContainerRef>();
const ref = createNavigationContainerRef<ParamListBase>();
const element = (
<BaseNavigationContainer ref={ref} onStateChange={onStateChange}>
@@ -867,7 +868,7 @@ it("prevents removing a grand child screen with 'beforeRemove' event", () => {
const onStateChange = jest.fn();
const ref = React.createRef<NavigationContainerRef>();
const ref = createNavigationContainerRef<ParamListBase>();
const element = (
<BaseNavigationContainer ref={ref} onStateChange={onStateChange}>
@@ -1065,7 +1066,7 @@ it("prevents removing by multiple screens with 'beforeRemove' event", () => {
const onStateChange = jest.fn();
const ref = React.createRef<NavigationContainerRef>();
const ref = createNavigationContainerRef<ParamListBase>();
const element = (
<BaseNavigationContainer ref={ref} onStateChange={onStateChange}>
@@ -1178,3 +1179,149 @@ it("prevents removing by multiple screens with 'beforeRemove' event", () => {
type: 'stack',
});
});
it("prevents removing a child screen with 'beforeRemove' event with 'resetRoot'", () => {
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder(StackRouter, props);
return (
<React.Fragment>
{state.routes.map((route) => descriptors[route.key].render())}
</React.Fragment>
);
};
const onBeforeRemove = jest.fn();
let shouldPrevent = true;
let shouldContinue = false;
const TestScreen = (props: any) => {
React.useEffect(
() =>
props.navigation.addListener('beforeRemove', (e: any) => {
onBeforeRemove();
if (shouldPrevent) {
e.preventDefault();
if (shouldContinue) {
props.navigation.dispatch(e.data.action);
}
}
}),
[props.navigation]
);
return null;
};
const onStateChange = jest.fn();
const ref = createNavigationContainerRef<ParamListBase>();
const element = (
<BaseNavigationContainer ref={ref} onStateChange={onStateChange}>
<TestNavigator>
<Screen name="foo">{() => null}</Screen>
<Screen name="bar">{() => null}</Screen>
<Screen name="baz">
{() => (
<TestNavigator>
<Screen name="qux" component={TestScreen} />
<Screen name="lex">{() => null}</Screen>
</TestNavigator>
)}
</Screen>
</TestNavigator>
</BaseNavigationContainer>
);
render(element);
act(() => ref.current?.navigate('baz'));
expect(onStateChange).toBeCalledTimes(1);
expect(onStateChange).toBeCalledWith({
index: 1,
key: 'stack-2',
routeNames: ['foo', 'bar', 'baz'],
routes: [
{ key: 'foo-3', name: 'foo' },
{
key: 'baz-4',
name: 'baz',
state: {
index: 0,
key: 'stack-6',
routeNames: ['qux', 'lex'],
routes: [{ key: 'qux-7', name: 'qux' }],
stale: false,
type: 'stack',
},
},
],
stale: false,
type: 'stack',
});
act(() =>
ref.current?.resetRoot({
index: 0,
key: 'stack-2',
routeNames: ['foo', 'bar', 'baz'],
routes: [{ key: 'foo-3', name: 'foo' }],
stale: false,
type: 'stack',
})
);
expect(onStateChange).toBeCalledTimes(1);
expect(onBeforeRemove).toBeCalledTimes(1);
expect(ref.current?.getRootState()).toEqual({
index: 1,
key: 'stack-2',
routeNames: ['foo', 'bar', 'baz'],
routes: [
{ key: 'foo-3', name: 'foo' },
{
key: 'baz-4',
name: 'baz',
state: {
index: 0,
key: 'stack-6',
routeNames: ['qux', 'lex'],
routes: [{ key: 'qux-7', name: 'qux' }],
stale: false,
type: 'stack',
},
},
],
stale: false,
type: 'stack',
});
shouldPrevent = false;
act(() =>
ref.current?.resetRoot({
index: 0,
key: 'stack-2',
routeNames: ['foo', 'bar', 'baz'],
routes: [{ key: 'foo-3', name: 'foo' }],
stale: false,
type: 'stack',
})
);
expect(onStateChange).toBeCalledTimes(2);
expect(onStateChange).toBeCalledWith({
index: 0,
key: 'stack-2',
routeNames: ['foo', 'bar', 'baz'],
routes: [{ key: 'foo-3', name: 'foo' }],
stale: false,
type: 'stack',
});
});

View File

@@ -0,0 +1,33 @@
import type { NavigationState, PartialState } from '@react-navigation/routers';
export default function checkDuplicateRouteNames(state: NavigationState) {
const duplicates: string[][] = [];
const getRouteNames = (
location: string,
state: NavigationState | PartialState<NavigationState>
) => {
state.routes.forEach((route: typeof state.routes[0]) => {
const currentLocation = location
? `${location} > ${route.name}`
: route.name;
route.state?.routeNames?.forEach((routeName) => {
if (routeName === route.name) {
duplicates.push([
currentLocation,
`${currentLocation} > ${route.name}`,
]);
}
});
if (route.state) {
getRouteNames(currentLocation, route.state);
}
});
};
getRouteNames('', state);
return duplicates;
}

View File

@@ -1,36 +0,0 @@
import type { PathConfigMap } from './types';
type Options = {
initialRouteName?: string;
screens: PathConfigMap;
};
export default function checkLegacyPathConfig(
config?: Options
): [boolean, Options | undefined] {
let legacy = false;
if (config) {
// Assume legacy configuration if config has any other keys except `screens` and `initialRouteName`
legacy = Object.keys(config).some(
(key) => key !== 'screens' && key !== 'initialRouteName'
);
if (
legacy &&
(config.hasOwnProperty('screens') ||
config.hasOwnProperty('initialRouteName'))
) {
throw new Error(
'Found invalid keys in the configuration object. See https://reactnavigation.org/docs/configuring-links/ for more details on the shape of the configuration object.'
);
}
}
if (legacy) {
// @ts-expect-error: we have incorrect type for config since we don't type legacy config
return [legacy, { screens: config }];
}
return [legacy, config];
}

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