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>
`;
}
```
132 lines
3 KiB
JavaScript
132 lines
3 KiB
JavaScript
import { html, nothing, TemplateResult, css, render, LitElement } from '@lion/core';
|
|
|
|
const isPromise = action => typeof action === 'object' && Promise.resolve(action) === action;
|
|
|
|
/**
|
|
* Custom element for rendering SVG icons
|
|
* @polymerElement
|
|
*/
|
|
export class LionIcon extends LitElement {
|
|
static get properties() {
|
|
return {
|
|
// svg is a property to ensure the setter is called if the property is set before upgrading
|
|
svg: {
|
|
type: Object,
|
|
},
|
|
role: {
|
|
type: String,
|
|
attribute: 'role',
|
|
reflect: true,
|
|
},
|
|
ariaLabel: {
|
|
type: String,
|
|
attribute: 'aria-label',
|
|
reflect: true,
|
|
},
|
|
};
|
|
}
|
|
|
|
static get styles() {
|
|
return [
|
|
css`
|
|
:host {
|
|
box-sizing: border-box;
|
|
display: inline-block;
|
|
width: 1em;
|
|
height: 1em;
|
|
}
|
|
|
|
:host:first-child {
|
|
margin-left: 0;
|
|
}
|
|
|
|
:host:last-child {
|
|
margin-right: 0;
|
|
}
|
|
|
|
::slotted(svg) {
|
|
display: block;
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
`,
|
|
];
|
|
}
|
|
|
|
constructor() {
|
|
super();
|
|
this.role = 'img';
|
|
}
|
|
|
|
update(changedProperties) {
|
|
super.update(changedProperties);
|
|
if (changedProperties.has('ariaLabel')) {
|
|
this._onLabelChanged(changedProperties);
|
|
}
|
|
}
|
|
|
|
render() {
|
|
return html`
|
|
<slot></slot>
|
|
`;
|
|
}
|
|
|
|
connectedCallback() {
|
|
// ensures that aria-hidden is set if there is no aria-label attribute
|
|
this._onLabelChanged();
|
|
super.connectedCallback();
|
|
}
|
|
|
|
/**
|
|
* On IE11, svgs without focusable false appear in the tab order
|
|
* so make sure to have <svg focusable="false"> in svg files
|
|
*/
|
|
set svg(svg) {
|
|
this.__svg = svg;
|
|
if (svg === undefined) {
|
|
this._renderSvg(nothing);
|
|
} else if (isPromise(svg)) {
|
|
this._renderSvg(nothing); // show nothing before resolved
|
|
svg.then(resolvedSvg => {
|
|
// render only if it is still the same and was not replaced after loading started
|
|
if (svg === this.__svg) {
|
|
this._renderSvg(this.constructor.__unwrapSvg(resolvedSvg));
|
|
}
|
|
});
|
|
} else {
|
|
this._renderSvg(this.constructor.__unwrapSvg(svg));
|
|
}
|
|
}
|
|
|
|
get svg() {
|
|
return this.__svg;
|
|
}
|
|
|
|
_onLabelChanged() {
|
|
if (this.ariaLabel) {
|
|
this.setAttribute('aria-hidden', 'false');
|
|
} else {
|
|
this.setAttribute('aria-hidden', 'true');
|
|
this.removeAttribute('aria-label');
|
|
}
|
|
}
|
|
|
|
_renderSvg(svgObject) {
|
|
this.constructor.__validateSvg(svgObject);
|
|
render(svgObject, this);
|
|
}
|
|
|
|
static __unwrapSvg(wrappedSvgObject) {
|
|
const svgObject =
|
|
wrappedSvgObject && wrappedSvgObject.default ? wrappedSvgObject.default : wrappedSvgObject;
|
|
return typeof svgObject === 'function' ? svgObject(html) : svgObject;
|
|
}
|
|
|
|
static __validateSvg(svg) {
|
|
if (!(svg === nothing || svg instanceof TemplateResult)) {
|
|
throw new Error(
|
|
'icon accepts only lit-html templates or functions like "tag => tag`<svg>...</svg>`"',
|
|
);
|
|
}
|
|
}
|
|
}
|