dom

package module
v0.11.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 30, 2026 License: MIT Imports: 1 Imported by: 10

README

tinywasm/dom

Ultra-minimal DOM & reactivity toolkit for Go (TinyGo WASM-optimized).

tinywasm/dom provides a type-safe, fine-grained reactive engine over the browser DOM for TinyGo/WASM. State lives in typed Signals; changing a signal patches only the bound DOM node — no Virtual DOM, no manual Update() calls, no re-renders.

Features

  • Fine-Grained Reactivity: SignalString / SignalBool / SignalNodes — O(1) surgical patches that preserve focus and IME composition.
  • Auto-tracking: BindTextFunc / DeriveString discover dependencies automatically — no explicit dep lists.
  • Typed builder: Text, Child, Attr, Class, Set(kv ...fmt.KeyValue) — no Add(...any).
  • Two-method contract: Render() *Element (pure, once per mount) + optional Init(ctx dom.Ctx) (side effects, once ever).
  • Keyed lists & conditional subtrees: BindChildren(SignalNodes) + Show(cond, renderFn).
  • No Virtual DOM: Zero diffing; nodes are never replaced unless structure truly changes.
  • TinyGo Optimized: Zero stdlib; tinywasm/fmt for logs; slices over maps; <500KB WASM binaries.
  • Isomorphic: same Render() produces correct SSR HTML on backend and live WASM on frontend.

Installation

go get github.com/tinywasm/dom

Quick Start

import (
    dom "github.com/tinywasm/dom"
    "github.com/tinywasm/fmt"
    "github.com/tinywasm/html"
)

type Counter struct {
    dom.Element
    n     int
    count *dom.SignalString
}

func (c *Counter) Init(ctx dom.Ctx) {
    c.count = dom.NewString("0")
}

func (c *Counter) Render() *dom.Element {
    return html.Div(
        html.Span().BindText(c.count).Class("count"),
        html.Button("Increment").On("click", func(e dom.Event) {
            c.n++
            c.count.Set(fmt.Sprint(c.n))
        }),
    )
}

func main() {
    d := dom.New(...)
    d.Render("app", &Counter{})
}

Component Contract

Method Role Cardinality
Render() *Element Pure: state → structure, no side effects Once per mount
Init(ctx dom.Ctx) Imperative: create signals, load storage, start timers Exactly once

Init is optional — only add it when there is setup to do.

Signals

// String cell — UI text, attr, input state
name := dom.NewString("World")
name.Get()           // "World"
name.Set("Alice")    // notifies all bindings
name.Update(func(v string) string { return v + "!" })

// Bool cell — class/attr toggles, Show conditions
active := dom.NewBool(false)
active.Toggle()

// List of rendered rows — keyed reconcile
rows := dom.NewNodes(elem1, elem2)
rows.Set(newRows)

// Derived (auto-tracking — no deps list)
full := dom.DeriveString(func() string { return first.Get() + " " + last.Get() })

Element Builder

html.Div().
    Class("card").
    Attr("role", "region").
    Child(
        html.Span().BindText(name),
        html.Input("text").Bind(name),           // two-way
        html.Button("Save").BindAttrBool("disabled", saving),
    )

Binding methods:

Method DOM target
.BindText(s *SignalString) textContent
.BindAttr(name, s) attribute value
.BindClass(class, on) class toggle
.BindAttrBool(name, on) boolean attribute (disabled, checked…)
.Bind(s) two-way <input>/<textarea>
.BindChildren(s *SignalNodes) keyed child list
.BindTextFunc(fn) computed text (auto-tracking)
.Autofocus() focus on first appearance

Structural:

dom.Show(visible, func() *dom.Element { return html.Div(...) })  // mount/unmount subtree
html.Ul().BindChildren(c.rows)                                    // keyed list

Lifecycle

Init (once) → Render → wire bindings & events
signal.Set  → patch bound node (O(1))
unmount     → run OnCleanup + unsubscribe signals

Mount Point

Always "app", never "body"Render("body", ...) overwrites innerHTML and destroys the SVG sprite injected by tinywasm/assetmin.

Dev Mode

dom.SetDevMode(true) // enabled at runtime; default false (production no-op)

When on:

  • Reactive trace: logs signal.Set → patch #node-id
  • BindChildren warns on duplicate/empty keys
  • Nil signal / non-input .Bind / pointer-embedded Element emit warnings instead of panicking

Documentation

License

MIT

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Append added in v0.2.0

func Append(parentID string, component Component) error

Append injects a component AFTER the last child of the parent element.

func GetDocumentAttr added in v0.9.0

func GetDocumentAttr(_ string) string

GetDocumentAttr returns an empty string on the backend.

func GetHash added in v0.0.11

func GetHash() string

GetHash gets the current hash.

func Log added in v0.0.7

func Log(v ...any)

Log provides logging functionality.

func OnHashChange added in v0.0.11

func OnHashChange(handler func(hash string))

OnHashChange registers a hash change listener.

func Render added in v0.2.0

func Render(parentID string, component Component) error

Render injects a component into a parent element.

func SetDevMode added in v0.11.0

