mirror of
https://github.com/zhigang1992/react-native-web.git
synced 2026-03-30 23:23:35 +08:00
Compare commits
65 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
9c8407162e | ||
|
|
16b9ec2917 | ||
|
|
d4af1eb981 | ||
|
|
405a3b79e8 | ||
|
|
afd5293172 | ||
|
|
e4831b7bd8 | ||
|
|
baffc9a9e6 | ||
|
|
4151b47005 | ||
|
|
96ec805f59 | ||
|
|
d2df2c296e | ||
|
|
af80b046fa | ||
|
|
1d01af57d0 | ||
|
|
e7613ca4d1 | ||
|
|
0e81c6ef27 | ||
|
|
7fa8940325 | ||
|
|
3e681bed3e | ||
|
|
ecd4b40c71 | ||
|
|
e7cb364b63 | ||
|
|
90bd23f783 | ||
|
|
47a281373a | ||
|
|
6a0302169c | ||
|
|
b195f2b1f5 | ||
|
|
7b81d2a7ec | ||
|
|
a1d8ea776e |
3
.github/CONTRIBUTING.md
vendored
3
.github/CONTRIBUTING.md
vendored
@@ -4,7 +4,8 @@
|
||||
|
||||
Before opening an issue, please search the [issue
|
||||
tracker](https://github.com/necolas/react-native-web/issues) to make sure your
|
||||
issue hasn't already been reported.
|
||||
issue hasn't already been reported. Please note that your issue may be closed
|
||||
if it doesn't include the information requested in the issue template.
|
||||
|
||||
## Getting started
|
||||
|
||||
|
||||
40
.github/ISSUE_TEMPLATE/bug.md
vendored
40
.github/ISSUE_TEMPLATE/bug.md
vendored
@@ -4,29 +4,49 @@ about: "If something isn't working as expected \U0001F914"
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
Thank you for reporting an issue. Please note that an issue must include the
|
||||
information that is marked as REQUIRED below, or it may be closed.
|
||||
-->
|
||||
|
||||
**The problem**
|
||||
A clear and concise description of the bug or problem.
|
||||
<!--
|
||||
REQUIRED: A clear and concise description of the bug or problem.
|
||||
-->
|
||||
|
||||
**How to reproduce**
|
||||
Test case: <!-- FORK THIS TEMPLATE: https://codesandbox.io/s/6lx6ql1w5r -->
|
||||
<!--
|
||||
REQUIRED: Create a test case by forking this template https://codesandbox.io/s/6lx6ql1w5r
|
||||
|
||||
Failing to include a reduced test case may result in the issue being closed,
|
||||
and will delay any potential fix. Your application or GitHub project is NOT
|
||||
considered a reduced test case. If the issue only affects certain browsers,
|
||||
providing screenshots is also helpful.
|
||||
-->
|
||||
Simplified test case: <!-- add link here -->
|
||||
|
||||
Steps to reproduce:
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
<!-- If applicable, add screenshots to help explain your problem. -->
|
||||
|
||||
**Expected behavior**
|
||||
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?**
|
||||
|
||||
* OS:
|
||||
* Device:
|
||||
* Browser:
|
||||
* React Native for Web (version):
|
||||
* React (version):
|
||||
* React Native for Web (version): TBC
|
||||
* React (version): TBC
|
||||
* Browser: TBC
|
||||
|
||||
<!--
|
||||
OPTIONAL:
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
-->
|
||||
|
||||
8
.github/ISSUE_TEMPLATE/feature.md
vendored
8
.github/ISSUE_TEMPLATE/feature.md
vendored
@@ -5,13 +5,13 @@ about: If you have a suggestion
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is, e.g., I have an issue when [...]
|
||||
<!-- A clear and concise description of what the problem is, e.g., I have an issue when [...] -->
|
||||
|
||||
**Describe a solution you'd like**
|
||||
A clear and concise description of what you want to happen. Add any considered drawbacks.
|
||||
<!-- A clear and concise description of what you want to happen. Add any considered drawbacks. -->
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
<!-- Add any other context or screenshots about the feature request here. -->
|
||||
|
||||
217
README.md
217
README.md
@@ -25,7 +25,7 @@ using Node.js.
|
||||
Who is using React Native in production web apps?
|
||||
[Twitter](https://mobile.twitter.com), [Major League
|
||||
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).
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
## Documentation
|
||||
|
||||
You can find the API documentation [on the website][website-url].
|
||||
|
||||
Please refer to the [React Native documentation][react-native-url] for more
|
||||
design details, and for information about the [Gesture Responder
|
||||
Please refer to the [React Native documentation][react-native-url] for the
|
||||
overall API, design details, and information about the [Gesture Responder
|
||||
system](https://facebook.github.io/react-native/docs/gesture-responder-system.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
|
||||
|
||||
* [Getting started](https://github.com/necolas/react-native-web/blob/master/packages/website/guides/getting-started.md)
|
||||
* [Style](https://github.com/necolas/react-native-web/blob/master/packages/website/guides/style.md)
|
||||
* [Accessibility](https://github.com/necolas/react-native-web/blob/master/packages/website/guides/accessibility.md)
|
||||
* [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)
|
||||
* [Experimental / unstable use](https://github.com/necolas/react-native-web/blob/master/packages/website/guides/advanced.md)
|
||||
These guides provide a detailed look at using React Native to create accessible
|
||||
web experiences. Certain web-specific patterns are documented in the "web
|
||||
recipes" guide.
|
||||
|
||||
* [Getting started](https://github.com/necolas/react-native-web/blob/master/docs/guides/getting-started.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)
|
||||
* [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
|
||||
|
||||
There are examples [on the website][website-url] ([source
|
||||
code](https://github.com/necolas/react-native-web/blob/master/packages/website).
|
||||
And all the [React Native examples][examples-url] ([source
|
||||
code](https://github.com/necolas/react-native-web/blob/master/packages/examples))
|
||||
are also available. Here is an example to get you started:
|
||||
Check out all the [React Native examples][examples-url] ([source
|
||||
code](https://github.com/necolas/react-native-web/blob/master/packages/examples)).
|
||||
There are more examples [on the website][website-url] ([source
|
||||
code](https://github.com/necolas/react-native-web/blob/master/packages/website)).
|
||||
And here is a simple example to get you started:
|
||||
|
||||
```js
|
||||
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
|
||||
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
|
||||
|
||||
React Native v0.55
|
||||
|
||||
### Components
|
||||
|
||||
| Name | Status | Notes |
|
||||
| :----------------------- | :------------------ | :---- |
|
||||
| ActivityIndicator | Available | |
|
||||
| ART | Available | |
|
||||
| Button | Available | |
|
||||
| CheckBox | Available | |
|
||||
| FlatList | Available | |
|
||||
| Image | Available (partial) | Missing multiple sources and HTTP headers. |
|
||||
| ImageBackground | Available | |
|
||||
| KeyboardAvoidingView | Available (mock) | |
|
||||
| ListView | Available | |
|
||||
| Modal | Not started | |
|
||||
| Picker | Available | |
|
||||
| RefreshControl | Not started | |
|
||||
| SafeAreaView | Available | |
|
||||
| ScrollView | Available (partial) | Missing momentum scroll events. |
|
||||
| SectionList | Available | |
|
||||
| Slider | Not started | |
|
||||
| StatusBar | Mock | |
|
||||
| SwipeableFlatList | Available | |
|
||||
| SwipeableListView | Available | |
|
||||
| Switch | Available | |
|
||||
| Text | Available (partial) | Missing `onLongPress` support. |
|
||||
| TextInput | Available (partial) | Missing rich text features and auto-expanding behaviour. |
|
||||
| Touchable | Available | Includes additional support for mouse and keyboard interactions. |
|
||||
| TouchableHighlight | Available | |
|
||||
| TouchableNativeFeedback | Not started | |
|
||||
| TouchableOpacity | Available | |
|
||||
| TouchableWithoutFeedback | Available | |
|
||||
| View | Available | |
|
||||
| VirtualizedList | Available | |
|
||||
| WebView | Not started | |
|
||||
| YellowBox | Mock | |
|
||||
| Name | Status | Notes |
|
||||
| :----------------------- | :----- | :---- |
|
||||
| ActivityIndicator | ✓ | |
|
||||
| ART | ✓ | |
|
||||
| Button | ✓ | |
|
||||
| CheckBox | ✓ | |
|
||||
| FlatList | ✓ | |
|
||||
| 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 | ✓ | |
|
||||
| KeyboardAvoidingView | (✓) | Mock. No equivalent web APIs. |
|
||||
| ListView | ✓ | |
|
||||
| Modal | ✘ | Not started ([#1020](https://github.com/necolas/react-native-web/issues/1020)). |
|
||||
| Picker | ✓ | |
|
||||
| RefreshControl | ✘ | Not started ([#1027](https://github.com/necolas/react-native-web/issues/1027)). |
|
||||
| SafeAreaView | ✓ | |
|
||||
| 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 | ✓ | |
|
||||
| Slider | ✘ | Not started ([#1022](https://github.com/necolas/react-native-web/issues/1022)). |
|
||||
| StatusBar | (✓) | Mock. No equivalent web APIs. |
|
||||
| SwipeableFlatList | ✓ | |
|
||||
| SwipeableListView | ✓ | |
|
||||
| Switch | ✓ | |
|
||||
| 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 | ✓ | 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 | ✓ | Includes additional support for mouse and keyboard interactions. |
|
||||
| TouchableHighlight | ✓ | |
|
||||
| TouchableNativeFeedback | ✘ | Not started ([#1024](https://github.com/necolas/react-native-web/issues/1024)). |
|
||||
| TouchableOpacity | ✓ | |
|
||||
| TouchableWithoutFeedback | ✓ | |
|
||||
| View | ✓ | |
|
||||
| VirtualizedList | ✓ | |
|
||||
| WebView | ✘ | Not started ([1025](https://github.com/necolas/react-native-web/issues/1025)). |
|
||||
| YellowBox | (✓) | Mock. No YellowBox functionality. |
|
||||
|
||||
### Modules
|
||||
|
||||
| Name | Status | Notes |
|
||||
| :----------------------- | :------------------ | :---- |
|
||||
| AccessibilityInfo | Mock | No equivalent web APIs. |
|
||||
| Alert | Not started | |
|
||||
| Animated | Available | Missing `useNativeDriver` support. |
|
||||
| AppRegistry | Available | Includes additional support for SSR with `getApplication`. |
|
||||
| AppState | Available | |
|
||||
| AsyncStorage | Available | |
|
||||
| BackHandler | Mock | No equivalent web APIs. |
|
||||
| CameraRoll | Not started | No equivalent web APIs. |
|
||||
| Clipboard | Available | |
|
||||
| ColorPropType | Available | |
|
||||
| DeviceInfo | Available (partial) | |
|
||||
| Dimensions | Available | |
|
||||
| Easing | Available | |
|
||||
| EdgeInsetsPropType | Available | |
|
||||
| Geolocation | Available | |
|
||||
| I18nManager | Available | Includes additional support for runtime switch to RTL. |
|
||||
| ImageEditor | Not started | No equivalent web APIs. |
|
||||
| ImageStore | Not started | No equivalent web APIs. |
|
||||
| InteractionManager | Available (partial) | |
|
||||
| Keyboard | Mock | |
|
||||
| LayoutAnimation | Available (partial) | Missing transform to web animation. |
|
||||
| Linking | Available | |
|
||||
| NativeEventEmitter | Available | |
|
||||
| NativeMethodsMixin | Available | |
|
||||
| NativeModules | Available (partial) | Mocked. Missing ability to load native modules. |
|
||||
| NetInfo | Available (partial) | Missing functionality to detect extensive connections. |
|
||||
| PanResponder | Available | |
|
||||
| PixelRatio | Available | |
|
||||
| Platform | Available | |
|
||||
| PointPropType | Available | |
|
||||
| Settings | Not started | |
|
||||
| Share | Available | Only available over HTTPS. Read about the [Web Share API](https://wicg.github.io/web-share/). |
|
||||
| StyleSheet | Available | |
|
||||
| TextPropTypes | Available | |
|
||||
| UIManager | Available | |
|
||||
| Vibration | Available | |
|
||||
| ViewPropTypes | Available | |
|
||||
| Name | Status | Notes |
|
||||
| :----------------------- | :----- | :---- |
|
||||
| AccessibilityInfo | (✓) | Mock. No equivalent web APIs. |
|
||||
| Alert | ✘ | Not started ([#1026](https://github.com/necolas/react-native-web/issues/1026)). |
|
||||
| Animated | ✓ | Missing `useNativeDriver` support. |
|
||||
| AppRegistry | ✓ | Includes additional support for server rendering with `getApplication`. |
|
||||
| AppState | ✓ | |
|
||||
| AsyncStorage | ✓ | |
|
||||
| BackHandler | (✓) | Mock. No equivalent web APIs. |
|
||||
| CameraRoll | ✘ | No equivalent web APIs. |
|
||||
| Clipboard | ✓ | |
|
||||
| ColorPropType | ✓ | |
|
||||
| DeviceInfo | (✓) | Limited information. |
|
||||
| Dimensions | ✓ | |
|
||||
| Easing | ✓ | |
|
||||
| EdgeInsetsPropType | ✓ | |
|
||||
| Geolocation | ✓ | |
|
||||
| I18nManager | ✓ | Includes additional support for runtime switch to RTL. |
|
||||
| ImageEditor | ✘ | No equivalent web APIs. |
|
||||
| ImageStore | ✘ | No equivalent web APIs. |
|
||||
| InteractionManager | (✓) | |
|
||||
| Keyboard | (✓) | Mock. |
|
||||
| LayoutAnimation | (✓) | Missing translation to web animations. |
|
||||
| Linking | ✓ | |
|
||||
| NativeEventEmitter | ✓ | |
|
||||
| NativeMethodsMixin | ✓ | |
|
||||
| NativeModules | (✓) | Mocked. Missing ability to load native modules. |
|
||||
| NetInfo | ✓ | Missing functionality to detect expensive connections as there are no equivalent web APIs. |
|
||||
| PanResponder | ✓ | |
|
||||
| PixelRatio | ✓ | |
|
||||
| Platform | ✓ | |
|
||||
| PointPropType | ✓ | |
|
||||
| Settings | ✘ | No equivalent web APIs. |
|
||||
| Share | ✓ | Only available over HTTPS. Read about the [Web Share API](https://wicg.github.io/web-share/). |
|
||||
| StyleSheet | ✓ | |
|
||||
| TextPropTypes | ✓ | |
|
||||
| UIManager | ✓ | |
|
||||
| Vibration | ✓ | |
|
||||
| ViewPropTypes | ✓ | |
|
||||
|
||||
## 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>
|
||||
```
|
||||
116
docs/guides/getting-started.md
Normal file
116
docs/guides/getting-started.md
Normal file
@@ -0,0 +1,116 @@
|
||||
# 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"
|
||||
}
|
||||
}]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 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 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.
|
||||
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,
|
||||
"version": "0.8.1",
|
||||
"version": "0.9.6",
|
||||
"name": "react-native-web-monorepo",
|
||||
"scripts": {
|
||||
"clean": "del ./packages/*/dist",
|
||||
@@ -39,8 +39,8 @@
|
||||
"babel-preset-react-native": "^4.0.0",
|
||||
"caniuse-api": "^2.0.0",
|
||||
"del-cli": "^1.1.0",
|
||||
"enzyme": "^3.3.0",
|
||||
"enzyme-adapter-react-16": "^1.1.0",
|
||||
"enzyme": "^3.6.0",
|
||||
"enzyme-adapter-react-16": "^1.5.0",
|
||||
"enzyme-to-json": "^3.3.3",
|
||||
"eslint": "^4.19.1",
|
||||
"eslint-config-prettier": "^2.9.0",
|
||||
@@ -54,10 +54,10 @@
|
||||
"lint-staged": "^7.1.0",
|
||||
"npm-run-all": "^4.1.3",
|
||||
"prettier": "^1.12.1",
|
||||
"react": "^16.3.2",
|
||||
"react-art": "^16.3.2",
|
||||
"react-dom": "^16.3.2",
|
||||
"react-test-renderer": "^16.3.2"
|
||||
"react": "^16.5.1",
|
||||
"react-art": "^16.5.1",
|
||||
"react-dom": "^16.5.1",
|
||||
"react-test-renderer": "^16.5.1"
|
||||
},
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "babel-plugin-react-native-web",
|
||||
"version": "0.8.1",
|
||||
"version": "0.9.6",
|
||||
"description": "Babel plugin for React Native for Web",
|
||||
"main": "index.js",
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1,37 +1,37 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "benchmarks",
|
||||
"version": "0.8.1",
|
||||
"version": "0.9.6",
|
||||
"scripts": {
|
||||
"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 -"
|
||||
},
|
||||
"dependencies": {
|
||||
"aphrodite": "^2.2.0",
|
||||
"classnames": "^2.2.5",
|
||||
"d3-scale-chromatic": "^1.2.0",
|
||||
"emotion": "^9.1.3",
|
||||
"fela": "^6.1.7",
|
||||
"aphrodite": "^2.2.2",
|
||||
"classnames": "^2.2.6",
|
||||
"d3-scale-chromatic": "^1.3.0",
|
||||
"emotion": "^9.2.4",
|
||||
"fela": "^6.1.9",
|
||||
"glamor": "2.20.40",
|
||||
"radium": "^0.24.0",
|
||||
"react": "^16.3.2",
|
||||
"react-dom": "^16.3.2",
|
||||
"react-fela": "^7.2.0",
|
||||
"react-jss": "^8.4.0",
|
||||
"react-native-web": "0.8.1",
|
||||
"reactxp": "^1.1.1",
|
||||
"styled-components": "^3.2.6",
|
||||
"styled-jsx": "^2.2.6",
|
||||
"react": "^16.5.1",
|
||||
"react-dom": "^16.5.1",
|
||||
"react-fela": "^7.3.1",
|
||||
"react-jss": "^8.6.1",
|
||||
"react-native-web": "0.9.6",
|
||||
"reactxp": "^1.3.0",
|
||||
"styled-components": "^3.3.3",
|
||||
"styled-jsx": "^2.2.7",
|
||||
"styletron-engine-atomic": "^1.0.5",
|
||||
"styletron-react": "^4.2.1"
|
||||
"styletron-react": "^4.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-plugin-react-native-web": "0.8.1",
|
||||
"css-loader": "^0.28.11",
|
||||
"babel-plugin-react-native-web": "0.9.6",
|
||||
"css-loader": "^1.0.0",
|
||||
"style-loader": "^0.21.0",
|
||||
"url-loader": "^1.0.1",
|
||||
"webpack": "^4.8.1",
|
||||
"webpack-bundle-analyzer": "^2.11.1",
|
||||
"webpack-cli": "^2.1.3"
|
||||
"webpack": "^4.15.1",
|
||||
"webpack-bundle-analyzer": "^2.13.1",
|
||||
"webpack-cli": "^3.0.8"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ module.exports = {
|
||||
'style-loader',
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: { module: true, localIdentName: '[hash:base64:8]' }
|
||||
options: { modules: true, localIdentName: '[hash:base64:8]' }
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "react-native-examples",
|
||||
"version": "0.8.1",
|
||||
"version": "0.9.6",
|
||||
"scripts": {
|
||||
"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 -"
|
||||
},
|
||||
"dependencies": {
|
||||
"babel-runtime": "^6.26.0",
|
||||
"react": "^16.3.2",
|
||||
"react-dom": "^16.3.2",
|
||||
"react-native-web": "0.8.1"
|
||||
"react": "^16.5.1",
|
||||
"react-dom": "^16.5.1",
|
||||
"react-native-web": "0.9.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-plugin-react-native-web": "0.8.1",
|
||||
"babel-plugin-react-native-web": "0.9.6",
|
||||
"babel-plugin-transform-runtime": "^6.23.0",
|
||||
"file-loader": "^1.1.11",
|
||||
"webpack": "^4.8.1",
|
||||
|
||||
@@ -29,7 +29,7 @@ class BasicStorageExample extends React.Component<{}, $FlowFixMeState> {
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this._loadInitialState().done();
|
||||
this._loadInitialState()//.done();
|
||||
}
|
||||
|
||||
_loadInitialState = async () => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-native-web",
|
||||
"version": "0.8.1",
|
||||
"version": "0.9.6",
|
||||
"description": "React Native for Web",
|
||||
"module": "dist/index.js",
|
||||
"main": "dist/cjs/index.js",
|
||||
@@ -8,6 +8,7 @@
|
||||
"files": [
|
||||
"dist",
|
||||
"jest",
|
||||
"jest-preset.json",
|
||||
"src",
|
||||
"!**/__tests__"
|
||||
],
|
||||
@@ -24,9 +25,9 @@
|
||||
"react-timer-mixin": "^0.13.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "16.x.x",
|
||||
"react-art": "16.x.x",
|
||||
"react-dom": "16.x.x"
|
||||
"react": ">=16.5.1",
|
||||
"react-art": ">=16.5.1",
|
||||
"react-dom": ">=16.5.1"
|
||||
},
|
||||
"author": "Nicolas Gallagher",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -8,11 +8,16 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
let clipboardAvailable;
|
||||
|
||||
export default class Clipboard {
|
||||
static isAvailable() {
|
||||
return (
|
||||
typeof document.queryCommandSupported === 'function' && document.queryCommandSupported('copy')
|
||||
);
|
||||
if (clipboardAvailable === undefined) {
|
||||
clipboardAvailable =
|
||||
typeof document.queryCommandSupported === 'function' &&
|
||||
document.queryCommandSupported('copy');
|
||||
}
|
||||
return clipboardAvailable;
|
||||
}
|
||||
|
||||
static getString(): Promise<string> {
|
||||
|
||||
@@ -18,10 +18,11 @@ import StyleSheetPropType from '../../modules/StyleSheetPropType';
|
||||
import StyleSheet from '../StyleSheet';
|
||||
import TextPropTypes from '../Text/TextPropTypes';
|
||||
import { arrayOf, bool, func, number, oneOfType, string } from 'prop-types';
|
||||
import ViewPropTypes, { type ViewProps } from '../ViewPropTypes';
|
||||
|
||||
const pickerStyleType = StyleSheetPropType(PickerStylePropTypes);
|
||||
|
||||
type Props = {
|
||||
type Props = ViewProps & {
|
||||
children?: PickerItem | Array<typeof PickerItem>,
|
||||
enabled?: boolean,
|
||||
onValueChange?: Function,
|
||||
@@ -36,6 +37,7 @@ type Props = {
|
||||
|
||||
class Picker extends Component<Props> {
|
||||
static propTypes = {
|
||||
...ViewPropTypes,
|
||||
children: oneOfType([PickerItemPropType, arrayOf(PickerItemPropType)]),
|
||||
enabled: bool,
|
||||
onValueChange: func,
|
||||
@@ -56,8 +58,10 @@ class Picker extends Component<Props> {
|
||||
/* eslint-disable */
|
||||
itemStyle,
|
||||
mode,
|
||||
prompt
|
||||
prompt,
|
||||
onValueChange,
|
||||
/* eslint-enable */
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
return createElement('select', {
|
||||
@@ -66,7 +70,8 @@ class Picker extends Component<Props> {
|
||||
onChange: this._handleChange,
|
||||
style: [styles.initial, style],
|
||||
testID,
|
||||
value: selectedValue
|
||||
value: selectedValue,
|
||||
...otherProps
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -266,7 +266,6 @@ const ScrollView = createReactClass({
|
||||
const commonStyle = {
|
||||
flexGrow: 1,
|
||||
flexShrink: 1,
|
||||
overscrollBehavior: 'contain',
|
||||
// Enable hardware compositing in modern browsers.
|
||||
// Creates a new layer with its own backing surface that can significantly
|
||||
// improve scroll performance.
|
||||
@@ -280,15 +279,13 @@ const styles = StyleSheet.create({
|
||||
...commonStyle,
|
||||
flexDirection: 'column',
|
||||
overflowX: 'hidden',
|
||||
overflowY: 'auto',
|
||||
touchAction: 'pan-y'
|
||||
overflowY: 'auto'
|
||||
},
|
||||
baseHorizontal: {
|
||||
...commonStyle,
|
||||
flexDirection: 'row',
|
||||
overflowX: 'auto',
|
||||
overflowY: 'hidden',
|
||||
touchAction: 'pan-x'
|
||||
overflowY: 'hidden'
|
||||
},
|
||||
contentContainerHorizontal: {
|
||||
flexDirection: 'row'
|
||||
|
||||
@@ -100,6 +100,8 @@ StyleSheetValidation.addValidStylePropTypes({
|
||||
fill: string,
|
||||
float: oneOf(['end', 'left', 'none', 'right', 'start']),
|
||||
listStyle: string,
|
||||
objectFit: oneOf(['fill', 'contain', 'cover', 'none', 'scale-down']),
|
||||
objectPosition: string,
|
||||
pointerEvents: string,
|
||||
tableLayout: string,
|
||||
/* @private */
|
||||
|
||||
@@ -247,7 +247,9 @@ describe('StyleSheet/createReactDOMStyle', () => {
|
||||
textDecorationStyle: 'dashed'
|
||||
})
|
||||
).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', () => {
|
||||
const initial = {
|
||||
float: 'start',
|
||||
textAlign: 'end'
|
||||
textAlign: 'end',
|
||||
transitionProperty: 'marginStart'
|
||||
};
|
||||
const expected = {
|
||||
float: 'left',
|
||||
textAlign: 'right'
|
||||
textAlign: 'right',
|
||||
transitionProperty: 'marginLeft'
|
||||
};
|
||||
expect(i18nStyle(initial)).toEqual(expected);
|
||||
});
|
||||
@@ -56,7 +58,8 @@ describe('StyleSheet/i18nStyle', () => {
|
||||
clear: 'left',
|
||||
float: 'left',
|
||||
textAlign: 'right',
|
||||
textShadowOffset: { width: '1rem', height: 10 }
|
||||
textShadowOffset: { width: '1rem', height: 10 },
|
||||
transitionProperty: 'marginLeft'
|
||||
};
|
||||
expect(i18nStyle(initial)).toEqual(initial);
|
||||
});
|
||||
@@ -116,11 +119,13 @@ describe('StyleSheet/i18nStyle', () => {
|
||||
test('converts end/start values', () => {
|
||||
const initial = {
|
||||
float: 'start',
|
||||
textAlign: 'end'
|
||||
textAlign: 'end',
|
||||
transitionProperty: 'marginStart'
|
||||
};
|
||||
const expected = {
|
||||
float: 'right',
|
||||
textAlign: 'left'
|
||||
textAlign: 'left',
|
||||
transitionProperty: 'marginRight'
|
||||
};
|
||||
expect(i18nStyle(initial)).toEqual(expected);
|
||||
});
|
||||
@@ -140,7 +145,8 @@ describe('StyleSheet/i18nStyle', () => {
|
||||
clear: 'left',
|
||||
float: 'left',
|
||||
textAlign: 'right',
|
||||
textShadowOffset: { width: '1rem', height: 10 }
|
||||
textShadowOffset: { width: '1rem', height: 10 },
|
||||
transitionProperty: 'marginLeft'
|
||||
};
|
||||
expect(i18nStyle(initial)).toEqual(initial);
|
||||
});
|
||||
@@ -183,11 +189,13 @@ describe('StyleSheet/i18nStyle', () => {
|
||||
test('converts end/start values', () => {
|
||||
const initial = {
|
||||
float: 'start',
|
||||
textAlign: 'end'
|
||||
textAlign: 'end',
|
||||
transitionProperty: 'marginStart'
|
||||
};
|
||||
const expected = {
|
||||
float: 'right',
|
||||
textAlign: 'left'
|
||||
textAlign: 'left',
|
||||
transitionProperty: 'marginRight'
|
||||
};
|
||||
expect(i18nStyle(initial)).toEqual(expected);
|
||||
});
|
||||
@@ -212,12 +220,14 @@ describe('StyleSheet/i18nStyle', () => {
|
||||
const initial = {
|
||||
float: 'left',
|
||||
textAlign: 'right',
|
||||
textShadowOffset: { width: '1rem', height: 10 }
|
||||
textShadowOffset: { width: '1rem', height: 10 },
|
||||
transitionProperty: 'marginLeft'
|
||||
};
|
||||
const expected = {
|
||||
float: 'right',
|
||||
textAlign: 'left',
|
||||
textShadowOffset: { width: '-1rem', height: 10 }
|
||||
textShadowOffset: { width: '-1rem', height: 10 },
|
||||
transitionProperty: 'marginRight'
|
||||
};
|
||||
expect(i18nStyle(initial)).toEqual(expected);
|
||||
});
|
||||
|
||||
@@ -95,10 +95,18 @@ const resolveShadow = (resolvedStyle, style) => {
|
||||
|
||||
const resolveTextDecoration = (resolvedStyle, style) => {
|
||||
const { textDecorationColor, textDecorationLine, textDecorationStyle } = style;
|
||||
const color = normalizeColor(textDecorationColor) || '';
|
||||
const lineStyle = textDecorationStyle || '';
|
||||
const color = normalizeColor(textDecorationColor);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -185,6 +193,16 @@ const createReducer = (style, styleProps) => {
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO: remove once this issue is fixed
|
||||
// https://github.com/rofrischmann/inline-style-prefixer/issues/159
|
||||
case 'backgroundClip': {
|
||||
if (value === 'text') {
|
||||
resolvedStyle.backgroundClip = value;
|
||||
resolvedStyle.WebkitBackgroundClip = value;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'display': {
|
||||
resolvedStyle.display = value;
|
||||
// A flex container in React Native has these defaults which should be
|
||||
|
||||
@@ -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') {
|
||||
nextStyle[prop] = value;
|
||||
nextStyle[prop].width = additiveInverse(value.width);
|
||||
|
||||
@@ -7,6 +7,11 @@ import Switch from '..';
|
||||
const checkboxSelector = 'input[type="checkbox"]';
|
||||
|
||||
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', () => {
|
||||
test('when "false" a default checkbox is rendered', () => {
|
||||
const component = shallow(<Switch />);
|
||||
|
||||
@@ -67,6 +67,7 @@ class Switch extends Component<*> {
|
||||
|
||||
render() {
|
||||
const {
|
||||
accessibilityLabel,
|
||||
activeThumbColor,
|
||||
activeTrackColor,
|
||||
disabled,
|
||||
@@ -115,6 +116,7 @@ class Switch extends Component<*> {
|
||||
];
|
||||
|
||||
const nativeControl = createElement('input', {
|
||||
accessibilityLabel,
|
||||
checked: value,
|
||||
disabled: disabled,
|
||||
onBlur: this._handleFocusState,
|
||||
|
||||
@@ -21,6 +21,7 @@ const TextPropTypes = {
|
||||
accessible: bool,
|
||||
children: any,
|
||||
importantForAccessibility: oneOf(['auto', 'no', 'no-hide-descendants', 'yes']),
|
||||
nativeID: string,
|
||||
numberOfLines: number,
|
||||
onBlur: func,
|
||||
onContextMenu: func,
|
||||
|
||||
@@ -7,12 +7,14 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import ColorPropType from '../ColorPropType';
|
||||
import TextStylePropTypes from '../Text/TextStylePropTypes';
|
||||
import { oneOf } from 'prop-types';
|
||||
|
||||
const TextInputStylePropTypes = {
|
||||
...TextStylePropTypes,
|
||||
/* @platform web */
|
||||
caretColor: ColorPropType,
|
||||
resize: oneOf(['none', 'vertical', 'horizontal', 'both'])
|
||||
};
|
||||
|
||||
|
||||
@@ -330,18 +330,27 @@ describe('components/TextInput', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('prop "onSelectionChange"', done => {
|
||||
const input = findNativeInput(
|
||||
mount(<TextInput defaultValue="12345" onSelectionChange={onSelectionChange} />)
|
||||
);
|
||||
input.simulate('select', {
|
||||
target: { selectionStart: 0, selectionEnd: 3 }
|
||||
describe('prop "onSelectionChange"', () => {
|
||||
test('is called on select', done => {
|
||||
const input = findNativeInput(
|
||||
mount(<TextInput defaultValue="12345" onSelectionChange={onSelectionChange} />)
|
||||
);
|
||||
input.simulate('select', {
|
||||
target: { selectionStart: 0, selectionEnd: 3 }
|
||||
});
|
||||
function onSelectionChange(e) {
|
||||
expect(e.nativeEvent.selection.end).toEqual(3);
|
||||
expect(e.nativeEvent.selection.start).toEqual(0);
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
test('is called on change', () => {
|
||||
const onSelectionChange = jest.fn();
|
||||
const input = findNativeInput(mount(<TextInput onSelectionChange={onSelectionChange} />));
|
||||
input.simulate('change');
|
||||
expect(onSelectionChange).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
function onSelectionChange(e) {
|
||||
expect(e.nativeEvent.selection.end).toEqual(3);
|
||||
expect(e.nativeEvent.selection.start).toEqual(0);
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
describe('prop "onSubmitEditing"', () => {
|
||||
@@ -368,6 +377,8 @@ describe('components/TextInput', () => {
|
||||
|
||||
test('multi-line input with "blurOnSubmit" triggers "onSubmitEditing"', () => {
|
||||
const onSubmitEditing = jest.fn();
|
||||
const preventDefault = jest.fn();
|
||||
|
||||
const input = findNativeTextarea(
|
||||
mount(
|
||||
<TextInput
|
||||
@@ -380,10 +391,13 @@ describe('components/TextInput', () => {
|
||||
);
|
||||
|
||||
// shift+enter should enter newline, not submit
|
||||
input.simulate('keyPress', { which: 13, shiftKey: true });
|
||||
input.simulate('keyPress', { which: 13 });
|
||||
expect(onSubmitEditing).toHaveBeenCalledTimes(1);
|
||||
input.simulate('keyPress', { which: 13, preventDefault, shiftKey: true });
|
||||
expect(onSubmitEditing).not.toHaveBeenCalledWith(expect.objectContaining({ shiftKey: true }));
|
||||
expect(preventDefault).not.toHaveBeenCalled();
|
||||
|
||||
input.simulate('keyPress', { which: 13, preventDefault });
|
||||
expect(onSubmitEditing).toHaveBeenCalledTimes(1);
|
||||
expect(preventDefault).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -81,6 +81,7 @@ class TextInput extends Component<*> {
|
||||
clearTextOnFocus: bool,
|
||||
defaultValue: string,
|
||||
editable: bool,
|
||||
inputAccessoryViewID: string,
|
||||
keyboardType: oneOf([
|
||||
'default',
|
||||
'email-address',
|
||||
@@ -142,7 +143,7 @@ class TextInput extends Component<*> {
|
||||
editable: true,
|
||||
keyboardType: 'default',
|
||||
multiline: false,
|
||||
numberOfLines: 2,
|
||||
numberOfLines: 1,
|
||||
secureTextEntry: false,
|
||||
style: emptyObject
|
||||
};
|
||||
@@ -183,28 +184,41 @@ class TextInput extends Component<*> {
|
||||
blurOnSubmit,
|
||||
clearTextOnFocus,
|
||||
onChangeText,
|
||||
onLayout,
|
||||
onSelectionChange,
|
||||
onSubmitEditing,
|
||||
selection,
|
||||
selectTextOnFocus,
|
||||
spellCheck,
|
||||
/* react-native compat */
|
||||
accessibilityViewIsModal,
|
||||
allowFontScaling,
|
||||
caretHidden,
|
||||
clearButtonMode,
|
||||
dataDetectorTypes,
|
||||
disableFullscreenUI,
|
||||
enablesReturnKeyAutomatically,
|
||||
hitSlop,
|
||||
inlineImageLeft,
|
||||
inlineImagePadding,
|
||||
inputAccessoryViewID,
|
||||
keyboardAppearance,
|
||||
needsOffscreenAlphaCompositing,
|
||||
onAccessibilityTap,
|
||||
onContentSizeChange,
|
||||
onEndEditing,
|
||||
onMagicTap,
|
||||
onScroll,
|
||||
removeClippedSubviews,
|
||||
renderToHardwareTextureAndroid,
|
||||
returnKeyLabel,
|
||||
returnKeyType,
|
||||
scrollEnabled,
|
||||
selectionColor,
|
||||
selectionState,
|
||||
shouldRasterizeIOS,
|
||||
textBreakStrategy,
|
||||
textContentType,
|
||||
underlineColorAndroid,
|
||||
/* eslint-enable */
|
||||
...otherProps
|
||||
@@ -281,6 +295,7 @@ class TextInput extends Component<*> {
|
||||
if (onChangeText) {
|
||||
onChangeText(text);
|
||||
}
|
||||
this._handleSelectionChange(e);
|
||||
};
|
||||
|
||||
_handleFocus = e => {
|
||||
@@ -375,6 +390,8 @@ class TextInput extends Component<*> {
|
||||
|
||||
if (!e.isDefaultPrevented() && e.which === 13 && !e.shiftKey) {
|
||||
if ((blurOnSubmit || !multiline) && onSubmitEditing) {
|
||||
// prevent "Enter" from inserting a newline
|
||||
e.preventDefault();
|
||||
e.nativeEvent = { target: e.target, text: e.target.value };
|
||||
onSubmitEditing(e);
|
||||
}
|
||||
|
||||
@@ -696,9 +696,16 @@ const TouchableMixin = {
|
||||
const touch = TouchEventUtils.extractSingleTouch(e.nativeEvent);
|
||||
const pageX = touch && touch.pageX;
|
||||
const pageY = touch && touch.pageY;
|
||||
const locationX = touch && touch.locationX;
|
||||
const locationY = touch && touch.locationY;
|
||||
this.pressInLocation = { pageX, pageY, locationX, locationY };
|
||||
this.pressInLocation = {
|
||||
pageX,
|
||||
pageY,
|
||||
get locationX() {
|
||||
return touch && touch.locationX;
|
||||
},
|
||||
get locationY() {
|
||||
return touch && touch.locationY;
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
_getDistanceBetweenPoints: function(aX: number, aY: number, bX: number, bY: number) {
|
||||
|
||||
@@ -38,6 +38,7 @@ export type ViewProps = {
|
||||
children?: any,
|
||||
hitSlop?: EdgeInsetsProp,
|
||||
importantForAccessibility?: 'auto' | 'yes' | 'no' | 'no-hide-descendants',
|
||||
nativeID?: string,
|
||||
onBlur?: Function,
|
||||
onClick?: Function,
|
||||
onClickCapture?: Function,
|
||||
@@ -87,6 +88,7 @@ const ViewPropTypes = {
|
||||
children: any,
|
||||
hitSlop: EdgeInsetsPropType,
|
||||
importantForAccessibility: oneOf(['auto', 'no', 'no-hide-descendants', 'yes']),
|
||||
nativeID: string,
|
||||
onBlur: func,
|
||||
onClick: func,
|
||||
onClickCapture: func,
|
||||
|
||||
@@ -8,6 +8,7 @@ const whitelist = {
|
||||
children: true,
|
||||
disabled: true,
|
||||
importantForAccessibility: true,
|
||||
nativeID: true,
|
||||
onBlur: true,
|
||||
onContextMenu: true,
|
||||
onFocus: true,
|
||||
@@ -38,19 +39,21 @@ const whitelist = {
|
||||
pointerEvents: true,
|
||||
style: true,
|
||||
testID: true,
|
||||
// escape-hatches for ScrollView
|
||||
/* @platform web */
|
||||
onScroll: true,
|
||||
onWheel: true,
|
||||
// escape-hatches for Touchable keyboard support
|
||||
// keyboard events
|
||||
onKeyDown: true,
|
||||
onKeyPress: true,
|
||||
onKeyUp: true,
|
||||
// escape-hatches for creating hover effects
|
||||
// mouse events (e.g, hover effects)
|
||||
onMouseDown: true,
|
||||
onMouseEnter: true,
|
||||
onMouseLeave: true,
|
||||
onMouseMove: true,
|
||||
onMouseOver: true,
|
||||
onMouseOut: true,
|
||||
onMouseUp: true,
|
||||
// unstable escape-hatches for web
|
||||
className: true,
|
||||
href: true,
|
||||
|
||||
@@ -36,24 +36,38 @@ describe('modules/createElement', () => {
|
||||
expect(component.find('div').length).toBe(1);
|
||||
});
|
||||
|
||||
[{ disabled: true }, { disabled: false }].forEach(({ disabled }) => {
|
||||
describe(`value is "button" and disabled is "${disabled}"`, () => {
|
||||
[{ name: 'Enter', which: 13 }, { name: 'Space', which: 32 }].forEach(({ name, which }) => {
|
||||
test(`"onClick" is ${disabled ? 'not ' : ''}called when "${name}" is pressed`, () => {
|
||||
const onClick = jest.fn();
|
||||
const component = shallow(
|
||||
createElement('span', { accessibilityRole: 'button', disabled, onClick })
|
||||
);
|
||||
component.find('span').simulate('keyPress', {
|
||||
isDefaultPrevented() {},
|
||||
nativeEvent: {},
|
||||
preventDefault() {},
|
||||
which
|
||||
});
|
||||
expect(onClick).toHaveBeenCalledTimes(disabled ? 0 : 1);
|
||||
const testRole = ({ accessibilityRole, disabled }) => {
|
||||
[{ key: 'Enter', which: 13 }, { key: 'Space', which: 32 }].forEach(({ key, which }) => {
|
||||
test(`"onClick" is ${disabled ? 'not ' : ''}called when "${key}" key is pressed`, () => {
|
||||
const onClick = jest.fn();
|
||||
const component = shallow(
|
||||
createElement('span', { accessibilityRole, disabled, onClick })
|
||||
);
|
||||
component.find('span').simulate('keyPress', {
|
||||
isDefaultPrevented() {},
|
||||
nativeEvent: {},
|
||||
preventDefault() {},
|
||||
which
|
||||
});
|
||||
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 createDOMProps from '../../modules/createDOMProps';
|
||||
import { injectEventPluginsByName } from 'react-dom/unstable-native-dependencies';
|
||||
import normalizeNativeEvent from '../../modules/normalizeNativeEvent';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import ResponderEventPlugin from '../../modules/ResponderEventPlugin';
|
||||
|
||||
const { EventPluginHub } = ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
|
||||
|
||||
EventPluginHub.injection.injectEventPluginsByName({
|
||||
injectEventPluginsByName({
|
||||
ResponderEventPlugin
|
||||
});
|
||||
|
||||
@@ -48,7 +46,7 @@ const eventHandlerNames = {
|
||||
const adjustProps = domProps => {
|
||||
const { onClick, onResponderRelease, role } = domProps;
|
||||
|
||||
const isButtonRole = role === 'button';
|
||||
const isButtonLikeRole = AccessibilityUtil.buttonLikeRoles[role];
|
||||
const isDisabled = AccessibilityUtil.isDisabled(domProps);
|
||||
const isLinkRole = role === 'link';
|
||||
|
||||
@@ -56,7 +54,7 @@ const adjustProps = domProps => {
|
||||
const prop = domProps[propName];
|
||||
const isEventHandler = typeof prop === 'function' && eventHandlerNames[propName];
|
||||
if (isEventHandler) {
|
||||
if (isButtonRole && isDisabled) {
|
||||
if (isButtonLikeRole && isDisabled) {
|
||||
domProps[propName] = undefined;
|
||||
} else {
|
||||
// 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.
|
||||
if (isButtonRole && !isDisabled) {
|
||||
// Button-like roles should trigger 'onClick' if SPACE or ENTER keys are pressed.
|
||||
if (isButtonLikeRole && !isDisabled) {
|
||||
domProps.onKeyPress = function(e) {
|
||||
if (!e.isDefaultPrevented() && (e.which === 13 || e.which === 32)) {
|
||||
e.preventDefault();
|
||||
|
||||
@@ -8,4 +8,15 @@
|
||||
*/
|
||||
|
||||
import { findDOMNode } from 'react-dom';
|
||||
export default findDOMNode;
|
||||
|
||||
const findNodeHandle = component => {
|
||||
let node;
|
||||
|
||||
try {
|
||||
node = findDOMNode(component);
|
||||
} catch (e) {}
|
||||
|
||||
return node;
|
||||
};
|
||||
|
||||
export default findNodeHandle;
|
||||
|
||||
2
packages/react-native-web/src/index.js
vendored
2
packages/react-native-web/src/index.js
vendored
@@ -83,7 +83,7 @@ const ImageStore = UnimplementedView;
|
||||
const InputAccessoryView = UnimplementedView;
|
||||
const MaskedViewIOS = UnimplementedView;
|
||||
const NavigatorIOS = UnimplementedView;
|
||||
const PickerIOS = UnimplementedView;
|
||||
const PickerIOS = Picker;
|
||||
const ProgressBarAndroid = UnimplementedView;
|
||||
const ProgressViewIOS = UnimplementedView;
|
||||
const SegmentedControlIOS = UnimplementedView;
|
||||
|
||||
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
|
||||
*/
|
||||
|
||||
import buttonLikeRoles from './buttonLikeRoles';
|
||||
import isDisabled from './isDisabled';
|
||||
import propsToAccessibilityComponent from './propsToAccessibilityComponent';
|
||||
import propsToAriaRole from './propsToAriaRole';
|
||||
|
||||
const AccessibilityUtil = {
|
||||
buttonLikeRoles,
|
||||
isDisabled,
|
||||
propsToAccessibilityComponent,
|
||||
propsToAriaRole
|
||||
|
||||
@@ -52,12 +52,14 @@ const ImageLoader = {
|
||||
image.onerror = onError;
|
||||
image.onload = e => {
|
||||
// avoid blocking the main thread
|
||||
const onDecode = () => onLoad(e);
|
||||
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 {
|
||||
setTimeout(() => {
|
||||
onLoad(e);
|
||||
}, 0);
|
||||
setTimeout(onDecode, 0);
|
||||
}
|
||||
};
|
||||
image.src = uri;
|
||||
|
||||
@@ -100,12 +100,14 @@ const NativeMethodsMixin = {
|
||||
return;
|
||||
}
|
||||
const node = findNodeHandle(this);
|
||||
// Next state is determined by comparison to existing state (in the DOM).
|
||||
// Existing state has already gone through i18n transform
|
||||
const domProps = createDOMProps(null, nativeProps, style =>
|
||||
styleResolver.resolveWithNode(style, node)
|
||||
);
|
||||
UIManager.updateView(node, domProps, this);
|
||||
if (node) {
|
||||
// Next state is determined by comparison to existing state (in the DOM).
|
||||
// Existing state has already gone through i18n transform
|
||||
const domProps = createDOMProps(null, nativeProps, style =>
|
||||
styleResolver.resolveWithNode(style, node)
|
||||
);
|
||||
UIManager.updateView(node, domProps, this);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -44,6 +44,9 @@ if (!ResponderEventPlugin.eventTypes.responderMove.dependencies) {
|
||||
}
|
||||
|
||||
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;
|
||||
ResponderEventPlugin.extractEvents = (topLevelType, targetInst, nativeEvent, nativeEventTarget) => {
|
||||
@@ -55,7 +58,7 @@ ResponderEventPlugin.extractEvents = (topLevelType, targetInst, nativeEvent, nat
|
||||
lastActiveTouchTimestamp = Date.now();
|
||||
} else if (lastActiveTouchTimestamp && eventType.indexOf('mouse') > -1) {
|
||||
const now = Date.now();
|
||||
shouldSkipMouseAfterTouch = now - lastActiveTouchTimestamp < 250;
|
||||
shouldSkipMouseAfterTouch = now - lastActiveTouchTimestamp < EMULATED_MOUSE_THERSHOLD_MS;
|
||||
}
|
||||
|
||||
if (
|
||||
|
||||
@@ -52,8 +52,10 @@ const observe = instance => {
|
||||
|
||||
if (resizeObserver) {
|
||||
const node = findNodeHandle(instance);
|
||||
node._layoutId = id;
|
||||
resizeObserver.observe(node);
|
||||
if (node) {
|
||||
node._layoutId = id;
|
||||
resizeObserver.observe(node);
|
||||
}
|
||||
} else {
|
||||
instance._layoutId = id;
|
||||
instance._handleLayout();
|
||||
@@ -61,12 +63,15 @@ const observe = instance => {
|
||||
};
|
||||
|
||||
const unobserve = instance => {
|
||||
delete registry[instance._layoutId];
|
||||
if (resizeObserver) {
|
||||
const node = findNodeHandle(instance);
|
||||
delete node._layoutId;
|
||||
resizeObserver.unobserve(node);
|
||||
if (node) {
|
||||
delete registry[node._layoutId];
|
||||
delete node._layoutId;
|
||||
resizeObserver.unobserve(node);
|
||||
}
|
||||
} else {
|
||||
delete registry[instance._layoutId];
|
||||
delete instance._layoutId;
|
||||
}
|
||||
};
|
||||
@@ -93,7 +98,9 @@ const applyLayout = Component => {
|
||||
function componentDidMount() {
|
||||
this._layoutState = emptyObject;
|
||||
this._isMounted = true;
|
||||
observe(this);
|
||||
if (this.props.onLayout) {
|
||||
observe(this);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@@ -112,7 +119,9 @@ const applyLayout = Component => {
|
||||
componentWillUnmount,
|
||||
function componentWillUnmount() {
|
||||
this._isMounted = false;
|
||||
unobserve(this);
|
||||
if (this.props.onLayout) {
|
||||
unobserve(this);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@@ -131,11 +140,12 @@ const applyLayout = Component => {
|
||||
) {
|
||||
this._layoutState = { x, y, width, height };
|
||||
const nativeEvent = {
|
||||
layout: this._layoutState,
|
||||
get target() {
|
||||
return findNodeHandle(this);
|
||||
}
|
||||
layout: this._layoutState
|
||||
};
|
||||
Object.defineProperty(nativeEvent, 'target', {
|
||||
enumerable: true,
|
||||
get: () => findNodeHandle(this)
|
||||
});
|
||||
onLayout({ nativeEvent, timeStamp: Date.now() });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,9 +65,7 @@ describe('modules/createDOMProps', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('"accessibilityRole" of "button"', () => {
|
||||
const accessibilityRole = 'button';
|
||||
|
||||
const testFocusableRole = accessibilityRole => {
|
||||
test('default case', () => {
|
||||
expect(createProps({ accessibilityRole })).toEqual(
|
||||
expect.objectContaining({ 'data-focusable': true, tabIndex: '0' })
|
||||
@@ -118,6 +116,14 @@ describe('modules/createDOMProps', () => {
|
||||
})
|
||||
).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', () => {
|
||||
@@ -177,6 +183,12 @@ describe('modules/createDOMProps', () => {
|
||||
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"', () => {
|
||||
const testID = 'Example.testID';
|
||||
const props = createProps({ testID });
|
||||
|
||||
@@ -77,6 +77,7 @@ const createDOMProps = (component, props, styleResolver) => {
|
||||
accessibilityLabel,
|
||||
accessibilityLiveRegion,
|
||||
importantForAccessibility,
|
||||
nativeID,
|
||||
placeholderTextColor,
|
||||
pointerEvents,
|
||||
style: providedStyle,
|
||||
@@ -131,7 +132,7 @@ const createDOMProps = (component, props, styleResolver) => {
|
||||
} else {
|
||||
domProps['data-focusable'] = true;
|
||||
}
|
||||
} else if (role === 'button' || role === 'textbox') {
|
||||
} else if (AccessibilityUtil.buttonLikeRoles[role] || role === 'textbox') {
|
||||
if (accessible !== false && focusable) {
|
||||
domProps['data-focusable'] = true;
|
||||
domProps.tabIndex = '0';
|
||||
@@ -164,10 +165,15 @@ const createDOMProps = (component, props, styleResolver) => {
|
||||
}
|
||||
|
||||
// OTHER
|
||||
// Native element ID
|
||||
if (nativeID && nativeID.constructor === String) {
|
||||
domProps.id = nativeID;
|
||||
}
|
||||
// Link security and automation test ids
|
||||
if (component === 'a' && domProps.target === '_blank') {
|
||||
domProps.rel = `${domProps.rel || ''} noopener noreferrer`;
|
||||
}
|
||||
// Automated test IDs
|
||||
if (testID && testID.constructor === String) {
|
||||
domProps['data-testid'] = testID;
|
||||
}
|
||||
|
||||
@@ -78,6 +78,7 @@ function normalizeTouchEvent(nativeEvent) {
|
||||
typeof nativeEvent.stopPropagation === 'function'
|
||||
? nativeEvent.stopPropagation.bind(nativeEvent)
|
||||
: emptyFunction;
|
||||
const singleChangedTouch = changedTouches[0];
|
||||
|
||||
const event = {
|
||||
_normalized: true,
|
||||
@@ -85,11 +86,15 @@ function normalizeTouchEvent(nativeEvent) {
|
||||
cancelable: nativeEvent.cancelable,
|
||||
changedTouches,
|
||||
defaultPrevented: nativeEvent.defaultPrevented,
|
||||
identifier: undefined,
|
||||
locationX: undefined,
|
||||
locationY: undefined,
|
||||
pageX: nativeEvent.pageX,
|
||||
pageY: nativeEvent.pageY,
|
||||
identifier: singleChangedTouch ? singleChangedTouch.identifier : undefined,
|
||||
get locationX() {
|
||||
return singleChangedTouch ? singleChangedTouch.locationX : undefined;
|
||||
},
|
||||
get locationY() {
|
||||
return singleChangedTouch ? singleChangedTouch.locationY : undefined;
|
||||
},
|
||||
pageX: singleChangedTouch ? singleChangedTouch.pageX : nativeEvent.pageX,
|
||||
pageY: singleChangedTouch ? singleChangedTouch.pageY : nativeEvent.pageY,
|
||||
preventDefault,
|
||||
stopImmediatePropagation,
|
||||
stopPropagation,
|
||||
@@ -102,14 +107,6 @@ function normalizeTouchEvent(nativeEvent) {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -164,8 +161,12 @@ function normalizeMouseEvent(nativeEvent) {
|
||||
changedTouches: touches,
|
||||
defaultPrevented: nativeEvent.defaultPrevented,
|
||||
identifier: touches[0].identifier,
|
||||
locationX: touches[0].locationX,
|
||||
locationY: touches[0].locationY,
|
||||
get locationX() {
|
||||
return touches[0].locationX;
|
||||
},
|
||||
get locationY() {
|
||||
return touches[0].locationY;
|
||||
},
|
||||
pageX: nativeEvent.pageX,
|
||||
pageY: nativeEvent.pageY,
|
||||
preventDefault,
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import UIManager from '../../../exports/UIManager';
|
||||
|
||||
const __DEV__ = process.env.NODE !== 'production';
|
||||
const __DEV__ = process.env.NODE_ENV !== 'production';
|
||||
const { checkPropTypes } = PropTypes;
|
||||
|
||||
const Types = {
|
||||
|
||||
@@ -399,7 +399,7 @@ class VirtualizedList extends React.PureComponent<Props, State> {
|
||||
if (this._scrollRef && this._scrollRef.getScrollableNode) {
|
||||
return this._scrollRef.getScrollableNode();
|
||||
} else {
|
||||
return ReactNative.findNodeHandle(this._scrollRef);
|
||||
return findNodeHandle(this._scrollRef);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -410,7 +410,7 @@ class VirtualizedList extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
disableVirtualization: false,
|
||||
disableVirtualization: process.env.NODE_ENV === 'test',
|
||||
horizontal: false,
|
||||
initialNumToRender: 10,
|
||||
keyExtractor: (item: Item, index: number) => {
|
||||
@@ -1058,8 +1058,8 @@ class VirtualizedList extends React.PureComponent<Props, State> {
|
||||
|
||||
_measureLayoutRelativeToContainingList(): void {
|
||||
UIManager.measureLayout(
|
||||
ReactNative.findNodeHandle(this),
|
||||
ReactNative.findNodeHandle(
|
||||
findNodeHandle(this),
|
||||
findNodeHandle(
|
||||
this.context.virtualizedList.getOutermostParentListRef(),
|
||||
),
|
||||
error => {
|
||||
@@ -1562,6 +1562,7 @@ class CellRenderer extends React.Component<
|
||||
renderItem: renderItemType,
|
||||
},
|
||||
prevCellKey: ?string,
|
||||
style: ?DangerouslyImpreciseStyleProp,
|
||||
},
|
||||
$FlowFixMeState,
|
||||
> {
|
||||
@@ -1630,6 +1631,7 @@ class CellRenderer extends React.Component<
|
||||
index,
|
||||
inversionStyle,
|
||||
parentProps,
|
||||
style,
|
||||
} = this.props;
|
||||
const {renderItem, getItemLayout} = parentProps;
|
||||
invariant(renderItem, 'no renderItem!');
|
||||
@@ -1649,9 +1651,9 @@ class CellRenderer extends React.Component<
|
||||
);
|
||||
const cellStyle = inversionStyle
|
||||
? horizontal
|
||||
? [{flexDirection: 'row-reverse'}, inversionStyle]
|
||||
: [{flexDirection: 'column-reverse'}, inversionStyle]
|
||||
: horizontal ? [{flexDirection: 'row'}, inversionStyle] : inversionStyle;
|
||||
? [styles.rowReverse, inversionStyle, style]
|
||||
: [styles.columnReverse, inversionStyle, style]
|
||||
: horizontal ? [styles.row, inversionStyle, style] : [inversionStyle, style];
|
||||
if (!CellRendererComponent) {
|
||||
return (
|
||||
<View style={cellStyle} onLayout={onLayout}>
|
||||
@@ -1702,6 +1704,15 @@ const styles = StyleSheet.create({
|
||||
horizontallyInverted: {
|
||||
transform: [{scaleX: -1}],
|
||||
},
|
||||
row: {
|
||||
flexDirection: 'row'
|
||||
},
|
||||
rowReverse: {
|
||||
flexDirection: 'row-reverse'
|
||||
},
|
||||
columnReverse: {
|
||||
flexDirection: 'column-reverse'
|
||||
}
|
||||
});
|
||||
|
||||
export default VirtualizedList;
|
||||
|
||||
@@ -1,349 +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'
|
||||
```
|
||||
|
||||
## 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,
|
||||
"name": "website",
|
||||
"version": "0.8.1",
|
||||
"version": "0.9.6",
|
||||
"scripts": {
|
||||
"build": "build-storybook -o ./dist -c ./storybook/.storybook",
|
||||
"start": "start-storybook -p 9001 -c ./storybook/.storybook",
|
||||
@@ -10,12 +10,12 @@
|
||||
"dependencies": {
|
||||
"@storybook/addon-options": "^3.4.3",
|
||||
"@storybook/react": "^3.4.3",
|
||||
"react": "^16.3.2",
|
||||
"react-dom": "^16.3.2",
|
||||
"react-native-web": "0.8.1"
|
||||
"react": "^16.5.1",
|
||||
"react-dom": "^16.5.1",
|
||||
"react-native-web": "0.9.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-plugin-react-native-web": "0.8.1",
|
||||
"babel-plugin-react-native-web": "0.9.6",
|
||||
"url-loader": "^1.0.1",
|
||||
"webpack": "^4.8.1"
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ const PickerScreen = () => (
|
||||
<AppText>Renders the native <select> component.</AppText>
|
||||
</Description>
|
||||
<Section title="Props">
|
||||
<DocItem name="...View props" />
|
||||
|
||||
<DocItem
|
||||
name="children"
|
||||
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
|
||||
name="numberOfLines"
|
||||
typeInfo="?number"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*/
|
||||
|
||||
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 {
|
||||
buttons = ['One', 'Two', 'Three'];
|
||||
@@ -26,7 +26,7 @@ export default class TouchableCustomStyleOverridesExample extends React.Componen
|
||||
key={button}
|
||||
onPress={this.select(button)}
|
||||
style={[styles.touchable, this.state[button] && styles.blue]}
|
||||
underlayColor={processColor('#1B95E0', 0.125)}
|
||||
underlayColor="#1b95e020"
|
||||
>
|
||||
<Text style={[!this.state[button] && styles.text]}>{button}</Text>
|
||||
</TouchableHighlight>
|
||||
@@ -39,7 +39,7 @@ export default class TouchableCustomStyleOverridesExample extends React.Componen
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
blue: {
|
||||
backgroundColor: processColor('#1B95E0', 0.25),
|
||||
backgroundColor: '#1b95e040',
|
||||
borderColor: '#1B95E0'
|
||||
},
|
||||
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="onContextMenu" typeInfo="?function" />
|
||||
<DocItem name="onFocus" typeInfo="?function" />
|
||||
|
||||
@@ -3,7 +3,7 @@ import { View, StyleSheet } from 'react-native';
|
||||
|
||||
export default class Sandbox extends React.PureComponent {
|
||||
render() {
|
||||
return <View styles={styles.root} />;
|
||||
return <View style={styles.root} />;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ const Divider = () => <View style={styles.divider} />;
|
||||
|
||||
const SourceLink = ({ uri }) => (
|
||||
<ExternalLink
|
||||
href={`https://github.com/necolas/react-native-web/tree/master/packages/docs/storybook/${uri}`}
|
||||
href={`https://github.com/necolas/react-native-web/tree/master/packages/website/storybook/${uri}`}
|
||||
style={styles.link}
|
||||
>
|
||||
View source code on GitHub
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user