Compare commits

..

34 Commits

Author SHA1 Message Date
Nicolas Gallagher
24836afd6a 0.0.26 2016-06-28 16:38:31 -07:00
Nicolas Gallagher
c46f242f6b [add] ReactDOM server API to ReactNative API 2016-06-28 16:38:21 -07:00
Nicolas Gallagher
1940868065 [fix] TextInput support for Text styles
Fix #81
Fix #133
2016-06-28 15:55:27 -07:00
Nicolas Gallagher
65a9317756 [fix] TextInput placeholder layout and focus
Fix the layout of placeholder text and shift focus to the DOM input when
`TextInput` is clicked or pressed.

Thanks to @tuckerconnelly and @Dremora.

Fix #138
Fix #119
Close #137
2016-06-28 15:04:38 -07:00
Nicolas Gallagher
3da05c48b0 [fix] support 'onClick' prop in 'View' 2016-06-28 15:04:17 -07:00
Nicolas Gallagher
f33312a4dd [change] Use animatedjs/animated
Depend on 'animatedjs/animated' for the Animation implementation. This
patch also replaces 'es6-set' with a small shim to reduce impact on
production bundle size.

Fix #95
2016-06-23 15:10:43 -07:00
Nicolas Gallagher
4516c72296 Use enzyme for Image tests 2016-06-23 13:52:08 -07:00
Nicolas Gallagher
7f94c4bf06 Install enzyme
Fix #83
2016-06-23 13:50:06 -07:00
Nicolas Gallagher
37781171aa [add] more ReactNative exports 2016-06-23 10:16:45 -07:00
Nicolas Gallagher
22f45e350b [fix] React@15: remove inline-style fallback values
React 15 has no way to handle fallback CSS values (for example, vendor
prefixed 'display:flex' values) in inline styles. This patch drops all
fallback values for inline styles at the cost of regressing browser
support (those without standard flexbox support will not layout React
Native components correctly).

Fix #131
2016-06-22 16:13:48 -07:00
Nicolas Gallagher
af40f98f23 [fix] AppState event handler registration
Fix #151
2016-06-22 15:41:35 -07:00
Nicolas Gallagher
eca2f69593 Remove a React error from test report 2016-06-21 14:59:45 -07:00
Nicolas Gallagher
d03d89ac71 [fix] CoreComponent -> createNativeComponent
'CoreComponent' creates new component instances and clutters the React
component tree during debugging. This patch converts 'CoreComponent' to
a simple function that creates a native web element.

This patch also includes a fix for use of the 'flexShrink' style on
'View'.

Fix #140
2016-06-21 14:59:38 -07:00
Nicolas Gallagher
393a6ef835 [fix] don't use 'bind' in JSX props 2016-06-20 11:31:38 -07:00
Nicolas Gallagher
36e89d5275 [fix] installation on Windows
Fix #114
2016-06-20 11:22:42 -07:00
Nicolas Gallagher
d53d1e6e56 Add link to react-native-web-starter 2016-06-20 11:09:50 -07:00
Nicolas Gallagher
2cb68a45be [add] Platform.select 2016-06-18 16:43:22 -07:00
Nicolas Gallagher
b56b8e494a Update various packages (inc. babel and eslint) 2016-06-14 16:05:30 -07:00
Nicolas Gallagher
60ad0e9ec5 Further fixes to examples following react@15 update 2016-06-14 15:56:10 -07:00
Nicolas Gallagher
f2ea7c089c [change] separate the React and React Native APIs
Fix #136
2016-06-14 13:47:47 -07:00
Nicolas Gallagher
a3b59ed2b4 [fix] Touchable with React@15
Fix #123
2016-06-14 13:47:39 -07:00
Nicolas Gallagher
a378d3cce2 [change] update to React@15 2016-06-14 13:04:30 -07:00
Nicolas Gallagher
462f9793ea Fix code style issue 2016-06-13 15:05:03 -07:00
Monir Abu Hilal
ae38bb538c Do not treat lineHeight as a unitless numbers
Match the behavior of react-native for iOS and Android

The browser treats the 'line-height' CSS property as an 'em' value,
while react-native treats it as pixel unit (or device unit, which should
be 'px' for the web), this issue is causing the 'TextInput' component to
be sized incorrectly.

Close #142
2016-06-13 12:04:53 -07:00
Nicolas Gallagher
93d1488cc7 Fix README link to View 2016-06-13 11:59:24 -07:00
Nicolas Gallagher
a16e542bd8 [fix] don't replace 'className' value 2016-06-13 11:58:05 -07:00
Nicolas Gallagher
62cd335788 [fix] TouchableHighlight default underlay style 2016-06-13 11:57:02 -07:00
Nicolas Gallagher
288e14cd70 Remove MediaQueryWidget from examples 2016-06-13 11:56:15 -07:00
Nicolas Gallagher
71cfd23624 0.0.25 2016-04-29 12:49:13 -07:00
Nicolas Gallagher
77b8e4a1fc [fix] pin inline-style-prefix-all
Version 1.1.0 contains a breaking change
2016-04-29 12:48:40 -07:00
Nicolas Gallagher
9543a79c3f 0.0.24 2016-04-20 11:38:38 -07:00
Nicolas Gallagher
e3eea6e132 [fix] TouchableHighlight
The fix in 97c0a31ce6 was incomplete due
to state key not being renamed.
2016-04-20 11:37:15 -07:00
Nicolas Gallagher
4d3418a968 0.0.23 2016-04-19 17:11:26 -07:00
Nicolas Gallagher
ea9bc734f1 [fix] TouchableWithoutFeedback
Fix #127
2016-04-19 17:10:50 -07:00
67 changed files with 751 additions and 2946 deletions

View File

@@ -1,7 +1,6 @@
language: node_js
node_js:
- "5"
- "4"
- "6"
before_script:
- export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start

View File

