@servlyadmin/runtime-react 0.1.7 → 0.1.8

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/index.cjs CHANGED
@@ -38,6 +38,7 @@ module.exports = __toCommonJS(index_exports);
38
38
 
39
39
  // src/ServlyComponent.tsx
40
40
  var import_react = require("react");
41
+ var import_react_dom = require("react-dom");
41
42
  var import_runtime_core = require("@servlyadmin/runtime-core");
42
43
  var import_jsx_runtime = require("react/jsx-runtime");
43
44
  var LoadingSkeleton = ({ className }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -92,10 +93,30 @@ var ErrorDisplay = ({ error, onRetry, className, style }) => /* @__PURE__ */ (0,
92
93
  ]
93
94
  }
94
95
  );
96
+ function useSlotElements(containerRef, isRendered) {
97
+ const [slotElements, setSlotElements] = (0, import_react.useState)({});
98
+ (0, import_react.useEffect)(() => {
99
+ if (!isRendered || !containerRef.current) {
100
+ setSlotElements({});
101
+ return;
102
+ }
103
+ const found = {};
104
+ const slots = containerRef.current.querySelectorAll("[data-slot]");
105
+ slots.forEach((el) => {
106
+ const slotName = el.getAttribute("data-slot");
107
+ if (slotName) {
108
+ found[slotName] = el;
109
+ }
110
+ });
111
+ setSlotElements(found);
112
+ }, [isRendered, containerRef]);
113
+ return slotElements;
114
+ }
95
115
  function ServlyComponent({
96
116
  id,
97
117
  version = "latest",
98
118
  props = {},
119
+ slots,
99
120
  fallback,
100
121
  onError,
101
122
  onLoad,
@@ -104,22 +125,33 @@ function ServlyComponent({
104
125
  showSkeleton = true,
105
126
  cacheStrategy = "memory",
106
127
  retryConfig,
107
- eventHandlers
128
+ eventHandlers,
129
+ children
108
130
  }) {
109
131
  const containerRef = (0, import_react.useRef)(null);
110
132
  const renderResultRef = (0, import_react.useRef)(null);
111
133
  const abortControllerRef = (0, import_react.useRef)(null);
134
+ const [isRendered, setIsRendered] = (0, import_react.useState)(false);
112
135
  const [state, setState] = (0, import_react.useState)({
113
136
  loading: true,
114
137
  error: null,
115
138
  data: null
116
139
  });
140
+ const slotElements = useSlotElements(containerRef, isRendered);
141
+ const effectiveSlots = (0, import_react.useMemo)(() => {
142
+ const result = { ...slots };
143
+ if (children && !result.default) {
144
+ result.default = children;
145
+ }
146
+ return result;
147
+ }, [slots, children]);
117
148
  const loadComponent = (0, import_react.useCallback)(async () => {
118
149
  if (abortControllerRef.current) {
119
150
  abortControllerRef.current.abort();
120
151
  }
121
152
  abortControllerRef.current = new AbortController();
122
153
  setState((prev) => ({ ...prev, loading: true, error: null }));
154
+ setIsRendered(false);
123
155
  const fetchOptions = {
124
156
  version,
125
157
  cacheStrategy,
@@ -170,13 +202,15 @@ function ServlyComponent({
170
202
  context,
171
203
  eventHandlers
172
204
  });
205
+ setIsRendered(true);
173
206
  return () => {
174
207
  if (renderResultRef.current) {
175
208
  renderResultRef.current.destroy();
176
209
  renderResultRef.current = null;
210
+ setIsRendered(false);
177
211
  }
178
212
  };
179
- }, [state.data, props, eventHandlers]);
213
+ }, [state.data, eventHandlers]);
180
214
  (0, import_react.useEffect)(() => {
181
215
  if (!renderResultRef.current || !state.data) return;
182
216
  const context = {
@@ -203,16 +237,23 @@ function ServlyComponent({
203
237
  }
204
238
  );
205
239
  }
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
- );
240
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
241
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
242
+ "div",
243
+ {
244
+ ref: containerRef,
245
+ className: `servly-component ${className || ""}`,
246
+ style,
247
+ "data-servly-id": id,
248
+ "data-servly-version": state.data?.version
249
+ }
250
+ ),
251
+ Object.entries(effectiveSlots).map(([slotName, content]) => {
252
+ const slotEl = slotElements[slotName];
253
+ if (!slotEl || !content) return null;
254
+ return (0, import_react_dom.createPortal)(content, slotEl, `slot-${slotName}`);
255
+ })
256
+ ] });
216
257
  }