func SetDevMode(on bool)

SetDevMode enables or disables development mode features.

func SetDocumentAttr added in v0.9.0

func SetDocumentAttr(_, _ string)

SetDocumentAttr is a no-op on the backend.

func SetHash added in v0.0.11

func SetHash(hash string)

SetHash sets the current hash.

func SetLog added in v0.0.7

func SetLog(log func(v ...any))

SetLog sets the logging function.

Types

type Component

type Component interface {
	GetID() string
	SetID(id string)
	String() string
	Children() []Component
}

Component is the minimal interface for components. All components must implement this for both SSR (backend) and WASM (frontend).

NOTE: If your struct embeds Element, embed it as a VALUE, not a pointer:

type MyComponent struct {
  Element       // ✅ Correct — never nil
  // NOT: *Element // ❌ Wrong — nil pointer causes panic in renderToHTML
}

This is because renderToHTML calls GetID() on every Component child before checking ViewRenderer.

type Ctx added in v0.11.0

type Ctx interface {
	OnCleanup(fn func())
}

Ctx is handed to the Init hook. Register teardown for async resources (timers, websockets).

type DOM

type DOM interface {
	// Render injecta un componente en un elemento padre.
	// 1. Llama a componente.Init(ctx) si existe (una sola vez)
	// 2. Llama a componente.Render() para obtener el árbol de elementos
	// 3. Inyecta el HTML resultante y enlaza bindings y eventos
	Render(parentID string, component Component) error

	// Append injecta un componente DESPUÉS del último hijo del elemento padre.
	// Útil para listas dinámicas.
	Append(parentID string, component Component) error

	// OnHashChange registra un listener para cambios en el hash de la URL.
	OnHashChange(handler func(hash string))

	// GetHash devuelve el hash actual de la URL (ej. "#help").
	GetHash() string

	// SetHash actualiza el hash de la URL.
	SetHash(hash string)

	// Get retrieves an element by ID.
	Get(id string) (Reference, bool)

	// Log provides logging functionality using the log function passed to New.
	Log(v ...any)
}

DOM is the main entry point for interacting with the browser. It is designed to be injected into your components.

type Element

type Element struct {
	// contains filtered or unexported fields
}

Element represents a DOM element in the fluent Element API.

func NewElement added in v0.10.1

func NewElement(tag string) *Element

NewElement creates an Element with the given HTML tag. Used by tinywasm/html, tinywasm/svg, tinywasm/image to build elements.

func Show added in v0.11.0

func Show(cond *SignalBool, render func() *Element) *Element

Show is implemented for SSR.

func (*Element) Attr added in v0.2.3

func (b *Element) Attr(key, val string) *Element

Attr sets an attribute on the element.

func (*Element) Autofocus added in v0.11.0

func (b *Element) Autofocus() *Element

Autofocus marks the element to be focused when it first appears.

func (*Element) Bind added in v0.11.0

func (b *Element) Bind(s *SignalString) *Element

Bind provides two-way binding for <input> and <textarea>.

func (*Element) BindAttr added in v0.11.0

func (b *Element) BindAttr(name string, s *SignalString) *Element

BindAttr links an attribute to a SignalString.

func (*Element) BindAttrBool added in v0.11.0

func (b *Element) BindAttrBool(name string, on *SignalBool) *Element

BindAttrBool toggles a boolean attribute (disabled, checked, etc.) based on a SignalBool.

func (*Element) BindAttrBoolFunc added in v0.11.0

func (b *Element) BindAttrBoolFunc(name string, fn func() bool) *Element

BindAttrBoolFunc toggles a boolean attribute based on a computed boolean.

func (*Element) BindAttrFunc added in v0.11.0

func (b *Element) BindAttrFunc(name string, fn func() string) *Element

BindAttrFunc links an attribute to a computed string.

func (*Element) BindChildren added in v0.11.0

func (b *Element) BindChildren(s *SignalNodes) *Element

BindChildren links a container's children to a SignalNodes.

func (*Element) BindClass added in v0.11.0

func (b *Element) BindClass(class string, on *SignalBool) *Element

BindClass toggles a class based on a SignalBool.

func (*Element) BindClassFunc added in v0.11.0

func (b *Element) BindClassFunc(class string, fn func() bool) *Element

BindClassFunc toggles a class based on a computed boolean.

func (*Element) BindText added in v0.11.0

func (b *Element) BindText(s *SignalString) *Element

BindText links the element's textContent to a SignalString.

func (*Element) BindTextFunc added in v0.11.0

func (b *Element) BindTextFunc(fn func() string) *Element

BindTextFunc links the element's textContent to a computed string.

func (*Element) Child added in v0.11.0

func (b *Element) Child(c ...Component) *Element

Child adds one or more elements or components as children.

func (*Element) Children added in v0.2.3

func (b *Element) Children() []Component

Children returns the component's children (components only).

func (*Element) Class added in v0.2.3

func (b *Element) Class(class ...string) *Element

Class adds a class to the element.

func (*Element) For added in v0.8.0

func (b *Element) For(other *Element) *Element

