Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions packages/runtime-core/__tests__/components/Teleport.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,37 @@ describe('renderer: teleport', () => {
expect(serializeInner(targetB)).toBe(`<div>teleported</div>`)
})

test('should clean up anchors when target becomes invalid', async () => {
const targetA = nodeOps.createElement('div')
const to = ref<any>(targetA)
const root = nodeOps.createElement('div')

render(
h(() => h(Teleport, { to: to.value }, h('div', 'teleported'))),
root,
)

expect(serializeInner(root)).toBe(
`<!--teleport start--><!--teleport end-->`,
)
expect(serializeInner(targetA)).toBe(`<div>teleported</div>`)
expect(targetA.children.length).toBe(3)

to.value = null
await nextTick()
expect('Invalid Teleport target').toHaveBeenWarned()

expect(serializeInner(root)).toBe(
`<!--teleport start--><!--teleport end-->`,
)
expect(serializeInner(targetA)).toBe(`<div>teleported</div>`)
expect(targetA.children.length).toBe(3)

render(null, root)
expect(serializeInner(targetA)).toBe(``)
expect(targetA.children.length).toBe(0)
})

test('move cached text nodes', async () => {
document.body.innerHTML = ''
const root = document.createElement('div')
Expand Down
21 changes: 8 additions & 13 deletions packages/runtime-core/src/components/Teleport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -317,19 +317,14 @@ export const TeleportImpl = {
{ um: unmount, o: { remove: hostRemove } }: RendererInternals,
doRemove: boolean,
): void {
const {
shapeFlag,
children,
anchor,
targetStart,
targetAnchor,
target,
props,
} = vnode

if (target) {
hostRemove(targetStart!)
hostRemove(targetAnchor!)
const { shapeFlag, children, anchor, targetStart, targetAnchor, props } =
vnode

if (targetStart) {
hostRemove(targetStart)
}
if (targetAnchor) {
hostRemove(targetAnchor)
}

// an unmounted teleport should always unmount its children whether it's disabled or not
Expand Down
45 changes: 45 additions & 0 deletions packages/runtime-vapor/__tests__/components/Teleport.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -787,6 +787,51 @@ function runSharedTests(deferMode: boolean): void {
expect(targetB.innerHTML).toBe('<div>teleported</div>')
})

test('should clean up anchors when target becomes invalid', async () => {
const targetA = document.createElement('div')
const target = ref<any>(targetA)
const root = document.createElement('div')

const { app } = define({
setup() {
const n0 = createComponent(
VaporTeleport,
{
to: () => target.value,
},
{
default: () => template('<div>teleported</div>')(),
},
)
const n1 = template('<div>root</div>')()
return [n0, n1]
},
}).create()

app.mount(root)

expect(root.innerHTML).toBe(
'<!--teleport start--><!--teleport end--><div>root</div>',
)
expect(targetA.innerHTML).toBe('<div>teleported</div>')
expect(targetA.childNodes.length).toBe(3)

target.value = '#missing-teleport-target'
await nextTick()
expect('Failed to locate Teleport target').toHaveBeenWarned()
expect('Invalid Teleport target on update').toHaveBeenWarned()

expect(root.innerHTML).toBe(
'<!--teleport start--><!--teleport end--><div>root</div>',
)
expect(targetA.innerHTML).toBe('<div>teleported</div>')
expect(targetA.childNodes.length).toBe(3)

app.unmount()
expect(targetA.innerHTML).toBe('')
expect(targetA.childNodes.length).toBe(0)
})

test('should update children', async () => {
const target = document.createElement('div')
const root = document.createElement('div')
Expand Down
23 changes: 14 additions & 9 deletions packages/runtime-vapor/src/components/Teleport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@ import {
move,
remove,
} from '../block'
import { createComment, createTextNode, querySelector } from '../dom/node'
import {
createComment,
createTextNode,
parentNode,
querySelector,
} from '../dom/node'
import {
type LooseRawProps,
type LooseRawSlots,
Expand Down Expand Up @@ -113,7 +118,7 @@ export class TeleportFragment extends VaporFragment {
}

get parent(): ParentNode | null {
return this.anchor ? this.anchor.parentNode : null
return this.anchor ? parentNode(this.anchor) : null
}

private initChildren(): void {
Expand Down Expand Up @@ -218,14 +223,14 @@ export class TeleportFragment extends VaporFragment {
// initial mount into target
!this.targetAnchor ||
// target changed
this.targetAnchor.parentNode !== target
parentNode(this.targetAnchor) !== target
) {
// clean up old anchors from previous target when target changes
if (this.targetStart) {
remove(this.targetStart, this.targetStart.parentNode!)
remove(this.targetStart, parentNode(this.targetStart)!)
}
if (this.targetAnchor) {
remove(this.targetAnchor, this.targetAnchor.parentNode!)
remove(this.targetAnchor, parentNode(this.targetAnchor)!)
}
insert((this.targetStart = createTextNode('')), target)
insert((this.targetAnchor = createTextNode('')), target)
Expand Down Expand Up @@ -302,14 +307,14 @@ export class TeleportFragment extends VaporFragment {

// remove anchors
if (this.targetStart) {
remove(this.targetStart, this.target!)
remove(this.targetStart, parentNode(this.targetStart)!)
this.targetStart = undefined
remove(this.targetAnchor!, this.target!)
remove(this.targetAnchor!, parentNode(this.targetAnchor!)!)
this.targetAnchor = undefined
}

if (this.anchor) {
remove(this.anchor, this.anchor.parentNode!)
remove(this.anchor, parentNode(this.anchor)!)
this.anchor = undefined
}

Expand Down Expand Up @@ -349,7 +354,7 @@ export class TeleportFragment extends VaporFragment {
let nextNode = this.placeholder!.nextSibling!
setCurrentHydrationNode(nextNode)
this.mountAnchor = this.anchor = locateTeleportEndAnchor(nextNode)!
this.mountContainer = this.anchor.parentNode
this.mountContainer = parentNode(this.anchor)
if (target) {
this.hydrateTargetAnchors(target, targetNode)
} else {
Expand Down
Loading