@servlyadmin/runtime-react
Advanced tools
| import React from 'react'; | ||
| import { CacheStrategy, RetryConfig } from '@servlyadmin/runtime-core'; | ||
| export { BindingContext, CacheStrategy, ComponentData, FetchOptions, LayoutElement, PropDefinition, RetryConfig, clearAllCaches, compareVersions, fetchComponent, getRegistryUrl, invalidateCache, isComponentAvailable, parseVersion, prefetchComponents, resolveVersion, satisfiesVersion, setRegistryUrl } from '@servlyadmin/runtime-core'; | ||
| /** | ||
| * ServlyComponent | ||
| * React wrapper for Servly runtime renderer with slot support | ||
| */ | ||
| /** | ||
| * Slot content type - React nodes keyed by slot name | ||
| */ | ||
| type SlotContent = Record<string, React.ReactNode>; | ||
| /** | ||
| * Props for ServlyComponent | ||
| */ | ||
| interface ServlyComponentProps<P = Record<string, any>> { | ||
| /** Component ID from the registry */ | ||
| id: string; | ||
| /** Version specifier (exact, range, or "latest") */ | ||
| version?: string; | ||
| /** Props to pass to the component */ | ||
| props?: P; | ||
| /** Slot content - React nodes to portal into named slots */ | ||
| slots?: SlotContent; | ||
| /** Fallback UI while loading or on error */ | ||
| fallback?: React.ReactNode; | ||
| /** Error callback */ | ||
| onError?: (error: Error) => void; | ||
| /** Load complete callback */ | ||
| onLoad?: () => void; | ||
| /** Custom className for wrapper */ | ||
| className?: string; | ||
| /** Custom styles for wrapper */ | ||
| style?: React.CSSProperties; | ||
| /** Show loading skeleton */ | ||
| showSkeleton?: boolean; | ||
| /** Cache strategy */ | ||
| cacheStrategy?: CacheStrategy; | ||
| /** Retry configuration */ | ||
| retryConfig?: Partial<RetryConfig>; | ||
| /** Event handlers keyed by element ID then event name */ | ||
| eventHandlers?: Record<string, Record<string, (e: Event) => void>>; | ||
| /** Children - rendered into default slot if no slots prop */ | ||
| children?: React.ReactNode; | ||
| /** Wait for Tailwind CSS to load before showing component (prevents FOUC) */ | ||
| waitForStyles?: boolean; | ||
| } | ||
| /** | ||
| * ServlyComponent - React wrapper for Servly runtime with slot support | ||
| */ | ||
| 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; | ||
| export { ServlyComponent, type ServlyComponentProps, ServlyComponent as default }; |
| import React from 'react'; | ||
| import { CacheStrategy, RetryConfig } from '@servlyadmin/runtime-core'; | ||
| export { BindingContext, CacheStrategy, ComponentData, FetchOptions, LayoutElement, PropDefinition, RetryConfig, clearAllCaches, compareVersions, fetchComponent, getRegistryUrl, invalidateCache, isComponentAvailable, parseVersion, prefetchComponents, resolveVersion, satisfiesVersion, setRegistryUrl } from '@servlyadmin/runtime-core'; | ||
| /** | ||
| * ServlyComponent | ||
| * React wrapper for Servly runtime renderer with slot support | ||
| */ | ||
| /** | ||
| * Slot content type - React nodes keyed by slot name | ||
| */ | ||
| type SlotContent = Record<string, React.ReactNode>; | ||
| /** | ||
| * Props for ServlyComponent | ||
| */ | ||
| interface ServlyComponentProps<P = Record<string, any>> { | ||
| /** Component ID from the registry */ | ||
| id: string; | ||
| /** Version specifier (exact, range, or "latest") */ | ||
| version?: string; | ||
| /** Props to pass to the component */ | ||
| props?: P; | ||
| /** Slot content - React nodes to portal into named slots */ | ||
| slots?: SlotContent; | ||
| /** Fallback UI while loading or on error */ | ||
| fallback?: React.ReactNode; | ||
| /** Error callback */ | ||
| onError?: (error: Error) => void; | ||
| /** Load complete callback */ | ||
| onLoad?: () => void; | ||
| /** Custom className for wrapper */ | ||
| className?: string; | ||
| /** Custom styles for wrapper */ | ||
| style?: React.CSSProperties; | ||
| /** Show loading skeleton */ | ||
| showSkeleton?: boolean; | ||
| /** Cache strategy */ | ||
| cacheStrategy?: CacheStrategy; | ||
| /** Retry configuration */ | ||
| retryConfig?: Partial<RetryConfig>; | ||
| /** Event handlers keyed by element ID then event name */ | ||
| eventHandlers?: Record<string, Record<string, (e: Event) => void>>; | ||
| /** Children - rendered into default slot if no slots prop */ | ||
| children?: React.ReactNode; | ||
| /** Wait for Tailwind CSS to load before showing component (prevents FOUC) */ | ||
| waitForStyles?: boolean; | ||
| } | ||
| /** | ||
| * ServlyComponent - React wrapper for Servly runtime with slot support | ||
| */ | ||
| 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; | ||
| export { ServlyComponent, type ServlyComponentProps, ServlyComponent as default }; |
+41
-26
@@ -129,3 +129,4 @@ "use strict"; | ||
| eventHandlers, | ||
| children | ||
| children, | ||
| waitForStyles = true | ||
| }) { | ||
@@ -139,3 +140,5 @@ const containerRef = (0, import_react.useRef)(null); | ||
| error: null, | ||
| data: null | ||
| data: null, | ||
| stylesReady: !waitForStyles | ||
| // If not waiting, styles are "ready" | ||
| }); | ||
@@ -165,3 +168,4 @@ const slotElements = useSlotElements(containerRef, isRendered); | ||
| const result = await (0, import_runtime_core.fetchComponent)(id, fetchOptions); | ||
| setState({ | ||
| setState((prev) => ({ | ||
| ...prev, | ||
| loading: false, | ||
@@ -172,3 +176,3 @@ error: null, | ||
| registry: result.registry | ||
| }); | ||
| })); | ||
| onLoad?.(); | ||
@@ -178,3 +182,4 @@ } catch (error) { | ||
| if (err.message === "Fetch aborted") return; | ||
| setState({ | ||
| setState((prev) => ({ | ||
| ...prev, | ||
| loading: false, | ||
@@ -185,3 +190,3 @@ error: err, | ||
| registry: void 0 | ||
| }); | ||
| })); | ||
| onError?.(err); | ||
@@ -200,20 +205,30 @@ } | ||
| if (!state.data || !containerRef.current) return; | ||
| const context = { | ||
| props, | ||
| state: {}, | ||
| context: {} | ||
| const doRender = async () => { | ||
| if (waitForStyles) { | ||
| await (0, import_runtime_core.waitForTailwind)(); | ||
| setState((prev) => ({ ...prev, stylesReady: true })); | ||
| } | ||
| const context = { | ||
| props, | ||
| state: {}, | ||
| context: {} | ||
| }; | ||
| if (renderResultRef.current) { | ||
| renderResultRef.current.update(context); | ||
| return; | ||
| } | ||
| renderResultRef.current = (0, import_runtime_core.render)({ | ||
| container: containerRef.current, | ||
| elements: state.data.layout, | ||
| context, | ||
| eventHandlers, | ||
| views: state.views, | ||
| componentRegistry: state.registry | ||
| }); | ||
| if (containerRef.current) { | ||
| (0, import_runtime_core.markElementReady)(containerRef.current); | ||
| } | ||
| setIsRendered(true); | ||
| }; | ||
| if (renderResultRef.current) { | ||
| renderResultRef.current.update(context); | ||
| return; | ||
| } | ||
| renderResultRef.current = (0, import_runtime_core.render)({ | ||
| container: containerRef.current, | ||
| elements: state.data.layout, | ||
| context, | ||
| eventHandlers, | ||
| views: state.views, | ||
| componentRegistry: state.registry | ||
| }); | ||
| setIsRendered(true); | ||
| doRender(); | ||
| return () => { | ||
@@ -226,3 +241,3 @@ if (renderResultRef.current) { | ||
| }; | ||
| }, [state.data, eventHandlers]); | ||
| }, [state.data, eventHandlers, waitForStyles]); | ||
| (0, import_react.useEffect)(() => { | ||
@@ -237,3 +252,3 @@ if (!renderResultRef.current || !state.data) return; | ||
| }, [props, state.data]); | ||
| if (state.loading) { | ||
| if (state.loading || waitForStyles && !state.stylesReady) { | ||
| if (fallback) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: fallback }); | ||
@@ -260,3 +275,3 @@ if (showSkeleton) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LoadingSkeleton, { className }); | ||
| ref: containerRef, | ||
| className: `servly-component ${className || ""}`, | ||
| className: `servly-component servly-ready ${className || ""}`, | ||
| style, | ||
@@ -263,0 +278,0 @@ "data-servly-id": id, |
+44
-27
@@ -6,3 +6,5 @@ // src/ServlyComponent.tsx | ||
| render, | ||
| fetchComponent | ||
| fetchComponent, | ||
| waitForTailwind, | ||
| markElementReady | ||
| } from "@servlyadmin/runtime-core"; | ||
@@ -95,3 +97,4 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime"; | ||
| eventHandlers, | ||
| children | ||
| children, | ||
| waitForStyles = true | ||
| }) { | ||
@@ -105,3 +108,5 @@ const containerRef = useRef(null); | ||
| error: null, | ||
| data: null | ||
| data: null, | ||
| stylesReady: !waitForStyles | ||
| // If not waiting, styles are "ready" | ||
| }); | ||
@@ -131,3 +136,4 @@ const slotElements = useSlotElements(containerRef, isRendered); | ||
| const result = await fetchComponent(id, fetchOptions); | ||
| setState({ | ||
| setState((prev) => ({ | ||
| ...prev, | ||
| loading: false, | ||
@@ -138,3 +144,3 @@ error: null, | ||
| registry: result.registry | ||
| }); | ||
| })); | ||
| onLoad?.(); | ||
@@ -144,3 +150,4 @@ } catch (error) { | ||
| if (err.message === "Fetch aborted") return; | ||
| setState({ | ||
| setState((prev) => ({ | ||
| ...prev, | ||
| loading: false, | ||
@@ -151,3 +158,3 @@ error: err, | ||
| registry: void 0 | ||
| }); | ||
| })); | ||
| onError?.(err); | ||
@@ -166,20 +173,30 @@ } | ||
| if (!state.data || !containerRef.current) return; | ||
| const context = { | ||
| props, | ||
| state: {}, | ||
| context: {} | ||
| const doRender = async () => { | ||
| if (waitForStyles) { | ||
| await waitForTailwind(); | ||
| setState((prev) => ({ ...prev, stylesReady: true })); | ||
| } | ||
| const context = { | ||
| props, | ||
| state: {}, | ||
| context: {} | ||
| }; | ||
| if (renderResultRef.current) { | ||
| renderResultRef.current.update(context); | ||
| return; | ||
| } | ||
| renderResultRef.current = render({ | ||
| container: containerRef.current, | ||
| elements: state.data.layout, | ||
| context, | ||
| eventHandlers, | ||
| views: state.views, | ||
| componentRegistry: state.registry | ||
| }); | ||
| if (containerRef.current) { | ||
| markElementReady(containerRef.current); | ||
| } | ||
| setIsRendered(true); | ||
| }; | ||
| if (renderResultRef.current) { | ||
| renderResultRef.current.update(context); | ||
| return; | ||
| } | ||
| renderResultRef.current = render({ | ||
| container: containerRef.current, | ||
| elements: state.data.layout, | ||
| context, | ||
| eventHandlers, | ||
| views: state.views, | ||
| componentRegistry: state.registry | ||
| }); | ||
| setIsRendered(true); | ||
| doRender(); | ||
| return () => { | ||
@@ -192,3 +209,3 @@ if (renderResultRef.current) { | ||
| }; | ||
| }, [state.data, eventHandlers]); | ||
| }, [state.data, eventHandlers, waitForStyles]); | ||
| useEffect(() => { | ||
@@ -203,3 +220,3 @@ if (!renderResultRef.current || !state.data) return; | ||
| }, [props, state.data]); | ||
| if (state.loading) { | ||
| if (state.loading || waitForStyles && !state.stylesReady) { | ||
| if (fallback) return /* @__PURE__ */ jsx(Fragment, { children: fallback }); | ||
@@ -226,3 +243,3 @@ if (showSkeleton) return /* @__PURE__ */ jsx(LoadingSkeleton, { className }); | ||
| ref: containerRef, | ||
| className: `servly-component ${className || ""}`, | ||
| className: `servly-component servly-ready ${className || ""}`, | ||
| style, | ||
@@ -229,0 +246,0 @@ "data-servly-id": id, |
+3
-3
| { | ||
| "name": "@servlyadmin/runtime-react", | ||
| "version": "0.1.31", | ||
| "version": "0.1.33", | ||
| "description": "React wrapper for Servly runtime renderer", | ||
@@ -39,3 +39,3 @@ "type": "module", | ||
| "dependencies": { | ||
| "@servlyadmin/runtime-core": "^0.1.31" | ||
| "@servlyadmin/runtime-core": "^0.1.43" | ||
| }, | ||
@@ -50,2 +50,2 @@ "devDependencies": { | ||
| } | ||
| } | ||
| } |
29270
24.86%6
50%621
15.64%