[add] Update components with native methods

Hack in touch event normalization within `View` to produce events that
contain `pageX`, `pageY` for React Native compatibility.
This commit is contained in:
Nicolas Gallagher
2016-02-22 16:41:50 -08:00
parent 267a9b55bf
commit 4845de5cb5
14 changed files with 196 additions and 31 deletions

View File

@@ -3,5 +3,8 @@
"es2015",
"stage-1",
"react"
],
"plugins": [
"transform-decorators-legacy"
]
}

View File

@@ -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)

View File

@@ -31,7 +31,7 @@ module.exports = {
],
resolve: {
alias: {
'react-native': path.join(__dirname, '../dist/react-native-web')
'react-native': path.join(__dirname, '../src')
}
}
}

View File

@@ -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 {
</View>
)
}
_manageAnimation() {
if (this._player) {
if (this.props.animating) {
this._player.play()
} else {
this._player.cancel()
}
}
}
}
const styles = StyleSheet.create({

View File

@@ -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,

View File

@@ -0,0 +1,10 @@
import keyMirror from 'fbjs/lib/keyMirror';
const ImageResizeMode = keyMirror({
contain: null,
cover: null,
none: null,
stretch: null
})
export default ImageResizeMode

View File

@@ -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

View File

@@ -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,

View File

@@ -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'

View File

@@ -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 (
<View
_className='ScrollView'
onScroll={(e) => this._onScroll(e)}
onTouchMove={(e) => this._maybePreventScroll(e)}
onWheel={(e) => this._maybePreventScroll(e)}

View File

@@ -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,

View File

@@ -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

View File

@@ -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 (
<CoreComponent
{...other}
onClick={this._handleClick}
onClickCapture={this._handleClickCapture}
onTouchCancel={this._handleTouchCancel}
onTouchCancelCapture={this._handleTouchCancelCapture}
onTouchEnd={this._handleTouchEnd}
onTouchEndCapture={this._handleTouchEndCapture}
onTouchMove={this._handleTouchMove}
onTouchMoveCapture={this._handleTouchMoveCapture}
onTouchStart={this._handleTouchStart}
onTouchStartCapture={this._handleTouchStartCapture}
style={[
styles.initial,
style,
@@ -40,6 +86,80 @@ export default class View extends Component {
/>
)
}
/**
* 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({

View File

@@ -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