@vaadin/icon
Version:
Web component for creating SVG icons
200 lines (179 loc) • 5.21 kB
JavaScript
/**
* @license
* Copyright (c) 2021 - 2026 Vaadin Ltd.
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
*/
import { cloneSvgNode } from './vaadin-icon-svg.js';
const iconsetRegistry = {};
const attachedIcons = new Set();
function getIconId(id, name) {
return (id || '').replace(`${name}:`, '');
}
function getIconsetName(icon) {
if (!icon) {
return;
}
const parts = icon.split(':');
// Use "vaadin" as a fallback
return parts[0] || 'vaadin';
}
function initIconsMap(iconset, name) {
iconset._icons = [...iconset.querySelectorAll('[id]')].reduce((map, svg) => {
const key = getIconId(svg.id, name);
map[key] = svg;
return map;
}, {});
}
/**
* @polymerMixin
*/
export const IconsetMixin = (superClass) =>
class extends superClass {
static get observedAttributes() {
return ['name', 'size'];
}
/**
* Set of the `vaadin-icon` instances in the DOM.
*
* @return {Set<Icon>}
*/
static get attachedIcons() {
return attachedIcons;
}
/**
* Returns an instance of the iconset by its name.
*
* @param {string} name
* @return {Iconset}
*/
static getIconset(name) {
return iconsetRegistry[name];
}
/**
* Returns SVGTemplateResult for the `icon` ID matching `name` of the
* iconset, or `nothing` literal if there is no matching icon found.
*
* @param {string} icon
* @param {?string} name
*/
static getIconSvg(icon, name) {
const iconsetName = name || getIconsetName(icon);
const iconset = this.getIconset(iconsetName);
if (!icon || !iconset) {
// Missing icon, return `nothing` literal.
return {
svg: cloneSvgNode(null),
};
}
const iconId = getIconId(icon, iconsetName);
const iconSvg = iconset._icons[iconId];
return {
preserveAspectRatio: iconSvg ? iconSvg.getAttribute('preserveAspectRatio') : null,
svg: cloneSvgNode(iconSvg),
size: iconset.size,
viewBox: iconSvg ? iconSvg.getAttribute('viewBox') : null,
};
}
/**
* Register an iconset without adding to the DOM.
*
* @param {string} name
* @param {number} size
* @param {?HTMLTemplateElement} template
*/
static register(name, size, template) {
if (!iconsetRegistry[name]) {
const iconset = document.createElement('vaadin-iconset');
iconset.appendChild(template.content.cloneNode(true));
iconsetRegistry[name] = iconset;
initIconsMap(iconset, name);
iconset.size = size;
iconset.name = name;
}
}
/**
* The name of the iconset. Every iconset is required to have its own unique name.
* All the SVG icons in the iconset must have IDs conforming to its name.
*
* See also [`name`](#/elements/vaadin-icon#property-name) property of `vaadin-icon`.
*
* @return {string}
*/
get name() {
return this.__name;
}
/**
* @type {string}
*/
set name(name) {
const oldName = this.__name;
this.__name = name;
this.__nameChanged(name, oldName);
}
/**
* The size of an individual icon. Note that icons must be square.
*
* When using `vaadin-icon`, the size of the iconset will take precedence
* over the size defined by the user to ensure correct appearance.
*
* @return {number}
*/
get size() {
// Use default property value as a fallback here instead of the constructor
// to not override an instance property in the lazy upgrade scenario below.
return this.__size !== undefined ? this.__size : 24;
}
/**
* @type {number}
*/
set size(size) {
this.__size = size;
}
/** @protected */
connectedCallback() {
// A user may set a property on an _instance_ of an element
// before the custom element is lazily imported and upgraded.
// If so, we need to run it through the proper class setter.
['name', 'size'].forEach((prop) => {
// eslint-disable-next-line no-prototype-builtins
if (this.hasOwnProperty(prop)) {
const value = this[prop];
delete this[prop];
this[prop] = value;
}
});
this.style.display = 'none';
}
/** @protected */
attributeChangedCallback(attr, _oldValue, newValue) {
if (attr === 'name') {
this.name = newValue;
} else if (attr === 'size') {
this.size = newValue == null ? null : Number(newValue);
}
}
/**
* Update all the icons instances in the DOM.
*
* @param {string} name
* @private
*/
__updateIcons(name) {
attachedIcons.forEach((element) => {
if (name === getIconsetName(element.icon)) {
element._applyIcon();
}
});
}
/** @private */
__nameChanged(name, oldName) {
if (oldName) {
delete iconsetRegistry[oldName];
}
if (name) {
iconsetRegistry[name] = this;
initIconsMap(this, name);
this.__updateIcons(name);
}
}
};