From d54a84701a25ac3c9bb9e310cb03b2f097f6227a Mon Sep 17 00:00:00 2001 From: Nicolas Gallagher Date: Fri, 29 Jul 2016 14:00:35 -0700 Subject: [PATCH] [fix] ScrollView scrolling Scrolling is broken by the patch that adds ResponderEvent support for multi-input devices: 6a9212df4004a40ec96000fa9bc17847db89cf51 By calling 'preventDefault' on every touch event, scroll events were cancelled. This patch shifts the responsibility for calling 'preventDefault' to the 'View' event handler normalizer, and only on touch events within the Responder system. Fix #175 --- src/components/ScrollView/index.js | 2 +- src/components/View/index.js | 19 ++++++++++++++----- src/modules/injectResponderEventPlugin.js | 6 ------ src/modules/normalizeNativeEvent.js | 16 ++++++++++++---- 4 files changed, 27 insertions(+), 16 deletions(-) diff --git a/src/components/ScrollView/index.js b/src/components/ScrollView/index.js index e92e15e3..682d76dd 100644 --- a/src/components/ScrollView/index.js +++ b/src/components/ScrollView/index.js @@ -117,7 +117,7 @@ const ScrollView = React.createClass({ _handleContentOnLayout(e: Object) { const { width, height } = e.nativeEvent.layout - this.props.onContentSizeChange && this.props.onContentSizeChange(width, height) + this.props.onContentSizeChange(width, height) }, render() { diff --git a/src/components/View/index.js b/src/components/View/index.js index bd8d03bf..52ad4734 100644 --- a/src/components/View/index.js +++ b/src/components/View/index.js @@ -105,7 +105,7 @@ class View extends Component { const normalizedEventHandlers = eventHandlerNames.reduce((handlerProps, handlerName) => { const handler = this.props[handlerName] if (typeof handler === 'function') { - handlerProps[handlerName] = this._normalizeEventForHandler(handler) + handlerProps[handlerName] = this._normalizeEventForHandler(handler, handlerName) } return handlerProps }, {}) @@ -125,12 +125,21 @@ class View extends Component { return createReactDOMComponent(props) } - _normalizeEventForHandler(handler) { - const callback = (e) => { + _normalizeEventForHandler(handler, handlerName) { + // Browsers fire mouse events after touch events. This causes the + // ResponderEvents and their handlers to fire twice for Touchables. + // Auto-fix this issue by calling 'preventDefault' to cancel the mouse + // events. + const shouldCancelEvent = handlerName.indexOf('onResponder') === 0 + + return (e) => { e.nativeEvent = normalizeNativeEvent(e.nativeEvent) - return handler(e) + const returnValue = handler(e) + if (shouldCancelEvent && e.cancelable) { + e.preventDefault() + } + return returnValue } - return callback } } diff --git a/src/modules/injectResponderEventPlugin.js b/src/modules/injectResponderEventPlugin.js index adbe2a8a..69ecc308 100644 --- a/src/modules/injectResponderEventPlugin.js +++ b/src/modules/injectResponderEventPlugin.js @@ -45,12 +45,6 @@ ResponderTouchHistoryStore.recordTouchTrack = (topLevelType, nativeEvent) => { if ((topLevelType === topMouseMove) && !ResponderTouchHistoryStore.touchHistory.touchBank.length) { return } - // Cancel mouse events that browsers fire after touch events - if (topLevelType === topTouchStart || topLevelType === topTouchMove || topLevelType === topTouchEnd) { - if (nativeEvent.target.getAttribute('href') !== undefined) { - nativeEvent.preventDefault() - } - } const normalizedEvent = normalizeNativeEvent(nativeEvent) originalRecordTouchTrack.call(ResponderTouchHistoryStore, topLevelType, normalizedEvent) diff --git a/src/modules/normalizeNativeEvent.js b/src/modules/normalizeNativeEvent.js index fa8d8c67..4602fd94 100644 --- a/src/modules/normalizeNativeEvent.js +++ b/src/modules/normalizeNativeEvent.js @@ -7,6 +7,7 @@ const normalizeTouches = (touches = []) => Array.prototype.slice.call(touches).m const locationY = touch.pageY - rect.top return { + _normalized: true, clientX: touch.clientX, clientY: touch.clientY, force: touch.force, @@ -32,10 +33,13 @@ function normalizeTouchEvent(nativeEvent) { const touches = normalizeTouches(nativeEvent.touches) const event = { + _normalized: true, changedTouches, - originalEvent: nativeEvent, pageX: nativeEvent.pageX, pageY: nativeEvent.pageY, + preventDefault: nativeEvent.preventDefault.bind(nativeEvent), + stopImmediatePropagation: nativeEvent.stopImmediatePropagation.bind(nativeEvent), + stopPropagation: nativeEvent.stopPropagation.bind(nativeEvent), target: nativeEvent.target, // normalize the timestamp // https://stackoverflow.com/questions/26177087/ios-8-mobile-safari-wrong-timestamp-on-touch-events @@ -56,6 +60,7 @@ function normalizeTouchEvent(nativeEvent) { function normalizeMouseEvent(nativeEvent) { const touches = [{ + _normalized: true, clientX: nativeEvent.clientX, clientY: nativeEvent.clientY, force: nativeEvent.force, @@ -70,13 +75,16 @@ function normalizeMouseEvent(nativeEvent) { timestamp: Date.now() }] return { + _normalized: true, changedTouches: touches, identifier: touches[0].identifier, locationX: nativeEvent.offsetX, locationY: nativeEvent.offsetY, - originalEvent: nativeEvent, pageX: nativeEvent.pageX, pageY: nativeEvent.pageY, + preventDefault: nativeEvent.preventDefault.bind(nativeEvent), + stopImmediatePropagation: nativeEvent.stopImmediatePropagation.bind(nativeEvent), + stopPropagation: nativeEvent.stopPropagation.bind(nativeEvent), target: nativeEvent.target, timestamp: touches[0].timestamp, touches: (nativeEvent.type === 'mouseup') ? [] : touches @@ -84,8 +92,8 @@ function normalizeMouseEvent(nativeEvent) { } function normalizeNativeEvent(nativeEvent) { - if (nativeEvent.originalEvent) { return nativeEvent } - const eventType = nativeEvent.type || (nativeEvent.originalEvent && nativeEvent.originalEvent.type) || '' + if (nativeEvent._normalized) { return nativeEvent } + const eventType = nativeEvent.type || '' const mouse = eventType.indexOf('mouse') >= 0 return mouse ? normalizeMouseEvent(nativeEvent) : normalizeTouchEvent(nativeEvent) }