Skip to content

Commit 681a359

Browse files
committed
fix: create svg nested children with correct namespace
1 parent cb8013c commit 681a359

File tree

4 files changed

+100
-10
lines changed

4 files changed

+100
-10
lines changed

.changeset/long-shirts-thank.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@qwik.dev/core': patch
3+
---
4+
5+
fix: create svg nested children with correct namespace

packages/qwik/src/core/client/vnode-namespace.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
} from './types';
1111
import {
1212
ensureElementVNode,
13+
fastNamespaceURI,
1314
shouldIgnoreChildren,
1415
vnode_getDOMChildNodes,
1516
vnode_getDomParentVNode,
@@ -35,13 +36,15 @@ export const vnode_isDefaultNamespace = (vnode: ElementVNode): boolean => {
3536
return (flags & VNodeFlags.NAMESPACE_MASK) === 0;
3637
};
3738

38-
export const vnode_getElementNamespaceFlags = (elementName: string) => {
39-
if (isSvgElement(elementName)) {
40-
return VNodeFlags.NS_svg;
41-
} else if (isMathElement(elementName)) {
42-
return VNodeFlags.NS_math;
43-
} else {
44-
return VNodeFlags.NS_html;
39+
export const vnode_getElementNamespaceFlags = (element: Element) => {
40+
const namespace = fastNamespaceURI(element);
41+
switch (namespace) {
42+
case SVG_NS:
43+
return VNodeFlags.NS_svg;
44+
case MATH_NS:
45+
return VNodeFlags.NS_math;
46+
default:
47+
return VNodeFlags.NS_html;
4548
}
4649
};
4750

packages/qwik/src/core/client/vnode.ts

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ import {
170170
} from './vnode-namespace';
171171
import { escapeHTML } from '../shared/utils/character-escaping';
172172
import { QError, qError } from '../shared/error/error';
173+
import { qTest } from '../shared/utils/qdev';
173174

174175
//////////////////////////////////////////////////////////////////////////////////////////////////////
175176

@@ -1192,9 +1193,14 @@ export const vnode_getElementName = (vnode: ElementVNode): string => {
11921193
const elementVNode = ensureElementVNode(vnode);
11931194
let elementName = elementVNode[ElementVNodeProps.elementName];
11941195
if (elementName === undefined) {
1195-
elementName = elementVNode[ElementVNodeProps.elementName] =
1196-
elementVNode[ElementVNodeProps.element].nodeName.toLowerCase();
1197-
elementVNode[VNodeProps.flags] |= vnode_getElementNamespaceFlags(elementName);
1196+
const element = elementVNode[ElementVNodeProps.element];
1197+
let nodeName = fastNodeName(element)!;
1198+
if (qTest) {
1199+
// DominoJS returns uppercase node names, we need to convert them to lowercase.
1200+
nodeName = nodeName.toLowerCase();
1201+
}
1202+
elementName = elementVNode[ElementVNodeProps.elementName] = nodeName;
1203+
elementVNode[VNodeProps.flags] |= vnode_getElementNamespaceFlags(element);
11981204
}
11991205
return elementName;
12001206
};
@@ -1405,6 +1411,22 @@ const fastFirstChild = (node: Node | null): Node | null => {
14051411
return node;
14061412
};
14071413

1414+
let _fastNamespaceURI: ((this: Element) => string | null) | null = null;
1415+
export const fastNamespaceURI = (element: Element): string | null => {
1416+
if (!_fastNamespaceURI) {
1417+
_fastNamespaceURI = fastGetter<typeof _fastNamespaceURI>(element, 'namespaceURI')!;
1418+
}
1419+
return _fastNamespaceURI.call(element);
1420+
};
1421+
1422+
let _fastNodeName: ((this: Element) => string | null) | null = null;
1423+
export const fastNodeName = (element: Element): string | null => {
1424+
if (!_fastNodeName) {
1425+
_fastNodeName = fastGetter<typeof _fastNodeName>(element, 'nodeName')!;
1426+
}
1427+
return _fastNodeName.call(element);
1428+
};
1429+
14081430
const fastGetter = <T>(prototype: any, name: string): T => {
14091431
let getter: any;
14101432
while (prototype && !(getter = Object.getOwnPropertyDescriptor(prototype, name)?.get)) {

packages/qwik/src/core/tests/render-namespace.spec.tsx

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,66 @@ describe.each([
117117

118118
await expect(container.document.body.querySelector('button')).toMatchDOM(<button></button>);
119119
});
120+
121+
it('should rerender svg nested children', async () => {
122+
const SvgComp = component$((props: { show: boolean }) => {
123+
return (
124+
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" key="0">
125+
<defs>
126+
{props.show && (
127+
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
128+
<stop offset="0%" style="stop-color:#ff0000;stop-opacity:1px" />
129+
<stop offset="100%" style="stop-color:#0000ff;stop-opacity:1px" />
130+
</linearGradient>
131+
)}
132+
</defs>
133+
</svg>
134+
);
135+
});
136+
const Parent = component$(() => {
137+
const show = useSignal(false);
138+
return (
139+
<button onClick$={() => (show.value = !show.value)}>
140+
<SvgComp show={show.value} />
141+
</button>
142+
);
143+
});
144+
const { vNode, container } = await render(<Parent />, { debug });
145+
expect(vNode).toMatchVDOM(
146+
<Component>
147+
<button>
148+
<Component>
149+
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" key="0">
150+
<defs></defs>
151+
</svg>
152+
</Component>
153+
</button>
154+
</Component>
155+
);
156+
157+
await trigger(container.element, 'button', 'click');
158+
expect(vNode).toMatchVDOM(
159+
<Component>
160+
<button>
161+
<Component>
162+
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" key="0">
163+
<defs>
164+
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
165+
<stop offset="0%" style="stop-color:#ff0000;stop-opacity:1px" />
166+
<stop offset="100%" style="stop-color:#0000ff;stop-opacity:1px" />
167+
</linearGradient>
168+
</defs>
169+
</svg>
170+
</Component>
171+
</button>
172+
</Component>
173+
);
174+
175+
expect(
176+
container.document.querySelector('svg')?.querySelector('linearGradient')?.namespaceURI
177+
).toEqual(SVG_NS);
178+
});
179+
120180
it('should rerender svg child elements', async () => {
121181
const SvgComp = component$((props: { child: JSXOutput }) => {
122182
return (

0 commit comments

Comments
 (0)