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

@weasel-js/core

v0.8.0

Published

Domain-agnostic 2D scene graph primitives for React: viewport math, drag/resize/insert/clone interactions, layered canvas rendering.

Downloads

1,092

Readme

weasel

Domain-agnostic 2D scene-graph hooks for React, rendered on WebGL2. Bring your own object type and pose shape; weasel handles the viewport math, pointer gestures (move / resize / insert / clone / area-select / text edit), layered scene rendering, an op-based undo/redo model, and a stack of selection-driven action hooks (delete, duplicate, nudge, group, clipboard, undo/redo, …) wired to keyboard shortcuts when you ask.

Built for diagram editors, sketch tools, schematic editors, scene composers — anything where "objects on a canvas the user can grab, move, and arrange" is the substrate.

Pre-1.0: the API surface (paths, nesting, per-subobject units) is still settling. Expect breaking changes between minor versions until 1.0.

Features

  • Pointer gestures: move, resize, rotate, insert, clone, area-select, text edit
  • Op-based scene mutation with undo/redo and coalescing
  • Selection-driven action hooks (delete, duplicate, nudge, reorder, clipboard, …)
  • Centralized actions registry with default keybindings (@experimental)
  • Layered canvas rendering with debug overlays
  • Path poses, rect poses, rotated poses; first-class compound paths
  • Viewport with zoom/pan tools, momentum, and boundary clamping
  • Detached read-only scene views and navigation minimap (<SceneViewCanvas>, <MinimapCanvas>)
  • WebGL2 renderer with MSDF text, gradients, patterns, and per-vertex colors
  • Custom fragment shaders via registerProgram (@experimental)

Install

npm install @weasel-js/core react

react is a peer dependency (>=18).

How it fits together

Every interaction takes a small, narrow adapter — a few methods that read the current scene and apply ops back. The kit doesn't own your scene; it asks. That keeps it agnostic to whether your scene lives in React state, Zustand, Redux, or a CRDT.

import { useMove, useDelete, createHistory, snap, gridSnapStrategy } from '@weasel-js/core';

const history = createHistory(adapter);

// Drag-to-move with snapping, history, and parent reparenting:
const move = useMove(adapter, {
  behaviors: [snap(gridSnapStrategy(20))],
});

// Selection-aware delete with Backspace/Delete bound:
useDelete(adapter, { bindKeyboard: true });

Most apps don't call the gesture hooks directly — <SceneCanvas> owns useMove / useResize / useInsert / useAreaSelect / useSelection internally. Drop in a useScene() tree and a layers map and you get click-to-select, drag-to-move, corner-handle-resize, and an tool="insert" mode for free.

See the live demo for a full working example: https://orochi235.github.io/weasel/

Demo

Live demo: https://orochi235.github.io/weasel/

Text rendering

Text is rendered via MSDF atlases. Register fonts before the first paint:

import { registerFont } from '@weasel-js/core';

await registerFont('Inter', { weight: 400 }, '/fonts/Inter-400.json', '/fonts/Inter-400.png');

Core doesn't ship a prebuilt atlas — bake one with npm run gen:font -- <font.ttf> --name Inter-400 --out public/fonts (see scripts/gen-font.ts) and serve the resulting .json/.png pair. The hud package ships its own bundled Inter atlas for consumers who don't need custom fonts.

Actions registry

<ActionsProvider> wires a single keydown listener and dispatches to a registry of Action descriptors. <SceneCanvas> auto-mounts a provider (if no parent provider exists) and registers default actions for select-all, escape, duplicate, nudge, and reorder, all derived from the scene/selection/adapter it already owns.

import { ActionsProvider, SceneCanvas } from '@weasel-js/core';

<ActionsProvider>
  <SceneCanvas
    scene={scene}
    selection={selection}
    actions={{
      selectAll: null,                              // disable the default Cmd+A
      copy: {                                       // add an app-specific action
        label: 'Copy',
        defaultBinding: { key: 'c', mod: true },
        run: () => clipboard.copy(selection.current),
      },
    }}
  />
</ActionsProvider>

The actions prop accepts null (disable all defaults), a partial override of any default by id, or a full Action descriptor for new ids. Consumers that need finer control can call individual hooks (useSelectAll, useEscape, useDuplicate, useNudge, useReorder) which auto-register into a parent provider when present and fall back to direct keybindings when not.

Custom shaders (@experimental)

The renderer supports kind: 'shader' DrawCommands for layers that want a custom fragment shader. Register the program once, then emit a draw command with uniforms and bounds:

import { registerProgram, registerTexture } from '@weasel-js/core';

const voronoi = registerProgram(
  'voronoi',
  /* vert */ null,                            // null → use the default quad prelude
  /* frag */ `
    precision highp float;
    varying vec2 v_uv;
    uniform float u_time;
    void main() { /* … */ }
  `,
);

// Inside a RenderLayer.draw, return a tree of DrawCommands:
return {
  kind: 'shader',
  program: voronoi,
  uniforms: { u_time: performance.now() / 1000 },
  bounds: { x: 0, y: 0, w: 256, h: 256 },
};

Uniforms support number, vec2..4, mat3, mat4, and TextureHandle (from registerTexture). The vertex prelude exposes v_uv, v_screen, and v_world varyings. API may change before v2.

Subpath imports

For tree-shaking and clarity, hook-specific helpers are scoped:

import { snapToGrid } from '@weasel-js/core/move';
import { snapToGrid, clampMinSize } from '@weasel-js/core/resize';
import { snapToGrid } from '@weasel-js/core/insert';

Documentation

License

MIT.