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

@oh-just-another/editor

v0.61.1

Published

Drop-in diagram editor as a single React component. Auto-detects renderer / WASM / worker capabilities and exposes a programmatic editor API.

Readme

@oh-just-another/editor

npm version

Drop-in diagram editor as a single React component. Auto-detects the best renderer (WebGL2 / Canvas2D / OffscreenCanvas), opt-in WASM text-shaping and rasterisation, and worker offloading — then exposes one <Editor> component plus a programmatic handle for driving it from code.

The umbrella package (L6): it composes @oh-just-another/react-ui, @oh-just-another/state, the renderers, serialization and templates into a ready-to-mount editor. For lower-level building blocks, depend on those packages directly.

Install

pnpm add @oh-just-another/editor react react-dom

react / react-dom (>=18) are peer dependencies.

Quick start

import { Editor } from "@oh-just-another/editor";
// The chrome (toolbar, panels, menus) is styled by the react-ui stylesheet.
import "@oh-just-another/react-ui/styles.css";

export function App() {
  return <Editor style={{ position: "fixed", inset: 0 }} />;
}

Theming. @oh-just-another/react-ui/styles.css ships the complete token set — light and dark, plus a prefers-color-scheme fallback for "system" — so the import above is all you need. The editor toggles data-theme on its own root element (not the global <html>, so multiple editors theme independently) via the theme prop; override any --du-* variable in your own stylesheet to re-skin.

Driving it from code

Pass a ref to get an imperative handle for programmatic control — undo/redo, zoom, selection, mutations:

import { useRef } from "react";
import { Editor, type EditorAPI } from "@oh-just-another/editor";

function Host() {
  const ref = useRef<EditorAPI>(null);

  const exportScene = () => {
    const scene = ref.current?.getScene();
    // ... persist `scene`
  };

  return <Editor ref={ref} onReady={(editor) => console.log(editor)} />;
}

EditorAPI:

| Member | Type | Notes | | -------------------------------------- | --------------------------- | ------------------------------------------------------------ | | editor | EditorInstance \| null | The full live engine (escape hatch); null until onReady. | | capabilities | CapabilityProfile \| null | Resolved renderer / WASM / worker profile. | | getScene() | () => Scene | Current scene (the seed before the editor exists). | | loadScene(s) | (scene: Scene) => void | Replace the scene (resets undo history). | | getActiveTool() / setActiveTool(t) | ActiveTool / Mode | Read the active-tool object / switch the tool. | | getSelection() / setSelection(ids) | ReadonlySet<ElementId> | Read / set the selection. | | undo() / redo() | void | History navigation. | | zoomToFit() | void | Fit the scene to the viewport. |

Anything not on the handle is reachable through editor — the full EditorInstance from @oh-just-another/state.

Props

| Prop | Type | Purpose | | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | --------------------------------------------------------------------------------------- | | initialScene | Scene | Seed scene (defaults to empty). | | initialTool | Mode | Initial tool mode (default "select"). | | capabilities | CapabilityOverrides | Force / disable renderer, WASM, workers, tiles. | | templates / fileDropHandlers / layoutKinds / animationAdapters | arrays | Plugins registered on mount. | | onReady / onSceneChange / onSelectionChange | callbacks | Lifecycle + change notifications. | | theme / defaultTheme / onThemeChange / persistTheme | theme | Controlled or self-managed theme; optional localStorage persistence. | | hideTopBar / hideBottomBar / hideToolbar / hideMainMenu / hideLibraryButton / hideZoomControls / hideResetToContent / hideHelpButton / hideContextMenu / hideSelectionPanel | boolean | Toggle chrome off. | | renderTopBar{Left,Center,Right} / renderBottomBar{Left,Center,Right} / renderMainMenuExtras | () => ReactNode | Inject custom content into the bars / menu. | | onImportTemplates | () => void | Library-panel "Import" click. | | repositoryUrl | string \| null | Help-menu link target; null hides it. | | onConfirm / onNotify | callbacks | Override the reset-confirm / error dialogs (default window.confirm / window.alert). | | workerFactory | () => Worker | Override the offscreen render-worker constructor. | | className / style | — | Applied to the editor root. |

Capabilities

detectCapabilities() runs once on mount and picks the backend the runtime supports, collapsing to a concrete CapabilityProfile:

  • rendererwebgl2 (also chosen when WebGPU is present) → offscreen (OffscreenCanvas + Worker) → canvas2d.
  • wasmText / wasmRaster — bundled WASM shaper / rasterizer when supported (rasterizer only on the WebGL2 path).
  • workers / tiles / touch — off-thread tiling, large-scene tile cache, coarse-pointer modality.

Override any field via the capabilities prop ("auto" or omit = detect):

<Editor capabilities={{ renderer: "canvas2d", wasmText: false }} />

Bundlers & workers

The OffscreenCanvas render worker is constructed with new Worker(new URL("./render-worker.js", import.meta.url), { type: "module" }), which Vite, Rollup and webpack 5 detect and bundle automatically. On bundlers that don't (esbuild, Parcel, no bundler), either:

  • pass your own workerFactory that resolves the worker the way your bundler expects, or
  • skip the worker path entirely with capabilities={{ renderer: "canvas2d" }} (or webgl2 if available) — the offscreen backend is only one of several.

The worker is an optimisation; the editor renders fine without it.

Custom shapes

defineShape registers a custom element type in one call — bounds (spatial index / hit-test / culling), rendering (all backends), and optional interaction hooks. Serialization is automatic (unknown types pass through).

import { defineShape, type ElementBase } from "@oh-just-another/editor";

interface Sticker extends ElementBase {
  readonly type: "sticker";
  readonly size: number;
}

defineShape<Sticker>({
  type: "sticker",
  bounds: (s) => ({ x: 0, y: 0, width: s.size, height: s.size }),
  render: (s, target) => {
    target.setFill(s.style.fill ?? "#facc15");
    target.beginPath();
    target.rect(0, 0, s.size, s.size);
    target.fill();
  },
  // Optional: interactiveHitTest, rotateAnchor.
});

Returns { dispose() } — currently a no-op (registries are append-only), reserved for future deregistration.

Plug-in points

These registries are re-exported from this package, so the editor is the single import for extending the kernel without forking:

import { registerBounder, registerElementRenderer } from "@oh-just-another/editor";
  • defineShape — one-call custom shape (wraps the two registries below plus registerInteractiveHitTester / registerRotateAnchor).
  • registerBounder (AABB) + registerElementRenderer (draw) — custom element types.
  • registerMigration — wire-format migrations.
  • registerLayoutKind / registerAnimationAdapter — or pass via the layoutKinds / animationAdapters props.
  • exportSceneToPng — render the full scene to a PNG Blob headlessly.

Diagram is a deprecated alias of Editor.