Files
SnowLuma/.pnpm-store/v11/files/33/d6ef22958840446c5698c16973621fa9ad806128d8a812b0e6d79dd01e4daa20db26f3be57fc1b2754e2e86191cedba04e97f51ec912583ccc0b7893ac83ea
T
TanTan 2a69b23b47
Dev Build / cleanup (push) Waiting to run
Dev Build / typecheck (push) Waiting to run
docs: 仓库链接指向本镜像仓库
2026-11-13 09:27:07 +08:00

517 lines
17 KiB
Plaintext

import { DOM_ERROR_HANDLER, DOM_INTERNAL_TAG, DOM_MEMO, DOM_RENDERER, DOM_STASH } from "../constants.js";
import { createContext } from "./context.js";
import { globalContexts, useContext } from "../context.js";
import { toArray } from "../children.js";
import { normalizeIntrinsicElementKey, styleObjectForEach } from "../utils.js";
import "../hooks/index.js";
//#region src/jsx/dom/render.ts
const HONO_PORTAL_ELEMENT = "_hp";
const eventAliasMap = {
Change: "Input",
DoubleClick: "DblClick"
};
const nameSpaceMap = {
svg: "2000/svg",
math: "1998/Math/MathML"
};
const buildDataStack = [];
const refCleanupMap = /* @__PURE__ */ new WeakMap();
let nameSpaceContext = void 0;
const getNameSpaceContext = () => nameSpaceContext;
const isNodeString = (node) => "t" in node;
const eventCache = { onClick: ["click", false] };
const getEventSpec = (key) => {
if (!key.startsWith("on")) return;
if (eventCache[key]) return eventCache[key];
const match = key.match(/^on([A-Z][a-zA-Z]+?(?:PointerCapture)?)(Capture)?$/);
if (match) {
const [, eventName, capture] = match;
return eventCache[key] = [(eventAliasMap[eventName] || eventName).toLowerCase(), !!capture];
}
};
const toAttributeName = (element, key) => nameSpaceContext && element instanceof SVGElement && /[A-Z]/.test(key) && (key in element.style || key.match(/^(?:o|pai|str|u|ve)/)) ? key.replace(/([A-Z])/g, "-$1").toLowerCase() : key;
const normalizeFormValue = (value) => value === null || value === void 0 || value === false ? null : value;
const applySelectValue = (select, props) => {
if (!("value" in props)) return;
select.value = normalizeFormValue(props["value"]);
if (!select.multiple && select.selectedIndex === -1) select.selectedIndex = 0;
};
const isIgnorableAttributeError = (error) => error instanceof DOMException && error.name === "InvalidCharacterError";
const applyProps = (container, attributes, oldAttributes) => {
attributes ||= {};
for (let key in attributes) {
const value = attributes[key];
if (key !== "children" && (!oldAttributes || oldAttributes[key] !== value)) {
key = normalizeIntrinsicElementKey(key);
const eventSpec = getEventSpec(key);
if (eventSpec) {
if (oldAttributes?.[key] !== value) {
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") {
refCleanupMap.get(container)?.();
let cleanup;
if (typeof value === "function") cleanup = value(container) || (() => value(null));
else if (value && "current" in value) {
value.current = container;
cleanup = () => value.current = null;
}
refCleanupMap.set(container, cleanup);
} 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 {
if (key === "value") {
const nodeName = container.nodeName;
if (nodeName === "SELECT") continue;
else if (nodeName === "INPUT" || nodeName === "TEXTAREA") {
container.value = normalizeFormValue(value);
if (nodeName === "TEXTAREA") {
container.textContent = value;
continue;
}
}
} else if (key === "checked" && container.nodeName === "INPUT" || key === "selected" && container.nodeName === "OPTION") container[key] = value;
const k = toAttributeName(container, key);
try {
if (value === null || value === void 0 || value === false) container.removeAttribute(k);
else if (value === true) container.setAttribute(k, "");
else if (typeof value === "string" || typeof value === "number") container.setAttribute(k, value);
else container.setAttribute(k, value.toString());
} catch (e) {
if (!isIgnorableAttributeError(e)) throw e;
}
}
}
}
if (oldAttributes) for (let key in oldAttributes) {
const value = oldAttributes[key];
if (key !== "children" && !(key in attributes)) {
key = normalizeIntrinsicElementKey(key);
const eventSpec = getEventSpec(key);
if (eventSpec) container.removeEventListener(eventSpec[0], value, eventSpec[1]);
else if (key === "ref") {
refCleanupMap.get(container)?.();
refCleanupMap.delete(container);
} else try {
container.removeAttribute(toAttributeName(container, key));
} catch (e) {
if (!isIgnorableAttributeError(e)) throw e;
}
}
}
};
const invokeTag = (context, node) => {
node[DOM_STASH][0] = 0;
buildDataStack.push([context, node]);
const func = node.tag[DOM_RENDERER] || node.tag;
const props = func.defaultProps ? {
...func.defaultProps,
...node.props
} : node.props;
try {
return [func.call(null, props)];
} finally {
buildDataStack.pop();
}
};
const getNextChildren = (node, container, nextChildren, childrenToRemove, callbacks) => {
if (node.vR?.length) {
childrenToRemove.push(...node.vR);
delete node.vR;
}
if (typeof node.tag === "function") node[DOM_STASH][1][1]?.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;
const currentNextChildrenIndex = nextChildren.length;
getNextChildren(child, container, nextChildren, childrenToRemove, callbacks);
if (child.s) {
for (let i = currentNextChildrenIndex; i < nextChildren.length; i++) nextChildren[i].s = true;
child.s = false;
}
} else {
nextChildren.push(child);
if (child.vR?.length) {
childrenToRemove.push(...child.vR);
delete child.vR;
}
}
});
};
const findInsertBefore = (node) => {
while (node && (node.tag === HONO_PORTAL_ELEMENT || !node.e)) node = node.tag === HONO_PORTAL_ELEMENT || !node.vC?.[0] ? node.nN : node.vC[0];
return node?.e;
};
const removeNode = (node) => {
if (!isNodeString(node)) {
node[DOM_STASH]?.[1][1]?.forEach((data) => data[2]?.());
refCleanupMap.get(node.e)?.();
if (node.p === 2) node.vC?.forEach((n) => n.p = 2);
node.vC?.forEach(removeNode);
}
if (!node.p) {
node.e?.remove();
delete node.e;
}
if (typeof node.tag === "function") {
updateMap.delete(node);
fallbackUpdateFnArrayMap.delete(node);
delete node[DOM_STASH][3];
node.a = true;
}
};
const apply = (node, container, isNew) => {
node.c = container;
applyNodeObject(node, container, isNew);
};
const findChildNodeIndex = (childNodes, child) => {
if (!child) return;
for (let i = 0, len = childNodes.length; i < len; i++) if (childNodes[i] === child) return i;
};
const cancelBuild = Symbol();
const applyNodeObject = (node, container, isNew) => {
const next = [];
const remove = [];
const callbacks = [];
getNextChildren(node, container, next, remove, callbacks);
remove.forEach(removeNode);
const childNodes = isNew ? void 0 : container.childNodes;
let offset;
let insertBeforeNode = null;
if (isNew) offset = -1;
else if (!childNodes.length) offset = 0;
else {
const offsetByNextNode = findChildNodeIndex(childNodes, findInsertBefore(node.nN));
if (offsetByNextNode !== void 0) {
insertBeforeNode = childNodes[offsetByNextNode];
offset = offsetByNextNode;
} else offset = findChildNodeIndex(childNodes, next.find((n) => n.tag !== HONO_PORTAL_ELEMENT && n.e)?.e) ?? -1;
if (offset === -1) isNew = true;
}
for (let i = 0, len = next.length; i < len; i++, offset++) {
const child = next[i];
let el;
if (child.s && child.e) {
el = child.e;
child.s = false;
} else {
const isNewLocal = isNew || !child.e;
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);
applyNodeObject(child, el, isNewLocal);
if (child.tag === "select") applySelectValue(el, child.props);
}
}
if (child.tag === HONO_PORTAL_ELEMENT) offset--;
else if (isNew) {
if (!el.parentNode) container.appendChild(el);
} else if (childNodes[offset] !== el && childNodes[offset - 1] !== el) {
if (childNodes[offset + 1] === el) container.appendChild(childNodes[offset]);
else container.insertBefore(el, insertBeforeNode || childNodes[offset] || null);
}
}
if (node.pP) node.pP = void 0;
if (callbacks.length) {
const useLayoutEffectCbs = [];
const useEffectCbs = [];
callbacks.forEach(([, useLayoutEffectCb, , useEffectCb, useInsertionEffectCb]) => {
if (useLayoutEffectCb) useLayoutEffectCbs.push(useLayoutEffectCb);
if (useEffectCb) useEffectCbs.push(useEffectCb);
useInsertionEffectCb?.();
});
useLayoutEffectCbs.forEach((cb) => cb());
if (useEffectCbs.length) requestAnimationFrame(() => {
useEffectCbs.forEach((cb) => cb());
});
}
};
const isSameContext = (oldContexts, newContexts) => !!(oldContexts && oldContexts.length === newContexts.length && oldContexts.every((ctx, i) => ctx[1] === newContexts[i][1]));
const indexChildrenByKey = (children) => {
const index = /* @__PURE__ */ new Map();
for (const child of children) {
const key = child.key;
if (index.has(key)) return;
index.set(key, child);
}
return index;
};
const fallbackUpdateFnArrayMap = /* @__PURE__ */ new WeakMap();
const build = (context, node, children) => {
const buildWithPreviousChildren = !children && node.pC;
if (children) node.pC ||= node.vC;
let foundErrorHandler;
try {
children ||= typeof node.tag == "function" ? invokeTag(context, node) : toArray(node.props.children);
if (children[0]?.tag === "" && children[0][DOM_ERROR_HANDLER]) {
foundErrorHandler = children[0][DOM_ERROR_HANDLER];
context[5].push([
context,
foundErrorHandler,
node
]);
}
let oldVChildren = buildWithPreviousChildren ? [...node.pC] : node.vC ? [...node.vC] : void 0;
const vChildren = [];
let prevNode;
let scanBudget = (oldVChildren?.length || 0) * 2;
let oldChildrenByKey;
for (let i = 0; i < children.length; i++) {
if (Array.isArray(children[i])) {
children.splice(i, 1, ...children[i].flat(Infinity));
i--;
continue;
}
let child = buildNode(children[i]);
if (child) {
if (typeof child.tag === "function" && !child.tag[DOM_INTERNAL_TAG]) {
if (globalContexts.length > 0) child[DOM_STASH][2] = globalContexts.map((c) => [c, c.values.at(-1)]);
if (context[5]?.length) child[DOM_STASH][3] = context[5].at(-1);
}
let oldChild;
if (oldChildrenByKey && child.key === void 0 && !isNodeString(child)) {
oldVChildren = [...oldChildrenByKey.values()];
oldChildrenByKey = void 0;
}
if (oldChildrenByKey) {
const key = child.key;
const candidate = oldChildrenByKey.get(key);
if (candidate && (isNodeString(child) ? isNodeString(candidate) : candidate.tag === child.tag && candidate.key === key)) {
oldChild = candidate;
oldChildrenByKey.delete(key);
}
} else if (oldVChildren && oldVChildren.length) {
const first = oldVChildren[0];
if (isNodeString(child) ? isNodeString(first) : !isNodeString(first) && (child.key !== void 0 ? first.key === child.key && first.tag === child.tag : first.tag === child.tag)) oldChild = oldVChildren.shift();
else {
const i = oldVChildren.findIndex(isNodeString(child) ? (c) => isNodeString(c) : child.key !== void 0 ? (c) => c.key === child.key && c.tag === child.tag : (c) => c.tag === child.tag);
scanBudget -= i === -1 ? oldVChildren.length : i;
if (i !== -1) {
oldChild = oldVChildren[i];
oldVChildren.splice(i, 1);
}
if (scanBudget < 0) {
scanBudget = Infinity;
oldChildrenByKey = indexChildrenByKey(oldVChildren);
}
}
}
if (oldChild) {
if (isNodeString(child)) {
if (oldChild.t !== child.t) {
oldChild.t = child.t;
oldChild.d = true;
}
child = oldChild;
} else {
const pP = oldChild.pP = oldChild.props;
oldChild.props = child.props;
oldChild.f ||= child.f || node.f;
if (typeof child.tag === "function") {
const oldContexts = oldChild[DOM_STASH][2];
oldChild[DOM_STASH][2] = child[DOM_STASH][2] || [];
oldChild[DOM_STASH][3] = child[DOM_STASH][3];
if (!oldChild.f && ((oldChild.o || oldChild) === child.o || oldChild.tag[DOM_MEMO]?.(pP, oldChild.props)) && isSameContext(oldContexts, oldChild[DOM_STASH][2])) oldChild.s = true;
}
child = oldChild;
}
} else if (!isNodeString(child) && nameSpaceContext) {
const ns = useContext(nameSpaceContext);
if (ns) child.n = ns;
}
if (!isNodeString(child) && !child.s) {
build(context, child);
delete child.f;
}
vChildren.push(child);
if (prevNode && !prevNode.s && !child.s) for (let p = prevNode; p && !isNodeString(p); p = p.vC?.at(-1)) p.nN = child;
prevNode = child;
}
}
oldVChildren = oldChildrenByKey ? [...oldChildrenByKey.values()] : oldVChildren;
node.vR = buildWithPreviousChildren ? [...node.vC, ...oldVChildren || []] : oldVChildren || [];
node.vC = vChildren;
if (buildWithPreviousChildren) delete node.pC;
} catch (e) {
node.f = true;
if (e === cancelBuild) {
if (foundErrorHandler) return;
else throw e;
}
const [errorHandlerContext, errorHandler, errorHandlerNode] = node[DOM_STASH]?.[3] || [];
if (errorHandler) {
const fallbackUpdateFn = () => update([
0,
false,
context[2]
], errorHandlerNode);
const fallbackUpdateFnArray = fallbackUpdateFnArrayMap.get(errorHandlerNode) || [];
fallbackUpdateFnArray.push(fallbackUpdateFn);
fallbackUpdateFnArrayMap.set(errorHandlerNode, fallbackUpdateFnArray);
const fallback = errorHandler(e, () => {
const fnArray = fallbackUpdateFnArrayMap.get(errorHandlerNode);
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, errorHandlerNode, [fallback]);
if ((errorHandler.length === 1 || context !== errorHandlerContext) && errorHandlerNode.c) {
apply(errorHandlerNode, errorHandlerNode.c, false);
return;
}
}
throw cancelBuild;
}
}
throw e;
} finally {
if (foundErrorHandler) context[5].pop();
}
};
const buildNode = (node) => {
if (node === void 0 || node === null || typeof node === "boolean") return;
else if (typeof node === "string" || typeof node === "number") return {
t: node.toString(),
d: true
};
else {
if ("vR" in node) node = {
tag: node.tag,
props: node.props,
key: node.key,
f: node.f,
type: node.tag,
ref: node.props.ref,
o: node.o || node
};
if (typeof node.tag === "function") node[DOM_STASH] = [0, []];
else {
const ns = nameSpaceMap[node.tag];
if (ns) {
nameSpaceContext ||= createContext("");
node.props.children = [{
tag: nameSpaceContext,
props: {
value: node.n = `http://www.w3.org/${ns}`,
children: node.props.children
}
}];
}
}
return node;
}
};
const replaceContainer = (node, from, to) => {
if (node.c === from) {
node.c = to;
node.vC.forEach((child) => replaceContainer(child, from, to));
}
};
const updateSync = (context, node) => {
node[DOM_STASH][2]?.forEach(([c, v]) => {
c.values.push(v);
});
try {
build(context, node, void 0);
} catch {
return;
}
if (node.a) {
delete node.a;
return;
}
node[DOM_STASH][2]?.forEach(([c]) => {
c.values.pop();
});
if (context[0] !== 1 || !context[1]) apply(node, node.c, false);
};
const updateMap = /* @__PURE__ */ new WeakMap();
const currentUpdateSets = [];
const update = async (context, node) => {
context[5] ||= [];
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, (context) => {
updateSync(context, 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;
};
const renderNode = (node, container) => {
const context = [];
context[5] = [];
context[4] = true;
build(context, node, void 0);
context[4] = false;
const fragment = document.createDocumentFragment();
apply(node, fragment, true);
replaceContainer(node, fragment, container);
container.replaceChildren(fragment);
};
const render = (jsxNode, container) => {
renderNode(buildNode({
tag: "",
props: { children: jsxNode }
}), container);
};
const 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();
};
const createPortal = (children, container, key) => ({
tag: HONO_PORTAL_ELEMENT,
props: { children },
key,
e: container,
p: 1
});
//#endregion
export { build, buildDataStack, buildNode, createPortal, flushSync, getNameSpaceContext, render, renderNode, update };