UNPKG

vxe-pc-ui

Version:
414 lines (373 loc) 12.5 kB
import { defineComponent, ref, h, reactive, provide, PropType, watch, nextTick, onMounted, computed, onUnmounted, createCommentVNode } from 'vue' import { getConfig, getIcon, createEvent } from '../../ui' import { getSlotVNs } from '../../ui/src/vn' import { toCssUnit } from '../..//ui/src/dom' import VxeLoadingComponent from '../../loading/src/loading' import XEUtils from 'xe-utils' import type { CarouselReactData, CarouselPrivateRef, VxeCarouselPropTypes, CarouselInternalData, CarouselMethods, CarouselPrivateMethods, VxeCarouselEmits, VxeCarouselDefines, VxeCarouselPrivateComputed, VxeCarouselConstructor, ValueOf, VxeCarouselPrivateMethods } from '../../../types' export default defineComponent({ name: 'VxeCarousel', props: { modelValue: [String, Number] as PropType<VxeCarouselPropTypes.ModelValue>, options: Array as PropType<VxeCarouselPropTypes.Options>, loading: Boolean as PropType<VxeCarouselPropTypes.Loading>, height: { type: [Number, String] as PropType<VxeCarouselPropTypes.Height>, default: () => getConfig().carousel.height }, width: { type: [Number, String] as PropType<VxeCarouselPropTypes.Width>, default: () => getConfig().carousel.width }, autoPlay: { type: Boolean as PropType<VxeCarouselPropTypes.AutoPlay>, default: () => getConfig().carousel.autoPlay }, interval: { type: [Number, String] as PropType<VxeCarouselPropTypes.Interval>, default: () => getConfig().carousel.interval }, loop: { type: Boolean as PropType<VxeCarouselPropTypes.Loop>, default: () => getConfig().carousel.loop }, vertical: { type: Boolean as PropType<VxeCarouselPropTypes.Vertical>, default: () => getConfig().carousel.vertical }, showIndicators: { type: Boolean as PropType<VxeCarouselPropTypes.ShowIndicators>, default: () => getConfig().carousel.showIndicators } }, emits: [ 'update:modelValue', 'change' ] as VxeCarouselEmits, setup (props, context) { const { emit, slots } = context const xID = XEUtils.uniqueId() const refElem = ref<HTMLDivElement>() const refWrapperElem = ref<HTMLDivElement>() const reactData = reactive<CarouselReactData>({ activeName: '', staticItems: [], itemWidth: 0, itemHeight: 0 }) const internalData: CarouselInternalData = { apTimeout: undefined, stopFlag: false } const refMaps: CarouselPrivateRef = { refElem } const computeListStyle = computed(() => { const { vertical, options } = props const { activeName, itemWidth, itemHeight, staticItems } = reactData const list = (staticItems && staticItems.length ? staticItems : options) || [] const activeIndex = Math.max(0, XEUtils.findIndexOf(list, item => item.name === activeName)) const stys: Record<string, any> = {} if (vertical) { stys.transform = `translateY(-${activeIndex * itemHeight}px)` } else { stys.width = `${itemWidth * list.length}px` stys.transform = `translateX(-${activeIndex * itemWidth}px)` } return stys }) const computeMaps: VxeCarouselPrivateComputed = { } const $xeCarousel = { xID, props, context, reactData, internalData, getRefMaps: () => refMaps, getComputeMaps: () => computeMaps } as unknown as VxeCarouselConstructor & VxeCarouselPrivateMethods const updateStyle = () => { nextTick(() => { const wrapperElem = refWrapperElem.value if (wrapperElem) { reactData.itemWidth = wrapperElem.clientWidth reactData.itemHeight = wrapperElem.clientHeight } }) } const clickItemEvent = (evnt: Event, item: VxeCarouselDefines.ItemConfig | VxeCarouselPropTypes.Option) => { const value = item.name reactData.activeName = item.name emit('update:modelValue', value) emit('change', { value }, evnt) updateStyle() } const initDefaultActive = (list?: VxeCarouselDefines.ItemConfig[] | VxeCarouselPropTypes.Options) => { let activeName: VxeCarouselPropTypes.ModelValue | undefined = null if (list && list.length) { let validVal = false activeName = props.modelValue list.forEach((item) => { if (activeName === item.name) { validVal = true } }) if (!validVal) { activeName = list[0].name emit('update:modelValue', activeName) } } reactData.activeName = activeName } const dispatchEvent = (type: ValueOf<VxeCarouselEmits>, params: Record<string, any>, evnt: Event | null) => { emit(type, createEvent(evnt, { $carousel: $xeCarousel }, params)) } const handlePrevNext = (isNext: boolean) => { const { options, loop } = props const { activeName, staticItems } = reactData const list = (staticItems && staticItems.length ? staticItems : options) || [] const index = Math.max(0, XEUtils.findIndexOf(list, item => item.name === activeName)) if (index > -1) { let item: VxeCarouselDefines.ItemConfig | VxeCarouselPropTypes.Option | null = null if (isNext) { if (index < list.length - 1) { item = list[index + 1] } else { if (loop) { item = list[0] } } } else { if (index > 0) { item = list[index - 1] } else { if (loop) { item = list[list.length - 1] } } } if (item) { const name = item.name const value = name reactData.activeName = name emit('update:modelValue', value) return true } } return false } const carouselMethods: CarouselMethods = { dispatchEvent, prev () { if (handlePrevNext(false)) { handleAutoPlay() } return nextTick() }, next () { if (handlePrevNext(true)) { handleAutoPlay() } return nextTick() } } const prevEvent = (evnt: Event) => { if (handlePrevNext(false)) { const value = reactData.activeName emit('change', { value }, evnt) } } const nextEvent = (evnt: Event) => { if (handlePrevNext(true)) { const value = reactData.activeName emit('change', { value }, evnt) } } const stopAutoPlay = () => { const { apTimeout } = internalData internalData.stopFlag = true if (apTimeout) { clearTimeout(apTimeout) internalData.apTimeout = undefined } } const handleAutoPlay = () => { const { autoPlay, interval } = props const { stopFlag } = internalData stopAutoPlay() if (autoPlay) { internalData.stopFlag = false internalData.apTimeout = setTimeout(() => { if (!stopFlag) { handlePrevNext(true) } }, XEUtils.toNumber(interval) || 300) } } const mouseenterEvent = () => { stopAutoPlay() } const mouseleaveEvent = () => { handleAutoPlay() } const carouselPrivateMethods: CarouselPrivateMethods = { } const callSlot = (slotFunc: any, params: any) => { if (slotFunc) { if (XEUtils.isString(slotFunc)) { slotFunc = slots[slotFunc] || null } if (XEUtils.isFunction(slotFunc)) { return getSlotVNs(slotFunc(params)) } } return [] } Object.assign($xeCarousel, carouselMethods, carouselPrivateMethods) const renderItemWrapper = (list: VxeCarouselDefines.ItemConfig[] | VxeCarouselPropTypes.Options) => { const { height } = props const { activeName } = reactData const listStyle = computeListStyle.value return h('div', { class: 'vxe-carousel--list', style: listStyle }, list.map(item => { const { name, url, slots } = item const defaultSlot = slots ? slots.default : null return h('div', { key: `${name}`, class: ['vxe-carousel--item-inner', { 'is--active': activeName === name }], style: height ? { height: toCssUnit(height) } : null }, defaultSlot ? callSlot(defaultSlot, {}) : [ h('img', { class: 'vxe-carousel--item-img', src: url }) ]) })) } const renderIndicators = (list: VxeCarouselDefines.ItemConfig[] | VxeCarouselPropTypes.Options) => { const { activeName } = reactData return h('div', { class: 'vxe-carousel--indicators' }, list.map((item) => { const { name } = item return h('div', { key: `${name}`, class: ['vxe-carousel--indicators-item', { 'is--active': activeName === name }], onClick (evnt) { clickItemEvent(evnt, item) } }) })) } const renderVN = () => { const { loading, height, width, showIndicators, vertical, options } = props const { staticItems } = reactData const defaultSlot = slots.default const list = (staticItems && staticItems.length ? staticItems : options) || [] return h('div', { ref: refElem, class: ['vxe-carousel', `is--${vertical ? 'vertical' : 'horizontal'}`], style: width ? { width: toCssUnit(width) } : null, onMouseenter: mouseenterEvent, onMouseleave: mouseleaveEvent }, [ h('div', { class: 'vxe-carousel--slots' }, defaultSlot ? defaultSlot({}) : []), h('div', { ref: refWrapperElem, class: 'vxe-carousel--item-wrapper', style: height ? { height: toCssUnit(height) } : null }, [ renderItemWrapper(list) ]), showIndicators ? renderIndicators(list) : createCommentVNode(), h('div', { class: 'vxe-carousel--btn-wrapper' }, [ h('div', { class: 'vxe-carousel--previous-btn', onClick: prevEvent }, [ h('i', { class: vertical ? getIcon().CAROUSEL_VERTICAL_PREVIOUS : getIcon().CAROUSEL_HORIZONTAL_PREVIOUS }) ]), h('div', { class: 'vxe-carousel--next-btn', onClick: nextEvent }, [ h('i', { class: vertical ? getIcon().CAROUSEL_VERTICAL_NEXT : getIcon().CAROUSEL_HORIZONTAL_NEXT }) ]) ]), /** * 加载中 */ h(VxeLoadingComponent, { class: 'vxe-carousel--loading', modelValue: loading }) ]) } const optsFlag = ref(0) watch(() => props.options ? props.options.length : -1, () => { optsFlag.value++ }) watch(() => props.options, () => { optsFlag.value++ }) watch(optsFlag, () => { initDefaultActive(props.options) }) const stFlag = ref(0) watch(() => reactData.staticItems ? reactData.staticItems.length : -1, () => { stFlag.value++ }) watch(() => reactData.staticItems, () => { stFlag.value++ }) watch(stFlag, () => { initDefaultActive(reactData.staticItems) }) watch(() => props.autoPlay, () => { handleAutoPlay() }) initDefaultActive(reactData.staticItems.length ? reactData.staticItems : props.options) onMounted(() => { handleAutoPlay() updateStyle() }) onUnmounted(() => { stopAutoPlay() }) provide('$xeCarousel', $xeCarousel) $xeCarousel.renderVN = renderVN return $xeCarousel }, render () { return this.renderVN() } })