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

@oppulence/infinite-canvas

v0.3.2

Published

Infinite canvas spatial editor for HTML/React designs — DOM artboards, layers, inspector, undo/redo, pluggable collaboration

Readme

@oppulence/infinite-canvas

An infinite canvas spatial editor for HTML/React designs — real DOM artboards (not vector graphics), a typed scene graph, layers, an inspector, undo/redo, and pluggable real-time collaboration. Ships as raw TypeScript source; consumers own all storage.

Built for two consumers in oppulence-canvas (apps/web, corinthian/corinthian-web), who persist documents through their own tRPC → Postgres stacks. The library exposes serializable document types + change signals only.

Install

bun add @oppulence/infinite-canvas

Peer dependencies: react/react-dom ^19, zustand ^5, zod ^4.3.5. Optional peers: @oppulence/design-system (./panels only), tailwindcss, and — only if you import ./collab/yjsyjs, y-protocols, @hocuspocus/provider.

Consumer checklist (Next.js)

  1. next.config: add "@oppulence/infinite-canvas" to transpilePackages (it ships raw TS source).
  2. Monorepo bunfig.toml: add it to minimumReleaseAgeExcludes (same PR as the dep) or installs hit the 3-day quarantine.
  3. CSS: import "@oppulence/infinite-canvas/styles.css" once, and add @source "../../../../node_modules/@oppulence/infinite-canvas/src"; to your Tailwind entry (the load-bearing canvas chrome ships in styles.css, but panel utilities need the @source).
  4. Only if using ./panels: install @oppulence/design-system and add it to transpilePackages + @source. Prefer ./headless + your own chrome to skip this.

Quick start

"use client";
import { CanvasProvider, CanvasRoot } from "@oppulence/infinite-canvas";
import {
  CanvasLayersPanel,
  CanvasInspectorPanel,
  CanvasToolbar,
} from "@oppulence/infinite-canvas/panels";
import { migrateCanvasDocument } from "@oppulence/infinite-canvas/document";
import "@oppulence/infinite-canvas/styles.css";

export function Editor({
  raw,
  save,
}: {
  raw: unknown;
  save: (doc: unknown) => void;
}) {
  return (
    <CanvasProvider
      initialDocument={migrateCanvasDocument(raw)}
      storage={{ onDocumentChange: ({ getSnapshot }) => save(getSnapshot()) }}
    >
      <div
        style={{
          display: "grid",
          gridTemplateColumns: "16rem 1fr 18rem",
          height: "100dvh",
        }}
      >
        <CanvasLayersPanel />
        <div
          style={{
            position: "relative",
            display: "grid",
            gridTemplateRows: "auto 1fr",
          }}
        >
          <CanvasToolbar />
          <CanvasRoot />
        </div>
        <CanvasInspectorPanel />
      </div>
    </CanvasProvider>
  );
}

Loading is consumer-owned: fetch/parse the document yourself, render your own skeleton, then mount CanvasProvider keyed by document id. Debounce your onDocumentChange save (1.5–2s) and call getSnapshot() at flush time only.

Entrypoints

| Import | Contents | | ---------------------------- | ------------------------------------------------------------------------------------------------------------- | | @oppulence/infinite-canvas | Provider, CanvasRoot, hooks, tools, registry, CanvasApi | | .../document | Server-safe (no React): document/op types, Zod schemas, migrateCanvasDocument, sanitizeNode | | .../headless | Hooks with store-only deps (no design-system): useLayerTree, useInspectorSections, useSelectionProps, … | | .../panels | Shipped UI: CanvasLayersPanel, CanvasInspectorPanel, CanvasToolbar | | .../collab | CollabAdapter/PresenceAdapter interfaces + NullCollabAdapter/LocalPresenceAdapter (yjs-free) | | .../collab/yjs | createYjsCanvasCollab — the only entry that imports yjs/hocuspocus | | .../agent | AI op-authoring: LLM-friendly commands → validated op batches, JSON-Schema tool contracts, describeCanvas | | .../export | exportToHtml / exportToReact / PDF (useCanvasExport) — design → shippable code | | .../testing | createLinkedAdapterPair, InMemoryCollabAdapter, document factories (yjs-free) | | .../styles.css | Load-bearing canvas chrome |

Capabilities

Beyond the editor, the package ships product-grade capabilities for AI/finance/invoicing use cases:

  • AI op-authoring (./agent) — useAgentAuthoring() turns an LLM's high-level commands (add-frame/add-text/add-component/…) into validated, sanitized op batches. Hand the model agentCommandsJsonSchema() as a tool and describeCanvas() as context; it builds and edits designs. Guardrailed by the same sanitize boundary as human edits.
  • Data-binding → templates — text/attrs/props hold {{ path | filter }} expressions; pass data to CanvasProvider and the design renders live (currency/number/date filters built in). Design an invoice once, render it per-customer.
  • Export (./export) — real HTML (exportToHtml), a React component (exportToReact, components stay real), or PDF via native print. Directly usable for Conduitt invoice PDFs.
  • Review commentsuseComments() + canvas-anchored pins with replies/resolve, consumer-persisted (initialComments/onCommentsChange).
  • Block/template libraryuseBlockLibrary() saves a selection as a reusable, id-remappable block; a shared library of invoice sections / dashboard cards.
  • Responsive<ResponsivePreview> shows an artboard reflowing at multiple widths.
  • Insert palette<CanvasPalette> drops frames/text/images/registered components.
  • A11y lintuseDesignLint() / lintDocument() flag WCAG contrast, missing alt, small fonts.