217
258
  var ServlyComponent_default = ServlyComponent;
218
259
 
package/dist/index.d.cts CHANGED
@@ -4,9 +4,13 @@ export { BindingContext, CacheStrategy, ComponentData, FetchOptions, LayoutEleme
4
4
 
5
5
  /**
6
6
  * ServlyComponent
7
- * React wrapper for Servly runtime renderer
7
+ * React wrapper for Servly runtime renderer with slot support
8
8
  */
9
9
 
10
+ /**
11
+ * Slot content type - React nodes keyed by slot name
12
+ */
13
+ type SlotContent = Record<string, React.ReactNode>;
10
14
  /**
11
15
  * Props for ServlyComponent
12
16
  */
@@ -17,6 +21,8 @@ interface ServlyComponentProps<P = Record<string, any>> {
17
21
  version?: string;
18
22
  /** Props to pass to the component */
19
23
  props?: P;
24
+ /** Slot content - React nodes to portal into named slots */
25
+ slots?: SlotContent;
20
26
  /** Fallback UI while loading or on error */
21
27
  fallback?: React.ReactNode;
22
28
  /** Error callback */
@@ -35,10 +41,12 @@ interface ServlyComponentProps<P = Record<string, any>> {
35
41
  retryConfig?: Partial<RetryConfig>;
36
42
  /** Event handlers keyed by element ID then event name */
37
43
  eventHandlers?: Record<string, Record<string, (e: Event) => void>>;
44
+ /** Children - rendered into default slot if no slots prop */
45
+ children?: React.ReactNode;
38
46
  }
39
47
  /**
40
- * ServlyComponent - React wrapper for Servly runtime
48
+ * ServlyComponent - React wrapper for Servly runtime with slot support
41
49
  */
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;
50
+ declare function ServlyComponent<P = Record<string, any>>({ id, version, props, slots, fallback, onError, onLoad, className, style, showSkeleton, cacheStrategy, retryConfig, eventHandlers, children, }: ServlyComponentProps<P>): React.ReactElement | null;
43
51
 
44
52
  export { ServlyComponent, type ServlyComponentProps, ServlyComponent as default };
package/dist/index.d.ts CHANGED
@@ -4,9 +4,13 @@ export { BindingContext, CacheStrategy, ComponentData, FetchOptions, LayoutEleme
4
4
 
5
5
  /**
6
6
  * ServlyComponent
7
- * React wrapper for Servly runtime renderer
7
+ * React wrapper for Servly runtime renderer with slot support
8
8
  */
9
9
 
10
+ /**
11
+ * Slot content type - React nodes keyed by slot name
12
+ */
13
+ type SlotContent = Record<string, React.ReactNode>;
10
14
  /**
11
15
  * Props for ServlyComponent
12
16
  */
@@ -17,6 +21,8 @@ interface ServlyComponentProps<P = Record<string, any>> {
17
21
  version?: string;
18
22
  /** Props to pass to the component */
19
23
  props?: P;
24
+ /** Slot content - React nodes to portal into named slots */
25
+ slots?: SlotContent;
20
26
  /** Fallback UI while loading or on error */
21
27
  fallback?: React.ReactNode;
22
28
  /** Error callback */
@@ -35,10 +41,12 @@ interface ServlyComponentProps<P = Record<string, any>> {
35
41
  retryConfig?: Partial<RetryConfig>;
36
42
  /** Event handlers keyed by element ID then event name */
37
43
  eventHandlers?: Record<string, Record<string, (e: Event) => void>>;
44
+ /** Children - rendered into default slot if no slots prop */
45
+ children?: React.ReactNode;
38
46
  }
39
47
  /**
40
- * ServlyComponent - React wrapper for Servly runtime
48
+ * ServlyComponent - React wrapper for Servly runtime with slot support
41
49
  */
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;
50
+ declare function ServlyComponent<P = Record<string, any>>({ id, version, props, slots, fallback, onError, onLoad, className, style, showSkeleton, cacheStrategy, retryConfig, eventHandlers, children, }: ServlyComponentProps<P>): React.ReactElement | null;
43
51
 
44
52
  export { ServlyComponent, type ServlyComponentProps, ServlyComponent as default };
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  // src/ServlyComponent.tsx
2
- import { useRef, useEffect, useState, useCallback } from "react";
2
+ import { useRef, useEffect, useState, useCallback, useMemo } from "react";
3
+ import { createPortal } from "react-dom";
3
4
  import {
4
5
  render,
5
6
  fetchComponent
@@ -57,10 +58,30 @@ var ErrorDisplay = ({ error, onRetry, className, style }) => /* @__PURE__ */ jsx
57
58
  ]
58
59
  }
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
+ }
60
80
  function ServlyComponent({
61
81
  id,
62
82
  version = "latest",
63
83
  props = {},
84
+ slots,
64
85
  fallback,
65
86
  onError,
66
87
  onLoad,
@@ -69,22 +90,33 @@ function ServlyComponent({
69
90
  showSkeleton = true,
70
91
  cacheStrategy = "memory",
71
92
  retryConfig,
72
- eventHandlers
93
+ eventHandlers,
94
+ children
73
95
  }) {
74
96
  const containerRef = useRef(null);
75
97
  const renderResultRef = useRef(null);
76
98
  const abortControllerRef = useRef(null);
99
+ const [isRendered, setIsRendered] = useState(false);
77
100
  const [state, setState] = useState({
78
101
  loading: true,
79
102
  error: null,
80
103
  data: null
81
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]);
82
113
  const loadComponent = useCallback(async () => {
83
114
  if (abortControllerRef.current) {
84
115
  abortControllerRef.current.abort();
85
116
  }
86
117
  abortControllerRef.current = new AbortController();
87
118
  setState((prev) => ({ ...prev, loading: true, error: null }));
119
+ setIsRendered(false);
88
120
  const fetchOptions = {
89
121
  version,
90
122
  cacheStrategy,
@@ -135,13 +167,15 @@ function ServlyComponent({
135
167
  context,
136
168
  eventHandlers
137
169
  });
170
+ setIsRendered(true);
138
171
  return () => {
139
172
  if (renderResultRef.current) {
140
173
  renderResultRef.current.destroy();
141
174
  renderResultRef.current = null;
175
+ setIsRendered(false);
142
176
  }
143
177
  };
144
- }, [state.data, props, eventHandlers]);
178
+ }, [state.data, eventHandlers]);
145
179
  useEffect(() => {
146
180
  if (!renderResultRef.current || !state.data) return;
147
181
  const context = {
@@ -168,16 +202,23 @@ function ServlyComponent({
168
202
  }
169
203
  );
170
204
  }
171
- return /* @__PURE__ */ jsx(
172
- "div",
173
- {
174
- ref: containerRef,
175
- className: `servly-component ${className || ""}`,
176
- style,
177
- "data-servly-id": id,
178
- "data-servly-version": state.data?.version
179
- }
180
- );
205
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
206
+ /* @__PURE__ */ 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
+ Object.entries(effectiveSlots).map(([slotName, content]) => {
217
+ const slotEl = slotElements[slotName];
218
+ if (!slotEl || !content) return null;
219
+ return createPortal(content, slotEl, `slot-${slotName}`);
220
+ })
221
+ ] });
181
222
  }
182
223
  var ServlyComponent_default = ServlyComponent;
183
224
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@servlyadmin/runtime-react",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "React wrapper for Servly runtime renderer",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -34,8 +34,7 @@
34
34
  "react-dom": ">=17.0.0"
35
35
  },
36
36
  "dependencies": {
37
- "@servlyadmin/runtime-core": "^0.1.7",
38
- "@servlyadmin/runtime-react": "^0.1.6"
37
+ "@servlyadmin/runtime-core": "^0.1.8"
39
38
  },
40
39
  "devDependencies": {
41
40
  "@types/react": "^18.2.0",