react-view-pager
Version:
View-Pager/Slider/Carousel powered by React Motion.
838 lines (696 loc) • 25.4 kB
JavaScript
'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);
var _reactMotion = require('react-motion');
var _PagerElement = require('./PagerElement');
var _PagerElement2 = _interopRequireDefault(_PagerElement);
var _getIndex = require('./get-index');
var _getIndex2 = _interopRequireDefault(_getIndex);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; }
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 TRANSFORM = require('get-prefix')('transform');
function modulo(val, max) {
return (val % max + max) % max;
}
function clamp(val, min, max) {
return Math.min(Math.max(min, val), max);
}
function getTouchEvent(e) {
return e.touches && e.touches[0] || e;
}
var Pager = function () {
function Pager() {
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
_classCallCheck(this, Pager);
this.views = [];
this.currentIndex = 0;
this.currentView = null;
this.trackPosition = 0;
this.isSwiping = false;
this.options = _extends({
axis: 'x',
align: 0,
contain: false,
infinite: false,
viewsToMove: 1
}, options);
}
_createClass(Pager, [{
key: 'addFrame',
value: function addFrame(node) {
this.frame = new _PagerElement2.default({ node: node, pager: this });
}
}, {
key: 'addTrack',
value: function addTrack(node) {
this.track = new _PagerElement2.default({ node: node, pager: this });
}
}, {
key: 'addView',
value: function addView(node) {
var align = this.options.align;
var index = this.views.length;
var view = new _PagerElement2.default({
node: node,
pager: this
});
// add view to collection
// allow option to prepend, insert, or append
this.views.push(view);
// set target position
var target = this.getStartCoords(index);
if (align) {
target += this.getAlignOffset(view);
}
view.target = target;
// set this as the first view if there isn't one
if (!this.currentView) {
this.currentView = view;
}
// with each view added we need to re-calculate positions
this.positionViews();
return view;
}
}, {
key: 'prev',
value: function prev() {
this.setCurrentView(-1);
}
}, {
key: 'next',
value: function next() {
this.setCurrentView(1);
}
}, {
key: 'setPositionValue',
value: function setPositionValue() {
var trackPosition = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.currentView ? this.currentView.target : 0;
var _options = this.options,
infinite = _options.infinite,
contain = _options.contain;
var trackSize = this.getTrackSize();
if (infinite && !this.isSwiping) {
// we offset by a track multiplier so infinite animation works as expected
trackPosition -= (Math.floor(this.currentIndex / this.views.length) || 0) * trackSize;
}
if (contain && !this.isSwiping) {
trackPosition = clamp(trackPosition, this.frame.getSize() - trackSize, 0);
}
this.trackPosition = trackPosition;
}
}, {
key: 'setCurrentView',
value: function setCurrentView(direction) {
var index = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.currentIndex;
var _options2 = this.options,
viewsToMove = _options2.viewsToMove,
infinite = _options2.infinite,
onChange = _options2.onChange;
var newIndex = index + direction * viewsToMove;
var currentIndex = infinite ? newIndex : clamp(newIndex, 0, this.views.length - 1);
this.currentIndex = currentIndex;
this.currentView = this.getView(currentIndex);
this.setPositionValue();
}
}, {
key: 'resetViews',
value: function resetViews() {
// reset back to a normal index
this.setCurrentView(0, modulo(this.currentIndex, this.views.length));
}
}, {
key: 'getTransformValue',
value: function getTransformValue() {
var trackPosition = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.trackPosition;
var _options3 = this.options,
infinite = _options3.infinite,
contain = _options3.contain;
var position = { x: 0, y: 0 };
if (infinite) {
trackPosition = this.getWrappedTrackValue(trackPosition);
}
position[this.options.axis] = trackPosition;
return 'translate3d(' + position.x + 'px, ' + position.y + 'px, 0)';
}
}, {
key: 'getWrappedTrackValue',
value: function getWrappedTrackValue(position) {
var trackSize = this.getTrackSize();
return modulo(position, -trackSize) || 0;
}
// where the view should start
}, {
key: 'getStartCoords',
value: function getStartCoords(index) {
var target = 0;
this.views.slice(0, index).forEach(function (view) {
target -= view.getSize();
});
return target;
}
}, {
key: 'getFrameAutoSize',
value: function getFrameAutoSize() {
var _options4 = this.options,
viewsToShow = _options4.viewsToShow,
axis = _options4.axis;
var maxHeight = 0;
var maxWidth = 0;
if (viewsToShow !== 'auto') {
this.views.slice(this.currentIndex, this.currentIndex + viewsToShow).forEach(function (view) {
var width = view.getSize('width');
var height = view.getSize('height');
if (axis === 'x') {
maxWidth += width;
if (height > maxHeight) {
maxHeight = height;
}
} else {
maxHeight += height;
if (width > maxWidth) {
maxWidth = width;
}
}
});
} else {
maxWidth = this.currentView.getSize('width');
maxHeight = this.currentView.getSize('height');
}
return {
width: maxWidth,
height: maxHeight
};
}
}, {
key: 'getTrackSize',
value: function getTrackSize() {
var size = 0;
this.views.forEach(function (view) {
size += view.getSize();
});
return size;
}
// how much to offset the view considering the align option
}, {
key: 'getAlignOffset',
value: function getAlignOffset(view) {
var frameSize = this.frame.getSize();
var viewSize = view.getSize();
return (frameSize - viewSize) * this.options.align;
}
}, {
key: 'getView',
value: function getView(index) {
return this.views[modulo(index, this.views.length)];
}
}, {
key: 'positionViews',
value: function positionViews() {
var trackPosition = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
var _options5 = this.options,
infinite = _options5.infinite,
align = _options5.align;
var frameSize = this.frame.getSize();
var trackSize = this.getTrackSize();
trackPosition = modulo(trackPosition, -trackSize);
this.views.reduce(function (lastPosition, view, index) {
var viewSize = view.getSize();
var nextPosition = lastPosition + viewSize;
var position = lastPosition;
if (infinite) {
// shift views around so they are always visible in frame
if (nextPosition + viewSize * align < Math.abs(trackPosition)) {
position += trackSize;
} else if (lastPosition > frameSize - trackPosition) {
position -= trackSize;
}
}
view.setPosition(position);
return nextPosition;
}, 0);
}
}]);
return Pager;
}();
var Frame = function (_Component) {
_inherits(Frame, _Component);
function Frame() {
_classCallCheck(this, Frame);
return _possibleConstructorReturn(this, (Frame.__proto__ || Object.getPrototypeOf(Frame)).apply(this, arguments));
}
_createClass(Frame, [{
key: 'componentDidMount',
value: function componentDidMount() {
this.context.viewPager.addFrame((0, _reactDom.findDOMNode)(this));
}
}, {
key: 'render',
value: function render() {
var _props = this.props,
tag = _props.tag,
restProps = _objectWithoutProperties(_props, ['tag']);
return (0, _react.createElement)(tag, restProps);
}
}]);
return Frame;
}(_react.Component);
Frame.defaultProps = {
tag: 'div'
};
Frame.contextTypes = {
viewPager: _react.PropTypes.instanceOf(Pager)
};
var Track = function (_Component2) {
_inherits(Track, _Component2);
function Track() {
_classCallCheck(this, Track);
return _possibleConstructorReturn(this, (Track.__proto__ || Object.getPrototypeOf(Track)).apply(this, arguments));
}
_createClass(Track, [{
key: 'componentDidMount',
value: function componentDidMount() {
this.context.viewPager.addTrack((0, _reactDom.findDOMNode)(this));
}
}, {
key: 'componentWillUpdate',
value: function componentWillUpdate(_ref) {
var position = _ref.position;
// update view positions with current position tween
// this method can get called thousands of times, let's make sure to optimize as much as we can
this.context.viewPager.positionViews(position);
}
}, {
key: 'render',
value: function render() {
var _props2 = this.props,
tag = _props2.tag,
position = _props2.position,
restProps = _objectWithoutProperties(_props2, ['tag', 'position']);
var style = _extends({}, restProps.style, {
transform: this.context.viewPager.getTransformValue(position)
});
return (0, _react.createElement)(tag, _extends({}, restProps, { style: style }));
}
}]);
return Track;
}(_react.Component);
Track.defaultProps = {
tag: 'div'
};
Track.contextTypes = {
viewPager: _react.PropTypes.instanceOf(Pager)
};
var View = function (_Component3) {
_inherits(View, _Component3);
function View() {
var _ref2;
var _temp, _this3, _ret;
_classCallCheck(this, View);
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
return _ret = (_temp = (_this3 = _possibleConstructorReturn(this, (_ref2 = View.__proto__ || Object.getPrototypeOf(View)).call.apply(_ref2, [this].concat(args))), _this3), _this3.state = {
viewInstance: null
}, _temp), _possibleConstructorReturn(_this3, _ret);
}
_createClass(View, [{
key: 'componentDidMount',
value: function componentDidMount() {
this.setState({
viewInstance: this.context.viewPager.addView((0, _reactDom.findDOMNode)(this))
});
}
}, {
key: 'render',
value: function render() {
var _context$viewPager$op = this.context.viewPager.options,
viewsToShow = _context$viewPager$op.viewsToShow,
axis = _context$viewPager$op.axis;
var _props3 = this.props,
children = _props3.children,
restProps = _objectWithoutProperties(_props3, ['children']);
var viewInstance = this.state.viewInstance;
var child = _react.Children.only(children);
var style = _extends({}, child.props.style);
if (viewsToShow !== 'auto') {
style[axis === 'x' ? 'width' : 'height'] = this.context.viewPager.frame.getSize() / viewsToShow;
}
if (viewInstance) {
style[axis === 'y' ? 'top' : 'left'] = viewInstance.getPosition();
}
return (0, _react.cloneElement)(child, _extends({}, restProps, { style: style }));
}
}]);
return View;
}(_react.Component);
View.contextTypes = {
viewPager: _react.PropTypes.instanceOf(Pager)
};
var ViewPager = function (_Component4) {
_inherits(ViewPager, _Component4);
function ViewPager(props) {
_classCallCheck(this, ViewPager);
var _this4 = _possibleConstructorReturn(this, (ViewPager.__proto__ || Object.getPrototypeOf(ViewPager)).call(this, props));
_this4._onSwipeStart = function (e) {
var _getTouchEvent = getTouchEvent(e),
pageX = _getTouchEvent.pageX,
pageY = _getTouchEvent.pageY;
// we're now swiping
_this4._viewPager.isSwiping = true;
// store the initial starting coordinates
_this4._startTrack = _this4._currentTween;
_this4._startSwipe = {
x: pageX,
y: pageY
};
// determine if a flick or not
_this4._isFlick = true;
setTimeout(function () {
_this4._isFlick = false;
}, _this4.props.flickTimeout);
};
_this4._onSwipeMove = function (e) {
// bail if we aren't swiping
if (!_this4._viewPager.isSwiping) return;
var _this4$props = _this4.props,
swipeThreshold = _this4$props.swipeThreshold,
axis = _this4$props.axis;
var _getTouchEvent2 = getTouchEvent(e),
pageX = _getTouchEvent2.pageX,
pageY = _getTouchEvent2.pageY;
// determine how much we have moved
_this4._swipeDiff = {
x: _this4._startSwipe.x - pageX,
y: _this4._startSwipe.y - pageY
};
if (_this4._isSwipe(swipeThreshold)) {
e.preventDefault();
e.stopPropagation();
var swipeDiff = _this4._swipeDiff[axis];
var trackPosition = _this4._startTrack - swipeDiff;
_this4._viewPager.setPositionValue(trackPosition);
_this4.setState({
instant: true
});
}
};
_this4._onSwipeEnd = function () {
var _this4$props2 = _this4.props,
swipeThreshold = _this4$props2.swipeThreshold,
viewsToMove = _this4$props2.viewsToMove,
axis = _this4$props2.axis,
infinite = _this4$props2.infinite;
var _this4$_viewPager = _this4._viewPager,
frame = _this4$_viewPager.frame,
currentView = _this4$_viewPager.currentView,
trackPosition = _this4$_viewPager.trackPosition;
var threshold = _this4._isFlick ? swipeThreshold : currentView.getSize() * viewsToMove * swipeThreshold;
_this4._viewPager.isSwiping = false;
_this4.setState({
instant: true
}, function () {
if (_this4._isSwipe(threshold)) {
_this4._swipeDiff[axis] < 0 ? _this4.prev() : _this4.next();
} else {
_this4._viewPager.setPositionValue();
}
_this4.setState({ instant: false });
});
};
_this4._onSwipePast = function () {
// perform a swipe end if we swiped past the component
if (_this4._viewPager.isSwiping) {
_this4._onSwipeEnd();
}
};
_this4._handleOnRest = function () {
var _this4$props3 = _this4.props,
infinite = _this4$props3.infinite,
children = _this4$props3.children;
if (infinite && !_this4.state.instant) {
// reset back to a normal index
_this4._viewPager.resetViews();
// set instant flag so we can prime track for next move
_this4.setState({ instant: true }, function () {
_this4.setState({ instant: false });
});
}
};
_this4._viewPager = new Pager(props);
_this4._currentTween = 0;
// swiping
_this4._startSwipe = {};
_this4._swipeDiff = {};
_this4._isFlick = false;
_this4.state = {
currentView: (0, _getIndex2.default)(props.currentView, props.children),
frameSize: {},
instant: false,
isMounted: false
};
return _this4;
}
_createClass(ViewPager, [{
key: 'getChildContext',
value: function getChildContext() {
return {
viewPager: this._viewPager
};
}
}, {
key: 'componentDidMount',
value: function componentDidMount() {
var _this5 = this;
// we need to mount the frame and track before we can gather the proper info
// for views, we use this flag to determine when we can mount the views
this.setState({ isMounted: true }, function () {
_this5._setFrameAutoSize();
_this5._viewPager.setPositionValue();
// now the pager is ready, animate to whatever value instantly
_this5.setState({ instant: true }, function () {
_this5.props.onReady();
_this5.setState({ instant: false });
});
});
}
}, {
key: 'componentWillReceiveProps',
value: function componentWillReceiveProps(_ref3) {
var currentView = _ref3.currentView,
children = _ref3.children,
instant = _ref3.instant;
// update state with new index if necessary
if ((typeof currentView === 'undefined' ? 'undefined' : _typeof(currentView)) !== undefined && this.props.currentView !== currentView) {
var newIndex = (0, _getIndex2.default)(currentView, children);
// set the new view index
this._viewPager.setCurrentView(0, newIndex);
// store it so we can compare it later
this.setState({
currentView: newIndex
});
}
// update instant state from props
if (this.props.instant !== instant) {
this.setState({
instant: instant
});
}
}
}, {
key: 'componentDidUpdate',
value: function componentDidUpdate(lastProps, lastState) {
if (this.state.currentView !== lastState.currentView) {
// update frame size to match new view size
this._setFrameAutoSize();
}
}
}, {
key: '_setFrameAutoSize',
value: function _setFrameAutoSize() {
if (!this.props.autoSize) return;
this.setState({
frameSize: this._viewPager.getFrameAutoSize()
});
}
}, {
key: 'prev',
value: function prev() {
this._viewPager.prev();
this.setState({
currentView: this._viewPager.currentIndex
});
}
}, {
key: 'next',
value: function next() {
this._viewPager.next();
this.setState({
currentView: this._viewPager.currentIndex
});
}
}, {
key: '_isSwipe',
value: function _isSwipe(threshold) {
var _swipeDiff = this._swipeDiff,
x = _swipeDiff.x,
y = _swipeDiff.y;
return this.props.axis === 'x' ? Math.abs(x) > Math.max(threshold, Math.abs(y)) : Math.abs(x) < Math.max(threshold, Math.abs(y));
}
}, {
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: '_getMotionStyle',
value: function _getMotionStyle() {
var val = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
var springConfig = this.props.springConfig;
var instant = this.state.instant;
return this._viewPager.isSwiping || instant ? val : (0, _reactMotion.spring)(val, springConfig);
}
}, {
key: '_getFrameStyle',
value: function _getFrameStyle() {
var frameSize = this.state.frameSize;
return {
width: this._getMotionStyle(frameSize.width),
height: this._getMotionStyle(frameSize.height)
};
}
}, {
key: '_getTrackStyle',
value: function _getTrackStyle() {
return {
trackPosition: this._getMotionStyle(this._viewPager.trackPosition)
};
}
}, {
key: '_renderViews',
value: function _renderViews() {
return this.state.isMounted && _react.Children.map(this.props.children, function (child, index) {
return _react2.default.createElement(View, { children: child });
});
}
}, {
key: 'render',
value: function render() {
var _this6 = this;
return _react2.default.createElement(
_reactMotion.Motion,
{ style: this._getFrameStyle() },
function (frameStyles) {
return _react2.default.createElement(
Frame,
_extends({
className: 'frame',
style: {
width: frameStyles.width ? frameStyles.width : null,
height: frameStyles.height ? frameStyles.height : null
}
}, _this6._getSwipeEvents()),
_react2.default.createElement(
_reactMotion.Motion,
{
style: _this6._getTrackStyle(),
onRest: _this6._handleOnRest
},
function (_ref4) {
var trackPosition = _ref4.trackPosition;
_this6._currentTween = trackPosition;
if (!_this6.state.instant) {
_this6._startTrack = _this6._currentTween;
}
return _react2.default.createElement(
Track,
{
position: trackPosition,
className: 'track'
},
_this6._renderViews()
);
}
)
);
}
);
}
}]);
return ViewPager;
}(_react.Component);
ViewPager.propTypes = {
currentView: _react.PropTypes.any,
viewsToShow: _react.PropTypes.any,
viewsToMove: _react.PropTypes.number,
align: _react.PropTypes.number,
contain: _react.PropTypes.bool,
axis: _react.PropTypes.oneOf(['x', 'y']),
autoSize: _react.PropTypes.bool,
infinite: _react.PropTypes.bool,
instant: _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,
// rightToLeft: PropTypes.bool,
// lazyLoad: PropTypes.bool, // lazyily load components as they enter
springConfig: _react2.default.PropTypes.objectOf(_react2.default.PropTypes.number),
onReady: _react.PropTypes.func
};
ViewPager.defaultProps = {
currentView: 0,
viewsToShow: 'auto',
viewsToMove: 1,
align: 0,
contain: false,
axis: 'x',
autoSize: false,
infinite: false,
instant: false,
swipe: true,
swipeThreshold: 0.5,
flickTimeout: 300,
rightToLeft: false,
lazyLoad: false,
springConfig: _reactMotion.presets.noWobble,
onReady: function onReady() {
return null;
},
onChange: function onChange() {
return null;
},
beforeAnimation: function beforeAnimation() {
return null;
},
afterAnimation: function afterAnimation() {
return null;
}
};
ViewPager.childContextTypes = {
viewPager: _react.PropTypes.instanceOf(Pager)
};
exports.default = ViewPager;
module.exports = exports['default'];