UNPKG

react-swipeable

Version:
344 lines (286 loc) 9.85 kB
import React from 'react'; import PropTypes from 'prop-types'; function _extends() { _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; }; return _extends.apply(this, arguments); } function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; subClass.__proto__ = superClass; } var defaultProps = { preventDefaultTouchmoveEvent: false, delta: 10, rotationAngle: 0, trackMouse: false, trackTouch: true }; var initialState = { xy: [0, 0], swiping: false, eventData: undefined, start: undefined }; var LEFT = 'Left'; var RIGHT = 'Right'; var UP = 'Up'; var DOWN = 'Down'; var touchStart = 'touchstart'; var touchMove = 'touchmove'; var touchEnd = 'touchend'; var mouseMove = 'mousemove'; var mouseUp = 'mouseup'; function getDirection(absX, absY, deltaX, deltaY) { if (absX > absY) { if (deltaX > 0) { return LEFT; } return RIGHT; } else if (deltaY > 0) { return UP; } return DOWN; } function rotateXYByAngle(pos, angle) { if (angle === 0) return pos; var angleInRadians = Math.PI / 180 * angle; var x = pos[0] * Math.cos(angleInRadians) + pos[1] * Math.sin(angleInRadians); var y = pos[1] * Math.cos(angleInRadians) - pos[0] * Math.sin(angleInRadians); return [x, y]; } function getHandlers(set, handlerProps) { var onStart = function onStart(event) { // if more than a single touch don't track, for now... if (event.touches && event.touches.length > 1) return; set(function (state, props) { // setup mouse listeners on document to track swipe since swipe can leave container if (props.trackMouse) { document.addEventListener(mouseMove, onMove); document.addEventListener(mouseUp, onUp); } var _ref = event.touches ? event.touches[0] : event, clientX = _ref.clientX, clientY = _ref.clientY; var xy = rotateXYByAngle([clientX, clientY], props.rotationAngle); return _extends({}, state, initialState, { eventData: { initial: [].concat(xy) }, xy: xy, start: event.timeStamp || 0 }); }); }; var onMove = function onMove(event) { set(function (state, props) { if (!state.xy[0] || !state.xy[1] || event.touches && event.touches.length > 1) { return state; } var _ref2 = event.touches ? event.touches[0] : event, clientX = _ref2.clientX, clientY = _ref2.clientY; var _rotateXYByAngle = rotateXYByAngle([clientX, clientY], props.rotationAngle), x = _rotateXYByAngle[0], y = _rotateXYByAngle[1]; var deltaX = state.xy[0] - x; var deltaY = state.xy[1] - y; var absX = Math.abs(deltaX); var absY = Math.abs(deltaY); var time = (event.timeStamp || 0) - state.start; var velocity = Math.sqrt(absX * absX + absY * absY) / (time || 1); // if swipe is under delta and we have not started to track a swipe: skip update if (absX < props.delta && absY < props.delta && !state.swiping) return state; var dir = getDirection(absX, absY, deltaX, deltaY); var eventData = _extends({}, state.eventData, { event: event, absX: absX, absY: absY, deltaX: deltaX, deltaY: deltaY, velocity: velocity, dir: dir }); props.onSwiping && props.onSwiping(eventData); // track if a swipe is cancelable(handler for swiping or swiped(dir) exists) // so we can call preventDefault if needed var cancelablePageSwipe = false; if (props.onSwiping || props.onSwiped || props["onSwiped" + dir]) { cancelablePageSwipe = true; } if (cancelablePageSwipe && props.preventDefaultTouchmoveEvent && props.trackTouch && event.cancelable) event.preventDefault(); return _extends({}, state, { eventData: eventData, swiping: true }); }); }; var onEnd = function onEnd(event) { set(function (state, props) { var eventData; if (state.swiping) { eventData = _extends({}, state.eventData, { event: event }); props.onSwiped && props.onSwiped(eventData); props["onSwiped" + eventData.dir] && props["onSwiped" + eventData.dir](eventData); } return _extends({}, state, initialState, { eventData: eventData }); }); }; var cleanUpMouse = function cleanUpMouse() { // safe to just call removeEventListener document.removeEventListener(mouseMove, onMove); document.removeEventListener(mouseUp, onUp); }; var onUp = function onUp(e) { cleanUpMouse(); onEnd(e); }; var attachTouch = function attachTouch(el) { if (el && el.addEventListener) { // attach touch event listeners and handlers var tls = [[touchStart, onStart], [touchMove, onMove], [touchEnd, onEnd]]; tls.forEach(function (_ref3) { var e = _ref3[0], h = _ref3[1]; return el.addEventListener(e, h); }); // return properly scoped cleanup method for removing listeners return function () { return tls.forEach(function (_ref4) { var e = _ref4[0], h = _ref4[1]; return el.removeEventListener(e, h); }); }; } }; var onRef = function onRef(el) { // "inline" ref functions are called twice on render, once with null then again with DOM element // ignore null here if (el === null) return; set(function (state, props) { // if the same DOM el as previous just return state if (state.el === el) return state; var addState = {}; // if new DOM el clean up old DOM and reset cleanUpTouch if (state.el && state.el !== el && state.cleanUpTouch) { state.cleanUpTouch(); addState.cleanUpTouch = null; } // only attach if we want to track touch if (props.trackTouch && el) { addState.cleanUpTouch = attachTouch(el); } // store event attached DOM el for comparison, clean up, and re-attachment return _extends({}, state, { el: el }, addState); }); }; // set ref callback to attach touch event listeners var output = { ref: onRef // if track mouse attach mouse down listener }; if (handlerProps.trackMouse) { output.onMouseDown = onStart; } return [output, attachTouch]; } function updateTransientState(state, props, attachTouch) { var addState = {}; // clean up touch handlers if no longer tracking touches if (!props.trackTouch && state.cleanUpTouch) { state.cleanUpTouch(); addState.cleanUpTouch = null; } else if (props.trackTouch && !state.cleanUpTouch) { // attach/re-attach touch handlers if (state.el) { addState.cleanUpTouch = attachTouch(state.el); } } return _extends({}, state, addState); } function useSwipeable(props) { var trackMouse = props.trackMouse; var transientState = React.useRef(_extends({}, initialState, { type: 'hook' })); var transientProps = React.useRef(); transientProps.current = _extends({}, defaultProps, props); var _React$useMemo = React.useMemo(function () { return getHandlers(function (cb) { return transientState.current = cb(transientState.current, transientProps.current); }, { trackMouse: trackMouse }); }, [trackMouse]), handlers = _React$useMemo[0], attachTouch = _React$useMemo[1]; transientState.current = updateTransientState(transientState.current, transientProps.current, attachTouch); return handlers; } var Swipeable = /*#__PURE__*/ function (_React$PureComponent) { _inheritsLoose(Swipeable, _React$PureComponent); function Swipeable(props) { var _this; _this = _React$PureComponent.call(this, props) || this; _this._set = function (cb) { _this.transientState = cb(_this.transientState, _this.props); }; _this.transientState = _extends({}, initialState, { type: 'class' }); return _this; } var _proto = Swipeable.prototype; _proto.render = function render() { var _this$props = this.props, className = _this$props.className, style = _this$props.style, _this$props$nodeName = _this$props.nodeName, nodeName = _this$props$nodeName === void 0 ? 'div' : _this$props$nodeName, innerRef = _this$props.innerRef, children = _this$props.children, trackMouse = _this$props.trackMouse; var _getHandlers = getHandlers(this._set, { trackMouse: trackMouse }), handlers = _getHandlers[0], attachTouch = _getHandlers[1]; this.transientState = updateTransientState(this.transientState, this.props, attachTouch); var ref = innerRef ? function (el) { return innerRef(el), handlers.ref(el); } : handlers.ref; return React.createElement(nodeName, _extends({}, handlers, { className: className, style: style, ref: ref }), children); }; return Swipeable; }(React.PureComponent); Swipeable.propTypes = { onSwiped: PropTypes.func, onSwiping: PropTypes.func, onSwipedUp: PropTypes.func, onSwipedRight: PropTypes.func, onSwipedDown: PropTypes.func, onSwipedLeft: PropTypes.func, delta: PropTypes.number, preventDefaultTouchmoveEvent: PropTypes.bool, nodeName: PropTypes.string, trackMouse: PropTypes.bool, trackTouch: PropTypes.bool, innerRef: PropTypes.func, rotationAngle: PropTypes.number }; Swipeable.defaultProps = defaultProps; export { DOWN, LEFT, RIGHT, Swipeable, UP, useSwipeable };