react-composite-events
Version:
A collection of higher-order components (HOCs) to easily create composite events in React components
176 lines (142 loc) • 7.64 kB
JavaScript
import React, { Component } from 'react';
import { getDisplayName } from './utils';
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;
};
export default (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 (process.env.NODE_ENV !== 'production') {
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 && process.env.NODE_ENV !== '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
if (process.env.NODE_ENV !== 'production') {
// 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.createElement(Element, babelHelpers.extends({}, passThruProps, triggerEventHandlers, cancelEventHandlers));
}
}]);
return CompositeEventWrapper;
}(Component), _class.displayName = elementDisplayName + '-' + eventPropName + durationSuffix, _temp2;
};
};
});