mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-01-13 22:42:25 +08:00
Compare commits
6 Commits
@react-nav
...
@react-nav
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1dddaff45c | ||
|
|
21b397f0d6 | ||
|
|
2ff0531695 | ||
|
|
0149e85a95 | ||
|
|
3c47716826 | ||
|
|
acc9646426 |
@@ -3,6 +3,22 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [5.4.4](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.4.3...@react-navigation/bottom-tabs@5.4.4) (2020-05-14)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.4.3](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.4.2...@react-navigation/bottom-tabs@5.4.3) (2020-05-14)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [5.4.2](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.4.1...@react-navigation/bottom-tabs@5.4.2) (2020-05-10)
|
## [5.4.2](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.4.1...@react-navigation/bottom-tabs@5.4.2) (2020-05-10)
|
||||||
|
|
||||||
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@react-navigation/bottom-tabs",
|
"name": "@react-navigation/bottom-tabs",
|
||||||
"description": "Bottom tab navigator following iOS design guidelines",
|
"description": "Bottom tab navigator following iOS design guidelines",
|
||||||
"version": "5.4.2",
|
"version": "5.4.4",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"react-native-component",
|
"react-native-component",
|
||||||
"react-component",
|
"react-component",
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@react-native-community/bob": "^0.13.1",
|
"@react-native-community/bob": "^0.13.1",
|
||||||
"@react-navigation/native": "^5.3.0",
|
"@react-navigation/native": "^5.3.2",
|
||||||
"@types/color": "^3.0.1",
|
"@types/color": "^3.0.1",
|
||||||
"@types/react": "^16.9.34",
|
"@types/react": "^16.9.34",
|
||||||
"@types/react-native": "^0.62.7",
|
"@types/react-native": "^0.62.7",
|
||||||
|
|||||||
@@ -3,6 +3,22 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [5.1.20](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.19...@react-navigation/compat@5.1.20) (2020-05-14)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/compat
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.1.19](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.18...@react-navigation/compat@5.1.19) (2020-05-14)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/compat
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [5.1.18](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.17...@react-navigation/compat@5.1.18) (2020-05-10)
|
## [5.1.18](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.17...@react-navigation/compat@5.1.18) (2020-05-10)
|
||||||
|
|
||||||
**Note:** Version bump only for package @react-navigation/compat
|
**Note:** Version bump only for package @react-navigation/compat
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@react-navigation/compat",
|
"name": "@react-navigation/compat",
|
||||||
"description": "Compatibility layer to write navigator definitions in static configuration format",
|
"description": "Compatibility layer to write navigator definitions in static configuration format",
|
||||||
"version": "5.1.18",
|
"version": "5.1.20",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": "https://github.com/react-navigation/react-navigation/tree/master/packages/compat",
|
"repository": "https://github.com/react-navigation/react-navigation/tree/master/packages/compat",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@react-native-community/bob": "^0.13.1",
|
"@react-native-community/bob": "^0.13.1",
|
||||||
"@react-navigation/native": "^5.3.0",
|
"@react-navigation/native": "^5.3.2",
|
||||||
"@types/react": "^16.9.34",
|
"@types/react": "^16.9.34",
|
||||||
"react": "~16.9.0",
|
"react": "~16.9.0",
|
||||||
"typescript": "^3.8.3"
|
"typescript": "^3.8.3"
|
||||||
|
|||||||
@@ -3,6 +3,34 @@
|
|||||||
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.6.1](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.6.0...@react-navigation/core@5.6.1) (2020-05-14)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* don't use flat since it's not supported in node ([21b397f](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/21b397f0d6b96ec4875d3172f47533130bb08009))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [5.6.0](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.5.2...@react-navigation/core@5.6.0) (2020-05-14)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* ignore extra slashes in the pattern ([3c47716](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/3c47716826d0dfa69dfa6112141c116723372ea1))
|
||||||
|
* ignore state updates when we're not mounted ([0149e85](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/0149e85a95b90c6a9d487fa753ddbf5d01c03e3d)), closes [#8226](https://github.com/react-navigation/react-navigation/tree/master/packages/core/issues/8226)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* merge path patterns for nested screens ([#8253](https://github.com/react-navigation/react-navigation/tree/master/packages/core/issues/8253)) ([acc9646](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/acc9646426fee53558d686dfbe5fd0e35361d8c0))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [5.5.2](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.5.1...@react-navigation/core@5.5.2) (2020-05-08)
|
## [5.5.2](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.5.1...@react-navigation/core@5.5.2) (2020-05-08)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@react-navigation/core",
|
"name": "@react-navigation/core",
|
||||||
"description": "Core utilities for building navigators",
|
"description": "Core utilities for building navigators",
|
||||||
"version": "5.5.2",
|
"version": "5.6.1",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"react",
|
"react",
|
||||||
"react-native",
|
"react-native",
|
||||||
|
|||||||
@@ -117,7 +117,8 @@ it("doesn't add query param for empty params", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('handles state with config with nested screens', () => {
|
it('handles state with config with nested screens', () => {
|
||||||
const path = '/foe/bar/sweet/apple/baz/jane?answer=42&count=10&valid=true';
|
const path =
|
||||||
|
'/foo/foe/bar/sweet/apple/baz/jane?answer=42&count=10&valid=true';
|
||||||
const config = {
|
const config = {
|
||||||
Foo: {
|
Foo: {
|
||||||
path: 'foo',
|
path: 'foo',
|
||||||
@@ -182,8 +183,77 @@ it('handles state with config with nested screens', () => {
|
|||||||
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
|
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('handles state with config with nested screens and exact', () => {
|
||||||
|
const path = '/foe/bar/sweet/apple/baz/jane?answer=42&count=10&valid=true';
|
||||||
|
const config = {
|
||||||
|
Foo: {
|
||||||
|
path: 'foo',
|
||||||
|
screens: {
|
||||||
|
Foe: {
|
||||||
|
path: 'foe',
|
||||||
|
exact: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Bar: 'bar/:type/:fruit',
|
||||||
|
Baz: {
|
||||||
|
path: 'baz/:author',
|
||||||
|
parse: {
|
||||||
|
author: (author: string) =>
|
||||||
|
author.replace(/^\w/, (c) => c.toUpperCase()),
|
||||||
|
count: Number,
|
||||||
|
valid: Boolean,
|
||||||
|
},
|
||||||
|
stringify: {
|
||||||
|
author: (author: string) => author.toLowerCase(),
|
||||||
|
id: (id: number) => `x${id}`,
|
||||||
|
unknown: (_: unknown) => 'x',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
name: 'Foo',
|
||||||
|
state: {
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
name: 'Foe',
|
||||||
|
state: {
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
name: 'Bar',
|
||||||
|
params: { fruit: 'apple', type: 'sweet' },
|
||||||
|
state: {
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
name: 'Baz',
|
||||||
|
params: {
|
||||||
|
author: 'Jane',
|
||||||
|
count: '10',
|
||||||
|
answer: '42',
|
||||||
|
valid: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(getPathFromState(state, config)).toBe(path);
|
||||||
|
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
|
||||||
|
});
|
||||||
|
|
||||||
it('handles state with config with nested screens and unused configs', () => {
|
it('handles state with config with nested screens and unused configs', () => {
|
||||||
const path = '/foe/baz/jane?answer=42&count=10&valid=true';
|
const path = '/foo/foe/baz/jane?answer=42&count=10&valid=true';
|
||||||
const config = {
|
const config = {
|
||||||
Foo: {
|
Foo: {
|
||||||
path: 'foo',
|
path: 'foo',
|
||||||
@@ -239,6 +309,66 @@ it('handles state with config with nested screens and unused configs', () => {
|
|||||||
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
|
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('handles state with config with nested screens and unused configs with exact', () => {
|
||||||
|
const path = '/foe/baz/jane?answer=42&count=10&valid=true';
|
||||||
|
const config = {
|
||||||
|
Foo: {
|
||||||
|
path: 'foo',
|
||||||
|
screens: {
|
||||||
|
Foe: {
|
||||||
|
path: 'foe',
|
||||||
|
exact: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Baz: {
|
||||||
|
path: 'baz/:author',
|
||||||
|
parse: {
|
||||||
|
author: (author: string) =>
|
||||||
|
author.replace(/^\w/, (c) => c.toUpperCase()),
|
||||||
|
count: Number,
|
||||||
|
valid: Boolean,
|
||||||
|
},
|
||||||
|
stringify: {
|
||||||
|
author: (author: string) =>
|
||||||
|
author.replace(/^\w/, (c) => c.toLowerCase()),
|
||||||
|
unknown: (_: unknown) => 'x',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
name: 'Foo',
|
||||||
|
state: {
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
name: 'Foe',
|
||||||
|
state: {
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
name: 'Baz',
|
||||||
|
params: {
|
||||||
|
author: 'Jane',
|
||||||
|
count: 10,
|
||||||
|
answer: '42',
|
||||||
|
valid: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(getPathFromState(state, config)).toBe(path);
|
||||||
|
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
|
||||||
|
});
|
||||||
|
|
||||||
it('handles nested object with stringify in it', () => {
|
it('handles nested object with stringify in it', () => {
|
||||||
const path = '/bar/sweet/apple/foo/bis/jane?answer=42&count=10&valid=true';
|
const path = '/bar/sweet/apple/foo/bis/jane?answer=42&count=10&valid=true';
|
||||||
const config = {
|
const config = {
|
||||||
@@ -252,7 +382,6 @@ it('handles nested object with stringify in it', () => {
|
|||||||
},
|
},
|
||||||
Bar: 'bar/:type/:fruit',
|
Bar: 'bar/:type/:fruit',
|
||||||
Baz: {
|
Baz: {
|
||||||
path: 'baz',
|
|
||||||
screens: {
|
screens: {
|
||||||
Bos: 'bos',
|
Bos: 'bos',
|
||||||
Bis: {
|
Bis: {
|
||||||
@@ -312,8 +441,82 @@ it('handles nested object with stringify in it', () => {
|
|||||||
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
|
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('handles nested object with stringify in it with exact', () => {
|
||||||
|
const path = '/bar/sweet/apple/foo/bis/jane?answer=42&count=10&valid=true';
|
||||||
|
const config = {
|
||||||
|
Foo: {
|
||||||
|
path: 'foo',
|
||||||
|
screens: {
|
||||||
|
Foe: {
|
||||||
|
path: 'foe',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Bar: 'bar/:type/:fruit',
|
||||||
|
Baz: {
|
||||||
|
path: 'baz',
|
||||||
|
screens: {
|
||||||
|
Bos: 'bos',
|
||||||
|
Bis: {
|
||||||
|
path: 'bis/:author',
|
||||||
|
exact: true,
|
||||||
|
stringify: {
|
||||||
|
author: (author: string) =>
|
||||||
|
author.replace(/^\w/, (c) => c.toLowerCase()),
|
||||||
|
},
|
||||||
|
parse: {
|
||||||
|
author: (author: string) =>
|
||||||
|
author.replace(/^\w/, (c) => c.toUpperCase()),
|
||||||
|
count: Number,
|
||||||
|
valid: Boolean,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
name: 'Bar',
|
||||||
|
params: { fruit: 'apple', type: 'sweet' },
|
||||||
|
state: {
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
name: 'Foo',
|
||||||
|
state: {
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
name: 'Baz',
|
||||||
|
state: {
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
name: 'Bis',
|
||||||
|
params: {
|
||||||
|
author: 'Jane',
|
||||||
|
count: 10,
|
||||||
|
answer: '42',
|
||||||
|
valid: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(getPathFromState(state, config)).toBe(path);
|
||||||
|
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
|
||||||
|
});
|
||||||
|
|
||||||
it('handles nested object for second route depth', () => {
|
it('handles nested object for second route depth', () => {
|
||||||
const path = '/baz';
|
const path = '/foo/bar/baz';
|
||||||
const config = {
|
const config = {
|
||||||
Foo: {
|
Foo: {
|
||||||
path: 'foo',
|
path: 'foo',
|
||||||
@@ -351,7 +554,95 @@ it('handles nested object for second route depth', () => {
|
|||||||
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
|
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles nested object for second route depth and and path and stringify in roots', () => {
|
it('handles nested object for second route depth with exact', () => {
|
||||||
|
const path = '/baz';
|
||||||
|
const config = {
|
||||||
|
Foo: {
|
||||||
|
path: 'foo',
|
||||||
|
screens: {
|
||||||
|
Foe: 'foe',
|
||||||
|
Bar: {
|
||||||
|
path: 'bar',
|
||||||
|
screens: {
|
||||||
|
Baz: {
|
||||||
|
path: 'baz',
|
||||||
|
exact: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
name: 'Foo',
|
||||||
|
state: {
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
name: 'Bar',
|
||||||
|
state: {
|
||||||
|
routes: [{ name: 'Baz' }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(getPathFromState(state, config)).toBe(path);
|
||||||
|
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles nested object for second route depth and path and stringify in roots', () => {
|
||||||
|
const path = '/foo/dathomir/bar/42/baz';
|
||||||
|
const config = {
|
||||||
|
Foo: {
|
||||||
|
path: 'foo/:planet',
|
||||||
|
stringify: {
|
||||||
|
id: (id: number) => `planet=${id}`,
|
||||||
|
},
|
||||||
|
screens: {
|
||||||
|
Foe: 'foe',
|
||||||
|
Bar: {
|
||||||
|
path: 'bar/:id',
|
||||||
|
parse: {
|
||||||
|
id: Number,
|
||||||
|
},
|
||||||
|
screens: {
|
||||||
|
Baz: 'baz',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
name: 'Foo',
|
||||||
|
params: { planet: 'dathomir' },
|
||||||
|
state: {
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
name: 'Bar',
|
||||||
|
state: {
|
||||||
|
routes: [{ name: 'Baz', params: { id: 42 } }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(getPathFromState(state, config)).toBe(path);
|
||||||
|
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles nested object for second route depth and path and stringify in roots with exact', () => {
|
||||||
const path = '/baz';
|
const path = '/baz';
|
||||||
const config = {
|
const config = {
|
||||||
Foo: {
|
Foo: {
|
||||||
@@ -370,7 +661,10 @@ it('handles nested object for second route depth and and path and stringify in r
|
|||||||
id: Number,
|
id: Number,
|
||||||
},
|
},
|
||||||
screens: {
|
screens: {
|
||||||
Baz: 'baz',
|
Baz: {
|
||||||
|
path: 'baz',
|
||||||
|
exact: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -470,7 +764,7 @@ it('keeps query params if path is empty', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('cuts nested configs too', () => {
|
it('cuts nested configs too', () => {
|
||||||
const path = '/baz';
|
const path = '/foo/baz';
|
||||||
const config = {
|
const config = {
|
||||||
Foo: {
|
Foo: {
|
||||||
path: 'foo',
|
path: 'foo',
|
||||||
@@ -478,7 +772,48 @@ it('cuts nested configs too', () => {
|
|||||||
Bar: '',
|
Bar: '',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Baz: { path: 'baz' },
|
Baz: {
|
||||||
|
path: 'baz',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
name: 'Foo',
|
||||||
|
state: {
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
name: 'Bar',
|
||||||
|
state: {
|
||||||
|
routes: [{ name: 'Baz' }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(getPathFromState(state, config)).toBe(path);
|
||||||
|
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('cuts nested configs too with exact', () => {
|
||||||
|
const path = '/baz';
|
||||||
|
const config = {
|
||||||
|
Foo: {
|
||||||
|
path: 'foo',
|
||||||
|
screens: {
|
||||||
|
Bar: {
|
||||||
|
path: '',
|
||||||
|
exact: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Baz: {
|
||||||
|
path: 'baz',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
@@ -504,7 +839,7 @@ it('cuts nested configs too', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('handles empty path at the end', () => {
|
it('handles empty path at the end', () => {
|
||||||
const path = '/bar';
|
const path = '/foo/bar';
|
||||||
const config = {
|
const config = {
|
||||||
Foo: {
|
Foo: {
|
||||||
path: 'foo',
|
path: 'foo',
|
||||||
@@ -641,7 +976,6 @@ it('strips undefined query params', () => {
|
|||||||
},
|
},
|
||||||
Bar: 'bar/:type/:fruit',
|
Bar: 'bar/:type/:fruit',
|
||||||
Baz: {
|
Baz: {
|
||||||
path: 'baz',
|
|
||||||
screens: {
|
screens: {
|
||||||
Bos: 'bos',
|
Bos: 'bos',
|
||||||
Bis: {
|
Bis: {
|
||||||
@@ -681,7 +1015,79 @@ it('strips undefined query params', () => {
|
|||||||
params: {
|
params: {
|
||||||
author: 'Jane',
|
author: 'Jane',
|
||||||
count: 10,
|
count: 10,
|
||||||
answer: undefined,
|
valid: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(getPathFromState(state, config)).toBe(path);
|
||||||
|
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('strips undefined query params with exact', () => {
|
||||||
|
const path = '/bar/sweet/apple/foo/bis/jane?count=10&valid=true';
|
||||||
|
const config = {
|
||||||
|
Foo: {
|
||||||
|
path: 'foo',
|
||||||
|
screens: {
|
||||||
|
Foe: {
|
||||||
|
path: 'foe',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Bar: 'bar/:type/:fruit',
|
||||||
|
Baz: {
|
||||||
|
path: 'baz',
|
||||||
|
screens: {
|
||||||
|
Bos: 'bos',
|
||||||
|
Bis: {
|
||||||
|
path: 'bis/:author',
|
||||||
|
exact: true,
|
||||||
|
stringify: {
|
||||||
|
author: (author: string) =>
|
||||||
|
author.replace(/^\w/, (c) => c.toLowerCase()),
|
||||||
|
},
|
||||||
|
parse: {
|
||||||
|
author: (author: string) =>
|
||||||
|
author.replace(/^\w/, (c) => c.toUpperCase()),
|
||||||
|
count: Number,
|
||||||
|
valid: Boolean,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
name: 'Bar',
|
||||||
|
params: { fruit: 'apple', type: 'sweet' },
|
||||||
|
state: {
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
name: 'Foo',
|
||||||
|
state: {
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
name: 'Baz',
|
||||||
|
state: {
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
name: 'Bis',
|
||||||
|
params: {
|
||||||
|
author: 'Jane',
|
||||||
|
count: 10,
|
||||||
valid: true,
|
valid: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -714,7 +1120,6 @@ it('handles stripping all query params', () => {
|
|||||||
},
|
},
|
||||||
Bar: 'bar/:type/:fruit',
|
Bar: 'bar/:type/:fruit',
|
||||||
Baz: {
|
Baz: {
|
||||||
path: 'baz',
|
|
||||||
screens: {
|
screens: {
|
||||||
Bos: 'bos',
|
Bos: 'bos',
|
||||||
Bis: {
|
Bis: {
|
||||||
@@ -753,9 +1158,6 @@ it('handles stripping all query params', () => {
|
|||||||
name: 'Bis',
|
name: 'Bis',
|
||||||
params: {
|
params: {
|
||||||
author: 'Jane',
|
author: 'Jane',
|
||||||
count: undefined,
|
|
||||||
answer: undefined,
|
|
||||||
valid: undefined,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -773,3 +1175,93 @@ it('handles stripping all query params', () => {
|
|||||||
expect(getPathFromState(state, config)).toBe(path);
|
expect(getPathFromState(state, config)).toBe(path);
|
||||||
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
|
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('handles stripping all query params with exact', () => {
|
||||||
|
const path = '/bar/sweet/apple/foo/bis/jane';
|
||||||
|
const config = {
|
||||||
|
Foo: {
|
||||||
|
path: 'foo',
|
||||||
|
screens: {
|
||||||
|
Foe: {
|
||||||
|
path: 'foe',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Bar: 'bar/:type/:fruit',
|
||||||
|
Baz: {
|
||||||
|
path: 'baz',
|
||||||
|
screens: {
|
||||||
|
Bos: 'bos',
|
||||||
|
Bis: {
|
||||||
|
path: 'bis/:author',
|
||||||
|
exact: true,
|
||||||
|
stringify: {
|
||||||
|
author: (author: string) =>
|
||||||
|
author.replace(/^\w/, (c) => c.toLowerCase()),
|
||||||
|
},
|
||||||
|
parse: {
|
||||||
|
author: (author: string) =>
|
||||||
|
author.replace(/^\w/, (c) => c.toUpperCase()),
|
||||||
|
count: Number,
|
||||||
|
valid: Boolean,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
name: 'Bar',
|
||||||
|
params: { fruit: 'apple', type: 'sweet' },
|
||||||
|
state: {
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
name: 'Foo',
|
||||||
|
state: {
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
name: 'Baz',
|
||||||
|
state: {
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
name: 'Bis',
|
||||||
|
params: {
|
||||||
|
author: 'Jane',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(getPathFromState(state, config)).toBe(path);
|
||||||
|
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('replaces undefined query params', () => {
|
||||||
|
const path = '/bar/undefined/apple';
|
||||||
|
const config = {
|
||||||
|
Bar: 'bar/:type/:fruit',
|
||||||
|
};
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
name: 'Bar',
|
||||||
|
params: { fruit: 'apple' },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(getPathFromState(state, config)).toBe(path);
|
||||||
|
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
|
||||||
|
});
|
||||||
|
|||||||
@@ -147,7 +147,10 @@ it('converts path string to initial state with config with nested screens', () =
|
|||||||
Foo: {
|
Foo: {
|
||||||
path: 'foo',
|
path: 'foo',
|
||||||
screens: {
|
screens: {
|
||||||
Foe: 'foe',
|
Foe: {
|
||||||
|
path: 'foe',
|
||||||
|
exact: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Bar: 'bar/:type/:fruit',
|
Bar: 'bar/:type/:fruit',
|
||||||
@@ -213,7 +216,10 @@ it('converts path string to initial state with config with nested screens and un
|
|||||||
Foo: {
|
Foo: {
|
||||||
path: 'foo',
|
path: 'foo',
|
||||||
screens: {
|
screens: {
|
||||||
Foe: 'foe',
|
Foe: {
|
||||||
|
path: 'foe',
|
||||||
|
exact: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Baz: {
|
Baz: {
|
||||||
@@ -268,16 +274,23 @@ it('handles nested object with unused configs and with parse in it', () => {
|
|||||||
Foo: {
|
Foo: {
|
||||||
path: 'foo',
|
path: 'foo',
|
||||||
screens: {
|
screens: {
|
||||||
Foe: 'foe',
|
Foe: {
|
||||||
|
path: 'foe',
|
||||||
|
exact: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Bar: 'bar/:type/:fruit',
|
Bar: 'bar/:type/:fruit',
|
||||||
Baz: {
|
Baz: {
|
||||||
path: 'baz',
|
path: 'baz',
|
||||||
screens: {
|
screens: {
|
||||||
Bos: 'bos',
|
Bos: {
|
||||||
|
path: 'bos',
|
||||||
|
exact: true,
|
||||||
|
},
|
||||||
Bis: {
|
Bis: {
|
||||||
path: 'bis/:author',
|
path: 'bis/:author',
|
||||||
|
exact: true,
|
||||||
stringify: {
|
stringify: {
|
||||||
author: (author: string) =>
|
author: (author: string) =>
|
||||||
author.replace(/^\w/, (c) => c.toLowerCase()),
|
author.replace(/^\w/, (c) => c.toLowerCase()),
|
||||||
@@ -348,11 +361,18 @@ it('handles parse in nested object for second route depth', () => {
|
|||||||
Foo: {
|
Foo: {
|
||||||
path: 'foo',
|
path: 'foo',
|
||||||
screens: {
|
screens: {
|
||||||
Foe: 'foe',
|
Foe: {
|
||||||
|
path: 'foe',
|
||||||
|
exact: true,
|
||||||
|
},
|
||||||
Bar: {
|
Bar: {
|
||||||
path: 'bar',
|
path: 'bar',
|
||||||
|
exact: true,
|
||||||
screens: {
|
screens: {
|
||||||
Baz: 'baz',
|
Baz: {
|
||||||
|
path: 'baz',
|
||||||
|
exact: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -519,16 +539,23 @@ it('handles two initialRouteNames', () => {
|
|||||||
Foo: {
|
Foo: {
|
||||||
path: 'foo',
|
path: 'foo',
|
||||||
screens: {
|
screens: {
|
||||||
Foe: 'foe',
|
Foe: {
|
||||||
|
path: 'foe',
|
||||||
|
exact: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Bar: 'bar/:type/:fruit',
|
Bar: 'bar/:type/:fruit',
|
||||||
Baz: {
|
Baz: {
|
||||||
initialRouteName: 'Bos',
|
initialRouteName: 'Bos',
|
||||||
screens: {
|
screens: {
|
||||||
Bos: 'bos',
|
Bos: {
|
||||||
|
path: 'bos',
|
||||||
|
exact: true,
|
||||||
|
},
|
||||||
Bis: {
|
Bis: {
|
||||||
path: 'bis/:author',
|
path: 'bis/:author',
|
||||||
|
exact: true,
|
||||||
stringify: {
|
stringify: {
|
||||||
author: (author: string) =>
|
author: (author: string) =>
|
||||||
author.replace(/^\w/, (c) => c.toLowerCase()),
|
author.replace(/^\w/, (c) => c.toLowerCase()),
|
||||||
@@ -601,16 +628,23 @@ it('accepts initialRouteName without config for it', () => {
|
|||||||
Foo: {
|
Foo: {
|
||||||
path: 'foo',
|
path: 'foo',
|
||||||
screens: {
|
screens: {
|
||||||
Foe: 'foe',
|
Foe: {
|
||||||
|
path: 'foe',
|
||||||
|
exact: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Bar: 'bar/:type/:fruit',
|
Bar: 'bar/:type/:fruit',
|
||||||
Baz: {
|
Baz: {
|
||||||
initialRouteName: 'Bas',
|
initialRouteName: 'Bas',
|
||||||
screens: {
|
screens: {
|
||||||
Bos: 'bos',
|
Bos: {
|
||||||
|
path: 'bos',
|
||||||
|
exact: true,
|
||||||
|
},
|
||||||
Bis: {
|
Bis: {
|
||||||
path: 'bis/:author',
|
path: 'bis/:author',
|
||||||
|
exact: true,
|
||||||
stringify: {
|
stringify: {
|
||||||
author: (author: string) =>
|
author: (author: string) =>
|
||||||
author.replace(/^\w/, (c) => c.toLowerCase()),
|
author.replace(/^\w/, (c) => c.toLowerCase()),
|
||||||
@@ -1777,3 +1811,75 @@ it('handle optional params in the beginning v2', () => {
|
|||||||
state
|
state
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('merges parent patterns if needed', () => {
|
||||||
|
const path = 'foo/42/baz/babel';
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
Foo: {
|
||||||
|
path: 'foo/:bar',
|
||||||
|
parse: {
|
||||||
|
bar: Number,
|
||||||
|
},
|
||||||
|
screens: {
|
||||||
|
Baz: 'baz/:qux',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
name: 'Foo',
|
||||||
|
params: { bar: 42 },
|
||||||
|
state: {
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
name: 'Baz',
|
||||||
|
params: { qux: 'babel' },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(getStateFromPath(path, config)).toEqual(state);
|
||||||
|
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
||||||
|
state
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('ignores extra slashes in the pattern', () => {
|
||||||
|
const path = '/bar/42';
|
||||||
|
const config = {
|
||||||
|
Foo: {
|
||||||
|
screens: {
|
||||||
|
Bar: {
|
||||||
|
path: '/bar//:id/',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
name: 'Foo',
|
||||||
|
state: {
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
name: 'Bar',
|
||||||
|
params: { id: '42' },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(getStateFromPath(path, config)).toEqual(state);
|
||||||
|
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
||||||
|
state
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|||||||
@@ -9,16 +9,15 @@ type State = NavigationState | Omit<PartialState<NavigationState>, 'stale'>;
|
|||||||
|
|
||||||
type StringifyConfig = Record<string, (value: any) => string>;
|
type StringifyConfig = Record<string, (value: any) => string>;
|
||||||
|
|
||||||
type Options = {
|
type OptionsItem = {
|
||||||
[routeName: string]:
|
path?: string;
|
||||||
| string
|
exact?: boolean;
|
||||||
| {
|
stringify?: StringifyConfig;
|
||||||
path?: string;
|
screens?: Options;
|
||||||
stringify?: StringifyConfig;
|
|
||||||
screens?: Options;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type Options = Record<string, string | OptionsItem>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility to serialize a navigation state object to a path string.
|
* Utility to serialize a navigation state object to a path string.
|
||||||
*
|
*
|
||||||
@@ -53,84 +52,78 @@ export default function getPathFromState(
|
|||||||
if (state === undefined) {
|
if (state === undefined) {
|
||||||
throw Error('NavigationState not passed');
|
throw Error('NavigationState not passed');
|
||||||
}
|
}
|
||||||
let path = '/';
|
|
||||||
|
|
||||||
|
// Create a normalized configs array which will be easier to use
|
||||||
|
const configs = createNormalizedConfigs(options);
|
||||||
|
|
||||||
|
let path = '/';
|
||||||
let current: State | undefined = state;
|
let current: State | undefined = state;
|
||||||
|
|
||||||
|
const allParams: Record<string, any> = {};
|
||||||
|
|
||||||
while (current) {
|
while (current) {
|
||||||
let index = typeof current.index === 'number' ? current.index : 0;
|
let index = typeof current.index === 'number' ? current.index : 0;
|
||||||
let route = current.routes[index] as Route<string> & {
|
let route = current.routes[index] as Route<string> & {
|
||||||
state?: State;
|
state?: State;
|
||||||
};
|
};
|
||||||
let currentOptions = options;
|
|
||||||
let pattern = route.name;
|
|
||||||
// we keep all the route names that appeared during going deeper in config in case the pattern is resolved to undefined
|
|
||||||
let nestedRouteNames = '';
|
|
||||||
|
|
||||||
while (route.name in currentOptions) {
|
let pattern: string | undefined;
|
||||||
if (typeof currentOptions[route.name] === 'string') {
|
|
||||||
pattern = currentOptions[route.name] as string;
|
let currentParams: Record<string, any> = { ...route.params };
|
||||||
break;
|
let currentOptions = configs;
|
||||||
} else if (typeof currentOptions[route.name] === 'object') {
|
|
||||||
// if there is no `screens` property, we return pattern
|
// Keep all the route names that appeared during going deeper in config in case the pattern is resolved to undefined
|
||||||
if (
|
let nestedRouteNames = [];
|
||||||
!(currentOptions[route.name] as {
|
|
||||||
screens: Options;
|
let hasNext = true;
|
||||||
}).screens
|
|
||||||
) {
|
while (route.name in currentOptions && hasNext) {
|
||||||
pattern = (currentOptions[route.name] as { path: string }).path;
|
pattern = currentOptions[route.name].pattern;
|
||||||
nestedRouteNames = `${nestedRouteNames}/${route.name}`;
|
|
||||||
break;
|
nestedRouteNames.push(route.name);
|
||||||
|
|
||||||
|
if (route.params) {
|
||||||
|
const stringify = currentOptions[route.name]?.stringify;
|
||||||
|
|
||||||
|
currentParams = Object.fromEntries(
|
||||||
|
Object.entries(route.params).map(([key, value]) => [
|
||||||
|
key,
|
||||||
|
stringify?.[key] ? stringify[key](value) : String(value),
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
if (pattern) {
|
||||||
|
Object.assign(allParams, currentParams);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is no `screens` property or no nested state, we return pattern
|
||||||
|
if (!currentOptions[route.name].screens || route.state === undefined) {
|
||||||
|
hasNext = false;
|
||||||
|
} else {
|
||||||
|
index =
|
||||||
|
typeof route.state.index === 'number'
|
||||||
|
? route.state.index
|
||||||
|
: route.state.routes.length - 1;
|
||||||
|
|
||||||
|
const nextRoute = route.state.routes[index];
|
||||||
|
const nestedConfig = currentOptions[route.name].screens;
|
||||||
|
|
||||||
|
// if there is config for next route name, we go deeper
|
||||||
|
if (nestedConfig && nextRoute.name in nestedConfig) {
|
||||||
|
route = nextRoute as Route<string> & { state?: State };
|
||||||
|
currentOptions = nestedConfig;
|
||||||
} else {
|
} else {
|
||||||
// if it is the end of state, we return pattern
|
// If not, there is no sense in going deeper in config
|
||||||
if (route.state === undefined) {
|
hasNext = false;
|
||||||
pattern = (currentOptions[route.name] as { path: string }).path;
|
|
||||||
nestedRouteNames = `${nestedRouteNames}/${route.name}`;
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
index =
|
|
||||||
typeof route.state.index === 'number' ? route.state.index : 0;
|
|
||||||
const nextRoute = route.state.routes[index];
|
|
||||||
const deeperConfig = (currentOptions[route.name] as {
|
|
||||||
screens: Options;
|
|
||||||
}).screens;
|
|
||||||
// if there is config for next route name, we go deeper
|
|
||||||
if (nextRoute.name in deeperConfig) {
|
|
||||||
nestedRouteNames = `${nestedRouteNames}/${route.name}`;
|
|
||||||
route = nextRoute as Route<string> & { state?: State };
|
|
||||||
currentOptions = deeperConfig;
|
|
||||||
} else {
|
|
||||||
// if not, there is no sense in going deeper in config
|
|
||||||
pattern = (currentOptions[route.name] as { path: string }).path;
|
|
||||||
nestedRouteNames = `${nestedRouteNames}/${route.name}`;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pattern === undefined) {
|
if (pattern === undefined) {
|
||||||
// cut the first `/`
|
pattern = nestedRouteNames.join('/');
|
||||||
pattern = nestedRouteNames.substring(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const config =
|
|
||||||
currentOptions[route.name] !== undefined
|
|
||||||
? (currentOptions[route.name] as { stringify?: StringifyConfig })
|
|
||||||
.stringify
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
const params = route.params
|
|
||||||
? // Stringify all of the param values before we use them
|
|
||||||
Object.entries(route.params).reduce<{
|
|
||||||
[key: string]: string;
|
|
||||||
}>((acc, [key, value]) => {
|
|
||||||
acc[key] = config?.[key] ? config[key](value) : String(value);
|
|
||||||
return acc;
|
|
||||||
}, {})
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
if (currentOptions[route.name] !== undefined) {
|
if (currentOptions[route.name] !== undefined) {
|
||||||
path += pattern
|
path += pattern
|
||||||
.split('/')
|
.split('/')
|
||||||
@@ -138,16 +131,23 @@ export default function getPathFromState(
|
|||||||
const name = p.replace(/^:/, '').replace(/\?$/, '');
|
const name = p.replace(/^:/, '').replace(/\?$/, '');
|
||||||
|
|
||||||
// If the path has a pattern for a param, put the param in the path
|
// If the path has a pattern for a param, put the param in the path
|
||||||
if (params && name in params && p.startsWith(':')) {
|
if (p.startsWith(':')) {
|
||||||
const value = params[name];
|
const value = allParams[name];
|
||||||
|
|
||||||
// Remove the used value from the params object since we'll use the rest for query string
|
// Remove the used value from the params object since we'll use the rest for query string
|
||||||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
if (currentParams) {
|
||||||
delete params[name];
|
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
||||||
|
delete currentParams[name];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value === undefined && p.endsWith('?')) {
|
||||||
|
// Optional params without value assigned in route.params should be ignored
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
return encodeURIComponent(value);
|
return encodeURIComponent(value);
|
||||||
} else if (p.endsWith('?')) {
|
|
||||||
// optional params without value assigned in route.params should be ignored
|
|
||||||
return '';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return encodeURIComponent(p);
|
return encodeURIComponent(p);
|
||||||
})
|
})
|
||||||
.join('/');
|
.join('/');
|
||||||
@@ -157,14 +157,15 @@ export default function getPathFromState(
|
|||||||
|
|
||||||
if (route.state) {
|
if (route.state) {
|
||||||
path += '/';
|
path += '/';
|
||||||
} else if (params) {
|
} else if (currentParams) {
|
||||||
for (let param in params) {
|
for (let param in currentParams) {
|
||||||
if (params[param] === 'undefined') {
|
if (currentParams[param] === 'undefined') {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
||||||
delete params[param];
|
delete currentParams[param];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const query = queryString.stringify(params);
|
|
||||||
|
const query = queryString.stringify(currentParams);
|
||||||
|
|
||||||
if (query) {
|
if (query) {
|
||||||
path += `?${query}`;
|
path += `?${query}`;
|
||||||
@@ -180,3 +181,59 @@ export default function getPathFromState(
|
|||||||
|
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ConfigItem = {
|
||||||
|
pattern?: string;
|
||||||
|
stringify?: StringifyConfig;
|
||||||
|
screens?: Record<string, ConfigItem>;
|
||||||
|
};
|
||||||
|
|
||||||
|
function joinPaths(...paths: string[]): string {
|
||||||
|
return ([] as string[])
|
||||||
|
.concat(...paths.map((p) => p.split('/')))
|
||||||
|
.filter(Boolean)
|
||||||
|
.join('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
function createConfigItem(
|
||||||
|
config: OptionsItem | string,
|
||||||
|
parentPattern?: string
|
||||||
|
): ConfigItem {
|
||||||
|
if (typeof config === 'string') {
|
||||||
|
// If a string is specified as the value of the key(e.g. Foo: '/path'), use it as the pattern
|
||||||
|
const pattern = parentPattern ? joinPaths(parentPattern, config) : config;
|
||||||
|
|
||||||
|
return { pattern };
|
||||||
|
}
|
||||||
|
|
||||||
|
// If an object is specified as the value (e.g. Foo: { ... }),
|
||||||
|
// It can have `path` property and `screens` prop which has nested configs
|
||||||
|
const pattern =
|
||||||
|
config.exact !== true && parentPattern && config.path
|
||||||
|
? joinPaths(parentPattern, config.path)
|
||||||
|
: config.path;
|
||||||
|
|
||||||
|
const screens = config.screens
|
||||||
|
? createNormalizedConfigs(config.screens, pattern)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
return {
|
||||||
|
// Normalize pattern to remove any leading, trailing slashes, duplicate slashes etc.
|
||||||
|
pattern: pattern?.split('/').filter(Boolean).join('/'),
|
||||||
|
stringify: config.stringify,
|
||||||
|
screens,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createNormalizedConfigs(
|
||||||
|
options: Options,
|
||||||
|
pattern?: string
|
||||||
|
): Record<string, ConfigItem> {
|
||||||
|
return Object.fromEntries(
|
||||||
|
Object.entries(options).map(([name, c]) => {
|
||||||
|
const result = createConfigItem(c, pattern);
|
||||||
|
|
||||||
|
return [name, result];
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ type Options = {
|
|||||||
| string
|
| string
|
||||||
| {
|
| {
|
||||||
path?: string;
|
path?: string;
|
||||||
|
exact?: boolean;
|
||||||
parse?: ParseConfig;
|
parse?: ParseConfig;
|
||||||
screens?: Options;
|
screens?: Options;
|
||||||
initialRouteName?: string;
|
initialRouteName?: string;
|
||||||
@@ -21,10 +22,11 @@ type Options = {
|
|||||||
|
|
||||||
type RouteConfig = {
|
type RouteConfig = {
|
||||||
screen: string;
|
screen: string;
|
||||||
match: RegExp | null;
|
regex?: RegExp;
|
||||||
|
path: string;
|
||||||
pattern: string;
|
pattern: string;
|
||||||
routeNames: string[];
|
routeNames: string[];
|
||||||
parse: ParseConfig | undefined;
|
parse?: ParseConfig;
|
||||||
};
|
};
|
||||||
|
|
||||||
type InitialRouteConfig = {
|
type InitialRouteConfig = {
|
||||||
@@ -62,17 +64,17 @@ export default function getStateFromPath(
|
|||||||
let initialRoutes: InitialRouteConfig[] = [];
|
let initialRoutes: InitialRouteConfig[] = [];
|
||||||
|
|
||||||
// Create a normalized configs array which will be easier to use
|
// Create a normalized configs array which will be easier to use
|
||||||
const configs = ([] as RouteConfig[]).concat(
|
const configs = ([] as RouteConfig[])
|
||||||
...Object.keys(options).map((key) =>
|
.concat(
|
||||||
createNormalizedConfigs(key, options, [], initialRoutes)
|
...Object.keys(options).map((key) =>
|
||||||
|
createNormalizedConfigs(key, options, [], initialRoutes)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
);
|
.sort(
|
||||||
|
(a, b) =>
|
||||||
// sort configs so the most exhaustive is always first to be chosen
|
// Sort configs so the most exhaustive is always first to be chosen
|
||||||
configs.sort(
|
b.pattern.split('/').length - a.pattern.split('/').length
|
||||||
(config1, config2) =>
|
);
|
||||||
config2.pattern.split('/').length - config1.pattern.split('/').length
|
|
||||||
);
|
|
||||||
|
|
||||||
let remaining = path
|
let remaining = path
|
||||||
.replace(/\/+/g, '/') // Replace multiple slash (//) with single ones
|
.replace(/\/+/g, '/') // Replace multiple slash (//) with single ones
|
||||||
@@ -87,18 +89,23 @@ export default function getStateFromPath(
|
|||||||
// When handling empty path, we should only look at the root level config
|
// When handling empty path, we should only look at the root level config
|
||||||
const match = configs.find(
|
const match = configs.find(
|
||||||
(config) =>
|
(config) =>
|
||||||
config.pattern === '' &&
|
config.path === '' &&
|
||||||
config.routeNames.every(
|
config.routeNames.every(
|
||||||
// make sure that none of the parent configs have a non-empty path defined
|
// Make sure that none of the parent configs have a non-empty path defined
|
||||||
(name) => !configs.find((c) => c.screen === name)?.pattern
|
(name) => !configs.find((c) => c.screen === name)?.path
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (match) {
|
if (match) {
|
||||||
return createNestedStateObject(
|
return createNestedStateObject(
|
||||||
match.routeNames,
|
match.routeNames.map((name, i, self) => {
|
||||||
initialRoutes,
|
if (i === self.length - 1) {
|
||||||
parseQueryParams(path, match.parse)
|
return { name, params: parseQueryParams(path, match.parse) };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { name };
|
||||||
|
}),
|
||||||
|
initialRoutes
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,15 +117,15 @@ export default function getStateFromPath(
|
|||||||
|
|
||||||
while (remaining) {
|
while (remaining) {
|
||||||
let routeNames: string[] | undefined;
|
let routeNames: string[] | undefined;
|
||||||
let params: Record<string, any> | undefined;
|
let allParams: Record<string, any> | undefined;
|
||||||
|
|
||||||
// Go through all configs, and see if the next path segment matches our regex
|
// Go through all configs, and see if the next path segment matches our regex
|
||||||
for (const config of configs) {
|
for (const config of configs) {
|
||||||
if (!config.match) {
|
if (!config.regex) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const match = remaining.match(config.match);
|
const match = remaining.match(config.regex);
|
||||||
|
|
||||||
// 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) {
|
||||||
@@ -129,16 +136,10 @@ export default function getStateFromPath(
|
|||||||
.filter((p) => p.startsWith(':'));
|
.filter((p) => p.startsWith(':'));
|
||||||
|
|
||||||
if (paramPatterns.length) {
|
if (paramPatterns.length) {
|
||||||
params = paramPatterns.reduce<Record<string, any>>((acc, p, i) => {
|
allParams = paramPatterns.reduce<Record<string, any>>((acc, p, i) => {
|
||||||
const key = p.replace(/^:/, '').replace(/\?$/, '');
|
const value = match![(i + 1) * 2].replace(/\//, ''); // The param segments appear every second item starting from 2 in the regex match result
|
||||||
const value = match[(i + 1) * 2].replace(/\//, ''); // The param segments appear every second item starting from 2 in the regex match result
|
|
||||||
|
|
||||||
if (value) {
|
acc[p] = value;
|
||||||
acc[key] =
|
|
||||||
config.parse && config.parse[key]
|
|
||||||
? config.parse[key](value)
|
|
||||||
: value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
@@ -159,7 +160,46 @@ export default function getStateFromPath(
|
|||||||
remaining = segments.join('/');
|
remaining = segments.join('/');
|
||||||
}
|
}
|
||||||
|
|
||||||
const state = createNestedStateObject(routeNames, initialRoutes, params);
|
const state = createNestedStateObject(
|
||||||
|
routeNames.map((name) => {
|
||||||
|
const config = configs.find((c) => c.screen === name);
|
||||||
|
|
||||||
|
let params: object | undefined;
|
||||||
|
|
||||||
|
if (allParams && config?.path) {
|
||||||
|
const pattern = config.path;
|
||||||
|
|
||||||
|
if (pattern) {
|
||||||
|
const paramPatterns = pattern
|
||||||
|
.split('/')
|
||||||
|
.filter((p) => p.startsWith(':'));
|
||||||
|
|
||||||
|
if (paramPatterns.length) {
|
||||||
|
params = paramPatterns.reduce<Record<string, any>>((acc, p) => {
|
||||||
|
const key = p.replace(/^:/, '').replace(/\?$/, '');
|
||||||
|
const value = allParams![p];
|
||||||
|
|
||||||
|
if (value) {
|
||||||
|
acc[key] =
|
||||||
|
config.parse && config.parse[key]
|
||||||
|
? config.parse[key](value)
|
||||||
|
: value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params && Object.keys(params).length) {
|
||||||
|
return { name, params };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { name };
|
||||||
|
}),
|
||||||
|
initialRoutes
|
||||||
|
);
|
||||||
|
|
||||||
if (current) {
|
if (current) {
|
||||||
// The state should be nested inside the deepest route we parsed before
|
// The state should be nested inside the deepest route we parsed before
|
||||||
@@ -194,44 +234,66 @@ export default function getStateFromPath(
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function joinPaths(...paths: string[]): string {
|
||||||
|
return ([] as string[])
|
||||||
|
.concat(...paths.map((p) => p.split('/')))
|
||||||
|
.filter(Boolean)
|
||||||
|
.join('/');
|
||||||
|
}
|
||||||
|
|
||||||
function createNormalizedConfigs(
|
function createNormalizedConfigs(
|
||||||
key: string,
|
screen: string,
|
||||||
routeConfig: Options,
|
routeConfig: Options,
|
||||||
routeNames: string[] = [],
|
routeNames: string[] = [],
|
||||||
initials: InitialRouteConfig[]
|
initials: InitialRouteConfig[],
|
||||||
|
parentPattern?: string
|
||||||
): RouteConfig[] {
|
): RouteConfig[] {
|
||||||
const configs: RouteConfig[] = [];
|
const configs: RouteConfig[] = [];
|
||||||
|
|
||||||
routeNames.push(key);
|
routeNames.push(screen);
|
||||||
|
|
||||||
const value = routeConfig[key];
|
const config = routeConfig[screen];
|
||||||
|
|
||||||
if (typeof value === 'string') {
|
if (typeof config === 'string') {
|
||||||
// If a string is specified as the value of the key(e.g. Foo: '/path'), use it as the pattern
|
// If a string is specified as the value of the key(e.g. Foo: '/path'), use it as the pattern
|
||||||
configs.push(createConfigItem(key, routeNames, value));
|
const pattern = parentPattern ? joinPaths(parentPattern, config) : config;
|
||||||
} else if (typeof value === 'object') {
|
|
||||||
|
configs.push(createConfigItem(screen, routeNames, pattern, config));
|
||||||
|
} else if (typeof config === 'object') {
|
||||||
|
let pattern: string | undefined;
|
||||||
|
|
||||||
// if an object is specified as the value (e.g. Foo: { ... }),
|
// if an object is specified as the value (e.g. Foo: { ... }),
|
||||||
// it can have `path` property and
|
// it can have `path` property and
|
||||||
// it could have `screens` prop which has nested configs
|
// it could have `screens` prop which has nested configs
|
||||||
if (typeof value.path === 'string') {
|
if (typeof config.path === 'string') {
|
||||||
configs.push(createConfigItem(key, routeNames, value.path, value.parse));
|
pattern =
|
||||||
|
config.exact !== true && parentPattern
|
||||||
|
? joinPaths(parentPattern, config.path)
|
||||||
|
: config.path;
|
||||||
|
|
||||||
|
configs.push(
|
||||||
|
createConfigItem(screen, routeNames, pattern, config.path, config.parse)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value.screens) {
|
if (config.screens) {
|
||||||
// property `initialRouteName` without `screens` has no purpose
|
// property `initialRouteName` without `screens` has no purpose
|
||||||
if (value.initialRouteName) {
|
if (config.initialRouteName) {
|
||||||
initials.push({
|
initials.push({
|
||||||
initialRouteName: value.initialRouteName,
|
initialRouteName: config.initialRouteName,
|
||||||
connectedRoutes: Object.keys(value.screens),
|
connectedRoutes: Object.keys(config.screens),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Object.keys(value.screens).forEach((nestedConfig) => {
|
|
||||||
|
Object.keys(config.screens).forEach((nestedConfig) => {
|
||||||
const result = createNormalizedConfigs(
|
const result = createNormalizedConfigs(
|
||||||
nestedConfig,
|
nestedConfig,
|
||||||
value.screens as Options,
|
config.screens as Options,
|
||||||
routeNames,
|
routeNames,
|
||||||
initials
|
initials,
|
||||||
|
pattern
|
||||||
);
|
);
|
||||||
|
|
||||||
configs.push(...result);
|
configs.push(...result);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -246,9 +308,13 @@ function createConfigItem(
|
|||||||
screen: string,
|
screen: string,
|
||||||
routeNames: string[],
|
routeNames: string[],
|
||||||
pattern: string,
|
pattern: string,
|
||||||
|
path: string,
|
||||||
parse?: ParseConfig
|
parse?: ParseConfig
|
||||||
): RouteConfig {
|
): RouteConfig {
|
||||||
const match = pattern
|
// Normalize pattern to remove any leading, trailing slashes, duplicate slashes etc.
|
||||||
|
pattern = pattern.split('/').filter(Boolean).join('/');
|
||||||
|
|
||||||
|
const regex = pattern
|
||||||
? new RegExp(
|
? new RegExp(
|
||||||
`^(${pattern
|
`^(${pattern
|
||||||
.split('/')
|
.split('/')
|
||||||
@@ -261,12 +327,13 @@ function createConfigItem(
|
|||||||
})
|
})
|
||||||
.join('')})`
|
.join('')})`
|
||||||
)
|
)
|
||||||
: null;
|
: undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
screen,
|
screen,
|
||||||
match,
|
regex,
|
||||||
pattern,
|
pattern,
|
||||||
|
path,
|
||||||
// The routeNames array is mutated, so copy it to keep the current state
|
// The routeNames array is mutated, so copy it to keep the current state
|
||||||
routeNames: [...routeNames],
|
routeNames: [...routeNames],
|
||||||
parse,
|
parse,
|
||||||
@@ -305,21 +372,18 @@ function findInitialRoute(
|
|||||||
function createStateObject(
|
function createStateObject(
|
||||||
initialRoute: string | undefined,
|
initialRoute: string | undefined,
|
||||||
routeName: string,
|
routeName: string,
|
||||||
isEmpty: boolean,
|
params: Record<string, any> | undefined,
|
||||||
params?: Record<string, any> | undefined
|
isEmpty: boolean
|
||||||
): InitialState {
|
): InitialState {
|
||||||
if (isEmpty) {
|
if (isEmpty) {
|
||||||
if (initialRoute) {
|
if (initialRoute) {
|
||||||
return {
|
return {
|
||||||
index: 1,
|
index: 1,
|
||||||
routes: [
|
routes: [{ name: initialRoute }, { name: routeName as string, params }],
|
||||||
{ name: initialRoute },
|
|
||||||
{ name: routeName as string, ...(params && { params }) },
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
routes: [{ name: routeName as string, ...(params && { params }) }],
|
routes: [{ name: routeName as string, params }],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -328,44 +392,50 @@ function createStateObject(
|
|||||||
index: 1,
|
index: 1,
|
||||||
routes: [
|
routes: [
|
||||||
{ name: initialRoute },
|
{ name: initialRoute },
|
||||||
{ name: routeName as string, state: { routes: [] } },
|
{ name: routeName as string, params, state: { routes: [] } },
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return { routes: [{ name: routeName as string, state: { routes: [] } }] };
|
return {
|
||||||
|
routes: [{ name: routeName as string, params, state: { routes: [] } }],
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createNestedStateObject(
|
function createNestedStateObject(
|
||||||
routeNames: string[],
|
routes: { name: string; params?: object }[],
|
||||||
initialRoutes: InitialRouteConfig[],
|
initialRoutes: InitialRouteConfig[]
|
||||||
params: object | undefined
|
|
||||||
) {
|
) {
|
||||||
let state: InitialState;
|
let state: InitialState;
|
||||||
let routeName = routeNames.shift() as string;
|
let route = routes.shift() as { name: string; params?: object };
|
||||||
let initialRoute = findInitialRoute(routeName, initialRoutes);
|
let initialRoute = findInitialRoute(route.name, initialRoutes);
|
||||||
|
|
||||||
state = createStateObject(
|
state = createStateObject(
|
||||||
initialRoute,
|
initialRoute,
|
||||||
routeName,
|
route.name,
|
||||||
routeNames.length === 0,
|
route.params,
|
||||||
params
|
routes.length === 0
|
||||||
);
|
);
|
||||||
|
|
||||||
if (routeNames.length > 0) {
|
if (routes.length > 0) {
|
||||||
let nestedState = state;
|
let nestedState = state;
|
||||||
|
|
||||||
while ((routeName = routeNames.shift() as string)) {
|
while ((route = routes.shift() as { name: string; params?: object })) {
|
||||||
initialRoute = findInitialRoute(routeName, initialRoutes);
|
initialRoute = findInitialRoute(route.name, initialRoutes);
|
||||||
nestedState.routes[nestedState.index || 0].state = createStateObject(
|
|
||||||
|
const nestedStateIndex =
|
||||||
|
nestedState.index || nestedState.routes.length - 1;
|
||||||
|
|
||||||
|
nestedState.routes[nestedStateIndex].state = createStateObject(
|
||||||
initialRoute,
|
initialRoute,
|
||||||
routeName,
|
route.name,
|
||||||
routeNames.length === 0,
|
route.params,
|
||||||
params
|
routes.length === 0
|
||||||
);
|
);
|
||||||
if (routeNames.length > 0) {
|
|
||||||
nestedState = nestedState.routes[nestedState.index || 0]
|
if (routes.length > 0) {
|
||||||
|
nestedState = nestedState.routes[nestedStateIndex]
|
||||||
.state as InitialState;
|
.state as InitialState;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,15 @@ const UNINTIALIZED_STATE = {};
|
|||||||
export default function useSyncState<T>(initialState?: (() => T) | T) {
|
export default function useSyncState<T>(initialState?: (() => T) | T) {
|
||||||
const stateRef = React.useRef<T>(UNINTIALIZED_STATE as any);
|
const stateRef = React.useRef<T>(UNINTIALIZED_STATE as any);
|
||||||
const isSchedulingRef = React.useRef(false);
|
const isSchedulingRef = React.useRef(false);
|
||||||
|
const isMountedRef = React.useRef(true);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
isMountedRef.current = true;
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
isMountedRef.current = false;
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
if (stateRef.current === UNINTIALIZED_STATE) {
|
if (stateRef.current === UNINTIALIZED_STATE) {
|
||||||
stateRef.current =
|
stateRef.current =
|
||||||
@@ -20,7 +29,7 @@ export default function useSyncState<T>(initialState?: (() => T) | T) {
|
|||||||
const getState = React.useCallback(() => stateRef.current, []);
|
const getState = React.useCallback(() => stateRef.current, []);
|
||||||
|
|
||||||
const setState = React.useCallback((state: T) => {
|
const setState = React.useCallback((state: T) => {
|
||||||
if (state === stateRef.current) {
|
if (state === stateRef.current || !isMountedRef.current) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,6 +51,10 @@ export default function useSyncState<T>(initialState?: (() => T) | T) {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const flushUpdates = React.useCallback(() => {
|
const flushUpdates = React.useCallback(() => {
|
||||||
|
if (!isMountedRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Make sure that the tracking state is up-to-date.
|
// Make sure that the tracking state is up-to-date.
|
||||||
// We call it unconditionally, but React should skip the update if state is unchanged.
|
// We call it unconditionally, but React should skip the update if state is unchanged.
|
||||||
setTrackingState(stateRef.current);
|
setTrackingState(stateRef.current);
|
||||||
|
|||||||
@@ -3,6 +3,22 @@
|
|||||||
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.7.4](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.7.3...@react-navigation/drawer@5.7.4) (2020-05-14)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/drawer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.7.3](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.7.2...@react-navigation/drawer@5.7.3) (2020-05-14)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/drawer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [5.7.2](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.7.1...@react-navigation/drawer@5.7.2) (2020-05-10)
|
## [5.7.2](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.7.1...@react-navigation/drawer@5.7.2) (2020-05-10)
|
||||||
|
|
||||||
**Note:** Version bump only for package @react-navigation/drawer
|
**Note:** Version bump only for package @react-navigation/drawer
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@react-navigation/drawer",
|
"name": "@react-navigation/drawer",
|
||||||
"description": "Drawer navigator component with animated transitions and gesturess",
|
"description": "Drawer navigator component with animated transitions and gesturess",
|
||||||
"version": "5.7.2",
|
"version": "5.7.4",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"react-native-component",
|
"react-native-component",
|
||||||
"react-component",
|
"react-component",
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@react-native-community/bob": "^0.13.1",
|
"@react-native-community/bob": "^0.13.1",
|
||||||
"@react-navigation/native": "^5.3.0",
|
"@react-navigation/native": "^5.3.2",
|
||||||
"@types/react": "^16.9.34",
|
"@types/react": "^16.9.34",
|
||||||
"@types/react-native": "^0.62.7",
|
"@types/react-native": "^0.62.7",
|
||||||
"del-cli": "^3.0.0",
|
"del-cli": "^3.0.0",
|
||||||
|
|||||||
@@ -3,6 +3,22 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [5.2.4](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.2.3...@react-navigation/material-bottom-tabs@5.2.4) (2020-05-14)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.2.3](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.2.2...@react-navigation/material-bottom-tabs@5.2.3) (2020-05-14)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [5.2.2](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.2.1...@react-navigation/material-bottom-tabs@5.2.2) (2020-05-10)
|
## [5.2.2](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.2.1...@react-navigation/material-bottom-tabs@5.2.2) (2020-05-10)
|
||||||
|
|
||||||
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@react-navigation/material-bottom-tabs",
|
"name": "@react-navigation/material-bottom-tabs",
|
||||||
"description": "Integration for bottom navigation component from react-native-paper",
|
"description": "Integration for bottom navigation component from react-native-paper",
|
||||||
"version": "5.2.2",
|
"version": "5.2.4",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"react-native-component",
|
"react-native-component",
|
||||||
"react-component",
|
"react-component",
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@react-native-community/bob": "^0.13.1",
|
"@react-native-community/bob": "^0.13.1",
|
||||||
"@react-navigation/native": "^5.3.0",
|
"@react-navigation/native": "^5.3.2",
|
||||||
"@types/react": "^16.9.34",
|
"@types/react": "^16.9.34",
|
||||||
"@types/react-native": "^0.62.7",
|
"@types/react-native": "^0.62.7",
|
||||||
"@types/react-native-vector-icons": "^6.4.5",
|
"@types/react-native-vector-icons": "^6.4.5",
|
||||||
|
|||||||
@@ -3,6 +3,22 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [5.2.4](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.2.3...@react-navigation/material-top-tabs@5.2.4) (2020-05-14)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.2.3](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.2.2...@react-navigation/material-top-tabs@5.2.3) (2020-05-14)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [5.2.2](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.2.1...@react-navigation/material-top-tabs@5.2.2) (2020-05-10)
|
## [5.2.2](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.2.1...@react-navigation/material-top-tabs@5.2.2) (2020-05-10)
|
||||||
|
|
||||||
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@react-navigation/material-top-tabs",
|
"name": "@react-navigation/material-top-tabs",
|
||||||
"description": "Integration for the animated tab view component from react-native-tab-view",
|
"description": "Integration for the animated tab view component from react-native-tab-view",
|
||||||
"version": "5.2.2",
|
"version": "5.2.4",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"react-native-component",
|
"react-native-component",
|
||||||
"react-component",
|
"react-component",
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@react-native-community/bob": "^0.13.1",
|
"@react-native-community/bob": "^0.13.1",
|
||||||
"@react-navigation/native": "^5.3.0",
|
"@react-navigation/native": "^5.3.2",
|
||||||
"@types/react": "^16.9.34",
|
"@types/react": "^16.9.34",
|
||||||
"@types/react-native": "^0.62.7",
|
"@types/react-native": "^0.62.7",
|
||||||
"del-cli": "^3.0.0",
|
"del-cli": "^3.0.0",
|
||||||
|
|||||||
@@ -3,6 +3,22 @@
|
|||||||
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.3.2](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.3.1...@react-navigation/native@5.3.2) (2020-05-14)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/native
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.3.1](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.3.0...@react-navigation/native@5.3.1) (2020-05-14)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/native
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# [5.3.0](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.2.6...@react-navigation/native@5.3.0) (2020-05-10)
|
# [5.3.0](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.2.6...@react-navigation/native@5.3.0) (2020-05-10)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@react-navigation/native",
|
"name": "@react-navigation/native",
|
||||||
"description": "React Native integration for React Navigation",
|
"description": "React Native integration for React Navigation",
|
||||||
"version": "5.3.0",
|
"version": "5.3.2",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"react-native",
|
"react-native",
|
||||||
"react-navigation",
|
"react-navigation",
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
"clean": "del lib"
|
"clean": "del lib"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@react-navigation/core": "^5.5.2"
|
"@react-navigation/core": "^5.6.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@react-native-community/bob": "^0.13.1",
|
"@react-native-community/bob": "^0.13.1",
|
||||||
|
|||||||
@@ -3,6 +3,22 @@
|
|||||||
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.3.5](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.3.4...@react-navigation/stack@5.3.5) (2020-05-14)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/stack
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.3.4](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.3.3...@react-navigation/stack@5.3.4) (2020-05-14)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/stack
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [5.3.3](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.3.2...@react-navigation/stack@5.3.3) (2020-05-11)
|
## [5.3.3](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.3.2...@react-navigation/stack@5.3.3) (2020-05-11)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@react-navigation/stack",
|
"name": "@react-navigation/stack",
|
||||||
"description": "Stack navigator component for iOS and Android with animated transitions and gestures",
|
"description": "Stack navigator component for iOS and Android with animated transitions and gestures",
|
||||||
"version": "5.3.3",
|
"version": "5.3.5",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"react-native-component",
|
"react-native-component",
|
||||||
"react-component",
|
"react-component",
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@react-native-community/bob": "^0.13.1",
|
"@react-native-community/bob": "^0.13.1",
|
||||||
"@react-native-community/masked-view": "^0.1.10",
|
"@react-native-community/masked-view": "^0.1.10",
|
||||||
"@react-navigation/native": "^5.3.0",
|
"@react-navigation/native": "^5.3.2",
|
||||||
"@types/color": "^3.0.1",
|
"@types/color": "^3.0.1",
|
||||||
"@types/react": "^16.9.34",
|
"@types/react": "^16.9.34",
|
||||||
"@types/react-native": "^0.62.7",
|
"@types/react-native": "^0.62.7",
|
||||||
|
|||||||
Reference in New Issue
Block a user