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

@astrov/react-jsoncanvas

v0.1.6

Published

React implementation of JSONCanvas renderer with interactive features

Readme

React JSONCanvas

A React implementation of the JSONCanvas format renderer with interactive features including zoom, pan, drag-and-drop, and touch support.

Features

  • 🎨 Full JSONCanvas Support - Renders text, link, and group nodes with edges
  • 🔍 Interactive Zoom & Pan - Mouse wheel zoom, keyboard shortcuts, touch gestures
  • 🖱️ Drag & Drop - Move nodes around the canvas with mouse or touch
  • 🧭 Selection & Editing - Marquee and multi-selection, resize, copy/paste, and delete
  • ↩️ History - Undo and redo editor mutations
  • 🗂️ Deterministic Layers - Persistent node ordering with undoable z-order commands
  • 🔗 Edge Editing - Select, label, recolor, delete, create, and rewire connections
  • ⚡ Viewport Culling - Skips off-screen nodes and edges on larger canvases
  • 📱 Touch Support - Full mobile and tablet support with gestures
  • ⌨️ Keyboard Shortcuts - Space for panning, Ctrl+scroll for zoom
  • 🎯 TypeScript - Fully typed with comprehensive type definitions
  • 🎛️ Customizable - Configurable colors, styles, and behavior
  • 📦 Lightweight - Minimal dependencies, optimized bundle size

Installation

npm install @astrov/react-jsoncanvas

Basic Usage

import React from 'react';
import { Canvas } from '@astrov/react-jsoncanvas';
import { GenericNode, Edge } from '@trbn/jsoncanvas';
import '@astrov/react-jsoncanvas/styles';

const nodes: GenericNode[] = [
  {
    id: 'node1',
    type: 'text',
    text: 'Hello World!',
    x: 100,
    y: 100,
    width: 200,
    height: 100,
  },
  {
    id: 'node2',
    type: 'text',
    text: 'Another node',
    x: 400,
    y: 200,
    width: 180,
    height: 80,
  },
];

const edges: Edge[] = [
  {
    id: 'edge1',
    fromNode: 'node1',
    toNode: 'node2',
    toEnd: 'arrow',
  },
];

function App() {
  return (
    <div style={{ width: '100vw', height: '100vh' }}>
      <Canvas nodes={nodes} edges={edges} />
    </div>
  );
}

nodes and edges initialize the editor document. Canvas owns subsequent changes internally. To replace the whole document, remount it with a new key:

<Canvas key={documentId} nodes={nodes} edges={edges} />

Node Layer Order

The nodes array is ordered from back to front: the first node is the lowest layer and the last node is the highest. Dragging one or more selected nodes moves that group to the front while preserving its internal order. The layer change and position change are stored as one undoable history entry.

Components rendered inside Canvas can use the layer commands from useCanvas():

import { useCanvas } from '@astrov/react-jsoncanvas';

const {
  state,
  bringNodesToFront,
  bringNodesForward,
  sendNodesBackward,
  sendNodesToBack,
} = useCanvas();

bringNodesForward(state.selectedNodeIds);

Edges remain below nodes. Within the edge SVG, connections follow the layer of their highest endpoint, and connections to selected nodes render last.

Edge Editing

Click an edge to select it and show its editing toolbar. The toolbar can delete the edge, choose a JSONCanvas color, or edit its label. Clicking an already selected edge also switches its label directly into editing mode.

Dragging an edge starts endpoint rewiring only after the pointer moves beyond the click threshold. A click without movement never detaches the edge.

Custom Nodes

Register custom node content with nodeRenderers. Canvas keeps ownership of the wrapper, so custom nodes automatically participate in selection, dragging, resizing, layer ordering, and edge creation.

import {
  Canvas,
  defineCustomNode,
  type CustomNodeRendererProps,
  type GenericNode,
} from '@astrov/react-jsoncanvas';

interface StatusNode extends GenericNode {
  type: 'status';
  label: string;
  status: 'todo' | 'done';
}

