// Html Module - DSL for building HTML/DOM structures // // This module provides a type-safe way to describe HTML elements that can be // compiled to efficient JavaScript for browser rendering. // // Example usage: // let myView = div([class("container")], [ // h1([id("title")], [text("Hello!")]), // button([onClick(Increment)], [text("+")]) // ]) // Html type represents a DOM structure // Parameterized by Msg - the type of messages emitted by event handlers pub type Html = | Element(String, List>, List>) | Text(String) | Empty // Attributes that can be applied to elements pub type Attr = | Class(String) | Id(String) | Style(String, String) | Href(String) | Src(String) | Alt(String) | Type(String) | Value(String) | Placeholder(String) | Disabled(Bool) | Checked(Bool) | Name(String) | OnClick(M) | OnInput(fn(String): M) | OnSubmit(M) | OnChange(fn(String): M) | OnMouseEnter(M) | OnMouseLeave(M) | OnFocus(M) | OnBlur(M) | OnKeyDown(fn(String): M) | OnKeyUp(fn(String): M) | DataAttr(String, String) // ============================================================================ // Element builders - Container elements // ============================================================================ pub fn div(attrs: List>, children: List>): Html = Element("div", attrs, children) pub fn span(attrs: List>, children: List>): Html = Element("span", attrs, children) pub fn section(attrs: List>, children: List>): Html = Element("section", attrs, children) pub fn article(attrs: List>, children: List>): Html = Element("article", attrs, children) pub fn header(attrs: List>, children: List>): Html = Element("header", attrs, children) pub fn footer(attrs: List>, children: List>): Html = Element("footer", attrs, children) pub fn nav(attrs: List>, children: List>): Html = Element("nav", attrs, children) pub fn main(attrs: List>, children: List>): Html = Element("main", attrs, children) pub fn aside(attrs: List>, children: List>): Html = Element("aside", attrs, children) // ============================================================================ // Element builders - Text elements // ============================================================================ pub fn h1(attrs: List>, children: List>): Html = Element("h1", attrs, children) pub fn h2(attrs: List>, children: List>): Html = Element("h2", attrs, children) pub fn h3(attrs: List>, children: List>): Html = Element("h3", attrs, children) pub fn h4(attrs: List>, children: List>): Html = Element("h4", attrs, children) pub fn h5(attrs: List>, children: List>): Html = Element("h5", attrs, children) pub fn h6(attrs: List>, children: List>): Html = Element("h6", attrs, children) pub fn p(attrs: List>, children: List>): Html = Element("p", attrs, children) pub fn pre(attrs: List>, children: List>): Html = Element("pre", attrs, children) pub fn code(attrs: List>, children: List>): Html = Element("code", attrs, children) pub fn blockquote(attrs: List>, children: List>): Html = Element("blockquote", attrs, children) // ============================================================================ // Element builders - Inline elements // ============================================================================ pub fn a(attrs: List>, children: List>): Html = Element("a", attrs, children) pub fn strong(attrs: List>, children: List>): Html = Element("strong", attrs, children) pub fn em(attrs: List>, children: List>): Html = Element("em", attrs, children) pub fn small(attrs: List>, children: List>): Html = Element("small", attrs, children) pub fn br(): Html = Element("br", [], []) pub fn hr(): Html = Element("hr", [], []) // ============================================================================ // Element builders - Lists // ============================================================================ pub fn ul(attrs: List>, children: List>): Html = Element("ul", attrs, children) pub fn ol(attrs: List>, children: List>): Html = Element("ol", attrs, children) pub fn li(attrs: List>, children: List>): Html = Element("li", attrs, children) // ============================================================================ // Element builders - Forms // ============================================================================ pub fn form(attrs: List>, children: List>): Html = Element("form", attrs, children) pub fn input(attrs: List>): Html = Element("input", attrs, []) pub fn textarea(attrs: List>, children: List>): Html = Element("textarea", attrs, children) pub fn button(attrs: List>, children: List>): Html = Element("button", attrs, children) pub fn label(attrs: List>, children: List>): Html = Element("label", attrs, children) pub fn select(attrs: List>, children: List>): Html = Element("select", attrs, children) pub fn option(attrs: List>, children: List>): Html = Element("option", attrs, children) // ============================================================================ // Element builders - Media // ============================================================================ pub fn img(attrs: List>): Html = Element("img", attrs, []) pub fn video(attrs: List>, children: List>): Html = Element("video", attrs, children) pub fn audio(attrs: List>, children: List>): Html = Element("audio", attrs, children) // ============================================================================ // Element builders - Tables // ============================================================================ pub fn table(attrs: List>, children: List>): Html = Element("table", attrs, children) pub fn thead(attrs: List>, children: List>): Html = Element("thead", attrs, children) pub fn tbody(attrs: List>, children: List>): Html = Element("tbody", attrs, children) pub fn tr(attrs: List>, children: List>): Html = Element("tr", attrs, children) pub fn th(attrs: List>, children: List>): Html = Element("th", attrs, children) pub fn td(attrs: List>, children: List>): Html = Element("td", attrs, children) // ============================================================================ // Text and empty nodes // ============================================================================ pub fn text(content: String): Html = Text(content) pub fn empty(): Html = Empty // ============================================================================ // Attribute helpers // ============================================================================ pub fn class(name: String): Attr = Class(name) pub fn id(name: String): Attr = Id(name) pub fn style(property: String, value: String): Attr = Style(property, value) pub fn href(url: String): Attr = Href(url) pub fn src(url: String): Attr = Src(url) pub fn alt(description: String): Attr = Alt(description) pub fn inputType(t: String): Attr = Type(t) pub fn value(v: String): Attr = Value(v) pub fn placeholder(p: String): Attr = Placeholder(p) pub fn disabled(d: Bool): Attr = Disabled(d) pub fn checked(c: Bool): Attr = Checked(c) pub fn name(n: String): Attr = Name(n) pub fn onClick(msg: M): Attr = OnClick(msg) pub fn onInput(h: fn(String): M): Attr = OnInput(h) pub fn onSubmit(msg: M): Attr = OnSubmit(msg) pub fn onChange(h: fn(String): M): Attr = OnChange(h) pub fn onMouseEnter(msg: M): Attr = OnMouseEnter(msg) pub fn onMouseLeave(msg: M): Attr = OnMouseLeave(msg) pub fn onFocus(msg: M): Attr = OnFocus(msg) pub fn onBlur(msg: M): Attr = OnBlur(msg) pub fn onKeyDown(h: fn(String): M): Attr = OnKeyDown(h) pub fn onKeyUp(h: fn(String): M): Attr = OnKeyUp(h) pub fn data(name: String, value: String): Attr = DataAttr(name, value) // ============================================================================ // Utility functions // ============================================================================ // Conditionally include an element pub fn when(condition: Bool, element: Html): Html = if condition then element else Empty // Conditionally apply attributes pub fn attrIf(condition: Bool, attr: Attr): List> = if condition then [attr] else [] // ============================================================================ // Static HTML Rendering (for SSG) // ============================================================================ // Render an attribute to a string pub fn renderAttr(attr: Attr): String = match attr { Class(name) => " class=\"" + name + "\"", Id(name) => " id=\"" + name + "\"", Style(prop, val) => " style=\"" + prop + ": " + val + "\"", Href(url) => " href=\"" + url + "\"", Src(url) => " src=\"" + url + "\"", Alt(desc) => " alt=\"" + desc + "\"", Type(t) => " type=\"" + t + "\"", Value(v) => " value=\"" + v + "\"", Placeholder(p) => " placeholder=\"" + p + "\"", Disabled(true) => " disabled", Disabled(false) => "", Checked(true) => " checked", Checked(false) => "", Name(n) => " name=\"" + n + "\"", DataAttr(name, value) => " data-" + name + "=\"" + value + "\"", // Event handlers are ignored in static rendering OnClick(_) => "", OnInput(_) => "", OnSubmit(_) => "", OnChange(_) => "", OnMouseEnter(_) => "", OnMouseLeave(_) => "", OnFocus(_) => "", OnBlur(_) => "", OnKeyDown(_) => "", OnKeyUp(_) => "" } // Render attributes list to string pub fn renderAttrs(attrs: List>): String = List.fold(attrs, "", fn(acc, attr) => acc + renderAttr(attr)) // Self-closing tags pub fn isSelfClosing(tag: String): Bool = tag == "br" || tag == "hr" || tag == "img" || tag == "input" || tag == "meta" || tag == "link" || tag == "area" || tag == "base" || tag == "col" || tag == "embed" || tag == "source" || tag == "track" || tag == "wbr" // Render Html to string pub fn render(html: Html): String = match html { Element(tag, attrs, children) => { let attrStr = renderAttrs(attrs) if isSelfClosing(tag) then "<" + tag + attrStr + " />" else { let childrenStr = List.fold(children, "", fn(acc, child) => acc + render(child)) "<" + tag + attrStr + ">" + childrenStr + "" } }, Text(content) => escapeHtml(content), Empty => "" } // Escape HTML special characters pub fn escapeHtml(s: String): String = { // Simple replacement - a full implementation would handle all entities let s1 = String.replace(s, "&", "&") let s2 = String.replace(s1, "<", "<") let s3 = String.replace(s2, ">", ">") let s4 = String.replace(s3, "\"", """) s4 } // Render a full HTML document pub fn document(title: String, headExtra: List>, bodyContent: List>): String = { let headElements = List.concat([ [Element("meta", [DataAttr("charset", "UTF-8")], [])], [Element("meta", [Name("viewport"), Value("width=device-width, initial-scale=1.0")], [])], [Element("title", [], [Text(title)])], headExtra ]) let doc = Element("html", [DataAttr("lang", "en")], [ Element("head", [], headElements), Element("body", [], bodyContent) ]) "\n" + render(doc) }