Compare commits
79 Commits
@react-nav
...
@react-nav
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ba85db28d4 | ||
|
|
e5063b9339 | ||
|
|
ec35bd5821 | ||
|
|
22e306004a | ||
|
|
8a835b3556 | ||
|
|
d9237e9a0d | ||
|
|
0644ac03aa | ||
|
|
41a5e1a385 | ||
|
|
3d937d1e65 | ||
|
|
343320783f | ||
|
|
5411816188 | ||
|
|
d8bda604ee | ||
|
|
c70635b7d7 | ||
|
|
94d7b28c0b | ||
|
|
b19b3b2725 | ||
|
|
3dcec142f7 | ||
|
|
0c159db4c9 | ||
|
|
2479da98ed | ||
|
|
5197ee2a9c | ||
|
|
0ead2662ec | ||
|
|
5af5c29f07 | ||
|
|
d448cdc11f | ||
|
|
0e8fda3196 | ||
|
|
9198597b7f | ||
|
|
9be904d9c4 | ||
|
|
fa4a959549 | ||
|
|
d0510d0220 | ||
|
|
0b4bf1dcc8 | ||
|
|
5a3f8356b0 | ||
|
|
eeae11033a | ||
|
|
b931ae62df | ||
|
|
ea66b1a3b8 | ||
|
|
4bc0c8f66f | ||
|
|
68016de385 | ||
|
|
e55e866af2 | ||
|
|
50b366e734 | ||
|
|
edf96d839f | ||
|
|
141d397bdf | ||
|
|
0f18b91690 | ||
|
|
6262f7298b | ||
|
|
a6f58677dc | ||
|
|
9bfb295620 | ||
|
|
ecd68afb46 | ||
|
|
5fe140e61b | ||
|
|
944fa35ed4 | ||
|
|
2243b45cc1 | ||
|
|
5e7cfc4ac0 | ||
|
|
5751e7f97a | ||
|
|
179e807a64 | ||
|
|
2f1f0af862 | ||
|
|
9976a888a0 | ||
|
|
16c64e7298 | ||
|
|
f1fe951cf9 | ||
|
|
14250851d1 | ||
|
|
42586462fd | ||
|
|
3dede316cc | ||
|
|
63988e0da8 | ||
|
|
67b2ecfcfc | ||
|
|
68ed8a7259 | ||
|
|
6c2acbb304 | ||
|
|
84d75b37e7 | ||
|
|
65e5147910 | ||
|
|
321fa653ad | ||
|
|
2a76dc4d3c | ||
|
|
0a982ee698 | ||
|
|
1da4a6437f | ||
|
|
f1df4a0808 | ||
|
|
14ae3738cf | ||
|
|
32a2206513 | ||
|
|
38520a97ff | ||
|
|
3bf5ddde2a | ||
|
|
43d2c456be | ||
|
|
fe82276b1f | ||
|
|
1e53821d52 | ||
|
|
23ab45aceb | ||
|
|
d9059b56d8 | ||
|
|
ad4eaff1e9 | ||
|
|
da67e134d2 | ||
|
|
ee381a4ba3 |
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
* text eol=lf
|
||||||
53
.github/workflows/expo-preview.yml
vendored
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
name: Expo Preview
|
||||||
|
on: [pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish:
|
||||||
|
name: Install and publish
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v1
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v1
|
||||||
|
with:
|
||||||
|
node-version: 10.x
|
||||||
|
|
||||||
|
- name: Setup Expo
|
||||||
|
uses: expo/expo-github-action@v5
|
||||||
|
with:
|
||||||
|
expo-version: 3.x
|
||||||
|
expo-username: ${{ secrets.EXPO_CLI_USERNAME }}
|
||||||
|
expo-password: ${{ secrets.EXPO_CLI_PASSWORD }}
|
||||||
|
expo-cache: true
|
||||||
|
|
||||||
|
- name: Get yarn cache
|
||||||
|
id: yarn-cache
|
||||||
|
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||||
|
|
||||||
|
- name: Check yarn cache
|
||||||
|
uses: actions/cache@v1
|
||||||
|
with:
|
||||||
|
path: ${{ steps.yarn-cache.outputs.dir }}
|
||||||
|
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-yarn-
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: yarn
|
||||||
|
|
||||||
|
- name: Publish Expo app
|
||||||
|
working-directory: ./example
|
||||||
|
run: expo publish --release-channel=pr-${{ github.event.number }}
|
||||||
|
|
||||||
|
- name: Get expo link
|
||||||
|
id: expo
|
||||||
|
run: echo "::set-output name=path::@react-navigation/react-navigation-example?release-channel=pr-${{ github.event.number }}"
|
||||||
|
|
||||||
|
- name: Comment on PR
|
||||||
|
uses: unsplash/comment-on-pr@master
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
msg: The Expo app for the example from this branch is ready!<br><br>[expo.io/${{ steps.expo.outputs.path }}](https://expo.io/${{ steps.expo.outputs.path }})<br><br><a href="https://exp.host/${{ steps.expo.outputs.path }}"><img src="https://api.qrserver.com/v1/create-qr-code/?size=400x400&data=exp://exp.host/${{ steps.expo.outputs.path }}" height="200px" width="200px"></a>.
|
||||||
44
.github/workflows/expo.yml
vendored
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
name: Expo Publish
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish:
|
||||||
|
name: Install and publish
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v1
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v1
|
||||||
|
with:
|
||||||
|
node-version: 10.x
|
||||||
|
|
||||||
|
- name: Setup Expo
|
||||||
|
uses: expo/expo-github-action@v5
|
||||||
|
with:
|
||||||
|
expo-version: 3.x
|
||||||
|
expo-username: ${{ secrets.EXPO_CLI_USERNAME }}
|
||||||
|
expo-password: ${{ secrets.EXPO_CLI_PASSWORD }}
|
||||||
|
expo-cache: true
|
||||||
|
|
||||||
|
- name: Get yarn cache
|
||||||
|
id: yarn-cache
|
||||||
|
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||||
|
|
||||||
|
- uses: actions/cache@v1
|
||||||
|
with:
|
||||||
|
path: ${{ steps.yarn-cache.outputs.dir }}
|
||||||
|
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-yarn-
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: yarn
|
||||||
|
|
||||||
|
- name: Publish Expo app
|
||||||
|
working-directory: ./example
|
||||||
|
run: expo publish
|
||||||
27
.github/workflows/rebase.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
name: Automatic Rebase
|
||||||
|
on:
|
||||||
|
issue_comment:
|
||||||
|
types: [created]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
rebase:
|
||||||
|
name: Rebase
|
||||||
|
if: github.event.issue.pull_request != '' && contains(github.event.comment.body, '/rebase')
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@master
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Automatic Rebase
|
||||||
|
uses: cirrus-actions/rebase@1.2
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
# https://github.community/t5/GitHub-Actions/Workflow-is-failing-if-no-job-can-be-ran-due-to-condition/m-p/38186#M3250
|
||||||
|
always_job:
|
||||||
|
name: Always run job
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Always run
|
||||||
|
run: echo "This job is used to prevent the workflow to fail when all other jobs are skipped."
|
||||||
@@ -16,5 +16,7 @@ module.exports = {
|
|||||||
plugins: [
|
plugins: [
|
||||||
'@babel/plugin-proposal-class-properties',
|
'@babel/plugin-proposal-class-properties',
|
||||||
'@babel/plugin-proposal-optional-chaining',
|
'@babel/plugin-proposal-optional-chaining',
|
||||||
|
'@babel/transform-flow-strip-types',
|
||||||
|
'@babel/plugin-proposal-nullish-coalescing-operator',
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,3 +5,5 @@ If you want to run the example from the repo,
|
|||||||
- Clone the repository and run `yarn` in the project root
|
- Clone the repository and run `yarn` in the project root
|
||||||
- Run `yarn example start` to start the packager
|
- Run `yarn example start` to start the packager
|
||||||
- Follow the instructions to open it with the [Expo app](https://expo.io/)
|
- Follow the instructions to open it with the [Expo app](https://expo.io/)
|
||||||
|
|
||||||
|
You can also run the currently published [app on Expo](https://expo.io/@react-navigation/react-navigation-example) on your Android device or iOS simulator or the [web app](https://react-navigation-example.netlify.com/) in your browser.
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
<facet type="android-gradle" name="Android-Gradle">
|
<facet type="android-gradle" name="Android-Gradle">
|
||||||
<configuration>
|
<configuration>
|
||||||
<option name="GRADLE_PROJECT_PATH" value=":app" />
|
<option name="GRADLE_PROJECT_PATH" value=":app" />
|
||||||
<option name="LAST_SUCCESSFUL_SYNC_AGP_VERSION" value="3.3.0" />
|
<option name="LAST_SUCCESSFUL_SYNC_AGP_VERSION" value="3.4.2" />
|
||||||
<option name="LAST_KNOWN_AGP_VERSION" value="3.3.0" />
|
<option name="LAST_KNOWN_AGP_VERSION" value="3.4.2" />
|
||||||
</configuration>
|
</configuration>
|
||||||
</facet>
|
</facet>
|
||||||
<facet type="android" name="Android">
|
<facet type="android" name="Android">
|
||||||
@@ -19,8 +19,8 @@
|
|||||||
<option name="ALLOW_USER_CONFIGURATION" value="false" />
|
<option name="ALLOW_USER_CONFIGURATION" value="false" />
|
||||||
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
|
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
|
||||||
<option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" />
|
<option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" />
|
||||||
<option name="RES_FOLDERS_RELATIVE_PATH" value="file://$MODULE_DIR$/src/main/res;file://$MODULE_DIR$/build/generated/res/rs/debug;file://$MODULE_DIR$/build/generated/res/resValues/debug" />
|
<option name="RES_FOLDERS_RELATIVE_PATH" value="file://$MODULE_DIR$/src/main/res;file://$MODULE_DIR$/build/generated/res/resValues/debug" />
|
||||||
<option name="TEST_RES_FOLDERS_RELATIVE_PATH" value="file://$MODULE_DIR$/build/generated/res/rs/androidTest/debug" />
|
<option name="TEST_RES_FOLDERS_RELATIVE_PATH" value="" />
|
||||||
<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/src/main/assets" />
|
<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/src/main/assets" />
|
||||||
</configuration>
|
</configuration>
|
||||||
</facet>
|
</facet>
|
||||||
@@ -70,6 +70,7 @@
|
|||||||
<sourceFolder url="file://$MODULE_DIR$/src/main/assets" type="java-resource" />
|
<sourceFolder url="file://$MODULE_DIR$/src/main/assets" type="java-resource" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/main/aidl" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/src/main/aidl" isTestSource="false" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/build/generated/rncli/src/main/java" isTestSource="false" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/main/rs" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/src/main/rs" isTestSource="false" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/main/shaders" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/src/main/shaders" isTestSource="false" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/res" type="java-test-resource" />
|
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/res" type="java-test-resource" />
|
||||||
@@ -90,91 +91,103 @@
|
|||||||
</content>
|
</content>
|
||||||
<orderEntry type="jdk" jdkName="Android API 28 Platform" jdkType="Android SDK" />
|
<orderEntry type="jdk" jdkName="Android API 28 Platform" jdkType="Android SDK" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
<orderEntry type="library" name="Gradle: com.android.support:collections:28.0.0@jar" level="project" />
|
|
||||||
<orderEntry type="library" name="Gradle: android.arch.lifecycle:common:1.1.1@jar" level="project" />
|
|
||||||
<orderEntry type="library" name="Gradle: android.arch.core:common:1.1.1@jar" level="project" />
|
|
||||||
<orderEntry type="library" name="Gradle: com.android.support:support-annotations:28.0.0@jar" level="project" />
|
|
||||||
<orderEntry type="library" name="Gradle: com.facebook.infer.annotation:infer-annotation:0.11.2@jar" level="project" />
|
<orderEntry type="library" name="Gradle: com.facebook.infer.annotation:infer-annotation:0.11.2@jar" level="project" />
|
||||||
<orderEntry type="library" name="Gradle: javax.inject:javax.inject:1@jar" level="project" />
|
|
||||||
<orderEntry type="library" name="Gradle: com.google.code.findbugs:jsr305:3.0.2@jar" level="project" />
|
<orderEntry type="library" name="Gradle: com.google.code.findbugs:jsr305:3.0.2@jar" level="project" />
|
||||||
|
<orderEntry type="library" name="Gradle: javax.inject:javax.inject:1@jar" level="project" />
|
||||||
|
<orderEntry type="library" name="Gradle: androidx.collection:collection:1.1.0@jar" level="project" />
|
||||||
|
<orderEntry type="library" name="Gradle: androidx.lifecycle:lifecycle-common:2.1.0@jar" level="project" />
|
||||||
|
<orderEntry type="library" name="Gradle: androidx.arch.core:core-common:2.1.0@jar" level="project" />
|
||||||
|
<orderEntry type="library" name="Gradle: androidx.annotation:annotation:1.1.0@jar" level="project" />
|
||||||
<orderEntry type="library" name="Gradle: com.squareup.okhttp3:okhttp-urlconnection:3.12.1@jar" level="project" />
|
<orderEntry type="library" name="Gradle: com.squareup.okhttp3:okhttp-urlconnection:3.12.1@jar" level="project" />
|
||||||
<orderEntry type="library" name="Gradle: com.squareup.okhttp3:okhttp:3.12.1@jar" level="project" />
|
<orderEntry type="library" name="Gradle: com.squareup.okhttp3:okhttp:3.12.1@jar" level="project" />
|
||||||
<orderEntry type="library" name="Gradle: com.squareup.okio:okio:1.15.0@jar" level="project" />
|
<orderEntry type="library" name="Gradle: com.squareup.okio:okio:1.15.0@jar" level="project" />
|
||||||
|
<orderEntry type="library" name="Gradle: com.github.bumptech.glide:disklrucache:4.9.0@jar" level="project" />
|
||||||
|
<orderEntry type="library" name="Gradle: com.github.bumptech.glide:annotations:4.9.0@jar" level="project" />
|
||||||
|
<orderEntry type="library" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.41@jar" level="project" />
|
||||||
|
<orderEntry type="library" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib:1.3.41@jar" level="project" />
|
||||||
|
<orderEntry type="library" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-common:1.3.41@jar" level="project" />
|
||||||
|
<orderEntry type="library" name="Gradle: org.jetbrains:annotations:13.0@jar" level="project" />
|
||||||
<orderEntry type="library" name="Gradle: com.facebook.device.yearclass:yearclass:2.1.0@jar" level="project" />
|
<orderEntry type="library" name="Gradle: com.facebook.device.yearclass:yearclass:2.1.0@jar" level="project" />
|
||||||
<orderEntry type="library" name="Gradle: commons-codec:commons-codec:1.10@jar" level="project" />
|
<orderEntry type="library" name="Gradle: commons-codec:commons-codec:1.10@jar" level="project" />
|
||||||
<orderEntry type="library" name="Gradle: commons-io:commons-io:1.4@jar" level="project" />
|
<orderEntry type="library" name="Gradle: commons-io:commons-io:1.4@jar" level="project" />
|
||||||
<orderEntry type="library" name="Gradle: com.github.bumptech.glide:disklrucache:4.9.0@jar" level="project" />
|
|
||||||
<orderEntry type="library" name="Gradle: com.github.bumptech.glide:annotations:4.9.0@jar" level="project" />
|
|
||||||
<orderEntry type="library" name="Gradle: com.parse.bolts:bolts-tasks:1.4.0@jar" level="project" />
|
|
||||||
<orderEntry type="library" name="Gradle: com.facebook.react:react-native:0.61.5@aar" level="project" />
|
<orderEntry type="library" name="Gradle: com.facebook.react:react-native:0.61.5@aar" level="project" />
|
||||||
<orderEntry type="library" name="Gradle: com.android.support:appcompat-v7:28.0.0@aar" level="project" />
|
<orderEntry type="library" name="Gradle: androidx.appcompat:appcompat:1.1.0@aar" level="project" />
|
||||||
|
<orderEntry type="library" name="Gradle: androidx.browser:browser:1.0.0@aar" level="project" />
|
||||||
<orderEntry type="library" name="Gradle: com.github.bumptech.glide:glide:4.9.0@aar" level="project" />
|
<orderEntry type="library" name="Gradle: com.github.bumptech.glide:glide:4.9.0@aar" level="project" />
|
||||||
|
<orderEntry type="library" name="Gradle: com.github.bumptech.glide:gifdecoder:4.9.0@aar" level="project" />
|
||||||
<orderEntry type="library" name="Gradle: com.google.android.gms:play-services-location:16.0.0@aar" level="project" />
|
<orderEntry type="library" name="Gradle: com.google.android.gms:play-services-location:16.0.0@aar" level="project" />
|
||||||
<orderEntry type="library" name="Gradle: com.google.android.gms:play-services-base:16.0.1@aar" level="project" />
|
<orderEntry type="library" name="Gradle: com.google.android.gms:play-services-base:16.0.1@aar" level="project" />
|
||||||
<orderEntry type="library" name="Gradle: com.google.android.gms:play-services-places-placereport:16.0.0@aar" level="project" />
|
<orderEntry type="library" name="Gradle: com.google.android.gms:play-services-places-placereport:16.0.0@aar" level="project" />
|
||||||
<orderEntry type="library" name="Gradle: com.google.android.gms:play-services-tasks:16.0.1@aar" level="project" />
|
<orderEntry type="library" name="Gradle: com.google.android.gms:play-services-tasks:16.0.1@aar" level="project" />
|
||||||
<orderEntry type="library" name="Gradle: com.google.android.gms:play-services-basement:16.0.1@aar" level="project" />
|
<orderEntry type="library" name="Gradle: com.google.android.gms:play-services-basement:16.0.1@aar" level="project" />
|
||||||
<orderEntry type="library" name="Gradle: com.android.support:support-v4:28.0.0@aar" level="project" />
|
<orderEntry type="library" name="Gradle: androidx.legacy:legacy-support-v4:1.0.0@aar" level="project" />
|
||||||
<orderEntry type="library" name="Gradle: com.android.support:support-fragment:28.0.0@aar" level="project" />
|
<orderEntry type="library" name="Gradle: androidx.fragment:fragment:1.1.0@aar" level="project" />
|
||||||
<orderEntry type="library" name="Gradle: com.android.support:animated-vector-drawable:28.0.0@aar" level="project" />
|
<orderEntry type="library" name="Gradle: androidx.media:media:1.0.0@aar" level="project" />
|
||||||
<orderEntry type="library" name="Gradle: com.android.support:customtabs:28.0.0@aar" level="project" />
|
<orderEntry type="library" name="Gradle: androidx.legacy:legacy-support-core-ui:1.0.0@aar" level="project" />
|
||||||
<orderEntry type="library" name="Gradle: com.android.support:support-core-ui:28.0.0@aar" level="project" />
|
<orderEntry type="library" name="Gradle: androidx.legacy:legacy-support-core-utils:1.0.0@aar" level="project" />
|
||||||
<orderEntry type="library" name="Gradle: com.android.support:support-core-utils:28.0.0@aar" level="project" />
|
<orderEntry type="library" name="Gradle: androidx.appcompat:appcompat-resources:1.1.0@aar" level="project" />
|
||||||
<orderEntry type="library" name="Gradle: com.android.support:support-vector-drawable:28.0.0@aar" level="project" />
|
<orderEntry type="library" name="Gradle: androidx.drawerlayout:drawerlayout:1.0.0@aar" level="project" />
|
||||||
<orderEntry type="library" name="Gradle: com.android.support:loader:28.0.0@aar" level="project" />
|
<orderEntry type="library" name="Gradle: androidx.viewpager:viewpager:1.0.0@aar" level="project" />
|
||||||
<orderEntry type="library" name="Gradle: com.android.support:support-media-compat:28.0.0@aar" level="project" />
|
<orderEntry type="library" name="Gradle: androidx.loader:loader:1.0.0@aar" level="project" />
|
||||||
<orderEntry type="library" name="Gradle: com.android.support:viewpager:28.0.0@aar" level="project" />
|
<orderEntry type="library" name="Gradle: androidx.activity:activity:1.0.0@aar" level="project" />
|
||||||
<orderEntry type="library" name="Gradle: com.android.support:coordinatorlayout:28.0.0@aar" level="project" />
|
<orderEntry type="library" name="Gradle: androidx.vectordrawable:vectordrawable-animated:1.1.0@aar" level="project" />
|
||||||
<orderEntry type="library" name="Gradle: com.android.support:drawerlayout:28.0.0@aar" level="project" />
|
<orderEntry type="library" name="Gradle: androidx.vectordrawable:vectordrawable:1.1.0@aar" level="project" />
|
||||||
<orderEntry type="library" name="Gradle: com.android.support:slidingpanelayout:28.0.0@aar" level="project" />
|
<orderEntry type="library" name="Gradle: androidx.coordinatorlayout:coordinatorlayout:1.0.0@aar" level="project" />
|
||||||
<orderEntry type="library" name="Gradle: com.android.support:customview:28.0.0@aar" level="project" />
|
<orderEntry type="library" name="Gradle: androidx.slidingpanelayout:slidingpanelayout:1.0.0@aar" level="project" />
|
||||||
<orderEntry type="library" name="Gradle: com.android.support:swiperefreshlayout:28.0.0@aar" level="project" />
|
<orderEntry type="library" name="Gradle: androidx.customview:customview:1.0.0@aar" level="project" />
|
||||||
<orderEntry type="library" name="Gradle: com.android.support:asynclayoutinflater:28.0.0@aar" level="project" />
|
<orderEntry type="library" name="Gradle: androidx.swiperefreshlayout:swiperefreshlayout:1.0.0@aar" level="project" />
|
||||||
<orderEntry type="library" name="Gradle: com.android.support:support-compat:28.0.0@aar" level="project" />
|
<orderEntry type="library" name="Gradle: androidx.asynclayoutinflater:asynclayoutinflater:1.0.0@aar" level="project" />
|
||||||
<orderEntry type="library" name="Gradle: com.android.support:versionedparcelable:28.0.0@aar" level="project" />
|
<orderEntry type="library" name="Gradle: androidx.core:core:1.1.0@aar" level="project" />
|
||||||
<orderEntry type="library" name="Gradle: com.android.support:cursoradapter:28.0.0@aar" level="project" />
|
<orderEntry type="library" name="Gradle: androidx.cursoradapter:cursoradapter:1.0.0@aar" level="project" />
|
||||||
<orderEntry type="library" name="Gradle: android.arch.lifecycle:runtime:1.1.1@aar" level="project" />
|
<orderEntry type="library" name="Gradle: androidx.versionedparcelable:versionedparcelable:1.1.0@aar" level="project" />
|
||||||
<orderEntry type="library" name="Gradle: com.android.support:documentfile:28.0.0@aar" level="project" />
|
<orderEntry type="library" name="Gradle: androidx.interpolator:interpolator:1.0.0@aar" level="project" />
|
||||||
<orderEntry type="library" name="Gradle: com.android.support:localbroadcastmanager:28.0.0@aar" level="project" />
|
<orderEntry type="library" name="Gradle: androidx.lifecycle:lifecycle-viewmodel:2.1.0@aar" level="project" />
|
||||||
<orderEntry type="library" name="Gradle: com.android.support:print:28.0.0@aar" level="project" />
|
<orderEntry type="library" name="Gradle: androidx.lifecycle:lifecycle-runtime:2.1.0@aar" level="project" />
|
||||||
<orderEntry type="library" name="Gradle: android.arch.lifecycle:viewmodel:1.1.1@aar" level="project" />
|
<orderEntry type="library" name="Gradle: androidx.documentfile:documentfile:1.0.0@aar" level="project" />
|
||||||
<orderEntry type="library" name="Gradle: com.github.bumptech.glide:gifdecoder:4.9.0@aar" level="project" />
|
<orderEntry type="library" name="Gradle: androidx.localbroadcastmanager:localbroadcastmanager:1.0.0@aar" level="project" />
|
||||||
<orderEntry type="library" name="Gradle: com.android.support:interpolator:28.0.0@aar" level="project" />
|
<orderEntry type="library" name="Gradle: androidx.print:print:1.0.0@aar" level="project" />
|
||||||
<orderEntry type="library" name="Gradle: android.arch.lifecycle:livedata:1.1.1@aar" level="project" />
|
<orderEntry type="library" name="Gradle: androidx.savedstate:savedstate:1.0.0@aar" level="project" />
|
||||||
<orderEntry type="library" name="Gradle: android.arch.lifecycle:livedata-core:1.1.1@aar" level="project" />
|
<orderEntry type="library" name="Gradle: androidx.lifecycle:lifecycle-livedata:2.0.0@aar" level="project" />
|
||||||
<orderEntry type="library" name="Gradle: android.arch.core:runtime:1.1.1@aar" level="project" />
|
<orderEntry type="library" name="Gradle: androidx.lifecycle:lifecycle-livedata-core:2.0.0@aar" level="project" />
|
||||||
<orderEntry type="library" name="Gradle: com.facebook.fresco:fresco:1.10.0@aar" level="project" />
|
<orderEntry type="library" name="Gradle: androidx.arch.core:core-runtime:2.0.0@aar" level="project" />
|
||||||
<orderEntry type="library" name="Gradle: com.facebook.fresco:imagepipeline-okhttp3:1.10.0@aar" level="project" />
|
<orderEntry type="library" name="Gradle: com.facebook.fresco:fresco:2.0.0@aar" level="project" />
|
||||||
<orderEntry type="library" name="Gradle: com.facebook.fresco:drawee:1.10.0@aar" level="project" />
|
<orderEntry type="library" name="Gradle: com.facebook.fresco:fbcore:2.0.0@aar" level="project" />
|
||||||
<orderEntry type="library" name="Gradle: com.facebook.fresco:imagepipeline:1.10.0@aar" level="project" />
|
<orderEntry type="library" name="Gradle: com.facebook.fresco:drawee:2.0.0@aar" level="project" />
|
||||||
<orderEntry type="library" name="Gradle: com.facebook.fresco:imagepipeline-base:1.10.0@aar" level="project" />
|
<orderEntry type="library" name="Gradle: com.facebook.fresco:imagepipeline:2.0.0@aar" level="project" />
|
||||||
|
<orderEntry type="library" name="Gradle: com.facebook.fresco:imagepipeline-base:2.0.0@aar" level="project" />
|
||||||
<orderEntry type="library" name="Gradle: com.facebook.soloader:soloader:0.6.0@aar" level="project" />
|
<orderEntry type="library" name="Gradle: com.facebook.soloader:soloader:0.6.0@aar" level="project" />
|
||||||
|
<orderEntry type="library" name="Gradle: com.facebook.fresco:nativeimagefilters:2.0.0@aar" level="project" />
|
||||||
|
<orderEntry type="library" name="Gradle: com.facebook.fresco:nativeimagetranscoder:2.0.0@aar" level="project" />
|
||||||
|
<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: io.nlopez.smartlocation:library:3.2.11@aar" level="project" />
|
||||||
<orderEntry type="library" name="Gradle: com.facebook.fresco:fbcore:1.10.0@aar" level="project" />
|
<orderEntry type="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="react-native-safe-area-context" />
|
||||||
<orderEntry type="module" module-name="react-native-screens" />
|
<orderEntry type="module" module-name="react-native-screens" />
|
||||||
<orderEntry type="module" module-name="react-native-reanimated" />
|
|
||||||
<orderEntry type="module" module-name="react-native-gesture-handler" />
|
|
||||||
<orderEntry type="module" module-name="expo-permissions" />
|
|
||||||
<orderEntry type="module" module-name="unimodules-core" />
|
|
||||||
<orderEntry type="module" module-name="unimodules-react-native-adapter" />
|
|
||||||
<orderEntry type="module" module-name="expo-app-loader-provider" />
|
|
||||||
<orderEntry type="module" module-name="expo-constants" />
|
|
||||||
<orderEntry type="module" module-name="expo-file-system" />
|
|
||||||
<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-location" />
|
|
||||||
<orderEntry type="module" module-name="expo-sqlite" />
|
|
||||||
<orderEntry type="module" module-name="expo-web-browser" />
|
|
||||||
<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-image-loader-interface" />
|
|
||||||
<orderEntry type="module" module-name="unimodules-permissions-interface" />
|
|
||||||
<orderEntry type="module" module-name="unimodules-sensors-interface" />
|
|
||||||
<orderEntry type="module" module-name="unimodules-task-manager-interface" />
|
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
||||||
@@ -36,12 +36,19 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
|
android:launchMode="singleTask"
|
||||||
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
|
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
|
||||||
android:windowSoftInputMode="adjustResize">
|
android:windowSoftInputMode="adjustResize">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data android:scheme="rne" />
|
||||||
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
|
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
|
||||||
</application>
|
</application>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
"displayName": "React Navigation Example",
|
"displayName": "React Navigation Example",
|
||||||
"expo": {
|
"expo": {
|
||||||
"name": "@react-navigation/example",
|
"name": "@react-navigation/example",
|
||||||
|
"owner": "react-navigation",
|
||||||
"slug": "react-navigation-example",
|
"slug": "react-navigation-example",
|
||||||
"description": "Demo app to showcase various functionality of React Navigation",
|
"description": "Demo app to showcase various functionality of React Navigation",
|
||||||
"privacy": "public",
|
"privacy": "public",
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
BIN
example/assets/album-art-04.jpg
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
example/assets/album-art-05.jpg
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
example/assets/album-art-06.jpg
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
example/assets/album-art-07.jpg
Normal file
|
After Width: | Height: | Size: 9.7 KiB |
BIN
example/assets/album-art-08.jpg
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
example/assets/album-art-09.jpg
Normal file
|
After Width: | Height: | Size: 110 KiB |
BIN
example/assets/album-art-10.jpg
Normal file
|
After Width: | Height: | Size: 229 KiB |
BIN
example/assets/album-art-11.jpg
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
example/assets/album-art-12.jpg
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
example/assets/album-art-13.jpg
Normal file
|
After Width: | Height: | Size: 81 KiB |
BIN
example/assets/album-art-14.jpg
Normal file
|
After Width: | Height: | Size: 75 KiB |
BIN
example/assets/album-art-15.jpg
Normal file
|
After Width: | Height: | Size: 84 KiB |
BIN
example/assets/album-art-16.jpg
Normal file
|
After Width: | Height: | Size: 116 KiB |
BIN
example/assets/album-art-17.jpg
Normal file
|
After Width: | Height: | Size: 87 KiB |
BIN
example/assets/album-art-18.jpg
Normal file
|
After Width: | Height: | Size: 136 KiB |
BIN
example/assets/album-art-19.jpg
Normal file
|
After Width: | Height: | Size: 93 KiB |
BIN
example/assets/album-art-20.jpg
Normal file
|
After Width: | Height: | Size: 116 KiB |
BIN
example/assets/album-art-21.jpg
Normal file
|
After Width: | Height: | Size: 186 KiB |
BIN
example/assets/album-art-22.jpg
Normal file
|
After Width: | Height: | Size: 78 KiB |
BIN
example/assets/album-art-23.jpg
Normal file
|
After Width: | Height: | Size: 55 KiB |
BIN
example/assets/album-art-24.jpg
Normal file
|
After Width: | Height: | Size: 125 KiB |
|
Before Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 93 KiB |
|
Before Width: | Height: | Size: 900 B After Width: | Height: | Size: 683 B |
@@ -2,6 +2,8 @@ PODS:
|
|||||||
- boost-for-react-native (1.63.0)
|
- boost-for-react-native (1.63.0)
|
||||||
- DoubleConversion (1.1.6)
|
- DoubleConversion (1.1.6)
|
||||||
- EXAppLoaderProvider (8.0.0)
|
- EXAppLoaderProvider (8.0.0)
|
||||||
|
- EXBlur (8.0.0):
|
||||||
|
- UMCore
|
||||||
- EXConstants (8.0.0):
|
- EXConstants (8.0.0):
|
||||||
- UMConstantsInterface
|
- UMConstantsInterface
|
||||||
- UMCore
|
- UMCore
|
||||||
@@ -48,6 +50,8 @@ PODS:
|
|||||||
- glog
|
- glog
|
||||||
- glog (0.3.5)
|
- glog (0.3.5)
|
||||||
- RCTRequired (0.61.5)
|
- RCTRequired (0.61.5)
|
||||||
|
- RCTRestart (0.0.13):
|
||||||
|
- React
|
||||||
- RCTTypeSafety (0.61.5):
|
- RCTTypeSafety (0.61.5):
|
||||||
- FBLazyVector (= 0.61.5)
|
- FBLazyVector (= 0.61.5)
|
||||||
- Folly (= 2018.10.22.00)
|
- Folly (= 2018.10.22.00)
|
||||||
@@ -249,11 +253,11 @@ PODS:
|
|||||||
- ReactCommon/jscallinvoker (= 0.61.5)
|
- ReactCommon/jscallinvoker (= 0.61.5)
|
||||||
- RNCMaskedView (0.1.5):
|
- RNCMaskedView (0.1.5):
|
||||||
- React
|
- React
|
||||||
- RNGestureHandler (1.5.3):
|
- RNGestureHandler (1.5.5):
|
||||||
- React
|
- React
|
||||||
- RNReanimated (1.4.0):
|
- RNReanimated (1.4.0):
|
||||||
- React
|
- React
|
||||||
- RNScreens (2.0.0-alpha.22):
|
- RNScreens (2.0.0-alpha.33):
|
||||||
- React
|
- React
|
||||||
- UMBarCodeScannerInterface (5.0.0)
|
- UMBarCodeScannerInterface (5.0.0)
|
||||||
- UMCameraInterface (5.0.0)
|
- UMCameraInterface (5.0.0)
|
||||||
@@ -275,6 +279,7 @@ PODS:
|
|||||||
DEPENDENCIES:
|
DEPENDENCIES:
|
||||||
- DoubleConversion (from `../../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`)
|
- DoubleConversion (from `../../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`)
|
||||||
- EXAppLoaderProvider (from `../../node_modules/expo-app-loader-provider/ios`)
|
- EXAppLoaderProvider (from `../../node_modules/expo-app-loader-provider/ios`)
|
||||||
|
- EXBlur (from `../../node_modules/expo-blur/ios`)
|
||||||
- EXConstants (from `../../node_modules/expo-constants/ios`)
|
- EXConstants (from `../../node_modules/expo-constants/ios`)
|
||||||
- EXErrorRecovery (from `../../node_modules/expo-error-recovery/ios`)
|
- EXErrorRecovery (from `../../node_modules/expo-error-recovery/ios`)
|
||||||
- EXFileSystem (from `../../node_modules/expo-file-system/ios`)
|
- EXFileSystem (from `../../node_modules/expo-file-system/ios`)
|
||||||
@@ -290,6 +295,7 @@ DEPENDENCIES:
|
|||||||
- Folly (from `../../node_modules/react-native/third-party-podspecs/Folly.podspec`)
|
- Folly (from `../../node_modules/react-native/third-party-podspecs/Folly.podspec`)
|
||||||
- glog (from `../../node_modules/react-native/third-party-podspecs/glog.podspec`)
|
- glog (from `../../node_modules/react-native/third-party-podspecs/glog.podspec`)
|
||||||
- RCTRequired (from `../../node_modules/react-native/Libraries/RCTRequired`)
|
- RCTRequired (from `../../node_modules/react-native/Libraries/RCTRequired`)
|
||||||
|
- RCTRestart (from `../../node_modules/react-native-restart/ios`)
|
||||||
- RCTTypeSafety (from `../../node_modules/react-native/Libraries/TypeSafety`)
|
- RCTTypeSafety (from `../../node_modules/react-native/Libraries/TypeSafety`)
|
||||||
- React (from `../../node_modules/react-native/`)
|
- React (from `../../node_modules/react-native/`)
|
||||||
- React-Core (from `../../node_modules/react-native/`)
|
- React-Core (from `../../node_modules/react-native/`)
|
||||||
@@ -340,6 +346,9 @@ EXTERNAL SOURCES:
|
|||||||
EXAppLoaderProvider:
|
EXAppLoaderProvider:
|
||||||
:path: !ruby/object:Pathname
|
:path: !ruby/object:Pathname
|
||||||
path: "../../node_modules/expo-app-loader-provider/ios"
|
path: "../../node_modules/expo-app-loader-provider/ios"
|
||||||
|
EXBlur:
|
||||||
|
:path: !ruby/object:Pathname
|
||||||
|
path: "../../node_modules/expo-blur/ios"
|
||||||
EXConstants:
|
EXConstants:
|
||||||
:path: !ruby/object:Pathname
|
:path: !ruby/object:Pathname
|
||||||
path: "../../node_modules/expo-constants/ios"
|
path: "../../node_modules/expo-constants/ios"
|
||||||
@@ -380,6 +389,8 @@ EXTERNAL SOURCES:
|
|||||||
:podspec: "../../node_modules/react-native/third-party-podspecs/glog.podspec"
|
:podspec: "../../node_modules/react-native/third-party-podspecs/glog.podspec"
|
||||||
RCTRequired:
|
RCTRequired:
|
||||||
:path: "../../node_modules/react-native/Libraries/RCTRequired"
|
:path: "../../node_modules/react-native/Libraries/RCTRequired"
|
||||||
|
RCTRestart:
|
||||||
|
:path: "../../node_modules/react-native-restart/ios"
|
||||||
RCTTypeSafety:
|
RCTTypeSafety:
|
||||||
:path: "../../node_modules/react-native/Libraries/TypeSafety"
|
:path: "../../node_modules/react-native/Libraries/TypeSafety"
|
||||||
React:
|
React:
|
||||||
@@ -469,6 +480,7 @@ SPEC CHECKSUMS:
|
|||||||
boost-for-react-native: 39c7adb57c4e60d6c5479dd8623128eb5b3f0f2c
|
boost-for-react-native: 39c7adb57c4e60d6c5479dd8623128eb5b3f0f2c
|
||||||
DoubleConversion: 5805e889d232975c086db112ece9ed034df7a0b2
|
DoubleConversion: 5805e889d232975c086db112ece9ed034df7a0b2
|
||||||
EXAppLoaderProvider: ebdb6bc2632c1ccadbe49f5e4104d8d690969c49
|
EXAppLoaderProvider: ebdb6bc2632c1ccadbe49f5e4104d8d690969c49
|
||||||
|
EXBlur: d1604f66f89a9414f5ee65dfb23874437c1bb147
|
||||||
EXConstants: 4051b16c17ef3defa03c541d42811dc92b249146
|
EXConstants: 4051b16c17ef3defa03c541d42811dc92b249146
|
||||||
EXErrorRecovery: d36db99ec6a3808f313f01b0890eb443796dd1c2
|
EXErrorRecovery: d36db99ec6a3808f313f01b0890eb443796dd1c2
|
||||||
EXFileSystem: 6e0d9bb6cc4ea404dbb8f583c1a8a2dcdf4b83b6
|
EXFileSystem: 6e0d9bb6cc4ea404dbb8f583c1a8a2dcdf4b83b6
|
||||||
@@ -484,6 +496,7 @@ SPEC CHECKSUMS:
|
|||||||
Folly: 30e7936e1c45c08d884aa59369ed951a8e68cf51
|
Folly: 30e7936e1c45c08d884aa59369ed951a8e68cf51
|
||||||
glog: 1f3da668190260b06b429bb211bfbee5cd790c28
|
glog: 1f3da668190260b06b429bb211bfbee5cd790c28
|
||||||
RCTRequired: b153add4da6e7dbc44aebf93f3cf4fcae392ddf1
|
RCTRequired: b153add4da6e7dbc44aebf93f3cf4fcae392ddf1
|
||||||
|
RCTRestart: dd19aab87fc1118e05b6b5b91b959105647f56b4
|
||||||
RCTTypeSafety: 9aa1b91d7f9310fc6eadc3cf95126ffe818af320
|
RCTTypeSafety: 9aa1b91d7f9310fc6eadc3cf95126ffe818af320
|
||||||
React: b6a59ef847b2b40bb6e0180a97d0ca716969ac78
|
React: b6a59ef847b2b40bb6e0180a97d0ca716969ac78
|
||||||
React-Core: 688b451f7d616cc1134ac95295b593d1b5158a04
|
React-Core: 688b451f7d616cc1134ac95295b593d1b5158a04
|
||||||
@@ -504,9 +517,9 @@ SPEC CHECKSUMS:
|
|||||||
React-RCTVibration: a49a1f42bf8f5acf1c3e297097517c6b3af377ad
|
React-RCTVibration: a49a1f42bf8f5acf1c3e297097517c6b3af377ad
|
||||||
ReactCommon: 198c7c8d3591f975e5431bec1b0b3b581aa1c5dd
|
ReactCommon: 198c7c8d3591f975e5431bec1b0b3b581aa1c5dd
|
||||||
RNCMaskedView: dd13f9f7b146a9ad82f9b7eb6c9b5548fcf6e990
|
RNCMaskedView: dd13f9f7b146a9ad82f9b7eb6c9b5548fcf6e990
|
||||||
RNGestureHandler: 02905abe54e1f6e59c081a10b4bd689721e17aa6
|
RNGestureHandler: d2270608171c868581b840cfc692f2962c05cd17
|
||||||
RNReanimated: b2ab0b693dddd2339bd2f300e770f6302d2e960c
|
RNReanimated: b2ab0b693dddd2339bd2f300e770f6302d2e960c
|
||||||
RNScreens: 6adf01eb4080f44af6cca551207c6b0505066857
|
RNScreens: 1c7fd499b915c77c21e8e6c327890c5af9b4cf7e
|
||||||
UMBarCodeScannerInterface: 3802c8574ef119c150701d679ab386e2266d6a54
|
UMBarCodeScannerInterface: 3802c8574ef119c150701d679ab386e2266d6a54
|
||||||
UMCameraInterface: 985d301f688ed392f815728f0dd906ca34b7ccb1
|
UMCameraInterface: 985d301f688ed392f815728f0dd906ca34b7ccb1
|
||||||
UMConstantsInterface: bda5f8bd3403ad99e663eb3c4da685d063c5653c
|
UMConstantsInterface: bda5f8bd3403ad99e663eb3c4da685d063c5653c
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
#import <UMCore/UMModuleRegistry.h>
|
#import <UMCore/UMModuleRegistry.h>
|
||||||
#import <UMReactNativeAdapter/UMNativeModulesProxy.h>
|
#import <UMReactNativeAdapter/UMNativeModulesProxy.h>
|
||||||
#import <UMReactNativeAdapter/UMModuleRegistryAdapter.h>
|
#import <UMReactNativeAdapter/UMModuleRegistryAdapter.h>
|
||||||
|
#import <React/RCTLinkingManager.h>
|
||||||
|
|
||||||
@implementation AppDelegate
|
@implementation AppDelegate
|
||||||
|
|
||||||
@@ -52,4 +53,10 @@
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url
|
||||||
|
options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options
|
||||||
|
{
|
||||||
|
return [RCTLinkingManager application:app openURL:url options:options];
|
||||||
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -20,30 +20,24 @@
|
|||||||
<string>1.0</string>
|
<string>1.0</string>
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
|
<key>CFBundleURLTypes</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>Editor</string>
|
||||||
|
<key>CFBundleURLName</key>
|
||||||
|
<string>React Navigation Example</string>
|
||||||
|
<key>CFBundleURLSchemes</key>
|
||||||
|
<array>
|
||||||
|
<string>rne</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1</string>
|
<string>1</string>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>NSLocationWhenInUseUsageDescription</key>
|
|
||||||
<string></string>
|
|
||||||
<key>UILaunchStoryboardName</key>
|
|
||||||
<string>LaunchScreen</string>
|
|
||||||
<key>UIRequiredDeviceCapabilities</key>
|
|
||||||
<array>
|
|
||||||
<string>armv7</string>
|
|
||||||
</array>
|
|
||||||
<key>UISupportedInterfaceOrientations</key>
|
|
||||||
<array>
|
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
|
||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
|
||||||
</array>
|
|
||||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
|
||||||
<false/>
|
|
||||||
<key>NSLocationWhenInUseUsageDescription</key>
|
|
||||||
<string></string>
|
|
||||||
<key>NSAppTransportSecurity</key>
|
<key>NSAppTransportSecurity</key>
|
||||||
<!--See http://ste.vn/2015/06/10/configuring-app-transport-security-ios-9-osx-10-11/ -->
|
|
||||||
<dict>
|
<dict>
|
||||||
<key>NSAllowsArbitraryLoads</key>
|
<key>NSAllowsArbitraryLoads</key>
|
||||||
<true/>
|
<true/>
|
||||||
@@ -78,5 +72,19 @@
|
|||||||
<string>Give React Navigation Example periences permission to access your photos</string>
|
<string>Give React Navigation Example periences permission to access your photos</string>
|
||||||
<key>NSRemindersUsageDescription</key>
|
<key>NSRemindersUsageDescription</key>
|
||||||
<string>Allow React Navigation Example to access your reminders</string>
|
<string>Allow React Navigation Example to access your reminders</string>
|
||||||
|
<key>UILaunchStoryboardName</key>
|
||||||
|
<string>LaunchScreen</string>
|
||||||
|
<key>UIRequiredDeviceCapabilities</key>
|
||||||
|
<array>
|
||||||
|
<string>armv7</string>
|
||||||
|
</array>
|
||||||
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
|
<array>
|
||||||
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
|
</array>
|
||||||
|
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||||
|
<false/>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ const escape = require('escape-string-regexp');
|
|||||||
const blacklist = require('metro-config/src/defaults/blacklist');
|
const blacklist = require('metro-config/src/defaults/blacklist');
|
||||||
|
|
||||||
const root = path.resolve(__dirname, '..');
|
const root = path.resolve(__dirname, '..');
|
||||||
const packages = path.resolve(__dirname, '..', 'packages');
|
const packages = path.resolve(root, 'packages');
|
||||||
|
|
||||||
// Get the list of dependencies for all packages in the monorepo
|
// Get the list of dependencies for all packages in the monorepo
|
||||||
const modules = ['@expo/vector-icons']
|
const modules = ['@expo/vector-icons']
|
||||||
@@ -22,7 +22,7 @@ const modules = ['@expo/vector-icons']
|
|||||||
);
|
);
|
||||||
|
|
||||||
// We need to collect list of deps that this package imports
|
// We need to collect list of deps that this package imports
|
||||||
// Collecting both dependencies are peerDependencies sould do it
|
// Collecting both dependencies are peerDependencies should do it
|
||||||
return Object.keys({
|
return Object.keys({
|
||||||
...pak.dependencies,
|
...pak.dependencies,
|
||||||
...pak.peerDependencies,
|
...pak.peerDependencies,
|
||||||
@@ -59,7 +59,7 @@ module.exports = {
|
|||||||
// When we import a package from the monorepo, metro won't be able to find their deps
|
// When we import a package from the monorepo, metro won't be able to find their deps
|
||||||
// We need to specify them in `extraNodeModules` to tell metro where to find them
|
// We need to specify them in `extraNodeModules` to tell metro where to find them
|
||||||
extraNodeModules: modules.reduce((acc, name) => {
|
extraNodeModules: modules.reduce((acc, name) => {
|
||||||
acc[name] = path.join(__dirname, '..', 'node_modules', name);
|
acc[name] = path.join(root, 'node_modules', name);
|
||||||
return acc;
|
return acc;
|
||||||
}, {}),
|
}, {}),
|
||||||
},
|
},
|
||||||
@@ -67,11 +67,11 @@ module.exports = {
|
|||||||
server: {
|
server: {
|
||||||
enhanceMiddleware: middleware => {
|
enhanceMiddleware: middleware => {
|
||||||
return (req, res, next) => {
|
return (req, res, next) => {
|
||||||
|
// When an asset is imported outside the project root, it has wrong path on Android
|
||||||
|
// This happens for the back button in stack, so we fix the path to correct one
|
||||||
const assets = '/packages/stack/src/views/assets';
|
const assets = '/packages/stack/src/views/assets';
|
||||||
|
|
||||||
if (req.url.startsWith(assets)) {
|
if (req.url.startsWith(assets)) {
|
||||||
// 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
|
|
||||||
req.url = req.url.replace(
|
req.url = req.url.replace(
|
||||||
assets,
|
assets,
|
||||||
'/assets/../packages/stack/src/views/assets'
|
'/assets/../packages/stack/src/views/assets'
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "expo start",
|
"start": "expo start",
|
||||||
"web": "expo start --web",
|
"web": "expo start:web",
|
||||||
"native": "react-native start",
|
"native": "react-native start",
|
||||||
"android": "react-native run-android",
|
"android": "react-native run-android",
|
||||||
"ios": "react-native run-ios"
|
"ios": "react-native run-ios"
|
||||||
@@ -13,18 +13,21 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@expo/vector-icons": "^10.0.0",
|
"@expo/vector-icons": "^10.0.0",
|
||||||
"@react-native-community/masked-view": "0.1.5",
|
"@react-native-community/masked-view": "0.1.5",
|
||||||
|
"@types/react-native-restart": "^0.0.0",
|
||||||
"color": "^3.1.2",
|
"color": "^3.1.2",
|
||||||
"expo": "^36.0.2",
|
"expo": "^36.0.2",
|
||||||
"expo-asset": "~8.0.0",
|
"expo-asset": "~8.0.0",
|
||||||
|
"expo-blur": "^8.0.0",
|
||||||
"react": "~16.9.0",
|
"react": "~16.9.0",
|
||||||
"react-dom": "~16.9.0",
|
"react-dom": "~16.9.0",
|
||||||
"react-native": "~0.61.5",
|
"react-native": "~0.61.5",
|
||||||
"react-native-gesture-handler": "~1.5.3",
|
"react-native-gesture-handler": "^1.5.5",
|
||||||
"react-native-paper": "^3.4.0",
|
"react-native-paper": "^3.5.0",
|
||||||
"react-native-reanimated": "^1.4.0",
|
"react-native-reanimated": "^1.4.0",
|
||||||
|
"react-native-restart": "^0.0.13",
|
||||||
"react-native-safe-area-context": "^0.6.2",
|
"react-native-safe-area-context": "^0.6.2",
|
||||||
"react-native-screens": "^2.0.0-alpha.22",
|
"react-native-screens": "^2.0.0-alpha.33",
|
||||||
"react-native-tab-view": "2.11.0",
|
"react-native-tab-view": "2.13.0",
|
||||||
"react-native-unimodules": "^0.7.0",
|
"react-native-unimodules": "^0.7.0",
|
||||||
"react-native-web": "^0.11.7"
|
"react-native-web": "^0.11.7"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
export default [];
|
export default ['rne://127.0.0.1:19000/--/'];
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { View, TextInput, ActivityIndicator, StyleSheet } from 'react-native';
|
import { View, TextInput, ActivityIndicator, StyleSheet } from 'react-native';
|
||||||
import { Title, Button } from 'react-native-paper';
|
import { Title, Button } from 'react-native-paper';
|
||||||
import { ParamListBase } from '@react-navigation/native';
|
import { useTheme, ParamListBase } from '@react-navigation/native';
|
||||||
import {
|
import {
|
||||||
createStackNavigator,
|
createStackNavigator,
|
||||||
HeaderBackButton,
|
HeaderBackButton,
|
||||||
@@ -40,11 +40,25 @@ const SplashScreen = () => {
|
|||||||
|
|
||||||
const SignInScreen = () => {
|
const SignInScreen = () => {
|
||||||
const { signIn } = React.useContext(AuthContext);
|
const { signIn } = React.useContext(AuthContext);
|
||||||
|
const { colors } = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.content}>
|
<View style={styles.content}>
|
||||||
<TextInput placeholder="Username" style={styles.input} />
|
<TextInput
|
||||||
<TextInput placeholder="Password" secureTextEntry style={styles.input} />
|
placeholder="Username"
|
||||||
|
style={[
|
||||||
|
styles.input,
|
||||||
|
{ backgroundColor: colors.card, color: colors.text },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
placeholder="Password"
|
||||||
|
secureTextEntry
|
||||||
|
style={[
|
||||||
|
styles.input,
|
||||||
|
{ backgroundColor: colors.card, color: colors.text },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
<Button mode="contained" onPress={signIn} style={styles.button}>
|
<Button mode="contained" onPress={signIn} style={styles.button}>
|
||||||
Sign in
|
Sign in
|
||||||
</Button>
|
</Button>
|
||||||
@@ -73,6 +87,7 @@ type Props = {
|
|||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
|
isSignout: boolean;
|
||||||
userToken: undefined | string;
|
userToken: undefined | string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -94,17 +109,20 @@ export default function SimpleStackScreen({ navigation }: Props) {
|
|||||||
case 'SIGN_IN':
|
case 'SIGN_IN':
|
||||||
return {
|
return {
|
||||||
...prevState,
|
...prevState,
|
||||||
|
isSignout: false,
|
||||||
userToken: action.token,
|
userToken: action.token,
|
||||||
};
|
};
|
||||||
case 'SIGN_OUT':
|
case 'SIGN_OUT':
|
||||||
return {
|
return {
|
||||||
...prevState,
|
...prevState,
|
||||||
|
isSignout: true,
|
||||||
userToken: undefined,
|
userToken: undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
|
isSignout: false,
|
||||||
userToken: undefined,
|
userToken: undefined,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -147,7 +165,10 @@ export default function SimpleStackScreen({ navigation }: Props) {
|
|||||||
) : state.userToken === undefined ? (
|
) : state.userToken === undefined ? (
|
||||||
<SimpleStack.Screen
|
<SimpleStack.Screen
|
||||||
name="SignIn"
|
name="SignIn"
|
||||||
options={{ title: 'Sign in' }}
|
options={{
|
||||||
|
title: 'Sign in',
|
||||||
|
animationTypeForReplace: state.isSignout ? 'pop' : 'push',
|
||||||
|
}}
|
||||||
component={SignInScreen}
|
component={SignInScreen}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
@@ -171,7 +192,6 @@ const styles = StyleSheet.create({
|
|||||||
input: {
|
input: {
|
||||||
margin: 8,
|
margin: 8,
|
||||||
padding: 10,
|
padding: 10,
|
||||||
backgroundColor: 'white',
|
|
||||||
borderRadius: 3,
|
borderRadius: 3,
|
||||||
borderWidth: StyleSheet.hairlineWidth,
|
borderWidth: StyleSheet.hairlineWidth,
|
||||||
borderColor: 'rgba(0, 0, 0, 0.08)',
|
borderColor: 'rgba(0, 0, 0, 0.08)',
|
||||||
|
|||||||
56
example/src/Screens/DynamicTabs.tsx
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { View, StyleSheet } from 'react-native';
|
||||||
|
import { Title, Button } from 'react-native-paper';
|
||||||
|
import { Feather } from '@expo/vector-icons';
|
||||||
|
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
|
||||||
|
|
||||||
|
type BottomTabParams = {
|
||||||
|
[key: string]: undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const BottomTabs = createBottomTabNavigator<BottomTabParams>();
|
||||||
|
|
||||||
|
export default function BottomTabsScreen() {
|
||||||
|
const [tabs, setTabs] = React.useState([0, 1]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BottomTabs.Navigator>
|
||||||
|
{tabs.map(i => (
|
||||||
|
<BottomTabs.Screen
|
||||||
|
key={i}
|
||||||
|
name={`tab-${i}`}
|
||||||
|
options={{
|
||||||
|
title: `Tab ${i}`,
|
||||||
|
tabBarIcon: ({ color, size }) => (
|
||||||
|
<Feather name="octagon" color={color} size={size} />
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{() => (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<Title>Tab {i}</Title>
|
||||||
|
<Button onPress={() => setTabs(tabs => [...tabs, tabs.length])}>
|
||||||
|
Add a tab
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onPress={() =>
|
||||||
|
setTabs(tabs => (tabs.length > 1 ? tabs.slice(0, -1) : tabs))
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Remove a tab
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</BottomTabs.Screen>
|
||||||
|
))}
|
||||||
|
</BottomTabs.Navigator>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { View, StyleSheet } from 'react-native';
|
import { View, StyleSheet, ScrollView } from 'react-native';
|
||||||
import { Button } from 'react-native-paper';
|
import { Button } from 'react-native-paper';
|
||||||
import { RouteProp, ParamListBase } from '@react-navigation/native';
|
import { RouteProp, ParamListBase } from '@react-navigation/native';
|
||||||
import {
|
import {
|
||||||
@@ -25,7 +25,7 @@ const ArticleScreen = ({
|
|||||||
route: RouteProp<ModalStackParams, 'Article'>;
|
route: RouteProp<ModalStackParams, 'Article'>;
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<ScrollView>
|
||||||
<View style={styles.buttons}>
|
<View style={styles.buttons}>
|
||||||
<Button
|
<Button
|
||||||
mode="contained"
|
mode="contained"
|
||||||
@@ -42,14 +42,14 @@ const ArticleScreen = ({
|
|||||||
Go back
|
Go back
|
||||||
</Button>
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
<Article author={{ name: route.params.author }} />
|
<Article author={{ name: route.params.author }} scrollEnabled={false} />
|
||||||
</React.Fragment>
|
</ScrollView>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const AlbumsScreen = ({ navigation }: { navigation: ModalStackNavigation }) => {
|
const AlbumsScreen = ({ navigation }: { navigation: ModalStackNavigation }) => {
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<ScrollView>
|
||||||
<View style={styles.buttons}>
|
<View style={styles.buttons}>
|
||||||
<Button
|
<Button
|
||||||
mode="contained"
|
mode="contained"
|
||||||
@@ -66,8 +66,8 @@ const AlbumsScreen = ({ navigation }: { navigation: ModalStackNavigation }) => {
|
|||||||
Go back
|
Go back
|
||||||
</Button>
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
<Albums />
|
<Albums scrollEnabled={false} />
|
||||||
</React.Fragment>
|
</ScrollView>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ const AlbumsScreen = ({
|
|||||||
}: {
|
}: {
|
||||||
navigation: NativeStackNavigation;
|
navigation: NativeStackNavigation;
|
||||||
}) => (
|
}) => (
|
||||||
<React.Fragment>
|
<ScrollView>
|
||||||
<View style={styles.buttons}>
|
<View style={styles.buttons}>
|
||||||
<Button
|
<Button
|
||||||
mode="contained"
|
mode="contained"
|
||||||
@@ -154,8 +154,8 @@ const AlbumsScreen = ({
|
|||||||
Go back
|
Go back
|
||||||
</Button>
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
<Albums />
|
<Albums scrollEnabled={false} />
|
||||||
</React.Fragment>
|
</ScrollView>
|
||||||
);
|
);
|
||||||
|
|
||||||
const NativeStack = createNativeStackNavigator<NativeStackParams>();
|
const NativeStack = createNativeStackNavigator<NativeStackParams>();
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { View, StyleSheet } from 'react-native';
|
import { View, StyleSheet, ScrollView } from 'react-native';
|
||||||
import { Button } from 'react-native-paper';
|
import { Button } from 'react-native-paper';
|
||||||
import { RouteProp, ParamListBase } from '@react-navigation/native';
|
import { RouteProp, ParamListBase } from '@react-navigation/native';
|
||||||
import {
|
import {
|
||||||
@@ -8,9 +8,11 @@ import {
|
|||||||
} from '@react-navigation/stack';
|
} from '@react-navigation/stack';
|
||||||
import Article from '../Shared/Article';
|
import Article from '../Shared/Article';
|
||||||
import Albums from '../Shared/Albums';
|
import Albums from '../Shared/Albums';
|
||||||
|
import NewsFeed from '../Shared/NewsFeed';
|
||||||
|
|
||||||
type SimpleStackParams = {
|
type SimpleStackParams = {
|
||||||
Article: { author: string };
|
Article: { author: string };
|
||||||
|
NewsFeed: undefined;
|
||||||
Album: undefined;
|
Album: undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -24,14 +26,42 @@ const ArticleScreen = ({
|
|||||||
route: RouteProp<SimpleStackParams, 'Article'>;
|
route: RouteProp<SimpleStackParams, 'Article'>;
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<ScrollView>
|
||||||
<View style={styles.buttons}>
|
<View style={styles.buttons}>
|
||||||
<Button
|
<Button
|
||||||
mode="contained"
|
mode="contained"
|
||||||
onPress={() => navigation.push('Album')}
|
onPress={() => navigation.replace('NewsFeed')}
|
||||||
style={styles.button}
|
style={styles.button}
|
||||||
>
|
>
|
||||||
Push album
|
Replace with feed
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
mode="outlined"
|
||||||
|
onPress={() => navigation.pop()}
|
||||||
|
style={styles.button}
|
||||||
|
>
|
||||||
|
Pop screen
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
<Article author={{ name: route.params.author }} scrollEnabled={false} />
|
||||||
|
</ScrollView>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const NewsFeedScreen = ({
|
||||||
|
navigation,
|
||||||
|
}: {
|
||||||
|
navigation: SimpleStackNavigation;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<ScrollView>
|
||||||
|
<View style={styles.buttons}>
|
||||||
|
<Button
|
||||||
|
mode="contained"
|
||||||
|
onPress={() => navigation.navigate('Album')}
|
||||||
|
style={styles.button}
|
||||||
|
>
|
||||||
|
Navigate to album
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
mode="outlined"
|
mode="outlined"
|
||||||
@@ -41,8 +71,8 @@ const ArticleScreen = ({
|
|||||||
Go back
|
Go back
|
||||||
</Button>
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
<Article author={{ name: route.params.author }} />
|
<NewsFeed scrollEnabled={false} />
|
||||||
</React.Fragment>
|
</ScrollView>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -52,7 +82,7 @@ const AlbumsScreen = ({
|
|||||||
navigation: SimpleStackNavigation;
|
navigation: SimpleStackNavigation;
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<ScrollView>
|
||||||
<View style={styles.buttons}>
|
<View style={styles.buttons}>
|
||||||
<Button
|
<Button
|
||||||
mode="contained"
|
mode="contained"
|
||||||
@@ -63,14 +93,14 @@ const AlbumsScreen = ({
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
mode="outlined"
|
mode="outlined"
|
||||||
onPress={() => navigation.goBack()}
|
onPress={() => navigation.pop(2)}
|
||||||
style={styles.button}
|
style={styles.button}
|
||||||
>
|
>
|
||||||
Go back
|
Pop by 2
|
||||||
</Button>
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
<Albums />
|
<Albums scrollEnabled={false} />
|
||||||
</React.Fragment>
|
</ScrollView>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -95,6 +125,11 @@ export default function SimpleStackScreen({ navigation, ...rest }: Props) {
|
|||||||
})}
|
})}
|
||||||
initialParams={{ author: 'Gandalf' }}
|
initialParams={{ author: 'Gandalf' }}
|
||||||
/>
|
/>
|
||||||
|
<SimpleStack.Screen
|
||||||
|
name="NewsFeed"
|
||||||
|
component={NewsFeedScreen}
|
||||||
|
options={{ title: 'Feed' }}
|
||||||
|
/>
|
||||||
<SimpleStack.Screen
|
<SimpleStack.Screen
|
||||||
name="Album"
|
name="Album"
|
||||||
component={AlbumsScreen}
|
component={AlbumsScreen}
|
||||||
|
|||||||
158
example/src/Screens/StackHeaderCustomization.tsx
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { View, StyleSheet, ScrollView, Alert, Platform } from 'react-native';
|
||||||
|
import { Button, Appbar } from 'react-native-paper';
|
||||||
|
import { BlurView } from 'expo-blur';
|
||||||
|
import { MaterialCommunityIcons } from '@expo/vector-icons';
|
||||||
|
import { RouteProp, ParamListBase } from '@react-navigation/native';
|
||||||
|
import {
|
||||||
|
createStackNavigator,
|
||||||
|
StackNavigationProp,
|
||||||
|
HeaderBackground,
|
||||||
|
useHeaderHeight,
|
||||||
|
} from '@react-navigation/stack';
|
||||||
|
import Article from '../Shared/Article';
|
||||||
|
import Albums from '../Shared/Albums';
|
||||||
|
|
||||||
|
type SimpleStackParams = {
|
||||||
|
Article: { author: string };
|
||||||
|
Album: undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
type SimpleStackNavigation = StackNavigationProp<SimpleStackParams>;
|
||||||
|
|
||||||
|
const ArticleScreen = ({
|
||||||
|
navigation,
|
||||||
|
route,
|
||||||
|
}: {
|
||||||
|
navigation: SimpleStackNavigation;
|
||||||
|
route: RouteProp<SimpleStackParams, 'Article'>;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<ScrollView>
|
||||||
|
<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>
|
||||||
|
<Article author={{ name: route.params.author }} scrollEnabled={false} />
|
||||||
|
</ScrollView>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const AlbumsScreen = ({
|
||||||
|
navigation,
|
||||||
|
}: {
|
||||||
|
navigation: SimpleStackNavigation;
|
||||||
|
}) => {
|
||||||
|
const headerHeight = useHeaderHeight();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ScrollView contentContainerStyle={{ paddingTop: headerHeight }}>
|
||||||
|
<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 SimpleStack = createStackNavigator<SimpleStackParams>();
|
||||||
|
|
||||||
|
type Props = Partial<React.ComponentProps<typeof SimpleStack.Navigator>> & {
|
||||||
|
navigation: StackNavigationProp<ParamListBase>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function SimpleStackScreen({ navigation, ...rest }: Props) {
|
||||||
|
navigation.setOptions({
|
||||||
|
headerShown: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SimpleStack.Navigator {...rest}>
|
||||||
|
<SimpleStack.Screen
|
||||||
|
name="Article"
|
||||||
|
component={ArticleScreen}
|
||||||
|
options={({ route }) => ({
|
||||||
|
title: `Article by ${route.params?.author}`,
|
||||||
|
headerTintColor: '#fff',
|
||||||
|
headerStyle: { backgroundColor: '#ff005d' },
|
||||||
|
headerBackTitleVisible: false,
|
||||||
|
headerTitleAlign: 'center',
|
||||||
|
headerBackImage: ({ tintColor }) => (
|
||||||
|
<MaterialCommunityIcons
|
||||||
|
name="arrow-left-circle-outline"
|
||||||
|
color={tintColor}
|
||||||
|
size={24}
|
||||||
|
style={{ marginHorizontal: Platform.OS === 'ios' ? 8 : 0 }}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
headerRight: ({ tintColor }) => (
|
||||||
|
<Appbar.Action
|
||||||
|
color={tintColor}
|
||||||
|
icon="dots-horizontal-circle-outline"
|
||||||
|
onPress={() =>
|
||||||
|
Alert.alert(
|
||||||
|
'Never gonna give you up!',
|
||||||
|
'Never gonna let you down! Never gonna run around and desert you!'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
})}
|
||||||
|
initialParams={{ author: 'Gandalf' }}
|
||||||
|
/>
|
||||||
|
<SimpleStack.Screen
|
||||||
|
name="Album"
|
||||||
|
component={AlbumsScreen}
|
||||||
|
options={{
|
||||||
|
title: 'Album',
|
||||||
|
headerBackTitle: 'Back',
|
||||||
|
headerTransparent: true,
|
||||||
|
headerBackground: () => (
|
||||||
|
<HeaderBackground style={{ backgroundColor: 'transparent' }}>
|
||||||
|
<BlurView
|
||||||
|
tint="light"
|
||||||
|
intensity={75}
|
||||||
|
style={StyleSheet.absoluteFill}
|
||||||
|
/>
|
||||||
|
</HeaderBackground>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</SimpleStack.Navigator>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
buttons: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
padding: 8,
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
margin: 8,
|
||||||
|
},
|
||||||
|
});
|
||||||
154
example/src/Screens/StackTransparent.tsx
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { View, StyleSheet, ScrollView } from 'react-native';
|
||||||
|
import { Button, Paragraph } from 'react-native-paper';
|
||||||
|
import { RouteProp, ParamListBase, useTheme } from '@react-navigation/native';
|
||||||
|
import {
|
||||||
|
createStackNavigator,
|
||||||
|
StackNavigationProp,
|
||||||
|
} from '@react-navigation/stack';
|
||||||
|
import Article from '../Shared/Article';
|
||||||
|
|
||||||
|
type SimpleStackParams = {
|
||||||
|
Article: { author: string };
|
||||||
|
Dialog: undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
type SimpleStackNavigation = StackNavigationProp<SimpleStackParams>;
|
||||||
|
|
||||||
|
const ArticleScreen = ({
|
||||||
|
navigation,
|
||||||
|
route,
|
||||||
|
}: {
|
||||||
|
navigation: SimpleStackNavigation;
|
||||||
|
route: RouteProp<SimpleStackParams, 'Article'>;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<ScrollView>
|
||||||
|
<View style={styles.buttons}>
|
||||||
|
<Button
|
||||||
|
mode="contained"
|
||||||
|
onPress={() => navigation.push('Dialog')}
|
||||||
|
style={styles.button}
|
||||||
|
>
|
||||||
|
Show Dialog
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
mode="outlined"
|
||||||
|
onPress={() => navigation.goBack()}
|
||||||
|
style={styles.button}
|
||||||
|
>
|
||||||
|
Go back
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
<Article author={{ name: route.params.author }} scrollEnabled={false} />
|
||||||
|
</ScrollView>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const DialogScreen = ({
|
||||||
|
navigation,
|
||||||
|
}: {
|
||||||
|
navigation: SimpleStackNavigation;
|
||||||
|
}) => {
|
||||||
|
const { colors } = useTheme();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<View style={[styles.dialog, { backgroundColor: colors.card }]}>
|
||||||
|
<Paragraph>
|
||||||
|
Mise en place is a French term that literally means “put in place.” It
|
||||||
|
also refers to a way cooks in professional kitchens and restaurants
|
||||||
|
set up their work stations—first by gathering all ingredients for a
|
||||||
|
recipes, partially preparing them (like measuring out and chopping),
|
||||||
|
and setting them all near each other. Setting up mise en place before
|
||||||
|
cooking is another top tip for home cooks, as it seriously helps with
|
||||||
|
organization. It’ll pretty much guarantee you never forget to add an
|
||||||
|
ingredient and save you time from running back and forth from the
|
||||||
|
pantry ten times.
|
||||||
|
</Paragraph>
|
||||||
|
<Button style={styles.close} compact onPress={navigation.goBack}>
|
||||||
|
Okay
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const SimpleStack = createStackNavigator<SimpleStackParams>();
|
||||||
|
|
||||||
|
type Props = Partial<React.ComponentProps<typeof SimpleStack.Navigator>> & {
|
||||||
|
navigation: StackNavigationProp<ParamListBase>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function SimpleStackScreen({ navigation, ...rest }: Props) {
|
||||||
|
navigation.setOptions({
|
||||||
|
headerShown: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SimpleStack.Navigator mode="modal" {...rest}>
|
||||||
|
<SimpleStack.Screen
|
||||||
|
name="Article"
|
||||||
|
component={ArticleScreen}
|
||||||
|
initialParams={{ author: 'Gandalf' }}
|
||||||
|
/>
|
||||||
|
<SimpleStack.Screen
|
||||||
|
name="Dialog"
|
||||||
|
component={DialogScreen}
|
||||||
|
options={{
|
||||||
|
headerShown: false,
|
||||||
|
cardStyle: { backgroundColor: 'transparent' },
|
||||||
|
cardOverlayEnabled: true,
|
||||||
|
cardStyleInterpolator: ({ current: { progress } }) => ({
|
||||||
|
cardStyle: {
|
||||||
|
opacity: progress.interpolate({
|
||||||
|
inputRange: [0, 0.5, 0.9, 1],
|
||||||
|
outputRange: [0, 0.25, 0.7, 1],
|
||||||
|
}),
|
||||||
|
transform: [
|
||||||
|
{
|
||||||
|
scale: progress.interpolate({
|
||||||
|
inputRange: [0, 1],
|
||||||
|
outputRange: [0.9, 1],
|
||||||
|
extrapolate: 'clamp',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
overlayStyle: {
|
||||||
|
opacity: progress.interpolate({
|
||||||
|
inputRange: [0, 1],
|
||||||
|
outputRange: [0, 0.5],
|
||||||
|
extrapolate: 'clamp',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</SimpleStack.Navigator>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
buttons: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
padding: 8,
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
margin: 8,
|
||||||
|
},
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
dialog: {
|
||||||
|
padding: 16,
|
||||||
|
width: '90%',
|
||||||
|
maxWidth: 400,
|
||||||
|
borderRadius: 3,
|
||||||
|
},
|
||||||
|
close: {
|
||||||
|
alignSelf: 'flex-end',
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -1,53 +1,90 @@
|
|||||||
/* eslint-disable import/no-commonjs */
|
/* eslint-disable import/no-commonjs */
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { Image, Dimensions, ScrollView, StyleSheet } from 'react-native';
|
import {
|
||||||
|
View,
|
||||||
|
Image,
|
||||||
|
ScrollView,
|
||||||
|
StyleSheet,
|
||||||
|
ScrollViewProps,
|
||||||
|
Dimensions,
|
||||||
|
Platform,
|
||||||
|
} from 'react-native';
|
||||||
import { useScrollToTop } from '@react-navigation/native';
|
import { useScrollToTop } from '@react-navigation/native';
|
||||||
|
|
||||||
const COVERS = [
|
const COVERS = [
|
||||||
require('../../assets/album-art-1.jpg'),
|
require('../../assets/album-art-01.jpg'),
|
||||||
require('../../assets/album-art-2.jpg'),
|
require('../../assets/album-art-02.jpg'),
|
||||||
require('../../assets/album-art-3.jpg'),
|
require('../../assets/album-art-03.jpg'),
|
||||||
require('../../assets/album-art-4.jpg'),
|
require('../../assets/album-art-04.jpg'),
|
||||||
require('../../assets/album-art-5.jpg'),
|
require('../../assets/album-art-05.jpg'),
|
||||||
require('../../assets/album-art-6.jpg'),
|
require('../../assets/album-art-06.jpg'),
|
||||||
require('../../assets/album-art-7.jpg'),
|
require('../../assets/album-art-07.jpg'),
|
||||||
require('../../assets/album-art-8.jpg'),
|
require('../../assets/album-art-08.jpg'),
|
||||||
|
require('../../assets/album-art-09.jpg'),
|
||||||
|
require('../../assets/album-art-10.jpg'),
|
||||||
|
require('../../assets/album-art-11.jpg'),
|
||||||
|
require('../../assets/album-art-12.jpg'),
|
||||||
|
require('../../assets/album-art-13.jpg'),
|
||||||
|
require('../../assets/album-art-14.jpg'),
|
||||||
|
require('../../assets/album-art-15.jpg'),
|
||||||
|
require('../../assets/album-art-16.jpg'),
|
||||||
|
require('../../assets/album-art-17.jpg'),
|
||||||
|
require('../../assets/album-art-18.jpg'),
|
||||||
|
require('../../assets/album-art-19.jpg'),
|
||||||
|
require('../../assets/album-art-20.jpg'),
|
||||||
|
require('../../assets/album-art-21.jpg'),
|
||||||
|
require('../../assets/album-art-22.jpg'),
|
||||||
|
require('../../assets/album-art-23.jpg'),
|
||||||
|
require('../../assets/album-art-24.jpg'),
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function Albums() {
|
export default function Albums(props: Partial<ScrollViewProps>) {
|
||||||
const ref = React.useRef<ScrollView>(null);
|
const ref = React.useRef<ScrollView>(null);
|
||||||
|
|
||||||
useScrollToTop(ref);
|
useScrollToTop(ref);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollView
|
<ScrollView ref={ref} contentContainerStyle={styles.content} {...props}>
|
||||||
ref={ref}
|
|
||||||
style={styles.container}
|
|
||||||
contentContainerStyle={styles.content}
|
|
||||||
>
|
|
||||||
{COVERS.map((source, i) => (
|
{COVERS.map((source, i) => (
|
||||||
// eslint-disable-next-line react/no-array-index-key
|
// eslint-disable-next-line react/no-array-index-key
|
||||||
<Image key={i} source={source} style={styles.cover} />
|
<View key={i} style={styles.item}>
|
||||||
))}
|
<Image source={source} style={styles.photo} />
|
||||||
{COVERS.map((source, i) => (
|
</View>
|
||||||
// eslint-disable-next-line react/no-array-index-key
|
|
||||||
<Image key={i + 'F'} source={source} style={styles.cover} />
|
|
||||||
))}
|
))}
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
|
||||||
backgroundColor: '#000',
|
|
||||||
},
|
|
||||||
content: {
|
content: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
flexWrap: 'wrap',
|
flexWrap: 'wrap',
|
||||||
},
|
},
|
||||||
cover: {
|
...Platform.select({
|
||||||
width: '50%',
|
web: {
|
||||||
|
content: {
|
||||||
|
display: 'grid' as 'none',
|
||||||
|
gridTemplateColumns: 'repeat(auto-fill, minmax(150px, 1fr))',
|
||||||
|
},
|
||||||
|
item: {
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: {
|
||||||
|
content: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
},
|
||||||
|
item: {
|
||||||
height: Dimensions.get('window').width / 2,
|
height: Dimensions.get('window').width / 2,
|
||||||
|
width: '50%',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
photo: {
|
||||||
|
flex: 1,
|
||||||
|
resizeMode: 'cover',
|
||||||
|
paddingTop: '100%',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,8 +1,15 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { View, Text, Image, ScrollView, StyleSheet } from 'react-native';
|
import {
|
||||||
|
View,
|
||||||
|
Text,
|
||||||
|
Image,
|
||||||
|
ScrollView,
|
||||||
|
StyleSheet,
|
||||||
|
ScrollViewProps,
|
||||||
|
} from 'react-native';
|
||||||
import { useScrollToTop, useTheme } from '@react-navigation/native';
|
import { useScrollToTop, useTheme } from '@react-navigation/native';
|
||||||
|
|
||||||
type Props = {
|
type Props = Partial<ScrollViewProps> & {
|
||||||
date?: string;
|
date?: string;
|
||||||
author?: {
|
author?: {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -14,6 +21,7 @@ export default function Article({
|
|||||||
author = {
|
author = {
|
||||||
name: 'Knowledge Bot',
|
name: 'Knowledge Bot',
|
||||||
},
|
},
|
||||||
|
...rest
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const ref = React.useRef<ScrollView>(null);
|
const ref = React.useRef<ScrollView>(null);
|
||||||
|
|
||||||
@@ -26,6 +34,7 @@ export default function Article({
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
style={{ backgroundColor: colors.card }}
|
style={{ backgroundColor: colors.card }}
|
||||||
contentContainerStyle={styles.content}
|
contentContainerStyle={styles.content}
|
||||||
|
{...rest}
|
||||||
>
|
>
|
||||||
<View style={styles.author}>
|
<View style={styles.author}>
|
||||||
<Image
|
<Image
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
TextInput,
|
TextInput,
|
||||||
ScrollView,
|
ScrollView,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
|
ScrollViewProps,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { useScrollToTop, useTheme } from '@react-navigation/native';
|
import { useScrollToTop, useTheme } from '@react-navigation/native';
|
||||||
import Color from 'color';
|
import Color from 'color';
|
||||||
@@ -17,7 +18,7 @@ const MESSAGES = [
|
|||||||
'make me a sandwich',
|
'make me a sandwich',
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function Chat() {
|
export default function Chat(props: Partial<ScrollViewProps>) {
|
||||||
const ref = React.useRef<ScrollView>(null);
|
const ref = React.useRef<ScrollView>(null);
|
||||||
|
|
||||||
useScrollToTop(ref);
|
useScrollToTop(ref);
|
||||||
@@ -29,6 +30,7 @@ export default function Chat() {
|
|||||||
<ScrollView
|
<ScrollView
|
||||||
style={styles.inverted}
|
style={styles.inverted}
|
||||||
contentContainerStyle={styles.content}
|
contentContainerStyle={styles.content}
|
||||||
|
{...props}
|
||||||
>
|
>
|
||||||
{MESSAGES.map((text, i) => {
|
{MESSAGES.map((text, i) => {
|
||||||
const odd = i % 2;
|
const odd = i % 2;
|
||||||
|
|||||||
146
example/src/Shared/NewsFeed.tsx
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import {
|
||||||
|
View,
|
||||||
|
TextInput,
|
||||||
|
Image,
|
||||||
|
ScrollView,
|
||||||
|
StyleSheet,
|
||||||
|
ScrollViewProps,
|
||||||
|
} from 'react-native';
|
||||||
|
import { useScrollToTop, useTheme } from '@react-navigation/native';
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
Text,
|
||||||
|
Avatar,
|
||||||
|
Subheading,
|
||||||
|
IconButton,
|
||||||
|
Divider,
|
||||||
|
} from 'react-native-paper';
|
||||||
|
import Color from 'color';
|
||||||
|
|
||||||
|
type Props = Partial<ScrollViewProps>;
|
||||||
|
|
||||||
|
const Author = () => {
|
||||||
|
return (
|
||||||
|
<View style={[styles.row, styles.attribution]}>
|
||||||
|
<Avatar.Image source={require('../../assets/avatar-1.png')} size={32} />
|
||||||
|
<Subheading style={styles.author}>Joke bot</Subheading>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Footer = () => {
|
||||||
|
return (
|
||||||
|
<View style={styles.row}>
|
||||||
|
<IconButton style={styles.icon} size={16} icon="heart-outline" />
|
||||||
|
<IconButton style={styles.icon} size={16} icon="comment-outline" />
|
||||||
|
<IconButton style={styles.icon} size={16} icon="share-outline" />
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function NewsFeed(props: Props) {
|
||||||
|
const ref = React.useRef<ScrollView>(null);
|
||||||
|
|
||||||
|
useScrollToTop(ref);
|
||||||
|
|
||||||
|
const { colors } = useTheme();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ScrollView ref={ref} {...props}>
|
||||||
|
<Card style={styles.card}>
|
||||||
|
<TextInput
|
||||||
|
placeholder="What's on your mind?"
|
||||||
|
placeholderTextColor={Color(colors.text)
|
||||||
|
.alpha(0.5)
|
||||||
|
.rgb()
|
||||||
|
.string()}
|
||||||
|
style={styles.input}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
<Card style={styles.card}>
|
||||||
|
<Author />
|
||||||
|
<Card.Content style={styles.content}>
|
||||||
|
<Text>
|
||||||
|
If you aren't impressed with the picture of the first Black
|
||||||
|
Hole, you clearly don't understand the gravity of the
|
||||||
|
situation.
|
||||||
|
</Text>
|
||||||
|
</Card.Content>
|
||||||
|
<Divider />
|
||||||
|
<Footer />
|
||||||
|
</Card>
|
||||||
|
<Card style={styles.card}>
|
||||||
|
<Author />
|
||||||
|
<Card.Content style={styles.content}>
|
||||||
|
<Text>
|
||||||
|
I went to the zoo and I saw a baguette in a cage. I asked the
|
||||||
|
zookeeper about it and he said it was bread in captivity.
|
||||||
|
</Text>
|
||||||
|
</Card.Content>
|
||||||
|
<Image source={require('../../assets/book.jpg')} style={styles.cover} />
|
||||||
|
<Footer />
|
||||||
|
</Card>
|
||||||
|
<Card style={styles.card}>
|
||||||
|
<Author />
|
||||||
|
<Card.Content style={styles.content}>
|
||||||
|
<Text>Why didn't 4 ask 5 out? Because he was 2².</Text>
|
||||||
|
</Card.Content>
|
||||||
|
<Divider />
|
||||||
|
<Footer />
|
||||||
|
</Card>
|
||||||
|
<Card style={styles.card}>
|
||||||
|
<Author />
|
||||||
|
<Card.Content style={styles.content}>
|
||||||
|
<Text>
|
||||||
|
What did Master Yoda say when he first saw himself in 4k? HDMI.
|
||||||
|
</Text>
|
||||||
|
</Card.Content>
|
||||||
|
<Divider />
|
||||||
|
<Footer />
|
||||||
|
</Card>
|
||||||
|
<Card style={styles.card}>
|
||||||
|
<Author />
|
||||||
|
<Card.Content style={styles.content}>
|
||||||
|
<Text>
|
||||||
|
Someone broke into my house and stole 20% of my couch. Ouch!
|
||||||
|
</Text>
|
||||||
|
</Card.Content>
|
||||||
|
<Divider />
|
||||||
|
<Footer />
|
||||||
|
</Card>
|
||||||
|
</ScrollView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
input: {
|
||||||
|
padding: 16,
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
margin: 0,
|
||||||
|
},
|
||||||
|
card: {
|
||||||
|
marginVertical: 8,
|
||||||
|
borderRadius: 0,
|
||||||
|
},
|
||||||
|
cover: {
|
||||||
|
height: 160,
|
||||||
|
borderRadius: 0,
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
marginBottom: 12,
|
||||||
|
},
|
||||||
|
attribution: {
|
||||||
|
margin: 12,
|
||||||
|
},
|
||||||
|
author: {
|
||||||
|
marginHorizontal: 8,
|
||||||
|
},
|
||||||
|
row: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
26
example/src/Shared/SettingsItem.tsx
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { View } from 'react-native';
|
||||||
|
import { Subheading, Switch } from 'react-native-paper';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
label: string;
|
||||||
|
value: boolean;
|
||||||
|
onValueChange: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function SettingsItem({ label, value, onValueChange }: Props) {
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
paddingHorizontal: 16,
|
||||||
|
paddingVertical: 12,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Subheading>{label}</Subheading>
|
||||||
|
<Switch value={value} onValueChange={onValueChange} />
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,21 +1,20 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import {
|
import {
|
||||||
View,
|
|
||||||
ScrollView,
|
ScrollView,
|
||||||
AsyncStorage,
|
AsyncStorage,
|
||||||
YellowBox,
|
YellowBox,
|
||||||
Platform,
|
Platform,
|
||||||
StatusBar,
|
StatusBar,
|
||||||
|
I18nManager,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
|
import RNRestart from 'react-native-restart';
|
||||||
import { MaterialIcons } from '@expo/vector-icons';
|
import { MaterialIcons } from '@expo/vector-icons';
|
||||||
import {
|
import {
|
||||||
Provider as PaperProvider,
|
Provider as PaperProvider,
|
||||||
DefaultTheme as PaperLightTheme,
|
DefaultTheme as PaperLightTheme,
|
||||||
DarkTheme as PaperDarkTheme,
|
DarkTheme as PaperDarkTheme,
|
||||||
Subheading,
|
|
||||||
Appbar,
|
Appbar,
|
||||||
List,
|
List,
|
||||||
Switch,
|
|
||||||
Divider,
|
Divider,
|
||||||
} from 'react-native-paper';
|
} from 'react-native-paper';
|
||||||
import { Asset } from 'expo-asset';
|
import { Asset } from 'expo-asset';
|
||||||
@@ -42,11 +41,16 @@ import LinkingPrefixes from './LinkingPrefixes';
|
|||||||
import SimpleStack from './Screens/SimpleStack';
|
import SimpleStack from './Screens/SimpleStack';
|
||||||
import NativeStack from './Screens/NativeStack';
|
import NativeStack from './Screens/NativeStack';
|
||||||
import ModalPresentationStack from './Screens/ModalPresentationStack';
|
import ModalPresentationStack from './Screens/ModalPresentationStack';
|
||||||
|
import StackTransparent from './Screens/StackTransparent';
|
||||||
|
import StackHeaderCustomization from './Screens/StackHeaderCustomization';
|
||||||
import BottomTabs from './Screens/BottomTabs';
|
import BottomTabs from './Screens/BottomTabs';
|
||||||
import MaterialTopTabsScreen from './Screens/MaterialTopTabs';
|
import MaterialTopTabsScreen from './Screens/MaterialTopTabs';
|
||||||
import MaterialBottomTabs from './Screens/MaterialBottomTabs';
|
import MaterialBottomTabs from './Screens/MaterialBottomTabs';
|
||||||
|
import DynamicTabs from './Screens/DynamicTabs';
|
||||||
import AuthFlow from './Screens/AuthFlow';
|
import AuthFlow from './Screens/AuthFlow';
|
||||||
import CompatAPI from './Screens/CompatAPI';
|
import CompatAPI from './Screens/CompatAPI';
|
||||||
|
import SettingsItem from './Shared/SettingsItem';
|
||||||
|
import { Updates } from 'expo';
|
||||||
|
|
||||||
YellowBox.ignoreWarnings(['Require cycle:', 'Warning: Async Storage']);
|
YellowBox.ignoreWarnings(['Require cycle:', 'Warning: Async Storage']);
|
||||||
|
|
||||||
@@ -68,6 +72,14 @@ const SCREENS = {
|
|||||||
title: 'Modal Presentation Stack',
|
title: 'Modal Presentation Stack',
|
||||||
component: ModalPresentationStack,
|
component: ModalPresentationStack,
|
||||||
},
|
},
|
||||||
|
StackTransparent: {
|
||||||
|
title: 'Transparent Stack',
|
||||||
|
component: StackTransparent,
|
||||||
|
},
|
||||||
|
StackHeaderCustomization: {
|
||||||
|
title: 'Header Customization in Stack',
|
||||||
|
component: StackHeaderCustomization,
|
||||||
|
},
|
||||||
BottomTabs: { title: 'Bottom Tabs', component: BottomTabs },
|
BottomTabs: { title: 'Bottom Tabs', component: BottomTabs },
|
||||||
MaterialTopTabs: {
|
MaterialTopTabs: {
|
||||||
title: 'Material Top Tabs',
|
title: 'Material Top Tabs',
|
||||||
@@ -77,6 +89,10 @@ const SCREENS = {
|
|||||||
title: 'Material Bottom Tabs',
|
title: 'Material Bottom Tabs',
|
||||||
component: MaterialBottomTabs,
|
component: MaterialBottomTabs,
|
||||||
},
|
},
|
||||||
|
DynamicTabs: {
|
||||||
|
title: 'Dynamic Tabs',
|
||||||
|
component: DynamicTabs,
|
||||||
|
},
|
||||||
AuthFlow: {
|
AuthFlow: {
|
||||||
title: 'Auth Flow',
|
title: 'Auth Flow',
|
||||||
component: AuthFlow,
|
component: AuthFlow,
|
||||||
@@ -101,11 +117,15 @@ export default function App() {
|
|||||||
// To test deep linking on, run the following in the Terminal:
|
// 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"
|
// Android: adb shell am start -a android.intent.action.VIEW -d "exp://127.0.0.1:19000/--/simple-stack"
|
||||||
// iOS: xcrun simctl openurl booted exp://127.0.0.1:19000/--/simple-stack
|
// iOS: xcrun simctl openurl booted exp://127.0.0.1:19000/--/simple-stack
|
||||||
|
// Android (bare): adb shell am start -a android.intent.action.VIEW -d "rne://127.0.0.1:19000/--/simple-stack"
|
||||||
|
// iOS (bare): xcrun simctl openurl booted rne://127.0.0.1:19000/--/simple-stack
|
||||||
// The first segment of the link is the the scheme + host (returned by `Linking.makeUrl`)
|
// The first segment of the link is the the scheme + host (returned by `Linking.makeUrl`)
|
||||||
const { getInitialState } = useLinking(containerRef, {
|
const { getInitialState } = useLinking(containerRef, {
|
||||||
prefixes: LinkingPrefixes,
|
prefixes: LinkingPrefixes,
|
||||||
config: {
|
config: {
|
||||||
Root: Object.keys(SCREENS).reduce<{ [key: string]: string }>(
|
Root: {
|
||||||
|
path: 'root',
|
||||||
|
screens: Object.keys(SCREENS).reduce<{ [key: string]: string }>(
|
||||||
(acc, name) => {
|
(acc, name) => {
|
||||||
// Convert screen names such as SimpleStack to kebab case (simple-stack)
|
// Convert screen names such as SimpleStack to kebab case (simple-stack)
|
||||||
acc[name] = name
|
acc[name] = name
|
||||||
@@ -118,6 +138,7 @@ export default function App() {
|
|||||||
{}
|
{}
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const [theme, setTheme] = React.useState(DefaultTheme);
|
const [theme, setTheme] = React.useState(DefaultTheme);
|
||||||
@@ -132,7 +153,7 @@ export default function App() {
|
|||||||
try {
|
try {
|
||||||
let state = await getInitialState();
|
let state = await getInitialState();
|
||||||
|
|
||||||
if (state === undefined) {
|
if (Platform.OS !== 'web' && state === undefined) {
|
||||||
const savedState = await AsyncStorage.getItem(
|
const savedState = await AsyncStorage.getItem(
|
||||||
NAVIGATION_PERSISTENCE_KEY
|
NAVIGATION_PERSISTENCE_KEY
|
||||||
);
|
);
|
||||||
@@ -233,16 +254,22 @@ export default function App() {
|
|||||||
<ScrollView
|
<ScrollView
|
||||||
style={{ backgroundColor: theme.colors.background }}
|
style={{ backgroundColor: theme.colors.background }}
|
||||||
>
|
>
|
||||||
<View
|
<SettingsItem
|
||||||
style={{
|
label="Right to left"
|
||||||
flexDirection: 'row',
|
value={I18nManager.isRTL}
|
||||||
alignItems: 'center',
|
onValueChange={() => {
|
||||||
justifyContent: 'space-between',
|
I18nManager.forceRTL(!I18nManager.isRTL);
|
||||||
padding: 16,
|
// @ts-ignore
|
||||||
|
if (global.Expo) {
|
||||||
|
Updates.reloadFromCache();
|
||||||
|
} else {
|
||||||
|
RNRestart.Restart();
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
<Subheading>Dark theme</Subheading>
|
<Divider />
|
||||||
<Switch
|
<SettingsItem
|
||||||
|
label="Dark theme"
|
||||||
value={theme.dark}
|
value={theme.dark}
|
||||||
onValueChange={() => {
|
onValueChange={() => {
|
||||||
AsyncStorage.setItem(
|
AsyncStorage.setItem(
|
||||||
@@ -253,14 +280,13 @@ export default function App() {
|
|||||||
setTheme(t => (t.dark ? DefaultTheme : DarkTheme));
|
setTheme(t => (t.dark ? DefaultTheme : DarkTheme));
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</View>
|
|
||||||
<Divider />
|
<Divider />
|
||||||
{(Object.keys(SCREENS) as (keyof typeof SCREENS)[]).map(
|
{(Object.keys(SCREENS) as (keyof typeof SCREENS)[]).map(
|
||||||
name => (
|
name => (
|
||||||
<List.Item
|
<List.Item
|
||||||
key={name}
|
key={name}
|
||||||
title={SCREENS[name].title}
|
title={SCREENS[name].title}
|
||||||
onPress={() => navigation.push(name)}
|
onPress={() => navigation.navigate(name)}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ const packages = path.resolve(__dirname, '..', 'packages');
|
|||||||
module.exports = async function(env, argv) {
|
module.exports = async function(env, argv) {
|
||||||
const config = await createExpoWebpackConfigAsync(env, argv);
|
const config = await createExpoWebpackConfigAsync(env, argv);
|
||||||
|
|
||||||
|
config.context = path.resolve(__dirname, '..');
|
||||||
|
|
||||||
config.module.rules.push({
|
config.module.rules.push({
|
||||||
test: /\.(js|ts|tsx)$/,
|
test: /\.(js|ts|tsx)$/,
|
||||||
include: /(packages|example)\/.+/,
|
include: /(packages|example)\/.+/,
|
||||||
|
|||||||
@@ -64,7 +64,8 @@
|
|||||||
],
|
],
|
||||||
"moduleNameMapper": {
|
"moduleNameMapper": {
|
||||||
"@react-navigation/([^/]+)": "<rootDir>/packages/$1/src"
|
"@react-navigation/([^/]+)": "<rootDir>/packages/$1/src"
|
||||||
}
|
},
|
||||||
|
"preset": "react-native"
|
||||||
},
|
},
|
||||||
"prettier": {
|
"prettier": {
|
||||||
"tabWidth": 2,
|
"tabWidth": 2,
|
||||||
|
|||||||
@@ -3,6 +3,63 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [5.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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [5.0.0-alpha.42](https://github.com/react-navigation/navigation-ex/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.0.0-alpha.39...@react-navigation/bottom-tabs@5.0.0-alpha.42) (2020-02-02)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* add licenses ([0c159db](https://github.com/react-navigation/navigation-ex/tree/master/packages/bottom-tabs/commit/0c159db4c9bc85e83b5cfe6819ab2562669a4d8f))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [5.0.0-alpha.40](https://github.com/react-navigation/navigation-ex/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.0.0-alpha.39...@react-navigation/bottom-tabs@5.0.0-alpha.40) (2020-02-02)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* add licenses ([0c159db](https://github.com/react-navigation/navigation-ex/tree/master/packages/bottom-tabs/commit/0c159db4c9bc85e83b5cfe6819ab2562669a4d8f))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [5.0.0-alpha.39](https://github.com/react-navigation/navigation-ex/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.0.0-alpha.38...@react-navigation/bottom-tabs@5.0.0-alpha.39) (2020-01-24)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* use layout instead of dimensions for determining tab bar layout ([f1fe951](https://github.com/react-navigation/navigation-ex/tree/master/packages/bottom-tabs/commit/f1fe951cf9d602e1b6d4932e3c6c77bbeaaec5c0))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [5.0.0-alpha.38](https://github.com/react-navigation/navigation-ex/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.0.0-alpha.37...@react-navigation/bottom-tabs@5.0.0-alpha.38) (2020-01-23)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* don't use native driver on web ([0a982ee](https://github.com/react-navigation/navigation-ex/tree/master/packages/bottom-tabs/commit/0a982ee6984b24c0ba053a30223e255f3835e050))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* let the navigator specify if default can be prevented ([da67e13](https://github.com/react-navigation/navigation-ex/tree/master/packages/bottom-tabs/commit/da67e134d2157201360427d3c10da24f24cae7aa))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# [5.0.0-alpha.37](https://github.com/react-navigation/navigation-ex/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.0.0-alpha.36...@react-navigation/bottom-tabs@5.0.0-alpha.37) (2020-01-14)
|
# [5.0.0-alpha.37](https://github.com/react-navigation/navigation-ex/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.0.0-alpha.36...@react-navigation/bottom-tabs@5.0.0-alpha.37) (2020-01-14)
|
||||||
|
|
||||||
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||||
|
|||||||
21
packages/bottom-tabs/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2017 React Navigation Contributors
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
"android",
|
"android",
|
||||||
"tab"
|
"tab"
|
||||||
],
|
],
|
||||||
"version": "5.0.0-alpha.37",
|
"version": "5.0.0-alpha.43",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": "https://github.com/react-navigation/navigation-ex/tree/master/packages/bottom-tabs",
|
"repository": "https://github.com/react-navigation/navigation-ex/tree/master/packages/bottom-tabs",
|
||||||
"main": "lib/commonjs/index.js",
|
"main": "lib/commonjs/index.js",
|
||||||
@@ -21,6 +21,7 @@
|
|||||||
"src",
|
"src",
|
||||||
"lib"
|
"lib"
|
||||||
],
|
],
|
||||||
|
"sideEffects": false,
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
@@ -29,7 +30,7 @@
|
|||||||
"clean": "del lib"
|
"clean": "del lib"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@react-navigation/routers": "^5.0.0-alpha.25",
|
"@react-navigation/routers": "^5.0.0-alpha.31",
|
||||||
"color": "^3.1.2",
|
"color": "^3.1.2",
|
||||||
"react-native-iphone-x-helper": "^1.2.1"
|
"react-native-iphone-x-helper": "^1.2.1"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -17,11 +17,11 @@ export type BottomTabNavigationEventMap = {
|
|||||||
/**
|
/**
|
||||||
* Event which fires on tapping on the tab in the tab bar.
|
* Event which fires on tapping on the tab in the tab bar.
|
||||||
*/
|
*/
|
||||||
tabPress: undefined;
|
tabPress: { data: undefined; canPreventDefault: true };
|
||||||
/**
|
/**
|
||||||
* Event which fires on long press on the tab in the tab bar.
|
* Event which fires on long press on the tab in the tab bar.
|
||||||
*/
|
*/
|
||||||
tabLongPress: undefined;
|
tabLongPress: { data: undefined };
|
||||||
};
|
};
|
||||||
|
|
||||||
export type LabelPosition = 'beside-icon' | 'below-icon';
|
export type LabelPosition = 'beside-icon' | 'below-icon';
|
||||||
@@ -99,6 +99,12 @@ export type BottomTabNavigationOptions = {
|
|||||||
* Renders `TouchableWithoutFeedback` by default.
|
* Renders `TouchableWithoutFeedback` by default.
|
||||||
*/
|
*/
|
||||||
tabBarButton?: (props: BottomTabBarButtonProps) => React.ReactNode;
|
tabBarButton?: (props: BottomTabBarButtonProps) => React.ReactNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether this screen should be unmounted when navigating away from it.
|
||||||
|
* Defaults to `false`.
|
||||||
|
*/
|
||||||
|
unmountOnBlur?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type BottomTabDescriptor = Descriptor<
|
export type BottomTabDescriptor = Descriptor<
|
||||||
@@ -118,11 +124,6 @@ export type BottomTabNavigationConfig = {
|
|||||||
* Set it to `false` if you want to render all screens on initial render.
|
* Set it to `false` if you want to render all screens on initial render.
|
||||||
*/
|
*/
|
||||||
lazy?: boolean;
|
lazy?: boolean;
|
||||||
/**
|
|
||||||
* Whether a screen should be unmounted when navigating away from it.
|
|
||||||
* Defaults to `false`.
|
|
||||||
*/
|
|
||||||
unmountInactiveScreens?: boolean;
|
|
||||||
/**
|
/**
|
||||||
* Function that returns a React element to display as the tab bar.
|
* Function that returns a React element to display as the tab bar.
|
||||||
*/
|
*/
|
||||||
@@ -176,14 +177,9 @@ export type BottomTabBarOptions = {
|
|||||||
tabStyle?: StyleProp<ViewStyle>;
|
tabStyle?: StyleProp<ViewStyle>;
|
||||||
/**
|
/**
|
||||||
* Whether the label is renderd below the icon or beside the icon.
|
* Whether the label is renderd below the icon or beside the icon.
|
||||||
* When a function is passed, it receives the device dimensions to render the label differently.
|
|
||||||
* By default, in `vertical` orinetation, label is rendered below and in `horizontal` orientation, it's renderd beside.
|
* By default, in `vertical` orinetation, label is rendered below and in `horizontal` orientation, it's renderd beside.
|
||||||
*/
|
*/
|
||||||
labelPosition?:
|
labelPosition?: LabelPosition;
|
||||||
| LabelPosition
|
|
||||||
| ((options: {
|
|
||||||
dimensions: { height: number; width: number };
|
|
||||||
}) => LabelPosition);
|
|
||||||
/**
|
/**
|
||||||
* Whether the label position should adapt to the orientation.
|
* Whether the label position should adapt to the orientation.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ type Props = BottomTabBarProps & {
|
|||||||
const DEFAULT_TABBAR_HEIGHT = 50;
|
const DEFAULT_TABBAR_HEIGHT = 50;
|
||||||
const DEFAULT_MAX_TAB_ITEM_WIDTH = 125;
|
const DEFAULT_MAX_TAB_ITEM_WIDTH = 125;
|
||||||
|
|
||||||
|
const useNativeDriver = Platform.OS !== 'web';
|
||||||
|
|
||||||
export default function BottomTabBar({
|
export default function BottomTabBar({
|
||||||
state,
|
state,
|
||||||
navigation,
|
navigation,
|
||||||
@@ -48,7 +50,10 @@ export default function BottomTabBar({
|
|||||||
const { colors } = useTheme();
|
const { colors } = useTheme();
|
||||||
|
|
||||||
const [dimensions, setDimensions] = React.useState(Dimensions.get('window'));
|
const [dimensions, setDimensions] = React.useState(Dimensions.get('window'));
|
||||||
const [layout, setLayout] = React.useState({ height: 0, width: 0 });
|
const [layout, setLayout] = React.useState({
|
||||||
|
height: 0,
|
||||||
|
width: dimensions.width,
|
||||||
|
});
|
||||||
const [keyboardShown, setKeyboardShown] = React.useState(false);
|
const [keyboardShown, setKeyboardShown] = React.useState(false);
|
||||||
|
|
||||||
const [visible] = React.useState(() => new Animated.Value(0));
|
const [visible] = React.useState(() => new Animated.Value(0));
|
||||||
@@ -60,7 +65,7 @@ export default function BottomTabBar({
|
|||||||
Animated.timing(visible, {
|
Animated.timing(visible, {
|
||||||
toValue: 0,
|
toValue: 0,
|
||||||
duration: 200,
|
duration: 200,
|
||||||
useNativeDriver: true,
|
useNativeDriver,
|
||||||
}).start();
|
}).start();
|
||||||
}
|
}
|
||||||
}, [keyboardShown, visible]);
|
}, [keyboardShown, visible]);
|
||||||
@@ -76,7 +81,7 @@ export default function BottomTabBar({
|
|||||||
Animated.timing(visible, {
|
Animated.timing(visible, {
|
||||||
toValue: 1,
|
toValue: 1,
|
||||||
duration: 250,
|
duration: 250,
|
||||||
useNativeDriver: true,
|
useNativeDriver,
|
||||||
}).start(({ finished }) => {
|
}).start(({ finished }) => {
|
||||||
if (finished) {
|
if (finished) {
|
||||||
setKeyboardShown(false);
|
setKeyboardShown(false);
|
||||||
@@ -122,27 +127,15 @@ export default function BottomTabBar({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const shouldUseHorizontalLabels = () => {
|
const shouldUseHorizontalLabels = () => {
|
||||||
const isLandscape = dimensions.width > dimensions.height;
|
|
||||||
|
|
||||||
if (labelPosition) {
|
if (labelPosition) {
|
||||||
let position;
|
return labelPosition === 'beside-icon';
|
||||||
|
|
||||||
if (typeof labelPosition === 'string') {
|
|
||||||
position = labelPosition;
|
|
||||||
} else {
|
|
||||||
position = labelPosition({ dimensions });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (position) {
|
|
||||||
return position === 'beside-icon';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!adaptive) {
|
if (!adaptive) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dimensions.width >= 768) {
|
if (layout.width >= 768) {
|
||||||
// Screen size matches a tablet
|
// Screen size matches a tablet
|
||||||
let maxTabItemWidth = DEFAULT_MAX_TAB_ITEM_WIDTH;
|
let maxTabItemWidth = DEFAULT_MAX_TAB_ITEM_WIDTH;
|
||||||
|
|
||||||
@@ -156,8 +149,10 @@ export default function BottomTabBar({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return routes.length * maxTabItemWidth <= dimensions.width;
|
return routes.length * maxTabItemWidth <= layout.width;
|
||||||
} else {
|
} else {
|
||||||
|
const isLandscape = dimensions.width > dimensions.height;
|
||||||
|
|
||||||
return isLandscape;
|
return isLandscape;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -205,6 +200,7 @@ export default function BottomTabBar({
|
|||||||
const event = navigation.emit({
|
const event = navigation.emit({
|
||||||
type: 'tabPress',
|
type: 'tabPress',
|
||||||
target: route.key,
|
target: route.key,
|
||||||
|
canPreventDefault: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!focused && !event.defaultPrevented) {
|
if (!focused && !event.defaultPrevented) {
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ export default class BottomTabView extends React.Component<Props, State> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { state, descriptors, lazy, unmountInactiveScreens } = this.props;
|
const { state, descriptors, lazy } = this.props;
|
||||||
const { routes } = state;
|
const { routes } = state;
|
||||||
const { loaded } = this.state;
|
const { loaded } = this.state;
|
||||||
|
|
||||||
@@ -101,17 +101,19 @@ export default class BottomTabView extends React.Component<Props, State> {
|
|||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<ScreenContainer style={styles.pages}>
|
<ScreenContainer style={styles.pages}>
|
||||||
{routes.map((route, index) => {
|
{routes.map((route, index) => {
|
||||||
if (unmountInactiveScreens && index !== state.index) {
|
const descriptor = descriptors[route.key];
|
||||||
|
const { unmountOnBlur } = descriptor.options;
|
||||||
|
const isFocused = state.index === index;
|
||||||
|
|
||||||
|
if (unmountOnBlur && !isFocused) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lazy && !loaded.includes(index)) {
|
if (lazy && !loaded.includes(index) && !isFocused) {
|
||||||
// Don't render a screen if we've never navigated to it
|
// Don't render a screen if we've never navigated to it
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isFocused = state.index === index;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ResourceSavingScene
|
<ResourceSavingScene
|
||||||
key={route.key}
|
key={route.key}
|
||||||
@@ -119,7 +121,7 @@ export default class BottomTabView extends React.Component<Props, State> {
|
|||||||
isVisible={isFocused}
|
isVisible={isFocused}
|
||||||
>
|
>
|
||||||
<SceneContent isFocused={isFocused}>
|
<SceneContent isFocused={isFocused}>
|
||||||
{descriptors[route.key].render()}
|
{descriptor.render()}
|
||||||
</SceneContent>
|
</SceneContent>
|
||||||
</ResourceSavingScene>
|
</ResourceSavingScene>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,6 +3,58 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [5.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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [5.0.0-alpha.31](https://github.com/react-navigation/navigation-ex/tree/master/packages/compat/compare/@react-navigation/compat@5.0.0-alpha.28...@react-navigation/compat@5.0.0-alpha.31) (2020-02-02)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* add licenses ([0c159db](https://github.com/react-navigation/navigation-ex/tree/master/packages/compat/commit/0c159db4c9bc85e83b5cfe6819ab2562669a4d8f))
|
||||||
|
* throw when assigning or accessing the router property in compat ([944fa35](https://github.com/react-navigation/navigation-ex/tree/master/packages/compat/commit/944fa35ed4778ebc7fa7cd50092719cbd5bf3caf))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [5.0.0-alpha.29](https://github.com/react-navigation/navigation-ex/tree/master/packages/compat/compare/@react-navigation/compat@5.0.0-alpha.28...@react-navigation/compat@5.0.0-alpha.29) (2020-02-02)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* add licenses ([0c159db](https://github.com/react-navigation/navigation-ex/tree/master/packages/compat/commit/0c159db4c9bc85e83b5cfe6819ab2562669a4d8f))
|
||||||
|
* throw when assigning or accessing the router property in compat ([944fa35](https://github.com/react-navigation/navigation-ex/tree/master/packages/compat/commit/944fa35ed4778ebc7fa7cd50092719cbd5bf3caf))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [5.0.0-alpha.28](https://github.com/react-navigation/navigation-ex/tree/master/packages/compat/compare/@react-navigation/compat@5.0.0-alpha.27...@react-navigation/compat@5.0.0-alpha.28) (2020-01-24)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/compat
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [5.0.0-alpha.27](https://github.com/react-navigation/navigation-ex/tree/master/packages/compat/compare/@react-navigation/compat@5.0.0-alpha.26...@react-navigation/compat@5.0.0-alpha.27) (2020-01-23)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* ensure re-render on isFirstRouteInParent change in compat layer ([14ae373](https://github.com/react-navigation/navigation-ex/tree/master/packages/compat/commit/14ae3738cf46088e082bd1c60b9dcc6dacacd1bf))
|
||||||
|
* improvements to the compat layer ([2a76dc4](https://github.com/react-navigation/navigation-ex/tree/master/packages/compat/commit/2a76dc4d3c4cc0365a3afcff6ac321145efed026))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# [5.0.0-alpha.26](https://github.com/react-navigation/navigation-ex/tree/master/packages/compat/compare/@react-navigation/compat@5.0.0-alpha.25...@react-navigation/compat@5.0.0-alpha.26) (2020-01-14)
|
# [5.0.0-alpha.26](https://github.com/react-navigation/navigation-ex/tree/master/packages/compat/compare/@react-navigation/compat@5.0.0-alpha.25...@react-navigation/compat@5.0.0-alpha.26) (2020-01-14)
|
||||||
|
|
||||||
**Note:** Version bump only for package @react-navigation/compat
|
**Note:** Version bump only for package @react-navigation/compat
|
||||||
|
|||||||
21
packages/compat/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2017 React Navigation Contributors
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@react-navigation/compat",
|
"name": "@react-navigation/compat",
|
||||||
"description": "Compatibility layer to write navigator definitions in static configuration format",
|
"description": "Compatibility layer to write navigator definitions in static configuration format",
|
||||||
"version": "5.0.0-alpha.26",
|
"version": "5.0.0-alpha.32",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": "https://github.com/react-navigation/navigation-ex/tree/master/packages/compat",
|
"repository": "https://github.com/react-navigation/navigation-ex/tree/master/packages/compat",
|
||||||
"main": "lib/commonjs/index.js",
|
"main": "lib/commonjs/index.js",
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
"src",
|
"src",
|
||||||
"lib"
|
"lib"
|
||||||
],
|
],
|
||||||
|
"sideEffects": false,
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
@@ -20,7 +21,7 @@
|
|||||||
"clean": "del lib"
|
"clean": "del lib"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@react-navigation/routers": "^5.0.0-alpha.25"
|
"@react-navigation/routers": "^5.0.0-alpha.31"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/react": "^16.9.17",
|
"@types/react": "^16.9.17",
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
RouteProp,
|
RouteProp,
|
||||||
} from '@react-navigation/native';
|
} from '@react-navigation/native';
|
||||||
import ScreenPropsContext from './ScreenPropsContext';
|
import ScreenPropsContext from './ScreenPropsContext';
|
||||||
import createCompatNavigationProp from './createCompatNavigationProp';
|
import useCompatNavigation from './useCompatNavigation';
|
||||||
|
|
||||||
type Props<ParamList extends ParamListBase> = {
|
type Props<ParamList extends ParamListBase> = {
|
||||||
navigation: NavigationProp<ParamList>;
|
navigation: NavigationProp<ParamList>;
|
||||||
@@ -16,12 +16,7 @@ type Props<ParamList extends ParamListBase> = {
|
|||||||
function ScreenComponent<ParamList extends ParamListBase>(
|
function ScreenComponent<ParamList extends ParamListBase>(
|
||||||
props: Props<ParamList>
|
props: Props<ParamList>
|
||||||
) {
|
) {
|
||||||
const navigation = React.useMemo(
|
const navigation = useCompatNavigation();
|
||||||
() =>
|
|
||||||
createCompatNavigationProp(props.navigation as any, props.route as any),
|
|
||||||
[props.navigation, props.route]
|
|
||||||
);
|
|
||||||
|
|
||||||
const screenProps = React.useContext(ScreenPropsContext);
|
const screenProps = React.useContext(ScreenPropsContext);
|
||||||
|
|
||||||
return <props.component navigation={navigation} screenProps={screenProps} />;
|
return <props.component navigation={navigation} screenProps={screenProps} />;
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export function replace({
|
|||||||
key?: string;
|
key?: string;
|
||||||
newKey?: string;
|
newKey?: string;
|
||||||
action?: never;
|
action?: never;
|
||||||
}): CommonActions.Action {
|
}): StackActionType {
|
||||||
if (action !== undefined) {
|
if (action !== undefined) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Sub-actions are not supported for `replace`. Remove the `action` key from the options.'
|
'Sub-actions are not supported for `replace`. Remove the `action` key from the options.'
|
||||||
|
|||||||
@@ -8,11 +8,17 @@ import {
|
|||||||
import * as helpers from './helpers';
|
import * as helpers from './helpers';
|
||||||
import { CompatNavigationProp } from './types';
|
import { CompatNavigationProp } from './types';
|
||||||
|
|
||||||
type EventName = 'willFocus' | 'willBlur' | 'didFocus' | 'didBlur' | 'refocus';
|
type EventName =
|
||||||
|
| 'action'
|
||||||
|
| 'willFocus'
|
||||||
|
| 'willBlur'
|
||||||
|
| 'didFocus'
|
||||||
|
| 'didBlur'
|
||||||
|
| 'refocus';
|
||||||
|
|
||||||
const focusSubscriptions = new WeakMap<() => void, () => void>();
|
// const focusSubscriptions = new WeakMap<() => void, () => void>();
|
||||||
const blurSubscriptions = new WeakMap<() => void, () => void>();
|
// const blurSubscriptions = new WeakMap<() => void, () => void>();
|
||||||
const refocusSubscriptions = new WeakMap<() => void, () => void>();
|
// const refocusSubscriptions = new WeakMap<() => void, () => void>();
|
||||||
|
|
||||||
export default function createCompatNavigationProp<
|
export default function createCompatNavigationProp<
|
||||||
NavigationPropType extends NavigationProp<ParamListBase>,
|
NavigationPropType extends NavigationProp<ParamListBase>,
|
||||||
@@ -28,8 +34,17 @@ export default function createCompatNavigationProp<
|
|||||||
state?: NavigationState | PartialState<NavigationState>;
|
state?: NavigationState | PartialState<NavigationState>;
|
||||||
})
|
})
|
||||||
| NavigationState
|
| NavigationState
|
||||||
| PartialState<NavigationState>
|
| PartialState<NavigationState>,
|
||||||
|
context: Record<string, any>,
|
||||||
|
isFirstRouteInParent?: boolean
|
||||||
): CompatNavigationProp<NavigationPropType> {
|
): CompatNavigationProp<NavigationPropType> {
|
||||||
|
context.parent = context.parent || {};
|
||||||
|
context.subscriptions = context.subscriptions || {
|
||||||
|
didFocus: new Map<() => void, () => void>(),
|
||||||
|
didBlur: new Map<() => void, () => void>(),
|
||||||
|
refocus: new Map<() => void, () => void>(),
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...navigation,
|
...navigation,
|
||||||
...Object.entries(helpers).reduce<{
|
...Object.entries(helpers).reduce<{
|
||||||
@@ -61,7 +76,7 @@ export default function createCompatNavigationProp<
|
|||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
unsubscribe = navigation.addListener('transitionEnd', listener);
|
unsubscribe = navigation.addListener('transitionEnd', listener);
|
||||||
focusSubscriptions.set(callback, unsubscribe);
|
context.subscriptions.didFocus.set(callback, unsubscribe);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'didBlur': {
|
case 'didBlur': {
|
||||||
@@ -73,7 +88,7 @@ export default function createCompatNavigationProp<
|
|||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
unsubscribe = navigation.addListener('transitionEnd', listener);
|
unsubscribe = navigation.addListener('transitionEnd', listener);
|
||||||
blurSubscriptions.set(callback, unsubscribe);
|
context.subscriptions.didBlur.set(callback, unsubscribe);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'refocus': {
|
case 'refocus': {
|
||||||
@@ -85,9 +100,11 @@ export default function createCompatNavigationProp<
|
|||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
unsubscribe = navigation.addListener('tabPress', listener);
|
unsubscribe = navigation.addListener('tabPress', listener);
|
||||||
refocusSubscriptions.set(callback, unsubscribe);
|
context.subscriptions.refocus.set(callback, unsubscribe);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'action':
|
||||||
|
throw new Error("Listening to 'action' events is not supported.");
|
||||||
default:
|
default:
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
unsubscribe = navigation.addListener(type, callback);
|
unsubscribe = navigation.addListener(type, callback);
|
||||||
@@ -100,6 +117,8 @@ export default function createCompatNavigationProp<
|
|||||||
return subscription;
|
return subscription;
|
||||||
},
|
},
|
||||||
removeListener(type: EventName, callback: () => void) {
|
removeListener(type: EventName, callback: () => void) {
|
||||||
|
context.subscriptions = context.subscriptions || {};
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'willFocus':
|
case 'willFocus':
|
||||||
navigation.removeListener('focus', callback);
|
navigation.removeListener('focus', callback);
|
||||||
@@ -108,20 +127,22 @@ export default function createCompatNavigationProp<
|
|||||||
navigation.removeListener('blur', callback);
|
navigation.removeListener('blur', callback);
|
||||||
break;
|
break;
|
||||||
case 'didFocus': {
|
case 'didFocus': {
|
||||||
const unsubscribe = focusSubscriptions.get(callback);
|
const unsubscribe = context.subscriptions.didFocus.get(callback);
|
||||||
unsubscribe?.();
|
unsubscribe?.();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'didBlur': {
|
case 'didBlur': {
|
||||||
const unsubscribe = blurSubscriptions.get(callback);
|
const unsubscribe = context.subscriptions.didBlur.get(callback);
|
||||||
unsubscribe?.();
|
unsubscribe?.();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'refocus': {
|
case 'refocus': {
|
||||||
const unsubscribe = refocusSubscriptions.get(callback);
|
const unsubscribe = context.subscriptions.refocus.get(callback);
|
||||||
unsubscribe?.();
|
unsubscribe?.();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'action':
|
||||||
|
throw new Error("Listening to 'action' events is not supported.");
|
||||||
default:
|
default:
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
navigation.removeListener(type, callback);
|
navigation.removeListener(type, callback);
|
||||||
@@ -174,6 +195,10 @@ export default function createCompatNavigationProp<
|
|||||||
return defaultValue;
|
return defaultValue;
|
||||||
},
|
},
|
||||||
isFirstRouteInParent(): boolean {
|
isFirstRouteInParent(): boolean {
|
||||||
|
if (typeof isFirstRouteInParent === 'boolean') {
|
||||||
|
return isFirstRouteInParent;
|
||||||
|
}
|
||||||
|
|
||||||
const { routes } = navigation.dangerouslyGetState();
|
const { routes } = navigation.dangerouslyGetState();
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@@ -185,7 +210,8 @@ export default function createCompatNavigationProp<
|
|||||||
if (parent) {
|
if (parent) {
|
||||||
return createCompatNavigationProp(
|
return createCompatNavigationProp(
|
||||||
parent,
|
parent,
|
||||||
navigation.dangerouslyGetState()
|
navigation.dangerouslyGetState(),
|
||||||
|
context.parent
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ export default function createCompatNavigatorFactory<
|
|||||||
? {
|
? {
|
||||||
navigation: createCompatNavigationProp<
|
navigation: createCompatNavigationProp<
|
||||||
NavigationPropType
|
NavigationPropType
|
||||||
>(navigation, route),
|
>(navigation, route, {}),
|
||||||
navigationOptions: defaultNavigationOptions || {},
|
navigationOptions: defaultNavigationOptions || {},
|
||||||
screenProps,
|
screenProps,
|
||||||
}
|
}
|
||||||
@@ -165,7 +165,25 @@ export default function createCompatNavigatorFactory<
|
|||||||
return Navigator;
|
return Navigator;
|
||||||
};
|
};
|
||||||
|
|
||||||
createCompatNavigator.isCompat = true;
|
Object.defineProperties(createCompatNavigator, {
|
||||||
|
isCompat: {
|
||||||
|
get() {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
router: {
|
||||||
|
get() {
|
||||||
|
throw new Error(
|
||||||
|
"It's no longer possible to access the router with the 'router' property."
|
||||||
|
);
|
||||||
|
},
|
||||||
|
set() {
|
||||||
|
throw new Error(
|
||||||
|
"It's no longer possible to override the router by assigning the 'router' property."
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
return createCompatNavigator;
|
return createCompatNavigator;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
useRoute,
|
useRoute,
|
||||||
NavigationProp,
|
NavigationProp,
|
||||||
ParamListBase,
|
ParamListBase,
|
||||||
|
useNavigationState,
|
||||||
} from '@react-navigation/native';
|
} from '@react-navigation/native';
|
||||||
import createCompatNavigationProp from './createCompatNavigationProp';
|
import createCompatNavigationProp from './createCompatNavigationProp';
|
||||||
import { CompatNavigationProp } from './types';
|
import { CompatNavigationProp } from './types';
|
||||||
@@ -14,12 +15,20 @@ export default function useCompatNavigation<
|
|||||||
const navigation = useNavigation();
|
const navigation = useNavigation();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|
||||||
|
const isFirstRouteInParent = useNavigationState(
|
||||||
|
state => state.routes[0].key === route.key
|
||||||
|
);
|
||||||
|
|
||||||
|
const context = React.useRef<Record<string, any>>({});
|
||||||
|
|
||||||
return React.useMemo(
|
return React.useMemo(
|
||||||
() =>
|
() =>
|
||||||
createCompatNavigationProp(
|
createCompatNavigationProp(
|
||||||
navigation,
|
navigation,
|
||||||
route as any
|
route as any,
|
||||||
|
context.current,
|
||||||
|
isFirstRouteInParent
|
||||||
) as CompatNavigationProp<T>,
|
) as CompatNavigationProp<T>,
|
||||||
[navigation, route]
|
[isFirstRouteInParent, navigation, route]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,92 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [5.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)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* ignore circular references when checking serializable ([e5063b9](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/commit/e5063b93398350511f3fd2ef48425559f871781f))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [5.0.0-alpha.40](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/compare/@react-navigation/core@5.0.0-alpha.37...@react-navigation/core@5.0.0-alpha.40) (2020-02-02)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* add licenses ([0c159db](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/commit/0c159db4c9bc85e83b5cfe6819ab2562669a4d8f))
|
||||||
|
* add warning when passing inline function to component prop ([fa4a959](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/commit/fa4a959549ccd9dc2f9bd2ea495e99abdedc9f94))
|
||||||
|
* tweak error messages for validation ([2243b45](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/commit/2243b45cc1addf83727166d82736d214f181b1fb))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add `screens` prop for nested configs ([#308](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/issues/308)) ([b931ae6](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/commit/b931ae62dfb2c5253c94ea5ace73e9070ec17c4a))
|
||||||
|
* add useIsDrawerOpen hook ([#299](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/issues/299)) ([ecd68af](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/commit/ecd68afb46a4c56200748da5e5fb284fa5a839db))
|
||||||
|
* integrate with history API on web ([5a3f835](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/commit/5a3f8356b05bff7ed20893a5db6804612da3e568))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [5.0.0-alpha.38](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/compare/@react-navigation/core@5.0.0-alpha.37...@react-navigation/core@5.0.0-alpha.38) (2020-02-02)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* add licenses ([0c159db](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/commit/0c159db4c9bc85e83b5cfe6819ab2562669a4d8f))
|
||||||
|
* add warning when passing inline function to component prop ([fa4a959](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/commit/fa4a959549ccd9dc2f9bd2ea495e99abdedc9f94))
|
||||||
|
* tweak error messages for validation ([2243b45](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/commit/2243b45cc1addf83727166d82736d214f181b1fb))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add `screens` prop for nested configs ([#308](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/issues/308)) ([b931ae6](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/commit/b931ae62dfb2c5253c94ea5ace73e9070ec17c4a))
|
||||||
|
* add useIsDrawerOpen hook ([#299](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/issues/299)) ([ecd68af](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/commit/ecd68afb46a4c56200748da5e5fb284fa5a839db))
|
||||||
|
* integrate with history API on web ([5a3f835](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/commit/5a3f8356b05bff7ed20893a5db6804612da3e568))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [5.0.0-alpha.37](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/compare/@react-navigation/core@5.0.0-alpha.36...@react-navigation/core@5.0.0-alpha.37) (2020-01-24)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* add error message when trying to use v4 API with v5 ([179e807](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/commit/179e807a64a7d031d671c2c4b12edaee3c3440c5))
|
||||||
|
* validate screen configs ([2f1f0af](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/commit/2f1f0af862ef8625da4c2aaf463d45fe17a4ac88))
|
||||||
|
* warn if non-serializable values found in state ([5751e7f](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/commit/5751e7f97a1731a5c71862174dfd931b6ffe13e2))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [5.0.0-alpha.36](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/compare/@react-navigation/core@5.0.0-alpha.35...@react-navigation/core@5.0.0-alpha.36) (2020-01-23)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* disallow canPreventDefault option if not present in types ([d9059b5](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/commit/d9059b56d8a89b39fec43d38a7b0514d41c0b550))
|
||||||
|
* don't add ?if query params is empty ([3bf5ddd](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/commit/3bf5ddde2ac1ba45f1123752d37532175f18a3d9))
|
||||||
|
* fix types for useFocusEffect ([23ab45a](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/commit/23ab45aceb72cc27ebfacdedfbf60d0c540fecfb)), closes [#270](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/issues/270)
|
||||||
|
* make sure that we return correct value if selector changes ([6c2acbb](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/commit/6c2acbb304a9f11789b45a410b6c41911eca3947)), closes [/github.com/react-navigation/navigation-ex/pull/273#issuecomment-576581225](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/issues/issuecomment-576581225)
|
||||||
|
* use protected for private value store ([ad4eaff](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/commit/ad4eaff1e99e4f9fca3a193764fd0f26efa41341))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add useNavigationState hook ([32a2206](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/commit/32a2206513bc084d8da07187385d11db498f1e2a))
|
||||||
|
* let the navigator specify if default can be prevented ([da67e13](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/commit/da67e134d2157201360427d3c10da24f24cae7aa))
|
||||||
|
* support nested config in getPathFromState ([#266](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/issues/266)) ([1e53821](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/commit/1e53821d52be182369add07a86c72221c5dba53e))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# [5.0.0-alpha.35](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/compare/@react-navigation/core@5.0.0-alpha.34...@react-navigation/core@5.0.0-alpha.35) (2020-01-14)
|
# [5.0.0-alpha.35](https://github.com/react-navigation/navigation-ex/tree/master/packages/core/compare/@react-navigation/core@5.0.0-alpha.34...@react-navigation/core@5.0.0-alpha.35) (2020-01-14)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
21
packages/core/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2017 React Navigation Contributors
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
"react-native",
|
"react-native",
|
||||||
"react-navigation"
|
"react-navigation"
|
||||||
],
|
],
|
||||||
"version": "5.0.0-alpha.35",
|
"version": "5.0.0-alpha.41",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": "https://github.com/react-navigation/navigation-ex/tree/master/packages/core",
|
"repository": "https://github.com/react-navigation/navigation-ex/tree/master/packages/core",
|
||||||
"main": "lib/commonjs/index.js",
|
"main": "lib/commonjs/index.js",
|
||||||
@@ -27,6 +27,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"escape-string-regexp": "^2.0.0",
|
"escape-string-regexp": "^2.0.0",
|
||||||
"query-string": "^6.9.0",
|
"query-string": "^6.9.0",
|
||||||
|
"react-is": "^16.12.0",
|
||||||
"shortid": "^2.2.15",
|
"shortid": "^2.2.15",
|
||||||
"use-subscription": "^1.3.0"
|
"use-subscription": "^1.3.0"
|
||||||
},
|
},
|
||||||
@@ -34,6 +35,7 @@
|
|||||||
"@babel/core": "^7.7.7",
|
"@babel/core": "^7.7.7",
|
||||||
"@react-native-community/bob": "^0.8.0",
|
"@react-native-community/bob": "^0.8.0",
|
||||||
"@types/react": "^16.9.17",
|
"@types/react": "^16.9.17",
|
||||||
|
"@types/react-is": "^16.7.1",
|
||||||
"@types/shortid": "^0.0.29",
|
"@types/shortid": "^0.0.29",
|
||||||
"@types/use-subscription": "^1.0.0",
|
"@types/use-subscription": "^1.0.0",
|
||||||
"del-cli": "^3.0.0",
|
"del-cli": "^3.0.0",
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import shortid from 'shortid';
|
|
||||||
import { CommonAction, NavigationState, PartialState } from './types';
|
import { CommonAction, NavigationState, PartialState } from './types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -11,35 +10,6 @@ const BaseRouter = {
|
|||||||
action: CommonAction
|
action: CommonAction
|
||||||
): State | PartialState<State> | null {
|
): State | PartialState<State> | null {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case 'REPLACE': {
|
|
||||||
const index = action.source
|
|
||||||
? state.routes.findIndex(r => r.key === action.source)
|
|
||||||
: state.index;
|
|
||||||
|
|
||||||
if (index === -1) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { name, key, params } = action.payload;
|
|
||||||
|
|
||||||
if (!state.routeNames.includes(name)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
routes: state.routes.map((route, i) =>
|
|
||||||
i === index
|
|
||||||
? {
|
|
||||||
key: key !== undefined ? key : `${name}-${shortid()}`,
|
|
||||||
name,
|
|
||||||
params,
|
|
||||||
}
|
|
||||||
: route
|
|
||||||
),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'SET_PARAMS': {
|
case 'SET_PARAMS': {
|
||||||
const index = action.source
|
const index = action.source
|
||||||
? state.routes.findIndex(r => r.key === action.source)
|
? state.routes.findIndex(r => r.key === action.source)
|
||||||
|
|||||||
@@ -14,12 +14,6 @@ export type Action =
|
|||||||
source?: string;
|
source?: string;
|
||||||
target?: string;
|
target?: string;
|
||||||
}
|
}
|
||||||
| {
|
|
||||||
type: 'REPLACE';
|
|
||||||
payload: { name: string; key?: string; params?: object };
|
|
||||||
source?: string;
|
|
||||||
target?: string;
|
|
||||||
}
|
|
||||||
| {
|
| {
|
||||||
type: 'RESET';
|
type: 'RESET';
|
||||||
payload: PartialState<NavigationState>;
|
payload: PartialState<NavigationState>;
|
||||||
@@ -59,10 +53,6 @@ export function navigate(...args: any): Action {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function replace(name: string, params?: object): Action {
|
|
||||||
return { type: 'REPLACE', payload: { name, params } };
|
|
||||||
}
|
|
||||||
|
|
||||||
export function reset(state: PartialState<NavigationState>): Action {
|
export function reset(state: PartialState<NavigationState>): Action {
|
||||||
return { type: 'RESET', payload: state };
|
return { type: 'RESET', payload: state };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ import * as React from 'react';
|
|||||||
import * as CommonActions from './CommonActions';
|
import * as CommonActions from './CommonActions';
|
||||||
import EnsureSingleNavigator from './EnsureSingleNavigator';
|
import EnsureSingleNavigator from './EnsureSingleNavigator';
|
||||||
import NavigationBuilderContext from './NavigationBuilderContext';
|
import NavigationBuilderContext from './NavigationBuilderContext';
|
||||||
import ResetRootContext from './ResetRootContext';
|
|
||||||
import useFocusedListeners from './useFocusedListeners';
|
import useFocusedListeners from './useFocusedListeners';
|
||||||
import useDevTools from './useDevTools';
|
import useDevTools from './useDevTools';
|
||||||
import useStateGetters from './useStateGetters';
|
import useStateGetters from './useStateGetters';
|
||||||
|
import isSerializable from './isSerializable';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Route,
|
Route,
|
||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
NavigationContainerRef,
|
NavigationContainerRef,
|
||||||
NavigationContainerProps,
|
NavigationContainerProps,
|
||||||
} from './types';
|
} from './types';
|
||||||
|
import useEventEmitter from './useEventEmitter';
|
||||||
|
|
||||||
type State = NavigationState | PartialState<NavigationState> | undefined;
|
type State = NavigationState | PartialState<NavigationState> | undefined;
|
||||||
|
|
||||||
@@ -45,6 +46,8 @@ export const NavigationStateContext = React.createContext<{
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let hasWarnedForSerialization = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove `key` and `routeNames` from the state objects recursively to get partial state.
|
* Remove `key` and `routeNames` from the state objects recursively to get partial state.
|
||||||
*
|
*
|
||||||
@@ -203,6 +206,8 @@ const Container = React.forwardRef(function NavigationContainer(
|
|||||||
return getStateForRoute('root');
|
return getStateForRoute('root');
|
||||||
}, [getStateForRoute]);
|
}, [getStateForRoute]);
|
||||||
|
|
||||||
|
const emitter = useEventEmitter();
|
||||||
|
|
||||||
React.useImperativeHandle(ref, () => ({
|
React.useImperativeHandle(ref, () => ({
|
||||||
...(Object.keys(CommonActions) as (keyof typeof CommonActions)[]).reduce<
|
...(Object.keys(CommonActions) as (keyof typeof CommonActions)[]).reduce<
|
||||||
any
|
any
|
||||||
@@ -216,6 +221,7 @@ const Container = React.forwardRef(function NavigationContainer(
|
|||||||
);
|
);
|
||||||
return acc;
|
return acc;
|
||||||
}, {}),
|
}, {}),
|
||||||
|
...emitter.create('root'),
|
||||||
resetRoot,
|
resetRoot,
|
||||||
dispatch,
|
dispatch,
|
||||||
canGoBack,
|
canGoBack,
|
||||||
@@ -242,6 +248,25 @@ const Container = React.forwardRef(function NavigationContainer(
|
|||||||
);
|
);
|
||||||
|
|
||||||
React.useEffect(() => {
|
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) {
|
if (skipTrackingRef.current) {
|
||||||
skipTrackingRef.current = false;
|
skipTrackingRef.current = false;
|
||||||
} else {
|
} else {
|
||||||
@@ -256,14 +281,12 @@ const Container = React.forwardRef(function NavigationContainer(
|
|||||||
}
|
}
|
||||||
|
|
||||||
isFirstMountRef.current = false;
|
isFirstMountRef.current = false;
|
||||||
}, [state, onStateChange, trackState, getRootState]);
|
}, [state, onStateChange, trackState, getRootState, emitter]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NavigationBuilderContext.Provider value={builderContext}>
|
<NavigationBuilderContext.Provider value={builderContext}>
|
||||||
<NavigationStateContext.Provider value={context}>
|
<NavigationStateContext.Provider value={context}>
|
||||||
<ResetRootContext.Provider value={resetRoot}>
|
|
||||||
<EnsureSingleNavigator>{children}</EnsureSingleNavigator>
|
<EnsureSingleNavigator>{children}</EnsureSingleNavigator>
|
||||||
</ResetRootContext.Provider>
|
|
||||||
</NavigationStateContext.Provider>
|
</NavigationStateContext.Provider>
|
||||||
</NavigationBuilderContext.Provider>
|
</NavigationBuilderContext.Provider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
import * as React from 'react';
|
|
||||||
import { NavigationState, PartialState } from './types';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Context which holds the method to reset root navigator state.
|
|
||||||
*/
|
|
||||||
const ResetRootContext = React.createContext<
|
|
||||||
(state: PartialState<NavigationState> | NavigationState) => void
|
|
||||||
>(() => {
|
|
||||||
throw new Error(
|
|
||||||
"We couldn't find a way to reset root state. Have you wrapped your app with 'NavigationContainer'?"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
export default ResetRootContext;
|
|
||||||
@@ -16,64 +16,6 @@ const STATE = {
|
|||||||
routeNames: ['foo', 'bar', 'baz', 'qux'],
|
routeNames: ['foo', 'bar', 'baz', 'qux'],
|
||||||
};
|
};
|
||||||
|
|
||||||
it('replaces focused screen with REPLACE', () => {
|
|
||||||
const result = BaseRouter.getStateForAction(
|
|
||||||
STATE,
|
|
||||||
CommonActions.replace('qux', { answer: 42 })
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(result).toEqual({
|
|
||||||
stale: false,
|
|
||||||
type: 'test',
|
|
||||||
key: 'root',
|
|
||||||
index: 1,
|
|
||||||
routes: [
|
|
||||||
{ key: 'foo', name: 'foo' },
|
|
||||||
{ key: 'qux-test', name: 'qux', params: { answer: 42 } },
|
|
||||||
{ key: 'baz', name: 'baz' },
|
|
||||||
],
|
|
||||||
routeNames: ['foo', 'bar', 'baz', 'qux'],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('replaces source screen with REPLACE', () => {
|
|
||||||
const result = BaseRouter.getStateForAction(STATE, {
|
|
||||||
...CommonActions.replace('qux', { answer: 42 }),
|
|
||||||
source: 'baz',
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(result).toEqual({
|
|
||||||
stale: false,
|
|
||||||
type: 'test',
|
|
||||||
key: 'root',
|
|
||||||
index: 1,
|
|
||||||
routes: [
|
|
||||||
{ key: 'foo', name: 'foo' },
|
|
||||||
{ key: 'bar', name: 'bar', params: { fruit: 'orange' } },
|
|
||||||
{ key: 'qux-test', name: 'qux', params: { answer: 42 } },
|
|
||||||
],
|
|
||||||
routeNames: ['foo', 'bar', 'baz', 'qux'],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("doesn't handle REPLACE if source key isn't present", () => {
|
|
||||||
const result = BaseRouter.getStateForAction(STATE, {
|
|
||||||
...CommonActions.replace('qux', { answer: 42 }),
|
|
||||||
source: 'magic',
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(result).toBe(null);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("doesn't handle REPLACE if screen to replace with isn't present", () => {
|
|
||||||
const result = BaseRouter.getStateForAction(
|
|
||||||
STATE,
|
|
||||||
CommonActions.replace('nonexistent', { answer: 42 })
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(result).toBe(null);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('sets params for the focused screen with SET_PARAMS', () => {
|
it('sets params for the focused screen with SET_PARAMS', () => {
|
||||||
const result = BaseRouter.getStateForAction(
|
const result = BaseRouter.getStateForAction(
|
||||||
STATE,
|
STATE,
|
||||||
|
|||||||
@@ -233,8 +233,8 @@ it('handle dispatching with ref', () => {
|
|||||||
<Screen name="foo2">
|
<Screen name="foo2">
|
||||||
{() => (
|
{() => (
|
||||||
<ChildNavigator>
|
<ChildNavigator>
|
||||||
<Screen name="qux" component={() => null} />
|
<Screen name="qux">{() => null}</Screen>
|
||||||
<Screen name="lex" component={() => null} />
|
<Screen name="lex">{() => null}</Screen>
|
||||||
</ChildNavigator>
|
</ChildNavigator>
|
||||||
)}
|
)}
|
||||||
</Screen>
|
</Screen>
|
||||||
@@ -242,8 +242,8 @@ it('handle dispatching with ref', () => {
|
|||||||
<Screen name="baz">
|
<Screen name="baz">
|
||||||
{() => (
|
{() => (
|
||||||
<ChildNavigator>
|
<ChildNavigator>
|
||||||
<Screen name="qux" component={() => null} />
|
<Screen name="qux">{() => null}</Screen>
|
||||||
<Screen name="lex" component={() => null} />
|
<Screen name="lex">{() => null}</Screen>
|
||||||
</ChildNavigator>
|
</ChildNavigator>
|
||||||
)}
|
)}
|
||||||
</Screen>
|
</Screen>
|
||||||
@@ -254,9 +254,7 @@ it('handle dispatching with ref', () => {
|
|||||||
render(element).update(element);
|
render(element).update(element);
|
||||||
|
|
||||||
act(() => {
|
act(() => {
|
||||||
if (ref.current != null) {
|
ref.current?.dispatch({ type: 'REVERSE' });
|
||||||
ref.current.dispatch({ type: 'REVERSE' });
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(onStateChange).toBeCalledTimes(1);
|
expect(onStateChange).toBeCalledTimes(1);
|
||||||
@@ -309,8 +307,8 @@ it('handle resetting state with ref', () => {
|
|||||||
<Screen name="foo2">
|
<Screen name="foo2">
|
||||||
{() => (
|
{() => (
|
||||||
<TestNavigator>
|
<TestNavigator>
|
||||||
<Screen name="qux" component={() => null} />
|
<Screen name="qux">{() => null}</Screen>
|
||||||
<Screen name="lex" component={() => null} />
|
<Screen name="lex">{() => null}</Screen>
|
||||||
</TestNavigator>
|
</TestNavigator>
|
||||||
)}
|
)}
|
||||||
</Screen>
|
</Screen>
|
||||||
@@ -318,8 +316,8 @@ it('handle resetting state with ref', () => {
|
|||||||
<Screen name="baz">
|
<Screen name="baz">
|
||||||
{() => (
|
{() => (
|
||||||
<TestNavigator>
|
<TestNavigator>
|
||||||
<Screen name="qux" component={() => null} />
|
<Screen name="qux">{() => null}</Screen>
|
||||||
<Screen name="lex" component={() => null} />
|
<Screen name="lex">{() => null}</Screen>
|
||||||
</TestNavigator>
|
</TestNavigator>
|
||||||
)}
|
)}
|
||||||
</Screen>
|
</Screen>
|
||||||
@@ -350,9 +348,7 @@ it('handle resetting state with ref', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
act(() => {
|
act(() => {
|
||||||
if (ref.current != null) {
|
ref.current?.resetRoot(state);
|
||||||
ref.current.resetRoot(state);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(onStateChange).toBeCalledTimes(1);
|
expect(onStateChange).toBeCalledTimes(1);
|
||||||
@@ -383,7 +379,7 @@ it('handle resetting state with ref', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handle getRootState', () => {
|
it('handles getRootState', () => {
|
||||||
const TestNavigator = (props: any) => {
|
const TestNavigator = (props: any) => {
|
||||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||||
|
|
||||||
@@ -398,12 +394,12 @@ it('handle getRootState', () => {
|
|||||||
<Screen name="foo">
|
<Screen name="foo">
|
||||||
{() => (
|
{() => (
|
||||||
<TestNavigator>
|
<TestNavigator>
|
||||||
<Screen name="qux" component={() => null} />
|
<Screen name="qux">{() => null}</Screen>
|
||||||
<Screen name="lex" component={() => null} />
|
<Screen name="lex">{() => null}</Screen>
|
||||||
</TestNavigator>
|
</TestNavigator>
|
||||||
)}
|
)}
|
||||||
</Screen>
|
</Screen>
|
||||||
<Screen name="bar" component={() => null} />
|
<Screen name="bar">{() => null}</Screen>
|
||||||
</TestNavigator>
|
</TestNavigator>
|
||||||
</NavigationContainer>
|
</NavigationContainer>
|
||||||
);
|
);
|
||||||
@@ -440,3 +436,68 @@ it('handle getRootState', () => {
|
|||||||
type: 'test',
|
type: 'test',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
it('emits state events when the state changes', () => {
|
||||||
|
const TestNavigator = (props: any) => {
|
||||||
|
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
{state.routes.map(route => descriptors[route.key].render())}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ref = React.createRef<NavigationContainerRef>();
|
||||||
|
|
||||||
|
const element = (
|
||||||
|
<NavigationContainer ref={ref}>
|
||||||
|
<TestNavigator>
|
||||||
|
<Screen name="foo">{() => null}</Screen>
|
||||||
|
<Screen name="bar">{() => null}</Screen>
|
||||||
|
<Screen name="baz">{() => null}</Screen>
|
||||||
|
</TestNavigator>
|
||||||
|
</NavigationContainer>
|
||||||
|
);
|
||||||
|
|
||||||
|
render(element).update(element);
|
||||||
|
|
||||||
|
const listener = jest.fn();
|
||||||
|
|
||||||
|
ref.current?.addListener('state', listener);
|
||||||
|
|
||||||
|
expect(listener).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
ref.current?.navigate('bar');
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(listener.mock.calls[0][0].data.state).toEqual({
|
||||||
|
type: 'test',
|
||||||
|
stale: false,
|
||||||
|
index: 1,
|
||||||
|
key: '9',
|
||||||
|
routeNames: ['foo', 'bar', 'baz'],
|
||||||
|
routes: [
|
||||||
|
{ key: 'foo', name: 'foo' },
|
||||||
|
{ key: 'bar', name: 'bar' },
|
||||||
|
{ key: 'baz', name: 'baz' },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
ref.current?.navigate('baz', { answer: 42 });
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(listener.mock.calls[1][0].data.state).toEqual({
|
||||||
|
type: 'test',
|
||||||
|
stale: false,
|
||||||
|
index: 2,
|
||||||
|
key: '9',
|
||||||
|
routeNames: ['foo', 'bar', 'baz'],
|
||||||
|
routes: [
|
||||||
|
{ key: 'foo', name: 'foo' },
|
||||||
|
{ key: 'bar', name: 'bar' },
|
||||||
|
{ key: 'baz', name: 'baz', params: { answer: 42 } },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
12
packages/core/src/__tests__/createNavigatorFactory.test.tsx
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import createNavigatorFactory from '../createNavigatorFactory';
|
||||||
|
|
||||||
|
it('throws descriptive error if an argument is passed', () => {
|
||||||
|
const createDummyNavigator = createNavigatorFactory(() => null);
|
||||||
|
|
||||||
|
expect(() => createDummyNavigator()).not.toThrowError();
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
expect(() => createDummyNavigator({})).toThrowError(
|
||||||
|
"Creating a navigator doesn't take an argument."
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import getPathFromState from '../getPathFromState';
|
import getPathFromState from '../getPathFromState';
|
||||||
|
import getStateFromPath from '../getStateFromPath';
|
||||||
|
|
||||||
it('converts state to path string', () => {
|
it('converts state to path string', () => {
|
||||||
expect(
|
const state = {
|
||||||
getPathFromState({
|
|
||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
name: 'foo',
|
name: 'foo',
|
||||||
@@ -26,14 +26,34 @@ it('converts state to path string', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
};
|
||||||
).toMatchInlineSnapshot(`"/foo/bar/baz%20qux?author=jane&valid=true"`);
|
|
||||||
|
const path = '/foo/bar/baz%20qux?author=jane&valid=true';
|
||||||
|
|
||||||
|
expect(getPathFromState(state)).toBe(path);
|
||||||
|
expect(getPathFromState(getStateFromPath(path))).toBe(path);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('converts state to path string with config', () => {
|
it('converts state to path string with config', () => {
|
||||||
expect(
|
const path = '/few/bar/sweet/apple/baz/jane?id=x10&valid=true';
|
||||||
getPathFromState(
|
const config = {
|
||||||
{
|
Foo: 'few',
|
||||||
|
Bar: 'bar/:type/:fruit',
|
||||||
|
Baz: {
|
||||||
|
path: 'baz/:author',
|
||||||
|
parse: {
|
||||||
|
author: (author: string) => author.replace(/^\w/, c => c.toUpperCase()),
|
||||||
|
id: (id: string) => Number(id.replace(/^x/, '')),
|
||||||
|
valid: Boolean,
|
||||||
|
},
|
||||||
|
stringify: {
|
||||||
|
author: (author: string) => author.toLowerCase(),
|
||||||
|
id: (id: number) => `x${id}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const state = {
|
||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
name: 'Foo',
|
name: 'Foo',
|
||||||
@@ -57,25 +77,15 @@ it('converts state to path string with config', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
};
|
||||||
{
|
|
||||||
Foo: 'few',
|
expect(getPathFromState(state, config)).toBe(path);
|
||||||
Bar: 'bar/:type/:fruit',
|
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
|
||||||
Baz: {
|
|
||||||
path: 'baz/:author',
|
|
||||||
stringify: {
|
|
||||||
author: author => author.toLowerCase(),
|
|
||||||
id: id => `x${id}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
).toMatchInlineSnapshot(`"/few/bar/sweet/apple/baz/jane?id=x10&valid=true"`);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles route without param', () => {
|
it('handles route without param', () => {
|
||||||
expect(
|
const path = '/foo/bar';
|
||||||
getPathFromState({
|
const state = {
|
||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
name: 'foo',
|
name: 'foo',
|
||||||
@@ -84,6 +94,308 @@ it('handles route without param', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
};
|
||||||
).toBe('/foo/bar');
|
|
||||||
|
expect(getPathFromState(state)).toBe(path);
|
||||||
|
expect(getPathFromState(getStateFromPath(path))).toBe(path);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("doesn't add query param for empty params", () => {
|
||||||
|
const path = '/foo';
|
||||||
|
const state = {
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
name: 'foo',
|
||||||
|
params: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(getPathFromState(state)).toBe(path);
|
||||||
|
expect(getPathFromState(getStateFromPath(path))).toBe(path);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles state with config with nested screens', () => {
|
||||||
|
const path = '/foe/bar/sweet/apple/baz/jane?answer=42&count=10&valid=true';
|
||||||
|
const config = {
|
||||||
|
Foo: {
|
||||||
|
path: 'foo',
|
||||||
|
screens: {
|
||||||
|
Foe: 'foe',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Bar: 'bar/:type/:fruit',
|
||||||
|
Baz: {
|
||||||
|
path: 'baz/:author',
|
||||||
|
parse: {
|
||||||
|
author: (author: string) => author.replace(/^\w/, c => c.toUpperCase()),
|
||||||
|
count: Number,
|
||||||
|
valid: Boolean,
|
||||||
|
},
|
||||||
|
stringify: {
|
||||||
|
author: (author: string) => author.toLowerCase(),
|
||||||
|
id: (id: number) => `x${id}`,
|
||||||
|
unknown: (_: unknown) => 'x',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
name: 'Foo',
|
||||||
|
state: {
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
name: 'Foe',
|
||||||
|
state: {
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
name: 'Bar',
|
||||||
|
params: { fruit: 'apple', type: 'sweet' },
|
||||||
|
state: {
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
name: 'Baz',
|
||||||
|
params: {
|
||||||
|
author: 'Jane',
|
||||||
|
count: '10',
|
||||||
|
answer: '42',
|
||||||
|
valid: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(getPathFromState(state, config)).toBe(path);
|
||||||
|
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles state with config with nested screens and unused configs', () => {
|
||||||
|
const path = '/foe/baz/jane?answer=42&count=10&valid=true';
|
||||||
|
const config = {
|
||||||
|
Foo: {
|
||||||
|
path: 'foo',
|
||||||
|
screens: {
|
||||||
|
Foe: 'foe',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Baz: {
|
||||||
|
path: 'baz/:author',
|
||||||
|
parse: {
|
||||||
|
author: (author: string) => author.replace(/^\w/, c => c.toUpperCase()),
|
||||||
|
count: Number,
|
||||||
|
valid: Boolean,
|
||||||
|
},
|
||||||
|
stringify: {
|
||||||
|
author: (author: string) => author.replace(/^\w/, c => c.toLowerCase()),
|
||||||
|
unknown: (_: unknown) => 'x',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
name: 'Foo',
|
||||||
|
state: {
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
name: 'Foe',
|
||||||
|
state: {
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
name: 'Baz',
|
||||||
|
params: {
|
||||||
|
author: 'Jane',
|
||||||
|
count: 10,
|
||||||
|
answer: '42',
|
||||||
|
valid: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(getPathFromState(state, config)).toBe(path);
|
||||||
|
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles nested object with stringify in it', () => {
|
||||||
|
const path = '/bar/sweet/apple/foe/bis/jane?answer=42&count=10&valid=true';
|
||||||
|
const config = {
|
||||||
|
Foo: {
|
||||||
|
path: 'foo',
|
||||||
|
screens: {
|
||||||
|
Foe: '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: 'Foe',
|
||||||
|
state: {
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
name: 'Baz',
|
||||||
|
state: {
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
name: 'Bis',
|
||||||
|
params: {
|
||||||
|
author: 'Jane',
|
||||||
|
count: 10,
|
||||||
|
answer: '42',
|
||||||
|
valid: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(getPathFromState(state, config)).toBe(path);
|
||||||
|
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles nested object for second route depth', () => {
|
||||||
|
const path = '/baz';
|
||||||
|
const config = {
|
||||||
|
Foo: {
|
||||||
|
path: 'foo',
|
||||||
|
screens: {
|
||||||
|
Foe: 'foe',
|
||||||
|
Bar: {
|
||||||
|
path: 'bar',
|
||||||
|
screens: {
|
||||||
|
Baz: '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 nested object for second route depth and and path and stringify in roots', () => {
|
||||||
|
const path = '/baz';
|
||||||
|
const config = {
|
||||||
|
Foo: {
|
||||||
|
path: 'foo/:id',
|
||||||
|
stringify: {
|
||||||
|
id: (id: number) => `id=${id}`,
|
||||||
|
},
|
||||||
|
screens: {
|
||||||
|
Foe: 'foe',
|
||||||
|
Bar: {
|
||||||
|
path: 'bar/:id',
|
||||||
|
stringify: {
|
||||||
|
id: (id: number) => `id=${id}`,
|
||||||
|
},
|
||||||
|
parse: {
|
||||||
|
id: Number,
|
||||||
|
},
|
||||||
|
screens: {
|
||||||
|
Baz: '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);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
import getStateFromPath from '../getStateFromPath';
|
import getStateFromPath from '../getStateFromPath';
|
||||||
|
import getPathFromState from '../getPathFromState';
|
||||||
|
|
||||||
|
it('returns undefined for invalid path', () => {
|
||||||
|
expect(getStateFromPath('//')).toBe(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
it('converts path string to initial state', () => {
|
it('converts path string to initial state', () => {
|
||||||
expect(
|
const path = 'foo/bar/baz%20qux?author=jane%20%26%20co&valid=true';
|
||||||
getStateFromPath('foo/bar/baz%20qux?author=jane%20%26%20co&valid=true')
|
const state = {
|
||||||
).toEqual({
|
|
||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
name: 'foo',
|
name: 'foo',
|
||||||
@@ -24,28 +28,31 @@ it('converts path string to initial state', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
};
|
||||||
|
|
||||||
|
expect(getStateFromPath(path)).toEqual(state);
|
||||||
|
expect(getStateFromPath(getPathFromState(state))).toEqual(state);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('converts path string to initial state with config', () => {
|
it('converts path string to initial state with config', () => {
|
||||||
expect(
|
const path = '/foo/bar/sweet/apple/baz/jane?count=10&answer=42&valid=true';
|
||||||
getStateFromPath(
|
const config = {
|
||||||
'/few/bar/sweet/apple/baz/jane?count=10&answer=42&valid=true',
|
Foo: 'foo',
|
||||||
{
|
|
||||||
Foo: 'few',
|
|
||||||
Bar: 'bar/:type/:fruit',
|
Bar: 'bar/:type/:fruit',
|
||||||
Baz: {
|
Baz: {
|
||||||
path: 'baz/:author',
|
path: 'baz/:author',
|
||||||
parse: {
|
parse: {
|
||||||
author: (author: string) =>
|
author: (author: string) => author.replace(/^\w/, c => c.toUpperCase()),
|
||||||
author.replace(/^\w/, c => c.toUpperCase()),
|
|
||||||
count: Number,
|
count: Number,
|
||||||
valid: Boolean,
|
valid: Boolean,
|
||||||
},
|
},
|
||||||
|
stringify: {
|
||||||
|
author: (author: string) => author.toLowerCase(),
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
)
|
};
|
||||||
).toEqual({
|
|
||||||
|
const state = {
|
||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
name: 'Foo',
|
name: 'Foo',
|
||||||
@@ -72,7 +79,12 @@ it('converts path string to initial state with config', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
};
|
||||||
|
|
||||||
|
expect(getStateFromPath(path, config)).toEqual(state);
|
||||||
|
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
||||||
|
state
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles leading slash when converting', () => {
|
it('handles leading slash when converting', () => {
|
||||||
@@ -112,7 +124,8 @@ it('handles ending slash when converting', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('handles route without param', () => {
|
it('handles route without param', () => {
|
||||||
expect(getStateFromPath('foo/bar')).toEqual({
|
const path = 'foo/bar';
|
||||||
|
const state = {
|
||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
name: 'foo',
|
name: 'foo',
|
||||||
@@ -121,34 +134,36 @@ it('handles route without param', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
};
|
||||||
});
|
|
||||||
|
|
||||||
it('returns undefined for invalid path', () => {
|
expect(getStateFromPath(path)).toEqual(state);
|
||||||
expect(getStateFromPath('//')).toBe(undefined);
|
expect(getStateFromPath(getPathFromState(state))).toEqual(state);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('converts path string to initial state with config with nested screens', () => {
|
it('converts path string to initial state with config with nested screens', () => {
|
||||||
expect(
|
const path = '/foe/bar/sweet/apple/baz/jane?count=10&answer=42&valid=true';
|
||||||
getStateFromPath(
|
const config = {
|
||||||
'/few/bar/sweet/apple/baz/jane?count=10&answer=42&valid=true',
|
|
||||||
{
|
|
||||||
Foo: {
|
Foo: {
|
||||||
Foe: 'few',
|
path: 'foo',
|
||||||
|
screens: {
|
||||||
|
Foe: 'foe',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Bar: 'bar/:type/:fruit',
|
Bar: 'bar/:type/:fruit',
|
||||||
Baz: {
|
Baz: {
|
||||||
path: 'baz/:author',
|
path: 'baz/:author',
|
||||||
parse: {
|
parse: {
|
||||||
author: (author: string) =>
|
author: (author: string) => author.replace(/^\w/, c => c.toUpperCase()),
|
||||||
author.replace(/^\w/, c => c.toUpperCase()),
|
|
||||||
count: Number,
|
count: Number,
|
||||||
valid: Boolean,
|
valid: Boolean,
|
||||||
},
|
},
|
||||||
|
stringify: {
|
||||||
|
author: (author: string) => author.toLowerCase(),
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
)
|
};
|
||||||
).toEqual({
|
|
||||||
|
const state = {
|
||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
name: 'Foo',
|
name: 'Foo',
|
||||||
@@ -182,26 +197,35 @@ it('converts path string to initial state with config with nested screens', () =
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
};
|
||||||
|
|
||||||
|
expect(getStateFromPath(path, config)).toEqual(state);
|
||||||
|
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
||||||
|
state
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('converts path string to initial state with config with nested screens and unused configs', () => {
|
it('converts path string to initial state with config with nested screens and unused parse functions', () => {
|
||||||
expect(
|
const path = '/foe/baz/jane?count=10&answer=42&valid=true';
|
||||||
getStateFromPath('/few/baz/jane?count=10&answer=42&valid=true', {
|
const config = {
|
||||||
Foo: {
|
Foo: {
|
||||||
Foe: 'few',
|
path: 'foo',
|
||||||
|
screens: {
|
||||||
|
Foe: 'foe',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Baz: {
|
Baz: {
|
||||||
path: 'baz/:author',
|
path: 'baz/:author',
|
||||||
parse: {
|
parse: {
|
||||||
author: (author: string) =>
|
author: (author: string) => author.replace(/^\w/, c => c.toUpperCase()),
|
||||||
author.replace(/^\w/, c => c.toUpperCase()),
|
|
||||||
count: Number,
|
count: Number,
|
||||||
valid: Boolean,
|
valid: Boolean,
|
||||||
|
id: Boolean,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
};
|
||||||
).toEqual({
|
|
||||||
|
const state = {
|
||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
name: 'Foo',
|
name: 'Foo',
|
||||||
@@ -227,21 +251,34 @@ it('converts path string to initial state with config with nested screens and un
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
};
|
||||||
|
|
||||||
|
expect(getStateFromPath(path, config)).toEqual(state);
|
||||||
|
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
||||||
|
state
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles parse in nested object with parse in it', () => {
|
it('handles nested object with unused configs and with parse in it', () => {
|
||||||
expect(
|
const path = '/bar/sweet/apple/foe/bis/jane?count=10&answer=42&valid=true';
|
||||||
getStateFromPath(
|
const config = {
|
||||||
'/bar/sweet/apple/few/bis/jane?count=10&answer=42&valid=true',
|
|
||||||
{
|
|
||||||
Foo: {
|
Foo: {
|
||||||
Foe: 'few',
|
path: 'foo',
|
||||||
|
screens: {
|
||||||
|
Foe: 'foe',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Bar: 'bar/:type/:fruit',
|
Bar: 'bar/:type/:fruit',
|
||||||
Baz: {
|
Baz: {
|
||||||
|
path: 'baz',
|
||||||
|
screens: {
|
||||||
|
Bos: 'bos',
|
||||||
Bis: {
|
Bis: {
|
||||||
path: 'bis/:author',
|
path: 'bis/:author',
|
||||||
|
stringify: {
|
||||||
|
author: (author: string) =>
|
||||||
|
author.replace(/^\w/, c => c.toLowerCase()),
|
||||||
|
},
|
||||||
parse: {
|
parse: {
|
||||||
author: (author: string) =>
|
author: (author: string) =>
|
||||||
author.replace(/^\w/, c => c.toUpperCase()),
|
author.replace(/^\w/, c => c.toUpperCase()),
|
||||||
@@ -250,9 +287,10 @@ it('handles parse in nested object with parse in it', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
)
|
};
|
||||||
).toEqual({
|
|
||||||
|
const state = {
|
||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
name: 'Bar',
|
name: 'Bar',
|
||||||
@@ -293,21 +331,32 @@ it('handles parse in nested object with parse in it', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
};
|
||||||
|
|
||||||
|
expect(getStateFromPath(path, config)).toEqual(state);
|
||||||
|
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
||||||
|
state
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles parse in nested object for second route depth', () => {
|
it('handles parse in nested object for second route depth', () => {
|
||||||
expect(
|
const path = '/baz';
|
||||||
getStateFromPath('/baz', {
|
const config = {
|
||||||
Foo: {
|
Foo: {
|
||||||
path: 'foo',
|
path: 'foo',
|
||||||
|
screens: {
|
||||||
Foe: 'foe',
|
Foe: 'foe',
|
||||||
Bar: {
|
Bar: {
|
||||||
|
path: 'bar',
|
||||||
|
screens: {
|
||||||
Baz: 'baz',
|
Baz: 'baz',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
},
|
||||||
).toEqual({
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const state = {
|
||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
name: 'Foo',
|
name: 'Foo',
|
||||||
@@ -323,28 +372,44 @@ it('handles parse in nested object for second route depth', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
};
|
||||||
|
|
||||||
|
expect(getStateFromPath(path, config)).toEqual(state);
|
||||||
|
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
||||||
|
state
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles parse in nested object for second route depth and and path and parse in roots', () => {
|
it('handles parse in nested object for second route depth and and path and parse in roots', () => {
|
||||||
expect(
|
const path = '/baz';
|
||||||
getStateFromPath('/baz', {
|
const config = {
|
||||||
Foo: {
|
Foo: {
|
||||||
path: 'foo/:id',
|
path: 'foo/:id',
|
||||||
parse: {
|
parse: {
|
||||||
id: Number,
|
id: Number,
|
||||||
},
|
},
|
||||||
|
stringify: {
|
||||||
|
id: (id: number) => `id=${id}`,
|
||||||
|
},
|
||||||
|
screens: {
|
||||||
Foe: 'foe',
|
Foe: 'foe',
|
||||||
Bar: {
|
Bar: {
|
||||||
path: 'bar/:id',
|
path: 'bar/:id',
|
||||||
parse: {
|
parse: {
|
||||||
id: Number,
|
id: Number,
|
||||||
},
|
},
|
||||||
|
stringify: {
|
||||||
|
id: (id: number) => `id=${id}`,
|
||||||
|
},
|
||||||
|
screens: {
|
||||||
Baz: 'baz',
|
Baz: 'baz',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
},
|
||||||
).toEqual({
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const state = {
|
||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
name: 'Foo',
|
name: 'Foo',
|
||||||
@@ -360,5 +425,41 @@ it('handles parse in nested object for second route depth and and path and parse
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
};
|
||||||
|
|
||||||
|
expect(getStateFromPath(path, config)).toEqual(state);
|
||||||
|
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
||||||
|
state
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns undefined if path is empty', () => {
|
||||||
|
const config = {
|
||||||
|
Foo: {
|
||||||
|
path: 'foo/:id',
|
||||||
|
starting: true,
|
||||||
|
stringify: {
|
||||||
|
id: (id: number) => `id=${id}`,
|
||||||
|
},
|
||||||
|
screens: {
|
||||||
|
Foe: 'foe',
|
||||||
|
Bar: {
|
||||||
|
path: 'bar/:id',
|
||||||
|
parse: {
|
||||||
|
id: Number,
|
||||||
|
},
|
||||||
|
stringify: {
|
||||||
|
id: (id: number) => `id=${id}`,
|
||||||
|
},
|
||||||
|
screens: {
|
||||||
|
Baz: 'baz',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const path = '';
|
||||||
|
|
||||||
|
expect(getStateFromPath(path, config)).toEqual(undefined);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -936,3 +936,138 @@ it('switches rendered navigators', () => {
|
|||||||
'Another navigator is already registered for this container.'
|
'Another navigator is already registered for this container.'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('throws if no name is passed to Screen', () => {
|
||||||
|
const TestNavigator = (props: any) => {
|
||||||
|
useNavigationBuilder(MockRouter, props);
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const element = (
|
||||||
|
<NavigationContainer>
|
||||||
|
<TestNavigator>
|
||||||
|
<Screen name={undefined as any} component={jest.fn()} />
|
||||||
|
</TestNavigator>
|
||||||
|
</NavigationContainer>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(() => render(element).update(element)).toThrowError(
|
||||||
|
'Got an invalid name (undefined) for the screen. It must be a non-empty string.'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws if invalid name is passed to Screen', () => {
|
||||||
|
const TestNavigator = (props: any) => {
|
||||||
|
useNavigationBuilder(MockRouter, props);
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const element = (
|
||||||
|
<NavigationContainer>
|
||||||
|
<TestNavigator>
|
||||||
|
<Screen name={[] as any} component={jest.fn()} />
|
||||||
|
</TestNavigator>
|
||||||
|
</NavigationContainer>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(() => render(element).update(element)).toThrowError(
|
||||||
|
'Got an invalid name ([]) for the screen. It must be a non-empty string.'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws if both children and component are passed', () => {
|
||||||
|
const TestNavigator = (props: any) => {
|
||||||
|
useNavigationBuilder(MockRouter, props);
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const element = (
|
||||||
|
<NavigationContainer>
|
||||||
|
<TestNavigator>
|
||||||
|
<Screen name="foo" component={jest.fn()}>
|
||||||
|
{jest.fn()}
|
||||||
|
</Screen>
|
||||||
|
</TestNavigator>
|
||||||
|
</NavigationContainer>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(() => render(element).update(element)).toThrowError(
|
||||||
|
"Got both 'component' and 'children' props for the screen 'foo'. You must pass only one of them."
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws descriptive error for undefined screen component', () => {
|
||||||
|
const TestNavigator = (props: any) => {
|
||||||
|
useNavigationBuilder(MockRouter, props);
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const element = (
|
||||||
|
<NavigationContainer>
|
||||||
|
<TestNavigator>
|
||||||
|
<Screen name="foo" component={undefined as any} />
|
||||||
|
</TestNavigator>
|
||||||
|
</NavigationContainer>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(() => render(element).update(element)).toThrowError(
|
||||||
|
"Couldn't find a 'component' or 'children' prop for the screen 'foo'"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws descriptive error for invalid screen component', () => {
|
||||||
|
const TestNavigator = (props: any) => {
|
||||||
|
useNavigationBuilder(MockRouter, props);
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const element = (
|
||||||
|
<NavigationContainer>
|
||||||
|
<TestNavigator>
|
||||||
|
<Screen name="foo" component={{} as any} />
|
||||||
|
</TestNavigator>
|
||||||
|
</NavigationContainer>
|
||||||
|
);
|
||||||
|
|
||||||
|
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."
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws descriptive error for invalid children', () => {
|
||||||
|
const TestNavigator = (props: any) => {
|
||||||
|
useNavigationBuilder(MockRouter, props);
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const element = (
|
||||||
|
<NavigationContainer>
|
||||||
|
<TestNavigator>
|
||||||
|
<Screen name="foo">{[] as any}</Screen>
|
||||||
|
</TestNavigator>
|
||||||
|
</NavigationContainer>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(() => render(element).update(element)).toThrowError(
|
||||||
|
"Got an invalid value for 'children' prop for the screen 'foo'. It must be a function returning a React Element."
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("doesn't throw if children is null", () => {
|
||||||
|
const TestNavigator = (props: any) => {
|
||||||
|
useNavigationBuilder(MockRouter, props);
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const element = (
|
||||||
|
<NavigationContainer>
|
||||||
|
<TestNavigator>
|
||||||
|
<Screen name="foo" component={jest.fn()}>
|
||||||
|
{null as any}
|
||||||
|
</Screen>
|
||||||
|
</TestNavigator>
|
||||||
|
</NavigationContainer>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(() => render(element).update(element)).not.toThrowError();
|
||||||
|
});
|
||||||
|
|||||||
70
packages/core/src/__tests__/isSerializable.test.tsx
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import isSerializable from '../isSerializable';
|
||||||
|
|
||||||
|
it('returns true for serializable object', () => {
|
||||||
|
expect(
|
||||||
|
isSerializable({
|
||||||
|
index: 0,
|
||||||
|
key: '7',
|
||||||
|
routeNames: ['foo', 'bar'],
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
key: 'foo',
|
||||||
|
name: 'foo',
|
||||||
|
state: {
|
||||||
|
index: 0,
|
||||||
|
key: '8',
|
||||||
|
routeNames: ['qux', 'lex'],
|
||||||
|
routes: [
|
||||||
|
{ key: 'qux', name: 'qux' },
|
||||||
|
{ key: 'lex', name: 'lex' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns false for non-serializable object', () => {
|
||||||
|
expect(
|
||||||
|
isSerializable({
|
||||||
|
index: 0,
|
||||||
|
key: '7',
|
||||||
|
routeNames: ['foo', 'bar'],
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
key: 'foo',
|
||||||
|
name: 'foo',
|
||||||
|
state: {
|
||||||
|
index: 0,
|
||||||
|
key: '8',
|
||||||
|
routeNames: ['qux', 'lex'],
|
||||||
|
routes: [
|
||||||
|
{ key: 'qux', name: 'qux', params: () => 42 },
|
||||||
|
{ key: 'lex', name: 'lex' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns false for circular references', () => {
|
||||||
|
const o = {
|
||||||
|
index: 0,
|
||||||
|
key: '7',
|
||||||
|
routeNames: ['foo', 'bar'],
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
key: 'foo',
|
||||||
|
name: 'foo',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
o.routes[0].state = o;
|
||||||
|
|
||||||
|
expect(isSerializable(o)).toBe(false);
|
||||||
|
});
|
||||||
@@ -592,7 +592,7 @@ it('returns true for canGoBack when parent router handles GO_BACK', () => {
|
|||||||
<Screen name="qux">
|
<Screen name="qux">
|
||||||
{() => (
|
{() => (
|
||||||
<OverrodeNavigator>
|
<OverrodeNavigator>
|
||||||
<Screen name="qux" component={() => null} />
|
<Screen name="qux">{() => null}</Screen>
|
||||||
</OverrodeNavigator>
|
</OverrodeNavigator>
|
||||||
)}
|
)}
|
||||||
</Screen>
|
</Screen>
|
||||||
|
|||||||
@@ -391,6 +391,8 @@ it('fires custom events', () => {
|
|||||||
expect(thirdCallback).toBeCalledTimes(1);
|
expect(thirdCallback).toBeCalledTimes(1);
|
||||||
expect(thirdCallback.mock.calls[0][0].type).toBe('someSuperCoolEvent');
|
expect(thirdCallback.mock.calls[0][0].type).toBe('someSuperCoolEvent');
|
||||||
expect(thirdCallback.mock.calls[0][0].data).toBe(42);
|
expect(thirdCallback.mock.calls[0][0].data).toBe(42);
|
||||||
|
expect(thirdCallback.mock.calls[0][0].defaultPrevented).toBe(undefined);
|
||||||
|
expect(thirdCallback.mock.calls[0][0].preventDefault).toBe(undefined);
|
||||||
|
|
||||||
act(() => {
|
act(() => {
|
||||||
ref.current.navigation.emit({ type: eventName });
|
ref.current.navigation.emit({ type: eventName });
|
||||||
@@ -400,3 +402,62 @@ it('fires custom events', () => {
|
|||||||
expect(secondCallback).toBeCalledTimes(1);
|
expect(secondCallback).toBeCalledTimes(1);
|
||||||
expect(thirdCallback).toBeCalledTimes(2);
|
expect(thirdCallback).toBeCalledTimes(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('has option to prevent default', () => {
|
||||||
|
expect.assertions(5);
|
||||||
|
|
||||||
|
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 = (e: any) => {
|
||||||
|
expect(e.type).toBe('someSuperCoolEvent');
|
||||||
|
expect(e.data).toBe(42);
|
||||||
|
expect(e.defaultPrevented).toBe(false);
|
||||||
|
expect(e.preventDefault).not.toBe(undefined);
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
expect(e.defaultPrevented).toBe(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Test = ({ navigation }: any) => {
|
||||||
|
React.useEffect(() => navigation.addListener(eventName, callback), [
|
||||||
|
navigation,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ref = React.createRef<any>();
|
||||||
|
|
||||||
|
const element = (
|
||||||
|
<NavigationContainer>
|
||||||
|
<TestNavigator ref={ref}>
|
||||||
|
<Screen name="first" component={Test} />
|
||||||
|
</TestNavigator>
|
||||||
|
</NavigationContainer>
|
||||||
|
);
|
||||||
|
|
||||||
|
render(element);
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
ref.current.navigation.emit({
|
||||||
|
type: eventName,
|
||||||
|
data: 42,
|
||||||
|
canPreventDefault: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
58
packages/core/src/__tests__/useNavigationCache.test.tsx
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { render } from 'react-native-testing-library';
|
||||||
|
import useEventEmitter from '../useEventEmitter';
|
||||||
|
import useNavigationCache from '../useNavigationCache';
|
||||||
|
import MockRouter, { MockRouterKey } from './__fixtures__/MockRouter';
|
||||||
|
|
||||||
|
beforeEach(() => (MockRouterKey.current = 0));
|
||||||
|
|
||||||
|
it('preserves reference for navigation objects', () => {
|
||||||
|
expect.assertions(2);
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
type: 'tab',
|
||||||
|
stale: false as const,
|
||||||
|
index: 1,
|
||||||
|
key: 'State',
|
||||||
|
routeNames: ['Foo', 'Bar'],
|
||||||
|
routes: [
|
||||||
|
{ key: 'Foo', name: 'Foo' },
|
||||||
|
{ key: 'Bar', name: 'Bar' },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const getState = () => state;
|
||||||
|
const navigation = {} as any;
|
||||||
|
const setOptions = (() => {}) as any;
|
||||||
|
const router = MockRouter({});
|
||||||
|
|
||||||
|
const Test = () => {
|
||||||
|
const previous = React.useRef<any>();
|
||||||
|
|
||||||
|
const emitter = useEventEmitter();
|
||||||
|
const navigations = useNavigationCache({
|
||||||
|
state,
|
||||||
|
getState,
|
||||||
|
navigation,
|
||||||
|
setOptions,
|
||||||
|
router,
|
||||||
|
emitter,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (previous.current) {
|
||||||
|
Object.keys(navigations).forEach(key => {
|
||||||
|
expect(navigations[key]).toBe(previous.current[key]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
previous.current = navigations;
|
||||||
|
});
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const root = render(<Test />);
|
||||||
|
|
||||||
|
root.update(<Test />);
|
||||||
|
});
|
||||||
156
packages/core/src/__tests__/useNavigationState.test.tsx
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { render, act } from 'react-native-testing-library';
|
||||||
|
import useNavigationBuilder from '../useNavigationBuilder';
|
||||||
|
import useNavigationState from '../useNavigationState';
|
||||||
|
import NavigationContainer from '../NavigationContainer';
|
||||||
|
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());
|
||||||
|
};
|
||||||
|
|
||||||
|
const callback = jest.fn();
|
||||||
|
|
||||||
|
const Test = () => {
|
||||||
|
const state = useNavigationState(state => state);
|
||||||
|
|
||||||
|
callback(state);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const navigation = React.createRef<any>();
|
||||||
|
|
||||||
|
const element = (
|
||||||
|
<NavigationContainer ref={navigation}>
|
||||||
|
<TestNavigator>
|
||||||
|
<Screen name="first" component={Test} />
|
||||||
|
<Screen name="second">{() => null}</Screen>
|
||||||
|
<Screen name="third">{() => null}</Screen>
|
||||||
|
</TestNavigator>
|
||||||
|
</NavigationContainer>
|
||||||
|
);
|
||||||
|
|
||||||
|
render(element);
|
||||||
|
|
||||||
|
expect(callback).toBeCalledTimes(1);
|
||||||
|
expect(callback.mock.calls[0][0].index).toBe(0);
|
||||||
|
|
||||||
|
act(() => navigation.current.navigate('second'));
|
||||||
|
|
||||||
|
expect(callback).toBeCalledTimes(2);
|
||||||
|
expect(callback.mock.calls[1][0].index).toBe(1);
|
||||||
|
|
||||||
|
act(() => navigation.current.navigate('third'));
|
||||||
|
|
||||||
|
expect(callback).toBeCalledTimes(3);
|
||||||
|
expect(callback.mock.calls[2][0].index).toBe(2);
|
||||||
|
|
||||||
|
act(() => navigation.current.navigate('second', { answer: 42 }));
|
||||||
|
|
||||||
|
expect(callback).toBeCalledTimes(4);
|
||||||
|
expect(callback.mock.calls[3][0].index).toBe(1);
|
||||||
|
expect(callback.mock.calls[3][0].routes[1].params).toEqual({ answer: 42 });
|
||||||
|
});
|
||||||
|
|
||||||
|
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());
|
||||||
|
};
|
||||||
|
|
||||||
|
const callback = jest.fn();
|
||||||
|
|
||||||
|
const Test = () => {
|
||||||
|
const index = useNavigationState(state => state.index);
|
||||||
|
|
||||||
|
callback(index);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const navigation = React.createRef<any>();
|
||||||
|
|
||||||
|
const element = (
|
||||||
|
<NavigationContainer ref={navigation}>
|
||||||
|
<TestNavigator>
|
||||||
|
<Screen name="first" component={Test} />
|
||||||
|
<Screen name="second">{() => null}</Screen>
|
||||||
|
<Screen name="third">{() => null}</Screen>
|
||||||
|
</TestNavigator>
|
||||||
|
</NavigationContainer>
|
||||||
|
);
|
||||||
|
|
||||||
|
render(element);
|
||||||
|
|
||||||
|
expect(callback).toBeCalledTimes(1);
|
||||||
|
expect(callback.mock.calls[0][0]).toBe(0);
|
||||||
|
|
||||||
|
act(() => navigation.current.navigate('second'));
|
||||||
|
|
||||||
|
expect(callback).toBeCalledTimes(2);
|
||||||
|
expect(callback.mock.calls[1][0]).toBe(1);
|
||||||
|
|
||||||
|
act(() => navigation.current.navigate('third'));
|
||||||
|
|
||||||
|
expect(callback).toBeCalledTimes(3);
|
||||||
|
expect(callback.mock.calls[1][0]).toBe(1);
|
||||||
|
|
||||||
|
act(() => navigation.current.navigate('second'));
|
||||||
|
|
||||||
|
expect(callback).toBeCalledTimes(4);
|
||||||
|
expect(callback.mock.calls[3][0]).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
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());
|
||||||
|
};
|
||||||
|
|
||||||
|
const callback = jest.fn();
|
||||||
|
|
||||||
|
const SelectorContext = React.createContext<any>(null);
|
||||||
|
|
||||||
|
const Test = () => {
|
||||||
|
const selector = React.useContext(SelectorContext);
|
||||||
|
const result = useNavigationState(selector);
|
||||||
|
|
||||||
|
callback(result);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const navigation = React.createRef<any>();
|
||||||
|
|
||||||
|
const App = ({ selector }: { selector: (state: NavigationState) => any }) => {
|
||||||
|
return (
|
||||||
|
<SelectorContext.Provider value={selector}>
|
||||||
|
<NavigationContainer ref={navigation}>
|
||||||
|
<TestNavigator>
|
||||||
|
<Screen name="first" component={Test} />
|
||||||
|
<Screen name="second">{() => null}</Screen>
|
||||||
|
<Screen name="third">{() => null}</Screen>
|
||||||
|
</TestNavigator>
|
||||||
|
</NavigationContainer>
|
||||||
|
</SelectorContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
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} />);
|
||||||
|
|
||||||
|
expect(callback).toBeCalledTimes(2);
|
||||||
|
expect(callback.mock.calls[1][0]).toBe('first');
|
||||||
|
});
|
||||||
@@ -181,8 +181,8 @@ it("lets children handle the action if parent didn't", () => {
|
|||||||
<Screen name="baz">
|
<Screen name="baz">
|
||||||
{() => (
|
{() => (
|
||||||
<ChildNavigator>
|
<ChildNavigator>
|
||||||
<Screen name="qux" component={() => null} />
|
<Screen name="qux">{() => null}</Screen>
|
||||||
<Screen name="lex" component={() => null} />
|
<Screen name="lex">{() => null}</Screen>
|
||||||
</ChildNavigator>
|
</ChildNavigator>
|
||||||
)}
|
)}
|
||||||
</Screen>
|
</Screen>
|
||||||
@@ -291,8 +291,8 @@ it("action doesn't bubble if target is specified", () => {
|
|||||||
<Screen name="baz">
|
<Screen name="baz">
|
||||||
{() => (
|
{() => (
|
||||||
<ChildNavigator>
|
<ChildNavigator>
|
||||||
<Screen name="qux" component={() => null} />
|
<Screen name="qux">{() => null}</Screen>
|
||||||
<Screen name="lex" component={() => null} />
|
<Screen name="lex">{() => null}</Screen>
|
||||||
</ChildNavigator>
|
</ChildNavigator>
|
||||||
)}
|
)}
|
||||||
</Screen>
|
</Screen>
|
||||||
@@ -356,8 +356,8 @@ it('logs error if no navigator handled the action', () => {
|
|||||||
<Screen name="baz">
|
<Screen name="baz">
|
||||||
{() => (
|
{() => (
|
||||||
<TestNavigator>
|
<TestNavigator>
|
||||||
<Screen name="qux" component={() => null} />
|
<Screen name="qux">{() => null}</Screen>
|
||||||
<Screen name="lex" component={() => null} />
|
<Screen name="lex">{() => null}</Screen>
|
||||||
</TestNavigator>
|
</TestNavigator>
|
||||||
)}
|
)}
|
||||||
</Screen>
|
</Screen>
|
||||||
|
|||||||
@@ -13,11 +13,17 @@ export default function createNavigatorFactory<
|
|||||||
ScreenOptions extends object,
|
ScreenOptions extends object,
|
||||||
NavigatorComponent extends React.ComponentType<any>
|
NavigatorComponent extends React.ComponentType<any>
|
||||||
>(Navigator: NavigatorComponent) {
|
>(Navigator: NavigatorComponent) {
|
||||||
return <ParamList extends ParamListBase>(): TypedNavigator<
|
return function<ParamList extends ParamListBase>(): TypedNavigator<
|
||||||
ParamList,
|
ParamList,
|
||||||
ScreenOptions,
|
ScreenOptions,
|
||||||
typeof Navigator
|
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?"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
Navigator,
|
Navigator,
|
||||||
Screen,
|
Screen,
|
||||||
|
|||||||
@@ -6,7 +6,13 @@ type State = NavigationState | Omit<PartialState<NavigationState>, 'stale'>;
|
|||||||
type StringifyConfig = Record<string, (value: any) => string>;
|
type StringifyConfig = Record<string, (value: any) => string>;
|
||||||
|
|
||||||
type Options = {
|
type Options = {
|
||||||
[routeName: string]: string | { path: string; stringify?: StringifyConfig };
|
[routeName: string]:
|
||||||
|
| string
|
||||||
|
| {
|
||||||
|
path: string;
|
||||||
|
stringify?: StringifyConfig;
|
||||||
|
screens?: Options;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -37,22 +43,50 @@ type Options = {
|
|||||||
* @returns Path representing the state, e.g. /foo/bar?count=42.
|
* @returns Path representing the state, e.g. /foo/bar?count=42.
|
||||||
*/
|
*/
|
||||||
export default function getPathFromState(
|
export default function getPathFromState(
|
||||||
state: State,
|
state?: State,
|
||||||
options: Options = {}
|
options: Options = {}
|
||||||
): string {
|
): string {
|
||||||
|
if (state === undefined) {
|
||||||
|
throw Error('NavigationState not passed');
|
||||||
|
}
|
||||||
let path = '/';
|
let path = '/';
|
||||||
|
|
||||||
let current: State | undefined = state;
|
let current: State | undefined = state;
|
||||||
|
|
||||||
while (current) {
|
while (current) {
|
||||||
const index = typeof current.index === 'number' ? current.index : 0;
|
let index = typeof current.index === 'number' ? current.index : 0;
|
||||||
const route = current.routes[index] as Route<string> & {
|
let route = current.routes[index] as Route<string> & {
|
||||||
state?: State | undefined;
|
state?: State;
|
||||||
};
|
};
|
||||||
|
let currentOptions = options;
|
||||||
|
let pattern = route.name;
|
||||||
|
|
||||||
|
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) {
|
||||||
|
pattern = (currentOptions[route.name] as { path: string }).path;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
if (!(currentOptions[route.name] as { screens?: Options }).screens) {
|
||||||
|
throw Error('Wrong Options object passed');
|
||||||
|
}
|
||||||
|
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 =
|
const config =
|
||||||
options[route.name] !== undefined
|
currentOptions[route.name] !== undefined
|
||||||
? (options[route.name] as { stringify?: StringifyConfig }).stringify
|
? (currentOptions[route.name] as { stringify?: StringifyConfig })
|
||||||
|
.stringify
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const params = route.params
|
const params = route.params
|
||||||
@@ -65,12 +99,7 @@ export default function getPathFromState(
|
|||||||
}, {})
|
}, {})
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
if (options[route.name] !== undefined) {
|
if (currentOptions[route.name] !== undefined) {
|
||||||
const pattern =
|
|
||||||
typeof options[route.name] === 'string'
|
|
||||||
? (options[route.name] as string)
|
|
||||||
: (options[route.name] as { path: string }).path;
|
|
||||||
|
|
||||||
path += pattern
|
path += pattern
|
||||||
.split('/')
|
.split('/')
|
||||||
.map(p => {
|
.map(p => {
|
||||||
@@ -95,7 +124,11 @@ export default function getPathFromState(
|
|||||||
if (route.state) {
|
if (route.state) {
|
||||||
path += '/';
|
path += '/';
|
||||||
} else if (params) {
|
} else if (params) {
|
||||||
path += `?${queryString.stringify(params)}`;
|
const query = queryString.stringify(params);
|
||||||
|
|
||||||
|
if (query) {
|
||||||
|
path += `?${query}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
current = route.state;
|
current = route.state;
|
||||||
|
|||||||
@@ -5,14 +5,20 @@ import { NavigationState, PartialState, InitialState } from './types';
|
|||||||
type ParseConfig = Record<string, (value: string) => any>;
|
type ParseConfig = Record<string, (value: string) => any>;
|
||||||
|
|
||||||
type Options = {
|
type Options = {
|
||||||
[routeName: string]: string | { path: string; parse?: ParseConfig } | Options;
|
[routeName: string]:
|
||||||
|
| string
|
||||||
|
| {
|
||||||
|
path: string;
|
||||||
|
parse?: ParseConfig;
|
||||||
|
screens?: Options;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
type RouteConfig = {
|
type RouteConfig = {
|
||||||
match: RegExp;
|
match: RegExp;
|
||||||
pattern: string;
|
pattern: string;
|
||||||
routeNames: string[];
|
routeNames: string[];
|
||||||
parse: Record<string, (value: string) => any> | undefined;
|
parse: ParseConfig | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ResultState = PartialState<NavigationState> & {
|
type ResultState = PartialState<NavigationState> & {
|
||||||
@@ -42,6 +48,9 @@ export default function getStateFromPath(
|
|||||||
path: string,
|
path: string,
|
||||||
options: Options = {}
|
options: Options = {}
|
||||||
): ResultState | undefined {
|
): ResultState | undefined {
|
||||||
|
if (path === '') {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
// Create a normalized configs array which will be easier to use
|
// Create a normalized configs array which will be easier to use
|
||||||
const configs = ([] as RouteConfig[]).concat(
|
const configs = ([] as RouteConfig[]).concat(
|
||||||
...Object.keys(options).map(key => createNormalizedConfigs(key, options))
|
...Object.keys(options).map(key => createNormalizedConfigs(key, options))
|
||||||
@@ -65,7 +74,7 @@ export default function getStateFromPath(
|
|||||||
|
|
||||||
// If our regex matches, we need to extract params from the path
|
// If our regex matches, we need to extract params from the path
|
||||||
if (match) {
|
if (match) {
|
||||||
routeNames = config.routeNames;
|
routeNames = [...config.routeNames];
|
||||||
|
|
||||||
const paramPatterns = config.pattern
|
const paramPatterns = config.pattern
|
||||||
.split('/')
|
.split('/')
|
||||||
@@ -164,7 +173,7 @@ export default function getStateFromPath(
|
|||||||
const route = current.routes[0];
|
const route = current.routes[0];
|
||||||
|
|
||||||
const params = queryString.parse(query);
|
const params = queryString.parse(query);
|
||||||
const parseFunction = findParseConfigForRoute(route.name, options);
|
const parseFunction = findParseConfigForRoute(route.name, configs);
|
||||||
|
|
||||||
if (parseFunction) {
|
if (parseFunction) {
|
||||||
Object.keys(params).forEach(name => {
|
Object.keys(params).forEach(name => {
|
||||||
@@ -185,7 +194,7 @@ function createNormalizedConfigs(
|
|||||||
routeConfig: Options,
|
routeConfig: Options,
|
||||||
routeNames: string[] = []
|
routeNames: string[] = []
|
||||||
): RouteConfig[] {
|
): RouteConfig[] {
|
||||||
const configs = [];
|
const configs: RouteConfig[] = [];
|
||||||
|
|
||||||
routeNames.push(key);
|
routeNames.push(key);
|
||||||
|
|
||||||
@@ -196,31 +205,20 @@ function createNormalizedConfigs(
|
|||||||
configs.push(createConfigItem(routeNames, value));
|
configs.push(createConfigItem(routeNames, value));
|
||||||
} else if (typeof value === 'object') {
|
} else if (typeof value === 'object') {
|
||||||
// if an object is specified as the value (e.g. Foo: { ... }),
|
// if an object is specified as the value (e.g. Foo: { ... }),
|
||||||
// it could have config object and optionally nested config
|
// it has `path` property and
|
||||||
Object.keys(value).forEach(nestedKey => {
|
// it could have `screens` prop which has nested configs
|
||||||
if (nestedKey === 'path') {
|
configs.push(createConfigItem(routeNames, value.path, value.parse));
|
||||||
configs.push(
|
if (value.screens) {
|
||||||
createConfigItem(
|
Object.keys(value.screens).forEach(nestedConfig => {
|
||||||
routeNames,
|
|
||||||
value[nestedKey] as string,
|
|
||||||
value.parse ? (value.parse as ParseConfig) : undefined
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} else if (nestedKey === 'parse') {
|
|
||||||
// We handle custom parse function when a `path` is specified (in nestedKey === path)
|
|
||||||
} else {
|
|
||||||
// If the name of the key is not `path` or `parse`, it's a nested config for route
|
|
||||||
// So we need to traverse into it and collect the configs
|
|
||||||
const result = createNormalizedConfigs(
|
const result = createNormalizedConfigs(
|
||||||
nestedKey,
|
nestedConfig,
|
||||||
routeConfig[key] as Options,
|
value.screens as Options,
|
||||||
routeNames
|
routeNames
|
||||||
);
|
);
|
||||||
|
|
||||||
configs.push(...result);
|
configs.push(...result);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
routeNames.pop();
|
routeNames.pop();
|
||||||
|
|
||||||
@@ -247,21 +245,12 @@ function createConfigItem(
|
|||||||
|
|
||||||
function findParseConfigForRoute(
|
function findParseConfigForRoute(
|
||||||
routeName: string,
|
routeName: string,
|
||||||
config: Options
|
flatConfig: RouteConfig[]
|
||||||
): ParseConfig | undefined {
|
): ParseConfig | undefined {
|
||||||
if (config[routeName]) {
|
for (const config of flatConfig) {
|
||||||
return (config[routeName] as { parse?: ParseConfig }).parse;
|
if (routeName === config.routeNames[config.routeNames.length - 1]) {
|
||||||
}
|
return config.parse;
|
||||||
|
|
||||||
for (const name in config) {
|
|
||||||
if (typeof config[name] === 'object') {
|
|
||||||
const parse = findParseConfigForRoute(routeName, config[name] as Options);
|
|
||||||
|
|
||||||
if (parse) {
|
|
||||||
return parse;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export { default as useNavigation } from './useNavigation';
|
|||||||
export { default as useRoute } from './useRoute';
|
export { default as useRoute } from './useRoute';
|
||||||
export { default as useFocusEffect } from './useFocusEffect';
|
export { default as useFocusEffect } from './useFocusEffect';
|
||||||
export { default as useIsFocused } from './useIsFocused';
|
export { default as useIsFocused } from './useIsFocused';
|
||||||
|
export { default as useNavigationState } from './useNavigationState';
|
||||||
|
|
||||||
export { default as getStateFromPath } from './getStateFromPath';
|
export { default as getStateFromPath } from './getStateFromPath';
|
||||||
export { default as getPathFromState } from './getPathFromState';
|
export { default as getPathFromState } from './getPathFromState';
|
||||||
|
|||||||
47
packages/core/src/isSerializable.tsx
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
const isSerializableWithoutCircularReference = (
|
||||||
|
o: { [key: string]: any },
|
||||||
|
seen = new Set<any>()
|
||||||
|
): boolean => {
|
||||||
|
if (
|
||||||
|
o === undefined ||
|
||||||
|
o === null ||
|
||||||
|
typeof o === 'boolean' ||
|
||||||
|
typeof o === 'number' ||
|
||||||
|
typeof o === 'string'
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
Object.prototype.toString.call(o) !== '[object Object]' &&
|
||||||
|
!Array.isArray(o)
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (seen.has(o)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
seen.add(o);
|
||||||
|
|
||||||
|
if (Array.isArray(o)) {
|
||||||
|
for (const it of o) {
|
||||||
|
if (!isSerializableWithoutCircularReference(it, seen)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (const key in o) {
|
||||||
|
if (!isSerializableWithoutCircularReference(o[key], seen)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function isSerializable(o: { [key: string]: any }) {
|
||||||
|
return isSerializableWithoutCircularReference(o);
|
||||||
|
}
|
||||||
@@ -16,6 +16,10 @@ export type NavigationState = {
|
|||||||
* List of valid route names as defined in the screen components.
|
* List of valid route names as defined in the screen components.
|
||||||
*/
|
*/
|
||||||
routeNames: string[];
|
routeNames: string[];
|
||||||
|
/**
|
||||||
|
* Alternative entries for history.
|
||||||
|
*/
|
||||||
|
history?: unknown[];
|
||||||
/**
|
/**
|
||||||
* List of rendered routes.
|
* List of rendered routes.
|
||||||
*/
|
*/
|
||||||
@@ -207,16 +211,28 @@ export type Router<
|
|||||||
|
|
||||||
export type ParamListBase = Record<string, object | undefined>;
|
export type ParamListBase = Record<string, object | undefined>;
|
||||||
|
|
||||||
export type EventMapBase = {
|
export type EventMapBase = Record<
|
||||||
focus: undefined;
|
string,
|
||||||
blur: undefined;
|
{ data?: any; canPreventDefault?: boolean }
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type EventMapCore<State extends NavigationState> = {
|
||||||
|
focus: { data: undefined };
|
||||||
|
blur: { data: undefined };
|
||||||
|
state: { data: { state: State } };
|
||||||
};
|
};
|
||||||
|
|
||||||
export type EventArg<EventName extends string, Data = undefined> = {
|
export type EventArg<
|
||||||
|
EventName extends string,
|
||||||
|
CanPreventDefault extends boolean | undefined = false,
|
||||||
|
Data = undefined
|
||||||
|
> = {
|
||||||
/**
|
/**
|
||||||
* Type of the event (e.g. `focus`, `blur`)
|
* Type of the event (e.g. `focus`, `blur`)
|
||||||
*/
|
*/
|
||||||
readonly type: EventName;
|
readonly type: EventName;
|
||||||
|
} & (CanPreventDefault extends true
|
||||||
|
? {
|
||||||
/**
|
/**
|
||||||
* Whether `event.preventDefault()` was called on this event object.
|
* Whether `event.preventDefault()` was called on this event object.
|
||||||
*/
|
*/
|
||||||
@@ -225,13 +241,22 @@ export type EventArg<EventName extends string, Data = undefined> = {
|
|||||||
* Prevent the default action which happens on this event.
|
* Prevent the default action which happens on this event.
|
||||||
*/
|
*/
|
||||||
preventDefault(): void;
|
preventDefault(): void;
|
||||||
} & (Data extends undefined ? {} : { readonly data: Data });
|
}
|
||||||
|
: {}) &
|
||||||
|
(Data extends undefined ? {} : { readonly data: Data });
|
||||||
|
|
||||||
export type EventListenerCallback<EventName extends string, Data> = (
|
export type EventListenerCallback<
|
||||||
e: EventArg<EventName, Data>
|
EventMap extends EventMapBase,
|
||||||
|
EventName extends keyof EventMap
|
||||||
|
> = (
|
||||||
|
e: EventArg<
|
||||||
|
Extract<EventName, string>,
|
||||||
|
EventMap[EventName]['canPreventDefault'],
|
||||||
|
EventMap[EventName]['data']
|
||||||
|
>
|
||||||
) => void;
|
) => void;
|
||||||
|
|
||||||
export type EventConsumer<EventMap extends Record<string, any>> = {
|
export type EventConsumer<EventMap extends EventMapBase> = {
|
||||||
/**
|
/**
|
||||||
* Subscribe to events from the parent navigator.
|
* Subscribe to events from the parent navigator.
|
||||||
*
|
*
|
||||||
@@ -240,15 +265,15 @@ export type EventConsumer<EventMap extends Record<string, any>> = {
|
|||||||
*/
|
*/
|
||||||
addListener<EventName extends Extract<keyof EventMap, string>>(
|
addListener<EventName extends Extract<keyof EventMap, string>>(
|
||||||
type: EventName,
|
type: EventName,
|
||||||
callback: EventListenerCallback<EventName, EventMap[EventName]>
|
callback: EventListenerCallback<EventMap, EventName>
|
||||||
): () => void;
|
): () => void;
|
||||||
removeListener<EventName extends Extract<keyof EventMap, string>>(
|
removeListener<EventName extends Extract<keyof EventMap, string>>(
|
||||||
type: EventName,
|
type: EventName,
|
||||||
callback: EventListenerCallback<EventName, EventMap[EventName]>
|
callback: EventListenerCallback<EventMap, EventName>
|
||||||
): void;
|
): void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type EventEmitter<EventMap extends Record<string, any>> = {
|
export type EventEmitter<EventMap extends EventMapBase> = {
|
||||||
/**
|
/**
|
||||||
* Emit an event to child screens.
|
* Emit an event to child screens.
|
||||||
*
|
*
|
||||||
@@ -261,10 +286,17 @@ export type EventEmitter<EventMap extends Record<string, any>> = {
|
|||||||
options: {
|
options: {
|
||||||
type: EventName;
|
type: EventName;
|
||||||
target?: string;
|
target?: string;
|
||||||
} & (EventMap[EventName] extends undefined
|
} & (EventMap[EventName]['canPreventDefault'] extends true
|
||||||
|
? { canPreventDefault: true }
|
||||||
|
: {}) &
|
||||||
|
(EventMap[EventName]['data'] extends undefined
|
||||||
? {}
|
? {}
|
||||||
: { data: EventMap[EventName] })
|
: { data: EventMap[EventName]['data'] })
|
||||||
): EventArg<EventName, EventMap[EventName]>;
|
): EventArg<
|
||||||
|
EventName,
|
||||||
|
EventMap[EventName]['canPreventDefault'],
|
||||||
|
EventMap[EventName]['data']
|
||||||
|
>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class PrivateValueStore<A, B, C> {
|
export class PrivateValueStore<A, B, C> {
|
||||||
@@ -277,10 +309,8 @@ export class PrivateValueStore<A, B, C> {
|
|||||||
* The problem with a normal property is that it shows up in intelliSense.
|
* The problem with a normal property is that it shows up in intelliSense.
|
||||||
* Adding private keyword works, but the annotation is stripped away in declaration.
|
* Adding private keyword works, but the annotation is stripped away in declaration.
|
||||||
* Turns out if we use an empty string, it doesn't show up in intelliSense.
|
* Turns out if we use an empty string, it doesn't show up in intelliSense.
|
||||||
*
|
|
||||||
* @protected
|
|
||||||
*/
|
*/
|
||||||
''?: { a: A; b: B; c: C };
|
protected ''?: { a: A; b: B; c: C };
|
||||||
}
|
}
|
||||||
|
|
||||||
type NavigationHelpersCommon<
|
type NavigationHelpersCommon<
|
||||||
@@ -339,13 +369,6 @@ type NavigationHelpersCommon<
|
|||||||
*/
|
*/
|
||||||
reset(state: PartialState<State> | State): void;
|
reset(state: PartialState<State> | State): void;
|
||||||
|
|
||||||
/**
|
|
||||||
* Reset the navigation state of the root navigator to the provided state.
|
|
||||||
*
|
|
||||||
* @param state Navigation state object.
|
|
||||||
*/
|
|
||||||
resetRoot(state?: PartialState<NavigationState> | NavigationState): void;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Go back to the previous route in history.
|
* Go back to the previous route in history.
|
||||||
*/
|
*/
|
||||||
@@ -368,7 +391,7 @@ type NavigationHelpersCommon<
|
|||||||
|
|
||||||
export type NavigationHelpers<
|
export type NavigationHelpers<
|
||||||
ParamList extends ParamListBase,
|
ParamList extends ParamListBase,
|
||||||
EventMap extends Record<string, any> = {}
|
EventMap extends EventMapBase = {}
|
||||||
> = NavigationHelpersCommon<ParamList> &
|
> = NavigationHelpersCommon<ParamList> &
|
||||||
EventEmitter<EventMap> & {
|
EventEmitter<EventMap> & {
|
||||||
/**
|
/**
|
||||||
@@ -408,7 +431,7 @@ export type NavigationProp<
|
|||||||
RouteName extends keyof ParamList = string,
|
RouteName extends keyof ParamList = string,
|
||||||
State extends NavigationState = NavigationState,
|
State extends NavigationState = NavigationState,
|
||||||
ScreenOptions extends object = {},
|
ScreenOptions extends object = {},
|
||||||
EventMap extends Record<string, any> = {}
|
EventMap extends EventMapBase = {}
|
||||||
> = NavigationHelpersCommon<ParamList, State> & {
|
> = NavigationHelpersCommon<ParamList, State> & {
|
||||||
/**
|
/**
|
||||||
* Update the param object for the route.
|
* Update the param object for the route.
|
||||||
@@ -439,7 +462,7 @@ export type NavigationProp<
|
|||||||
* Note that this method doesn't re-render screen when the result changes. So don't use it in `render`.
|
* Note that this method doesn't re-render screen when the result changes. So don't use it in `render`.
|
||||||
*/
|
*/
|
||||||
dangerouslyGetState(): State;
|
dangerouslyGetState(): State;
|
||||||
} & EventConsumer<EventMap & EventMapBase> &
|
} & EventConsumer<EventMap & EventMapCore<State>> &
|
||||||
PrivateValueStore<ParamList, RouteName, EventMap>;
|
PrivateValueStore<ParamList, RouteName, EventMap>;
|
||||||
|
|
||||||
export type RouteProp<
|
export type RouteProp<
|
||||||
@@ -493,7 +516,7 @@ export type Descriptor<
|
|||||||
RouteName extends keyof ParamList = string,
|
RouteName extends keyof ParamList = string,
|
||||||
State extends NavigationState = NavigationState,
|
State extends NavigationState = NavigationState,
|
||||||
ScreenOptions extends object = {},
|
ScreenOptions extends object = {},
|
||||||
EventMap extends Record<string, any> = {}
|
EventMap extends EventMapBase = {}
|
||||||
> = {
|
> = {
|
||||||
/**
|
/**
|
||||||
* Render the component associated with this route.
|
* Render the component associated with this route.
|
||||||
@@ -546,10 +569,7 @@ export type RouteConfig<
|
|||||||
/**
|
/**
|
||||||
* React component to render for this screen.
|
* React component to render for this screen.
|
||||||
*/
|
*/
|
||||||
component: React.ComponentType<{
|
component: React.ComponentType<any>;
|
||||||
route: RouteProp<ParamList, RouteName>;
|
|
||||||
navigation: any;
|
|
||||||
}>;
|
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
/**
|
/**
|
||||||
@@ -563,13 +583,19 @@ export type RouteConfig<
|
|||||||
);
|
);
|
||||||
|
|
||||||
export type NavigationContainerRef =
|
export type NavigationContainerRef =
|
||||||
| (NavigationHelpers<ParamListBase> & {
|
| (NavigationHelpers<ParamListBase> &
|
||||||
|
EventConsumer<{ state: { data: { state: NavigationState } } }> & {
|
||||||
/**
|
/**
|
||||||
* Reset the navigation state of the root navigator to the provided state.
|
* Reset the navigation state of the root navigator to the provided state.
|
||||||
*
|
*
|
||||||
* @param state Navigation state object.
|
* @param state Navigation state object.
|
||||||
*/
|
*/
|
||||||
resetRoot(state?: PartialState<NavigationState> | NavigationState): void;
|
resetRoot(
|
||||||
|
state?: PartialState<NavigationState> | NavigationState
|
||||||
|
): void;
|
||||||
|
/**
|
||||||
|
* Get the rehydrated navigation state of the navigation tree.
|
||||||
|
*/
|
||||||
getRootState(): NavigationState;
|
getRootState(): NavigationState;
|
||||||
})
|
})
|
||||||
| undefined
|
| undefined
|
||||||
|
|||||||