import { ErrorCodes, callWithAsyncErrorHandling } from '../errorHandling'
import { PatchFlags, ShapeFlags, isArray, isFunction } from '@vue/shared'
import { onBeforeUnmount, onMounted } from '../apiLifecycle'
+import { isTeleport } from './Teleport'
import type { RendererElement } from '../renderer'
import { SchedulerJobFlags } from '../scheduler'
return
}
- 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
- }
- }
- }
-
+ const child: VNode = findNonCommentChild(children)
// there's no need to track reactivity for these props so use the raw
// props for a bit better perf
const rawProps = toRaw(props)
// in the case of <transition><keep-alive/></transition>, we need to
// compare the type of the kept-alive children.
- const innerChild = getKeepAliveChild(child)
+ const innerChild = getInnerChild(child)
if (!innerChild) {
return emptyPlaceholder(child)
}
setTransitionHooks(innerChild, enterHooks)
const oldChild = instance.subTree
- const oldInnerChild = oldChild && getKeepAliveChild(oldChild)
+ const oldInnerChild = oldChild && getInnerChild(oldChild)
// handle mode
if (
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
// also to avoid inline import() in generated d.ts files
export const BaseTransition = BaseTransitionImpl as unknown as {
}
}
-function getKeepAliveChild(vnode: VNode): VNode | undefined {
+function getInnerChild(vnode: VNode): VNode | undefined {
if (!isKeepAlive(vnode)) {
+ if (isTeleport(vnode.type) && vnode.children) {
+ return findNonCommentChild(vnode.children as VNode[])
+ }
+
return vnode
}
// #7121 ensure get the child component subtree in case
)
})
+ 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', () => {
test(
'named transition with v-show',