Compare commits

..

11 Commits

Author SHA1 Message Date
Nicolas Gallagher
ed70617e91 0.0.117 2017-07-30 20:13:29 -07:00
Nicolas Gallagher
134114de83 [fix] TextInput: call on onKeyPress for Tab and Cmd+Enter
Add support for more key combinations that only fire 'keydown' React DOM
events. And allow users to call 'preventDefault', etc., on the event.

Fix #567
Close #582
2017-07-30 20:11:19 -07:00
Nicolas Gallagher
08ee7c83bb Minor starting documentation improvement 2017-07-30 18:45:08 -07:00
Nicolas Gallagher
5fad78dcad [fix] remove unsupported TextInput props
For compatibility with React Native, do not pass on unsupported
TextInput props to avoid ReactDOM warnings.

Close #571
2017-07-29 17:30:31 -07:00
Nicolas Gallagher
e04343e48e Benchmarks: use glamor@2 and fix yarn.lock paths 2017-07-28 19:08:49 -07:00
Nicolas Gallagher
5e3a946f8b Upgrade docs to use @storybook/react 2017-07-28 15:29:39 -07:00
Nicolas Gallagher
4e3d8dbb02 [change] support CSS custom properties
Update 'setValueForStyles' and style validation to support defining and
using custom properties.

Fix #516
2017-07-28 15:29:07 -07:00
Nicolas Gallagher
fee909d26a [fix] AppState on the server
Fix #578
2017-07-27 16:30:28 -07:00
Nicolas Gallagher
9e58a7b5f1 Update GitHub files 2017-07-27 11:38:50 -07:00
Nicolas Gallagher
20e1febe21 0.0.116 2017-07-26 19:58:34 -07:00
Nicolas Gallagher
ef209ca281 Fix caniuse-api install, again 2017-07-26 19:55:43 -07:00
58 changed files with 2240 additions and 1171 deletions

View File

