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

@nakednous/ui

v0.0.8

Published

ui — vanilla DOM UI components. Zero p5 deps.

Readme

@nakednous/ui

Parameter binding panels and animation transport controls — zero dependencies, pure vanilla DOM.


Installation

npm install @nakednous/ui
import { createPanel } from '@nakednous/ui'

Architecture

@nakednous/ui is the DOM layer of a three-package stack. It knows nothing about renderers or p5 — it mounts into any HTMLElement.

  application
      │
      ▼
  p5.tree.js        ← bridge: wires tree + ui into p5.js v2
      │
      ├── @nakednous/ui    ← this package: param panels, transport controls
      │
      └── @nakednous/tree  ← math, spaces, animation, visibility

The target contract is minimal: a plain function (name, value) => ... or an object with .set(name, value). Nothing renderer-specific. Shader wiring (setUniform) is handled by the p5.tree bridge, not here.


createPanel

The single public export. Dispatches on the first argument:

// track (has .play) → transport panel
createPanel(track, opt)

// plain schema object → parameter binding panel
createPanel(schema, opt)

The duck-type check is typeof first?.play === 'function'. Schema objects are plain config bags — none will ever have .play.


Parameter binding panel

Binds named schema keys to DOM controls. Each binding maps a parameter name to a control, and the control to a target sink.

import { createPanel } from '@nakednous/ui'

const panel = createPanel({
  speed:     { min: 0, max: 0.05, value: 0.012, step: 0.001 },
  shininess: { min: 1, max: 200,  value: 80,    step: 1,    type: 'int' },
  showGrid:  { value: true },
  tint:      { value: '#ff8844' },
  fxOrder:   { type: 'select', options: [
                 { label: 'noise → dof', value: '1' },
                 { label: 'dof → noise', value: '2' }
               ], value: '1' }
}, { x: 10, y: 10, width: 160, labels: true, title: 'Scene', color: 'white' })

// call every frame
panel.tick()

Type inference

| Schema value | Control | |--------------------|------------------| | number | slider | | boolean | checkbox | | CSS color string | color picker | | array length 2–4 | vec2/3/4 sliders | | options array | dropdown | | onClick function | button |

Override with { type: 'int', ... }.

Target

// plain function — called (name, value) each tick for dirty bindings
createPanel(schema, { target: (name, value) => myObj[name] = value })

// object with .set
createPanel(schema, { target: myObject })   // myObject.set(name, value)

// omitted — read manually
panel.speed.value()
panel.speed.set(0.02)
panel.speed.reset()
panel.speed.visible = false

Tick model

tick() pushes dirty bindings to target at most once per binding per frame. The first tick always pushes all bindings to initialise target state. Multiple interactions within a single frame collapse to one push at tick() time — correct for rendering sinks (shaders, scene params).

Per-binding API

panel.speed.value()       // current value
panel.speed.set(0.02)     // set programmatically — marks dirty, pushed on next tick
panel.speed.reset()       // restore initial value — marks dirty
panel.speed.visible = false
panel.speed.el            // raw HTMLElement(s)

Panel API

panel.el             // HTMLElement container
panel.visible        // get/set boolean — whole panel
panel.collapsed      // get/set boolean — body visibility (requires collapsible + title)
panel.each(fn)       // iterate bindings: fn(name, binding)
panel.elts()         // flat array of all bound DOM elements
panel.reset()        // reset all bindings
panel.parent(el)     // re-mount into a new HTMLElement
panel.tick()         // push dirty bindings — call once per frame
panel.dispose()      // remove from DOM

Layout options

| Option | Default | Description | |---------------|-----------------|------------------------------------------| | target | — | Value sink: (name,val)=>... or {set}. | | x | 0 | Container left (px). | | y | 0 | Container top (px). | | width | 120 | Default slider/select width (px). | | offset | 6 | Vertical gap between rows (px). | | labels | false | Show per-binding labels. | | title | — | Bold title row. | | collapsible | false | Title row becomes a collapse toggle. | | collapsed | false | Start collapsed (implies collapsible). | | color | — | Container text color. | | hidden | false | Start hidden. | | parent | document.body | Mount target (HTMLElement). |


Transport panel

Controls playback of any track-compatible target. Duck-typed: the target needs play, stop, seek, time, and playing.

import { createPanel } from '@nakednous/ui'

const ui = createPanel(track, {
  x: 10, y: 10, width: 170,
  loop: false, rate: 1,
  seek: true, props: true, info: true,
  color: 'white'
})

