react-magic-slider-dots
Version:
React Magic Slider Dots Component for React Slick Carousel. Inspired by Instagram.
325 lines (282 loc) • 13.2 kB
JavaScript
import React, { Component } from 'react';
import PropTypes from '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.cloneElement(firstViewableDot, {
className: prevNextDotClassName
})], toConsumableArray(adjustedDots.slice(firstViewableDotIndex + 1, lastViewableDotIndex)), [React.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.cloneElement(firstViewableDot, {
className: prevNextDotClassName
})], toConsumableArray(adjustedDots.slice(firstViewableDotIndex + 1, lastViewableDotIndex)), [this.addActiveClassToLastDot || activeIndex === lastViewableDotIndex ? React.cloneElement(lastViewableDot, {
className: this.props.activeDotClassName
}) : lastViewableDot]);
} else if (activeIndex > 1 && !isMovingForward) {
// moving backwards the left
adjustedDots = [].concat(toConsumableArray(adjustedDots.slice(0, firstViewableDotIndex)), [React.cloneElement(firstViewableDot, {
className: prevNextDotClassName
})], toConsumableArray(adjustedDots.slice(firstViewableDotIndex + 1, lastViewableDotIndex)), [React.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.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.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.createElement(
'div',
{
className: dotContainerClassName,
style: {
position: 'relative',
overflow: 'hidden',
margin: 'auto',
width: containerWidth + 'px'
}
},
React.createElement(
'ul',
{ style: { transform: 'translateX(' + leftOffset + 'px)' } },
' ',
adjustedDots,
' '
)
);
}
}]);
return MagicSliderDots;
}(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'
};
export default MagicSliderDots;
//# sourceMappingURL=index.es.js.map