react-view-pager
Version:
View-Pager/Slider/Carousel powered by React Motion.
619 lines (507 loc) • 20 kB
JavaScript
'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 _mitt = require('mitt');
var _mitt2 = _interopRequireDefault(_mitt);
var _tabbable = require('tabbable');
var _tabbable2 = _interopRequireDefault(_tabbable);
var _animationBus = require('animation-bus');
var _animationBus2 = _interopRequireDefault(_animationBus);
var _resizeObserverPolyfill = require('resize-observer-polyfill');
var _resizeObserverPolyfill2 = _interopRequireDefault(_resizeObserverPolyfill);
var _PagerElement3 = require('./PagerElement');
var _PagerElement4 = _interopRequireDefault(_PagerElement3);
var _utils = require('./utils');
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 _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; }
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 TRANSFORM = require('get-prefix')('transform');
var isWindowDefined = typeof window !== 'undefined';
var Track = function (_PagerElement) {
_inherits(Track, _PagerElement);
function Track() {
_classCallCheck(this, Track);
return _possibleConstructorReturn(this, (Track.__proto__ || Object.getPrototypeOf(Track)).apply(this, arguments));
}
_createClass(Track, [{
key: 'getStyles',
value: function getStyles(trackPosition) {
var _pager$getPositionVal = this.pager.getPositionValue(trackPosition),
x = _pager$getPositionVal.x,
y = _pager$getPositionVal.y;
var trackSize = this.pager.getTrackSize();
var style = _defineProperty({}, TRANSFORM, 'translate3d(' + x + 'px, ' + y + 'px, 0)');
if (trackSize) {
var _pager$options = this.pager.options,
axis = _pager$options.axis,
viewsToShow = _pager$options.viewsToShow;
var dimension = axis === 'x' ? 'width' : 'height';
style[dimension] = viewsToShow === 'auto' ? trackSize : this.pager.views.length / viewsToShow * 100 + '%';
}
return style;
}
}]);
return Track;
}(_PagerElement4.default);
var View = function (_PagerElement2) {
_inherits(View, _PagerElement2);
function View(_ref) {
var index = _ref.index,
restOptions = _objectWithoutProperties(_ref, ['index']);
_classCallCheck(this, View);
var _this2 = _possibleConstructorReturn(this, (View.__proto__ || Object.getPrototypeOf(View)).call(this, restOptions));
_this2.index = index;
_this2.inBounds = true;
_this2.tabbableChildren = [];
_this2.setCurrent(false);
_this2.setVisible(false);
_this2.setTarget();
_this2.setOrigin();
// TODO: look into getting rid of setTimeout
// not sure the reason for needing setTimeout in order to get proper children,
// might be due to something in React that we're not doing at the right time
setTimeout(function () {
_this2.tabbableChildren = (0, _tabbable2.default)(_this2.node);
_this2.setTabbableChildren();
});
return _this2;
}
_createClass(View, [{
key: 'setCurrent',
value: function setCurrent(isCurrent) {
this.isCurrent = isCurrent;
}
}, {
key: 'setVisible',
value: function setVisible(isVisible) {
this.isVisible = isVisible;
this.setTabbableChildren();
}
}, {
key: 'setTabbableChildren',
value: function setTabbableChildren() {
// only allow tabbing in current slides
for (var i = 0; i < this.tabbableChildren.length; i++) {
this.tabbableChildren[i].tabIndex = this.isCurrent ? 0 : -1;
}
}
}, {
key: 'setTarget',
value: function setTarget() {
var _pager$options2 = this.pager.options,
align = _pager$options2.align,
viewsToShow = _pager$options2.viewsToShow;
var target = this.pager.getStartCoords(this.index);
if (align) {
target += this.pager.getAlignOffset(this);
}
this.target = target;
}
}, {
key: 'setOrigin',
value: function setOrigin() {
var trackPosition = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.pager.trackPosition;
this.origin = this.target - trackPosition;
}
}, {
key: 'getStyles',
value: function getStyles() {
var _pager$options3 = this.pager.options,
axis = _pager$options3.axis,
viewsToShow = _pager$options3.viewsToShow,
infinite = _pager$options3.infinite;
var style = {};
// we need to position views inline when using the x axis
if (axis === 'x') {
style.display = 'inline-block';
style.verticalAlign = 'top';
}
// set width or height on view when viewsToShow is not auto
if (viewsToShow !== 'auto') {
style[axis === 'x' ? 'width' : 'height'] = 100 / this.pager.views.length + '%';
}
// make sure view stays in frame when using infinite option
if (infinite && !this.inBounds) {
style.position = 'relative';
style[axis === 'y' ? 'top' : 'left'] = this.getPosition();
}
// finally, apply any animations
return _extends({}, style, this.pager.animationBus.getStyles(this));
}
}]);
return View;
}(_PagerElement4.default);
var defaultOptions = {
viewsToShow: 1,
viewsToMove: 1,
align: 0,
contain: false,
axis: 'x',
autoSize: false,
animations: [],
infinite: false,
instant: false,
swipe: true,
swipeThreshold: 0.5,
flickTimeout: 300,
accessibility: true
};
var Pager = function () {
function Pager() {
var _this3 = this;
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
_classCallCheck(this, Pager);
this.hydrate = function () {
_this3.frame.setSize();
_this3.track.setSize();
_this3.views.forEach(function (view) {
view.setSize();
view.setTarget();
});
_this3.setPositionValue();
_this3.setViewStyles();
_this3.emit('hydrated');
};
var emitter = (0, _mitt2.default)();
this.on = emitter.on;
this.emit = emitter.emit;
this.off = emitter.off;
this.views = [];
this.currentIndex = 0;
this.currentView = null;
this.currentTween = 0;
this.trackPosition = 0;
this.isSwiping = false;
this.options = _extends({}, defaultOptions, options);
this.animationBus = new _animationBus2.default({
animations: this.options.animations,
origin: function origin(view) {
return view.origin;
}
});
if (isWindowDefined) {
this.resizeObserver = new _resizeObserverPolyfill2.default(function () {
_this3.hydrate();
});
}
}
_createClass(Pager, [{
key: 'setOptions',
value: function setOptions(options) {
var lastOptions = this.options;
// spread new options over the old ones
this.options = _extends({}, this.options, options);
// merge animations into animation bus
this.animationBus.animations = this.options.animations;
// fire a viewChange event with the new indicies if viewsToShow has changed
if (lastOptions.viewsToShow !== this.options.viewsToShow) {
this.emit('viewChange', this.getCurrentIndicies());
}
}
}, {
key: 'addFrame',
value: function addFrame(node) {
this.frame = new _PagerElement4.default({
node: node,
pager: this
});
}
}, {
key: 'addTrack',
value: function addTrack(node) {
this.track = new Track({
node: node,
pager: this
});
}
}, {
key: 'addView',
value: function addView(node) {
var index = this.views.length;
var view = new View({
node: node,
index: index,
pager: this
});
// add view to collection
this.views.push(view);
// set this as the first view if there isn't one yet
if (!this.currentView) {
this.setCurrentView({
index: index,
suppressEvent: true
});
}
// listen for width and height changes
if (isWindowDefined) {
this.resizeObserver.observe(node);
}
// fire an event
this.emit('viewAdded');
return view;
}
}, {
key: 'removeView',
value: function removeView(view) {
// filter out view
this.views = this.views.filter(function (_view) {
return view !== _view;
});
// stop observing node
if (isWindowDefined) {
this.resizeObserver.disconnect(view.node);
}
// fire an event
this.emit('viewRemoved');
}
}, {
key: 'prev',
value: function prev() {
this.setCurrentView({ direction: -1 });
}
}, {
key: 'next',
value: function next() {
this.setCurrentView({ direction: 1 });
}
}, {
key: 'setCurrentView',
value: function setCurrentView(_ref2) {
var _ref2$direction = _ref2.direction,
direction = _ref2$direction === undefined ? 0 : _ref2$direction,
_ref2$index = _ref2.index,
index = _ref2$index === undefined ? this.currentIndex : _ref2$index,
_ref2$suppressEvent = _ref2.suppressEvent,
suppressEvent = _ref2$suppressEvent === undefined ? false : _ref2$suppressEvent;
var _options = this.options,
viewsToMove = _options.viewsToMove,
infinite = _options.infinite;
var newIndex = index + direction * viewsToMove;
var previousIndex = this.currentIndex;
var currentIndex = infinite ? newIndex : (0, _utils.clamp)(newIndex, 0, this.views.length - 1);
var previousView = this.getView(previousIndex);
var currentView = this.getView(currentIndex);
// set current index and view
this.currentIndex = currentIndex;
this.currentView = currentView;
// swap current view flags
previousView.setCurrent(false);
currentView.setCurrent(true);
var rangeStart = currentIndex;
var rangeEnd = currentIndex + viewsToMove - 1;
var viewRange = (0, _utils.range)(rangeStart, rangeEnd, this.views.length);
// set flags for which views are currently showing
this.views.forEach(function (view, index) {
view.setVisible(index === currentIndex);
});
// set the track position to the new view
this.setPositionValue();
if (!suppressEvent) {
this.emit('viewChange', this.getCurrentIndicies());
}
}
}, {
key: 'setPositionValue',
value: function setPositionValue() {
var trackPosition = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.currentView ? this.currentView.target : 0;
if (!this.isSwiping) {
var _options2 = this.options,
viewsToShow = _options2.viewsToShow,
autoSize = _options2.autoSize,
infinite = _options2.infinite,
contain = _options2.contain;
var trackSize = this.getTrackSize();
if (infinite) {
// we offset by a track multiplier so infinite animation can take advantage of
// physics by animating to a large value, the final value provided in getTransformValue
// will return the proper wrapped value
trackPosition -= (Math.floor(this.currentIndex / this.views.length) || 0) * trackSize;
}
if (contain) {
var trackEndOffset = viewsToShow === 'auto' && autoSize || viewsToShow <= 1 ? 0 : this.getFrameSize({ autoSize: false });
trackPosition = (0, _utils.clamp)(trackPosition, trackEndOffset - trackSize, 0);
}
}
this.trackPosition = trackPosition;
}
}, {
key: 'setViewStyles',
value: function setViewStyles() {
var trackPosition = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
var _options3 = this.options,
infinite = _options3.infinite,
align = _options3.align;
var frameSize = this.getFrameSize();
var trackSize = this.getTrackSize();
var wrappedtrackPosition = (0, _utils.modulo)(trackPosition, -trackSize);
this.views.reduce(function (lastPosition, view, index) {
var viewSize = view.getSize();
var nextPosition = lastPosition + viewSize;
var position = lastPosition;
if (nextPosition + viewSize * align < Math.abs(wrappedtrackPosition)) {
// shift views around so they are always visible in frame
if (infinite) {
position += trackSize - lastPosition;
}
view.inBounds = false;
} else {
view.inBounds = true;
}
view.setPosition(position);
view.setOrigin(trackPosition);
return nextPosition;
}, 0);
}
}, {
key: 'getNumericViewsToShow',
value: function getNumericViewsToShow() {
return isNaN(this.options.viewsToShow) ? 1 : this.options.viewsToShow;
}
}, {
key: 'getMaxDimensions',
value: function getMaxDimensions(indices) {
var _this4 = this;
var axis = this.options.axis;
var widths = [];
var heights = [];
indices.forEach(function (index) {
var view = isNaN(index) ? index : _this4.getView(index);
widths.push(view.getSize('width'));
heights.push(view.getSize('height'));
});
return {
width: axis === 'x' ? (0, _utils.sum)(widths) : (0, _utils.max)(widths),
height: axis === 'y' ? (0, _utils.sum)(heights) : (0, _utils.max)(heights)
};
}
}, {
key: 'getCurrentIndicies',
value: function getCurrentIndicies() {
var _options4 = this.options,
infinite = _options4.infinite,
contain = _options4.contain;
var currentViews = [];
var viewsToShow = isNaN(this.options.viewsToShow) ? 1 : this.options.viewsToShow;
var minIndex = this.currentIndex;
var maxIndex = this.currentIndex + (viewsToShow - 1);
if (contain) {
// if containing, we need to clamp the start and end indexes so we only return what's in view
minIndex = (0, _utils.clamp)(minIndex, 0, this.views.length - viewsToShow);
maxIndex = (0, _utils.clamp)(maxIndex, 0, this.views.length - 1);
for (var i = minIndex; i <= maxIndex; i++) {
currentViews.push(i);
}
} else {
for (var _i = minIndex; _i <= maxIndex; _i++) {
currentViews.push(infinite ? (0, _utils.modulo)(_i, this.views.length) : (0, _utils.clamp)(_i, 0, this.views.length - 1));
}
}
return currentViews;
}
}, {
key: 'getFrameSize',
value: function getFrameSize() {
var _ref3 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
_ref3$autoSize = _ref3.autoSize,
autoSize = _ref3$autoSize === undefined ? this.options.autoSize : _ref3$autoSize,
_ref3$fullSize = _ref3.fullSize,
fullSize = _ref3$fullSize === undefined ? false : _ref3$fullSize;
var dimensions = {
width: 0,
height: 0
};
if (this.views.length) {
if (autoSize) {
var currentViews = this.getCurrentIndicies();
dimensions = this.getMaxDimensions(currentViews);
} else if (this.frame) {
dimensions = {
width: this.frame.getSize('width'),
height: this.frame.getSize('height')
};
}
}
if (fullSize) {
return dimensions;
} else {
return dimensions[this.options.axis === 'x' ? 'width' : 'height'];
}
}
}, {
key: 'getTrackSize',
value: function getTrackSize() {
var includeLastSlide = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
var lastIndex = includeLastSlide ? this.views.length : this.views.length - 1;
var size = 0;
this.views.slice(0, lastIndex).forEach(function (view) {
size += view.getSize();
});
return size;
}
}, {
key: 'getView',
value: function getView(index) {
return this.views[(0, _utils.modulo)(index, this.views.length)];
}
// 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;
}
// how much to offset the view defined by the align option
}, {
key: 'getAlignOffset',
value: function getAlignOffset(view) {
var frameSize = this.getFrameSize({ autoSize: false });
var viewSize = view.getSize();
return (frameSize - viewSize) * this.options.align;
}
}, {
key: 'getPositionValue',
value: function getPositionValue() {
var trackPosition = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.trackPosition;
var _options5 = this.options,
infinite = _options5.infinite,
contain = _options5.contain;
var position = { x: 0, y: 0
// store the current animated value so we can reference it later
};this.currentTween = trackPosition;
// wrap the track position if this is an infinite track
if (infinite) {
var trackSize = this.getTrackSize();
trackPosition = (0, _utils.modulo)(trackPosition, -trackSize) || 0;
}
// emit a "scroll" event so we can do things based on the progress of the track
this.emit('scroll', {
progress: trackPosition / this.getTrackSize(false),
position: trackPosition
});
// set the proper transform axis based on our options
position[this.options.axis] = trackPosition;
return position;
}
}, {
key: 'resetViewIndex',
value: function resetViewIndex() {
// reset back to a normal index
this.setCurrentView({
index: (0, _utils.modulo)(this.currentIndex, this.views.length),
suppressEvent: true
});
}
}]);
return Pager;
}();
exports.default = Pager;
module.exports = exports['default'];