For sets the for= attribute pointing to other's ID, auto-generating other's ID if it has none. Use for label/input pairing and aria-* references.

func (*Element) GetID added in v0.2.3

func (b *Element) GetID() string

GetID returns the element's ID.

func (*Element) ID added in v0.2.3

func (b *Element) ID(id string) *Element

ID sets the ID of the element.

func (*Element) Key added in v0.11.0

func (b *Element) Key(key string) *Element

Key sets a stable identity for keyed reconciliation in BindChildren.

func (*Element) NoCloseTag added in v0.10.1

func (b *Element) NoCloseTag() *Element

NoCloseTag marks the element as self-closing (no closing tag rendered). Use for void HTML elements: br, hr, img, input, link, meta, etc.

func (*Element) On

func (b *Element) On(t string, h func(Event)) *Element

On adds a generic event handler.

func (*Element) Render added in v0.2.3

func (b *Element) Render(parentID string) error

Render renders the element to the parent. This is a terminal operation.

func (*Element) Set added in v0.11.0

func (b *Element) Set(kv ...fmt.KeyValue) *Element

Set applies multiple attributes or classes at once using KeyValue pairs.

func (*Element) SetID added in v0.2.3

func (b *Element) SetID(id string)

SetID sets the element's ID.

func (*Element) String added in v0.10.0

func (b *Element) String() string

String serializes the element tree to its string representation.

func (*Element) Text added in v0.2.3

func (b *Element) Text(text string) *Element

Text adds a text node child.

type Event

type Event interface {
	// PreventDefault prevents the default action of the event.
	PreventDefault()
	// StopPropagation stops the event from bubbling up the DOM tree.
	StopPropagation()
	// TargetValue returns the value of the event's target element.
	// Useful for input, textarea, and select elements.
	TargetValue() string
	// TargetID returns the ID of the event's target element.
	TargetID() string
	// TargetChecked returns the checked status of the event's target element.
	// Useful for checkbox and radio input elements.
	TargetChecked() bool
}

Event represents a DOM event.

type Reference added in v0.2.3

type Reference interface {

	// GetAttr retrieves an attribute value.
	GetAttr(key string) string

	// Value returns the current value of an input/textarea/select.
	Value() string

	// SetValue sets element.value (inputs, textarea, select).
	SetValue(value string)

	// SetAttr calls element.setAttribute(key, value).
	// Use empty string for boolean attributes (e.g., SetAttr("disabled", "")).
	SetAttr(key, value string)

	// RemoveAttr calls element.removeAttribute(key).
	RemoveAttr(key string)

	// SetText sets element.textContent.
	// Safe for plain text — does not parse HTML.
	SetText(text string)

	// Checked returns the current checked state of a checkbox or radio button.
	Checked() bool

	// On registers a generic event handler (e.g., "click", "change", "input", "keydown").
	On(eventType string, handler func(event Event))

	// Focus sets focus to the element.
	Focus()
}

Reference represents a reference to a DOM node. It provides methods for reading and interaction.

func Get added in v0.0.7

func Get(id string) (Reference, bool)

Get retrieves an element by ID.

type SignalBool added in v0.11.0

type SignalBool struct {
	// contains filtered or unexported fields
}

SignalBool — same shape for class/attr toggles and Show conditions.

func DeriveBool added in v0.11.0

func DeriveBool(compute func() bool) *SignalBool

func NewBool added in v0.11.0

func NewBool(v bool) *SignalBool

func (*SignalBool) Get added in v0.11.0

func (s *SignalBool) Get() bool

func (*SignalBool) Set added in v0.11.0

func (s *SignalBool) Set(v bool)

func (*SignalBool) Toggle added in v0.11.0

func (s *SignalBool) Toggle()

type SignalNodes added in v0.11.0

type SignalNodes struct {
	// contains filtered or unexported fields
}

SignalNodes is an observable list of rendered rows. No generics; the component builds the Elements.

func NewNodes added in v0.11.0

func NewNodes(v ...*Element) *SignalNodes

func (*SignalNodes) Get added in v0.11.0

func (s *SignalNodes) Get() []*Element

func (*SignalNodes) Set added in v0.11.0

func (s *SignalNodes) Set(v []*Element)

type SignalString added in v0.11.0

type SignalString struct {
	// contains filtered or unexported fields
}

SignalString is an observable string cell. UI text/attr/input state lives here. Explicit Get/Set.

func DeriveString added in v0.11.0

func DeriveString(compute func() string) *SignalString

DeriveString / DeriveBool: read-only computed cells. Re-run automatically when any signal the closure READS changes — no deps argument.

func NewString added in v0.11.0

func NewString(v string) *SignalString

func (*SignalString) Get added in v0.11.0

func (s *SignalString) Get() string

func (*SignalString) Set added in v0.11.0

func (s *SignalString) Set(v string)

func (*SignalString) Update added in v0.11.0

func (s *SignalString) Update(fn func(string) string)

type ViewRenderer added in v0.2.0

type ViewRenderer interface {
	Render() *Element
}

ViewRenderer returns a Node tree for declarative UI.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL