refactor: carousel

refactor-carousel
tangjinzhou 2022-02-24 21:48:58 +08:00
parent 5edc8cadca
commit 7a4965f953
11 changed files with 351 additions and 171 deletions

View File

@ -1,4 +1,4 @@
import type { ExtractPropTypes } from 'vue'; import type { ExtractPropTypes, PropType } from 'vue';
import { defineComponent, inject } from 'vue'; import { defineComponent, inject } from 'vue';
import PropTypes from '../_util/vue-types'; import PropTypes from '../_util/vue-types';
import debounce from 'lodash-es/debounce'; import debounce from 'lodash-es/debounce';
@ -7,58 +7,73 @@ import { defaultConfigProvider } from '../config-provider';
import warning from '../_util/warning'; import warning from '../_util/warning';
import classNames from '../_util/classNames'; import classNames from '../_util/classNames';
import SlickCarousel from '../vc-slick/src'; 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 // Carousel
export const carouselProps = { export const carouselProps = () => ({
effect: PropTypes.oneOf(tuple('scrollx', 'fade')), effect: String as PropType<CarouselEffect>,
dots: PropTypes.looseBool.def(true), dots: { type: Boolean, default: true },
vertical: PropTypes.looseBool, vertical: { type: Boolean, default: undefined },
autoplay: PropTypes.looseBool, autoplay: { type: Boolean, default: undefined },
easing: PropTypes.string, easing: String,
beforeChange: PropTypes.func, beforeChange: Function as PropType<(currentSlide: number, nextSlide: number) => void>,
afterChange: PropTypes.func, afterChange: Function as PropType<(currentSlide: number) => void>,
// style: PropTypes.React.CSSProperties, // style: PropTypes.React.CSSProperties,
prefixCls: PropTypes.string, prefixCls: String,
accessibility: PropTypes.looseBool, accessibility: { type: Boolean, default: undefined },
nextArrow: PropTypes.any, nextArrow: PropTypes.any,
prevArrow: PropTypes.any, prevArrow: PropTypes.any,
pauseOnHover: PropTypes.looseBool, pauseOnHover: { type: Boolean, default: undefined },
// className: PropTypes.string, // className: String,
adaptiveHeight: PropTypes.looseBool, adaptiveHeight: { type: Boolean, default: undefined },
arrows: PropTypes.looseBool.def(false), arrows: { type: Boolean, default: false },
autoplaySpeed: PropTypes.number, autoplaySpeed: Number,
centerMode: PropTypes.looseBool, centerMode: { type: Boolean, default: undefined },
centerPadding: PropTypes.string, centerPadding: String,
cssEase: PropTypes.string, cssEase: String,
dotsClass: PropTypes.string, dotsClass: String,
draggable: PropTypes.looseBool.def(false), draggable: { type: Boolean, default: false },
fade: PropTypes.looseBool, fade: { type: Boolean, default: undefined },
focusOnSelect: PropTypes.looseBool, focusOnSelect: { type: Boolean, default: undefined },
infinite: PropTypes.looseBool, infinite: { type: Boolean, default: undefined },
initialSlide: PropTypes.number, initialSlide: Number,
lazyLoad: PropTypes.looseBool, lazyLoad: String as PropType<LazyLoadTypes>,
rtl: PropTypes.looseBool, rtl: { type: Boolean, default: undefined },
slide: PropTypes.string, slide: String,
slidesToShow: PropTypes.number, slidesToShow: Number,
slidesToScroll: PropTypes.number, slidesToScroll: Number,
speed: PropTypes.number, speed: Number,
swipe: PropTypes.looseBool, swipe: { type: Boolean, default: undefined },
swipeToSlide: PropTypes.looseBool, swipeToSlide: { type: Boolean, default: undefined },
touchMove: PropTypes.looseBool, touchMove: { type: Boolean, default: undefined },
touchThreshold: PropTypes.number, touchThreshold: Number,
variableWidth: PropTypes.looseBool, variableWidth: { type: Boolean, default: undefined },
useCSS: PropTypes.looseBool, useCSS: { type: Boolean, default: undefined },
slickGoTo: PropTypes.number, slickGoTo: Number,
responsive: PropTypes.array, responsive: Array,
dotPosition: PropTypes.oneOf(tuple('top', 'bottom', 'left', 'right')), dotPosition: String as PropType<DotPosition>,
verticalSwiping: PropTypes.looseBool.def(false), verticalSwiping: { type: Boolean, default: false },
}; });
export type CarouselProps = Partial<ExtractPropTypes<typeof carouselProps>>; export type CarouselProps = Partial<ExtractPropTypes<ReturnType<typeof carouselProps>>>;
const Carousel = defineComponent({ const Carousel = defineComponent({
name: 'ACarousel', name: 'ACarousel',
inheritAttrs: false, inheritAttrs: false,
props: carouselProps, props: carouselProps(),
setup() { setup() {
return { return {
configProvider: inject('configProvider', defaultConfigProvider), configProvider: inject('configProvider', defaultConfigProvider),

View File

@ -1,18 +1,20 @@
@import '../../style/themes/index'; @import '../../style/themes/index';
@import '../../style/mixins/index'; @import '../../style/mixins/index';
.@{ant-prefix}-carousel { @carousel-prefix-cls: ~'@{ant-prefix}-carousel';
.@{carousel-prefix-cls} {
.reset-component(); .reset-component();
.slick-slider { .slick-slider {
position: relative; position: relative;
display: block; display: block;
box-sizing: border-box; box-sizing: border-box;
-webkit-touch-callout: none;
-ms-touch-action: pan-y;
touch-action: pan-y; touch-action: pan-y;
-webkit-touch-callout: none;
-webkit-tap-highlight-color: transparent; -webkit-tap-highlight-color: transparent;
} }
.slick-list { .slick-list {
position: relative; position: relative;
display: block; display: block;
@ -45,11 +47,20 @@
visibility: visible; 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-track,
.slick-slider .slick-list { .slick-slider .slick-list {
transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0);
touch-action: pan-y;
} }
.slick-track { .slick-track {
@ -72,17 +83,17 @@
visibility: hidden; visibility: hidden;
} }
} }
.slick-slide { .slick-slide {
display: none; display: none;
float: left; float: left;
height: 100%; height: 100%;
min-height: 1px; min-height: 1px;
[dir='rtl'] & {
float: right;
}
img { img {
display: block; display: block;
} }
&.slick-loading img { &.slick-loading img {
display: none; display: none;
} }
@ -103,8 +114,8 @@
.slick-vertical .slick-slide { .slick-vertical .slick-slide {
display: block; display: block;
height: auto; height: auto;
border: @border-width-base @border-style-base transparent;
} }
.slick-arrow.slick-hidden { .slick-arrow.slick-hidden {
display: none; display: none;
} }
@ -126,15 +137,18 @@
border: 0; border: 0;
outline: none; outline: none;
cursor: pointer; cursor: pointer;
&:hover, &:hover,
&:focus { &:focus {
color: transparent; color: transparent;
background: transparent; background: transparent;
outline: none; outline: none;
&::before { &::before {
opacity: 1; opacity: 1;
} }
} }
&.slick-disabled::before { &.slick-disabled::before {
opacity: 0.25; opacity: 0.25;
} }
@ -142,6 +156,7 @@
.slick-prev { .slick-prev {
left: -25px; left: -25px;
&::before { &::before {
content: '←'; content: '←';
} }
@ -149,6 +164,7 @@
.slick-next { .slick-next {
right: -25px; right: -25px;
&::before { &::before {
content: '→'; content: '→';
} }
@ -157,29 +173,45 @@
// Dots // Dots
.slick-dots { .slick-dots {
position: absolute; position: absolute;
display: block; right: 0;
width: 100%; bottom: 0;
height: @carousel-dot-height; left: 0;
margin: 0; z-index: 15;
padding: 0; display: flex !important;
text-align: center; justify-content: center;
margin-right: 15%;
margin-left: 15%;
padding-left: 0;
list-style: none; list-style: none;
&-bottom { &-bottom {
bottom: 12px; bottom: 12px;
} }
&-top { &-top {
top: 12px; top: 12px;
bottom: auto;
} }
li { li {
position: relative; position: relative;
display: inline-block; display: inline-block;
flex: 0 1 auto;
box-sizing: content-box;
width: @carousel-dot-width;
height: @carousel-dot-height;
margin: 0 2px; margin: 0 2px;
margin-right: 3px;
margin-left: 3px;
padding: 0; padding: 0;
text-align: center; text-align: center;
text-indent: -999px;
vertical-align: top; vertical-align: top;
transition: all 0.5s;
button { button {
display: block; display: block;
width: @carousel-dot-width; width: 100%;
height: @carousel-dot-height; height: @carousel-dot-height;
padding: 0; padding: 0;
color: transparent; color: transparent;
@ -191,15 +223,21 @@
cursor: pointer; cursor: pointer;
opacity: 0.3; opacity: 0.3;
transition: all 0.5s; transition: all 0.5s;
&:hover, &:hover,
&:focus { &:focus {
opacity: 0.75; opacity: 0.75;
} }
} }
&.slick-active button {
&.slick-active {
width: @carousel-dot-active-width; width: @carousel-dot-active-width;
background: @component-background;
opacity: 1; & button {
background: @component-background;
opacity: 1;
}
&:hover, &:hover,
&:focus { &:focus {
opacity: 1; opacity: 1;
@ -213,26 +251,44 @@
.slick-dots { .slick-dots {
top: 50%; top: 50%;
bottom: auto; bottom: auto;
flex-direction: column;
width: @carousel-dot-height; width: @carousel-dot-height;
height: auto; height: auto;
margin: 0;
transform: translateY(-50%); transform: translateY(-50%);
&-left { &-left {
right: auto;
left: 12px; left: 12px;
} }
&-right { &-right {
right: 12px; right: 12px;
left: auto;
} }
li { li {
margin: 0 2px; width: @carousel-dot-height;
height: @carousel-dot-width;
margin: 4px 2px;
vertical-align: baseline; vertical-align: baseline;
button { button {
width: @carousel-dot-height; width: @carousel-dot-height;
height: @carousel-dot-width; height: @carousel-dot-width;
} }
&.slick-active button {
&.slick-active {
width: @carousel-dot-height; width: @carousel-dot-height;
height: @carousel-dot-active-width; height: @carousel-dot-active-width;
button {
width: @carousel-dot-height;
height: @carousel-dot-active-width;
}
} }
} }
} }
} }
@import './rtl';

View File

@ -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;
}
}
}

View File

@ -1,5 +1,6 @@
import classnames from '../../_util/classNames'; import classnames from '../../_util/classNames';
import { cloneElement } from '../../_util/vnode'; import { cloneElement } from '../../_util/vnode';
import { clamp } from './utils/innerSliderUtils';
const getDotCount = function (spec) { const getDotCount = function (spec) {
let dots; let dots;
@ -39,24 +40,26 @@ const Dots = (_, { attrs }) => {
// //
// Credit: http://stackoverflow.com/a/13735425/1849458 // Credit: http://stackoverflow.com/a/13735425/1849458
const mouseEvents = { onMouseenter, onMouseover, onMouseleave }; const mouseEvents = { onMouseenter, onMouseover, onMouseleave };
const dots = Array.apply( let dots = [];
null, for (let i = 0; i < dotCount; i++) {
Array(dotCount + 1) let _rightBound = (i + 1) * slidesToScroll - 1;
.join('0') let rightBound = infinite ? _rightBound : clamp(_rightBound, 0, slideCount - 1);
.split(''), let _leftBound = rightBound - (slidesToScroll - 1);
).map((x, i) => { let leftBound = infinite ? _leftBound : clamp(_leftBound, 0, slideCount - 1);
const leftBound = i * slidesToScroll;
const rightBound = i * slidesToScroll + (slidesToScroll - 1); let className = classnames({
const className = classnames({ 'slick-active': infinite
'slick-active': currentSlide >= leftBound && currentSlide <= rightBound, ? currentSlide >= leftBound && currentSlide <= rightBound
: currentSlide === leftBound,
}); });
const dotOptions = { let dotOptions = {
message: 'dots', message: 'dots',
index: i, index: i,
slidesToScroll, slidesToScroll,
currentSlide, currentSlide,
}; };
function onClick(e) { function onClick(e) {
// In Autoplay the focus stays on clicked button even after transition // In Autoplay the focus stays on clicked button even after transition
// to next slide. That only goes away by click somewhere outside // to next slide. That only goes away by click somewhere outside
@ -65,14 +68,12 @@ const Dots = (_, { attrs }) => {
} }
clickHandler(dotOptions); clickHandler(dotOptions);
} }
return ( dots = dots.concat(
<li key={i} class={className}> <li key={i} class={className}>
{cloneElement(customPaging({ i }), { {cloneElement(customPaging({ i }), { onClick })}
onClick, </li>,
})}
</li>
); );
}); }
return cloneElement(appendDots({ dots }), { return cloneElement(appendDots({ dots }), {
class: dotsClass, class: dotsClass,

View File

@ -1,4 +1,4 @@
// base react-slick 0.23.2 // base react-slick 0.28.2
import Slider from './slider'; import Slider from './slider';
export default Slider; export default Slider;

View File

@ -21,6 +21,7 @@ const initialState = {
touchObject: { startX: 0, startY: 0, curX: 0, curY: 0 }, touchObject: { startX: 0, startY: 0, curX: 0, curY: 0 },
trackStyle: {}, trackStyle: {},
trackWidth: 0, trackWidth: 0,
targetSlide: 0,
}; };
export default initialState; export default initialState;

View File

@ -42,10 +42,12 @@ export default {
this.callbackTimers = []; this.callbackTimers = [];
this.clickable = true; this.clickable = true;
this.debouncedResize = null; this.debouncedResize = null;
const ssrState = this.ssrInit();
return { return {
...initialState, ...initialState,
currentSlide: this.initialSlide, currentSlide: this.initialSlide,
slideCount: this.children.length, slideCount: this.children.length,
...ssrState,
}; };
}, },
watch: { watch: {
@ -83,7 +85,9 @@ export default {
currentSlide: this.currentSlide, currentSlide: this.currentSlide,
}); });
} }
if (nextProps.autoplay) { if (!this.preProps.autoplay && nextProps.autoplay) {
this.handleAutoPlay('playing');
} else if (nextProps.autoplay) {
this.handleAutoPlay('update'); this.handleAutoPlay('update');
} else { } else {
this.pause('paused'); this.pause('paused');
@ -92,8 +96,7 @@ export default {
this.preProps = { ...nextProps }; this.preProps = { ...nextProps };
}, },
}, },
beforeMount() { mounted() {
this.ssrInit();
this.__emit('init'); this.__emit('init');
if (this.lazyLoad) { if (this.lazyLoad) {
const slidesToLoad = getOnDemandLazySlides({ const slidesToLoad = getOnDemandLazySlides({
@ -107,8 +110,6 @@ export default {
this.__emit('lazyLoad', slidesToLoad); this.__emit('lazyLoad', slidesToLoad);
} }
} }
},
mounted() {
this.$nextTick(() => { this.$nextTick(() => {
const spec = { const spec = {
listRef: this.list, listRef: this.list,
@ -118,7 +119,7 @@ export default {
}; };
this.updateState(spec, true, () => { this.updateState(spec, true, () => {
this.adaptHeight(); this.adaptHeight();
this.autoplay && this.handleAutoPlay('update'); this.autoplay && this.handleAutoPlay('playing');
}); });
if (this.lazyLoad === 'progressive') { if (this.lazyLoad === 'progressive') {
this.lazyLoadTimer = setInterval(this.progressiveLazyLoad, 1000); this.lazyLoadTimer = setInterval(this.progressiveLazyLoad, 1000);
@ -132,14 +133,11 @@ export default {
} }
}); });
this.ro.observe(this.list); this.ro.observe(this.list);
Array.prototype.forEach.call(document.querySelectorAll('.slick-slide'), slide => { document.querySelectorAll &&
slide.onfocus = this.$props.pauseOnFocus ? this.onSlideFocus : null; Array.prototype.forEach.call(document.querySelectorAll('.slick-slide'), slide => {
slide.onblur = this.$props.pauseOnFocus ? this.onSlideBlur : null; slide.onfocus = this.$props.pauseOnFocus ? this.onSlideFocus : null;
}); slide.onblur = this.$props.pauseOnFocus ? this.onSlideBlur : null;
// To support server-side rendering });
if (!window) {
return;
}
if (window.addEventListener) { if (window.addEventListener) {
window.addEventListener('resize', this.onWindowResized); window.addEventListener('resize', this.onWindowResized);
} else { } else {
@ -166,6 +164,7 @@ export default {
if (this.autoplayTimer) { if (this.autoplayTimer) {
clearInterval(this.autoplayTimer); clearInterval(this.autoplayTimer);
} }
this.ro.disconnect();
}, },
updated() { updated() {
this.checkImagesLoad(); this.checkImagesLoad();
@ -206,7 +205,8 @@ export default {
this.debouncedResize(); this.debouncedResize();
}, },
resizeWindow(setTrackStyle = true) { resizeWindow(setTrackStyle = true) {
if (!this.track) return; const isTrackMounted = Boolean(this.track);
if (!isTrackMounted) return;
const spec = { const spec = {
listRef: this.list, listRef: this.list,
trackRef: this.track, trackRef: this.track,
@ -278,10 +278,9 @@ export default {
const currentWidth = `${childrenWidths[this.currentSlide]}px`; const currentWidth = `${childrenWidths[this.currentSlide]}px`;
trackStyle.left = `calc(${trackStyle.left} + (100% - ${currentWidth}) / 2 ) `; trackStyle.left = `calc(${trackStyle.left} + (100% - ${currentWidth}) / 2 ) `;
} }
this.setState({ return {
trackStyle, trackStyle,
}); };
return;
} }
const childrenCount = children.length; const childrenCount = children.length;
const spec = { ...this.$props, ...this.$data, slideCount: childrenCount }; const spec = { ...this.$props, ...this.$data, slideCount: childrenCount };
@ -296,13 +295,17 @@ export default {
width: trackWidth + '%', width: trackWidth + '%',
left: trackLeft + '%', left: trackLeft + '%',
}; };
this.setState({ return {
slideWidth: slideWidth + '%', slideWidth: slideWidth + '%',
trackStyle, trackStyle,
}); };
}, },
checkImagesLoad() { 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; const imagesCount = images.length;
let loadedCount = 0; let loadedCount = 0;
Array.prototype.forEach.call(images, image => { Array.prototype.forEach.call(images, image => {
@ -376,10 +379,16 @@ export default {
if (this.$attrs.onLazyLoad && slidesToLoad.length > 0) { if (this.$attrs.onLazyLoad && slidesToLoad.length > 0) {
this.__emit('lazyLoad', slidesToLoad); this.__emit('lazyLoad', slidesToLoad);
} }
if (!this.$props.waitForAnimate && this.animationEndCallback) {
clearTimeout(this.animationEndCallback);
afterChange && afterChange(currentSlide);
delete this.animationEndCallback;
}
this.setState(state, () => { this.setState(state, () => {
asNavFor && if (asNavFor && this.asNavForIndex !== index) {
asNavFor.innerSlider.currentSlide !== currentSlide && this.asNavForIndex = index;
asNavFor.innerSlider.slideHandler(index); asNavFor.innerSlider.slideHandler(index);
}
if (!nextState) return; if (!nextState) return;
this.animationEndCallback = setTimeout(() => { this.animationEndCallback = setTimeout(() => {
const { animating, ...firstBatch } = nextState; const { animating, ...firstBatch } = nextState;
@ -400,6 +409,11 @@ export default {
} else { } else {
this.slideHandler(targetSlide); 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) { clickHandler(e) {
if (this.clickable === false) { if (this.clickable === false) {
@ -465,6 +479,10 @@ export default {
this.enableBodyScroll(); this.enableBodyScroll();
} }
}, },
touchEnd(e) {
this.swipeEnd(e);
this.clickable = true;
},
slickPrev() { slickPrev() {
// this and fellow methods are wrapped in setTimeout // this and fellow methods are wrapped in setTimeout
// to make sure initialize setState has happened before // to make sure initialize setState has happened before
@ -599,11 +617,13 @@ export default {
'variableWidth', 'variableWidth',
'unslick', 'unslick',
'centerPadding', 'centerPadding',
'targetSlide',
'useCSS',
]); ]);
const { pauseOnHover } = this.$props; const { pauseOnHover } = this.$props;
trackProps = { trackProps = {
...trackProps, ...trackProps,
focusOnSelect: this.focusOnSelect ? this.selectHandler : null, focusOnSelect: this.focusOnSelect && this.clickable ? this.selectHandler : null,
ref: this.trackRefHandler, ref: this.trackRefHandler,
onMouseleave: pauseOnHover ? this.onTrackLeave : noop, onMouseleave: pauseOnHover ? this.onTrackLeave : noop,
onMouseover: pauseOnHover ? this.onTrackOver : noop, onMouseover: pauseOnHover ? this.onTrackOver : noop,
@ -701,14 +721,15 @@ export default {
: noop, : noop,
[supportsPassive ? 'onTouchmovePassive' : 'onTouchmove']: [supportsPassive ? 'onTouchmovePassive' : 'onTouchmove']:
this.dragging && touchMove ? this.swipeMove : noop, this.dragging && touchMove ? this.swipeMove : noop,
onTouchend: touchMove ? this.swipeEnd : noop, onTouchend: touchMove ? this.touchEnd : noop,
onTouchcancel: this.dragging && touchMove ? this.swipeEnd : noop, onTouchcancel: this.dragging && touchMove ? this.swipeEnd : noop,
onKeydown: this.accessibility ? this.keyHandler : noop, onKeydown: this.accessibility ? this.keyHandler : noop,
}; };
let innerSliderProps = { let innerSliderProps = {
class: className, class: className,
// dir: 'ltr', dir: 'ltr',
style: this.$attrs.style,
}; };
if (this.unslick) { if (this.unslick) {

View File

@ -21,7 +21,7 @@ export default defineComponent({
}; };
}, },
// handles responsive breakpoints // handles responsive breakpoints
beforeMount() { mounted() {
if (this.responsive) { if (this.responsive) {
const breakpoints = this.responsive.map(breakpt => breakpt.breakpoint); const breakpoints = this.responsive.map(breakpt => breakpt.breakpoint);
// sort them in increasing order of their numerical value // sort them in increasing order of their numerical value
@ -185,7 +185,7 @@ export default defineComponent({
if (settings === 'unslick') { if (settings === 'unslick') {
const className = 'regular slider ' + (this.className || ''); const className = 'regular slider ' + (this.className || '');
return <div class={className}>{newChildren}</div>; return <div class={className}>{children}</div>;
} else if (newChildren.length <= settings.slidesToShow) { } else if (newChildren.length <= settings.slidesToShow) {
settings.unslick = true; settings.unslick = true;
} }

View File

@ -24,7 +24,15 @@ const getSlideClasses = spec => {
} else { } else {
slickActive = spec.currentSlide <= index && index < spec.currentSlide + spec.slidesToShow; 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 { return {
'slick-slide': true, 'slick-slide': true,
'slick-active': slickActive, 'slick-active': slickActive,
@ -49,32 +57,24 @@ const getSlideStyle = function (spec) {
style.left = -spec.index * parseInt(spec.slideWidth) + 'px'; style.left = -spec.index * parseInt(spec.slideWidth) + 'px';
} }
style.opacity = spec.currentSlide === spec.index ? 1 : 0; style.opacity = spec.currentSlide === spec.index ? 1 : 0;
style.transition = if (spec.useCSS) {
'opacity ' + style.transition =
spec.speed + 'opacity ' +
'ms ' + spec.speed +
spec.cssEase + 'ms ' +
', ' + spec.cssEase +
'visibility ' + ', ' +
spec.speed + 'visibility ' +
'ms ' + spec.speed +
spec.cssEase; 'ms ' +
style.WebkitTransition = spec.cssEase;
'opacity ' + }
spec.speed +
'ms ' +
spec.cssEase +
', ' +
'visibility ' +
spec.speed +
'ms ' +
spec.cssEase;
} }
return style; 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) { const renderSlides = function (spec, children) {
let key; let key;

View File

@ -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 => { export const getOnDemandLazySlides = spec => {
const onDemandSlides = []; const onDemandSlides = [];
@ -91,8 +102,10 @@ export const extractObject = (spec, keys) => {
export const initializedState = spec => { export const initializedState = spec => {
// spec also contains listRef, trackRef // spec also contains listRef, trackRef
const slideCount = spec.children.length; const slideCount = spec.children.length;
const listWidth = Math.ceil(getWidth(spec.listRef)); const listNode = spec.listRef;
const trackWidth = Math.ceil(getWidth(spec.trackRef)); const listWidth = Math.ceil(getWidth(listNode));
const trackNode = spec.trackRef;
const trackWidth = Math.ceil(getWidth(trackNode));
let slideWidth; let slideWidth;
if (!spec.vertical) { if (!spec.vertical) {
let centerPaddingAdj = spec.centerMode && parseInt(spec.centerPadding) * 2; let centerPaddingAdj = spec.centerMode && parseInt(spec.centerPadding) * 2;
@ -103,15 +116,15 @@ export const initializedState = spec => {
} else { } else {
slideWidth = listWidth; 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; const listHeight = slideHeight * spec.slidesToShow;
let currentSlide = spec.currentSlide === undefined ? spec.initialSlide : spec.currentSlide; let currentSlide = spec.currentSlide === undefined ? spec.initialSlide : spec.currentSlide;
if (spec.rtl && spec.currentSlide === undefined) { if (spec.rtl && spec.currentSlide === undefined) {
currentSlide = slideCount - 1 - spec.initialSlide; currentSlide = slideCount - 1 - spec.initialSlide;
} }
const lazyLoadedList = spec.lazyLoadedList || []; let lazyLoadedList = spec.lazyLoadedList || [];
const slidesToLoad = getOnDemandLazySlides({ currentSlide, lazyLoadedList }, spec); const slidesToLoad = getOnDemandLazySlides({ ...spec, currentSlide, lazyLoadedList }, spec);
lazyLoadedList.concat(slidesToLoad); lazyLoadedList = lazyLoadedList.concat(slidesToLoad);
const state = { const state = {
slideCount, slideCount,
@ -139,7 +152,6 @@ export const slideHandler = spec => {
infinite, infinite,
index, index,
slideCount, slideCount,
lazyLoadedList,
lazyLoad, lazyLoad,
currentSlide, currentSlide,
centerMode, centerMode,
@ -147,6 +159,7 @@ export const slideHandler = spec => {
slidesToShow, slidesToShow,
useCSS, useCSS,
} = spec; } = spec;
let { lazyLoadedList } = spec;
if (waitForAnimate && animating) return {}; if (waitForAnimate && animating) return {};
let animationSlide = index; let animationSlide = index;
let finalSlide; let finalSlide;
@ -154,6 +167,7 @@ export const slideHandler = spec => {
let finalLeft; let finalLeft;
let state = {}; let state = {};
let nextState = {}; let nextState = {};
const targetSlide = infinite ? index : clamp(index, 0, slideCount - 1);
if (fade) { if (fade) {
if (!infinite && (index < 0 || index >= slideCount)) return {}; if (!infinite && (index < 0 || index >= slideCount)) return {};
if (index < 0) { if (index < 0) {
@ -162,14 +176,15 @@ export const slideHandler = spec => {
animationSlide = index - slideCount; animationSlide = index - slideCount;
} }
if (lazyLoad && lazyLoadedList.indexOf(animationSlide) < 0) { if (lazyLoad && lazyLoadedList.indexOf(animationSlide) < 0) {
lazyLoadedList.push(animationSlide); lazyLoadedList = lazyLoadedList.concat(animationSlide);
} }
state = { state = {
animating: true, animating: true,
currentSlide: animationSlide, currentSlide: animationSlide,
lazyLoadedList, lazyLoadedList,
targetSlide: animationSlide,
}; };
nextState = { animating: false }; nextState = { animating: false, targetSlide: animationSlide };
} else { } else {
finalSlide = animationSlide; finalSlide = animationSlide;
if (animationSlide < 0) { if (animationSlide < 0) {
@ -188,19 +203,28 @@ export const slideHandler = spec => {
if (!infinite) finalSlide = slideCount - slidesToShow; if (!infinite) finalSlide = slideCount - slidesToShow;
else if (slideCount % slidesToScroll !== 0) finalSlide = 0; else if (slideCount % slidesToScroll !== 0) finalSlide = 0;
} }
if (!infinite && animationSlide + slidesToShow >= slideCount) {
finalSlide = slideCount - slidesToShow;
}
animationLeft = getTrackLeft({ ...spec, slideIndex: animationSlide }); animationLeft = getTrackLeft({ ...spec, slideIndex: animationSlide });
finalLeft = getTrackLeft({ ...spec, slideIndex: finalSlide }); finalLeft = getTrackLeft({ ...spec, slideIndex: finalSlide });
if (!infinite) { if (!infinite) {
if (animationLeft === finalLeft) animationSlide = finalSlide; if (animationLeft === finalLeft) animationSlide = finalSlide;
animationLeft = finalLeft; animationLeft = finalLeft;
} }
lazyLoad && if (lazyLoad) {
lazyLoadedList.concat(getOnDemandLazySlides({ ...spec, currentSlide: animationSlide })); lazyLoadedList = lazyLoadedList.concat(
getOnDemandLazySlides({ ...spec, currentSlide: animationSlide }),
);
}
if (!useCSS) { if (!useCSS) {
state = { state = {
currentSlide: finalSlide, currentSlide: finalSlide,
trackStyle: getTrackCSS({ ...spec, left: finalLeft }), trackStyle: getTrackCSS({ ...spec, left: finalLeft }),
lazyLoadedList, lazyLoadedList,
targetSlide,
}; };
} else { } else {
state = { state = {
@ -208,12 +232,14 @@ export const slideHandler = spec => {
currentSlide: finalSlide, currentSlide: finalSlide,
trackStyle: getTrackAnimateCSS({ ...spec, left: animationLeft }), trackStyle: getTrackAnimateCSS({ ...spec, left: animationLeft }),
lazyLoadedList, lazyLoadedList,
targetSlide,
}; };
nextState = { nextState = {
animating: false, animating: false,
currentSlide: finalSlide, currentSlide: finalSlide,
trackStyle: getTrackCSS({ ...spec, left: finalLeft }), trackStyle: getTrackCSS({ ...spec, left: finalLeft }),
swipeLeft: null, swipeLeft: null,
targetSlide,
}; };
} }
} }
@ -222,7 +248,15 @@ export const slideHandler = spec => {
export const changeSlide = (spec, options) => { export const changeSlide = (spec, options) => {
let previousInt, slideOffset, targetSlide; 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 unevenOffset = slideCount % slidesToScroll !== 0;
const indexOffset = unevenOffset ? 0 : (slideCount - currentSlide) % slidesToScroll; const indexOffset = unevenOffset ? 0 : (slideCount - currentSlide) % slidesToScroll;
@ -233,24 +267,25 @@ export const changeSlide = (spec, options) => {
previousInt = currentSlide - slideOffset; previousInt = currentSlide - slideOffset;
targetSlide = previousInt === -1 ? slideCount - 1 : previousInt; targetSlide = previousInt === -1 ? slideCount - 1 : previousInt;
} }
if (!infinite) {
targetSlide = previousTargetSlide - slidesToScroll;
}
} else if (options.message === 'next') { } else if (options.message === 'next') {
slideOffset = indexOffset === 0 ? slidesToScroll : indexOffset; slideOffset = indexOffset === 0 ? slidesToScroll : indexOffset;
targetSlide = currentSlide + slideOffset; targetSlide = currentSlide + slideOffset;
if (lazyLoad && !infinite) { if (lazyLoad && !infinite) {
targetSlide = ((currentSlide + slidesToScroll) % slideCount) + indexOffset; targetSlide = ((currentSlide + slidesToScroll) % slideCount) + indexOffset;
} }
if (!infinite) {
targetSlide = previousTargetSlide + slidesToScroll;
}
} else if (options.message === 'dots') { } else if (options.message === 'dots') {
// Click on dots // Click on dots
targetSlide = options.index * options.slidesToScroll; targetSlide = options.index * options.slidesToScroll;
if (targetSlide === options.currentSlide) {
return null;
}
} else if (options.message === 'children') { } else if (options.message === 'children') {
// Click on the slides // Click on the slides
targetSlide = options.index; targetSlide = options.index;
if (targetSlide === options.currentSlide) {
return null;
}
if (infinite) { if (infinite) {
const direction = siblingDirection({ ...spec, targetSlide }); const direction = siblingDirection({ ...spec, targetSlide });
if (targetSlide > options.currentSlide && direction === 'left') { if (targetSlide > options.currentSlide && direction === 'left') {
@ -261,9 +296,6 @@ export const changeSlide = (spec, options) => {
} }
} else if (options.message === 'index') { } else if (options.message === 'index') {
targetSlide = Number(options.index); targetSlide = Number(options.index);
if (targetSlide === options.currentSlide) {
return null;
}
} }
return targetSlide; return targetSlide;
}; };
@ -277,7 +309,7 @@ export const keyHandler = (e, accessibility, rtl) => {
}; };
export const swipeStart = (e, swipe, draggable) => { 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 ''; if (!swipe || (!draggable && e.type.indexOf('mouse') !== -1)) return '';
return { return {
dragging: true, dragging: true,
@ -313,11 +345,8 @@ export const swipeMove = (e, spec) => {
listWidth, listWidth,
} = spec; } = spec;
if (scrolling) return; if (scrolling) return;
if (animating) { if (animating) return safePreventDefault(e);
!supportsPassive && e.preventDefault(); if (vertical && swipeToSlide && verticalSwiping) safePreventDefault(e);
return;
}
if (vertical && swipeToSlide && verticalSwiping) !supportsPassive && e.preventDefault();
let swipeLeft; let swipeLeft;
let state = {}; let state = {};
const curLeft = getTrackLeft(spec); const curLeft = getTrackLeft(spec);
@ -343,9 +372,9 @@ export const swipeMove = (e, spec) => {
let touchSwipeLength = touchObject.swipeLength; let touchSwipeLength = touchObject.swipeLength;
if (!infinite) { if (!infinite) {
if ( if (
(currentSlide === 0 && swipeDirection === 'right') || (currentSlide === 0 && (swipeDirection === 'right' || swipeDirection === 'down')) ||
(currentSlide + 1 >= dotCount && swipeDirection === 'left') || (currentSlide + 1 >= dotCount && (swipeDirection === 'left' || swipeDirection === 'up')) ||
(!canGoNext(spec) && swipeDirection === 'left') (!canGoNext(spec) && (swipeDirection === 'left' || swipeDirection === 'up'))
) { ) {
touchSwipeLength = touchObject.swipeLength * edgeFriction; touchSwipeLength = touchObject.swipeLength * edgeFriction;
if (edgeDragged === false && onEdge) { if (edgeDragged === false && onEdge) {
@ -384,7 +413,7 @@ export const swipeMove = (e, spec) => {
} }
if (touchObject.swipeLength > 10) { if (touchObject.swipeLength > 10) {
state['swiping'] = true; state['swiping'] = true;
!supportsPassive && e.preventDefault(); safePreventDefault(e);
} }
return state; return state;
}; };
@ -397,13 +426,15 @@ export const swipeEnd = (e, spec) => {
touchThreshold, touchThreshold,
verticalSwiping, verticalSwiping,
listHeight, listHeight,
currentSlide,
swipeToSlide, swipeToSlide,
scrolling, scrolling,
onSwipe, onSwipe,
targetSlide,
currentSlide,
infinite,
} = spec; } = spec;
if (!dragging) { if (!dragging) {
if (swipe) e.preventDefault(); if (swipe) safePreventDefault(e);
return {}; return {};
} }
const minSwipe = verticalSwiping ? listHeight / touchThreshold : listWidth / touchThreshold; const minSwipe = verticalSwiping ? listHeight / touchThreshold : listWidth / touchThreshold;
@ -425,26 +456,27 @@ export const swipeEnd = (e, spec) => {
return state; return state;
} }
if (touchObject.swipeLength > minSwipe) { if (touchObject.swipeLength > minSwipe) {
e.preventDefault(); safePreventDefault(e);
if (onSwipe) { if (onSwipe) {
onSwipe(swipeDirection); onSwipe(swipeDirection);
} }
let slideCount, newSlide; let slideCount, newSlide;
let activeSlide = infinite ? currentSlide : targetSlide;
switch (swipeDirection) { switch (swipeDirection) {
case 'left': case 'left':
case 'up': case 'up':
newSlide = currentSlide + getSlideCount(spec); newSlide = activeSlide + getSlideCount(spec);
slideCount = swipeToSlide ? checkNavigable(spec, newSlide) : newSlide; slideCount = swipeToSlide ? checkNavigable(spec, newSlide) : newSlide;
state['currentDirection'] = 0; state['currentDirection'] = 0;
break; break;
case 'right': case 'right':
case 'down': case 'down':
newSlide = currentSlide - getSlideCount(spec); newSlide = activeSlide - getSlideCount(spec);
slideCount = swipeToSlide ? checkNavigable(spec, newSlide) : newSlide; slideCount = swipeToSlide ? checkNavigable(spec, newSlide) : newSlide;
state['currentDirection'] = 1; state['currentDirection'] = 1;
break; break;
default: default:
slideCount = currentSlide; slideCount = activeSlide;
} }
state['triggerSlideHandler'] = slideCount; state['triggerSlideHandler'] = slideCount;
} else { } else {
@ -487,7 +519,7 @@ export const getSlideCount = spec => {
if (spec.swipeToSlide) { if (spec.swipeToSlide) {
let swipedSlide; let swipedSlide;
const slickList = spec.listRef; const slickList = spec.listRef;
const slides = slickList.querySelectorAll('.slick-slide'); const slides = (slickList.querySelectorAll && slickList.querySelectorAll('.slick-slide')) || [];
Array.from(slides).every(slide => { Array.from(slides).every(slide => {
if (!spec.vertical) { if (!spec.vertical) {
if (slide.offsetLeft - centerOffset + getWidth(slide) / 2 > spec.swipeLeft * -1) { if (slide.offsetLeft - centerOffset + getWidth(slide) / 2 > spec.swipeLeft * -1) {