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

@charcuterie/logic

v0.1.0

Published

The five Charcuterie state kinds — Visibility, VisibilityGroup, SinglePicker, MultiplePicker, RovingFocus, and Status — as framework-free cores with React and Preact bindings.

Readme

@charcuterie/logic

The five Charcuterie state kinds, as framework-free cores with React 19 and Preact bindings. No components — those are @charcuterie/ui (M3).

@charcuterie/logic            React 19 binding (default entry)
@charcuterie/logic/core       framework-free factories, zero dependencies
@charcuterie/logic/preact     Preact binding (no preact/compat)
@charcuterie/logic/jotai      optional store adapter
@charcuterie/logic/signals    optional store adapter

The five kinds

| Kind | Question it answers | Used by | | --- | --- | --- | | createVisibility | Is this shown? | Modal, Drawer, Popover, Tooltip, Disclosure | | createVisibilityGroup | Which one is shown? | Tabs, Accordion, a Menu's submenus | | createSinglePicker | Which one is chosen? | Radio, Select, segmented control | | createMultiplePicker | Which set is chosen? | Checkbox group, multi-select filter | | createRovingFocus | Which one is tabbable? | Listbox, Menu, Toolbar, Tab list | | createStatus | Where in a lifecycle? | Badge, LiveStatusIndicator, ProgressBar |

The last two are new in v2; see the five-state-kinds decision. createLinkedIds sits alongside them as shared aria-controls / aria-labelledby bookkeeping — it holds no user-facing state, so it is not a sixth kind.

Shape

Every core is { getState, subscribe, ...commands } plus pure selectors:

import {
  createVisibilityGroup,
  selectIsKeyVisible,
} from "@charcuterie/logic/core"

const tabs = createVisibilityGroup()

const release = tabs.register("overview")

tabs.show("overview")

selectIsKeyVisible(tabs.getState(), "overview") // true

getState() returns a frozen object whose identity changes only when the state does. That is load-bearing, not housekeeping: useSyncExternalStore re-renders on identity, so a core that rebuilt its derived arrays on every read would re-render a whole listbox for nothing — and, in the Preact binding, loop.

The React and Preact hooks flatten that into one object:

const { register, show, visibleKey } = useVisibilityGroup()

They are uncontrolled: visibleKey is an initial value, and onChange is the observation escape hatch. See the uncontrolled-hooks decision.

Intent, registration, and what falls out

The four member-having kinds store what the consumer asked for and derive the public answer from that plus who is currently mounted. Three properties come free:

  • A value set before its member mounts is not lost. It parks in pendingKey/pendingValue(s) and is promoted on registration — which is what makes a form's initial value survive its options mounting a tick later.
  • A remount round-trips. StrictMode's double mount, a route change, a virtualised list scrolling a row out and back — the intent was never discarded, so it comes back selected.
  • The invariants are true by representation. "At most one visible" is one field. "selectedValue is always a registered option or null" is a derivation, not a rule six commands have to remember.

Registrations are a multiset, not a set. Two things register the same key more often than you would guess, and with a plain set the first unmount unregisters it out from under the survivor.

createRovingFocus breaks the symmetry in one direction on purpose: unregistering the focused member moves focus to its neighbour rather than parking it, because a keyboard user whose row disappeared expects the next row.

Store injection

import { createStoreFromJotai } from "@charcuterie/logic/jotai"

useVisibility({ createStore: createStoreFromJotai(jotaiStore) })

The seam has exactly three members — get, set, subscribe — and the default is a 20-line observable ref with no dependencies. Jotai is not a dependency; see the store-injection decision.

Testing

yarn vitest run --project logic       # cores, node
yarn vitest run --project logic-dom   # React + Preact bindings, chromium

src/conformance/ holds one model-based suite that runs against five adapters: the core, the core over a Jotai store, the core over a @preact/signals-core store, the React 19 binding, and the Preact binding. fast-check generates random command sequences, applies each to both a naive reference model and the real thing, and asserts after every step.

The DOM adapters mount a host component that renders null and rebuild getState() from the last committed render — so a binding that failed to re-render reads stale and the model catches it on the next assertion.

Mutation-checked, because a green suite that cannot fail proves nothing:

| Deliberate regression | Result | | --- | --- | | multiset → plain Set in registrations.ts | 15 core properties fail | | Preact useStoreValue stops subscribing | 10 Preact properties fail, React unaffected |

Sandbox note, corrected 2026-07-29 (M3). PLAYWRIGHT_BROWSERS_PATH points at /opt/pw-browsers, which now ships both chromium 1234 and chromium_headless_shell-1234. No override is needed, and the one M2 recommended ($HOME/.cache/ms-playwright) now breaks the run — that directory no longer exists.

What is deliberately not here

Floating positioning, dismiss layers, focus traps, typeahead, virtualisation, and date logic. Those arrive in M4 on @floating-ui/react, which is controlled by construction — you pass state in, it never stores it — so VisibilityGroup stays the sole owner. Radix, Base UI, and Ark UI all own open themselves, which is the conflict this package exists to avoid.