From cd928362232747a51d1fd4790bb20adcdd59d187 Mon Sep 17 00:00:00 2001 From: Yasser Lahbibi Date: Mon, 30 Nov 2020 23:30:41 +0100 Subject: [PATCH] fix(teleport): Teleport into SVG elements (#2648) fix #2652 --- .../__tests__/components/Teleport.spec.ts | 36 +++++++++++++++++-- .../runtime-core/src/components/Teleport.ts | 8 ++++- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/packages/runtime-core/__tests__/components/Teleport.spec.ts b/packages/runtime-core/__tests__/components/Teleport.spec.ts index eb8ee416..15afa6db 100644 --- a/packages/runtime-core/__tests__/components/Teleport.spec.ts +++ b/packages/runtime-core/__tests__/components/Teleport.spec.ts @@ -7,10 +7,11 @@ import { Text, ref, nextTick, - markRaw + markRaw, + defineComponent } from '@vue/runtime-test' import { createVNode, Fragment } from '../../src/vnode' -import { compile } from 'vue' +import { compile, render as domRender } from 'vue' describe('renderer: teleport', () => { test('should work', () => { @@ -33,6 +34,37 @@ describe('renderer: teleport', () => { ) }) + test('should work with SVG', async () => { + const root = document.createElement('div') + const svg = ref() + const circle = ref() + + const Comp = defineComponent({ + setup() { + return { + svg, + circle + } + }, + template: ` + + + + ` + }) + + domRender(h(Comp), root) + + await nextTick() + + expect(root.innerHTML).toMatchInlineSnapshot( + `""` + ) + + expect(svg.value.namespaceURI).toBe('http://www.w3.org/2000/svg') + expect(circle.value.namespaceURI).toBe('http://www.w3.org/2000/svg') + }) + test('should update target', async () => { const targetA = nodeOps.createElement('div') const targetB = nodeOps.createElement('div') diff --git a/packages/runtime-core/src/components/Teleport.ts b/packages/runtime-core/src/components/Teleport.ts index 48f6e04a..fa61c636 100644 --- a/packages/runtime-core/src/components/Teleport.ts +++ b/packages/runtime-core/src/components/Teleport.ts @@ -24,6 +24,9 @@ export const isTeleport = (type: any): boolean => type.__isTeleport export const isTeleportDisabled = (props: VNode['props']): boolean => props && (props.disabled || props.disabled === '') +const isTargetSVG = (target: RendererElement): boolean => + typeof SVGElement !== 'undefined' && target instanceof SVGElement + const resolveTarget = ( props: TeleportProps | null, select: RendererOptions['querySelector'] @@ -80,6 +83,7 @@ export const TeleportImpl = { const disabled = isTeleportDisabled(n2.props) const { shapeFlag, children } = n2 + if (n1 == null) { // insert anchors in the main view const placeholder = (n2.el = __DEV__ @@ -90,11 +94,12 @@ export const TeleportImpl = { : createText('')) insert(placeholder, container, anchor) insert(mainAnchor, container, anchor) - const target = (n2.target = resolveTarget(n2.props, querySelector)) const targetAnchor = (n2.targetAnchor = createText('')) if (target) { insert(targetAnchor, target) + // #2652 we could be teleporting from a non-SVG tree into an SVG tree + isSVG = isSVG || isTargetSVG(target) } else if (__DEV__ && !disabled) { warn('Invalid Teleport target on mount:', target, `(${typeof target})`) } @@ -129,6 +134,7 @@ export const TeleportImpl = { const wasDisabled = isTeleportDisabled(n1.props) const currentContainer = wasDisabled ? container : target const currentAnchor = wasDisabled ? mainAnchor : targetAnchor + isSVG = isSVG || isTargetSVG(target) if (n2.dynamicChildren) { // fast path when the teleport happens to be a block root