dom

package module
v0.13.4 Latest Latest
Warning

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

Go to latest
Published: Aug 6, 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, content).
  • 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().Child(
        html.Span().BindText(c.count).Class("count"),
        html.Button().Text("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 (before render)
Mounted() Imperative: DOM operations (measure, focus, scroll) On every insertion

Init and Mounted are optional — only add them when there is work 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().Text("Save").BindAttrBool("disabled", saving),
    )

Builders take no arguments — children go in Child(...) (variadic) and text in .Text(...). The only exceptions are A(href), Input(type), Option(value, text) and SelectedOption(value, text).

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, html.Div().Child(...))  // toggle subtree visibility via display:none
html.Ul().BindChildren(c.rows)                                          // keyed list

Lifecycle

Init (once) → Render → Insert → Wire bindings & events → children Mounted → own Mounted
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 OnScrollCapture added in v0.13.1

func OnScrollCapture(handler func(scrollTop float64))

OnScrollCapture registra un listener de scroll en FASE DE CAPTURA sobre el documento, de modo que se dispara para CUALQUIER scroller de la página, no solo para la ventana.

Existe porque el evento scroll no burbujea: se dispara únicamente en el elemento que se desplazó. Un shell que quiere reaccionar al scroll de su contenido no puede saber qué descendiente de qué componente es el que realmente desborda, y registrar el listener elemento por elemento lo obligaría a conocer el interior de otros paquetes.

scrollTop es la posición vertical del elemento que disparó el evento. Con varios scrollers en pantalla los valores se intercalan: quien compare posiciones debe tolerarlo con un umbral, no asumir una serie continua.

No hay forma de darlo de baja: es un listener del documento que vive lo que vive la página.

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))

	// OnScrollCapture registra un listener de scroll en fase de captura sobre el
	// documento: se dispara para cualquier scroller de la página. Ver la función
	// de paquete del mismo nombre.
	OnScrollCapture(handler func(scrollTop float64))

	// 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, content Component) *Element

Show is implemented for SSR: the child is always serialized; the container carries display:none when cond is false, matching the WASM initial markup.

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) BindState added in v0.12.1

func (b *Element) BindState(s StateAttr, on *SignalBool) *Element

BindState writes the state's attribute while on is true and removes it when false. This is the ONLY supported way to write a widget state: the value the stylesheet selects on comes from the state itself, so markup and CSS cannot disagree.

Not BindAttrBool: that writes the HTML boolean form (`data-x=""`), which no data-state selector matches. That mistake shipped once and was invisible.

func (*Element) BindStateFunc added in v0.12.1

func (b *Element) BindStateFunc(s StateAttr, fn func() bool) *Element

BindStateFunc is the computed form, for a state derived from more than one signal.

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) SetState added in v0.12.1

func (b *Element) SetState(s StateAttr) *Element

SetState writes the state unconditionally, for markup that is born in it.

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()

	// ScrollIntoView smooth-scrolls the element into view (e.g. to jump a
	// horizontal scroll-snap container to a different panel programmatically —
	// the browser resolves the final resting position against any
	// scroll-snap-align on this element and its container).
	ScrollIntoView()

	// ScrollsX reports whether the element can actually scroll along the inline
	// axis — its content is wider than its box.
	//
	// It exists because ScrollIntoView walks EVERY scrollable ancestor, not just
	// the one the caller had in mind. A component that drives a horizontal strip
	// on narrow screens and lays the same panels out side by side on wide ones
	// has to know which it is looking at: on the wide layout the nearest
	// scroller is somebody else's, and scrolling it moves the whole application.
	ScrollsX() bool
}

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 StateAttr added in v0.12.1

type StateAttr interface {
	Key() string
	Value() string
}

StateAttr is anything that names a data-state attribute and the value the stylesheet selects on. widget.State satisfies it; nothing else needs to.

Declared here rather than imported so that dom keeps no dependency on the widget vocabulary — the same seam Class.AsAttr already uses in the other direction.

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