cabbage-react 1.0.14 → 1.0.16

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.
@@ -1,25 +1,94 @@
1
1
  export class Cabbage {
2
2
  /**
3
- * Main entry point for sending any data from UI widgets to the Cabbage backend.
3
+ * Main entry point for sending widget value changes to the Cabbage backend.
4
+ *
4
5
  * This function automatically routes messages to the appropriate backend function
5
6
  * based on the automatable flag:
6
7
  *
7
- * - automatable=true: Routes to sendParameterUpdate for real-time parameter control /
8
- * this also sends the value as channel data to Csound
8
+ * - `automatable=true`: Routes to `sendParameterUpdate()` for DAW-automatable parameters.
9
+ * The value is sent to the DAW for automation recording and also forwarded to Csound.
10
+ *
11
+ * - `automatable=false`: Routes to `sendChannelData()` for non-automatable data.
12
+ * The value is sent directly to Csound without DAW parameter involvement.
13
+ *
14
+ * **When to use**: Call this from widget event handlers (e.g., pointer events, input changes)
15
+ * when the user interacts with a widget.
16
+ *
17
+ * **When NOT to use**: Do not call this when handling incoming `parameterChange` messages
18
+ * from the backend. Those messages are for display updates only.
19
+ *
20
+ * @param {Object} message - The message object containing widget data
21
+ * @param {string} message.channel - The channel name
22
+ * @param {number} message.paramIdx - Parameter index (required if automatable)
23
+ * @param {number|string} message.value - The value to send
24
+ * @param {Object|null} vscode - VS Code API object (null for plugin mode)
25
+ * @param {boolean} automatable - Whether this widget is DAW-automatable
26
+ */
27
+ static sendChannelUpdate(message: {
28
+ channel: string;
29
+ paramIdx: number;
30
+ value: number | string;
31
+ }, vscode?: Object | null, automatable?: boolean): void;
32
+ /**
33
+ * Internal: Send a parameter update to the DAW for automation recording.
34
+ * Use `sendChannelUpdate()` instead - it will route here automatically for automatable widgets.
35
+ *
36
+ * This function sends the parameter value to the DAW's automation system.
37
+ * The DAW will then send the value back via a `parameterChange` message,
38
+ * which updates both the UI and Csound.
9
39
  *
10
- * - automatable=false: Routes to sendChannelData for string/numeric data transmission
40
+ * **Important**: This creates a round-trip through the DAW:
41
+ * 1. UI calls `sendParameterUpdate()` with value
42
+ * 2. DAW receives and records/processes the value
43
+ * 3. DAW sends `parameterChange` back to the plugin
44
+ * 4. Plugin updates Csound and sends `parameterChange` to UI
45
+ * 5. UI updates its display (but does NOT call sendParameterUpdate again!)
11
46
  *
12
- * All widget interactions should use this function instead of calling the lower-level
13
- * sendParameterUpdate or sendChannelData functions directly.
47
+ * @param {Object} message - The parameter message
48
+ * @param {number} message.paramIdx - The parameter index (must be >= 0)
49
+ * @param {string} message.channel - The channel name
50
+ * @param {number} message.value - The parameter value (full range, not normalized)
51
+ * @param {string} [message.channelType="number"] - The channel type
52
+ * @param {Object|null} vscode - VS Code API object (null for plugin mode)
14
53
  */
15
- static sendChannelUpdate(message: any, vscode?: null, automatable?: boolean): void;
16
- static sendParameterUpdate(message: any, vscode?: null): void;
54
+ static sendParameterUpdate(message: {
55
+ paramIdx: number;
56
+ channel: string;
57
+ value: number;
58
+ channelType?: string | undefined;
59
+ }, vscode?: Object | null): void;
17
60
  static sendCustomCommand(command: any, vscode?: null, additionalData?: {}): void;
18
61
  static sendWidgetUpdate(widget: any, vscode?: null): void;
19
62
  static sendMidiMessageFromUI(statusByte: any, dataByte1: any, dataByte2: any, vscode?: null): void;
20
- static sendChannelData(channel: any, data: any, vscode?: null): void;
63
+ /**
64
+ * @private
65
+ * Internal: Send channel data directly to Csound without DAW automation involvement.
66
+ * Use `sendChannelUpdate()` instead - it will route here automatically for non-automatable widgets.
67
+ *
68
+ * Used for non-automatable widgets like buttons, file selectors, or
69
+ * any widget that sends string data. The value is sent directly to Csound's
70
+ * channel system and is not recorded by DAW automation.
71
+ *
72
+ * @param {string} channel - The Csound channel name
73
+ * @param {number|string} data - The data to send (number or string)
74
+ * @param {Object|null} vscode - VS Code API object (null for plugin mode)
75
+ */
76
+ private static sendChannelData;
21
77
  static MidiMessageFromHost(statusByte: any, dataByte1: any, dataByte2: any): void;
22
78
  static triggerFileOpenDialog(vscode: any, channel: any, options?: {}): void;
23
79
  static openUrl(vscode: any, url: any, file: any): void;
80
+ /**
81
+ * Request a resize of the plugin GUI window.
82
+ * This is only supported in plugin mode (CLAP/VST3/AUv2).
83
+ * The host may accept or reject the resize request.
84
+ *
85
+ * @param {number} width - The requested width in pixels
86
+ * @param {number} height - The requested height in pixels
87
+ * @param {object} vscode - The vscode API object (null for plugin mode)
88
+ *
89
+ * The response will be sent via hostMessageCallback with:
90
+ * {command: "resizeResponse", accepted: boolean, width: number, height: number}
91
+ */
92
+ static requestResize(width: number, height: number, vscode?: object): void;
24
93
  }
