shoelace/src/components/icon/icon.ts

133 wiersze
3.6 KiB
TypeScript
Czysty Zwykły widok Historia

2021-07-10 00:45:44 +00:00
import { LitElement, html } from 'lit';
2021-05-27 21:00:43 +00:00
import { customElement, property, state } from 'lit/decorators.js';
2021-11-16 14:19:49 +00:00
import { ifDefined } from 'lit/directives/if-defined.js';
2021-09-29 12:40:26 +00:00
import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
import styles from './icon.styles';
2021-02-26 14:09:13 +00:00
import { getIconLibrary, watchIcon, unwatchIcon } from './library';
2020-07-15 21:30:37 +00:00
import { requestIcon } from './request';
import { emit } from '~/internal/event';
import { watch } from '~/internal/watch';
2020-07-15 21:30:37 +00:00
const parser = new DOMParser();
/**
2020-07-17 10:09:10 +00:00
* @since 2.0
2020-07-15 21:30:37 +00:00
* @status stable
*
2021-06-25 20:25:46 +00:00
* @event sl-load - Emitted when the icon has loaded.
2021-07-06 21:56:19 +00:00
* @event {{ status: number }} sl-error - Emitted when the icon fails to load due to an error.
*
2021-06-25 20:25:46 +00:00
* @csspart base - The component's base wrapper.
2020-07-15 21:30:37 +00:00
*/
2021-03-18 13:04:23 +00:00
@customElement('sl-icon')
2021-03-09 00:14:32 +00:00
export default class SlIcon extends LitElement {
2021-07-10 00:45:44 +00:00
static styles = styles;
2020-07-15 21:30:37 +00:00
@state() private svg = '';
2020-07-15 21:30:37 +00:00
/** The name of the icon to draw. */
@property() name?: string;
2020-07-15 21:30:37 +00:00
/**
* An external URL of an SVG file.
*
* WARNING: Be sure you trust the content you are including as it will be executed as code and can result in XSS attacks.
*/
@property() src?: string;
2020-07-15 21:30:37 +00:00
2021-11-16 14:19:49 +00:00
/** An alternate description to use for accessibility. If omitted, the icon will be ignored by assistive devices. */
@property() label = '';
2020-07-15 21:30:37 +00:00
2020-10-07 13:34:05 +00:00
/** The name of a registered custom icon library. */
2021-07-01 00:04:46 +00:00
@property() library = 'default';
2021-03-06 17:01:39 +00:00
connectedCallback() {
super.connectedCallback();
2021-02-26 14:09:13 +00:00
watchIcon(this);
}
2021-03-06 17:01:39 +00:00
firstUpdated() {
void this.setIcon();
}
2021-03-06 17:01:39 +00:00
disconnectedCallback() {
super.disconnectedCallback();
2021-02-26 14:09:13 +00:00
unwatchIcon(this);
2020-07-15 21:30:37 +00:00
}
private getUrl() {
2021-04-12 14:40:36 +00:00
const library = getIconLibrary(this.library);
if (typeof this.name !== 'undefined' && typeof library !== 'undefined') {
2021-04-12 14:40:36 +00:00
return library.resolver(this.name);
}
return this.src;
2021-04-12 14:40:36 +00:00
}
2021-02-26 14:09:13 +00:00
/** @internal Fetches the icon and redraws it. Used to handle library registrations. */
redraw() {
void this.setIcon();
2021-02-26 14:09:13 +00:00
}
2021-03-06 19:39:48 +00:00
@watch('name')
@watch('src')
@watch('library')
async setIcon() {
2021-02-26 14:09:13 +00:00
const library = getIconLibrary(this.library);
2021-04-12 14:40:36 +00:00
const url = this.getUrl();
if (typeof url !== 'undefined' && url.length > 0) {
try {
2021-02-26 14:09:13 +00:00
const file = await requestIcon(url)!;
2021-04-12 14:40:36 +00:00
if (url !== this.getUrl()) {
2021-04-12 14:44:29 +00:00
// If the url has changed while fetching the icon, ignore this request
2021-04-12 14:40:36 +00:00
return;
} else if (file.ok) {
const doc = parser.parseFromString(file.svg, 'text/html');
2021-02-26 14:09:13 +00:00
const svgEl = doc.body.querySelector('svg');
if (svgEl !== null) {
library?.mutator?.(svgEl);
2020-10-07 13:34:05 +00:00
2021-03-06 17:01:39 +00:00
this.svg = svgEl.outerHTML;
emit(this, 'sl-load');
} else {
this.svg = '';
emit(this, 'sl-error', { detail: { status: file.status } });
}
} else {
2021-02-26 14:09:13 +00:00
this.svg = '';
emit(this, 'sl-error', { detail: { status: file.status } });
}
} catch {
emit(this, 'sl-error', { detail: { status: -1 } });
}
} else if (this.svg.length > 0) {
// If we can't resolve a URL and an icon was previously set, remove it
2021-02-26 14:09:13 +00:00
this.svg = '';
}
}
2021-02-26 14:09:13 +00:00
handleChange() {
void this.setIcon();
2021-02-26 14:09:13 +00:00
}
2020-07-15 21:30:37 +00:00
render() {
2021-11-16 14:19:49 +00:00
const hasLabel = typeof this.label === 'string' && this.label.length > 0;
return html` <div
part="base"
class="icon"
role=${ifDefined(hasLabel ? 'img' : undefined)}
aria-label=${ifDefined(hasLabel ? this.label : undefined)}
aria-hidden=${ifDefined(hasLabel ? undefined : 'true')}
>
${unsafeSVG(this.svg)}
</div>`;
2020-07-15 21:30:37 +00:00
}
}
2021-03-12 14:09:08 +00:00
declare global {
interface HTMLElementTagNameMap {
'sl-icon': SlIcon;
}
}