@servlyadmin/runtime-react 0.1.31 → 0.1.33

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
@@ -126,7 +126,8 @@ function ServlyComponent({
126
126
  cacheStrategy = "memory",
127
127
  retryConfig,
128
128
  eventHandlers,
129
- children
129
+ children,
130
+ waitForStyles = true
130
131
  }) {
131
132
  const containerRef = (0, import_react.useRef)(null);
132
133
  const renderResultRef = (0, import_react.useRef)(null);
@@ -135,7 +136,9 @@ function ServlyComponent({
135
136
  const [state, setState] = (0, import_react.useState)({
136
137
  loading: true,
137
138
  error: null,
138
- data: null
139
+ data: null,
140
+ stylesReady: !waitForStyles
141
+ // If not waiting, styles are "ready"
139
142
  });
140
143
  const slotElements = useSlotElements(containerRef, isRendered);
141
144
  const effectiveSlots = (0, import_react.useMemo)(() => {
@@ -160,24 +163,26 @@ function ServlyComponent({
160
163
  };
161
164
  try {
162
165
  const result = await (0, import_runtime_core.fetchComponent)(id, fetchOptions);
163
- setState({
166
+ setState((prev) => ({
167
+ ...prev,
164
168
  loading: false,
165
169
  error: null,
166
170
  data: result.data,
167
171
  views: result.views,
168
172
  registry: result.registry
169
- });
173
+ }));
170
174
  onLoad?.();
171
175
  } catch (error) {
172
176
  const err = error instanceof Error ? error : new Error(String(error));
173
177
  if (err.message === "Fetch aborted") return;
174
- setState({
178
+ setState((prev) => ({
179
+ ...prev,
175
180
  loading: false,
176
181
  error: err,
177
182
  data: null,
178
183
  views: void 0,
179
184
  registry: void 0
180
- });
185
+ }));
181
186
  onError?.(err);
182
187
  }
183
188
  }, [id, version, cacheStrategy, retryConfig, onLoad, onError]);
@@ -191,24 +196,34 @@ function ServlyComponent({
191
196
  }, [loadComponent]);
192
197
  (0, import_react.useEffect)(() => {
193
198
  if (!state.data || !containerRef.current) return;
194
- const context = {
195
- props,
196
- state: {},
197
- context: {}
199
+ const doRender = async () => {
200
+ if (waitForStyles) {
201
+ await (0, import_runtime_core.waitForTailwind)();
202
+ setState((prev) => ({ ...prev, stylesReady: true }));
203
+ }
204
+ const context = {
205
+ props,
206
+ state: {},
207
+ context: {}
208
+ };
209
+ if (renderResultRef.current) {
210
+ renderResultRef.current.update(context);
211
+ return;
212
+ }
213
+ renderResultRef.current = (0, import_runtime_core.render)({
214
+ container: containerRef.current,
215
+ elements: state.data.layout,
216
+ context,
217
+ eventHandlers,
218
+ views: state.views,
219
+ componentRegistry: state.registry
220
+ });
221
+ if (containerRef.current) {
222
+ (0, import_runtime_core.markElementReady)(containerRef.current);
223
+ }
224
+ setIsRendered(true);
198
225
  };
199
- if (renderResultRef.current) {
200
- renderResultRef.current.update(context);
201
- return;
202
- }
203
- renderResultRef.current = (0, import_runtime_core.render)({
204
- container: containerRef.current,
205
- elements: state.data.layout,
206
- context,
207
- eventHandlers,
208
- views: state.views,
209
- componentRegistry: state.registry
210
- });
211
- setIsRendered(true);
226
+ doRender();
212
227
  return () => {
213
228
  if (renderResultRef.current) {
214
229
  renderResultRef.current.destroy();
@@ -216,7 +231,7 @@ function ServlyComponent({
216
231
  setIsRendered(false);
217
232
  }
218
233
  };
219
- }, [state.data, eventHandlers]);
234
+ }, [state.data, eventHandlers, waitForStyles]);
220
235
  (0, import_react.useEffect)(() => {
221
236
  if (!renderResultRef.current || !state.data) return;
222
237
  const context = {
@@ -226,7 +241,7 @@ function ServlyComponent({
226
241
  };
227
242
  renderResultRef.current.update(context);
228
243
  }, [props, state.data]);
229
- if (state.loading) {
244
+ if (state.loading || waitForStyles && !state.stylesReady) {
230
245
  if (fallback) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: fallback });
231
246
  if (showSkeleton) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LoadingSkeleton, { className });
232
247
  return null;
@@ -248,7 +263,7 @@ function ServlyComponent({
248
263
  "div",
249
264
  {
250
265
  ref: containerRef,
251
- className: `servly-component ${className || ""}`,
266
+ className: `servly-component servly-ready ${className || ""}`,
252
267
  style,
253
268
  "data-servly-id": id,
254
269
  "data-servly-version": state.data?.version
@@ -0,0 +1,54 @@
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 with slot support
8
+ */
9
+
10
+ /**
11
+ * Slot content type - React nodes keyed by slot name
12
+ */
13
+ type SlotContent = Record<string, React.ReactNode>;
14
+ /**
15
+ * Props for ServlyComponent
16
+ */
17
+ interface ServlyComponentProps<P = Record<string, any>> {
18
+ /** Component ID from the registry */
19
+ id: string;
20
+ /** Version specifier (exact, range, or "latest") */
21
+ version?: string;
22
+ /** Props to pass to the component */
23
+ props?: P;
24
+ /** Slot content - React nodes to portal into named slots */
25
+ slots?: SlotContent;
26
+ /** Fallback UI while loading or on error */
27
+ fallback?: React.ReactNode;
28
+ /** Error callback */
29
+ onError?: (error: Error) => void;
30
+ /** Load complete callback */
31
+ onLoad?: () => void;
32
+ /** Custom className for wrapper */
33
+ className?: string;
34
+ /** Custom styles for wrapper */
35
+ style?: React.CSSProperties;
36
+ /** Show loading skeleton */
37
+ showSkeleton?: boolean;
38
+ /** Cache strategy */
39
+ cacheStrategy?: CacheStrategy;
40
+ /** Retry configuration */
41
+ retryConfig?: Partial<RetryConfig>;
42
+ /** Event handlers keyed by element ID then event name */
43
+ eventHandlers?: Record<string, Record<string, (e: Event) => void>>;
44
+ /** Children - rendered into default slot if no slots prop */
45
+ children?: React.ReactNode;
46
+ /** Wait for Tailwind CSS to load before showing component (prevents FOUC) */
47
+ waitForStyles?: boolean;
48
+ }
49
+ /**
50
+ * ServlyComponent - React wrapper for Servly runtime with slot support
51
+ */
52
+ declare function ServlyComponent<P = Record<string, any>>({ id, version, props, slots, fallback, onError, onLoad, className, style, showSkeleton, cacheStrategy, retryConfig, eventHandlers, children, waitForStyles, }: ServlyComponentProps<P>): React.ReactElement | null;
53
+
54
+ export { ServlyComponent, type ServlyComponentProps, ServlyComponent as default };
@@ -0,0 +1,54 @@
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 with slot support
8
+ */
9
+
10
+ /**
11
+ * Slot content type - React nodes keyed by slot name
12
+ */
13
+ type SlotContent = Record<string, React.ReactNode>;
14
+ /**
15
+ * Props for ServlyComponent
16
+ */
17
+ interface ServlyComponentProps<P = Record<string, any>> {
18
+ /** Component ID from the registry */
19
+ id: string;
20
+ /** Version specifier (exact, range, or "latest") */
21
+ version?: string;
22
+ /** Props to pass to the component */
23
+ props?: P;
24
+ /** Slot content - React nodes to portal into named slots */
25
+ slots?: SlotContent;
26
+ /** Fallback UI while loading or on error */
27
+ fallback?: React.ReactNode;
28
+ /** Error callback */
29
+ onError?: (error: Error) => void;
30
+ /** Load complete callback */
31
+ onLoad?: () => void;
32
+ /** Custom className for wrapper */
33
+ className?: string;
34
+ /** Custom styles for wrapper */
35
+ style?: React.CSSProperties;
36
+ /** Show loading skeleton */
37
+ showSkeleton?: boolean;
38
+ /** Cache strategy */
39
+ cacheStrategy?: CacheStrategy;
40
+ /** Retry configuration */
41
+ retryConfig?: Partial<RetryConfig>;
42
+ /** Event handlers keyed by element ID then event name */
43
+ eventHandlers?: Record<string, Record<string, (e: Event) => void>>;
44
+ /** Children - rendered into default slot if no slots prop */
45
+ children?: React.ReactNode;
46
+ /** Wait for Tailwind CSS to load before showing component (prevents FOUC) */
47
+ waitForStyles?: boolean;
48
+ }
49
+ /**
50
+ * ServlyComponent - React wrapper for Servly runtime with slot support
51
+ */
52
+ declare function ServlyComponent<P = Record<string, any>>({ id, version, props, slots, fallback, onError, onLoad, className, style, showSkeleton, cacheStrategy, retryConfig, eventHandlers, children, waitForStyles, }: ServlyComponentProps<P>): React.ReactElement | null;
53
+
54
+ export { ServlyComponent, type ServlyComponentProps, ServlyComponent as default };
package/dist/index.js CHANGED
@@ -3,7 +3,9 @@ import { useRef, useEffect, useState, useCallback, useMemo } from "react";
3
3
  import { createPortal } from "react-dom";
4
4
  import {
5
5
  render,
6
- fetchComponent
6
+ fetchComponent,
7
+ waitForTailwind,
8
+ markElementReady
7
9
  } from "@servlyadmin/runtime-core";
8
10
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
9
11
  var LoadingSkeleton = ({ className }) => /* @__PURE__ */ jsx(
@@ -91,7 +93,8 @@ function ServlyComponent({
91
93
  cacheStrategy = "memory",
92
94
  retryConfig,
93
95
  eventHandlers,
94
- children
96
+ children,
97
+ waitForStyles = true
95
98
  }) {
96
99
  const containerRef = useRef(null);
97
100
  const renderResultRef = useRef(null);
@@ -100,7 +103,9 @@ function ServlyComponent({
100
103
  const [state, setState] = useState({
101
104
  loading: true,
102
105
  error: null,
103
- data: null
106
+ data: null,
107
+ stylesReady: !waitForStyles
108
+ // If not waiting, styles are "ready"
104
109
  });
105
110
  const slotElements = useSlotElements(containerRef, isRendered);
106
111
  const effectiveSlots = useMemo(() => {
@@ -125,24 +130,26 @@ function ServlyComponent({
125
130
  };
126
131
  try {
127
132
  const result = await fetchComponent(id, fetchOptions);
128
- setState({
133
+ setState((prev) => ({
134
+ ...prev,
129
135
  loading: false,
130
136
  error: null,
131
137
  data: result.data,
132
138
  views: result.views,
133
139
  registry: result.registry
134
- });
140
+ }));
135
141
  onLoad?.();
136
142
  } catch (error) {
137
143
  const err = error instanceof Error ? error : new Error(String(error));
138
144
  if (err.message === "Fetch aborted") return;
139
- setState({
145
+ setState((prev) => ({
146
+ ...prev,
140
147
  loading: false,
141
148
  error: err,
142
149
  data: null,
143
150
  views: void 0,
144
151
  registry: void 0
145
- });
152
+ }));
146
153
  onError?.(err);
147
154
  }
148
155
  }, [id, version, cacheStrategy, retryConfig, onLoad, onError]);
@@ -156,24 +163,34 @@ function ServlyComponent({
156
163
  }, [loadComponent]);
157
164
  useEffect(() => {
158
165
  if (!state.data || !containerRef.current) return;
159
- const context = {
160
- props,
161
- state: {},
162
- context: {}
166
+ const doRender = async () => {
167
+ if (waitForStyles) {
168
+ await waitForTailwind();
169
+ setState((prev) => ({ ...prev, stylesReady: true }));
170
+ }
171
+ const context = {
172
+ props,
173
+ state: {},
174
+ context: {}
175
+ };
176
+ if (renderResultRef.current) {
177
+ renderResultRef.current.update(context);
178
+ return;
179
+ }
180
+ renderResultRef.current = render({
181
+ container: containerRef.current,
182
+ elements: state.data.layout,
183
+ context,
184
+ eventHandlers,
185
+ views: state.views,
186
+ componentRegistry: state.registry
187
+ });
188
+ if (containerRef.current) {
189
+ markElementReady(containerRef.current);
190
+ }
191
+ setIsRendered(true);
163
192
  };
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);
193
+ doRender();
177
194
  return () => {
178
195
  if (renderResultRef.current) {
179
196
  renderResultRef.current.destroy();
@@ -181,7 +198,7 @@ function ServlyComponent({
181
198
  setIsRendered(false);
182
199
  }
183
200
  };
184
- }, [state.data, eventHandlers]);
201
+ }, [state.data, eventHandlers, waitForStyles]);
185
202
  useEffect(() => {
186
203
  if (!renderResultRef.current || !state.data) return;
187
204
  const context = {
@@ -191,7 +208,7 @@ function ServlyComponent({
191
208
  };
192
209
  renderResultRef.current.update(context);
193
210
  }, [props, state.data]);
194
- if (state.loading) {
211
+ if (state.loading || waitForStyles && !state.stylesReady) {
195
212
  if (fallback) return /* @__PURE__ */ jsx(Fragment, { children: fallback });
196
213
  if (showSkeleton) return /* @__PURE__ */ jsx(LoadingSkeleton, { className });
197
214
  return null;
@@ -213,7 +230,7 @@ function ServlyComponent({
213
230
  "div",
214
231
  {
215
232
  ref: containerRef,
216
- className: `servly-component ${className || ""}`,
233
+ className: `servly-component servly-ready ${className || ""}`,
217
234
  style,
218
235
  "data-servly-id": id,
219
236
  "data-servly-version": state.data?.version
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@servlyadmin/runtime-react",
3
- "version": "0.1.31",
3
+ "version": "0.1.33",
4
4
  "description": "React wrapper for Servly runtime renderer",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -36,7 +36,7 @@
36
36
  "react-dom": ">=17.0.0"
37
37
  },
38
38
  "dependencies": {
39
- "@servlyadmin/runtime-core": "^0.1.31"
39
+ "@servlyadmin/runtime-core": "^0.1.43"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@types/react": "^18.2.0",
@@ -46,4 +46,4 @@
46
46
  "tsup": "^8.0.0",
47
47
  "typescript": "^5.3.0"
48
48
  }
49
- }
49
+ }