// call every frame
ui.tick()

Target contract

| Member | Required | Description | |---------------|----------|-------------------------------------------| | play(opts?) | ✓ | Start or update playback. | | stop() | ✓ | Stop playback. | | seek(t) | ✓ | Set normalised position [0, 1]. | | time() | ✓ | Returns normalised position [0, 1]. | | playing | ✓ | Boolean — true while playing. | | loop | ✓ | Boolean — read at panel creation time. | | bounce | ✓ | Boolean — read at panel creation time. | | rate | ✓ | Number — read at panel creation time. | | _onPlay | ✓ | Lib-space hook — assigned by this panel. | | _onEnd | ✓ | Lib-space hook — assigned by this panel. | | _onStop | ✓ | Lib-space hook — assigned by this panel. | | add(depth?) | optional | Add a keyframe. Enables the + button. | | reset() | optional | Clear all keyframes. Enables . | | info() | optional | Returns { keyframes, segments, ... }. |

Transport model

The Play/Pause button is the sole control that starts or stops playback. The rate slider adjusts speed without starting or stopping. The seek slider scrubs position without affecting playing. The loop and bounce checkboxes change looping behaviour without starting playback.

Loop modesloop and bounce are fully independent:

| loop | bounce | behaviour | |------|--------|-----------| | ☐ | ☐ | play once — stop at end | | ☑ | ☐ | repeat — wrap back to start | | ☑ | ☑ | bounce forever at boundaries | | ☐ | ☑ | bounce once — flip at far boundary, stop at origin |

Both checkboxes are always visible and independent of each other.

State initialisation

rate is seeded once at creation from the live track state (falling back to opt.rate) and is fully UI-owned thereafter — the panel never reads rate back from the track. loop and bounce are seeded the same way and additionally polled from the track every tick() while playing, so external play() calls are always reflected:

// play before createPanel — panel opens with correct state
track.play({ loop: true })
createPanel(track, ...)      // loop checkbox checked ✓

// createPanel before play — polled on next tick while playing
createPanel(track, ...)
track.play({ bounce: true }) // bounce checkbox checked ✓

Layout (top → bottom)

  Title row  — optional, becomes collapse toggle when collapsible=true
  [ + ]  [ ▶/⏸ ]  [ ↺ ]        — add / play-pause / reset
  depth: ──────────────        — placement depth (0 = near, 1 = far)
  seek:  ──────────────        — scrub position [0, 1]
  rate:  ──────────────        — signed speed (negative reverses)
  loop: [ ☐ ]  bounce: [ ☐ ]   — independent checkboxes, always visible
  t: 0.412  seg 1/3  kf 4      — info readout

Transport options

| Option | Default | Description | |---------------|-----------------|----------------------------------------------------------------------| | seek | true | Show seek slider. | | props | true | Show rate slider + loop controls. | | info | false | Show time/keyframe readout. | | rate | target.rate | Initial rate (seeded once; UI-owned after creation). | | loop | target.loop | Initial loop state (seeded from live track; polled while playing). | | bounce | target.bounce | Initial bounce state (seeded from live track; polled while playing). | | depth | 0.5 | Initial add-pose depth [0..1]. | | title | — | Optional title row. | | collapsible | false | Title row becomes a collapse toggle. | | collapsed | false | Start collapsed (implies collapsible). | | x | 0 | Container left (px). | | y | 0 | Container top (px). | | width | 120 | Slider width (px). | | rateWidth | width | Rate slider width override (px). | | depthWidth | width | Depth slider width override (px). | | color | — | Container text color. | | hidden | false | Start hidden. | | parent | document.body | Mount target (HTMLElement). |

Panel API

ui.el              // HTMLElement container
ui.visible         // get/set boolean
ui.collapsed       // get/set boolean (requires collapsible + title)
ui.parent(el)      // re-mount into a new HTMLElement
ui.tick()          // sync seek slider, play button, enabled state — call every frame
ui.dispose()       // remove DOM and clear lib-space hooks

Collapsible panels

Any panel with title set can be made collapsible. Clicking anywhere on the title row toggles the content area.

// explicit
createPanel(schema, { title: 'Noise', collapsible: true })

// start collapsed — implies collapsible
createPanel(track, { title: 'Camera path', collapsed: true })

Programmatic control:

panel.collapsed = true
panel.collapsed = false

License

AGPL-3.0-only
© JP Charalambos