@myop/cli 0.1.45 → 0.1.47

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,438 @@
1
+ ---
2
+ name: myop-react-native-host
3
+ description: "Integrate Myop components into React Native applications using @myop/react-native. ALWAYS use the MyopComponent component or auto-generated packages — NEVER create WebViews manually. Covers MyopComponent props, CTA event handling, data binding, preloading, auto-generated packages, native-specific features (scroll, zoom, selection control), and local dev setup. Activate when the user is building a React Native app that hosts Myop components, or when you see @myop/react-native in package.json."
4
+ ---
5
+
6
+ # Myop React Native Host Integration
7
+
8
+ Embed Myop components in React Native applications using `@myop/react-native`. Components render inside a WebView with a native bridge for CTA communication.
9
+
10
+ ## CRITICAL: Always Use the SDK
11
+
12
+ **NEVER create `<WebView>` elements manually. NEVER call `myop_init_interface()` or wire `myop_cta_handler()` directly.**
13
+
14
+ The `@myop/react-native` SDK handles all WebView management, native bridge communication, loading, error handling, and caching. Always use `<MyopComponent>` or an auto-generated package.
15
+
16
+ ```tsx
17
+ // WRONG — never do this
18
+ <WebView source={{ uri: componentUrl }} />
19
+
20
+ // CORRECT — always use the SDK
21
+ <MyopComponent componentId="abc-123" data={data} on={handler} />
22
+ ```
23
+
24
+ ## End-to-End Workflow: React Native App with Myop Components
25
+
26
+ Each Myop component is a **separate project** in its own directory. You create them, develop them, push them to get a `componentId`, then reference that ID in your React Native host app.
27
+
28
+ ### Step 1: Create component projects
29
+
30
+ ```bash
31
+ # Each component gets its own directory
32
+ mkdir components/sidebar && cd components/sidebar
33
+ npx myop create # Scaffolds index.html + myop.config.json
34
+ # Build the component UI (see myop-component skill), then Ctrl+C
35
+
36
+ cd ../ && mkdir chart && cd chart
37
+ npx myop create
38
+ ```
39
+
40
+ ### Step 2: Push components to get IDs
41
+
42
+ ```bash
43
+ cd components/sidebar
44
+ npx myop push # Uploads → componentId written to myop.config.json
45
+
46
+ cd ../chart
47
+ npx myop push
48
+ ```
49
+
50
+ ### Step 3: Use in your React Native app
51
+
52
+ ```bash
53
+ cd my-rn-app
54
+ npm install @myop/react-native react-native-webview
55
+ ```
56
+
57
+ ```tsx
58
+ import { MyopComponent } from "@myop/react-native";
59
+
60
+ function App() {
61
+ return (
62
+ <View style={{ flex: 1 }}>
63
+ <MyopComponent
64
+ componentId="<sidebar-componentId-from-step-2>"
65
+ data={{ items: ["Home", "Settings"] }}
66
+ on={(action, payload) => console.log(action, payload)}
67
+ style={{ height: 300 }}
68
+ />
69
+ <MyopComponent
70
+ componentId="<chart-componentId-from-step-2>"
71
+ data={{ values: [10, 20, 30] }}
72
+ style={{ flex: 1 }}
73
+ />
74
+ </View>
75
+ );
76
+ }
77
+ ```
78
+
79
+ ### Working locally on existing components (componentId already in code)
80
+
81
+ When you find `componentId` values already used in the codebase and the developer wants to modify those components locally:
82
+
83
+ **Step A: Pull the component source**
84
+
85
+ ```bash
86
+ mkdir components/sidebar && cd components/sidebar
87
+ npx myop pull <componentId>
88
+ # Downloads index.html + creates myop.config.json with the componentId
89
+ ```
90
+
91
+ **Step B: Start the local dev server**
92
+
93
+ ```bash
94
+ npx myop dev # Serves component on port 9292 with HMR
95
+ ```
96
+
97
+ **Step C: Point the React Native app to local dev server**
98
+
99
+ ```tsx
100
+ import { enableLocalDev, setCloudRepositoryUrl } from "@myop/react-native";
101
+ enableLocalDev(); // All <MyopComponent> instances load from localhost:9292
102
+
103
+ // Android emulator requires explicit IP:
104
+ // setCloudRepositoryUrl("http://10.0.2.2:9292");
105
+ // Physical device — use your machine's IP:
106
+ // setCloudRepositoryUrl("http://192.168.1.100:9292");
107
+ ```
108
+
109
+ Now edits to `index.html` are reflected instantly in the React Native app.
110
+
111
+ **Step D: Push changes when done**
112
+
113
+ ```bash
114
+ npx myop push # Uploads updated component to the same componentId
115
+ ```
116
+
117
+ Remove or comment out `enableLocalDev()` to go back to loading from the Myop cloud.
118
+
119
+ **Multiple components:**
120
+
121
+ ```bash
122
+ mkdir -p components && cd components
123
+ npx myop pull <sidebar-id> -o sidebar/index.html
124
+ npx myop pull <chart-id> -o chart/index.html
125
+ npx myop dev -m # Monorepo mode — select which components to serve
126
+ ```
127
+
128
+ ## When This Skill Activates
129
+
130
+ - `@myop/react-native` is in `package.json` dependencies
131
+ - User asks to "add a Myop component to React Native", "integrate Myop in mobile"
132
+ - Files import from `@myop/react-native`
133
+
134
+ ## Installation
135
+
136
+ ```bash
137
+ npm install @myop/react-native react-native-webview
138
+ ```
139
+
140
+ Peer dependencies: `react >= 16.8.0`, `react-native >= 0.60.0`, `react-native-webview >= 11.0.0`.
141
+
142
+ For Expo projects:
143
+ ```bash
144
+ npx expo install react-native-webview
145
+ npm install @myop/react-native
146
+ ```
147
+
148
+ ## Quick Start
149
+
150
+ ### Option 1: Auto-Generated Package (Recommended)
151
+
152
+ ```bash
153
+ npm install https://cloud.myop.dev/npm/{componentId}/react-native
154
+ ```
155
+
156
+ ```tsx
157
+ import { MyComponent } from "@myop/my-component";
158
+
159
+ function App() {
160
+ return (
161
+ <MyComponent
162
+ data={{ title: "Hello", items: ["a", "b"] }}
163
+ onItemSelected={(payload) => {
164
+ console.log(payload.itemId);
165
+ }}
166
+ style={{ flex: 1 }}
167
+ />
168
+ );
169
+ }
170
+ ```
171
+
172
+ The auto-generated package bakes in the `componentId` and exports typed interfaces.
173
+
174
+ ### Option 2: MyopComponent Directly
175
+
176
+ ```tsx
177
+ import { MyopComponent } from "@myop/react-native";
178
+
179
+ function App() {
180
+ return (
181
+ <MyopComponent
182
+ componentId="your-component-id"
183
+ data={{ title: "Hello" }}
184
+ on={(action, payload) => {
185
+ console.log(action, payload);
186
+ }}
187
+ style={{ flex: 1 }}
188
+ />
189
+ );
190
+ }
191
+ ```
192
+
193
+ ## MyopComponent Props
194
+
195
+ | Prop | Type | Default | Description |
196
+ |------|------|---------|-------------|
197
+ | `componentId` | `string` | — | Myop component ID (UUID) |
198
+ | `data` | `any` | — | Data passed to `myop_init_interface`. Reactive — updates trigger re-render |
199
+ | `on` | `(action, payload) => void` | — | Generic handler for all CTA events |
200
+ | `onLoad` | `(component: IMyopComponentProxy) => void` | — | Called when component finishes loading |
201
+ | `onError` | `(error: string) => void` | — | Called on load failure |
202
+ | `style` | `StyleProp<ViewStyle>` | `{width:'100%', height:'100%'}` | Style for the outer View container |
203
+ | `loader` | `ReactNode` | — | Custom loading indicator |
204
+ | `fallback` | `ReactNode` | — | Custom error fallback UI |
205
+ | `fadeDuration` | `number` | `200` | Loader fade-out duration in ms |
206
+ | `environment` | `string` | — | Load from specific environment |
207
+ | `preview` | `boolean` | `false` | Load unpublished preview version |
208
+ | `scrollEnabled` | `boolean` | `false` | Allow scrolling inside the WebView |
209
+ | `zoomEnabled` | `boolean` | `false` | Allow pinch-to-zoom |
210
+ | `selectionEnabled` | `boolean` | `false` | Allow text selection (disabled for native feel) |
211
+ | `componentConfig` | `IComponentInstanceConfig` | — | Direct config object (advanced) |
212
+
213
+ ### Native-Specific Props
214
+
215
+ The `scrollEnabled`, `zoomEnabled`, and `selectionEnabled` props are unique to React Native:
216
+
217
+ ```tsx
218
+ <MyopComponent
219
+ componentId="article-viewer"
220
+ data={{ article }}
221
+ scrollEnabled={true} // Allow scrolling for long content
222
+ zoomEnabled={false} // Disable pinch zoom
223
+ selectionEnabled={false} // Disable text selection for native feel
224
+ style={{ flex: 1 }}
225
+ />
226
+ ```
227
+
228
+ When `selectionEnabled={false}` (default), CSS rules are injected to prevent text selection and touch callout, with exceptions for `<input>`, `<textarea>`, and `[contenteditable]` elements.
229
+
230
+ ## CTA Event Handling
231
+
232
+ CTA events from the component are received via `postMessage` bridge and dispatched to the `on` callback:
233
+
234
+ ```tsx
235
+ <MyopComponent
236
+ componentId="task-list"
237
+ data={{ tasks }}
238
+ on={(action, payload) => {
239
+ switch (action) {
240
+ case 'task-toggled':
241
+ console.log(payload.taskId, payload.completed);
242
+ break;
243
+ case 'task-deleted':
244
+ console.log(payload.taskId);
245
+ break;
246
+ }
247
+ }}
248
+ />
249
+ ```
250
+
251
+ ## Data Binding
252
+
253
+ The `data` prop is reactive. When it changes (compared by JSON serialization to avoid redundant calls), `myop_init_interface(data)` is called automatically:
254
+
255
+ ```tsx
256
+ function Counter() {
257
+ const [count, setCount] = useState(0);
258
+
259
+ return (
260
+ <View>
261
+ <Button title="+1" onPress={() => setCount(c => c + 1)} />
262
+ <MyopComponent
263
+ componentId="counter-display"
264
+ data={{ count }}
265
+ style={{ height: 200 }}
266
+ />
267
+ </View>
268
+ );
269
+ }
270
+ ```
271
+
272
+ **Note:** When `data` is provided, the WebView is hidden (opacity: 0) until the component has loaded and real data has been applied. This prevents the component's preview/placeholder content from flashing before `myop_init_interface` is called.
273
+
274
+ ## Component Instance (IMyopComponentProxy)
275
+
276
+ The `onLoad` callback receives a proxy object for controlling the component:
277
+
278
+ ```tsx
279
+ <MyopComponent
280
+ componentId="..."
281
+ onLoad={(component) => {
282
+ // component.id — the component ID
283
+ // component.props.myop_init_interface(data) — update data
284
+ // component.element.style.set('background', 'red') — style DOM
285
+ // component.element.style.get('background') — returns Promise
286
+ // component.element.set('innerHTML', '<b>hi</b>') — set DOM property
287
+ // component.element.get('scrollHeight') — returns Promise
288
+ // component.dispose() — cleanup
289
+ // component.hide() / component.show()
290
+ }}
291
+ />
292
+ ```
293
+
294
+ The proxy communicates with the WebView via `injectJavaScript`. Element `get()` operations are asynchronous (return Promises) because they cross the native-WebView bridge.
295
+
296
+ ## Preloading
297
+
298
+ ```tsx
299
+ import { preloadComponents, isPreloaded } from "@myop/react-native";
300
+
301
+ // Preload on app startup or before navigating to a screen
302
+ await preloadComponents(["component-id-1", "component-id-2"]);
303
+ await preloadComponents(["component-id-1"], "staging");
304
+
305
+ if (isPreloaded("component-id-1")) {
306
+ console.log("Will render instantly — no loader shown");
307
+ }
308
+ ```
309
+
310
+ When a component is preloaded, the loader is not shown at all.
311
+
312
+ ## Configuration Functions
313
+
314
+ ```tsx
315
+ import {
316
+ enableLocalDev,
317
+ setCloudRepositoryUrl,
318
+ setEnvironment,
319
+ } from "@myop/react-native";
320
+
321
+ enableLocalDev(); // localhost:9292
322
+ setCloudRepositoryUrl("https://custom"); // Custom URL
323
+ setEnvironment("staging"); // Default environment
324
+ ```
325
+
326
+ **Local dev note:** For iOS simulator, `localhost` works. For Android emulator, use `10.0.2.2` instead. For physical devices, use your machine's IP address:
327
+
328
+ ```tsx
329
+ import { setCloudRepositoryUrl } from "@myop/react-native";
330
+
331
+ // Android emulator
332
+ setCloudRepositoryUrl("http://10.0.2.2:9292");
333
+
334
+ // Physical device (replace with your machine's IP)
335
+ setCloudRepositoryUrl("http://192.168.1.100:9292");
336
+ ```
337
+
338
+ ## Custom Loader
339
+
340
+ Pass a React Native component as the `loader` prop. The loader supports a fade-out animation via `MyopLoader`:
341
+
342
+ ```tsx
343
+ import { MyopComponent, MyopLoader } from "@myop/react-native";
344
+
345
+ <MyopComponent
346
+ componentId="..."
347
+ loader={
348
+ <MyopLoader>
349
+ <ActivityIndicator size="large" color="#007AFF" />
350
+ <Text>Loading component...</Text>
351
+ </MyopLoader>
352
+ }
353
+ fallback={<Text>Failed to load component</Text>}
354
+ fadeDuration={300}
355
+ />
356
+ ```
357
+
358
+ ## Complete Example
359
+
360
+ ```tsx
361
+ import React, { useState } from 'react';
362
+ import { View, Button, Text, StyleSheet } from 'react-native';
363
+ import { MyopComponent, preloadComponents } from '@myop/react-native';
364
+ import type { IMyopComponentProxy } from '@myop/react-native';
365
+
366
+ // Preload on module load
367
+ preloadComponents(['task-list-abc123']);
368
+
369
+ function TaskScreen() {
370
+ const [tasks, setTasks] = useState([
371
+ { id: '1', title: 'Review PR', completed: false },
372
+ { id: '2', title: 'Deploy', completed: true },
373
+ ]);
374
+
375
+ return (
376
+ <View style={styles.container}>
377
+ <Text style={styles.title}>My Tasks</Text>
378
+ <MyopComponent
379
+ componentId="task-list-abc123"
380
+ data={{ tasks }}
381
+ on={(action, payload) => {
382
+ if (action === 'task-toggled') {
383
+ setTasks(prev =>
384
+ prev.map(t =>
385
+ t.id === payload.taskId
386
+ ? { ...t, completed: payload.completed }
387
+ : t
388
+ )
389
+ );
390
+ }
391
+ if (action === 'task-deleted') {
392
+ setTasks(prev => prev.filter(t => t.id !== payload.taskId));
393
+ }
394
+ }}
395
+ onLoad={(component) => {
396
+ console.log('Component loaded:', component.id);
397
+ }}
398
+ onError={(error) => {
399
+ console.error('Failed:', error);
400
+ }}
401
+ scrollEnabled={true}
402
+ selectionEnabled={false}
403
+ style={styles.component}
404
+ />
405
+ </View>
406
+ );
407
+ }
408
+
409
+ const styles = StyleSheet.create({
410
+ container: { flex: 1 },
411
+ title: { fontSize: 24, padding: 16 },
412
+ component: { flex: 1 },
413
+ });
414
+ ```
415
+
416
+ ## Auto-Generated React Native Package
417
+
418
+ ```bash
419
+ npm install https://cloud.myop.dev/npm/{componentId}/react-native
420
+ ```
421
+
422
+ The generated package exports:
423
+ - Named component with `componentId` baked in (no `componentId` prop needed)
424
+ - Typed `data` prop from `MyopInitData`
425
+ - Typed `on[ActionName]` handlers from `MyopCtaPayloads`
426
+ - `COMPONENT_ID` constant
427
+
428
+ ## TypeScript Types
429
+
430
+ ```typescript
431
+ import type {
432
+ IMyopComponentProxy, // Component instance proxy (onLoad callback)
433
+ IComponentInstanceConfig, // Direct config object type
434
+ } from "@myop/react-native";
435
+
436
+ import { MyopLoader } from "@myop/react-native";
437
+ import type { MyopLoaderRef } from "@myop/react-native";
438
+ ```