function StatusContent({
  node,
  isEditing,
  updateNode,
  setEditing,
}: CustomNodeRendererProps<StatusNode>) {
  if (isEditing) {
    return (
      <input
        autoFocus
        defaultValue={node.label}
        onBlur={event => {
          updateNode({ label: event.currentTarget.value });
          setEditing(false);
        }}
      />
    );
  }

  return <strong>{node.status}: {node.label}</strong>;
}

const nodeRenderers = {
  status: defineCustomNode<StatusNode>({
    component: StatusContent,
    editable: true,
    className: 'status-node',
  }),
};

<Canvas nodes={nodes} edges={edges} nodeRenderers={nodeRenderers} />;

The text, link, and group type names are reserved for built-ins. Missing registrations continue to use the unknown-node fallback renderer.

To create a custom node when a connection is dropped on empty canvas space, return the node from onConnectDrop. The callback receives the world-space drop position and the side of the new node that must face the source anchor. The mapping is left -> right, right -> left, top -> bottom, and bottom -> top.

import type { EdgeSide, Point } from '@astrov/react-jsoncanvas';

const NODE_WIDTH = 220;
const NODE_HEIGHT = 120;

function getNodePosition(position: Point, toSide: EdgeSide) {
  return {
    x:
      toSide === 'left'
        ? position.x
        : toSide === 'right'
          ? position.x - NODE_WIDTH
          : position.x - NODE_WIDTH / 2,
    y:
      toSide === 'top'
        ? position.y
        : toSide === 'bottom'
          ? position.y - NODE_HEIGHT
          : position.y - NODE_HEIGHT / 2,
  };
}

<Canvas
  nodes={nodes}
  edges={edges}
  nodeRenderers={nodeRenderers}
  onConnectDrop={({ position, toSide }) => ({
    id: crypto.randomUUID(),
    type: 'status',
    label: 'New status',
    status: 'todo',
    ...getNodePosition(position, toSide),
    width: NODE_WIDTH,
    height: NODE_HEIGHT,
  })}
/>

The node and its connecting edge are added as one undoable document change. Returning null or undefined keeps the default behavior and cancels the unfinished connection.

Advanced Usage

With Controls

import React from 'react';
import { 
  Canvas, 
  ZoomControls, 
  ViewportControls, 
  ExportControls 
} from '@astrov/react-jsoncanvas';

function App() {
  return (
    <div style={{ width: '100vw', height: '100vh', position: 'relative' }}>
      <Canvas nodes={nodes} edges={edges}>
        {/* Controls positioned absolutely */}
        <div style={{ position: 'absolute', top: 16, right: 16, zIndex: 10 }}>
          <ZoomControls showLabels />
        </div>
        <div style={{ position: 'absolute', top: 16, left: 16, zIndex: 10 }}>
          <ViewportControls showLabels />
        </div>
        <div style={{ position: 'absolute', bottom: 16, right: 16, zIndex: 10 }}>
          <ExportControls showLabels />
        </div>
      </Canvas>
    </div>
  );
}

With Event Handlers

import React from 'react';
import { Canvas } from '@astrov/react-jsoncanvas';

function App() {
  const handleNodeMove = (nodeId: string, position: { x: number; y: number }) => {
    console.log(`Node ${nodeId} moved to`, position);
  };

  const handleNodeSelect = (nodeId: string | null) => {
    console.log('Selected node:', nodeId);
  };

  const handleViewportChange = (viewport: ViewportState) => {
    console.log('Viewport changed:', viewport);
  };

  return (
    <Canvas
      nodes={nodes}
      edges={edges}
      onNodeMove={handleNodeMove}
      onNodeSelect={handleNodeSelect}
      onViewportChange={handleViewportChange}
    />
  );
}

Custom Configuration

import React from 'react';
import { Canvas } from '@astrov/react-jsoncanvas';

