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

@ticketlayer/seatmap-render

v0.1.2

Published

The Ticketlayer seat map renderer: a framework-free 2D canvas renderer and camera for a venue plan, with seats in every state, the curved stage, pins, hover, pan, pinch, wheel zoom and section focus. Colours and sizes come from @ticketlayer/theme

Readme

@ticketlayer/seatmap-render

The seat map's 2D canvas renderer and camera, from the Seat Explorer prototype (3dvenue/prototypes/explorer/src/app-a.js) without its tilt, panels or list. Framework free: the tl-seat-* harnesses, Box Office's tlb-seat-* and the Manage editor each own a <canvas> and hand it here.

It knows nothing of Live, the seat map token or fetch. The host fetches and decodes with @ticketlayer/seatmap and passes the results in; the renderer draws them and reports what the person did through callbacks.

pnpm --filter @ticketlayer/seatmap-render build   # dist/esm, ESM only
pnpm --filter @ticketlayer/seatmap-render test    # node --test on a fake canvas

Use

import { decodeOverlay, decodeTLSC, decodeTLST, parseOverview } from '@ticketlayer/seatmap';
import { createSeatmapRenderer } from '@ticketlayer/seatmap-render';

const map = createSeatmapRenderer(canvas, {
  overview: parseOverview(overviewJson),
  onSeatTap: ({ seat, sectionId, state }) => { /* reserve on tap when state is 'available' */ },
  onSeatHover: (e) => { /* e is { seat, sectionId, state } or null */ },
  onSectionFocus: ({ sectionId }) => { /* fetch that section's chunk; null is back to the venue */ },
  onSectionHover: (e) => {},
  onPinTap: ({ id }) => {},
});

map.addChunk(section.chunk, decodeTLSC(bytes));     // once per block, as it arrives
map.setOverlay(decodeOverlay(overlayJson, overview.seatCount));
map.setStatus(decodeTLST(statusBytes));               // every poll; null before the first
map.setMine([10422, 10423]);                          // the seats this buyer has reserved
map.setPins([{ id: 's1', title: 'Stalls Left', label: 'From £15.50', at: { sectionId: 's1' }, color: '#7c3aed', priority: 3 }]);
map.focusSection('s1');                               // or null for the whole venue
map.destroy();

API

createSeatmapRenderer(canvas, options): SeatmapRenderer

| Option | | |---|---| | overview | the parsed overview (required) | | colorScheme | light (default) or dark: which theme defaults to use for tokens the canvas's style does not set | | style | token values by name that win over the computed style | | tokenSource | where tokens are read; the canvas's computed style by default | | pixelRatio | device pixels per CSS pixel; devicePixelRatio, at most 3 | | insets | CSS pixels at each edge kept clear for the host's controls; framing centres in what is left | | obstacles | rectangles the host draws over the canvas (zoom controls, a map key, a top bar); a fit clears each by whichever edge leaves the venue bigger, with a margin in proportion to the view, and no pin is placed under one | | turnToStage | turn a focused section so its seats face up the screen (default true) | | animate | animate camera moves (default true; off under prefers-reduced-motion) | | keyboard | arrows pan, + and - zoom, Escape leaves a section (default true; give the canvas a tabindex) | | stageLabel | the text across the stage, upper-cased; the decoration's own label ("Stage", "Pitch") by default (a harness passes its own string for another language) | | backgroundAlpha | how opaque what is painted under the seats is, 0 to 1: the background, the floor plates and the section fills. 1 by default; 0 leaves the canvas transparent under the venue (see "A picture underneath") | | onSeatHover, onSeatTap, onSectionHover, onSectionFocus, onPinTap | the events out | | onCameraChange | the camera moved (a drag, a pinch, the wheel, a key, a frame of a fly-to): called once per drawn frame in which it changed, so a harness keeps what it places over the canvas with the plan |

