diff --git a/components/carousel/index.tsx b/components/carousel/index.tsx index d1b44230f..fe5c6bae3 100644 --- a/components/carousel/index.tsx +++ b/components/carousel/index.tsx @@ -1,4 +1,4 @@ -import type { ExtractPropTypes } from 'vue'; +import type { ExtractPropTypes, PropType } from 'vue'; import { defineComponent, inject } from 'vue'; import PropTypes from '../_util/vue-types'; import debounce from 'lodash-es/debounce'; @@ -7,58 +7,73 @@ import { defaultConfigProvider } from '../config-provider'; import warning from '../_util/warning'; import classNames from '../_util/classNames'; import SlickCarousel from '../vc-slick/src'; -import { tuple, withInstall } from '../_util/type'; +import { withInstall } from '../_util/type'; + +export type SwipeDirection = 'left' | 'down' | 'right' | 'up' | string; + +export type LazyLoadTypes = 'ondemand' | 'progressive'; + +export type CarouselEffect = 'scrollx' | 'fade'; +export type DotPosition = 'top' | 'bottom' | 'left' | 'right'; + +export interface CarouselRef { + goTo: (slide: number, dontAnimate?: boolean) => void; + next: () => void; + prev: () => void; + autoPlay: (palyType?: 'update' | 'leave' | 'blur') => void; + innerSlider: any; +} // Carousel -export const carouselProps = { - effect: PropTypes.oneOf(tuple('scrollx', 'fade')), - dots: PropTypes.looseBool.def(true), - vertical: PropTypes.looseBool, - autoplay: PropTypes.looseBool, - easing: PropTypes.string, - beforeChange: PropTypes.func, - afterChange: PropTypes.func, +export const carouselProps = () => ({ + effect: String as PropType, + dots: { type: Boolean, default: true }, + vertical: { type: Boolean, default: undefined }, + autoplay: { type: Boolean, default: undefined }, + easing: String, + beforeChange: Function as PropType<(currentSlide: number, nextSlide: number) => void>, + afterChange: Function as PropType<(currentSlide: number) => void>, // style: PropTypes.React.CSSProperties, - prefixCls: PropTypes.string, - accessibility: PropTypes.looseBool, + prefixCls: String, + accessibility: { type: Boolean, default: undefined }, nextArrow: PropTypes.any, prevArrow: PropTypes.any, - pauseOnHover: PropTypes.looseBool, - // className: PropTypes.string, - adaptiveHeight: PropTypes.looseBool, - arrows: PropTypes.looseBool.def(false), - autoplaySpeed: PropTypes.number, - centerMode: PropTypes.looseBool, - centerPadding: PropTypes.string, - cssEase: PropTypes.string, - dotsClass: PropTypes.string, - draggable: PropTypes.looseBool.def(false), - fade: PropTypes.looseBool, - focusOnSelect: PropTypes.looseBool, - infinite: PropTypes.looseBool, - initialSlide: PropTypes.number, - lazyLoad: PropTypes.looseBool, - rtl: PropTypes.looseBool, - slide: PropTypes.string, - slidesToShow: PropTypes.number, - slidesToScroll: PropTypes.number, - speed: PropTypes.number, - swipe: PropTypes.looseBool, - swipeToSlide: PropTypes.looseBool, - touchMove: PropTypes.looseBool, - touchThreshold: PropTypes.number, - variableWidth: PropTypes.looseBool, - useCSS: PropTypes.looseBool, - slickGoTo: PropTypes.number, - responsive: PropTypes.array, - dotPosition: PropTypes.oneOf(tuple('top', 'bottom', 'left', 'right')), - verticalSwiping: PropTypes.looseBool.def(false), -}; -export type CarouselProps = Partial>; + pauseOnHover: { type: Boolean, default: undefined }, + // className: String, + adaptiveHeight: { type: Boolean, default: undefined }, + arrows: { type: Boolean, default: false }, + autoplaySpeed: Number, + centerMode: { type: Boolean, default: undefined }, + centerPadding: String, + cssEase: String, + dotsClass: String, + draggable: { type: Boolean, default: false }, + fade: { type: Boolean, default: undefined }, + focusOnSelect: { type: Boolean, default: undefined }, + infinite: { type: Boolean, default: undefined }, + initialSlide: Number, + lazyLoad: String as PropType, + rtl: { type: Boolean, default: undefined }, + slide: String, + slidesToShow: Number, + slidesToScroll: Number, + speed: Number, + swipe: { type: Boolean, default: undefined }, + swipeToSlide: { type: Boolean, default: undefined }, + touchMove: { type: Boolean, default: undefined }, + touchThreshold: Number, + variableWidth: { type: Boolean, default: undefined }, + useCSS: { type: Boolean, default: undefined }, + slickGoTo: Number, + responsive: Array, + dotPosition: String as PropType, + verticalSwiping: { type: Boolean, default: false }, +}); +export type CarouselProps = Partial>>; const Carousel = defineComponent({ name: 'ACarousel', inheritAttrs: false, - props: carouselProps, + props: carouselProps(), setup() { return { configProvider: inject('configProvider', defaultConfigProvider), diff --git a/components/carousel/style/index.less b/components/carousel/style/index.less index 8d2c99452..031383b2b 100644 --- a/components/carousel/style/index.less +++ b/components/carousel/style/index.less @@ -1,18 +1,20 @@ @import '../../style/themes/index'; @import '../../style/mixins/index'; -.@{ant-prefix}-carousel { +@carousel-prefix-cls: ~'@{ant-prefix}-carousel'; + +.@{carousel-prefix-cls} { .reset-component(); .slick-slider { position: relative; display: block; box-sizing: border-box; - -webkit-touch-callout: none; - -ms-touch-action: pan-y; touch-action: pan-y; + -webkit-touch-callout: none; -webkit-tap-highlight-color: transparent; } + .slick-list { position: relative; display: block; @@ -45,11 +47,20 @@ visibility: visible; } } + + // fix Carousel content height not match parent node + // when children is empty node + // https://github.com/ant-design/ant-design/issues/25878 + > div > div { + vertical-align: bottom; + } } } + .slick-slider .slick-track, .slick-slider .slick-list { transform: translate3d(0, 0, 0); + touch-action: pan-y; } .slick-track { @@ -72,17 +83,17 @@ visibility: hidden; } } + .slick-slide { display: none; float: left; height: 100%; min-height: 1px; - [dir='rtl'] & { - float: right; - } + img { display: block; } + &.slick-loading img { display: none; } @@ -103,8 +114,8 @@ .slick-vertical .slick-slide { display: block; height: auto; - border: @border-width-base @border-style-base transparent; } + .slick-arrow.slick-hidden { display: none; } @@ -126,15 +137,18 @@ border: 0; outline: none; cursor: pointer; + &:hover, &:focus { color: transparent; background: transparent; outline: none; + &::before { opacity: 1; } } + &.slick-disabled::before { opacity: 0.25; } @@ -142,6 +156,7 @@ .slick-prev { left: -25px; + &::before { content: '←'; } @@ -149,6 +164,7 @@ .slick-next { right: -25px; + &::before { content: '→'; } @@ -157,29 +173,45 @@ // Dots .slick-dots { position: absolute; - display: block; - width: 100%; - height: @carousel-dot-height; - margin: 0; - padding: 0; - text-align: center; + right: 0; + bottom: 0; + left: 0; + z-index: 15; + display: flex !important; + justify-content: center; + margin-right: 15%; + margin-left: 15%; + padding-left: 0; list-style: none; + &-bottom { bottom: 12px; } + &-top { top: 12px; + bottom: auto; } + li { position: relative; display: inline-block; + flex: 0 1 auto; + box-sizing: content-box; + width: @carousel-dot-width; + height: @carousel-dot-height; margin: 0 2px; + margin-right: 3px; + margin-left: 3px; padding: 0; text-align: center; + text-indent: -999px; vertical-align: top; + transition: all 0.5s; + button { display: block; - width: @carousel-dot-width; + width: 100%; height: @carousel-dot-height; padding: 0; color: transparent; @@ -191,15 +223,21 @@ cursor: pointer; opacity: 0.3; transition: all 0.5s; + &:hover, &:focus { opacity: 0.75; } } - &.slick-active button { + + &.slick-active { width: @carousel-dot-active-width; - background: @component-background; - opacity: 1; + + & button { + background: @component-background; + opacity: 1; + } + &:hover, &:focus { opacity: 1; @@ -213,26 +251,44 @@ .slick-dots { top: 50%; bottom: auto; + flex-direction: column; width: @carousel-dot-height; height: auto; + margin: 0; transform: translateY(-50%); + &-left { + right: auto; left: 12px; } + &-right { right: 12px; + left: auto; } + li { - margin: 0 2px; + width: @carousel-dot-height; + height: @carousel-dot-width; + margin: 4px 2px; vertical-align: baseline; + button { width: @carousel-dot-height; height: @carousel-dot-width; } - &.slick-active button { + + &.slick-active { width: @carousel-dot-height; height: @carousel-dot-active-width; + + button { + width: @carousel-dot-height; + height: @carousel-dot-active-width; + } } } } } + +@import './rtl'; diff --git a/components/carousel/style/index.ts b/components/carousel/style/index.tsx similarity index 100% rename from components/carousel/style/index.ts rename to components/carousel/style/index.tsx diff --git a/components/carousel/style/rtl.less b/components/carousel/style/rtl.less new file mode 100644 index 000000000..c2853a2ac --- /dev/null +++ b/components/carousel/style/rtl.less @@ -0,0 +1,54 @@ +@import '../../style/themes/index'; +@import '../../style/mixins/index'; + +@carousel-prefix-cls: ~'@{ant-prefix}-carousel'; + +.@{carousel-prefix-cls} { + &-rtl { + direction: rtl; + } + + .slick-track { + .@{carousel-prefix-cls}-rtl & { + right: 0; + left: auto; + } + } + + .slick-prev { + .@{carousel-prefix-cls}-rtl & { + right: -25px; + left: auto; + + &::before { + content: '→'; + } + } + } + + .slick-next { + .@{carousel-prefix-cls}-rtl & { + right: auto; + left: -25px; + + &::before { + content: '←'; + } + } + } + + // Dots + .slick-dots { + .@{carousel-prefix-cls}-rtl& { + flex-direction: row-reverse; + } + } +} + +.@{ant-prefix}-carousel-vertical { + .slick-dots { + .@{carousel-prefix-cls}-rtl& { + flex-direction: column; + } + } +} diff --git a/components/vc-slick/src/dots.jsx b/components/vc-slick/src/dots.jsx index e8f02e378..80d96a802 100644 --- a/components/vc-slick/src/dots.jsx +++ b/components/vc-slick/src/dots.jsx @@ -1,5 +1,6 @@ import classnames from '../../_util/classNames'; import { cloneElement } from '../../_util/vnode'; +import { clamp } from './utils/innerSliderUtils'; const getDotCount = function (spec) { let dots; @@ -39,24 +40,26 @@ const Dots = (_, { attrs }) => { // // Credit: http://stackoverflow.com/a/13735425/1849458 const mouseEvents = { onMouseenter, onMouseover, onMouseleave }; - const dots = Array.apply( - null, - Array(dotCount + 1) - .join('0') - .split(''), - ).map((x, i) => { - const leftBound = i * slidesToScroll; - const rightBound = i * slidesToScroll + (slidesToScroll - 1); - const className = classnames({ - 'slick-active': currentSlide >= leftBound && currentSlide <= rightBound, + let dots = []; + for (let i = 0; i < dotCount; i++) { + let _rightBound = (i + 1) * slidesToScroll - 1; + let rightBound = infinite ? _rightBound : clamp(_rightBound, 0, slideCount - 1); + let _leftBound = rightBound - (slidesToScroll - 1); + let leftBound = infinite ? _leftBound : clamp(_leftBound, 0, slideCount - 1); + + let className = classnames({ + 'slick-active': infinite + ? currentSlide >= leftBound && currentSlide <= rightBound + : currentSlide === leftBound, }); - const dotOptions = { + let dotOptions = { message: 'dots', index: i, slidesToScroll, currentSlide, }; + function onClick(e) { // In Autoplay the focus stays on clicked button even after transition // to next slide. That only goes away by click somewhere outside @@ -65,14 +68,12 @@ const Dots = (_, { attrs }) => { } clickHandler(dotOptions); } - return ( + dots = dots.concat(
  • - {cloneElement(customPaging({ i }), { - onClick, - })} -
  • + {cloneElement(customPaging({ i }), { onClick })} + , ); - }); + } return cloneElement(appendDots({ dots }), { class: dotsClass, diff --git a/components/vc-slick/src/index.js b/components/vc-slick/src/index.js index 9de99faa2..0925ed269 100644 --- a/components/vc-slick/src/index.js +++ b/components/vc-slick/src/index.js @@ -1,4 +1,4 @@ -// base react-slick 0.23.2 +// base react-slick 0.28.2 import Slider from './slider'; export default Slider; diff --git a/components/vc-slick/src/initial-state.js b/components/vc-slick/src/initial-state.js index 16f56b53c..d1e4c7d34 100644 --- a/components/vc-slick/src/initial-state.js +++ b/components/vc-slick/src/initial-state.js @@ -21,6 +21,7 @@ const initialState = { touchObject: { startX: 0, startY: 0, curX: 0, curY: 0 }, trackStyle: {}, trackWidth: 0, + targetSlide: 0, }; export default initialState; diff --git a/components/vc-slick/src/inner-slider.jsx b/components/vc-slick/src/inner-slider.jsx index c454636bc..1ed82b589 100644 --- a/components/vc-slick/src/inner-slider.jsx +++ b/components/vc-slick/src/inner-slider.jsx @@ -42,10 +42,12 @@ export default { this.callbackTimers = []; this.clickable = true; this.debouncedResize = null; + const ssrState = this.ssrInit(); return { ...initialState, currentSlide: this.initialSlide, slideCount: this.children.length, + ...ssrState, }; }, watch: { @@ -83,7 +85,9 @@ export default { currentSlide: this.currentSlide, }); } - if (nextProps.autoplay) { + if (!this.preProps.autoplay && nextProps.autoplay) { + this.handleAutoPlay('playing'); + } else if (nextProps.autoplay) { this.handleAutoPlay('update'); } else { this.pause('paused'); @@ -92,8 +96,7 @@ export default { this.preProps = { ...nextProps }; }, }, - beforeMount() { - this.ssrInit(); + mounted() { this.__emit('init'); if (this.lazyLoad) { const slidesToLoad = getOnDemandLazySlides({ @@ -107,8 +110,6 @@ export default { this.__emit('lazyLoad', slidesToLoad); } } - }, - mounted() { this.$nextTick(() => { const spec = { listRef: this.list, @@ -118,7 +119,7 @@ export default { }; this.updateState(spec, true, () => { this.adaptHeight(); - this.autoplay && this.handleAutoPlay('update'); + this.autoplay && this.handleAutoPlay('playing'); }); if (this.lazyLoad === 'progressive') { this.lazyLoadTimer = setInterval(this.progressiveLazyLoad, 1000); @@ -132,14 +133,11 @@ export default { } }); this.ro.observe(this.list); - Array.prototype.forEach.call(document.querySelectorAll('.slick-slide'), slide => { - slide.onfocus = this.$props.pauseOnFocus ? this.onSlideFocus : null; - slide.onblur = this.$props.pauseOnFocus ? this.onSlideBlur : null; - }); - // To support server-side rendering - if (!window) { - return; - } + document.querySelectorAll && + Array.prototype.forEach.call(document.querySelectorAll('.slick-slide'), slide => { + slide.onfocus = this.$props.pauseOnFocus ? this.onSlideFocus : null; + slide.onblur = this.$props.pauseOnFocus ? this.onSlideBlur : null; + }); if (window.addEventListener) { window.addEventListener('resize', this.onWindowResized); } else { @@ -166,6 +164,7 @@ export default { if (this.autoplayTimer) { clearInterval(this.autoplayTimer); } + this.ro.disconnect(); }, updated() { this.checkImagesLoad(); @@ -206,7 +205,8 @@ export default { this.debouncedResize(); }, resizeWindow(setTrackStyle = true) { - if (!this.track) return; + const isTrackMounted = Boolean(this.track); + if (!isTrackMounted) return; const spec = { listRef: this.list, trackRef: this.track, @@ -278,10 +278,9 @@ export default { const currentWidth = `${childrenWidths[this.currentSlide]}px`; trackStyle.left = `calc(${trackStyle.left} + (100% - ${currentWidth}) / 2 ) `; } - this.setState({ + return { trackStyle, - }); - return; + }; } const childrenCount = children.length; const spec = { ...this.$props, ...this.$data, slideCount: childrenCount }; @@ -296,13 +295,17 @@ export default { width: trackWidth + '%', left: trackLeft + '%', }; - this.setState({ + return { slideWidth: slideWidth + '%', trackStyle, - }); + }; }, checkImagesLoad() { - const images = document.querySelectorAll('.slick-slide img'); + let images = + (this.list && + this.list.querySelectorAll && + this.list.querySelectorAll('.slick-slide img')) || + []; const imagesCount = images.length; let loadedCount = 0; Array.prototype.forEach.call(images, image => { @@ -376,10 +379,16 @@ export default { if (this.$attrs.onLazyLoad && slidesToLoad.length > 0) { this.__emit('lazyLoad', slidesToLoad); } + if (!this.$props.waitForAnimate && this.animationEndCallback) { + clearTimeout(this.animationEndCallback); + afterChange && afterChange(currentSlide); + delete this.animationEndCallback; + } this.setState(state, () => { - asNavFor && - asNavFor.innerSlider.currentSlide !== currentSlide && + if (asNavFor && this.asNavForIndex !== index) { + this.asNavForIndex = index; asNavFor.innerSlider.slideHandler(index); + } if (!nextState) return; this.animationEndCallback = setTimeout(() => { const { animating, ...firstBatch } = nextState; @@ -400,6 +409,11 @@ export default { } else { this.slideHandler(targetSlide); } + this.$props.autoplay && this.handleAutoPlay('update'); + if (this.$props.focusOnSelect) { + const nodes = this.list.querySelectorAll('.slick-current'); + nodes[0] && nodes[0].focus(); + } }, clickHandler(e) { if (this.clickable === false) { @@ -465,6 +479,10 @@ export default { this.enableBodyScroll(); } }, + touchEnd(e) { + this.swipeEnd(e); + this.clickable = true; + }, slickPrev() { // this and fellow methods are wrapped in setTimeout // to make sure initialize setState has happened before @@ -599,11 +617,13 @@ export default { 'variableWidth', 'unslick', 'centerPadding', + 'targetSlide', + 'useCSS', ]); const { pauseOnHover } = this.$props; trackProps = { ...trackProps, - focusOnSelect: this.focusOnSelect ? this.selectHandler : null, + focusOnSelect: this.focusOnSelect && this.clickable ? this.selectHandler : null, ref: this.trackRefHandler, onMouseleave: pauseOnHover ? this.onTrackLeave : noop, onMouseover: pauseOnHover ? this.onTrackOver : noop, @@ -701,14 +721,15 @@ export default { : noop, [supportsPassive ? 'onTouchmovePassive' : 'onTouchmove']: this.dragging && touchMove ? this.swipeMove : noop, - onTouchend: touchMove ? this.swipeEnd : noop, + onTouchend: touchMove ? this.touchEnd : noop, onTouchcancel: this.dragging && touchMove ? this.swipeEnd : noop, onKeydown: this.accessibility ? this.keyHandler : noop, }; let innerSliderProps = { class: className, - // dir: 'ltr', + dir: 'ltr', + style: this.$attrs.style, }; if (this.unslick) { diff --git a/components/vc-slick/src/slider.jsx b/components/vc-slick/src/slider.jsx index e4f474218..6318cc5ff 100644 --- a/components/vc-slick/src/slider.jsx +++ b/components/vc-slick/src/slider.jsx @@ -21,7 +21,7 @@ export default defineComponent({ }; }, // handles responsive breakpoints - beforeMount() { + mounted() { if (this.responsive) { const breakpoints = this.responsive.map(breakpt => breakpt.breakpoint); // sort them in increasing order of their numerical value @@ -185,7 +185,7 @@ export default defineComponent({ if (settings === 'unslick') { const className = 'regular slider ' + (this.className || ''); - return
    {newChildren}
    ; + return
    {children}
    ; } else if (newChildren.length <= settings.slidesToShow) { settings.unslick = true; } diff --git a/components/vc-slick/src/track.jsx b/components/vc-slick/src/track.jsx index 4b3fe4de8..a001e818f 100644 --- a/components/vc-slick/src/track.jsx +++ b/components/vc-slick/src/track.jsx @@ -24,7 +24,15 @@ const getSlideClasses = spec => { } else { slickActive = spec.currentSlide <= index && index < spec.currentSlide + spec.slidesToShow; } - const slickCurrent = index === spec.currentSlide; + let focusedSlide; + if (spec.targetSlide < 0) { + focusedSlide = spec.targetSlide + spec.slideCount; + } else if (spec.targetSlide >= spec.slideCount) { + focusedSlide = spec.targetSlide - spec.slideCount; + } else { + focusedSlide = spec.targetSlide; + } + let slickCurrent = index === focusedSlide; return { 'slick-slide': true, 'slick-active': slickActive, @@ -49,32 +57,24 @@ const getSlideStyle = function (spec) { style.left = -spec.index * parseInt(spec.slideWidth) + 'px'; } style.opacity = spec.currentSlide === spec.index ? 1 : 0; - style.transition = - 'opacity ' + - spec.speed + - 'ms ' + - spec.cssEase + - ', ' + - 'visibility ' + - spec.speed + - 'ms ' + - spec.cssEase; - style.WebkitTransition = - 'opacity ' + - spec.speed + - 'ms ' + - spec.cssEase + - ', ' + - 'visibility ' + - spec.speed + - 'ms ' + - spec.cssEase; + if (spec.useCSS) { + style.transition = + 'opacity ' + + spec.speed + + 'ms ' + + spec.cssEase + + ', ' + + 'visibility ' + + spec.speed + + 'ms ' + + spec.cssEase; + } } return style; }; -const getKey = (child, fallbackKey) => child.key || (child.key === 0 && '0') || fallbackKey; +const getKey = (child, fallbackKey) => child.key + '-' + fallbackKey; const renderSlides = function (spec, children) { let key; diff --git a/components/vc-slick/src/utils/innerSliderUtils.js b/components/vc-slick/src/utils/innerSliderUtils.js index 0db587921..ec3658bf0 100644 --- a/components/vc-slick/src/utils/innerSliderUtils.js +++ b/components/vc-slick/src/utils/innerSliderUtils.js @@ -1,4 +1,15 @@ -import supportsPassive from '../../../_util/supportsPassive'; +// import supportsPassive from '../../../_util/supportsPassive'; + +export function clamp(number, lowerBound, upperBound) { + return Math.max(lowerBound, Math.min(number, upperBound)); +} + +export const safePreventDefault = event => { + const passiveEvents = ['touchstart', 'touchmove', 'wheel']; + if (!passiveEvents.includes(event.type)) { + event.preventDefault(); + } +}; export const getOnDemandLazySlides = spec => { const onDemandSlides = []; @@ -91,8 +102,10 @@ export const extractObject = (spec, keys) => { export const initializedState = spec => { // spec also contains listRef, trackRef const slideCount = spec.children.length; - const listWidth = Math.ceil(getWidth(spec.listRef)); - const trackWidth = Math.ceil(getWidth(spec.trackRef)); + const listNode = spec.listRef; + const listWidth = Math.ceil(getWidth(listNode)); + const trackNode = spec.trackRef; + const trackWidth = Math.ceil(getWidth(trackNode)); let slideWidth; if (!spec.vertical) { let centerPaddingAdj = spec.centerMode && parseInt(spec.centerPadding) * 2; @@ -103,15 +116,15 @@ export const initializedState = spec => { } else { slideWidth = listWidth; } - const slideHeight = spec.listRef && getHeight(spec.listRef.querySelector('[data-index="0"]')); + const slideHeight = listNode && getHeight(listNode.querySelector('[data-index="0"]')); const listHeight = slideHeight * spec.slidesToShow; let currentSlide = spec.currentSlide === undefined ? spec.initialSlide : spec.currentSlide; if (spec.rtl && spec.currentSlide === undefined) { currentSlide = slideCount - 1 - spec.initialSlide; } - const lazyLoadedList = spec.lazyLoadedList || []; - const slidesToLoad = getOnDemandLazySlides({ currentSlide, lazyLoadedList }, spec); - lazyLoadedList.concat(slidesToLoad); + let lazyLoadedList = spec.lazyLoadedList || []; + const slidesToLoad = getOnDemandLazySlides({ ...spec, currentSlide, lazyLoadedList }, spec); + lazyLoadedList = lazyLoadedList.concat(slidesToLoad); const state = { slideCount, @@ -139,7 +152,6 @@ export const slideHandler = spec => { infinite, index, slideCount, - lazyLoadedList, lazyLoad, currentSlide, centerMode, @@ -147,6 +159,7 @@ export const slideHandler = spec => { slidesToShow, useCSS, } = spec; + let { lazyLoadedList } = spec; if (waitForAnimate && animating) return {}; let animationSlide = index; let finalSlide; @@ -154,6 +167,7 @@ export const slideHandler = spec => { let finalLeft; let state = {}; let nextState = {}; + const targetSlide = infinite ? index : clamp(index, 0, slideCount - 1); if (fade) { if (!infinite && (index < 0 || index >= slideCount)) return {}; if (index < 0) { @@ -162,14 +176,15 @@ export const slideHandler = spec => { animationSlide = index - slideCount; } if (lazyLoad && lazyLoadedList.indexOf(animationSlide) < 0) { - lazyLoadedList.push(animationSlide); + lazyLoadedList = lazyLoadedList.concat(animationSlide); } state = { animating: true, currentSlide: animationSlide, lazyLoadedList, + targetSlide: animationSlide, }; - nextState = { animating: false }; + nextState = { animating: false, targetSlide: animationSlide }; } else { finalSlide = animationSlide; if (animationSlide < 0) { @@ -188,19 +203,28 @@ export const slideHandler = spec => { if (!infinite) finalSlide = slideCount - slidesToShow; else if (slideCount % slidesToScroll !== 0) finalSlide = 0; } + + if (!infinite && animationSlide + slidesToShow >= slideCount) { + finalSlide = slideCount - slidesToShow; + } + animationLeft = getTrackLeft({ ...spec, slideIndex: animationSlide }); finalLeft = getTrackLeft({ ...spec, slideIndex: finalSlide }); if (!infinite) { if (animationLeft === finalLeft) animationSlide = finalSlide; animationLeft = finalLeft; } - lazyLoad && - lazyLoadedList.concat(getOnDemandLazySlides({ ...spec, currentSlide: animationSlide })); + if (lazyLoad) { + lazyLoadedList = lazyLoadedList.concat( + getOnDemandLazySlides({ ...spec, currentSlide: animationSlide }), + ); + } if (!useCSS) { state = { currentSlide: finalSlide, trackStyle: getTrackCSS({ ...spec, left: finalLeft }), lazyLoadedList, + targetSlide, }; } else { state = { @@ -208,12 +232,14 @@ export const slideHandler = spec => { currentSlide: finalSlide, trackStyle: getTrackAnimateCSS({ ...spec, left: animationLeft }), lazyLoadedList, + targetSlide, }; nextState = { animating: false, currentSlide: finalSlide, trackStyle: getTrackCSS({ ...spec, left: finalLeft }), swipeLeft: null, + targetSlide, }; } } @@ -222,7 +248,15 @@ export const slideHandler = spec => { export const changeSlide = (spec, options) => { let previousInt, slideOffset, targetSlide; - const { slidesToScroll, slidesToShow, slideCount, currentSlide, lazyLoad, infinite } = spec; + const { + slidesToScroll, + slidesToShow, + slideCount, + currentSlide, + targetSlide: previousTargetSlide, + lazyLoad, + infinite, + } = spec; const unevenOffset = slideCount % slidesToScroll !== 0; const indexOffset = unevenOffset ? 0 : (slideCount - currentSlide) % slidesToScroll; @@ -233,24 +267,25 @@ export const changeSlide = (spec, options) => { previousInt = currentSlide - slideOffset; targetSlide = previousInt === -1 ? slideCount - 1 : previousInt; } + if (!infinite) { + targetSlide = previousTargetSlide - slidesToScroll; + } } else if (options.message === 'next') { slideOffset = indexOffset === 0 ? slidesToScroll : indexOffset; targetSlide = currentSlide + slideOffset; if (lazyLoad && !infinite) { targetSlide = ((currentSlide + slidesToScroll) % slideCount) + indexOffset; } + if (!infinite) { + targetSlide = previousTargetSlide + slidesToScroll; + } } else if (options.message === 'dots') { // Click on dots targetSlide = options.index * options.slidesToScroll; - if (targetSlide === options.currentSlide) { - return null; - } } else if (options.message === 'children') { // Click on the slides targetSlide = options.index; - if (targetSlide === options.currentSlide) { - return null; - } + if (infinite) { const direction = siblingDirection({ ...spec, targetSlide }); if (targetSlide > options.currentSlide && direction === 'left') { @@ -261,9 +296,6 @@ export const changeSlide = (spec, options) => { } } else if (options.message === 'index') { targetSlide = Number(options.index); - if (targetSlide === options.currentSlide) { - return null; - } } return targetSlide; }; @@ -277,7 +309,7 @@ export const keyHandler = (e, accessibility, rtl) => { }; export const swipeStart = (e, swipe, draggable) => { - e.target.tagName === 'IMG' && !supportsPassive && e.preventDefault(); + e.target.tagName === 'IMG' && safePreventDefault.preventDefault(); if (!swipe || (!draggable && e.type.indexOf('mouse') !== -1)) return ''; return { dragging: true, @@ -313,11 +345,8 @@ export const swipeMove = (e, spec) => { listWidth, } = spec; if (scrolling) return; - if (animating) { - !supportsPassive && e.preventDefault(); - return; - } - if (vertical && swipeToSlide && verticalSwiping) !supportsPassive && e.preventDefault(); + if (animating) return safePreventDefault(e); + if (vertical && swipeToSlide && verticalSwiping) safePreventDefault(e); let swipeLeft; let state = {}; const curLeft = getTrackLeft(spec); @@ -343,9 +372,9 @@ export const swipeMove = (e, spec) => { let touchSwipeLength = touchObject.swipeLength; if (!infinite) { if ( - (currentSlide === 0 && swipeDirection === 'right') || - (currentSlide + 1 >= dotCount && swipeDirection === 'left') || - (!canGoNext(spec) && swipeDirection === 'left') + (currentSlide === 0 && (swipeDirection === 'right' || swipeDirection === 'down')) || + (currentSlide + 1 >= dotCount && (swipeDirection === 'left' || swipeDirection === 'up')) || + (!canGoNext(spec) && (swipeDirection === 'left' || swipeDirection === 'up')) ) { touchSwipeLength = touchObject.swipeLength * edgeFriction; if (edgeDragged === false && onEdge) { @@ -384,7 +413,7 @@ export const swipeMove = (e, spec) => { } if (touchObject.swipeLength > 10) { state['swiping'] = true; - !supportsPassive && e.preventDefault(); + safePreventDefault(e); } return state; }; @@ -397,13 +426,15 @@ export const swipeEnd = (e, spec) => { touchThreshold, verticalSwiping, listHeight, - currentSlide, swipeToSlide, scrolling, onSwipe, + targetSlide, + currentSlide, + infinite, } = spec; if (!dragging) { - if (swipe) e.preventDefault(); + if (swipe) safePreventDefault(e); return {}; } const minSwipe = verticalSwiping ? listHeight / touchThreshold : listWidth / touchThreshold; @@ -425,26 +456,27 @@ export const swipeEnd = (e, spec) => { return state; } if (touchObject.swipeLength > minSwipe) { - e.preventDefault(); + safePreventDefault(e); if (onSwipe) { onSwipe(swipeDirection); } let slideCount, newSlide; + let activeSlide = infinite ? currentSlide : targetSlide; switch (swipeDirection) { case 'left': case 'up': - newSlide = currentSlide + getSlideCount(spec); + newSlide = activeSlide + getSlideCount(spec); slideCount = swipeToSlide ? checkNavigable(spec, newSlide) : newSlide; state['currentDirection'] = 0; break; case 'right': case 'down': - newSlide = currentSlide - getSlideCount(spec); + newSlide = activeSlide - getSlideCount(spec); slideCount = swipeToSlide ? checkNavigable(spec, newSlide) : newSlide; state['currentDirection'] = 1; break; default: - slideCount = currentSlide; + slideCount = activeSlide; } state['triggerSlideHandler'] = slideCount; } else { @@ -487,7 +519,7 @@ export const getSlideCount = spec => { if (spec.swipeToSlide) { let swipedSlide; const slickList = spec.listRef; - const slides = slickList.querySelectorAll('.slick-slide'); + const slides = (slickList.querySelectorAll && slickList.querySelectorAll('.slick-slide')) || []; Array.from(slides).every(slide => { if (!spec.vertical) { if (slide.offsetLeft - centerOffset + getWidth(slide) / 2 > spec.swipeLeft * -1) {