@myop/cli 0.1.45 → 0.1.46
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 +1411 -1184
- package/dist/skills/myop-angular-host/SKILL.md +320 -0
- package/dist/skills/myop-component/SKILL.md +73 -6
- package/dist/skills/myop-component/references/dev-workflow.md +71 -6
- package/dist/skills/myop-react-host/SKILL.md +265 -0
- package/dist/skills/myop-react-host/references/auto-generated-packages.md +70 -0
- package/dist/skills/myop-react-native-host/SKILL.md +320 -0
- package/dist/skills/myop-vue-host/SKILL.md +263 -0
- package/package.json +1 -1
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: myop-react-host
|
|
3
|
+
description: "Integrate Myop components into React applications using @myop/react. This skill covers MyopComponent props, typed event handlers, data binding, preloading, auto-generated packages, and local dev setup. Activate when the user is building a React app that hosts Myop components, or when you see @myop/react in package.json."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Myop React Host Integration
|
|
7
|
+
|
|
8
|
+
Embed Myop components in React applications using `@myop/react`.
|
|
9
|
+
|
|
10
|
+
## When This Skill Activates
|
|
11
|
+
|
|
12
|
+
- `@myop/react` is in `package.json` dependencies
|
|
13
|
+
- User asks to "add a Myop component to React", "integrate Myop", "use MyopComponent"
|
|
14
|
+
- Files import from `@myop/react`
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install @myop/react @myop/sdk
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
23
|
+
|
|
24
|
+
### Option 1: Auto-Generated Package (Recommended)
|
|
25
|
+
|
|
26
|
+
Every Myop component has an auto-generated, fully typed React package:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm install https://cloud.myop.dev/npm/{componentId}/react
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
import { MyComponent } from "@myop/my-component";
|
|
34
|
+
|
|
35
|
+
function App() {
|
|
36
|
+
return (
|
|
37
|
+
<MyComponent
|
|
38
|
+
data={{ title: "Hello", items: ["a", "b"] }}
|
|
39
|
+
onItemSelected={(payload) => {
|
|
40
|
+
// payload is typed: { itemId: string }
|
|
41
|
+
console.log(payload.itemId);
|
|
42
|
+
}}
|
|
43
|
+
/>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**Why auto-generated packages:**
|
|
49
|
+
- Full TypeScript types for `data` and all CTA events — auto-generated from the component's `<script type="myop/types">` block
|
|
50
|
+
- No `componentId` prop needed — baked into the package
|
|
51
|
+
- Tiny bundle impact (~6KB for `@myop/react`, component loads at runtime)
|
|
52
|
+
|
|
53
|
+
### Option 2: MyopComponent Directly
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
import { MyopComponent } from "@myop/react";
|
|
57
|
+
|
|
58
|
+
function App() {
|
|
59
|
+
return (
|
|
60
|
+
<MyopComponent
|
|
61
|
+
componentId="your-component-id"
|
|
62
|
+
data={{ title: "Hello" }}
|
|
63
|
+
on={(action, payload) => {
|
|
64
|
+
console.log(action, payload);
|
|
65
|
+
}}
|
|
66
|
+
/>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## MyopComponent Props
|
|
72
|
+
|
|
73
|
+
| Prop | Type | Default | Description |
|
|
74
|
+
|------|------|---------|-------------|
|
|
75
|
+
| `componentId` | `string` | — | Myop component ID (UUID) |
|
|
76
|
+
| `data` | `TData` | — | Data passed to `myop_init_interface`. Reactive — updates trigger re-render in component |
|
|
77
|
+
| `on` | `(action, payload) => void` | — | Generic handler for all CTA events |
|
|
78
|
+
| `on[ActionName]` | `(payload) => void` | — | Typed handler for specific CTA (e.g., `onItemSelected`) |
|
|
79
|
+
| `onLoad` | `(component) => void` | — | Called when component finishes loading |
|
|
80
|
+
| `onError` | `(error: string) => void` | — | Called on load failure |
|
|
81
|
+
| `onSizeChange` | `(size) => boolean \| void` | — | Called when auto-sized component changes dimensions. Return `false` to reject |
|
|
82
|
+
| `style` | `CSSProperties` | — | CSS styles for the outer container |
|
|
83
|
+
| `loader` | `ReactNode` | — | Custom loading indicator (shown while component loads) |
|
|
84
|
+
| `fallback` | `ReactNode` | — | Custom error fallback UI |
|
|
85
|
+
| `fadeDuration` | `number` | `200` | Loader fade-out duration in ms |
|
|
86
|
+
| `autoSize` | `boolean` | `false` | Auto-size container to match iframe content |
|
|
87
|
+
| `environment` | `string` | — | Load from specific environment (e.g., `"staging"`) |
|
|
88
|
+
| `preview` | `boolean` | `false` | Load unpublished preview version |
|
|
89
|
+
|
|
90
|
+
## Typed Event Handlers
|
|
91
|
+
|
|
92
|
+
CTA actions from the component are converted to `on[PascalCase]` props:
|
|
93
|
+
|
|
94
|
+
| Component CTA action | React prop |
|
|
95
|
+
|----------------------|------------|
|
|
96
|
+
| `'item-selected'` | `onItemSelected` |
|
|
97
|
+
| `'form-submitted'` | `onFormSubmitted` |
|
|
98
|
+
| `'row-clicked'` | `onRowClicked` |
|
|
99
|
+
|
|
100
|
+
```tsx
|
|
101
|
+
// Both work — use typed handlers for type safety, `on` for catch-all
|
|
102
|
+
<MyopComponent<MyData, MyCtaPayloads>
|
|
103
|
+
componentId="..."
|
|
104
|
+
data={data}
|
|
105
|
+
onItemSelected={(payload) => {
|
|
106
|
+
// payload typed as MyCtaPayloads['item-selected']
|
|
107
|
+
}}
|
|
108
|
+
on={(action, payload) => {
|
|
109
|
+
// Generic catch-all
|
|
110
|
+
}}
|
|
111
|
+
/>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Data Binding
|
|
115
|
+
|
|
116
|
+
The `data` prop is reactive. When it changes, `myop_init_interface(data)` is called automatically:
|
|
117
|
+
|
|
118
|
+
```tsx
|
|
119
|
+
function App() {
|
|
120
|
+
const [count, setCount] = useState(0);
|
|
121
|
+
|
|
122
|
+
return (
|
|
123
|
+
<>
|
|
124
|
+
<button onClick={() => setCount(c => c + 1)}>+1</button>
|
|
125
|
+
<MyopComponent
|
|
126
|
+
componentId="counter-display"
|
|
127
|
+
data={{ count }} // Component updates when count changes
|
|
128
|
+
/>
|
|
129
|
+
</>
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Preloading
|
|
135
|
+
|
|
136
|
+
Preload components to eliminate loading delay when they mount:
|
|
137
|
+
|
|
138
|
+
```tsx
|
|
139
|
+
import { preloadComponents, isPreloaded } from "@myop/react";
|
|
140
|
+
|
|
141
|
+
// Preload on app startup or route entry
|
|
142
|
+
await preloadComponents(["component-id-1", "component-id-2"]);
|
|
143
|
+
|
|
144
|
+
// Optionally preload for a specific environment
|
|
145
|
+
await preloadComponents(["component-id-1"], "staging");
|
|
146
|
+
|
|
147
|
+
// Check if a component is cached
|
|
148
|
+
if (isPreloaded("component-id-1")) {
|
|
149
|
+
console.log("Will render instantly");
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
**When to preload:**
|
|
154
|
+
- App startup — preload components on the landing page
|
|
155
|
+
- Route transitions — preload components for the next page
|
|
156
|
+
- Tab containers — preload all tab contents when container mounts
|
|
157
|
+
|
|
158
|
+
## Configuration Functions
|
|
159
|
+
|
|
160
|
+
```tsx
|
|
161
|
+
import {
|
|
162
|
+
enableLocalDev,
|
|
163
|
+
setCloudRepositoryUrl,
|
|
164
|
+
setEnvironment,
|
|
165
|
+
} from "@myop/react";
|
|
166
|
+
|
|
167
|
+
// Point to local dev server (myop dev on port 9292)
|
|
168
|
+
enableLocalDev();
|
|
169
|
+
|
|
170
|
+
// Custom cloud URL
|
|
171
|
+
setCloudRepositoryUrl("https://custom.myop.dev");
|
|
172
|
+
|
|
173
|
+
// Set default environment for all components
|
|
174
|
+
setEnvironment("staging");
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Accessing the Component Instance
|
|
178
|
+
|
|
179
|
+
Use `onLoad` to get direct access to the loaded component:
|
|
180
|
+
|
|
181
|
+
```tsx
|
|
182
|
+
<MyopComponent
|
|
183
|
+
componentId="..."
|
|
184
|
+
onLoad={(component) => {
|
|
185
|
+
// component.props.myop_init_interface(data) — update data
|
|
186
|
+
// component.props.myop_cta_handler — read current handler
|
|
187
|
+
// component.dispose() — cleanup
|
|
188
|
+
// component.hide() / component.show()
|
|
189
|
+
}}
|
|
190
|
+
/>
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Auto-Size Mode
|
|
194
|
+
|
|
195
|
+
When `autoSize={true}`, the container dimensions follow the iframe content:
|
|
196
|
+
|
|
197
|
+
```tsx
|
|
198
|
+
<MyopComponent
|
|
199
|
+
componentId="..."
|
|
200
|
+
autoSize={true}
|
|
201
|
+
onSizeChange={(size) => {
|
|
202
|
+
console.log(size.width, size.height);
|
|
203
|
+
// Return false to reject the size change
|
|
204
|
+
}}
|
|
205
|
+
style={{ maxWidth: 600, maxHeight: 400 }}
|
|
206
|
+
/>
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## Complete Example
|
|
210
|
+
|
|
211
|
+
```tsx
|
|
212
|
+
import { MyopComponent, preloadComponents } from "@myop/react";
|
|
213
|
+
import { useEffect, useState } from "react";
|
|
214
|
+
|
|
215
|
+
// Preload on module load
|
|
216
|
+
preloadComponents(["sidebar-abc123", "chart-def456"]);
|
|
217
|
+
|
|
218
|
+
function Dashboard() {
|
|
219
|
+
const [tasks, setTasks] = useState([
|
|
220
|
+
{ id: "1", title: "Review PR", completed: false },
|
|
221
|
+
{ id: "2", title: "Deploy", completed: true },
|
|
222
|
+
]);
|
|
223
|
+
|
|
224
|
+
return (
|
|
225
|
+
<div style={{ display: "flex", height: "100vh" }}>
|
|
226
|
+
<MyopComponent
|
|
227
|
+
componentId="sidebar-abc123"
|
|
228
|
+
data={{ tasks }}
|
|
229
|
+
onTaskToggled={({ taskId, completed }) => {
|
|
230
|
+
setTasks(prev =>
|
|
231
|
+
prev.map(t => t.id === taskId ? { ...t, completed } : t)
|
|
232
|
+
);
|
|
233
|
+
}}
|
|
234
|
+
onTaskDeleted={({ taskId }) => {
|
|
235
|
+
setTasks(prev => prev.filter(t => t.id !== taskId));
|
|
236
|
+
}}
|
|
237
|
+
loader={<div>Loading sidebar...</div>}
|
|
238
|
+
fallback={<div>Failed to load sidebar</div>}
|
|
239
|
+
style={{ width: 300, height: "100%" }}
|
|
240
|
+
/>
|
|
241
|
+
<MyopComponent
|
|
242
|
+
componentId="chart-def456"
|
|
243
|
+
data={{ tasks }}
|
|
244
|
+
style={{ flex: 1 }}
|
|
245
|
+
/>
|
|
246
|
+
</div>
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## TypeScript Types
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
import type {
|
|
255
|
+
IPropTypes, // Full props type (base + event handlers)
|
|
256
|
+
ITypedMyopComponent, // Component instance with typed props
|
|
257
|
+
IMyopComponentProps, // Props interface (myop_init_interface + myop_cta_handler)
|
|
258
|
+
EventHandlerProps, // Generated on[Action] props type
|
|
259
|
+
KebabToPascal, // Utility: 'item-selected' -> 'ItemSelected'
|
|
260
|
+
} from "@myop/react";
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
## Reference
|
|
264
|
+
|
|
265
|
+
- [Auto-Generated Packages](references/auto-generated-packages.md)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Auto-Generated Packages
|
|
2
|
+
|
|
3
|
+
Myop auto-generates a fully typed npm package for every component, for each framework.
|
|
4
|
+
|
|
5
|
+
## Install URL Pattern
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
https://cloud.myop.dev/npm/{componentId}/react
|
|
9
|
+
https://cloud.myop.dev/npm/{componentId}/vue
|
|
10
|
+
https://cloud.myop.dev/npm/{componentId}/angular
|
|
11
|
+
https://cloud.myop.dev/npm/{componentId}/react-native
|
|
12
|
+
https://cloud.myop.dev/npm/{componentId}/typescript
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## How It Works
|
|
16
|
+
|
|
17
|
+
1. The server reads the component's `<script type="myop/types">` block
|
|
18
|
+
2. Extracts `MyopInitData` and `MyopCtaPayloads` interfaces
|
|
19
|
+
3. Generates a typed wrapper component with the `componentId` baked in
|
|
20
|
+
4. Returns a `.tgz` package installable via `npm install <url>`
|
|
21
|
+
|
|
22
|
+
## What Gets Generated (React)
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
package/
|
|
26
|
+
package.json # @myop/{component-name}, peerDeps: react, @myop/react
|
|
27
|
+
index.tsx # forwardRef component wrapping MyopComponent
|
|
28
|
+
index.d.ts # TypeScript declarations
|
|
29
|
+
index.js # Compiled CommonJS
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Generated Component Code
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
import { MyopComponent, IPropTypes } from '@myop/react';
|
|
36
|
+
import { forwardRef } from 'react';
|
|
37
|
+
|
|
38
|
+
// Types extracted from the component's <script type="myop/types">
|
|
39
|
+
export interface MyopInitData { /* ... */ }
|
|
40
|
+
export interface MyopCtaPayloads { /* ... */ }
|
|
41
|
+
|
|
42
|
+
export type MyComponentProps = Omit<IPropTypes<MyopInitData, MyopCtaPayloads>, 'componentId'>;
|
|
43
|
+
|
|
44
|
+
export const MyComponent = forwardRef<any, MyComponentProps>((props, ref) => (
|
|
45
|
+
<MyopComponent<MyopInitData, MyopCtaPayloads>
|
|
46
|
+
{...props}
|
|
47
|
+
componentId="baked-in-uuid"
|
|
48
|
+
ref={ref}
|
|
49
|
+
/>
|
|
50
|
+
));
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### What You Get
|
|
54
|
+
|
|
55
|
+
- `data` prop is typed as `MyopInitData`
|
|
56
|
+
- Individual CTA handlers are typed (e.g., `onItemSelected: (payload: { itemId: string }) => void`)
|
|
57
|
+
- No need to pass `componentId` — it's baked in
|
|
58
|
+
- Full IntelliSense / autocomplete in IDEs
|
|
59
|
+
|
|
60
|
+
## Info Endpoint
|
|
61
|
+
|
|
62
|
+
Get install instructions and component info:
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
https://cloud.myop.dev/npm/{componentId}/react/info
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Updating
|
|
69
|
+
|
|
70
|
+
Re-run `npm install <url>` to get the latest types after updating the component's type definitions.
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: myop-react-native-host
|
|
3
|
+
description: "Integrate Myop components into React Native applications using @myop/react-native. This skill 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
|
+
## When This Skill Activates
|
|
11
|
+
|
|
12
|
+
- `@myop/react-native` is in `package.json` dependencies
|
|
13
|
+
- User asks to "add a Myop component to React Native", "integrate Myop in mobile"
|
|
14
|
+
- Files import from `@myop/react-native`
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install @myop/react-native react-native-webview
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Peer dependencies: `react >= 16.8.0`, `react-native >= 0.60.0`, `react-native-webview >= 11.0.0`.
|
|
23
|
+
|
|
24
|
+
For Expo projects:
|
|
25
|
+
```bash
|
|
26
|
+
npx expo install react-native-webview
|
|
27
|
+
npm install @myop/react-native
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Quick Start
|
|
31
|
+
|
|
32
|
+
### Option 1: Auto-Generated Package (Recommended)
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npm install https://cloud.myop.dev/npm/{componentId}/react-native
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
```tsx
|
|
39
|
+
import { MyComponent } from "@myop/my-component";
|
|
40
|
+
|
|
41
|
+
function App() {
|
|
42
|
+
return (
|
|
43
|
+
<MyComponent
|
|
44
|
+
data={{ title: "Hello", items: ["a", "b"] }}
|
|
45
|
+
onItemSelected={(payload) => {
|
|
46
|
+
console.log(payload.itemId);
|
|
47
|
+
}}
|
|
48
|
+
style={{ flex: 1 }}
|
|
49
|
+
/>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The auto-generated package bakes in the `componentId` and exports typed interfaces.
|
|
55
|
+
|
|
56
|
+
### Option 2: MyopComponent Directly
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
import { MyopComponent } from "@myop/react-native";
|
|
60
|
+
|
|
61
|
+
function App() {
|
|
62
|
+
return (
|
|
63
|
+
<MyopComponent
|
|
64
|
+
componentId="your-component-id"
|
|
65
|
+
data={{ title: "Hello" }}
|
|
66
|
+
on={(action, payload) => {
|
|
67
|
+
console.log(action, payload);
|
|
68
|
+
}}
|
|
69
|
+
style={{ flex: 1 }}
|
|
70
|
+
/>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## MyopComponent Props
|
|
76
|
+
|
|
77
|
+
| Prop | Type | Default | Description |
|
|
78
|
+
|------|------|---------|-------------|
|
|
79
|
+
| `componentId` | `string` | — | Myop component ID (UUID) |
|
|
80
|
+
| `data` | `any` | — | Data passed to `myop_init_interface`. Reactive — updates trigger re-render |
|
|
81
|
+
| `on` | `(action, payload) => void` | — | Generic handler for all CTA events |
|
|
82
|
+
| `onLoad` | `(component: IMyopComponentProxy) => void` | — | Called when component finishes loading |
|
|
83
|
+
| `onError` | `(error: string) => void` | — | Called on load failure |
|
|
84
|
+
| `style` | `StyleProp<ViewStyle>` | `{width:'100%', height:'100%'}` | Style for the outer View container |
|
|
85
|
+
| `loader` | `ReactNode` | — | Custom loading indicator |
|
|
86
|
+
| `fallback` | `ReactNode` | — | Custom error fallback UI |
|
|
87
|
+
| `fadeDuration` | `number` | `200` | Loader fade-out duration in ms |
|
|
88
|
+
| `environment` | `string` | — | Load from specific environment |
|
|
89
|
+
| `preview` | `boolean` | `false` | Load unpublished preview version |
|
|
90
|
+
| `scrollEnabled` | `boolean` | `false` | Allow scrolling inside the WebView |
|
|
91
|
+
| `zoomEnabled` | `boolean` | `false` | Allow pinch-to-zoom |
|
|
92
|
+
| `selectionEnabled` | `boolean` | `false` | Allow text selection (disabled for native feel) |
|
|
93
|
+
| `componentConfig` | `IComponentInstanceConfig` | — | Direct config object (advanced) |
|
|
94
|
+
|
|
95
|
+
### Native-Specific Props
|
|
96
|
+
|
|
97
|
+
The `scrollEnabled`, `zoomEnabled`, and `selectionEnabled` props are unique to React Native:
|
|
98
|
+
|
|
99
|
+
```tsx
|
|
100
|
+
<MyopComponent
|
|
101
|
+
componentId="article-viewer"
|
|
102
|
+
data={{ article }}
|
|
103
|
+
scrollEnabled={true} // Allow scrolling for long content
|
|
104
|
+
zoomEnabled={false} // Disable pinch zoom
|
|
105
|
+
selectionEnabled={false} // Disable text selection for native feel
|
|
106
|
+
style={{ flex: 1 }}
|
|
107
|
+
/>
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
When `selectionEnabled={false}` (default), CSS rules are injected to prevent text selection and touch callout, with exceptions for `<input>`, `<textarea>`, and `[contenteditable]` elements.
|
|
111
|
+
|
|
112
|
+
## CTA Event Handling
|
|
113
|
+
|
|
114
|
+
CTA events from the component are received via `postMessage` bridge and dispatched to the `on` callback:
|
|
115
|
+
|
|
116
|
+
```tsx
|
|
117
|
+
<MyopComponent
|
|
118
|
+
componentId="task-list"
|
|
119
|
+
data={{ tasks }}
|
|
120
|
+
on={(action, payload) => {
|
|
121
|
+
switch (action) {
|
|
122
|
+
case 'task-toggled':
|
|
123
|
+
console.log(payload.taskId, payload.completed);
|
|
124
|
+
break;
|
|
125
|
+
case 'task-deleted':
|
|
126
|
+
console.log(payload.taskId);
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
}}
|
|
130
|
+
/>
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Data Binding
|
|
134
|
+
|
|
135
|
+
The `data` prop is reactive. When it changes (compared by JSON serialization to avoid redundant calls), `myop_init_interface(data)` is called automatically:
|
|
136
|
+
|
|
137
|
+
```tsx
|
|
138
|
+
function Counter() {
|
|
139
|
+
const [count, setCount] = useState(0);
|
|
140
|
+
|
|
141
|
+
return (
|
|
142
|
+
<View>
|
|
143
|
+
<Button title="+1" onPress={() => setCount(c => c + 1)} />
|
|
144
|
+
<MyopComponent
|
|
145
|
+
componentId="counter-display"
|
|
146
|
+
data={{ count }}
|
|
147
|
+
style={{ height: 200 }}
|
|
148
|
+
/>
|
|
149
|
+
</View>
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
**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.
|
|
155
|
+
|
|
156
|
+
## Component Instance (IMyopComponentProxy)
|
|
157
|
+
|
|
158
|
+
The `onLoad` callback receives a proxy object for controlling the component:
|
|
159
|
+
|
|
160
|
+
```tsx
|
|
161
|
+
<MyopComponent
|
|
162
|
+
componentId="..."
|
|
163
|
+
onLoad={(component) => {
|
|
164
|
+
// component.id — the component ID
|
|
165
|
+
// component.props.myop_init_interface(data) — update data
|
|
166
|
+
// component.element.style.set('background', 'red') — style DOM
|
|
167
|
+
// component.element.style.get('background') — returns Promise
|
|
168
|
+
// component.element.set('innerHTML', '<b>hi</b>') — set DOM property
|
|
169
|
+
// component.element.get('scrollHeight') — returns Promise
|
|
170
|
+
// component.dispose() — cleanup
|
|
171
|
+
// component.hide() / component.show()
|
|
172
|
+
}}
|
|
173
|
+
/>
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
The proxy communicates with the WebView via `injectJavaScript`. Element `get()` operations are asynchronous (return Promises) because they cross the native-WebView bridge.
|
|
177
|
+
|
|
178
|
+
## Preloading
|
|
179
|
+
|
|
180
|
+
```tsx
|
|
181
|
+
import { preloadComponents, isPreloaded } from "@myop/react-native";
|
|
182
|
+
|
|
183
|
+
// Preload on app startup or before navigating to a screen
|
|
184
|
+
await preloadComponents(["component-id-1", "component-id-2"]);
|
|
185
|
+
await preloadComponents(["component-id-1"], "staging");
|
|
186
|
+
|
|
187
|
+
if (isPreloaded("component-id-1")) {
|
|
188
|
+
console.log("Will render instantly — no loader shown");
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
When a component is preloaded, the loader is not shown at all.
|
|
193
|
+
|
|
194
|
+
## Configuration Functions
|
|
195
|
+
|
|
196
|
+
```tsx
|
|
197
|
+
import {
|
|
198
|
+
enableLocalDev,
|
|
199
|
+
setCloudRepositoryUrl,
|
|
200
|
+
setEnvironment,
|
|
201
|
+
} from "@myop/react-native";
|
|
202
|
+
|
|
203
|
+
enableLocalDev(); // localhost:9292
|
|
204
|
+
setCloudRepositoryUrl("https://custom"); // Custom URL
|
|
205
|
+
setEnvironment("staging"); // Default environment
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
**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:
|
|
209
|
+
|
|
210
|
+
```tsx
|
|
211
|
+
import { setCloudRepositoryUrl } from "@myop/react-native";
|
|
212
|
+
|
|
213
|
+
// Android emulator
|
|
214
|
+
setCloudRepositoryUrl("http://10.0.2.2:9292");
|
|
215
|
+
|
|
216
|
+
// Physical device (replace with your machine's IP)
|
|
217
|
+
setCloudRepositoryUrl("http://192.168.1.100:9292");
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## Custom Loader
|
|
221
|
+
|
|
222
|
+
Pass a React Native component as the `loader` prop. The loader supports a fade-out animation via `MyopLoader`:
|
|
223
|
+
|
|
224
|
+
```tsx
|
|
225
|
+
import { MyopComponent, MyopLoader } from "@myop/react-native";
|
|
226
|
+
|
|
227
|
+
<MyopComponent
|
|
228
|
+
componentId="..."
|
|
229
|
+
loader={
|
|
230
|
+
<MyopLoader>
|
|
231
|
+
<ActivityIndicator size="large" color="#007AFF" />
|
|
232
|
+
<Text>Loading component...</Text>
|
|
233
|
+
</MyopLoader>
|
|
234
|
+
}
|
|
235
|
+
fallback={<Text>Failed to load component</Text>}
|
|
236
|
+
fadeDuration={300}
|
|
237
|
+
/>
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
## Complete Example
|
|
241
|
+
|
|
242
|
+
```tsx
|
|
243
|
+
import React, { useState } from 'react';
|
|
244
|
+
import { View, Button, Text, StyleSheet } from 'react-native';
|
|
245
|
+
import { MyopComponent, preloadComponents } from '@myop/react-native';
|
|
246
|
+
import type { IMyopComponentProxy } from '@myop/react-native';
|
|
247
|
+
|
|
248
|
+
// Preload on module load
|
|
249
|
+
preloadComponents(['task-list-abc123']);
|
|
250
|
+
|
|
251
|
+
function TaskScreen() {
|
|
252
|
+
const [tasks, setTasks] = useState([
|
|
253
|
+
{ id: '1', title: 'Review PR', completed: false },
|
|
254
|
+
{ id: '2', title: 'Deploy', completed: true },
|
|
255
|
+
]);
|
|
256
|
+
|
|
257
|
+
return (
|
|
258
|
+
<View style={styles.container}>
|
|
259
|
+
<Text style={styles.title}>My Tasks</Text>
|
|
260
|
+
<MyopComponent
|
|
261
|
+
componentId="task-list-abc123"
|
|
262
|
+
data={{ tasks }}
|
|
263
|
+
on={(action, payload) => {
|
|
264
|
+
if (action === 'task-toggled') {
|
|
265
|
+
setTasks(prev =>
|
|
266
|
+
prev.map(t =>
|
|
267
|
+
t.id === payload.taskId
|
|
268
|
+
? { ...t, completed: payload.completed }
|
|
269
|
+
: t
|
|
270
|
+
)
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
if (action === 'task-deleted') {
|
|
274
|
+
setTasks(prev => prev.filter(t => t.id !== payload.taskId));
|
|
275
|
+
}
|
|
276
|
+
}}
|
|
277
|
+
onLoad={(component) => {
|
|
278
|
+
console.log('Component loaded:', component.id);
|
|
279
|
+
}}
|
|
280
|
+
onError={(error) => {
|
|
281
|
+
console.error('Failed:', error);
|
|
282
|
+
}}
|
|
283
|
+
scrollEnabled={true}
|
|
284
|
+
selectionEnabled={false}
|
|
285
|
+
style={styles.component}
|
|
286
|
+
/>
|
|
287
|
+
</View>
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const styles = StyleSheet.create({
|
|
292
|
+
container: { flex: 1 },
|
|
293
|
+
title: { fontSize: 24, padding: 16 },
|
|
294
|
+
component: { flex: 1 },
|
|
295
|
+
});
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
## Auto-Generated React Native Package
|
|
299
|
+
|
|
300
|
+
```bash
|
|
301
|
+
npm install https://cloud.myop.dev/npm/{componentId}/react-native
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
The generated package exports:
|
|
305
|
+
- Named component with `componentId` baked in (no `componentId` prop needed)
|
|
306
|
+
- Typed `data` prop from `MyopInitData`
|
|
307
|
+
- Typed `on[ActionName]` handlers from `MyopCtaPayloads`
|
|
308
|
+
- `COMPONENT_ID` constant
|
|
309
|
+
|
|
310
|
+
## TypeScript Types
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
import type {
|
|
314
|
+
IMyopComponentProxy, // Component instance proxy (onLoad callback)
|
|
315
|
+
IComponentInstanceConfig, // Direct config object type
|
|
316
|
+
} from "@myop/react-native";
|
|
317
|
+
|
|
318
|
+
import { MyopLoader } from "@myop/react-native";
|
|
319
|
+
import type { MyopLoaderRef } from "@myop/react-native";
|
|
320
|
+
```
|