Skip to content

Commit 2c2f6a2

Browse files
yichenyYunYouJun
andauthored
feat(components): [tabs] add default-value prop, suppress flicker (element-plus#22815)
closed element-plus#22776 Co-authored-by: 云游君 <me@yunyoujun.cn>
1 parent 0df5270 commit 2c2f6a2

File tree

4 files changed

+63
-2
lines changed

4 files changed

+63
-2
lines changed

docs/en-US/component/tabs.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ tabs/customized-trigger
9090
| Name | Description | Type | Default |
9191
| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | ---------- |
9292
| model-value / v-model | binding value, name of the selected tab, the default value is the name of first tab | ^[string] / ^[number] ||
93+
| default-value | initial value when `model-value` is not set | ^[string] / ^[number] ||
9394
| type | type of Tab | ^[enum]`'' \| 'card' \| 'border-card'` | '' |
9495
| closable | whether Tab is closable | ^[boolean] | false |
9596
| addable | whether Tab is addable | ^[boolean] | false |

packages/components/tabs/__tests__/tabs.test.tsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,38 @@ describe('Tabs.vue', () => {
100100
expect(tabsWrapper.vm.$.exposed!.currentName.value).toEqual('c')
101101
})
102102

103+
test('default-value', async () => {
104+
const activeName = ref<TabPaneName | undefined>()
105+
const wrapper = mount(() => (
106+
<Tabs v-model={activeName.value} defaultValue="b">
107+
<TabPane name="a" label="label-1">
108+
A
109+
</TabPane>
110+
<TabPane name="b" label="label-2">
111+
B
112+
</TabPane>
113+
<TabPane name="c" label="label-3">
114+
C
115+
</TabPane>
116+
</Tabs>
117+
))
118+
119+
const tabsWrapper = wrapper.findComponent(Tabs)
120+
const navWrapper = wrapper.findComponent(TabNav)
121+
await nextTick()
122+
123+
let navItemsWrapper = navWrapper.findAll('.el-tabs__item')
124+
expect(navItemsWrapper[1].classes('is-active')).toBe(true)
125+
expect(tabsWrapper.vm.$.exposed!.currentName.value).toEqual('b')
126+
127+
activeName.value = 'c'
128+
await nextTick()
129+
130+
navItemsWrapper = navWrapper.findAll('.el-tabs__item')
131+
expect(navItemsWrapper[2].classes('is-active')).toBe(true)
132+
expect(tabsWrapper.vm.$.exposed!.currentName.value).toEqual('c')
133+
})
134+
103135
test('card', async () => {
104136
const wrapper = mount(() => (
105137
<Tabs type="card">

packages/components/tabs/src/tab-bar.vue

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
<template>
22
<div
3+
v-if="shouldRenderBar"
34
ref="barRef"
45
:class="[ns.e('active-bar'), ns.is(rootTabs!.props.tabPosition)]"
56
:style="barStyle"
67
/>
78
</template>
89

910
<script lang="ts" setup>
10-
import { inject, nextTick, onBeforeUnmount, ref, watch } from 'vue'
11+
import { computed, inject, nextTick, onBeforeUnmount, ref, watch } from 'vue'
1112
import { useResizeObserver } from '@vueuse/core'
1213
import { capitalize, isUndefined, throwError } from '@element-plus/utils'
1314
import { useNamespace } from '@element-plus/hooks'
@@ -29,6 +30,15 @@ const ns = useNamespace('tabs')
2930
3031
const barRef = ref<HTMLDivElement>()
3132
const barStyle = ref<CSSProperties>()
33+
const shouldDisableInitialTransition = computed(
34+
() =>
35+
isUndefined(rootTabs.props.modelValue) &&
36+
!isUndefined(rootTabs.props.defaultValue)
37+
)
38+
const shouldRenderBar = computed(
39+
() =>
40+
!shouldDisableInitialTransition.value || Boolean(barStyle.value?.transform)
41+
)
3242
3343
const getBarStyle = (): CSSProperties => {
3444
let offset = 0

packages/components/tabs/src/tabs.tsx

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@ export const tabsProps = buildProps({
5353
modelValue: {
5454
type: [String, Number],
5555
},
56+
/**
57+
* @description initial value when `model-value` is not set
58+
*/
59+
defaultValue: {
60+
type: [String, Number],
61+
},
5662
/**
5763
* @description whether Tab is addable and closable
5864
*/
@@ -126,7 +132,10 @@ const Tabs = defineComponent({
126132
} = useOrderedChildren<TabsPaneContext>(getCurrentInstance()!, 'ElTabPane')
127133

128134
const nav$ = ref<TabNavInstance>()
129-
const currentName = ref<TabPaneName>(props.modelValue ?? '0')
135+
const currentName = ref<TabPaneName>(
136+
(isUndefined(props.modelValue) ? props.defaultValue : props.modelValue) ??
137+
'0'
138+
)
130139

131140
const setCurrentName = async (value?: TabPaneName, trigger = false) => {
132141
// should do nothing.
@@ -209,6 +218,15 @@ const Tabs = defineComponent({
209218
(modelValue) => setCurrentName(modelValue)
210219
)
211220

221+
watch(
222+
() => props.defaultValue,
223+
(defaultValue) => {
224+
if (isUndefined(props.modelValue)) {
225+
setCurrentName(defaultValue)
226+
}
227+
}
228+
)
229+
212230
watch(currentName, async () => {
213231
await nextTick()
214232
nav$.value?.scrollToActiveTab()

0 commit comments

Comments
 (0)