mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-01-13 22:42:25 +08:00
Compare commits
40 Commits
@react-nav
...
@react-nav
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6bec620a3f | ||
|
|
c7b8e2e966 | ||
|
|
719e1a7b46 | ||
|
|
10eca8b92e | ||
|
|
b66e3436a7 | ||
|
|
1c075ffb16 | ||
|
|
1ee3038a4d | ||
|
|
961b519fb1 | ||
|
|
0a19e94b23 | ||
|
|
1e73fed6de | ||
|
|
3193a30da6 | ||
|
|
499c50cd43 | ||
|
|
420f6926e1 | ||
|
|
70be3f6d86 | ||
|
|
bd35b4fc20 | ||
|
|
c511bc0b2b | ||
|
|
b4834ce703 | ||
|
|
ae5442ebe8 | ||
|
|
6dd52d35cf | ||
|
|
d6fa279d93 | ||
|
|
c3fa83efe0 | ||
|
|
f2291d110f | ||
|
|
942d2be2c7 | ||
|
|
b747e527a4 | ||
|
|
38020de80b | ||
|
|
67404f4999 | ||
|
|
2792f438fe | ||
|
|
2573b5beaa | ||
|
|
2697355ab2 | ||
|
|
a695cf9c05 | ||
|
|
c9c825bee6 | ||
|
|
b172b51f17 | ||
|
|
9c05af50b4 | ||
|
|
24febf6ea9 | ||
|
|
8cbb201f1a | ||
|
|
2467ce4ff7 | ||
|
|
5683bebfd6 | ||
|
|
78485cea69 | ||
|
|
1613915669 | ||
|
|
335a04edc1 |
@@ -16,7 +16,9 @@ jobs:
|
||||
keys:
|
||||
- v1-dependencies-{{ checksum "yarn.lock" }}
|
||||
- v1-dependencies-
|
||||
- run: yarn install --frozen-lockfile
|
||||
- run:
|
||||
name: Install project dependencies
|
||||
command: yarn install --frozen-lockfile
|
||||
- save_cache:
|
||||
key: v1-dependencies-{{ checksum "yarn.lock" }}
|
||||
paths: node_modules
|
||||
@@ -28,28 +30,57 @@ jobs:
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: ~/project
|
||||
- run: |
|
||||
yarn lint
|
||||
yarn typescript
|
||||
- run:
|
||||
name: Lint files
|
||||
command: yarn lint
|
||||
- run:
|
||||
name: Typecheck files
|
||||
command: yarn typescript
|
||||
unit-tests:
|
||||
<<: *defaults
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: ~/project
|
||||
- run: |
|
||||
yarn test --coverage
|
||||
cat ./coverage/lcov.info | ./node_modules/.bin/codecov
|
||||
- run:
|
||||
name: Run unit tests
|
||||
command: yarn test --coverage
|
||||
- run:
|
||||
name: Upload test coverage
|
||||
command: cat ./coverage/lcov.info | ./node_modules/.bin/codecov
|
||||
- store_artifacts:
|
||||
path: coverage
|
||||
destination: coverage
|
||||
integration-tests:
|
||||
<<: *defaults
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: ~/project
|
||||
- run:
|
||||
name: Install Headless Chrome dependencies
|
||||
command: |
|
||||
sudo apt-get install -yq \
|
||||
gconf-service libasound2 libatk1.0-0 libatk-bridge2.0-0 libc6 libcairo2 libcups2 libdbus-1-3 \
|
||||
libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 \
|
||||
libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 \
|
||||
libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 ca-certificates \
|
||||
fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget
|
||||
- run:
|
||||
name: Build example for web
|
||||
command: yarn example expo build:web --no-pwa
|
||||
- run:
|
||||
name: Run integration tests
|
||||
command: yarn example test --maxWorkers=2
|
||||
build-packages:
|
||||
<<: *defaults
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: ~/project
|
||||
- run: |
|
||||
yarn lerna run prepare
|
||||
node scripts/check-types-path.js
|
||||
- run:
|
||||
name: Build packages in the monorepo
|
||||
command: yarn lerna run prepare
|
||||
- run:
|
||||
name: Verify paths for types
|
||||
command: node scripts/check-types-path.js
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
@@ -62,6 +93,9 @@ workflows:
|
||||
- unit-tests:
|
||||
requires:
|
||||
- install-dependencies
|
||||
- integration-tests:
|
||||
requires:
|
||||
- install-dependencies
|
||||
- build-packages:
|
||||
requires:
|
||||
- install-dependencies
|
||||
|
||||
27
.github/workflows/versions.yml
vendored
Normal file
27
.github/workflows/versions.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
name: Check versions
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
|
||||
jobs:
|
||||
check-versions:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: react-navigation/check-versions-action@master
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
packages: |
|
||||
@react-navigation/bottom-tabs
|
||||
@react-navigation/compat
|
||||
@react-navigation/core
|
||||
@react-navigation/drawer
|
||||
@react-navigation/material-bottom-tabs
|
||||
@react-navigation/material-top-tabs
|
||||
@react-navigation/native
|
||||
@react-navigation/routers
|
||||
@react-navigation/stack
|
||||
react-navigation-animated-switch
|
||||
react-navigation-drawer
|
||||
react-navigation-material-bottom-tabs
|
||||
react-navigation-stack
|
||||
react-navigation-tabs
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"settings": {
|
||||
"import/core-modules": [
|
||||
"detox",
|
||||
"detox/runners/jest/adapter",
|
||||
"detox/runners/jest/specReporter"
|
||||
]
|
||||
},
|
||||
"env": { "jest": true, "jasmine": true }
|
||||
}
|
||||
44
example/e2e/__integration_tests__/Link.test.tsx
Normal file
44
example/e2e/__integration_tests__/Link.test.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import { page } from '../config/setup-playwright';
|
||||
|
||||
beforeEach(async () => {
|
||||
await page.click('[data-testid=LinkComponent]');
|
||||
});
|
||||
|
||||
it('loads the article page', async () => {
|
||||
expect(await page.evaluate(() => location.pathname + location.search)).toBe(
|
||||
'/link-component/Article?author=Gandalf'
|
||||
);
|
||||
expect(
|
||||
((await page.accessibility.snapshot()) as any)?.children?.find(
|
||||
(it: any) => it.role === 'heading'
|
||||
)?.name
|
||||
).toBe('Article by Gandalf');
|
||||
});
|
||||
|
||||
it('goes to the album page and goes back', async () => {
|
||||
await page.click('[href="/link-component/Album"]');
|
||||
|
||||
expect(await page.evaluate(() => location.pathname + location.search)).toBe(
|
||||
'/link-component/Album'
|
||||
);
|
||||
|
||||
expect(
|
||||
((await page.accessibility.snapshot()) as any)?.children?.find(
|
||||
(it: any) => it.role === 'heading'
|
||||
)?.name
|
||||
).toBe('Album');
|
||||
|
||||
await page.click('[aria-label="Article by Gandalf, back"]');
|
||||
|
||||
await page.waitForNavigation();
|
||||
|
||||
expect(await page.evaluate(() => location.pathname + location.search)).toBe(
|
||||
'/link-component/Article?author=Gandalf'
|
||||
);
|
||||
|
||||
expect(
|
||||
((await page.accessibility.snapshot()) as any)?.children?.find(
|
||||
(it: any) => it.role === 'heading'
|
||||
)?.name
|
||||
).toBe('Article by Gandalf');
|
||||
});
|
||||
@@ -1,9 +0,0 @@
|
||||
import { by, element, expect, device } from 'detox';
|
||||
|
||||
beforeEach(async () => {
|
||||
await device.reloadReactNative();
|
||||
});
|
||||
|
||||
it('has dark theme toggle', async () => {
|
||||
await expect(element(by.text('Dark theme'))).toBeVisible();
|
||||
});
|
||||
13
example/e2e/__integration_tests__/index.test.tsx
Normal file
13
example/e2e/__integration_tests__/index.test.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import { page } from '../config/setup-playwright';
|
||||
|
||||
it('loads the example app', async () => {
|
||||
const snapshot = await page.accessibility.snapshot();
|
||||
|
||||
// @ts-ignore
|
||||
expect(snapshot?.children?.find((it) => it.role === 'heading')?.name).toBe(
|
||||
'Examples'
|
||||
);
|
||||
const title = await page.$eval('[role=heading]', (el) => el.textContent);
|
||||
|
||||
expect(title).toBe('Examples');
|
||||
});
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"setupFilesAfterEnv": ["./init.js"],
|
||||
"testEnvironment": "node",
|
||||
"reporters": ["detox/runners/jest/streamlineReporter"],
|
||||
"verbose": true
|
||||
}
|
||||
24
example/e2e/config/setup-playwright.tsx
Normal file
24
example/e2e/config/setup-playwright.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
/* eslint-env jest */
|
||||
|
||||
import { chromium, Browser, BrowserContext, Page } from 'playwright';
|
||||
|
||||
let browser: Browser;
|
||||
let context: BrowserContext;
|
||||
let page: Page;
|
||||
|
||||
beforeAll(async () => {
|
||||
browser = await chromium.launch();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await browser.close();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
context = await browser.newContext();
|
||||
page = await context.newPage();
|
||||
|
||||
await page.goto('http://localhost:3579');
|
||||
});
|
||||
|
||||
export { browser, context, page };
|
||||
8
example/e2e/config/setup-server.tsx
Normal file
8
example/e2e/config/setup-server.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
import { setup } from 'jest-dev-server';
|
||||
|
||||
export default async function () {
|
||||
await setup({
|
||||
command: 'yarn serve -l 3579 web-build',
|
||||
port: 3579,
|
||||
});
|
||||
}
|
||||
5
example/e2e/config/teardown-server.tsx
Normal file
5
example/e2e/config/teardown-server.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import { teardown } from 'jest-dev-server';
|
||||
|
||||
export default async function () {
|
||||
await teardown();
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
/* eslint-disable import/no-commonjs */
|
||||
|
||||
const detox = require('detox');
|
||||
const config = require('../../package.json').detox;
|
||||
const adapter = require('detox/runners/jest/adapter');
|
||||
const specReporter = require('detox/runners/jest/specReporter');
|
||||
|
||||
// Set the default timeout
|
||||
jest.setTimeout(120000);
|
||||
|
||||
jasmine.getEnv().addReporter(adapter);
|
||||
|
||||
// This takes care of generating status logs on a per-spec basis. By default, jest only reports at file-level.
|
||||
// This is strictly optional.
|
||||
jasmine.getEnv().addReporter(specReporter);
|
||||
|
||||
beforeAll(async () => {
|
||||
await detox.init(config);
|
||||
}, 300000);
|
||||
|
||||
beforeEach(async () => {
|
||||
await adapter.beforeEach();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await adapter.afterAll();
|
||||
await detox.cleanup();
|
||||
});
|
||||
6
example/jest.config.js
Normal file
6
example/jest.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
testRegex: '/__integration_tests__/.*\\.(test|spec)\\.(js|tsx?)$',
|
||||
globalSetup: './e2e/config/setup-server.tsx',
|
||||
globalTeardown: './e2e/config/teardown-server.tsx',
|
||||
setupFilesAfterEnv: ['./e2e/config/setup-playwright.tsx'],
|
||||
};
|
||||
@@ -8,7 +8,8 @@
|
||||
"web": "expo start:web",
|
||||
"native": "react-native start",
|
||||
"android": "react-native run-android",
|
||||
"ios": "react-native run-ios"
|
||||
"ios": "react-native run-ios",
|
||||
"test": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@expo/vector-icons": "^10.0.0",
|
||||
@@ -32,10 +33,15 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@expo/webpack-config": "^0.11.19",
|
||||
"@types/jest-dev-server": "^4.2.0",
|
||||
"@types/react": "^16.9.23",
|
||||
"@types/react-native": "^0.60.22",
|
||||
"babel-preset-expo": "^8.1.0",
|
||||
"expo-cli": "^3.17.18",
|
||||
"jest": "^25.2.7",
|
||||
"jest-dev-server": "^4.4.0",
|
||||
"playwright": "^0.14.0",
|
||||
"serve": "^11.3.0",
|
||||
"typescript": "^3.8.3"
|
||||
}
|
||||
}
|
||||
|
||||
3
example/src/AsyncStorage.native.tsx
Normal file
3
example/src/AsyncStorage.native.tsx
Normal file
@@ -0,0 +1,3 @@
|
||||
import { AsyncStorage } from 'react-native';
|
||||
|
||||
export default AsyncStorage;
|
||||
14
example/src/AsyncStorage.tsx
Normal file
14
example/src/AsyncStorage.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
export default {
|
||||
getItem(key: string) {
|
||||
return Promise.resolve(localStorage.getItem(key));
|
||||
},
|
||||
setItem(key: string, value: string) {
|
||||
return Promise.resolve(localStorage.setItem(key, value));
|
||||
},
|
||||
removeItem(key: string) {
|
||||
return Promise.resolve(localStorage.removeItem(key));
|
||||
},
|
||||
clear() {
|
||||
return Promise.resolve(localStorage.clear());
|
||||
},
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import { Platform } from 'react-native';
|
||||
import { MaterialCommunityIcons } from '@expo/vector-icons';
|
||||
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
|
||||
import TouchableBounce from '../Shared/TouchableBounce';
|
||||
@@ -28,7 +29,10 @@ export default function BottomTabsScreen() {
|
||||
return (
|
||||
<BottomTabs.Navigator
|
||||
screenOptions={{
|
||||
tabBarButton: (props) => <TouchableBounce {...props} />,
|
||||
tabBarButton:
|
||||
Platform.OS === 'web'
|
||||
? undefined
|
||||
: (props) => <TouchableBounce {...props} />,
|
||||
}}
|
||||
>
|
||||
<BottomTabs.Screen
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import { View, StyleSheet } from 'react-native';
|
||||
import { View, ScrollView, StyleSheet, Platform } from 'react-native';
|
||||
import { Button } from 'react-native-paper';
|
||||
import {
|
||||
createCompatNavigatorFactory,
|
||||
@@ -11,25 +11,32 @@ import {
|
||||
} from '@react-navigation/stack';
|
||||
import Article from '../Shared/Article';
|
||||
import Albums from '../Shared/Albums';
|
||||
import NewsFeed from '../Shared/NewsFeed';
|
||||
|
||||
type CompatStackParams = {
|
||||
Article: { author: string };
|
||||
Album: undefined;
|
||||
Albums: undefined;
|
||||
Nested: { author: string };
|
||||
};
|
||||
|
||||
const ArticleScreen: CompatScreenType<StackNavigationProp<
|
||||
CompatStackParams,
|
||||
'Article'
|
||||
type NestedStackParams = {
|
||||
Feed: undefined;
|
||||
Article: { author: string };
|
||||
};
|
||||
|
||||
const scrollEnabled = Platform.select({ web: true, default: false });
|
||||
|
||||
const AlbumsScreen: CompatScreenType<StackNavigationProp<
|
||||
CompatStackParams
|
||||
>> = ({ navigation }) => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<ScrollView>
|
||||
<View style={styles.buttons}>
|
||||
<Button
|
||||
mode="contained"
|
||||
onPress={() => navigation.push('Album')}
|
||||
onPress={() => navigation.push('Nested', { author: 'Babel fish' })}
|
||||
style={styles.button}
|
||||
>
|
||||
Push album
|
||||
Push nested
|
||||
</Button>
|
||||
<Button
|
||||
mode="outlined"
|
||||
@@ -39,24 +46,20 @@ const ArticleScreen: CompatScreenType<StackNavigationProp<
|
||||
Go back
|
||||
</Button>
|
||||
</View>
|
||||
<Article author={{ name: navigation.getParam('author') }} />
|
||||
</React.Fragment>
|
||||
<Albums scrollEnabled={scrollEnabled} />
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
ArticleScreen.navigationOptions = ({ navigation }) => ({
|
||||
title: `Article by ${navigation.getParam('author')}`,
|
||||
});
|
||||
|
||||
const AlbumsScreen: CompatScreenType<StackNavigationProp<
|
||||
CompatStackParams
|
||||
>> = ({ navigation }) => {
|
||||
const FeedScreen: CompatScreenType<StackNavigationProp<NestedStackParams>> = ({
|
||||
navigation,
|
||||
}) => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<ScrollView>
|
||||
<View style={styles.buttons}>
|
||||
<Button
|
||||
mode="contained"
|
||||
onPress={() => navigation.push('Article', { author: 'Babel fish' })}
|
||||
onPress={() => navigation.push('Article')}
|
||||
style={styles.button}
|
||||
>
|
||||
Push article
|
||||
@@ -69,22 +72,69 @@ const AlbumsScreen: CompatScreenType<StackNavigationProp<
|
||||
Go back
|
||||
</Button>
|
||||
</View>
|
||||
<Albums />
|
||||
</React.Fragment>
|
||||
<NewsFeed scrollEnabled={scrollEnabled} />
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
const CompatStack = createCompatNavigatorFactory(createStackNavigator)<
|
||||
const ArticleScreen: CompatScreenType<StackNavigationProp<
|
||||
NestedStackParams,
|
||||
'Article'
|
||||
>> = ({ navigation }) => {
|
||||
navigation.dangerouslyGetParent();
|
||||
return (
|
||||
<ScrollView>
|
||||
<View style={styles.buttons}>
|
||||
<Button
|
||||
mode="contained"
|
||||
onPress={() => navigation.push('Albums')}
|
||||
style={styles.button}
|
||||
>
|
||||
Push albums
|
||||
</Button>
|
||||
<Button
|
||||
mode="outlined"
|
||||
onPress={() => navigation.goBack()}
|
||||
style={styles.button}
|
||||
>
|
||||
Go back
|
||||
</Button>
|
||||
</View>
|
||||
<Article
|
||||
author={{ name: navigation.getParam('author') }}
|
||||
scrollEnabled={scrollEnabled}
|
||||
/>
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
ArticleScreen.navigationOptions = ({ navigation }) => ({
|
||||
title: `Article by ${navigation.getParam('author')}`,
|
||||
});
|
||||
|
||||
const createCompatStackNavigator = createCompatNavigatorFactory(
|
||||
createStackNavigator
|
||||
);
|
||||
|
||||
const CompatStack = createCompatStackNavigator<
|
||||
StackNavigationProp<CompatStackParams>
|
||||
>(
|
||||
{
|
||||
Article: {
|
||||
screen: ArticleScreen,
|
||||
Albums: AlbumsScreen,
|
||||
Nested: {
|
||||
screen: createCompatStackNavigator<
|
||||
StackNavigationProp<NestedStackParams>
|
||||
>(
|
||||
{
|
||||
Feed: FeedScreen,
|
||||
Article: ArticleScreen,
|
||||
},
|
||||
{ navigationOptions: { headerShown: false } }
|
||||
),
|
||||
params: {
|
||||
author: 'Gandalf',
|
||||
},
|
||||
},
|
||||
Album: AlbumsScreen,
|
||||
},
|
||||
{
|
||||
mode: 'modal',
|
||||
|
||||
162
example/src/Screens/LinkComponent.tsx
Normal file
162
example/src/Screens/LinkComponent.tsx
Normal file
@@ -0,0 +1,162 @@
|
||||
import * as React from 'react';
|
||||
import { View, StyleSheet, ScrollView, Platform } from 'react-native';
|
||||
import { Button } from 'react-native-paper';
|
||||
import {
|
||||
Link,
|
||||
StackActions,
|
||||
RouteProp,
|
||||
ParamListBase,
|
||||
useLinkProps,
|
||||
} from '@react-navigation/native';
|
||||
import {
|
||||
createStackNavigator,
|
||||
StackNavigationProp,
|
||||
} 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 scrollEnabled = Platform.select({ web: true, default: false });
|
||||
|
||||
const LinkButton = ({
|
||||
to,
|
||||
...rest
|
||||
}: React.ComponentProps<typeof Button> & { to: string }) => {
|
||||
const { onPress, ...props } = useLinkProps({ to });
|
||||
|
||||
return (
|
||||
<Button
|
||||
{...props}
|
||||
{...rest}
|
||||
{...Platform.select({
|
||||
web: { onClick: onPress } as any,
|
||||
default: { onPress },
|
||||
})}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const ArticleScreen = ({
|
||||
navigation,
|
||||
route,
|
||||
}: {
|
||||
navigation: SimpleStackNavigation;
|
||||
route: RouteProp<SimpleStackParams, 'Article'>;
|
||||
}) => {
|
||||
return (
|
||||
<ScrollView>
|
||||
<View style={styles.buttons}>
|
||||
<Link
|
||||
to="/link-component/Album"
|
||||
style={[styles.button, { padding: 8 }]}
|
||||
>
|
||||
Go to /link-component/Album
|
||||
</Link>
|
||||
<Link
|
||||
to="/link-component/Album"
|
||||
action={StackActions.replace('Album')}
|
||||
style={[styles.button, { padding: 8 }]}
|
||||
>
|
||||
Replace with /link-component/Album
|
||||
</Link>
|
||||
<LinkButton
|
||||
to="/link-component/Album"
|
||||
mode="contained"
|
||||
style={styles.button}
|
||||
>
|
||||
Go to /link-component/Album
|
||||
</LinkButton>
|
||||
<Button
|
||||
mode="outlined"
|
||||
onPress={() => navigation.goBack()}
|
||||
style={styles.button}
|
||||
>
|
||||
Go back
|
||||
</Button>
|
||||
</View>
|
||||
<Article
|
||||
author={{ name: route.params.author }}
|
||||
scrollEnabled={scrollEnabled}
|
||||
/>
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
const AlbumsScreen = ({
|
||||
navigation,
|
||||
}: {
|
||||
navigation: SimpleStackNavigation;
|
||||
}) => {
|
||||
return (
|
||||
<ScrollView>
|
||||
<View style={styles.buttons}>
|
||||
<Link
|
||||
to="/link-component/Article?author=Babel"
|
||||
style={[styles.button, { padding: 8 }]}
|
||||
>
|
||||
Go to /link-component/Article
|
||||
</Link>
|
||||
<LinkButton
|
||||
to="/link-component/Article?author=Babel"
|
||||
mode="contained"
|
||||
style={styles.button}
|
||||
>
|
||||
Go to /link-component/Article
|
||||
</LinkButton>
|
||||
<Button
|
||||
mode="outlined"
|
||||
onPress={() => navigation.goBack()}
|
||||
style={styles.button}
|
||||
>
|
||||
Go back
|
||||
</Button>
|
||||
</View>
|
||||
<Albums scrollEnabled={scrollEnabled} />
|
||||
</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}`,
|
||||
})}
|
||||
initialParams={{ author: 'Gandalf' }}
|
||||
/>
|
||||
<SimpleStack.Screen
|
||||
name="Album"
|
||||
component={AlbumsScreen}
|
||||
options={{ title: 'Album' }}
|
||||
/>
|
||||
</SimpleStack.Navigator>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
buttons: {
|
||||
padding: 8,
|
||||
},
|
||||
button: {
|
||||
margin: 8,
|
||||
},
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import { View, StyleSheet, ScrollView } from 'react-native';
|
||||
import { View, StyleSheet, ScrollView, Platform } from 'react-native';
|
||||
import { Button } from 'react-native-paper';
|
||||
import { RouteProp, ParamListBase } from '@react-navigation/native';
|
||||
import {
|
||||
@@ -17,6 +17,8 @@ type ModalStackParams = {
|
||||
|
||||
type ModalStackNavigation = StackNavigationProp<ModalStackParams>;
|
||||
|
||||
const scrollEnabled = Platform.select({ web: true, default: false });
|
||||
|
||||
const ArticleScreen = ({
|
||||
navigation,
|
||||
route,
|
||||
@@ -42,7 +44,10 @@ const ArticleScreen = ({
|
||||
Go back
|
||||
</Button>
|
||||
</View>
|
||||
<Article author={{ name: route.params.author }} scrollEnabled={false} />
|
||||
<Article
|
||||
author={{ name: route.params.author }}
|
||||
scrollEnabled={scrollEnabled}
|
||||
/>
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
@@ -66,7 +71,7 @@ const AlbumsScreen = ({ navigation }: { navigation: ModalStackNavigation }) => {
|
||||
Go back
|
||||
</Button>
|
||||
</View>
|
||||
<Albums scrollEnabled={false} />
|
||||
<Albums scrollEnabled={scrollEnabled} />
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import { View, StyleSheet, ScrollView } from 'react-native';
|
||||
import { View, Platform, StyleSheet, ScrollView } from 'react-native';
|
||||
import { Button } from 'react-native-paper';
|
||||
import { RouteProp, ParamListBase } from '@react-navigation/native';
|
||||
import {
|
||||
@@ -18,6 +18,8 @@ type SimpleStackParams = {
|
||||
|
||||
type SimpleStackNavigation = StackNavigationProp<SimpleStackParams>;
|
||||
|
||||
const scrollEnabled = Platform.select({ web: true, default: false });
|
||||
|
||||
const ArticleScreen = ({
|
||||
navigation,
|
||||
route,
|
||||
@@ -43,7 +45,10 @@ const ArticleScreen = ({
|
||||
Pop screen
|
||||
</Button>
|
||||
</View>
|
||||
<Article author={{ name: route.params.author }} scrollEnabled={false} />
|
||||
<Article
|
||||
author={{ name: route.params.author }}
|
||||
scrollEnabled={scrollEnabled}
|
||||
/>
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
@@ -71,7 +76,7 @@ const NewsFeedScreen = ({
|
||||
Go back
|
||||
</Button>
|
||||
</View>
|
||||
<NewsFeed scrollEnabled={false} />
|
||||
<NewsFeed scrollEnabled={scrollEnabled} />
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
@@ -99,7 +104,7 @@ const AlbumsScreen = ({
|
||||
Pop by 2
|
||||
</Button>
|
||||
</View>
|
||||
<Albums scrollEnabled={false} />
|
||||
<Albums scrollEnabled={scrollEnabled} />
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -20,6 +20,8 @@ type SimpleStackParams = {
|
||||
|
||||
type SimpleStackNavigation = StackNavigationProp<SimpleStackParams>;
|
||||
|
||||
const scrollEnabled = Platform.select({ web: true, default: false });
|
||||
|
||||
const ArticleScreen = ({
|
||||
navigation,
|
||||
route,
|
||||
@@ -45,7 +47,10 @@ const ArticleScreen = ({
|
||||
Go back
|
||||
</Button>
|
||||
</View>
|
||||
<Article author={{ name: route.params.author }} scrollEnabled={false} />
|
||||
<Article
|
||||
author={{ name: route.params.author }}
|
||||
scrollEnabled={scrollEnabled}
|
||||
/>
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
@@ -75,7 +80,7 @@ const AlbumsScreen = ({
|
||||
Go back
|
||||
</Button>
|
||||
</View>
|
||||
<Albums scrollEnabled={false} />
|
||||
<Albums scrollEnabled={scrollEnabled} />
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import { View, StyleSheet, ScrollView } from 'react-native';
|
||||
import { View, StyleSheet, ScrollView, Platform } from 'react-native';
|
||||
import { Button, Paragraph } from 'react-native-paper';
|
||||
import { RouteProp, ParamListBase, useTheme } from '@react-navigation/native';
|
||||
import {
|
||||
@@ -15,6 +15,8 @@ type SimpleStackParams = {
|
||||
|
||||
type SimpleStackNavigation = StackNavigationProp<SimpleStackParams>;
|
||||
|
||||
const scrollEnabled = Platform.select({ web: true, default: false });
|
||||
|
||||
const ArticleScreen = ({
|
||||
navigation,
|
||||
route,
|
||||
@@ -40,7 +42,10 @@ const ArticleScreen = ({
|
||||
Go back
|
||||
</Button>
|
||||
</View>
|
||||
<Article author={{ name: route.params.author }} scrollEnabled={false} />
|
||||
<Article
|
||||
author={{ name: route.params.author }}
|
||||
scrollEnabled={scrollEnabled}
|
||||
/>
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import {
|
||||
ScrollView,
|
||||
AsyncStorage,
|
||||
YellowBox,
|
||||
Platform,
|
||||
StatusBar,
|
||||
@@ -22,11 +21,10 @@ import {
|
||||
Appbar,
|
||||
List,
|
||||
Divider,
|
||||
Text,
|
||||
} from 'react-native-paper';
|
||||
import {
|
||||
InitialState,
|
||||
useLinking,
|
||||
NavigationContainerRef,
|
||||
NavigationContainer,
|
||||
DefaultTheme,
|
||||
DarkTheme,
|
||||
@@ -42,6 +40,7 @@ import {
|
||||
HeaderStyleInterpolators,
|
||||
} from '@react-navigation/stack';
|
||||
|
||||
import AsyncStorage from './AsyncStorage';
|
||||
import LinkingPrefixes from './LinkingPrefixes';
|
||||
import SettingsItem from './Shared/SettingsItem';
|
||||
import SimpleStack from './Screens/SimpleStack';
|
||||
@@ -55,11 +54,15 @@ import DynamicTabs from './Screens/DynamicTabs';
|
||||
import AuthFlow from './Screens/AuthFlow';
|
||||
import CompatAPI from './Screens/CompatAPI';
|
||||
import MasterDetail from './Screens/MasterDetail';
|
||||
import LinkComponent from './Screens/LinkComponent';
|
||||
|
||||
YellowBox.ignoreWarnings(['Require cycle:', 'Warning: Async Storage']);
|
||||
|
||||
enableScreens();
|
||||
|
||||
// @ts-ignore
|
||||
global.REACT_NAVIGATION_REDUX_DEVTOOLS_EXTENSION_INTEGRATION_ENABLED = true;
|
||||
|
||||
type RootDrawerParamList = {
|
||||
Root: undefined;
|
||||
Another: undefined;
|
||||
@@ -110,6 +113,10 @@ const SCREENS = {
|
||||
title: 'Compat Layer',
|
||||
component: CompatAPI,
|
||||
},
|
||||
LinkComponent: {
|
||||
title: '<Link />',
|
||||
component: LinkComponent,
|
||||
},
|
||||
};
|
||||
|
||||
const Drawer = createDrawerNavigator<RootDrawerParamList>();
|
||||
@@ -121,36 +128,6 @@ const THEME_PERSISTENCE_KEY = 'THEME_TYPE';
|
||||
Asset.loadAsync(StackAssets);
|
||||
|
||||
export default function App() {
|
||||
const containerRef = React.useRef<NavigationContainerRef>(null);
|
||||
|
||||
// To test deep linking on, run the following in the Terminal:
|
||||
// Android: adb shell am start -a android.intent.action.VIEW -d "exp://127.0.0.1:19000/--/simple-stack"
|
||||
// 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`)
|
||||
const { getInitialState } = useLinking(containerRef, {
|
||||
prefixes: LinkingPrefixes,
|
||||
config: {
|
||||
Root: {
|
||||
path: '',
|
||||
initialRouteName: 'Home',
|
||||
screens: Object.keys(SCREENS).reduce<{ [key: string]: string }>(
|
||||
(acc, name) => {
|
||||
// Convert screen names such as SimpleStack to kebab case (simple-stack)
|
||||
acc[name] = name
|
||||
.replace(/([A-Z]+)/g, '-$1')
|
||||
.replace(/^-/, '')
|
||||
.toLowerCase();
|
||||
|
||||
return acc;
|
||||
},
|
||||
{ Home: '' }
|
||||
),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const [theme, setTheme] = React.useState(DefaultTheme);
|
||||
|
||||
const [isReady, setIsReady] = React.useState(false);
|
||||
@@ -161,12 +138,13 @@ export default function App() {
|
||||
React.useEffect(() => {
|
||||
const restoreState = async () => {
|
||||
try {
|
||||
let state = await getInitialState();
|
||||
let state;
|
||||
|
||||
if (Platform.OS !== 'web' && state === undefined) {
|
||||
const savedState = await AsyncStorage.getItem(
|
||||
NAVIGATION_PERSISTENCE_KEY
|
||||
);
|
||||
|
||||
state = savedState ? JSON.parse(savedState) : undefined;
|
||||
}
|
||||
|
||||
@@ -187,7 +165,7 @@ export default function App() {
|
||||
};
|
||||
|
||||
restoreState();
|
||||
}, [getInitialState]);
|
||||
}, []);
|
||||
|
||||
const paperTheme = React.useMemo(() => {
|
||||
const t = theme.dark ? PaperDarkTheme : PaperLightTheme;
|
||||
@@ -227,7 +205,6 @@ export default function App() {
|
||||
<StatusBar barStyle={theme.dark ? 'light-content' : 'dark-content'} />
|
||||
)}
|
||||
<NavigationContainer
|
||||
ref={containerRef}
|
||||
initialState={initialState}
|
||||
onStateChange={(state) =>
|
||||
AsyncStorage.setItem(
|
||||
@@ -236,6 +213,34 @@ export default function App() {
|
||||
)
|
||||
}
|
||||
theme={theme}
|
||||
linking={{
|
||||
// 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"
|
||||
// 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`)
|
||||
prefixes: LinkingPrefixes,
|
||||
config: {
|
||||
Root: {
|
||||
path: '',
|
||||
initialRouteName: 'Home',
|
||||
screens: Object.keys(SCREENS).reduce<{ [key: string]: string }>(
|
||||
(acc, name) => {
|
||||
// Convert screen names such as SimpleStack to kebab case (simple-stack)
|
||||
acc[name] = name
|
||||
.replace(/([A-Z]+)/g, '-$1')
|
||||
.replace(/^-/, '')
|
||||
.toLowerCase();
|
||||
|
||||
return acc;
|
||||
},
|
||||
{ Home: '' }
|
||||
),
|
||||
},
|
||||
},
|
||||
}}
|
||||
fallback={<Text>Loading…</Text>}
|
||||
>
|
||||
<Drawer.Navigator drawerType={isLargeScreen ? 'permanent' : undefined}>
|
||||
<Drawer.Screen
|
||||
@@ -311,6 +316,7 @@ export default function App() {
|
||||
(name) => (
|
||||
<List.Item
|
||||
key={name}
|
||||
testID={name}
|
||||
title={SCREENS[name].title}
|
||||
onPress={() => navigation.navigate(name)}
|
||||
/>
|
||||
|
||||
1
example/web/_redirects
Normal file
1
example/web/_redirects
Normal file
@@ -0,0 +1 @@
|
||||
/* /index.html 200
|
||||
@@ -3,6 +3,50 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [5.3.3](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.3.2...@react-navigation/bottom-tabs@5.3.3) (2020-05-01)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.3.2](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.3.1...@react-navigation/bottom-tabs@5.3.2) (2020-05-01)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.3.1](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.3.0...@react-navigation/bottom-tabs@5.3.1) (2020-04-30)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.3.0](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.2.8...@react-navigation/bottom-tabs@5.3.0) (2020-04-30)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add `useLinkBuilder` hook to build links ([2792f43](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/commit/2792f438fe45428fe193e3708fee7ad61966cbf4))
|
||||
* add action prop to Link ([942d2be](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/commit/942d2be2c72720469475ce12ec8df23825994dbf))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.8](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.2.7...@react-navigation/bottom-tabs@5.2.8) (2020-04-27)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.7](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.2.6...@react-navigation/bottom-tabs@5.2.7) (2020-04-17)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/bottom-tabs",
|
||||
"description": "Bottom tab navigator following iOS design guidelines",
|
||||
"version": "5.2.7",
|
||||
"version": "5.3.3",
|
||||
"keywords": [
|
||||
"react-native-component",
|
||||
"react-component",
|
||||
@@ -35,7 +35,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.10.0",
|
||||
"@react-navigation/native": "^5.1.6",
|
||||
"@react-navigation/native": "^5.2.3",
|
||||
"@types/color": "^3.0.1",
|
||||
"@types/react": "^16.9.23",
|
||||
"@types/react-native": "^0.61.22",
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
StyleProp,
|
||||
TextStyle,
|
||||
ViewStyle,
|
||||
GestureResponderEvent,
|
||||
} from 'react-native';
|
||||
import {
|
||||
NavigationHelpers,
|
||||
@@ -196,6 +197,13 @@ export type BottomTabBarProps = BottomTabBarOptions & {
|
||||
navigation: NavigationHelpers<ParamListBase, BottomTabNavigationEventMap>;
|
||||
};
|
||||
|
||||
export type BottomTabBarButtonProps = TouchableWithoutFeedbackProps & {
|
||||
export type BottomTabBarButtonProps = Omit<
|
||||
TouchableWithoutFeedbackProps,
|
||||
'onPress'
|
||||
> & {
|
||||
to?: string;
|
||||
children: React.ReactNode;
|
||||
onPress?: (
|
||||
e: React.MouseEvent<HTMLAnchorElement, MouseEvent> | GestureResponderEvent
|
||||
) => void;
|
||||
};
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
NavigationRouteContext,
|
||||
CommonActions,
|
||||
useTheme,
|
||||
useLinkBuilder,
|
||||
} from '@react-navigation/native';
|
||||
import { useSafeArea } from 'react-native-safe-area-context';
|
||||
|
||||
@@ -50,6 +51,7 @@ export default function BottomTabBar({
|
||||
tabStyle,
|
||||
}: Props) {
|
||||
const { colors } = useTheme();
|
||||
const buildLink = useLinkBuilder();
|
||||
|
||||
const [dimensions, setDimensions] = React.useState(() => {
|
||||
const { height = 0, width = 0 } = Dimensions.get('window');
|
||||
@@ -260,6 +262,7 @@ export default function BottomTabBar({
|
||||
onPress={onPress}
|
||||
onLongPress={onLongPress}
|
||||
accessibilityLabel={accessibilityLabel}
|
||||
to={buildLink(route.name, route.params)}
|
||||
testID={options.tabBarTestID}
|
||||
allowFontScaling={allowFontScaling}
|
||||
activeTintColor={activeTintColor}
|
||||
|
||||
@@ -4,11 +4,13 @@ import {
|
||||
TouchableWithoutFeedback,
|
||||
Animated,
|
||||
StyleSheet,
|
||||
Platform,
|
||||
StyleProp,
|
||||
ViewStyle,
|
||||
TextStyle,
|
||||
GestureResponderEvent,
|
||||
} from 'react-native';
|
||||
import { Route, useTheme } from '@react-navigation/native';
|
||||
import { Link, Route, useTheme } from '@react-navigation/native';
|
||||
import Color from 'color';
|
||||
|
||||
import TabBarIcon from './TabBarIcon';
|
||||
@@ -37,6 +39,10 @@ type Props = {
|
||||
size: number;
|
||||
color: string;
|
||||
}) => React.ReactNode;
|
||||
/**
|
||||
* URL to use for the link to the tab.
|
||||
*/
|
||||
to?: string;
|
||||
/**
|
||||
* The button for the tab. Uses a `TouchableWithoutFeedback` by default.
|
||||
*/
|
||||
@@ -50,13 +56,16 @@ type Props = {
|
||||
*/
|
||||
testID?: string;
|
||||
/**
|
||||
* Function to execute on press.
|
||||
* Function to execute on press in React Native.
|
||||
* On the web, this will use onClick.
|
||||
*/
|
||||
onPress: () => void;
|
||||
onPress: (
|
||||
e: React.MouseEvent<HTMLElement, MouseEvent> | GestureResponderEvent
|
||||
) => void;
|
||||
/**
|
||||
* Function to execute on long press.
|
||||
*/
|
||||
onLongPress: () => void;
|
||||
onLongPress: (e: GestureResponderEvent) => void;
|
||||
/**
|
||||
* Whether the label should be aligned with the icon horizontally.
|
||||
*/
|
||||
@@ -104,11 +113,48 @@ export default function BottomTabBarItem({
|
||||
route,
|
||||
label,
|
||||
icon,
|
||||
button = ({ children, style, ...rest }: BottomTabBarButtonProps) => (
|
||||
<TouchableWithoutFeedback {...rest}>
|
||||
<View style={style}>{children}</View>
|
||||
</TouchableWithoutFeedback>
|
||||
),
|
||||
to,
|
||||
button = ({
|
||||
children,
|
||||
style,
|
||||
onPress,
|
||||
to,
|
||||
accessibilityRole,
|
||||
...rest
|
||||
}: BottomTabBarButtonProps) => {
|
||||
if (Platform.OS === 'web' && to) {
|
||||
// React Native Web doesn't forward `onClick` if we use `TouchableWithoutFeedback`.
|
||||
// We need to use `onClick` to be able to prevent default browser handling of links.
|
||||
return (
|
||||
<Link
|
||||
{...rest}
|
||||
to={to}
|
||||
style={[styles.button, style]}
|
||||
onPress={(e: any) => {
|
||||
if (
|
||||
!(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) && // ignore clicks with modifier keys
|
||||
(e.button == null || e.button === 0) // ignore everything but left clicks
|
||||
) {
|
||||
e.preventDefault();
|
||||
onPress?.(e);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<TouchableWithoutFeedback
|
||||
{...rest}
|
||||
accessibilityRole={accessibilityRole}
|
||||
onPress={onPress}
|
||||
>
|
||||
<View style={style}>{children}</View>
|
||||
</TouchableWithoutFeedback>
|
||||
);
|
||||
}
|
||||
},
|
||||
accessibilityLabel,
|
||||
testID,
|
||||
onPress,
|
||||
@@ -196,6 +242,7 @@ export default function BottomTabBarItem({
|
||||
: inactiveBackgroundColor;
|
||||
|
||||
return button({
|
||||
to,
|
||||
onPress,
|
||||
onLongPress,
|
||||
testID,
|
||||
@@ -248,4 +295,7 @@ const styles = StyleSheet.create({
|
||||
fontSize: 12,
|
||||
marginLeft: 20,
|
||||
},
|
||||
button: {
|
||||
display: 'flex',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import * as React from 'react';
|
||||
import { View, StyleSheet } from 'react-native';
|
||||
|
||||
import { TabNavigationState, useTheme } from '@react-navigation/native';
|
||||
import {
|
||||
NavigationHelpersContext,
|
||||
TabNavigationState,
|
||||
useTheme,
|
||||
} from '@react-navigation/native';
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
import { ScreenContainer } from 'react-native-screens';
|
||||
|
||||
@@ -91,44 +95,46 @@ export default class BottomTabView extends React.Component<Props, State> {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { state, descriptors, lazy } = this.props;
|
||||
const { state, descriptors, navigation, lazy } = this.props;
|
||||
const { routes } = state;
|
||||
const { loaded } = this.state;
|
||||
|
||||
return (
|
||||
<SafeAreaProviderCompat>
|
||||
<View style={styles.container}>
|
||||
<ScreenContainer style={styles.pages}>
|
||||
{routes.map((route, index) => {
|
||||
const descriptor = descriptors[route.key];
|
||||
const { unmountOnBlur } = descriptor.options;
|
||||
const isFocused = state.index === index;
|
||||
<NavigationHelpersContext.Provider value={navigation}>
|
||||
<SafeAreaProviderCompat>
|
||||
<View style={styles.container}>
|
||||
<ScreenContainer style={styles.pages}>
|
||||
{routes.map((route, index) => {
|
||||
const descriptor = descriptors[route.key];
|
||||
const { unmountOnBlur } = descriptor.options;
|
||||
const isFocused = state.index === index;
|
||||
|
||||
if (unmountOnBlur && !isFocused) {
|
||||
return null;
|
||||
}
|
||||
if (unmountOnBlur && !isFocused) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (lazy && !loaded.includes(index) && !isFocused) {
|
||||
// Don't render a screen if we've never navigated to it
|
||||
return null;
|
||||
}
|
||||
if (lazy && !loaded.includes(index) && !isFocused) {
|
||||
// Don't render a screen if we've never navigated to it
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ResourceSavingScene
|
||||
key={route.key}
|
||||
style={StyleSheet.absoluteFill}
|
||||
isVisible={isFocused}
|
||||
>
|
||||
<SceneContent isFocused={isFocused}>
|
||||
{descriptor.render()}
|
||||
</SceneContent>
|
||||
</ResourceSavingScene>
|
||||
);
|
||||
})}
|
||||
</ScreenContainer>
|
||||
{this.renderTabBar()}
|
||||
</View>
|
||||
</SafeAreaProviderCompat>
|
||||
return (
|
||||
<ResourceSavingScene
|
||||
key={route.key}
|
||||
style={StyleSheet.absoluteFill}
|
||||
isVisible={isFocused}
|
||||
>
|
||||
<SceneContent isFocused={isFocused}>
|
||||
{descriptor.render()}
|
||||
</SceneContent>
|
||||
</ResourceSavingScene>
|
||||
);
|
||||
})}
|
||||
</ScreenContainer>
|
||||
{this.renderTabBar()}
|
||||
</View>
|
||||
</SafeAreaProviderCompat>
|
||||
</NavigationHelpersContext.Provider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,50 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [5.1.14](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.13...@react-navigation/compat@5.1.14) (2020-05-01)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.13](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.12...@react-navigation/compat@5.1.13) (2020-05-01)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.12](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.11...@react-navigation/compat@5.1.12) (2020-04-30)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.11](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.10...@react-navigation/compat@5.1.11) (2020-04-30)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.10](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.9...@react-navigation/compat@5.1.10) (2020-04-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix typo in navigationOptions ([8cbb201](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/commit/8cbb201f1a7fb90e45a078df6bc42ce4771cc6a6))
|
||||
* spread parent params to children in compat navigator ([24febf6](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/commit/24febf6ea99be2e5f22005fdd2a82136d647255c)), closes [#6785](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/issues/6785)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.9](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.8...@react-navigation/compat@5.1.9) (2020-04-17)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/compat",
|
||||
"description": "Compatibility layer to write navigator definitions in static configuration format",
|
||||
"version": "5.1.9",
|
||||
"version": "5.1.14",
|
||||
"license": "MIT",
|
||||
"repository": "https://github.com/react-navigation/react-navigation/tree/master/packages/compat",
|
||||
"bugs": {
|
||||
@@ -26,7 +26,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.10.0",
|
||||
"@react-navigation/native": "^5.1.6",
|
||||
"@react-navigation/native": "^5.2.3",
|
||||
"@types/react": "^16.9.23",
|
||||
"react": "~16.9.0",
|
||||
"typescript": "^3.8.3"
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
NavigationProp,
|
||||
RouteProp,
|
||||
EventMapBase,
|
||||
NavigationRouteContext,
|
||||
} from '@react-navigation/native';
|
||||
import CompatScreen from './CompatScreen';
|
||||
import ScreenPropsContext from './ScreenPropsContext';
|
||||
@@ -67,6 +68,9 @@ export default function createCompatNavigatorFactory<
|
||||
const routeNames = order !== undefined ? order : Object.keys(routeConfig);
|
||||
|
||||
function Navigator({ screenProps }: { screenProps?: unknown }) {
|
||||
const parentRouteParams = React.useContext(NavigationRouteContext)
|
||||
?.params;
|
||||
|
||||
const screens = React.useMemo(
|
||||
() =>
|
||||
routeNames.map((name) => {
|
||||
@@ -135,7 +139,7 @@ export default function createCompatNavigatorFactory<
|
||||
<Pair.Screen
|
||||
key={name}
|
||||
name={name}
|
||||
initialParams={initialParams}
|
||||
initialParams={{ ...parentRouteParams, ...initialParams }}
|
||||
options={screenOptions}
|
||||
>
|
||||
{({ navigation, route }) => (
|
||||
@@ -148,7 +152,7 @@ export default function createCompatNavigatorFactory<
|
||||
</Pair.Screen>
|
||||
);
|
||||
}),
|
||||
[screenProps]
|
||||
[parentRouteParams, screenProps]
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -163,7 +167,7 @@ export default function createCompatNavigatorFactory<
|
||||
);
|
||||
}
|
||||
|
||||
Navigator.navigationOtions = parentNavigationOptions;
|
||||
Navigator.navigationOptions = parentNavigationOptions;
|
||||
|
||||
return Navigator;
|
||||
};
|
||||
|
||||
@@ -3,6 +3,34 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [5.4.0](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.3.5...@react-navigation/core@5.4.0) (2020-04-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* handle empty paths when parsing ([c3fa83e](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/c3fa83efe0d73db76365f8be3d6a8ca1d1289b71))
|
||||
* parsing url ([bd35b4f](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/bd35b4fc202c3868fb75c3675b62de67557089e1))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add `useLinkBuilder` hook to build links ([2792f43](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/2792f438fe45428fe193e3708fee7ad61966cbf4))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.3.5](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.3.4...@react-navigation/core@5.3.5) (2020-04-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add config to enable redux devtools integration ([c9c825b](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/c9c825bee61426635a28ee149eeeff3d628171cd))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.3.4](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.3.3...@react-navigation/core@5.3.4) (2020-04-17)
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/core",
|
||||
"description": "Core utilities for building navigators",
|
||||
"version": "5.3.4",
|
||||
"version": "5.4.0",
|
||||
"keywords": [
|
||||
"react",
|
||||
"react-native",
|
||||
@@ -29,7 +29,7 @@
|
||||
"clean": "del lib"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-navigation/routers": "^5.4.0",
|
||||
"@react-navigation/routers": "^5.4.2",
|
||||
"escape-string-regexp": "^2.0.0",
|
||||
"nanoid": "^3.0.2",
|
||||
"query-string": "^6.12.0",
|
||||
|
||||
@@ -21,6 +21,9 @@ import { NavigationContainerRef, NavigationContainerProps } from './types';
|
||||
|
||||
type State = NavigationState | PartialState<NavigationState> | undefined;
|
||||
|
||||
const DEVTOOLS_CONFIG_KEY =
|
||||
'REACT_NAVIGATION_REDUX_DEVTOOLS_EXTENSION_INTEGRATION_ENABLED';
|
||||
|
||||
const MISSING_CONTEXT_ERROR =
|
||||
"Couldn't find a navigation context. Have you wrapped your app with 'NavigationContainer'? See https://reactnavigation.org/docs/getting-started for setup instructions.";
|
||||
|
||||
@@ -143,7 +146,9 @@ const BaseNavigationContainer = React.forwardRef(
|
||||
);
|
||||
|
||||
const { trackState, trackAction } = useDevTools({
|
||||
enabled: false,
|
||||
enabled:
|
||||
// @ts-ignore
|
||||
DEVTOOLS_CONFIG_KEY in global ? global[DEVTOOLS_CONFIG_KEY] : false,
|
||||
name: '@react-navigation',
|
||||
reset,
|
||||
state,
|
||||
@@ -214,6 +219,8 @@ const BaseNavigationContainer = React.forwardRef(
|
||||
dispatch,
|
||||
canGoBack,
|
||||
getRootState,
|
||||
dangerouslyGetState: () => state,
|
||||
dangerouslyGetParent: () => undefined,
|
||||
}));
|
||||
|
||||
const builderContext = React.useMemo(
|
||||
|
||||
13
packages/core/src/NavigationHelpersContext.tsx
Normal file
13
packages/core/src/NavigationHelpersContext.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import * as React from 'react';
|
||||
import { ParamListBase } from '@react-navigation/routers';
|
||||
import { NavigationHelpers } from './types';
|
||||
|
||||
/**
|
||||
* Context which holds the navigation helpers of the parent navigator.
|
||||
* Navigators should use this context in their view component.
|
||||
*/
|
||||
const NavigationHelpersContext = React.createContext<
|
||||
NavigationHelpers<ParamListBase> | undefined
|
||||
>(undefined);
|
||||
|
||||
export default NavigationHelpersContext;
|
||||
@@ -426,6 +426,49 @@ it('ignores empty string paths', () => {
|
||||
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
|
||||
});
|
||||
|
||||
it('keeps query params if path is empty', () => {
|
||||
const path = '/?foo=42';
|
||||
const config = {
|
||||
Foo: {
|
||||
screens: {
|
||||
Foe: 'foe',
|
||||
Bar: {
|
||||
screens: {
|
||||
Qux: {
|
||||
path: '',
|
||||
parse: { foo: Number },
|
||||
},
|
||||
Baz: 'baz',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const state = {
|
||||
routes: [
|
||||
{
|
||||
name: 'Foo',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'Bar',
|
||||
state: {
|
||||
routes: [{ name: 'Qux', params: { foo: 42 } }],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(getPathFromState(state, config)).toBe(path);
|
||||
expect(getPathFromState(getStateFromPath(path, config), config)).toEqual(
|
||||
path
|
||||
);
|
||||
});
|
||||
|
||||
it('cuts nested configs too', () => {
|
||||
const path = '/baz';
|
||||
const config = {
|
||||
@@ -495,6 +538,8 @@ it('handles empty path at the end', () => {
|
||||
});
|
||||
|
||||
it('returns "/" for empty path', () => {
|
||||
const path = '/';
|
||||
|
||||
const config = {
|
||||
Foo: {
|
||||
path: '',
|
||||
@@ -519,7 +564,8 @@ it('returns "/" for empty path', () => {
|
||||
],
|
||||
};
|
||||
|
||||
expect(getPathFromState(state, config)).toBe('/');
|
||||
expect(getPathFromState(state, config)).toBe(path);
|
||||
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
|
||||
});
|
||||
|
||||
it('parses no path specified', () => {
|
||||
|
||||
@@ -677,7 +677,7 @@ it('accepts initialRouteName without config for it', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('returns undefined if path is empty', () => {
|
||||
it('returns undefined if path is empty and no matching screen is present', () => {
|
||||
const config = {
|
||||
Foo: {
|
||||
screens: {
|
||||
@@ -695,3 +695,292 @@ it('returns undefined if path is empty', () => {
|
||||
|
||||
expect(getStateFromPath(path, config)).toEqual(undefined);
|
||||
});
|
||||
|
||||
it('returns matching screen if path is empty', () => {
|
||||
const path = '';
|
||||
const config = {
|
||||
Foo: {
|
||||
screens: {
|
||||
Foe: 'foe',
|
||||
Bar: {
|
||||
screens: {
|
||||
Qux: '',
|
||||
Baz: 'baz',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const state = {
|
||||
routes: [
|
||||
{
|
||||
name: 'Foo',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'Bar',
|
||||
state: {
|
||||
routes: [{ name: 'Qux' }],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(getStateFromPath(path, config)).toEqual(state);
|
||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
||||
state
|
||||
);
|
||||
});
|
||||
|
||||
it('returns matching screen with params if path is empty', () => {
|
||||
const path = '?foo=42';
|
||||
const config = {
|
||||
Foo: {
|
||||
screens: {
|
||||
Foe: 'foe',
|
||||
Bar: {
|
||||
screens: {
|
||||
Qux: {
|
||||
path: '',
|
||||
parse: { foo: Number },
|
||||
},
|
||||
Baz: 'baz',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const state = {
|
||||
routes: [
|
||||
{
|
||||
name: 'Foo',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'Bar',
|
||||
state: {
|
||||
routes: [{ name: 'Qux', params: { foo: 42 } }],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(getStateFromPath(path, config)).toEqual(state);
|
||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
||||
state
|
||||
);
|
||||
});
|
||||
|
||||
it("doesn't match nested screen if path is empty", () => {
|
||||
const config = {
|
||||
Foo: {
|
||||
screens: {
|
||||
Foe: 'foe',
|
||||
Bar: {
|
||||
path: 'bar',
|
||||
screens: {
|
||||
Qux: {
|
||||
path: '',
|
||||
parse: { foo: Number },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const path = '';
|
||||
|
||||
expect(getStateFromPath(path, config)).toEqual(undefined);
|
||||
});
|
||||
|
||||
it('chooses more exhaustive pattern', () => {
|
||||
const path = '/foo/5';
|
||||
|
||||
const config = {
|
||||
Foe: {
|
||||
path: '/',
|
||||
initialRouteName: 'Foo',
|
||||
screens: {
|
||||
Foo: 'foo',
|
||||
Bis: {
|
||||
path: 'foo/:id',
|
||||
parse: {
|
||||
id: Number,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const state = {
|
||||
routes: [
|
||||
{
|
||||
name: 'Foe',
|
||||
state: {
|
||||
index: 1,
|
||||
routes: [
|
||||
{
|
||||
name: 'Foo',
|
||||
},
|
||||
{
|
||||
name: 'Bis',
|
||||
params: { id: 5 },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(getStateFromPath(path, config)).toEqual(state);
|
||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
||||
state
|
||||
);
|
||||
});
|
||||
|
||||
it('handles same paths beginnings', () => {
|
||||
const path = '/foos';
|
||||
|
||||
const config = {
|
||||
Foe: {
|
||||
path: '/',
|
||||
initialRouteName: 'Foo',
|
||||
screens: {
|
||||
Foo: 'foo',
|
||||
Bis: {
|
||||
path: 'foos',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const state = {
|
||||
routes: [
|
||||
{
|
||||
name: 'Foe',
|
||||
state: {
|
||||
index: 1,
|
||||
routes: [
|
||||
{
|
||||
name: 'Foo',
|
||||
},
|
||||
{
|
||||
name: 'Bis',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(getStateFromPath(path, config)).toEqual(state);
|
||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
||||
state
|
||||
);
|
||||
});
|
||||
|
||||
it('handles same paths beginnings with params', () => {
|
||||
const path = '/foos/5';
|
||||
|
||||
const config = {
|
||||
Foe: {
|
||||
path: '/',
|
||||
initialRouteName: 'Foo',
|
||||
screens: {
|
||||
Foo: 'foo',
|
||||
Bis: {
|
||||
path: 'foos/:id',
|
||||
parse: {
|
||||
id: Number,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const state = {
|
||||
routes: [
|
||||
{
|
||||
name: 'Foe',
|
||||
state: {
|
||||
index: 1,
|
||||
routes: [
|
||||
{
|
||||
name: 'Foo',
|
||||
},
|
||||
{
|
||||
name: 'Bis',
|
||||
params: { id: 5 },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(getStateFromPath(path, config)).toEqual(state);
|
||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
||||
state
|
||||
);
|
||||
});
|
||||
|
||||
it('handles not taking path with too many segments', () => {
|
||||
const path = '/foos/5';
|
||||
|
||||
const config = {
|
||||
Foe: {
|
||||
path: '/',
|
||||
initialRouteName: 'Foo',
|
||||
screens: {
|
||||
Foo: 'foo',
|
||||
Bis: {
|
||||
path: 'foos/:id',
|
||||
parse: {
|
||||
id: Number,
|
||||
},
|
||||
},
|
||||
Bas: {
|
||||
path: 'foos/:id/:nip',
|
||||
parse: {
|
||||
id: Number,
|
||||
pwd: Number,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const state = {
|
||||
routes: [
|
||||
{
|
||||
name: 'Foe',
|
||||
state: {
|
||||
index: 1,
|
||||
routes: [
|
||||
{
|
||||
name: 'Foo',
|
||||
},
|
||||
{
|
||||
name: 'Bis',
|
||||
params: { id: 5 },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(getStateFromPath(path, config)).toEqual(state);
|
||||
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
|
||||
state
|
||||
);
|
||||
});
|
||||
|
||||
@@ -115,70 +115,66 @@ export default function getPathFromState(
|
||||
pattern = nestedRouteNames.substring(1);
|
||||
}
|
||||
|
||||
// we don't add empty path strings to path
|
||||
if (pattern !== '') {
|
||||
const config =
|
||||
currentOptions[route.name] !== undefined
|
||||
? (currentOptions[route.name] as { stringify?: StringifyConfig })
|
||||
.stringify
|
||||
: undefined;
|
||||
|
||||
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;
|
||||
}, {})
|
||||
const config =
|
||||
currentOptions[route.name] !== undefined
|
||||
? (currentOptions[route.name] as { stringify?: StringifyConfig })
|
||||
.stringify
|
||||
: undefined;
|
||||
|
||||
if (currentOptions[route.name] !== undefined) {
|
||||
path += pattern
|
||||
.split('/')
|
||||
.map((p) => {
|
||||
const name = p.replace(/^:/, '');
|
||||
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 the path has a pattern for a param, put the param in the path
|
||||
if (params && name in params && p.startsWith(':')) {
|
||||
const value = params[name];
|
||||
// Remove the used value from the params object since we'll use the rest for query string
|
||||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
||||
delete params[name];
|
||||
return encodeURIComponent(value);
|
||||
}
|
||||
if (currentOptions[route.name] !== undefined) {
|
||||
path += pattern
|
||||
.split('/')
|
||||
.map((p) => {
|
||||
const name = p.replace(/^:/, '');
|
||||
|
||||
return encodeURIComponent(p);
|
||||
})
|
||||
.join('/');
|
||||
} else {
|
||||
path += encodeURIComponent(route.name);
|
||||
}
|
||||
|
||||
if (route.state) {
|
||||
path += '/';
|
||||
} else if (params) {
|
||||
for (let param in params) {
|
||||
if (params[param] === 'undefined') {
|
||||
// If the path has a pattern for a param, put the param in the path
|
||||
if (params && name in params && p.startsWith(':')) {
|
||||
const value = params[name];
|
||||
// Remove the used value from the params object since we'll use the rest for query string
|
||||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
||||
delete params[param];
|
||||
delete params[name];
|
||||
return encodeURIComponent(value);
|
||||
}
|
||||
}
|
||||
const query = queryString.stringify(params);
|
||||
|
||||
if (query) {
|
||||
path += `?${query}`;
|
||||
return encodeURIComponent(p);
|
||||
})
|
||||
.join('/');
|
||||
} else {
|
||||
path += encodeURIComponent(route.name);
|
||||
}
|
||||
|
||||
if (route.state) {
|
||||
path += '/';
|
||||
} else if (params) {
|
||||
for (let param in params) {
|
||||
if (params[param] === 'undefined') {
|
||||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
||||
delete params[param];
|
||||
}
|
||||
}
|
||||
const query = queryString.stringify(params);
|
||||
|
||||
if (query) {
|
||||
path += `?${query}`;
|
||||
}
|
||||
}
|
||||
|
||||
current = route.state;
|
||||
}
|
||||
|
||||
path =
|
||||
path !== '/' && path.slice(path.length - 1) === '/'
|
||||
? path.slice(0, -1)
|
||||
: path;
|
||||
// Remove multiple as well as trailing slashes
|
||||
path = path.replace(/\/+/, '/');
|
||||
path = path.length > 1 ? path.replace(/\/$/, '') : path;
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import escape from 'escape-string-regexp';
|
||||
import queryString from 'query-string';
|
||||
import {
|
||||
NavigationState,
|
||||
@@ -20,7 +19,8 @@ type Options = {
|
||||
};
|
||||
|
||||
type RouteConfig = {
|
||||
match: RegExp;
|
||||
screen: string;
|
||||
match: string | null;
|
||||
pattern: string;
|
||||
routeNames: string[];
|
||||
parse: ParseConfig | undefined;
|
||||
@@ -58,10 +58,8 @@ export default function getStateFromPath(
|
||||
path: string,
|
||||
options: Options = {}
|
||||
): ResultState | undefined {
|
||||
if (path === '') {
|
||||
return undefined;
|
||||
}
|
||||
let initialRoutes: InitialRouteConfig[] = [];
|
||||
|
||||
// Create a normalized configs array which will be easier to use
|
||||
const configs = ([] as RouteConfig[]).concat(
|
||||
...Object.keys(options).map((key) =>
|
||||
@@ -69,24 +67,74 @@ export default function getStateFromPath(
|
||||
)
|
||||
);
|
||||
|
||||
let result: PartialState<NavigationState> | undefined;
|
||||
let current: PartialState<NavigationState> | undefined;
|
||||
// sort configs so the most exhaustive is always first to be chosen
|
||||
configs.sort(
|
||||
(config1, config2) =>
|
||||
config2.pattern.split('/').length - config1.pattern.split('/').length
|
||||
);
|
||||
|
||||
let remaining = path
|
||||
.replace(/[/]+/, '/') // Replace multiple slash (//) with single ones
|
||||
.replace(/^\//, '') // Remove extra leading slash
|
||||
.replace(/\?.*/, ''); // Remove query params which we will handle later
|
||||
|
||||
if (remaining === '') {
|
||||
// We need to add special handling of empty path so navigation to empty path also works
|
||||
// When handling empty path, we should only look at the root level config
|
||||
const match = configs.find(
|
||||
(config) =>
|
||||
config.pattern === '' &&
|
||||
config.routeNames.every(
|
||||
// make sure that none of the parent configs have a non-empty path defined
|
||||
(name) => !configs.find((c) => c.screen === name)?.pattern
|
||||
)
|
||||
);
|
||||
|
||||
if (match) {
|
||||
return createNestedStateObject(
|
||||
match.routeNames,
|
||||
initialRoutes,
|
||||
parseQueryParams(path, match.parse)
|
||||
);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let result: PartialState<NavigationState> | undefined;
|
||||
let current: PartialState<NavigationState> | undefined;
|
||||
|
||||
while (remaining) {
|
||||
let routeNames: string[] | undefined;
|
||||
let params: Record<string, any> | undefined;
|
||||
|
||||
// Go through all configs, and see if the next path segment matches our regex
|
||||
for (const config of configs) {
|
||||
const match = remaining.match(config.match);
|
||||
if (!config.match) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If our regex matches, we need to extract params from the path
|
||||
if (match) {
|
||||
let didMatch = true;
|
||||
const matchParts = config.match.split('/');
|
||||
const remainingParts = remaining.split('/');
|
||||
|
||||
// we check if remaining path has enough segments to be handled with this pattern
|
||||
if (config.pattern.split('/').length > remainingParts.length) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// we keep info about the index of segment on which the params start
|
||||
let paramsIndex = 0;
|
||||
// the beginning of the remaining path should be the same as the part of config before params
|
||||
for (paramsIndex; paramsIndex < matchParts.length; paramsIndex++) {
|
||||
if (matchParts[paramsIndex] !== remainingParts[paramsIndex]) {
|
||||
didMatch = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If the first part of the path matches, we need to extract params from the path
|
||||
if (didMatch) {
|
||||
routeNames = [...config.routeNames];
|
||||
|
||||
const paramPatterns = config.pattern
|
||||
@@ -96,8 +144,7 @@ export default function getStateFromPath(
|
||||
if (paramPatterns.length) {
|
||||
params = paramPatterns.reduce<Record<string, any>>((acc, p, i) => {
|
||||
const key = p.replace(/^:/, '');
|
||||
const value = match[i + 1]; // The param segments start from index 1 in the regex match result
|
||||
|
||||
const value = remainingParts[i + paramsIndex]; // The param segments start from the end of matched part
|
||||
acc[key] =
|
||||
config.parse && config.parse[key]
|
||||
? config.parse[key](value)
|
||||
@@ -107,8 +154,16 @@ export default function getStateFromPath(
|
||||
}, {});
|
||||
}
|
||||
|
||||
// Remove the matched segment from the remaining path
|
||||
remaining = remaining.replace(match[0], '');
|
||||
// if pattern and remaining path have same amount of segments, there should be nothing left
|
||||
if (config.pattern.split('/').length === remainingParts.length) {
|
||||
remaining = '';
|
||||
} else {
|
||||
// For each segment of the pattern, remove one segment from remaining path
|
||||
let i = config.pattern.split('/').length;
|
||||
while (i--) {
|
||||
remaining = remaining.substr(remaining.indexOf('/') + 1);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -123,34 +178,7 @@ export default function getStateFromPath(
|
||||
remaining = segments.join('/');
|
||||
}
|
||||
|
||||
let state: InitialState;
|
||||
let routeName = routeNames.shift() as string;
|
||||
let initialRoute = findInitialRoute(routeName, initialRoutes);
|
||||
|
||||
state = createNestedState(
|
||||
initialRoute,
|
||||
routeName,
|
||||
routeNames.length === 0,
|
||||
params
|
||||
);
|
||||
|
||||
if (routeNames.length > 0) {
|
||||
let nestedState = state;
|
||||
|
||||
while ((routeName = routeNames.shift() as string)) {
|
||||
initialRoute = findInitialRoute(routeName, initialRoutes);
|
||||
nestedState.routes[nestedState.index || 0].state = createNestedState(
|
||||
initialRoute,
|
||||
routeName,
|
||||
routeNames.length === 0,
|
||||
params
|
||||
);
|
||||
if (routeNames.length > 0) {
|
||||
nestedState = nestedState.routes[nestedState.index || 0]
|
||||
.state as InitialState;
|
||||
}
|
||||
}
|
||||
}
|
||||
const state = createNestedStateObject(routeNames, initialRoutes, params);
|
||||
|
||||
if (current) {
|
||||
// The state should be nested inside the deepest route we parsed before
|
||||
@@ -172,29 +200,13 @@ export default function getStateFromPath(
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const query = path.split('?')[1];
|
||||
|
||||
if (query) {
|
||||
while (current?.routes[current.index || 0].state) {
|
||||
// The query params apply to the deepest route
|
||||
current = current.routes[current.index || 0].state;
|
||||
}
|
||||
|
||||
const route = (current as PartialState<NavigationState>).routes[
|
||||
current?.index || 0
|
||||
];
|
||||
|
||||
const params = queryString.parse(query);
|
||||
const parseFunction = findParseConfigForRoute(route.name, configs);
|
||||
|
||||
if (parseFunction) {
|
||||
Object.keys(params).forEach((name) => {
|
||||
if (parseFunction[name] && typeof params[name] === 'string') {
|
||||
params[name] = parseFunction[name](params[name] as string);
|
||||
}
|
||||
});
|
||||
}
|
||||
const route = findFocusedRoute(current);
|
||||
const params = parseQueryParams(
|
||||
path,
|
||||
findParseConfigForRoute(route.name, configs)
|
||||
);
|
||||
|
||||
if (params) {
|
||||
route.params = { ...route.params, ...params };
|
||||
}
|
||||
|
||||
@@ -215,16 +227,15 @@ function createNormalizedConfigs(
|
||||
|
||||
if (typeof value === 'string') {
|
||||
// If a string is specified as the value of the key(e.g. Foo: '/path'), use it as the pattern
|
||||
if (value !== '') {
|
||||
configs.push(createConfigItem(routeNames, value));
|
||||
}
|
||||
configs.push(createConfigItem(key, routeNames, value));
|
||||
} else if (typeof value === 'object') {
|
||||
// if an object is specified as the value (e.g. Foo: { ... }),
|
||||
// it can have `path` property and
|
||||
// it could have `screens` prop which has nested configs
|
||||
if (value.path && value.path !== '') {
|
||||
configs.push(createConfigItem(routeNames, value.path, value.parse));
|
||||
if (typeof value.path === 'string') {
|
||||
configs.push(createConfigItem(key, routeNames, value.path, value.parse));
|
||||
}
|
||||
|
||||
if (value.screens) {
|
||||
// property `initialRouteName` without `screens` has no purpose
|
||||
if (value.initialRouteName) {
|
||||
@@ -251,15 +262,16 @@ function createNormalizedConfigs(
|
||||
}
|
||||
|
||||
function createConfigItem(
|
||||
screen: string,
|
||||
routeNames: string[],
|
||||
pattern: string,
|
||||
parse?: ParseConfig
|
||||
): RouteConfig {
|
||||
const match = new RegExp(
|
||||
'^' + escape(pattern).replace(/:[a-z0-9]+/gi, '([^/]+)') + '/?'
|
||||
);
|
||||
// part being matched ends on the first param
|
||||
const match = pattern !== '' ? pattern.split('/:')[0] : null;
|
||||
|
||||
return {
|
||||
screen,
|
||||
match,
|
||||
pattern,
|
||||
// The routeNames array is mutated, so copy it to keep the current state
|
||||
@@ -295,9 +307,9 @@ function findInitialRoute(
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// returns nested state object with values depending on whether
|
||||
// returns state object with values depending on whether
|
||||
// it is the end of state and if there is initialRoute for this level
|
||||
function createNestedState(
|
||||
function createStateObject(
|
||||
initialRoute: string | undefined,
|
||||
routeName: string,
|
||||
isEmpty: boolean,
|
||||
@@ -331,3 +343,73 @@ function createNestedState(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createNestedStateObject(
|
||||
routeNames: string[],
|
||||
initialRoutes: InitialRouteConfig[],
|
||||
params: object | undefined
|
||||
) {
|
||||
let state: InitialState;
|
||||
let routeName = routeNames.shift() as string;
|
||||
let initialRoute = findInitialRoute(routeName, initialRoutes);
|
||||
|
||||
state = createStateObject(
|
||||
initialRoute,
|
||||
routeName,
|
||||
routeNames.length === 0,
|
||||
params
|
||||
);
|
||||
|
||||
if (routeNames.length > 0) {
|
||||
let nestedState = state;
|
||||
|
||||
while ((routeName = routeNames.shift() as string)) {
|
||||
initialRoute = findInitialRoute(routeName, initialRoutes);
|
||||
nestedState.routes[nestedState.index || 0].state = createStateObject(
|
||||
initialRoute,
|
||||
routeName,
|
||||
routeNames.length === 0,
|
||||
params
|
||||
);
|
||||
if (routeNames.length > 0) {
|
||||
nestedState = nestedState.routes[nestedState.index || 0]
|
||||
.state as InitialState;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
function findFocusedRoute(state: InitialState) {
|
||||
let current: InitialState | undefined = state;
|
||||
|
||||
while (current?.routes[current.index || 0].state) {
|
||||
// The query params apply to the deepest route
|
||||
current = current.routes[current.index || 0].state;
|
||||
}
|
||||
|
||||
const route = (current as PartialState<NavigationState>).routes[
|
||||
current?.index || 0
|
||||
];
|
||||
|
||||
return route;
|
||||
}
|
||||
|
||||
function parseQueryParams(
|
||||
path: string,
|
||||
parseConfig?: Record<string, (value: string) => any>
|
||||
) {
|
||||
const query = path.split('?')[1];
|
||||
const params = queryString.parse(query);
|
||||
|
||||
if (parseConfig) {
|
||||
Object.keys(params).forEach((name) => {
|
||||
if (parseConfig[name] && typeof params[name] === 'string') {
|
||||
params[name] = parseConfig[name](params[name] as string);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return Object.keys(params).length ? params : undefined;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ export * from '@react-navigation/routers';
|
||||
export { default as BaseNavigationContainer } from './BaseNavigationContainer';
|
||||
export { default as createNavigatorFactory } from './createNavigatorFactory';
|
||||
|
||||
export { default as NavigationHelpersContext } from './NavigationHelpersContext';
|
||||
export { default as NavigationContext } from './NavigationContext';
|
||||
export { default as NavigationRouteContext } from './NavigationRouteContext';
|
||||
|
||||
|
||||
@@ -193,6 +193,20 @@ type NavigationHelpersCommon<
|
||||
* Note that this method doesn't re-render screen when the result changes. So don't use it in `render`.
|
||||
*/
|
||||
canGoBack(): boolean;
|
||||
|
||||
/**
|
||||
* Returns the parent navigator, if any. Reason why the function is called
|
||||
* dangerouslyGetParent is to warn developers against overusing it to eg. get parent
|
||||
* of parent and other hard-to-follow patterns.
|
||||
*/
|
||||
dangerouslyGetParent<T = NavigationProp<ParamListBase> | undefined>(): T;
|
||||
|
||||
/**
|
||||
* Returns the navigator's state. Reason why the function is called
|
||||
* dangerouslyGetState is to discourage developers to use internal navigation's state.
|
||||
* Note that this method doesn't re-render screen when the result changes. So don't use it in `render`.
|
||||
*/
|
||||
dangerouslyGetState(): State;
|
||||
} & PrivateValueStore<ParamList, keyof ParamList, {}>;
|
||||
|
||||
export type NavigationHelpers<
|
||||
@@ -254,20 +268,6 @@ export type NavigationProp<
|
||||
* @param options Options object for the route.
|
||||
*/
|
||||
setOptions(options: Partial<ScreenOptions>): void;
|
||||
|
||||
/**
|
||||
* Returns the parent navigator, if any. Reason why the function is called
|
||||
* dangerouslyGetParent is to warn developers against overusing it to eg. get parent
|
||||
* of parent and other hard-to-follow patterns.
|
||||
*/
|
||||
dangerouslyGetParent<T = NavigationProp<ParamListBase> | undefined>(): T;
|
||||
|
||||
/**
|
||||
* Returns the navigator's state. Reason why the function is called
|
||||
* dangerouslyGetState is to discourage developers to use internal navigation's state.
|
||||
* Note that this method doesn't re-render screen when the result changes. So don't use it in `render`.
|
||||
*/
|
||||
dangerouslyGetState(): State;
|
||||
} & EventConsumer<EventMap & EventMapCore<State>> &
|
||||
PrivateValueStore<ParamList, RouteName, EventMap>;
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
Router,
|
||||
} from '@react-navigation/routers';
|
||||
import { NavigationEventEmitter } from './useEventEmitter';
|
||||
import NavigationContext from './NavigationContext';
|
||||
|
||||
import { NavigationHelpers, NavigationProp } from './types';
|
||||
|
||||
@@ -49,12 +48,10 @@ export default function useNavigationCache<
|
||||
// Cache object which holds navigation objects for each screen
|
||||
// We use `React.useMemo` instead of `React.useRef` coz we want to invalidate it when deps change
|
||||
// In reality, these deps will rarely change, if ever
|
||||
const parentNavigation = React.useContext(NavigationContext);
|
||||
|
||||
const cache = React.useMemo(
|
||||
() => ({ current: {} as NavigationCache<State, ScreenOptions> }),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[getState, navigation, setOptions, router, emitter, parentNavigation]
|
||||
[getState, navigation, setOptions, router, emitter]
|
||||
);
|
||||
|
||||
const actions = {
|
||||
@@ -99,8 +96,6 @@ export default function useNavigationCache<
|
||||
...rest,
|
||||
...helpers,
|
||||
...emitter.create(route.key),
|
||||
dangerouslyGetParent: () => parentNavigation as any,
|
||||
dangerouslyGetState: getState,
|
||||
dispatch,
|
||||
setOptions: (options: object) =>
|
||||
setOptions((o) => ({
|
||||
|
||||
@@ -112,6 +112,8 @@ export default function useNavigationHelpers<
|
||||
false
|
||||
);
|
||||
},
|
||||
dangerouslyGetParent: () => parentNavigationHelpers as any,
|
||||
dangerouslyGetState: getState,
|
||||
} as NavigationHelpers<ParamListBase, EventMap> &
|
||||
(NavigationProp<ParamListBase, string, any, any, any> | undefined);
|
||||
}, [router, getState, parentNavigationHelpers, emitter.emit, onAction]);
|
||||
|
||||
@@ -3,6 +3,56 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [5.6.3](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.6.2...@react-navigation/drawer@5.6.3) (2020-05-01)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/drawer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.6.2](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.6.1...@react-navigation/drawer@5.6.2) (2020-05-01)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/drawer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.6.1](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.6.0...@react-navigation/drawer@5.6.1) (2020-04-30)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/drawer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.6.0](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.5.1...@react-navigation/drawer@5.6.0) (2020-04-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix closing drawer on web with tap on overlay ([70be3f6](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/70be3f6d863c56211e2f90bdf743bd8526338248))
|
||||
* make sure the address bar hides when scrolling on web ([0a19e94](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/0a19e94b23a4d2b5f22d1d9deb0544f586f475ee))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add `useLinkBuilder` hook to build links ([2792f43](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/2792f438fe45428fe193e3708fee7ad61966cbf4))
|
||||
* add action prop to Link ([942d2be](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/942d2be2c72720469475ce12ec8df23825994dbf))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.5.1](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.5.0...@react-navigation/drawer@5.5.1) (2020-04-27)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/drawer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.5.0](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.4.1...@react-navigation/drawer@5.5.0) (2020-04-17)
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/drawer",
|
||||
"description": "Drawer navigator component with animated transitions and gesturess",
|
||||
"version": "5.5.0",
|
||||
"version": "5.6.3",
|
||||
"keywords": [
|
||||
"react-native-component",
|
||||
"react-component",
|
||||
@@ -40,7 +40,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.10.0",
|
||||
"@react-navigation/native": "^5.1.6",
|
||||
"@react-navigation/native": "^5.2.3",
|
||||
"@types/react": "^16.9.23",
|
||||
"@types/react-native": "^0.61.22",
|
||||
"del-cli": "^3.0.0",
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
DrawerNavigationState,
|
||||
DrawerActionHelpers,
|
||||
} from '@react-navigation/native';
|
||||
import { PanGestureHandler } from 'react-native-gesture-handler';
|
||||
import type { PanGestureHandlerProperties } from 'react-native-gesture-handler';
|
||||
|
||||
export type Scene = {
|
||||
route: Route<string>;
|
||||
@@ -32,6 +32,7 @@ export type DrawerNavigationConfig<T = DrawerContentOptions> = {
|
||||
drawerType?: 'front' | 'back' | 'slide' | 'permanent';
|
||||
/**
|
||||
* How far from the edge of the screen the swipe gesture should activate.
|
||||
* Not supported on Web.
|
||||
*/
|
||||
edgeWidth?: number;
|
||||
/**
|
||||
@@ -58,8 +59,9 @@ export type DrawerNavigationConfig<T = DrawerContentOptions> = {
|
||||
statusBarAnimation?: 'slide' | 'none' | 'fade';
|
||||
/**
|
||||
* Props to pass to the underlying pan gesture handler.
|
||||
* Not supported on Web.
|
||||
*/
|
||||
gestureHandlerProps?: React.ComponentProps<typeof PanGestureHandler>;
|
||||
gestureHandlerProps?: PanGestureHandlerProperties;
|
||||
/**
|
||||
* Whether the screens should render the first time they are accessed. Defaults to `true`.
|
||||
* Set it to `false` if you want to render all screens on initial render.
|
||||
@@ -113,13 +115,15 @@ export type DrawerNavigationOptions = {
|
||||
* Whether you can use gestures to open or close the drawer.
|
||||
* Setting this to `false` disables swipe gestures as well as tap on overlay to close.
|
||||
* See `swipeEnabled` to disable only the swipe gesture.
|
||||
* Defaults to `true`
|
||||
* Defaults to `true`.
|
||||
* Not supported on Web.
|
||||
*/
|
||||
gestureEnabled?: boolean;
|
||||
|
||||
/**
|
||||
* Whether you can use swipe gestures to open or close the drawer.
|
||||
* Defaults to `true`
|
||||
* Defaults to `true`.
|
||||
* Not supported on Web.
|
||||
*/
|
||||
swipeEnabled?: boolean;
|
||||
|
||||
|
||||
@@ -10,14 +10,14 @@ import {
|
||||
StyleProp,
|
||||
View,
|
||||
InteractionManager,
|
||||
TouchableWithoutFeedback,
|
||||
} from 'react-native';
|
||||
import Animated from 'react-native-reanimated';
|
||||
import {
|
||||
PanGestureHandler,
|
||||
TapGestureHandler,
|
||||
State as GestureState,
|
||||
TapGestureHandlerStateChangeEvent,
|
||||
} from 'react-native-gesture-handler';
|
||||
import Animated from 'react-native-reanimated';
|
||||
GestureState,
|
||||
} from './GestureHandler';
|
||||
import Overlay from './Overlay';
|
||||
|
||||
const {
|
||||
@@ -79,7 +79,6 @@ type Props = {
|
||||
open: boolean;
|
||||
onOpen: () => void;
|
||||
onClose: () => void;
|
||||
onGestureRef?: (ref: PanGestureHandler | null) => void;
|
||||
gestureEnabled: boolean;
|
||||
swipeEnabled: boolean;
|
||||
drawerPosition: 'left' | 'right';
|
||||
@@ -511,28 +510,17 @@ export default class DrawerView extends React.Component<Props> {
|
||||
},
|
||||
]);
|
||||
|
||||
private handleTapStateChange =
|
||||
Platform.OS === 'web'
|
||||
? // FIXME: Drawer doesn't close on Web with the same code that we use for native
|
||||
({ nativeEvent }: TapGestureHandlerStateChangeEvent) => {
|
||||
if (
|
||||
nativeEvent.state === GestureState.END &&
|
||||
nativeEvent.oldState === GestureState.ACTIVE
|
||||
) {
|
||||
this.toggleDrawer(false);
|
||||
}
|
||||
}
|
||||
: event([
|
||||
{
|
||||
nativeEvent: {
|
||||
oldState: (s: Animated.Value<number>) =>
|
||||
cond(
|
||||
eq(s, GestureState.ACTIVE),
|
||||
set(this.manuallyTriggerSpring, TRUE)
|
||||
),
|
||||
},
|
||||
},
|
||||
]);
|
||||
private handleTapStateChange = event([
|
||||
{
|
||||
nativeEvent: {
|
||||
oldState: (s: Animated.Value<number>) =>
|
||||
cond(
|
||||
eq(s, GestureState.ACTIVE),
|
||||
set(this.manuallyTriggerSpring, TRUE)
|
||||
),
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
private handleContainerLayout = (e: LayoutChangeEvent) =>
|
||||
this.containerWidth.setValue(e.nativeEvent.layout.width);
|
||||
@@ -579,7 +567,6 @@ export default class DrawerView extends React.Component<Props> {
|
||||
sceneContainerStyle,
|
||||
drawerStyle,
|
||||
overlayStyle,
|
||||
onGestureRef,
|
||||
renderDrawerContent,
|
||||
renderSceneContent,
|
||||
gestureHandlerProps,
|
||||
@@ -624,7 +611,6 @@ export default class DrawerView extends React.Component<Props> {
|
||||
|
||||
return (
|
||||
<PanGestureHandler
|
||||
ref={onGestureRef}
|
||||
activeOffsetX={[-SWIPE_DISTANCE_MINIMUM, SWIPE_DISTANCE_MINIMUM]}
|
||||
failOffsetY={[-SWIPE_DISTANCE_MINIMUM, SWIPE_DISTANCE_MINIMUM]}
|
||||
onGestureEvent={this.handleGestureEvent}
|
||||
@@ -663,7 +649,15 @@ export default class DrawerView extends React.Component<Props> {
|
||||
</View>
|
||||
{
|
||||
// Disable overlay if sidebar is permanent
|
||||
drawerType === 'permanent' ? null : (
|
||||
drawerType === 'permanent' ? null : Platform.OS === 'web' ? (
|
||||
<TouchableWithoutFeedback
|
||||
onPress={
|
||||
gestureEnabled ? () => this.toggleDrawer(false) : undefined
|
||||
}
|
||||
>
|
||||
<Overlay progress={progress} style={overlayStyle} />
|
||||
</TouchableWithoutFeedback>
|
||||
) : (
|
||||
<TapGestureHandler
|
||||
enabled={gestureEnabled}
|
||||
onHandlerStateChange={this.handleTapStateChange}
|
||||
@@ -737,6 +731,11 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
main: {
|
||||
flex: 1,
|
||||
overflow: 'hidden',
|
||||
...Platform.select({
|
||||
// FIXME: We need to hide `overflowX` on Web so the translated content doesn't show offscreen.
|
||||
// But adding `overflowX: 'hidden'` prevents content from collapsing the URL bar.
|
||||
web: null,
|
||||
default: { overflow: 'hidden' },
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -6,8 +6,10 @@ import {
|
||||
StyleProp,
|
||||
ViewStyle,
|
||||
TextStyle,
|
||||
Platform,
|
||||
TouchableWithoutFeedbackProps,
|
||||
} from 'react-native';
|
||||
import { useTheme } from '@react-navigation/native';
|
||||
import { Link, useTheme } from '@react-navigation/native';
|
||||
import Color from 'color';
|
||||
import TouchableItem from './TouchableItem';
|
||||
|
||||
@@ -26,6 +28,10 @@ type Props = {
|
||||
size: number;
|
||||
color: string;
|
||||
}) => React.ReactNode;
|
||||
/**
|
||||
* URL to use for the link to the tab.
|
||||
*/
|
||||
to?: string;
|
||||
/**
|
||||
* Whether to highlight the drawer item as active.
|
||||
*/
|
||||
@@ -60,6 +66,54 @@ type Props = {
|
||||
style?: StyleProp<ViewStyle>;
|
||||
};
|
||||
|
||||
const Touchable = ({
|
||||
children,
|
||||
style,
|
||||
onPress,
|
||||
to,
|
||||
accessibilityRole,
|
||||
delayPressIn,
|
||||
...rest
|
||||
}: TouchableWithoutFeedbackProps & {
|
||||
to?: string;
|
||||
children: React.ReactNode;
|
||||
onPress?: () => void;
|
||||
}) => {
|
||||
if (Platform.OS === 'web' && to) {
|
||||
// React Native Web doesn't forward `onClick` if we use `TouchableWithoutFeedback`.
|
||||
// We need to use `onClick` to be able to prevent default browser handling of links.
|
||||
return (
|
||||
<Link
|
||||
{...rest}
|
||||
to={to}
|
||||
style={[styles.button, style]}
|
||||
onPress={(e: any) => {
|
||||
if (
|
||||
!(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) && // ignore clicks with modifier keys
|
||||
(e.button == null || e.button === 0) // ignore everything but left clicks
|
||||
) {
|
||||
e.preventDefault();
|
||||
onPress?.(e);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<TouchableItem
|
||||
{...rest}
|
||||
accessibilityRole={accessibilityRole}
|
||||
delayPressIn={delayPressIn}
|
||||
onPress={onPress}
|
||||
>
|
||||
<View style={style}>{children}</View>
|
||||
</TouchableItem>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A component used to show an action item with an icon and a label in a navigation drawer.
|
||||
*/
|
||||
@@ -70,6 +124,7 @@ export default function DrawerItem(props: Props) {
|
||||
icon,
|
||||
label,
|
||||
labelStyle,
|
||||
to,
|
||||
focused = false,
|
||||
activeTintColor = colors.primary,
|
||||
inactiveTintColor = Color(colors.text).alpha(0.68).rgb().string(),
|
||||
@@ -94,7 +149,7 @@ export default function DrawerItem(props: Props) {
|
||||
{...rest}
|
||||
style={[styles.container, { borderRadius, backgroundColor }, style]}
|
||||
>
|
||||
<TouchableItem
|
||||
<Touchable
|
||||
delayPressIn={0}
|
||||
onPress={onPress}
|
||||
style={[styles.wrapper, { borderRadius }]}
|
||||
@@ -102,6 +157,7 @@ export default function DrawerItem(props: Props) {
|
||||
accessibilityComponentType="button"
|
||||
accessibilityRole="button"
|
||||
accessibilityStates={focused ? ['selected'] : []}
|
||||
to={to}
|
||||
>
|
||||
<React.Fragment>
|
||||
{iconNode}
|
||||
@@ -129,7 +185,7 @@ export default function DrawerItem(props: Props) {
|
||||
)}
|
||||
</View>
|
||||
</React.Fragment>
|
||||
</TouchableItem>
|
||||
</Touchable>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -148,4 +204,7 @@ const styles = StyleSheet.create({
|
||||
label: {
|
||||
marginRight: 32,
|
||||
},
|
||||
button: {
|
||||
display: 'flex',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
CommonActions,
|
||||
DrawerActions,
|
||||
DrawerNavigationState,
|
||||
useLinkBuilder,
|
||||
} from '@react-navigation/native';
|
||||
import DrawerItem from './DrawerItem';
|
||||
import {
|
||||
@@ -31,6 +32,8 @@ export default function DrawerItemList({
|
||||
itemStyle,
|
||||
labelStyle,
|
||||
}: Props) {
|
||||
const buildLink = useLinkBuilder();
|
||||
|
||||
return (state.routes.map((route, i) => {
|
||||
const focused = i === state.index;
|
||||
const { title, drawerLabel, drawerIcon } = descriptors[route.key].options;
|
||||
@@ -53,6 +56,7 @@ export default function DrawerItemList({
|
||||
inactiveBackgroundColor={inactiveBackgroundColor}
|
||||
labelStyle={labelStyle}
|
||||
style={itemStyle}
|
||||
to={buildLink(route.name, route.params)}
|
||||
onPress={() => {
|
||||
navigation.dispatch({
|
||||
...(focused
|
||||
|
||||
@@ -12,16 +12,13 @@ import {
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
import { ScreenContainer } from 'react-native-screens';
|
||||
import {
|
||||
PanGestureHandler,
|
||||
GestureHandlerRootView,
|
||||
} from 'react-native-gesture-handler';
|
||||
import {
|
||||
NavigationHelpersContext,
|
||||
DrawerNavigationState,
|
||||
DrawerActions,
|
||||
useTheme,
|
||||
} from '@react-navigation/native';
|
||||
|
||||
import DrawerGestureContext from '../utils/DrawerGestureContext';
|
||||
import { GestureHandlerRootView } from './GestureHandler';
|
||||
import SafeAreaProviderCompat from './SafeAreaProviderCompat';
|
||||
import ResourceSavingScene from './ResourceSavingScene';
|
||||
import DrawerContent from './DrawerContent';
|
||||
@@ -93,8 +90,6 @@ export default function DrawerView({
|
||||
Dimensions.get('window')
|
||||
);
|
||||
|
||||
const drawerGestureRef = React.useRef<PanGestureHandler>(null);
|
||||
|
||||
const { colors } = useTheme();
|
||||
|
||||
const isDrawerOpen = state.history.some((it) => it.type === 'drawer');
|
||||
@@ -201,9 +196,9 @@ export default function DrawerView({
|
||||
const { gestureEnabled, swipeEnabled } = descriptors[activeKey].options;
|
||||
|
||||
return (
|
||||
<GestureHandlerWrapper style={styles.content}>
|
||||
<SafeAreaProviderCompat>
|
||||
<DrawerGestureContext.Provider value={drawerGestureRef}>
|
||||
<NavigationHelpersContext.Provider value={navigation}>
|
||||
<GestureHandlerWrapper style={styles.content}>
|
||||
<SafeAreaProviderCompat>
|
||||
<DrawerOpenContext.Provider value={isDrawerOpen}>
|
||||
<Drawer
|
||||
open={isDrawerOpen}
|
||||
@@ -211,10 +206,6 @@ export default function DrawerView({
|
||||
swipeEnabled={swipeEnabled}
|
||||
onOpen={handleDrawerOpen}
|
||||
onClose={handleDrawerClose}
|
||||
onGestureRef={(ref) => {
|
||||
// @ts-ignore
|
||||
drawerGestureRef.current = ref;
|
||||
}}
|
||||
gestureHandlerProps={gestureHandlerProps}
|
||||
drawerType={drawerType}
|
||||
drawerPosition={drawerPosition}
|
||||
@@ -251,9 +242,9 @@ export default function DrawerView({
|
||||
dimensions={dimensions}
|
||||
/>
|
||||
</DrawerOpenContext.Provider>
|
||||
</DrawerGestureContext.Provider>
|
||||
</SafeAreaProviderCompat>
|
||||
</GestureHandlerWrapper>
|
||||
</SafeAreaProviderCompat>
|
||||
</GestureHandlerWrapper>
|
||||
</NavigationHelpersContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
23
packages/drawer/src/views/GestureHandler.native.tsx
Normal file
23
packages/drawer/src/views/GestureHandler.native.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import * as React from 'react';
|
||||
import {
|
||||
PanGestureHandler as PanGestureHandlerNative,
|
||||
PanGestureHandlerProperties,
|
||||
} from 'react-native-gesture-handler';
|
||||
import DrawerGestureContext from '../utils/DrawerGestureContext';
|
||||
|
||||
export function PanGestureHandler(props: PanGestureHandlerProperties) {
|
||||
const gestureRef = React.useRef<PanGestureHandlerNative>(null);
|
||||
|
||||
return (
|
||||
<DrawerGestureContext.Provider value={gestureRef}>
|
||||
<PanGestureHandlerNative {...props} />
|
||||
</DrawerGestureContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export {
|
||||
GestureHandlerRootView,
|
||||
TapGestureHandler,
|
||||
State as GestureState,
|
||||
PanGestureHandlerGestureEvent,
|
||||
} from 'react-native-gesture-handler';
|
||||
31
packages/drawer/src/views/GestureHandler.tsx
Normal file
31
packages/drawer/src/views/GestureHandler.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import * as React from 'react';
|
||||
import { View } from 'react-native';
|
||||
import type {
|
||||
PanGestureHandlerProperties,
|
||||
TapGestureHandlerProperties,
|
||||
} from 'react-native-gesture-handler';
|
||||
|
||||
const Dummy: any = ({ children }: { children: React.ReactNode }) => (
|
||||
<>{children}</>
|
||||
);
|
||||
|
||||
export const PanGestureHandler = Dummy as React.ComponentType<
|
||||
PanGestureHandlerProperties
|
||||
>;
|
||||
|
||||
export const TapGestureHandler = Dummy as React.ComponentType<
|
||||
TapGestureHandlerProperties
|
||||
>;
|
||||
|
||||
export const GestureHandlerRootView = View;
|
||||
|
||||
export const GestureState = {
|
||||
UNDETERMINED: 0,
|
||||
FAILED: 1,
|
||||
BEGAN: 2,
|
||||
CANCELLED: 3,
|
||||
ACTIVE: 4,
|
||||
END: 5,
|
||||
};
|
||||
|
||||
export type { PanGestureHandlerGestureEvent } from 'react-native-gesture-handler';
|
||||
@@ -29,7 +29,7 @@ export default class ResourceSavingScene extends React.Component<Props> {
|
||||
styles.container,
|
||||
Platform.OS === 'web'
|
||||
? { display: isVisible ? 'flex' : 'none' }
|
||||
: null,
|
||||
: { overflow: 'hidden' },
|
||||
style,
|
||||
]}
|
||||
collapsable={false}
|
||||
@@ -52,7 +52,6 @@ export default class ResourceSavingScene extends React.Component<Props> {
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
attached: {
|
||||
flex: 1,
|
||||
|
||||
@@ -3,6 +3,46 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [5.1.14](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.1.13...@react-navigation/material-bottom-tabs@5.1.14) (2020-05-01)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.13](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.1.12...@react-navigation/material-bottom-tabs@5.1.13) (2020-05-01)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.12](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.1.11...@react-navigation/material-bottom-tabs@5.1.12) (2020-04-30)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.11](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.1.10...@react-navigation/material-bottom-tabs@5.1.11) (2020-04-30)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.10](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.1.9...@react-navigation/material-bottom-tabs@5.1.10) (2020-04-27)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.9](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.1.8...@react-navigation/material-bottom-tabs@5.1.9) (2020-04-17)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/material-bottom-tabs",
|
||||
"description": "Integration for bottom navigation component from react-native-paper",
|
||||
"version": "5.1.9",
|
||||
"version": "5.1.14",
|
||||
"keywords": [
|
||||
"react-native-component",
|
||||
"react-component",
|
||||
@@ -36,7 +36,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.10.0",
|
||||
"@react-navigation/native": "^5.1.6",
|
||||
"@react-navigation/native": "^5.2.3",
|
||||
"@types/react": "^16.9.23",
|
||||
"@types/react-native": "^0.61.22",
|
||||
"@types/react-native-vector-icons": "^6.4.5",
|
||||
|
||||
@@ -3,6 +3,7 @@ import { StyleSheet } from 'react-native';
|
||||
import { BottomNavigation, DefaultTheme, DarkTheme } from 'react-native-paper';
|
||||
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
import {
|
||||
NavigationHelpersContext,
|
||||
Route,
|
||||
TabNavigationState,
|
||||
TabActions,
|
||||
@@ -45,66 +46,68 @@ export default function MaterialBottomTabView({
|
||||
}, [colors, dark]);
|
||||
|
||||
return (
|
||||
<BottomNavigation
|
||||
{...rest}
|
||||
theme={theme}
|
||||
navigationState={state}
|
||||
onIndexChange={(index: number) =>
|
||||
navigation.dispatch({
|
||||
...TabActions.jumpTo(state.routes[index].name),
|
||||
target: state.key,
|
||||
})
|
||||
}
|
||||
renderScene={({ route }) => descriptors[route.key].render()}
|
||||
renderIcon={({ route, focused, color }) => {
|
||||
const { options } = descriptors[route.key];
|
||||
|
||||
if (typeof options.tabBarIcon === 'string') {
|
||||
return (
|
||||
<MaterialCommunityIcons
|
||||
name={options.tabBarIcon}
|
||||
color={color}
|
||||
size={24}
|
||||
style={styles.icon}
|
||||
importantForAccessibility="no-hide-descendants"
|
||||
accessibilityElementsHidden
|
||||
/>
|
||||
);
|
||||
<NavigationHelpersContext.Provider value={navigation}>
|
||||
<BottomNavigation
|
||||
{...rest}
|
||||
theme={theme}
|
||||
navigationState={state}
|
||||
onIndexChange={(index: number) =>
|
||||
navigation.dispatch({
|
||||
...TabActions.jumpTo(state.routes[index].name),
|
||||
target: state.key,
|
||||
})
|
||||
}
|
||||
renderScene={({ route }) => descriptors[route.key].render()}
|
||||
renderIcon={({ route, focused, color }) => {
|
||||
const { options } = descriptors[route.key];
|
||||
|
||||
if (typeof options.tabBarIcon === 'function') {
|
||||
return options.tabBarIcon({ focused, color });
|
||||
if (typeof options.tabBarIcon === 'string') {
|
||||
return (
|
||||
<MaterialCommunityIcons
|
||||
name={options.tabBarIcon}
|
||||
color={color}
|
||||
size={24}
|
||||
style={styles.icon}
|
||||
importantForAccessibility="no-hide-descendants"
|
||||
accessibilityElementsHidden
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (typeof options.tabBarIcon === 'function') {
|
||||
return options.tabBarIcon({ focused, color });
|
||||
}
|
||||
|
||||
return null;
|
||||
}}
|
||||
getLabelText={({ route }: Scene) => {
|
||||
const { options } = descriptors[route.key];
|
||||
|
||||
return options.tabBarLabel !== undefined
|
||||
? options.tabBarLabel
|
||||
: options.title !== undefined
|
||||
? options.title
|
||||
: (route as Route<string>).name;
|
||||
}}
|
||||
getColor={({ route }) => descriptors[route.key].options.tabBarColor}
|
||||
getBadge={({ route }) => descriptors[route.key].options.tabBarBadge}
|
||||
getAccessibilityLabel={({ route }) =>
|
||||
descriptors[route.key].options.tabBarAccessibilityLabel
|
||||
}
|
||||
getTestID={({ route }) => descriptors[route.key].options.tabBarTestID}
|
||||
onTabPress={({ route, preventDefault }) => {
|
||||
const event = navigation.emit({
|
||||
type: 'tabPress',
|
||||
target: route.key,
|
||||
canPreventDefault: true,
|
||||
});
|
||||
|
||||
return null;
|
||||
}}
|
||||
getLabelText={({ route }: Scene) => {
|
||||
const { options } = descriptors[route.key];
|
||||
|
||||
return options.tabBarLabel !== undefined
|
||||
? options.tabBarLabel
|
||||
: options.title !== undefined
|
||||
? options.title
|
||||
: (route as Route<string>).name;
|
||||
}}
|
||||
getColor={({ route }) => descriptors[route.key].options.tabBarColor}
|
||||
getBadge={({ route }) => descriptors[route.key].options.tabBarBadge}
|
||||
getAccessibilityLabel={({ route }) =>
|
||||
descriptors[route.key].options.tabBarAccessibilityLabel
|
||||
}
|
||||
getTestID={({ route }) => descriptors[route.key].options.tabBarTestID}
|
||||
onTabPress={({ route, preventDefault }) => {
|
||||
const event = navigation.emit({
|
||||
type: 'tabPress',
|
||||
target: route.key,
|
||||
canPreventDefault: true,
|
||||
});
|
||||
|
||||
if (event.defaultPrevented) {
|
||||
preventDefault();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
if (event.defaultPrevented) {
|
||||
preventDefault();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</NavigationHelpersContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,46 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [5.1.14](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.1.13...@react-navigation/material-top-tabs@5.1.14) (2020-05-01)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.13](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.1.12...@react-navigation/material-top-tabs@5.1.13) (2020-05-01)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.12](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.1.11...@react-navigation/material-top-tabs@5.1.12) (2020-04-30)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.11](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.1.10...@react-navigation/material-top-tabs@5.1.11) (2020-04-30)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.10](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.1.9...@react-navigation/material-top-tabs@5.1.10) (2020-04-27)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.9](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.1.8...@react-navigation/material-top-tabs@5.1.9) (2020-04-17)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/material-top-tabs",
|
||||
"description": "Integration for the animated tab view component from react-native-tab-view",
|
||||
"version": "5.1.9",
|
||||
"version": "5.1.14",
|
||||
"keywords": [
|
||||
"react-native-component",
|
||||
"react-component",
|
||||
@@ -39,7 +39,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.10.0",
|
||||
"@react-navigation/native": "^5.1.6",
|
||||
"@react-navigation/native": "^5.2.3",
|
||||
"@types/react": "^16.9.23",
|
||||
"@types/react-native": "^0.61.22",
|
||||
"del-cli": "^3.0.0",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as React from 'react';
|
||||
import { TabView, SceneRendererProps } from 'react-native-tab-view';
|
||||
import {
|
||||
NavigationHelpersContext,
|
||||
TabNavigationState,
|
||||
TabActions,
|
||||
useTheme,
|
||||
@@ -45,25 +46,27 @@ export default function MaterialTopTabView({
|
||||
};
|
||||
|
||||
return (
|
||||
<TabView
|
||||
{...rest}
|
||||
onIndexChange={(index) =>
|
||||
navigation.dispatch({
|
||||
...TabActions.jumpTo(state.routes[index].name),
|
||||
target: state.key,
|
||||
})
|
||||
}
|
||||
renderScene={({ route }) => descriptors[route.key].render()}
|
||||
navigationState={state}
|
||||
renderTabBar={renderTabBar}
|
||||
renderPager={pager}
|
||||
renderLazyPlaceholder={lazyPlaceholder}
|
||||
onSwipeStart={() => navigation.emit({ type: 'swipeStart' })}
|
||||
onSwipeEnd={() => navigation.emit({ type: 'swipeEnd' })}
|
||||
sceneContainerStyle={[
|
||||
{ backgroundColor: colors.background },
|
||||
sceneContainerStyle,
|
||||
]}
|
||||
/>
|
||||
<NavigationHelpersContext.Provider value={navigation}>
|
||||
<TabView
|
||||
{...rest}
|
||||
onIndexChange={(index) =>
|
||||
navigation.dispatch({
|
||||
...TabActions.jumpTo(state.routes[index].name),
|
||||
target: state.key,
|
||||
})
|
||||
}
|
||||
renderScene={({ route }) => descriptors[route.key].render()}
|
||||
navigationState={state}
|
||||
renderTabBar={renderTabBar}
|
||||
renderPager={pager}
|
||||
renderLazyPlaceholder={lazyPlaceholder}
|
||||
onSwipeStart={() => navigation.emit({ type: 'swipeStart' })}
|
||||
onSwipeEnd={() => navigation.emit({ type: 'swipeEnd' })}
|
||||
sceneContainerStyle={[
|
||||
{ backgroundColor: colors.background },
|
||||
sceneContainerStyle,
|
||||
]}
|
||||
/>
|
||||
</NavigationHelpersContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,67 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [5.2.3](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.2.2...@react-navigation/native@5.2.3) (2020-05-01)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* default linking enabled to true ([c7b8e2e](https://github.com/react-navigation/react-navigation/tree/master/packages/native/commit/c7b8e2e9666733143eef156b27f3e4995c36b856))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.2](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.2.1...@react-navigation/native@5.2.2) (2020-05-01)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* don't throw when using 'useLinking'. fixes [#8171](https://github.com/react-navigation/react-navigation/tree/master/packages/native/issues/8171) ([10eca8b](https://github.com/react-navigation/react-navigation/tree/master/packages/native/commit/10eca8b92edbce6dbef8abaf189e4b59a29b3748))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.1](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.2.0...@react-navigation/native@5.2.1) (2020-04-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* render fallback only if linking is enabled. closes [#8161](https://github.com/react-navigation/react-navigation/tree/master/packages/native/issues/8161) ([1c075ff](https://github.com/react-navigation/react-navigation/tree/master/packages/native/commit/1c075ffb169d233ed0515efea264a5a69b4de52e))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.2.0](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.1.7...@react-navigation/native@5.2.0) (2020-04-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add catch to thenable returned by getInitialState ([d6fa279](https://github.com/react-navigation/react-navigation/tree/master/packages/native/commit/d6fa279d9371c7a6403d10d209a2a64147891c63))
|
||||
* return onPress instead of onClick for useLinkProps ([ae5442e](https://github.com/react-navigation/react-navigation/tree/master/packages/native/commit/ae5442ebe812b91fa1f12164f27d1aeed918ab0e))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add `useLinkBuilder` hook to build links ([2792f43](https://github.com/react-navigation/react-navigation/tree/master/packages/native/commit/2792f438fe45428fe193e3708fee7ad61966cbf4))
|
||||
* add a useLinkProps hook ([f2291d1](https://github.com/react-navigation/react-navigation/tree/master/packages/native/commit/f2291d110faa2aa8e10c9133c1c0c28d54af7917))
|
||||
* add action prop to Link ([942d2be](https://github.com/react-navigation/react-navigation/tree/master/packages/native/commit/942d2be2c72720469475ce12ec8df23825994dbf))
|
||||
* add Link component as useLinkTo hook for navigating to links ([2573b5b](https://github.com/react-navigation/react-navigation/tree/master/packages/native/commit/2573b5beaac1240434e52f3f57bb29da2f541c88))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.7](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.1.6...@react-navigation/native@5.1.7) (2020-04-27)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/native
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.6](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.1.5...@react-navigation/native@5.1.6) (2020-04-17)
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/native",
|
||||
"description": "React Native integration for React Navigation",
|
||||
"version": "5.1.6",
|
||||
"version": "5.2.3",
|
||||
"keywords": [
|
||||
"react-native",
|
||||
"react-navigation",
|
||||
@@ -31,7 +31,7 @@
|
||||
"clean": "del lib"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-navigation/core": "^5.3.4"
|
||||
"@react-navigation/core": "^5.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.10.0",
|
||||
|
||||
42
packages/native/src/Link.tsx
Normal file
42
packages/native/src/Link.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import * as React from 'react';
|
||||
import { Text, TextProps, GestureResponderEvent, Platform } from 'react-native';
|
||||
import { NavigationAction } from '@react-navigation/core';
|
||||
import useLinkProps from './useLinkProps';
|
||||
|
||||
type Props = {
|
||||
to: string;
|
||||
action?: NavigationAction;
|
||||
target?: string;
|
||||
} & (TextProps & { children: React.ReactNode });
|
||||
|
||||
/**
|
||||
* Component to render link to another screen using a path.
|
||||
* Uses an anchor tag on the web.
|
||||
*
|
||||
* @param props.to Absolute path to screen (e.g. `/feeds/hot`).
|
||||
* @param props.action Optional action to use for in-page navigation. By default, the path is parsed to an action based on linking config.
|
||||
* @param props.children Child elements to render the content.
|
||||
*/
|
||||
export default function Link({ to, action, ...rest }: Props) {
|
||||
const props = useLinkProps({ to, action });
|
||||
|
||||
const onPress = (
|
||||
e: React.MouseEvent<HTMLAnchorElement, MouseEvent> | GestureResponderEvent
|
||||
) => {
|
||||
if ('onPress' in rest) {
|
||||
// @ts-ignore
|
||||
rest.onPress?.(e);
|
||||
}
|
||||
|
||||
props.onPress(e);
|
||||
};
|
||||
|
||||
return React.createElement(Text, {
|
||||
...props,
|
||||
...rest,
|
||||
...Platform.select({
|
||||
web: { onClick: onPress } as any,
|
||||
default: { onPress },
|
||||
}),
|
||||
});
|
||||
}
|
||||
8
packages/native/src/LinkingContext.tsx
Normal file
8
packages/native/src/LinkingContext.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
import * as React from 'react';
|
||||
import { LinkingOptions } from './types';
|
||||
|
||||
const LinkingContext = React.createContext<{
|
||||
options: LinkingOptions | undefined;
|
||||
}>({ options: undefined });
|
||||
|
||||
export default LinkingContext;
|
||||
@@ -6,38 +6,70 @@ import {
|
||||
} from '@react-navigation/core';
|
||||
import ThemeProvider from './theming/ThemeProvider';
|
||||
import DefaultTheme from './theming/DefaultTheme';
|
||||
import LinkingContext from './LinkingContext';
|
||||
import useThenable from './useThenable';
|
||||
import useLinking from './useLinking';
|
||||
import useBackButton from './useBackButton';
|
||||
import { Theme } from './types';
|
||||
import { Theme, LinkingOptions } from './types';
|
||||
|
||||
type Props = NavigationContainerProps & {
|
||||
theme?: Theme;
|
||||
linking?: LinkingOptions;
|
||||
fallback?: React.ReactNode;
|
||||
};
|
||||
|
||||
/**
|
||||
* Container component which holds the navigation state
|
||||
* designed for mobile apps.
|
||||
* Container component which holds the navigation state designed for React Native apps.
|
||||
* This should be rendered at the root wrapping the whole app.
|
||||
*
|
||||
* @param props.initialState Initial state object for the navigation tree.
|
||||
* @param props.initialState Initial state object for the navigation tree. When deep link handling is enabled, this will be ignored if there's an incoming link.
|
||||
* @param props.onStateChange Callback which is called with the latest navigation state when it changes.
|
||||
* @param props.theme Theme object for the navigators.
|
||||
* @param props.linking Options for deep linking. Deep link handling is enabled when this prop is provided, unless `linking.enabled` is `false`.
|
||||
* @param props.fallback Fallback component to render until we have finished getting initial state when linking is enabled. Defaults to `null`.
|
||||
* @param props.children Child elements to render the content.
|
||||
* @param props.ref Ref object which refers to the navigation object containing helper methods.
|
||||
*/
|
||||
const NavigationContainer = React.forwardRef(function NavigationContainer(
|
||||
{ theme = DefaultTheme, ...rest }: Props,
|
||||
{ theme = DefaultTheme, linking, fallback = null, ...rest }: Props,
|
||||
ref?: React.Ref<NavigationContainerRef | null>
|
||||
) {
|
||||
const isLinkingEnabled = linking ? linking.enabled !== false : false;
|
||||
|
||||
const refContainer = React.useRef<NavigationContainerRef>(null);
|
||||
|
||||
useBackButton(refContainer);
|
||||
|
||||
const { getInitialState } = useLinking(refContainer, {
|
||||
enabled: isLinkingEnabled,
|
||||
prefixes: [],
|
||||
...linking,
|
||||
});
|
||||
|
||||
const [isReady, initialState = rest.initialState] = useThenable(
|
||||
getInitialState
|
||||
);
|
||||
|
||||
React.useImperativeHandle(ref, () => refContainer.current);
|
||||
|
||||
const linkingContext = React.useMemo(() => ({ options: linking }), [linking]);
|
||||
|
||||
if (isLinkingEnabled && !isReady) {
|
||||
// This is temporary until we have Suspense for data-fetching
|
||||
// Then the fallback will be handled by a parent `Suspense` component
|
||||
return fallback as React.ReactElement;
|
||||
}
|
||||
|
||||
return (
|
||||
<ThemeProvider value={theme}>
|
||||
<BaseNavigationContainer {...rest} ref={refContainer} />
|
||||
</ThemeProvider>
|
||||
<LinkingContext.Provider value={linkingContext}>
|
||||
<ThemeProvider value={theme}>
|
||||
<BaseNavigationContainer
|
||||
{...rest}
|
||||
initialState={initialState}
|
||||
ref={refContainer}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
</LinkingContext.Provider>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
export default function () {
|
||||
throw new Error(
|
||||
"'NavigationNativeContainer' has been renamed to 'NavigationContainer"
|
||||
);
|
||||
}
|
||||
@@ -17,7 +17,7 @@ it('throws if multiple instances of useLinking are used', () => {
|
||||
let element;
|
||||
|
||||
expect(() => (element = render(<Sample />))).toThrowError(
|
||||
"Looks like you are using 'useLinking' in multiple components."
|
||||
'Looks like you have configured linking in multiple places.'
|
||||
);
|
||||
|
||||
// @ts-ignore
|
||||
@@ -41,9 +41,7 @@ it('throws if multiple instances of useLinking are used', () => {
|
||||
<B />
|
||||
</>
|
||||
))
|
||||
).toThrowError(
|
||||
"Looks like you are using 'useLinking' in multiple components."
|
||||
);
|
||||
).toThrowError('Looks like you have configured linking in multiple places.');
|
||||
|
||||
// @ts-ignore
|
||||
element?.unmount();
|
||||
@@ -57,5 +55,19 @@ it('throws if multiple instances of useLinking are used', () => {
|
||||
|
||||
render(wrapper2).unmount();
|
||||
|
||||
expect(() => render(wrapper2)).not.toThrow();
|
||||
expect(() => (element = render(wrapper2))).not.toThrow();
|
||||
|
||||
// @ts-ignore
|
||||
element?.unmount();
|
||||
|
||||
function Sample3() {
|
||||
useLinking(ref, options);
|
||||
useLinking(ref, { ...options, enabled: false });
|
||||
return null;
|
||||
}
|
||||
|
||||
expect(() => (element = render(<Sample3 />))).not.toThrowError();
|
||||
|
||||
// @ts-ignore
|
||||
element?.unmount();
|
||||
});
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
export * from '@react-navigation/core';
|
||||
|
||||
export { default as NavigationContainer } from './NavigationContainer';
|
||||
export { default as NavigationNativeContainer } from './NavigationNativeContainer';
|
||||
|
||||
export { default as useBackButton } from './useBackButton';
|
||||
export { default as useLinking } from './useLinking';
|
||||
export { default as useScrollToTop } from './useScrollToTop';
|
||||
|
||||
export { default as DefaultTheme } from './theming/DefaultTheme';
|
||||
export { default as DarkTheme } from './theming/DarkTheme';
|
||||
export { default as ThemeProvider } from './theming/ThemeProvider';
|
||||
export { default as useTheme } from './theming/useTheme';
|
||||
|
||||
export { default as Link } from './Link';
|
||||
export { default as useLinking } from './useLinking';
|
||||
export { default as useLinkTo } from './useLinkTo';
|
||||
export { default as useLinkProps } from './useLinkProps';
|
||||
export { default as useLinkBuilder } from './useLinkBuilder';
|
||||
|
||||
@@ -15,6 +15,11 @@ export type Theme = {
|
||||
};
|
||||
|
||||
export type LinkingOptions = {
|
||||
/**
|
||||
* Whether deep link handling should be enabled.
|
||||
* Defaults to true.
|
||||
*/
|
||||
enabled?: boolean;
|
||||
/**
|
||||
* The prefixes are stripped from the URL before parsing them.
|
||||
* Usually they are the `scheme` + `host` (e.g. `myapp://chat?user=jane`)
|
||||
|
||||
77
packages/native/src/useLinkBuilder.tsx
Normal file
77
packages/native/src/useLinkBuilder.tsx
Normal file
@@ -0,0 +1,77 @@
|
||||
import * as React from 'react';
|
||||
import {
|
||||
NavigationHelpers,
|
||||
NavigationHelpersContext,
|
||||
NavigationProp,
|
||||
ParamListBase,
|
||||
getPathFromState,
|
||||
} from '@react-navigation/core';
|
||||
import LinkingContext from './LinkingContext';
|
||||
|
||||
type NavigationObject =
|
||||
| NavigationHelpers<ParamListBase>
|
||||
| NavigationProp<ParamListBase>;
|
||||
|
||||
type MinimalState = {
|
||||
index: number;
|
||||
routes: { name: string; params?: object; state?: MinimalState }[];
|
||||
};
|
||||
|
||||
const getRootStateForNavigate = (
|
||||
navigation: NavigationObject,
|
||||
state: MinimalState
|
||||
): MinimalState => {
|
||||
const parent = navigation.dangerouslyGetParent();
|
||||
|
||||
if (parent) {
|
||||
const parentState = parent.dangerouslyGetState();
|
||||
|
||||
return getRootStateForNavigate(parent, {
|
||||
index: 0,
|
||||
routes: [
|
||||
{
|
||||
...parentState.routes[parentState.index],
|
||||
state: state,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
return state;
|
||||
};
|
||||
|
||||
/**
|
||||
* Build destination link for a navigate action.
|
||||
* Useful for showing anchor tags on the web for buttons that perform navigation.
|
||||
*/
|
||||
export default function useLinkBuilder() {
|
||||
const navigation = React.useContext(NavigationHelpersContext);
|
||||
const linking = React.useContext(LinkingContext);
|
||||
|
||||
const buildLink = React.useCallback(
|
||||
(name: string, params?: object) => {
|
||||
const { options } = linking;
|
||||
|
||||
// If we couldn't find a navigation object in context, we're at root
|
||||
// So we'll construct a basic state object to use
|
||||
const state = navigation
|
||||
? getRootStateForNavigate(navigation, {
|
||||
index: 0,
|
||||
routes: [{ name, params }],
|
||||
})
|
||||
: {
|
||||
index: 0,
|
||||
routes: [{ name, params }],
|
||||
};
|
||||
|
||||
const path = options?.getPathFromState
|
||||
? options.getPathFromState(state, options?.config)
|
||||
: getPathFromState(state, options?.config);
|
||||
|
||||
return path;
|
||||
},
|
||||
[linking, navigation]
|
||||
);
|
||||
|
||||
return buildLink;
|
||||
}
|
||||
62
packages/native/src/useLinkProps.tsx
Normal file
62
packages/native/src/useLinkProps.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
import * as React from 'react';
|
||||
import { Platform, GestureResponderEvent } from 'react-native';
|
||||
import {
|
||||
NavigationAction,
|
||||
NavigationHelpersContext,
|
||||
} from '@react-navigation/core';
|
||||
import useLinkTo from './useLinkTo';
|
||||
|
||||
type Props = {
|
||||
to: string;
|
||||
action?: NavigationAction;
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook to get props for an anchor tag so it can work with in page navigation.
|
||||
*
|
||||
* @param props.to Absolute path to screen (e.g. `/feeds/hot`).
|
||||
* @param props.action Optional action to use for in-page navigation. By default, the path is parsed to an action based on linking config.
|
||||
*/
|
||||
export default function useLinkProps({ to, action }: Props) {
|
||||
const navigation = React.useContext(NavigationHelpersContext);
|
||||
const linkTo = useLinkTo();
|
||||
|
||||
const onPress = (
|
||||
e: React.MouseEvent<HTMLAnchorElement, MouseEvent> | GestureResponderEvent
|
||||
) => {
|
||||
let shouldHandle = false;
|
||||
|
||||
if (Platform.OS !== 'web' || !e) {
|
||||
shouldHandle = e ? !e.defaultPrevented : true;
|
||||
} else if (
|
||||
!e.defaultPrevented && // onPress prevented default
|
||||
// @ts-ignore
|
||||
!(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) && // ignore clicks with modifier keys
|
||||
// @ts-ignore
|
||||
(e.button == null || e.button === 0) && // ignore everything but left clicks
|
||||
// @ts-ignore
|
||||
[undefined, null, '', 'self'].includes(e.currentTarget?.target) // let browser handle "target=_blank" etc.
|
||||
) {
|
||||
e.preventDefault();
|
||||
shouldHandle = true;
|
||||
}
|
||||
|
||||
if (shouldHandle) {
|
||||
if (action) {
|
||||
if (navigation) {
|
||||
navigation.dispatch(action);
|
||||
} else {
|
||||
throw new Error("Couldn't find a navigation object.");
|
||||
}
|
||||
} else {
|
||||
linkTo(to);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
href: to,
|
||||
accessibilityRole: 'link' as const,
|
||||
onPress,
|
||||
};
|
||||
}
|
||||
55
packages/native/src/useLinkTo.tsx
Normal file
55
packages/native/src/useLinkTo.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
import * as React from 'react';
|
||||
import {
|
||||
getStateFromPath,
|
||||
getActionFromState,
|
||||
NavigationContext,
|
||||
} from '@react-navigation/core';
|
||||
import LinkingContext from './LinkingContext';
|
||||
|
||||
export default function useLinkTo() {
|
||||
const navigation = React.useContext(NavigationContext);
|
||||
const linking = React.useContext(LinkingContext);
|
||||
|
||||
const linkTo = React.useCallback(
|
||||
(path: string) => {
|
||||
if (!path.startsWith('/')) {
|
||||
throw new Error(`The path must start with '/' (${path}).`);
|
||||
}
|
||||
|
||||
if (navigation === undefined) {
|
||||
throw new Error(
|
||||
"Couldn't find a navigation object. Is your component inside a screen in a navigator?"
|
||||
);
|
||||
}
|
||||
|
||||
const { options } = linking;
|
||||
|
||||
const state = options?.getStateFromPath
|
||||
? options.getStateFromPath(path, options.config)
|
||||
: getStateFromPath(path, options?.config);
|
||||
|
||||
if (state) {
|
||||
let root = navigation;
|
||||
let current;
|
||||
|
||||
// Traverse up to get the root navigation
|
||||
while ((current = root.dangerouslyGetParent())) {
|
||||
root = current;
|
||||
}
|
||||
|
||||
const action = getActionFromState(state);
|
||||
|
||||
if (action !== undefined) {
|
||||
root.dispatch(action);
|
||||
} else {
|
||||
root.reset(state);
|
||||
}
|
||||
} else {
|
||||
throw new Error('Failed to parse the path to a navigation state.');
|
||||
}
|
||||
},
|
||||
[linking, navigation]
|
||||
);
|
||||
|
||||
return linkTo;
|
||||
}
|
||||
@@ -12,21 +12,28 @@ let isUsingLinking = false;
|
||||
export default function useLinking(
|
||||
ref: React.RefObject<NavigationContainerRef>,
|
||||
{
|
||||
enabled = true,
|
||||
prefixes,
|
||||
config,
|
||||
getStateFromPath = getStateFromPathDefault,
|
||||
}: LinkingOptions
|
||||
) {
|
||||
React.useEffect(() => {
|
||||
if (isUsingLinking) {
|
||||
if (enabled !== false && isUsingLinking) {
|
||||
throw new Error(
|
||||
"Looks like you are using 'useLinking' in multiple components. This is likely an error since deep links should only be handled in one place to avoid conflicts." +
|
||||
(Platform.OS === 'android'
|
||||
? "\n\nIf you're not using it in multiple components, ensure that you have set 'android:launchMode=singleTask' in the '<activity />' section of the 'AndroidManifest.xml' file to avoid launching multiple activities which run multiple instances of the root component."
|
||||
: '')
|
||||
[
|
||||
'Looks like you have configured linking in multiple places. This is likely an error since deep links should only be handled in one place to avoid conflicts. Make sure that:',
|
||||
"- You are not using both 'linking' prop and 'useLinking'",
|
||||
"- You don't have 'useLinking' in multiple components",
|
||||
Platform.OS === 'android'
|
||||
? "- You have set 'android:launchMode=singleTask' in the '<activity />' section of the 'AndroidManifest.xml' file to avoid launching multiple instances"
|
||||
: '',
|
||||
]
|
||||
.join('\n')
|
||||
.trim()
|
||||
);
|
||||
} else {
|
||||
isUsingLinking = true;
|
||||
isUsingLinking = enabled !== false;
|
||||
}
|
||||
|
||||
return () => {
|
||||
@@ -37,15 +44,17 @@ export default function useLinking(
|
||||
// We store these options in ref to avoid re-creating getInitialState and re-subscribing listeners
|
||||
// This lets user avoid wrapping the items in `React.useCallback` or `React.useMemo`
|
||||
// Not re-creating `getInitialState` is important coz it makes it easier for the user to use in an effect
|
||||
const enabledRef = React.useRef(enabled);
|
||||
const prefixesRef = React.useRef(prefixes);
|
||||
const configRef = React.useRef(config);
|
||||
const getStateFromPathRef = React.useRef(getStateFromPath);
|
||||
|
||||
React.useEffect(() => {
|
||||
enabledRef.current = enabled;
|
||||
prefixesRef.current = prefixes;
|
||||
configRef.current = config;
|
||||
getStateFromPathRef.current = getStateFromPath;
|
||||
}, [config, getStateFromPath, prefixes]);
|
||||
}, [config, enabled, getStateFromPath, prefixes]);
|
||||
|
||||
const extractPathFromURL = React.useCallback((url: string) => {
|
||||
for (const prefix of prefixesRef.current) {
|
||||
@@ -58,7 +67,19 @@ export default function useLinking(
|
||||
}, []);
|
||||
|
||||
const getInitialState = React.useCallback(async () => {
|
||||
const url = await Linking.getInitialURL();
|
||||
if (!enabledRef.current) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const url = await (Promise.race([
|
||||
Linking.getInitialURL(),
|
||||
new Promise((resolve) =>
|
||||
// Timeout in 150ms if `getInitialState` doesn't resolve
|
||||
// Workaround for https://github.com/facebook/react-native/issues/25675
|
||||
setTimeout(resolve, 150)
|
||||
),
|
||||
]) as Promise<string | null | undefined>);
|
||||
|
||||
const path = url ? extractPathFromURL(url) : null;
|
||||
|
||||
if (path) {
|
||||
@@ -70,6 +91,10 @@ export default function useLinking(
|
||||
|
||||
React.useEffect(() => {
|
||||
const listener = ({ url }: { url: string }) => {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const path = extractPathFromURL(url);
|
||||
const navigation = ref.current;
|
||||
|
||||
@@ -91,7 +116,7 @@ export default function useLinking(
|
||||
Linking.addEventListener('url', listener);
|
||||
|
||||
return () => Linking.removeEventListener('url', listener);
|
||||
}, [extractPathFromURL, ref]);
|
||||
}, [enabled, extractPathFromURL, ref]);
|
||||
|
||||
return {
|
||||
getInitialState,
|
||||
|
||||
@@ -8,6 +8,17 @@ import {
|
||||
} from '@react-navigation/core';
|
||||
import { LinkingOptions } from './types';
|
||||
|
||||
type ResultState = ReturnType<typeof getStateFromPathDefault>;
|
||||
|
||||
type HistoryState = { index: number };
|
||||
|
||||
declare const history: {
|
||||
state?: HistoryState;
|
||||
go(delta: number): void;
|
||||
pushState(state: HistoryState, title: string, url: string): void;
|
||||
replaceState(state: HistoryState, title: string, url: string): void;
|
||||
};
|
||||
|
||||
const getStateLength = (state: NavigationState) => {
|
||||
let length = 0;
|
||||
|
||||
@@ -32,18 +43,25 @@ let isUsingLinking = false;
|
||||
export default function useLinking(
|
||||
ref: React.RefObject<NavigationContainerRef>,
|
||||
{
|
||||
enabled = true,
|
||||
config,
|
||||
getStateFromPath = getStateFromPathDefault,
|
||||
getPathFromState = getPathFromStateDefault,
|
||||
}: LinkingOptions
|
||||
) {
|
||||
React.useEffect(() => {
|
||||
if (isUsingLinking) {
|
||||
if (enabled !== false && isUsingLinking) {
|
||||
throw new Error(
|
||||
"Looks like you are using 'useLinking' in multiple components. This is likely an error since URL integration should only be handled in one place to avoid conflicts. Also ensure that you set your android activity launchMode to single task in your AndroiManifest.xml file."
|
||||
[
|
||||
'Looks like you have configured linking in multiple places. This is likely an error since URL integration should only be handled in one place to avoid conflicts. Make sure that:',
|
||||
"- You are not using both 'linking' prop and 'useLinking'",
|
||||
"- You don't have 'useLinking' in multiple components",
|
||||
]
|
||||
.join('\n')
|
||||
.trim()
|
||||
);
|
||||
} else {
|
||||
isUsingLinking = true;
|
||||
isUsingLinking = enabled !== false;
|
||||
}
|
||||
|
||||
return () => {
|
||||
@@ -54,25 +72,41 @@ export default function useLinking(
|
||||
// We store these options in ref to avoid re-creating getInitialState and re-subscribing listeners
|
||||
// This lets user avoid wrapping the items in `React.useCallback` or `React.useMemo`
|
||||
// Not re-creating `getInitialState` is important coz it makes it easier for the user to use in an effect
|
||||
const enabledRef = React.useRef(enabled);
|
||||
const configRef = React.useRef(config);
|
||||
const getStateFromPathRef = React.useRef(getStateFromPath);
|
||||
const getPathFromStateRef = React.useRef(getPathFromState);
|
||||
|
||||
React.useEffect(() => {
|
||||
enabledRef.current = enabled;
|
||||
configRef.current = config;
|
||||
getStateFromPathRef.current = getStateFromPath;
|
||||
getPathFromStateRef.current = getPathFromState;
|
||||
}, [config, getPathFromState, getStateFromPath]);
|
||||
}, [config, enabled, getPathFromState, getStateFromPath]);
|
||||
|
||||
// Make it an async function to keep consistent with the native impl
|
||||
const getInitialState = React.useCallback(async () => {
|
||||
const path = location.pathname + location.search;
|
||||
const getInitialState = React.useCallback(() => {
|
||||
let value: ResultState | undefined;
|
||||
|
||||
if (path) {
|
||||
return getStateFromPathRef.current(path, configRef.current);
|
||||
} else {
|
||||
return undefined;
|
||||
if (enabledRef.current) {
|
||||
const path = location.pathname + location.search;
|
||||
|
||||
if (path) {
|
||||
value = getStateFromPathRef.current(path, configRef.current);
|
||||
}
|
||||
}
|
||||
|
||||
const then = (callback: (state: ResultState | undefined) => void) =>
|
||||
Promise.resolve(callback(value));
|
||||
|
||||
// Make it a thenable to keep consistent with the native impl
|
||||
const thenable = {
|
||||
then,
|
||||
catch() {
|
||||
return thenable;
|
||||
},
|
||||
};
|
||||
|
||||
return thenable;
|
||||
}, []);
|
||||
|
||||
const previousStateLengthRef = React.useRef<number | undefined>(undefined);
|
||||
@@ -92,10 +126,10 @@ export default function useLinking(
|
||||
const numberOfIndicesAhead = React.useRef(0);
|
||||
|
||||
React.useEffect(() => {
|
||||
window.addEventListener('popstate', () => {
|
||||
const onPopState = () => {
|
||||
const navigation = ref.current;
|
||||
|
||||
if (!navigation) {
|
||||
if (!navigation || !enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -169,10 +203,18 @@ export default function useLinking(
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}, [ref]);
|
||||
};
|
||||
|
||||
window.addEventListener('popstate', onPopState);
|
||||
|
||||
return () => window.removeEventListener('popstate', onPopState);
|
||||
}, [enabled, ref]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ref.current && previousStateLengthRef.current === undefined) {
|
||||
previousStateLengthRef.current = getStateLength(
|
||||
ref.current.getRootState()
|
||||
|
||||
47
packages/native/src/useThenable.tsx
Normal file
47
packages/native/src/useThenable.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import * as React from 'react';
|
||||
|
||||
type Thenable<T> = { then(cb: (result: T) => void): void };
|
||||
|
||||
export default function useThenable<T>(create: () => Thenable<T>) {
|
||||
const [promise] = React.useState(create);
|
||||
|
||||
// Check if our thenable is synchronous
|
||||
let resolved = false;
|
||||
let value: T | undefined;
|
||||
|
||||
promise.then((result) => {
|
||||
resolved = true;
|
||||
value = result;
|
||||
});
|
||||
|
||||
const [state, setState] = React.useState<[boolean, T | undefined]>([
|
||||
resolved,
|
||||
value,
|
||||
]);
|
||||
|
||||
React.useEffect(() => {
|
||||
let cancelled = false;
|
||||
|
||||
const resolve = async () => {
|
||||
let result;
|
||||
|
||||
try {
|
||||
result = await promise;
|
||||
} finally {
|
||||
if (!cancelled) {
|
||||
setState([true, result]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (!resolved) {
|
||||
resolve();
|
||||
}
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [promise, resolved]);
|
||||
|
||||
return state;
|
||||
}
|
||||
@@ -3,6 +3,28 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [5.4.2](https://github.com/react-navigation/react-navigation/tree/master/packages/routers/compare/@react-navigation/routers@5.4.1...@react-navigation/routers@5.4.2) (2020-04-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix backBehavior with initialRoute ([#8110](https://github.com/react-navigation/react-navigation/tree/master/packages/routers/issues/8110)) ([420f692](https://github.com/react-navigation/react-navigation/tree/master/packages/routers/commit/420f6926e111d32c2388c44ff0bee2b8ea238a57))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.4.1](https://github.com/react-navigation/react-navigation/tree/master/packages/routers/compare/@react-navigation/routers@5.4.0...@react-navigation/routers@5.4.1) (2020-04-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix behaviour of openByDefault in drawer when focus changes ([b172b51](https://github.com/react-navigation/react-navigation/tree/master/packages/routers/commit/b172b51f175a9f8044cb2a8e9d74a86480d8f11e))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.4.0](https://github.com/react-navigation/react-navigation/tree/master/packages/routers/compare/@react-navigation/routers@5.3.0...@react-navigation/routers@5.4.0) (2020-04-17)
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/routers",
|
||||
"description": "Routers to help build custom navigators",
|
||||
"version": "5.4.0",
|
||||
"version": "5.4.2",
|
||||
"keywords": [
|
||||
"react",
|
||||
"react-native",
|
||||
|
||||
@@ -153,6 +153,10 @@ export default function DrawerRouter({
|
||||
getStateForRouteFocus(state, key) {
|
||||
const result = router.getStateForRouteFocus(state, key);
|
||||
|
||||
if (openByDefault) {
|
||||
return openDrawer(result);
|
||||
}
|
||||
|
||||
return closeDrawer(result);
|
||||
},
|
||||
|
||||
|
||||
@@ -59,21 +59,31 @@ export const TabActions = {
|
||||
const getRouteHistory = (
|
||||
routes: Route<string>[],
|
||||
index: number,
|
||||
backBehavior: BackBehavior
|
||||
backBehavior: BackBehavior,
|
||||
initialRouteName: string | undefined
|
||||
) => {
|
||||
const history = [{ type: TYPE_ROUTE, key: routes[index].key }];
|
||||
let initialRouteIndex;
|
||||
|
||||
switch (backBehavior) {
|
||||
case 'initialRoute':
|
||||
if (index !== 0) {
|
||||
history.unshift({ type: TYPE_ROUTE, key: routes[0].key });
|
||||
}
|
||||
break;
|
||||
case 'order':
|
||||
for (let i = index; i > 0; i--) {
|
||||
history.unshift({ type: TYPE_ROUTE, key: routes[i - 1].key });
|
||||
}
|
||||
break;
|
||||
case 'initialRoute':
|
||||
initialRouteIndex = routes.findIndex(
|
||||
(route) => route.name === initialRouteName
|
||||
);
|
||||
initialRouteIndex = initialRouteIndex === -1 ? 0 : initialRouteIndex;
|
||||
|
||||
if (initialRouteIndex !== index) {
|
||||
history.unshift({
|
||||
type: TYPE_ROUTE,
|
||||
key: routes[initialRouteIndex].key,
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'history':
|
||||
// The history will fill up on navigation
|
||||
break;
|
||||
@@ -85,7 +95,8 @@ const getRouteHistory = (
|
||||
const changeIndex = (
|
||||
state: TabNavigationState,
|
||||
index: number,
|
||||
backBehavior: BackBehavior
|
||||
backBehavior: BackBehavior,
|
||||
initialRouteName: string | undefined
|
||||
) => {
|
||||
let history;
|
||||
|
||||
@@ -96,7 +107,12 @@ const changeIndex = (
|
||||
.filter((it) => (it.type === 'route' ? it.key !== currentKey : false))
|
||||
.concat({ type: TYPE_ROUTE, key: currentKey });
|
||||
} else {
|
||||
history = getRouteHistory(state.routes, index, backBehavior);
|
||||
history = getRouteHistory(
|
||||
state.routes,
|
||||
index,
|
||||
backBehavior,
|
||||
initialRouteName
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -130,7 +146,12 @@ export default function TabRouter({
|
||||
params: routeParamList[name],
|
||||
}));
|
||||
|
||||
const history = getRouteHistory(routes, index, backBehavior);
|
||||
const history = getRouteHistory(
|
||||
routes,
|
||||
index,
|
||||
backBehavior,
|
||||
initialRouteName
|
||||
);
|
||||
|
||||
return {
|
||||
stale: false,
|
||||
@@ -189,7 +210,12 @@ export default function TabRouter({
|
||||
);
|
||||
|
||||
if (!history?.length) {
|
||||
history = getRouteHistory(routes, index, backBehavior);
|
||||
history = getRouteHistory(
|
||||
routes,
|
||||
index,
|
||||
backBehavior,
|
||||
initialRouteName
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -223,7 +249,12 @@ export default function TabRouter({
|
||||
);
|
||||
|
||||
if (!history.length) {
|
||||
history = getRouteHistory(routes, index, backBehavior);
|
||||
history = getRouteHistory(
|
||||
routes,
|
||||
index,
|
||||
backBehavior,
|
||||
initialRouteName
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -242,7 +273,7 @@ export default function TabRouter({
|
||||
return state;
|
||||
}
|
||||
|
||||
return changeIndex(state, index, backBehavior);
|
||||
return changeIndex(state, index, backBehavior, initialRouteName);
|
||||
},
|
||||
|
||||
getStateForAction(state, action) {
|
||||
@@ -284,7 +315,8 @@ export default function TabRouter({
|
||||
: state.routes,
|
||||
},
|
||||
index,
|
||||
backBehavior
|
||||
backBehavior,
|
||||
initialRouteName
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -214,6 +214,161 @@ it("doesn't rehydrate state if it's not stale", () => {
|
||||
).toBe(state);
|
||||
});
|
||||
|
||||
it('restores correct history on rehydrating with backBehavior: order', () => {
|
||||
const router = TabRouter({ backBehavior: 'order' });
|
||||
|
||||
const options = {
|
||||
routeNames: ['foo', 'bar', 'baz', 'qux'],
|
||||
routeParamList: {},
|
||||
};
|
||||
|
||||
expect(
|
||||
router.getRehydratedState(
|
||||
{
|
||||
index: 2,
|
||||
routes: [
|
||||
{ key: 'foo-0', name: 'foo' },
|
||||
{ key: 'bar-0', name: 'bar' },
|
||||
{ key: 'baz-0', name: 'baz' },
|
||||
{ key: 'qux-0', name: 'qux' },
|
||||
],
|
||||
},
|
||||
options
|
||||
)
|
||||
).toEqual({
|
||||
key: 'tab-test',
|
||||
index: 2,
|
||||
routeNames: ['foo', 'bar', 'baz', 'qux'],
|
||||
routes: [
|
||||
{ key: 'foo-0', name: 'foo' },
|
||||
{ key: 'bar-0', name: 'bar' },
|
||||
{ key: 'baz-0', name: 'baz' },
|
||||
{ key: 'qux-0', name: 'qux' },
|
||||
],
|
||||
history: [
|
||||
{ key: 'foo-0', type: 'route' },
|
||||
{ key: 'bar-0', type: 'route' },
|
||||
{ key: 'baz-0', type: 'route' },
|
||||
],
|
||||
stale: false,
|
||||
type: 'tab',
|
||||
});
|
||||
});
|
||||
|
||||
it('restores correct history on rehydrating with backBehavior: history', () => {
|
||||
const router = TabRouter({ backBehavior: 'history' });
|
||||
|
||||
const options = {
|
||||
routeNames: ['foo', 'bar', 'baz', 'qux'],
|
||||
routeParamList: {},
|
||||
};
|
||||
|
||||
expect(
|
||||
router.getRehydratedState(
|
||||
{
|
||||
index: 2,
|
||||
routes: [
|
||||
{ key: 'foo-0', name: 'foo' },
|
||||
{ key: 'bar-0', name: 'bar' },
|
||||
{ key: 'baz-0', name: 'baz' },
|
||||
{ key: 'qux-0', name: 'qux' },
|
||||
],
|
||||
},
|
||||
options
|
||||
)
|
||||
).toEqual({
|
||||
key: 'tab-test',
|
||||
index: 2,
|
||||
routeNames: ['foo', 'bar', 'baz', 'qux'],
|
||||
routes: [
|
||||
{ key: 'foo-0', name: 'foo' },
|
||||
{ key: 'bar-0', name: 'bar' },
|
||||
{ key: 'baz-0', name: 'baz' },
|
||||
{ key: 'qux-0', name: 'qux' },
|
||||
],
|
||||
history: [{ key: 'baz-0', type: 'route' }],
|
||||
stale: false,
|
||||
type: 'tab',
|
||||
});
|
||||
});
|
||||
|
||||
it('restores correct history on rehydrating with backBehavior: initialRoute', () => {
|
||||
const router = TabRouter({ backBehavior: 'initialRoute' });
|
||||
|
||||
const options = {
|
||||
routeNames: ['foo', 'bar', 'baz', 'qux'],
|
||||
routeParamList: {},
|
||||
};
|
||||
|
||||
expect(
|
||||
router.getRehydratedState(
|
||||
{
|
||||
index: 2,
|
||||
routes: [
|
||||
{ key: 'foo-0', name: 'foo' },
|
||||
{ key: 'bar-0', name: 'bar' },
|
||||
{ key: 'baz-0', name: 'baz' },
|
||||
{ key: 'qux-0', name: 'qux' },
|
||||
],
|
||||
},
|
||||
options
|
||||
)
|
||||
).toEqual({
|
||||
key: 'tab-test',
|
||||
index: 2,
|
||||
routeNames: ['foo', 'bar', 'baz', 'qux'],
|
||||
routes: [
|
||||
{ key: 'foo-0', name: 'foo' },
|
||||
{ key: 'bar-0', name: 'bar' },
|
||||
{ key: 'baz-0', name: 'baz' },
|
||||
{ key: 'qux-0', name: 'qux' },
|
||||
],
|
||||
history: [
|
||||
{ key: 'foo-0', type: 'route' },
|
||||
{ key: 'baz-0', type: 'route' },
|
||||
],
|
||||
stale: false,
|
||||
type: 'tab',
|
||||
});
|
||||
});
|
||||
|
||||
it('restores correct history on rehydrating with backBehavior: none', () => {
|
||||
const router = TabRouter({ backBehavior: 'none' });
|
||||
|
||||
const options = {
|
||||
routeNames: ['foo', 'bar', 'baz', 'qux'],
|
||||
routeParamList: {},
|
||||
};
|
||||
|
||||
expect(
|
||||
router.getRehydratedState(
|
||||
{
|
||||
index: 2,
|
||||
routes: [
|
||||
{ key: 'foo-0', name: 'foo' },
|
||||
{ key: 'bar-0', name: 'bar' },
|
||||
{ key: 'baz-0', name: 'baz' },
|
||||
{ key: 'qux-0', name: 'qux' },
|
||||
],
|
||||
},
|
||||
options
|
||||
)
|
||||
).toEqual({
|
||||
key: 'tab-test',
|
||||
index: 2,
|
||||
routeNames: ['foo', 'bar', 'baz', 'qux'],
|
||||
routes: [
|
||||
{ key: 'foo-0', name: 'foo' },
|
||||
{ key: 'bar-0', name: 'bar' },
|
||||
{ key: 'baz-0', name: 'baz' },
|
||||
{ key: 'qux-0', name: 'qux' },
|
||||
],
|
||||
history: [{ key: 'baz-0', type: 'route' }],
|
||||
stale: false,
|
||||
type: 'tab',
|
||||
});
|
||||
});
|
||||
|
||||
it('gets state on route names change', () => {
|
||||
const router = TabRouter({});
|
||||
|
||||
@@ -254,6 +409,38 @@ it('gets state on route names change', () => {
|
||||
stale: false,
|
||||
type: 'tab',
|
||||
});
|
||||
|
||||
expect(
|
||||
router.getStateForRouteNamesChange(
|
||||
{
|
||||
index: 0,
|
||||
key: 'tab-test',
|
||||
routeNames: ['bar', 'baz'],
|
||||
routes: [
|
||||
{ key: 'bar-test', name: 'bar' },
|
||||
{ key: 'baz-test', name: 'baz', params: { answer: 42 } },
|
||||
],
|
||||
history: [{ type: 'route', key: 'bar-test' }],
|
||||
stale: false,
|
||||
type: 'tab',
|
||||
},
|
||||
{
|
||||
routeNames: ['foo', 'fiz'],
|
||||
routeParamList: {},
|
||||
}
|
||||
)
|
||||
).toEqual({
|
||||
index: 0,
|
||||
key: 'tab-test',
|
||||
routeNames: ['foo', 'fiz'],
|
||||
routes: [
|
||||
{ key: 'foo-test', name: 'foo' },
|
||||
{ key: 'fiz-test', name: 'fiz' },
|
||||
],
|
||||
history: [{ type: 'route', key: 'foo-test' }],
|
||||
stale: false,
|
||||
type: 'tab',
|
||||
});
|
||||
});
|
||||
|
||||
it('preserves focused route on route names change', () => {
|
||||
@@ -695,6 +882,78 @@ it('handles back action with backBehavior: initialRoute', () => {
|
||||
).toEqual(null);
|
||||
});
|
||||
|
||||
it('handles back action with backBehavior: initialRoute and initialRouteName', () => {
|
||||
const router = TabRouter({
|
||||
backBehavior: 'initialRoute',
|
||||
initialRouteName: 'baz',
|
||||
});
|
||||
|
||||
const options = {
|
||||
routeNames: ['bar', 'baz', 'qux'],
|
||||
routeParamList: {},
|
||||
};
|
||||
|
||||
let state = router.getInitialState(options);
|
||||
|
||||
expect(
|
||||
router.getStateForAction(state, CommonActions.goBack(), options)
|
||||
).toEqual(null);
|
||||
|
||||
state = router.getStateForAction(
|
||||
state,
|
||||
TabActions.jumpTo('qux'),
|
||||
options
|
||||
) as TabNavigationState;
|
||||
|
||||
expect(
|
||||
router.getStateForAction(state, CommonActions.goBack(), options)
|
||||
).toEqual({
|
||||
stale: false,
|
||||
type: 'tab',
|
||||
key: 'tab-test',
|
||||
index: 1,
|
||||
routeNames: ['bar', 'baz', 'qux'],
|
||||
routes: [
|
||||
{ key: 'bar-test', name: 'bar' },
|
||||
{ key: 'baz-test', name: 'baz' },
|
||||
{ key: 'qux-test', name: 'qux' },
|
||||
],
|
||||
history: [{ type: 'route', key: 'baz-test' }],
|
||||
});
|
||||
|
||||
state = router.getStateForAction(
|
||||
state,
|
||||
TabActions.jumpTo('bar'),
|
||||
options
|
||||
) as TabNavigationState;
|
||||
|
||||
expect(
|
||||
router.getStateForAction(state, CommonActions.goBack(), options)
|
||||
).toEqual({
|
||||
stale: false,
|
||||
type: 'tab',
|
||||
key: 'tab-test',
|
||||
index: 1,
|
||||
routeNames: ['bar', 'baz', 'qux'],
|
||||
routes: [
|
||||
{ key: 'bar-test', name: 'bar' },
|
||||
{ key: 'baz-test', name: 'baz' },
|
||||
{ key: 'qux-test', name: 'qux' },
|
||||
],
|
||||
history: [{ type: 'route', key: 'baz-test' }],
|
||||
});
|
||||
|
||||
state = router.getStateForAction(
|
||||
state,
|
||||
TabActions.jumpTo('baz'),
|
||||
options
|
||||
) as TabNavigationState;
|
||||
|
||||
expect(
|
||||
router.getStateForAction(state, CommonActions.goBack(), options)
|
||||
).toEqual(null);
|
||||
});
|
||||
|
||||
it('handles back action with backBehavior: none', () => {
|
||||
const router = TabRouter({ backBehavior: 'none' });
|
||||
const options = {
|
||||
|
||||
@@ -3,6 +3,63 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [5.2.18](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.2.17...@react-navigation/stack@5.2.18) (2020-05-01)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/stack
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.17](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.2.16...@react-navigation/stack@5.2.17) (2020-05-01)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/stack
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.16](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.2.15...@react-navigation/stack@5.2.16) (2020-04-30)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/stack
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.15](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.2.14...@react-navigation/stack@5.2.15) (2020-04-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* make sure the address bar hides when scrolling on web ([0a19e94](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/commit/0a19e94b23a4d2b5f22d1d9deb0544f586f475ee))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.14](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.2.13...@react-navigation/stack@5.2.14) (2020-04-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* don't add back the route being replaced ([a695cf9](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/commit/a695cf9c058521ccb4a83eb206dc0da7ce100032))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.12](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.2.11...@react-navigation/stack@5.2.12) (2020-04-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* animate card to existing closing state on gesture end ([78485ce](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/commit/78485cea6939b9ffec76e0c4b410bc426ed93402)), closes [#7938](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/issues/7938)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.11](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.2.10...@react-navigation/stack@5.2.11) (2020-04-17)
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/stack",
|
||||
"description": "Stack navigator component for iOS and Android with animated transitions and gestures",
|
||||
"version": "5.2.11",
|
||||
"version": "5.2.18",
|
||||
"keywords": [
|
||||
"react-native-component",
|
||||
"react-component",
|
||||
@@ -40,7 +40,7 @@
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.10.0",
|
||||
"@react-native-community/masked-view": "^0.1.7",
|
||||
"@react-navigation/native": "^5.1.6",
|
||||
"@react-navigation/native": "^5.2.3",
|
||||
"@types/color": "^3.0.1",
|
||||
"@types/react": "^16.9.23",
|
||||
"@types/react-native": "^0.61.22",
|
||||
|
||||
@@ -276,7 +276,8 @@ export type StackNavigationOptions = StackHeaderOptions &
|
||||
cardStyle?: StyleProp<ViewStyle>;
|
||||
/**
|
||||
* Whether transition animation should be enabled the screen.
|
||||
* If you set it to `false`, the screen won't animate when pushing or popping. Defaults to `true`.
|
||||
* If you set it to `false`, the screen won't animate when pushing or popping.
|
||||
* Defaults to `true` on Android and iOS, `false` on Web.
|
||||
*/
|
||||
animationEnabled?: boolean;
|
||||
/**
|
||||
@@ -286,10 +287,12 @@ export type StackNavigationOptions = StackHeaderOptions &
|
||||
animationTypeForReplace?: 'push' | 'pop';
|
||||
/**
|
||||
* Whether you can use gestures to dismiss this screen. Defaults to `true` on iOS, `false` on Android.
|
||||
* Not supported on Web.
|
||||
*/
|
||||
gestureEnabled?: boolean;
|
||||
/**
|
||||
* Object to override the distance of touch start from the edge of the screen to recognize gestures.
|
||||
* Not supported on Web.
|
||||
*/
|
||||
gestureResponseDistance?: {
|
||||
/**
|
||||
@@ -302,8 +305,8 @@ export type StackNavigationOptions = StackHeaderOptions &
|
||||
horizontal?: number;
|
||||
};
|
||||
/**
|
||||
* Number which determines the relevance of velocity for the gesture.
|
||||
* Defaults to 0.3.
|
||||
* Number which determines the relevance of velocity for the gesture. Defaults to 0.3.
|
||||
* Not supported on Web.
|
||||
*/
|
||||
gestureVelocityImpact?: number;
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import { PanGestureHandler } from 'react-native-gesture-handler';
|
||||
|
||||
export default React.createContext<React.Ref<PanGestureHandler> | undefined>(
|
||||
undefined
|
||||
);
|
||||
export default React.createContext<React.Ref<
|
||||
import('react-native-gesture-handler').PanGestureHandler
|
||||
> | null>(null);
|
||||
|
||||
22
packages/stack/src/views/GestureHandler.native.tsx
Normal file
22
packages/stack/src/views/GestureHandler.native.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import * as React from 'react';
|
||||
import {
|
||||
PanGestureHandler as PanGestureHandlerNative,
|
||||
PanGestureHandlerProperties,
|
||||
} from 'react-native-gesture-handler';
|
||||
import GestureHandlerRefContext from '../utils/GestureHandlerRefContext';
|
||||
|
||||
export function PanGestureHandler(props: PanGestureHandlerProperties) {
|
||||
const gestureRef = React.useRef<PanGestureHandlerNative>(null);
|
||||
|
||||
return (
|
||||
<GestureHandlerRefContext.Provider value={gestureRef}>
|
||||
<PanGestureHandlerNative {...props} />
|
||||
</GestureHandlerRefContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export {
|
||||
GestureHandlerRootView,
|
||||
State as GestureState,
|
||||
PanGestureHandlerGestureEvent,
|
||||
} from 'react-native-gesture-handler';
|
||||
24
packages/stack/src/views/GestureHandler.tsx
Normal file
24
packages/stack/src/views/GestureHandler.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import * as React from 'react';
|
||||
import { View } from 'react-native';
|
||||
import type { PanGestureHandlerProperties } from 'react-native-gesture-handler';
|
||||
|
||||
const Dummy: any = ({ children }: { children: React.ReactNode }) => (
|
||||
<>{children}</>
|
||||
);
|
||||
|
||||
export const PanGestureHandler = Dummy as React.ComponentType<
|
||||
PanGestureHandlerProperties
|
||||
>;
|
||||
|
||||
export const GestureHandlerRootView = View;
|
||||
|
||||
export const GestureState = {
|
||||
UNDETERMINED: 0,
|
||||
FAILED: 1,
|
||||
BEGAN: 2,
|
||||
CANCELLED: 3,
|
||||
ACTIVE: 4,
|
||||
END: 5,
|
||||
};
|
||||
|
||||
export type { PanGestureHandlerGestureEvent } from 'react-native-gesture-handler';
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
} from 'react-native';
|
||||
import { useTheme } from '@react-navigation/native';
|
||||
import MaskedView from '../MaskedView';
|
||||
import TouchableItem from '../TouchableItem';
|
||||
import { TouchableItem } from '../TouchableItem';
|
||||
import { StackHeaderLeftButtonProps } from '../../types';
|
||||
|
||||
type Props = StackHeaderLeftButtonProps;
|
||||
|
||||
@@ -1,17 +1,26 @@
|
||||
import * as React from 'react';
|
||||
import { UIManager } from 'react-native';
|
||||
import RNCMaskedView from '@react-native-community/masked-view';
|
||||
|
||||
type Props = React.ComponentProps<typeof RNCMaskedView> & {
|
||||
type MaskedViewType = typeof import('@react-native-community/masked-view').default;
|
||||
|
||||
type Props = React.ComponentProps<MaskedViewType> & {
|
||||
children: React.ReactElement;
|
||||
};
|
||||
|
||||
let RNCMaskedView: MaskedViewType | undefined;
|
||||
|
||||
try {
|
||||
RNCMaskedView = require('@react-native-community/masked-view').default;
|
||||
} catch (e) {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
const isMaskedViewAvailable =
|
||||
// @ts-ignore
|
||||
UIManager.getViewManagerConfig('RNCMaskedView') != null;
|
||||
|
||||
export default function MaskedView({ children, ...rest }: Props) {
|
||||
if (isMaskedViewAvailable) {
|
||||
if (isMaskedViewAvailable && RNCMaskedView) {
|
||||
return <RNCMaskedView {...rest}>{children}</RNCMaskedView>;
|
||||
}
|
||||
|
||||
|
||||
71
packages/stack/src/views/Screens.tsx
Normal file
71
packages/stack/src/views/Screens.tsx
Normal file
@@ -0,0 +1,71 @@
|
||||
import * as React from 'react';
|
||||
import { Animated, View, Platform, ViewProps } from 'react-native';
|
||||
|
||||
let Screens: typeof import('react-native-screens') | undefined;
|
||||
|
||||
try {
|
||||
Screens = require('react-native-screens');
|
||||
} catch (e) {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
// The web implementation in react-native-screens seems buggy.
|
||||
// The view doesn't become visible after coming back in some cases.
|
||||
// So we use our custom implementation.
|
||||
class WebScreen extends React.Component<
|
||||
ViewProps & {
|
||||
active: number;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
> {
|
||||
render() {
|
||||
const { active, style, ...rest } = this.props;
|
||||
|
||||
return (
|
||||
<View
|
||||
// @ts-ignore
|
||||
hidden={!active}
|
||||
style={[style, { display: active ? 'flex' : 'none' }]}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const AnimatedWebScreen = Animated.createAnimatedComponent(WebScreen);
|
||||
|
||||
export const MaybeScreenContainer = ({
|
||||
enabled,
|
||||
...rest
|
||||
}: ViewProps & {
|
||||
enabled: boolean;
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
if (enabled && Platform.OS !== 'web' && Screens && Screens.screensEnabled()) {
|
||||
return <Screens.ScreenContainer {...rest} />;
|
||||
}
|
||||
|
||||
return <View {...rest} />;
|
||||
};
|
||||
|
||||
export const MaybeScreen = ({
|
||||
enabled,
|
||||
active,
|
||||
...rest
|
||||
}: ViewProps & {
|
||||
enabled: boolean;
|
||||
active: number | Animated.AnimatedInterpolation;
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
if (enabled && Platform.OS === 'web') {
|
||||
// @ts-ignore
|
||||
return <AnimatedWebScreen active={active} {...rest} />;
|
||||
}
|
||||
|
||||
if (enabled && Screens && Screens.screensEnabled()) {
|
||||
// @ts-ignore
|
||||
return <Screens.Screen active={active} {...rest} />;
|
||||
}
|
||||
|
||||
return <View {...rest} />;
|
||||
};
|
||||
@@ -9,14 +9,15 @@ import {
|
||||
Platform,
|
||||
InteractionManager,
|
||||
} from 'react-native';
|
||||
import {
|
||||
PanGestureHandler,
|
||||
State as GestureState,
|
||||
PanGestureHandlerGestureEvent,
|
||||
} from 'react-native-gesture-handler';
|
||||
import { EdgeInsets } from 'react-native-safe-area-context';
|
||||
import Color from 'color';
|
||||
import StackGestureRefContext from '../../utils/GestureHandlerRefContext';
|
||||
|
||||
import CardSheet from './CardSheet';
|
||||
import {
|
||||
PanGestureHandler,
|
||||
GestureState,
|
||||
PanGestureHandlerGestureEvent,
|
||||
} from '../GestureHandler';
|
||||
import CardAnimationContext from '../../utils/CardAnimationContext';
|
||||
import getDistanceForDirection from '../../utils/getDistanceForDirection';
|
||||
import getInvertedMultiplier from '../../utils/getInvertedMultiplier';
|
||||
@@ -36,6 +37,7 @@ type Props = ViewProps & {
|
||||
gesture: Animated.Value;
|
||||
layout: Layout;
|
||||
insets: EdgeInsets;
|
||||
pageOverflowEnabled: boolean;
|
||||
gestureDirection: GestureDirection;
|
||||
onOpen: () => void;
|
||||
onClose: () => void;
|
||||
@@ -286,7 +288,7 @@ export default class Card extends React.Component<Props> {
|
||||
getInvertedMultiplier(gestureDirection) >
|
||||
distance / 2
|
||||
? velocity !== 0 || translation !== 0
|
||||
: false;
|
||||
: this.props.closing;
|
||||
|
||||
this.animate({ closing, velocity });
|
||||
|
||||
@@ -412,8 +414,6 @@ export default class Card extends React.Component<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
private gestureRef = React.createRef<PanGestureHandler>();
|
||||
|
||||
private contentRef = React.createRef<View>();
|
||||
|
||||
render() {
|
||||
@@ -430,6 +430,7 @@ export default class Card extends React.Component<Props> {
|
||||
shadowEnabled,
|
||||
gestureEnabled,
|
||||
gestureDirection,
|
||||
pageOverflowEnabled,
|
||||
children,
|
||||
containerStyle: customContainerStyle,
|
||||
contentStyle,
|
||||
@@ -499,7 +500,6 @@ export default class Card extends React.Component<Props> {
|
||||
pointerEvents="box-none"
|
||||
>
|
||||
<PanGestureHandler
|
||||
ref={this.gestureRef}
|
||||
enabled={layout.width !== 0 && gestureEnabled}
|
||||
onGestureEvent={handleGestureEvent}
|
||||
onHandlerStateChange={this.handleGestureStateChange}
|
||||
@@ -523,14 +523,14 @@ export default class Card extends React.Component<Props> {
|
||||
pointerEvents="none"
|
||||
/>
|
||||
) : null}
|
||||
<View
|
||||
<CardSheet
|
||||
ref={this.contentRef}
|
||||
style={[styles.content, contentStyle]}
|
||||
enabled={pageOverflowEnabled}
|
||||
layout={layout}
|
||||
style={contentStyle}
|
||||
>
|
||||
<StackGestureRefContext.Provider value={this.gestureRef}>
|
||||
{children}
|
||||
</StackGestureRefContext.Provider>
|
||||
</View>
|
||||
{children}
|
||||
</CardSheet>
|
||||
</Animated.View>
|
||||
</PanGestureHandler>
|
||||
</Animated.View>
|
||||
@@ -544,10 +544,6 @@ const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
content: {
|
||||
flex: 1,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
overlay: {
|
||||
flex: 1,
|
||||
backgroundColor: '#000',
|
||||
|
||||
@@ -4,7 +4,13 @@ import { Route, useTheme } from '@react-navigation/native';
|
||||
import { Props as HeaderContainerProps } from '../Header/HeaderContainer';
|
||||
import Card from './Card';
|
||||
import HeaderHeightContext from '../../utils/HeaderHeightContext';
|
||||
import { Scene, Layout, StackHeaderMode, TransitionPreset } from '../../types';
|
||||
import {
|
||||
Scene,
|
||||
Layout,
|
||||
StackHeaderMode,
|
||||
StackCardMode,
|
||||
TransitionPreset,
|
||||
} from '../../types';
|
||||
|
||||
type Props = TransitionPreset & {
|
||||
index: number;
|
||||
@@ -45,6 +51,7 @@ type Props = TransitionPreset & {
|
||||
horizontal?: number;
|
||||
};
|
||||
gestureVelocityImpact?: number;
|
||||
mode: StackCardMode;
|
||||
headerMode: StackHeaderMode;
|
||||
headerShown?: boolean;
|
||||
headerTransparent?: boolean;
|
||||
@@ -73,6 +80,7 @@ function CardContainer({
|
||||
gestureVelocityImpact,
|
||||
getPreviousRoute,
|
||||
getFocusedRoute,
|
||||
mode,
|
||||
headerMode,
|
||||
headerShown,
|
||||
headerStyleInterpolator,
|
||||
@@ -178,6 +186,7 @@ function CardContainer({
|
||||
accessibilityElementsHidden={!focused}
|
||||
importantForAccessibility={focused ? 'auto' : 'no-hide-descendants'}
|
||||
pointerEvents={active ? 'box-none' : pointerEvents}
|
||||
pageOverflowEnabled={headerMode === 'screen' && mode === 'card'}
|
||||
containerStyle={
|
||||
headerMode === 'float' && !headerTransparent && headerShown !== false
|
||||
? { marginTop: headerHeight }
|
||||
|
||||
49
packages/stack/src/views/Stack/CardSheet.tsx
Normal file
49
packages/stack/src/views/Stack/CardSheet.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import * as React from 'react';
|
||||
import { View, ViewProps, StyleSheet } from 'react-native';
|
||||
|
||||
type Props = ViewProps & {
|
||||
enabled: boolean;
|
||||
layout: { width: number; height: number };
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
// This component will render a page which overflows the screen
|
||||
// if the container fills the body by comparing the size
|
||||
// This lets the document.body handle scrolling of the content
|
||||
// It's necessary for mobile browsers to be able to hide address bar on scroll
|
||||
export default React.forwardRef<View, Props>(function CardSheet(
|
||||
{ enabled, layout, style, ...rest },
|
||||
ref
|
||||
) {
|
||||
const [fill, setFill] = React.useState(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (typeof document === 'undefined' || !document.body) {
|
||||
// Only run when DOM is available
|
||||
return;
|
||||
}
|
||||
|
||||
const width = document.body.clientWidth;
|
||||
const height = document.body.clientHeight;
|
||||
|
||||
setFill(width === layout.width && height === layout.height);
|
||||
}, [layout.height, layout.width]);
|
||||
|
||||
return (
|
||||
<View
|
||||
{...rest}
|
||||
ref={ref}
|
||||
style={[enabled && fill ? styles.page : styles.card, style]}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
page: {
|
||||
minHeight: '100%',
|
||||
},
|
||||
card: {
|
||||
flex: 1,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
});
|
||||
@@ -1,18 +1,15 @@
|
||||
import * as React from 'react';
|
||||
import {
|
||||
Animated,
|
||||
View,
|
||||
StyleSheet,
|
||||
LayoutChangeEvent,
|
||||
Dimensions,
|
||||
Platform,
|
||||
ViewProps,
|
||||
} from 'react-native';
|
||||
import { EdgeInsets } from 'react-native-safe-area-context';
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
import { ScreenContainer, Screen, screensEnabled } from 'react-native-screens'; // Import with * as to prevent getters being called
|
||||
import { Route, StackNavigationState } from '@react-navigation/native';
|
||||
|
||||
import { MaybeScreenContainer, MaybeScreen } from '../Screens';
|
||||
import { getDefaultHeaderHeight } from '../Header/HeaderSegment';
|
||||
import { Props as HeaderContainerProps } from '../Header/HeaderContainer';
|
||||
import CardContainer from './CardContainer';
|
||||
@@ -75,67 +72,6 @@ type State = {
|
||||
|
||||
const EPSILON = 0.01;
|
||||
|
||||
// The web implementation in react-native-screens seems buggy.
|
||||
// The view doesn't become visible after coming back in some cases.
|
||||
// So we use our custom implementation.
|
||||
class WebScreen extends React.Component<
|
||||
ViewProps & {
|
||||
active: number;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
> {
|
||||
render() {
|
||||
const { active, style, ...rest } = this.props;
|
||||
|
||||
return (
|
||||
<View
|
||||
// @ts-ignore
|
||||
hidden={!active}
|
||||
style={[style, { display: active ? 'flex' : 'none' }]}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const AnimatedWebScreen = Animated.createAnimatedComponent(WebScreen);
|
||||
|
||||
const MaybeScreenContainer = ({
|
||||
enabled,
|
||||
...rest
|
||||
}: ViewProps & {
|
||||
enabled: boolean;
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
if (enabled && Platform.OS !== 'web' && screensEnabled()) {
|
||||
return <ScreenContainer {...rest} />;
|
||||
}
|
||||
|
||||
return <View {...rest} />;
|
||||
};
|
||||
|
||||
const MaybeScreen = ({
|
||||
enabled,
|
||||
active,
|
||||
...rest
|
||||
}: ViewProps & {
|
||||
enabled: boolean;
|
||||
active: number | Animated.AnimatedInterpolation;
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
if (enabled && Platform.OS === 'web') {
|
||||
// @ts-ignore
|
||||
return <AnimatedWebScreen active={active} {...rest} />;
|
||||
}
|
||||
|
||||
if (enabled && screensEnabled()) {
|
||||
// @ts-ignore
|
||||
return <Screen active={active} {...rest} />;
|
||||
}
|
||||
|
||||
return <View {...rest} />;
|
||||
};
|
||||
|
||||
const FALLBACK_DESCRIPTOR = Object.freeze({ options: {} });
|
||||
|
||||
const getHeaderHeights = (
|
||||
@@ -583,6 +519,7 @@ export default class CardStack extends React.Component<Props, State> {
|
||||
onHeaderHeightChange={this.handleHeaderLayout}
|
||||
getPreviousRoute={getPreviousRoute}
|
||||
getFocusedRoute={this.getFocusedRoute}
|
||||
mode={mode}
|
||||
headerMode={headerMode}
|
||||
headerShown={headerShown}
|
||||
headerTransparent={headerTransparent}
|
||||
@@ -628,7 +565,6 @@ export default class CardStack extends React.Component<Props, State> {
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
floating: {
|
||||
position: 'absolute',
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import * as React from 'react';
|
||||
import { View, Platform, StyleSheet } from 'react-native';
|
||||
import { SafeAreaConsumer, EdgeInsets } from 'react-native-safe-area-context';
|
||||
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
||||
import {
|
||||
NavigationHelpersContext,
|
||||
StackActions,
|
||||
StackNavigationState,
|
||||
Route,
|
||||
} from '@react-navigation/native';
|
||||
|
||||
import { GestureHandlerRootView } from '../GestureHandler';
|
||||
import CardStack from './CardStack';
|
||||
import KeyboardManager from '../KeyboardManager';
|
||||
import HeaderContainer, {
|
||||
@@ -147,7 +148,7 @@ export default class StackView extends React.Component<Props, State> {
|
||||
// We only need to animate routes if the focused route changed
|
||||
// Animating previous routes won't be visible coz the focused route is on top of everything
|
||||
|
||||
if (!previousRoutes.find((r) => r.key === nextFocusedRoute.key)) {
|
||||
if (!previousRoutes.some((r) => r.key === nextFocusedRoute.key)) {
|
||||
// A new route has come to the focus, we treat this as a push
|
||||
// A replace can also trigger this, the animation should look like push
|
||||
|
||||
@@ -166,7 +167,7 @@ export default class StackView extends React.Component<Props, State> {
|
||||
(key) => key !== nextFocusedRoute.key
|
||||
);
|
||||
|
||||
if (!routes.find((r) => r.key === previousFocusedRoute.key)) {
|
||||
if (!routes.some((r) => r.key === previousFocusedRoute.key)) {
|
||||
// The previous focused route isn't present in state, we treat this as a replace
|
||||
|
||||
openingRouteKeys = openingRouteKeys.filter(
|
||||
@@ -206,7 +207,7 @@ export default class StackView extends React.Component<Props, State> {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (!routes.find((r) => r.key === previousFocusedRoute.key)) {
|
||||
} else if (!routes.some((r) => r.key === previousFocusedRoute.key)) {
|
||||
// The previously focused route was removed, we treat this as a pop
|
||||
|
||||
if (
|
||||
@@ -328,24 +329,38 @@ export default class StackView extends React.Component<Props, State> {
|
||||
};
|
||||
|
||||
private handleOpenRoute = ({ route }: { route: Route<string> }) => {
|
||||
this.setState((state) => ({
|
||||
routes: state.replacingRouteKeys.length
|
||||
? state.routes.filter((r) => !state.replacingRouteKeys.includes(r.key))
|
||||
: state.routes,
|
||||
openingRouteKeys: state.openingRouteKeys.filter(
|
||||
(key) => key !== route.key
|
||||
),
|
||||
closingRouteKeys: state.closingRouteKeys.filter(
|
||||
(key) => key !== route.key
|
||||
),
|
||||
replacingRouteKeys: [],
|
||||
}));
|
||||
const { state, navigation } = this.props;
|
||||
|
||||
if (
|
||||
this.state.replacingRouteKeys.every((key) => key !== route.key) &&
|
||||
state.routeNames.includes(route.name) &&
|
||||
!state.routes.some((r) => r.key === route.key)
|
||||
) {
|
||||
// If route isn't present in current state, assume that a close animation was cancelled
|
||||
// So we need to add this route back to the state
|
||||
navigation.navigate(route);
|
||||
} else {
|
||||
this.setState((state) => ({
|
||||
routes: state.replacingRouteKeys.length
|
||||
? state.routes.filter(
|
||||
(r) => !state.replacingRouteKeys.includes(r.key)
|
||||
)
|
||||
: state.routes,
|
||||
openingRouteKeys: state.openingRouteKeys.filter(
|
||||
(key) => key !== route.key
|
||||
),
|
||||
closingRouteKeys: state.closingRouteKeys.filter(
|
||||
(key) => key !== route.key
|
||||
),
|
||||
replacingRouteKeys: [],
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
private handleCloseRoute = ({ route }: { route: Route<string> }) => {
|
||||
const { state, navigation } = this.props;
|
||||
|
||||
if (state.routes.find((r) => r.key === route.key)) {
|
||||
if (state.routes.some((r) => r.key === route.key)) {
|
||||
// If a route exists in state, trigger a pop
|
||||
// This will happen in when the route was closed from the card component
|
||||
// e.g. When the close animation triggered from a gesture ends
|
||||
@@ -391,7 +406,6 @@ export default class StackView extends React.Component<Props, State> {
|
||||
render() {
|
||||
const {
|
||||
state,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
navigation,
|
||||
keyboardHandlingEnabled,
|
||||
mode = 'card',
|
||||
@@ -406,41 +420,43 @@ export default class StackView extends React.Component<Props, State> {
|
||||
} = this.state;
|
||||
|
||||
const headerMode =
|
||||
mode !== 'modal' && Platform.OS === 'ios' ? 'float' : 'screen';
|
||||
mode === 'card' && Platform.OS === 'ios' ? 'float' : 'screen';
|
||||
|
||||
return (
|
||||
<GestureHandlerWrapper style={styles.container}>
|
||||
<SafeAreaProviderCompat>
|
||||
<SafeAreaConsumer>
|
||||
{(insets) => (
|
||||
<KeyboardManager enabled={keyboardHandlingEnabled !== false}>
|
||||
{(props) => (
|
||||
<CardStack
|
||||
mode={mode}
|
||||
insets={insets as EdgeInsets}
|
||||
getPreviousRoute={this.getPreviousRoute}
|
||||
getGesturesEnabled={this.getGesturesEnabled}
|
||||
routes={routes}
|
||||
openingRouteKeys={openingRouteKeys}
|
||||
closingRouteKeys={closingRouteKeys}
|
||||
onOpenRoute={this.handleOpenRoute}
|
||||
onCloseRoute={this.handleCloseRoute}
|
||||
onTransitionStart={this.handleTransitionStart}
|
||||
onTransitionEnd={this.handleTransitionEnd}
|
||||
renderHeader={this.renderHeader}
|
||||
renderScene={this.renderScene}
|
||||
headerMode={headerMode}
|
||||
state={state}
|
||||
descriptors={descriptors}
|
||||
{...rest}
|
||||
{...props}
|
||||
/>
|
||||
)}
|
||||
</KeyboardManager>
|
||||
)}
|
||||
</SafeAreaConsumer>
|
||||
</SafeAreaProviderCompat>
|
||||
</GestureHandlerWrapper>
|
||||
<NavigationHelpersContext.Provider value={navigation}>
|
||||
<GestureHandlerWrapper style={styles.container}>
|
||||
<SafeAreaProviderCompat>
|
||||
<SafeAreaConsumer>
|
||||
{(insets) => (
|
||||
<KeyboardManager enabled={keyboardHandlingEnabled !== false}>
|
||||
{(props) => (
|
||||
<CardStack
|
||||
mode={mode}
|
||||
insets={insets as EdgeInsets}
|
||||
getPreviousRoute={this.getPreviousRoute}
|
||||
getGesturesEnabled={this.getGesturesEnabled}
|
||||
routes={routes}
|
||||
openingRouteKeys={openingRouteKeys}
|
||||
closingRouteKeys={closingRouteKeys}
|
||||
onOpenRoute={this.handleOpenRoute}
|
||||
onCloseRoute={this.handleCloseRoute}
|
||||
onTransitionStart={this.handleTransitionStart}
|
||||
onTransitionEnd={this.handleTransitionEnd}
|
||||
renderHeader={this.renderHeader}
|
||||
renderScene={this.renderScene}
|
||||
headerMode={headerMode}
|
||||
state={state}
|
||||
descriptors={descriptors}
|
||||
{...rest}
|
||||
{...props}
|
||||
/>
|
||||
)}
|
||||
</KeyboardManager>
|
||||
)}
|
||||
</SafeAreaConsumer>
|
||||
</SafeAreaProviderCompat>
|
||||
</GestureHandlerWrapper>
|
||||
</NavigationHelpersContext.Provider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
81
packages/stack/src/views/TouchableItem.native.tsx
Normal file
81
packages/stack/src/views/TouchableItem.native.tsx
Normal file
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* TouchableItem renders a touchable that looks native on both iOS and Android.
|
||||
*
|
||||
* It provides an abstraction on top of TouchableNativeFeedback and
|
||||
* TouchableOpacity.
|
||||
*
|
||||
* On iOS you can pass the props of TouchableOpacity, on Android pass the props
|
||||
* of TouchableNativeFeedback.
|
||||
*/
|
||||
import * as React from 'react';
|
||||
import {
|
||||
Platform,
|
||||
TouchableNativeFeedback,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
ViewProps,
|
||||
} from 'react-native';
|
||||
|
||||
import BorderlessButton from './BorderlessButton';
|
||||
|
||||
export type Props = ViewProps & {
|
||||
pressColor: string;
|
||||
disabled?: boolean;
|
||||
borderless?: boolean;
|
||||
delayPressIn?: number;
|
||||
onPress?: () => void;
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
const ANDROID_VERSION_LOLLIPOP = 21;
|
||||
|
||||
export class TouchableItem extends React.Component<Props> {
|
||||
static defaultProps = {
|
||||
borderless: false,
|
||||
pressColor: 'rgba(0, 0, 0, .32)',
|
||||
};
|
||||
|
||||
render() {
|
||||
/*
|
||||
* TouchableNativeFeedback.Ripple causes a crash on old Android versions,
|
||||
* therefore only enable it on Android Lollipop and above.
|
||||
*
|
||||
* All touchables on Android should have the ripple effect according to
|
||||
* platform design guidelines.
|
||||
* We need to pass the background prop to specify a borderless ripple effect.
|
||||
*/
|
||||
if (
|
||||
Platform.OS === 'android' &&
|
||||
Platform.Version >= ANDROID_VERSION_LOLLIPOP
|
||||
) {
|
||||
const { style, pressColor, borderless, children, ...rest } = this.props;
|
||||
|
||||
return (
|
||||
<TouchableNativeFeedback
|
||||
{...rest}
|
||||
useForeground={TouchableNativeFeedback.canUseNativeForeground()}
|
||||
background={TouchableNativeFeedback.Ripple(pressColor, borderless)}
|
||||
>
|
||||
<View style={style}>{React.Children.only(children)}</View>
|
||||
</TouchableNativeFeedback>
|
||||
);
|
||||
} else if (Platform.OS === 'ios') {
|
||||
return (
|
||||
<BorderlessButton
|
||||
hitSlop={{ top: 10, bottom: 10, right: 10, left: 10 }}
|
||||
disallowInterruption
|
||||
enabled={!this.props.disabled}
|
||||
{...this.props}
|
||||
>
|
||||
{this.props.children}
|
||||
</BorderlessButton>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<TouchableOpacity {...this.props}>
|
||||
{this.props.children}
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,80 +1,3 @@
|
||||
/**
|
||||
* TouchableItem renders a touchable that looks native on both iOS and Android.
|
||||
*
|
||||
* It provides an abstraction on top of TouchableNativeFeedback and
|
||||
* TouchableOpacity.
|
||||
*
|
||||
* On iOS you can pass the props of TouchableOpacity, on Android pass the props
|
||||
* of TouchableNativeFeedback.
|
||||
*/
|
||||
import * as React from 'react';
|
||||
import {
|
||||
Platform,
|
||||
TouchableNativeFeedback,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
ViewProps,
|
||||
} from 'react-native';
|
||||
import { TouchableOpacity } from 'react-native';
|
||||
|
||||
import BorderlessButton from './BorderlessButton';
|
||||
|
||||
type Props = ViewProps & {
|
||||
pressColor: string;
|
||||
disabled?: boolean;
|
||||
borderless?: boolean;
|
||||
delayPressIn?: number;
|
||||
onPress?: () => void;
|
||||
};
|
||||
|
||||
const ANDROID_VERSION_LOLLIPOP = 21;
|
||||
|
||||
export default class TouchableItem extends React.Component<Props> {
|
||||
static defaultProps = {
|
||||
borderless: false,
|
||||
pressColor: 'rgba(0, 0, 0, .32)',
|
||||
};
|
||||
|
||||
render() {
|
||||
/*
|
||||
* TouchableNativeFeedback.Ripple causes a crash on old Android versions,
|
||||
* therefore only enable it on Android Lollipop and above.
|
||||
*
|
||||
* All touchables on Android should have the ripple effect according to
|
||||
* platform design guidelines.
|
||||
* We need to pass the background prop to specify a borderless ripple effect.
|
||||
*/
|
||||
if (
|
||||
Platform.OS === 'android' &&
|
||||
Platform.Version >= ANDROID_VERSION_LOLLIPOP
|
||||
) {
|
||||
const { style, pressColor, borderless, children, ...rest } = this.props;
|
||||
|
||||
return (
|
||||
<TouchableNativeFeedback
|
||||
{...rest}
|
||||
useForeground={TouchableNativeFeedback.canUseNativeForeground()}
|
||||
background={TouchableNativeFeedback.Ripple(pressColor, borderless)}
|
||||
>
|
||||
<View style={style}>{React.Children.only(children)}</View>
|
||||
</TouchableNativeFeedback>
|
||||
);
|
||||
} else if (Platform.OS === 'ios') {
|
||||
return (
|
||||
<BorderlessButton
|
||||
hitSlop={{ top: 10, bottom: 10, right: 10, left: 10 }}
|
||||
disallowInterruption
|
||||
enabled={!this.props.disabled}
|
||||
{...this.props}
|
||||
>
|
||||
{this.props.children}
|
||||
</BorderlessButton>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<TouchableOpacity {...this.props}>
|
||||
{this.props.children}
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
export const TouchableItem = (TouchableOpacity as any) as typeof import('./TouchableItem.native').TouchableItem;
|
||||
|
||||
439
yarn.lock
439
yarn.lock
@@ -3397,6 +3397,14 @@
|
||||
"@types/istanbul-lib-coverage" "*"
|
||||
"@types/istanbul-lib-report" "*"
|
||||
|
||||
"@types/jest-dev-server@^4.2.0":
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/jest-dev-server/-/jest-dev-server-4.2.0.tgz#72026a5c8c30e01a37df9b94af3ee7e886e5d132"
|
||||
integrity sha512-3GYNmVxcLIdW892W1uuhjuBIcDpP7GqQd/YFfNNBKPhA6yaOXnP29QKUwAM8thZ516/f9VJ4m4gAl/o5hxnqNg==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
"@types/wait-on" "*"
|
||||
|
||||
"@types/jest@^25.2.1":
|
||||
version "25.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/jest/-/jest-25.2.1.tgz#9544cd438607955381c1bdbdb97767a249297db5"
|
||||
@@ -3579,6 +3587,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/use-subscription/-/use-subscription-1.0.0.tgz#d146f8d834f70f50d48bd8246a481d096f11db19"
|
||||
integrity sha512-0WWZ5GUDKMXUY/1zy4Ur5/zsC0s/B+JjXfHdkvx6JgDNZzZV5eW+KKhDqsTGyqX56uh99gwGwbsKbVwkcVIKQA==
|
||||
|
||||
"@types/wait-on@*":
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/wait-on/-/wait-on-4.0.0.tgz#fb6fa2854b592f7344f1dd9836b5655795510dce"
|
||||
integrity sha512-Cj2jcMOzrdvWMP+Vl+qlz942eQfJk96S9kRnB1ejVMl+w9/9mUn0+pF4J+v0Iv+6zCrmBBODZAXKsRo6Y91Cfw==
|
||||
|
||||
"@types/webpack-sources@*":
|
||||
version "0.1.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/webpack-sources/-/webpack-sources-0.1.7.tgz#0a330a9456113410c74a5d64180af0cbca007141"
|
||||
@@ -3624,6 +3637,13 @@
|
||||
dependencies:
|
||||
"@types/yargs-parser" "*"
|
||||
|
||||
"@types/yauzl@^2.9.1":
|
||||
version "2.9.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.9.1.tgz#d10f69f9f522eef3cf98e30afb684a1e1ec923af"
|
||||
integrity sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@typescript-eslint/eslint-plugin@^2.12.0":
|
||||
version "2.27.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.27.0.tgz#e479cdc4c9cf46f96b4c287755733311b0d0ba4b"
|
||||
@@ -3855,6 +3875,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d"
|
||||
integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==
|
||||
|
||||
"@zeit/schemas@2.6.0":
|
||||
version "2.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@zeit/schemas/-/schemas-2.6.0.tgz#004e8e553b4cd53d538bd38eac7bcbf58a867fe3"
|
||||
integrity sha512-uUrgZ8AxS+Lio0fZKAipJjAh415JyrOZowliZAzmnJSsf7piVL5w+G0+gFJ0KSu3QRhvui/7zuvpLz03YjXAhg==
|
||||
|
||||
"@zkochan/cmd-shim@^3.1.0":
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@zkochan/cmd-shim/-/cmd-shim-3.1.0.tgz#2ab8ed81f5bb5452a85f25758eb9b8681982fd2e"
|
||||
@@ -4000,6 +4025,16 @@ ajv-keywords@^3.1.0, ajv-keywords@^3.4.1:
|
||||
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.4.1.tgz#ef916e271c64ac12171fd8384eaae6b2345854da"
|
||||
integrity sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==
|
||||
|
||||
ajv@6.5.3:
|
||||
version "6.5.3"
|
||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.5.3.tgz#71a569d189ecf4f4f321224fecb166f071dd90f9"
|
||||
integrity sha512-LqZ9wY+fx3UMiiPd741yB2pj3hhil+hQc8taf4o2QGRFpWgZ2V5C8HA165DY9sS3fJwsk7uT7ZlFEyC3Ig3lLg==
|
||||
dependencies:
|
||||
fast-deep-equal "^2.0.1"
|
||||
fast-json-stable-stringify "^2.0.0"
|
||||
json-schema-traverse "^0.4.1"
|
||||
uri-js "^4.2.2"
|
||||
|
||||
ajv@^5.2.2:
|
||||
version "5.5.2"
|
||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965"
|
||||
@@ -4040,6 +4075,13 @@ analytics-node@3.3.0:
|
||||
remove-trailing-slash "^0.1.0"
|
||||
uuid "^3.2.1"
|
||||
|
||||
ansi-align@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-2.0.0.tgz#c36aeccba563b89ceb556f3690f0b1d9e3547f7f"
|
||||
integrity sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=
|
||||
dependencies:
|
||||
string-width "^2.0.0"
|
||||
|
||||
ansi-align@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.0.tgz#b536b371cf687caaef236c18d3e21fe3797467cb"
|
||||
@@ -4211,6 +4253,11 @@ aproba@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc"
|
||||
integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==
|
||||
|
||||
arch@^2.1.0:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/arch/-/arch-2.1.1.tgz#8f5c2731aa35a30929221bb0640eed65175ec84e"
|
||||
integrity sha512-BLM56aPo9vLLFVa8+/+pJLnrZ7QGGTVHWsCwieAWT9o9K8UeGaQbzZbGoabWLOo2ksBCztoXdqBZBplqLDDCSg==
|
||||
|
||||
are-we-there-yet@~1.1.2:
|
||||
version "1.1.5"
|
||||
resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21"
|
||||
@@ -4219,6 +4266,11 @@ are-we-there-yet@~1.1.2:
|
||||
delegates "^1.0.0"
|
||||
readable-stream "^2.0.6"
|
||||
|
||||
arg@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/arg/-/arg-2.0.0.tgz#c06e7ff69ab05b3a4a03ebe0407fac4cba657545"
|
||||
integrity sha512-XxNTUzKnz1ctK3ZIcI2XUPlD96wbHP2nGqkPKpvk/HNRlPveYrXIVSTk9m3LcqOgDPg3B1nMvdV/K8wZd7PG4w==
|
||||
|
||||
argparse@^1.0.7:
|
||||
version "1.0.10"
|
||||
resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
|
||||
@@ -4844,6 +4896,19 @@ boolbase@^1.0.0, boolbase@~1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
|
||||
integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24=
|
||||
|
||||
boxen@1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/boxen/-/boxen-1.3.0.tgz#55c6c39a8ba58d9c61ad22cd877532deb665a20b"
|
||||
integrity sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw==
|
||||
dependencies:
|
||||
ansi-align "^2.0.0"
|
||||
camelcase "^4.0.0"
|
||||
chalk "^2.0.1"
|
||||
cli-boxes "^1.0.0"
|
||||
string-width "^2.0.0"
|
||||
term-size "^1.2.0"
|
||||
widest-line "^2.0.0"
|
||||
|
||||
boxen@4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/boxen/-/boxen-4.1.0.tgz#256f6b2eb09ba22ea558e5acc0a5ff637bf8ed03"
|
||||
@@ -5023,7 +5088,7 @@ buffer-alloc@^1.1.0, buffer-alloc@^1.2.0:
|
||||
buffer-alloc-unsafe "^1.1.0"
|
||||
buffer-fill "^1.0.0"
|
||||
|
||||
buffer-crc32@^0.2.13:
|
||||
buffer-crc32@^0.2.13, buffer-crc32@~0.2.3:
|
||||
version "0.2.13"
|
||||
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
|
||||
integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=
|
||||
@@ -5266,7 +5331,7 @@ camelcase@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f"
|
||||
integrity sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=
|
||||
|
||||
camelcase@^4.1.0:
|
||||
camelcase@^4.0.0, camelcase@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd"
|
||||
integrity sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=
|
||||
@@ -5439,6 +5504,11 @@ clean-webpack-plugin@^3.0.0:
|
||||
"@types/webpack" "^4.4.31"
|
||||
del "^4.1.1"
|
||||
|
||||
cli-boxes@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-1.0.0.tgz#4fa917c3e59c94a004cd61f8ee509da651687143"
|
||||
integrity sha1-T6kXw+WclKAEzWH47lCdplFocUM=
|
||||
|
||||
cli-boxes@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.0.tgz#538ecae8f9c6ca508e3c3c95b453fe93cb4c168d"
|
||||
@@ -5475,6 +5545,14 @@ cli-width@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639"
|
||||
integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=
|
||||
|
||||
clipboardy@1.2.3:
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/clipboardy/-/clipboardy-1.2.3.tgz#0526361bf78724c1f20be248d428e365433c07ef"
|
||||
integrity sha512-2WNImOvCRe6r63Gk9pShfkwXsVtKCroMAevIbiae021mS850UkWPbevxsBz3tnvjZIEGvlwaqCPsw+4ulzNgJA==
|
||||
dependencies:
|
||||
arch "^2.1.0"
|
||||
execa "^0.8.0"
|
||||
|
||||
cliui@^3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d"
|
||||
@@ -5667,11 +5745,16 @@ commander@2.20.0:
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422"
|
||||
integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==
|
||||
|
||||
commander@^2.19.0, commander@^2.20.0, commander@^2.9.0, commander@~2.20.3:
|
||||
commander@^2.11.0, commander@^2.19.0, commander@^2.20.0, commander@^2.9.0, commander@~2.20.3:
|
||||
version "2.20.3"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
||||
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
|
||||
|
||||
commander@^3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-3.0.2.tgz#6837c3fb677ad9933d1cfba42dd14d5117d6b39e"
|
||||
integrity sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==
|
||||
|
||||
commander@~2.13.0:
|
||||
version "2.13.0"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c"
|
||||
@@ -5724,13 +5807,26 @@ component-type@^1.2.1:
|
||||
resolved "https://registry.yarnpkg.com/component-type/-/component-type-1.2.1.tgz#8a47901700238e4fc32269771230226f24b415a9"
|
||||
integrity sha1-ikeQFwAjjk/DIml3EjAibyS0Fak=
|
||||
|
||||
compressible@~2.0.16:
|
||||
compressible@~2.0.14, compressible@~2.0.16:
|
||||
version "2.0.18"
|
||||
resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba"
|
||||
integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==
|
||||
dependencies:
|
||||
mime-db ">= 1.43.0 < 2"
|
||||
|
||||
compression@1.7.3:
|
||||
version "1.7.3"
|
||||
resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.3.tgz#27e0e176aaf260f7f2c2813c3e440adb9f1993db"
|
||||
integrity sha512-HSjyBG5N1Nnz7tF2+O7A9XUhyjru71/fwgNb7oIsEVHR0WShfs2tIS/EySLgiTe98aOK18YDlMXpzjCXY/n9mg==
|
||||
dependencies:
|
||||
accepts "~1.3.5"
|
||||
bytes "3.0.0"
|
||||
compressible "~2.0.14"
|
||||
debug "2.6.9"
|
||||
on-headers "~1.0.1"
|
||||
safe-buffer "5.1.2"
|
||||
vary "~1.1.2"
|
||||
|
||||
compression@^1.5.2, compression@^1.7.1:
|
||||
version "1.7.4"
|
||||
resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f"
|
||||
@@ -6019,7 +6115,7 @@ core-js@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636"
|
||||
integrity sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=
|
||||
|
||||
core-js@^2.2.2, core-js@^2.4.0, core-js@^2.4.1, core-js@^2.5.0:
|
||||
core-js@^2.2.2, core-js@^2.4.0, core-js@^2.4.1, core-js@^2.5.0, core-js@^2.6.5:
|
||||
version "2.6.11"
|
||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c"
|
||||
integrity sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==
|
||||
@@ -6356,6 +6452,14 @@ currently-unhandled@^0.4.1:
|
||||
dependencies:
|
||||
array-find-index "^1.0.1"
|
||||
|
||||
cwd@^0.10.0:
|
||||
version "0.10.0"
|
||||
resolved "https://registry.yarnpkg.com/cwd/-/cwd-0.10.0.tgz#172400694057c22a13b0cf16162c7e4b7a7fe567"
|
||||
integrity sha1-FyQAaUBXwioTsM8WFix+S3p/5Wc=
|
||||
dependencies:
|
||||
find-pkg "^0.1.2"
|
||||
fs-exists-sync "^0.1.0"
|
||||
|
||||
cyclist@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9"
|
||||
@@ -6399,7 +6503,7 @@ debounce@^1.2.0:
|
||||
resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.0.tgz#44a540abc0ea9943018dc0eaa95cce87f65cd131"
|
||||
integrity sha512-mYtLl1xfZLi1m4RtQYlZgJUNQjl4ZxVnHzIR8nLLgi4q1YT8o/WM+MK/f8yfcc9s5Ir5zRaPZyZU6xs1Syoocg==
|
||||
|
||||
debug@2, debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9:
|
||||
debug@2, debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.8, debug@^2.6.9:
|
||||
version "2.6.9"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
||||
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
|
||||
@@ -7530,6 +7634,19 @@ execa@^0.7.0:
|
||||
signal-exit "^3.0.0"
|
||||
strip-eof "^1.0.0"
|
||||
|
||||
execa@^0.8.0:
|
||||
version "0.8.0"
|
||||
resolved "https://registry.yarnpkg.com/execa/-/execa-0.8.0.tgz#d8d76bbc1b55217ed190fd6dd49d3c774ecfc8da"
|
||||
integrity sha1-2NdrvBtVIX7RkP1t1J08d07PyNo=
|
||||
dependencies:
|
||||
cross-spawn "^5.0.1"
|
||||
get-stream "^3.0.0"
|
||||
is-stream "^1.1.0"
|
||||
npm-run-path "^2.0.0"
|
||||
p-finally "^1.0.0"
|
||||
signal-exit "^3.0.0"
|
||||
strip-eof "^1.0.0"
|
||||
|
||||
execa@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8"
|
||||
@@ -7587,6 +7704,13 @@ expand-brackets@^2.1.4:
|
||||
snapdragon "^0.8.1"
|
||||
to-regex "^3.0.1"
|
||||
|
||||
expand-tilde@^1.2.2:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-1.2.2.tgz#0b81eba897e5a3d31d1c3d102f8f01441e559449"
|
||||
integrity sha1-C4HrqJflo9MdHD0QL48BRB5VlEk=
|
||||
dependencies:
|
||||
os-homedir "^1.0.1"
|
||||
|
||||
expect@^25.2.7:
|
||||
version "25.2.7"
|
||||
resolved "https://registry.yarnpkg.com/expect/-/expect-25.2.7.tgz#509b79f47502835f4071ff3ecc401f2eaecca709"
|
||||
@@ -7946,6 +8070,17 @@ extglob@^2.0.4:
|
||||
snapdragon "^0.8.1"
|
||||
to-regex "^3.0.1"
|
||||
|
||||
extract-zip@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.0.tgz#f53b71d44f4ff5a4527a2259ade000fb8b303492"
|
||||
integrity sha512-i42GQ498yibjdvIhivUsRslx608whtGoFIhF26Z7O4MYncBxp8CwalOs1lnHy21A9sIohWO2+uiE4SRtC9JXDg==
|
||||
dependencies:
|
||||
debug "^4.1.1"
|
||||
get-stream "^5.1.0"
|
||||
yauzl "^2.10.0"
|
||||
optionalDependencies:
|
||||
"@types/yauzl" "^2.9.1"
|
||||
|
||||
extsprintf@1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
|
||||
@@ -7971,6 +8106,11 @@ fast-deep-equal@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz#c053477817c86b51daa853c81e059b733d023614"
|
||||
integrity sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=
|
||||
|
||||
fast-deep-equal@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49"
|
||||
integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=
|
||||
|
||||
fast-deep-equal@^3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz#545145077c501491e33b15ec408c294376e94ae4"
|
||||
@@ -8015,6 +8155,13 @@ fast-levenshtein@~2.0.6:
|
||||
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
|
||||
integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
|
||||
|
||||
fast-url-parser@1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/fast-url-parser/-/fast-url-parser-1.1.3.tgz#f4af3ea9f34d8a271cf58ad2b3759f431f0b318d"
|
||||
integrity sha1-9K8+qfNNiicc9YrSs3WfQx8LMY0=
|
||||
dependencies:
|
||||
punycode "^1.3.2"
|
||||
|
||||
fastparse@^1.1.1:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.2.tgz#91728c5a5942eced8531283c79441ee4122c35a9"
|
||||
@@ -8103,6 +8250,13 @@ fbjs@^1.0.0:
|
||||
setimmediate "^1.0.5"
|
||||
ua-parser-js "^0.7.18"
|
||||
|
||||
fd-slicer@~1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e"
|
||||
integrity sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=
|
||||
dependencies:
|
||||
pend "~1.2.0"
|
||||
|
||||
figgy-pudding@^3.4.1, figgy-pudding@^3.5.1:
|
||||
version "3.5.2"
|
||||
resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e"
|
||||
@@ -8212,6 +8366,30 @@ find-cache-dir@^2.0.0, find-cache-dir@^2.1.0:
|
||||
make-dir "^2.0.0"
|
||||
pkg-dir "^3.0.0"
|
||||
|
||||
find-file-up@^0.1.2:
|
||||
version "0.1.3"
|
||||
resolved "https://registry.yarnpkg.com/find-file-up/-/find-file-up-0.1.3.tgz#cf68091bcf9f300a40da411b37da5cce5a2fbea0"
|
||||
integrity sha1-z2gJG8+fMApA2kEbN9pczlovvqA=
|
||||
dependencies:
|
||||
fs-exists-sync "^0.1.0"
|
||||
resolve-dir "^0.1.0"
|
||||
|
||||
find-pkg@^0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/find-pkg/-/find-pkg-0.1.2.tgz#1bdc22c06e36365532e2a248046854b9788da557"
|
||||
integrity sha1-G9wiwG42NlUy4qJIBGhUuXiNpVc=
|
||||
dependencies:
|
||||
find-file-up "^0.1.2"
|
||||
|
||||
find-process@^1.4.3:
|
||||
version "1.4.3"
|
||||
resolved "https://registry.yarnpkg.com/find-process/-/find-process-1.4.3.tgz#25f9105dc32e42abad4636752c37c51cd57dce45"
|
||||
integrity sha512-+IA+AUsQCf3uucawyTwMWcY+2M3FXq3BRvw3S+j5Jvydjk31f/+NPWpYZOJs+JUs2GvxH4Yfr6Wham0ZtRLlPA==
|
||||
dependencies:
|
||||
chalk "^2.0.1"
|
||||
commander "^2.11.0"
|
||||
debug "^2.6.8"
|
||||
|
||||
find-up@3.0.0, find-up@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73"
|
||||
@@ -8380,6 +8558,11 @@ fs-constants@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
|
||||
integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
|
||||
|
||||
fs-exists-sync@^0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz#982d6893af918e72d08dec9e8673ff2b5a8d6add"
|
||||
integrity sha1-mC1ok6+RjnLQjeyehnP/K1qNat0=
|
||||
|
||||
fs-extra@6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-6.0.1.tgz#8abc128f7946e310135ddc93b98bddb410e7a34b"
|
||||
@@ -8748,6 +8931,24 @@ global-modules@2.0.0:
|
||||
dependencies:
|
||||
global-prefix "^3.0.0"
|
||||
|
||||
global-modules@^0.2.3:
|
||||
version "0.2.3"
|
||||
resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-0.2.3.tgz#ea5a3bed42c6d6ce995a4f8a1269b5dae223828d"
|
||||
integrity sha1-6lo77ULG1s6ZWk+KEmm12uIjgo0=
|
||||
dependencies:
|
||||
global-prefix "^0.1.4"
|
||||
is-windows "^0.2.0"
|
||||
|
||||
global-prefix@^0.1.4:
|
||||
version "0.1.5"
|
||||
resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-0.1.5.tgz#8d3bc6b8da3ca8112a160d8d496ff0462bfef78f"
|
||||
integrity sha1-jTvGuNo8qBEqFg2NSW/wRiv+948=
|
||||
dependencies:
|
||||
homedir-polyfill "^1.0.0"
|
||||
ini "^1.3.4"
|
||||
is-windows "^0.2.0"
|
||||
which "^1.2.12"
|
||||
|
||||
global-prefix@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-3.0.0.tgz#fc85f73064df69f50421f47f883fe5b913ba9b97"
|
||||
@@ -9085,6 +9286,13 @@ hoist-non-react-statics@^3.3.0:
|
||||
dependencies:
|
||||
react-is "^16.7.0"
|
||||
|
||||
homedir-polyfill@^1.0.0:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8"
|
||||
integrity sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==
|
||||
dependencies:
|
||||
parse-passwd "^1.0.0"
|
||||
|
||||
hosted-git-info@^2.1.4, hosted-git-info@^2.6.0, hosted-git-info@^2.7.1:
|
||||
version "2.8.8"
|
||||
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488"
|
||||
@@ -9298,6 +9506,14 @@ https-proxy-agent@^2.2.3:
|
||||
agent-base "^4.3.0"
|
||||
debug "^3.1.0"
|
||||
|
||||
https-proxy-agent@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-3.0.1.tgz#b8c286433e87602311b01c8ea34413d856a4af81"
|
||||
integrity sha512-+ML2Rbh6DAuee7d07tYGEKOEi2voWPUGan+ExdPbPW6Z3svq+JCqr0v8WmKPOkz1vOVykPCBSuobe7G8GJUtVg==
|
||||
dependencies:
|
||||
agent-base "^4.3.0"
|
||||
debug "^3.1.0"
|
||||
|
||||
https-proxy-agent@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz#702b71fb5520a132a66de1f67541d9e62154d82b"
|
||||
@@ -10063,6 +10279,11 @@ is-utf8@^0.2.0:
|
||||
resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72"
|
||||
integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=
|
||||
|
||||
is-windows@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-0.2.0.tgz#de1aa6d63ea29dd248737b69f1ff8b8002d2108c"
|
||||
integrity sha1-3hqm1j6indJIc3tp8f+LgALSEIw=
|
||||
|
||||
is-windows@^1.0.0, is-windows@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
|
||||
@@ -10231,6 +10452,19 @@ jest-config@^25.2.7:
|
||||
pretty-format "^25.2.6"
|
||||
realpath-native "^2.0.0"
|
||||
|
||||
jest-dev-server@^4.4.0:
|
||||
version "4.4.0"
|
||||
resolved "https://registry.yarnpkg.com/jest-dev-server/-/jest-dev-server-4.4.0.tgz#557113faae2877452162696aa94c1e44491ab011"
|
||||
integrity sha512-STEHJ3iPSC8HbrQ3TME0ozGX2KT28lbT4XopPxUm2WimsX3fcB3YOptRh12YphQisMhfqNSNTZUmWyT3HEXS2A==
|
||||
dependencies:
|
||||
chalk "^3.0.0"
|
||||
cwd "^0.10.0"
|
||||
find-process "^1.4.3"
|
||||
prompts "^2.3.0"
|
||||
spawnd "^4.4.0"
|
||||
tree-kill "^1.2.2"
|
||||
wait-on "^3.3.0"
|
||||
|
||||
jest-diff@^25.2.1, jest-diff@^25.2.6:
|
||||
version "25.2.6"
|
||||
resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-25.2.6.tgz#a6d70a9ab74507715ea1092ac513d1ab81c1b5e7"
|
||||
@@ -10659,7 +10893,7 @@ join-component@^1.1.0:
|
||||
resolved "https://registry.yarnpkg.com/join-component/-/join-component-1.1.0.tgz#b8417b750661a392bee2c2537c68b2a9d4977cd5"
|
||||
integrity sha1-uEF7dQZho5K+4sJTfGiyqdSXfNU=
|
||||
|
||||
jpeg-js@^0.3.4:
|
||||
jpeg-js@^0.3.4, jpeg-js@^0.3.7:
|
||||
version "0.3.7"
|
||||
resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.3.7.tgz#471a89d06011640592d314158608690172b1028d"
|
||||
integrity sha512-9IXdWudL61npZjvLuVe/ktHiA41iE8qFyLB+4VDTblEsWBzeg8WQTlktdUK4CdncUqtUgUg0bbOmTE2bKBKaBQ==
|
||||
@@ -11793,6 +12027,11 @@ mime-db@~1.23.0:
|
||||
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.23.0.tgz#a31b4070adaea27d732ea333740a64d0ec9a6659"
|
||||
integrity sha1-oxtAcK2uon1zLqMzdApk0OyaZlk=
|
||||
|
||||
mime-db@~1.33.0:
|
||||
version "1.33.0"
|
||||
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db"
|
||||
integrity sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==
|
||||
|
||||
mime-types@2.1.11:
|
||||
version "2.1.11"
|
||||
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.11.tgz#c259c471bda808a85d6cd193b430a5fae4473b3c"
|
||||
@@ -11800,6 +12039,13 @@ mime-types@2.1.11:
|
||||
dependencies:
|
||||
mime-db "~1.23.0"
|
||||
|
||||
mime-types@2.1.18:
|
||||
version "2.1.18"
|
||||
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.18.tgz#6f323f60a83d11146f831ff11fd66e2fe5503bb8"
|
||||
integrity sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==
|
||||
dependencies:
|
||||
mime-db "~1.33.0"
|
||||
|
||||
mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24:
|
||||
version "2.1.26"
|
||||
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.26.tgz#9c921fc09b7e149a65dfdc0da4d20997200b0a06"
|
||||
@@ -12644,7 +12890,7 @@ on-finished@~2.3.0:
|
||||
dependencies:
|
||||
ee-first "1.1.1"
|
||||
|
||||
on-headers@~1.0.2:
|
||||
on-headers@~1.0.1, on-headers@~1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f"
|
||||
integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==
|
||||
@@ -12745,7 +12991,7 @@ os-browserify@^0.3.0:
|
||||
resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27"
|
||||
integrity sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=
|
||||
|
||||
os-homedir@^1.0.0:
|
||||
os-homedir@^1.0.0, os-homedir@^1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3"
|
||||
integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M=
|
||||
@@ -13101,6 +13347,11 @@ parse-node-version@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/parse-node-version/-/parse-node-version-1.0.1.tgz#e2b5dbede00e7fa9bc363607f53327e8b073189b"
|
||||
integrity sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==
|
||||
|
||||
parse-passwd@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6"
|
||||
integrity sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=
|
||||
|
||||
parse-path@^4.0.0:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/parse-path/-/parse-path-4.0.1.tgz#0ec769704949778cb3b8eda5e994c32073a1adff"
|
||||
@@ -13194,7 +13445,7 @@ path-is-absolute@^1.0.0, path-is-absolute@^1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
|
||||
integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
|
||||
|
||||
path-is-inside@^1.0.1, path-is-inside@^1.0.2:
|
||||
path-is-inside@1.0.2, path-is-inside@^1.0.1, path-is-inside@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53"
|
||||
integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=
|
||||
@@ -13219,6 +13470,11 @@ path-to-regexp@0.1.7:
|
||||
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
|
||||
integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=
|
||||
|
||||
path-to-regexp@2.2.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-2.2.1.tgz#90b617025a16381a879bc82a38d4e8bdeb2bcf45"
|
||||
integrity sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==
|
||||
|
||||
path-type@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441"
|
||||
@@ -13258,6 +13514,11 @@ pbkdf2@^3.0.3:
|
||||
safe-buffer "^5.0.1"
|
||||
sha.js "^2.4.8"
|
||||
|
||||
pend@~1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
|
||||
integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA=
|
||||
|
||||
performance-now@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
|
||||
@@ -13342,6 +13603,29 @@ pkg-up@2.0.0, pkg-up@^2.0.0:
|
||||
dependencies:
|
||||
find-up "^2.1.0"
|
||||
|
||||
playwright-core@=0.14.0:
|
||||
version "0.14.0"
|
||||
resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-0.14.0.tgz#2d3d7eb4ccbc7ef45e36ddcead4a638c1d2c1313"
|
||||
integrity sha512-RttBQ23zOFw6Dyke9WUKnkmLCuMovvW2JiOJ+M3in0SPo5rDASDqG8evBPW6w0Y2RyRKYFB0yhjn5LQG2qIayQ==
|
||||
dependencies:
|
||||
debug "^4.1.1"
|
||||
extract-zip "^2.0.0"
|
||||
https-proxy-agent "^3.0.0"
|
||||
jpeg-js "^0.3.7"
|
||||
mime "^2.4.4"
|
||||
pngjs "^5.0.0"
|
||||
progress "^2.0.3"
|
||||
proxy-from-env "^1.1.0"
|
||||
rimraf "^3.0.2"
|
||||
ws "^6.1.0"
|
||||
|
||||
playwright@^0.14.0:
|
||||
version "0.14.0"
|
||||
resolved "https://registry.yarnpkg.com/playwright/-/playwright-0.14.0.tgz#82a6daf593b978597285cacdd6dfc87f5d4333fb"
|
||||
integrity sha512-bdRnA4hvcEGVoDDnIuyF1Oa4P+Z0GVCQGXFA70L4CX9XhL79K4ib75RFZ5xLpFtuD+cZiLoBgTkrSpel7HX8xw==
|
||||
dependencies:
|
||||
playwright-core "=0.14.0"
|
||||
|
||||
please-upgrade-node@^3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz#aeddd3f994c933e4ad98b99d9a556efa0e2fe942"
|
||||
@@ -13379,6 +13663,11 @@ pngjs@3.4.0, pngjs@^3.0.0, pngjs@^3.3.0, pngjs@^3.3.3:
|
||||
resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-3.4.0.tgz#99ca7d725965fb655814eaf65f38f12bbdbf555f"
|
||||
integrity sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==
|
||||
|
||||
pngjs@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-5.0.0.tgz#e79dd2b215767fd9c04561c01236df960bce7fbb"
|
||||
integrity sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==
|
||||
|
||||
pnp-webpack-plugin@^1.5.0:
|
||||
version "1.6.4"
|
||||
resolved "https://registry.yarnpkg.com/pnp-webpack-plugin/-/pnp-webpack-plugin-1.6.4.tgz#c9711ac4dc48a685dabafc86f8b6dd9f8df84149"
|
||||
@@ -13870,7 +14159,7 @@ promise@^7.1.1:
|
||||
dependencies:
|
||||
asap "~2.0.3"
|
||||
|
||||
prompts@^2.0.1:
|
||||
prompts@^2.0.1, prompts@^2.3.0:
|
||||
version "2.3.2"
|
||||
resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.3.2.tgz#480572d89ecf39566d2bd3fe2c9fccb7c4c0b068"
|
||||
integrity sha512-Q06uKs2CkNYVID0VqwfAl9mipo99zkBv/n2JtWY89Yxa3ZabWSrs0e2KTudKVa3peLUvYXMefDqIleLPVUBZMA==
|
||||
@@ -13924,6 +14213,11 @@ proxy-addr@~2.0.4, proxy-addr@~2.0.5:
|
||||
forwarded "~0.1.2"
|
||||
ipaddr.js "1.9.1"
|
||||
|
||||
proxy-from-env@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
|
||||
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
|
||||
|
||||
prr@~1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476"
|
||||
@@ -13994,7 +14288,7 @@ punycode@2.x.x, punycode@^2.1.0, punycode@^2.1.1:
|
||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
|
||||
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
|
||||
|
||||
punycode@^1.2.4, punycode@^1.4.1:
|
||||
punycode@^1.2.4, punycode@^1.3.2, punycode@^1.4.1:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
|
||||
integrity sha1-wNWmOycYgArY4esPpSachN1BhF4=
|
||||
@@ -14068,6 +14362,11 @@ randomfill@^1.0.3:
|
||||
randombytes "^2.0.5"
|
||||
safe-buffer "^5.1.0"
|
||||
|
||||
range-parser@1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e"
|
||||
integrity sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=
|
||||
|
||||
range-parser@^1.2.1, range-parser@~1.2.0, range-parser@~1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
|
||||
@@ -14850,6 +15149,14 @@ resolve-cwd@^3.0.0:
|
||||
dependencies:
|
||||
resolve-from "^5.0.0"
|
||||
|
||||
resolve-dir@^0.1.0:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-0.1.1.tgz#b219259a5602fac5c5c496ad894a6e8cc430261e"
|
||||
integrity sha1-shklmlYC+sXFxJatiUpujMQwJh4=
|
||||
dependencies:
|
||||
expand-tilde "^1.2.2"
|
||||
global-modules "^0.2.3"
|
||||
|
||||
resolve-from@5.0.0, resolve-from@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69"
|
||||
@@ -15026,6 +15333,11 @@ rx-lite@*, rx-lite@^4.0.8:
|
||||
resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444"
|
||||
integrity sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=
|
||||
|
||||
rx@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/rx/-/rx-4.1.0.tgz#a5f13ff79ef3b740fe30aa803fb09f98805d4782"
|
||||
integrity sha1-pfE/957zt0D+MKqAP7CfmIBdR4I=
|
||||
|
||||
rxjs@^5.4.3, rxjs@^5.5.2:
|
||||
version "5.5.12"
|
||||
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.5.12.tgz#6fa61b8a77c3d793dbaf270bee2f43f652d741cc"
|
||||
@@ -15247,6 +15559,20 @@ serialize-javascript@^2.1.2:
|
||||
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-2.1.2.tgz#ecec53b0e0317bdc95ef76ab7074b7384785fa61"
|
||||
integrity sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==
|
||||
|
||||
serve-handler@6.1.2:
|
||||
version "6.1.2"
|
||||
resolved "https://registry.yarnpkg.com/serve-handler/-/serve-handler-6.1.2.tgz#f05b0421a313fff2d257838cba00cbcc512cd2b6"
|
||||
integrity sha512-RFh49wX7zJmmOVDcIjiDSJnMH+ItQEvyuYLYuDBVoA/xmQSCuj+uRmk1cmBB5QQlI3qOiWKp6p4DUGY+Z5AB2A==
|
||||
dependencies:
|
||||
bytes "3.0.0"
|
||||
content-disposition "0.5.2"
|
||||
fast-url-parser "1.1.3"
|
||||
mime-types "2.1.18"
|
||||
minimatch "3.0.4"
|
||||
path-is-inside "1.0.2"
|
||||
path-to-regexp "2.2.1"
|
||||
range-parser "1.2.0"
|
||||
|
||||
serve-index@^1.7.2:
|
||||
version "1.9.1"
|
||||
resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239"
|
||||
@@ -15280,6 +15606,21 @@ serve-static@1.14.1, serve-static@^1.13.1:
|
||||
parseurl "~1.3.3"
|
||||
send "0.17.1"
|
||||
|
||||
serve@^11.3.0:
|
||||
version "11.3.0"
|
||||
resolved "https://registry.yarnpkg.com/serve/-/serve-11.3.0.tgz#1d342e13e310501ecf17b6602f1f35da640d6448"
|
||||
integrity sha512-AU0g50Q1y5EVFX56bl0YX5OtVjUX1N737/Htj93dQGKuHiuLvVB45PD8Muar70W6Kpdlz8aNJfoUqTyAq9EE/A==
|
||||
dependencies:
|
||||
"@zeit/schemas" "2.6.0"
|
||||
ajv "6.5.3"
|
||||
arg "2.0.0"
|
||||
boxen "1.3.0"
|
||||
chalk "2.4.1"
|
||||
clipboardy "1.2.3"
|
||||
compression "1.7.3"
|
||||
serve-handler "6.1.2"
|
||||
update-check "1.5.2"
|
||||
|
||||
set-blocking@^2.0.0, set-blocking@~2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
|
||||
@@ -15590,6 +15931,16 @@ source-map@^0.7.3:
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
|
||||
integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
|
||||
|
||||
spawnd@^4.4.0:
|
||||
version "4.4.0"
|
||||
resolved "https://registry.yarnpkg.com/spawnd/-/spawnd-4.4.0.tgz#bb52c5b34a22e3225ae1d3acb873b2cd58af0886"
|
||||
integrity sha512-jLPOfB6QOEgMOQY15Z6+lwZEhH3F5ncXxIaZ7WHPIapwNNLyjrs61okj3VJ3K6tmP5TZ6cO0VAu9rEY4MD4YQg==
|
||||
dependencies:
|
||||
exit "^0.1.2"
|
||||
signal-exit "^3.0.2"
|
||||
tree-kill "^1.2.2"
|
||||
wait-port "^0.2.7"
|
||||
|
||||
spdx-correct@^3.0.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.0.tgz#fb83e504445268f154b074e218c87c003cd31df4"
|
||||
@@ -16274,6 +16625,13 @@ tempy@0.3.0, tempy@^0.3.0:
|
||||
type-fest "^0.3.1"
|
||||
unique-string "^1.0.0"
|
||||
|
||||
term-size@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/term-size/-/term-size-1.2.0.tgz#458b83887f288fc56d6fffbfad262e26638efa69"
|
||||
integrity sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=
|
||||
dependencies:
|
||||
execa "^0.7.0"
|
||||
|
||||
term-size@^2.1.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/term-size/-/term-size-2.2.0.tgz#1f16adedfe9bdc18800e1776821734086fcc6753"
|
||||
@@ -16550,6 +16908,11 @@ tree-kill@1.2.0:
|
||||
resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.0.tgz#5846786237b4239014f05db156b643212d4c6f36"
|
||||
integrity sha512-DlX6dR0lOIRDFxI0mjL9IYg6OTncLm/Zt+JiBhE5OlFcAR8yc9S7FFXU9so0oda47frdM/JFsk7UjNt9vscKcg==
|
||||
|
||||
tree-kill@^1.2.2:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc"
|
||||
integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==
|
||||
|
||||
trim-newlines@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613"
|
||||
@@ -16903,6 +17266,14 @@ upath@^1.1.1, upath@^1.2.0:
|
||||
resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894"
|
||||
integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==
|
||||
|
||||
update-check@1.5.2:
|
||||
version "1.5.2"
|
||||
resolved "https://registry.yarnpkg.com/update-check/-/update-check-1.5.2.tgz#2fe09f725c543440b3d7dabe8971f2d5caaedc28"
|
||||
integrity sha512-1TrmYLuLj/5ZovwUS7fFd1jMH3NnFDN1y1A8dboedIDt7zs/zJMo6TwwlhYKkSeEwzleeiSBV5/3c9ufAQWDaQ==
|
||||
dependencies:
|
||||
registry-auth-token "3.3.2"
|
||||
registry-url "3.1.0"
|
||||
|
||||
update-check@1.5.3:
|
||||
version "1.5.3"
|
||||
resolved "https://registry.yarnpkg.com/update-check/-/update-check-1.5.3.tgz#45240fcfb8755a7c7fa68bbdd9eda026a41639ed"
|
||||
@@ -17161,6 +17532,26 @@ w3c-xmlserializer@^1.1.2:
|
||||
webidl-conversions "^4.0.2"
|
||||
xml-name-validator "^3.0.0"
|
||||
|
||||
wait-on@^3.3.0:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/wait-on/-/wait-on-3.3.0.tgz#9940981d047a72a9544a97b8b5fca45b2170a082"
|
||||
integrity sha512-97dEuUapx4+Y12aknWZn7D25kkjMk16PbWoYzpSdA8bYpVfS6hpl2a2pOWZ3c+Tyt3/i4/pglyZctG3J4V1hWQ==
|
||||
dependencies:
|
||||
"@hapi/joi" "^15.0.3"
|
||||
core-js "^2.6.5"
|
||||
minimist "^1.2.0"
|
||||
request "^2.88.0"
|
||||
rx "^4.1.0"
|
||||
|
||||
wait-port@^0.2.7:
|
||||
version "0.2.7"
|
||||
resolved "https://registry.yarnpkg.com/wait-port/-/wait-port-0.2.7.tgz#cdb4b78e662328099b187c7bb75fe0aa9cb6eb6c"
|
||||
integrity sha512-pJ6cSBIa0w1sDg4y/wXN4bmvhM9OneOvwdFHo647L2NShBi/oXG4lRaLic5cO1HaYGbUhEvratPfl/WMlIC+tg==
|
||||
dependencies:
|
||||
chalk "^2.4.2"
|
||||
commander "^3.0.2"
|
||||
debug "^4.1.1"
|
||||
|
||||
walker@^1.0.7, walker@~1.0.5:
|
||||
version "1.0.7"
|
||||
resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb"
|
||||
@@ -17369,7 +17760,7 @@ which-pm-runs@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb"
|
||||
integrity sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=
|
||||
|
||||
which@^1.2.9, which@^1.3.0, which@^1.3.1:
|
||||
which@^1.2.12, which@^1.2.9, which@^1.3.0, which@^1.3.1:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
|
||||
integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
|
||||
@@ -17390,6 +17781,13 @@ wide-align@^1.1.0:
|
||||
dependencies:
|
||||
string-width "^1.0.2 || 2"
|
||||
|
||||
widest-line@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-2.0.1.tgz#7438764730ec7ef4381ce4df82fb98a53142a3fc"
|
||||
integrity sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==
|
||||
dependencies:
|
||||
string-width "^2.1.1"
|
||||
|
||||
widest-line@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca"
|
||||
@@ -17687,6 +18085,13 @@ ws@^3.0.0, ws@^3.3.1:
|
||||
safe-buffer "~5.1.0"
|
||||
ultron "~1.1.0"
|
||||
|
||||
ws@^6.1.0:
|
||||
version "6.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb"
|
||||
integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==
|
||||
dependencies:
|
||||
async-limiter "~1.0.0"
|
||||
|
||||
ws@^7.0.0:
|
||||
version "7.2.3"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-7.2.3.tgz#a5411e1fb04d5ed0efee76d26d5c46d830c39b46"
|
||||
@@ -17975,6 +18380,14 @@ yargs@^9.0.0:
|
||||
y18n "^3.2.1"
|
||||
yargs-parser "^7.0.0"
|
||||
|
||||
yauzl@^2.10.0:
|
||||
version "2.10.0"
|
||||
resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"
|
||||
integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=
|
||||
dependencies:
|
||||
buffer-crc32 "~0.2.3"
|
||||
fd-slicer "~1.1.0"
|
||||
|
||||
yup@^0.27.0:
|
||||
version "0.27.0"
|
||||
resolved "https://registry.yarnpkg.com/yup/-/yup-0.27.0.tgz#f8cb198c8e7dd2124beddc2457571329096b06e7"
|
||||
|
||||
Reference in New Issue
Block a user