UNPKG

react-magic-slider-dots

Version:

React Magic Slider Dots Component for React Slick Carousel. Inspired by Instagram.

330 lines (285 loc) 13.5 kB
'use strict'; function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } var React = require('react'); var React__default = _interopDefault(React); var PropTypes = _interopDefault(require('prop-types')); var classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; 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 inherits = function (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 possibleConstructorReturn = function (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; }; var toConsumableArray = function (arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) arr2[i] = arr[i]; return arr2; } else { return Array.from(arr); } }; var MagicSliderDots = function (_Component) { inherits(MagicSliderDots, _Component); function MagicSliderDots(props) { classCallCheck(this, MagicSliderDots); // init var _this = possibleConstructorReturn(this, (MagicSliderDots.__proto__ || Object.getPrototypeOf(MagicSliderDots)).call(this, props)); _this.previousActiveIndex = 0; _this.hasAnimated = false; _this.minIndex = 0; _this.maxIndex = 0; _this.breakPointActiveIndex = 0; _this.addActiveClassToLastDot = false; return _this; } //handle react-slick breakpoints createClass(MagicSliderDots, [{ key: 'componentDidUpdate', value: function componentDidUpdate(prevProps) { var prevDots = prevProps.dots; var _props = this.props, currentDots = _props.dots, activeDotClassName = _props.activeDotClassName, numDotsToShow = _props.numDotsToShow; //moving from more dots to less dots if (prevDots && currentDots && prevDots.length > currentDots.length) { //edge case - last dot was active if (prevDots[prevDots.length - 1].props.className === activeDotClassName) { this.breakPointActiveIndex = currentDots.length - 1; this.previousActiveIndex = this.breakPointActiveIndex - 1; this.addActiveClassToLastDot = true; } //edge case - last active index is at end of current dots or exceeds current dot length var lastActiveDot = prevDots.find(function (dot) { return dot.props.className === activeDotClassName; }); var lastActiveIndex = parseInt(lastActiveDot.key); if (lastActiveIndex > currentDots.length - 1) { this.breakPointActiveIndex = currentDots.length - 1; this.previousActiveIndex = this.breakPointActiveIndex - 1; this.addActiveClassToLastDot = true; } //adjust minIndex and maxIndex if necessary if (this.minIndex < 0) { this.minIndex = 0; this.maxIndex = numDotsToShow - 1; } if (this.maxIndex > currentDots.length - 1) { this.maxIndex = currentDots.length - 1; this.minIndex = this.maxIndex - numDotsToShow + 1; } this.forceUpdate(); } else if (prevDots && currentDots && prevDots.length < currentDots.length) { //edge case - adjust minIndex and maxIndex if active dot will be out of the View determined by min/max index var currentActiveDot = currentDots.find(function (dot) { return dot.props.className === activeDotClassName; }); var currentActiveIndex = parseInt(currentActiveDot.key); if (currentActiveIndex >= this.maxIndex) { this.maxIndex = currentActiveIndex + 1; this.minIndex = this.maxIndex - numDotsToShow + 1; } //adjust minIndex and maxIndex if necessary if (this.maxIndex > currentDots.length - 1) { this.maxIndex = currentDots.length - 1; this.minIndex = this.maxIndex - numDotsToShow + 1; } this.forceUpdate(); } } }, { key: 'render', value: function render() { var _props2 = this.props, dots = _props2.dots, numDotsToShow = _props2.numDotsToShow, dotWidth = _props2.dotWidth, dotContainerClassName = _props2.dotContainerClassName, activeDotClassName = _props2.activeDotClassName, prevNextDotClassName = _props2.prevNextDotClassName; var active = dots.find(function (dot) { return dot.props.className === activeDotClassName; }); var adjustedDots = [].concat(toConsumableArray(dots)); //if no current activeIndex, then due to react-slick breakpoint - use generated breakPointActiveIndex var activeIndex = active ? parseInt(active.key) : this.breakPointActiveIndex; //consider '>=' as moving forward to support react-slick breakpoint use case var isMovingForward = activeIndex >= this.previousActiveIndex; // need to subtract 2 from numDotsToShow since array index are zero-based if (activeIndex > numDotsToShow - 2 && adjustedDots.length > numDotsToShow || this.hasAnimated) { if (isMovingForward) { if (activeIndex === this.maxIndex && activeIndex !== dots.length - 1) { // list will move left this.minIndex = activeIndex - (numDotsToShow - 2); this.maxIndex = activeIndex + 1; } else { // special case - handle if initialSlide from react-slick has a value greater than 0 if (this.minIndex === 0 && this.maxIndex === 0) { if (activeIndex === dots.length - 1) { this.maxIndex = activeIndex; this.minIndex = this.maxIndex - (numDotsToShow - 1); } else { this.minIndex = activeIndex - 1 < 0 ? 0 : activeIndex - 1; this.maxIndex = this.minIndex + (numDotsToShow - 1) > dots.length - 1 ? dots.length - 1 : this.minIndex + (numDotsToShow - 1); } } else { if (activeIndex === dots.length - 1) { // moving carousel backward from 0 to max index this.maxIndex = dots.length - 1; this.minIndex = dots.length - numDotsToShow; } } } } else { // movingBackwards if (activeIndex === this.minIndex && activeIndex !== 0) { // list will move right this.minIndex = activeIndex - 1; this.maxIndex = this.minIndex + (numDotsToShow - 1); } else { if (activeIndex === 0) { // moving carousel forward from max index to 0 this.maxIndex = numDotsToShow - 1; this.minIndex = 0; } } } this.hasAnimated = true; var firstViewableDotIndex = this.minIndex; var firstViewableDot = adjustedDots[firstViewableDotIndex]; var lastViewableDotIndex = this.maxIndex; var lastViewableDot = adjustedDots[lastViewableDotIndex]; //outside of bounds check - can be caused when using react-slick breakpoints //return null and dots will correctly re-render once componentDidUpdate lifecycle recalculates indexes if (!firstViewableDot || !lastViewableDot) { console.log('rendering null - outside of bounds', firstViewableDot, lastViewableDot); return null; } if (lastViewableDotIndex < adjustedDots.length - 1 && isMovingForward) { // moving foward - but not on the last dot adjustedDots = [].concat(toConsumableArray(adjustedDots.slice(0, firstViewableDotIndex)), [React__default.cloneElement(firstViewableDot, { className: prevNextDotClassName })], toConsumableArray(adjustedDots.slice(firstViewableDotIndex + 1, lastViewableDotIndex)), [React__default.cloneElement(lastViewableDot, { className: prevNextDotClassName })], toConsumableArray(adjustedDots.slice(lastViewableDotIndex + 1))); } else if (lastViewableDotIndex === adjustedDots.length - 1) { // moving foward or backward - last dot visible - should appear not small adjustedDots = [].concat(toConsumableArray(adjustedDots.slice(0, firstViewableDotIndex)), [React__default.cloneElement(firstViewableDot, { className: prevNextDotClassName })], toConsumableArray(adjustedDots.slice(firstViewableDotIndex + 1, lastViewableDotIndex)), [this.addActiveClassToLastDot || activeIndex === lastViewableDotIndex ? React__default.cloneElement(lastViewableDot, { className: this.props.activeDotClassName }) : lastViewableDot]); } else if (activeIndex > 1 && !isMovingForward) { // moving backwards the left adjustedDots = [].concat(toConsumableArray(adjustedDots.slice(0, firstViewableDotIndex)), [React__default.cloneElement(firstViewableDot, { className: prevNextDotClassName })], toConsumableArray(adjustedDots.slice(firstViewableDotIndex + 1, lastViewableDotIndex)), [React__default.cloneElement(lastViewableDot, { className: prevNextDotClassName })], toConsumableArray(adjustedDots.slice(lastViewableDotIndex + 1))); } else { this.hasAnimated = false; // moving backwards on first dot - should appear not small // eq: (activeIndex === 1 || activeIndex === 0) && !isMovingForward adjustedDots = [].concat(toConsumableArray(adjustedDots.slice(0, lastViewableDotIndex)), [React__default.cloneElement(lastViewableDot, { className: prevNextDotClassName })], toConsumableArray(adjustedDots.slice(lastViewableDotIndex + 1))); } } // no leftOffset in place, just render the dots else { var _lastViewableDotIndex = Math.min(numDotsToShow, dots.length) - 1; this.minIndex = 0; this.maxIndex = _lastViewableDotIndex; if (_lastViewableDotIndex < adjustedDots.length - 1) { var _lastViewableDot = adjustedDots[_lastViewableDotIndex]; adjustedDots = [].concat(toConsumableArray(adjustedDots.slice(0, _lastViewableDotIndex)), [React__default.cloneElement(_lastViewableDot, { className: prevNextDotClassName })], toConsumableArray(adjustedDots.slice(_lastViewableDotIndex + 1))); } } // track active index this.previousActiveIndex = activeIndex; this.addActiveClassToLastDot = false; // calculate container width var containerWidth = dots.length < numDotsToShow ? dots.length * dotWidth : numDotsToShow * dotWidth; var midIndex = (this.minIndex + this.maxIndex) / 2; // only give leftOffset if number of dots exceeds number of dots to show at one time var leftOffset = dots.length < numDotsToShow ? 0 : (dotWidth * numDotsToShow - dotWidth) / 2 - midIndex * dotWidth; return React__default.createElement( 'div', { className: dotContainerClassName, style: { position: 'relative', overflow: 'hidden', margin: 'auto', width: containerWidth + 'px' } }, React__default.createElement( 'ul', { style: { transform: 'translateX(' + leftOffset + 'px)' } }, ' ', adjustedDots, ' ' ) ); } }]); return MagicSliderDots; }(React.Component); MagicSliderDots.propTypes = { /** array of HTML li elements representing the slider dot. */ dots: PropTypes.array.isRequired, /** number of slider dots to show. */ numDotsToShow: PropTypes.number.isRequired, /** width, in pixels, of a slider dot including any margins/padding. */ dotWidth: PropTypes.number.isRequired, /** class name of parent div. */ dotContainerClassName: PropTypes.string, /** class name of active slider dot. */ activeDotClassName: PropTypes.string, /** class name of left-most (prev) and right-most (next) slider dot. */ prevNextDotClassName: PropTypes.string }; MagicSliderDots.defaultProps = { dotContainerClassName: 'magic-dots slick-dots', activeDotClassName: 'slick-active', prevNextDotClassName: 'small' }; module.exports = MagicSliderDots; //# sourceMappingURL=index.js.map