Files
dees-catalog-mobile/ts_web/elements/00group-ui/dees-mobile-icon/dees-mobile-icon.ts
2025-12-22 10:53:15 +00:00

190 lines
4.7 KiB
TypeScript

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<string, string>();
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`
<style>
#iconContainer {
width: ${this.size}px;
height: ${this.size}px;
}
</style>
<div id="iconContainer"></div>
`;
}
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;
}
}