Skip to content

Commit a6b0780

Browse files
author
Mikhail Bashkirov
committed
feat(icon): enforce icon security using tagged templates
BREAKING: Icon definition requires a function using a tag passed via arguments: ```js // myicon.svg.js // before export default '<svg>...</svg>'; // after export default tag => tag`<svg>...</svg>`; ``` Application developers have an alternative shortcut to use in-place with lit-html: ```js // MyComponent.js // before render() { return html` <lion-icon .svg="${'<svg>...</svg>'}"></lion-icon> `; } // after render() { return html` <lion-icon .svg="${html`<svg>...</svg>`}"></lion-icon> `; } ```
1 parent 0ef6a7c commit a6b0780

28 files changed

+129
-36
lines changed

packages/icon/README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,19 @@ Use it in your lit-html template:
3232
<lion-icon .svg=${bugSvg}></lion-icon>
3333
```
3434

35+
### Icon format
36+
37+
Icon file is an ES module with an extension `.svg.js` which exports a function like this:
38+
39+
```js
40+
// bug.svg.js
41+
export default tag => tag`
42+
<svg focusable="false" ...>...</svg>
43+
`;
44+
```
45+
46+
Make sure you have `focusable="false"` in the icon file to prevent bugs in IE/Edge when the icon appears in tab-order.
47+
3548
### Accessibiltiy
3649

3750
You may add an `aria-label` to provide information to visually impaired users:

packages/icon/src/LionIcon.js

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { html, css, LitElement } from '@lion/core';
1+
import { html, nothing, TemplateResult, css, render, LitElement } from '@lion/core';
22

33
const isPromise = action => typeof action === 'object' && Promise.resolve(action) === action;
44

@@ -11,7 +11,7 @@ export class LionIcon extends LitElement {
1111
return {
1212
// svg is a property to ensure the setter is called if the property is set before upgrading
1313
svg: {
14-
type: String,
14+
type: Object,
1515
},
1616
role: {
1717
type: String,
@@ -84,17 +84,17 @@ export class LionIcon extends LitElement {
8484
set svg(svg) {
8585
this.__svg = svg;
8686
if (svg === undefined) {
87-
this._renderSvg('');
87+
this._renderSvg(nothing);
8888
} else if (isPromise(svg)) {
89-
this._renderSvg(''); // show nothing before resolved
89+
this._renderSvg(nothing); // show nothing before resolved
9090
svg.then(resolvedSvg => {
9191
// render only if it is still the same and was not replaced after loading started
9292
if (svg === this.__svg) {
93-
this._renderSvg(resolvedSvg);
93+
this._renderSvg(this.constructor.__unwrapSvg(resolvedSvg));
9494
}
9595
});
9696
} else {
97-
this._renderSvg(svg);
97+
this._renderSvg(this.constructor.__unwrapSvg(svg));
9898
}
9999
}
100100

@@ -111,8 +111,22 @@ export class LionIcon extends LitElement {
111111
}
112112
}
113113

114-
_renderSvg(svgOrModule) {
115-
const svg = svgOrModule && svgOrModule.default ? svgOrModule.default : svgOrModule;
116-
this.innerHTML = svg;
114+
_renderSvg(svgObject) {
115+
this.constructor.__validateSvg(svgObject);
116+
render(svgObject, this);
117+
}
118+
119+
static __unwrapSvg(wrappedSvgObject) {
120+
const svgObject =
121+
wrappedSvgObject && wrappedSvgObject.default ? wrappedSvgObject.default : wrappedSvgObject;
122+
return typeof svgObject === 'function' ? svgObject(html) : svgObject;
123+
}
124+
125+
static __validateSvg(svg) {
126+
if (!(svg === nothing || svg instanceof TemplateResult)) {
127+
throw new Error(
128+
'icon accepts only lit-html templates or functions like "tag => tag`<svg>...</svg>`"',
129+
);
130+
}
117131
}
118132
}

packages/icon/stories/icons/bugs/bug01.svg.js

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/icon/stories/icons/bugs/bug02.svg.js

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)