refactor: carousel

refactor-carousel
tangjinzhou 2022-02-25 14:25:46 +08:00
parent 7a4965f953
commit a6fa226562
8 changed files with 98 additions and 138 deletions

View File

@ -2,7 +2,7 @@
exports[`renders ./components/carousel/demo/autoplay.vue correctly 1`] = `
<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-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`] = `
<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-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`] = `
<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="slick-list">
<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`] = `
<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-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;">
@ -310,7 +310,7 @@ exports[`renders ./components/carousel/demo/customPaging.vue correctly 1`] = `
exports[`renders ./components/carousel/demo/fade.vue correctly 1`] = `
<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-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`] = `
<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="slick-slider slick-initialized">
<div class="slick-slider slick-initialized" dir="ltr">
<!---->
<div class="slick-list">
<div class="slick-track" style="opacity: 1; transform: translate3d(0px, 0px, 0px);">

View File

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

View File

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

View File

@ -17,14 +17,14 @@ A carousel component. Scales with its container.
| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| afterChange | Callback function called after the current index changes | function(current) | - | |
| 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` | |
| 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` | |
| easing | Transition interpolation function name | string | `linear` | |
| 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
@ -34,4 +34,4 @@ A carousel component. Scales with its container.
| next() | Change current slide to next 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,13 +1,11 @@
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 debounce from 'lodash-es/debounce';
import hasProp, { getComponent } from '../_util/props-util';
import { defaultConfigProvider } from '../config-provider';
import warning from '../_util/warning';
import classNames from '../_util/classNames';
import SlickCarousel from '../vc-slick/src';
import { withInstall } from '../_util/type';
import useConfigInject from '../_util/hooks/useConfigInject';
export type SwipeDirection = 'left' | 'down' | 'right' | 'up' | string;
@ -20,7 +18,7 @@ export interface CarouselRef {
goTo: (slide: number, dontAnimate?: boolean) => void;
next: () => void;
prev: () => void;
autoPlay: (palyType?: 'update' | 'leave' | 'blur') => void;
autoplay: (palyType?: 'update' | 'leave' | 'blur') => void;
innerSlider: any;
}
@ -60,13 +58,14 @@ export const carouselProps = () => ({
speed: Number,
swipe: { type: Boolean, default: undefined },
swipeToSlide: { type: Boolean, default: undefined },
swipeEvent: Function as PropType<(swipeDirection: SwipeDirection) => void>,
touchMove: { type: Boolean, default: undefined },
touchThreshold: Number,
variableWidth: { type: Boolean, default: undefined },
useCSS: { type: Boolean, default: undefined },
slickGoTo: Number,
responsive: Array,
dotPosition: String as PropType<DotPosition>,
dotPosition: { type: String as PropType<DotPosition>, default: undefined },
verticalSwiping: { type: Boolean, default: false },
});
export type CarouselProps = Partial<ExtractPropTypes<ReturnType<typeof carouselProps>>>;
@ -74,110 +73,76 @@ const Carousel = defineComponent({
name: 'ACarousel',
inheritAttrs: false,
props: carouselProps(),
setup() {
return {
configProvider: inject('configProvider', defaultConfigProvider),
slick: undefined,
innerSlider: undefined,
setup(props, { slots, attrs, expose }) {
const slickRef = ref();
const goTo = (slide: number, dontAnimate = false) => {
slickRef.value?.slickGoTo(slide, dontAnimate);
};
},
beforeMount() {
this.onWindowResized = debounce(this.onWindowResized, 500, {
leading: false,
});
},
mounted() {
if (hasProp(this, 'vertical')) {
expose({
goTo,
autoplay: palyType => {
slickRef.value?.innerSlider?.handleAutoPlay(palyType);
},
prev: () => {
slickRef.value?.slickPrev();
},
next: () => {
slickRef.value?.slickNext();
},
innerSlider: computed(() => {
return slickRef.value?.innerSlider;
}),
} as CarouselRef);
watchEffect(() => {
warning(
!this.vertical,
props.vertical === undefined,
'Carousel',
'`vertical` is deprecated, please use `dotPosition` instead.',
);
}
const { autoplay } = this;
if (autoplay) {
window.addEventListener('resize', this.onWindowResized);
}
// 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';
}
});
const { prefixCls, direction } = useConfigInject('carousel', props);
const dotPosition = computed(() => {
if (props.dotPosition) return props.dotPosition;
if (props.vertical !== undefined) return props.vertical ? 'right' : '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({
[cls]: !!cls,
[className]: !!className,
[`${className}-vertical`]: props.vertical,
const vertical = computed(() => dotPosition.value === 'left' || dotPosition.value === 'right');
const dsClass = computed(() => {
const dotsClass = 'slick-dots';
return classNames({
[dotsClass]: true,
[`${dotsClass}-${dotPosition.value}`]: true,
[`${props.dotsClass}`]: !!props.dotsClass,
});
});
const SlickCarouselProps = {
...props,
...restAttrs,
nextArrow: getComponent(this, 'nextArrow'),
prevArrow: getComponent(this, 'prevArrow'),
return () => {
const { dots, arrows, draggable, effect } = props;
const { class: cls, style, ...restAttrs } = attrs;
const fade = effect === 'fade' ? true : props.fade;
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 | |
| 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 | |
| dotsClass | 面板指示点类名 | string | `slick-dots` | |
| easing | 动画效果 | string | linear | |
| effect | 动画效果函数,可取 scrollx, fade | string | scrollx | |
| easing | 动画效果 | string | `linear` | |
| 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() | 切换到下一面板 | |
| 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

@ -164,7 +164,7 @@ export default {
if (this.autoplayTimer) {
clearInterval(this.autoplayTimer);
}
this.ro.disconnect();
this.ro?.disconnect();
},
updated() {
this.checkImagesLoad();

View File

@ -77,19 +77,19 @@ export default defineComponent({
this._responsiveMediaHandlers.push({ mql, query, listener });
},
slickPrev() {
this.innerSlider.slickPrev();
this.innerSlider?.slickPrev();
},
slickNext() {
this.innerSlider.slickNext();
this.innerSlider?.slickNext();
},
slickGoTo(slide, dontAnimate = false) {
this.innerSlider.slickGoTo(slide, dontAnimate);
this.innerSlider?.slickGoTo(slide, dontAnimate);
},
slickPause() {
this.innerSlider.pause('paused');
this.innerSlider?.pause('paused');
},
slickPlay() {
this.innerSlider.handleAutoPlay('play');
this.innerSlider?.handleAutoPlay('play');
},
},