diff --git a/.babelrc b/.babelrc index a9d17f4e..4a129cb7 100644 --- a/.babelrc +++ b/.babelrc @@ -3,5 +3,8 @@ "es2015", "stage-1", "react" + ], + "plugins": [ + "transform-decorators-legacy" ] } diff --git a/README.md b/README.md index af4c6650..9f3fe869 100644 --- a/README.md +++ b/README.md @@ -100,12 +100,14 @@ Exported modules: * [`TouchableWithoutFeedback`](docs/components/TouchableWithoutFeedback.md) * [`View`](docs/components/View.md) * APIs + * [`Animated`](http://facebook.github.io/react-native/releases/0.20/docs/animated.html) (mirrors React Native) * [`AppRegistry`](docs/apis/AppRegistry.md) * [`AppState`](docs/apis/AppState.md) * [`AsyncStorage`](docs/apis/AsyncStorage.md) * [`Dimensions`](docs/apis/Dimensions.md) * [`NativeMethods`](docs/apis/NativeMethods.md) * [`NetInfo`](docs/apis/NetInfo.md) + * [`PanResponder`](http://facebook.github.io/react-native/releases/0.20/docs/panresponder.html#content) (mirrors React Native) * [`PixelRatio`](docs/apis/PixelRatio.md) * [`Platform`](docs/apis/Platform.md) * [`StyleSheet`](docs/apis/StyleSheet.md) diff --git a/config/webpack.config.example.js b/config/webpack.config.example.js index cebbaa72..0679084a 100644 --- a/config/webpack.config.example.js +++ b/config/webpack.config.example.js @@ -31,7 +31,7 @@ module.exports = { ], resolve: { alias: { - 'react-native': path.join(__dirname, '../dist/react-native-web') + 'react-native': path.join(__dirname, '../src') } } } diff --git a/src/components/ActivityIndicator/index.js b/src/components/ActivityIndicator/index.js index a26c5215..c2711328 100644 --- a/src/components/ActivityIndicator/index.js +++ b/src/components/ActivityIndicator/index.js @@ -1,3 +1,4 @@ +import { NativeMethodsDecorator } from '../../modules/NativeMethodsMixin' import React, { Component, PropTypes } from 'react' import ReactDOM from 'react-dom' import StyleSheet from '../../apis/StyleSheet' @@ -18,6 +19,7 @@ const keyframeEffects = [ { transform: 'scale(0.95)', opacity: 0.5 } ] +@NativeMethodsDecorator export default class ActivityIndicator extends Component { static propTypes = { animating: PropTypes.bool, @@ -39,19 +41,11 @@ export default class ActivityIndicator extends Component { if (document.documentElement.animate) { this._player = ReactDOM.findDOMNode(this._indicatorRef).animate(keyframeEffects, animationEffectTimingProperties) } - if (this.props.animating) { - this._player.play() - } else { - this._player.cancel() - } + this._manageAnimation() } componentDidUpdate() { - if (this.props.animating) { - this._player.play() - } else { - this._player.cancel() - } + this._manageAnimation() } render() { @@ -77,6 +71,16 @@ export default class ActivityIndicator extends Component { ) } + + _manageAnimation() { + if (this._player) { + if (this.props.animating) { + this._player.play() + } else { + this._player.cancel() + } + } + } } const styles = StyleSheet.create({ diff --git a/src/components/CoreComponent/index.js b/src/components/CoreComponent/index.js index 323bc163..54bb8f00 100644 --- a/src/components/CoreComponent/index.js +++ b/src/components/CoreComponent/index.js @@ -1,3 +1,4 @@ +import { NativeMethodsDecorator } from '../../modules/NativeMethodsMixin' import React, { Component, PropTypes } from 'react' import StyleSheet from '../../apis/StyleSheet' @@ -17,6 +18,7 @@ const roleComponents = { region: 'section' } +@NativeMethodsDecorator export default class CoreComponent extends Component { static propTypes = { accessibilityLabel: PropTypes.string, diff --git a/src/components/Image/ImageResizeMode.js b/src/components/Image/ImageResizeMode.js new file mode 100644 index 00000000..cd9b18ca --- /dev/null +++ b/src/components/Image/ImageResizeMode.js @@ -0,0 +1,10 @@ +import keyMirror from 'fbjs/lib/keyMirror'; + +const ImageResizeMode = keyMirror({ + contain: null, + cover: null, + none: null, + stretch: null +}) + +export default ImageResizeMode diff --git a/src/components/Image/index.js b/src/components/Image/index.js index 463c540a..f6198a01 100644 --- a/src/components/Image/index.js +++ b/src/components/Image/index.js @@ -1,6 +1,8 @@ /* global window */ +import { NativeMethodsDecorator } from '../../modules/NativeMethodsMixin' import StyleSheet from '../../apis/StyleSheet' import CoreComponent from '../CoreComponent' +import ImageResizeMode from './ImageResizeMode' import ImageStylePropTypes from './ImageStylePropTypes' import React, { Component, PropTypes } from 'react' import StyleSheetPropType from '../../apis/StyleSheet/StyleSheetPropType' @@ -12,17 +14,8 @@ const STATUS_LOADING = 'LOADING' const STATUS_PENDING = 'PENDING' const STATUS_IDLE = 'IDLE' +@NativeMethodsDecorator export default class Image extends Component { - constructor(props, context) { - super(props, context) - const { uri } = props.source - // state - this.state = { status: uri ? STATUS_PENDING : STATUS_IDLE } - // autobinding - this._onError = this._onError.bind(this) - this._onLoad = this._onLoad.bind(this) - } - static propTypes = { accessibilityLabel: CoreComponent.propTypes.accessibilityLabel, accessible: CoreComponent.propTypes.accessible, @@ -45,6 +38,18 @@ export default class Image extends Component { source: {} }; + static resizeMode = ImageResizeMode; + + constructor(props, context) { + super(props, context) + const { uri } = 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 { source } = this.props diff --git a/src/components/ListView/index.js b/src/components/ListView/index.js index 01896c75..9a913daa 100644 --- a/src/components/ListView/index.js +++ b/src/components/ListView/index.js @@ -1,6 +1,8 @@ +import { NativeMethodsDecorator } from '../../modules/NativeMethodsMixin' import React, { Component, PropTypes } from 'react' import ScrollView from '../ScrollView' +@NativeMethodsDecorator export default class ListView extends Component { static propTypes = { children: PropTypes.any, diff --git a/src/components/Portal/index.js b/src/components/Portal/index.js index 17bbf190..5d09ff70 100644 --- a/src/components/Portal/index.js +++ b/src/components/Portal/index.js @@ -6,7 +6,7 @@ * @flow */ -import invariant from 'invariant' +import invariant from 'fbjs/lib/invariant' import Platform from '../../apis/Platform' import React, { Component, PropTypes } from 'react' import StyleSheet from '../../apis/StyleSheet' diff --git a/src/components/ScrollView/index.js b/src/components/ScrollView/index.js index ab4b50bb..f0b0c11e 100644 --- a/src/components/ScrollView/index.js +++ b/src/components/ScrollView/index.js @@ -1,8 +1,10 @@ +import { NativeMethodsDecorator } from '../../modules/NativeMethodsMixin' import debounce from 'lodash.debounce' import React, { Component, PropTypes } from 'react' import StyleSheet from '../../apis/StyleSheet' import View from '../View' +@NativeMethodsDecorator export default class ScrollView extends Component { static propTypes = { children: PropTypes.any, @@ -91,7 +93,6 @@ export default class ScrollView extends Component { return ( this._onScroll(e)} onTouchMove={(e) => this._maybePreventScroll(e)} onWheel={(e) => this._maybePreventScroll(e)} diff --git a/src/components/Text/index.js b/src/components/Text/index.js index 9e047c76..b47836f3 100644 --- a/src/components/Text/index.js +++ b/src/components/Text/index.js @@ -1,9 +1,11 @@ +import { NativeMethodsDecorator } from '../../modules/NativeMethodsMixin' import CoreComponent from '../CoreComponent' import React, { Component, PropTypes } from 'react' import StyleSheet from '../../apis/StyleSheet' import StyleSheetPropType from '../../apis/StyleSheet/StyleSheetPropType' import TextStylePropTypes from './TextStylePropTypes' +@NativeMethodsDecorator export default class Text extends Component { static propTypes = { accessibilityLabel: CoreComponent.propTypes.accessibilityLabel, diff --git a/src/components/TextInput/index.js b/src/components/TextInput/index.js index 5f9737e7..4319d016 100644 --- a/src/components/TextInput/index.js +++ b/src/components/TextInput/index.js @@ -1,3 +1,4 @@ +import { NativeMethodsDecorator } from '../../modules/NativeMethodsMixin' import CoreComponent from '../CoreComponent' import React, { Component, PropTypes } from 'react' import ReactDOM from 'react-dom' @@ -6,12 +7,8 @@ import Text from '../Text' import TextareaAutosize from 'react-textarea-autosize' import View from '../View' +@NativeMethodsDecorator export default class TextInput extends Component { - constructor(props, context) { - super(props, context) - this.state = { showPlaceholder: !props.value && !props.defaultValue } - } - static propTypes = { ...View.propTypes, autoComplete: PropTypes.bool, @@ -47,6 +44,23 @@ export default class TextInput extends Component { style: {} }; + constructor(props, context) { + super(props, context) + this.state = { showPlaceholder: !props.value && !props.defaultValue } + } + + blur() { + this.refs.input.blur() + } + + focus() { + this.refs.input.focus() + } + + setNativeProps(props) { + this.refs.input.setNativeProps(props) + } + _onBlur(e) { const { onBlur } = this.props const value = e.target.value diff --git a/src/components/View/index.js b/src/components/View/index.js index 939ddf91..22610479 100644 --- a/src/components/View/index.js +++ b/src/components/View/index.js @@ -1,9 +1,11 @@ +import { NativeMethodsDecorator } from '../../modules/NativeMethodsMixin' import CoreComponent from '../CoreComponent' import React, { Component, PropTypes } from 'react' import StyleSheet from '../../apis/StyleSheet' import StyleSheetPropType from '../../apis/StyleSheet/StyleSheetPropType' import ViewStylePropTypes from './ViewStylePropTypes' +@NativeMethodsDecorator export default class View extends Component { static propTypes = { accessibilityLabel: CoreComponent.propTypes.accessibilityLabel, @@ -11,6 +13,26 @@ export default class View extends Component { accessibilityRole: CoreComponent.propTypes.accessibilityRole, accessible: CoreComponent.propTypes.accessible, children: PropTypes.any, + onClick: PropTypes.func, + onClickCapture: PropTypes.func, + onMoveShouldSetResponder: PropTypes.func, + onMoveShouldSetResponderCapture: PropTypes.func, + onResponderGrant: PropTypes.func, + onResponderMove: PropTypes.func, + onResponderReject: PropTypes.func, + onResponderRelease: PropTypes.func, + onResponderTerminate: PropTypes.func, + onResponderTerminationRequest: PropTypes.func, + onStartShouldSetResponder: PropTypes.func, + onStartShouldSetResponderCapture: PropTypes.func, + onTouchCancel: PropTypes.func, + onTouchCancelCapture: PropTypes.func, + onTouchEnd: PropTypes.func, + onTouchEndCapture: PropTypes.func, + onTouchMove: PropTypes.func, + onTouchMoveCapture: PropTypes.func, + onTouchStart: PropTypes.func, + onTouchStartCapture: PropTypes.func, pointerEvents: PropTypes.oneOf(['auto', 'box-none', 'box-only', 'none']), style: StyleSheetPropType(ViewStylePropTypes), testID: CoreComponent.propTypes.testID @@ -20,6 +42,20 @@ export default class View extends Component { accessible: true }; + constructor(props, context) { + super(props, context) + this._handleClick = this._handleClick.bind(this) + this._handleClickCapture = this._handleClickCapture.bind(this) + this._handleTouchCancel = this._handleTouchCancel.bind(this) + this._handleTouchCancelCapture = this._handleTouchCancelCapture.bind(this) + this._handleTouchEnd = this._handleTouchEnd.bind(this) + this._handleTouchEndCapture = this._handleTouchEndCapture.bind(this) + this._handleTouchMove = this._handleTouchMove.bind(this) + this._handleTouchMoveCapture = this._handleTouchMoveCapture.bind(this) + this._handleTouchStart = this._handleTouchStart.bind(this) + this._handleTouchStartCapture = this._handleTouchStartCapture.bind(this) + } + render() { const { pointerEvents, @@ -32,6 +68,16 @@ export default class View extends Component { return ( ) } + + /** + * React Native expects `pageX` and `pageY` to be on the `nativeEvent`, but + * React doesn't include them for touch events. + */ + _normalizeTouchEvent(event) { + const { pageX, changedTouches } = event.nativeEvent + if (pageX === undefined) { + const { pageX, pageY } = changedTouches[0] + event.nativeEvent.pageX = pageX + event.nativeEvent.pageY = pageY + } + return event + } + + _handleClick(e) { + if (this.props.onClick) { + this.props.onClick(this._normalizeTouchEvent(e)) + } + } + + _handleClickCapture(e) { + if (this.props.onClickCapture) { + this.props.onClickCapture(this._normalizeTouchEvent(e)) + } + } + + _handleTouchCancel(e) { + if (this.props.onTouchCancel) { + this.props.onTouchCancel(this._normalizeTouchEvent(e)) + } + } + + _handleTouchCancelCapture(e) { + if (this.props.onTouchCancelCapture) { + this.props.onTouchCancelCapture(this._normalizeTouchEvent(e)) + } + } + + _handleTouchEnd(e) { + if (this.props.onTouchEnd) { + this.props.onTouchEnd(this._normalizeTouchEvent(e)) + } + } + + _handleTouchEndCapture(e) { + if (this.props.onTouchEndCapture) { + this.props.onTouchEndCapture(this._normalizeTouchEvent(e)) + } + } + + _handleTouchMove(e) { + if (this.props.onTouchMove) { + this.props.onTouchMove(this._normalizeTouchEvent(e)) + } + } + + _handleTouchMoveCapture(e) { + if (this.props.onTouchMoveCapture) { + this.props.onTouchMoveCapture(this._normalizeTouchEvent(e)) + } + } + + _handleTouchStart(e) { + if (this.props.onTouchStart) { + this.props.onTouchStart(this._normalizeTouchEvent(e)) + } + } + + _handleTouchStartCapture(e) { + if (this.props.onTouchStartCapture) { + this.props.onTouchStartCapture(this._normalizeTouchEvent(e)) + } + } } const styles = StyleSheet.create({ diff --git a/src/modules/NativeMethodsMixin/index.js b/src/modules/NativeMethodsMixin/index.js index 503cf0d8..aadbf528 100644 --- a/src/modules/NativeMethodsMixin/index.js +++ b/src/modules/NativeMethodsMixin/index.js @@ -90,11 +90,11 @@ const mountSafeCallback = (context: Component, callback: ?Function) => () => { return callback.apply(context, arguments) } -export default NativeMethodsMixin - -export const nativeMethodsDecorator = (Component) => { +export const NativeMethodsDecorator = (Component) => { Object.keys(NativeMethodsMixin).forEach((method) => { Component.prototype[method] = NativeMethodsMixin[method] }) return Component } + +export default NativeMethodsMixin