mirror of https://github.com/vuejs/core.git
feat(transition): support directly nesting Teleport inside Transition (#6548)
close #5836
This commit is contained in:
parent
0c3a920012
commit
0e6e3c7eb0
|
@ -18,6 +18,7 @@ import { toRaw } from '@vue/reactivity'
|
||||||
import { ErrorCodes, callWithAsyncErrorHandling } from '../errorHandling'
|
import { ErrorCodes, callWithAsyncErrorHandling } from '../errorHandling'
|
||||||
import { PatchFlags, ShapeFlags, isArray, isFunction } from '@vue/shared'
|
import { PatchFlags, ShapeFlags, isArray, isFunction } from '@vue/shared'
|
||||||
import { onBeforeUnmount, onMounted } from '../apiLifecycle'
|
import { onBeforeUnmount, onMounted } from '../apiLifecycle'
|
||||||
|
import { isTeleport } from './Teleport'
|
||||||
import type { RendererElement } from '../renderer'
|
import type { RendererElement } from '../renderer'
|
||||||
import { SchedulerJobFlags } from '../scheduler'
|
import { SchedulerJobFlags } from '../scheduler'
|
||||||
|
|
||||||
|
@ -152,27 +153,7 @@ const BaseTransitionImpl: ComponentOptions = {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let child: VNode = children[0]
|
const child: VNode = findNonCommentChild(children)
|
||||||
if (children.length > 1) {
|
|
||||||
let hasFound = false
|
|
||||||
// locate first non-comment child
|
|
||||||
for (const c of children) {
|
|
||||||
if (c.type !== Comment) {
|
|
||||||
if (__DEV__ && hasFound) {
|
|
||||||
// warn more than one non-comment child
|
|
||||||
warn(
|
|
||||||
'<transition> can only be used on a single element or component. ' +
|
|
||||||
'Use <transition-group> for lists.',
|
|
||||||
)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
child = c
|
|
||||||
hasFound = true
|
|
||||||
if (!__DEV__) break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// there's no need to track reactivity for these props so use the raw
|
// there's no need to track reactivity for these props so use the raw
|
||||||
// props for a bit better perf
|
// props for a bit better perf
|
||||||
const rawProps = toRaw(props)
|
const rawProps = toRaw(props)
|
||||||
|
@ -194,7 +175,7 @@ const BaseTransitionImpl: ComponentOptions = {
|
||||||
|
|
||||||
// in the case of <transition><keep-alive/></transition>, we need to
|
// in the case of <transition><keep-alive/></transition>, we need to
|
||||||
// compare the type of the kept-alive children.
|
// compare the type of the kept-alive children.
|
||||||
const innerChild = getKeepAliveChild(child)
|
const innerChild = getInnerChild(child)
|
||||||
if (!innerChild) {
|
if (!innerChild) {
|
||||||
return emptyPlaceholder(child)
|
return emptyPlaceholder(child)
|
||||||
}
|
}
|
||||||
|
@ -208,7 +189,7 @@ const BaseTransitionImpl: ComponentOptions = {
|
||||||
setTransitionHooks(innerChild, enterHooks)
|
setTransitionHooks(innerChild, enterHooks)
|
||||||
|
|
||||||
const oldChild = instance.subTree
|
const oldChild = instance.subTree
|
||||||
const oldInnerChild = oldChild && getKeepAliveChild(oldChild)
|
const oldInnerChild = oldChild && getInnerChild(oldChild)
|
||||||
|
|
||||||
// handle mode
|
// handle mode
|
||||||
if (
|
if (
|
||||||
|
@ -268,6 +249,30 @@ if (__COMPAT__) {
|
||||||
BaseTransitionImpl.__isBuiltIn = true
|
BaseTransitionImpl.__isBuiltIn = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function findNonCommentChild(children: VNode[]): VNode {
|
||||||
|
let child: VNode = children[0]
|
||||||
|
if (children.length > 1) {
|
||||||
|
let hasFound = false
|
||||||
|
// locate first non-comment child
|
||||||
|
for (const c of children) {
|
||||||
|
if (c.type !== Comment) {
|
||||||
|
if (__DEV__ && hasFound) {
|
||||||
|
// warn more than one non-comment child
|
||||||
|
warn(
|
||||||
|
'<transition> can only be used on a single element or component. ' +
|
||||||
|
'Use <transition-group> for lists.',
|
||||||
|
)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
child = c
|
||||||
|
hasFound = true
|
||||||
|
if (!__DEV__) break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return child
|
||||||
|
}
|
||||||
|
|
||||||
// export the public type for h/tsx inference
|
// export the public type for h/tsx inference
|
||||||
// also to avoid inline import() in generated d.ts files
|
// also to avoid inline import() in generated d.ts files
|
||||||
export const BaseTransition = BaseTransitionImpl as unknown as {
|
export const BaseTransition = BaseTransitionImpl as unknown as {
|
||||||
|
@ -458,8 +463,12 @@ function emptyPlaceholder(vnode: VNode): VNode | undefined {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getKeepAliveChild(vnode: VNode): VNode | undefined {
|
function getInnerChild(vnode: VNode): VNode | undefined {
|
||||||
if (!isKeepAlive(vnode)) {
|
if (!isKeepAlive(vnode)) {
|
||||||
|
if (isTeleport(vnode.type) && vnode.children) {
|
||||||
|
return findNonCommentChild(vnode.children as VNode[])
|
||||||
|
}
|
||||||
|
|
||||||
return vnode
|
return vnode
|
||||||
}
|
}
|
||||||
// #7121 ensure get the child component subtree in case
|
// #7121 ensure get the child component subtree in case
|
||||||
|
|
|
@ -1725,6 +1725,95 @@ describe('e2e: Transition', () => {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('transition with Teleport', () => {
|
||||||
|
test(
|
||||||
|
'apply transition to teleport child',
|
||||||
|
async () => {
|
||||||
|
await page().evaluate(() => {
|
||||||
|
const { createApp, ref, h } = (window as any).Vue
|
||||||
|
createApp({
|
||||||
|
template: `
|
||||||
|
<div id="target"></div>
|
||||||
|
<div id="container">
|
||||||
|
<transition>
|
||||||
|
<Teleport to="#target">
|
||||||
|
<!-- comment -->
|
||||||
|
<Comp v-if="toggle" class="test">content</Comp>
|
||||||
|
</Teleport>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
<button id="toggleBtn" @click="click">button</button>
|
||||||
|
`,
|
||||||
|
components: {
|
||||||
|
Comp: {
|
||||||
|
setup() {
|
||||||
|
return () => 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('<!-- comment --><!--v-if-->')
|
||||||
|
expect(await html('#container')).toBe(
|
||||||
|
'<!--teleport start--><!--teleport end-->',
|
||||||
|
)
|
||||||
|
|
||||||
|
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(
|
||||||
|
'<!-- comment --><div class="test">content</div>',
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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('<!-- comment --><!--v-if-->')
|
||||||
|
expect(await html('#container')).toBe(
|
||||||
|
'<!--teleport start--><!--teleport end-->',
|
||||||
|
)
|
||||||
|
},
|
||||||
|
E2E_TIMEOUT,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
describe('transition with v-show', () => {
|
describe('transition with v-show', () => {
|
||||||
test(
|
test(
|
||||||
'named transition with v-show',
|
'named transition with v-show',
|
||||||
|
|
Loading…
Reference in New Issue