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

@orpham/seatcharts

v1.0.3

Published

Interactive seat charts.

Downloads

80

Readme

seatcharts

CI jsDelivr

Interactive seat charts — no jQuery required.

A TypeScript rewrite of jQuery-Seat-Charts with a modern class-based API, full type declarations, and zero runtime dependencies.

Installation

npm install @orpham/seatcharts

Quick Start

<div id="seat-map"></div>
<div id="legend"></div>
import {SeatCharts} from '@orpham/seatcharts';

const sc = new SeatCharts(document.getElementById('seat-map')!, {
    map: [
        'aaaaaaaaaaaa',
        'aaaaaaaaaaaa',
        'bbbbbbbbbb__',
        'bbbbbbbbbb__',
        'bbbbbbbbbbbb',
        'cccccccccccc'
    ],
    seats: {
        a: {classes: ['front-seat'], price: 99.99},
        b: {price: 49.99},
        c: {price: 19.99}
    },
    legend: {
        node: document.getElementById('legend')!,
        items: [
            ['a', 'available', 'Front (€ 99.99)'],
            ['b', 'available', 'Middle (€ 49.99)'],
            ['c', 'available', 'Back (€ 19.99)'],
            ['a', 'unavailable', 'Unavailable'],
            ['a', 'selected', 'Your selection']
        ]
    },
    click() {
        if (this.status() === 'available') return 'selected';
        if (this.status() === 'selected') return 'available';
        return this.style();
    }
});

// mark already booked seats
sc.find('c.available').status('unavailable');

// read price of a seat
console.log('Seat 1_2 costs ' + sc.get('1_2').data().price);

CDN (no bundler)

<script src="https://cdn.jsdelivr.net/npm/@orpham/seatcharts/dist/seatcharts.umd.cjs"></script>
<script>
    const sc = new seatcharts.SeatCharts(container, options);
</script>

Map

The map option is an array of strings where each string represents one row. Every character represents a seat type of your choice — you can use any letter except _, which is reserved as a blank spacer:

map: [
    'aaaaaa__DDDDD',
    'aaaaaa__aaaaa',
    'bbbbbb__bbbbb',
    'ccccccccccccc'
]

Custom ID and label per seat

By default, each seat gets an id of row_column (e.g. 1_3) and a label equal to its column number. You can override both inline using square-bracket notation:

map: [
    'a[myId,My Label]aa', // custom id and label
    'a[onlyId]aa', // custom id, default label
    'a[,Just a label]aa' // default id, custom label
]

IDs may contain letters, digits, and underscores. Labels may additionally contain spaces.


Seat Setup

The seats option maps each character to a SeatSetup object:

| Property | Type | Description | |-----------|-------------------------------|----------------------------------------------------| | classes | string[] | Extra CSS classes added to every seat of this type | | data | Record<string, unknown> | Arbitrary data attached to the seat (price, etc.) | | price | number | Shorthand stored in data.price | | click | (this: ISeat) => SeatStatus | Per-character click handler (overrides global) | | focus | (this: ISeat) => SeatStatus | Per-character focus handler (overrides global) | | blur | (this: ISeat) => SeatStatus | Per-character blur handler (overrides global) |

seats: {
    v: {
        classes: ['vip'],
        price: 300,
        click() {
            if (this.status() === 'available') return 'selected';
            if (this.status() === 'selected') return 'available';
            return this.style();
        }
    },
    e: {
        classes: ['economy'],
        price: 50
    }
}

Events

Three events drive every interaction — mouse, touch, and keyboard are unified behind the same handlers:

| Event | Triggered by | |---------|--------------------------| | click | Mouse click · Spacebar | | focus | Mouse enter · Arrow keys | | blur | Mouse leave · Arrow keys |

Each handler is called with this bound to the seat (ISeat) and must return the new seat status. You can define handlers globally on the chart or per character in seats — the per-character handler takes precedence.

Default handlers

click() {
    if (this.status() === 'available') return 'selected';
    if (this.status() === 'selected') return 'available';
    return this.style(); // keep current visual state (e.g., unavailable)
}

focus() {
    if (this.status() === 'available') return 'focused';
    return this.style();
}

