cabbage-react 1.0.44 → 1.0.45

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 CHANGED
@@ -34,14 +34,14 @@ Get properties for a widget from the backend. This hook:
34
34
  Get messages from the backend. This hook:
35
35
 
36
36
  - Automatically updates local state when a message is received
37
- - Expects a serialized JSON object (sent as a string) with an `id` property
37
+ - Expects a serialized JSON object (sent as a string) with a `messageType` property
38
38
 
39
39
  #### Example: sending a message from Csound
40
40
 
41
41
  ```csd
42
42
  jsonData:S = sprintf({{
43
43
  {
44
- "id":"NoteData",
44
+ "messageType":"NoteData",
45
45
  "noteCount":%f,
46
46
  "note":%f,
47
47
  "noteLength":%f,
@@ -1,3 +1,88 @@
1
+ /**
2
+ * @fileoverview Cabbage API - Communication layer between the webview UI and the Cabbage backend.
3
+ *
4
+ * ## Architecture Overview
5
+ *
6
+ * This module provides the API for widget-to-backend communication in Cabbage plugins.
7
+ * There are two communication directions:
8
+ *
9
+ * ### UI -> Backend (Outgoing)
10
+ * Use `sendControlData({ channel, value, gesture })` to send widget value changes to the backend.
11
+ * Values should be sent in their full range (e.g., 20-20000 Hz for a filter
12
+ * frequency slider). The backend handles all value normalization needed by the host DAW.
13
+ * The backend automatically determines whether the channel is automatable and routes
14
+ * accordingly:
15
+ * - Automatable channels -> DAW parameter system -> Csound
16
+ * - Non-automatable channels -> Csound directly
17
+ * **Thread Safety**: This function is asynchronous and does NOT block the audio thread.
18
+ * Parameter updates are queued and processed safely without interrupting real-time audio processing.
19
+ *
20
+ * ### Backend -> UI (Incoming)
21
+ * The backend sends messages via `hostMessageCallback()`. Values received from the host
22
+ * are in their full range (e.g., 20-20000 Hz for a filter frequency slider).
23
+ * The backend handles all value normalization needed by the host DAW.
24
+ * To intercept these messages, define a global function in your UI code:
25
+ *
26
+ * ```javascript
27
+ * window.hostMessageCallback = function(data) {
28
+ * if (data.command === "parameterChange") {
29
+ * // Handle parameter update from DAW - update display only, don't send back!
30
+ * } else if (data.command === "updateWidget") {
31
+ * // Handle widget update from plugin, or Csound opcodes (cabbageSetValue, cabbageSet)
32
+ * } else if (data.command === "channelDataUpdate") {
33
+ * // Handle channel data from Csound
34
+ * } else if (data.command === "resizeResponse") {
35
+ * // Handle resize response
36
+ * }
37
+ * };
38
+ * ```
39
+ *
40
+ * Common incoming commands:
41
+ * - `parameterChange` - Parameter value updated (from DAW automation or backend)
42
+ * - `updateWidget` - Widget value/property update from plugin, or Csound opcodes (cabbageSetValue, cabbageSet)
43
+ * - `channelDataUpdate` - Channel data from Csound
44
+ * - `resizeResponse` - Response to resize request
45
+ *
46
+ * ## Important: Avoiding Feedback Loops
47
+ *
48
+ * When the UI receives a `parameterChange` message from the backend, it should ONLY
49
+ * update its visual display. It must NEVER send a parameter update back to the backend.
50
+ *
51
+ * The reason: `parameterChange` messages represent the current value from the
52
+ * DAW. Sending updates back would create feedback loops and could interfere
53
+ * with DAW automation playback.
54
+ *
55
+ * Correct pattern:
56
+ * - User drags slider -> UI sends `sendControlData()` -> DAW records automation
57
+ * - DAW plays automation -> Backend sends `parameterChange` -> UI updates display only
58
+ *
59
+ * ## Handling User Interaction (isDragging pattern)
60
+ *
61
+ * When the user is actively dragging a slider, you typically want to ignore incoming
62
+ * `parameterChange` messages to prevent the slider from "fighting" with the user's input.
63
+ * Implement this by tracking an `isDragging` state:
64
+ *
65
+ * ```javascript
66
+ * let isDragging = false;
67
+ *
68
+ * // In your slider's event handlers:
69
+ * slider.onpointerdown = () => { isDragging = true; };
70
+ * slider.onpointerup = () => { isDragging = false; };
71
+ *
72
+ * // In hostMessageCallback:
73
+ * window.hostMessageCallback = function(data) {
74
+ * if (data.command === "parameterChange") {
75
+ * if (!isDragging) {
76
+ * // Safe to update display - user isn't interacting
77
+ * updateSliderDisplay(data.value);
78
+ * }
79
+ * // If isDragging, ignore the update - user's input takes priority
80
+ * }
81
+ * };
82
+ * ```
83
+ *
84
+ * @module Cabbage
85
+ */
1
86
  export class Cabbage {
2
87
  /**
3
88
  * Send a widget value change to the Cabbage backend.
@@ -84,6 +169,26 @@ export class Cabbage {
84
169
  * {command: "resizeResponse", accepted: boolean, width: number, height: number}
85
170
  */
86
171
  static requestResize(width: number, height: number, vscode?: object): void;
172
+ /**
173
+ * Control whether keyboard events are captured by the webview or forwarded
174
+ * to the host DAW. Only relevant on Windows in plugin mode.
175
+ *
176
+ * By default (false), all key events are forwarded to the DAW so that
177
+ * keyboard shortcuts continue to work while the plugin UI has focus.
178
+ * Set to true when a custom text-entry widget (that is not a native
179
+ * <input> or <textarea>) needs keyboard input, then restore to false
180
+ * when the widget loses focus.
181
+ *
182
+ * Note: Native <input> and <textarea> elements are handled automatically
183
+ * and do not require calling this function.
184
+ *
185
+ * @param {boolean} consume - true to capture keys in webview, false to pass through to DAW
186
+ *
187
+ * @example
188
+ * myCustomEditor.addEventListener('focus', () => Cabbage.consumeKeypresses(true));
189
+ * myCustomEditor.addEventListener('blur', () => Cabbage.consumeKeypresses(false));
190
+ */
191
+ static consumeKeypresses(consume: boolean): void;
87
192
  /**
88
193
  * Send channel data directly to Csound without DAW automation involvement.
89
194
  *
@@ -1 +1 @@
1
- {"version":3,"file":"cabbage.d.ts","sourceRoot":"","sources":["../../src/cabbage/cabbage.js"],"names":[],"mappings":"AA4FA;IACC;;;;;;;;;;;;;;;;;;;OAmBG;IACH,oDALG;QAAqB,OAAO,EAApB,MAAM;QACc,KAAK,EAAzB,MAAM,GAAC,MAAM;QACC,OAAO;KAC7B,WAAQ,MAAM,GAAC,IAAI,QAyBrB;IAED;;;;;OAKG;IACH,8BAHW,MAAM,GAAC,IAAI,mBACX,MAAM,QAIhB;IACD;;;;;;;OAOG;IACH,yCALW,MAAM,aACN,MAAM,aACN,MAAM,WACN,MAAM,GAAC,IAAI,QA+BrB;IAED;;;;;;OAMG;IACH,uCAJW,MAAM,aACN,MAAM,aACN,MAAM,QAWhB;IAED;;;;;;;;;OASG;IACH,qCAPW,MAAM,GAAC,IAAI,WACX,MAAM,YAEd;QAAyB,SAAS;QACT,OAAO;QACN,uBAAuB;KACnD,QA4BA;IAED;;;;;;OAMG;IACH,uBAJW,MAAM,GAAC,IAAI,OACX,MAAM,QACN,MAAM,QAwBhB;IAED;;;;;;;;;;;OAWG;IACH,4BAPW,MAAM,UACN,MAAM,WACN,MAAM,QA4BhB;IAED;;;;;;OAMG;IACH,gCAJW,MAAM,QACN,MAAM,GAAC,MAAM,WACb,MAAM,GAAC,IAAI,QAsCrB;IAED;;;;;;OAMG;IACH,gCAiBC;IAED;;;;;;OAMG;IACH,kCAJW,MAAM,WACN,MAAM,GAAC,IAAI,mBACX,MAAM,QAmChB;CACD"}
1
+ {"version":3,"file":"cabbage.d.ts","sourceRoot":"","sources":["../../src/cabbage/cabbage.js"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoFG;AAEH;IACC;;;;;;;;;;;;;;;;;;;OAmBG;IACH,oDALG;QAAqB,OAAO,EAApB,MAAM;QACc,KAAK,EAAzB,MAAM,GAAC,MAAM;QACC,OAAO;KAC7B,WAAQ,MAAM,GAAC,IAAI,QAyBrB;IAED;;;;;OAKG;IACH,8BAHW,MAAM,GAAC,IAAI,mBACX,MAAM,QAIhB;IACD;;;;;;;OAOG;IACH,yCALW,MAAM,aACN,MAAM,aACN,MAAM,WACN,MAAM,GAAC,IAAI,QA+BrB;IAED;;;;;;OAMG;IACH,uCAJW,MAAM,aACN,MAAM,aACN,MAAM,QAE8C;IAE/D;;;;;;;;;OASG;IACH,qCAPW,MAAM,GAAC,IAAI,WACX,MAAM,YAEd;QAAyB,SAAS;QACT,OAAO;QACN,uBAAuB;KACnD,QA4BA;IAED;;;;;;OAMG;IACH,uBAJW,MAAM,GAAC,IAAI,OACX,MAAM,QACN,MAAM,QAwBhB;IAED;;;;;;;;;;;OAWG;IACH,4BAPW,MAAM,UACN,MAAM,WACN,MAAM,QA4BhB;IAED;;;;;;;;;;;;;;;;;;OAkBG;IACH,kCANW,OAAO,QAUjB;IAED;;;;;;OAMG;IACH,gCAJW,MAAM,QACN,MAAM,GAAC,MAAM,WACb,MAAM,GAAC,IAAI,QAqCrB;IAED;;;;;;OAMG;IACH,gCAiBC;IAED;;;;;;OAMG;IACH,kCAJW,MAAM,WACN,MAAM,GAAC,IAAI,mBACX,MAAM,QAiChB;CACD"}
@@ -4,7 +4,7 @@
4
4
  * whenever new data is received.
5
5
  * @param messageId
6
6
  */
7
- export declare const useCabbageMessage: <T>(messageId: string) => {
7
+ export declare const useCabbageMessage: <T>(messageType: string) => {
8
8
  data: T | undefined;
9
9
  };
10
10
  //# sourceMappingURL=useCabbageMessage.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useCabbageMessage.d.ts","sourceRoot":"","sources":["../../src/hooks/useCabbageMessage.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,GAAI,CAAC,aAAa,MAAM;;CA8BrD,CAAC"}
1
+ {"version":3,"file":"useCabbageMessage.d.ts","sourceRoot":"","sources":["../../src/hooks/useCabbageMessage.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,GAAI,CAAC,eAAe,MAAM;;CA8BvD,CAAC"}
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const l=require("react");console.log("Cabbage: loading cabbage.js");class p{static sendControlData({channel:o,value:s,gesture:a="complete"},n=null){const e={command:"controlData",channel:o,value:s,gesture:a};n!==null?n.postMessage(e):typeof window.sendMessageFromUI=="function"?window.sendMessageFromUI(e):console.error("Cabbage: window.sendMessageFromUI is not available. Message:",e)}static isReadyToLoad(o=null,s={}){this.sendCustomCommand("isReadyToLoad",o)}static sendMidiMessageFromUI(o,s,a,n=null){var e={statusByte:o,dataByte1:s,dataByte2:a};const r={command:"midiMessage",obj:JSON.stringify(e)};n!==null?n.postMessage(r):typeof window.sendMessageFromUI=="function"?window.sendMessageFromUI(r):console.error("Cabbage: window.sendMessageFromUI is not available. Message:",r)}static MidiMessageFromHost(o,s,a){console.log("Cabbage: Got MIDI Message"+o+":"+s+":"+a)}static triggerFileOpenDialog(o,s,a={}){var n={channel:s,directory:a.directory||"",filters:a.filters||"*",openAtLastKnownLocation:a.openAtLastKnownLocation!==void 0?a.openAtLastKnownLocation:!0};const e={command:"fileOpen",obj:JSON.stringify(n)};o!==null?o.postMessage(e):typeof window.sendMessageFromUI=="function"?window.sendMessageFromUI(e):console.error("Cabbage: window.sendMessageFromUI is not available. Message:",e)}static openUrl(o,s,a){var n={url:s,file:a};const e={command:"openUrl",obj:JSON.stringify(n)};o!==null?o.postMessage(e):typeof window.sendMessageFromUI=="function"?window.sendMessageFromUI(e):console.error("Cabbage: window.sendMessageFromUI is not available. Message:",e)}static requestResize(o,s,a=null){const n={command:"requestResize",width:o,height:s};if(a!==null){console.warn("Cabbage: requestResize is not supported in VS Code extension mode");return}else typeof window.sendMessageFromUI=="function"?window.sendMessageFromUI(n):console.error("Cabbage: window.sendMessageFromUI is not available. Message:",n)}static sendChannelData(o,s,a=null){var n={channel:o};if(typeof s=="string")n.stringData=s;else if(typeof s=="number")n.floatData=s;else{console.warn("Cabbage: sendChannelData received unsupported data type:",typeof s);return}const e={command:"channelData",obj:JSON.stringify(n)};console.log("Cabbage: sending channel data from UI",n),a!==null?a.postMessage(e):typeof window.sendMessageFromUI=="function"?window.sendMessageFromUI(e):console.error("Cabbage: window.sendMessageFromUI is not available. Message:",e)}static sendWidgetUpdate(o,s=null){const a={command:"widgetStateUpdate",obj:JSON.stringify(CabbageUtils.sanitizeForEditor(o))};s!==null?s.postMessage(a):typeof window.sendMessageFromUI=="function"?window.sendMessageFromUI(a):console.error("Cabbage: window.sendMessageFromUI is not available. Message:",a)}static sendCustomCommand(o,s=null,a={}){const n={command:o,text:JSON.stringify(a)};if(s!==null)s.postMessage(n);else if(typeof window.sendMessageFromUI=="function"){console.log("Cabbage: Calling window.sendMessageFromUI with:",n);try{const e=window.sendMessageFromUI(n);console.log("Cabbage: sendMessageFromUI returned:",e)}catch(e){console.error("Cabbage: sendMessageFromUI threw error:",e),console.error("Cabbage: Error stack:",e.stack)}}else console.error("Cabbage: window.sendMessageFromUI is not available yet. Message:",n),console.error("Cabbage: typeof window.sendMessageFromUI:",typeof window.sendMessageFromUI),console.error("Cabbage: window.sendMessageFromUI value:",window.sendMessageFromUI)}}const C=i=>{const[o,s]=l.useState();return l.useEffect(()=>{const a=n=>{const{id:e,widgetJson:r,command:u}=n.data;if(e===i&&r&&u==="widgetUpdate"){const t=JSON.parse(r);console.log(`[Cabbage-React] Received properties for channelId: ${e}`,t),s(t)}};return window.addEventListener("message",a),()=>{window.removeEventListener("message",a)}},[]),{properties:o}},I=(i,o="complete")=>{const{properties:s}=C(i),[a,n]=l.useState(),[e,r]=l.useState(),u=t=>{n(t),p.sendControlData({channel:i,value:t,gesture:o})};return l.useEffect(()=>{var b;const t=s==null?void 0:s.channels.find(d=>d.id===i);if(!t)return;const g=t.parameterIndex;if(e===void 0&&g!==void 0&&(console.log(`[Cabbage-React] Received parameterIndex for channelId: ${t.id}`,g),r(g)),a!==void 0)return;const c=(b=t.range)==null?void 0:b.value;c!=null&&(console.log(`[Cabbage-React] Received initial value for channelId: ${t.id}`,c),n(c))},[s]),l.useEffect(()=>{const t=g=>{var b;const{command:c}=g.data;if(c==="parameterChange"){const{value:d,paramIdx:m}=g.data.data;if(m!==e||d===null)return;console.log(`[Cabbage-React] Received parameterChange for parameterIndex: ${m}`,d),n(d)}else if(c==="batchWidgetUpdate"){const d=g.data.widgets,m=d==null?void 0:d.find(f=>f.id===i);if(!m)return;const w=JSON.parse(m.widgetJson).channels.find(f=>f.id===i),M=(b=w==null?void 0:w.range)==null?void 0:b.value;console.log(`[Cabbage-React] Received batch widget update for channelId: ${m.id}`,M),n(M)}};return window.addEventListener("message",t),()=>{window.removeEventListener("message",t)}},[e]),{value:a,setValue:u}},U=i=>{const[o,s]=l.useState();return l.useEffect(()=>{const a=n=>{const{data:e,type:r}=n;if(e&&r==="message"){if(e.id!==i)return;console.log(`[Cabbage-React] Received data for messageId: ${e.id}`,e),s(e)}};return window.addEventListener("message",a),()=>{window.removeEventListener("message",a)}},[]),{data:o}};exports.Cabbage=p;exports.useCabbageMessage=U;exports.useCabbageProperties=C;exports.useCabbageState=I;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const l=require("react");class M{static sendControlData({channel:o,value:s,gesture:a="complete"},n=null){const e={command:"controlData",channel:o,value:s,gesture:a};n!==null?n.postMessage(e):typeof window.sendMessageFromUI=="function"?window.sendMessageFromUI(e):console.error("Cabbage: window.sendMessageFromUI is not available. Message:",e)}static isReadyToLoad(o=null,s={}){this.sendCustomCommand("isReadyToLoad",o)}static sendMidiMessageFromUI(o,s,a,n=null){var e={statusByte:o,dataByte1:s,dataByte2:a};const r={command:"midiMessage",obj:JSON.stringify(e)};n!==null?n.postMessage(r):typeof window.sendMessageFromUI=="function"?window.sendMessageFromUI(r):console.error("Cabbage: window.sendMessageFromUI is not available. Message:",r)}static MidiMessageFromHost(o,s,a){}static triggerFileOpenDialog(o,s,a={}){var n={channel:s,directory:a.directory||"",filters:a.filters||"*",openAtLastKnownLocation:a.openAtLastKnownLocation!==void 0?a.openAtLastKnownLocation:!0};const e={command:"fileOpen",obj:JSON.stringify(n)};o!==null?o.postMessage(e):typeof window.sendMessageFromUI=="function"?window.sendMessageFromUI(e):console.error("Cabbage: window.sendMessageFromUI is not available. Message:",e)}static openUrl(o,s,a){var n={url:s,file:a};const e={command:"openUrl",obj:JSON.stringify(n)};o!==null?o.postMessage(e):typeof window.sendMessageFromUI=="function"?window.sendMessageFromUI(e):console.error("Cabbage: window.sendMessageFromUI is not available. Message:",e)}static requestResize(o,s,a=null){const n={command:"requestResize",width:o,height:s};if(a!==null){console.warn("Cabbage: requestResize is not supported in VS Code extension mode");return}else typeof window.sendMessageFromUI=="function"?window.sendMessageFromUI(n):console.error("Cabbage: window.sendMessageFromUI is not available. Message:",n)}static consumeKeypresses(o){typeof window.consumeKeypresses=="function"&&window.consumeKeypresses(o)}static sendChannelData(o,s,a=null){var n={channel:o};if(typeof s=="string")n.stringData=s;else if(typeof s=="number")n.floatData=s;else{console.warn("Cabbage: sendChannelData received unsupported data type:",typeof s);return}const e={command:"channelData",obj:JSON.stringify(n)};a!==null?a.postMessage(e):typeof window.sendMessageFromUI=="function"?window.sendMessageFromUI(e):console.error("Cabbage: window.sendMessageFromUI is not available. Message:",e)}static sendWidgetUpdate(o,s=null){const a={command:"widgetStateUpdate",obj:JSON.stringify(CabbageUtils.sanitizeForEditor(o))};s!==null?s.postMessage(a):typeof window.sendMessageFromUI=="function"?window.sendMessageFromUI(a):console.error("Cabbage: window.sendMessageFromUI is not available. Message:",a)}static sendCustomCommand(o,s=null,a={}){const n={command:o,text:JSON.stringify(a)};if(s!==null)s.postMessage(n);else if(typeof window.sendMessageFromUI=="function")try{const e=window.sendMessageFromUI(n)}catch(e){console.error("Cabbage: sendMessageFromUI threw error:",e),console.error("Cabbage: Error stack:",e.stack)}else console.error("Cabbage: window.sendMessageFromUI is not available yet. Message:",n),console.error("Cabbage: typeof window.sendMessageFromUI:",typeof window.sendMessageFromUI),console.error("Cabbage: window.sendMessageFromUI value:",window.sendMessageFromUI)}}const C=i=>{const[o,s]=l.useState();return l.useEffect(()=>{const a=n=>{const{id:e,widgetJson:r,command:w}=n.data;if(e===i&&r&&w==="widgetUpdate"){const t=JSON.parse(r);console.log(`[Cabbage-React] Received properties for channelId: ${e}`,t),s(t)}};return window.addEventListener("message",a),()=>{window.removeEventListener("message",a)}},[]),{properties:o}},y=(i,o="complete")=>{const{properties:s}=C(i),[a,n]=l.useState(),[e,r]=l.useState(),w=t=>{n(t),M.sendControlData({channel:i,value:t,gesture:o})};return l.useEffect(()=>{var u;const t=s==null?void 0:s.channels.find(d=>d.id===i);if(!t)return;const g=t.parameterIndex;if(e===void 0&&g!==void 0&&(console.log(`[Cabbage-React] Received parameterIndex for channelId: ${t.id}`,g),r(g)),a!==void 0)return;const c=(u=t.range)==null?void 0:u.value;c!=null&&(console.log(`[Cabbage-React] Received initial value for channelId: ${t.id}`,c),n(c))},[s]),l.useEffect(()=>{const t=g=>{var u;const{command:c}=g.data;if(c==="parameterChange"){const{value:d,paramIdx:m}=g.data.data;if(m!==e||d===null)return;console.log(`[Cabbage-React] Received parameterChange for parameterIndex: ${m}`,d),n(d)}else if(c==="batchWidgetUpdate"){const d=g.data.widgets,m=d==null?void 0:d.find(b=>b.id===i);if(!m)return;const f=JSON.parse(m.widgetJson).channels.find(b=>b.id===i),p=(u=f==null?void 0:f.range)==null?void 0:u.value;console.log(`[Cabbage-React] Received batch widget update for channelId: ${m.id}`,p),n(p)}};return window.addEventListener("message",t),()=>{window.removeEventListener("message",t)}},[e]),{value:a,setValue:w}},I=i=>{const[o,s]=l.useState();return l.useEffect(()=>{const a=n=>{const{data:e,type:r}=n;if(e&&r==="message"){if(e.messageType!==i)return;console.log(`[Cabbage-React] Received data for messageType: ${e.messageType}`,e),s(e)}};return window.addEventListener("message",a),()=>{window.removeEventListener("message",a)}},[]),{data:o}};exports.Cabbage=M;exports.useCabbageMessage=I;exports.useCabbageProperties=C;exports.useCabbageState=y;
package/dist/index.mjs CHANGED
@@ -1,6 +1,5 @@
1
- import { useState as w, useEffect as b } from "react";
2
- console.log("Cabbage: loading cabbage.js");
3
- class C {
1
+ import { useState as w, useEffect as f } from "react";
2
+ class y {
4
3
  /**
5
4
  * Send a widget value change to the Cabbage backend.
6
5
  *
@@ -73,9 +72,6 @@ class C {
73
72
  * @param {number} dataByte2 - Second MIDI data byte
74
73
  */
75
74
  static MidiMessageFromHost(o, s, a) {
76
- console.log(
77
- "Cabbage: Got MIDI Message" + o + ":" + s + ":" + a
78
- );
79
75
  }
80
76
  /**
81
77
  * Trigger a native file open dialog for file selection widgets.
@@ -153,6 +149,28 @@ class C {
153
149
  n
154
150
  );
155
151
  }
152
+ /**
153
+ * Control whether keyboard events are captured by the webview or forwarded
154
+ * to the host DAW. Only relevant on Windows in plugin mode.
155
+ *
156
+ * By default (false), all key events are forwarded to the DAW so that
157
+ * keyboard shortcuts continue to work while the plugin UI has focus.
158
+ * Set to true when a custom text-entry widget (that is not a native
159
+ * <input> or <textarea>) needs keyboard input, then restore to false
160
+ * when the widget loses focus.
161
+ *
162
+ * Note: Native <input> and <textarea> elements are handled automatically
163
+ * and do not require calling this function.
164
+ *
165
+ * @param {boolean} consume - true to capture keys in webview, false to pass through to DAW
166
+ *
167
+ * @example
168
+ * myCustomEditor.addEventListener('focus', () => Cabbage.consumeKeypresses(true));
169
+ * myCustomEditor.addEventListener('blur', () => Cabbage.consumeKeypresses(false));
170
+ */
171
+ static consumeKeypresses(o) {
172
+ typeof window.consumeKeypresses == "function" && window.consumeKeypresses(o);
173
+ }
156
174
  /**
157
175
  * Send channel data directly to Csound without DAW automation involvement.
158
176
  *
@@ -179,7 +197,7 @@ class C {
179
197
  command: "channelData",
180
198
  obj: JSON.stringify(n)
181
199
  };
182
- console.log("Cabbage: sending channel data from UI", n), a !== null ? a.postMessage(e) : typeof window.sendMessageFromUI == "function" ? window.sendMessageFromUI(e) : console.error(
200
+ a !== null ? a.postMessage(e) : typeof window.sendMessageFromUI == "function" ? window.sendMessageFromUI(e) : console.error(
183
201
  "Cabbage: window.sendMessageFromUI is not available. Message:",
184
202
  e
185
203
  );
@@ -215,15 +233,13 @@ class C {
215
233
  };
216
234
  if (s !== null)
217
235
  s.postMessage(n);
218
- else if (typeof window.sendMessageFromUI == "function") {
219
- console.log("Cabbage: Calling window.sendMessageFromUI with:", n);
236
+ else if (typeof window.sendMessageFromUI == "function")
220
237
  try {
221
238
  const e = window.sendMessageFromUI(n);
222
- console.log("Cabbage: sendMessageFromUI returned:", e);
223
239
  } catch (e) {
224
240
  console.error("Cabbage: sendMessageFromUI threw error:", e), console.error("Cabbage: Error stack:", e.stack);
225
241
  }
226
- } else
242
+ else
227
243
  console.error(
228
244
  "Cabbage: window.sendMessageFromUI is not available yet. Message:",
229
245
  n
@@ -236,12 +252,12 @@ class C {
236
252
  );
237
253
  }
238
254
  }
239
- const I = (i) => {
255
+ const C = (i) => {
240
256
  const [o, s] = w();
241
- return b(() => {
257
+ return f(() => {
242
258
  const a = (n) => {
243
- const { id: e, widgetJson: r, command: f } = n.data;
244
- if (e === i && r && f === "widgetUpdate") {
259
+ const { id: e, widgetJson: r, command: u } = n.data;
260
+ if (e === i && r && u === "widgetUpdate") {
245
261
  const t = JSON.parse(r);
246
262
  console.log(
247
263
  `[Cabbage-React] Received properties for channelId: ${e}`,
@@ -255,15 +271,15 @@ const I = (i) => {
255
271
  }, []), {
256
272
  properties: o
257
273
  };
258
- }, v = (i, o = "complete") => {
259
- const { properties: s } = I(i), [a, n] = w(), [e, r] = w(), f = (t) => {
260
- n(t), C.sendControlData({
274
+ }, F = (i, o = "complete") => {
275
+ const { properties: s } = C(i), [a, n] = w(), [e, r] = w(), u = (t) => {
276
+ n(t), y.sendControlData({
261
277
  channel: i,
262
278
  value: t,
263
279
  gesture: o
264
280
  });
265
281
  };
266
- return b(() => {
282
+ return f(() => {
267
283
  var m;
268
284
  const t = s == null ? void 0 : s.channels.find(
269
285
  (d) => d.id === i
@@ -279,7 +295,7 @@ const I = (i) => {
279
295
  `[Cabbage-React] Received initial value for channelId: ${t.id}`,
280
296
  g
281
297
  ), n(g));
282
- }, [s]), b(() => {
298
+ }, [s]), f(() => {
283
299
  const t = (l) => {
284
300
  var m;
285
301
  const { command: g } = l.data;
@@ -291,15 +307,15 @@ const I = (i) => {
291
307
  d
292
308
  ), n(d);
293
309
  } else if (g === "batchWidgetUpdate") {
294
- const d = l.data.widgets, c = d == null ? void 0 : d.find((M) => M.id === i);
310
+ const d = l.data.widgets, c = d == null ? void 0 : d.find((p) => p.id === i);
295
311
  if (!c) return;
296
- const u = JSON.parse(c.widgetJson).channels.find(
297
- (M) => M.id === i
298
- ), p = (m = u == null ? void 0 : u.range) == null ? void 0 : m.value;
312
+ const b = JSON.parse(c.widgetJson).channels.find(
313
+ (p) => p.id === i
314
+ ), M = (m = b == null ? void 0 : b.range) == null ? void 0 : m.value;
299
315
  console.log(
300
316
  `[Cabbage-React] Received batch widget update for channelId: ${c.id}`,
301
- p
302
- ), n(p);
317
+ M
318
+ ), n(M);
303
319
  }
304
320
  };
305
321
  return window.addEventListener("message", t), () => {
@@ -307,17 +323,17 @@ const I = (i) => {
307
323
  };
308
324
  }, [e]), {
309
325
  value: a,
310
- setValue: f
326
+ setValue: u
311
327
  };
312
- }, y = (i) => {
328
+ }, v = (i) => {
313
329
  const [o, s] = w();
314
- return b(() => {
330
+ return f(() => {
315
331
  const a = (n) => {
316
332
  const { data: e, type: r } = n;
317
333
  if (e && r === "message") {
318
- if (e.id !== i) return;
334
+ if (e.messageType !== i) return;
319
335
  console.log(
320
- `[Cabbage-React] Received data for messageId: ${e.id}`,
336
+ `[Cabbage-React] Received data for messageType: ${e.messageType}`,
321
337
  e
322
338
  ), s(e);
323
339
  }
@@ -330,8 +346,8 @@ const I = (i) => {
330
346
  };
331
347
  };
332
348
  export {
333
- C as Cabbage,
334
- y as useCabbageMessage,
335
- I as useCabbageProperties,
336
- v as useCabbageState
349
+ y as Cabbage,
350
+ v as useCabbageMessage,
351
+ C as useCabbageProperties,
352
+ F as useCabbageState
337
353
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cabbage-react",
3
- "version": "1.0.44",
3
+ "version": "1.0.45",
4
4
  "type": "module",
5
5
  "keywords": [
6
6
  "cabbage",