@wavecraft/components 0.7.0

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.
package/README.md ADDED
@@ -0,0 +1,82 @@
1
+ # @wavecraft/components
2
+
3
+ Pre-built React components for Wavecraft audio plugins.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @wavecraft/core @wavecraft/components
9
+ ```
10
+
11
+ > Note: `@wavecraft/core` is a peer dependency and must be installed.
12
+
13
+ ## Quick Start
14
+
15
+ ```tsx
16
+ import { useAllParameters } from '@wavecraft/core';
17
+ import { Meter, ParameterSlider } from '@wavecraft/components';
18
+
19
+ function PluginUI() {
20
+ const { params } = useAllParameters();
21
+
22
+ return (
23
+ <div className="p-4 bg-plugin-dark">
24
+ <Meter />
25
+ {params?.map((p) => (
26
+ <ParameterSlider key={p.id} id={p.id} />
27
+ ))}
28
+ </div>
29
+ );
30
+ }
31
+ ```
32
+
33
+ ## TailwindCSS Configuration
34
+
35
+ Components use TailwindCSS utility classes. Configure your `tailwind.config.js`:
36
+
37
+ ```javascript
38
+ module.exports = {
39
+ content: [
40
+ './src/**/*.{js,ts,jsx,tsx}',
41
+ './node_modules/@wavecraft/components/dist/**/*.js',
42
+ ],
43
+ theme: {
44
+ extend: {
45
+ colors: {
46
+ 'plugin-dark': '#1a1a1a',
47
+ 'plugin-surface': '#2a2a2a',
48
+ 'plugin-border': '#444444',
49
+ 'accent': '#4a9eff',
50
+ 'accent-light': '#6bb0ff',
51
+ 'meter-safe': '#4caf50',
52
+ 'meter-warning': '#ffeb3b',
53
+ 'meter-clip': '#ff1744',
54
+ },
55
+ },
56
+ },
57
+ };
58
+ ```
59
+
60
+ ## Components
61
+
62
+ | Component | Description |
63
+ |-----------|-------------|
64
+ | `Meter` | Audio level meter with peak/RMS display |
65
+ | `ParameterSlider` | Slider control for a parameter |
66
+ | `ParameterGroup` | Group of related parameters |
67
+ | `ParameterToggle` | Boolean parameter toggle |
68
+ | `VersionBadge` | Displays plugin version |
69
+ | `ConnectionStatus` | Shows IPC connection state |
70
+ | `LatencyMonitor` | Displays IPC latency stats |
71
+ | `ResizeHandle` | Draggable resize corner |
72
+ | `ResizeControls` | Preset size buttons |
73
+
74
+ ## Requirements
75
+
76
+ - React 18+
77
+ - `@wavecraft/core` ^0.7.0
78
+ - TailwindCSS 3.x (for styling)
79
+
80
+ ## License
81
+
82
+ MIT
@@ -0,0 +1,52 @@
1
+ import { default as default_2 } from 'react';
2
+ import { ParameterGroup as ParameterGroup_2 } from '../../core/src';
3
+
4
+ export declare function ConnectionStatus(): default_2.JSX.Element;
5
+
6
+ export declare function LatencyMonitor(): default_2.JSX.Element;
7
+
8
+ export declare function Meter(): default_2.JSX.Element;
9
+
10
+ /**
11
+ * Displays a group of parameters with a header and visual grouping.
12
+ *
13
+ * @example
14
+ * ```tsx
15
+ * const { parameters } = useAllParameters();
16
+ * const groups = useParameterGroups(parameters);
17
+ *
18
+ * return (
19
+ * <div className="space-y-4">
20
+ * {groups.map(group => (
21
+ * <ParameterGroup key={group.name} group={group} />
22
+ * ))}
23
+ * </div>
24
+ * );
25
+ * ```
26
+ */
27
+ export declare function ParameterGroup({ group }: Readonly<ParameterGroupProps>): default_2.JSX.Element;
28
+
29
+ declare interface ParameterGroupProps {
30
+ /** The parameter group to display */
31
+ group: ParameterGroup_2;
32
+ }
33
+
34
+ export declare function ParameterSlider({ id }: ParameterSliderProps): default_2.JSX.Element;
35
+
36
+ declare interface ParameterSliderProps {
37
+ readonly id: string;
38
+ }
39
+
40
+ export declare function ParameterToggle({ id }: ParameterToggleProps): default_2.JSX.Element;
41
+
42
+ declare interface ParameterToggleProps {
43
+ readonly id: string;
44
+ }
45
+
46
+ export declare function ResizeControls(): default_2.JSX.Element;
47
+
48
+ export declare function ResizeHandle(): default_2.JSX.Element;
49
+
50
+ export declare function VersionBadge(): default_2.JSX.Element;
51
+
52
+ export { }
package/dist/index.js ADDED
@@ -0,0 +1,516 @@
1
+ import { jsxs, jsx, Fragment } from "react/jsx-runtime";
2
+ import { useState, useRef, useEffect, useCallback } from "react";
3
+ import { useConnectionStatus, getMeterFrame, linearToDb, useParameter, logger, useLatencyMonitor, useRequestResize } from "@wavecraft/core";
4
+ const METER_UPDATE_HZ = 30;
5
+ const METER_FLOOR_DB = -60;
6
+ const METER_RANGE_DB = 60;
7
+ const CLIP_THRESHOLD = 1;
8
+ const CLIP_HOLD_MS = 2e3;
9
+ function Meter() {
10
+ const { connected } = useConnectionStatus();
11
+ const [frame, setFrame] = useState(null);
12
+ const [clippedL, setClippedL] = useState(false);
13
+ const [clippedR, setClippedR] = useState(false);
14
+ const clipLTimeoutRef = useRef(null);
15
+ const clipRTimeoutRef = useRef(null);
16
+ useEffect(() => {
17
+ if (!connected) {
18
+ return;
19
+ }
20
+ const interval = setInterval(async () => {
21
+ const newFrame = await getMeterFrame();
22
+ setFrame(newFrame);
23
+ if (newFrame) {
24
+ if (newFrame.peak_l > CLIP_THRESHOLD) {
25
+ setClippedL(true);
26
+ if (clipLTimeoutRef.current !== null) {
27
+ clearTimeout(clipLTimeoutRef.current);
28
+ }
29
+ clipLTimeoutRef.current = globalThis.setTimeout(() => {
30
+ setClippedL(false);
31
+ clipLTimeoutRef.current = null;
32
+ }, CLIP_HOLD_MS);
33
+ }
34
+ if (newFrame.peak_r > CLIP_THRESHOLD) {
35
+ setClippedR(true);
36
+ if (clipRTimeoutRef.current !== null) {
37
+ clearTimeout(clipRTimeoutRef.current);
38
+ }
39
+ clipRTimeoutRef.current = globalThis.setTimeout(() => {
40
+ setClippedR(false);
41
+ clipRTimeoutRef.current = null;
42
+ }, CLIP_HOLD_MS);
43
+ }
44
+ }
45
+ }, 1e3 / METER_UPDATE_HZ);
46
+ return () => {
47
+ clearInterval(interval);
48
+ if (clipLTimeoutRef.current !== null) {
49
+ clearTimeout(clipLTimeoutRef.current);
50
+ }
51
+ if (clipRTimeoutRef.current !== null) {
52
+ clearTimeout(clipRTimeoutRef.current);
53
+ }
54
+ };
55
+ }, [connected]);
56
+ const peakLDb = frame ? linearToDb(frame.peak_l, METER_FLOOR_DB) : METER_FLOOR_DB;
57
+ const peakRDb = frame ? linearToDb(frame.peak_r, METER_FLOOR_DB) : METER_FLOOR_DB;
58
+ const rmsLDb = frame ? linearToDb(frame.rms_l, METER_FLOOR_DB) : METER_FLOOR_DB;
59
+ const rmsRDb = frame ? linearToDb(frame.rms_r, METER_FLOOR_DB) : METER_FLOOR_DB;
60
+ const peakLPercent = (peakLDb - METER_FLOOR_DB) / METER_RANGE_DB * 100;
61
+ const peakRPercent = (peakRDb - METER_FLOOR_DB) / METER_RANGE_DB * 100;
62
+ const rmsLPercent = (rmsLDb - METER_FLOOR_DB) / METER_RANGE_DB * 100;
63
+ const rmsRPercent = (rmsRDb - METER_FLOOR_DB) / METER_RANGE_DB * 100;
64
+ const handleResetClip = () => {
65
+ setClippedL(false);
66
+ setClippedR(false);
67
+ if (clipLTimeoutRef.current !== null) {
68
+ clearTimeout(clipLTimeoutRef.current);
69
+ clipLTimeoutRef.current = null;
70
+ }
71
+ if (clipRTimeoutRef.current !== null) {
72
+ clearTimeout(clipRTimeoutRef.current);
73
+ clipRTimeoutRef.current = null;
74
+ }
75
+ };
76
+ if (!connected) {
77
+ return /* @__PURE__ */ jsxs(
78
+ "div",
79
+ {
80
+ "data-testid": "meter",
81
+ className: "flex flex-col gap-2 rounded-lg border border-plugin-border bg-plugin-surface p-4 font-sans",
82
+ children: [
83
+ /* @__PURE__ */ jsx("div", { className: "flex items-center justify-between gap-2", children: /* @__PURE__ */ jsx("div", { className: "text-xs font-semibold uppercase tracking-wide text-gray-500", children: "Levels" }) }),
84
+ /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-8 text-sm text-gray-400", children: "⏳ Connecting..." })
85
+ ]
86
+ }
87
+ );
88
+ }
89
+ return /* @__PURE__ */ jsxs(
90
+ "div",
91
+ {
92
+ "data-testid": "meter",
93
+ className: "flex flex-col gap-2 rounded-lg border border-plugin-border bg-plugin-surface p-4 font-sans",
94
+ children: [
95
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
96
+ /* @__PURE__ */ jsx("div", { className: "text-xs font-semibold uppercase tracking-wide text-gray-500", children: "Levels" }),
97
+ (clippedL || clippedR) && /* @__PURE__ */ jsx(
98
+ "button",
99
+ {
100
+ "data-testid": "meter-clip-button",
101
+ className: "animate-clip-pulse cursor-pointer select-none rounded border-none bg-meter-clip px-2 py-0.5 text-[10px] font-bold text-white hover:bg-meter-clip-dark active:scale-95",
102
+ onClick: handleResetClip,
103
+ title: "Click to reset",
104
+ type: "button",
105
+ children: "CLIP"
106
+ }
107
+ )
108
+ ] }),
109
+ /* @__PURE__ */ jsxs("div", { "data-testid": "meter-L", className: "flex items-center gap-2 rounded bg-plugin-dark p-2", children: [
110
+ /* @__PURE__ */ jsx("div", { className: "w-4 text-center text-[11px] font-semibold text-gray-300", children: "L" }),
111
+ /* @__PURE__ */ jsx("div", { className: "relative h-6 flex-1", children: /* @__PURE__ */ jsxs(
112
+ "div",
113
+ {
114
+ className: `relative h-full w-full overflow-hidden rounded bg-[#333] transition-shadow duration-100 ${clippedL ? "shadow-[inset_0_0_8px_rgba(255,23,68,0.8)]" : ""}`,
115
+ children: [
116
+ /* @__PURE__ */ jsx(
117
+ "div",
118
+ {
119
+ "data-testid": "meter-L-rms",
120
+ className: "absolute left-0 top-0 h-full bg-gradient-to-r from-meter-safe to-meter-safe-light transition-[width] duration-100",
121
+ style: { width: `${Math.max(0, Math.min(100, rmsLPercent))}%` }
122
+ }
123
+ ),
124
+ /* @__PURE__ */ jsx(
125
+ "div",
126
+ {
127
+ "data-testid": "meter-L-peak",
128
+ className: "duration-50 absolute left-0 top-0 h-full bg-gradient-to-r from-meter-safe via-meter-warning to-orange-500 opacity-60 transition-[width]",
129
+ style: { width: `${Math.max(0, Math.min(100, peakLPercent))}%` }
130
+ }
131
+ )
132
+ ]
133
+ }
134
+ ) }),
135
+ /* @__PURE__ */ jsxs(
136
+ "div",
137
+ {
138
+ "data-testid": "meter-L-db",
139
+ className: `w-[60px] text-right font-mono text-[11px] text-gray-300 transition-colors duration-100 ${clippedL ? "font-semibold text-meter-clip" : ""}`,
140
+ children: [
141
+ peakLDb.toFixed(1),
142
+ " dB"
143
+ ]
144
+ }
145
+ )
146
+ ] }),
147
+ /* @__PURE__ */ jsxs("div", { "data-testid": "meter-R", className: "flex items-center gap-2 rounded bg-plugin-dark p-2", children: [
148
+ /* @__PURE__ */ jsx("div", { className: "w-4 text-center text-[11px] font-semibold text-gray-300", children: "R" }),
149
+ /* @__PURE__ */ jsx("div", { className: "relative h-6 flex-1", children: /* @__PURE__ */ jsxs(
150
+ "div",
151
+ {
152
+ className: `relative h-full w-full overflow-hidden rounded bg-[#333] transition-shadow duration-100 ${clippedR ? "shadow-[inset_0_0_8px_rgba(255,23,68,0.8)]" : ""}`,
153
+ children: [
154
+ /* @__PURE__ */ jsx(
155
+ "div",
156
+ {
157
+ "data-testid": "meter-R-rms",
158
+ className: "absolute left-0 top-0 h-full bg-gradient-to-r from-meter-safe to-meter-safe-light transition-[width] duration-100",
159
+ style: { width: `${Math.max(0, Math.min(100, rmsRPercent))}%` }
160
+ }
161
+ ),
162
+ /* @__PURE__ */ jsx(
163
+ "div",
164
+ {
165
+ "data-testid": "meter-R-peak",
166
+ className: "duration-50 absolute left-0 top-0 h-full bg-gradient-to-r from-meter-safe via-meter-warning to-orange-500 opacity-60 transition-[width]",
167
+ style: { width: `${Math.max(0, Math.min(100, peakRPercent))}%` }
168
+ }
169
+ )
170
+ ]
171
+ }
172
+ ) }),
173
+ /* @__PURE__ */ jsxs(
174
+ "div",
175
+ {
176
+ "data-testid": "meter-R-db",
177
+ className: `w-[60px] text-right font-mono text-[11px] text-gray-300 transition-colors duration-100 ${clippedR ? "font-semibold text-meter-clip" : ""}`,
178
+ children: [
179
+ peakRDb.toFixed(1),
180
+ " dB"
181
+ ]
182
+ }
183
+ )
184
+ ] })
185
+ ]
186
+ }
187
+ );
188
+ }
189
+ function ParameterSlider({ id }) {
190
+ const { param, setValue, isLoading, error } = useParameter(id);
191
+ if (isLoading) {
192
+ return /* @__PURE__ */ jsxs("div", { className: "mb-4 rounded-lg border border-plugin-border bg-plugin-surface p-4 italic text-gray-500", children: [
193
+ "Loading ",
194
+ id,
195
+ "..."
196
+ ] });
197
+ }
198
+ if (error || !param) {
199
+ return /* @__PURE__ */ jsxs("div", { className: "mb-4 rounded-lg border border-red-400 bg-plugin-surface p-4 text-red-400", children: [
200
+ "Error: ",
201
+ (error == null ? void 0 : error.message) || "Parameter not found"
202
+ ] });
203
+ }
204
+ const handleChange = (e) => {
205
+ const value = Number.parseFloat(e.target.value);
206
+ setValue(value).catch((err) => {
207
+ logger.error("Failed to set parameter", { error: err, parameterId: id });
208
+ });
209
+ };
210
+ const unitSuffix = param.unit === "%" ? param.unit : ` ${param.unit}`;
211
+ const displayValue = param.unit ? `${(param.value * 100).toFixed(1)}${unitSuffix}` : param.value.toFixed(3);
212
+ return /* @__PURE__ */ jsxs(
213
+ "div",
214
+ {
215
+ "data-testid": `param-${id}`,
216
+ className: "mb-4 rounded-lg border border-plugin-border bg-plugin-surface p-4",
217
+ children: [
218
+ /* @__PURE__ */ jsxs("div", { className: "mb-2 flex items-center justify-between", children: [
219
+ /* @__PURE__ */ jsx(
220
+ "label",
221
+ {
222
+ "data-testid": `param-${id}-label`,
223
+ htmlFor: `slider-${id}`,
224
+ className: "font-semibold text-gray-200",
225
+ children: param.name
226
+ }
227
+ ),
228
+ /* @__PURE__ */ jsx("span", { "data-testid": `param-${id}-value`, className: "font-mono text-sm text-accent", children: displayValue })
229
+ ] }),
230
+ /* @__PURE__ */ jsx(
231
+ "input",
232
+ {
233
+ "data-testid": `param-${id}-slider`,
234
+ id: `slider-${id}`,
235
+ type: "range",
236
+ min: "0",
237
+ max: "1",
238
+ step: "0.001",
239
+ value: param.value,
240
+ onChange: handleChange,
241
+ className: "slider-thumb h-1.5 w-full appearance-none rounded-sm bg-plugin-border outline-none"
242
+ }
243
+ )
244
+ ]
245
+ }
246
+ );
247
+ }
248
+ function ParameterGroup({ group }) {
249
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
250
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold uppercase tracking-wider text-gray-400", children: group.name }),
251
+ /* @__PURE__ */ jsx("div", { className: "space-y-3", children: group.parameters.map((param) => /* @__PURE__ */ jsx(ParameterSlider, { id: param.id }, param.id)) })
252
+ ] });
253
+ }
254
+ function ParameterToggle({ id }) {
255
+ const { param, setValue, isLoading, error } = useParameter(id);
256
+ if (isLoading) {
257
+ return /* @__PURE__ */ jsxs("div", { className: "mb-4 flex items-center justify-between rounded-lg border border-plugin-border bg-plugin-surface p-4 italic text-gray-500", children: [
258
+ "Loading ",
259
+ id,
260
+ "..."
261
+ ] });
262
+ }
263
+ if (error || !param) {
264
+ return /* @__PURE__ */ jsxs("div", { className: "mb-4 flex items-center justify-between rounded-lg border border-red-400 bg-plugin-surface p-4 text-red-400", children: [
265
+ "Error: ",
266
+ (error == null ? void 0 : error.message) || "Parameter not found"
267
+ ] });
268
+ }
269
+ const isOn = param.value >= 0.5;
270
+ const handleToggle = () => {
271
+ const newValue = isOn ? 0 : 1;
272
+ setValue(newValue).catch((err) => {
273
+ logger.error("Failed to set parameter", { error: err, parameterId: id });
274
+ });
275
+ };
276
+ return /* @__PURE__ */ jsxs("div", { className: "mb-4 flex items-center justify-between rounded-lg border border-plugin-border bg-plugin-surface p-4", children: [
277
+ /* @__PURE__ */ jsx("label", { htmlFor: `toggle-${id}`, className: "font-semibold text-gray-200", children: param.name }),
278
+ /* @__PURE__ */ jsx(
279
+ "button",
280
+ {
281
+ id: `toggle-${id}`,
282
+ className: `relative h-[26px] w-[50px] cursor-pointer rounded-full border-none outline-none transition-colors duration-200 ${isOn ? "bg-accent hover:bg-accent-light" : "bg-gray-600 hover:bg-gray-500"}`,
283
+ onClick: handleToggle,
284
+ "aria-pressed": isOn,
285
+ children: /* @__PURE__ */ jsx(
286
+ "span",
287
+ {
288
+ className: `absolute top-[3px] h-5 w-5 rounded-full bg-white transition-[left] duration-200 ${isOn ? "left-[27px]" : "left-[3px]"}`
289
+ }
290
+ )
291
+ }
292
+ )
293
+ ] });
294
+ }
295
+ function VersionBadge() {
296
+ return /* @__PURE__ */ jsxs("span", { "data-testid": "version-badge", className: "text-sm font-medium text-accent", children: [
297
+ "v",
298
+ __APP_VERSION__
299
+ ] });
300
+ }
301
+ function ConnectionStatus() {
302
+ const { connected, transport } = useConnectionStatus();
303
+ if (transport === "native") {
304
+ return /* @__PURE__ */ jsx(Fragment, {});
305
+ }
306
+ return /* @__PURE__ */ jsxs(
307
+ "div",
308
+ {
309
+ "data-testid": "connection-status",
310
+ className: `flex items-center gap-2 rounded px-3 py-1.5 text-sm ${connected ? "bg-green-900/30 text-green-400" : "bg-yellow-900/30 text-yellow-400"}`,
311
+ children: [
312
+ /* @__PURE__ */ jsx("div", { className: `h-2 w-2 rounded-full ${connected ? "bg-green-400" : "bg-yellow-400"}` }),
313
+ /* @__PURE__ */ jsx("span", { children: connected ? "Connected" : "Connecting..." }),
314
+ transport === "websocket" && /* @__PURE__ */ jsx("span", { className: "text-xs opacity-70", children: "(WebSocket)" })
315
+ ]
316
+ }
317
+ );
318
+ }
319
+ function LatencyMonitor() {
320
+ const { latency, avg, max, count } = useLatencyMonitor(1e3);
321
+ const getStatusColor = () => {
322
+ if (avg < 5) return "text-green-400";
323
+ if (avg < 10) return "text-yellow-400";
324
+ return "text-red-400";
325
+ };
326
+ const getStatusText = () => {
327
+ if (avg < 5) return "✓ Excellent";
328
+ if (avg < 10) return "⚠ Fair";
329
+ return "✗ Poor";
330
+ };
331
+ return /* @__PURE__ */ jsxs("div", { className: "mb-4 rounded-lg border border-plugin-border bg-plugin-surface p-4", children: [
332
+ /* @__PURE__ */ jsx("h3", { className: "m-0 mb-3 text-base font-semibold text-gray-200", children: "IPC Latency" }),
333
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-2", children: [
334
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-between rounded bg-plugin-dark p-2", children: [
335
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-gray-500", children: "Current:" }),
336
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-sm font-semibold text-accent", children: latency === null ? "—" : `${latency.toFixed(2)} ms` })
337
+ ] }),
338
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-between rounded bg-plugin-dark p-2", children: [
339
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-gray-500", children: "Average:" }),
340
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-sm font-semibold text-accent", children: avg > 0 ? `${avg.toFixed(2)} ms` : "—" })
341
+ ] }),
342
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-between rounded bg-plugin-dark p-2", children: [
343
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-gray-500", children: "Max:" }),
344
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-sm font-semibold text-accent", children: max > 0 ? `${max.toFixed(2)} ms` : "—" })
345
+ ] }),
346
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-between rounded bg-plugin-dark p-2", children: [
347
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-gray-500", children: "Samples:" }),
348
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-sm font-semibold text-accent", children: count })
349
+ ] })
350
+ ] }),
351
+ /* @__PURE__ */ jsx("div", { className: "mt-3 text-center text-sm font-semibold", children: avg > 0 && /* @__PURE__ */ jsx("span", { className: getStatusColor(), children: getStatusText() }) })
352
+ ] });
353
+ }
354
+ function ResizeHandle() {
355
+ const requestResize = useRequestResize();
356
+ const [isDragging, setIsDragging] = useState(false);
357
+ const dragStartRef = useRef({ x: 0, y: 0, width: 0, height: 0 });
358
+ const handleMouseDown = useCallback(
359
+ (e) => {
360
+ e.preventDefault();
361
+ setIsDragging(true);
362
+ dragStartRef.current = {
363
+ x: e.clientX,
364
+ y: e.clientY,
365
+ width: window.innerWidth,
366
+ height: window.innerHeight
367
+ };
368
+ const handleMouseMove = (moveEvent) => {
369
+ const deltaX = moveEvent.clientX - dragStartRef.current.x;
370
+ const deltaY = moveEvent.clientY - dragStartRef.current.y;
371
+ const newWidth = Math.max(400, dragStartRef.current.width + deltaX);
372
+ const newHeight = Math.max(300, dragStartRef.current.height + deltaY);
373
+ requestResize(newWidth, newHeight).catch((err) => {
374
+ logger.error("Resize request failed", { error: err, width: newWidth, height: newHeight });
375
+ });
376
+ };
377
+ const handleMouseUp = () => {
378
+ setIsDragging(false);
379
+ document.removeEventListener("mousemove", handleMouseMove);
380
+ document.removeEventListener("mouseup", handleMouseUp);
381
+ };
382
+ document.addEventListener("mousemove", handleMouseMove);
383
+ document.addEventListener("mouseup", handleMouseUp);
384
+ },
385
+ [requestResize]
386
+ );
387
+ return /* @__PURE__ */ jsx(
388
+ "button",
389
+ {
390
+ "data-testid": "resize-handle",
391
+ className: `group fixed bottom-1 right-5 z-[9999] flex h-9 w-9 cursor-nwse-resize select-none items-center justify-center rounded border-none bg-transparent p-0 transition-colors duration-150 ${isDragging ? "bg-accent/20" : "hover:bg-white/10"}`,
392
+ onMouseDown: handleMouseDown,
393
+ "aria-label": "Resize window",
394
+ type: "button",
395
+ children: /* @__PURE__ */ jsxs(
396
+ "svg",
397
+ {
398
+ width: "20",
399
+ height: "20",
400
+ viewBox: "0 0 16 16",
401
+ fill: "none",
402
+ xmlns: "http://www.w3.org/2000/svg",
403
+ className: `transition-colors duration-150 ${isDragging ? "text-accent-light" : "text-white/50 group-hover:text-accent"}`,
404
+ children: [
405
+ /* @__PURE__ */ jsx(
406
+ "line",
407
+ {
408
+ x1: "14",
409
+ y1: "2",
410
+ x2: "2",
411
+ y2: "14",
412
+ stroke: "currentColor",
413
+ strokeWidth: "1.5",
414
+ strokeLinecap: "round"
415
+ }
416
+ ),
417
+ /* @__PURE__ */ jsx(
418
+ "line",
419
+ {
420
+ x1: "14",
421
+ y1: "6",
422
+ x2: "6",
423
+ y2: "14",
424
+ stroke: "currentColor",
425
+ strokeWidth: "1.5",
426
+ strokeLinecap: "round"
427
+ }
428
+ ),
429
+ /* @__PURE__ */ jsx(
430
+ "line",
431
+ {
432
+ x1: "14",
433
+ y1: "10",
434
+ x2: "10",
435
+ y2: "14",
436
+ stroke: "currentColor",
437
+ strokeWidth: "1.5",
438
+ strokeLinecap: "round"
439
+ }
440
+ )
441
+ ]
442
+ }
443
+ )
444
+ }
445
+ );
446
+ }
447
+ const PRESET_SIZES = [
448
+ { name: "Small", width: 600, height: 400 },
449
+ { name: "Medium", width: 800, height: 600 },
450
+ { name: "Large", width: 1024, height: 768 },
451
+ { name: "Extra Large", width: 1280, height: 960 }
452
+ ];
453
+ function ResizeControls() {
454
+ const requestResize = useRequestResize();
455
+ const [status, setStatus] = useState("");
456
+ const [isLoading, setIsLoading] = useState(false);
457
+ const handleResize = async (width, height) => {
458
+ setIsLoading(true);
459
+ setStatus(`Requesting ${width}x${height}...`);
460
+ try {
461
+ const accepted = await requestResize(width, height);
462
+ if (accepted) {
463
+ setStatus(`✓ Resized to ${width}x${height}`);
464
+ } else {
465
+ setStatus(`✗ Host rejected ${width}x${height}`);
466
+ }
467
+ } catch (error) {
468
+ setStatus(`✗ Error: ${error}`);
469
+ } finally {
470
+ setIsLoading(false);
471
+ }
472
+ };
473
+ return /* @__PURE__ */ jsxs("div", { className: "rounded-lg bg-black/5 p-5", children: [
474
+ /* @__PURE__ */ jsx("h3", { className: "m-0 mb-4 text-sm font-semibold uppercase tracking-wide text-black/70", children: "Window Size" }),
475
+ /* @__PURE__ */ jsx("div", { className: "mb-4 grid grid-cols-2 gap-2.5", children: PRESET_SIZES.map((preset) => /* @__PURE__ */ jsxs(
476
+ "button",
477
+ {
478
+ onClick: () => handleResize(preset.width, preset.height),
479
+ disabled: isLoading,
480
+ className: "flex cursor-pointer flex-col items-center justify-center rounded-md border border-gray-300 bg-white p-3 font-medium text-gray-800 transition-all duration-200 hover:-translate-y-px hover:border-blue-500 hover:bg-gray-100 hover:shadow-md disabled:cursor-not-allowed disabled:opacity-50",
481
+ children: [
482
+ preset.name,
483
+ /* @__PURE__ */ jsxs("span", { className: "mt-1 text-[11px] text-gray-500", children: [
484
+ preset.width,
485
+ " × ",
486
+ preset.height
487
+ ] })
488
+ ]
489
+ },
490
+ preset.name
491
+ )) }),
492
+ status && /* @__PURE__ */ jsx(
493
+ "div",
494
+ {
495
+ className: `rounded px-3 py-2 text-center text-sm ${(() => {
496
+ if (status.startsWith("✓")) return "bg-green-500/10 text-green-600";
497
+ if (status.startsWith("✗")) return "bg-red-500/10 text-red-500";
498
+ return "bg-black/5 text-gray-500";
499
+ })()}`,
500
+ children: status
501
+ }
502
+ )
503
+ ] });
504
+ }
505
+ export {
506
+ ConnectionStatus,
507
+ LatencyMonitor,
508
+ Meter,
509
+ ParameterGroup,
510
+ ParameterSlider,
511
+ ParameterToggle,
512
+ ResizeControls,
513
+ ResizeHandle,
514
+ VersionBadge
515
+ };
516
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../src/Meter.tsx","../src/ParameterSlider.tsx","../src/ParameterGroup.tsx","../src/ParameterToggle.tsx","../src/VersionBadge.tsx","../src/ConnectionStatus.tsx","../src/LatencyMonitor.tsx","../src/ResizeHandle.tsx","../src/ResizeControls.tsx"],"sourcesContent":["/**\n * Meter - Audio level meter visualization component\n *\n * Displays peak and RMS levels for stereo audio with dB scaling\n */\n\nimport React, { useEffect, useState, useRef } from 'react';\nimport {\n getMeterFrame,\n linearToDb,\n useConnectionStatus,\n type MeterFrame,\n} from '@wavecraft/core';\n\nconst METER_UPDATE_HZ = 30;\nconst METER_FLOOR_DB = -60;\nconst METER_RANGE_DB = 60; // 0 to -60 dB\nconst CLIP_THRESHOLD = 1; // Linear amplitude threshold\nconst CLIP_HOLD_MS = 2000; // Hold clip indicator for 2 seconds\n\nexport function Meter(): React.JSX.Element {\n const { connected } = useConnectionStatus();\n const [frame, setFrame] = useState<MeterFrame | null>(null);\n const [clippedL, setClippedL] = useState(false);\n const [clippedR, setClippedR] = useState(false);\n\n const clipLTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const clipRTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n useEffect(() => {\n // Only poll when connected\n if (!connected) {\n return;\n }\n\n // Poll meter frames at 30 Hz\n const interval = setInterval(async () => {\n const newFrame = await getMeterFrame();\n setFrame(newFrame);\n\n // Detect clipping (peak > 1.0 linear = 0 dB)\n if (newFrame) {\n if (newFrame.peak_l > CLIP_THRESHOLD) {\n setClippedL(true);\n // Clear existing timeout and set new one\n if (clipLTimeoutRef.current !== null) {\n clearTimeout(clipLTimeoutRef.current);\n }\n clipLTimeoutRef.current = globalThis.setTimeout(() => {\n setClippedL(false);\n clipLTimeoutRef.current = null;\n }, CLIP_HOLD_MS);\n }\n\n if (newFrame.peak_r > CLIP_THRESHOLD) {\n setClippedR(true);\n // Clear existing timeout and set new one\n if (clipRTimeoutRef.current !== null) {\n clearTimeout(clipRTimeoutRef.current);\n }\n clipRTimeoutRef.current = globalThis.setTimeout(() => {\n setClippedR(false);\n clipRTimeoutRef.current = null;\n }, CLIP_HOLD_MS);\n }\n }\n }, 1000 / METER_UPDATE_HZ);\n\n return (): void => {\n clearInterval(interval);\n if (clipLTimeoutRef.current !== null) {\n clearTimeout(clipLTimeoutRef.current);\n }\n if (clipRTimeoutRef.current !== null) {\n clearTimeout(clipRTimeoutRef.current);\n }\n };\n }, [connected]);\n\n // Convert linear to dB for display\n const peakLDb = frame ? linearToDb(frame.peak_l, METER_FLOOR_DB) : METER_FLOOR_DB;\n const peakRDb = frame ? linearToDb(frame.peak_r, METER_FLOOR_DB) : METER_FLOOR_DB;\n const rmsLDb = frame ? linearToDb(frame.rms_l, METER_FLOOR_DB) : METER_FLOOR_DB;\n const rmsRDb = frame ? linearToDb(frame.rms_r, METER_FLOOR_DB) : METER_FLOOR_DB;\n\n // Normalize to 0-100% for CSS\n const peakLPercent = ((peakLDb - METER_FLOOR_DB) / METER_RANGE_DB) * 100;\n const peakRPercent = ((peakRDb - METER_FLOOR_DB) / METER_RANGE_DB) * 100;\n const rmsLPercent = ((rmsLDb - METER_FLOOR_DB) / METER_RANGE_DB) * 100;\n const rmsRPercent = ((rmsRDb - METER_FLOOR_DB) / METER_RANGE_DB) * 100;\n\n const handleResetClip = (): void => {\n setClippedL(false);\n setClippedR(false);\n if (clipLTimeoutRef.current !== null) {\n clearTimeout(clipLTimeoutRef.current);\n clipLTimeoutRef.current = null;\n }\n if (clipRTimeoutRef.current !== null) {\n clearTimeout(clipRTimeoutRef.current);\n clipRTimeoutRef.current = null;\n }\n };\n\n // Show connecting state when not connected\n if (!connected) {\n return (\n <div\n data-testid=\"meter\"\n className=\"flex flex-col gap-2 rounded-lg border border-plugin-border bg-plugin-surface p-4 font-sans\"\n >\n <div className=\"flex items-center justify-between gap-2\">\n <div className=\"text-xs font-semibold uppercase tracking-wide text-gray-500\">Levels</div>\n </div>\n <div className=\"flex items-center justify-center py-8 text-sm text-gray-400\">\n ⏳ Connecting...\n </div>\n </div>\n );\n }\n\n return (\n <div\n data-testid=\"meter\"\n className=\"flex flex-col gap-2 rounded-lg border border-plugin-border bg-plugin-surface p-4 font-sans\"\n >\n <div className=\"flex items-center justify-between gap-2\">\n <div className=\"text-xs font-semibold uppercase tracking-wide text-gray-500\">Levels</div>\n {(clippedL || clippedR) && (\n <button\n data-testid=\"meter-clip-button\"\n className=\"animate-clip-pulse cursor-pointer select-none rounded border-none bg-meter-clip px-2 py-0.5 text-[10px] font-bold text-white hover:bg-meter-clip-dark active:scale-95\"\n onClick={handleResetClip}\n title=\"Click to reset\"\n type=\"button\"\n >\n CLIP\n </button>\n )}\n </div>\n\n <div data-testid=\"meter-L\" className=\"flex items-center gap-2 rounded bg-plugin-dark p-2\">\n <div className=\"w-4 text-center text-[11px] font-semibold text-gray-300\">L</div>\n <div className=\"relative h-6 flex-1\">\n <div\n className={`relative h-full w-full overflow-hidden rounded bg-[#333] transition-shadow duration-100 ${\n clippedL ? 'shadow-[inset_0_0_8px_rgba(255,23,68,0.8)]' : ''\n }`}\n >\n <div\n data-testid=\"meter-L-rms\"\n className=\"absolute left-0 top-0 h-full bg-gradient-to-r from-meter-safe to-meter-safe-light transition-[width] duration-100\"\n style={{ width: `${Math.max(0, Math.min(100, rmsLPercent))}%` }}\n />\n <div\n data-testid=\"meter-L-peak\"\n className=\"duration-50 absolute left-0 top-0 h-full bg-gradient-to-r from-meter-safe via-meter-warning to-orange-500 opacity-60 transition-[width]\"\n style={{ width: `${Math.max(0, Math.min(100, peakLPercent))}%` }}\n />\n </div>\n </div>\n <div\n data-testid=\"meter-L-db\"\n className={`w-[60px] text-right font-mono text-[11px] text-gray-300 transition-colors duration-100 ${\n clippedL ? 'font-semibold text-meter-clip' : ''\n }`}\n >\n {peakLDb.toFixed(1)} dB\n </div>\n </div>\n\n <div data-testid=\"meter-R\" className=\"flex items-center gap-2 rounded bg-plugin-dark p-2\">\n <div className=\"w-4 text-center text-[11px] font-semibold text-gray-300\">R</div>\n <div className=\"relative h-6 flex-1\">\n <div\n className={`relative h-full w-full overflow-hidden rounded bg-[#333] transition-shadow duration-100 ${\n clippedR ? 'shadow-[inset_0_0_8px_rgba(255,23,68,0.8)]' : ''\n }`}\n >\n <div\n data-testid=\"meter-R-rms\"\n className=\"absolute left-0 top-0 h-full bg-gradient-to-r from-meter-safe to-meter-safe-light transition-[width] duration-100\"\n style={{ width: `${Math.max(0, Math.min(100, rmsRPercent))}%` }}\n />\n <div\n data-testid=\"meter-R-peak\"\n className=\"duration-50 absolute left-0 top-0 h-full bg-gradient-to-r from-meter-safe via-meter-warning to-orange-500 opacity-60 transition-[width]\"\n style={{ width: `${Math.max(0, Math.min(100, peakRPercent))}%` }}\n />\n </div>\n </div>\n <div\n data-testid=\"meter-R-db\"\n className={`w-[60px] text-right font-mono text-[11px] text-gray-300 transition-colors duration-100 ${\n clippedR ? 'font-semibold text-meter-clip' : ''\n }`}\n >\n {peakRDb.toFixed(1)} dB\n </div>\n </div>\n </div>\n );\n}\n","/**\n * ParameterSlider - Slider control for float parameters\n */\n\nimport React from 'react';\nimport { useParameter, logger } from '@wavecraft/core';\n\ninterface ParameterSliderProps {\n readonly id: string;\n}\n\nexport function ParameterSlider({ id }: ParameterSliderProps): React.JSX.Element {\n const { param, setValue, isLoading, error } = useParameter(id);\n\n if (isLoading) {\n return (\n <div className=\"mb-4 rounded-lg border border-plugin-border bg-plugin-surface p-4 italic text-gray-500\">\n Loading {id}...\n </div>\n );\n }\n\n if (error || !param) {\n return (\n <div className=\"mb-4 rounded-lg border border-red-400 bg-plugin-surface p-4 text-red-400\">\n Error: {error?.message || 'Parameter not found'}\n </div>\n );\n }\n\n const handleChange = (e: React.ChangeEvent<HTMLInputElement>): void => {\n const value = Number.parseFloat(e.target.value);\n setValue(value).catch((err) => {\n logger.error('Failed to set parameter', { error: err, parameterId: id });\n });\n };\n\n // Format display value\n const unitSuffix = param.unit === '%' ? param.unit : ` ${param.unit}`;\n const displayValue = param.unit\n ? `${(param.value * 100).toFixed(1)}${unitSuffix}`\n : param.value.toFixed(3);\n\n return (\n <div\n data-testid={`param-${id}`}\n className=\"mb-4 rounded-lg border border-plugin-border bg-plugin-surface p-4\"\n >\n <div className=\"mb-2 flex items-center justify-between\">\n <label\n data-testid={`param-${id}-label`}\n htmlFor={`slider-${id}`}\n className=\"font-semibold text-gray-200\"\n >\n {param.name}\n </label>\n <span data-testid={`param-${id}-value`} className=\"font-mono text-sm text-accent\">\n {displayValue}\n </span>\n </div>\n <input\n data-testid={`param-${id}-slider`}\n id={`slider-${id}`}\n type=\"range\"\n min=\"0\"\n max=\"1\"\n step=\"0.001\"\n value={param.value}\n onChange={handleChange}\n className=\"slider-thumb h-1.5 w-full appearance-none rounded-sm bg-plugin-border outline-none\"\n />\n </div>\n );\n}\n","/**\n * ParameterGroup component - displays a group of parameters with a header.\n *\n * This component provides visual organization for parameters by grouping them\n * under a common header. It's typically used with the useParameterGroups hook.\n */\n\nimport React from 'react';\nimport type { ParameterGroup as ParameterGroupType } from '@wavecraft/core';\nimport { ParameterSlider } from './ParameterSlider';\n\nexport interface ParameterGroupProps {\n /** The parameter group to display */\n group: ParameterGroupType;\n}\n\n/**\n * Displays a group of parameters with a header and visual grouping.\n *\n * @example\n * ```tsx\n * const { parameters } = useAllParameters();\n * const groups = useParameterGroups(parameters);\n *\n * return (\n * <div className=\"space-y-4\">\n * {groups.map(group => (\n * <ParameterGroup key={group.name} group={group} />\n * ))}\n * </div>\n * );\n * ```\n */\nexport function ParameterGroup({ group }: Readonly<ParameterGroupProps>): React.JSX.Element {\n return (\n <div className=\"space-y-2\">\n {/* Group header */}\n <h3 className=\"text-sm font-semibold uppercase tracking-wider text-gray-400\">{group.name}</h3>\n\n {/* Parameter list */}\n <div className=\"space-y-3\">\n {group.parameters.map((param) => (\n <ParameterSlider key={param.id} id={param.id} />\n ))}\n </div>\n </div>\n );\n}\n","/**\n * ParameterToggle - Toggle control for boolean parameters\n */\n\nimport React from 'react';\nimport { useParameter, logger } from '@wavecraft/core';\n\ninterface ParameterToggleProps {\n readonly id: string;\n}\n\nexport function ParameterToggle({ id }: ParameterToggleProps): React.JSX.Element {\n const { param, setValue, isLoading, error } = useParameter(id);\n\n if (isLoading) {\n return (\n <div className=\"mb-4 flex items-center justify-between rounded-lg border border-plugin-border bg-plugin-surface p-4 italic text-gray-500\">\n Loading {id}...\n </div>\n );\n }\n\n if (error || !param) {\n return (\n <div className=\"mb-4 flex items-center justify-between rounded-lg border border-red-400 bg-plugin-surface p-4 text-red-400\">\n Error: {error?.message || 'Parameter not found'}\n </div>\n );\n }\n\n const isOn = param.value >= 0.5;\n\n const handleToggle = (): void => {\n const newValue = isOn ? 0 : 1;\n setValue(newValue).catch((err) => {\n logger.error('Failed to set parameter', { error: err, parameterId: id });\n });\n };\n\n return (\n <div className=\"mb-4 flex items-center justify-between rounded-lg border border-plugin-border bg-plugin-surface p-4\">\n <label htmlFor={`toggle-${id}`} className=\"font-semibold text-gray-200\">\n {param.name}\n </label>\n <button\n id={`toggle-${id}`}\n className={`relative h-[26px] w-[50px] cursor-pointer rounded-full border-none outline-none transition-colors duration-200 ${\n isOn ? 'bg-accent hover:bg-accent-light' : 'bg-gray-600 hover:bg-gray-500'\n }`}\n onClick={handleToggle}\n aria-pressed={isOn}\n >\n <span\n className={`absolute top-[3px] h-5 w-5 rounded-full bg-white transition-[left] duration-200 ${\n isOn ? 'left-[27px]' : 'left-[3px]'\n }`}\n ></span>\n </button>\n </div>\n );\n}\n","/**\n * VersionBadge Component\n *\n * Displays the plugin version in a clearly visible badge.\n * Version is injected at build time from engine/Cargo.toml.\n */\n\nimport React from 'react';\n\nexport function VersionBadge(): React.JSX.Element {\n return (\n <span data-testid=\"version-badge\" className=\"text-sm font-medium text-accent\">\n v{__APP_VERSION__}\n </span>\n );\n}\n","/**\n * ConnectionStatus - Visual indicator for IPC transport connection\n *\n * Shows connection status for WebSocket transport in browser mode.\n * Native transport (WKWebView) is always connected.\n */\n\nimport React from 'react';\nimport { useConnectionStatus } from '@wavecraft/core';\n\nexport function ConnectionStatus(): React.JSX.Element {\n const { connected, transport } = useConnectionStatus();\n\n // Native transport is always connected - no need to show indicator\n if (transport === 'native') {\n return <></>;\n }\n\n return (\n <div\n data-testid=\"connection-status\"\n className={`flex items-center gap-2 rounded px-3 py-1.5 text-sm ${\n connected ? 'bg-green-900/30 text-green-400' : 'bg-yellow-900/30 text-yellow-400'\n }`}\n >\n <div className={`h-2 w-2 rounded-full ${connected ? 'bg-green-400' : 'bg-yellow-400'}`} />\n <span>{connected ? 'Connected' : 'Connecting...'}</span>\n {transport === 'websocket' && <span className=\"text-xs opacity-70\">(WebSocket)</span>}\n </div>\n );\n}\n","/**\n * LatencyMonitor - Displays IPC roundtrip latency metrics\n */\n\nimport React from 'react';\nimport { useLatencyMonitor } from '@wavecraft/core';\n\nexport function LatencyMonitor(): React.JSX.Element {\n const { latency, avg, max, count } = useLatencyMonitor(1000);\n\n const getStatusColor = (): string => {\n if (avg < 5) return 'text-green-400';\n if (avg < 10) return 'text-yellow-400';\n return 'text-red-400';\n };\n\n const getStatusText = (): string => {\n if (avg < 5) return '✓ Excellent';\n if (avg < 10) return '⚠ Fair';\n return '✗ Poor';\n };\n\n return (\n <div className=\"mb-4 rounded-lg border border-plugin-border bg-plugin-surface p-4\">\n <h3 className=\"m-0 mb-3 text-base font-semibold text-gray-200\">IPC Latency</h3>\n <div className=\"grid grid-cols-2 gap-2\">\n <div className=\"flex justify-between rounded bg-plugin-dark p-2\">\n <span className=\"text-sm text-gray-500\">Current:</span>\n <span className=\"font-mono text-sm font-semibold text-accent\">\n {latency === null ? '—' : `${latency.toFixed(2)} ms`}\n </span>\n </div>\n <div className=\"flex justify-between rounded bg-plugin-dark p-2\">\n <span className=\"text-sm text-gray-500\">Average:</span>\n <span className=\"font-mono text-sm font-semibold text-accent\">\n {avg > 0 ? `${avg.toFixed(2)} ms` : '—'}\n </span>\n </div>\n <div className=\"flex justify-between rounded bg-plugin-dark p-2\">\n <span className=\"text-sm text-gray-500\">Max:</span>\n <span className=\"font-mono text-sm font-semibold text-accent\">\n {max > 0 ? `${max.toFixed(2)} ms` : '—'}\n </span>\n </div>\n <div className=\"flex justify-between rounded bg-plugin-dark p-2\">\n <span className=\"text-sm text-gray-500\">Samples:</span>\n <span className=\"font-mono text-sm font-semibold text-accent\">{count}</span>\n </div>\n </div>\n <div className=\"mt-3 text-center text-sm font-semibold\">\n {avg > 0 && <span className={getStatusColor()}>{getStatusText()}</span>}\n </div>\n </div>\n );\n}\n","/**\n * ResizeHandle component - Bottom-right corner resize grip\n *\n * Provides a visual affordance for freeform window resizing.\n * Captures mouse drag events and communicates size changes to the host.\n */\n\nimport React, { useCallback, useRef, useState } from 'react';\nimport { useRequestResize, logger } from '@wavecraft/core';\n\nexport function ResizeHandle(): React.JSX.Element {\n const requestResize = useRequestResize();\n const [isDragging, setIsDragging] = useState(false);\n const dragStartRef = useRef({ x: 0, y: 0, width: 0, height: 0 });\n\n const handleMouseDown = useCallback(\n (e: React.MouseEvent) => {\n e.preventDefault();\n setIsDragging(true);\n\n // Capture initial state\n dragStartRef.current = {\n x: e.clientX,\n y: e.clientY,\n width: window.innerWidth,\n height: window.innerHeight,\n };\n\n const handleMouseMove = (moveEvent: MouseEvent): void => {\n const deltaX = moveEvent.clientX - dragStartRef.current.x;\n const deltaY = moveEvent.clientY - dragStartRef.current.y;\n\n const newWidth = Math.max(400, dragStartRef.current.width + deltaX);\n const newHeight = Math.max(300, dragStartRef.current.height + deltaY);\n\n // Request resize from host\n requestResize(newWidth, newHeight).catch((err) => {\n logger.error('Resize request failed', { error: err, width: newWidth, height: newHeight });\n });\n };\n\n const handleMouseUp = (): void => {\n setIsDragging(false);\n document.removeEventListener('mousemove', handleMouseMove);\n document.removeEventListener('mouseup', handleMouseUp);\n };\n\n document.addEventListener('mousemove', handleMouseMove);\n document.addEventListener('mouseup', handleMouseUp);\n },\n [requestResize]\n );\n\n return (\n <button\n data-testid=\"resize-handle\"\n className={`group fixed bottom-1 right-5 z-[9999] flex h-9 w-9 cursor-nwse-resize select-none items-center justify-center rounded border-none bg-transparent p-0 transition-colors duration-150 ${\n isDragging ? 'bg-accent/20' : 'hover:bg-white/10'\n }`}\n onMouseDown={handleMouseDown}\n aria-label=\"Resize window\"\n type=\"button\"\n >\n <svg\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n className={`transition-colors duration-150 ${\n isDragging ? 'text-accent-light' : 'text-white/50 group-hover:text-accent'\n }`}\n >\n {/* Diagonal grip lines */}\n <line\n x1=\"14\"\n y1=\"2\"\n x2=\"2\"\n y2=\"14\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n />\n <line\n x1=\"14\"\n y1=\"6\"\n x2=\"6\"\n y2=\"14\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n />\n <line\n x1=\"14\"\n y1=\"10\"\n x2=\"10\"\n y2=\"14\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n />\n </svg>\n </button>\n );\n}\n","/**\n * ResizeControls component - Demonstrates window resizing functionality\n */\n\nimport React, { useState } from 'react';\nimport { useRequestResize } from '@wavecraft/core';\n\nconst PRESET_SIZES = [\n { name: 'Small', width: 600, height: 400 },\n { name: 'Medium', width: 800, height: 600 },\n { name: 'Large', width: 1024, height: 768 },\n { name: 'Extra Large', width: 1280, height: 960 },\n];\n\nexport function ResizeControls(): React.JSX.Element {\n const requestResize = useRequestResize();\n const [status, setStatus] = useState<string>('');\n const [isLoading, setIsLoading] = useState(false);\n\n const handleResize = async (width: number, height: number): Promise<void> => {\n setIsLoading(true);\n setStatus(`Requesting ${width}x${height}...`);\n\n try {\n const accepted = await requestResize(width, height);\n if (accepted) {\n setStatus(`✓ Resized to ${width}x${height}`);\n } else {\n setStatus(`✗ Host rejected ${width}x${height}`);\n }\n } catch (error) {\n setStatus(`✗ Error: ${error}`);\n } finally {\n setIsLoading(false);\n }\n };\n\n return (\n <div className=\"rounded-lg bg-black/5 p-5\">\n <h3 className=\"m-0 mb-4 text-sm font-semibold uppercase tracking-wide text-black/70\">\n Window Size\n </h3>\n <div className=\"mb-4 grid grid-cols-2 gap-2.5\">\n {PRESET_SIZES.map((preset) => (\n <button\n key={preset.name}\n onClick={() => handleResize(preset.width, preset.height)}\n disabled={isLoading}\n className=\"flex cursor-pointer flex-col items-center justify-center rounded-md border border-gray-300 bg-white p-3 font-medium text-gray-800 transition-all duration-200 hover:-translate-y-px hover:border-blue-500 hover:bg-gray-100 hover:shadow-md disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {preset.name}\n <span className=\"mt-1 text-[11px] text-gray-500\">\n {preset.width} × {preset.height}\n </span>\n </button>\n ))}\n </div>\n {status && (\n <div\n className={`rounded px-3 py-2 text-center text-sm ${((): string => {\n if (status.startsWith('✓')) return 'bg-green-500/10 text-green-600';\n if (status.startsWith('✗')) return 'bg-red-500/10 text-red-500';\n return 'bg-black/5 text-gray-500';\n })()}`}\n >\n {status}\n </div>\n )}\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@wavecraft/components",
3
+ "version": "0.7.0",
4
+ "description": "Pre-built React components for Wavecraft audio plugins",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/index.js",
12
+ "types": "./dist/index.d.ts"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md"
18
+ ],
19
+ "sideEffects": false,
20
+ "keywords": [
21
+ "wavecraft",
22
+ "vst",
23
+ "audio",
24
+ "plugin",
25
+ "react",
26
+ "components",
27
+ "meter",
28
+ "slider",
29
+ "ui"
30
+ ],
31
+ "author": "Ron Houben",
32
+ "license": "MIT",
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "git+https://github.com/RonHouben/wavecraft.git",
36
+ "directory": "ui/packages/components"
37
+ },
38
+ "homepage": "https://github.com/RonHouben/wavecraft",
39
+ "bugs": {
40
+ "url": "https://github.com/RonHouben/wavecraft/issues"
41
+ },
42
+ "peerDependencies": {
43
+ "@wavecraft/core": "^0.7.0",
44
+ "react": "^18.0.0",
45
+ "react-dom": "^18.0.0"
46
+ },
47
+ "peerDependenciesMeta": {
48
+ "react-dom": {
49
+ "optional": true
50
+ }
51
+ },
52
+ "scripts": {
53
+ "build": "npm run build:lib",
54
+ "build:lib": "vite build --config vite.lib.config.ts",
55
+ "prepublishOnly": "npm run build:lib"
56
+ }
57
+ }