vxe-pc-ui
Version:
A vue based PC component library
414 lines (373 loc) • 12.5 kB
text/typescript
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()
}
})