mirror of https://github.com/vuejs/core.git
fix(teleport): skip teleported nodes when locating patch anchor
close #9071 close #9134 close #9313 Tests reused from #9313
This commit is contained in:
parent
50ddafe91b
commit
8655ced480
|
@ -16,7 +16,7 @@ import {
|
||||||
serializeInner,
|
serializeInner,
|
||||||
withDirectives,
|
withDirectives,
|
||||||
} from '@vue/runtime-test'
|
} from '@vue/runtime-test'
|
||||||
import { Fragment, createVNode } from '../../src/vnode'
|
import { Fragment, createCommentVNode, createVNode } from '../../src/vnode'
|
||||||
import { compile, render as domRender } from 'vue'
|
import { compile, render as domRender } from 'vue'
|
||||||
|
|
||||||
describe('renderer: teleport', () => {
|
describe('renderer: teleport', () => {
|
||||||
|
@ -553,4 +553,71 @@ describe('renderer: teleport', () => {
|
||||||
`"<div>teleported</div>"`,
|
`"<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-->"')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -21,6 +21,8 @@ export interface TeleportProps {
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const TeleportEndKey = Symbol('_vte')
|
||||||
|
|
||||||
export const isTeleport = (type: any): boolean => type.__isTeleport
|
export const isTeleport = (type: any): boolean => type.__isTeleport
|
||||||
|
|
||||||
const isTeleportDisabled = (props: VNode['props']): boolean =>
|
const isTeleportDisabled = (props: VNode['props']): boolean =>
|
||||||
|
@ -105,11 +107,16 @@ export const TeleportImpl = {
|
||||||
const mainAnchor = (n2.anchor = __DEV__
|
const mainAnchor = (n2.anchor = __DEV__
|
||||||
? createComment('teleport end')
|
? createComment('teleport end')
|
||||||
: createText(''))
|
: createText(''))
|
||||||
|
const target = (n2.target = resolveTarget(n2.props, querySelector))
|
||||||
|
const targetStart = (n2.targetStart = createText(''))
|
||||||
|
const targetAnchor = (n2.targetAnchor = createText(''))
|
||||||
insert(placeholder, container, anchor)
|
insert(placeholder, container, anchor)
|
||||||
insert(mainAnchor, container, anchor)
|
insert(mainAnchor, container, anchor)
|
||||||
const target = (n2.target = resolveTarget(n2.props, querySelector))
|
// attach a special property so we can skip teleported content in
|
||||||
const targetAnchor = (n2.targetAnchor = createText(''))
|
// renderer's nextSibling search
|
||||||
|
targetStart[TeleportEndKey] = targetAnchor
|
||||||
if (target) {
|
if (target) {
|
||||||
|
insert(targetStart, target)
|
||||||
insert(targetAnchor, target)
|
insert(targetAnchor, target)
|
||||||
// #2652 we could be teleporting from a non-SVG tree into an SVG tree
|
// #2652 we could be teleporting from a non-SVG tree into an SVG tree
|
||||||
if (namespace === 'svg' || isTargetSVG(target)) {
|
if (namespace === 'svg' || isTargetSVG(target)) {
|
||||||
|
@ -146,6 +153,7 @@ export const TeleportImpl = {
|
||||||
} else {
|
} else {
|
||||||
// update content
|
// update content
|
||||||
n2.el = n1.el
|
n2.el = n1.el
|
||||||
|
n2.targetStart = n1.targetStart
|
||||||
const mainAnchor = (n2.anchor = n1.anchor)!
|
const mainAnchor = (n2.anchor = n1.anchor)!
|
||||||
const target = (n2.target = n1.target)!
|
const target = (n2.target = n1.target)!
|
||||||
const targetAnchor = (n2.targetAnchor = n1.targetAnchor)!
|
const targetAnchor = (n2.targetAnchor = n1.targetAnchor)!
|
||||||
|
@ -253,9 +261,18 @@ export const TeleportImpl = {
|
||||||
{ um: unmount, o: { remove: hostRemove } }: RendererInternals,
|
{ um: unmount, o: { remove: hostRemove } }: RendererInternals,
|
||||||
doRemove: boolean,
|
doRemove: boolean,
|
||||||
) {
|
) {
|
||||||
const { shapeFlag, children, anchor, targetAnchor, target, props } = vnode
|
const {
|
||||||
|
shapeFlag,
|
||||||
|
children,
|
||||||
|
anchor,
|
||||||
|
targetStart,
|
||||||
|
targetAnchor,
|
||||||
|
target,
|
||||||
|
props,
|
||||||
|
} = vnode
|
||||||
|
|
||||||
if (target) {
|
if (target) {
|
||||||
|
hostRemove(targetStart!)
|
||||||
hostRemove(targetAnchor!)
|
hostRemove(targetAnchor!)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -58,7 +58,11 @@ import {
|
||||||
type SuspenseImpl,
|
type SuspenseImpl,
|
||||||
queueEffectWithSuspense,
|
queueEffectWithSuspense,
|
||||||
} from './components/Suspense'
|
} 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 { type KeepAliveContext, isKeepAlive } from './components/KeepAlive'
|
||||||
import { isHmrUpdating, registerHMR, unregisterHMR } from './hmr'
|
import { isHmrUpdating, registerHMR, unregisterHMR } from './hmr'
|
||||||
import { type RootHydrateFunction, createHydrationFunctions } from './hydration'
|
import { type RootHydrateFunction, createHydrationFunctions } from './hydration'
|
||||||
|
@ -140,7 +144,7 @@ export interface RendererOptions<
|
||||||
// functions provided via options, so the internal constraint is really just
|
// functions provided via options, so the internal constraint is really just
|
||||||
// a generic object.
|
// a generic object.
|
||||||
export interface RendererNode {
|
export interface RendererNode {
|
||||||
[key: string]: any
|
[key: string | symbol]: any
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RendererElement extends RendererNode {}
|
export interface RendererElement extends RendererNode {}
|
||||||
|
@ -2368,7 +2372,12 @@ function baseCreateRenderer(
|
||||||
if (__FEATURE_SUSPENSE__ && vnode.shapeFlag & ShapeFlags.SUSPENSE) {
|
if (__FEATURE_SUSPENSE__ && vnode.shapeFlag & ShapeFlags.SUSPENSE) {
|
||||||
return vnode.suspense!.next()
|
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
|
let isFlushing = false
|
||||||
|
|
|
@ -198,6 +198,7 @@ export interface VNode<
|
||||||
el: HostNode | null
|
el: HostNode | null
|
||||||
anchor: HostNode | null // fragment anchor
|
anchor: HostNode | null // fragment anchor
|
||||||
target: HostElement | null // teleport target
|
target: HostElement | null // teleport target
|
||||||
|
targetStart: HostNode | null // teleport target start anchor
|
||||||
targetAnchor: HostNode | null // teleport target anchor
|
targetAnchor: HostNode | null // teleport target anchor
|
||||||
/**
|
/**
|
||||||
* number of elements contained in a static vnode
|
* number of elements contained in a static vnode
|
||||||
|
@ -477,6 +478,7 @@ function createBaseVNode(
|
||||||
el: null,
|
el: null,
|
||||||
anchor: null,
|
anchor: null,
|
||||||
target: null,
|
target: null,
|
||||||
|
targetStart: null,
|
||||||
targetAnchor: null,
|
targetAnchor: null,
|
||||||
staticCount: 0,
|
staticCount: 0,
|
||||||
shapeFlag,
|
shapeFlag,
|
||||||
|
@ -677,6 +679,7 @@ export function cloneVNode<T, U>(
|
||||||
? (children as VNode[]).map(deepCloneVNode)
|
? (children as VNode[]).map(deepCloneVNode)
|
||||||
: children,
|
: children,
|
||||||
target: vnode.target,
|
target: vnode.target,
|
||||||
|
targetStart: vnode.targetStart,
|
||||||
targetAnchor: vnode.targetAnchor,
|
targetAnchor: vnode.targetAnchor,
|
||||||
staticCount: vnode.staticCount,
|
staticCount: vnode.staticCount,
|
||||||
shapeFlag: vnode.shapeFlag,
|
shapeFlag: vnode.shapeFlag,
|
||||||
|
|
Loading…
Reference in New Issue