@vsreact/core 0.0.9 → 0.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,3 +8,9 @@ export { Button } from "./button";
8
8
  export type { ButtonProps } from "./button";
9
9
  export { Tooltip, Modal } from "./popover";
10
10
  export type { TooltipProps, ModalProps } from "./popover";
11
+ export { NumberBox, Checkbox, RadioGroup, ParamNumberBox, ParamCheckbox, ParamRadioGroup, snapToStep, } from "./fields";
12
+ export type { NumberBoxProps, CheckboxProps, RadioGroupProps, ParamNumberBoxProps, ParamCheckboxProps, ParamRadioGroupProps, } from "./fields";
13
+ export { ProgressBar, Spinner } from "./feedback";
14
+ export type { ProgressBarProps, SpinnerProps } from "./feedback";
15
+ export { MacroPad, ParamMacroPad, HardwareKnob, ParamHardwareKnob, Crossfader, ParamCrossfader, PulseOrb, } from "./specialty";
16
+ export type { MacroPadProps, ParamMacroPadProps, HardwareKnobProps, ParamHardwareKnobProps, CrossfaderProps, ParamCrossfaderProps, PulseOrbProps, } from "./specialty";
@@ -10,3 +10,6 @@ export { Meter, usePeakHold, peakHoldStep } from "./meter";
10
10
  export { Bars, Waveform, useRollingBuffer, pushRolling } from "./visualizers";
11
11
  export { Button } from "./button";
12
12
  export { Tooltip, Modal } from "./popover";
