Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@thi.ng/wasm-api-dom

Package Overview
Dependencies
Maintainers
0
Versions
131
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@thi.ng/wasm-api-dom

Browser DOM bridge API for hybrid TypeScript & WASM (Zig) applications

  • 0.11.97
  • Source
  • npm
  • Socket score

Version published
Weekly downloads
97
increased by646.15%
Maintainers
0
Weekly downloads
 
Created
Source

@thi.ng/wasm-api-dom

npm version npm downloads Mastodon Follow

[!NOTE] This is one of 188 standalone projects, maintained as part of the @thi.ng/umbrella monorepo and anti-framework.

🚀 Please help me to work full-time on these projects by sponsoring me on GitHub. Thank you! ❤️

About

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

This package provides a small TypeScript core API and related Ziglang bindings for UI & DOM creation/manipulation via WebAssembly.

Current key features for the Zig (WASM) side:

  • Fully declarative or imperative DOM tree creation & manipulation
  • ID handle management for WASM created DOM elements & listeners
  • 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

Module initialization

Before the Zig WASM API module can be used, it must be initialized (automatically or manually) with a standard std.mem.Allocator. The current recommended pattern looks something like this:

const std = @import("std");
const wasm = @import("wasm-api");
const dom = @import("wasm-api-dom");

// expose thi.ng/wasm-api core API (incl. panic handler & allocation fns)
pub usingnamespace wasm;

// allocator, also exposed & used by JS-side WasmBridge & DOM module
// see further comments in:
// https://github.com/thi-ng/umbrella/blob/develop/packages/wasm-api/zig/lib.zig
// https://github.com/thi-ng/umbrella/blob/develop/packages/wasm-api-dom/zig/events.zig
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
pub const WASM_ALLOCATOR = gpa.allocator();

/// Since various initialization functions can return errors
/// we're bundling them all in a single fn, which is then called by start()
/// and so only needs one code site for error handling
fn init() !void {
    // all WASM API modules auto-initialize themselves if the root source
    // file exposes a `WASM_ALLOCATOR`, otherwise you'll have to initialize manually:
    // try dom.init(customAllocator);

    // ...
}

/// Main entry point
export fn start() void {
    init() catch |e| @panic(@errorName(e));
}

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

These 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 (incl. event handler setup and custom attributes) can be created via the createElement() function.

Attribute definitions need to be wrapped using dom.attribs() and child elements via dom.children(), as shown here:

const wasm = @import("wasm-api");
const dom = @import("wasm-api-dom");

const Attrib = dom.Attrib;

// snippet taken from the zig-todo-list example project

const handle = dom.createElement(&.{
    // element name
    .tag = "div",
    // CSS classes
    .class = "flex flex-column mb3",
    // nested child elements
    .children = dom.children(&.{
        .{ .tag = "h3", .text = "Add new task" },
        .{
            .tag = "input",
            // element's ID attribute
            .id = "newtask",
            // attribute & event listener definitions
            .attribs = dom.attribs(&.{
                Attrib.string("placeholder", "What needs to be done?"),
                Attrib.flag("autofocus", true),
                // event listener setup:
                // last arg is optional opaque pointer to arbitrary user state/context
                Attrib.event("keydown", onKeydown, &STATE),
                Attrib.event("input", onInput, null),
            }),
        },
        .{
            .tag = "button",
            // Element .innerText content
            .text = "Add Task",
            .attribs = dom.attribs(&.{
                Attrib.event("click", onAddTask, null),
            }),
        },
    }),
});

fn onInput(e: *const dom.Event, _: ?*anyopaque) callconv(.C) void {
    wasm.printStr(e.body.input.getValue());
}

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

HTML canvas support

A separate function (createCanvas()) with similar options exist for <canvas> elements.

Also see the thi.ng/wasm-api-canvas sibling package for working with canvases...

Attribute creation & accessors

As already shown above, attributes can be provided as part of the CreateElementOpts and/or accessed imperatively:

Zig example:

// creating & configuring an <input type="range"> element
_ = dom.createElement(&.{
    .tag = "input",
    .parent = dom.body,
    // optional attrib declarations
    .attribs = dom.attribs(&.{
        // string attrib
        dom.Attrib.string("type", "range"),
        // numeric attribs
        dom.Attrib.number("min", 0),
        dom.Attrib.number("max", 100),
        dom.Attrib.number("step", 10),
        dom.Attrib.number("value", 20),
        // boolean attrib (only will be created if true)
        dom.Attrib.flag("disabled", true),
    }),
});

The following accessors are provided (see /zig/lib.zig for documentation):

  • getStringAttrib() / setStringAttrib()
  • getNumericAttrib() / setNumericAttrib()
  • getBooleanAttrib() / setBooleanAttrib()

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.

A more advanced version of the following click counter button component can be seen in action in the zig-counter example project. Also check other supplied Zig examples for more realworld usage examples.

IMPORTANT: In Zig v0.12+ all event handlers must explicitly specify callconv(.C) See docs for more reference.

const wasm = @import("wasm-api");
const dom = @import("wasm-api-dom");

/// Simple click counter component
const Counter = struct {
    elementID: i32,
    clicks: usize,
    step: usize,

    const Self = @This();

    /// Initialize internal state & DOM element w/ listener
    pub fn init(self: *Self, parent: i32, step: usize) !void {
        self.clicks = 0;
        self.step = step;
        // create DOM button element
        self.elementID = dom.createElement(&.{
            .tag = "button",
            // Tachyons CSS class names
            .class = "db w5 ma2 pa2 tc bn",
            .text = "click me!",
            .parent = parent,
            .attribs = dom.attribs(&.{
                // define & add click event listener w/ user context arg
                dom.Attrib.event("click", onClick, self),
            }),
        });
    }

    fn update(self: *const Self) void {
        // format new button label
        var buf: [32]u8 = undefined;
        var label = std.fmt.bufPrintZ(&buf, "clicks: {d:0>4}", .{self.clicks}) catch return;
        // update DOM element
        dom.setInnerText(self.elementID, label);
    }

    /// event listener & state update
    fn onClick(_: *const dom.Event, raw: ?*anyopaque) callconv(.C) void {
        // safely cast raw pointer
        if (wasm.ptrCast(*Self, raw)) |self| {
            self.clicks += self.step;
            self.update();
        }
    }
};

Status

ALPHA - bleeding edge / work-in-progress

Search or submit any issues for this package

Installation

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

ESM import:

import * as wad from "@thi.ng/wasm-api-dom";

Browser ESM import:

<script type="module" src="https://esm.run/@thi.ng/wasm-api-dom"></script>

JSDelivr documentation

Package sizes (brotli'd, pre-treeshake): ESM: 4.25 KB

Dependencies

Usage examples

Several projects in this repo's /examples directory are using this package:

ScreenshotDescriptionLive demoSource
Zig-based DOM creation & canvas drawing appDemoSource
Zig-based 2D multi-behavior cellular automataDemoSource
Simple Zig/WASM click counter DOM componentDemoSource
Zig-based To-Do list, DOM creation, local storage task persistenceDemoSource

API

Generated API docs

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

Authors

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 - 2024 Karsten Schmidt // Apache License 2.0

Keywords

FAQs

Package last updated on 16 Jul 2024

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc