Files
element-plus/packages/components/tabs/src/tabs.ts
Aex 4510816802 refactor(componentes): deprecate the icon font of some components (#4067)
* refactor(components): [el-breadcumb] icon

* refactor(components): el-tabs icon

* refactor(components): color picker icon

* refactor(components): menus icon

* refactor(components): loading spinner icon

* fix(components): panel-data-pick elicon import

* refactor(components): dropdown item icon

* refactor(components): tabs plus icon

* fix: message & tabs icon test

* fix(components): breadcrumb docs

* fix(components): colorPicker docs

* fix(components): loading docs

Co-authored-by: imswk <syfzxx@vip.qq.com>
2021-10-28 23:37:26 +08:00

292 lines
6.5 KiB
TypeScript

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 ElIcon from '@element-plus/components/icon'
import { Plus } from '@element-plus/icons'
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, () => {
nextTick(() => {
nav$.value &&
nav$.value.$nextTick(() => {
nav$.value && 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(ElIcon, { class: 'is-icon-plus' }, () => [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]
)
},
})