@rozenite/controls-plugin 1.11.0 → 1.12.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/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # @rozenite/controls-plugin
2
2
 
3
+ ## 1.12.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#292](https://github.com/callstackincubator/rozenite/pull/292) [`c595bad`](https://github.com/callstackincubator/rozenite/commit/c595bad6b9b05d0212cb18aaf6afcd5134e89288) Thanks [@draggie](https://github.com/draggie)! - Allow multiple `useRozeniteControlsPlugin` hook instances to contribute controls sections to one combined panel.
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies []:
12
+ - @rozenite/agent-bridge@1.12.0
13
+ - @rozenite/agent-shared@1.12.0
14
+ - @rozenite/plugin-bridge@1.12.0
15
+
3
16
  ## 1.11.0
4
17
 
5
18
  ### Patch Changes
package/README.md CHANGED
@@ -86,6 +86,39 @@ function App() {
86
86
  }
87
87
  ```
88
88
 
89
+ You can also call the hook from multiple components. Each active hook instance contributes sections to the same panel:
90
+
91
+ ```ts
92
+ function LocaleControls() {
93
+ useRozeniteControlsPlugin((previousOptions) => ({
94
+ sections: [
95
+ ...previousOptions.sections,
96
+ createSection({
97
+ id: 'locale',
98
+ title: 'Locale',
99
+ items: [
100
+ {
101
+ id: 'language',
102
+ type: 'select',
103
+ title: 'Language',
104
+ value: 'en',
105
+ options: [
106
+ { label: 'English', value: 'en' },
107
+ { label: 'Polish', value: 'pl' },
108
+ ],
109
+ onUpdate: (language) => {
110
+ i18n.changeLanguage(language);
111
+ },
112
+ },
113
+ ],
114
+ }),
115
+ ],
116
+ }));
117
+
118
+ return null;
119
+ }
120
+ ```
121
+
89
122
  ## Supported Controls
90
123
 
91
124
  - `text`: Show read-only runtime values such as current environment, build label, or connection status.
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const A=require("@rozenite/plugin-bridge"),c=require("react"),f=require("@rozenite/agent-bridge"),b=require("@rozenite/agent-shared"),E=(n,t)=>n?n(t):{valid:!0},P=n=>n.type==="text"?n:n.type==="toggle"?{id:n.id,type:n.type,title:n.title,value:n.value,description:n.description,disabled:n.disabled}:n.type==="button"?{id:n.id,type:n.type,title:n.title,actionLabel:n.actionLabel,description:n.description,disabled:n.disabled}:n.type==="select"?{id:n.id,type:n.type,title:n.title,value:n.value,options:n.options,description:n.description,disabled:n.disabled}:{id:n.id,type:n.type,title:n.title,value:n.value,placeholder:n.placeholder,applyLabel:n.applyLabel,description:n.description,disabled:n.disabled},S=n=>n.map(t=>({id:t.id,title:t.title,description:t.description,items:t.items.map(P)})),z=n=>{const t=new Map;return n.forEach(o=>{o.items.forEach(e=>{const i=`${o.id}:${e.id}`;e.type==="toggle"&&t.set(i,{type:"toggle",validate:e.validate,onUpdate:e.onUpdate}),e.type==="button"&&t.set(i,{type:"button",onPress:e.onPress}),e.type==="select"&&t.set(i,{type:"select",validate:e.validate,onUpdate:e.onUpdate}),e.type==="input"&&t.set(i,{type:"input",validate:e.validate,onUpdate:e.onUpdate})})}),t},R=(n,t)=>`${n}:${t}`,v="@rozenite/controls-plugin",h={listSections:b.defineAgentToolContract({name:"list-sections",description:"List all controls sections with their item IDs, types, and titles. Does not include values — call get-item for that.",inputSchema:{type:"object",properties:{}}}),getItem:b.defineAgentToolContract({name:"get-item",description:"Get full details of a single controls item including its current value. For select items this includes available options.",inputSchema:{type:"object",properties:{sectionId:{type:"string",description:"Section ID."},itemId:{type:"string",description:"Item ID."}},required:["sectionId","itemId"]}}),setValue:b.defineAgentToolContract({name:"set-value",description:"Update the value of a toggle, select, or input item. Runs the validate callback when present. Fails for text (read-only) and button items.",inputSchema:{type:"object",properties:{sectionId:{type:"string",description:"Section ID."},itemId:{type:"string",description:"Item ID."},value:{description:"New value. Boolean for toggle items, string for select/input items."}},required:["sectionId","itemId","value"]}}),pressButton:b.defineAgentToolContract({name:"press-button",description:"Trigger a button item's action. Fails if the item is not a button or is disabled.",inputSchema:{type:"object",properties:{sectionId:{type:"string",description:"Section ID."},itemId:{type:"string",description:"Item ID."}},required:["sectionId","itemId"]}})},w=(n,t,o)=>{const e=n.find(s=>s.id===t);if(!e){const s=n.map(u=>u.id).join(", ");throw new Error(`Section "${t}" not found. Available: ${s||"(none)"}`)}const i=e.items.find(s=>s.id===o);if(!i){const s=e.items.map(u=>u.id).join(", ");throw new Error(`Item "${o}" not found in section "${t}". Available: ${s||"(none)"}`)}return{section:e,item:i}},T=n=>{f.useRozenitePluginAgentTool({pluginId:v,tool:h.listSections,handler:()=>({sections:n.map(t=>({id:t.id,title:t.title,description:t.description,items:t.items.map(o=>({id:o.id,type:o.type,title:o.title,disabled:"disabled"in o?o.disabled:void 0}))}))})}),f.useRozenitePluginAgentTool({pluginId:v,tool:h.getItem,handler:({sectionId:t,itemId:o})=>{const{item:e}=w(n,t,o);return e.type==="text"?{sectionId:t,item:{id:e.id,type:e.type,title:e.title,value:e.value,description:e.description}}:e.type==="toggle"?{sectionId:t,item:{id:e.id,type:e.type,title:e.title,value:e.value,description:e.description,disabled:e.disabled}}:e.type==="button"?{sectionId:t,item:{id:e.id,type:e.type,title:e.title,actionLabel:e.actionLabel,description:e.description,disabled:e.disabled}}:e.type==="select"?{sectionId:t,item:{id:e.id,type:e.type,title:e.title,value:e.value,options:e.options,description:e.description,disabled:e.disabled}}:{sectionId:t,item:{id:e.id,type:e.type,title:e.title,value:e.value,placeholder:e.placeholder,applyLabel:e.applyLabel,description:e.description,disabled:e.disabled}}}}),f.useRozenitePluginAgentTool({pluginId:v,tool:h.setValue,handler:async({sectionId:t,itemId:o,value:e})=>{const{item:i}=w(n,t,o);if(i.type==="text")throw new Error(`Item "${o}" is a read-only text item and cannot be updated.`);if(i.type==="button")throw new Error(`Item "${o}" is a button. Use press-button to trigger its action.`);if(i.disabled)throw new Error(`Item "${o}" is disabled.`);if(i.type==="toggle"){if(typeof e!="boolean")throw new Error(`Expected boolean value for toggle item "${o}".`);if(i.validate){const s=i.validate(e);if(!s.valid)throw new Error(s.message)}return await i.onUpdate(e),{applied:!0,sectionId:t,itemId:o}}if(typeof e!="string")throw new Error(`Expected string value for ${i.type} item "${o}".`);if(i.type==="select"){const s=i.options.map(u=>u.value);if(!s.includes(e))throw new Error(`Invalid option "${e}" for item "${o}". Valid options: ${s.join(", ")}`)}if(i.validate){const s=i.validate(e);if(!s.valid)throw new Error(s.message)}return await i.onUpdate(e),{applied:!0,sectionId:t,itemId:o}}}),f.useRozenitePluginAgentTool({pluginId:v,tool:h.pressButton,handler:async({sectionId:t,itemId:o})=>{const{item:e}=w(n,t,o);if(e.type!=="button")throw new Error(`Item "${o}" is not a button (type: ${e.type}). Use set-value to update its value.`);if(e.disabled)throw new Error(`Button "${o}" is disabled.`);return await e.onPress(),{pressed:!0,sectionId:t,itemId:o}}})},C=({sections:n})=>{const t=A.useRozeniteDevToolsClient({pluginId:"@rozenite/controls-plugin"});T(n);const o=c.useMemo(()=>S(n),[n]),e=c.useMemo(()=>z(n),[n]),i=c.useRef(e);return c.useEffect(()=>{i.current=e},[e]),c.useEffect(()=>{t&&t.send("snapshot",{type:"snapshot",sections:o})},[t,o]),c.useEffect(()=>{if(!t)return;const s=async({requestId:r,sectionId:a,itemId:l,value:d})=>{const y=R(a,l),p=i.current.get(y);if(!p||p.type==="button"){t.send("update-result",{type:"update-result",requestId:r,sectionId:a,itemId:l,status:"error",message:"Update target not found."});return}try{if(p.type==="toggle"){if(typeof d!="boolean"){t.send("update-result",{type:"update-result",requestId:r,sectionId:a,itemId:l,status:"error",message:"Invalid toggle value."});return}const $=E(p.validate,d);if(!$.valid){t.send("update-result",{type:"update-result",requestId:r,sectionId:a,itemId:l,status:"error",message:$.message});return}await p.onUpdate(d),t.send("update-result",{type:"update-result",requestId:r,sectionId:a,itemId:l,status:"ok"});return}if(typeof d!="string"){t.send("update-result",{type:"update-result",requestId:r,sectionId:a,itemId:l,status:"error",message:`Invalid ${p.type} value.`});return}const g=E(p.validate,d);if(!g.valid){t.send("update-result",{type:"update-result",requestId:r,sectionId:a,itemId:l,status:"error",message:g.message});return}await p.onUpdate(d),t.send("update-result",{type:"update-result",requestId:r,sectionId:a,itemId:l,status:"ok"})}catch(g){console.warn(`[Rozenite] Controls Plugin: Update failed for ${a}/${l}.`,g),t.send("update-result",{type:"update-result",requestId:r,sectionId:a,itemId:l,status:"error",message:"Update failed on the device."})}},u=async({sectionId:r,itemId:a,action:l})=>{if(l!=="press"){console.warn(`[Rozenite] Controls Plugin: Unsupported action "${l}" for ${r}/${a}.`);return}const d=R(r,a),y=i.current.get(d);if(!y){console.warn(`[Rozenite] Controls Plugin: Action target not found for ${r}/${a}.`);return}try{if(y.type!=="button"){console.warn(`[Rozenite] Controls Plugin: Invalid press action payload for ${r}/${a}.`);return}await y.onPress()}catch(p){console.warn(`[Rozenite] Controls Plugin: Action failed for ${r}/${a}.`,p)}},U=[t.onMessage("get-snapshot",()=>{t.send("snapshot",{type:"snapshot",sections:o})}),t.onMessage("update-request",r=>{s(r)}),t.onMessage("invoke-action",r=>{u(r)})];return()=>{U.forEach(r=>r.remove())}},[t,o]),t};exports.useRozeniteControlsPlugin=C;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const z=require("@rozenite/plugin-bridge"),y=require("react"),v=require("@rozenite/agent-bridge"),w=require("@rozenite/agent-shared"),P=(e,n)=>e?e(n):{valid:!0},O=e=>e.type==="text"?e:e.type==="toggle"?{id:e.id,type:e.type,title:e.title,value:e.value,description:e.description,disabled:e.disabled}:e.type==="button"?{id:e.id,type:e.type,title:e.title,actionLabel:e.actionLabel,description:e.description,disabled:e.disabled}:e.type==="select"?{id:e.id,type:e.type,title:e.title,value:e.value,options:e.options,description:e.description,disabled:e.disabled}:{id:e.id,type:e.type,title:e.title,value:e.value,placeholder:e.placeholder,applyLabel:e.applyLabel,description:e.description,disabled:e.disabled},U=e=>e.map(n=>({id:n.id,title:n.title,description:n.description,items:n.items.map(O)})),A=e=>{const n=new Map;return e.forEach(r=>{r.items.forEach(o=>{const t=`${r.id}:${o.id}`;o.type==="toggle"&&n.set(t,{type:"toggle",validate:o.validate,onUpdate:o.onUpdate}),o.type==="button"&&n.set(t,{type:"button",onPress:o.onPress}),o.type==="select"&&n.set(t,{type:"select",validate:o.validate,onUpdate:o.onUpdate}),o.type==="input"&&n.set(t,{type:"input",validate:o.validate,onUpdate:o.onUpdate})})}),n},T=(e,n)=>`${e}:${n}`,$="@rozenite/controls-plugin",E={listSections:w.defineAgentToolContract({name:"list-sections",description:"List all controls sections with their item IDs, types, and titles. Does not include values — call get-item for that.",inputSchema:{type:"object",properties:{}}}),getItem:w.defineAgentToolContract({name:"get-item",description:"Get full details of a single controls item including its current value. For select items this includes available options.",inputSchema:{type:"object",properties:{sectionId:{type:"string",description:"Section ID."},itemId:{type:"string",description:"Item ID."}},required:["sectionId","itemId"]}}),setValue:w.defineAgentToolContract({name:"set-value",description:"Update the value of a toggle, select, or input item. Runs the validate callback when present. Fails for text (read-only) and button items.",inputSchema:{type:"object",properties:{sectionId:{type:"string",description:"Section ID."},itemId:{type:"string",description:"Item ID."},value:{description:"New value. Boolean for toggle items, string for select/input items."}},required:["sectionId","itemId","value"]}}),pressButton:w.defineAgentToolContract({name:"press-button",description:"Trigger a button item's action. Fails if the item is not a button or is disabled.",inputSchema:{type:"object",properties:{sectionId:{type:"string",description:"Section ID."},itemId:{type:"string",description:"Item ID."}},required:["sectionId","itemId"]}})},R=(e,n,r)=>{const o=e.find(i=>i.id===n);if(!o){const i=e.map(s=>s.id).join(", ");throw new Error(`Section "${n}" not found. Available: ${i||"(none)"}`)}const t=o.items.find(i=>i.id===r);if(!t){const i=o.items.map(s=>s.id).join(", ");throw new Error(`Item "${r}" not found in section "${n}". Available: ${i||"(none)"}`)}return{section:o,item:t}},C=(e,n=!0)=>{v.useRozenitePluginAgentTool({pluginId:$,tool:E.listSections,enabled:n,handler:()=>({sections:e().map(r=>({id:r.id,title:r.title,description:r.description,items:r.items.map(o=>({id:o.id,type:o.type,title:o.title,disabled:"disabled"in o?o.disabled:void 0}))}))})}),v.useRozenitePluginAgentTool({pluginId:$,tool:E.getItem,enabled:n,handler:({sectionId:r,itemId:o})=>{const{item:t}=R(e(),r,o);return t.type==="text"?{sectionId:r,item:{id:t.id,type:t.type,title:t.title,value:t.value,description:t.description}}:t.type==="toggle"?{sectionId:r,item:{id:t.id,type:t.type,title:t.title,value:t.value,description:t.description,disabled:t.disabled}}:t.type==="button"?{sectionId:r,item:{id:t.id,type:t.type,title:t.title,actionLabel:t.actionLabel,description:t.description,disabled:t.disabled}}:t.type==="select"?{sectionId:r,item:{id:t.id,type:t.type,title:t.title,value:t.value,options:t.options,description:t.description,disabled:t.disabled}}:{sectionId:r,item:{id:t.id,type:t.type,title:t.title,value:t.value,placeholder:t.placeholder,applyLabel:t.applyLabel,description:t.description,disabled:t.disabled}}}}),v.useRozenitePluginAgentTool({pluginId:$,tool:E.setValue,enabled:n,handler:async({sectionId:r,itemId:o,value:t})=>{const{item:i}=R(e(),r,o);if(i.type==="text")throw new Error(`Item "${o}" is a read-only text item and cannot be updated.`);if(i.type==="button")throw new Error(`Item "${o}" is a button. Use press-button to trigger its action.`);if(i.disabled)throw new Error(`Item "${o}" is disabled.`);if(i.type==="toggle"){if(typeof t!="boolean")throw new Error(`Expected boolean value for toggle item "${o}".`);if(i.validate){const s=i.validate(t);if(!s.valid)throw new Error(s.message)}return await i.onUpdate(t),{applied:!0,sectionId:r,itemId:o}}if(typeof t!="string")throw new Error(`Expected string value for ${i.type} item "${o}".`);if(i.type==="select"){const s=i.options.map(p=>p.value);if(!s.includes(t))throw new Error(`Invalid option "${t}" for item "${o}". Valid options: ${s.join(", ")}`)}if(i.validate){const s=i.validate(t);if(!s.valid)throw new Error(s.message)}return await i.onUpdate(t),{applied:!0,sectionId:r,itemId:o}}}),v.useRozenitePluginAgentTool({pluginId:$,tool:E.pressButton,enabled:n,handler:async({sectionId:r,itemId:o})=>{const{item:t}=R(e(),r,o);if(t.type!=="button")throw new Error(`Item "${o}" is not a button (type: ${t.type}). Use set-value to update its value.`);if(t.disabled)throw new Error(`Button "${o}" is disabled.`);return await t.onPress(),{pressed:!0,sectionId:r,itemId:o}}})},x={sections:[]},D=(e,n)=>typeof n=="function"?n(e):{...e,...n,sections:[...e.sections,...n.sections]},L=()=>{const e=new Map,n=new Set;let r=0;const o=()=>e.keys().next().value,t=()=>{r+=1,n.forEach(s=>s())},i=s=>{const p=Array.from(e.entries());return s?p.findIndex(([a])=>a===s.id)===-1?[...p,[s.id,s.input]]:p.map(([a,l])=>a===s.id?[a,s.input]:[a,l]):p};return{set(s,p){const f=o();e.set(s,p),f!==o()&&t()},delete(s){e.delete(s),t()},getOptions(s){return i(s).map(([,p])=>p).reduce(D,x)},isOwner(s,p){return i(p)[0]?.[0]===s},getSnapshot(){return r},subscribe(s){return n.add(s),()=>{n.delete(s)}}}},u=L(),m=e=>{const n=z.useRozeniteDevToolsClient({pluginId:"@rozenite/controls-plugin"}),o=y.useRef(Symbol("rozenite-controls")).current,t=y.useSyncExternalStore(u.subscribe,u.getSnapshot,u.getSnapshot);y.useEffect(()=>()=>{u.delete(o)},[o]),y.useEffect(()=>{u.set(o,e)},[e,o]);const i=y.useMemo(()=>u.isOwner(o,{id:o,input:e}),[e,t,o]);return C(()=>u.getOptions().sections,i),y.useEffect(()=>{n&&n.send("snapshot",{type:"snapshot",sections:U(u.getOptions().sections)})},[n,e,t]),y.useEffect(()=>{if(!n||!i)return;const s=async({requestId:a,sectionId:l,itemId:d,value:g})=>{const b=T(l,d),c=A(u.getOptions().sections).get(b);if(!c||c.type==="button"){n.send("update-result",{type:"update-result",requestId:a,sectionId:l,itemId:d,status:"error",message:"Update target not found."});return}try{if(c.type==="toggle"){if(typeof g!="boolean"){n.send("update-result",{type:"update-result",requestId:a,sectionId:l,itemId:d,status:"error",message:"Invalid toggle value."});return}const S=P(c.validate,g);if(!S.valid){n.send("update-result",{type:"update-result",requestId:a,sectionId:l,itemId:d,status:"error",message:S.message});return}await c.onUpdate(g),n.send("update-result",{type:"update-result",requestId:a,sectionId:l,itemId:d,status:"ok"});return}if(typeof g!="string"){n.send("update-result",{type:"update-result",requestId:a,sectionId:l,itemId:d,status:"error",message:`Invalid ${c.type} value.`});return}const h=P(c.validate,g);if(!h.valid){n.send("update-result",{type:"update-result",requestId:a,sectionId:l,itemId:d,status:"error",message:h.message});return}await c.onUpdate(g),n.send("update-result",{type:"update-result",requestId:a,sectionId:l,itemId:d,status:"ok"})}catch(h){console.warn(`[Rozenite] Controls Plugin: Update failed for ${l}/${d}.`,h),n.send("update-result",{type:"update-result",requestId:a,sectionId:l,itemId:d,status:"error",message:"Update failed on the device."})}},p=async({sectionId:a,itemId:l,action:d})=>{if(d!=="press"){console.warn(`[Rozenite] Controls Plugin: Unsupported action "${d}" for ${a}/${l}.`);return}const g=T(a,l),b=A(u.getOptions().sections).get(g);if(!b){console.warn(`[Rozenite] Controls Plugin: Action target not found for ${a}/${l}.`);return}try{if(b.type!=="button"){console.warn(`[Rozenite] Controls Plugin: Invalid press action payload for ${a}/${l}.`);return}await b.onPress()}catch(c){console.warn(`[Rozenite] Controls Plugin: Action failed for ${a}/${l}.`,c)}},f=[n.onMessage("get-snapshot",()=>{n.send("snapshot",{type:"snapshot",sections:U(u.getOptions().sections)})}),n.onMessage("update-request",a=>{s(a)}),n.onMessage("invoke-action",a=>{p(a)})];return()=>{f.forEach(a=>a.remove())}},[n,i]),n};exports.useRozeniteControlsPlugin=m;