Compare commits

..

65 Commits
0.8.1 ... 0.9.6

Author SHA1 Message Date
Nicolas Gallagher
931d666fcc 0.9.6 2018-11-01 12:29:18 -07:00
hushicai
40c433c6df [fix] only call 'getBoundingClientRect' if nativeEvent.location{X,Y} is accessed
Calculating the `location{X,Y}` values for events requires a call to
`getBoundingClientRect`. To prevent unnecessary performance costs, these values
are implemented as getters and will only make the DOM API call when accessed in
application code.

Close #1157
2018-11-01 09:06:10 -07:00
hushicai
9888c2a3c6 [fix] scrolling of nested ScrollViews
Use of 'pan-x' and 'pan-y' on ScrollView prevents the browser handling
scrolling of a parent ScrollView that is scrollable along the other axis.

Fix #1160
Close #1161
2018-11-01 09:01:29 -07:00
Nicolas Gallagher
3fa18becc7 0.9.5 2018-10-29 18:04:02 -07:00
Nicolas Gallagher
aafeb0adad [fix] RTL support for 'transitionProperty' style
The 'transitionProperty' value can be any property and this patch processes
those values in the same way as properties.

Fix #1131
2018-10-29 13:20:09 -07:00
Nicolas Gallagher
89468b7d6e 0.9.4 2018-10-22 20:06:58 -07:00
Nicolas Gallagher
f66af5e04d Update Gatsby plugin link 2018-10-22 20:03:49 -07:00
Mo Kouli
2363524fa7 [fix] process.env.NODE -> process.env.NODE_ENV
Close #1145
2018-10-22 19:14:04 -07:00
Charlie Croom
5855e55615 [fix] cache Clipboard.isAvailable() value
Fix #1149
Close #1150
2018-10-22 19:10:56 -07:00
Charlie Croom
5033e12d18 Add nativeID to View supported props filter
Close #1147
2018-10-22 18:18:53 -07:00
Nicolas Gallagher
d6e8530f4d 0.9.3 2018-10-11 17:40:41 -07:00
Charlie Croom
ad188a7ad6 [fix] Memory leak in applyLayout registry
Remove component instances from the layout registry when umounting views
that are using ResizeObserver.

Fix #1133
Close #1134
2018-10-11 17:35:04 -07:00
Nicolas Gallagher
bfaeae904e [add] View/Text prop nativeID
Maps the View and Text prop 'nativeID' to DOM 'id' as these are
equivalent.  Enables declarative use of various 'aria-*' properties that
require ID references.

Ref #1116
Close #1130
2018-10-11 17:33:08 -07:00
Nicolas Gallagher
a54bdeec09 0.9.2 2018-10-09 18:02:55 -07:00
atp
8fa7dc63ec [add] TextInput support for 'caretColor' CSS property
Close #1127
2018-10-09 17:59:57 -07:00
Bruno Lemos
d841db2337 [fix] VirtualizedList sticky header support
The way that sticky headers work on web requires the ScrollView to apply
'position:sticky' to a clone of the element. This wasn't working for
VirtualizedList because the style prop was not passed to the default
CellRendererComponent implementation.

Fix #1066
Close #1122
2018-10-09 17:54:10 -07:00
Rick
8e7d31cff5 [fix] ignore native-only TextInput props
TextInput inherits ViewPropTypes but many of them do nothing (even in
React Native).

Close #1123
2018-10-09 17:46:19 -07:00
Nicolas Gallagher
0764687a8f [fix] VirtualizedList disabled virtualization in tests
Fix #1077
Close #1118
2018-10-09 17:37:02 -07:00
Charlie Croom
d31bdf2cf8 [fix] ResizeObserve only on elements using 'onLayout' prop
Only observe nodes when the 'onLayout' prop is specified on the
element. Fixes performance regression for browsers that rely on
MutationObserver-based shim for ResizeObserver.

Fix #1128
Close #1129
2018-10-09 17:31:32 -07:00
Nicolas Gallagher
1f3a77dada 0.9.1 2018-09-27 14:30:26 -07:00
Nicolas Gallagher
c3cbd53a8a [fix] ResponderEventPlugin removal of emulated mouse events
If the work related to a touch/press event takes a long enough time
(i.e., CPU intensive, old device, etc.) the browser may produce emulated
mouse events >500ms after the original touch event. This causes the
related Responder events to fire twice. To avoid that happening, this
patch increases the filter threshold used by the ResponderEventPlugin
from 200ms to 1000ms.

Fix #1078
2018-09-22 16:33:46 -07:00
Nicolas Gallagher
4f5ee15e4b Fix website TouchableHighlight example 2018-09-22 16:20:49 -07:00
Nicolas Gallagher
9a1cade1f0 0.9.0 2018-09-17 10:55:16 -07:00
Nicolas Gallagher
506dba933c [change] Support React DOM 16.5
React DOM 16.5 changed unstable APIs that this project depends upon.
This regression was fixed in React DOM 16.5.1 but requires React Native
for Web to migrate to a different unstable API exported by React DOM.

