#@mizchi/signals/ui

    Platform-independent Virtual Node (VNode) representation.

    #Overview

    This module provides:

    • VNode: Platform-agnostic virtual DOM node types
    • AsyncState: Generic async state representation

    #Design

    Type parameters E (event type) and A (attribute value type) enable the same VNode definitions across different platforms:

    PlatformEA
    BrowserDomEventString
    SSRUnitString
    TUITuiEventTuiAttrValue

    #Usage

    #Basic Elements

    // Text node
    let text_node : Node[Unit, String] = text("Hello World")

    // Element node
    let div : Node[Unit, String] = h(
    "div",
    [("class", attr_static("container"))],
    [text("Content")]
    )

    // Nested elements
    let nested : Node[Unit, String] = h("div", [], [
    h("span", [], [text("Title")]),
    h("p", [], [text("Paragraph")])
    ])

    #Dynamic Content

    // Dynamic text (value from getter function)
    let count = @signals.signal(0)
    let dynamic : Node[Unit, String] = text_dyn(fn() { count.get().to_string() })

    // Dynamic attribute
    let cls = @signals.signal("active")
    let dynamic_attr : Node[Unit, String] = h(
    "div",
    [("class", attr_dynamic(fn() { cls.get() }))],
    []
    )

    #Conditional Rendering

    // show: render only when condition is true
    let visible = @signals.signal(true)
    let conditional : Node[Unit, String] = show(
    fn() { visible.get() },
    fn() { text("Visible") }
    )

    // switch: multiple condition branches
    let state = @signals.signal(1)
    let switched : Node[Unit, String] = switch_(
    cases=[
    match_case(when=fn() { state.get() == 1 }, render=fn() { text("State 1") }),
    match_case(when=fn() { state.get() == 2 }, render=fn() { text("State 2") }),
    ],
    fallback=Some(fn() { text("Other") })
    )

    #List Rendering

    let items = @signals.signal(["Apple", "Banana", "Cherry"])
    let list : Node[Unit, String] = for_each(fn() {
    items.get().map(fn(item) { h("li", [], [text(item)]) })
    })

    #Fragment

    // Group multiple nodes without a wrapper element
    let frag : Node[Unit, String] = fragment([
    text("Hello"),
    text(" "),
    text("World")
    ])

    #Component

    fn button(label : String) -> Node[Unit, String] {
    component(fn() {
    h("button", [("class", attr_static("btn"))], [text(label)])
    })
    }

    #Event Handler

    let on_click : EventHandler[DomEvent] = handler(fn(e) {
    // handle event
    })

    let btn : Node[DomEvent, String] = h(
    "button",
    [("onclick", attr_handler(on_click))],
    [text("Click me")]
    )

    #AsyncState

    Represents async operation state:

    pub enum AsyncState[T] {
    Pending
    Success(T)
    Failure(String)
    }

    let state : AsyncState[User] = Pending
    state.is_pending() // true
    state.value() // None

    let loaded : AsyncState[User] = Success(user)
    loaded.value() // Some(user)

    #Node Types

    TypeDescription
    ElementHTML element with tag, attrs, children
    TextStatic text content
    DynamicTextText from getter function
    FragmentGroup of nodes
    ShowConditional rendering
    ForList rendering
    ComponentLazy-evaluated component
    AsyncAsync content with fallback
    ErrorBoundaryError catching wrapper
    SwitchMulti-case conditional
    RawHtmlUnescaped HTML string

    AsyncState

    pub(all) enum AsyncState[T] {
    Pending
    Success(T)
    Failure(String)
    }

    Async state representation - environment independent

    AsyncState::error

    fn[T] AsyncState::error(self : AsyncState[T]) -> String?

    Get error if failure, None otherwise

    AsyncState::is_failure

    fn[T] AsyncState::is_failure(self : AsyncState[T]) -> Bool

    Check if state is failure

    AsyncState::is_pending

    fn[T] AsyncState::is_pending(self : AsyncState[T]) -> Bool

    Check if state is pending

    AsyncState::is_success

    fn[T] AsyncState::is_success(self : AsyncState[T]) -> Bool

    Check if state is success

    AsyncState::value

    fn[T] AsyncState::value(self : AsyncState[T]) -> T?

    Get value if success, None otherwise

    Attr

    pub(all) enum Attr[E, A] {
    VStatic(A)
    VDynamic(() -> A)
    VHandler(EventHandler[E])
    }

    Attribute value that can be static or dynamic (signal-based) Type parameter A represents the attribute value type:
    • Web: A = String (HTML attributes are strings)
    • TUI: A = TuiAttrValue (typed values like Dimension, Color)

    EventHandler

    pub struct EventHandler[E] {
    callback : (E) -> Unit
    }

    Event handler type - newtype wrapper for callback function

    EventHandler::get_callback

    fn[E] EventHandler::get_callback(self : EventHandler[E]) -> ((E) -> Unit)

    Get the callback function

    MatchCase

    pub struct MatchCase[E, A] {
    when : () -> Bool
    render : () -> Node[E, A]
    }

    Match case for Switch - pairs a condition with content

    Node

    pub(all) enum Node[E, A] {
    Element(VElement[E, A])
    Text(String)
    DynamicText(() -> String)
    Fragment(Array[Node[E, A]])
    Show(condition~ : () -> Bool, child~ : () -> Node[E, A])
    For(render~ : () -> Array[Node[E, A]])
    Component(render~ : () -> Node[E, A])
    Async(VAsync[E, A])
    ErrorBoundary(VErrorBoundary[E, A])
    Switch(VSwitch[E, A])
    RawHtml(String)
    }

    Virtual DOM node types Type parameter A represents the attribute value type (see Attr[E, A])

    VAsync

    pub struct VAsync[E, A] {
    render : async () -> Node[E, A]
    fallback : () -> Node[E, A]
    on_error : (Error) -> Node[E, A]?
    }

    Virtual Async node for async rendering with error handling

    VElement

    pub struct VElement[E, A] {
    tag : String
    attrs : Array[(String, Attr[E, A])]
    children : Array[Node[E, A]]
    }

    Virtual element node

    VErrorBoundary

    pub struct VErrorBoundary[E, A] {
    children : () -> Node[E, A] raise
    fallback : (Error, () -> Unit) -> Node[E, A] raise
    }

    Virtual ErrorBoundary node for catching rendering errors

    VSwitch

    pub struct VSwitch[E, A] {
    cases : Array[MatchCase[E, A]]
    fallback : () -> Node[E, A]?
    }

    Virtual Switch node - renders first matching case

    async_

    fn[E, A] async_(render~ : async () -> Node[E, A], fallback~ : () -> Node[E, A], on_error? : (Error) -> Node[E, A]?) -> Node[E, A]

    Create an async VNode with fallback

    attr_dynamic

    fn[E, A] attr_dynamic(getter : () -> A) -> Attr[E, A]

    Create a dynamic attribute value.

    attr_dynamic_style

    fn[E] attr_dynamic_style(getter : () -> String) -> Attr[E, String]

    Create a dynamic style attribute value

    attr_handler

    fn[E, A] attr_handler(handler : EventHandler[E]) -> Attr[E, A]

    Create a handler attribute value

    attr_static

    fn[E, A] attr_static(value : A) -> Attr[E, A]

    Create a static attribute value.

    attr_style

    fn[E] attr_style(style : String) -> Attr[E, String]

    Create a style attribute value (string form)

    component

    fn[E, A] component(render : () -> Node[E, A]) -> Node[E, A]

    Create a component VNode.

    error_boundary

    fn[E, A] error_boundary(children~ : () -> Node[E, A] raise, fallback~ : (Error, () -> Unit) -> Node[E, A] raise) -> Node[E, A]

    Create an error boundary VNode

    for_each

    fn[E, A] for_each(items : () -> Array[Node[E, A]]) -> Node[E, A]

    Create a list VNode.

    fragment

    fn[E, A] fragment(children : Array[Node[E, A]]) -> Node[E, A]

    Create a fragment VNode.
    fn[E, A] h(tag : String, attrs : Array[(String, Attr[E, A])], children : Array[Node[E, A]]) -> Node[E, A]

    Create a VNode element.

    handler

    fn[E] handler(f : (E) -> Unit) -> EventHandler[E]

    Create an event handler from a callback.

    has_dynamic_content

    fn[E, A] has_dynamic_content(attrs : Array[(String, Attr[E, A])]) -> Bool

    Check if element has dynamic content that needs hydration

    match_case

    fn[E, A] match_case(when~ : () -> Bool, render~ : () -> Node[E, A]) -> MatchCase[E, A]

    Create a match case for Switch

    raw_html

    fn[E, A] raw_html(content : String) -> Node[E, A]

    Create a raw HTML VNode (content is not escaped).

    show

    fn[E, A] show(when : () -> Bool, child : () -> Node[E, A]) -> Node[E, A]

    Create a conditional VNode.

    switch_

    fn[E, A] switch_(cases~ : Array[MatchCase[E, A]], fallback? : () -> Node[E, A]?) -> Node[E, A]

    Create a switch VNode - renders first matching case

    text

    fn[E, A] text(content : String) -> Node[E, A]

    Create a text VNode.

    text_dyn

    fn[E, A] text_dyn(content : () -> String) -> Node[E, A]

    Create a dynamic text VNode.

    Powered by MoonBit

    Site sourceReport issuePackagesBuild queueSkillsStatistics

    © 2026 mooncakes.io