Compare commits

..

63 Commits

Author SHA1 Message Date
Nicolas Gallagher
000b92e707 0.9.13 2018-12-31 17:31:46 -08:00
Nicolas Gallagher
d5f5dbccdb [fix] inline-style-prefixer API update
Fix #1217
2018-12-31 17:22:05 -08:00
Nicolas Gallagher
79456d5920 0.9.12 2018-12-31 10:34:04 -08:00
Nicolas Gallagher
2d1e303a6a [fix] ScrollView with stickyHeaderIndices regression
A ScrollView with stickyHeaderIndices would not render children that
weren't sticky.

Fixes 1e202b6bd5
2018-12-31 10:26:33 -08:00
Nicolas Gallagher
209ff1fa40 0.9.11 2018-12-31 08:23:58 -08:00
Nicolas Gallagher
34d8160a43 [fix] CSS animation insertion for Android 5.1 HuaWei browser
Inserting unprefixed CSS keyframes rules causes a `SYNTAX_ERR: DOM Exception
12` error in Android 5.1. A similar issue with inserting rules containing
vendor-prefixed pseudo-selectors was patched by wrapping rule in `@media all
{}` blocks. This patch removes the media query wrapper from keyframe animations
(as it doesn't prevent the error) and relies on `CSSStyleSheet::insertRule`
being called within a try-catch block.

Fix #1199
Close #1210
2018-12-31 07:47:52 -08:00
Nicolas Gallagher
ada5651be2 Don't minify local benchmarks build 2018-12-24 13:35:21 +00:00
Nicolas Gallagher
9fe9d3c68a Update react-native-web dependencies 2018-12-24 13:03:49 +00:00
krister
1e202b6bd5 [add] ScrollView support for pagingEnabled
Available in browsers that support CSS snappoints.

Fix #1057
Close #1212

Co-authored-by: Nicolas Gallagher <nicolasgallagher@gmail.com>
2018-12-23 18:05:29 +00:00
Robert Sayre
2b77bfd853 [fix] improve style resolver performance
Script time in the benchmark was profiled by adding `console.profile` around
the timings for script time. The call to Array.join in the resolve function
stood out. Since the code already iterates over the array it can run slightly
faster by building the cache key in that loop instead.

Close #1213
2018-12-23 15:07:48 +00:00
Nicolas Gallagher
d0ac55aa4f Update benchmarks dependencies 2018-12-21 21:33:43 +00:00
Nicolas Gallagher
66dd1bd9ef Remove glamor and radium from benchmarks
Glamor is unmaintained. Radium is extremely slow. There is no need to compare
against these libraries.
2018-12-21 21:20:30 +00:00
Nicolas Gallagher
6add18c6f0 0.9.10 2018-12-21 21:04:26 +00:00
krister
30d7c31b65 [fix] ScrollView smooth scrolling
Rely on the `element.scroll()` programmatic API when available (or polyfilled).

Fix #1203
Fix #1173
Close #1208
2018-12-21 20:57:25 +00:00
Raibima Putra
f7e6b43422 Fix docs typo
Close #1209
2018-12-21 20:46:29 +00:00
Nicolas Gallagher
4b3f6efb21 Support style inspection in production 2018-12-10 17:01:23 -08:00
Nicolas Gallagher
85e098deec 0.9.9 2018-12-05 14:46:11 -08:00
Nicolas Gallagher
c949b0145a [fix] TextInput onKeyPress supports Escape key
Fix #1189
2018-11-27 12:15:59 -08:00
Charlie Croom
86b4cf5a51 [add] scaleZ and scale3d style transforms
Web-specific additions similar to the web-specific additions to `translate` styles.

Close #1184
2018-11-27 11:53:04 -08:00
Giuseppe
1b7ce4eec6 [fix] Fix regression in modality refactor
Fixes https://github.com/necolas/react-native-web/pull/1169#issuecomment-440590544

And removes the focus ring for press-after-keyboard edge cases.

Close #1186

Co-authored-by: Nicolas Gallagher <nicolasgallagher@gmail.com>
2018-11-27 11:52:46 -08:00
Nicolas Gallagher
8c8978ff76 0.9.8 2018-11-15 21:40:18 -08:00
Nicolas Gallagher
513b5de881 Partially revert d841db2337
The modification to VirtualizedList is not required now that ScrollView wraps
sticky header items in a View.
2018-11-15 10:26:49 -08:00
Julian Hundeloh
145f80409d [fix] ScrollView dependency on 'style' forwarding for sticky headers
Not every item that may be rendered by a ScrollView will forward 'style', so cloning the item element is not safe. Instead, we can wrap the item in a 'View' and apply the styles for the sticky header to this element.

Close #1175
2018-11-15 10:24:54 -08:00
Julian Hundeloh
6d92cc5ec3 [fix] ListView section error when missing renderHeader
Close #1174
2018-11-15 10:11:54 -08:00
Nicolas Gallagher
ec6458c09d Update some links in README 2018-11-15 10:09:14 -08:00
James Munro
a84c2ac95e Add DataCamp to companies using react-native-web in production
Close #1176
2018-11-15 10:07:26 -08:00
Nicolas Gallagher
75db0e9183 0.9.7 2018-11-12 17:53:16 -08:00
Giuseppe Gurgone
4b9a5fd8b4 [fix] modality performance
Inserting and deleting the CSS ':focus' rule triggers styles recalculation for the
entire DOM tree which results in performance degradation on focus and makes the
keyboard very slow to open in Safari iOS.

This commit fixes the issue by picking up the upstream changes to the W3C
focus-visible polyfill. A class name is applied to elements when they receive
focus via keyboard. The related CSS rule is inserted only once into the style
sheet. This performs much better since the browser needs to recalculate styles
only for the focused element and its small subtree.

Fix #1155
Close #1169
2018-11-12 15:54:07 -08:00
Nicolas Gallagher
b6be677db9 [fix] ref.focus() should focus any element type
Ensure that programmatic focus can be moved to any element. Each
instance of a primitive component type (e.g., `View`, `Text`, etc.)
includes a `focus` method. However, on the web only certain elements can
receive programmatic focus by default: those that can also receive
keyboard focus, e.g., `a`, `button`, `input`, etc. All other element
types must set `tabIndex="-1"` in order to be programmatically focusable
without also being focusable via keyboard or mouse.

Fix #1099
2018-11-10 12:03:44 -08:00
Nicolas Gallagher
91c9457392 Remove reference to create-react-native-app
Close #1167
2018-11-10 11:23:04 -08:00
Nicolas Gallagher
006d315a1a Add example Next.js recipes 2018-11-10 11:07:54 -08:00
Nicolas Gallagher
220eb79357 Fix UIExplorer headings accessibility 2018-11-10 11:05:49 -08:00
Murtaza Raja
5db9a765b0 Fix README install command typo
Close #1163
2018-11-04 18:39:23 -08:00
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
68 changed files with 1971 additions and 936 deletions

View File

@@ -23,10 +23,11 @@ React Native for Web can also render to HTML and critical CSS on the server
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/), Uber, [The
Times](https://github.com/newsuk/times-components).
[Twitter](https://mobile.twitter.com),
[Major League Soccer](https://matchcenter.mlssoccer.com),
[Flipkart](https://twitter.com/naqvitalha/status/969577892991549440),
[Uber](https://www.youtube.com/watch?v=RV9rxrNIxnY),
[The Times](https://github.com/newsuk/times-components), [DataCamp](https://www.datacamp.com/community/tech/porting-practice-to-web-part1).
Browser support: Chrome, Firefox, Edge, Safari 7+, IE 10+.
@@ -73,9 +74,9 @@ recipes" guide.
Examples of using React Native for Web with other web tools:
* [Docz](https://github.com/pedronauck/docz/tree/master/examples/react-native-flow)
* [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)
* [Docz](https://github.com/pedronauck/docz-plugin-react-native)
* [Gatsby](https://github.com/slorber/gatsby-plugin-react-native-web)
* [Next.js](https://github.com/zeit/next.js/tree/master/examples/with-react-native-web) (and [example recipes](https://gist.github.com/necolas/f9034091723f1b279be86c7429eb0c96))
* [Phenomic](https://github.com/phenomic/phenomic/tree/master/examples/react-native-web-app)
* [Razzle](https://github.com/jaredpalmer/razzle/tree/master/examples/with-react-native-web)
* [Storybook](https://github.com/necolas/react-native-web/tree/master/packages/website/storybook/.storybook)
@@ -139,7 +140,7 @@ React Native v0.55
| 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)). |
| ScrollView | ✓ | Missing momentum scroll events ([#1021](https://github.com/necolas/react-native-web/issues/1021)). |
| SectionList | ✓ | |
| Slider | ✘ | Not started ([#1022](https://github.com/necolas/react-native-web/issues/1022)). |
| StatusBar | (✓) | Mock. No equivalent web APIs. |

View File

@@ -24,20 +24,13 @@ yarn add react-art
## Starter kits
Web: [create-react-app](https://github.com/facebookincubator/create-react-app)
[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

View File

@@ -36,7 +36,7 @@ target platform.
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
also use 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
@@ -54,7 +54,7 @@ from your web app build. To help with this, you can install the following Babel
plugin:
```
yarn install --dev babel-plugin-react-native-web
yarn add --dev babel-plugin-react-native-web
```
Create a `web/webpack.config.js` file:

View File

@@ -1,6 +1,6 @@
{
"private": true,
"version": "0.8.10",
"version": "0.9.13",
"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",
@@ -49,15 +49,16 @@
"flow-bin": "^0.63.1",
"glob": "^7.1.2",
"husky": "^0.14.3",
"inline-style-prefixer": "^5.0.3",
"jest": "^22.4.3",
"jest-canvas-mock": "^1.0.2",
"lint-staged": "^7.1.0",
"npm-run-all": "^4.1.3",
"prettier": "^1.12.1",
"react": "^16.4.1",
"react-art": "^16.4.1",
"react-dom": "^16.4.1",
"react-test-renderer": "^16.4.1"
"react": "^16.7.0",
"react-art": "^16.7.0",
"react-dom": "^16.7.0",
"react-test-renderer": "^16.7.0"
},
"workspaces": [
"packages/*"

View File

@@ -1,6 +1,6 @@
{
"name": "babel-plugin-react-native-web",
"version": "0.8.10",
"version": "0.9.13",
"description": "Babel plugin for React Native for Web",
"main": "index.js",
"devDependencies": {

View File

@@ -1,37 +1,35 @@
{
"private": true,
"name": "benchmarks",
"version": "0.8.10",
"version": "0.9.13",
"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 -"
"release": "NODE_ENV=production 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.2",
"aphrodite": "^2.2.3",
"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.4.1",
"react-dom": "^16.4.1",
"react-fela": "^7.3.1",
"d3-scale-chromatic": "^1.3.3",
"emotion": "^10.0.5",
"fela": "^10.0.2",
"react": "^16.7.0",
"react-dom": "^16.7.0",
"react-fela": "^10.0.2",
"react-jss": "^8.6.1",
"react-native-web": "0.8.10",
"reactxp": "^1.3.0",
"styled-components": "^3.3.3",
"styled-jsx": "^2.2.7",
"styletron-engine-atomic": "^1.0.5",
"styletron-react": "^4.3.1"
"react-native-web": "0.9.13",
"reactxp": "^1.5.0",
"styled-components": "^4.1.3",
"styled-jsx": "^3.1.2",
"styletron-engine-atomic": "^1.0.13",
"styletron-react": "^4.4.4"
},
"devDependencies": {
"babel-plugin-react-native-web": "0.8.10",
"css-loader": "^1.0.0",
"style-loader": "^0.21.0",
"url-loader": "^1.0.1",
"webpack": "^4.15.1",
"webpack-bundle-analyzer": "^2.13.1",
"webpack-cli": "^3.0.8"
"babel-plugin-react-native-web": "0.9.13",
"css-loader": "^2.0.2",
"style-loader": "^0.23.1",
"url-loader": "^1.1.2",
"webpack": "^4.28.1",
"webpack-bundle-analyzer": "^3.0.3",
"webpack-cli": "^3.1.2"
}
}

View File

@@ -1,49 +0,0 @@
/* eslint-disable react/prop-types */
import React from 'react';
import View from './View';
const Box = ({ color, fixed = false, layout = 'column', outer = false, ...other }) => (
<View
{...other}
style={[
styles[`color${color}`],
fixed && styles.fixed,
layout === 'row' && styles.row,
outer && styles.outer
]}
/>
);
const styles = {
outer: {
alignSelf: 'flex-start',
padding: 4
},
row: {
flexDirection: 'row'
},
color0: {
backgroundColor: '#14171A'
},
color1: {
backgroundColor: '#AAB8C2'
},
color2: {
backgroundColor: '#E6ECF0'
},
color3: {
backgroundColor: '#FFAD1F'
},
color4: {
backgroundColor: '#F45D22'
},
color5: {
backgroundColor: '#E0245E'
},
fixed: {
width: 6,
height: 6
}
};
export default Box;

View File

@@ -1,33 +0,0 @@
/* eslint-disable react/prop-types */
import React from 'react';
import { css } from 'glamor';
const Dot = ({ size, x, y, children, color }) => (
<div
className={css(styles.root, {
borderBottomColor: color,
borderRightWidth: `${size / 2}px`,
borderBottomWidth: `${size / 2}px`,
borderLeftWidth: `${size / 2}px`,
marginLeft: `${x}px`,
marginTop: `${y}px`
})}
>
{children}
</div>
);
const styles = {
root: {
position: 'absolute',
cursor: 'pointer',
width: 0,
height: 0,
borderColor: 'transparent',
borderStyle: 'solid',
borderTopWidth: 0,
transform: 'translate(50%, 50%)'
}
};
export default Dot;

View File

@@ -1,2 +0,0 @@
import View from './View';
export default View;

View File

@@ -1,29 +0,0 @@
/* eslint-disable react/prop-types */
import { css } from 'glamor';
import React from 'react';
class View extends React.Component {
render() {
const { style, ...other } = this.props;
return <div {...other} className={css(viewStyle, ...style)} />;
}
}
const viewStyle = {
alignItems: 'stretch',
borderWidth: 0,
borderStyle: 'solid',
boxSizing: 'border-box',
display: 'flex',
flexBasis: 'auto',
flexDirection: 'column',
flexShrink: 0,
margin: 0,
padding: 0,
position: 'relative',
// fix flexbox bugs
minHeight: 0,
minWidth: 0
};
export default View;

View File

@@ -1,11 +0,0 @@
import Box from './Box';
import Dot from './Dot';
import Provider from './Provider';
import View from './View';
export default {
Box,
Dot,
Provider,
View
};

View File

@@ -1,50 +0,0 @@
/* eslint-disable react/prop-types */
import Radium from 'radium';
import React from 'react';
import View from './View';
const Box = ({ color, fixed = false, layout = 'column', outer = false, ...other }) => (
<View
{...other}
style={[
styles[`color${color}`],
fixed && styles.fixed,
layout === 'row' && styles.row,
outer && styles.outer
]}
/>
);
const styles = {
outer: {
alignSelf: 'flex-start',
padding: 4
},
row: {
flexDirection: 'row'
},
color0: {
backgroundColor: '#14171A'
},
color1: {
backgroundColor: '#AAB8C2'
},
color2: {
backgroundColor: '#E6ECF0'
},
color3: {
backgroundColor: '#FFAD1F'
},
color4: {
backgroundColor: '#F45D22'
},
color5: {
backgroundColor: '#E0245E'
},
fixed: {
width: 6,
height: 6
}
};
export default Radium(Box);

View File

@@ -1,36 +0,0 @@
/* eslint-disable react/prop-types */
import Radium from 'radium';
import React from 'react';
const Dot = ({ size, x, y, children, color }) => (
<div
style={[
styles.root,
{
borderBottomColor: color,
borderRightWidth: `${size / 2}px`,
borderBottomWidth: `${size / 2}px`,
borderLeftWidth: `${size / 2}px`,
marginLeft: `${x}px`,
marginTop: `${y}px`
}
]}
>
{children}
</div>
);
const styles = {
root: {
position: 'absolute',
cursor: 'pointer',
width: 0,
height: 0,
borderColor: 'transparent',
borderStyle: 'solid',
borderTopWidth: 0,
transform: 'translate(50%, 50%)'
}
};
export default Radium(Dot);

View File

@@ -1,2 +0,0 @@
import View from './View';
export default View;

View File

@@ -1,31 +0,0 @@
/* eslint-disable react/prop-types */
import Radium from 'radium';
import React from 'react';
class View extends React.Component {
render() {
const { style, ...other } = this.props;
return <div {...other} style={[styles.root, style]} />;
}
}
const styles = {
root: {
alignItems: 'stretch',
borderWidth: 0,
borderStyle: 'solid',
boxSizing: 'border-box',
display: 'flex',
flexBasis: 'auto',
flexDirection: 'column',
flexShrink: 0,
margin: 0,
padding: 0,
position: 'relative',
// fix flexbox bugs
minHeight: 0,
minWidth: 0
}
};
export default Radium(View);

View File

@@ -1,11 +0,0 @@
import Box from './Box';
import Dot from './Dot';
import Provider from './Provider';
import View from './View';
export default {
Box,
Dot,
Provider,
View
};

View File

@@ -12,6 +12,9 @@ module.exports = {
path: path.resolve(appDirectory, 'dist'),
filename: 'bundle.js'
},
optimization: {
minimize: process.env.NODE_ENV === 'production'
},
module: {
rules: [
{

View File

@@ -1,19 +1,19 @@
{
"private": true,
"name": "react-native-examples",
"version": "0.8.10",
"version": "0.9.13",
"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.4.1",
"react-dom": "^16.4.1",
"react-native-web": "0.8.10"
"react": "^16.5.1",
"react-dom": "^16.5.1",
"react-native-web": "0.9.13"
},
"devDependencies": {
"babel-plugin-react-native-web": "0.8.10",
"babel-plugin-react-native-web": "0.9.13",
"babel-plugin-transform-runtime": "^6.23.0",
"file-loader": "^1.1.11",
"webpack": "^4.8.1",

View File

@@ -1,6 +1,6 @@
{
"name": "react-native-web",
"version": "0.8.10",
"version": "0.9.13",
"description": "React Native for Web",
"module": "dist/index.js",
"main": "dist/cjs/index.js",
@@ -15,19 +15,19 @@
"dependencies": {
"array-find-index": "^1.0.2",
"create-react-class": "^15.6.2",
"debounce": "^1.1.0",
"deep-assign": "^2.0.0",
"fbjs": "^0.8.16",
"debounce": "^1.2.0",
"deep-assign": "^3.0.0",
"fbjs": "^1.0.0",
"hyphenate-style-name": "^1.0.2",
"inline-style-prefixer": "^4.0.2",
"inline-style-prefixer": "^5.0.3",
"normalize-css-color": "^1.0.2",
"prop-types": "^15.6.0",
"react-timer-mixin": "^0.13.3"
"react-timer-mixin": "^0.13.4"
},
"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

@@ -59,6 +59,7 @@ class Picker extends Component<Props> {
itemStyle,
mode,
prompt,
onValueChange,
/* eslint-enable */
...otherProps
} = this.props;

View File

@@ -0,0 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`components/ScrollView "pagingEnabled" prop 1`] = `undefined`;
exports[`components/ScrollView "pagingEnabled" prop 2`] = `"y mandatory"`;
exports[`components/ScrollView "pagingEnabled" prop 3`] = `"start"`;

View File

@@ -2,7 +2,9 @@
import React from 'react';
import ScrollView from '..';
import { mount } from 'enzyme';
import StyleSheet from '../../StyleSheet';
import View from '../../View';
import { mount, shallow } from 'enzyme';
describe('components/ScrollView', () => {
test('instance method setNativeProps', () => {
@@ -11,4 +13,32 @@ describe('components/ScrollView', () => {
instance.setNativeProps();
}).not.toThrow();
});
test('"children" prop', () => {
const component = shallow(
<ScrollView>
<View testID="child" />
</ScrollView>
);
expect(component.find({ testID: 'child' }).length).toBe(1);
component.setProps({ stickyHeaderIndices: [4] });
expect(component.find({ testID: 'child' }).length).toBe(1);
component.setProps({ pagingEnabled: true });
expect(component.find({ testID: 'child' }).length).toBe(1);
});
test('"pagingEnabled" prop', () => {
const getStyleProp = (component, prop) => StyleSheet.flatten(component.prop('style'))[prop];
// false
const component = shallow(<ScrollView children={'Child'} />);
expect(getStyleProp(component, 'scrollSnapType')).toMatchSnapshot();
// true
component.setProps({ pagingEnabled: true });
expect(getStyleProp(component, 'scrollSnapType')).toMatchSnapshot();
expect(getStyleProp(component.children().childAt(0), 'scrollSnapAlign')).toMatchSnapshot();
});
});

View File

@@ -137,10 +137,10 @@ const ScrollView = createReactClass({
onContentSizeChange,
refreshControl,
stickyHeaderIndices,
pagingEnabled,
/* eslint-disable */
keyboardDismissMode,
onScroll,
pagingEnabled,
/* eslint-enable */
...other
} = this.props;
@@ -164,11 +164,22 @@ const ScrollView = createReactClass({
};
}
const hasStickyHeaderIndices = !horizontal && Array.isArray(stickyHeaderIndices);
const children =
!horizontal && Array.isArray(stickyHeaderIndices)
hasStickyHeaderIndices || pagingEnabled
? React.Children.map(this.props.children, (child, i) => {
if (stickyHeaderIndices.indexOf(i) > -1) {
return React.cloneElement(child, { style: [child.props.style, styles.stickyHeader] });
const isSticky = hasStickyHeaderIndices && stickyHeaderIndices.indexOf(i) > -1;
if (child != null && (isSticky || pagingEnabled)) {
return (
<View
style={StyleSheet.compose(
isSticky && styles.stickyHeader,
pagingEnabled && styles.pagingEnabledChild
)}
>
{child}
</View>
);
} else {
return child;
}
@@ -181,15 +192,21 @@ const ScrollView = createReactClass({
children={children}
collapsable={false}
ref={this._setInnerViewRef}
style={[horizontal && styles.contentContainerHorizontal, contentContainerStyle]}
style={StyleSheet.compose(
horizontal && styles.contentContainerHorizontal,
contentContainerStyle
)}
/>
);
const baseStyle = horizontal ? styles.baseHorizontal : styles.baseVertical;
const pagingEnabledStyle = horizontal
? styles.pagingEnabledHorizontal
: styles.pagingEnabledVertical;
const props = {
...other,
style: [baseStyle, this.props.style],
style: [baseStyle, pagingEnabled && pagingEnabledStyle, this.props.style],
onTouchStart: this.scrollResponderHandleTouchStart,
onTouchMove: this.scrollResponderHandleTouchMove,
onTouchEnd: this.scrollResponderHandleTouchEnd,
@@ -223,7 +240,7 @@ const ScrollView = createReactClass({
}
return (
<ScrollViewClass {...props} ref={this._setScrollViewRef} style={props.style}>
<ScrollViewClass {...props} ref={this._setScrollViewRef}>
{contentContainer}
</ScrollViewClass>
);
@@ -279,15 +296,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'
@@ -296,6 +311,15 @@ const styles = StyleSheet.create({
position: 'sticky',
top: 0,
zIndex: 10
},
pagingEnabledHorizontal: {
scrollSnapType: 'x mandatory'
},
pagingEnabledVertical: {
scrollSnapType: 'y mandatory'
},
pagingEnabledChild: {
scrollSnapAlign: 'start'
}
});

View File

@@ -84,15 +84,19 @@ export default class ReactNativeStyleResolver {
// otherwise fallback to resolving
const flatArray = flattenArray(style);
let isArrayOfNumbers = true;
let cacheKey = '';
for (let i = 0; i < flatArray.length; i++) {
const id = flatArray[i];
if (typeof id !== 'number') {
isArrayOfNumbers = false;
} else {
if (isArrayOfNumbers) {
cacheKey += (id + '-');
}
this._injectRegisteredStyle(id);
}
}
const key = isArrayOfNumbers ? createCacheKey(flatArray.join('-')) : null;
const key = isArrayOfNumbers ? createCacheKey(cacheKey) : null;
return this._resolveStyleIfNeeded(flatArray, key);
}

View File

@@ -55,7 +55,15 @@ export default class WebStyleSheet {
// doesn't include styles injected via 'insertRule')
if (this._textContent.indexOf(rule) === -1 && this._sheet) {
const pos = position || this._sheet.cssRules.length;
this._sheet.insertRule(rule, pos);
try {
this._sheet.insertRule(rule, pos);
} catch (e) {
if (process.env.NODE_ENV !== 'production') {
console.warn(
`Failed to inject CSS: "${rule}". The browser may have rejecting unrecognized vendor prefixes. (This should have no user-facing impact.)`
);
}
}
}
}
}

View File

@@ -2,10 +2,10 @@
exports[`StyleSheet/createAtomicRules transforms custom "animationName" declaration 1`] = `
Array [
"@media all {@-webkit-keyframes rn-anim-2k74q5{0%{top:0px}50%{top:5px}100%{top:10px}}}",
"@media all {@keyframes rn-anim-2k74q5{0%{top:0px}50%{top:5px}100%{top:10px}}}",
"@media all {@-webkit-keyframes rn-anim-zc91cv{from{left:0px}to{left:10px}}}",
"@media all {@keyframes rn-anim-zc91cv{from{left:0px}to{left:10px}}}",
"@-webkit-keyframes rn-anim-2k74q5{0%{top:0px}50%{top:5px}100%{top:10px}}",
"@keyframes rn-anim-2k74q5{0%{top:0px}50%{top:5px}100%{top:10px}}",
"@-webkit-keyframes rn-anim-zc91cv{from{left:0px}to{left:10px}}",
"@keyframes rn-anim-zc91cv{from{left:0px}to{left:10px}}",
".test{-webkit-animation-name:rn-anim-2k74q5,rn-anim-zc91cv;animation-name:rn-anim-2k74q5,rn-anim-zc91cv}",
]
`;

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

@@ -29,7 +29,7 @@ const makeSteps = keyframes =>
const createKeyframesRules = (keyframes: Object): Array<String> => {
const identifier = createIdentifier(keyframes);
const rules = prefixes.map(prefix => {
return `@media all {@${prefix}keyframes ${identifier}{${makeSteps(keyframes)}}}`;
return `@${prefix}keyframes ${identifier}{${makeSteps(keyframes)}}`;
});
return { identifier, rules };
};

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

@@ -1,11 +1,9 @@
import { canUseDOM } from 'fbjs/lib/ExecutionEnvironment';
import StyleSheet from './StyleSheet';
// allow component styles to be editable in React Dev Tools
if (process.env.NODE_ENV !== 'production') {
if (canUseDOM && window.__REACT_DEVTOOLS_GLOBAL_HOOK__) {
window.__REACT_DEVTOOLS_GLOBAL_HOOK__.resolveRNStyle = StyleSheet.flatten;
}
// allow original component styles to be inspected in React Dev Tools
if (canUseDOM && window.__REACT_DEVTOOLS_GLOBAL_HOOK__) {
window.__REACT_DEVTOOLS_GLOBAL_HOOK__.resolveRNStyle = StyleSheet.flatten;
}
export default StyleSheet;

View File

@@ -11,125 +11,269 @@
* 1. a keydown event occurred immediately before a focus event;
* 2. a focus event happened on an element which requires keyboard interaction (e.g., a text field);
*
* Based on https://github.com/WICG/focus-ring
* This software or document includes material copied from or derived from https://github.com/WICG/focus-visible.
* Copyright © 2018 W3C® (MIT, ERCIM, Keio, Beihang).
* W3C Software Notice and License: https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document
*
* @noflow
*/
import { canUseDOM } from 'fbjs/lib/ExecutionEnvironment';
import hash from '../../vendor/hash';
const rule = ':focus { outline: none; }';
let ruleExists = false;
const focusVisibleAttributeName =
'data-rn-' +
(process.env.NODE_ENV !== 'production' ? 'focusvisible-' : '') +
hash('focusvisible');
const rule = `:focus:not([${focusVisibleAttributeName}]){outline: none;}`;
const modality = styleElement => {
if (!canUseDOM) {
return;
}
let hadKeyboardEvent = false;
let keyboardThrottleTimeoutID = 0;
let hadKeyboardEvent = true;
let hadFocusVisibleRecently = false;
let hadFocusVisibleRecentlyTimeout = null;
const proto = window.Element.prototype;
const matches =
proto.matches ||
proto.mozMatchesSelector ||
proto.msMatchesSelector ||
proto.webkitMatchesSelector;
const inputTypesWhitelist = {
text: true,
search: true,
url: true,
tel: true,
email: true,
password: true,
number: true,
date: true,
month: true,
week: true,
time: true,
datetime: true,
'datetime-local': true
};
// These elements should always have a focus ring drawn, because they are
// associated with switching to a keyboard modality.
const keyboardModalityWhitelist = [
'input:not([type])',
'input[type=text]',
'input[type=search]',
'input[type=url]',
'input[type=tel]',
'input[type=email]',
'input[type=password]',
'input[type=number]',
'input[type=date]',
'input[type=month]',
'input[type=week]',
'input[type=time]',
'input[type=datetime]',
'input[type=datetime-local]',
'textarea',
'[role=textbox]'
].join(',');
/**
* Helper function for legacy browsers and iframes which sometimes focus
* elements like document, body, and non-interactive SVG.
*/
function isValidFocusTarget(el) {
if (
el &&
el !== document &&
el.nodeName !== 'HTML' &&
el.nodeName !== 'BODY' &&
'classList' in el &&
'contains' in el.classList
) {
return true;
}
return false;
}
/**
* Computes whether the given element should automatically trigger the
* `focus-ring`.
* `focus-visible` attribute being added, i.e. whether it should always match
* `:focus-visible` when focused.
*/
const focusTriggersKeyboardModality = el => {
if (matches) {
return matches.call(el, keyboardModalityWhitelist) && matches.call(el, ':not([readonly])');
} else {
return false;
}
};
function focusTriggersKeyboardModality(el) {
const type = el.type;
const tagName = el.tagName;
const isReadOnly = el.readOnly;
/**
* Add the focus ring style
*/
const addFocusRing = () => {
if (styleElement && ruleExists) {
styleElement.sheet.deleteRule(0);
ruleExists = false;
if (tagName === 'INPUT' && inputTypesWhitelist[type] && !isReadOnly) {
return true;
}
};
/**
* Remove the focus ring style
*/
const removeFocusRing = () => {
if (styleElement && !ruleExists) {
styleElement.sheet.insertRule(rule, 0);
ruleExists = true;
if (tagName === 'TEXTAREA' && !isReadOnly) {
return true;
}
};
/**
* On `keydown`, set `hadKeyboardEvent`, to be removed 100ms later if there
* are no further keyboard events. The 100ms throttle handles cases where
* focus is redirected programmatically after a keyboard event, such as
* opening a menu or dialog.
*/
const handleKeyDown = e => {
hadKeyboardEvent = true;
if (keyboardThrottleTimeoutID !== 0) {
clearTimeout(keyboardThrottleTimeoutID);
if (el.isContentEditable) {
return true;
}
keyboardThrottleTimeoutID = setTimeout(() => {
hadKeyboardEvent = false;
keyboardThrottleTimeoutID = 0;
}, 100);
};
/**
* Display the focus-ring when the keyboard was used to focus
*/
const handleFocus = e => {
if (hadKeyboardEvent || focusTriggersKeyboardModality(e.target)) {
addFocusRing();
}
};
/**
* Remove the focus-ring when the keyboard was used to focus
*/
const handleBlur = () => {
if (!hadKeyboardEvent) {
removeFocusRing();
}
};
if (document.body && document.body.addEventListener) {
removeFocusRing();
document.body.addEventListener('keydown', handleKeyDown, true);
document.body.addEventListener('focus', handleFocus, true);
document.body.addEventListener('blur', handleBlur, true);
return false;
}
/**
* Add the `focus-visible` attribute to the given element if it was not added by
* the author.
*/
function addFocusVisibleAttribute(el) {
if (el.hasAttribute(focusVisibleAttributeName)) {
return;
}
el.setAttribute(focusVisibleAttributeName, true);
}
/**
* Remove the `focus-visible` attribute from the given element if it was not
* originally added by the author.
*/
function removeFocusVisibleAttribute(el) {
el.removeAttribute(focusVisibleAttributeName);
}
/**
* Remove the `focus-visible` attribute from all elements in the document.
*/
function removeAllFocusVisibleAttributes() {
const list = document.querySelectorAll(`[${focusVisibleAttributeName}]`);
for (let i = 0; i < list.length; i += 1) {
removeFocusVisibleAttribute(list[i]);
}
}
/**
* Treat `keydown` as a signal that the user is in keyboard modality.
* Apply `focus-visible` to any current active element and keep track
* of our keyboard modality state with `hadKeyboardEvent`.
*/
function onKeyDown(e) {
if (e.key !== 'Tab' && (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey)) {
return;
}
if (isValidFocusTarget(document.activeElement)) {
addFocusVisibleAttribute(document.activeElement);
}
hadKeyboardEvent = true;
}
/**
* If at any point a user clicks with a pointing device, ensure that we change
* the modality away from keyboard.
* This avoids the situation where a user presses a key on an already focused
* element, and then clicks on a different element, focusing it with a
* pointing device, while we still think we're in keyboard modality.
* It also avoids the situation where a user presses on an element within a
* previously keyboard-focused element (i.e., `e.target` is not the previously
* focused element, but one of its descendants) and we need to remove the
* focus ring because a `blur` event doesn't occur.
*/
function onPointerDown(e) {
if (hadKeyboardEvent === true) {
removeAllFocusVisibleAttributes();
}
hadKeyboardEvent = false;
}
/**
* On `focus`, add the `focus-visible` attribute to the target if:
* - the target received focus as a result of keyboard navigation, or
* - the event target is an element that will likely require interaction
* via the keyboard (e.g. a text box)
*/
function onFocus(e) {
// Prevent IE from focusing the document or HTML element.
if (!isValidFocusTarget(e.target)) {
return;
}
if (hadKeyboardEvent || focusTriggersKeyboardModality(e.target)) {
addFocusVisibleAttribute(e.target);
}
}
/**
* On `blur`, remove the `focus-visible` attribute from the target.
*/
function onBlur(e) {
if (!isValidFocusTarget(e.target)) {
return;
}
if (e.target.hasAttribute(focusVisibleAttributeName)) {
// To detect a tab/window switch, we look for a blur event followed
// rapidly by a visibility change.
// If we don't see a visibility change within 100ms, it's probably a
// regular focus change.
hadFocusVisibleRecently = true;
window.clearTimeout(hadFocusVisibleRecentlyTimeout);
hadFocusVisibleRecentlyTimeout = window.setTimeout(function() {
hadFocusVisibleRecently = false;
window.clearTimeout(hadFocusVisibleRecentlyTimeout);
}, 100);
removeFocusVisibleAttribute(e.target);
}
}
/**
* If the user changes tabs, keep track of whether or not the previously
* focused element had the focus-visible attribute.
*/
function onVisibilityChange(e) {
if (document.visibilityState === 'hidden') {
// If the tab becomes active again, the browser will handle calling focus
// on the element (Safari actually calls it twice).
// If this tab change caused a blur on an element with focus-visible,
// re-apply the attribute when the user switches back to the tab.
if (hadFocusVisibleRecently) {
hadKeyboardEvent = true;
}
addInitialPointerMoveListeners();
}
}
/**
* Add a group of listeners to detect usage of any pointing devices.
* These listeners will be added when the polyfill first loads, and anytime
* the window is blurred, so that they are active when the window regains
* focus.
*/
function addInitialPointerMoveListeners() {
document.addEventListener('mousemove', onInitialPointerMove);
document.addEventListener('mousedown', onInitialPointerMove);
document.addEventListener('mouseup', onInitialPointerMove);
document.addEventListener('pointermove', onInitialPointerMove);
document.addEventListener('pointerdown', onInitialPointerMove);
document.addEventListener('pointerup', onInitialPointerMove);
document.addEventListener('touchmove', onInitialPointerMove);
document.addEventListener('touchstart', onInitialPointerMove);
document.addEventListener('touchend', onInitialPointerMove);
}
function removeInitialPointerMoveListeners() {
document.removeEventListener('mousemove', onInitialPointerMove);
document.removeEventListener('mousedown', onInitialPointerMove);
document.removeEventListener('mouseup', onInitialPointerMove);
document.removeEventListener('pointermove', onInitialPointerMove);
document.removeEventListener('pointerdown', onInitialPointerMove);
document.removeEventListener('pointerup', onInitialPointerMove);
document.removeEventListener('touchmove', onInitialPointerMove);
document.removeEventListener('touchstart', onInitialPointerMove);
document.removeEventListener('touchend', onInitialPointerMove);
}
/**
* When the polfyill first loads, assume the user is in keyboard modality.
* If any event is received from a pointing device (e.g. mouse, pointer,
* touch), turn off keyboard modality.
* This accounts for situations where focus enters the page from the URL bar.
*/
function onInitialPointerMove(e) {
// Work around a Safari quirk that fires a mousemove on <html> whenever the
// window blurs, even if you're tabbing out of the page. ¯\_(ツ)_/¯
if (e.target.nodeName === 'HTML') {
return;
}
hadKeyboardEvent = false;
removeInitialPointerMoveListeners();
}
styleElement.sheet.insertRule(rule, 0);
document.addEventListener('keydown', onKeyDown, true);
document.addEventListener('mousedown', onPointerDown, true);
document.addEventListener('pointerdown', onPointerDown, true);
document.addEventListener('touchstart', onPointerDown, true);
document.addEventListener('focus', onFocus, true);
document.addEventListener('blur', onBlur, true);
document.addEventListener('visibilitychange', onVisibilityChange, true);
addInitialPointerMoveListeners();
};
export default modality;

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

@@ -180,6 +180,25 @@ describe('components/TextInput', () => {
});
describe('prop "onKeyPress"', () => {
test('arrow key', () => {
const onKeyPress = jest.fn();
const input = findNativeInput(mount(<TextInput onKeyPress={onKeyPress} />));
input.simulate('keyPress', { which: 37 });
expect(onKeyPress).toHaveBeenCalledTimes(1);
expect(onKeyPress).toBeCalledWith(
expect.objectContaining({
nativeEvent: {
altKey: undefined,
ctrlKey: undefined,
key: 'ArrowLeft',
metaKey: undefined,
shiftKey: undefined,
target: expect.anything()
}
})
);
});
test('backspace key', () => {
const onKeyPress = jest.fn();
const input = findNativeInput(mount(<TextInput onKeyPress={onKeyPress} />));
@@ -199,25 +218,6 @@ describe('components/TextInput', () => {
);
});
test('tab key', () => {
const onKeyPress = jest.fn();
const input = findNativeInput(mount(<TextInput onKeyPress={onKeyPress} />));
input.simulate('keyDown', { which: 9 });
expect(onKeyPress).toHaveBeenCalledTimes(1);
expect(onKeyPress).toBeCalledWith(
expect.objectContaining({
nativeEvent: {
altKey: undefined,
ctrlKey: undefined,
key: 'Tab',
metaKey: undefined,
shiftKey: undefined,
target: expect.anything()
}
})
);
});
test('enter key', () => {
const onKeyPress = jest.fn();
const input = findNativeInput(mount(<TextInput onKeyPress={onKeyPress} />));
@@ -237,6 +237,25 @@ describe('components/TextInput', () => {
);
});
test('escape key', () => {
const onKeyPress = jest.fn();
const input = findNativeInput(mount(<TextInput onKeyPress={onKeyPress} />));
input.simulate('keyPress', { which: 27 });
expect(onKeyPress).toHaveBeenCalledTimes(1);
expect(onKeyPress).toBeCalledWith(
expect.objectContaining({
nativeEvent: {
altKey: undefined,
ctrlKey: undefined,
key: 'Escape',
metaKey: undefined,
shiftKey: undefined,
target: expect.anything()
}
})
);
});
test('space key', () => {
const onKeyPress = jest.fn();
const input = findNativeInput(mount(<TextInput onKeyPress={onKeyPress} />));
@@ -256,17 +275,17 @@ describe('components/TextInput', () => {
);
});
test('arrow key', () => {
test('tab key', () => {
const onKeyPress = jest.fn();
const input = findNativeInput(mount(<TextInput onKeyPress={onKeyPress} />));
input.simulate('keyPress', { which: 37 });
input.simulate('keyDown', { which: 9 });
expect(onKeyPress).toHaveBeenCalledTimes(1);
expect(onKeyPress).toBeCalledWith(
expect.objectContaining({
nativeEvent: {
altKey: undefined,
ctrlKey: undefined,
key: 'ArrowLeft',
key: 'Tab',
metaKey: undefined,
shiftKey: undefined,
target: expect.anything()

View File

@@ -175,8 +175,6 @@ class TextInput extends Component<*> {
const {
autoCorrect,
editable,
/* eslint-disable */
inputAccessoryViewID,
keyboardType,
multiline,
numberOfLines,
@@ -193,22 +191,34 @@ class TextInput extends Component<*> {
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
@@ -307,11 +317,13 @@ class TextInput extends Component<*> {
// Prevent key events bubbling (see #612)
e.stopPropagation();
// Backspace, Tab, Cmd+Enter, and Arrow keys only fire 'keydown' DOM events
// Backspace, Escape, Tab, Cmd+Enter, and Arrow keys only fire 'keydown'
// DOM events
if (
e.which === 8 ||
e.which === 9 ||
(e.which === 13 && e.metaKey) ||
e.which === 27 ||
e.which === 37 ||
e.which === 38 ||
e.which === 39 ||
@@ -338,6 +350,9 @@ class TextInput extends Component<*> {
case 13:
keyValue = 'Enter';
break;
case 27:
keyValue = 'Escape';
break;
case 32:
keyValue = ' ';
break;

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

@@ -2,8 +2,8 @@
import UIManager from '..';
const createStyledNode = (style = {}) => {
const root = document.createElement('div');
const createStyledNode = (name = 'div', style = {}) => {
const root = document.createElement(name);
Object.keys(style).forEach(prop => {
root.style[prop] = style[prop];
});
@@ -18,6 +18,29 @@ const componentStub = {
};
describe('apis/UIManager', () => {
describe('focus', () => {
test('sets tabIndex="-1" on elements not programmatically focusable by default', () => {
const node = createStyledNode();
UIManager.focus(node);
expect(node.getAttribute('tabIndex')).toEqual('-1');
});
test('doesn\'t set tabIndex="-1" on elements with an existing tabIndex', () => {
const node = createStyledNode();
node.tabIndex = 0;
UIManager.focus(node);
expect(node.getAttribute('tabIndex')).toEqual('0');
});
test('doesn\'t set tabIndex="-1" on elements focusable by default', () => {
['a', 'input', 'select', 'textarea'].forEach(name => {
const node = createStyledNode(name);
UIManager.focus(node);
expect(node.getAttribute('tabIndex')).toBeNull();
});
});
});
describe('updateView', () => {
test('supports className alias for class', () => {
const node = createStyledNode();
@@ -27,7 +50,7 @@ describe('apis/UIManager', () => {
});
test('adds correct DOM styles to existing style', () => {
const node = createStyledNode({ color: 'red' });
const node = createStyledNode('div', { color: 'red' });
const props = { style: { marginTop: 0, marginBottom: 0, opacity: 0 } };
UIManager.updateView(node, props, componentStub);
expect(node.getAttribute('style')).toEqual(
@@ -36,7 +59,7 @@ describe('apis/UIManager', () => {
});
test('replaces input and textarea text', () => {
const node = createStyledNode();
const node = createStyledNode('textarea');
node.value = 'initial';
const textProp = { text: 'expected-text' };
const valueProp = { value: 'expected-value' };

View File

@@ -37,6 +37,13 @@ const measureLayout = (node, relativeToNativeNode, callback) => {
}
};
const focusableElements = {
A: true,
INPUT: true,
SELECT: true,
TEXTAREA: true
};
const UIManager = {
blur(node) {
try {
@@ -46,6 +53,13 @@ const UIManager = {
focus(node) {
try {
const name = node.nodeName;
// A tabIndex of -1 allows element to be programmatically focused but
// prevents keyboard focus, so we don't want to set the value on elements
// that support keyboard focus by default.
if (node.getAttribute('tabIndex') == null && focusableElements[name] == null) {
node.setAttribute('tabIndex', '-1');
}
node.focus();
} catch (err) {}
},

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

@@ -51,6 +51,8 @@ const ViewStylePropTypes = {
overscrollBehavior: overscrollBehaviorType,
overscrollBehaviorX: overscrollBehaviorType,
overscrollBehaviorY: overscrollBehaviorType,
scrollSnapAlign: string,
scrollSnapType: string,
WebkitMaskImage: string,
WebkitOverflowScrolling: oneOf(['auto', 'touch'])
};

View File

@@ -8,6 +8,7 @@ const whitelist = {
children: true,
disabled: true,
importantForAccessibility: true,
nativeID: true,
onBlur: true,
onContextMenu: true,
onFocus: 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

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

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

@@ -375,9 +375,14 @@ const ScrollResponderMixin = {
({ x, y, animated } = x || emptyObject);
}
const node = this.scrollResponderGetScrollableNode();
UIManager.updateView(node, { style: { scrollBehavior: !animated ? 'auto' : 'smooth' } }, this);
node.scrollLeft = x || 0;
node.scrollTop = y || 0;
const left = x || 0;
const top = y || 0;
if (typeof node.scroll === 'function') {
node.scroll({ top, left, behavior: !animated ? 'auto' : 'smooth' });
} else {
node.scrollLeft = left;
node.scrollTop = top;
}
},
/**

View File

@@ -23,6 +23,8 @@ const TransformPropTypes = {
shape({ scale: number }),
shape({ scaleX: number }),
shape({ scaleY: number }),
shape({ scaleZ: number }),
shape({ scale3d: string }),
shape({ skewX: string }),
shape({ skewY: string }),
shape({ translateX: numberOrString }),

View File

@@ -63,14 +63,15 @@ const observe = instance => {
};
const unobserve = instance => {
delete registry[instance._layoutId];
if (resizeObserver) {
const node = findNodeHandle(instance);
if (node) {
delete registry[node._layoutId];
delete node._layoutId;
resizeObserver.unobserve(node);
}
} else {
delete registry[instance._layoutId];
delete instance._layoutId;
}
};
@@ -97,7 +98,9 @@ const applyLayout = Component => {
function componentDidMount() {
this._layoutState = emptyObject;
this._isMounted = true;
observe(this);
if (this.props.onLayout) {
observe(this);
}
}
);
@@ -116,7 +119,9 @@ const applyLayout = Component => {
componentWillUnmount,
function componentWillUnmount() {
this._isMounted = false;
unobserve(this);
if (this.props.onLayout) {
unobserve(this);
}
}
);

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

@@ -7,7 +7,7 @@
* @flow
*/
import createPrefixer from 'inline-style-prefixer/static/createPrefixer';
import createPrefixer from 'inline-style-prefixer/lib/createPrefixer';
import staticData from './static';
const prefixAll = createPrefixer(staticData);

View File

@@ -1,14 +1,15 @@
import crossFade from 'inline-style-prefixer/static/plugins/crossFade';
import cursor from 'inline-style-prefixer/static/plugins/cursor';
import filter from 'inline-style-prefixer/static/plugins/filter';
import flex from 'inline-style-prefixer/static/plugins/flex';
import flexboxIE from 'inline-style-prefixer/static/plugins/flexboxIE';
import flexboxOld from 'inline-style-prefixer/static/plugins/flexboxOld';
import gradient from 'inline-style-prefixer/static/plugins/gradient';
import imageSet from 'inline-style-prefixer/static/plugins/imageSet';
import position from 'inline-style-prefixer/static/plugins/position';
import sizing from 'inline-style-prefixer/static/plugins/sizing';
import transition from 'inline-style-prefixer/static/plugins/transition';
import backgroundClip from 'inline-style-prefixer/lib/plugins/backgroundClip';
import crossFade from 'inline-style-prefixer/lib/plugins/crossFade';
import cursor from 'inline-style-prefixer/lib/plugins/cursor';
import filter from 'inline-style-prefixer/lib/plugins/filter';
import flex from 'inline-style-prefixer/lib/plugins/flex';
import flexboxIE from 'inline-style-prefixer/lib/plugins/flexboxIE';
import flexboxOld from 'inline-style-prefixer/lib/plugins/flexboxOld';
import gradient from 'inline-style-prefixer/lib/plugins/gradient';
import imageSet from 'inline-style-prefixer/lib/plugins/imageSet';
import position from 'inline-style-prefixer/lib/plugins/position';
import sizing from 'inline-style-prefixer/lib/plugins/sizing';
import transition from 'inline-style-prefixer/lib/plugins/transition';
const w = ['Webkit'];
const m = ['Moz'];
const ms = ['ms'];
@@ -18,6 +19,7 @@ const wmms = ['Webkit', 'Moz', 'ms'];
export default {
plugins: [
backgroundClip,
crossFade,
cursor,
filter,
@@ -120,6 +122,7 @@ export default {
flowInto: wms,
flowFrom: wms,
regionFragment: wms,
textOrientation: w,
textAlignLast: m,
tabSize: m,
wrapFlow: ms,
@@ -144,7 +147,7 @@ export default {
gridRowGap: ms,
gridArea: ms,
gridGap: ms,
textSizeAdjust: wms,
textSizeAdjust: ['ms', 'Webkit'],
borderImage: w,
borderImageOutset: w,
borderImageRepeat: w,

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

@@ -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) => {
@@ -1561,7 +1561,7 @@ class CellRenderer extends React.Component<
getItemLayout?: ?Function,
renderItem: renderItemType,
},
prevCellKey: ?string,
prevCellKey: ?string
},
$FlowFixMeState,
> {
@@ -1649,9 +1649,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]
: [styles.columnReverse, inversionStyle]
: horizontal ? [styles.row, inversionStyle] : inversionStyle;
if (!CellRendererComponent) {
return (
<View style={cellStyle} onLayout={onLayout}>
@@ -1702,6 +1702,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,7 +1,7 @@
{
"private": true,
"name": "website",
"version": "0.8.10",
"version": "0.9.13",
"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.4.1",
"react-dom": "^16.4.1",
"react-native-web": "0.8.10"
"react": "^16.5.1",
"react-dom": "^16.5.1",
"react-native-web": "0.9.13"
},
"devDependencies": {
"babel-plugin-react-native-web": "0.8.10",
"babel-plugin-react-native-web": "0.9.13",
"url-loader": "^1.0.1",
"webpack": "^4.8.1"
}

View File

@@ -101,6 +101,12 @@ const ScrollViewScreen = () => (
]}
/>
<DocItem
name="pagingEnabled"
typeInfo="?boolean = false"
description="When true, the scroll view snaps to individual items in the list when scrolling."
/>
<DocItem
name="scrollEnabled"
typeInfo="?boolean = true"

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

@@ -14,12 +14,12 @@ class AppText extends React.PureComponent {
};
render() {
const { style, ...rest } = this.props;
const { accessibilityRole, style, ...rest } = this.props;
const isInAParentText = this.context;
return (
<Text
{...rest}
accessibilityRole={rest.href ? 'link' : undefined}
accessibilityRole={rest.href ? 'link' : accessibilityRole}
style={[!isInAParentText && styles.baseText, style, rest.href && styles.link]}
/>
);

View File

@@ -8,7 +8,11 @@ import AppText from './AppText';
import React from 'react';
import { StyleSheet, View } from 'react-native';
const SectionTitle = ({ children }) => <AppText style={styles.sectionTitle}>{children}</AppText>;
const SectionTitle = ({ children }) => (
<AppText accessibilityRole="heading" aria-level="2" style={styles.sectionTitle}>
{children}
</AppText>
);
const Section = ({ children, title }) => (
<View>

View File

@@ -10,7 +10,11 @@ import insertBetween from './insertBetween';
import React from 'react';
import { StyleSheet, View } from 'react-native';
const Title = ({ children }) => <AppText style={styles.title}>{children}</AppText>;
const Title = ({ children }) => (
<AppText accessibilityRole="heading" style={styles.title}>
{children}
</AppText>
);
export const Description = ({ children }) => (
<AppText style={styles.description}>

View File

@@ -1,6 +1,6 @@
'use strict';
const generator = require('inline-style-prefixer/generator');
const generator = require('inline-style-prefixer/lib/generator').default;
const path = require('path');
const browserList = {
@@ -19,8 +19,5 @@ const browserList = {
};
generator(browserList, {
staticPath: path.join(
__dirname,
'../../packages/react-native-web/src/modules/prefixStyles/static.js'
)
path: path.join(__dirname, '../../packages/react-native-web/src/modules/prefixStyles/static.js')
});

1601
yarn.lock

File diff suppressed because it is too large Load Diff