From f6f8d30aba5a405427ab15b9b3c9351c630fb767 Mon Sep 17 00:00:00 2001 From: IjzerenHein Date: Fri, 11 Mar 2016 15:09:18 +0100 Subject: [PATCH] [fix] PanResponder improvements + mouse support - Adds `locationX` and `locationY` to touch events - Adds `timestamp` to the `touches` and `touchesChanged` data - Add mouse event support Close #94 --- src/apis/PanResponder/index.js | 36 +++++--- .../injectResponderEventPlugin.js | 49 ++--------- src/apis/PanResponder/normalizeNativeEvent.js | 84 +++++++++++++++++++ 3 files changed, 113 insertions(+), 56 deletions(-) create mode 100644 src/apis/PanResponder/normalizeNativeEvent.js diff --git a/src/apis/PanResponder/index.js b/src/apis/PanResponder/index.js index d6be5844..48c001ff 100644 --- a/src/apis/PanResponder/index.js +++ b/src/apis/PanResponder/index.js @@ -6,6 +6,7 @@ "use strict"; +import normalizeNativeEvent from './normalizeNativeEvent'; var TouchHistoryMath = require('./TouchHistoryMath'); var currentCentroidXOfTouchesChangedAfter = @@ -287,21 +288,26 @@ var PanResponder = { var panHandlers = { onStartShouldSetResponder: function(e) { return config.onStartShouldSetPanResponder === undefined ? false : - config.onStartShouldSetPanResponder(e, gestureState); + config.onStartShouldSetPanResponder(normalizeEvent(e), gestureState); }, onMoveShouldSetResponder: function(e) { return config.onMoveShouldSetPanResponder === undefined ? false : - config.onMoveShouldSetPanResponder(e, gestureState); + config.onMoveShouldSetPanResponder(normalizeEvent(e), gestureState); }, onStartShouldSetResponderCapture: function(e) { // TODO: Actually, we should reinitialize the state any time // touches.length increases from 0 active to > 0 active. - if (e.nativeEvent.touches.length === 1) { + if (e.nativeEvent.touches) { + if (e.nativeEvent.touches.length === 1) { + PanResponder._initializeGestureState(gestureState); + } + } + else if (e.nativeEvent.type === 'mousedown') { PanResponder._initializeGestureState(gestureState); } gestureState.numberActiveTouches = e.touchHistory.numberActiveTouches; return config.onStartShouldSetPanResponderCapture !== undefined ? - config.onStartShouldSetPanResponderCapture(e, gestureState) : false; + config.onStartShouldSetPanResponderCapture(normalizeEvent(e), gestureState) : false; }, onMoveShouldSetResponderCapture: function(e) { @@ -314,7 +320,7 @@ var PanResponder = { } PanResponder._updateGestureStateOnMove(gestureState, touchHistory); return config.onMoveShouldSetPanResponderCapture ? - config.onMoveShouldSetPanResponderCapture(e, gestureState) : false; + config.onMoveShouldSetPanResponderCapture(normalizeEvent(e), gestureState) : false; }, onResponderGrant: function(e) { @@ -322,25 +328,25 @@ var PanResponder = { gestureState.y0 = currentCentroidY(e.touchHistory); gestureState.dx = 0; gestureState.dy = 0; - config.onPanResponderGrant && config.onPanResponderGrant(e, gestureState); + config.onPanResponderGrant && config.onPanResponderGrant(normalizeEvent(e), gestureState); // TODO: t7467124 investigate if this can be removed return config.onShouldBlockNativeResponder === undefined ? true : config.onShouldBlockNativeResponder(); }, onResponderReject: function(e) { - config.onPanResponderReject && config.onPanResponderReject(e, gestureState); + config.onPanResponderReject && config.onPanResponderReject(normalizeEvent(e), gestureState); }, onResponderRelease: function(e) { - config.onPanResponderRelease && config.onPanResponderRelease(e, gestureState); + config.onPanResponderRelease && config.onPanResponderRelease(normalizeEvent(e), gestureState); PanResponder._initializeGestureState(gestureState); }, onResponderStart: function(e) { var touchHistory = e.touchHistory; gestureState.numberActiveTouches = touchHistory.numberActiveTouches; - config.onPanResponderStart && config.onPanResponderStart(e, gestureState); + config.onPanResponderStart && config.onPanResponderStart(normalizeEvent(e), gestureState); }, onResponderMove: function(e) { @@ -353,13 +359,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(e, gestureState); + config.onPanResponderMove && config.onPanResponderMove(normalizeEvent(e), gestureState); }, onResponderEnd: function(e) { var touchHistory = e.touchHistory; gestureState.numberActiveTouches = touchHistory.numberActiveTouches; - config.onPanResponderEnd && config.onPanResponderEnd(e, gestureState); + config.onPanResponderEnd && config.onPanResponderEnd(normalizeEvent(e), gestureState); }, onResponderTerminate: function(e) { @@ -370,11 +376,17 @@ var PanResponder = { onResponderTerminationRequest: function(e) { return config.onPanResponderTerminationRequest === undefined ? true : - config.onPanResponderTerminationRequest(e, gestureState); + config.onPanResponderTerminationRequest(normalizeEvent(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/apis/PanResponder/injectResponderEventPlugin.js b/src/apis/PanResponder/injectResponderEventPlugin.js index e464abe5..cc388e01 100644 --- a/src/apis/PanResponder/injectResponderEventPlugin.js +++ b/src/apis/PanResponder/injectResponderEventPlugin.js @@ -4,6 +4,7 @@ import EventConstants from 'react/lib/EventConstants' import EventPluginRegistry from 'react/lib/EventPluginRegistry' import ResponderEventPlugin from 'react/lib/ResponderEventPlugin' import ResponderTouchHistoryStore from 'react/lib/ResponderTouchHistoryStore' +import normalizeNativeEvent from './normalizeNativeEvent' const { topMouseDown, @@ -37,53 +38,13 @@ ResponderEventPlugin.eventTypes.selectionChangeShouldSetResponder.dependencies = ResponderEventPlugin.eventTypes.scrollShouldSetResponder.dependencies = [ topScroll ] ResponderEventPlugin.eventTypes.startShouldSetResponder.dependencies = startDependencies -// Mobile Safari re-uses touch objects, so we copy the properties we want and normalize the identifier -const normalizeTouches = (touches = []) => Array.prototype.slice.call(touches).map((touch) => { - const identifier = touch.identifier > 20 ? (touch.identifier % 20) : touch.identifier - - return { - clientX: touch.clientX, - clientY: touch.clientY, - force: touch.force, - identifier: identifier, - pageX: touch.pageX, - pageY: touch.pageY, - radiusX: touch.radiusX, - radiusY: touch.radiusY, - rotationAngle: touch.rotationAngle, - screenX: touch.screenX, - screenY: touch.screenY, - target: touch.target - } -}) - -const normalizeNativeEvent = (nativeEvent) => { - const changedTouches = normalizeTouches(nativeEvent.changedTouches) - const touches = normalizeTouches(nativeEvent.touches) - - const event = { - changedTouches, - pageX: nativeEvent.pageX, - pageY: nativeEvent.pageY, - target: nativeEvent.target, - // normalize the timestamp - // https://stackoverflow.com/questions/26177087/ios-8-mobile-safari-wrong-timestamp-on-touch-events - timestamp: Date.now(), - touches - } - - if (changedTouches[0]) { - event.identifier = changedTouches[0].identifier - event.pageX = changedTouches[0].pageX - event.pageY = changedTouches[0].pageY - } - - return event -} - const originalRecordTouchTrack = ResponderTouchHistoryStore.recordTouchTrack ResponderTouchHistoryStore.recordTouchTrack = (topLevelType, nativeEvent) => { + // Filter out mouse-move events when the mouse button is not down + if ((topLevelType === 'topMouseMove') && !ResponderTouchHistoryStore.touchHistory.touchBank.length) { + return + } originalRecordTouchTrack.call(ResponderTouchHistoryStore, topLevelType, normalizeNativeEvent(nativeEvent)) } diff --git a/src/apis/PanResponder/normalizeNativeEvent.js b/src/apis/PanResponder/normalizeNativeEvent.js new file mode 100644 index 00000000..cfd21cb4 --- /dev/null +++ b/src/apis/PanResponder/normalizeNativeEvent.js @@ -0,0 +1,84 @@ +// Mobile Safari re-uses touch objects, so we copy the properties we want and normalize the identifier +const normalizeTouches = (touches = []) => Array.prototype.slice.call(touches).map((touch) => { + const identifier = touch.identifier > 20 ? (touch.identifier % 20) : touch.identifier + + return { + clientX: touch.clientX, + clientY: touch.clientY, + force: touch.force, + identifier: identifier, + pageX: touch.pageX, + pageY: touch.pageY, + radiusX: touch.radiusX, + radiusY: touch.radiusY, + rotationAngle: touch.rotationAngle, + screenX: touch.screenX, + screenY: touch.screenY, + target: touch.target, + // normalize the timestamp + // https://stackoverflow.com/questions/26177087/ios-8-mobile-safari-wrong-timestamp-on-touch-events + timestamp: Date.now() + } +}) + +function normalizeTouchEvent(nativeEvent) { + const changedTouches = normalizeTouches(nativeEvent.changedTouches) + const touches = normalizeTouches(nativeEvent.touches) + + const event = { + changedTouches, + domEvent: nativeEvent, + pageX: nativeEvent.pageX, + pageY: nativeEvent.pageY, + target: nativeEvent.target, + // normalize the timestamp + // https://stackoverflow.com/questions/26177087/ios-8-mobile-safari-wrong-timestamp-on-touch-events + timestamp: Date.now(), + touches + } + + if (changedTouches[0]) { + event.identifier = changedTouches[0].identifier + event.pageX = changedTouches[0].pageX + event.pageY = changedTouches[0].pageY + const rect = changedTouches[0].target.getBoundingClientRect() + event.locationX = changedTouches[0].pageX - rect.left + event.locationY = changedTouches[0].pageY - rect.top + } + + return event +} + +function normalizeMouseEvent(nativeEvent) { + const touches = [{ + clientX: nativeEvent.clientX, + clientY: nativeEvent.clientY, + force: nativeEvent.force, + identifier: 0, + pageX: nativeEvent.pageX, + pageY: nativeEvent.pageY, + screenX: nativeEvent.screenX, + screenY: nativeEvent.screenY, + target: nativeEvent.target, + timestamp: nativeEvent.timestamp || Date.now() + }] + return { + changedTouches: touches, + domEvent: nativeEvent, + identifier: touches[0].identifier, + locationX: nativeEvent.offsetX, + locationY: nativeEvent.offsetY, + pageX: nativeEvent.pageX, + pageY: nativeEvent.pageY, + target: nativeEvent.target, + timestamp: touches[0].timestamp, + touches: (nativeEvent.type === 'mouseup') ? [] : touches + } +} + +function normalizeNativeEvent(nativeEvent) { + const mouse = nativeEvent.type.indexOf('mouse') >= 0 + return mouse ? normalizeMouseEvent(nativeEvent) : normalizeTouchEvent(nativeEvent) +} + +export default normalizeNativeEvent