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

@shapelayer/zoomable-image

v0.1.0

Published

Dependency-free zoomable image Web Component with captions and annotations

Readme

Zoomable Image

A dependency-free TypeScript Web Component for zooming images, captions, and image annotations. MIT licensed. Requires Custom Elements v1, Shadow DOM, Pointer Events, ResizeObserver, modal dialog, and dynamic viewport units in a modern browser.

Getting Started

npm install @shapelayer/zoomable-image
import { defineZoomableImage } from '@shapelayer/zoomable-image';
defineZoomableImage();

Or import @shapelayer/zoomable-image/register once to register automatically. The package ships native ESM and TypeScript declarations; it has no framework, stylesheet, or icon-font dependency.

<zoomable-image
  src="/diagram.png"
  alt="Architecture diagram"
  width="1200"
  height="800"
  caption="Deployment architecture"
  display-width="80%"
></zoomable-image>
const image = document.querySelector('zoomable-image');
image.annotations = [
  {
    id: 'api',
    x: 10,
    y: 20,
    width: 30,
    height: 15,
    text: 'API server',
    tooltip: 'Handles incoming requests.'
  }
];
image.labels = {
  open: '이미지 열기',
  close: '이미지 닫기',
  zoomIn: '확대',
  zoomOut: '축소'
};
image.addEventListener('zoomable-annotation', (event) => {
  console.log(event.detail.annotation);
});

Annotation coordinates are percentages of the whole source image. Invalid regions and duplicate IDs throw TypeError without changing the existing data. Caption, labels, and tooltip text are rendered as plain text. Arrays and labels are copied; reassign properties to update them. Properties assigned before registration are upgraded on connection.

API

| Kind | Names | Behavior | | --------------------- | ----------------------------------------------------------- | ----------------------------------------------------------------- | | Attributes | src, alt, width, height, caption, display-width | Reactive HTML values; width/height are intrinsic image dimensions | | Reflected properties | src, alt, caption, displayWidth | Strings reflected to the matching attribute | | Structured properties | annotations, labels | Values assigned in JavaScript | | Read-only properties | isOpen, zoom | Viewer state and current scale | | Methods | open(), close(), zoomIn(), zoomOut(), reset() | Open/close, step zoom, or reset pan and zoom | | Events | zoomable-open, zoomable-close | Viewer lifecycle | | Events | zoomable-zoom | detail: { scale } | | Events | zoomable-annotation | detail: { annotation } on annotation activation | | Events | zoomable-error | detail: { src } when the viewer image fails to load |

All events bubble and cross shadow boundaries. ImageAnnotation, ViewerLabels, and ZoomableImageEventMap are exported types. HTMLElementTagNameMap and HTMLElementEventMap provide typed element lookup and event listeners. The package includes a Custom Elements Manifest at @shapelayer/zoomable-image/custom-elements.json for editor/tooling support.

Zoom steps: 50%, 75%, 100%, 150%, 200%, 300%, 400%. Drag with a mouse or a single touch to pan above 100%. Enter/Space opens the thumbnail, +/− zoom, Escape closes, and Tab navigates controls and annotation regions. Regions show a tooltip on hover, focus, or activation. Zoom/pan hides the tooltip.

Native modal dialog provides background inertness and focus containment. Focus is restored on close, and document scrolling is locked while any library viewer remains open. Removal closes the viewer and disconnects observers/listeners. Reconnection is supported. Events from image activation and viewer keyboard shortcuts are isolated from surrounding clickable cards and bubble-phase popup handlers; capture-phase handlers in host applications must account for their own event priority.

Styling

Use ::part(trigger), thumbnail, dialog, image, close, caption, toolbar, annotation, or tooltip to style the shadow content.

zoomable-image {
  --zoomable-image-width: 80%;
  --zoomable-image-backdrop: rgba(0, 0, 0, 0.85);
  --zoomable-image-controls: #25364c;
}
zoomable-image::part(annotation) {
  border-color: #93c5fd;
}

Annotation box colors can be configured per viewer with CSS custom properties. Omitting them preserves the existing colors:

| CSS property | Default | Applies to | | ----------------------------------------------- | ------------------------ | ----------------------------------- | | --zoomable-image-annotation-background | rgba(28, 32, 40, 0.15) | Normal background | | --zoomable-image-annotation-active-background | rgba(28, 32, 40, 0.25) | Hover and keyboard focus background | | --zoomable-image-annotation-border-color | rgba(28, 32, 40, 0.2) | Border |

zoomable-image {
  --zoomable-image-annotation-background: rgba(59, 130, 246, 0.15);
  --zoomable-image-annotation-active-background: rgba(59, 130, 246, 0.3);
  --zoomable-image-annotation-border-color: #3b82f6;
}

display-width controls the inline width; normal author CSS can override it. The viewer remains in the browser top layer even inside a transformed or clipped ancestor.

SSR and frameworks

The main entry is safe to import on an SSR server and does not register elements. Register on the client; DOM objects cannot be instantiated on the server. The register entry is the automatic-registration entry. For HTML rendered before hydration or with JavaScript disabled, put a fallback image inside the element:

<zoomable-image src="/photo.jpg" alt="A landscape">
  <img src="/photo.jpg" alt="A landscape" loading="lazy" style="width:100%;height:auto" />
</zoomable-image>

The shadow tree hides fallback children after upgrade. Framework adapters should assign annotations and labels as properties after mounting. The site adapter demonstrates Svelte 5 client registration, reactive properties, localized labels, and an SSR fallback.

defineZoomableImage(tagName?, registry?) supports custom tag names and a registry, is idempotent for this library, and reports collisions with unrelated elements. After default registration, new ZoomableImage() also works in the browser. Custom tag names do not automatically augment TypeScript's tag-name map.

Development and verification

From repository root (Node 22+, pnpm 12.6.0):

pnpm install --frozen-lockfile
pnpm exec playwright install chromium firefox webkit
pnpm test:release
pnpm test:package

test:release runs type, unit, and browser checks. Browser projects cover Chromium, Firefox, WebKit, Android Chromium emulation, and iPhone WebKit emulation. test:package packs and installs the actual artifact into an isolated consumer, verifies exports/SSR/types/license, and rejects unexpected files.

Build with pnpm build. Run node test/server.mjs and open http://127.0.0.1:4174/examples/ for the self-contained demo. Host integration tests are maintained in the separate jonghyeon.me repository.

On macOS 27, Playwright Firefox may exit before tests with Could not find profile folder due to its shared Firefox app-data directory (upstream report). The configuration accepts ZOOMABLE_FIREFOX_EXECUTABLE for an isolated test-browser launcher. CI uses Linux and the normal bundled Firefox. Test results describe browser-engine checks and emulated touch environments, not physical-device certification.