const config = {
  minScale: 0.1,
  maxScale: 3.0,
  zoomSpeed: 0.2,
  enableTouch: true,
  enableKeyboardShortcuts: true,
};

function App() {
  return (
    <Canvas
      nodes={nodes}
      edges={edges}
      config={config}
    />
  );
}

API Reference

Canvas Component

| Prop | Type | Description | |------|------|-------------| | nodes | GenericNode[] | Initial nodes; changes after mount are not synchronized | | edges | Edge[] | Initial edges; changes after mount are not synchronized | | className | string | Additional CSS class | | style | React.CSSProperties | Inline styles | | config | Partial<CanvasConfig> | Configuration options | | nodeRenderers | CustomNodeRenderers | Custom content renderers keyed by node type | | onNodeMove | (nodeId: string, position: Point) => void | Called when a node is moved | | onNodeSelect | (nodeId: string \| null) => void | Called when a node is selected | | onViewportChange | (viewport: ViewportState) => void | Called when viewport changes | | onCanvasChange | (nodes: GenericNode[], edges: Edge[]) => void | Called when canvas data changes | | onEdgeCreate | (edge: Edge) => void | Called when an edge is created | | onConnectDrop | (event: ConnectDropEvent) => GenericNode \| null \| undefined | Creates and connects a node when an edge is dropped on empty space |

Configuration Options

interface CanvasConfig {
  minScale: number;           // Minimum zoom level (default: 0.25)
  maxScale: number;           // Maximum zoom level (default: 3)
  zoomSpeed: number;          // Zoom step size (default: 0.3)
  touchThreshold: number;     // Touch drag threshold (default: 10)
  curveTightness: number;     // Edge curve tightness (default: 0.75)
  enableKeyboardShortcuts: boolean; // Enable keyboard shortcuts (default: true)
  enableTouch: boolean;       // Enable touch support (default: true)
  snapToGrid: boolean;        // Snap node changes to the grid (default: true)
  gridSize: number;           // Grid step in world units (default: 20)
}

Control Components

  • ZoomControls - Zoom in/out buttons with current zoom level
  • ViewportControls - Fit to screen, reset zoom, reset viewport buttons
  • ExportControls - Export canvas data as JSON

Keyboard Shortcuts

  • Space + Drag - Pan around the canvas
  • Ctrl/Cmd + Wheel - Zoom in/out
  • Ctrl/Cmd + 0 - Reset zoom to 100%
  • Ctrl/Cmd + = - Zoom in
  • Ctrl/Cmd + - - Zoom out
  • Ctrl/Cmd + Z - Undo
  • Ctrl/Cmd + Shift + Z or Ctrl/Cmd + Y - Redo
  • Ctrl/Cmd + C / V / D - Copy, paste, or duplicate selected nodes
  • Delete / Backspace - Delete selected nodes or the selected edge

Touch Gestures

  • Single finger drag - Pan around the canvas
  • Pinch - Zoom in/out
  • Tap and drag node - Move nodes around

Styling

The library includes default styles, but you can customize the appearance using CSS custom properties:

:root {
  /* Node colors */
  --canvas-color-red: #ff6b6b;
  --canvas-color-orange: #ffa726;
  --canvas-color-yellow: #ffeb3b;
  --canvas-color-green: #66bb6a;
  --canvas-color-cyan: #26c6da;
  --canvas-color-purple: #ab47bc;
  
  /* Edge styles */
  --canvas-edge-color: #000000;
  --canvas-edge-width: 2;
  
  /* Node styles */
  --canvas-node-border-radius: 8px;
  --canvas-node-background: #ffffff;
  --canvas-node-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

TypeScript Support

This library is written in TypeScript and provides comprehensive type definitions. All JSONCanvas types are re-exported for convenience:

import type {
  GenericNode, 
  JSONCanvasEdge,
  ViewportState,
  CanvasConfig 
} from '@astrov/react-jsoncanvas';

License

MIT

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Planned work

  • render node header
  • edge style improvements
  • overall style enhancements
  • smooth LERP zoom