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

lenslite

v0.1.0

Published

Mobile-first, dependency-free lightbox gallery with full gesture support

Readme

LensLite

A mobile-first, dependency-free lightbox gallery in TypeScript. All gestures supported from day one, 100% typed, 100% tested.

Gestures

| Gesture | Action | | --- | --- | | Drag left / right | Slide to the next / previous image, following the finger; snaps back below a quarter of the screen width (unless flicked), rubber-bands at the ends | | Swipe or drag up / down | Dismiss (with drag preview and snap-back) | | Double-tap | Zoom in towards the tapped point / zoom back out | | Pinch | Zoom in and out towards the fingers' midpoint (clamped between 1× and maxZoom) | | Pan while zoomed | Move around the image, clamped to the frame, with momentum on release | | Tap | Toggle the chrome (buttons and counter) |

Keyboard works too: ← / → navigate, Escape closes, and Tab cycles the controls without leaving the dialog. Focus moves into the dialog on open and returns to the previously focused element on close. A polite live region announces each image ("Image 2 of 9: …") to screen readers, and all transitions are disabled under prefers-reduced-motion.

Opening pushes a history entry, so the browser (or hardware) back button closes the viewer instead of leaving the page; closing it any other way pops the entry again. Pass history: false to opt out.

Adjacent images are rendered on off-screen slides, so they are preloaded before you navigate to them. On viewport resize or device rotation the zoom pan is re-clamped to the new frame.

Usage

import { LensLite } from 'lenslite'

const box = new LensLite({
  items: [
    { src: '/photos/one.jpg', alt: 'One', caption: 'Day one' },
    {
      src: '/photos/two.jpg',
      srcset: '/photos/two-800.jpg 800w, /photos/two-1600.jpg 1600w',
      sizes: '100vw',
    },
  ],
  loop: true,       // wrap around at the ends (default false)
  maxZoom: 4,       // pinch/zoom ceiling (default 4)
  doubleTapZoom: 2.5,
  dismissThreshold: 100, // px of vertical drag that dismisses
  history: true,    // back button closes the viewer (default true)
})

box.open(0)

box.on('change', (index) => console.log('now showing', index))
box.on('error', (index) => console.log('image failed to load', index))
box.on('close', () => console.log('closed'))

Binding to existing markup

LensLite.from(selector, options?) builds a gallery from elements already on the page and opens it at the clicked one. Each matched element contributes one item: an anchor's href, an image's src, or the src of the first image inside the element (alt text comes along the same way). Captions come from a data-caption attribute or a <figcaption> inside the element. Responsive candidates come from data-srcset / data-sizes, or from the element's own srcset / sizes when the element itself is the image — an inner thumbnail's srcset is never used, since it lists the wrong files.

import { LensLite } from 'lenslite'

// <a href="/photos/one.jpg" data-caption="Day one">
//   <img src="/thumbs/one.jpg" alt="One"></a> …
const box = LensLite.from('a[data-gallery]', { loop: true })

If an image fails to load, the viewer shows an inline "Image failed to load" message on that slide and emits the error event with the failed item's index (neighboring slides preload, so this can fire for an image you are not looking at yet).

Styles are injected automatically on first open. The GestureController (Pointer Events → tap / double-tap / pan / swipe / pinch) is exported on its own if you want to build something else with it.

Development

npm install
npm test               # run the suite
npm run test:coverage  # enforces 100% lines/branches/functions/statements
npm run typecheck
npm run build          # emits ESM + d.ts to dist/

To try the demo: npm run build, then serve the repo root (for example npx serve .) and open /demo/.