mirror of
https://github.com/zhigang1992/react-native-web.git
synced 2026-04-23 12:07:26 +08:00
Compare commits
48 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
75db0e9183 | ||
|
|
4b9a5fd8b4 | ||
|
|
b6be677db9 | ||
|
|
91c9457392 | ||
|
|
006d315a1a | ||
|
|
220eb79357 | ||
|
|
5db9a765b0 | ||
|
|
931d666fcc | ||
|
|
40c433c6df | ||
|
|
9888c2a3c6 | ||
|
|
3fa18becc7 | ||
|
|
aafeb0adad | ||
|
|
89468b7d6e | ||
|
|
f66af5e04d | ||
|
|
2363524fa7 | ||
|
|
5855e55615 | ||
|
|
5033e12d18 | ||
|
|
d6e8530f4d | ||
|
|
ad188a7ad6 | ||
|
|
bfaeae904e | ||
|
|
a54bdeec09 | ||
|
|
8fa7dc63ec | ||
|
|
d841db2337 | ||
|
|
8e7d31cff5 | ||
|
|
0764687a8f | ||
|
|
d31bdf2cf8 | ||
|
|
1f3a77dada | ||
|
|
c3cbd53a8a | ||
|
|
4f5ee15e4b | ||
|
|
9a1cade1f0 | ||
|
|
506dba933c | ||
|
|
c0de9dddf3 | ||
|
|
96c9c06272 | ||
|
|
505e3faee8 | ||
|
|
1f06229289 | ||
|
|
fc743e6eee | ||
|
|
ef97adec6e | ||
|
|
f196335281 | ||
|
|
d29e31d9d6 | ||
|
|
c7c1f29016 | ||
|
|
b56a737d62 | ||
|
|
f062eded40 | ||
|
|
fcc4fbf678 | ||
|
|
a18d30c809 | ||
|
|
b9172ceb8e | ||
|
|
744aaa26d4 | ||
|
|
2b5ddf753e | ||
|
|
b84e3b938a |
3
.github/ISSUE_TEMPLATE/bug.md
vendored
3
.github/ISSUE_TEMPLATE/bug.md
vendored
@@ -33,6 +33,9 @@ Steps to reproduce:
|
|||||||
**Expected behavior**
|
**Expected behavior**
|
||||||
<!--
|
<!--
|
||||||
REQUIRED: A clear and concise description of what you expected to happen.
|
REQUIRED: A clear and concise description of what you expected to happen.
|
||||||
|
|
||||||
|
Please check that the behaviour is not expected React Native behaviour by
|
||||||
|
running your test case on iOS or Android using https://snack.expo.io.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
**Environment (include versions). Did this work in previous versions?**
|
**Environment (include versions). Did this work in previous versions?**
|
||||||
|
|||||||
217
README.md
217
README.md
@@ -25,7 +25,7 @@ using Node.js.
|
|||||||
Who is using React Native in production web apps?
|
Who is using React Native in production web apps?
|
||||||
[Twitter](https://mobile.twitter.com), [Major League
|
[Twitter](https://mobile.twitter.com), [Major League
|
||||||
Soccer](https://matchcenter.mlssoccer.com),
|
Soccer](https://matchcenter.mlssoccer.com),
|
||||||
[Flipkart](https://www.flipkart.com/), Playstation, Uber, [The
|
[Flipkart](https://www.flipkart.com/), Uber, [The
|
||||||
Times](https://github.com/newsuk/times-components).
|
Times](https://github.com/newsuk/times-components).
|
||||||
|
|
||||||
Browser support: Chrome, Firefox, Edge, Safari 7+, IE 10+.
|
Browser support: Chrome, Firefox, Edge, Safari 7+, IE 10+.
|
||||||
@@ -38,34 +38,56 @@ The easiest way to get started is to edit this
|
|||||||
anything to try it out.
|
anything to try it out.
|
||||||
|
|
||||||
For installation and configuration details please read the [getting
|
For installation and configuration details please read the [getting
|
||||||
started](https://github.com/necolas/react-native-web/blob/master/packages/website/guides/getting-started.md)
|
started](https://github.com/necolas/react-native-web/blob/master/docs/guides/getting-started.md)
|
||||||
guide.
|
guide.
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
You can find the API documentation [on the website][website-url].
|
Please refer to the [React Native documentation][react-native-url] for the
|
||||||
|
overall API, design details, and information about the [Gesture Responder
|
||||||
Please refer to the [React Native documentation][react-native-url] for more
|
|
||||||
design details, and for information about the [Gesture Responder
|
|
||||||
system](https://facebook.github.io/react-native/docs/gesture-responder-system.html)
|
system](https://facebook.github.io/react-native/docs/gesture-responder-system.html)
|
||||||
and [animations](https://facebook.github.io/react-native/docs/animations.html).
|
and [animations](https://facebook.github.io/react-native/docs/animations.html).
|
||||||
|
|
||||||
|
Some components and APIs are extended with additional features for the web. And
|
||||||
|
in a few cases, features present for Android or iOS are missing on the web.
|
||||||
|
These differences are documented [on the website][website-url].
|
||||||
|
|
||||||
### Guides
|
### Guides
|
||||||
|
|
||||||
* [Getting started](https://github.com/necolas/react-native-web/blob/master/packages/website/guides/getting-started.md)
|
These guides provide a detailed look at using React Native to create accessible
|
||||||
* [Style](https://github.com/necolas/react-native-web/blob/master/packages/website/guides/style.md)
|
web experiences. Certain web-specific patterns are documented in the "web
|
||||||
* [Accessibility](https://github.com/necolas/react-native-web/blob/master/packages/website/guides/accessibility.md)
|
recipes" guide.
|
||||||
* [Internationalization](https://github.com/necolas/react-native-web/blob/master/packages/website/guides/internationalization.md)
|
|
||||||
* [Direct manipulation](https://github.com/necolas/react-native-web/blob/master/packages/website/guides/direct-manipulation.md)
|
* [Getting started](https://github.com/necolas/react-native-web/blob/master/docs/guides/getting-started.md)
|
||||||
* [Experimental / unstable use](https://github.com/necolas/react-native-web/blob/master/packages/website/guides/advanced.md)
|
* [Client-side rendering](https://github.com/necolas/react-native-web/blob/master/docs/guides/client-side-rendering.md)
|
||||||
|
* [Server-side rendering](https://github.com/necolas/react-native-web/blob/master/docs/guides/server-side-rendering.md)
|
||||||
|
* [Style](https://github.com/necolas/react-native-web/blob/master/docs/guides/style.md)
|
||||||
|
* [Accessibility](https://github.com/necolas/react-native-web/blob/master/docs/guides/accessibility.md)
|
||||||
|
* [Internationalization](https://github.com/necolas/react-native-web/blob/master/docs/guides/internationalization.md)
|
||||||
|
* [Direct manipulation](https://github.com/necolas/react-native-web/blob/master/docs/guides/direct-manipulation.md)
|
||||||
|
* [Web recipes](https://github.com/necolas/react-native-web/blob/master/docs/guides/web-recipes.md)
|
||||||
|
* [Multi-platform apps](https://github.com/necolas/react-native-web/blob/master/docs/guides/multi-platform-apps.md)
|
||||||
|
* [Experimental / unstable use](https://github.com/necolas/react-native-web/blob/master/docs/guides/advanced.md)
|
||||||
|
|
||||||
|
## Integrations
|
||||||
|
|
||||||
|
Examples of using React Native for Web with other web tools:
|
||||||
|
|
||||||
|
* [Docz](https://github.com/pedronauck/docz-plugin-react-native)
|
||||||
|
* [Gatsby](https://github.com/slorber/gatsby-plugin-react-native-web)
|
||||||
|
* [Next.js](https://github.com/zeit/next.js/tree/master/examples/with-react-native-web) (and [example recipes](https://gist.github.com/necolas/f9034091723f1b279be86c7429eb0c96))
|
||||||
|
* [Phenomic](https://github.com/phenomic/phenomic/tree/master/examples/react-native-web-app)
|
||||||
|
* [Razzle](https://github.com/jaredpalmer/razzle/tree/master/examples/with-react-native-web)
|
||||||
|
* [Storybook](https://github.com/necolas/react-native-web/tree/master/packages/website/storybook/.storybook)
|
||||||
|
* [Styleguidist](https://github.com/styleguidist/react-styleguidist/tree/master/examples/react-native)
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
There are examples [on the website][website-url] ([source
|
Check out all the [React Native examples][examples-url] ([source
|
||||||
code](https://github.com/necolas/react-native-web/blob/master/packages/website).
|
code](https://github.com/necolas/react-native-web/blob/master/packages/examples)).
|
||||||
And all the [React Native examples][examples-url] ([source
|
There are more examples [on the website][website-url] ([source
|
||||||
code](https://github.com/necolas/react-native-web/blob/master/packages/examples))
|
code](https://github.com/necolas/react-native-web/blob/master/packages/website)).
|
||||||
are also available. Here is an example to get you started:
|
And here is a simple example to get you started:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
@@ -96,104 +118,87 @@ You'll notice that there is no reference to `react-dom`; the `App` component is
|
|||||||
defined using the platform-agnostic APIs and Components introduced by React
|
defined using the platform-agnostic APIs and Components introduced by React
|
||||||
Native. This allows the app to be rendered to web and native platforms.
|
Native. This allows the app to be rendered to web and native platforms.
|
||||||
|
|
||||||
## Integrations
|
|
||||||
|
|
||||||
Examples of using React Native for Web with other web tools:
|
|
||||||
|
|
||||||
* [Gatsby](https://github.com/gatsbyjs/gatsby/tree/master/examples/using-react-native-web)
|
|
||||||
* [Next.js](https://github.com/zeit/next.js/tree/master/examples/with-react-native-web)
|
|
||||||
* [Phenomic](https://github.com/phenomic/phenomic/tree/master/examples/react-native-web-app)
|
|
||||||
* [Razzle](https://github.com/jaredpalmer/razzle/tree/master/examples/with-react-native-web)
|
|
||||||
* [Storybook](https://github.com/necolas/react-native-web/tree/master/packages/website/storybook/.storybook)
|
|
||||||
* [Styleguidist](https://github.com/styleguidist/react-styleguidist/tree/master/examples/react-native)
|
|
||||||
|
|
||||||
Example recipes for web-specific UI patterns:
|
|
||||||
|
|
||||||
* [Links](https://codesandbox.io/s/53r88k5opx)
|
|
||||||
* [Hover styles](https://codesandbox.io/s/o9q8vy70l5)
|
|
||||||
* [Root element styles](https://codesandbox.io/s/52x1871vjl)
|
|
||||||
|
|
||||||
## Compatibility with React Native
|
## Compatibility with React Native
|
||||||
|
|
||||||
React Native v0.55
|
React Native v0.55
|
||||||
|
|
||||||
### Components
|
### Components
|
||||||
|
|
||||||
| Name | Status | Notes |
|
| Name | Status | Notes |
|
||||||
| :----------------------- | :------------------ | :---- |
|
| :----------------------- | :----- | :---- |
|
||||||
| ActivityIndicator | Available | |
|
| ActivityIndicator | ✓ | |
|
||||||
| ART | Available | |
|
| ART | ✓ | |
|
||||||
| Button | Available | |
|
| Button | ✓ | |
|
||||||
| CheckBox | Available | |
|
| CheckBox | ✓ | |
|
||||||
| FlatList | Available | |
|
| FlatList | ✓ | |
|
||||||
| Image | Available (partial) | Missing multiple sources and HTTP headers. |
|
| Image | ✓ | Missing multiple sources ([#515](https://github.com/necolas/react-native-web/issues/515)) and HTTP headers ([#1019](https://github.com/necolas/react-native-web/issues/1019)). |
|
||||||
| ImageBackground | Available | |
|
| ImageBackground | ✓ | |
|
||||||
| KeyboardAvoidingView | Available (mock) | |
|
| KeyboardAvoidingView | (✓) | Mock. No equivalent web APIs. |
|
||||||
| ListView | Available | |
|
| ListView | ✓ | |
|
||||||
| Modal | Not started | |
|
| Modal | ✘ | Not started ([#1020](https://github.com/necolas/react-native-web/issues/1020)). |
|
||||||
| Picker | Available | |
|
| Picker | ✓ | |
|
||||||
| RefreshControl | Not started | |
|
| RefreshControl | ✘ | Not started ([#1027](https://github.com/necolas/react-native-web/issues/1027)). |
|
||||||
| SafeAreaView | Available | |
|
| SafeAreaView | ✓ | |
|
||||||
| ScrollView | Available (partial) | Missing momentum scroll events. |
|
| ScrollView | ✓ | Missing momentum scroll events ([#1021](https://github.com/necolas/react-native-web/issues/1021)) and `pagingEnabled` ([#1057](https://github.com/necolas/react-native-web/issues/1057)). |
|
||||||
| SectionList | Available | |
|
| SectionList | ✓ | |
|
||||||
| Slider | Not started | |
|
| Slider | ✘ | Not started ([#1022](https://github.com/necolas/react-native-web/issues/1022)). |
|
||||||
| StatusBar | Mock | |
|
| StatusBar | (✓) | Mock. No equivalent web APIs. |
|
||||||
| SwipeableFlatList | Available | |
|
| SwipeableFlatList | ✓ | |
|
||||||
| SwipeableListView | Available | |
|
| SwipeableListView | ✓ | |
|
||||||
| Switch | Available | |
|
| Switch | ✓ | |
|
||||||
| Text | Available (partial) | Missing `onLongPress` support. |
|
| Text | ✓ | Missing `onLongPress` ([#1011](https://github.com/necolas/react-native-web/issues/1011)) and `numberOfLines` ([#13](https://github.com/necolas/react-native-web/issues/13)) support. |
|
||||||
| TextInput | Available (partial) | Missing rich text features and auto-expanding behaviour. |
|
| TextInput | ✓ | Missing `onContentSizeChange` ([#793](https://github.com/necolas/react-native-web/issues/793)), rich text features ([#1023](https://github.com/necolas/react-native-web/issues/1023)), and auto-expanding behaviour ([#795](https://github.com/necolas/react-native-web/issues/795)). |
|
||||||
| Touchable | Available | Includes additional support for mouse and keyboard interactions. |
|
| Touchable | ✓ | Includes additional support for mouse and keyboard interactions. |
|
||||||
| TouchableHighlight | Available | |
|
| TouchableHighlight | ✓ | |
|
||||||
| TouchableNativeFeedback | Not started | |
|
| TouchableNativeFeedback | ✘ | Not started ([#1024](https://github.com/necolas/react-native-web/issues/1024)). |
|
||||||
| TouchableOpacity | Available | |
|
| TouchableOpacity | ✓ | |
|
||||||
| TouchableWithoutFeedback | Available | |
|
| TouchableWithoutFeedback | ✓ | |
|
||||||
| View | Available | |
|
| View | ✓ | |
|
||||||
| VirtualizedList | Available | |
|
| VirtualizedList | ✓ | |
|
||||||
| WebView | Not started | |
|
| WebView | ✘ | Not started ([1025](https://github.com/necolas/react-native-web/issues/1025)). |
|
||||||
| YellowBox | Mock | |
|
| YellowBox | (✓) | Mock. No YellowBox functionality. |
|
||||||
|
|
||||||
### Modules
|
### Modules
|
||||||
|
|
||||||
| Name | Status | Notes |
|
| Name | Status | Notes |
|
||||||
| :----------------------- | :------------------ | :---- |
|
| :----------------------- | :----- | :---- |
|
||||||
| AccessibilityInfo | Mock | No equivalent web APIs. |
|
| AccessibilityInfo | (✓) | Mock. No equivalent web APIs. |
|
||||||
| Alert | Not started | |
|
| Alert | ✘ | Not started ([#1026](https://github.com/necolas/react-native-web/issues/1026)). |
|
||||||
| Animated | Available | Missing `useNativeDriver` support. |
|
| Animated | ✓ | Missing `useNativeDriver` support. |
|
||||||
| AppRegistry | Available | Includes additional support for SSR with `getApplication`. |
|
| AppRegistry | ✓ | Includes additional support for server rendering with `getApplication`. |
|
||||||
| AppState | Available | |
|
| AppState | ✓ | |
|
||||||
| AsyncStorage | Available | |
|
| AsyncStorage | ✓ | |
|
||||||
| BackHandler | Mock | No equivalent web APIs. |
|
| BackHandler | (✓) | Mock. No equivalent web APIs. |
|
||||||
| CameraRoll | Not started | No equivalent web APIs. |
|
| CameraRoll | ✘ | No equivalent web APIs. |
|
||||||
| Clipboard | Available | |
|
| Clipboard | ✓ | |
|
||||||
| ColorPropType | Available | |
|
| ColorPropType | ✓ | |
|
||||||
| DeviceInfo | Available (partial) | |
|
| DeviceInfo | (✓) | Limited information. |
|
||||||
| Dimensions | Available | |
|
| Dimensions | ✓ | |
|
||||||
| Easing | Available | |
|
| Easing | ✓ | |
|
||||||
| EdgeInsetsPropType | Available | |
|
| EdgeInsetsPropType | ✓ | |
|
||||||
| Geolocation | Available | |
|
| Geolocation | ✓ | |
|
||||||
| I18nManager | Available | Includes additional support for runtime switch to RTL. |
|
| I18nManager | ✓ | Includes additional support for runtime switch to RTL. |
|
||||||
| ImageEditor | Not started | No equivalent web APIs. |
|
| ImageEditor | ✘ | No equivalent web APIs. |
|
||||||
| ImageStore | Not started | No equivalent web APIs. |
|
| ImageStore | ✘ | No equivalent web APIs. |
|
||||||
| InteractionManager | Available (partial) | |
|
| InteractionManager | (✓) | |
|
||||||
| Keyboard | Mock | |
|
| Keyboard | (✓) | Mock. |
|
||||||
| LayoutAnimation | Available (partial) | Missing transform to web animation. |
|
| LayoutAnimation | (✓) | Missing translation to web animations. |
|
||||||
| Linking | Available | |
|
| Linking | ✓ | |
|
||||||
| NativeEventEmitter | Available | |
|
| NativeEventEmitter | ✓ | |
|
||||||
| NativeMethodsMixin | Available | |
|
| NativeMethodsMixin | ✓ | |
|
||||||
| NativeModules | Available (partial) | Mocked. Missing ability to load native modules. |
|
| NativeModules | (✓) | Mocked. Missing ability to load native modules. |
|
||||||
| NetInfo | Available (partial) | Missing functionality to detect extensive connections. |
|
| NetInfo | ✓ | Missing functionality to detect expensive connections as there are no equivalent web APIs. |
|
||||||
| PanResponder | Available | |
|
| PanResponder | ✓ | |
|
||||||
| PixelRatio | Available | |
|
| PixelRatio | ✓ | |
|
||||||
| Platform | Available | |
|
| Platform | ✓ | |
|
||||||
| PointPropType | Available | |
|
| PointPropType | ✓ | |
|
||||||
| Settings | Not started | |
|
| Settings | ✘ | No equivalent web APIs. |
|
||||||
| Share | Available | Only available over HTTPS. Read about the [Web Share API](https://wicg.github.io/web-share/). |
|
| Share | ✓ | Only available over HTTPS. Read about the [Web Share API](https://wicg.github.io/web-share/). |
|
||||||
| StyleSheet | Available | |
|
| StyleSheet | ✓ | |
|
||||||
| TextPropTypes | Available | |
|
| TextPropTypes | ✓ | |
|
||||||
| UIManager | Available | |
|
| UIManager | ✓ | |
|
||||||
| Vibration | Available | |
|
| Vibration | ✓ | |
|
||||||
| ViewPropTypes | Available | |
|
| ViewPropTypes | ✓ | |
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
|||||||
42
docs/guides/client-side-rendering.md
Normal file
42
docs/guides/client-side-rendering.md
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# Client-side rendering
|
||||||
|
|
||||||
|
Render apps using `AppRegistry`:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// index.web.js
|
||||||
|
|
||||||
|
import App from './src/App';
|
||||||
|
import React from 'react';
|
||||||
|
import { AppRegistry } from 'react-native';
|
||||||
|
|
||||||
|
// register the app
|
||||||
|
AppRegistry.registerComponent('App', () => App);
|
||||||
|
|
||||||
|
AppRegistry.runApplication('App', {
|
||||||
|
initialProps: {},
|
||||||
|
rootTag: document.getElementById('react-app')
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Or render individual components:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import AppHeader from './src/AppHeader';
|
||||||
|
import React from 'react';
|
||||||
|
import { render } from 'react-native';
|
||||||
|
|
||||||
|
render(<AppHeader />, document.getElementById('react-app-header'))
|
||||||
|
```
|
||||||
|
|
||||||
|
(Components will also be rendered within a tree produced by calling
|
||||||
|
`ReactDOM.render` (i.e., an existing web app), but
|
||||||
|
otherwise it is not recommended.)
|
||||||
|
|
||||||
|
You might need to adjust the styles of the HTML document's root elements for
|
||||||
|
your app to fill the viewport.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<html style="height:100%">
|
||||||
|
<body style="height:100%">
|
||||||
|
<div id="react-root" style="display:flex;height:100%"></div>
|
||||||
|
```
|
||||||
109
docs/guides/getting-started.md
Normal file
109
docs/guides/getting-started.md
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
# Getting started
|
||||||
|
|
||||||
|
This guide will help you render components and applications with React Native
|
||||||
|
for Web.
|
||||||
|
|
||||||
|
Your application may need to polyfill `Promise`, `Object.assign`, `Array.from`,
|
||||||
|
and [`ResizeObserver`](https://github.com/que-etc/resize-observer-polyfill) as
|
||||||
|
necessary for your desired browser support.
|
||||||
|
|
||||||
|
If you're not familiar with setting up a new React web project, please follow
|
||||||
|
the recommendations in the [React documentation](https://reactjs.org/).
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
```
|
||||||
|
yarn add react react-dom react-native-web
|
||||||
|
```
|
||||||
|
|
||||||
|
And if you need to use `ART`:
|
||||||
|
|
||||||
|
```
|
||||||
|
yarn add react-art
|
||||||
|
```
|
||||||
|
|
||||||
|
## Starter kits
|
||||||
|
|
||||||
|
[create-react-app](https://github.com/facebookincubator/create-react-app)
|
||||||
|
includes built-in support for aliasing `react-native-web` to `react-native`.
|
||||||
|
|
||||||
|
```
|
||||||
|
create-react-app my-app
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuring a module bundler
|
||||||
|
|
||||||
|
If you have a custom setup, you may choose to configure your module bundler to
|
||||||
|
alias the package to `react-native`.
|
||||||
|
|
||||||
|
For example, modify your [webpack](https://github.com/webpack/webpack)
|
||||||
|
configuration as follows:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// webpack.config.js
|
||||||
|
module.exports = {
|
||||||
|
// ...the rest of your config
|
||||||
|
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'react-native$': 'react-native-web'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Now you can create your components and applications with the React Native API.
|
||||||
|
|
||||||
|
## Configuring Babel
|
||||||
|
|
||||||
|
If you need to do the aliasing with Babel you can use
|
||||||
|
[babel-plugin-module-resolver](https://www.npmjs.com/package/babel-plugin-module-resolver)
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"plugins": [
|
||||||
|
["module-resolver", {
|
||||||
|
"alias": {
|
||||||
|
"^react-native$": "react-native-web"
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuring Jest
|
||||||
|
|
||||||
|
[Jest](https://facebook.github.io/jest/) can be configured using the provided
|
||||||
|
preset. This will map `react-native` to `react-native-web` and provide
|
||||||
|
appropriate mocks:
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"preset": "react-native-web"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Please refer to the Jest documentation for more information.
|
||||||
|
|
||||||
|
## Configuring Flow
|
||||||
|
|
||||||
|
[Flow](https://flow.org) can be configured to understand the aliased module:
|
||||||
|
|
||||||
|
```
|
||||||
|
[options]
|
||||||
|
module.name_mapper='^react-native$' -> 'react-native-web'
|
||||||
|
```
|
||||||
|
|
||||||
|
You may also need to include a custom libdef
|
||||||
|
([example](https://gist.github.com/paularmstrong/f60b40d16fc83e1e8e532d483336f9bb))
|
||||||
|
in your config.
|
||||||
|
|
||||||
|
## Other notes
|
||||||
|
|
||||||
|
### Safari flexbox performance
|
||||||
|
|
||||||
|
Safari prior to version 10.1 can suffer from extremely [poor flexbox
|
||||||
|
performance](https://bugs.webkit.org/show_bug.cgi?id=150445). The recommended
|
||||||
|
way to work around this issue (as used on mobile.twitter.com) is to set
|
||||||
|
`display:block` on Views in your element hierarchy that you know don't need
|
||||||
|
flexbox layout.
|
||||||
154
docs/guides/multi-platform-apps.md
Normal file
154
docs/guides/multi-platform-apps.md
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
# Multi-platform applications
|
||||||
|
|
||||||
|
## Web-specific code
|
||||||
|
|
||||||
|
Minor platform differences can use the `Platform` module.
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { Platform } from 'react-native';
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
height: (Platform.OS === 'web') ? 200 : 100,
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
More significant platform differences should use platform-specific files (see
|
||||||
|
the webpack configuration below for resolving `*.web.js` files):
|
||||||
|
|
||||||
|
For example, with the following files in your project:
|
||||||
|
|
||||||
|
```
|
||||||
|
MyComponent.android.js
|
||||||
|
MyComponent.ios.js
|
||||||
|
MyComponent.web.js
|
||||||
|
```
|
||||||
|
|
||||||
|
And the following import:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import MyComponent from './MyComponent';
|
||||||
|
```
|
||||||
|
|
||||||
|
React Native will automatically import the correct variant for each specific
|
||||||
|
target platform.
|
||||||
|
|
||||||
|
## Web packaging for existing React Native apps
|
||||||
|
|
||||||
|
What follows is merely an _example_ of one basic way to package a web app using
|
||||||
|
[Webpack](https://webpack.js.org) and [Babel](https://babeljs.io/). (You can
|
||||||
|
also the React Native bundler, [Metro](https://github.com/facebook/metro), to
|
||||||
|
build web apps.)
|
||||||
|
|
||||||
|
Packaging web apps is subtly different to packaging React Native apps and is
|
||||||
|
complicated by the need to tree-shake and code-split non-trivial apps.
|
||||||
|
|
||||||
|
Install webpack-related dependencies, for example:
|
||||||
|
|
||||||
|
```
|
||||||
|
yarn add --dev babel-loader url-loader webpack webpack-cli webpack-dev-server
|
||||||
|
```
|
||||||
|
|
||||||
|
React Native's Babel preset rewrites ES modules to CommonJS modules, preventing
|
||||||
|
bundlers from automatically performing "tree-shaking" to remove unused modules
|
||||||
|
from your web app build. To help with this, you can install the following Babel
|
||||||
|
plugin:
|
||||||
|
|
||||||
|
```
|
||||||
|
yarn add --dev babel-plugin-react-native-web
|
||||||
|
```
|
||||||
|
|
||||||
|
Create a `web/webpack.config.js` file:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// web/webpack.config.js
|
||||||
|
|
||||||
|
const path = require('path');
|
||||||
|
const webpack = require('webpack');
|
||||||
|
|
||||||
|
const appDirectory = path.resolve(__dirname, '../');
|
||||||
|
|
||||||
|
// This is needed for webpack to compile JavaScript.
|
||||||
|
// Many OSS React Native packages are not compiled to ES5 before being
|
||||||
|
// published. If you depend on uncompiled packages they may cause webpack build
|
||||||
|
// errors. To fix this webpack can be configured to compile to the necessary
|
||||||
|
// `node_module`.
|
||||||
|
const babelLoaderConfiguration = {
|
||||||
|
test: /\.js$/,
|
||||||
|
// Add every directory that needs to be compiled by Babel during the build.
|
||||||
|
include: [
|
||||||
|
path.resolve(appDirectory, 'index.web.js'),
|
||||||
|
path.resolve(appDirectory, 'src'),
|
||||||
|
path.resolve(appDirectory, 'node_modules/react-native-uncompiled')
|
||||||
|
],
|
||||||
|
use: {
|
||||||
|
loader: 'babel-loader',
|
||||||
|
options: {
|
||||||
|
cacheDirectory: true,
|
||||||
|
// The 'react-native' preset is recommended to match React Native's packager
|
||||||
|
presets: ['react-native'],
|
||||||
|
// Re-write paths to import only the modules needed by the app
|
||||||
|
plugins: ['react-native-web']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// This is needed for webpack to import static images in JavaScript files.
|
||||||
|
const imageLoaderConfiguration = {
|
||||||
|
test: /\.(gif|jpe?g|png|svg)$/,
|
||||||
|
use: {
|
||||||
|
loader: 'url-loader',
|
||||||
|
options: {
|
||||||
|
name: '[name].[ext]'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
entry: [
|
||||||
|
// load any web API polyfills
|
||||||
|
// path.resolve(appDirectory, 'polyfills-web.js'),
|
||||||
|
// your web-specific entry file
|
||||||
|
path.resolve(appDirectory, 'index.web.js')
|
||||||
|
],
|
||||||
|
|
||||||
|
// configures where the build ends up
|
||||||
|
output: {
|
||||||
|
filename: 'bundle.web.js',
|
||||||
|
path: path.resolve(appDirectory, 'dist')
|
||||||
|
},
|
||||||
|
|
||||||
|
// ...the rest of your config
|
||||||
|
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
babelLoaderConfiguration,
|
||||||
|
imageLoaderConfiguration
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
resolve: {
|
||||||
|
// This will only alias the exact import "react-native"
|
||||||
|
alias: {
|
||||||
|
'react-native$': 'react-native-web'
|
||||||
|
},
|
||||||
|
// If you're working on a multi-platform React Native app, web-specific
|
||||||
|
// module implementations should be written in files using the extension
|
||||||
|
// `.web.js`.
|
||||||
|
extensions: [ '.web.js', '.js' ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
To run in development from the root of your application:
|
||||||
|
|
||||||
|
```
|
||||||
|
./node_modules/.bin/webpack-dev-server -d --config ./web/webpack.config.js --inline --hot --colors
|
||||||
|
```
|
||||||
|
|
||||||
|
To build for production:
|
||||||
|
|
||||||
|
```
|
||||||
|
./node_modules/.bin/webpack -p --config ./web/webpack.config.js
|
||||||
|
```
|
||||||
|
|
||||||
|
Please refer to the Webpack documentation for more information on configuration.
|
||||||
33
docs/guides/server-side-rendering.md
Normal file
33
docs/guides/server-side-rendering.md
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# Server-side rendering
|
||||||
|
|
||||||
|
Server-side rendering to HTML is supported using `AppRegistry`:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import App from './src/App';
|
||||||
|
import ReactDOMServer from 'react-dom/server';
|
||||||
|
import { AppRegistry } from 'react-native-web';
|
||||||
|
|
||||||
|
// register the app
|
||||||
|
AppRegistry.registerComponent('App', () => App);
|
||||||
|
|
||||||
|
// prerender the app
|
||||||
|
const { element, getStyleElement } = AppRegistry.getApplication('App', { initialProps });
|
||||||
|
// first the element
|
||||||
|
const html = ReactDOMServer.renderToString(element);
|
||||||
|
// then the styles (optionally include a nonce if your CSP policy requires it)
|
||||||
|
const css = ReactDOMServer.renderToStaticMarkup(getStyleElement({ nonce }));
|
||||||
|
|
||||||
|
// example HTML document string
|
||||||
|
const document = `
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html style="height:100%">
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
${css}
|
||||||
|
<body style="height:100%; overflow-y:hidden">
|
||||||
|
<div id="root" style="display:flex; height: 100%">
|
||||||
|
${html}
|
||||||
|
</div>
|
||||||
|
<script nonce="${nonce}" src="${bundlePath}"></script>
|
||||||
|
`
|
||||||
|
```
|
||||||
51
docs/guides/web-recipes.md
Normal file
51
docs/guides/web-recipes.md
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# Web recipes
|
||||||
|
|
||||||
|
Examples of how to implement web patterns with React Native.
|
||||||
|
|
||||||
|
#### `html` and `body` styles
|
||||||
|
|
||||||
|
The `html` and `body` elements require certain styles to support full-screen
|
||||||
|
React Native apps, and disable "features" like pull-to-refresh in mobile
|
||||||
|
browsers. Using the `body` as the root scroll view is [not reliably
|
||||||
|
supported](https://github.com/necolas/react-native-web/issues/829).
|
||||||
|
|
||||||
|
[Example code](https://codesandbox.io/s/52x1871vjl).
|
||||||
|
|
||||||
|
#### Hover styles
|
||||||
|
|
||||||
|
Relying on the web's native hover styles can result in several unwanted UX
|
||||||
|
consequences. Hover styles might be displayed during touch interactions and can
|
||||||
|
remain visually "stuck". Furthermore, there's no way to delay or persist hover
|
||||||
|
for accessibility purposes. This recipe shows how to apply hover styles that
|
||||||
|
integrate with the Responder event system (e.g., the `Touchable*` press styles)
|
||||||
|
and display styles *only* when the mouse is active. It can also be used as the
|
||||||
|
basis for programmatic hover delays and rendering of components (e.g., hover
|
||||||
|
cards).
|
||||||
|
|
||||||
|
[Example code](https://codesandbox.io/s/o9q8vy70l5)
|
||||||
|
|
||||||
|
#### Link styles
|
||||||
|
|
||||||
|
Cross-platform link components are straight-forward to create and can be
|
||||||
|
combined with the hover recipe.
|
||||||
|
|
||||||
|
[Example code](https://codesandbox.io/s/53r88k5opx)
|
||||||
|
|
||||||
|
<!--
|
||||||
|
#### Typography
|
||||||
|
|
||||||
|
Rather than relying on inhereted global typography styles, React Native allows
|
||||||
|
you to define components that encapsulate a typographic system. This has the
|
||||||
|
added benefit of enabling content-specific typography adjustments, e.g.,
|
||||||
|
different font sizes or weights for different languages.
|
||||||
|
|
||||||
|
[Example code]()
|
||||||
|
|
||||||
|
#### Responsive font size
|
||||||
|
|
||||||
|
[Example code]()
|
||||||
|
|
||||||
|
#### SVG icons
|
||||||
|
|
||||||
|
[Example code]()
|
||||||
|
-->
|
||||||
14
package.json
14
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.8.9",
|
"version": "0.9.7",
|
||||||
"name": "react-native-web-monorepo",
|
"name": "react-native-web-monorepo",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"clean": "del ./packages/*/dist",
|
"clean": "del ./packages/*/dist",
|
||||||
@@ -39,8 +39,8 @@
|
|||||||
"babel-preset-react-native": "^4.0.0",
|
"babel-preset-react-native": "^4.0.0",
|
||||||
"caniuse-api": "^2.0.0",
|
"caniuse-api": "^2.0.0",
|
||||||
"del-cli": "^1.1.0",
|
"del-cli": "^1.1.0",
|
||||||
"enzyme": "^3.3.0",
|
"enzyme": "^3.6.0",
|
||||||
"enzyme-adapter-react-16": "^1.1.0",
|
"enzyme-adapter-react-16": "^1.5.0",
|
||||||
"enzyme-to-json": "^3.3.3",
|
"enzyme-to-json": "^3.3.3",
|
||||||
"eslint": "^4.19.1",
|
"eslint": "^4.19.1",
|
||||||
"eslint-config-prettier": "^2.9.0",
|
"eslint-config-prettier": "^2.9.0",
|
||||||
@@ -54,10 +54,10 @@
|
|||||||
"lint-staged": "^7.1.0",
|
"lint-staged": "^7.1.0",
|
||||||
"npm-run-all": "^4.1.3",
|
"npm-run-all": "^4.1.3",
|
||||||
"prettier": "^1.12.1",
|
"prettier": "^1.12.1",
|
||||||
"react": "^16.4.1",
|
"react": "^16.5.1",
|
||||||
"react-art": "^16.4.1",
|
"react-art": "^16.5.1",
|
||||||
"react-dom": "^16.4.1",
|
"react-dom": "^16.5.1",
|
||||||
"react-test-renderer": "^16.4.1"
|
"react-test-renderer": "^16.5.1"
|
||||||
},
|
},
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "babel-plugin-react-native-web",
|
"name": "babel-plugin-react-native-web",
|
||||||
"version": "0.8.9",
|
"version": "0.9.7",
|
||||||
"description": "Babel plugin for React Native for Web",
|
"description": "Babel plugin for React Native for Web",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
"name": "benchmarks",
|
"name": "benchmarks",
|
||||||
"version": "0.8.9",
|
"version": "0.9.7",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "mkdir -p dist && cp -f index.html dist/index.html && ./node_modules/.bin/webpack-cli --config ./webpack.config.js",
|
"build": "mkdir -p dist && cp -f index.html dist/index.html && ./node_modules/.bin/webpack-cli --config ./webpack.config.js",
|
||||||
"release": "yarn build && git checkout gh-pages && rm -rf ../../benchmarks && mv dist ../../benchmarks && git add -A && git commit -m \"Benchmarks deploy\" && git push origin gh-pages && git checkout -"
|
"release": "yarn build && git checkout gh-pages && rm -rf ../../benchmarks && mv dist ../../benchmarks && git add -A && git commit -m \"Benchmarks deploy\" && git push origin gh-pages && git checkout -"
|
||||||
@@ -14,11 +14,11 @@
|
|||||||
"fela": "^6.1.9",
|
"fela": "^6.1.9",
|
||||||
"glamor": "2.20.40",
|
"glamor": "2.20.40",
|
||||||
"radium": "^0.24.0",
|
"radium": "^0.24.0",
|
||||||
"react": "^16.4.1",
|
"react": "^16.5.1",
|
||||||
"react-dom": "^16.4.1",
|
"react-dom": "^16.5.1",
|
||||||
"react-fela": "^7.3.1",
|
"react-fela": "^7.3.1",
|
||||||
"react-jss": "^8.6.1",
|
"react-jss": "^8.6.1",
|
||||||
"react-native-web": "0.8.9",
|
"react-native-web": "0.9.7",
|
||||||
"reactxp": "^1.3.0",
|
"reactxp": "^1.3.0",
|
||||||
"styled-components": "^3.3.3",
|
"styled-components": "^3.3.3",
|
||||||
"styled-jsx": "^2.2.7",
|
"styled-jsx": "^2.2.7",
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
"styletron-react": "^4.3.1"
|
"styletron-react": "^4.3.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel-plugin-react-native-web": "0.8.9",
|
"babel-plugin-react-native-web": "0.9.7",
|
||||||
"css-loader": "^1.0.0",
|
"css-loader": "^1.0.0",
|
||||||
"style-loader": "^0.21.0",
|
"style-loader": "^0.21.0",
|
||||||
"url-loader": "^1.0.1",
|
"url-loader": "^1.0.1",
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
"name": "react-native-examples",
|
"name": "react-native-examples",
|
||||||
"version": "0.8.9",
|
"version": "0.9.7",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "mkdir -p dist && cp -f src/index.html dist/index.html && ./node_modules/.bin/webpack-cli --config ./webpack.config.js",
|
"build": "mkdir -p dist && cp -f src/index.html dist/index.html && ./node_modules/.bin/webpack-cli --config ./webpack.config.js",
|
||||||
"release": "yarn build && git checkout gh-pages && rm -rf ../../examples && mv dist ../../examples && git add -A && git commit -m \"Examples deploy\" && git push origin gh-pages && git checkout -"
|
"release": "yarn build && git checkout gh-pages && rm -rf ../../examples && mv dist ../../examples && git add -A && git commit -m \"Examples deploy\" && git push origin gh-pages && git checkout -"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"babel-runtime": "^6.26.0",
|
"babel-runtime": "^6.26.0",
|
||||||
"react": "^16.4.1",
|
"react": "^16.5.1",
|
||||||
"react-dom": "^16.4.1",
|
"react-dom": "^16.5.1",
|
||||||
"react-native-web": "0.8.9"
|
"react-native-web": "0.9.7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel-plugin-react-native-web": "0.8.9",
|
"babel-plugin-react-native-web": "0.9.7",
|
||||||
"babel-plugin-transform-runtime": "^6.23.0",
|
"babel-plugin-transform-runtime": "^6.23.0",
|
||||||
"file-loader": "^1.1.11",
|
"file-loader": "^1.1.11",
|
||||||
"webpack": "^4.8.1",
|
"webpack": "^4.8.1",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "react-native-web",
|
"name": "react-native-web",
|
||||||
"version": "0.8.9",
|
"version": "0.9.7",
|
||||||
"description": "React Native for Web",
|
"description": "React Native for Web",
|
||||||
"module": "dist/index.js",
|
"module": "dist/index.js",
|
||||||
"main": "dist/cjs/index.js",
|
"main": "dist/cjs/index.js",
|
||||||
@@ -25,9 +25,9 @@
|
|||||||
"react-timer-mixin": "^0.13.3"
|
"react-timer-mixin": "^0.13.3"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "16.x.x",
|
"react": ">=16.5.1",
|
||||||
"react-art": "16.x.x",
|
"react-art": ">=16.5.1",
|
||||||
"react-dom": "16.x.x"
|
"react-dom": ">=16.5.1"
|
||||||
},
|
},
|
||||||
"author": "Nicolas Gallagher",
|
"author": "Nicolas Gallagher",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -8,11 +8,16 @@
|
|||||||
* @flow
|
* @flow
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
let clipboardAvailable;
|
||||||
|
|
||||||
export default class Clipboard {
|
export default class Clipboard {
|
||||||
static isAvailable() {
|
static isAvailable() {
|
||||||
return (
|
if (clipboardAvailable === undefined) {
|
||||||
typeof document.queryCommandSupported === 'function' && document.queryCommandSupported('copy')
|
clipboardAvailable =
|
||||||
);
|
typeof document.queryCommandSupported === 'function' &&
|
||||||
|
document.queryCommandSupported('copy');
|
||||||
|
}
|
||||||
|
return clipboardAvailable;
|
||||||
}
|
}
|
||||||
|
|
||||||
static getString(): Promise<string> {
|
static getString(): Promise<string> {
|
||||||
|
|||||||
@@ -18,10 +18,11 @@ import StyleSheetPropType from '../../modules/StyleSheetPropType';
|
|||||||
import StyleSheet from '../StyleSheet';
|
import StyleSheet from '../StyleSheet';
|
||||||
import TextPropTypes from '../Text/TextPropTypes';
|
import TextPropTypes from '../Text/TextPropTypes';
|
||||||
import { arrayOf, bool, func, number, oneOfType, string } from 'prop-types';
|
import { arrayOf, bool, func, number, oneOfType, string } from 'prop-types';
|
||||||
|
import ViewPropTypes, { type ViewProps } from '../ViewPropTypes';
|
||||||
|
|
||||||
const pickerStyleType = StyleSheetPropType(PickerStylePropTypes);
|
const pickerStyleType = StyleSheetPropType(PickerStylePropTypes);
|
||||||
|
|
||||||
type Props = {
|
type Props = ViewProps & {
|
||||||
children?: PickerItem | Array<typeof PickerItem>,
|
children?: PickerItem | Array<typeof PickerItem>,
|
||||||
enabled?: boolean,
|
enabled?: boolean,
|
||||||
onValueChange?: Function,
|
onValueChange?: Function,
|
||||||
@@ -36,6 +37,7 @@ type Props = {
|
|||||||
|
|
||||||
class Picker extends Component<Props> {
|
class Picker extends Component<Props> {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
...ViewPropTypes,
|
||||||
children: oneOfType([PickerItemPropType, arrayOf(PickerItemPropType)]),
|
children: oneOfType([PickerItemPropType, arrayOf(PickerItemPropType)]),
|
||||||
enabled: bool,
|
enabled: bool,
|
||||||
onValueChange: func,
|
onValueChange: func,
|
||||||
@@ -56,8 +58,10 @@ class Picker extends Component<Props> {
|
|||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
itemStyle,
|
itemStyle,
|
||||||
mode,
|
mode,
|
||||||
prompt
|
prompt,
|
||||||
|
onValueChange,
|
||||||
/* eslint-enable */
|
/* eslint-enable */
|
||||||
|
...otherProps
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
return createElement('select', {
|
return createElement('select', {
|
||||||
@@ -66,7 +70,8 @@ class Picker extends Component<Props> {
|
|||||||
onChange: this._handleChange,
|
onChange: this._handleChange,
|
||||||
style: [styles.initial, style],
|
style: [styles.initial, style],
|
||||||
testID,
|
testID,
|
||||||
value: selectedValue
|
value: selectedValue,
|
||||||
|
...otherProps
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -279,15 +279,13 @@ const styles = StyleSheet.create({
|
|||||||
...commonStyle,
|
...commonStyle,
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
overflowX: 'hidden',
|
overflowX: 'hidden',
|
||||||
overflowY: 'auto',
|
overflowY: 'auto'
|
||||||
touchAction: 'pan-y'
|
|
||||||
},
|
},
|
||||||
baseHorizontal: {
|
baseHorizontal: {
|
||||||
...commonStyle,
|
...commonStyle,
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
overflowX: 'auto',
|
overflowX: 'auto',
|
||||||
overflowY: 'hidden',
|
overflowY: 'hidden'
|
||||||
touchAction: 'pan-x'
|
|
||||||
},
|
},
|
||||||
contentContainerHorizontal: {
|
contentContainerHorizontal: {
|
||||||
flexDirection: 'row'
|
flexDirection: 'row'
|
||||||
|
|||||||
@@ -100,6 +100,8 @@ StyleSheetValidation.addValidStylePropTypes({
|
|||||||
fill: string,
|
fill: string,
|
||||||
float: oneOf(['end', 'left', 'none', 'right', 'start']),
|
float: oneOf(['end', 'left', 'none', 'right', 'start']),
|
||||||
listStyle: string,
|
listStyle: string,
|
||||||
|
objectFit: oneOf(['fill', 'contain', 'cover', 'none', 'scale-down']),
|
||||||
|
objectPosition: string,
|
||||||
pointerEvents: string,
|
pointerEvents: string,
|
||||||
tableLayout: string,
|
tableLayout: string,
|
||||||
/* @private */
|
/* @private */
|
||||||
|
|||||||
@@ -247,7 +247,9 @@ describe('StyleSheet/createReactDOMStyle', () => {
|
|||||||
textDecorationStyle: 'dashed'
|
textDecorationStyle: 'dashed'
|
||||||
})
|
})
|
||||||
).toEqual({
|
).toEqual({
|
||||||
textDecoration: 'underline dashed rgba(255,0,0,1.00)'
|
textDecoration: 'underline',
|
||||||
|
textDecorationColor: 'rgba(255,0,0,1.00)',
|
||||||
|
textDecorationStyle: 'dashed'
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -32,11 +32,13 @@ describe('StyleSheet/i18nStyle', () => {
|
|||||||
test('converts end/start values', () => {
|
test('converts end/start values', () => {
|
||||||
const initial = {
|
const initial = {
|
||||||
float: 'start',
|
float: 'start',
|
||||||
textAlign: 'end'
|
textAlign: 'end',
|
||||||
|
transitionProperty: 'marginStart'
|
||||||
};
|
};
|
||||||
const expected = {
|
const expected = {
|
||||||
float: 'left',
|
float: 'left',
|
||||||
textAlign: 'right'
|
textAlign: 'right',
|
||||||
|
transitionProperty: 'marginLeft'
|
||||||
};
|
};
|
||||||
expect(i18nStyle(initial)).toEqual(expected);
|
expect(i18nStyle(initial)).toEqual(expected);
|
||||||
});
|
});
|
||||||
@@ -56,7 +58,8 @@ describe('StyleSheet/i18nStyle', () => {
|
|||||||
clear: 'left',
|
clear: 'left',
|
||||||
float: 'left',
|
float: 'left',
|
||||||
textAlign: 'right',
|
textAlign: 'right',
|
||||||
textShadowOffset: { width: '1rem', height: 10 }
|
textShadowOffset: { width: '1rem', height: 10 },
|
||||||
|
transitionProperty: 'marginLeft'
|
||||||
};
|
};
|
||||||
expect(i18nStyle(initial)).toEqual(initial);
|
expect(i18nStyle(initial)).toEqual(initial);
|
||||||
});
|
});
|
||||||
@@ -116,11 +119,13 @@ describe('StyleSheet/i18nStyle', () => {
|
|||||||
test('converts end/start values', () => {
|
test('converts end/start values', () => {
|
||||||
const initial = {
|
const initial = {
|
||||||
float: 'start',
|
float: 'start',
|
||||||
textAlign: 'end'
|
textAlign: 'end',
|
||||||
|
transitionProperty: 'marginStart'
|
||||||
};
|
};
|
||||||
const expected = {
|
const expected = {
|
||||||
float: 'right',
|
float: 'right',
|
||||||
textAlign: 'left'
|
textAlign: 'left',
|
||||||
|
transitionProperty: 'marginRight'
|
||||||
};
|
};
|
||||||
expect(i18nStyle(initial)).toEqual(expected);
|
expect(i18nStyle(initial)).toEqual(expected);
|
||||||
});
|
});
|
||||||
@@ -140,7 +145,8 @@ describe('StyleSheet/i18nStyle', () => {
|
|||||||
clear: 'left',
|
clear: 'left',
|
||||||
float: 'left',
|
float: 'left',
|
||||||
textAlign: 'right',
|
textAlign: 'right',
|
||||||
textShadowOffset: { width: '1rem', height: 10 }
|
textShadowOffset: { width: '1rem', height: 10 },
|
||||||
|
transitionProperty: 'marginLeft'
|
||||||
};
|
};
|
||||||
expect(i18nStyle(initial)).toEqual(initial);
|
expect(i18nStyle(initial)).toEqual(initial);
|
||||||
});
|
});
|
||||||
@@ -183,11 +189,13 @@ describe('StyleSheet/i18nStyle', () => {
|
|||||||
test('converts end/start values', () => {
|
test('converts end/start values', () => {
|
||||||
const initial = {
|
const initial = {
|
||||||
float: 'start',
|
float: 'start',
|
||||||
textAlign: 'end'
|
textAlign: 'end',
|
||||||
|
transitionProperty: 'marginStart'
|
||||||
};
|
};
|
||||||
const expected = {
|
const expected = {
|
||||||
float: 'right',
|
float: 'right',
|
||||||
textAlign: 'left'
|
textAlign: 'left',
|
||||||
|
transitionProperty: 'marginRight'
|
||||||
};
|
};
|
||||||
expect(i18nStyle(initial)).toEqual(expected);
|
expect(i18nStyle(initial)).toEqual(expected);
|
||||||
});
|
});
|
||||||
@@ -212,12 +220,14 @@ describe('StyleSheet/i18nStyle', () => {
|
|||||||
const initial = {
|
const initial = {
|
||||||
float: 'left',
|
float: 'left',
|
||||||
textAlign: 'right',
|
textAlign: 'right',
|
||||||
textShadowOffset: { width: '1rem', height: 10 }
|
textShadowOffset: { width: '1rem', height: 10 },
|
||||||
|
transitionProperty: 'marginLeft'
|
||||||
};
|
};
|
||||||
const expected = {
|
const expected = {
|
||||||
float: 'right',
|
float: 'right',
|
||||||
textAlign: 'left',
|
textAlign: 'left',
|
||||||
textShadowOffset: { width: '-1rem', height: 10 }
|
textShadowOffset: { width: '-1rem', height: 10 },
|
||||||
|
transitionProperty: 'marginRight'
|
||||||
};
|
};
|
||||||
expect(i18nStyle(initial)).toEqual(expected);
|
expect(i18nStyle(initial)).toEqual(expected);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -95,10 +95,18 @@ const resolveShadow = (resolvedStyle, style) => {
|
|||||||
|
|
||||||
const resolveTextDecoration = (resolvedStyle, style) => {
|
const resolveTextDecoration = (resolvedStyle, style) => {
|
||||||
const { textDecorationColor, textDecorationLine, textDecorationStyle } = style;
|
const { textDecorationColor, textDecorationLine, textDecorationStyle } = style;
|
||||||
const color = normalizeColor(textDecorationColor) || '';
|
const color = normalizeColor(textDecorationColor);
|
||||||
const lineStyle = textDecorationStyle || '';
|
|
||||||
if (textDecorationLine) {
|
if (textDecorationLine) {
|
||||||
resolvedStyle.textDecoration = `${textDecorationLine} ${lineStyle} ${color}`.trim();
|
// use 'text-decoration' for browsers that support CSS2 text-decoration (e.g., IE, Edge)
|
||||||
|
resolvedStyle.textDecoration = textDecorationLine;
|
||||||
|
|
||||||
|
if (textDecorationColor) {
|
||||||
|
resolvedStyle.textDecorationColor = color;
|
||||||
|
}
|
||||||
|
if (textDecorationStyle) {
|
||||||
|
resolvedStyle.textDecorationStyle = textDecorationStyle;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -116,6 +116,19 @@ const i18nStyle = originalStyle => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BiDi flip transitionProperty value
|
||||||
|
if (prop === 'transitionProperty') {
|
||||||
|
// BiDi flip properties
|
||||||
|
if (PROPERTIES_I18N.hasOwnProperty(value)) {
|
||||||
|
// convert start/end
|
||||||
|
const convertedValue = PROPERTIES_I18N[originalValue];
|
||||||
|
value = isRTL ? PROPERTIES_FLIP[convertedValue] : convertedValue;
|
||||||
|
} else if (isRTL && doLeftAndRightSwapInRTL && PROPERTIES_FLIP[originalValue]) {
|
||||||
|
value = PROPERTIES_FLIP[originalValue];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create finalized style
|
||||||
if (isRTL && prop === 'textShadowOffset') {
|
if (isRTL && prop === 'textShadowOffset') {
|
||||||
nextStyle[prop] = value;
|
nextStyle[prop] = value;
|
||||||
nextStyle[prop].width = additiveInverse(value.width);
|
nextStyle[prop].width = additiveInverse(value.width);
|
||||||
|
|||||||
@@ -11,125 +11,250 @@
|
|||||||
* 1. a keydown event occurred immediately before a focus event;
|
* 1. a keydown event occurred immediately before a focus event;
|
||||||
* 2. a focus event happened on an element which requires keyboard interaction (e.g., a text field);
|
* 2. a focus event happened on an element which requires keyboard interaction (e.g., a text field);
|
||||||
*
|
*
|
||||||
* Based on https://github.com/WICG/focus-ring
|
* This software or document includes material copied from or derived from https://github.com/WICG/focus-visible.
|
||||||
|
* Copyright © 2018 W3C® (MIT, ERCIM, Keio, Beihang).
|
||||||
|
* W3C Software Notice and License: https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document
|
||||||
*
|
*
|
||||||
* @noflow
|
* @noflow
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { canUseDOM } from 'fbjs/lib/ExecutionEnvironment';
|
import { canUseDOM } from 'fbjs/lib/ExecutionEnvironment';
|
||||||
|
import hash from '../../vendor/hash';
|
||||||
|
|
||||||
const rule = ':focus { outline: none; }';
|
const focusVisibleClass =
|
||||||
let ruleExists = false;
|
'rn-' + (process.env.NODE_ENV !== 'production' ? 'focusVisible-' : '') + hash('focus-visible');
|
||||||
|
|
||||||
|
const rule = `:focus:not(.${focusVisibleClass}) { outline: none; }`;
|
||||||
|
|
||||||
const modality = styleElement => {
|
const modality = styleElement => {
|
||||||
if (!canUseDOM) {
|
if (!canUseDOM) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let hadKeyboardEvent = false;
|
let hadKeyboardEvent = true;
|
||||||
let keyboardThrottleTimeoutID = 0;
|
let hadFocusVisibleRecently = false;
|
||||||
|
let hadFocusVisibleRecentlyTimeout = null;
|
||||||
|
|
||||||
const proto = window.Element.prototype;
|
const inputTypesWhitelist = {
|
||||||
const matches =
|
text: true,
|
||||||
proto.matches ||
|
search: true,
|
||||||
proto.mozMatchesSelector ||
|
url: true,
|
||||||
proto.msMatchesSelector ||
|
tel: true,
|
||||||
proto.webkitMatchesSelector;
|
email: true,
|
||||||
|
password: true,
|
||||||
|
number: true,
|
||||||
|
date: true,
|
||||||
|
month: true,
|
||||||
|
week: true,
|
||||||
|
time: true,
|
||||||
|
datetime: true,
|
||||||
|
'datetime-local': true
|
||||||
|
};
|
||||||
|
|
||||||
// These elements should always have a focus ring drawn, because they are
|
/**
|
||||||
// associated with switching to a keyboard modality.
|
* Helper function for legacy browsers and iframes which sometimes focus
|
||||||
const keyboardModalityWhitelist = [
|
* elements like document, body, and non-interactive SVG.
|
||||||
'input:not([type])',
|
*/
|
||||||
'input[type=text]',
|
function isValidFocusTarget(el) {
|
||||||
'input[type=search]',
|
if (
|
||||||
'input[type=url]',
|
el &&
|
||||||
'input[type=tel]',
|
el !== document &&
|
||||||
'input[type=email]',
|
el.nodeName !== 'HTML' &&
|
||||||
'input[type=password]',
|
el.nodeName !== 'BODY' &&
|
||||||
'input[type=number]',
|
'classList' in el &&
|
||||||
'input[type=date]',
|
'contains' in el.classList
|
||||||
'input[type=month]',
|
) {
|
||||||
'input[type=week]',
|
return true;
|
||||||
'input[type=time]',
|
}
|
||||||
'input[type=datetime]',
|
return false;
|
||||||
'input[type=datetime-local]',
|
}
|
||||||
'textarea',
|
|
||||||
'[role=textbox]'
|
|
||||||
].join(',');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Computes whether the given element should automatically trigger the
|
* Computes whether the given element should automatically trigger the
|
||||||
* `focus-ring`.
|
* `focus-visible` class being added, i.e. whether it should always match
|
||||||
|
* `:focus-visible` when focused.
|
||||||
*/
|
*/
|
||||||
const focusTriggersKeyboardModality = el => {
|
function focusTriggersKeyboardModality(el) {
|
||||||
if (matches) {
|
const type = el.type;
|
||||||
return matches.call(el, keyboardModalityWhitelist) && matches.call(el, ':not([readonly])');
|
const tagName = el.tagName;
|
||||||
} else {
|
const isReadOnly = el.readOnly;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
if (tagName === 'INPUT' && inputTypesWhitelist[type] && !isReadOnly) {
|
||||||
* Add the focus ring style
|
return true;
|
||||||
*/
|
|
||||||
const addFocusRing = () => {
|
|
||||||
if (styleElement && ruleExists) {
|
|
||||||
styleElement.sheet.deleteRule(0);
|
|
||||||
ruleExists = false;
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
if (tagName === 'TEXTAREA' && !isReadOnly) {
|
||||||
* Remove the focus ring style
|
return true;
|
||||||
*/
|
|
||||||
const removeFocusRing = () => {
|
|
||||||
if (styleElement && !ruleExists) {
|
|
||||||
styleElement.sheet.insertRule(rule, 0);
|
|
||||||
ruleExists = true;
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
if (el.isContentEditable) {
|
||||||
* On `keydown`, set `hadKeyboardEvent`, to be removed 100ms later if there
|
return true;
|
||||||
* are no further keyboard events. The 100ms throttle handles cases where
|
|
||||||
* focus is redirected programmatically after a keyboard event, such as
|
|
||||||
* opening a menu or dialog.
|
|
||||||
*/
|
|
||||||
const handleKeyDown = e => {
|
|
||||||
hadKeyboardEvent = true;
|
|
||||||
if (keyboardThrottleTimeoutID !== 0) {
|
|
||||||
clearTimeout(keyboardThrottleTimeoutID);
|
|
||||||
}
|
}
|
||||||
keyboardThrottleTimeoutID = setTimeout(() => {
|
|
||||||
hadKeyboardEvent = false;
|
|
||||||
keyboardThrottleTimeoutID = 0;
|
|
||||||
}, 100);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
return false;
|
||||||
* Display the focus-ring when the keyboard was used to focus
|
|
||||||
*/
|
|
||||||
const handleFocus = e => {
|
|
||||||
if (hadKeyboardEvent || focusTriggersKeyboardModality(e.target)) {
|
|
||||||
addFocusRing();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove the focus-ring when the keyboard was used to focus
|
|
||||||
*/
|
|
||||||
const handleBlur = () => {
|
|
||||||
if (!hadKeyboardEvent) {
|
|
||||||
removeFocusRing();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (document.body && document.body.addEventListener) {
|
|
||||||
removeFocusRing();
|
|
||||||
document.body.addEventListener('keydown', handleKeyDown, true);
|
|
||||||
document.body.addEventListener('focus', handleFocus, true);
|
|
||||||
document.body.addEventListener('blur', handleBlur, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the `focus-visible` class to the given element if it was not added by
|
||||||
|
* the author.
|
||||||
|
*/
|
||||||
|
function addFocusVisibleClass(el) {
|
||||||
|
if (el.classList.contains(focusVisibleClass)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
el.classList.add(focusVisibleClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the `focus-visible` class from the given element if it was not
|
||||||
|
* originally added by the author.
|
||||||
|
*/
|
||||||
|
function removeFocusVisibleClass(el) {
|
||||||
|
el.classList.remove(focusVisibleClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Treat `keydown` as a signal that the user is in keyboard modality.
|
||||||
|
* Apply `focus-visible` to any current active element and keep track
|
||||||
|
* of our keyboard modality state with `hadKeyboardEvent`.
|
||||||
|
*/
|
||||||
|
function onKeyDown(e) {
|
||||||
|
if (e.key !== 'Tab' && (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isValidFocusTarget(document.activeElement)) {
|
||||||
|
addFocusVisibleClass(document.activeElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
hadKeyboardEvent = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If at any point a user clicks with a pointing device, ensure that we change
|
||||||
|
* the modality away from keyboard.
|
||||||
|
* This avoids the situation where a user presses a key on an already focused
|
||||||
|
* element, and then clicks on a different element, focusing it with a
|
||||||
|
* pointing device, while we still think we're in keyboard modality.
|
||||||
|
*/
|
||||||
|
function onPointerDown(e) {
|
||||||
|
hadKeyboardEvent = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On `focus`, add the `focus-visible` class to the target if:
|
||||||
|
* - the target received focus as a result of keyboard navigation, or
|
||||||
|
* - the event target is an element that will likely require interaction
|
||||||
|
* via the keyboard (e.g. a text box)
|
||||||
|
*/
|
||||||
|
function onFocus(e) {
|
||||||
|
// Prevent IE from focusing the document or HTML element.
|
||||||
|
if (!isValidFocusTarget(e.target)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hadKeyboardEvent || focusTriggersKeyboardModality(e.target)) {
|
||||||
|
addFocusVisibleClass(e.target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On `blur`, remove the `focus-visible` class from the target.
|
||||||
|
*/
|
||||||
|
function onBlur(e) {
|
||||||
|
if (!isValidFocusTarget(e.target)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.target.classList.contains(focusVisibleClass)) {
|
||||||
|
// To detect a tab/window switch, we look for a blur event followed
|
||||||
|
// rapidly by a visibility change.
|
||||||
|
// If we don't see a visibility change within 100ms, it's probably a
|
||||||
|
// regular focus change.
|
||||||
|
hadFocusVisibleRecently = true;
|
||||||
|
window.clearTimeout(hadFocusVisibleRecentlyTimeout);
|
||||||
|
hadFocusVisibleRecentlyTimeout = window.setTimeout(function() {
|
||||||
|
hadFocusVisibleRecently = false;
|
||||||
|
window.clearTimeout(hadFocusVisibleRecentlyTimeout);
|
||||||
|
}, 100);
|
||||||
|
removeFocusVisibleClass(e.target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the user changes tabs, keep track of whether or not the previously
|
||||||
|
* focused element had .focus-visible.
|
||||||
|
*/
|
||||||
|
function onVisibilityChange(e) {
|
||||||
|
if (document.visibilityState === 'hidden') {
|
||||||
|
// If the tab becomes active again, the browser will handle calling focus
|
||||||
|
// on the element (Safari actually calls it twice).
|
||||||
|
// If this tab change caused a blur on an element with focus-visible,
|
||||||
|
// re-apply the class when the user switches back to the tab.
|
||||||
|
if (hadFocusVisibleRecently) {
|
||||||
|
hadKeyboardEvent = true;
|
||||||
|
}
|
||||||
|
addInitialPointerMoveListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a group of listeners to detect usage of any pointing devices.
|
||||||
|
* These listeners will be added when the polyfill first loads, and anytime
|
||||||
|
* the window is blurred, so that they are active when the window regains
|
||||||
|
* focus.
|
||||||
|
*/
|
||||||
|
function addInitialPointerMoveListeners() {
|
||||||
|
document.addEventListener('mousemove', onInitialPointerMove);
|
||||||
|
document.addEventListener('mousedown', onInitialPointerMove);
|
||||||
|
document.addEventListener('mouseup', onInitialPointerMove);
|
||||||
|
document.addEventListener('pointermove', onInitialPointerMove);
|
||||||
|
document.addEventListener('pointerdown', onInitialPointerMove);
|
||||||
|
document.addEventListener('pointerup', onInitialPointerMove);
|
||||||
|
document.addEventListener('touchmove', onInitialPointerMove);
|
||||||
|
document.addEventListener('touchstart', onInitialPointerMove);
|
||||||
|
document.addEventListener('touchend', onInitialPointerMove);
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeInitialPointerMoveListeners() {
|
||||||
|
document.removeEventListener('mousemove', onInitialPointerMove);
|
||||||
|
document.removeEventListener('mousedown', onInitialPointerMove);
|
||||||
|
document.removeEventListener('mouseup', onInitialPointerMove);
|
||||||
|
document.removeEventListener('pointermove', onInitialPointerMove);
|
||||||
|
document.removeEventListener('pointerdown', onInitialPointerMove);
|
||||||
|
document.removeEventListener('pointerup', onInitialPointerMove);
|
||||||
|
document.removeEventListener('touchmove', onInitialPointerMove);
|
||||||
|
document.removeEventListener('touchstart', onInitialPointerMove);
|
||||||
|
document.removeEventListener('touchend', onInitialPointerMove);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When the polfyill first loads, assume the user is in keyboard modality.
|
||||||
|
* If any event is received from a pointing device (e.g. mouse, pointer,
|
||||||
|
* touch), turn off keyboard modality.
|
||||||
|
* This accounts for situations where focus enters the page from the URL bar.
|
||||||
|
*/
|
||||||
|
function onInitialPointerMove(e) {
|
||||||
|
// Work around a Safari quirk that fires a mousemove on <html> whenever the
|
||||||
|
// window blurs, even if you're tabbing out of the page. ¯\_(ツ)_/¯
|
||||||
|
if (e.target.nodeName === 'HTML') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
hadKeyboardEvent = false;
|
||||||
|
removeInitialPointerMoveListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
styleElement.sheet.insertRule(rule, 0);
|
||||||
|
|
||||||
|
document.addEventListener('keydown', onKeyDown, true);
|
||||||
|
document.addEventListener('mousedown', onPointerDown, true);
|
||||||
|
document.addEventListener('pointerdown', onPointerDown, true);
|
||||||
|
document.addEventListener('touchstart', onPointerDown, true);
|
||||||
|
document.addEventListener('focus', onFocus, true);
|
||||||
|
document.addEventListener('blur', onBlur, true);
|
||||||
|
document.addEventListener('visibilitychange', onVisibilityChange, true);
|
||||||
|
addInitialPointerMoveListeners();
|
||||||
};
|
};
|
||||||
|
|
||||||
export default modality;
|
export default modality;
|
||||||
|
|||||||
@@ -7,6 +7,11 @@ import Switch from '..';
|
|||||||
const checkboxSelector = 'input[type="checkbox"]';
|
const checkboxSelector = 'input[type="checkbox"]';
|
||||||
|
|
||||||
describe('components/Switch', () => {
|
describe('components/Switch', () => {
|
||||||
|
test('accessibilityLabel is applied to native checkbox', () => {
|
||||||
|
const component = shallow(<Switch accessibilityLabel="switch" />);
|
||||||
|
expect(component.find(checkboxSelector).prop('aria-label')).toBe('switch');
|
||||||
|
});
|
||||||
|
|
||||||
describe('disabled', () => {
|
describe('disabled', () => {
|
||||||
test('when "false" a default checkbox is rendered', () => {
|
test('when "false" a default checkbox is rendered', () => {
|
||||||
const component = shallow(<Switch />);
|
const component = shallow(<Switch />);
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ class Switch extends Component<*> {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
|
accessibilityLabel,
|
||||||
activeThumbColor,
|
activeThumbColor,
|
||||||
activeTrackColor,
|
activeTrackColor,
|
||||||
disabled,
|
disabled,
|
||||||
@@ -115,6 +116,7 @@ class Switch extends Component<*> {
|
|||||||
];
|
];
|
||||||
|
|
||||||
const nativeControl = createElement('input', {
|
const nativeControl = createElement('input', {
|
||||||
|
accessibilityLabel,
|
||||||
checked: value,
|
checked: value,
|
||||||
disabled: disabled,
|
disabled: disabled,
|
||||||
onBlur: this._handleFocusState,
|
onBlur: this._handleFocusState,
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ const TextPropTypes = {
|
|||||||
accessible: bool,
|
accessible: bool,
|
||||||
children: any,
|
children: any,
|
||||||
importantForAccessibility: oneOf(['auto', 'no', 'no-hide-descendants', 'yes']),
|
importantForAccessibility: oneOf(['auto', 'no', 'no-hide-descendants', 'yes']),
|
||||||
|
nativeID: string,
|
||||||
numberOfLines: number,
|
numberOfLines: number,
|
||||||
onBlur: func,
|
onBlur: func,
|
||||||
onContextMenu: func,
|
onContextMenu: func,
|
||||||
|
|||||||
@@ -7,12 +7,14 @@
|
|||||||
* @flow
|
* @flow
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import ColorPropType from '../ColorPropType';
|
||||||
import TextStylePropTypes from '../Text/TextStylePropTypes';
|
import TextStylePropTypes from '../Text/TextStylePropTypes';
|
||||||
import { oneOf } from 'prop-types';
|
import { oneOf } from 'prop-types';
|
||||||
|
|
||||||
const TextInputStylePropTypes = {
|
const TextInputStylePropTypes = {
|
||||||
...TextStylePropTypes,
|
...TextStylePropTypes,
|
||||||
/* @platform web */
|
/* @platform web */
|
||||||
|
caretColor: ColorPropType,
|
||||||
resize: oneOf(['none', 'vertical', 'horizontal', 'both'])
|
resize: oneOf(['none', 'vertical', 'horizontal', 'both'])
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ class TextInput extends Component<*> {
|
|||||||
clearTextOnFocus: bool,
|
clearTextOnFocus: bool,
|
||||||
defaultValue: string,
|
defaultValue: string,
|
||||||
editable: bool,
|
editable: bool,
|
||||||
|
inputAccessoryViewID: string,
|
||||||
keyboardType: oneOf([
|
keyboardType: oneOf([
|
||||||
'default',
|
'default',
|
||||||
'email-address',
|
'email-address',
|
||||||
@@ -142,7 +143,7 @@ class TextInput extends Component<*> {
|
|||||||
editable: true,
|
editable: true,
|
||||||
keyboardType: 'default',
|
keyboardType: 'default',
|
||||||
multiline: false,
|
multiline: false,
|
||||||
numberOfLines: 2,
|
numberOfLines: 1,
|
||||||
secureTextEntry: false,
|
secureTextEntry: false,
|
||||||
style: emptyObject
|
style: emptyObject
|
||||||
};
|
};
|
||||||
@@ -190,22 +191,34 @@ class TextInput extends Component<*> {
|
|||||||
selectTextOnFocus,
|
selectTextOnFocus,
|
||||||
spellCheck,
|
spellCheck,
|
||||||
/* react-native compat */
|
/* react-native compat */
|
||||||
|
accessibilityViewIsModal,
|
||||||
|
allowFontScaling,
|
||||||
caretHidden,
|
caretHidden,
|
||||||
clearButtonMode,
|
clearButtonMode,
|
||||||
dataDetectorTypes,
|
dataDetectorTypes,
|
||||||
disableFullscreenUI,
|
disableFullscreenUI,
|
||||||
enablesReturnKeyAutomatically,
|
enablesReturnKeyAutomatically,
|
||||||
|
hitSlop,
|
||||||
inlineImageLeft,
|
inlineImageLeft,
|
||||||
inlineImagePadding,
|
inlineImagePadding,
|
||||||
|
inputAccessoryViewID,
|
||||||
keyboardAppearance,
|
keyboardAppearance,
|
||||||
|
needsOffscreenAlphaCompositing,
|
||||||
|
onAccessibilityTap,
|
||||||
onContentSizeChange,
|
onContentSizeChange,
|
||||||
onEndEditing,
|
onEndEditing,
|
||||||
|
onMagicTap,
|
||||||
onScroll,
|
onScroll,
|
||||||
|
removeClippedSubviews,
|
||||||
|
renderToHardwareTextureAndroid,
|
||||||
returnKeyLabel,
|
returnKeyLabel,
|
||||||
returnKeyType,
|
returnKeyType,
|
||||||
|
scrollEnabled,
|
||||||
selectionColor,
|
selectionColor,
|
||||||
selectionState,
|
selectionState,
|
||||||
|
shouldRasterizeIOS,
|
||||||
textBreakStrategy,
|
textBreakStrategy,
|
||||||
|
textContentType,
|
||||||
underlineColorAndroid,
|
underlineColorAndroid,
|
||||||
/* eslint-enable */
|
/* eslint-enable */
|
||||||
...otherProps
|
...otherProps
|
||||||
|
|||||||
@@ -696,9 +696,16 @@ const TouchableMixin = {
|
|||||||
const touch = TouchEventUtils.extractSingleTouch(e.nativeEvent);
|
const touch = TouchEventUtils.extractSingleTouch(e.nativeEvent);
|
||||||
const pageX = touch && touch.pageX;
|
const pageX = touch && touch.pageX;
|
||||||
const pageY = touch && touch.pageY;
|
const pageY = touch && touch.pageY;
|
||||||
const locationX = touch && touch.locationX;
|
this.pressInLocation = {
|
||||||
const locationY = touch && touch.locationY;
|
pageX,
|
||||||
this.pressInLocation = { pageX, pageY, locationX, locationY };
|
pageY,
|
||||||
|
get locationX() {
|
||||||
|
return touch && touch.locationX;
|
||||||
|
},
|
||||||
|
get locationY() {
|
||||||
|
return touch && touch.locationY;
|
||||||
|
}
|
||||||
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
_getDistanceBetweenPoints: function(aX: number, aY: number, bX: number, bY: number) {
|
_getDistanceBetweenPoints: function(aX: number, aY: number, bX: number, bY: number) {
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
import UIManager from '..';
|
import UIManager from '..';
|
||||||
|
|
||||||
const createStyledNode = (style = {}) => {
|
const createStyledNode = (name = 'div', style = {}) => {
|
||||||
const root = document.createElement('div');
|
const root = document.createElement(name);
|
||||||
Object.keys(style).forEach(prop => {
|
Object.keys(style).forEach(prop => {
|
||||||
root.style[prop] = style[prop];
|
root.style[prop] = style[prop];
|
||||||
});
|
});
|
||||||
@@ -18,6 +18,29 @@ const componentStub = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
describe('apis/UIManager', () => {
|
describe('apis/UIManager', () => {
|
||||||
|
describe('focus', () => {
|
||||||
|
test('sets tabIndex="-1" on elements not programmatically focusable by default', () => {
|
||||||
|
const node = createStyledNode();
|
||||||
|
UIManager.focus(node);
|
||||||
|
expect(node.getAttribute('tabIndex')).toEqual('-1');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('doesn\'t set tabIndex="-1" on elements with an existing tabIndex', () => {
|
||||||
|
const node = createStyledNode();
|
||||||
|
node.tabIndex = 0;
|
||||||
|
UIManager.focus(node);
|
||||||
|
expect(node.getAttribute('tabIndex')).toEqual('0');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('doesn\'t set tabIndex="-1" on elements focusable by default', () => {
|
||||||
|
['a', 'input', 'select', 'textarea'].forEach(name => {
|
||||||
|
const node = createStyledNode(name);
|
||||||
|
UIManager.focus(node);
|
||||||
|
expect(node.getAttribute('tabIndex')).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('updateView', () => {
|
describe('updateView', () => {
|
||||||
test('supports className alias for class', () => {
|
test('supports className alias for class', () => {
|
||||||
const node = createStyledNode();
|
const node = createStyledNode();
|
||||||
@@ -27,7 +50,7 @@ describe('apis/UIManager', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('adds correct DOM styles to existing style', () => {
|
test('adds correct DOM styles to existing style', () => {
|
||||||
const node = createStyledNode({ color: 'red' });
|
const node = createStyledNode('div', { color: 'red' });
|
||||||
const props = { style: { marginTop: 0, marginBottom: 0, opacity: 0 } };
|
const props = { style: { marginTop: 0, marginBottom: 0, opacity: 0 } };
|
||||||
UIManager.updateView(node, props, componentStub);
|
UIManager.updateView(node, props, componentStub);
|
||||||
expect(node.getAttribute('style')).toEqual(
|
expect(node.getAttribute('style')).toEqual(
|
||||||
@@ -36,7 +59,7 @@ describe('apis/UIManager', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('replaces input and textarea text', () => {
|
test('replaces input and textarea text', () => {
|
||||||
const node = createStyledNode();
|
const node = createStyledNode('textarea');
|
||||||
node.value = 'initial';
|
node.value = 'initial';
|
||||||
const textProp = { text: 'expected-text' };
|
const textProp = { text: 'expected-text' };
|
||||||
const valueProp = { value: 'expected-value' };
|
const valueProp = { value: 'expected-value' };
|
||||||
|
|||||||
@@ -37,6 +37,13 @@ const measureLayout = (node, relativeToNativeNode, callback) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const focusableElements = {
|
||||||
|
A: true,
|
||||||
|
INPUT: true,
|
||||||
|
SELECT: true,
|
||||||
|
TEXTAREA: true
|
||||||
|
};
|
||||||
|
|
||||||
const UIManager = {
|
const UIManager = {
|
||||||
blur(node) {
|
blur(node) {
|
||||||
try {
|
try {
|
||||||
@@ -46,6 +53,13 @@ const UIManager = {
|
|||||||
|
|
||||||
focus(node) {
|
focus(node) {
|
||||||
try {
|
try {
|
||||||
|
const name = node.nodeName;
|
||||||
|
// A tabIndex of -1 allows element to be programmatically focused but
|
||||||
|
// prevents keyboard focus, so we don't want to set the value on elements
|
||||||
|
// that support keyboard focus by default.
|
||||||
|
if (node.getAttribute('tabIndex') == null && focusableElements[name] == null) {
|
||||||
|
node.setAttribute('tabIndex', '-1');
|
||||||
|
}
|
||||||
node.focus();
|
node.focus();
|
||||||
} catch (err) {}
|
} catch (err) {}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ export type ViewProps = {
|
|||||||
children?: any,
|
children?: any,
|
||||||
hitSlop?: EdgeInsetsProp,
|
hitSlop?: EdgeInsetsProp,
|
||||||
importantForAccessibility?: 'auto' | 'yes' | 'no' | 'no-hide-descendants',
|
importantForAccessibility?: 'auto' | 'yes' | 'no' | 'no-hide-descendants',
|
||||||
|
nativeID?: string,
|
||||||
onBlur?: Function,
|
onBlur?: Function,
|
||||||
onClick?: Function,
|
onClick?: Function,
|
||||||
onClickCapture?: Function,
|
onClickCapture?: Function,
|
||||||
@@ -87,6 +88,7 @@ const ViewPropTypes = {
|
|||||||
children: any,
|
children: any,
|
||||||
hitSlop: EdgeInsetsPropType,
|
hitSlop: EdgeInsetsPropType,
|
||||||
importantForAccessibility: oneOf(['auto', 'no', 'no-hide-descendants', 'yes']),
|
importantForAccessibility: oneOf(['auto', 'no', 'no-hide-descendants', 'yes']),
|
||||||
|
nativeID: string,
|
||||||
onBlur: func,
|
onBlur: func,
|
||||||
onClick: func,
|
onClick: func,
|
||||||
onClickCapture: func,
|
onClickCapture: func,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ const whitelist = {
|
|||||||
children: true,
|
children: true,
|
||||||
disabled: true,
|
disabled: true,
|
||||||
importantForAccessibility: true,
|
importantForAccessibility: true,
|
||||||
|
nativeID: true,
|
||||||
onBlur: true,
|
onBlur: true,
|
||||||
onContextMenu: true,
|
onContextMenu: true,
|
||||||
onFocus: true,
|
onFocus: true,
|
||||||
|
|||||||
@@ -36,24 +36,38 @@ describe('modules/createElement', () => {
|
|||||||
expect(component.find('div').length).toBe(1);
|
expect(component.find('div').length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
[{ disabled: true }, { disabled: false }].forEach(({ disabled }) => {
|
const testRole = ({ accessibilityRole, disabled }) => {
|
||||||
describe(`value is "button" and disabled is "${disabled}"`, () => {
|
[{ key: 'Enter', which: 13 }, { key: 'Space', which: 32 }].forEach(({ key, which }) => {
|
||||||
[{ name: 'Enter', which: 13 }, { name: 'Space', which: 32 }].forEach(({ name, which }) => {
|
test(`"onClick" is ${disabled ? 'not ' : ''}called when "${key}" key is pressed`, () => {
|
||||||
test(`"onClick" is ${disabled ? 'not ' : ''}called when "${name}" is pressed`, () => {
|
const onClick = jest.fn();
|
||||||
const onClick = jest.fn();
|
const component = shallow(
|
||||||
const component = shallow(
|
createElement('span', { accessibilityRole, disabled, onClick })
|
||||||
createElement('span', { accessibilityRole: 'button', disabled, onClick })
|
);
|
||||||
);
|
component.find('span').simulate('keyPress', {
|
||||||
component.find('span').simulate('keyPress', {
|
isDefaultPrevented() {},
|
||||||
isDefaultPrevented() {},
|
nativeEvent: {},
|
||||||
nativeEvent: {},
|
preventDefault() {},
|
||||||
preventDefault() {},
|
which
|
||||||
which
|
|
||||||
});
|
|
||||||
expect(onClick).toHaveBeenCalledTimes(disabled ? 0 : 1);
|
|
||||||
});
|
});
|
||||||
|
expect(onClick).toHaveBeenCalledTimes(disabled ? 0 : 1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('value is "button" and disabled is "true"', () => {
|
||||||
|
testRole({ accessibilityRole: 'button', disabled: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('value is "button" and disabled is "false"', () => {
|
||||||
|
testRole({ accessibilityRole: 'button', disabled: false });
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('value is "menuitem" and disabled is "true"', () => {
|
||||||
|
testRole({ accessibilityRole: 'menuitem', disabled: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('value is "menuitem" and disabled is "false"', () => {
|
||||||
|
testRole({ accessibilityRole: 'menuitem', disabled: false });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,14 +9,12 @@
|
|||||||
|
|
||||||
import AccessibilityUtil from '../../modules/AccessibilityUtil';
|
import AccessibilityUtil from '../../modules/AccessibilityUtil';
|
||||||
import createDOMProps from '../../modules/createDOMProps';
|
import createDOMProps from '../../modules/createDOMProps';
|
||||||
|
import { injectEventPluginsByName } from 'react-dom/unstable-native-dependencies';
|
||||||
import normalizeNativeEvent from '../../modules/normalizeNativeEvent';
|
import normalizeNativeEvent from '../../modules/normalizeNativeEvent';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
|
||||||
import ResponderEventPlugin from '../../modules/ResponderEventPlugin';
|
import ResponderEventPlugin from '../../modules/ResponderEventPlugin';
|
||||||
|
|
||||||
const { EventPluginHub } = ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
|
injectEventPluginsByName({
|
||||||
|
|
||||||
EventPluginHub.injection.injectEventPluginsByName({
|
|
||||||
ResponderEventPlugin
|
ResponderEventPlugin
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -48,7 +46,7 @@ const eventHandlerNames = {
|
|||||||
const adjustProps = domProps => {
|
const adjustProps = domProps => {
|
||||||
const { onClick, onResponderRelease, role } = domProps;
|
const { onClick, onResponderRelease, role } = domProps;
|
||||||
|
|
||||||
const isButtonRole = role === 'button';
|
const isButtonLikeRole = AccessibilityUtil.buttonLikeRoles[role];
|
||||||
const isDisabled = AccessibilityUtil.isDisabled(domProps);
|
const isDisabled = AccessibilityUtil.isDisabled(domProps);
|
||||||
const isLinkRole = role === 'link';
|
const isLinkRole = role === 'link';
|
||||||
|
|
||||||
@@ -56,7 +54,7 @@ const adjustProps = domProps => {
|
|||||||
const prop = domProps[propName];
|
const prop = domProps[propName];
|
||||||
const isEventHandler = typeof prop === 'function' && eventHandlerNames[propName];
|
const isEventHandler = typeof prop === 'function' && eventHandlerNames[propName];
|
||||||
if (isEventHandler) {
|
if (isEventHandler) {
|
||||||
if (isButtonRole && isDisabled) {
|
if (isButtonLikeRole && isDisabled) {
|
||||||
domProps[propName] = undefined;
|
domProps[propName] = undefined;
|
||||||
} else {
|
} else {
|
||||||
// TODO: move this out of the render path
|
// TODO: move this out of the render path
|
||||||
@@ -80,8 +78,8 @@ const adjustProps = domProps => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Button role should trigger 'onClick' if SPACE or ENTER keys are pressed.
|
// Button-like roles should trigger 'onClick' if SPACE or ENTER keys are pressed.
|
||||||
if (isButtonRole && !isDisabled) {
|
if (isButtonLikeRole && !isDisabled) {
|
||||||
domProps.onKeyPress = function(e) {
|
domProps.onKeyPress = function(e) {
|
||||||
if (!e.isDefaultPrevented() && (e.which === 13 || e.which === 32)) {
|
if (!e.isDefaultPrevented() && (e.which === 13 || e.which === 32)) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|||||||
@@ -8,4 +8,15 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { findDOMNode } from 'react-dom';
|
import { findDOMNode } from 'react-dom';
|
||||||
export default findDOMNode;
|
|
||||||
|
const findNodeHandle = component => {
|
||||||
|
let node;
|
||||||
|
|
||||||
|
try {
|
||||||
|
node = findDOMNode(component);
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
return node;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default findNodeHandle;
|
||||||
|
|||||||
19
packages/react-native-web/src/modules/AccessibilityUtil/buttonLikeRoles.js
vendored
Normal file
19
packages/react-native-web/src/modules/AccessibilityUtil/buttonLikeRoles.js
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2017-present, Nicolas Gallagher.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*
|
||||||
|
* @flow
|
||||||
|
*/
|
||||||
|
|
||||||
|
const buttonLikeRoles: { [string]: boolean } = {
|
||||||
|
// ARIA button behaves like native 'button' element
|
||||||
|
button: true,
|
||||||
|
// ARIA menuitem responds to Enter/Space like a button. Spec requires AT to
|
||||||
|
// ignore ARIA roles of any children.
|
||||||
|
// https://www.w3.org/WAI/GL/wiki/Using_ARIA_menus
|
||||||
|
menuitem: true
|
||||||
|
};
|
||||||
|
|
||||||
|
export default buttonLikeRoles;
|
||||||
@@ -7,11 +7,13 @@
|
|||||||
* @flow
|
* @flow
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import buttonLikeRoles from './buttonLikeRoles';
|
||||||
import isDisabled from './isDisabled';
|
import isDisabled from './isDisabled';
|
||||||
import propsToAccessibilityComponent from './propsToAccessibilityComponent';
|
import propsToAccessibilityComponent from './propsToAccessibilityComponent';
|
||||||
import propsToAriaRole from './propsToAriaRole';
|
import propsToAriaRole from './propsToAriaRole';
|
||||||
|
|
||||||
const AccessibilityUtil = {
|
const AccessibilityUtil = {
|
||||||
|
buttonLikeRoles,
|
||||||
isDisabled,
|
isDisabled,
|
||||||
propsToAccessibilityComponent,
|
propsToAccessibilityComponent,
|
||||||
propsToAriaRole
|
propsToAriaRole
|
||||||
|
|||||||
@@ -52,12 +52,14 @@ const ImageLoader = {
|
|||||||
image.onerror = onError;
|
image.onerror = onError;
|
||||||
image.onload = e => {
|
image.onload = e => {
|
||||||
// avoid blocking the main thread
|
// avoid blocking the main thread
|
||||||
|
const onDecode = () => onLoad(e);
|
||||||
if (typeof image.decode === 'function') {
|
if (typeof image.decode === 'function') {
|
||||||
image.decode().then(() => { onLoad(e) });
|
// Safari currently throws exceptions when decoding svgs.
|
||||||
|
// We want to catch that error and allow the load handler
|
||||||
|
// to be forwarded to the onLoad handler in this case
|
||||||
|
image.decode().then(onDecode, onDecode);
|
||||||
} else {
|
} else {
|
||||||
setTimeout(() => {
|
setTimeout(onDecode, 0);
|
||||||
onLoad(e);
|
|
||||||
}, 0);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
image.src = uri;
|
image.src = uri;
|
||||||
|
|||||||
@@ -100,12 +100,14 @@ const NativeMethodsMixin = {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const node = findNodeHandle(this);
|
const node = findNodeHandle(this);
|
||||||
// Next state is determined by comparison to existing state (in the DOM).
|
if (node) {
|
||||||
// Existing state has already gone through i18n transform
|
// Next state is determined by comparison to existing state (in the DOM).
|
||||||
const domProps = createDOMProps(null, nativeProps, style =>
|
// Existing state has already gone through i18n transform
|
||||||
styleResolver.resolveWithNode(style, node)
|
const domProps = createDOMProps(null, nativeProps, style =>
|
||||||
);
|
styleResolver.resolveWithNode(style, node)
|
||||||
UIManager.updateView(node, domProps, this);
|
);
|
||||||
|
UIManager.updateView(node, domProps, this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,9 @@ if (!ResponderEventPlugin.eventTypes.responderMove.dependencies) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let lastActiveTouchTimestamp = null;
|
let lastActiveTouchTimestamp = null;
|
||||||
|
// The length of time after a touch that we ignore the browser's emulated mouse events
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/API/Touch_events/Using_Touch_Events
|
||||||
|
const EMULATED_MOUSE_THERSHOLD_MS = 1000;
|
||||||
|
|
||||||
const originalExtractEvents = ResponderEventPlugin.extractEvents;
|
const originalExtractEvents = ResponderEventPlugin.extractEvents;
|
||||||
ResponderEventPlugin.extractEvents = (topLevelType, targetInst, nativeEvent, nativeEventTarget) => {
|
ResponderEventPlugin.extractEvents = (topLevelType, targetInst, nativeEvent, nativeEventTarget) => {
|
||||||
@@ -55,7 +58,7 @@ ResponderEventPlugin.extractEvents = (topLevelType, targetInst, nativeEvent, nat
|
|||||||
lastActiveTouchTimestamp = Date.now();
|
lastActiveTouchTimestamp = Date.now();
|
||||||
} else if (lastActiveTouchTimestamp && eventType.indexOf('mouse') > -1) {
|
} else if (lastActiveTouchTimestamp && eventType.indexOf('mouse') > -1) {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
shouldSkipMouseAfterTouch = now - lastActiveTouchTimestamp < 250;
|
shouldSkipMouseAfterTouch = now - lastActiveTouchTimestamp < EMULATED_MOUSE_THERSHOLD_MS;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -63,14 +63,15 @@ const observe = instance => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const unobserve = instance => {
|
const unobserve = instance => {
|
||||||
delete registry[instance._layoutId];
|
|
||||||
if (resizeObserver) {
|
if (resizeObserver) {
|
||||||
const node = findNodeHandle(instance);
|
const node = findNodeHandle(instance);
|
||||||
if (node) {
|
if (node) {
|
||||||
|
delete registry[node._layoutId];
|
||||||
delete node._layoutId;
|
delete node._layoutId;
|
||||||
resizeObserver.unobserve(node);
|
resizeObserver.unobserve(node);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
delete registry[instance._layoutId];
|
||||||
delete instance._layoutId;
|
delete instance._layoutId;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -97,7 +98,9 @@ const applyLayout = Component => {
|
|||||||
function componentDidMount() {
|
function componentDidMount() {
|
||||||
this._layoutState = emptyObject;
|
this._layoutState = emptyObject;
|
||||||
this._isMounted = true;
|
this._isMounted = true;
|
||||||
observe(this);
|
if (this.props.onLayout) {
|
||||||
|
observe(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -116,7 +119,9 @@ const applyLayout = Component => {
|
|||||||
componentWillUnmount,
|
componentWillUnmount,
|
||||||
function componentWillUnmount() {
|
function componentWillUnmount() {
|
||||||
this._isMounted = false;
|
this._isMounted = false;
|
||||||
unobserve(this);
|
if (this.props.onLayout) {
|
||||||
|
unobserve(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -65,9 +65,7 @@ describe('modules/createDOMProps', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('"accessibilityRole" of "button"', () => {
|
const testFocusableRole = accessibilityRole => {
|
||||||
const accessibilityRole = 'button';
|
|
||||||
|
|
||||||
test('default case', () => {
|
test('default case', () => {
|
||||||
expect(createProps({ accessibilityRole })).toEqual(
|
expect(createProps({ accessibilityRole })).toEqual(
|
||||||
expect.objectContaining({ 'data-focusable': true, tabIndex: '0' })
|
expect.objectContaining({ 'data-focusable': true, tabIndex: '0' })
|
||||||
@@ -118,6 +116,14 @@ describe('modules/createDOMProps', () => {
|
|||||||
})
|
})
|
||||||
).not.toEqual(expect.objectContaining({ 'data-focusable': true, tabIndex: '0' }));
|
).not.toEqual(expect.objectContaining({ 'data-focusable': true, tabIndex: '0' }));
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('"accessibilityRole" of "button"', () => {
|
||||||
|
testFocusableRole('button');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('"accessibilityRole" of "menuitem"', () => {
|
||||||
|
testFocusableRole('menuitem');
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('with unfocusable accessibilityRole', () => {
|
describe('with unfocusable accessibilityRole', () => {
|
||||||
@@ -177,6 +183,12 @@ describe('modules/createDOMProps', () => {
|
|||||||
expect(props['aria-hidden']).toEqual(true);
|
expect(props['aria-hidden']).toEqual(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('prop "nativeID" becomes "id"', () => {
|
||||||
|
const nativeID = 'Example.nativeID';
|
||||||
|
const props = createProps({ nativeID });
|
||||||
|
expect(props.id).toEqual(nativeID);
|
||||||
|
});
|
||||||
|
|
||||||
test('prop "testID" becomes "data-testid"', () => {
|
test('prop "testID" becomes "data-testid"', () => {
|
||||||
const testID = 'Example.testID';
|
const testID = 'Example.testID';
|
||||||
const props = createProps({ testID });
|
const props = createProps({ testID });
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ const createDOMProps = (component, props, styleResolver) => {
|
|||||||
accessibilityLabel,
|
accessibilityLabel,
|
||||||
accessibilityLiveRegion,
|
accessibilityLiveRegion,
|
||||||
importantForAccessibility,
|
importantForAccessibility,
|
||||||
|
nativeID,
|
||||||
placeholderTextColor,
|
placeholderTextColor,
|
||||||
pointerEvents,
|
pointerEvents,
|
||||||
style: providedStyle,
|
style: providedStyle,
|
||||||
@@ -131,7 +132,7 @@ const createDOMProps = (component, props, styleResolver) => {
|
|||||||
} else {
|
} else {
|
||||||
domProps['data-focusable'] = true;
|
domProps['data-focusable'] = true;
|
||||||
}
|
}
|
||||||
} else if (role === 'button' || role === 'textbox') {
|
} else if (AccessibilityUtil.buttonLikeRoles[role] || role === 'textbox') {
|
||||||
if (accessible !== false && focusable) {
|
if (accessible !== false && focusable) {
|
||||||
domProps['data-focusable'] = true;
|
domProps['data-focusable'] = true;
|
||||||
domProps.tabIndex = '0';
|
domProps.tabIndex = '0';
|
||||||
@@ -164,10 +165,15 @@ const createDOMProps = (component, props, styleResolver) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// OTHER
|
// OTHER
|
||||||
|
// Native element ID
|
||||||
|
if (nativeID && nativeID.constructor === String) {
|
||||||
|
domProps.id = nativeID;
|
||||||
|
}
|
||||||
// Link security and automation test ids
|
// Link security and automation test ids
|
||||||
if (component === 'a' && domProps.target === '_blank') {
|
if (component === 'a' && domProps.target === '_blank') {
|
||||||
domProps.rel = `${domProps.rel || ''} noopener noreferrer`;
|
domProps.rel = `${domProps.rel || ''} noopener noreferrer`;
|
||||||
}
|
}
|
||||||
|
// Automated test IDs
|
||||||
if (testID && testID.constructor === String) {
|
if (testID && testID.constructor === String) {
|
||||||
domProps['data-testid'] = testID;
|
domProps['data-testid'] = testID;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ function normalizeTouchEvent(nativeEvent) {
|
|||||||
typeof nativeEvent.stopPropagation === 'function'
|
typeof nativeEvent.stopPropagation === 'function'
|
||||||
? nativeEvent.stopPropagation.bind(nativeEvent)
|
? nativeEvent.stopPropagation.bind(nativeEvent)
|
||||||
: emptyFunction;
|
: emptyFunction;
|
||||||
|
const singleChangedTouch = changedTouches[0];
|
||||||
|
|
||||||
const event = {
|
const event = {
|
||||||
_normalized: true,
|
_normalized: true,
|
||||||
@@ -85,11 +86,15 @@ function normalizeTouchEvent(nativeEvent) {
|
|||||||
cancelable: nativeEvent.cancelable,
|
cancelable: nativeEvent.cancelable,
|
||||||
changedTouches,
|
changedTouches,
|
||||||
defaultPrevented: nativeEvent.defaultPrevented,
|
defaultPrevented: nativeEvent.defaultPrevented,
|
||||||
identifier: undefined,
|
identifier: singleChangedTouch ? singleChangedTouch.identifier : undefined,
|
||||||
locationX: undefined,
|
get locationX() {
|
||||||
locationY: undefined,
|
return singleChangedTouch ? singleChangedTouch.locationX : undefined;
|
||||||
pageX: nativeEvent.pageX,
|
},
|
||||||
pageY: nativeEvent.pageY,
|
get locationY() {
|
||||||
|
return singleChangedTouch ? singleChangedTouch.locationY : undefined;
|
||||||
|
},
|
||||||
|
pageX: singleChangedTouch ? singleChangedTouch.pageX : nativeEvent.pageX,
|
||||||
|
pageY: singleChangedTouch ? singleChangedTouch.pageY : nativeEvent.pageY,
|
||||||
preventDefault,
|
preventDefault,
|
||||||
stopImmediatePropagation,
|
stopImmediatePropagation,
|
||||||
stopPropagation,
|
stopPropagation,
|
||||||
@@ -102,14 +107,6 @@ function normalizeTouchEvent(nativeEvent) {
|
|||||||
which: nativeEvent.which
|
which: nativeEvent.which
|
||||||
};
|
};
|
||||||
|
|
||||||
if (changedTouches[0]) {
|
|
||||||
event.identifier = changedTouches[0].identifier;
|
|
||||||
event.pageX = changedTouches[0].pageX;
|
|
||||||
event.pageY = changedTouches[0].pageY;
|
|
||||||
event.locationX = changedTouches[0].locationX;
|
|
||||||
event.locationY = changedTouches[0].locationY;
|
|
||||||
}
|
|
||||||
|
|
||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,8 +161,12 @@ function normalizeMouseEvent(nativeEvent) {
|
|||||||
changedTouches: touches,
|
changedTouches: touches,
|
||||||
defaultPrevented: nativeEvent.defaultPrevented,
|
defaultPrevented: nativeEvent.defaultPrevented,
|
||||||
identifier: touches[0].identifier,
|
identifier: touches[0].identifier,
|
||||||
locationX: touches[0].locationX,
|
get locationX() {
|
||||||
locationY: touches[0].locationY,
|
return touches[0].locationX;
|
||||||
|
},
|
||||||
|
get locationY() {
|
||||||
|
return touches[0].locationY;
|
||||||
|
},
|
||||||
pageX: nativeEvent.pageX,
|
pageX: nativeEvent.pageX,
|
||||||
pageY: nativeEvent.pageY,
|
pageY: nativeEvent.pageY,
|
||||||
preventDefault,
|
preventDefault,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import UIManager from '../../../exports/UIManager';
|
import UIManager from '../../../exports/UIManager';
|
||||||
|
|
||||||
const __DEV__ = process.env.NODE !== 'production';
|
const __DEV__ = process.env.NODE_ENV !== 'production';
|
||||||
const { checkPropTypes } = PropTypes;
|
const { checkPropTypes } = PropTypes;
|
||||||
|
|
||||||
const Types = {
|
const Types = {
|
||||||
|
|||||||
@@ -410,7 +410,7 @@ class VirtualizedList extends React.PureComponent<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
disableVirtualization: false,
|
disableVirtualization: process.env.NODE_ENV === 'test',
|
||||||
horizontal: false,
|
horizontal: false,
|
||||||
initialNumToRender: 10,
|
initialNumToRender: 10,
|
||||||
keyExtractor: (item: Item, index: number) => {
|
keyExtractor: (item: Item, index: number) => {
|
||||||
@@ -1562,6 +1562,7 @@ class CellRenderer extends React.Component<
|
|||||||
renderItem: renderItemType,
|
renderItem: renderItemType,
|
||||||
},
|
},
|
||||||
prevCellKey: ?string,
|
prevCellKey: ?string,
|
||||||
|
style: ?DangerouslyImpreciseStyleProp,
|
||||||
},
|
},
|
||||||
$FlowFixMeState,
|
$FlowFixMeState,
|
||||||
> {
|
> {
|
||||||
@@ -1630,6 +1631,7 @@ class CellRenderer extends React.Component<
|
|||||||
index,
|
index,
|
||||||
inversionStyle,
|
inversionStyle,
|
||||||
parentProps,
|
parentProps,
|
||||||
|
style,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const {renderItem, getItemLayout} = parentProps;
|
const {renderItem, getItemLayout} = parentProps;
|
||||||
invariant(renderItem, 'no renderItem!');
|
invariant(renderItem, 'no renderItem!');
|
||||||
@@ -1649,9 +1651,9 @@ class CellRenderer extends React.Component<
|
|||||||
);
|
);
|
||||||
const cellStyle = inversionStyle
|
const cellStyle = inversionStyle
|
||||||
? horizontal
|
? horizontal
|
||||||
? [{flexDirection: 'row-reverse'}, inversionStyle]
|
? [styles.rowReverse, inversionStyle, style]
|
||||||
: [{flexDirection: 'column-reverse'}, inversionStyle]
|
: [styles.columnReverse, inversionStyle, style]
|
||||||
: horizontal ? [{flexDirection: 'row'}, inversionStyle] : inversionStyle;
|
: horizontal ? [styles.row, inversionStyle, style] : [inversionStyle, style];
|
||||||
if (!CellRendererComponent) {
|
if (!CellRendererComponent) {
|
||||||
return (
|
return (
|
||||||
<View style={cellStyle} onLayout={onLayout}>
|
<View style={cellStyle} onLayout={onLayout}>
|
||||||
@@ -1702,6 +1704,15 @@ const styles = StyleSheet.create({
|
|||||||
horizontallyInverted: {
|
horizontallyInverted: {
|
||||||
transform: [{scaleX: -1}],
|
transform: [{scaleX: -1}],
|
||||||
},
|
},
|
||||||
|
row: {
|
||||||
|
flexDirection: 'row'
|
||||||
|
},
|
||||||
|
rowReverse: {
|
||||||
|
flexDirection: 'row-reverse'
|
||||||
|
},
|
||||||
|
columnReverse: {
|
||||||
|
flexDirection: 'column-reverse'
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export default VirtualizedList;
|
export default VirtualizedList;
|
||||||
|
|||||||
@@ -1,353 +0,0 @@
|
|||||||
# Getting started
|
|
||||||
|
|
||||||
This guide will help you render components and applications with React Native
|
|
||||||
for Web.
|
|
||||||
|
|
||||||
Your application may need to polyfill `Promise`, `Object.assign`, `Array.from`,
|
|
||||||
and [`ResizeObserver`](https://github.com/que-etc/resize-observer-polyfill) as
|
|
||||||
necessary for your desired browser support.
|
|
||||||
|
|
||||||
If you're not familiar with setting up a new React web project, please follow
|
|
||||||
the recommendations in the [React documentation](https://reactjs.org/).
|
|
||||||
|
|
||||||
## Install
|
|
||||||
|
|
||||||
```
|
|
||||||
yarn add react react-dom react-native-web
|
|
||||||
```
|
|
||||||
|
|
||||||
And if you need to use `ART`:
|
|
||||||
|
|
||||||
```
|
|
||||||
yarn add react-art
|
|
||||||
```
|
|
||||||
|
|
||||||
## Starter kits
|
|
||||||
|
|
||||||
Web: [create-react-app](https://github.com/facebookincubator/create-react-app)
|
|
||||||
includes built-in support for aliasing `react-native-web` to `react-native`.
|
|
||||||
|
|
||||||
```
|
|
||||||
create-react-app my-app
|
|
||||||
```
|
|
||||||
|
|
||||||
Multi-platform: [create-react-native-app](https://github.com/react-community/create-react-native-app)
|
|
||||||
includes experimental support for Web.
|
|
||||||
|
|
||||||
```
|
|
||||||
create-react-native-app my-app --with-web-support
|
|
||||||
```
|
|
||||||
|
|
||||||
## Configuring a module bundler
|
|
||||||
|
|
||||||
If you have a custom setup, you may choose to configure your module bundler to
|
|
||||||
alias the package to `react-native`.
|
|
||||||
|
|
||||||
For example, modify your [webpack](https://github.com/webpack/webpack)
|
|
||||||
configuration as follows:
|
|
||||||
|
|
||||||
```js
|
|
||||||
// webpack.config.js
|
|
||||||
module.exports = {
|
|
||||||
// ...the rest of your config
|
|
||||||
|
|
||||||
resolve: {
|
|
||||||
alias: {
|
|
||||||
'react-native$': 'react-native-web'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Now you can create your components and applications with the React Native API.
|
|
||||||
|
|
||||||
## Configuring babel
|
|
||||||
|
|
||||||
If you need to do the aliasing with Babel you can use
|
|
||||||
[babel-plugin-module-resolver](https://www.npmjs.com/package/babel-plugin-module-resolver)
|
|
||||||
|
|
||||||
```
|
|
||||||
{
|
|
||||||
"plugins": [
|
|
||||||
["module-resolver", {
|
|
||||||
"alias": {
|
|
||||||
"^react-native$": "react-native-web"
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Client-side rendering
|
|
||||||
|
|
||||||
Render apps using `AppRegistry`:
|
|
||||||
|
|
||||||
```js
|
|
||||||
// index.web.js
|
|
||||||
|
|
||||||
import App from './src/App';
|
|
||||||
import React from 'react';
|
|
||||||
import { AppRegistry } from 'react-native';
|
|
||||||
|
|
||||||
// register the app
|
|
||||||
AppRegistry.registerComponent('App', () => App);
|
|
||||||
|
|
||||||
AppRegistry.runApplication('App', {
|
|
||||||
initialProps: {},
|
|
||||||
rootTag: document.getElementById('react-app')
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
Or render individual components:
|
|
||||||
|
|
||||||
```js
|
|
||||||
import AppHeader from './src/AppHeader';
|
|
||||||
import React from 'react';
|
|
||||||
import { render } from 'react-native';
|
|
||||||
|
|
||||||
render(<AppHeader />, document.getElementById('react-app-header'))
|
|
||||||
```
|
|
||||||
|
|
||||||
(Components will also be rendered within a tree produced by calling
|
|
||||||
`ReactDOM.render` (i.e., an existing web app), but
|
|
||||||
otherwise it is not recommended.)
|
|
||||||
|
|
||||||
You might need to adjust the styles of the HTML document's root elements for
|
|
||||||
your app to fill the viewport.
|
|
||||||
|
|
||||||
```html
|
|
||||||
<html style="height:100%">
|
|
||||||
<body style="height:100%">
|
|
||||||
<div id="react-root" style="display:flex;height:100%"></div>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Server-side rendering
|
|
||||||
|
|
||||||
Server-side rendering to HTML is supported using `AppRegistry`:
|
|
||||||
|
|
||||||
```js
|
|
||||||
import App from './src/App';
|
|
||||||
import ReactDOMServer from 'react-dom/server';
|
|
||||||
import { AppRegistry } from 'react-native-web';
|
|
||||||
|
|
||||||
// register the app
|
|
||||||
AppRegistry.registerComponent('App', () => App);
|
|
||||||
|
|
||||||
// prerender the app
|
|
||||||
const { element, getStyleElement } = AppRegistry.getApplication('App', { initialProps });
|
|
||||||
// first the element
|
|
||||||
const html = ReactDOMServer.renderToString(element);
|
|
||||||
// then the styles (optionally include a nonce if your CSP policy requires it)
|
|
||||||
const css = ReactDOMServer.renderToStaticMarkup(getStyleElement({ nonce }));
|
|
||||||
|
|
||||||
// example HTML document string
|
|
||||||
const document = `
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html style="height:100%">
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
${css}
|
|
||||||
<body style="height:100%; overflow-y:hidden">
|
|
||||||
<div id="root" style="display:flex; height: 100%">
|
|
||||||
${html}
|
|
||||||
</div>
|
|
||||||
<script nonce="${nonce}" src="${bundlePath}"></script>
|
|
||||||
`
|
|
||||||
```
|
|
||||||
|
|
||||||
## Testing with Jest
|
|
||||||
|
|
||||||
[Jest](https://facebook.github.io/jest/) can be configured using the provided
|
|
||||||
preset. This will map `react-native` to `react-native-web` and provide
|
|
||||||
appropriate mocks:
|
|
||||||
|
|
||||||
```
|
|
||||||
{
|
|
||||||
"preset": "react-native-web"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Please refer to the Jest documentation for more information.
|
|
||||||
|
|
||||||
## Using Flow
|
|
||||||
|
|
||||||
[Flow](https://flow.org) can be configured to understand the aliased module:
|
|
||||||
|
|
||||||
```
|
|
||||||
[options]
|
|
||||||
module.name_mapper='^react-native$' -> 'react-native-web'
|
|
||||||
```
|
|
||||||
|
|
||||||
You may also need to inlucde a custom libdef
|
|
||||||
([example](https://gist.github.com/paularmstrong/f60b40d16fc83e1e8e532d483336f9bb))
|
|
||||||
in your config.
|
|
||||||
|
|
||||||
## Multi-platform applications
|
|
||||||
|
|
||||||
### Web-specific code
|
|
||||||
|
|
||||||
Minor platform differences can use the `Platform` module.
|
|
||||||
|
|
||||||
```js
|
|
||||||
import { Platform } from 'react-native';
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
height: (Platform.OS === 'web') ? 200 : 100,
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
More significant platform differences should use platform-specific files (see
|
|
||||||
the webpack configuration below for resolving `*.web.js` files):
|
|
||||||
|
|
||||||
For example, with the following files in your project:
|
|
||||||
|
|
||||||
```
|
|
||||||
MyComponent.android.js
|
|
||||||
MyComponent.ios.js
|
|
||||||
MyComponent.web.js
|
|
||||||
```
|
|
||||||
|
|
||||||
And the following import:
|
|
||||||
|
|
||||||
```js
|
|
||||||
import MyComponent from './MyComponent';
|
|
||||||
```
|
|
||||||
|
|
||||||
React Native will automatically import the correct variant for each specific
|
|
||||||
target platform.
|
|
||||||
|
|
||||||
## Web packaging for existing React Native apps
|
|
||||||
|
|
||||||
What follows is merely an _example_ of one basic way to package a web app using
|
|
||||||
[Webpack](https://webpack.js.org) and [Babel](https://babeljs.io/). (You can
|
|
||||||
also the React Native bundler, [Metro](https://github.com/facebook/metro), to
|
|
||||||
build web apps.)
|
|
||||||
|
|
||||||
Packaging web apps is subtly different to packaging React Native apps and is
|
|
||||||
complicated by the need to tree-shake and code-split non-trivial apps.
|
|
||||||
|
|
||||||
Install webpack-related dependencies, for example:
|
|
||||||
|
|
||||||
```
|
|
||||||
yarn add --dev babel-loader url-loader webpack webpack-cli webpack-dev-server
|
|
||||||
```
|
|
||||||
|
|
||||||
React Native's Babel preset rewrites ES modules to CommonJS modules, preventing
|
|
||||||
bundlers from automatically performing "tree-shaking" to remove unused modules
|
|
||||||
from your web app build. To help with this, you can install the following Babel
|
|
||||||
plugin:
|
|
||||||
|
|
||||||
```
|
|
||||||
yarn install --dev babel-plugin-react-native-web
|
|
||||||
```
|
|
||||||
|
|
||||||
Create a `web/webpack.config.js` file:
|
|
||||||
|
|
||||||
```js
|
|
||||||
// web/webpack.config.js
|
|
||||||
|
|
||||||
const path = require('path');
|
|
||||||
const webpack = require('webpack');
|
|
||||||
|
|
||||||
const appDirectory = path.resolve(__dirname, '../');
|
|
||||||
|
|
||||||
// This is needed for webpack to compile JavaScript.
|
|
||||||
// Many OSS React Native packages are not compiled to ES5 before being
|
|
||||||
// published. If you depend on uncompiled packages they may cause webpack build
|
|
||||||
// errors. To fix this webpack can be configured to compile to the necessary
|
|
||||||
// `node_module`.
|
|
||||||
const babelLoaderConfiguration = {
|
|
||||||
test: /\.js$/,
|
|
||||||
// Add every directory that needs to be compiled by Babel during the build.
|
|
||||||
include: [
|
|
||||||
path.resolve(appDirectory, 'index.web.js'),
|
|
||||||
path.resolve(appDirectory, 'src'),
|
|
||||||
path.resolve(appDirectory, 'node_modules/react-native-uncompiled')
|
|
||||||
],
|
|
||||||
use: {
|
|
||||||
loader: 'babel-loader',
|
|
||||||
options: {
|
|
||||||
cacheDirectory: true,
|
|
||||||
// The 'react-native' preset is recommended to match React Native's packager
|
|
||||||
presets: ['react-native'],
|
|
||||||
// Re-write paths to import only the modules needed by the app
|
|
||||||
plugins: ['react-native-web']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// This is needed for webpack to import static images in JavaScript files.
|
|
||||||
const imageLoaderConfiguration = {
|
|
||||||
test: /\.(gif|jpe?g|png|svg)$/,
|
|
||||||
use: {
|
|
||||||
loader: 'url-loader',
|
|
||||||
options: {
|
|
||||||
name: '[name].[ext]'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
entry: [
|
|
||||||
// load any web API polyfills
|
|
||||||
// path.resolve(appDirectory, 'polyfills-web.js'),
|
|
||||||
// your web-specific entry file
|
|
||||||
path.resolve(appDirectory, 'index.web.js')
|
|
||||||
],
|
|
||||||
|
|
||||||
// configures where the build ends up
|
|
||||||
output: {
|
|
||||||
filename: 'bundle.web.js',
|
|
||||||
path: path.resolve(appDirectory, 'dist')
|
|
||||||
},
|
|
||||||
|
|
||||||
// ...the rest of your config
|
|
||||||
|
|
||||||
module: {
|
|
||||||
rules: [
|
|
||||||
babelLoaderConfiguration,
|
|
||||||
imageLoaderConfiguration
|
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
resolve: {
|
|
||||||
// This will only alias the exact import "react-native"
|
|
||||||
alias: {
|
|
||||||
'react-native$': 'react-native-web'
|
|
||||||
},
|
|
||||||
// If you're working on a multi-platform React Native app, web-specific
|
|
||||||
// module implementations should be written in files using the extension
|
|
||||||
// `.web.js`.
|
|
||||||
extensions: [ '.web.js', '.js' ]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
To run in development from the root of your application:
|
|
||||||
|
|
||||||
```
|
|
||||||
./node_modules/.bin/webpack-dev-server -d --config ./web/webpack.config.js --inline --hot --colors
|
|
||||||
```
|
|
||||||
|
|
||||||
To build for production:
|
|
||||||
|
|
||||||
```
|
|
||||||
./node_modules/.bin/webpack -p --config ./web/webpack.config.js
|
|
||||||
```
|
|
||||||
|
|
||||||
Please refer to the Webpack documentation for more information on configuration.
|
|
||||||
|
|
||||||
## Other notes
|
|
||||||
|
|
||||||
### Safari flexbox performance
|
|
||||||
|
|
||||||
Safari prior to version 10.1 can suffer from extremely [poor flexbox
|
|
||||||
performance](https://bugs.webkit.org/show_bug.cgi?id=150445). The recommended
|
|
||||||
way to work around this issue (as used on mobile.twitter.com) is to set
|
|
||||||
`display:block` on Views in your element hierarchy that you know don't need
|
|
||||||
flexbox layout.
|
|
||||||
|
|
||||||
### Platform-specific component props
|
|
||||||
|
|
||||||
There are properties that do not work across all platforms. All web-specific
|
|
||||||
props are annotated with `(web)` in the documentation.
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
"name": "website",
|
"name": "website",
|
||||||
"version": "0.8.9",
|
"version": "0.9.7",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "build-storybook -o ./dist -c ./storybook/.storybook",
|
"build": "build-storybook -o ./dist -c ./storybook/.storybook",
|
||||||
"start": "start-storybook -p 9001 -c ./storybook/.storybook",
|
"start": "start-storybook -p 9001 -c ./storybook/.storybook",
|
||||||
@@ -10,12 +10,12 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@storybook/addon-options": "^3.4.3",
|
"@storybook/addon-options": "^3.4.3",
|
||||||
"@storybook/react": "^3.4.3",
|
"@storybook/react": "^3.4.3",
|
||||||
"react": "^16.4.1",
|
"react": "^16.5.1",
|
||||||
"react-dom": "^16.4.1",
|
"react-dom": "^16.5.1",
|
||||||
"react-native-web": "0.8.9"
|
"react-native-web": "0.9.7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel-plugin-react-native-web": "0.8.9",
|
"babel-plugin-react-native-web": "0.9.7",
|
||||||
"url-loader": "^1.0.1",
|
"url-loader": "^1.0.1",
|
||||||
"webpack": "^4.8.1"
|
"webpack": "^4.8.1"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ const PickerScreen = () => (
|
|||||||
<AppText>Renders the native <select> component.</AppText>
|
<AppText>Renders the native <select> component.</AppText>
|
||||||
</Description>
|
</Description>
|
||||||
<Section title="Props">
|
<Section title="Props">
|
||||||
|
<DocItem name="...View props" />
|
||||||
|
|
||||||
<DocItem
|
<DocItem
|
||||||
name="children"
|
name="children"
|
||||||
typeInfo="?Array<Picker.Item>"
|
typeInfo="?Array<Picker.Item>"
|
||||||
|
|||||||
@@ -116,6 +116,12 @@ const TextScreen = () => (
|
|||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<DocItem
|
||||||
|
name="nativeID"
|
||||||
|
typeInfo="?string"
|
||||||
|
description="Used to locate this view from any native DOM code, or to define accessibility relationships. This is rendered to the native 'id' DOM attribute"
|
||||||
|
/>
|
||||||
|
|
||||||
<DocItem
|
<DocItem
|
||||||
name="numberOfLines"
|
name="numberOfLines"
|
||||||
typeInfo="?number"
|
typeInfo="?number"
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { processColor, StyleSheet, View, Text, TouchableHighlight } from 'react-native';
|
import { StyleSheet, View, Text, TouchableHighlight } from 'react-native';
|
||||||
|
|
||||||
export default class TouchableCustomStyleOverridesExample extends React.Component {
|
export default class TouchableCustomStyleOverridesExample extends React.Component {
|
||||||
buttons = ['One', 'Two', 'Three'];
|
buttons = ['One', 'Two', 'Three'];
|
||||||
@@ -26,7 +26,7 @@ export default class TouchableCustomStyleOverridesExample extends React.Componen
|
|||||||
key={button}
|
key={button}
|
||||||
onPress={this.select(button)}
|
onPress={this.select(button)}
|
||||||
style={[styles.touchable, this.state[button] && styles.blue]}
|
style={[styles.touchable, this.state[button] && styles.blue]}
|
||||||
underlayColor={processColor('#1B95E0', 0.125)}
|
underlayColor="#1b95e020"
|
||||||
>
|
>
|
||||||
<Text style={[!this.state[button] && styles.text]}>{button}</Text>
|
<Text style={[!this.state[button] && styles.text]}>{button}</Text>
|
||||||
</TouchableHighlight>
|
</TouchableHighlight>
|
||||||
@@ -39,7 +39,7 @@ export default class TouchableCustomStyleOverridesExample extends React.Componen
|
|||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
blue: {
|
blue: {
|
||||||
backgroundColor: processColor('#1B95E0', 0.25),
|
backgroundColor: '#1b95e040',
|
||||||
borderColor: '#1B95E0'
|
borderColor: '#1B95E0'
|
||||||
},
|
},
|
||||||
text: {
|
text: {
|
||||||
|
|||||||
@@ -119,6 +119,12 @@ const ViewScreen = () => (
|
|||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<DocItem
|
||||||
|
name="nativeID"
|
||||||
|
typeInfo="?string"
|
||||||
|
description="Used to locate this view from any native DOM code, or to define accessibility relationships. This is rendered to the native 'id' DOM attribute"
|
||||||
|
/>
|
||||||
|
|
||||||
<DocItem name="onBlur" typeInfo="?function" />
|
<DocItem name="onBlur" typeInfo="?function" />
|
||||||
<DocItem name="onContextMenu" typeInfo="?function" />
|
<DocItem name="onContextMenu" typeInfo="?function" />
|
||||||
<DocItem name="onFocus" typeInfo="?function" />
|
<DocItem name="onFocus" typeInfo="?function" />
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { View, StyleSheet } from 'react-native';
|
|||||||
|
|
||||||
export default class Sandbox extends React.PureComponent {
|
export default class Sandbox extends React.PureComponent {
|
||||||
render() {
|
render() {
|
||||||
return <View styles={styles.root} />;
|
return <View style={styles.root} />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,12 +14,12 @@ class AppText extends React.PureComponent {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { style, ...rest } = this.props;
|
const { accessibilityRole, style, ...rest } = this.props;
|
||||||
const isInAParentText = this.context;
|
const isInAParentText = this.context;
|
||||||
return (
|
return (
|
||||||
<Text
|
<Text
|
||||||
{...rest}
|
{...rest}
|
||||||
accessibilityRole={rest.href ? 'link' : undefined}
|
accessibilityRole={rest.href ? 'link' : accessibilityRole}
|
||||||
style={[!isInAParentText && styles.baseText, style, rest.href && styles.link]}
|
style={[!isInAParentText && styles.baseText, style, rest.href && styles.link]}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -8,7 +8,11 @@ import AppText from './AppText';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { StyleSheet, View } from 'react-native';
|
import { StyleSheet, View } from 'react-native';
|
||||||
|
|
||||||
const SectionTitle = ({ children }) => <AppText style={styles.sectionTitle}>{children}</AppText>;
|
const SectionTitle = ({ children }) => (
|
||||||
|
<AppText accessibilityRole="heading" aria-level="2" style={styles.sectionTitle}>
|
||||||
|
{children}
|
||||||
|
</AppText>
|
||||||
|
);
|
||||||
|
|
||||||
const Section = ({ children, title }) => (
|
const Section = ({ children, title }) => (
|
||||||
<View>
|
<View>
|
||||||
|
|||||||
@@ -10,7 +10,11 @@ import insertBetween from './insertBetween';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { StyleSheet, View } from 'react-native';
|
import { StyleSheet, View } from 'react-native';
|
||||||
|
|
||||||
const Title = ({ children }) => <AppText style={styles.title}>{children}</AppText>;
|
const Title = ({ children }) => (
|
||||||
|
<AppText accessibilityRole="heading" style={styles.title}>
|
||||||
|
{children}
|
||||||
|
</AppText>
|
||||||
|
);
|
||||||
|
|
||||||
export const Description = ({ children }) => (
|
export const Description = ({ children }) => (
|
||||||
<AppText style={styles.description}>
|
<AppText style={styles.description}>
|
||||||
|
|||||||
133
yarn.lock
133
yarn.lock
@@ -3568,25 +3568,25 @@ envinfo@^5.7.0:
|
|||||||
version "5.10.0"
|
version "5.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-5.10.0.tgz#503a9774ae15b93ea68bdfae2ccd6306624ea6df"
|
resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-5.10.0.tgz#503a9774ae15b93ea68bdfae2ccd6306624ea6df"
|
||||||
|
|
||||||
enzyme-adapter-react-16@^1.1.0:
|
enzyme-adapter-react-16@^1.5.0:
|
||||||
version "1.1.1"
|
version "1.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.1.1.tgz#a8f4278b47e082fbca14f5bfb1ee50ee650717b4"
|
resolved "https://registry.yarnpkg.com/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.5.0.tgz#50af8d76a45fe0915de932bd95d34cdca75c0be3"
|
||||||
dependencies:
|
dependencies:
|
||||||
enzyme-adapter-utils "^1.3.0"
|
enzyme-adapter-utils "^1.8.0"
|
||||||
lodash "^4.17.4"
|
function.prototype.name "^1.1.0"
|
||||||
object.assign "^4.0.4"
|
object.assign "^4.1.0"
|
||||||
object.values "^1.0.4"
|
object.values "^1.0.4"
|
||||||
prop-types "^15.6.0"
|
prop-types "^15.6.2"
|
||||||
react-reconciler "^0.7.0"
|
react-is "^16.4.2"
|
||||||
react-test-renderer "^16.0.0-0"
|
react-test-renderer "^16.0.0-0"
|
||||||
|
|
||||||
enzyme-adapter-utils@^1.3.0:
|
enzyme-adapter-utils@^1.8.0:
|
||||||
version "1.3.0"
|
version "1.8.0"
|
||||||
resolved "https://registry.yarnpkg.com/enzyme-adapter-utils/-/enzyme-adapter-utils-1.3.0.tgz#d6c85756826c257a8544d362cc7a67e97ea698c7"
|
resolved "https://registry.yarnpkg.com/enzyme-adapter-utils/-/enzyme-adapter-utils-1.8.0.tgz#ee9f07250663a985f1f2caaf297720787da559f1"
|
||||||
dependencies:
|
dependencies:
|
||||||
lodash "^4.17.4"
|
function.prototype.name "^1.1.0"
|
||||||
object.assign "^4.0.4"
|
object.assign "^4.1.0"
|
||||||
prop-types "^15.6.0"
|
prop-types "^15.6.2"
|
||||||
|
|
||||||
enzyme-to-json@^3.3.3:
|
enzyme-to-json@^3.3.3:
|
||||||
version "3.3.4"
|
version "3.3.4"
|
||||||
@@ -3594,26 +3594,29 @@ enzyme-to-json@^3.3.3:
|
|||||||
dependencies:
|
dependencies:
|
||||||
lodash "^4.17.4"
|
lodash "^4.17.4"
|
||||||
|
|
||||||
enzyme@^3.3.0:
|
enzyme@^3.6.0:
|
||||||
version "3.3.0"
|
version "3.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/enzyme/-/enzyme-3.3.0.tgz#0971abd167f2d4bf3f5bd508229e1c4b6dc50479"
|
resolved "https://registry.yarnpkg.com/enzyme/-/enzyme-3.6.0.tgz#d213f280a258f61e901bc663d4cc2d6fd9a9dec8"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
array.prototype.flat "^1.2.1"
|
||||||
cheerio "^1.0.0-rc.2"
|
cheerio "^1.0.0-rc.2"
|
||||||
function.prototype.name "^1.0.3"
|
function.prototype.name "^1.1.0"
|
||||||
has "^1.0.1"
|
has "^1.0.3"
|
||||||
is-boolean-object "^1.0.0"
|
is-boolean-object "^1.0.0"
|
||||||
is-callable "^1.1.3"
|
is-callable "^1.1.4"
|
||||||
is-number-object "^1.0.3"
|
is-number-object "^1.0.3"
|
||||||
is-string "^1.0.4"
|
is-string "^1.0.4"
|
||||||
is-subset "^0.1.1"
|
is-subset "^0.1.1"
|
||||||
lodash "^4.17.4"
|
lodash.escape "^4.0.1"
|
||||||
object-inspect "^1.5.0"
|
lodash.isequal "^4.5.0"
|
||||||
|
object-inspect "^1.6.0"
|
||||||
object-is "^1.0.1"
|
object-is "^1.0.1"
|
||||||
object.assign "^4.1.0"
|
object.assign "^4.1.0"
|
||||||
object.entries "^1.0.4"
|
object.entries "^1.0.4"
|
||||||
object.values "^1.0.4"
|
object.values "^1.0.4"
|
||||||
raf "^3.4.0"
|
raf "^3.4.0"
|
||||||
rst-selector-parser "^2.2.3"
|
rst-selector-parser "^2.2.3"
|
||||||
|
string.prototype.trim "^1.1.2"
|
||||||
|
|
||||||
errno@^0.1.3, errno@~0.1.7:
|
errno@^0.1.3, errno@~0.1.7:
|
||||||
version "0.1.7"
|
version "0.1.7"
|
||||||
@@ -3634,7 +3637,7 @@ error@^7.0.2:
|
|||||||
string-template "~0.2.1"
|
string-template "~0.2.1"
|
||||||
xtend "~4.0.0"
|
xtend "~4.0.0"
|
||||||
|
|
||||||
es-abstract@^1.10.0, es-abstract@^1.12.0, es-abstract@^1.4.3, es-abstract@^1.5.1, es-abstract@^1.6.1, es-abstract@^1.7.0, es-abstract@^1.9.0:
|
es-abstract@^1.10.0, es-abstract@^1.12.0, es-abstract@^1.4.3, es-abstract@^1.5.0, es-abstract@^1.5.1, es-abstract@^1.6.1, es-abstract@^1.7.0, es-abstract@^1.9.0:
|
||||||
version "1.12.0"
|
version "1.12.0"
|
||||||
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.12.0.tgz#9dbbdd27c6856f0001421ca18782d786bf8a6165"
|
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.12.0.tgz#9dbbdd27c6856f0001421ca18782d786bf8a6165"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -4414,7 +4417,7 @@ function-bind@^1.0.2, function-bind@^1.1.0, function-bind@^1.1.1:
|
|||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
|
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
|
||||||
|
|
||||||
function.prototype.name@^1.0.3, function.prototype.name@^1.1.0:
|
function.prototype.name@^1.1.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.0.tgz#8bd763cc0af860a859cc5d49384d74b932cd2327"
|
resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.0.tgz#8bd763cc0af860a859cc5d49384d74b932cd2327"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -5231,7 +5234,7 @@ is-builtin-module@^1.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
builtin-modules "^1.0.0"
|
builtin-modules "^1.0.0"
|
||||||
|
|
||||||
is-callable@^1.1.1, is-callable@^1.1.3:
|
is-callable@^1.1.1, is-callable@^1.1.3, is-callable@^1.1.4:
|
||||||
version "1.1.4"
|
version "1.1.4"
|
||||||
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75"
|
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75"
|
||||||
|
|
||||||
@@ -6363,6 +6366,10 @@ lodash.debounce@^4.0.8:
|
|||||||
version "4.0.8"
|
version "4.0.8"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
|
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
|
||||||
|
|
||||||
|
lodash.escape@^4.0.1:
|
||||||
|
version "4.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-4.0.1.tgz#c9044690c21e04294beaa517712fded1fa88de98"
|
||||||
|
|
||||||
lodash.flattendeep@^4.4.0:
|
lodash.flattendeep@^4.4.0:
|
||||||
version "4.4.0"
|
version "4.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2"
|
resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2"
|
||||||
@@ -6375,6 +6382,10 @@ lodash.isarray@^3.0.0:
|
|||||||
version "3.0.4"
|
version "3.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55"
|
resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55"
|
||||||
|
|
||||||
|
lodash.isequal@^4.5.0:
|
||||||
|
version "4.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
|
||||||
|
|
||||||
lodash.isplainobject@^4.0.6:
|
lodash.isplainobject@^4.0.6:
|
||||||
version "4.0.6"
|
version "4.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
|
resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
|
||||||
@@ -7111,7 +7122,7 @@ object-copy@^0.1.0:
|
|||||||
define-property "^0.2.5"
|
define-property "^0.2.5"
|
||||||
kind-of "^3.0.3"
|
kind-of "^3.0.3"
|
||||||
|
|
||||||
object-inspect@^1.5.0:
|
object-inspect@^1.6.0:
|
||||||
version "1.6.0"
|
version "1.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.6.0.tgz#c70b6cbf72f274aab4c34c0c82f5167bf82cf15b"
|
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.6.0.tgz#c70b6cbf72f274aab4c34c0c82f5167bf82cf15b"
|
||||||
|
|
||||||
@@ -7129,7 +7140,7 @@ object-visit@^1.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
isobject "^3.0.0"
|
isobject "^3.0.0"
|
||||||
|
|
||||||
object.assign@^4.0.4, object.assign@^4.1.0:
|
object.assign@^4.1.0:
|
||||||
version "4.1.0"
|
version "4.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da"
|
resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -8097,16 +8108,16 @@ react-addons-shallow-compare@^15.6.2:
|
|||||||
fbjs "^0.8.4"
|
fbjs "^0.8.4"
|
||||||
object-assign "^4.1.0"
|
object-assign "^4.1.0"
|
||||||
|
|
||||||
react-art@^16.4.1:
|
react-art@^16.5.1:
|
||||||
version "16.4.1"
|
version "16.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-art/-/react-art-16.4.1.tgz#3544c13038d7ddfe8b1cc1170b02d99492c03b50"
|
resolved "https://registry.yarnpkg.com/react-art/-/react-art-16.5.1.tgz#534a265dafc5c0e310da9ef9afbf76d6e221be56"
|
||||||
dependencies:
|
dependencies:
|
||||||
art "^0.10.1"
|
art "^0.10.1"
|
||||||
create-react-class "^15.6.2"
|
create-react-class "^15.6.2"
|
||||||
fbjs "^0.8.16"
|
|
||||||
loose-envify "^1.1.0"
|
loose-envify "^1.1.0"
|
||||||
object-assign "^4.1.1"
|
object-assign "^4.1.1"
|
||||||
prop-types "^15.6.0"
|
prop-types "^15.6.2"
|
||||||
|
schedule "^0.4.0"
|
||||||
|
|
||||||
react-deep-force-update@^1.0.0:
|
react-deep-force-update@^1.0.0:
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
@@ -8147,14 +8158,14 @@ react-docgen@^3.0.0-beta11:
|
|||||||
node-dir "^0.1.10"
|
node-dir "^0.1.10"
|
||||||
recast "^0.12.6"
|
recast "^0.12.6"
|
||||||
|
|
||||||
react-dom@^16.4.1:
|
react-dom@^16.5.1:
|
||||||
version "16.4.1"
|
version "16.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.4.1.tgz#7f8b0223b3a5fbe205116c56deb85de32685dad6"
|
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.5.1.tgz#29d0c5a01ed3b6b4c14309aa91af6ec4eb4f292c"
|
||||||
dependencies:
|
dependencies:
|
||||||
fbjs "^0.8.16"
|
|
||||||
loose-envify "^1.1.0"
|
loose-envify "^1.1.0"
|
||||||
object-assign "^4.1.1"
|
object-assign "^4.1.1"
|
||||||
prop-types "^15.6.0"
|
prop-types "^15.6.2"
|
||||||
|
schedule "^0.4.0"
|
||||||
|
|
||||||
react-error-overlay@^4.0.0:
|
react-error-overlay@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
@@ -8204,6 +8215,10 @@ react-is@^16.3.1, react-is@^16.4.1:
|
|||||||
version "16.4.1"
|
version "16.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.4.1.tgz#d624c4650d2c65dbd52c72622bbf389435d9776e"
|
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.4.1.tgz#d624c4650d2c65dbd52c72622bbf389435d9776e"
|
||||||
|
|
||||||
|
react-is@^16.4.2, react-is@^16.5.1:
|
||||||
|
version "16.5.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.5.1.tgz#c6e8734fd548a22e1cef4fd0833afbeb433b85ee"
|
||||||
|
|
||||||
react-jss@^8.6.1:
|
react-jss@^8.6.1:
|
||||||
version "8.6.1"
|
version "8.6.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-jss/-/react-jss-8.6.1.tgz#a06e2e1d2c4d91b4d11befda865e6c07fbd75252"
|
resolved "https://registry.yarnpkg.com/react-jss/-/react-jss-8.6.1.tgz#a06e2e1d2c4d91b4d11befda865e6c07fbd75252"
|
||||||
@@ -8234,15 +8249,6 @@ react-proxy@^1.1.7:
|
|||||||
lodash "^4.6.1"
|
lodash "^4.6.1"
|
||||||
react-deep-force-update "^1.0.0"
|
react-deep-force-update "^1.0.0"
|
||||||
|
|
||||||
react-reconciler@^0.7.0:
|
|
||||||
version "0.7.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/react-reconciler/-/react-reconciler-0.7.0.tgz#9614894103e5f138deeeb5eabaf3ee80eb1d026d"
|
|
||||||
dependencies:
|
|
||||||
fbjs "^0.8.16"
|
|
||||||
loose-envify "^1.1.0"
|
|
||||||
object-assign "^4.1.1"
|
|
||||||
prop-types "^15.6.0"
|
|
||||||
|
|
||||||
react-split-pane@^0.1.77:
|
react-split-pane@^0.1.77:
|
||||||
version "0.1.81"
|
version "0.1.81"
|
||||||
resolved "https://registry.yarnpkg.com/react-split-pane/-/react-split-pane-0.1.81.tgz#b1e8b82e0a6edd10f18fd639a5f512db3cbbb4e6"
|
resolved "https://registry.yarnpkg.com/react-split-pane/-/react-split-pane-0.1.81.tgz#b1e8b82e0a6edd10f18fd639a5f512db3cbbb4e6"
|
||||||
@@ -8258,7 +8264,7 @@ react-style-proptype@^3.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
prop-types "^15.5.4"
|
prop-types "^15.5.4"
|
||||||
|
|
||||||
react-test-renderer@^16.0.0-0, react-test-renderer@^16.4.1:
|
react-test-renderer@^16.0.0-0:
|
||||||
version "16.4.1"
|
version "16.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.4.1.tgz#f2fb30c2c7b517db6e5b10ed20bb6b0a7ccd8d70"
|
resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.4.1.tgz#f2fb30c2c7b517db6e5b10ed20bb6b0a7ccd8d70"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -8267,6 +8273,15 @@ react-test-renderer@^16.0.0-0, react-test-renderer@^16.4.1:
|
|||||||
prop-types "^15.6.0"
|
prop-types "^15.6.0"
|
||||||
react-is "^16.4.1"
|
react-is "^16.4.1"
|
||||||
|
|
||||||
|
react-test-renderer@^16.5.1:
|
||||||
|
version "16.5.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.5.1.tgz#17f020fb0cf884cadebb5240d9d9c23452f18299"
|
||||||
|
dependencies:
|
||||||
|
object-assign "^4.1.1"
|
||||||
|
prop-types "^15.6.2"
|
||||||
|
react-is "^16.5.1"
|
||||||
|
schedule "^0.4.0"
|
||||||
|
|
||||||
react-timer-mixin@^0.13.3:
|
react-timer-mixin@^0.13.3:
|
||||||
version "0.13.3"
|
version "0.13.3"
|
||||||
resolved "https://registry.yarnpkg.com/react-timer-mixin/-/react-timer-mixin-0.13.3.tgz#0da8b9f807ec07dc3e854d082c737c65605b3d22"
|
resolved "https://registry.yarnpkg.com/react-timer-mixin/-/react-timer-mixin-0.13.3.tgz#0da8b9f807ec07dc3e854d082c737c65605b3d22"
|
||||||
@@ -8298,14 +8313,14 @@ react-treebeard@^2.1.0:
|
|||||||
shallowequal "^0.2.2"
|
shallowequal "^0.2.2"
|
||||||
velocity-react "^1.3.1"
|
velocity-react "^1.3.1"
|
||||||
|
|
||||||
react@^16.4.1:
|
react@^16.5.1:
|
||||||
version "16.4.1"
|
version "16.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/react/-/react-16.4.1.tgz#de51ba5764b5dbcd1f9079037b862bd26b82fe32"
|
resolved "https://registry.yarnpkg.com/react/-/react-16.5.1.tgz#8cb8e9f8cdcb4bde41c9a138bfbf907e66132372"
|
||||||
dependencies:
|
dependencies:
|
||||||
fbjs "^0.8.16"
|
|
||||||
loose-envify "^1.1.0"
|
loose-envify "^1.1.0"
|
||||||
object-assign "^4.1.1"
|
object-assign "^4.1.1"
|
||||||
prop-types "^15.6.0"
|
prop-types "^15.6.2"
|
||||||
|
schedule "^0.4.0"
|
||||||
|
|
||||||
reactxp@^1.3.0:
|
reactxp@^1.3.0:
|
||||||
version "1.3.0"
|
version "1.3.0"
|
||||||
@@ -8836,6 +8851,12 @@ sax@^1.2.4, sax@~1.2.1:
|
|||||||
version "1.2.4"
|
version "1.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
|
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
|
||||||
|
|
||||||
|
schedule@^0.4.0:
|
||||||
|
version "0.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/schedule/-/schedule-0.4.0.tgz#fa20cfd0bfbf91c47d02272fd7096780d3170bbb"
|
||||||
|
dependencies:
|
||||||
|
object-assign "^4.1.1"
|
||||||
|
|
||||||
schema-utils@^0.3.0:
|
schema-utils@^0.3.0:
|
||||||
version "0.3.0"
|
version "0.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.3.0.tgz#f5877222ce3e931edae039f17eb3716e7137f8cf"
|
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.3.0.tgz#f5877222ce3e931edae039f17eb3716e7137f8cf"
|
||||||
@@ -9299,6 +9320,14 @@ string.prototype.padstart@^3.0.0:
|
|||||||
es-abstract "^1.4.3"
|
es-abstract "^1.4.3"
|
||||||
function-bind "^1.0.2"
|
function-bind "^1.0.2"
|
||||||
|
|
||||||
|
string.prototype.trim@^1.1.2:
|
||||||
|
version "1.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.1.2.tgz#d04de2c89e137f4d7d206f086b5ed2fae6be8cea"
|
||||||
|
dependencies:
|
||||||
|
define-properties "^1.1.2"
|
||||||
|
es-abstract "^1.5.0"
|
||||||
|
function-bind "^1.0.2"
|
||||||
|
|
||||||
string_decoder@^1.0.0, string_decoder@~1.1.1:
|
string_decoder@^1.0.0, string_decoder@~1.1.1:
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
|
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
|
||||||
|
|||||||
Reference in New Issue
Block a user