From 7aa760506a799b1e2a72217c4d2299c229359329 Mon Sep 17 00:00:00 2001 From: Nicolas Gallagher Date: Fri, 22 Jul 2016 11:28:08 -0700 Subject: [PATCH] [fix] improve event normalization coverage --- examples/PanResponder/PanResponderExample.js | 117 +++++++++++++++++++ src/apis/PanResponder/index.js | 31 ++--- src/components/View/index.js | 60 ++++++---- src/modules/normalizeNativeEvent.js | 1 + 4 files changed, 167 insertions(+), 42 deletions(-) create mode 100644 examples/PanResponder/PanResponderExample.js diff --git a/examples/PanResponder/PanResponderExample.js b/examples/PanResponder/PanResponderExample.js new file mode 100644 index 00000000..cd1905d8 --- /dev/null +++ b/examples/PanResponder/PanResponderExample.js @@ -0,0 +1,117 @@ +'use strict'; + +import { storiesOf, action } from '@kadira/storybook'; + +var React = require('react'); +var ReactNative = require('react-native'); +var { + PanResponder, + StyleSheet, + View +} = ReactNative; + +var CIRCLE_SIZE = 80; + +var PanResponderExample = React.createClass({ + _panResponder: {}, + _previousLeft: 0, + _previousTop: 0, + _circleStyles: {}, + circle: (null : ?{ setNativeProps(props: Object): void }), + + componentWillMount: function() { + this._panResponder = PanResponder.create({ + onStartShouldSetPanResponder: this._handleStartShouldSetPanResponder, + onMoveShouldSetPanResponder: this._handleMoveShouldSetPanResponder, + onPanResponderGrant: this._handlePanResponderGrant, + onPanResponderMove: this._handlePanResponderMove, + onPanResponderRelease: this._handlePanResponderEnd, + onPanResponderTerminate: this._handlePanResponderEnd, + }); + this._previousLeft = 20; + this._previousTop = 84; + this._circleStyles = { + style: { + left: this._previousLeft, + top: this._previousTop, + backgroundColor: 'green', + } + }; + }, + + componentDidMount: function() { + this._updateNativeStyles(); + }, + + render: function() { + return ( + + { + this.circle = circle; + }} + style={styles.circle} + {...this._panResponder.panHandlers} + /> + + ); + }, + + _highlight: function() { + this._circleStyles.style.backgroundColor = 'blue'; + this._updateNativeStyles(); + }, + + _unHighlight: function() { + this._circleStyles.style.backgroundColor = 'green'; + this._updateNativeStyles(); + }, + + _updateNativeStyles: function() { + this.circle && this.circle.setNativeProps(this._circleStyles); + }, + + _handleStartShouldSetPanResponder: function(e: Object, gestureState: Object): boolean { + // Should we become active when the user presses down on the circle? + return true; + }, + + _handleMoveShouldSetPanResponder: function(e: Object, gestureState: Object): boolean { + // Should we become active when the user moves a touch over the circle? + return false; + }, + + _handlePanResponderGrant: function(e: Object, gestureState: Object) { + this._highlight(); + }, + _handlePanResponderMove: function(e: Object, gestureState: Object) { + this._circleStyles.style.left = this._previousLeft + gestureState.dx; + this._circleStyles.style.top = this._previousTop + gestureState.dy; + this._updateNativeStyles(); + }, + _handlePanResponderEnd: function(e: Object, gestureState: Object) { + this._unHighlight(); + this._previousLeft += gestureState.dx; + this._previousTop += gestureState.dy; + }, +}); + +var styles = StyleSheet.create({ + circle: { + width: CIRCLE_SIZE, + height: CIRCLE_SIZE, + borderRadius: CIRCLE_SIZE / 2, + position: 'absolute', + left: 0, + top: 0, + }, + container: { + flex: 1, + paddingTop: 64, + }, +}); + + +storiesOf('PanResponder', module) + .add('example', () => ) diff --git a/src/apis/PanResponder/index.js b/src/apis/PanResponder/index.js index 00ce1303..e8a61591 100644 --- a/src/apis/PanResponder/index.js +++ b/src/apis/PanResponder/index.js @@ -6,7 +6,6 @@ "use strict"; -import normalizeNativeEvent from '../../modules/normalizeNativeEvent'; var TouchHistoryMath = require('react/lib/TouchHistoryMath'); var currentCentroidXOfTouchesChangedAfter = @@ -288,11 +287,11 @@ var PanResponder = { var panHandlers = { onStartShouldSetResponder: function(e) { return config.onStartShouldSetPanResponder === undefined ? false : - config.onStartShouldSetPanResponder(normalizeEvent(e), gestureState); + config.onStartShouldSetPanResponder(e, gestureState); }, onMoveShouldSetResponder: function(e) { return config.onMoveShouldSetPanResponder === undefined ? false : - config.onMoveShouldSetPanResponder(normalizeEvent(e), gestureState); + config.onMoveShouldSetPanResponder(e, gestureState); }, onStartShouldSetResponderCapture: function(e) { // TODO: Actually, we should reinitialize the state any time @@ -302,12 +301,12 @@ var PanResponder = { PanResponder._initializeGestureState(gestureState); } } - else if (e.nativeEvent.type === 'mousedown') { + else if (e.nativeEvent.originalEvent && e.nativeEvent.originalEvent.type === 'mousedown') { PanResponder._initializeGestureState(gestureState); } gestureState.numberActiveTouches = e.touchHistory.numberActiveTouches; return config.onStartShouldSetPanResponderCapture !== undefined ? - config.onStartShouldSetPanResponderCapture(normalizeEvent(e), gestureState) : false; + config.onStartShouldSetPanResponderCapture(e, gestureState) : false; }, onMoveShouldSetResponderCapture: function(e) { @@ -320,7 +319,7 @@ var PanResponder = { } PanResponder._updateGestureStateOnMove(gestureState, touchHistory); return config.onMoveShouldSetPanResponderCapture ? - config.onMoveShouldSetPanResponderCapture(normalizeEvent(e), gestureState) : false; + config.onMoveShouldSetPanResponderCapture(e, gestureState) : false; }, onResponderGrant: function(e) { @@ -328,25 +327,25 @@ var PanResponder = { gestureState.y0 = currentCentroidY(e.touchHistory); gestureState.dx = 0; gestureState.dy = 0; - config.onPanResponderGrant && config.onPanResponderGrant(normalizeEvent(e), gestureState); + config.onPanResponderGrant && config.onPanResponderGrant(e, gestureState); // TODO: t7467124 investigate if this can be removed return config.onShouldBlockNativeResponder === undefined ? true : config.onShouldBlockNativeResponder(); }, onResponderReject: function(e) { - config.onPanResponderReject && config.onPanResponderReject(normalizeEvent(e), gestureState); + config.onPanResponderReject && config.onPanResponderReject(e, gestureState); }, onResponderRelease: function(e) { - config.onPanResponderRelease && config.onPanResponderRelease(normalizeEvent(e), gestureState); + config.onPanResponderRelease && config.onPanResponderRelease(e, gestureState); PanResponder._initializeGestureState(gestureState); }, onResponderStart: function(e) { var touchHistory = e.touchHistory; gestureState.numberActiveTouches = touchHistory.numberActiveTouches; - config.onPanResponderStart && config.onPanResponderStart(normalizeEvent(e), gestureState); + config.onPanResponderStart && config.onPanResponderStart(e, gestureState); }, onResponderMove: function(e) { @@ -359,13 +358,13 @@ var PanResponder = { // Filter out any touch moves past the first one - we would have // already processed multi-touch geometry during the first event. PanResponder._updateGestureStateOnMove(gestureState, touchHistory); - config.onPanResponderMove && config.onPanResponderMove(normalizeEvent(e), gestureState); + config.onPanResponderMove && config.onPanResponderMove(e, gestureState); }, onResponderEnd: function(e) { var touchHistory = e.touchHistory; gestureState.numberActiveTouches = touchHistory.numberActiveTouches; - config.onPanResponderEnd && config.onPanResponderEnd(normalizeEvent(e), gestureState); + config.onPanResponderEnd && config.onPanResponderEnd(e, gestureState); }, onResponderTerminate: function(e) { @@ -376,17 +375,11 @@ var PanResponder = { onResponderTerminationRequest: function(e) { return config.onPanResponderTerminationRequest === undefined ? true : - config.onPanResponderTerminationRequest(normalizeEvent(e), gestureState); + config.onPanResponderTerminationRequest(e, gestureState); }, }; return {panHandlers: panHandlers}; }, }; -function normalizeEvent(e) { - const normalizedEvent = Object.create(e); - normalizedEvent.nativeEvent = normalizeNativeEvent(e.nativeEvent, e.type); - return normalizedEvent; -} - module.exports = PanResponder; diff --git a/src/components/View/index.js b/src/components/View/index.js index bca651e5..bd8d03bf 100644 --- a/src/components/View/index.js +++ b/src/components/View/index.js @@ -8,6 +8,29 @@ import StyleSheet from '../../apis/StyleSheet' import StyleSheetPropType from '../../propTypes/StyleSheetPropType' import ViewStylePropTypes from './ViewStylePropTypes' +const eventHandlerNames = [ + 'onClick', + 'onClickCapture', + 'onMoveShouldSetResponder', + 'onMoveShouldSetResponderCapture', + 'onResponderGrant', + 'onResponderMove', + 'onResponderReject', + 'onResponderRelease', + 'onResponderTerminate', + 'onResponderTerminationRequest', + 'onStartShouldSetResponder', + 'onStartShouldSetResponderCapture', + 'onTouchCancel', + 'onTouchCancelCapture', + 'onTouchEnd', + 'onTouchEndCapture', + 'onTouchMove', + 'onTouchMoveCapture', + 'onTouchStart', + 'onTouchStartCapture' +] + class View extends Component { static displayName = 'View' @@ -79,19 +102,18 @@ class View extends Component { // 'View' needs to set 'flexShrink:0' only when there is no 'flex' or 'flexShrink' style provided const needsFlexReset = flattenedStyle.flex == null && flattenedStyle.flexShrink == null + const normalizedEventHandlers = eventHandlerNames.reduce((handlerProps, handlerName) => { + const handler = this.props[handlerName] + if (typeof handler === 'function') { + handlerProps[handlerName] = this._normalizeEventForHandler(handler) + } + return handlerProps + }, {}) + const props = { ...other, + ...normalizedEventHandlers, component: this.context.isInAButtonView ? 'span' : 'div', - 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, @@ -103,23 +125,15 @@ class View extends Component { return createReactDOMComponent(props) } - /** - * React Native expects `pageX` and `pageY` to be on the `nativeEvent`, but - * React doesn't include them for touch events. - */ _normalizeEventForHandler(handler) { - return (e) => { - const { pageX } = e.nativeEvent - if (pageX === undefined) { - e.nativeEvent = normalizeNativeEvent(e.nativeEvent) - } - handler && handler(e) + const callback = (e) => { + e.nativeEvent = normalizeNativeEvent(e.nativeEvent) + return handler(e) } + return callback } } -applyLayout(applyNativeMethods(View)) - const styles = StyleSheet.create({ // https://github.com/facebook/css-layout#default-values initial: { @@ -150,4 +164,4 @@ const styles = StyleSheet.create({ } }) -module.exports = View +module.exports = applyLayout(applyNativeMethods(View)) diff --git a/src/modules/normalizeNativeEvent.js b/src/modules/normalizeNativeEvent.js index 4954568d..8310721d 100644 --- a/src/modules/normalizeNativeEvent.js +++ b/src/modules/normalizeNativeEvent.js @@ -84,6 +84,7 @@ function normalizeMouseEvent(nativeEvent) { } function normalizeNativeEvent(nativeEvent) { + if (nativeEvent.originalEvent) { return nativeEvent; } const eventType = nativeEvent.type || (nativeEvent.originalEvent && nativeEvent.originalEvent.type) || '' const mouse = eventType.indexOf('mouse') >= 0 return mouse ? normalizeMouseEvent(nativeEvent) : normalizeTouchEvent(nativeEvent)