UNPKG

@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
// 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