vxe-pc-ui
Version:
A vue based PC component library
941 lines (874 loc) • 34.7 kB
text/typescript
import { ref, h, reactive, inject, PropType, provide, computed, onUnmounted, watch, nextTick, onMounted } from 'vue'
import { defineVxeComponent } from '../../ui/src/comp'
import { createEvent, getConfig, getIcon, globalEvents, permission, useSize, renderEmptyElement } from '../../ui'
import { getSlotVNs } from '../../ui/src/vn'
import { toCssUnit, addClass, removeClass } from '../../ui/src/dom'
import { isEnableConf } from '../../ui/src/utils'
import { warnLog, errLog } from '../../ui/src/log'
import XEUtils from 'xe-utils'
import VxeLoadingComponent from '../../loading/src/loading'
import type { VxeTabsPropTypes, VxeTabPaneProps, VxeTabsEmits, TabsInternalData, TabsReactData, TabsPrivateRef, VxeTabsPrivateComputed, VxeTabsConstructor, VxeTabsPrivateMethods, VxeTabPaneDefines, ValueOf, TabsMethods, TabsPrivateMethods, VxeComponentStyleType } from '../../../types'
const scrollbarOffsetSize = 20
export default defineVxeComponent({
name: 'VxeTabs',
props: {
modelValue: [String, Number, Boolean] as PropType<VxeTabsPropTypes.ModelValue>,
options: Array as PropType<VxeTabsPropTypes.Options>,
width: [String, Number] as PropType<VxeTabsPropTypes.Width>,
height: [String, Number] as PropType<VxeTabsPropTypes.Height>,
destroyOnClose: {
type: Boolean as PropType<VxeTabsPropTypes.DestroyOnClose>,
default: () => getConfig().tabs.destroyOnClose
},
titleWidth: [String, Number] as PropType<VxeTabsPropTypes.TitleWidth>,
titleAlign: [String, Number] as PropType<VxeTabsPropTypes.TitleAlign>,
type: {
type: String as PropType<VxeTabsPropTypes.Type>,
default: () => getConfig().tabs.type
},
position: {
type: String as PropType<VxeTabsPropTypes.Position>,
default: () => getConfig().tabs.position
},
showClose: Boolean as PropType<VxeTabsPropTypes.ShowClose>,
showBody: {
type: Boolean as PropType<VxeTabsPropTypes.ShowBody>,
default: true
},
padding: {
type: Boolean as PropType<VxeTabsPropTypes.Padding>,
default: () => getConfig().tabs.padding
},
trigger: String as PropType<VxeTabsPropTypes.Trigger>,
beforeChangeMethod: Function as PropType<VxeTabsPropTypes.BeforeChangeMethod>,
closeConfig: Object as PropType<VxeTabsPropTypes.CloseConfig>,
refreshConfig: Object as PropType<VxeTabsPropTypes.RefreshConfig>,
size: {
type: String as PropType<VxeTabsPropTypes.Size>,
default: () => getConfig().tabs.size || getConfig().size
},
// 已废弃
beforeCloseMethod: Function as PropType<VxeTabsPropTypes.BeforeCloseMethod>
},
emits: [
'update:modelValue',
'change',
'tab-change',
'tab-change-fail',
'tab-close',
'tab-close-fail',
'tab-click',
'tab-load'
] as VxeTabsEmits,
setup (props, context) {
const { slots, emit } = context
const xID = XEUtils.uniqueId()
const $xeParentTabs = inject<(VxeTabsConstructor & VxeTabsPrivateMethods) | null>('$xeTabs', null)
const { computeSize } = useSize(props)
const refElem = ref<HTMLDivElement>()
const refHeadWrapperElem = ref<HTMLDivElement>()
const refHeadPrevElem = ref<HTMLDivElement>()
const refHeadNextElem = ref<HTMLDivElement>()
const reactData = reactive<TabsReactData>({
staticTabs: [],
activeName: null,
initNames: [],
lintLeft: 0,
lintTop: 0,
lintWidth: 0,
lintHeight: 0,
scrollbarWidth: 0,
scrollbarHeight: 0,
isTabOver: false,
resizeFlag: 1,
cacheTabMaps: {}
})
const internalData: TabsInternalData = {
slTimeout: undefined
}
const refMaps: TabsPrivateRef = {
refElem
}
const computeTabType = computed(() => {
const { type } = props
return type || 'default'
})
const computeTabPosition = computed(() => {
const { position } = props
return position || 'top'
})
const computeLrPosition = computed(() => {
const tabPosition = computeTabPosition.value
return tabPosition === 'left' || tabPosition === 'right'
})
const computeLineStyle = computed(() => {
const { lintLeft, lintTop, lintWidth, lintHeight } = reactData
const lrPosition = computeLrPosition.value
return lrPosition
? {
top: `${lintTop}px`,
height: `${lintHeight}px`
}
: {
left: `${lintLeft}px`,
width: `${lintWidth}px`
}
})
const computeWrapperStyle = computed(() => {
const { width } = props
const stys: VxeComponentStyleType = {}
if (width) {
stys.width = toCssUnit(width)
}
return stys
})
const computeCloseOpts = computed(() => {
return Object.assign({}, getConfig().tabs.closeConfig, props.closeConfig)
})
const computeRefreshOpts = computed(() => {
return Object.assign({}, getConfig().tabs.refreshConfig, props.refreshConfig)
})
const computeTabOptions = computed(() => {
const { options } = props
return (options || []).filter((item) => handleFilterTab(item))
})
const computeTabStaticOptions = computed(() => {
const { staticTabs } = reactData
return staticTabs.filter((item) => handleFilterTab(item))
})
const computeParentTabsResizeFlag = computed(() => {
return $xeParentTabs ? $xeParentTabs.reactData.resizeFlag : null
})
const computeMaps: VxeTabsPrivateComputed = {
}
const $xeTabs = {
xID,
props,
context,
reactData,
getRefMaps: () => refMaps,
getComputeMaps: () => computeMaps
} as unknown as VxeTabsConstructor & VxeTabsPrivateMethods
const handleFilterTab = (item: VxeTabPaneProps | VxeTabPaneDefines.TabConfig) => {
const { permissionCode } = item
if (permissionCode) {
if (!permission.checkVisible(permissionCode)) {
return false
}
}
return true
}
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 []
}
const checkScrolling = () => {
const lrPosition = computeLrPosition.value
const headerWrapperEl = refHeadWrapperElem.value
const headPrevEl = refHeadPrevElem.value
const headNextEl = refHeadNextElem.value
if (headerWrapperEl) {
const { scrollLeft, scrollTop, clientWidth, clientHeight, scrollWidth, scrollHeight } = headerWrapperEl
if (headPrevEl) {
if ((lrPosition ? scrollTop : scrollLeft) > 0) {
addClass(headPrevEl, 'scrolling--middle')
} else {
removeClass(headPrevEl, 'scrolling--middle')
}
}
if (headNextEl) {
if (lrPosition ? (clientHeight < scrollHeight - Math.ceil(scrollTop)) : (clientWidth < scrollWidth - Math.ceil(scrollLeft))) {
addClass(headNextEl, 'scrolling--middle')
} else {
removeClass(headNextEl, 'scrolling--middle')
}
}
}
}
const updateTabStyle = () => {
const handleStyle = () => {
const { activeName } = reactData
const tabType = computeTabType.value
const tabOptions = computeTabOptions.value
const tabStaticOptions = computeTabStaticOptions.value
const headerWrapperEl = refHeadWrapperElem.value
const lrPosition = computeLrPosition.value
let lintWidth = 0
let lintHeight = 0
let lintLeft = 0
let lintTop = 0
let sBarWidth = 0
let sBarHeight = 0
let isOver = false
if (headerWrapperEl) {
const index = XEUtils.findIndexOf(tabStaticOptions.length ? tabStaticOptions : tabOptions, item => item.name === activeName)
const { children, offsetWidth, scrollWidth, offsetHeight, scrollHeight, clientWidth, clientHeight } = headerWrapperEl
sBarWidth = offsetWidth - clientWidth
sBarHeight = offsetHeight - clientHeight
if (lrPosition) {
isOver = scrollHeight !== clientHeight
if (index > -1) {
const tabEl = children[index] as HTMLDivElement
if (tabEl) {
const tabHeight = tabEl.clientHeight
const tabWidth = tabEl.clientWidth
if (tabType === 'card') {
lintWidth = tabWidth
lintHeight = tabHeight
lintTop = tabEl.offsetTop
} else if (tabType === 'border-card') {
lintWidth = tabWidth
lintHeight = tabHeight
lintTop = tabEl.offsetTop - 1
} else {
lintHeight = Math.max(4, Math.floor(tabHeight * 0.6))
lintTop = tabEl.offsetTop + Math.floor((tabHeight - lintHeight) / 2)
}
}
}
} else {
isOver = scrollWidth !== clientWidth
if (index > -1) {
const tabEl = children[index] as HTMLDivElement
if (tabEl) {
const tabWidth = tabEl.clientWidth
if (tabType === 'card') {
lintWidth = tabWidth + 1
lintLeft = tabEl.offsetLeft
} else if (tabType === 'border-card') {
lintWidth = tabWidth
lintLeft = tabEl.offsetLeft - 1
} else {
lintWidth = Math.max(4, Math.floor(tabWidth * 0.6))
lintLeft = tabEl.offsetLeft + Math.floor((tabWidth - lintWidth) / 2)
}
}
}
}
}
reactData.scrollbarWidth = sBarWidth
reactData.scrollbarHeight = sBarHeight
reactData.lintLeft = lintLeft
reactData.lintTop = lintTop
reactData.lintWidth = lintWidth
reactData.lintHeight = lintHeight
reactData.isTabOver = isOver
checkScrolling()
}
handleStyle()
nextTick(handleStyle)
}
const dispatchEvent = (type: ValueOf<VxeTabsEmits>, params: Record<string, any>, evnt: Event | null) => {
emit(type, createEvent(evnt, { $tabs: $xeTabs }, params))
}
const emitModel = (value: any) => {
emit('update:modelValue', value)
}
const addInitName = (name: VxeTabsPropTypes.ModelValue, evnt: Event | null) => {
const { initNames } = reactData
if (name && !initNames.includes(name)) {
dispatchEvent('tab-load', { name }, evnt)
initNames.push(name)
return true
}
return false
}
const initDefaultName = (list?: VxeTabsPropTypes.Options | VxeTabPaneDefines.TabConfig[]) => {
let activeName: VxeTabsPropTypes.ModelValue = null
const nameMaps: Record<string, {
loading: boolean
}> = {}
if (list && list.length) {
let validVal = false
activeName = props.modelValue
list.forEach((item) => {
const { name, preload } = item || {}
if (name) {
nameMaps[`${name}`] = {
loading: false
}
if (activeName === name) {
validVal = true
}
if (preload) {
addInitName(name, null)
}
}
})
if (!validVal) {
activeName = list[0].name
addInitName(activeName, null)
emitModel(activeName)
}
}
reactData.activeName = activeName
reactData.cacheTabMaps = nameMaps
}
const clickEvent = (evnt: KeyboardEvent, item: VxeTabPaneProps | VxeTabPaneDefines.TabConfig) => {
const { trigger } = props
const beforeMethod = props.beforeChangeMethod || getConfig().tabs.beforeChangeMethod
const { activeName } = reactData
const { name } = item
const value = name
dispatchEvent('tab-click', { name }, evnt)
if (trigger === 'manual') {
return
}
if (name !== activeName) {
Promise.resolve(
!beforeMethod || beforeMethod({ $tabs: $xeTabs, name, oldName: activeName, newName: name, option: item })
).then((status) => {
if (status) {
reactData.activeName = name
emitModel(value)
addInitName(name, evnt)
dispatchEvent('change', { value, name, oldName: activeName, newName: name, option: item }, evnt)
dispatchEvent('tab-change', { value, name, oldName: activeName, newName: name, option: item }, evnt)
} else {
dispatchEvent('tab-change-fail', { value, name, oldName: activeName, newName: name, option: item }, evnt)
}
}).catch(() => {
dispatchEvent('tab-change-fail', { value, name, oldName: activeName, newName: name, option: item }, evnt)
})
}
}
const handleRefreshTabEvent = (evnt: KeyboardEvent, item: VxeTabPaneDefines.TabConfig | VxeTabPaneProps) => {
evnt.stopPropagation()
const { activeName, cacheTabMaps } = reactData
const { name } = item
const refreshOpts = computeRefreshOpts.value
const { queryMethod } = refreshOpts
const cacheItem = name ? cacheTabMaps[`${name}`] : null
if (cacheItem) {
if (queryMethod) {
if (cacheItem.loading) {
return
}
cacheItem.loading = true
Promise.resolve(
queryMethod({ $tabs: $xeTabs, value: activeName, name, option: item })
).finally(() => {
cacheItem.loading = false
})
} else {
errLog('vxe.error.notFunc', ['refresh-config.queryMethod'])
}
}
}
const handleCloseTabEvent = (evnt: KeyboardEvent, item: VxeTabPaneDefines.TabConfig | VxeTabPaneProps, index: number, list: VxeTabsPropTypes.Options | VxeTabPaneDefines.TabConfig[]) => {
evnt.stopPropagation()
const { activeName } = reactData
const closeOpts = computeCloseOpts.value
const beforeMethod = closeOpts.beforeMethod || props.beforeCloseMethod || getConfig().tabs.beforeCloseMethod
const { name } = item
const value = activeName
let nextName = value
if (activeName === name) {
const nextItem = index < list.length - 1 ? list[index + 1] : list[index - 1]
nextName = nextItem ? nextItem.name : null
}
Promise.resolve(
!beforeMethod || beforeMethod({ $tabs: $xeTabs, value, name, nextName, option: item })
).then(status => {
if (status) {
dispatchEvent('tab-close', { value, name, nextName }, evnt)
} else {
dispatchEvent('tab-close-fail', { value, name, nextName }, evnt)
}
}).catch(() => {
dispatchEvent('tab-close-fail', { value, name, nextName }, evnt)
})
}
const startScrollAnimation = (offsetPos: number, offsetSize: number) => {
const { slTimeout } = internalData
const lrPosition = computeLrPosition.value
let offsetLeft = lrPosition ? 0 : offsetSize
let offsetTop = lrPosition ? offsetSize : 0
let scrollCount = 6
let delayNum = 35
if (slTimeout) {
clearTimeout(slTimeout)
internalData.slTimeout = undefined
}
const scrollAnimate = () => {
const headerWrapperEl = refHeadWrapperElem.value
if (scrollCount > 0) {
scrollCount--
if (headerWrapperEl) {
const { clientWidth, clientHeight, scrollWidth, scrollHeight, scrollLeft, scrollTop } = headerWrapperEl
if (lrPosition) {
offsetTop = Math.floor(offsetTop / 2)
if (offsetPos > 0) {
if (clientHeight + scrollTop < scrollHeight) {
headerWrapperEl.scrollTop += offsetTop
delayNum -= 4
internalData.slTimeout = setTimeout(scrollAnimate, delayNum)
}
} else {
if (scrollTop > 0) {
headerWrapperEl.scrollTop -= offsetTop
delayNum -= 4
internalData.slTimeout = setTimeout(scrollAnimate, delayNum)
}
}
} else {
offsetLeft = Math.floor(offsetLeft / 2)
if (offsetPos > 0) {
if (clientWidth + scrollLeft < scrollWidth) {
headerWrapperEl.scrollLeft += offsetLeft
delayNum -= 4
internalData.slTimeout = setTimeout(scrollAnimate, delayNum)
}
} else {
if (scrollLeft > 0) {
headerWrapperEl.scrollLeft -= offsetLeft
delayNum -= 4
internalData.slTimeout = setTimeout(scrollAnimate, delayNum)
}
}
}
updateTabStyle()
}
}
}
scrollAnimate()
}
const handleScrollToLeft = (offsetPos: number) => {
const lrPosition = computeLrPosition.value
const headerWrapperEl = refHeadWrapperElem.value
if (headerWrapperEl) {
const { clientWidth, clientHeight } = headerWrapperEl
const offsetSize = Math.floor((lrPosition ? clientHeight : clientWidth) * 0.75)
startScrollAnimation(offsetPos, offsetSize)
}
}
const scrollLeftEvent = () => {
handleScrollToLeft(-1)
}
const scrollRightEvent = () => {
handleScrollToLeft(1)
}
const scrollToTab = (name: VxeTabsPropTypes.ModelValue) => {
const tabOptions = computeTabOptions.value
const tabStaticOptions = computeTabStaticOptions.value
const lrPosition = computeLrPosition.value
return nextTick().then(() => {
const headerWrapperEl = refHeadWrapperElem.value
if (headerWrapperEl) {
const index = XEUtils.findIndexOf(tabStaticOptions.length ? tabStaticOptions : tabOptions, item => item.name === name)
if (index > -1) {
const { scrollLeft, scrollTop, clientWidth, clientHeight, children } = headerWrapperEl
const tabEl = children[index] as HTMLDivElement
if (tabEl) {
if (lrPosition) {
const tabOffsetTop = tabEl.offsetTop
const tabClientHeight = tabEl.clientHeight
// 如果顶部被挡
const overSize = (tabOffsetTop + tabClientHeight) - (scrollTop + clientHeight)
if (overSize > 0) {
headerWrapperEl.scrollTop += overSize
}
// 如果底部被挡,优先
if (tabOffsetTop < scrollTop) {
headerWrapperEl.scrollTop = tabOffsetTop
}
} else {
const tabOffsetLeft = tabEl.offsetLeft
const tabClientWidth = tabEl.clientWidth
// 如果右侧被挡
const overSize = (tabOffsetLeft + tabClientWidth) - (scrollLeft + clientWidth)
if (overSize > 0) {
headerWrapperEl.scrollLeft += overSize
}
// 如果左侧被挡,优先
if (tabOffsetLeft < scrollLeft) {
headerWrapperEl.scrollLeft = tabOffsetLeft
}
}
}
}
updateTabStyle()
}
})
}
const handlePrevNext = (isNext: boolean) => {
const { activeName } = reactData
const tabOptions = computeTabOptions.value
const tabStaticOptions = computeTabStaticOptions.value
const list = tabStaticOptions.length ? tabStaticOptions : tabOptions
const index = XEUtils.findIndexOf(list, item => item.name === activeName)
if (index > -1) {
let item: VxeTabPaneProps | null = null
if (isNext) {
if (index < list.length - 1) {
item = list[index + 1]
}
} else {
if (index > 0) {
item = list[index - 1]
}
}
if (item) {
const name = item.name
const value = name
reactData.activeName = name
emitModel(value)
addInitName(name, null)
}
}
return nextTick()
}
const tabsMethods: TabsMethods = {
dispatchEvent,
scrollToTab,
prev () {
return handlePrevNext(false)
},
next () {
return handlePrevNext(true)
},
prevTab () {
warnLog('vxe.error.delFunc', ['[tabs] prevTab', 'prev'])
return tabsMethods.prev()
},
nextTab () {
warnLog('vxe.error.delFunc', ['[tabs] nextTab', 'next'])
return tabsMethods.next()
}
}
const tabsPrivateMethods: TabsPrivateMethods = {
}
Object.assign($xeTabs, tabsMethods, tabsPrivateMethods)
const renderTabHeader = (tabList: VxeTabsPropTypes.Options | VxeTabPaneDefines.TabConfig[]) => {
const { titleWidth: allTitleWidth, titleAlign: allTitleAlign, showClose, closeConfig, refreshConfig } = props
const { activeName, scrollbarWidth, scrollbarHeight, isTabOver, cacheTabMaps } = reactData
const tabType = computeTabType.value
const tabPosition = computeTabPosition.value
const lrPosition = computeLrPosition.value
const lineStyle = computeLineStyle.value
const tabPrefixSlot = slots.tabPrefix || slots['tab-prefix'] || slots.prefix
const tabSuffixSlot = slots.tabSuffix || slots['tab-suffix'] || slots.suffix || slots.extra
const closeOpts = computeCloseOpts.value
const closeVisibleMethod = closeOpts.visibleMethod
const refreshOpts = computeRefreshOpts.value
const refreshVisibleMethod = refreshOpts.visibleMethod
return h('div', {
key: 'th',
class: ['vxe-tabs-header', `type--${tabType}`, `pos--${tabPosition}`]
}, [
tabPrefixSlot
? h('div', {
class: ['vxe-tabs-header--prefix', `type--${tabType}`, `pos--${tabPosition}`]
}, callSlot(tabPrefixSlot, { name: activeName }))
: renderEmptyElement($xeTabs),
isTabOver
? h('div', {
ref: refHeadPrevElem,
class: ['vxe-tabs-header--bar vxe-tabs-header--prev-bar', `type--${tabType}`, `pos--${tabPosition}`],
onClick: scrollLeftEvent
}, [
h('span', {
class: lrPosition ? getIcon().TABS_TAB_BUTTON_TOP : getIcon().TABS_TAB_BUTTON_LEFT
})
])
: renderEmptyElement($xeTabs),
h('div', {
class: ['vxe-tabs-header--wrapper', `type--${tabType}`, `pos--${tabPosition}`]
}, [
h('div', {
ref: refHeadWrapperElem,
class: 'vxe-tabs-header--item-wrapper',
style: lrPosition
? {
marginRight: `-${scrollbarWidth + scrollbarOffsetSize}px`,
paddingRight: `${scrollbarOffsetSize}px`
}
: {
marginBottom: `-${scrollbarHeight + scrollbarOffsetSize}px`,
paddingBottom: `${scrollbarOffsetSize}px`
},
onScroll: checkScrolling
}, tabList.map((item, index) => {
const { title, titleWidth, titleAlign, icon, name } = item
const itemSlots = item.slots || {}
const titleSlot = itemSlots.title || itemSlots.tab
const titlePrefixSlot = itemSlots.titlePrefix || (itemSlots as any)['title-prefix']
const titleSuffixSlot = itemSlots.titleSuffix || (itemSlots as any)['title-suffix']
const itemWidth = titleWidth || allTitleWidth
const itemAlign = titleAlign || allTitleAlign
const params = { $tabs: $xeTabs, value: activeName, name, option: item }
const isActive = activeName === name
const cacheItem = name ? cacheTabMaps[`${name}`] : null
const isLoading = cacheItem ? cacheItem.loading : false
return h('div', {
key: `${name}`,
class: ['vxe-tabs-header--item', `type--${tabType}`, `pos--${tabPosition}`, itemAlign ? `align--${itemAlign}` : '', {
'is--active': isActive
}],
style: itemWidth
? {
width: toCssUnit(itemWidth)
}
: undefined,
onClick (evnt: KeyboardEvent) {
clickEvent(evnt, item)
}
}, [
h('div', {
class: 'vxe-tabs-header--item-inner'
}, [
h('div', {
class: 'vxe-tabs-header--item-content'
}, [
icon
? h('span', {
class: 'vxe-tabs-header--item-icon'
}, [
h('i', {
class: icon
})
])
: renderEmptyElement($xeTabs),
titlePrefixSlot
? h('span', {
class: 'vxe-tabs-header--item-prefix'
}, callSlot(titlePrefixSlot, { name, title }))
: renderEmptyElement($xeTabs),
h('span', {
class: 'vxe-tabs-header--item-name'
}, titleSlot ? callSlot(titleSlot, { name, title }) : `${title}`),
titleSuffixSlot
? h('span', {
class: 'vxe-tabs-header--item-suffix'
}, callSlot(titleSuffixSlot, { name, title }))
: renderEmptyElement($xeTabs)
]),
(isEnableConf(refreshConfig) || refreshOpts.enabled) && (refreshVisibleMethod ? refreshVisibleMethod(params) : true)
? h('div', {
class: ['vxe-tabs-header--refresh-btn', {
'is--active': isActive,
'is--loading': isLoading,
'is--disabled': isLoading
}],
onClick (evnt: KeyboardEvent) {
handleRefreshTabEvent(evnt, item)
}
}, [
h('i', {
class: isLoading ? getIcon().TABS_TAB_REFRESH_LOADING : getIcon().TABS_TAB_REFRESH
})
])
: renderEmptyElement($xeTabs),
(showClose || (isEnableConf(closeConfig) || closeOpts.enabled)) && (!closeVisibleMethod || closeVisibleMethod(params))
? h('div', {
class: ['vxe-tabs-header--close-btn', {
'is--active': isActive
}],
onClick (evnt: KeyboardEvent) {
handleCloseTabEvent(evnt, item, index, tabList)
}
}, [
h('i', {
class: getIcon().TABS_TAB_CLOSE
})
])
: renderEmptyElement($xeTabs)
])
])
}).concat([
h('span', {
key: 'line',
class: ['vxe-tabs-header--active-line', `type--${tabType}`, `pos--${tabPosition}`],
style: lineStyle
})
]))
]),
isTabOver
? h('div', {
ref: refHeadNextElem,
class: ['vxe-tabs-header--bar vxe-tabs-header--next-bar', `type--${tabType}`, `pos--${tabPosition}`],
onClick: scrollRightEvent
}, [
h('span', {
class: lrPosition ? getIcon().TABS_TAB_BUTTON_BOTTOM : getIcon().TABS_TAB_BUTTON_RIGHT
})
])
: renderEmptyElement($xeTabs),
tabSuffixSlot
? h('div', {
class: ['vxe-tabs-header--suffix', `type--${tabType}`, `pos--${tabPosition}`]
}, callSlot(tabSuffixSlot, { name: activeName }))
: renderEmptyElement($xeTabs)
])
}
const renderTabPane = (item: VxeTabPaneProps | VxeTabPaneDefines.TabConfig) => {
const { initNames, activeName } = reactData
const { name, slots } = item
const defaultSlot = slots ? slots.default : null
return name && initNames.includes(name)
? h('div', {
key: `${name}`,
class: ['vxe-tabs-pane--item', {
'is--visible': activeName === name
}]
}, defaultSlot ? callSlot(defaultSlot, { name }) : [])
: renderEmptyElement($xeTabs)
}
const renderTabContent = (tabList: VxeTabsPropTypes.Options | VxeTabPaneDefines.TabConfig[]) => {
const { destroyOnClose } = props
const { activeName } = reactData
if (destroyOnClose) {
const activeTab = tabList.find(item => item.name === activeName)
return [activeTab ? renderTabPane(activeTab) : renderEmptyElement($xeTabs)]
}
return tabList.map((item) => renderTabPane(item))
}
const rendetTabBody = (tabList: VxeTabsPropTypes.Options | VxeTabPaneDefines.TabConfig[]) => {
const { height, padding, showBody } = props
const { activeName, cacheTabMaps } = reactData
const vSize = computeSize.value
const tabType = computeTabType.value
const tabPosition = computeTabPosition.value
const refreshOpts = computeRefreshOpts.value
const { showLoading } = refreshOpts
const headerpSlot = slots.header
const footerSlot = slots.footer
if (!showBody) {
return renderEmptyElement($xeTabs)
}
const cacheItem = activeName ? cacheTabMaps[`${activeName}`] : null
const isLoading = cacheItem ? cacheItem.loading : false
const defParams = { name: activeName }
return h('div', {
key: 'tb',
class: ['vxe-tabs-pane--wrapper', `type--${tabType}`, `pos--${tabPosition}`, {
'is--content': showBody
}]
}, [
headerpSlot
? h('div', {
class: 'vxe-tabs-pane--header'
}, callSlot(headerpSlot, defParams))
: renderEmptyElement($xeTabs),
h('div', {
class: ['vxe-tabs-pane--body', `type--${tabType}`, `pos--${tabPosition}`, {
[`size--${vSize}`]: vSize,
'is--padding': padding,
'is--height': height
}]
}, renderTabContent(tabList)),
footerSlot
? h('div', {
class: 'vxe-tabs-pane--footer'
}, callSlot(footerSlot, defParams))
: renderEmptyElement($xeTabs),
showLoading && isLoading
? renderEmptyElement($xeTabs)
: h(VxeLoadingComponent, {
class: 'vxe-tabs--loading',
modelValue: isLoading
})
])
}
const renderVN = () => {
const { height, padding, trigger } = props
const { activeName } = reactData
const vSize = computeSize.value
const tabOptions = computeTabOptions.value
const tabStaticOptions = computeTabStaticOptions.value
const tabType = computeTabType.value
const tabPosition = computeTabPosition.value
const wrapperStyle = computeWrapperStyle.value
const defaultSlot = slots.default
const tabList = defaultSlot ? tabStaticOptions : tabOptions
const vns = [
h('div', {
key: 'ts',
class: 'vxe-tabs-slots'
}, defaultSlot ? defaultSlot({ name: activeName }) : [])
]
if (tabPosition === 'right' || tabPosition === 'bottom') {
vns.push(
rendetTabBody(tabList),
renderTabHeader(tabList)
)
} else {
vns.push(
renderTabHeader(tabList),
rendetTabBody(tabList)
)
}
return h('div', {
ref: refElem,
class: ['vxe-tabs', `pos--${tabPosition}`, `vxe-tabs--${tabType}`, `trigger--${trigger === 'manual' ? 'trigger' : 'default'}`, {
[`size--${vSize}`]: vSize,
'is--padding': padding,
'is--height': height
}],
style: wrapperStyle
}, vns)
}
watch(() => props.position, () => {
reactData.resizeFlag++
})
watch(() => props.modelValue, (val) => {
addInitName(val, null)
reactData.activeName = val
})
watch(() => reactData.activeName, (val) => {
scrollToTab(val)
})
const optsFlag = ref(0)
watch(() => props.options ? props.options.length : -1, () => {
optsFlag.value++
})
watch(() => props.options, () => {
optsFlag.value++
})
watch(optsFlag, () => {
initDefaultName(props.options)
reactData.resizeFlag++
})
const stFlag = ref(0)
watch(() => reactData.staticTabs ? reactData.staticTabs.length : -1, () => {
stFlag.value++
})
watch(() => reactData.staticTabs, () => {
stFlag.value++
})
watch(stFlag, () => {
initDefaultName(reactData.staticTabs)
reactData.resizeFlag++
})
watch(computeParentTabsResizeFlag, () => {
reactData.resizeFlag++
})
watch(() => reactData.resizeFlag, () => {
nextTick(() => {
updateTabStyle()
})
})
onMounted(() => {
updateTabStyle()
globalEvents.on($xeTabs, 'resize', updateTabStyle)
})
onUnmounted(() => {
globalEvents.off($xeTabs, 'resize')
})
provide('$xeTabs', $xeTabs)
addInitName(props.modelValue, null)
initDefaultName(reactData.staticTabs.length ? reactData.staticTabs : props.options)
$xeTabs.renderVN = renderVN
return $xeTabs
},
render () {
return this.renderVN()
}
})