25
94
  //# sourceMappingURL=cabbage.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cabbage.d.ts","sourceRoot":"","sources":["../../src/cabbage/cabbage.js"],"names":[],"mappings":"AAMA;IACC;;;;;;;;;;;;OAYG;IACH,mFAYC;IAED,8DAsCC;IAED,iFAiCC;IAED,0DAiBC;IAED,mGA6BC;IAED,qEAoCC;IAED,kFASC;IAED,4EA2BC;IAED,uDAsBC;CACD"}
1
+ {"version":3,"file":"cabbage.d.ts","sourceRoot":"","sources":["../../src/cabbage/cabbage.js"],"names":[],"mappings":"AAqFA;IACC;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACH,kCANG;QAAwB,OAAO,EAAvB,MAAM;QACU,QAAQ,EAAxB,MAAM;QACiB,KAAK,EAA5B,MAAM,GAAC,MAAM;KACrB,WAAQ,MAAM,GAAC,IAAI,gBACX,OAAO,QAcjB;IAED;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,oCANG;QAAwB,QAAQ,EAAxB,MAAM;QACU,OAAO,EAAvB,MAAM;QACU,KAAK,EAArB,MAAM;QACW,WAAW;KACpC,WAAQ,MAAM,GAAC,IAAI,QAwCrB;IAED,iFAiCC;IAED,0DAiBC;IAED,mGA6BC;IAED;;;;;;;;;;;;OAYG;IACH,+BAoCC;IAED,kFASC;IAED,4EA2BC;IAED,uDAsBC;IAED;;;;;;;;;;;OAWG;IACH,4BAPW,MAAM,UACN,MAAM,WACN,MAAM,QA4BhB;CACD"}
@@ -3,8 +3,9 @@
3
3
  * This hook listens for updates to a parameter value from Cabbage and
4
4
  * sends updates to Cabbage when the parameter value changes locally (e.g., through a UI slider).
5
5
  */