Every capability is demonstrated in Storybook under Canvas/ with browser play tests.

Collaboration

Single-player by default. For multiplayer, pass a CollabAdapter:

import { createYjsCanvasCollab } from "@oppulence/infinite-canvas/collab/yjs";

const { collab, presence } = useMemo(
  () => createYjsCanvasCollab(config),
  [config.documentName, config.hocuspocusUrl], // NOT the whole config object (avoids reconnect flap)
);
<CanvasProvider collab={collab} presence={presence} self={me} /* … */ />;

getToken must be a stable callback that fetches a fresh ticket on demand. In Yjs mode the Hocuspocus server's CRDT bytea is authoritative; JSON onDocumentChange saves are a derived read-model. Corinthian (no CRDT infra) implements PresenceAdapter only over its own transport and keeps NullCollabAdapter.

Theming

The canvas chrome (selection, handles, marquee, grid, panels, comment pins) is driven by --ic-* CSS variables whose defaults map to the design-system --color-* tokens — so an app that already ships the design system themes the canvas automatically. Three ways to brand it, in order of convenience:

// 1. Programmatic — a theme object (sets the tokens inline; cascades to canvas + panels)
<CanvasProvider theme={{ accent: "#7c3aed", canvasBackground: "#faf5ff", gridColor: "#e9d5ff", showGrid: true }} … />
/* 2. CSS — override any token in your own stylesheet, scoped however you like */
.my-canvas-shell {
  --ic-accent: #7c3aed;
  --ic-canvas-bg: #faf5ff;
}
  1. Nothing — if your app already sets the design-system --color-primary / --color-border / --color-background variables, the canvas inherits them out of the box.

Tokens: --ic-accent, --ic-accent-fade, --ic-canvas-bg, --ic-artboard-bg, --ic-border, --ic-muted, --ic-error, --ic-snap, --ic-grid, --ic-font, --ic-handle-size. See the Canvas/Theming story for grape/emerald/midnight examples.

Security

Documents are untrusted (in collab, other users author them). sanitizeNode runs at every boundary (local apply, remote apply, JSON load): finite-number guard, prototype- pollution guard (by shape, all namespaces), a style.custom CSS allow/deny-list, an attrs allowlist + URL-scheme allowlist, and per-node bounds. All document strings render as escaped React text; raw HTML injection is banned package-wide (lint-enforced).

Consumer responsibility: component prop values are untrusted data — a registered component must never interpolate a prop into an href or raw HTML.

Rich text, image export & align/distribute

  • Rich text (TextNode.rich) edits through a floating toolbar backed by the standard Selection/Range DOM APIs (renderer/rich-text-commands.ts) — bold/italic/underline/strike, links, block type (H1/H2/P) and lists — not the deprecated document.execCommand. The contentEditable DOM is the source of truth; domToRich re-derives the typed model on commit.
  • Image export: exportToSvg is lossless and server-safe; rasterizeSvg/toImageBlob produce PNG/JPEG/WebP and, by default (inlineAssets), inline external http(s) images as data: URIs first so fetchable (same-origin / CORS) images no longer taint the canvas. An image that can't be fetched at all still taints the PNG (a browser security limit); SVG is unaffected. Consumer React components are runtime-only and never appear in an export.
  • Align & distribute ship as pure op-math commands (api.commands).

v1 limitations & non-goals

  • Leaf components only in the registry — serializable props, no children/ReactNode/ function props (a slots design is reserved for a later schema version).
  • Repeaters register one rect per logical node. A repeat node renders N DOM copies that share one node.id; only the canonical (first) projection registers in the rect cache, so hit-testing/selection resolves to that logical node, not the individual copy under the pointer. Per-instance selection needs an instance-addressing model (composite rect keys + hit-test/culling resolution) and is deferred.
  • No rotation (the schema reserves the field); no rulers/grid-settings.
  • Flow-child drag is reorder-only in v1; reparent via the layers panel.
  • Interactive HTML tags (input/iframe/script/base/meta) are excluded.
  • Library chrome strings are English-only.

Development

bun run --filter @oppulence/infinite-canvas test        # vitest
bun run --filter @oppulence/infinite-canvas typecheck
bun run --filter @oppulence/infinite-canvas lint        # prettier + import-boundary guard

License: MIT.