react-smooth-slider
Version:
457 lines (365 loc) • 18.5 kB
JavaScript
;
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;