From 1eb9106d3d499cfb2b03eacd1365c519e2de6fce Mon Sep 17 00:00:00 2001 From: Ryan Christian Date: Tue, 30 Apr 2024 01:18:52 -0500 Subject: [PATCH 01/12] feat: Support creating MathML elements --- src/diff/index.js | 5 +++++ test/browser/mathml.test.js | 24 ++++++++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 test/browser/mathml.test.js diff --git a/src/diff/index.js b/src/diff/index.js index 3766beb7d1..3e2c8934f3 100644 --- a/src/diff/index.js +++ b/src/diff/index.js @@ -404,6 +404,11 @@ function diffElementNodes( if (isSvg) { dom = document.createElementNS('http://www.w3.org/2000/svg', nodeType); + } else if (nodeType === 'math') { + dom = document.createElementNS( + 'http://www.w3.org/1998/Math/MathML', + nodeType + ); } else { dom = document.createElement(nodeType, newProps.is && newProps); } diff --git a/test/browser/mathml.test.js b/test/browser/mathml.test.js new file mode 100644 index 0000000000..1d4790bf38 --- /dev/null +++ b/test/browser/mathml.test.js @@ -0,0 +1,24 @@ +import { createElement, render } from 'preact'; +import { setupScratch, teardown } from '../_util/helpers'; + +/** @jsx createElement */ + +describe('mathml', () => { + let scratch; + + beforeEach(() => { + scratch = setupScratch(); + }); + + afterEach(() => { + teardown(scratch); + }); + + it('should render with the correct namespace URI', () => { + render(, scratch); + + let namespace = scratch.querySelector('math').namespaceURI; + + expect(namespace).to.equal('http://www.w3.org/1998/Math/MathML'); + }); +}); From f4139619828db3a5ea3d5204271e5e3e4a10eab5 Mon Sep 17 00:00:00 2001 From: Ryan Christian Date: Tue, 30 Apr 2024 02:01:13 -0500 Subject: [PATCH 02/12] test: Add svg & mathml child namespace tests --- test/browser/mathml.test.js | 13 +++++++++++++ test/browser/svg.test.js | 13 +++++++++++++ 2 files changed, 26 insertions(+) diff --git a/test/browser/mathml.test.js b/test/browser/mathml.test.js index 1d4790bf38..da4c77b9c3 100644 --- a/test/browser/mathml.test.js +++ b/test/browser/mathml.test.js @@ -21,4 +21,17 @@ describe('mathml', () => { expect(namespace).to.equal('http://www.w3.org/1998/Math/MathML'); }); + + it('should render children with the correct namespace URI', () => { + render( + + + , + scratch + ); + + let namespace = scratch.querySelector('mrow').namespaceURI; + + expect(namespace).to.equal('http://www.w3.org/1998/Math/MathML'); + }); }); diff --git a/test/browser/svg.test.js b/test/browser/svg.test.js index 65ddf642d6..b4f3db9a8d 100644 --- a/test/browser/svg.test.js +++ b/test/browser/svg.test.js @@ -133,6 +133,19 @@ describe('svg', () => { expect(namespace).to.equal('http://www.w3.org/2000/svg'); }); + it('should render children with the correct namespace URI', () => { + render( + + Foo + , + scratch + ); + + let namespace = scratch.querySelector('text').namespaceURI; + + expect(namespace).to.equal('http://www.w3.org/2000/svg'); + }); + it('should use attributes for className', () => { const Demo = ({ c }) => ( From ded6e001421881206df44534793e5c9b58be900f Mon Sep 17 00:00:00 2001 From: Ryan Christian Date: Tue, 30 Apr 2024 02:04:40 -0500 Subject: [PATCH 03/12] refactor: Switch `isSvg` to a `namespace` var to support mathml --- src/component.js | 2 +- src/diff/children.js | 6 +++--- src/diff/index.js | 29 +++++++++++++++-------------- src/diff/props.js | 14 +++++++------- src/internal.d.ts | 6 ++++++ src/render.js | 2 +- 6 files changed, 33 insertions(+), 26 deletions(-) diff --git a/src/component.js b/src/component.js index 4f4e5b2744..faa909806c 100644 --- a/src/component.js +++ b/src/component.js @@ -136,7 +136,7 @@ function renderComponent(component) { newVNode, oldVNode, component._globalContext, - component._parentDom.ownerSVGElement !== undefined, + component._parentDom.ownerSVGElement !== undefined ? 2 : 1, oldVNode._flags & MODE_HYDRATE ? [oldDom] : null, commitQueue, oldDom == null ? getDomSibling(oldVNode) : oldDom, diff --git a/src/diff/children.js b/src/diff/children.js index 1b4d6b55dc..87c9590ac0 100644 --- a/src/diff/children.js +++ b/src/diff/children.js @@ -15,7 +15,7 @@ import { getDomSibling } from '../component'; * diff'ed against newParentVNode * @param {object} globalContext The current context object - modified by * getChildContext - * @param {boolean} isSvg Whether or not this DOM node is an SVG node + * @param {ElementNamespace} namespace Current namespace of the DOM node (HTML, SVG, or MathML) * @param {Array} excessDomChildren * @param {Array} commitQueue List of components which have callbacks * to invoke in commitRoot @@ -32,7 +32,7 @@ export function diffChildren( newParentVNode, oldParentVNode, globalContext, - isSvg, + namespace, excessDomChildren, commitQueue, oldDom, @@ -87,7 +87,7 @@ export function diffChildren( childVNode, oldVNode, globalContext, - isSvg, + namespace, excessDomChildren, commitQueue, oldDom, diff --git a/src/diff/index.js b/src/diff/index.js index 3e2c8934f3..3a78f31b30 100644 --- a/src/diff/index.js +++ b/src/diff/index.js @@ -18,7 +18,7 @@ import options from '../options'; * @param {VNode} oldVNode The old virtual node * @param {object} globalContext The current context object. Modified by * getChildContext - * @param {boolean} isSvg Whether or not this element is an SVG node + * @param {ElementNamespace} namespace Current namespace of the DOM node (HTML, SVG, or MathML) * @param {Array} excessDomChildren * @param {Array} commitQueue List of components which have callbacks * to invoke in commitRoot @@ -34,7 +34,7 @@ export function diff( newVNode, oldVNode, globalContext, - isSvg, + namespace, excessDomChildren, commitQueue, oldDom, @@ -245,7 +245,7 @@ export function diff( newVNode, oldVNode, globalContext, - isSvg, + namespace, excessDomChildren, commitQueue, oldDom, @@ -294,7 +294,7 @@ export function diff( newVNode, oldVNode, globalContext, - isSvg, + namespace, excessDomChildren, commitQueue, isHydrating, @@ -341,7 +341,7 @@ export function commitRoot(commitQueue, root, refQueue) { * @param {VNode} newVNode The new virtual node * @param {VNode} oldVNode The old virtual node * @param {object} globalContext The current context object - * @param {boolean} isSvg Whether or not this DOM node is an SVG node + * @param {ElementNamespace} namespace Current namespace of the DOM node (HTML, SVG, or MathML) * @param {Array} excessDomChildren * @param {Array} commitQueue List of components which have callbacks * to invoke in commitRoot @@ -354,7 +354,7 @@ function diffElementNodes( newVNode, oldVNode, globalContext, - isSvg, + namespace, excessDomChildren, commitQueue, isHydrating, @@ -376,7 +376,8 @@ function diffElementNodes( let checked; // Tracks entering and exiting SVG namespace when descending through the tree. - if (nodeType === 'svg') isSvg = true; + if (nodeType === 'svg') namespace = 2; + else if (nodeType === 'math') namespace = 3; if (excessDomChildren != null) { for (i = 0; i < excessDomChildren.length; i++) { @@ -402,9 +403,9 @@ function diffElementNodes( return document.createTextNode(newProps); } - if (isSvg) { + if (namespace == 2) { dom = document.createElementNS('http://www.w3.org/2000/svg', nodeType); - } else if (nodeType === 'math') { + } else if (namespace == 3) { dom = document.createElementNS( 'http://www.w3.org/1998/Math/MathML', nodeType @@ -454,7 +455,7 @@ function diffElementNodes( ) { continue; } - setProperty(dom, i, null, value, isSvg); + setProperty(dom, i, null, value, namespace); } } @@ -475,7 +476,7 @@ function diffElementNodes( (!isHydrating || typeof value == 'function') && oldProps[i] !== value ) { - setProperty(dom, i, value, oldProps[i], isSvg); + setProperty(dom, i, value, oldProps[i], namespace); } } @@ -501,7 +502,7 @@ function diffElementNodes( newVNode, oldVNode, globalContext, - isSvg && nodeType !== 'foreignObject', + nodeType == 'foreignObject' ? 1 : namespace, excessDomChildren, commitQueue, excessDomChildren @@ -535,12 +536,12 @@ function diffElementNodes( // again, which triggers IE11 to re-evaluate the select value (nodeType === 'option' && inputValue !== oldProps[i])) ) { - setProperty(dom, i, inputValue, oldProps[i], false); + setProperty(dom, i, inputValue, oldProps[i], namespace); } i = 'checked'; if (checked !== undefined && checked !== dom[i]) { - setProperty(dom, i, checked, oldProps[i], false); + setProperty(dom, i, checked, oldProps[i], namespace); } } } diff --git a/src/diff/props.js b/src/diff/props.js index 722f1a5061..0858d9e77f 100644 --- a/src/diff/props.js +++ b/src/diff/props.js @@ -32,9 +32,9 @@ let eventClock = 0; * @param {string} name The name of the property to set * @param {*} value The value to set the property to * @param {*} oldValue The old value the property had - * @param {boolean} isSvg Whether or not this DOM node is an SVG node or not + * @param {ElementNamespace} namespace Whether or not this DOM node is an SVG node or not */ -export function setProperty(dom, name, value, oldValue, isSvg) { +export function setProperty(dom, name, value, oldValue, namespace) { let useCapture; o: if (name === 'style') { @@ -83,10 +83,10 @@ export function setProperty(dom, name, value, oldValue, isSvg) { if (!oldValue) { value._attached = eventClock; dom.addEventListener( - name, - useCapture ? eventProxyCapture : eventProxy, - useCapture - ); + name, + useCapture ? eventProxyCapture : eventProxy, + useCapture + ); } else { value._attached = oldValue._attached; } @@ -98,7 +98,7 @@ export function setProperty(dom, name, value, oldValue, isSvg) { ); } } else { - if (isSvg) { + if (namespace == 2) { // Normalize incorrect prop usage for SVG: // - xlink:href / xlinkHref --> href (xlink:href was removed from SVG and isn't needed) // - className --> class diff --git a/src/internal.d.ts b/src/internal.d.ts index fda7ce89fd..8e03c6ae25 100644 --- a/src/internal.d.ts +++ b/src/internal.d.ts @@ -16,6 +16,12 @@ declare global { useDebugvalue = 11 } + export enum ElementNamespace { + html = 1, + svg = 2, + mathml = 3 + } + export interface DevSource { fileName: string; lineNumber: number; diff --git a/src/render.js b/src/render.js index 1ee326bc92..ff018aa4a3 100644 --- a/src/render.js +++ b/src/render.js @@ -41,7 +41,7 @@ export function render(vnode, parentDom, replaceNode) { vnode, oldVNode || EMPTY_OBJ, EMPTY_OBJ, - parentDom.ownerSVGElement !== undefined, + parentDom.ownerSVGElement !== undefined ? 2 : 1, !isHydrating && replaceNode ? [replaceNode] : oldVNode From d899c8a1ea4088fb457556abbd8e7c846a77af3e Mon Sep 17 00:00:00 2001 From: Ryan Christian Date: Tue, 30 Apr 2024 03:40:21 -0500 Subject: [PATCH 04/12] test: Copy over more SVG tests --- test/browser/mathml.test.js | 61 +++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/test/browser/mathml.test.js b/test/browser/mathml.test.js index da4c77b9c3..b54a073716 100644 --- a/test/browser/mathml.test.js +++ b/test/browser/mathml.test.js @@ -34,4 +34,65 @@ describe('mathml', () => { expect(namespace).to.equal('http://www.w3.org/1998/Math/MathML'); }); + + it('should use attributes for className', () => { + const Demo = ({ c }) => ( + + + c + 2 + + + ); + render(, scratch); + let root = scratch.firstChild; + sinon.spy(root, 'removeAttribute'); + render(, scratch); + expect(root.removeAttribute).to.have.been.calledOnce.and.calledWith( + 'class' + ); + + root.removeAttribute.restore(); + + render(
, scratch); + render(, scratch); + root = scratch.firstChild; + sinon.spy(root, 'setAttribute'); + render(, scratch); + expect(root.setAttribute).to.have.been.calledOnce.and.calledWith( + 'class', + 'foo_2' + ); + + root.setAttribute.restore(); + }); + + it('should support class attribute', () => { + render(, scratch); + + expect(scratch.innerHTML).to.contain(` class="foo bar"`); + }); + + it('should support className attribute', () => { + render(, scratch); + + expect(scratch.innerHTML).to.contain(` class="foo bar"`); + }); + + it('should transition from DOM to MathML and back', () => { + render( +
+ + + c + 2 + + +
, + scratch + ); + + expect(scratch.firstChild).to.be.an('HTMLDivElement'); + expect(scratch.firstChild.firstChild).to.be.an('MathMLElement'); + }); }); From fae2f58a7a89b9677df204d47866db5216771ee9 Mon Sep 17 00:00:00 2001 From: Ryan Christian Date: Tue, 30 Apr 2024 02:23:02 -0500 Subject: [PATCH 05/12] refactor: golf & fix comment --- src/diff/index.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/diff/index.js b/src/diff/index.js index 3a78f31b30..fbef9a0aca 100644 --- a/src/diff/index.js +++ b/src/diff/index.js @@ -375,7 +375,7 @@ function diffElementNodes( let inputValue; let checked; - // Tracks entering and exiting SVG namespace when descending through the tree. + // Tracks entering and exiting namespaces when descending through the tree. if (nodeType === 'svg') namespace = 2; else if (nodeType === 'math') namespace = 3; @@ -403,15 +403,15 @@ function diffElementNodes( return document.createTextNode(newProps); } - if (namespace == 2) { - dom = document.createElementNS('http://www.w3.org/2000/svg', nodeType); - } else if (namespace == 3) { + if (namespace == 1) { + dom = document.createElement(nodeType, newProps.is && newProps); + } else { dom = document.createElementNS( - 'http://www.w3.org/1998/Math/MathML', + namespace == 2 + ? 'http://www.w3.org/2000/svg' + : 'http://www.w3.org/1998/Math/MathML', nodeType ); - } else { - dom = document.createElement(nodeType, newProps.is && newProps); } // we created a new parent, so none of the previously attached children can be reused: @@ -502,7 +502,7 @@ function diffElementNodes( newVNode, oldVNode, globalContext, - nodeType == 'foreignObject' ? 1 : namespace, + nodeType === 'foreignObject' ? 1 : namespace, excessDomChildren, commitQueue, excessDomChildren From 90edbab8984733226db00ef28298e9406c3a3fc7 Mon Sep 17 00:00:00 2001 From: Ryan Christian Date: Tue, 30 Apr 2024 07:46:35 -0500 Subject: [PATCH 06/12] refactor: Fix namespace inheritance from parent --- src/component.js | 6 +++++- src/render.js | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/component.js b/src/component.js index faa909806c..a215592f87 100644 --- a/src/component.js +++ b/src/component.js @@ -136,7 +136,11 @@ function renderComponent(component) { newVNode, oldVNode, component._globalContext, - component._parentDom.ownerSVGElement !== undefined ? 2 : 1, + component._parentDom.namespaceURI == 'http://www.w3.org/1999/xhtml' + ? 1 + : component._parentDom.namespaceURI == 'http://www.w3.org/2000/svg' + ? 2 + : 3, oldVNode._flags & MODE_HYDRATE ? [oldDom] : null, commitQueue, oldDom == null ? getDomSibling(oldVNode) : oldDom, diff --git a/src/render.js b/src/render.js index ff018aa4a3..dccc97f226 100644 --- a/src/render.js +++ b/src/render.js @@ -41,7 +41,11 @@ export function render(vnode, parentDom, replaceNode) { vnode, oldVNode || EMPTY_OBJ, EMPTY_OBJ, - parentDom.ownerSVGElement !== undefined ? 2 : 1, + parentDom.namespaceURI == 'http://www.w3.org/1999/xhtml' + ? 1 + : parentDom.namespaceURI == 'http://www.w3.org/2000/svg' + ? 2 + : 3, !isHydrating && replaceNode ? [replaceNode] : oldVNode From 5a62c052272af8d548b741b8c5afbdaf737125f2 Mon Sep 17 00:00:00 2001 From: Ryan Christian Date: Tue, 30 Apr 2024 07:47:01 -0500 Subject: [PATCH 07/12] test: Determine SVG & MathML determine namespace from parent correctly --- test/browser/mathml.test.js | 14 ++++++++++++++ test/browser/svg.test.js | 13 ++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/test/browser/mathml.test.js b/test/browser/mathml.test.js index b54a073716..530a85ae55 100644 --- a/test/browser/mathml.test.js +++ b/test/browser/mathml.test.js @@ -35,6 +35,20 @@ describe('mathml', () => { expect(namespace).to.equal('http://www.w3.org/1998/Math/MathML'); }); + it('should inherit correct namespace URI from parent', () => { + const math = document.createElementNS( + 'http://www.w3.org/1998/Math/MathML', + 'math' + ); + scratch.appendChild(math); + + render(, scratch.firstChild); + + let namespace = scratch.querySelector('mrow').namespaceURI; + + expect(namespace).to.equal('http://www.w3.org/1998/Math/MathML'); + }); + it('should use attributes for className', () => { const Demo = ({ c }) => ( diff --git a/test/browser/svg.test.js b/test/browser/svg.test.js index b4f3db9a8d..420f2d474e 100644 --- a/test/browser/svg.test.js +++ b/test/browser/svg.test.js @@ -136,7 +136,7 @@ describe('svg', () => { it('should render children with the correct namespace URI', () => { render( - Foo + , scratch ); @@ -146,6 +146,17 @@ describe('svg', () => { expect(namespace).to.equal('http://www.w3.org/2000/svg'); }); + it('should inherit correct namespace URI from parent', () => { + const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); + scratch.appendChild(svg); + + render(, scratch.firstChild); + + let namespace = scratch.querySelector('text').namespaceURI; + + expect(namespace).to.equal('http://www.w3.org/2000/svg'); + }); + it('should use attributes for className', () => { const Demo = ({ c }) => ( From 58a043a87df11e63be413ff6a1f51560fccc4534 Mon Sep 17 00:00:00 2001 From: Ryan Christian Date: Tue, 30 Apr 2024 07:51:27 -0500 Subject: [PATCH 08/12] chore: Appease linters --- src/internal.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/internal.d.ts b/src/internal.d.ts index 8e03c6ae25..db760b3ab7 100644 --- a/src/internal.d.ts +++ b/src/internal.d.ts @@ -89,8 +89,8 @@ declare global { export type ComponentType

= ComponentClass

| FunctionComponent

; export interface PreactElement extends preact.ContainerNode { - // SVG detection - readonly ownerSVGElement?: SVGElement['ownerSVGElement']; + // Namespace detection + readonly namespaceURI?: string; // Property used to update Text nodes data?: CharacterData['data']; // Property to set __dangerouslySetInnerHTML From 7f3d7cb6995b2b428b49ade61164313d479c1d72 Mon Sep 17 00:00:00 2001 From: Ryan Christian Date: Tue, 30 Apr 2024 07:53:58 -0500 Subject: [PATCH 09/12] refactor: golf -- better gzip, worse brotli --- src/component.js | 9 +++++---- src/render.js | 8 ++++---- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/component.js b/src/component.js index a215592f87..b56c1bcb30 100644 --- a/src/component.js +++ b/src/component.js @@ -136,11 +136,12 @@ function renderComponent(component) { newVNode, oldVNode, component._globalContext, - component._parentDom.namespaceURI == 'http://www.w3.org/1999/xhtml' - ? 1 - : component._parentDom.namespaceURI == 'http://www.w3.org/2000/svg' + component._parentDom.namespaceURI == 'http://www.w3.org/2000/svg' ? 2 - : 3, + : component._parentDom.namespaceURI == + 'http://www.w3.org/1998/Math/MathML' + ? 3 + : 1, oldVNode._flags & MODE_HYDRATE ? [oldDom] : null, commitQueue, oldDom == null ? getDomSibling(oldVNode) : oldDom, diff --git a/src/render.js b/src/render.js index dccc97f226..2861befbee 100644 --- a/src/render.js +++ b/src/render.js @@ -41,11 +41,11 @@ export function render(vnode, parentDom, replaceNode) { vnode, oldVNode || EMPTY_OBJ, EMPTY_OBJ, - parentDom.namespaceURI == 'http://www.w3.org/1999/xhtml' - ? 1 - : parentDom.namespaceURI == 'http://www.w3.org/2000/svg' + parentDom.namespaceURI == 'http://www.w3.org/2000/svg' ? 2 - : 3, + : parentDom.namespaceURI == 'http://www.w3.org/1998/Math/MathML' + ? 3 + : 1, !isHydrating && replaceNode ? [replaceNode] : oldVNode From 9c4c61be1ec53ede4a22db123d28764f3f7cad9b Mon Sep 17 00:00:00 2001 From: Ryan Christian Date: Tue, 30 Apr 2024 08:05:33 -0500 Subject: [PATCH 10/12] test: Remove perhaps superfluous mathml tests for the moment --- test/browser/mathml.test.js | 44 ------------------------------------- 1 file changed, 44 deletions(-) diff --git a/test/browser/mathml.test.js b/test/browser/mathml.test.js index 530a85ae55..8a2e7d2093 100644 --- a/test/browser/mathml.test.js +++ b/test/browser/mathml.test.js @@ -49,50 +49,6 @@ describe('mathml', () => { expect(namespace).to.equal('http://www.w3.org/1998/Math/MathML'); }); - it('should use attributes for className', () => { - const Demo = ({ c }) => ( - - - c - 2 - - - ); - render(, scratch); - let root = scratch.firstChild; - sinon.spy(root, 'removeAttribute'); - render(, scratch); - expect(root.removeAttribute).to.have.been.calledOnce.and.calledWith( - 'class' - ); - - root.removeAttribute.restore(); - - render(

, scratch); - render(, scratch); - root = scratch.firstChild; - sinon.spy(root, 'setAttribute'); - render(, scratch); - expect(root.setAttribute).to.have.been.calledOnce.and.calledWith( - 'class', - 'foo_2' - ); - - root.setAttribute.restore(); - }); - - it('should support class attribute', () => { - render(, scratch); - - expect(scratch.innerHTML).to.contain(` class="foo bar"`); - }); - - it('should support className attribute', () => { - render(, scratch); - - expect(scratch.innerHTML).to.contain(` class="foo bar"`); - }); - it('should transition from DOM to MathML and back', () => { render(
From 19f7d5bec2b87d2cccfda10514078e84ac1a08ae Mon Sep 17 00:00:00 2001 From: Ryan Christian Date: Wed, 1 May 2024 03:14:05 -0500 Subject: [PATCH 11/12] test: Add tests for namespace after rerender --- test/browser/mathml.test.js | 31 +++++++++++++++++++++++++++++-- test/browser/svg.test.js | 27 ++++++++++++++++++++++++++- 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/test/browser/mathml.test.js b/test/browser/mathml.test.js index 8a2e7d2093..92e2bb6bcf 100644 --- a/test/browser/mathml.test.js +++ b/test/browser/mathml.test.js @@ -1,4 +1,5 @@ -import { createElement, render } from 'preact'; +import { createElement, Component, render } from 'preact'; +import { setupRerender } from 'preact/test-utils'; import { setupScratch, teardown } from '../_util/helpers'; /** @jsx createElement */ @@ -45,10 +46,36 @@ describe('mathml', () => { render(, scratch.firstChild); let namespace = scratch.querySelector('mrow').namespaceURI; - expect(namespace).to.equal('http://www.w3.org/1998/Math/MathML'); }); + it('should inherit correct namespace URI from parent upon updating', () => { + setupRerender(); + + const math = document.createElementNS( + 'http://www.w3.org/1998/Math/MathML', + 'math' + ); + scratch.appendChild(math); + + class App extends Component { + state = { show: true }; + componentDidMount() { + // eslint-disable-next-line + this.setState({ show: false }, () => { + expect(scratch.querySelector('mo').namespaceURI).to.equal( + 'http://www.w3.org/1998/Math/MathML' + ); + }); + } + render() { + return this.state.show ? 1 : 2; + } + } + + render(, scratch.firstChild); + }); + it('should transition from DOM to MathML and back', () => { render(
diff --git a/test/browser/svg.test.js b/test/browser/svg.test.js index 420f2d474e..1b19b50854 100644 --- a/test/browser/svg.test.js +++ b/test/browser/svg.test.js @@ -1,4 +1,5 @@ -import { createElement, render } from 'preact'; +import { createElement, Component, render } from 'preact'; +import { setupRerender } from 'preact/test-utils'; import { setupScratch, teardown, sortAttributes } from '../_util/helpers'; /** @jsx createElement */ @@ -157,6 +158,30 @@ describe('svg', () => { expect(namespace).to.equal('http://www.w3.org/2000/svg'); }); + it('should inherit correct namespace URI from parent upon updating', () => { + setupRerender(); + + const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); + scratch.appendChild(svg); + + class App extends Component { + state = { show: true }; + componentDidMount() { + // eslint-disable-next-line + this.setState({ show: false }, () => { + expect(scratch.querySelector('circle').namespaceURI).to.equal( + 'http://www.w3.org/2000/svg' + ); + }); + } + render() { + return this.state.show ? : ; + } + } + + render(, scratch.firstChild); + }); + it('should use attributes for className', () => { const Demo = ({ c }) => ( From 2d07ca89c7f6bd7a0511768c80e2a07282f68cf3 Mon Sep 17 00:00:00 2001 From: Ryan Christian Date: Wed, 1 May 2024 17:30:20 -0500 Subject: [PATCH 12/12] refactor: golf --- src/component.js | 7 +------ src/diff/children.js | 2 +- src/diff/index.js | 29 ++++++++++++++--------------- src/diff/props.js | 4 ++-- src/internal.d.ts | 6 ------ src/render.js | 6 +----- 6 files changed, 19 insertions(+), 35 deletions(-) diff --git a/src/component.js b/src/component.js index b56c1bcb30..287f3233f4 100644 --- a/src/component.js +++ b/src/component.js @@ -136,12 +136,7 @@ function renderComponent(component) { newVNode, oldVNode, component._globalContext, - component._parentDom.namespaceURI == 'http://www.w3.org/2000/svg' - ? 2 - : component._parentDom.namespaceURI == - 'http://www.w3.org/1998/Math/MathML' - ? 3 - : 1, + component._parentDom.namespaceURI, oldVNode._flags & MODE_HYDRATE ? [oldDom] : null, commitQueue, oldDom == null ? getDomSibling(oldVNode) : oldDom, diff --git a/src/diff/children.js b/src/diff/children.js index 87c9590ac0..912cc72d7c 100644 --- a/src/diff/children.js +++ b/src/diff/children.js @@ -15,7 +15,7 @@ import { getDomSibling } from '../component'; * diff'ed against newParentVNode * @param {object} globalContext The current context object - modified by * getChildContext - * @param {ElementNamespace} namespace Current namespace of the DOM node (HTML, SVG, or MathML) + * @param {string} namespace Current namespace of the DOM node (HTML, SVG, or MathML) * @param {Array} excessDomChildren * @param {Array} commitQueue List of components which have callbacks * to invoke in commitRoot diff --git a/src/diff/index.js b/src/diff/index.js index fbef9a0aca..a8bb012672 100644 --- a/src/diff/index.js +++ b/src/diff/index.js @@ -18,7 +18,7 @@ import options from '../options'; * @param {VNode} oldVNode The old virtual node * @param {object} globalContext The current context object. Modified by * getChildContext - * @param {ElementNamespace} namespace Current namespace of the DOM node (HTML, SVG, or MathML) + * @param {string} namespace Current namespace of the DOM node (HTML, SVG, or MathML) * @param {Array} excessDomChildren * @param {Array} commitQueue List of components which have callbacks * to invoke in commitRoot @@ -341,7 +341,7 @@ export function commitRoot(commitQueue, root, refQueue) { * @param {VNode} newVNode The new virtual node * @param {VNode} oldVNode The old virtual node * @param {object} globalContext The current context object - * @param {ElementNamespace} namespace Current namespace of the DOM node (HTML, SVG, or MathML) + * @param {string} namespace Current namespace of the DOM node (HTML, SVG, or MathML) * @param {Array} excessDomChildren * @param {Array} commitQueue List of components which have callbacks * to invoke in commitRoot @@ -376,8 +376,10 @@ function diffElementNodes( let checked; // Tracks entering and exiting namespaces when descending through the tree. - if (nodeType === 'svg') namespace = 2; - else if (nodeType === 'math') namespace = 3; + if (nodeType === 'svg') namespace = 'http://www.w3.org/2000/svg'; + else if (nodeType === 'math') + namespace = 'http://www.w3.org/1998/Math/MathML'; + else if (!namespace) namespace = 'http://www.w3.org/1999/xhtml'; if (excessDomChildren != null) { for (i = 0; i < excessDomChildren.length; i++) { @@ -403,16 +405,11 @@ function diffElementNodes( return document.createTextNode(newProps); } - if (namespace == 1) { - dom = document.createElement(nodeType, newProps.is && newProps); - } else { - dom = document.createElementNS( - namespace == 2 - ? 'http://www.w3.org/2000/svg' - : 'http://www.w3.org/1998/Math/MathML', - nodeType - ); - } + dom = document.createElementNS( + namespace, + nodeType, + newProps.is && newProps + ); // we created a new parent, so none of the previously attached children can be reused: excessDomChildren = null; @@ -502,7 +499,9 @@ function diffElementNodes( newVNode, oldVNode, globalContext, - nodeType === 'foreignObject' ? 1 : namespace, + nodeType === 'foreignObject' + ? 'http://www.w3.org/1999/xhtml' + : namespace, excessDomChildren, commitQueue, excessDomChildren diff --git a/src/diff/props.js b/src/diff/props.js index 0858d9e77f..d03492293a 100644 --- a/src/diff/props.js +++ b/src/diff/props.js @@ -32,7 +32,7 @@ let eventClock = 0; * @param {string} name The name of the property to set * @param {*} value The value to set the property to * @param {*} oldValue The old value the property had - * @param {ElementNamespace} namespace Whether or not this DOM node is an SVG node or not + * @param {string} namespace Whether or not this DOM node is an SVG node or not */ export function setProperty(dom, name, value, oldValue, namespace) { let useCapture; @@ -98,7 +98,7 @@ export function setProperty(dom, name, value, oldValue, namespace) { ); } } else { - if (namespace == 2) { + if (namespace == 'http://www.w3.org/2000/svg') { // Normalize incorrect prop usage for SVG: // - xlink:href / xlinkHref --> href (xlink:href was removed from SVG and isn't needed) // - className --> class diff --git a/src/internal.d.ts b/src/internal.d.ts index db760b3ab7..ab03abc67c 100644 --- a/src/internal.d.ts +++ b/src/internal.d.ts @@ -16,12 +16,6 @@ declare global { useDebugvalue = 11 } - export enum ElementNamespace { - html = 1, - svg = 2, - mathml = 3 - } - export interface DevSource { fileName: string; lineNumber: number; diff --git a/src/render.js b/src/render.js index 2861befbee..eeeb452a4f 100644 --- a/src/render.js +++ b/src/render.js @@ -41,11 +41,7 @@ export function render(vnode, parentDom, replaceNode) { vnode, oldVNode || EMPTY_OBJ, EMPTY_OBJ, - parentDom.namespaceURI == 'http://www.w3.org/2000/svg' - ? 2 - : parentDom.namespaceURI == 'http://www.w3.org/1998/Math/MathML' - ? 3 - : 1, + parentDom.namespaceURI, !isHydrating && replaceNode ? [replaceNode] : oldVNode