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

@lunchfirm/pentool

v0.1.5

Published

A zero-dependency, embeddable SVG pen tool for the web. Mount on any element, get standard SVG path data out.

Readme

PenTool.js

A zero-dependency, embeddable SVG pen tool for the web.

npm install @lunchfirm/pentool

Mount it on any element. Users draw paths with Illustrator-style pen-tool interaction. The result is structured data that you can do whatever you want with.


What it is

The pen tool is an input device. It doesn't render anything permanent. It gives you anchor positions, Bézier handles, and a valid SVG d string; the consuming app builds whatever interface it wants around that.

Use it for:

  • Image annotation, hotspot editors, ROI selectors
  • Polygon-based labelling tools
  • Region-of-interest pickers, mask editors
  • Any UI where the user needs to trace a closed or open path on an image

Quick start

ESM (bundlers, modern browsers)

import { PenTool } from "@lunchfirm/pentool";

const pen = new PenTool(document.getElementById("canvas"), {
  viewBox: [800, 600],
});

pen.on("path", (path) => {
  console.log(path.d); // "M 100 200 C 150 100 ... Z"
  console.log(path.points); // [{x, y, handleIn, handleOut}, ...]
  console.log(path.closed); // true | false
});

Script tag

<script src="https://unpkg.com/@lunchfirm/pentool"></script>
<script>
  const pen = new PenTool(document.getElementById("canvas"), {
    viewBox: [800, 600],
  });
  pen.on("path", (path) => console.log(path.d));
</script>

The target element must have position: relative or absolute, the pen tool mounts a transparent SVG overlay positioned to fill it.

Interactions

Identical to Illustrator / Figma / Inkscape.

Drawing

| Input | Action | | ------------------------- | --------------------------------------------------- | | Click | Place a corner anchor (straight segments) | | Click + drag | Place a smooth anchor with symmetric Bézier handles | | Click first anchor | Close the path (straight closing segment) | | Click + drag first anchor | Close with a smooth loop point | | Alt + drag last handle | Convert most-recent anchor to a cusp | | Enter | Finish as an open path (no Z) | | Escape | Cancel the in-progress path | | Backspace / Delete | Remove the last placed anchor |

Editing (after the path is finished)

| Input | Action | | -------------------------- | ------------------------------------------------------------------- | | Drag anchor | Move it (its handles follow) | | Drag handle | Adjust the curve (mirrors on smooth points) | | Alt + drag handle | Break the mirror, move freely | | Alt + click anchor | Toggle smooth/corner | | + then click segment | Insert a new anchor on the curve (shape preserved via de Casteljau) | | then click anchor | Remove that anchor | | Escape (in +/ mode) | Exit back to default editing |

API

new PenTool(element: HTMLElement, options?: { viewBox?: [number, number] })

| Parameter | Description | | ----------------- | ------------------------------------------------------------------------------------------------------- | | element | The container to mount on. Must be positioned. | | options.viewBox | Authored coordinate space [w, h]. Optional — if omitted, uses pixel dimensions and updates on resize. |

Events

pen.on("path", (path) => {}); // user committed (close, edit, +/-, toggle)
pen.on("update", (path) => {}); // every change during a drag — for live previews
pen.on("cancel", () => {}); // user pressed Escape during drawing

path is { d: string, closed: boolean, points: PathPoint[] }.

PathPoint is { x, y, handleIn, handleOut }. Handles are {x, y} in absolute coordinates (matching SVG C semantics), or null for straight segments.

Methods

| Method | Description | | ----------------- | ----------------------------------------------------------------------------------------------- | | pen.snapshot() | Read the current path state. | | pen.load(input) | Load existing path data for editing. Accepts a points array or a { points, closed } snapshot. | | pen.clear() | Reset to drawing mode. | | pen.destroy() | Remove the overlay, detach all listeners. |

Coordinate system

The pen tool is always pinned to the coordinate space of the element it's mounted on. Pointer events map to viewBox coordinates via getBoundingClientRect(), the same transform SVG performs natively. Resize the element and the overlay scales with it; emitted points never change.

Pass viewBox: [imageWidth, imageHeight] when drawing over an image so the coordinates you get back are in the image's native space, regardless of how the image is sized on screen.

Styling

The overlay reads its colors from CSS custom properties. Override on .pentool-overlay or any parent:

.pentool-overlay {
  --pt-path-stroke: #1a1410;
  --pt-anchor-fill: #1a1410;
  --pt-anchor-stroke: transparent;
  --pt-handle-fill: #ffffff;
  --pt-handle-stroke: #6b5945;
  --pt-handle-line: #a08c6e;
  --pt-close-hint: #8c2818;
  --pt-mode-bg: #ffffff;
  --pt-mode-fg: #1a1410;
}

All sizes (anchor radius, handle radius, stroke widths) are kept constant in screen space regardless of viewBox scale.

Demo

The repo ships with two examples:

  • examples/basic.html — minimal stage + live d string output
  • examples/hotspot.html — a hotspot editor that lets you load an image, draw multiple named regions with optional href and arbitrary key/value metadata, and export a single self-contained interactive HTML file

Run them locally:

git clone https://github.com/declareupdate/pentool
cd pentool
python3 -m http.server 8080
# open http://localhost:8080/examples/hotspot.html

Design

Full design rationale, interaction priority, data format conventions, and build plan are in pen-tool-spec.html, open it in a browser for the rendered spec.

License

MIT — see LICENSE.