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

@8bitscript/ui

v0.22.0

Published

Reusable interface components for 8BitScript programs: a menu bar today, drawn on the character grid through @8bitscript/text, so one component works on every target.

Downloads

5,419

Readme

@8bitscript/ui

Reusable interface components for 8BitScript programs. A component draws on the character grid through @8bitscript/text, so the same code puts the same thing on a PET's 40 columns, a VIC-20's 22, and a Commander X16's 76 — there is one menu bar, not nine.

Today there is one component.

pnpm add @8bitscript/ui
import { text, TextColor } from "@8bitscript/text";
import { menubar } from "@8bitscript/ui/menubar";

export function main(): void {
    menubar.setColors(TextColor.WHITE, TextColor.YELLOW);
    menubar.select(1);

    menubar.begin(0, text.COLUMNS);
    menubar.item("FILE");
    if (menubar.item("EDIT")) {
        text.print(text.COLUMNS, "EDIT MENU");
    }
    menubar.item("VIEW");
    menubar.end();
}
 FILE -EDIT- VIEW

Every component is its own subpath, so a program links the ones it names and nothing else — there is no import ... from "@8bitscript/ui".

The menu bar

@8bitscript/ui/menubar draws one row of items with the selected one inverted — reverse video in the item color, a filled bar rather than a recolored word.

| Call | What it does | | --- | --- | | begin(at, cells) | Open a bar at cell at, using cells cells and never one more | | item(name) | Draw the next item; returns whether it is the highlighted one | | end() | Close the bar, blanking whatever is left of it | | select(i) / selected() | Which item is highlighted; 0 is the first | | count() | How many items the last run offered, drawn or not | | clipped() | Whether an item had to be dropped for want of room | | setColors(item, highlight) | Item color; invert uses it, highlight is unused | | setPadding(cells) | Space either side of each label; 1 by default | | setMarker(s) | The bracket, a one-character string; "-" by default, "" for none | | HEIGHT | Rows a bar occupies, so a program can lay out under it |

The bar is a run of calls, not an object. There is no list of items kept anywhere: begin() opens the bar, each item() draws one and moves the cursor along, and end() closes it. Redrawing means running the calls again. Nothing allocates, and no RAM is spent remembering what the program already knows — which matters on a machine with 3583 bytes for the whole program.

item() returns whether that item is the highlighted one, so

if (menubar.item("FILE")) { drawFileMenu(); }

is how a program hangs behavior off the selection. Nothing moves the highlight on its own: this component never reads input, so a program calls select(), next(), previous() or deselect() itself, driving them from @8bitscript/input or from anything else it likes.

The selected item is inverted, not recolored. Reverse video fills the label and its padding in the item color; a reverse space is a solid block, so the item reads as a button. That works on all nine, including the three with no per-cell color: the NES ships inverted copies of its font at ASCII+128, and the PET and Atari invert with bit 7 of the screen code.

Drawing goes through text.print, and that is worth 423 bytes. print does a machine's per-run video setup once and then walks the string; putChar is self-sufficient, so it redoes that setup — and an ASCII conversion, and a sixteen-bit pointer build — for every cell, inlined into wherever it was called. So the bar prints everything: begin() blanks the row in one pass, item() prints its label and a padding space either side (reverse spaces are the filled ends of a selected item), and reverse rides along on print rather than a second pass over the cells. Built the obvious way instead — runs of putChar for padding, marker, label, marker, padding — item() compiled to 743 bytes on a C64 rather than 285. Adding the bar to Studio costs 423 bytes on a C64 and 396 on a VIC-20; AGENTS.md has the table for every machine, the four shapes measured on the way, and the @8bitscript/text primitive that would take another bite out of it.

Drawing a bar leaves the text color set to the bar's. That is how the color gets on without a second pass, and there is no text.getColor to put your setting back with. Draw the bar, then set your own color:

menubar.setColors(TextColor.CYAN, TextColor.WHITE);
drawBar();
text.setColor(TextColor.WHITE);   // the rest of the screen is yours

A bar never writes past the cells it was given. An item with no room left is not drawn — it still takes its index, so which item is selected does not depend on the width of the screen — and clipped() says so afterwards. FILE EDIT VIEW HELP needs 24 cells and a VIC-20's row has 22, so on that machine HELP is dropped and clipped() is true. What to do about it is the program's decision: shorter labels, less padding, or a second row.

See it run

8BitScript Studio uses a four-item bar across the top of its front door. The VIC-20 is parked — 8bs build --target vic20 still refuses, since that machine's own backend doesn't exist yet — so that picture is the pre-0.2.0 layout, not something trunk emits today. It's the machine where FILE EDIT VIEW HELP does not fit a 22-column row: HELP is dropped and clipped() is true.

Adding a component

See AGENTS.md: what belongs here rather than in a machine package, what the character grid does and does not give you on each target, and the rule that every component links for all nine.