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

@designcompute/aerial

v0.1.0

Published

Controls you can grab, for React apps. Trackball, xy pad, timeline, slider.

Readme

aerial

Rotation and position are hard to get right by typing numbers at them. Aerial gives you controls you can grab and drag instead, and it drops into any React app.

npm i @designcompute/aerial     # or: pnpm add / yarn add / bun add

React 18 or 19 as a peer dependency. Nothing else at runtime.

import { useAerial, AerialPanel, trackball, xy, timeline, slider } from "@designcompute/aerial";

function Scene() {
  const { rot, pan, t, gain } = useAerial({
    rot:  trackball({ value: [0, 0, 0] }),
    pan:  xy({ x: [-1, 1], y: [-1, 1] }),
    t:    timeline({ duration: 12, loop: true }),
    gain: slider({ min: 0, max: 1, step: 0.01 }),
  });

  return <mesh rotation={rot} position={[pan[0], pan[1], t.time]} scale={gain} />;
}

// once, anywhere. there is one store, so nothing gets passed around
<AerialPanel />

Four controls

| | | |---|---| | trackball() | Rotation you can grab. Drag near the middle and it tumbles, near the edge and it rolls. Shift rolls from anywhere. It stops where you let go. | | xy() | Two numbers that move together. Shift stays on one axis, Alt slows it down. | | timeline() | Scrub through time. The value is { time, playing }. Scrubbing takes over playback, and the clock keeps running with the panel closed. | | slider() | One number. Drag the bar, or click the readout and type. |

All of them: double-click to reset, arrow keys to nudge, Shift and Alt to change how fine the movement is.

Reading values

useAerial re-renders your component when its values change, which is what you want for anything that drives React. If something redraws every frame, read it without subscribing instead:

const aerial = useAerialRef();

useFrame(() => {
  mesh.rotation.set(...aerial.get("rot"));
});

Paths are the keys you wrote. The schema is flat, so there is nothing to nest and nothing to mistype. Two components using the same key share one control, which is usually what you want in a dev panel and the one thing to watch for.

Dragging a control only redraws that control, not the tree that declared it.

Not just for dev

Every row in the panel is a thin wrapper around a plain controlled component, so the same controls work in your product UI:

<Trackball value={rot} onChange={setRot} label="rotation" />
<XYPad defaultValue={[0, 0]} x={[-1, 1]} y={[-1, 1]} />
<Timeline duration={30} fps={24} markers={[{ time: 4.5 }]} />
<Slider value={gain} onChange={setGain} min={0} max={1} step={0.01} />

Theming

No stylesheet to import and no CSS framework. Styles inject themselves once, and every color is a custom property that the canvas controls read back out, so a theme reaches inside the drawings too.

setTheme({ accent: "oklch(65% 0.17 255)", bg: "oklch(20% 0.01 285 / 0.8)" });
<AerialPanel theme="light" width={300} vars={{ "--aerial-accent": "oklch(65% 0.17 255)" }} />

Panel

<AerialPanel
  title="controls"
  side="right"           // or "left". sticks to that top corner
  hotkey="`"             // toggles visibility, false to disable
  hidden={!import.meta.env.DEV}
/>

Writing a control

A control is a plain object. There is no registry, no type tag, and nothing in the panel that knows what a slider is:

type Control<V> = {
  value: V                                  // initial
  render: (path: string) => ReactNode       // its own row, label included
  sanitize?: (v: V) => V                    // clamp or quantize on the way in
  attach?: (path, store) => () => void      // optional driver, like the timeline clock
  label?: string
  hidden?: boolean
}

So slider() is a closure over its options:

export function slider(opts: SliderOpts = {}): Control<number> {
  const { min = 0, max = 1, step } = opts;
  const sanitize = (v: number) => clamp(step ? quantize(v, step, min) : v, min, max);
  return {
    value: sanitize(opts.value ?? clamp(0, min, max)),
    sanitize,
    render: (path) => <SliderRow key={path} path={path} opts={opts} />,
  };
}

useControl(path) is the only bridge between a component and the store. useDrag, useCanvas and usePalette are exported too, so a new control gets the same pointer, DPR and theming handling as the built-ins.

Development

pnpm install
pnpm dev          # docs site at :5173, library aliased to source with HMR
pnpm build        # library to packages/aerial/dist
pnpm typecheck
  • packages/aerial is the library
  • apps/docs is the landing page and visual dev environment