Skip to content

Commit

Permalink
Allow user-specified click rejection
Browse files Browse the repository at this point in the history
There are occasions where you might want to reject clicks using a
different strategy than just based on time-between-events. This
allows the consumer to supply their own way of deciding whether or
not to trigger the click event.
  • Loading branch information
tomhicks-bsf committed Jul 8, 2015
1 parent 341ba52 commit 9915966
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 56 deletions.
101 changes: 49 additions & 52 deletions src/TapEventPlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
* @typechecks static-only
*/

"use strict";
'use strict';

var EventConstants = require('react/lib/EventConstants');
var EventPluginUtils = require('react/lib/EventPluginUtils');
Expand All @@ -40,14 +40,13 @@ var isTouch = function(topLevelType) {
topLevelTypes.topTouchMove
];
return touchTypes.indexOf(topLevelType) >= 0;
}
};

/**
* Number of pixels that are tolerated in between a `touchStart` and `touchEnd`
* in order to still be considered a 'tap' event.
*/
var tapMoveThreshold = 10;
var ignoreMouseThreshold = 750;
var startCoords = {x: null, y: null};
var lastTouchEvent = null;

Expand Down Expand Up @@ -105,64 +104,62 @@ var now = (function() {
} else {
// IE8 support: http://stackoverflow.com/questions/9430357/please-explain-why-and-how-new-date-works-as-workaround-for-date-now-in
return function () {
return +new Date;
}
return +(new Date());
};
}
})();

var TapEventPlugin = {
module.exports = function buildTapEventPlugin (shouldRejectClick) {

tapMoveThreshold: tapMoveThreshold,
return {

ignoreMouseThreshold: ignoreMouseThreshold,
tapMoveThreshold: tapMoveThreshold,

eventTypes: eventTypes,
eventTypes: eventTypes,

/**
* @param {string} topLevelType Record from `EventConstants`.
* @param {DOMEventTarget} topLevelTarget The listening component root node.
* @param {string} topLevelTargetID ID of `topLevelTarget`.
* @param {object} nativeEvent Native browser event.
* @return {*} An accumulation of synthetic events.
* @see {EventPluginHub.extractEvents}
*/
extractEvents: function(
topLevelType,
topLevelTarget,
topLevelTargetID,
nativeEvent) {
/**
* @param {string} topLevelType Record from `EventConstants`.
* @param {DOMEventTarget} topLevelTarget The listening component root node.
* @param {string} topLevelTargetID ID of `topLevelTarget`.
* @param {object} nativeEvent Native browser event.
* @return {*} An accumulation of synthetic events.
* @see {EventPluginHub.extractEvents}
*/
extractEvents: function(
topLevelType,
topLevelTarget,
topLevelTargetID,
nativeEvent) {

if (isTouch(topLevelType)) {
lastTouchEvent = now();
} else {
if (shouldRejectClick(lastTouchEvent, now())) {
return null;
}
}

if (isTouch(topLevelType)) {
lastTouchEvent = now();
} else {
if (lastTouchEvent && (now() - lastTouchEvent) < ignoreMouseThreshold) {
if (!isStartish(topLevelType) && !isEndish(topLevelType)) {
return null;
}
var event = null;
var distance = getDistance(startCoords, nativeEvent);
if (isEndish(topLevelType) && distance < tapMoveThreshold) {
event = SyntheticUIEvent.getPooled(
eventTypes.touchTap,
topLevelTargetID,
nativeEvent
);
}
if (isStartish(topLevelType)) {
startCoords.x = getAxisCoordOfEvent(Axis.x, nativeEvent);
startCoords.y = getAxisCoordOfEvent(Axis.y, nativeEvent);
} else if (isEndish(topLevelType)) {
startCoords.x = 0;
startCoords.y = 0;
}
EventPropagators.accumulateTwoPhaseDispatches(event);
return event;
}

if (!isStartish(topLevelType) && !isEndish(topLevelType)) {
return null;
}
var event = null;
var distance = getDistance(startCoords, nativeEvent);
if (isEndish(topLevelType) && distance < tapMoveThreshold) {
event = SyntheticUIEvent.getPooled(
eventTypes.touchTap,
topLevelTargetID,
nativeEvent
);
}
if (isStartish(topLevelType)) {
startCoords.x = getAxisCoordOfEvent(Axis.x, nativeEvent);
startCoords.y = getAxisCoordOfEvent(Axis.y, nativeEvent);
} else if (isEndish(topLevelType)) {
startCoords.x = 0;
startCoords.y = 0;
}
EventPropagators.accumulateTwoPhaseDispatches(event);
return event;
}

};
};

module.exports = TapEventPlugin;
3 changes: 3 additions & 0 deletions src/defaultClickRejectionStrategy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = function rejectGhostClicks (lastTouchEventTimestamp, clickTimestamp) {
return lastTouchEventTimestamp && (clickTimestamp - lastTouchEventTimestamp < 750);
}
16 changes: 12 additions & 4 deletions src/injectTapEventPlugin.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
module.exports = function injectTapEventPlugin () {
"use strict";

var defaultClickRejectionStrategy = require("./defaultClickRejectionStrategy");

module.exports = function injectTapEventPlugin (strategyOverrides) {
strategyOverrides = strategyOverrides || {};

var React = require("react");
React.initializeTouchEvents(true);

require('react/lib/EventPluginHub').injection.injectEventPluginsByName({
"ResponderEventPlugin": require('./ResponderEventPlugin.js'),
"TapEventPlugin": require('./TapEventPlugin.js')
var shouldRejectClick = strategyOverrides.shouldRejectClick || defaultClickRejectionStrategy;

require("react/lib/EventPluginHub").injection.injectEventPluginsByName({
"ResponderEventPlugin": require("./ResponderEventPlugin.js"),
"TapEventPlugin": require("./TapEventPlugin.js")(shouldRejectClick)
});
};

0 comments on commit 9915966

Please sign in to comment.