serializeInner,
withDirectives,
} from '@vue/runtime-test'
-import { Fragment, createVNode } from '../../src/vnode'
+import { Fragment, createCommentVNode, createVNode } from '../../src/vnode'
import { compile, render as domRender } from 'vue'
describe('renderer: teleport', () => {
`"<div>teleported</div>"`,
)
})
+
+ //#9071
+ test('toggle sibling node inside target node', async () => {
+ const root = document.createElement('div')
+ const show = ref(false)
+ const App = defineComponent({
+ setup() {
+ return () => {
+ return show.value
+ ? h(Teleport, { to: root }, [h('div', 'teleported')])
+ : h('div', 'foo')
+ }
+ },
+ })
+
+ domRender(h(App), root)
+ expect(root.innerHTML).toMatchInlineSnapshot('"<div>foo</div>"')
+
+ show.value = true
+ await nextTick()
+
+ expect(root.innerHTML).toMatchInlineSnapshot(
+ '"<!--teleport start--><!--teleport end--><div>teleported</div>"',
+ )
+
+ show.value = false
+ await nextTick()
+
+ expect(root.innerHTML).toMatchInlineSnapshot('"<div>foo</div>"')
+ })
+
+ test('unmount previous sibling node inside target node', async () => {
+ const root = document.createElement('div')
+ const parentShow = ref(false)
+ const childShow = ref(true)
+
+ const Comp = {
+ setup() {
+ return () => h(Teleport, { to: root }, [h('div', 'foo')])
+ },
+ }
+
+ const App = defineComponent({
+ setup() {
+ return () => {
+ return parentShow.value
+ ? h(Fragment, { key: 0 }, [
+ childShow.value ? h(Comp) : createCommentVNode('v-if'),
+ ])
+ : createCommentVNode('v-if')
+ }
+ },
+ })
+
+ domRender(h(App), root)
+ expect(root.innerHTML).toMatchInlineSnapshot('"<!--v-if-->"')
+
+ parentShow.value = true
+ await nextTick()
+ expect(root.innerHTML).toMatchInlineSnapshot(
+ '"<!--teleport start--><!--teleport end--><div>foo</div>"',
+ )
+
+ parentShow.value = false
+ await nextTick()
+ expect(root.innerHTML).toMatchInlineSnapshot('"<!--v-if-->"')
+ })
})
disabled?: boolean
}
+export const TeleportEndKey = Symbol('_vte')
+
export const isTeleport = (type: any): boolean => type.__isTeleport
const isTeleportDisabled = (props: VNode['props']): boolean =>
const mainAnchor = (n2.anchor = __DEV__
? createComment('teleport end')
: createText(''))
- insert(placeholder, container, anchor)
- insert(mainAnchor, container, anchor)
const target = (n2.target = resolveTarget(n2.props, querySelector))
+ const targetStart = (n2.targetStart = createText(''))
const targetAnchor = (n2.targetAnchor = createText(''))
+ insert(placeholder, container, anchor)
+ insert(mainAnchor, container, anchor)
+ // attach a special property so we can skip teleported content in
+ // renderer's nextSibling search
+ targetStart[TeleportEndKey] = targetAnchor
if (target) {
+ insert(targetStart, target)
insert(targetAnchor, target)
// #2652 we could be teleporting from a non-SVG tree into an SVG tree
if (namespace === 'svg' || isTargetSVG(target)) {
} else {
// update content
n2.el = n1.el
+ n2.targetStart = n1.targetStart
const mainAnchor = (n2.anchor = n1.anchor)!
const target = (n2.target = n1.target)!
const targetAnchor = (n2.targetAnchor = n1.targetAnchor)!
{ um: unmount, o: { remove: hostRemove } }: RendererInternals,
doRemove: boolean,
) {
- const { shapeFlag, children, anchor, targetAnchor, target, props } = vnode
+ const {
+ shapeFlag,
+ children,
+ anchor,
+ targetStart,
+ targetAnchor,
+ target,
+ props,
+ } = vnode
if (target) {
+ hostRemove(targetStart!)
hostRemove(targetAnchor!)
}
type SuspenseImpl,
queueEffectWithSuspense,
} from './components/Suspense'
-import type { TeleportImpl, TeleportVNode } from './components/Teleport'
+import {
+ TeleportEndKey,
+ type TeleportImpl,
+ type TeleportVNode,
+} from './components/Teleport'
import { type KeepAliveContext, isKeepAlive } from './components/KeepAlive'
import { isHmrUpdating, registerHMR, unregisterHMR } from './hmr'
import { type RootHydrateFunction, createHydrationFunctions } from './hydration'
// functions provided via options, so the internal constraint is really just
// a generic object.
export interface RendererNode {
- [key: string]: any
+ [key: string | symbol]: any
}
export interface RendererElement extends RendererNode {}
if (__FEATURE_SUSPENSE__ && vnode.shapeFlag & ShapeFlags.SUSPENSE) {
return vnode.suspense!.next()
}
- return hostNextSibling((vnode.anchor || vnode.el)!)
+ const el = hostNextSibling((vnode.anchor || vnode.el)!)
+ // #9071, #9313
+ // teleported content can mess up nextSibling searches during patch so
+ // we need to skip them during nextSibling search
+ const teleportEnd = el && el[TeleportEndKey]
+ return teleportEnd ? hostNextSibling(teleportEnd) : el
}
let isFlushing = false
el: HostNode | null
anchor: HostNode | null // fragment anchor
target: HostElement | null // teleport target
+ targetStart: HostNode | null // teleport target start anchor
targetAnchor: HostNode | null // teleport target anchor
/**
* number of elements contained in a static vnode
el: null,
anchor: null,
target: null,
+ targetStart: null,
targetAnchor: null,
staticCount: 0,
shapeFlag,
? (children as VNode[]).map(deepCloneVNode)
: children,
target: vnode.target,
+ targetStart: vnode.targetStart,
targetAnchor: vnode.targetAnchor,
staticCount: vnode.staticCount,
shapeFlag: vnode.shapeFlag,