npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@dadaki/editor

v1.0.0-beta.3

Published

Dadaki — a high-performance, embeddable in-browser vector graphics editor (CanvasKit + Rust/WASM).

Readme

@dadaki/editor

A high-performance, embeddable in-browser vector graphics editor. The rendering + geometry core is compiled from Rust to WebAssembly and drawn with CanvasKit (Skia); the editor UI (tools, layers, properties, rulers, multi-document tabs) is TypeScript.

It ships as a single embeddable component: give it a DOM container and a CanvasKit instance, and it builds the whole editor inside that container.

Install

npm install @dadaki/editor [email protected]

canvaskit-wasm is a peer dependency: the host loads CanvasKit and passes the instance in, so there is exactly one copy of Skia on the page.

From inside this repository, use the workspace instead:

// package.json
{
  "dependencies": {
    "@dadaki/editor": "workspace:*" // or "link:../vector-editor/packages/editor"
  }
}

What the package ships, and what your bundler needs

The package is distributed as TypeScript source — exports points at src/index.ts — together with the compiled wasm engine in engine/pkg/. Your build compiles it along with your own code, which is what lets you follow a stack trace into the editor and what keeps the wasm a single artifact rather than one copy per consumer.

That means the package expects a Vite-class bundler:

  • src/index.ts imports the editor chrome as ./chrome.html?raw, so the build must understand the ?raw suffix (Vite does; other bundlers need a raw loader).
  • It must resolve .wasm as an asset — engine/pkg/engine.js loads engine_bg.wasm relative to its own module URL.
  • If you typecheck the source, add "vite/client" to compilerOptions.types, which supplies the ?raw module declaration.

Exclude the package from dependency pre-bundling. Vite's dev-mode optimizer pre-bundles anything under node_modules without running the plugins the source relies on, so vite dev fails on the ?raw import unless you opt out. vite build does not go through the optimizer and works either way — which means skipping this step gives you a project that builds but will not start:

// vite.config.ts
export default defineConfig({
    optimizeDeps: { exclude: ['@dadaki/editor'] },
});

Usage

import { createEditor } from '@dadaki/editor';
import '@dadaki/editor/style.css';

// The host loads CanvasKit (via the canvaskit.js <script> or the npm package)
// and passes the instance in. Optionally load the `lucide` global for icons.
const canvasKit = await CanvasKitInit({ locateFile: (f) => `/${f}` });

const editor = await createEditor(document.getElementById('app')!, {
  canvasKit,
  // optional: receive analytics events
  analyticsSink: (name, props) => console.log('event', name, props),
});

// The handle exposes the pieces a host legitimately needs:
editor.documentManager; // open/create/rename/close documents
editor.activeDocument(); // currently active document
editor.destroy();        // tear down + clear the container

createEditor(container, options)

| Option | Type | Description | | --------------- | --------------- | ------------------------------------------------------- | | canvasKit | CanvasKit | Required. A loaded CanvasKit instance. | | analyticsSink | AnalyticsSink | Optional. Receives (eventName, props) for every event.|

Returns an EditorHandle with scene, ui, input, renderer, documentManager, fileService, activeDocument(), stress(), and destroy().

Host contract

  • The host loads CanvasKit and passes the instance to createEditor — the library never fetches it. (canvaskit.js + canvaskit.wasm are served by the host; see the @dadaki/app demo shell.)
  • The host may load the lucide icon global (<script src=".../lucide">). Icons are optional chrome; the editor works without it.
  • The library owns everything inside container. It never imports an analytics or backend SDK, reads environment variables, or reaches into host-page structure.
  • Documents persist locally (IndexedDB) by default — there is no backend dependency. Cloud sync is layered on top by a host app (see @dadaki/cloud).
  • A host that puts more than one session in the same document must assign each a distinct siteId (see below). Single-user hosts can ignore it.

Object identity (siteId)

Node ids are partitioned by site:

id = (siteId << 22) | counter        siteId 0…1023, counter 0…4_194_303

A "site" is one editing session, not one user and not one account — two tabs are two sites. Give concurrent sessions different site ids and they can create objects at the same time without ever producing the same id, which is the prerequisite for merging their edits at all.

const editor = await createEditor(el, { canvasKit, siteId: 3 });
editor.setSiteId(7);   // if the host learns its site later (e.g. a presence handshake)

Three properties worth knowing, each covered by tests in engine/src/lib.rs (mod identity_tests):

  • siteId: 0 is the legacy numbering. make_id(0, n) === n, so documents written before sites existed are bit-identical and keep allocating where they left off. This is why the default is 0 and why single-user hosts see no change.
  • Site ids are reusable. On load, an engine resumes its own site's counter past the highest id that site already has in the document, so a later session reusing site 3 cannot reissue ids the earlier one created. Uniqueness is only required among sessions that overlap in time — which is what keeps 10 bits enough.
  • Undo never recycles an id. Undo rewinds the serialized counter (so a snapshot round-trip is byte-identical) but allocation is floored by a session watermark. An id retired by undo is not handed to a different object — a peer may already know that id, and reissuing it would give two distinct objects one identity.

Changing siteId mid-session is safe: existing objects keep their ids, and the counter for the new site resumes from what the document already contains.

Limitations (v1)

The injected chrome uses stable element ids, so one editor instance per document is supported. Full container-scoped, multi-instance isolation is a planned hardening step.

License

MIT © Dadaki