diff --git a/packages/components/menu/src/menu.ts b/packages/components/menu/src/menu.ts index ac6ffe9e3b..9f011262f3 100644 --- a/packages/components/menu/src/menu.ts +++ b/packages/components/menu/src/menu.ts @@ -30,7 +30,7 @@ import { useMenuCssVar } from './use-menu-css-var' import type { MenuItemClicked, MenuProvider, SubMenuProvider } from './types' import type { NavigationFailure, Router } from 'vue-router' -import type { ExtractPropTypes, VNode } from 'vue' +import type { ExtractPropTypes, VNode, VNodeArrayChildren } from 'vue' import type { UseResizeObserverReturn } from '@vueuse/core' export const menuProps = buildProps({ @@ -358,11 +358,11 @@ export default defineComponent({ } return () => { - let slot = slots.default?.() ?? [] + let slot: VNodeArrayChildren = slots.default?.() ?? [] const vShowMore: VNode[] = [] if (props.mode === 'horizontal' && menu.value) { - const originalSlot = flattedChildren(slot) + const originalSlot = flattedChildren(slot) as VNodeArrayChildren const slotDefault = sliceIndex.value === -1 ? originalSlot diff --git a/packages/components/tabs/src/tabs.tsx b/packages/components/tabs/src/tabs.tsx index 4582700e21..e23570734e 100644 --- a/packages/components/tabs/src/tabs.tsx +++ b/packages/components/tabs/src/tabs.tsx @@ -4,16 +4,15 @@ import { getCurrentInstance, nextTick, provide, - reactive, ref, renderSlot, + shallowReactive, + shallowRef, watch, } from 'vue' import { buildProps, definePropType, - flattedChildren, - getFirstValidNode, isNumber, isString, isUndefined, @@ -24,10 +23,11 @@ import { Plus } from '@element-plus/icons-vue' import { tabsRootContextKey } from '@element-plus/tokens' import { useDeprecated, useNamespace } from '@element-plus/hooks' import TabNav from './tab-nav' +import { getOrderedPanes } from './utils/pane' import type { TabNavInstance } from './tab-nav' import type { TabsPaneContext } from '@element-plus/tokens' -import type { ExtractPropTypes, VNode, VNodeNormalizedChildren } from 'vue' +import type { ExtractPropTypes } from 'vue' import type { Awaitable } from '@element-plus/utils' export type TabPanelName = string | number @@ -79,6 +79,8 @@ export const tabsEmits = { } export type TabsEmits = typeof tabsEmits +export type TabsPanes = Record + export default defineComponent({ name: 'ElTabs', @@ -91,8 +93,8 @@ export default defineComponent({ const ns = useNamespace('tabs') const nav$ = ref() - const panes = reactive>({}) - const orderedPanes = ref>([]) + const panes = shallowReactive({}) + const orderedPanes = shallowRef([]) const currentName = ref( props.modelValue ?? props.activeName ?? '0' ) @@ -172,39 +174,15 @@ export default defineComponent({ nav$.value?.scrollToActiveTab() }) - // panes-order control { - const calcOrderedPanes = () => { - const tabContentChildren = getTabContentChildren() - orderedPanes.value = tabContentChildren.map( - (node) => panes[node.component.uid] - ) - } - - const getTabContentChildren = () => { - const node = vm.subTree - if (Array.isArray(node?.children)) { - const nodeTabContent = node.children.find((x) => { - return (x as VNode)?.props?.class === ns.e('content') - }) - const validNodeTabContent = getFirstValidNode( - nodeTabContent as VNodeNormalizedChildren - ) - const nodePanes = flattedChildren( - validNodeTabContent as VNodeNormalizedChildren - ) - return nodePanes - } - return [] - } - const registerPane = (pane: TabsPaneContext) => { panes[pane.uid] = pane - calcOrderedPanes() + orderedPanes.value = getOrderedPanes(vm, panes) } + const unregisterPane = (uid: number) => { delete panes[uid] - calcOrderedPanes() + orderedPanes.value = getOrderedPanes(vm, panes) } provide(tabsRootContextKey, { diff --git a/packages/components/tabs/src/utils/pane.ts b/packages/components/tabs/src/utils/pane.ts new file mode 100644 index 0000000000..b43b1a000d --- /dev/null +++ b/packages/components/tabs/src/utils/pane.ts @@ -0,0 +1,20 @@ +import { flattedChildren, isVNode } from '@element-plus/utils' +import type { ComponentInternalInstance, VNode } from 'vue' +import type { TabsPanes } from '../tabs' + +export const getTabPanes = (vm: ComponentInternalInstance) => { + const nodes = flattedChildren(vm.subTree) + return nodes.filter( + (n): n is VNode => + isVNode(n) && (n.type as any)?.name === 'ElTabPane' && !!n.component + ) +} + +export const getOrderedPanes = ( + vm: ComponentInternalInstance, + panes: TabsPanes +) => { + const nodes = getTabPanes(vm) + const uids = nodes.map((n) => n.component!.uid) + return uids.map((uid) => panes[uid]).filter((p) => !!p) +} diff --git a/packages/utils/vue/vnode.ts b/packages/utils/vue/vnode.ts index 9c5f461814..8248934962 100644 --- a/packages/utils/vue/vnode.ts +++ b/packages/utils/vue/vnode.ts @@ -35,6 +35,12 @@ export enum PatchFlags { BAIL = -2, } +export type VNodeChildAtom = Exclude> +export type RawSlots = Exclude< + VNodeNormalizedChildren, + Array | null | string +> + export function isFragment(node: VNode): boolean export function isFragment(node: unknown): node is VNode export function isFragment(node: unknown): node is VNode { @@ -138,11 +144,18 @@ export const ensureOnlyChild = (children: VNodeArrayChildren | undefined) => { return children[0] } -export const flattedChildren = (children: VNodeNormalizedChildren) => { +export type FlattenVNodes = Array + +export const flattedChildren = ( + children: FlattenVNodes | VNode | VNodeNormalizedChildren +): FlattenVNodes => { const vNodes = isArray(children) ? children : [children] - const result: any[] = [] - vNodes.forEach((child: any) => { - if (isArray(child.children)) { + const result: FlattenVNodes = [] + + vNodes.forEach((child) => { + if (isArray(child)) { + result.push(...flattedChildren(child)) + } else if (isVNode(child) && isArray(child.children)) { result.push(...flattedChildren(child.children)) } else { result.push(child)