Refactor carousel (#5292)

* refactor: carousel

* refactor: carousel

* style: update vc-slick path
pull/5317/head
tangjinzhou 2022-02-25 14:38:09 +08:00 committed by GitHub
parent 5edc8cadca
commit c7492a0b59
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 462 additions and 322 deletions

View File

@ -2,7 +2,7 @@
exports[`renders ./components/carousel/demo/autoplay.vue correctly 1`] = ` exports[`renders ./components/carousel/demo/autoplay.vue correctly 1`] = `
<div class="ant-carousel"> <div class="ant-carousel">
<div class="slick-slider slick-initialized"> <div class="slick-slider slick-initialized" dir="ltr">
<!----> <!---->
<div class="slick-list"> <div class="slick-list">
<div class="slick-track" style="opacity: 1; transform: translate3d(0px, 0px, 0px);"> <div class="slick-track" style="opacity: 1; transform: translate3d(0px, 0px, 0px);">
@ -84,7 +84,7 @@ exports[`renders ./components/carousel/demo/autoplay.vue correctly 1`] = `
exports[`renders ./components/carousel/demo/basic.vue correctly 1`] = ` exports[`renders ./components/carousel/demo/basic.vue correctly 1`] = `
<div class="ant-carousel"> <div class="ant-carousel">
<div class="slick-slider slick-initialized"> <div class="slick-slider slick-initialized" dir="ltr">
<!----> <!---->
<div class="slick-list"> <div class="slick-list">
<div class="slick-track" style="opacity: 1; transform: translate3d(0px, 0px, 0px);"> <div class="slick-track" style="opacity: 1; transform: translate3d(0px, 0px, 0px);">
@ -166,7 +166,7 @@ exports[`renders ./components/carousel/demo/basic.vue correctly 1`] = `
exports[`renders ./components/carousel/demo/customArrows.vue correctly 1`] = ` exports[`renders ./components/carousel/demo/customArrows.vue correctly 1`] = `
<div class="ant-carousel"> <div class="ant-carousel">
<div class="slick-slider slick-initialized"> <div class="slick-slider slick-initialized" dir="ltr">
<div class="custom-slick-arrow slick-arrow slick-prev" style="left: 10px; z-index: 1; display: block;"><span role="img" aria-label="left-circle" class="anticon anticon-left-circle"><svg focusable="false" class="" data-icon="left-circle" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M603.3 327.5l-246 178a7.95 7.95 0 000 12.9l246 178c5.3 3.8 12.7 0 12.7-6.5V643c0-10.2-4.9-19.9-13.2-25.9L457.4 512l145.4-105.2c8.3-6 13.2-15.6 13.2-25.9V334c0-6.5-7.4-10.3-12.7-6.5z"></path><path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z"></path></svg></span></div> <div class="custom-slick-arrow slick-arrow slick-prev" style="left: 10px; z-index: 1; display: block;"><span role="img" aria-label="left-circle" class="anticon anticon-left-circle"><svg focusable="false" class="" data-icon="left-circle" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M603.3 327.5l-246 178a7.95 7.95 0 000 12.9l246 178c5.3 3.8 12.7 0 12.7-6.5V643c0-10.2-4.9-19.9-13.2-25.9L457.4 512l145.4-105.2c8.3-6 13.2-15.6 13.2-25.9V334c0-6.5-7.4-10.3-12.7-6.5z"></path><path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z"></path></svg></span></div>
<div class="slick-list"> <div class="slick-list">
<div class="slick-track" style="opacity: 1; transform: translate3d(0px, 0px, 0px);"> <div class="slick-track" style="opacity: 1; transform: translate3d(0px, 0px, 0px);">
@ -248,7 +248,7 @@ exports[`renders ./components/carousel/demo/customArrows.vue correctly 1`] = `
exports[`renders ./components/carousel/demo/customPaging.vue correctly 1`] = ` exports[`renders ./components/carousel/demo/customPaging.vue correctly 1`] = `
<div class="ant-carousel"> <div class="ant-carousel">
<div class="slick-slider slick-initialized"><button type="button" data-role="none" class="slick-arrow slick-prev" style="display: block;"> Previous</button> <div class="slick-slider slick-initialized" dir="ltr"><button type="button" data-role="none" class="slick-arrow slick-prev" style="display: block;"> Previous</button>
<div class="slick-list"> <div class="slick-list">
<div class="slick-track" style="opacity: 1; transform: translate3d(0px, 0px, 0px);"> <div class="slick-track" style="opacity: 1; transform: translate3d(0px, 0px, 0px);">
<div class="slick-slide slick-cloned" tabindex="-1" data-index="-1" aria-hidden="true" style="width: 0px;"> <div class="slick-slide slick-cloned" tabindex="-1" data-index="-1" aria-hidden="true" style="width: 0px;">
@ -310,7 +310,7 @@ exports[`renders ./components/carousel/demo/customPaging.vue correctly 1`] = `
exports[`renders ./components/carousel/demo/fade.vue correctly 1`] = ` exports[`renders ./components/carousel/demo/fade.vue correctly 1`] = `
<div class="ant-carousel"> <div class="ant-carousel">
<div class="slick-slider slick-initialized"> <div class="slick-slider slick-initialized" dir="ltr">
<!----> <!---->
<div class="slick-list"> <div class="slick-list">
<div class="slick-track" style="opacity: 1;"> <div class="slick-track" style="opacity: 1;">
@ -358,7 +358,7 @@ exports[`renders ./components/carousel/demo/fade.vue correctly 1`] = `
exports[`renders ./components/carousel/demo/position.vue correctly 1`] = ` exports[`renders ./components/carousel/demo/position.vue correctly 1`] = `
<div class="ant-radio-group ant-radio-group-outline ant-radio-group-default" style="margin-bottom: 8px;"><label class="ant-radio-button-wrapper ant-radio-button-wrapper-checked"><span class="ant-radio-button ant-radio-button-checked"><input type="radio" class="ant-radio-button-input" value="top"><span class="ant-radio-button-inner"></span></span><span>Top</span></label><label class="ant-radio-button-wrapper"><span class="ant-radio-button"><input type="radio" class="ant-radio-button-input" value="bottom"><span class="ant-radio-button-inner"></span></span><span>Bottom</span></label><label class="ant-radio-button-wrapper"><span class="ant-radio-button"><input type="radio" class="ant-radio-button-input" value="left"><span class="ant-radio-button-inner"></span></span><span>Left</span></label><label class="ant-radio-button-wrapper"><span class="ant-radio-button"><input type="radio" class="ant-radio-button-input" value="right"><span class="ant-radio-button-inner"></span></span><span>Right</span></label></div> <div class="ant-radio-group ant-radio-group-outline ant-radio-group-default" style="margin-bottom: 8px;"><label class="ant-radio-button-wrapper ant-radio-button-wrapper-checked"><span class="ant-radio-button ant-radio-button-checked"><input type="radio" class="ant-radio-button-input" value="top"><span class="ant-radio-button-inner"></span></span><span>Top</span></label><label class="ant-radio-button-wrapper"><span class="ant-radio-button"><input type="radio" class="ant-radio-button-input" value="bottom"><span class="ant-radio-button-inner"></span></span><span>Bottom</span></label><label class="ant-radio-button-wrapper"><span class="ant-radio-button"><input type="radio" class="ant-radio-button-input" value="left"><span class="ant-radio-button-inner"></span></span><span>Left</span></label><label class="ant-radio-button-wrapper"><span class="ant-radio-button"><input type="radio" class="ant-radio-button-input" value="right"><span class="ant-radio-button-inner"></span></span><span>Right</span></label></div>
<div class="ant-carousel"> <div class="ant-carousel">
<div class="slick-slider slick-initialized"> <div class="slick-slider slick-initialized" dir="ltr">
<!----> <!---->
<div class="slick-list"> <div class="slick-list">
<div class="slick-track" style="opacity: 1; transform: translate3d(0px, 0px, 0px);"> <div class="slick-track" style="opacity: 1; transform: translate3d(0px, 0px, 0px);">

View File

@ -21,9 +21,7 @@ describe('Carousel', () => {
sync: false, sync: false,
}; };
const wrapper = mount(Carousel, props); const wrapper = mount(Carousel, props);
const { innerSlider, slick } = wrapper.vm; const { innerSlider } = wrapper.componentVM;
const innerSliderFromRefs = slick.innerSlider;
expect(innerSlider).toBe(innerSliderFromRefs);
expect(typeof innerSlider.slickNext).toBe('function'); expect(typeof innerSlider.slickNext).toBe('function');
}); });
@ -39,26 +37,25 @@ describe('Carousel', () => {
sync: false, sync: false,
}; };
const wrapper = mount(Carousel, props); const wrapper = mount(Carousel, props);
const { prev, next, goTo } = wrapper.vm; const { prev, next, goTo, innerSlider } = wrapper.componentVM;
expect(typeof prev).toBe('function'); expect(typeof prev).toBe('function');
expect(typeof next).toBe('function'); expect(typeof next).toBe('function');
expect(typeof goTo).toBe('function'); expect(typeof goTo).toBe('function');
const slick = wrapper.vm.slick;
expect(slick.innerSlider.currentSlide).toBe(0); expect(innerSlider.currentSlide).toBe(0);
wrapper.vm.goTo(2); wrapper.vm.goTo(2);
await asyncExpect(() => { await asyncExpect(() => {
expect(slick.innerSlider.currentSlide).toBe(2); expect(innerSlider.currentSlide).toBe(2);
}, 1000); }, 1000);
prev(); prev();
await asyncExpect(() => { await asyncExpect(() => {
expect(slick.innerSlider.currentSlide).toBe(1); expect(innerSlider.currentSlide).toBe(1);
}, 1000); }, 1000);
next(); next();
await asyncExpect(() => { await asyncExpect(() => {
expect(slick.innerSlider.currentSlide).toBe(2); expect(innerSlider.currentSlide).toBe(2);
}, 1000); }, 1000);
}); });
// TODO // TODO
@ -77,8 +74,8 @@ describe('Carousel', () => {
// sync: false, // sync: false,
// }; // };
// const wrapper = mount(Carousel, props); // const wrapper = mount(Carousel, props);
// await sleep(100);
// const spy = jest.spyOn(wrapper.vm.slick.innerSlider, 'handleAutoPlay'); // const spy = jest.spyOn(wrapper.componentVM.innerSlider, 'handleAutoPlay');
// window.resizeTo(1000); // window.resizeTo(1000);
// expect(spy).not.toHaveBeenCalled(); // expect(spy).not.toHaveBeenCalled();
// await new Promise(resolve => setTimeout(resolve, 1000)); // await new Promise(resolve => setTimeout(resolve, 1000));
@ -100,12 +97,9 @@ describe('Carousel', () => {
sync: false, sync: false,
}; };
const wrapper = mount(Carousel, props); const wrapper = mount(Carousel, props);
const { onWindowResized } = wrapper.vm; const spy = jest.spyOn(window, 'removeEventListener');
const spy = jest.spyOn(wrapper.vm.onWindowResized, 'cancel');
const spy2 = jest.spyOn(window, 'removeEventListener');
wrapper.unmount(); wrapper.unmount();
expect(spy).toHaveBeenCalled(); expect(spy).toHaveBeenCalled();
expect(spy2).toHaveBeenCalledWith('resize', onWindowResized);
}); });
describe('should works for dotPosition', () => { describe('should works for dotPosition', () => {

View File

@ -71,6 +71,7 @@ export default defineComponent({
width: 100%; width: 100%;
height: 100%; height: 100%;
filter: grayscale(100%); filter: grayscale(100%);
display: block;
} }
.ant-carousel :deep .slick-thumb li.slick-active img { .ant-carousel :deep .slick-thumb li.slick-active img {
filter: grayscale(0%); filter: grayscale(0%);

View File

@ -17,14 +17,14 @@ A carousel component. Scales with its container.
| Property | Description | Type | Default | Version | | Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| afterChange | Callback function called after the current index changes | function(current) | - | |
| autoplay | Whether to scroll automatically | boolean | `false` | | | autoplay | Whether to scroll automatically | boolean | `false` | |
| beforeChange | Callback function called before the current index changes | function(from, to) | - | |
| dots | Whether to show the dots at the bottom of the gallery | boolean | `true` | | | dots | Whether to show the dots at the bottom of the gallery | boolean | `true` | |
| dotPosition | The position of the dots, which can be one of `top` `bottom` `left` `right` | string | bottom | 1.5.0 | | dotPosition | The position of the dots, which can be one of `top` `bottom` `left` `right` | string | `bottom` | 1.5.0 |
| dotsClass | Class name of the dots | string | `slick-dots` | | | dotsClass | Class name of the dots | string | `slick-dots` | |
| easing | Transition interpolation function name | string | `linear` | | | easing | Transition interpolation function name | string | `linear` | |
| effect | Transition effect | `scrollx` \| `fade` | `scrollx` | | | effect | Transition effect | `scrollx` \| `fade` | `scrollx` | |
| afterChange | Callback function called after the current index changes | function(current) | - | |
| beforeChange | Callback function called before the current index changes | function(from, to) | - | |
## Methods ## Methods
@ -34,4 +34,4 @@ A carousel component. Scales with its container.
| next() | Change current slide to next slide | | | next() | Change current slide to next slide | |
| prev() | Change current slide to previous slide | | | prev() | Change current slide to previous slide | |
For more info on the parameters, refer to the [vc-slick props](https://github.com/vueComponent/ant-design-vue/blob/next/components/vc-slick/src/default-props.js#L3) For more info on the props, refer to the [carousel props](https://github.com/vueComponent/ant-design-vue/blob/next/components/carousel/index.tsx)

View File

@ -1,168 +1,148 @@
import type { ExtractPropTypes } from 'vue'; import type { ExtractPropTypes, PropType } from 'vue';
import { defineComponent, inject } from 'vue'; import { ref, computed, watchEffect, defineComponent } from 'vue';
import PropTypes from '../_util/vue-types'; import PropTypes from '../_util/vue-types';
import debounce from 'lodash-es/debounce';
import hasProp, { getComponent } from '../_util/props-util';
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';
import { tuple, withInstall } from '../_util/type'; import { withInstall } from '../_util/type';
import useConfigInject from '../_util/hooks/useConfigInject';
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, swipeEvent: Function as PropType<(swipeDirection: SwipeDirection) => void>,
touchThreshold: PropTypes.number, touchMove: { type: Boolean, default: undefined },
variableWidth: PropTypes.looseBool, touchThreshold: Number,
useCSS: PropTypes.looseBool, variableWidth: { type: Boolean, default: undefined },
slickGoTo: PropTypes.number, useCSS: { type: Boolean, default: undefined },
responsive: PropTypes.array, slickGoTo: Number,
dotPosition: PropTypes.oneOf(tuple('top', 'bottom', 'left', 'right')), responsive: Array,
verticalSwiping: PropTypes.looseBool.def(false), dotPosition: { type: String as PropType<DotPosition>, default: undefined },
}; 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(props, { slots, attrs, expose }) {
return { const slickRef = ref();
configProvider: inject('configProvider', defaultConfigProvider),
slick: undefined, const goTo = (slide: number, dontAnimate = false) => {
innerSlider: undefined, slickRef.value?.slickGoTo(slide, dontAnimate);
}; };
},
beforeMount() { expose({
this.onWindowResized = debounce(this.onWindowResized, 500, { goTo,
leading: false, autoplay: palyType => {
}); slickRef.value?.innerSlider?.handleAutoPlay(palyType);
}, },
mounted() { prev: () => {
if (hasProp(this, 'vertical')) { slickRef.value?.slickPrev();
},
next: () => {
slickRef.value?.slickNext();
},
innerSlider: computed(() => {
return slickRef.value?.innerSlider;
}),
} as CarouselRef);
watchEffect(() => {
warning( warning(
!this.vertical, props.vertical === undefined,
'Carousel', 'Carousel',
'`vertical` is deprecated, please use `dotPosition` instead.', '`vertical` is deprecated, please use `dotPosition` instead.',
); );
} });
const { autoplay } = this; const { prefixCls, direction } = useConfigInject('carousel', props);
if (autoplay) { const dotPosition = computed(() => {
window.addEventListener('resize', this.onWindowResized); if (props.dotPosition) return props.dotPosition;
} if (props.vertical !== undefined) return props.vertical ? 'right' : 'bottom';
// https://github.com/ant-design/ant-design/issues/7191
this.innerSlider = this.slick && this.slick.innerSlider;
},
beforeUnmount() {
const { autoplay } = this;
if (autoplay) {
window.removeEventListener('resize', this.onWindowResized);
(this.onWindowResized as any).cancel();
}
},
methods: {
getDotPosition() {
if (this.dotPosition) {
return this.dotPosition;
}
if (hasProp(this, 'vertical')) {
return this.vertical ? 'right' : 'bottom';
}
return 'bottom'; return 'bottom';
},
saveSlick(node: HTMLElement) {
this.slick = node;
},
onWindowResized() {
// Fix https://github.com/ant-design/ant-design/issues/2550
const { autoplay } = this;
if (autoplay && this.slick && this.slick.innerSlider && this.slick.innerSlider.autoPlay) {
this.slick.innerSlider.autoPlay();
}
},
next() {
this.slick.slickNext();
},
prev() {
this.slick.slickPrev();
},
goTo(slide: number, dontAnimate = false) {
this.slick.slickGoTo(slide, dontAnimate);
},
},
render() {
const props = { ...this.$props };
const { $slots } = this;
if (props.effect === 'fade') {
props.fade = true;
}
const { class: cls, style, ...restAttrs } = this.$attrs as any;
const getPrefixCls = this.configProvider.getPrefixCls;
let className = getPrefixCls('carousel', props.prefixCls);
const dotsClass = 'slick-dots';
const dotPosition = this.getDotPosition();
props.vertical = dotPosition === 'left' || dotPosition === 'right';
props.dotsClass = classNames(`${dotsClass}`, `${dotsClass}-${dotPosition || 'bottom'}`, {
[`${props.dotsClass}`]: !!props.dotsClass,
}); });
className = classNames({ const vertical = computed(() => dotPosition.value === 'left' || dotPosition.value === 'right');
[cls]: !!cls, const dsClass = computed(() => {
[className]: !!className, const dotsClass = 'slick-dots';
[`${className}-vertical`]: props.vertical, return classNames({
[dotsClass]: true,
[`${dotsClass}-${dotPosition.value}`]: true,
[`${props.dotsClass}`]: !!props.dotsClass,
});
}); });
const SlickCarouselProps = { return () => {
...props, const { dots, arrows, draggable, effect } = props;
...restAttrs, const { class: cls, style, ...restAttrs } = attrs;
nextArrow: getComponent(this, 'nextArrow'), const fade = effect === 'fade' ? true : props.fade;
prevArrow: getComponent(this, 'prevArrow'), const className = classNames(prefixCls.value, {
[`${prefixCls.value}-rtl`]: direction.value === 'rtl',
[`${prefixCls.value}-vertical`]: vertical.value,
[`${cls}`]: !!cls,
});
return (
<div class={className} style={style}>
<SlickCarousel
ref={slickRef}
{...props}
{...restAttrs}
dots={!!dots}
dotsClass={dsClass.value}
arrows={arrows}
draggable={draggable}
fade={fade}
vertical={vertical.value}
v-slots={slots}
/>
</div>
);
}; };
return (
<div class={className} style={style}>
<SlickCarousel
ref={this.saveSlick}
{...SlickCarouselProps}
v-slots={$slots}
></SlickCarousel>
</div>
);
}, },
}); });

View File

@ -18,14 +18,14 @@ cover: https://gw.alipayobjects.com/zos/antfincdn/%24C9tmj978R/Carousel.svg
| 参数 | 说明 | 类型 | 默认值 | 版本 | | 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| afterChange | 切换面板的回调 | function(current) | 无 | |
| autoplay | 是否自动切换 | boolean | false | | | autoplay | 是否自动切换 | boolean | false | |
| beforeChange | 切换面板的回调 | function(from, to) | 无 | | | dotPosition | 面板指示点位置,可选 `top` `bottom` `left` `right` | string | `bottom` | 1.5.0 |
| dotPosition | 面板指示点位置,可选 `top` `bottom` `left` `right` | string | bottom | 1.5.0 |
| dots | 是否显示面板指示点 | boolean | true | | | dots | 是否显示面板指示点 | boolean | true | |
| dotsClass | 面板指示点类名 | string | `slick-dots` | | | dotsClass | 面板指示点类名 | string | `slick-dots` | |
| easing | 动画效果 | string | linear | | | easing | 动画效果 | string | `linear` | |
| effect | 动画效果函数,可取 scrollx, fade | string | scrollx | | | effect | 动画效果函数 | `scrollx` \| `fade` | `scrollx` | |
| afterChange | 切换面板的回调 | function(current) | - | |
| beforeChange | 切换面板的回调 | function(from, to) | - | |
## 方法 ## 方法
@ -35,4 +35,4 @@ cover: https://gw.alipayobjects.com/zos/antfincdn/%24C9tmj978R/Carousel.svg
| next() | 切换到下一面板 | | | next() | 切换到下一面板 | |
| prev() | 切换到上一面板 | | | prev() | 切换到上一面板 | |
更多参数可参考:[vc-slick props](https://github.com/vueComponent/ant-design-vue/blob/next/components/vc-slick/src/default-props.js#L3) 更多属性可参考源码:[carousel props](https://github.com/vueComponent/ant-design-vue/blob/next/components/carousel/index.tsx)

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,5 @@
import classnames from '../../_util/classNames'; import classnames from '../_util/classNames';
import { cloneElement } from '../../_util/vnode'; import { cloneElement } from '../_util/vnode';
import { canGoNext } from './utils/innerSliderUtils'; import { canGoNext } from './utils/innerSliderUtils';
function noop() {} function noop() {}

View File

@ -1,4 +1,4 @@
import PropTypes from '../../_util/vue-types'; import PropTypes from '../_util/vue-types';
const defaultProps = { const defaultProps = {
accessibility: PropTypes.looseBool.def(true), accessibility: PropTypes.looseBool.def(true),

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

@ -1,7 +1,7 @@
import debounce from 'lodash-es/debounce'; import debounce from 'lodash-es/debounce';
import ResizeObserver from 'resize-observer-polyfill'; import ResizeObserver from 'resize-observer-polyfill';
import classnames from '../../_util/classNames'; import classnames from '../_util/classNames';
import BaseMixin from '../../_util/BaseMixin'; import BaseMixin from '../_util/BaseMixin';
import defaultProps from './default-props'; import defaultProps from './default-props';
import initialState from './initial-state'; import initialState from './initial-state';
import { import {
@ -24,7 +24,7 @@ import {
import Track from './track'; import Track from './track';
import Dots from './dots'; import Dots from './dots';
import { PrevArrow, NextArrow } from './arrows'; import { PrevArrow, NextArrow } from './arrows';
import supportsPassive from '../../_util/supportsPassive'; import supportsPassive from '../_util/supportsPassive';
function noop() {} function noop() {}
@ -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

@ -1,10 +1,10 @@
import json2mq from '../../_util/json2mq'; import json2mq from '../_util/json2mq';
import BaseMixin from '../../_util/BaseMixin'; import BaseMixin from '../_util/BaseMixin';
import { cloneElement } from '../../_util/vnode'; import { cloneElement } from '../_util/vnode';
import InnerSlider from './inner-slider'; import InnerSlider from './inner-slider';
import defaultProps from './default-props'; import defaultProps from './default-props';
import { canUseDOM } from './utils/innerSliderUtils'; import { canUseDOM } from './utils/innerSliderUtils';
import { getSlot } from '../../_util/props-util'; import { getSlot } from '../_util/props-util';
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
export default defineComponent({ export default defineComponent({
@ -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
@ -77,19 +77,19 @@ export default defineComponent({
this._responsiveMediaHandlers.push({ mql, query, listener }); this._responsiveMediaHandlers.push({ mql, query, listener });
}, },
slickPrev() { slickPrev() {
this.innerSlider.slickPrev(); this.innerSlider?.slickPrev();
}, },
slickNext() { slickNext() {
this.innerSlider.slickNext(); this.innerSlider?.slickNext();
}, },
slickGoTo(slide, dontAnimate = false) { slickGoTo(slide, dontAnimate = false) {
this.innerSlider.slickGoTo(slide, dontAnimate); this.innerSlider?.slickGoTo(slide, dontAnimate);
}, },
slickPause() { slickPause() {
this.innerSlider.pause('paused'); this.innerSlider?.pause('paused');
}, },
slickPlay() { slickPlay() {
this.innerSlider.handleAutoPlay('play'); this.innerSlider?.handleAutoPlay('play');
}, },
}, },
@ -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

@ -1,7 +1,7 @@
import { createVNode } from 'vue'; import { createVNode } from 'vue';
import classnames from '../../_util/classNames'; import classnames from '../_util/classNames';
import { cloneElement } from '../../_util/vnode'; import { cloneElement } from '../_util/vnode';
import { flattenChildren } from '../../_util/props-util'; import { flattenChildren } from '../_util/props-util';
import { lazyStartIndex, lazyEndIndex, getPreClones } from './utils/innerSliderUtils'; import { lazyStartIndex, lazyEndIndex, getPreClones } from './utils/innerSliderUtils';
// given specifications/props for a slide, fetch all the classes that need to be applied to the slide // given specifications/props for a slide, fetch all the classes that need to be applied to the slide
@ -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) {