fix(dom): fix <svg> and <foreignObject> mount and updates
This commit is contained in:
parent
da8c31dc7f
commit
4f06eebc1c
@ -70,9 +70,7 @@ export const transformElement: NodeTransform = (node, context) => {
|
|||||||
let runtimeDirectives: DirectiveNode[] | undefined
|
let runtimeDirectives: DirectiveNode[] | undefined
|
||||||
let dynamicPropNames: string[] | undefined
|
let dynamicPropNames: string[] | undefined
|
||||||
let dynamicComponent: string | CallExpression | undefined
|
let dynamicComponent: string | CallExpression | undefined
|
||||||
// technically this is web specific but we are keeping it in core to avoid
|
let shouldUseBlock = false
|
||||||
// extra complexity
|
|
||||||
let isSVG = false
|
|
||||||
|
|
||||||
// handle dynamic component
|
// handle dynamic component
|
||||||
const isProp = findProp(node, 'is')
|
const isProp = findProp(node, 'is')
|
||||||
@ -110,8 +108,12 @@ export const transformElement: NodeTransform = (node, context) => {
|
|||||||
nodeType = toValidAssetId(tag, `component`)
|
nodeType = toValidAssetId(tag, `component`)
|
||||||
} else {
|
} else {
|
||||||
// plain element
|
// plain element
|
||||||
nodeType = `"${node.tag}"`
|
nodeType = `"${tag}"`
|
||||||
isSVG = node.tag === 'svg'
|
// <svg> and <foreignObject> must be forced into blocks so that block
|
||||||
|
// updates inside get proper isSVG flag at runtime. (#639, #643)
|
||||||
|
// This is technically web-specific, but splitting the logic out of core
|
||||||
|
// leads to too much unnecessary complexity.
|
||||||
|
shouldUseBlock = tag === 'svg' || tag === 'foreignObject'
|
||||||
}
|
}
|
||||||
|
|
||||||
const args: CallExpression['arguments'] = [nodeType]
|
const args: CallExpression['arguments'] = [nodeType]
|
||||||
@ -197,10 +199,8 @@ export const transformElement: NodeTransform = (node, context) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { loc } = node
|
const { loc } = node
|
||||||
const vnode = isSVG
|
const vnode = shouldUseBlock
|
||||||
? // <svg> must be forced into blocks so that block updates inside retain
|
? createSequenceExpression([
|
||||||
// isSVG flag at runtime. (#639, #643)
|
|
||||||
createSequenceExpression([
|
|
||||||
createCallExpression(context.helper(OPEN_BLOCK)),
|
createCallExpression(context.helper(OPEN_BLOCK)),
|
||||||
createCallExpression(context.helper(CREATE_BLOCK), args, loc)
|
createCallExpression(context.helper(CREATE_BLOCK), args, loc)
|
||||||
])
|
])
|
||||||
|
@ -370,7 +370,7 @@ export function createRenderer<
|
|||||||
optimized: boolean
|
optimized: boolean
|
||||||
) {
|
) {
|
||||||
const el = (vnode.el = hostCreateElement(vnode.type as string, isSVG))
|
const el = (vnode.el = hostCreateElement(vnode.type as string, isSVG))
|
||||||
const { props, shapeFlag, transition, scopeId } = vnode
|
const { type, props, shapeFlag, transition, scopeId } = vnode
|
||||||
|
|
||||||
// props
|
// props
|
||||||
if (props != null) {
|
if (props != null) {
|
||||||
@ -406,7 +406,7 @@ export function createRenderer<
|
|||||||
null,
|
null,
|
||||||
parentComponent,
|
parentComponent,
|
||||||
parentSuspense,
|
parentSuspense,
|
||||||
isSVG,
|
isSVG && type !== 'foreignObject',
|
||||||
optimized || vnode.dynamicChildren !== null
|
optimized || vnode.dynamicChildren !== null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -562,6 +562,7 @@ export function createRenderer<
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const areChildrenSVG = isSVG && n2.type !== 'foreignObject'
|
||||||
if (dynamicChildren != null) {
|
if (dynamicChildren != null) {
|
||||||
patchBlockChildren(
|
patchBlockChildren(
|
||||||
n1.dynamicChildren!,
|
n1.dynamicChildren!,
|
||||||
@ -569,11 +570,19 @@ export function createRenderer<
|
|||||||
el,
|
el,
|
||||||
parentComponent,
|
parentComponent,
|
||||||
parentSuspense,
|
parentSuspense,
|
||||||
isSVG
|
areChildrenSVG
|
||||||
)
|
)
|
||||||
} else if (!optimized) {
|
} else if (!optimized) {
|
||||||
// full diff
|
// full diff
|
||||||
patchChildren(n1, n2, el, null, parentComponent, parentSuspense, isSVG)
|
patchChildren(
|
||||||
|
n1,
|
||||||
|
n2,
|
||||||
|
el,
|
||||||
|
null,
|
||||||
|
parentComponent,
|
||||||
|
parentSuspense,
|
||||||
|
areChildrenSVG
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newProps.onVnodeUpdated != null) {
|
if (newProps.onVnodeUpdated != null) {
|
||||||
|
63
packages/vue/__tests__/svg.spec.ts
Normal file
63
packages/vue/__tests__/svg.spec.ts
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
// SVG logic is technically dom-specific, but the logic is placed in core
|
||||||
|
// because splitting it out of core would lead to unnecessary complexity in both
|
||||||
|
// the renderer and compiler implementations.
|
||||||
|
// Related files:
|
||||||
|
// - runtime-core/src/renderer.ts
|
||||||
|
// - compiler-core/src/transoforms/transformElement.ts
|
||||||
|
|
||||||
|
import { render, h, ref, nextTick } from '../src'
|
||||||
|
|
||||||
|
describe('SVG support', () => {
|
||||||
|
test('should mount elements with correct namespaces', () => {
|
||||||
|
const root = document.createElement('div')
|
||||||
|
document.body.appendChild(root)
|
||||||
|
const App = {
|
||||||
|
template: `
|
||||||
|
<div id="e0">
|
||||||
|
<svg id="e1">
|
||||||
|
<foreignObject id="e2">
|
||||||
|
<div id="e3"/>
|
||||||
|
</foreignObject>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
render(h(App), root)
|
||||||
|
const e0 = document.getElementById('e0')!
|
||||||
|
expect(e0.namespaceURI).toMatch('xhtml')
|
||||||
|
expect(e0.querySelector('#e1')!.namespaceURI).toMatch('svg')
|
||||||
|
expect(e0.querySelector('#e2')!.namespaceURI).toMatch('svg')
|
||||||
|
expect(e0.querySelector('#e3')!.namespaceURI).toMatch('xhtml')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should patch elements with correct namespaces', async () => {
|
||||||
|
const root = document.createElement('div')
|
||||||
|
document.body.appendChild(root)
|
||||||
|
const cls = ref('foo')
|
||||||
|
const App = {
|
||||||
|
setup: () => ({ cls }),
|
||||||
|
template: `
|
||||||
|
<div>
|
||||||
|
<svg id="f1" :class="cls">
|
||||||
|
<foreignObject>
|
||||||
|
<div id="f2" :class="cls"/>
|
||||||
|
</foreignObject>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
render(h(App), root)
|
||||||
|
const f1 = document.querySelector('#f1')!
|
||||||
|
const f2 = document.querySelector('#f2')!
|
||||||
|
expect(f1.getAttribute('class')).toBe('foo')
|
||||||
|
expect(f2.className).toBe('foo')
|
||||||
|
|
||||||
|
// set a transition class on the <div> - which is only respected on non-svg
|
||||||
|
// patches
|
||||||
|
;(f2 as any)._vtc = ['baz']
|
||||||
|
cls.value = 'bar'
|
||||||
|
await nextTick()
|
||||||
|
expect(f1.getAttribute('class')).toBe('bar')
|
||||||
|
expect(f2.className).toBe('bar baz')
|
||||||
|
})
|
||||||
|
})
|
Loading…
Reference in New Issue
Block a user