UNPKG

@identitate-md/logos

Version:

Logo-uri oficiale ale instituțiilor publice din Republica Moldova — Official logos of public institutions from the Republic of Moldova

202 lines (173 loc) 4.98 kB
/** * IdentitateMD Web Component Loader * * A custom HTML element for loading Romanian institution logos * Framework-agnostic, works with React, Vue, Angular, vanilla HTML, etc. * * Usage: * <identity-icon src="https://cdn.jsdelivr.net/npm/@identitate-md/logos@latest/logos/anaf/anaf.svg"></identity-icon> * * Features: * - Automatic SVG fetching and injection * - Built-in caching (loads each SVG only once) * - CSS styling support (fill: currentColor) * - Error handling with fallback display * - Observable attributes (dynamic updates) * * @version 1.0.0 * @license MIT */ // Global cache to prevent duplicate downloads const svgCache = new Map(); class IdentityIcon extends HTMLElement { constructor() { super(); this._isLoading = false; } /** * Called when element is added to the DOM */ connectedCallback() { this.render(); } /** * Define which attributes to observe for changes */ static get observedAttributes() { return ["src", "size"]; } /** * Called when an observed attribute changes */ attributeChangedCallback(name, oldValue, newValue) { if (oldValue !== newValue && !this._isLoading) { this.render(); } } /** * Main rendering logic */ async render() { let url = this.getAttribute("src"); if (!url) { this.showError("Missing src attribute"); return; } // Resolve relative paths to absolute URLs (for local dev / same-origin usage) if ( !url.startsWith("http://") && !url.startsWith("https://") && !url.startsWith("data:") ) { try { url = new URL(url, document.baseURI).href; } catch (e) { this.showError("Invalid URL: " + url); return; } } // Check cache first if (svgCache.has(url)) { this.injectSvg(svgCache.get(url)); return; } // Show loading state this.showLoading(); this._isLoading = true; try { // Fetch SVG content const response = await fetch(url); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const contentType = response.headers.get("content-type") || ""; const isSvg = contentType.includes("svg") || contentType.includes("xml") || url.endsWith(".svg"); if (!isSvg) { throw new Error("Response is not an SVG file"); } const svgContent = await response.text(); // Basic security: remove script tags const cleanedSvg = this.sanitizeSvg(svgContent); // Save to cache svgCache.set(url, cleanedSvg); // Inject into DOM this.injectSvg(cleanedSvg); } catch (error) { console.error("[IdentitateMD] Failed to load logo:", url, error); this.showError(error.message); } finally { this._isLoading = false; } } /** * Remove potentially dangerous content from SVG */ sanitizeSvg(svgContent) { // Remove script tags and event handlers return svgContent .replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "") .replace(/on\w+="[^"]*"/gi, "") .replace(/on\w+='[^']*'/gi, ""); } /** * Inject SVG content into the element */ injectSvg(svgContent) { this.innerHTML = svgContent; // Configure SVG element for CSS styling const svg = this.querySelector("svg"); if (svg) { // Make SVG responsive svg.setAttribute("width", "100%"); svg.setAttribute("height", "100%"); // Enable CSS color control svg.style.fill = "currentColor"; // Preserve aspect ratio if ( !svg.hasAttribute("viewBox") && svg.hasAttribute("width") && svg.hasAttribute("height") ) { const width = svg.getAttribute("width"); const height = svg.getAttribute("height"); svg.setAttribute("viewBox", `0 0 ${width} ${height}`); } // Apply size attribute if present const size = this.getAttribute("size"); if (size) { this.style.width = size; this.style.height = size; } // Ensure display is set if (!this.style.display || this.style.display === "") { this.style.display = "inline-block"; } } } /** * Show loading state */ showLoading() { this.innerHTML = `<span style="opacity: 0.5; font-size: 0.75em;">⏳</span>`; this.setAttribute("aria-busy", "true"); } /** * Show error state */ showError(message) { this.innerHTML = `<span style="opacity: 0.5; font-size: 0.75em;" title="${message}">⚠️</span>`; this.setAttribute("aria-invalid", "true"); this.removeAttribute("aria-busy"); } } // Register the custom element if (!customElements.get("identity-icon")) { customElements.define("identity-icon", IdentityIcon); } // Export for module environments if (typeof module !== "undefined" && module.exports) { module.exports = IdentityIcon; }