mirror of
https://github.com/zhigang1992/react-native-web.git
synced 2026-03-24 22:47:12 +08:00
Compare commits
63 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
000b92e707 | ||
|
|
d5f5dbccdb | ||
|
|
79456d5920 | ||
|
|
2d1e303a6a | ||
|
|
209ff1fa40 | ||
|
|
34d8160a43 | ||
|
|
ada5651be2 | ||
|
|
9fe9d3c68a | ||
|
|
1e202b6bd5 | ||
|
|
2b77bfd853 | ||
|
|
d0ac55aa4f | ||
|
|
66dd1bd9ef | ||
|
|
6add18c6f0 | ||
|
|
30d7c31b65 | ||
|
|
f7e6b43422 | ||
|
|
4b3f6efb21 | ||
|
|
85e098deec | ||
|
|
c949b0145a | ||
|
|
86b4cf5a51 | ||
|
|
1b7ce4eec6 | ||
|
|
8c8978ff76 | ||
|
|
513b5de881 | ||
|
|
145f80409d | ||
|
|
6d92cc5ec3 | ||
|
|
ec6458c09d | ||
|
|
a84c2ac95e | ||
|
|
75db0e9183 | ||
|
|
4b9a5fd8b4 | ||
|
|
b6be677db9 | ||
|
|
91c9457392 | ||
|
|
006d315a1a | ||
|
|
220eb79357 | ||
|
|
5db9a765b0 | ||
|
|
931d666fcc | ||
|
|
40c433c6df | ||
|
|
9888c2a3c6 | ||
|
|
3fa18becc7 | ||
|
|
aafeb0adad | ||
|
|
89468b7d6e | ||
|
|
f66af5e04d | ||
|
|
2363524fa7 | ||
|
|
5855e55615 | ||
|
|
5033e12d18 | ||
|
|
d6e8530f4d | ||
|
|
ad188a7ad6 | ||
|
|
bfaeae904e | ||
|
|
a54bdeec09 | ||
|
|
8fa7dc63ec | ||
|
|
d841db2337 | ||
|
|
8e7d31cff5 | ||
|
|
0764687a8f | ||
|
|
d31bdf2cf8 | ||
|
|
1f3a77dada | ||
|
|
c3cbd53a8a | ||
|
|
4f5ee15e4b | ||
|
|
9a1cade1f0 | ||
|
|
506dba933c | ||
|
|
c0de9dddf3 | ||
|
|
96c9c06272 | ||
|
|
505e3faee8 | ||
|
|
1f06229289 | ||
|
|
fc743e6eee | ||
|
|
ef97adec6e |
17
README.md
17
README.md
@@ -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. |
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
15
package.json
15
package.json
@@ -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/*"
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -1,2 +0,0 @@
|
||||
import View from './View';
|
||||
export default View;
|
||||
@@ -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;
|
||||
@@ -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
|
||||
};
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -1,2 +0,0 @@
|
||||
import View from './View';
|
||||
export default View;
|
||||
@@ -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);
|
||||
@@ -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
|
||||
};
|
||||
@@ -12,6 +12,9 @@ module.exports = {
|
||||
path: path.resolve(appDirectory, 'dist'),
|
||||
filename: 'bundle.js'
|
||||
},
|
||||
optimization: {
|
||||
minimize: process.env.NODE_ENV === 'production'
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -59,6 +59,7 @@ class Picker extends Component<Props> {
|
||||
itemStyle,
|
||||
mode,
|
||||
prompt,
|
||||
onValueChange,
|
||||
/* eslint-enable */
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
@@ -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"`;
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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.)`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}",
|
||||
]
|
||||
`;
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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 };
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -21,6 +21,7 @@ const TextPropTypes = {
|
||||
accessible: bool,
|
||||
children: any,
|
||||
importantForAccessibility: oneOf(['auto', 'no', 'no-hide-descendants', 'yes']),
|
||||
nativeID: string,
|
||||
numberOfLines: number,
|
||||
onBlur: func,
|
||||
onContextMenu: func,
|
||||
|
||||
@@ -7,12 +7,14 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import ColorPropType from '../ColorPropType';
|
||||
import TextStylePropTypes from '../Text/TextStylePropTypes';
|
||||
import { oneOf } from 'prop-types';
|
||||
|
||||
const TextInputStylePropTypes = {
|
||||
...TextStylePropTypes,
|
||||
/* @platform web */
|
||||
caretColor: ColorPropType,
|
||||
resize: oneOf(['none', 'vertical', 'horizontal', 'both'])
|
||||
};
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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' };
|
||||
|
||||
@@ -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) {}
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -51,6 +51,8 @@ const ViewStylePropTypes = {
|
||||
overscrollBehavior: overscrollBehaviorType,
|
||||
overscrollBehaviorX: overscrollBehaviorType,
|
||||
overscrollBehaviorY: overscrollBehaviorType,
|
||||
scrollSnapAlign: string,
|
||||
scrollSnapType: string,
|
||||
WebkitMaskImage: string,
|
||||
WebkitOverflowScrolling: oneOf(['auto', 'touch'])
|
||||
};
|
||||
|
||||
@@ -8,6 +8,7 @@ const whitelist = {
|
||||
children: true,
|
||||
disabled: true,
|
||||
importantForAccessibility: true,
|
||||
nativeID: true,
|
||||
onBlur: true,
|
||||
onContextMenu: true,
|
||||
onFocus: true,
|
||||
|
||||
@@ -36,24 +36,38 @@ describe('modules/createElement', () => {
|
||||
expect(component.find('div').length).toBe(1);
|
||||
});
|
||||
|
||||
[{ disabled: true }, { disabled: false }].forEach(({ disabled }) => {
|
||||
describe(`value is "button" and disabled is "${disabled}"`, () => {
|
||||
[{ name: 'Enter', which: 13 }, { name: 'Space', which: 32 }].forEach(({ name, which }) => {
|
||||
test(`"onClick" is ${disabled ? 'not ' : ''}called when "${name}" is pressed`, () => {
|
||||
const onClick = jest.fn();
|
||||
const component = shallow(
|
||||
createElement('span', { accessibilityRole: 'button', disabled, onClick })
|
||||
);
|
||||
component.find('span').simulate('keyPress', {
|
||||
isDefaultPrevented() {},
|
||||
nativeEvent: {},
|
||||
preventDefault() {},
|
||||
which
|
||||
});
|
||||
expect(onClick).toHaveBeenCalledTimes(disabled ? 0 : 1);
|
||||
const testRole = ({ accessibilityRole, disabled }) => {
|
||||
[{ key: 'Enter', which: 13 }, { key: 'Space', which: 32 }].forEach(({ key, which }) => {
|
||||
test(`"onClick" is ${disabled ? 'not ' : ''}called when "${key}" key is pressed`, () => {
|
||||
const onClick = jest.fn();
|
||||
const component = shallow(
|
||||
createElement('span', { accessibilityRole, disabled, onClick })
|
||||
);
|
||||
component.find('span').simulate('keyPress', {
|
||||
isDefaultPrevented() {},
|
||||
nativeEvent: {},
|
||||
preventDefault() {},
|
||||
which
|
||||
});
|
||||
expect(onClick).toHaveBeenCalledTimes(disabled ? 0 : 1);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
describe('value is "button" and disabled is "true"', () => {
|
||||
testRole({ accessibilityRole: 'button', disabled: true });
|
||||
});
|
||||
|
||||
describe('value is "button" and disabled is "false"', () => {
|
||||
testRole({ accessibilityRole: 'button', disabled: false });
|
||||
});
|
||||
|
||||
describe('value is "menuitem" and disabled is "true"', () => {
|
||||
testRole({ accessibilityRole: 'menuitem', disabled: true });
|
||||
});
|
||||
|
||||
describe('value is "menuitem" and disabled is "false"', () => {
|
||||
testRole({ accessibilityRole: 'menuitem', disabled: false });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,14 +9,12 @@
|
||||
|
||||
import AccessibilityUtil from '../../modules/AccessibilityUtil';
|
||||
import createDOMProps from '../../modules/createDOMProps';
|
||||
import { injectEventPluginsByName } from 'react-dom/unstable-native-dependencies';
|
||||
import normalizeNativeEvent from '../../modules/normalizeNativeEvent';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import ResponderEventPlugin from '../../modules/ResponderEventPlugin';
|
||||
|
||||
const { EventPluginHub } = ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
|
||||
|
||||
EventPluginHub.injection.injectEventPluginsByName({
|
||||
injectEventPluginsByName({
|
||||
ResponderEventPlugin
|
||||
});
|
||||
|
||||
@@ -48,7 +46,7 @@ const eventHandlerNames = {
|
||||
const adjustProps = domProps => {
|
||||
const { onClick, onResponderRelease, role } = domProps;
|
||||
|
||||
const isButtonRole = role === 'button';
|
||||
const isButtonLikeRole = AccessibilityUtil.buttonLikeRoles[role];
|
||||
const isDisabled = AccessibilityUtil.isDisabled(domProps);
|
||||
const isLinkRole = role === 'link';
|
||||
|
||||
@@ -56,7 +54,7 @@ const adjustProps = domProps => {
|
||||
const prop = domProps[propName];
|
||||
const isEventHandler = typeof prop === 'function' && eventHandlerNames[propName];
|
||||
if (isEventHandler) {
|
||||
if (isButtonRole && isDisabled) {
|
||||
if (isButtonLikeRole && isDisabled) {
|
||||
domProps[propName] = undefined;
|
||||
} else {
|
||||
// TODO: move this out of the render path
|
||||
@@ -80,8 +78,8 @@ const adjustProps = domProps => {
|
||||
};
|
||||
}
|
||||
|
||||
// Button role should trigger 'onClick' if SPACE or ENTER keys are pressed.
|
||||
if (isButtonRole && !isDisabled) {
|
||||
// Button-like roles should trigger 'onClick' if SPACE or ENTER keys are pressed.
|
||||
if (isButtonLikeRole && !isDisabled) {
|
||||
domProps.onKeyPress = function(e) {
|
||||
if (!e.isDefaultPrevented() && (e.which === 13 || e.which === 32)) {
|
||||
e.preventDefault();
|
||||
|
||||
19
packages/react-native-web/src/modules/AccessibilityUtil/buttonLikeRoles.js
vendored
Normal file
19
packages/react-native-web/src/modules/AccessibilityUtil/buttonLikeRoles.js
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Copyright (c) 2017-present, Nicolas Gallagher.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
const buttonLikeRoles: { [string]: boolean } = {
|
||||
// ARIA button behaves like native 'button' element
|
||||
button: true,
|
||||
// ARIA menuitem responds to Enter/Space like a button. Spec requires AT to
|
||||
// ignore ARIA roles of any children.
|
||||
// https://www.w3.org/WAI/GL/wiki/Using_ARIA_menus
|
||||
menuitem: true
|
||||
};
|
||||
|
||||
export default buttonLikeRoles;
|
||||
@@ -7,11 +7,13 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import buttonLikeRoles from './buttonLikeRoles';
|
||||
import isDisabled from './isDisabled';
|
||||
import propsToAccessibilityComponent from './propsToAccessibilityComponent';
|
||||
import propsToAriaRole from './propsToAriaRole';
|
||||
|
||||
const AccessibilityUtil = {
|
||||
buttonLikeRoles,
|
||||
isDisabled,
|
||||
propsToAccessibilityComponent,
|
||||
propsToAriaRole
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 }),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -65,9 +65,7 @@ describe('modules/createDOMProps', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('"accessibilityRole" of "button"', () => {
|
||||
const accessibilityRole = 'button';
|
||||
|
||||
const testFocusableRole = accessibilityRole => {
|
||||
test('default case', () => {
|
||||
expect(createProps({ accessibilityRole })).toEqual(
|
||||
expect.objectContaining({ 'data-focusable': true, tabIndex: '0' })
|
||||
@@ -118,6 +116,14 @@ describe('modules/createDOMProps', () => {
|
||||
})
|
||||
).not.toEqual(expect.objectContaining({ 'data-focusable': true, tabIndex: '0' }));
|
||||
});
|
||||
};
|
||||
|
||||
describe('"accessibilityRole" of "button"', () => {
|
||||
testFocusableRole('button');
|
||||
});
|
||||
|
||||
describe('"accessibilityRole" of "menuitem"', () => {
|
||||
testFocusableRole('menuitem');
|
||||
});
|
||||
|
||||
describe('with unfocusable accessibilityRole', () => {
|
||||
@@ -177,6 +183,12 @@ describe('modules/createDOMProps', () => {
|
||||
expect(props['aria-hidden']).toEqual(true);
|
||||
});
|
||||
|
||||
test('prop "nativeID" becomes "id"', () => {
|
||||
const nativeID = 'Example.nativeID';
|
||||
const props = createProps({ nativeID });
|
||||
expect(props.id).toEqual(nativeID);
|
||||
});
|
||||
|
||||
test('prop "testID" becomes "data-testid"', () => {
|
||||
const testID = 'Example.testID';
|
||||
const props = createProps({ testID });
|
||||
|
||||
@@ -77,6 +77,7 @@ const createDOMProps = (component, props, styleResolver) => {
|
||||
accessibilityLabel,
|
||||
accessibilityLiveRegion,
|
||||
importantForAccessibility,
|
||||
nativeID,
|
||||
placeholderTextColor,
|
||||
pointerEvents,
|
||||
style: providedStyle,
|
||||
@@ -131,7 +132,7 @@ const createDOMProps = (component, props, styleResolver) => {
|
||||
} else {
|
||||
domProps['data-focusable'] = true;
|
||||
}
|
||||
} else if (role === 'button' || role === 'textbox') {
|
||||
} else if (AccessibilityUtil.buttonLikeRoles[role] || role === 'textbox') {
|
||||
if (accessible !== false && focusable) {
|
||||
domProps['data-focusable'] = true;
|
||||
domProps.tabIndex = '0';
|
||||
@@ -164,10 +165,15 @@ const createDOMProps = (component, props, styleResolver) => {
|
||||
}
|
||||
|
||||
// OTHER
|
||||
// Native element ID
|
||||
if (nativeID && nativeID.constructor === String) {
|
||||
domProps.id = nativeID;
|
||||
}
|
||||
// Link security and automation test ids
|
||||
if (component === 'a' && domProps.target === '_blank') {
|
||||
domProps.rel = `${domProps.rel || ''} noopener noreferrer`;
|
||||
}
|
||||
// Automated test IDs
|
||||
if (testID && testID.constructor === String) {
|
||||
domProps['data-testid'] = testID;
|
||||
}
|
||||
|
||||
@@ -78,6 +78,7 @@ function normalizeTouchEvent(nativeEvent) {
|
||||
typeof nativeEvent.stopPropagation === 'function'
|
||||
? nativeEvent.stopPropagation.bind(nativeEvent)
|
||||
: emptyFunction;
|
||||
const singleChangedTouch = changedTouches[0];
|
||||
|
||||
const event = {
|
||||
_normalized: true,
|
||||
@@ -85,11 +86,15 @@ function normalizeTouchEvent(nativeEvent) {
|
||||
cancelable: nativeEvent.cancelable,
|
||||
changedTouches,
|
||||
defaultPrevented: nativeEvent.defaultPrevented,
|
||||
identifier: undefined,
|
||||
locationX: undefined,
|
||||
locationY: undefined,
|
||||
pageX: nativeEvent.pageX,
|
||||
pageY: nativeEvent.pageY,
|
||||
identifier: singleChangedTouch ? singleChangedTouch.identifier : undefined,
|
||||
get locationX() {
|
||||
return singleChangedTouch ? singleChangedTouch.locationX : undefined;
|
||||
},
|
||||
get locationY() {
|
||||
return singleChangedTouch ? singleChangedTouch.locationY : undefined;
|
||||
},
|
||||
pageX: singleChangedTouch ? singleChangedTouch.pageX : nativeEvent.pageX,
|
||||
pageY: singleChangedTouch ? singleChangedTouch.pageY : nativeEvent.pageY,
|
||||
preventDefault,
|
||||
stopImmediatePropagation,
|
||||
stopPropagation,
|
||||
@@ -102,14 +107,6 @@ function normalizeTouchEvent(nativeEvent) {
|
||||
which: nativeEvent.which
|
||||
};
|
||||
|
||||
if (changedTouches[0]) {
|
||||
event.identifier = changedTouches[0].identifier;
|
||||
event.pageX = changedTouches[0].pageX;
|
||||
event.pageY = changedTouches[0].pageY;
|
||||
event.locationX = changedTouches[0].locationX;
|
||||
event.locationY = changedTouches[0].locationY;
|
||||
}
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
@@ -164,8 +161,12 @@ function normalizeMouseEvent(nativeEvent) {
|
||||
changedTouches: touches,
|
||||
defaultPrevented: nativeEvent.defaultPrevented,
|
||||
identifier: touches[0].identifier,
|
||||
locationX: touches[0].locationX,
|
||||
locationY: touches[0].locationY,
|
||||
get locationX() {
|
||||
return touches[0].locationX;
|
||||
},
|
||||
get locationY() {
|
||||
return touches[0].locationY;
|
||||
},
|
||||
pageX: nativeEvent.pageX,
|
||||
pageY: nativeEvent.pageY,
|
||||
preventDefault,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -116,6 +116,12 @@ const TextScreen = () => (
|
||||
]}
|
||||
/>
|
||||
|
||||
<DocItem
|
||||
name="nativeID"
|
||||
typeInfo="?string"
|
||||
description="Used to locate this view from any native DOM code, or to define accessibility relationships. This is rendered to the native 'id' DOM attribute"
|
||||
/>
|
||||
|
||||
<DocItem
|
||||
name="numberOfLines"
|
||||
typeInfo="?number"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { processColor, StyleSheet, View, Text, TouchableHighlight } from 'react-native';
|
||||
import { StyleSheet, View, Text, TouchableHighlight } from 'react-native';
|
||||
|
||||
export default class TouchableCustomStyleOverridesExample extends React.Component {
|
||||
buttons = ['One', 'Two', 'Three'];
|
||||
@@ -26,7 +26,7 @@ export default class TouchableCustomStyleOverridesExample extends React.Componen
|
||||
key={button}
|
||||
onPress={this.select(button)}
|
||||
style={[styles.touchable, this.state[button] && styles.blue]}
|
||||
underlayColor={processColor('#1B95E0', 0.125)}
|
||||
underlayColor="#1b95e020"
|
||||
>
|
||||
<Text style={[!this.state[button] && styles.text]}>{button}</Text>
|
||||
</TouchableHighlight>
|
||||
@@ -39,7 +39,7 @@ export default class TouchableCustomStyleOverridesExample extends React.Componen
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
blue: {
|
||||
backgroundColor: processColor('#1B95E0', 0.25),
|
||||
backgroundColor: '#1b95e040',
|
||||
borderColor: '#1B95E0'
|
||||
},
|
||||
text: {
|
||||
|
||||
@@ -119,6 +119,12 @@ const ViewScreen = () => (
|
||||
]}
|
||||
/>
|
||||
|
||||
<DocItem
|
||||
name="nativeID"
|
||||
typeInfo="?string"
|
||||
description="Used to locate this view from any native DOM code, or to define accessibility relationships. This is rendered to the native 'id' DOM attribute"
|
||||
/>
|
||||
|
||||
<DocItem name="onBlur" typeInfo="?function" />
|
||||
<DocItem name="onContextMenu" typeInfo="?function" />
|
||||
<DocItem name="onFocus" typeInfo="?function" />
|
||||
|
||||
@@ -3,7 +3,7 @@ import { View, StyleSheet } from 'react-native';
|
||||
|
||||
export default class Sandbox extends React.PureComponent {
|
||||
render() {
|
||||
return <View styles={styles.root} />;
|
||||
return <View style={styles.root} />;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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]}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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')
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user