import { LitElement, html, css, PropertyValues } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
import type { TemplateResult } from 'lit';
import { classMap } from 'lit/directives/class-map.js';
import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
import { unsafeHTML } from 'lit/directives/unsafe-html.js';
import * as Icons from './icons/svg';
import { aliasMap } from './aliasmap';
interface IconPaths {
[key: string]: string;
}
interface ButtonClassMap {
[key:string]: boolean;
}
const iconPaths: IconPaths = Icons as IconPaths;
const SMALL_ICON_SIZE = 16;
const LARGE_ICON_SIZE = 24;
/**
* Nile icon component.
*
* @tag nile-icon
*
*/
@customElement('nile-icon')
export class NileIcon extends LitElement {
/**
* The name of the icon set
* @attr icon set
* @type {IconName | undefined}
*/
@property({ type: String, reflect: true })
public set?: string = 'local';
/**
* The name of the icon
* @attr name
* @type {IconName | undefined}
*/
@property({ type: String, reflect: true })
public name?: string;
/**
* A description of what the icon represents
* @attr description
*/
@property({ type: String, reflect: true })
public description = '';
@property({ type: String, reflect: true })
public method = 'fill';
/**
* A path to a custom SVG file to display as the icon
* @attr customSvgPath
*/
@property({ type: String })
public customSvgPath?: string;
/**
* A size of what the icon represents
* @attr size
*/
@property({ type: String, reflect: true })
public size = SMALL_ICON_SIZE;
@state()
private _svg = '';
@property({ type: String }) override title = 'agents';
/**
* Color
*/
@property({ reflect: true }) color: any ;
@property({ type: Boolean })
public push = false;
@property({ type: Boolean })
public noFill = false;
/**
* Retain Viewbox
*/
@property({ reflect: true }) frame: any;
static override styles = css`
:host {
display: inline-flex;
align-items: center;
justify-content: center;
contain: content;
-webkit-font-smoothing: var(--nile-webkit-font-smoothing, var(--ng-webkit-font-smoothing));
-moz-osx-font-smoothing: var(--nile-moz-osx-font-smoothing, var(--ng-moz-osx-font-smoothing));
text-rendering: var(--nile-text-rendering, var(--ng-text-rendering));
}
.nds-icon {
display: flex;
align-items: center;
justify-content: center;
}
.nds-icon svg {
height: var(--nile-svg-height);
width: var(--nile-svg-width);
stroke: var(--nile-svg-stroke);
}
.nds-icon svg path {
fill: var(--nile-svg-fill);
}
.nds-icon.no-fill svg path {
fill: revert-layer;
}
.stroke svg path {
fill: none !important;
stroke: var(--nile-svg-fill) !important;
stroke-width: var(--nile-svg-stroke-width, var(--ng-svg-stroke-width));
}
.nds-icon--push {
margin-right: var(--nile-spacing-lg);
}
`;
private _getIconSize(): number {
return this.name?.endsWith('-small') ? SMALL_ICON_SIZE : LARGE_ICON_SIZE;
}
protected override async updated(
changedProperties: PropertyValues
): Promise {
if (changedProperties.has('size')) {
const resolved = this.resolveCssVarExpression(`${this.size}`);
if (resolved) {
const numeric = parseInt(resolved.replace(/px$/, '').trim(), 10);
if (!isNaN(numeric)) {
this.size = numeric;
}
}
this.style.setProperty('--nile-svg-height', `${this.size}px`);
this.style.setProperty('--nile-svg-width', `${this.size}px`);
}
if (changedProperties.has('color')) {
this.style.setProperty('--nile-svg-fill', `${this.color}`);
}
if (changedProperties.has('method')) {
const resolved = this.resolveCssVarExpression(this.method);
if (resolved) this.method = resolved.trim();
}
if (changedProperties.has('name') || changedProperties.has('customSvgPath')) {
const raw = this.name?.replace(/-/g, '') ?? '';
const iconName = aliasMap[raw] || raw;
if (iconName && iconPaths[iconName]) {
this._svg = atob(iconPaths[iconName]);
} else if (this.customSvgPath) {
this._svg = await this.fetchSvg(this.customSvgPath);
} else {
// throw new Error(`No icon named "${this.name}"`);
const a = this.resolveCssVarExpression(this.name!)
this.name = a!;
}
}
this.addAttributesToSvg();
}
resolveCssVarExpression(expression: string): string | null {
if(!expression) {
return null;
}
const regex = /var\((--[^,\s)]+)(?:,\s*(.+))?\)/;
let match = expression?.trim();
let attempts = 0;
while (match.startsWith("var(") && attempts++ < 10) {
const parts = match.match(regex);
if (!parts) break;
const [, varName, fallback] = parts;
const value = getComputedStyle(document.documentElement).getPropertyValue(varName).trim();
if (value) {
return value;
} else if (fallback) {
match = fallback.trim();
} else {
return null;
}
}
return match || null;
}
private addAttributesToSvg(){
const svg = this.renderRoot.querySelector('#svg')
const attrs = svg?.attributes
if(!attrs?.getNamedItem('xmlns')){
svg?.setAttribute('xmlns',"http://www.w3.org/2000/svg");
}
if(!attrs?.getNamedItem('version')){
svg?.setAttribute("version","1.1")
}
if(this.frame){
svg?.setAttribute('viewBox',`0 0 ${this.frame} ${this.frame}`)
}
else if(!attrs?.getNamedItem('viewBox')){
const viewboxLogic = this.frame
? `0 0 ${this.frame} ${this.frame}`
: '0 0 16 16';
svg?.setAttribute('viewBox',viewboxLogic)
}
if(!attrs?.getNamedItem('height')){
svg?.setAttribute('height',String(this.size));
svg?.setAttribute('width',String(this.size));
}
svg?.setAttribute('aria-hidden',`${this.description === ''}`);
svg?.setAttribute('aria-label',this.description || '');
const additionalClasses = Object.keys(this.buttonClassMap).filter((key:string)=> this.buttonClassMap[key])
svg?.classList.add('nds-icon',...additionalClasses)
}
private async fetchSvg(path: string): Promise {
const response = await fetch(path);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.text();
}
private removeHyphens(iconName: string) {
return iconName.replace(/-/g, '');
}
private getSvgTemplate(){
const svgTagRegexPattern =/