UNPKG

@esri/calcite-components

Version:

Web Components for Esri's Calcite Design System.

155 lines (154 loc) • 5.8 kB
/*! 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 };