UNPKG

react-pointable

Version:

Declarative pointer event binding. Works well alongside PEP.

204 lines (160 loc) 7.53 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.__test__ = undefined; var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _react = require('react'); var _react2 = _interopRequireDefault(_react); var _propTypes = require('prop-types'); var _propTypes2 = _interopRequireDefault(_propTypes); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } // Allows listening for pointer events using PEP for browsers that don't support them. // This component also adds support for the declarative event binding that React does well, // even though React doesn't support pointer events yet. // A mapping of pointer event props to event names. var pointerEventMap = { onPointerMove: 'pointermove', onPointerDown: 'pointerdown', onPointerUp: 'pointerup', onPointerOver: 'pointerover', onPointerOut: 'pointerout', onPointerEnter: 'pointerenter', onPointerLeave: 'pointerleave', onPointerCancel: 'pointercancel' }; // An array of just the pointer event props. var pointerEventProps = Object.keys(pointerEventMap); // Component with pointer events enabled (specially made for Pointer Events Polyfill) var Pointable = function (_React$Component) { _inherits(Pointable, _React$Component); function Pointable(props) { _classCallCheck(this, Pointable); var _this = _possibleConstructorReturn(this, (Pointable.__proto__ || Object.getPrototypeOf(Pointable)).call(this, props)); _this.setRef = _this.setRef.bind(_this); return _this; } // When component mounts, check for pointer event listeners in props and register them manually. _createClass(Pointable, [{ key: 'componentDidMount', value: function componentDidMount() { initNodeWithPE(this.pointableNode, this.props); } // When component updates, diff pointer events and manually remove/add event listeners as needed. }, { key: 'componentDidUpdate', value: function componentDidUpdate(prevProps) { updateNodeWithPE(this.pointableNode, prevProps, this.props); } }, { key: 'setRef', value: function setRef(node) { this.pointableNode = node; if (this.props.elementRef) { this.props.elementRef(node); } } }, { key: 'render', value: function render() { // Collect unused props to pass along to rendered node. // This could be done simply with lodash, but avoiding the extra dependency here isn't difficult. // Create a shallow copy of props var otherProps = _extends({}, this.props); // Remove known pointer event props pointerEventProps.forEach(function (prop) { return delete otherProps[prop]; }); // Remove other props used by <Pointable /> delete otherProps.children; delete otherProps.tagName; delete otherProps.touchAction; delete otherProps.elementRef; var El = this.props.tagName; return _react2.default.createElement( El, _extends({ ref: this.setRef }, otherProps), this.props.children ); } }]); return Pointable; }(_react2.default.Component); Pointable.propTypes = { tagName: _propTypes2.default.string.isRequired, touchAction: _propTypes2.default.oneOf(['auto', 'none', 'pan-x', 'pan-y', 'manipulation']).isRequired, elementRef: _propTypes2.default.func, onPointerMove: _propTypes2.default.func, onPointerDown: _propTypes2.default.func, onPointerUp: _propTypes2.default.func, onPointerOver: _propTypes2.default.func, onPointerOut: _propTypes2.default.func, onPointerEnter: _propTypes2.default.func, onPointerLeave: _propTypes2.default.func, onPointerCancel: _propTypes2.default.func }; Pointable.defaultProps = { tagName: 'div', touchAction: 'auto' }; exports.default = Pointable; // Helper methods // Given a DOM node and a props object, add appropriate pointer events. var initNodeWithPE = function initNodeWithPE(node, props) { var hasPE = false; // Check for all possible pointer event prop names. pointerEventProps.forEach(function (eventProp) { // If an event prop exists in given props, add the event listener. var listener = props[eventProp]; if (listener) { hasPE = true; node.addEventListener(pointerEventMap[eventProp], listener); } }); // For PEP, add the 'touch-action' attribute if pointer events were registered. if (hasPE) { node.setAttribute('touch-action', props.touchAction); } }; // Given a DOM node, a stale props object, and a new props object, compute which pointer events to remove and/or add. var updateNodeWithPE = function updateNodeWithPE(node, prevProps, nextProps) { var hasPE = false; pointerEventProps.forEach(function (eventProp) { // To perform diff, grab references to old and new listener functions for event. var listenerOld = prevProps[eventProp]; var listenerNew = nextProps[eventProp]; // If a listener (still) exists, mark that there are pointer events. if (listenerNew) { hasPE = true; } // If listener hasn't changed, there's nothing to do. // Note the additional check exists because `undefined` !== `null` but both mean "no event listener". if (listenerOld === listenerNew || !listenerOld && !listenerNew) return; // Remove existing event listener. if (listenerOld) { node.removeEventListener(pointerEventMap[eventProp], listenerOld); } // Add/update with new event listener. if (listenerNew) { node.addEventListener(pointerEventMap[eventProp], listenerNew); } }); // For PEP, ensure the 'touch-action' attribute reflects the currently attached event listeners. if (hasPE) { node.setAttribute('touch-action', nextProps.touchAction); } else { node.removeAttribute('touch-action'); } }; /////////////////////// // TEST-ONLY EXPORTS // /////////////////////// // // These methods are private to the module, but should still be tested. var __test__ = exports.__test__ = { initNodeWithPE: initNodeWithPE, updateNodeWithPE: updateNodeWithPE };