blur() {
    return this.status(); // revert to actual status
}

Note on focused: focused is a visual-only style — it does not change the seat's underlying status. Calling seat.status() on a focused seat returns its real status (available, selected, etc.). Use seat.style() when you need the current visual state.


Options

Constructor

new SeatCharts(container: HTMLElement, options: SeatChartsOptions)

map (required)

string[] — Row definitions. See Map.

naming

Controls row and column labels and ID generation.

| Property | Type | Default | Description | |------------|------------------------------------------------|------------------------------|------------------------------------------| | top | boolean | true | Show a column-header row above the map | | left | boolean | true | Show a row-label column left of the map | | rows | (string \| number)[] | [1, 2, 3, ...] | Row label for each row | | columns | (string \| number)[] | [1, 2, 3, ...] | Column label for each column | | getId | (character, row, column) => string | `${row}_${column}` | ID generator — also used as the DOM id | | getLabel | (character, row, column) => string \| number | (_c, _r, column) => column | Label displayed inside the seat cell |

naming: {
    rows: ['A', 'B', 'C', 'D'],
    columns: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
    getId: (char, row, col) => `${row}${col}`,
    getLabel: (_char, row, col) => `${row}${col}`
}

seats

Record<string, SeatSetup> — Per-character configuration. See Seat Setup.

legend

| Property | Type | Description | |----------|-----------------------|----------------------------------------------------------------------------| | node | HTMLElement \| null | Container for the legend. If omitted, a <div> is inserted after the map. | | items | LegendItem[] | Array of [character, cssClass, label] tuples. |

legend: {
    node: document.getElementById('legend')!,
    items: [
        ['a', 'available', 'Available'],
        ['a', 'unavailable', 'Unavailable'],
        ['a', 'selected', 'Selected']
    ]
}

click / focus / blur

Global event handlers. See Events.

onChange

onChange?: (seat: ISeat, newStatus: SeatStatus) => void

Called after every click-triggered status change, with the seat and its new status already applied. Use this to react to selections without needing to read state inside the click handler:

onChange(seat, newStatus) {
    if (newStatus === 'selected') {
        cart.add(seat.data().price);
    } else {
        cart.remove(seat.data().price);
    }
}

i18n

i18n: {
    t: (key: string) => string
}

Translation function used for seat aria-label attributes. Use the bundled createI18n helper or wire in any i18n library. See i18n.


API

SeatCharts

status(id, newStatus?) / status(ids, newStatus)

Read or update the status of one or more seats by ID:

sc.status('1_3'); // 'available'
sc.status('1_3', 'unavailable'); // set one seat
sc.status(['1_3', '1_4'], 'unavailable'); // set multiple seats

get(id) / get(ids)

Retrieve a single ISeat or a ISeatSet:

const seat = sc.get('1_3');
const set = sc.get(['1_3', '1_4']);

find(query)

Search the entire map. Returns an ISeatSet. See Selectors.

each(callback)

Iterate over every seat. Return false from the callback to break early:

sc.each(function (id) {
    console.log(id, this.status());
});

node()

Returns an HTMLElement[] of every seat node in map order.

destroy()

Removes all DOM elements created by the chart and clears event listeners. The container is restored to its original state.

sc.destroy();

Live Updates

Use sc.status() to reflect new bookings in real time — for example by polling an endpoint every few seconds:

setInterval(async () => {
    const bookings: { seatId: string }[] = await fetch('/bookings').then(r => r.json());
    sc.status(bookings.map(b => b.seatId), 'unavailable');
}, 10_000);

Selectors

find() and get() return an ISeatSet that exposes the same methods, so they can be chained:

sc.find('a'); // all 'a' seats
sc.find('unavailable'); // all unavailable seats
sc.find('a.available'); // available 'a' seats
sc.find(/^2_/); // all seats in row 2 (regex on id)

sc.get(['1_2', '1_3']).find('available'); // available seats within a subset

Set methods

| Method | Description | |---------------------|---------------------------------------------------------------------| | status() | Returns the status of the single seat in the set (length must be 1) | | status(newStatus) | Sets the status of every seat in the set | | node() | Returns HTMLElement[] for every seat in the set | | each(callback) | Iterates the set; return false to break | | find(query) | Searches within the set (not the whole map) | | get(id\|ids) | Retrieves seat(s) within the set by ID |


