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

@the-portland-company/canvas-editor

v0.4.2

Published

Shared VRM canvas editor substrate — block/layout document model, block registry, HTML-email renderer, and the drag-and-drop editor surface for Politogy VRM apps. Framework-agnostic: styling via brand CSS vars, data/persistence/media injected by the app t

Readme

@the-portland-company/canvas-editor

The shared VRM Canvas Editor substrate for Politogy VRM apps: one block/layout document model, one block registry, one HTML renderer, and one drag-and-drop editor surface. Every module that needs canvas editing (Email, and later Forms, Surveys, CTAs, landing pages) binds to this package and registers its own blocks — nobody rebuilds the editor.

Framework-agnostic by construction: the package never imports next/*, @supabase/*, @the-portland-company/shell, or any app code. All persistence, media, templates, brand tokens, and merge-field resolution are injected by the host through a single CanvasHostAdapter. It runs in a Next.js (React 19) app and a Vite (React 18) app alike.

Extracted verbatim from the V1 email canvas (politogy-email) so behavior is identical. See the audit in politogy-email/CANVAS_EDITOR_AUDIT.md.

Entry points

| Import | Contents | Environment | | --- | --- | --- | | @the-portland-company/canvas-editor | Document model, block registry, tree ops, HTML renderer, adapter types | Server-safe — pure logic, no React, no @dnd-kit. Import from Server Components / server actions / API routes. | | @the-portland-company/canvas-editor/editor | <CanvasEditor> + hooks | "use client" — pulls @dnd-kit. Client only. | | @the-portland-company/canvas-editor/renderer | renderEmailHtml / renderEmailText / pinBlockSchemaVersion | Server-safe. | | @the-portland-company/canvas-editor/skeletons | Skeleton / SkeletonBar (self-contained, token-driven) | Server-renderable. | | @the-portland-company/canvas-editor/css | Primitive component classes (.card/.input/.btn/.skeleton…) | Import once in the app. |

Peer dependencies (install alongside)

React 18 || 19, @dnd-kit/{core,sortable,utilities}, and — since 0.3.0, for the tiptap rich-text editor — @tiptap/core @tiptap/react @tiptap/starter-kit @tiptap/extension-underline @tiptap/extension-text-align @tiptap/extension-link @tiptap/extension-text-style @tiptap/extension-color @tiptap/extension-font-family (^3.27) and emoji-picker-react (^4.19). The package externalizes all of them.

Never import /editor from a server module@dnd-kit evaluates React.createContext at module load and will crash in an RSC / server-action context. Server code that needs block logic imports the root (.) only.

Consuming it

  1. Install from the registry (never npm link; use npm pack tarballs for local dev against an unpublished change):

    npm i @the-portland-company/canvas-editor
  2. Tailwind @source — the editor ships as Tailwind utility classes in dist, and Tailwind v4 does not scan node_modules. Add to your app/globals.css or it renders unstyled (silent — builds green, diagnose against the live DOM):

    @source "../node_modules/@the-portland-company/canvas-editor/dist";
  3. CSS + brand tokens — import the primitive CSS, and make sure your app already loads the runtime brand-tokens <link> (shell requirement). The package paints only through var(--token, fallback); colors and fonts come from the app's runtime tokens, and dark mode flips automatically under your app's .dark ancestor. The package defines no tokens.

    import "@the-portland-company/canvas-editor/css";
  4. Implement the adapter and render the editor:

    "use client";
    import { CanvasEditor } from "@the-portland-company/canvas-editor/editor";
    import type { CanvasHostAdapter } from "@the-portland-company/canvas-editor";
    
    const adapter: CanvasHostAdapter = {
      save: (id, doc, expectedUpdatedAt) => myServerActions.save(id, doc, expectedUpdatedAt),
      renderPreview: (id, doc, opts) => myServerActions.renderPreview(id, doc, opts),
      sendTest: (id, addrs) => myServerActions.sendTest(id, addrs),
      templates: { list, loadInto, saveAs, resetToBlank },
      media: { list, upload, updateAltText, delete: del, usageCount, trackUsage },
      personalizationTokens: () => myServerActions.tokens(),
      brand: () => myServerActions.brand(),
    };
    
    <CanvasEditor
      adapter={adapter}
      documentId={id}
      initialDocument={{ name, blocks, settings, meta: { subject, preheader } }}
      initialUpdatedAt={updatedAt}
      nextAction={{ label: "Next: Sender →", href: senderHref }}
      metaPanel={({ doc, setMeta }) => <MyEmailMetaPanel doc={doc} setMeta={setMeta} />}
      insertMode="center"   // initial only; users switch Side/Center in the block tray (remembered per browser)
    />

    brand() may also return org: { companyName, address, websiteUrl, privacyUrl, contactUrl, logoUrl } so the Footer block derives its defaults from the host.

The boundary

  • The package owns: the document model (CanvasDocument = name + blocks
    • settings + opaque meta), the block registry (core blocks pre-registered), the editor UI (palette, canvas, selection, inline toolbar, insert model, properties panel, mobile overrides, preview modes, autosave), and the renderer.
  • The host supplies (via CanvasHostAdapter): persistence, server-rendered preview, send-test, templates, media library, personalization tokens, and brand info. Host-specific fields (e.g. an email's subject/preheader and its deliverability panel) live in the host and mount into the editor through the metaPanel slot — they are carried in the document's opaque meta bag, which the package never interprets.
  • Merge-field / UVP resolution stays in the host adapter — provenance and exposure are enforced at the host's data layer, never in this package.

Development

npm run type-check   # tsc --noEmit
npm test             # vitest (CORE parity tests ported from the email app)
npm run build        # tsup dual-build + re-assert "use client" on the editor entry

Publishing is a tag-driven CI lane (v* tag → npm). prepublishOnly runs type-check + npm audit --audit-level=high + tests + build.