mirror of
https://github.com/element-plus/element-plus.git
synced 2026-03-13 07:51:17 +08:00
fix(components): [tabs] update tabs order correctly when reordering (#21064)
* fix(components): [tabs] update tabs order correctly when reordering * fix * perf: reorder only if child components have been moved * chore: tweak the test --------- Co-authored-by: dopamine <coderzyou@gmail.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import type { InjectionKey, Ref } from 'vue'
|
||||
import type { InjectionKey, Ref, VNode } from 'vue'
|
||||
import type { CarouselItemProps } from './carousel-item'
|
||||
|
||||
export type CarouselItemStates = {
|
||||
@@ -15,6 +15,7 @@ export type CarouselItemContext = {
|
||||
props: CarouselItemProps
|
||||
states: CarouselItemStates
|
||||
uid: number
|
||||
getVnode: () => VNode
|
||||
translateItem: (index: number, activeIndex: number, oldIndex?: number) => void
|
||||
}
|
||||
|
||||
|
||||
@@ -144,6 +144,7 @@ export const useCarouselItem = (props: CarouselItemProps) => {
|
||||
animating,
|
||||
}),
|
||||
uid: instance.uid,
|
||||
getVnode: () => instance.vnode,
|
||||
translateItem,
|
||||
}
|
||||
|
||||
|
||||
@@ -155,6 +155,7 @@ watch(
|
||||
const _panel = reactive({
|
||||
el: panelEl.value!,
|
||||
uid,
|
||||
getVnode: () => instance.vnode,
|
||||
setIndex,
|
||||
...props,
|
||||
collapsible: getCollapsible(props.collapsible),
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import type { InjectionKey, UnwrapRef } from 'vue'
|
||||
import type { InjectionKey, UnwrapRef, VNode } from 'vue'
|
||||
|
||||
export type Layout = 'horizontal' | 'vertical'
|
||||
|
||||
export type PanelItemState = UnwrapRef<{
|
||||
uid: number
|
||||
getVnode: () => VNode
|
||||
el: HTMLElement
|
||||
collapsible: { start?: boolean; end?: boolean }
|
||||
max?: number | string
|
||||
|
||||
@@ -62,11 +62,12 @@ import { isNumber } from '@element-plus/utils'
|
||||
import { stepProps } from './item'
|
||||
import { STEPS_INJECTION_KEY } from './tokens'
|
||||
|
||||
import type { CSSProperties, Ref, VNode } from 'vue'
|
||||
import type { StepsProps } from './steps'
|
||||
import type { CSSProperties, Ref } from 'vue'
|
||||
|
||||
export interface StepItemState {
|
||||
uid: number
|
||||
getVnode: () => VNode
|
||||
currentStatus: string
|
||||
setIndex: (val: number) => void
|
||||
calcProgress: (status: string) => void
|
||||
@@ -192,6 +193,7 @@ const updateStatus = (activeIndex: number) => {
|
||||
|
||||
const stepItemState = reactive({
|
||||
uid: currentInstance.uid,
|
||||
getVnode: () => currentInstance.vnode,
|
||||
currentStatus,
|
||||
setIndex,
|
||||
calcProgress,
|
||||
|
||||
@@ -950,4 +950,45 @@ describe('Tabs.vue', () => {
|
||||
// Verify the model value has been updated
|
||||
expect(activeName.value).toBe('tab2')
|
||||
})
|
||||
|
||||
test('tab order should update when v-for array is reordered', async () => {
|
||||
const itemList = ref([
|
||||
{ key: 'a', value: 'A' },
|
||||
{ key: 'b', value: 'B' },
|
||||
{ key: 'c', value: 'C' },
|
||||
{ key: 'd', value: 'D' },
|
||||
])
|
||||
|
||||
const wrapper = mount(() => (
|
||||
<Tabs>
|
||||
{itemList.value.map((item) => (
|
||||
<TabPane key={item.key} label={item.value}></TabPane>
|
||||
))}
|
||||
</Tabs>
|
||||
))
|
||||
|
||||
await nextTick()
|
||||
|
||||
const navWrapper = wrapper.findComponent(TabNav)
|
||||
let navItemsWrapper = navWrapper.findAll('.el-tabs__item')
|
||||
|
||||
// Check initial order
|
||||
expect(navItemsWrapper[0].text()).toContain('A')
|
||||
expect(navItemsWrapper[1].text()).toContain('B')
|
||||
expect(navItemsWrapper[2].text()).toContain('C')
|
||||
expect(navItemsWrapper[3].text()).toContain('D')
|
||||
|
||||
// Reverse the array
|
||||
itemList.value.reverse()
|
||||
|
||||
await nextTick()
|
||||
|
||||
navItemsWrapper = navWrapper.findAll('.el-tabs__item')
|
||||
|
||||
// Check that the order has updated
|
||||
expect(navItemsWrapper[0].text()).toContain('D')
|
||||
expect(navItemsWrapper[1].text()).toContain('C')
|
||||
expect(navItemsWrapper[2].text()).toContain('B')
|
||||
expect(navItemsWrapper[3].text()).toContain('A')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
import type { ComputedRef, InjectionKey, Ref, Slots, UnwrapRef } from 'vue'
|
||||
import type {
|
||||
ComputedRef,
|
||||
InjectionKey,
|
||||
Ref,
|
||||
Slots,
|
||||
UnwrapRef,
|
||||
VNode,
|
||||
} from 'vue'
|
||||
import type { TabsProps } from './tabs'
|
||||
import type { TabPaneProps } from './tab-pane'
|
||||
|
||||
@@ -6,6 +13,7 @@ export type TabPaneName = string | number
|
||||
|
||||
export type TabsPaneContext = UnwrapRef<{
|
||||
uid: number
|
||||
getVnode: () => VNode
|
||||
slots: Slots
|
||||
props: TabPaneProps
|
||||
paneName: ComputedRef<TabPaneName | undefined>
|
||||
|
||||
@@ -67,6 +67,7 @@ watch(active, (val) => {
|
||||
|
||||
const pane = reactive({
|
||||
uid: instance.uid,
|
||||
getVnode: () => instance.vnode,
|
||||
slots,
|
||||
props,
|
||||
paneName,
|
||||
|
||||
@@ -1,8 +1,20 @@
|
||||
import { defineComponent, h, isVNode, shallowRef, triggerRef } from 'vue'
|
||||
import {
|
||||
defineComponent,
|
||||
h,
|
||||
isVNode,
|
||||
onMounted,
|
||||
shallowRef,
|
||||
triggerRef,
|
||||
} from 'vue'
|
||||
import { flattedChildren } from '@element-plus/utils'
|
||||
|
||||
import type { ComponentInternalInstance, VNode } from 'vue'
|
||||
|
||||
type ChildEssential = {
|
||||
uid: number
|
||||
getVnode: () => VNode
|
||||
}
|
||||
|
||||
const getOrderedChildren = <T>(
|
||||
vm: ComponentInternalInstance,
|
||||
childComponentName: string,
|
||||
@@ -18,21 +30,53 @@ const getOrderedChildren = <T>(
|
||||
return uids.map((uid) => children[uid]).filter((p) => !!p)
|
||||
}
|
||||
|
||||
export const useOrderedChildren = <T extends { uid: number }>(
|
||||
export const useOrderedChildren = <T extends ChildEssential>(
|
||||
vm: ComponentInternalInstance,
|
||||
childComponentName: string
|
||||
) => {
|
||||
const children = shallowRef<Record<number, T>>({})
|
||||
const orderedChildren = shallowRef<T[]>([])
|
||||
const nodesMap = new WeakMap<ParentNode, Node[]>()
|
||||
|
||||
const addChild = (child: T) => {
|
||||
children.value[child.uid] = child
|
||||
triggerRef(children)
|
||||
|
||||
onMounted(() => {
|
||||
const childNode = child.getVnode().el! as Node
|
||||
const parentNode = childNode.parentNode!
|
||||
|
||||
if (!nodesMap.has(parentNode)) {
|
||||
nodesMap.set(parentNode, [])
|
||||
|
||||
const originalFn = parentNode.insertBefore.bind(parentNode)
|
||||
parentNode.insertBefore = <T extends Node>(
|
||||
node: T,
|
||||
anchor: Node | null
|
||||
) => {
|
||||
// Schedule a job to update `orderedChildren` if the root element of child components is moved
|
||||
const shouldSortChildren = nodesMap
|
||||
.get(parentNode)!
|
||||
.some((el) => node === el || anchor === el)
|
||||
if (shouldSortChildren) triggerRef(children)
|
||||
|
||||
return originalFn(node, anchor)
|
||||
}
|
||||
}
|
||||
|
||||
nodesMap.get(parentNode)!.push(childNode)
|
||||
})
|
||||
}
|
||||
|
||||
const removeChild = (child: T) => {
|
||||
delete children.value[child.uid]
|
||||
triggerRef(children)
|
||||
|
||||
const childNode = child.getVnode().el! as Node
|
||||
const parentNode = childNode.parentNode!
|
||||
const childNodes = nodesMap.get(parentNode)!
|
||||
const index = childNodes.indexOf(childNode)
|
||||
childNodes.splice(index, 1)
|
||||
}
|
||||
|
||||
const sortChildren = () => {
|
||||
|
||||
Reference in New Issue
Block a user