Skip to content

Commit a8f3dc6

Browse files
authored
🎨 Add built-in support for inline SVG (#202)
* Add built-in support for inline SVG (adds ~115b) * Port basic SVG tests from preact-compat
1 parent fbfa26b commit a8f3dc6

File tree

4 files changed

+79
-19
lines changed

4 files changed

+79
-19
lines changed

src/dom/index.js

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export function removeNode(node) {
3030
* @param {any} previousValue The last value that was set for this name/node pair
3131
* @private
3232
*/
33-
export function setAccessor(node, name, value) {
33+
export function setAccessor(node, name, value, isSvg) {
3434
ensureNodeData(node)[name] = value;
3535

3636
if (name==='key' || name==='children') return;
@@ -44,7 +44,7 @@ export function setAccessor(node, name, value) {
4444
else if (name==='dangerouslySetInnerHTML') {
4545
if (value && value.__html) node.innerHTML = value.__html;
4646
}
47-
else if (name!=='type' && name in node) {
47+
else if (!isSvg && name!=='type' && name in node) {
4848
setProperty(node, name, empty(value) ? '' : value);
4949
if (falsey(value)) node.removeAttribute(name);
5050
}
@@ -55,11 +55,16 @@ export function setAccessor(node, name, value) {
5555
else if (!value) node.removeEventListener(name, eventProxy);
5656
l[name] = value;
5757
}
58-
else if (falsey(value)) {
59-
node.removeAttribute(name);
60-
}
61-
else if (typeof value!=='object' && !isFunction(value)) {
62-
node.setAttribute(name, value);
58+
else {
59+
let ns = isSvg && name.match(/^xlink\:?(.+)/);
60+
if (falsey(value)) {
61+
if (ns) node.removeAttributeNS('http://www.w3.org/1999/xlink', ns[1]);
62+
else node.removeAttribute(name);
63+
}
64+
else if (typeof value!=='object' && !isFunction(value)) {
65+
if (ns) node.setAttributeNS('http://www.w3.org/1999/xlink', ns[1], value);
66+
else node.setAttribute(name, value);
67+
}
6368
}
6469
}
6570

src/dom/recycler.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,9 @@ export function collectNode(node) {
1414
}
1515

1616

17-
export function createNode(nodeName) {
17+
export function createNode(nodeName, isSvg) {
1818
let name = toLowerCase(nodeName),
19-
list = nodes[name],
20-
node = list && list.pop() || document.createElement(nodeName);
19+
node = nodes[name] && nodes[name].pop() || (isSvg ? document.createElementNS('http://www.w3.org/2000/svg', nodeName) : document.createElement(nodeName));
2120
ensureNodeData(node);
2221
node.normalizedNodeName = name;
2322
return node;

src/vdom/diff.js

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { ATTR_KEY } from '../constants';
2-
import { empty, isString, isFunction } from '../util';
2+
import { toLowerCase, empty, isString, isFunction } from '../util';
33
import { hook, deepHook } from '../hooks';
44
import { isSameNodeType, isNamedNode } from '.';
55
import { isFunctionalComponent, buildFunctionalComponent } from './functional-component';
@@ -9,6 +9,8 @@ import { createNode, collectNode } from '../dom/recycler';
99
import { unmountComponent } from './component';
1010

1111

12+
let SVG_MODE = false;
13+
1214

1315
/** Apply differences in a given vnode (and it's deep children) to a real DOM Node.
1416
* @param {Element} [dom=null] A DOM node to mutate into the shape of the `vnode`
@@ -36,19 +38,26 @@ export function diff(dom, vnode, context, mountAll, unmountChildrenOnly) {
3638
return document.createTextNode(vnode);
3739
}
3840

39-
if (isFunction(vnode.nodeName)) {
41+
let out = dom,
42+
nodeName = vnode.nodeName,
43+
svgMode;
44+
45+
if (isFunction(nodeName)) {
4046
return buildComponentFromVNode(dom, vnode, context, mountAll);
4147
}
48+
if (!isString(nodeName)) {
49+
nodeName = String(nodeName);
50+
}
4251

43-
let out = dom,
44-
nodeName = vnode.nodeName;
45-
if (!isString(nodeName)) nodeName = String(nodeName);
52+
svgMode = toLowerCase(nodeName)==='svg';
53+
54+
if (svgMode) SVG_MODE = true;
4655

4756
if (!dom) {
48-
out = createNode(nodeName);
57+
out = createNode(nodeName, SVG_MODE);
4958
}
5059
else if (!isNamedNode(dom, nodeName)) {
51-
out = createNode(nodeName);
60+
out = createNode(nodeName, SVG_MODE);
5261
// move children into the replacement node
5362
while (dom.firstChild) out.appendChild(dom.firstChild);
5463
// reclaim element nodes
@@ -63,6 +72,8 @@ export function diff(dom, vnode, context, mountAll, unmountChildrenOnly) {
6372
(out[ATTR_KEY].ref = originalAttributes.ref)(out);
6473
}
6574

75+
if (svgMode) SVG_MODE = false;
76+
6677
return out;
6778
}
6879

@@ -232,15 +243,15 @@ function diffAttributes(dom, attrs) {
232243
// removeAttributes(dom, old, attrs || EMPTY);
233244
for (let name in old) {
234245
if (!attrs || !(name in attrs)) {
235-
setAccessor(dom, name, null);
246+
setAccessor(dom, name, null, SVG_MODE);
236247
}
237248
}
238249

239250
// new & updated
240251
if (attrs) {
241252
for (let name in attrs) {
242253
if (!(name in old) || attrs[name]!=(name==='value' || name==='selected' || name==='checked' ? dom[name] : old[name])) {
243-
setAccessor(dom, name, attrs[name]);
254+
setAccessor(dom, name, attrs[name], SVG_MODE);
244255
}
245256
}
246257
}

test/browser/svg.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { h, render } from '../../src/preact';
2+
/** @jsx h */
3+
4+
describe('svg', () => {
5+
let scratch;
6+
7+
before( () => {
8+
scratch = document.createElement('div');
9+
(document.body || document.documentElement).appendChild(scratch);
10+
});
11+
12+
beforeEach( () => {
13+
scratch.innerHTML = '';
14+
});
15+
16+
after( () => {
17+
scratch.parentNode.removeChild(scratch);
18+
scratch = null;
19+
});
20+
21+
it('should render SVG to string', () => {
22+
render((
23+
<svg viewBox="0 0 360 360">
24+
<path stroke="white" fill="black" d="M347.1 357.9L183.3 256.5 13 357.9V1.7h334.1v356.2zM58.5 47.2v231.4l124.8-74.1 118.3 72.8V47.2H58.5z" />
25+
</svg>
26+
), scratch);
27+
28+
expect(scratch.innerHTML).to.equal(`
29+
<svg viewBox="0 0 360 360">
30+
<path stroke="white" fill="black" d="M347.1 357.9L183.3 256.5 13 357.9V1.7h334.1v356.2zM58.5 47.2v231.4l124.8-74.1 118.3 72.8V47.2H58.5z"></path>
31+
</svg>
32+
`.replace(/[\n\t]+/g,''));
33+
});
34+
35+
it('should render SVG to DOM', () => {
36+
const Demo = () => (
37+
<svg viewBox="0 0 360 360">
38+
<path stroke="white" fill="black" d="M347.1 357.9L183.3 256.5 13 357.9V1.7h334.1v356.2zM58.5 47.2v231.4l124.8-74.1 118.3 72.8V47.2H58.5z" />
39+
</svg>
40+
);
41+
render(<Demo />, scratch);
42+
43+
expect(scratch.innerHTML).to.equal('<svg viewBox="0 0 360 360"><path stroke="white" fill="black" d="M347.1 357.9L183.3 256.5 13 357.9V1.7h334.1v356.2zM58.5 47.2v231.4l124.8-74.1 118.3 72.8V47.2H58.5z"></path></svg>');
44+
});
45+
});

0 commit comments

Comments
 (0)