| Method | | |---|---| | addChunk(name, chunk) | add a decoded TLSC block; the sections it covers draw their seats and turn to face their seats' way | | setOverlay(overlay \| null) | categories and their colours, not-sold and killed seats | | setStatus(status \| null) | a TLST snapshot; with none, every sold seat draws as available | | setMine(seats) | the seats reserved by this buyer | | setPins(pins) | { id, label, at: { sectionId } \| { seat } \| { point }, color?, active?, title?, priority?, members? }; one greedy collision pass, active pins first, then by priority. A section's pin is a chip that stands in for its name (title over price), on the label anchor or just outside the section when the anchor is among seats, never over a seat; a pin with members stands in for an area's sections while they are too small for one each, and a tap on it zooms to them | | setInsets(insets) | change the clear edges and refit | | setObstacles(rects) | change what the host draws over the map; refits unless the buyer has moved it | | viewBox | the size and insets the camera projects into, for a host's own project and unproject | | focusSection(id \| null, { animate? }), focusedSection | fly to a section, or back to the venue | | zoomBy(factor), reset() | | | flyTo(camera, { animate? }) | move the camera there, its centre kept over the plan: for a harness bringing a seat and the card beside it into view. Asked while another move is under way (a section focus), it takes that move over from where the camera is and ends when that one would have: one move, not two | | target | where the camera is going: the end of the move under way, or camera when nothing moves. A harness that adjusts a move (room for a card beside a seat in a section it just focused) works from this | | toScreen(x, y) | where a plan point is on screen now, in CSS pixels from the canvas's top left | | hitRadius(seat) | how far from the seat's centre, in CSS pixels, a tap still lands on it now: the circle a harness keeps clear when it puts a card beside the seat. 0 before the seat's block is loaded | | seatAt(x, y) | the seat index under a point in CSS pixels from the canvas's top left, or -1. Only seats at least seat-hit-radius on screen | | sectionAt(x, y) | the topmost section id under the point, or null | | seatState(seat) | mine, killed, unsold, unavailable or available, in that order of precedence | | camera | { cx, cy, scale, bearing } | | resize() | read the canvas size again; a ResizeObserver calls it when there is one | | refreshStyle() | read the tokens again, after a theme change | | setBackgroundAlpha(alpha), backgroundAlpha | change how opaque the background, plates and section fills are, clamped to 0..1 | | render() | draw now; otherwise drawing waits for the next animation frame | | destroy() | remove every listener and stop drawing |

A tap on a seat (big enough to tap) is onSeatTap with its state, whatever that state is: the harness decides what a tap on an unavailable seat means. A tap on a section other than the focused one focuses it. A drag pans and never taps.

The building blocks are exported for a harness that needs them: Scene (the plan in plan units, seatNear, sectionAt), the camera functions (project, unproject, zoomAt, panBy, interpolate), resolveStyle with SEATMAP_RENDER_TOKENS, and the seat's paint as pure functions, seatStateOf(seat, { overlay, status, mine, marks, released }) and seatColourOf(seat, state, overlay, style), which the renderer draws with and which any other view of the same seats (the explorer's 3D mode) uses so a seat is the same colour everywhere.

Colours and sizes

Every colour and size is a @ticketlayer/theme token in the seatmap group (TOKENS.md, "Seat map"), read once from the canvas's computed style and falling back to TOKEN_DEFAULTS (or TOKEN_DARK with colorScheme: 'dark'). Nothing is written down here. The names are in three families: map-* for the map itself (background, plates, sections, labels, pins, pillars and aisles), stage-* for the stage and seat-* for the seats. The derived tokens follow the brand: a selected seat (--tl-seat-selected) is --tl-primary, the hover ring --tl-foreground. A seat's category colour is the event's, from the overlay. After a channel theme is applied, call refreshStyle().

Pillars, aisles and text

The overview's decorations (@ticketlayer/seatmap's README, "Decorations") are drawn when there are any, and nothing changes when there are none. Under the seats, after the section fills: each aisle as its walkway in map-aisle with a dashed map-aisle-line marking along it (the marking alone when the plan gives no width or the walkway is under two pixels), then the pillars, solid in map-pillar with a map-pillar-edge outline, round or rectangular, at any size they can be seen at (at least a pixel and a half across), since a pillar is what a restricted view is restricted by. Over them, after the pins are placed: the venue's text, upright in map-label, at its letter height (the map's label size when it has none), once it is eight pixels or more and only where it covers no seat, pin or name placed before it. Scene reads them into scene.decorations (aisles, pillars, texts, each with its floor's draw order).

A picture underneath

Manage traces a venue's source plan (a PDF page or a picture) with the seats drawn over it. The picture belongs under the seats, not over them: put it behind the canvas (an element or a second canvas below it) and pass backgroundAlpha: 0. The renderer then clears the canvas each frame and paints no background, no plate and no section fill, so the picture shows through under the stage, the seats, their labels and the pins, all drawn as usual. A value between 0 and 1 tints the picture with the map's own colours instead; setBackgroundAlpha changes it without recreating the renderer, for a slider. The canvas element itself must have no CSS background.

Harnesses

ESM only: it reads the theme package at runtime, and that is ESM only. A Stencil spec under Jest has no canvas, so a tl-seat-* spec mocks this package rather than loading it; the drawing and the input are tested here on a fake canvas (test/fake-canvas.mjs).

Levels are drawn in plan position, upper floors over lower ones, and hit testing takes the topmost section. A venue whose levels overlap on the plan needs its overview's outlines laid out apart, which is the publisher's job.

One stage

A plan that draws its levels apart has a stage decoration per level. The building has one stage: the lowest floor draws it, and each higher level gets a cue at its front edge (a short bar and the word) saying which way it faces, as the prototype draws it. Each section turns, when focused, to the stage of its own floor.