UNPKG

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
(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