import { JSX } from "solid-js"; import { Dynamic } from "solid-js/web"; import type { Element, Text, Root, Comment, Doctype, ElementContent } from "hast"; export type HastNode = Element | Text | Root | Comment | Doctype; export type Components = { [Key in keyof JSX.IntrinsicElements]?: ( props: JSX.IntrinsicElements[Key] & { node?: Element } ) => JSX.Element; }; export interface HastToSolidOptions { components?: Components; } function isElement(node: HastNode): node is Element { return node.type === "element"; } function isText(node: HastNode): node is Text { return node.type === "text"; } function isRoot(node: HastNode): node is Root { return node.type === "root"; } function convertProperties(properties: Record): Record { const result: Record = {}; for (const [key, value] of Object.entries(properties)) { if (value === undefined || value === null) continue; // Convert className to class for Solid if (key === "className") { result.class = Array.isArray(value) ? value.join(" ") : value; continue; } // Convert htmlFor to for if (key === "htmlFor") { result.for = value; continue; } // Handle style object if (key === "style" && typeof value === "object") { result.style = value; continue; } // Handle boolean attributes if (typeof value === "boolean") { if (value) { result[key] = true; } continue; } // Handle arrays (like class lists) if (Array.isArray(value)) { result[key] = value.join(" "); continue; } result[key] = value; } return result; } function renderNode( node: HastNode | ElementContent, options: HastToSolidOptions, key?: number ): JSX.Element | string | null { if (isText(node as HastNode)) { return (node as Text).value; } if (isElement(node as HastNode)) { const element = node as Element; const tagName = element.tagName as keyof JSX.IntrinsicElements; const CustomComponent = options.components?.[tagName]; const props = convertProperties(element.properties || {}); const children = element.children?.map((child, i) => renderNode(child, options, i)) || []; if (CustomComponent) { return ( {children} ); } // Use Dynamic for standard HTML elements return {children}; } if (isRoot(node as HastNode)) { const root = node as Root; const children = root.children?.map((child, i) => renderNode(child, options, i)) || []; return <>{children}; } // Ignore comments and doctypes return null; } export function hastToSolid(tree: HastNode, options: HastToSolidOptions = {}): JSX.Element { return renderNode(tree, options) as JSX.Element; }