Fix #1096
Close #1106
2018-09-17 10:16:33 -07:00
Nicolas Gallagher
c0de9dddf3 0.8.11 2018-09-17 09:47:04 -07:00
David Calhoun
96c9c06272 Update the Docz integration link
Close #1102
2018-09-17 09:37:32 -07:00
Charlie Croom
505e3faee8 [fix] 'menuitem' role supports Enter/Space keyboard interaction
The 'menuitem' ARIA role should support Enter/Space keyboard interaction
as if it were a button. This is required because the ARIA spec makes it
so that the ARIA properties of 'menuitem' children are ignored, i.e.,
you can't just wrap a button in a 'menuitem' and expect Assistive
Technologies to surface the button to users.

Fix #1068
Close #1069
2018-09-17 09:30:06 -07:00
Nicolas Gallagher
1f06229289 [fix] react-native-web@0.8 pinned to React 16.4 2018-09-17 08:47:37 -07:00
Nicolas Gallagher
fc743e6eee Fix sandbox placeholder 2018-09-17 08:44:52 -07:00
Tomoya Hirano
ef97adec6e [fix] Picker doesn't pass 'onValueChange' to DOM node
Fix #1104
Close #1107
2018-09-17 08:19:13 -07:00
Nicolas Gallagher
f196335281 0.8.10 2018-09-09 13:10:51 -07:00
Anton Nyman
d29e31d9d6 [fix] better cross-browser support for textDecoration styles
Safari requires '-webkit' prefixes for CSS3 text-decoration styles, and
IE/Edge only support CSS2 text-decoration styles. This patch provides a
fallback for the CSS2 case and relies on the CSS3 properties for color
and style.

Close #1053

Co-authored-by: Nicolas Gallagher <nicolasgallagher@gmail.com>
2018-09-09 12:03:10 -07:00
Nicolas Gallagher
c7c1f29016 Reorganize documentation
Close #1092
Close #1095
2018-09-09 11:33:09 -07:00
Nicolas Gallagher
b56a737d62 [fix] prevent findNodeHandle throwing on unmounted components
Fix #1097
2018-09-09 11:11:18 -07:00
Nicolas Gallagher
f062eded40 [fix] Switch applies accessibilityLabel to native control
VoiceOver requires that the aria-label for the checkbox is applied to
the checkbox itself and not a wrapping element.

Fix #1072
2018-08-17 15:16:38 -07:00
Nicolas Gallagher
fcc4fbf678 [fix] TextInput defaults to numberOfLines={1} for multiline
Fix #1071
2018-08-17 11:23:23 -07:00
James Long
a18d30c809 [fix] Add inputAccessoryViewID to TextInput props
Close #1076
2018-08-17 11:12:56 -07:00
Marcel Miranda Ackerman
b9172ceb8e [fix] Image EncodingError for SVG in Safari iOS 11
Fixes an EncodingError exception being thrown in Safari iOS 11 when
decoding an SVG URL. The exception prevents the onLoad handler from
being called.

Close #1063
2018-08-17 11:09:32 -07:00
Michael S. Kazmier
744aaa26d4 [fix] Picker supports View props
Close #1064
2018-08-17 11:07:22 -07:00
Justine De Caires
2b5ddf753e [add] objectFit and objectPosition to style validation
Close #1046
2018-08-17 10:58:05 -07:00
Ben Milman
b84e3b938a Fix typo in docs
Close #1049
2018-08-17 10:56:54 -07:00
Nicolas Gallagher
9c8407162e 0.8.9 2018-07-19 12:32:40 -07:00
Jeremy
16b9ec2917 [fix] nested ScrollView scroll behaviour propagation
Allow ScrollView to be scrolled from gestures originating in a
descendent ScrollView.

Fix #1042
Close #1043
2018-07-19 12:17:42 -07:00
Nicolas Gallagher
d4af1eb981 0.8.8 2018-07-06 15:06:46 -07:00
Nicolas Gallagher
405a3b79e8 [fix] TextInput calls onSelectionChange upon user input
Fix #1018
2018-07-06 15:00:50 -07:00
Nicolas Gallagher
afd5293172 Update React packages 2018-07-06 13:46:21 -07:00
Nicolas Gallagher
e4831b7bd8 Update benchmarks dependencies 2018-07-06 13:45:56 -07:00
Nicolas Gallagher
baffc9a9e6 0.8.7 2018-07-05 11:50:17 -07:00
Nicolas Gallagher
4151b47005 [fix] backgroundClip prefixing when value is 'text'
Temporary work-around for a bug in inline-style-prefixer.

