mirror of
https://github.com/zhigang1992/react-native-web.git
synced 2026-03-30 09:24:09 +08:00
Compare commits
47 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e473031321 | ||
|
|
97e2fb53a1 | ||
|
|
7b79c27bd0 | ||
|
|
18248f1229 | ||
|
|
45ccd9517f | ||
|
|
550eca672f | ||
|
|
020c06d3f2 | ||
|
|
93eb3f041f | ||
|
|
77c778a9bf | ||
|
|
05e87ab3f1 | ||
|
|
e7108fd414 | ||
|
|
1183cf36b8 | ||
|
|
b65743fa66 | ||
|
|
b8995c20eb | ||
|
|
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 |
@@ -1,4 +1,11 @@
|
||||
{
|
||||
"settings": {
|
||||
"react": {
|
||||
"pragma": "React",
|
||||
"version": "16.6",
|
||||
"flowVersion": "0.78" // Flow version
|
||||
}
|
||||
},
|
||||
// babel parser to support ES6/7 features
|
||||
"parser": "babel-eslint",
|
||||
"parserOptions": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
language: node_js
|
||||
|
||||
node_js:
|
||||
- "8"
|
||||
- "10"
|
||||
|
||||
before_install:
|
||||
# Install Yarn
|
||||
|
||||
13
README.md
13
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+.
|
||||
|
||||
@@ -75,7 +76,7 @@ Examples of using React Native for Web with other web tools:
|
||||
|
||||
* [Docz](https://github.com/pedronauck/docz-plugin-react-native)
|
||||
* [Gatsby](https://github.com/slorber/gatsby-plugin-react-native-web)
|
||||
* [Next.js](https://github.com/zeit/next.js/tree/master/examples/with-react-native-web)
|
||||
* [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. |
|
||||
|
||||
7
babel.config.js
Normal file
7
babel.config.js
Normal file
@@ -0,0 +1,7 @@
|
||||
module.exports = function(api) {
|
||||
api.cache(true);
|
||||
|
||||
return {
|
||||
presets: ['./scripts/babel/preset']
|
||||
};
|
||||
};
|
||||
@@ -35,6 +35,34 @@ using `aria-label`.
|
||||
</TouchableOpacity>
|
||||
```
|
||||
|
||||
### accessibilityLiveRegion
|
||||
|
||||
When components dynamically change we may need to inform the user. The
|
||||
`accessibilityLiveRegion` property serves this purpose and can be set to
|
||||
`none`, `polite` and `assertive`. On web, `accessibilityLiveRegion` is
|
||||
implemented using `aria-live`.
|
||||
|
||||
* `none`: Accessibility services should not announce changes to this view.
|
||||
* `polite`: Accessibility services should announce changes to this view.
|
||||
* `assertive`: Accessibility services should interrupt ongoing speech to immediately announce changes to this view.
|
||||
|
||||
```
|
||||
<TouchableWithoutFeedback onPress={this._addOne}>
|
||||
<View style={styles.embedded}>
|
||||
<Text>Click me</Text>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
|
||||
<Text accessibilityLiveRegion="polite">
|
||||
Clicked {this.state.count} times
|
||||
</Text>
|
||||
```
|
||||
|
||||
In the above example, method `_addOne` changes the `state.count` variable. As
|
||||
soon as an end user clicks the `TouchableWithoutFeedback`, screen readers
|
||||
announce text in the `Text` view because of its
|
||||
`accessibilityLiveRegion="polite"` property.
|
||||
|
||||
### accessibilityRole
|
||||
|
||||
In some cases, we also want to alert the end user of the type of selected
|
||||
@@ -49,7 +77,8 @@ element][html-aria-url] and ARIA `role`, where possible. In most cases, both
|
||||
the element and ARIA `role` are rendered. While this may contradict some ARIA
|
||||
recommendations, it also helps avoid certain browser bugs, HTML5 conformance
|
||||
errors, and accessibility anti-patterns (e.g., giving a `heading` role to a
|
||||
`button` element).
|
||||
`button` element). On the Web, `accessibilityRole` supports more values than
|
||||
React Native does for [Andriod and iOS](https://facebook.github.io/react-native/docs/accessibility#accessibilityrole-ios-android).
|
||||
|
||||
Straight-forward examples:
|
||||
|
||||
@@ -85,33 +114,12 @@ Note: Avoid changing `accessibilityRole` values over time or after user
|
||||
actions. Generally, accessibility APIs do not provide a means of notifying
|
||||
assistive technologies of a `role` value change.
|
||||
|
||||
### accessibilityLiveRegion
|
||||
### accessibilityStates
|
||||
|
||||
When components dynamically change we may need to inform the user. The
|
||||
`accessibilityLiveRegion` property serves this purpose and can be set to
|
||||
`none`, `polite` and `assertive`. On web, `accessibilityLiveRegion` is
|
||||
implemented using `aria-live`.
|
||||
|
||||
* `none`: Accessibility services should not announce changes to this view.
|
||||
* `polite`: Accessibility services should announce changes to this view.
|
||||
* `assertive`: Accessibility services should interrupt ongoing speech to immediately announce changes to this view.
|
||||
|
||||
```
|
||||
<TouchableWithoutFeedback onPress={this._addOne}>
|
||||
<View style={styles.embedded}>
|
||||
<Text>Click me</Text>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
|
||||
<Text accessibilityLiveRegion="polite">
|
||||
Clicked {this.state.count} times
|
||||
</Text>
|
||||
```
|
||||
|
||||
In the above example, method `_addOne` changes the `state.count` variable. As
|
||||
soon as an end user clicks the `TouchableWithoutFeedback`, screen readers
|
||||
announce text in the `Text` view because of its
|
||||
`accessibilityLiveRegion="polite"` property.
|
||||
The `accessibilityStates` prop is an array of values used to infer the
|
||||
analogous ARIA states, e.g., `aria-disabled`, `aria-pressed`, `aria-selected`.
|
||||
On the Web, `accessibilityStates` supports more values than React Native does
|
||||
for [Andriod and iOS](https://facebook.github.io/react-native/docs/accessibility#accessibilitystate-ios-android).
|
||||
|
||||
### importantForAccessibility
|
||||
|
||||
|
||||
@@ -109,5 +109,5 @@ If you're using `skpm`, you can rely on an [undocumented
|
||||
feature](https://github.com/sketch-pm/skpm/blob/master/lib/utils/webpackConfig.js)
|
||||
which will merge your `webpack.config.js`, `.babelrc`, or `package.json` Babel
|
||||
config into its internal webpack config. The simplest option may be to use the
|
||||
[babel-plugin-module-alias](https://www.npmjs.com/package/babel-plugin-module-alias)
|
||||
[babel-plugin-module-resolver](https://www.npmjs.com/package/babel-plugin-module-resolver)
|
||||
and configure it in your `package.json`.
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -148,8 +148,8 @@ styles defined using `StyleSheet.create` will ultimately be rendered using CSS
|
||||
class names.
|
||||
|
||||
React Native for Web introduced a novel strategy to achieve this. Each rule is
|
||||
broken down into declarations, properties are expanded to their long-form, and
|
||||
the resulting key-value pairs are mapped to unique "atomic CSS" class names.
|
||||
broken down into declarations and the resulting key-value pairs are mapped to
|
||||
unique "atomic CSS" class names.
|
||||
|
||||
Input:
|
||||
|
||||
@@ -158,7 +158,9 @@ const Box = () => <View style={styles.box} />
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
box: {
|
||||
margin: 0
|
||||
backgroundColor: 'red',
|
||||
height: 100,
|
||||
width: 100,
|
||||
}
|
||||
});
|
||||
```
|
||||
@@ -167,13 +169,12 @@ Output:
|
||||
|
||||
```html
|
||||
<style>
|
||||
.rn-1mnahxq { margin-top: 0px; }
|
||||
.rn-61z16t { margin-right: 0px; }
|
||||
.rn-p1pxzi { margin-bottom: 0px; }
|
||||
.rn-11wrixw { margin-left: 0px; }
|
||||
.r-1mnahxq { background-color: red; }
|
||||
.r-p1pxzi { height: 100px; }
|
||||
.r-61z16t { width: 100px; }
|
||||
</style>
|
||||
|
||||
<div class="rn-156q2ks rn-61z16t rn-p1pxzi rn-11wrixw"></div>
|
||||
<div class="rn-156q2ks rn-61z16t rn-p1pxzi"></div>
|
||||
```
|
||||
|
||||
This ensures that CSS order doesn't impact rendering and CSS rules are
|
||||
@@ -226,6 +227,6 @@ handled at the component-level.
|
||||
|
||||
### What about using Dev Tools?
|
||||
|
||||
React Dev Tools supports inspecting and editing of React Native styles. It's
|
||||
React Dev Tools supports inspecting of React Native styles. It's
|
||||
recommended that you rely more on React Dev Tools and live/hot-reloading rather
|
||||
than inspecting and editing the DOM directly.
|
||||
|
||||
50
package.json
50
package.json
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"private": true,
|
||||
"version": "0.9.6",
|
||||
"version": "0.9.13",
|
||||
"name": "react-native-web-monorepo",
|
||||
"scripts": {
|
||||
"clean": "del ./packages/*/dist",
|
||||
"compile": "npm-run-all clean -p \"compile:* -- {@}\" --",
|
||||
"compile:commonjs": "cd packages/react-native-web && BABEL_ENV=commonjs babel src --out-dir dist/cjs --ignore \"**/__tests__\"",
|
||||
"compile:es": "cd packages/react-native-web && babel src --out-dir dist --ignore \"**/__tests__\"",
|
||||
"compile:commonjs": "cd packages/react-native-web && BABEL_ENV=commonjs babel --root-mode upward src --out-dir dist/cjs --ignore \"**/__tests__\"",
|
||||
"compile:es": "cd packages/react-native-web && babel --root-mode upward src --out-dir dist --ignore \"**/__tests__\"",
|
||||
"benchmarks": "cd packages/benchmarks && yarn build",
|
||||
"benchmarks:release": "cd packages/benchmarks && yarn release",
|
||||
"examples": "cd packages/examples && yarn build",
|
||||
@@ -15,7 +15,7 @@
|
||||
"website:release": "cd packages/website && yarn release",
|
||||
"flow": "flow",
|
||||
"fmt": "prettier --write \"**/*.js\"",
|
||||
"jest": "BABEL_ENV=commonjs jest --config ./scripts/jest/config.js",
|
||||
"jest": "jest --config ./scripts/jest/config.js",
|
||||
"lint": "yarn lint:check --fix",
|
||||
"lint:check": "eslint packages scripts",
|
||||
"precommit": "lint-staged",
|
||||
@@ -25,39 +25,41 @@
|
||||
"test": "yarn flow && yarn lint:check && yarn jest --runInBand"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-cli": "^6.26.0",
|
||||
"babel-core": "^6.26.0",
|
||||
"babel-eslint": "^8.2.3",
|
||||
"babel-loader": "^7.1.2",
|
||||
"babel-plugin-add-module-exports": "^0.2.1",
|
||||
"babel-plugin-transform-class-properties": "^6.24.1",
|
||||
"babel-plugin-transform-object-rest-spread": "^6.26.0",
|
||||
"babel-plugin-transform-react-remove-prop-types": "^0.4.10",
|
||||
"babel-preset-env": "^1.6.1",
|
||||
"babel-preset-flow": "^6.23.0",
|
||||
"babel-preset-react": "^6.24.1",
|
||||
"babel-preset-react-native": "^4.0.0",
|
||||
"@babel/cli": "^7.2.3",
|
||||
"@babel/core": "^7.2.2",
|
||||
"@babel/plugin-proposal-class-properties": "^7.2.3",
|
||||
"@babel/plugin-proposal-object-rest-spread": "^7.2.0",
|
||||
"@babel/preset-env": "^7.2.3",
|
||||
"@babel/preset-flow": "^7.0.0",
|
||||
"@babel/preset-react": "^7.0.0",
|
||||
"babel-eslint": "^10.0.0",
|
||||
"babel-jest": "24.0.0-alpha.9",
|
||||
"babel-loader": "^8.0.0",
|
||||
"babel-plugin-add-module-exports": "^1.0.0",
|
||||
"babel-plugin-transform-react-remove-prop-types": "^0.4.21",
|
||||
"caniuse-api": "^2.0.0",
|
||||
"del-cli": "^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",
|
||||
"eslint-plugin-promise": "^3.7.0",
|
||||
"eslint": "^5.11.1",
|
||||
"eslint-config-prettier": "^3.3.0",
|
||||
"eslint-plugin-promise": "^4.0.1",
|
||||
"eslint-plugin-react": "^7.7.0",
|
||||
"flow-bin": "^0.63.1",
|
||||
"glob": "^7.1.2",
|
||||
"husky": "^0.14.3",
|
||||
"jest": "^22.4.3",
|
||||
"inline-style-prefixer": "^5.0.3",
|
||||
"jest": "24.0.0-alpha.9",
|
||||
"jest-canvas-mock": "^1.0.2",
|
||||
"lint-staged": "^7.1.0",
|
||||
"metro-react-native-babel-preset": "^0.51.0",
|
||||
"npm-run-all": "^4.1.3",
|
||||
"prettier": "^1.12.1",
|
||||
"react": "^16.5.1",
|
||||
"react-art": "^16.5.1",
|
||||
"react-dom": "^16.5.1",
|
||||
"react-test-renderer": "^16.5.1"
|
||||
"react": "^16.7.0",
|
||||
"react-art": "^16.7.0",
|
||||
"react-dom": "^16.7.0",
|
||||
"react-test-renderer": "^16.7.0"
|
||||
},
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
{
|
||||
"name": "babel-plugin-react-native-web",
|
||||
"version": "0.9.6",
|
||||
"version": "0.9.13",
|
||||
"description": "Babel plugin for React Native for Web",
|
||||
"main": "index.js",
|
||||
"devDependencies": {
|
||||
"babel-core": "^6.26.3",
|
||||
"babel-plugin-tester": "^5.0.0"
|
||||
},
|
||||
"author": "Nicolas Gallagher",
|
||||
|
||||
@@ -1,37 +1,35 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "benchmarks",
|
||||
"version": "0.9.6",
|
||||
"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.5.1",
|
||||
"react-dom": "^16.5.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.9.6",
|
||||
"reactxp": "^1.3.0",
|
||||
"styled-components": "^3.3.3",
|
||||
"styled-jsx": "^2.2.7",
|
||||
"styletron-engine-atomic": "^1.0.5",
|
||||
"styletron-react": "^4.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.9.6",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
* The MIT License (MIT)
|
||||
* Copyright (c) 2017 Paul Armstrong
|
||||
* https://github.com/paularmstrong/react-component-benchmark
|
||||
* @flow
|
||||
*/
|
||||
|
||||
/* global $Values */
|
||||
/**
|
||||
* @flow
|
||||
*/
|
||||
/* eslint-disable react/prop-types */
|
||||
|
||||
import * as Timing from './timing';
|
||||
import React, { Component } from 'react';
|
||||
import { getMean, getMedian, getStdDev } from './math';
|
||||
|
||||
45
packages/benchmarks/src/cases/TextTree.js
Normal file
45
packages/benchmarks/src/cases/TextTree.js
Normal file
@@ -0,0 +1,45 @@
|
||||
import { BenchmarkType } from '../app/Benchmark';
|
||||
import { number, object } from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
|
||||
class TextTree extends Component {
|
||||
static displayName = 'TextTree';
|
||||
|
||||
static benchmarkType = BenchmarkType.MOUNT;
|
||||
|
||||
static propTypes = {
|
||||
breadth: number.isRequired,
|
||||
components: object,
|
||||
depth: number.isRequired,
|
||||
id: number.isRequired,
|
||||
wrap: number.isRequired
|
||||
};
|
||||
|
||||
render() {
|
||||
const { breadth, components, depth, id, wrap } = this.props;
|
||||
const { TextBox } = components;
|
||||
|
||||
let result = (
|
||||
<TextBox children={'TextBox ${id % 3}'} color={id % 3} outer>
|
||||
{depth === 0 && <TextBox children={'Depth 0'} color={(id % 3) + 3} />}
|
||||
{depth !== 0 &&
|
||||
Array.from({ length: breadth }).map((el, i) => (
|
||||
<TextTree
|
||||
breadth={breadth}
|
||||
components={components}
|
||||
depth={depth - 1}
|
||||
id={i}
|
||||
key={i}
|
||||
wrap={wrap}
|
||||
/>
|
||||
))}
|
||||
</TextBox>
|
||||
);
|
||||
for (let i = 0; i < wrap; i++) {
|
||||
result = <TextBox>{result}</TextBox>;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
export default TextTree;
|
||||
@@ -8,6 +8,7 @@ type ComponentsType = {
|
||||
Box: Component,
|
||||
Dot: Component,
|
||||
Provider: Component,
|
||||
TextBox: Component,
|
||||
View: Component
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
};
|
||||
39
packages/benchmarks/src/implementations/react-native-web/TextBox.js
vendored
Normal file
39
packages/benchmarks/src/implementations/react-native-web/TextBox.js
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
import React from 'react';
|
||||
import { StyleSheet, Text } from 'react-native';
|
||||
|
||||
const TextBox = ({ color, outer = false, ...other }) => (
|
||||
<Text {...other} style={[styles.root, styles[`color${color}`], outer && styles.outer]} />
|
||||
);
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
root: {
|
||||
color: 'white'
|
||||
},
|
||||
outer: {
|
||||
fontStyle: 'italic'
|
||||
},
|
||||
row: {
|
||||
flexDirection: 'row'
|
||||
},
|
||||
color0: {
|
||||
color: '#14171A'
|
||||
},
|
||||
color1: {
|
||||
color: '#AAB8C2'
|
||||
},
|
||||
color2: {
|
||||
color: '#E6ECF0'
|
||||
},
|
||||
color3: {
|
||||
color: '#FFAD1F'
|
||||
},
|
||||
color4: {
|
||||
color: '#F45D22'
|
||||
},
|
||||
color5: {
|
||||
color: '#E0245E'
|
||||
}
|
||||
});
|
||||
|
||||
export default TextBox;
|
||||
@@ -1,11 +1,13 @@
|
||||
import Box from './Box';
|
||||
import Dot from './Dot';
|
||||
import Provider from './Provider';
|
||||
import TextBox from './TextBox';
|
||||
import { View } from 'react-native';
|
||||
|
||||
export default {
|
||||
Box,
|
||||
Dot,
|
||||
Provider,
|
||||
TextBox,
|
||||
View
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import App from './app/App';
|
||||
import impl from './impl';
|
||||
import TextTree from './cases/TextTree';
|
||||
import Tree from './cases/Tree';
|
||||
import SierpinskiTriangle from './cases/SierpinskiTriangle';
|
||||
|
||||
@@ -50,6 +51,13 @@ const tests = {
|
||||
},
|
||||
Provider: components.Provider,
|
||||
sampleCount: 100
|
||||
})),
|
||||
'Mount text tree': createTestBlock(components => ({
|
||||
benchmarkType: 'mount',
|
||||
Component: TextTree,
|
||||
getComponentProps: () => ({ breadth: 6, components, depth: 3, id: 0, wrap: 2 }),
|
||||
Provider: components.Provider,
|
||||
sampleCount: 50
|
||||
}))
|
||||
};
|
||||
|
||||
|
||||
@@ -12,6 +12,9 @@ module.exports = {
|
||||
path: path.resolve(appDirectory, 'dist'),
|
||||
filename: 'bundle.js'
|
||||
},
|
||||
optimization: {
|
||||
minimize: process.env.NODE_ENV === 'production'
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
@@ -31,7 +34,7 @@ module.exports = {
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
cacheDirectory: false,
|
||||
presets: babelPreset,
|
||||
presets: [babelPreset],
|
||||
plugins: ['styled-jsx/babel']
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "react-native-examples",
|
||||
"version": "0.9.6",
|
||||
"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",
|
||||
"@babel/runtime": "^7.2.0",
|
||||
"react": "^16.5.1",
|
||||
"react-dom": "^16.5.1",
|
||||
"react-native-web": "0.9.6"
|
||||
"react-native-web": "0.9.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-plugin-react-native-web": "0.9.6",
|
||||
"@babel/plugin-transform-runtime": "^7.2.0",
|
||||
"babel-plugin-react-native-web": "0.9.13",
|
||||
"babel-plugin-transform-runtime": "^6.23.0",
|
||||
"file-loader": "^1.1.11",
|
||||
"webpack": "^4.8.1",
|
||||
"webpack-bundle-analyzer": "^2.11.1",
|
||||
"webpack-cli": "^2.1.3"
|
||||
"webpack": "^4.28.3",
|
||||
"webpack-bundle-analyzer": "^3.0.3",
|
||||
"webpack-cli": "^3.1.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,10 +34,10 @@ module.exports = {
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
cacheDirectory: false,
|
||||
presets: ['react-native'],
|
||||
presets: ['module:metro-react-native-babel-preset'],
|
||||
plugins: [
|
||||
// needed to support async/await
|
||||
'transform-runtime'
|
||||
'@babel/plugin-transform-runtime'
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-native-web",
|
||||
"version": "0.9.6",
|
||||
"version": "0.9.13",
|
||||
"description": "React Native for Web",
|
||||
"module": "dist/index.js",
|
||||
"main": "dist/cjs/index.js",
|
||||
@@ -15,14 +15,14 @@
|
||||
"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.5.1",
|
||||
|
||||
@@ -2,56 +2,32 @@
|
||||
|
||||
exports[`AppRegistry getApplication "getStyleElement" produces styles that are a function of rendering "element": Additional CSS for styled app 1`] = `
|
||||
"
|
||||
.rn-backgroundColor-aot4c7{background-color:rgba(128,0,128,1.00)}
|
||||
.rn-borderTopWidth-10pzpfo{border-top-width:1234px}
|
||||
.rn-borderRightWidth-1y24uml{border-right-width:1234px}
|
||||
.rn-borderBottomWidth-98wxn4{border-bottom-width:1234px}
|
||||
.rn-borderLeftWidth-150mub4{border-left-width:1234px}"
|
||||
.r-backgroundColor-aot4c7{background-color:rgba(128,0,128,1.00)}
|
||||
.r-borderTopWidth-10pzpfo{border-top-width:1234px}
|
||||
.r-borderRightWidth-1y24uml{border-right-width:1234px}
|
||||
.r-borderBottomWidth-98wxn4{border-bottom-width:1234px}
|
||||
.r-borderLeftWidth-150mub4{border-left-width:1234px}"
|
||||
`;
|
||||
|
||||
exports[`AppRegistry getApplication "getStyleElement" produces styles that are a function of rendering "element": CSS for an unstyled app 1`] = `
|
||||
"@media all{
|
||||
":focus:not([data-r-focusvisible-x92cna]){outline: none;}
|
||||
@media all{
|
||||
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0);}
|
||||
body{margin:0;}
|
||||
button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0;}
|
||||
input::-webkit-inner-spin-button,input::-webkit-outer-spin-button,input::-webkit-search-cancel-button,input::-webkit-search-decoration,input::-webkit-search-results-button,input::-webkit-search-results-decoration{display:none;}
|
||||
.r-reset{background-color:transparent;color:inherit;font:inherit;list-style:none;margin:0;text-align:inherit;text-decoration:none;}
|
||||
.r-pointer{cursor:pointer;}
|
||||
}
|
||||
.rn-pointerEvents-12vffkv > *{pointer-events:auto}
|
||||
.rn-pointerEvents-12vffkv{pointer-events:none !important}
|
||||
.rn-alignItems-1oszu61{-ms-flex-align:stretch;-webkit-align-items:stretch;-webkit-box-align:stretch;align-items:stretch}
|
||||
.rn-borderTopStyle-1efd50x{border-top-style:solid}
|
||||
.rn-borderRightStyle-14skgim{border-right-style:solid}
|
||||
.rn-borderBottomStyle-rull8r{border-bottom-style:solid}
|
||||
.rn-borderLeftStyle-mm0ijv{border-left-style:solid}
|
||||
.rn-borderTopWidth-13yce4e{border-top-width:0px}
|
||||
.rn-borderRightWidth-fnigne{border-right-width:0px}
|
||||
.rn-borderBottomWidth-ndvcnb{border-bottom-width:0px}
|
||||
.rn-borderLeftWidth-gxnn5r{border-left-width:0px}
|
||||
.rn-boxSizing-deolkf{box-sizing:border-box}
|
||||
.rn-display-6koalj{display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex}
|
||||
.rn-flexShrink-1qe8dj5{-ms-flex-negative:0;-webkit-flex-shrink:0;flex-shrink:0}
|
||||
.rn-flexBasis-1mlwlqe{-ms-flex-preferred-size:auto;-webkit-flex-basis:auto;flex-basis:auto}
|
||||
.rn-flexDirection-eqz5dr{-ms-flex-direction:column;-webkit-box-direction:normal;-webkit-box-orient:vertical;-webkit-flex-direction:column;flex-direction:column}
|
||||
.rn-marginTop-1mnahxq{margin-top:0px}
|
||||
.rn-marginRight-61z16t{margin-right:0px}
|
||||
.rn-marginBottom-p1pxzi{margin-bottom:0px}
|
||||
.rn-marginLeft-11wrixw{margin-left:0px}
|
||||
.rn-minHeight-ifefl9{min-height:0px}
|
||||
.rn-minWidth-bcqeeo{min-width:0px}
|
||||
.rn-paddingTop-wk8lta{padding-top:0px}
|
||||
.rn-paddingRight-9aemit{padding-right:0px}
|
||||
.rn-paddingBottom-1mdbw0j{padding-bottom:0px}
|
||||
.rn-paddingLeft-gy4na3{padding-left:0px}
|
||||
.rn-position-bnwqim{position:relative}
|
||||
.rn-zIndex-1lgpqti{z-index:0}
|
||||
.rn-flexGrow-16y2uox{-ms-flex-positive:1;-webkit-box-flex:1;-webkit-flex-grow:1;flex-grow:1}
|
||||
.rn-flexShrink-1wbh5a2{-ms-flex-negative:1;-webkit-flex-shrink:1;flex-shrink:1}
|
||||
.rn-flexBasis-1ro0kt6{-ms-flex-preferred-size:0%;-webkit-flex-basis:0%;flex-basis:0%}"
|
||||
.r-pointerEvents-12vffkv > *{pointer-events:auto}
|
||||
.r-pointerEvents-12vffkv{pointer-events:none !important}
|
||||
.r-flexGrow-16y2uox{-ms-flex-positive:1;-webkit-box-flex:1;-webkit-flex-grow:1;flex-grow:1}
|
||||
.r-flexShrink-1wbh5a2{-ms-flex-negative:1;-webkit-flex-shrink:1;flex-shrink:1}
|
||||
.r-flexBasis-1ro0kt6{-ms-flex-preferred-size:0%;-webkit-flex-basis:0%;flex-basis:0%}"
|
||||
`;
|
||||
|
||||
exports[`AppRegistry getApplication returns "element" and "getStyleElement" 1`] = `
|
||||
<AppContainer
|
||||
WrapperComponent={undefined}
|
||||
rootTag={Object {}}
|
||||
>
|
||||
<RootComponent />
|
||||
@@ -59,10 +35,20 @@ exports[`AppRegistry getApplication returns "element" and "getStyleElement" 1`]
|
||||
`;
|
||||
|
||||
exports[`AppRegistry getApplication returns "element" and "getStyleElement" 2`] = `
|
||||
"<style id=\\"react-native-stylesheet\\">@media all{
|
||||
"<style id=\\"react-native-stylesheet\\">:focus:not([data-r-focusvisible-x92cna]){outline: none;}
|
||||
@media all{
|
||||
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0);}
|
||||
body{margin:0;}
|
||||
button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0;}
|
||||
input::-webkit-inner-spin-button,input::-webkit-outer-spin-button,input::-webkit-search-cancel-button,input::-webkit-search-decoration,input::-webkit-search-results-button,input::-webkit-search-results-decoration{display:none;}
|
||||
}</style>"
|
||||
.r-reset{background-color:transparent;color:inherit;font:inherit;list-style:none;margin:0;text-align:inherit;text-decoration:none;}
|
||||
.r-pointer{cursor:pointer;}
|
||||
}
|
||||
.r-ui-textinput-14mbsun{-moz-appearance:textfield;-webkit-appearance:none;background-color:transparent;border-radius:0px;border:0 solid black;box-sizing:border-box;font-family:14px system-ui, -apple-system, BlinkMacSystemFont, \\"Segoe UI\\", Roboto, Ubuntu, \\"Helvetica Neue\\", sans-serif;padding:0px;resize:none}
|
||||
.r-ui-textSingleLine-1xjj19i{max-width:100%;overflow:hidden !important;text-overflow:ellipsis !important;white-space:nowrap !important}
|
||||
.r-ui-textHasAncestor-z2plr{color:inherit;font:inherit;text-decoration:inherit;white-space:inherit}
|
||||
.r-ui-textIsRoot-gw3a6r{color:black;font:normal 14px system-ui, -apple-system, BlinkMacSystemFont, \\"Segoe UI\\", Roboto, Ubuntu, \\"Helvetica Neue\\", sans-serif;text-decoration:none;white-space:pre-wrap}
|
||||
.r-ui-text-1ntzlq4{background-color:transparent;border-width:0px;box-sizing:border-box;display:inline;margin:0px;padding:0px;text-align:inherit;word-wrap:break-word}
|
||||
.r-ui-hitSlop-14nrb4u{bottom:0px;left:0px;position:absolute;right:0px;top:0px;z-index:-1}
|
||||
.r-ui-view-15pvbv0{-ms-flex-align:stretch;-ms-flex-direction:column;-ms-flex-negative:0;-ms-flex-preferred-size:auto;-webkit-align-items:stretch;-webkit-box-align:stretch;-webkit-box-direction:normal;-webkit-box-orient:vertical;-webkit-flex-basis:auto;-webkit-flex-direction:column;-webkit-flex-shrink:0;align-items:stretch;background-color:transparent;border:0 solid black;box-sizing:border-box;color:inherit;display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;flex-basis:auto;flex-direction:column;flex-shrink:0;font:inherit;list-style:none;margin:0px;min-height:0px;min-width:0px;padding:0px;position:relative;text-align:inherit;text-decoration:none;z-index:0}</style>"
|
||||
`;
|
||||
|
||||
@@ -90,7 +90,7 @@ export default class AppRegistry {
|
||||
}
|
||||
|
||||
static runApplication(appKey: string, appParameters: Object): void {
|
||||
const isDevelopment = process.env.NODE_ENV !== 'production';
|
||||
const isDevelopment = process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test';
|
||||
if (isDevelopment) {
|
||||
const params = { ...appParameters };
|
||||
params.rootTag = `#${params.rootTag.id}`;
|
||||
|
||||
@@ -10,7 +10,7 @@ exports[`components/Picker prop "children" items 1`] = `
|
||||
|
||||
exports[`components/Picker prop "children" renders items 1`] = `
|
||||
<select
|
||||
className="rn-fontFamily-14xgk7a rn-fontSize-7cikom rn-marginTop-1mnahxq rn-marginRight-61z16t rn-marginBottom-p1pxzi rn-marginLeft-11wrixw"
|
||||
className="r-fontFamily-14xgk7a r-fontSize-7cikom r-marginTop-1mnahxq r-marginRight-61z16t r-marginBottom-p1pxzi r-marginLeft-11wrixw"
|
||||
data-focusable={true}
|
||||
onChange={[Function]}
|
||||
>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
@@ -294,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);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,9 +15,9 @@ import WebStyleSheet from './WebStyleSheet';
|
||||
const emptyObject = {};
|
||||
const STYLE_ELEMENT_ID = 'react-native-stylesheet';
|
||||
|
||||
const createClassName = (prop, value) => {
|
||||
const hashed = hash(prop + normalizeValue(value));
|
||||
return process.env.NODE_ENV !== 'production' ? `rn-${prop}-${hashed}` : `rn-${hashed}`;
|
||||
const createClassName = (name, value) => {
|
||||
const hashed = hash(name + normalizeValue(value));
|
||||
return process.env.NODE_ENV !== 'production' ? `r-${name}-${hashed}` : `r-${hashed}`;
|
||||
};
|
||||
|
||||
const normalizeValue = value => (typeof value === 'object' ? JSON.stringify(value) : value);
|
||||
@@ -69,6 +69,14 @@ export default class StyleSheetManager {
|
||||
return className;
|
||||
}
|
||||
|
||||
injectRule(name, body: string): void {
|
||||
const className = createClassName(`ui-${name}`, body);
|
||||
const rule = `.${className}{${body}}`;
|
||||
// insert after the reset + modality but before atomic css
|
||||
this._sheet.insertRuleOnce(rule, 2);
|
||||
return className;
|
||||
}
|
||||
|
||||
_addToCache(className, prop, value) {
|
||||
const cache = this._cache;
|
||||
if (!cache.byProp[prop]) {
|
||||
|
||||
@@ -103,10 +103,7 @@ StyleSheetValidation.addValidStylePropTypes({
|
||||
objectFit: oneOf(['fill', 'contain', 'cover', 'none', 'scale-down']),
|
||||
objectPosition: string,
|
||||
pointerEvents: string,
|
||||
tableLayout: string,
|
||||
/* @private */
|
||||
MozAppearance: string,
|
||||
WebkitAppearance: string
|
||||
tableLayout: string
|
||||
});
|
||||
|
||||
export default StyleSheetValidation;
|
||||
|
||||
@@ -30,12 +30,13 @@ export default class WebStyleSheet {
|
||||
}
|
||||
|
||||
if (domStyleElement) {
|
||||
modality(domStyleElement);
|
||||
// $FlowFixMe
|
||||
this._sheet = domStyleElement.sheet;
|
||||
this._textContent = domStyleElement.textContent;
|
||||
}
|
||||
}
|
||||
|
||||
modality((rule) => this.insertRuleOnce(rule, 0));
|
||||
}
|
||||
|
||||
containsRule(rule: string): boolean {
|
||||
@@ -49,13 +50,25 @@ export default class WebStyleSheet {
|
||||
insertRuleOnce(rule: string, position: ?number) {
|
||||
// Reduce chance of duplicate rules
|
||||
if (!this.containsRule(rule)) {
|
||||
this._cssRules.push(rule);
|
||||
if (position != null) {
|
||||
this._cssRules.splice(position, 0, rule);
|
||||
} else {
|
||||
this._cssRules.push(rule);
|
||||
}
|
||||
|
||||
// Check whether a rule was part of any prerendered styles (textContent
|
||||
// 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.)`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,102 +3,102 @@
|
||||
exports[`StyleSheet/ReactNativeStyleResolver resolve resolves inline-style pointerEvents to classname 1`] = `
|
||||
Object {
|
||||
"classList": Array [
|
||||
"rn-pointerEvents-12vffkv",
|
||||
"r-pointerEvents-12vffkv",
|
||||
],
|
||||
"className": "rn-pointerEvents-12vffkv",
|
||||
"className": "r-pointerEvents-12vffkv",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`StyleSheet/ReactNativeStyleResolver resolve with register before RTL, resolves to correct className 1`] = `
|
||||
Object {
|
||||
"classList": Array [
|
||||
"rn-marginRight-zso239",
|
||||
"rn-right-1bnbe1j",
|
||||
"rn-textAlign-1ff274t",
|
||||
"r-marginRight-zso239",
|
||||
"r-right-1bnbe1j",
|
||||
"r-textAlign-1ff274t",
|
||||
],
|
||||
"className": "rn-marginRight-zso239 rn-right-1bnbe1j rn-textAlign-1ff274t",
|
||||
"className": "r-marginRight-zso239 r-right-1bnbe1j r-textAlign-1ff274t",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`StyleSheet/ReactNativeStyleResolver resolve with register before RTL, resolves to correct className 2`] = `
|
||||
Object {
|
||||
"classList": Array [
|
||||
"rn-left-2s0hu9",
|
||||
"rn-marginLeft-1n0xq6e",
|
||||
"rn-textAlign-fdjqy7",
|
||||
"r-left-2s0hu9",
|
||||
"r-marginLeft-1n0xq6e",
|
||||
"r-textAlign-fdjqy7",
|
||||
],
|
||||
"className": "rn-left-2s0hu9 rn-marginLeft-1n0xq6e rn-textAlign-fdjqy7",
|
||||
"className": "r-left-2s0hu9 r-marginLeft-1n0xq6e r-textAlign-fdjqy7",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`StyleSheet/ReactNativeStyleResolver resolve with register, resolves to className 1`] = `
|
||||
Object {
|
||||
"classList": Array [
|
||||
"rn-borderTopColor-3vzq9n",
|
||||
"rn-borderRightColor-ycnuvz",
|
||||
"rn-borderBottomColor-wqks8h",
|
||||
"rn-borderLeftColor-3se2kx",
|
||||
"rn-borderTopWidth-13yce4e",
|
||||
"rn-borderRightWidth-fnigne",
|
||||
"rn-borderBottomWidth-ndvcnb",
|
||||
"rn-borderLeftWidth-gxnn5r",
|
||||
"rn-left-1tsx3h3",
|
||||
"rn-opacity-icoktb",
|
||||
"rn-position-u8s1d",
|
||||
"rn-width-b8lwoo",
|
||||
"r-borderTopColor-3vzq9n",
|
||||
"r-borderRightColor-ycnuvz",
|
||||
"r-borderBottomColor-wqks8h",
|
||||
"r-borderLeftColor-3se2kx",
|
||||
"r-borderTopWidth-13yce4e",
|
||||
"r-borderRightWidth-fnigne",
|
||||
"r-borderBottomWidth-ndvcnb",
|
||||
"r-borderLeftWidth-gxnn5r",
|
||||
"r-left-1tsx3h3",
|
||||
"r-opacity-icoktb",
|
||||
"r-position-u8s1d",
|
||||
"r-width-b8lwoo",
|
||||
],
|
||||
"className": "rn-borderTopColor-3vzq9n rn-borderRightColor-ycnuvz rn-borderBottomColor-wqks8h rn-borderLeftColor-3se2kx rn-borderTopWidth-13yce4e rn-borderRightWidth-fnigne rn-borderBottomWidth-ndvcnb rn-borderLeftWidth-gxnn5r rn-left-1tsx3h3 rn-opacity-icoktb rn-position-u8s1d rn-width-b8lwoo",
|
||||
"className": "r-borderTopColor-3vzq9n r-borderRightColor-ycnuvz r-borderBottomColor-wqks8h r-borderLeftColor-3se2kx r-borderTopWidth-13yce4e r-borderRightWidth-fnigne r-borderBottomWidth-ndvcnb r-borderLeftWidth-gxnn5r r-left-1tsx3h3 r-opacity-icoktb r-position-u8s1d r-width-b8lwoo",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`StyleSheet/ReactNativeStyleResolver resolve with register, resolves to className 2`] = `
|
||||
Object {
|
||||
"classList": Array [
|
||||
"rn-borderTopColor-3vzq9n",
|
||||
"rn-borderRightColor-ycnuvz",
|
||||
"rn-borderBottomColor-wqks8h",
|
||||
"rn-borderLeftColor-3se2kx",
|
||||
"rn-borderTopWidth-13yce4e",
|
||||
"rn-borderRightWidth-fnigne",
|
||||
"rn-borderBottomWidth-ndvcnb",
|
||||
"rn-borderLeftWidth-gxnn5r",
|
||||
"rn-left-1tsx3h3",
|
||||
"rn-opacity-icoktb",
|
||||
"rn-position-u8s1d",
|
||||
"rn-width-l0gwng",
|
||||
"r-borderTopColor-3vzq9n",
|
||||
"r-borderRightColor-ycnuvz",
|
||||
"r-borderBottomColor-wqks8h",
|
||||
"r-borderLeftColor-3se2kx",
|
||||
"r-borderTopWidth-13yce4e",
|
||||
"r-borderRightWidth-fnigne",
|
||||
"r-borderBottomWidth-ndvcnb",
|
||||
"r-borderLeftWidth-gxnn5r",
|
||||
"r-left-1tsx3h3",
|
||||
"r-opacity-icoktb",
|
||||
"r-position-u8s1d",
|
||||
"r-width-l0gwng",
|
||||
],
|
||||
"className": "rn-borderTopColor-3vzq9n rn-borderRightColor-ycnuvz rn-borderBottomColor-wqks8h rn-borderLeftColor-3se2kx rn-borderTopWidth-13yce4e rn-borderRightWidth-fnigne rn-borderBottomWidth-ndvcnb rn-borderLeftWidth-gxnn5r rn-left-1tsx3h3 rn-opacity-icoktb rn-position-u8s1d rn-width-l0gwng",
|
||||
"className": "r-borderTopColor-3vzq9n r-borderRightColor-ycnuvz r-borderBottomColor-wqks8h r-borderLeftColor-3se2kx r-borderTopWidth-13yce4e r-borderRightWidth-fnigne r-borderBottomWidth-ndvcnb r-borderLeftWidth-gxnn5r r-left-1tsx3h3 r-opacity-icoktb r-position-u8s1d r-width-l0gwng",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`StyleSheet/ReactNativeStyleResolver resolve with register, resolves to className 3`] = `
|
||||
Object {
|
||||
"classList": Array [
|
||||
"rn-borderTopColor-3vzq9n",
|
||||
"rn-borderRightColor-ycnuvz",
|
||||
"rn-borderBottomColor-wqks8h",
|
||||
"rn-borderLeftColor-3se2kx",
|
||||
"rn-borderTopWidth-13yce4e",
|
||||
"rn-borderRightWidth-fnigne",
|
||||
"rn-borderBottomWidth-ndvcnb",
|
||||
"rn-borderLeftWidth-gxnn5r",
|
||||
"rn-left-1tsx3h3",
|
||||
"rn-opacity-icoktb",
|
||||
"rn-position-u8s1d",
|
||||
"rn-width-b8lwoo",
|
||||
"r-borderTopColor-3vzq9n",
|
||||
"r-borderRightColor-ycnuvz",
|
||||
"r-borderBottomColor-wqks8h",
|
||||
"r-borderLeftColor-3se2kx",
|
||||
"r-borderTopWidth-13yce4e",
|
||||
"r-borderRightWidth-fnigne",
|
||||
"r-borderBottomWidth-ndvcnb",
|
||||
"r-borderLeftWidth-gxnn5r",
|
||||
"r-left-1tsx3h3",
|
||||
"r-opacity-icoktb",
|
||||
"r-position-u8s1d",
|
||||
"r-width-b8lwoo",
|
||||
],
|
||||
"className": "rn-borderTopColor-3vzq9n rn-borderRightColor-ycnuvz rn-borderBottomColor-wqks8h rn-borderLeftColor-3se2kx rn-borderTopWidth-13yce4e rn-borderRightWidth-fnigne rn-borderBottomWidth-ndvcnb rn-borderLeftWidth-gxnn5r rn-left-1tsx3h3 rn-opacity-icoktb rn-position-u8s1d rn-width-b8lwoo",
|
||||
"className": "r-borderTopColor-3vzq9n r-borderRightColor-ycnuvz r-borderBottomColor-wqks8h r-borderLeftColor-3se2kx r-borderTopWidth-13yce4e r-borderRightWidth-fnigne r-borderBottomWidth-ndvcnb r-borderLeftWidth-gxnn5r r-left-1tsx3h3 r-opacity-icoktb r-position-u8s1d r-width-b8lwoo",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`StyleSheet/ReactNativeStyleResolver resolve with register, resolves to mixed 1`] = `
|
||||
Object {
|
||||
"classList": Array [
|
||||
"rn-left-1tsx3h3",
|
||||
"rn-opacity-icoktb",
|
||||
"rn-position-u8s1d",
|
||||
"r-left-1tsx3h3",
|
||||
"r-opacity-icoktb",
|
||||
"r-position-u8s1d",
|
||||
],
|
||||
"className": "rn-left-1tsx3h3 rn-opacity-icoktb rn-position-u8s1d",
|
||||
"className": "r-left-1tsx3h3 r-opacity-icoktb r-position-u8s1d",
|
||||
"style": Object {
|
||||
"borderBottomColor": "rgba(255,0,0,1.00)",
|
||||
"borderBottomWidth": "0px",
|
||||
@@ -116,12 +116,12 @@ Object {
|
||||
exports[`StyleSheet/ReactNativeStyleResolver resolve with register, resolves to mixed 2`] = `
|
||||
Object {
|
||||
"classList": Array [
|
||||
"rn-left-1tsx3h3",
|
||||
"rn-opacity-icoktb",
|
||||
"rn-position-u8s1d",
|
||||
"rn-width-l0gwng",
|
||||
"r-left-1tsx3h3",
|
||||
"r-opacity-icoktb",
|
||||
"r-position-u8s1d",
|
||||
"r-width-l0gwng",
|
||||
],
|
||||
"className": "rn-left-1tsx3h3 rn-opacity-icoktb rn-position-u8s1d rn-width-l0gwng",
|
||||
"className": "r-left-1tsx3h3 r-opacity-icoktb r-position-u8s1d r-width-l0gwng",
|
||||
"style": Object {
|
||||
"borderBottomColor": "rgba(255,0,0,1.00)",
|
||||
"borderBottomWidth": "0px",
|
||||
@@ -138,11 +138,11 @@ Object {
|
||||
exports[`StyleSheet/ReactNativeStyleResolver resolve with register, resolves to mixed 3`] = `
|
||||
Object {
|
||||
"classList": Array [
|
||||
"rn-left-1tsx3h3",
|
||||
"rn-opacity-icoktb",
|
||||
"rn-position-u8s1d",
|
||||
"r-left-1tsx3h3",
|
||||
"r-opacity-icoktb",
|
||||
"r-position-u8s1d",
|
||||
],
|
||||
"className": "rn-left-1tsx3h3 rn-opacity-icoktb rn-position-u8s1d",
|
||||
"className": "r-left-1tsx3h3 r-opacity-icoktb r-position-u8s1d",
|
||||
"style": Object {
|
||||
"borderBottomColor": "rgba(255,0,0,1.00)",
|
||||
"borderBottomWidth": "0px",
|
||||
@@ -222,7 +222,7 @@ Object {
|
||||
|
||||
exports[`StyleSheet/ReactNativeStyleResolver resolveWithNode next class names have priority over current inline styles 1`] = `
|
||||
Object {
|
||||
"className": "rn-opacity-6dt33c",
|
||||
"className": "r-opacity-6dt33c",
|
||||
"style": Object {
|
||||
"opacity": "",
|
||||
},
|
||||
@@ -270,7 +270,7 @@ Object {
|
||||
|
||||
exports[`StyleSheet/ReactNativeStyleResolver resolveWithNode when isRTL=true, resolves to flipped classNames 1`] = `
|
||||
Object {
|
||||
"className": "rn-left-1u10d71 rn-marginRight-zso239",
|
||||
"className": "r-left-1u10d71 r-marginRight-zso239",
|
||||
"style": Object {
|
||||
"marginRight": "",
|
||||
"right": "5px",
|
||||
|
||||
@@ -5,12 +5,15 @@ exports[`StyleSheet/StyleSheetManager getClassName 1`] = `undefined`;
|
||||
exports[`StyleSheet/StyleSheetManager getStyleSheet 1`] = `
|
||||
Object {
|
||||
"id": "react-native-stylesheet",
|
||||
"textContent": "@media all{
|
||||
"textContent": ":focus:not([data-r-focusvisible-x92cna]){outline: none;}
|
||||
@media all{
|
||||
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0);}
|
||||
body{margin:0;}
|
||||
button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0;}
|
||||
input::-webkit-inner-spin-button,input::-webkit-outer-spin-button,input::-webkit-search-cancel-button,input::-webkit-search-decoration,input::-webkit-search-results-button,input::-webkit-search-results-decoration{display:none;}
|
||||
.r-reset{background-color:transparent;color:inherit;font:inherit;list-style:none;margin:0;text-align:inherit;text-decoration:none;}
|
||||
.r-pointer{cursor:pointer;}
|
||||
}
|
||||
.rn---test-property-ax3bxi{--test-property:test-value}",
|
||||
.r---test-property-ax3bxi{--test-property:test-value}",
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
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}}}",
|
||||
".test{-webkit-animation-name:rn-anim-2k74q5,rn-anim-zc91cv;animation-name:rn-anim-2k74q5,rn-anim-zc91cv}",
|
||||
"@-webkit-keyframes r-anim-2k74q5{0%{top:0px}50%{top:5px}100%{top:10px}}",
|
||||
"@keyframes r-anim-2k74q5{0%{top:0px}50%{top:5px}100%{top:10px}}",
|
||||
"@-webkit-keyframes r-anim-zc91cv{from{left:0px}to{left:10px}}",
|
||||
"@keyframes r-anim-zc91cv{from{left:0px}to{left:10px}}",
|
||||
".test{-webkit-animation-name:r-anim-2k74q5,r-anim-zc91cv;animation-name:r-anim-2k74q5,r-anim-zc91cv}",
|
||||
]
|
||||
`;
|
||||
|
||||
|
||||
@@ -39,44 +39,17 @@ describe('StyleSheet/createReactDOMStyle', () => {
|
||||
expect(createReactDOMStyle(style)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe('borderWidth styles', () => {
|
||||
test('defaults to 0 when "null"', () => {
|
||||
expect(createReactDOMStyle({ borderWidth: null })).toEqual({
|
||||
borderTopWidth: '0px',
|
||||
borderRightWidth: '0px',
|
||||
borderBottomWidth: '0px',
|
||||
borderLeftWidth: '0px'
|
||||
});
|
||||
expect(createReactDOMStyle({ borderWidth: 2, borderRightWidth: null })).toEqual({
|
||||
borderTopWidth: '2px',
|
||||
borderRightWidth: '0px',
|
||||
borderBottomWidth: '2px',
|
||||
borderLeftWidth: '2px'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('flexbox styles', () => {
|
||||
test('flex defaults', () => {
|
||||
expect(createReactDOMStyle({ display: 'flex' })).toEqual({
|
||||
display: 'flex',
|
||||
flexShrink: 0,
|
||||
flexBasis: 'auto'
|
||||
});
|
||||
});
|
||||
|
||||
test('flex: -1', () => {
|
||||
expect(createReactDOMStyle({ display: 'flex', flex: -1 })).toEqual({
|
||||
display: 'flex',
|
||||
expect(createReactDOMStyle({ flex: -1 })).toEqual({
|
||||
flexBasis: 'auto',
|
||||
flexGrow: 0,
|
||||
flexShrink: 1,
|
||||
flexBasis: 'auto'
|
||||
flexShrink: 1
|
||||
});
|
||||
});
|
||||
|
||||
test('flex: 0', () => {
|
||||
expect(createReactDOMStyle({ display: 'flex', flex: 0 })).toEqual({
|
||||
display: 'flex',
|
||||
expect(createReactDOMStyle({ flex: 0 })).toEqual({
|
||||
flexGrow: 0,
|
||||
flexShrink: 0,
|
||||
flexBasis: '0%'
|
||||
@@ -84,8 +57,7 @@ describe('StyleSheet/createReactDOMStyle', () => {
|
||||
});
|
||||
|
||||
test('flex: 1', () => {
|
||||
expect(createReactDOMStyle({ display: 'flex', flex: 1 })).toEqual({
|
||||
display: 'flex',
|
||||
expect(createReactDOMStyle({ flex: 1 })).toEqual({
|
||||
flexGrow: 1,
|
||||
flexShrink: 1,
|
||||
flexBasis: '0%'
|
||||
@@ -93,8 +65,7 @@ describe('StyleSheet/createReactDOMStyle', () => {
|
||||
});
|
||||
|
||||
test('flex: 10', () => {
|
||||
expect(createReactDOMStyle({ display: 'flex', flex: 10 })).toEqual({
|
||||
display: 'flex',
|
||||
expect(createReactDOMStyle({ flex: 10 })).toEqual({
|
||||
flexGrow: 10,
|
||||
flexShrink: 1,
|
||||
flexBasis: '0%'
|
||||
@@ -103,15 +74,12 @@ describe('StyleSheet/createReactDOMStyle', () => {
|
||||
|
||||
test('flexBasis overrides', () => {
|
||||
// is flex-basis applied?
|
||||
expect(createReactDOMStyle({ display: 'flex', flexBasis: '25%' })).toEqual({
|
||||
display: 'flex',
|
||||
flexShrink: 0,
|
||||
expect(createReactDOMStyle({ flexBasis: '25%' })).toEqual({
|
||||
flexBasis: '25%'
|
||||
});
|
||||
|
||||
// can flex-basis override the 'flex' expansion?
|
||||
expect(createReactDOMStyle({ display: 'flex', flex: 1, flexBasis: '25%' })).toEqual({
|
||||
display: 'flex',
|
||||
expect(createReactDOMStyle({ flex: 1, flexBasis: '25%' })).toEqual({
|
||||
flexGrow: 1,
|
||||
flexShrink: 1,
|
||||
flexBasis: '25%'
|
||||
@@ -120,15 +88,12 @@ describe('StyleSheet/createReactDOMStyle', () => {
|
||||
|
||||
test('flexShrink overrides', () => {
|
||||
// is flex-shrink applied?
|
||||
expect(createReactDOMStyle({ display: 'flex', flexShrink: 1 })).toEqual({
|
||||
display: 'flex',
|
||||
flexShrink: 1,
|
||||
flexBasis: 'auto'
|
||||
expect(createReactDOMStyle({ flexShrink: 1 })).toEqual({
|
||||
flexShrink: 1
|
||||
});
|
||||
|
||||
// can flex-shrink override the 'flex' expansion?
|
||||
expect(createReactDOMStyle({ display: 'flex', flex: 1, flexShrink: 2 })).toEqual({
|
||||
display: 'flex',
|
||||
expect(createReactDOMStyle({ flex: 1, flexShrink: 2 })).toEqual({
|
||||
flexGrow: 1,
|
||||
flexShrink: 2,
|
||||
flexBasis: '0%'
|
||||
|
||||
13
packages/react-native-web/src/exports/StyleSheet/constants.js
vendored
Normal file
13
packages/react-native-web/src/exports/StyleSheet/constants.js
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Copyright (c) 2016-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 strict
|
||||
*/
|
||||
|
||||
export const monospaceFontStack = 'monospace, monospace';
|
||||
|
||||
export const systemFontStack =
|
||||
'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, "Helvetica Neue", sans-serif';
|
||||
@@ -7,7 +7,7 @@ const hashObject = obj => hash(JSON.stringify(obj));
|
||||
|
||||
const createIdentifier = obj => {
|
||||
const hashed = hashObject(obj);
|
||||
return process.env.NODE_ENV !== 'production' ? `rn-anim-${hashed}` : `rn-${hashed}`;
|
||||
return process.env.NODE_ENV !== 'production' ? `r-anim-${hashed}` : `r-${hashed}`;
|
||||
};
|
||||
|
||||
const prefixes = ['-webkit-', ''];
|
||||
@@ -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 };
|
||||
};
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
* @noflow
|
||||
*/
|
||||
|
||||
import { monospaceFontStack, systemFontStack } from './constants';
|
||||
import normalizeColor from '../../modules/normalizeColor';
|
||||
import normalizeValue from './normalizeValue';
|
||||
import resolveShadowValue from './resolveShadowValue';
|
||||
@@ -54,29 +55,6 @@ const colorProps = {
|
||||
color: true
|
||||
};
|
||||
|
||||
const borderWidthProps = {
|
||||
borderWidth: true,
|
||||
borderTopWidth: true,
|
||||
borderRightWidth: true,
|
||||
borderBottomWidth: true,
|
||||
borderLeftWidth: true
|
||||
};
|
||||
|
||||
const monospaceFontStack = 'monospace, monospace';
|
||||
const systemFontStack =
|
||||
'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, "Helvetica Neue", sans-serif';
|
||||
|
||||
const alphaSortProps = propsArray =>
|
||||
propsArray.sort((a, b) => {
|
||||
if (a < b) {
|
||||
return -1;
|
||||
}
|
||||
if (a > b) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
const defaultOffset = { height: 0, width: 0 };
|
||||
|
||||
/**
|
||||
@@ -167,12 +145,6 @@ const createReducer = (style, styleProps) => {
|
||||
return (resolvedStyle, prop) => {
|
||||
let value = normalizeValue(prop, style[prop]);
|
||||
|
||||
// Make sure the default border width is explicitly set to '0' to avoid
|
||||
// falling back to any unwanted user-agent styles.
|
||||
if (borderWidthProps[prop]) {
|
||||
value = value == null ? normalizeValue(null, 0) : value;
|
||||
}
|
||||
|
||||
// Normalize color values
|
||||
if (colorProps[prop]) {
|
||||
value = normalizeColor(value);
|
||||
@@ -203,21 +175,6 @@ const createReducer = (style, styleProps) => {
|
||||
break;
|
||||
}
|
||||
|
||||
case 'display': {
|
||||
resolvedStyle.display = value;
|
||||
// A flex container in React Native has these defaults which should be
|
||||
// set only if there is no otherwise supplied flex style.
|
||||
if (style.display === 'flex' && style.flex == null) {
|
||||
if (style.flexShrink == null) {
|
||||
resolvedStyle.flexShrink = 0;
|
||||
}
|
||||
if (style.flexBasis == null) {
|
||||
resolvedStyle.flexBasis = 'auto';
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// The 'flex' property value in React Native must be a positive integer,
|
||||
// 0, or -1.
|
||||
case 'flex': {
|
||||
@@ -324,7 +281,7 @@ const createReactDOMStyle = style => {
|
||||
return emptyObject;
|
||||
}
|
||||
const styleProps = Object.keys(style);
|
||||
const sortedStyleProps = alphaSortProps(styleProps);
|
||||
const sortedStyleProps = styleProps.sort();
|
||||
const reducer = createReducer(style, styleProps);
|
||||
const resolvedStyle = sortedStyleProps.reduce(reducer, {});
|
||||
return resolvedStyle;
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
*/
|
||||
|
||||
import hyphenateStyleName from 'hyphenate-style-name';
|
||||
import mapKeyValue from '../../modules/mapKeyValue';
|
||||
import normalizeValue from './normalizeValue';
|
||||
import prefixStyles from '../../modules/prefixStyles';
|
||||
|
||||
@@ -27,9 +26,15 @@ const createDeclarationString = (prop, val) => {
|
||||
* createRuleBlock({ width: 20, color: 'blue' });
|
||||
* // => 'color:blue;width:20px'
|
||||
*/
|
||||
const createRuleBlock = style =>
|
||||
mapKeyValue(prefixStyles(style), createDeclarationString)
|
||||
.sort()
|
||||
.join(';');
|
||||
const createRuleBlock = style => {
|
||||
const prefixedStyle = prefixStyles(style);
|
||||
return (
|
||||
Object.keys(prefixedStyle)
|
||||
.map(prop => createDeclarationString(prop, prefixedStyle[prop]))
|
||||
// put short-form and vendor prefixed properties first
|
||||
.sort()
|
||||
.join(';')
|
||||
);
|
||||
};
|
||||
|
||||
export default createRuleBlock;
|
||||
|
||||
53
packages/react-native-web/src/exports/StyleSheet/css.js
vendored
Normal file
53
packages/react-native-web/src/exports/StyleSheet/css.js
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Copyright (c) 2016-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.
|
||||
*
|
||||
* @noflow
|
||||
*/
|
||||
|
||||
import createRuleBlock from './createRuleBlock';
|
||||
import styleResolver from './styleResolver';
|
||||
import { systemFontStack } from './constants';
|
||||
|
||||
const fontFamilyProperties = ['font', 'fontFamily'];
|
||||
/**
|
||||
* A simple (and dangerous) CSS system.
|
||||
* The order of CSS rule insertion is not guaranteed.
|
||||
* Avoiding combining 2 or more classes that modify the same property.
|
||||
*/
|
||||
const css = {
|
||||
/**
|
||||
* const classes = css.create({ base: {}, extra: {} })
|
||||
*/
|
||||
create(rules) {
|
||||
const result = {};
|
||||
Object.keys(rules).forEach(key => {
|
||||
const rule = rules[key];
|
||||
fontFamilyProperties.forEach(prop => {
|
||||
const value = rule[prop];
|
||||
if (value && value.indexOf('System') > -1) {
|
||||
rule[prop] = value.replace('System', systemFontStack);
|
||||
}
|
||||
});
|
||||
const cssRule = createRuleBlock(rule);
|
||||
const className = styleResolver.styleSheetManager.injectRule(key, cssRule);
|
||||
result[key] = className;
|
||||
});
|
||||
return result;
|
||||
},
|
||||
/**
|
||||
* css.combine(classes.base, classes.extra)
|
||||
*/
|
||||
combine(...args) {
|
||||
return args.reduce((className, value) => {
|
||||
if (value) {
|
||||
className += className.length > 0 ? ' ' + value : value;
|
||||
}
|
||||
return className;
|
||||
}, '');
|
||||
}
|
||||
};
|
||||
|
||||
export default css;
|
||||
@@ -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;
|
||||
|
||||
@@ -12,13 +12,34 @@ const safeRule = rule => `@media all{\n${rule}\n}`;
|
||||
|
||||
const resets = [
|
||||
// minimal top-level reset
|
||||
'html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0);}',
|
||||
'html{' +
|
||||
'-ms-text-size-adjust:100%;' +
|
||||
'-webkit-text-size-adjust:100%;' +
|
||||
'-webkit-tap-highlight-color:rgba(0,0,0,0);' +
|
||||
'}',
|
||||
'body{margin:0;}',
|
||||
// minimal form pseudo-element reset
|
||||
'button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0;}',
|
||||
'input::-webkit-inner-spin-button,input::-webkit-outer-spin-button,' +
|
||||
'input::-webkit-search-cancel-button,input::-webkit-search-decoration,' +
|
||||
'input::-webkit-search-results-button,input::-webkit-search-results-decoration{display:none;}'
|
||||
'input::-webkit-inner-spin-button,' +
|
||||
'input::-webkit-outer-spin-button,' +
|
||||
'input::-webkit-search-cancel-button,' +
|
||||
'input::-webkit-search-decoration,' +
|
||||
'input::-webkit-search-results-button,' +
|
||||
'input::-webkit-search-results-decoration{' +
|
||||
'display:none;' +
|
||||
'}',
|
||||
// Reset styles for heading, link, and list DOM elements
|
||||
'.r-reset{' +
|
||||
'background-color:transparent;' +
|
||||
'color:inherit;' +
|
||||
'font:inherit;' +
|
||||
'list-style:none;' +
|
||||
'margin:0;' +
|
||||
'text-align:inherit;' +
|
||||
'text-decoration:none;' +
|
||||
'}',
|
||||
// For pressable elements
|
||||
'.r-pointer{cursor:pointer;}'
|
||||
];
|
||||
|
||||
const reset = [safeRule(resets.join('\n'))];
|
||||
|
||||
@@ -11,125 +11,267 @@
|
||||
* 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-r-' + (process.env.NODE_ENV !== 'production' ? 'focusvisible-' : '') + hash('focusvisible');
|
||||
|
||||
const rule = `:focus:not([${focusVisibleAttributeName}]){outline: none;}`;
|
||||
|
||||
const modality = insertRule => {
|
||||
insertRule(rule);
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
@@ -42,12 +42,12 @@ const TextStylePropTypes = {
|
||||
textShadowColor: ColorPropType,
|
||||
textShadowOffset: shape({ width: number, height: number }),
|
||||
textShadowRadius: number,
|
||||
textTransform: oneOf(['capitalize', 'lowercase', 'none', 'uppercase']),
|
||||
writingDirection: oneOf(['auto', 'ltr', 'rtl']),
|
||||
/* @platform web */
|
||||
textIndent: numberOrString,
|
||||
textOverflow: string,
|
||||
textRendering: oneOf(['auto', 'geometricPrecision', 'optimizeLegibility', 'optimizeSpeed']),
|
||||
textTransform: oneOf(['capitalize', 'lowercase', 'none', 'uppercase']),
|
||||
unicodeBidi: oneOf([
|
||||
'normal',
|
||||
'bidi-override',
|
||||
@@ -57,6 +57,7 @@ const TextStylePropTypes = {
|
||||
'plaintext'
|
||||
]),
|
||||
whiteSpace: string,
|
||||
wordBreak: oneOf(['normal', 'break-all', 'break-word', 'keep-all']),
|
||||
wordWrap: string,
|
||||
MozOsxFontSmoothing: string,
|
||||
WebkitFontSmoothing: string
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`components/Text prop "children" 1`] = `
|
||||
<span
|
||||
class="r-ui-text-1ntzlq4 r-ui-textHasAncestor-z2plr"
|
||||
data-testid="child"
|
||||
dir="auto"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`components/Text prop "onPress" 1`] = `
|
||||
<div
|
||||
className="rn-borderTopWidth-13yce4e rn-borderRightWidth-fnigne rn-borderBottomWidth-ndvcnb rn-borderLeftWidth-gxnn5r rn-boxSizing-deolkf rn-color-homxoj rn-cursor-1loqt21 rn-display-1471scf rn-fontFamily-14xgk7a rn-fontSize-1b43r93 rn-fontStyle-o11vmf rn-fontVariant-ebii48 rn-fontWeight-gul640 rn-lineHeight-t9a87b rn-marginTop-1mnahxq rn-marginRight-61z16t rn-marginBottom-p1pxzi rn-marginLeft-11wrixw rn-paddingTop-wk8lta rn-paddingRight-9aemit rn-paddingBottom-1mdbw0j rn-paddingLeft-gy4na3 rn-textDecoration-bauka4 rn-whiteSpace-q42fyq rn-wordWrap-qvutc0"
|
||||
className="r-ui-text-1ntzlq4 r-ui-textIsRoot-gw3a6r r-cursor-1loqt21"
|
||||
data-focusable={true}
|
||||
dir="auto"
|
||||
onClick={[Function]}
|
||||
@@ -13,14 +21,14 @@ exports[`components/Text prop "onPress" 1`] = `
|
||||
|
||||
exports[`components/Text prop "selectable" 1`] = `
|
||||
<div
|
||||
className="rn-borderTopWidth-13yce4e rn-borderRightWidth-fnigne rn-borderBottomWidth-ndvcnb rn-borderLeftWidth-gxnn5r rn-boxSizing-deolkf rn-color-homxoj rn-display-1471scf rn-fontFamily-14xgk7a rn-fontSize-1b43r93 rn-fontStyle-o11vmf rn-fontVariant-ebii48 rn-fontWeight-gul640 rn-lineHeight-t9a87b rn-marginTop-1mnahxq rn-marginRight-61z16t rn-marginBottom-p1pxzi rn-marginLeft-11wrixw rn-paddingTop-wk8lta rn-paddingRight-9aemit rn-paddingBottom-1mdbw0j rn-paddingLeft-gy4na3 rn-textDecoration-bauka4 rn-whiteSpace-q42fyq rn-wordWrap-qvutc0"
|
||||
className="r-ui-text-1ntzlq4 r-ui-textIsRoot-gw3a6r"
|
||||
dir="auto"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`components/Text prop "selectable" 2`] = `
|
||||
<div
|
||||
className="rn-borderTopWidth-13yce4e rn-borderRightWidth-fnigne rn-borderBottomWidth-ndvcnb rn-borderLeftWidth-gxnn5r rn-boxSizing-deolkf rn-color-homxoj rn-display-1471scf rn-fontFamily-14xgk7a rn-fontSize-1b43r93 rn-fontStyle-o11vmf rn-fontVariant-ebii48 rn-fontWeight-gul640 rn-lineHeight-t9a87b rn-marginTop-1mnahxq rn-marginRight-61z16t rn-marginBottom-p1pxzi rn-marginLeft-11wrixw rn-paddingTop-wk8lta rn-paddingRight-9aemit rn-paddingBottom-1mdbw0j rn-paddingLeft-gy4na3 rn-textDecoration-bauka4 rn-userSelect-lrvibr rn-whiteSpace-q42fyq rn-wordWrap-qvutc0"
|
||||
className="r-ui-text-1ntzlq4 r-ui-textIsRoot-gw3a6r r-userSelect-lrvibr"
|
||||
dir="auto"
|
||||
/>
|
||||
`;
|
||||
|
||||
@@ -29,12 +29,12 @@ describe('components/Text', () => {
|
||||
});
|
||||
|
||||
test('prop "children"', () => {
|
||||
const children = <Text testID="1" />;
|
||||
const component = shallow(<Text children={children} />);
|
||||
expect(component.contains(children)).toEqual(true);
|
||||
const children = <Text testID="child" />;
|
||||
const component = render(<Text children={children} />);
|
||||
expect(component.children()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('prop "numberOfLines"');
|
||||
test('prop "numberOfLines"', () => {});
|
||||
|
||||
test('prop "onPress"', () => {
|
||||
const onPress = e => {};
|
||||
|
||||
@@ -13,6 +13,7 @@ import applyNativeMethods from '../../modules/applyNativeMethods';
|
||||
import { bool } from 'prop-types';
|
||||
import { Component } from 'react';
|
||||
import createElement from '../createElement';
|
||||
import css from '../StyleSheet/css';
|
||||
import StyleSheet from '../StyleSheet';
|
||||
import TextPropTypes from './TextPropTypes';
|
||||
|
||||
@@ -57,7 +58,7 @@ class Text extends Component<*> {
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
const { isInAParentText } = this.context;
|
||||
const { isInAParentText: hasTextAncestor } = this.context;
|
||||
|
||||
if (onPress) {
|
||||
otherProps.accessible = true;
|
||||
@@ -65,18 +66,20 @@ class Text extends Component<*> {
|
||||
otherProps.onKeyDown = this._createEnterHandler(onPress);
|
||||
}
|
||||
|
||||
// allow browsers to automatically infer the language writing direction
|
||||
otherProps.dir = dir !== undefined ? dir : 'auto';
|
||||
otherProps.className = css.combine(
|
||||
classes.text,
|
||||
hasTextAncestor ? classes.textHasAncestor : classes.textIsRoot,
|
||||
numberOfLines === 1 && classes.textSingleLine
|
||||
);
|
||||
otherProps.style = [
|
||||
styles.initial,
|
||||
this.context.isInAParentText === true && styles.isInAParentText,
|
||||
style,
|
||||
selectable === false && styles.notSelectable,
|
||||
numberOfLines === 1 && styles.singleLineStyle,
|
||||
onPress && styles.pressable
|
||||
];
|
||||
// allow browsers to automatically infer the language writing direction
|
||||
otherProps.dir = dir !== undefined ? dir : 'auto';
|
||||
|
||||
const component = isInAParentText ? 'span' : 'div';
|
||||
const component = hasTextAncestor ? 'span' : 'div';
|
||||
|
||||
return createElement(component, otherProps);
|
||||
}
|
||||
@@ -97,41 +100,45 @@ class Text extends Component<*> {
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
initial: {
|
||||
const classes = css.create({
|
||||
text: {
|
||||
backgroundColor: 'transparent',
|
||||
borderWidth: 0,
|
||||
boxSizing: 'border-box',
|
||||
color: 'inherit',
|
||||
display: 'inline',
|
||||
fontFamily: 'System',
|
||||
fontSize: 14,
|
||||
fontStyle: 'inherit',
|
||||
fontVariant: ['inherit'],
|
||||
fontWeight: 'inherit',
|
||||
lineHeight: 'inherit',
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
textDecorationLine: 'none',
|
||||
whiteSpace: 'pre-wrap',
|
||||
textAlign: 'inherit',
|
||||
wordWrap: 'break-word'
|
||||
},
|
||||
isInAParentText: {
|
||||
// inherit parent font styles
|
||||
fontFamily: 'inherit',
|
||||
fontSize: 'inherit',
|
||||
textIsRoot: {
|
||||
color: 'black',
|
||||
font: 'normal 14px System',
|
||||
textDecoration: 'none',
|
||||
whiteSpace: 'pre-wrap'
|
||||
},
|
||||
textHasAncestor: {
|
||||
color: 'inherit',
|
||||
font: 'inherit',
|
||||
textDecoration: 'inherit',
|
||||
whiteSpace: 'inherit'
|
||||
},
|
||||
// "!important" is used to prevent essential styles from being overridden
|
||||
// by merged styles
|
||||
textSingleLine: {
|
||||
maxWidth: '100%',
|
||||
overflow: 'hidden !important',
|
||||
textOverflow: 'ellipsis !important',
|
||||
whiteSpace: 'nowrap !important'
|
||||
}
|
||||
});
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
notSelectable: {
|
||||
userSelect: 'none'
|
||||
},
|
||||
pressable: {
|
||||
cursor: 'pointer'
|
||||
},
|
||||
singleLineStyle: {
|
||||
maxWidth: '100%',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap'
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ const testIfDocumentIsFocused = (message, fn) => {
|
||||
if (document.hasFocus && document.hasFocus()) {
|
||||
test(message, fn);
|
||||
} else {
|
||||
test.skip(`${message} – document is not focused`);
|
||||
test.skip(`${message} – document is not focused`, () => {});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -180,86 +180,10 @@ describe('components/TextInput', () => {
|
||||
});
|
||||
|
||||
describe('prop "onKeyPress"', () => {
|
||||
test('backspace key', () => {
|
||||
const onKeyPress = jest.fn();
|
||||
const input = findNativeInput(mount(<TextInput onKeyPress={onKeyPress} />));
|
||||
input.simulate('keyDown', { which: 8 });
|
||||
expect(onKeyPress).toHaveBeenCalledTimes(1);
|
||||
expect(onKeyPress).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
nativeEvent: {
|
||||
altKey: undefined,
|
||||
ctrlKey: undefined,
|
||||
key: 'Backspace',
|
||||
metaKey: undefined,
|
||||
shiftKey: undefined,
|
||||
target: expect.anything()
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
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} />));
|
||||
input.simulate('keyPress', { which: 13 });
|
||||
expect(onKeyPress).toHaveBeenCalledTimes(1);
|
||||
expect(onKeyPress).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
nativeEvent: {
|
||||
altKey: undefined,
|
||||
ctrlKey: undefined,
|
||||
key: 'Enter',
|
||||
metaKey: undefined,
|
||||
shiftKey: undefined,
|
||||
target: expect.anything()
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test('space key', () => {
|
||||
const onKeyPress = jest.fn();
|
||||
const input = findNativeInput(mount(<TextInput onKeyPress={onKeyPress} />));
|
||||
input.simulate('keyPress', { which: 32 });
|
||||
expect(onKeyPress).toHaveBeenCalledTimes(1);
|
||||
expect(onKeyPress).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
nativeEvent: {
|
||||
altKey: undefined,
|
||||
ctrlKey: undefined,
|
||||
key: ' ',
|
||||
metaKey: undefined,
|
||||
shiftKey: undefined,
|
||||
target: expect.anything()
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test('arrow key', () => {
|
||||
const onKeyPress = jest.fn();
|
||||
const input = findNativeInput(mount(<TextInput onKeyPress={onKeyPress} />));
|
||||
input.simulate('keyPress', { which: 37 });
|
||||
input.simulate('keyPress', { key: 'ArrowLeft' });
|
||||
expect(onKeyPress).toHaveBeenCalledTimes(1);
|
||||
expect(onKeyPress).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
@@ -275,10 +199,105 @@ describe('components/TextInput', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('backspace key', () => {
|
||||
const onKeyPress = jest.fn();
|
||||
const input = findNativeInput(mount(<TextInput onKeyPress={onKeyPress} />));
|
||||
input.simulate('keyDown', { key: 'Backspace' });
|
||||
expect(onKeyPress).toHaveBeenCalledTimes(1);
|
||||
expect(onKeyPress).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
nativeEvent: {
|
||||
altKey: undefined,
|
||||
ctrlKey: undefined,
|
||||
key: 'Backspace',
|
||||
metaKey: undefined,
|
||||
shiftKey: undefined,
|
||||
target: expect.anything()
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test('enter key', () => {
|
||||
const onKeyPress = jest.fn();
|
||||
const input = findNativeInput(mount(<TextInput onKeyPress={onKeyPress} />));
|
||||
input.simulate('keyPress', { key: 'Enter' });
|
||||
expect(onKeyPress).toHaveBeenCalledTimes(1);
|
||||
expect(onKeyPress).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
nativeEvent: {
|
||||
altKey: undefined,
|
||||
ctrlKey: undefined,
|
||||
key: 'Enter',
|
||||
metaKey: undefined,
|
||||
shiftKey: undefined,
|
||||
target: expect.anything()
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test('escape key', () => {
|
||||
const onKeyPress = jest.fn();
|
||||
const input = findNativeInput(mount(<TextInput onKeyPress={onKeyPress} />));
|
||||
input.simulate('keyPress', { key: 'Escape' });
|
||||
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} />));
|
||||
input.simulate('keyPress', { key: ' ' });
|
||||
expect(onKeyPress).toHaveBeenCalledTimes(1);
|
||||
expect(onKeyPress).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
nativeEvent: {
|
||||
altKey: undefined,
|
||||
ctrlKey: undefined,
|
||||
key: ' ',
|
||||
metaKey: undefined,
|
||||
shiftKey: undefined,
|
||||
target: expect.anything()
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test('tab key', () => {
|
||||
const onKeyPress = jest.fn();
|
||||
const input = findNativeInput(mount(<TextInput onKeyPress={onKeyPress} />));
|
||||
input.simulate('keyDown', { key: 'Tab' });
|
||||
expect(onKeyPress).toHaveBeenCalledTimes(1);
|
||||
expect(onKeyPress).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
nativeEvent: {
|
||||
altKey: undefined,
|
||||
ctrlKey: undefined,
|
||||
key: 'Tab',
|
||||
metaKey: undefined,
|
||||
shiftKey: undefined,
|
||||
target: expect.anything()
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test('text key', () => {
|
||||
const onKeyPress = jest.fn();
|
||||
const input = findNativeInput(mount(<TextInput onKeyPress={onKeyPress} />));
|
||||
input.simulate('keyPress', { which: 97 });
|
||||
input.simulate('keyPress', { key: 'a' });
|
||||
expect(onKeyPress).toHaveBeenCalledTimes(1);
|
||||
expect(onKeyPress).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
@@ -302,7 +321,7 @@ describe('components/TextInput', () => {
|
||||
ctrlKey: true,
|
||||
metaKey: true,
|
||||
shiftKey: true,
|
||||
which: 32
|
||||
key: ' '
|
||||
});
|
||||
expect(onKeyPress).toHaveBeenCalledTimes(1);
|
||||
expect(onKeyPress).toBeCalledWith(
|
||||
@@ -324,7 +343,7 @@ describe('components/TextInput', () => {
|
||||
const input = findNativeInput(mount(<TextInput onKeyPress={onKeyPress} />));
|
||||
input.simulate('keyDown', {
|
||||
metaKey: true,
|
||||
which: 13
|
||||
key: 'Enter'
|
||||
});
|
||||
expect(onKeyPress).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
@@ -358,7 +377,7 @@ describe('components/TextInput', () => {
|
||||
const input = findNativeInput(
|
||||
mount(<TextInput defaultValue="12345" onSubmitEditing={onSubmitEditing} />)
|
||||
);
|
||||
input.simulate('keyPress', { which: 13 });
|
||||
input.simulate('keyPress', { key: 'Enter' });
|
||||
function onSubmitEditing(e) {
|
||||
expect(e.nativeEvent.target).toBeDefined();
|
||||
expect(e.nativeEvent.text).toBe('12345');
|
||||
@@ -371,7 +390,7 @@ describe('components/TextInput', () => {
|
||||
const input = findNativeTextarea(
|
||||
mount(<TextInput defaultValue="12345" multiline onSubmitEditing={onSubmitEditing} />)
|
||||
);
|
||||
input.simulate('keyPress', { which: 13 });
|
||||
input.simulate('keyPress', { key: 'Enter' });
|
||||
expect(onSubmitEditing).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -391,11 +410,11 @@ describe('components/TextInput', () => {
|
||||
);
|
||||
|
||||
// shift+enter should enter newline, not submit
|
||||
input.simulate('keyPress', { which: 13, preventDefault, shiftKey: true });
|
||||
input.simulate('keyPress', { key: 'Enter', preventDefault, shiftKey: true });
|
||||
expect(onSubmitEditing).not.toHaveBeenCalledWith(expect.objectContaining({ shiftKey: true }));
|
||||
expect(preventDefault).not.toHaveBeenCalled();
|
||||
|
||||
input.simulate('keyPress', { which: 13, preventDefault });
|
||||
input.simulate('keyPress', { key: 'Enter', preventDefault });
|
||||
expect(onSubmitEditing).toHaveBeenCalledTimes(1);
|
||||
expect(preventDefault).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
@@ -14,8 +14,8 @@ import { canUseDOM } from 'fbjs/lib/ExecutionEnvironment';
|
||||
import { Component } from 'react';
|
||||
import ColorPropType from '../ColorPropType';
|
||||
import createElement from '../createElement';
|
||||
import css from '../StyleSheet/css';
|
||||
import findNodeHandle from '../findNodeHandle';
|
||||
import StyleSheet from '../StyleSheet';
|
||||
import StyleSheetPropType from '../../modules/StyleSheetPropType';
|
||||
import TextInputStylePropTypes from './TextInputStylePropTypes';
|
||||
import TextInputState from '../../modules/TextInputState';
|
||||
@@ -150,8 +150,6 @@ class TextInput extends Component<*> {
|
||||
|
||||
static State = TextInputState;
|
||||
|
||||
blur: Function;
|
||||
|
||||
clear() {
|
||||
this._node.value = '';
|
||||
}
|
||||
@@ -256,6 +254,7 @@ class TextInput extends Component<*> {
|
||||
|
||||
Object.assign(otherProps, {
|
||||
autoCorrect: autoCorrect ? 'on' : 'off',
|
||||
className: classes.textinput,
|
||||
dir: 'auto',
|
||||
onBlur: normalizeEventHandler(this._handleBlur),
|
||||
onChange: normalizeEventHandler(this._handleChange),
|
||||
@@ -266,7 +265,7 @@ class TextInput extends Component<*> {
|
||||
readOnly: !editable,
|
||||
ref: this._setNode,
|
||||
spellCheck: spellCheck != null ? spellCheck : autoCorrect,
|
||||
style: [styles.initial, style]
|
||||
style
|
||||
});
|
||||
|
||||
if (multiline) {
|
||||
@@ -317,15 +316,17 @@ 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 === 37 ||
|
||||
e.which === 38 ||
|
||||
e.which === 39 ||
|
||||
e.which === 40
|
||||
e.key === 'ArrowLeft' ||
|
||||
e.key === 'ArrowUp' ||
|
||||
e.key === 'ArrowRight' ||
|
||||
e.key === 'ArrowDown' ||
|
||||
e.key === 'Backspace' ||
|
||||
e.key === 'Escape' ||
|
||||
(e.key === 'Enter' && e.metaKey) ||
|
||||
e.key === 'Tab'
|
||||
) {
|
||||
this._handleKeyPress(e);
|
||||
}
|
||||
@@ -337,43 +338,7 @@ class TextInput extends Component<*> {
|
||||
const shouldBlurOnSubmit = blurOnSubmit == null ? blurOnSubmitDefault : blurOnSubmit;
|
||||
|
||||
if (onKeyPress) {
|
||||
let keyValue;
|
||||
switch (e.which) {
|
||||
case 8:
|
||||
keyValue = 'Backspace';
|
||||
break;
|
||||
case 9:
|
||||
keyValue = 'Tab';
|
||||
break;
|
||||
case 13:
|
||||
keyValue = 'Enter';
|
||||
break;
|
||||
case 32:
|
||||
keyValue = ' ';
|
||||
break;
|
||||
case 37:
|
||||
keyValue = 'ArrowLeft';
|
||||
break;
|
||||
case 38:
|
||||
keyValue = 'ArrowUp';
|
||||
break;
|
||||
case 39:
|
||||
keyValue = 'ArrowRight';
|
||||
break;
|
||||
case 40:
|
||||
keyValue = 'ArrowDown';
|
||||
break;
|
||||
default: {
|
||||
// Trim to only care about the keys that have a textual representation
|
||||
if (e.shiftKey) {
|
||||
keyValue = String.fromCharCode(e.which).trim();
|
||||
} else {
|
||||
keyValue = String.fromCharCode(e.which)
|
||||
.toLowerCase()
|
||||
.trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
const keyValue = e.key;
|
||||
|
||||
if (keyValue) {
|
||||
e.nativeEvent = {
|
||||
@@ -388,7 +353,7 @@ class TextInput extends Component<*> {
|
||||
}
|
||||
}
|
||||
|
||||
if (!e.isDefaultPrevented() && e.which === 13 && !e.shiftKey) {
|
||||
if (!e.isDefaultPrevented() && e.key === 'Enter' && !e.shiftKey) {
|
||||
if ((blurOnSubmit || !multiline) && onSubmitEditing) {
|
||||
// prevent "Enter" from inserting a newline
|
||||
e.preventDefault();
|
||||
@@ -396,6 +361,7 @@ class TextInput extends Component<*> {
|
||||
onSubmitEditing(e);
|
||||
}
|
||||
if (shouldBlurOnSubmit) {
|
||||
// $FlowFixMe
|
||||
this.blur();
|
||||
}
|
||||
}
|
||||
@@ -423,18 +389,15 @@ class TextInput extends Component<*> {
|
||||
};
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
initial: {
|
||||
const classes = css.create({
|
||||
textinput: {
|
||||
MozAppearance: 'textfield',
|
||||
WebkitAppearance: 'none',
|
||||
backgroundColor: 'transparent',
|
||||
borderColor: 'black',
|
||||
border: '0 solid black',
|
||||
borderRadius: 0,
|
||||
borderStyle: 'solid',
|
||||
borderWidth: 0,
|
||||
boxSizing: 'border-box',
|
||||
fontFamily: 'System',
|
||||
fontSize: 14,
|
||||
fontFamily: '14px System',
|
||||
padding: 0,
|
||||
resize: 'none'
|
||||
}
|
||||
|
||||
@@ -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' };
|
||||
|
||||
@@ -7,28 +7,24 @@
|
||||
* @noflow
|
||||
*/
|
||||
|
||||
import getBoundingClientRect from '../../modules/getBoundingClientRect';
|
||||
import setValueForStyles from '../../vendor/react-dom/setValueForStyles';
|
||||
|
||||
const getRect = node => {
|
||||
const height = node.offsetHeight;
|
||||
// Unlike the DOM's getBoundingClientRect, React Native layout measurements
|
||||
// for "height" and "width" ignore scale transforms.
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/CSS_Object_Model/Determining_the_dimensions_of_elements
|
||||
const { x, y, top, left } = getBoundingClientRect(node);
|
||||
const width = node.offsetWidth;
|
||||
let left = node.offsetLeft;
|
||||
let top = node.offsetTop;
|
||||
node = node.offsetParent;
|
||||
|
||||
while (node && node.nodeType === 1 /* Node.ELEMENT_NODE */) {
|
||||
left += node.offsetLeft - node.scrollLeft;
|
||||
top += node.offsetTop - node.scrollTop;
|
||||
node = node.offsetParent;
|
||||
}
|
||||
return { height, left, top, width };
|
||||
const height = node.offsetHeight;
|
||||
return { x, y, width, height, top, left };
|
||||
};
|
||||
|
||||
const measureLayout = (node, relativeToNativeNode, callback) => {
|
||||
const relativeNode = relativeToNativeNode || (node && node.parentNode);
|
||||
if (node && relativeNode) {
|
||||
setTimeout(() => {
|
||||
const relativeRect = getRect(relativeNode);
|
||||
const relativeRect = getBoundingClientRect(relativeNode);
|
||||
const { height, left, top, width } = getRect(node);
|
||||
const x = left - relativeRect.left;
|
||||
const y = top - relativeRect.top;
|
||||
@@ -37,6 +33,13 @@ const measureLayout = (node, relativeToNativeNode, callback) => {
|
||||
}
|
||||
};
|
||||
|
||||
const focusableElements = {
|
||||
A: true,
|
||||
INPUT: true,
|
||||
SELECT: true,
|
||||
TEXTAREA: true
|
||||
};
|
||||
|
||||
const UIManager = {
|
||||
blur(node) {
|
||||
try {
|
||||
@@ -46,6 +49,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) {}
|
||||
},
|
||||
|
||||
@@ -33,6 +33,7 @@ export type ViewProps = {
|
||||
accessibilityLabel?: string,
|
||||
accessibilityLiveRegion?: 'none' | 'polite' | 'assertive',
|
||||
accessibilityRole?: string,
|
||||
accessibilityStates?: Array<string>,
|
||||
accessibilityTraits?: string | Array<string>,
|
||||
accessible?: boolean,
|
||||
children?: any,
|
||||
@@ -83,6 +84,16 @@ const ViewPropTypes = {
|
||||
accessibilityLabel: string,
|
||||
accessibilityLiveRegion: oneOf(['assertive', 'none', 'polite']),
|
||||
accessibilityRole: string,
|
||||
accessibilityStates: oneOf([
|
||||
'disabled',
|
||||
'selected',
|
||||
/* web-only */ 'busy',
|
||||
'checked',
|
||||
'expanded',
|
||||
'grabbed',
|
||||
'invalid',
|
||||
'pressed'
|
||||
]),
|
||||
accessibilityTraits: oneOfType([array, string]),
|
||||
accessible: bool,
|
||||
children: any,
|
||||
|
||||
@@ -35,6 +35,7 @@ const ViewStylePropTypes = {
|
||||
/**
|
||||
* @platform web
|
||||
*/
|
||||
backdropFilter: string,
|
||||
backgroundAttachment: string,
|
||||
backgroundBlendMode: string,
|
||||
backgroundClip: string,
|
||||
@@ -51,6 +52,8 @@ const ViewStylePropTypes = {
|
||||
overscrollBehavior: overscrollBehaviorType,
|
||||
overscrollBehaviorX: overscrollBehaviorType,
|
||||
overscrollBehaviorY: overscrollBehaviorType,
|
||||
scrollSnapAlign: string,
|
||||
scrollSnapType: string,
|
||||
WebkitMaskImage: string,
|
||||
WebkitOverflowScrolling: oneOf(['auto', 'touch'])
|
||||
};
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
const whitelist = {
|
||||
const supportedProps = {
|
||||
accessibilityComponentType: true,
|
||||
accessibilityLabel: true,
|
||||
accessibilityLiveRegion: true,
|
||||
accessibilityRole: true,
|
||||
accessibilityStates: true,
|
||||
accessibilityTraits: true,
|
||||
accessible: true,
|
||||
children: true,
|
||||
@@ -67,7 +68,7 @@ const filterSupportedProps = props => {
|
||||
const safeProps = {};
|
||||
for (const prop in props) {
|
||||
if (props.hasOwnProperty(prop)) {
|
||||
if (whitelist[prop] || prop.indexOf('aria-') === 0 || prop.indexOf('data-') === 0) {
|
||||
if (supportedProps[prop] || prop.indexOf('aria-') === 0 || prop.indexOf('data-') === 0) {
|
||||
safeProps[prop] = props[prop];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import applyLayout from '../../modules/applyLayout';
|
||||
import applyNativeMethods from '../../modules/applyNativeMethods';
|
||||
import { bool } from 'prop-types';
|
||||
import createElement from '../createElement';
|
||||
import css from '../StyleSheet/css';
|
||||
import filterSupportedProps from './filterSupportedProps';
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
import StyleSheet from '../StyleSheet';
|
||||
@@ -51,14 +52,18 @@ class View extends Component<ViewProps> {
|
||||
|
||||
const { isInAParentText } = this.context;
|
||||
|
||||
supportedProps.className = classes.view;
|
||||
supportedProps.style = StyleSheet.compose(
|
||||
styles.initial,
|
||||
StyleSheet.compose(isInAParentText && styles.inline, this.props.style)
|
||||
isInAParentText && styles.inline,
|
||||
this.props.style
|
||||
);
|
||||
|
||||
if (hitSlop) {
|
||||
const hitSlopStyle = calculateHitSlopStyle(hitSlop);
|
||||
const hitSlopChild = createElement('span', { style: [styles.hitSlop, hitSlopStyle] });
|
||||
const hitSlopChild = createElement('span', {
|
||||
className: classes.hitslop,
|
||||
style: hitSlopStyle
|
||||
});
|
||||
supportedProps.children = React.Children.toArray([hitSlopChild, supportedProps.children]);
|
||||
}
|
||||
|
||||
@@ -66,32 +71,45 @@ class View extends Component<ViewProps> {
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
// https://github.com/facebook/css-layout#default-values
|
||||
initial: {
|
||||
const classes = css.create({
|
||||
view: {
|
||||
alignItems: 'stretch',
|
||||
borderWidth: 0,
|
||||
borderStyle: 'solid',
|
||||
border: '0 solid black',
|
||||
boxSizing: 'border-box',
|
||||
display: 'flex',
|
||||
flexBasis: 'auto',
|
||||
flexDirection: 'column',
|
||||
flexShrink: 0,
|
||||
margin: 0,
|
||||
minHeight: 0,
|
||||
minWidth: 0,
|
||||
padding: 0,
|
||||
position: 'relative',
|
||||
zIndex: 0,
|
||||
// fix flexbox bugs
|
||||
minHeight: 0,
|
||||
minWidth: 0
|
||||
},
|
||||
inline: {
|
||||
display: 'inline-flex'
|
||||
// resets for if View is rendered as a link or list DOM element
|
||||
backgroundColor: 'transparent',
|
||||
color: 'inherit',
|
||||
font: 'inherit',
|
||||
listStyle: 'none',
|
||||
textAlign: 'inherit',
|
||||
textDecoration: 'none'
|
||||
},
|
||||
// this zIndex-ordering positions the hitSlop above the View but behind
|
||||
// its children
|
||||
hitSlop: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
zIndex: -1
|
||||
}
|
||||
});
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
inline: {
|
||||
display: 'inline-flex'
|
||||
}
|
||||
});
|
||||
|
||||
export default applyLayout(applyNativeMethods(View));
|
||||
|
||||
@@ -25,4 +25,11 @@ describe('modules/AccessibilityUtil/propsToAriaRole', () => {
|
||||
})
|
||||
).toEqual('link');
|
||||
});
|
||||
|
||||
test('when "accessibilityRole" is a native-only value', () => {
|
||||
expect(propsToAriaRole({ accessibilityRole: 'none' })).toEqual('presentation');
|
||||
expect(propsToAriaRole({ accessibilityRole: 'imagebutton' })).toEqual(undefined);
|
||||
// not really native-only, but used to allow Web to render <label> around TextInput
|
||||
expect(propsToAriaRole({ accessibilityRole: 'label' })).toEqual(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
const isDisabled = (props: Object) => props.disabled || props['aria-disabled'];
|
||||
const isDisabled = (props: Object) =>
|
||||
props.disabled ||
|
||||
(Array.isArray(props.accessibilityStates) && props.accessibilityStates.indexOf('disabled') > -1);
|
||||
|
||||
export default isDisabled;
|
||||
|
||||
@@ -15,7 +15,6 @@ const roleComponents = {
|
||||
complementary: 'aside',
|
||||
contentinfo: 'footer',
|
||||
form: 'form',
|
||||
label: 'label',
|
||||
link: 'a',
|
||||
list: 'ul',
|
||||
listitem: 'li',
|
||||
@@ -27,6 +26,11 @@ const roleComponents = {
|
||||
const emptyObject = {};
|
||||
|
||||
const propsToAccessibilityComponent = (props: Object = emptyObject) => {
|
||||
// special-case for "label" role which doesn't map to an ARIA role
|
||||
if (props.accessibilityRole === 'label') {
|
||||
return 'label';
|
||||
}
|
||||
|
||||
const role = propsToAriaRole(props);
|
||||
if (role) {
|
||||
if (role === 'heading') {
|
||||
|
||||
@@ -23,6 +23,21 @@ const accessibilityTraitsToRole = {
|
||||
summary: 'region'
|
||||
};
|
||||
|
||||
const accessibilityRoleToWebRole = {
|
||||
adjustable: 'slider',
|
||||
button: 'button',
|
||||
header: 'heading',
|
||||
image: 'img',
|
||||
imagebutton: null,
|
||||
keyboardkey: null,
|
||||
label: null,
|
||||
link: 'link',
|
||||
none: 'presentation',
|
||||
search: 'search',
|
||||
summary: 'region',
|
||||
text: null
|
||||
};
|
||||
|
||||
/**
|
||||
* Provides compatibility with React Native's "accessibilityTraits" (iOS) and
|
||||
* "accessibilityComponentType" (Android), converting them to equivalent ARIA
|
||||
@@ -34,7 +49,11 @@ const propsToAriaRole = ({
|
||||
accessibilityTraits
|
||||
}: Object) => {
|
||||
if (accessibilityRole) {
|
||||
return accessibilityRole;
|
||||
const inferredRole = accessibilityRoleToWebRole[accessibilityRole];
|
||||
if (inferredRole !== null) {
|
||||
// ignore roles that don't map to web
|
||||
return inferredRole || accessibilityRole;
|
||||
}
|
||||
}
|
||||
if (accessibilityTraits) {
|
||||
const trait = Array.isArray(accessibilityTraits) ? accessibilityTraits[0] : accessibilityTraits;
|
||||
|
||||
@@ -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 }),
|
||||
|
||||
@@ -2,10 +2,14 @@
|
||||
|
||||
exports[`modules/createDOMProps includes "rel" values for "a" elements (to securely open external links) 1`] = `" noopener noreferrer"`;
|
||||
|
||||
exports[`modules/createDOMProps includes cursor style for "button" role 1`] = `"rn-cursor-1loqt21"`;
|
||||
exports[`modules/createDOMProps includes base reset style for browser-styled elements 1`] = `"r-reset"`;
|
||||
|
||||
exports[`modules/createDOMProps includes reset styles for "a" elements 1`] = `"rn-backgroundColor-1niwhzg rn-color-homxoj rn-textDecoration-bauka4"`;
|
||||
exports[`modules/createDOMProps includes base reset style for browser-styled elements 2`] = `"r-reset"`;
|
||||
|
||||
exports[`modules/createDOMProps includes reset styles for "button" elements 1`] = `"rn-appearance-30o5oe rn-backgroundColor-1niwhzg rn-color-homxoj rn-fontFamily-poiln3 rn-fontSize-7cikom rn-fontStyle-o11vmf rn-fontVariant-ebii48 rn-fontWeight-gul640 rn-lineHeight-t9a87b rn-textAlign-1ttztb7"`;
|
||||
exports[`modules/createDOMProps includes base reset style for browser-styled elements 3`] = `"r-reset"`;
|
||||
|
||||
exports[`modules/createDOMProps includes reset styles for "ul" elements 1`] = `"rn-listStyle-1ebb2ja"`;
|
||||
exports[`modules/createDOMProps includes base reset style for browser-styled elements 4`] = `"r-reset"`;
|
||||
|
||||
exports[`modules/createDOMProps includes cursor style for pressable roles 1`] = `"r-pointer"`;
|
||||
|
||||
exports[`modules/createDOMProps includes cursor style for pressable roles 2`] = `"r-pointer"`;
|
||||
|
||||
@@ -35,18 +35,12 @@ describe('modules/createDOMProps', () => {
|
||||
expect(createProps({ accessibilityRole, disabled: true })).toEqual(
|
||||
expect.objectContaining({ 'aria-disabled': true, disabled: true, tabIndex: '-1' })
|
||||
);
|
||||
expect(createProps({ accessibilityRole, 'aria-disabled': true })).toEqual(
|
||||
expect.objectContaining({ 'aria-disabled': true, disabled: true, tabIndex: '-1' })
|
||||
);
|
||||
});
|
||||
|
||||
test('when "disabled" is false', () => {
|
||||
expect(createProps({ accessibilityRole, disabled: false })).toEqual(
|
||||
expect.objectContaining({ 'data-focusable': true })
|
||||
);
|
||||
expect(createProps({ accessibilityRole, 'aria-disabled': false })).toEqual(
|
||||
expect.objectContaining({ 'data-focusable': true })
|
||||
);
|
||||
});
|
||||
|
||||
test('when "importantForAccessibility" is "no"', () => {
|
||||
@@ -88,18 +82,12 @@ describe('modules/createDOMProps', () => {
|
||||
expect(createProps({ accessibilityRole, disabled: true })).toEqual(
|
||||
expect.objectContaining({ 'aria-disabled': true, disabled: true })
|
||||
);
|
||||
expect(createProps({ accessibilityRole, 'aria-disabled': true })).toEqual(
|
||||
expect.objectContaining({ 'aria-disabled': true, disabled: true })
|
||||
);
|
||||
});
|
||||
|
||||
test('when "disabled" is false', () => {
|
||||
expect(createProps({ accessibilityRole, disabled: false })).toEqual(
|
||||
expect.objectContaining({ 'data-focusable': true, tabIndex: '0' })
|
||||
);
|
||||
expect(createProps({ accessibilityRole, 'aria-disabled': false })).toEqual(
|
||||
expect.objectContaining({ 'data-focusable': true, tabIndex: '0' })
|
||||
);
|
||||
});
|
||||
|
||||
test('when "importantForAccessibility" is "no"', () => {
|
||||
@@ -164,12 +152,17 @@ describe('modules/createDOMProps', () => {
|
||||
expect(props['aria-live']).toEqual('off');
|
||||
});
|
||||
|
||||
describe('prop "accessibilityRole"', () => {
|
||||
test('does not become "role" when value is "label"', () => {
|
||||
const accessibilityRole = 'label';
|
||||
const props = createProps({ accessibilityRole });
|
||||
expect(props.role).toBeUndefined();
|
||||
});
|
||||
test('prop "accessibilityRole" becomes "role"', () => {
|
||||
const accessibilityRole = 'button';
|
||||
const props = createProps({ accessibilityRole });
|
||||
expect(props.role).toEqual('button');
|
||||
});
|
||||
|
||||
test('prop "accessibilityStates" becomes ARIA states', () => {
|
||||
const accessibilityStates = ['disabled', 'selected'];
|
||||
const props = createProps({ accessibilityStates });
|
||||
expect(props['aria-disabled']).toEqual(true);
|
||||
expect(props['aria-selected']).toEqual(true);
|
||||
});
|
||||
|
||||
test('prop "className" is preserved', () => {
|
||||
@@ -200,23 +193,15 @@ describe('modules/createDOMProps', () => {
|
||||
expect(props.rel).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('includes reset styles for "a" elements', () => {
|
||||
const props = createDOMProps('a');
|
||||
expect(props.className).toMatchSnapshot();
|
||||
test('includes cursor style for pressable roles', () => {
|
||||
expect(createDOMProps('span', { accessibilityRole: 'link' }).className).toMatchSnapshot();
|
||||
expect(createDOMProps('span', { accessibilityRole: 'button' }).className).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('includes reset styles for "button" elements', () => {
|
||||
const props = createDOMProps('button');
|
||||
expect(props.className).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('includes cursor style for "button" role', () => {
|
||||
const props = createDOMProps('span', { accessibilityRole: 'button' });
|
||||
expect(props.className).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('includes reset styles for "ul" elements', () => {
|
||||
const props = createDOMProps('ul');
|
||||
expect(props.className).toMatchSnapshot();
|
||||
test('includes base reset style for browser-styled elements', () => {
|
||||
expect(createDOMProps('a').className).toMatchSnapshot();
|
||||
expect(createDOMProps('button').className).toMatchSnapshot();
|
||||
expect(createDOMProps('li').className).toMatchSnapshot();
|
||||
expect(createDOMProps('ul').className).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,40 +13,6 @@ import styleResolver from '../../exports/StyleSheet/styleResolver';
|
||||
|
||||
const emptyObject = {};
|
||||
|
||||
const resetStyles = StyleSheet.create({
|
||||
ariaButton: {
|
||||
cursor: 'pointer'
|
||||
},
|
||||
button: {
|
||||
appearance: 'none',
|
||||
backgroundColor: 'transparent',
|
||||
color: 'inherit',
|
||||
fontFamily: 'inherit',
|
||||
fontSize: 'inherit',
|
||||
fontStyle: 'inherit',
|
||||
fontVariant: ['inherit'],
|
||||
fontWeight: 'inherit',
|
||||
lineHeight: 'inherit',
|
||||
textAlign: 'inherit'
|
||||
},
|
||||
heading: {
|
||||
fontFamily: 'inherit',
|
||||
fontSize: 'inherit',
|
||||
fontStyle: 'inherit',
|
||||
fontVariant: ['inherit'],
|
||||
fontWeight: 'inherit',
|
||||
lineHeight: 'inherit'
|
||||
},
|
||||
link: {
|
||||
backgroundColor: 'transparent',
|
||||
color: 'inherit',
|
||||
textDecorationLine: 'none'
|
||||
},
|
||||
list: {
|
||||
listStyle: 'none'
|
||||
}
|
||||
});
|
||||
|
||||
const pointerEventsStyles = StyleSheet.create({
|
||||
auto: {
|
||||
pointerEvents: 'auto'
|
||||
@@ -76,6 +42,7 @@ const createDOMProps = (component, props, styleResolver) => {
|
||||
const {
|
||||
accessibilityLabel,
|
||||
accessibilityLiveRegion,
|
||||
accessibilityStates,
|
||||
importantForAccessibility,
|
||||
nativeID,
|
||||
placeholderTextColor,
|
||||
@@ -104,7 +71,12 @@ const createDOMProps = (component, props, styleResolver) => {
|
||||
if (accessibilityLiveRegion && accessibilityLiveRegion.constructor === String) {
|
||||
domProps['aria-live'] = accessibilityLiveRegion === 'none' ? 'off' : accessibilityLiveRegion;
|
||||
}
|
||||
if (role && role.constructor === String && role !== 'label') {
|
||||
if (Array.isArray(accessibilityStates)) {
|
||||
for (let i = 0; i < accessibilityStates.length; i += 1) {
|
||||
domProps[`aria-${accessibilityStates[i]}`] = true;
|
||||
}
|
||||
}
|
||||
if (role && role.constructor === String) {
|
||||
domProps.role = role;
|
||||
}
|
||||
|
||||
@@ -123,6 +95,8 @@ const createDOMProps = (component, props, styleResolver) => {
|
||||
importantForAccessibility !== 'no-hide-descendants';
|
||||
if (
|
||||
role === 'link' ||
|
||||
component === 'a' ||
|
||||
component === 'button' ||
|
||||
component === 'input' ||
|
||||
component === 'select' ||
|
||||
component === 'textarea'
|
||||
@@ -146,30 +120,56 @@ const createDOMProps = (component, props, styleResolver) => {
|
||||
|
||||
// STYLE
|
||||
// Resolve React Native styles to optimized browser equivalent
|
||||
const reactNativeStyle = [
|
||||
component === 'a' && resetStyles.link,
|
||||
component === 'button' && resetStyles.button,
|
||||
role === 'heading' && resetStyles.heading,
|
||||
component === 'ul' && resetStyles.list,
|
||||
role === 'button' && !disabled && resetStyles.ariaButton,
|
||||
const reactNativeStyle = StyleSheet.compose(
|
||||
pointerEvents && pointerEventsStyles[pointerEvents],
|
||||
providedStyle,
|
||||
placeholderTextColor && { placeholderTextColor }
|
||||
];
|
||||
StyleSheet.compose(
|
||||
providedStyle,
|
||||
placeholderTextColor && { placeholderTextColor }
|
||||
)
|
||||
);
|
||||
const { className, style } = styleResolver(reactNativeStyle);
|
||||
if (className && className.constructor === String) {
|
||||
domProps.className = props.className ? `${props.className} ${className}` : className;
|
||||
}
|
||||
if (style) {
|
||||
domProps.style = style;
|
||||
}
|
||||
|
||||
// CLASSNAME
|
||||
// Apply static style resets
|
||||
let c;
|
||||
// style interactive elements for mouse and mobile browsers
|
||||
if ((role === 'button' || role === 'link') && !disabled) {
|
||||
c = 'r-pointer';
|
||||
}
|
||||
// style reset various elements (not all are used internally)
|
||||
if (
|
||||
component === 'a' ||
|
||||
component === 'button' ||
|
||||
component === 'li' ||
|
||||
component === 'ul' ||
|
||||
role === 'heading'
|
||||
) {
|
||||
c = 'r-reset' + (c != null ? ' ' + c : '');
|
||||
}
|
||||
// style from createElement use
|
||||
if (props.className != null) {
|
||||
c = props.className + (c != null ? ' ' + c : '');
|
||||
}
|
||||
// style from React Native StyleSheets
|
||||
if (className != null && className !== '') {
|
||||
c = (c != null ? c + ' ' : '') + className;
|
||||
}
|
||||
if (c != null) {
|
||||
domProps.className = c;
|
||||
}
|
||||
|
||||
// OTHER
|
||||
// Native element ID
|
||||
if (nativeID && nativeID.constructor === String) {
|
||||
domProps.id = nativeID;
|
||||
}
|
||||
// Link security and automation test ids
|
||||
// Link security
|
||||
// https://mathiasbynens.github.io/rel-noopener/
|
||||
// Note: using "noreferrer" doesn't impact referrer tracking for https
|
||||
// transfers (i.e., from https to https).
|
||||
if (component === 'a' && domProps.target === '_blank') {
|
||||
domProps.rel = `${domProps.rel || ''} noopener noreferrer`;
|
||||
}
|
||||
|
||||
21
packages/react-native-web/src/modules/getBoundingClientRect/index.js
vendored
Normal file
21
packages/react-native-web/src/modules/getBoundingClientRect/index.js
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Copyright (c) 2015-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 strict
|
||||
*/
|
||||
|
||||
/* global HTMLElement */
|
||||
|
||||
const getBoundingClientRect = (node: HTMLElement) => {
|
||||
if (node) {
|
||||
const isElement = node.nodeType === 1; /* Node.ELEMENT_NODE */
|
||||
if (isElement && typeof node.getBoundingClientRect === 'function') {
|
||||
return node.getBoundingClientRect();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default getBoundingClientRect;
|
||||
@@ -1,14 +0,0 @@
|
||||
const hasOwnProperty = Object.prototype.hasOwnProperty;
|
||||
|
||||
const mapKeyValue = (obj, fn) => {
|
||||
const result = [];
|
||||
for (const key in obj) {
|
||||
if (hasOwnProperty.call(obj, key)) {
|
||||
const r = fn(key, obj[key]);
|
||||
r && result.push(r);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
export default mapKeyValue;
|
||||
@@ -7,18 +7,11 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import getBoundingClientRect from '../getBoundingClientRect';
|
||||
|
||||
const emptyArray = [];
|
||||
const emptyFunction = () => {};
|
||||
|
||||
const getRect = node => {
|
||||
if (node) {
|
||||
const isElement = node.nodeType === 1 /* Node.ELEMENT_NODE */;
|
||||
if (isElement && typeof node.getBoundingClientRect === 'function') {
|
||||
return node.getBoundingClientRect();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Mobile Safari re-uses touch objects, so we copy the properties we want and normalize the identifier
|
||||
const normalizeTouches = touches => {
|
||||
if (!touches) {
|
||||
@@ -35,13 +28,13 @@ const normalizeTouches = touches => {
|
||||
clientY: touch.clientY,
|
||||
force: touch.force,
|
||||
get locationX() {
|
||||
rect = rect || getRect(touch.target);
|
||||
rect = rect || getBoundingClientRect(touch.target);
|
||||
if (rect) {
|
||||
return touch.pageX - rect.left;
|
||||
}
|
||||
},
|
||||
get locationY() {
|
||||
rect = rect || getRect(touch.target);
|
||||
rect = rect || getBoundingClientRect(touch.target);
|
||||
if (rect) {
|
||||
return touch.pageY - rect.top;
|
||||
}
|
||||
@@ -121,13 +114,13 @@ function normalizeMouseEvent(nativeEvent) {
|
||||
force: nativeEvent.force,
|
||||
identifier: 0,
|
||||
get locationX() {
|
||||
rect = rect || getRect(nativeEvent.target);
|
||||
rect = rect || getBoundingClientRect(nativeEvent.target);
|
||||
if (rect) {
|
||||
return nativeEvent.pageX - rect.left;
|
||||
}
|
||||
},
|
||||
get locationY() {
|
||||
rect = rect || getRect(nativeEvent.target);
|
||||
rect = rect || getBoundingClientRect(nativeEvent.target);
|
||||
if (rect) {
|
||||
return nativeEvent.pageY - rect.top;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -1561,8 +1561,7 @@ class CellRenderer extends React.Component<
|
||||
getItemLayout?: ?Function,
|
||||
renderItem: renderItemType,
|
||||
},
|
||||
prevCellKey: ?string,
|
||||
style: ?DangerouslyImpreciseStyleProp,
|
||||
prevCellKey: ?string
|
||||
},
|
||||
$FlowFixMeState,
|
||||
> {
|
||||
@@ -1631,7 +1630,6 @@ class CellRenderer extends React.Component<
|
||||
index,
|
||||
inversionStyle,
|
||||
parentProps,
|
||||
style,
|
||||
} = this.props;
|
||||
const {renderItem, getItemLayout} = parentProps;
|
||||
invariant(renderItem, 'no renderItem!');
|
||||
@@ -1651,9 +1649,9 @@ class CellRenderer extends React.Component<
|
||||
);
|
||||
const cellStyle = inversionStyle
|
||||
? horizontal
|
||||
? [styles.rowReverse, inversionStyle, style]
|
||||
: [styles.columnReverse, inversionStyle, style]
|
||||
: horizontal ? [styles.row, inversionStyle, style] : [inversionStyle, style];
|
||||
? [styles.rowReverse, inversionStyle]
|
||||
: [styles.columnReverse, inversionStyle]
|
||||
: horizontal ? [styles.row, inversionStyle] : inversionStyle;
|
||||
if (!CellRendererComponent) {
|
||||
return (
|
||||
<View style={cellStyle} onLayout={onLayout}>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "website",
|
||||
"version": "0.9.6",
|
||||
"version": "0.9.13",
|
||||
"scripts": {
|
||||
"build": "build-storybook -o ./dist -c ./storybook/.storybook",
|
||||
"start": "start-storybook -p 9001 -c ./storybook/.storybook",
|
||||
@@ -12,10 +12,10 @@
|
||||
"@storybook/react": "^3.4.3",
|
||||
"react": "^16.5.1",
|
||||
"react-dom": "^16.5.1",
|
||||
"react-native-web": "0.9.6"
|
||||
"react-native-web": "0.9.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-plugin-react-native-web": "0.9.6",
|
||||
"babel-plugin-react-native-web": "0.9.13",
|
||||
"url-loader": "^1.0.1",
|
||||
"webpack": "^4.8.1"
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"presets": [
|
||||
"react-native"
|
||||
],
|
||||
"plugins": [
|
||||
"react-native-web"
|
||||
]
|
||||
}
|
||||
7
packages/website/storybook/.storybook/babel.config.js
Normal file
7
packages/website/storybook/.storybook/babel.config.js
Normal file
@@ -0,0 +1,7 @@
|
||||
module.exports = function(api) {
|
||||
api.cache(true);
|
||||
return {
|
||||
presets: ['module:metro-react-native-babel-preset'],
|
||||
plugins: ['react-native-web']
|
||||
};
|
||||
};
|
||||
@@ -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"
|
||||
|
||||
@@ -266,7 +266,6 @@ const stylePropTypes = [
|
||||
typeInfo: 'number | string'
|
||||
},
|
||||
{
|
||||
label: 'web',
|
||||
name: 'textTransform',
|
||||
typeInfo: 'string'
|
||||
},
|
||||
@@ -279,6 +278,12 @@ const stylePropTypes = [
|
||||
name: 'whiteSpace',
|
||||
typeInfo: 'string'
|
||||
},
|
||||
{
|
||||
label: 'web',
|
||||
name: 'wordBreak',
|
||||
typeInfo: 'enum("normal", "break-all", "break-word", "keep-all")'
|
||||
},
|
||||
|
||||
{
|
||||
label: 'web',
|
||||
name: 'wordWrap',
|
||||
|
||||
@@ -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,11 +1,11 @@
|
||||
const createConfig = ({ modules }) => ({
|
||||
presets: [
|
||||
[
|
||||
'babel-preset-env',
|
||||
'@babel/preset-env',
|
||||
{
|
||||
loose: true,
|
||||
modules,
|
||||
exclude: ['transform-es2015-typeof-symbol'],
|
||||
exclude: ['transform-typeof-symbol'],
|
||||
targets: {
|
||||
browsers: [
|
||||
'chrome 38',
|
||||
@@ -24,17 +24,19 @@ const createConfig = ({ modules }) => ({
|
||||
}
|
||||
}
|
||||
],
|
||||
'babel-preset-react',
|
||||
'babel-preset-flow'
|
||||
'@babel/preset-react',
|
||||
'@babel/preset-flow'
|
||||
],
|
||||
plugins: [
|
||||
['babel-plugin-transform-class-properties', { loose: true }],
|
||||
['babel-plugin-transform-object-rest-spread', { useBuiltIns: true }],
|
||||
['babel-plugin-transform-react-remove-prop-types', { mode: 'wrap' }]
|
||||
'@babel/plugin-transform-flow-strip-types',
|
||||
['babel-plugin-transform-react-remove-prop-types', { mode: 'wrap' }],
|
||||
['@babel/plugin-proposal-class-properties', { loose: true }],
|
||||
['@babel/plugin-proposal-object-rest-spread', { useBuiltIns: true }],
|
||||
].concat(modules ? ['babel-plugin-add-module-exports'] : [])
|
||||
});
|
||||
|
||||
module.exports =
|
||||
process.env.BABEL_ENV === 'commonjs'
|
||||
module.exports = function() {
|
||||
return process.env.BABEL_ENV === 'commonjs' || process.env.NODE_ENV === 'test'
|
||||
? createConfig({ modules: 'commonjs' })
|
||||
: createConfig({ modules: false });
|
||||
};
|
||||
|
||||
@@ -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')
|
||||
});
|
||||
|
||||
@@ -11,7 +11,7 @@ module.exports = {
|
||||
rootDir: process.cwd(),
|
||||
roots: ['<rootDir>/packages'],
|
||||
setupFiles: ['jest-canvas-mock'],
|
||||
setupTestFrameworkScriptFile: require.resolve('./setupFramework.js'),
|
||||
setupFilesAfterEnv: [require.resolve('./setupFramework.js')],
|
||||
snapshotSerializers: ['enzyme-to-json/serializer'],
|
||||
testEnvironment: 'jsdom',
|
||||
timers: 'fake'
|
||||
|
||||
Reference in New Issue
Block a user