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

jellies-draw

v0.4.1

Published

A drawer for jellies

Downloads

95

Readme

jellies-draw

A Vue 2 + Konva drawing toolkit that turns any DOM container into a whiteboard. Ships three components — a canvas, a tool palette, and a property picker — that share state via a singleton store, so a single toolbar can drive one or many canvases simultaneously.

Features at a glance:

  • Drawing tools: pen, rectangle, ellipse, line, arrow, text, image, eraser, clear.
  • Selection & manipulation: selector tool with multi-select, drag, transform, copy/cut/paste, undo/redo.
  • Laser pointer: temporary red trail for presenting, with a hot-key toggle.
  • Multi-canvas: mount any number of <drawing-canvas> instances; one toolbar controls whichever canvas was last focused.
  • Save / load: serialize a canvas — drawing plus its full undo history — to a JSON string, and restore it later.
  • Keyboard shortcuts: tool picking, color/size cycling, clipboard, history, alt-to-penetrate.

Install

yarn add jellies-draw
# or
npm install jellies-draw

Peer requirements: vue@^2.7 and konva@^9.

Quick start

Mount all three components inside a parent — the canvas can live anywhere, but <tool-buttons> must be mounted to drive the shared tool state, and to enable keyboard shortcuts when has-short-cuts is true.

<template>
  <div id="app">
    <div class="toolbar">
      <tool-buttons :has-short-cuts="true" />
      <property-pickers />
    </div>
    <div class="canvas-area">
      <drawing-canvas background-color="#ffffff" />
    </div>
  </div>
</template>

<script>
import { DrawingCanvas, ToolButtons, PropertyPickers } from 'jellies-draw'

export default {
  components: { DrawingCanvas, ToolButtons, PropertyPickers }
}
</script>