Fix #1014
2018-07-05 11:46:49 -07:00
Nicolas Gallagher
96ec805f59 [fix] TextInput multiline value with onSubmitEditing
Prevent a newline from being added when submitting a multiline text
input using the "Enter" key.

Fix #1013
2018-07-04 12:32:48 -07:00
Nicolas Gallagher
d2df2c296e 0.8.6 2018-06-28 09:47:56 -07:00
Nicolas Gallagher
af80b046fa Update Flow config documentation
Close #1007
2018-06-28 09:33:07 -07:00
Nicolas Gallagher
1d01af57d0 [fix] add mousedown and mouseup to supported View props
Fix #1008
2018-06-28 09:08:28 -07:00
Johannes
e7613ca4d1 [fix] onLayout TypeError if node already unmounted
Close #993
2018-06-28 09:02:20 -07:00
Nicolas Gallagher
0e81c6ef27 0.8.5 2018-06-26 07:55:35 -07:00
Nicolas Gallagher
7fa8940325 [fix] VirtualizedList calls to findNodeHandle
Fix #1004
2018-06-26 07:52:07 -07:00
Nicolas Gallagher
3e681bed3e GitHub issue template adjustment
Move help text into HTML comments and make it clear that issues will be
closed if the issue template is ignored.
2018-06-15 10:47:04 -07:00
ral51
ecd4b40c71 Fix source code links on website
Close #994
2018-06-14 13:45:38 -07:00
Nicolas Gallagher
e7cb364b63 0.8.4 2018-06-14 13:40:57 -07:00
Nicolas Gallagher
90bd23f783 [fix] onLayout event object
Make sure that `this` within the `target` getter references the React
component rather than the layout event object.

Fix #996
2018-06-14 13:21:25 -07:00
Nicolas Gallagher
47a281373a 0.8.3 2018-06-06 16:24:02 -07:00
Nicolas Gallagher
6a0302169c [fix] react-native-web package files
Fix #987
2018-06-06 16:21:33 -07:00
Nicolas Gallagher
b195f2b1f5 0.8.2 2018-06-05 13:22:24 -07:00
Nicolas Gallagher
7b81d2a7ec Fix AsyncStorageExample
Comment out React Native's use of non-standard `done()` method, which
isn't available on `async` functions even though it is polyfilled for
the global Promise.
2018-06-05 13:13:36 -07:00
Nicolas Gallagher
a1d8ea776e [fix] PickerIOS is alias for Picker
PickerIOS API is a subset of the cross-platform Picker. This patch
provides some compatibility for use of PickerIOS / PickerIOS.Item in
legacy code.
2018-06-05 13:12:59 -07:00
63 changed files with 2037 additions and 6293 deletions

View File

@@ -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

View File

@@ -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.
-->

View File

@@ -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
View File

@@ -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

View 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>
```

View 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.

View 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.

View 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>
`
```

View 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]()
-->

View File

@@ -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/*"

View File

@@ -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": {

View File

@@ -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"
}
}

View File

@@ -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]' }
}
]
},

View File

@@ -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",

View File

@@ -29,7 +29,7 @@ class BasicStorageExample extends React.Component<{}, $FlowFixMeState> {
};
componentDidMount() {
this._loadInitialState().done();
this._loadInitialState()//.done();
}
_loadInitialState = async () => {

View File

@@ -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",

View File

@@ -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> {

View File

@@ -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
});
}

View File

@@ -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'

View File

@@ -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 */

View File

@@ -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'
});
});
});

View File

@@ -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);
});

View File

@@ -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

View File

@@ -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);

View File

@@ -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 />);

View File

@@ -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,

View File

@@ -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,

View File

@@ -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'])
};

View File

@@ -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);
});
});

View File

@@ -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);
}

View File

@@ -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) {

View File

@@ -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,

View File

@@ -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,

View File

@@ -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 });
});
});
});

View File

@@ -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();

View File

@@ -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;

View File

@@ -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;

View 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;

View File

@@ -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

View File

@@ -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;

View File

@@ -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);
}
}
};

View File

@@ -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 (

View File

@@ -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() });
}
}

View File

@@ -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 });

View File

@@ -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;
}

View File

@@ -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,

View File

@@ -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 = {

View File

@@ -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;

View File

@@ -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.

View File

@@ -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"
}

View File

@@ -23,6 +23,8 @@ const PickerScreen = () => (
<AppText>Renders the native &lt;select&gt; component.</AppText>
</Description>
<Section title="Props">
<DocItem name="...View props" />
<DocItem
name="children"
typeInfo="?Array<Picker.Item>"

View File

@@ -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"

View File

@@ -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: {

View File

@@ -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" />

View File

@@ -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} />;
}
}

View File

@@ -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

2093
yarn.lock

File diff suppressed because it is too large Load Diff