let runtimeDirectives: DirectiveNode[] | undefined
let dynamicPropNames: string[] | undefined
let dynamicComponent: string | CallExpression | undefined
- // technically this is web specific but we are keeping it in core to avoid
- // extra complexity
- let isSVG = false
+ let shouldUseBlock = false
// handle dynamic component
const isProp = findProp(node, 'is')
nodeType = toValidAssetId(tag, `component`)
} else {
// plain element
- nodeType = `"${node.tag}"`
- isSVG = node.tag === 'svg'
+ nodeType = `"${tag}"`
+ // <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 { loc } = node
- const vnode = isSVG
- ? // <svg> must be forced into blocks so that block updates inside retain
- // isSVG flag at runtime. (#639, #643)
- createSequenceExpression([
+ const vnode = shouldUseBlock
+ ? createSequenceExpression([
createCallExpression(context.helper(OPEN_BLOCK)),
createCallExpression(context.helper(CREATE_BLOCK), args, loc)
])
optimized: boolean
) {
const el = (vnode.el = hostCreateElement(vnode.type as string, isSVG))
- const { props, shapeFlag, transition, scopeId } = vnode
+ const { type, props, shapeFlag, transition, scopeId } = vnode
// props
if (props != null) {
null,
parentComponent,
parentSuspense,
- isSVG,
+ isSVG && type !== 'foreignObject',
optimized || vnode.dynamicChildren !== null
)
}
)
}
+ const areChildrenSVG = isSVG && n2.type !== 'foreignObject'
if (dynamicChildren != null) {
patchBlockChildren(
n1.dynamicChildren!,
el,
parentComponent,
parentSuspense,
- isSVG
+ areChildrenSVG
)
} else if (!optimized) {
// full diff
- patchChildren(n1, n2, el, null, parentComponent, parentSuspense, isSVG)
+ patchChildren(
+ n1,
+ n2,
+ el,
+ null,
+ parentComponent,
+ parentSuspense,
+ areChildrenSVG
+ )
}
if (newProps.onVnodeUpdated != null) {
--- /dev/null
+// 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')
+ })
+})