@wordpress/interactivity
Version:
Package that provides a standard and simple way to handle the frontend interactivity of Gutenberg blocks.
186 lines (185 loc) • 5.84 kB
JavaScript
// packages/interactivity/src/vdom.ts
import { h } from "preact";
import { warn } from "./utils";
var directivePrefix = `data-wp-`;
var namespaces = [];
var currentNamespace = () => namespaces[namespaces.length - 1] ?? null;
var isObject = (item) => Boolean(item && typeof item === "object" && item.constructor === Object);
var invalidCharsRegex = /[^a-z0-9-_]/i;
function parseDirectiveName(directiveName) {
const name = directiveName.substring(8);
if (invalidCharsRegex.test(name)) {
return null;
}
const suffixIndex = name.indexOf("--");
if (suffixIndex === -1) {
return { prefix: name, suffix: null, uniqueId: null };
}
const prefix = name.substring(0, suffixIndex);
const remaining = name.substring(suffixIndex);
if (remaining.startsWith("---") && remaining[3] !== "-") {
return {
prefix,
suffix: null,
uniqueId: remaining.substring(3) || null
};
}
let suffix = remaining.substring(2);
const uniqueIdIndex = suffix.indexOf("---");
if (uniqueIdIndex !== -1 && suffix.substring(uniqueIdIndex)[3] !== "-") {
const uniqueId = suffix.substring(uniqueIdIndex + 3) || null;
suffix = suffix.substring(0, uniqueIdIndex) || null;
return { prefix, suffix, uniqueId };
}
return { prefix, suffix: suffix || null, uniqueId: null };
}
var nsPathRegExp = /^([\w_\/-]+)::(.+)$/;
var hydratedIslands = /* @__PURE__ */ new WeakSet();
function toVdom(root) {
const nodesToRemove = /* @__PURE__ */ new Set();
const nodesToReplace = /* @__PURE__ */ new Set();
const treeWalker = document.createTreeWalker(
root,
205
// TEXT + CDATA_SECTION + COMMENT + PROCESSING_INSTRUCTION + ELEMENT
);
function walk(node) {
const { nodeType } = node;
if (nodeType === 3) {
return node.data;
}
if (nodeType === 4) {
nodesToReplace.add(node);
return node.nodeValue;
}
if (nodeType === 8 || nodeType === 7) {
nodesToRemove.add(node);
return null;
}
const elementNode = node;
const { attributes } = elementNode;
const localName = elementNode.localName;
const props = {};
const children = [];
const directives = [];
let ignore = false;
let island = false;
for (let i = 0; i < attributes.length; i++) {
const attributeName = attributes[i].name;
const attributeValue = attributes[i].value;
if (attributeName[directivePrefix.length] && attributeName.slice(0, directivePrefix.length) === directivePrefix) {
if (attributeName === "data-wp-ignore") {
ignore = true;
} else {
const regexResult = nsPathRegExp.exec(attributeValue);
const namespace = regexResult?.[1] ?? null;
let value = regexResult?.[2] ?? attributeValue;
try {
const parsedValue = JSON.parse(value);
value = isObject(parsedValue) ? parsedValue : value;
} catch {
}
if (attributeName === "data-wp-interactive") {
island = true;
const islandNamespace = (
// eslint-disable-next-line no-nested-ternary
typeof value === "string" ? value : typeof value?.namespace === "string" ? value.namespace : null
);
namespaces.push(islandNamespace);
} else {
directives.push([attributeName, namespace, value]);
}
}
} else if (attributeName === "ref") {
continue;
}
props[attributeName] = attributeValue;
}
if (ignore && !island) {
return [
h(localName, {
...props,
innerHTML: elementNode.innerHTML,
__directives: { ignore: true }
})
];
}
if (island) {
hydratedIslands.add(elementNode);
}
if (directives.length) {
props.__directives = directives.reduce((obj, [name, ns, value]) => {
const directiveParsed = parseDirectiveName(name);
if (directiveParsed === null) {
if (globalThis.SCRIPT_DEBUG) {
warn(`Found malformed directive name: ${name}.`);
}
return obj;
}
const { prefix, suffix, uniqueId } = directiveParsed;
obj[prefix] = obj[prefix] || [];
obj[prefix].push({
namespace: ns ?? currentNamespace(),
value,
suffix,
uniqueId
});
return obj;
}, {});
for (const prefix in props.__directives) {
props.__directives[prefix].sort(
(a, b) => {
const aSuffix = a.suffix ?? "";
const bSuffix = b.suffix ?? "";
if (aSuffix !== bSuffix) {
return aSuffix < bSuffix ? -1 : 1;
}
const aId = a.uniqueId ?? "";
const bId = b.uniqueId ?? "";
return +(aId > bId) - +(aId < bId);
}
);
}
}
if (props.__directives?.["each-child"]) {
props.dangerouslySetInnerHTML = {
__html: elementNode.innerHTML
};
} else if (localName === "template") {
props.content = [
...elementNode.content.childNodes
].map((childNode) => toVdom(childNode));
} else {
let child = treeWalker.firstChild();
if (child) {
while (child) {
const vnode = walk(child);
if (vnode) {
children.push(vnode);
}
child = treeWalker.nextSibling();
}
treeWalker.parentNode();
}
}
if (island) {
namespaces.pop();
}
return h(localName, props, children);
}
const vdom = walk(treeWalker.currentNode);
nodesToRemove.forEach(
(node) => node.remove()
);
nodesToReplace.forEach(
(node) => node.replaceWith(
new window.Text(node.nodeValue ?? "")
)
);
return vdom;
}
export {
hydratedIslands,
toVdom
};
//# sourceMappingURL=vdom.js.map