devuiGuideDemosAPIGitHub

API

Every entry point's exports, from the types. Generated from the source on each build.

@haxiomic/devui

apiVersion

the extension API's version: it changes only when an extension written for it would break

const apiVersion: 1

type AxisName

type AxisName = '+x' | '-x' | '+y' | '-y' | '+z' | '-z'

build

const build: { commit: string; date: string; } | null

interface ChangeEvent

interface ChangeListenOptions

type ChangeSource

Who caused a change. Only user-ish sources become persisted overrides or undo entries. external is the app's own write, seen by polling; compare is an A/B preview, timeline a timeline playing and driver an audio band or other driver moving it: shown, never saved or recorded in history.

type ChangeSource = 'user' | 'code' | 'external' | 'load' | 'preset' | 'undo' | 'compare' | 'timeline' | 'driver'

interface Codec

Native value ⇄ canonical value. Writes mutate in place where the native is an object.

interface Command

Something the palette, menus and hotkeys can run.

class Compare

class Control

createGUI()

a new, independent GUI with the std extension registered

function createGUI(options?: GUIOptions): GUI

curve()

a curve: curve([[0, 0], [0.5, 1], [1, 0]]), curve(easing('easeInOutCubic'))

function curve(keys?: readonly KeyInput[], extrapolate?: Extrapolate): Curve

class Curve

interface CurveValue

defineExtension()

An extension, checked and stamped with the API version it was written for. Register it with gui.use(ext) (or createGUI({ extensions: [ext] })).

function defineExtension<E extends Omit<Extension, 'apiVersion'>>(def: E): E & { apiVersion: number; }

interface Drops

what a folder is, dropped into lists, and what it takes (see Hints.drops)

type Ease

type Ease = 'linear' | 'in' | 'out' | 'inOut'

easing()

an easing as two bezier keys, from (0,0) to (1,1)

function easing(name: EasingName | readonly [number, number, number, number]): Key[]

type EasingName

type EasingName = keyof typeof easings

easings

