vanjs-htm
Version:
HTM with VanJS for JSX-like syntax in vanilla JavaScript using VanJS reactivity.
111 lines (109 loc) • 3.84 kB
JavaScript
// src/index.ts
var vanHTM = (options) => {
const { htm, van, vanX } = options;
const _document = document;
const _undefined = void 0;
const isTypeOfFunction = (object) => typeof object === "function";
const isTypeOfString = (value) => typeof value === "string";
const objectHasOwn = Object.hasOwn;
const directives = {
f: { e: "for:each" },
p: { m: "portal:mount" },
s: { f: "show:fallback", w: "show:when" },
svg: "vh:svg"
};
const extractProperty = (object, key) => {
const value = object[key];
delete object[key];
return value;
};
const hasShowWhenProperty = (props) => objectHasOwn(props, directives.s.w);
const handleShow = (fnOrNode, props, children, isTag = true) => {
let fallback = extractProperty(props, directives.s.f) ?? "";
let when = extractProperty(props, directives.s.w);
return () => {
let condition = when && typeof when === "object" && "val" in when ? when.val : isTypeOfFunction(when) ? when() : when;
return condition ? isTag ? fnOrNode(props, ...children) : fnOrNode() : isTypeOfFunction(fallback) ? fallback() : fallback;
};
};
let portalIdCounter = 0;
const handleFor = (tag, props, itemFunc) => {
const items = extractProperty(props, directives.f.e);
const listFn = () => vanX.list(tag(props), items, itemFunc);
return hasShowWhenProperty(props) ? handleShow(listFn, props, _undefined, false) : listFn();
}, handlePortal = (tag, props, children) => {
const mount = extractProperty(props, directives.p.m);
const targetElement = isTypeOfString(mount) ? _document.querySelector(mount) : mount;
const portalId = `p-${portalIdCounter++}`;
const portalContentFn = () => {
const element = tag(props, ...children);
element?.setAttribute?.("p:id", portalId);
return element;
};
van.add(
targetElement,
hasShowWhenProperty(props) ? handleShow(portalContentFn, props, _undefined, false) : portalContentFn()
);
return _document.createComment(portalId);
};
const svgElements = /* @__PURE__ */ new Set([
"circle",
"clipPath",
"defs",
"desc",
"ellipse",
"filter",
"foreignObject",
"g",
"line",
"linearGradient",
"marker",
"mask",
"path",
"pattern",
"polygon",
"polyline",
"radialGradient",
"rect",
"stop",
"svg",
"symbol",
"text",
"textPath",
"tspan",
"use"
]);
function h(type, props, ...children) {
this[0] = 3;
const tag = isTypeOfString(type) ? (props && objectHasOwn(props, directives.svg) ? !!extractProperty(props, directives.svg) : svgElements.has(type)) ? van.tags("http://www.w3.org/2000/svg")[type] : van.tags[type] : type;
const decodedChildren = children;
if (props) {
if (objectHasOwn(props, directives.f.e)) {
return handleFor(tag, props, decodedChildren[0]);
} else if (objectHasOwn(props, directives.p.m)) {
return handlePortal(tag, props, decodedChildren);
} else if (objectHasOwn(props, directives.s.w)) {
return handleShow(tag, props, decodedChildren);
}
}
return tag(props, ...decodedChildren);
}
return {
html: htm.bind(h),
rmPortals: (parent, portalTarget = _document.body) => {
let targetElem = isTypeOfString(portalTarget) ? _document.querySelector(portalTarget) : portalTarget;
if (!targetElem) return;
const result = [];
let child = parent.firstChild;
while (child) {
if (child.nodeType === Node.COMMENT_NODE && child.data.startsWith("p-")) {
result.push(child.data);
}
child = child.nextSibling;
}
for (const portalId of result) targetElem.querySelector(`[p\\:id="${portalId}"]`)?.remove();
}
};
};
var src_default = vanHTM;
export { src_default as default };