Seat Methods

Every ISeat instance exposes the following methods. Inside event handlers this is the seat.

| Method | Signature | Description | |-------------|---------------------------------|--------------------------------------------------------| | status() | () => SeatStatus | Returns the current seat status | | status(s) | (s: SeatStatus) => SeatStatus | Sets a new status and returns it | | style() | () => string | Returns the current visual style (may be 'focused') | | style(s) | (s: string) => string | Sets the visual style without touching the status | | node() | () => HTMLElement | Returns the seat's DOM element | | data() | () => SeatData | Returns the arbitrary data object attached to the seat | | char() | () => string | Returns the seat's map character | | click() | () => SeatStatus | Programmatically fires the click handler | | focus() | () => SeatStatus | Programmatically fires the focus handler | | blur() | () => SeatStatus | Programmatically fires the blur handler |

Seat statuses

| Status | Meaning | |---------------|--------------------------------------------| | available | Seat can be selected by the user | | unavailable | Seat is already taken or blocked | | selected | Seat has been selected by the current user | | focused | Visual-only hover/keyboard focus style |

You can also return any custom string from an event handler to use your own status / style.


Styling

The library generates plain HTML with predictable CSS classes — no styles are bundled, so you have full control.

| Class | Element | |---------------------------------|---------------------------------------------------| | .seatCharts-container | The root container element | | .seatCharts-row | Each row <div> | | .seatCharts-header | The column-header row (when naming.top is true) | | .seatCharts-cell | Every cell — seats, spacers, and header cells | | .seatCharts-seat | Seat cells only | | .seatCharts-space | Spacer cells (_ in the map) | | .seatCharts-seat.available | Available seats | | .seatCharts-seat.selected | Selected seats | | .seatCharts-seat.unavailable | Unavailable seats | | .seatCharts-seat.focused | Focused seats (hover / keyboard) | | .seatCharts-legend | The legend container | | .seatCharts-legendList | The <ul> inside the legend | | .seatCharts-legendItem | Each <li> in the legend | | .seatCharts-legendDescription | The text label next to each legend icon |

To style seat types differently, combine the status class with your custom class:

/* vip seats */
.seatCharts-seat.available.vip {
    background: #ffd700;
}

.seatCharts-seat.selected.vip {
    background: #ff8c00;
}

.seatCharts-seat.focused.vip {
    background: #ffe680;
}

.seatCharts-seat.unavailable.vip {
    background: #ccc;
}

/* economy seats */
.seatCharts-seat.available.economy {
    background: #90ee90;
}

.seatCharts-seat.selected.economy {
    background: #228b22;
}
seats: {
    v: {
        classes: ['vip'],
        price: 300
    },
    e: {
        classes: ['economy'],
        price: 50
    }
}

i18n

The library uses translation keys for seat aria-label attributes, making the chart fully accessible in any language.

| Key | Default (fallback to key) | Usage | |---------------|---------------------------|-----------------------------------| | available | "available" | aria-label of available seats | | unavailable | "unavailable" | aria-label of unavailable seats | | selected | "selected" | aria-label of selected seats | | focused | "focused" | aria-label of focused seats |

Use the bundled createI18n helper with one of the included locale files:

import {SeatCharts, createI18n} from '@orpham/seatcharts';
import de from '@orpham/seatcharts/locales/de.json';

const sc = new SeatCharts(container, {
    map: ['aaa'],
    i18n: createI18n(de)
});

Available locales: cs, de, el, en, es, fr, it, nl, no, pl, pt, sk, tr.

You can also wire in any i18n library directly:

import i18next from 'i18next';

const sc = new SeatCharts(container, {
    map: ['aaa'],
    i18n: {t: (key) => i18next.t(key)}
});

Build

npm run build # produces dist/seatcharts.js and dist/seatcharts.umd.cjs

Test

npm test # run once
npm run test:watch # watch mode

License

MIT — see LICENSE.txt. Based on jQuery-Seat-Charts by Mateusz Markowski — see NOTICE.