@@ -20,7 +20,7 @@ vendor prefixes), or require build tool configuration. This project allows
components built upon React Native to be run on the Web, and it manages all
component styling out-of-the-box.
For example, the [`View`](docs/apis/View.md) component makes it easy to build
For example, the [`View`](docs/components/View.md) component makes it easy to build
cross-browser layouts with flexbox, such as stacked and nested boxes with
margin and padding. And the [`StyleSheet`](docs/guides/style.md) API converts
styles defined in JavaScript into "Atomic CSS".
@@ -30,11 +30,14 @@ styles defined in JavaScript into "Atomic CSS".
To install in your app:
```
npm install --save react@0.14 react-dom@0.14 react-native-web
npm install --save react@0.15 react-native-web
```
Read the [Client and Server rendering](docs/guides/rendering.md) guide.
You can also bootstrap a standard React Native project structure for web by
using [react-native-web-starter](https://github.com/grabcode/react-native-web-starter).
## Examples
Demos:
@@ -46,7 +49,8 @@ Demos:
Sample:
```js
import React, { AppRegistry, Image, StyleSheet, Text, View } from 'react-native'
import React from 'react'
import { AppRegistry, Image, StyleSheet, Text, View } from 'react-native'
// Components
const Card = ({ children }) => <View style={styles.card}>{children}</View>

View File

@@ -37,7 +37,6 @@ class Example extends React.Component {
constructor(props) {
super(props)
this.state = { currentAppState: AppState.currentState }
this._handleAppStateChange = this._handleAppStateChange.bind(this)
}
componentDidMount() {
@@ -48,7 +47,7 @@ class Example extends React.Component {
AppState.removeEventListener('change', this._handleAppStateChange);
}
_handleAppStateChange(currentAppState) {
_handleAppStateChange = (currentAppState) => {
this.setState({ currentAppState });
}

View File

@@ -10,19 +10,36 @@ specific.
`Platform.OS` will be `web` when running in a Web browser.
**userAgent**: string
On Web, the `Platform` module can be also be used to detect the browser
`userAgent`.
## Examples
```js
import { Platform } from 'react-native';
const styles = StyleSheet.create({
height: (Platform.OS === 'web') ? 200 : 100,
});
if (Platform.userAgent.includes('Android')) {
console.log('Running on Android!');
}
```
## Methods
**select**: any
`Platform.select` takes an object containing `Platform.OS` as keys and returns
the value for the platform you are currently running on.
```js
import { Platform } from 'react-native';
const containerStyles = {
flex: 1,
...Platform.select({
android: {
backgroundColor: 'blue'
},
ios: {
backgroundColor: 'red'
},
web: {
backgroundColor: 'green'
}
})
});
```

View File

@@ -23,7 +23,8 @@ Size of the indicator. Small has a height of `20`, large has a height of `36`.
## Examples
```js
import React, { ActivityIndicator, Component, StyleSheet, View } from 'react-native'
import React, { Component } from 'react'
import { ActivityIndicator, StyleSheet, View } from 'react-native'
class ToggleAnimatingActivityIndicator extends Component {
constructor(props) {

View File

@@ -78,7 +78,8 @@ Example usage:
```js
import placeholderAvatar from './placeholderAvatar.png'
import React, { Component, Image, PropTypes, StyleSheet } from 'react-native'
import React, { Component } from 'react'
import { Image, PropTypes, StyleSheet } from 'react-native'
export default class ImageExample extends Component {
constructor(props, context) {

View File

@@ -15,7 +15,8 @@ Content to display over the image.
## Examples
```js
import React, { Component, ListView, PropTypes } from 'react-native'
import React, { Component, PropTypes } from 'react'
import { ListView } from 'react-native'
export default class ListViewExample extends Component {
static propTypes = {}

View File

@@ -33,7 +33,8 @@ React component to render. This same tag can later be used in `closeModal`.
## Examples
```js
import React, { Portal, Text, Touchable } from 'react-native'
import React, { Component } from 'react'
import { Portal, Text, Touchable } from 'react-native'
export default class PortalExample extends Component {
componentWillMount() {

View File

@@ -83,7 +83,8 @@ Scrolls to a given `x`, `y` offset (animation is not currently supported).
## Examples
```js
import React, { Component, ScrollView, StyleSheet } from 'react-native'
import React, { Component } from 'react'
import { ScrollView, StyleSheet } from 'react-native'
import Item from './Item'
export default class ScrollViewExample extends Component {

View File

@@ -75,7 +75,8 @@ Used to locate this view in end-to-end tests.
## Examples
```js
import React, { Component, PropTypes, StyleSheet, Text } from 'react-native'
import React, { Component, PropTypes } from 'react'
import { StyleSheet, Text } from 'react-native'
export default class PrettyText extends Component {
static propTypes = {

View File

@@ -164,7 +164,8 @@ Focus the underlying DOM input.
## Examples
```js
import React, { Component, StyleSheet, TextInput } from 'react-native'
import React, { Component } from 'react'
import { StyleSheet, TextInput } from 'react-native'
export default class TextInputExample extends Component {
constructor(props, context) {

View File

@@ -184,7 +184,8 @@ Used to locate this view in end-to-end tests.
## Examples
```js
import React, { Component, PropTypes, StyleSheet, View } from 'react-native'
import React, { Component, PropTypes } from 'react'
import { StyleSheet, View } from 'react-native'
export default class ViewExample extends Component {
render() {

View File

@@ -40,11 +40,7 @@ module.exports = {
Minor platform differences can use the `Platform` module.
```js
import { AppRegistry, Platform, StyleSheet } from 'react-native'
const styles = StyleSheet.create({
height: (Platform.OS === 'web') ? 200 : 100
})
import { AppRegistry, Platform } from 'react-native'
AppRegistry.registerComponent('MyApp', () => MyApp)

View File

@@ -21,14 +21,14 @@ module.exports = {
Rendering without using the `AppRegistry`:
```js
import React from 'react-native'
import ReactNative from 'react-native'
// DOM render
React.render(<div />, document.getElementById('react-app'))
ReactNative.render(<div />, document.getElementById('react-app'))
// Server render
React.renderToString(<div />)
React.renderToStaticMarkup(<div />)
ReactNative.renderToString(<div />)
ReactNative.renderToStaticMarkup(<div />)
```
Rendering using the `AppRegistry`:
@@ -36,7 +36,7 @@ Rendering using the `AppRegistry`:
```js
// App.js
import React, { AppRegistry } from 'react-native'
import React from 'react'
// component that renders the app
const AppContainer = (props) => { /* ... */ }
@@ -46,14 +46,17 @@ export default AppContainer
```js
// client.js
import React, { AppRegistry } from 'react-native'
import App from './App'
import { AppRegistry } from 'react-native'
// registers the app
AppRegistry.registerComponent('App', () => App)
// mounts and runs the app within the `rootTag` DOM node
AppRegistry.runApplication('App', { initialProps, rootTag: document.getElementById('react-app') })
AppRegistry.runApplication('App', {
initialProps: {},
rootTag: document.getElementById('react-app')
})
```
React Native for Web extends `AppRegistry` to provide support for server-side
@@ -62,7 +65,7 @@ rendering.
```js
// AppShell.js
import React from 'react-native'
import React from 'react'
const AppShell = (html, styleElement) => (
<html>
@@ -82,9 +85,9 @@ export default AppShell
```js
// server.js
import React, { AppRegistry } from 'react-native'
import App from './App'
import AppShell from './AppShell'
import ReactNative, { AppRegistry } from 'react-native'
// registers the app
AppRegistry.registerComponent('App', () => App)
@@ -93,5 +96,5 @@ AppRegistry.registerComponent('App', () => App)
const { html, style, styleElement } = AppRegistry.prerenderApplication('App', { initialProps })
// renders the full-page markup
const renderedApplicationHTML = React.renderToStaticMarkup(<AppShell html={html} styleElement={styleElement} />)
const renderedApplicationHTML = ReactNative.renderToStaticMarkup(<AppShell html={html} styleElement={styleElement} />)
```

View File

@@ -16,7 +16,8 @@
*/
'use strict';
var React = require('react-native');
var React = require('react');
var ReactNative = require('react-native');
var {
Animated,
AppRegistry,
@@ -24,7 +25,7 @@ var {
Text,
TouchableBounce,
View,
} = React;
} = ReactNative;
var GameBoard = require('./GameBoard');

View File

@@ -16,14 +16,15 @@
*/
'use strict';
var React = require('react-native');
var React = require('react');
var ReactNative = require('react-native');
var {
AppRegistry,
StyleSheet,
Text,
TouchableHighlight,
View,
} = React;
} = ReactNative;
class Board {
grid: Array<Array<number>>;

View File

@@ -1,27 +1,23 @@
import GridView from './GridView'
import Heading from './Heading'
import MediaQueryWidget from './MediaQueryWidget'
import React, { Image, StyleSheet, ScrollView, Text, TextInput, TouchableHighlight, View } from 'react-native'
import React from 'react'
import { Image, StyleSheet, ScrollView, Text, TextInput, TouchableHighlight, View } from 'react-native'
export default class App extends React.Component {
static propTypes = {
mediaQuery: React.PropTypes.object,
style: View.propTypes.style
}
constructor(...args) {
super(...args)
constructor(props) {
super(props)
this.state = {
scrollEnabled: true
}
}
render() {
const { mediaQuery } = this.props
const finalRootStyles = [
rootStyles.common,
mediaQuery.small.matches && rootStyles.mqSmall,
mediaQuery.large.matches && rootStyles.mqLarge
rootStyles.common
]
return (
@@ -35,8 +31,6 @@ export default class App extends React.Component {
scroll views from which more complex components and apps can be
constructed.</Text>
<MediaQueryWidget mediaQuery={mediaQuery} />
<Heading size='large'>Image</Heading>
<Image
accessibilityLabel='accessible image'
@@ -96,7 +90,10 @@ export default class App extends React.Component {
/>
<TextInput secureTextEntry />
<TextInput defaultValue='read only' editable={false} />
<TextInput keyboardType='email-address' placeholder='you@domain.com' placeholderTextColor='red' />
<TextInput
style={{ flex:1, height: 60, padding: 20, fontSize: 20, textAlign: 'center' }}
keyboardType='email-address' placeholder='you@domain.com' placeholderTextColor='red'
/>
<TextInput keyboardType='numeric' />
<TextInput keyboardType='phone-pad' />
<TextInput defaultValue='https://delete-me' keyboardType='url' placeholder='https://www.some-website.com' selectTextOnFocus />

View File

@@ -1,4 +1,5 @@
import React, { Component, PropTypes, StyleSheet, View } from 'react-native'
import React, { Component, PropTypes } from 'react'
import { StyleSheet, View } from 'react-native'
export default class GridView extends Component {
static propTypes = {
@@ -12,8 +13,8 @@ export default class GridView extends Component {
}
static defaultProps = {
alley: '0',
gutter: '0'
alley: '0px',
gutter: '0px'
}
render() {

View File

@@ -1,4 +1,5 @@
import React, { StyleSheet, Text } from 'react-native'
import React from 'react'
import { StyleSheet, Text } from 'react-native'
const Heading = ({ children, size = 'normal' }) => (
<Text

View File

@@ -1,39 +0,0 @@
import React, { StyleSheet, Text, View } from 'react-native'
const MediaQueryWidget = ({ mediaQuery = {} }) => {
const active = Object.keys(mediaQuery).reduce((active, alias) => {
if (mediaQuery[alias].matches) {
active = {
alias,
mql: mediaQuery[alias]
}
}
return active
}, {})
return (
<View style={styles.root}>
<Text style={styles.heading}>Active Media Query</Text>
<Text style={styles.text}>{`"${active.alias}"`} {active.mql && active.mql.media}</Text>
</View>
)
}
const styles = StyleSheet.create({
root: {
alignItems: 'center',
borderWidth: 1,
marginVertical: 10,
padding: 10
},
heading: {
fontWeight: 'bold',
padding: 5,
textAlign: 'center'
},
text: {
textAlign: 'center'
}
})
export default MediaQueryWidget

View File

@@ -1,7 +1,10 @@
import React, { AppRegistry } from 'react-native'
import { AppRegistry } from 'react-native'
import App from './components/App'
import Game2048 from './2048/Game2048'
import TicTacToeApp from './TicTacToe/TicTacToe'
AppRegistry.runApplication('Game2048', {
AppRegistry.registerComponent('App', () => App)
AppRegistry.runApplication('App', {
rootTag: document.getElementById('react-root')
})

View File

@@ -28,6 +28,11 @@ module.exports = {
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development')
}),
new webpack.optimize.DedupePlugin(),
// https://github.com/animatedjs/animated/issues/40
new webpack.NormalModuleReplacementPlugin(
/es6-set/,
path.join(__dirname, '../src/modules/polyfills/Set.js')
),
new webpack.optimize.OccurenceOrderPlugin()
],
resolve: {

View File

@@ -1,6 +1,6 @@
var webpack = require('webpack')
const webpack = require('webpack')
var testEntry = 'tests.webpack.js'
const testEntry = 'tests.webpack.js'
module.exports = function (config) {
config.set({
@@ -19,17 +19,24 @@ module.exports = function (config) {
'karma-chrome-launcher',
'karma-firefox-launcher',
'karma-mocha',
'karma-mocha-reporter',
'karma-sourcemap-loader',
'karma-spec-reporter',
'karma-webpack'
],
preprocessors: {
[testEntry]: [ 'webpack', 'sourcemap' ]
},
reporters: process.env.TRAVIS ? [ 'dots' ] : [ 'spec' ],
reporters: process.env.TRAVIS ? [ 'dots' ] : [ 'mocha' ],
singleRun: true,
webpack: {
devtool: 'inline-source-map',
// required by 'enzyme'
externals: {
'cheerio': 'window',
'react/addons': true,
'react/lib/ExecutionEnvironment': true,
'react/lib/ReactContext': true
},
module: {
loaders: [
{

View File

@@ -1,13 +1,13 @@
{
"name": "react-native-web",
"version": "0.0.22",
"version": "0.0.26",
"description": "React Native for Web",
"main": "dist/index.js",
"files": [
"dist"
],
"scripts": {
"build": "rm -rf ./dist && mkdir dist && babel src -d dist --ignore **/__tests__,src/modules/specHelpers",
"build": "del ./dist && mkdir dist && babel src -d dist --ignore **/__tests__,src/modules/specHelpers",
"build:umd": "webpack --config webpack.config.js --sort-assets-by --progress",
"examples": "webpack-dev-server --config examples/webpack.config.js --inline --hot --colors --quiet",
"lint": "eslint src",
@@ -16,47 +16,49 @@
"test:watch": "npm run test -- --no-single-run"
},
"dependencies": {
"fbjs": "0.6.x || 0.7.x",
"inline-style-prefix-all": "^1.0.4",
"lodash.debounce": "^4.0.3",
"react-textarea-autosize": "^3.1.0",
"animated": "^0.1.3",
"babel-runtime": "^6.9.2",
"fbjs": "^0.8.1",
"inline-style-prefix-all": "^2.0.2",
"lodash": "^4.13.1",
"react-dom": "^15.1.0",
"react-textarea-autosize": "^4.0.2",
"react-timer-mixin": "^0.13.3"
},
"devDependencies": {
"babel-cli": "^6.3.17",
"babel-core": "^6.3.13",
"babel-eslint": "^4.1.6",
"babel-loader": "^6.2.0",
"babel-cli": "^6.10.1",
"babel-core": "^6.9.1",
"babel-eslint": "^6.0.4",
"babel-loader": "^6.2.4",
"babel-plugin-transform-decorators-legacy": "^1.3.4",
"babel-preset-es2015": "^6.3.13",
"babel-preset-react": "^6.3.13",
"babel-preset-stage-1": "^6.3.13",
"babel-runtime": "^6.3.19",
"eslint": "^1.10.3",
"eslint-config-standard": "^4.4.0",
"eslint-config-standard-react": "^1.2.1",
"eslint-plugin-react": "^3.13.1",
"eslint-plugin-standard": "^1.3.1",
"karma": "^0.13.16",
"karma-browserstack-launcher": "^0.1.8",
"karma-chrome-launcher": "^0.2.2",
"karma-firefox-launcher": "^0.1.7",
"karma-mocha": "^0.2.1",
"karma-sourcemap-loader": "^0.3.6",
"karma-spec-reporter": "0.0.23",
"babel-preset-es2015": "^6.9.0",
"babel-preset-react": "^6.5.0",
"babel-preset-stage-1": "^6.5.0",
"del-cli": "^0.2.0",
"enzyme": "^2.3.0",
"eslint": "^2.12.0",
"eslint-config-standard": "^5.3.1",
"eslint-config-standard-react": "^2.4.0",
"eslint-plugin-promise": "^1.3.2",
"eslint-plugin-react": "^5.1.1",
"eslint-plugin-standard": "^1.3.2",
"karma": "^0.13.22",
"karma-browserstack-launcher": "^1.0.1",
"karma-chrome-launcher": "^1.0.1",
"karma-firefox-launcher": "^1.0.0",
"karma-mocha": "^1.0.1",
"karma-mocha-reporter": "^2.0.4",
"karma-sourcemap-loader": "^0.3.7",
"karma-webpack": "^1.7.0",
"mocha": "^2.3.4",
"mocha": "^2.5.3",
"node-libs-browser": "^0.5.3",
"react": "^0.14.3",
"react-addons-test-utils": "^0.14.3",
"react-dom": "^0.14.3",
"react-media-queries": "^2.0.1",
"webpack": "^1.12.9",
"webpack-dev-server": "^1.14.0"
"react": "^15.1.0",
"react-addons-test-utils": "^15.1.0",
"webpack": "^1.13.1",
"webpack-dev-server": "^1.14.1"
},
"peerDependencies": {
"react": "^0.14.3",
"react-dom": "^0.14.3"
"react": "^15.1.0"
},
"author": "Nicolas Gallagher",
"license": "BSD-3-Clause",

View File

@@ -1,23 +0,0 @@
/* eslint-env mocha */
import assert from 'assert'
import React from '..'
suite('ReactNativeWeb', () => {
suite('exports', () => {
test('React', () => {
assert.ok(React)
})
test('ReactDOM methods', () => {
assert.ok(React.findDOMNode)
assert.ok(React.render)
assert.ok(React.unmountComponentAtNode)
})
test('ReactDOM/server methods', () => {
assert.ok(React.renderToString)
assert.ok(React.renderToStaticMarkup)
})
})
})

File diff suppressed because it is too large Load Diff

View File

@@ -1,288 +0,0 @@
/* eslint-disable */
/**
* Copyright (c) 2015-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 Interpolation
* @flow
*/
/* eslint no-bitwise: 0 */
'use strict';
/* @edit start */
var normalizeColor = require('../StyleSheet/normalizeColor');
var invariant = require('fbjs/lib/invariant');
/* @edit end */
type ExtrapolateType = 'extend' | 'identity' | 'clamp';
export type InterpolationConfigType = {
inputRange: Array<number>;
outputRange: (Array<number> | Array<string>);
easing?: ((input: number) => number);
extrapolate?: ExtrapolateType;
extrapolateLeft?: ExtrapolateType;
extrapolateRight?: ExtrapolateType;
};
var linear = (t) => t;
/**
* Very handy helper to map input ranges to output ranges with an easing
* function and custom behavior outside of the ranges.
*/
class Interpolation {
static create(config: InterpolationConfigType): (input: number) => number | string {
if (config.outputRange && typeof config.outputRange[0] === 'string') {
return createInterpolationFromStringOutputRange(config);
}
var outputRange: Array<number> = (config.outputRange: any);
checkInfiniteRange('outputRange', outputRange);
var inputRange = config.inputRange;
checkInfiniteRange('inputRange', inputRange);
checkValidInputRange(inputRange);
invariant(
inputRange.length === outputRange.length,
'inputRange (' + inputRange.length + ') and outputRange (' +
outputRange.length + ') must have the same length'
);
var easing = config.easing || linear;
var extrapolateLeft: ExtrapolateType = 'extend';
if (config.extrapolateLeft !== undefined) {
extrapolateLeft = config.extrapolateLeft;
} else if (config.extrapolate !== undefined) {
extrapolateLeft = config.extrapolate;
}
var extrapolateRight: ExtrapolateType = 'extend';
if (config.extrapolateRight !== undefined) {
extrapolateRight = config.extrapolateRight;
} else if (config.extrapolate !== undefined) {
extrapolateRight = config.extrapolate;
}
return (input) => {
invariant(
typeof input === 'number',
'Cannot interpolation an input which is not a number'
);
var range = findRange(input, inputRange);
return interpolate(
input,
inputRange[range],
inputRange[range + 1],
outputRange[range],
outputRange[range + 1],
easing,
extrapolateLeft,
extrapolateRight,
);
};
}
}
function interpolate(
input: number,
inputMin: number,
inputMax: number,
outputMin: number,
outputMax: number,
easing: ((input: number) => number),
extrapolateLeft: ExtrapolateType,
extrapolateRight: ExtrapolateType,
) {
var result = input;
// Extrapolate
if (result < inputMin) {
if (extrapolateLeft === 'identity') {
return result;
} else if (extrapolateLeft === 'clamp') {
result = inputMin;
} else if (extrapolateLeft === 'extend') {
// noop
}
}
if (result > inputMax) {
if (extrapolateRight === 'identity') {
return result;
} else if (extrapolateRight === 'clamp') {
result = inputMax;
} else if (extrapolateRight === 'extend') {
// noop
}
}
if (outputMin === outputMax) {
return outputMin;
}
if (inputMin === inputMax) {
if (input <= inputMin) {
return outputMin;
}
return outputMax;
}
// Input Range
if (inputMin === -Infinity) {
result = -result;
} else if (inputMax === Infinity) {
result = result - inputMin;
} else {
result = (result - inputMin) / (inputMax - inputMin);
}
// Easing
result = easing(result);
// Output Range
if (outputMin === -Infinity) {
result = -result;
} else if (outputMax === Infinity) {
result = result + outputMin;
} else {
result = result * (outputMax - outputMin) + outputMin;
}
return result;
}
function colorToRgba(input: string): string {
var int32Color = normalizeColor(input);
if (int32Color === null) {
return input;
}
int32Color = int32Color || 0; // $FlowIssue
var r = (int32Color & 0xff000000) >>> 24;
var g = (int32Color & 0x00ff0000) >>> 16;
var b = (int32Color & 0x0000ff00) >>> 8;
var a = (int32Color & 0x000000ff) / 255;
return `rgba(${r}, ${g}, ${b}, ${a})`;
}
var stringShapeRegex = /[0-9\.-]+/g;
/**
* Supports string shapes by extracting numbers so new values can be computed,
* and recombines those values into new strings of the same shape. Supports
* things like:
*
* rgba(123, 42, 99, 0.36) // colors
* -45deg // values with units
*/
function createInterpolationFromStringOutputRange(
config: InterpolationConfigType,
): (input: number) => string {
var outputRange: Array<string> = (config.outputRange: any);
invariant(outputRange.length >= 2, 'Bad output range');
outputRange = outputRange.map(colorToRgba);
checkPattern(outputRange);
// ['rgba(0, 100, 200, 0)', 'rgba(50, 150, 250, 0.5)']
// ->
// [
// [0, 50],
// [100, 150],
// [200, 250],
// [0, 0.5],
// ]
/* $FlowFixMe(>=0.18.0): `outputRange[0].match()` can return `null`. Need to
* guard against this possibility.
*/
var outputRanges = outputRange[0].match(stringShapeRegex).map(() => []);
outputRange.forEach(value => {
/* $FlowFixMe(>=0.18.0): `value.match()` can return `null`. Need to guard
* against this possibility.
*/
value.match(stringShapeRegex).forEach((number, i) => {
outputRanges[i].push(+number);
});
});
/* $FlowFixMe(>=0.18.0): `outputRange[0].match()` can return `null`. Need to
* guard against this possibility.
*/
var interpolations = outputRange[0].match(stringShapeRegex).map((value, i) => {
return Interpolation.create({
...config,
outputRange: outputRanges[i],
});
});
return (input) => {
var i = 0;
// 'rgba(0, 100, 200, 0)'
// ->
// 'rgba(${interpolations[0](input)}, ${interpolations[1](input)}, ...'
return outputRange[0].replace(stringShapeRegex, () => {
return String(interpolations[i++](input));
});
};
}
function checkPattern(arr: Array<string>) {
var pattern = arr[0].replace(stringShapeRegex, '');
for (var i = 1; i < arr.length; ++i) {
invariant(
pattern === arr[i].replace(stringShapeRegex, ''),
'invalid pattern ' + arr[0] + ' and ' + arr[i],
);
}
}
function findRange(input: number, inputRange: Array<number>) {
for (var i = 1; i < inputRange.length - 1; ++i) {
if (inputRange[i] >= input) {
break;
}
}
return i - 1;
}
function checkValidInputRange(arr: Array<number>) {
invariant(arr.length >= 2, 'inputRange must have at least 2 elements');
for (var i = 1; i < arr.length; ++i) {
invariant(
arr[i] >= arr[i - 1],
/* $FlowFixMe(>=0.13.0) - In the addition expression below this comment,
* one or both of the operands may be something that doesn't cleanly
* convert to a string, like undefined, null, and object, etc. If you really
* mean this implicit string conversion, you can do something like
* String(myThing)
*/
'inputRange must be monotonically increasing ' + arr
);
}
}
function checkInfiniteRange(name: string, arr: Array<number>) {
invariant(arr.length >= 2, name + ' must have at least 2 elements');
invariant(
arr.length !== 2 || arr[0] !== -Infinity || arr[1] !== Infinity,
/* $FlowFixMe(>=0.13.0) - In the addition expression below this comment,
* one or both of the operands may be something that doesn't cleanly convert
* to a string, like undefined, null, and object, etc. If you really mean
* this implicit string conversion, you can do something like
* String(myThing)
*/
name + 'cannot be ]-infinity;+infinity[ ' + arr
);
}
module.exports = Interpolation;

View File

@@ -1,103 +0,0 @@
/* eslint-disable */
/**
* Copyright (c) 2015-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 SpringConfig
* @flow
*/
'use strict';
type SpringConfigType = {
tension: number,
friction: number,
};
function tensionFromOrigamiValue(oValue) {
return (oValue - 30) * 3.62 + 194;
}
function frictionFromOrigamiValue(oValue) {
return (oValue - 8) * 3 + 25;
}
function fromOrigamiTensionAndFriction(
tension: number,
friction: number,
): SpringConfigType {
return {
tension: tensionFromOrigamiValue(tension),
friction: frictionFromOrigamiValue(friction)
};
}
function fromBouncinessAndSpeed(
bounciness: number,
speed: number,
): SpringConfigType {
function normalize(value, startValue, endValue) {
return (value - startValue) / (endValue - startValue);
}
function projectNormal(n, start, end) {
return start + (n * (end - start));
}
function linearInterpolation(t, start, end) {
return t * end + (1 - t) * start;
}
function quadraticOutInterpolation(t, start, end) {
return linearInterpolation(2 * t - t * t, start, end);
}
function b3Friction1(x) {
return (0.0007 * Math.pow(x, 3)) -
(0.031 * Math.pow(x, 2)) + 0.64 * x + 1.28;
}
function b3Friction2(x) {
return (0.000044 * Math.pow(x, 3)) -
(0.006 * Math.pow(x, 2)) + 0.36 * x + 2;
}
function b3Friction3(x) {
return (0.00000045 * Math.pow(x, 3)) -
(0.000332 * Math.pow(x, 2)) + 0.1078 * x + 5.84;
}
function b3Nobounce(tension) {
if (tension <= 18) {
return b3Friction1(tension);
} else if (tension > 18 && tension <= 44) {
return b3Friction2(tension);
} else {
return b3Friction3(tension);
}
}
var b = normalize(bounciness / 1.7, 0, 20);
b = projectNormal(b, 0, 0.8);
var s = normalize(speed / 1.7, 0, 20);
var bouncyTension = projectNormal(s, 0.5, 200);
var bouncyFriction = quadraticOutInterpolation(
b,
b3Nobounce(bouncyTension),
0.01
);
return {
tension: tensionFromOrigamiValue(bouncyTension),
friction: frictionFromOrigamiValue(bouncyFriction)
};
}
module.exports = {
fromOrigamiTensionAndFriction,
fromBouncinessAndSpeed,
};

View File

@@ -1,19 +0,0 @@
/**
* Copyright (c) 2016-present, Nicolas Gallagher.
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* @flow
*/
import AnimatedImplementation from './AnimatedImplementation'
import Image from '../../components/Image'
import Text from '../../components/Text'
import View from '../../components/View'
module.exports = {
...AnimatedImplementation,
View: AnimatedImplementation.createAnimatedComponent(View),
Text: AnimatedImplementation.createAnimatedComponent(Text),
Image: AnimatedImplementation.createAnimatedComponent(Image)
}

View File

@@ -1,15 +0,0 @@
function SetPolyfill() {
this._cache = []
}
SetPolyfill.prototype.add = function (e) {
if (this._cache.indexOf(e) === -1) {
this._cache.push(e)
}
}
SetPolyfill.prototype.forEach = function (cb) {
this._cache.forEach(cb)
}
export default SetPolyfill

View File

@@ -11,25 +11,24 @@ class ReactNativeApp extends Component {
rootTag: PropTypes.any
};
constructor(props, context) {
super(props, context)
this._handleModalVisibilityChange = this._handleModalVisibilityChange.bind(this)
}
_handleModalVisibilityChange(modalVisible) {
ReactDOM.findDOMNode(this._root).setAttribute('aria-hidden', `${modalVisible}`)
}
render() {
const { initialProps, rootComponent: RootComponent, rootTag } = this.props
return (
<View style={styles.appContainer}>
<RootComponent {...initialProps} ref={(c) => { this._root = c }} rootTag={rootTag} />
<RootComponent {...initialProps} ref={this._createRootRef} rootTag={rootTag} />
<Portal onModalVisibilityChanged={this._handleModalVisibilityChange} />
</View>
)
}
_createRootRef = (component) => {
this._root = component
}
_handleModalVisibilityChange = (modalVisible) => {
ReactDOM.findDOMNode(this._root).setAttribute('aria-hidden', `${modalVisible}`)
}
}
const styles = StyleSheet.create({

View File

@@ -33,7 +33,7 @@ class AppRegistry {
invariant(
runnables[appKey] && runnables[appKey].prerender,
`Application ${appKey} has not been registered. ` +
`This is either due to an import error during initialization or failure to call AppRegistry.registerComponent.`
'This is either due to an import error during initialization or failure to call AppRegistry.registerComponent.'
)
return runnables[appKey].prerender(appParameters)
@@ -78,7 +78,7 @@ class AppRegistry {
invariant(
runnables[appKey] && runnables[appKey].run,
`Application "${appKey}" has not been registered. ` +
`This is either due to an import error during initialization or failure to call AppRegistry.registerComponent.`
'This is either due to an import error during initialization or failure to call AppRegistry.registerComponent.'
)
runnables[appKey].run(appParameters)

View File

@@ -1,5 +1,31 @@
/* eslint-env mocha */
import AppState from '..'
import assert from 'assert'
suite('apis/AppState', () => {
test.skip('NO TEST COVERAGE', () => {})
const handler = () => {}
teardown(() => {
try { AppState.removeEventListener('change', handler) } catch (e) {}
})
suite('addEventListener', () => {
test('throws if the provided "eventType" is not supported', () => {
assert.throws(() => AppState.addEventListener('foo', handler))
assert.doesNotThrow(() => AppState.addEventListener('change', handler))
})
})
suite('removeEventListener', () => {
test('throws if the handler is not registered', () => {
assert.throws(() => AppState.removeEventListener('change', handler))
})
test('throws if the provided "eventType" is not supported', () => {
AppState.addEventListener('change', handler)
assert.throws(() => AppState.removeEventListener('foo', handler))
assert.doesNotThrow(() => AppState.removeEventListener('change', handler))
})
})
})

View File

@@ -1,30 +1,53 @@
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment'
import findIndex from 'lodash/findIndex'
import invariant from 'fbjs/lib/invariant'
const listeners = {}
const eventTypes = [ 'change' ]
const EVENT_TYPES = [ 'change' ]
const VISIBILITY_CHANGE_EVENT = 'visibilitychange'
const AppStates = {
BACKGROUND: 'background',
ACTIVE: 'active'
}
const listeners = []
class AppState {
static isSupported = ExecutionEnvironment.canUseDOM && document.visibilityState
static get currentState() {
if (!AppState.isSupported) {
return AppState.ACTIVE
}
switch (document.visibilityState) {
case 'hidden':
case 'prerender':
case 'unloaded':
return 'background'
return AppStates.BACKGROUND
default:
return 'active'
return AppStates.ACTIVE
}
}
static addEventListener(type: string, handler: Function) {
listeners[handler] = () => handler(AppState.currentState)
invariant(eventTypes.indexOf(type) !== -1, 'Trying to subscribe to unknown event: "%s"', type)
document.addEventListener('visibilitychange', listeners[handler], false)
if (AppState.isSupported) {
invariant(EVENT_TYPES.indexOf(type) !== -1, 'Trying to subscribe to unknown event: "%s"', type)
const callback = () => handler(AppState.currentState)
listeners.push([ handler, callback ])
document.addEventListener(VISIBILITY_CHANGE_EVENT, callback, false)
}
}
static removeEventListener(type: string, handler: Function) {
invariant(eventTypes.indexOf(type) !== -1, 'Trying to remove listener for unknown event: "%s"', type)
document.removeEventListener('visibilitychange', listeners[handler], false)
delete listeners[handler]
if (AppState.isSupported) {
invariant(EVENT_TYPES.indexOf(type) !== -1, 'Trying to remove listener for unknown event: "%s"', type)
const listenerIndex = findIndex(listeners, (pair) => pair[0] === handler)
invariant(listenerIndex !== -1, 'Trying to remove AppState listener for unregistered handler')
const callback = listeners[listenerIndex][1]
document.removeEventListener(VISIBILITY_CHANGE_EVENT, callback, false)
listeners.splice(listenerIndex, 1)
}
}
}

View File

@@ -6,7 +6,7 @@
* @flow
*/
import debounce from 'lodash.debounce'
import debounce from 'lodash/debounce'
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment'
import invariant from 'fbjs/lib/invariant'

View File

@@ -1,83 +0,0 @@
/* eslint-disable */
/**
* https://github.com/arian/cubic-bezier
*
* MIT License
*
* Copyright (c) 2013 Arian Stolwijk
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* @providesModule bezier
* @nolint
*/
module.exports = function(x1, y1, x2, y2, epsilon){
var curveX = function(t){
var v = 1 - t;
return 3 * v * v * t * x1 + 3 * v * t * t * x2 + t * t * t;
};
var curveY = function(t){
var v = 1 - t;
return 3 * v * v * t * y1 + 3 * v * t * t * y2 + t * t * t;
};
var derivativeCurveX = function(t){
var v = 1 - t;
return 3 * (2 * (t - 1) * t + v * v) * x1 + 3 * (-t * t * t + 2 * v * t) * x2;
};
return function(t){
var x = t, t0, t1, t2, x2, d2, i;
// First try a few iterations of Newton's method -- normally very fast.
for (t2 = x, i = 0; i < 8; i++){
x2 = curveX(t2) - x;
if (Math.abs(x2) < epsilon) { return curveY(t2); }
d2 = derivativeCurveX(t2);
if (Math.abs(d2) < 1e-6) { break; }
t2 = t2 - x2 / d2;
}
t0 = 0;
t1 = 1;
t2 = x;
if (t2 < t0) { return curveY(t0); }
if (t2 > t1) { return curveY(t1); }
// Fallback to the bisection method for reliability.
while (t0 < t1){
x2 = curveX(t2);
if (Math.abs(x2 - x) < epsilon) { return curveY(t2); }
if (x > x2) { t0 = t2; }
else { t1 = t2; }
t2 = (t1 - t0) * 0.5 + t0;
}
// Failure
return curveY(t2);
};
};

View File

@@ -1,155 +0,0 @@
/* eslint-disable */
/**
* Copyright (c) 2015-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 Easing
* @flow
*/
'use strict';
var _bezier = require('./bezier');
/**
* This class implements common easing functions. The math is pretty obscure,
* but this cool website has nice visual illustrations of what they represent:
* http://xaedes.de/dev/transitions/
*/
class Easing {
static step0(n) {
return n > 0 ? 1 : 0;
}
static step1(n) {
return n >= 1 ? 1 : 0;
}
static linear(t) {
return t;
}
static ease(t: number): number {
return ease(t);
}
static quad(t) {
return t * t;
}
static cubic(t) {
return t * t * t;
}
static poly(n) {
return (t) => Math.pow(t, n);
}
static sin(t) {
return 1 - Math.cos(t * Math.PI / 2);
}
static circle(t) {
return 1 - Math.sqrt(1 - t * t);
}
static exp(t) {
return Math.pow(2, 10 * (t - 1));
}
/**
* A simple elastic interaction, similar to a spring. Default bounciness
* is 1, which overshoots a little bit once. 0 bounciness doesn't overshoot
* at all, and bounciness of N > 1 will overshoot about N times.
*
* Wolfram Plots:
*
* http://tiny.cc/elastic_b_1 (default bounciness = 1)
* http://tiny.cc/elastic_b_3 (bounciness = 3)
*/
static elastic(bounciness: number = 1): (t: number) => number {
var p = bounciness * Math.PI;
return (t) => 1 - Math.pow(Math.cos(t * Math.PI / 2), 3) * Math.cos(t * p);
}
static back(s: number): (t: number) => number {
if (s === undefined) {
s = 1.70158;
}
return (t) => t * t * ((s + 1) * t - s);
}
static bounce(t: number): number {
if (t < 1 / 2.75) {
return 7.5625 * t * t;
}
if (t < 2 / 2.75) {
t -= 1.5 / 2.75;
return 7.5625 * t * t + 0.75;
}
if (t < 2.5 / 2.75) {
t -= 2.25 / 2.75;
return 7.5625 * t * t + 0.9375;
}
t -= 2.625 / 2.75;
return 7.5625 * t * t + 0.984375;
}
static bezier(
x1: number,
y1: number,
x2: number,
y2: number,
epsilon?: ?number,
): (t: number) => number {
if (epsilon === undefined) {
// epsilon determines the precision of the solved values
// a good approximation is:
var duration = 500; // duration of animation in milliseconds.
epsilon = (1000 / 60 / duration) / 4;
}
return _bezier(x1, y1, x2, y2, epsilon);
}
static in(
easing: (t: number) => number,
): (t: number) => number {
return easing;
}
/**
* Runs an easing function backwards.
*/
static out(
easing: (t: number) => number,
): (t: number) => number {
return (t) => 1 - easing(1 - t);
}
/**
* Makes any easing function symmetrical.
*/
static inOut(
easing: (t: number) => number,
): (t: number) => number {
return (t) => {
if (t < 0.5) {
return easing(t * 2) / 2;
}
return 1 - easing((1 - t) * 2) / 2;
};
}
}
var ease = Easing.bezier(0.42, 0, 1, 1);
module.exports = Easing;

View File

@@ -1,8 +1,6 @@
import { canUseDOM } from 'fbjs/lib/ExecutionEnvironment'
const Platform = {
OS: 'web',
userAgent: canUseDOM ? window.navigator.userAgent : ''
select: (obj: Object) => obj.web
}
module.exports = Platform

View File

@@ -26,7 +26,14 @@ const createCssDeclarations = (style) => {
return Object.keys(style).map((prop) => {
const property = hyphenate(prop)
const value = style[prop]
return `${property}:${value};`
if (Array.isArray(value)) {
return value.reduce((acc, curr) => {
acc += `${property}:${curr};`
return acc
}, '')
} else {
return `${property}:${value};`
}
}).sort().join('')
}
@@ -91,9 +98,20 @@ class StyleSheetRegistry {
}
}
/**
* React 15 removed undocumented support for fallback values in
* inline-styles. For now, pick the last value and regress browser support
* for CSS features like flexbox.
*/
const finalStyle = Object.keys(prefixAll(style)).reduce((acc, prop) => {
const value = style[prop]
acc[prop] = Array.isArray(value) ? value[value.length - 1] : value
return acc
}, {})
return {
className: classList.join(' '),
style: prefixAll(style)
style: finalStyle
}
}
}

View File

@@ -30,7 +30,7 @@ suite('apis/StyleSheet', () => {
`${resetCSS}\n${predefinedCSS}\n` +
`/* 2 unique declarations */\n` +
`.__style1{opacity:1;}\n` +
`.__style2{color:red;}`
'.__style2{color:red;}'
)
// teardown
@@ -45,7 +45,17 @@ suite('apis/StyleSheet', () => {
StyleSheet.renderToString(),
`${resetCSS}\n${predefinedCSS}\n` +
`/* 1 unique declarations */\n` +
`.__style1{opacity:1;}`
'.__style1{opacity:1;}'
)
})
test('resolve', () => {
assert.deepEqual(
StyleSheet.resolve({ className: 'test', style: styles.root }),
{
className: 'test',
style: { opacity: 1 }
}
)
})
})

View File

@@ -56,8 +56,12 @@ const renderToString = () => {
* Accepts React props and converts inline styles to single purpose classes
* where possible.
*/
const resolve = ({ style = {} }) => {
return StyleSheetRegistry.getStyleAsNativeProps(style, isRendered)
const resolve = ({ className, style = {} }) => {
const props = StyleSheetRegistry.getStyleAsNativeProps(style, isRendered)
return {
...props,
className: className ? `${props.className} ${className}`.trim() : props.className
}
}
module.exports = {

View File

@@ -9,7 +9,6 @@ const unitlessNumbers = {
flexNegative: true,
fontWeight: true,
lineClamp: true,
lineHeight: true,
opacity: true,
order: true,
orphans: true,

View File

@@ -94,18 +94,25 @@ suite('apis/UIManager', () => {
})
suite('updateView', () => {
const componentStub = {
_reactInternalInstance: {
_currentElement: { _owner: {} },
_debugID: 1
}
}
test('add new className to existing className', () => {
const node = createNode()
node.className = 'existing'
const props = { className: 'extra' }
UIManager.updateView(node, props)
UIManager.updateView(node, props, componentStub)
assert.equal(node.getAttribute('class'), 'existing extra')
})
test('adds new style to existing style', () => {
const node = createNode({ color: 'red' })
const props = { style: { opacity: 0 } }
UIManager.updateView(node, props)
UIManager.updateView(node, props, componentStub)
assert.equal(node.getAttribute('style'), 'color: red; opacity: 0;')
})

View File

@@ -34,7 +34,7 @@ const UIManager = {
_measureLayout(node, relativeTo, onSuccess)
},
updateView(node, props) {
updateView(node, props, component /* only needed to surpress React errors in __DEV__ */) {
for (const prop in props) {
let nativeProp
const value = props[prop]
@@ -42,7 +42,11 @@ const UIManager = {
switch (prop) {
case 'style':
// convert styles to DOM-styles
CSSPropertyOperations.setValueForStyles(node, processTransform(flattenStyle(value)))
CSSPropertyOperations.setValueForStyles(
node,
processTransform(flattenStyle(value)),
component._reactInternalInstance
)
break
case 'class':
case 'className':

View File

@@ -61,7 +61,7 @@ class ActivityIndicator extends Component {
return (
<View {...other} style={[ styles.container, style ]}>
<View
ref={(c) => { this._indicatorRef = c }}
ref={this._createIndicatorRef}
style={[
indicatorStyles[size],
hidesWhenStopped && !animating && styles.hidesWhenStopped,
@@ -72,6 +72,10 @@ class ActivityIndicator extends Component {
)
}
_createIndicatorRef = (component) => {
this._indicatorRef = component
}
_manageAnimation() {
if (this._player) {
if (this.props.animating) {

View File

@@ -1,68 +0,0 @@
import NativeMethodsDecorator from '../../modules/NativeMethodsDecorator'
import React, { Component, PropTypes } from 'react'
import StyleSheet from '../../apis/StyleSheet'
const roleComponents = {
article: 'article',
banner: 'header',
button: 'button',
complementary: 'aside',
contentinfo: 'footer',
form: 'form',
heading: 'h1',
link: 'a',
list: 'ul',
listitem: 'li',
main: 'main',
navigation: 'nav',
region: 'section'
}
@NativeMethodsDecorator
class CoreComponent extends Component {
static propTypes = {
accessibilityLabel: PropTypes.string,
accessibilityLiveRegion: PropTypes.oneOf([ 'assertive', 'off', 'polite' ]),
accessibilityRole: PropTypes.string,
accessible: PropTypes.bool,
component: PropTypes.oneOfType([ PropTypes.func, PropTypes.string ]),
style: PropTypes.oneOfType([ PropTypes.array, PropTypes.object ]),
testID: PropTypes.string,
type: PropTypes.string
};
static defaultProps = {
accessible: true,
component: 'div'
};
render() {
const {
accessibilityLabel,
accessibilityLiveRegion,
accessibilityRole,
accessible,
component,
testID,
type,
...other
} = this.props
const Component = roleComponents[accessibilityRole] || component
return (
<Component
{...other}
{...StyleSheet.resolve(other)}
aria-hidden={accessible ? null : true}
aria-label={accessibilityLabel}
aria-live={accessibilityLiveRegion}
data-testid={testID}
role={accessibilityRole}
type={accessibilityRole === 'button' ? 'button' : type}
/>
)
}
}
module.exports = CoreComponent

View File

@@ -1,55 +1,56 @@
/* eslint-env mocha */
import * as utils from '../../../modules/specHelpers'
import { mount, shallow } from 'enzyme'
import assert from 'assert'
import React from 'react'
import flattenStyle from '../../../apis/StyleSheet/flattenStyle'
import StyleSheet from '../../../apis/StyleSheet'
import Image from '../'
const getStyleBackgroundSize = (element) => flattenStyle(element.props.style).backgroundSize
suite('components/Image', () => {
test('default accessibility', () => {
const dom = utils.renderToDOM(<Image />)
assert.equal(dom.getAttribute('role'), 'img')
test('sets correct accessibility role"', () => {
const image = shallow(<Image />)
assert.equal(image.prop('accessibilityRole'), 'img')
})
test('prop "accessibilityLabel"', () => {
const accessibilityLabel = 'accessibilityLabel'
const result = utils.shallowRender(<Image accessibilityLabel={accessibilityLabel} />)
assert.equal(result.props.accessibilityLabel, accessibilityLabel)
const image = shallow(<Image accessibilityLabel={accessibilityLabel} />)
assert.equal(image.prop('accessibilityLabel'), accessibilityLabel)
})
test('prop "accessible"', () => {
const accessible = false
const result = utils.shallowRender(<Image accessible={accessible} />)
assert.equal(result.props.accessible, accessible)
const image = shallow(<Image accessible={accessible} />)
assert.equal(image.prop('accessible'), accessible)
})
test('prop "children"')
test('prop "defaultSource"', () => {
const defaultSource = { uri: 'https://google.com/favicon.ico' }
const dom = utils.renderToDOM(<Image defaultSource={defaultSource} />)
const backgroundImage = dom.style.backgroundImage
assert(backgroundImage.indexOf(defaultSource.uri) > -1)
test('prop "children"', () => {
const children = <div className='unique' />
const wrapper = shallow(<Image>{children}</Image>)
assert.equal(wrapper.contains(children), true)
})
test('prop "defaultSource" with string value"', () => {
// emulate require-ed asset
const defaultSource = 'https://google.com/favicon.ico'
const dom = utils.renderToDOM(<Image defaultSource={defaultSource} />)
const backgroundImage = dom.style.backgroundImage
assert(backgroundImage.indexOf(defaultSource) > -1)
suite('prop "defaultSource"', () => {
test('sets background image when value is an object', () => {
const defaultSource = { uri: 'https://google.com/favicon.ico' }
const image = shallow(<Image defaultSource={defaultSource} />)
const backgroundImage = StyleSheet.flatten(image.prop('style')).backgroundImage
assert(backgroundImage.indexOf(defaultSource.uri) > -1)
})
test('sets background image when value is a string', () => {
// emulate require-ed asset
const defaultSource = 'https://google.com/favicon.ico'
const image = shallow(<Image defaultSource={defaultSource} />)
const backgroundImage = StyleSheet.flatten(image.prop('style')).backgroundImage
assert(backgroundImage.indexOf(defaultSource) > -1)
})
})
test('prop "onError"', function (done) {
this.timeout(5000)
utils.render(<Image
onError={onError}
source={{ uri: 'https://google.com/favicon.icox' }}
/>)
mount(<Image onError={onError} source={{ uri: 'https://google.com/favicon.icox' }} />)
function onError(e) {
assert.equal(e.nativeEvent.type, 'error')
done()
@@ -58,82 +59,104 @@ suite('components/Image', () => {
test('prop "onLoad"', function (done) {
this.timeout(5000)
utils.render(<Image onLoad={onLoad} source={{ uri: 'https://google.com/favicon.ico' }} />)
const image = mount(<Image onLoad={onLoad} source={{ uri: 'https://google.com/favicon.ico' }} />)
function onLoad(e) {
assert.equal(e.nativeEvent.type, 'load')
const backgroundImage = StyleSheet.flatten(image.ref('root').prop('style')).backgroundImage
assert.notDeepEqual(backgroundImage, undefined)
done()
}
})
test('prop "onLoadEnd"')
test('prop "onLoadEnd"', function (done) {
this.timeout(5000)
const image = mount(<Image onLoadEnd={onLoadEnd} source={{ uri: 'https://google.com/favicon.ico' }} />)
function onLoadEnd() {
assert.ok(true)
const backgroundImage = StyleSheet.flatten(image.ref('root').prop('style')).backgroundImage
assert.notDeepEqual(backgroundImage, undefined)
done()
}
})
test('prop "onLoadStart"')
test('prop "onLoadStart"', function (done) {
this.timeout(5000)
mount(<Image onLoadStart={onLoadStart} source={{ uri: 'https://google.com/favicon.ico' }} />)
function onLoadStart() {
assert.ok(true)
done()
}
})
suite('prop "resizeMode"', () => {
const getBackgroundSize = (image) => StyleSheet.flatten(image.prop('style')).backgroundSize
test('value "contain"', () => {
const result = utils.shallowRender(<Image resizeMode={Image.resizeMode.contain} />)
assert.equal(getStyleBackgroundSize(result), 'contain')
const image = shallow(<Image resizeMode={Image.resizeMode.contain} />)
assert.equal(getBackgroundSize(image), 'contain')
})
test('value "cover"', () => {
const result = utils.shallowRender(<Image resizeMode={Image.resizeMode.cover} />)
assert.equal(getStyleBackgroundSize(result), 'cover')
const image = shallow(<Image resizeMode={Image.resizeMode.cover} />)
assert.equal(getBackgroundSize(image), 'cover')
})
test('value "none"', () => {
const result = utils.shallowRender(<Image resizeMode={Image.resizeMode.none} />)
assert.equal(getStyleBackgroundSize(result), 'auto')
const image = shallow(<Image resizeMode={Image.resizeMode.none} />)
assert.equal(getBackgroundSize(image), 'auto')
})
test('value "stretch"', () => {
const result = utils.shallowRender(<Image resizeMode={Image.resizeMode.stretch} />)
assert.equal(getStyleBackgroundSize(result), '100% 100%')
const image = shallow(<Image resizeMode={Image.resizeMode.stretch} />)
assert.equal(getBackgroundSize(image), '100% 100%')
})
test('no value', () => {
const result = utils.shallowRender(<Image />)
assert.equal(getStyleBackgroundSize(result), 'cover')
const image = shallow(<Image />)
assert.equal(getBackgroundSize(image), 'cover')
})
})
test('prop "source"', function (done) {
suite('prop "source"', function () {
this.timeout(5000)
const source = { uri: 'https://google.com/favicon.ico' }
utils.render(<Image onLoad={onLoad} source={source} />)
function onLoad(e) {
const src = e.nativeEvent.target.src
assert.equal(src, source.uri)
done()
}
})
test('prop "source" with string value', function (done) {
this.timeout(5000)
// emulate require-ed asset
const source = 'https://google.com/favicon.ico'
utils.render(<Image onLoad={onLoad} source={source} />)
function onLoad(e) {
const src = e.nativeEvent.target.src
assert.equal(src, source)
done()
}
test('sets background image when value is an object', (done) => {
const source = { uri: 'https://google.com/favicon.ico' }
mount(<Image onLoad={onLoad} source={source} />)
function onLoad(e) {
const src = e.nativeEvent.target.src
assert.equal(src, source.uri)
done()
}
})
test('sets background image when value is a string', (done) => {
// emulate require-ed asset
const source = 'https://google.com/favicon.ico'
mount(<Image onLoad={onLoad} source={source} />)
function onLoad(e) {
const src = e.nativeEvent.target.src
assert.equal(src, source)
done()
}
})
})
suite('prop "style"', () => {
test('converts "resizeMode" property', () => {
const result = utils.shallowRender(<Image style={{ resizeMode: Image.resizeMode.contain }} />)
assert.equal(getStyleBackgroundSize(result), 'contain')
const image = shallow(<Image style={{ resizeMode: Image.resizeMode.contain }} />)
assert.equal(StyleSheet.flatten(image.prop('style')).backgroundSize, 'contain')
})
test('removes "resizeMode" property', () => {
const result = utils.shallowRender(<Image style={{ resizeMode: Image.resizeMode.contain }} />)
assert.equal(flattenStyle(result.props.style).resizeMode, undefined)
const image = shallow(<Image style={{ resizeMode: Image.resizeMode.contain }} />)
assert.equal(StyleSheet.flatten(image.prop('style')).resizeMode, undefined)
})
})
test('prop "testID"', () => {
const testID = 'testID'
const result = utils.shallowRender(<Image testID={testID} />)
assert.equal(result.props.testID, testID)
const image = shallow(<Image testID={testID} />)
assert.equal(image.prop('testID'), testID)
})
})

View File

@@ -1,9 +1,9 @@
/* global window */
import NativeMethodsDecorator from '../../modules/NativeMethodsDecorator'
import resolveAssetSource from './resolveAssetSource'
import CoreComponent from '../CoreComponent'
import createNativeComponent from '../../modules/createNativeComponent'
import ImageResizeMode from './ImageResizeMode'
import ImageStylePropTypes from './ImageStylePropTypes'
import NativeMethodsDecorator from '../../modules/NativeMethodsDecorator'
import resolveAssetSource from './resolveAssetSource'
import React, { Component, PropTypes } from 'react'
import StyleSheet from '../../apis/StyleSheet'
import StyleSheetPropType from '../../apis/StyleSheet/StyleSheetPropType'
@@ -25,8 +25,8 @@ const ImageSourcePropType = PropTypes.oneOfType([
@NativeMethodsDecorator
class Image extends Component {
static propTypes = {
accessibilityLabel: CoreComponent.propTypes.accessibilityLabel,
accessible: CoreComponent.propTypes.accessible,
accessibilityLabel: createNativeComponent.propTypes.accessibilityLabel,
accessible: createNativeComponent.propTypes.accessible,
children: PropTypes.any,
defaultSource: ImageSourcePropType,
onError: PropTypes.func,
@@ -36,7 +36,7 @@ class Image extends Component {
resizeMode: PropTypes.oneOf(['contain', 'cover', 'none', 'stretch']),
source: ImageSourcePropType,
style: StyleSheetPropType(ImageStylePropTypes),
testID: CoreComponent.propTypes.testID
testID: createNativeComponent.propTypes.testID
};
static defaultProps = {
@@ -49,61 +49,7 @@ class Image extends Component {
constructor(props, context) {
super(props, context)
const uri = resolveAssetSource(props.source)
// state
this.state = { status: uri ? STATUS_PENDING : STATUS_IDLE }
// autobinding
this._onError = this._onError.bind(this)
this._onLoad = this._onLoad.bind(this)
}
_createImageLoader() {
const uri = resolveAssetSource(this.props.source)
this._destroyImageLoader()
this.image = new window.Image()
this.image.onerror = this._onError
this.image.onload = this._onLoad
this.image.src = uri
this._onLoadStart()
}
_destroyImageLoader() {
if (this.image) {
this.image.onerror = null
this.image.onload = null
this.image = null
}
}
_onError(e) {
const { onError } = this.props
const event = { nativeEvent: e }
this._destroyImageLoader()
this.setState({ status: STATUS_ERRORED })
this._onLoadEnd()
if (onError) onError(event)
}
_onLoad(e) {
const { onLoad } = this.props
const event = { nativeEvent: e }
this._destroyImageLoader()
this.setState({ status: STATUS_LOADED })
if (onLoad) onLoad(event)
this._onLoadEnd()
}
_onLoadEnd() {
const { onLoadEnd } = this.props
if (onLoadEnd) onLoadEnd()
}
_onLoadStart() {
const { onLoadStart } = this.props
this.setState({ status: STATUS_LOADING })
if (onLoadStart) onLoadStart()
}
componentDidMount() {
@@ -162,6 +108,7 @@ class Image extends Component {
accessibilityLabel={accessibilityLabel}
accessibilityRole='img'
accessible={accessible}
ref='root'
style={[
styles.initial,
style,
@@ -170,13 +117,63 @@ class Image extends Component {
]}
testID={testID}
>
<img src={displayImage} style={styles.img} />
{createNativeComponent({ component: 'img', src: displayImage, style: styles.img })}
{children ? (
<View children={children} pointerEvents='box-none' style={styles.children} />
) : null}
</View>
)
}
_createImageLoader() {
const uri = resolveAssetSource(this.props.source)
this._destroyImageLoader()
this.image = new window.Image()
this.image.onerror = this._onError
this.image.onload = this._onLoad
this.image.src = uri
this._onLoadStart()
}
_destroyImageLoader() {
if (this.image) {
this.image.onerror = null
this.image.onload = null
this.image = null
}
}
_onError = (e) => {
const { onError } = this.props
const event = { nativeEvent: e }
this._destroyImageLoader()
this.setState({ status: STATUS_ERRORED })
this._onLoadEnd()
if (onError) onError(event)
}
_onLoad = (e) => {
const { onLoad } = this.props
const event = { nativeEvent: e }
this._destroyImageLoader()
this.setState({ status: STATUS_LOADED })
if (onLoad) onLoad(event)
this._onLoadEnd()
}
_onLoadEnd() {
const { onLoadEnd } = this.props
if (onLoadEnd) onLoadEnd()
}
_onLoadStart() {
const { onLoadStart } = this.props
this.setState({ status: STATUS_LOADING })
if (onLoadStart) onLoadStart()
}
}
const styles = StyleSheet.create({

View File

@@ -6,7 +6,7 @@
* @flow
*/
import debounce from 'lodash.debounce'
import debounce from 'lodash/debounce'
import React, { Component, PropTypes } from 'react'
import View from '../View'

View File

@@ -8,24 +8,6 @@ import ReactTestUtils from 'react-addons-test-utils'
import Text from '../'
suite('components/Text', () => {
test('prop "accessibilityLabel"', () => {
const accessibilityLabel = 'accessibilityLabel'
const result = utils.shallowRender(<Text accessibilityLabel={accessibilityLabel} />)
assert.equal(result.props.accessibilityLabel, accessibilityLabel)
})
test('prop "accessibilityRole"', () => {
const accessibilityRole = 'accessibilityRole'
const result = utils.shallowRender(<Text accessibilityRole={accessibilityRole} />)
assert.equal(result.props.accessibilityRole, accessibilityRole)
})
test('prop "accessible"', () => {
const accessible = false
const result = utils.shallowRender(<Text accessible={accessible} />)
assert.equal(result.props.accessible, accessible)
})
test('prop "children"', () => {
const children = 'children'
const result = utils.shallowRender(<Text>{children}</Text>)
@@ -42,10 +24,4 @@ suite('components/Text', () => {
done()
}
})
test('prop "testID"', () => {
const testID = 'testID'
const result = utils.shallowRender(<Text testID={testID} />)
assert.equal(result.props.testID, testID)
})
})

View File

@@ -1,6 +1,6 @@
import createNativeComponent from '../../modules/createNativeComponent'
import NativeMethodsDecorator from '../../modules/NativeMethodsDecorator'
import CoreComponent from '../CoreComponent'
import React, { Component, PropTypes } from 'react'
import { Component, PropTypes } from 'react'
import StyleSheet from '../../apis/StyleSheet'
import StyleSheetPropType from '../../apis/StyleSheet/StyleSheetPropType'
import TextStylePropTypes from './TextStylePropTypes'
@@ -8,44 +8,44 @@ import TextStylePropTypes from './TextStylePropTypes'
@NativeMethodsDecorator
class Text extends Component {
static propTypes = {
accessibilityLabel: CoreComponent.propTypes.accessibilityLabel,
accessibilityRole: CoreComponent.propTypes.accessibilityRole,
accessible: CoreComponent.propTypes.accessible,
accessibilityLabel: createNativeComponent.propTypes.accessibilityLabel,
accessibilityRole: createNativeComponent.propTypes.accessibilityRole,
accessible: createNativeComponent.propTypes.accessible,
children: PropTypes.any,
numberOfLines: PropTypes.number,
onPress: PropTypes.func,
style: StyleSheetPropType(TextStylePropTypes),
testID: CoreComponent.propTypes.testID
testID: createNativeComponent.propTypes.testID
};
static defaultProps = {
accessible: true
};
_onPress(e) {
_onPress = (e) => {
if (this.props.onPress) this.props.onPress(e)
}
render() {
const {
numberOfLines,
/* eslint-disable no-unused-vars */
onPress,
/* eslint-enable no-unused-vars */
style,
...other
} = this.props
return (
<CoreComponent
{...other}
component='span'
onClick={this._onPress.bind(this)}
style={[
styles.initial,
style,
numberOfLines === 1 && styles.singleLineStyle
]}
/>
)
return createNativeComponent({
...other,
component: 'span',
onClick: this._onPress,
style: [
styles.initial,
style,
numberOfLines === 1 && styles.singleLineStyle
]
})
}
}

View File

@@ -13,12 +13,6 @@ const findShallowInput = (vdom) => vdom.props.children.props.children[0]
const findShallowPlaceholder = (vdom) => vdom.props.children.props.children[1]
suite('components/TextInput', () => {
test('prop "accessibilityLabel"', () => {
const accessibilityLabel = 'accessibilityLabel'
const result = utils.shallowRender(<TextInput accessibilityLabel={accessibilityLabel} />)
assert.equal(result.props.accessibilityLabel, accessibilityLabel)
})
test('prop "autoComplete"', () => {
// off
let input = findInput(utils.renderToDOM(<TextInput />))
@@ -28,7 +22,7 @@ suite('components/TextInput', () => {
assert.equal(input.getAttribute('autocomplete'), 'on')
})
test('prop "autoFocus"', () => {
test.skip('prop "autoFocus"', () => {
// false
let input = findInput(utils.renderToDOM(<TextInput />))
assert.deepEqual(document.activeElement, document.body)
@@ -186,17 +180,17 @@ suite('components/TextInput', () => {
test('prop "placeholder"', () => {
const placeholder = 'placeholder'
const result = findShallowPlaceholder(utils.shallowRender(<TextInput placeholder={placeholder} />))
assert.equal(result.props.children, placeholder)
assert.equal(result.props.children.props.children, placeholder)
})
test('prop "placeholderTextColor"', () => {
const placeholder = 'placeholder'
let result = findShallowPlaceholder(utils.shallowRender(<TextInput placeholder={placeholder} />))
assert.equal(StyleSheet.flatten(result.props.style).color, 'darkgray')
assert.equal(StyleSheet.flatten(result.props.children.props.style).color, 'darkgray')
result = findShallowPlaceholder(utils.shallowRender(<TextInput placeholder={placeholder} placeholderTextColor='red' />))
assert.equal(StyleSheet.flatten(result.props.style).color, 'red')
assert.equal(StyleSheet.flatten(result.props.children.props.style).color, 'red')
})
test('prop "secureTextEntry"', () => {
@@ -221,12 +215,6 @@ suite('components/TextInput', () => {
assert.equal(input.selectionStart, 0)
})
test('prop "testID"', () => {
const testID = 'testID'
const result = utils.shallowRender(<TextInput testID={testID} />)
assert.equal(result.props.testID, testID)
})
test('prop "value"', () => {
const value = 'value'
const input = findShallowInput(utils.shallowRender(<TextInput value={value} />))

View File

@@ -1,5 +1,7 @@
import createNativeComponent from '../../modules/createNativeComponent'
import NativeMethodsDecorator from '../../modules/NativeMethodsDecorator'
import CoreComponent from '../CoreComponent'
import omit from 'lodash/omit'
import pick from 'lodash/pick'
import React, { Component, PropTypes } from 'react'
import ReactDOM from 'react-dom'
import StyleSheet from '../../apis/StyleSheet'
@@ -7,6 +9,9 @@ import Text from '../Text'
import TextareaAutosize from 'react-textarea-autosize'
import TextInputState from './TextInputState'
import View from '../View'
import ViewStylePropTypes from '../View/ViewStylePropTypes'
const viewStyleProps = Object.keys(ViewStylePropTypes)
@NativeMethodsDecorator
class TextInput extends Component {
@@ -32,7 +37,7 @@ class TextInput extends Component {
secureTextEntry: PropTypes.bool,
selectTextOnFocus: PropTypes.bool,
style: Text.propTypes.style,
testID: CoreComponent.propTypes.testID,
testID: Text.propTypes.testID,
value: PropTypes.string
};
@@ -66,50 +71,6 @@ class TextInput extends Component {
this.refs.input.setNativeProps(props)
}
_onBlur(e) {
const { onBlur } = this.props
const text = e.target.value
this.setState({ showPlaceholder: text === '' })
this.blur()
if (onBlur) onBlur(e)
}
_onChange(e) {
const { onChange, onChangeText } = this.props
const text = e.target.value
this.setState({ showPlaceholder: text === '' })
if (onChange) onChange(e)
if (onChangeText) onChangeText(text)
if (!this.refs.input) {
// calling `this.props.onChange` or `this.props.onChangeText`
// may clean up the input itself. Exits here.
return
}
}
_onFocus(e) {
const { clearTextOnFocus, onFocus, selectTextOnFocus } = this.props
const node = ReactDOM.findDOMNode(this.refs.input)
const text = e.target.value
this.focus()
if (onFocus) onFocus(e)
if (clearTextOnFocus) this.clear()
if (selectTextOnFocus) node.select()
this.setState({ showPlaceholder: text === '' })
}
_onSelectionChange(e) {
const { onSelectionChange } = this.props
const { selectionDirection, selectionEnd, selectionStart } = e.target
const event = {
selectionDirection,
selectionEnd,
selectionStart,
nativeEvent: e.nativeEvent
}
if (onSelectionChange) onSelectionChange(event)
}
render() {
const {
/* eslint-disable react/prop-types */
@@ -158,17 +119,23 @@ class TextInput extends Component {
type = 'password'
}
// In order to support 'Text' styles on 'TextInput', we split the 'Text'
// and 'View' styles and apply them to the 'Text' and 'View' components
// used in the implementation
const rootStyles = pick(style, viewStyleProps)
const textStyles = omit(style, viewStyleProps)
const propsCommon = {
autoComplete: autoComplete && 'on',
autoFocus,
defaultValue,
maxLength,
onBlur: this._onBlur.bind(this),
onChange: this._onChange.bind(this),
onFocus: this._onFocus.bind(this),
onSelect: onSelectionChange && this._onSelectionChange.bind(this),
onBlur: this._handleBlur,
onChange: this._handleChange,
onFocus: this._handleFocus,
onSelect: onSelectionChange && this._handleSelectionChange,
readOnly: !editable,
style: { ...styles.input, outline: style.outline },
style: { ...styles.input, ...textStyles, outline: style.outline },
value
}
@@ -187,28 +154,82 @@ class TextInput extends Component {
const props = multiline ? propsMultiline : propsSingleline
const optionalPlaceholder = placeholder && this.state.showPlaceholder && (
<View pointerEvents='none' style={styles.placeholder}>
<Text
children={placeholder}
style={[
styles.placeholderText,
textStyles,
placeholderTextColor && { color: placeholderTextColor }
]}
/>
</View>
)
return (
<View
accessibilityLabel={accessibilityLabel}
style={[
styles.initial,
style
]}
onClick={this._handleClick}
style={[ styles.initial, rootStyles ]}
testID={testID}
>
<View style={styles.wrapper}>
<CoreComponent {...props} ref='input' />
{placeholder && this.state.showPlaceholder && <Text
pointerEvents='none'
style={[
styles.placeholder,
placeholderTextColor && { color: placeholderTextColor }
]}
>{placeholder}</Text>}
{createNativeComponent({ ...props, ref: 'input' })}
{optionalPlaceholder}
</View>
</View>
)
}
_handleBlur = (e) => {
const { onBlur } = this.props
const text = e.target.value
this.setState({ showPlaceholder: text === '' })
this.blur()
if (onBlur) onBlur(e)
}
_handleChange = (e) => {
const { onChange, onChangeText } = this.props
const text = e.target.value
this.setState({ showPlaceholder: text === '' })
if (onChange) onChange(e)
if (onChangeText) onChangeText(text)
if (!this.refs.input) {
// calling `this.props.onChange` or `this.props.onChangeText`
// may clean up the input itself. Exits here.
return
}
}
_handleClick = (e) => {
this.focus()
}
_handleFocus = (e) => {
const { clearTextOnFocus, onFocus, selectTextOnFocus } = this.props
const node = ReactDOM.findDOMNode(this.refs.input)
const text = e.target.value
if (onFocus) onFocus(e)
if (clearTextOnFocus) this.clear()
if (selectTextOnFocus) node.select()
this.setState({ showPlaceholder: text === '' })
}
_handleSelectionChange = (e) => {
const { onSelectionChange } = this.props
try {
const { selectionDirection, selectionEnd, selectionStart } = e.target
const event = {
selectionDirection,
selectionEnd,
selectionStart,
nativeEvent: e.nativeEvent
}
if (onSelectionChange) onSelectionChange(event)
} catch (e) {}
}
}
const styles = StyleSheet.create({
@@ -232,12 +253,15 @@ const styles = StyleSheet.create({
},
placeholder: {
bottom: 0,
color: 'darkgray',
justifyContent: 'center',
left: 0,
overflow: 'hidden',
position: 'absolute',
right: 0,
top: 0,
top: 0
},
placeholderText: {
color: 'darkgray',
overflow: 'hidden',
whiteSpace: 'pre'
}
})

View File

@@ -1,17 +1,26 @@
/* eslint-disable */
/**
* Copyright (c) 2015-present, Facebook, Inc.
* Copyright (c) 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 Touchable
*/
'use strict';
/* @edit start */
var BoundingDimensions = require('./BoundingDimensions');
var Position = require('./Position');
var TouchEventUtils = require('fbjs/lib/TouchEventUtils');
var keyMirror = require('fbjs/lib/keyMirror');
var UIManager = require('../../apis/UIManager');
const BoundingDimensions = require('./BoundingDimensions');
const keyMirror = require('fbjs/lib/keyMirror');
const normalizeColor = require('../../apis/StyleSheet/normalizeColor');
const Position = require('./Position');
const React = require('react');
const TouchEventUtils = require('fbjs/lib/TouchEventUtils');
const UIManager = require('../../apis/UIManager');
const View = require('../../components/View');
/* @edit end */
/**
@@ -353,10 +362,10 @@ var TouchableMixin = {
/**
* Place as callback for a DOM element's `onResponderGrant` event.
* @param {SyntheticEvent} e Synthetic event from event system.
* @param {string} dispatchID ID of node that e was dispatched to.
*
*/
touchableHandleResponderGrant: function(e, dispatchID) {
touchableHandleResponderGrant: function(e) {
var dispatchID = e.currentTarget;
// Since e is used in a callback invoked on another event loop
// (as in setTimeout etc), we need to call e.persist() on the
// event to make sure it doesn't get reused in the event object pool.
@@ -717,7 +726,38 @@ var TouchableMixin = {
};
var Touchable = {
Mixin: TouchableMixin
Mixin: TouchableMixin,
TOUCH_TARGET_DEBUG: false, // Highlights all touchable targets. Toggle with Inspector.
/**
* Renders a debugging overlay to visualize touch target with hitSlop (might not work on Android).
*/
renderDebugView: ({color, hitSlop}) => {
if (!Touchable.TOUCH_TARGET_DEBUG) {
return null;
}
if (!__DEV__) {
throw Error('Touchable.TOUCH_TARGET_DEBUG should not be enabled in prod!');
}
const debugHitSlopStyle = {};
hitSlop = hitSlop || {top: 0, bottom: 0, left: 0, right: 0};
for (const key in hitSlop) {
debugHitSlopStyle[key] = -hitSlop[key];
}
const hexColor = '#' + ('00000000' + normalizeColor(color).toString(16)).substr(-8);
return (
<View
pointerEvents="none"
style={{
position: 'absolute',
borderColor: hexColor.slice(0, -2) + '55', // More opaque
borderWidth: 1,
borderStyle: 'dashed',
backgroundColor: hexColor.slice(0, -2) + '0F', // Less opaque
...debugHitSlopStyle
}}
/>
);
}
};
module.exports = Touchable;

View File

@@ -12,7 +12,7 @@
*/
'use strict';
var Animated = require('../../apis/Animated');
var Animated = require('animated');
var EdgeInsetsPropType = require('../../apis/StyleSheet/EdgeInsetsPropType');
var NativeMethodsMixin = require('../../modules/NativeMethodsMixin');
var React = require('react');

View File

@@ -93,21 +93,22 @@ var TouchableHighlight = React.createClass({
getDefaultProps: () => DEFAULT_PROPS,
// Performance optimization to avoid constantly re-generating these objects.
computeSyntheticState: function(props) {
computeSyntheticState: (props) => {
const { activeOpacity, style, underlayColor } = props;
return {
activeProps: {
style: {
opacity: props.activeOpacity,
opacity: activeOpacity,
}
},
activeUnderlayProps: {
style: {
backgroundColor: props.underlayColor,
backgroundColor: underlayColor,
}
},
underlayProps: {
style: {
backgroundColor: props.style.backgroundColor || null
backgroundColor: style && style.backgroundColor || null
}
}
};
@@ -205,7 +206,7 @@ var TouchableHighlight = React.createClass({
this._hideTimeout = null;
if (this._hasPressHandler() && this.refs[UNDERLAY_REF]) {
this.refs[CHILD_REF].setNativeProps(INACTIVE_CHILD_PROPS);
this.refs[UNDERLAY_REF].setNativeProps(this.state.underlayStyle);
this.refs[UNDERLAY_REF].setNativeProps(this.state.underlayProps);
this.props.onHideUnderlay && this.props.onHideUnderlay();
}
},

View File

@@ -14,7 +14,7 @@
// Note (avik): add @flow when Flow supports spread properties in propTypes
var Animated = require('../../apis/Animated');
var Animated = require('animated');
var NativeMethodsMixin = require('../../modules/NativeMethodsMixin');
var React = require('react');
var StyleSheet = require('../../apis/StyleSheet');

View File

@@ -145,7 +145,7 @@ var TouchableWithoutFeedback = React.createClass({
render: function(): ReactElement {
// Note(avik): remove dynamic typecast once Flow has been upgraded
return (React: any).cloneElement(React.children.only(this.props.children), {
return (React: any).cloneElement(React.Children.only(this.props.children), {
accessible: this.props.accessible !== false,
accessibilityLabel: this.props.accessibilityLabel,
accessibilityRole: this.props.accessibilityRole,

View File

@@ -3,35 +3,10 @@
import * as utils from '../../../modules/specHelpers'
import assert from 'assert'
import React from 'react'
import StyleSheet from '../../../apis/StyleSheet'
import View from '../'
suite('components/View', () => {
test('prop "accessibilityLabel"', () => {
const accessibilityLabel = 'accessibilityLabel'
const result = utils.shallowRender(<View accessibilityLabel={accessibilityLabel} />)
assert.equal(result.props.accessibilityLabel, accessibilityLabel)
})
test('prop "accessibilityLiveRegion"', () => {
const accessibilityLiveRegion = 'polite'
const result = utils.shallowRender(<View accessibilityLiveRegion={accessibilityLiveRegion} />)
assert.equal(result.props.accessibilityLiveRegion, accessibilityLiveRegion)
})
test('prop "accessibilityRole"', () => {
const accessibilityRole = 'accessibilityRole'
const result = utils.shallowRender(<View accessibilityRole={accessibilityRole} />)
assert.equal(result.props.accessibilityRole, accessibilityRole)
})
test('prop "accessible"', () => {
const accessible = false
const result = utils.shallowRender(<View accessible={accessible} />)
assert.equal(result.props.accessible, accessible)
})
test('prop "children"', () => {
const children = 'children'
const result = utils.shallowRender(<View>{children}</View>)
@@ -40,12 +15,20 @@ suite('components/View', () => {
test('prop "pointerEvents"', () => {
const result = utils.shallowRender(<View pointerEvents='box-only' />)
assert.equal(StyleSheet.flatten(result.props.style).pointerEvents, 'box-only')
assert.equal(result.props.className, '__style_pebo')
})
test('prop "testID"', () => {
const testID = 'testID'
const result = utils.shallowRender(<View testID={testID} />)
assert.equal(result.props.testID, testID)
test('prop "style"', () => {
const noFlex = utils.shallowRender(<View />)
assert.equal(noFlex.props.style.flexShrink, 0)
const flex = utils.shallowRender(<View style={{ flex: 1 }} />)
assert.equal(flex.props.style.flexShrink, 1)
const flexShrink = utils.shallowRender(<View style={{ flexShrink: 1 }} />)
assert.equal(flexShrink.props.style.flexShrink, 1)
const flexAndShrink = utils.shallowRender(<View style={{ flex: 1, flexShrink: 2 }} />)
assert.equal(flexAndShrink.props.style.flexShrink, 2)
})
})

View File

@@ -1,7 +1,7 @@
import createNativeComponent from '../../modules/createNativeComponent'
import NativeMethodsDecorator from '../../modules/NativeMethodsDecorator'
import normalizeNativeEvent from '../../apis/PanResponder/normalizeNativeEvent'
import CoreComponent from '../CoreComponent'
import React, { Component, PropTypes } from 'react'
import { Component, PropTypes } from 'react'
import StyleSheet from '../../apis/StyleSheet'
import StyleSheetPropType from '../../apis/StyleSheet/StyleSheetPropType'
import ViewStylePropTypes from './ViewStylePropTypes'
@@ -9,10 +9,10 @@ import ViewStylePropTypes from './ViewStylePropTypes'
@NativeMethodsDecorator
class View extends Component {
static propTypes = {
accessibilityLabel: CoreComponent.propTypes.accessibilityLabel,
accessibilityLiveRegion: CoreComponent.propTypes.accessibilityLiveRegion,
accessibilityRole: CoreComponent.propTypes.accessibilityRole,
accessible: CoreComponent.propTypes.accessible,
accessibilityLabel: createNativeComponent.propTypes.accessibilityLabel,
accessibilityLiveRegion: createNativeComponent.propTypes.accessibilityLiveRegion,
accessibilityRole: createNativeComponent.propTypes.accessibilityRole,
accessible: createNativeComponent.propTypes.accessible,
children: PropTypes.any,
onClick: PropTypes.func,
onClickCapture: PropTypes.func,
@@ -36,7 +36,7 @@ class View extends Component {
onTouchStartCapture: PropTypes.func,
pointerEvents: PropTypes.oneOf(['auto', 'box-none', 'box-only', 'none']),
style: StyleSheetPropType(ViewStylePropTypes),
testID: CoreComponent.propTypes.testID
testID: createNativeComponent.propTypes.testID
};
static defaultProps = {
@@ -59,28 +59,28 @@ class View extends Component {
const flattenedStyle = StyleSheet.flatten(style)
const pointerEventsStyle = pointerEvents && { pointerEvents }
return (
<CoreComponent
{...other}
onClick={this._handleClick}
onClickCapture={this._normalizeEventForHandler(this.props.onClickCapture)}
onTouchCancel={this._normalizeEventForHandler(this.props.onTouchCancel)}
onTouchCancelCapture={this._normalizeEventForHandler(this.props.onTouchCancelCapture)}
onTouchEnd={this._normalizeEventForHandler(this.props.onTouchEnd)}
onTouchEndCapture={this._normalizeEventForHandler(this.props.onTouchEndCapture)}
onTouchMove={this._normalizeEventForHandler(this.props.onTouchMove)}
onTouchMoveCapture={this._normalizeEventForHandler(this.props.onTouchMoveCapture)}
onTouchStart={this._normalizeEventForHandler(this.props.onTouchStart)}
onTouchStartCapture={this._normalizeEventForHandler(this.props.onTouchStartCapture)}
style={[
styles.initial,
style,
// 'View' needs to use 'flexShrink' in its reset when there is no 'flex' style provided
flattenedStyle.flex == null && styles.flexReset,
pointerEventsStyle
]}
/>
)
const props = {
...other,
onClick: this._normalizeEventForHandler(this.props.onClick),
onClickCapture: this._normalizeEventForHandler(this.props.onClickCapture),
onTouchCancel: this._normalizeEventForHandler(this.props.onTouchCancel),
onTouchCancelCapture: this._normalizeEventForHandler(this.props.onTouchCancelCapture),
onTouchEnd: this._normalizeEventForHandler(this.props.onTouchEnd),
onTouchEndCapture: this._normalizeEventForHandler(this.props.onTouchEndCapture),
onTouchMove: this._normalizeEventForHandler(this.props.onTouchMove),
onTouchMoveCapture: this._normalizeEventForHandler(this.props.onTouchMoveCapture),
onTouchStart: this._normalizeEventForHandler(this.props.onTouchStart),
onTouchStartCapture: this._normalizeEventForHandler(this.props.onTouchStartCapture),
style: [
styles.initial,
style,
// 'View' needs to use 'flexShrink' in its reset when there is no 'flex' style provided
(flattenedStyle.flex == null && flattenedStyle.flexShrink == null) && styles.flexReset,
pointerEventsStyle
]
}
return createNativeComponent(props)
}
/**

View File

@@ -1,16 +1,16 @@
import React from 'react'
import './apis/PanResponder/injectResponderEventPlugin'
import findNodeHandle from './modules/findNodeHandle'
import ReactDOM from 'react-dom'
import ReactDOMServer from 'react-dom/server'
import './apis/PanResponder/injectResponderEventPlugin'
// apis
import Animated from './apis/Animated'
import Animated from 'animated'
import AppRegistry from './apis/AppRegistry'
import AppState from './apis/AppState'
import AsyncStorage from './apis/AsyncStorage'
import Dimensions from './apis/Dimensions'
import Easing from './apis/Easing'
import Easing from 'animated/lib/Easing'
import InteractionManager from './apis/InteractionManager'
import NetInfo from './apis/NetInfo'
import PanResponder from './apis/PanResponder'
@@ -43,9 +43,24 @@ import ColorPropType from './apis/StyleSheet/ColorPropType'
import EdgeInsetsPropType from './apis/StyleSheet/EdgeInsetsPropType'
import PointPropType from './apis/StyleSheet/PointPropType'
Animated.inject.FlattenStyle(StyleSheet.flatten)
const ReactNative = {
// top-level API
findNodeHandle,
render: ReactDOM.render,
unmountComponentAtNode: ReactDOM.unmountComponentAtNode,
// web-only
renderToStaticMarkup: ReactDOMServer.renderToStaticMarkup,
renderToString: ReactDOMServer.renderToString,
// apis
Animated,
Animated: {
...Animated,
Image: Animated.createAnimatedComponent(Image),
Text: Animated.createAnimatedComponent(Text),
View: Animated.createAnimatedComponent(View)
},
AppRegistry,
AppState,
AsyncStorage,
@@ -80,12 +95,7 @@ const ReactNative = {
// propTypes
ColorPropType,
EdgeInsetsPropType,
PointPropType,
// React
...React,
...ReactDOM,
...ReactDOMServer
PointPropType
}
module.exports = ReactNative

View File

@@ -103,7 +103,8 @@ const NativeMethodsMixin = {
setNativeProps(nativeProps: Object) {
UIManager.updateView(
ReactDOM.findDOMNode(this),
nativeProps
nativeProps,
this
)
}
}

View File

@@ -1,62 +1,61 @@
/* eslint-env mocha */
import * as utils from '../../../modules/specHelpers'
import * as utils from '../../specHelpers'
import assert from 'assert'
import React from 'react'
import CoreComponent from '../'
import createNativeComponent from '../'
suite('components/CoreComponent', () => {
suite('modules/createNativeComponent', () => {
test('prop "accessibilityLabel"', () => {
const accessibilityLabel = 'accessibilityLabel'
const dom = utils.renderToDOM(<CoreComponent accessibilityLabel={accessibilityLabel} />)
const dom = utils.renderToDOM(createNativeComponent({ accessibilityLabel }))
assert.equal(dom.getAttribute('aria-label'), accessibilityLabel)
})
test('prop "accessibilityLiveRegion"', () => {
const accessibilityLiveRegion = 'polite'
const dom = utils.renderToDOM(<CoreComponent accessibilityLiveRegion={accessibilityLiveRegion} />)
const dom = utils.renderToDOM(createNativeComponent({ accessibilityLiveRegion }))
assert.equal(dom.getAttribute('aria-live'), accessibilityLiveRegion)
})
test('prop "accessibilityRole"', () => {
const accessibilityRole = 'banner'
let dom = utils.renderToDOM(<CoreComponent accessibilityRole={accessibilityRole} />)
let dom = utils.renderToDOM(createNativeComponent({ accessibilityRole }))
assert.equal(dom.getAttribute('role'), accessibilityRole)
assert.equal((dom.tagName).toLowerCase(), 'header')
const button = 'button'
dom = utils.renderToDOM(<CoreComponent accessibilityRole={button} />)
dom = utils.renderToDOM(createNativeComponent({ accessibilityRole: 'button' }))
assert.equal(dom.getAttribute('type'), button)
assert.equal((dom.tagName).toLowerCase(), button)
})
test('prop "accessible"', () => {
// accessible (implicit)
let dom = utils.renderToDOM(<CoreComponent />)
let dom = utils.renderToDOM(createNativeComponent({}))
assert.equal(dom.getAttribute('aria-hidden'), null)
// accessible (explicit)
dom = utils.renderToDOM(<CoreComponent accessible />)
dom = utils.renderToDOM(createNativeComponent({ accessible: true }))
assert.equal(dom.getAttribute('aria-hidden'), null)
// not accessible
dom = utils.renderToDOM(<CoreComponent accessible={false} />)
dom = utils.renderToDOM(createNativeComponent({ accessible: false }))
assert.equal(dom.getAttribute('aria-hidden'), 'true')
})
test('prop "component"', () => {
const component = 'main'
const dom = utils.renderToDOM(<CoreComponent component={component} />)
const dom = utils.renderToDOM(createNativeComponent({ component }))
const tagName = (dom.tagName).toLowerCase()
assert.equal(tagName, component)
})
test('prop "testID"', () => {
// no testID
let dom = utils.renderToDOM(<CoreComponent />)
let dom = utils.renderToDOM(createNativeComponent({}))
assert.equal(dom.getAttribute('data-testid'), null)
// with testID
const testID = 'Example.testID'
dom = utils.renderToDOM(<CoreComponent testID={testID} />)
dom = utils.renderToDOM(createNativeComponent({ testID }))
assert.equal(dom.getAttribute('data-testid'), testID)
})
})

View File

@@ -0,0 +1,57 @@
import React, { PropTypes } from 'react'
import StyleSheet from '../../apis/StyleSheet'
const roleComponents = {
article: 'article',
banner: 'header',
button: 'button',
complementary: 'aside',
contentinfo: 'footer',
form: 'form',
heading: 'h1',
link: 'a',
list: 'ul',
listitem: 'li',
main: 'main',
navigation: 'nav',
region: 'section'
}
const createNativeComponent = ({
accessibilityLabel,
accessibilityLiveRegion,
accessibilityRole,
accessible = true,
component = 'div',
testID,
type,
...other
}) => {
const Component = accessibilityRole && roleComponents[accessibilityRole] ? roleComponents[accessibilityRole] : component
return (
<Component
{...other}
{...StyleSheet.resolve(other)}
aria-hidden={accessible ? null : true}
aria-label={accessibilityLabel}
aria-live={accessibilityLiveRegion}
data-testid={testID}
role={accessibilityRole}
type={accessibilityRole === 'button' ? 'button' : type}
/>
)
}
createNativeComponent.propTypes = {
accessibilityLabel: PropTypes.string,
accessibilityLiveRegion: PropTypes.oneOf([ 'assertive', 'off', 'polite' ]),
accessibilityRole: PropTypes.string,
accessible: PropTypes.bool,
component: PropTypes.oneOfType([ PropTypes.func, PropTypes.string ]),
style: PropTypes.oneOfType([ PropTypes.array, PropTypes.object ]),
testID: PropTypes.string,
type: PropTypes.string
}
module.exports = createNativeComponent

View File

@@ -0,0 +1,3 @@
import ReactDOM from 'react-dom'
const findNodeHandle = ReactDOM.findDOMNode
export default findNodeHandle

View File

@@ -0,0 +1,8 @@
function SetPolyfill() { this._cache = [] }
SetPolyfill.prototype.add = function (e) {
if (this._cache.indexOf(e) === -1) { this._cache.push(e) }
}
SetPolyfill.prototype.forEach = function (cb) {
this._cache.forEach(cb)
}
module.exports = SetPolyfill

View File

@@ -1,4 +1,5 @@
var webpack = require('webpack')
const path = require('path')
const webpack = require('webpack')
const DIST_DIRECTORY = './dist'
@@ -8,13 +9,18 @@ module.exports = {
},
output: {
filename: 'ReactNative.js',
library: 'React',
library: 'ReactNative',
libraryTarget: 'umd',
path: DIST_DIRECTORY
},
plugins: [
new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify('production') }),
new webpack.optimize.DedupePlugin(),
// https://github.com/animatedjs/animated/issues/40
new webpack.NormalModuleReplacementPlugin(
/es6-set/,
path.join(__dirname, 'src/modules/polyfills/Set.js')
),
new webpack.optimize.OccurenceOrderPlugin(),
new webpack.optimize.UglifyJsPlugin({
compress: {