<style>
.toolbar { display: flex; align-items: center; height: 40px; background: #EBEEF3; }
.canvas-area { position: relative; height: calc(100vh - 40px); }
</style>

The canvas fills its parent (width: 100%; height: 100%), so give the wrapper an explicit size. Each canvas observes its container with a ResizeObserver, so the Konva stage resizes whenever the wrapper's box changes (window resize, layout reflow, parent resize, etc.).

Components

<drawing-canvas>

Renders a Konva stage inside its container.

| Prop | Type | Default | Description | | ----------------- | -------- | --------------- | ---------------------------------------------------------------- | | background-color| String | 'transparent' | CSS color for the whiteboard surface. |

Emits:

  • penetrable-updated(isPenetrable: Boolean) — fires whenever the canvas becomes click-through (laser tool, Alt held, or selector locked).

Methods — call these on a component ref:

  • save()String — serialize the canvas to a JSON string. The JSON holds the entire undo history (every snapshot plus the current index), not just the visible drawing.
  • load(json) — restore the canvas from a string produced by save(), bringing back both the drawing and its undo/redo stack. Pass null or '' to clear the canvas to a blank state.

<tool-buttons>

Renders the tool palette. The palette mutates a shared singleton store, so one instance already drives every mounted canvas — mount exactly one <tool-buttons> per page. A second instance's has-short-cuts / are-canvases-linked watchers clobber the first's, so whichever mounted last silently wins.

| Prop | Type | Default | Description | | ---------------------- | --------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | has-short-cuts | Boolean | false | Enables keyboard shortcuts (tool picking, color/size, clipboard, history). | | are-canvases-linked | Boolean | false | Treat every mounted canvas as one logical document: clear wipes them all, and undo/redo replays a shared history stack across all of them in lockstep. |

Tools in order: laser, selector, rectangle, ellipse, arrow, line, pen, text, image, eraser, clear. Numeric shortcuts 19, 0 map to positions 1–10 (laser is intentionally unbound; use ` to toggle it).

Tool behavior:

  • Lockable (selector, rectangle, ellipse, arrow, line): click an active tool again to lock it on — it stays selected after each use instead of falling back to the previous tool.
  • Continuous (pen, text, eraser, laser): stay selected until the user picks a different tool.
  • Instant (image, clear): perform a one-shot action and revert to the previous tool after ~300 ms. image opens a file picker; clear wipes the active canvas (or every canvas, when are-canvases-linked is true).

<property-pickers>

Color and size controls. Two color swatches (fill and stroke) and a size dropdown that switches between stroke widths and font sizes depending on whether a text tool/node is active.

  • Right-click the fill swatch to set fill to transparent.
  • Stroke widths: 2 (H), 3 (HB), 4 (B), 6 (2B).
  • Font sizes: 15 (S), 20 (M), 35 (L), 50 (XL).

Multi-canvas usage

Multiple <drawing-canvas> instances can coexist on the same page. The most recently interacted-with canvas becomes the active target for the toolbar — pointer events on an inactive canvas first promote it to active, then begin drawing.

<template>
  <div>
    <div class="toolbar">
      <tool-buttons :has-short-cuts="true" />
      <property-pickers />
    </div>
    <div class="split">
      <drawing-canvas background-color="#fffdfa" />
      <drawing-canvas background-color="#fafdff" />
    </div>
  </div>
</template>

<style>
.split { display: flex; height: calc(100vh - 40px); }
.split > * { flex: 1; position: relative; }
</style>

Use are-canvases-linked on <tool-buttons> to choose how the mounted canvases relate to each other:

  • are-canvases-linked="false" (default — independent documents). Each canvas keeps its own undo/redo stack and its own clear action. Pick this when several canvases are mounted at once as separate artboards. (For pages shown one at a time, keep a single canvas and swap content instead — see Paged documents.)
  • are-canvases-linked="true" (one logical document). All mounted canvases share a single history stack; undo/redo replays every canvas in lockstep, and clear wipes them all. Pick this when the canvases together form one drawing surface (e.g. a split view, or canvases overlaid on a long scrollable page).

Always shared (regardless of mode):

  • Tool selection, colors, sizes, and the laser pointer. Picking a tool or color in the toolbar applies to whichever canvas the user interacts with next.
  • The active canvas. Pointer events on an inactive canvas first promote it to active, then begin drawing.

Limitations:

  • A drawing stays on the canvas it started on. A stroke or shape begun on one canvas is bound to that canvas until the pointer is released. Dragging onto another canvas (or off-canvas) mid-draw is not captured there, and leaves a straight gap if the pointer returns. You cannot draw one continuous stroke spanning two canvases — treat each as a separate drawing surface.
  • Line / arrow attachments are scoped to a single canvas. Endpoints that snap to a rectangle/ellipse only track shapes on the same canvas; cross-canvas attachments are not supported.
  • Pick a mode at mount time. The linked and per-canvas modes maintain separate history stacks under the hood; flipping are-canvases-linked mid-session switches which stack drives undo/redo, but does not migrate entries between them. If you need to flip dynamically, unmount and remount the canvases (the demo's mode switcher does this by toggling v-if).

Paged documents

When pages are shown one at a time — a carousel, a paged reader, a slide deck — don't mount one canvas per page. As pages scroll out of view their DOM is usually unmounted, which destroys the canvas and its drawing along with it.

Instead, keep a single <drawing-canvas> overlaid on the page area and swap each page's content with save() / load(). The host stores one JSON string per page:

<template>
  <div class="reader">
    <div class="page-stage">
      <div :key="page" class="page" v-html="pages[page - 1]" />
      <drawing-canvas ref="board" class="overlay" background-color="transparent" />
    </div>
    <button :disabled="page === 1" @click="go(page - 1)">Prev</button>
    <button :disabled="page === pages.length" @click="go(page + 1)">Next</button>
  </div>
</template>

<script>
import { DrawingCanvas } from 'jellies-draw'

export default {
  components: { DrawingCanvas },
  data: () => ({
    page: 1,
    pages: ['<h2>Page 1</h2>', '<h2>Page 2</h2>'],
    drawings: {}
  }),
  methods: {
    go(target) {
      if (target < 1 || target > this.pages.length) return
      this.drawings[this.page] = this.$refs.board.save()   // stash the page being left
      this.page = target
      this.$refs.board.load(this.drawings[target] || null) // restore the page being entered
    }
  }
}
</script>

<style>
.page-stage { position: relative; height: 70vh; }
.overlay { position: absolute; inset: 0; }
</style>

Because save() captures the full undo history, undo/redo keeps working per page after you navigate away and back. Persist the drawings map to a backend if annotations must outlive a reload.

A couple of things to keep in mind:

  • The single canvas is one fixed overlay — it does not scroll with page content. If a page scrolls internally, mount the canvas inside the scrolling element (sized to the full content) rather than over the viewport.
  • While a drawing tool is active the overlay captures pointer events. Keep navigation controls outside the overlay, or rely on penetrable mode so clicks fall through when you are not drawing.

The demo's Paged layout is a working example of this pattern.

Keyboard shortcuts

Enabled when <tool-buttons :has-short-cuts="true"> is mounted and an input/text field is not focused.

| Key | Action | | -------------------- | --------------------------------------------------------- | | 19, 0 | Select tool 1–10 (see tool order above). | | ` | Toggle laser pointer. | | Esc | Deselect all nodes. | | Backspace | Delete selected nodes. | | Cmd/Ctrl + A | Select all nodes. | | Cmd/Ctrl + C/X/V | Copy / cut / paste. | | Cmd/Ctrl + Z | Undo. | | Cmd/Ctrl + Shift+Z | Redo. | | Shift + 19 | Pick stroke color (red, green, orange, blue, pink, yellow, purple, light gray, dark gray). | | Shift + = / - | Increase / decrease size (font size or stroke width). | | Hold Alt | Make the canvas click-through temporarily. |

Text editing suspends shortcuts so typing is uninterrupted.

Penetrable mode

The canvas becomes click-through when:

  • The laser tool is active.
  • Alt is held.
  • The selector tool is locked.

While penetrable, pointer events fall through to whatever sits behind the canvas — useful for annotating over an underlying UI.

Demo / development

The repo includes a Vue CLI demo app showing single, split, paged, and scrollable layouts.

yarn install
yarn serve            # dev server with hot reload
yarn build            # build the UMD bundle to dist/jellies-draw.js
yarn lint

License

MIT.