@esri/calcite-components
Version:
Web Components for Esri's Calcite Design System.
155 lines (154 loc) • 5.8 kB
JavaScript
/*! All material copyright ESRI, All Rights Reserved, unless otherwise specified.
See https://github.com/Esri/calcite-design-system/blob/dev/LICENSE.md for details.
v3.2.1 */
import { g as getAssetPath, c as customElement } from "../../chunks/runtime.js";
import { isServer, nothing, svg, html } from "lit";
import { LitElement, safeClassMap } from "@arcgis/lumina";
import { g as getElementDir, t as toAriaBoolean } from "../../chunks/dom.js";
import { c as createObserver } from "../../chunks/observers.js";
import { l as logger } from "../../chunks/logger.js";
import { css } from "@lit/reactive-element/css-tag.js";
const CSS = {
flipRtl: "flip-rtl"
};
const iconCache = {};
const requestCache = {};
const scaleToPx = {
s: 16,
m: 24,
l: 32
};
function generateIconId({ icon, scale }) {
const size = scaleToPx[scale];
const name = normalizeIconName(icon);
const filled = name.charAt(name.length - 1) === "F";
const iconName = filled ? name.substring(0, name.length - 1) : name;
return `${iconName}${size}${filled ? "F" : ""}`;
}
async function fetchIcon(props) {
const cachedIconKey = generateIconId(props);
const cachedIconData = getCachedIconDataByKey(cachedIconKey);
if (cachedIconData) {
return cachedIconData;
}
if (!requestCache[cachedIconKey]) {
requestCache[cachedIconKey] = fetch(getAssetPath(`./assets/icon/${cachedIconKey}.json`)).then((resp) => resp.json()).catch(() => {
logger.error(`${props.icon} (${props.scale}) icon failed to load`);
return "";
});
}
const path = await requestCache[cachedIconKey];
iconCache[cachedIconKey] = path;
return path;
}
function getCachedIconData(props) {
return getCachedIconDataByKey(generateIconId(props));
}
function getCachedIconDataByKey(id) {
return iconCache[id];
}
function normalizeIconName(name) {
const numberLeadingName = !isNaN(Number(name.charAt(0)));
const parts = name.split("-");
const kebabCased = parts.length > 0;
if (kebabCased) {
const firstNonDigitInPartPattern = /[a-z]/i;
name = parts.map((part, partIndex) => {
return part.replace(firstNonDigitInPartPattern, function replacer(match, offset) {
const isFirstCharInName = partIndex === 0 && offset === 0;
if (isFirstCharInName) {
return match;
}
return match.toUpperCase();
});
}).join("");
}
return numberLeadingName ? `i${name}` : name;
}
const styles = css`:host{display:inline-flex;color:var(--calcite-icon-color, var(--calcite-ui-icon-color, currentColor))}:host([scale=s]){inline-size:16px;block-size:16px;min-inline-size:16px;min-block-size:16px}:host([scale=m]){inline-size:24px;block-size:24px;min-inline-size:24px;min-block-size:24px}:host([scale=l]){inline-size:32px;block-size:32px;min-inline-size:32px;min-block-size:32px}.flip-rtl{transform:scaleX(-1)}.svg{display:block}:host([hidden]){display:none}[hidden]{display:none}`;
class Icon extends LitElement {
constructor() {
super(...arguments);
this.visible = false;
this.flipRtl = false;
this.icon = null;
this.preload = false;
this.scale = "m";
}
static {
this.properties = { pathData: [16, {}, { state: true }], visible: [16, {}, { state: true }], flipRtl: [7, {}, { reflect: true, type: Boolean }], icon: [3, {}, { reflect: true }], preload: [7, {}, { reflect: true, type: Boolean }], scale: [3, {}, { reflect: true }], textLabel: 1 };
}
static {
this.styles = styles;
}
connectedCallback() {
super.connectedCallback();
if (this.preload) {
this.visible = true;
this.loadIconPathData();
return;
}
if (!this.visible) {
this.waitUntilVisible(() => {
this.visible = true;
this.loadIconPathData();
});
}
}
willUpdate(changes) {
if (changes.has("icon") && (this.hasUpdated || this.icon !== null) || changes.has("scale") && (this.hasUpdated || this.scale !== "m")) {
this.loadIconPathData();
}
}
disconnectedCallback() {
super.disconnectedCallback();
this.intersectionObserver?.disconnect();
this.intersectionObserver = null;
}
async loadIconPathData() {
const { icon, scale, visible } = this;
if (isServer || !icon || !visible) {
return;
}
const fetchIconProps = { icon, scale };
const pathData = getCachedIconData(fetchIconProps) || await fetchIcon(fetchIconProps);
if (icon !== this.icon) {
return;
}
this.pathData = pathData;
}
waitUntilVisible(callback) {
this.intersectionObserver = createObserver("intersection", (entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
this.intersectionObserver.disconnect();
this.intersectionObserver = null;
callback();
}
});
}, { rootMargin: "50px" });
if (!this.intersectionObserver) {
callback();
return;
}
this.intersectionObserver.observe(this.el);
}
render() {
const { el, flipRtl, pathData, scale, textLabel } = this;
const dir = getElementDir(el);
const size = scaleToPx[scale];
const semantic = !!textLabel;
const paths = [].concat(pathData || "");
this.el.ariaHidden = toAriaBoolean(!semantic);
this.el.ariaLabel = semantic ? textLabel : null;
this.el.role = semantic ? "img" : null;
return html`<svg aria-hidden=true class=${safeClassMap({
[CSS.flipRtl]: dir === "rtl" && flipRtl,
svg: true
})} fill=currentColor height=100% viewBox=${`0 0 ${size} ${size}`} width=100% xmlns=http://www.w3.org/2000/svg>${paths.map((path) => typeof path === "string" ? svg`<path d=${path ?? nothing} />` : svg`<path d=${path.d ?? nothing} opacity=${("opacity" in path ? path.opacity : 1) ?? nothing} />`)}</svg>`;
}
}
customElement("calcite-icon", Icon);
export {
Icon
};