import { DeesElement, css, cssManager, customElement, html, property, type TemplateResult, } from '@design.estate/dees-element'; import * as lucideIcons from 'lucide'; import { createElement } from 'lucide'; import { demoFunc } from './dees-mobile-icon.demo.js'; // Create a type-safe icon name type export type LucideIconName = keyof typeof lucideIcons; // Cache for rendered icons to improve performance const iconCache = new Map(); const MAX_CACHE_SIZE = 500; function limitCacheSize() { if (iconCache.size > MAX_CACHE_SIZE) { const keysToDelete = Array.from(iconCache.keys()).slice(0, MAX_CACHE_SIZE / 5); keysToDelete.forEach(key => iconCache.delete(key)); } } declare global { interface HTMLElementTagNameMap { 'dees-mobile-icon': DeesMobileIcon; } } @customElement('dees-mobile-icon') export class DeesMobileIcon extends DeesElement { public static demo = demoFunc; @property({ type: String }) accessor icon: string = ''; @property({ type: Number }) accessor size: number = 20; @property({ type: String }) accessor color: string = 'currentColor'; @property({ type: Number }) accessor strokeWidth: number = 2; private lastIcon: string | null = null; private lastSize: number | null = null; private lastColor: string | null = null; private lastStrokeWidth: number | null = null; public static styles = [ cssManager.defaultStyles, css` :host { display: inline-flex; align-items: center; justify-content: center; line-height: 1; vertical-align: middle; } #iconContainer { display: flex; align-items: center; justify-content: center; } #iconContainer svg { display: block; height: 100%; width: 100%; } `, ]; private renderLucideIcon(iconName: string): string { const cacheKey = `${iconName}:${this.size}:${this.color}:${this.strokeWidth}`; if (iconCache.has(cacheKey)) { return iconCache.get(cacheKey) || ''; } try { // Convert kebab-case to PascalCase (e.g., "chevron-down" -> "ChevronDown") const pascalCaseName = iconName .split('-') .map(part => part.charAt(0).toUpperCase() + part.slice(1)) .join(''); if (!(lucideIcons as any)[pascalCaseName]) { console.warn(`Lucide icon '${pascalCaseName}' not found`); return ''; } const svgElement = createElement((lucideIcons as any)[pascalCaseName], { color: this.color, size: this.size, strokeWidth: this.strokeWidth }); if (!svgElement) { console.warn(`createElement returned empty result for ${pascalCaseName}`); return ''; } const result = svgElement.outerHTML; iconCache.set(cacheKey, result); limitCacheSize(); return result; } catch (error) { console.error(`Error rendering Lucide icon ${iconName}:`, error); return ''; } } public render(): TemplateResult { return html`
`; } updated() { // Check if we actually need to update the icon if (this.lastIcon === this.icon && this.lastSize === this.size && this.lastColor === this.color && this.lastStrokeWidth === this.strokeWidth) { return; } this.lastIcon = this.icon || null; this.lastSize = this.size; this.lastColor = this.color; this.lastStrokeWidth = this.strokeWidth; const container = this.shadowRoot?.querySelector('#iconContainer'); if (!container || !this.icon) return; container.innerHTML = ''; try { const pascalCaseName = this.icon .split('-') .map(part => part.charAt(0).toUpperCase() + part.slice(1)) .join(''); if ((lucideIcons as any)[pascalCaseName]) { const svgElement = createElement((lucideIcons as any)[pascalCaseName], { color: this.color, size: this.size, strokeWidth: this.strokeWidth }); if (svgElement) { container.appendChild(svgElement); return; } } // Fall back to string-based approach const iconHtml = this.renderLucideIcon(this.icon); if (iconHtml) { container.innerHTML = iconHtml; } } catch (error) { console.error(`Error updating icon ${this.icon}:`, error); } } async disconnectedCallback() { await super.disconnectedCallback(); this.lastIcon = null; this.lastSize = null; this.lastColor = null; this.lastStrokeWidth = null; } }