@jdcfe/yep-react
Version:
一套移动端的React组件库
682 lines (556 loc) • 21.5 kB
JavaScript
import _extends from "@babel/runtime/helpers/extends";
import _defineProperty from "@babel/runtime/helpers/defineProperty";
import _classCallCheck from "@babel/runtime/helpers/classCallCheck";
import _createClass from "@babel/runtime/helpers/createClass";
import _inherits from "@babel/runtime/helpers/inherits";
import _possibleConstructorReturn from "@babel/runtime/helpers/possibleConstructorReturn";
import _getPrototypeOf from "@babel/runtime/helpers/getPrototypeOf";
function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }
function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } }
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import classNames from 'classnames';
import klass from './cssClasses';
import Gesture from '@jdcfe/yep-gesture';
import noop from '../_utils/noop';
var defaultStatusFormatter = function defaultStatusFormatter(current, total) {
return "".concat(current, " / ").concat(total);
};
var CSSTranslate = function CSSTranslate(position, vertical) {
var positionCss = !vertical ? [position, 0, 0] : [0, position, 0];
var transitionProp = 'translate3d';
var translatedPosition = '(' + positionCss.join(',') + ')';
return transitionProp + translatedPosition;
};
var Carousel = /*#__PURE__*/function (_React$Component) {
_inherits(Carousel, _React$Component);
var _super = _createSuper(Carousel);
function Carousel(props) {
var _this;
_classCallCheck(this, Carousel);
_this = _super.call(this, props);
_this.setCarouselWrapperRef = function (node) {
_this.carouselWrapperRef = node;
};
_this.setListRef = function (node) {
_this.listRef = node;
};
_this.setItemsWrapperRef = function (node) {
_this.itemsWrapperRef = node;
};
_this.setItemsRef = function (node, index) {
if (!_this.itemsRef) {
_this.itemsRef = [];
}
_this.itemsRef[index] = node;
};
_this.autoPlay = function () {
if (!_this.state.autoPlay || React.Children.count(_this.props.children) <= 1) {
return;
}
clearTimeout(_this.timer);
_this.timer = setTimeout(function () {
_this.increment(); //@ts-ignore
}, _this.props.autoPlay);
};
_this.clearAutoPlay = function () {
if (!_this.state.autoPlay) {
return;
}
clearTimeout(_this.timer);
};
_this.resetAutoPlay = function () {
_this.clearAutoPlay();
_this.autoPlay();
};
_this.updateSizes = function () {
if (!_this.state.initialized) {
return;
}
var isHorizontal = !_this.props.vertical;
var firstItem = _this.itemsRef[0];
var itemSize = isHorizontal ? firstItem.clientWidth : firstItem.clientHeight;
_this.setState(function (_state) {
return {
itemSize: itemSize
};
});
};
_this.setMountState = function () {
_this.setState({
hasMount: true
});
_this.updateSizes();
};
_this.handleClickItem = function (index, item) {
if (React.Children.count(_this.props.children) === 0) {
return;
}
if (_this.state.cancelClick) {
_this.setState({
cancelClick: false
});
return;
}
_this.props.onClickItem && _this.props.onClickItem(index, item);
if (index !== _this.state.selectedItem) {
_this.setState({
selectedItem: index
});
}
};
_this.handleOnChange = function (index, item) {
if (React.Children.count(_this.props.children) <= 1) {
return;
}
_this.props.onTransitionEnd && _this.props.onTransitionEnd(index, item);
_this.props.onChange && _this.props.onChange(index, item);
};
_this.onSwipeStart = function (event) {
_this.setState({
swiping: true
});
_this.props.onSwipeStart && _this.props.onSwipeStart(event);
_this.clearAutoPlay();
};
_this.onSwipeEnd = function (event) {
_this.setState({
swiping: false,
cancelClick: false
});
_this.props.onSwipeEnd && _this.props.onSwipeEnd(event);
_this.autoPlay();
};
_this.onSwipeMove = function (delta, event) {
_this.props.onSwipeMove && _this.props.onSwipeMove(event);
var isHorizontal = _this.props.vertical === false;
var childrenLength = React.Children.count(_this.props.children);
var initialBoundry = 0;
var currentPosition = _this.getPosition(_this.state.selectedItem);
var finalBoundry = _this.props.isInfinite ? _this.getPosition(childrenLength - 1) - 100 : _this.getPosition(childrenLength - 1);
var axisDelta = isHorizontal ? delta.x : delta.y;
var handledDelta = axisDelta; // prevent user from swiping left out of boundaries
if (currentPosition === initialBoundry && axisDelta > 0) {
handledDelta = 0;
} // prevent user from swiping right out of boundaries
if (currentPosition === finalBoundry && axisDelta < 0) {
handledDelta = 0;
}
var position = currentPosition + 100 / (_this.state.itemSize / handledDelta);
if (_this.props.isInfinite) {
// When allowing infinite loop, if we slide left from position 0 we reveal the cloned last slide that appears before it
// if we slide even further we need to jump to other side so it can continue - and vice versa for the last slide
if (_this.state.selectedItem === 0 && position > -100) {
position -= childrenLength * 100;
} else if (_this.state.selectedItem === childrenLength - 1 && position < -childrenLength * 100) {
position += childrenLength * 100;
}
} //@ts-ignore
position += '%'; //@ts-ignore
_this.setPosition(position); // allows scroll if the swipe was within the tolerance
var hasMoved = Math.abs(axisDelta) > _this.props.distance;
if (hasMoved && !_this.state.cancelClick) {
_this.setState({
cancelClick: true
});
}
return hasMoved;
};
_this.getPosition = function (index) {
var centerSlidePercentage = _this.props.centerSlidePercentage;
if (_this.props.isInfinite) {
// index has to be added by 1 because of the first cloned slide
++index;
}
if (index === 0) {
return 0;
}
var childrenLength = React.Children.count(_this.props.children);
if (_this.props.centerMode && _this.props.vertical === false) {
var currentPosition = -index * centerSlidePercentage;
var lastPosition = childrenLength - 1;
if (index && (index !== lastPosition || _this.props.isInfinite)) {
currentPosition += (100 - centerSlidePercentage) / 2;
} else if (index === lastPosition) {
currentPosition += 100 - centerSlidePercentage;
}
return currentPosition;
}
return -index * 100;
};
_this.setPosition = function (position, forceReflow) {
var list = ReactDOM.findDOMNode(_this.listRef);
['WebkitTransform', 'transform'].forEach(function (prop) {
//@ts-ignore
list.style[prop] = CSSTranslate(position, _this.props.vertical);
});
if (forceReflow) {
//@ts-ignore
list.offsetLeft;
}
};
_this.resetPosition = function () {
var currentPosition = _this.getPosition(_this.state.selectedItem) + '%'; //@ts-ignore
_this.setPosition(currentPosition);
};
_this.decrement = function () {
var positions = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
var fromSwipe = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
_this.moveTo(_this.state.selectedItem - (typeof positions === 'number' ? positions : 1), fromSwipe);
};
_this.increment = function () {
var positions = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
var fromSwipe = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
_this.moveTo(_this.state.selectedItem + (typeof positions === 'number' ? positions : 1), fromSwipe);
};
_this.moveTo = function (position, fromSwipe) {
var centerSlidePercentage = _this.props.centerSlidePercentage;
var lastPosition = React.Children.count(_this.props.children) - 1;
var needClonedSlide = _this.props.isInfinite && !fromSwipe && (position < 0 || position > lastPosition);
var oldPosition = position;
if (position < 0) {
position = _this.props.isInfinite ? lastPosition : 0;
}
if (position > lastPosition) {
position = _this.props.isInfinite ? 0 : lastPosition;
}
if (needClonedSlide) {
// set swiping true would disable transition time, then we set slider to cloned position and force a reflow
// this is only needed for non-swiping situation
_this.setState({
swiping: true
}, function () {
if (oldPosition < 0) {
if (_this.props.centerMode && !_this.props.vertical) {
_this.setPosition("-".concat((lastPosition + 2) * centerSlidePercentage - (100 - centerSlidePercentage) / 2, "%"), true);
} else {
_this.setPosition("-".concat((lastPosition + 2) * 100, "%"), true);
}
} else if (oldPosition > lastPosition) {
_this.setPosition(0, true);
} //@ts-ignore
_this.selectItem({
selectedItem: position,
swiping: false
});
});
} else {
//@ts-ignore
_this.selectItem({
// if it's not a slider, we don't need to set position here
selectedItem: position
});
} // don't reset auto play when stop on hover is enabled, doing so will trigger a call to auto play more than once
// and will result in the interval function not being cleared correctly.
if (_this.state.autoPlay && _this.state.isMouseEntered === false) {
_this.resetAutoPlay();
}
};
_this.onSwipeForward = function () {
_this.increment(1, true);
};
_this.onSwipeBackwards = function () {
_this.decrement(1, true);
};
_this.changeItem = function (e) {
if (!e.key || e.key === 'Enter') {
var newIndex = e.target.value; //@ts-ignore
_this.selectItem({
selectedItem: newIndex
});
}
};
_this.selectItem = function (state, cb) {
_this.setState(state, cb);
_this.handleOnChange(state.selectedItem, React.Children.toArray(_this.props.children)[state.selectedItem]);
};
_this.getInitialImage = function () {
var selectedItem = _this.props.initPage;
var item = _this.itemsRef && _this.itemsRef[selectedItem];
var images = item && item.getElementsByTagName('img');
return images && images[selectedItem];
};
_this.getVariableImageHeight = function (position) {
var item = _this.itemsRef && _this.itemsRef[position];
var images = item && item.getElementsByTagName('img');
if (_this.state.hasMount && images.length > 0) {
var image = images[0];
if (!image.complete) {
// if the image is still loading, the size won't be available so we trigger a new render after it's done
var onImageLoad = function onImageLoad() {
_this.forceUpdate();
image.removeEventListener('load', onImageLoad);
};
image.addEventListener('load', onImageLoad);
}
var height = image.clientHeight;
return height > 0 ? height : null;
}
return null;
};
_this.state = {
initialized: false,
selectedItem: props.initPage,
hasMount: false,
isMouseEntered: false,
autoPlay: props.autoPlay
};
return _this;
}
_createClass(Carousel, [{
key: "componentDidMount",
value: function componentDidMount() {
if (!this.props.children) {
return;
}
this.setupCarousel();
}
}, {
key: "componentDidUpdate",
value: function componentDidUpdate(prevProps, prevState) {
if (!prevProps.children && this.props.children && !this.state.initialized) {
this.setupCarousel();
}
if (prevState.swiping && !this.state.swiping) {
// We stopped swiping, ensure we are heading to the new/current slide and not stuck
this.resetPosition();
}
if (prevProps.initPage !== this.props.initPage || prevProps.centerMode !== this.props.centerMode) {
this.updateSizes();
this.moveTo(this.props.initPage);
}
if (prevProps.autoPlay !== this.props.autoPlay) {
if (this.props.autoPlay) {
this.setupAutoPlay();
} else {
this.destroyAutoPlay();
}
this.setState({
autoPlay: this.props.autoPlay
});
}
}
}, {
key: "componentWillUnmount",
value: function componentWillUnmount() {
this.destroyCarousel();
}
}, {
key: "setupCarousel",
value: function setupCarousel() {
this.bindEvents();
if (this.state.autoPlay && React.Children.count(this.props.children) > 1) {
this.setupAutoPlay();
}
this.setState({
initialized: true
});
var initialImage = this.getInitialImage();
if (initialImage) {
// if it's a carousel of images, we set the mount state after the first image is loaded
initialImage.addEventListener('load', this.setMountState);
} else {
this.setMountState();
}
}
}, {
key: "destroyCarousel",
value: function destroyCarousel() {
if (this.state.initialized) {
this.unbindEvents();
this.destroyAutoPlay();
}
}
}, {
key: "setupAutoPlay",
value: function setupAutoPlay() {
this.autoPlay();
}
}, {
key: "destroyAutoPlay",
value: function destroyAutoPlay() {
this.clearAutoPlay();
}
}, {
key: "bindEvents",
value: function bindEvents() {
// as the widths are calculated, we need to resize
// the carousel when the window is resized
window.addEventListener('resize', this.updateSizes); // issue #2 - image loading smaller
window.addEventListener('DOMContentLoaded', this.updateSizes);
}
}, {
key: "unbindEvents",
value: function unbindEvents() {
// removing listeners
window.removeEventListener('resize', this.updateSizes);
window.removeEventListener('DOMContentLoaded', this.updateSizes);
var initialImage = this.getInitialImage();
if (initialImage) {
initialImage.removeEventListener('load', this.setMountState);
}
}
}, {
key: "renderItems",
value: function renderItems(isClone) {
var _this2 = this;
return React.Children.map(this.props.children, function (item, index) {
var slideProps = {
ref: function ref(e) {
return _this2.setItemsRef(e, index);
},
key: 'itemKey' + index + (isClone ? 'clone' : ''),
className: klass.ITEM(true, index === _this2.state.selectedItem),
onClick: _this2.handleClickItem.bind(_this2, index, item)
};
if (_this2.props.centerMode && !_this2.props.vertical) {
//@ts-ignore
slideProps.style = {
minWidth: _this2.props.centerSlidePercentage + '%'
};
}
return /*#__PURE__*/React.createElement("li", slideProps, _this2.props.renderItem(item, {
isSelected: index === _this2.state.selectedItem
}));
});
}
}, {
key: "renderControls",
value: function renderControls() {
var _this3 = this;
var _this$props = this.props,
dots = _this$props.dots,
dotsClass = _this$props.dotsClass;
if (!dots) {
return null;
}
return /*#__PURE__*/React.createElement("ul", {
className: classNames('Yep-control-dots', _defineProperty({}, "".concat(dotsClass), !!dotsClass))
}, React.Children.map(this.props.children, function (item, index) {
return _this3.props.renderIndicator(_this3.changeItem, index === _this3.state.selectedItem, index, item);
}));
}
}, {
key: "renderStatus",
value: function renderStatus() {
if (!this.props.showPager) {
return null;
}
return /*#__PURE__*/React.createElement("p", {
className: "".concat(this.props.prefixCls, "-status")
}, this.props.renderPage(this.state.selectedItem + 1, React.Children.count(this.props.children)));
}
}, {
key: "render",
value: function render() {
if (!this.props.children || React.Children.count(this.props.children) === 0) {
return null;
}
var isHorizontal = !this.props.vertical; // obj to hold the transformations and styles
var itemListStyles = {};
var currentPosition = this.getPosition(this.state.selectedItem); // if 3d is available, let's take advantage of the performance of transform
var transformProp = CSSTranslate(currentPosition + '%', this.props.vertical);
var transitionTime = this.props.transitionTime + 'ms';
itemListStyles = {
WebkitTransform: transformProp,
transform: transformProp
};
if (!this.state.swiping) {
itemListStyles = Object.assign(Object.assign({}, itemListStyles), {
WebkitTransitionDuration: transitionTime,
transitionDuration: transitionTime
});
}
var itemsClone = this.renderItems(true);
var firstClone = itemsClone.shift();
var lastClone = itemsClone.pop();
var swiperProps = {
onSwipeMove: this.onSwipeMove,
onSwipeStart: this.onSwipeStart,
onSwipeEnd: this.onSwipeEnd
};
var ulProps = {
style: {
height: 'auto'
}
};
var containerStyles = {};
if (isHorizontal) {
//@ts-ignore
swiperProps.onSwipeLeft = this.onSwipeForward; //@ts-ignore
swiperProps.onSwipeRight = this.onSwipeBackwards;
if (this.props.dynamicHeight) {
var itemHeight = this.getVariableImageHeight(this.state.selectedItem);
ulProps.style.height = itemHeight; //@ts-ignore
containerStyles.height = itemHeight || 'auto';
}
} else {
//@ts-ignore
swiperProps.onSwipeUp = this.props.verticalSwipe === 'natural' ? this.onSwipeBackwards : this.onSwipeForward; //@ts-ignore
swiperProps.onSwipeDown = this.props.verticalSwipe === 'natural' ? this.onSwipeForward : this.onSwipeBackwards;
ulProps.style.height = this.state.itemSize; //@ts-ignore
containerStyles.height = this.state.itemSize;
}
return /*#__PURE__*/React.createElement("div", {
className: klass.ROOT(this.props.className),
style: this.props.style,
ref: this.setCarouselWrapperRef
}, /*#__PURE__*/React.createElement("div", {
className: klass.CAROUSEL(true),
style: {
width: this.props.width
}
}, /*#__PURE__*/React.createElement("div", {
className: klass.WRAPPER(true, this.props.vertical),
style: containerStyles,
ref: this.setItemsWrapperRef
}, /*#__PURE__*/React.createElement(Gesture, _extends({
ref: this.setListRef
}, swiperProps), /*#__PURE__*/React.createElement("ul", {
style: Object.assign(Object.assign({}, itemListStyles), ulProps),
className: klass.SLIDER(true, this.state.swiping)
}, this.props.isInfinite && lastClone, this.renderItems(), this.props.isInfinite && firstClone))), this.renderControls(), this.renderStatus()));
}
}]);
return Carousel;
}(React.Component);
Carousel.defaultProps = {
prefixCls: 'Yep-carousel',
style: {},
dots: true,
showPager: false,
isInfinite: false,
initPage: 0,
vertical: false,
verticalSwipe: 'standard',
width: '100%',
autoPlay: false,
transitionTime: 350,
distance: 5,
dynamicHeight: false,
onClickItem: noop,
onClickThumb: noop,
onChange: noop,
renderPage: defaultStatusFormatter,
centerMode: false,
centerSlidePercentage: 80,
onSwipeStart: function onSwipeStart() {},
onSwipeEnd: function onSwipeEnd() {},
onSwipeMove: function onSwipeMove() {},
renderIndicator: function renderIndicator(onClickHandler, isSelected, index, label) {
return /*#__PURE__*/React.createElement("li", {
className: klass.DOT(isSelected),
onClick: onClickHandler,
onKeyDown: onClickHandler,
value: index,
key: index,
role: "button",
tabIndex: 0,
"aria-label": "".concat(label, " ").concat(index + 1)
});
},
renderItem: function renderItem(item) {
return item;
}
};
export default Carousel;