UNPKG

nuorder-carousel

Version:
619 lines (507 loc) 22.9 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); 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 _reactDom = require('react-dom'); var _reactDom2 = _interopRequireDefault(_reactDom); var _propTypes = require('prop-types'); var _propTypes2 = _interopRequireDefault(_propTypes); var _cssClasses = require('../cssClasses'); var _cssClasses2 = _interopRequireDefault(_cssClasses); var _CSSTranslate = require('../CSSTranslate'); var _CSSTranslate2 = _interopRequireDefault(_CSSTranslate); var _reactEasySwipe = require('react-easy-swipe'); var _reactEasySwipe2 = _interopRequireDefault(_reactEasySwipe); var _Thumbs = require('./Thumbs'); var _Thumbs2 = _interopRequireDefault(_Thumbs); var _customPropTypes = require('../customPropTypes'); var customPropTypes = _interopRequireWildcard(_customPropTypes); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } 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; } var noop = function noop() {}; var defaultStatusFormatter = function defaultStatusFormatter(current, total) { return current + ' of ' + total; }; var Carousel = function (_Component) { _inherits(Carousel, _Component); function Carousel(props) { _classCallCheck(this, Carousel); var _this = _possibleConstructorReturn(this, (Carousel.__proto__ || Object.getPrototypeOf(Carousel)).call(this, props)); _this.autoPlay = function () { _this.timer = setTimeout(function () { _this.increment(); }, _this.props.interval); }; _this.clearAutoPlay = function () { if (!_this.props.autoPlay) { return; } clearTimeout(_this.timer); }; _this.resetAutoPlay = function () { _this.clearAutoPlay(); _this.autoPlay(); }; _this.stopOnHover = function () { _this.clearAutoPlay(); }; _this.navigateWithKeyboard = function (e) { var axis = _this.props.axis; var isHorizontal = axis === 'horizontal'; var nextKey = isHorizontal ? 'ArrowRight' : 'ArrowDown'; var prevKey = isHorizontal ? 'ArrowLeft' : 'ArrowUp'; if (nextKey === e.key) { _this.increment(); } else if (prevKey === e.key) { _this.decrement(); } }; _this.updateSizes = function () { if (!_this.state.initialized) { return; } var isHorizontal = _this.props.axis === 'horizontal'; var firstItem = _this.refs.item0; var itemSize = isHorizontal ? firstItem.clientWidth : firstItem.clientHeight; _this.setState({ itemSize: itemSize, wrapperSize: isHorizontal ? itemSize * _this.props.children.length : itemSize }); }; _this.setMountState = function () { _this.setState({ hasMount: true }); _this.updateSizes(); }; _this.handleClickItem = function (index, item) { if (_this.state.cancelClick) { _this.setState({ cancelClick: false }); return; } _this.props.onClickItem(index, item); if (index !== _this.state.selectedItem) { _this.setState({ selectedItem: index }); } }; _this.handleOnChange = function (index, item) { _this.props.onChange(index, item); }; _this.handleClickThumb = function (index, item) { _this.props.onClickThumb(index, item); _this.selectItem({ selectedItem: index }); }; _this.onSwipeStart = function () { _this.setState({ swiping: true }); _this.clearAutoPlay(); }; _this.onSwipeEnd = function () { _this.setState({ swiping: false }); _this.autoPlay(); }; _this.onSwipeMove = function (delta) { var list = _reactDom2.default.findDOMNode(_this.refs.itemList); var isHorizontal = _this.props.axis === 'horizontal'; var initialBoundry = 0; var currentPosition = -_this.state.selectedItem * 100; var finalBoundry = -(_this.props.children.length - 1) * 100; var axisDelta = isHorizontal ? delta.x : delta.y; var handledDelta = axisDelta; // prevent user from swiping left out of boundaries if (currentPosition === initialBoundry && axisDelta > 0) { handledDelta = 0; } // prevent user from swiping right out of boundaries if (currentPosition === finalBoundry && axisDelta < 0) { handledDelta = 0; } var position = currentPosition + 100 / (_this.state.itemSize / handledDelta) + '%'; ['WebkitTransform', 'MozTransform', 'MsTransform', 'OTransform', 'transform', 'msTransform'].forEach(function (prop) { list.style[prop] = (0, _CSSTranslate2.default)(position, _this.props.axis); }); // allows scroll if the swipe was within the tolerance var hasMoved = Math.abs(axisDelta) > _this.props.swipeScrollTolerance; if (hasMoved && !_this.state.cancelClick) { _this.setState({ cancelClick: true }); } return hasMoved; }; _this.decrement = function (positions) { _this.moveTo(_this.state.selectedItem - (typeof positions === 'Number' ? positions : 1)); }; _this.increment = function (positions) { _this.moveTo(_this.state.selectedItem + (typeof positions === 'Number' ? positions : 1)); }; _this.moveTo = function (position) { var lastPosition = _this.props.children.length - 1; if (position < 0) { position = _this.props.infiniteLoop ? lastPosition : 0; } if (position > lastPosition) { position = _this.props.infiniteLoop ? 0 : lastPosition; } _this.selectItem({ // if it's not a slider, we don't need to set position here selectedItem: position }); if (_this.props.autoPlay) { _this.resetAutoPlay(); } }; _this.changeItem = function (e) { var newIndex = e.target.value; _this.selectItem({ selectedItem: newIndex }); }; _this.selectItem = function (state) { _this.setState(state); _this.handleOnChange(state.selectedItem, _this.props.children[state.selectedItem]); }; _this.getInitialImage = function () { var selectedItem = _this.props.selectedItem; var item = _this.refs['item' + selectedItem]; var images = item && item.getElementsByTagName('img'); return images && images[selectedItem]; }; _this.getVariableImageHeight = function (position) { var item = _this.refs['item' + position]; var images = item && item.getElementsByTagName('img'); if (_this.state.hasMount && images.length > 0) { var image = images[0]; if (!image.complete) { // if the image is still loading, the size won't be available so we trigger a new render after it's done var onImageLoad = function onImageLoad() { _this.forceUpdate(); image.removeEventListener('load', onImageLoad); }; image.addEventListener('load', onImageLoad); } var height = image.clientHeight; return height > 0 ? height : null; } return null; }; _this.state = { initialized: false, selectedItem: props.selectedItem, hasMount: false }; return _this; } _createClass(Carousel, [{ key: 'componentDidMount', value: function componentDidMount() { if (!this.props.children) { return; } this.setupCarousel(); } }, { key: 'componentWillReceiveProps', value: function componentWillReceiveProps(nextProps) { if (nextProps.selectedItem !== this.state.selectedItem) { this.updateSizes(); this.setState({ selectedItem: nextProps.selectedItem }); } if (nextProps.autoPlay !== this.props.autoPlay) { if (nextProps.autoPlay) { this.setupAutoPlay(); } else { this.destroyAutoPlay(); } } } }, { key: 'componentDidUpdate', value: function componentDidUpdate(prevProps) { if (!prevProps.children && this.props.children && !this.state.initialized) { this.setupCarousel(); } } }, { key: 'componentWillUnmount', value: function componentWillUnmount() { this.destroyCarousel(); } }, { key: 'setupCarousel', value: function setupCarousel() { this.bindEvents(); if (this.props.autoPlay) { this.setupAutoPlay(); } this.setState({ initialized: true }); var initialImage = this.getInitialImage(); if (initialImage) { // if it's a carousel of images, we set the mount state after the first image is loaded initialImage.addEventListener('load', this.setMountState); } else { this.setMountState(); } } }, { key: 'destroyCarousel', value: function destroyCarousel() { if (this.state.initialized) { this.unbindEvents(); this.destroyAutoPlay(); } } }, { key: 'setupAutoPlay', value: function setupAutoPlay() { this.autoPlay(); var carouselWrapper = this.refs['carouselWrapper']; if (this.props.stopOnHover && carouselWrapper) { carouselWrapper.addEventListener('mouseenter', this.stopOnHover); carouselWrapper.addEventListener('mouseleave', this.autoPlay); } } }, { key: 'destroyAutoPlay', value: function destroyAutoPlay() { this.clearAutoPlay(); var carouselWrapper = this.refs['carouselWrapper']; if (this.props.stopOnHover && carouselWrapper) { carouselWrapper.removeEventListener('mouseenter', this.stopOnHover); carouselWrapper.removeEventListener('mouseleave', this.autoPlay); } } }, { key: 'bindEvents', value: function bindEvents() { // as the widths are calculated, we need to resize // the carousel when the window is resized window.addEventListener("resize", this.updateSizes); // issue #2 - image loading smaller window.addEventListener("DOMContentLoaded", this.updateSizes); if (this.props.useKeyboardArrows) { document.addEventListener("keydown", this.navigateWithKeyboard); } } }, { key: 'unbindEvents', value: function unbindEvents() { // removing listeners window.removeEventListener("resize", this.updateSizes); window.removeEventListener("DOMContentLoaded", this.updateSizes); var initialImage = this.getInitialImage(); if (initialImage) { initialImage.removeEventListener("load", this.setMountState); } if (this.props.useKeyboardArrows) { document.removeEventListener("keydown", this.navigateWithKeyboard); } } }, { key: 'renderItems', value: function renderItems() { var _this2 = this; return _react2.default.Children.map(this.props.children, function (item, index) { var hasMount = _this2.state.hasMount; var itemClass = _cssClasses2.default.ITEM(true, index === _this2.state.selectedItem); return _react2.default.createElement( 'li', { ref: "item" + index, key: "itemKey" + index, className: itemClass, onClick: _this2.handleClickItem.bind(_this2, index, item) }, item ); }); } }, { key: 'renderControls', value: function renderControls() { var _this3 = this; if (!this.props.showIndicators) { return null; } return _react2.default.createElement( 'ul', { className: 'control-dots' }, _react2.default.Children.map(this.props.children, function (item, index) { return _react2.default.createElement('li', { className: _cssClasses2.default.DOT(index === _this3.state.selectedItem), onClick: _this3.changeItem, value: index, key: index }); }) ); } }, { key: 'renderStatus', value: function renderStatus() { if (!this.props.showStatus) { return null; } return _react2.default.createElement( 'p', { className: 'carousel-status' }, this.props.statusFormatter(this.state.selectedItem + 1, this.props.children.length) ); } }, { key: 'renderThumbs', value: function renderThumbs() { if (!this.props.showThumbs || this.props.children.length === 0) { return null; } return _react2.default.createElement( _Thumbs2.default, { onSelectItem: this.handleClickThumb, selectedItem: this.state.selectedItem, transitionTime: this.props.transitionTime }, this.props.children ); } }, { key: 'render', value: function render() { if (!this.props.children || this.props.children.length === 0) { return null; } var itemsLength = this.props.children.length; var isHorizontal = this.props.axis === 'horizontal'; var canShowArrows = this.props.showArrows && itemsLength > 1; // show left arrow? var hasPrev = canShowArrows && (this.state.selectedItem > 0 || this.props.infiniteLoop); // show right arrow var hasNext = canShowArrows && (this.state.selectedItem < itemsLength - 1 || this.props.infiniteLoop); // obj to hold the transformations and styles var itemListStyles = {}; var currentPosition = -this.state.selectedItem * 100 + '%'; // if 3d is available, let's take advantage of the performance of transform var transformProp = (0, _CSSTranslate2.default)(currentPosition, this.props.axis); var transitionTime = this.props.transitionTime + 'ms'; itemListStyles = { 'WebkitTransform': transformProp, 'MozTransform': transformProp, 'MsTransform': transformProp, 'OTransform': transformProp, 'transform': transformProp, 'msTransform': transformProp }; if (!this.state.swiping) { itemListStyles = _extends({}, itemListStyles, { 'WebkitTransitionDuration': transitionTime, 'MozTransitionDuration': transitionTime, 'MsTransitionDuration': transitionTime, 'OTransitionDuration': transitionTime, 'transitionDuration': transitionTime, 'msTransitionDuration': transitionTime }); } var swiperProps = { selectedItem: this.state.selectedItem, className: _cssClasses2.default.SLIDER(true, this.state.swiping), onSwipeMove: this.onSwipeMove, onSwipeStart: this.onSwipeStart, onSwipeEnd: this.onSwipeEnd, style: itemListStyles, tolerance: this.props.swipeScrollTolerance, ref: 'itemList' }; var containerStyles = {}; if (isHorizontal) { swiperProps.onSwipeLeft = this.increment; swiperProps.onSwipeRight = this.decrement; if (this.props.dynamicHeight) { var itemHeight = this.getVariableImageHeight(this.state.selectedItem); swiperProps.style.height = itemHeight || 'auto'; containerStyles.height = itemHeight || 'auto'; } } else { swiperProps.onSwipeUp = this.decrement; swiperProps.onSwipeDown = this.increment; swiperProps.style.height = this.state.itemSize; containerStyles.height = this.state.itemSize; } return _react2.default.createElement( 'div', { className: this.props.className, ref: 'carouselWrapper' }, _react2.default.createElement( 'div', { className: _cssClasses2.default.CAROUSEL(true), style: { width: this.props.width } }, _react2.default.createElement('button', { type: 'button', className: _cssClasses2.default.ARROW_PREV(!hasPrev), onClick: this.decrement }), _react2.default.createElement( 'div', { className: _cssClasses2.default.WRAPPER(true, this.props.axis), style: containerStyles, ref: 'itemsWrapper' }, _react2.default.createElement( _reactEasySwipe2.default, _extends({ tagName: 'ul' }, swiperProps, { allowMouseEvents: this.props.emulateTouch }), this.renderItems() ) ), _react2.default.createElement('button', { type: 'button', className: _cssClasses2.default.ARROW_NEXT(!hasNext), onClick: this.increment }), this.renderControls(), this.renderStatus() ), this.renderThumbs() ); } }]); return Carousel; }(_react.Component); Carousel.displayName = 'Carousel'; Carousel.propTypes = { className: _propTypes2.default.string, children: _propTypes2.default.node, showArrows: _propTypes2.default.bool, showStatus: _propTypes2.default.bool, showIndicators: _propTypes2.default.bool, infiniteLoop: _propTypes2.default.bool, showThumbs: _propTypes2.default.bool, selectedItem: _propTypes2.default.number, onClickItem: _propTypes2.default.func.isRequired, onClickThumb: _propTypes2.default.func.isRequired, onChange: _propTypes2.default.func.isRequired, axis: _propTypes2.default.oneOf(['horizontal', 'vertical']), width: customPropTypes.unit, useKeyboardArrows: _propTypes2.default.bool, autoPlay: _propTypes2.default.bool, stopOnHover: _propTypes2.default.bool, interval: _propTypes2.default.number, transitionTime: _propTypes2.default.number, swipeScrollTolerance: _propTypes2.default.oneOfType([_propTypes2.default.number]), dynamicHeight: _propTypes2.default.bool, emulateTouch: _propTypes2.default.bool, statusFormatter: _propTypes2.default.func.isRequired }; Carousel.defaultProps = { showIndicators: true, showArrows: true, showStatus: true, showThumbs: true, infiniteLoop: false, selectedItem: 0, axis: 'horizontal', width: '100%', useKeyboardArrows: false, autoPlay: false, stopOnHover: true, interval: 3000, transitionTime: 350, swipeScrollTolerance: 5, dynamicHeight: false, emulateTouch: false, onClickItem: noop, onClickThumb: noop, onChange: noop, statusFormatter: defaultStatusFormatter }; exports.default = Carousel;