Compare commits

...

169 Commits

Author SHA1 Message Date
Satyajit Sahoo
5683bebfd6 chore: publish
- @react-navigation/stack@5.2.12
2020-04-22 16:26:11 +02:00
Satyajit Sahoo
78485cea69 fix: animate card to existing closing state on gesture end
fixes #7938
2020-04-22 15:16:39 +02:00
Satyajit Sahoo
1613915669 chore: mark screens and masked view as optional in stack
Needs e54819c4de to work.
2020-04-22 14:02:21 +02:00
Satyajit Sahoo
335a04edc1 chore: add action to check package versions 2020-04-20 14:35:07 +02:00
Satyajit Sahoo
5e0069a896 chore: publish
- @react-navigation/bottom-tabs@5.2.7
 - @react-navigation/compat@5.1.9
 - @react-navigation/core@5.3.4
 - @react-navigation/drawer@5.5.0
 - @react-navigation/material-bottom-tabs@5.1.9
 - @react-navigation/material-top-tabs@5.1.9
 - @react-navigation/native@5.1.6
 - @react-navigation/routers@5.4.0
 - @react-navigation/stack@5.2.11
2020-04-18 01:28:05 +02:00
Satyajit Sahoo
249248e741 chore: update yarn.lock 2020-04-18 01:24:16 +02:00
Evan Bacon
821343fed3 fix: webkit style error in overlay 2020-04-18 01:14:56 +02:00
Satyajit Sahoo
82edb2581b fix: hide inactive screens for stack on web (#8010) 2020-04-18 01:14:11 +02:00
Satyajit Sahoo
cb67530dc5 chore: tweak album example 2020-04-18 01:13:34 +02:00
Satyajit Sahoo
36689e24c2 feat: add openByDefault option to drawer 2020-04-18 01:13:34 +02:00
Gheorghe Pinzaru
6e51f596fa fix: ios presentation modal cuts the topOffset on the bottom (#7943)
* Add padding bottom to ios presentation modal

Because of the translateY moving the screen out to the bottom of view by 10 pt, these 10pt are hidden under the screen, or steal this size from the safe area. To avoid cutting elements, the size of the screen could be decreased by the `topOffset` using padding on the bottom. Fixes #7856

* Update packages/stack/src/TransitionConfigs/CardStyleInterpolators.tsx

Co-Authored-By: Serhii Vecherenko <SDSLeon999@gmail.com>

Co-authored-by: Satyajit Sahoo <satyajit.happy@gmail.com>
Co-authored-by: Serhii Vecherenko <SDSLeon999@gmail.com>
2020-04-18 01:13:34 +02:00
Satyajit Sahoo
402df73aa2 chore: add link to how to create minimal repro 2020-04-17 00:24:22 +02:00
Satyajit Sahoo
187aefe9c4 fix: handle initial: false for nested route after first initialization 2020-04-14 17:06:58 +02:00
Satyajit Sahoo
2613a62874 chore: add config for netlify 2020-04-12 22:11:22 +02:00
Satyajit Sahoo
6bdf6ae4ed fix: handle in-page go back when there's no history
fixes #7852
2020-04-10 17:59:40 +02:00
Satyajit Sahoo
e2bcf5168c fix: fix drawer not closing on web
fixes #6759
2020-04-10 17:59:07 +02:00
Satyajit Sahoo
dfdba8d741 fix: disable animation by default on web for stack 2020-04-10 17:02:32 +02:00
Satyajit Sahoo
a3f7a5feba fix: add initial param for actions from deep link 2020-04-10 12:05:16 +02:00
Satyajit Sahoo
004c7d7ab1 fix: add initial option for navigating to nested navigators
By default, params passed to nested navigators is used to initialize the navigator if it's not rendered already. The `initial` option would let the user control this behaviour. By specifying `initial: false`, it'll be possible to acheive the old behaviour of rendering the initial route of the stack before navigating to the new screen.

Example:

```js
navigation.navigate('Account', {
  screen: 'Settings',
  initial: false,
});
```
2020-04-10 11:51:32 +02:00
Satyajit Sahoo
49f658fbc0 chore: publish
- @react-navigation/bottom-tabs@5.2.6
 - @react-navigation/compat@5.1.8
 - @react-navigation/core@5.3.3
 - @react-navigation/drawer@5.4.1
 - @react-navigation/material-bottom-tabs@5.1.8
 - @react-navigation/material-top-tabs@5.1.8
 - @react-navigation/native@5.1.5
 - @react-navigation/routers@5.3.0
 - @react-navigation/stack@5.2.10
2020-04-08 12:17:31 +02:00
Satyajit Sahoo
cb2f157a56 fix: don't hide content from accessibility with permanent drawer
closes #7976
2020-04-08 12:17:09 +02:00
Juang, Yi-Lin
c4acdaa703 docs: fix typo (#7865) 2020-04-08 11:35:45 +02:00
Satyajit Sahoo
f1a8bceba5 fix: make color of shadow element same as card color in stack 2020-04-07 23:34:55 +02:00
Ruben Grimm
44081172d4 fix: use 1 as default in compatibility pop action 2020-04-07 23:33:38 +02:00
Satyajit Sahoo
de5d985f3b chore: upgrade depenendecies 2020-04-07 15:44:58 +02:00
Satyajit Sahoo
b71de6cc79 fix: mark type exports for all packages 2020-04-07 11:22:47 +02:00
raajnadar
303f0b78a5 fix: separate normal exports and type exports 2020-04-07 11:17:06 +02:00
Satyajit Sahoo
ce3994c82c fix: switch order of focus and blur events. closes #7963 2020-04-07 11:07:16 +02:00
Satyajit Sahoo
ba1f405129 feat: make replace bubble up 2020-04-07 00:02:54 +02:00
Satyajit Sahoo
d4fd906915 fix: workaround warning about setState in another component in render 2020-04-06 23:58:25 +02:00
Vinícius Fraga Modesto
b7fa90bf8d docs: fixes typo (#7923)
This PR fixes a typo in activeBackgroundColor's description
2020-03-31 17:56:26 +02:00
Satyajit Sahoo
9556aa9eff chore: publish
- @react-navigation/bottom-tabs@5.2.5
 - @react-navigation/compat@5.1.7
 - @react-navigation/core@5.3.2
 - @react-navigation/drawer@5.4.0
 - @react-navigation/material-bottom-tabs@5.1.7
 - @react-navigation/material-top-tabs@5.1.7
 - @react-navigation/native@5.1.4
 - @react-navigation/routers@5.2.1
 - @react-navigation/stack@5.2.9
2020-03-30 22:22:25 +02:00
Satyajit Sahoo
9a8fea8f2c fix: when comparing changed routes, only check keys 2020-03-30 22:20:16 +02:00
Satyajit Sahoo
9973db86f0 chore: use non-secure nanoid to be able to run in RN 2020-03-30 22:04:53 +02:00
max
8432e5ab25 fix: dismiss keyboard on screen change for android 2020-03-30 21:50:52 +02:00
Satyajit Sahoo
9bb5cfded3 refactor: replace shortid with nanoid. closes #7858 2020-03-30 21:42:58 +02:00
Satyajit Sahoo
4ac40b5c5d chore: update typescript and babel 2020-03-30 21:42:58 +02:00
Wojciech Lewicki
cd47915861 fix: handle no path property and undefined query params (#7911) 2020-03-30 17:11:33 +02:00
Andrius Janauskas
d649fbc669 fix: finish stack animation on CANCELLED event (#7898)
fixes #7897
2020-03-30 14:36:04 +02:00
Satyajit Sahoo
105da6ab2f fix: disable only swipe gesture on safari 2020-03-30 13:56:30 +02:00
Rajendran Nadar
ac7f972e92 feat: add swipeEnabled option to disable swipe gesture in drawer (#7834) 2020-03-30 13:51:32 +02:00
Satyajit Sahoo
babb5027f9 chore: publish
- @react-navigation/stack@5.2.8
2020-03-27 15:01:32 +01:00
Satyajit Sahoo
78d7a66b2b chore: remove detox dep coz we don't use it 2020-03-27 14:54:54 +01:00
osdnk
a248c453ba chore: publish
- @react-navigation/stack@5.2.7
2020-03-26 17:07:40 +01:00
Wojciech Stanisz
e097df880a fix: add pointerEvents=box-none to overlay View (#7871) 2020-03-26 13:38:30 +01:00
Satyajit Sahoo
856449b200 chore: publish
- @react-navigation/bottom-tabs@5.2.4
 - @react-navigation/compat@5.1.6
 - @react-navigation/core@5.3.1
 - @react-navigation/drawer@5.3.4
 - @react-navigation/material-bottom-tabs@5.1.6
 - @react-navigation/material-top-tabs@5.1.6
 - @react-navigation/native@5.1.3
 - @react-navigation/stack@5.2.6
2020-03-23 17:07:43 +01:00
Steven Bell
d94e43c3c8 fix: add info about android launchMode in useLinking error 2020-03-23 16:39:18 +01:00
Satyajit Sahoo
3096de6286 fix: only call listeners for focused screen for global events 2020-03-23 13:43:43 +01:00
Satyajit Sahoo
1c001424b5 fix: don't emit events for screens that don't exist anymore 2020-03-23 13:03:33 +01:00
Satyajit Sahoo
0f2368965c chore: publish
- @react-navigation/stack@5.2.5
2020-03-23 11:42:01 +01:00
Satyajit Sahoo
61f16d3f25 fix: fix swipe gestures requiring a lot of velocity to dismiss 2020-03-23 11:40:37 +01:00
Satyajit Sahoo
853740bfaf chore: publish
- @react-navigation/bottom-tabs@5.2.3
 - @react-navigation/compat@5.1.5
 - @react-navigation/core@5.3.0
 - @react-navigation/drawer@5.3.3
 - @react-navigation/material-bottom-tabs@5.1.5
 - @react-navigation/material-top-tabs@5.1.5
 - @react-navigation/native@5.1.2
 - @react-navigation/routers@5.2.0
 - @react-navigation/stack@5.2.4
2020-03-23 00:00:55 +01:00
Satyajit Sahoo
179b6312fe chore: update prettier 2020-03-22 23:58:06 +01:00
Satyajit Sahoo
043924ca48 fix: fix swipe not dismissing card in RTL
closes #7841
2020-03-22 23:55:16 +01:00
Satyajit Sahoo
813a5903b5 feat: add keys to routes missing keys during reset 2020-03-22 23:38:40 +01:00
Satyajit Sahoo
3709e652f4 feat: support function in listeners prop 2020-03-22 23:33:25 +01:00
Satyajit Sahoo
5b15c7164f fix: return correct value for isFocused after changing screens
fixes #7843
2020-03-22 23:31:04 +01:00
Satyajit Sahoo
e030932497 chore: publish
- @react-navigation/stack@5.2.3
2020-03-19 21:56:36 +01:00
Tien Pham
adbfedcd58 fix: use the correct velocity value in closing animation (#7836)
In this commit f24d3a3461 we modified the `velocity` in inverted gesture, but since we also use this value in the closing animation, the change in that commit also introduced a new bug:
![Mar-20-2020 03-40-05](https://user-images.githubusercontent.com/57227217/77113229-006f0500-6a5d-11ea-97b5-571e8301cd87.gif)

This PR fixes the issue by keeping the original velocity value.
2020-03-19 21:55:03 +01:00
Satyajit Sahoo
bc9b044fb3 chore: publish
- @react-navigation/bottom-tabs@5.2.2
 - @react-navigation/compat@5.1.4
 - @react-navigation/core@5.2.3
 - @react-navigation/drawer@5.3.2
 - @react-navigation/material-bottom-tabs@5.1.4
 - @react-navigation/material-top-tabs@5.1.4
 - @react-navigation/native@5.1.1
 - @react-navigation/stack@5.2.2
2020-03-19 19:48:37 +01:00
Alexey Vlasenko
f24d3a3461 fix: fix closing stack using inverted gesture. (#7824)
Co-authored-by: Satyajit Sahoo <satyajit.happy@gmail.com>
2020-03-19 19:32:29 +01:00
Satyajit Sahoo
3df65e2819 fix: initialize height and width to zero if undefined
closes #6789
2020-03-19 19:03:23 +01:00
Satyajit Sahoo
5c4afc5cb4 fix: close drawer on pressing Esc on web
closes #6745
2020-03-19 18:51:16 +01:00
Satyajit Sahoo
d5bb357053 chore: temporarily disables devtools until we add a public API
closes #7726
2020-03-19 18:39:04 +01:00
Satyajit Sahoo
b1fe73097f fix: only dismiss previously focused input on page change. closes #6918 2020-03-19 18:30:54 +01:00
Satyajit Sahoo
49f6fed6d3 fix: fix blank page if stack was inside display: none before 2020-03-19 18:11:55 +01:00
Satyajit Sahoo
b1a65fc73e fix: don't use react-native-screens on web
seems `react-native-screens` doesn't handle active screens properly and shows a blank page instead on web when a number is specified in the `active` prop.

closes #7485
2020-03-19 17:28:35 +01:00
Noemi Rozpara
3ea8eec432 fix: fix permanent sidebar position (#7830) 2020-03-19 11:44:13 +01:00
Satyajit Sahoo
00e0f05190 chore: publish
- @react-navigation/drawer@5.3.1
2020-03-17 20:13:03 +01:00
Satyajit Sahoo
193c344ba5 refactor: fix useIsDrawerOpen hook 2020-03-17 19:22:12 +01:00
Satyajit Sahoo
358d9e9feb chore: publish
- @react-navigation/bottom-tabs@5.2.1
 - @react-navigation/compat@5.1.3
 - @react-navigation/drawer@5.3.0
 - @react-navigation/material-bottom-tabs@5.1.3
 - @react-navigation/material-top-tabs@5.1.3
 - @react-navigation/native@5.1.0
 - @react-navigation/stack@5.2.1
2020-03-17 14:37:21 +01:00
Satyajit Sahoo
6a5d0a035a feat: add permanent drawer type (#7818)
Co-authored-by: NoemiRozpara <nrozpara@gmail.com>
2020-03-17 14:11:00 +01:00
Satyajit Sahoo
b75744abd5 chore: publish
- @react-navigation/bottom-tabs@5.2.0
 - @react-navigation/compat@5.1.2
 - @react-navigation/core@5.2.2
 - @react-navigation/drawer@5.2.0
 - @react-navigation/material-bottom-tabs@5.1.2
 - @react-navigation/material-top-tabs@5.1.2
 - @react-navigation/native@5.0.10
 - @react-navigation/routers@5.1.1
 - @react-navigation/stack@5.2.0
2020-03-16 14:29:25 +01:00
Satyajit Sahoo
6dbda1a0c2 chore: upgrade depenendecies 2020-03-16 14:28:10 +01:00
Michał Osadnik
70029d6c13 feat: add an option to change use a custom card overlay (#7809)
I find it sometimes useful to define overlay renderer on my own. Eg. I needed to replace the background with BlurView and with this API I find it quite easy

Co-authored-by: Satyajit Sahoo <satyajit.happy@gmail.com>
2020-03-16 14:28:10 +01:00
Tien Pham
469d0542c7 fix: fix back gesture cancellation (#7700)
The problem here is that when we scroll back really fast, even though velocity is negative, `Math.abs(translation + velocity * gestureVelocityImpact)` will end up bigger than `distance / 2`.

I removed the `Math.abs`, I think it's not necessary. When `translation + velocity * gestureVelocityImpact` is negative, it's also < `distance / 2` and we should just close the screen.

Closes #6782
2020-03-16 12:03:16 +01:00
Vojtech Novak
0dcaea3242 fix: fix android header title font weight (#7720)
the previously used fort weight of 500 would effectively be converted to `fontWeight: bold` because of https://github.com/facebook/react-native/pull/25341

this fixes the title appearance to look as customary
2020-03-16 11:05:02 +01:00
Satyajit Sahoo
646cbfb28e refactor: move action helper types to routers 2020-03-13 12:34:37 +01:00
Satyajit Sahoo
660cac3557 fix: don't handle action if no routes are present 2020-03-11 18:17:19 +01:00
Satyajit Sahoo
e637250a7e chore: add a triage action for feature requests 2020-03-11 03:19:49 +01:00
Satyajit Sahoo
82af7bed71 feat: add safeAreaInsets to bottom tabs 2020-03-09 22:21:04 +01:00
Michał Osadnik
cb46d0bca4 feat: make useIsDrawerOpen workable inside drawer content (#7746) 2020-03-06 15:46:13 +01:00
Mike Rogers
b3665a325d Correcting spelling 'Supress' > 'Suppress' (#7731) 2020-03-06 07:24:43 -03:00
Satyajit Sahoo
0cc7a12b9c chore: remove stale action coz it's not keeping issues open after reply 2020-03-04 13:53:42 +01:00
Satyajit Sahoo
90e417248d chore: fix typo in expo preview url 2020-03-03 18:38:20 +01:00
Satyajit Sahoo
e071a978e6 chore: publish
- @react-navigation/bottom-tabs@5.1.1
 - @react-navigation/compat@5.1.1
 - @react-navigation/core@5.2.1
 - @react-navigation/drawer@5.1.1
 - @react-navigation/material-bottom-tabs@5.1.1
 - @react-navigation/material-top-tabs@5.1.1
 - @react-navigation/native@5.0.9
 - @react-navigation/routers@5.1.0
 - @react-navigation/stack@5.1.1
2020-03-03 11:58:45 +01:00
Satyajit Sahoo
296c836064 fix: ignore back button press if screen isn't focused. closes #7673 2020-03-03 11:34:38 +01:00
Satyajit Sahoo
09f6808d7d feat: make reset bubble up 2020-03-01 02:45:08 +01:00
Satyajit Sahoo
5bb0f405ce fix: fix links for documentation 2020-02-28 17:12:18 +01:00
Satyajit Sahoo
2dfa4f3629 fix: move updating state to useEffect 2020-02-28 17:01:58 +01:00
Satyajit Sahoo
cf41288760 chore: run clean before release 2020-02-26 15:03:57 +01:00
Satyajit Sahoo
3677818f63 chore: publish
- @react-navigation/bottom-tabs@5.1.0
 - @react-navigation/compat@5.1.0
 - @react-navigation/core@5.2.0
 - @react-navigation/drawer@5.1.0
 - @react-navigation/material-bottom-tabs@5.1.0
 - @react-navigation/material-top-tabs@5.1.0
 - @react-navigation/native@5.0.8
 - @react-navigation/routers@5.0.3
 - @react-navigation/stack@5.1.0
2020-02-26 13:57:42 +01:00
Satyajit Sahoo
162410843c feat: add ability add listeners with listeners prop
This adds ability to listen to events from the component where the navigator is defined, even if the screen is not rendered.

```js
<Tabs.Screen
  name="Chat"
  component={Chat}
  options={{ title: 'Chat' }}
  listeners={{
    tabPress: e => console.log('Tab press', e.target),
  }}
/>
```

Closes #6756
2020-02-26 13:02:22 +01:00
Satyajit Sahoo
028c2887c6 refactor: tweak error messages more 2020-02-25 20:58:14 +01:00
Satyajit Sahoo
7a44cda136 refactor: tweak error messages 2020-02-25 17:58:09 +01:00
Satyajit Sahoo
a046db536f chore: publish
- @react-navigation/stack@5.0.9
2020-02-24 14:45:00 +01:00
Satyajit Sahoo
d115787b1c chore: mark yarn script as binary 2020-02-24 14:44:29 +01:00
Michał Osadnik
80a337024a fix: enhance border radius in modals on new iPhones (#6945)
Co-authored-by: Satyajit Sahoo <satyajit.happy@gmail.com>
2020-02-24 14:44:20 +01:00
Satyajit Sahoo
c19da31240 refactor: enable screens only for last screen
This will avoid issues such as https://github.com/react-navigation/react-navigation/issues/6909
2020-02-24 11:37:25 +01:00
Satyajit Sahoo
85e9376302 chore: publish
- @react-navigation/stack@5.0.8
2020-02-21 20:09:06 +01:00
Satyajit Sahoo
a67b49477e fix: fix transparent header on Android 2020-02-21 20:07:38 +01:00
Satyajit Sahoo
225cb298b6 chore: publish
- @react-navigation/bottom-tabs@5.0.7
 - @react-navigation/compat@5.0.7
 - @react-navigation/core@5.1.6
 - @react-navigation/drawer@5.0.7
 - @react-navigation/material-bottom-tabs@5.0.7
 - @react-navigation/material-top-tabs@5.0.7
 - @react-navigation/native@5.0.7
 - @react-navigation/routers@5.0.2
 - @react-navigation/stack@5.0.7
2020-02-21 19:18:56 +01:00
Satyajit Sahoo
c8ea4199f4 fix: tweak error message for navigate 2020-02-21 19:13:11 +01:00
Satyajit Sahoo
f16700812f fix: avoid emitting focus events twice
fixes #6749
2020-02-21 18:56:06 +01:00
Satyajit Sahoo
240ce01822 fix: make sure header is visibile to accessibility tools on iOS 2020-02-21 16:30:05 +01:00
Satyajit Sahoo
c7dd3a58b1 fix: debounce back button by default in stack header 2020-02-21 15:31:50 +01:00
Satyajit Sahoo
125bd70e49 fix: preserve screen order with numeric names
fixes #6900
2020-02-21 05:43:32 +01:00
Satyajit Sahoo
4578849ebf chore: publish
- @react-navigation/bottom-tabs@5.0.6
 - @react-navigation/compat@5.0.6
 - @react-navigation/core@5.1.5
 - @react-navigation/drawer@5.0.6
 - @react-navigation/material-bottom-tabs@5.0.6
 - @react-navigation/material-top-tabs@5.0.6
 - @react-navigation/native@5.0.6
 - @react-navigation/stack@5.0.6
2020-02-19 23:34:10 +01:00
Satyajit Sahoo
c084517d7b chore: add release script 2020-02-19 23:30:12 +01:00
Satyajit Sahoo
22c85ff6a9 chore: remove native-stack from the repo
it's now moved to https://github.com/kmagiera/react-native-screens
2020-02-19 23:18:11 +01:00
Satyajit Sahoo
bf76075e0f fix: add accessibilityLabel prop to back button
fixes #6895
2020-02-19 23:09:00 +01:00
Satyajit Sahoo
d69b0db604 fix: add NavigationEvents
See https://github.com/react-navigation/react-navigation/issues/6821#issuecomment-588268512
2020-02-19 23:04:14 +01:00
Satyajit Sahoo
cdb2fed43d chore: update .gitignore 2020-02-18 21:03:18 +01:00
Satyajit Sahoo
bb0226e26d chore: tweak error message 2020-02-18 18:59:39 +01:00
Satyajit Sahoo
1a28c299b5 fix: show descriptive error for invalid return for useFocusEffect 2020-02-15 20:02:39 +01:00
Satyajit Sahoo
e0c3298e64 fix: delay showing drawer by one frame after layout 2020-02-15 19:17:20 +01:00
Satyajit Sahoo
040f5dbb9d refactor: drop use of performTransaction 2020-02-15 19:05:50 +01:00
Satyajit Sahoo
5b7bbbdfd9 chore: publish
- @react-navigation/bottom-tabs@5.0.5
- @react-navigation/compat@5.0.5
- @react-navigation/core@5.1.4
- @react-navigation/drawer@5.0.5
- @react-navigation/material-bottom-tabs@5.0.5
- @react-navigation/material-top-tabs@5.0.5
- @react-navigation/native-stack@5.0.5
- @react-navigation/native@5.0.5
- @react-navigation/stack@5.0.5
2020-02-15 00:18:52 +01:00
Satyajit Sahoo
c5fefc6ee9 chore: tweak versions for peer dependencies 2020-02-15 00:15:01 +01:00
Satyajit Sahoo
aaf01e01e7 fix: return '/' for empty paths 2020-02-14 23:17:29 +01:00
Satyajit Sahoo
ac242fd281 refactor: discard all routes but last when getting action from state 2020-02-14 23:04:09 +01:00
Satyajit Sahoo
c5fcfbd427 fix: link to migration guide on invalid usage 2020-02-14 22:48:04 +01:00
Satyajit Sahoo
424c9469e4 chore: publish
- @react-navigation/bottom-tabs@5.0.4
 - @react-navigation/compat@5.0.4
 - @react-navigation/core@5.1.3
 - @react-navigation/drawer@5.0.4
 - @react-navigation/material-bottom-tabs@5.0.4
 - @react-navigation/material-top-tabs@5.0.4
 - @react-navigation/native-stack@5.0.4
 - @react-navigation/native@5.0.4
 - @react-navigation/stack@5.0.4
2020-02-14 18:50:01 +01:00
Satyajit Sahoo
8f40a98086 fix: hard code header height for animation
closes #6818
2020-02-14 18:44:54 +01:00
Satyajit Sahoo
f964200b0d fix: update links in error messages 2020-02-14 18:32:53 +01:00
Satyajit Sahoo
bd2f008a83 chore: build related changes 2020-02-14 18:32:53 +01:00
Chris
e37d6598ca docs: Update types.tsx (#6849)
Typo :)
2020-02-14 10:43:53 +01:00
Satyajit Sahoo
c8ac5fab61 fix: return false for canGoBack if navigator hasn't finished mounting 2020-02-12 21:28:03 +01:00
Satyajit Sahoo
b6accd03f6 fix: throw a descriptive error if navigation object hasn't initialized 2020-02-12 20:59:58 +01:00
Satyajit Sahoo
0cca1309ec chore: publish
- @react-navigation/bottom-tabs@5.0.3
 - @react-navigation/compat@5.0.3
 - @react-navigation/core@5.1.2
 - @react-navigation/drawer@5.0.3
 - @react-navigation/material-bottom-tabs@5.0.3
 - @react-navigation/material-top-tabs@5.0.3
 - @react-navigation/native-stack@5.0.3
 - @react-navigation/native@5.0.3
 - @react-navigation/stack@5.0.3
2020-02-12 16:58:48 +01:00
Satyajit Sahoo
6c9447a38c fix: check if we can go baack before dispatching pop 2020-02-12 13:17:08 +01:00
Satyajit Sahoo
030c63c89f fix: fix false positives for circular object check
fixes #6827
2020-02-12 11:42:36 +01:00
Abhinandan Ramaprasath
2bf0958502 fix: static container memo check (#6825)
Memo check compared elements of prevProps to nextProps but failed
to take new props into account. Fixed the logic and added a new
test.
2020-02-12 10:56:23 +01:00
Satyajit Sahoo
94cff2380a chore: publish
- @react-navigation/bottom-tabs@5.0.2
 - @react-navigation/compat@5.0.2
 - @react-navigation/core@5.1.1
 - @react-navigation/drawer@5.0.2
 - @react-navigation/material-bottom-tabs@5.0.2
 - @react-navigation/material-top-tabs@5.0.2
 - @react-navigation/native-stack@5.0.2
 - @react-navigation/native@5.0.2
 - @react-navigation/stack@5.0.2
2020-02-11 18:57:48 +01:00
Satyajit Sahoo
359ae1bfac fix: don't cleanup state on switching navigator
This leads to a glitch. Switching navigators should be handled by the router properly.
2020-02-11 18:56:27 +01:00
Satyajit Sahoo
031136f7c8 fix: remove unnecessary borderless from drawer item
closes #6801
2020-02-11 17:21:05 +01:00
Satyajit Sahoo
b6e7e08b9a fix: provide route context to header and bottom tabs 2020-02-11 15:42:00 +01:00
Satyajit Sahoo
6c6102b459 fix: make getInitialState async on web 2020-02-11 15:40:49 +01:00
David Govea
0c59ef7328 fix: initialize keyboard-hiding tabBar to visible=true (#6740, #6799)
Looks like this was an accidental refactor bug introduced with commit
38a38b0 (refactor from 	class component to function component)
2020-02-11 12:16:41 +01:00
Satyajit Sahoo
297eabb90e chore: fix typo 2020-02-11 00:34:04 +01:00
Satyajit Sahoo
b234b035c3 chore: publish
- @react-navigation/bottom-tabs@5.0.1
 - @react-navigation/compat@5.0.1
 - @react-navigation/core@5.1.0
 - @react-navigation/drawer@5.0.1
 - @react-navigation/material-bottom-tabs@5.0.1
 - @react-navigation/material-top-tabs@5.0.1
 - @react-navigation/native-stack@5.0.1
 - @react-navigation/native@5.0.1
 - @react-navigation/routers@5.0.1
 - @react-navigation/stack@5.0.1
2020-02-10 17:22:48 +01:00
Satyajit Sahoo
80629bf30b fix: merge initial params on replace
fixes 6792
2020-02-10 17:19:07 +01:00
Satyajit Sahoo
688d16de5d fix: prevent ripple from bleeding out of drawer item
closes #6801
2020-02-10 16:28:29 +01:00
Satyajit Sahoo
13b4e07348 fix: add some links in the error messages 2020-02-10 16:16:54 +01:00
Satyajit Sahoo
86c39d2e0e refactor: move types and base router to routers package 2020-02-10 16:04:20 +01:00
Satyajit Sahoo
7160a511e6 chore: set initial insets for safe areas 2020-02-10 16:04:20 +01:00
Satyajit Sahoo
ae680a1e3c chore: upgrade depenendecies 2020-02-10 16:04:20 +01:00
Satyajit Sahoo
7c72337c33 chore: lock yarn version
The latest version of yarn has a bug where trying to upgrade a package fails with an error such as 'expected workspace package to exist for ...'. Downgrading to an older version fixes it.
2020-02-10 16:04:20 +01:00
Satyajit Sahoo
6c188addc6 chore: add comment for question label 2020-02-10 13:32:30 +01:00
Satyajit Sahoo
cff2d06adc chore: update contributing guidelines 2020-02-10 09:22:56 +01:00
Satyajit Sahoo
4c9f87df6d chore: update contributing guidelines 2020-02-10 09:17:19 +01:00
Satyajit Sahoo
fa48c9d42a chore: add a action for triage 2020-02-10 08:13:26 +01:00
Satyajit Sahoo
abb595830e chore: update README 2020-02-07 14:10:08 +01:00
Satyajit Sahoo
8ad2922f35 chore: add stale action 2020-02-07 14:05:04 +01:00
Satyajit Sahoo
c715fef2bd chore: update issue templates 2020-02-07 14:01:50 +01:00
Satyajit Sahoo
cea2fc29ba chore: fix missing dev dependency 2020-02-05 15:06:54 +01:00
Satyajit Sahoo
79ab56fe41 chore: release stable version 2020-02-05 14:54:44 +01:00
Satyajit Sahoo
a121844148 chore: prepare for stable release (#354) 2020-02-05 14:47:36 +01:00
Wojciech Lewicki
61b1134f90 feat: support ignoring empty path strings (#349) 2020-02-05 13:31:40 +01:00
Satyajit Sahoo
9fcf3be364 chore: publish
- @react-navigation/stack@5.0.0-alpha.71
2020-02-05 09:43:24 +01:00
Satyajit Sahoo
f746ece61b fix: use addListener only when available 2020-02-05 09:43:00 +01:00
Satyajit Sahoo
264537bdb4 chore: publish
- @react-navigation/bottom-tabs@5.0.0-alpha.45
 - @react-navigation/compat@5.0.0-alpha.34
 - @react-navigation/core@5.0.0-alpha.43
 - @react-navigation/drawer@5.0.0-alpha.47
 - @react-navigation/material-bottom-tabs@5.0.0-alpha.42
 - @react-navigation/material-top-tabs@5.0.0-alpha.41
 - @react-navigation/native-stack@5.0.0-alpha.35
 - @react-navigation/native@5.0.0-alpha.35
 - @react-navigation/routers@5.0.0-alpha.33
 - @react-navigation/stack@5.0.0-alpha.70
2020-02-04 17:34:55 +01:00
Satyajit Sahoo
ca4a36070a fix: improve error message for unhandled action 2020-02-04 17:33:03 +01:00
Wojciech Lewicki
4ca5cc6329 feat: add initialRouteName property to config (#322) 2020-02-04 14:44:57 +01:00
osdnk
25c3fc440f chore: publish
- @react-navigation/bottom-tabs@5.0.0-alpha.44
 - @react-navigation/compat@5.0.0-alpha.33
 - @react-navigation/core@5.0.0-alpha.42
 - @react-navigation/drawer@5.0.0-alpha.46
 - @react-navigation/material-bottom-tabs@5.0.0-alpha.41
 - @react-navigation/material-top-tabs@5.0.0-alpha.40
 - @react-navigation/native-stack@5.0.0-alpha.34
 - @react-navigation/native@5.0.0-alpha.34
 - @react-navigation/routers@5.0.0-alpha.32
 - @react-navigation/stack@5.0.0-alpha.69
2020-02-04 11:04:43 +01:00
Satyajit Sahoo
89fa363883 chore: don't run expo preview for contributors 2020-02-04 10:54:12 +01:00
Michał Osadnik
bec2f754d4 refactor: rename NavigationNativeContainer to NavigationContainer (#344) 2020-02-04 10:21:16 +01:00
Evan Bacon
b277927925 feat: disable pan gesture by default in the browser for Apple devices
fixes #287
2020-02-04 09:12:40 +01:00
osdnk
72993c6463 chore: delete .gitattributes 2020-02-03 14:53:11 +01:00
219 changed files with 158083 additions and 7461 deletions

View File

@@ -31,7 +31,7 @@ jobs:
- run: |
yarn lint
yarn typescript
unit-test:
unit-tests:
<<: *defaults
steps:
- attach_workspace:
@@ -59,7 +59,7 @@ workflows:
- lint-and-typecheck:
requires:
- install-dependencies
- unit-test:
- unit-tests:
requires:
- install-dependencies
- build-packages:

View File

@@ -4,3 +4,7 @@ dist/
lib/
web-build/
web-report/
.expo/
.yarn/
.vscode/

View File

@@ -8,7 +8,6 @@
"@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
.gitattributes vendored
View File

@@ -1 +1 @@
* text eol=lf
yarn-*.js binary

View File

@@ -1,8 +1,8 @@
---
name: Native Stack Navigator
about: Report an issue with Native Stack Navigator (@react-navigation/native-stack)
name: React Navigation 4
about: Report an issue with React Navigation 4
title: ''
labels: bug, package:native-stack
labels: bug, version-4
assignees: ''
---
@@ -29,8 +29,13 @@ assignees: ''
| software | version |
| ------------------------------ | ------- |
| iOS or Android |
| @react-navigation/native |
| @react-navigation/native-stack |
| react-navigation |
| react-navigation-stack |
| react-navigation-tabs |
| react-navigation-drawer |
| react-native-reanimated |
| react-native-gesture-handler |
| react-native-safe-area-context |
| react-native-screens |
| react-native |
| expo |

View File

@@ -1,10 +1,10 @@
blank_issues_enabled: false
contact_links:
- name: Troubleshooting
url: https://reactnavigation.org/docs/en/next/troubleshooting.html
url: https://reactnavigation.org/docs/troubleshooting.html
about: Read how to troubleshoot and fix common issues and mistakes.
- name: Documentation
url: https://next.reactnavigation.org
url: https://reactnavigation.org
about: Read the official documentation.
- name: Feature requests
url: https://react-navigation.canny.io/feature-requests

View File

@@ -5,6 +5,7 @@ jobs:
publish:
name: Install and publish
runs-on: ubuntu-latest
if: github.event.pull_request.head.repo.owner.login == 'react-navigation'
steps:
- name: Checkout
uses: actions/checkout@v1

49
.github/workflows/triage.yml vendored Normal file
View File

@@ -0,0 +1,49 @@
name: Triage
on:
issues:
types: [labeled]
jobs:
needs-more-info:
runs-on: ubuntu-latest
if: github.event.label.name == 'needs more info'
steps:
- uses: actions/checkout@master
- uses: actions/github@v1.0.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
args: comment "Hey! Thanks for opening the issue. Can you provide more information about the issue? Please fill the issue template when opening the issue without deleting any section. We need all the information we can to be able to help. Make sure to at least provide - Current behaviour, Expected behaviour, A way to [reproduce the issue with minimal code](https://stackoverflow.com/help/minimal-reproducible-example) (link to [snack.expo.io](https://snack.expo.io)) or a repo on GitHub, and the information about your environment (such as the platform of the device, exact versions of all the packages mentioned in the template etc.)."
needs-repro:
runs-on: ubuntu-latest
if: github.event.label.name == 'needs repro'
steps:
- uses: actions/checkout@master
- uses: actions/github@v1.0.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
args: comment "Hey! Thanks for opening the issue. Can you provide a [minimal repro](https://stackoverflow.com/help/minimal-reproducible-example) which demonstrates the issue? Posting a snippet of your code in the issue is useful, but it's not usually straightforward to run. A repro will help us debug the issue faster. Please try to keep the repro as small as possible. The easiest way to provide a repro is on [snack.expo.io](https://snack.expo.io). If it's not possible to repro it on [snack.expo.io](https://snack.expo.io), then you can also provide the repro in a GitHub repository."
question:
runs-on: ubuntu-latest
if: github.event.label.name == 'question'
steps:
- uses: actions/checkout@master
- uses: actions/github@v1.0.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
args: comment "Hey! Thanks for opening the issue. The issue tracker is intended for only tracking bug reports. This helps us prioritize fixing bugs in the library. Seems you have a usage question. Please ask the question on [StackOverflow](https://stackoverflow.com/questions/tagged/react-navigation) instead using the `react-navigation` label. You can also chat with other community members on [Reactiflux Discord server](https://www.reactiflux.com/) in the `#react-navigation` channel."
feature-request:
runs-on: ubuntu-latest
if: github.event.label.name == 'feature-request'
steps:
- uses: actions/checkout@master
- uses: actions/github@v1.0.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
args: comment "Hey! Thanks for opening the issue. The issue tracker is intended for only tracking bug reports. Seems you have a feature request. Please post the feature request on [Canny](https://react-navigation.canny.io/feature-requests). This lets other users upvote your feature request and helps us prioritize the most requested features."

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

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

3
.gitignore vendored
View File

@@ -4,6 +4,9 @@
.idea
.expo
.gradle
.project
.settings
.history
local.properties

147155
.yarn/releases/yarn-1.18.0.js vendored Executable file

File diff suppressed because one or more lines are too long

5
.yarnrc Normal file
View File

@@ -0,0 +1,5 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
yarn-path ".yarn/releases/yarn-1.18.0.js"

204
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,204 @@
# Contributing
This library is a community effort: it can only be great if we all help out in one way or another! If you feel like you aren't experienced enough using React Navigation to contribute, you can still make an impact by:
- Responding to one of the open [issues](https://github.com/react-navigation/react-navigation/issues). Even if you can't resolve or fully answer a question, asking for more information or clarity on an issue is extremely beneficial for someone to come after you to resolve the issue.
- Creating public example repositories or [Snacks](https://snack.expo.io/) of navigation problems you have solved and sharing the links.
- Answering questions on [Stack Overflow](https://stackoverflow.com/search?q=react-navigation).
- Answering questions in our [Reactiflux](https://www.reactiflux.com/) channel.
- Providing feedback on the open [PRs](https://github.com/react-navigation/react-navigation/pulls).
- Providing feedback on the open [RFCs](https://github.com/react-navigation/rfcs).
- Improving the [website](https://github.com/react-navigation/react-navigation.github.io).
If you don't know where to start, check the ones with the label [`good first issue`](https://github.com/react-navigation/react-navigation/labels/good%20first%20issue) - even fixing a typo in the documentation is a worthy contribution!
## Development workflow
The project uses a monorepo structure for the packages managed by [yarn workspaces](https://yarnpkg.com/lang/en/docs/workspaces/) and [lerna](https://lerna.js.org). To get started with the project, run `yarn` in the root directory to install the required dependencies for each package:
```sh
yarn
```
While developing, you can run the [example app](/example/) with [Expo](https://expo.io/) to test your changes:
```sh
yarn example start
```
Make sure your code passes TypeScript and ESLint. Run the following to verify:
```sh
yarn typescript
yarn lint
```
To fix formatting errors, run the following:
```sh
yarn lint --fix
```
Remember to add tests for your change if possible. Run the unit tests by:
```sh
yarn test
```
Running the e2e tests with Detox (on iOS) requires the following:
- Mac with macOS (at least macOS High Sierra 10.13.6)
- Xcode 10.1+ with Xcode command line tools
First you need to install `applesimutils` and `detox-cli`:
```sh
brew tap wix/brew
brew install applesimutils
yarn global add detox-cli
```
Then you can build and run the tests:
```sh
detox build -c ios.sim.debug
detox test -c ios.sim.debug
```
### Commit message convention
We follow the [conventional commits specification](https://www.conventionalcommits.org/en) for our commit messages:
- `fix`: bug fixes, e.g. fix crash due to deprecated method.
- `feat`: new features, e.g. add new method to the module.
- `refactor`: code refactor, e.g. migrate from class components to hooks.
- `docs`: changes into documentation, e.g. add usage example for the module..
- `test`: adding or updating tests, eg add integration tests using detox.
- `chore`: tooling changes, e.g. change CI config.
Our pre-commit hooks verify that your commit message matches this format when committing.
### Linting and tests
[ESLint](https://eslint.org/), [Prettier](https://prettier.io/), [TypeScript](https://www.typescriptlang.org/)
We use [TypeScript](https://www.typescriptlang.org/) for type checking, [ESLint](https://eslint.org/) with [Prettier](https://prettier.io/) for linting and formatting the code, and [Jest](https://jestjs.io/) for testing.
Our pre-commit hooks verify that the linter and tests pass when committing.
### Scripts
The `package.json` file contains various scripts for common tasks:
- `yarn install`: setup project by installing all dependencies and pods.
- `yarn typescript`: type-check files with TypeScript.
- `yarn lint`: lint files with ESLint.
- `yarn test`: run unit tests with Jest.
- `yarn example start`: run the example app with Expo.
### Sending a pull request
> **Working on your first pull request?** You can learn how from this _free_ series: [How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github).
When you're sending a pull request:
- Prefer small pull requests focused on one change.
- Verify that linters and tests are passing.
- Review the documentation to make sure it looks good.
- Follow the pull request template when opening a pull request.
- For pull requests that change the API or implementation, discuss with maintainers first by opening an issue.
## Publishing
Maintainers with write access to the GitHub repo and the npm organization can publish new versions. To publish a new version, first, you need to export a `GH_TOKEN` environment variable as mentioned [here](https://github.com/lerna/lerna/tree/master/commands/version#--create-release-type). Then run:
```sh
yarn release
```
This will automatically bump the version and publish the packages. It'll also publish the changelogs on GitHub for each package.
## Code of Conduct
### Our Pledge
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
### Our Standards
Examples of behavior that contributes to a positive environment for our community include:
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
- Focusing on what is best not just for us as individuals, but for the overall community
Examples of unacceptable behavior include:
- The use of sexualized language or imagery, and sexual attention or
advances of any kind
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email
address, without their explicit permission
- Other conduct which could reasonably be considered inappropriate in a
professional setting
### Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
### Scope
This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
### Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to Brent Vatne ([brentvatne@gmail.com](mailto:brentvatne@gmail.com)), Satyajit Sahoo ([satyajit.happy@gmail.com](mailto:satyajit.happy@gmail.com)) or Michał Osadnik ([micosa97@gmail.com](mailto:micosa97@gmail.com)). All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the reporter of any incident.
### Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
#### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
#### 2. Warning
**Community Impact**: A violation through a single incident or series of actions.
**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
#### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
#### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within the community.
### Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0,
available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.

View File

@@ -4,72 +4,15 @@
[![Code Coverage][coverage-badge]][coverage]
[![MIT License][license-badge]][license]
Routing and navigation for your React Native apps with a component-first API.
Routing and navigation for your React Native apps.
Documentation can be found at [next.reactnavigation.org](https://next.reactnavigation.org/).
Documentation can be found at [reactnavigation.org](https://reactnavigation.org/).
If you are looking for version 4, the code can be found in the [4.x branch](https://github.com/react-navigation/react-navigation/tree/4.x).
## Contributing
The project uses a monorepo structure for the packages managed by [yarn workspaces](https://yarnpkg.com/lang/en/docs/workspaces/) and [lerna](https://lerna.js.org). To get started with the project, run `yarn` in the root directory to install the required dependencies for each package:
```sh
yarn
```
While developing, you can run the [example app](/example/) with [Expo](https://expo.io/) to test your changes:
```sh
yarn example start
```
Make sure your code passes TypeScript and ESLint. Run the following to verify:
```sh
yarn typescript
yarn lint
```
To fix formatting errors, run the following:
```sh
yarn lint --fix
```
Remember to add tests for your change if possible. Run the unit tests by:
```sh
yarn test
```
Running the e2e tests with Detox (on iOS) requires the following:
- Mac with macOS (at least macOS High Sierra 10.13.6)
- Xcode 10.1+ with Xcode command line tools
First you need to install `applesimutils` and `detox-cli`:
```sh
brew tap wix/brew
brew install applesimutils
yarn global add detox-cli
```
Then you can build and run the tests:
```sh
detox build -c ios.sim.debug
detox test -c ios.sim.debug
```
## Publishing
To publish a new version, first we need to export a `GH_TOKEN` environment variable as mentioned [here](https://github.com/lerna/lerna/tree/master/commands/version#--create-release-type). Then run:
```sh
yarn lerna publish
```
This will automatically bump the version and publish the packages. It'll also publish the changelogs on GitHub for each package.
Please read through our [contribution guide](CONTRIBUTING.md) a to get started!
## Installing from a fork on GitHub
@@ -106,9 +49,9 @@ Remember to replace `<user>`, `<repo>` and `<name>` with right values.
<!-- badges -->
[build-badge]: https://img.shields.io/circleci/project/github/react-navigation/navigation-ex/master.svg?style=flat-square
[build]: https://circleci.com/gh/react-navigation/navigation-ex
[coverage-badge]: https://img.shields.io/codecov/c/github/react-navigation/navigation-ex.svg?style=flat-square
[coverage]: https://codecov.io/github/react-navigation/navigation-ex
[build-badge]: https://img.shields.io/circleci/project/github/react-navigation/react-navigation/master.svg?style=flat-square
[build]: https://circleci.com/gh/react-navigation/react-navigation
[coverage-badge]: https://img.shields.io/codecov/c/github/react-navigation/react-navigation.svg?style=flat-square
[coverage]: https://codecov.io/github/react-navigation/react-navigation
[license-badge]: https://img.shields.io/npm/l/@react-navigation/core.svg?style=flat-square
[license]: https://opensource.org/licenses/MIT

View File

@@ -1,230 +0,0 @@
# Change Log
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [5.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)
### Bug Fixes
* close drawer on navigate ([655a220](https://github.com/react-navigation/navigation-ex/commit/655a220))
# [5.0.0-alpha.7](https://github.com/satya164/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.6...@react-navigation/example@5.0.0-alpha.7) (2019-09-16)
### Features
* compatibility layer ([e0f28a4](https://github.com/satya164/navigation-ex/commit/e0f28a4))
* make example run as bare react-native project as well ([#85](https://github.com/satya164/navigation-ex/issues/85)) ([d16c20c](https://github.com/satya164/navigation-ex/commit/d16c20c))
# [5.0.0-alpha.6](https://github.com/satya164/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.4...@react-navigation/example@5.0.0-alpha.6) (2019-08-31)
**Note:** Version bump only for package @react-navigation/example
# [5.0.0-alpha.5](https://github.com/satya164/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.4...@react-navigation/example@5.0.0-alpha.5) (2019-08-31)
**Note:** Version bump only for package @react-navigation/example
# [5.0.0-alpha.4](https://github.com/satya164/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.3...@react-navigation/example@5.0.0-alpha.4) (2019-08-31)
### Bug Fixes
* handle route names change when all routes are removed ([#86](https://github.com/satya164/navigation-ex/issues/86)) ([1b2983e](https://github.com/satya164/navigation-ex/commit/1b2983e))
# [5.0.0-alpha.3](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.2...@react-navigation/example@5.0.0-alpha.3) (2019-08-29)
### Features
* handle more methods in useScrollToTop ([f9e8c7e](https://github.com/react-navigation/navigation-ex/commit/f9e8c7e))
# [5.0.0-alpha.2](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.1...@react-navigation/example@5.0.0-alpha.2) (2019-08-27)
### Features
* add native container ([d26b77f](https://github.com/react-navigation/navigation-ex/commit/d26b77f))
# 5.0.0-alpha.1 (2019-08-21)
### Bug Fixes
* add margin on left when left button is specified in header ([f1f1541](https://github.com/satya164/navigation-ex/commit/f1f1541))
### Features
* add a simple stack and material tabs integration ([#39](https://github.com/satya164/navigation-ex/issues/39)) ([e0bee10](https://github.com/satya164/navigation-ex/commit/e0bee10))
* add hook for deep link support ([35987ae](https://github.com/satya164/navigation-ex/commit/35987ae))
* add integration for paper's bottom navigation ([f3b6d1f](https://github.com/satya164/navigation-ex/commit/f3b6d1f))
* add native container with back button integration ([#48](https://github.com/satya164/navigation-ex/issues/48)) ([b7735af](https://github.com/satya164/navigation-ex/commit/b7735af))
* integrate reanimated based stack ([#42](https://github.com/satya164/navigation-ex/issues/42)) ([dcf57c0](https://github.com/satya164/navigation-ex/commit/dcf57c0))

View File

@@ -13,7 +13,7 @@
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.gradle" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="jdk" jdkName="1.8" jdkType="JavaSDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@@ -161,32 +161,33 @@
<orderEntry type="library" name="Gradle: com.facebook.fresco:imagepipeline-okhttp3:2.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: io.nlopez.smartlocation:library:3.2.11@aar" level="project" />
<orderEntry type="library" name="Gradle: org.webkit:android-jsc:r245459@aar" level="project" />
<orderEntry type="module" module-name="android-expo-permissions" />
<orderEntry type="module" module-name="android-expo-constants" />
<orderEntry type="module" module-name="android-unimodules-image-loader-interface" />
<orderEntry type="module" module-name="android-expo-web-browser" />
<orderEntry type="module" module-name="android-unimodules-react-native-adapter" />
<orderEntry type="module" module-name="android-expo-file-system" />
<orderEntry type="module" module-name="android-expo-location" />
<orderEntry type="module" module-name="android-expo-error-recovery" />
<orderEntry type="module" module-name="android-unimodules-permissions-interface" />
<orderEntry type="module" module-name="android-unimodules-core" />
<orderEntry type="module" module-name="android-expo-app-loader-provider" />
<orderEntry type="module" module-name="android-expo-font" />
<orderEntry type="module" module-name="android-expo-keep-awake" />
<orderEntry type="module" module-name="android-expo-linear-gradient" />
<orderEntry type="module" module-name="android-expo-sqlite" />
<orderEntry type="module" module-name="android-unimodules-barcode-scanner-interface" />
<orderEntry type="module" module-name="android-unimodules-camera-interface" />
<orderEntry type="module" module-name="android-unimodules-constants-interface" />
<orderEntry type="module" module-name="android-unimodules-face-detector-interface" />
<orderEntry type="module" module-name="android-unimodules-file-system-interface" />
<orderEntry type="module" module-name="android-unimodules-font-interface" />
<orderEntry type="module" module-name="android-unimodules-sensors-interface" />
<orderEntry type="module" module-name="android-unimodules-task-manager-interface" />
<orderEntry type="module" module-name="android-@react-native-community_masked-view" />
<orderEntry type="module" module-name="android-react-native-gesture-handler" />
<orderEntry type="module" module-name="android-react-native-reanimated" />
<orderEntry type="module" module-name="expo-permissions" />
<orderEntry type="module" module-name="expo-constants" />
<orderEntry type="module" module-name="unimodules-image-loader-interface" />
<orderEntry type="module" module-name="expo-web-browser" />
<orderEntry type="module" module-name="unimodules-react-native-adapter" />
<orderEntry type="module" module-name="expo-file-system" />
<orderEntry type="module" module-name="expo-location" />
<orderEntry type="module" module-name="expo-error-recovery" />
<orderEntry type="module" module-name="unimodules-permissions-interface" />
<orderEntry type="module" module-name="unimodules-core" />
<orderEntry type="module" module-name="expo-app-loader-provider" />
<orderEntry type="module" module-name="expo-font" />
<orderEntry type="module" module-name="expo-keep-awake" />
<orderEntry type="module" module-name="expo-linear-gradient" />
<orderEntry type="module" module-name="expo-sqlite" />
<orderEntry type="module" module-name="unimodules-barcode-scanner-interface" />
<orderEntry type="module" module-name="unimodules-camera-interface" />
<orderEntry type="module" module-name="unimodules-constants-interface" />
<orderEntry type="module" module-name="unimodules-face-detector-interface" />
<orderEntry type="module" module-name="unimodules-file-system-interface" />
<orderEntry type="module" module-name="unimodules-font-interface" />
<orderEntry type="module" module-name="unimodules-sensors-interface" />
<orderEntry type="module" module-name="unimodules-task-manager-interface" />
<orderEntry type="module" module-name="@react-native-community_masked-view" />
<orderEntry type="module" module-name="react-native-gesture-handler" />
<orderEntry type="module" module-name="react-native-reanimated" />
<orderEntry type="module" module-name="react-native-restart" />
<orderEntry type="module" module-name="react-native-safe-area-context" />
<orderEntry type="module" module-name="react-native-screens" />
</component>

View File

@@ -7,7 +7,7 @@
"slug": "react-navigation-example",
"description": "Demo app to showcase various functionality of React Navigation",
"privacy": "public",
"sdkVersion": "36.0.0",
"sdkVersion": "37.0.0",
"platforms": [
"ios",
"android",

View File

@@ -1,4 +1,4 @@
module.exports = function(api) {
module.exports = function (api) {
api.cache(true);
return {
presets: ['babel-preset-expo'],

View File

@@ -1,4 +1,4 @@
/* eslint-disable jest/no-jasmine-globals, import/no-commonjs */
/* eslint-disable import/no-commonjs */
const detox = require('detox');
const config = require('../../package.json').detox;

View File

@@ -15,8 +15,8 @@ const modules = ['@expo/vector-icons']
// List all packages under `packages/`
.readdirSync(packages)
// Ignore hidden files such as .DS_Store
.filter(p => !p.startsWith('.'))
.map(p => {
.filter((p) => !p.startsWith('.'))
.map((p) => {
const pak = JSON.parse(
fs.readFileSync(path.join(packages, p, 'package.json'), 'utf8')
);
@@ -50,9 +50,9 @@ module.exports = {
blacklistRE: blacklist(
fs
.readdirSync(packages)
.map(p => path.join(packages, p))
.map((p) => path.join(packages, p))
.map(
it => new RegExp(`^${escape(path.join(it, 'node_modules'))}\\/.*$`)
(it) => new RegExp(`^${escape(path.join(it, 'node_modules'))}\\/.*$`)
)
),
@@ -65,7 +65,7 @@ module.exports = {
},
server: {
enhanceMiddleware: middleware => {
enhanceMiddleware: (middleware) => {
return (req, res, next) => {
// When an asset is imported outside the project root, it has wrong path on Android
// This happens for the back button in stack, so we fix the path to correct one

View File

@@ -1,7 +1,7 @@
{
"name": "@react-navigation/example",
"description": "Demo app to showcase various functionality of React Navigation",
"version": "5.0.0-alpha.23",
"version": "5.0.0",
"private": true,
"scripts": {
"start": "expo start",
@@ -12,32 +12,30 @@
},
"dependencies": {
"@expo/vector-icons": "^10.0.0",
"@react-native-community/masked-view": "0.1.5",
"@types/react-native-restart": "^0.0.0",
"@react-native-community/masked-view": "^0.1.7",
"color": "^3.1.2",
"expo": "^36.0.2",
"expo-asset": "~8.0.0",
"expo-blur": "^8.0.0",
"expo": "^37.0.0",
"expo-asset": "~8.1.3",
"expo-blur": "~8.1.0",
"react": "~16.9.0",
"react-dom": "~16.9.0",
"react-native": "~0.61.5",
"react-native-gesture-handler": "^1.5.5",
"react-native-paper": "^3.5.0",
"react-native-reanimated": "^1.4.0",
"react-native-restart": "^0.0.13",
"react-native-safe-area-context": "^0.6.2",
"react-native-screens": "^2.0.0-alpha.33",
"react-native-tab-view": "2.13.0",
"react-native-unimodules": "^0.7.0",
"react-native-gesture-handler": "^1.6.0",
"react-native-paper": "^3.7.0",
"react-native-reanimated": "^1.7.0",
"react-native-restart": "^0.0.14",
"react-native-safe-area-context": "^0.7.3",
"react-native-screens": "^2.3.0",
"react-native-tab-view": "2.14.0",
"react-native-unimodules": "~0.8.1",
"react-native-web": "^0.11.7"
},
"devDependencies": {
"@babel/core": "^7.7.7",
"@expo/webpack-config": "^0.10.9",
"@types/react": "^16.9.17",
"@types/react-native": "^0.60.30",
"babel-preset-expo": "^8.0.0",
"expo-cli": "^3.11.5",
"typescript": "^3.7.4"
"@expo/webpack-config": "^0.11.19",
"@types/react": "^16.9.23",
"@types/react-native": "^0.60.22",
"babel-preset-expo": "^8.1.0",
"expo-cli": "^3.17.18",
"typescript": "^3.8.3"
}
}

View File

@@ -28,7 +28,7 @@ export default function BottomTabsScreen() {
return (
<BottomTabs.Navigator
screenOptions={{
tabBarButton: props => <TouchableBounce {...props} />,
tabBarButton: (props) => <TouchableBounce {...props} />,
}}
>
<BottomTabs.Screen
@@ -38,7 +38,7 @@ export default function BottomTabsScreen() {
tabBarIcon: getTabBarIcon('file-document-box'),
}}
>
{props => <SimpleStackScreen {...props} headerMode="none" />}
{(props) => <SimpleStackScreen {...props} headerMode="none" />}
</BottomTabs.Screen>
<BottomTabs.Screen
name="Chat"

View File

@@ -15,7 +15,7 @@ export default function BottomTabsScreen() {
return (
<BottomTabs.Navigator>
{tabs.map(i => (
{tabs.map((i) => (
<BottomTabs.Screen
key={i}
name={`tab-${i}`}
@@ -29,12 +29,14 @@ export default function BottomTabsScreen() {
{() => (
<View style={styles.container}>
<Title>Tab {i}</Title>
<Button onPress={() => setTabs(tabs => [...tabs, tabs.length])}>
<Button onPress={() => setTabs((tabs) => [...tabs, tabs.length])}>
Add a tab
</Button>
<Button
onPress={() =>
setTabs(tabs => (tabs.length > 1 ? tabs.slice(0, -1) : tabs))
setTabs((tabs) =>
tabs.length > 1 ? tabs.slice(0, -1) : tabs
)
}
>
Remove a tab

View File

@@ -0,0 +1,127 @@
import * as React from 'react';
import { Dimensions, ScaledSize } from 'react-native';
import { Appbar } from 'react-native-paper';
import { ParamListBase } from '@react-navigation/native';
import { StackNavigationProp } from '@react-navigation/stack';
import {
createDrawerNavigator,
DrawerNavigationProp,
DrawerContent,
} from '@react-navigation/drawer';
import Article from '../Shared/Article';
import Albums from '../Shared/Albums';
import NewsFeed from '../Shared/NewsFeed';
type DrawerParams = {
Article: undefined;
NewsFeed: undefined;
Album: undefined;
};
type DrawerNavigation = DrawerNavigationProp<DrawerParams>;
const useIsLargeScreen = () => {
const [dimensions, setDimensions] = React.useState(Dimensions.get('window'));
React.useEffect(() => {
const onDimensionsChange = ({ window }: { window: ScaledSize }) => {
setDimensions(window);
};
Dimensions.addEventListener('change', onDimensionsChange);
return () => Dimensions.removeEventListener('change', onDimensionsChange);
}, []);
return dimensions.width > 414;
};
const Header = ({
onGoBack,
title,
}: {
onGoBack: () => void;
title: string;
}) => {
const isLargeScreen = useIsLargeScreen();
return (
<Appbar.Header>
{isLargeScreen ? null : <Appbar.BackAction onPress={onGoBack} />}
<Appbar.Content title={title} />
</Appbar.Header>
);
};
const ArticleScreen = ({ navigation }: { navigation: DrawerNavigation }) => {
return (
<>
<Header title="Article" onGoBack={() => navigation.toggleDrawer()} />
<Article />
</>
);
};
const NewsFeedScreen = ({ navigation }: { navigation: DrawerNavigation }) => {
return (
<>
<Header title="Feed" onGoBack={() => navigation.toggleDrawer()} />
<NewsFeed />
</>
);
};
const AlbumsScreen = ({ navigation }: { navigation: DrawerNavigation }) => {
return (
<>
<Header title="Albums" onGoBack={() => navigation.toggleDrawer()} />
<Albums />
</>
);
};
const Drawer = createDrawerNavigator<DrawerParams>();
type Props = Partial<React.ComponentProps<typeof Drawer.Navigator>> & {
navigation: StackNavigationProp<ParamListBase>;
};
export default function DrawerScreen({ navigation, ...rest }: Props) {
navigation.setOptions({
headerShown: false,
gestureEnabled: false,
});
const isLargeScreen = useIsLargeScreen();
return (
<Drawer.Navigator
openByDefault
drawerType={isLargeScreen ? 'permanent' : 'back'}
drawerStyle={isLargeScreen ? null : { width: '100%' }}
overlayColor="transparent"
drawerContent={(props) => (
<>
<Appbar.Header>
<Appbar.Action icon="close" onPress={() => navigation.goBack()} />
<Appbar.Content title="Pages" />
</Appbar.Header>
<DrawerContent {...props} />
</>
)}
{...rest}
>
<Drawer.Screen name="Article" component={ArticleScreen} />
<Drawer.Screen
name="NewsFeed"
component={NewsFeedScreen}
options={{ title: 'Feed' }}
/>
<Drawer.Screen
name="Album"
component={AlbumsScreen}
options={{ title: 'Album' }}
/>
</Drawer.Navigator>
);
}

View File

@@ -28,7 +28,7 @@ export default function MaterialBottomTabsScreen() {
tabBarColor: '#C9E7F8',
}}
>
{props => <SimpleStackScreen {...props} headerMode="none" />}
{(props) => <SimpleStackScreen {...props} headerMode="none" />}
</MaterialBottomTabs.Screen>
<MaterialBottomTabs.Screen
name="Chat"

View File

@@ -1,233 +0,0 @@
import * as React from 'react';
import { View, Text, ScrollView, StyleSheet } from 'react-native';
import { Button } from 'react-native-paper';
// eslint-disable-next-line import/no-unresolved
import { enableScreens } from 'react-native-screens';
import {
RouteProp,
ParamListBase,
useFocusEffect,
useTheme,
} from '@react-navigation/native';
import { DrawerNavigationProp } from '@react-navigation/drawer';
import { StackNavigationProp } from '@react-navigation/stack';
import {
createNativeStackNavigator,
NativeStackNavigationProp,
} from '@react-navigation/native-stack';
import Albums from '../Shared/Albums';
type NativeStackParams = {
Article: { author: string };
Album: undefined;
};
type NativeStackNavigation = NativeStackNavigationProp<NativeStackParams>;
const Title = ({ children }: { children: React.ReactNode }) => {
const { colors } = useTheme();
return <Text style={[styles.title, { color: colors.text }]}>{children}</Text>;
};
const Paragraph = ({ children }: { children: React.ReactNode }) => {
const { colors } = useTheme();
return (
<Text style={[styles.paragraph, { color: colors.text }]}>{children}</Text>
);
};
const ArticleScreen = ({
navigation,
}: {
navigation: NativeStackNavigation;
route: RouteProp<NativeStackParams, 'Article'>;
}) => {
const { colors } = useTheme();
return (
<ScrollView
style={{ backgroundColor: colors.card }}
contentContainerStyle={styles.content}
>
<View style={styles.buttons}>
<Button
mode="contained"
onPress={() => navigation.push('Album')}
style={styles.button}
>
Push album
</Button>
<Button
mode="outlined"
onPress={() => navigation.goBack()}
style={styles.button}
>
Go back
</Button>
</View>
<Title>What is Lorem Ipsum?</Title>
<Paragraph>
Lorem Ipsum is simply dummy text of the printing and typesetting
industry. Lorem Ipsum has been the industry&apos;s standard dummy text
ever since the 1500s, when an unknown printer took a galley of type and
scrambled it to make a type specimen book. It has survived not only five
centuries, but also the leap into electronic typesetting, remaining
essentially unchanged. It was popularised in the 1960s with the release
of Letraset sheets containing Lorem Ipsum passages, and more recently
with desktop publishing software like Aldus PageMaker including versions
of Lorem Ipsum.
</Paragraph>
<Title>Where does it come from?</Title>
<Paragraph>
Contrary to popular belief, Lorem Ipsum is not simply random text. It
has roots in a piece of classical Latin literature from 45 BC, making it
over 2000 years old. Richard McClintock, a Latin professor at
Hampden-Sydney College in Virginia, looked up one of the more obscure
Latin words, consectetur, from a Lorem Ipsum passage, and going through
the cites of the word in classical literature, discovered the
undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33
of &quot;de Finibus Bonorum et Malorum&quot; (The Extremes of Good and
Evil) by Cicero, written in 45 BC. This book is a treatise on the theory
of ethics, very popular during the Renaissance. The first line of Lorem
Ipsum, &quot;Lorem ipsum dolor sit amet..&quot;, comes from a line in
section 1.10.32.
</Paragraph>
<Paragraph>
The standard chunk of Lorem Ipsum used since the 1500s is reproduced
below for those interested. Sections 1.10.32 and 1.10.33 from &quot;de
Finibus Bonorum et Malorum&quot; by Cicero are also reproduced in their
exact original form, accompanied by English versions from the 1914
translation by H. Rackham.
</Paragraph>
<Title>Why do we use it?</Title>
<Paragraph>
It is a long established fact that a reader will be distracted by the
readable content of a page when looking at its layout. The point of
using Lorem Ipsum is that it has a more-or-less normal distribution of
letters, as opposed to using &quot;Content here, content here&quot;,
making it look like readable English. Many desktop publishing packages
and web page editors now use Lorem Ipsum as their default model text,
and a search for &quot;lorem ipsum&quot; will uncover many web sites
still in their infancy. Various versions have evolved over the years,
sometimes by accident, sometimes on purpose (injected humour and the
like).
</Paragraph>
<Title>Where can I get some?</Title>
<Paragraph>
There are many variations of passages of Lorem Ipsum available, but the
majority have suffered alteration in some form, by injected humour, or
randomised words which don&apos;t look even slightly believable. If you
are going to use a passage of Lorem Ipsum, you need to be sure there
isn&apos;t anything embarrassing hidden in the middle of text. All the
Lorem Ipsum generators on the Internet tend to repeat predefined chunks
as necessary, making this the first true generator on the Internet. It
uses a dictionary of over 200 Latin words, combined with a handful of
model sentence structures, to generate Lorem Ipsum which looks
reasonable. The generated Lorem Ipsum is therefore always free from
repetition, injected humour, or non-characteristic words etc.
</Paragraph>
</ScrollView>
);
};
const AlbumsScreen = ({
navigation,
}: {
navigation: NativeStackNavigation;
}) => (
<ScrollView>
<View style={styles.buttons}>
<Button
mode="contained"
onPress={() => navigation.push('Article', { author: 'Babel fish' })}
style={styles.button}
>
Push article
</Button>
<Button
mode="outlined"
onPress={() => navigation.goBack()}
style={styles.button}
>
Go back
</Button>
</View>
<Albums scrollEnabled={false} />
</ScrollView>
);
const NativeStack = createNativeStackNavigator<NativeStackParams>();
type Props = {
navigation: StackNavigationProp<ParamListBase>;
};
export default function NativeStackScreen({ navigation }: Props) {
navigation.setOptions({
headerShown: false,
});
useFocusEffect(
React.useCallback(() => {
const drawer = navigation.dangerouslyGetParent() as DrawerNavigationProp<
ParamListBase
>;
navigation.setOptions({ gestureEnabled: false });
drawer.setOptions({ gestureEnabled: false });
return () => {
navigation.setOptions({ gestureEnabled: true });
drawer.setOptions({ gestureEnabled: true });
};
}, [navigation])
);
return (
<NativeStack.Navigator>
<NativeStack.Screen
name="Article"
component={ArticleScreen}
options={{
title: 'Lorem Ipsum',
headerLargeTitle: true,
headerHideShadow: true,
}}
/>
<NativeStack.Screen
name="Album"
component={AlbumsScreen}
options={{ title: 'Album' }}
/>
</NativeStack.Navigator>
);
}
enableScreens(true);
const styles = StyleSheet.create({
buttons: {
flexDirection: 'row',
padding: 8,
},
button: {
margin: 8,
},
content: {
paddingVertical: 16,
},
title: {
fontWeight: 'bold',
fontSize: 24,
marginVertical: 8,
marginHorizontal: 16,
},
paragraph: {
fontSize: 16,
lineHeight: 24,
marginVertical: 8,
marginHorizontal: 16,
},
});

View File

@@ -1,23 +0,0 @@
import * as React from 'react';
import { View, Text, StyleSheet } from 'react-native';
export default function NativeStack() {
return (
<View style={styles.container}>
<Text style={styles.text}>Not supported on Web :(</Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#eceff1',
},
text: {
fontSize: 16,
color: '#999',
},
});

View File

@@ -9,6 +9,7 @@ import {
ScrollViewProps,
Dimensions,
Platform,
ScaledSize,
} from 'react-native';
import { useScrollToTop } from '@react-navigation/native';
@@ -40,15 +41,38 @@ const COVERS = [
];
export default function Albums(props: Partial<ScrollViewProps>) {
const [dimensions, setDimensions] = React.useState(Dimensions.get('window'));
React.useEffect(() => {
const onDimensionsChange = ({ window }: { window: ScaledSize }) => {
setDimensions(window);
};
Dimensions.addEventListener('change', onDimensionsChange);
return () => Dimensions.removeEventListener('change', onDimensionsChange);
}, []);
const ref = React.useRef<ScrollView>(null);
useScrollToTop(ref);
const itemSize = dimensions.width / Math.floor(dimensions.width / 150);
return (
<ScrollView ref={ref} contentContainerStyle={styles.content} {...props}>
{COVERS.map((source, i) => (
// eslint-disable-next-line react/no-array-index-key
<View key={i} style={styles.item}>
<View
// eslint-disable-next-line react/no-array-index-key
key={i}
style={[
styles.item,
Platform.OS !== 'web' && {
height: itemSize,
width: itemSize,
},
]}
>
<Image source={source} style={styles.photo} />
</View>
))}
@@ -76,10 +100,6 @@ const styles = StyleSheet.create({
flexDirection: 'row',
flexWrap: 'wrap',
},
item: {
height: Dimensions.get('window').width / 2,
width: '50%',
},
},
}),
photo: {

View File

@@ -68,10 +68,7 @@ export default function Chat(props: Partial<ScrollViewProps>) {
styles.input,
{ backgroundColor: colors.card, color: colors.text },
]}
placeholderTextColor={Color(colors.text)
.alpha(0.5)
.rgb()
.string()}
placeholderTextColor={Color(colors.text).alpha(0.5).rgb().string()}
placeholder="Write a message"
underlineColorAndroid="transparent"
/>

View File

@@ -51,10 +51,7 @@ export default function NewsFeed(props: Props) {
<Card style={styles.card}>
<TextInput
placeholder="What's on your mind?"
placeholderTextColor={Color(colors.text)
.alpha(0.5)
.rgb()
.string()}
placeholderTextColor={Color(colors.text).alpha(0.5).rgb().string()}
style={styles.input}
/>
</Card>

View File

@@ -6,8 +6,14 @@ import {
Platform,
StatusBar,
I18nManager,
Dimensions,
ScaledSize,
} from 'react-native';
// eslint-disable-next-line import/no-unresolved
import { enableScreens } from 'react-native-screens';
import RNRestart from 'react-native-restart';
import { Updates } from 'expo';
import { Asset } from 'expo-asset';
import { MaterialIcons } from '@expo/vector-icons';
import {
Provider as PaperProvider,
@@ -17,12 +23,11 @@ import {
List,
Divider,
} from 'react-native-paper';
import { Asset } from 'expo-asset';
import {
InitialState,
useLinking,
NavigationContainerRef,
NavigationNativeContainer,
NavigationContainer,
DefaultTheme,
DarkTheme,
} from '@react-navigation/native';
@@ -38,8 +43,8 @@ import {
} from '@react-navigation/stack';
import LinkingPrefixes from './LinkingPrefixes';
import SettingsItem from './Shared/SettingsItem';
import SimpleStack from './Screens/SimpleStack';
import NativeStack from './Screens/NativeStack';
import ModalPresentationStack from './Screens/ModalPresentationStack';
import StackTransparent from './Screens/StackTransparent';
import StackHeaderCustomization from './Screens/StackHeaderCustomization';
@@ -49,11 +54,12 @@ import MaterialBottomTabs from './Screens/MaterialBottomTabs';
import DynamicTabs from './Screens/DynamicTabs';
import AuthFlow from './Screens/AuthFlow';
import CompatAPI from './Screens/CompatAPI';
import SettingsItem from './Shared/SettingsItem';
import { Updates } from 'expo';
import MasterDetail from './Screens/MasterDetail';
YellowBox.ignoreWarnings(['Require cycle:', 'Warning: Async Storage']);
enableScreens();
type RootDrawerParamList = {
Root: undefined;
Another: undefined;
@@ -67,7 +73,6 @@ type RootStackParamList = {
const SCREENS = {
SimpleStack: { title: 'Simple Stack', component: SimpleStack },
NativeStack: { title: 'Native Stack', component: NativeStack },
ModalPresentationStack: {
title: 'Modal Presentation Stack',
component: ModalPresentationStack,
@@ -93,6 +98,10 @@ const SCREENS = {
title: 'Dynamic Tabs',
component: DynamicTabs,
},
MasterDetail: {
title: 'Master Detail',
component: MasterDetail,
},
AuthFlow: {
title: 'Auth Flow',
component: AuthFlow,
@@ -112,7 +121,7 @@ const THEME_PERSISTENCE_KEY = 'THEME_TYPE';
Asset.loadAsync(StackAssets);
export default function App() {
const containerRef = React.useRef<NavigationContainerRef>();
const containerRef = React.useRef<NavigationContainerRef>(null);
// To test deep linking on, run the following in the Terminal:
// Android: adb shell am start -a android.intent.action.VIEW -d "exp://127.0.0.1:19000/--/simple-stack"
@@ -124,7 +133,8 @@ export default function App() {
prefixes: LinkingPrefixes,
config: {
Root: {
path: 'root',
path: '',
initialRouteName: 'Home',
screens: Object.keys(SCREENS).reduce<{ [key: string]: string }>(
(acc, name) => {
// Convert screen names such as SimpleStack to kebab case (simple-stack)
@@ -135,7 +145,7 @@ export default function App() {
return acc;
},
{}
{ Home: '' }
),
},
},
@@ -193,19 +203,33 @@ export default function App() {
};
}, [theme.colors, theme.dark]);
const [dimensions, setDimensions] = React.useState(Dimensions.get('window'));
React.useEffect(() => {
const onDimensionsChange = ({ window }: { window: ScaledSize }) => {
setDimensions(window);
};
Dimensions.addEventListener('change', onDimensionsChange);
return () => Dimensions.removeEventListener('change', onDimensionsChange);
}, []);
if (!isReady) {
return null;
}
const isLargeScreen = dimensions.width >= 1024;
return (
<PaperProvider theme={paperTheme}>
{Platform.OS === 'ios' && (
<StatusBar barStyle={theme.dark ? 'light-content' : 'dark-content'} />
)}
<NavigationNativeContainer
<NavigationContainer
ref={containerRef}
initialState={initialState}
onStateChange={state =>
onStateChange={(state) =>
AsyncStorage.setItem(
NAVIGATION_PERSISTENCE_KEY,
JSON.stringify(state)
@@ -213,7 +237,7 @@ export default function App() {
}
theme={theme}
>
<Drawer.Navigator>
<Drawer.Navigator drawerType={isLargeScreen ? 'permanent' : undefined}>
<Drawer.Screen
name="Root"
options={{
@@ -237,13 +261,15 @@ export default function App() {
name="Home"
options={{
title: 'Examples',
headerLeft: () => (
<Appbar.Action
color={theme.colors.text}
icon="menu"
onPress={() => navigation.toggleDrawer()}
/>
),
headerLeft: isLargeScreen
? undefined
: () => (
<Appbar.Action
color={theme.colors.text}
icon="menu"
onPress={() => navigation.toggleDrawer()}
/>
),
}}
>
{({
@@ -277,12 +303,12 @@ export default function App() {
theme.dark ? 'light' : 'dark'
);
setTheme(t => (t.dark ? DefaultTheme : DarkTheme));
setTheme((t) => (t.dark ? DefaultTheme : DarkTheme));
}}
/>
<Divider />
{(Object.keys(SCREENS) as (keyof typeof SCREENS)[]).map(
name => (
(name) => (
<List.Item
key={name}
title={SCREENS[name].title}
@@ -294,7 +320,7 @@ export default function App() {
)}
</Stack.Screen>
{(Object.keys(SCREENS) as (keyof typeof SCREENS)[]).map(
name => (
(name) => (
<Stack.Screen
key={name}
name={name}
@@ -307,7 +333,7 @@ export default function App() {
)}
</Drawer.Screen>
</Drawer.Navigator>
</NavigationNativeContainer>
</NavigationContainer>
</PaperProvider>
);
}

View File

@@ -7,7 +7,7 @@ const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
const node_modules = path.resolve(__dirname, '..', 'node_modules');
const packages = path.resolve(__dirname, '..', 'packages');
module.exports = async function(env, argv) {
module.exports = async function (env, argv) {
const config = await createExpoWebpackConfigAsync(env, argv);
config.context = path.resolve(__dirname, '..');
@@ -20,7 +20,7 @@ module.exports = async function(env, argv) {
});
config.resolve.plugins = config.resolve.plugins.filter(
p => !(p instanceof ModuleScopePlugin)
(p) => !(p instanceof ModuleScopePlugin)
);
Object.assign(config.resolve.alias, {
@@ -30,7 +30,7 @@ module.exports = async function(env, argv) {
'@expo/vector-icons': path.resolve(node_modules, '@expo/vector-icons'),
});
fs.readdirSync(packages).forEach(name => {
fs.readdirSync(packages).forEach((name) => {
config.resolve.alias[`@react-navigation/${name}`] = path.resolve(
packages,
name,

View File

@@ -1,7 +1,7 @@
const error = console.error;
console.error = (...args) =>
// Supress error messages regarding error boundary in tests
// Suppress error messages regarding error boundary in tests
/(Consider adding an error boundary to your tree to customize error handling behavior|React will try to recreate this component tree from scratch using the error boundary you provided|Error boundaries should implement getDerivedStateFromError)/m.test(
args[0]
)

View File

@@ -11,8 +11,6 @@
"allowBranch": "master",
"conventionalCommits": true,
"createRelease": "github",
"preId": "alpha",
"preDistTag": "next",
"message": "chore: publish",
"ignoreChanges": [
"**/__fixtures__/**",

5
netlify.toml Normal file
View File

@@ -0,0 +1,5 @@
[build]
base = "/"
publish = "example/web-build"
command = "yarn example expo build:web"

View File

@@ -18,30 +18,33 @@
"author": "Satyajit Sahoo <satyajit.happy@gmail.com> (https://github.com/satya164/), Michał Osadnik <micosa97@gmail.com> (https://github.com/osdnk/)",
"scripts": {
"lint": "eslint --ext '.js,.ts,.tsx' .",
"typescript": "tsc --noEmit",
"typescript": "tsc --noEmit --composite false",
"test": "jest",
"prerelease": "lerna run clean",
"release": "lerna publish",
"example": "yarn --cwd example"
},
"devDependencies": {
"@babel/plugin-proposal-class-properties": "^7.7.0",
"@babel/plugin-proposal-optional-chaining": "^7.7.5",
"@babel/preset-env": "^7.7.7",
"@babel/preset-react": "^7.7.0",
"@babel/preset-typescript": "^7.7.7",
"@babel/runtime": "^7.7.7",
"@babel/plugin-proposal-class-properties": "^7.8.3",
"@babel/plugin-proposal-optional-chaining": "^7.9.0",
"@babel/preset-env": "^7.9.0",
"@babel/preset-flow": "^7.9.0",
"@babel/preset-react": "^7.9.4",
"@babel/preset-typescript": "^7.9.0",
"@babel/runtime": "^7.9.2",
"@commitlint/config-conventional": "^8.3.4",
"@types/jest": "^24.0.25",
"codecov": "^3.6.1",
"commitlint": "^8.3.4",
"core-js": "^3.6.2",
"detox": "^15.0.0",
"@types/jest": "^25.2.1",
"babel-jest": "^25.2.6",
"codecov": "^3.6.5",
"commitlint": "^8.3.5",
"core-js": "^3.6.4",
"eslint": "^6.8.0",
"eslint-config-satya164": "^3.1.5",
"husky": "^4.0.1",
"jest": "^24.9.0",
"eslint-config-satya164": "^3.1.6",
"husky": "^4.2.3",
"jest": "^25.2.7",
"lerna": "^3.20.2",
"prettier": "^1.19.1",
"typescript": "^3.7.4"
"prettier": "^2.0.4",
"typescript": "^3.8.3"
},
"resolutions": {
"react": "~16.9.0",

View File

@@ -3,6 +3,175 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [5.2.7](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.2.6...@react-navigation/bottom-tabs@5.2.7) (2020-04-17)
**Note:** Version bump only for package @react-navigation/bottom-tabs
## [5.2.6](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.2.5...@react-navigation/bottom-tabs@5.2.6) (2020-04-08)
### Bug Fixes
* mark type exports for all packages ([b71de6c](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/commit/b71de6cc799143f1d0e8a0cfcc34f0a2381f9840))
## [5.2.5](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.2.4...@react-navigation/bottom-tabs@5.2.5) (2020-03-30)
**Note:** Version bump only for package @react-navigation/bottom-tabs
## [5.2.4](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.2.3...@react-navigation/bottom-tabs@5.2.4) (2020-03-23)
**Note:** Version bump only for package @react-navigation/bottom-tabs
## [5.2.3](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.2.2...@react-navigation/bottom-tabs@5.2.3) (2020-03-22)
**Note:** Version bump only for package @react-navigation/bottom-tabs
## [5.2.2](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.2.1...@react-navigation/bottom-tabs@5.2.2) (2020-03-19)
### Bug Fixes
* don't use react-native-screens on web ([b1a65fc](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/commit/b1a65fc73e8603ae2c06ef101a74df31e80bb9b2)), closes [#7485](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/issues/7485)
* initialize height and width to zero if undefined ([3df65e2](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/commit/3df65e28197db3bb8371059146546d57661c5ba3)), closes [#6789](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/issues/6789)
## [5.2.1](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.2.0...@react-navigation/bottom-tabs@5.2.1) (2020-03-17)
**Note:** Version bump only for package @react-navigation/bottom-tabs
# [5.2.0](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.1.1...@react-navigation/bottom-tabs@5.2.0) (2020-03-16)
### Features
* add safeAreaInsets to bottom tabs ([82af7be](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/commit/82af7bed7135e42e24693b48cf7f1c6f9f5a6981))
## [5.1.1](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.1.0...@react-navigation/bottom-tabs@5.1.1) (2020-03-03)
**Note:** Version bump only for package @react-navigation/bottom-tabs
# [5.1.0](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.0.7...@react-navigation/bottom-tabs@5.1.0) (2020-02-26)
### Features
* add ability add listeners with listeners prop ([1624108](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/commit/162410843c4f175ae107756de1c3af04d1d47aa7)), closes [#6756](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/issues/6756)
## [5.0.7](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.0.6...@react-navigation/bottom-tabs@5.0.7) (2020-02-21)
**Note:** Version bump only for package @react-navigation/bottom-tabs
## [5.0.6](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.0.5...@react-navigation/bottom-tabs@5.0.6) (2020-02-19)
**Note:** Version bump only for package @react-navigation/bottom-tabs
## [5.0.5](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.0.4...@react-navigation/bottom-tabs@5.0.5) (2020-02-14)
**Note:** Version bump only for package @react-navigation/bottom-tabs
## [5.0.4](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.0.3...@react-navigation/bottom-tabs@5.0.4) (2020-02-14)
**Note:** Version bump only for package @react-navigation/bottom-tabs
## [5.0.3](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.0.2...@react-navigation/bottom-tabs@5.0.3) (2020-02-12)
**Note:** Version bump only for package @react-navigation/bottom-tabs
## [5.0.2](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.0.1...@react-navigation/bottom-tabs@5.0.2) (2020-02-11)
### Bug Fixes
* initialize keyboard-hiding tabBar to visible=true ([#6740](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/issues/6740), [#6799](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/issues/6799)) ([0c59ef7](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/commit/0c59ef7328c63108a2a2c04e927794d73cead63a))
* provide route context to header and bottom tabs ([b6e7e08](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/commit/b6e7e08b9a05be6c04ed21e938b9580876239116))
## [5.0.1](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.0.0-alpha.45...@react-navigation/bottom-tabs@5.0.1) (2020-02-10)
**Note:** Version bump only for package @react-navigation/bottom-tabs
# [5.0.0-alpha.45](https://github.com/react-navigation/navigation-ex/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.0.0-alpha.44...@react-navigation/bottom-tabs@5.0.0-alpha.45) (2020-02-04)
**Note:** Version bump only for package @react-navigation/bottom-tabs
# [5.0.0-alpha.44](https://github.com/react-navigation/navigation-ex/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.0.0-alpha.43...@react-navigation/bottom-tabs@5.0.0-alpha.44) (2020-02-04)
**Note:** Version bump only for package @react-navigation/bottom-tabs
# [5.0.0-alpha.43](https://github.com/react-navigation/navigation-ex/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.0.0-alpha.42...@react-navigation/bottom-tabs@5.0.0-alpha.43) (2020-02-03)
**Note:** Version bump only for package @react-navigation/bottom-tabs

View File

@@ -2,80 +2,4 @@
Bottom tab navigator for React Navigation following iOS design guidelines.
Documentation can be found on the [React Navigation website](https://reactnavigation.org/docs/en/next/bottom-tab-navigator.html).
## Installation
Open a Terminal in your project's folder and run,
```sh
yarn add @react-navigation/native @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() {
return (
<BottomTabs.Navigator>
<BottomTabs.Screen
name="article"
component={Article}
options={{
tabBarLabel: 'Article',
tabBarIcon: getTabBarIcon('file-document-box'),
}}
/>
<BottomTabs.Screen
name="chat"
component={Chat}
options={{
tabBarLabel: 'Chat',
tabBarIcon: getTabBarIcon('message-reply'),
}}
/>
<BottomTabs.Screen
name="contacts"
component={Contacts}
options={{
tabBarLabel: 'Contacts',
tabBarIcon: getTabBarIcon('contacts'),
}}
/>
</BottomTabs.Navigator>
);
}
```
Installation instructions and documentation can be found on the [React Navigation website](https://reactnavigation.org/docs/bottom-tab-navigator.html).

View File

@@ -1,6 +1,7 @@
{
"name": "@react-navigation/bottom-tabs",
"description": "Bottom tab navigator following iOS design guidelines",
"version": "5.2.7",
"keywords": [
"react-native-component",
"react-component",
@@ -10,9 +11,8 @@
"android",
"tab"
],
"version": "5.0.0-alpha.43",
"license": "MIT",
"repository": "https://github.com/react-navigation/navigation-ex/tree/master/packages/bottom-tabs",
"repository": "https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs",
"main": "lib/commonjs/index.js",
"react-native": "src/index.tsx",
"module": "lib/module/index.js",
@@ -30,26 +30,28 @@
"clean": "del lib"
},
"dependencies": {
"@react-navigation/routers": "^5.0.0-alpha.31",
"color": "^3.1.2",
"react-native-iphone-x-helper": "^1.2.1"
},
"devDependencies": {
"@react-native-community/bob": "^0.8.0",
"@react-native-community/bob": "^0.10.0",
"@react-navigation/native": "^5.1.6",
"@types/color": "^3.0.1",
"@types/react": "^16.9.17",
"@types/react-native": "^0.60.30",
"@types/react": "^16.9.23",
"@types/react-native": "^0.61.22",
"del-cli": "^3.0.0",
"react": "~16.9.0",
"react-native": "~0.61.5",
"react-native-safe-area-context": "^0.6.2",
"typescript": "^3.7.4"
"react-native-safe-area-context": "^0.7.3",
"react-native-screens": "^2.3.0",
"typescript": "^3.8.3"
},
"peerDependencies": {
"@react-navigation/native": "^5.0.0-alpha.0",
"@react-navigation/native": "^5.0.5",
"react": "*",
"react-native": "*",
"react-native-safe-area-context": "^0.6.0"
"react-native-safe-area-context": ">= 0.6.0",
"react-native-screens": ">= 2.0.0-alpha.0 || >= 2.0.0-beta.0 || >= 2.0.0"
},
"@react-native-community/bob": {
"source": "src",

View File

@@ -12,7 +12,7 @@ export { default as BottomTabBar } from './views/BottomTabBar';
/**
* Types
*/
export {
export type {
BottomTabNavigationOptions,
BottomTabNavigationProp,
BottomTabBarProps,

View File

@@ -3,12 +3,10 @@ import {
useNavigationBuilder,
createNavigatorFactory,
DefaultNavigatorOptions,
} from '@react-navigation/native';
import {
TabRouter,
TabRouterOptions,
TabNavigationState,
} from '@react-navigation/routers';
} from '@react-navigation/native';
import BottomTabView from '../views/BottomTabView';
import {
BottomTabNavigationConfig,
@@ -50,6 +48,8 @@ function BottomTabNavigator({
}
export default createNavigatorFactory<
TabNavigationState,
BottomTabNavigationOptions,
BottomTabNavigationEventMap,
typeof BottomTabNavigator
>(BottomTabNavigator);

View File

@@ -10,8 +10,9 @@ import {
NavigationProp,
ParamListBase,
Descriptor,
TabNavigationState,
TabActionHelpers,
} from '@react-navigation/native';
import { TabNavigationState } from '@react-navigation/routers';
export type BottomTabNavigationEventMap = {
/**
@@ -40,19 +41,8 @@ export type BottomTabNavigationProp<
TabNavigationState,
BottomTabNavigationOptions,
BottomTabNavigationEventMap
> & {
/**
* Jump to an existing tab.
*
* @param name Name of the route for the tab.
* @param [params] Params object for the route.
*/
jumpTo<RouteName extends Extract<keyof ParamList, string>>(
...args: ParamList[RouteName] extends undefined | any
? [RouteName] | [RouteName, ParamList[RouteName]]
: [RouteName, ParamList[RouteName]]
): void;
};
> &
TabActionHelpers<ParamList>;
export type BottomTabNavigationOptions = {
/**
@@ -148,7 +138,7 @@ export type BottomTabBarOptions = {
*/
inactiveTintColor?: string;
/**
* Background olor for the active tab.
* Background color for the active tab.
*/
activeBackgroundColor?: string;
/**
@@ -176,14 +166,24 @@ export type BottomTabBarOptions = {
*/
tabStyle?: StyleProp<ViewStyle>;
/**
* Whether the label is renderd below the icon or beside the icon.
* By default, in `vertical` orinetation, label is rendered below and in `horizontal` orientation, it's renderd beside.
* Whether the label is rendered below the icon or beside the icon.
* By default, in `vertical` orinetation, label is rendered below and in `horizontal` orientation, it's rendered beside.
*/
labelPosition?: LabelPosition;
/**
* Whether the label position should adapt to the orientation.
*/
adaptive?: boolean;
/**
* Safe area insets for the tab bar. This is used to avoid elements like the navigation bar on Android and bottom safe area on iOS.
* By default, the device's safe area insets are automatically detected. You can override the behavior with this option.
*/
safeAreaInsets?: {
top?: number;
right?: number;
bottom?: number;
left?: number;
};
/**
* Style object for the tab bar container.
*/

View File

@@ -11,10 +11,11 @@ import {
} from 'react-native';
import {
NavigationContext,
NavigationRouteContext,
CommonActions,
useTheme,
} from '@react-navigation/native';
import { SafeAreaConsumer } from 'react-native-safe-area-context';
import { useSafeArea } from 'react-native-safe-area-context';
import BottomTabItem from './BottomTabItem';
import { BottomTabBarProps } from '../types';
@@ -42,6 +43,7 @@ export default function BottomTabBar({
keyboardHidesTabBar = false,
labelPosition,
labelStyle,
safeAreaInsets,
showIcon,
showLabel,
style,
@@ -49,14 +51,19 @@ export default function BottomTabBar({
}: Props) {
const { colors } = useTheme();
const [dimensions, setDimensions] = React.useState(Dimensions.get('window'));
const [dimensions, setDimensions] = React.useState(() => {
const { height = 0, width = 0 } = Dimensions.get('window');
return { height, width };
});
const [layout, setLayout] = React.useState({
height: 0,
width: dimensions.width,
});
const [keyboardShown, setKeyboardShown] = React.useState(false);
const [visible] = React.useState(() => new Animated.Value(0));
const [visible] = React.useState(() => new Animated.Value(1));
const { routes } = state;
@@ -114,7 +121,7 @@ export default function BottomTabBar({
const handleLayout = (e: LayoutChangeEvent) => {
const { height, width } = e.nativeEvent.layout;
setLayout(layout => {
setLayout((layout) => {
if (height === layout.height && width === layout.width) {
return layout;
} else {
@@ -157,114 +164,122 @@ export default function BottomTabBar({
}
};
const defaultInsets = useSafeArea();
const insets = {
top: safeAreaInsets?.top ?? defaultInsets.top,
right: safeAreaInsets?.right ?? defaultInsets.right,
bottom: safeAreaInsets?.bottom ?? defaultInsets.bottom,
left: safeAreaInsets?.left ?? defaultInsets.left,
};
return (
<SafeAreaConsumer>
{insets => (
<Animated.View
style={[
styles.tabBar,
{
backgroundColor: colors.card,
borderTopColor: colors.border,
},
keyboardHidesTabBar
? {
// When the keyboard is shown, slide down the tab bar
transform: [
{
translateY: visible.interpolate({
inputRange: [0, 1],
outputRange: [layout.height, 0],
}),
},
],
// Absolutely position the tab bar so that the content is below it
// This is needed to avoid gap at bottom when the tab bar is hidden
position: keyboardShown ? 'absolute' : null,
}
: null,
{
height: DEFAULT_TABBAR_HEIGHT + (insets ? insets.bottom : 0),
paddingBottom: insets ? insets.bottom : 0,
},
style,
]}
pointerEvents={keyboardHidesTabBar && keyboardShown ? 'none' : 'auto'}
>
<View style={styles.content} onLayout={handleLayout}>
{routes.map((route, index) => {
const focused = index === state.index;
const { options } = descriptors[route.key];
<Animated.View
style={[
styles.tabBar,
{
backgroundColor: colors.card,
borderTopColor: colors.border,
},
keyboardHidesTabBar
? {
// When the keyboard is shown, slide down the tab bar
transform: [
{
translateY: visible.interpolate({
inputRange: [0, 1],
outputRange: [layout.height, 0],
}),
},
],
// Absolutely position the tab bar so that the content is below it
// This is needed to avoid gap at bottom when the tab bar is hidden
position: keyboardShown ? 'absolute' : null,
}
: null,
{
height: DEFAULT_TABBAR_HEIGHT + insets.bottom,
paddingBottom: insets.bottom,
paddingHorizontal: Math.max(insets.left, insets.right),
},
style,
]}
pointerEvents={keyboardHidesTabBar && keyboardShown ? 'none' : 'auto'}
>
<View style={styles.content} onLayout={handleLayout}>
{routes.map((route, index) => {
const focused = index === state.index;
const { options } = descriptors[route.key];
const onPress = () => {
const event = navigation.emit({
type: 'tabPress',
target: route.key,
canPreventDefault: true,
});
const onPress = () => {
const event = navigation.emit({
type: 'tabPress',
target: route.key,
canPreventDefault: true,
});
if (!focused && !event.defaultPrevented) {
navigation.dispatch({
...CommonActions.navigate(route.name),
target: state.key,
});
}
};
if (!focused && !event.defaultPrevented) {
navigation.dispatch({
...CommonActions.navigate(route.name),
target: state.key,
});
}
};
const onLongPress = () => {
navigation.emit({
type: 'tabLongPress',
target: route.key,
});
};
const onLongPress = () => {
navigation.emit({
type: 'tabLongPress',
target: route.key,
});
};
const label =
options.tabBarLabel !== undefined
? options.tabBarLabel
: options.title !== undefined
? options.title
: route.name;
const label =
options.tabBarLabel !== undefined
? options.tabBarLabel
: options.title !== undefined
? options.title
: route.name;
const accessibilityLabel =
options.tabBarAccessibilityLabel !== undefined
? options.tabBarAccessibilityLabel
: typeof label === 'string'
? `${label}, tab, ${index + 1} of ${routes.length}`
: undefined;
const accessibilityLabel =
options.tabBarAccessibilityLabel !== undefined
? options.tabBarAccessibilityLabel
: typeof label === 'string'
? `${label}, tab, ${index + 1} of ${routes.length}`
: undefined;
return (
<NavigationContext.Provider
key={route.key}
value={descriptors[route.key].navigation}
>
<BottomTabItem
route={route}
focused={focused}
horizontal={shouldUseHorizontalLabels()}
onPress={onPress}
onLongPress={onLongPress}
accessibilityLabel={accessibilityLabel}
testID={options.tabBarTestID}
allowFontScaling={allowFontScaling}
activeTintColor={activeTintColor}
inactiveTintColor={inactiveTintColor}
activeBackgroundColor={activeBackgroundColor}
inactiveBackgroundColor={inactiveBackgroundColor}
button={options.tabBarButton}
icon={options.tabBarIcon}
label={label}
showIcon={showIcon}
showLabel={showLabel}
labelStyle={labelStyle}
style={tabStyle}
/>
</NavigationContext.Provider>
);
})}
</View>
</Animated.View>
)}
</SafeAreaConsumer>
return (
<NavigationContext.Provider
key={route.key}
value={descriptors[route.key].navigation}
>
<NavigationRouteContext.Provider value={route}>
<BottomTabItem
route={route}
focused={focused}
horizontal={shouldUseHorizontalLabels()}
onPress={onPress}
onLongPress={onLongPress}
accessibilityLabel={accessibilityLabel}
testID={options.tabBarTestID}
allowFontScaling={allowFontScaling}
activeTintColor={activeTintColor}
inactiveTintColor={inactiveTintColor}
activeBackgroundColor={activeBackgroundColor}
inactiveBackgroundColor={inactiveBackgroundColor}
button={options.tabBarButton}
icon={options.tabBarIcon}
label={label}
showIcon={showIcon}
showLabel={showLabel}
labelStyle={labelStyle}
style={tabStyle}
/>
</NavigationRouteContext.Provider>
</NavigationContext.Provider>
);
})}
</View>
</Animated.View>
);
}

View File

@@ -133,9 +133,7 @@ export default function BottomTabBarItem({
const inactiveTintColor =
customInactiveTintColor === undefined
? Color(colors.text)
.mix(Color(colors.card), 0.5)
.hex()
? Color(colors.text).mix(Color(colors.card), 0.5).hex()
: customInactiveTintColor;
const renderLabel = ({ focused }: { focused: boolean }) => {

View File

@@ -1,8 +1,7 @@
import * as React from 'react';
import { View, StyleSheet } from 'react-native';
import { TabNavigationState } from '@react-navigation/routers';
import { useTheme } from '@react-navigation/native';
import { TabNavigationState, useTheme } from '@react-navigation/native';
// eslint-disable-next-line import/no-unresolved
import { ScreenContainer } from 'react-native-screens';

View File

@@ -1,6 +1,5 @@
import * as React from 'react';
import { Platform, StyleSheet, View } from 'react-native';
// eslint-disable-next-line import/no-unresolved
import { Screen, screensEnabled } from 'react-native-screens';
@@ -10,12 +9,14 @@ type Props = {
style?: any;
};
const FAR_FAR_AWAY = 3000; // this should be big enough to move the whole view out of its container
const FAR_FAR_AWAY = 30000; // this should be big enough to move the whole view out of its container
export default class ResourceSavingScene extends React.Component<Props> {
render() {
if (screensEnabled?.()) {
// react-native-screens is buggy on web
if (screensEnabled?.() && Platform.OS !== 'web') {
const { isVisible, ...rest } = this.props;
// @ts-ignore
return <Screen active={isVisible ? 1 : 0} {...rest} />;
}
@@ -24,7 +25,13 @@ export default class ResourceSavingScene extends React.Component<Props> {
return (
<View
style={[styles.container, style, { opacity: isVisible ? 1 : 0 }]}
style={[
styles.container,
Platform.OS === 'web'
? { display: isVisible ? 'flex' : 'none' }
: null,
style,
]}
collapsable={false}
removeClippedSubviews={
// On iOS, set removeClippedSubviews to true only when not focused

View File

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

View File

@@ -2,8 +2,8 @@
"extends": "../../tsconfig",
"references": [
{ "path": "../core" },
{ "path": "../native" },
{ "path": "../routers" }
{ "path": "../routers" },
{ "path": "../native" }
],
"compilerOptions": {
"outDir": "./lib/typescript"

View File

@@ -3,6 +3,167 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [5.1.9](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.8...@react-navigation/compat@5.1.9) (2020-04-17)
**Note:** Version bump only for package @react-navigation/compat
## [5.1.8](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.7...@react-navigation/compat@5.1.8) (2020-04-08)
### Bug Fixes
* use 1 as default in compatibility pop action ([4408117](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/commit/44081172d440c713ad3543a2d5e1e18ebc8f72a4))
## [5.1.7](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.6...@react-navigation/compat@5.1.7) (2020-03-30)
**Note:** Version bump only for package @react-navigation/compat
## [5.1.6](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.5...@react-navigation/compat@5.1.6) (2020-03-23)
**Note:** Version bump only for package @react-navigation/compat
## [5.1.5](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.4...@react-navigation/compat@5.1.5) (2020-03-22)
**Note:** Version bump only for package @react-navigation/compat
## [5.1.4](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.3...@react-navigation/compat@5.1.4) (2020-03-19)
**Note:** Version bump only for package @react-navigation/compat
## [5.1.3](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.2...@react-navigation/compat@5.1.3) (2020-03-17)
**Note:** Version bump only for package @react-navigation/compat
## [5.1.2](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.1...@react-navigation/compat@5.1.2) (2020-03-16)
**Note:** Version bump only for package @react-navigation/compat
## [5.1.1](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.0...@react-navigation/compat@5.1.1) (2020-03-03)
**Note:** Version bump only for package @react-navigation/compat
# [5.1.0](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.0.7...@react-navigation/compat@5.1.0) (2020-02-26)
### Features
* add ability add listeners with listeners prop ([1624108](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/commit/162410843c4f175ae107756de1c3af04d1d47aa7)), closes [#6756](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/issues/6756)
## [5.0.7](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.0.6...@react-navigation/compat@5.0.7) (2020-02-21)
**Note:** Version bump only for package @react-navigation/compat
## [5.0.6](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.0.5...@react-navigation/compat@5.0.6) (2020-02-19)
### Bug Fixes
* add NavigationEvents ([d69b0db](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/commit/d69b0db60455b8789276822ba73f5349db8842d7)), closes [/github.com/react-navigation/react-navigation/issues/6821#issuecomment-588268512](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/issues/issuecomment-588268512)
## [5.0.5](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.0.4...@react-navigation/compat@5.0.5) (2020-02-14)
**Note:** Version bump only for package @react-navigation/compat
## [5.0.4](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.0.3...@react-navigation/compat@5.0.4) (2020-02-14)
**Note:** Version bump only for package @react-navigation/compat
## [5.0.3](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.0.2...@react-navigation/compat@5.0.3) (2020-02-12)
**Note:** Version bump only for package @react-navigation/compat
## [5.0.2](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.0.1...@react-navigation/compat@5.0.2) (2020-02-11)
**Note:** Version bump only for package @react-navigation/compat
## [5.0.1](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.0.0-alpha.34...@react-navigation/compat@5.0.1) (2020-02-10)
**Note:** Version bump only for package @react-navigation/compat
# [5.0.0-alpha.34](https://github.com/react-navigation/navigation-ex/tree/master/packages/compat/compare/@react-navigation/compat@5.0.0-alpha.33...@react-navigation/compat@5.0.0-alpha.34) (2020-02-04)
**Note:** Version bump only for package @react-navigation/compat
# [5.0.0-alpha.33](https://github.com/react-navigation/navigation-ex/tree/master/packages/compat/compare/@react-navigation/compat@5.0.0-alpha.32...@react-navigation/compat@5.0.0-alpha.33) (2020-02-04)
**Note:** Version bump only for package @react-navigation/compat
# [5.0.0-alpha.32](https://github.com/react-navigation/navigation-ex/tree/master/packages/compat/compare/@react-navigation/compat@5.0.0-alpha.31...@react-navigation/compat@5.0.0-alpha.32) (2020-02-03)
**Note:** Version bump only for package @react-navigation/compat

View File

@@ -2,29 +2,4 @@
Compatibility layer to write navigator definitions in static configuration format.
Documentation can be found on the [React Navigation website](https://reactnavigation.org/docs/en/next/compatibility.html).
## Installation
Open a Terminal in your project's folder and run,
```sh
yarn add @react-navigation/native @react-navigation/compat
```
## Usage
```js
import { createCompatNavigatorFactory } from '@react-navigation/compat';
import { createStackNavigator } from '@react-navigation/stack';
const RootStack = createCompatNavigatorFactory(createStackNavigator)(
{
Home: { screen: HomeScreen },
Profile: { screen: ProfileScreen },
},
{
initialRouteName: 'Profile',
}
);
```
Installation instructions and documentation can be found on the [React Navigation website](https://reactnavigation.org/docs/compatibility.html).

View File

@@ -1,9 +1,13 @@
{
"name": "@react-navigation/compat",
"description": "Compatibility layer to write navigator definitions in static configuration format",
"version": "5.0.0-alpha.32",
"version": "5.1.9",
"license": "MIT",
"repository": "https://github.com/react-navigation/navigation-ex/tree/master/packages/compat",
"repository": "https://github.com/react-navigation/react-navigation/tree/master/packages/compat",
"bugs": {
"url": "https://github.com/react-navigation/react-navigation/issues"
},
"homepage": "https://reactnavigation.org/docs/compatibility.html",
"main": "lib/commonjs/index.js",
"react-native": "src/index.tsx",
"module": "lib/module/index.js",
@@ -20,17 +24,16 @@
"prepare": "bob build",
"clean": "del lib"
},
"dependencies": {
"@react-navigation/routers": "^5.0.0-alpha.31"
},
"devDependencies": {
"@types/react": "^16.9.17",
"@react-native-community/bob": "^0.10.0",
"@react-navigation/native": "^5.1.6",
"@types/react": "^16.9.23",
"react": "~16.9.0",
"typescript": "^3.7.4"
"typescript": "^3.8.3"
},
"peerDependencies": {
"@react-navigation/native": "^5.0.0-alpha.0",
"react": "~16.9.0"
"@react-navigation/native": "^5.0.5",
"react": "*"
},
"@react-native-community/bob": {
"source": "src",

View File

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

View File

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

View File

@@ -1,5 +1,8 @@
import { CommonActions } from '@react-navigation/native';
import { StackActions, StackActionType } from '@react-navigation/routers';
import {
CommonActions,
StackActions,
StackActionType,
} from '@react-navigation/native';
export function reset(): CommonActions.Action {
throw new Error(

View File

@@ -1,4 +1,4 @@
import { TabActions, TabActionType } from '@react-navigation/routers';
import { TabActions, TabActionType } from '@react-navigation/native';
export function jumpTo({
routeName,

View File

@@ -6,6 +6,7 @@ import {
TypedNavigator,
NavigationProp,
RouteProp,
EventMapBase,
} from '@react-navigation/native';
import CompatScreen from './CompatScreen';
import ScreenPropsContext from './ScreenPropsContext';
@@ -15,7 +16,9 @@ import { CompatScreenType, CompatRouteConfig } from './types';
export default function createCompatNavigatorFactory<
CreateNavigator extends () => TypedNavigator<
ParamListBase,
NavigationState,
{},
EventMapBase,
React.ComponentType<any>
>
>(createNavigator: CreateNavigator) {
@@ -66,7 +69,7 @@ export default function createCompatNavigatorFactory<
function Navigator({ screenProps }: { screenProps?: unknown }) {
const screens = React.useMemo(
() =>
routeNames.map(name => {
routeNames.map((name) => {
let getScreenComponent: () => CompatScreenType<NavigationPropType>;
let initialParams;

View File

@@ -2,12 +2,10 @@ import {
useNavigationBuilder,
createNavigatorFactory,
DefaultNavigatorOptions,
} from '@react-navigation/native';
import {
TabRouter,
TabRouterOptions,
TabNavigationState,
} from '@react-navigation/routers';
} from '@react-navigation/native';
import createCompatNavigatorFactory from './createCompatNavigatorFactory';
type Props = DefaultNavigatorOptions<{}> & TabRouterOptions;
@@ -24,5 +22,7 @@ function SwitchNavigator(props: Props) {
}
export default createCompatNavigatorFactory(
createNavigatorFactory<{}, typeof SwitchNavigator>(SwitchNavigator)
createNavigatorFactory<TabNavigationState, {}, {}, typeof SwitchNavigator>(
SwitchNavigator
)
);

View File

@@ -57,7 +57,7 @@ export function push(routeName: string, params?: object, action?: never) {
});
}
export function pop(n: number) {
export function pop(n: number = 1) {
return StackActions.pop(typeof n === 'number' ? { n } : n);
}

View File

@@ -14,4 +14,6 @@ export { default as createSwitchNavigator } from './createSwitchNavigator';
export { default as withNavigation } from './withNavigation';
export { default as withNavigationFocus } from './withNavigationFocus';
export { default as NavigationEvents } from './NavigationEvents';
export * from './types';

View File

@@ -16,7 +16,7 @@ export default function useCompatNavigation<
const route = useRoute();
const isFirstRouteInParent = useNavigationState(
state => state.routes[0].key === route.key
(state) => state.routes[0].key === route.key
);
const context = React.useRef<Record<string, any>>({});

View File

@@ -26,8 +26,9 @@ export default function withNavigation<
return <Comp ref={onRef} navigation={navigation} {...rest} />;
};
WrappedComponent.displayName = `withNavigation(${Comp.displayName ||
Comp.name})`;
WrappedComponent.displayName = `withNavigation(${
Comp.displayName || Comp.name
})`;
return WrappedComponent;
}

View File

@@ -23,8 +23,9 @@ export default function withNavigationFocus<
return <Comp ref={onRef} isFocused={isFocused} {...rest} />;
};
WrappedComponent.displayName = `withNavigationFocus(${Comp.displayName ||
Comp.name})`;
WrappedComponent.displayName = `withNavigationFocus(${
Comp.displayName || Comp.name
})`;
return WrappedComponent;
}

View File

@@ -2,8 +2,8 @@
"extends": "../../tsconfig",
"references": [
{ "path": "../core" },
{ "path": "../native" },
{ "path": "../routers" }
{ "path": "../routers" },
{ "path": "../native" }
],
"compilerOptions": {
"outDir": "./lib/typescript"

View File

@@ -3,6 +3,220 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [5.3.4](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.3.3...@react-navigation/core@5.3.4) (2020-04-17)
### Bug Fixes
* add initial option for navigating to nested navigators ([004c7d7](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/004c7d7ab1f80faf04b2a1836ec6b79a5419e45f))
* add initial param for actions from deep link ([a3f7a5f](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/a3f7a5feba2e6aa2158aeaea6cde73ae1603173e))
* handle initial: false for nested route after first initialization ([187aefe](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/187aefe9c400b499f920c212bf856414e25c5aaf))
## [5.3.3](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.3.2...@react-navigation/core@5.3.3) (2020-04-08)
### Bug Fixes
* switch order of focus and blur events. closes [#7963](https://github.com/react-navigation/react-navigation/tree/master/packages/core/issues/7963) ([ce3994c](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/ce3994c82c28669d5742017eb7627e9adf996933))
* workaround warning about setState in another component in render ([d4fd906](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/d4fd906915cc20d6fb21508384c05a540d8644d8))
## [5.3.2](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.3.1...@react-navigation/core@5.3.2) (2020-03-30)
### Bug Fixes
* handle no path property and undefined query params ([#7911](https://github.com/react-navigation/react-navigation/tree/master/packages/core/issues/7911)) ([cd47915](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/cd47915861a56cd7eaa9ac79f5139cde56ca95a7))
## [5.3.1](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.3.0...@react-navigation/core@5.3.1) (2020-03-23)
### Bug Fixes
* don't emit events for screens that don't exist anymore ([1c00142](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/1c001424b595b40f9db9343096c833f75353b099))
* only call listeners for focused screen for global events ([3096de6](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/3096de62868a7ed9ed65e529c8ddfa001b9be486))
# [5.3.0](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.2.3...@react-navigation/core@5.3.0) (2020-03-22)
### Bug Fixes
* return correct value for isFocused after changing screens ([5b15c71](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/5b15c7164f5503f2f0d51006a3f23bd0c58fd9b7)), closes [#7843](https://github.com/react-navigation/react-navigation/tree/master/packages/core/issues/7843)
### Features
* support function in listeners prop ([3709e65](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/3709e652f41a16c2c2b05d5dbbe1da2017ba2c3f))
## [5.2.3](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.2.2...@react-navigation/core@5.2.3) (2020-03-19)
**Note:** Version bump only for package @react-navigation/core
## [5.2.2](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.2.1...@react-navigation/core@5.2.2) (2020-03-16)
**Note:** Version bump only for package @react-navigation/core
## [5.2.1](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.2.0...@react-navigation/core@5.2.1) (2020-03-03)
### Bug Fixes
* fix links for documentation ([5bb0f40](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/5bb0f405ceb5755d39a0b5b1f2e4ecee0da051bc))
* move updating state to useEffect ([2dfa4f3](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/2dfa4f36293a2acb718814f6b2fa79d7c7ddf09c))
# [5.2.0](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.1.6...@react-navigation/core@5.2.0) (2020-02-26)
### Features
* add ability add listeners with listeners prop ([1624108](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/162410843c4f175ae107756de1c3af04d1d47aa7)), closes [#6756](https://github.com/react-navigation/react-navigation/tree/master/packages/core/issues/6756)
## [5.1.6](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.1.5...@react-navigation/core@5.1.6) (2020-02-21)
### Bug Fixes
* avoid emitting focus events twice ([f167008](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/f16700812f3757713b04ca3a860209795b4a6c44)), closes [#6749](https://github.com/react-navigation/react-navigation/tree/master/packages/core/issues/6749)
* preserve screen order with numeric names ([125bd70](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/125bd70e49b708d936a2eee72ba5cb92eacf26a9)), closes [#6900](https://github.com/react-navigation/react-navigation/tree/master/packages/core/issues/6900)
## [5.1.5](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.1.4...@react-navigation/core@5.1.5) (2020-02-19)
### Bug Fixes
* show descriptive error for invalid return for useFocusEffect ([1a28c29](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/1a28c299b5e3f0805eb6e9ea3cf5e9cc90c7a280))
## [5.1.4](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.1.3...@react-navigation/core@5.1.4) (2020-02-14)
### Bug Fixes
* link to migration guide on invalid usage ([c5fcfbd](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/c5fcfbd4277541e131acbaa7602a5d7e636afebb))
* return '/' for empty paths ([aaf01e0](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/aaf01e01e7b47b375f68aebe6d0effe82878d060))
## [5.1.3](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.1.2...@react-navigation/core@5.1.3) (2020-02-14)
### Bug Fixes
* return false for canGoBack if navigator hasn't finished mounting ([c8ac5fa](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/c8ac5fab61cf127985431075a3c59c1f3dfa42da))
* throw a descriptive error if navigation object hasn't initialized ([b6accd0](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/b6accd03f69dd438e595094d8bf8599cc12e71ac))
* update links in error messages ([f964200](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/f964200b0dcbc19d5f88ad2dd1eb8e5576973497))
## [5.1.2](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.1.1...@react-navigation/core@5.1.2) (2020-02-12)
### Bug Fixes
* fix false positives for circular object check ([030c63c](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/030c63c89fe447aa484b767831c8f8e26e90431c)), closes [#6827](https://github.com/react-navigation/react-navigation/tree/master/packages/core/issues/6827)
* static container memo check ([#6825](https://github.com/react-navigation/react-navigation/tree/master/packages/core/issues/6825)) ([2bf0958](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/2bf09585021470f500d967e9242836840efe970f))
## [5.1.1](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.1.0...@react-navigation/core@5.1.1) (2020-02-11)
### Bug Fixes
* don't cleanup state on switching navigator ([359ae1b](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/359ae1bfacec5ef880b3944f465c881aedb16767))
# [5.1.0](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.0.0-alpha.43...@react-navigation/core@5.1.0) (2020-02-10)
### Bug Fixes
* add some links in the error messages ([13b4e07](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/13b4e07348496f7cb516d625b44a6a7d310ef9af))
### Features
* support ignoring empty path strings ([#349](https://github.com/react-navigation/react-navigation/tree/master/packages/core/issues/349)) ([61b1134](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/61b1134f90310390fe819622c1f33273fca0bd42))
# [5.0.0-alpha.43](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/compare/@react-navigation/core@5.0.0-alpha.42...@react-navigation/core@5.0.0-alpha.43) (2020-02-04)
### Bug Fixes
* improve error message for unhandled action ([ca4a360](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/commit/ca4a36070a21c4fe86cb1cc55a4452dca293f215))
### Features
* add initialRouteName property to config ([#322](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/issues/322)) ([4ca5cc6](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/commit/4ca5cc632992187f12870281e4cf4c7d1f799967))
# [5.0.0-alpha.42](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/compare/@react-navigation/core@5.0.0-alpha.41...@react-navigation/core@5.0.0-alpha.42) (2020-02-04)
**Note:** Version bump only for package @react-navigation/core
# [5.0.0-alpha.41](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/compare/@react-navigation/core@5.0.0-alpha.40...@react-navigation/core@5.0.0-alpha.41) (2020-02-03)

View File

@@ -1,6 +1,6 @@
# `@react-navigation/core`
Core utilities for building navigators.
Core utilities for building navigators independent of the platform.
## Installation

View File

@@ -1,14 +1,18 @@
{
"name": "@react-navigation/core",
"description": "Core utilities for building navigators",
"version": "5.3.4",
"keywords": [
"react",
"react-native",
"react-navigation"
],
"version": "5.0.0-alpha.41",
"license": "MIT",
"repository": "https://github.com/react-navigation/navigation-ex/tree/master/packages/core",
"repository": "https://github.com/react-navigation/react-navigation/tree/master/packages/core",
"bugs": {
"url": "https://github.com/react-navigation/react-navigation/issues"
},
"homepage": "https://reactnavigation.org",
"main": "lib/commonjs/index.js",
"react-native": "src/index.tsx",
"module": "lib/module/index.js",
@@ -25,27 +29,26 @@
"clean": "del lib"
},
"dependencies": {
"@react-navigation/routers": "^5.4.0",
"escape-string-regexp": "^2.0.0",
"query-string": "^6.9.0",
"react-is": "^16.12.0",
"shortid": "^2.2.15",
"use-subscription": "^1.3.0"
"nanoid": "^3.0.2",
"query-string": "^6.12.0",
"react-is": "^16.13.0",
"use-subscription": "^1.4.0"
},
"devDependencies": {
"@babel/core": "^7.7.7",
"@react-native-community/bob": "^0.8.0",
"@types/react": "^16.9.17",
"@react-native-community/bob": "^0.10.0",
"@types/react": "^16.9.23",
"@types/react-is": "^16.7.1",
"@types/shortid": "^0.0.29",
"@types/use-subscription": "^1.0.0",
"del-cli": "^3.0.0",
"react": "~16.9.0",
"react-native-testing-library": "^1.12.0",
"react-test-renderer": "~16.12.0",
"typescript": "^3.7.4"
"react-test-renderer": "~16.13.1",
"typescript": "^3.8.3"
},
"peerDependencies": {
"react": "~16.9.0"
"react": "*"
},
"@react-native-community/bob": {
"source": "src",

View File

@@ -0,0 +1,289 @@
import * as React from 'react';
import {
CommonActions,
Route,
NavigationState,
InitialState,
PartialState,
NavigationAction,
} from '@react-navigation/routers';
import EnsureSingleNavigator from './EnsureSingleNavigator';
import NavigationBuilderContext from './NavigationBuilderContext';
import { ScheduleUpdateContext } from './useScheduleUpdate';
import useFocusedListeners from './useFocusedListeners';
import useDevTools from './useDevTools';
import useStateGetters from './useStateGetters';
import useEventEmitter from './useEventEmitter';
import useSyncState from './useSyncState';
import isSerializable from './isSerializable';
import { NavigationContainerRef, NavigationContainerProps } from './types';
type State = NavigationState | PartialState<NavigationState> | undefined;
const MISSING_CONTEXT_ERROR =
"Couldn't find a navigation context. Have you wrapped your app with 'NavigationContainer'? See https://reactnavigation.org/docs/getting-started for setup instructions.";
const NOT_INITIALIZED_ERROR =
"The 'navigation' object hasn't been initialized yet. This might happen if you don't have a navigator mounted, or if the navigator hasn't finished mounting. See https://reactnavigation.org/docs/navigating-without-navigation-prop#handling-initialization for more details.";
export const NavigationStateContext = React.createContext<{
isDefault?: true;
state?: NavigationState | PartialState<NavigationState>;
getKey: () => string | undefined;
setKey: (key: string) => void;
getState: () => NavigationState | PartialState<NavigationState> | undefined;
setState: (
state: NavigationState | PartialState<NavigationState> | undefined
) => void;
}>({
isDefault: true,
get getKey(): any {
throw new Error(MISSING_CONTEXT_ERROR);
},
get setKey(): any {
throw new Error(MISSING_CONTEXT_ERROR);
},
get getState(): any {
throw new Error(MISSING_CONTEXT_ERROR);
},
get setState(): any {
throw new Error(MISSING_CONTEXT_ERROR);
},
});
let hasWarnedForSerialization = false;
/**
* Remove `key` and `routeNames` from the state objects recursively to get partial state.
*
* @param state Initial state object.
*/
const getPartialState = (
state: InitialState | undefined
): PartialState<NavigationState> | undefined => {
if (state === undefined) {
return;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { key, routeNames, ...partialState } = state;
// @ts-ignore
return {
...partialState,
stale: true,
routes: state.routes.map((route) => {
if (route.state === undefined) {
return route as Route<string> & {
state?: PartialState<NavigationState>;
};
}
return { ...route, state: getPartialState(route.state) };
}),
};
};
/**
* Container component which holds the navigation state.
* This should be rendered at the root wrapping the whole app.
*
* @param props.initialState Initial state object for the navigation tree.
* @param props.onStateChange Callback which is called with the latest navigation state when it changes.
* @param props.children Child elements to render the content.
* @param props.ref Ref object which refers to the navigation object containing helper methods.
*/
const BaseNavigationContainer = React.forwardRef(
function BaseNavigationContainer(
{
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. Note that this will make the child navigators disconnected from the parent and you won't be able to navigate between them."
);
}
const [
state,
getState,
setState,
scheduleUpdate,
flushUpdates,
] = useSyncState<State>(() =>
getPartialState(initialState == null ? undefined : initialState)
);
const isFirstMountRef = React.useRef<boolean>(true);
const skipTrackingRef = React.useRef<boolean>(false);
const navigatorKeyRef = React.useRef<string | undefined>();
const getKey = React.useCallback(() => navigatorKeyRef.current, []);
const setKey = React.useCallback((key: string) => {
navigatorKeyRef.current = key;
}, []);
const reset = React.useCallback(
(state: NavigationState) => {
skipTrackingRef.current = true;
setState(state);
},
[setState]
);
const { trackState, trackAction } = useDevTools({
enabled: false,
name: '@react-navigation',
reset,
state,
});
const {
listeners,
addListener: addFocusedListener,
} = useFocusedListeners();
const { getStateForRoute, addStateGetter } = useStateGetters();
const dispatch = (
action: NavigationAction | ((state: NavigationState) => NavigationAction)
) => {
if (listeners[0] == null) {
throw new Error(NOT_INITIALIZED_ERROR);
}
listeners[0]((navigation) => navigation.dispatch(action));
};
const canGoBack = () => {
if (listeners[0] == null) {
return false;
}
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');
setState(state);
},
[setState, trackAction]
);
const getRootState = React.useCallback(() => {
return getStateForRoute('root');
}, [getStateForRoute]);
const emitter = useEventEmitter();
React.useImperativeHandle(ref, () => ({
...(Object.keys(CommonActions) as (keyof typeof CommonActions)[]).reduce<
any
>((acc, name) => {
acc[name] = (...args: any[]) =>
dispatch(
CommonActions[name](
// @ts-ignore
...args
)
);
return acc;
}, {}),
...emitter.create('root'),
resetRoot,
dispatch,
canGoBack,
getRootState,
}));
const builderContext = React.useMemo(
() => ({
addFocusedListener,
addStateGetter,
trackAction,
}),
[addFocusedListener, trackAction, addStateGetter]
);
const scheduleContext = React.useMemo(
() => ({ scheduleUpdate, flushUpdates }),
[scheduleUpdate, flushUpdates]
);
const context = React.useMemo(
() => ({
state,
getState,
setState,
getKey,
setKey,
}),
[getKey, getState, setKey, setState, state]
);
React.useEffect(() => {
if (process.env.NODE_ENV !== 'production') {
if (
state !== undefined &&
!isSerializable(state) &&
!hasWarnedForSerialization
) {
hasWarnedForSerialization = true;
console.warn(
"Non-serializable values were found in the navigation state, which can break usage such as persisting and restoring state. This might happen if you passed non-serializable values such as function, class instances etc. in params. If you need to use components with callbacks in your options, you can use 'navigation.setOptions' instead. See https://reactnavigation.org/docs/troubleshooting#i-get-the-warning-non-serializable-values-were-found-in-the-navigation-state for more details."
);
}
}
emitter.emit({
type: 'state',
data: { state },
});
if (skipTrackingRef.current) {
skipTrackingRef.current = false;
} else {
trackState(getRootState);
}
if (!isFirstMountRef.current && onStateChange) {
onStateChange(getRootState());
}
isFirstMountRef.current = false;
}, [onStateChange, trackState, getRootState, emitter, state]);
return (
<ScheduleUpdateContext.Provider value={scheduleContext}>
<NavigationBuilderContext.Provider value={builderContext}>
<NavigationStateContext.Provider value={context}>
<EnsureSingleNavigator>{children}</EnsureSingleNavigator>
</NavigationStateContext.Provider>
</NavigationBuilderContext.Provider>
</ScheduleUpdateContext.Provider>
);
}
);
export default BaseNavigationContainer;

View File

@@ -1,45 +0,0 @@
import { CommonAction, NavigationState, PartialState } from './types';
/**
* Base router object that can be used when writing custom routers.
* This provides few helper methods to handle common actions such as `RESET`.
*/
const BaseRouter = {
getStateForAction<State extends NavigationState>(
state: State,
action: CommonAction
): State | PartialState<State> | null {
switch (action.type) {
case 'SET_PARAMS': {
const index = action.source
? state.routes.findIndex(r => r.key === action.source)
: state.index;
if (index === -1) {
return null;
}
return {
...state,
routes: state.routes.map((r, i) =>
i === index
? { ...r, params: { ...r.params, ...action.payload.params } }
: r
),
};
}
case 'RESET':
return action.payload as PartialState<State>;
default:
return null;
}
},
shouldActionChangeFocus(action: CommonAction) {
return action.type === 'NAVIGATE';
},
};
export default BaseRouter;

View File

@@ -4,7 +4,7 @@ type Props = {
children: React.ReactNode;
};
const MULTIPLE_NAVIGATOR_ERROR = `Another navigator is already registered for this container. You likely have multiple navigators under a single "NavigationContainer" or "Screen". Make sure each navigator is under a separate "Screen" container.`;
const MULTIPLE_NAVIGATOR_ERROR = `Another navigator is already registered for this container. You likely have multiple navigators under a single "NavigationContainer" or "Screen". Make sure each navigator is under a separate "Screen" container. See https://reactnavigation.org/docs/nesting-navigators for a guide on nesting.`;
export const SingleNavigatorContext = React.createContext<
| {

View File

@@ -1,10 +1,10 @@
import * as React from 'react';
import {
NavigationAction,
NavigationHelpers,
NavigationState,
ParamListBase,
} from './types';
} from '@react-navigation/routers';
import { NavigationHelpers } from './types';
export type ChildActionListener = (
action: NavigationAction,

View File

@@ -1,295 +0,0 @@
import * as React from 'react';
import * as CommonActions from './CommonActions';
import EnsureSingleNavigator from './EnsureSingleNavigator';
import NavigationBuilderContext from './NavigationBuilderContext';
import useFocusedListeners from './useFocusedListeners';
import useDevTools from './useDevTools';
import useStateGetters from './useStateGetters';
import isSerializable from './isSerializable';
import {
Route,
NavigationState,
InitialState,
PartialState,
NavigationAction,
NavigationContainerRef,
NavigationContainerProps,
} from './types';
import useEventEmitter from './useEventEmitter';
type State = NavigationState | PartialState<NavigationState> | undefined;
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 | PartialState<NavigationState> | undefined
) => void;
key?: string;
performTransaction: (action: () => void) => void;
}>({
isDefault: true,
get getState(): any {
throw new Error(MISSING_CONTEXT_ERROR);
},
get setState(): any {
throw new Error(MISSING_CONTEXT_ERROR);
},
get performTransaction(): any {
throw new Error(MISSING_CONTEXT_ERROR);
},
});
let hasWarnedForSerialization = false;
/**
* Remove `key` and `routeNames` from the state objects recursively to get partial state.
*
* @param state Initial state object.
*/
const getPartialState = (
state: InitialState | undefined
): PartialState<NavigationState> | undefined => {
if (state === undefined) {
return;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { key, routeNames, ...partialState } = state;
// @ts-ignore
return {
...partialState,
stale: true,
routes: state.routes.map(route => {
if (route.state === undefined) {
return route as Route<string> & {
state?: PartialState<NavigationState>;
};
}
return { ...route, state: getPartialState(route.state) };
}),
};
};
/**
* Container component which holds the navigation state.
* This should be rendered at the root wrapping the whole app.
*
* @param props.initialState Initial state object for the navigation tree.
* @param props.onStateChange Callback which is called with the latest navigation state when it changes.
* @param props.children Child elements to render the content.
* @param props.ref Ref object which refers to the navigation object containing helper methods.
*/
const Container = React.forwardRef(function NavigationContainer(
{
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 == null ? undefined : initialState)
);
const navigationStateRef = React.useRef<State>();
const transactionStateRef = React.useRef<State | null>(null);
const isTransactionActiveRef = React.useRef<boolean>(false);
const isFirstMountRef = React.useRef<boolean>(true);
const skipTrackingRef = React.useRef<boolean>(false);
const performTransaction = React.useCallback((callback: () => void) => {
if (isTransactionActiveRef.current) {
throw new Error(
"Only one transaction can be active at a time. Did you accidentally nest 'performTransaction'?"
);
}
setNavigationState((navigationState: State) => {
isTransactionActiveRef.current = true;
transactionStateRef.current = navigationState;
try {
callback();
} finally {
isTransactionActiveRef.current = false;
}
return transactionStateRef.current;
});
}, []);
const getState = React.useCallback(
() =>
transactionStateRef.current !== null
? transactionStateRef.current
: navigationStateRef.current,
[]
);
const setState = React.useCallback((navigationState: State) => {
if (transactionStateRef.current === null) {
throw new Error(
"Any 'setState' calls need to be done inside 'performTransaction'"
);
}
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]);
const emitter = useEventEmitter();
React.useImperativeHandle(ref, () => ({
...(Object.keys(CommonActions) as (keyof typeof CommonActions)[]).reduce<
any
>((acc, name) => {
acc[name] = (...args: any[]) =>
dispatch(
CommonActions[name](
// @ts-ignore
...args
)
);
return acc;
}, {}),
...emitter.create('root'),
resetRoot,
dispatch,
canGoBack,
getRootState,
}));
const builderContext = React.useMemo(
() => ({
addFocusedListener,
addStateGetter,
trackAction,
}),
[addFocusedListener, trackAction, addStateGetter]
);
const context = React.useMemo(
() => ({
state,
performTransaction,
getState,
setState,
}),
[getState, performTransaction, setState, state]
);
React.useEffect(() => {
if (process.env.NODE_ENV !== 'production') {
if (
state !== undefined &&
!isSerializable(state) &&
!hasWarnedForSerialization
) {
hasWarnedForSerialization = true;
console.warn(
"We found non-serializable values in the navigation state, which can break usage such as persisting and restoring state. This might happen if you passed non-serializable values such as function, class instances etc. in params. If you need to use functions in your options, you can use 'navigation.setOptions' instead."
);
}
}
emitter.emit({
type: 'state',
data: { state },
});
if (skipTrackingRef.current) {
skipTrackingRef.current = false;
} else {
trackState(getRootState);
}
navigationStateRef.current = state;
transactionStateRef.current = null;
if (!isFirstMountRef.current && onStateChange) {
onStateChange(getRootState());
}
isFirstMountRef.current = false;
}, [state, onStateChange, trackState, getRootState, emitter]);
return (
<NavigationBuilderContext.Provider value={builderContext}>
<NavigationStateContext.Provider value={context}>
<EnsureSingleNavigator>{children}</EnsureSingleNavigator>
</NavigationStateContext.Provider>
</NavigationBuilderContext.Provider>
);
});
export default Container;

View File

@@ -1,5 +1,6 @@
import * as React from 'react';
import { NavigationProp, ParamListBase } from './types';
import { ParamListBase } from '@react-navigation/routers';
import { NavigationProp } from './types';
/**
* Context which holds the navigation prop for a screen.

View File

@@ -1,5 +1,5 @@
import * as React from 'react';
import { Route } from './types';
import { Route } from '@react-navigation/routers';
/**
* Context which holds the route prop for a screen.

View File

@@ -1,20 +1,23 @@
import * as React from 'react';
import { NavigationStateContext } from './NavigationContainer';
import NavigationContext from './NavigationContext';
import NavigationRouteContext from './NavigationRouteContext';
import StaticContainer from './StaticContainer';
import EnsureSingleNavigator from './EnsureSingleNavigator';
import {
Route,
ParamListBase,
NavigationState,
NavigationProp,
RouteConfig,
PartialState,
} from './types';
} from '@react-navigation/routers';
import { NavigationStateContext } from './BaseNavigationContainer';
import NavigationContext from './NavigationContext';
import NavigationRouteContext from './NavigationRouteContext';
import StaticContainer from './StaticContainer';
import EnsureSingleNavigator from './EnsureSingleNavigator';
import { NavigationProp, RouteConfig, EventMapBase } from './types';
type Props<State extends NavigationState, ScreenOptions extends object> = {
screen: RouteConfig<ParamListBase, string, ScreenOptions>;
type Props<
State extends NavigationState,
ScreenOptions extends object,
EventMap extends EventMapBase
> = {
screen: RouteConfig<ParamListBase, string, State, ScreenOptions, EventMap>;
navigation: NavigationProp<ParamListBase, string, State, ScreenOptions>;
route: Route<string> & {
state?: NavigationState | PartialState<NavigationState>;
@@ -29,19 +32,26 @@ type Props<State extends NavigationState, ScreenOptions extends object> = {
*/
export default function SceneView<
State extends NavigationState,
ScreenOptions extends object
ScreenOptions extends object,
EventMap extends EventMapBase
>({
screen,
route,
navigation,
getState,
setState,
}: Props<State, ScreenOptions>) {
const { performTransaction } = React.useContext(NavigationStateContext);
}: Props<State, ScreenOptions, EventMap>) {
const navigatorKeyRef = React.useRef<string | undefined>();
const getKey = React.useCallback(() => navigatorKeyRef.current, []);
const setKey = React.useCallback((key: string) => {
navigatorKeyRef.current = key;
}, []);
const getCurrentState = React.useCallback(() => {
const state = getState();
const currentRoute = state.routes.find(r => r.key === route.key);
const currentRoute = state.routes.find((r) => r.key === route.key);
return currentRoute ? currentRoute.state : undefined;
}, [getState, route.key]);
@@ -52,7 +62,7 @@ export default function SceneView<
setState({
...state,
routes: state.routes.map(r =>
routes: state.routes.map((r) =>
r.key === route.key ? { ...r, state: child } : r
),
});
@@ -65,16 +75,10 @@ export default function SceneView<
state: route.state,
getState: getCurrentState,
setState: setCurrentState,
performTransaction,
key: route.key,
getKey,
setKey,
}),
[
getCurrentState,
performTransaction,
route.key,
route.state,
setCurrentState,
]
[getCurrentState, getKey, route.state, setCurrentState, setKey]
);
return (

View File

@@ -1,4 +1,5 @@
import { RouteConfig, ParamListBase } from './types';
import { ParamListBase, NavigationState } from '@react-navigation/routers';
import { RouteConfig, EventMapBase } from './types';
/**
* Empty component used for specifying route configuration.
@@ -6,8 +7,10 @@ import { RouteConfig, ParamListBase } from './types';
export default function Screen<
ParamList extends ParamListBase,
RouteName extends keyof ParamList,
ScreenOptions extends object
>(_: RouteConfig<ParamList, RouteName, ScreenOptions>) {
State extends NavigationState,
ScreenOptions extends object,
EventMap extends EventMapBase
>(_: RouteConfig<ParamList, RouteName, State, ScreenOptions, EventMap>) {
/* istanbul ignore next */
return null;
}

View File

@@ -8,12 +8,19 @@ function StaticContainer(props: any) {
}
export default React.memo(StaticContainer, (prevProps: any, nextProps: any) => {
for (const prop in prevProps) {
if (prop === 'children') {
const prevPropKeys = Object.keys(prevProps);
const nextPropKeys = Object.keys(nextProps);
if (prevPropKeys.length !== nextPropKeys.length) {
return false;
}
for (const key of prevPropKeys) {
if (key === 'children') {
continue;
}
if (prevProps[prop] !== nextProps[prop]) {
if (prevProps[key] !== nextProps[key]) {
return false;
}
}

View File

@@ -1,17 +1,17 @@
import * as React from 'react';
import { act, render } from 'react-native-testing-library';
import NavigationContainer, {
NavigationStateContext,
} from '../NavigationContainer';
import MockRouter, { MockActions } from './__fixtures__/MockRouter';
import useNavigationBuilder from '../useNavigationBuilder';
import Screen from '../Screen';
import {
DefaultRouterOptions,
NavigationState,
Router,
NavigationContainerRef,
} from '../types';
} from '@react-navigation/routers';
import BaseNavigationContainer, {
NavigationStateContext,
} from '../BaseNavigationContainer';
import MockRouter, { MockActions } from './__fixtures__/MockRouter';
import useNavigationBuilder from '../useNavigationBuilder';
import Screen from '../Screen';
import { NavigationContainerRef } from '../types';
it('throws when getState is accessed without a container', () => {
expect.assertions(1);
@@ -28,7 +28,7 @@ it('throws when getState is accessed without a container', () => {
const element = <Test />;
expect(() => render(element).update(element)).toThrowError(
"We couldn't find a navigation context. Have you wrapped your app with 'NavigationContainer'?"
"Couldn't find a navigation context. Have you wrapped your app with 'NavigationContainer'?"
);
});
@@ -47,89 +47,18 @@ it('throws when setState is accessed without a container', () => {
const element = <Test />;
expect(() => render(element).update(element)).toThrowError(
"We couldn't find a navigation context. Have you wrapped your app with 'NavigationContainer'?"
);
});
it('throws when performTransaction is accessed without a container', () => {
expect.assertions(1);
const Test = () => {
const { performTransaction } = React.useContext(NavigationStateContext);
// eslint-disable-next-line babel/no-unused-expressions
performTransaction;
return null;
};
const element = <Test />;
expect(() => render(element).update(element)).toThrowError(
"We couldn't find a navigation context. Have you wrapped your app with 'NavigationContainer'?"
);
});
it('throws when setState is called outside performTransaction', () => {
expect.assertions(1);
const Test = () => {
const { setState } = React.useContext(NavigationStateContext);
React.useEffect(() => {
setState(undefined);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return null;
};
const element = (
<NavigationContainer>
<Test />
</NavigationContainer>
);
expect(() => render(element).update(element)).toThrowError(
"Any 'setState' calls need to be done inside 'performTransaction'"
);
});
it('throws when nesting performTransaction', () => {
expect.assertions(1);
const Test = () => {
const { performTransaction } = React.useContext(NavigationStateContext);
React.useEffect(() => {
performTransaction(() => {
performTransaction(() => {});
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return null;
};
const element = (
<NavigationContainer>
<Test />
</NavigationContainer>
);
expect(() => render(element).update(element)).toThrowError(
"Only one transaction can be active at a time. Did you accidentally nest 'performTransaction'?"
"Couldn't find a navigation context. Have you wrapped your app with 'NavigationContainer'?"
);
});
it('throws when nesting containers', () => {
expect(() =>
render(
<NavigationContainer>
<NavigationContainer>
<BaseNavigationContainer>
<BaseNavigationContainer>
<React.Fragment />
</NavigationContainer>
</NavigationContainer>
</BaseNavigationContainer>
</BaseNavigationContainer>
)
).toThrowError(
"Looks like you have nested a 'NavigationContainer' inside another."
@@ -137,11 +66,11 @@ it('throws when nesting containers', () => {
expect(() =>
render(
<NavigationContainer>
<NavigationContainer independent>
<BaseNavigationContainer>
<BaseNavigationContainer independent>
<React.Fragment />
</NavigationContainer>
</NavigationContainer>
</BaseNavigationContainer>
</BaseNavigationContainer>
)
).not.toThrowError(
"Looks like you have nested a 'NavigationContainer' inside another."
@@ -193,7 +122,7 @@ it('handle dispatching with ref', () => {
return (
<React.Fragment>
{state.routes.map(route => descriptors[route.key].render())}
{state.routes.map((route) => descriptors[route.key].render())}
</React.Fragment>
);
};
@@ -223,7 +152,7 @@ it('handle dispatching with ref', () => {
};
const element = (
<NavigationContainer
<BaseNavigationContainer
ref={ref}
initialState={initialState}
onStateChange={onStateChange}
@@ -248,7 +177,7 @@ it('handle dispatching with ref', () => {
)}
</Screen>
</ParentNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
render(element).update(element);
@@ -291,7 +220,7 @@ it('handle resetting state with ref', () => {
return (
<React.Fragment>
{state.routes.map(route => descriptors[route.key].render())}
{state.routes.map((route) => descriptors[route.key].render())}
</React.Fragment>
);
};
@@ -301,7 +230,7 @@ it('handle resetting state with ref', () => {
const onStateChange = jest.fn();
const element = (
<NavigationContainer ref={ref} onStateChange={onStateChange}>
<BaseNavigationContainer ref={ref} onStateChange={onStateChange}>
<TestNavigator>
<Screen name="foo">{() => null}</Screen>
<Screen name="foo2">
@@ -322,7 +251,7 @@ it('handle resetting state with ref', () => {
)}
</Screen>
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
render(element).update(element);
@@ -389,7 +318,7 @@ it('handles getRootState', () => {
const ref = React.createRef<NavigationContainerRef>();
const element = (
<NavigationContainer ref={ref}>
<BaseNavigationContainer ref={ref}>
<TestNavigator initialRouteName="foo">
<Screen name="foo">
{() => (
@@ -401,7 +330,7 @@ it('handles getRootState', () => {
</Screen>
<Screen name="bar">{() => null}</Screen>
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
render(element);
@@ -442,7 +371,7 @@ it('emits state events when the state changes', () => {
return (
<React.Fragment>
{state.routes.map(route => descriptors[route.key].render())}
{state.routes.map((route) => descriptors[route.key].render())}
</React.Fragment>
);
};
@@ -450,13 +379,13 @@ it('emits state events when the state changes', () => {
const ref = React.createRef<NavigationContainerRef>();
const element = (
<NavigationContainer ref={ref}>
<BaseNavigationContainer ref={ref}>
<TestNavigator>
<Screen name="foo">{() => null}</Screen>
<Screen name="bar">{() => null}</Screen>
<Screen name="baz">{() => null}</Screen>
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
render(element).update(element);
@@ -501,3 +430,51 @@ it('emits state events when the state changes', () => {
],
});
});
it('throws if there is no navigator rendered', () => {
expect.assertions(1);
const ref = React.createRef<NavigationContainerRef>();
const element = <BaseNavigationContainer ref={ref} children={null} />;
render(element);
act(() => {
expect(() => ref.current?.dispatch({ type: 'WHATEVER' })).toThrow(
"The 'navigation' object hasn't been initialized yet."
);
});
});
it("throws if the ref hasn't finished initializing", () => {
expect.assertions(1);
const ref = React.createRef<NavigationContainerRef>();
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return descriptors[state.routes[state.index].key].render();
};
const TestScreen = () => {
React.useEffect(() => {
expect(() => ref.current?.dispatch({ type: 'WHATEVER' })).toThrow(
"The 'navigation' object hasn't been initialized yet."
);
}, []);
return null;
};
const element = (
<BaseNavigationContainer ref={ref}>
<TestNavigator>
<Screen name="foo" component={TestScreen} />
</TestNavigator>
</BaseNavigationContainer>
);
render(element);
});

View File

@@ -1,43 +0,0 @@
import * as React from 'react';
import { render } from 'react-native-testing-library';
import Screen from '../Screen';
import NavigationContainer from '../NavigationContainer';
import useNavigationBuilder from '../useNavigationBuilder';
import MockRouter, { MockRouterKey } from './__fixtures__/MockRouter';
beforeEach(() => (MockRouterKey.current = 0));
it('throws if NAVIGATE dispatched neither key nor name', () => {
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.navigate({});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return null;
};
const onStateChange = jest.fn();
const element = (
<NavigationContainer onStateChange={onStateChange}>
<TestNavigator initialRouteName="foo">
<Screen
name="foo"
component={FooScreen}
initialParams={{ count: 10 }}
/>
</TestNavigator>
</NavigationContainer>
);
expect(() => render(element).update(element)).toThrowError(
'While calling navigate with an object as the argument, you need to specify name or key'
);
});

View File

@@ -49,3 +49,27 @@ it('updates element if any props changed', () => {
expect(root).toMatchInlineSnapshot(`"second"`);
});
it('updates element if any props are added', () => {
expect.assertions(2);
const Test = ({ label }: any) => {
return label;
};
const root = render(
<StaticContainer count={42}>
<Test label="first" />
</StaticContainer>
);
expect(root).toMatchInlineSnapshot(`"first"`);
root.update(
<StaticContainer count={42} moreCounts={12}>
<Test label="second" />
</StaticContainer>
);
expect(root).toMatchInlineSnapshot(`"second"`);
});

View File

@@ -1,13 +1,13 @@
import BaseRouter from '../../BaseRouter';
import {
BaseRouter,
Router,
CommonAction,
CommonNavigationAction,
NavigationState,
Route,
DefaultRouterOptions,
} from '../../types';
} from '@react-navigation/routers';
export type MockActions = CommonAction | { type: 'NOOP' | 'UPDATE' };
export type MockActions = CommonNavigationAction | { type: 'NOOP' | 'UPDATE' };
export const MockRouterKey = { current: 0 };
@@ -27,7 +27,7 @@ export default function MockRouter(options: DefaultRouterOptions) {
key: String(MockRouterKey.current++),
index,
routeNames,
routes: routeNames.map(name => ({
routes: routeNames.map((name) => ({
name,
key: name,
params: routeParamList[name],
@@ -43,9 +43,9 @@ export default function MockRouter(options: DefaultRouterOptions) {
}
const routes = state.routes
.filter(route => routeNames.includes(route.name))
.filter((route) => routeNames.includes(route.name))
.map(
route =>
(route) =>
({
...route,
key: route.key || `${route.name}-${MockRouterKey.current++}`,
@@ -73,7 +73,7 @@ export default function MockRouter(options: DefaultRouterOptions) {
},
getStateForRouteNamesChange(state, { routeNames }) {
const routes = state.routes.filter(route =>
const routes = state.routes.filter((route) =>
routeNames.includes(route.name)
);
@@ -86,7 +86,7 @@ export default function MockRouter(options: DefaultRouterOptions) {
},
getStateForRouteFocus(state, key) {
const index = state.routes.findIndex(r => r.key === key);
const index = state.routes.findIndex((r) => r.key === key);
if (index === -1 || index === state.index) {
return state;
@@ -105,7 +105,7 @@ export default function MockRouter(options: DefaultRouterOptions) {
case 'NAVIGATE': {
const index = state.routes.findIndex(
route => route.name === action.payload.name
(route) => route.name === action.payload.name
);
if (index === -1) {

View File

@@ -35,8 +35,49 @@ it('gets navigate action from state', () => {
author: 'jane',
},
screen: 'qux',
initial: true,
},
screen: 'bar',
initial: true,
},
},
type: 'NAVIGATE',
});
expect(
getActionFromState({
routes: [
{
name: 'foo',
state: {
routes: [
{
name: 'bar',
state: {
routes: [
{
name: 'qux',
params: { author: 'jane' },
},
{ name: 'quz' },
],
},
},
],
},
},
],
})
).toEqual({
payload: {
name: 'foo',
params: {
initial: true,
screen: 'bar',
params: {
screen: 'quz',
initial: false,
},
},
},
type: 'NAVIGATE',
@@ -53,13 +94,7 @@ it('gets reset action from state', () => {
{
name: 'bar',
state: {
routes: [
{
name: 'qux',
params: { author: 'jane' },
},
{ name: 'quz' },
],
routes: [],
},
},
],
@@ -68,8 +103,6 @@ it('gets reset action from state', () => {
],
};
expect(getActionFromState(state)).toEqual({
payload: state,
type: 'RESET_ROOT',
});
expect(getActionFromState(state)).toBe(undefined);
expect(getActionFromState({ routes: [] })).toBe(undefined);
});

View File

@@ -42,7 +42,8 @@ it('converts state to path string with config', () => {
Baz: {
path: 'baz/:author',
parse: {
author: (author: string) => author.replace(/^\w/, c => c.toUpperCase()),
author: (author: string) =>
author.replace(/^\w/, (c) => c.toUpperCase()),
id: (id: string) => Number(id.replace(/^x/, '')),
valid: Boolean,
},
@@ -128,7 +129,8 @@ it('handles state with config with nested screens', () => {
Baz: {
path: 'baz/:author',
parse: {
author: (author: string) => author.replace(/^\w/, c => c.toUpperCase()),
author: (author: string) =>
author.replace(/^\w/, (c) => c.toUpperCase()),
count: Number,
valid: Boolean,
},
@@ -192,12 +194,14 @@ it('handles state with config with nested screens and unused configs', () => {
Baz: {
path: 'baz/:author',
parse: {
author: (author: string) => author.replace(/^\w/, c => c.toUpperCase()),
author: (author: string) =>
author.replace(/^\w/, (c) => c.toUpperCase()),
count: Number,
valid: Boolean,
},
stringify: {
author: (author: string) => author.replace(/^\w/, c => c.toLowerCase()),
author: (author: string) =>
author.replace(/^\w/, (c) => c.toLowerCase()),
unknown: (_: unknown) => 'x',
},
},
@@ -236,12 +240,14 @@ it('handles state with config with nested screens and unused configs', () => {
});
it('handles nested object with stringify in it', () => {
const path = '/bar/sweet/apple/foe/bis/jane?answer=42&count=10&valid=true';
const path = '/bar/sweet/apple/foo/bis/jane?answer=42&count=10&valid=true';
const config = {
Foo: {
path: 'foo',
screens: {
Foe: 'foe',
Foe: {
path: 'foe',
},
},
},
Bar: 'bar/:type/:fruit',
@@ -253,11 +259,11 @@ it('handles nested object with stringify in it', () => {
path: 'bis/:author',
stringify: {
author: (author: string) =>
author.replace(/^\w/, c => c.toLowerCase()),
author.replace(/^\w/, (c) => c.toLowerCase()),
},
parse: {
author: (author: string) =>
author.replace(/^\w/, c => c.toUpperCase()),
author.replace(/^\w/, (c) => c.toUpperCase()),
count: Number,
valid: Boolean,
},
@@ -278,23 +284,16 @@ it('handles nested object with stringify in it', () => {
state: {
routes: [
{
name: 'Foe',
name: 'Baz',
state: {
routes: [
{
name: 'Baz',
state: {
routes: [
{
name: 'Bis',
params: {
author: 'Jane',
count: 10,
answer: '42',
valid: true,
},
},
],
name: 'Bis',
params: {
author: 'Jane',
count: 10,
answer: '42',
valid: true,
},
},
],
@@ -399,3 +398,332 @@ it('handles nested object for second route depth and and path and stringify in r
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
});
it('ignores empty string paths', () => {
const path = '/bar';
const config = {
Foo: {
path: '',
screens: {
Foe: 'foe',
},
},
Bar: 'bar',
};
const state = {
routes: [
{
name: 'Foo',
state: {
routes: [{ name: 'Bar' }],
},
},
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
});
it('cuts nested configs too', () => {
const path = '/baz';
const config = {
Foo: {
path: 'foo',
screens: {
Bar: '',
},
},
Baz: { path: 'baz' },
};
const state = {
routes: [
{
name: 'Foo',
state: {
routes: [
{
name: 'Bar',
state: {
routes: [{ name: 'Baz' }],
},
},
],
},
},
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
});
it('handles empty path at the end', () => {
const path = '/bar';
const config = {
Foo: {
path: 'foo',
screens: {
Bar: 'bar',
},
},
Baz: { path: '' },
};
const state = {
routes: [
{
name: 'Foo',
state: {
routes: [
{
name: 'Bar',
state: {
routes: [{ name: 'Baz' }],
},
},
],
},
},
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
});
it('returns "/" for empty path', () => {
const config = {
Foo: {
path: '',
screens: {
Bar: '',
},
},
};
const state = {
routes: [
{
name: 'Foo',
state: {
routes: [
{
name: 'Bar',
},
],
},
},
],
};
expect(getPathFromState(state, config)).toBe('/');
});
it('parses no path specified', () => {
const path = '/Foo/bar';
const config = {
Foo: {
screens: {
Foe: {},
},
},
Bar: 'bar',
};
const state = {
routes: [
{
name: 'Foo',
state: {
routes: [{ name: 'Bar' }],
},
},
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
});
it('parses no path specified in nested config', () => {
const path = '/Foo/Foe/bar';
const config = {
Foo: {
path: 'foo',
screens: {
Foe: {},
},
},
Bar: 'bar',
};
const state = {
routes: [
{
name: 'Foo',
state: {
routes: [
{
name: 'Foe',
state: {
routes: [{ name: 'Bar' }],
},
},
],
},
},
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
});
it('strips undefined query params', () => {
const path = '/bar/sweet/apple/foo/bis/jane?count=10&valid=true';
const config = {
Foo: {
path: 'foo',
screens: {
Foe: {
path: 'foe',
},
},
},
Bar: 'bar/:type/:fruit',
Baz: {
path: 'baz',
screens: {
Bos: 'bos',
Bis: {
path: 'bis/:author',
stringify: {
author: (author: string) =>
author.replace(/^\w/, (c) => c.toLowerCase()),
},
parse: {
author: (author: string) =>
author.replace(/^\w/, (c) => c.toUpperCase()),
count: Number,
valid: Boolean,
},
},
},
},
};
const state = {
routes: [
{
name: 'Bar',
params: { fruit: 'apple', type: 'sweet' },
state: {
routes: [
{
name: 'Foo',
state: {
routes: [
{
name: 'Baz',
state: {
routes: [
{
name: 'Bis',
params: {
author: 'Jane',
count: 10,
answer: undefined,
valid: true,
},
},
],
},
},
],
},
},
],
},
},
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
});
it('handles stripping all query params', () => {
const path = '/bar/sweet/apple/foo/bis/jane';
const config = {
Foo: {
path: 'foo',
screens: {
Foe: {
path: 'foe',
},
},
},
Bar: 'bar/:type/:fruit',
Baz: {
path: 'baz',
screens: {
Bos: 'bos',
Bis: {
path: 'bis/:author',
stringify: {
author: (author: string) =>
author.replace(/^\w/, (c) => c.toLowerCase()),
},
parse: {
author: (author: string) =>
author.replace(/^\w/, (c) => c.toUpperCase()),
count: Number,
valid: Boolean,
},
},
},
},
};
const state = {
routes: [
{
name: 'Bar',
params: { fruit: 'apple', type: 'sweet' },
state: {
routes: [
{
name: 'Foo',
state: {
routes: [
{
name: 'Baz',
state: {
routes: [
{
name: 'Bis',
params: {
author: 'Jane',
count: undefined,
answer: undefined,
valid: undefined,
},
},
],
},
},
],
},
},
],
},
},
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
});

View File

@@ -42,7 +42,8 @@ it('converts path string to initial state with config', () => {
Baz: {
path: 'baz/:author',
parse: {
author: (author: string) => author.replace(/^\w/, c => c.toUpperCase()),
author: (author: string) =>
author.replace(/^\w/, (c) => c.toUpperCase()),
count: Number,
valid: Boolean,
},
@@ -153,7 +154,8 @@ it('converts path string to initial state with config with nested screens', () =
Baz: {
path: 'baz/:author',
parse: {
author: (author: string) => author.replace(/^\w/, c => c.toUpperCase()),
author: (author: string) =>
author.replace(/^\w/, (c) => c.toUpperCase()),
count: Number,
valid: Boolean,
},
@@ -217,7 +219,8 @@ it('converts path string to initial state with config with nested screens and un
Baz: {
path: 'baz/:author',
parse: {
author: (author: string) => author.replace(/^\w/, c => c.toUpperCase()),
author: (author: string) =>
author.replace(/^\w/, (c) => c.toUpperCase()),
count: Number,
valid: Boolean,
id: Boolean,
@@ -277,11 +280,11 @@ it('handles nested object with unused configs and with parse in it', () => {
path: 'bis/:author',
stringify: {
author: (author: string) =>
author.replace(/^\w/, c => c.toLowerCase()),
author.replace(/^\w/, (c) => c.toLowerCase()),
},
parse: {
author: (author: string) =>
author.replace(/^\w/, c => c.toUpperCase()),
author.replace(/^\w/, (c) => c.toUpperCase()),
count: Number,
valid: Boolean,
},
@@ -394,13 +397,6 @@ it('handles parse in nested object for second route depth and and path and parse
screens: {
Foe: 'foe',
Bar: {
path: 'bar/:id',
parse: {
id: Number,
},
stringify: {
id: (id: number) => `id=${id}`,
},
screens: {
Baz: 'baz',
},
@@ -433,24 +429,260 @@ it('handles parse in nested object for second route depth and and path and parse
);
});
it('returns undefined if path is empty', () => {
it('handles initialRouteName', () => {
const path = '/baz';
const config = {
Foo: {
path: 'foo/:id',
starting: true,
stringify: {
id: (id: number) => `id=${id}`,
},
initialRouteName: 'Foe',
screens: {
Foe: 'foe',
Bar: {
path: 'bar/:id',
parse: {
id: Number,
screens: {
Baz: 'baz',
},
},
},
},
};
const state = {
routes: [
{
name: 'Foo',
state: {
index: 1,
routes: [
{
name: 'Foe',
},
{
name: 'Bar',
state: {
routes: [{ name: 'Baz' }],
},
},
],
},
},
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
});
it('handles initialRouteName included in path', () => {
const path = '/baz';
const config = {
Foo: {
initialRouteName: 'Foe',
screens: {
Foe: {
screens: {
Baz: 'baz',
},
},
Bar: 'bar',
},
},
};
const state = {
routes: [
{
name: 'Foo',
state: {
routes: [
{
name: 'Foe',
state: {
routes: [{ name: 'Baz' }],
},
},
],
},
},
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
});
it('handles two initialRouteNames', () => {
const path = '/bar/sweet/apple/foe/bis/jane?count=10&answer=42&valid=true';
const config = {
Foo: {
path: 'foo',
screens: {
Foe: 'foe',
},
},
Bar: 'bar/:type/:fruit',
Baz: {
initialRouteName: 'Bos',
screens: {
Bos: 'bos',
Bis: {
path: 'bis/:author',
stringify: {
id: (id: number) => `id=${id}`,
author: (author: string) =>
author.replace(/^\w/, (c) => c.toLowerCase()),
},
parse: {
author: (author: string) =>
author.replace(/^\w/, (c) => c.toUpperCase()),
count: Number,
valid: Boolean,
},
},
},
},
};
const state = {
routes: [
{
name: 'Bar',
params: { fruit: 'apple', type: 'sweet' },
state: {
routes: [
{
name: 'Foo',
state: {
routes: [
{
name: 'Foe',
state: {
routes: [
{
name: 'Baz',
state: {
index: 1,
routes: [
{ name: 'Bos' },
{
name: 'Bis',
params: {
author: 'Jane',
count: 10,
answer: '42',
valid: true,
},
},
],
},
},
],
},
},
],
},
},
],
},
},
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
});
it('accepts initialRouteName without config for it', () => {
const path = '/bar/sweet/apple/foe/bis/jane?count=10&answer=42&valid=true';
const config = {
Foo: {
path: 'foo',
screens: {
Foe: 'foe',
},
},
Bar: 'bar/:type/:fruit',
Baz: {
initialRouteName: 'Bas',
screens: {
Bos: 'bos',
Bis: {
path: 'bis/:author',
stringify: {
author: (author: string) =>
author.replace(/^\w/, (c) => c.toLowerCase()),
},
parse: {
author: (author: string) =>
author.replace(/^\w/, (c) => c.toUpperCase()),
count: Number,
valid: Boolean,
},
},
},
},
};
const state = {
routes: [
{
name: 'Bar',
params: { fruit: 'apple', type: 'sweet' },
state: {
routes: [
{
name: 'Foo',
state: {
routes: [
{
name: 'Foe',
state: {
routes: [
{
name: 'Baz',
state: {
index: 1,
routes: [
{ name: 'Bas' },
{
name: 'Bis',
params: {
author: 'Jane',
count: 10,
answer: '42',
valid: true,
},
},
],
},
},
],
},
},
],
},
},
],
},
},
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
});
it('returns undefined if path is empty', () => {
const config = {
Foo: {
screens: {
Foe: 'foe',
Bar: {
screens: {
Baz: 'baz',
},

View File

@@ -1,11 +1,12 @@
import * as React from 'react';
import { render, act } from 'react-native-testing-library';
import { NavigationState } from '@react-navigation/routers';
import Screen from '../Screen';
import NavigationContainer from '../NavigationContainer';
import BaseNavigationContainer from '../BaseNavigationContainer';
import useNavigationBuilder from '../useNavigationBuilder';
import useNavigation from '../useNavigation';
import MockRouter, { MockRouterKey } from './__fixtures__/MockRouter';
import { NavigationState, NavigationContainerRef } from '../types';
import { NavigationContainerRef } from '../types';
beforeEach(() => (MockRouterKey.current = 0));
@@ -28,7 +29,7 @@ it('initializes state for a navigator on navigation', () => {
const onStateChange = jest.fn();
const element = (
<NavigationContainer onStateChange={onStateChange}>
<BaseNavigationContainer onStateChange={onStateChange}>
<TestNavigator initialRouteName="foo">
<Screen
name="foo"
@@ -44,7 +45,7 @@ it('initializes state for a navigator on navigation', () => {
)}
</Screen>
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
render(element).update(element);
@@ -75,11 +76,11 @@ it("doesn't crash when initialState is null", () => {
const element = (
// @ts-ignore
<NavigationContainer initialState={null}>
<BaseNavigationContainer initialState={null}>
<TestNavigator>
<Screen name="foo" component={TestScreen} />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
expect(() => render(element)).not.toThrowError();
@@ -112,7 +113,7 @@ it('rehydrates state for a navigator on navigation', () => {
const onStateChange = jest.fn();
const element = (
<NavigationContainer
<BaseNavigationContainer
initialState={initialState}
onStateChange={onStateChange}
>
@@ -120,7 +121,7 @@ it('rehydrates state for a navigator on navigation', () => {
<Screen name="foo" component={jest.fn()} />
<Screen name="bar" component={BarScreen} />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
render(element).update(element);
@@ -166,7 +167,7 @@ it("doesn't rehydrate state if the type of state didn't match router", () => {
const onStateChange = jest.fn();
const element = (
<NavigationContainer
<BaseNavigationContainer
initialState={initialState}
onStateChange={onStateChange}
>
@@ -178,7 +179,7 @@ it("doesn't rehydrate state if the type of state didn't match router", () => {
/>
<Screen name="bar" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
render(element).update(element);
@@ -219,7 +220,7 @@ it('initializes state for nested screens in React.Fragment', () => {
const onStateChange = jest.fn();
const element = (
<NavigationContainer onStateChange={onStateChange}>
<BaseNavigationContainer onStateChange={onStateChange}>
<TestNavigator>
<Screen name="foo" component={TestScreen} />
<React.Fragment>
@@ -227,7 +228,7 @@ it('initializes state for nested screens in React.Fragment', () => {
<Screen name="baz" component={jest.fn()} />
</React.Fragment>
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
render(element).update(element);
@@ -266,7 +267,7 @@ it('initializes state for nested navigator on navigation', () => {
const onStateChange = jest.fn();
const element = (
<NavigationContainer onStateChange={onStateChange}>
<BaseNavigationContainer onStateChange={onStateChange}>
<TestNavigator initialRouteName="baz">
<Screen name="foo" component={jest.fn()} />
<Screen name="bar" component={jest.fn()} />
@@ -278,7 +279,7 @@ it('initializes state for nested navigator on navigation', () => {
)}
</Screen>
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
render(element).update(element);
@@ -328,12 +329,12 @@ it("doesn't update state if nothing changed", () => {
const onStateChange = jest.fn();
render(
<NavigationContainer onStateChange={onStateChange}>
<BaseNavigationContainer onStateChange={onStateChange}>
<TestNavigator initialRouteName="foo">
<Screen name="foo" component={FooScreen} />
<Screen name="bar" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
expect(onStateChange).toBeCalledTimes(0);
@@ -360,18 +361,18 @@ it("doesn't update state if action wasn't handled", () => {
const spy = jest.spyOn(console, 'error').mockImplementation();
render(
<NavigationContainer onStateChange={onStateChange}>
<BaseNavigationContainer onStateChange={onStateChange}>
<TestNavigator initialRouteName="foo">
<Screen name="foo" component={FooScreen} />
<Screen name="bar" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
expect(onStateChange).toBeCalledTimes(0);
expect(spy.mock.calls[0][0]).toMatch(
"The action 'INVALID' with payload 'undefined' was not handled by any navigator."
"The action 'INVALID' was not handled by any navigator."
);
spy.mockRestore();
@@ -396,12 +397,12 @@ it('cleans up state when the navigator unmounts', () => {
const onStateChange = jest.fn();
const element = (
<NavigationContainer onStateChange={onStateChange}>
<BaseNavigationContainer onStateChange={onStateChange}>
<TestNavigator>
<Screen name="foo" component={FooScreen} />
<Screen name="bar" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
const root = render(element);
@@ -422,7 +423,7 @@ it('cleans up state when the navigator unmounts', () => {
});
root.update(
<NavigationContainer onStateChange={onStateChange} children={null} />
<BaseNavigationContainer onStateChange={onStateChange} children={null} />
);
expect(onStateChange).toBeCalledTimes(2);
@@ -454,12 +455,12 @@ it('allows state updates by dispatching a function returning an action', () => {
const onStateChange = jest.fn();
const element = (
<NavigationContainer onStateChange={onStateChange}>
<BaseNavigationContainer onStateChange={onStateChange}>
<TestNavigator initialRouteName="foo">
<Screen name="foo" component={FooScreen} />
<Screen name="bar" component={BarScreen} />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
render(element).update(element);
@@ -496,12 +497,12 @@ it('updates route params with setParams', () => {
const onStateChange = jest.fn();
render(
<NavigationContainer onStateChange={onStateChange}>
<BaseNavigationContainer onStateChange={onStateChange}>
<TestNavigator initialRouteName="foo">
<Screen name="foo" component={FooScreen} />
<Screen name="bar" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
act(() => setParams({ username: 'alice' }));
@@ -556,7 +557,7 @@ it('updates route params with setParams applied to parent', () => {
const onStateChange = jest.fn();
render(
<NavigationContainer onStateChange={onStateChange}>
<BaseNavigationContainer onStateChange={onStateChange}>
<TestNavigator initialRouteName="foo">
<Screen name="foo">
{() => (
@@ -567,7 +568,7 @@ it('updates route params with setParams applied to parent', () => {
</Screen>
<Screen name="bar" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
act(() => setParams({ username: 'alice' }));
@@ -625,7 +626,7 @@ it('updates route params with setParams applied to parent', () => {
});
});
it('handles change in route names', () => {
it('handles change in route names', async () => {
const TestNavigator = (props: any): any => {
useNavigationBuilder(MockRouter, props);
return null;
@@ -634,22 +635,22 @@ it('handles change in route names', () => {
const onStateChange = jest.fn();
const root = render(
<NavigationContainer onStateChange={onStateChange}>
<BaseNavigationContainer>
<TestNavigator initialRouteName="bar">
<Screen name="foo" component={jest.fn()} />
<Screen name="bar" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
root.update(
<NavigationContainer onStateChange={onStateChange}>
<BaseNavigationContainer onStateChange={onStateChange}>
<TestNavigator>
<Screen name="foo" component={jest.fn()} />
<Screen name="baz" component={jest.fn()} />
<Screen name="qux" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
expect(onStateChange).toBeCalledWith({
@@ -677,7 +678,7 @@ it('navigates to nested child in a navigator', () => {
const navigation = React.createRef<NavigationContainerRef>();
const element = render(
<NavigationContainer ref={navigation} onStateChange={onStateChange}>
<BaseNavigationContainer ref={navigation} onStateChange={onStateChange}>
<TestNavigator>
<Screen name="foo">
{() => (
@@ -704,7 +705,7 @@ it('navigates to nested child in a navigator', () => {
)}
</Screen>
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
expect(element).toMatchInlineSnapshot(`"[foo-a, undefined]"`);
@@ -736,6 +737,366 @@ it('navigates to nested child in a navigator', () => {
);
});
it('navigates to nested child in a navigator with initial: false', () => {
const TestRouter: typeof MockRouter = (options) => {
const router = MockRouter(options);
return {
...router,
getStateForAction(state, action, options) {
switch (action.type) {
case 'NAVIGATE': {
if (!options.routeNames.includes(action.payload.name as any)) {
return null;
}
const routes = [
...state.routes,
{
key: String(MockRouterKey.current++),
name: action.payload.name,
params: action.payload.params,
},
];
return {
...state,
index: routes.length - 1,
routes,
};
}
default:
return router.getStateForAction(state, action, options);
}
},
} as typeof router;
};
const TestNavigator = (props: any): any => {
const { state, descriptors } = useNavigationBuilder(TestRouter, 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 first = render(
<BaseNavigationContainer ref={navigation} onStateChange={onStateChange}>
<TestNavigator>
<Screen name="foo">
{() => (
<TestNavigator>
<Screen name="foo-a" component={TestComponent} />
<Screen name="foo-b" component={TestComponent} />
</TestNavigator>
)}
</Screen>
<Screen name="bar">
{() => (
<TestNavigator initialRouteName="bar-a">
<Screen
name="bar-a"
component={TestComponent}
initialParams={{ lol: 'why' }}
/>
<Screen
name="bar-b"
component={TestComponent}
initialParams={{ some: 'stuff' }}
/>
</TestNavigator>
)}
</Screen>
</TestNavigator>
</BaseNavigationContainer>
);
expect(first).toMatchInlineSnapshot(`"[foo-a, undefined]"`);
expect(navigation.current?.getRootState()).toEqual({
index: 0,
key: '0',
routeNames: ['foo', 'bar'],
routes: [
{
key: 'foo',
name: 'foo',
state: {
index: 0,
key: '1',
routeNames: ['foo-a', 'foo-b'],
routes: [
{
key: 'foo-a',
name: 'foo-a',
},
{
key: 'foo-b',
name: 'foo-b',
},
],
stale: false,
type: 'test',
},
},
{ key: 'bar', name: 'bar' },
],
stale: false,
type: 'test',
});
act(
() =>
navigation.current &&
navigation.current.navigate('bar', {
screen: 'bar-b',
params: { test: 42 },
})
);
expect(first).toMatchInlineSnapshot(
`"[bar-b, {\\"some\\":\\"stuff\\",\\"test\\":42}]"`
);
expect(navigation.current?.getRootState()).toEqual({
index: 2,
key: '0',
routeNames: ['foo', 'bar'],
routes: [
{ key: 'foo', name: 'foo' },
{ key: 'bar', name: 'bar' },
{
key: '2',
name: 'bar',
params: { params: { test: 42 }, screen: 'bar-b' },
state: {
index: 1,
key: '3',
routeNames: ['bar-a', 'bar-b'],
routes: [
{
key: 'bar-a',
name: 'bar-a',
params: { lol: 'why' },
},
{
key: 'bar-b',
name: 'bar-b',
params: { some: 'stuff', test: 42 },
},
],
stale: false,
type: 'test',
},
},
],
stale: false,
type: 'test',
});
const second = render(
<BaseNavigationContainer ref={navigation} onStateChange={onStateChange}>
<TestNavigator>
<Screen name="foo">
{() => (
<TestNavigator>
<Screen name="foo-a" component={TestComponent} />
<Screen name="foo-b" component={TestComponent} />
</TestNavigator>
)}
</Screen>
<Screen name="bar">
{() => (
<TestNavigator initialRouteName="bar-a">
<Screen
name="bar-a"
component={TestComponent}
initialParams={{ lol: 'why' }}
/>
<Screen
name="bar-b"
component={TestComponent}
initialParams={{ some: 'stuff' }}
/>
</TestNavigator>
)}
</Screen>
</TestNavigator>
</BaseNavigationContainer>
);
expect(second).toMatchInlineSnapshot(`"[foo-a, undefined]"`);
expect(navigation.current?.getRootState()).toEqual({
index: 0,
key: '4',
routeNames: ['foo', 'bar'],
routes: [
{
key: 'foo',
name: 'foo',
state: {
index: 0,
key: '5',
routeNames: ['foo-a', 'foo-b'],
routes: [
{ key: 'foo-a', name: 'foo-a' },
{ key: 'foo-b', name: 'foo-b' },
],
stale: false,
type: 'test',
},
},
{ key: 'bar', name: 'bar' },
],
stale: false,
type: 'test',
});
act(
() =>
navigation.current &&
navigation.current.navigate('bar', {
screen: 'bar-b',
params: { test: 42 },
initial: false,
})
);
expect(second).toMatchInlineSnapshot(`"[bar-b, {\\"test\\":42}]"`);
expect(navigation.current?.getRootState()).toEqual({
index: 2,
key: '4',
routeNames: ['foo', 'bar'],
routes: [
{ key: 'foo', name: 'foo' },
{ key: 'bar', name: 'bar' },
{
key: '6',
name: 'bar',
params: { initial: false, params: { test: 42 }, screen: 'bar-b' },
state: {
index: 2,
key: '7',
routeNames: ['bar-a', 'bar-b'],
routes: [
{
key: 'bar-a',
name: 'bar-a',
params: { lol: 'why' },
},
{
key: 'bar-b',
name: 'bar-b',
params: { some: 'stuff' },
},
{ key: '8', name: 'bar-b', params: { test: 42 } },
],
stale: false,
type: 'test',
},
},
],
stale: false,
type: 'test',
});
const third = render(
<BaseNavigationContainer
ref={navigation}
initialState={{
index: 1,
routes: [
{ name: 'foo' },
{
name: 'bar',
params: { initial: false, params: { test: 42 }, screen: 'bar-b' },
state: {
index: 1,
key: '7',
routes: [
{
name: 'bar-a',
params: { lol: 'why' },
},
{
name: 'bar-b',
params: { some: 'stuff' },
},
],
type: 'test',
},
},
],
type: 'test',
}}
>
<TestNavigator>
<Screen name="foo" component={TestComponent} />
<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>
</BaseNavigationContainer>
);
expect(third).toMatchInlineSnapshot(`"[bar-b, {\\"some\\":\\"stuff\\"}]"`);
expect(navigation.current?.getRootState()).toEqual({
index: 1,
key: '11',
routeNames: ['foo', 'bar'],
routes: [
{ key: 'foo-9', name: 'foo' },
{
key: 'bar-10',
name: 'bar',
params: { initial: false, params: { test: 42 }, screen: 'bar-b' },
state: {
index: 1,
key: '14',
routeNames: ['bar-a', 'bar-b'],
routes: [
{
key: 'bar-a-12',
name: 'bar-a',
params: { lol: 'why' },
},
{
key: 'bar-b-13',
name: 'bar-b',
params: { some: 'stuff' },
},
],
stale: false,
type: 'test',
},
},
],
stale: false,
type: 'test',
});
});
it('gives access to internal state', () => {
const TestNavigator = (props: any): any => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
@@ -752,11 +1113,11 @@ it('gives access to internal state', () => {
};
const root = (
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator initialRouteName="bar">
<Screen name="bar" component={Test} />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
render(root).update(root);
@@ -771,6 +1132,60 @@ it('gives access to internal state', () => {
});
});
it('preserves order of screens in state with non-numeric names', () => {
const TestNavigator = (props: any): any => {
useNavigationBuilder(MockRouter, props);
return null;
};
const navigation = React.createRef<NavigationContainerRef>();
const root = (
<BaseNavigationContainer ref={navigation}>
<TestNavigator>
<Screen name="foo" component={jest.fn()} />
<Screen name="bar" component={jest.fn()} />
<Screen name="baz" component={jest.fn()} />
</TestNavigator>
</BaseNavigationContainer>
);
render(root);
expect(navigation.current?.getRootState().routeNames).toEqual([
'foo',
'bar',
'baz',
]);
});
it('preserves order of screens in state with numeric names', () => {
const TestNavigator = (props: any): any => {
useNavigationBuilder(MockRouter, props);
return null;
};
const navigation = React.createRef<NavigationContainerRef>();
const root = (
<BaseNavigationContainer ref={navigation}>
<TestNavigator>
<Screen name="4" component={jest.fn()} />
<Screen name="7" component={jest.fn()} />
<Screen name="1" component={jest.fn()} />
</TestNavigator>
</BaseNavigationContainer>
);
render(root);
expect(navigation.current?.getRootState().routeNames).toEqual([
'4',
'7',
'1',
]);
});
it("throws if navigator doesn't have any screens", () => {
const TestNavigator = (props: any) => {
useNavigationBuilder(MockRouter, props);
@@ -778,9 +1193,9 @@ it("throws if navigator doesn't have any screens", () => {
};
const element = (
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator />
</NavigationContainer>
</BaseNavigationContainer>
);
expect(() => render(element).update(element)).toThrowError(
@@ -812,14 +1227,14 @@ it('throws if multiple navigators rendered under one container', () => {
};
const element = (
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator>
<Screen name="foo" component={jest.fn()} />
</TestNavigator>
<TestNavigator>
<Screen name="foo" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
expect(() => render(element).update(element)).toThrowError(
@@ -836,12 +1251,12 @@ it('throws when Screen is not the direct children', () => {
const Bar = () => null;
const element = (
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator>
<Screen name="foo" component={jest.fn()} />
<Bar />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
expect(() => render(element).update(element)).toThrowError(
@@ -856,12 +1271,12 @@ it('throws when a React Element is not the direct children', () => {
};
const element = (
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator>
<Screen name="foo" component={jest.fn()} />
Hello world
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
expect(() => render(element).update(element)).toThrowError(
@@ -877,7 +1292,7 @@ it("doesn't throw when direct children is Screen or empty element", () => {
};
render(
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator>
<Screen name="foo" component={jest.fn()} />
{null}
@@ -885,7 +1300,7 @@ it("doesn't throw when direct children is Screen or empty element", () => {
{false}
{true}
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
});
@@ -896,13 +1311,13 @@ it('throws when multiple screens with same name are defined', () => {
};
const element = (
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator>
<Screen name="foo" component={jest.fn()} />
<Screen name="bar" component={jest.fn()} />
<Screen name="foo" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
expect(() => render(element).update(element)).toThrowError(
@@ -917,20 +1332,20 @@ it('switches rendered navigators', () => {
};
const root = render(
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator key="a">
<Screen name="foo" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
expect(() =>
root.update(
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator key="b">
<Screen name="foo" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
)
).not.toThrowError(
'Another navigator is already registered for this container.'
@@ -944,11 +1359,11 @@ it('throws if no name is passed to Screen', () => {
};
const element = (
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator>
<Screen name={undefined as any} component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
expect(() => render(element).update(element)).toThrowError(
@@ -963,11 +1378,11 @@ it('throws if invalid name is passed to Screen', () => {
};
const element = (
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator>
<Screen name={[] as any} component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
expect(() => render(element).update(element)).toThrowError(
@@ -982,13 +1397,13 @@ it('throws if both children and component are passed', () => {
};
const element = (
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator>
<Screen name="foo" component={jest.fn()}>
{jest.fn()}
</Screen>
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
expect(() => render(element).update(element)).toThrowError(
@@ -1003,11 +1418,11 @@ it('throws descriptive error for undefined screen component', () => {
};
const element = (
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator>
<Screen name="foo" component={undefined as any} />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
expect(() => render(element).update(element)).toThrowError(
@@ -1022,15 +1437,15 @@ it('throws descriptive error for invalid screen component', () => {
};
const element = (
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator>
<Screen name="foo" component={{} as any} />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
expect(() => render(element).update(element)).toThrowError(
"Got an invalid value for 'component' prop for the screen 'foo'. It must be a a valid React Component."
"Got an invalid value for 'component' prop for the screen 'foo'. It must be a valid React Component."
);
});
@@ -1041,11 +1456,11 @@ it('throws descriptive error for invalid children', () => {
};
const element = (
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator>
<Screen name="foo">{[] as any}</Screen>
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
expect(() => render(element).update(element)).toThrowError(
@@ -1060,13 +1475,13 @@ it("doesn't throw if children is null", () => {
};
const element = (
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator>
<Screen name="foo" component={jest.fn()}>
{null as any}
</Screen>
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
expect(() => render(element).update(element)).not.toThrowError();

View File

@@ -51,20 +51,55 @@ it('returns false for non-serializable object', () => {
});
it('returns false for circular references', () => {
const o = {
index: 0,
key: '7',
routeNames: ['foo', 'bar'],
routes: [
{
key: 'foo',
name: 'foo',
},
],
const x = {
a: 1,
b: { b1: 1 },
};
// @ts-ignore
o.routes[0].state = o;
x.b.b2 = x;
// @ts-ignore
x.c = x.b;
expect(isSerializable(o)).toBe(false);
expect(isSerializable(x)).toBe(false);
const y = [
{
label: 'home',
children: [{ label: 'product' }],
},
{ label: 'about', extend: {} },
];
// @ts-ignore
y[0].children[0].parent = y[0];
// @ts-ignore
y[1].extend.home = y[0].children[0];
expect(isSerializable(y)).toBe(false);
const z = {
name: 'sun',
child: [{ name: 'flower' }],
};
// @ts-ignore
z.child[0].parent = z;
expect(isSerializable(z)).toBe(false);
});
it("doesn't fail if same object used multiple times", () => {
const o = { foo: 'bar' };
expect(
isSerializable({
baz: 'bax',
first: o,
second: o,
stuff: {
b: o,
},
})
).toBe(true);
});

View File

@@ -1,13 +1,17 @@
import * as React from 'react';
import { render, act } from 'react-native-testing-library';
import {
DefaultRouterOptions,
NavigationState,
Router,
} from '@react-navigation/routers';
import useNavigationBuilder from '../useNavigationBuilder';
import NavigationContainer from '../NavigationContainer';
import BaseNavigationContainer from '../BaseNavigationContainer';
import Screen from '../Screen';
import MockRouter, {
MockActions,
MockRouterKey,
} from './__fixtures__/MockRouter';
import { DefaultRouterOptions, NavigationState, Router } from '../types';
jest.useFakeTimers();
@@ -34,7 +38,7 @@ it('sets options with options prop as an object', () => {
const TestScreen = (): any => 'Test screen';
const root = render(
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator>
<Screen
name="foo"
@@ -43,7 +47,7 @@ it('sets options with options prop as an object', () => {
/>
<Screen name="bar" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
expect(root).toMatchInlineSnapshot(`
@@ -79,7 +83,7 @@ it('sets options with options prop as a fuction', () => {
const TestScreen = (): any => 'Test screen';
const root = render(
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator>
<Screen
name="foo"
@@ -89,7 +93,7 @@ it('sets options with options prop as a fuction', () => {
/>
<Screen name="bar" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
expect(root).toMatchInlineSnapshot(`
@@ -115,7 +119,7 @@ it('sets options with screenOptions prop as an object', () => {
return (
<>
{state.routes.map(route => {
{state.routes.map((route) => {
const { render, options } = descriptors[route.key];
return (
@@ -134,12 +138,12 @@ it('sets options with screenOptions prop as an object', () => {
const TestScreenB = (): any => 'Test screen B';
const root = render(
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator screenOptions={{ title: 'Hello world' }}>
<Screen name="foo" component={TestScreenA} />
<Screen name="bar" component={TestScreenB} />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
expect(root).toMatchInlineSnapshot(`
@@ -175,7 +179,7 @@ it('sets options with screenOptions prop as a fuction', () => {
return (
<>
{state.routes.map(route => {
{state.routes.map((route) => {
const { render, options } = descriptors[route.key];
return (
@@ -194,7 +198,7 @@ it('sets options with screenOptions prop as a fuction', () => {
const TestScreenB = (): any => 'Test screen B';
const root = render(
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator
screenOptions={({ route }: any) => ({
title: `${route.name}: ${route.params.author || route.params.fruit}`,
@@ -211,7 +215,7 @@ it('sets options with screenOptions prop as a fuction', () => {
initialParams={{ fruit: 'Apple' }}
/>
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
expect(root).toMatchInlineSnapshot(`
@@ -258,22 +262,24 @@ it('sets initial options with setOptions', () => {
};
const TestScreen = ({ navigation }: any): any => {
navigation.setOptions({
title: 'Hello world',
React.useEffect(() => {
navigation.setOptions({
title: 'Hello world',
});
});
return 'Test screen';
};
const root = render(
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator>
<Screen name="foo" options={{ color: 'blue' }}>
{props => <TestScreen {...props} />}
{(props) => <TestScreen {...props} />}
</Screen>
<Screen name="bar" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
expect(root).toMatchInlineSnapshot(`
@@ -311,12 +317,12 @@ it('updates options with setOptions', () => {
};
const TestScreen = ({ navigation }: any): any => {
navigation.setOptions({
title: 'Hello world',
description: 'Something here',
});
React.useEffect(() => {
navigation.setOptions({
title: 'Hello world',
description: 'Something here',
});
const timer = setTimeout(() =>
navigation.setOptions({
title: 'Hello again',
@@ -331,14 +337,14 @@ it('updates options with setOptions', () => {
};
const element = (
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator>
<Screen name="foo" options={{ color: 'blue' }}>
{props => <TestScreen {...props} />}
{(props) => <TestScreen {...props} />}
</Screen>
<Screen name="bar" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
const root = render(element);
@@ -396,7 +402,7 @@ it("returns correct value for canGoBack when it's not overridden", () => {
};
const root = (
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator>
<Screen
name="foo"
@@ -405,7 +411,7 @@ it("returns correct value for canGoBack when it's not overridden", () => {
/>
<Screen name="bar" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
render(root).update(root);
@@ -452,11 +458,11 @@ it(`returns false for canGoBack when current router doesn't handle GO_BACK`, ()
};
const root = (
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator>
<Screen name="baz" component={TestScreen} />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
render(root).update(root);
@@ -513,7 +519,7 @@ it('returns true for canGoBack when current router handles GO_BACK', () => {
};
const root = (
<NavigationContainer>
<BaseNavigationContainer>
<ParentNavigator>
<Screen name="baz">
{() => (
@@ -523,7 +529,7 @@ it('returns true for canGoBack when current router handles GO_BACK', () => {
)}
</Screen>
</ParentNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
render(root).update(root);
@@ -580,7 +586,7 @@ it('returns true for canGoBack when parent router handles GO_BACK', () => {
};
const root = (
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator>
<Screen name="baz">
{() => (
@@ -597,7 +603,7 @@ it('returns true for canGoBack when parent router handles GO_BACK', () => {
)}
</Screen>
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
render(root).update(root);

View File

@@ -1,10 +1,10 @@
import * as React from 'react';
import { render, act } from 'react-native-testing-library';
import { Router, NavigationState } from '@react-navigation/routers';
import useNavigationBuilder from '../useNavigationBuilder';
import NavigationContainer from '../NavigationContainer';
import BaseNavigationContainer from '../BaseNavigationContainer';
import Screen from '../Screen';
import MockRouter from './__fixtures__/MockRouter';
import { Router, NavigationState } from '../types';
it('fires focus and blur events in root navigator', () => {
const TestNavigator = React.forwardRef((props: any, ref: any): any => {
@@ -15,7 +15,7 @@ it('fires focus and blur events in root navigator', () => {
React.useImperativeHandle(ref, () => navigation, [navigation]);
return state.routes.map(route => descriptors[route.key].render());
return state.routes.map((route) => descriptors[route.key].render());
});
const firstFocusCallback = jest.fn();
@@ -47,7 +47,7 @@ it('fires focus and blur events in root navigator', () => {
const navigation = React.createRef<any>();
const element = (
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator ref={navigation}>
<Screen
name="first"
@@ -66,7 +66,7 @@ it('fires focus and blur events in root navigator', () => {
component={createComponent(fourthFocusCallback, fourthBlurCallback)}
/>
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
render(element);
@@ -97,6 +97,69 @@ it('fires focus and blur events in root navigator', () => {
expect(fourthBlurCallback).toBeCalledTimes(0);
});
it('fires focus event after blur', () => {
const TestNavigator = React.forwardRef((props: any, ref: any): any => {
const { state, navigation, descriptors } = useNavigationBuilder(
MockRouter,
props
);
React.useImperativeHandle(ref, () => navigation, [navigation]);
return state.routes.map((route) => descriptors[route.key].render());
});
const callback = jest.fn();
const Test = ({ route, navigation }: any) => {
React.useEffect(
() =>
navigation.addListener('focus', () => callback(route.name, 'focus')),
[navigation, route.name]
);
React.useEffect(
() => navigation.addListener('blur', () => callback(route.name, 'blur')),
[navigation, route.name]
);
return null;
};
const navigation = React.createRef<any>();
const element = (
<BaseNavigationContainer>
<TestNavigator ref={navigation}>
<Screen name="first" component={Test} />
<Screen name="second" component={Test} />
</TestNavigator>
</BaseNavigationContainer>
);
render(element);
expect(callback.mock.calls).toEqual([['first', 'focus']]);
act(() => navigation.current.navigate('second'));
expect(callback.mock.calls).toEqual([
['first', 'focus'],
['first', 'blur'],
['second', 'focus'],
]);
act(() => navigation.current.navigate('first'));
expect(callback.mock.calls).toEqual([
['first', 'focus'],
['first', 'blur'],
['second', 'focus'],
['second', 'blur'],
['first', 'focus'],
]);
});
it('fires focus and blur events in nested navigator', () => {
const TestNavigator = React.forwardRef((props: any, ref: any): any => {
const { state, navigation, descriptors } = useNavigationBuilder(
@@ -106,7 +169,7 @@ it('fires focus and blur events in nested navigator', () => {
React.useImperativeHandle(ref, () => navigation, [navigation]);
return state.routes.map(route => descriptors[route.key].render());
return state.routes.map((route) => descriptors[route.key].render());
});
const firstFocusCallback = jest.fn();
@@ -139,7 +202,7 @@ it('fires focus and blur events in nested navigator', () => {
const child = React.createRef<any>();
const element = (
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator ref={parent}>
<Screen
name="first"
@@ -170,7 +233,7 @@ it('fires focus and blur events in nested navigator', () => {
)}
</Screen>
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
render(element);
@@ -188,10 +251,12 @@ it('fires focus and blur events in nested navigator', () => {
expect(thirdFocusCallback).toBeCalledTimes(0);
expect(secondFocusCallback).toBeCalledTimes(1);
expect(fourthBlurCallback).toBeCalledTimes(0);
act(() => parent.current.navigate('nested'));
expect(firstBlurCallback).toBeCalledTimes(1);
expect(secondBlurCallback).toBeCalledTimes(1);
expect(thirdFocusCallback).toBeCalledTimes(0);
expect(fourthFocusCallback).toBeCalledTimes(1);
@@ -199,6 +264,35 @@ it('fires focus and blur events in nested navigator', () => {
expect(fourthBlurCallback).toBeCalledTimes(1);
expect(thirdFocusCallback).toBeCalledTimes(1);
act(() => parent.current.navigate('first'));
expect(firstFocusCallback).toBeCalledTimes(2);
expect(thirdBlurCallback).toBeCalledTimes(1);
act(() => parent.current.navigate('nested', { screen: 'fourth' }));
expect(fourthFocusCallback).toBeCalledTimes(2);
expect(thirdBlurCallback).toBeCalledTimes(1);
expect(firstBlurCallback).toBeCalledTimes(2);
act(() => parent.current.navigate('nested', { screen: 'third' }));
expect(thirdFocusCallback).toBeCalledTimes(2);
expect(fourthBlurCallback).toBeCalledTimes(2);
// Make sure nothing else has changed
expect(firstFocusCallback).toBeCalledTimes(2);
expect(firstBlurCallback).toBeCalledTimes(2);
expect(secondFocusCallback).toBeCalledTimes(1);
expect(secondBlurCallback).toBeCalledTimes(1);
expect(thirdFocusCallback).toBeCalledTimes(2);
expect(thirdBlurCallback).toBeCalledTimes(1);
expect(fourthFocusCallback).toBeCalledTimes(2);
expect(fourthBlurCallback).toBeCalledTimes(2);
});
it('fires blur event when a route is removed with a delay', async () => {
@@ -307,12 +401,12 @@ it('fires blur event when a route is removed with a delay', async () => {
const navigation = React.createRef<any>();
const element = (
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator ref={navigation}>
<Screen name="first" component={First} />
<Screen name="second" component={Second} />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
render(element);
@@ -331,7 +425,7 @@ it('fires blur event when a route is removed with a delay', async () => {
expect(blurCallback).toBeCalledTimes(1);
});
it('fires custom events', () => {
it('fires custom events added with addListener', () => {
const eventName = 'someSuperCoolEvent';
const TestNavigator = React.forwardRef((props: any, ref: any): any => {
@@ -345,7 +439,7 @@ it('fires custom events', () => {
state,
]);
return state.routes.map(route => descriptors[route.key].render());
return state.routes.map((route) => descriptors[route.key].render());
});
const firstCallback = jest.fn();
@@ -363,13 +457,13 @@ it('fires custom events', () => {
const ref = React.createRef<any>();
const element = (
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator ref={ref}>
<Screen name="first" component={createComponent(firstCallback)} />
<Screen name="second" component={createComponent(secondCallback)} />
<Screen name="third" component={createComponent(thirdCallback)} />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
render(element);
@@ -378,10 +472,13 @@ it('fires custom events', () => {
expect(secondCallback).toBeCalledTimes(0);
expect(thirdCallback).toBeCalledTimes(0);
const target =
ref.current.state.routes[ref.current.state.routes.length - 1].key;
act(() => {
ref.current.navigation.emit({
type: eventName,
target: ref.current.state.routes[ref.current.state.routes.length - 1].key,
target,
data: 42,
});
});
@@ -391,6 +488,7 @@ it('fires custom events', () => {
expect(thirdCallback).toBeCalledTimes(1);
expect(thirdCallback.mock.calls[0][0].type).toBe('someSuperCoolEvent');
expect(thirdCallback.mock.calls[0][0].data).toBe(42);
expect(thirdCallback.mock.calls[0][0].target).toBe(target);
expect(thirdCallback.mock.calls[0][0].defaultPrevented).toBe(undefined);
expect(thirdCallback.mock.calls[0][0].preventDefault).toBe(undefined);
@@ -398,11 +496,280 @@ it('fires custom events', () => {
ref.current.navigation.emit({ type: eventName });
});
expect(firstCallback.mock.calls[0][0].target).toBe(undefined);
expect(secondCallback.mock.calls[0][0].target).toBe(undefined);
expect(thirdCallback.mock.calls[1][0].target).toBe(undefined);
expect(firstCallback).toBeCalledTimes(1);
expect(secondCallback).toBeCalledTimes(1);
expect(thirdCallback).toBeCalledTimes(2);
});
it("doesn't call same listener multiple times with addListener", () => {
const eventName = 'someSuperCoolEvent';
const TestNavigator = React.forwardRef((props: any, ref: any): any => {
const { state, navigation, descriptors } = useNavigationBuilder(
MockRouter,
props
);
React.useImperativeHandle(ref, () => ({ navigation, state }), [
navigation,
state,
]);
return state.routes.map((route) => descriptors[route.key].render());
});
const callback = jest.fn();
const Test = ({ navigation }: any) => {
React.useEffect(() => navigation.addListener(eventName, callback), [
navigation,
]);
return null;
};
const ref = React.createRef<any>();
const element = (
<BaseNavigationContainer>
<TestNavigator ref={ref}>
<Screen name="first" component={Test} />
<Screen name="second" component={Test} />
<Screen name="third" component={Test} />
</TestNavigator>
</BaseNavigationContainer>
);
render(element);
expect(callback).toBeCalledTimes(0);
act(() => {
ref.current.navigation.emit({ type: eventName });
});
expect(callback).toBeCalledTimes(1);
});
it('fires custom events added with listeners prop', () => {
const eventName = 'someSuperCoolEvent';
const TestNavigator = React.forwardRef((props: any, ref: any): any => {
const { state, navigation } = useNavigationBuilder(MockRouter, props);
React.useImperativeHandle(ref, () => ({ navigation, state }), [
navigation,
state,
]);
return null;
});
const firstCallback = jest.fn();
const secondCallback = jest.fn();
const thirdCallback = jest.fn();
const ref = React.createRef<any>();
const element = (
<BaseNavigationContainer>
<TestNavigator ref={ref}>
<Screen
name="first"
listeners={{ someSuperCoolEvent: firstCallback }}
component={jest.fn()}
/>
<Screen
name="second"
listeners={{ someSuperCoolEvent: secondCallback }}
component={jest.fn()}
/>
<Screen
name="third"
listeners={{ someSuperCoolEvent: thirdCallback }}
component={jest.fn()}
/>
</TestNavigator>
</BaseNavigationContainer>
);
render(element);
expect(firstCallback).toBeCalledTimes(0);
expect(secondCallback).toBeCalledTimes(0);
expect(thirdCallback).toBeCalledTimes(0);
const target =
ref.current.state.routes[ref.current.state.routes.length - 1].key;
act(() => {
ref.current.navigation.emit({
type: eventName,
target,
data: 42,
});
});
expect(firstCallback).toBeCalledTimes(0);
expect(secondCallback).toBeCalledTimes(0);
expect(thirdCallback).toBeCalledTimes(1);
expect(thirdCallback.mock.calls[0][0].type).toBe('someSuperCoolEvent');
expect(thirdCallback.mock.calls[0][0].data).toBe(42);
expect(thirdCallback.mock.calls[0][0].target).toBe(target);
expect(thirdCallback.mock.calls[0][0].defaultPrevented).toBe(undefined);
expect(thirdCallback.mock.calls[0][0].preventDefault).toBe(undefined);
act(() => {
ref.current.navigation.emit({ type: eventName });
});
expect(firstCallback.mock.calls[0][0].target).toBe(undefined);
expect(firstCallback).toBeCalledTimes(1);
expect(secondCallback).toBeCalledTimes(0);
expect(thirdCallback).toBeCalledTimes(1);
});
it("doesn't call same listener multiple times with listeners", () => {
const eventName = 'someSuperCoolEvent';
const TestNavigator = React.forwardRef((props: any, ref: any): any => {
const { state, navigation } = useNavigationBuilder(MockRouter, props);
React.useImperativeHandle(ref, () => ({ navigation, state }), [
navigation,
state,
]);
return null;
});
const callback = jest.fn();
const ref = React.createRef<any>();
const element = (
<BaseNavigationContainer>
<TestNavigator ref={ref}>
<Screen
name="first"
listeners={{ someSuperCoolEvent: callback }}
component={jest.fn()}
/>
<Screen
name="second"
listeners={{ someSuperCoolEvent: callback }}
component={jest.fn()}
/>
<Screen
name="third"
listeners={{ someSuperCoolEvent: callback }}
component={jest.fn()}
/>
</TestNavigator>
</BaseNavigationContainer>
);
render(element);
expect(callback).toBeCalledTimes(0);
act(() => {
ref.current.navigation.emit({ type: eventName });
});
expect(callback).toBeCalledTimes(1);
});
it('fires listeners when callback is provided for listeners prop', () => {
const eventName = 'someSuperCoolEvent';
const TestNavigator = React.forwardRef((props: any, ref: any): any => {
const { state, navigation } = useNavigationBuilder(MockRouter, props);
React.useImperativeHandle(ref, () => ({ navigation, state }), [
navigation,
state,
]);
return null;
});
const firstCallback = jest.fn();
const secondCallback = jest.fn();
const thirdCallback = jest.fn();
const ref = React.createRef<any>();
const element = (
<BaseNavigationContainer>
<TestNavigator ref={ref}>
<Screen
name="first"
listeners={({ route, navigation }) => ({
someSuperCoolEvent: (e) => firstCallback(e, route, navigation),
})}
component={jest.fn()}
/>
<Screen
name="second"
listeners={({ route, navigation }) => ({
someSuperCoolEvent: (e) => secondCallback(e, route, navigation),
})}
component={jest.fn()}
/>
<Screen
name="third"
listeners={({ route, navigation }) => ({
someSuperCoolEvent: (e) => thirdCallback(e, route, navigation),
})}
component={jest.fn()}
/>
</TestNavigator>
</BaseNavigationContainer>
);
render(element);
expect(firstCallback).toBeCalledTimes(0);
expect(secondCallback).toBeCalledTimes(0);
expect(thirdCallback).toBeCalledTimes(0);
const target =
ref.current.state.routes[ref.current.state.routes.length - 1].key;
act(() => {
ref.current.navigation.emit({
type: eventName,
target,
data: 42,
});
});
expect(firstCallback).toBeCalledTimes(0);
expect(secondCallback).toBeCalledTimes(0);
expect(thirdCallback).toBeCalledTimes(1);
expect(thirdCallback.mock.calls[0][0].type).toBe('someSuperCoolEvent');
expect(thirdCallback.mock.calls[0][0].data).toBe(42);
expect(thirdCallback.mock.calls[0][0].target).toBe(target);
expect(thirdCallback.mock.calls[0][0].defaultPrevented).toBe(undefined);
expect(thirdCallback.mock.calls[0][0].preventDefault).toBe(undefined);
act(() => {
ref.current.navigation.emit({ type: eventName });
});
expect(firstCallback.mock.calls[0][0].target).toBe(undefined);
expect(firstCallback).toBeCalledTimes(1);
expect(secondCallback).toBeCalledTimes(0);
expect(thirdCallback).toBeCalledTimes(1);
});
it('has option to prevent default', () => {
expect.assertions(5);
@@ -419,7 +786,7 @@ it('has option to prevent default', () => {
state,
]);
return state.routes.map(route => descriptors[route.key].render());
return state.routes.map((route) => descriptors[route.key].render());
});
const callback = (e: any) => {
@@ -444,11 +811,11 @@ it('has option to prevent default', () => {
const ref = React.createRef<any>();
const element = (
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator ref={ref}>
<Screen name="first" component={Test} />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
render(element);

View File

@@ -2,7 +2,7 @@ import * as React from 'react';
import { render, act } from 'react-native-testing-library';
import useNavigationBuilder from '../useNavigationBuilder';
import useFocusEffect from '../useFocusEffect';
import NavigationContainer from '../NavigationContainer';
import BaseNavigationContainer from '../BaseNavigationContainer';
import Screen from '../Screen';
import MockRouter from './__fixtures__/MockRouter';
@@ -10,7 +10,7 @@ it('runs focus effect on focus change', () => {
const TestNavigator = (props: any): any => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return state.routes.map(route => descriptors[route.key].render());
return state.routes.map((route) => descriptors[route.key].render());
};
const focusEffect = jest.fn();
@@ -31,13 +31,13 @@ it('runs focus effect on focus change', () => {
const navigation = React.createRef<any>();
const element = (
<NavigationContainer ref={navigation}>
<BaseNavigationContainer ref={navigation}>
<TestNavigator>
<Screen name="first">{() => null}</Screen>
<Screen name="second" component={Test} />
<Screen name="third">{() => null}</Screen>
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
render(element);
@@ -84,12 +84,12 @@ it('runs focus effect on deps change', () => {
};
const App = ({ count }: { count: number }) => (
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator>
<Screen name="first">{() => <Test count={count} />}</Screen>
<Screen name="second">{() => null}</Screen>
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
const root = render(<App count={1} />);
@@ -107,7 +107,7 @@ it('runs focus effect when initial state is given', () => {
const TestNavigator = (props: any): any => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return state.routes.map(route => descriptors[route.key].render());
return state.routes.map((route) => descriptors[route.key].render());
};
const focusEffect = jest.fn();
@@ -135,13 +135,13 @@ it('runs focus effect when initial state is given', () => {
const navigation = React.createRef<any>();
const element = (
<NavigationContainer ref={navigation} initialState={initialState}>
<BaseNavigationContainer ref={navigation} initialState={initialState}>
<TestNavigator>
<Screen name="first">{() => null}</Screen>
<Screen name="second">{() => null}</Screen>
<Screen name="third" component={Test} />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
render(element);
@@ -178,13 +178,13 @@ it('runs focus effect when only focused route is rendered', () => {
const navigation = React.createRef<any>();
const element = (
<NavigationContainer ref={navigation}>
<BaseNavigationContainer ref={navigation}>
<TestNavigator>
<Screen name="first" component={Test} />
<Screen name="second">{() => null}</Screen>
<Screen name="third">{() => null}</Screen>
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
render(element);
@@ -221,12 +221,12 @@ it('runs cleanup when component is unmounted', () => {
const TestB = () => null;
const App = ({ mounted }: { mounted: boolean }) => (
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator>
<Screen name="first" component={mounted ? TestA : TestB} />
<Screen name="second">{() => null}</Screen>
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
const root = render(<App mounted />);

View File

@@ -2,7 +2,7 @@ import * as React from 'react';
import { render, act } from 'react-native-testing-library';
import useNavigationBuilder from '../useNavigationBuilder';
import useIsFocused from '../useIsFocused';
import NavigationContainer from '../NavigationContainer';
import BaseNavigationContainer from '../BaseNavigationContainer';
import Screen from '../Screen';
import MockRouter from './__fixtures__/MockRouter';
@@ -10,7 +10,7 @@ it('renders correct focus state', () => {
const TestNavigator = (props: any): any => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return state.routes.map(route => descriptors[route.key].render());
return state.routes.map((route) => descriptors[route.key].render());
};
const Test = () => {
@@ -24,13 +24,13 @@ it('renders correct focus state', () => {
const navigation = React.createRef<any>();
const root = render(
<NavigationContainer ref={navigation}>
<BaseNavigationContainer ref={navigation}>
<TestNavigator>
<Screen name="first">{() => null}</Screen>
<Screen name="second" component={Test} />
<Screen name="third">{() => null}</Screen>
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
expect(root).toMatchInlineSnapshot(`"unfocused"`);

View File

@@ -2,7 +2,7 @@ import * as React from 'react';
import { render } from 'react-native-testing-library';
import useNavigationBuilder from '../useNavigationBuilder';
import useNavigation from '../useNavigation';
import NavigationContainer from '../NavigationContainer';
import BaseNavigationContainer from '../BaseNavigationContainer';
import Screen from '../Screen';
import MockRouter from './__fixtures__/MockRouter';
@@ -12,7 +12,7 @@ it('gets navigation prop from context', () => {
const TestNavigator = (props: any): any => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return state.routes.map(route => descriptors[route.key].render());
return state.routes.map((route) => descriptors[route.key].render());
};
const Test = () => {
@@ -24,11 +24,11 @@ it('gets navigation prop from context', () => {
};
render(
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator>
<Screen name="foo" component={Test} />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
});
@@ -38,7 +38,7 @@ it("gets navigation's parent from context", () => {
const TestNavigator = (props: any): any => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return state.routes.map(route => descriptors[route.key].render());
return state.routes.map((route) => descriptors[route.key].render());
};
const Test = () => {
@@ -50,7 +50,7 @@ it("gets navigation's parent from context", () => {
};
render(
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator>
<Screen name="foo">
{() => (
@@ -60,7 +60,7 @@ it("gets navigation's parent from context", () => {
)}
</Screen>
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
});
@@ -70,7 +70,7 @@ it("gets navigation's parent's parent from context", () => {
const TestNavigator = (props: any): any => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return state.routes.map(route => descriptors[route.key].render());
return state.routes.map((route) => descriptors[route.key].render());
};
const Test = () => {
@@ -86,7 +86,7 @@ it("gets navigation's parent's parent from context", () => {
};
render(
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator>
<Screen name="foo">
{() => (
@@ -102,7 +102,7 @@ it("gets navigation's parent's parent from context", () => {
)}
</Screen>
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
});
@@ -112,7 +112,7 @@ it('throws if called outside a navigation context', () => {
const Test = () => {
// eslint-disable-next-line react-hooks/rules-of-hooks
expect(() => useNavigation()).toThrow(
"We couldn't find a navigation object. Is your component inside a navigator?"
"Couldn't find a navigation object. Is your component inside a screen in a navigator?"
);
return null;

View File

@@ -1,7 +1,10 @@
import * as React from 'react';
import { render } from 'react-native-testing-library';
import { render, act } from 'react-native-testing-library';
import useEventEmitter from '../useEventEmitter';
import useNavigationCache from '../useNavigationCache';
import useNavigationBuilder from '../useNavigationBuilder';
import BaseNavigationContainer from '../BaseNavigationContainer';
import Screen from '../Screen';
import MockRouter, { MockRouterKey } from './__fixtures__/MockRouter';
beforeEach(() => (MockRouterKey.current = 0));
@@ -40,7 +43,7 @@ it('preserves reference for navigation objects', () => {
});
if (previous.current) {
Object.keys(navigations).forEach(key => {
Object.keys(navigations).forEach((key) => {
expect(navigations[key]).toBe(previous.current[key]);
});
}
@@ -56,3 +59,136 @@ it('preserves reference for navigation objects', () => {
root.update(<Test />);
});
it('returns correct value for isFocused', () => {
const TestNavigator = (props: any): any => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return state.routes.map((route) => descriptors[route.key].render());
};
let navigation: any;
const Test = (props: any) => {
navigation = props.navigation;
return null;
};
render(
<BaseNavigationContainer>
<TestNavigator>
<Screen name="first">{() => null}</Screen>
<Screen name="second" component={Test} />
<Screen name="third">{() => null}</Screen>
</TestNavigator>
</BaseNavigationContainer>
);
expect(navigation.isFocused()).toBe(false);
act(() => navigation.navigate('second'));
expect(navigation.isFocused()).toBe(true);
act(() => navigation.navigate('third'));
expect(navigation.isFocused()).toBe(false);
act(() => navigation.navigate('second'));
expect(navigation.isFocused()).toBe(true);
});
it('returns correct value for isFocused after changing screens', () => {
const TestRouter = (
options: Parameters<typeof MockRouter>[0]
): ReturnType<typeof MockRouter> => {
const router = MockRouter(options);
return {
...router,
getStateForRouteNamesChange(state, { routeNames }) {
const routes = routeNames.map(
(name) =>
state.routes.find((r) => r.name === name) || {
name,
key: name,
}
);
return {
...state,
routeNames,
routes,
index: routes.length - 1,
};
},
};
};
const TestNavigator = (props: any): any => {
const { state, descriptors } = useNavigationBuilder(TestRouter, props);
return state.routes.map((route) => descriptors[route.key].render());
};
let navigation: any;
const Test = (props: any) => {
navigation = props.navigation;
return null;
};
const root = render(
<BaseNavigationContainer>
<TestNavigator>
<Screen name="first">{() => null}</Screen>
<Screen name="second" component={Test} />
<Screen name="third">{() => null}</Screen>
</TestNavigator>
</BaseNavigationContainer>
);
expect(navigation.isFocused()).toBe(false);
root.update(
<BaseNavigationContainer>
<TestNavigator>
<Screen name="first">{() => null}</Screen>
<Screen name="third">{() => null}</Screen>
<Screen name="second" component={Test} />
</TestNavigator>
</BaseNavigationContainer>
);
expect(navigation.isFocused()).toBe(true);
root.update(
<BaseNavigationContainer>
<TestNavigator>
<Screen name="first">{() => null}</Screen>
<Screen name="third">{() => null}</Screen>
<Screen name="fourth">{() => null}</Screen>
<Screen name="second" component={Test} />
</TestNavigator>
</BaseNavigationContainer>
);
expect(navigation.isFocused()).toBe(true);
root.update(
<BaseNavigationContainer>
<TestNavigator>
<Screen name="first">{() => null}</Screen>
<Screen name="third">{() => null}</Screen>
<Screen name="second" component={Test} />
<Screen name="fourth">{() => null}</Screen>
</TestNavigator>
</BaseNavigationContainer>
);
expect(navigation.isFocused()).toBe(false);
});

View File

@@ -1,23 +1,23 @@
import * as React from 'react';
import { render, act } from 'react-native-testing-library';
import { NavigationState } from '@react-navigation/routers';
import useNavigationBuilder from '../useNavigationBuilder';
import useNavigationState from '../useNavigationState';
import NavigationContainer from '../NavigationContainer';
import BaseNavigationContainer from '../BaseNavigationContainer';
import Screen from '../Screen';
import MockRouter from './__fixtures__/MockRouter';
import { NavigationState } from '../types';
it('gets the current navigation state', () => {
const TestNavigator = (props: any): any => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return state.routes.map(route => descriptors[route.key].render());
return state.routes.map((route) => descriptors[route.key].render());
};
const callback = jest.fn();
const Test = () => {
const state = useNavigationState(state => state);
const state = useNavigationState((state) => state);
callback(state);
@@ -27,13 +27,13 @@ it('gets the current navigation state', () => {
const navigation = React.createRef<any>();
const element = (
<NavigationContainer ref={navigation}>
<BaseNavigationContainer ref={navigation}>
<TestNavigator>
<Screen name="first" component={Test} />
<Screen name="second">{() => null}</Screen>
<Screen name="third">{() => null}</Screen>
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
render(element);
@@ -62,13 +62,13 @@ it('gets the current navigation state with selector', () => {
const TestNavigator = (props: any): any => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return state.routes.map(route => descriptors[route.key].render());
return state.routes.map((route) => descriptors[route.key].render());
};
const callback = jest.fn();
const Test = () => {
const index = useNavigationState(state => state.index);
const index = useNavigationState((state) => state.index);
callback(index);
@@ -78,13 +78,13 @@ it('gets the current navigation state with selector', () => {
const navigation = React.createRef<any>();
const element = (
<NavigationContainer ref={navigation}>
<BaseNavigationContainer ref={navigation}>
<TestNavigator>
<Screen name="first" component={Test} />
<Screen name="second">{() => null}</Screen>
<Screen name="third">{() => null}</Screen>
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
render(element);
@@ -112,7 +112,7 @@ it('gets the correct value if selector changes', () => {
const TestNavigator = (props: any): any => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return state.routes.map(route => descriptors[route.key].render());
return state.routes.map((route) => descriptors[route.key].render());
};
const callback = jest.fn();
@@ -133,23 +133,23 @@ it('gets the correct value if selector changes', () => {
const App = ({ selector }: { selector: (state: NavigationState) => any }) => {
return (
<SelectorContext.Provider value={selector}>
<NavigationContainer ref={navigation}>
<BaseNavigationContainer ref={navigation}>
<TestNavigator>
<Screen name="first" component={Test} />
<Screen name="second">{() => null}</Screen>
<Screen name="third">{() => null}</Screen>
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
</SelectorContext.Provider>
);
};
const root = render(<App selector={state => state.index} />);
const root = render(<App selector={(state) => state.index} />);
expect(callback).toBeCalledTimes(1);
expect(callback.mock.calls[0][0]).toBe(0);
root.update(<App selector={state => state.routes[state.index].name} />);
root.update(<App selector={(state) => state.routes[state.index].name} />);
expect(callback).toBeCalledTimes(2);
expect(callback.mock.calls[1][0]).toBe('first');

View File

@@ -1,13 +1,17 @@
import * as React from 'react';
import { render } from 'react-native-testing-library';
import {
Router,
DefaultRouterOptions,
NavigationState,
} from '@react-navigation/routers';
import useNavigationBuilder from '../useNavigationBuilder';
import NavigationContainer from '../NavigationContainer';
import BaseNavigationContainer from '../BaseNavigationContainer';
import Screen from '../Screen';
import MockRouter, {
MockActions,
MockRouterKey,
} from './__fixtures__/MockRouter';
import { Router, DefaultRouterOptions, NavigationState } from '../types';
beforeEach(() => (MockRouterKey.current = 0));
@@ -58,7 +62,7 @@ it("lets parent handle the action if child didn't", () => {
const onStateChange = jest.fn();
render(
<NavigationContainer onStateChange={onStateChange}>
<BaseNavigationContainer onStateChange={onStateChange}>
<ParentNavigator initialRouteName="baz">
<Screen name="foo">{() => null}</Screen>
<Screen name="bar">{() => null}</Screen>
@@ -70,7 +74,7 @@ it("lets parent handle the action if child didn't", () => {
)}
</Screen>
</ParentNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
expect(onStateChange).toBeCalledTimes(1);
@@ -133,7 +137,7 @@ it("lets children handle the action if parent didn't", () => {
return (
<React.Fragment>
{state.routes.map(route => descriptors[route.key].render())}
{state.routes.map((route) => descriptors[route.key].render())}
</React.Fragment>
);
};
@@ -171,7 +175,7 @@ it("lets children handle the action if parent didn't", () => {
};
const element = (
<NavigationContainer
<BaseNavigationContainer
initialState={initialState}
onStateChange={onStateChange}
>
@@ -187,7 +191,7 @@ it("lets children handle the action if parent didn't", () => {
)}
</Screen>
</ParentNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
render(element).update(element);
@@ -266,7 +270,7 @@ it("action doesn't bubble if target is specified", () => {
return (
<React.Fragment>
{state.routes.map(route => descriptors[route.key].render())}
{state.routes.map((route) => descriptors[route.key].render())}
</React.Fragment>
);
};
@@ -284,7 +288,7 @@ it("action doesn't bubble if target is specified", () => {
const onStateChange = jest.fn();
const element = (
<NavigationContainer onStateChange={onStateChange}>
<BaseNavigationContainer onStateChange={onStateChange}>
<ParentNavigator>
<Screen name="foo">{() => null}</Screen>
<Screen name="bar" component={TestScreen} />
@@ -297,7 +301,7 @@ it("action doesn't bubble if target is specified", () => {
)}
</Screen>
</ParentNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
render(element).update(element);
@@ -313,7 +317,7 @@ it('logs error if no navigator handled the action', () => {
return (
<React.Fragment>
{state.routes.map(route => descriptors[route.key].render())}
{state.routes.map((route) => descriptors[route.key].render())}
</React.Fragment>
);
};
@@ -349,7 +353,7 @@ it('logs error if no navigator handled the action', () => {
};
const element = (
<NavigationContainer initialState={initialState}>
<BaseNavigationContainer initialState={initialState}>
<TestNavigator>
<Screen name="foo">{() => null}</Screen>
<Screen name="bar" component={TestScreen} />
@@ -362,7 +366,7 @@ it('logs error if no navigator handled the action', () => {
)}
</Screen>
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
const spy = jest.spyOn(console, 'error').mockImplementation();
@@ -370,7 +374,7 @@ it('logs error if no navigator handled the action', () => {
render(element).update(element);
expect(spy.mock.calls[0][0]).toMatch(
"The action 'UNKNOWN' with payload 'undefined' was not handled by any navigator."
"The action 'UNKNOWN' was not handled by any navigator."
);
spy.mockRestore();

View File

@@ -2,7 +2,7 @@ import * as React from 'react';
import { render } from 'react-native-testing-library';
import useNavigationBuilder from '../useNavigationBuilder';
import useRoute from '../useRoute';
import NavigationContainer from '../NavigationContainer';
import BaseNavigationContainer from '../BaseNavigationContainer';
import Screen from '../Screen';
import MockRouter from './__fixtures__/MockRouter';
import { RouteProp } from '../types';
@@ -13,7 +13,7 @@ it('gets route prop from context', () => {
const TestNavigator = (props: any): any => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return state.routes.map(route => descriptors[route.key].render());
return state.routes.map((route) => descriptors[route.key].render());
};
const Test = () => {
@@ -25,10 +25,10 @@ it('gets route prop from context', () => {
};
render(
<NavigationContainer>
<BaseNavigationContainer>
<TestNavigator>
<Screen name="foo" component={Test} initialParams={{ x: 1 }} />
</TestNavigator>
</NavigationContainer>
</BaseNavigationContainer>
);
});

View File

@@ -1,6 +1,7 @@
import * as React from 'react';
import { ParamListBase, NavigationState } from '@react-navigation/routers';
import Screen from './Screen';
import { ParamListBase, TypedNavigator } from './types';
import { TypedNavigator, EventMapBase } from './types';
/**
* Higher order component to create a `Navigator` and `Screen` pair.
@@ -10,17 +11,21 @@ import { ParamListBase, TypedNavigator } from './types';
* @returns Factory method to create a `Navigator` and `Screen` pair.
*/
export default function createNavigatorFactory<
State extends NavigationState,
ScreenOptions extends object,
EventMap extends EventMapBase,
NavigatorComponent extends React.ComponentType<any>
>(Navigator: NavigatorComponent) {
return function<ParamList extends ParamListBase>(): TypedNavigator<
return function <ParamList extends ParamListBase>(): TypedNavigator<
ParamList,
State,
ScreenOptions,
EventMap,
typeof Navigator
> {
if (arguments[0] !== undefined) {
throw new Error(
"Creating a navigator doesn't take an argument. Maybe you are trying to use React Navigation 4 API with React Navigation 5?"
"Creating a navigator doesn't take an argument. Maybe you are trying to use React Navigation 4 API with React Navigation 5? See https://reactnavigation.org/docs/upgrading-from-4.x for migration guide."
);
}

View File

@@ -1,67 +1,56 @@
import { PartialState, NavigationState } from './types';
import { PartialState, NavigationState } from '@react-navigation/routers';
type NavigateParams = {
screen?: string;
params?: NavigateParams;
initial?: boolean;
};
type Action =
| {
type: 'NAVIGATE';
payload: { name: string; params: NavigateParams };
}
| {
type: 'RESET_ROOT';
payload: PartialState<NavigationState>;
};
type NavigateAction = {
type: 'NAVIGATE';
payload: { name: string; params: NavigateParams };
};
export default function getActionFromState(
state: PartialState<NavigationState>
): Action {
let payload: { name: string; params: NavigateParams } | undefined;
if (state.routes.length === 1) {
// Try to construct payload for a `NAVIGATE` action from the state
// This lets us preserve the navigation state and not lose it
let route = state.routes[0];
payload = {
name: route.name,
params: { ...route.params },
};
let current = state.routes[0].state;
let params = payload.params;
while (current) {
if (current.routes.length === 1) {
route = current.routes[0];
params.screen = route.name;
if (route.state) {
params.params = { ...route.params };
params = params.params;
} else {
params.params = route.params;
}
current = route.state;
} else {
payload = undefined;
break;
}
}
): NavigateAction | undefined {
if (state.routes.length === 0) {
return undefined;
}
if (payload) {
return {
type: 'NAVIGATE',
payload,
};
// Try to construct payload for a `NAVIGATE` action from the state
// This lets us preserve the navigation state and not lose it
let route = state.routes[state.routes.length - 1];
let payload: { name: string; params: NavigateParams } = {
name: route.name,
params: { ...route.params },
};
let current = route.state;
let params = payload.params;
while (current) {
if (current.routes.length === 0) {
return undefined;
}
route = current.routes[current.routes.length - 1];
params.initial = current.routes.length === 1;
params.screen = route.name;
if (route.state) {
params.params = { ...route.params };
params = params.params;
} else {
params.params = route.params;
}
current = route.state;
}
return {
type: 'RESET_ROOT',
payload: state,
type: 'NAVIGATE',
payload,
};
}

View File

@@ -1,5 +1,9 @@
import queryString from 'query-string';
import { NavigationState, PartialState, Route } from './types';
import {
NavigationState,
PartialState,
Route,
} from '@react-navigation/routers';
type State = NavigationState | Omit<PartialState<NavigationState>, 'stale'>;
@@ -9,7 +13,7 @@ type Options = {
[routeName: string]:
| string
| {
path: string;
path?: string;
stringify?: StringifyConfig;
screens?: Options;
};
@@ -60,79 +64,121 @@ export default function getPathFromState(
};
let currentOptions = options;
let pattern = route.name;
// we keep all the route names that appeared during going deeper in config in case the pattern is resolved to undefined
let nestedRouteNames = '';
while (route.name in currentOptions) {
if (typeof currentOptions[route.name] === 'string') {
pattern = currentOptions[route.name] as string;
break;
} else if (typeof currentOptions[route.name] === 'object') {
if (route.state === undefined) {
// if there is no `screens` property, we return pattern
if (
!(currentOptions[route.name] as {
screens: Options;
}).screens
) {
pattern = (currentOptions[route.name] as { path: string }).path;
nestedRouteNames = `${nestedRouteNames}/${route.name}`;
break;
} else {
if (!(currentOptions[route.name] as { screens?: Options }).screens) {
throw Error('Wrong Options object passed');
// if it is the end of state, we return pattern
if (route.state === undefined) {
pattern = (currentOptions[route.name] as { path: string }).path;
nestedRouteNames = `${nestedRouteNames}/${route.name}`;
break;
} else {
index =
typeof route.state.index === 'number' ? route.state.index : 0;
const nextRoute = route.state.routes[index];
const deeperConfig = (currentOptions[route.name] as {
screens: Options;
}).screens;
// if there is config for next route name, we go deeper
if (nextRoute.name in deeperConfig) {
nestedRouteNames = `${nestedRouteNames}/${route.name}`;
route = nextRoute as Route<string> & { state?: State };
currentOptions = deeperConfig;
} else {
// if not, there is no sense in going deeper in config
pattern = (currentOptions[route.name] as { path: string }).path;
nestedRouteNames = `${nestedRouteNames}/${route.name}`;
break;
}
}
currentOptions = (currentOptions[route.name] as { screens: Options })
.screens;
index = typeof route.state.index === 'number' ? route.state.index : 0;
route = route.state.routes[index] as Route<string> & {
state?: State;
};
}
}
}
const config =
currentOptions[route.name] !== undefined
? (currentOptions[route.name] as { stringify?: StringifyConfig })
.stringify
: undefined;
const params = route.params
? // Stringify all of the param values before we use them
Object.entries(route.params).reduce<{
[key: string]: string;
}>((acc, [key, value]) => {
acc[key] = config?.[key] ? config[key](value) : String(value);
return acc;
}, {})
: undefined;
if (currentOptions[route.name] !== undefined) {
path += pattern
.split('/')
.map(p => {
const name = p.replace(/^:/, '');
// If the path has a pattern for a param, put the param in the path
if (params && name in params && p.startsWith(':')) {
const value = params[name];
// Remove the used value from the params object since we'll use the rest for query string
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete params[name];
return encodeURIComponent(value);
}
return encodeURIComponent(p);
})
.join('/');
} else {
path += encodeURIComponent(route.name);
if (pattern === undefined) {
// cut the first `/`
pattern = nestedRouteNames.substring(1);
}
if (route.state) {
path += '/';
} else if (params) {
const query = queryString.stringify(params);
// we don't add empty path strings to path
if (pattern !== '') {
const config =
currentOptions[route.name] !== undefined
? (currentOptions[route.name] as { stringify?: StringifyConfig })
.stringify
: undefined;
if (query) {
path += `?${query}`;
const params = route.params
? // Stringify all of the param values before we use them
Object.entries(route.params).reduce<{
[key: string]: string;
}>((acc, [key, value]) => {
acc[key] = config?.[key] ? config[key](value) : String(value);
return acc;
}, {})
: undefined;
if (currentOptions[route.name] !== undefined) {
path += pattern
.split('/')
.map((p) => {
const name = p.replace(/^:/, '');
// If the path has a pattern for a param, put the param in the path
if (params && name in params && p.startsWith(':')) {
const value = params[name];
// Remove the used value from the params object since we'll use the rest for query string
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete params[name];
return encodeURIComponent(value);
}
return encodeURIComponent(p);
})
.join('/');
} else {
path += encodeURIComponent(route.name);
}
if (route.state) {
path += '/';
} else if (params) {
for (let param in params) {
if (params[param] === 'undefined') {
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete params[param];
}
}
const query = queryString.stringify(params);
if (query) {
path += `?${query}`;
}
}
}
current = route.state;
}
path =
path !== '/' && path.slice(path.length - 1) === '/'
? path.slice(0, -1)
: path;
return path;
}

View File

@@ -1,6 +1,10 @@
import escape from 'escape-string-regexp';
import queryString from 'query-string';
import { NavigationState, PartialState, InitialState } from './types';
import {
NavigationState,
PartialState,
InitialState,
} from '@react-navigation/routers';
type ParseConfig = Record<string, (value: string) => any>;
@@ -8,9 +12,10 @@ type Options = {
[routeName: string]:
| string
| {
path: string;
path?: string;
parse?: ParseConfig;
screens?: Options;
initialRouteName?: string;
};
};
@@ -21,6 +26,11 @@ type RouteConfig = {
parse: ParseConfig | undefined;
};
type InitialRouteConfig = {
initialRouteName: string;
connectedRoutes: string[];
};
type ResultState = PartialState<NavigationState> & {
state?: ResultState;
};
@@ -51,9 +61,12 @@ export default function getStateFromPath(
if (path === '') {
return undefined;
}
let initialRoutes: InitialRouteConfig[] = [];
// Create a normalized configs array which will be easier to use
const configs = ([] as RouteConfig[]).concat(
...Object.keys(options).map(key => createNormalizedConfigs(key, options))
...Object.keys(options).map((key) =>
createNormalizedConfigs(key, options, [], initialRoutes)
)
);
let result: PartialState<NavigationState> | undefined;
@@ -65,8 +78,8 @@ export default function getStateFromPath(
.replace(/\?.*/, ''); // Remove query params which we will handle later
while (remaining) {
let routeNames;
let params;
let routeNames: string[] | undefined;
let params: Record<string, any> | undefined;
// Go through all configs, and see if the next path segment matches our regex
for (const config of configs) {
@@ -78,7 +91,7 @@ export default function getStateFromPath(
const paramPatterns = config.pattern
.split('/')
.filter(p => p.startsWith(':'));
.filter((p) => p.startsWith(':'));
if (paramPatterns.length) {
params = paramPatterns.reduce<Record<string, any>>((acc, p, i) => {
@@ -111,46 +124,43 @@ export default function getStateFromPath(
}
let state: InitialState;
let routeName = routeNames.shift() as string;
let initialRoute = findInitialRoute(routeName, initialRoutes);
if (routeNames.length === 1) {
state = {
routes: [
{ name: routeNames.shift() as string, ...(params && { params }) },
],
};
} else {
state = {
routes: [{ name: routeNames.shift() as string, state: { routes: [] } }],
};
state = createNestedState(
initialRoute,
routeName,
routeNames.length === 0,
params
);
let helper = state.routes[0].state as InitialState;
let routeName;
if (routeNames.length > 0) {
let nestedState = state;
while ((routeName = routeNames.shift())) {
if (routeNames.length === 0) {
helper.routes.push({
name: routeName,
...(params && { params }),
});
} else {
helper.routes[0] = {
name: routeName,
state: {
routes: [],
},
};
helper = helper.routes[0].state as InitialState;
while ((routeName = routeNames.shift() as string)) {
initialRoute = findInitialRoute(routeName, initialRoutes);
nestedState.routes[nestedState.index || 0].state = createNestedState(
initialRoute,
routeName,
routeNames.length === 0,
params
);
if (routeNames.length > 0) {
nestedState = nestedState.routes[nestedState.index || 0]
.state as InitialState;
}
}
}
if (current) {
// The state should be nested inside the deepest route we parsed before
while (current.routes[0].state) {
current = current.routes[0].state;
while (current?.routes[current.index || 0].state) {
current = current.routes[current.index || 0].state;
}
current.routes[0].state = state;
(current as PartialState<NavigationState>).routes[
current?.index || 0
].state = state;
} else {
result = state;
}
@@ -165,18 +175,20 @@ export default function getStateFromPath(
const query = path.split('?')[1];
if (query) {
while (current.routes[0].state) {
while (current?.routes[current.index || 0].state) {
// The query params apply to the deepest route
current = current.routes[0].state;
current = current.routes[current.index || 0].state;
}
const route = current.routes[0];
const route = (current as PartialState<NavigationState>).routes[
current?.index || 0
];
const params = queryString.parse(query);
const parseFunction = findParseConfigForRoute(route.name, configs);
if (parseFunction) {
Object.keys(params).forEach(name => {
Object.keys(params).forEach((name) => {
if (parseFunction[name] && typeof params[name] === 'string') {
params[name] = parseFunction[name](params[name] as string);
}
@@ -192,7 +204,8 @@ export default function getStateFromPath(
function createNormalizedConfigs(
key: string,
routeConfig: Options,
routeNames: string[] = []
routeNames: string[] = [],
initials: InitialRouteConfig[]
): RouteConfig[] {
const configs: RouteConfig[] = [];
@@ -202,18 +215,30 @@ function createNormalizedConfigs(
if (typeof value === 'string') {
// If a string is specified as the value of the key(e.g. Foo: '/path'), use it as the pattern
configs.push(createConfigItem(routeNames, value));
if (value !== '') {
configs.push(createConfigItem(routeNames, value));
}
} else if (typeof value === 'object') {
// if an object is specified as the value (e.g. Foo: { ... }),
// it has `path` property and
// it can have `path` property and
// it could have `screens` prop which has nested configs
configs.push(createConfigItem(routeNames, value.path, value.parse));
if (value.path && value.path !== '') {
configs.push(createConfigItem(routeNames, value.path, value.parse));
}
if (value.screens) {
Object.keys(value.screens).forEach(nestedConfig => {
// property `initialRouteName` without `screens` has no purpose
if (value.initialRouteName) {
initials.push({
initialRouteName: value.initialRouteName,
connectedRoutes: Object.keys(value.screens),
});
}
Object.keys(value.screens).forEach((nestedConfig) => {
const result = createNormalizedConfigs(
nestedConfig,
value.screens as Options,
routeNames
routeNames,
initials
);
configs.push(...result);
});
@@ -254,3 +279,55 @@ function findParseConfigForRoute(
}
return undefined;
}
// tries to find an initial route connected with the one passed
function findInitialRoute(
routeName: string,
initialRoutes: InitialRouteConfig[]
): string | undefined {
for (const config of initialRoutes) {
if (config.connectedRoutes.includes(routeName)) {
return config.initialRouteName === routeName
? undefined
: config.initialRouteName;
}
}
return undefined;
}
// returns nested state object with values depending on whether
// it is the end of state and if there is initialRoute for this level
function createNestedState(
initialRoute: string | undefined,
routeName: string,
isEmpty: boolean,
params?: Record<string, any> | undefined
): InitialState {
if (isEmpty) {
if (initialRoute) {
return {
index: 1,
routes: [
{ name: initialRoute },
{ name: routeName as string, ...(params && { params }) },
],
};
} else {
return {
routes: [{ name: routeName as string, ...(params && { params }) }],
};
}
} else {
if (initialRoute) {
return {
index: 1,
routes: [
{ name: initialRoute },
{ name: routeName as string, state: { routes: [] } },
],
};
} else {
return { routes: [{ name: routeName as string, state: { routes: [] } }] };
}
}
}

View File

@@ -1,9 +1,6 @@
import * as CommonActions from './CommonActions';
export * from '@react-navigation/routers';
export { CommonActions };
export { default as BaseRouter } from './BaseRouter';
export { default as NavigationContainer } from './NavigationContainer';
export { default as BaseNavigationContainer } from './BaseNavigationContainer';
export { default as createNavigatorFactory } from './createNavigatorFactory';
export { default as NavigationContext } from './NavigationContext';

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