UNPKG

mancha

Version:

Javscript HTML rendering engine

249 lines 8.01 kB
import { safeAttrPrefix } from "safevalues"; import { safeElement } from "safevalues/dom"; const SAFE_ATTRS = [safeAttrPrefix `:`, safeAttrPrefix `style`, safeAttrPrefix `class`]; /** * Traverses the DOM tree starting from the given root node and yields each child node. * Nodes in the `skip` set will be skipped during traversal. * * @param root - The root node to start the traversal from. * @param skip - A set of nodes to skip during traversal. * @returns A generator that yields each child node in the DOM tree. */ export function* traverse(root, skip = new Set()) { const explored = new Set(); const frontier = Array.from(root.childNodes).filter((node) => !skip.has(node)); // Also yield the root node. yield root; while (frontier.length) { const node = frontier.shift(); if (!explored.has(node)) { explored.add(node); yield node; } if (node.childNodes) { Array.from(node.childNodes) .filter((node) => !skip.has(node)) .forEach((node) => { frontier.push(node); }); } } } export function hasProperty(obj, prop) { return typeof obj?.[prop] !== "undefined"; } export function hasFunction(obj, func) { return typeof obj?.[func] === "function"; } /** * Converts from an attribute name to camelCase, e.g. `foo-bar` becomes `fooBar`. * @param name attribute name * @returns camel-cased attribute name */ export function attributeNameToCamelCase(name) { return name.replace(/-./g, (c) => c[1].toUpperCase()); } export function getAttribute(elem, name) { if (elem.attribs) return elem.attribs[name] ?? null; else return elem.getAttribute?.(name) ?? null; } export function hasAttribute(elem, name) { if (elem.attribs) return name in elem.attribs; else return elem.hasAttribute?.(name) ?? false; } export function getAttributeOrDataset(elem, name, attributePrefix = "") { return (getAttribute(elem, attributePrefix + name) || getAttribute(elem, `data-${name}`) || (elem.dataset?.[attributeNameToCamelCase(name)] ?? null)); } export function hasAttributeOrDataset(elem, name, attributePrefix = "") { return (hasAttribute(elem, attributePrefix + name) || hasAttribute(elem, `data-${name}`) || hasProperty(elem.dataset, attributeNameToCamelCase(name))); } export function setAttribute(elem, name, value) { if (elem.attribs) elem.attribs[name] = value; // tsec-disable-next-line else elem.setAttribute?.(name, value); } export function safeSetAttribute(elem, name, value) { if (elem.attribs) elem.attribs[name] = value; else safeElement.setPrefixedAttribute(SAFE_ATTRS, elem, name, value); } export function setProperty(elem, name, value) { switch (name) { // Directly set some safe, known properties. case "disabled": elem.disabled = value; return; case "selected": elem.selected = value; return; case "checked": elem.checked = value; return; // Fall back to setting the property directly (unsafe). default: elem[name] = value; } } export function removeAttribute(elem, name) { if (elem.attribs) delete elem.attribs[name]; else elem.removeAttribute?.(name); } export function setAttributeOrDataset(elem, name, value, prefix = "") { // Update whichever form of the attribute exists, preferring prefix form. if (hasAttribute(elem, `${prefix}${name}`)) { setAttribute(elem, `${prefix}${name}`, value); } else if (hasAttribute(elem, `data-${name}`)) { setAttribute(elem, `data-${name}`, value); } else { // Default to prefix form if neither exists. setAttribute(elem, `${prefix}${name}`, value); } } export function removeAttributeOrDataset(elem, name, prefix = "") { removeAttribute(elem, `${prefix}${name}`); removeAttribute(elem, `data-${name}`); } export function cloneAttribute(elemFrom, elemDest, name) { if (elemFrom.attribs && elemDest.attribs) { elemDest.attribs[name] = elemFrom.attribs[name]; } else if (name.startsWith("data-")) { const datasetKey = attributeNameToCamelCase(name.slice(5)); if (elemDest.dataset && elemFrom.dataset) { elemDest.dataset[datasetKey] = elemFrom.dataset[datasetKey]; } } else { const attr = elemFrom?.getAttribute?.(name); safeSetAttribute(elemDest, name, attr || ""); } } export function firstElementChild(elem) { if (hasProperty(elem, "firstElementChild")) { return elem.firstElementChild; } else { const children = Array.from(elem.children); return children.find((child) => child.nodeType === 1); } } export function replaceWith(original, ...replacement) { if (hasFunction(original, "replaceWith")) { original.replaceWith(...replacement); return; } else { const elem = original; const parent = elem.parentNode; if (!parent) return; // Should not happen if replacing const index = Array.from(parent.childNodes).indexOf(elem); elem.parentNode = null; replacement.forEach((elem) => { elem.parentNode = parent; }); parent.childNodes = [] .concat(Array.from(parent.childNodes).slice(0, index)) .concat(replacement) .concat(Array.from(parent.childNodes).slice(index + 1)); } } export function replaceChildren(parent, ...nodes) { if (hasFunction(parent, "replaceChildren")) { parent.replaceChildren(...nodes); } else { parent.childNodes = nodes; nodes.forEach((node) => { node.parentNode = parent; }); } } export function appendChild(parent, node) { if (hasFunction(node, "appendChild")) { return parent.appendChild(node); } else { parent.childNodes.push(node); node.parentNode = parent; return node; } } export function removeChild(parent, node) { if (hasFunction(node, "removeChild")) { return parent.removeChild(node); } else { replaceChildren(parent, ...Array.from(parent.childNodes).filter((child) => child !== node)); return node; } } export function insertBefore(parent, node, reference) { if (!reference) { return appendChild(parent, node); } else if (hasFunction(parent, "insertBefore")) { return parent.insertBefore(node, reference); } else { replaceWith(reference, node, reference); return node; } } export function ellipsize(str, maxLength = 0) { if (!str) return ""; else if (str.length <= maxLength) return str; else return `${str.slice(0, maxLength - 1)}…`; } export function nodeToString(node, maxLength = 0) { if (globalThis.DocumentFragment && node instanceof DocumentFragment) { return Array.from(node.childNodes) .map((node) => nodeToString(node, maxLength)) .join(""); } return ellipsize(node.outerHTML || node.nodeValue || String(node), maxLength); } /** * Returns the directory name from a given file path. * @param fpath - The file path. * @returns The directory name. */ export function dirname(fpath) { if (!fpath.includes("/")) { return ""; } else { return fpath.split("/").slice(0, -1).join("/"); } } /** * Checks if a given file path is a relative path. * * @param fpath - The file path to check. * @returns A boolean indicating whether the file path is relative or not. */ export function isRelativePath(fpath) { return (!fpath.includes("://") && !fpath.startsWith("/") && !fpath.startsWith("#") && !fpath.startsWith("data:")); } //# sourceMappingURL=dome.js.map