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

@emkodev/svg-engine

v0.2.0

Published

Declarative SVG graph canvas — web components for interactive node/edge diagrams

Readme

@emkodev/svg-engine

Declarative SVG graph canvas as native web components. Drop in <svg-canvas> with <svg-node> and <svg-edge> children and get a fully interactive diagram — pan, zoom, drag, connect — with zero JavaScript required from the consumer.

Install

npm install @emkodev/svg-engine

Usage

HTML (script tag)

<svg-canvas>
  <svg-node id="a" x="200" y="200" title="Start" start></svg-node>
  <svg-node id="b" x="400" y="300" title="End"></svg-node>
  <svg-edge from="a" to="b"></svg-edge>
</svg-canvas>

<script type="module" src="node_modules/@emkodev/svg-engine/dist/index.js"></script>

ES module

import '@emkodev/svg-engine';
<svg-canvas>
  <svg-node id="a" x="200" y="200" title="Start" start></svg-node>
  <svg-node id="b" x="400" y="300" title="End"></svg-node>
  <svg-edge from="a" to="b"></svg-edge>
</svg-canvas>

Elements

<svg-canvas>

The root container. Renders an infinite scrollable SVG world with a dot-grid background, edge layer, and node layer inside its shadow DOM. Provides a default toolbar and panel that activate automatically — no configuration needed.

| Attribute | Default | Description | |----------------|---------|------------------------------------| | world-width | 6000 | SVG canvas width in world units | | world-height | 4000 | SVG canvas height in world units |

Slots

| Slot | Description | |-------------|--------------------------------------------------------------| | toolbar | Replace the default toolbar. Omit to use the built-in one. | | panel | Replace the default node-edit panel. Omit for the built-in. | | (default) | <svg-node> and <svg-edge> declarations, plus any other light DOM content. |

Declarative wiring

Any element inside the canvas (including slotted toolbar/panel) can trigger canvas actions with data-action and sync node attributes with data-node-attr — no JavaScript needed:

<svg-canvas>
  <div slot="toolbar">
    <button data-action="create-node">+ Node</button>
    <button data-action="zoom-in">+</button>
    <button data-action="zoom-out">−</button>
    <button data-action="fit">Fit</button>
    <span data-zoom-display></span>
  </div>
  <div slot="panel" popover="manual">
    <input type="text" data-node-attr="title" placeholder="Title">
    <label><input type="checkbox" data-node-attr="start"> Start</label>
    <button data-action="delete-node">Delete</button>
  </div>
</svg-canvas>

data-action values: create-node, zoom-in, zoom-out, fit, delete-node. data-node-attr value: any attribute name on <svg-node> (title, start, incomplete, …).

Public API

canvas.createNode(x?: number, y?: number): void
canvas.deleteSelected(): void  // also bound to Delete/Backspace keys
canvas.zoomIn(): void
canvas.zoomOut(): void
canvas.zoomBy(delta: number): void
canvas.applyZoom(z: number): void
canvas.fitToContent(): void
canvas.getZoom(): number
canvas.worldCoords(clientX: number, clientY: number): { x: number; y: number }
canvas.refreshEdges(): void
canvas.refreshEdgesForNode(id: string): void
canvas.refreshOrphans(): void

Events

All events bubble.

| Event | detail | Fired when | |------------------|-----------------------------|------------------------------------------| | zoom-change | { zoom: number } | Zoom level changes | | node-select | { id: string } | A node is selected | | node-deselect | — | Selection is cleared | | node-move | { id: string } | A node is dropped after dragging | | node-delete | { id: string } | A node is deleted | | edge-create | { from: string, to: string } | A new edge is drawn between ports | | edge-click | { from: string, to: string } | An edge is clicked (also removes it) | | canvas-dblclick| { x: number, y: number } | Canvas double-clicked (cancelable — cancel to suppress node creation) |

canvas.addEventListener('node-move', (e: NodeMoveEvent) => {
  console.log('moved', e.detail.id);
});

Keyboard shortcuts

| Key | Action | |-----------------|-------------------------| | Shift+F | Fit content to viewport | | + / = | Zoom in | | - / _ | Zoom out | | Arrow keys | Scroll | | Delete / | Delete selected node | | Escape | Deselect |


<svg-node>

Represents a graph node. Must be a direct child of <svg-canvas>.

| Attribute | Type | Description | |--------------|---------|--------------------------------------| | id | string | Required. Unique node identifier | | x | number | World x position | | y | number | World y position | | title | string | Label text (truncated at 22 chars) | | start | boolean | Marks the node as a start node (shows accent sidebar) | | incomplete | boolean | Shows an orange indicator dot |


<svg-edge>

Represents a directed edge between two nodes. Must be a direct child of <svg-canvas>.

| Attribute | Type | Description | |-----------|--------|-------------------------------| | from | string | Source node id | | to | string | Target node id |


Theming

The canvas exposes CSS custom properties. All have typed @property declarations with light-dark() initial values, so the component adapts to the OS color scheme out of the box when no overrides are set.

| Property | Default (dark) | Default (light) | |-----------------|-------------------------------|-------------------------------| | --accent | #5b6af0 | #5b6af0 | | --bg | #0f0f0f | #f4f4f5 | | --surface | #1a1a1a | #ffffff | | --border | oklch(1 0 0 / .08) | oklch(0 0 0 / .1) | | --border-hover| oklch(1 0 0 / .2) | oklch(0 0 0 / .25) | | --text | #e8e6e0 | #111111 | | --text-muted | #666666 | #555555 |

Override in CSS:

svg-canvas {
  --accent: #e040fb;
  --bg: #0a0a0a;
}

TypeScript types

import type {
  ZoomChangeEvent,
  NodeSelectEvent,
  NodeMoveEvent,
  NodeDeleteEvent,
  EdgeCreateEvent,
  CanvasDblClickEvent,
} from '@emkodev/svg-engine';

License

MIT