UNPKG

@sandstreamdev/react-swipeable-list

Version:
678 lines (544 loc) 23 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('react'), require('prop-types')) : typeof define === 'function' && define.amd ? define(['exports', 'react', 'prop-types'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global['@sandstreamdev/react-swipeable-list'] = {}, global.React, global.PropTypes)); }(this, (function (exports, React, PropTypes) { 'use strict'; function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } var React__default = /*#__PURE__*/_interopDefaultLegacy(React); var PropTypes__default = /*#__PURE__*/_interopDefaultLegacy(PropTypes); var SwipeableList = function SwipeableList(_ref) { var children = _ref.children, scrollStartThreshold = _ref.scrollStartThreshold, swipeStartThreshold = _ref.swipeStartThreshold, threshold = _ref.threshold; return typeof children === 'function' ? children({ className: 'swipeable-list', scrollStartThreshold, swipeStartThreshold, threshold }) : /*#__PURE__*/React__default['default'].createElement("div", { className: "swipeable-list" }, React__default['default'].Children.map(children, function (child) { return /*#__PURE__*/React__default['default'].cloneElement(child, { scrollStartThreshold, swipeStartThreshold, threshold }); })); }; SwipeableList.propTypes = { children: PropTypes__default['default'].oneOfType([PropTypes__default['default'].node, PropTypes__default['default'].func]), scrollStartThreshold: PropTypes__default['default'].number, swipeStartThreshold: PropTypes__default['default'].number, threshold: PropTypes__default['default'].number }; function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a 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); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } } function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } function _possibleConstructorReturn(self, call) { if (call && (typeof call === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } var ActionAnimations = { RETURN: Symbol('Return'), REMOVE: Symbol('Remove'), NONE: Symbol('None') }; var SwipeActionPropType = PropTypes__default['default'].shape({ action: PropTypes__default['default'].func.isRequired, actionAnimation: PropTypes__default['default'].oneOf(Object.values(ActionAnimations)), content: PropTypes__default['default'].node.isRequired }); var DragDirection = { UP: 1, DOWN: 2, LEFT: 3, RIGHT: 4, UNKNOWN: 5 }; var FPS_INTERVAL = 1000 / 60; var SwipeableListItem = /*#__PURE__*/function (_PureComponent) { _inherits(SwipeableListItem, _PureComponent); var _super = _createSuper(SwipeableListItem); function SwipeableListItem(_props) { var _this; _classCallCheck(this, SwipeableListItem); _this = _super.call(this, _props); _defineProperty(_assertThisInitialized(_this), "resetState", function () { _this.dragStartPoint = { x: -1, y: -1 }; _this.dragDirection = DragDirection.UNKNOWN; _this.left = 0; _this.previousSwipeDistancePercent = 0; }); _defineProperty(_assertThisInitialized(_this), "handleDragStartMouse", function (event) { window.addEventListener('mouseup', _this.handleDragEndMouse); window.addEventListener('mousemove', _this.handleMouseMove); _this.wrapper.addEventListener('mouseup', _this.handleDragEndMouse); _this.wrapper.addEventListener('mousemove', _this.handleMouseMove); _this.handleDragStart(event); }); _defineProperty(_assertThisInitialized(_this), "handleDragStartTouch", function (event) { window.addEventListener('touchend', _this.handleDragEndTouch); var touch = event.targetTouches[0]; _this.handleDragStart(touch); }); _defineProperty(_assertThisInitialized(_this), "handleDragStart", function (_ref) { var clientX = _ref.clientX, clientY = _ref.clientY; _this.resetState(); _this.dragStartPoint = { x: clientX, y: clientY }; _this.listElement.className = 'swipeable-list-item__content'; if (_this.contentLeft !== null) { _this.contentLeft.className = 'swipeable-list-item__content-left'; } if (_this.contentRight !== null) { _this.contentRight.className = 'swipeable-list-item__content-right'; } _this.startTime = Date.now(); _this.scheduleUpdatePosition(); }); _defineProperty(_assertThisInitialized(_this), "handleMouseMove", function (event) { if (_this.dragStartedWithinItem()) { var clientX = event.clientX, clientY = event.clientY; _this.setDragDirection(clientX, clientY); if (_this.isSwiping()) { event.stopPropagation(); event.preventDefault(); _this.left = clientX - _this.dragStartPoint.x; _this.scheduleUpdatePosition(); } } }); _defineProperty(_assertThisInitialized(_this), "handleTouchMove", function (event) { if (_this.dragStartedWithinItem()) { var _event$targetTouches$ = event.targetTouches[0], clientX = _event$targetTouches$.clientX, clientY = _event$targetTouches$.clientY; _this.setDragDirection(clientX, clientY); if (!event.cancelable) { return; } if (_this.isSwiping()) { event.stopPropagation(); event.preventDefault(); _this.left = clientX - _this.dragStartPoint.x; _this.scheduleUpdatePosition(); } } }); _defineProperty(_assertThisInitialized(_this), "handleDragEndMouse", function () { window.removeEventListener('mouseup', _this.handleDragEndMouse); window.removeEventListener('mousemove', _this.handleMouseMove); if (_this.wrapper) { _this.wrapper.removeEventListener('mouseup', _this.handleDragEndMouse); _this.wrapper.removeEventListener('mousemove', _this.handleMouseMove); } _this.handleDragEnd(); }); _defineProperty(_assertThisInitialized(_this), "handleDragEndTouch", function () { window.removeEventListener('touchend', _this.handleDragEndTouch); _this.handleDragEnd(); }); _defineProperty(_assertThisInitialized(_this), "playReturnAnimation", function () { var _assertThisInitialize = _assertThisInitialized(_this), contentLeft = _assertThisInitialize.contentLeft, contentRight = _assertThisInitialize.contentRight, listElement = _assertThisInitialize.listElement; if (listElement) { listElement.className = 'swipeable-list-item__content swipeable-list-item__content--return'; listElement.style.transform = 'translateX(0px)'; } // hide backgrounds if (contentLeft !== null) { contentLeft.style.opacity = 0; contentLeft.className = 'swipeable-list-item__content-left swipeable-list-item__content-left--return'; } if (contentRight !== null) { contentRight.style.opacity = 0; contentRight.className = 'swipeable-list-item__content-right swipeable-list-item__content-right--return'; } }); _defineProperty(_assertThisInitialized(_this), "playRemoveAnimation", function (direction) { var _assertThisInitialize2 = _assertThisInitialized(_this), listElement = _assertThisInitialize2.listElement; if (listElement) { listElement.className = 'swipeable-list-item__content swipeable-list-item__content--remove'; listElement.style.transform = "translateX(".concat(listElement.offsetWidth * (direction === DragDirection.LEFT ? -1 : 1), "px)"); } }); _defineProperty(_assertThisInitialized(_this), "playActionAnimation", function (type, direction) { var _assertThisInitialize3 = _assertThisInitialized(_this), listElement = _assertThisInitialize3.listElement; if (listElement) { switch (type) { case ActionAnimations.REMOVE: _this.playRemoveAnimation(direction); break; case ActionAnimations.NONE: break; default: _this.playReturnAnimation(); } } }); _defineProperty(_assertThisInitialized(_this), "handleDragEnd", function () { var _assertThisInitialize4 = _assertThisInitialized(_this), left = _assertThisInitialize4.left, listElement = _assertThisInitialize4.listElement, props = _assertThisInitialize4.props; var swipeLeft = props.swipeLeft, swipeRight = props.swipeRight, _props$threshold = props.threshold, threshold = _props$threshold === void 0 ? 0.5 : _props$threshold; var actionTriggered = false; if (_this.isSwiping()) { if (listElement) { if (left < listElement.offsetWidth * threshold * -1) { _this.playActionAnimation(swipeLeft.actionAnimation, DragDirection.LEFT); _this.handleSwipedLeft(); actionTriggered = true; } else if (left > listElement.offsetWidth * threshold) { _this.playActionAnimation(swipeRight.actionAnimation, DragDirection.RIGHT); _this.handleSwipedRight(); actionTriggered = true; } } if (_this.props.onSwipeEnd) { _this.props.onSwipeEnd(); } } _this.resetState(); if (!actionTriggered) { _this.playReturnAnimation(); } }); _defineProperty(_assertThisInitialized(_this), "dragStartedWithinItem", function () { var _this$dragStartPoint = _this.dragStartPoint, x = _this$dragStartPoint.x, y = _this$dragStartPoint.y; return x !== -1 && y !== -1; }); _defineProperty(_assertThisInitialized(_this), "setDragDirection", function (x, y) { if (_this.dragDirection === DragDirection.UNKNOWN) { var _this$dragStartPoint2 = _this.dragStartPoint, startX = _this$dragStartPoint2.x, startY = _this$dragStartPoint2.y; var horizontalDistance = Math.abs(x - startX); var verticalDistance = Math.abs(y - startY); if (horizontalDistance <= _this.dragHorizontalDirectionThreshold && verticalDistance <= _this.dragVerticalDirectionThreshold) { return; } var angle = Math.atan2(y - startY, x - startX); var octant = Math.round(8 * angle / (2 * Math.PI) + 8) % 8; switch (octant) { case 0: if (_this.contentRight !== null && horizontalDistance > _this.dragHorizontalDirectionThreshold) { _this.dragDirection = DragDirection.RIGHT; } break; case 1: case 2: case 3: if (verticalDistance > _this.dragVerticalDirectionThreshold) { _this.dragDirection = DragDirection.DOWN; } break; case 4: if (_this.contentLeft !== null && horizontalDistance > _this.dragHorizontalDirectionThreshold) { _this.dragDirection = DragDirection.LEFT; } break; case 5: case 6: case 7: if (verticalDistance > _this.dragVerticalDirectionThreshold) { _this.dragDirection = DragDirection.UP; } break; } if (_this.props.onSwipeStart && _this.isSwiping()) { _this.props.onSwipeStart(); } } }); _defineProperty(_assertThisInitialized(_this), "isSwiping", function () { var blockSwipe = _this.props.blockSwipe; var horizontalDrag = _this.dragDirection === DragDirection.LEFT || _this.dragDirection === DragDirection.RIGHT; return !blockSwipe && _this.dragStartedWithinItem() && horizontalDrag; }); _defineProperty(_assertThisInitialized(_this), "scheduleUpdatePosition", function () { if (_this.requestedAnimationFrame) { return; } _this.requestedAnimationFrame = requestAnimationFrame(function () { _this.requestedAnimationFrame = null; _this.updatePosition(); }); }); _defineProperty(_assertThisInitialized(_this), "updatePosition", function () { var now = Date.now(); var elapsed = now - _this.startTime; if (elapsed > FPS_INTERVAL && _this.isSwiping()) { var contentToShow = _this.left < 0 ? _this.contentLeft : _this.contentRight; if (_this.onlyLeftContent && _this.left > 0) { _this.left = 0; contentToShow = _this.contentLeft; } if (_this.onlyRightContent && _this.left < 0) { _this.left = 0; contentToShow = _this.contentRight; } if (!contentToShow) { return; } if (_this.listElement) { _this.listElement.style.transform = "translateX(".concat(_this.left, "px)"); } var opacity = (Math.abs(_this.left) / 100).toFixed(2); if (_this.props.onSwipeProgress && _this.listElement) { var listElementWidth = _this.listElement.offsetWidth; var swipeDistancePercent = _this.previousSwipeDistancePercent; if (listElementWidth !== 0) { var swipeDistance = Math.max(0, listElementWidth - Math.abs(_this.left)); swipeDistancePercent = 100 - Math.round(100 * swipeDistance / listElementWidth); } if (_this.previousSwipeDistancePercent !== swipeDistancePercent) { _this.props.onSwipeProgress(swipeDistancePercent); _this.previousSwipeDistancePercent = swipeDistancePercent; } } if (opacity < 1 && opacity.toString() !== contentToShow.style.opacity) { contentToShow.style.opacity = opacity.toString(); var contentToHide = _this.left < 0 ? _this.contentRight : _this.contentLeft; if (contentToHide) { contentToHide.style.opacity = '0'; } } if (opacity >= 1) { contentToShow.style.opacity = '1'; } _this.startTime = Date.now(); } }); _defineProperty(_assertThisInitialized(_this), "handleSwipedLeft", function () { var _this$props$swipeLeft = _this.props.swipeLeft; _this$props$swipeLeft = _this$props$swipeLeft === void 0 ? {} : _this$props$swipeLeft; var action = _this$props$swipeLeft.action; if (action) { action(); } }); _defineProperty(_assertThisInitialized(_this), "handleSwipedRight", function () { var _this$props$swipeRigh = _this.props.swipeRight; _this$props$swipeRigh = _this$props$swipeRigh === void 0 ? {} : _this$props$swipeRigh; var action = _this$props$swipeRigh.action; if (action) { action(); } }); _defineProperty(_assertThisInitialized(_this), "bindContentLeft", function (ref) { return _this.contentLeft = ref; }); _defineProperty(_assertThisInitialized(_this), "bindContentRight", function (ref) { return _this.contentRight = ref; }); _defineProperty(_assertThisInitialized(_this), "bindListElement", function (ref) { return _this.listElement = ref; }); _defineProperty(_assertThisInitialized(_this), "bindWrapper", function (ref) { return _this.wrapper = ref; }); _this.contentLeft = null; _this.contentRight = null; _this.listElement = null; _this.requestedAnimationFrame = null; _this.wrapper = null; _this.startTime = null; _this.previousSwipeDistancePercent = 0; _this.resetState(); return _this; } _createClass(SwipeableListItem, [{ key: "dragHorizontalDirectionThreshold", get: function get() { return this.props.swipeStartThreshold || 10; } }, { key: "dragVerticalDirectionThreshold", get: function get() { return this.props.scrollStartThreshold || 10; } }, { key: "componentDidMount", value: function componentDidMount() { this.wrapper.addEventListener('mousedown', this.handleDragStartMouse); this.wrapper.addEventListener('touchstart', this.handleDragStartTouch); this.wrapper.addEventListener('touchend', this.handleDragEndTouch); this.wrapper.addEventListener('touchmove', this.handleTouchMove, { capture: true, passive: false }); } }, { key: "componentWillUnmount", value: function componentWillUnmount() { if (this.requestedAnimationFrame) { cancelAnimationFrame(this.requestedAnimationFrame); this.requestedAnimationFrame = null; } this.wrapper.removeEventListener('mousedown', this.handleDragStartMouse); this.wrapper.removeEventListener('touchstart', this.handleDragStartTouch); this.wrapper.removeEventListener('touchend', this.handleDragEndTouch); this.wrapper.removeEventListener('touchmove', this.handleTouchMove, { capture: true, passive: false }); } }, { key: "onlyLeftContent", get: function get() { return this.contentLeft !== null && this.contentRight === null; } }, { key: "onlyRightContent", get: function get() { return this.contentLeft === null && this.contentRight !== null; } }, { key: "render", value: function render() { var _this$props = this.props, children = _this$props.children, swipeLeft = _this$props.swipeLeft, swipeRight = _this$props.swipeRight; return /*#__PURE__*/React__default['default'].createElement("div", { className: "swipeable-list-item", ref: this.bindWrapper }, swipeLeft && /*#__PURE__*/React__default['default'].createElement("div", { className: "swipeable-list-item__content-left", "data-testid": "swipe-left-content", ref: this.bindContentLeft }, swipeLeft.content), swipeRight && /*#__PURE__*/React__default['default'].createElement("div", { className: "swipeable-list-item__content-right", "data-testid": "swipe-right-content", ref: this.bindContentRight }, swipeRight.content), /*#__PURE__*/React__default['default'].createElement("div", { className: "swipeable-list-item__content", "data-testid": "content", ref: this.bindListElement }, children)); } }]); return SwipeableListItem; }(React.PureComponent); SwipeableListItem.propTypes = { blockSwipe: PropTypes__default['default'].bool, children: PropTypes__default['default'].node.isRequired, swipeLeft: SwipeActionPropType, swipeRight: SwipeActionPropType, scrollStartThreshold: PropTypes__default['default'].number, swipeStartThreshold: PropTypes__default['default'].number, threshold: PropTypes__default['default'].number, onSwipeEnd: PropTypes__default['default'].func, onSwipeProgress: PropTypes__default['default'].func, onSwipeStart: PropTypes__default['default'].func }; exports.ActionAnimations = ActionAnimations; exports.SwipeableList = SwipeableList; exports.SwipeableListItem = SwipeableListItem; Object.defineProperty(exports, '__esModule', { value: true }); })));