mancha
Version:
Javscript HTML rendering engine
249 lines • 8.01 kB
JavaScript
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