/**
* Melina.js JSX-to-DOM Runtime
*
* JSX in client.tsx files creates REAL DOM elements, not virtual DOM.
*
* Usage:
* const el =
Hello
;
* document.body.appendChild(el); // Works directly!
*/
import {
assignElementStyle,
createDomElement,
domAttributeName,
setElementClass,
} from "./dom";
type Child = Node | string | number | boolean | null | undefined | Child[];
export function jsx(
tag: string | ((props: any) => Node),
props: Record | null,
...children: Child[]
): Node {
// Function component
if (typeof tag === "function") {
const finalProps = { ...props };
// Use varargs children only if props.children is missing (Classic Runtime fallback)
if ((!props || props.children === undefined) && children.length > 0) {
finalProps.children = children.length === 1 ? children[0] : children;
}
return tag(finalProps);
}
const el = createDomElement(tag);
// Set attributes/properties
if (props) {
for (const [key, value] of Object.entries(props)) {
if (key === "children") continue;
if (value === null || value === undefined || value === false) continue;
if (key === "style" && typeof value === "object") {
assignElementStyle(el, value);
} else if (key === "className" || key === "class") {
setElementClass(el, value);
} else if (key === "htmlFor") {
el.setAttribute(domAttributeName(el, key), String(value));
} else if (key === "dangerouslySetInnerHTML") {
el.innerHTML = value.__html || "";
} else if (key.startsWith("on") && typeof value === "function") {
// Event handlers: onClick -> click
const event = key.slice(2).toLowerCase();
el.addEventListener(event, value);
} else if (key === "ref" && typeof value === "function") {
value(el);
} else if (value === true) {
el.setAttribute(domAttributeName(el, key), "");
} else {
el.setAttribute(domAttributeName(el, key), String(value));
}
}
// Handle children passed as prop (Automatic Runtime)
if (props.children !== undefined) {
appendChildren(
el,
Array.isArray(props.children) ? props.children : [props.children],
);
}
}
// Append direct children (Classic Runtime fallback)
if ((!props || props.children === undefined) && children.length > 0) {
appendChildren(el, children);
}
return el;
}
function appendChildren(parent: Element, children: Child[]) {
for (const child of children) {
if (
child === null ||
child === undefined ||
child === false ||
child === true
)
continue;
if (Array.isArray(child)) {
appendChildren(parent, child);
} else if (child instanceof Node) {
parent.appendChild(child);
} else {
parent.appendChild(document.createTextNode(String(child)));
}
}
}
// JSX runtime entry points (used by Bun/esbuild JSX transform)
export const jsxs = jsx;
export const jsxDEV = jsx;
export const Fragment = ({ children }: { children: Child | Child[] }) => {
const frag = document.createDocumentFragment();
if (children !== undefined) {
appendChildren(
frag as any,
Array.isArray(children) ? children : [children],
);
}
return frag;
};
export default { jsx, jsxs, jsxDEV, Fragment };