Skip to content

Commit a7749a3

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

File tree

5 files changed

+98
-11
lines changed

5 files changed

+98
-11
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-diff.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -722,7 +722,9 @@ export const vnode_diff = (
722722

723723
function expectElement(jsx: JSXNodeInternal, elementName: string) {
724724
const isSameElementName =
725-
vCurrent && vnode_isElementVNode(vCurrent) && elementName === vnode_getElementName(vCurrent);
725+
vCurrent &&
726+
vnode_isElementVNode(vCurrent) &&
727+
elementName.toLowerCase() === vnode_getElementName(vCurrent);
726728
const jsxKey: string | null = jsx.key;
727729
let needsQDispatchEventPatch = false;
728730
const currentFile = getFileLocationFromJsx(jsx.dev);

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: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1192,9 +1192,10 @@ export const vnode_getElementName = (vnode: ElementVNode): string => {
11921192
const elementVNode = ensureElementVNode(vnode);
11931193
let elementName = elementVNode[ElementVNodeProps.elementName];
11941194
if (elementName === undefined) {
1195-
elementName = elementVNode[ElementVNodeProps.elementName] =
1196-
elementVNode[ElementVNodeProps.element].nodeName.toLowerCase();
1197-
elementVNode[VNodeProps.flags] |= vnode_getElementNamespaceFlags(elementName);
1195+
const element = elementVNode[ElementVNodeProps.element];
1196+
const nodeName = fastNodeName(element)!.toLowerCase();
1197+
elementName = elementVNode[ElementVNodeProps.elementName] = nodeName;
1198+
elementVNode[VNodeProps.flags] |= vnode_getElementNamespaceFlags(element);
11981199
}
11991200
return elementName;
12001201
};
@@ -1405,6 +1406,22 @@ const fastFirstChild = (node: Node | null): Node | null => {
14051406
return node;
14061407
};
14071408

1409+
let _fastNamespaceURI: ((this: Element) => string | null) | null = null;
1410+
export const fastNamespaceURI = (element: Element): string | null => {
1411+
if (!_fastNamespaceURI) {
1412+
_fastNamespaceURI = fastGetter<typeof _fastNamespaceURI>(element, 'namespaceURI')!;
1413+
}
1414+
return _fastNamespaceURI.call(element);
1415+
};
1416+
1417+
let _fastNodeName: ((this: Element) => string | null) | null = null;
1418+
export const fastNodeName = (element: Element): string | null => {
1419+
if (!_fastNodeName) {
1420+
_fastNodeName = fastGetter<typeof _fastNodeName>(element, 'nodeName')!;
1421+
}
1422+
return _fastNodeName.call(element);
1423+
};
1424+
14081425
const fastGetter = <T>(prototype: any, name: string): T => {
14091426
let getter: any;
14101427
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)