@servlyadmin/runtime-react 0.1.7 → 0.1.9
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/README.md +292 -0
- package/dist/index.js +129 -60
- package/dist/index.mjs +259 -0
- package/package.json +6 -9
- package/dist/index.cjs +0 -235
- package/dist/index.d.cts +0 -44
- package/dist/index.d.ts +0 -44
package/README.md
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
# @servlyadmin/runtime-react
|
|
2
|
+
|
|
3
|
+
React wrapper for Servly runtime renderer. Render Servly components in your React applications with full support for props, slots, and event handling.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @servlyadmin/runtime-react @servlyadmin/runtime-core
|
|
9
|
+
# or
|
|
10
|
+
yarn add @servlyadmin/runtime-react @servlyadmin/runtime-core
|
|
11
|
+
# or
|
|
12
|
+
pnpm add @servlyadmin/runtime-react @servlyadmin/runtime-core
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
import { ServlyComponent } from '@servlyadmin/runtime-react';
|
|
19
|
+
|
|
20
|
+
function App() {
|
|
21
|
+
return (
|
|
22
|
+
<ServlyComponent
|
|
23
|
+
id="my-component"
|
|
24
|
+
version="latest"
|
|
25
|
+
props={{ title: 'Hello World' }}
|
|
26
|
+
/>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Props
|
|
32
|
+
|
|
33
|
+
| Prop | Type | Default | Description |
|
|
34
|
+
|------|------|---------|-------------|
|
|
35
|
+
| `id` | `string` | required | Component ID from the registry |
|
|
36
|
+
| `version` | `string` | `'latest'` | Version specifier |
|
|
37
|
+
| `props` | `object` | `{}` | Props to pass to the component |
|
|
38
|
+
| `slots` | `Record<string, ReactNode>` | - | Slot content |
|
|
39
|
+
| `fallback` | `ReactNode` | - | Loading/error fallback |
|
|
40
|
+
| `onError` | `(error: Error) => void` | - | Error callback |
|
|
41
|
+
| `onLoad` | `() => void` | - | Load complete callback |
|
|
42
|
+
| `className` | `string` | - | Wrapper class name |
|
|
43
|
+
| `style` | `CSSProperties` | - | Wrapper styles |
|
|
44
|
+
| `showSkeleton` | `boolean` | `true` | Show loading skeleton |
|
|
45
|
+
| `cacheStrategy` | `CacheStrategy` | `'memory'` | Cache strategy |
|
|
46
|
+
| `eventHandlers` | `object` | - | Event handlers by element ID |
|
|
47
|
+
| `children` | `ReactNode` | - | Default slot content |
|
|
48
|
+
|
|
49
|
+
## Usage Examples
|
|
50
|
+
|
|
51
|
+
### Basic Usage
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
import { ServlyComponent } from '@servlyadmin/runtime-react';
|
|
55
|
+
|
|
56
|
+
function MyPage() {
|
|
57
|
+
return (
|
|
58
|
+
<ServlyComponent
|
|
59
|
+
id="hero-section"
|
|
60
|
+
props={{
|
|
61
|
+
title: 'Welcome',
|
|
62
|
+
subtitle: 'Get started today',
|
|
63
|
+
}}
|
|
64
|
+
/>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### With Slots
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
function CardExample() {
|
|
73
|
+
return (
|
|
74
|
+
<ServlyComponent
|
|
75
|
+
id="card-component"
|
|
76
|
+
slots={{
|
|
77
|
+
header: <h2>Card Title</h2>,
|
|
78
|
+
footer: <button>Learn More</button>,
|
|
79
|
+
}}
|
|
80
|
+
>
|
|
81
|
+
{/* Children go to default slot */}
|
|
82
|
+
<p>This is the card content.</p>
|
|
83
|
+
</ServlyComponent>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### With Event Handlers
|
|
89
|
+
|
|
90
|
+
```tsx
|
|
91
|
+
function InteractiveExample() {
|
|
92
|
+
const [count, setCount] = useState(0);
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<ServlyComponent
|
|
96
|
+
id="counter-component"
|
|
97
|
+
props={{ count }}
|
|
98
|
+
eventHandlers={{
|
|
99
|
+
'increment-btn': {
|
|
100
|
+
click: () => setCount(c => c + 1),
|
|
101
|
+
},
|
|
102
|
+
'decrement-btn': {
|
|
103
|
+
click: () => setCount(c => c - 1),
|
|
104
|
+
},
|
|
105
|
+
}}
|
|
106
|
+
/>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Loading States
|
|
112
|
+
|
|
113
|
+
```tsx
|
|
114
|
+
function WithLoadingState() {
|
|
115
|
+
return (
|
|
116
|
+
<ServlyComponent
|
|
117
|
+
id="data-component"
|
|
118
|
+
fallback={<div>Loading...</div>}
|
|
119
|
+
onLoad={() => console.log('Component loaded!')}
|
|
120
|
+
onError={(error) => console.error('Failed to load:', error)}
|
|
121
|
+
/>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Custom Loading Skeleton
|
|
127
|
+
|
|
128
|
+
```tsx
|
|
129
|
+
function WithCustomSkeleton() {
|
|
130
|
+
return (
|
|
131
|
+
<ServlyComponent
|
|
132
|
+
id="profile-card"
|
|
133
|
+
showSkeleton={false}
|
|
134
|
+
fallback={
|
|
135
|
+
<div className="animate-pulse">
|
|
136
|
+
<div className="h-32 bg-gray-200 rounded" />
|
|
137
|
+
<div className="h-4 bg-gray-200 rounded mt-4 w-3/4" />
|
|
138
|
+
</div>
|
|
139
|
+
}
|
|
140
|
+
/>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Version Pinning
|
|
146
|
+
|
|
147
|
+
```tsx
|
|
148
|
+
function VersionedComponent() {
|
|
149
|
+
return (
|
|
150
|
+
<>
|
|
151
|
+
{/* Exact version */}
|
|
152
|
+
<ServlyComponent id="my-component" version="1.2.3" />
|
|
153
|
+
|
|
154
|
+
{/* Version range */}
|
|
155
|
+
<ServlyComponent id="my-component" version="^1.0.0" />
|
|
156
|
+
|
|
157
|
+
{/* Latest */}
|
|
158
|
+
<ServlyComponent id="my-component" version="latest" />
|
|
159
|
+
</>
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Cache Control
|
|
165
|
+
|
|
166
|
+
```tsx
|
|
167
|
+
function CacheExample() {
|
|
168
|
+
return (
|
|
169
|
+
<>
|
|
170
|
+
{/* Memory cache (default) */}
|
|
171
|
+
<ServlyComponent id="comp1" cacheStrategy="memory" />
|
|
172
|
+
|
|
173
|
+
{/* Persist to localStorage */}
|
|
174
|
+
<ServlyComponent id="comp2" cacheStrategy="localStorage" />
|
|
175
|
+
|
|
176
|
+
{/* No caching */}
|
|
177
|
+
<ServlyComponent id="comp3" cacheStrategy="none" />
|
|
178
|
+
</>
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Dynamic Props
|
|
184
|
+
|
|
185
|
+
```tsx
|
|
186
|
+
function DynamicPropsExample() {
|
|
187
|
+
const [theme, setTheme] = useState('light');
|
|
188
|
+
const [user, setUser] = useState({ name: 'Guest' });
|
|
189
|
+
|
|
190
|
+
return (
|
|
191
|
+
<ServlyComponent
|
|
192
|
+
id="themed-component"
|
|
193
|
+
props={{
|
|
194
|
+
theme,
|
|
195
|
+
userName: user.name,
|
|
196
|
+
timestamp: Date.now(),
|
|
197
|
+
}}
|
|
198
|
+
/>
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Error Boundary Integration
|
|
204
|
+
|
|
205
|
+
```tsx
|
|
206
|
+
import { ErrorBoundary } from 'react-error-boundary';
|
|
207
|
+
|
|
208
|
+
function SafeComponent() {
|
|
209
|
+
return (
|
|
210
|
+
<ErrorBoundary fallback={<div>Something went wrong</div>}>
|
|
211
|
+
<ServlyComponent
|
|
212
|
+
id="risky-component"
|
|
213
|
+
onError={(error) => {
|
|
214
|
+
// Log to error tracking service
|
|
215
|
+
logError(error);
|
|
216
|
+
}}
|
|
217
|
+
/>
|
|
218
|
+
</ErrorBoundary>
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## TypeScript
|
|
224
|
+
|
|
225
|
+
Full TypeScript support:
|
|
226
|
+
|
|
227
|
+
```tsx
|
|
228
|
+
import { ServlyComponent, type ServlyComponentProps } from '@servlyadmin/runtime-react';
|
|
229
|
+
|
|
230
|
+
interface MyComponentProps {
|
|
231
|
+
title: string;
|
|
232
|
+
count: number;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function TypedExample() {
|
|
236
|
+
return (
|
|
237
|
+
<ServlyComponent<MyComponentProps>
|
|
238
|
+
id="typed-component"
|
|
239
|
+
props={{
|
|
240
|
+
title: 'Hello',
|
|
241
|
+
count: 42,
|
|
242
|
+
}}
|
|
243
|
+
/>
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
## Server-Side Rendering
|
|
249
|
+
|
|
250
|
+
The component handles SSR gracefully by rendering the fallback on the server and hydrating on the client.
|
|
251
|
+
|
|
252
|
+
```tsx
|
|
253
|
+
// Works with Next.js, Remix, etc.
|
|
254
|
+
function SSRPage() {
|
|
255
|
+
return (
|
|
256
|
+
<ServlyComponent
|
|
257
|
+
id="ssr-component"
|
|
258
|
+
fallback={<div>Loading component...</div>}
|
|
259
|
+
/>
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
## Performance Tips
|
|
265
|
+
|
|
266
|
+
1. **Use version pinning** in production to leverage caching
|
|
267
|
+
2. **Prefetch components** that will be needed soon
|
|
268
|
+
3. **Use `cacheStrategy="localStorage"`** for components that rarely change
|
|
269
|
+
4. **Memoize event handlers** to prevent unnecessary re-renders
|
|
270
|
+
|
|
271
|
+
```tsx
|
|
272
|
+
import { useMemo, useCallback } from 'react';
|
|
273
|
+
|
|
274
|
+
function OptimizedExample() {
|
|
275
|
+
const eventHandlers = useMemo(() => ({
|
|
276
|
+
'btn': {
|
|
277
|
+
click: () => console.log('clicked'),
|
|
278
|
+
},
|
|
279
|
+
}), []);
|
|
280
|
+
|
|
281
|
+
return (
|
|
282
|
+
<ServlyComponent
|
|
283
|
+
id="optimized"
|
|
284
|
+
eventHandlers={eventHandlers}
|
|
285
|
+
/>
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
## License
|
|
291
|
+
|
|
292
|
+
MIT
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,46 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __export = (target, all) => {
|
|
6
|
+
for (var name in all)
|
|
7
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
8
|
+
};
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
18
|
+
|
|
19
|
+
// packages/runtime-react/src/index.ts
|
|
20
|
+
var index_exports = {};
|
|
21
|
+
__export(index_exports, {
|
|
22
|
+
ServlyComponent: () => ServlyComponent,
|
|
23
|
+
clearAllCaches: () => import_runtime_core2.clearAllCaches,
|
|
24
|
+
compareVersions: () => import_runtime_core2.compareVersions,
|
|
25
|
+
default: () => ServlyComponent_default,
|
|
26
|
+
fetchComponent: () => import_runtime_core2.fetchComponent,
|
|
27
|
+
getRegistryUrl: () => import_runtime_core2.getRegistryUrl,
|
|
28
|
+
invalidateCache: () => import_runtime_core2.invalidateCache,
|
|
29
|
+
isComponentAvailable: () => import_runtime_core2.isComponentAvailable,
|
|
30
|
+
parseVersion: () => import_runtime_core2.parseVersion,
|
|
31
|
+
prefetchComponents: () => import_runtime_core2.prefetchComponents,
|
|
32
|
+
resolveVersion: () => import_runtime_core2.resolveVersion,
|
|
33
|
+
satisfiesVersion: () => import_runtime_core2.satisfiesVersion,
|
|
34
|
+
setRegistryUrl: () => import_runtime_core2.setRegistryUrl
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(index_exports);
|
|
37
|
+
|
|
38
|
+
// packages/runtime-react/src/ServlyComponent.tsx
|
|
39
|
+
var import_react = require("react");
|
|
40
|
+
var import_react_dom = require("react-dom");
|
|
41
|
+
var import_runtime_core = require("@servlyadmin/runtime-core");
|
|
42
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
43
|
+
var LoadingSkeleton = ({ className }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
9
44
|
"div",
|
|
10
45
|
{
|
|
11
46
|
className: `servly-skeleton ${className || ""}`,
|
|
@@ -15,7 +50,7 @@ var LoadingSkeleton = ({ className }) => /* @__PURE__ */ jsx(
|
|
|
15
50
|
minHeight: "100px",
|
|
16
51
|
animation: "servly-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite"
|
|
17
52
|
},
|
|
18
|
-
children: /* @__PURE__ */ jsx("style", { children: `
|
|
53
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: `
|
|
19
54
|
@keyframes servly-pulse {
|
|
20
55
|
0%, 100% { opacity: 1; }
|
|
21
56
|
50% { opacity: 0.5; }
|
|
@@ -23,7 +58,7 @@ var LoadingSkeleton = ({ className }) => /* @__PURE__ */ jsx(
|
|
|
23
58
|
` })
|
|
24
59
|
}
|
|
25
60
|
);
|
|
26
|
-
var ErrorDisplay = ({ error, onRetry, className, style }) => /* @__PURE__ */ jsxs(
|
|
61
|
+
var ErrorDisplay = ({ error, onRetry, className, style }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
27
62
|
"div",
|
|
28
63
|
{
|
|
29
64
|
className: `servly-error ${className || ""}`,
|
|
@@ -35,9 +70,9 @@ var ErrorDisplay = ({ error, onRetry, className, style }) => /* @__PURE__ */ jsx
|
|
|
35
70
|
...style
|
|
36
71
|
},
|
|
37
72
|
children: [
|
|
38
|
-
/* @__PURE__ */ jsx("p", { style: { margin: 0, fontWeight: 500 }, children: "Failed to load component" }),
|
|
39
|
-
/* @__PURE__ */ jsx("p", { style: { margin: "8px 0 0", fontSize: "14px", color: "#991b1b" }, children: error.message }),
|
|
40
|
-
onRetry && /* @__PURE__ */ jsx(
|
|
73
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { margin: 0, fontWeight: 500 }, children: "Failed to load component" }),
|
|
74
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { margin: "8px 0 0", fontSize: "14px", color: "#991b1b" }, children: error.message }),
|
|
75
|
+
onRetry && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
41
76
|
"button",
|
|
42
77
|
{
|
|
43
78
|
onClick: onRetry,
|
|
@@ -57,10 +92,30 @@ var ErrorDisplay = ({ error, onRetry, className, style }) => /* @__PURE__ */ jsx
|
|
|
57
92
|
]
|
|
58
93
|
}
|
|
59
94
|
);
|
|
95
|
+
function useSlotElements(containerRef, isRendered) {
|
|
96
|
+
const [slotElements, setSlotElements] = (0, import_react.useState)({});
|
|
97
|
+
(0, import_react.useEffect)(() => {
|
|
98
|
+
if (!isRendered || !containerRef.current) {
|
|
99
|
+
setSlotElements({});
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const found = {};
|
|
103
|
+
const slots = containerRef.current.querySelectorAll("[data-slot]");
|
|
104
|
+
slots.forEach((el) => {
|
|
105
|
+
const slotName = el.getAttribute("data-slot");
|
|
106
|
+
if (slotName) {
|
|
107
|
+
found[slotName] = el;
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
setSlotElements(found);
|
|
111
|
+
}, [isRendered, containerRef]);
|
|
112
|
+
return slotElements;
|
|
113
|
+
}
|
|
60
114
|
function ServlyComponent({
|
|
61
115
|
id,
|
|
62
116
|
version = "latest",
|
|
63
117
|
props = {},
|
|
118
|
+
slots,
|
|
64
119
|
fallback,
|
|
65
120
|
onError,
|
|
66
121
|
onLoad,
|
|
@@ -69,22 +124,33 @@ function ServlyComponent({
|
|
|
69
124
|
showSkeleton = true,
|
|
70
125
|
cacheStrategy = "memory",
|
|
71
126
|
retryConfig,
|
|
72
|
-
eventHandlers
|
|
127
|
+
eventHandlers,
|
|
128
|
+
children
|
|
73
129
|
}) {
|
|
74
|
-
const containerRef = useRef(null);
|
|
75
|
-
const renderResultRef = useRef(null);
|
|
76
|
-
const abortControllerRef = useRef(null);
|
|
77
|
-
const [
|
|
130
|
+
const containerRef = (0, import_react.useRef)(null);
|
|
131
|
+
const renderResultRef = (0, import_react.useRef)(null);
|
|
132
|
+
const abortControllerRef = (0, import_react.useRef)(null);
|
|
133
|
+
const [isRendered, setIsRendered] = (0, import_react.useState)(false);
|
|
134
|
+
const [state, setState] = (0, import_react.useState)({
|
|
78
135
|
loading: true,
|
|
79
136
|
error: null,
|
|
80
137
|
data: null
|
|
81
138
|
});
|
|
82
|
-
const
|
|
139
|
+
const slotElements = useSlotElements(containerRef, isRendered);
|
|
140
|
+
const effectiveSlots = (0, import_react.useMemo)(() => {
|
|
141
|
+
const result = { ...slots };
|
|
142
|
+
if (children && !result.default) {
|
|
143
|
+
result.default = children;
|
|
144
|
+
}
|
|
145
|
+
return result;
|
|
146
|
+
}, [slots, children]);
|
|
147
|
+
const loadComponent = (0, import_react.useCallback)(async () => {
|
|
83
148
|
if (abortControllerRef.current) {
|
|
84
149
|
abortControllerRef.current.abort();
|
|
85
150
|
}
|
|
86
151
|
abortControllerRef.current = new AbortController();
|
|
87
152
|
setState((prev) => ({ ...prev, loading: true, error: null }));
|
|
153
|
+
setIsRendered(false);
|
|
88
154
|
const fetchOptions = {
|
|
89
155
|
version,
|
|
90
156
|
cacheStrategy,
|
|
@@ -92,11 +158,13 @@ function ServlyComponent({
|
|
|
92
158
|
signal: abortControllerRef.current.signal
|
|
93
159
|
};
|
|
94
160
|
try {
|
|
95
|
-
const result = await fetchComponent(id, fetchOptions);
|
|
161
|
+
const result = await (0, import_runtime_core.fetchComponent)(id, fetchOptions);
|
|
96
162
|
setState({
|
|
97
163
|
loading: false,
|
|
98
164
|
error: null,
|
|
99
|
-
data: result.data
|
|
165
|
+
data: result.data,
|
|
166
|
+
views: result.views,
|
|
167
|
+
registry: result.registry
|
|
100
168
|
});
|
|
101
169
|
onLoad?.();
|
|
102
170
|
} catch (error) {
|
|
@@ -105,12 +173,14 @@ function ServlyComponent({
|
|
|
105
173
|
setState({
|
|
106
174
|
loading: false,
|
|
107
175
|
error: err,
|
|
108
|
-
data: null
|
|
176
|
+
data: null,
|
|
177
|
+
views: void 0,
|
|
178
|
+
registry: void 0
|
|
109
179
|
});
|
|
110
180
|
onError?.(err);
|
|
111
181
|
}
|
|
112
182
|
}, [id, version, cacheStrategy, retryConfig, onLoad, onError]);
|
|
113
|
-
useEffect(() => {
|
|
183
|
+
(0, import_react.useEffect)(() => {
|
|
114
184
|
loadComponent();
|
|
115
185
|
return () => {
|
|
116
186
|
if (abortControllerRef.current) {
|
|
@@ -118,7 +188,7 @@ function ServlyComponent({
|
|
|
118
188
|
}
|
|
119
189
|
};
|
|
120
190
|
}, [loadComponent]);
|
|
121
|
-
useEffect(() => {
|
|
191
|
+
(0, import_react.useEffect)(() => {
|
|
122
192
|
if (!state.data || !containerRef.current) return;
|
|
123
193
|
const context = {
|
|
124
194
|
props,
|
|
@@ -129,20 +199,24 @@ function ServlyComponent({
|
|
|
129
199
|
renderResultRef.current.update(context);
|
|
130
200
|
return;
|
|
131
201
|
}
|
|
132
|
-
renderResultRef.current = render({
|
|
202
|
+
renderResultRef.current = (0, import_runtime_core.render)({
|
|
133
203
|
container: containerRef.current,
|
|
134
204
|
elements: state.data.layout,
|
|
135
205
|
context,
|
|
136
|
-
eventHandlers
|
|
206
|
+
eventHandlers,
|
|
207
|
+
views: state.views,
|
|
208
|
+
componentRegistry: state.registry
|
|
137
209
|
});
|
|
210
|
+
setIsRendered(true);
|
|
138
211
|
return () => {
|
|
139
212
|
if (renderResultRef.current) {
|
|
140
213
|
renderResultRef.current.destroy();
|
|
141
214
|
renderResultRef.current = null;
|
|
215
|
+
setIsRendered(false);
|
|
142
216
|
}
|
|
143
217
|
};
|
|
144
|
-
}, [state.data,
|
|
145
|
-
useEffect(() => {
|
|
218
|
+
}, [state.data, eventHandlers]);
|
|
219
|
+
(0, import_react.useEffect)(() => {
|
|
146
220
|
if (!renderResultRef.current || !state.data) return;
|
|
147
221
|
const context = {
|
|
148
222
|
props,
|
|
@@ -152,13 +226,13 @@ function ServlyComponent({
|
|
|
152
226
|
renderResultRef.current.update(context);
|
|
153
227
|
}, [props, state.data]);
|
|
154
228
|
if (state.loading) {
|
|
155
|
-
if (fallback) return /* @__PURE__ */ jsx(Fragment, { children: fallback });
|
|
156
|
-
if (showSkeleton) return /* @__PURE__ */ jsx(LoadingSkeleton, { className });
|
|
229
|
+
if (fallback) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: fallback });
|
|
230
|
+
if (showSkeleton) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LoadingSkeleton, { className });
|
|
157
231
|
return null;
|
|
158
232
|
}
|
|
159
233
|
if (state.error) {
|
|
160
|
-
if (fallback) return /* @__PURE__ */ jsx(Fragment, { children: fallback });
|
|
161
|
-
return /* @__PURE__ */ jsx(
|
|
234
|
+
if (fallback) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: fallback });
|
|
235
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
162
236
|
ErrorDisplay,
|
|
163
237
|
{
|
|
164
238
|
error: state.error,
|
|
@@ -168,39 +242,34 @@ function ServlyComponent({
|
|
|
168
242
|
}
|
|
169
243
|
);
|
|
170
244
|
}
|
|
171
|
-
return /* @__PURE__ */
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
245
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
246
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
247
|
+
"div",
|
|
248
|
+
{
|
|
249
|
+
ref: containerRef,
|
|
250
|
+
className: `servly-component ${className || ""}`,
|
|
251
|
+
style,
|
|
252
|
+
"data-servly-id": id,
|
|
253
|
+
"data-servly-version": state.data?.version
|
|
254
|
+
}
|
|
255
|
+
),
|
|
256
|
+
Object.entries(effectiveSlots).map(([slotName, content]) => {
|
|
257
|
+
const slotEl = slotElements[slotName];
|
|
258
|
+
if (!slotEl || !content) return null;
|
|
259
|
+
return (0, import_react_dom.createPortal)(content, slotEl, `slot-${slotName}`);
|
|
260
|
+
})
|
|
261
|
+
] });
|
|
181
262
|
}
|
|
182
263
|
var ServlyComponent_default = ServlyComponent;
|
|
183
264
|
|
|
184
|
-
// src/index.ts
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
isComponentAvailable,
|
|
189
|
-
setRegistryUrl,
|
|
190
|
-
getRegistryUrl,
|
|
191
|
-
invalidateCache,
|
|
192
|
-
clearAllCaches,
|
|
193
|
-
parseVersion,
|
|
194
|
-
compareVersions,
|
|
195
|
-
satisfiesVersion,
|
|
196
|
-
resolveVersion
|
|
197
|
-
} from "@servlyadmin/runtime-core";
|
|
198
|
-
export {
|
|
265
|
+
// packages/runtime-react/src/index.ts
|
|
266
|
+
var import_runtime_core2 = require("@servlyadmin/runtime-core");
|
|
267
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
268
|
+
0 && (module.exports = {
|
|
199
269
|
ServlyComponent,
|
|
200
270
|
clearAllCaches,
|
|
201
271
|
compareVersions,
|
|
202
|
-
|
|
203
|
-
fetchComponent2 as fetchComponent,
|
|
272
|
+
fetchComponent,
|
|
204
273
|
getRegistryUrl,
|
|
205
274
|
invalidateCache,
|
|
206
275
|
isComponentAvailable,
|
|
@@ -209,4 +278,4 @@ export {
|
|
|
209
278
|
resolveVersion,
|
|
210
279
|
satisfiesVersion,
|
|
211
280
|
setRegistryUrl
|
|
212
|
-
};
|
|
281
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
// packages/runtime-react/src/ServlyComponent.tsx
|
|
2
|
+
import { useRef, useEffect, useState, useCallback, useMemo } from "react";
|
|
3
|
+
import { createPortal } from "react-dom";
|
|
4
|
+
import {
|
|
5
|
+
render,
|
|
6
|
+
fetchComponent
|
|
7
|
+
} from "@servlyadmin/runtime-core";
|
|
8
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
9
|
+
var LoadingSkeleton = ({ className }) => /* @__PURE__ */ jsx(
|
|
10
|
+
"div",
|
|
11
|
+
{
|
|
12
|
+
className: `servly-skeleton ${className || ""}`,
|
|
13
|
+
style: {
|
|
14
|
+
backgroundColor: "#f3f4f6",
|
|
15
|
+
borderRadius: "8px",
|
|
16
|
+
minHeight: "100px",
|
|
17
|
+
animation: "servly-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite"
|
|
18
|
+
},
|
|
19
|
+
children: /* @__PURE__ */ jsx("style", { children: `
|
|
20
|
+
@keyframes servly-pulse {
|
|
21
|
+
0%, 100% { opacity: 1; }
|
|
22
|
+
50% { opacity: 0.5; }
|
|
23
|
+
}
|
|
24
|
+
` })
|
|
25
|
+
}
|
|
26
|
+
);
|
|
27
|
+
var ErrorDisplay = ({ error, onRetry, className, style }) => /* @__PURE__ */ jsxs(
|
|
28
|
+
"div",
|
|
29
|
+
{
|
|
30
|
+
className: `servly-error ${className || ""}`,
|
|
31
|
+
style: {
|
|
32
|
+
padding: "16px",
|
|
33
|
+
color: "#ef4444",
|
|
34
|
+
backgroundColor: "#fef2f2",
|
|
35
|
+
borderRadius: "8px",
|
|
36
|
+
...style
|
|
37
|
+
},
|
|
38
|
+
children: [
|
|
39
|
+
/* @__PURE__ */ jsx("p", { style: { margin: 0, fontWeight: 500 }, children: "Failed to load component" }),
|
|
40
|
+
/* @__PURE__ */ jsx("p", { style: { margin: "8px 0 0", fontSize: "14px", color: "#991b1b" }, children: error.message }),
|
|
41
|
+
onRetry && /* @__PURE__ */ jsx(
|
|
42
|
+
"button",
|
|
43
|
+
{
|
|
44
|
+
onClick: onRetry,
|
|
45
|
+
style: {
|
|
46
|
+
marginTop: "12px",
|
|
47
|
+
padding: "8px 16px",
|
|
48
|
+
backgroundColor: "#ef4444",
|
|
49
|
+
color: "white",
|
|
50
|
+
border: "none",
|
|
51
|
+
borderRadius: "4px",
|
|
52
|
+
cursor: "pointer",
|
|
53
|
+
fontSize: "14px"
|
|
54
|
+
},
|
|
55
|
+
children: "Retry"
|
|
56
|
+
}
|
|
57
|
+
)
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
);
|
|
61
|
+
function useSlotElements(containerRef, isRendered) {
|
|
62
|
+
const [slotElements, setSlotElements] = useState({});
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
if (!isRendered || !containerRef.current) {
|
|
65
|
+
setSlotElements({});
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const found = {};
|
|
69
|
+
const slots = containerRef.current.querySelectorAll("[data-slot]");
|
|
70
|
+
slots.forEach((el) => {
|
|
71
|
+
const slotName = el.getAttribute("data-slot");
|
|
72
|
+
if (slotName) {
|
|
73
|
+
found[slotName] = el;
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
setSlotElements(found);
|
|
77
|
+
}, [isRendered, containerRef]);
|
|
78
|
+
return slotElements;
|
|
79
|
+
}
|
|
80
|
+
function ServlyComponent({
|
|
81
|
+
id,
|
|
82
|
+
version = "latest",
|
|
83
|
+
props = {},
|
|
84
|
+
slots,
|
|
85
|
+
fallback,
|
|
86
|
+
onError,
|
|
87
|
+
onLoad,
|
|
88
|
+
className,
|
|
89
|
+
style,
|
|
90
|
+
showSkeleton = true,
|
|
91
|
+
cacheStrategy = "memory",
|
|
92
|
+
retryConfig,
|
|
93
|
+
eventHandlers,
|
|
94
|
+
children
|
|
95
|
+
}) {
|
|
96
|
+
const containerRef = useRef(null);
|
|
97
|
+
const renderResultRef = useRef(null);
|
|
98
|
+
const abortControllerRef = useRef(null);
|
|
99
|
+
const [isRendered, setIsRendered] = useState(false);
|
|
100
|
+
const [state, setState] = useState({
|
|
101
|
+
loading: true,
|
|
102
|
+
error: null,
|
|
103
|
+
data: null
|
|
104
|
+
});
|
|
105
|
+
const slotElements = useSlotElements(containerRef, isRendered);
|
|
106
|
+
const effectiveSlots = useMemo(() => {
|
|
107
|
+
const result = { ...slots };
|
|
108
|
+
if (children && !result.default) {
|
|
109
|
+
result.default = children;
|
|
110
|
+
}
|
|
111
|
+
return result;
|
|
112
|
+
}, [slots, children]);
|
|
113
|
+
const loadComponent = useCallback(async () => {
|
|
114
|
+
if (abortControllerRef.current) {
|
|
115
|
+
abortControllerRef.current.abort();
|
|
116
|
+
}
|
|
117
|
+
abortControllerRef.current = new AbortController();
|
|
118
|
+
setState((prev) => ({ ...prev, loading: true, error: null }));
|
|
119
|
+
setIsRendered(false);
|
|
120
|
+
const fetchOptions = {
|
|
121
|
+
version,
|
|
122
|
+
cacheStrategy,
|
|
123
|
+
retryConfig,
|
|
124
|
+
signal: abortControllerRef.current.signal
|
|
125
|
+
};
|
|
126
|
+
try {
|
|
127
|
+
const result = await fetchComponent(id, fetchOptions);
|
|
128
|
+
setState({
|
|
129
|
+
loading: false,
|
|
130
|
+
error: null,
|
|
131
|
+
data: result.data,
|
|
132
|
+
views: result.views,
|
|
133
|
+
registry: result.registry
|
|
134
|
+
});
|
|
135
|
+
onLoad?.();
|
|
136
|
+
} catch (error) {
|
|
137
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
138
|
+
if (err.message === "Fetch aborted") return;
|
|
139
|
+
setState({
|
|
140
|
+
loading: false,
|
|
141
|
+
error: err,
|
|
142
|
+
data: null,
|
|
143
|
+
views: void 0,
|
|
144
|
+
registry: void 0
|
|
145
|
+
});
|
|
146
|
+
onError?.(err);
|
|
147
|
+
}
|
|
148
|
+
}, [id, version, cacheStrategy, retryConfig, onLoad, onError]);
|
|
149
|
+
useEffect(() => {
|
|
150
|
+
loadComponent();
|
|
151
|
+
return () => {
|
|
152
|
+
if (abortControllerRef.current) {
|
|
153
|
+
abortControllerRef.current.abort();
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
}, [loadComponent]);
|
|
157
|
+
useEffect(() => {
|
|
158
|
+
if (!state.data || !containerRef.current) return;
|
|
159
|
+
const context = {
|
|
160
|
+
props,
|
|
161
|
+
state: {},
|
|
162
|
+
context: {}
|
|
163
|
+
};
|
|
164
|
+
if (renderResultRef.current) {
|
|
165
|
+
renderResultRef.current.update(context);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
renderResultRef.current = render({
|
|
169
|
+
container: containerRef.current,
|
|
170
|
+
elements: state.data.layout,
|
|
171
|
+
context,
|
|
172
|
+
eventHandlers,
|
|
173
|
+
views: state.views,
|
|
174
|
+
componentRegistry: state.registry
|
|
175
|
+
});
|
|
176
|
+
setIsRendered(true);
|
|
177
|
+
return () => {
|
|
178
|
+
if (renderResultRef.current) {
|
|
179
|
+
renderResultRef.current.destroy();
|
|
180
|
+
renderResultRef.current = null;
|
|
181
|
+
setIsRendered(false);
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
}, [state.data, eventHandlers]);
|
|
185
|
+
useEffect(() => {
|
|
186
|
+
if (!renderResultRef.current || !state.data) return;
|
|
187
|
+
const context = {
|
|
188
|
+
props,
|
|
189
|
+
state: {},
|
|
190
|
+
context: {}
|
|
191
|
+
};
|
|
192
|
+
renderResultRef.current.update(context);
|
|
193
|
+
}, [props, state.data]);
|
|
194
|
+
if (state.loading) {
|
|
195
|
+
if (fallback) return /* @__PURE__ */ jsx(Fragment, { children: fallback });
|
|
196
|
+
if (showSkeleton) return /* @__PURE__ */ jsx(LoadingSkeleton, { className });
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
if (state.error) {
|
|
200
|
+
if (fallback) return /* @__PURE__ */ jsx(Fragment, { children: fallback });
|
|
201
|
+
return /* @__PURE__ */ jsx(
|
|
202
|
+
ErrorDisplay,
|
|
203
|
+
{
|
|
204
|
+
error: state.error,
|
|
205
|
+
onRetry: loadComponent,
|
|
206
|
+
className,
|
|
207
|
+
style
|
|
208
|
+
}
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
212
|
+
/* @__PURE__ */ jsx(
|
|
213
|
+
"div",
|
|
214
|
+
{
|
|
215
|
+
ref: containerRef,
|
|
216
|
+
className: `servly-component ${className || ""}`,
|
|
217
|
+
style,
|
|
218
|
+
"data-servly-id": id,
|
|
219
|
+
"data-servly-version": state.data?.version
|
|
220
|
+
}
|
|
221
|
+
),
|
|
222
|
+
Object.entries(effectiveSlots).map(([slotName, content]) => {
|
|
223
|
+
const slotEl = slotElements[slotName];
|
|
224
|
+
if (!slotEl || !content) return null;
|
|
225
|
+
return createPortal(content, slotEl, `slot-${slotName}`);
|
|
226
|
+
})
|
|
227
|
+
] });
|
|
228
|
+
}
|
|
229
|
+
var ServlyComponent_default = ServlyComponent;
|
|
230
|
+
|
|
231
|
+
// packages/runtime-react/src/index.ts
|
|
232
|
+
import {
|
|
233
|
+
fetchComponent as fetchComponent2,
|
|
234
|
+
prefetchComponents,
|
|
235
|
+
isComponentAvailable,
|
|
236
|
+
setRegistryUrl,
|
|
237
|
+
getRegistryUrl,
|
|
238
|
+
invalidateCache,
|
|
239
|
+
clearAllCaches,
|
|
240
|
+
parseVersion,
|
|
241
|
+
compareVersions,
|
|
242
|
+
satisfiesVersion,
|
|
243
|
+
resolveVersion
|
|
244
|
+
} from "@servlyadmin/runtime-core";
|
|
245
|
+
export {
|
|
246
|
+
ServlyComponent,
|
|
247
|
+
clearAllCaches,
|
|
248
|
+
compareVersions,
|
|
249
|
+
ServlyComponent_default as default,
|
|
250
|
+
fetchComponent2 as fetchComponent,
|
|
251
|
+
getRegistryUrl,
|
|
252
|
+
invalidateCache,
|
|
253
|
+
isComponentAvailable,
|
|
254
|
+
parseVersion,
|
|
255
|
+
prefetchComponents,
|
|
256
|
+
resolveVersion,
|
|
257
|
+
satisfiesVersion,
|
|
258
|
+
setRegistryUrl
|
|
259
|
+
};
|
package/package.json
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@servlyadmin/runtime-react",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "React wrapper for Servly runtime renderer",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "./dist/index.
|
|
7
|
-
"module": "./dist/index.
|
|
8
|
-
"types": "./dist/index.d.ts",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.mjs",
|
|
9
8
|
"exports": {
|
|
10
9
|
".": {
|
|
11
|
-
"import": "./dist/index.
|
|
12
|
-
"require": "./dist/index.
|
|
13
|
-
"types": "./dist/index.d.ts"
|
|
10
|
+
"import": "./dist/index.mjs",
|
|
11
|
+
"require": "./dist/index.js"
|
|
14
12
|
}
|
|
15
13
|
},
|
|
16
14
|
"files": [
|
|
@@ -34,8 +32,7 @@
|
|
|
34
32
|
"react-dom": ">=17.0.0"
|
|
35
33
|
},
|
|
36
34
|
"dependencies": {
|
|
37
|
-
"@servlyadmin/runtime-core": "^0.1.
|
|
38
|
-
"@servlyadmin/runtime-react": "^0.1.6"
|
|
35
|
+
"@servlyadmin/runtime-core": "^0.1.9"
|
|
39
36
|
},
|
|
40
37
|
"devDependencies": {
|
|
41
38
|
"@types/react": "^18.2.0",
|
package/dist/index.cjs
DELETED
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
-
var __export = (target, all) => {
|
|
7
|
-
for (var name in all)
|
|
8
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
-
};
|
|
10
|
-
var __copyProps = (to, from, except, desc) => {
|
|
11
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
-
for (let key of __getOwnPropNames(from))
|
|
13
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
-
}
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
-
|
|
20
|
-
// src/index.ts
|
|
21
|
-
var index_exports = {};
|
|
22
|
-
__export(index_exports, {
|
|
23
|
-
ServlyComponent: () => ServlyComponent,
|
|
24
|
-
clearAllCaches: () => import_runtime_core2.clearAllCaches,
|
|
25
|
-
compareVersions: () => import_runtime_core2.compareVersions,
|
|
26
|
-
default: () => ServlyComponent_default,
|
|
27
|
-
fetchComponent: () => import_runtime_core2.fetchComponent,
|
|
28
|
-
getRegistryUrl: () => import_runtime_core2.getRegistryUrl,
|
|
29
|
-
invalidateCache: () => import_runtime_core2.invalidateCache,
|
|
30
|
-
isComponentAvailable: () => import_runtime_core2.isComponentAvailable,
|
|
31
|
-
parseVersion: () => import_runtime_core2.parseVersion,
|
|
32
|
-
prefetchComponents: () => import_runtime_core2.prefetchComponents,
|
|
33
|
-
resolveVersion: () => import_runtime_core2.resolveVersion,
|
|
34
|
-
satisfiesVersion: () => import_runtime_core2.satisfiesVersion,
|
|
35
|
-
setRegistryUrl: () => import_runtime_core2.setRegistryUrl
|
|
36
|
-
});
|
|
37
|
-
module.exports = __toCommonJS(index_exports);
|
|
38
|
-
|
|
39
|
-
// src/ServlyComponent.tsx
|
|
40
|
-
var import_react = require("react");
|
|
41
|
-
var import_runtime_core = require("@servlyadmin/runtime-core");
|
|
42
|
-
var import_jsx_runtime = require("react/jsx-runtime");
|
|
43
|
-
var LoadingSkeleton = ({ className }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
44
|
-
"div",
|
|
45
|
-
{
|
|
46
|
-
className: `servly-skeleton ${className || ""}`,
|
|
47
|
-
style: {
|
|
48
|
-
backgroundColor: "#f3f4f6",
|
|
49
|
-
borderRadius: "8px",
|
|
50
|
-
minHeight: "100px",
|
|
51
|
-
animation: "servly-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite"
|
|
52
|
-
},
|
|
53
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: `
|
|
54
|
-
@keyframes servly-pulse {
|
|
55
|
-
0%, 100% { opacity: 1; }
|
|
56
|
-
50% { opacity: 0.5; }
|
|
57
|
-
}
|
|
58
|
-
` })
|
|
59
|
-
}
|
|
60
|
-
);
|
|
61
|
-
var ErrorDisplay = ({ error, onRetry, className, style }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
62
|
-
"div",
|
|
63
|
-
{
|
|
64
|
-
className: `servly-error ${className || ""}`,
|
|
65
|
-
style: {
|
|
66
|
-
padding: "16px",
|
|
67
|
-
color: "#ef4444",
|
|
68
|
-
backgroundColor: "#fef2f2",
|
|
69
|
-
borderRadius: "8px",
|
|
70
|
-
...style
|
|
71
|
-
},
|
|
72
|
-
children: [
|
|
73
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { margin: 0, fontWeight: 500 }, children: "Failed to load component" }),
|
|
74
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { margin: "8px 0 0", fontSize: "14px", color: "#991b1b" }, children: error.message }),
|
|
75
|
-
onRetry && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
76
|
-
"button",
|
|
77
|
-
{
|
|
78
|
-
onClick: onRetry,
|
|
79
|
-
style: {
|
|
80
|
-
marginTop: "12px",
|
|
81
|
-
padding: "8px 16px",
|
|
82
|
-
backgroundColor: "#ef4444",
|
|
83
|
-
color: "white",
|
|
84
|
-
border: "none",
|
|
85
|
-
borderRadius: "4px",
|
|
86
|
-
cursor: "pointer",
|
|
87
|
-
fontSize: "14px"
|
|
88
|
-
},
|
|
89
|
-
children: "Retry"
|
|
90
|
-
}
|
|
91
|
-
)
|
|
92
|
-
]
|
|
93
|
-
}
|
|
94
|
-
);
|
|
95
|
-
function ServlyComponent({
|
|
96
|
-
id,
|
|
97
|
-
version = "latest",
|
|
98
|
-
props = {},
|
|
99
|
-
fallback,
|
|
100
|
-
onError,
|
|
101
|
-
onLoad,
|
|
102
|
-
className,
|
|
103
|
-
style,
|
|
104
|
-
showSkeleton = true,
|
|
105
|
-
cacheStrategy = "memory",
|
|
106
|
-
retryConfig,
|
|
107
|
-
eventHandlers
|
|
108
|
-
}) {
|
|
109
|
-
const containerRef = (0, import_react.useRef)(null);
|
|
110
|
-
const renderResultRef = (0, import_react.useRef)(null);
|
|
111
|
-
const abortControllerRef = (0, import_react.useRef)(null);
|
|
112
|
-
const [state, setState] = (0, import_react.useState)({
|
|
113
|
-
loading: true,
|
|
114
|
-
error: null,
|
|
115
|
-
data: null
|
|
116
|
-
});
|
|
117
|
-
const loadComponent = (0, import_react.useCallback)(async () => {
|
|
118
|
-
if (abortControllerRef.current) {
|
|
119
|
-
abortControllerRef.current.abort();
|
|
120
|
-
}
|
|
121
|
-
abortControllerRef.current = new AbortController();
|
|
122
|
-
setState((prev) => ({ ...prev, loading: true, error: null }));
|
|
123
|
-
const fetchOptions = {
|
|
124
|
-
version,
|
|
125
|
-
cacheStrategy,
|
|
126
|
-
retryConfig,
|
|
127
|
-
signal: abortControllerRef.current.signal
|
|
128
|
-
};
|
|
129
|
-
try {
|
|
130
|
-
const result = await (0, import_runtime_core.fetchComponent)(id, fetchOptions);
|
|
131
|
-
setState({
|
|
132
|
-
loading: false,
|
|
133
|
-
error: null,
|
|
134
|
-
data: result.data
|
|
135
|
-
});
|
|
136
|
-
onLoad?.();
|
|
137
|
-
} catch (error) {
|
|
138
|
-
const err = error instanceof Error ? error : new Error(String(error));
|
|
139
|
-
if (err.message === "Fetch aborted") return;
|
|
140
|
-
setState({
|
|
141
|
-
loading: false,
|
|
142
|
-
error: err,
|
|
143
|
-
data: null
|
|
144
|
-
});
|
|
145
|
-
onError?.(err);
|
|
146
|
-
}
|
|
147
|
-
}, [id, version, cacheStrategy, retryConfig, onLoad, onError]);
|
|
148
|
-
(0, import_react.useEffect)(() => {
|
|
149
|
-
loadComponent();
|
|
150
|
-
return () => {
|
|
151
|
-
if (abortControllerRef.current) {
|
|
152
|
-
abortControllerRef.current.abort();
|
|
153
|
-
}
|
|
154
|
-
};
|
|
155
|
-
}, [loadComponent]);
|
|
156
|
-
(0, import_react.useEffect)(() => {
|
|
157
|
-
if (!state.data || !containerRef.current) return;
|
|
158
|
-
const context = {
|
|
159
|
-
props,
|
|
160
|
-
state: {},
|
|
161
|
-
context: {}
|
|
162
|
-
};
|
|
163
|
-
if (renderResultRef.current) {
|
|
164
|
-
renderResultRef.current.update(context);
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
renderResultRef.current = (0, import_runtime_core.render)({
|
|
168
|
-
container: containerRef.current,
|
|
169
|
-
elements: state.data.layout,
|
|
170
|
-
context,
|
|
171
|
-
eventHandlers
|
|
172
|
-
});
|
|
173
|
-
return () => {
|
|
174
|
-
if (renderResultRef.current) {
|
|
175
|
-
renderResultRef.current.destroy();
|
|
176
|
-
renderResultRef.current = null;
|
|
177
|
-
}
|
|
178
|
-
};
|
|
179
|
-
}, [state.data, props, eventHandlers]);
|
|
180
|
-
(0, import_react.useEffect)(() => {
|
|
181
|
-
if (!renderResultRef.current || !state.data) return;
|
|
182
|
-
const context = {
|
|
183
|
-
props,
|
|
184
|
-
state: {},
|
|
185
|
-
context: {}
|
|
186
|
-
};
|
|
187
|
-
renderResultRef.current.update(context);
|
|
188
|
-
}, [props, state.data]);
|
|
189
|
-
if (state.loading) {
|
|
190
|
-
if (fallback) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: fallback });
|
|
191
|
-
if (showSkeleton) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LoadingSkeleton, { className });
|
|
192
|
-
return null;
|
|
193
|
-
}
|
|
194
|
-
if (state.error) {
|
|
195
|
-
if (fallback) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: fallback });
|
|
196
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
197
|
-
ErrorDisplay,
|
|
198
|
-
{
|
|
199
|
-
error: state.error,
|
|
200
|
-
onRetry: loadComponent,
|
|
201
|
-
className,
|
|
202
|
-
style
|
|
203
|
-
}
|
|
204
|
-
);
|
|
205
|
-
}
|
|
206
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
207
|
-
"div",
|
|
208
|
-
{
|
|
209
|
-
ref: containerRef,
|
|
210
|
-
className: `servly-component ${className || ""}`,
|
|
211
|
-
style,
|
|
212
|
-
"data-servly-id": id,
|
|
213
|
-
"data-servly-version": state.data?.version
|
|
214
|
-
}
|
|
215
|
-
);
|
|
216
|
-
}
|
|
217
|
-
var ServlyComponent_default = ServlyComponent;
|
|
218
|
-
|
|
219
|
-
// src/index.ts
|
|
220
|
-
var import_runtime_core2 = require("@servlyadmin/runtime-core");
|
|
221
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
222
|
-
0 && (module.exports = {
|
|
223
|
-
ServlyComponent,
|
|
224
|
-
clearAllCaches,
|
|
225
|
-
compareVersions,
|
|
226
|
-
fetchComponent,
|
|
227
|
-
getRegistryUrl,
|
|
228
|
-
invalidateCache,
|
|
229
|
-
isComponentAvailable,
|
|
230
|
-
parseVersion,
|
|
231
|
-
prefetchComponents,
|
|
232
|
-
resolveVersion,
|
|
233
|
-
satisfiesVersion,
|
|
234
|
-
setRegistryUrl
|
|
235
|
-
});
|
package/dist/index.d.cts
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { CacheStrategy, RetryConfig } from '@servlyadmin/runtime-core';
|
|
3
|
-
export { BindingContext, CacheStrategy, ComponentData, FetchOptions, LayoutElement, PropDefinition, RetryConfig, clearAllCaches, compareVersions, fetchComponent, getRegistryUrl, invalidateCache, isComponentAvailable, parseVersion, prefetchComponents, resolveVersion, satisfiesVersion, setRegistryUrl } from '@servlyadmin/runtime-core';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* ServlyComponent
|
|
7
|
-
* React wrapper for Servly runtime renderer
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Props for ServlyComponent
|
|
12
|
-
*/
|
|
13
|
-
interface ServlyComponentProps<P = Record<string, any>> {
|
|
14
|
-
/** Component ID from the registry */
|
|
15
|
-
id: string;
|
|
16
|
-
/** Version specifier (exact, range, or "latest") */
|
|
17
|
-
version?: string;
|
|
18
|
-
/** Props to pass to the component */
|
|
19
|
-
props?: P;
|
|
20
|
-
/** Fallback UI while loading or on error */
|
|
21
|
-
fallback?: React.ReactNode;
|
|
22
|
-
/** Error callback */
|
|
23
|
-
onError?: (error: Error) => void;
|
|
24
|
-
/** Load complete callback */
|
|
25
|
-
onLoad?: () => void;
|
|
26
|
-
/** Custom className for wrapper */
|
|
27
|
-
className?: string;
|
|
28
|
-
/** Custom styles for wrapper */
|
|
29
|
-
style?: React.CSSProperties;
|
|
30
|
-
/** Show loading skeleton */
|
|
31
|
-
showSkeleton?: boolean;
|
|
32
|
-
/** Cache strategy */
|
|
33
|
-
cacheStrategy?: CacheStrategy;
|
|
34
|
-
/** Retry configuration */
|
|
35
|
-
retryConfig?: Partial<RetryConfig>;
|
|
36
|
-
/** Event handlers keyed by element ID then event name */
|
|
37
|
-
eventHandlers?: Record<string, Record<string, (e: Event) => void>>;
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* ServlyComponent - React wrapper for Servly runtime
|
|
41
|
-
*/
|
|
42
|
-
declare function ServlyComponent<P = Record<string, any>>({ id, version, props, fallback, onError, onLoad, className, style, showSkeleton, cacheStrategy, retryConfig, eventHandlers, }: ServlyComponentProps<P>): React.ReactElement | null;
|
|
43
|
-
|
|
44
|
-
export { ServlyComponent, type ServlyComponentProps, ServlyComponent as default };
|
package/dist/index.d.ts
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { CacheStrategy, RetryConfig } from '@servlyadmin/runtime-core';
|
|
3
|
-
export { BindingContext, CacheStrategy, ComponentData, FetchOptions, LayoutElement, PropDefinition, RetryConfig, clearAllCaches, compareVersions, fetchComponent, getRegistryUrl, invalidateCache, isComponentAvailable, parseVersion, prefetchComponents, resolveVersion, satisfiesVersion, setRegistryUrl } from '@servlyadmin/runtime-core';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* ServlyComponent
|
|
7
|
-
* React wrapper for Servly runtime renderer
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Props for ServlyComponent
|
|
12
|
-
*/
|
|
13
|
-
interface ServlyComponentProps<P = Record<string, any>> {
|
|
14
|
-
/** Component ID from the registry */
|
|
15
|
-
id: string;
|
|
16
|
-
/** Version specifier (exact, range, or "latest") */
|
|
17
|
-
version?: string;
|
|
18
|
-
/** Props to pass to the component */
|
|
19
|
-
props?: P;
|
|
20
|
-
/** Fallback UI while loading or on error */
|
|
21
|
-
fallback?: React.ReactNode;
|
|
22
|
-
/** Error callback */
|
|
23
|
-
onError?: (error: Error) => void;
|
|
24
|
-
/** Load complete callback */
|
|
25
|
-
onLoad?: () => void;
|
|
26
|
-
/** Custom className for wrapper */
|
|
27
|
-
className?: string;
|
|
28
|
-
/** Custom styles for wrapper */
|
|
29
|
-
style?: React.CSSProperties;
|
|
30
|
-
/** Show loading skeleton */
|
|
31
|
-
showSkeleton?: boolean;
|
|
32
|
-
/** Cache strategy */
|
|
33
|
-
cacheStrategy?: CacheStrategy;
|
|
34
|
-
/** Retry configuration */
|
|
35
|
-
retryConfig?: Partial<RetryConfig>;
|
|
36
|
-
/** Event handlers keyed by element ID then event name */
|
|
37
|
-
eventHandlers?: Record<string, Record<string, (e: Event) => void>>;
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* ServlyComponent - React wrapper for Servly runtime
|
|
41
|
-
*/
|
|
42
|
-
declare function ServlyComponent<P = Record<string, any>>({ id, version, props, fallback, onError, onLoad, className, style, showSkeleton, cacheStrategy, retryConfig, eventHandlers, }: ServlyComponentProps<P>): React.ReactElement | null;
|
|
43
|
-
|
|
44
|
-
export { ServlyComponent, type ServlyComponentProps, ServlyComponent as default };
|