mirror of
https://github.com/nvms/soma3.git
synced 2025-12-16 07:20:52 +00:00
235 lines
6.2 KiB
TypeScript
235 lines
6.2 KiB
TypeScript
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<void>((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}></${tagName}>`;
|
|
});
|
|
|
|
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(" ");
|
|
}
|