CSS-style easings as cubic-bezier control points (easings.net's fits)

const easings: { readonly linear: readonly [0, 0, 1, 1]; readonly ease: readonly [0.25, 0.1, 0.25, 1]; readonly easeIn: readonly [0.42, 0, 1, 1]; readonly easeOut: readonly [0, 0, 0.58, 1]; readonly easeInOut: readonly [0.42, 0, 0.58,…

type EulerOrder

type EulerOrder = 'XYZ' | 'YXZ' | 'ZXY' | 'ZYX' | 'YZX' | 'XZY'

interface Extension

type Extrapolate

what happens outside the keys: hold the end values, loop, go back and forth, or keep the slope

type Extrapolate = 'hold' | 'loop' | 'pingpong' | 'continue'

class Folder

interface Glass

How see-through the panel, its windows and menus are, on any surface: opacity, how much of its own colour the panel keeps (1: opaque; menus and fields keep more), and blur, how far the page behind is blurred (px)

interface GPUTextureSource

what you pass to view a WebGPU texture: the texture and its device (a GPUTexture can't tell you)

interface Group

gui

the default GUI; its panel mounts the first time something is added

const gui: GUI

class GUI

interface GUIEvents

interface GUIOptions

interface Hints

Everything a caller can say about a control. Inference fills in what's missing.

interface Insets

px that windows docked full length against each edge of the browser window cover

interface InspectOptions

filling a folder from an object (add(obj, opts), folder.inspect(obj, opts))

interface Inspector

Lays out a whole known object, e.g. gui.add(material), through the public API only.

type Interp

Curves: keyframes of a value over a domain (0–1, or time), for mapping and animation. A live value like a ramp: the panel edits it in place, and your code samples it (sample, lut). Works the same in production builds (the noop build exports it too). Each segment runs from a key to the next with the left key's interpolation: - smooth: automatic tangents that never overshoot (monotone cubic) - bezier: the key's own handles (out, and the next key's in) - linear, or step (hold until the next key)

type Interp = 'smooth' | 'bezier' | 'linear' | 'step'

type Json

type Json = null | boolean | number | string | Json[] | { [key: string]: Json }

interface Key

interface KeyClip

copied keys, times from the first

type KeyInput

[x, y], [x, y, interp], or a full key

type KeyInput = [x: number, y: number, interp?: Interp] | Key

type KeyOf

a property of T to bind: one of its keys, or an index when T is an array

type KeyOf = (keyof T & string) | (T extends readonly unknown[] ? number : never)

type KeySelection

keys by track: path → key times

type KeySelection = Record<string, number[]>

class Layout

interface LayoutDoc

interface LazyRowView

a row view loaded the first time a row needs it: load: () => import('./my-view')

interface LifetimeHook

Hooks a control's lifetime to an object, e.g. three.js dispose events.

interface MatchContext

interface Matcher

interface MountedView

a row view drawn by an extension: what mount returns

interface OpenWindowOptions

interface OptionItem

interface OrientationHints

a rotation shown on the orientation sphere (view 'orientation', and 'direction')

type Params

{ curl: { value: 30, min: 0 } } → { curl: number }

type Params = { -readonly [K in keyof S]: S[K] extends { value: infer V } ? (S[K] extends Record<string, unknown> ? V : S[K]) : S[K]; }

class Perf

interface PerfOptions

PINNED

const PINNED: 'pinned'

class Plot

interface PlotOptions

interface Preset

class Presets

ramp()

a colour ramp: ramp(['#000', '#f80', '#fff']), ramp([[0, 'navy'], [0.7, '#f0f']], 'oklch'), ramp([...], 'oklab', { smooth: true })

function ramp(stops?: readonly StopInput[], mode?: RampMode, options?: RampOptions): Ramp

class Ramp

type RampMode

how colour runs between stops: perceptual (oklab, straight; oklch, round the hue the short way; oklch-long, the long way), light (linear), as stored (srgb), or bands (step)

type RampMode = 'oklab' | 'oklch' | 'oklch-long' | 'linear' | 'srgb' | 'step'

interface RampStop

a stop: its place along the ramp (0–1) and its colour (linear sRGB)

interface RampValue

a ramp's value, as the panel edits and saves it

interface RawGPUTexture

A raw WebGPU texture, with the device it belongs to (a GPUTexture can't tell you).

interface RawWebGLTexture

A raw WebGL2 texture plus the facts GL can't tell us cheaply.

interface Resolution

type ResolvedTexture

Something drawable by the texture viewer, resolved from a library object.

type ResolvedTexture = | { kind: 'webgl'; texture: import('../gpu/types').RawWebGLTexture; /** a multi-attachment source (MRT): names to switch between, `attachment` picks one (-1 is depth) */ attachments?: string[]; /** the extension can capture each write to it (`capture: 'on-write'`) */ onWrite?: boolean; } | { kind: 'webgpu'; texture: import('../gpu/types').RawGPUTexture; attachments?: string[]; onWrite?: boolean; …

interface RowView

A row view an extension draws, for controls whose view is its id. It draws into a plain element (the row's value cell, in the panel's shadow root: style it with the panel's CSS variables, --accent, --fg, --row, --row-h…), with any framework or none.

interface Spec

Hints after inference, with options normalised.

std

const std: { name: string; types: ValueType<any>[]; matchers: Matcher[]; textureSources: TextureSourceResolver<any>[]; lifetimes: never[]; } & { apiVersion: number; }

type StopInput

a CSS colour, a 0xrrggbb number, linear RGBA, or any of those at a place: [0.3, '#f80']

type StopInput = string | number | RGBA | [t: number, color: string | number | RGBA]

type Surface

How the panel's surfaces are drawn, in any theme: 'flat', tones apart (the default); 'soft', depth: each open folder a card raised on a soft shadow, what shows data set in; or 'inset', soft's inverse: each open folder a tray set into the panel, its controls raised in it. Each can be see-through too (Glass, translucent)

type Surface = 'flat' | 'soft' | 'inset'

type TextureKind

type TextureKind = '2d' | 'cube' | '2d-array' | '3d'

interface TextureSourceResolver

interface TextureViewState

A texture view's settings: a texture control's (Control.texture), kept in the layout.

interface Theme

The panel's colours: a few you choose, the rest derived from them (in OKLCH) the way the default theme's are. Any CSS colour works (hex, rgb(), hsl(), oklch()).

class Timeline

interface TimelineJSON

interface TimelineOptions

interface Track

interface TrackJSON

type TrackKind

type TrackKind = 'number' | 'vector' | 'color' | 'boolean'

interface ValuesDoc

interface ValueType

Canonical semantics of a value type, shared by every codec of that type.

interface ViewContext

what a row view is given, and how it changes the value

interface ViewDrag

a view's drag (ViewContext.drag)

interface WindowRect

type WindowShows

what a window shows: a folder, a group, a texture's inspector, a control's editor (a curve's) or a timeline. One window each: a folder's, in whatever form (WindowState.compact), is the one place it shows outside the panel's lists

type WindowShows = { folder: string } | { group: string } | { texture: string } | { editor: string } | { timeline: string }

interface WindowState

@haxiomic/devui/three

colorCodec

THREE.Color in the working colour space ⇄ canonical linear sRGB

const colorCodec: Codec<THREE.Color, RGBA>

curveTexture()

function curveTexture(curve: Curve, size?: number): THREE.DataTexture

eulerCodec

const eulerCodec: Codec<THREE.Euler, number[]>

quaternionCodec

quaternions are edited as Euler angles

const quaternionCodec: Codec<THREE.Quaternion, number[]>

rampTexture()

function rampTexture(ramp: Ramp, size?: number): THREE.DataTexture

threeExtension()

function threeExtension(options?: ThreeExtensionOptions): Extension

interface ThreeExtensionOptions

interface WebGPURendererLike

three's WebGPURenderer (from 'three/webgpu'), as far as devui needs it. Its backend is WebGPU, or WebGL2 where there's no WebGPU; devui views textures in whichever it is.

@haxiomic/devui/frames

bytesLabel()

bytes: B, then KB, MB and GB (of 1024), a tenth shown below 10 of each

function bytesLabel(n: number): string

Clear

const Clear: { readonly Color: 1; readonly Depth: 2; readonly Stencil: 4; }

interface FrameCaptureOptions

which frame a capture takes (FrameScope.capture)

interface FrameCounts

what a frame did, summed

class FrameRecord

one frame, as columns

frames()

the frame scope for a context, device or renderer, in a folder of gui (one per context)

function frames(gui: GUI, target: FrameTarget, opts?: FramesOptions): FrameScope

class FrameScope

interface FramesOptions

type FrameTarget

what can be attached: a WebGL2 context, a WebGPU device, or a three.js renderer

type FrameTarget = WebGL2RenderingContext | WebGLRenderingContext | GPUDevice | ThreeRendererLike | ThreeWebGPURendererLike

type Id

a resource's id: small integers, from a WeakMap on the API object

type Id = number

interface Memory

GPU memory the recorder has seen allocated, by kind

Mode

a draw's primitive mode, as GL numbers it (WebGPU maps onto these)

const Mode: { readonly Points: 0; readonly Lines: 1; readonly LineLoop: 2; readonly LineStrip: 3; readonly Triangles: 4; readonly TriangleStrip: 5; readonly TriangleFan: 6; readonly Dispatch: 7; }

observeThree()

A frame scope on each three.js renderer an app makes, through three's own devtools hook: for an app that bundles its own three (no import map, no global THREE), whose classes can't be reached. three announces each WebGLRenderer as it's made (an observe event on __THREE_DEVTOOLS__), so call this before the app's bundle runs: its renderers then have scopes with three's names, and GPU memory counted from the start. onScope hears of each; the scopes made are in scopes; off() stops. const three = observeThree(gui, { history: 120, onScope: (scope) => console.log(scope.folder.path) });

function observeThree(gui: GUI, opts?: FramesOptions & { onScope?: (scope: FrameScope) => void; }): { scopes: FrameScope[]; off(): void; }

PassFlag

const PassFlag: { readonly Devui: 1; readonly Blit: 2; readonly Compute: 4; readonly CopyOut: 8; readonly Earlier: 16; readonly Split: 32; }

primitives()

primitives a draw makes: triangles, lines or points (none for a dispatch)

function primitives(mode: number, count: number): number

class Recorder

what a frame scope reads: the frames, the resources and their names

interface ResourceInfo

interface ScopeTiming

whether a scope's passes are timed on the GPU, as it is now

interface ThreeWebGPURendererLike

a three.js WebGPURenderer (after await renderer.init()): its backend's device, or the WebGL2 context it fell back to

watchFrames()

Record a WebGL2 context or a GPUDevice from now, before there's a scope to show it: the scope attached later (frames(gui, context)) knows what it made meanwhile (its textures and buffers, counted in its memory; on WebGPU, made copyable for a capture), as if it had been there from the start. Nothing shows until then. Its draws are recorded only until its first frame that draws or dispatches, which onDraw hears of: a context made only to test for support never does. Returns a stop, which lets go of it if no scope has taken it over.

function watchFrames(context: WebGL2RenderingContext | WebGLRenderingContext | GPUDevice, opts?: Pick<FramesOptions, 'timing' | 'history'> & { onDraw?: () => void; }): () => void

@haxiomic/devui/shader

interface BlockLayout

interface CompileError

detectLang()

GLSL or WGSL, by the look of it

function detectLang(source: string): ShaderLang

layoutBlocks()

each uniform buffer's layout, in source order

function layoutBlocks(uniforms: UniformInfo[]): BlockLayout[]

interface Member

pack()

a block's values (by uniform name) into its bytes

function pack(block: BlockLayout, value: (name: string) => unknown, out?: ArrayBuffer): ArrayBuffer

parseShader()

function parseShader(source: string, lang?: ShaderLang): ParsedShader

shader()

a shader's uniforms as controls in gui (a GUI or folder)

function shader(gui: FolderLike, source: string, opts?: ShaderOptions): Shader

class Shader

type ShaderLang

type ShaderLang = 'glsl' | 'wgsl'

interface ShaderOptions

interface UniformInfo

interface UniformType

a uniform's type: its base, its component count, and for WGSL its size and alignment

@haxiomic/devui/audio

audio()

audio levels, spectrum and bands in a folder of gui; bands can drive controls

function audio(gui: GUI, source: Source, opts?: AudioOptions): AudioInput

class AudioInput

interface AudioOptions

interface BindOptions

@haxiomic/devui/midi

midi()

MIDI learn for gui's controls (asks the browser for MIDI access)

function midi(gui: GUI, opts?: MidiOptions): Midi

class Midi

interface MidiAccessLike

what's used of the Web MIDI API

interface MidiInputLike

interface MidiMapping

interface MidiOptions

noteName

function noteName(n: number): string

@haxiomic/devui/remote

broadcast()

windows of one origin: the pop-out

function broadcast<Out, In>(name: string): Transport<Out, In>

client()

function client(transport: Transport<ClientMessage, HostMessage>, opts?: ClientOptions): RemotePanel

type ClientMessage

type ClientMessage = | { t: 'hello' } | { t: 'set'; path: string; value: Json; transient: boolean } | { t: 'press'; path: string } | { t: 'begin' } | { t: 'end'; cancel?: boolean } | { t: 'materialise'; path: string } /** still here (every few seconds): an app that stops hearing these shows the panel as gone */ | { t: 'ping' } | { t: 'bye' }

interface ClientOptions

type HostMessage

type HostMessage = | { t: 'structure'; title: string; nodes: NodeSnap[] } | { t: 'values'; values: Record<string, Json> } | { t: 'plots'; samples: Record<string, (number | Record<string, number>)[]> } | { t: 'bye' }

type NodeSnap

type NodeSnap = | { k: 'folder'; path: string; label: string; collapsed: boolean; lazy: boolean; order: number } | { k: 'control'; path: string; label: string; type: string; view: string; spec: Json; value: Json; readonly: boolean; hidden: boolean; order: number; description?: string } | { k: 'action'; path: string; label: string; order: number; description?: string; key?: string } | { k: 'plot'; path: string; l…

popOut()

The panel in a window of its own (a second monitor). It's this GUI's own panel, drawn there by the window's own copy of devui's UI, so everything in it works as it does here: texture views and inspectors, the frame scope, captures. This window's panel comes down until that one closes.

function popOut(gui: GUI, features?: string): { window: Window; close(): void; }

popOutWindow()

One of the panel's windows in a browser window of its own, filling it (a capture on a second monitor): id is its id in the layout (gui.layout.windows). It's drawn there as popOut draws the panel, with any window opened from it meanwhile; the panel and the rest stay here, and it comes back when that window closes.

function popOutWindow(gui: GUI, id: string, features?: string): { window: Window; close(): void; }

qrPath()

a QR code as an SVG path (one unit per module), with its size

function qrPath(text: string): { d: string; size: number; }

relay()

Through the dev server (devui's Vite plugin): messages posted to a room reach the others in it, as server-sent events. base is the server's devui endpoint, e.g. http://192.168.1.5:5173/__devui/.

function relay<Out, In>(base: string, room: string): Transport<Out, In>

interface RemotePanel

resumeShare()

this tab was sharing before it reloaded: share the same room again, so the device carries on

function resumeShare(gui: GUI): Promise<Share> | null

serve()

function serve(gui: GUI, transport: Transport<HostMessage, ClientMessage>): Session

interface Session

interface Share

shareToDevice()

The panel on another device: joins a new room on the dev server's relay and shows a QR code (and the address) to open it with. Needs devui's Vite plugin, and the dev server reachable on your network (vite --host).

function shareToDevice(gui: GUI, opts?: { dialog?: boolean; room?: string; resumed?: boolean; }): Promise<Share>

interface Transport

a way to talk: a BroadcastChannel, or the dev server's relay

@haxiomic/devui/react

DevUIProvider()

use a GUI other than the default gui below this point

function DevUIProvider({ gui, children }: { gui: GUI; children?: ReactNode; }): import("react").FunctionComponentElement<import("react").ProviderProps<GUI | null>>

releaseGraceMs

const releaseGraceMs: 250

useButton()

a button; fn can be a fresh closure each render

function useButton(label: string, fn: () => void, hints?: Hints & Where): Control | null

useControl()

bind one property (type, range and view inferred); null until mounted

function useControl<T extends object, K extends keyof T & string>(target: T | null | undefined, key: K, hints?: Hints & Where): Control | null

useFolder()

the folder at a path (created on first use)

function useFolder(path: string, hints?: Hints): Folder

useGUI()

function useGUI(): GUI

useMonitor()

a read-only readout of a property, polled while on screen; view: 'graph' plots it

function useMonitor<T extends object, K extends keyof T & string>(target: T | null | undefined, key: K, hints?: Hints & PlotOptions & Where): Control | Plot | null

useParams()

Declare parameters in a folder and get the live object back. The same object across renders (and StrictMode's double render); editing the schema under Fast Refresh rebinds it, keeping tweaks. Re-renders the component on change unless rerender: false (then read it in a frame loop).

function useParams<S extends Record<string, unknown>>(folder: string, schema: S, opts?: { rerender?: boolean; }): Params<S>

usePlot()

a live plot: push values from your frame loop with plot?.push(v)

function usePlot(label: string, opts?: PlotOptions & Where): Plot | null

useTexture()

a texture view of anything an extension can resolve (canvas, raw WebGL texture, three.js texture…)

function useTexture(label: string, source: unknown, hints?: Hints & Partial<TextureViewState> & Where): Control | null

useValue()

the current value of a bound property; re-renders when it changes (from the panel or code)

function useValue<T extends object, K extends keyof T & string>(target: T, key: K, hints?: Hints & Where): T[K]

type Where

where a hook's control goes: a folder path ('A/B'), or the root

type Where = { folder?: string }