import { Component } from "."; export function stringToElement(template: string): Element { const parser = new DOMParser(); const doc = parser.parseFromString(template, "text/html"); return doc.body.firstChild as Element; } export const isText = (node: Node): node is Text => { return node.nodeType === Node.TEXT_NODE; }; export const isTemplate = (node: Node): node is HTMLTemplateElement => { return node.nodeName === "TEMPLATE"; }; export const isElement = (node: Node): node is Element => { return node.nodeType === Node.ELEMENT_NODE; }; export function isObject(value: any): value is object { return value !== null && typeof value === "object" && !isArray(value); } export function isArray(value: any): value is any[] { return Array.isArray(value); } export function checkAndRemoveAttribute(el: Element, attrName: string): string | null { // Attempt to get the attribute value const attributeValue = el.getAttribute(attrName); // If attribute exists, remove it from the element if (attributeValue !== null) { el.removeAttribute(attrName); } // Return the value of the attribute or null if not present return attributeValue; } export interface Slot { node: Element; name: string; } export interface Template { targetSlotName: string; node: HTMLTemplateElement; } export function findSlotNodes(element: Element): Slot[] { const slots: Slot[] = []; const findSlots = (node: Element) => { Array.from(node.childNodes).forEach((node) => { if (isElement(node)) { if (node.nodeName === "SLOT") { slots.push({ node, name: node.getAttribute("name") || "default" }); } if (node.hasChildNodes()) { findSlots(node); } } }); }; findSlots(element); return slots; } export function findTemplateNodes(element: Element) { const templates: Template[] = []; const findTemplates = (element: Element) => { let defaultContentNodes: Node[] = []; Array.from(element.childNodes).forEach((node) => { if (isElement(node) || isText(node)) { if (isElement(node) && node.nodeName === "TEMPLATE" && isTemplate(node)) { templates.push({ targetSlotName: node.getAttribute("slot") || "", node }); } else { // Capture non-template top-level nodes and text nodes for default slot defaultContentNodes.push(node); } } }); if (defaultContentNodes.length > 0) { // Create a template element with a default slot const defaultTemplate = document.createElement("template"); defaultTemplate.setAttribute("slot", "default"); defaultContentNodes.forEach((node) => { defaultTemplate.content.appendChild(node); }); templates.push({ targetSlotName: "default", node: defaultTemplate }); } }; findTemplates(element); return templates; } export const nextTick = async (f?: Function) => { await new Promise((r) => setTimeout((_) => requestAnimationFrame((_) => { f && f(); r(); }), ), ); }; export function html(strings: TemplateStringsArray, ...values: any[]): string { // List of valid self-closing tags in HTML const selfClosingTags = ["area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr"]; // Join the strings and values into a single template let result = strings.reduce((acc, str, i) => acc + str + (values[i] || ""), ""); // Match non-HTML valid self-closing tags result = result.replace(/<([a-zA-Z][^\s/>]*)\s*([^>]*?)\/>/g, (match, tagName, attributes) => { // If the tag is a valid self-closing tag, return it as is if (selfClosingTags.includes(tagName.toLowerCase())) { return match; } // Return the tag as an open/close tag preserving attributes return `<${tagName} ${attributes}>`; }); return result; } export function toDisplayString(value: unknown) { return value == null ? "" : isObject(value) ? JSON.stringify(value, null, 2) : String(value); } export function insertAfter(newNode: Node, existingNode: Node) { if (existingNode.nextSibling) { existingNode.parentNode.insertBefore(newNode, existingNode.nextSibling); } else { existingNode?.parentNode?.appendChild(newNode); } } export function insertBefore(newNode: Node, existingNode: Node) { existingNode.parentNode?.insertBefore(newNode, existingNode); } export function isPropAttribute(attrName: string) { if (attrName.startsWith(".")) { return true; } if (attrName.startsWith("{") && attrName.endsWith("}")) { return true; } return false; } export function isSpreadProp(attr: string) { return attr.startsWith("..."); } export function isMirrorProp(attr: string) { return attr.startsWith("{") && attr.endsWith("}"); } export function isRegularProp(attr: string) { return attr.startsWith("."); } export function isEventAttribute(attrName: string) { return attrName.startsWith("@"); } export function componentHasPropByName(name: string, component: Component) { return Object.keys(component?.props ?? {}).some((prop) => prop === name); } export function extractAttributeName(attrName: string) { return attrName .replace(/^\.\.\./, "") .replace(/^\./, "") .replace(/^{/, "") .replace(/}$/, "") .replace(/:bind$/, ""); } function dashToCamel(str: string) { return str.toLowerCase().replace(/-([a-z])/g, (g) => g[1].toUpperCase()); } export function extractPropName(attrName: string) { return dashToCamel(extractAttributeName(attrName)); } export function classNames(_: any) { const classes = []; for (let i = 0; i < arguments.length; i++) { const arg = arguments[i]; if (!arg) continue; const argType = typeof arg; if (argType === "string" || argType === "number") { classes.push(arg); } else if (Array.isArray(arg)) { if (arg.length) { const inner = classNames.apply(null, arg); if (inner) { classes.push(inner); } } } else if (argType === "object") { if (arg.toString === Object.prototype.toString) { for (let key in arg) { if (Object.hasOwnProperty.call(arg, key) && arg[key]) { classes.push(key); } } } else { classes.push(arg.toString()); } } } return classes.join(" "); }