13
+ export { NumberBox, Checkbox, RadioGroup, ParamNumberBox, ParamCheckbox, ParamRadioGroup, snapToStep, } from "./fields";
14
+ export { ProgressBar, Spinner } from "./feedback";
15
+ export { MacroPad, ParamMacroPad, HardwareKnob, ParamHardwareKnob, Crossfader, ParamCrossfader, PulseOrb, } from "./specialty";
@@ -40,6 +40,8 @@ export interface SliderProps {
40
40
  height?: number;
41
41
  /** Vertical fader — drag up for more, fill rises from the bottom. */
42
42
  vertical?: boolean;
43
+ /** Flat hardware-style bar thumb instead of the dot. */
44
+ barThumb?: boolean;
43
45
  label?: string;
44
46
  disabled?: boolean;
45
47
  /** Double-click resets to this (DAW convention). */
@@ -52,7 +54,7 @@ export interface SliderProps {
52
54
  onBegin?: () => void;
53
55
  onEnd?: () => void;
54
56
  }
55
- export declare function Slider({ value, width, height, vertical, label, disabled, defaultValue, wheelSensitivity, trackColor, valueColor, onChange, onBegin, onEnd, }: SliderProps): import("react").JSX.Element;
57
+ export declare function Slider({ value, width, height, vertical, barThumb, label, disabled, defaultValue, wheelSensitivity, trackColor, valueColor, onChange, onBegin, onEnd, }: SliderProps): import("react").JSX.Element;
56
58
  export interface ParamSliderProps {
57
59
  paramId: string;
58
60
  label?: string;
@@ -67,6 +69,9 @@ export declare function ParamSlider({ paramId, label, ...rest }: ParamSliderProp
67
69
  export interface ToggleProps {
68
70
  on: boolean;
69
71
  label?: string;
72
+ /** Side captions, hardware style: <Toggle offLabel="OFF" onLabel="ON" />. */
73
+ offLabel?: string;
74
+ onLabel?: string;
70
75
  /** Track height; width is 1.8×. Default 22. */
71
76
  size?: number;
72
77
  disabled?: boolean;
@@ -76,7 +81,7 @@ export interface ToggleProps {
76
81
  onChange: (on: boolean) => void;
77
82
  }
78
83
  /** A switch with a spring-animated thumb — bypass, on/off, A/B. */
79
- export declare function Toggle({ on, label, size, disabled, trackColor, onColor, thumbColor, onChange, }: ToggleProps): import("react").JSX.Element;
84
+ export declare function Toggle({ on, label, offLabel, onLabel, size, disabled, trackColor, onColor, thumbColor, onChange, }: ToggleProps): import("react").JSX.Element;
80
85
  export interface ParamToggleProps {
81
86
  paramId: string;
82
87
  label?: string;
@@ -148,8 +153,9 @@ export interface GenericEditorProps {
148
153
  trackColor?: string;
149
154
  valueColor?: string;
150
155
  }
151
- /** The zero-effort editor: one knob per APVTS parameter, laid out in
152
- rows. `render(<GenericEditor />)` is a complete, working plugin UI. */
156
+ /** The zero-effort editor: one knob per APVTS parameter with a live
157
+ value label and name under each, laid out in rows.
158
+ `render(<GenericEditor />)` is a complete, working plugin UI. */
153
159
  export declare function GenericEditor({ columns, size, trackColor, valueColor }: GenericEditorProps): import("react").JSX.Element;
154
160
  export interface SelectProps {
155
161
  options: string[];
package/dist/controls.js CHANGED
@@ -50,7 +50,7 @@ export function ParamKnob({ paramId, label, ...rest }) {
50
50
  const param = useParameter(paramId);
51
51
  return (_jsx(Knob, { value: param.value, text: param.text, label: label ?? param.name.toUpperCase(), defaultValue: param.defaultValue, onChange: param.set, onBegin: param.begin, onEnd: param.end, ...rest }));
52
52
  }
53
- export function Slider({ value, width = 160, height = 160, vertical, label, disabled, defaultValue, wheelSensitivity = 0.4, trackColor = "#2A2F27", valueColor = "#C6F135", onChange, onBegin, onEnd, }) {
53
+ export function Slider({ value, width = 160, height = 160, vertical, barThumb, label, disabled, defaultValue, wheelSensitivity = 0.4, trackColor = "#2A2F27", valueColor = "#C6F135", onChange, onBegin, onEnd, }) {
54
54
  const startValue = useRef(0);
55
55
  const clamped = clamp01(value);
56
56
  const onDragStart = disabled
@@ -70,9 +70,9 @@ export function Slider({ value, width = 160, height = 160, vertical, label, disa
70
70
  ? undefined
71
71
  : (e) => nudge(clamped + e.dy * wheelSensitivity);
72
72
  if (vertical) {
73
- return (_jsxs(View, { className: "items-center gap-2", children: [_jsxs(View, { className: `w-[18] relative ${disabled ? "opacity-40" : "cursor-pointer"}`, style: { height }, onDragStart: onDragStart, onDrag: disabled ? undefined : (e) => onChange(clamp01(startValue.current - e.dy / height)), onDragEnd: onDragEnd, onDoubleClick: onDoubleClick, onWheel: onWheel, children: [_jsx(View, { className: "absolute w-[4] rounded-full left-[7] top-0 bottom-0", style: { backgroundColor: trackColor } }), _jsx(View, { className: "absolute w-[4] rounded-full left-[7] bottom-0", style: { height: clamped * height, backgroundColor: valueColor } }), _jsx(View, { className: "absolute w-[12] h-[12] rounded-full left-[3]", style: { top: (1 - clamped) * (height - 12), backgroundColor: valueColor } })] }), label !== undefined ? (_jsx(Text, { className: "text-faint text-[10] font-bold tracking-widest", children: label })) : null] }));
73
+ return (_jsxs(View, { className: "items-center gap-2", children: [_jsxs(View, { className: `w-[18] relative ${disabled ? "opacity-40" : "cursor-pointer"}`, style: { height }, onDragStart: onDragStart, onDrag: disabled ? undefined : (e) => onChange(clamp01(startValue.current - e.dy / height)), onDragEnd: onDragEnd, onDoubleClick: onDoubleClick, onWheel: onWheel, children: [_jsx(View, { className: "absolute w-[4] rounded-full left-[7] top-0 bottom-0", style: { backgroundColor: trackColor } }), _jsx(View, { className: "absolute w-[4] rounded-full left-[7] bottom-0", style: { height: clamped * height, backgroundColor: valueColor } }), barThumb ? (_jsx(View, { className: "absolute w-[18] h-[5] rounded-[2] left-0", style: { top: (1 - clamped) * (height - 5), backgroundColor: valueColor } })) : (_jsx(View, { className: "absolute w-[12] h-[12] rounded-full left-[3]", style: { top: (1 - clamped) * (height - 12), backgroundColor: valueColor } }))] }), label !== undefined ? (_jsx(Text, { className: "text-faint text-[10] font-bold tracking-widest", children: label })) : null] }));
74
74
  }
75
- return (_jsxs(View, { className: "gap-2", children: [_jsxs(View, { className: `h-[18] justify-center relative ${disabled ? "opacity-40" : "cursor-pointer"}`, style: { width }, onDragStart: onDragStart, onDrag: disabled ? undefined : (e) => onChange(clamp01(startValue.current + e.dx / width)), onDragEnd: onDragEnd, onDoubleClick: onDoubleClick, onWheel: onWheel, children: [_jsx(View, { className: "h-[4] rounded-full", style: { backgroundColor: trackColor } }), _jsx(View, { className: "absolute h-[4] rounded-full top-[7]", style: { width: clamped * width, backgroundColor: valueColor } }), _jsx(View, { className: "absolute w-[12] h-[12] rounded-full top-[3]", style: { left: clamped * (width - 12), backgroundColor: valueColor } })] }), label !== undefined ? (_jsx(Text, { className: "text-faint text-[10] font-bold tracking-widest", children: label })) : null] }));
75
+ return (_jsxs(View, { className: "gap-2", children: [_jsxs(View, { className: `h-[18] justify-center relative ${disabled ? "opacity-40" : "cursor-pointer"}`, style: { width }, onDragStart: onDragStart, onDrag: disabled ? undefined : (e) => onChange(clamp01(startValue.current + e.dx / width)), onDragEnd: onDragEnd, onDoubleClick: onDoubleClick, onWheel: onWheel, children: [_jsx(View, { className: "h-[4] rounded-full", style: { backgroundColor: trackColor } }), _jsx(View, { className: "absolute h-[4] rounded-full top-[7]", style: { width: clamped * width, backgroundColor: valueColor } }), barThumb ? (_jsx(View, { className: "absolute w-[5] h-[18] rounded-[2] top-0", style: { left: clamped * (width - 5), backgroundColor: valueColor } })) : (_jsx(View, { className: "absolute w-[12] h-[12] rounded-full top-[3]", style: { left: clamped * (width - 12), backgroundColor: valueColor } }))] }), label !== undefined ? (_jsx(Text, { className: "text-faint text-[10] font-bold tracking-widest", children: label })) : null] }));
76
76
  }
77
77
  /** A Slider bound to an APVTS parameter — double-click resets to the
78
78
  host's default, the wheel nudges, gestures stay automation-safe. */
@@ -81,18 +81,18 @@ export function ParamSlider({ paramId, label, ...rest }) {
81
81
  return (_jsx(Slider, { value: param.value, label: label ?? param.name.toUpperCase(), defaultValue: param.defaultValue, onChange: param.set, onBegin: param.begin, onEnd: param.end, ...rest }));
82
82
  }
83
83
  /** A switch with a spring-animated thumb — bypass, on/off, A/B. */
84
- export function Toggle({ on, label, size = 22, disabled, trackColor = "#2A2F27", onColor = "#C6F135", thumbColor = "#F4F4F5", onChange, }) {
84
+ export function Toggle({ on, label, offLabel, onLabel, size = 22, disabled, trackColor = "#2A2F27", onColor = "#C6F135", thumbColor = "#F4F4F5", onChange, }) {
85
85
  const t = useSpring(on ? 1 : 0, { stiffness: 300, damping: 28 });
86
86
  const trackWidth = Math.round(size * 1.8);
87
87
  const thumb = size - 6;
88
88
  const travel = trackWidth - thumb - 6;
89
- return (_jsxs(View, { className: "items-center gap-2", children: [_jsx(View, { className: `relative rounded-full ${disabled ? "opacity-40" : "cursor-pointer"}`, style: { width: trackWidth, height: size, backgroundColor: on ? onColor : trackColor }, onClick: disabled ? undefined : () => onChange(!on), children: _jsx(View, { className: "absolute rounded-full", style: {
90
- width: thumb,
91
- height: thumb,
92
- top: 3,
93
- left: 3 + clamp01(t) * travel,
94
- backgroundColor: thumbColor,
95
- } }) }), label !== undefined ? (_jsx(Text, { className: "text-faint text-[10] font-bold tracking-widest", children: label })) : null] }));
89
+ return (_jsxs(View, { className: "items-center gap-2", children: [_jsxs(View, { className: "flex-row items-center gap-2", children: [offLabel !== undefined ? (_jsx(Text, { className: "text-[9] font-bold tracking-widest", style: { color: on ? "#6f6e66" : "#ECF2E8" }, children: offLabel })) : null, _jsx(View, { className: `relative rounded-full ${disabled ? "opacity-40" : "cursor-pointer"}`, style: { width: trackWidth, height: size, backgroundColor: on ? onColor : trackColor }, onClick: disabled ? undefined : () => onChange(!on), children: _jsx(View, { className: "absolute rounded-full", style: {
90
+ width: thumb,
91
+ height: thumb,
92
+ top: 3,
93
+ left: 3 + clamp01(t) * travel,
94
+ backgroundColor: thumbColor,
95
+ } }) }), onLabel !== undefined ? (_jsx(Text, { className: "text-[9] font-bold tracking-widest", style: { color: on ? "#ECF2E8" : "#6f6e66" }, children: onLabel })) : null] }), label !== undefined ? (_jsx(Text, { className: "text-faint text-[10] font-bold tracking-widest", children: label })) : null] }));
96
96
  }
97
97
  /** A Toggle bound to a bool-style APVTS parameter (on = value ≥ 0.5). */
98
98
  export function ParamToggle({ paramId, label, ...rest }) {
@@ -152,14 +152,20 @@ export function ParamSegmented({ paramId, options, label, ...rest }) {
152
152
  param.end();
153
153
  }, ...rest }));
154
154
  }
155
- /** The zero-effort editor: one knob per APVTS parameter, laid out in
156
- rows. `render(<GenericEditor />)` is a complete, working plugin UI. */
155
+ /** One GenericEditor cell: knob + live value label + name. */
156
+ function GenericEditorKnob({ paramId, size, trackColor, valueColor, }) {
157
+ const param = useParameter(paramId);
158
+ return (_jsxs(View, { className: "items-center gap-1", children: [_jsx(Knob, { value: param.value, size: size, defaultValue: param.defaultValue, trackColor: trackColor, valueColor: valueColor, onChange: param.set, onBegin: param.begin, onEnd: param.end }), _jsx(Text, { className: "text-text text-[11] font-bold", children: param.text }), _jsx(Text, { className: "text-faint text-[9] font-bold tracking-widest", children: param.name.toUpperCase() })] }));
159
+ }
160
+ /** The zero-effort editor: one knob per APVTS parameter with a live
161
+ value label and name under each, laid out in rows.
162
+ `render(<GenericEditor />)` is a complete, working plugin UI. */
157
163
  export function GenericEditor({ columns = 4, size = 72, trackColor, valueColor }) {
158
164
  const params = useParameterList();
159
165
  const rows = [];
160
166
  for (let i = 0; i < params.length; i += Math.max(1, columns))
161
167
  rows.push(params.slice(i, i + Math.max(1, columns)));
162
- return (_jsx(View, { className: "flex-1 flex-col items-center justify-center gap-6 p-4", children: rows.map((row, index) => (_jsx(View, { className: "flex-row gap-7", children: row.map((param) => (_jsx(ParamKnob, { paramId: param.id, size: size, trackColor: trackColor, valueColor: valueColor }, param.id))) }, index))) }));
168
+ return (_jsx(View, { className: "flex-1 flex-col items-center justify-center gap-6 p-4", children: rows.map((row, index) => (_jsx(View, { className: "flex-row gap-7", children: row.map((param) => (_jsx(GenericEditorKnob, { paramId: param.id, size: size, trackColor: trackColor, valueColor: valueColor }, param.id))) }, index))) }));
163
169
  }
164
170
  const SELECT_ROW_HEIGHT = 30;
165
171
  /** A dropdown: the menu renders in the overlay layer, positioned under
@@ -0,0 +1,24 @@
1
+ export interface ProgressBarProps {
2
+ /** 0..1. */
3
+ value: number;
4
+ width?: number;
5
+ height?: number;
6
+ /** Renders "42%" alongside the bar. Default false. */
7
+ showPercent?: boolean;
8
+ trackColor?: string;
9
+ color?: string;
10
+ label?: string;
11
+ }
12
+ /** A determinate progress bar — downloads, renders, analysis passes. */
13
+ export declare function ProgressBar({ value, width, height, showPercent, trackColor, color, label, }: ProgressBarProps): import("react").JSX.Element;
14
+ export interface SpinnerProps {
15
+ /** Diameter. Default 28. */
16
+ size?: number;
17
+ thickness?: number;
18
+ color?: string;
19
+ trackColor?: string;
20
+ /** Degrees per 16ms tick. Default 9 (~ one turn / 0.64s). */
21
+ speed?: number;
22
+ }
23
+ /** An indeterminate spinner — a 100° arc chasing its own tail. */
24
+ export declare function Spinner({ size, thickness, color, trackColor, speed, }: SpinnerProps): import("react").JSX.Element;
@@ -0,0 +1,31 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // Feedback components — determinate progress and the indeterminate
3
+ // spinner, both painted natively.
4
+ import { useState } from "react";
5
+ import { View, Text } from "./primitives";
6
+ import { useInterval } from "./hooks";
7
+ const clamp01 = (v) => Math.min(1, Math.max(0, v));
8
+ /** A determinate progress bar — downloads, renders, analysis passes. */
9
+ export function ProgressBar({ value, width = 200, height = 8, showPercent = false, trackColor = "#2A2F27", color = "#C6F135", label, }) {
10
+ const level = clamp01(value);
11
+ const bar = (_jsxs(View, { className: "flex-row items-center gap-3", children: [_jsx(View, { className: "rounded-full overflow-hidden", style: { width, height, backgroundColor: trackColor }, children: _jsx(View, { className: "rounded-full", style: { width: level * width, height, backgroundColor: color } }) }), showPercent ? (_jsx(Text, { className: "text-faint text-[11] font-bold", style: { width: 34 }, children: `${Math.round(level * 100)}%` })) : null] }));
12
+ if (label === undefined)
13
+ return bar;
14
+ return (_jsxs(View, { className: "items-center gap-2", children: [bar, _jsx(Text, { className: "text-faint text-[10] font-bold tracking-widest", children: label })] }));
15
+ }
16
+ /** An indeterminate spinner — a 100° arc chasing its own tail. */
17
+ export function Spinner({ size = 28, thickness, color = "#C6F135", trackColor = "#FFFFFF14", speed = 9, }) {
18
+ const [angle, setAngle] = useState(0);
19
+ useInterval(() => setAngle((a) => (a + speed) % 360), 16);
20
+ return (_jsx(View, { style: {
21
+ width: size,
22
+ height: size,
23
+ arcTrackColor: trackColor,
24
+ arcColor: color,
25
+ arcStart: 0,
26
+ arcEnd: 360,
27
+ arcValueStart: angle,
28
+ arcValueEnd: angle + 100,
29
+ arcThickness: thickness ?? Math.max(2.5, size * 0.11),
30
+ } }));
31
+ }
@@ -0,0 +1,79 @@
1
+ /** Snap onto the step grid without float dust. */
2
+ export declare function snapToStep(value: number, step: number, min: number): number;
3
+ export interface NumberBoxProps {
4
+ value: number;
5
+ min?: number;
6
+ max?: number;
7
+ /** Value change per step; drags move ~a step per 4px. Default 1. */
8
+ step?: number;
9
+ /** Renders the value. Default: trimmed to 2 decimals. */
10
+ format?: (value: number) => string;
11
+ label?: string;
12
+ width?: number;
13
+ disabled?: boolean;
14
+ /** Double-click resets to this. */
15
+ defaultValue?: number;
16
+ trackColor?: string;
17
+ textColor?: string;
18
+ onChange: (value: number) => void;
19
+ onBegin?: () => void;
20
+ onEnd?: () => void;
21
+ }
22
+ /** The draggable number — BPM, milliseconds, semitones. Drag vertically,
23
+ wheel to step, double-click to reset. */
24
+ export declare function NumberBox({ value, min, max, step, format, label, width, disabled, defaultValue, trackColor, textColor, onChange, onBegin, onEnd, }: NumberBoxProps): import("react").JSX.Element;
25
+ export interface ParamNumberBoxProps {
26
+ paramId: string;
27
+ label?: string;
28
+ width?: number;
29
+ /** Normalized step per wheel notch / drag unit. Default 0.01. */
30
+ step?: number;
31
+ }
32
+ /** A NumberBox over a host parameter: shows the host's formatted text,
33
+ drags the normalized value, double-click resets to the default. */
34
+ export declare function ParamNumberBox({ paramId, label, width, step }: ParamNumberBoxProps): import("react").JSX.Element;
35
+ export interface CheckboxProps {
36
+ checked: boolean;
37
+ label?: string;
38
+ disabled?: boolean;
39
+ /** Box edge length. Default 16. */
40
+ size?: number;
41
+ accentColor?: string;
42
+ boxColor?: string;
43
+ textColor?: string;
44
+ onChange: (checked: boolean) => void;
45
+ }
46
+ /** A checkbox row — settings panels, option lists. */
47
+ export declare function Checkbox({ checked, label, disabled, size, accentColor, boxColor, textColor, onChange, }: CheckboxProps): import("react").JSX.Element;
48
+ export interface ParamCheckboxProps {
49
+ paramId: string;
50
+ label?: string;
51
+ size?: number;
52
+ accentColor?: string;
53
+ }
54
+ /** A Checkbox bound to a bool-style parameter (checked = value ≥ 0.5). */
55
+ export declare function ParamCheckbox({ paramId, label, ...rest }: ParamCheckboxProps): import("react").JSX.Element;
56
+ export interface RadioGroupProps {
57
+ options: string[];
58
+ index: number;
59
+ disabled?: boolean;
60
+ /** Row spacing. Default 8. */
61
+ gap?: number;
62
+ accentColor?: string;
63
+ dotColor?: string;
64
+ textColor?: string;
65
+ activeTextColor?: string;
66
+ onChange: (index: number) => void;
67
+ }
68
+ /** Vertical exclusive options with dots — the settings-panel sibling of
69
+ <Segmented>. */
70
+ export declare function RadioGroup({ options, index, disabled, gap, accentColor, dotColor, textColor, activeTextColor, onChange, }: RadioGroupProps): import("react").JSX.Element;
71
+ export interface ParamRadioGroupProps {
72
+ paramId: string;
73
+ options: string[];
74
+ gap?: number;
75
+ accentColor?: string;
76
+ }
77
+ /** A RadioGroup bound to a choice-style parameter (value ↔ index, like
78
+ ParamSegmented). */
79
+ export declare function ParamRadioGroup({ paramId, options, ...rest }: ParamRadioGroupProps): import("react").JSX.Element;
package/dist/fields.js ADDED
@@ -0,0 +1,73 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // Field controls — the number box, checkbox, and radio group. Settings
3
+ // panels and fine-value entry, with the same DAW conventions as the
4
+ // knobs: drag, wheel, double-click reset, automation-safe Param twins.
5
+ import { useRef } from "react";
6
+ import { View, Text } from "./primitives";
7
+ import { useParameter } from "./parameters";
8
+ const clamp = (v, min, max) => Math.min(max, Math.max(min, v));
9
+ /** Snap onto the step grid without float dust. */
10
+ export function snapToStep(value, step, min) {
11
+ const steps = Math.round((value - min) / step);
12
+ return Number((min + steps * step).toPrecision(12));
13
+ }
14
+ /** The draggable number — BPM, milliseconds, semitones. Drag vertically,
15
+ wheel to step, double-click to reset. */
16
+ export function NumberBox({ value, min = 0, max = 100, step = 1, format = (v) => String(Math.round(v * 100) / 100), label, width = 84, disabled, defaultValue, trackColor = "#2A2F27", textColor = "#ECF2E8", onChange, onBegin, onEnd, }) {
17
+ const startValue = useRef(0);
18
+ const clamped = clamp(value, min, max);
19
+ const commit = (raw) => onChange(clamp(snapToStep(raw, step, min), min, max));
20
+ const nudge = (raw) => {
21
+ onBegin?.();
22
+ commit(raw);
23
+ onEnd?.();
24
+ };
25
+ return (_jsxs(View, { className: "items-center gap-2", children: [_jsx(View, { className: `items-center rounded-lg border px-3 py-[7] ${disabled ? "opacity-40" : "cursor-pointer"}`, style: { width, backgroundColor: trackColor, borderColor: "#00000055" }, onDragStart: disabled
26
+ ? undefined
27
+ : () => {
28
+ startValue.current = clamped;
29
+ onBegin?.();
30
+ }, onDrag: disabled ? undefined : (e) => commit(startValue.current - (e.dy * step) / 4), onDragEnd: disabled ? undefined : () => onEnd?.(), onDoubleClick: disabled || defaultValue === undefined ? undefined : () => nudge(defaultValue), onWheel: disabled ? undefined : (e) => nudge(clamped + (e.dy > 0 ? step : -step)), children: _jsx(Text, { className: "text-[13] font-bold", style: { color: textColor }, children: format(clamped) }) }), label !== undefined ? (_jsx(Text, { className: "text-faint text-[10] font-bold tracking-widest", children: label })) : null] }));
31
+ }
32
+ /** A NumberBox over a host parameter: shows the host's formatted text,
33
+ drags the normalized value, double-click resets to the default. */
34
+ export function ParamNumberBox({ paramId, label, width, step = 0.01 }) {
35
+ const param = useParameter(paramId);
36
+ return (_jsx(NumberBox, { value: param.value, min: 0, max: 1, step: step, format: () => param.text, label: label ?? param.name.toUpperCase(), width: width, defaultValue: param.defaultValue, onChange: param.set, onBegin: param.begin, onEnd: param.end }));
37
+ }
38
+ /** A checkbox row — settings panels, option lists. */
39
+ export function Checkbox({ checked, label, disabled, size = 16, accentColor = "#C6F135", boxColor = "#2A2F27", textColor = "#d4d4d8", onChange, }) {
40
+ return (_jsxs(View, { className: `flex-row items-center gap-2 ${disabled ? "opacity-40" : "cursor-pointer"}`, onClick: disabled ? undefined : () => onChange(!checked), children: [_jsx(View, { className: "items-center justify-center rounded-[4] border", style: {
41
+ width: size,
42
+ height: size,
43
+ backgroundColor: checked ? accentColor : boxColor,
44
+ borderColor: "#00000066",
45
+ }, children: checked ? (_jsx(Text, { className: "font-bold", style: { fontSize: size * 0.7, color: "#09090b" }, children: "\u2713" })) : null }), label !== undefined ? (_jsx(Text, { className: "text-[12]", style: { color: textColor }, children: label })) : null] }));
46
+ }
47
+ /** A Checkbox bound to a bool-style parameter (checked = value ≥ 0.5). */
48
+ export function ParamCheckbox({ paramId, label, ...rest }) {
49
+ const param = useParameter(paramId);
50
+ return (_jsx(Checkbox, { checked: param.value >= 0.5, label: label ?? param.name, onChange: (next) => {
51
+ param.begin();
52
+ param.set(next ? 1 : 0);
53
+ param.end();
54
+ }, ...rest }));
55
+ }
56
+ /** Vertical exclusive options with dots — the settings-panel sibling of
57
+ <Segmented>. */
58
+ export function RadioGroup({ options, index, disabled, gap = 8, accentColor = "#C6F135", dotColor = "#2A2F27", textColor = "#a1a1aa", activeTextColor = "#ECF2E8", onChange, }) {
59
+ const current = Math.min(options.length - 1, Math.max(0, index));
60
+ return (_jsx(View, { className: disabled ? "opacity-40" : "", style: { rowGap: gap }, children: options.map((option, i) => (_jsxs(View, { className: `flex-row items-center gap-2 ${disabled ? "" : "cursor-pointer"}`, onClick: disabled ? undefined : () => onChange(i), children: [_jsx(View, { className: "items-center justify-center rounded-full border", style: { width: 15, height: 15, backgroundColor: dotColor, borderColor: "#00000066" }, children: i === current ? (_jsx(View, { className: "rounded-full", style: { width: 7, height: 7, backgroundColor: accentColor } })) : null }), _jsx(Text, { className: "text-[12]", style: { color: i === current ? activeTextColor : textColor }, children: option })] }, `${option}-${i}`))) }));
61
+ }
62
+ /** A RadioGroup bound to a choice-style parameter (value ↔ index, like
63
+ ParamSegmented). */
64
+ export function ParamRadioGroup({ paramId, options, ...rest }) {
65
+ const param = useParameter(paramId);
66
+ const count = Math.max(1, options.length);
67
+ const index = Math.round(param.value * (count - 1));
68
+ return (_jsx(RadioGroup, { options: options, index: index, onChange: (i) => {
69
+ param.begin();
70
+ param.set(count <= 1 ? 0 : i / (count - 1));
71
+ param.end();
72
+ }, ...rest }));
73
+ }
package/dist/index.d.ts CHANGED
@@ -24,4 +24,10 @@ export { Knob, Slider, Toggle, XYPad, Segmented, Select, GenericEditor, ParamKno
24
24
  export type { KnobProps, SliderProps, ToggleProps, XYPadProps, SegmentedProps, SelectProps, GenericEditorProps, ParamKnobProps, ParamSliderProps, ParamToggleProps, ParamXYPadProps, ParamSegmentedProps, ParamSelectProps, } from "./controls";
25
25
  export { Meter, usePeakHold, peakHoldStep } from "./meter";
26
26
  export type { MeterProps, PeakHoldOptions, PeakHoldState } from "./meter";
27
+ export { NumberBox, Checkbox, RadioGroup, ParamNumberBox, ParamCheckbox, ParamRadioGroup, snapToStep, } from "./fields";
28
+ export type { NumberBoxProps, CheckboxProps, RadioGroupProps, ParamNumberBoxProps, ParamCheckboxProps, ParamRadioGroupProps, } from "./fields";
29
+ export { ProgressBar, Spinner } from "./feedback";
30
+ export type { ProgressBarProps, SpinnerProps } from "./feedback";
31
+ export { MacroPad, ParamMacroPad, HardwareKnob, ParamHardwareKnob, Crossfader, ParamCrossfader, PulseOrb, } from "./specialty";
32
+ export type { MacroPadProps, ParamMacroPadProps, HardwareKnobProps, ParamHardwareKnobProps, CrossfaderProps, ParamCrossfaderProps, PulseOrbProps, } from "./specialty";
27
33
  export type { DragEventPayload, LayoutRect, WheelEventPayload } from "./primitives";
package/dist/index.js CHANGED
@@ -16,3 +16,6 @@ export { useTween, useSpring, springStep, lerp, Easing } from "./animation";
16
16
  export { useParameter, useParameterList } from "./parameters";
17
17
  export { Knob, Slider, Toggle, XYPad, Segmented, Select, GenericEditor, ParamKnob, ParamSlider, ParamToggle, ParamXYPad, ParamSegmented, ParamSelect, dragToValue, } from "./controls";
18
18
  export { Meter, usePeakHold, peakHoldStep } from "./meter";
19
+ export { NumberBox, Checkbox, RadioGroup, ParamNumberBox, ParamCheckbox, ParamRadioGroup, snapToStep, } from "./fields";
20
+ export { ProgressBar, Spinner } from "./feedback";
21
+ export { MacroPad, ParamMacroPad, HardwareKnob, ParamHardwareKnob, Crossfader, ParamCrossfader, PulseOrb, } from "./specialty";
@@ -0,0 +1,109 @@
1
+ export interface MacroPadProps {
2
+ /** Normalized 0..1. */
3
+ x: number;
4
+ /** Normalized 0..1 — 1 is the top. */
5
+ y: number;
6
+ /** Diameter. Default 220. */
7
+ size?: number;
8
+ labelX?: string;
9
+ labelY?: string;
10
+ disabled?: boolean;
11
+ /** Concentric ring count. Default 9. */
12
+ rings?: number;
13
+ /** Freeze the ring motion (reduced-motion, screenshots). */
14
+ animate?: boolean;
15
+ color?: string;
16
+ trackColor?: string;
17
+ onChange: (x: number, y: number) => void;
18
+ onBegin?: () => void;
19
+ onEnd?: () => void;
20
+ }
21
+ /** The centerpiece macro control: a circular 2D pad whose concentric
22
+ rings breathe with the values — x spreads them, y drives their
23
+ intensity. One drag, two parameters, instrument-grade presence. */
24
+ export declare function MacroPad({ x, y, size, labelX, labelY, disabled, rings, animate, color, trackColor, onChange, onBegin, onEnd, }: MacroPadProps): import("react").JSX.Element;
25
+ export interface ParamMacroPadProps {
26
+ paramX: string;
27
+ paramY: string;
28
+ size?: number;
29
+ labelX?: string;
30
+ labelY?: string;
31
+ rings?: number;
32
+ animate?: boolean;
33
+ color?: string;
34
+ trackColor?: string;
35
+ }
36
+ /** A MacroPad driving two host parameters — labels default to their
37
+ names. */
38
+ export declare function ParamMacroPad({ paramX, paramY, labelX, labelY, ...rest }: ParamMacroPadProps): import("react").JSX.Element;
39
+ export interface HardwareKnobProps {
40
+ value: number;
41
+ size?: number;
42
+ label?: string;
43
+ text?: string;
44
+ disabled?: boolean;
45
+ defaultValue?: number;
46
+ wheelSensitivity?: number;
47
+ capColor?: string;
48
+ pointerColor?: string;
49
+ tickColor?: string;
50
+ onChange: (value: number) => void;
51
+ onBegin?: () => void;
52
+ onEnd?: () => void;
53
+ }
54
+ /** The skeuomorphic knob: a dark hardware cap with a glowing pointer
55
+ notch at the rim and a faint tick track — the audio-ui.com look,
56
+ painted natively. */
57
+ export declare function HardwareKnob({ value, size, label, text, disabled, defaultValue, wheelSensitivity, capColor, pointerColor, tickColor, onChange, onBegin, onEnd, }: HardwareKnobProps): import("react").JSX.Element;
58
+ export interface ParamHardwareKnobProps {
59
+ paramId: string;
60
+ size?: number;
61
+ label?: string;
62
+ capColor?: string;
63
+ pointerColor?: string;
64
+ tickColor?: string;
65
+ wheelSensitivity?: number;
66
+ }
67
+ /** A HardwareKnob bound to a host parameter. */
68
+ export declare function ParamHardwareKnob({ paramId, label, ...rest }: ParamHardwareKnobProps): import("react").JSX.Element;
69
+ export interface CrossfaderProps {
70
+ /** 0 = fully start-side (dry), 1 = fully end-side (wet). */
71
+ value: number;
72
+ width?: number;
73
+ height?: number;
74
+ labelStart?: string;
75
+ labelEnd?: string;
76
+ disabled?: boolean;
77
+ trackColor?: string;
78
+ handleColor?: string;
79
+ textColor?: string;
80
+ onChange: (value: number) => void;
81
+ onBegin?: () => void;
82
+ onEnd?: () => void;
83
+ }
84
+ /** The DRY/WET strip: a wide track with a grippy rectangular handle. */
85
+ export declare function Crossfader({ value, width, height, labelStart, labelEnd, disabled, trackColor, handleColor, textColor, onChange, onBegin, onEnd, }: CrossfaderProps): import("react").JSX.Element;
86
+ export interface ParamCrossfaderProps {
87
+ paramId: string;
88
+ width?: number;
89
+ height?: number;
90
+ labelStart?: string;
91
+ labelEnd?: string;
92
+ trackColor?: string;
93
+ handleColor?: string;
94
+ }
95
+ /** A Crossfader bound to a host parameter — the classic mix control. */
96
+ export declare function ParamCrossfader({ paramId, ...rest }: ParamCrossfaderProps): import("react").JSX.Element;
97
+ export interface PulseOrbProps {
98
+ /** Level 0..1 — drives ring intensity and core glow. */
99
+ value: number;
100
+ size?: number;
101
+ color?: string;
102
+ /** Echo ring count. Default 4. */
103
+ rings?: number;
104
+ /** Freeze the motion. */
105
+ animate?: boolean;
106
+ }
107
+ /** A value-reactive orb: a glowing core emitting echo rings — visual
108
+ feedback for levels, activity, or just presence. */
109
+ export declare function PulseOrb({ value, size, color, rings, animate, }: PulseOrbProps): import("react").JSX.Element;