chowa
Version:
UI component library based on React
329 lines (328 loc) • 12.6 kB
JavaScript
/**
* @license chowa v1.1.3
*
* Copyright (c) Chowa Techonlogies Co.,Ltd.(http://www.chowa.cn).
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const React = require("react");
const PropTypes = require("prop-types");
const resize_observer_polyfill_1 = require("resize-observer-polyfill");
const classnames_1 = require("classnames");
const utils_1 = require("../utils");
const icon_1 = require("../icon");
const transition_1 = require("../transition");
const carousel_item_1 = require("./carousel-item");
class Carousel extends React.PureComponent {
constructor(props) {
super(props);
this.timer = null;
this.state = {
arrowVisible: props.arrowTrigger === 'always',
amount: this.computedItemAmount(props.children),
activeIndex: props.current,
clientWidth: 0,
clientHeight: 0,
slideLock: false,
slideEffectStyle: {}
};
[
'selectPageHandler',
'updateRenderParams',
'onMouseEnterHandler',
'onMouseLeaveHandler',
'preItemHandler',
'nextItemHandler',
'onSlideTransitionEnd'
].forEach((fn) => {
this[fn] = this[fn].bind(this);
});
}
componentDidMount() {
this.crontab();
this.resizeObserver = new resize_observer_polyfill_1.default(this.updateRenderParams);
this.resizeObserver.observe(this.wrapperEle);
}
componentDidUpdate(preProps) {
if (preProps.autoPlay !== this.props.autoPlay) {
this.crontab();
}
if (preProps.arrowTrigger !== this.props.arrowTrigger && this.props.arrowTrigger === 'always') {
this.setState({ arrowVisible: true });
}
if (this.state.amount !== React.Children.count(this.props.children)) {
this.setState({
amount: this.computedItemAmount(this.props.children)
}, () => {
this.updateRenderParams();
});
}
if (preProps.current !== this.props.current && this.state.activeIndex !== this.props.current) {
this.setState({
activeIndex: this.props.current
}, () => {
this.updateRenderParams();
});
}
}
componentWillUnmount() {
this.clearTimer();
this.resizeObserver.unobserve(this.wrapperEle);
this.resizeObserver.disconnect();
}
updateRenderParams() {
const { width, height } = utils_1.doms.rect(this.wrapperEle);
const { effect } = this.props;
const { activeIndex, amount } = this.state;
this.setState({
clientWidth: width,
clientHeight: height,
slideEffectStyle: effect === 'slide'
? {
width: (amount + 2) * width,
transform: `translateX(-${width * (activeIndex + 1)}px)`
}
: {}
});
}
computedItemAmount(children) {
let amount = 0;
React.Children.forEach(children, (child) => {
if (child.type !== carousel_item_1.default) {
return;
}
amount++;
});
return amount;
}
clearTimer() {
if (this.timer !== null) {
clearInterval(this.timer);
this.timer = null;
}
}
crontab() {
const { autoPlay, delay } = this.props;
if (!autoPlay) {
return this.clearTimer();
}
this.timer = window.setInterval(() => {
this.nextItemHandler();
}, delay);
}
preItemHandler() {
const { activeIndex, amount } = this.state;
let index = activeIndex - 1;
if (index < 0) {
index = amount - 1;
}
this.selectPageHandler(index, activeIndex);
}
nextItemHandler() {
const { activeIndex, amount } = this.state;
let index = activeIndex + 1;
if (index === amount) {
index = 0;
}
this.selectPageHandler(index, activeIndex);
}
onMouseLeaveHandler() {
if (this.props.arrowTrigger === 'hover') {
this.setState({
arrowVisible: false
});
}
this.crontab();
}
onMouseEnterHandler() {
if (this.props.arrowTrigger === 'hover') {
this.setState({
arrowVisible: true
});
}
this.clearTimer();
}
selectPageHandler(index, preIndex) {
const { slideLock, amount, clientWidth } = this.state;
const { effect } = this.props;
let translateX = 0;
if (slideLock) {
return;
}
if (effect === 'slide') {
if (index === 0 && preIndex === amount - 1) {
translateX = (amount + 1) * clientWidth;
}
else if (index === amount - 1 && preIndex === 0) {
translateX = 0;
}
else {
translateX = clientWidth * (index + 1);
}
}
this.setState({
activeIndex: index,
slideLock: effect === 'slide' ? true : false,
slideEffectStyle: effect === 'slide'
? {
width: (amount + 2) * clientWidth,
transition: 'all 0.4s ease-in',
transform: `translateX(-${translateX}px)`
}
: {}
});
}
onSlideTransitionEnd(e) {
if (e.target !== e.currentTarget) {
return;
}
const { activeIndex, amount, clientWidth } = this.state;
this.setState({
slideLock: false,
slideEffectStyle: {
width: (amount + 2) * clientWidth,
transition: 'none',
transform: `translateX(-${clientWidth * (activeIndex + 1)}px)`
}
});
}
compileItems() {
const { activeIndex, amount, clientWidth, clientHeight } = this.state;
const { children, effect, smashRow, smashCol } = this.props;
const nodes = [];
if (amount === 0) {
return null;
}
React.Children.forEach(children, (child, index) => {
const isActive = index === activeIndex;
if (child.type !== carousel_item_1.default) {
return;
}
if (effect === 'smash') {
const smashNodes = [];
for (let row = 0; row < smashRow; row++) {
for (let col = 0; col < smashCol; col++) {
const x = clientWidth / smashCol * col;
const y = clientHeight / smashRow * row;
const z = isActive ? 0 : 300 + Math.round(Math.random() * 300);
const r = isActive ? 0
: 135 + Math.round(Math.random() * 90) * (Math.random() < 0.5 ? -1 : 1);
smashNodes.push(React.createElement("div", { key: row + '-' + col, style: {
overflow: 'hidden',
height: clientHeight / smashRow,
width: clientWidth / smashCol,
transform: `translateZ(${z}px) rotateY(${r}deg) rotateZ(${r}deg)`
}, className: utils_1.preClass('carousel-fragment') }, React.cloneElement(child.props.children, {
style: {
transform: `translate(-${x}px, -${y}px)`,
clientWidth,
clientHeight
}
})));
}
}
child = (React.createElement(carousel_item_1.default, Object.assign({}, child.props, { active: isActive, key: index }), smashNodes));
}
else {
child = React.cloneElement(child, {
active: isActive,
key: index
});
}
nodes.push(child);
});
if (effect === 'slide' && nodes.length > 0) {
nodes.unshift(React.cloneElement(nodes[amount - 1], {
key: 'last-slide'
}));
nodes.push(React.cloneElement(nodes[1], {
key: 'first-slide'
}));
}
return nodes;
}
renderPages() {
const { showPages, pagesPlacement } = this.props;
const { amount, activeIndex } = this.state;
const nodes = [];
if (!showPages || amount < 2) {
return null;
}
for (let i = 0; i < amount; i++) {
const isActive = i === activeIndex;
const btnClass = classnames_1.default({
[utils_1.preClass('carousel-pages-btn')]: true,
[utils_1.preClass('carousel-pages-active')]: isActive
});
nodes.push(React.createElement("dd", { className: btnClass, key: i },
React.createElement("button", { onClick: isActive ? null : this.selectPageHandler.bind(this, i, activeIndex) }, i)));
}
return (React.createElement("dl", { className: classnames_1.default({
[utils_1.preClass('carousel-pages')]: true,
[utils_1.preClass(`carousel-pages-${pagesPlacement}`)]: true
}) }, nodes));
}
renderArrow() {
const { showArrow } = this.props;
const { amount, arrowVisible } = this.state;
const nodes = [];
if (!showArrow || amount < 2) {
return null;
}
nodes.push(React.createElement(transition_1.default, { key: 'pre', visible: arrowVisible },
React.createElement("button", { className: utils_1.preClass('carousel-pre-btn'), onClick: this.preItemHandler },
React.createElement(icon_1.default, { type: 'arrow-left' }))));
nodes.push(React.createElement(transition_1.default, { key: 'next', visible: arrowVisible },
React.createElement("button", { className: utils_1.preClass('carousel-next-btn'), onClick: this.nextItemHandler },
React.createElement(icon_1.default, { type: 'arrow-right' }))));
return nodes;
}
render() {
const { className, style, effect, arrowTrigger, autoPlay } = this.props;
const { slideEffectStyle, clientWidth, clientHeight } = this.state;
const componentClass = classnames_1.default({
[utils_1.preClass('carousel')]: true,
[utils_1.preClass(`carousel-${effect}`)]: true,
[className]: utils_1.isExist(className)
});
const itemsWrapperStyle = Object.assign({ height: clientHeight, width: clientWidth }, slideEffectStyle);
return (React.createElement("section", { className: componentClass, style: style, onMouseEnter: (autoPlay || arrowTrigger === 'hover') ? this.onMouseEnterHandler : null, onMouseLeave: (autoPlay || arrowTrigger === 'hover') ? this.onMouseLeaveHandler : null, ref: (ele) => {
this.wrapperEle = ele;
} },
React.createElement("div", { className: utils_1.preClass('carousel-container') },
React.createElement("ul", { style: itemsWrapperStyle, onTransitionEnd: effect === 'slide' ? this.onSlideTransitionEnd : null, className: utils_1.preClass('carousel-items-wrapper') }, this.compileItems()),
this.renderArrow()),
this.renderPages()));
}
}
Carousel.propTypes = {
className: PropTypes.string,
style: PropTypes.object,
current: PropTypes.number,
autoPlay: PropTypes.bool,
delay: PropTypes.number,
showPages: PropTypes.bool,
pagesPlacement: PropTypes.oneOf(['inside', 'outside']),
showArrow: PropTypes.bool,
arrowTrigger: PropTypes.oneOf(['hover', 'always', 'never']),
effect: PropTypes.oneOf(['slide', 'fade', 'smash']),
smashRow: PropTypes.number,
smashCol: PropTypes.number
};
Carousel.defaultProps = {
current: 0,
autoPlay: true,
delay: 6000,
showPages: true,
pagesPlacement: 'inside',
showArrow: true,
arrowTrigger: 'always',
effect: 'slide',
smashRow: 2,
smashCol: 6
};
Carousel.Item = carousel_item_1.default;
exports.default = Carousel;