From 000c04f5287c5c24d3bb62402e3a588b456ae46e Mon Sep 17 00:00:00 2001 From: yangchangtao Date: Wed, 18 Sep 2024 16:00:29 +0800 Subject: [PATCH] feat(transition): support transition to teleport component child --- .../runtime-core/src/componentRenderUtils.ts | 7 +- .../src/components/BaseTransition.ts | 2 +- packages/vue/__tests__/e2e/Transition.spec.ts | 85 ++++++++++++++++++- 3 files changed, 89 insertions(+), 5 deletions(-) diff --git a/packages/runtime-core/src/componentRenderUtils.ts b/packages/runtime-core/src/componentRenderUtils.ts index a15d18d56bf..c045252551a 100644 --- a/packages/runtime-core/src/componentRenderUtils.ts +++ b/packages/runtime-core/src/componentRenderUtils.ts @@ -27,7 +27,7 @@ import { warnDeprecation, } from './compat/compatConfig' import { shallowReadonly } from '@vue/reactivity' -import { setTransitionHooks } from './components/BaseTransition' +import { getInnerChild, setTransitionHooks } from './components/BaseTransition' /** * dev only flag to track whether $attrs was used during render. @@ -248,13 +248,14 @@ export function renderComponentRoot( } // inherit transition data if (vnode.transition) { - if (__DEV__ && !isElementRoot(root)) { + const child = getInnerChild(root) ?? root + if (__DEV__ && !isElementRoot(child)) { warn( `Component inside renders non-element root node ` + `that cannot be animated.`, ) } - setTransitionHooks(root, vnode.transition) + setTransitionHooks(child, vnode.transition) } if (__DEV__ && setRoot) { diff --git a/packages/runtime-core/src/components/BaseTransition.ts b/packages/runtime-core/src/components/BaseTransition.ts index 6ce06d28239..511454be9c5 100644 --- a/packages/runtime-core/src/components/BaseTransition.ts +++ b/packages/runtime-core/src/components/BaseTransition.ts @@ -484,7 +484,7 @@ function emptyPlaceholder(vnode: VNode): VNode | undefined { } } -function getInnerChild(vnode: VNode): VNode | undefined { +export function getInnerChild(vnode: VNode): VNode | undefined { if (!isKeepAlive(vnode)) { if (isTeleport(vnode.type) && vnode.children) { return findNonCommentChild(vnode.children as VNode[]) diff --git a/packages/vue/__tests__/e2e/Transition.spec.ts b/packages/vue/__tests__/e2e/Transition.spec.ts index c0863a75991..1d72a894ccb 100644 --- a/packages/vue/__tests__/e2e/Transition.spec.ts +++ b/packages/vue/__tests__/e2e/Transition.spec.ts @@ -1,6 +1,6 @@ import { E2E_TIMEOUT, setupPuppeteer } from './e2eUtils' import path from 'node:path' -import { Transition, createApp, h, nextTick, ref } from 'vue' +import { Teleport, Transition, createApp, h, nextTick, ref } from 'vue' describe('e2e: Transition', () => { const { page, html, classList, isVisible, timeout, nextFrame, click } = @@ -2310,6 +2310,89 @@ describe('e2e: Transition', () => { ) }, E2E_TIMEOUT, + ), + test( + 'apply transition to teleport component child', + async () => { + await page().evaluate(() => { + const { createApp, ref, h } = (window as any).Vue + createApp({ + template: ` +
+
+ + content + +
+ + `, + components: { + Comp: { + setup() { + return () => h(Teleport, { to: '#target' }, [h('div', { class: 'test' }, 'content')]) + }, + }, + }, + setup: () => { + const toggle = ref(false) + const click = () => (toggle.value = !toggle.value) + return { toggle, click } + }, + }).mount('#app') + }) + + expect(await html('#target')).toBe('') + expect(await html('#container')).toBe( + '', + ) + + const classWhenTransitionStart = () => + page().evaluate(() => { + ;(document.querySelector('#toggleBtn') as any)!.click() + return Promise.resolve().then(() => { + // find the class of teleported node + return document + .querySelector('#target div')! + .className.split(/\s+/g) + }) + }) + + // enter + expect(await classWhenTransitionStart()).toStrictEqual([ + 'test', + 'v-enter-from', + 'v-enter-active', + ]) + await nextFrame() + expect(await classList('.test')).toStrictEqual([ + 'test', + 'v-enter-active', + 'v-enter-to', + ]) + await transitionFinish() + expect(await html('#target')).toBe( + '
content
', + ) + + // leave + expect(await classWhenTransitionStart()).toStrictEqual([ + 'test', + 'v-leave-from', + 'v-leave-active', + ]) + await nextFrame() + expect(await classList('.test')).toStrictEqual([ + 'test', + 'v-leave-active', + 'v-leave-to', + ]) + await transitionFinish() + expect(await html('#target')).toBe('') + expect(await html('#container')).toBe( + '', + ) + }, + E2E_TIMEOUT, ) })