Compare commits

..

133 Commits

Author SHA1 Message Date
Satyajit Sahoo
e87925c086 chore: publish
- @react-navigation/bottom-tabs@5.0.0-alpha.23
 - @react-navigation/core@5.0.0-alpha.26
 - @react-navigation/drawer@5.0.0-alpha.25
 - @react-navigation/material-bottom-tabs@5.0.0-alpha.22
 - @react-navigation/material-top-tabs@5.0.0-alpha.20
 - @react-navigation/native-stack@5.0.0-alpha.15
 - @react-navigation/stack@5.0.0-alpha.40
2019-12-07 05:23:12 +01:00
Satyajit Sahoo
7b13a81ac8 fix: don't handle replace if screen to replace with isn't present
fixes #193
2019-12-07 05:22:19 +01:00
Satyajit Sahoo
d618ab382e feat: export underlying views used to build navigators (#191)
Exporting the underlying views makes it easy to build custom navigators on top of our views. Libraries such as react-native-router-flux rely on such exports to build custom routing solutions while being able to take advantage of our work.

This can also be the solution to adding custom behaviour without us needing to add separate config to override the router.
2019-12-04 00:22:53 +01:00
Satyajit Sahoo
c7a5cfd5b2 chore: publish
- @react-navigation/stack@5.0.0-alpha.39
2019-12-03 20:40:35 +01:00
Satyajit Sahoo
87d445b4e4 fix: disable pointerEvents on header when not focused 2019-12-03 20:33:41 +01:00
Satyajit Sahoo
eaf88478cc fix: correctly update layout on onLayout events 2019-12-03 20:32:38 +01:00
Satyajit Sahoo
f271e299ac chore: publish
- @react-navigation/core@5.0.0-alpha.25
 - @react-navigation/stack@5.0.0-alpha.38
2019-11-29 18:10:20 +01:00
Michał Osadnik
5a0dfa1a15 fix: wrap reset and resetRoot inside transaction (#189)
fixes #185
2019-11-29 17:40:28 +01:00
Satyajit Sahoo
2750cad272 fix: respect custom safearea insets when calculating header height
fixes #190
2019-11-29 17:25:34 +01:00
Satyajit Sahoo
2e715ef48e chore: publish
- @react-navigation/drawer@5.0.0-alpha.24
2019-11-27 13:07:20 +01:00
Satyajit Sahoo
7080517c91 fix: enable gestures by default in drawer. closes #188 2019-11-27 13:04:31 +01:00
osdnk
d733066476 chore: publish
- @react-navigation/core@5.0.0-alpha.24
 - @react-navigation/example@5.0.0-alpha.23
 - @react-navigation/material-top-tabs@5.0.0-alpha.19
 - @react-navigation/native-stack@5.0.0-alpha.14
2019-11-20 16:01:25 +01:00
osdnk
d0099f0968 chore: bump rntv 2019-11-20 16:00:30 +01:00
Thibault Malbranche
c3e9e4578e fix: allow passing partial params to setParams (#177)
Since params get merged, no need to send them all I think :)
2019-11-19 22:58:44 +01:00
Satyajit Sahoo
cb426d06de docs: add large title example 2019-11-18 01:16:33 +01:00
Satyajit Sahoo
353b3fd7de chore: publish
- @react-navigation/bottom-tabs@5.0.0-alpha.22
 - @react-navigation/compat@5.0.0-alpha.15
 - @react-navigation/core@5.0.0-alpha.23
 - @react-navigation/drawer@5.0.0-alpha.23
 - @react-navigation/example@5.0.0-alpha.22
 - @react-navigation/material-bottom-tabs@5.0.0-alpha.21
 - @react-navigation/material-top-tabs@5.0.0-alpha.18
 - @react-navigation/native-stack@5.0.0-alpha.13
 - @react-navigation/native@5.0.0-alpha.16
 - @react-navigation/routers@5.0.0-alpha.15
 - @react-navigation/stack@5.0.0-alpha.37
2019-11-17 02:46:59 +01:00
Satyajit Sahoo
d619292bf2 chore: upgrade deps 2019-11-17 02:42:28 +01:00
Danijel Dedic
cd7c9c4398 fix: pass labelStyle prop in DrawerItem label (#170) 2019-11-17 01:25:54 +01:00
Janic Duplessis
442e1121fc docs: headerHideShadow works on iOS too 2019-11-17 01:22:48 +01:00
Satyajit Sahoo
11efb06642 fix: merge initial params on push 2019-11-17 01:23:49 +01:00
Satyajit Sahoo
c17ad18b20 fix: workaround SafereaProvider causing jumping
see #174
2019-11-16 19:41:42 +01:00
Satyajit Sahoo
31b8819e17 refactor: use functions to specify custom react node for icons and label 2019-11-16 19:11:42 +01:00
Satyajit Sahoo
2baa235ce9 refactor: drop drawerLockMode in favor of gestureEnabled in drawer 2019-11-16 19:06:23 +01:00
Satyajit Sahoo
71b2bcd14f chore: publish
- @react-navigation/bottom-tabs@5.0.0-alpha.21
 - @react-navigation/compat@5.0.0-alpha.14
 - @react-navigation/core@5.0.0-alpha.22
 - @react-navigation/drawer@5.0.0-alpha.22
 - @react-navigation/example@5.0.0-alpha.21
 - @react-navigation/material-bottom-tabs@5.0.0-alpha.20
 - @react-navigation/material-top-tabs@5.0.0-alpha.17
 - @react-navigation/native-stack@5.0.0-alpha.12
 - @react-navigation/routers@5.0.0-alpha.14
 - @react-navigation/stack@5.0.0-alpha.36
2019-11-11 00:34:18 +01:00
Satyajit Sahoo
56c2779184 chore: add icons for the iOS app 2019-11-10 21:28:40 +01:00
Satyajit Sahoo
c77532174a chore: upgrade to screens alpha 7 2019-11-10 21:17:44 +01:00
Satyajit Sahoo
d4072e7d88 fix: throw when containers are nested within another 2019-11-10 20:40:45 +01:00
Satyajit Sahoo
941249dba9 refactor: rename createNavigator since it doesn't create a navigator 2019-11-10 20:12:32 +01:00
Satyajit Sahoo
d1ca7f9a09 fix: make bottom tab bar consistent across platforms 2019-11-10 20:10:51 +01:00
Satyajit Sahoo
f494f992fa chore: publish
- @react-navigation/bottom-tabs@5.0.0-alpha.20
 - @react-navigation/compat@5.0.0-alpha.13
 - @react-navigation/core@5.0.0-alpha.21
 - @react-navigation/drawer@5.0.0-alpha.21
 - @react-navigation/example@5.0.0-alpha.20
 - @react-navigation/material-bottom-tabs@5.0.0-alpha.19
 - @react-navigation/material-top-tabs@5.0.0-alpha.16
 - @react-navigation/native-stack@5.0.0-alpha.11
 - @react-navigation/native@5.0.0-alpha.15
 - @react-navigation/routers@5.0.0-alpha.13
 - @react-navigation/stack@5.0.0-alpha.35
2019-11-08 15:07:35 +01:00
Janic Duplessis
66551f29d4 fix: don't call getNode if ref is already scrollable (#162)
66e72bb4e0 deprecates `getNode` since Animated components now use `React.forwardRef`. To avoid triggering the warning I added a check to see if the node is already a scrollable node before trying to call the various method to get the scrollable node. This avoids calling getNode in newer RN versions.
2019-11-08 15:03:48 +01:00
Satyajit Sahoo
270fbdcfaf fix: don't crash if initialState is null 2019-11-07 16:00:24 +01:00
Satyajit Sahoo
e871fdb074 fix: fix types for resetRoot to accept undefined 2019-11-07 15:48:40 +01:00
Satyajit Sahoo
b5d9ad900d fix: handle invalid initialRouteName gracefully 2019-11-07 15:43:51 +01:00
Satyajit Sahoo
6aebeec90c chore: upgrade to typescript 3.7 2019-11-06 21:01:18 +01:00
satyajit.happy
75ed888b33 chore: publish
- @react-navigation/bottom-tabs@5.0.0-alpha.19
 - @react-navigation/native-stack@5.0.0-alpha.10
2019-11-04 17:21:32 +01:00
satyajit.happy
ee82ab1d1b refactor: remove TouchableWithoutFeedbackWrapper 2019-11-04 17:20:36 +01:00
satyajit.happy
301c35ec32 fix: popToTop on tab press in native stack 2019-11-04 17:09:42 +01:00
Oliver Winter
22cb675608 fix: fix default BottomTabBar button (#161) 2019-11-04 17:05:05 +01:00
satyajit.happy
c41c824aae chore: publish
- @react-navigation/bottom-tabs@5.0.0-alpha.18
 - @react-navigation/compat@5.0.0-alpha.12
 - @react-navigation/drawer@5.0.0-alpha.20
 - @react-navigation/example@5.0.0-alpha.19
 - @react-navigation/material-bottom-tabs@5.0.0-alpha.18
 - @react-navigation/material-top-tabs@5.0.0-alpha.15
 - @react-navigation/native-stack@5.0.0-alpha.9
 - @react-navigation/routers@5.0.0-alpha.12
 - @react-navigation/stack@5.0.0-alpha.34
2019-11-04 07:52:29 +01:00
Freddy Harris
a93a81e5d3 feat: support transform style for header (#158) 2019-11-04 07:50:02 +01:00
satyajit.happy
57b411cea8 refactor: replace XComponent props in favor of render callbacks
Making these props components makes it impossible pass additional props to them from the parent component. Render callbacks are more dynamic and flexible for this case
2019-11-04 07:47:41 +01:00
satyajit.happy
3a4c38bb72 fix: close drawer on back button press 2019-11-02 08:52:12 +01:00
satyajit.happy
e6a06ac56e chore: publish
- @react-navigation/core@5.0.0-alpha.20
 - @react-navigation/native-stack@5.0.0-alpha.8
 - @react-navigation/stack@5.0.0-alpha.33
2019-11-02 05:12:02 +01:00
satyajit.happy
2ef5ad4cc2 fix: add horizontal margin to centered title 2019-11-02 05:08:10 +01:00
freddy
74ee216ed4 fix: remove unnecessary paddingHorizontal on stack header 2019-11-02 04:54:07 +01:00
Tien Pham
77f29d374f feat: add headerBackTitleVisible to navigation options in native stack 2019-11-02 04:53:23 +01:00
satyajit.happy
5a34764404 fix: pass rehydrated state in onStateChange and devtools 2019-11-02 04:43:47 +01:00
satyajit.happy
738f226535 chore: publish
- @react-navigation/drawer@5.0.0-alpha.19
 - @react-navigation/example@5.0.0-alpha.18
 - @react-navigation/native-stack@5.0.0-alpha.7
 - @react-navigation/stack@5.0.0-alpha.32
2019-11-02 02:58:08 +01:00
satyajit.happy
5cd69401ec fix: remove top margin from screen in native stack on Android
In newer versions of react-native-screens, the content is no longer below the header, so the top margin in not needed anymore.
2019-11-02 02:56:46 +01:00
satyajit.happy
c55d4511ff chore: update react-native-screens 2019-10-31 13:49:22 +01:00
osdnk
c07d61dc41 chore: bump react-native-screens pods 2019-10-31 12:53:46 +01:00
satyajit.happy
67fd69adf6 fix: minor tweaks for web and fix example 2019-10-31 11:03:34 +01:00
satyajit.happy
3de9edbe72 chore: publish
- @react-navigation/bottom-tabs@5.0.0-alpha.17
 - @react-navigation/compat@5.0.0-alpha.11
 - @react-navigation/core@5.0.0-alpha.19
 - @react-navigation/drawer@5.0.0-alpha.18
 - @react-navigation/material-bottom-tabs@5.0.0-alpha.17
 - @react-navigation/material-top-tabs@5.0.0-alpha.14
 - @react-navigation/native-stack@5.0.0-alpha.6
 - @react-navigation/native@5.0.0-alpha.14
 - @react-navigation/routers@5.0.0-alpha.11
 - @react-navigation/stack@5.0.0-alpha.31
2019-10-30 23:51:33 +01:00
satyajit.happy
12d597fcc0 feat: add an 'unmountInactiveScreens' option 2019-10-30 23:50:33 +01:00
satyajit.happy
50dea65377 fix: support scroll to top in navigators nested in tab 2019-10-30 21:34:00 +01:00
Satyajit Sahoo
3d9db6ff25 refactor: add a type property to router and state (#146)
The `type` property denotes the type of the router. It can be used to verify compatibility of navigation state and the router when rehydrating state, making rehydration more resilient.

It can also help our utilities to detect the type of the navigator to properly implement some functionality. For example, the `useScrollToTop` hook can now know if it's not inside a tab navigator and needs to find the tab navigator in a parent.
2019-10-30 12:03:53 +01:00
Satyajit Sahoo
fb749ac064 fix: hide screen from screen reader when drawer is open (#147) 2019-10-30 11:59:55 +01:00
Satyajit Sahoo
58f7115298 fix: hide inactive pages from screen reader in tabs (#148) 2019-10-30 11:59:31 +01:00
Satyajit Sahoo
3a77107968 fix: drop isFirstRouteInParent method (#145)
The `isFirstRouteInParent` method was added to determine whether the back button should be shown in the header for stack navigator or not.
This was mainly due to the API of the old version of stack whose public API of header didn't have all required info to determine whether it should be shown.
It was probably a mistake to add it, because this method doesn't look at history and so pretty much useless for other navigators which aren't stack.

In the new stack, the header receives a `previous` prop and the `headerLeft` option receives a `canGoBack` prop which can be used for this purpose.
It's also possible to check if a route is the first by doing `navigation.dangerouslyGetState().routes[0].key === route.key`.

So it's time to drop this method.
2019-10-30 08:51:59 +01:00
satyajit.happy
aa40130266 chore: publish
- @react-navigation/bottom-tabs@5.0.0-alpha.16
 - @react-navigation/compat@5.0.0-alpha.10
 - @react-navigation/core@5.0.0-alpha.18
 - @react-navigation/drawer@5.0.0-alpha.17
 - @react-navigation/example@5.0.0-alpha.17
 - @react-navigation/material-bottom-tabs@5.0.0-alpha.16
 - @react-navigation/material-top-tabs@5.0.0-alpha.13
 - @react-navigation/native-stack@5.0.0-alpha.5
 - @react-navigation/routers@5.0.0-alpha.10
 - @react-navigation/stack@5.0.0-alpha.30
2019-10-29 21:20:18 +01:00
satyajit.happy
7635373366 fix: use index of first route when rehydrating tab state 2019-10-29 21:15:02 +01:00
satyajit.happy
941f4e2dec chore: update react-native-paper and screens 2019-10-29 02:42:37 +01:00
osdnk
876c3183e1 fix: make clearKeyboardTimeout private 2019-10-25 09:29:36 +02:00
satyajit.happy
1cc31bf900 chore: fix cocoa pods 2019-10-24 19:32:33 +02:00
satyajit.happy
8f16085808 fix: improve type annotation for screens 2019-10-24 18:56:26 +02:00
satyajit.happy
58fbfdf5c3 chore: remove log 2019-10-24 18:56:26 +02:00
osdnk
07bfc86327 fix: keyboard manager in stack for fast swipe 2019-10-24 18:50:16 +02:00
osdnk
e54d87c41c chore: publish
- @react-navigation/stack@5.0.0-alpha.29
2019-10-22 21:06:37 +02:00
osdnk
225e760a54 fix: conditions in gesture direction 2019-10-22 21:06:02 +02:00
satyajit.happy
f9cfbd01d8 chore: publish
- @react-navigation/bottom-tabs@5.0.0-alpha.15
 - @react-navigation/example@5.0.0-alpha.16
2019-10-22 15:45:16 +02:00
satyajit.happy
3bf46e1ad2 refactor: don't pass orientation to label 2019-10-22 15:30:49 +02:00
satyajit.happy
de2b6d8715 chore: publish
- @react-navigation/compat@5.0.0-alpha.9
 - @react-navigation/core@5.0.0-alpha.17
 - @react-navigation/drawer@5.0.0-alpha.16
 - @react-navigation/example@5.0.0-alpha.15
 - @react-navigation/material-bottom-tabs@5.0.0-alpha.15
 - @react-navigation/native-stack@5.0.0-alpha.4
 - @react-navigation/native@5.0.0-alpha.13
 - @react-navigation/stack@5.0.0-alpha.28
2019-10-22 10:58:35 +02:00
Satyajit Sahoo
f22abb726c fix: don't fire onOpen when screen is unmounting (#137)
I can't think of a scenario a screen would unmount when opening.
So it's probably a safe-bet to always call onClose.

Fixes #136
2019-10-22 09:26:59 +02:00
satyajit.happy
031c4d2378 fix: don't keep unfocused header backgrounds visible 2019-10-22 04:54:38 +02:00
satyajit.happy
2b5955efbe refactor: use Record type for objects 2019-10-22 00:53:11 +02:00
satyajit.happy
70f7e7a7c0 ci: update CI config 2019-10-21 15:34:02 +02:00
David
6cf1a041b2 feat(native-stack): add support for large title attributes (#135)
Co-Authored-By: Satyajit Sahoo <satyajit.happy@gmail.com>
2019-10-21 15:25:50 +02:00
osdnk
0d8cdc8a27 fix: navigation drawer sometimes not closing when pressed outside 2019-10-21 11:26:30 +02:00
satyajit.happy
2680b461a2 chore: publish
- @react-navigation/native-stack@5.0.0-alpha.3
2019-10-18 19:16:33 +02:00
satyajit.happy
fb726eede3 fix: remove top margin when header is hidden in native stack. fixes #131 2019-10-18 19:15:43 +02:00
satyajit.happy
3aaf6eb648 chore: publish
- @react-navigation/core@5.0.0-alpha.16
 - @react-navigation/native-stack@5.0.0-alpha.2
 - @react-navigation/stack@5.0.0-alpha.27
2019-10-18 16:44:00 +02:00
Janic Duplessis
477c08858d fix: fix header font size config in native stack (#128)
Oups copy paste mistake :o
2019-10-18 16:43:04 +02:00
satyajit.happy
300791ab49 feat: add an option to override safe area insets 2019-10-18 16:20:53 +02:00
satyajit.happy
3e92e22941 fix: rehydrate state before using it 2019-10-18 16:06:21 +02:00
Satyajit Sahoo
a543f1bfc3 feat: make it easier to navigate to a specific route in navigator (#114)
We now use the `params` of the route to determine `initialRouteName` and `initialParams` of a navigator.

So you can do something like this:

```js
navigation.push('Auth', { name: 'Login', params: { token 'xxx' } })
```

This will navigate to the `Login` screen inside the `Auth` navigator.

Closes #90
2019-10-18 15:16:19 +02:00
satyajit.happy
cd5f355bd0 chore: publish
- @react-navigation/drawer@5.0.0-alpha.15
 - @react-navigation/example@5.0.0-alpha.14
 - @react-navigation/material-bottom-tabs@5.0.0-alpha.14
 - @react-navigation/stack@5.0.0-alpha.26
2019-10-18 01:57:14 +02:00
satyajit.happy
cab616069f fix: fix passing content options in drawer 2019-10-18 01:56:04 +02:00
satyajit.happy
f90e00cc93 chore: update paper so types work 2019-10-17 22:52:48 +02:00
satyajit.happy
731cf7d5b1 fix: fix incorrect type 2019-10-16 22:59:45 +02:00
Sirui Li
c6d0c19b49 fix: don't fade incoming background when fading header (#127) 2019-10-16 22:58:31 +02:00
satyajit.happy
442b95d9e4 fix: use header height from style if specified 2019-10-16 15:02:39 +02:00
osdnk
01277575f2 chore: publish
- @react-navigation/bottom-tabs@5.0.0-alpha.14
 - @react-navigation/compat@5.0.0-alpha.8
 - @react-navigation/core@5.0.0-alpha.15
 - @react-navigation/drawer@5.0.0-alpha.14
 - @react-navigation/example@5.0.0-alpha.13
 - @react-navigation/material-bottom-tabs@5.0.0-alpha.13
 - @react-navigation/material-top-tabs@5.0.0-alpha.12
 - @react-navigation/native-stack@5.0.0-alpha.1
 - @react-navigation/native@5.0.0-alpha.12
 - @react-navigation/stack@5.0.0-alpha.25
2019-10-15 16:08:48 +02:00
Michał Osadnik
386d1c0888 fix: block GH interactions in Native Stack example (#126) 2019-10-15 14:28:39 +02:00
satyajit.happy
9d9fe31f02 fix: don't ignore descriptors change 2019-10-15 14:17:49 +02:00
satyajit.happy
0a5fb3e268 fix: fix content component not rendering in drawer 2019-10-11 18:20:56 +02:00
satyajit.happy
1d7a37cd4a chore: fix minor typo 2019-10-11 18:05:32 +02:00
satyajit.happy
2b57702a62 fix: add flex: 1 to drawer content 2019-10-11 17:49:15 +02:00
satyajit.happy
c7da1e4145 fix: increase hitSlop of back button on Android 2019-10-11 13:39:29 +02:00
satyajit.happy
6a0ca90873 feat: add a headerTitleAlign option to center or left align title 2019-10-11 13:38:53 +02:00
osdnk
e9a1cfcec3 chore: bump screens 2019-10-11 13:26:52 +02:00
satyajit.happy
b71f4e52a3 fix: update supported options for native stack 2019-10-11 13:12:39 +02:00
satyajit.happy
9ce8ec59fb refactor: refresh drawer according to latest material design guidelines 2019-10-11 00:54:22 +02:00
satyajit.happy
d685e78fa9 refactor: change tintColor argument to just color 2019-10-11 00:40:39 +02:00
satyajit.happy
7a901af5b5 fix: make it possible to run the example on web 2019-10-11 00:28:38 +02:00
Michał Osadnik
ba3f718ab3 feat: initial version of native stack (#102) 2019-10-10 23:39:04 +02:00
satyajit.happy
42beb660ca fix: improve keyboard handling with bottom tab bar 2019-10-10 14:50:42 +02:00
Satyajit Sahoo
e789846692 fix: make modal presentation mode fullscreen on landscape (#124) 2019-10-08 15:50:52 +02:00
osdnk
b32cda2446 fix: interpolation in iOS modal presentation 2019-10-08 13:01:15 +02:00
satyajit.happy
708dde0e47 feat: export TransitionSpecs 2019-10-07 17:43:37 +02:00
satyajit.happy
5303e8ffb5 chore: publish
- @react-navigation/bottom-tabs@5.0.0-alpha.13
 - @react-navigation/compat@5.0.0-alpha.7
 - @react-navigation/core@5.0.0-alpha.14
 - @react-navigation/drawer@5.0.0-alpha.13
 - @react-navigation/example@5.0.0-alpha.12
 - @react-navigation/material-bottom-tabs@5.0.0-alpha.12
 - @react-navigation/material-top-tabs@5.0.0-alpha.11
 - @react-navigation/native@5.0.0-alpha.11
 - @react-navigation/stack@5.0.0-alpha.24
2019-10-06 16:44:17 +02:00
satyajit.happy
ba6b6ae025 feat: drop header: null in favor of more explitit headerShown option 2019-10-06 15:56:30 +02:00
satyajit.happy
16079d1050 fix: actually expose gestureVelocityImpact in the public API 2019-10-06 04:17:49 +02:00
satyajit.happy
b4a76814c6 fix: use next screen's animation when not focused. fixes #87 2019-10-06 04:13:13 +02:00
Michał Osadnik
8294efc8f4 feat: add gestureVelocityImpact as a prop for stack (#123) 2019-10-06 00:05:42 +02:00
Michał Osadnik
a27ade8881 fix: handling vertical gesture in RTL (#122) 2019-10-06 00:00:43 +02:00
satyajit.happy
615b523d26 fix: don't recompute if routes didn't change 2019-10-05 22:50:09 +02:00
satyajit.happy
070c46ba64 chore: fix react and react-native versions 2019-10-04 14:36:49 +02:00
satyajit.happy
d8394cf919 chore: publish
- @react-navigation/example@5.0.0-alpha.11
 - @react-navigation/stack@5.0.0-alpha.23
2019-10-04 00:54:40 +02:00
satyajit.happy
a7c4a4d7cd fix: fix vertical gesture 2019-10-04 00:53:26 +02:00
satyajit.happy
282b465ae1 chore: add example for modal presentation 2019-10-04 00:12:56 +02:00
satyajit.happy
6f5f4b7d35 fix: fix passing insets to interpolator 2019-10-04 00:01:45 +02:00
satyajit.happy
d75915d1f3 chore: publish
- @react-navigation/bottom-tabs@5.0.0-alpha.12
 - @react-navigation/compat@5.0.0-alpha.6
 - @react-navigation/core@5.0.0-alpha.13
 - @react-navigation/drawer@5.0.0-alpha.12
 - @react-navigation/example@5.0.0-alpha.10
 - @react-navigation/material-bottom-tabs@5.0.0-alpha.11
 - @react-navigation/material-top-tabs@5.0.0-alpha.10
 - @react-navigation/native@5.0.0-alpha.10
 - @react-navigation/stack@5.0.0-alpha.22
2019-10-03 21:33:06 +02:00
satyajit.happy
832ed882bc refactor: use react-native-safe-area-context 2019-10-03 21:31:09 +02:00
satyajit.happy
8318c49331 chore: publish
- @react-navigation/bottom-tabs@5.0.0-alpha.11
 - @react-navigation/compat@5.0.0-alpha.5
 - @react-navigation/core@5.0.0-alpha.12
 - @react-navigation/drawer@5.0.0-alpha.11
 - @react-navigation/example@5.0.0-alpha.9
 - @react-navigation/material-bottom-tabs@5.0.0-alpha.10
 - @react-navigation/material-top-tabs@5.0.0-alpha.9
 - @react-navigation/native@5.0.0-alpha.9
 - @react-navigation/routers@5.0.0-alpha.9
 - @react-navigation/stack@5.0.0-alpha.21
2019-10-03 19:47:41 +02:00
satyajit.happy
ece6e3899e fix: add missing React import 2019-10-03 19:07:56 +02:00
satyajit.happy
da944ccef9 fix: fix header buttons not clickable on Android. fixes #108 2019-10-03 19:07:21 +02:00
satyajit.happy
bc3586ae3e fix: keep the routes we are closing or replacing 2019-10-03 18:44:18 +02:00
satyajit.happy
19be2b4bbc fix: don't throw when switching navigator. fixes #91 2019-10-03 17:51:13 +02:00
satyajit.happy
a8851b730d chore: upgrade deps 2019-10-03 17:35:24 +02:00
Michał Osadnik
7a5bcb446b feat: add a getRootState method (#119) 2019-10-01 23:17:28 +02:00
satyajit.happy
1345a8fec6 chore: upgrade eslint config 2019-09-28 11:58:21 +02:00
satyajit.happy
7393464515 fix: don't merge state with existing state during reset. fixes #111 2019-09-28 11:49:45 +02:00
181 changed files with 8724 additions and 4429 deletions

View File

@@ -14,11 +14,11 @@ jobs:
at: ~/project
- restore_cache:
keys:
- v1-dependencies-{{ checksum "package.json" }}
- v1-dependencies-{{ checksum "yarn.lock" }}
- v1-dependencies-
- run: yarn install
- run: yarn install --frozen-lockfile
- save_cache:
key: v1-dependencies-{{ checksum "package.json" }}
key: v1-dependencies-{{ checksum "yarn.lock" }}
paths: node_modules
- persist_to_workspace:
root: .

View File

@@ -8,6 +8,7 @@
"@react-navigation/routers",
"@react-navigation/compat",
"@react-navigation/stack",
"@react-navigation/native-stack",
"@react-navigation/drawer",
"@react-navigation/bottom-tabs",
"@react-navigation/material-top-tabs",

2
.gitignore vendored
View File

@@ -5,6 +5,8 @@
.expo
.gradle
local.properties
/coverage/
/types/
/dist/

View File

@@ -1,14 +0,0 @@
{
"git": {
"commitMessage": "chore: release %s",
"tagName": "v%s"
},
"github": {
"release": true
},
"plugins": {
"@release-it/conventional-changelog": {
"preset": "angular"
}
}
}

View File

@@ -35,7 +35,7 @@ Navigators bundle a router and a view which takes the navigation state and decid
A simple navigator could look like this:
```js
import { createNavigator } from '@react-navigation/core';
import { createNavigatorFactory } from '@react-navigation/core';
function StackNavigator({ initialRouteName, children, ...rest }) {
// The `navigation` object contains the navigation state and some helpers (e.g. push, pop)
@@ -56,7 +56,7 @@ function StackNavigator({ initialRouteName, children, ...rest }) {
);
}
export default createNavigator(StackNavigator);
export default createNavigatorFactory(StackNavigator);
```
The navigator can render a screen by calling `descriptors[route.key].render()`. Internally, the descriptor adds appropriate wrappers to handle nested state.

View File

@@ -1,5 +1,3 @@
/* eslint-disable import/no-commonjs */
module.exports = {
presets: [
[

View File

@@ -1,5 +1,3 @@
/* eslint-disable import/no-commonjs */
module.exports = {
extends: ['@commitlint/config-conventional'],
};

View File

@@ -22,28 +22,27 @@
"example": "yarn --cwd packages/example"
},
"devDependencies": {
"@babel/plugin-proposal-class-properties": "^7.5.5",
"@babel/preset-env": "^7.5.5",
"@babel/preset-react": "^7.0.0",
"@babel/preset-typescript": "^7.3.3",
"@babel/runtime": "^7.5.5",
"@commitlint/config-conventional": "^8.0.0",
"@types/jest": "^24.0.13",
"codecov": "^3.5.0",
"commitlint": "^8.0.0",
"core-js": "^3.2.1",
"eslint": "^5.16.0",
"eslint-config-satya164": "^2.4.1",
"husky": "^2.4.0",
"@babel/plugin-proposal-class-properties": "^7.7.0",
"@babel/preset-env": "^7.7.1",
"@babel/preset-react": "^7.7.0",
"@babel/preset-typescript": "^7.7.2",
"@babel/runtime": "^7.7.2",
"@commitlint/config-conventional": "^8.2.0",
"@types/jest": "^24.0.23",
"codecov": "^3.6.1",
"commitlint": "^8.2.0",
"core-js": "^3.4.1",
"eslint": "^6.6.0",
"eslint-config-satya164": "^3.1.2",
"husky": "^3.0.9",
"jest": "^24.8.0",
"lerna": "^3.16.4",
"prettier": "^1.18.2",
"typescript": "^3.5.3"
"lerna": "^3.18.4",
"prettier": "^1.19.1",
"typescript": "^3.7.2"
},
"resolutions": {
"react": "16.8.3",
"react-native": "0.59.10",
"react-native-safe-area-view": "0.14.7"
"react": "~16.8.3",
"react-native": "~0.59.10"
},
"husky": {
"hooks": {

View File

@@ -3,6 +3,139 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [5.0.0-alpha.23](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.22...@react-navigation/bottom-tabs@5.0.0-alpha.23) (2019-12-07)
### Features
* export underlying views used to build navigators ([#191](https://github.com/react-navigation/navigation-ex/issues/191)) ([d618ab3](https://github.com/react-navigation/navigation-ex/commit/d618ab382ecc5eccbcd5faa89e76f9ed2d75f405))
# [5.0.0-alpha.22](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.21...@react-navigation/bottom-tabs@5.0.0-alpha.22) (2019-11-17)
### Bug Fixes
* workaround SafereaProvider causing jumping ([c17ad18](https://github.com/react-navigation/navigation-ex/commit/c17ad18b20cb05c577e1235a58ccc1c856fee086)), closes [#174](https://github.com/react-navigation/navigation-ex/issues/174)
# [5.0.0-alpha.21](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.20...@react-navigation/bottom-tabs@5.0.0-alpha.21) (2019-11-10)
### Bug Fixes
* make bottom tab bar consistent across platforms ([d1ca7f9](https://github.com/react-navigation/navigation-ex/commit/d1ca7f9))
# [5.0.0-alpha.20](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.19...@react-navigation/bottom-tabs@5.0.0-alpha.20) (2019-11-08)
**Note:** Version bump only for package @react-navigation/bottom-tabs
# [5.0.0-alpha.19](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.18...@react-navigation/bottom-tabs@5.0.0-alpha.19) (2019-11-04)
### Bug Fixes
* fix default BottomTabBar button ([#161](https://github.com/react-navigation/navigation-ex/issues/161)) ([22cb675](https://github.com/react-navigation/navigation-ex/commit/22cb675))
# [5.0.0-alpha.18](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.17...@react-navigation/bottom-tabs@5.0.0-alpha.18) (2019-11-04)
**Note:** Version bump only for package @react-navigation/bottom-tabs
# [5.0.0-alpha.17](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.16...@react-navigation/bottom-tabs@5.0.0-alpha.17) (2019-10-30)
### Bug Fixes
* hide inactive pages from screen reader in tabs ([#148](https://github.com/react-navigation/navigation-ex/issues/148)) ([58f7115](https://github.com/react-navigation/navigation-ex/commit/58f7115))
### Features
* add an 'unmountInactiveScreens' option ([12d597f](https://github.com/react-navigation/navigation-ex/commit/12d597f))
# [5.0.0-alpha.16](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.15...@react-navigation/bottom-tabs@5.0.0-alpha.16) (2019-10-29)
**Note:** Version bump only for package @react-navigation/bottom-tabs
# [5.0.0-alpha.15](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.14...@react-navigation/bottom-tabs@5.0.0-alpha.15) (2019-10-22)
**Note:** Version bump only for package @react-navigation/bottom-tabs
# [5.0.0-alpha.14](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.13...@react-navigation/bottom-tabs@5.0.0-alpha.14) (2019-10-15)
### Bug Fixes
* improve keyboard handling with bottom tab bar ([42beb66](https://github.com/react-navigation/navigation-ex/commit/42beb66))
* make it possible to run the example on web ([7a901af](https://github.com/react-navigation/navigation-ex/commit/7a901af))
### 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.13](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.12...@react-navigation/bottom-tabs@5.0.0-alpha.13) (2019-10-06)
**Note:** Version bump only for package @react-navigation/bottom-tabs
# [5.0.0-alpha.12](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.11...@react-navigation/bottom-tabs@5.0.0-alpha.12) (2019-10-03)
**Note:** Version bump only for package @react-navigation/bottom-tabs
# [5.0.0-alpha.11](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.10...@react-navigation/bottom-tabs@5.0.0-alpha.11) (2019-10-03)
**Note:** Version bump only for package @react-navigation/bottom-tabs
# [5.0.0-alpha.10](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.9...@react-navigation/bottom-tabs@5.0.0-alpha.10) (2019-09-27)

View File

@@ -10,11 +10,40 @@ Open a Terminal in your project's folder and run,
yarn add @react-navigation/core @react-navigation/bottom-tabs
```
Now we need to install [`react-native-safe-area-context`](https://github.com/th3rdwave/react-native-safe-area-context).
If you are using Expo, to ensure that you get the compatible versions of the libraries, run:
```sh
expo install react-native-safe-area-context
```
If you are not using Expo, run the following:
```sh
yarn add react-native-safe-area-context
```
If you are using Expo, you are done. Otherwise, continue to the next steps.
To complete the linking on iOS, make sure you have [Cocoapods](https://cocoapods.org/) installed. Then run:
```sh
cd ios
pod install
cd ..
```
## Usage
```js
import { MaterialCommunityIcons } from 'react-native-vector-icons';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
const getTabBarIcon = name => ({ color, size }) => (
<MaterialCommunityIcons name={name} color={color} size={size} />
);
const BottomTabs = createBottomTabNavigator();
export default function App() {
@@ -25,7 +54,7 @@ export default function App() {
component={Article}
options={{
tabBarLabel: 'Article',
tabBarIcon: 'chrome-reader-mode',
tabBarIcon: getTabBarIcon('file-document-box'),
}}
/>
<BottomTabs.Screen
@@ -33,7 +62,7 @@ export default function App() {
component={Chat}
options={{
tabBarLabel: 'Chat',
tabBarIcon: 'chat-bubble',
tabBarIcon: getTabBarIcon('message-reply'),
}}
/>
<BottomTabs.Screen
@@ -41,7 +70,7 @@ export default function App() {
component={Contacts}
options={{
tabBarLabel: 'Contacts',
tabBarIcon: 'contacts',
tabBarIcon: getTabBarIcon('contacts'),
}}
/>
</BottomTabs.Navigator>

View File

@@ -10,7 +10,7 @@
"android",
"tab"
],
"version": "5.0.0-alpha.10",
"version": "5.0.0-alpha.23",
"license": "MIT",
"repository": {
"type": "git",
@@ -33,22 +33,23 @@
"clean": "del lib"
},
"dependencies": {
"@react-navigation/routers": "^5.0.0-alpha.8",
"react-native-safe-area-view": "^0.14.6"
"@react-navigation/routers": "^5.0.0-alpha.15"
},
"devDependencies": {
"@react-native-community/bob": "^0.7.0",
"@types/react": "^16.8.24",
"@types/react-native": "^0.60.2",
"del-cli": "^2.0.0",
"react": "16.8.3",
"react-native": "0.59.10",
"typescript": "^3.5.3"
"@types/react": "^16.9.11",
"@types/react-native": "^0.60.22",
"del-cli": "^3.0.0",
"react": "~16.8.3",
"react-native": "~0.59.10",
"react-native-safe-area-context": "^0.6.0",
"typescript": "^3.7.2"
},
"peerDependencies": {
"@react-navigation/core": "^5.0.0-alpha.0",
"react": "*",
"react-native": "*"
"react-native": "*",
"react-native-safe-area-context": "^0.3.6"
},
"@react-native-community/bob": {
"source": "src",

View File

@@ -1,13 +1,12 @@
/**
* Navigators
*/
export {
default as createBottomTabNavigator,
} from './navigators/createBottomTabNavigator';
export { default as createBottomTabNavigator } from './navigators/createBottomTabNavigator';
/**
* Views
*/
export { default as BottomTabView } from './views/BottomTabView';
export { default as BottomTabBar } from './views/BottomTabBar';
/**

View File

@@ -1,7 +1,7 @@
import * as React from 'react';
import {
useNavigationBuilder,
createNavigator,
createNavigatorFactory,
DefaultNavigatorOptions,
} from '@react-navigation/core';
import {
@@ -49,7 +49,7 @@ function BottomTabNavigator({
);
}
export default createNavigator<
export default createNavigatorFactory<
BottomTabNavigationOptions,
typeof BottomTabNavigator
>(BottomTabNavigator);

View File

@@ -1,12 +1,12 @@
import * as React from 'react';
import {
TouchableWithoutFeedbackProps,
AccessibilityRole,
AccessibilityStates,
StyleProp,
TextStyle,
ViewStyle,
} from 'react-native';
import SafeAreaView from 'react-native-safe-area-view';
import {
NavigationHelpers,
NavigationProp,
@@ -27,8 +27,6 @@ export type BottomTabNavigationEventMap = {
tabLongPress: undefined;
};
export type Orientation = 'horizontal' | 'vertical';
export type LabelPosition = 'beside-icon' | 'below-icon';
export type BottomTabNavigationHelpers = NavigationHelpers<
@@ -53,7 +51,7 @@ export type BottomTabNavigationProp<
* @param [params] Params object for the route.
*/
jumpTo<RouteName extends Extract<keyof ParamList, string>>(
...args: ParamList[RouteName] extends (undefined | any)
...args: ParamList[RouteName] extends undefined | any
? [RouteName] | [RouteName, ParamList[RouteName]]
: [RouteName, ParamList[RouteName]]
): void;
@@ -66,28 +64,22 @@ export type BottomTabNavigationOptions = {
title?: string;
/**
* Title string of a tab displayed in the tab bar or React Element
* or a function that given { focused: boolean, tintColor: string } returns a React.Node, to display in tab bar.
* Title string of a tab displayed in the tab bar
* or a function that given { focused: boolean, color: string } returns a React.Node to display in tab bar.
* When undefined, scene title is used. To hide, see tabBarOptions.showLabel in the previous section.
*/
tabBarLabel?:
| React.ReactNode
| ((props: {
focused: boolean;
tintColor: string;
horizontal: boolean;
}) => React.ReactNode);
| string
| ((props: { focused: boolean; color: string }) => React.ReactNode);
/**
* React Element or a function that given { focused: boolean, tintColor: string } returns a React.Node, to display in the tab bar.
* A function that given { focused: boolean, color: string } returns a React.Node to display in the tab bar.
*/
tabBarIcon?:
| React.ReactNode
| ((props: {
focused: boolean;
tintColor: string;
horizontal: boolean;
}) => React.ReactNode);
tabBarIcon?: (props: {
focused: boolean;
color: string;
size: number;
}) => React.ReactNode;
/**
* Accessibility label for the tab button. This is read by the screen reader when the user taps the tab.
@@ -106,9 +98,10 @@ export type BottomTabNavigationOptions = {
tabBarVisible?: boolean;
/**
* Buttton component to render for the tab items instead of the default `TouchableWithoutFeedback`
* Function which returns a React element to render as the tab bar button.
* Renders `TouchableWithoutFeedback` by default.
*/
tabBarButtonComponent?: React.ComponentType<any>;
tabBarButton?: (props: BottomTabBarButtonProps) => React.ReactNode;
};
export type BottomTabDescriptor = Descriptor<
@@ -129,9 +122,14 @@ export type BottomTabNavigationConfig = {
*/
lazy?: boolean;
/**
* Custom tab bar component.
* Whether a screen should be unmounted when navigating away from it.
* Defaults to `false`.
*/
tabBarComponent?: React.ComponentType<BottomTabBarProps>;
unmountInactiveScreens?: boolean;
/**
* Function that returns a React element to display as the tab bar.
*/
tabBar?: (props: BottomTabBarProps) => React.ReactNode;
/**
* Options for the tab bar which will be passed as props to the tab bar component.
*/
@@ -140,7 +138,7 @@ export type BottomTabNavigationConfig = {
export type BottomTabBarOptions = {
/**
* Whether the tab bar gets hidden when the keyboard is shown.
* Whether the tab bar gets hidden when the keyboard is shown. Defaults to `false`.
*/
keyboardHidesTabBar?: boolean;
/**
@@ -181,12 +179,14 @@ export type BottomTabBarOptions = {
tabStyle?: StyleProp<ViewStyle>;
/**
* Whether the label is renderd below the icon or beside the icon.
* When a function is passed, it receives the device orientation to render the label differently.
* When a function is passed, it receives the device dimensions to render the label differently.
* By default, in `vertical` orinetation, label is rendered below and in `horizontal` orientation, it's renderd beside.
*/
labelPosition?:
| LabelPosition
| ((options: { deviceOrientation: Orientation }) => LabelPosition);
| ((options: {
dimensions: { height: number; width: number };
}) => LabelPosition);
/**
* Whether the label position should adapt to the orientation.
*/
@@ -213,26 +213,26 @@ export type BottomTabBarProps = BottomTabBarOptions & {
route: Route<string>;
focused: boolean;
}) => AccessibilityStates[];
getButtonComponent: (props: {
route: Route<string>;
}) => React.ComponentType<any> | undefined;
getLabelText: (props: {
route: Route<string>;
}) =>
| ((scene: {
focused: boolean;
tintColor: string;
orientation: 'horizontal' | 'vertical';
color: string;
}) => React.ReactNode | undefined)
| React.ReactNode;
| string;
getTestID: (props: { route: Route<string> }) => string | undefined;
renderButton: (
props: { route: Route<string> } & BottomTabBarButtonProps
) => React.ReactNode;
renderIcon: (props: {
route: Route<string>;
focused: boolean;
tintColor: string;
horizontal: boolean;
color: string;
size: number;
}) => React.ReactNode;
activeTintColor: string;
inactiveTintColor: string;
safeAreaInset?: React.ComponentProps<typeof SafeAreaView>['forceInset'];
};
export type BottomTabBarButtonProps = TouchableWithoutFeedbackProps & {
children: React.ReactNode;
};

View File

@@ -1,5 +1,6 @@
import React from 'react';
import {
View,
Animated,
StyleSheet,
Keyboard,
@@ -8,11 +9,10 @@ import {
ScaledSize,
Dimensions,
} from 'react-native';
import SafeAreaView from 'react-native-safe-area-view';
import { Route, NavigationContext } from '@react-navigation/core';
import { SafeAreaConsumer } from 'react-native-safe-area-context';
import TabBarIcon from './TabBarIcon';
import TouchableWithoutFeedbackWrapper from './TouchableWithoutFeedbackWrapper';
import { BottomTabBarProps } from '../types';
type State = {
@@ -23,18 +23,16 @@ type State = {
};
type Props = BottomTabBarProps & {
safeAreaInset: React.ComponentProps<typeof SafeAreaView>['forceInset'];
activeTintColor: string;
inactiveTintColor: string;
};
const majorVersion = parseInt(Platform.Version as string, 10);
const isIos = Platform.OS === 'ios';
const isIOS11 = majorVersion >= 11 && isIos;
const DEFAULT_TABBAR_HEIGHT = 50;
const DEFAULT_MAX_TAB_ITEM_WIDTH = 125;
export default class TabBarBottom extends React.Component<Props, State> {
static defaultProps = {
keyboardHidesTabBar: true,
keyboardHidesTabBar: false,
activeTintColor: '#007AFF',
activeBackgroundColor: 'transparent',
inactiveTintColor: '#8E8E93',
@@ -42,10 +40,7 @@ export default class TabBarBottom extends React.Component<Props, State> {
showLabel: true,
showIcon: true,
allowFontScaling: true,
adaptive: isIOS11,
safeAreaInset: { bottom: 'always', top: 'never' } as React.ComponentProps<
typeof SafeAreaView
>['forceInset'],
adaptive: true,
};
state = {
@@ -87,7 +82,7 @@ export default class TabBarBottom extends React.Component<Props, State> {
this.setState({ keyboard: true }, () =>
Animated.timing(this.state.visible, {
toValue: 0,
duration: 150,
duration: 200,
useNativeDriver: true,
}).start()
);
@@ -95,10 +90,12 @@ export default class TabBarBottom extends React.Component<Props, State> {
private handleKeyboardHide = () =>
Animated.timing(this.state.visible, {
toValue: 1,
duration: 100,
duration: 250,
useNativeDriver: true,
}).start(() => {
this.setState({ keyboard: false });
}).start(({ finished }) => {
if (finished) {
this.setState({ keyboard: false });
}
});
private handleLayout = (e: LayoutChangeEvent) => {
@@ -139,7 +136,7 @@ export default class TabBarBottom extends React.Component<Props, State> {
const label = this.props.getLabelText({ route });
const horizontal = this.shouldUseHorizontalLabels();
const tintColor = focused ? activeTintColor : inactiveTintColor;
const color = focused ? activeTintColor : inactiveTintColor;
if (typeof label === 'string') {
return (
@@ -147,7 +144,7 @@ export default class TabBarBottom extends React.Component<Props, State> {
numberOfLines={1}
style={[
styles.label,
{ color: tintColor },
{ color },
showIcon && horizontal ? styles.labelBeside : styles.labelBeneath,
labelStyle,
]}
@@ -158,15 +155,11 @@ export default class TabBarBottom extends React.Component<Props, State> {
);
}
if (typeof label === 'function') {
return label({
focused,
tintColor,
orientation: horizontal ? 'horizontal' : 'vertical',
});
if (typeof label === 'string') {
return label;
}
return label;
return label({ focused, color });
};
private renderIcon = ({
@@ -181,7 +174,6 @@ export default class TabBarBottom extends React.Component<Props, State> {
inactiveTintColor,
renderIcon,
showIcon,
showLabel,
} = this.props;
if (showIcon === false) {
@@ -196,17 +188,13 @@ export default class TabBarBottom extends React.Component<Props, State> {
return (
<TabBarIcon
route={route}
horizontal={horizontal}
size={horizontal ? 17 : 24}
activeOpacity={activeOpacity}
inactiveOpacity={inactiveOpacity}
activeTintColor={activeTintColor}
inactiveTintColor={inactiveTintColor}
renderIcon={renderIcon}
style={[
styles.iconWithExplicitHeight,
showLabel === false && !horizontal && styles.iconWithoutLabel,
showLabel !== false && !horizontal && styles.iconWithLabel,
]}
style={horizontal ? styles.iconHorizontal : styles.iconVertical}
/>
);
};
@@ -219,12 +207,11 @@ export default class TabBarBottom extends React.Component<Props, State> {
if (labelPosition) {
let position;
if (typeof labelPosition === 'string') {
position = labelPosition;
} else {
position = labelPosition({
deviceOrientation: isLandscape ? 'horizontal' : 'vertical',
});
position = labelPosition({ dimensions });
}
if (position) {
@@ -236,8 +223,8 @@ export default class TabBarBottom extends React.Component<Props, State> {
return false;
}
// @ts-ignore
if (Platform.isPad) {
if (dimensions.width >= 768) {
// Screen size matches a tablet
let maxTabItemWidth = DEFAULT_MAX_TAB_ITEM_WIDTH;
const flattenedStyle = StyleSheet.flatten(tabStyle);
@@ -268,131 +255,121 @@ export default class TabBarBottom extends React.Component<Props, State> {
getAccessibilityLabel,
getAccessibilityRole,
getAccessibilityStates,
getButtonComponent,
renderButton,
getTestID,
safeAreaInset,
style,
tabStyle,
} = this.props;
const { routes } = state;
const tabBarStyle = [
styles.tabBar,
// @ts-ignore
this.shouldUseHorizontalLabels() && !Platform.isPad
? styles.tabBarCompact
: styles.tabBarRegular,
style,
];
return (
<Animated.View
style={[
styles.container,
keyboardHidesTabBar
? // eslint-disable-next-line react-native/no-inline-styles
<SafeAreaConsumer>
{insets => (
<Animated.View
style={[
styles.tabBar,
keyboardHidesTabBar
? {
// When the keyboard is shown, slide down the tab bar
transform: [
{
translateY: this.state.visible.interpolate({
inputRange: [0, 1],
outputRange: [this.state.layout.height, 0],
}),
},
],
// Absolutely position the tab bar so that the content is below it
// This is needed to avoid gap at bottom when the tab bar is hidden
position: this.state.keyboard ? 'absolute' : null,
}
: null,
{
// When the keyboard is shown, slide down the tab bar
transform: [
{
translateY: this.state.visible.interpolate({
inputRange: [0, 1],
outputRange: [this.state.layout.height, 0],
}),
},
],
// Absolutely position the tab bar so that the content is below it
// This is needed to avoid gap at bottom when the tab bar is hidden
position: this.state.keyboard ? 'absolute' : null,
}
: null,
]}
pointerEvents={
keyboardHidesTabBar && this.state.keyboard ? 'none' : 'auto'
}
onLayout={this.handleLayout}
>
<SafeAreaView style={tabBarStyle} forceInset={safeAreaInset}>
{routes.map((route, index) => {
const focused = index === state.index;
const scene = { route, focused };
const accessibilityLabel = getAccessibilityLabel({
route,
});
height: DEFAULT_TABBAR_HEIGHT + (insets ? insets.bottom : 0),
paddingBottom: insets ? insets.bottom : 0,
},
style,
]}
pointerEvents={
keyboardHidesTabBar && this.state.keyboard ? 'none' : 'auto'
}
>
<View style={styles.content} onLayout={this.handleLayout}>
{routes.map((route, index) => {
const focused = index === state.index;
const scene = { route, focused };
const accessibilityLabel = getAccessibilityLabel({
route,
});
const accessibilityRole = getAccessibilityRole({
route,
});
const accessibilityRole = getAccessibilityRole({
route,
});
const accessibilityStates = getAccessibilityStates(scene);
const accessibilityStates = getAccessibilityStates(scene);
const testID = getTestID({ route });
const testID = getTestID({ route });
const backgroundColor = focused
? activeBackgroundColor
: inactiveBackgroundColor;
const backgroundColor = focused
? activeBackgroundColor
: inactiveBackgroundColor;
const ButtonComponent =
getButtonComponent({ route }) || TouchableWithoutFeedbackWrapper;
return (
<NavigationContext.Provider
key={route.key}
value={descriptors[route.key].navigation}
>
<ButtonComponent
onPress={() => onTabPress({ route })}
onLongPress={() => onTabLongPress({ route })}
testID={testID}
accessibilityLabel={accessibilityLabel}
accessibilityRole={accessibilityRole}
accessibilityStates={accessibilityStates}
style={[
styles.tab,
{ backgroundColor },
this.shouldUseHorizontalLabels()
? styles.tabLandscape
: styles.tabPortrait,
tabStyle,
]}
>
{this.renderIcon(scene)}
{this.renderLabel(scene)}
</ButtonComponent>
</NavigationContext.Provider>
);
})}
</SafeAreaView>
</Animated.View>
return (
<NavigationContext.Provider
key={route.key}
value={descriptors[route.key].navigation}
>
{renderButton({
route,
onPress: () => onTabPress({ route }),
onLongPress: () => onTabLongPress({ route }),
testID,
accessibilityLabel,
accessibilityRole,
accessibilityStates,
style: [
styles.tab,
{ backgroundColor },
this.shouldUseHorizontalLabels()
? styles.tabLandscape
: styles.tabPortrait,
tabStyle,
],
children: (
<React.Fragment>
{this.renderIcon(scene)}
{this.renderLabel(scene)}
</React.Fragment>
),
})}
</NavigationContext.Provider>
);
})}
</View>
</Animated.View>
)}
</SafeAreaConsumer>
);
}
}
const DEFAULT_HEIGHT = 49;
const COMPACT_HEIGHT = 29;
const styles = StyleSheet.create({
tabBar: {
backgroundColor: '#fff',
borderTopWidth: StyleSheet.hairlineWidth,
borderTopColor: 'rgba(0, 0, 0, .3)',
flexDirection: 'row',
},
container: {
left: 0,
right: 0,
bottom: 0,
backgroundColor: '#fff',
borderTopWidth: StyleSheet.hairlineWidth,
borderTopColor: 'rgba(0, 0, 0, .3)',
elevation: 8,
},
tabBarCompact: {
height: COMPACT_HEIGHT,
},
tabBarRegular: {
height: DEFAULT_HEIGHT,
content: {
flex: 1,
flexDirection: 'row',
},
tab: {
flex: 1,
alignItems: isIos ? 'center' : 'stretch',
alignItems: 'center',
},
tabPortrait: {
justifyContent: 'flex-end',
@@ -402,15 +379,11 @@ const styles = StyleSheet.create({
justifyContent: 'center',
flexDirection: 'row',
},
iconWithoutLabel: {
iconVertical: {
flex: 1,
},
iconWithLabel: {
flex: 1,
},
iconWithExplicitHeight: {
// @ts-ignore
height: Platform.isPad ? DEFAULT_HEIGHT : COMPACT_HEIGHT,
iconHorizontal: {
height: '100%',
},
label: {
textAlign: 'center',

View File

@@ -1,22 +1,27 @@
import * as React from 'react';
import {
View,
TouchableWithoutFeedback,
StyleSheet,
AccessibilityRole,
AccessibilityStates,
} from 'react-native';
import { Route, CommonActions } from '@react-navigation/core';
import { TabNavigationState } from '@react-navigation/routers';
// eslint-disable-next-line import/no-unresolved
import { ScreenContainer } from 'react-native-screens';
import SafeAreaProviderCompat from './SafeAreaProviderCompat';
import ResourceSavingScene from './ResourceSavingScene';
import BottomTabBar from './BottomTabBar';
import {
BottomTabNavigationConfig,
BottomTabDescriptorMap,
BottomTabNavigationHelpers,
BottomTabBarProps,
BottomTabBarButtonProps,
} from '../types';
import ResourceSavingScene from './ResourceSavingScene';
type Props = BottomTabNavigationConfig & {
state: TabNavigationState;
@@ -48,37 +53,46 @@ export default class BottomTabView extends React.Component<Props, State> {
loaded: [this.props.state.index],
};
private getButtonComponent = ({ route }: { route: Route<string> }) => {
private renderButton = ({
route,
children,
style,
...rest
}: { route: Route<string> } & BottomTabBarButtonProps) => {
const { descriptors } = this.props;
const descriptor = descriptors[route.key];
const options = descriptor.options;
if (options.tabBarButtonComponent) {
return options.tabBarButtonComponent;
if (options.tabBarButton) {
return options.tabBarButton({ children, style, ...rest });
}
return undefined;
return (
<TouchableWithoutFeedback {...rest}>
<View style={style}>{children}</View>
</TouchableWithoutFeedback>
);
};
private renderIcon = ({
route,
focused,
tintColor,
horizontal,
color,
size,
}: {
route: Route<string>;
focused: boolean;
tintColor: string;
horizontal: boolean;
color: string;
size: number;
}) => {
const { descriptors } = this.props;
const descriptor = descriptors[route.key];
const options = descriptor.options;
if (options.tabBarIcon) {
return typeof options.tabBarIcon === 'function'
? options.tabBarIcon({ focused, tintColor, horizontal })
: options.tabBarIcon;
return typeof options.tabBarIcon === 'string'
? options.tabBarIcon
: options.tabBarIcon({ focused, color, size });
}
return null;
@@ -158,7 +172,7 @@ export default class BottomTabView extends React.Component<Props, State> {
private renderTabBar = () => {
const {
tabBarComponent: TabBarComponent = BottomTabBar,
tabBar = (props: BottomTabBarProps) => <BottomTabBar {...props} />,
tabBarOptions,
state,
navigation,
@@ -173,54 +187,66 @@ export default class BottomTabView extends React.Component<Props, State> {
return null;
}
return (
<TabBarComponent
{...tabBarOptions}
state={state}
descriptors={descriptors}
navigation={navigation}
onTabPress={this.handleTabPress}
onTabLongPress={this.handleTabLongPress}
getLabelText={this.getLabelText}
getButtonComponent={this.getButtonComponent}
getAccessibilityLabel={this.getAccessibilityLabel}
getAccessibilityRole={this.getAccessibilityRole}
getAccessibilityStates={this.getAccessibilityStates}
getTestID={this.getTestID}
renderIcon={this.renderIcon}
/>
);
return tabBar({
...tabBarOptions,
state: state,
descriptors: descriptors,
navigation: navigation,
onTabPress: this.handleTabPress,
onTabLongPress: this.handleTabLongPress,
getLabelText: this.getLabelText,
getAccessibilityLabel: this.getAccessibilityLabel,
getAccessibilityRole: this.getAccessibilityRole,
getAccessibilityStates: this.getAccessibilityStates,
getTestID: this.getTestID,
renderButton: this.renderButton,
renderIcon: this.renderIcon,
});
};
render() {
const { state, descriptors, lazy } = this.props;
const { state, descriptors, lazy, unmountInactiveScreens } = this.props;
const { routes } = state;
const { loaded } = this.state;
return (
<View style={styles.container}>
<ScreenContainer style={styles.pages}>
{routes.map((route, index) => {
if (lazy && !loaded.includes(index)) {
// Don't render a screen if we've never navigated to it
return null;
}
<SafeAreaProviderCompat>
<View style={styles.container}>
<ScreenContainer style={styles.pages}>
{routes.map((route, index) => {
if (unmountInactiveScreens && index !== state.index) {
return null;
}
const isFocused = state.index === index;
if (lazy && !loaded.includes(index)) {
// Don't render a screen if we've never navigated to it
return null;
}
return (
<ResourceSavingScene
key={route.key}
style={StyleSheet.absoluteFill}
isVisible={isFocused}
>
{descriptors[route.key].render()}
</ResourceSavingScene>
);
})}
</ScreenContainer>
{this.renderTabBar()}
</View>
const isFocused = state.index === index;
return (
<ResourceSavingScene
key={route.key}
style={StyleSheet.absoluteFill}
isVisible={isFocused}
>
<View
accessibilityElementsHidden={!isFocused}
importantForAccessibility={
isFocused ? 'auto' : 'no-hide-descendants'
}
style={styles.content}
>
{descriptors[route.key].render()}
</View>
</ResourceSavingScene>
);
})}
</ScreenContainer>
{this.renderTabBar()}
</View>
</SafeAreaProviderCompat>
);
}
}
@@ -233,4 +259,7 @@ const styles = StyleSheet.create({
pages: {
flex: 1,
},
content: {
flex: 1,
},
});

View File

@@ -24,12 +24,7 @@ export default class ResourceSavingScene extends React.Component<Props> {
return (
<View
style={[
styles.container,
style,
// eslint-disable-next-line react-native/no-inline-styles
{ opacity: isVisible ? 1 : 0 },
]}
style={[styles.container, style, { opacity: isVisible ? 1 : 0 }]}
collapsable={false}
removeClippedSubviews={
// On iOS, set removeClippedSubviews to true only when not focused

View File

@@ -0,0 +1,26 @@
import * as React from 'react';
import {
SafeAreaProvider,
SafeAreaConsumer,
} from 'react-native-safe-area-context';
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/navigation-ex/issues/174
return children;
}
return <SafeAreaProvider>{children}</SafeAreaProvider>;
}}
</SafeAreaConsumer>
);
}

View File

@@ -4,7 +4,7 @@ import { Route } from '@react-navigation/core';
type Props = {
route: Route<string>;
horizontal: boolean;
size: number;
activeOpacity: number;
inactiveOpacity: number;
activeTintColor: string;
@@ -12,8 +12,8 @@ type Props = {
renderIcon: (props: {
route: Route<string>;
focused: boolean;
tintColor: string;
horizontal: boolean;
color: string;
size: number;
}) => React.ReactNode;
style: StyleProp<ViewStyle>;
};
@@ -25,7 +25,7 @@ export default function TabBarIcon({
activeTintColor,
inactiveTintColor,
renderIcon,
horizontal,
size,
style,
}: Props) {
// We render the icon twice at the same position on top of each other:
@@ -36,16 +36,16 @@ export default function TabBarIcon({
{renderIcon({
route,
focused: true,
horizontal,
tintColor: activeTintColor,
size,
color: activeTintColor,
})}
</View>
<View style={[styles.icon, { opacity: inactiveOpacity }]}>
{renderIcon({
route,
focused: false,
horizontal,
tintColor: inactiveTintColor,
size,
color: inactiveTintColor,
})}
</View>
</View>

View File

@@ -1,28 +0,0 @@
import React from 'react';
import { TouchableWithoutFeedback, View } from 'react-native';
export default function TouchableWithoutFeedbackWrapper({
onPress,
onLongPress,
testID,
accessibilityLabel,
accessibilityRole,
accessibilityStates,
...rest
}: React.ComponentProps<typeof TouchableWithoutFeedback> & {
children: React.ReactNode;
}) {
return (
<TouchableWithoutFeedback
onPress={onPress}
onLongPress={onLongPress}
testID={testID}
hitSlop={{ left: 15, right: 15, top: 0, bottom: 5 }}
accessibilityLabel={accessibilityLabel}
accessibilityRole={accessibilityRole}
accessibilityStates={accessibilityStates}
>
<View {...rest} />
</TouchableWithoutFeedback>
);
}

View File

@@ -3,6 +3,100 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [5.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

View File

@@ -1,7 +1,7 @@
{
"name": "@react-navigation/compat",
"description": "Compatibility layer to write navigator definitions in static configuration format",
"version": "5.0.0-alpha.4",
"version": "5.0.0-alpha.15",
"license": "MIT",
"repository": {
"type": "git",
@@ -24,16 +24,16 @@
"clean": "del lib"
},
"dependencies": {
"@react-navigation/routers": "^5.0.0-alpha.8"
"@react-navigation/routers": "^5.0.0-alpha.15"
},
"devDependencies": {
"@types/react": "^16.8.19",
"react": "^16.8.3",
"typescript": "^3.5.3"
"@types/react": "^16.9.11",
"react": "~16.8.3",
"typescript": "^3.7.2"
},
"peerDependencies": {
"@react-navigation/core": "^5.0.0-alpha.0",
"react": "^16.8.3"
"react": "~16.8.3"
},
"@react-native-community/bob": {
"source": "src",

View File

@@ -173,6 +173,12 @@ export default function createCompatNavigationProp<
return defaultValue;
},
isFirstRouteInParent(): boolean {
const { routes } = navigation.dangerouslyGetState();
// @ts-ignore
return routes[0].key === state.key;
},
dangerouslyGetParent() {
const parent = navigation.dangerouslyGetParent();

View File

@@ -49,7 +49,7 @@ export default function createCompatNavigatorFactory<
navigationConfig: Partial<Omit<NavigationConfig, 'screenOptions'>> & {
order?: Array<Extract<keyof ParamList, string>>;
defaultNavigationOptions?: ScreenOptions;
navigationOptions?: { [key: string]: any };
navigationOptions?: Record<string, any>;
} = {}
) => {
const Pair = createNavigator();

View File

@@ -1,6 +1,6 @@
import {
useNavigationBuilder,
createNavigator,
createNavigatorFactory,
DefaultNavigatorOptions,
} from '@react-navigation/core';
import {
@@ -24,5 +24,5 @@ function SwitchNavigator(props: Props) {
}
export default createCompatNavigatorFactory(
createNavigator<{}, typeof SwitchNavigator>(SwitchNavigator)
createNavigatorFactory<{}, typeof SwitchNavigator>(SwitchNavigator)
);

View File

@@ -5,13 +5,9 @@ import * as SwitchActions from './SwitchActions';
export { NavigationActions, StackActions, DrawerActions, SwitchActions };
export {
default as createCompatNavigatorFactory,
} from './createCompatNavigatorFactory';
export { default as createCompatNavigatorFactory } from './createCompatNavigatorFactory';
export {
default as createCompatNavigationProp,
} from './createCompatNavigationProp';
export { default as createCompatNavigationProp } from './createCompatNavigationProp';
export { default as createSwitchNavigator } from './createSwitchNavigator';

View File

@@ -25,6 +25,7 @@ export type CompatNavigationProp<
paramName: T,
defaultValue?: ParamList[RouteName][T]
): ParamList[RouteName][T];
isFirstRouteInParent(): boolean;
dangerouslyGetParent<
T = NavigationProp<ParamListBase> | undefined
>(): T extends NavigationProp<ParamListBase>

View File

@@ -3,6 +3,174 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [5.0.0-alpha.26](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.25...@react-navigation/core@5.0.0-alpha.26) (2019-12-07)
### Bug Fixes
* don't handle replace if screen to replace with isn't present ([7b13a81](https://github.com/react-navigation/navigation-ex/commit/7b13a81ac8260879c8658be5704f46db59a72c73)), closes [#193](https://github.com/react-navigation/navigation-ex/issues/193)
# [5.0.0-alpha.25](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.24...@react-navigation/core@5.0.0-alpha.25) (2019-11-29)
### Bug Fixes
* wrap reset and resetRoot inside transaction ([#189](https://github.com/react-navigation/navigation-ex/issues/189)) ([5a0dfa1](https://github.com/react-navigation/navigation-ex/commit/5a0dfa1a155715714c8483fafc5a94dbc5120754)), closes [#185](https://github.com/react-navigation/navigation-ex/issues/185)
# [5.0.0-alpha.24](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.23...@react-navigation/core@5.0.0-alpha.24) (2019-11-20)
### Bug Fixes
* allow passing partial params to `setParams` ([#177](https://github.com/react-navigation/navigation-ex/issues/177)) ([c3e9e45](https://github.com/react-navigation/navigation-ex/commit/c3e9e4578e98aa5b0635949a288e19eaeec12c85))
# [5.0.0-alpha.23](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.22...@react-navigation/core@5.0.0-alpha.23) (2019-11-17)
### Bug Fixes
* merge initial params on push ([11efb06](https://github.com/react-navigation/navigation-ex/commit/11efb066429a3fc8b7e8e48d897286208d9a5449))
# [5.0.0-alpha.22](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.21...@react-navigation/core@5.0.0-alpha.22) (2019-11-10)
### Bug Fixes
* throw when containers are nested within another ([d4072e7](https://github.com/react-navigation/navigation-ex/commit/d4072e7))
# [5.0.0-alpha.21](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.20...@react-navigation/core@5.0.0-alpha.21) (2019-11-08)
### Bug Fixes
* don't crash if initialState is null ([270fbdc](https://github.com/react-navigation/navigation-ex/commit/270fbdc))
* fix types for resetRoot to accept undefined ([e871fdb](https://github.com/react-navigation/navigation-ex/commit/e871fdb))
# [5.0.0-alpha.20](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.19...@react-navigation/core@5.0.0-alpha.20) (2019-11-02)
### Bug Fixes
* pass rehydrated state in onStateChange and devtools ([5a34764](https://github.com/react-navigation/navigation-ex/commit/5a34764))
# [5.0.0-alpha.19](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.18...@react-navigation/core@5.0.0-alpha.19) (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.18](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.17...@react-navigation/core@5.0.0-alpha.18) (2019-10-29)
### Bug Fixes
* improve type annotation for screens ([8f16085](https://github.com/react-navigation/navigation-ex/commit/8f16085))
# [5.0.0-alpha.17](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.16...@react-navigation/core@5.0.0-alpha.17) (2019-10-22)
**Note:** Version bump only for package @react-navigation/core
# [5.0.0-alpha.16](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.15...@react-navigation/core@5.0.0-alpha.16) (2019-10-18)
### Bug Fixes
* rehydrate state before using it ([3e92e22](https://github.com/react-navigation/navigation-ex/commit/3e92e22))
### Features
* make it easier to navigate to a specific route in navigator ([#114](https://github.com/react-navigation/navigation-ex/issues/114)) ([a543f1b](https://github.com/react-navigation/navigation-ex/commit/a543f1b)), closes [#90](https://github.com/react-navigation/navigation-ex/issues/90)
# [5.0.0-alpha.15](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.14...@react-navigation/core@5.0.0-alpha.15) (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.14](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.13...@react-navigation/core@5.0.0-alpha.14) (2019-10-06)
**Note:** Version bump only for package @react-navigation/core
# [5.0.0-alpha.13](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.12...@react-navigation/core@5.0.0-alpha.13) (2019-10-03)
**Note:** Version bump only for package @react-navigation/core
# [5.0.0-alpha.12](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.11...@react-navigation/core@5.0.0-alpha.12) (2019-10-03)
### Bug Fixes
* don't merge state with existing state during reset. fixes [#111](https://github.com/react-navigation/navigation-ex/issues/111) ([7393464](https://github.com/react-navigation/navigation-ex/commit/7393464))
* don't throw when switching navigator. fixes [#91](https://github.com/react-navigation/navigation-ex/issues/91) ([19be2b4](https://github.com/react-navigation/navigation-ex/commit/19be2b4))
### Features
* add a getRootState method ([#119](https://github.com/react-navigation/navigation-ex/issues/119)) ([7a5bcb4](https://github.com/react-navigation/navigation-ex/commit/7a5bcb4))
# [5.0.0-alpha.11](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.10...@react-navigation/core@5.0.0-alpha.11) (2019-09-27)

View File

@@ -15,7 +15,7 @@ yarn add @react-navigation/core
A basic custom navigator bundling a router and a view looks like this:
```js
import { useNavigationBuilder } from '@react-navigation/core';
import { createNavigatorFactory, useNavigationBuilder } from '@react-navigation/core';
import { StackRouter } from '@react-navigation/routers';
function StackNavigator({ initialRouteName, children, ...rest }) {
@@ -34,5 +34,5 @@ function StackNavigator({ initialRouteName, children, ...rest }) {
);
}
export default createNavigator(StackNavigator);
export default createNavigatorFactory(StackNavigator);
```

View File

@@ -6,7 +6,7 @@
"react-native",
"react-navigation"
],
"version": "5.0.0-alpha.11",
"version": "5.0.0-alpha.26",
"license": "MIT",
"repository": {
"type": "git",
@@ -30,23 +30,23 @@
},
"dependencies": {
"escape-string-regexp": "^2.0.0",
"query-string": "^6.8.3",
"shortid": "^2.2.14",
"use-subscription": "^1.0.0"
"query-string": "^6.9.0",
"shortid": "^2.2.15",
"use-subscription": "^1.3.0"
},
"devDependencies": {
"@babel/core": "^7.4.5",
"@babel/core": "^7.7.2",
"@react-native-community/bob": "^0.7.0",
"@types/react": "^16.8.19",
"@types/react": "^16.9.11",
"@types/shortid": "^0.0.29",
"del-cli": "^2.0.0",
"react": "^16.8.3",
"del-cli": "^3.0.0",
"react": "~16.8.3",
"react-native-testing-library": "^1.9.1",
"react-test-renderer": "16.8.3",
"typescript": "^3.5.3"
"react-test-renderer": "~16.8.3",
"typescript": "^3.7.2"
},
"peerDependencies": {
"react": "^16.8.3"
"react": "~16.8.3"
},
"@react-native-community/bob": {
"source": "src",

View File

@@ -1,5 +1,5 @@
import shortid from 'shortid';
import { CommonAction, NavigationState } from './types';
import { CommonAction, NavigationState, PartialState } from './types';
/**
* Base router object that can be used when writing custom routers.
@@ -9,7 +9,7 @@ const BaseRouter = {
getStateForAction<State extends NavigationState>(
state: State,
action: CommonAction
): State | null {
): State | PartialState<State> | null {
switch (action.type) {
case 'REPLACE': {
const index = action.source
@@ -22,6 +22,10 @@ const BaseRouter = {
const { name, key, params } = action.payload;
if (!state.routeNames.includes(name)) {
return null;
}
return {
...state,
routes: state.routes.map((route, i) =>
@@ -56,12 +60,7 @@ const BaseRouter = {
}
case 'RESET':
return {
...state,
...action.payload,
key: state.key,
routeNames: state.routeNames,
};
return action.payload as PartialState<State>;
default:
return null;

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line import/no-cycle
import { NavigationState, PartialState } from './types';
export type Action =

View File

@@ -18,25 +18,30 @@ export const SingleNavigatorContext = React.createContext<
* Component which ensures that there's only one navigator nested under it.
*/
export default function EnsureSingleNavigator({ children }: Props) {
const [currentKey, setCurrentKey] = React.useState<string | undefined>();
const navigatorKeyRef = React.useRef<string | undefined>();
const value = React.useMemo(
() => ({
register(key: string) {
const currentKey = navigatorKeyRef.current;
if (currentKey !== undefined && key !== currentKey) {
throw new Error(MULTIPLE_NAVIGATOR_ERROR);
}
setCurrentKey(key);
navigatorKeyRef.current = key;
},
unregister(key: string) {
if (currentKey !== undefined && key !== currentKey) {
throw new Error(MULTIPLE_NAVIGATOR_ERROR);
const currentKey = navigatorKeyRef.current;
if (key !== currentKey) {
return;
}
setCurrentKey(undefined);
navigatorKeyRef.current = undefined;
},
}),
[currentKey]
[]
);
return (

View File

@@ -1,5 +1,10 @@
import * as React from 'react';
import { NavigationAction, NavigationHelpers, ParamListBase } from './types';
import {
NavigationAction,
NavigationHelpers,
NavigationState,
ParamListBase,
} from './types';
export type ChildActionListener = (
action: NavigationAction,
@@ -14,6 +19,8 @@ export type FocusedNavigationListener = <T>(
callback: FocusedNavigationCallback<T>
) => { handled: boolean; result: T };
export type NavigatorStateGetter = () => NavigationState;
/**
* Context which holds the required helpers needed to build nested navigators.
*/
@@ -25,6 +32,7 @@ const NavigationBuilderContext = React.createContext<{
addActionListener?: (listener: ChildActionListener) => void;
addFocusedListener?: (listener: FocusedNavigationListener) => void;
onRouteFocus?: (key: string) => void;
addStateGetter?: (key: string, getter: NavigatorStateGetter) => void;
trackAction: (action: NavigationAction) => void;
}>({
trackAction: () => undefined,

View File

@@ -5,6 +5,7 @@ import NavigationBuilderContext from './NavigationBuilderContext';
import ResetRootContext from './ResetRootContext';
import useFocusedListeners from './useFocusedListeners';
import useDevTools from './useDevTools';
import useStateGetters from './useStateGetters';
import {
Route,
@@ -22,12 +23,17 @@ const MISSING_CONTEXT_ERROR =
"We couldn't find a navigation context. Have you wrapped your app with 'NavigationContainer'?";
export const NavigationStateContext = React.createContext<{
isDefault?: true;
state?: NavigationState | PartialState<NavigationState>;
getState: () => NavigationState | PartialState<NavigationState> | undefined;
setState: (state: NavigationState | undefined) => void;
setState: (
state: NavigationState | PartialState<NavigationState> | undefined
) => void;
key?: string;
performTransaction: (action: () => void) => void;
}>({
isDefault: true,
get getState(): any {
throw new Error(MISSING_CONTEXT_ERROR);
},
@@ -80,11 +86,24 @@ const getPartialState = (
* @param props.ref Ref object which refers to the navigation object containing helper methods.
*/
const Container = React.forwardRef(function NavigationContainer(
{ initialState, onStateChange, children }: NavigationContainerProps,
{
initialState,
onStateChange,
independent,
children,
}: NavigationContainerProps,
ref: React.Ref<NavigationContainerRef>
) {
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."
);
}
const [state, setNavigationState] = React.useState<State>(() =>
getPartialState(initialState)
getPartialState(initialState == null ? undefined : initialState)
);
const navigationStateRef = React.useRef<State>();
@@ -93,72 +112,6 @@ const Container = React.forwardRef(function NavigationContainer(
const isFirstMountRef = React.useRef<boolean>(true);
const skipTrackingRef = React.useRef<boolean>(false);
const reset = React.useCallback((state: NavigationState) => {
skipTrackingRef.current = true;
setNavigationState(state);
}, []);
const { trackState, trackAction } = useDevTools({
name: '@react-navigation',
reset,
state,
});
const { listeners, addListener: addFocusedListener } = useFocusedListeners();
const dispatch = (
action: NavigationAction | ((state: NavigationState) => NavigationAction)
) => {
listeners[0](navigation => navigation.dispatch(action));
};
const canGoBack = () => {
const { result, handled } = listeners[0](navigation =>
navigation.canGoBack()
);
if (handled) {
return result;
} else {
return false;
}
};
const resetRoot = React.useCallback(
(state: PartialState<NavigationState> | NavigationState) => {
trackAction('@@RESET_ROOT');
setNavigationState(state);
},
[trackAction]
);
React.useImperativeHandle(ref, () => ({
...(Object.keys(CommonActions) as Array<keyof typeof CommonActions>).reduce<
any
>((acc, name) => {
acc[name] = (...args: any[]) =>
dispatch(
// eslint-disable-next-line import/namespace
CommonActions[name](
// @ts-ignore
...args
)
);
return acc;
}, {}),
resetRoot,
dispatch,
canGoBack,
}));
const builderContext = React.useMemo(
() => ({
addFocusedListener,
trackAction,
}),
[addFocusedListener, trackAction]
);
const performTransaction = React.useCallback((callback: () => void) => {
if (isTransactionActiveRef.current) {
throw new Error(
@@ -196,6 +149,86 @@ const Container = React.forwardRef(function NavigationContainer(
transactionStateRef.current = navigationState;
}, []);
const reset = React.useCallback(
(state: NavigationState) => {
performTransaction(() => {
skipTrackingRef.current = true;
setState(state);
});
},
[performTransaction, setState]
);
const { trackState, trackAction } = useDevTools({
name: '@react-navigation',
reset,
state,
});
const { listeners, addListener: addFocusedListener } = useFocusedListeners();
const { getStateForRoute, addStateGetter } = useStateGetters();
const dispatch = (
action: NavigationAction | ((state: NavigationState) => NavigationAction)
) => {
listeners[0](navigation => navigation.dispatch(action));
};
const canGoBack = () => {
const { result, handled } = listeners[0](navigation =>
navigation.canGoBack()
);
if (handled) {
return result;
} else {
return false;
}
};
const resetRoot = React.useCallback(
(state?: PartialState<NavigationState> | NavigationState) => {
performTransaction(() => {
trackAction('@@RESET_ROOT');
setState(state);
});
},
[performTransaction, setState, trackAction]
);
const getRootState = React.useCallback(() => {
return getStateForRoute('root');
}, [getStateForRoute]);
React.useImperativeHandle(ref, () => ({
...(Object.keys(CommonActions) as Array<keyof typeof CommonActions>).reduce<
any
>((acc, name) => {
acc[name] = (...args: any[]) =>
dispatch(
CommonActions[name](
// @ts-ignore
...args
)
);
return acc;
}, {}),
resetRoot,
dispatch,
canGoBack,
getRootState,
}));
const builderContext = React.useMemo(
() => ({
addFocusedListener,
addStateGetter,
trackAction,
}),
[addFocusedListener, trackAction, addStateGetter]
);
const context = React.useMemo(
() => ({
state,
@@ -210,18 +243,18 @@ const Container = React.forwardRef(function NavigationContainer(
if (skipTrackingRef.current) {
skipTrackingRef.current = false;
} else {
trackState(state);
trackState(getRootState);
}
navigationStateRef.current = state;
transactionStateRef.current = null;
if (!isFirstMountRef.current && onStateChange) {
onStateChange(state);
onStateChange(getRootState());
}
isFirstMountRef.current = false;
}, [state, onStateChange, trackState]);
}, [state, onStateChange, trackState, getRootState]);
return (
<NavigationBuilderContext.Provider value={builderContext}>

View File

@@ -47,7 +47,7 @@ export default function SceneView<
}, [getState, route.key]);
const setCurrentState = React.useCallback(
(child: NavigationState | undefined) => {
(child: NavigationState | PartialState<NavigationState> | undefined) => {
const state = getState();
setState({
@@ -90,8 +90,10 @@ export default function SceneView<
route={route}
>
{'component' in screen && screen.component !== undefined ? (
// @ts-ignore
<screen.component navigation={navigation} route={route} />
) : 'children' in screen && screen.children !== undefined ? (
// @ts-ignore
screen.children({ navigation, route })
) : null}
</StaticContainer>

View File

@@ -4,7 +4,8 @@ import * as CommonActions from '../CommonActions';
jest.mock('shortid', () => () => 'test');
const STATE = {
stale: false as false,
stale: false as const,
type: 'test',
key: 'root',
index: 1,
routes: [
@@ -23,6 +24,7 @@ it('replaces focused screen with REPLACE', () => {
expect(result).toEqual({
stale: false,
type: 'test',
key: 'root',
index: 1,
routes: [
@@ -42,6 +44,7 @@ it('replaces source screen with REPLACE', () => {
expect(result).toEqual({
stale: false,
type: 'test',
key: 'root',
index: 1,
routes: [
@@ -62,6 +65,15 @@ it("doesn't handle REPLACE if source key isn't present", () => {
expect(result).toBe(null);
});
it("doesn't handle REPLACE if screen to replace with isn't present", () => {
const result = BaseRouter.getStateForAction(
STATE,
CommonActions.replace('nonexistent', { answer: 42 })
);
expect(result).toBe(null);
});
it('sets params for the focused screen with SET_PARAMS', () => {
const result = BaseRouter.getStateForAction(
STATE,
@@ -70,6 +82,7 @@ it('sets params for the focused screen with SET_PARAMS', () => {
expect(result).toEqual({
stale: false,
type: 'test',
key: 'root',
index: 1,
routes: [
@@ -89,6 +102,7 @@ it('sets params for the source screen with SET_PARAMS', () => {
expect(result).toEqual({
stale: false,
type: 'test',
key: 'root',
index: 1,
routes: [
@@ -125,15 +139,5 @@ it('resets state to new state with RESET', () => {
})
);
expect(result).toEqual({ ...STATE, index: 0, routes });
});
it('ignores key and routeNames when resetting with RESET', () => {
const result = BaseRouter.getStateForAction(
STATE,
// @ts-ignore
CommonActions.reset({ index: 2, key: 'foo', routeNames: ['test'] })
);
expect(result).toEqual({ ...STATE, index: 2 });
expect(result).toEqual({ index: 0, routes });
});

View File

@@ -122,6 +122,32 @@ it('throws when nesting performTransaction', () => {
);
});
it('throws when nesting containers', () => {
expect(() =>
render(
<NavigationContainer>
<NavigationContainer>
<React.Fragment />
</NavigationContainer>
</NavigationContainer>
)
).toThrowError(
"Looks like you have nested a 'NavigationContainer' inside another."
);
expect(() =>
render(
<NavigationContainer>
<NavigationContainer independent>
<React.Fragment />
</NavigationContainer>
</NavigationContainer>
)
).not.toThrowError(
"Looks like you have nested a 'NavigationContainer' inside another."
);
});
it('handle dispatching with ref', () => {
const CurrentParentRouter = MockRouter;
@@ -137,14 +163,14 @@ it('handle dispatching with ref', () => {
return true;
},
getStateForAction(state, action) {
getStateForAction(state, action, options) {
if (action.type === 'REVERSE') {
return {
...state,
routes: state.routes.slice().reverse(),
};
}
return CurrentMockRouter.getStateForAction(state, action);
return CurrentMockRouter.getStateForAction(state, action, options);
},
};
return ChildRouter;
@@ -186,7 +212,10 @@ it('handle dispatching with ref', () => {
index: 0,
key: '4',
routeNames: ['qux', 'lex'],
routes: [{ key: 'qux', name: 'qux' }, { key: 'lex', name: 'lex' }],
routes: [
{ key: 'qux', name: 'qux' },
{ key: 'lex', name: 'lex' },
],
},
},
{ key: 'bar', name: 'bar' },
@@ -233,6 +262,7 @@ it('handle dispatching with ref', () => {
expect(onStateChange).toBeCalledTimes(1);
expect(onStateChange).lastCalledWith({
stale: false,
type: 'test',
index: 0,
key: '0',
routeNames: ['foo', 'foo2', 'bar', 'baz'],
@@ -242,10 +272,14 @@ it('handle dispatching with ref', () => {
name: 'baz',
state: {
stale: false,
type: 'test',
index: 0,
key: '1',
routeNames: ['qux', 'lex'],
routes: [{ key: 'lex', name: 'lex' }, { key: 'qux', name: 'qux' }],
routes: [
{ key: 'lex', name: 'lex' },
{ key: 'qux', name: 'qux' },
],
},
},
{ key: 'bar', name: 'bar' },
@@ -305,7 +339,10 @@ it('handle resetting state with ref', () => {
index: 0,
key: '4',
routeNames: ['qux', 'lex'],
routes: [{ key: 'qux', name: 'qux' }, { key: 'lex', name: 'lex' }],
routes: [
{ key: 'qux', name: 'qux' },
{ key: 'lex', name: 'lex' },
],
},
},
{ key: 'bar', name: 'bar' },
@@ -319,5 +356,87 @@ it('handle resetting state with ref', () => {
});
expect(onStateChange).toBeCalledTimes(1);
expect(onStateChange).lastCalledWith(state);
expect(onStateChange).lastCalledWith({
index: 1,
key: '5',
routeNames: ['foo', 'foo2', 'bar', 'baz'],
routes: [
{
key: 'baz',
name: 'baz',
state: {
index: 0,
key: '6',
routeNames: ['qux', 'lex'],
routes: [
{ key: 'qux', name: 'qux' },
{ key: 'lex', name: 'lex' },
],
stale: false,
type: 'test',
},
},
{ key: 'bar', name: 'bar' },
],
stale: false,
type: 'test',
});
});
it('handle getRootState', () => {
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return descriptors[state.routes[state.index].key].render();
};
const ref = React.createRef<NavigationContainerRef>();
const element = (
<NavigationContainer ref={ref}>
<TestNavigator initialRouteName="foo">
<Screen name="foo">
{() => (
<TestNavigator>
<Screen name="qux" component={() => null} />
<Screen name="lex" component={() => null} />
</TestNavigator>
)}
</Screen>
<Screen name="bar" component={() => null} />
</TestNavigator>
</NavigationContainer>
);
render(element);
let state;
if (ref.current) {
state = ref.current.getRootState();
}
expect(state).toEqual({
index: 0,
key: '7',
routeNames: ['foo', 'bar'],
routes: [
{
key: 'foo',
name: 'foo',
state: {
index: 0,
key: '8',
routeNames: ['qux', 'lex'],
routes: [
{ key: 'qux', name: 'qux' },
{ key: 'lex', name: 'lex' },
],
stale: false,
type: 'test',
},
},
{ key: 'bar', name: 'bar' },
],
stale: false,
type: 'test',
});
});

View File

@@ -13,6 +13,8 @@ export const MockRouterKey = { current: 0 };
export default function MockRouter(options: DefaultRouterOptions) {
const router: Router<NavigationState, MockActions> = {
type: 'test',
getInitialState({ routeNames, routeParamList }) {
const index =
options.initialRouteName === undefined
@@ -21,6 +23,7 @@ export default function MockRouter(options: DefaultRouterOptions) {
return {
stale: false,
type: 'test',
key: String(MockRouterKey.current++),
index,
routeNames,
@@ -58,6 +61,7 @@ export default function MockRouter(options: DefaultRouterOptions) {
return {
stale: false,
type: 'test',
key: String(MockRouterKey.current++),
index:
typeof state.index === 'number' && state.index < routes.length
@@ -108,7 +112,24 @@ export default function MockRouter(options: DefaultRouterOptions) {
return null;
}
return { ...state, index };
return {
...state,
index,
routes:
action.payload.params !== undefined
? state.routes.map((route, i) =>
i === index
? {
...route,
params: {
...route.params,
...action.payload.params,
},
}
: route
)
: state.routes,
};
}
default:

View File

@@ -5,7 +5,7 @@ import NavigationContainer from '../NavigationContainer';
import useNavigationBuilder from '../useNavigationBuilder';
import useNavigation from '../useNavigation';
import MockRouter, { MockRouterKey } from './__fixtures__/MockRouter';
import { NavigationState } from '../types';
import { NavigationState, NavigationContainerRef } from '../types';
beforeEach(() => (MockRouterKey.current = 0));
@@ -52,6 +52,7 @@ it('initializes state for a navigator on navigation', () => {
expect(onStateChange).toBeCalledTimes(1);
expect(onStateChange).toBeCalledWith({
stale: false,
type: 'test',
index: 0,
key: '0',
routeNames: ['foo', 'bar', 'baz'],
@@ -63,6 +64,27 @@ it('initializes state for a navigator on navigation', () => {
});
});
it("doesn't crash when initialState is null", () => {
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return descriptors[state.routes[state.index].key].render();
};
const TestScreen = () => null;
const element = (
// @ts-ignore
<NavigationContainer initialState={null}>
<TestNavigator>
<Screen name="foo" component={TestScreen} />
</TestNavigator>
</NavigationContainer>
);
expect(() => render(element)).not.toThrowError();
});
it('rehydrates state for a navigator on navigation', () => {
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
@@ -81,7 +103,10 @@ it('rehydrates state for a navigator on navigation', () => {
const initialState = {
index: 1,
routes: [{ key: 'foo', name: 'foo' }, { key: 'bar', name: 'bar' }],
routes: [
{ key: 'foo', name: 'foo' },
{ key: 'bar', name: 'bar' },
],
};
const onStateChange = jest.fn();
@@ -104,8 +129,74 @@ it('rehydrates state for a navigator on navigation', () => {
index: 1,
key: '0',
routeNames: ['foo', 'bar'],
routes: [{ key: 'foo', name: 'foo' }, { key: 'bar', name: 'bar' }],
routes: [
{ key: 'foo', name: 'foo' },
{ key: 'bar', name: 'bar' },
],
stale: false,
type: 'test',
});
});
it("doesn't rehydrate state if the type of state didn't match router", () => {
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return descriptors[state.routes[state.index].key].render();
};
const FooScreen = (props: any) => {
React.useEffect(() => {
props.navigation.dispatch({ type: 'UPDATE' });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return null;
};
const initialState = {
index: 1,
type: 'something-else',
routes: [
{ key: 'foo', name: 'foo' },
{ key: 'bar', name: 'bar' },
],
};
const onStateChange = jest.fn();
const element = (
<NavigationContainer
initialState={initialState}
onStateChange={onStateChange}
>
<TestNavigator initialRouteName="foo">
<Screen
name="foo"
component={FooScreen}
initialParams={{ answer: 42 }}
/>
<Screen name="bar" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
);
render(element).update(element);
expect(onStateChange).lastCalledWith({
index: 0,
key: '0',
routeNames: ['foo', 'bar'],
routes: [
{
key: 'foo',
name: 'foo',
params: { answer: 42 },
},
{ key: 'bar', name: 'bar' },
],
stale: false,
type: 'test',
});
});
@@ -144,6 +235,7 @@ it('initializes state for nested screens in React.Fragment', () => {
expect(onStateChange).toBeCalledTimes(1);
expect(onStateChange).toBeCalledWith({
stale: false,
type: 'test',
index: 0,
key: '0',
routeNames: ['foo', 'bar', 'baz'],
@@ -194,6 +286,7 @@ it('initializes state for nested navigator on navigation', () => {
expect(onStateChange).toBeCalledTimes(1);
expect(onStateChange).toBeCalledWith({
stale: false,
type: 'test',
index: 2,
key: '0',
routeNames: ['foo', 'bar', 'baz'],
@@ -205,6 +298,7 @@ it('initializes state for nested navigator on navigation', () => {
name: 'baz',
state: {
stale: false,
type: 'test',
index: 0,
key: '1',
routeNames: ['qux'],
@@ -309,10 +403,14 @@ it('cleans up state when the navigator unmounts', () => {
expect(onStateChange).toBeCalledTimes(1);
expect(onStateChange).lastCalledWith({
stale: false,
type: 'test',
index: 0,
key: '0',
routeNames: ['foo', 'bar'],
routes: [{ key: 'foo', name: 'foo' }, { key: 'bar', name: 'bar' }],
routes: [
{ key: 'foo', name: 'foo' },
{ key: 'bar', name: 'bar' },
],
});
root.update(
@@ -361,10 +459,14 @@ it('allows state updates by dispatching a function returning an action', () => {
expect(onStateChange).toBeCalledTimes(1);
expect(onStateChange).toBeCalledWith({
stale: false,
type: 'test',
index: 1,
key: '0',
routeNames: ['foo', 'bar'],
routes: [{ key: 'foo', name: 'foo' }, { key: 'bar', name: 'bar' }],
routes: [
{ key: 'foo', name: 'foo' },
{ key: 'bar', name: 'bar' },
],
});
});
@@ -399,6 +501,7 @@ it('updates route params with setParams', () => {
expect(onStateChange).toBeCalledTimes(1);
expect(onStateChange).lastCalledWith({
stale: false,
type: 'test',
index: 0,
key: '0',
routeNames: ['foo', 'bar'],
@@ -413,6 +516,7 @@ it('updates route params with setParams', () => {
expect(onStateChange).toBeCalledTimes(2);
expect(onStateChange).lastCalledWith({
stale: false,
type: 'test',
index: 0,
key: '0',
routeNames: ['foo', 'bar'],
@@ -466,10 +570,23 @@ it('updates route params with setParams applied to parent', () => {
key: '0',
routeNames: ['foo', 'bar'],
routes: [
{ key: 'foo', name: 'foo', params: { username: 'alice' } },
{
key: 'foo',
name: 'foo',
params: { username: 'alice' },
state: {
index: 0,
key: '1',
routeNames: ['baz'],
routes: [{ key: 'baz', name: 'baz' }],
stale: false,
type: 'test',
},
},
{ key: 'bar', name: 'bar' },
],
stale: false,
type: 'test',
});
act(() => setParams({ age: 25 }));
@@ -480,10 +597,23 @@ it('updates route params with setParams applied to parent', () => {
key: '0',
routeNames: ['foo', 'bar'],
routes: [
{ key: 'foo', name: 'foo', params: { username: 'alice', age: 25 } },
{
key: 'foo',
name: 'foo',
params: { username: 'alice', age: 25 },
state: {
index: 0,
key: '1',
routeNames: ['baz'],
routes: [{ key: 'baz', name: 'baz' }],
stale: false,
type: 'test',
},
},
{ key: 'bar', name: 'bar' },
],
stale: false,
type: 'test',
});
});
@@ -516,6 +646,7 @@ it('handles change in route names', () => {
expect(onStateChange).toBeCalledWith({
stale: false,
type: 'test',
index: 0,
key: '0',
routeNames: ['foo', 'baz', 'qux'],
@@ -523,6 +654,80 @@ it('handles change in route names', () => {
});
});
it('navigates to 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 = React.createRef<NavigationContainerRef>();
const element = render(
<NavigationContainer 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}
initialParams={{ lol: 'why' }}
/>
<Screen
name="bar-b"
component={TestComponent}
initialParams={{ some: 'stuff' }}
/>
</TestNavigator>
)}
</Screen>
</TestNavigator>
</NavigationContainer>
);
expect(element).toMatchInlineSnapshot(`"[foo-a, undefined]"`);
act(
() =>
navigation.current &&
navigation.current.navigate('bar', {
screen: 'bar-b',
params: { test: 42 },
})
);
expect(element).toMatchInlineSnapshot(
`"[bar-b, {\\"some\\":\\"stuff\\",\\"test\\":42}]"`
);
act(
() =>
navigation.current &&
navigation.current.navigate('bar', {
screen: 'bar-a',
params: { whoa: 'test' },
})
);
expect(element).toMatchInlineSnapshot(
`"[bar-a, {\\"lol\\":\\"why\\",\\"whoa\\":\\"test\\"}]"`
);
});
it('gives access to internal state', () => {
const TestNavigator = (props: any): any => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
@@ -554,6 +759,7 @@ it('gives access to internal state', () => {
routeNames: ['bar'],
routes: [{ key: 'bar', name: 'bar' }],
stale: false,
type: 'test',
});
});
@@ -655,6 +861,7 @@ it('throws when a React Element is not the direct children', () => {
);
});
// eslint-disable-next-line jest/expect-expect
it("doesn't throw when direct children is Screen or empty element", () => {
const TestNavigator = (props: any) => {
useNavigationBuilder(MockRouter, props);
@@ -694,3 +901,30 @@ it('throws when multiple screens with same name are defined', () => {
"A navigator cannot contain multiple 'Screen' components with the same name (found duplicate screen named 'foo')"
);
});
it('switches rendered navigators', () => {
const TestNavigator = (props: any) => {
useNavigationBuilder(MockRouter, props);
return null;
};
const root = render(
<NavigationContainer>
<TestNavigator key="a">
<Screen name="foo" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
);
expect(() =>
root.update(
<NavigationContainer>
<TestNavigator key="b">
<Screen name="foo" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
)
).not.toThrowError(
'Another navigator is already registered for this container.'
);
});

View File

@@ -419,12 +419,12 @@ it(`returns false for canGoBack when current router doesn't handle GO_BACK`, ()
const ChildRouter: Router<NavigationState, MockActions> = {
...CurrentMockRouter,
getStateForAction(state, action) {
getStateForAction(state, action, options) {
if (action.type === 'GO_BACK') {
return null;
}
return CurrentMockRouter.getStateForAction(state, action);
return CurrentMockRouter.getStateForAction(state, action, options);
},
};
return ChildRouter;
@@ -470,12 +470,12 @@ it('returns true for canGoBack when current router handles GO_BACK', () => {
const ChildRouter: Router<NavigationState, MockActions> = {
...CurrentMockRouter,
getStateForAction(state, action) {
getStateForAction(state, action, options) {
if (action.type === 'GO_BACK') {
return state;
}
return CurrentMockRouter.getStateForAction(state, action);
return CurrentMockRouter.getStateForAction(state, action, options);
},
};
return ChildRouter;
@@ -537,12 +537,12 @@ it('returns true for canGoBack when parent router handles GO_BACK', () => {
const ChildRouter: Router<NavigationState, MockActions> = {
...CurrentMockRouter,
getStateForAction(state, action) {
getStateForAction(state, action, options) {
if (action.type === 'GO_BACK') {
return state;
}
return CurrentMockRouter.getStateForAction(state, action);
return CurrentMockRouter.getStateForAction(state, action, options);
},
};
return ChildRouter;

View File

@@ -216,6 +216,7 @@ it('fires blur event when a route is removed with a delay', async () => {
return {
stale: false,
type: 'test',
key: 'stack',
index: 0,
routeNames,
@@ -229,7 +230,7 @@ it('fires blur event when a route is removed with a delay', async () => {
};
},
getStateForAction(state, action) {
getStateForAction(state, action, options) {
switch (action.type) {
case 'PUSH':
return {
@@ -247,7 +248,7 @@ it('fires blur event when a route is removed with a delay', async () => {
};
}
default:
return router.getStateForAction(state, action);
return router.getStateForAction(state, action, options);
}
},
@@ -273,7 +274,11 @@ it('fires blur event when a route is removed with a delay', async () => {
const [previous, dispatch] = React.useReducer(
(state, action) => {
return { ...state, ...action };
if (state.routes !== action.routes) {
return { ...state, ...action };
}
return state;
},
{ routes: state.routes, descriptors }
);

View File

@@ -20,7 +20,7 @@ it("lets parent handle the action if child didn't", () => {
> = {
...CurrentMockRouter,
getStateForAction(state, action) {
getStateForAction(state, action, options) {
if (action.type === 'REVERSE') {
return {
...state,
@@ -28,7 +28,7 @@ it("lets parent handle the action if child didn't", () => {
};
}
return CurrentMockRouter.getStateForAction(state, action);
return CurrentMockRouter.getStateForAction(state, action, options);
},
};
return ParentRouter;
@@ -76,6 +76,7 @@ it("lets parent handle the action if child didn't", () => {
expect(onStateChange).toBeCalledTimes(1);
expect(onStateChange).lastCalledWith({
stale: false,
type: 'test',
index: 2,
key: '0',
routeNames: ['foo', 'bar', 'baz'],
@@ -102,14 +103,14 @@ it("lets children handle the action if parent didn't", () => {
return true;
},
getStateForAction(state, action) {
getStateForAction(state, action, options) {
if (action.type === 'REVERSE') {
return {
...state,
routes: state.routes.slice().reverse(),
};
}
return CurrentMockRouter.getStateForAction(state, action);
return CurrentMockRouter.getStateForAction(state, action, options);
},
};
return ChildRouter;
@@ -159,7 +160,10 @@ it("lets children handle the action if parent didn't", () => {
index: 0,
key: '4',
routeNames: ['qux', 'lex'],
routes: [{ key: 'qux', name: 'qux' }, { key: 'lex', name: 'lex' }],
routes: [
{ key: 'qux', name: 'qux' },
{ key: 'lex', name: 'lex' },
],
},
},
{ key: 'bar', name: 'bar' },
@@ -191,6 +195,7 @@ it("lets children handle the action if parent didn't", () => {
expect(onStateChange).toBeCalledTimes(1);
expect(onStateChange).lastCalledWith({
stale: false,
type: 'test',
index: 0,
key: '0',
routeNames: ['foo', 'bar', 'baz'],
@@ -200,10 +205,14 @@ it("lets children handle the action if parent didn't", () => {
name: 'baz',
state: {
stale: false,
type: 'test',
index: 0,
key: '1',
routeNames: ['qux', 'lex'],
routes: [{ key: 'lex', name: 'lex' }, { key: 'qux', name: 'qux' }],
routes: [
{ key: 'lex', name: 'lex' },
{ key: 'qux', name: 'qux' },
],
},
},
{ key: 'bar', name: 'bar' },
@@ -226,7 +235,7 @@ it("action doesn't bubble if target is specified", () => {
return true;
},
getStateForAction(state, action) {
getStateForAction(state, action, options) {
if (action.type === 'REVERSE') {
return {
...state,
@@ -234,7 +243,7 @@ it("action doesn't bubble if target is specified", () => {
};
}
return CurrentMockRouter.getStateForAction(state, action);
return CurrentMockRouter.getStateForAction(state, action, options);
},
};
return ChildRouter;
@@ -296,6 +305,7 @@ it("action doesn't bubble if target is specified", () => {
expect(onStateChange).not.toBeCalled();
});
// eslint-disable-next-line jest/expect-expect
it("doesn't crash if no navigator handled the action", () => {
const TestRouter = MockRouter;
@@ -329,7 +339,10 @@ it("doesn't crash if no navigator handled the action", () => {
index: 0,
key: '4',
routeNames: ['qux', 'lex'],
routes: [{ key: 'qux', name: 'qux' }, { key: 'lex', name: 'lex' }],
routes: [
{ key: 'qux', name: 'qux' },
{ key: 'lex', name: 'lex' },
],
},
},
{ key: 'bar', name: 'bar' },

View File

@@ -9,7 +9,7 @@ import { ParamListBase, TypedNavigator } from './types';
* @param Navigator The navigtor component to wrap.
* @returns Factory method to create a `Navigator` and `Screen` pair.
*/
export default function createNavigator<
export default function createNavigatorFactory<
ScreenOptions extends object,
NavigatorComponent extends React.ComponentType<any>
>(Navigator: NavigatorComponent) {

View File

@@ -3,7 +3,7 @@ import { NavigationState, PartialState, Route } from './types';
type State = NavigationState | Omit<PartialState<NavigationState>, 'stale'>;
type StringifyConfig = { [key: string]: (value: any) => string };
type StringifyConfig = Record<string, (value: any) => string>;
type Options = {
[routeName: string]: string | { path: string; stringify?: StringifyConfig };

View File

@@ -2,7 +2,7 @@ import escape from 'escape-string-regexp';
import queryString from 'query-string';
import { NavigationState, PartialState } from './types';
type ParseConfig = { [key: string]: (value: string) => any };
type ParseConfig = Record<string, (value: string) => any>;
type Options = {
[routeName: string]: string | { path: string; parse?: ParseConfig };
@@ -78,7 +78,7 @@ export default function getStateFromPath(
.filter(p => p.startsWith(':'));
if (paramPatterns.length) {
params = paramPatterns.reduce<{ [key: string]: any }>((acc, p, i) => {
params = paramPatterns.reduce<Record<string, any>>((acc, p, i) => {
const key = p.replace(/^:/, '');
const value = match[i + 1]; // The param segments start from index 1 in the regex match result

View File

@@ -4,7 +4,7 @@ export { CommonActions };
export { default as BaseRouter } from './BaseRouter';
export { default as NavigationContainer } from './NavigationContainer';
export { default as createNavigator } from './createNavigator';
export { default as createNavigatorFactory } from './createNavigatorFactory';
export { default as NavigationContext } from './NavigationContext';
export { default as NavigationRouteContext } from './NavigationRouteContext';

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line import/no-cycle
import * as CommonActions from './CommonActions';
import * as React from 'react';
@@ -22,6 +23,12 @@ export type NavigationState = {
routes: Array<
Route<string> & { state?: NavigationState | PartialState<NavigationState> }
>;
/**
* Custom type for the state, whether it's for tab, stack, drawer etc.
* During rehydration, the state will be discarded if type doesn't match with router type.
* It can also be used to detect the type of the navigator we're dealing with.
*/
type: string;
/**
* Whether the navigation state has been rehydrated.
*/
@@ -35,9 +42,10 @@ export type InitialState = Partial<
};
export type PartialState<State extends NavigationState> = Partial<
Omit<State, 'stale' | 'key' | 'routes' | 'routeNames'>
Omit<State, 'stale' | 'type' | 'key' | 'routes' | 'routeNames'>
> & {
stale?: true;
type?: string;
routes: Array<
Omit<Route<string>, 'key'> & { key?: string; state?: InitialState }
>;
@@ -110,20 +118,28 @@ export type RouterFactory<
RouterOptions extends DefaultRouterOptions
> = (options: RouterOptions) => Router<State, Action>;
export type RouterConfigOptions = {
routeNames: string[];
routeParamList: ParamListBase;
};
export type Router<
State extends NavigationState,
Action extends NavigationAction
> = {
/**
* Type of the router. Should match the `type` property in state.
* If the type doesn't match, the state will be discarded during rehydration.
*/
type: State['type'];
/**
* Initialize the navigation state.
*
* @param options.routeNames List of valid route names as defined in the screen components.
* @param options.routeParamsList Object containing params for each route.
*/
getInitialState(options: {
routeNames: string[];
routeParamList: ParamListBase;
}): State;
getInitialState(options: RouterConfigOptions): State;
/**
* Rehydrate the full navigation state from a given partial state.
@@ -134,10 +150,7 @@ export type Router<
*/
getRehydratedState(
partialState: PartialState<State> | State,
options: {
routeNames: string[];
routeParamList: ParamListBase;
}
options: RouterConfigOptions
): State;
/**
@@ -149,10 +162,7 @@ export type Router<
*/
getStateForRouteNamesChange(
state: State,
options: {
routeNames: string[];
routeParamList: ParamListBase;
}
options: RouterConfigOptions
): State;
/**
@@ -169,8 +179,14 @@ export type Router<
*
* @param state State object to apply the action on.
* @param action Action object to apply.
* @param options.routeNames List of valid route names as defined in the screen components.
* @param options.routeParamsList Object containing params for each route.
*/
getStateForAction(state: State, action: Action): State | null;
getStateForAction(
state: State,
action: Action,
options: RouterConfigOptions
): State | PartialState<State> | null;
/**
* Whether the action should also change focus in parent navigator
@@ -185,7 +201,7 @@ export type Router<
actionCreators?: ActionCreators<Action>;
};
export type ParamListBase = { [key: string]: object | undefined };
export type ParamListBase = Record<string, object | undefined>;
export type EventMapBase = {
focus: undefined;
@@ -211,7 +227,7 @@ export type EventListenerCallback<EventName extends string, Data> = (
e: EventArg<EventName, Data>
) => void;
export type EventConsumer<EventMap extends { [key: string]: any }> = {
export type EventConsumer<EventMap extends Record<string, any>> = {
/**
* Subscribe to events from the parent navigator.
*
@@ -228,7 +244,7 @@ export type EventConsumer<EventMap extends { [key: string]: any }> = {
): void;
};
export type EventEmitter<EventMap extends { [key: string]: any }> = {
export type EventEmitter<EventMap extends Record<string, any>> = {
/**
* Emit an event to child screens.
*
@@ -281,7 +297,7 @@ type NavigationHelpersCommon<
* @param [params] Params object for the route.
*/
navigate<RouteName extends keyof ParamList>(
...args: ParamList[RouteName] extends (undefined | any)
...args: ParamList[RouteName] extends undefined | any
? [RouteName] | [RouteName, ParamList[RouteName]]
: [RouteName, ParamList[RouteName]]
): void;
@@ -321,7 +337,7 @@ type NavigationHelpersCommon<
*
* @param state Navigation state object.
*/
resetRoot(state: PartialState<NavigationState> | NavigationState): void;
resetRoot(state?: PartialState<NavigationState> | NavigationState): void;
/**
* Go back to the previous route in history.
@@ -345,7 +361,7 @@ type NavigationHelpersCommon<
export type NavigationHelpers<
ParamList extends ParamListBase,
EventMap extends { [key: string]: any } = {}
EventMap extends Record<string, any> = {}
> = NavigationHelpersCommon<ParamList> &
EventEmitter<EventMap> & {
/**
@@ -355,15 +371,28 @@ export type NavigationHelpers<
* @param params Params object for the current route.
*/
setParams<RouteName extends keyof ParamList>(
params: ParamList[RouteName]
params: Partial<ParamList[RouteName]>
): void;
};
export type NavigationContainerProps = {
/**
* Initial navigation state for the child navigators.
*/
initialState?: InitialState;
onStateChange?: (
state: NavigationState | PartialState<NavigationState> | undefined
) => void;
/**
* Callback which is called with the latest navigation state when it changes.
*/
onStateChange?: (state: NavigationState | undefined) => void;
/**
* Whether this navigation container should be independent of parent containers.
* If this is not set to `true`, this container cannot be nested inside another container.
* Setting it to `true` disconnects any children navigators from parent container.
*/
independent?: boolean;
/**
* Children elements to render.
*/
children: React.ReactNode;
};
@@ -372,7 +401,7 @@ export type NavigationProp<
RouteName extends keyof ParamList = string,
State extends NavigationState = NavigationState,
ScreenOptions extends object = {},
EventMap extends { [key: string]: any } = {}
EventMap extends Record<string, any> = {}
> = NavigationHelpersCommon<ParamList, State> & {
/**
* Update the param object for the route.
@@ -380,7 +409,7 @@ export type NavigationProp<
*
* @param params Params object for the current route.
*/
setParams(params: ParamList[RouteName]): void;
setParams(params: Partial<ParamList[RouteName]>): void;
/**
* Update the options for the route.
@@ -390,13 +419,6 @@ export type NavigationProp<
*/
setOptions(options: Partial<ScreenOptions>): void;
/**
* Check if the screen is the first route in the navigator.
* This method returns `true` if the index of the route is `0`, `false` otherwise.
* It can be useful to decide whether to display a back button in a stack.
*/
isFirstRouteInParent(): boolean;
/**
* Returns the parent navigator, if any. Reason why the function is called
* dangerouslyGetParent is to warn developers against overusing it to eg. get parent
@@ -464,7 +486,7 @@ export type Descriptor<
RouteName extends keyof ParamList = string,
State extends NavigationState = NavigationState,
ScreenOptions extends object = {},
EventMap extends { [key: string]: any } = {}
EventMap extends Record<string, any> = {}
> = {
/**
* Render the component associated with this route.
@@ -517,24 +539,32 @@ export type RouteConfig<
/**
* React component to render for this screen.
*/
component: React.ComponentType<any>;
component: React.ComponentType<{
route: RouteProp<ParamList, RouteName>;
navigation: any;
}>;
}
| {
/**
* Render callback to render content of this screen.
*/
children: (props: any) => React.ReactNode;
});
children: (props: {
route: RouteProp<ParamList, RouteName>;
navigation: any;
}) => React.ReactNode;
}
);
export type NavigationContainerRef =
| NavigationHelpers<ParamListBase> & {
| (NavigationHelpers<ParamListBase> & {
/**
* Reset the navigation state of the root navigator to the provided state.
*
* @param state Navigation state object.
*/
resetRoot(state: PartialState<NavigationState> | NavigationState): void;
}
resetRoot(state?: PartialState<NavigationState> | NavigationState): void;
getRootState(): NavigationState;
})
| undefined
| null;

View File

@@ -3,6 +3,7 @@ import SceneView from './SceneView';
import NavigationBuilderContext, {
ChildActionListener,
FocusedNavigationListener,
NavigatorStateGetter,
} from './NavigationBuilderContext';
import { NavigationEventEmitter } from './useEventEmitter';
import useNavigationCache from './useNavigationCache';
@@ -19,7 +20,7 @@ import {
type Options<State extends NavigationState, ScreenOptions extends object> = {
state: State;
screens: { [key: string]: RouteConfig<ParamListBase, string, ScreenOptions> };
screens: Record<string, RouteConfig<ParamListBase, string, ScreenOptions>>;
navigation: NavigationHelpers<ParamListBase>;
screenOptions?:
| ScreenOptions
@@ -35,6 +36,7 @@ type Options<State extends NavigationState, ScreenOptions extends object> = {
setState: (state: State) => void;
addActionListener: (listener: ChildActionListener) => void;
addFocusedListener: (listener: FocusedNavigationListener) => void;
addStateGetter: (key: string, getter: NavigatorStateGetter) => void;
onRouteFocus: (key: string) => void;
router: Router<State, NavigationAction>;
emitter: NavigationEventEmitter;
@@ -61,11 +63,12 @@ export default function useDescriptors<
setState,
addActionListener,
addFocusedListener,
addStateGetter,
onRouteFocus,
router,
emitter,
}: Options<State, ScreenOptions>) {
const [options, setOptions] = React.useState<{ [key: string]: object }>({});
const [options, setOptions] = React.useState<Record<string, object>>({});
const { trackAction } = React.useContext(NavigationBuilderContext);
const context = React.useMemo(
@@ -74,6 +77,7 @@ export default function useDescriptors<
onAction,
addActionListener,
addFocusedListener,
addStateGetter,
onRouteFocus,
trackAction,
}),
@@ -83,6 +87,7 @@ export default function useDescriptors<
addActionListener,
addFocusedListener,
onRouteFocus,
addStateGetter,
trackAction,
]
);

View File

@@ -18,6 +18,7 @@ type DevTools = {
};
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace NodeJS {
interface Global {
__REDUX_DEVTOOLS_EXTENSION__:
@@ -61,7 +62,7 @@ export default function useDevTools({ name, reset, state }: Options) {
);
const trackState = React.useCallback(
(state: State) => {
(getState: () => State) => {
if (!devTools) {
return;
}
@@ -70,9 +71,11 @@ export default function useDevTools({ name, reset, state }: Options) {
devTools.send(actions.current.shift(), lastStateRef.current);
}
const state = getState();
if (actions.current.length) {
devTools.send(actions.current.pop(), state);
} else if (lastStateRef.current !== state) {
} else {
devTools.send('@@UNKNOWN', state);
}

View File

@@ -1,8 +1,8 @@
import * as React from 'react';
import { EventEmitter, EventConsumer, EventArg } from './types';
export type NavigationEventEmitter = EventEmitter<{ [key: string]: any }> & {
create: (target: string) => EventConsumer<{ [key: string]: any }>;
export type NavigationEventEmitter = EventEmitter<Record<string, any>> & {
create: (target: string) => EventConsumer<Record<string, any>>;
};
type Listeners = Array<(data: any) => void>;
@@ -11,9 +11,7 @@ type Listeners = Array<(data: any) => void>;
* Hook to manage the event system used by the navigator to notify screens of various events.
*/
export default function useEventEmitter(): NavigationEventEmitter {
const listeners = React.useRef<{
[key: string]: { [key: string]: Listeners };
}>({});
const listeners = React.useRef<Record<string, Record<string, Listeners>>>({});
const create = React.useCallback((target: string) => {
const removeListener = (type: string, callback: (data: any) => void) => {

View File

@@ -1,6 +1,8 @@
import * as React from 'react';
import { NavigationStateContext } from './NavigationContainer';
import NavigationRouteContext from './NavigationRouteContext';
import Screen from './Screen';
import { navigate } from './CommonActions';
import useEventEmitter from './useEventEmitter';
import useRegisterNavigator from './useRegisterNavigator';
import useDescriptors from './useDescriptors';
@@ -23,11 +25,20 @@ import {
PrivateValueStore,
NavigationAction,
} from './types';
import useStateGetters from './useStateGetters';
import useOnGetState from './useOnGetState';
// This is to make TypeScript compiler happy
// eslint-disable-next-line babel/no-unused-expressions
PrivateValueStore;
type NavigatorRoute = {
params?: {
screen?: string;
params?: object;
};
};
/**
* Compare two arrays with primitive values as the content.
* We need to make sure that both values and order match.
@@ -50,11 +61,9 @@ const getRouteConfigsFromChildren = <ScreenOptions extends object>(
if (child.type === Screen) {
// We can only extract the config from `Screen` elements
// If something else was rendered, it's probably a bug
acc.push(child.props as RouteConfig<
ParamListBase,
string,
ScreenOptions
>);
acc.push(
child.props as RouteConfig<ParamListBase, string, ScreenOptions>
);
return acc;
}
@@ -87,39 +96,66 @@ export default function useNavigationBuilder<
State extends NavigationState,
RouterOptions extends DefaultRouterOptions,
ScreenOptions extends object,
EventMap extends { [key: string]: any }
EventMap extends Record<string, any>
>(
createRouter: RouterFactory<State, any, RouterOptions>,
options: DefaultNavigatorOptions<ScreenOptions> & RouterOptions
) {
useRegisterNavigator();
const route = React.useContext(NavigationRouteContext) as
| NavigatorRoute
| undefined;
const previousRouteRef = React.useRef(route);
React.useEffect(() => {
previousRouteRef.current = route;
}, [route]);
const { children, ...rest } = options;
const { current: router } = React.useRef<Router<State, any>>(
createRouter((rest as unknown) as RouterOptions)
createRouter({
...((rest as unknown) as RouterOptions),
...(route && route.params && typeof route.params.screen === 'string'
? { initialRouteName: route.params.screen }
: null),
})
);
const screens = getRouteConfigsFromChildren<ScreenOptions>(children).reduce(
(acc, curr) => {
if (curr.name in acc) {
throw new Error(
`A navigator cannot contain multiple 'Screen' components with the same name (found duplicate screen named '${curr.name}')`
);
}
const screens = getRouteConfigsFromChildren<ScreenOptions>(children).reduce<
Record<string, RouteConfig<ParamListBase, string, ScreenOptions>>
>((acc, curr) => {
if (curr.name in acc) {
throw new Error(
`A navigator cannot contain multiple 'Screen' components with the same name (found duplicate screen named '${curr.name}')`
);
}
acc[curr.name] = curr;
return acc;
},
{} as { [key: string]: RouteConfig<ParamListBase, string, ScreenOptions> }
);
acc[curr.name] = curr;
return acc;
}, {});
const routeNames = Object.keys(screens);
const routeParamList = routeNames.reduce(
const routeParamList = routeNames.reduce<Record<string, object | undefined>>(
(acc, curr) => {
acc[curr] = screens[curr].initialParams;
const { initialParams } = screens[curr];
const initialParamsFromParams =
route && route.params && route.params.screen === curr
? route.params.params
: undefined;
acc[curr] =
initialParams !== undefined || initialParamsFromParams !== undefined
? {
...initialParams,
...initialParamsFromParams,
}
: undefined;
return acc;
},
{} as { [key: string]: object | undefined }
{}
);
if (!routeNames.length) {
@@ -128,6 +164,17 @@ export default function useNavigationBuilder<
);
}
const isStateValid = React.useCallback(
state => state.type === undefined || state.type === router.type,
[router.type]
);
const isStateInitialized = React.useCallback(
state =>
state !== undefined && state.stale === false && isStateValid(state),
[isStateValid]
);
const {
state: currentState,
getState: getCurrentState,
@@ -150,7 +197,7 @@ export default function useNavigationBuilder<
// Otherwise assume that the state was provided as initial state
// So we need to rehydrate it to make it usable
initializedStateRef.current =
currentState === undefined
currentState === undefined || !isStateValid(currentState)
? router.getInitialState({
routeNames,
routeParamList,
@@ -169,32 +216,61 @@ export default function useNavigationBuilder<
// If the state isn't initialized, or stale, use the state we initialized instead
// The state won't update until there's a change needed in the state we have initalized locally
// So it'll be `undefined` or stale untill the first navigation event happens
currentState === undefined || currentState.stale !== false
? (initializedStateRef.current as State)
: (currentState as State);
isStateInitialized(currentState)
? (currentState as State)
: (initializedStateRef.current as State);
let nextState: State = state;
if (!isArrayEqual(state.routeNames, routeNames)) {
// When the list of route names change, the router should handle it to remove invalid routes
const nextState = router.getStateForRouteNamesChange(state, {
nextState = router.getStateForRouteNamesChange(state, {
routeNames,
routeParamList,
});
if (state !== nextState) {
// If the state needs to be updated, we'll schedule an update with React
// setState in render seems hacky, but that's how React docs implement getDerivedPropsFromState
// https://reactjs.org/docs/hooks-faq.html#how-do-i-implement-getderivedstatefromprops
performTransaction(() => {
setState(nextState);
});
}
// The up-to-date state will come in next render, but we don't need to wait for it
// We can't use the outdated state since the screens have changed, which will cause error due to mismatched config
// So we override the state objec we return to use the latest state as soon as possible
state = nextState;
}
if (
previousRouteRef.current &&
route &&
route.params &&
typeof route.params.screen === 'string' &&
route.params !== previousRouteRef.current.params
) {
// If the route was updated with new name and/or params, we should navigate there
// The update should be limited to current navigator only, so we call the router manually
const updatedState = router.getStateForAction(
state,
navigate(route.params.screen, route.params.params),
{
routeNames,
routeParamList,
}
);
nextState =
updatedState !== null
? router.getRehydratedState(updatedState, {
routeNames,
routeParamList,
})
: state;
}
if (state !== nextState) {
// If the state needs to be updated, we'll schedule an update with React
// setState in render seems hacky, but that's how React docs implement getDerivedPropsFromState
// https://reactjs.org/docs/hooks-faq.html#how-do-i-implement-getderivedstatefromprops
performTransaction(() => {
setState(nextState);
});
}
// The up-to-date state will come in next render, but we don't need to wait for it
// We can't use the outdated state since the screens have changed, which will cause error due to mismatched config
// So we override the state objec we return to use the latest state as soon as possible
state = nextState;
React.useEffect(() => {
return () => {
// We need to clean up state for this navigator on unmount
@@ -208,10 +284,10 @@ export default function useNavigationBuilder<
const getState = React.useCallback((): State => {
const currentState = getCurrentState();
return currentState === undefined || currentState.stale !== false
? (initializedStateRef.current as State)
: (currentState as State);
}, [getCurrentState]);
return isStateInitialized(currentState)
? (currentState as State)
: (initializedStateRef.current as State);
}, [getCurrentState, isStateInitialized]);
const emitter = useEventEmitter();
@@ -227,12 +303,18 @@ export default function useNavigationBuilder<
addListener: addFocusedListener,
} = useFocusedListeners();
const { getStateForRoute, addStateGetter } = useStateGetters();
const onAction = useOnAction({
router,
getState,
setState,
key,
listeners: actionListeners,
routerConfigOptions: {
routeNames,
routeParamList,
},
});
const onRouteFocus = useOnRouteFocus({
@@ -254,6 +336,11 @@ export default function useNavigationBuilder<
focusedListeners,
});
useOnGetState({
getState,
getStateForRoute,
});
const descriptors = useDescriptors<State, ScreenOptions>({
state,
screens,
@@ -265,6 +352,7 @@ export default function useNavigationBuilder<
onRouteFocus,
addActionListener,
addFocusedListener,
addStateGetter,
router,
emitter,
});

View File

@@ -18,7 +18,7 @@ type Options<State extends NavigationState> = {
navigation: NavigationHelpers<ParamListBase> &
Partial<NavigationProp<ParamListBase, string, any, any, any>>;
setOptions: (
cb: (options: { [key: string]: object }) => { [key: string]: object }
cb: (options: Record<string, object>) => Record<string, object>
) => void;
router: Router<State, NavigationAction>;
emitter: NavigationEventEmitter;
@@ -66,11 +66,9 @@ export default function useNavigationCache<
cache.current = state.routes.reduce<NavigationCache<State, ScreenOptions>>(
(acc, route, index) => {
const previous = cache.current[route.key];
const isFirst = route.key === state.routes[0].key;
if (previous && previous.isFirstRouteInParent() === isFirst) {
// If a cached navigation object already exists and has same `isFirstRouteInParent`, reuse it
// This method could return different result if the index of the route changes somehow
if (previous) {
// If a cached navigation object already exists, reuse it
acc[route.key] = previous;
} else {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -89,13 +87,13 @@ export default function useNavigationCache<
);
};
const helpers = Object.keys(actions).reduce(
const helpers = Object.keys(actions).reduce<Record<string, () => void>>(
(acc, name) => {
// @ts-ignore
acc[name] = (...args: any) => dispatch(actions[name](...args));
return acc;
},
{} as { [key: string]: () => void }
{}
);
acc[route.key] = {
@@ -121,7 +119,6 @@ export default function useNavigationCache<
// This makes sure that we return the focus state in the whole tree, not just this navigator
return navigation ? navigation.isFocused() : true;
},
isFirstRouteInParent: () => isFirst,
};
}

View File

@@ -35,7 +35,7 @@ type Options<State extends NavigationState, Action extends NavigationAction> = {
export default function useNavigationHelpers<
State extends NavigationState,
Action extends NavigationAction,
EventMap extends { [key: string]: any }
EventMap extends Record<string, any>
>({ onAction, getState, emitter, router }: Options<State, Action>) {
const resetRoot = React.useContext(ResetRootContext);
const parentNavigationHelpers = React.useContext(NavigationContext);
@@ -55,13 +55,13 @@ export default function useNavigationHelpers<
...CommonActions,
};
const helpers = Object.keys(actions).reduce(
const helpers = Object.keys(actions).reduce<Record<string, () => void>>(
(acc, name) => {
// @ts-ignore
acc[name] = (...args: any) => dispatch(actions[name](...args));
return acc;
},
{} as { [key: string]: () => void }
{}
);
return {
@@ -73,13 +73,18 @@ export default function useNavigationHelpers<
isFocused: parentNavigationHelpers
? parentNavigationHelpers.isFocused
: () => true,
canGoBack: () =>
router.getStateForAction(
getState(),
CommonActions.goBack() as Action
) !== null ||
(parentNavigationHelpers && parentNavigationHelpers.canGoBack()) ||
false,
canGoBack: () => {
const state = getState();
return (
router.getStateForAction(state, CommonActions.goBack() as Action, {
routeNames: state.routeNames,
routeParamList: {},
}) !== null ||
(parentNavigationHelpers && parentNavigationHelpers.canGoBack()) ||
false
);
},
} as NavigationHelpers<ParamListBase, EventMap> &
(NavigationProp<ParamListBase, string, any, any, any> | undefined);
}, [

View File

@@ -2,14 +2,21 @@ import * as React from 'react';
import NavigationBuilderContext, {
ChildActionListener,
} from './NavigationBuilderContext';
import { NavigationAction, NavigationState, Router } from './types';
import {
NavigationAction,
NavigationState,
PartialState,
Router,
RouterConfigOptions,
} from './types';
type Options = {
router: Router<NavigationState, NavigationAction>;
key?: string;
getState: () => NavigationState;
setState: (state: NavigationState) => void;
setState: (state: NavigationState | PartialState<NavigationState>) => void;
listeners: ChildActionListener[];
routerConfigOptions: RouterConfigOptions;
};
/**
@@ -27,6 +34,7 @@ export default function useOnAction({
setState,
key,
listeners,
routerConfigOptions,
}: Options) {
const {
onAction: onActionParent,
@@ -35,6 +43,14 @@ export default function useOnAction({
trackAction,
} = React.useContext(NavigationBuilderContext);
const routerConfigOptionsRef = React.useRef<RouterConfigOptions>(
routerConfigOptions
);
React.useEffect(() => {
routerConfigOptionsRef.current = routerConfigOptions;
});
const onAction = React.useCallback(
(
action: NavigationAction,
@@ -54,7 +70,11 @@ export default function useOnAction({
return false;
}
let result = router.getStateForAction(state, action);
let result = router.getStateForAction(
state,
action,
routerConfigOptionsRef.current
);
// If a target is specified and set to current navigator, the action shouldn't bubble
// So instead of `null`, we use the state object for such cases to signal that action was handled

View File

@@ -0,0 +1,31 @@
import * as React from 'react';
import NavigationBuilderContext from './NavigationBuilderContext';
import { NavigationState } from './types';
import NavigationRouteContext from './NavigationRouteContext';
export default function useOnGetState({
getStateForRoute,
getState,
}: {
getStateForRoute: (routeName: string) => NavigationState | undefined;
getState: () => NavigationState;
}) {
const { addStateGetter } = React.useContext(NavigationBuilderContext);
const route = React.useContext(NavigationRouteContext);
const key = route ? route.key : 'root';
const getRehydratedState = React.useCallback(() => {
const state = getState();
return {
...state,
routes: state.routes.map(route => ({
...route,
state: getStateForRoute(route.key),
})),
};
}, [getState, getStateForRoute]);
React.useEffect(() => {
return addStateGetter && addStateGetter(key, getRehydratedState);
}, [addStateGetter, getRehydratedState, key]);
}

View File

@@ -0,0 +1,35 @@
import * as React from 'react';
import { NavigatorStateGetter } from './NavigationBuilderContext';
/**
* Hook which lets child navigators add getters to be called for obtaining rehydrated state.
*/
export default function useStateGetters() {
const stateGetters = React.useRef<Record<string, NavigatorStateGetter>>({});
const getStateForRoute = React.useCallback(
(routeKey: string) =>
stateGetters.current[routeKey] === undefined
? undefined
: stateGetters.current[routeKey](),
[stateGetters]
);
const addStateGetter = React.useCallback(
(key: string, getter: NavigatorStateGetter) => {
stateGetters.current[key] = getter;
return () => {
// @ts-ignore
stateGetters.current[key] = undefined;
};
},
[]
);
return {
getStateForRoute,
addStateGetter,
};
}

View File

@@ -3,6 +3,159 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [5.0.0-alpha.25](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.24...@react-navigation/drawer@5.0.0-alpha.25) (2019-12-07)
### Features
* export underlying views used to build navigators ([#191](https://github.com/react-navigation/navigation-ex/issues/191)) ([d618ab3](https://github.com/react-navigation/navigation-ex/commit/d618ab382ecc5eccbcd5faa89e76f9ed2d75f405))
# [5.0.0-alpha.24](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.23...@react-navigation/drawer@5.0.0-alpha.24) (2019-11-27)
### Bug Fixes
* enable gestures by default in drawer. closes [#188](https://github.com/react-navigation/navigation-ex/issues/188) ([7080517](https://github.com/react-navigation/navigation-ex/commit/7080517c914b4821e07a6320de94660e50d02950))
# [5.0.0-alpha.23](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.22...@react-navigation/drawer@5.0.0-alpha.23) (2019-11-17)
### Bug Fixes
* pass labelStyle prop in DrawerItem label ([#170](https://github.com/react-navigation/navigation-ex/issues/170)) ([cd7c9c4](https://github.com/react-navigation/navigation-ex/commit/cd7c9c4398ce12a1e965786d91fdbe6e3c42ee0a))
* workaround SafereaProvider causing jumping ([c17ad18](https://github.com/react-navigation/navigation-ex/commit/c17ad18b20cb05c577e1235a58ccc1c856fee086)), closes [#174](https://github.com/react-navigation/navigation-ex/issues/174)
# [5.0.0-alpha.22](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.21...@react-navigation/drawer@5.0.0-alpha.22) (2019-11-10)
**Note:** Version bump only for package @react-navigation/drawer
# [5.0.0-alpha.21](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.20...@react-navigation/drawer@5.0.0-alpha.21) (2019-11-08)
**Note:** Version bump only for package @react-navigation/drawer
# [5.0.0-alpha.20](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.19...@react-navigation/drawer@5.0.0-alpha.20) (2019-11-04)
**Note:** Version bump only for package @react-navigation/drawer
# [5.0.0-alpha.19](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.18...@react-navigation/drawer@5.0.0-alpha.19) (2019-11-02)
**Note:** Version bump only for package @react-navigation/drawer
# [5.0.0-alpha.18](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.17...@react-navigation/drawer@5.0.0-alpha.18) (2019-10-30)
### Bug Fixes
* hide screen from screen reader when drawer is open ([#147](https://github.com/react-navigation/navigation-ex/issues/147)) ([fb749ac](https://github.com/react-navigation/navigation-ex/commit/fb749ac))
### Features
* add an 'unmountInactiveScreens' option ([12d597f](https://github.com/react-navigation/navigation-ex/commit/12d597f))
# [5.0.0-alpha.17](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.16...@react-navigation/drawer@5.0.0-alpha.17) (2019-10-29)
**Note:** Version bump only for package @react-navigation/drawer
# [5.0.0-alpha.16](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.15...@react-navigation/drawer@5.0.0-alpha.16) (2019-10-22)
### Bug Fixes
* navigation drawer sometimes not closing when pressed outside ([0d8cdc8](https://github.com/react-navigation/navigation-ex/commit/0d8cdc8))
# [5.0.0-alpha.15](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.14...@react-navigation/drawer@5.0.0-alpha.15) (2019-10-17)
### Bug Fixes
* fix passing content options in drawer ([cab6160](https://github.com/react-navigation/navigation-ex/commit/cab6160))
# [5.0.0-alpha.14](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.13...@react-navigation/drawer@5.0.0-alpha.14) (2019-10-15)
### Bug Fixes
* add flex: 1 to drawer content ([2b57702](https://github.com/react-navigation/navigation-ex/commit/2b57702))
* fix content component not rendering in drawer ([0a5fb3e](https://github.com/react-navigation/navigation-ex/commit/0a5fb3e))
### 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.13](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.12...@react-navigation/drawer@5.0.0-alpha.13) (2019-10-06)
**Note:** Version bump only for package @react-navigation/drawer
# [5.0.0-alpha.12](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.11...@react-navigation/drawer@5.0.0-alpha.12) (2019-10-03)
**Note:** Version bump only for package @react-navigation/drawer
# [5.0.0-alpha.11](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.10...@react-navigation/drawer@5.0.0-alpha.11) (2019-10-03)
**Note:** Version bump only for package @react-navigation/drawer
# [5.0.0-alpha.10](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.9...@react-navigation/drawer@5.0.0-alpha.10) (2019-09-27)

View File

@@ -10,44 +10,29 @@ Open a Terminal in your project's folder and run,
yarn add @react-navigation/core @react-navigation/drawer
```
Now we need to install [`react-native-gesture-handler`](https://github.com/kmagiera/react-native-gesture-handler) and [`react-native-reanimated`](https://github.com/kmagiera/react-native-reanimated).
Now we need to install [`react-native-gesture-handler`](https://github.com/kmagiera/react-native-gesture-handler), [`react-native-reanimated`](https://github.com/kmagiera/react-native-reanimated) and [`react-native-safe-area-context`](https://github.com/th3rdwave/react-native-safe-area-context).
If you are using Expo, to ensure that you get the compatible versions of the libraries, run:
```sh
expo install react-native-gesture-handler react-native-reanimated
expo install react-native-gesture-handler react-native-reanimated react-native-safe-area-context
```
If you are not using Expo, run the following:
```sh
yarn add react-native-reanimated react-native-gesture-handler
yarn add react-native-reanimated react-native-gesture-handler react-native-safe-area-context
```
If you are using Expo, you are done. Otherwise, continue to the next steps.
Next, we need to link these libraries. The steps depends on your React Native version:
To complete the linking on iOS, make sure you have [Cocoapods](https://cocoapods.org/) installed. Then run:
- **React Native 0.60 and higher**
On newer versions of React Native, [linking is automatic](https://github.com/react-native-community/cli/blob/master/docs/autolinking.md).
To complete the linking on iOS, make sure you have [Cocoapods](https://cocoapods.org/) installed. Then run:
```sh
cd ios
pod install
cd ..
```
- **React Native 0.59**
If you're on an older React Native version, you need to manually link the dependencies. To do that, run:
```sh
react-native link react-native-reanimated
react-native link react-native-gesture-handler
```
```sh
cd ios
pod install
cd ..
```
**IMPORTANT:** There are additional steps required for `react-native-gesture-handler` on Android after linking (for all React Native versions). Check the [this guide](https://kmagiera.github.io/react-native-gesture-handler/docs/getting-started.html) to complete the installation.

View File

@@ -11,7 +11,7 @@
"material",
"drawer"
],
"version": "5.0.0-alpha.10",
"version": "5.0.0-alpha.25",
"license": "MIT",
"repository": {
"type": "git",
@@ -34,20 +34,20 @@
"clean": "del lib"
},
"dependencies": {
"@react-navigation/routers": "^5.0.0-alpha.8",
"react-native-safe-area-view": "^0.14.6"
"@react-navigation/routers": "^5.0.0-alpha.15"
},
"devDependencies": {
"@react-native-community/bob": "^0.7.0",
"@types/react": "^16.8.24",
"@types/react-native": "^0.60.2",
"del-cli": "^2.0.0",
"react": "16.8.3",
"react-native": "0.59.10",
"react-native-gesture-handler": "^1.3.0",
"react-native-reanimated": "^1.1.0",
"react-native-screens": "^1.0.0-alpha.22",
"typescript": "^3.5.3"
"@types/react": "^16.9.11",
"@types/react-native": "^0.60.22",
"del-cli": "^3.0.0",
"react": "~16.8.3",
"react-native": "~0.59.10",
"react-native-gesture-handler": "^1.5.0",
"react-native-reanimated": "^1.4.0",
"react-native-safe-area-context": "^0.6.0",
"react-native-screens": "^2.0.0-alpha.11",
"typescript": "^3.7.2"
},
"peerDependencies": {
"@react-navigation/core": "^5.0.0-alpha.0",
@@ -55,7 +55,8 @@
"react-native": "*",
"react-native-gesture-handler": "^1.0.0",
"react-native-reanimated": "^1.0.0",
"react-native-screens": "^1.0.0-alpha.0"
"react-native-safe-area-context": "^0.3.6",
"react-native-screens": "^1.0.0-alpha.0 || ^2.0.0-alpha.0"
},
"@react-native-community/bob": {
"source": "src",

View File

@@ -1,16 +1,15 @@
/**
* Navigators
*/
export {
default as createDrawerNavigator,
} from './navigators/createDrawerNavigator';
export { default as createDrawerNavigator } from './navigators/createDrawerNavigator';
/**
* Views
*/
export { default as DrawerNavigatorItems } from './views/DrawerNavigatorItems';
export { default as DrawerSidebar } from './views/DrawerSidebar';
export { default as DrawerView } from './views/DrawerView';
export { default as DrawerItem } from './views/DrawerItem';
export { default as DrawerItemList } from './views/DrawerItemList';
export { default as DrawerContent } from './views/DrawerContent';
/**
* Utilities
@@ -23,5 +22,6 @@ export { default as DrawerGestureContext } from './utils/DrawerGestureContext';
export {
DrawerNavigationOptions,
DrawerNavigationProp,
DrawerNavigationItemsProps,
DrawerContentOptions,
DrawerContentComponentProps,
} from './types';

View File

@@ -1,6 +1,6 @@
import * as React from 'react';
import {
createNavigator,
createNavigatorFactory,
useNavigationBuilder,
DefaultNavigatorOptions,
} from '@react-navigation/core';
@@ -40,14 +40,15 @@ function DrawerNavigator({
return (
<DrawerView
{...rest}
state={state}
descriptors={descriptors}
navigation={navigation}
{...rest}
/>
);
}
export default createNavigator<DrawerNavigationOptions, typeof DrawerNavigator>(
DrawerNavigator
);
export default createNavigatorFactory<
DrawerNavigationOptions,
typeof DrawerNavigator
>(DrawerNavigator);

View File

@@ -12,16 +12,11 @@ import { PanGestureHandler } from 'react-native-gesture-handler';
export type Scene = {
route: Route<string>;
index: number;
focused: boolean;
tintColor?: string;
color?: string;
};
export type DrawerNavigationConfig = {
/**
* Custom background color for the drawer. Defaults to `white`.
*/
drawerBackgroundColor: string;
export type DrawerNavigationConfig<T = DrawerContentOptions> = {
/**
* Position of the drawer on the screen. Defaults to `left`.
*/
@@ -33,11 +28,6 @@ export type DrawerNavigationConfig = {
* - `slide`: Both the screen and the drawer slide on swipe to reveal the drawer.
*/
drawerType: 'front' | 'back' | 'slide';
/**
* Number or a function which returns the width of the drawer.
* If a function is provided, it'll be called again when the screen's dimensions change.
*/
drawerWidth: number | (() => number);
/**
* How far from the edge of the screen the swipe gesture should activate.
*/
@@ -77,54 +67,74 @@ export type DrawerNavigationConfig = {
* Whether a screen should be unmounted when navigating away from it.
* Defaults to `false`.
*/
unmountInactiveRoutes?: boolean;
unmountInactiveScreens?: boolean;
/**
* Custom component used to render as the content of the drawer, for example, navigation items.
* Defaults to `DrawerItems`.
* Function that returns React element to render as the content of the drawer, for example, navigation items.
* Defaults to `DrawerContent`.
*/
contentComponent: React.ComponentType<ContentComponentProps>;
drawerContent: (props: DrawerContentComponentProps<T>) => React.ReactNode;
/**
* Options for the content component which will be passed as props.
*/
contentOptions?: object;
drawerContentOptions?: T;
/**
* Style object for the component wrapping the screen content.
*/
sceneContainerStyle?: StyleProp<ViewStyle>;
style?: StyleProp<ViewStyle>;
/**
* Style object for the drawer component.
* You can pass a custom background color for a drawer or a custom width here.
*/
drawerStyle?: StyleProp<ViewStyle>;
};
export type DrawerNavigationOptions = {
/**
* Title text for the screen.
*/
title?: string;
/**
* Title string of a screen displayed in the drawer
* or a function that given { focused: boolean, color: string } returns a React.Node
* When undefined, scene title is used.
*/
drawerLabel?:
| string
| ((props: { tintColor?: string; focused: boolean }) => React.ReactElement);
| ((props: { color: string; focused: boolean }) => React.ReactNode);
/**
* A function that given { focused: boolean, color: string } returns a React.Node to display an icon the drawer.
*/
drawerIcon?: (props: {
tintColor?: string;
color: string;
size: number;
focused: boolean;
}) => React.ReactElement;
drawerLockMode?: 'unlocked' | 'locked-closed' | 'locked-open';
}) => React.ReactNode;
/**
* Whether you can use gestures to open or close the drawer.
* Defaults to `true`
*/
gestureEnabled?: boolean;
};
export type ContentComponentProps = DrawerNavigationItemsProps & {
export type DrawerContentComponentProps<T = DrawerContentOptions> = T & {
state: DrawerNavigationState;
navigation: NavigationHelpers<ParamListBase>;
descriptors: { [key: string]: any };
descriptors: DrawerDescriptorMap;
/**
* Animated node which represents the current progress of the drawer's open state.
* `0` is closed, `1` is open.
*/
drawerOpenProgress: Animated.Node<number>;
progress: Animated.Node<number>;
/**
* Position of the drawer on the screen.
*/
drawerPosition: 'left' | 'right';
};
export type DrawerNavigationItemsProps = {
/**
* The array of routes, can be modified or overridden to control what's shown in the drawer.
*/
items: Route<string>[];
/**
* Route key identifying the currently active route.
*/
activeItemKey?: string | null;
export type DrawerContentOptions = {
/**
* Color for the icon and label in the active item in the drawer.
*/
@@ -141,37 +151,22 @@ export type DrawerNavigationItemsProps = {
* Background color for the inactive items in the drawer.
*/
inactiveBackgroundColor?: string;
/**
* Style object for the content section.
*/
itemsContainerStyle?: ViewStyle;
/**
* Style object for the single item, which can contain an icon and/or a label.
*/
itemStyle?: StyleProp<ViewStyle>;
/**
* Style object to overwrite `Text` style inside content section which renders a label.
* Style object to apply to the `Text` inside content section which renders a label.
*/
labelStyle?: StyleProp<TextStyle>;
/**
* Style object to overwrite `Text` style of the active label.
* Style object for the content section.
*/
activeLabelStyle?: StyleProp<TextStyle>;
contentContainerStyle?: StyleProp<ViewStyle>;
/**
* Style object to overwrite `Text` style of the inactive label.
* Style object for the wrapper view.
*/
inactiveLabelStyle?: StyleProp<TextStyle>;
/**
* Style object for the wrapper `View` of the icon.
*/
iconContainerStyle?: StyleProp<ViewStyle>;
/**
* Position of the drawer on the screen.
*/
drawerPosition: 'left' | 'right';
getLabel: (scene: Scene) => React.ReactNode;
renderIcon: (scene: Scene) => React.ReactNode;
onItemPress: (scene: { route: Route<string>; focused: boolean }) => void;
style?: StyleProp<ViewStyle>;
};
export type DrawerNavigationEventMap = {

View File

@@ -8,6 +8,7 @@ import {
Keyboard,
StatusBar,
StyleProp,
View,
} from 'react-native';
import {
PanGestureHandler,
@@ -77,7 +78,7 @@ type Props = {
onOpen: () => void;
onClose: () => void;
onGestureRef?: (ref: PanGestureHandler | null) => void;
locked: boolean;
gestureEnabled: boolean;
drawerPosition: 'left' | 'right';
drawerType: 'front' | 'back' | 'slide';
keyboardDismissMode: 'none' | 'on-drag';
@@ -96,9 +97,9 @@ type Props = {
export default class DrawerView extends React.PureComponent<Props> {
static defaultProps = {
locked: false,
drawerPostion: I18nManager.isRTL ? 'left' : 'right',
drawerType: 'front',
gestureEnabled: true,
swipeEdgeWidth: 32,
swipeVelocityThreshold: 500,
keyboardDismissMode: 'on-drag',
@@ -111,14 +112,14 @@ export default class DrawerView extends React.PureComponent<Props> {
open,
drawerPosition,
drawerType,
locked,
gestureEnabled,
swipeDistanceThreshold,
swipeVelocityThreshold,
hideStatusBar,
} = this.props;
if (prevProps.locked !== locked) {
this.isLocked.setValue(locked ? TRUE : FALSE);
if (prevProps.gestureEnabled !== gestureEnabled) {
this.isGestureEnabled.setValue(gestureEnabled ? TRUE : FALSE);
}
if (
@@ -167,7 +168,9 @@ export default class DrawerView extends React.PureComponent<Props> {
private isDrawerTypeFront = new Value<Binary>(
this.props.drawerType === 'front' ? TRUE : FALSE
);
private isLocked = new Value(this.props.locked ? TRUE : FALSE);
private isGestureEnabled = new Value(
this.props.gestureEnabled ? TRUE : FALSE
);
private isOpen = new Value<Binary>(this.props.open ? TRUE : FALSE);
private nextIsOpen = new Value<Binary | -1>(UNSET);
@@ -428,7 +431,14 @@ export default class DrawerView extends React.PureComponent<Props> {
x: this.touchX,
translationX: this.gestureX,
velocityX: this.velocityX,
state: this.gestureState,
},
},
]);
private handleGestureStateChange = event([
{
nativeEvent: {
state: (s: Animated.Value<number>) => set(this.gestureState, s),
},
},
]);
@@ -437,10 +447,7 @@ export default class DrawerView extends React.PureComponent<Props> {
{
nativeEvent: {
oldState: (s: Animated.Value<number>) =>
cond(
and(eq(s, State.ACTIVE), eq(this.isLocked, FALSE)),
set(this.manuallyTriggerSpring, TRUE)
),
cond(eq(s, State.ACTIVE), set(this.manuallyTriggerSpring, TRUE)),
},
},
]);
@@ -480,7 +487,7 @@ export default class DrawerView extends React.PureComponent<Props> {
render() {
const {
open,
locked,
gestureEnabled,
drawerPosition,
drawerType,
swipeEdgeWidth,
@@ -519,9 +526,9 @@ export default class DrawerView extends React.PureComponent<Props> {
activeOffsetX={[-SWIPE_DISTANCE_MINIMUM, SWIPE_DISTANCE_MINIMUM]}
failOffsetY={[-SWIPE_DISTANCE_MINIMUM, SWIPE_DISTANCE_MINIMUM]}
onGestureEvent={this.handleGestureEvent}
onHandlerStateChange={this.handleGestureEvent}
onHandlerStateChange={this.handleGestureStateChange}
hitSlop={hitSlop}
enabled={!locked}
enabled={gestureEnabled}
{...gestureHandlerProps}
>
<Animated.View
@@ -537,7 +544,13 @@ export default class DrawerView extends React.PureComponent<Props> {
sceneContainerStyle as any,
]}
>
{renderSceneContent({ progress: this.progress })}
<View
accessibilityElementsHidden={open}
importantForAccessibility={open ? 'no-hide-descendants' : 'auto'}
style={styles.content}
>
{renderSceneContent({ progress: this.progress })}
</View>
<TapGestureHandler onHandlerStateChange={this.handleTapStateChange}>
<Animated.View
style={[
@@ -578,7 +591,6 @@ export default class DrawerView extends React.PureComponent<Props> {
style={[
styles.container,
right ? { right: offset } : { left: offset },
// eslint-disable-next-line react-native/no-inline-styles
{
transform: [{ translateX: drawerTranslateX }],
opacity: this.drawerOpacity,

View File

@@ -0,0 +1,36 @@
import * as React from 'react';
import { ScrollView, StyleSheet } from 'react-native';
import { useSafeArea } from 'react-native-safe-area-context';
import DrawerItemList from './DrawerItemList';
import { DrawerContentComponentProps } from '../types';
export default function DrawerContent({
contentContainerStyle,
style,
drawerPosition,
...rest
}: DrawerContentComponentProps) {
const insets = useSafeArea();
return (
<ScrollView
contentContainerStyle={[
{
paddingTop: insets.top + 4,
paddingLeft: drawerPosition === 'left' ? insets.left : 0,
paddingRight: drawerPosition === 'right' ? insets.right : 0,
},
contentContainerStyle,
]}
style={[styles.container, style]}
>
<DrawerItemList {...rest} />
</ScrollView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
});

View File

@@ -0,0 +1,145 @@
import * as React from 'react';
import {
Text,
View,
StyleSheet,
StyleProp,
ViewStyle,
TextStyle,
} from 'react-native';
import TouchableItem from './TouchableItem';
type Props = {
/**
* The label text of the item.
*/
label:
| string
| ((props: { focused: boolean; color: string }) => React.ReactNode);
/**
* Icon to display for the `DrawerItem`.
*/
icon?: (props: {
focused: boolean;
size: number;
color: string;
}) => React.ReactNode;
/**
* Whether to highlight the drawer item as active.
*/
focused?: boolean;
/**
* Function to execute on press.
*/
onPress: () => void;
/**
* Color for the icon and label when the item is active.
*/
activeTintColor?: string;
/**
* Color for the icon and label when the item is inactive.
*/
inactiveTintColor?: string;
/**
* Background color for item when its active.
*/
activeBackgroundColor?: string;
/**
* Background color for item when its inactive.
*/
inactiveBackgroundColor?: string;
/**
* Style object for the label element.
*/
labelStyle?: StyleProp<TextStyle>;
/**
* Style object for the wrapper element.
*/
style?: StyleProp<ViewStyle>;
};
/**
* A component used to show an action item with an icon and a label in a navigation drawer.
*/
export default function DrawerItem({
icon,
label,
labelStyle,
focused = false,
activeTintColor = '#6200ee',
inactiveTintColor = 'rgba(0, 0, 0, .68)',
activeBackgroundColor = 'rgba(98, 0, 238, 0.12)',
inactiveBackgroundColor = 'transparent',
style,
onPress,
...rest
}: Props) {
const { borderRadius = 4 } = StyleSheet.flatten(style || {});
const color = focused ? activeTintColor : inactiveTintColor;
const backgroundColor = focused
? activeBackgroundColor
: inactiveBackgroundColor;
const iconNode = icon ? icon({ size: 24, focused, color }) : null;
return (
<View
collapsable={false}
{...rest}
style={[styles.container, { borderRadius, backgroundColor }, style]}
>
<TouchableItem
borderless
delayPressIn={0}
onPress={onPress}
style={[styles.wrapper, { borderRadius }]}
accessibilityTraits={focused ? ['button', 'selected'] : 'button'}
accessibilityComponentType="button"
accessibilityRole="button"
accessibilityStates={focused ? ['selected'] : []}
>
<React.Fragment>
{iconNode}
<View
style={[
styles.label,
{ marginLeft: iconNode ? 32 : 0, marginVertical: 5 },
]}
>
{typeof label === 'string' ? (
<Text
numberOfLines={1}
style={[
{
color,
fontWeight: '500',
},
labelStyle,
]}
>
{label}
</Text>
) : (
label({ color, focused })
)}
</View>
</React.Fragment>
</TouchableItem>
</View>
);
}
const styles = StyleSheet.create({
container: {
marginHorizontal: 10,
marginVertical: 4,
},
wrapper: {
flexDirection: 'row',
alignItems: 'center',
padding: 8,
},
label: {
marginRight: 32,
},
});

View File

@@ -0,0 +1,67 @@
import * as React from 'react';
import { CommonActions } from '@react-navigation/core';
import {
DrawerActions,
DrawerNavigationState,
} from '@react-navigation/routers';
import DrawerItem from './DrawerItem';
import {
DrawerNavigationHelpers,
DrawerDescriptorMap,
DrawerContentOptions,
} from '../types';
type Props = Omit<DrawerContentOptions, 'contentContainerStyle' | 'style'> & {
state: DrawerNavigationState;
navigation: DrawerNavigationHelpers;
descriptors: DrawerDescriptorMap;
};
/**
* Component that renders the navigation list in the drawer.
*/
export default function DrawerItemList({
state,
navigation,
descriptors,
activeTintColor,
inactiveTintColor,
activeBackgroundColor,
inactiveBackgroundColor,
itemStyle,
labelStyle,
}: Props) {
return (state.routes.map((route, i) => {
const focused = i === state.index;
const { title, drawerLabel, drawerIcon } = descriptors[route.key].options;
return (
<DrawerItem
key={route.key}
label={
drawerLabel !== undefined
? drawerLabel
: title !== undefined
? title
: route.name
}
icon={drawerIcon}
focused={focused}
activeTintColor={activeTintColor}
inactiveTintColor={inactiveTintColor}
activeBackgroundColor={activeBackgroundColor}
inactiveBackgroundColor={inactiveBackgroundColor}
labelStyle={labelStyle}
style={itemStyle}
onPress={() => {
navigation.dispatch({
...(focused
? DrawerActions.closeDrawer()
: CommonActions.navigate(route.name)),
target: state.key,
});
}}
/>
);
}) as React.ReactNode) as React.ReactElement;
}

View File

@@ -1,120 +0,0 @@
import * as React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import SafeAreaView from 'react-native-safe-area-view';
import TouchableItem from './TouchableItem';
import { DrawerNavigationItemsProps } from '../types';
/**
* Component that renders the navigation list in the drawer.
*/
const DrawerNavigatorItems = ({
items,
activeItemKey,
activeTintColor,
activeBackgroundColor,
inactiveTintColor,
inactiveBackgroundColor,
getLabel,
renderIcon,
onItemPress,
itemsContainerStyle,
itemStyle,
labelStyle,
activeLabelStyle,
inactiveLabelStyle,
iconContainerStyle,
drawerPosition,
}: DrawerNavigationItemsProps) => (
<View style={[styles.container, itemsContainerStyle]}>
{items.map((route, index: number) => {
const focused = activeItemKey === route.key;
const color = focused ? activeTintColor : inactiveTintColor;
const backgroundColor = focused
? activeBackgroundColor
: inactiveBackgroundColor;
const scene = { route, index, focused, tintColor: color };
const icon = renderIcon(scene);
const label = getLabel(scene);
const accessibilityLabel = typeof label === 'string' ? label : undefined;
const extraLabelStyle = focused ? activeLabelStyle : inactiveLabelStyle;
return (
<TouchableItem
key={route.key}
accessible
accessibilityLabel={accessibilityLabel}
onPress={() => {
onItemPress({ route, focused });
}}
delayPressIn={0}
>
<SafeAreaView
style={[{ backgroundColor }, styles.item, itemStyle]}
forceInset={{
[drawerPosition]: 'always',
[drawerPosition === 'left' ? 'right' : 'left']: 'never',
vertical: 'never',
}}
>
{icon ? (
<View
style={[
styles.icon,
focused ? null : styles.inactiveIcon,
iconContainerStyle,
]}
>
{icon}
</View>
) : null}
{typeof label === 'string' ? (
<Text
style={[styles.label, { color }, labelStyle, extraLabelStyle]}
>
{label}
</Text>
) : (
label
)}
</SafeAreaView>
</TouchableItem>
);
})}
</View>
);
/* Material design specs - https://material.io/guidelines/patterns/navigation-drawer.html#navigation-drawer-specs */
DrawerNavigatorItems.defaultProps = {
activeTintColor: '#2196f3',
activeBackgroundColor: 'rgba(0, 0, 0, .04)',
inactiveTintColor: 'rgba(0, 0, 0, .87)',
inactiveBackgroundColor: 'transparent',
};
const styles = StyleSheet.create({
container: {
paddingVertical: 4,
},
item: {
flexDirection: 'row',
alignItems: 'center',
},
icon: {
marginHorizontal: 16,
width: 24,
alignItems: 'center',
},
inactiveIcon: {
/*
* Icons have 0.54 opacity according to guidelines
* 100/87 * 54 ~= 62
*/
opacity: 0.62,
},
label: {
margin: 16,
fontWeight: 'bold',
},
});
export default DrawerNavigatorItems;

View File

@@ -1,128 +0,0 @@
import * as React from 'react';
import { StyleSheet, View, ViewStyle, StyleProp } from 'react-native';
import Animated from 'react-native-reanimated';
import { Route, CommonActions } from '@react-navigation/core';
import {
DrawerActions,
DrawerNavigationState,
} from '@react-navigation/routers';
import {
Scene,
ContentComponentProps,
DrawerDescriptorMap,
DrawerNavigationHelpers,
} from '../types';
type Props = {
contentComponent?: React.ComponentType<ContentComponentProps>;
contentOptions?: object;
state: DrawerNavigationState;
navigation: DrawerNavigationHelpers;
descriptors: DrawerDescriptorMap;
drawerOpenProgress: Animated.Node<number>;
drawerPosition: 'left' | 'right';
style?: StyleProp<ViewStyle>;
};
/**
* Component that renders the sidebar screen of the drawer.
*/
class DrawerSidebar extends React.PureComponent<Props> {
private getScreenOptions = (routeKey: string) => {
const descriptor = this.props.descriptors[routeKey];
if (!descriptor.options) {
throw new Error(
'Cannot access screen descriptor options from drawer sidebar'
);
}
return descriptor.options;
};
private getLabel = ({ focused, tintColor, route }: Scene) => {
const { drawerLabel, title } = this.getScreenOptions(route.key);
if (drawerLabel) {
return typeof drawerLabel === 'function'
? drawerLabel({ tintColor, focused })
: drawerLabel;
}
if (typeof title === 'string') {
return title;
}
return route.name;
};
private renderIcon = ({ focused, tintColor, route }: Scene) => {
const { drawerIcon } = this.getScreenOptions(route.key);
if (drawerIcon) {
return typeof drawerIcon === 'function'
? drawerIcon({ tintColor, focused })
: drawerIcon;
}
return null;
};
private handleItemPress = ({
route,
focused,
}: {
route: Route<string>;
focused: boolean;
}) => {
const { state, navigation } = this.props;
navigation.dispatch({
...(focused
? DrawerActions.closeDrawer()
: CommonActions.navigate(route.name)),
target: state.key,
});
};
render() {
const ContentComponent = this.props.contentComponent;
if (!ContentComponent) {
return null;
}
const { state } = this.props;
if (typeof state.index !== 'number') {
throw new Error(
'The index of the route should be state in the navigation state'
);
}
return (
<View style={[styles.container, this.props.style]}>
<ContentComponent
{...this.props.contentOptions}
navigation={this.props.navigation}
descriptors={this.props.descriptors}
drawerOpenProgress={this.props.drawerOpenProgress}
items={state.routes}
activeItemKey={
state.routes[state.index] ? state.routes[state.index].key : null
}
getLabel={this.getLabel}
renderIcon={this.renderIcon}
onItemPress={this.handleItemPress}
drawerPosition={this.props.drawerPosition}
/>
</View>
);
}
}
export default DrawerSidebar;
const styles = StyleSheet.create({
container: {
flex: 1,
},
});

View File

@@ -1,30 +1,36 @@
import * as React from 'react';
import { Dimensions, StyleSheet, I18nManager, Platform } from 'react-native';
import {
Dimensions,
StyleSheet,
I18nManager,
Platform,
ScaledSize,
} from 'react-native';
// eslint-disable-next-line import/no-unresolved
import { ScreenContainer } from 'react-native-screens';
import SafeAreaView from 'react-native-safe-area-view';
import { PanGestureHandler, ScrollView } from 'react-native-gesture-handler';
import { PanGestureHandler } from 'react-native-gesture-handler';
import {
DrawerNavigationState,
DrawerActions,
} from '@react-navigation/routers';
import DrawerSidebar from './DrawerSidebar';
import DrawerGestureContext from '../utils/DrawerGestureContext';
import SafeAreaProviderCompat from './SafeAreaProviderCompat';
import ResourceSavingScene from './ResourceSavingScene';
import DrawerNavigatorItems from './DrawerNavigatorItems';
import DrawerContent from './DrawerContent';
import Drawer from './Drawer';
import {
DrawerDescriptorMap,
DrawerNavigationConfig,
ContentComponentProps,
DrawerNavigationHelpers,
DrawerContentComponentProps,
} from '../types';
type Props = DrawerNavigationConfig & {
type Props = Omit<DrawerNavigationConfig, 'overlayColor'> & {
state: DrawerNavigationState;
navigation: DrawerNavigationHelpers;
descriptors: DrawerDescriptorMap;
overlayColor: string;
};
type State = {
@@ -32,13 +38,26 @@ type State = {
drawerWidth: number;
};
const DefaultContentComponent = (props: ContentComponentProps) => (
<ScrollView alwaysBounceVertical={false}>
<SafeAreaView forceInset={{ top: 'always', horizontal: 'never' }}>
<DrawerNavigatorItems {...props} />
</SafeAreaView>
</ScrollView>
);
const getDefaultDrawerWidth = ({
height,
width,
}: {
height: number;
width: number;
}) => {
/*
* Default drawer width is screen width - header height
* with a max width of 280 on mobile and 320 on tablet
* https://material.io/guidelines/patterns/navigation-drawer.html
*/
const smallerAxisSize = Math.min(height, width);
const isLandscape = width > height;
const isTablet = smallerAxisSize >= 600;
const appBarHeight = Platform.OS === 'ios' ? (isLandscape ? 32 : 44) : 56;
const maxWidth = isTablet ? 320 : 280;
return Math.min(smallerAxisSize - appBarHeight, maxWidth);
};
/**
* Component that renders the drawer.
@@ -46,25 +65,12 @@ const DefaultContentComponent = (props: ContentComponentProps) => (
export default class DrawerView extends React.PureComponent<Props, State> {
static defaultProps = {
lazy: true,
drawerWidth: () => {
/*
* Default drawer width is screen width - header height
* with a max width of 280 on mobile and 320 on tablet
* https://material.io/guidelines/patterns/navigation-drawer.html
*/
const { height, width } = Dimensions.get('window');
const smallerAxisSize = Math.min(height, width);
const isLandscape = width > height;
const isTablet = smallerAxisSize >= 600;
const appBarHeight = Platform.OS === 'ios' ? (isLandscape ? 32 : 44) : 56;
const maxWidth = isTablet ? 320 : 280;
return Math.min(smallerAxisSize - appBarHeight, maxWidth);
},
contentComponent: DefaultContentComponent,
drawerContent: (props: DrawerContentComponentProps) => (
<DrawerContent {...props} />
),
drawerPosition: I18nManager.isRTL ? 'right' : 'left',
keyboardDismissMode: 'on-drag',
drawerBackgroundColor: 'white',
overlayColor: 'rgba(0, 0, 0, 0.5)',
drawerType: 'front',
hideStatusBar: false,
statusBarAnimation: 'slide',
@@ -83,10 +89,7 @@ export default class DrawerView extends React.PureComponent<Props, State> {
state: State = {
loaded: [this.props.state.index],
drawerWidth:
typeof this.props.drawerWidth === 'function'
? this.props.drawerWidth()
: this.props.drawerWidth,
drawerWidth: getDefaultDrawerWidth(Dimensions.get('window')),
};
componentDidMount() {
@@ -121,11 +124,8 @@ export default class DrawerView extends React.PureComponent<Props, State> {
navigation.emit({ type: 'drawerClose' });
};
private updateWidth = () => {
const drawerWidth =
typeof this.props.drawerWidth === 'function'
? this.props.drawerWidth()
: this.props.drawerWidth;
private updateWidth = ({ window }: { window: ScaledSize }) => {
const drawerWidth = getDefaultDrawerWidth(window);
if (this.state.drawerWidth !== drawerWidth) {
this.setState({ drawerWidth });
@@ -133,48 +133,57 @@ export default class DrawerView extends React.PureComponent<Props, State> {
};
private renderNavigationView = ({ progress }: any) => {
return <DrawerSidebar drawerOpenProgress={progress} {...this.props} />;
const {
state,
navigation,
descriptors,
drawerPosition,
drawerContent,
drawerContentOptions,
} = this.props;
return drawerContent({
...drawerContentOptions,
progress: progress,
state: state,
navigation: navigation,
descriptors: descriptors,
drawerPosition: drawerPosition,
});
};
private renderContent = () => {
let { lazy, state, descriptors, unmountInactiveRoutes } = this.props;
let { lazy, state, descriptors, unmountInactiveScreens } = this.props;
const { loaded } = this.state;
if (unmountInactiveRoutes) {
const activeKey = state.routes[state.index].key;
const descriptor = descriptors[activeKey];
return (
<ScreenContainer style={styles.content}>
{state.routes.map((route, index) => {
if (unmountInactiveScreens && index !== state.index) {
return null;
}
return descriptor.render();
} else {
return (
<ScreenContainer style={styles.content}>
{state.routes.map((route, index) => {
if (lazy && !loaded.includes(index)) {
// Don't render a screen if we've never navigated to it
return null;
}
if (lazy && !loaded.includes(index)) {
// Don't render a screen if we've never navigated to it
return null;
}
const isFocused = state.index === index;
const descriptor = descriptors[route.key];
const isFocused = state.index === index;
const descriptor = descriptors[route.key];
return (
<ResourceSavingScene
key={route.key}
style={[
StyleSheet.absoluteFill,
// eslint-disable-next-line react-native/no-inline-styles
{ opacity: isFocused ? 1 : 0 },
]}
isVisible={isFocused}
>
{descriptor.render()}
</ResourceSavingScene>
);
})}
</ScreenContainer>
);
}
return (
<ResourceSavingScene
key={route.key}
style={[StyleSheet.absoluteFill, { opacity: isFocused ? 1 : 0 }]}
isVisible={isFocused}
>
{descriptor.render()}
</ResourceSavingScene>
);
})}
</ScreenContainer>
);
};
private setDrawerGestureRef = (ref: PanGestureHandler | null) => {
@@ -188,9 +197,9 @@ export default class DrawerView extends React.PureComponent<Props, State> {
descriptors,
drawerType,
drawerPosition,
drawerBackgroundColor,
overlayColor,
sceneContainerStyle,
drawerStyle,
edgeWidth,
minSwipeDistance,
hideStatusBar,
@@ -198,46 +207,35 @@ export default class DrawerView extends React.PureComponent<Props, State> {
gestureHandlerProps,
} = this.props;
const activeKey = state.routes[state.index].key;
const { drawerLockMode } = descriptors[activeKey].options;
const { drawerWidth } = this.state;
const isOpen =
drawerLockMode === 'locked-closed'
? false
: drawerLockMode === 'locked-open'
? true
: state.isDrawerOpen;
const activeKey = state.routes[state.index].key;
const { gestureEnabled } = descriptors[activeKey].options;
return (
<DrawerGestureContext.Provider value={this.drawerGestureRef}>
<Drawer
open={isOpen}
locked={
drawerLockMode === 'locked-open' ||
drawerLockMode === 'locked-closed'
}
onOpen={this.handleDrawerOpen}
onClose={this.handleDrawerClose}
onGestureRef={this.setDrawerGestureRef}
gestureHandlerProps={gestureHandlerProps}
drawerType={drawerType}
drawerPosition={drawerPosition}
sceneContainerStyle={sceneContainerStyle}
drawerStyle={{
backgroundColor: drawerBackgroundColor || 'white',
width: this.state.drawerWidth,
}}
overlayStyle={{
backgroundColor: overlayColor || 'rgba(0, 0, 0, 0.5)',
}}
swipeEdgeWidth={edgeWidth}
swipeDistanceThreshold={minSwipeDistance}
hideStatusBar={hideStatusBar}
statusBarAnimation={statusBarAnimation}
renderDrawerContent={this.renderNavigationView}
renderSceneContent={this.renderContent}
/>
</DrawerGestureContext.Provider>
<SafeAreaProviderCompat>
<DrawerGestureContext.Provider value={this.drawerGestureRef}>
<Drawer
open={state.isDrawerOpen}
gestureEnabled={gestureEnabled !== false}
onOpen={this.handleDrawerOpen}
onClose={this.handleDrawerClose}
onGestureRef={this.setDrawerGestureRef}
gestureHandlerProps={gestureHandlerProps}
drawerType={drawerType}
drawerPosition={drawerPosition}
sceneContainerStyle={sceneContainerStyle}
drawerStyle={[{ width: drawerWidth }, drawerStyle]}
overlayStyle={{ backgroundColor: overlayColor }}
swipeEdgeWidth={edgeWidth}
swipeDistanceThreshold={minSwipeDistance}
hideStatusBar={hideStatusBar}
statusBarAnimation={statusBarAnimation}
renderDrawerContent={this.renderNavigationView}
renderSceneContent={this.renderContent}
/>
</DrawerGestureContext.Provider>
</SafeAreaProviderCompat>
);
}
}

View File

@@ -0,0 +1,26 @@
import * as React from 'react';
import {
SafeAreaProvider,
SafeAreaConsumer,
} from 'react-native-safe-area-context';
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/navigation-ex/issues/174
return children;
}
return <SafeAreaProvider>{children}</SafeAreaProvider>;
}}
</SafeAreaConsumer>
);
}

View File

@@ -3,6 +3,144 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [5.0.0-alpha.23](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.22...@react-navigation/example@5.0.0-alpha.23) (2019-11-20)
**Note:** Version bump only for package @react-navigation/example
# [5.0.0-alpha.22](https://github.com/satya164/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.21...@react-navigation/example@5.0.0-alpha.22) (2019-11-17)
**Note:** Version bump only for package @react-navigation/example
# [5.0.0-alpha.21](https://github.com/satya164/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.20...@react-navigation/example@5.0.0-alpha.21) (2019-11-10)
**Note:** Version bump only for package @react-navigation/example
# [5.0.0-alpha.20](https://github.com/satya164/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.19...@react-navigation/example@5.0.0-alpha.20) (2019-11-08)
**Note:** Version bump only for package @react-navigation/example
# [5.0.0-alpha.19](https://github.com/satya164/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.18...@react-navigation/example@5.0.0-alpha.19) (2019-11-04)
**Note:** Version bump only for package @react-navigation/example
# [5.0.0-alpha.18](https://github.com/satya164/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.17...@react-navigation/example@5.0.0-alpha.18) (2019-11-02)
### Bug Fixes
* minor tweaks for web and fix example ([67fd69a](https://github.com/satya164/navigation-ex/commit/67fd69a))
# [5.0.0-alpha.17](https://github.com/satya164/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.16...@react-navigation/example@5.0.0-alpha.17) (2019-10-29)
### Bug Fixes
* improve type annotation for screens ([8f16085](https://github.com/satya164/navigation-ex/commit/8f16085))
# [5.0.0-alpha.16](https://github.com/satya164/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.15...@react-navigation/example@5.0.0-alpha.16) (2019-10-22)
**Note:** Version bump only for package @react-navigation/example
# [5.0.0-alpha.15](https://github.com/satya164/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.14...@react-navigation/example@5.0.0-alpha.15) (2019-10-22)
**Note:** Version bump only for package @react-navigation/example
# [5.0.0-alpha.14](https://github.com/satya164/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.13...@react-navigation/example@5.0.0-alpha.14) (2019-10-17)
**Note:** Version bump only for package @react-navigation/example
# [5.0.0-alpha.13](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.12...@react-navigation/example@5.0.0-alpha.13) (2019-10-15)
### Bug Fixes
* block GH interactions in Native Stack example ([#126](https://github.com/react-navigation/navigation-ex/issues/126)) ([386d1c0](https://github.com/react-navigation/navigation-ex/commit/386d1c0))
* make it possible to run the example on web ([7a901af](https://github.com/react-navigation/navigation-ex/commit/7a901af))
### 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.12](https://github.com/satya164/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.11...@react-navigation/example@5.0.0-alpha.12) (2019-10-06)
### Features
* drop header: null in favor of more explitit headerShown option ([ba6b6ae](https://github.com/satya164/navigation-ex/commit/ba6b6ae))
# [5.0.0-alpha.11](https://github.com/satya164/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.10...@react-navigation/example@5.0.0-alpha.11) (2019-10-03)
**Note:** Version bump only for package @react-navigation/example
# [5.0.0-alpha.10](https://github.com/satya164/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.9...@react-navigation/example@5.0.0-alpha.10) (2019-10-03)
**Note:** Version bump only for package @react-navigation/example
# [5.0.0-alpha.9](https://github.com/satya164/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.8...@react-navigation/example@5.0.0-alpha.9) (2019-10-03)
**Note:** Version bump only for package @react-navigation/example
# [5.0.0-alpha.8](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.7...@react-navigation/example@5.0.0-alpha.8) (2019-09-27)

View File

@@ -151,6 +151,7 @@
<orderEntry type="library" name="Gradle: com.facebook.soloader:soloader:0.6.0@aar" level="project" />
<orderEntry type="library" name="Gradle: io.nlopez.smartlocation:library:3.2.11@aar" level="project" />
<orderEntry type="library" name="Gradle: com.facebook.fresco:fbcore:1.10.0@aar" level="project" />
<orderEntry type="module" module-name="react-native-screens" />
<orderEntry type="module" module-name="react-native-reanimated" />
<orderEntry type="module" module-name="react-native-gesture-handler" />
<orderEntry type="module" module-name="expo-permissions" />

View File

@@ -139,6 +139,8 @@ android {
}
dependencies {
implementation project(':react-native-safe-area-context')
implementation project(':react-native-screens')
implementation project(':react-native-reanimated')
implementation project(':react-native-gesture-handler')
implementation fileTree(dir: "libs", include: ["*.jar"])

View File

@@ -3,6 +3,8 @@ package com.reactnavigationexample;
import android.app.Application;
import com.facebook.react.ReactApplication;
import com.th3rdwave.safeareacontext.SafeAreaContextPackage;
import com.swmansion.rnscreens.RNScreensPackage;
import com.swmansion.reanimated.ReanimatedPackage;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
@@ -39,6 +41,8 @@ public class MainApplication extends Application implements ReactApplication {
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new SafeAreaContextPackage(),
new RNScreensPackage(),
new ReanimatedPackage(),
new RNGestureHandlerPackage(),
new ModuleRegistryAdapter(mModuleRegistryProvider)

View File

@@ -16,3 +16,6 @@
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
android.useAndroidX=true
android.enableJetifier=true

View File

@@ -1,8 +0,0 @@
## This file must *NOT* be checked into Version Control Systems,
# as it contains information specific to your local configuration.
#
# Location of the SDK. This is only used by Gradle.
# For customization when using a Version Control System, please read the
# header note.
#Wed Sep 18 15:42:44 CEST 2019
sdk.dir=/Users/osdnk/Library/Android/sdk

View File

@@ -1,4 +1,8 @@
apply from: '../node_modules/react-native-unimodules/gradle.groovy'
include ':react-native-safe-area-context'
project(':react-native-safe-area-context').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-safe-area-context/android')
include ':react-native-screens'
project(':react-native-screens').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-screens/android')
include ':react-native-reanimated'
project(':react-native-reanimated').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-reanimated/android')
includeUnimodulesProjects()

View File

@@ -3,14 +3,13 @@
"name": "@react-navigation/example",
"slug": "react-navigation-example",
"privacy": "public",
"sdkVersion": "34.0.0",
"sdkVersion": "35.0.0",
"platforms": [
"ios",
"android",
"web"
],
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
"splash": {
"image": "./assets/splash.png",
@@ -30,4 +29,4 @@
},
"displayName": "React Navigation Example",
"name": "ReactNavigationExample"
}
}

View File

@@ -1,5 +1,3 @@
/* eslint-disable import/no-commonjs */
module.exports = function(api) {
api.cache(true);
return {

View File

@@ -28,6 +28,9 @@ target 'ReactNavigationExample' do
pod 'Folly', :podspec => '../node_modules/react-native/third-party-podspecs/Folly.podspec'
pod 'RNGestureHandler', :podspec => '../node_modules/react-native-gesture-handler/RNGestureHandler.podspec'
pod 'RNReanimated', :podspec => '../node_modules/react-native-reanimated/RNReanimated.podspec'
pod 'RNScreens', :path => '../node_modules/react-native-screens/RNScreens.podspec'
use_unimodules!
pod 'react-native-safe-area-context', :path => '../node_modules/react-native-safe-area-context'
end

View File

@@ -1,31 +1,31 @@
PODS:
- boost-for-react-native (1.63.0)
- DoubleConversion (1.1.6)
- EXAppLoaderProvider (6.0.0)
- EXConstants (6.0.0):
- EXAppLoaderProvider (7.0.0)
- EXConstants (7.0.0):
- UMConstantsInterface
- UMCore
- EXFileSystem (6.0.2):
- EXFileSystem (7.0.0):
- UMCore
- UMFileSystemInterface
- EXFont (6.0.1):
- EXFont (7.0.0):
- UMCore
- UMFontInterface
- EXKeepAwake (6.0.0):
- EXKeepAwake (7.0.0):
- UMCore
- EXLinearGradient (6.0.0):
- EXLinearGradient (7.0.0):
- UMCore
- EXLocation (6.0.0):
- EXLocation (7.0.0):
- UMCore
- UMPermissionsInterface
- UMTaskManagerInterface
- EXPermissions (6.0.0):
- EXPermissions (7.0.0):
- UMCore
- UMPermissionsInterface
- EXSQLite (6.0.0):
- EXSQLite (7.0.0):
- UMCore
- UMFileSystemInterface
- EXWebBrowser (6.0.0):
- EXWebBrowser (7.0.1):
- UMCore
- Folly (2018.10.22.00):
- boost-for-react-native
@@ -34,6 +34,8 @@ PODS:
- glog (0.3.5)
- React (0.59.10):
- React/Core (= 0.59.10)
- react-native-safe-area-context (0.6.0):
- React
- React/Core (0.59.10):
- yoga (= 0.59.10.React)
- React/CxxBridge (0.59.10):
@@ -89,23 +91,25 @@ PODS:
- React/RCTBlob
- RNGestureHandler (1.3.0):
- React
- RNReanimated (1.1.0):
- RNReanimated (1.2.0):
- React
- UMBarCodeScannerInterface (3.0.0)
- UMCameraInterface (3.0.0)
- UMConstantsInterface (3.0.0)
- UMCore (3.0.2)
- UMFaceDetectorInterface (3.0.0)
- UMFileSystemInterface (3.0.0)
- UMFontInterface (3.0.0)
- UMImageLoaderInterface (3.0.0)
- UMPermissionsInterface (3.0.0)
- UMReactNativeAdapter (3.0.0):
- RNScreens (2.0.0-alpha.11):
- React
- UMBarCodeScannerInterface (4.0.0)
- UMCameraInterface (4.0.0)
- UMConstantsInterface (4.0.0)
- UMCore (4.0.0)
- UMFaceDetectorInterface (4.0.0)
- UMFileSystemInterface (4.0.0)
- UMFontInterface (4.0.0)
- UMImageLoaderInterface (4.0.0)
- UMPermissionsInterface (4.0.0)
- UMReactNativeAdapter (5.0.0-alpha.0):
- React
- UMCore
- UMFontInterface
- UMSensorsInterface (3.0.0)
- UMTaskManagerInterface (3.0.0)
- UMSensorsInterface (4.0.0)
- UMTaskManagerInterface (4.0.0)
- yoga (0.59.10.React)
DEPENDENCIES:
@@ -122,6 +126,7 @@ DEPENDENCIES:
- EXWebBrowser (from `../node_modules/expo-web-browser/ios`)
- Folly (from `../node_modules/react-native/third-party-podspecs/Folly.podspec`)
- glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`)
- react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`)
- React/Core (from `../node_modules/react-native`)
- React/CxxBridge (from `../node_modules/react-native`)
- React/DevSupport (from `../node_modules/react-native`)
@@ -138,6 +143,7 @@ DEPENDENCIES:
- React/RCTWebSocket (from `../node_modules/react-native`)
- RNGestureHandler (from `../node_modules/react-native-gesture-handler/RNGestureHandler.podspec`)
- RNReanimated (from `../node_modules/react-native-reanimated/RNReanimated.podspec`)
- RNScreens (from `../node_modules/react-native-screens/RNScreens.podspec`)
- UMBarCodeScannerInterface (from `../node_modules/unimodules-barcode-scanner-interface/ios`)
- UMCameraInterface (from `../node_modules/unimodules-camera-interface/ios`)
- UMConstantsInterface (from `../node_modules/unimodules-constants-interface/ios`)
@@ -147,13 +153,13 @@ DEPENDENCIES:
- UMFontInterface (from `../node_modules/unimodules-font-interface/ios`)
- UMImageLoaderInterface (from `../node_modules/unimodules-image-loader-interface/ios`)
- UMPermissionsInterface (from `../node_modules/unimodules-permissions-interface/ios`)
- "UMReactNativeAdapter (from `../node_modules/@unimodules/react-native-adapter/ios`)"
- "UMReactNativeAdapter (from `../node_modules/react-native-unimodules/node_modules/@unimodules/react-native-adapter/ios`)"
- UMSensorsInterface (from `../node_modules/unimodules-sensors-interface/ios`)
- UMTaskManagerInterface (from `../node_modules/unimodules-task-manager-interface/ios`)
- yoga (from `../node_modules/react-native/ReactCommon/yoga`)
SPEC REPOS:
https://github.com/cocoapods/specs.git:
trunk:
- boost-for-react-native
EXTERNAL SOURCES:
@@ -195,10 +201,14 @@ EXTERNAL SOURCES:
:podspec: "../node_modules/react-native/third-party-podspecs/glog.podspec"
React:
:path: "../node_modules/react-native"
react-native-safe-area-context:
:path: "../node_modules/react-native-safe-area-context"
RNGestureHandler:
:podspec: "../node_modules/react-native-gesture-handler/RNGestureHandler.podspec"
RNReanimated:
:podspec: "../node_modules/react-native-reanimated/RNReanimated.podspec"
RNScreens:
:path: "../node_modules/react-native-screens/RNScreens.podspec"
UMBarCodeScannerInterface:
:path: !ruby/object:Pathname
path: "../node_modules/unimodules-barcode-scanner-interface/ios"
@@ -228,7 +238,7 @@ EXTERNAL SOURCES:
path: "../node_modules/unimodules-permissions-interface/ios"
UMReactNativeAdapter:
:path: !ruby/object:Pathname
path: "../node_modules/@unimodules/react-native-adapter/ios"
path: "../node_modules/react-native-unimodules/node_modules/@unimodules/react-native-adapter/ios"
UMSensorsInterface:
:path: !ruby/object:Pathname
path: "../node_modules/unimodules-sensors-interface/ios"
@@ -241,35 +251,37 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
boost-for-react-native: 39c7adb57c4e60d6c5479dd8623128eb5b3f0f2c
DoubleConversion: bb338842f62ab1d708ceb63ec3d999f0f3d98ecd
EXAppLoaderProvider: 7a8185228d8ba9e689a0e2d6d957fe9bdd49c8a0
EXConstants: 5d81e84ca71b9a552529889cc798b4a04e9e22b3
EXFileSystem: 091907902fcec9f9182b656fdead41a82f30986a
EXFont: c862449210fc86aa11d24a202cb22c71a0d39609
EXKeepAwake: e7cb6516675052b12a7d23291e33078b4239653a
EXLinearGradient: 40781b77e58f844c8dc4ad310dc9755b4d3792a7
EXLocation: 4eb76115832f08b1e78003b335c210e18fa60424
EXPermissions: 99e52dc3e5f8e55153f1958004f6df2a30a1f2f5
EXSQLite: 8dab6a5ab1b78be7925073d6071eb22095d4dda6
EXWebBrowser: def838b95aa9d396f9ce71ace4e614ee16e7ee30
EXAppLoaderProvider: 5d348813a9cf09b03bbe5b8b55437bc1bfbddbd1
EXConstants: 31e5318521be6175009af6ccd3e97dedf39da96a
EXFileSystem: 7e53a2c30a2eb6987ba6d5158ab908f947523228
EXFont: 71d07dc5d2153db7d1a23f1e0cc1b6341d55c432
EXKeepAwake: d4caf9a1a7691126ade4ca0b76592e93250a8f29
EXLinearGradient: ebfd46cb98a46330213e4945b96227d98f624054
EXLocation: 452a1d9edceb1b93deb86fd785236606d33742dd
EXPermissions: df10ad83df2f6b647aec304619354f8ab48d5f63
EXSQLite: ddc1e6727bd7d36e649f07590fe3ae5511c1f039
EXWebBrowser: 18924c3d2a3a1aa95d413672f058beff589c80f4
Folly: de497beb10f102453a1afa9edbf8cf8a251890de
glog: aefd1eb5dda2ab95ba0938556f34b98e2da3a60d
React: 36d0768f9e93be2473b37e7fa64f92c1d5341eef
react-native-safe-area-context: d288138da2c800caa111f9352e9333f186a06ead
RNGestureHandler: 5329a942fce3d41c68b84c2c2276ce06a696d8b0
RNReanimated: 7a52c90473b5e81c13408d40d797b98387eaddde
UMBarCodeScannerInterface: 84ea2d6b58ff0dc27ef9b68bab71286be18ee020
UMCameraInterface: 26b26005d1756a0d5f4f04f1e168e39ea9154535
UMConstantsInterface: 038bacb19de12b6fd328c589122c8dc977cccf61
UMCore: 733094f43f7244c60ce1f0592d00013ed68fa52c
UMFaceDetectorInterface: c9c3ae4cb045421283667a1698c2f31331f55e3f
UMFileSystemInterface: e9adc71027017de38eaf7d05fa58b2848ecb3797
UMFontInterface: f0c5846977ee8a93d7cfa8ae7e666772c727d195
UMImageLoaderInterface: 36e54e570acc4d720856f03ceebc441f73ea472c
UMPermissionsInterface: 938d010c74c43fcefc9bb990633a7c5a1631267e
UMReactNativeAdapter: 131ea2b944ade8035f0b54c6570c405f6000548d
UMSensorsInterface: 0ed023ce9b96f2ca6fada7bda05b7760da60b293
UMTaskManagerInterface: 8664abd37a00715727e60df9ecd65e42ba47b548
RNReanimated: 1b52415c4302f198cb581282a0166690bad62c43
RNScreens: ad3661f864ef18d952e9a4799b6791683e33c1fc
UMBarCodeScannerInterface: d5a6fdc98ed6241225b0a8432a7f4e2b397668bc
UMCameraInterface: 68870a3197fee85bd5afca5609ba4a5b7257d19d
UMConstantsInterface: d25b8e8887ca7aaf568c06caf08f4d40734ee4ef
UMCore: 402cee150324974974f5c32b5404d8af65e4cff5
UMFaceDetectorInterface: 7b4f1a92f0c726b58b086296048efe193b570678
UMFileSystemInterface: aadb9a67aa6470d7ebc06cf04dc54fee6781ac48
UMFontInterface: 2d3c128285086bbed3d2a650f1d698323ef3b25a
UMImageLoaderInterface: 2829a7571a12d2e754c73c55ffe7e327d8402c7d
UMPermissionsInterface: b6a6e96db0f4011a25aaca14e6022529dd3d6e4e
UMReactNativeAdapter: e93109c6de5ea830d4a78d08f3a65b8d33a143a3
UMSensorsInterface: cf59dd7602764a2419e00167429be3e4be39c61d
UMTaskManagerInterface: 1e70fe58b872355f0ecb44fb81bb1a16484047f0
yoga: 684513b14b03201579ba3cee20218c9d1298b0cc
PODFILE CHECKSUM: 41592ff50a43d56f905dbf9c7eded4b358264eba
PODFILE CHECKSUM: 277599ab8fceae1c57f639a14203691239c429ab
COCOAPODS: 1.7.5
COCOAPODS: 1.8.4

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 337 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 589 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 894 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 748 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 713 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1017 B

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