Skip to content

Latest commit

 

History

History

wasm-api-dom

wasm-api-dom

npm version npm downloads Twitter Follow

This project is part of the @thi.ng/umbrella monorepo.

About

Browser DOM bridge API for hybrid TypeScript & Zig applications. This is a support package for @thi.ng/wasm-api.

This package provides a minimal, but already quite usable TypeScript core API and related Ziglang bindings for UI & DOM creation/manipulation via WebAssembly.

Current key features for the Zig (WASM) side:

  • ID handle management for WASM created DOM elements & listeners
  • Declarative & imperative DOM tree creation
  • Canvas element creation (with HDPI support, see @thi.ng/adapt-dpi)
  • Attribute setters/getters (string, numeric, boolean)
  • .innerHTML & .innerText setters
  • Event handlers, event types (see generated types in api.zig for details):
    • drag 'n drop (WIP)
    • focus
    • input
    • key
    • mouse
    • pointer
    • scroll
    • touch
    • wheel
  • Fullscreen API wrapper
  • (Browser) window info queries

ID handles

Since DOM related resources created on the JS side cannot be returned to the WASM module directly, the bridge API caches those on the host side and uses managed ID (integer) handles to exchange them. These IDs can then be used in subsequent API calls to refer to certain DOM elements, listeners etc.

For element & event related functionality the following IDs are reserved:

  • -1 the browser window itself
  • 0 document.head
  • 1 document.body

All are exposed in the Zig module as window, head, body constants to help avoiding magic numbers in userland code.

DOM tree creation

Single DOM elements and entire element trees can be created via the createElement() function:

const dom = @import("wasmdom");

const handle = dom.createElement(&.{
	// element tag
	.tag = "div",
	// CSS classes
	.class = "bg-red white",
	// parent element ID (here `document.body`)
	.parent = dom.body,
	// optional child element specs
	.children = &.{
		.{
			.tag = "h1",
			// text content for this element
			.text = "OMG, it works!",
			// nested childen
			.children = &.{
				.{
					.tag = "span",
					.text = "(recursive DOM creation FTW!)",
					.class = "bg-yellow black",
				},
			},
		},
	},
});

The CreateElementOpts struct has some additional options and more are planned. All WIP!

Event listeners

Once a DOM element has been created, event listeners can be attached to it. All listeners take two arguments: an Event struct and an optional opaque pointer for passing arbitrary user context.

This small Zig example defines a click counter button component:

const wasm = @import("wasmapi");
const dom = @import("wasmdom");

/// Simple click counter component
const Counter = struct {
	/// element ID
    el: i32,
	/// listener ID
    listener: u16,
	/// click counter
    clicks: usize,

    const Self = @This();

	/// Create & setup DOM element w/ listener
    pub fn init(self: *Self, parent: i32) !void {
        self.clicks = 0;
		// create DOM button element
        self.el = dom.createElement(&.{
            .tag = "button",
            .text = "click me!",
            .parent = parent,
        });
		// add click event listener w/ user context arg
        self.listener = try dom.addListener(self.el, "click", &.{
            .callback = onClick,
            .ctx = self,
        });
    }

	/// Event listener & state update
    fn onClick(_: *const dom.Event, raw: ?*anyopaque) void {
		// safely cast raw pointer
        if (wasm.ptrCast(*Self, raw)) |self| {
            self.clicks += 1;
			// format new button label
            var buf: [16]u8 = undefined;
            var label = std.fmt.bufPrint(&buf, "clicks: {d}", .{self.clicks}) catch return;
			// update DOM element
            dom.setInnerText(self.el, label);
        }
    }
};

var counter: Counter = undefined;

export fn start() void {
	// instantiate counter
	counter.init(dom.body) catch |e| @panic(@errorName(e));
}

Status

ALPHA - bleeding edge / work-in-progress

Search or submit any issues for this package

Installation

yarn add @thi.ng/wasm-api-dom

ES module import:

<script type="module" src="https://cdn.skypack.dev/@thi.ng/wasm-api-dom"></script>

Skypack documentation

For Node.js REPL:

# with flag only for < v16
node --experimental-repl-await

> const wasmApiDom = await import("@thi.ng/wasm-api-dom");

Package sizes (gzipped, pre-treeshake): ESM: 3.88 KB

Dependencies

Usage examples

Several demos in this repo's /examples directory are using this package.

A selection:

Screenshot Description Live demo Source
Zig-based DOM creation & canvas drawing app Demo Source

API

Generated API docs

For now, please see the package docs, source code comments (TS & Zig) and the various comments in the zig-canvas example project for further reference and usage patterns! Thank you!

Authors

Maintainer

Contributors

If this project contributes to an academic publication, please cite it as:

@misc{thing-wasm-api-dom,
  title = "@thi.ng/wasm-api-dom",
  author = "Karsten Schmidt and others",
  note = "https://thi.ng/wasm-api-dom",
  year = 2022
}

License

© 2022 Karsten Schmidt // Apache Software License 2.0