import { iconExists, loadIcon } from 'iconify-icon'; export type Node = HTMLElement | SVGElement | Text | undefined; export type NodeChildren = Array | undefined; export type Class = string | Array | Record | undefined; type Listener = | ((this: HTMLElement, ev: HTMLElementEventMap[K]) => any) | { options?: boolean | AddEventListenerOptions; listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any; } | undefined; export interface NodeProperties { attributes?: Record; text?: string; class?: Class; style?: Record | string; listeners?: { [K in keyof HTMLElementEventMap]?: Listener }; } export const cancelPropagation = (e: Event) => e.stopImmediatePropagation(); export function dom(tag: K, properties?: NodeProperties, children?: NodeChildren): HTMLElementTagNameMap[K] { const element = document.createElement(tag); if(children && children.length > 0) for(const c of children) if(c !== undefined) element.appendChild(c); if(properties?.attributes) for(const [k, v] of Object.entries(properties.attributes)) if(typeof v === 'string' || typeof v === 'number') element.setAttribute(k, v.toString(10)); else if(typeof v === 'boolean') element.toggleAttribute(k, v); if(properties?.text) element.textContent = properties.text; if(properties?.listeners) { for(let [k, v] of Object.entries(properties.listeners)) { const key = k as keyof HTMLElementEventMap, value = v as Listener; if(typeof value === 'function') element.addEventListener(key, value.bind(element)); else if(value) element.addEventListener(key, value.listener.bind(element), value.options); } } styling(element, properties ?? {}); return element; } export function div(cls?: Class, children?: NodeChildren): HTMLDivElement { return dom("div", { class: cls }, children); } export function span(cls?: Class, text?: string): HTMLSpanElement { return dom("span", { class: cls, text: text }); } export function svg(tag: K, properties?: NodeProperties, children?: SVGElement[]): SVGElementTagNameMap[K] { const element = document.createElementNS("http://www.w3.org/2000/svg", tag); if(children && children.length > 0) for(const c of children) if(c !== undefined) element.appendChild(c); if(properties?.attributes) for(const [k, v] of Object.entries(properties.attributes)) if(typeof v === 'string') element.setAttribute(k, v); else if(typeof v === 'boolean') element.toggleAttribute(k, v); if(properties?.text) element.textContent = properties.text; styling(element, properties ?? {}); return element; } export function text(data: string): Text { return document.createTextNode(data); } export function styling(element: SVGElement | HTMLElement, properties: { class?: Class; style?: Record | string; }): SVGElement | HTMLElement { if(properties?.class) { element.setAttribute('class', mergeClasses(properties.class)); } if(properties?.style) { if(typeof properties.style === 'string') { element.setAttribute('style', properties.style); } else for(const [k, v] of Object.entries(properties.style)) if(v !== undefined && v !== false) element.attributeStyleMap.set(k, v); } return element; } export interface IconProperties { mode?: string; inline?: boolean; noobserver?: boolean; width?: string|number; height?: string|number; flip?: string; rotate?: number|string; style?: Record | string; class?: Class; } const iconCache: Map = new Map(); export function icon(name: string, properties?: IconProperties): HTMLElement { let element; if(iconCache.has(name)) element = iconCache.get(name)!.cloneNode() as HTMLElement; else { element = document.createElement('iconify-icon'); if(!iconExists(name)) loadIcon(name); element.setAttribute('icon', name); iconCache.set(name, element.cloneNode() as HTMLElement); } properties?.mode && element.setAttribute('mode', properties?.mode.toString()); properties?.inline && element.toggleAttribute('inline', properties?.inline); element.toggleAttribute('noobserver', properties?.noobserver ?? true); properties?.width && element.setAttribute('width', properties?.width.toString()); properties?.height && element.setAttribute('height', properties?.height.toString()); properties?.flip && element.setAttribute('flip', properties?.flip.toString()); properties?.rotate && element.setAttribute('rotate', properties?.rotate.toString()); if(properties?.class) { element.setAttribute('class', mergeClasses(properties.class)); } if(properties?.style) { if(typeof properties.style === 'string') { element.setAttribute('style', properties.style); } else for(const [k, v] of Object.entries(properties.style)) if(v !== undefined) element.attributeStyleMap.set(k, v); } return element; } export function mergeClasses(classes: Class): string { if(typeof classes === 'string') { return classes.trim(); } else if(Array.isArray(classes)) { return classes.map(e => mergeClasses(e)).join(' '); } else if(classes) { return Object.entries(classes).filter(e => e[1]).map(e => e[0].trim()).join(' '); } else { return ''; } }