UNPKG

react-view-pager

Version:

View-Pager/Slider/Carousel powered by React Motion.

775 lines (642 loc) 23.5 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; 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); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 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; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var modulo = function modulo(num, max) { return (num % max + max) % max; }; // react-view-pager // const Slider = ({ slides }) => ( // <Frame> // { ({ position, isSliding, isSwiping }) => // <Motion style={{ value: isSwiping ? position : spring(position) }}> // { value => // <Track position={value}> // overrides internal position // {slides} // that position would set proper wrapper values // </Track> // } // </Motion> // } // </Frame> // ) var ALIGN_OFFSETS = { left: 0, center: 0.5, right: 1 }; var ElementSize = function () { function ElementSize(_ref) { var node = _ref.node, axis = _ref.axis, width = _ref.width, height = _ref.height; _classCallCheck(this, ElementSize); this.node = node; this.axis = axis; this.setSize(width, height); } _createClass(ElementSize, [{ key: 'setSize', value: function setSize(width, height) { this.width = width || this.node.offsetWidth; this.height = height || this.node.offsetHeight; } }, { key: 'getSize', value: function getSize(dimension) { if (dimension === 'width' || dimension === 'height') { return this[dimension]; } else { return this[this.axis === 'x' ? 'width' : 'height']; } } }]); return ElementSize; }(); var View = function (_ElementSize) { _inherits(View, _ElementSize); function View(options) { _classCallCheck(this, View); var _this = _possibleConstructorReturn(this, (View.__proto__ || Object.getPrototypeOf(View)).call(this, options)); _this.top = _this.left = { original: 0, offset: { pixel: 0, percent: 0 } }; return _this; } _createClass(View, [{ key: 'setCoords', value: function setCoords(position) { this[this.axis === 'y' ? 'top' : 'left'] = position; } }, { key: 'getCoords', value: function getCoords() { return this[this.axis === 'y' ? 'top' : 'left']; } }]); return View; }(ElementSize); var Views = function () { function Views(axis, viewsToShow, infinite) { _classCallCheck(this, Views); this.axis = axis; this.viewsToShow = viewsToShow; this.infinite = infinite; this.collection = []; } _createClass(Views, [{ key: 'setFrame', value: function setFrame(frame) { this.frame = frame; } }, { key: 'setTrack', value: function setTrack(track) { this.track = track; } }, { key: 'addView', value: function addView(node) { var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; var view = new View(_extends({ node: node, axis: this.axis, track: this.track }, options)); this.collection.push(view); // hydrate positions every time a new view is added this.setPositions(); } }, { key: 'setPositions', value: function setPositions() { var _this2 = this; // bail if frame or track haven't been set yet if (!this.frame && !this.track) return; var frameSize = this.frame.getSize(); var trackSize = this.getTotalSize(); var trackPosition = this.track.getPosition(); var startCoords = { top: 0, left: 0 }; this.collection.reduce(function (lastView, view) { var lastPosition = lastView && lastView.getCoords().original || 0; var nextPosition = lastPosition + view.getSize() / (_this2.viewsToShow || 1); var offsetPosition = lastPosition; // offset slides in the proper position when wrapping if (_this2.infinite) { if (nextPosition < Math.abs(trackPosition)) { offsetPosition += trackSize; } else if (lastPosition > frameSize - trackPosition) { offsetPosition -= trackSize; } } view.setCoords({ original: nextPosition, offset: { pixel: offsetPosition, percent: _this2.getPercentValue(offsetPosition) } }); return view; }, null); } }, { key: 'getTotalSize', value: function getTotalSize() { var _this3 = this; if (this.viewsToShow) { return this.frame.getSize() / this.viewsToShow * this.collection.length; } else { var _ret = function () { var dimension = _this3.axis === 'x' ? 'width' : 'height'; var size = 0; _this3.collection.forEach(function (view) { size += view[dimension]; }); return { v: size }; }(); if ((typeof _ret === 'undefined' ? 'undefined' : _typeof(_ret)) === "object") return _ret.v; } } }, { key: 'getStartCoords', value: function getStartCoords(index) { var _this4 = this; var size = 0; this.collection.slice(0, index).forEach(function (view) { size -= view.getSize() / (_this4.viewsToShow || 1); }); return size; } }, { key: 'getPercentValue', value: function getPercentValue(position) { return Math.round(position / this.frame.getSize() * 10000) * 0.01; } }]); return Views; }(); var Track = function (_ElementSize2) { _inherits(Track, _ElementSize2); function Track(options) { _classCallCheck(this, Track); var _this5 = _possibleConstructorReturn(this, (Track.__proto__ || Object.getPrototypeOf(Track)).call(this, options)); _this5.x = _this5.y = 0; return _this5; } _createClass(Track, [{ key: 'setPosition', value: function setPosition(position) { this[this.axis] = position; } }, { key: 'getPosition', value: function getPosition() { return this[this.axis]; } }]); return Track; }(ElementSize); var Frame = function (_ElementSize3) { _inherits(Frame, _ElementSize3); function Frame(options) { _classCallCheck(this, Frame); var _this6 = _possibleConstructorReturn(this, (Frame.__proto__ || Object.getPrototypeOf(Frame)).call(this, options)); _this6.x = _this6.y = 0; return _this6; } _createClass(Frame, [{ key: 'setPosition', value: function setPosition(position) { this[this.axis] = position; } }, { key: 'getPosition', value: function getPosition() { return this[this.axis]; } }]); return Frame; }(ElementSize); /////////////////////////////////////////////////////////// // START REACT /////////////////////////////////////////////////////////// function getTouchEvent(e) { return e.touches && e.touches[0] || e; } function clamp(val, min, max) { return Math.min(Math.max(min, val), max); } var ViewPage = function (_Component) { _inherits(ViewPage, _Component); function ViewPage() { _classCallCheck(this, ViewPage); return _possibleConstructorReturn(this, (ViewPage.__proto__ || Object.getPrototypeOf(ViewPage)).apply(this, arguments)); } _createClass(ViewPage, [{ key: 'componentDidMount', value: function componentDidMount() { this.props.onMount((0, _reactDom.findDOMNode)(this)); } }, { key: 'render', value: function render() { var _props = this.props, view = _props.view, viewsToShow = _props.viewsToShow, axis = _props.axis, children = _props.children; var child = _react.Children.only(children); var style = _extends({}, child.props.style, { top: (view.top && view.top.offset.percent) + '%' || 0 + '%', left: (view.left && view.left.offset.percent) + '%' || 0 + '%' }); if (viewsToShow) { style[axis === 'x' ? 'width' : 'height'] = 100 / viewsToShow + '%'; } return (0, _react.cloneElement)(child, { ref: this._handleNode, style: style }); } }]); return ViewPage; }(_react.Component); var ViewPager = function (_Component2) { _inherits(ViewPager, _Component2); function ViewPager(props) { _classCallCheck(this, ViewPager); var _this8 = _possibleConstructorReturn(this, (ViewPager.__proto__ || Object.getPrototypeOf(ViewPager)).call(this, props)); _this8.slide = function (direction) { var index = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : _this8.state.currentView; var _this8$props = _this8.props, children = _this8$props.children, viewsToMove = _this8$props.viewsToMove, infinite = _this8$props.infinite; var newIndex = index + direction * viewsToMove; var currentView = infinite ? modulo(newIndex, _this8._viewCount - 1) : clamp(newIndex, 0, _this8._viewCount - 1); _this8.setState({ currentView: currentView }, function () { _this8.props.onChange(currentView); }); }; _this8._handleViewMount = function (node) { _this8._views.addView(node); _this8.forceUpdate(); }; _this8._onSwipeStart = function (e) { var _getTouchEvent = getTouchEvent(e), pageX = _getTouchEvent.pageX, pageY = _getTouchEvent.pageY; // we're now swiping _this8._isSwiping = true; // store the initial starting coordinates _this8._startTrack = _this8._track.getPosition() - _this8._getAlignOffset(); _this8._startSwipe = { x: pageX, y: pageY }; // determine if a flick or not _this8._isFlick = true; setTimeout(function () { _this8._isFlick = false; }, _this8.props.flickTimeout); }; _this8._onSwipeMove = function (e) { // bail if we aren't swiping if (!_this8._isSwiping) return; var _this8$props2 = _this8.props, swipeThreshold = _this8$props2.swipeThreshold, axis = _this8$props2.axis, viewsToMove = _this8$props2.viewsToMove; var _getTouchEvent2 = getTouchEvent(e), pageX = _getTouchEvent2.pageX, pageY = _getTouchEvent2.pageY; // determine how much we have moved _this8._swipeDiff = { x: _this8._startSwipe.x - pageX, y: _this8._startSwipe.y - pageY }; if (_this8._isSwipe(swipeThreshold)) { e.preventDefault(); e.stopPropagation(); // let swipDiff = this._swipeDiff[axis] * edgeFriction var swipDiff = _this8._swipeDiff[axis]; var newTrackPosition = (_this8._startTrack - swipDiff) * viewsToMove; // console.log(this._getOutsideTrackBounds(newTrackPosition)) // don't allow swiping if we are containing slides // this logic will make sure the track position stays within the bounds of the frame // all while allowing to swipe past // if it goes past the bounds we will activate the edgeFriction // if (Math.abs(newTrackPosition) > this._track.getSize() - this._frame.getSize()) { // // } else if () { // // } // make sure we stay within the defined threshold // newTrackPosition = clamp(val, min, max) _this8._setTrackPosition(newTrackPosition); } }; _this8._onSwipeEnd = function () { var _this8$props3 = _this8.props, swipeThreshold = _this8$props3.swipeThreshold, axis = _this8$props3.axis; var currentViewSize = _this8._getCurrentViewSize(); var threshold = _this8._isFlick ? swipeThreshold : currentViewSize * swipeThreshold; // if (this._isSwipe(threshold)) { // (this._swipeDiff[axis] < 0) ? this.prev() : this.next() // } _this8._isSwiping = false; }; _this8._onSwipePast = function () { // perform a swipe end if we swiped past the component if (_this8._isSwiping) { _this8._onSwipeEnd(); } }; _this8._views = new Views(props.axis, props.viewsToShow, props.infinite); _this8._viewCount = _react.Children.count(props.children); // swiping _this8._startSwipe = {}; _this8._swipeDiff = {}; _this8._isSwiping = false; _this8._isFlick = false; _this8.state = { currentTrackPosition: 0, currentView: props.currentView }; return _this8; } _createClass(ViewPager, [{ key: 'componentDidMount', value: function componentDidMount() { var _props2 = this.props, autoSize = _props2.autoSize, axis = _props2.axis; this._frame = new Frame({ node: this._frameNode, width: autoSize && this._getCurrentViewSize('width'), height: autoSize && this._getCurrentViewSize('height'), axis: axis }); this._track = new Track({ node: this._trackNode, axis: axis }); // set frame and track for views to access this._views.setFrame(this._frame); this._views.setTrack(this._track); // set positions so we can get a total width this._views.setPositions(); // set track width to the size of views var totalViewSize = this._views.getTotalSize(); this._track.setSize(totalViewSize, totalViewSize); // finally, set the initial track position this._setTrackPosition(this._getStartCoords()); } }, { key: 'componentWillReceiveProps', value: function componentWillReceiveProps(_ref2) { var currentView = _ref2.currentView; // update state with new index if necessary if ((typeof currentView === 'undefined' ? 'undefined' : _typeof(currentView)) !== undefined && this.props.currentView !== currentView) { this.setState({ currentView: currentView }); } } }, { key: 'componentDidUpdate', value: function componentDidUpdate(lastProps, lastState) { // reposition slider if index has changed if (this.state.currentView !== lastState.currentView) { this._setTrackPosition(this._getStartCoords()); } // update frame size to match new view size if (this.props.autoSize) { var width = this._getCurrentViewSize('width'); var height = this._getCurrentViewSize('height'); // update frame size this._frame.setSize(width, height); // update view positions this._views.setPositions(); } } }, { key: 'prev', value: function prev() { this.slide(-1); } }, { key: 'next', value: function next() { this.slide(1); } }, { key: '_getStartCoords', value: function _getStartCoords() { var index = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.state.currentView; return this._views.getStartCoords(index); } }, { key: '_getCurrentViewSize', value: function _getCurrentViewSize(dimension) { var currentView = this._views.collection[this.state.currentView]; return currentView && currentView.getSize(dimension) || 0; } }, { key: '_getAlignOffset', value: function _getAlignOffset() { var _props3 = this.props, align = _props3.align, viewsToShow = _props3.viewsToShow; var frameSize = this._frame.getSize(); var currentViewSize = this._getCurrentViewSize(); var alignMultiplier = isNaN(align) ? ALIGN_OFFSETS[align] : align; return (frameSize - currentViewSize / (viewsToShow || 1)) * alignMultiplier; } }, { key: '_setTrackPosition', value: function _setTrackPosition(position) { var _props4 = this.props, infinite = _props4.infinite, contain = _props4.contain; var frameSize = this._frame.getSize(); var trackSize = this._track.getSize(); // wrapping if (infinite) { position = modulo(position, trackSize) - trackSize; } // alignment position += this._getAlignOffset(); // clamp value if we want to contain the position if (contain) { position = clamp(position, -(trackSize - frameSize), 0); } // set new track position this._track.setPosition(position); // update view positions this._views.setPositions(); // update state this.setState({ currentTrackPosition: position }); } }, { key: '_isSwipe', value: function _isSwipe(threshold) { var axis = this.props.axis; var _swipeDiff = this._swipeDiff, x = _swipeDiff.x, y = _swipeDiff.y; return axis === 'x' ? Math.abs(x) > Math.max(threshold, Math.abs(y)) : Math.abs(x) < Math.max(threshold, Math.abs(y)); } }, { key: '_getOutsideTrackBounds', value: function _getOutsideTrackBounds(trackPosition) { return { first: trackPosition > 0, last: Math.abs(trackPosition) > this._track.getSize() - this._frame.getSize() }; } }, { key: '_getSwipeEvents', value: function _getSwipeEvents() { var swipe = this.props.swipe; var swipeEvents = {}; if (swipe === true || swipe === 'mouse') { swipeEvents.onMouseDown = this._onSwipeStart; swipeEvents.onMouseMove = this._onSwipeMove; swipeEvents.onMouseUp = this._onSwipeEnd; swipeEvents.onMouseLeave = this._onSwipePast; } if (swipe === true || swipe === 'touch') { swipeEvents.onTouchStart = this._onSwipeStart; swipeEvents.onTouchMove = this._onSwipeMove; swipeEvents.onTouchEnd = this._onSwipeEnd; } return swipeEvents; } }, { key: '_getPositionValue', value: function _getPositionValue(position) { var frameSize = this._frame && this._frame.getSize() || 0; return Math.round(position / frameSize * 10000) * 0.01; } }, { key: '_getTransformValue', value: function _getTransformValue(trackPosition) { var axis = this.props.axis; var position = { x: 0, y: 0 }; position[axis] = trackPosition || 0; return 'translate3d(' + position.x + '%, ' + position.y + '%, 0)'; } }, { key: 'render', value: function render() { var _this9 = this; var _props5 = this.props, autoSize = _props5.autoSize, viewsToShow = _props5.viewsToShow, axis = _props5.axis, children = _props5.children; var trackPosition = this._getPositionValue(this.state.currentTrackPosition); var frameStyles = {}; if (autoSize) { frameStyles.width = this._getCurrentViewSize('width') || 'auto'; frameStyles.height = this._getCurrentViewSize('height') || 'auto'; } return _react2.default.createElement( 'div', _extends({ ref: function ref(c) { return _this9._frameNode = (0, _reactDom.findDOMNode)(c); }, className: 'frame', style: frameStyles }, this._getSwipeEvents()), _react2.default.createElement( 'div', { ref: function ref(c) { return _this9._trackNode = (0, _reactDom.findDOMNode)(c); }, className: 'track', style: { transform: this._getTransformValue(trackPosition) } }, _react.Children.map(children, function (child, index) { return _react2.default.createElement(ViewPage, { view: _this9._views.collection[index] || {}, viewsToShow: viewsToShow, axis: axis, onMount: _this9._handleViewMount, children: child }); }) ) ); } }]); return ViewPager; }(_react.Component); ViewPager.propTypes = { currentView: _react.PropTypes.any, viewsToShow: _react.PropTypes.number, viewsToMove: _react.PropTypes.number, infinite: _react.PropTypes.bool, instant: _react.PropTypes.bool, axis: _react.PropTypes.oneOf(['x', 'y']), align: _react.PropTypes.oneOf(['left', 'center', 'right', _react.PropTypes.number]), autoSize: _react.PropTypes.bool, swipe: _react.PropTypes.oneOf([true, false, 'mouse', 'touch']), swipeThreshold: _react.PropTypes.number, // to advance slides, the user must swipe a length of (1/touchThreshold) * the width of the slider flickTimeout: _react.PropTypes.number, contain: _react.PropTypes.bool, // lazyLoad: PropTypes.bool, // rtl: PropTypes.bool, // springConfig: React.PropTypes.objectOf(React.PropTypes.number), beforeAnimation: _react.PropTypes.func, afterAnimation: _react.PropTypes.func }; ViewPager.defaultProps = { currentView: 0, viewsToShow: 0, viewsToMove: 1, infinite: true, instant: false, axis: 'x', align: 'left', autoSize: false, contain: false, // don't allow slider to show empty cells swipe: true, swipeThreshold: 0.5, flickTimeout: 300, edgeFriction: 0, // the amount the slider can swipe past on the ends if infinite is false // lazyLoad: false, // lazyily load components as they enter // rtl: false, // right to left // springConfig: presets.gentle, onChange: function onChange() { return null; }, beforeAnimation: function beforeAnimation() { return null; }, afterAnimation: function afterAnimation() { return null; } }; exports.default = ViewPager; module.exports = exports['default'];