Files
discord-clone/node_modules/@unhead/dom/dist/index.cjs
2023-01-03 09:29:04 -06:00

200 lines
5.9 KiB
JavaScript

'use strict';
const TagsWithInnerContent = ["script", "style", "noscript"];
const HasElementTags = [
"base",
"meta",
"link",
"style",
"script",
"noscript"
];
const UniqueTags = ["base", "title", "titleTemplate", "bodyAttrs", "htmlAttrs"];
function tagDedupeKey(tag, fn) {
const { props, tag: tagName } = tag;
if (UniqueTags.includes(tagName))
return tagName;
if (tagName === "link" && props.rel === "canonical")
return "canonical";
if (props.charset)
return "charset";
const name = ["id"];
if (tagName === "meta")
name.push(...["name", "property", "http-equiv"]);
for (const n of name) {
if (typeof props[n] !== "undefined") {
const val = String(props[n]);
if (fn && !fn(val))
return false;
return `${tagName}:${n}:${val}`;
}
}
return false;
}
const setAttrs = (ctx, markSideEffect) => {
const { tag, $el } = ctx;
if (!$el)
return;
Object.entries(tag.props).forEach(([k, value]) => {
value = String(value);
const attrSdeKey = `attr:${k}`;
if (k === "class") {
if (!value)
return;
for (const c of value.split(" ")) {
const classSdeKey = `${attrSdeKey}:${c}`;
if (markSideEffect)
markSideEffect(ctx, classSdeKey, () => $el.classList.remove(c));
if (!$el.classList.contains(c))
$el.classList.add(c);
}
return;
}
if (markSideEffect && !k.startsWith("data-h-"))
markSideEffect(ctx, attrSdeKey, () => $el.removeAttribute(k));
if ($el.getAttribute(k) !== value)
$el.setAttribute(k, value);
});
if (TagsWithInnerContent.includes(tag.tag) && $el.innerHTML !== (tag.children || ""))
$el.innerHTML = tag.children || "";
};
function hashCode(s) {
let h = 9;
for (let i = 0; i < s.length; )
h = Math.imul(h ^ s.charCodeAt(i++), 9 ** 9);
return ((h ^ h >>> 9) + 65536).toString(16).substring(1, 8).toLowerCase();
}
async function renderDOMHead(head, options = {}) {
const ctx = { shouldRender: true };
await head.hooks.callHook("dom:beforeRender", ctx);
if (!ctx.shouldRender)
return;
const dom = options.document || window.document;
const staleSideEffects = head._popSideEffectQueue();
head.headEntries().map((entry) => entry._sde).forEach((sde) => {
Object.entries(sde).forEach(([key, fn]) => {
staleSideEffects[key] = fn;
});
});
const preRenderTag = async (tag) => {
const entry = head.headEntries().find((e) => e._i === tag._e);
const renderCtx = {
renderId: tag._d || hashCode(JSON.stringify({ ...tag, _e: void 0, _p: void 0 })),
$el: null,
shouldRender: true,
tag,
entry,
staleSideEffects
};
await head.hooks.callHook("dom:beforeRenderTag", renderCtx);
return renderCtx;
};
const renders = [];
const pendingRenders = {
body: [],
head: []
};
const markSideEffect = (ctx2, key, fn) => {
key = `${ctx2.renderId}:${key}`;
if (ctx2.entry)
ctx2.entry._sde[key] = fn;
delete staleSideEffects[key];
};
const markEl = (ctx2) => {
head._elMap[ctx2.renderId] = ctx2.$el;
renders.push(ctx2);
markSideEffect(ctx2, "el", () => {
ctx2.$el?.remove();
delete head._elMap[ctx2.renderId];
});
};
for (const t of await head.resolveTags()) {
const ctx2 = await preRenderTag(t);
if (!ctx2.shouldRender)
continue;
const { tag } = ctx2;
if (tag.tag === "title") {
dom.title = tag.children || "";
renders.push(ctx2);
continue;
}
if (tag.tag === "htmlAttrs" || tag.tag === "bodyAttrs") {
ctx2.$el = dom[tag.tag === "htmlAttrs" ? "documentElement" : "body"];
setAttrs(ctx2, markSideEffect);
renders.push(ctx2);
continue;
}
ctx2.$el = head._elMap[ctx2.renderId];
if (!ctx2.$el && tag._hash) {
ctx2.$el = dom.querySelector(`${tag.tagPosition?.startsWith("body") ? "body" : "head"} > ${tag.tag}[data-h-${tag._hash}]`);
}
if (ctx2.$el) {
if (ctx2.tag._d)
setAttrs(ctx2);
markEl(ctx2);
continue;
}
ctx2.$el = dom.createElement(tag.tag);
setAttrs(ctx2);
pendingRenders[tag.tagPosition?.startsWith("body") ? "body" : "head"].push(ctx2);
}
Object.entries(pendingRenders).forEach(([pos, queue]) => {
if (!queue.length)
return;
for (const $el of [...dom[pos].children].reverse()) {
const elTag = $el.tagName.toLowerCase();
if (!HasElementTags.includes(elTag))
continue;
const dedupeKey = tagDedupeKey({
tag: elTag,
props: $el.getAttributeNames().reduce((props, name) => ({ ...props, [name]: $el.getAttribute(name) }), {})
});
const matchIdx = queue.findIndex((ctx2) => ctx2 && (ctx2.tag._d === dedupeKey || $el.isEqualNode(ctx2.$el)));
if (matchIdx !== -1) {
const ctx2 = queue[matchIdx];
ctx2.$el = $el;
setAttrs(ctx2);
markEl(ctx2);
delete queue[matchIdx];
}
}
queue.forEach((ctx2) => {
if (!ctx2.$el)
return;
switch (ctx2.tag.tagPosition) {
case "bodyClose":
dom.body.appendChild(ctx2.$el);
break;
case "bodyOpen":
dom.body.insertBefore(ctx2.$el, dom.body.firstChild);
break;
case "head":
default:
dom.head.appendChild(ctx2.$el);
break;
}
markEl(ctx2);
});
});
for (const ctx2 of renders)
await head.hooks.callHook("dom:renderTag", ctx2);
Object.values(staleSideEffects).forEach((fn) => fn());
}
exports.domUpdatePromise = null;
async function debouncedRenderDOMHead(head, options = {}) {
function doDomUpdate() {
exports.domUpdatePromise = null;
return renderDOMHead(head, options);
}
const delayFn = options.delayFn || ((fn) => setTimeout(fn, 10));
return exports.domUpdatePromise = exports.domUpdatePromise || new Promise((resolve) => delayFn(() => resolve(doDomUpdate())));
}
exports.debouncedRenderDOMHead = debouncedRenderDOMHead;
exports.hashCode = hashCode;
exports.renderDOMHead = renderDOMHead;