react-composite-events
Version:
A collection of higher-order components (HOCs) to easily create composite events in React components
418 lines (348 loc) • 14.7 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('react')) :
typeof define === 'function' && define.amd ? define(['exports', 'react'], factory) :
(factory((global.RCE = {}),global.React));
}(this, (function (exports,React) { 'use strict';
var React__default = 'default' in React ? React['default'] : React;
var getDisplayName = function getDisplayName(Element) {
if (typeof Element === 'string') {
return Element;
}
// $FlowFixMe: Need to figure out how to properly validate that Element is a class component to get `displayName` statically
return Element.displayName || Element.name || 'WrapperComponent';
};
var _omit = function _omit(props, propToOmit) {
var propsCopy = babelHelpers.extends({}, props);
delete propsCopy[propToOmit];
return propsCopy;
};
var _eventNamesToHandlerLookup = function _eventNamesToHandlerLookup(eventNames, handler) {
return (eventNames || []).reduce(function (lookup, eventName) {
return babelHelpers.extends({}, lookup, babelHelpers.defineProperty({}, eventName, handler.bind(null, eventName)));
}, {});
};
var _isValidDuration = function _isValidDuration(duration) {
return duration > 0;
};
var compose = (function (_ref) {
var eventPropName = _ref.eventPropName,
triggerEvent = _ref.triggerEvent,
_ref$defaultDuration = _ref.defaultDuration,
defaultDuration = _ref$defaultDuration === undefined ? 0 : _ref$defaultDuration,
cancelEvent = _ref.cancelEvent,
_ref$shouldResetTimer = _ref.shouldResetTimerOnRetrigger,
shouldResetTimerOnRetrigger = _ref$shouldResetTimer === undefined ? true : _ref$shouldResetTimer,
_ref$beforeHandle = _ref.beforeHandle,
beforeHandle = _ref$beforeHandle === undefined ? function () {
return true;
} : _ref$beforeHandle;
// istanbul ignore next
{
if (!eventPropName) {
throw new Error('`eventPropName` configuration must be specified');
}
if (!triggerEvent) {
throw new Error('`triggerEvent` configuration must be specified');
}
}
var triggerEvents = Array.isArray(triggerEvent) ? triggerEvent : [triggerEvent];
var cancelEvents = void 0;
if (cancelEvent) {
cancelEvents = Array.isArray(cancelEvent) ? cancelEvent : [cancelEvent];
}
return function () {
var duration = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
var timeoutDuration = defaultDuration;
var durationSuffix = '';
// if defaultDuration is specified and there's a duration override,
// use the override. Otherwise there's no timeout happening
if (_isValidDuration(defaultDuration) && _isValidDuration(duration)) {
timeoutDuration = duration;
durationSuffix = '-' + duration;
}
// if the duration is passed the composite even prop name needs to be parameterized
var compositeEventPropName = '' + eventPropName + durationSuffix;
return function (Element) {
var _class, _temp2;
if (!Element && "development" !== 'production') {
throw new Error('Component/element to enhance must be specified');
}
var elementDisplayName = getDisplayName(Element);
return _temp2 = _class = function (_Component) {
babelHelpers.inherits(CompositeEventWrapper, _Component);
function CompositeEventWrapper() {
var _ref2;
var _temp, _this, _ret;
babelHelpers.classCallCheck(this, CompositeEventWrapper);
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
return _ret = (_temp = (_this = babelHelpers.possibleConstructorReturn(this, (_ref2 = CompositeEventWrapper.__proto__ || Object.getPrototypeOf(CompositeEventWrapper)).call.apply(_ref2, [this].concat(args))), _this), _this._delayTimeout = null, _this._callSpecificHandler = function (eventName, e) {
var onEvent = _this.props[eventName];
if (onEvent) {
onEvent(e);
}
}, _this._clearTimeout = function () {
_this._delayTimeout = clearTimeout(_this._delayTimeout);
}, _this._callCompositeEvent = function (e) {
var onCompositeEvent = _this.props[compositeEventPropName];
// If a before handle function is defined, call it and check to see what the function returns
// truthy - means that it wants the HOC to to call the final handler with the event object
// falsy - means that it doesn't want the HOC to do anything
// The function receives the composite event handler + event object to make it's decision and
// can call the handler directly
if (beforeHandle(onCompositeEvent, e)) {
onCompositeEvent(e);
}
}, _this._handleTriggerEvent = function (eventName, e) {
var onCompositeEvent = _this.props[compositeEventPropName];
// If a specific handler was passed, call that one first
_this._callSpecificHandler(eventName, e);
if (!onCompositeEvent) {
// istanbul ignore next
{
// eslint-disable-next-line no-console
console.warn('No handler was found for `' + compositeEventPropName + '` in `<' + elementDisplayName + ' />`! Was this a typo? If not, you should consider avoiding using the composite event HOC to improve performance');
}
return;
}
// Call the composite event handler
if (_isValidDuration(timeoutDuration)) {
// If shouldResetTimerOnRetrigger flag is not explicitly turned off, we need to
// clear any existing timeout and start a fresh timer because a retrigger happened.
if (shouldResetTimerOnRetrigger !== false) {
_this._clearTimeout();
}
// And we can start a timeout as long as we don't have an active one going
if (!_this._delayTimeout) {
_this._delayTimeout = setTimeout(function () {
return _this._callCompositeEvent(e);
}, timeoutDuration);
}
} else {
_this._callCompositeEvent(e);
}
}, _this._handleCancelEvent = function (eventName, e) {
// If a specific handler was passed, call that one first
_this._callSpecificHandler(eventName, e);
// just cancel the timeout so composite handler won't be called
_this._clearTimeout();
}, _temp), babelHelpers.possibleConstructorReturn(_this, _ret);
}
babelHelpers.createClass(CompositeEventWrapper, [{
key: 'render',
value: function render() {
// Want to pass all the props through to the underlying Component except the passed
// compositeEventPropName, which we need to handle specially.
// This will also include separate specific handlers matching trigger & cancel events
var passThruProps = _omit(this.props, compositeEventPropName);
// Create an object mapping of the trigger/cancel events to handlers.
// The handler needs to bind the event name so that we can check to see if
// a specific handler was specified so we can fire that too
var triggerEventHandlers = _eventNamesToHandlerLookup(triggerEvents, this._handleTriggerEvent);
var cancelEventHandlers = _eventNamesToHandlerLookup(cancelEvents, this._handleCancelEvent);
// As a result of cancelEventHandlers going after triggerEventHandlers below, if
// a cancel event matches a trigger event, the composite event will never be triggered
return React__default.createElement(Element, babelHelpers.extends({}, passThruProps, triggerEventHandlers, cancelEventHandlers));
}
}]);
return CompositeEventWrapper;
}(React.Component), _class.displayName = elementDisplayName + '-' + eventPropName + durationSuffix, _temp2;
};
};
});
var withLongPress = compose({
eventPropName: 'onLongPress',
triggerEvent: ['onMouseDown', 'onPressIn'],
defaultDuration: 1250,
cancelEvent: ['onMouseUp', 'onMouseOut', 'onPressOut']
});
var withRemainReleased = compose({
eventPropName: 'onRemainReleased',
triggerEvent: ['onMouseUp', 'onPressOut'],
defaultDuration: 500,
cancelEvent: ['onMouseDown', 'onPressIn']
});
var withRemainFocused = compose({
eventPropName: 'onRemainFocused',
triggerEvent: 'onFocus',
defaultDuration: 500,
cancelEvent: 'onBlur'
});
var withRemainBlurred = compose({
eventPropName: 'onRemainBlurred',
triggerEvent: 'onBlur',
defaultDuration: 500,
cancelEvent: 'onFocus'
});
var composeMouseEdge = function composeMouseEdge(_ref) {
var eventPropName = _ref.eventPropName,
direction = _ref.direction,
location = _ref.location;
return compose({
eventPropName: eventPropName,
triggerEvent: direction === 'enter' ? 'onMouseEnter' : 'onMouseLeave',
beforeHandle: function beforeHandle(handler, e) {
if (!e) {
return false;
}
var _ref2 = e,
screenX = _ref2.screenX,
screenY = _ref2.screenY,
currentTarget = _ref2.currentTarget;
if (!(currentTarget instanceof HTMLElement)) {
return false;
}
var _currentTarget$getBou = currentTarget.getBoundingClientRect(),
top = _currentTarget$getBou.top,
left = _currentTarget$getBou.left,
bottom = _currentTarget$getBou.bottom,
right = _currentTarget$getBou.right;
return location === 'left' && screenX <= left || location === 'right' && screenX >= right || location === 'top' && screenY <= top || location === 'bottom' && screenY >= bottom;
}
});
};
var composeMouseModifierKey = function composeMouseModifierKey(_ref3) {
var eventPropName = _ref3.eventPropName,
mouseEvent = _ref3.mouseEvent,
_ref3$alt = _ref3.alt,
alt = _ref3$alt === undefined ? false : _ref3$alt,
_ref3$ctrl = _ref3.ctrl,
ctrl = _ref3$ctrl === undefined ? false : _ref3$ctrl,
_ref3$meta = _ref3.meta,
meta = _ref3$meta === undefined ? false : _ref3$meta,
_ref3$shift = _ref3.shift,
shift = _ref3$shift === undefined ? false : _ref3$shift;
return compose({
eventPropName: eventPropName,
triggerEvent: mouseEvent,
beforeHandle: function beforeHandle(handler, e) {
var syntheticMouseEvent = e;
return syntheticMouseEvent && syntheticMouseEvent.altKey === alt && syntheticMouseEvent.ctrlKey === ctrl && syntheticMouseEvent.metaKey === meta && syntheticMouseEvent.shiftKey === shift;
}
});
};
var withMouseRest = compose({
eventPropName: 'onMouseRest',
triggerEvent: ['onMouseOver', 'onMouseMove'],
defaultDuration: 500,
cancelEvent: ['onMouseOut', 'onMouseDown']
});
var withMouseRemainOver = compose({
eventPropName: 'onMouseRemainOver',
triggerEvent: ['onMouseOver', 'onMouseMove'],
defaultDuration: 500,
// including `onMouseDown` because clicking is basically a different action
cancelEvent: ['onMouseOut', 'onMouseDown'],
// as long as the mouse is over, moving around doesn't matter
shouldResetTimerOnRetrigger: false
});
var withMouseRemainOut = compose({
eventPropName: 'onMouseRemainOut',
triggerEvent: 'onMouseOut',
defaultDuration: 500,
cancelEvent: 'onMouseOver'
});
var withMouseEnterLeft = composeMouseEdge({
eventPropName: 'onMouseEnterLeft',
direction: 'enter',
location: 'left'
});
var withMouseEnterRight = composeMouseEdge({
eventPropName: 'onMouseEnterRight',
direction: 'enter',
location: 'right'
});
var withMouseEnterTop = composeMouseEdge({
eventPropName: 'onMouseEnterTop',
direction: 'enter',
location: 'top'
});
var withMouseEnterBottom = composeMouseEdge({
eventPropName: 'onMouseEnterBottom',
direction: 'enter',
location: 'bottom'
});
var withMouseLeaveLeft = composeMouseEdge({
eventPropName: 'onMouseLeaveLeft',
direction: 'leave',
location: 'left'
});
var withMouseLeaveRight = composeMouseEdge({
eventPropName: 'onMouseLeaveRight',
direction: 'leave',
location: 'right'
});
var withMouseLeaveTop = composeMouseEdge({
eventPropName: 'onMouseLeaveTop',
direction: 'leave',
location: 'top'
});
var withMouseLeaveBottom = composeMouseEdge({
eventPropName: 'onMouseLeaveBottom',
direction: 'leave',
location: 'bottom'
});
var withOnlyClick = composeMouseModifierKey({
eventPropName: 'onOnlyClick',
mouseEvent: 'onClick'
});
var withAltClick = composeMouseModifierKey({
eventPropName: 'onAltClick',
mouseEvent: 'onClick',
alt: true
});
var withCtrlClick = composeMouseModifierKey({
eventPropName: 'onCtrlClick',
mouseEvent: 'onClick',
ctrl: true
});
var withMetaClick = composeMouseModifierKey({
eventPropName: 'onMetaClick',
mouseEvent: 'onClick',
meta: true
});
var withShiftClick = composeMouseModifierKey({
eventPropName: 'onShiftClick',
mouseEvent: 'onClick',
shift: true
});
var withKeyRemainDown = compose({
eventPropName: 'onKeyRemainDown',
triggerEvent: 'onKeyDown',
defaultDuration: 500,
cancelEvent: 'onKeyUp'
});
var withKeyRemainUp = compose({
eventPropName: 'onKeyRemainUp',
triggerEvent: 'onKeyUp',
defaultDuration: 500,
cancelEvent: 'onKeyDown'
});
exports.compose = compose;
exports.withLongPress = withLongPress;
exports.withRemainReleased = withRemainReleased;
exports.withRemainFocused = withRemainFocused;
exports.withRemainBlurred = withRemainBlurred;
exports.composeMouseModifierKey = composeMouseModifierKey;
exports.withMouseRest = withMouseRest;
exports.withMouseRemainOver = withMouseRemainOver;
exports.withMouseRemainOut = withMouseRemainOut;
exports.withMouseEnterLeft = withMouseEnterLeft;
exports.withMouseEnterRight = withMouseEnterRight;
exports.withMouseEnterTop = withMouseEnterTop;
exports.withMouseEnterBottom = withMouseEnterBottom;
exports.withMouseLeaveLeft = withMouseLeaveLeft;
exports.withMouseLeaveRight = withMouseLeaveRight;
exports.withMouseLeaveTop = withMouseLeaveTop;
exports.withMouseLeaveBottom = withMouseLeaveBottom;
exports.withOnlyClick = withOnlyClick;
exports.withAltClick = withAltClick;
exports.withCtrlClick = withCtrlClick;
exports.withMetaClick = withMetaClick;
exports.withShiftClick = withShiftClick;
exports.withKeyRemainDown = withKeyRemainDown;
exports.withKeyRemainUp = withKeyRemainUp;
Object.defineProperty(exports, '__esModule', { value: true });
})));
//# sourceMappingURL=react-composite-events.js.map