mirror of
https://gitee.com/element-plus/element-plus.git
synced 2024-12-04 04:08:34 +08:00
feat(tabs): add tabs
This commit is contained in:
parent
ed5d989bb5
commit
18aa2638f6
@ -24,6 +24,8 @@ import ElScrollBar from '@element-plus/scrollbar'
|
|||||||
import ElSteps from '@element-plus/steps'
|
import ElSteps from '@element-plus/steps'
|
||||||
import ElCollapse from '@element-plus/collapse'
|
import ElCollapse from '@element-plus/collapse'
|
||||||
import ElPopper from '@element-plus/popper'
|
import ElPopper from '@element-plus/popper'
|
||||||
|
import ElTabs from '@element-plus/tabs'
|
||||||
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
ElAlert,
|
ElAlert,
|
||||||
@ -50,6 +52,7 @@ export {
|
|||||||
ElSteps,
|
ElSteps,
|
||||||
ElRadio,
|
ElRadio,
|
||||||
ElCollapse,
|
ElCollapse,
|
||||||
|
ElTabs,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function install(app: App): void {
|
export default function install(app: App): void {
|
||||||
@ -78,4 +81,5 @@ export default function install(app: App): void {
|
|||||||
ElRadio(app)
|
ElRadio(app)
|
||||||
ElCollapse(app)
|
ElCollapse(app)
|
||||||
ElPopper(app)
|
ElPopper(app)
|
||||||
|
ElTabs(app)
|
||||||
}
|
}
|
||||||
|
@ -34,6 +34,7 @@
|
|||||||
"@element-plus/scrollbar": "^0.0.0",
|
"@element-plus/scrollbar": "^0.0.0",
|
||||||
"@element-plus/steps": "^0.0.0",
|
"@element-plus/steps": "^0.0.0",
|
||||||
"@element-plus/notification": "^0.0.0",
|
"@element-plus/notification": "^0.0.0",
|
||||||
"@element-plus/collapse": "^0.0.0"
|
"@element-plus/collapse": "^0.0.0",
|
||||||
|
"@element-plus/tabs": "^0.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
591
packages/tabs/__tests__/tabs.spec.ts
Normal file
591
packages/tabs/__tests__/tabs.spec.ts
Normal file
@ -0,0 +1,591 @@
|
|||||||
|
import { mount } from '@vue/test-utils'
|
||||||
|
import { nextTick } from 'vue'
|
||||||
|
import Tabs from '../src/tabs.vue'
|
||||||
|
import TabPane from '../src/tab-pane.vue'
|
||||||
|
import TabNav from '../src/tab-nav.vue'
|
||||||
|
|
||||||
|
describe('Tabs.vue', () => {
|
||||||
|
test('create', async () => {
|
||||||
|
const wrapper = mount({
|
||||||
|
components: {
|
||||||
|
'el-tabs': Tabs,
|
||||||
|
'el-tab-pane': TabPane,
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<el-tabs>
|
||||||
|
<el-tab-pane label="label-1">A</el-tab-pane>
|
||||||
|
<el-tab-pane label="label-2">B</el-tab-pane>
|
||||||
|
<el-tab-pane label="label-3" ref="pane-click">C</el-tab-pane>
|
||||||
|
<el-tab-pane label="label-4">D</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
|
||||||
|
const tabsWrapper = wrapper.findComponent(Tabs)
|
||||||
|
const navWrapper = wrapper.findComponent(TabNav)
|
||||||
|
const panesWrapper = wrapper.findAllComponents(TabPane)
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
|
const navItemsWrapper = navWrapper.findAll('.el-tabs__item')
|
||||||
|
|
||||||
|
expect(navItemsWrapper[0].classes('is-active')).toBe(true)
|
||||||
|
expect(panesWrapper[0].classes('el-tab-pane')).toBe(true)
|
||||||
|
expect(panesWrapper[0].attributes('id')).toBe('pane-0')
|
||||||
|
expect(panesWrapper[0].attributes('aria-hidden')).toEqual('false')
|
||||||
|
expect(tabsWrapper.vm.currentName).toEqual('0')
|
||||||
|
|
||||||
|
await navItemsWrapper[2].trigger('click')
|
||||||
|
expect(navItemsWrapper[0].classes('is-active')).toBe(false)
|
||||||
|
expect(panesWrapper[0].attributes('aria-hidden')).toEqual('true')
|
||||||
|
expect(navItemsWrapper[2].classes('is-active')).toBe(true)
|
||||||
|
expect(panesWrapper[2].attributes('aria-hidden')).toEqual('false')
|
||||||
|
expect(tabsWrapper.vm.currentName).toEqual('2')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('active-name', async () => {
|
||||||
|
const wrapper = mount({
|
||||||
|
components: {
|
||||||
|
'el-tabs': Tabs,
|
||||||
|
'el-tab-pane': TabPane,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
activeName: 'b',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleClick(tab) {
|
||||||
|
this.activeName = tab.setupState.paneName
|
||||||
|
},
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<el-tabs :active-name="activeName" @tab-click="handleClick">
|
||||||
|
<el-tab-pane name="a" label="label-1">A</el-tab-pane>
|
||||||
|
<el-tab-pane name="b" label="label-2">B</el-tab-pane>
|
||||||
|
<el-tab-pane name="c" label="label-3" ref="pane-click">C</el-tab-pane>
|
||||||
|
<el-tab-pane name="d" label="label-4">D</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
|
||||||
|
const tabsWrapper = wrapper.findComponent(Tabs)
|
||||||
|
const navWrapper = wrapper.findComponent(TabNav)
|
||||||
|
const panesWrapper = wrapper.findAllComponents(TabPane)
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
|
const navItemsWrapper = navWrapper.findAll('.el-tabs__item')
|
||||||
|
expect(navItemsWrapper[1].classes('is-active')).toBe(true)
|
||||||
|
expect(panesWrapper[1].classes('el-tab-pane')).toBe(true)
|
||||||
|
expect(panesWrapper[1].attributes('id')).toBe('pane-b')
|
||||||
|
expect(panesWrapper[1].attributes('aria-hidden')).toEqual('false')
|
||||||
|
expect(tabsWrapper.vm.currentName).toEqual('b')
|
||||||
|
|
||||||
|
await navItemsWrapper[2].trigger('click')
|
||||||
|
expect(navItemsWrapper[1].classes('is-active')).toBe(false)
|
||||||
|
expect(panesWrapper[1].attributes('aria-hidden')).toEqual('true')
|
||||||
|
expect(navItemsWrapper[2].classes('is-active')).toBe(true)
|
||||||
|
expect(panesWrapper[2].attributes('aria-hidden')).toEqual('false')
|
||||||
|
expect(tabsWrapper.vm.currentName).toEqual('c')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('card', async () => {
|
||||||
|
const wrapper = mount({
|
||||||
|
components: {
|
||||||
|
'el-tabs': Tabs,
|
||||||
|
'el-tab-pane': TabPane,
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<el-tabs type="card">
|
||||||
|
<el-tab-pane label="label-1">A</el-tab-pane>
|
||||||
|
<el-tab-pane label="label-2">B</el-tab-pane>
|
||||||
|
<el-tab-pane label="label-3" ref="pane-click">C</el-tab-pane>
|
||||||
|
<el-tab-pane label="label-4">D</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
|
||||||
|
const tabsWrapper = wrapper.findComponent(Tabs)
|
||||||
|
expect(tabsWrapper.classes('el-tabs--card')).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('border card', async () => {
|
||||||
|
const wrapper = mount({
|
||||||
|
components: {
|
||||||
|
'el-tabs': Tabs,
|
||||||
|
'el-tab-pane': TabPane,
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<el-tabs type="border-card">
|
||||||
|
<el-tab-pane label="label-1">A</el-tab-pane>
|
||||||
|
<el-tab-pane label="label-2">B</el-tab-pane>
|
||||||
|
<el-tab-pane label="label-3" ref="pane-click">C</el-tab-pane>
|
||||||
|
<el-tab-pane label="label-4">D</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
|
||||||
|
const tabsWrapper = wrapper.findComponent(Tabs)
|
||||||
|
expect(tabsWrapper.classes('el-tabs--border-card')).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('dynamic', async () => {
|
||||||
|
const wrapper = mount({
|
||||||
|
components: {
|
||||||
|
'el-tabs': Tabs,
|
||||||
|
'el-tab-pane': TabPane,
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<el-tabs type="card" ref="tabs">
|
||||||
|
<el-tab-pane :label="tab.label" :name="tab.name" v-for="tab in tabs" :key="tab.name">Test Content</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
`,
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
tabs: [{
|
||||||
|
label: 'tab1',
|
||||||
|
name: 'tab1',
|
||||||
|
}, {
|
||||||
|
label: 'tab2',
|
||||||
|
name: 'tab2',
|
||||||
|
}, {
|
||||||
|
label: 'tab3',
|
||||||
|
name: 'tab3',
|
||||||
|
}, {
|
||||||
|
label: 'tab4',
|
||||||
|
name: 'tab4',
|
||||||
|
}],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
let navWrapper = wrapper.findComponent(TabNav)
|
||||||
|
let panesWrapper = wrapper.findAllComponents(TabPane)
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
|
let navItemsWrapper = navWrapper.findAll('.el-tabs__item')
|
||||||
|
|
||||||
|
expect(navItemsWrapper.length).toEqual(4)
|
||||||
|
expect(panesWrapper.length).toEqual(4)
|
||||||
|
|
||||||
|
wrapper.vm.tabs.push({ label: 'tab5', name: 'tab5' })
|
||||||
|
|
||||||
|
await nextTick()
|
||||||
|
navWrapper = wrapper.findComponent(TabNav)
|
||||||
|
panesWrapper = wrapper.findAllComponents(TabPane)
|
||||||
|
navItemsWrapper = navWrapper.findAll('.el-tabs__item')
|
||||||
|
|
||||||
|
expect(navItemsWrapper.length).toEqual(5)
|
||||||
|
expect(panesWrapper.length).toEqual(5)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('editable', async () => {
|
||||||
|
const wrapper = mount({
|
||||||
|
components: {
|
||||||
|
'el-tabs': Tabs,
|
||||||
|
'el-tab-pane': TabPane,
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<el-tabs ref="tabs" v-model="editableTabsValue" type="card" editable @edit="handleTabsEdit">
|
||||||
|
<el-tab-pane
|
||||||
|
v-for="(item, index) in editableTabs"
|
||||||
|
:key="item.name"
|
||||||
|
:label="item.title"
|
||||||
|
:name="item.name"
|
||||||
|
>
|
||||||
|
{{item.content}}
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
`,
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
editableTabsValue: '2',
|
||||||
|
editableTabs: [{
|
||||||
|
title: 'Tab 1',
|
||||||
|
name: '1',
|
||||||
|
content: 'Tab 1 content',
|
||||||
|
}, {
|
||||||
|
title: 'Tab 2',
|
||||||
|
name: '2',
|
||||||
|
content: 'Tab 2 content',
|
||||||
|
}, {
|
||||||
|
title: 'Tab 3',
|
||||||
|
name: '3',
|
||||||
|
content: 'Tab 3 content',
|
||||||
|
}],
|
||||||
|
tabIndex: 3,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleTabsEdit(targetName, action) {
|
||||||
|
if (action === 'add') {
|
||||||
|
const newTabName = ++this.tabIndex + ''
|
||||||
|
this.editableTabs.push({
|
||||||
|
title: 'New Tab',
|
||||||
|
name: newTabName,
|
||||||
|
content: 'New Tab content',
|
||||||
|
})
|
||||||
|
this.editableTabsValue = newTabName
|
||||||
|
}
|
||||||
|
if (action === 'remove') {
|
||||||
|
const tabs = this.editableTabs
|
||||||
|
let activeName = this.editableTabsValue
|
||||||
|
if (activeName === targetName) {
|
||||||
|
tabs.forEach((tab, index) => {
|
||||||
|
if (tab.name === targetName) {
|
||||||
|
const nextTab = tabs[index + 1] || tabs[index - 1]
|
||||||
|
if (nextTab) {
|
||||||
|
activeName = nextTab.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.editableTabsValue = activeName
|
||||||
|
this.editableTabs = tabs.filter(tab => tab.name !== targetName)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const navWrapper = wrapper.findComponent(TabNav)
|
||||||
|
let panesWrapper = wrapper.findAllComponents(TabPane)
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
|
let navItemsWrapper = navWrapper.findAll('.el-tabs__item')
|
||||||
|
|
||||||
|
expect(navItemsWrapper.length).toEqual(3)
|
||||||
|
expect(panesWrapper.length).toEqual(3)
|
||||||
|
expect(navItemsWrapper[1].classes('is-active')).toBe(true)
|
||||||
|
|
||||||
|
// remove one tab, check panes length
|
||||||
|
await navItemsWrapper[1].find('.el-icon-close').trigger('click')
|
||||||
|
|
||||||
|
panesWrapper = wrapper.findAllComponents(TabPane)
|
||||||
|
navItemsWrapper = navWrapper.findAll('.el-tabs__item')
|
||||||
|
|
||||||
|
expect(navItemsWrapper.length).toEqual(2)
|
||||||
|
expect(panesWrapper.length).toEqual(2)
|
||||||
|
|
||||||
|
// add one tab, check panes length and current tab
|
||||||
|
await navWrapper.find('.el-tabs__new-tab').trigger('click')
|
||||||
|
|
||||||
|
panesWrapper = wrapper.findAllComponents(TabPane)
|
||||||
|
navItemsWrapper = navWrapper.findAll('.el-tabs__item')
|
||||||
|
|
||||||
|
expect(navItemsWrapper.length).toEqual(3)
|
||||||
|
expect(panesWrapper.length).toEqual(3)
|
||||||
|
expect(navItemsWrapper[2].classes('is-active')).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('addable & closable', async () => {
|
||||||
|
const wrapper = mount({
|
||||||
|
components: {
|
||||||
|
'el-tabs': Tabs,
|
||||||
|
'el-tab-pane': TabPane,
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<el-tabs
|
||||||
|
ref="tabs"
|
||||||
|
v-model="editableTabsValue"
|
||||||
|
type="card"
|
||||||
|
addable
|
||||||
|
closable
|
||||||
|
@tab-add="addTab"
|
||||||
|
@tab-remove="removeTab"
|
||||||
|
>
|
||||||
|
<el-tab-pane
|
||||||
|
v-for="(item, index) in editableTabs"
|
||||||
|
:label="item.title"
|
||||||
|
:key="item.name"
|
||||||
|
:name="item.name"
|
||||||
|
>
|
||||||
|
{{item.content}}
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
`,
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
editableTabsValue: '2',
|
||||||
|
editableTabs: [{
|
||||||
|
title: 'Tab 1',
|
||||||
|
name: '1',
|
||||||
|
content: 'Tab 1 content',
|
||||||
|
}, {
|
||||||
|
title: 'Tab 2',
|
||||||
|
name: '2',
|
||||||
|
content: 'Tab 2 content',
|
||||||
|
}],
|
||||||
|
tabIndex: 2,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
addTab() {
|
||||||
|
const newTabName = ++this.tabIndex + ''
|
||||||
|
this.editableTabs.push({
|
||||||
|
title: 'New Tab',
|
||||||
|
name: newTabName,
|
||||||
|
content: 'New Tab content',
|
||||||
|
})
|
||||||
|
this.editableTabsValue = newTabName
|
||||||
|
},
|
||||||
|
removeTab(targetName) {
|
||||||
|
const tabs = this.editableTabs
|
||||||
|
let activeName = this.editableTabsValue
|
||||||
|
if (activeName === targetName) {
|
||||||
|
tabs.forEach((tab, index) => {
|
||||||
|
if (tab.name === targetName) {
|
||||||
|
const nextTab = tabs[index + 1] || tabs[index - 1]
|
||||||
|
if (nextTab) {
|
||||||
|
activeName = nextTab.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.editableTabsValue = activeName
|
||||||
|
this.editableTabs = tabs.filter(tab => tab.name !== targetName)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const navWrapper = wrapper.findComponent(TabNav)
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
|
await navWrapper.find('.el-tabs__new-tab').trigger('click')
|
||||||
|
|
||||||
|
let navItemsWrapper = navWrapper.findAll('.el-tabs__item')
|
||||||
|
let panesWrapper = wrapper.findAllComponents(TabPane)
|
||||||
|
expect(navItemsWrapper.length).toEqual(3)
|
||||||
|
expect(panesWrapper.length).toEqual(3)
|
||||||
|
expect(navItemsWrapper[2].classes('is-active')).toBe(true)
|
||||||
|
|
||||||
|
await navItemsWrapper[2].find('.el-icon-close').trigger('click')
|
||||||
|
|
||||||
|
panesWrapper = wrapper.findAllComponents(TabPane)
|
||||||
|
navItemsWrapper = navWrapper.findAll('.el-tabs__item')
|
||||||
|
|
||||||
|
expect(navItemsWrapper.length).toEqual(2)
|
||||||
|
expect(panesWrapper.length).toEqual(2)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('closable in tab-pane', async () => {
|
||||||
|
const wrapper = mount({
|
||||||
|
components: {
|
||||||
|
'el-tabs': Tabs,
|
||||||
|
'el-tab-pane': TabPane,
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<el-tabs type="card" ref="tabs">
|
||||||
|
<el-tab-pane label="label-1" closable>A</el-tab-pane>
|
||||||
|
<el-tab-pane label="label-2">B</el-tab-pane>
|
||||||
|
<el-tab-pane label="label-3" closable>C</el-tab-pane>
|
||||||
|
<el-tab-pane label="label-4">D</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
|
||||||
|
const navWrapper = wrapper.findComponent(TabNav)
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
|
expect(navWrapper.findAll('.el-icon-close').length).toBe(2)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('disabled', async () => {
|
||||||
|
const wrapper = mount({
|
||||||
|
components: {
|
||||||
|
'el-tabs': Tabs,
|
||||||
|
'el-tab-pane': TabPane,
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<el-tabs type="card" ref="tabs">
|
||||||
|
<el-tab-pane label="label-1">A</el-tab-pane>
|
||||||
|
<el-tab-pane disabled label="label-2" ref="disabled">B</el-tab-pane>
|
||||||
|
<el-tab-pane label="label-3">C</el-tab-pane>
|
||||||
|
<el-tab-pane label="label-4">D</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
|
||||||
|
const navWrapper = wrapper.findComponent(TabNav)
|
||||||
|
await nextTick()
|
||||||
|
const navItemsWrapper = navWrapper.findAll('.el-tabs__item')
|
||||||
|
expect(navItemsWrapper[1].classes('is-active')).toBe(false)
|
||||||
|
|
||||||
|
await navItemsWrapper[1].trigger('click')
|
||||||
|
expect(navItemsWrapper[1].classes('is-active')).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('tab-position', async () => {
|
||||||
|
const wrapper = mount({
|
||||||
|
components: {
|
||||||
|
'el-tabs': Tabs,
|
||||||
|
'el-tab-pane': TabPane,
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<el-tabs ref="tabs" tab-position="left">
|
||||||
|
<el-tab-pane label="label-1">A</el-tab-pane>
|
||||||
|
<el-tab-pane label="label-2">B</el-tab-pane>
|
||||||
|
<el-tab-pane label="label-3" ref="pane-click">C</el-tab-pane>
|
||||||
|
<el-tab-pane label="label-4">D</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
|
||||||
|
const tabsWrapper = wrapper.findComponent(Tabs)
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
|
expect(tabsWrapper.classes('el-tabs--left')).toBe(true)
|
||||||
|
expect(tabsWrapper.find('.el-tabs__header').classes('is-left')).toBe(true)
|
||||||
|
expect(tabsWrapper.find('.el-tabs__nav-wrap').classes('is-left')).toBe(true)
|
||||||
|
expect(tabsWrapper.find('.el-tabs__nav').classes('is-left')).toBe(true)
|
||||||
|
expect(tabsWrapper.find('.el-tabs__active-bar').classes('is-left')).toBe(true)
|
||||||
|
expect(tabsWrapper.find('.el-tabs__item').classes('is-left')).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('stretch', async () => {
|
||||||
|
const wrapper = mount({
|
||||||
|
components: {
|
||||||
|
'el-tabs': Tabs,
|
||||||
|
'el-tab-pane': TabPane,
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<el-tabs ref="tabs" stretch :tab-position="tabPosition">
|
||||||
|
<el-tab-pane label="label-1">A</el-tab-pane>
|
||||||
|
<el-tab-pane label="label-2">B</el-tab-pane>
|
||||||
|
<el-tab-pane label="label-3">C</el-tab-pane>
|
||||||
|
<el-tab-pane label="label-4">D</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
`,
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
tabPosition: 'bottom',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const tabsWrapper = wrapper.findComponent(Tabs)
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
|
expect(tabsWrapper.find('.el-tabs__nav').classes('is-stretch')).toBe(true)
|
||||||
|
|
||||||
|
wrapper.vm.tabPosition = 'left'
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
|
expect(tabsWrapper.find('.el-tabs__nav').classes('is-stretch')).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('horizonal-scrollable', async () => {
|
||||||
|
// TODO: jsdom not support `clientWidth`.
|
||||||
|
})
|
||||||
|
|
||||||
|
test('vertical-scrollable', async () => {
|
||||||
|
// TODO: jsdom not support `clientWidth`.
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should work with lazy', async () => {
|
||||||
|
const wrapper = mount({
|
||||||
|
components: {
|
||||||
|
'el-tabs': Tabs,
|
||||||
|
'el-tab-pane': TabPane,
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<el-tabs ref="tabs">
|
||||||
|
<el-tab-pane label="label-1" name="A">A</el-tab-pane>
|
||||||
|
<el-tab-pane label="label-2" name="B">B</el-tab-pane>
|
||||||
|
<el-tab-pane label="label-3" name="C">C</el-tab-pane>
|
||||||
|
<el-tab-pane label="label-4" lazy name="D">D</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
|
||||||
|
const navWrapper = wrapper.findComponent(TabNav)
|
||||||
|
await nextTick()
|
||||||
|
const navItemsWrapper = navWrapper.findAll('.el-tabs__item')
|
||||||
|
|
||||||
|
expect(wrapper.findAll('.el-tab-pane').length).toBe(3)
|
||||||
|
|
||||||
|
await navItemsWrapper[3].trigger('click')
|
||||||
|
|
||||||
|
expect(wrapper.findAll('.el-tab-pane').length).toBe(4)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('before leave', async () => {
|
||||||
|
const wrapper = mount({
|
||||||
|
components: {
|
||||||
|
'el-tabs': Tabs,
|
||||||
|
'el-tab-pane': TabPane,
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<el-tabs ref="tabs" v-model="activeName" :before-leave="beforeLeave">
|
||||||
|
<el-tab-pane name="tab-A" label="label-1">A</el-tab-pane>
|
||||||
|
<el-tab-pane name="tab-B" label="label-2">B</el-tab-pane>
|
||||||
|
<el-tab-pane name="tab-C" label="label-3">C</el-tab-pane>
|
||||||
|
<el-tab-pane name="tab-D" label="label-4">D</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
`,
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
activeName: 'tab-B',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
beforeLeave() {
|
||||||
|
return new window.Promise((resolve, reject) => {
|
||||||
|
reject()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const navWrapper = wrapper.findComponent(TabNav)
|
||||||
|
const panesWrapper = wrapper.findAllComponents(TabPane)
|
||||||
|
await nextTick()
|
||||||
|
const navItemsWrapper = navWrapper.findAll('.el-tabs__item')
|
||||||
|
|
||||||
|
expect(navItemsWrapper[1].classes('is-active')).toBe(true)
|
||||||
|
expect(panesWrapper[1].attributes('style')).toBeFalsy()
|
||||||
|
|
||||||
|
await navItemsWrapper[3].trigger('click')
|
||||||
|
|
||||||
|
expect(navItemsWrapper[1].classes('is-active')).toBe(true)
|
||||||
|
expect(panesWrapper[1].attributes('style')).toBeFalsy()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('keyboard event', async () => {
|
||||||
|
const wrapper = mount({
|
||||||
|
components: {
|
||||||
|
'el-tabs': Tabs,
|
||||||
|
'el-tab-pane': TabPane,
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<el-tabs v-model="activeName">
|
||||||
|
<el-tab-pane label="label-1" name="first">A</el-tab-pane>
|
||||||
|
<el-tab-pane label="label-2" name="second">B</el-tab-pane>
|
||||||
|
<el-tab-pane label="label-3" name="third">C</el-tab-pane>
|
||||||
|
<el-tab-pane label="label-4" name="fourth">D</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
`,
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
activeName: 'second',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const vm = wrapper.vm
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
|
await wrapper.find('#tab-second').trigger('keydown', { keyCode: 39 })
|
||||||
|
expect(vm.activeName).toEqual('third')
|
||||||
|
|
||||||
|
await wrapper.find('#tab-third').trigger('keydown', { keyCode: 39 })
|
||||||
|
expect(vm.activeName).toEqual('fourth')
|
||||||
|
|
||||||
|
await wrapper.find('#tab-fourth').trigger('keydown', { keyCode: 39 })
|
||||||
|
expect(vm.activeName).toEqual('first')
|
||||||
|
|
||||||
|
await wrapper.find('#tab-first').trigger('keydown', { keyCode: 37 })
|
||||||
|
expect(vm.activeName).toEqual('fourth')
|
||||||
|
|
||||||
|
await wrapper.find('#tab-fourth').trigger('keydown', { keyCode: 37 })
|
||||||
|
expect(vm.activeName).toEqual('third')
|
||||||
|
})
|
||||||
|
})
|
265
packages/tabs/doc/basic.vue
Normal file
265
packages/tabs/doc/basic.vue
Normal file
@ -0,0 +1,265 @@
|
|||||||
|
<template>
|
||||||
|
<el-button @click="show = !show">
|
||||||
|
display/hidden tab item
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
@click="activeName = activeName === 'first' ? 'second' : 'first'"
|
||||||
|
>
|
||||||
|
Change
|
||||||
|
</el-button>
|
||||||
|
|
||||||
|
<div class="flag">
|
||||||
|
<el-tabs v-model="activeName" @tab-click="handleClick">
|
||||||
|
<el-tab-pane>
|
||||||
|
<template #label>label-1slot-title</template>
|
||||||
|
label-1slot-title
|
||||||
|
</el-tab-pane>
|
||||||
|
<template v-if="true">
|
||||||
|
<el-tab-pane
|
||||||
|
label="label-1"
|
||||||
|
name="first"
|
||||||
|
>
|
||||||
|
label-1
|
||||||
|
</el-tab-pane>
|
||||||
|
</template>
|
||||||
|
<el-tab-pane
|
||||||
|
v-if="show"
|
||||||
|
label="label-2"
|
||||||
|
name="fourth"
|
||||||
|
>
|
||||||
|
label-2
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane label="label-3" name="second">label-3</el-tab-pane>
|
||||||
|
<el-tab-pane label="label-4" name="third">label-4</el-tab-pane>
|
||||||
|
<el-tab-pane v-for="i in 3" :key="i" :label="`a-${i}`">
|
||||||
|
{{
|
||||||
|
`name-${i}`
|
||||||
|
}}
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flag">
|
||||||
|
<el-tabs v-model="activeName" type="card" @tab-click="handleClick">
|
||||||
|
<el-tab-pane
|
||||||
|
closable
|
||||||
|
label="label-1"
|
||||||
|
name="first"
|
||||||
|
>
|
||||||
|
label-1
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane
|
||||||
|
disabled
|
||||||
|
label="label-2"
|
||||||
|
name="fourth"
|
||||||
|
>
|
||||||
|
label-2
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane label="label-3" name="second">label-3</el-tab-pane>
|
||||||
|
<el-tab-pane label="label-4" name="third">label-4</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flag">
|
||||||
|
<el-tabs
|
||||||
|
v-model="activeName"
|
||||||
|
type="border-card"
|
||||||
|
@tab-click="handleClick"
|
||||||
|
>
|
||||||
|
<el-tab-pane
|
||||||
|
lazy
|
||||||
|
label="label-1"
|
||||||
|
name="first"
|
||||||
|
>
|
||||||
|
label-1
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane
|
||||||
|
lazy
|
||||||
|
label="label-2"
|
||||||
|
name="fourth"
|
||||||
|
>
|
||||||
|
label-2
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane
|
||||||
|
lazy
|
||||||
|
label="label-3"
|
||||||
|
name="second"
|
||||||
|
>
|
||||||
|
label-3
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane
|
||||||
|
lazy
|
||||||
|
label="label-4"
|
||||||
|
name="third"
|
||||||
|
>
|
||||||
|
label-4
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flag">
|
||||||
|
<el-button @click="tabPosition = 'top'">top</el-button>
|
||||||
|
<el-button @click="tabPosition = 'right'">right</el-button>
|
||||||
|
<el-button @click="tabPosition = 'bottom'">bottom</el-button>
|
||||||
|
<el-button @click="tabPosition = 'left'">left</el-button>
|
||||||
|
|
||||||
|
<el-tabs :tab-position="tabPosition" style="height: 200px;">
|
||||||
|
<el-tab-pane label="label-1">label-1</el-tab-pane>
|
||||||
|
<el-tab-pane label="label-3">label-3</el-tab-pane>
|
||||||
|
<el-tab-pane label="label-4">label-4</el-tab-pane>
|
||||||
|
<el-tab-pane label="label-2">label-2</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flag">
|
||||||
|
<el-tabs type="border-card">
|
||||||
|
<el-tab-pane>
|
||||||
|
<template #label>
|
||||||
|
<i class="el-icon-date"></i> label-1
|
||||||
|
</template>
|
||||||
|
label-1
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane label="label-6">label-6</el-tab-pane>
|
||||||
|
<el-tab-pane label="label-4">label-4</el-tab-pane>
|
||||||
|
<el-tab-pane label="label-2">label-2</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flag">
|
||||||
|
<el-tabs
|
||||||
|
v-model="editableTabsValue"
|
||||||
|
type="card"
|
||||||
|
editable
|
||||||
|
style="width: 450px;"
|
||||||
|
@edit="handleTabsEdit"
|
||||||
|
>
|
||||||
|
<el-tab-pane
|
||||||
|
v-for="item in editableTabs"
|
||||||
|
:key="item.name"
|
||||||
|
:label="item.title"
|
||||||
|
:name="item.name"
|
||||||
|
>
|
||||||
|
{{ item.content }}
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flag">
|
||||||
|
<div style="margin-bottom: 20px;">
|
||||||
|
<el-button size="small" @click="addTab(editableTabsValue)">
|
||||||
|
add tab
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
<el-tabs
|
||||||
|
v-model="editableTabsValue"
|
||||||
|
type="card"
|
||||||
|
stretch
|
||||||
|
addable
|
||||||
|
closable
|
||||||
|
@tab-remove="removeTab"
|
||||||
|
@tab-add="addTab(editableTabsValue)"
|
||||||
|
>
|
||||||
|
<el-tab-pane
|
||||||
|
v-for="item in editableTabs"
|
||||||
|
:key="item.name"
|
||||||
|
:label="item.title"
|
||||||
|
:name="item.name"
|
||||||
|
>
|
||||||
|
{{ item.content }}
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang='ts'>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
show: false,
|
||||||
|
activeName: 'second',
|
||||||
|
tabPosition: 'left',
|
||||||
|
|
||||||
|
editableTabsValue: '2',
|
||||||
|
editableTabs: [
|
||||||
|
{
|
||||||
|
title: 'Tab 1',
|
||||||
|
name: '1',
|
||||||
|
content: 'Tab 1 content',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Tab 2',
|
||||||
|
name: '2',
|
||||||
|
content: 'Tab 2 content',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
tabIndex: 2,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
addTab() {
|
||||||
|
let newTabName = ++this.tabIndex + ''
|
||||||
|
this.editableTabs.push({
|
||||||
|
title: 'New Tab',
|
||||||
|
name: newTabName,
|
||||||
|
content: 'New Tab content',
|
||||||
|
})
|
||||||
|
this.editableTabsValue = newTabName
|
||||||
|
},
|
||||||
|
removeTab(targetName) {
|
||||||
|
let tabs = this.editableTabs
|
||||||
|
let activeName = this.editableTabsValue
|
||||||
|
if (activeName === targetName) {
|
||||||
|
tabs.forEach((tab, index) => {
|
||||||
|
if (tab.name === targetName) {
|
||||||
|
let nextTab = tabs[index + 1] || tabs[index - 1]
|
||||||
|
if (nextTab) {
|
||||||
|
activeName = nextTab.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.editableTabsValue = activeName
|
||||||
|
this.editableTabs = tabs.filter((tab) => tab.name !== targetName)
|
||||||
|
},
|
||||||
|
handleTabsEdit(targetName, action) {
|
||||||
|
if (action === 'add') {
|
||||||
|
let newTabName = ++this.tabIndex + ''
|
||||||
|
this.editableTabs.push({
|
||||||
|
title: 'New Tab',
|
||||||
|
name: newTabName,
|
||||||
|
content: 'New Tab content',
|
||||||
|
})
|
||||||
|
this.editableTabsValue = newTabName
|
||||||
|
}
|
||||||
|
if (action === 'remove') {
|
||||||
|
let tabs = this.editableTabs
|
||||||
|
let activeName = this.editableTabsValue
|
||||||
|
if (activeName === targetName) {
|
||||||
|
tabs.forEach((tab, index) => {
|
||||||
|
if (tab.name === targetName) {
|
||||||
|
let nextTab = tabs[index + 1] || tabs[index - 1]
|
||||||
|
if (nextTab) {
|
||||||
|
activeName = nextTab.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.editableTabsValue = activeName
|
||||||
|
this.editableTabs = tabs.filter(
|
||||||
|
(tab) => tab.name !== targetName,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleClick(tab, event) {
|
||||||
|
console.log(tab, event)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
.flag {
|
||||||
|
border: 2px solid #eee;
|
||||||
|
margin: 15px 0;
|
||||||
|
padding: 10px;
|
||||||
|
min-height: 250px;
|
||||||
|
}
|
||||||
|
</style>
|
6
packages/tabs/doc/index.stories.ts
Normal file
6
packages/tabs/doc/index.stories.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export { default as BasicUsage } from './basic.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'Tabs',
|
||||||
|
}
|
||||||
|
|
11
packages/tabs/index.ts
Normal file
11
packages/tabs/index.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { App } from 'vue'
|
||||||
|
import Tabs from './src/tabs.vue'
|
||||||
|
import TabBar from './src/tab-bar.vue'
|
||||||
|
import TabNav from './src/tab-nav.vue'
|
||||||
|
import TabPane from './src/tab-pane.vue'
|
||||||
|
export default (app: App): void => {
|
||||||
|
app.component(Tabs.name, Tabs)
|
||||||
|
app.component(TabBar.name, TabBar)
|
||||||
|
app.component(TabNav.name, TabNav)
|
||||||
|
app.component(TabPane.name, TabPane)
|
||||||
|
}
|
12
packages/tabs/package.json
Normal file
12
packages/tabs/package.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"name": "@element-plus/tabs",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"main": "dist/index.js",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"vue": "^3.0.0-rc.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@vue/test-utils": "^2.0.0-beta.0"
|
||||||
|
}
|
||||||
|
}
|
78
packages/tabs/src/tab-bar.vue
Normal file
78
packages/tabs/src/tab-bar.vue
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
:class="['el-tabs__active-bar', `is-${ rootTabs.props.tabPosition }`]"
|
||||||
|
:style="barStyle"
|
||||||
|
></div>
|
||||||
|
</template>
|
||||||
|
<script lang='ts'>
|
||||||
|
import { defineComponent, inject, getCurrentInstance, watch, nextTick, ref, ComponentInternalInstance } from 'vue'
|
||||||
|
import { capitalize } from '@vue/shared'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'ElTabBar',
|
||||||
|
props: {
|
||||||
|
tabs: {
|
||||||
|
type: Array as PropType<ComponentInternalInstance[]>,
|
||||||
|
default: () => ([] as ComponentInternalInstance[]),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setup(props) {
|
||||||
|
const rootTabs = inject('rootTabs')
|
||||||
|
if (!rootTabs) {
|
||||||
|
throw new Error(`ElTabBar must use with ElTabs`)
|
||||||
|
}
|
||||||
|
const instance = getCurrentInstance()
|
||||||
|
|
||||||
|
const getBarStyle = () => {
|
||||||
|
let style: CSSStyleDeclaration = {} as CSSStyleDeclaration
|
||||||
|
let offset = 0
|
||||||
|
let tabSize = 0
|
||||||
|
|
||||||
|
const sizeName = ['top', 'bottom'].includes(rootTabs.props.tabPosition) ? 'width' : 'height'
|
||||||
|
const sizeDir = sizeName === 'width' ? 'x' : 'y'
|
||||||
|
|
||||||
|
props.tabs.every((tab) => {
|
||||||
|
let $el = instance.parent.refs?.[`tab-${tab.setupState.paneName}`]
|
||||||
|
if (!$el) { return false }
|
||||||
|
if (!tab.setupState.active) {
|
||||||
|
offset += $el[`client${capitalize(sizeName)}`]
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
tabSize = $el[`client${capitalize(sizeName)}`]
|
||||||
|
|
||||||
|
const tabStyles = window.getComputedStyle($el)
|
||||||
|
|
||||||
|
if (sizeName === 'width') {
|
||||||
|
if (props.tabs.length > 1) {
|
||||||
|
tabSize -= parseFloat(tabStyles.paddingLeft) + parseFloat(tabStyles.paddingRight)
|
||||||
|
}
|
||||||
|
offset += parseFloat(tabStyles.paddingLeft)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const transform = `translate${capitalize(sizeDir)}(${offset}px)`
|
||||||
|
style[sizeName] = `${tabSize}px`
|
||||||
|
style.transform = transform
|
||||||
|
style.msTransform = transform
|
||||||
|
style.webkitTransform = transform
|
||||||
|
|
||||||
|
return style
|
||||||
|
}
|
||||||
|
|
||||||
|
const barStyle = ref(getBarStyle())
|
||||||
|
|
||||||
|
watch(() => props.tabs, () => {
|
||||||
|
nextTick(() => {
|
||||||
|
barStyle.value = getBarStyle()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
rootTabs,
|
||||||
|
barStyle,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
380
packages/tabs/src/tab-nav.vue
Normal file
380
packages/tabs/src/tab-nav.vue
Normal file
@ -0,0 +1,380 @@
|
|||||||
|
<script lang='ts'>
|
||||||
|
import { h, defineComponent, ref, inject, computed, onUpdated, onMounted, onBeforeUnmount } from 'vue'
|
||||||
|
import { addResizeListener, removeResizeListener } from '@element-plus/utils/resize-event'
|
||||||
|
import TabBar from './tab-bar.vue'
|
||||||
|
import { NOOP, capitalize } from '@vue/shared'
|
||||||
|
|
||||||
|
type RefElement = Nullable<HTMLElement>
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'ElTabNav',
|
||||||
|
components: {
|
||||||
|
TabBar,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
panes: {
|
||||||
|
type: Array as PropType<ComponentInternalInstance[]>,
|
||||||
|
default: () => ([] as ComponentInternalInstance[]),
|
||||||
|
},
|
||||||
|
currentName: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
editable: Boolean,
|
||||||
|
onTabClick: {
|
||||||
|
type: Function,
|
||||||
|
default: NOOP,
|
||||||
|
},
|
||||||
|
onTabRemove: {
|
||||||
|
type: Function,
|
||||||
|
default: NOOP,
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
stretch: Boolean,
|
||||||
|
},
|
||||||
|
setup() {
|
||||||
|
const rootTabs = inject('rootTabs')
|
||||||
|
if (!rootTabs) {
|
||||||
|
throw new Error(`ElTabNav must use with ElTabs`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const scrollable = ref(false)
|
||||||
|
const navOffset = ref(0)
|
||||||
|
const isFocus = ref(false)
|
||||||
|
const focusable = ref(true)
|
||||||
|
|
||||||
|
const navScroll$ = ref<RefElement>(null)
|
||||||
|
const nav$ = ref<RefElement>(null)
|
||||||
|
const el$ = ref<RefElement>(null)
|
||||||
|
|
||||||
|
const sizeName = computed(() => {
|
||||||
|
return ['top', 'bottom'].includes(rootTabs.props.tabPosition) ? 'width' : 'height'
|
||||||
|
})
|
||||||
|
const navStyle = computed(() => {
|
||||||
|
const dir = sizeName.value === 'width' ? 'X' : 'Y'
|
||||||
|
return {
|
||||||
|
transform: `translate${dir}(-${navOffset.value}px)`,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const scrollPrev = () => {
|
||||||
|
const containerSize = navScroll$.value[`offset${capitalize(sizeName.value)}`]
|
||||||
|
const currentOffset = navOffset.value
|
||||||
|
|
||||||
|
if (!currentOffset) return
|
||||||
|
|
||||||
|
let newOffset = currentOffset > containerSize
|
||||||
|
? currentOffset - containerSize
|
||||||
|
: 0
|
||||||
|
|
||||||
|
navOffset.value = newOffset
|
||||||
|
}
|
||||||
|
|
||||||
|
const scrollNext = () => {
|
||||||
|
const navSize = nav$.value[`offset${capitalize(sizeName.value)}`]
|
||||||
|
const containerSize = navScroll$.value[`offset${capitalize(sizeName.value)}`]
|
||||||
|
const currentOffset = navOffset.value
|
||||||
|
|
||||||
|
if (navSize - currentOffset <= containerSize) return
|
||||||
|
|
||||||
|
let newOffset = navSize - currentOffset > containerSize * 2
|
||||||
|
? currentOffset + containerSize
|
||||||
|
: (navSize - containerSize)
|
||||||
|
|
||||||
|
navOffset.value = newOffset
|
||||||
|
}
|
||||||
|
|
||||||
|
const scrollToActiveTab = () => {
|
||||||
|
if (!scrollable.value) return
|
||||||
|
const nav = nav$.value
|
||||||
|
const activeTab = el$.value.querySelector('.is-active')
|
||||||
|
if (!activeTab) return
|
||||||
|
const navScroll = navScroll$.value
|
||||||
|
const isHorizontal = ['top', 'bottom'].includes(rootTabs.props.tabPosition)
|
||||||
|
const activeTabBounding = activeTab.getBoundingClientRect()
|
||||||
|
const navScrollBounding = navScroll.getBoundingClientRect()
|
||||||
|
const maxOffset = isHorizontal
|
||||||
|
? nav.offsetWidth - navScrollBounding.width
|
||||||
|
: nav.offsetHeight - navScrollBounding.height
|
||||||
|
const currentOffset = navOffset.value
|
||||||
|
let newOffset = currentOffset
|
||||||
|
|
||||||
|
if (isHorizontal) {
|
||||||
|
if (activeTabBounding.left < navScrollBounding.left) {
|
||||||
|
newOffset = currentOffset - (navScrollBounding.left - activeTabBounding.left)
|
||||||
|
}
|
||||||
|
if (activeTabBounding.right > navScrollBounding.right) {
|
||||||
|
newOffset = currentOffset + activeTabBounding.right - navScrollBounding.right
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (activeTabBounding.top < navScrollBounding.top) {
|
||||||
|
newOffset = currentOffset - (navScrollBounding.top - activeTabBounding.top)
|
||||||
|
}
|
||||||
|
if (activeTabBounding.bottom > navScrollBounding.bottom) {
|
||||||
|
newOffset = currentOffset + (activeTabBounding.bottom - navScrollBounding.bottom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newOffset = Math.max(newOffset, 0)
|
||||||
|
navOffset.value = Math.min(newOffset, maxOffset)
|
||||||
|
}
|
||||||
|
|
||||||
|
const update = () => {
|
||||||
|
if (!nav$.value) return
|
||||||
|
const navSize = nav$.value[`offset${capitalize(sizeName.value)}`]
|
||||||
|
const containerSize = navScroll$.value[`offset${capitalize(sizeName.value)}`]
|
||||||
|
const currentOffset = navOffset.value
|
||||||
|
|
||||||
|
if (containerSize < navSize) {
|
||||||
|
const currentOffset = navOffset.value
|
||||||
|
scrollable.value = scrollable.value || {}
|
||||||
|
scrollable.value.prev = currentOffset
|
||||||
|
scrollable.value.next = currentOffset + containerSize < navSize
|
||||||
|
if (navSize - currentOffset < containerSize) {
|
||||||
|
navOffset.value = navSize - containerSize
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
scrollable.value = false
|
||||||
|
if (currentOffset > 0) {
|
||||||
|
navOffset.value = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const changeTab = (e) => {
|
||||||
|
const keyCode = e.keyCode
|
||||||
|
let nextIndex
|
||||||
|
let currentIndex, tabList
|
||||||
|
if ([37, 38, 39, 40].indexOf(keyCode) !== -1) { // 左右上下键更换tab
|
||||||
|
tabList = e.currentTarget.querySelectorAll('[role=tab]')
|
||||||
|
currentIndex = Array.prototype.indexOf.call(tabList, e.target)
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (keyCode === 37 || keyCode === 38) { // left
|
||||||
|
if (currentIndex === 0) { // first
|
||||||
|
nextIndex = tabList.length - 1
|
||||||
|
} else {
|
||||||
|
nextIndex = currentIndex - 1
|
||||||
|
}
|
||||||
|
} else { // right
|
||||||
|
if (currentIndex < tabList.length - 1) { // not last
|
||||||
|
nextIndex = currentIndex + 1
|
||||||
|
} else {
|
||||||
|
nextIndex = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tabList[nextIndex].focus() // 改变焦点元素
|
||||||
|
tabList[nextIndex].click() // 选中下一个tab
|
||||||
|
setFocus()
|
||||||
|
}
|
||||||
|
|
||||||
|
const setFocus = () => {
|
||||||
|
if (focusable.value) {
|
||||||
|
isFocus.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeFocus = () => {
|
||||||
|
isFocus.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const visibilityChangeHandler = () => {
|
||||||
|
const visibility = document.visibilityState
|
||||||
|
if (visibility === 'hidden') {
|
||||||
|
focusable.value = false
|
||||||
|
} else if (visibility === 'visible') {
|
||||||
|
setTimeout(() => {
|
||||||
|
focusable.value = true
|
||||||
|
}, 50)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const windowBlurHandler = () => {
|
||||||
|
focusable.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const windowFocusHandler = () => {
|
||||||
|
setTimeout(() => {
|
||||||
|
focusable.value = true
|
||||||
|
}, 50)
|
||||||
|
}
|
||||||
|
|
||||||
|
onUpdated(() => {
|
||||||
|
update()
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
addResizeListener(el$.value, update)
|
||||||
|
document.addEventListener('visibilitychange', visibilityChangeHandler)
|
||||||
|
window.addEventListener('blur', windowBlurHandler)
|
||||||
|
window.addEventListener('focus', windowFocusHandler)
|
||||||
|
setTimeout(() => {
|
||||||
|
scrollToActiveTab()
|
||||||
|
}, 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if(el$.value) {
|
||||||
|
removeResizeListener(el$.value, update)
|
||||||
|
}
|
||||||
|
document.removeEventListener('visibilitychange', visibilityChangeHandler)
|
||||||
|
window.removeEventListener('blur', windowBlurHandler)
|
||||||
|
window.removeEventListener('focus', windowFocusHandler)
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
rootTabs,
|
||||||
|
|
||||||
|
scrollable,
|
||||||
|
navOffset,
|
||||||
|
isFocus,
|
||||||
|
focusable,
|
||||||
|
|
||||||
|
navScroll$,
|
||||||
|
nav$,
|
||||||
|
el$,
|
||||||
|
|
||||||
|
sizeName,
|
||||||
|
navStyle,
|
||||||
|
|
||||||
|
scrollPrev,
|
||||||
|
scrollNext,
|
||||||
|
scrollToActiveTab,
|
||||||
|
update,
|
||||||
|
changeTab,
|
||||||
|
setFocus,
|
||||||
|
removeFocus,
|
||||||
|
visibilityChangeHandler,
|
||||||
|
windowBlurHandler,
|
||||||
|
windowFocusHandler,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
type,
|
||||||
|
panes,
|
||||||
|
editable,
|
||||||
|
stretch,
|
||||||
|
onTabClick,
|
||||||
|
onTabRemove,
|
||||||
|
navStyle,
|
||||||
|
scrollable,
|
||||||
|
scrollNext,
|
||||||
|
scrollPrev,
|
||||||
|
changeTab,
|
||||||
|
setFocus,
|
||||||
|
removeFocus,
|
||||||
|
rootTabs,
|
||||||
|
isFocus,
|
||||||
|
} = this
|
||||||
|
|
||||||
|
const scrollBtn = scrollable ? [
|
||||||
|
h(
|
||||||
|
'span',
|
||||||
|
{
|
||||||
|
class: ['el-tabs__nav-prev', scrollable.prev ? '' : 'is-disabled'],
|
||||||
|
onClick: scrollPrev,
|
||||||
|
},
|
||||||
|
[h('i', { class: 'el-icon-arrow-left' })],
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
'span',
|
||||||
|
{
|
||||||
|
class: ['el-tabs__nav-next', scrollable.next ? '' : 'is-disabled'],
|
||||||
|
onClick: scrollNext,
|
||||||
|
},
|
||||||
|
[h('i', { class: 'el-icon-arrow-right' })],
|
||||||
|
),
|
||||||
|
] : null
|
||||||
|
|
||||||
|
const tabs = panes.map((pane, index) => {
|
||||||
|
let tabName = pane.props.name || pane.setupState.index || `${index}`
|
||||||
|
const closable = pane.setupState.isClosable || editable
|
||||||
|
|
||||||
|
pane.setupState.index = `${index}`
|
||||||
|
|
||||||
|
const btnClose = closable ?
|
||||||
|
h(
|
||||||
|
'span',
|
||||||
|
{
|
||||||
|
class: 'el-icon-close',
|
||||||
|
onClick: (ev) => { onTabRemove(pane, ev) },
|
||||||
|
},
|
||||||
|
) : null
|
||||||
|
|
||||||
|
const tabLabelContent = pane.slots.label?.() || pane.props.label
|
||||||
|
const tabindex = pane.setupState.active ? 0 : -1
|
||||||
|
|
||||||
|
return h(
|
||||||
|
'div',
|
||||||
|
{
|
||||||
|
class: {
|
||||||
|
'el-tabs__item': true,
|
||||||
|
[`is-${ rootTabs.props.tabPosition }`]: true,
|
||||||
|
'is-active': pane.setupState.active,
|
||||||
|
'is-disabled': pane.props.disabled,
|
||||||
|
'is-closable': closable,
|
||||||
|
'is-focus': isFocus,
|
||||||
|
},
|
||||||
|
id: `tab-${tabName}`,
|
||||||
|
key: `tab-${tabName}`,
|
||||||
|
'aria-controls': `pane-${tabName}`,
|
||||||
|
role: 'tab',
|
||||||
|
'aria-selected': pane.setupState.active ,
|
||||||
|
ref: `tab-${tabName}`,
|
||||||
|
tabindex: tabindex,
|
||||||
|
onFocus: () => { setFocus() },
|
||||||
|
onBlur: () => { removeFocus() },
|
||||||
|
onClick: (ev) => { removeFocus(); onTabClick(pane, tabName, ev) },
|
||||||
|
onKeydown: (ev) => { if (closable && (ev.keyCode === 46 || ev.keyCode === 8)) { onTabRemove(pane, ev)} },
|
||||||
|
},
|
||||||
|
[tabLabelContent, btnClose],
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
return h(
|
||||||
|
'div',
|
||||||
|
{
|
||||||
|
ref: 'el$',
|
||||||
|
class: ['el-tabs__nav-wrap', scrollable ? 'is-scrollable' : '', `is-${ rootTabs.props.tabPosition }`],
|
||||||
|
},
|
||||||
|
[
|
||||||
|
scrollBtn,
|
||||||
|
h(
|
||||||
|
'div',
|
||||||
|
{
|
||||||
|
class: 'el-tabs__nav-scroll',
|
||||||
|
ref: 'navScroll$',
|
||||||
|
},
|
||||||
|
[
|
||||||
|
h(
|
||||||
|
'div',
|
||||||
|
{
|
||||||
|
class: ['el-tabs__nav', `is-${ rootTabs.props.tabPosition }`, stretch && ['top', 'bottom'].includes(rootTabs.props.tabPosition) ? 'is-stretch' : ''],
|
||||||
|
ref: 'nav$',
|
||||||
|
style: navStyle,
|
||||||
|
role: 'tablist',
|
||||||
|
onKeydown: changeTab,
|
||||||
|
},
|
||||||
|
[
|
||||||
|
!type ? h(
|
||||||
|
TabBar,
|
||||||
|
{
|
||||||
|
tabs: panes,
|
||||||
|
},
|
||||||
|
) : null,
|
||||||
|
tabs,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
</style>
|
74
packages/tabs/src/tab-pane.vue
Normal file
74
packages/tabs/src/tab-pane.vue
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
v-if="shouldBeRender"
|
||||||
|
v-show="active"
|
||||||
|
:id="`pane-${paneName}`"
|
||||||
|
class="el-tab-pane"
|
||||||
|
role="tabpanel"
|
||||||
|
:aria-hidden="!active"
|
||||||
|
:aria-labelledby="`tab-${paneName}`"
|
||||||
|
>
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang='ts'>
|
||||||
|
import { defineComponent, ref, computed, inject } from 'vue'
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'ElTabPane',
|
||||||
|
props: {
|
||||||
|
label: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
labelContent: {
|
||||||
|
type: Function,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
closable: Boolean,
|
||||||
|
disabled: Boolean,
|
||||||
|
lazy: Boolean,
|
||||||
|
},
|
||||||
|
setup(props) {
|
||||||
|
const index = ref(null)
|
||||||
|
const loaded = ref(false)
|
||||||
|
const rootTabs = inject('rootTabs')
|
||||||
|
|
||||||
|
if (!rootTabs) {
|
||||||
|
throw new Error(`ElTabPane must use with ElTabs`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const isClosable = computed(() => {
|
||||||
|
return props.closable || rootTabs.props.closable
|
||||||
|
})
|
||||||
|
|
||||||
|
const active = computed(() => {
|
||||||
|
const active = rootTabs.setupState.currentName === (props.name || index.value)
|
||||||
|
if (active) {
|
||||||
|
loaded.value = true
|
||||||
|
}
|
||||||
|
return active
|
||||||
|
})
|
||||||
|
|
||||||
|
const paneName = computed(() => {
|
||||||
|
return props.name || index.value
|
||||||
|
})
|
||||||
|
|
||||||
|
const shouldBeRender = computed(() => {
|
||||||
|
return (!props.lazy || loaded.value) || active.value
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
index,
|
||||||
|
loaded,
|
||||||
|
isClosable,
|
||||||
|
active,
|
||||||
|
paneName,
|
||||||
|
shouldBeRender,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
232
packages/tabs/src/tabs.vue
Normal file
232
packages/tabs/src/tabs.vue
Normal file
@ -0,0 +1,232 @@
|
|||||||
|
<script lang='ts'>
|
||||||
|
import { h, defineComponent, ref, onMounted, onUpdated, provide, watch, nextTick, getCurrentInstance, ComponentInternalInstance } from 'vue'
|
||||||
|
import { Fragment } from '@vue/runtime-core'
|
||||||
|
import TabNav from './tab-nav.vue'
|
||||||
|
|
||||||
|
type RefElement = Nullable<HTMLElement>
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'ElTabs',
|
||||||
|
components: { TabNav },
|
||||||
|
props: {
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
activeName: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
closable: Boolean,
|
||||||
|
addable: Boolean,
|
||||||
|
modelValue: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
editable: Boolean,
|
||||||
|
tabPosition: {
|
||||||
|
type: String,
|
||||||
|
default: 'top',
|
||||||
|
},
|
||||||
|
beforeLeave: {
|
||||||
|
type: Function,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
stretch: Boolean,
|
||||||
|
},
|
||||||
|
emits: ['tab-click', 'edit', 'tab-remove', 'tab-add', 'input', 'update:modelValue'],
|
||||||
|
setup(props, ctx) {
|
||||||
|
const nav$ = ref<RefElement>(null)
|
||||||
|
const currentName = ref(props.modelValue || props.activeName || '0')
|
||||||
|
const panes = ref<ComponentInternalInstance[]>([])
|
||||||
|
const instance = getCurrentInstance()
|
||||||
|
|
||||||
|
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, paneInstanceList = []) => {
|
||||||
|
[...(vnode.children || [])].forEach((node) => {
|
||||||
|
let type = node.type
|
||||||
|
type = type.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 = [...children].find(({ props }) => {
|
||||||
|
return props.class === 'el-tabs__content'
|
||||||
|
})
|
||||||
|
|
||||||
|
if(!content) return
|
||||||
|
|
||||||
|
const paneInstanceList = getPaneInstanceFromSlot(content)
|
||||||
|
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) => {
|
||||||
|
if(currentName.value !== value && props.beforeLeave) {
|
||||||
|
const before = props.beforeLeave(value, currentName.value)
|
||||||
|
if(before && before.then) {
|
||||||
|
before.then(() => {
|
||||||
|
changeCurrentName(value)
|
||||||
|
nav$.value && nav$.value.removeFocus()
|
||||||
|
}, () => {
|
||||||
|
// ignore promise rejection in `before-leave` hook
|
||||||
|
})
|
||||||
|
} else if(before !== false) {
|
||||||
|
changeCurrentName(value)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
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')
|
||||||
|
}
|
||||||
|
|
||||||
|
provide('rootTabs', getCurrentInstance())
|
||||||
|
|
||||||
|
onUpdated(() => {
|
||||||
|
setPaneInstances()
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
setPaneInstances()
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
nav$,
|
||||||
|
handleTabClick,
|
||||||
|
handleTabRemove,
|
||||||
|
handleTabAdd,
|
||||||
|
currentName,
|
||||||
|
panes,
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
render(){
|
||||||
|
let {
|
||||||
|
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.keyCode === 13) { 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],
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
</style>
|
Loading…
Reference in New Issue
Block a user