Minor Changes
-
d7a4e25:
Timeline.Cliptakesadditive: every press on it is reported toonSelectedChange, as one with Shift, Cmd or Ctrl held is, so a selection can be built where there are no modifier keys, such as a touch screen. -
802f8bc: Envelope: bend the segments, and drag with finer control.
- Curved segments: a point takes an optional
curve, from -1 to 1, that bends the segment leaving it; 0 or unset keeps it straight. A positive curve holds the start of the segment and rushes its end, a negative one the opposite.Envelope.LineandEnvelope.Filldraw the bend. Envelope.Segments/Envelope.Segment: a handle on each segment's midpoint. Dragging it up or down bends the segment so its midpoint follows the pointer; the arrow keys bend it, and a double click or Delete straightens it. It is a slider on the curve, and carriesdata-flatwhen the two points are at the same level and there is nothing to bend. A moved point keeps its segment's curve.- Events:
onValueChangeandonValueCommittedreport bends withreason: "curve". - Fine drag: holding Shift while dragging a point moves it a quarter of the pointer's distance, without a jump when Shift is pressed or released.
- Types:
Envelope.Breakpointis{ x, y, curve? }, the type of the value. - Grid:
snaptakes the distance between grid lines in normalized units; the pointer snaps dragged and added points to it, and Cmd/Ctrl leaves the grid. - Adding on a curve: a point added inside a bent segment sits on the curve, and both halves are bent to follow it, so the shape is kept.
- Tab stops: the segment handles are one tab stop; Left and Right move between them.
- Alt gestures: as in a DAW's automation lane, Alt + drag on a segment bends it (reason
"curve"), Alt + double click straightens it, and Alt + click on a point removes it. They sit beside the segment handles, which stay for touch and keyboard. - Lost release: a drag now ends when its button was released where the page could not see it, outside the window or over a menu.
- Curved segments: a point takes an optional
-
d682f2f: Add the Envelope primitive: an interactive breakpoint envelope built on SVG.
value: { x, y }[](normalized 0-1, the consumer maps it to its own time/level domain) drives a set of draggable points connected by straight segments.- Parts:
Root,Graph(the SVG surface),LineandFill(the curve and the area under it),Points(one child per point) andPoint(the draggable handle). Every part takes arenderprop and state-awareclassName/style; points exposedata-dragging,data-removable,data-blocked,data-disabledanddata-index. - Points are added by double-clicking the graph and removed by double-clicking a point or pressing Delete/Backspace on the focused one, within
minPoints/maxPoints(equal values fix the shape); a refused action setsdata-blockedbriefly. Dragging only ever moves a point, and its x is clamped between its neighbors so points can never cross or reorder. onValueChange(points, eventDetails)/onValueCommitted(points, eventDetails), witheventDetails.reason("drag","keyboard","add","remove"),eventDetails.indexandeventDetails.cancel(). A dragged point follows the pointer even if a controlledvaluelags behind.- Keyboard: one roving tab stop; arrows nudge the focused point by
step(Shift for ten steps). EachPointis arole="slider": name it witharia-labeland map its coordinates to real units withgetAriaValueText(orformat/localeonRoot).
- Parts:
-
f8f6969: Rework the
FaderAPI. Breaking (pre-1.0):- Parts:
Root,Label,Value,Control(wasSlider),Track,Indicator(wasRange),Thumb. The decorativeThumbInner/ThumbMarkparts are gone, render your own elements insideThumb. - Types live on each part:
Fader.Root.Props,Fader.Root.State,Fader.Root.ChangeEventDetails, … (wasFader.RootProps). onValueChange(value, eventDetails)/onValueCommitted(value, eventDetails)(wasonValueCommit(value)), witheventDetails.reason("drag","track-press","keyboard","wheel", …) andeventDetails.cancel().- The accessible name goes on
Thumb(aria-label/aria-labelledby), which now renders a native<input type="range">. Disabled state is the presence attributedata-disabled.
New: the
renderprop and state-awareclassName/styleon every part, handler merging,name/formintegration,format/localewith a formattedFader.Valueandaria-valuetext,largeStep(Shift+Arrow), RTL. Kept: vertical -60…+6 defaults, mouse-wheel stepping, andtouch-action: noneonControl. While dragged, the thumb always follows the pointer even if a controlledvaluelags behind (reject values witheventDetails.cancel()).- New
anchor: the valueIndicatorfills from, such as0on a -1…1 pan, on whichever side of it the value is. - New
Mark: a graduation along the track at a value, withdata-activeonce the value has reached it from the anchor, anddata-orientation.
- Parts:
-
55451a8: Rework the
KnobAPI to matchFader. Breaking (pre-1.0):- Parts:
Root,Control(wasSlider: pointer drag, wheel, double-tap reset),TrackandIndicator(the rail and value arcs, wasArc),Thumb(rotates with the value, wasBody+ the needleIndicator), plus newValueandLabel. Draw your own pointer insideThumb. dragMode: "circular" | "vertical" | "revolution"replacesdragSensitivityanddragOptions(and its seven tuning thresholds)."vertical"is now a pure up/down drag.- The geometry props (
angleGapRad,angleOffset,angleRange,arcRadius,arcStrokeWidth,indicatorSpan,indicatorWidth) are gone;Track/Indicatortake astrokeWidth. onValueChange(value, eventDetails)/onValueCommitted(value, eventDetails)(wasonValueCommit(value)), witheventDetails.reason("drag","keyboard","wheel","reset","input-change") andeventDetails.cancel().- The accessible name goes on
Thumb(aria-label/aria-labelledby), which now renders a native<input type="range">(name/form,aria-valuetextfromformat/localeorgetAriaValueText). Without a name, the knob is no longer labelled "Knob" automatically. - Every part takes a
renderprop and state-awareclassName/style, and exposesdata-disabled/data-dragging.largeStep(default 10) drives Page Up/Down and Shift+Arrow keys. - New
scale:"linear"(default),"log"for frequency, Q and ratios (every decade the same share of the dial; needs a positivemin), or your own{ toPosition, toValue }. Drags move along the dial's travel; away from linear, keys and the wheel move 1% of it (10% with Shift and Page keys), always by at least onestep. Without adefaultValue, the knob starts and resets at the middle of its travel. - Holding Shift while dragging, in any
dragMode, turns the knob four times slower; small moves add up rather than rounding away. - New
Mark: a graduation at a value, rotated to where it sits on the dial's scale (its--knob-mark-angleturns a label back upright), withdata-activeonce the value has reached it from the anchor.
- Parts:
-
d682f2f: Add the Meter primitive: a multi-channel level meter (stereo output, channel strips, any multi-channel source). Display only, there is nothing to control.
- Parts:
Root(the group),Channels(one child per channel),Channel(arole="meter"),Track,Indicator(the lit part),Peak(the peak-hold mark),Value(the formatted level) andLabel. Channel parts exposedata-orientationanddata-clipping(at or aboveclipThreshold). - Levels come from
value/peakprops, or from a livesource({ getSnapshot, subscribe }, the shape of an external store). With a source the meter writes each frame straight to the DOM, so a signal updating 60 times a second never re-renders React; only a change in channel count does. - Each channel announces its level as a number (
format/locale, one decimal by default), not as a share of the range; add the unit withgetAriaValueText, e.g."-6 dB". Name channels witharia-labelorMeter.Label. - Peak-hold timing and decay stay outside the primitive: the meter has no clock.
- Parts:
-
c2a5437: Meter: a scale and a warning zone.
Meter.Markplaces a graduation at a level along the meter's length (upward for a vertical meter, rightward for a horizontal one), to hold a tick and a label such as -18.warnThresholdmarks the channels between that level and the clip threshold withdata-warning, for the amber of a level that is getting hot. A live clip now stays lit asdata-clippeduntil a click on the meter clears it.scalespreads the levels along the length:"linear"(the default) or"iec", the deflection of a digital peak meter, with more room at the top, where the registry element starts. A function gives your own curve. Marks and the indicator follow the same curve. -
4cf5a0b: Piano:
onNoteOnnow carries avelocity, from 0.25 to 1, in its event details. A pointer press near the free end of a key plays harder than one near its top, as on a real keyboard; a key pressed from the keyboard plays at 0.8.Knob, Fader and XYPad return to their default value (
defaultValue, or the middle) on a double click, an Alt + click or the Delete key, reportingreason: "reset". Meter: a live clip stays lit asdata-clippeduntil a click on the meter clears it. -
d682f2f: Add the Piano primitive: a keyboard to play, from
minPitchtomaxPitch(MIDI pitches, C3 to C5 by default) or exactly thepitchesgiven. Parts:Root,Keys(renders its children once per key),KeyandKeyLabel.- Events:
onNoteOn(pitch, eventDetails)andonNoteOff(pitch, eventDetails), withreason"press","glide"or"keyboard".eventDetails.cancel()inonNoteOnkeeps the key silent. - Pointer: a key sounds while pressed; dragging across the keys glides from one to the next, and each pointer holds its own key, so chords can be played on a touch screen.
activeNoteslights keys from elsewhere, such as a MIDI input. - Keyboard: each key is a
<button>named after its pitch ("C4"), behind one roving tab stop. The arrow keys walk the keys, an octave at a time with Shift or Page Up/Down, and Enter or Space holds the focused key until released. - Layout:
layout="keyboard"places the keys like a piano, black keys between the white ones;layout="even"gives each key the same share, the way a piano roll's rows are.orientation="vertical"puts the low end at the bottom. Keys exposedata-color,data-activeanddata-orientation.
Also exported from
@audio-ui/react/piano. - Events:
-
e836035: Add the PianoRoll primitive: notes on a timeline, edited by pointer and keyboard. It sits inside a
Timeline.Root, which gives it its length, zoom, grid and position, so a note'sstartandlengthare in the timeline's units (steps, beats, pulses) and every gesture lands on its grid unless Cmd/Ctrl is held. A note is{ id, pitch, start, length, velocity? }.Parts:
Root,Grid(a timeline lane),Rows/Row,Notes/Note,NoteHandle,Marquee,VelocityLane/Velocity, andKeys/Key/KeyLabelfor the keyboard down the side.- Rows: from
maxPitchat the top tominPitch; withfolded, only the pitches carrying notes get a row.--piano-roll-rowson the root counts them, so the grid and the keys can be sized per row, and every row-placed part sets--piano-roll-row.Rowbands a pitch across the grid withdata-color,data-octave(a C) anddata-active. - Drawing: with
mode="draw", a press on empty grid adds a note of the length last drawn or resized, and its end follows the pointer. Withmode="select"(the default), the press drags a selection rectangle (Marquee), Shift adding to the selection, and a double-click draws. Focused, the grid adds a note at the playback position with Enter, clears the selection with Escape and selects everything with Cmd/Ctrl+A. - Editing: dragging a note moves it, and the rest of the selection with it, keeping a chord's shape; with Alt the drag carries copies off. A
NoteHandleper edge resizes it, the start trimming it from the front. Shift + click toggles a note in the selection, a double-click removes it. Notes are toggle buttons (aria-pressedfor selected) in reading order: the arrow keys move them (Up and Down by a semitone, an octave with Shift), Shift + Left/Right changes their length, Enter or Space selects, Delete removes. - Velocity:
VelocityLaneis a timeline lane under the grid, with aVelocitybar per note at its start, as tall as its velocity. Dragging one moves the selection's velocities by the same amount, keeping a phrase's dynamics; a native range input inside takes the arrow keys. The velocity set last is what the next drawn note gets. - Events:
onValueChange/onValueCommittedcarryreason("draw","drag","keyboard","double-click","input-change") andedit("add","move","resize","duplicate","remove","velocity"). The selection is controlled or not (selection,onSelectionChange). While a gesture edits the notes they follow the pointer even when a controlledvaluelags. - Sound:
onNoteOn(pitch, eventDetails)/onNoteOffsay when to audition a pitch: a key pressed, a note drawn, picked up or moved to a new pitch.activePitcheslights keys and rows from elsewhere, such as the notes playing. - Keys: a
Pianolaid out on the roll's rows. Shift + press (or Shift + Enter) selects every note on a row.
- Rows: from
-
c118967: PianoRoll: editing commands from the keyboard, on the grid or on a note. Cmd/Ctrl+D repeats the selection right after itself and selects the copies; Cmd/Ctrl+C, X and V copy, cut and paste at the playback position; Q snaps the starts of the selection to the grid (every note when nothing is selected). They report
edit: "duplicate" | "paste" | "quantize" | "remove"withreason: "keyboard". -
02bba03: Plot: a
Plot.Handlereturns to itsdefaultValue(or the middle of the plot) on a double click, an Alt + click or the Delete key, along the axes it moves along, reportingreason: "reset"toonValueChangeandonValueCommitted, like Knob, Fader and XYPad. -
c378e46: Add the Plot primitive: two axes and what is drawn on them, for equalizers, filters and compressors. Values are in your own units, and each axis can be linear,
"log"(the way frequency is drawn) or your own{ toPosition, toValue }.- Root owns the axes (
minX/maxX/scaleX, and the same for y). - Graph is the drawing surface, an
<svg>stretched over the plot; Curve drawsy = fn(x)across it (withfillTo, closed down to a value as an area), Line a rule at an x or y value, Mark a label at a value on an axis, and Band a shaded stretch of one axis. - Grid draws grid lines and labels from the axes: round values spread along each axis you give, by powers of ten on a logarithmic one, or the
ticksyou name, withformatandcount;formatTickwrites a value short ("1k"). - Trace draws a live list of y values from a
source(a spectrum, a level history) across the x axis, painted straight in the DOM without a React render per frame, on a scale of its own withmin/max; Dot is a point that follows a live{ x, y }, such as the input level on a compressor's curve. - Handle is a point to drag, with a value in the axes' units and an
axisto hold it to one of them. It moves from where it is rather than jumping to the pointer, four times finer with Shift, and holds a native<input type="range">per axis for the keyboard and the accessible names (getAriaLabel,getAriaValueText). Events carryreason("drag","keyboard","input-change") and can be cancelled. - Cursor follows the mouse or pen over the plot (not touch), setting
--plot-cursor-x/--plot-cursor-yanddata-activein the DOM for crosshair lines; CursorReadout writes what is under it as text throughformat. - useValueAtPointer turns a pointer position into a value on the plot, to add a point where someone double-clicks.
Also exported from
@audio-ui/react/plot. - Root owns the axes (
-
8a34b12: Prune the public API down to audio primitives. Breaking (pre-1.0):
- Remove the
ChannelStripprimitive. It was layout only (section/header/div/footerwith no behavior) and now lives in the styled registry layer as plain markup. Faders and meters no longer inheritorientationfrom an enclosing strip, passorientationexplicitly. - Remove the
Transportprimitive. It duplicatedFader; build a seek bar withFader(orientation="horizontal",min/maxin your time unit) rendering any buffered range as your own element insideFader.Track. - Stop exporting the generic hooks (
usePointerDrag,useWheel,useKeyboardNavigation,useFocus,useControlledValue,useValueAsRef,useCallbackRef). They are internal implementation details, not audio primitives.
- Remove the
-
06453fd: The package depends on React alone:
@base-ui/reactis no longer a peer dependency, and an app that installed it only for audio/ui can remove it.Fader's parts are now the package's own. They behave as before (a press on the track jumps to the value under it, a press on the thumb drags it from where it was taken, the arrow, Page, Home and End keys and Shift move it through a native<input type="range">, the wheel steps it, a double click, Alt + click or Delete returns to the default) and keep their data attributes (data-orientation,data-disabled,data-dragging). What changes is in the types:Fader.Root.Props,Fader.Root.Stateand the event details are defined here, withformat,locale,largeStepandnameonFader.Root, andgetAriaValueTextandinputRefonFader.Thumb.Fader.Root.Stateis{ disabled, dragging, orientation, value }. AFadercosts about 5 KB gzipped where it cost about 19 KB with the slider it used to build on. -
d682f2f: Rebuild the Sequencer primitive: a grid of steps, a row per sound (
value: boolean[][]). Parts:Root,Rows,Row(named witharia-label),CellsandCell, each step a toggle button witharia-pressed.- Pointer: a press flips a step and dragging on paints the new state across every step the pointer crosses, found from the cells' own boxes.
- Keyboard: the steps are one tab stop; the arrow keys, Home and End (with Ctrl or Cmd, the corners of the grid) move between them, and Space or Enter flips one.
- Events:
onValueChange(value, eventDetails)withreason"press","paint"or"keyboard", cancellable;onValueCommittedwhen a gesture that changed the pattern ends. - Playhead:
source, the step being played as a live value, setsdata-currenton its cells straight in the DOM without re-rendering React; this replaces theactiveStepprop.
Also exported from
@audio-ui/react/sequencer. -
c90b335: Sequencer: each step has a velocity, from 0 to 1.
Sequencer.Velocityis a bar per step to put in a row under the cells: pressing it sets the velocity where the pointer is, dragging draws across the row, Alt + click or Delete returns the step to full, and a native range input inside takes the arrow keys, Page Up/Down and Home/End.Roottakesvelocities/defaultVelocities(a row per sound, likevalue; a step not listed plays at full) and reportsonVelocityChange/onVelocityCommittedwithreason: "drag" | "keyboard" | "input-change" | "reset". The pattern itself is unchanged. -
9c0f1ff: Make the package tree-shakable and add per-primitive entry points:
import { Knob } from "@audio-ui/react/knob"(the root@audio-ui/reactimport keeps working). Primitives are no longer TypeScriptnamespaceblocks, those compiled to side-effectful IIFEs, so importingKnobalone used to pull in every primitive (~21 KB gzip); it is now ~5.5 KB. Public names are unchanged (Knob.Root,Knob.RootProps, …). -
e8458ff: Add the clip to Timeline: a stretch of a source (a recording, a pattern) laid on a lane, with the model an arrangement needs.
- Clip takes a
valueof{ start, length, offset, fadeIn, fadeOut }in the timeline's units, andsourceLength. Dragging its ClipHeader, the title bar, moves it, on the grid unless Cmd/Ctrl is held; a press on the body selects it and puts the cursor there instead, and a drag scrubs. The change says which lane is under the pointer (eventDetails.lane, any element marked byTimeline.Lane), and a drop on another lane commits even when the position did not change. It follows the pointer vertically through--clip-drag-y, and sets--clip-offset,--clip-source-length,--clip-fade-inand--clip-fade-outso its content can show the stretch that is played.selected/defaultSelected/onSelectedChangehold the selection, and Delete or Backspace callonRemove. Its native<input type="range">moves it from the keyboard and carries the accessible name. - ClipHandle trims one edge: trimming the start moves where in the source the clip begins and leaves its content where it is, trimming the end stops where the source or the timeline does.
- ClipFadeHandle sets how long a fade lasts.
- ClipFade draws a fade as the DAWs do (the ramp as a line, what it attenuates lightly shaded).
fadeInShape/fadeOutShape("linear","fast","slow","smooth") set its curve; a fade handle steps through them on a double-click or Enter, andfadeGain,nextFadeShape,setClipFadeShapeandFADE_SHAPESare exported. - A move whose drag began with Alt held has
eventDetails.copy, for the app to add the dragged clip as a copy. - Lane takes
seekable: a press on its bare area moves the position and a drag scrubs it, on the grid unless Cmd/Ctrl is held, so the cursor can be put anywhere along a track. Presses on the parts in the lane go to those parts. coverClipslays a clip on a lane the way a DAW does: it covers what was under it, so a clip it lies over goes, one it overlaps by an edge is trimmed (its content staying in place), and one it lies inside is cut in two around it.adjacentPoint(points, from, direction)finds the next or previous point (a locator, a cue) from a position, for jumping between them.- A move reports
eventDetails.delta, how far the clip is from where the drag began, so an app can move a whole selection by the same amount, andonSelectedChangereports again a press that carries Shift, Cmd or Ctrl (eventDetails.event) so a selection can be added to or taken from. onDragOffsetChange(offset)follows a clip dragged up or down (in px from where the press began, and 0 when the drag ends), even when nothing moves along the lane, so the rest of a selection can follow it.- Events carry
eventDetails.target("move","trim-start","trim-end","fade-in","fade-out") andreason("drag","keyboard","input-change"). - The edits are plain functions too, for the model behind the clips:
moveClip,trimClipStart,trimClipEnd,setClipFadeIn,setClipFadeOut,splitClip,duplicateClipandclipEnd, exported from@audio-ui/reactand@audio-ui/react/timeline.
- Clip takes a
-
8ca3a5e: Add the Timeline primitive: the base for an arrangement view, a clip editor or a piano roll. Units are the consumer's (seconds, beats, pulses, steps); the primitive has no notion of tempo.
- Root owns the length (
duration), the zoom (zoom/defaultZoomin px per unit, clamped tominZoom…maxZoom,onZoomChange), the grid (snapStep,snapOffset) and the playback position (position/defaultPosition, or a livesourcefollowed outside React;onPositionChange/onPositionCommitted). Without a zoom it fits the width it is given. - Viewport is the scrolling window. Cmd/Ctrl + wheel (and trackpad pinch) zooms around the pointer, and every zoom change keeps its anchor point in place, or the middle of the view when there is none.
- Lane is a strip parts are placed on, as long as the timeline when zoomed. It sets
--timeline-unit, the width of one unit, so anything placed with it follows the zoom. - Ruler seeks: a press jumps to the pointer and a drag scrubs, on the grid unless Cmd/Ctrl is held, four times finer with Shift. It holds a native
<input type="range">for focus, the keyboard and the accessible name, announced throughgetAriaValueText. - Range is a stretch of the timeline (
[start, end]): a loop, a selection, a clip. Dragging its body moves it and a RangeHandle per edge resizes it, both on the grid unless Cmd/Ctrl is held and never shorter thanminLength; withdrawable, dragging across empty lane draws a new one. The range and each handle hold a native<input type="range">, so all of it works from the keyboard. Events carryeventDetails.edgeandreason("drag","draw","keyboard","input-change"). - Navigator is the whole timeline as one bar with the visible stretch inside it: dragging its NavigatorWindow scrolls, dragging a NavigatorHandle zooms while holding the other edge, a double-click fits everything. Window and handles hold native range inputs too.
- Mark places something at a value; Playhead follows the position through its own
--timeline-offsetvariable, without re-rendering React.
Also exported from
@audio-ui/react/timeline. - Root owns the length (
-
a8dda2c: Timeline:
Timeline.Rulerselects a stretch when you drag with Shift held, reported as[start, end]throughonSelectionChange/onSelectionCommitted(reason: "drag"), on the grid unless Cmd/Ctrl is held. Feed it to aTimeline.Rangeas a loop. A plain press and drag still move the position, and without the handler Shift changes nothing. -
7e7fc9f: Timeline has tracks:
Timeline.Trackis a row with a header beside a lane and a height you hold (height,onHeightChange,onHeightCommitted,selected),Timeline.TrackHeaderis the strip that names it and stays at the left edge while the lane scrolls, andTimeline.TrackResizeHandleresizes it by dragging or with the arrow, Page and Home/End keys through a native range input. A double-click on the handle clears the height (onHeightChange(undefined)), so the track shares the room again. -
1ad2ec3: Knob and Fader:
Valuetakeseditable. A double click on the readout, or Enter while it has focus, swaps it for a text field holding the value; Enter or leaving the field commits what was typed, Escape drops it. A unit or a decimal comma is fine ("-6 dB", "12,5"), the number is rounded tostepand clamped, and it is reported withreason: "input-change". The readout takes the pointer events it receives, so inside a thumb it does not also drag or reset. -
a0df6a2: Add the Waveform primitive: one family of parts for drawing audio, whether it is a file you scrub through or a signal as it happens.
- Data:
Roottakespeaks(at any resolution: 0-1 amplitudes, or{ min, max }with each slice's lowest and highest sample, which draws the waveform lopsided where the audio is) orlevels, a live signal ({ getSnapshot, subscribe }whose snapshot is the current levels). While a live signal is audible the root carriesdata-active;loadingmarks every part and makes the bars sweep. - Canvas draws on the device pixel grid: as many bars as fit its real width, the loudest amplitude kept per bar, every edge on a physical pixel at any pixel density or zoom level.
variantismirror,barsorline;barWidth,barGap,barRadiusandminBarHeightsize the bars,gainscales them. With a live signal,mode="static"spreads the current levels across the width (rising at once, falling smoothly, optionallymirrored, with anidleanimation) andmode="scrolling"records the loudest one over time atscrollSpeed, entering from the right, or from the middle outwards whenmirrored. Thescopevariant takes raw samples (-1 to 1) instead and traces them as one line oflineWidth. Colors come from CSS (--waveform-color,--waveform-progress-color,--waveform-bar-radius). - Bars / Bar draw the same levels as a fixed
countof elements, for a visualizer styled in plain CSS: each bar's level is its--waveform-levelvariable, and bars the position has passed carrydata-played. - Seek:
Controlis the seekable surface and holds a native<input type="range">; leave it out for a waveform that is only shown. A press jumps to the pointer and a drag scrubs; arrow keys step bystep, Page Up/Down and Shift + arrows bylargeStep, Home/End jump to the bounds.onValueChange(value, eventDetails)/onValueCommitted(value, eventDetails)carryeventDetails.reason("press","drag","keyboard","input-change") andeventDetails.cancel().valueruns from0toduration(default1). - Live position: pass a
sourceand the waveform follows it straight in the DOM, playhead, canvas and input, without re-rendering React. While the pointer scrubs, neither the source nor a late controlledvaluepulls the position back. The position is exposed as--waveform-position, and announced as the played share unlessgetAriaValueTextreturns something else. - On the timeline:
Regionis a stretch ([start, end]) that moves when its body is dragged, andRegionHandleresizes oneedgeby pointer or keyboard through its own native range input, stoppingminLengthshort of the other edge; their events carryeventDetails.edge.Markeris a point atvalue.PlayheadandThumbfollow the position, andHovershows at the hovered one. - Everything that moves is painted outside React, only when something changed, and not at all while off screen. With reduced motion requested, live waveforms repaint a few times a second, without smoothing, idle animation or sweep.
- Range:
rangeshows just a stretch of the audio,[start, end]indurationunits, such as a clip's window onto its source. Bars, seeking, the input's bounds, hover, regions and markers follow it; while the position is outside it the root carriesdata-outside. Canvases larger than browsers allow are drawn coarser rather than blank.
Also exports
createSource(initial): a{ getSnapshot, subscribe, set }store to push a live signal into any primitive's source yourself. - Data:
-
44e68fb: Waveform:
Waveform.Controlselects a stretch when you drag with Shift held, reported as[start, end]throughonSelectionChange/onSelectionCommitted(reason: "drag"), ready to feed aWaveform.Regionas a loop or a selection. A plain press and drag still seek, and without the handler Shift changes nothing. -
0dbd6ca: Rework the
XYPadAPI to matchFaderandKnob. Breaking (pre-1.0):- Parts:
Root,Control(wasSlider: press, drag, wheel),Thumb(wasCursor),Value(wasValueDisplay) andLabel. The decorative parts (Grid,GridLine,Crosshair,CursorGlow,CursorDot,CursorHighlight) are gone: draw grids and crosshairs with your own elements and the new--xy-pad-x/--xy-pad-yCSS variables. onValueChange(value, eventDetails)/onValueCommitted(value, eventDetails)(wasonValueCommit(value)), witheventDetails.reason("press","drag","keyboard","wheel","input-change") andeventDetails.cancel().Thumbrenders one native<input type="range">per axis, named fromaria-label("… X" / "… Y") orgetAriaLabel(axis). The pad no longer injects an English "XY Pad" label, usage instructions, or a live status region.defaultValuenow defaults to the center of the pad (was the bottom-left corner).Home/Endmove X to its min / max (were the top-left / bottom-right corners);Shift+ arrows move 10 steps. The wheel follows the scroll direction: up raises Y, right raises X.- Every part takes a
renderprop and state-awareclassName/style, and exposesdata-disabled/data-dragging.format/localefeedXYPad.Valueand each input'saria-valuetext.XYPad.Valuejoins the two axes with "; " in locales whose decimal mark is a comma, so "2,5; 0,5" can't be misread.
Also:
Fader,KnobandXYPadtreat a non-finite controlled value (NaN,±Infinity) asmin;KnobandXYPadcommit whenever a pointer gesture changed the value, even if it ends back on its starting value. - Parts:
-
288d5f0: XY Pad takes a
name: its two native inputs submit with a form asname.xandname.y, as the Knob and the Fader already do with their one.
Patch Changes
- 889e175: Fix the package's types under
moduleResolution: "node16"and"nodenext": the published declarations imported each other without a file extension, which those settings cannot resolve. They now carry the extensions. The package passespublint, andarethetypeswrongfinds no problem for any entry under its ESM-only profile. The README now introduces the package. - 2c9116c: Fix
disabledcursor feedback on Fader, Knob, and XYPad:Rootnow exposesdata-disabledso the styled registry layer can putcursor-not-allowedon a wrapper that stays hoverable, instead of on the samepointer-events-nonesurface where it could never actually resolve. - 6724811: Knob, XYPad, Envelope, Meter and Plot format numbers with a fixed language during server rendering and hydration, then with the browser's, so a page rendered on the server no longer hydrates with a different
aria-valuetext(such as "1 000" against "1,000"). Alocaleprop still wins. - 610df18:
Meter's parts are the package's own:Meter.Channelis therole="meter"with itsaria-valuenow,aria-valuetextandaria-labelledby,Meter.Labelnames the channel it sits in,Meter.Track,Meter.IndicatorandMeter.Valuerender as before. Their props and state types are now defined here. Arenderfunction onMeter.ChannelandMeter.Indicatorreceives the channel's state (clipping,warning,orientation,index). - 81f33f1: Every part renders and merges its props with the package's own code: the
renderprop (an element, or a function of the props and the state), thedata-*attributes drawn from each part's state, and the chaining of event handlers, class names and styles all behave as before, but no longer come from another library. The size budgets count this code since it is now part of each entry. - 8a34b12: - Ship with zero runtime dependencies: the few helpers previously pulled from
@audio-ui/utils(declared as"latest", so installs were not reproducible) are now bundled.- Fix
Knobignoring its documented default: withoutdefaultValue, it now starts at, and double-tap resets to, the midpoint ofmin…maxinstead of0. - Set
touch-action: noneon the drag surfaces ofKnob,Fader,XYPad,SequencerandEnvelopepoints, so touch drags no longer scroll the page when the primitives are used without the registry styles.
- Fix
- ffe9c39: Parts that take focus when pressed (Knob, XYPad, Envelope points, Waveform control and regions, Timeline ruler and range handles, PianoRoll grid, notes and velocity bars, Piano keys) no longer show the keyboard focus ring for a pointer press:
:focus-visiblestays for keyboard focus. - a26539d: Built on
@audio-ui/core, which installs with it. The exports are unchanged:createSourceand theSourcetypes, still available from@audio-ui/reactand@audio-ui/react/source, now come from the core. - 20c47b4: Add
bun run sizeto the package: it bundles each entry (React left out), gzips it and fails when it grows past its budget, so a primitive cannot get heavier unnoticed. XY Pad's tests now cover its state attributes, per-axis ranges and steps, the native inputs and its label. - 4b8c30d:
Timeline.Viewportadds up the wheel events that arrive within one frame and zooms once, so a trackpad sending several per frame no longer lays the timeline out several times;Timeline.Laneis laid out and styled apart from the rest of the page (contain), which makes each zoom step cheaper. - Updated dependencies [a26539d]
- @audio-ui/core@0.1.0