6
- export declare const useCabbageState: <T>(channelId: string) => {
6
+ export declare const useCabbageState: <T>(channelId: string, isDragging?: boolean) => {
7
7
  value: T | undefined;
8
8
  setValue: (value: T) => void;
9
+ parameterIndex: number | undefined;
9
10
  };
10
11
  //# sourceMappingURL=useCabbageState.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useCabbageState.d.ts","sourceRoot":"","sources":["../../src/hooks/useCabbageState.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AACH,eAAO,MAAM,eAAe,GAAI,CAAC,aAAa,MAAM;;sBAMjB,CAAC;CAqGnC,CAAC"}
1
+ {"version":3,"file":"useCabbageState.d.ts","sourceRoot":"","sources":["../../src/hooks/useCabbageState.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AACH,eAAO,MAAM,eAAe,GAAI,CAAC,aAAa,MAAM,eAAe,OAAO;;sBAMvC,CAAC;;CA+GnC,CAAC"}
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const u=require("react");console.log("Cabbage: loading cabbage.js");class f{static sendChannelUpdate(e,n=null,a=!1){if(a===!0||a===1)f.sendParameterUpdate(e,n);else{const s=e.value!==void 0?e.value:e.stringData||e.floatData;f.sendChannelData(e.channel,s,n)}}static sendParameterUpdate(e,n=null){if(e.paramIdx===void 0||e.paramIdx===null){console.error("Cabbage.sendParameterUpdate: message missing paramIdx!",e);return}if(e.paramIdx<0){console.warn("Cabbage.sendParameterUpdate: paramIdx is -1, skipping (non-automatable widget)",e);return}const a={command:"parameterChange",paramIdx:e.paramIdx,channel:e.channel,value:e.value,channelType:e.channelType||"number"};n!==null?n.postMessage(a):typeof window.sendMessageFromUI=="function"?window.sendMessageFromUI(a):console.error("Cabbage: window.sendMessageFromUI is not available. Message:",a)}static sendCustomCommand(e,n=null,a={}){const s={command:e,text:JSON.stringify(a)};if(n!==null)n.postMessage(s);else if(typeof window.sendMessageFromUI=="function"){console.log("Cabbage: Calling window.sendMessageFromUI with:",s);try{const o=window.sendMessageFromUI(s);console.log("Cabbage: sendMessageFromUI returned:",o)}catch(o){console.error("Cabbage: sendMessageFromUI threw error:",o),console.error("Cabbage: Error stack:",o.stack)}}else console.error("Cabbage: window.sendMessageFromUI is not available yet. Message:",s),console.error("Cabbage: typeof window.sendMessageFromUI:",typeof window.sendMessageFromUI),console.error("Cabbage: window.sendMessageFromUI value:",window.sendMessageFromUI)}static sendWidgetUpdate(e,n=null){const a={command:"widgetStateUpdate",obj:JSON.stringify(CabbageUtils.sanitizeForEditor(e))};n!==null?n.postMessage(a):typeof window.sendMessageFromUI=="function"?window.sendMessageFromUI(a):console.error("Cabbage: window.sendMessageFromUI is not available. Message:",a)}static sendMidiMessageFromUI(e,n,a,s=null){var o={statusByte:e,dataByte1:n,dataByte2:a};const l={command:"midiMessage",obj:JSON.stringify(o)};s!==null?s.postMessage(l):typeof window.sendMessageFromUI=="function"?window.sendMessageFromUI(l):console.error("Cabbage: window.sendMessageFromUI is not available. Message:",l)}static sendChannelData(e,n,a=null){var s={channel:e};if(typeof n=="string")s.stringData=n;else if(typeof n=="number")s.floatData=n;else{console.warn("Cabbage: sendChannelData received unsupported data type:",typeof n);return}const o={command:"channelData",obj:JSON.stringify(s)};console.log("Cabbage: sending channel data from UI",s),a!==null?a.postMessage(o):typeof window.sendMessageFromUI=="function"?window.sendMessageFromUI(o):console.error("Cabbage: window.sendMessageFromUI is not available. Message:",o)}static MidiMessageFromHost(e,n,a){console.log("Cabbage: Got MIDI Message"+e+":"+n+":"+a)}static triggerFileOpenDialog(e,n,a={}){var s={channel:n,directory:a.directory||"",filters:a.filters||"*",openAtLastKnownLocation:a.openAtLastKnownLocation!==void 0?a.openAtLastKnownLocation:!0};const o={command:"fileOpen",obj:JSON.stringify(s)};e!==null?e.postMessage(o):typeof window.sendMessageFromUI=="function"?window.sendMessageFromUI(o):console.error("Cabbage: window.sendMessageFromUI is not available. Message:",o)}static openUrl(e,n,a){var s={url:n,file:a};const o={command:"openUrl",obj:JSON.stringify(s)};e!==null?e.postMessage(o):typeof window.sendMessageFromUI=="function"?window.sendMessageFromUI(o):console.error("Cabbage: window.sendMessageFromUI is not available. Message:",o)}}const M=d=>{const[e,n]=u.useState();return u.useEffect(()=>{const a=s=>{const{id:o,widgetJson:l,command:t}=s.data;if(o===d&&l&&t==="widgetUpdate"){const r=JSON.parse(l);console.log(`[Cabbage-React] Received properties for channelId ${o}`,r),n(r)}};return window.addEventListener("message",a),()=>{window.removeEventListener("message",a)}},[]),{properties:e}},I=d=>{const{properties:e}=M(d),[n,a]=u.useState(),[s,o]=u.useState(),l=t=>{a(t);const r={channel:d,paramIdx:s,value:t};f.sendParameterUpdate(r,null)};return u.useEffect(()=>{var i;const t=e==null?void 0:e.channels.find(g=>g.id===d);if(!t)return;const r=t==null?void 0:t.parameterIndex;s===void 0&&r!==void 0&&(console.log(`[Cabbage-React] Received parameterIndex for channelId "${t.id}"`,r),o(r));const c=e==null?void 0:e.value;c!=null&&(console.log(`[Cabbage-React] Received initial value for channelId "${d}"`,c),a(c));const m=(i=t.range)==null?void 0:i.defaultValue;n===void 0&&m!==void 0&&(console.log(`[Cabbage-React] Received default value for channelId "${t.id}"`,m),a(m))},[e]),u.useEffect(()=>{const t=r=>{var m;const{command:c}=r.data;if(c==="parameterChange"){const{value:i,paramIdx:g}=r.data.data;if(g!==s||i===null)return;console.log(`[Cabbage-React] Received parameterChange for parameterIndex ${g}`,i),a(i)}else if(c==="batchWidgetUpdate"){const i=r.data.widgets,g=i==null?void 0:i.find(w=>w.id===d);if(!g)return;const b=JSON.parse(g.widgetJson).channels.find(w=>w.id===d),p=(m=b==null?void 0:b.range)==null?void 0:m.value;console.log(`[Cabbage-React] Received batch widget update for channelId ${g.id}`,p),a(p)}};return window.addEventListener("message",t),()=>{window.removeEventListener("message",t)}},[s]),{value:n,setValue:l}};exports.Cabbage=f;exports.useCabbageProperties=M;exports.useCabbageState=I;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const u=require("react");console.log("Cabbage: loading cabbage.js");class f{static sendChannelUpdate(e,a=null,n=!1){if(n===!0||n===1)f.sendParameterUpdate(e,a);else{const s=e.value!==void 0?e.value:e.stringData||e.floatData;f.sendChannelData(e.channel,s,a)}}static sendParameterUpdate(e,a=null){if(e.paramIdx===void 0||e.paramIdx===null){console.error("Cabbage.sendParameterUpdate: message missing paramIdx!",e);return}if(e.paramIdx<0){console.warn("Cabbage.sendParameterUpdate: paramIdx is -1, skipping (non-automatable widget)",e);return}const n={command:"parameterChange",paramIdx:e.paramIdx,channel:e.channel,value:e.value,channelType:e.channelType||"number"};a!==null?a.postMessage(n):typeof window.sendMessageFromUI=="function"?window.sendMessageFromUI(n):console.error("Cabbage: window.sendMessageFromUI is not available. Message:",n)}static sendCustomCommand(e,a=null,n={}){const s={command:e,text:JSON.stringify(n)};if(a!==null)a.postMessage(s);else if(typeof window.sendMessageFromUI=="function"){console.log("Cabbage: Calling window.sendMessageFromUI with:",s);try{const o=window.sendMessageFromUI(s);console.log("Cabbage: sendMessageFromUI returned:",o)}catch(o){console.error("Cabbage: sendMessageFromUI threw error:",o),console.error("Cabbage: Error stack:",o.stack)}}else console.error("Cabbage: window.sendMessageFromUI is not available yet. Message:",s),console.error("Cabbage: typeof window.sendMessageFromUI:",typeof window.sendMessageFromUI),console.error("Cabbage: window.sendMessageFromUI value:",window.sendMessageFromUI)}static sendWidgetUpdate(e,a=null){const n={command:"widgetStateUpdate",obj:JSON.stringify(CabbageUtils.sanitizeForEditor(e))};a!==null?a.postMessage(n):typeof window.sendMessageFromUI=="function"?window.sendMessageFromUI(n):console.error("Cabbage: window.sendMessageFromUI is not available. Message:",n)}static sendMidiMessageFromUI(e,a,n,s=null){var o={statusByte:e,dataByte1:a,dataByte2:n};const l={command:"midiMessage",obj:JSON.stringify(o)};s!==null?s.postMessage(l):typeof window.sendMessageFromUI=="function"?window.sendMessageFromUI(l):console.error("Cabbage: window.sendMessageFromUI is not available. Message:",l)}static sendChannelData(e,a,n=null){var s={channel:e};if(typeof a=="string")s.stringData=a;else if(typeof a=="number")s.floatData=a;else{console.warn("Cabbage: sendChannelData received unsupported data type:",typeof a);return}const o={command:"channelData",obj:JSON.stringify(s)};console.log("Cabbage: sending channel data from UI",s),n!==null?n.postMessage(o):typeof window.sendMessageFromUI=="function"?window.sendMessageFromUI(o):console.error("Cabbage: window.sendMessageFromUI is not available. Message:",o)}static MidiMessageFromHost(e,a,n){console.log("Cabbage: Got MIDI Message"+e+":"+a+":"+n)}static triggerFileOpenDialog(e,a,n={}){var s={channel:a,directory:n.directory||"",filters:n.filters||"*",openAtLastKnownLocation:n.openAtLastKnownLocation!==void 0?n.openAtLastKnownLocation:!0};const o={command:"fileOpen",obj:JSON.stringify(s)};e!==null?e.postMessage(o):typeof window.sendMessageFromUI=="function"?window.sendMessageFromUI(o):console.error("Cabbage: window.sendMessageFromUI is not available. Message:",o)}static openUrl(e,a,n){var s={url:a,file:n};const o={command:"openUrl",obj:JSON.stringify(s)};e!==null?e.postMessage(o):typeof window.sendMessageFromUI=="function"?window.sendMessageFromUI(o):console.error("Cabbage: window.sendMessageFromUI is not available. Message:",o)}static requestResize(e,a,n=null){const s={command:"requestResize",width:e,height:a};if(n!==null){console.warn("Cabbage: requestResize is not supported in VS Code extension mode");return}else typeof window.sendMessageFromUI=="function"?window.sendMessageFromUI(s):console.error("Cabbage: window.sendMessageFromUI is not available. Message:",s)}}const I=i=>{const[e,a]=u.useState();return u.useEffect(()=>{const n=s=>{const{id:o,widgetJson:l,command:b}=s.data;if(o===i&&l&&b==="widgetUpdate"){const t=JSON.parse(l);console.log(`[Cabbage-React] Received properties for channelId ${o}`,t),a(t)}};return window.addEventListener("message",n),()=>{window.removeEventListener("message",n)}},[]),{properties:e}},C=(i,e)=>{const{properties:a}=I(i),[n,s]=u.useState(),[o,l]=u.useState(),b=t=>{if(o===void 0){console.warn(`[Cabbage-React] parameterIndex not ready for "${i}"`);return}s(t);const d={paramIdx:o,channel:i,value:t,channelType:"number"};f.sendParameterUpdate(d,null)};return u.useEffect(()=>{var r;const t=a==null?void 0:a.channels.find(g=>g.id===i);if(!t)return;const d=t==null?void 0:t.parameterIndex;o===void 0&&d!==void 0&&(console.log(`[Cabbage-React] Received parameterIndex for channelId "${t.id}"`,d),l(d));const c=a==null?void 0:a.value;c!=null&&(console.log(`[Cabbage-React] Received initial value for channelId "${i}"`,c),s(c));const m=(r=t.range)==null?void 0:r.defaultValue;n===void 0&&m!==void 0&&(console.log(`[Cabbage-React] Received default value for channelId "${t.id}"`,m),s(m))},[a]),u.useEffect(()=>{const t=d=>{var m;const{command:c}=d.data;if(c==="parameterChange"){if(e)return;const{value:r,paramIdx:g}=d.data.data;if(g!==o||r===null)return;console.log(`[Cabbage-React] Received parameterChange for parameterIndex ${g}`,r),s(r)}else if(c==="batchWidgetUpdate"){const r=d.data.widgets,g=r==null?void 0:r.find(p=>p.id===i);if(!g)return;const w=JSON.parse(g.widgetJson).channels.find(p=>p.id===i),M=(m=w==null?void 0:w.range)==null?void 0:m.value;console.log(`[Cabbage-React] Received batch widget update for channelId ${g.id}`,M),s(M)}};return window.addEventListener("message",t),()=>{window.removeEventListener("message",t)}},[o]),{value:n,setValue:b,parameterIndex:o}};exports.Cabbage=f;exports.useCabbageProperties=I;exports.useCabbageState=C;
package/dist/index.mjs CHANGED
@@ -1,28 +1,62 @@
1
- import { useState as b, useEffect as p } from "react";
1
+ import { useState as p, useEffect as M } from "react";
2
2
  console.log("Cabbage: loading cabbage.js");
3
3
  class f {
4
4
  /**
5
- * Main entry point for sending any data from UI widgets to the Cabbage backend.
5
+ * Main entry point for sending widget value changes to the Cabbage backend.
6
+ *
6
7
  * This function automatically routes messages to the appropriate backend function
7
8
  * based on the automatable flag:
8
9
  *
9
- * - automatable=true: Routes to sendParameterUpdate for real-time parameter control /
10
- * this also sends the value as channel data to Csound
10
+ * - `automatable=true`: Routes to `sendParameterUpdate()` for DAW-automatable parameters.
11
+ * The value is sent to the DAW for automation recording and also forwarded to Csound.
12
+ *
13
+ * - `automatable=false`: Routes to `sendChannelData()` for non-automatable data.
14
+ * The value is sent directly to Csound without DAW parameter involvement.
11
15
  *
12
- * - automatable=false: Routes to sendChannelData for string/numeric data transmission
16
+ * **When to use**: Call this from widget event handlers (e.g., pointer events, input changes)
17
+ * when the user interacts with a widget.
13
18
  *
14
- * All widget interactions should use this function instead of calling the lower-level
15
- * sendParameterUpdate or sendChannelData functions directly.
19
+ * **When NOT to use**: Do not call this when handling incoming `parameterChange` messages
20
+ * from the backend. Those messages are for display updates only.
21
+ *
22
+ * @param {Object} message - The message object containing widget data
23
+ * @param {string} message.channel - The channel name
24
+ * @param {number} message.paramIdx - Parameter index (required if automatable)
25
+ * @param {number|string} message.value - The value to send
26
+ * @param {Object|null} vscode - VS Code API object (null for plugin mode)
27
+ * @param {boolean} automatable - Whether this widget is DAW-automatable
16
28
  */
17
- static sendChannelUpdate(e, n = null, a = !1) {
18
- if (a === !0 || a === 1)
19
- f.sendParameterUpdate(e, n);
29
+ static sendChannelUpdate(e, a = null, n = !1) {
30
+ if (n === !0 || n === 1)
31
+ f.sendParameterUpdate(e, a);
20
32
  else {
21
33
  const s = e.value !== void 0 ? e.value : e.stringData || e.floatData;
22
- f.sendChannelData(e.channel, s, n);
34
+ f.sendChannelData(e.channel, s, a);
23
35
  }
24
36
  }
25
- static sendParameterUpdate(e, n = null) {
37
+ /**
38
+ * Internal: Send a parameter update to the DAW for automation recording.
39
+ * Use `sendChannelUpdate()` instead - it will route here automatically for automatable widgets.
40
+ *
41
+ * This function sends the parameter value to the DAW's automation system.
42
+ * The DAW will then send the value back via a `parameterChange` message,
43
+ * which updates both the UI and Csound.
44
+ *
45
+ * **Important**: This creates a round-trip through the DAW:
46
+ * 1. UI calls `sendParameterUpdate()` with value
47
+ * 2. DAW receives and records/processes the value
48
+ * 3. DAW sends `parameterChange` back to the plugin
49
+ * 4. Plugin updates Csound and sends `parameterChange` to UI
50
+ * 5. UI updates its display (but does NOT call sendParameterUpdate again!)
51
+ *
52
+ * @param {Object} message - The parameter message
53
+ * @param {number} message.paramIdx - The parameter index (must be >= 0)
54
+ * @param {string} message.channel - The channel name
55
+ * @param {number} message.value - The parameter value (full range, not normalized)
56
+ * @param {string} [message.channelType="number"] - The channel type
57
+ * @param {Object|null} vscode - VS Code API object (null for plugin mode)
58
+ */
59
+ static sendParameterUpdate(e, a = null) {
26
60
  if (e.paramIdx === void 0 || e.paramIdx === null) {
27
61
  console.error(
28
62
  "Cabbage.sendParameterUpdate: message missing paramIdx!",
@@ -37,25 +71,25 @@ class f {
37
71
  );
38
72
  return;
39
73
  }
40
- const a = {
74
+ const n = {
41
75
  command: "parameterChange",
42
76
  paramIdx: e.paramIdx,
43
77
  channel: e.channel,
44
78
  value: e.value,
45
79
  channelType: e.channelType || "number"
46
80
  };
47
- n !== null ? n.postMessage(a) : typeof window.sendMessageFromUI == "function" ? window.sendMessageFromUI(a) : console.error(
81
+ a !== null ? a.postMessage(n) : typeof window.sendMessageFromUI == "function" ? window.sendMessageFromUI(n) : console.error(
48
82
  "Cabbage: window.sendMessageFromUI is not available. Message:",
49
- a
83
+ n
50
84
  );
51
85
  }
52
- static sendCustomCommand(e, n = null, a = {}) {
86
+ static sendCustomCommand(e, a = null, n = {}) {
53
87
  const s = {
54
88
  command: e,
55
- text: JSON.stringify(a)
89
+ text: JSON.stringify(n)
56
90
  };
57
- if (n !== null)
58
- n.postMessage(s);
91
+ if (a !== null)
92
+ a.postMessage(s);
59
93
  else if (typeof window.sendMessageFromUI == "function") {
60
94
  console.log("Cabbage: Calling window.sendMessageFromUI with:", s);
61
95
  try {
@@ -76,21 +110,21 @@ class f {
76
110
  window.sendMessageFromUI
77
111
  );
78
112
  }
79
- static sendWidgetUpdate(e, n = null) {
80
- const a = {
113
+ static sendWidgetUpdate(e, a = null) {
114
+ const n = {
81
115
  command: "widgetStateUpdate",
82
116
  obj: JSON.stringify(CabbageUtils.sanitizeForEditor(e))
83
117
  };
84
- n !== null ? n.postMessage(a) : typeof window.sendMessageFromUI == "function" ? window.sendMessageFromUI(a) : console.error(
118
+ a !== null ? a.postMessage(n) : typeof window.sendMessageFromUI == "function" ? window.sendMessageFromUI(n) : console.error(
85
119
  "Cabbage: window.sendMessageFromUI is not available. Message:",
86
- a
120
+ n
87
121
  );
88
122
  }
89
- static sendMidiMessageFromUI(e, n, a, s = null) {
123
+ static sendMidiMessageFromUI(e, a, n, s = null) {
90
124
  var o = {
91
125
  statusByte: e,
92
- dataByte1: n,
93
- dataByte2: a
126
+ dataByte1: a,
127
+ dataByte2: n
94
128
  };
95
129
  const l = {
96
130
  command: "midiMessage",
@@ -101,18 +135,31 @@ class f {
101
135
  l
102
136
  );
103
137
  }
104
- static sendChannelData(e, n, a = null) {
138
+ /**
139
+ * @private
140
+ * Internal: Send channel data directly to Csound without DAW automation involvement.
141
+ * Use `sendChannelUpdate()` instead - it will route here automatically for non-automatable widgets.
142
+ *
143
+ * Used for non-automatable widgets like buttons, file selectors, or
144
+ * any widget that sends string data. The value is sent directly to Csound's
145
+ * channel system and is not recorded by DAW automation.
146
+ *
147
+ * @param {string} channel - The Csound channel name
148
+ * @param {number|string} data - The data to send (number or string)
149
+ * @param {Object|null} vscode - VS Code API object (null for plugin mode)
150
+ */
151
+ static sendChannelData(e, a, n = null) {
105
152
  var s = {
106
153
  channel: e
107
154
  };
108
- if (typeof n == "string")
109
- s.stringData = n;
110
- else if (typeof n == "number")
111
- s.floatData = n;
155
+ if (typeof a == "string")
156
+ s.stringData = a;
157
+ else if (typeof a == "number")
158
+ s.floatData = a;
112
159
  else {
113
160
  console.warn(
114
161
  "Cabbage: sendChannelData received unsupported data type:",
115
- typeof n
162
+ typeof a
116
163
  );
117
164
  return;
118
165
  }
@@ -120,22 +167,22 @@ class f {
120
167
  command: "channelData",
121
168
  obj: JSON.stringify(s)
122
169
  };
123
- console.log("Cabbage: sending channel data from UI", s), a !== null ? a.postMessage(o) : typeof window.sendMessageFromUI == "function" ? window.sendMessageFromUI(o) : console.error(
170
+ console.log("Cabbage: sending channel data from UI", s), n !== null ? n.postMessage(o) : typeof window.sendMessageFromUI == "function" ? window.sendMessageFromUI(o) : console.error(
124
171
  "Cabbage: window.sendMessageFromUI is not available. Message:",
125
172
  o
126
173
  );
127
174
  }
128
- static MidiMessageFromHost(e, n, a) {
175
+ static MidiMessageFromHost(e, a, n) {
129
176
  console.log(
130
- "Cabbage: Got MIDI Message" + e + ":" + n + ":" + a
177
+ "Cabbage: Got MIDI Message" + e + ":" + a + ":" + n
131
178
  );
132
179
  }
133
- static triggerFileOpenDialog(e, n, a = {}) {
180
+ static triggerFileOpenDialog(e, a, n = {}) {
134
181
  var s = {
135
- channel: n,
136
- directory: a.directory || "",
137
- filters: a.filters || "*",
138
- openAtLastKnownLocation: a.openAtLastKnownLocation !== void 0 ? a.openAtLastKnownLocation : !0
182
+ channel: a,
183
+ directory: n.directory || "",
184
+ filters: n.filters || "*",
185
+ openAtLastKnownLocation: n.openAtLastKnownLocation !== void 0 ? n.openAtLastKnownLocation : !0
139
186
  };
140
187
  const o = {
141
188
  command: "fileOpen",
@@ -146,10 +193,10 @@ class f {
146
193
  o
147
194
  );
148
195
  }
149
- static openUrl(e, n, a) {
196
+ static openUrl(e, a, n) {
150
197
  var s = {
151
- url: n,
152
- file: a
198
+ url: a,
199
+ file: n
153
200
  };
154
201
  const o = {
155
202
  command: "openUrl",
@@ -160,90 +207,128 @@ class f {
160
207
  o
161
208
  );
162
209
  }
210
+ /**
211
+ * Request a resize of the plugin GUI window.
212
+ * This is only supported in plugin mode (CLAP/VST3/AUv2).
213
+ * The host may accept or reject the resize request.
214
+ *
215
+ * @param {number} width - The requested width in pixels
216
+ * @param {number} height - The requested height in pixels
217
+ * @param {object} vscode - The vscode API object (null for plugin mode)
218
+ *
219
+ * The response will be sent via hostMessageCallback with:
220
+ * {command: "resizeResponse", accepted: boolean, width: number, height: number}
221
+ */
222
+ static requestResize(e, a, n = null) {
223
+ const s = {
224
+ command: "requestResize",
225
+ width: e,
226
+ height: a
227
+ };
228
+ if (n !== null) {
229
+ console.warn(
230
+ "Cabbage: requestResize is not supported in VS Code extension mode"
231
+ );
232
+ return;
233
+ } else
234
+ typeof window.sendMessageFromUI == "function" ? window.sendMessageFromUI(s) : console.error(
235
+ "Cabbage: window.sendMessageFromUI is not available. Message:",
236
+ s
237
+ );
238
+ }
163
239
  }
164
- const I = (d) => {
165
- const [e, n] = b();
166
- return p(() => {
167
- const a = (s) => {
168
- const { id: o, widgetJson: l, command: t } = s.data;
169
- if (o === d && l && t === "widgetUpdate") {
170
- const r = JSON.parse(l);
240
+ const U = (i) => {
241
+ const [e, a] = p();
242
+ return M(() => {
243
+ const n = (s) => {
244
+ const { id: o, widgetJson: l, command: u } = s.data;
245
+ if (o === i && l && u === "widgetUpdate") {
246
+ const t = JSON.parse(l);
171
247
  console.log(
172
248
  `[Cabbage-React] Received properties for channelId ${o}`,
173
- r
174
- ), n(r);
249
+ t
250
+ ), a(t);
175
251
  }
176
252
  };
177
- return window.addEventListener("message", a), () => {
178
- window.removeEventListener("message", a);
253
+ return window.addEventListener("message", n), () => {
254
+ window.removeEventListener("message", n);
179
255
  };
180
256
  }, []), {
181
257
  properties: e
182
258
  };
183
- }, F = (d) => {
184
- const { properties: e } = I(d), [n, a] = b(), [s, o] = b(), l = (t) => {
185
- a(t);
186
- const r = {
187
- channel: d,
188
- paramIdx: s,
189
- value: t
190
- };
191
- f.sendParameterUpdate(r, null);
259
+ }, h = (i, e) => {
260
+ const { properties: a } = U(i), [n, s] = p(), [o, l] = p(), u = (t) => {
261
+ if (o === void 0) {
262
+ console.warn(
263
+ `[Cabbage-React] parameterIndex not ready for "${i}"`
264
+ );
265
+ return;
266
+ }
267
+ s(t);
268
+ const d = {
269
+ paramIdx: o,
270
+ channel: i,
271
+ value: t,
272
+ channelType: "number"
273
+ };
274
+ f.sendParameterUpdate(d, null);
192
275
  };
193
- return p(() => {
194
- var i;
195
- const t = e == null ? void 0 : e.channels.find(
196
- (g) => g.id === d
276
+ return M(() => {
277
+ var r;
278
+ const t = a == null ? void 0 : a.channels.find(
279
+ (g) => g.id === i
197
280
  );
198
281
  if (!t) return;
199
- const r = t == null ? void 0 : t.parameterIndex;
200
- s === void 0 && r !== void 0 && (console.log(
282
+ const d = t == null ? void 0 : t.parameterIndex;
283
+ o === void 0 && d !== void 0 && (console.log(
201
284
  `[Cabbage-React] Received parameterIndex for channelId "${t.id}"`,
202
- r
203
- ), o(r));
204
- const c = e == null ? void 0 : e.value;
285
+ d
286
+ ), l(d));
287
+ const c = a == null ? void 0 : a.value;
205
288
  c != null && (console.log(
206
- `[Cabbage-React] Received initial value for channelId "${d}"`,
289
+ `[Cabbage-React] Received initial value for channelId "${i}"`,
207
290
  c
208
- ), a(c));
209
- const m = (i = t.range) == null ? void 0 : i.defaultValue;
291
+ ), s(c));
292
+ const m = (r = t.range) == null ? void 0 : r.defaultValue;
210
293
  n === void 0 && m !== void 0 && (console.log(
211
294
  `[Cabbage-React] Received default value for channelId "${t.id}"`,
212
295
  m
213
- ), a(m));
214
- }, [e]), p(() => {
215
- const t = (r) => {
296
+ ), s(m));
297
+ }, [a]), M(() => {
298
+ const t = (d) => {
216
299
  var m;
217
- const { command: c } = r.data;
300
+ const { command: c } = d.data;
218
301
  if (c === "parameterChange") {
219
- const { value: i, paramIdx: g } = r.data.data;
220
- if (g !== s || i === null) return;
302
+ if (e) return;
303
+ const { value: r, paramIdx: g } = d.data.data;
304
+ if (g !== o || r === null) return;
221
305
  console.log(
222
306
  `[Cabbage-React] Received parameterChange for parameterIndex ${g}`,
223
- i
224
- ), a(i);
307
+ r
308
+ ), s(r);
225
309
  } else if (c === "batchWidgetUpdate") {
226
- const i = r.data.widgets, g = i == null ? void 0 : i.find((w) => w.id === d);
310
+ const r = d.data.widgets, g = r == null ? void 0 : r.find((b) => b.id === i);
227
311
  if (!g) return;
228
- const u = JSON.parse(g.widgetJson).channels.find(
229
- (w) => w.id === d
230
- ), M = (m = u == null ? void 0 : u.range) == null ? void 0 : m.value;
312
+ const w = JSON.parse(g.widgetJson).channels.find(
313
+ (b) => b.id === i
314
+ ), I = (m = w == null ? void 0 : w.range) == null ? void 0 : m.value;
231
315
  console.log(
232
316
  `[Cabbage-React] Received batch widget update for channelId ${g.id}`,
233
- M
234
- ), a(M);
317
+ I
318
+ ), s(I);
235
319
  }
236
320
  };
237
321
  return window.addEventListener("message", t), () => {
238
322
  window.removeEventListener("message", t);
239
323
  };
240
- }, [s]), {
324
+ }, [o]), {
241
325
  value: n,
242
- setValue: l
326
+ setValue: u,
327
+ parameterIndex: o
243
328
  };
244
329
  };
245
330
  export {
246
331
  f as Cabbage,
247
- I as useCabbageProperties,
248
- F as useCabbageState
332
+ U as useCabbageProperties,
333
+ h as useCabbageState
249
334
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cabbage-react",
3
- "version": "1.0.14",
3
+ "version": "1.0.16",
4
4
  "type": "module",
5
5
  "keywords": [
6
6
  "cabbage",