import debounce from 'lodash/debounce' import classnames from 'classnames' import Vue from 'vue' import ref from 'vue-ref' import { getStyle } from '../../_util/props-util' import BaseMixin from '../../_util/BaseMixin' import defaultProps from './default-props' import initialState from './initial-state' import { getOnDemandLazySlides, extractObject, initializedState, getHeight, canGoNext, slideHandler, changeSlide, keyHandler, swipeStart, swipeMove, swipeEnd, getPreClones, getPostClones, getTrackLeft, getTrackCSS, } from './utils/innerSliderUtils' import Track from './track' import Dots from './dots' import { PrevArrow, NextArrow } from './arrows' import ResizeObserver from 'resize-observer-polyfill' Vue.use(ref, { name: 'ant-ref' }) function noop () {} export default { props: { ...defaultProps, }, mixins: [BaseMixin], data () { this.preProps = { ...this.$props } this.list = null this.track = null this.callbackTimers = [] this.clickable = true this.debouncedResize = null return { ...initialState, currentSlide: this.initialSlide, slideCount: this.children.length, } }, methods: { listRefHandler (ref) { this.list = ref }, trackRefHandler (ref) { this.track = ref }, adaptHeight () { if (this.adaptiveHeight && this.list) { const elem = this.list.querySelector( `[data-index="${this.currentSlide}"]` ) this.list.style.height = getHeight(elem) + 'px' } }, onWindowResized (setTrackStyle) { if (this.debouncedResize) this.debouncedResize.cancel() this.debouncedResize = debounce(() => this.resizeWindow(setTrackStyle), 50) this.debouncedResize() }, resizeWindow (setTrackStyle = true) { if (!this.track) return const spec = { listRef: this.list, trackRef: this.track, children: this.children, ...this.$props, ...this.$data, } this.updateState(spec, setTrackStyle, () => { if (this.autoplay) { this.handleAutoPlay('update') } else { this.pause('paused') } }) // animating state should be cleared while resizing, otherwise autoplay stops working this.setState({ animating: false, }) clearTimeout(this.animationEndCallback) delete this.animationEndCallback }, updateState (spec, setTrackStyle, callback) { const updatedState = initializedState(spec) spec = { ...spec, ...updatedState, slideIndex: updatedState.currentSlide } const targetLeft = getTrackLeft(spec) spec = { ...spec, left: targetLeft } const trackStyle = getTrackCSS(spec) if ( setTrackStyle || this.children.length !== spec.children.length ) { updatedState['trackStyle'] = trackStyle } this.setState(updatedState, callback) }, ssrInit () { const children = this.children if (this.variableWidth) { let trackWidth = 0 let trackLeft = 0 const childrenWidths = [] const preClones = getPreClones({ ...this.$props, ...this.$data, slideCount: children.length, }) const postClones = getPostClones({ ...this.$props, ...this.$data, slideCount: children.length, }) children.forEach(child => { const childWidth = getStyle(child).width.split('px')[0] childrenWidths.push(childWidth) trackWidth += childWidth }) for (let i = 0; i < preClones; i++) { trackLeft += childrenWidths[childrenWidths.length - 1 - i] trackWidth += childrenWidths[childrenWidths.length - 1 - i] } for (let i = 0; i < postClones; i++) { trackWidth += childrenWidths[i] } for (let i = 0; i < this.currentSlide; i++) { trackLeft += childrenWidths[i] } const trackStyle = { width: trackWidth + 'px', left: -trackLeft + 'px', } if (this.centerMode) { const currentWidth = `${childrenWidths[this.currentSlide]}px` trackStyle.left = `calc(${ trackStyle.left } + (100% - ${currentWidth}) / 2 ) ` } this.setState({ trackStyle, }) return } const childrenCount = children.length const spec = { ...this.$props, ...this.$data, slideCount: childrenCount } const slideCount = getPreClones(spec) + getPostClones(spec) + childrenCount const trackWidth = 100 / this.slidesToShow * slideCount const slideWidth = 100 / slideCount let trackLeft = -slideWidth * (getPreClones(spec) + this.currentSlide) * trackWidth / 100 if (this.centerMode) { trackLeft += (100 - slideWidth * trackWidth / 100) / 2 } const trackStyle = { width: trackWidth + '%', left: trackLeft + '%', } this.setState({ slideWidth: slideWidth + '%', trackStyle: trackStyle, }) }, checkImagesLoad () { const images = document.querySelectorAll('.slick-slide img') const imagesCount = images.length let loadedCount = 0 Array.prototype.forEach.call(images, image => { const handler = () => ++loadedCount && loadedCount >= imagesCount && this.onWindowResized() if (!image.onclick) { image.onclick = () => image.parentNode.focus() } else { const prevClickHandler = image.onclick image.onclick = () => { prevClickHandler() image.parentNode.focus() } } if (!image.onload) { if (this.$props.lazyLoad) { image.onload = () => { this.adaptHeight() this.callbackTimers.push( setTimeout(this.onWindowResized, this.speed) ) } } else { image.onload = handler image.onerror = () => { handler() this.$emit('lazyLoadError') } } } }) }, progressiveLazyLoad () { const slidesToLoad = [] const spec = { ...this.$props, ...this.$data } for ( let index = this.currentSlide; index < this.slideCount + getPostClones(spec); index++ ) { if (this.lazyLoadedList.indexOf(index) < 0) { slidesToLoad.push(index) break } } for ( let index = this.currentSlide - 1; index >= -getPreClones(spec); index-- ) { if (this.lazyLoadedList.indexOf(index) < 0) { slidesToLoad.push(index) break } } if (slidesToLoad.length > 0) { this.setState(state => ({ lazyLoadedList: state.lazyLoadedList.concat(slidesToLoad), })) this.$emit('lazyLoad', slidesToLoad) } else { if (this.lazyLoadTimer) { clearInterval(this.lazyLoadTimer) delete this.lazyLoadTimer } } }, slideHandler (index, dontAnimate = false) { const { asNavFor, currentSlide, beforeChange, speed, afterChange, } = this.$props const { state, nextState } = slideHandler({ index, ...this.$props, ...this.$data, trackRef: this.track, useCSS: this.useCSS && !dontAnimate, }) if (!state) return beforeChange && beforeChange(currentSlide, state.currentSlide) const slidesToLoad = state.lazyLoadedList.filter( value => this.lazyLoadedList.indexOf(value) < 0 ) if (this.$listeners.lazyLoad && slidesToLoad.length > 0) { this.$emit('lazyLoad', slidesToLoad) } this.setState(state, () => { asNavFor && asNavFor.innerSlider.currentSlide !== currentSlide && asNavFor.innerSlider.slideHandler(index) if (!nextState) return this.animationEndCallback = setTimeout(() => { const { animating, ...firstBatch } = nextState this.setState(firstBatch, () => { this.callbackTimers.push( setTimeout(() => this.setState({ animating }), 10) ) afterChange && afterChange(state.currentSlide) delete this.animationEndCallback }) }, speed) }) }, changeSlide (options, dontAnimate = false) { const spec = { ...this.$props, ...this.$data } const targetSlide = changeSlide(spec, options) if (targetSlide !== 0 && !targetSlide) return if (dontAnimate === true) { this.slideHandler(targetSlide, dontAnimate) } else { this.slideHandler(targetSlide) } }, clickHandler (e) { if (this.clickable === false) { e.stopPropagation() e.preventDefault() } this.clickable = true }, keyHandler (e) { const dir = keyHandler(e, this.accessibility, this.rtl) dir !== '' && this.changeSlide({ message: dir }) }, selectHandler (options) { this.changeSlide(options) }, disableBodyScroll () { const preventDefault = e => { e = e || window.event if (e.preventDefault) e.preventDefault() e.returnValue = false } window.ontouchmove = preventDefault }, enableBodyScroll () { window.ontouchmove = null }, swipeStart (e) { if (this.verticalSwiping) { this.disableBodyScroll() } const state = swipeStart(e, this.swipe, this.draggable) state !== '' && this.setState(state) }, swipeMove (e) { const state = swipeMove(e, { ...this.$props, ...this.$data, trackRef: this.track, listRef: this.list, slideIndex: this.currentSlide, }) if (!state) return if (state['swiping']) { this.clickable = false } this.setState(state) }, swipeEnd (e) { const state = swipeEnd(e, { ...this.$props, ...this.$data, trackRef: this.track, listRef: this.list, slideIndex: this.currentSlide, }) if (!state) return const triggerSlideHandler = state['triggerSlideHandler'] delete state['triggerSlideHandler'] this.setState(state) if (triggerSlideHandler === undefined) return this.slideHandler(triggerSlideHandler) if (this.$props.verticalSwiping) { this.enableBodyScroll() } }, slickPrev () { // this and fellow methods are wrapped in setTimeout // to make sure initialize setState has happened before // any of such methods are called this.callbackTimers.push( setTimeout(() => this.changeSlide({ message: 'previous' }), 0) ) }, slickNext () { this.callbackTimers.push( setTimeout(() => this.changeSlide({ message: 'next' }), 0) ) }, slickGoTo (slide, dontAnimate = false) { slide = Number(slide) if (isNaN(slide)) return '' this.callbackTimers.push( setTimeout( () => this.changeSlide( { message: 'index', index: slide, currentSlide: this.currentSlide, }, dontAnimate ), 0 ) ) }, play () { let nextIndex if (this.rtl) { nextIndex = this.currentSlide - this.slidesToScroll } else { if (canGoNext({ ...this.$props, ...this.$data })) { nextIndex = this.currentSlide + this.slidesToScroll } else { return false } } this.slideHandler(nextIndex) }, handleAutoPlay (playType) { if (this.autoplayTimer) { clearInterval(this.autoplayTimer) } const autoplaying = this.autoplaying if (playType === 'update') { if ( autoplaying === 'hovered' || autoplaying === 'focused' || autoplaying === 'paused' ) { return } } else if (playType === 'leave') { if (autoplaying === 'paused' || autoplaying === 'focused') { return } } else if (playType === 'blur') { if (autoplaying === 'paused' || autoplaying === 'hovered') { return } } this.autoplayTimer = setInterval(this.play, this.autoplaySpeed + 50) this.setState({ autoplaying: 'playing' }) }, pause (pauseType) { if (this.autoplayTimer) { clearInterval(this.autoplayTimer) this.autoplayTimer = null } const autoplaying = this.autoplaying if (pauseType === 'paused') { this.setState({ autoplaying: 'paused' }) } else if (pauseType === 'focused') { if (autoplaying === 'hovered' || autoplaying === 'playing') { this.setState({ autoplaying: 'focused' }) } } else { // pauseType is 'hovered' if (autoplaying === 'playing') { this.setState({ autoplaying: 'hovered' }) } } }, onDotsOver () { this.autoplay && this.pause('hovered') }, onDotsLeave () { this.autoplay && this.autoplaying === 'hovered' && this.handleAutoPlay('leave') }, onTrackOver () { this.autoplay && this.pause('hovered') }, onTrackLeave () { this.autoplay && this.autoplaying === 'hovered' && this.handleAutoPlay('leave') }, onSlideFocus () { this.autoplay && this.pause('focused') }, onSlideBlur () { this.autoplay && this.autoplaying === 'focused' && this.handleAutoPlay('blur') }, customPaging ({ i }) { return }, appendDots ({ dots }) { return