diff --git a/.changeset/long-shirts-thank.md b/.changeset/long-shirts-thank.md new file mode 100644 index 00000000000..5204b0320bb --- /dev/null +++ b/.changeset/long-shirts-thank.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: create svg nested children with correct namespace diff --git a/packages/qwik/src/core/client/vnode-diff.ts b/packages/qwik/src/core/client/vnode-diff.ts index d54d298a2f2..10debcde491 100644 --- a/packages/qwik/src/core/client/vnode-diff.ts +++ b/packages/qwik/src/core/client/vnode-diff.ts @@ -722,7 +722,9 @@ export const vnode_diff = ( function expectElement(jsx: JSXNodeInternal, elementName: string) { const isSameElementName = - vCurrent && vnode_isElementVNode(vCurrent) && elementName === vnode_getElementName(vCurrent); + vCurrent && + vnode_isElementVNode(vCurrent) && + elementName.toLowerCase() === vnode_getElementName(vCurrent); const jsxKey: string | null = jsx.key; let needsQDispatchEventPatch = false; const currentFile = getFileLocationFromJsx(jsx.dev); diff --git a/packages/qwik/src/core/client/vnode-namespace.ts b/packages/qwik/src/core/client/vnode-namespace.ts index b2e361f0639..e80eb1c98a7 100644 --- a/packages/qwik/src/core/client/vnode-namespace.ts +++ b/packages/qwik/src/core/client/vnode-namespace.ts @@ -10,6 +10,7 @@ import { } from './types'; import { ensureElementVNode, + fastNamespaceURI, shouldIgnoreChildren, vnode_getDOMChildNodes, vnode_getDomParentVNode, @@ -35,13 +36,15 @@ export const vnode_isDefaultNamespace = (vnode: ElementVNode): boolean => { return (flags & VNodeFlags.NAMESPACE_MASK) === 0; }; -export const vnode_getElementNamespaceFlags = (elementName: string) => { - if (isSvgElement(elementName)) { - return VNodeFlags.NS_svg; - } else if (isMathElement(elementName)) { - return VNodeFlags.NS_math; - } else { - return VNodeFlags.NS_html; +export const vnode_getElementNamespaceFlags = (element: Element) => { + const namespace = fastNamespaceURI(element); + switch (namespace) { + case SVG_NS: + return VNodeFlags.NS_svg; + case MATH_NS: + return VNodeFlags.NS_math; + default: + return VNodeFlags.NS_html; } }; diff --git a/packages/qwik/src/core/client/vnode.ts b/packages/qwik/src/core/client/vnode.ts index 6b64d14ebe3..1e876c96f08 100644 --- a/packages/qwik/src/core/client/vnode.ts +++ b/packages/qwik/src/core/client/vnode.ts @@ -1192,9 +1192,10 @@ export const vnode_getElementName = (vnode: ElementVNode): string => { const elementVNode = ensureElementVNode(vnode); let elementName = elementVNode[ElementVNodeProps.elementName]; if (elementName === undefined) { - elementName = elementVNode[ElementVNodeProps.elementName] = - elementVNode[ElementVNodeProps.element].nodeName.toLowerCase(); - elementVNode[VNodeProps.flags] |= vnode_getElementNamespaceFlags(elementName); + const element = elementVNode[ElementVNodeProps.element]; + const nodeName = fastNodeName(element)!.toLowerCase(); + elementName = elementVNode[ElementVNodeProps.elementName] = nodeName; + elementVNode[VNodeProps.flags] |= vnode_getElementNamespaceFlags(element); } return elementName; }; @@ -1405,6 +1406,22 @@ const fastFirstChild = (node: Node | null): Node | null => { return node; }; +let _fastNamespaceURI: ((this: Element) => string | null) | null = null; +export const fastNamespaceURI = (element: Element): string | null => { + if (!_fastNamespaceURI) { + _fastNamespaceURI = fastGetter(element, 'namespaceURI')!; + } + return _fastNamespaceURI.call(element); +}; + +let _fastNodeName: ((this: Element) => string | null) | null = null; +export const fastNodeName = (element: Element): string | null => { + if (!_fastNodeName) { + _fastNodeName = fastGetter(element, 'nodeName')!; + } + return _fastNodeName.call(element); +}; + const fastGetter = (prototype: any, name: string): T => { let getter: any; while (prototype && !(getter = Object.getOwnPropertyDescriptor(prototype, name)?.get)) { diff --git a/packages/qwik/src/core/tests/render-namespace.spec.tsx b/packages/qwik/src/core/tests/render-namespace.spec.tsx index 4302e0d03f7..5e1086d5d92 100644 --- a/packages/qwik/src/core/tests/render-namespace.spec.tsx +++ b/packages/qwik/src/core/tests/render-namespace.spec.tsx @@ -117,6 +117,66 @@ describe.each([ await expect(container.document.body.querySelector('button')).toMatchDOM(); }); + + it('should rerender svg nested children', async () => { + const SvgComp = component$((props: { show: boolean }) => { + return ( + + + {props.show && ( + + + + + )} + + + ); + }); + const Parent = component$(() => { + const show = useSignal(false); + return ( + + ); + }); + const { vNode, container } = await render(, { debug }); + expect(vNode).toMatchVDOM( + + + + ); + + await trigger(container.element, 'button', 'click'); + expect(vNode).toMatchVDOM( + + + + ); + + expect( + container.document.querySelector('svg')?.querySelector('linearGradient')?.namespaceURI + ).toEqual(SVG_NS); + }); + it('should rerender svg child elements', async () => { const SvgComp = component$((props: { child: JSXOutput }) => { return (