@rozenite/controls-plugin 1.4.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.
@@ -0,0 +1,31 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>panel Panel</title>
7
+ <style>
8
+ html,
9
+ body {
10
+ height: 100%;
11
+ }
12
+
13
+ body {
14
+ overflow: hidden;
15
+ }
16
+
17
+ #root {
18
+ display: flex;
19
+ height: 100%;
20
+ }
21
+ </style>
22
+ <script>
23
+ var __ROZENITE_PANEL__ = true;
24
+ </script>
25
+ <script type="module" crossorigin src="./assets/panel-OL0XQF_c.js"></script>
26
+ <link rel="stylesheet" crossorigin href="./assets/panel-B5zz0XK3.css">
27
+ </head>
28
+ <body>
29
+ <div id="root"></div>
30
+ </body>
31
+ </html>
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const o=e=>e;exports.useRozeniteControlsPlugin=void 0;const n=process.env.NODE_ENV!=="production",t=typeof window>"u";n&&!t?exports.useRozeniteControlsPlugin=require("./useRozeniteControlsPlugin.cjs").useRozeniteControlsPlugin:exports.useRozeniteControlsPlugin=()=>null;exports.createSection=o;
@@ -0,0 +1,2 @@
1
+ export { createSection, type ControlsButtonItem, type ControlsInputItem, type ControlsItem, type ControlsSelectItem, type ControlsSelectOption, type ControlsSection, type ControlsTextItem, type ControlsToggleItem, type ControlsValidationResult, type RozeniteControlsPluginOptions, } from './src/shared/types';
2
+ export declare let useRozeniteControlsPlugin: typeof import('./src/react-native/useRozeniteControlsPlugin').useRozeniteControlsPlugin;
@@ -0,0 +1,8 @@
1
+ const i = (n) => n;
2
+ let e;
3
+ const o = process.env.NODE_ENV !== "production", t = typeof window > "u";
4
+ o && !t ? e = require("./useRozeniteControlsPlugin.js").useRozeniteControlsPlugin : e = () => null;
5
+ export {
6
+ i as createSection,
7
+ e as useRozeniteControlsPlugin
8
+ };
@@ -0,0 +1,7 @@
1
+ declare const _default: {
2
+ panels: {
3
+ name: string;
4
+ source: string;
5
+ }[];
6
+ };
7
+ export default _default;
@@ -0,0 +1 @@
1
+ {"name":"@rozenite/controls-plugin","version":"0.0.0","description":"Device-owned controls for Rozenite.","panels":[{"name":"Controls","source":"/panel.html"}]}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,3 @@
1
+ import { ControlsEventMap } from '../shared/messaging';
2
+ import { RozeniteControlsPluginOptions } from '../shared/types';
3
+ export declare const useRozeniteControlsPlugin: ({ sections, }: RozeniteControlsPluginOptions) => import('../../../plugin-bridge/src/index.ts').RozeniteDevToolsClient<ControlsEventMap> | null;
@@ -0,0 +1,35 @@
1
+ import { ControlsSectionSnapshot } from './types';
2
+ export type ControlsSnapshotEvent = {
3
+ type: 'snapshot';
4
+ sections: ControlsSectionSnapshot[];
5
+ };
6
+ export type ControlsGetSnapshotEvent = {
7
+ type: 'get-snapshot';
8
+ };
9
+ export type ControlsUpdateRequestEvent = {
10
+ type: 'update-request';
11
+ requestId: string;
12
+ sectionId: string;
13
+ itemId: string;
14
+ value: boolean | string;
15
+ };
16
+ export type ControlsUpdateResultEvent = {
17
+ type: 'update-result';
18
+ requestId: string;
19
+ sectionId: string;
20
+ itemId: string;
21
+ status: 'ok' | 'error';
22
+ message?: string;
23
+ };
24
+ export type ControlsInvokeActionEvent = {
25
+ type: 'invoke-action';
26
+ sectionId: string;
27
+ itemId: string;
28
+ action: 'press';
29
+ };
30
+ export type ControlsEvent = ControlsSnapshotEvent | ControlsGetSnapshotEvent | ControlsUpdateRequestEvent | ControlsUpdateResultEvent | ControlsInvokeActionEvent;
31
+ export type ControlsEventMap = {
32
+ [K in ControlsEvent['type']]: Extract<ControlsEvent, {
33
+ type: K;
34
+ }>;
35
+ };
@@ -0,0 +1,22 @@
1
+ import { ControlsButtonItem, ControlsInputItem, ControlsMutableItemBase, ControlsSelectItem, ControlsSection, ControlsSectionSnapshot, ControlsToggleItem, ControlsValidationResult } from './types';
2
+ export type ActionRegistryEntry = {
3
+ type: 'toggle';
4
+ validate?: ControlsToggleItem['validate'];
5
+ onUpdate: ControlsToggleItem['onUpdate'];
6
+ } | {
7
+ type: 'button';
8
+ onPress: ControlsButtonItem['onPress'];
9
+ } | {
10
+ type: 'select';
11
+ validate?: ControlsSelectItem['validate'];
12
+ onUpdate: ControlsSelectItem['onUpdate'];
13
+ } | {
14
+ type: 'input';
15
+ validate?: ControlsInputItem['validate'];
16
+ onUpdate: ControlsInputItem['onUpdate'];
17
+ };
18
+ declare const validateValue: <TValue>(validate: ControlsMutableItemBase<TValue>["validate"], value: TValue) => ControlsValidationResult;
19
+ export declare const serializeSections: (sections: ControlsSection[]) => ControlsSectionSnapshot[];
20
+ export declare const buildActionRegistry: (sections: ControlsSection[]) => Map<string, ActionRegistryEntry>;
21
+ export declare const getActionRegistryKey: (sectionId: string, itemId: string) => string;
22
+ export { validateValue };
@@ -0,0 +1,67 @@
1
+ export type ControlsTextItem = {
2
+ id: string;
3
+ type: 'text';
4
+ title: string;
5
+ value: string;
6
+ description?: string;
7
+ };
8
+ export type ControlsValidationResult = {
9
+ valid: true;
10
+ } | {
11
+ valid: false;
12
+ message: string;
13
+ };
14
+ export type ControlsMutableItemBase<TValue> = {
15
+ id: string;
16
+ title: string;
17
+ value: TValue;
18
+ description?: string;
19
+ disabled?: boolean;
20
+ validate?: (nextValue: TValue) => ControlsValidationResult;
21
+ onUpdate: (nextValue: TValue) => void | Promise<void>;
22
+ };
23
+ export type ControlsToggleItem = ControlsMutableItemBase<boolean> & {
24
+ type: 'toggle';
25
+ };
26
+ export type ControlsButtonItem = {
27
+ id: string;
28
+ type: 'button';
29
+ title: string;
30
+ actionLabel?: string;
31
+ description?: string;
32
+ disabled?: boolean;
33
+ onPress: () => void | Promise<void>;
34
+ };
35
+ export type ControlsSelectOption = {
36
+ label: string;
37
+ value: string;
38
+ };
39
+ export type ControlsSelectItem = ControlsMutableItemBase<string> & {
40
+ type: 'select';
41
+ options: ControlsSelectOption[];
42
+ };
43
+ export type ControlsInputItem = ControlsMutableItemBase<string> & {
44
+ type: 'input';
45
+ placeholder?: string;
46
+ applyLabel?: string;
47
+ };
48
+ export type ControlsItem = ControlsTextItem | ControlsToggleItem | ControlsButtonItem | ControlsSelectItem | ControlsInputItem;
49
+ export type ControlsSection = {
50
+ id: string;
51
+ title: string;
52
+ description?: string;
53
+ items: ControlsItem[];
54
+ };
55
+ export type ControlsTextItemSnapshot = Omit<ControlsTextItem, never>;
56
+ export type ControlsToggleItemSnapshot = Omit<ControlsToggleItem, 'validate' | 'onUpdate'>;
57
+ export type ControlsButtonItemSnapshot = Omit<ControlsButtonItem, 'onPress'>;
58
+ export type ControlsSelectItemSnapshot = Omit<ControlsSelectItem, 'validate' | 'onUpdate'>;
59
+ export type ControlsInputItemSnapshot = Omit<ControlsInputItem, 'validate' | 'onUpdate'>;
60
+ export type ControlsItemSnapshot = ControlsTextItemSnapshot | ControlsToggleItemSnapshot | ControlsButtonItemSnapshot | ControlsSelectItemSnapshot | ControlsInputItemSnapshot;
61
+ export type ControlsSectionSnapshot = Omit<ControlsSection, 'items'> & {
62
+ items: ControlsItemSnapshot[];
63
+ };
64
+ export type RozeniteControlsPluginOptions = {
65
+ sections: ControlsSection[];
66
+ };
67
+ export declare const createSection: <TSection extends ControlsSection>(section: TSection) => TSection;
@@ -0,0 +1 @@
1
+ export default function ControlsPanel(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const $=require("@rozenite/plugin-bridge"),i=require("react"),g=(e,t)=>e?e(t):{valid:!0},R=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(t=>({id:t.id,title:t.title,description:t.description,items:t.items.map(R)})),P=e=>{const t=new Map;return e.forEach(u=>{u.items.forEach(n=>{const l=`${u.id}:${n.id}`;n.type==="toggle"&&t.set(l,{type:"toggle",validate:n.validate,onUpdate:n.onUpdate}),n.type==="button"&&t.set(l,{type:"button",onPress:n.onPress}),n.type==="select"&&t.set(l,{type:"select",validate:n.validate,onUpdate:n.onUpdate}),n.type==="input"&&t.set(l,{type:"input",validate:n.validate,onUpdate:n.onUpdate})})}),t},f=(e,t)=>`${e}:${t}`,z=({sections:e})=>{const t=$.useRozeniteDevToolsClient({pluginId:"@rozenite/controls-plugin"}),u=i.useMemo(()=>U(e),[e]),n=i.useMemo(()=>P(e),[e]),l=i.useRef(n);return i.useEffect(()=>{l.current=n},[n]),i.useEffect(()=>{t&&t.send("snapshot",{type:"snapshot",sections:u})},[t,u]),i.useEffect(()=>{if(!t)return;const v=async({requestId:s,sectionId:r,itemId:o,value:p})=>{const d=f(r,o),a=l.current.get(d);if(!a||a.type==="button"){t.send("update-result",{type:"update-result",requestId:s,sectionId:r,itemId:o,status:"error",message:"Update target not found."});return}try{if(a.type==="toggle"){if(typeof p!="boolean"){t.send("update-result",{type:"update-result",requestId:s,sectionId:r,itemId:o,status:"error",message:"Invalid toggle value."});return}const y=g(a.validate,p);if(!y.valid){t.send("update-result",{type:"update-result",requestId:s,sectionId:r,itemId:o,status:"error",message:y.message});return}await a.onUpdate(p),t.send("update-result",{type:"update-result",requestId:s,sectionId:r,itemId:o,status:"ok"});return}if(typeof p!="string"){t.send("update-result",{type:"update-result",requestId:s,sectionId:r,itemId:o,status:"error",message:`Invalid ${a.type} value.`});return}const c=g(a.validate,p);if(!c.valid){t.send("update-result",{type:"update-result",requestId:s,sectionId:r,itemId:o,status:"error",message:c.message});return}await a.onUpdate(p),t.send("update-result",{type:"update-result",requestId:s,sectionId:r,itemId:o,status:"ok"})}catch(c){console.warn(`[Rozenite] Controls Plugin: Update failed for ${r}/${o}.`,c),t.send("update-result",{type:"update-result",requestId:s,sectionId:r,itemId:o,status:"error",message:"Update failed on the device."})}},b=async({sectionId:s,itemId:r,action:o})=>{if(o!=="press"){console.warn(`[Rozenite] Controls Plugin: Unsupported action "${o}" for ${s}/${r}.`);return}const p=f(s,r),d=l.current.get(p);if(!d){console.warn(`[Rozenite] Controls Plugin: Action target not found for ${s}/${r}.`);return}try{if(d.type!=="button"){console.warn(`[Rozenite] Controls Plugin: Invalid press action payload for ${s}/${r}.`);return}await d.onPress()}catch(a){console.warn(`[Rozenite] Controls Plugin: Action failed for ${s}/${r}.`,a)}},h=[t.onMessage("get-snapshot",()=>{t.send("snapshot",{type:"snapshot",sections:u})}),t.onMessage("update-request",s=>{v(s)}),t.onMessage("invoke-action",s=>{b(s)})];return()=>{h.forEach(s=>s.remove())}},[t,u]),t};exports.useRozeniteControlsPlugin=z;
@@ -0,0 +1,226 @@
1
+ import { useRozeniteDevToolsClient as R } from "@rozenite/plugin-bridge";
2
+ import { useMemo as f, useRef as U, useEffect as y } from "react";
3
+ const g = (e, t) => e ? e(t) : { valid: !0 }, w = (e) => e.type === "text" ? e : e.type === "toggle" ? {
4
+ id: e.id,
5
+ type: e.type,
6
+ title: e.title,
7
+ value: e.value,
8
+ description: e.description,
9
+ disabled: e.disabled
10
+ } : e.type === "button" ? {
11
+ id: e.id,
12
+ type: e.type,
13
+ title: e.title,
14
+ actionLabel: e.actionLabel,
15
+ description: e.description,
16
+ disabled: e.disabled
17
+ } : e.type === "select" ? {
18
+ id: e.id,
19
+ type: e.type,
20
+ title: e.title,
21
+ value: e.value,
22
+ options: e.options,
23
+ description: e.description,
24
+ disabled: e.disabled
25
+ } : {
26
+ id: e.id,
27
+ type: e.type,
28
+ title: e.title,
29
+ value: e.value,
30
+ placeholder: e.placeholder,
31
+ applyLabel: e.applyLabel,
32
+ description: e.description,
33
+ disabled: e.disabled
34
+ }, z = (e) => e.map((t) => ({
35
+ id: t.id,
36
+ title: t.title,
37
+ description: t.description,
38
+ items: t.items.map(w)
39
+ })), P = (e) => {
40
+ const t = /* @__PURE__ */ new Map();
41
+ return e.forEach((u) => {
42
+ u.items.forEach((n) => {
43
+ const p = `${u.id}:${n.id}`;
44
+ n.type === "toggle" && t.set(p, {
45
+ type: "toggle",
46
+ validate: n.validate,
47
+ onUpdate: n.onUpdate
48
+ }), n.type === "button" && t.set(p, {
49
+ type: "button",
50
+ onPress: n.onPress
51
+ }), n.type === "select" && t.set(p, {
52
+ type: "select",
53
+ validate: n.validate,
54
+ onUpdate: n.onUpdate
55
+ }), n.type === "input" && t.set(p, {
56
+ type: "input",
57
+ validate: n.validate,
58
+ onUpdate: n.onUpdate
59
+ });
60
+ });
61
+ }), t;
62
+ }, v = (e, t) => `${e}:${t}`, A = ({
63
+ sections: e
64
+ }) => {
65
+ const t = R({
66
+ pluginId: "@rozenite/controls-plugin"
67
+ }), u = f(() => z(e), [e]), n = f(() => P(e), [e]), p = U(n);
68
+ return y(() => {
69
+ p.current = n;
70
+ }, [n]), y(() => {
71
+ t && t.send("snapshot", {
72
+ type: "snapshot",
73
+ sections: u
74
+ });
75
+ }, [t, u]), y(() => {
76
+ if (!t)
77
+ return;
78
+ const b = async ({
79
+ requestId: s,
80
+ sectionId: r,
81
+ itemId: o,
82
+ value: l
83
+ }) => {
84
+ const d = v(r, o), a = p.current.get(d);
85
+ if (!a || a.type === "button") {
86
+ t.send("update-result", {
87
+ type: "update-result",
88
+ requestId: s,
89
+ sectionId: r,
90
+ itemId: o,
91
+ status: "error",
92
+ message: "Update target not found."
93
+ });
94
+ return;
95
+ }
96
+ try {
97
+ if (a.type === "toggle") {
98
+ if (typeof l != "boolean") {
99
+ t.send("update-result", {
100
+ type: "update-result",
101
+ requestId: s,
102
+ sectionId: r,
103
+ itemId: o,
104
+ status: "error",
105
+ message: "Invalid toggle value."
106
+ });
107
+ return;
108
+ }
109
+ const c = g(a.validate, l);
110
+ if (!c.valid) {
111
+ t.send("update-result", {
112
+ type: "update-result",
113
+ requestId: s,
114
+ sectionId: r,
115
+ itemId: o,
116
+ status: "error",
117
+ message: c.message
118
+ });
119
+ return;
120
+ }
121
+ await a.onUpdate(l), t.send("update-result", {
122
+ type: "update-result",
123
+ requestId: s,
124
+ sectionId: r,
125
+ itemId: o,
126
+ status: "ok"
127
+ });
128
+ return;
129
+ }
130
+ if (typeof l != "string") {
131
+ t.send("update-result", {
132
+ type: "update-result",
133
+ requestId: s,
134
+ sectionId: r,
135
+ itemId: o,
136
+ status: "error",
137
+ message: `Invalid ${a.type} value.`
138
+ });
139
+ return;
140
+ }
141
+ const i = g(a.validate, l);
142
+ if (!i.valid) {
143
+ t.send("update-result", {
144
+ type: "update-result",
145
+ requestId: s,
146
+ sectionId: r,
147
+ itemId: o,
148
+ status: "error",
149
+ message: i.message
150
+ });
151
+ return;
152
+ }
153
+ await a.onUpdate(l), t.send("update-result", {
154
+ type: "update-result",
155
+ requestId: s,
156
+ sectionId: r,
157
+ itemId: o,
158
+ status: "ok"
159
+ });
160
+ } catch (i) {
161
+ console.warn(
162
+ `[Rozenite] Controls Plugin: Update failed for ${r}/${o}.`,
163
+ i
164
+ ), t.send("update-result", {
165
+ type: "update-result",
166
+ requestId: s,
167
+ sectionId: r,
168
+ itemId: o,
169
+ status: "error",
170
+ message: "Update failed on the device."
171
+ });
172
+ }
173
+ }, h = async ({
174
+ sectionId: s,
175
+ itemId: r,
176
+ action: o
177
+ }) => {
178
+ if (o !== "press") {
179
+ console.warn(
180
+ `[Rozenite] Controls Plugin: Unsupported action "${o}" for ${s}/${r}.`
181
+ );
182
+ return;
183
+ }
184
+ const l = v(s, r), d = p.current.get(l);
185
+ if (!d) {
186
+ console.warn(
187
+ `[Rozenite] Controls Plugin: Action target not found for ${s}/${r}.`
188
+ );
189
+ return;
190
+ }
191
+ try {
192
+ if (d.type !== "button") {
193
+ console.warn(
194
+ `[Rozenite] Controls Plugin: Invalid press action payload for ${s}/${r}.`
195
+ );
196
+ return;
197
+ }
198
+ await d.onPress();
199
+ } catch (a) {
200
+ console.warn(
201
+ `[Rozenite] Controls Plugin: Action failed for ${s}/${r}.`,
202
+ a
203
+ );
204
+ }
205
+ }, $ = [
206
+ t.onMessage("get-snapshot", () => {
207
+ t.send("snapshot", {
208
+ type: "snapshot",
209
+ sections: u
210
+ });
211
+ }),
212
+ t.onMessage("update-request", (s) => {
213
+ b(s);
214
+ }),
215
+ t.onMessage("invoke-action", (s) => {
216
+ h(s);
217
+ })
218
+ ];
219
+ return () => {
220
+ $.forEach((s) => s.remove());
221
+ };
222
+ }, [t, u]), t;
223
+ };
224
+ export {
225
+ A as useRozeniteControlsPlugin
226
+ };
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@rozenite/controls-plugin",
3
+ "version": "1.4.0",
4
+ "description": "A Rozenite plugin for exposing app-defined controls directly in React Native DevTools.",
5
+ "type": "module",
6
+ "main": "./dist/react-native.cjs",
7
+ "module": "./dist/react-native.js",
8
+ "types": "./dist/react-native.d.ts",
9
+ "dependencies": {
10
+ "@rozenite/plugin-bridge": "1.4.0"
11
+ },
12
+ "devDependencies": {
13
+ "@types/react": "~19.2.2",
14
+ "autoprefixer": "^10.4.21",
15
+ "postcss": "^8.5.6",
16
+ "react": "19.2.0",
17
+ "react-dom": "19.2.0",
18
+ "react-native": "0.83.1",
19
+ "react-native-web": "^0.21.2",
20
+ "tailwindcss": "^3.4.17",
21
+ "tailwindcss-animate": "^1.0.7",
22
+ "typescript": "~5.9.3",
23
+ "vite": "^7.3.1",
24
+ "@rozenite/vite-plugin": "1.4.0",
25
+ "rozenite": "1.4.0"
26
+ },
27
+ "peerDependencies": {
28
+ "react": "*",
29
+ "react-native": "*"
30
+ },
31
+ "license": "MIT",
32
+ "scripts": {
33
+ "build": "rozenite build",
34
+ "dev": "rozenite dev",
35
+ "typecheck": "tsc -p tsconfig.json --noEmit",
36
+ "lint": "eslint ."
37
+ }
38
+ }
@@ -0,0 +1,6 @@
1
+ export default {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ };
@@ -0,0 +1,19 @@
1
+ export {
2
+ createSection,
3
+ type ControlsButtonItem,
4
+ type ControlsInputItem,
5
+ type ControlsItem,
6
+ type ControlsSelectItem,
7
+ type ControlsSelectOption,
8
+ type ControlsSection,
9
+ type ControlsTextItem,
10
+ type ControlsToggleItem,
11
+ type ControlsValidationResult,
12
+ type RozeniteControlsPluginOptions,
13
+ } from './src/shared/types';
14
+
15
+ export declare const useRozeniteControlsPlugin: (
16
+ options: import('./src/shared/types').RozeniteControlsPluginOptions
17
+ ) => import('@rozenite/plugin-bridge').RozeniteDevToolsClient<
18
+ import('./src/shared/messaging').ControlsEventMap
19
+ > | null;
@@ -0,0 +1,25 @@
1
+ export {
2
+ createSection,
3
+ type ControlsButtonItem,
4
+ type ControlsInputItem,
5
+ type ControlsItem,
6
+ type ControlsSelectItem,
7
+ type ControlsSelectOption,
8
+ type ControlsSection,
9
+ type ControlsTextItem,
10
+ type ControlsToggleItem,
11
+ type ControlsValidationResult,
12
+ type RozeniteControlsPluginOptions,
13
+ } from './src/shared/types';
14
+
15
+ export let useRozeniteControlsPlugin: typeof import('./src/react-native/useRozeniteControlsPlugin').useRozeniteControlsPlugin;
16
+
17
+ const isDev = process.env.NODE_ENV !== 'production';
18
+ const isServer = typeof window === 'undefined';
19
+
20
+ if (isDev && !isServer) {
21
+ useRozeniteControlsPlugin =
22
+ require('./src/react-native/useRozeniteControlsPlugin').useRozeniteControlsPlugin;
23
+ } else {
24
+ useRozeniteControlsPlugin = () => null;
25
+ }
@@ -0,0 +1,8 @@
1
+ export default {
2
+ panels: [
3
+ {
4
+ name: 'Controls',
5
+ source: './src/ui/panel.tsx',
6
+ },
7
+ ],
8
+ };