// src/jsx/dom/render.ts import { toArray } from "../children.js"; import { DOM_RENDERER, DOM_ERROR_HANDLER, DOM_STASH, DOM_INTERNAL_TAG } from "../constants.js"; import { globalContexts as globalJSXContexts, useContext } from "../context.js"; import { STASH_EFFECT } from "../hooks/index.js"; import { styleObjectForEach } from "../utils.js"; import { createContext } from "./context.js"; var HONO_PORTAL_ELEMENT = "_hp"; var eventAliasMap = { Change: "Input", DoubleClick: "DblClick" }; var nameSpaceMap = { svg: "http://www.w3.org/2000/svg", math: "http://www.w3.org/1998/Math/MathML" }; var skipProps = /* @__PURE__ */ new Set(["children"]); var buildDataStack = []; var nameSpaceContext = void 0; var isNodeString = (node) => "t" in node; var getEventSpec = (key) => { const match = key.match(/^on([A-Z][a-zA-Z]+?(?:PointerCapture)?)(Capture)?$/); if (match) { const [, eventName, capture] = match; return [(eventAliasMap[eventName] || eventName).toLowerCase(), !!capture]; } return void 0; }; var applyProps = (container, attributes, oldAttributes) => { attributes ||= {}; for (const [key, value] of Object.entries(attributes)) { if (!skipProps.has(key) && (!oldAttributes || oldAttributes[key] !== value)) { const eventSpec = getEventSpec(key); if (eventSpec) { if (oldAttributes) { container.removeEventListener(eventSpec[0], oldAttributes[key], eventSpec[1]); } if (value != null) { if (typeof value !== "function") { throw new Error(`Event handler for "${key}" is not a function`); } container.addEventListener(eventSpec[0], value, eventSpec[1]); } } else if (key === "dangerouslySetInnerHTML" && value) { container.innerHTML = value.__html; } else if (key === "ref") { if (typeof value === "function") { value(container); } else if (value && "current" in value) { value.current = container; } } else if (key === "style") { const style = container.style; if (typeof value === "string") { style.cssText = value; } else { style.cssText = ""; if (value != null) { styleObjectForEach(value, style.setProperty.bind(style)); } } } else { const nodeName = container.nodeName; if (key === "value") { if (nodeName === "INPUT" || nodeName === "TEXTAREA" || nodeName === "SELECT") { ; container.value = value === null || value === void 0 || value === false ? null : value; if (nodeName === "TEXTAREA") { container.textContent = value; continue; } else if (nodeName === "SELECT") { if (container.selectedIndex === -1) { ; container.selectedIndex = 0; } continue; } } } else if (key === "checked" && nodeName === "INPUT" || key === "selected" && nodeName === "OPTION") { ; container[key] = value; } if (value === null || value === void 0 || value === false) { container.removeAttribute(key); } else if (value === true) { container.setAttribute(key, ""); } else if (typeof value === "string" || typeof value === "number") { container.setAttribute(key, value); } else { container.setAttribute(key, value.toString()); } } } } if (oldAttributes) { for (const [key, value] of Object.entries(oldAttributes)) { if (!skipProps.has(key) && !(key in attributes)) { const eventSpec = getEventSpec(key); if (eventSpec) { container.removeEventListener(eventSpec[0], value, eventSpec[1]); } else if (key === "ref") { if (typeof value === "function") { value(null); } else { value.current = null; } } else { container.removeAttribute(key); } } } } }; var invokeTag = (context, node) => { if (node.s) { const res = node.s; node.s = void 0; return res; } node[DOM_STASH][0] = 0; buildDataStack.push([context, node]); const func = node.tag[DOM_RENDERER] || node.tag; try { return [ func.call(null, { ...func.defaultProps || {}, ...node.props }) ]; } finally { buildDataStack.pop(); } }; var getNextChildren = (node, container, nextChildren, childrenToRemove, callbacks) => { childrenToRemove.push(...node.vR); if (typeof node.tag === "function") { node[DOM_STASH][1][STASH_EFFECT]?.forEach((data) => callbacks.push(data)); } node.vC.forEach((child) => { if (isNodeString(child)) { nextChildren.push(child); } else { if (typeof child.tag === "function" || child.tag === "") { child.c = container; getNextChildren(child, container, nextChildren, childrenToRemove, callbacks); } else { nextChildren.push(child); childrenToRemove.push(...child.vR); } } }); }; var findInsertBefore = (node) => { if (!node) { return null; } else if (node.e) { return node.e; } if (node.vC) { for (let i = 0, len = node.vC.length; i < len; i++) { const e = findInsertBefore(node.vC[i]); if (e) { return e; } } } return findInsertBefore(node.nN); }; var removeNode = (node) => { if (!isNodeString(node)) { node[DOM_STASH]?.[1][STASH_EFFECT]?.forEach((data) => data[2]?.()); if (node.e && node.props?.ref) { if (typeof node.props.ref === "function") { node.props.ref(null); } else { node.props.ref.current = null; } } node.vC?.forEach(removeNode); } if (node.tag !== HONO_PORTAL_ELEMENT) { node.e?.remove(); } if (typeof node.tag === "function") { updateMap.delete(node); fallbackUpdateFnArrayMap.delete(node); } }; var apply = (node, container) => { node.c = container; applyNodeObject(node, container); }; var applyNode = (node, container) => { if (isNodeString(node)) { container.textContent = node.t; } else { applyNodeObject(node, container); } }; var findChildNodeIndex = (childNodes, child) => { if (!child) { return; } for (let i = 0, len = childNodes.length; i < len; i++) { if (childNodes[i] === child) { return i; } } return; }; var applyNodeObject = (node, container) => { const next = []; const remove = []; const callbacks = []; getNextChildren(node, container, next, remove, callbacks); const childNodes = container.childNodes; let offset = findChildNodeIndex(childNodes, findInsertBefore(node.nN)) ?? findChildNodeIndex(childNodes, next.find((n) => n.e)?.e) ?? childNodes.length; for (let i = 0, len = next.length; i < len; i++, offset++) { const child = next[i]; let el; if (isNodeString(child)) { if (child.e && child.d) { child.e.textContent = child.t; } child.d = false; el = child.e ||= document.createTextNode(child.t); } else { el = child.e ||= child.n ? document.createElementNS(child.n, child.tag) : document.createElement(child.tag); applyProps(el, child.props, child.pP); applyNode(child, el); } if (childNodes[offset] !== el && childNodes[offset - 1] !== child.e && child.tag !== HONO_PORTAL_ELEMENT) { container.insertBefore(el, childNodes[offset] || null); } } remove.forEach(removeNode); callbacks.forEach(([, cb]) => cb?.()); requestAnimationFrame(() => { callbacks.forEach(([, , , cb]) => cb?.()); }); }; var fallbackUpdateFnArrayMap = /* @__PURE__ */ new WeakMap(); var build = (context, node, topLevelErrorHandlerNode, children) => { let errorHandler; children ||= typeof node.tag == "function" ? invokeTag(context, node) : toArray(node.props.children); if (children[0]?.tag === "") { errorHandler = children[0][DOM_ERROR_HANDLER]; topLevelErrorHandlerNode ||= node; } const oldVChildren = node.vC ? [...node.vC] : []; const vChildren = []; const vChildrenToRemove = []; let prevNode; try { children.flat().forEach((c) => { let child = buildNode(c); if (child) { if (prevNode) { prevNode.nN = child; } prevNode = child; if (typeof child.tag === "function" && !child.tag[DOM_INTERNAL_TAG] && globalJSXContexts.length > 0) { child[DOM_STASH][2] = globalJSXContexts.map((c2) => [c2, c2.values.at(-1)]); } let oldChild; const i = oldVChildren.findIndex((c2) => c2.key === child.key); if (i !== -1) { oldChild = oldVChildren[i]; oldVChildren.splice(i, 1); } if (oldChild) { if (isNodeString(child)) { if (!isNodeString(oldChild)) { vChildrenToRemove.push(oldChild); } else { if (oldChild.t !== child.t) { oldChild.t = child.t; oldChild.d = true; } child = oldChild; } } else if (oldChild.tag !== child.tag) { vChildrenToRemove.push(oldChild); } else { oldChild.pP = oldChild.props; oldChild.props = child.props; if (typeof child.tag === "function") { oldChild[DOM_STASH][2] = child[DOM_STASH][2] || []; } child = oldChild; } } else if (!isNodeString(child) && nameSpaceContext) { const ns = useContext(nameSpaceContext); if (ns) { child.n = ns; } } if (!isNodeString(child)) { build(context, child, topLevelErrorHandlerNode); } vChildren.push(child); } }); node.vC = vChildren; vChildrenToRemove.push(...oldVChildren); node.vR = vChildrenToRemove; } catch (e) { if (errorHandler) { const fallbackUpdateFn = () => update([0, false, context[2]], topLevelErrorHandlerNode); const fallbackUpdateFnArray = fallbackUpdateFnArrayMap.get(topLevelErrorHandlerNode) || []; fallbackUpdateFnArray.push(fallbackUpdateFn); fallbackUpdateFnArrayMap.set(topLevelErrorHandlerNode, fallbackUpdateFnArray); const fallback = errorHandler(e, () => { const fnArray = fallbackUpdateFnArrayMap.get(topLevelErrorHandlerNode); if (fnArray) { const i = fnArray.indexOf(fallbackUpdateFn); if (i !== -1) { fnArray.splice(i, 1); return fallbackUpdateFn(); } } }); if (fallback) { if (context[0] === 1) { context[1] = true; } else { build(context, node, topLevelErrorHandlerNode, [fallback]); } return; } } throw e; } }; var buildNode = (node) => { if (node === void 0 || node === null || typeof node === "boolean") { return void 0; } else if (typeof node === "string" || typeof node === "number") { return { t: node.toString(), d: true }; } else { if (typeof node.tag === "function") { if (node[DOM_STASH]) { node = { ...node }; } ; node[DOM_STASH] = [0, []]; } else { const ns = nameSpaceMap[node.tag]; if (ns) { ; node.n = ns; nameSpaceContext ||= createContext(""); node.props.children = [ { tag: nameSpaceContext.Provider, props: { value: ns, children: node.props.children } } ]; } } return node; } }; var replaceContainer = (node, from, to) => { if (node.c === from) { node.c = to; node.vC.forEach((child) => replaceContainer(child, from, to)); } }; var updateSync = (context, node) => { node[DOM_STASH][2]?.forEach(([c, v]) => { c.values.push(v); }); build(context, node, void 0); node[DOM_STASH][2]?.forEach(([c]) => { c.values.pop(); }); if (context[0] !== 1 || !context[1]) { apply(node, node.c); } }; var updateMap = /* @__PURE__ */ new WeakMap(); var currentUpdateSets = []; var update = async (context, node) => { const existing = updateMap.get(node); if (existing) { existing[0](void 0); } let resolve; const promise = new Promise((r) => resolve = r); updateMap.set(node, [ resolve, () => { if (context[2]) { context[2](context, node, (context2) => { updateSync(context2, node); }).then(() => resolve(node)); } else { updateSync(context, node); resolve(node); } } ]); if (currentUpdateSets.length) { ; currentUpdateSets.at(-1).add(node); } else { await Promise.resolve(); const latest = updateMap.get(node); if (latest) { updateMap.delete(node); latest[1](); } } return promise; }; var render = (jsxNode, container) => { const node = buildNode({ tag: "", props: { children: jsxNode } }); const context = []; context[4] = true; build(context, node, void 0); context[4] = false; const fragment = document.createDocumentFragment(); apply(node, fragment); replaceContainer(node, fragment, container); container.replaceChildren(fragment); }; var flushSync = (callback) => { const set = /* @__PURE__ */ new Set(); currentUpdateSets.push(set); callback(); set.forEach((node) => { const latest = updateMap.get(node); if (latest) { updateMap.delete(node); latest[1](); } }); currentUpdateSets.pop(); }; var createPortal = (children, container, key) => ({ tag: HONO_PORTAL_ELEMENT, props: { children }, key, e: container }); export { build, buildDataStack, createPortal, flushSync, render, update };