UNPKG

chowa

Version:

UI component library based on React

329 lines (328 loc) 12.6 kB
/** * @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;