UNPKG

element-plus

Version:
291 lines (263 loc) 6.52 kB
import { defineComponent, Fragment, getCurrentInstance, h, nextTick, onMounted, onUpdated, provide, ref, watch, } from 'vue' import { isPromise } from '@vue/shared' import { EVENT_CODE } from '@element-plus/utils/aria' import TabNav from './tab-nav.vue' import type { Component, ComponentInternalInstance, PropType, VNode } from 'vue' import type { BeforeLeave, IElTabsProps, ITabType, ITabPosition, Pane, RootTabs, UpdatePaneStateCallback, } from './token' export default defineComponent({ name: 'ElTabs', components: { TabNav }, props: { type: { type: String as PropType<ITabType>, default: '', }, activeName: { type: String, default: '', }, closable: Boolean, addable: Boolean, modelValue: { type: String, default: '', }, editable: Boolean, tabPosition: { type: String as PropType<ITabPosition>, default: 'top', }, beforeLeave: { type: Function as PropType<BeforeLeave>, default: null, }, stretch: Boolean, }, emits: [ 'tab-click', 'edit', 'tab-remove', 'tab-add', 'input', 'update:modelValue', ], setup(props: IElTabsProps, ctx) { const nav$ = ref<typeof TabNav>(null) const currentName = ref(props.modelValue || props.activeName || '0') const panes = ref([]) const instance = getCurrentInstance() const paneStatesMap = {} provide<RootTabs>('rootTabs', { props, currentName, }) provide<UpdatePaneStateCallback>('updatePaneState', (pane: Pane) => { paneStatesMap[pane.uid] = pane }) watch( () => props.activeName, modelValue => { setCurrentName(modelValue) }, ) watch( () => props.modelValue, modelValue => { setCurrentName(modelValue) }, ) watch(currentName, () => { if (nav$.value) { nextTick(() => { nav$.value.$nextTick(() => { nav$.value.scrollToActiveTab() }) }) } setPaneInstances(true) }) const getPaneInstanceFromSlot = ( vnode: VNode, paneInstanceList: ComponentInternalInstance[] = [], ) => { Array.from((vnode.children || []) as ArrayLike<VNode>).forEach(node => { let type = node.type type = (type as Component).name || type if (type === 'ElTabPane' && node.component) { paneInstanceList.push(node.component) } else if (type === Fragment || type === 'template') { getPaneInstanceFromSlot(node, paneInstanceList) } }) return paneInstanceList } const setPaneInstances = (isForceUpdate = false) => { if (ctx.slots.default) { const children = instance.subTree.children const content = Array.from(children as ArrayLike<VNode>).find( ({ props }) => { return props.class === 'el-tabs__content' }, ) if (!content) return const paneInstanceList: Pane[] = getPaneInstanceFromSlot(content).map( paneComponent => { return paneStatesMap[paneComponent.uid] }, ) const panesChanged = !( paneInstanceList.length === panes.value.length && paneInstanceList.every( (pane, index) => pane.uid === panes.value[index].uid, ) ) if (isForceUpdate || panesChanged) { panes.value = paneInstanceList } } else if (panes.value.length !== 0) { panes.value = [] } } const changeCurrentName = value => { currentName.value = value ctx.emit('input', value) ctx.emit('update:modelValue', value) } const setCurrentName = value => { // should do nothing. if (currentName.value === value) return const beforeLeave = props.beforeLeave const before = beforeLeave && beforeLeave(value, currentName.value) if (before && isPromise(before)) { before.then( () => { changeCurrentName(value) nav$.value.removeFocus?.() }, () => { // ignore promise rejection in `before-leave` hook }, ) } else if (before !== false) { changeCurrentName(value) } } const handleTabClick = (tab, tabName, event) => { if (tab.props.disabled) return setCurrentName(tabName) ctx.emit('tab-click', tab, event) } const handleTabRemove = (pane, ev) => { if (pane.props.disabled) return ev.stopPropagation() ctx.emit('edit', pane.props.name, 'remove') ctx.emit('tab-remove', pane.props.name) } const handleTabAdd = () => { ctx.emit('edit', null, 'add') ctx.emit('tab-add') } onUpdated(() => { setPaneInstances() }) onMounted(() => { setPaneInstances() }) return { nav$, handleTabClick, handleTabRemove, handleTabAdd, currentName, panes, } }, render() { const { type, handleTabClick, handleTabRemove, handleTabAdd, currentName, panes, editable, addable, tabPosition, stretch, } = this const newButton = editable || addable ? h( 'span', { class: 'el-tabs__new-tab', tabindex: '0', onClick: handleTabAdd, onKeydown: ev => { if (ev.code === EVENT_CODE.enter) { handleTabAdd() } }, }, [h('i', { class: 'el-icon-plus' })], ) : null const header = h( 'div', { class: ['el-tabs__header', `is-${tabPosition}`], }, [ newButton, h(TabNav, { currentName, editable, type, panes, stretch, ref: 'nav$', onTabClick: handleTabClick, onTabRemove: handleTabRemove, }), ], ) const panels = h( 'div', { class: 'el-tabs__content', }, this.$slots?.default(), ) return h( 'div', { class: { 'el-tabs': true, 'el-tabs--card': type === 'card', [`el-tabs--${tabPosition}`]: true, 'el-tabs--border-card': type === 'border-card', }, }, tabPosition !== 'bottom' ? [header, panels] : [panels, header], ) }, })