@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.
- package/dist/myop-cli.js +1461 -1207
- package/dist/skills/myop-angular-host/SKILL.md +437 -0
- package/dist/skills/myop-cli/SKILL.md +147 -0
- package/dist/skills/myop-component/SKILL.md +112 -42
- package/dist/skills/myop-component/references/dev-workflow.md +74 -8
- package/dist/skills/myop-react-host/SKILL.md +407 -0
- package/dist/skills/myop-react-host/references/auto-generated-packages.md +70 -0
- package/dist/skills/myop-react-native-host/SKILL.md +438 -0
- package/dist/skills/myop-vue-host/SKILL.md +374 -0
- package/package.json +1 -1
|
@@ -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
|
+
```
|