Skip to content

Commit 0e6e3c7

Browse files
authored
feat(transition): support directly nesting Teleport inside Transition (#6548)
close #5836
1 parent 0c3a920 commit 0e6e3c7

File tree

2 files changed

+122
-24
lines changed

2 files changed

+122
-24
lines changed

packages/runtime-core/src/components/BaseTransition.ts

+33-24
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { toRaw } from '@vue/reactivity'
1818
import { ErrorCodes, callWithAsyncErrorHandling } from '../errorHandling'
1919
import { PatchFlags, ShapeFlags, isArray, isFunction } from '@vue/shared'
2020
import { onBeforeUnmount, onMounted } from '../apiLifecycle'
21+
import { isTeleport } from './Teleport'
2122
import type { RendererElement } from '../renderer'
2223
import { SchedulerJobFlags } from '../scheduler'
2324

@@ -152,27 +153,7 @@ const BaseTransitionImpl: ComponentOptions = {
152153
return
153154
}
154155

155-
let child: VNode = children[0]
156-
if (children.length > 1) {
157-
let hasFound = false
158-
// locate first non-comment child
159-
for (const c of children) {
160-
if (c.type !== Comment) {
161-
if (__DEV__ && hasFound) {
162-
// warn more than one non-comment child
163-
warn(
164-
'<transition> can only be used on a single element or component. ' +
165-
'Use <transition-group> for lists.',
166-
)
167-
break
168-
}
169-
child = c
170-
hasFound = true
171-
if (!__DEV__) break
172-
}
173-
}
174-
}
175-
156+
const child: VNode = findNonCommentChild(children)
176157
// there's no need to track reactivity for these props so use the raw
177158
// props for a bit better perf
178159
const rawProps = toRaw(props)
@@ -194,7 +175,7 @@ const BaseTransitionImpl: ComponentOptions = {
194175

195176
// in the case of <transition><keep-alive/></transition>, we need to
196177
// compare the type of the kept-alive children.
197-
const innerChild = getKeepAliveChild(child)
178+
const innerChild = getInnerChild(child)
198179
if (!innerChild) {
199180
return emptyPlaceholder(child)
200181
}
@@ -208,7 +189,7 @@ const BaseTransitionImpl: ComponentOptions = {
208189
setTransitionHooks(innerChild, enterHooks)
209190

210191
const oldChild = instance.subTree
211-
const oldInnerChild = oldChild && getKeepAliveChild(oldChild)
192+
const oldInnerChild = oldChild && getInnerChild(oldChild)
212193

213194
// handle mode
214195
if (
@@ -268,6 +249,30 @@ if (__COMPAT__) {
268249
BaseTransitionImpl.__isBuiltIn = true
269250
}
270251

252+
function findNonCommentChild(children: VNode[]): VNode {
253+
let child: VNode = children[0]
254+
if (children.length > 1) {
255+
let hasFound = false
256+
// locate first non-comment child
257+
for (const c of children) {
258+
if (c.type !== Comment) {
259+
if (__DEV__ && hasFound) {
260+
// warn more than one non-comment child
261+
warn(
262+
'<transition> can only be used on a single element or component. ' +
263+
'Use <transition-group> for lists.',
264+
)
265+
break
266+
}
267+
child = c
268+
hasFound = true
269+
if (!__DEV__) break
270+
}
271+
}
272+
}
273+
return child
274+
}
275+
271276
// export the public type for h/tsx inference
272277
// also to avoid inline import() in generated d.ts files
273278
export const BaseTransition = BaseTransitionImpl as unknown as {
@@ -458,8 +463,12 @@ function emptyPlaceholder(vnode: VNode): VNode | undefined {
458463
}
459464
}
460465

461-
function getKeepAliveChild(vnode: VNode): VNode | undefined {
466+
function getInnerChild(vnode: VNode): VNode | undefined {
462467
if (!isKeepAlive(vnode)) {
468+
if (isTeleport(vnode.type) && vnode.children) {
469+
return findNonCommentChild(vnode.children as VNode[])
470+
}
471+
463472
return vnode
464473
}
465474
// #7121 ensure get the child component subtree in case

packages/vue/__tests__/e2e/Transition.spec.ts

+89
Original file line numberDiff line numberDiff line change
@@ -1725,6 +1725,95 @@ describe('e2e: Transition', () => {
17251725
)
17261726
})
17271727

1728+
describe('transition with Teleport', () => {
1729+
test(
1730+
'apply transition to teleport child',
1731+
async () => {
1732+
await page().evaluate(() => {
1733+
const { createApp, ref, h } = (window as any).Vue
1734+
createApp({
1735+
template: `
1736+
<div id="target"></div>
1737+
<div id="container">
1738+
<transition>
1739+
<Teleport to="#target">
1740+
<!-- comment -->
1741+
<Comp v-if="toggle" class="test">content</Comp>
1742+
</Teleport>
1743+
</transition>
1744+
</div>
1745+
<button id="toggleBtn" @click="click">button</button>
1746+
`,
1747+
components: {
1748+
Comp: {
1749+
setup() {
1750+
return () => h('div', { class: 'test' }, 'content')
1751+
},
1752+
},
1753+
},
1754+
setup: () => {
1755+
const toggle = ref(false)
1756+
const click = () => (toggle.value = !toggle.value)
1757+
return { toggle, click }
1758+
},
1759+
}).mount('#app')
1760+
})
1761+
1762+
expect(await html('#target')).toBe('<!-- comment --><!--v-if-->')
1763+
expect(await html('#container')).toBe(
1764+
'<!--teleport start--><!--teleport end-->',
1765+
)
1766+
1767+
const classWhenTransitionStart = () =>
1768+
page().evaluate(() => {
1769+
;(document.querySelector('#toggleBtn') as any)!.click()
1770+
return Promise.resolve().then(() => {
1771+
// find the class of teleported node
1772+
return document
1773+
.querySelector('#target div')!
1774+
.className.split(/\s+/g)
1775+
})
1776+
})
1777+
1778+
// enter
1779+
expect(await classWhenTransitionStart()).toStrictEqual([
1780+
'test',
1781+
'v-enter-from',
1782+
'v-enter-active',
1783+
])
1784+
await nextFrame()
1785+
expect(await classList('.test')).toStrictEqual([
1786+
'test',
1787+
'v-enter-active',
1788+
'v-enter-to',
1789+
])
1790+
await transitionFinish()
1791+
expect(await html('#target')).toBe(
1792+
'<!-- comment --><div class="test">content</div>',
1793+
)
1794+
1795+
// leave
1796+
expect(await classWhenTransitionStart()).toStrictEqual([
1797+
'test',
1798+
'v-leave-from',
1799+
'v-leave-active',
1800+
])
1801+
await nextFrame()
1802+
expect(await classList('.test')).toStrictEqual([
1803+
'test',
1804+
'v-leave-active',
1805+
'v-leave-to',
1806+
])
1807+
await transitionFinish()
1808+
expect(await html('#target')).toBe('<!-- comment --><!--v-if-->')
1809+
expect(await html('#container')).toBe(
1810+
'<!--teleport start--><!--teleport end-->',
1811+
)
1812+
},
1813+
E2E_TIMEOUT,
1814+
)
1815+
})
1816+
17281817
describe('transition with v-show', () => {
17291818
test(
17301819
'named transition with v-show',

0 commit comments

Comments
 (0)