UNPKG

react-smooth-slider

Version:
457 lines (365 loc) 18.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _react = _interopRequireDefault(require("react")); var _propTypes = _interopRequireDefault(require("prop-types")); var _TouchHelper = _interopRequireDefault(require("./TouchHelper")); var _constants = require("./constants"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread(); } function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance"); } function _iterableToArray(iter) { if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter); } function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } 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 _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 _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } 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; } var Slider = /*#__PURE__*/ function (_React$Component) { _inherits(Slider, _React$Component); _createClass(Slider, null, [{ key: "getDerivedStateFromProps", // Update cards in state from props value: function getDerivedStateFromProps(props, state) { return _objectSpread({}, state, { cards: Slider.createCards(props, state.cardRefs) }); } // Clone components from props with reference properties }, { key: "createCards", value: function createCards(props, cardRefs) { return props.components.map(function (card, i) { var _props = _defineProperty({ id: "card-".concat(i, "-").concat(props.id) }, props.refKey, cardRefs[i]); var elementProps = {}; // Class / functional components if (!card.type.$$typeof) { elementProps.cardProps = _props; } else { // Direct component elementProps = _props; } return _react.default.cloneElement(card, elementProps); }); } }]); function Slider(props) { var _this; _classCallCheck(this, Slider); _this = _possibleConstructorReturn(this, _getPrototypeOf(Slider).call(this, props)); _defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "setVisibleCardIds", function () { return new Promise(function (resolve) { if (_this.touchHelper.isScrolling) return; var cardRefs = _this.state.cardRefs; var offset = _this.props.offset; var containerEl = _this.containerRef.current; var containerRect = containerEl.getBoundingClientRect(); var containerWidth = containerEl.clientWidth + containerRect.left; var visibleCardIds = cardRefs.reduce(function (all, card, id) { var cardEl = card.current; if (!cardEl) return all; var cardRect = cardEl.getBoundingClientRect(); var cardX = cardRect.left + offset; var cardX2 = cardX + cardRect.width; // Check if card is in view if (cardX < 0 && cardX2 > 0 || cardX >= 0 && cardX2 <= containerWidth || cardX >= 0 && cardX < containerWidth && cardX2 >= containerWidth) { return _toConsumableArray(all).concat([id]); } return all; }, []); var cardRefRect = function cardRefRect(id) { return cardRefs[id].current.getBoundingClientRect(); }; var lastCardId = visibleCardIds[visibleCardIds.length - 1]; var firstCardId = visibleCardIds[0] || Math.min(0, lastCardId - 1); var lastCardPos = cardRefRect(lastCardId); var firstCardPos = cardRefRect(firstCardId); // Cards that are at least 90% visible count as fully visible var threshold = 0.1 * lastCardPos.width; var lastFullVisibleCardId = lastCardPos.left >= 0 && lastCardPos.right <= containerWidth + threshold ? lastCardId : Math.max(0, lastCardId - 1); var firstFullVisibleCardId = firstCardPos.left >= 0 && firstCardPos.right <= containerWidth + threshold ? firstCardId : Math.min(firstCardId + 1, cardRefs.length - 1); _this.setState({ firstVisibleCardId: firstCardId, lastFullVisibleCardId: lastFullVisibleCardId, // eslint-disable-line firstFullVisibleCardId: firstFullVisibleCardId // eslint-disable-line }, function () { resolve(); }); }); }); _defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "onMousewheel", function (e) { if (!_this.containerRef) { return; } var scrollLeft = _this.state.scrollLeft; var allowVerticalScrolling = _this.props.allowVerticalScrolling; var _this$containerRef$cu = _this.containerRef.current, scrollWidth = _this$containerRef$cu.scrollWidth, clientWidth = _this$containerRef$cu.clientWidth; var delta = allowVerticalScrolling ? e.deltaY + e.deltaX : e.deltaX; var containerWidth = scrollWidth - clientWidth; var x = scrollLeft; // Set next scroll location if (x + delta >= 0 && x + delta <= containerWidth) { x = scrollLeft + delta; } if (x - delta < 0) { x = 0; } requestAnimationFrame(function () { _this.setVisibleCardIds(); if (x !== scrollLeft) { _this.setState({ // isScrolling: true, // need fix: isScrolling to false if done scrolling or press prev/next button scrollLeft: x }, function () { _this.containerRef.current.scrollLeft = _this.state.scrollLeft; }); } }); }); _defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "onTouchMove", function (e) { if (e.touches.length >= 2 && e.scale !== 1) { e.preventDefault(); } requestAnimationFrame(function () { _this.setVisibleCardIds(); _this.setState({ scrollLeft: _this.containerRef.current.scrollLeft }); if (!_this.touchHelper.swiped) { _this.touchHelper.swiped = true; _this.forceUpdate(); } _this.touchHelper.onTouchMove(e); }); }); _defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "onTouchEnd", function () { var _this$touchHelper$sta = _this.touchHelper.start, startX = _this$touchHelper$sta.x, startY = _this$touchHelper$sta.y; var _this$touchHelper$end = _this.touchHelper.end, endX = _this$touchHelper$end.x, endY = _this$touchHelper$end.y; var deltaX = startX - endX; var deltaY = startY - endY; if (Math.abs(deltaX) >= Math.abs(deltaY)) { if (deltaX < 0) { _this.scrollToCard(_constants.DIRECTION.PREV)(); } else if (deltaX > 0) { _this.scrollToCard(_constants.DIRECTION.NEXT)(); } } _this.touchHelper.isScrolling = false; }); _defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "createCardRefs", function () { return Array.from({ length: _this.props.components.length }).map(function () { return _react.default.createRef(); }); }); _defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "stopScroll", function () { _this.setVisibleCardIds().then(function () { _this.setState({ isScrolling: false }); _this.touchHelper.onTouchEnd(); }); }); _defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "scrollToX", function (targetX) { var duration = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 300; var startX = _this.state.scrollLeft; var distance = Math.max(0, targetX) - startX; var startTime = new Date().getTime(); var container = _this.containerRef.current; var containerWidth = container.getBoundingClientRect().width; duration = duration || Math.min(Math.abs(distance), 300); var loopScroll = function loopScroll() { _this.scrollTimeoutId = setTimeout(function () { // Scroll percentage var p = Math.min(1, (new Date().getTime() - startTime) / duration); // Current position var x = Math.max(0, // eslint-disable-next-line no-mixed-operators Math.floor(startX + distance * (p < 0.5 ? 2 * p * p : p * (4 - p * 2) - 1))); var maxScrollX = container.scrollWidth - container.clientWidth; if (x > maxScrollX) { x = maxScrollX; } requestAnimationFrame(function () { _this.setState({ isScrolling: true, scrollLeft: x }, function () { _this.containerRef.current.scrollLeft = _this.state.scrollLeft; if (p < 1 && containerWidth + x < container.scrollWidth) { loopScroll(); } else { _this.stopScroll(); } }); }); }, 9); }; loopScroll(); }); _defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "scrollToCard", function (direction) { return function () { var _this$state = _this.state, scrollLeft = _this$state.scrollLeft, firstVisibleCardId = _this$state.firstVisibleCardId, firstFullVisibleCardId = _this$state.firstFullVisibleCardId, cards = _this$state.cards; var _this$props = _this.props, offset = _this$props.offset, id = _this$props.id; var container = _this.containerRef.current; var containerRect = container.getBoundingClientRect(); if (direction === _constants.DIRECTION.PREV && firstVisibleCardId === 0 && scrollLeft === 0) { return; } var isNext = direction === _constants.DIRECTION.NEXT; var nextCardId = isNext ? Math.min(cards.length - 1, firstFullVisibleCardId + _this.state.scrollAmount) : Math.max(0, firstFullVisibleCardId - _this.state.scrollAmount); if (nextCardId == null || Number.isNaN(nextCardId)) { console.error('Error: Invalid card id', nextCardId); return _this.setVisibleCardIds().then(function () { return _this.scrollToCard(direction); }); } var card = document.getElementById("card-".concat(nextCardId, "-").concat(id)); var cardRect = card.getBoundingClientRect(); var targetX = scrollLeft + cardRect.left + offset - containerRect.left; // Scroll to boundaries of first or last card if (nextCardId === 0) { targetX = 0; } if (nextCardId === cards.length - 1) { targetX = container.scrollWidth - container.clientWidth; } // Scrolling resets the X position of the current card to 0, because they slide to the left. // Scrolling position does not reset. This means we have to add // our current scroll X to the X position of the next card to reach the next card _this.scrollToX(targetX); }; }); if (props.children != null && typeof props.children !== 'function') { console.error('Error: children prop is not a function. When using the childen prop, you have to use a render function. This function returns components passed in the components prop.'); } var _cardRefs = _this.createCardRefs(); _this.state = { isScrolling: false, scrollLeft: 0, firstVisibleCardId: 0, firstFullVisibleCardId: 0, lastFullVisibleCardId: 0, cardRefs: _cardRefs, cards: Slider.createCards(props, _cardRefs) }; _this.containerRef = _react.default.createRef(); _this.scrollingEventId = null; _this.scrollTimeoutId = null; _this.touchHelper = new _TouchHelper.default(); // Pass our scrollToNextCard func to prop func var scrollToPrevCard = props.scrollToPrevCard, scrollToNextCard = props.scrollToNextCard; if (scrollToPrevCard) { scrollToPrevCard(_this.scrollToCard(_constants.DIRECTION.PREV)); } else { _this.scrollToCard(_constants.DIRECTION.PREV); } if (scrollToNextCard) { scrollToNextCard(_this.scrollToCard(_constants.DIRECTION.NEXT)); } else { _this.scrollToCard(_constants.DIRECTION.NEXT); } return _this; } _createClass(Slider, [{ key: "componentDidMount", value: function componentDidMount() { var _this2 = this; this.setVisibleCardIds().then(function () { // Set scroll amount to props or visible amount _this2.setState(function (prevState, props) { if (typeof props.scrollAmount === 'number') { return { scrollAmount: props.scrollAmount }; } return { scrollAmount: prevState.lastFullVisibleCardId - prevState.firstVisibleCardId + 1 }; }); }); // Disable default touchmove behaviour on iOS this.containerRef.current.addEventListener('touchmove', this.onTouchMove, { passive: false }); } }, { key: "componentDidUpdate", value: function componentDidUpdate(prevProps, prevState) { var prevComponents = prevProps.components; var components = this.props.components; // Pass new isScrolling state to prop func if (this.props.isScrolling && prevState.isScrolling !== this.state.isScrolling) { this.props.isScrolling(this.state.isScrolling); } if (prevComponents.length !== components.length) { var cardRefs = this.createCardRefs(); // we need to call this.createCardRefs() and this is not possible // in a static method like getDerivedStateFromProps() // eslint-disable-next-line react/no-did-update-set-state this.setState({ cardRefs: cardRefs, cards: Slider.createCards(_objectSpread({}, this.props, { components: components }), cardRefs) }); } } // eslint-disable-next-line react/sort-comp }, { key: "render", value: function render() { var _this$state2 = this.state, cards = _this$state2.cards, isScrolling = _this$state2.isScrolling; var className = this.props.className; var children = this.props.children ? this.props.children({ components: cards, swiped: this.touchHelper.swiped, isScrolling: isScrolling }) : cards; return _react.default.createElement("div", { className: className, onWheel: this.onMousewheel, onTouchStart: this.touchHelper.onTouchStart, onTouchEnd: this.onTouchEnd, ref: this.containerRef }, children); } }]); return Slider; }(_react.default.Component); _defineProperty(Slider, "propTypes", { children: _propTypes.default.func, offset: _propTypes.default.number, className: _propTypes.default.string, components: _propTypes.default.arrayOf(_propTypes.default.element), allowVerticalScrolling: _propTypes.default.bool, scrollToPrevCard: _propTypes.default.func, scrollToNextCard: _propTypes.default.func, isScrolling: _propTypes.default.func, id: _propTypes.default.string.isRequired, scrollAmount: _propTypes.default.oneOfType([_propTypes.default.number, _propTypes.default.oneOf(Object.values(_constants.SCROLL_AMOUNT_STR))]), refKey: _propTypes.default.string // eslint-disable-line }); _defineProperty(Slider, "defaultProps", { offset: 0, allowVerticalScrolling: false, scrollAmount: 'visible', refKey: 'ref' }); var _default = Slider; exports.default = _default;