@@ -104,13 +104,17 @@ Please open an issue with a proposal for a new feature or refactoring before
starting on the work. We don't want you to waste your efforts on a pull request
that we won't want to accept.
## Submitting Changes
## Pull requests
* Open a new issue in the [Issue tracker](https://github.com/necolas/react-native-web/issues).
* Fork the repo.
* Create a new feature branch based off the `master` branch.
* Make sure all tests pass and there are no linting errors.
* Submit a pull request, referencing any issues it addresses.
**Before submitting a pull request,** please make sure the following is done:
1. Fork the repository and create your branch from `master`.
2. If you've added code that should be tested, add tests!
3. If you've changed APIs, update the documentation.
4. Ensure the tests pass (`yarn test`).
5. Lint and format your code (`yarn fmt && yarn lint`).
You can now submit a pull request, referencing any issues it addresses.
Please try to keep your pull request focused in scope and avoid including
unrelated commits.

View File

@@ -1,30 +1,20 @@
<!--
React Native for Web is an implementation of React Native. If you have feature
requests, you should post them to https://productpains.com/product/react-native/.
GitHub issues should only be used for bugs or Web-specific features you believe
React Native requires.
Make sure to add ALL the information needed to understand the bug so that
someone can help. If the info is missing we'll add the 'needs more information'
label and close the issue until there is enough information.
-->
**Do you want to request a *feature* or report a *bug*?**
**What is the current behavior?**
Link to minimal test case: (template: [codepen](https://codepen.io/necolas/pen/PZzwBR?editors=0010))
**What is the expected behaviour?**
**Steps to reproduce**
**If the current behavior is a bug, please provide the steps to reproduce and
if a minimal demo of the problem via Glitch or similar (template:
https://glitch.com/edit/#!/react-native-web-playground).**
1.
2.
**Environment (include versions)**
**What is the expected behavior?**
OS:
Device:
Browser:
React Native for Web (version):
React (version):
**Environment (include versions). Did this work in previous versions?**
* OS:
* Device:
* Browser:
* React Native for Web (version):
* React (version):

View File

@@ -1,18 +1 @@
<!--
Thanks for submitting a pull request! Make sure the PR does only one thing.
Please provide enough information so that others can review your pull
request. Make sure you have read the Contributing Guidelines -
https://github.com/necolas/react-native-web/CONTRIBUTING.md
-->
**This patch solves the following problem**
**Test plan**
**This pull request**
- [ ] includes documentation
- [ ] includes tests
- [ ] includes benchmark reports
- [ ] includes an interactive example
- [ ] includes screenshots/videos
**Before submitting a pull request,** please make sure you have followed the steps the CONTRIBUTING guide.

View File

@@ -25,14 +25,14 @@ Glitch.
## Quick start
To install in your app:
```
npm install --save react@15.6 react-dom@15.6 react-native-web
```
NOTE: React Native for Web supports React/ReactDOM 15.4, 15.5, or 15.6.
Install in your existing app using `yarn` or `npm`:
```
yarn add react@15.6 react-dom@15.6 react-native-web
```
Then read the [Getting Started](docs/guides/getting-started.md) guide.
## Documentation
@@ -50,6 +50,12 @@ Guides:
* [Advanced use](docs/guides/advanced.md)
* [Known issues](docs/guides/known-issues.md)
## Starter kits
* [create-react-app](https://github.com/facebookincubator/create-react-app) ([on Glitch](https://glitch.com/edit/#!/react-native-web-playground))
* [react-native-web-starter](https://github.com/grabcode/react-native-web-starter)
* [react-native-web-webpack](https://github.com/ndbroadbent/react-native-web-webpack)
## Example code
```js
@@ -96,12 +102,6 @@ AppRegistry.runApplication('MyApp', { rootTag: document.getElementById('react-ro
* [reactxp](https://github.com/microsoft/reactxp)
* [react-native-web-player](https://github.com/dabbott/react-native-web-player)
## Start kits
* [create-react-app](https://github.com/facebookincubator/create-react-app) ([on Glitch](https://glitch.com/edit/#!/react-native-web-playground))
* [react-native-web-starter](https://github.com/grabcode/react-native-web-starter)
* [react-native-web-webpack](https://github.com/ndbroadbent/react-native-web-webpack)
## License
React Native for Web is [BSD licensed](LICENSE).

View File

@@ -4,7 +4,7 @@
"dependencies": {
"aphrodite": "^1.2.3",
"classnames": "^2.2.5",
"glamor": "^3.0.0-3",
"glamor": "^2.20.37",
"marky": "^1.2.0",
"radium": "^0.19.1",
"react-jss": "^7.0.1",

View File

@@ -59,7 +59,11 @@ array-find@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/array-find/-/array-find-1.0.0.tgz#6c8e286d11ed768327f8e62ecee87353ca3e78b8"
asap@^2.0.3, asap@^2.0.5, asap@~2.0.3:
asap@^2.0.3, asap@^2.0.5:
version "2.0.6"
resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
asap@~2.0.3:
version "2.0.5"
resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.5.tgz#522765b50c3510490e52d7dcfe085ef9ba96958f"
@@ -89,8 +93,8 @@ babel-code-frame@^6.11.0:
js-tokens "^3.0.0"
babel-runtime@^6.23.0:
version "6.23.0"
resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.23.0.tgz#0a9489f144de70efb3ce4300accdb329e2fc543b"
version "6.25.0"
resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.25.0.tgz#33b98eaa5d482bb01a8d1aa6b437ad2b01aec41c"
dependencies:
core-js "^2.4.0"
regenerator-runtime "^0.10.0"
@@ -111,10 +115,14 @@ big.js@^3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.1.3.tgz#4cada2193652eb3ca9ec8e55c9015669c9806978"
bowser@^1.0.0, bowser@^1.6.0:
bowser@^1.0.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/bowser/-/bowser-1.6.0.tgz#37fc387b616cb6aef370dab4d6bd402b74c5c54d"
bowser@^1.6.0:
version "1.7.1"
resolved "https://registry.yarnpkg.com/bowser/-/bowser-1.7.1.tgz#a4de8f18a1a0dc9531eb2a92a1521fb6a9ba96a5"
brace-expansion@^1.1.7:
version "1.1.8"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292"
@@ -253,7 +261,7 @@ css-color-names@0.0.4:
css-in-js-utils@^1.0.3:
version "1.0.3"
resolved "https://artifactory.twitter.biz:443/api/npm/js-virtual/css-in-js-utils/-/css-in-js-utils-1.0.3.tgz#9ac7e02f763cf85d94017666565ed68a5b5f3215"
resolved "https://registry.yarnpkg.com/css-in-js-utils/-/css-in-js-utils-1.0.3.tgz#9ac7e02f763cf85d94017666565ed68a5b5f3215"
dependencies:
hyphenate-style-name "^1.0.2"
@@ -414,7 +422,19 @@ fastparse@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.1.tgz#d1e2643b38a94d7583b479060e6c4affc94071f8"
fbjs@^0.8.12, fbjs@^0.8.4, fbjs@^0.8.5, fbjs@^0.8.9:
fbjs@^0.8.12:
version "0.8.14"
resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.14.tgz#d1dbe2be254c35a91e09f31f9cd50a40b2a0ed1c"
dependencies:
core-js "^1.0.0"
isomorphic-fetch "^2.1.1"
loose-envify "^1.0.0"
object-assign "^4.1.0"
promise "^7.1.1"
setimmediate "^1.0.5"
ua-parser-js "^0.7.9"
fbjs@^0.8.4, fbjs@^0.8.5, fbjs@^0.8.9:
version "0.8.12"
resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.12.tgz#10b5d92f76d45575fd63a217d4ea02bea2f8ed04"
dependencies:
@@ -442,13 +462,14 @@ function-bind@^1.0.2:
version "1.1.0"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.0.tgz#16176714c801798e4e8f2cf7f7529467bb4a5771"
glamor@^3.0.0-3:
version "3.0.0-3"
resolved "https://registry.yarnpkg.com/glamor/-/glamor-3.0.0-3.tgz#62e1cf2ce70a4db0b247a5f95d4b7d6a89ac83c9"
glamor@^2.20.37:
version "2.20.37"
resolved "https://registry.yarnpkg.com/glamor/-/glamor-2.20.37.tgz#3cd576deb760eaaf475dcc1f9b77bf6a7d2f8b26"
dependencies:
fbjs "^0.8.12"
inline-style-prefixer "^3.0.3"
react-css-property-operations "^15.4.1"
inline-style-prefixer "^3.0.6"
object-assign "^4.1.1"
prop-types "^15.5.10"
glob@^7.0.5:
version "7.1.2"
@@ -537,7 +558,7 @@ inline-style-prefixer@^2.0.1, inline-style-prefixer@^2.0.5:
bowser "^1.0.0"
hyphenate-style-name "^1.0.1"
inline-style-prefixer@^3.0.1, inline-style-prefixer@^3.0.3, inline-style-prefixer@^3.0.6:
inline-style-prefixer@^3.0.1, inline-style-prefixer@^3.0.6:
version "3.0.6"
resolved "https://registry.yarnpkg.com/inline-style-prefixer/-/inline-style-prefixer-3.0.6.tgz#b27fe309b4168a31eaf38c8e8c60ab9e7c11731f"
dependencies:
@@ -1090,10 +1111,6 @@ react-addons-perf@^15.4.2:
fbjs "^0.8.4"
object-assign "^4.1.0"
react-css-property-operations@^15.4.1:
version "15.4.1"
resolved "https://registry.yarnpkg.com/react-css-property-operations/-/react-css-property-operations-15.4.1.tgz#4c0e305df4cc35f0f5fd2d65a79214c8b012db35"
react-jss@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/react-jss/-/react-jss-7.0.1.tgz#36c505c3798993edd46ea01734f171f895348e25"
@@ -1105,8 +1122,8 @@ react-jss@^7.0.1:
theming "^1.1.0"
react-native-web@0.0.x:
version "0.0.106"
resolved "https://registry.yarnpkg.com/react-native-web/-/react-native-web-0.0.106.tgz#928427320b5963548b372a32b62459154f1e1d7e"
version "0.0.116"
resolved "https://registry.yarnpkg.com/react-native-web/-/react-native-web-0.0.116.tgz#e05e376b34617a54d61826e4bc06b0bdbfd3f4b2"
dependencies:
animated "^0.2.0"
array-find-index "^1.0.2"
@@ -1184,8 +1201,8 @@ regenerate@^1.2.1:
resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.2.tgz#d1941c67bad437e1be76433add5b385f95b19260"
regenerator-runtime@^0.10.0:
version "0.10.3"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.3.tgz#8c4367a904b51ea62a908ac310bf99ff90a82a3e"
version "0.10.5"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz#336c3efc1220adcedda2c9fab67b5a7955a33658"
regexpu-core@^1.0.0:
version "1.0.0"
@@ -1249,7 +1266,7 @@ strict-uri-encode@^1.0.0:
string-hash@^1.1.3:
version "1.1.3"
resolved "https://artifactory.twitter.biz:443/api/npm/js-virtual/string-hash/-/string-hash-1.1.3.tgz#e8aafc0ac1855b4666929ed7dd1275df5d6c811b"
resolved "https://registry.yarnpkg.com/string-hash/-/string-hash-1.1.3.tgz#e8aafc0ac1855b4666929ed7dd1275df5d6c811b"
strip-ansi@^3.0.0:
version "3.0.1"

View File

@@ -1,25 +1,22 @@
# Getting started
This guide will help you to correctly configure build and test tools to work
with React Native for Web.
Alternatively, you can quickly setup a local project using
[create-react-app](https://github.com/facebookincubator/create-react-app)
(which supports `react-native-web` out-of-the-box once installed),
[react-native-web-starter](https://github.com/grabcode/react-native-web-starter),
or [react-native-web-webpack](https://github.com/ndbroadbent/react-native-web-webpack).
with React Native for Web. (Alternatively, you can quickly setup a local
project using the starter kits listed in the README.)
It is recommended that your application provide a `Promise` and `Array.from`
polyfill.
## Webpack and Babel
## Web packager
[Webpack](https://webpack.js.org) is a popular build tool for web apps. Below is an
example of how to configure a build that uses [Babel](https://babeljs.io/) to
compile your JavaScript for the web.
Create a `web/webpack.config.js` file:
```js
// webpack.config.js
// web/webpack.config.js
// This is needed for webpack to compile JavaScript.
// Many OSS React Native packages are not compiled to ES5 before being
@@ -37,7 +34,7 @@ const babelLoaderConfiguration = {
loader: 'babel-loader',
options: {
cacheDirectory: true,
// The 'react-native' preset is recommended
// The 'react-native' preset is recommended (or use your own .babelrc)
presets: ['react-native']
}
}
@@ -66,7 +63,8 @@ module.exports = {
plugins: [
// `process.env.NODE_ENV === 'production'` must be `true` for production
// builds to eliminate development checks and reduce build size.
// builds to eliminate development checks and reduce build size. You may
// wish to include additional optimizations.
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production')
})
@@ -85,36 +83,97 @@ module.exports = {
}
```
Please refer to the Webpack documentation for more information.
## Jest
[Jest](https://facebook.github.io/jest/) also needs to map `react-native` to `react-native-web`.
To run in development:
```
"jest": {
"moduleNameMapper": {
"react-native": "<rootDir>/node_modules/react-native-web"
}
}
./node_modules/.bin/webpack-dev-server -d --config web/webpack.config.js --inline --hot --colors
```
Please refer to the Jest documentation for more information.
To build for production:
```
./node_modules/.bin/webpack -p --config web/webpack.config.js
```
Please refer to the Webpack documentation for more information on configuration.
## Web entry
Create a `index.web.js` file (or simply `index.js` for web-only apps).
### Client-side rendering
Rendering using `AppRegistry`:
```js
// index.web.js
import App from './src/App';
import React from 'react';
import ReactNative, { AppRegistry } from 'react-native';
// register the app
AppRegistry.registerComponent('App', () => App);
AppRegistry.runApplication('App', {
initialProps: {},
rootTag: document.getElementById('react-app')
});
```
Rendering within existing web apps is also possible using `ReactNative`:
```js
import AppHeader from './src/AppHeader';
import React from 'react';
import ReactNative from 'react-native';
ReactNative.render(<AppHeader />, document.getElementById('react-app-header'))
```
And finally, `react-native-web` components will also be rendering within a tree
produced by calling `ReactDOM.render` (i.e., an existing web app), but
otherwise it is not recommended.
### Server-side rendering
Server-side rendering is supported using the `AppRegistry`:
```js
import App from './src/App';
import ReactDOMServer from 'react-dom/server'
import ReactNative, { AppRegistry } from 'react-native'
// register the app
AppRegistry.registerComponent('App', () => App)
// prerender the app
const { element, stylesheets } = AppRegistry.getApplication('App', { initialProps });
const initialHTML = ReactDOMServer.renderToString(element);
const initialStyles = stylesheets.map((sheet) => ReactDOMServer.renderToStaticMarkup(sheet)).join('\n');
// construct HTML document
const document = `
<!DOCTYPE html>
<html>
<head>
${initialStyles}
</head>
<body>
${initialHTML}
`
```
## Web-specific code
Minor platform differences can use the `Platform` module.
```js
import { AppRegistry, Platform } from 'react-native'
import { Platform } from 'react-native';
AppRegistry.registerComponent('MyApp', () => MyApp)
if (Platform.OS === 'web') {
AppRegistry.runApplication('MyApp', {
rootTag: document.getElementById('react-root')
});
}
const styles = StyleSheet.create({
height: (Platform.OS === 'web') ? 200 : 100,
});
```
More significant platform differences should use platform-specific files (see
@@ -137,68 +196,16 @@ import MyComponent from './MyComponent';
React Native will automatically import the correct variant for each specific
target platform.
## Client-side rendering
## Testing with Jest
Rendering using `ReactNative`:
[Jest](https://facebook.github.io/jest/) also needs to map `react-native` to `react-native-web`.
```js
import React from 'react'
import ReactNative from 'react-native'
// component that renders the app
const AppHeaderContainer = (props) => { /* ... */ }
ReactNative.render(<AppHeaderContainer />, document.getElementById('react-app-header'))
```
"jest": {
"moduleNameMapper": {
"react-native": "<rootDir>/node_modules/react-native-web"
}
}
```
Rendering using `AppRegistry`:
```js
import React from 'react'
import ReactNative, { AppRegistry } from 'react-native'
// component that renders the app
const AppContainer = (props) => { /* ... */ }
// register the app
AppRegistry.registerComponent('App', () => AppContainer)
AppRegistry.runApplication('App', {
initialProps: {},
rootTag: document.getElementById('react-app')
})
```
Rendering within `ReactDOM.render` also works when introducing
`react-native-web` to an existing web app, but otherwise it is not recommended.
## Server-side rendering
Rendering using the `AppRegistry`:
```js
import ReactDOMServer from 'react-dom/server'
import ReactNative, { AppRegistry } from 'react-native'
// component that renders the app
const AppContainer = (props) => { /* ... */ }
// register the app
AppRegistry.registerComponent('App', () => AppContainer)
// prerender the app
const { element, stylesheets } = AppRegistry.getApplication('App', { initialProps });
const initialHTML = ReactDOMServer.renderToString(element);
const initialStyles = stylesheets.map((sheet) => ReactDOMServer.renderToStaticMarkup(sheet)).join('\n');
// construct HTML document
const document = `
<!DOCTYPE html>
<html>
<head>
${initialStyles}
</head>
<body>
${initialHTML}
`
```
Please refer to the Jest documentation for more information.

View File

@@ -3,11 +3,11 @@
"private": true,
"scripts": {
"build": "yarn && build-storybook -o ./dist -c ./storybook/.storybook",
"start": "start-storybook -p 9001 -c ./storybook/.storybook --dont-track",
"start": "start-storybook -p 9001 -c ./storybook/.storybook",
"publish": "yarn build && git checkout gh-pages && rm -rf ./storybook && mv docs/dist storybook && git add -A && git commit -m \"Storybook deploy\" && git push origin gh-pages && git checkout -"
},
"dependencies": {
"@kadira/storybook": "^2.35.3",
"@kadira/storybook-addon-options": "^1.0.2"
"@storybook/addon-options": "^3.1.6",
"@storybook/react": "^3.1.9"
}
}

View File

@@ -1 +1 @@
import '@kadira/storybook-addon-options/register';
import '@storybook/addon-options/register';

View File

@@ -1,6 +1,6 @@
import { setOptions } from '@kadira/storybook-addon-options';
import { setOptions } from '@storybook/addon-options';
import centered from './decorator-centered';
import { configure, addDecorator } from '@kadira/storybook';
import { configure, addDecorator } from '@storybook/react';
const context = require.context('../', true, /Screen\.js$/);

View File

@@ -1,33 +1,36 @@
const path = require('path');
const webpack = require('webpack');
const DEV = process.env.NODE_ENV !== 'production';
module.exports = (storybookBaseConfig, configType) => {
const DEV = configType === 'DEVELOPMENT';
module.exports = {
module: {
loaders: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
query: { cacheDirectory: true }
},
{
test: /\.(gif|jpe?g|png|svg)$/,
loader: 'url-loader',
query: { name: '[name].[ext]' }
}
]
},
plugins: [
storybookBaseConfig.module.rules.push({
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: { cacheDirectory: true }
}
});
storybookBaseConfig.module.rules.push({
test: /\.(gif|jpe?g|png|svg)$/,
use: {
loader: 'url-loader',
options: { name: '[name].[ext]' }
}
});
storybookBaseConfig.plugins.push(
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),
'process.env.__REACT_NATIVE_DEBUG_ENABLED__': DEV
})
],
resolve: {
alias: {
'react-native': path.join(__dirname, '../../../src/module')
}
}
);
storybookBaseConfig.resolve.alias = {
'react-native': path.join(__dirname, '../../../src/module')
};
return storybookBaseConfig;
};

View File

@@ -9,8 +9,7 @@ import PropColor from './examples/PropColor';
import PropHidesWhenStopped from './examples/PropHidesWhenStopped';
import PropSize from './examples/PropSize';
import React from 'react';
import { storiesOf } from '@kadira/storybook';
import UIExplorer, { Description, DocItem, Section } from '../../ui-explorer';
import UIExplorer, { Description, DocItem, Section, storiesOf } from '../../ui-explorer';
const ActivityIndicatorScreen = () =>
<UIExplorer title="ActivityIndicator" url="components/ActivityIndicator">

View File

@@ -8,8 +8,14 @@ import React from 'react';
import PropColor from './examples/PropColor';
import PropDisabled from './examples/PropDisabled';
import PropOnPress from './examples/PropOnPress';
import UIExplorer, { AppText, Code, Description, DocItem, Section } from '../../ui-explorer';
import { storiesOf } from '@kadira/storybook';
import UIExplorer, {
AppText,
Code,
Description,
DocItem,
Section,
storiesOf
} from '../../ui-explorer';
const ButtonScreen = () =>
<UIExplorer title="Button" url="components/Button">

View File

@@ -16,8 +16,14 @@ import PropResizeMode from './examples/PropResizeMode';
import PropSource from './examples/PropSource';
import StaticGetSizeExample from './examples/StaticGetSize';
import StaticPrefetchExample from './examples/StaticPrefetch';
import { storiesOf } from '@kadira/storybook';
import UIExplorer, { AppText, Code, Description, DocItem, Section } from '../../ui-explorer';
import UIExplorer, {
AppText,
Code,
Description,
DocItem,
Section,
storiesOf
} from '../../ui-explorer';
const ImageScreen = () =>
<UIExplorer title="Image" url="components/Image">

View File

@@ -8,8 +8,7 @@ import PropIndeterminate from './examples/PropIndeterminate';
import PropProgress from './examples/PropProgress';
import PropTrackColor from './examples/PropTrackColor';
import React from 'react';
import { storiesOf } from '@kadira/storybook';
import UIExplorer, { Description, DocItem, Section } from '../../ui-explorer';
import UIExplorer, { Description, DocItem, Section, storiesOf } from '../../ui-explorer';
const ProgressBarScreen = () =>
<UIExplorer title="ProgressBar" url="components/ProgressBar">

View File

@@ -8,13 +8,13 @@ import { HorizontalExample } from './examples/Horizontal';
import ScrollToExample from './examples/ScrollTo';
import ScrollToEndExample from './examples/ScrollToEnd';
import React from 'react';
import { storiesOf } from '@kadira/storybook';
import UIExplorer, {
AppText,
Code,
Description,
DocItem,
Section,
storiesOf,
TextList
} from '../../ui-explorer';

View File

@@ -5,10 +5,11 @@
*/
import React from 'react';
import { action } from '@kadira/storybook';
import { ScrollView, StyleSheet, Text, View } from 'react-native';
const onScroll = action('ScrollView.onScroll');
const onScroll = () => {
console.log('ScrollView.onScroll');
};
const VerticalExample = () =>
<View style={styles.scrollViewContainer}>

View File

@@ -13,8 +13,14 @@ import PropThumbColor from './examples/PropThumbColor';
import PropTrackColor from './examples/PropTrackColor';
import PropValue from './examples/PropValue';
import React from 'react';
import { storiesOf } from '@kadira/storybook';
import UIExplorer, { AppText, Code, Description, DocItem, Section } from '../../ui-explorer';
import UIExplorer, {
AppText,
Code,
Description,
DocItem,
Section,
storiesOf
} from '../../ui-explorer';
const SwitchScreen = () =>
<UIExplorer title="Switch" url="components/Switch">

View File

@@ -8,13 +8,13 @@ import PropChildren from './examples/PropChildren';
import PropNumberOfLines from './examples/PropNumberOfLines';
import PropOnPress from './examples/PropOnPress';
import React from 'react';
import { storiesOf } from '@kadira/storybook';
import UIExplorer, {
AppText,
Code,
Description,
DocItem,
Section,
storiesOf,
StyleList
} from '../../ui-explorer';

View File

@@ -5,8 +5,7 @@
/*
import createReactClass from 'create-react-class';
import React from 'react';
import { storiesOf } from '@kadira/storybook';
import UIExplorer, { PropText, StyleList } from '../../ui-explorer';
import UIExplorer, { PropText, storiesOf, StyleList } from '../../ui-explorer';
import { Image, Text, View } from 'react-native';
const AttributeToggler = createReactClass({

View File

@@ -19,13 +19,13 @@ import PropSelectTextOnFocus from './examples/PropSelectTextOnFocus';
import TextInputEvents from './examples/TextInputEvents';
import TextInputRewrite, { TextInputRewriteInvalidCharacters } from './examples/Rewrite';
import React from 'react';
import { storiesOf } from '@kadira/storybook';
import UIExplorer, {
AppText,
Code,
Description,
DocItem,
Section,
storiesOf,
StyleList,
TextList
} from '../../ui-explorer';
@@ -226,6 +226,12 @@ nativeEvent: { key: keyValue } }`}</Code>{' '}
}
/>
<DocItem
name="onLayout"
typeInfo="?function"
description="Invoked on mount and layout changes with {x, y, width, height}."
/>
<DocItem
name="onSelectionChange"
typeInfo="?function"

View File

@@ -8,9 +8,8 @@ import CustomStyleOverrides from './examples/CustomStyleOverrides';
import DelayEvents from './examples/DelayEvents';
import FeedbackEvents from './examples/FeedbackEvents';
import React from 'react';
import { storiesOf } from '@kadira/storybook';
import { TouchableHighlightDisabled } from './examples/PropDisabled';
import UIExplorer, { AppText, Description, DocItem, Section } from '../../ui-explorer';
import UIExplorer, { AppText, Description, DocItem, Section, storiesOf } from '../../ui-explorer';
const TouchableHighlightScreen = () =>
<UIExplorer title="TouchableHighlight" url="components/Touchable">

View File

@@ -7,9 +7,8 @@
import DelayEvents from './examples/DelayEvents';
import FeedbackEvents from './examples/FeedbackEvents';
import React from 'react';
import { storiesOf } from '@kadira/storybook';
import { TouchableOpacityDisabled } from './examples/PropDisabled';
import UIExplorer, { AppText, Description, DocItem, Section } from '../../ui-explorer';
import UIExplorer, { AppText, Description, DocItem, Section, storiesOf } from '../../ui-explorer';
const TouchableOpacityScreen = () =>
<UIExplorer title="TouchableOpacity" url="components/Touchable">

View File

@@ -8,9 +8,15 @@ import DelayEvents from './examples/DelayEvents';
import FeedbackEvents from './examples/FeedbackEvents';
import React from 'react';
import PropHitSlop from './examples/PropHitSlop';
import { storiesOf } from '@kadira/storybook';
import { TouchableWithoutFeedbackDisabled } from './examples/PropDisabled';
import UIExplorer, { AppText, Code, Description, DocItem, Section } from '../../ui-explorer';
import UIExplorer, {
AppText,
Code,
Description,
DocItem,
Section,
storiesOf
} from '../../ui-explorer';
const TouchableWithoutFeedbackScreen = () =>
<UIExplorer title="TouchableWithoutFeedback" url="components/Touchable">

View File

@@ -3,7 +3,6 @@
*/
import React from 'react';
import { action } from '@kadira/storybook';
import {
StyleSheet,
View,
@@ -13,6 +12,10 @@ import {
TouchableWithoutFeedback
} from 'react-native';
const action = msg => () => {
console.log(msg);
};
class TouchableHighlightDisabled extends React.Component {
render() {
return (

View File

@@ -8,13 +8,14 @@ import PropPointerEvents from './examples/PropPointerEvents';
import transformExamples from './examples/transforms';
import ZIndexExample from './examples/ZIndex';
import React from 'react';
import { storiesOf } from '@kadira/storybook';
import UIExplorer, {
AppText,
Code,
Description,
DocItem,
ExternalLink,
Section,
storiesOf,
StyleList
} from '../../ui-explorer';
@@ -291,6 +292,12 @@ const ViewScreen = () =>
</UIExplorer>;
const stylePropTypes = [
{
label: 'web',
name: (
<ExternalLink href="https://drafts.csswg.org/css-variables/">Custom properties</ExternalLink>
)
},
{
name: 'alignContent',
typeInfo: 'string'

View File

@@ -3,8 +3,14 @@
*/
import React from 'react';
import { storiesOf } from '@kadira/storybook';
import UIExplorer, { AppText, Code, Description, DocItem, Section } from '../../ui-explorer';
import UIExplorer, {
AppText,
Code,
Description,
DocItem,
Section,
storiesOf
} from '../../ui-explorer';
const AppRegistryScreen = () =>
<UIExplorer title="AppRegistry" url="apis/AppRegistry">

View File

@@ -6,8 +6,14 @@
import React from 'react';
import StateChangesExample from './examples/StateChanges';
import { storiesOf } from '@kadira/storybook';
import UIExplorer, { AppText, Code, Description, DocItem, Section } from '../../ui-explorer';
import UIExplorer, {
AppText,
Code,
Description,
DocItem,
Section,
storiesOf
} from '../../ui-explorer';
const AppStateScreen = () =>
<UIExplorer title="AppState" url="apis/AppState">

View File

@@ -3,8 +3,14 @@
*/
import React from 'react';
import { storiesOf } from '@kadira/storybook';
import UIExplorer, { AppText, Code, Description, DocItem, Section } from '../../ui-explorer';
import UIExplorer, {
AppText,
Code,
Description,
DocItem,
Section,
storiesOf
} from '../../ui-explorer';
const AsyncStorageScreen = () =>
<UIExplorer title="AsyncStorage" url="apis/AsyncStorage">

View File

@@ -4,8 +4,7 @@
import React from 'react';
import SetStringExample from './examples/SetString';
import { storiesOf } from '@kadira/storybook';
import UIExplorer, { Description, DocItem, Section } from '../../ui-explorer';
import UIExplorer, { Description, DocItem, Section, storiesOf } from '../../ui-explorer';
const ClipboardScreen = () =>
<UIExplorer title="Clipboard" url="apis/Clipboard">

View File

@@ -5,13 +5,13 @@
*/
import DimensionsChange from './examples/DimensionsChange';
import { storiesOf } from '@kadira/storybook';
import UIExplorer, {
AppText,
Code,
Description,
DocItem,
Section,
storiesOf,
TextList
} from '../../ui-explorer';
import React from 'react';

View File

@@ -6,8 +6,7 @@
import RTLToggle from './examples/RTLToggle';
import React from 'react';
import { storiesOf } from '@kadira/storybook';
import UIExplorer, { Description, DocItem, Section } from '../../ui-explorer';
import UIExplorer, { Description, DocItem, Section, storiesOf } from '../../ui-explorer';
const I18nManagerScreen = () =>
<UIExplorer title="I18nManager" url="apis/I18nManager">

View File

@@ -6,8 +6,7 @@
import OpenURL from './examples/OpenURL';
import React from 'react';
import { storiesOf } from '@kadira/storybook';
import UIExplorer, { Description, DocItem, Section } from '../../ui-explorer';
import UIExplorer, { Description, DocItem, Section, storiesOf } from '../../ui-explorer';
const LinkingScreen = () =>
<UIExplorer title="Linking" url="apis/Linking">

View File

@@ -3,13 +3,13 @@
*/
import React from 'react';
import { storiesOf } from '@kadira/storybook';
import UIExplorer, {
AppText,
Code,
Description,
DocItem,
Section,
storiesOf,
TextList
} from '../../ui-explorer';

View File

@@ -3,9 +3,8 @@
*/
import DraggableCircleExample from './examples/DraggableCircle';
import { storiesOf } from '@kadira/storybook';
import React from 'react';
import UIExplorer, { AppText, Description, DocItem, Section } from '../../ui-explorer';
import UIExplorer, { AppText, Description, DocItem, Section, storiesOf } from '../../ui-explorer';
const PanResponderScreen = () =>
<UIExplorer title="PanResponder" url="apis/PanResponder">

View File

@@ -3,8 +3,14 @@
*/
import React from 'react';
import { storiesOf } from '@kadira/storybook';
import UIExplorer, { AppText, Code, Description, DocItem, Section } from '../../ui-explorer';
import UIExplorer, {
AppText,
Code,
Description,
DocItem,
Section,
storiesOf
} from '../../ui-explorer';
const PixelRatioScreen = () =>
<UIExplorer title="PixelRatio" url="apis/PixelRatio">

View File

@@ -2,9 +2,8 @@
* @flow
*/
import { storiesOf } from '@kadira/storybook';
import React from 'react';
import UIExplorer, { Description, DocItem, Section } from '../../ui-explorer';
import UIExplorer, { Description, DocItem, Section, storiesOf } from '../../ui-explorer';
const PlatformScreen = () =>
<UIExplorer title="Platform" url="apis/Platform">

View File

@@ -2,9 +2,15 @@
* @flow
*/
import { storiesOf } from '@kadira/storybook';
import React from 'react';
import UIExplorer, { AppText, Code, Description, DocItem, Section } from '../../ui-explorer';
import UIExplorer, {
AppText,
Code,
Description,
DocItem,
Section,
storiesOf
} from '../../ui-explorer';
const StyleSheetScreen = () =>
<UIExplorer title="StyleSheet" url="apis/StyleSheet">

View File

@@ -2,9 +2,15 @@
* @flow
*/
import { storiesOf } from '@kadira/storybook';
import React from 'react';
import UIExplorer, { AppText, Code, Description, DocItem, Section } from '../../ui-explorer';
import UIExplorer, {
AppText,
Code,
Description,
DocItem,
Section,
storiesOf
} from '../../ui-explorer';
const VibrationScreen = () =>
<UIExplorer title="Vibration" url="apis/Vibration">

View File

@@ -1,6 +1,6 @@
import Calculator from './Calculator';
import React from 'react';
import { storiesOf } from '@kadira/storybook';
import { storiesOf } from '../../ui-explorer';
import { StyleSheet, View } from 'react-native';
const CalculatorScreen = () =>

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { storiesOf } from '@kadira/storybook';
import { storiesOf } from '../../ui-explorer';
import Game2048 from './Game2048';
const Game2048Screen = () => <Game2048 />;

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { storiesOf } from '@kadira/storybook';
import { storiesOf } from '../../ui-explorer';
import TicTacToe from './TicTacToe';
const TicTacToeScreen = () => <TicTacToe />;

View File

@@ -0,0 +1,12 @@
/* eslint-disable react/prop-types */
/**
* @flow
*/
import AppText from './AppText';
import React from 'react';
const ExternalLink = props => <AppText {...props} accessibilityRole="link" target="_blank" />;
export default ExternalLink;

View File

@@ -16,10 +16,12 @@ const StyleList = ({ stylePropTypes }) =>
<Text style={styles.name}>
{name}
</Text>
{': '}
<Text style={styles.code}>
{typeInfo}
</Text>
{typeInfo ? ': ' : null}
{typeInfo
? <Text style={styles.code}>
{typeInfo}
</Text>
: null}
</AppText>
)}
</View>;

View File

@@ -5,6 +5,7 @@
*/
import AppText from './AppText';
import ExternalLink from './ExternalLink';
import insertBetween from './insertBetween';
import React from 'react';
import { StyleSheet, View } from 'react-native';
@@ -22,14 +23,12 @@ export const Description = ({ children }) =>
const Divider = () => <View style={styles.divider} />;
const SourceLink = ({ uri }) =>
<AppText
accessibilityRole="link"
<ExternalLink
href={`https://github.com/necolas/react-native-web/tree/master/docs/storybook/${uri}`}
style={styles.link}
target="_blank"
>
View source code on GitHub
</AppText>;
</ExternalLink>;
const UIExplorer = ({ children, description, sections, title, url }) =>
<View style={styles.root}>

View File

@@ -5,10 +5,22 @@
import AppText from './AppText';
import Code from './Code';
import DocItem from './DocItem';
import ExternalLink from './ExternalLink';
import Section from './Section';
import { storiesOf } from '@storybook/react';
import StyleList from './StyleList';
import TextList from './TextList';
import UIExplorer, { Description } from './UIExplorer';
export default UIExplorer;
export { AppText, Code, Description, DocItem, Section, StyleList, TextList };
export {
AppText,
Code,
Description,
DocItem,
ExternalLink,
Section,
storiesOf,
StyleList,
TextList
};

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "react-native-web",
"version": "0.0.115",
"version": "0.0.117",
"description": "React Native for Web",
"main": "dist/index.js",
"files": [

View File

@@ -10,12 +10,13 @@
* @noflow
*/
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment';
import { canUseDOM } from 'fbjs/lib/ExecutionEnvironment';
import findIndex from 'array-find-index';
import invariant from 'fbjs/lib/invariant';
// Android 4.4 browser
const isPrefixed = !document.hasOwnProperty('hidden') && document.hasOwnProperty('webkitHidden');
const isPrefixed =
canUseDOM && !document.hasOwnProperty('hidden') && document.hasOwnProperty('webkitHidden');
const EVENT_TYPES = ['change'];
const VISIBILITY_CHANGE_EVENT = isPrefixed ? 'webkitvisibilitychange' : 'visibilitychange';
@@ -29,7 +30,7 @@ const AppStates = {
const listeners = [];
export default class AppState {
static isAvailable = ExecutionEnvironment.canUseDOM && document[VISIBILITY_STATE_PROPERTY];
static isAvailable = canUseDOM && document[VISIBILITY_STATE_PROPERTY];
static get currentState() {
if (!AppState.isAvailable) {

View File

@@ -26,23 +26,27 @@ const ReactPropTypesSecret = 'SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED';
export default class StyleSheetValidation {
static validateStyleProp(prop, style, caller) {
if (process.env.NODE_ENV !== 'production') {
const isCustomProperty = prop.indexOf('--') === 0;
if (isCustomProperty) return;
if (allStylePropTypes[prop] === undefined) {
const message1 = '"' + prop + '" is not a valid style property.';
const message2 =
'\nValid style props: ' +
JSON.stringify(Object.keys(allStylePropTypes).sort(), null, ' ');
styleError(message1, style, caller, message2);
}
const error = allStylePropTypes[prop](
style,
prop,
caller,
'prop',
null,
ReactPropTypesSecret
);
if (error) {
styleError(error.message, style, caller);
} else {
const error = allStylePropTypes[prop](
style,
prop,
caller,
'prop',
null,
ReactPropTypesSecret
);
if (error) {
styleError(error.message, style, caller);
}
}
}
}

View File

@@ -109,105 +109,165 @@ describe('components/TextInput', () => {
expect(input.prop('rows')).toEqual(3);
});
test('prop "onBlur"', done => {
test('prop "onBlur"', () => {
const onBlur = jest.fn();
const input = findNativeInput(mount(<TextInput onBlur={onBlur} />));
input.simulate('blur');
function onBlur(e) {
expect(e).toBeTruthy();
done();
}
expect(onBlur).toHaveBeenCalledTimes(1);
});
test('prop "onChange"', done => {
test('prop "onChange"', () => {
const onChange = jest.fn();
const input = findNativeInput(mount(<TextInput onChange={onChange} />));
input.simulate('change');
function onChange(e) {
expect(e).toBeTruthy();
done();
}
expect(onChange).toHaveBeenCalledTimes(1);
});
test('prop "onChangeText"', done => {
test('prop "onChangeText"', () => {
const onChangeText = jest.fn();
const newText = 'newText';
const input = findNativeInput(mount(<TextInput onChangeText={onChangeText} />));
input.simulate('change', { target: { value: newText } });
function onChangeText(text) {
expect(text).toEqual(newText);
done();
}
expect(onChangeText).toHaveBeenCalledTimes(1);
expect(onChangeText).toBeCalledWith(newText);
});
test('prop "onFocus"', done => {
test('prop "onFocus"', () => {
const onFocus = jest.fn();
const input = findNativeInput(mount(<TextInput onFocus={onFocus} />));
input.simulate('focus');
function onFocus(e) {
expect(e).toBeTruthy();
done();
}
expect(onFocus).toHaveBeenCalledTimes(1);
});
describe('prop "onKeyPress"', () => {
test('enter key', done => {
const input = findNativeInput(mount(<TextInput onKeyPress={onKeyPress} />));
input.simulate('keyPress', { which: 13 });
function onKeyPress(e) {
expect(e.nativeEvent.key).toEqual('Enter');
done();
}
});
test('space key', done => {
const input = findNativeInput(mount(<TextInput onKeyPress={onKeyPress} />));
input.simulate('keyPress', { which: 32 });
function onKeyPress(e) {
expect(e.nativeEvent.key).toEqual(' ');
done();
}
});
test('backspace key', done => {
test('backspace key', () => {
const onKeyPress = jest.fn();
const input = findNativeInput(mount(<TextInput onKeyPress={onKeyPress} />));
input.simulate('keyDown', { which: 8 });
function onKeyPress(e) {
expect(e.nativeEvent.key).toEqual('Backspace');
done();
}
expect(onKeyPress).toHaveBeenCalledTimes(1);
expect(onKeyPress).toBeCalledWith(
expect.objectContaining({
nativeEvent: {
altKey: undefined,
ctrlKey: undefined,
key: 'Backspace',
metaKey: undefined,
shiftKey: undefined,
target: expect.anything()
}
})
);
});
test('text key', done => {
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('text key', () => {
const onKeyPress = jest.fn();
const input = findNativeInput(mount(<TextInput onKeyPress={onKeyPress} />));
input.simulate('keyPress', { which: 97 });
function onKeyPress(e) {
expect(e.nativeEvent.key).toEqual('a');
done();
}
expect(onKeyPress).toHaveBeenCalledTimes(1);
expect(onKeyPress).toBeCalledWith(
expect.objectContaining({
nativeEvent: {
altKey: undefined,
ctrlKey: undefined,
key: 'a',
metaKey: undefined,
shiftKey: undefined,
target: expect.anything()
}
})
);
});
test('target element is included', done => {
const input = findNativeInput(mount(<TextInput onKeyPress={onKeyPress} />));
input.simulate('keyPress');
function onKeyPress(e) {
expect(e.nativeEvent.target).toBeDefined();
done();
}
});
test('modifier keys are included', done => {
test('modifier keys are included', () => {
const onKeyPress = jest.fn();
const input = findNativeInput(mount(<TextInput onKeyPress={onKeyPress} />));
input.simulate('keyPress', {
altKey: true,
ctrlKey: true,
metaKey: true,
shiftKey: true,
which: 97
which: 32
});
function onKeyPress(e) {
expect(e.nativeEvent.altKey).toEqual(true);
expect(e.nativeEvent.ctrlKey).toEqual(true);
expect(e.nativeEvent.metaKey).toEqual(true);
expect(e.nativeEvent.shiftKey).toEqual(true);
done();
}
expect(onKeyPress).toHaveBeenCalledTimes(1);
expect(onKeyPress).toBeCalledWith(
expect.objectContaining({
nativeEvent: {
altKey: true,
ctrlKey: true,
key: ' ',
metaKey: true,
shiftKey: true,
target: expect.anything()
}
})
);
});
test('meta key + Enter calls "onKeyPress"', () => {
const onKeyPress = jest.fn();
const input = findNativeInput(mount(<TextInput onKeyPress={onKeyPress} />));
input.simulate('keyDown', {
metaKey: true,
which: 13
});
expect(onKeyPress).toHaveBeenCalledTimes(1);
});
});

View File

@@ -14,6 +14,7 @@ import applyLayout from '../../modules/applyLayout';
import applyNativeMethods from '../../modules/applyNativeMethods';
import { canUseDOM } from 'fbjs/lib/ExecutionEnvironment';
import { Component } from 'react';
import ColorPropType from '../../propTypes/ColorPropType';
import createDOMElement from '../../modules/createDOMElement';
import findNodeHandle from '../../modules/findNodeHandle';
import StyleSheet from '../../apis/StyleSheet';
@@ -21,7 +22,7 @@ import StyleSheetPropType from '../../propTypes/StyleSheetPropType';
import TextInputStylePropTypes from './TextInputStylePropTypes';
import TextInputState from './TextInputState';
import ViewPropTypes from '../View/ViewPropTypes';
import { bool, func, number, oneOf, shape, string } from 'prop-types';
import { any, bool, func, number, oneOf, shape, string } from 'prop-types';
const isAndroid = canUseDOM && /Android/i.test(navigator && navigator.userAgent);
const emptyObject = {};
@@ -103,7 +104,6 @@ class TextInput extends Component {
onSelectionChange: func,
onSubmitEditing: func,
placeholder: string,
placeholderTextColor: string,
secureTextEntry: bool,
selectTextOnFocus: bool,
selection: shape({
@@ -111,7 +111,29 @@ class TextInput extends Component {
end: number
}),
style: StyleSheetPropType(TextInputStylePropTypes),
value: string
value: string,
/* react-native compat */
/* eslint-disable */
caretHidden: bool,
clearButtonMode: string,
dataDetectorTypes: string,
disableFullscreenUI: bool,
enablesReturnKeyAutomatically: bool,
keyboardAppearance: string,
inlineImageLeft: string,
inlineImagePadding: number,
onContentSizeChange: func,
onEndEditing: func,
onScroll: func,
placeholderTextColor: ColorPropType,
returnKeyLabel: string,
returnKeyType: string,
selectionColor: ColorPropType,
selectionState: any,
spellCheck: bool,
textBreakStrategy: string,
underlineColorAndroid: ColorPropType
/* eslint-enable */
};
static defaultProps = {
@@ -163,23 +185,30 @@ class TextInput extends Component {
style,
/* eslint-disable */
blurOnSubmit,
caretHidden,
clearButtonMode,
clearTextOnFocus,
dataDetectorTypes,
enablesReturnKeyAutomatically,
keyboardAppearance,
onChangeText,
onContentSizeChange,
onEndEditing,
onLayout,
onSelectionChange,
onSubmitEditing,
placeholderTextColor,
returnKeyType,
selection,
selectionColor,
selectTextOnFocus,
/* react-native compat */
caretHidden,
clearButtonMode,
dataDetectorTypes,
disableFullscreenUI,
enablesReturnKeyAutomatically,
inlineImageLeft,
inlineImagePadding,
keyboardAppearance,
onContentSizeChange,
onEndEditing,
onScroll,
placeholderTextColor,
returnKeyLabel,
returnKeyType,
selectionColor,
selectionState,
spellCheck,
textBreakStrategy,
underlineColorAndroid,
/* eslint-enable */
@@ -272,9 +301,9 @@ class TextInput extends Component {
};
_handleKeyDown = e => {
const { onKeyPress } = this.props;
if (onKeyPress && e.which === 8) {
onKeyPress({ nativeEvent: { key: 'Backspace' } });
// Backspace, Tab, and Cmd+Enter only fire 'keydown' DOM events
if (e.which === 8 || e.which === 9 || (e.which === 13 && e.metaKey)) {
this._handleKeyPress(e);
}
};
@@ -285,23 +314,35 @@ class TextInput extends Component {
if (onKeyPress) {
let keyValue;
// enter
if (e.which === 13) {
keyValue = 'Enter';
} else if (e.which === 32) {
// space
keyValue = ' ';
} else {
// we trim to only care about the keys that has a textual representation
if (e.shiftKey) {
keyValue = String.fromCharCode(e.which).trim();
} else {
keyValue = String.fromCharCode(e.which).toLowerCase().trim();
switch (e.which) {
// backspace
case 8:
keyValue = 'Backspace';
break;
// tab
case 9:
keyValue = 'Tab';
break;
// enter
case 13:
keyValue = 'Enter';
break;
// spacebar
case 32:
keyValue = ' ';
break;
default: {
// we trim to only care about the keys that has a textual representation
if (e.shiftKey) {
keyValue = String.fromCharCode(e.which).trim();
} else {
keyValue = String.fromCharCode(e.which).toLowerCase().trim();
}
}
}
if (keyValue) {
const nativeEvent = {
e.nativeEvent = {
altKey: e.altKey,
ctrlKey: e.ctrlKey,
key: keyValue,
@@ -309,7 +350,7 @@ class TextInput extends Component {
shiftKey: e.shiftKey,
target: e.target
};
onKeyPress({ nativeEvent });
onKeyPress(e);
}
}

View File

@@ -35,7 +35,8 @@ const colorPropType = function(isRequired, props, propName, componentName, locat
return;
}
if (color === 'currentcolor' || color === 'inherit') {
// Web supports additional color keywords and custom property values
if (color === 'currentcolor' || color === 'inherit' || color.indexOf('var(') === 0) {
return;
}

View File

@@ -19,7 +19,15 @@ function StyleSheetPropType(shape: { [key: string]: ReactPropsCheckType }): Reac
if (props[propName]) {
// Just make a dummy prop object with only the flattened style
newProps = {};
newProps[propName] = StyleSheet.flatten(props[propName]);
const flatStyle = StyleSheet.flatten(props[propName]);
// Remove custom properties from check
const nextStyle = Object.keys(flatStyle).reduce((acc, curr) => {
if (curr.indexOf('--') !== 0) {
acc[curr] = flatStyle[curr];
}
return acc;
}, {});
newProps[propName] = nextStyle;
}
return shapePropType(newProps, propName, componentName, location, ...rest);
};

53
src/vendor/dangerousStyleValue/index.js vendored Normal file
View File

@@ -0,0 +1,53 @@
/* eslint-disable */
/**
* Copyright 2013-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule dangerousStyleValue
*/
import isUnitlessNumber from '../../modules/unitlessNumbers';
/**
* Convert a value into the proper css writable value. The style name `name`
* should be logical (no hyphens), as specified
* in `CSSProperty.isUnitlessNumber`.
*
* @param {string} name CSS property name such as `topMargin`.
* @param {*} value CSS property value such as `10px`.
* @return {string} Normalized style value with dimensions applied.
*/
function dangerousStyleValue(name, value, isCustomProperty) {
// Note that we've removed escapeTextForBrowser() calls here since the
// whole string will be escaped when the attribute is injected into
// the markup. If you provide unsafe user data here they can inject
// arbitrary CSS which may be problematic (I couldn't repro this):
// https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet
// http://www.thespanner.co.uk/2007/11/26/ultimate-xss-css-injection/
// This is not an XSS hole but instead a potential CSS injection issue
// which has lead to a greater discussion about how we're going to
// trust URLs moving forward. See #2115901
var isEmpty = value == null || typeof value === 'boolean' || value === '';
if (isEmpty) {
return '';
}
if (
!isCustomProperty &&
typeof value === 'number' &&
value !== 0 &&
!(isUnitlessNumber.hasOwnProperty(name) && isUnitlessNumber[name])
) {
return value + 'px'; // Presumes implicit 'px' suffix for unitless numbers
}
return ('' + value).trim();
}
export default dangerousStyleValue;

View File

@@ -10,199 +10,8 @@
*
*/
import unitlessNumbers from '../../modules/unitlessNumbers';
if (process.env.NODE_ENV !== 'production') {
var camelizeStyleName = require('fbjs/lib/camelizeStyleName');
var warning = require('fbjs/lib/warning');
// 'msTransform' is correct, but the other prefixes should be capitalized
var badVendoredStyleNamePattern = /^(?:webkit|moz|o)[A-Z]/;
// style values shouldn't contain a semicolon
var badStyleValueWithSemicolonPattern = /;\s*$/;
var warnedStyleNames = {};
var warnedStyleValues = {};
var warnedForNaNValue = false;
var warnHyphenatedStyleName = function(name, owner) {
if (warnedStyleNames.hasOwnProperty(name) && warnedStyleNames[name]) {
return;
}
warnedStyleNames[name] = true;
process.env.NODE_ENV !== 'production'
? warning(
false,
'Unsupported style property %s. Did you mean %s?%s',
name,
camelizeStyleName(name),
checkRenderMessage(owner)
)
: void 0;
};
var warnBadVendoredStyleName = function(name, owner) {
if (warnedStyleNames.hasOwnProperty(name) && warnedStyleNames[name]) {
return;
}
warnedStyleNames[name] = true;
process.env.NODE_ENV !== 'production'
? warning(
false,
'Unsupported vendor-prefixed style property %s. Did you mean %s?%s',
name,
name.charAt(0).toUpperCase() + name.slice(1),
checkRenderMessage(owner)
)
: void 0;
};
var warnStyleValueWithSemicolon = function(name, value, owner) {
if (warnedStyleValues.hasOwnProperty(value) && warnedStyleValues[value]) {
return;
}
warnedStyleValues[value] = true;
process.env.NODE_ENV !== 'production'
? warning(
false,
"Style property values shouldn't contain a semicolon.%s " + 'Try "%s: %s" instead.',
checkRenderMessage(owner),
name,
value.replace(badStyleValueWithSemicolonPattern, '')
)
: void 0;
};
var warnStyleValueIsNaN = function(name, value, owner) {
if (warnedForNaNValue) {
return;
}
warnedForNaNValue = true;
process.env.NODE_ENV !== 'production'
? warning(
false,
'`NaN` is an invalid value for the `%s` css style property.%s',
name,
checkRenderMessage(owner)
)
: void 0;
};
var checkRenderMessage = function(owner) {
if (owner) {
var name = owner.getName();
if (name) {
return ' Check the render method of `' + name + '`.';
}
}
return '';
};
/**
* @param {string} name
* @param {*} value
* @param {ReactDOMComponent} component
*/
var warnValidStyle = function(name, value, component) {
var owner;
if (component) {
owner = component._currentElement._owner;
}
if (name.indexOf('-') > -1) {
warnHyphenatedStyleName(name, owner);
} else if (badVendoredStyleNamePattern.test(name)) {
warnBadVendoredStyleName(name, owner);
} else if (badStyleValueWithSemicolonPattern.test(value)) {
warnStyleValueWithSemicolon(name, value, owner);
}
if (typeof value === 'number' && isNaN(value)) {
warnStyleValueIsNaN(name, value, owner);
}
};
}
var styleWarnings = {};
/**
* Convert a value into the proper css writable value. The style name `name`
* should be logical (no hyphens)
*
* @param {string} name CSS property name such as `topMargin`.
* @param {*} value CSS property value such as `10px`.
* @param {ReactDOMComponent} component
* @return {string} Normalized style value with dimensions applied.
*/
function dangerousStyleValue(name, value, component) {
// Note that we've removed escapeTextForBrowser() calls here since the
// whole string will be escaped when the attribute is injected into
// the markup. If you provide unsafe user data here they can inject
// arbitrary CSS which may be problematic (I couldn't repro this):
// https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet
// http://www.thespanner.co.uk/2007/11/26/ultimate-xss-css-injection/
// This is not an XSS hole but instead a potential CSS injection issue
// which has lead to a greater discussion about how we're going to
// trust URLs moving forward. See #2115901
var isEmpty = value == null || typeof value === 'boolean' || value === '';
if (isEmpty) {
return '';
}
var isNonNumeric = isNaN(value);
if (
isNonNumeric ||
value === 0 ||
(unitlessNumbers.hasOwnProperty(name) && unitlessNumbers[name])
) {
return '' + value; // cast to string
}
if (typeof value === 'string') {
if (process.env.NODE_ENV !== 'production') {
var warning = require('fbjs/lib/warning');
// Allow '0' to pass through without warning. 0 is already special and
// doesn't require units, so we don't need to warn about it.
if (component && value !== '0') {
var owner = component._currentElement._owner;
var ownerName = owner ? owner.getName() : null;
if (ownerName && !styleWarnings[ownerName]) {
styleWarnings[ownerName] = {};
}
var warned = false;
if (ownerName) {
var warnings = styleWarnings[ownerName];
warned = warnings[name];
if (!warned) {
warnings[name] = true;
}
}
if (!warned) {
process.env.NODE_ENV !== 'production'
? warning(
false,
'a `%s` tag (owner: `%s`) was passed a numeric string value ' +
'for CSS property `%s` (value: `%s`) which will be treated ' +
'as a unitless number in a future version of React.',
component._currentElement.type,
ownerName || 'unknown',
name,
value
)
: void 0;
}
}
}
value = value.trim();
}
return value + 'px';
}
import dangerousStyleValue from '../dangerousStyleValue';
import warnValidStyle from '../warnValidStyle';
/**
* Sets the value for multiple styles on a node. If a value is specified as
@@ -218,14 +27,19 @@ const setValueForStyles = function(node, styles, component) {
if (!styles.hasOwnProperty(styleName)) {
continue;
}
var isCustomProperty = styleName.indexOf('--') === 0;
if (process.env.NODE_ENV !== 'production') {
warnValidStyle(styleName, styles[styleName], component);
if (!isCustomProperty) {
warnValidStyle(styleName, styles[styleName], component);
}
}
var styleValue = dangerousStyleValue(styleName, styles[styleName], component);
if (styleName === 'float' || styleName === 'cssFloat') {
var styleValue = dangerousStyleValue(styleName, styles[styleName], isCustomProperty);
if (styleName === 'float') {
styleName = 'cssFloat';
}
if (styleValue) {
if (isCustomProperty) {
style.setProperty(styleName, styleValue);
} else if (styleValue) {
style[styleName] = styleValue;
} else {
style[styleName] = '';

170
src/vendor/warnValidStyle/index.js vendored Normal file
View File

@@ -0,0 +1,170 @@
/* eslint-disable */
/**
* Copyright 2013-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule warnValidStyle
*/
'use strict';
var emptyFunction = require('fbjs/lib/emptyFunction');
var warnValidStyle = emptyFunction;
if (process.env.NODE_ENV !== 'production') {
var camelizeStyleName = require('fbjs/lib/camelizeStyleName');
var warning = require('fbjs/lib/warning');
function getComponentName(instanceOrFiber) {
if (typeof instanceOrFiber.getName === 'function') {
// Stack reconciler
const instance = ((instanceOrFiber: any): ReactInstance);
return instance.getName();
}
if (typeof instanceOrFiber.tag === 'number') {
// Fiber reconciler
const fiber = ((instanceOrFiber: any): Fiber);
const { type } = fiber;
if (typeof type === 'string') {
return type;
}
if (typeof type === 'function') {
return type.displayName || type.name;
}
}
return null;
}
// 'msTransform' is correct, but the other prefixes should be capitalized
var badVendoredStyleNamePattern = /^(?:webkit|moz|o)[A-Z]/;
// style values shouldn't contain a semicolon
var badStyleValueWithSemicolonPattern = /;\s*$/;
var warnedStyleNames = {};
var warnedStyleValues = {};
var warnedForNaNValue = false;
var warnedForInfinityValue = false;
var warnHyphenatedStyleName = function(name, owner) {
if (warnedStyleNames.hasOwnProperty(name) && warnedStyleNames[name]) {
return;
}
warnedStyleNames[name] = true;
warning(
false,
'Unsupported style property %s. Did you mean %s?%s',
name,
camelizeStyleName(name),
checkRenderMessage(owner)
);
};
var warnBadVendoredStyleName = function(name, owner) {
if (warnedStyleNames.hasOwnProperty(name) && warnedStyleNames[name]) {
return;
}
warnedStyleNames[name] = true;
warning(
false,
'Unsupported vendor-prefixed style property %s. Did you mean %s?%s',
name,
name.charAt(0).toUpperCase() + name.slice(1),
checkRenderMessage(owner)
);
};
var warnStyleValueWithSemicolon = function(name, value, owner) {
if (warnedStyleValues.hasOwnProperty(value) && warnedStyleValues[value]) {
return;
}
warnedStyleValues[value] = true;
warning(
false,
"Style property values shouldn't contain a semicolon.%s " + 'Try "%s: %s" instead.',
checkRenderMessage(owner),
name,
value.replace(badStyleValueWithSemicolonPattern, '')
);
};
var warnStyleValueIsNaN = function(name, value, owner) {
if (warnedForNaNValue) {
return;
}
warnedForNaNValue = true;
warning(
false,
'`NaN` is an invalid value for the `%s` css style property.%s',
name,
checkRenderMessage(owner)
);
};
var warnStyleValueIsInfinity = function(name, value, owner) {
if (warnedForInfinityValue) {
return;
}
warnedForInfinityValue = true;
warning(
false,
'`Infinity` is an invalid value for the `%s` css style property.%s',
name,
checkRenderMessage(owner)
);
};
var checkRenderMessage = function(owner) {
var ownerName;
if (owner != null) {
// Stack passes the owner manually all the way to CSSPropertyOperations.
ownerName = getComponentName(owner);
} else {
// Fiber doesn't pass it but uses ReactDebugCurrentFiber to track it.
// It is only enabled in development and tracks host components too.
// var {getCurrentFiberOwnerName} = require('ReactDebugCurrentFiber');
// ownerName = getCurrentFiberOwnerName();
// TODO: also report the stack.
}
if (ownerName) {
return '\n\nCheck the render method of `' + ownerName + '`.';
}
return '';
};
warnValidStyle = function(name, value, component) {
var owner;
if (component) {
// TODO: this only works with Stack. Seems like we need to add unit tests?
// owner = component._currentElement._owner;
}
if (name.indexOf('-') > -1) {
warnHyphenatedStyleName(name, owner);
} else if (badVendoredStyleNamePattern.test(name)) {
warnBadVendoredStyleName(name, owner);
} else if (badStyleValueWithSemicolonPattern.test(value)) {
warnStyleValueWithSemicolon(name, value, owner);
}
if (typeof value === 'number') {
if (isNaN(value)) {
warnStyleValueIsNaN(name, value, owner);
} else if (!isFinite(value)) {
warnStyleValueIsInfinity(name, value, owner);
}
}
};
}
export default warnValidStyle;

View File

@@ -896,7 +896,7 @@ browserify-zlib@^0.1.4:
browserslist@^2.0.0:
version "2.2.2"
resolved "https://artifactory.twitter.biz:443/api/npm/js-virtual/browserslist/-/browserslist-2.2.2.tgz#e9b4618b8a01c193f9786beea09f6fd10dbe31c3"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-2.2.2.tgz#e9b4618b8a01c193f9786beea09f6fd10dbe31c3"
dependencies:
caniuse-lite "^1.0.30000704"
electron-to-chromium "^1.3.16"
@@ -985,7 +985,7 @@ caniuse-api@^2.0.0:
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000704:
version "1.0.30000706"
resolved "https://artifactory.twitter.biz:443/api/npm/js-virtual/caniuse-lite/-/caniuse-lite-1.0.30000706.tgz#bc59abc41ba7d4a3634dda95befded6114e1f24e"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000706.tgz#bc59abc41ba7d4a3634dda95befded6114e1f24e"
capture-stack-trace@^1.0.0:
version "1.0.0"
@@ -1575,7 +1575,7 @@ ejs@^2.5.6:
electron-to-chromium@^1.3.16:
version "1.3.16"
resolved "https://artifactory.twitter.biz:443/api/npm/js-virtual/electron-to-chromium/-/electron-to-chromium-1.3.16.tgz#d0e026735754770901ae301a21664cba45d92f7d"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.16.tgz#d0e026735754770901ae301a21664cba45d92f7d"
elegant-spinner@^1.0.1:
version "1.0.1"
@@ -3317,7 +3317,7 @@ lodash.map@^4.4.0:
lodash.memoize@^4.1.2:
version "4.1.2"
resolved "https://artifactory.twitter.biz:443/api/npm/js-virtual/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
lodash.merge@^4.4.0:
version "4.6.0"
@@ -3349,7 +3349,7 @@ lodash.some@^4.4.0:
lodash.uniq@^4.5.0:
version "4.5.0"
resolved "https://artifactory.twitter.biz:443/api/npm/js-virtual/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
lodash@^4.0.0, lodash@^4.14.0, lodash@^4.17.4, lodash@^4.3.0, lodash@^4.6.1:
version "4.17.4"