diff --git a/.babelrc b/.babelrc index a4c000e..3b4f768 100644 --- a/.babelrc +++ b/.babelrc @@ -8,7 +8,5 @@ ], "@babel/preset-react" ], - "plugins": [ - "@babel/plugin-transform-runtime" - ] + "plugins": ["@babel/plugin-transform-runtime"] } diff --git a/package.json b/package.json index 1777cd1..ca2ba11 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "prebuild": "npm run clean", "build": "babel src -d lib --extensions '.js,.jsx' && lessc src/carousel.less lib/carousel.css && postcss --no-map --use autoprefixer -o lib/carousel.css lib/carousel.css", "lint": "eslint --ext .js,.jsx src/ test/", + "lint:fix": "npm run lint -- --fix", "unit": "vitest run", "test": "vitest run --coverage", "test:watch": "vitest", diff --git a/src/index.jsx b/src/index.jsx index 45c7291..44a6c1b 100644 --- a/src/index.jsx +++ b/src/index.jsx @@ -20,7 +20,6 @@ const MAX_LOAD_RETRIES = 500; * @extends React.Component */ export default class Carousel extends Component { - static get propTypes() { return { initialSlide: PropTypes.number, @@ -49,15 +48,20 @@ export default class Carousel extends Component { slideAlignment: PropTypes.oneOf(['left', 'center', 'right']), beforeChange: PropTypes.func, afterChange: PropTypes.func, - transitionDuration: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), + transitionDuration: PropTypes.oneOfType([ + PropTypes.number, + PropTypes.string + ]), autoplay: PropTypes.bool, autoplaySpeed: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), lazyLoad: PropTypes.bool, - controls: PropTypes.arrayOf(PropTypes.shape({ - component: PropTypes.func.isRequired, - props: PropTypes.object, - position: PropTypes.oneOf(['top', 'bottom']) - })), + controls: PropTypes.arrayOf( + PropTypes.shape({ + component: PropTypes.func.isRequired, + props: PropTypes.object, + position: PropTypes.oneOf(['top', 'bottom']) + }) + ), draggable: PropTypes.bool, pauseOnHover: PropTypes.bool, clickToNavigate: PropTypes.bool, @@ -141,28 +145,36 @@ export default class Carousel extends Component { componentDidUpdate(prevProps, prevState) { const { children, autoplay, slideWidth, slideAlignment } = this.props; - const { currentSlide, loadedImages, direction, loading, slideDimensions } = this.state; + const { currentSlide, loadedImages, direction, loading, slideDimensions } = + this.state; const oldChildren = prevProps.children; - if (direction !== prevState.direction || - currentSlide !== prevState.currentSlide || - loadedImages !== prevState.loadedImages || - slideWidth !== prevProps.slideWidth || - slideDimensions.width !== prevState.slideDimensions.width || - slideDimensions.height !== prevState.slideDimensions.height || - slideAlignment !== prevProps.slideAlignment) { + if ( + direction !== prevState.direction || + currentSlide !== prevState.currentSlide || + loadedImages !== prevState.loadedImages || + slideWidth !== prevProps.slideWidth || + slideDimensions.width !== prevState.slideDimensions.width || + slideDimensions.height !== prevState.slideDimensions.height || + slideAlignment !== prevProps.slideAlignment + ) { // Whenever new images are loaded, the current slide index changes, the transition direction changes, or the // slide width changes, we need to recalculate the left offset positioning of the slides. this.calcLeftOffset(); } - if (!areChildImagesEqual(Children.toArray(children), Children.toArray(oldChildren))) { + if ( + !areChildImagesEqual( + Children.toArray(children), + Children.toArray(oldChildren) + ) + ) { // If the image source or number of images changed, we need to refetch images and force an update this._animating = false; this.fetchImages(); } - if (autoplay && (!loading && prevState.loading || !prevProps.autoplay)) { + if (autoplay && ((!loading && prevState.loading) || !prevProps.autoplay)) { this.startAutoplay(); } } @@ -183,7 +195,7 @@ export default class Carousel extends Component { window.addEventListener('resize', this.calcLeftOffset, false); if (window.IntersectionObserver) { - this._observer = new window.IntersectionObserver(entries => { + this._observer = new window.IntersectionObserver((entries) => { if (!this.props.autoplay) { return; } @@ -231,7 +243,10 @@ export default class Carousel extends Component { const { children } = this.props; const { loadedImages, currentSlide } = this.state; const slides = Children.toArray(children); - const imagesToPrefetch = Math.min(this.props.imagesToPrefetch, slides.length); + const imagesToPrefetch = Math.min( + this.props.imagesToPrefetch, + slides.length + ); const startIndex = currentSlide - Math.floor(imagesToPrefetch / 2); const endIndex = startIndex + imagesToPrefetch; const pendingImages = []; @@ -247,20 +262,26 @@ export default class Carousel extends Component { } if (pendingImages.length) { - pendingImages.forEach(image => { + pendingImages.forEach((image) => { const img = new Image(); img.onload = img.onerror = () => { if (this._isMounted) { - this.setState({ - loadedImages: { - ...this.state.loadedImages, - [image]: { width: img.width || 'auto', height: img.height || 'auto' } + this.setState( + { + loadedImages: { + ...this.state.loadedImages, + [image]: { + width: img.width || 'auto', + height: img.height || 'auto' + } + } + }, + () => { + if (image === currentImage) { + this.handleInitialLoad(); + } } - }, () => { - if (image === currentImage) { - this.handleInitialLoad(); - } - }); + ); } }; img.src = image; @@ -307,12 +328,22 @@ export default class Carousel extends Component { * @param {Boolean} autoSlide - The source of slide transition, should be true for autoPlay and false for user click. */ goToSlide = (index, direction, autoSlide = false) => { - const { beforeChange, transitionDuration, transition, onSlideTransitioned, children } = this.props; + const { + beforeChange, + transitionDuration, + transition, + onSlideTransitioned, + children + } = this.props; const { currentSlide } = this.state; const lastIndex = Children.count(children) - 1; - const newIndex = index < 0 ? lastIndex + index + 1 : - index <= lastIndex ? index : index - lastIndex - 1; + const newIndex = + index < 0 + ? lastIndex + index + 1 + : index <= lastIndex + ? index + : index - lastIndex - 1; direction = direction || (index > currentSlide ? 'right' : 'left'); @@ -380,15 +411,18 @@ export default class Carousel extends Component { if (!e || e.propertyName === 'transform') { this._animating = false; - this.setState({ - direction: null, - transitioningFrom: null, - transitionDuration: 0 - }, () => { - if (!this._allImagesLoaded) { - this.fetchImages(); + this.setState( + { + direction: null, + transitioningFrom: null, + transitionDuration: 0 + }, + () => { + if (!this._allImagesLoaded) { + this.fetchImages(); + } } - }); + ); if (this.props.autoplay) { this.startAutoplay(); @@ -401,7 +435,7 @@ export default class Carousel extends Component { /** * @returns {Array} Controls to be rendered with the carousel. */ - getControls() { + getControls = () => { const { arrows, dots, controls, isVertical } = this.props; let arr = controls.slice(0); @@ -411,38 +445,67 @@ export default class Carousel extends Component { if (arrows) { arr = arr.concat([ - { ...isVertical ? { component: Arrow, props: { direction: 'top' } } : { component: Arrow, props: { direction: 'left' } } }, - { ...isVertical ? { component: Arrow, props: { direction: 'bottom' } } : { component: Arrow, props: { direction: 'right' } } } + { + ...(isVertical + ? { component: Arrow, props: { direction: 'top' } } + : { component: Arrow, props: { direction: 'left' } }) + }, + { + ...(isVertical + ? { component: Arrow, props: { direction: 'bottom' } } + : { component: Arrow, props: { direction: 'right' } }) + } ]); } return arr; - } + }; /** * Renders the carousel. * * @returns {Object} Component to be rendered. */ - render() { - const { className, viewportWidth, viewportHeight, width, height, dots, infinite, - children, slideHeight, transition, style, draggable, easing, arrows, dir, isVertical } = this.props; - const { loading, transitionDuration, dragOffset, currentSlide, leftOffset } = this.state; + render = () => { + const { + className, + viewportWidth, + viewportHeight, + width, + height, + dots, + infinite, + children, + slideHeight, + transition, + style, + draggable, + easing, + arrows, + dir, + isVertical + } = this.props; + const { + loading, + transitionDuration, + dragOffset, + currentSlide, + leftOffset + } = this.state; const numSlides = Children.count(children); const classes = classnames('carousel', className, { loaded: !loading }); - const containerStyle = { ...(style.container || {}), - width, - height - }; - const innerContainerStyle = { ...(style.containerInner || {}), + const containerStyle = { ...(style.container || {}), width, height }; + const innerContainerStyle = { + ...(style.containerInner || {}), width, height, marginBottom: dots ? '20px' : 0, - ...isVertical && { display: 'flex' } + ...(isVertical && { display: 'flex' }) }; - const viewportStyle = { ...(style.viewport || {}), + const viewportStyle = { + ...(style.viewport || {}), width: viewportWidth, height: viewportHeight || slideHeight || 'auto' }; @@ -450,10 +513,17 @@ export default class Carousel extends Component { let trackStyle = { ...style.track }; if (transition === 'slide') { const leftPos = leftOffset + dragOffset; - trackStyle = { ...trackStyle, - ...isVertical && { transform: `translateY(${isRTL ? -leftPos : leftPos}px)` }, - ...!isVertical && { transform: `translateX(${isRTL ? -leftPos : leftPos}px)` }, - transition: transitionDuration ? `transform ${ms('' + transitionDuration)}ms ${easing}` : 'none' + trackStyle = { + ...trackStyle, + ...(isVertical && { + transform: `translateY(${isRTL ? -leftPos : leftPos}px)` + }), + ...(!isVertical && { + transform: `translateX(${isRTL ? -leftPos : leftPos}px)` + }), + transition: transitionDuration + ? `transform ${ms('' + transitionDuration)}ms ${easing}` + : 'none' }; } @@ -464,12 +534,19 @@ export default class Carousel extends Component { const controls = this.getControls(); return ( -